From d3f2b4a23f2068bbbc4d4afa592637fe909900ae Mon Sep 17 00:00:00 2001 From: Abdelilah El Aissaoui Date: Wed, 24 Aug 2022 05:17:18 +0200 Subject: [PATCH] Removed diff manager in favor of use cases Also simplified hunk generator and raw file manager --- .../kotlin/app/di/RawFileManagerFactory.kt | 16 -- src/main/kotlin/app/git/DiffManager.kt | 148 ------------------ src/main/kotlin/app/git/RawFileManager.kt | 66 ++++---- src/main/kotlin/app/git/StatusManager.kt | 24 ++- .../kotlin/app/git/diff/FormatDiffUseCase.kt | 64 ++++++++ .../git/diff/GetCommitDiffEntriesUseCase.kt | 51 ++++++ .../GetDiffEntryForUncommitedDiffUseCase.kt | 44 ++++++ .../kotlin/app/git/diff/HunkDiffGenerator.kt | 65 ++++---- .../repository/GetRepositoryStateUseCase.kt | 14 ++ .../kotlin/app/preferences/AppSettings.kt | 4 +- src/main/kotlin/app/ui/diff/Diff.kt | 1 + .../app/viewmodels/CommitChangesViewModel.kt | 6 +- .../kotlin/app/viewmodels/DiffViewModel.kt | 21 ++- .../kotlin/app/viewmodels/HistoryViewModel.kt | 9 +- .../kotlin/app/viewmodels/ViewDiffResult.kt | 3 +- .../diff/GetCommitDiffEntriesUseCaseTest.kt | 12 ++ 16 files changed, 298 insertions(+), 250 deletions(-) delete mode 100644 src/main/kotlin/app/di/RawFileManagerFactory.kt delete mode 100644 src/main/kotlin/app/git/DiffManager.kt create mode 100644 src/main/kotlin/app/git/diff/FormatDiffUseCase.kt create mode 100644 src/main/kotlin/app/git/diff/GetCommitDiffEntriesUseCase.kt create mode 100644 src/main/kotlin/app/git/diff/GetDiffEntryForUncommitedDiffUseCase.kt create mode 100644 src/main/kotlin/app/git/repository/GetRepositoryStateUseCase.kt create mode 100644 src/test/kotlin/app/git/diff/GetCommitDiffEntriesUseCaseTest.kt diff --git a/src/main/kotlin/app/di/RawFileManagerFactory.kt b/src/main/kotlin/app/di/RawFileManagerFactory.kt deleted file mode 100644 index 5d54d2e..0000000 --- a/src/main/kotlin/app/di/RawFileManagerFactory.kt +++ /dev/null @@ -1,16 +0,0 @@ -package app.di - -import app.git.RawFileManager -import app.git.diff.HunkDiffGenerator -import dagger.assisted.AssistedFactory -import org.eclipse.jgit.lib.Repository - -@AssistedFactory -interface RawFileManagerFactory { - fun create(repository: Repository): RawFileManager -} - -@AssistedFactory -interface HunkDiffGeneratorFactory { - fun create(repository: Repository, rawFileManager: RawFileManager): HunkDiffGenerator -} \ No newline at end of file diff --git a/src/main/kotlin/app/git/DiffManager.kt b/src/main/kotlin/app/git/DiffManager.kt deleted file mode 100644 index b7a5fc6..0000000 --- a/src/main/kotlin/app/git/DiffManager.kt +++ /dev/null @@ -1,148 +0,0 @@ -package app.git - -import app.di.HunkDiffGeneratorFactory -import app.di.RawFileManagerFactory -import app.exceptions.MissingDiffEntryException -import app.extensions.fullData -import app.extensions.isMerging -import app.git.branches.GetCurrentBranchUseCase -import app.git.diff.DiffResult -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.eclipse.jgit.api.Git -import org.eclipse.jgit.diff.DiffEntry -import org.eclipse.jgit.diff.DiffFormatter -import org.eclipse.jgit.dircache.DirCacheIterator -import org.eclipse.jgit.lib.Repository -import org.eclipse.jgit.revwalk.RevCommit -import org.eclipse.jgit.revwalk.RevTree -import org.eclipse.jgit.revwalk.RevWalk -import org.eclipse.jgit.treewalk.AbstractTreeIterator -import org.eclipse.jgit.treewalk.CanonicalTreeParser -import org.eclipse.jgit.treewalk.EmptyTreeIterator -import org.eclipse.jgit.treewalk.FileTreeIterator -import org.eclipse.jgit.treewalk.filter.PathFilter -import java.io.ByteArrayOutputStream -import javax.inject.Inject - - -class DiffManager @Inject constructor( - private val rawFileManagerFactory: RawFileManagerFactory, - private val hunkDiffGeneratorFactory: HunkDiffGeneratorFactory, - private val getCurrentBranchUseCase: GetCurrentBranchUseCase, - private val repositoryManager: RepositoryManager, -) { - suspend fun diffFormat(git: Git, diffEntryType: DiffEntryType): DiffResult = withContext(Dispatchers.IO) { - val byteArrayOutputStream = ByteArrayOutputStream() - val repository = git.repository - val diffEntry: DiffEntry - - DiffFormatter(byteArrayOutputStream).use { formatter -> - formatter.setRepository(repository) - - val oldTree = DirCacheIterator(repository.readDirCache()) - val newTree = FileTreeIterator(repository) - - if (diffEntryType is DiffEntryType.UnstagedDiff) - formatter.scan(oldTree, newTree) - - diffEntry = when (diffEntryType) { - is DiffEntryType.CommitDiff -> { - diffEntryType.diffEntry - } - is DiffEntryType.UncommitedDiff -> { - val statusEntry = diffEntryType.statusEntry - val cached = diffEntryType is DiffEntryType.StagedDiff - val firstDiffEntry = git.diff() - .setPathFilter(PathFilter.create(statusEntry.filePath)) - .setCached(cached).apply { - val repositoryState = repositoryManager.getRepositoryState(git) - if ( - getCurrentBranchUseCase(git) == null && - !repositoryState.isMerging && - !repositoryState.isRebasing && - cached - ) { - setOldTree(EmptyTreeIterator()) // Required if the repository is empty - } - } - .call() - .firstOrNull() - ?: throw MissingDiffEntryException("Diff entry not found") - - firstDiffEntry - } - } - - formatter.format(diffEntry) - - formatter.flush() - } - - val rawFileManager = rawFileManagerFactory.create(repository) - val hunkDiffGenerator = hunkDiffGeneratorFactory.create(repository, rawFileManager) - - var diffResult: DiffResult - - hunkDiffGenerator.use { - if (diffEntryType is DiffEntryType.UnstagedDiff) { - val oldTree = DirCacheIterator(repository.readDirCache()) - val newTree = FileTreeIterator(repository) - hunkDiffGenerator.scan(oldTree, newTree) - } - - diffResult = hunkDiffGenerator.format(diffEntry) - } - - return@withContext diffResult - } - - suspend fun commitDiffEntries(git: Git, commit: RevCommit): List = withContext(Dispatchers.IO) { - val fullCommit = commit.fullData(git.repository) ?: return@withContext emptyList() - - val repository = git.repository - - val parent = if (fullCommit.parentCount == 0) { - null - } else - fullCommit.parents.first().fullData(git.repository) - - val oldTreeParser = if (parent != null) - prepareTreeParser(repository, parent) - else { - CanonicalTreeParser() - } - - val newTreeParser = prepareTreeParser(repository, fullCommit) - - return@withContext git.diff() - .setNewTree(newTreeParser) - .setOldTree(oldTreeParser) - .call() - } -} - -fun prepareTreeParser(repository: Repository, commit: RevCommit): AbstractTreeIterator { - // from the commit we can build the tree which allows us to construct the TreeParser - RevWalk(repository).use { walk -> - val tree: RevTree = walk.parseTree(commit.tree.id) - val treeParser = CanonicalTreeParser() - repository.newObjectReader().use { reader -> treeParser.reset(reader, tree.id) } - - return treeParser - } -} - -enum class TextDiffType(val value: Int) { - SPLIT(0), - UNIFIED(1); -} - - -fun textDiffTypeFromValue(diffTypeValue: Int): TextDiffType { - return when (diffTypeValue) { - TextDiffType.SPLIT.value -> TextDiffType.SPLIT - TextDiffType.UNIFIED.value -> TextDiffType.UNIFIED - else -> throw NotImplementedError("Diff type not implemented") - } -} diff --git a/src/main/kotlin/app/git/RawFileManager.kt b/src/main/kotlin/app/git/RawFileManager.kt index c86a185..7e4075e 100644 --- a/src/main/kotlin/app/git/RawFileManager.kt +++ b/src/main/kotlin/app/git/RawFileManager.kt @@ -2,8 +2,6 @@ package app.git import app.TempFilesManager import app.extensions.systemSeparator -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import org.eclipse.jgit.diff.ContentSource import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.RawText @@ -15,18 +13,15 @@ import org.eclipse.jgit.treewalk.WorkingTreeIterator import org.eclipse.jgit.util.LfsFactory import java.io.FileOutputStream import java.nio.file.Path +import javax.inject.Inject import kotlin.io.path.createTempFile private const val DEFAULT_BINARY_FILE_THRESHOLD = PackConfig.DEFAULT_BIG_FILE_THRESHOLD -class RawFileManager @AssistedInject constructor( - @Assisted private val repository: Repository, +class RawFileManager @Inject constructor( private val tempFilesManager: TempFilesManager, -) : AutoCloseable { - private var reader: ObjectReader = repository.newObjectReader() - private var source: ContentSource.Pair - +) { private val imageFormatsSupported = listOf( "png", "jpg", @@ -34,38 +29,45 @@ class RawFileManager @AssistedInject constructor( "webp", ) - init { - val cs = ContentSource.create(reader) - source = ContentSource.Pair(cs, cs) - } - - fun scan(oldTreeIterator: AbstractTreeIterator, newTreeIterator: AbstractTreeIterator) { - source = ContentSource.Pair(source(oldTreeIterator), source(newTreeIterator)) - } - - private fun source(iterator: AbstractTreeIterator): ContentSource { + private fun source(iterator: AbstractTreeIterator, reader: ObjectReader): ContentSource { return if (iterator is WorkingTreeIterator) ContentSource.create(iterator) else ContentSource.create(reader) } - fun getRawContent(side: DiffEntry.Side, entry: DiffEntry): EntryContent { + fun getRawContent( + repository: Repository, + side: DiffEntry.Side, + entry: DiffEntry, + oldTreeIterator: AbstractTreeIterator?, + newTreeIterator: AbstractTreeIterator?, + ): EntryContent { if (entry.getMode(side) === FileMode.MISSING) return EntryContent.Missing if (entry.getMode(side).objectType != Constants.OBJ_BLOB) return EntryContent.InvalidObjectBlob - val ldr = LfsFactory.getInstance().applySmudgeFilter( - repository, - source.open(side, entry), entry.diffAttribute - ) + val reader: ObjectReader = repository.newObjectReader() + reader.use { + val source: ContentSource.Pair = if (oldTreeIterator != null && newTreeIterator != null) { + ContentSource.Pair(source(oldTreeIterator, reader), source(newTreeIterator, reader)) + } else { + val cs = ContentSource.create(reader) + ContentSource.Pair(cs, cs) + } - return try { - EntryContent.Text(RawText.load(ldr, DEFAULT_BINARY_FILE_THRESHOLD)) - } catch (ex: BinaryBlobException) { - if (isImage(entry)) { - generateImageBinary(ldr, entry, side) - } else - EntryContent.Binary + val ldr = LfsFactory.getInstance().applySmudgeFilter( + repository, + source.open(side, entry), entry.diffAttribute + ) + + return try { + EntryContent.Text(RawText.load(ldr, DEFAULT_BINARY_FILE_THRESHOLD)) + } catch (ex: BinaryBlobException) { + if (isImage(entry)) { + generateImageBinary(ldr, entry, side) + } else + EntryContent.Binary + } } } @@ -94,10 +96,6 @@ class RawFileManager @AssistedInject constructor( return imageFormatsSupported.contains(fileExtension.lowercase()) } - - override fun close() { - reader.close() - } } sealed class EntryContent { diff --git a/src/main/kotlin/app/git/StatusManager.kt b/src/main/kotlin/app/git/StatusManager.kt index 0913124..9357836 100644 --- a/src/main/kotlin/app/git/StatusManager.kt +++ b/src/main/kotlin/app/git/StatusManager.kt @@ -3,7 +3,6 @@ package app.git import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import app.di.RawFileManagerFactory import app.extensions.* import app.git.diff.Hunk import app.git.diff.LineType @@ -29,8 +28,7 @@ import javax.inject.Inject class StatusManager @Inject constructor( - private val rawFileManagerFactory: RawFileManagerFactory, - private val submodulesManager: SubmodulesManager, + private val rawFileManager: RawFileManager, ) { suspend fun hasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) { val status = git @@ -54,8 +52,13 @@ class StatusManager @Inject constructor( var completedWithErrors = true try { - val rawFileManager = rawFileManagerFactory.create(git.repository) - val entryContent = rawFileManager.getRawContent(DiffEntry.Side.OLD, diffEntry) + val entryContent = rawFileManager.getRawContent( + repository = git.repository, + side = DiffEntry.Side.OLD, + entry = diffEntry, + oldTreeIterator = null, + newTreeIterator = null + ) if (entryContent !is EntryContent.Text) return@withContext @@ -71,10 +74,12 @@ class StatusManager @Inject constructor( textLines.add(line.oldLineNumber + linesAdded, line.text) linesAdded++ } + LineType.REMOVED -> { textLines.removeAt(line.oldLineNumber + linesAdded) linesAdded-- } + else -> throw NotImplementedError("Line type not implemented for stage hunk") } } @@ -97,8 +102,13 @@ class StatusManager @Inject constructor( var completedWithErrors = true try { - val rawFileManager = rawFileManagerFactory.create(repository) - val entryContent = rawFileManager.getRawContent(DiffEntry.Side.NEW, diffEntry) + val entryContent = rawFileManager.getRawContent( + repository = repository, + side = DiffEntry.Side.NEW, + entry = diffEntry, + oldTreeIterator = null, + newTreeIterator = null + ) if (entryContent !is EntryContent.Text) return@withContext diff --git a/src/main/kotlin/app/git/diff/FormatDiffUseCase.kt b/src/main/kotlin/app/git/diff/FormatDiffUseCase.kt new file mode 100644 index 0000000..322e5fc --- /dev/null +++ b/src/main/kotlin/app/git/diff/FormatDiffUseCase.kt @@ -0,0 +1,64 @@ +package app.git.diff + +import app.git.DiffEntryType +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.diff.DiffEntry +import org.eclipse.jgit.diff.DiffFormatter +import org.eclipse.jgit.dircache.DirCacheIterator +import org.eclipse.jgit.treewalk.FileTreeIterator +import java.io.ByteArrayOutputStream +import javax.inject.Inject + +class FormatDiffUseCase @Inject constructor( + private val hunkDiffGenerator: HunkDiffGenerator, + private val getDiffEntryForUncommitedDiffUseCase: GetDiffEntryForUncommitedDiffUseCase, +) { + suspend operator fun invoke(git: Git, diffEntryType: DiffEntryType): DiffResult = withContext(Dispatchers.IO) { + val byteArrayOutputStream = ByteArrayOutputStream() + val repository = git.repository + val diffEntry: DiffEntry + + DiffFormatter(byteArrayOutputStream).use { formatter -> + formatter.setRepository(repository) + + val oldTree = DirCacheIterator(repository.readDirCache()) + val newTree = FileTreeIterator(repository) + + if (diffEntryType is DiffEntryType.UnstagedDiff) + formatter.scan(oldTree, newTree) + + diffEntry = when (diffEntryType) { + is DiffEntryType.CommitDiff -> { + diffEntryType.diffEntry + } + + is DiffEntryType.UncommitedDiff -> { + getDiffEntryForUncommitedDiffUseCase(git, diffEntryType) + } + } + + formatter.format(diffEntry) + formatter.flush() + } + + val oldTree: DirCacheIterator? + val newTree: FileTreeIterator? + + if (diffEntryType is DiffEntryType.UnstagedDiff) { + oldTree = DirCacheIterator(repository.readDirCache()) + newTree = FileTreeIterator(repository) + } else { + oldTree = null + newTree = null + } + + return@withContext hunkDiffGenerator.format( + repository, + diffEntry, + oldTree, + newTree, + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/diff/GetCommitDiffEntriesUseCase.kt b/src/main/kotlin/app/git/diff/GetCommitDiffEntriesUseCase.kt new file mode 100644 index 0000000..551d99c --- /dev/null +++ b/src/main/kotlin/app/git/diff/GetCommitDiffEntriesUseCase.kt @@ -0,0 +1,51 @@ +package app.git.diff + +import app.extensions.fullData +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.diff.DiffEntry +import org.eclipse.jgit.lib.Repository +import org.eclipse.jgit.revwalk.RevCommit +import org.eclipse.jgit.revwalk.RevTree +import org.eclipse.jgit.revwalk.RevWalk +import org.eclipse.jgit.treewalk.AbstractTreeIterator +import org.eclipse.jgit.treewalk.CanonicalTreeParser +import javax.inject.Inject + +class GetCommitDiffEntriesUseCase @Inject constructor() { + suspend operator fun invoke(git: Git, commit: RevCommit): List = withContext(Dispatchers.IO) { + val fullCommit = commit.fullData(git.repository) ?: return@withContext emptyList() + + val repository = git.repository + + val parent = if (fullCommit.parentCount == 0) { + null + } else + fullCommit.parents.first().fullData(git.repository) + + val oldTreeParser = if (parent != null) + prepareTreeParser(repository, parent) + else { + CanonicalTreeParser() + } + + val newTreeParser = prepareTreeParser(repository, fullCommit) + + return@withContext git.diff() + .setNewTree(newTreeParser) + .setOldTree(oldTreeParser) + .call() + } +} + +private fun prepareTreeParser(repository: Repository, commit: RevCommit): AbstractTreeIterator { + // from the commit we can build the tree which allows us to construct the TreeParser + RevWalk(repository).use { walk -> + val tree: RevTree = walk.parseTree(commit.tree.id) + val treeParser = CanonicalTreeParser() + repository.newObjectReader().use { reader -> treeParser.reset(reader, tree.id) } + + return treeParser + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/diff/GetDiffEntryForUncommitedDiffUseCase.kt b/src/main/kotlin/app/git/diff/GetDiffEntryForUncommitedDiffUseCase.kt new file mode 100644 index 0000000..cb4130d --- /dev/null +++ b/src/main/kotlin/app/git/diff/GetDiffEntryForUncommitedDiffUseCase.kt @@ -0,0 +1,44 @@ +package app.git.diff + +import app.exceptions.MissingDiffEntryException +import app.extensions.isMerging +import app.git.DiffEntryType +import app.git.branches.GetCurrentBranchUseCase +import app.git.repository.GetRepositoryStateUseCase +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.treewalk.EmptyTreeIterator +import org.eclipse.jgit.treewalk.filter.PathFilter +import javax.inject.Inject + +class GetDiffEntryForUncommitedDiffUseCase @Inject constructor( + private val getRepositoryStateUseCase: GetRepositoryStateUseCase, + private val getCurrentBranchUseCase: GetCurrentBranchUseCase, +) { + suspend operator fun invoke( + git: Git, + diffEntryType: DiffEntryType.UncommitedDiff, + ) = withContext(Dispatchers.IO) { + val statusEntry = diffEntryType.statusEntry + val cached = diffEntryType is DiffEntryType.StagedDiff + val firstDiffEntry = git.diff() + .setPathFilter(PathFilter.create(statusEntry.filePath)) + .setCached(cached).apply { + val repositoryState = getRepositoryStateUseCase(git) + if ( + getCurrentBranchUseCase(git) == null && + !repositoryState.isMerging && + !repositoryState.isRebasing && + cached + ) { + setOldTree(EmptyTreeIterator()) // Required if the repository is empty + } + } + .call() + .firstOrNull() + ?: throw MissingDiffEntryException("Diff entry not found") + + return@withContext firstDiffEntry + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/diff/HunkDiffGenerator.kt b/src/main/kotlin/app/git/diff/HunkDiffGenerator.kt index 3996b17..b27d5d7 100644 --- a/src/main/kotlin/app/git/diff/HunkDiffGenerator.kt +++ b/src/main/kotlin/app/git/diff/HunkDiffGenerator.kt @@ -3,7 +3,6 @@ package app.git.diff import app.extensions.lineAt import app.git.EntryContent import app.git.RawFileManager -import dagger.assisted.Assisted import dagger.assisted.AssistedInject import org.eclipse.jgit.diff.* import org.eclipse.jgit.lib.Repository @@ -13,6 +12,7 @@ import org.eclipse.jgit.treewalk.AbstractTreeIterator import java.io.ByteArrayOutputStream import java.io.IOException import java.io.InvalidObjectException +import javax.inject.Inject import kotlin.contracts.ExperimentalContracts import kotlin.math.max import kotlin.math.min @@ -22,46 +22,46 @@ private const val CONTEXT_LINES = 3 /** * Generator of [Hunk] lists from [DiffEntry] */ -class HunkDiffGenerator @AssistedInject constructor( - @Assisted private val repository: Repository, - @Assisted private val rawFileManager: RawFileManager, -) : AutoCloseable { +class HunkDiffGenerator @Inject constructor( + private val rawFileManager: RawFileManager, +) { + fun format( + repository: Repository, + diffEntry: DiffEntry, + oldTreeIterator: AbstractTreeIterator?, + newTreeIterator: AbstractTreeIterator?, + ): DiffResult { + val outputStream = ByteArrayOutputStream() // Dummy output stream used for the diff formatter + return outputStream.use { + val diffFormatter = DiffFormatter(outputStream).apply { + setRepository(repository) + } - private val outputStream = ByteArrayOutputStream() // Dummy output stream used for the diff formatter - private val diffFormatter = DiffFormatter(outputStream).apply { - setRepository(repository) - } + if (oldTreeIterator != null && newTreeIterator != null) { + diffFormatter.scan(oldTreeIterator, newTreeIterator) + } - override fun close() { - outputStream.close() - } + val fileHeader = diffFormatter.toFileHeader(diffEntry) - fun scan(oldTreeIterator: AbstractTreeIterator, newTreeIterator: AbstractTreeIterator) { - rawFileManager.scan(oldTreeIterator, newTreeIterator) - diffFormatter.scan(oldTreeIterator, newTreeIterator) - } + val rawOld = rawFileManager.getRawContent(repository, DiffEntry.Side.OLD, diffEntry, oldTreeIterator, newTreeIterator) + val rawNew = rawFileManager.getRawContent(repository, DiffEntry.Side.NEW, diffEntry, oldTreeIterator, newTreeIterator) - fun format(ent: DiffEntry): DiffResult { - val fileHeader = diffFormatter.toFileHeader(ent) + if (rawOld == EntryContent.InvalidObjectBlob || rawNew == EntryContent.InvalidObjectBlob) + throw InvalidObjectException("Invalid object in diff format") - val rawOld = rawFileManager.getRawContent(DiffEntry.Side.OLD, ent) - val rawNew = rawFileManager.getRawContent(DiffEntry.Side.NEW, ent) + var diffResult: DiffResult = DiffResult.Text(diffEntry, emptyList()) - if (rawOld == EntryContent.InvalidObjectBlob || rawNew == EntryContent.InvalidObjectBlob) - throw InvalidObjectException("Invalid object in diff format") + // If we can, generate text diff (if one of the files has never been a binary file) + val hasGeneratedTextDiff = canGenerateTextDiff(rawOld, rawNew) { oldRawText, newRawText -> + diffResult = DiffResult.Text(diffEntry, format(fileHeader, oldRawText, newRawText)) + } - var diffResult: DiffResult = DiffResult.Text(ent, emptyList()) + if (!hasGeneratedTextDiff) { + diffResult = DiffResult.NonText(diffEntry, rawOld, rawNew) + } - // If we can, generate text diff (if one of the files has never been a binary file) - val hasGeneratedTextDiff = canGenerateTextDiff(rawOld, rawNew) { oldRawText, newRawText -> - diffResult = DiffResult.Text(ent, format(fileHeader, oldRawText, newRawText)) + return@use diffResult } - - if (!hasGeneratedTextDiff) { - diffResult = DiffResult.NonText(ent, rawOld, rawNew) - } - - return diffResult } @OptIn(ExperimentalContracts::class) @@ -221,6 +221,7 @@ sealed class DiffResult( diffEntry: DiffEntry, val hunks: List ) : DiffResult(diffEntry) + class TextSplit( diffEntry: DiffEntry, val hunks: List diff --git a/src/main/kotlin/app/git/repository/GetRepositoryStateUseCase.kt b/src/main/kotlin/app/git/repository/GetRepositoryStateUseCase.kt new file mode 100644 index 0000000..84affcf --- /dev/null +++ b/src/main/kotlin/app/git/repository/GetRepositoryStateUseCase.kt @@ -0,0 +1,14 @@ +package app.git.repository + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.RepositoryState +import javax.inject.Inject + +class GetRepositoryStateUseCase @Inject constructor() { + suspend operator fun invoke(git: Git): RepositoryState = withContext(Dispatchers.IO) { + return@withContext git.repository.repositoryState + } + +} \ No newline at end of file diff --git a/src/main/kotlin/app/preferences/AppSettings.kt b/src/main/kotlin/app/preferences/AppSettings.kt index 2f59020..be992e5 100644 --- a/src/main/kotlin/app/preferences/AppSettings.kt +++ b/src/main/kotlin/app/preferences/AppSettings.kt @@ -1,8 +1,8 @@ package app.preferences import app.extensions.defaultWindowPlacement -import app.git.TextDiffType -import app.git.textDiffTypeFromValue +import app.viewmodels.TextDiffType +import app.viewmodels.textDiffTypeFromValue import app.theme.ColorsScheme import app.theme.Theme import kotlinx.coroutines.flow.MutableStateFlow diff --git a/src/main/kotlin/app/ui/diff/Diff.kt b/src/main/kotlin/app/ui/diff/Diff.kt index ac5c51d..22593fb 100644 --- a/src/main/kotlin/app/ui/diff/Diff.kt +++ b/src/main/kotlin/app/ui/diff/Diff.kt @@ -42,6 +42,7 @@ import app.theme.* import app.ui.components.ScrollableLazyColumn import app.ui.components.SecondaryButton import app.viewmodels.DiffViewModel +import app.viewmodels.TextDiffType import app.viewmodels.ViewDiffResult import org.eclipse.jgit.diff.DiffEntry import java.io.FileInputStream diff --git a/src/main/kotlin/app/viewmodels/CommitChangesViewModel.kt b/src/main/kotlin/app/viewmodels/CommitChangesViewModel.kt index d89ab89..82d5704 100644 --- a/src/main/kotlin/app/viewmodels/CommitChangesViewModel.kt +++ b/src/main/kotlin/app/viewmodels/CommitChangesViewModel.kt @@ -1,9 +1,9 @@ package app.viewmodels import app.extensions.delayedStateChange -import app.git.DiffManager import app.git.RefreshType import app.git.TabState +import app.git.diff.GetCommitDiffEntriesUseCase import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import org.eclipse.jgit.diff.DiffEntry @@ -14,7 +14,7 @@ private const val MIN_TIME_IN_MS_TO_SHOW_LOAD = 300L class CommitChangesViewModel @Inject constructor( private val tabState: TabState, - private val diffManager: DiffManager, + private val getCommitDiffEntriesUseCase: GetCommitDiffEntriesUseCase, ) { private val _commitChangesStatus = MutableStateFlow(CommitChangesStatus.Loading) val commitChangesStatus: StateFlow = _commitChangesStatus @@ -26,7 +26,7 @@ class CommitChangesViewModel @Inject constructor( delayMs = MIN_TIME_IN_MS_TO_SHOW_LOAD, onDelayTriggered = { _commitChangesStatus.value = CommitChangesStatus.Loading } ) { - val changes = diffManager.commitDiffEntries(git, commit) + val changes = getCommitDiffEntriesUseCase(git, commit) _commitChangesStatus.value = CommitChangesStatus.Loaded(commit, changes) } diff --git a/src/main/kotlin/app/viewmodels/DiffViewModel.kt b/src/main/kotlin/app/viewmodels/DiffViewModel.kt index a4849ff..43f716f 100644 --- a/src/main/kotlin/app/viewmodels/DiffViewModel.kt +++ b/src/main/kotlin/app/viewmodels/DiffViewModel.kt @@ -6,6 +6,7 @@ import app.exceptions.MissingDiffEntryException import app.extensions.delayedStateChange import app.git.* import app.git.diff.DiffResult +import app.git.diff.FormatDiffUseCase import app.git.diff.Hunk import app.preferences.AppSettings import app.git.diff.GenerateSplitHunkFromDiffResultUseCase @@ -20,7 +21,7 @@ private const val DIFF_MIN_TIME_IN_MS_TO_SHOW_LOAD = 200L class DiffViewModel @Inject constructor( private val tabState: TabState, - private val diffManager: DiffManager, + private val formatDiffUseCase: FormatDiffUseCase, private val statusManager: StatusManager, private val settings: AppSettings, private val generateSplitHunkFromDiffResultUseCase: GenerateSplitHunkFromDiffResultUseCase, @@ -84,7 +85,7 @@ class DiffViewModel @Inject constructor( delayMs = if (isFirstLoad) 0 else DIFF_MIN_TIME_IN_MS_TO_SHOW_LOAD, onDelayTriggered = { _diffResult.value = ViewDiffResult.Loading(diffEntryType.filePath) } ) { - val diffFormat = diffManager.diffFormat(git, diffEntryType) + val diffFormat = formatDiffUseCase(git, diffEntryType) val diffEntry = diffFormat.diffEntry if ( diffTypeFlow.value == TextDiffType.SPLIT && @@ -149,4 +150,18 @@ class DiffViewModel @Inject constructor( fun changeTextDiffType(newDiffType: TextDiffType) { settings.textDiffType = newDiffType } -} \ No newline at end of file +} + +enum class TextDiffType(val value: Int) { + SPLIT(0), + UNIFIED(1); +} + + +fun textDiffTypeFromValue(diffTypeValue: Int): TextDiffType { + return when (diffTypeValue) { + TextDiffType.SPLIT.value -> TextDiffType.SPLIT + TextDiffType.UNIFIED.value -> TextDiffType.UNIFIED + else -> throw NotImplementedError("Diff type not implemented") + } +} diff --git a/src/main/kotlin/app/viewmodels/HistoryViewModel.kt b/src/main/kotlin/app/viewmodels/HistoryViewModel.kt index 2bc9707..4c10c17 100644 --- a/src/main/kotlin/app/viewmodels/HistoryViewModel.kt +++ b/src/main/kotlin/app/viewmodels/HistoryViewModel.kt @@ -5,8 +5,10 @@ import app.exceptions.MissingDiffEntryException import app.extensions.filePath import app.git.* import app.git.diff.DiffResult +import app.git.diff.FormatDiffUseCase import app.preferences.AppSettings import app.git.diff.GenerateSplitHunkFromDiffResultUseCase +import app.git.diff.GetCommitDiffEntriesUseCase import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -15,7 +17,8 @@ import javax.inject.Inject class HistoryViewModel @Inject constructor( private val tabState: TabState, - private val diffManager: DiffManager, + private val formatDiffUseCase: FormatDiffUseCase, + private val getCommitDiffEntriesUseCase: GetCommitDiffEntriesUseCase, private val settings: AppSettings, private val generateSplitHunkFromDiffResultUseCase: GenerateSplitHunkFromDiffResultUseCase, ) { @@ -87,7 +90,7 @@ class HistoryViewModel @Inject constructor( ) { git -> try { - val diffEntries = diffManager.commitDiffEntries(git, commit) + val diffEntries = getCommitDiffEntriesUseCase(git, commit) val diffEntry = diffEntries.firstOrNull { entry -> entry.filePath == this.filePath } @@ -99,7 +102,7 @@ class HistoryViewModel @Inject constructor( val diffEntryType = DiffEntryType.CommitDiff(diffEntry) - val diffResult = diffManager.diffFormat(git, diffEntryType) + val diffResult = formatDiffUseCase(git, diffEntryType) val textDiffType = settings.textDiffType val formattedDiffResult = if (textDiffType == TextDiffType.SPLIT && diffResult is DiffResult.Text) { diff --git a/src/main/kotlin/app/viewmodels/ViewDiffResult.kt b/src/main/kotlin/app/viewmodels/ViewDiffResult.kt index b9eae9b..20a0520 100644 --- a/src/main/kotlin/app/viewmodels/ViewDiffResult.kt +++ b/src/main/kotlin/app/viewmodels/ViewDiffResult.kt @@ -1,7 +1,6 @@ package app.viewmodels import app.git.DiffEntryType -import app.git.TextDiffType import app.git.diff.DiffResult sealed interface ViewDiffResult { @@ -11,5 +10,5 @@ sealed interface ViewDiffResult { object DiffNotFound : ViewDiffResult - data class Loaded(val diffEntryType: DiffEntryType, val diffResult: DiffResult/*, val diffType: TextDiffType*/) : ViewDiffResult + data class Loaded(val diffEntryType: DiffEntryType, val diffResult: DiffResult) : ViewDiffResult } \ No newline at end of file diff --git a/src/test/kotlin/app/git/diff/GetCommitDiffEntriesUseCaseTest.kt b/src/test/kotlin/app/git/diff/GetCommitDiffEntriesUseCaseTest.kt new file mode 100644 index 0000000..5ba0cc3 --- /dev/null +++ b/src/test/kotlin/app/git/diff/GetCommitDiffEntriesUseCaseTest.kt @@ -0,0 +1,12 @@ +package app.git.diff + +import org.junit.jupiter.api.BeforeEach + +import org.junit.jupiter.api.Assertions.* + +internal class GetCommitDiffEntriesUseCaseTest { + + @BeforeEach + fun setUp() { + } +} \ No newline at end of file