Removed diff manager in favor of use cases

Also simplified hunk generator and raw file manager
This commit is contained in:
Abdelilah El Aissaoui 2022-08-24 05:17:18 +02:00
parent 270951fe66
commit d3f2b4a23f
16 changed files with 298 additions and 250 deletions

View File

@ -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
}

View File

@ -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<DiffEntry> = 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")
}
}

View File

@ -2,8 +2,6 @@ package app.git
import app.TempFilesManager import app.TempFilesManager
import app.extensions.systemSeparator import app.extensions.systemSeparator
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import org.eclipse.jgit.diff.ContentSource import org.eclipse.jgit.diff.ContentSource
import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.diff.RawText import org.eclipse.jgit.diff.RawText
@ -15,18 +13,15 @@ import org.eclipse.jgit.treewalk.WorkingTreeIterator
import org.eclipse.jgit.util.LfsFactory import org.eclipse.jgit.util.LfsFactory
import java.io.FileOutputStream import java.io.FileOutputStream
import java.nio.file.Path import java.nio.file.Path
import javax.inject.Inject
import kotlin.io.path.createTempFile import kotlin.io.path.createTempFile
private const val DEFAULT_BINARY_FILE_THRESHOLD = PackConfig.DEFAULT_BIG_FILE_THRESHOLD private const val DEFAULT_BINARY_FILE_THRESHOLD = PackConfig.DEFAULT_BIG_FILE_THRESHOLD
class RawFileManager @AssistedInject constructor( class RawFileManager @Inject constructor(
@Assisted private val repository: Repository,
private val tempFilesManager: TempFilesManager, private val tempFilesManager: TempFilesManager,
) : AutoCloseable { ) {
private var reader: ObjectReader = repository.newObjectReader()
private var source: ContentSource.Pair
private val imageFormatsSupported = listOf( private val imageFormatsSupported = listOf(
"png", "png",
"jpg", "jpg",
@ -34,38 +29,45 @@ class RawFileManager @AssistedInject constructor(
"webp", "webp",
) )
init { private fun source(iterator: AbstractTreeIterator, reader: ObjectReader): ContentSource {
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 {
return if (iterator is WorkingTreeIterator) return if (iterator is WorkingTreeIterator)
ContentSource.create(iterator) ContentSource.create(iterator)
else else
ContentSource.create(reader) 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) === FileMode.MISSING) return EntryContent.Missing
if (entry.getMode(side).objectType != Constants.OBJ_BLOB) return EntryContent.InvalidObjectBlob if (entry.getMode(side).objectType != Constants.OBJ_BLOB) return EntryContent.InvalidObjectBlob
val ldr = LfsFactory.getInstance().applySmudgeFilter( val reader: ObjectReader = repository.newObjectReader()
repository, reader.use {
source.open(side, entry), entry.diffAttribute 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 { val ldr = LfsFactory.getInstance().applySmudgeFilter(
EntryContent.Text(RawText.load(ldr, DEFAULT_BINARY_FILE_THRESHOLD)) repository,
} catch (ex: BinaryBlobException) { source.open(side, entry), entry.diffAttribute
if (isImage(entry)) { )
generateImageBinary(ldr, entry, side)
} else return try {
EntryContent.Binary 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()) return imageFormatsSupported.contains(fileExtension.lowercase())
} }
override fun close() {
reader.close()
}
} }
sealed class EntryContent { sealed class EntryContent {

View File

@ -3,7 +3,6 @@ package app.git
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import app.di.RawFileManagerFactory
import app.extensions.* import app.extensions.*
import app.git.diff.Hunk import app.git.diff.Hunk
import app.git.diff.LineType import app.git.diff.LineType
@ -29,8 +28,7 @@ import javax.inject.Inject
class StatusManager @Inject constructor( class StatusManager @Inject constructor(
private val rawFileManagerFactory: RawFileManagerFactory, private val rawFileManager: RawFileManager,
private val submodulesManager: SubmodulesManager,
) { ) {
suspend fun hasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) { suspend fun hasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) {
val status = git val status = git
@ -54,8 +52,13 @@ class StatusManager @Inject constructor(
var completedWithErrors = true var completedWithErrors = true
try { try {
val rawFileManager = rawFileManagerFactory.create(git.repository) val entryContent = rawFileManager.getRawContent(
val entryContent = rawFileManager.getRawContent(DiffEntry.Side.OLD, diffEntry) repository = git.repository,
side = DiffEntry.Side.OLD,
entry = diffEntry,
oldTreeIterator = null,
newTreeIterator = null
)
if (entryContent !is EntryContent.Text) if (entryContent !is EntryContent.Text)
return@withContext return@withContext
@ -71,10 +74,12 @@ class StatusManager @Inject constructor(
textLines.add(line.oldLineNumber + linesAdded, line.text) textLines.add(line.oldLineNumber + linesAdded, line.text)
linesAdded++ linesAdded++
} }
LineType.REMOVED -> { LineType.REMOVED -> {
textLines.removeAt(line.oldLineNumber + linesAdded) textLines.removeAt(line.oldLineNumber + linesAdded)
linesAdded-- linesAdded--
} }
else -> throw NotImplementedError("Line type not implemented for stage hunk") else -> throw NotImplementedError("Line type not implemented for stage hunk")
} }
} }
@ -97,8 +102,13 @@ class StatusManager @Inject constructor(
var completedWithErrors = true var completedWithErrors = true
try { try {
val rawFileManager = rawFileManagerFactory.create(repository) val entryContent = rawFileManager.getRawContent(
val entryContent = rawFileManager.getRawContent(DiffEntry.Side.NEW, diffEntry) repository = repository,
side = DiffEntry.Side.NEW,
entry = diffEntry,
oldTreeIterator = null,
newTreeIterator = null
)
if (entryContent !is EntryContent.Text) if (entryContent !is EntryContent.Text)
return@withContext return@withContext

View File

@ -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,
)
}
}

View File

@ -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<DiffEntry> = 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
}
}

View File

@ -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
}
}

View File

@ -3,7 +3,6 @@ package app.git.diff
import app.extensions.lineAt import app.extensions.lineAt
import app.git.EntryContent import app.git.EntryContent
import app.git.RawFileManager import app.git.RawFileManager
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import org.eclipse.jgit.diff.* import org.eclipse.jgit.diff.*
import org.eclipse.jgit.lib.Repository import org.eclipse.jgit.lib.Repository
@ -13,6 +12,7 @@ import org.eclipse.jgit.treewalk.AbstractTreeIterator
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.io.InvalidObjectException import java.io.InvalidObjectException
import javax.inject.Inject
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ -22,46 +22,46 @@ private const val CONTEXT_LINES = 3
/** /**
* Generator of [Hunk] lists from [DiffEntry] * Generator of [Hunk] lists from [DiffEntry]
*/ */
class HunkDiffGenerator @AssistedInject constructor( class HunkDiffGenerator @Inject constructor(
@Assisted private val repository: Repository, private val rawFileManager: RawFileManager,
@Assisted private val rawFileManager: RawFileManager, ) {
) : AutoCloseable { 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 if (oldTreeIterator != null && newTreeIterator != null) {
private val diffFormatter = DiffFormatter(outputStream).apply { diffFormatter.scan(oldTreeIterator, newTreeIterator)
setRepository(repository) }
}
override fun close() { val fileHeader = diffFormatter.toFileHeader(diffEntry)
outputStream.close()
}
fun scan(oldTreeIterator: AbstractTreeIterator, newTreeIterator: AbstractTreeIterator) { val rawOld = rawFileManager.getRawContent(repository, DiffEntry.Side.OLD, diffEntry, oldTreeIterator, newTreeIterator)
rawFileManager.scan(oldTreeIterator, newTreeIterator) val rawNew = rawFileManager.getRawContent(repository, DiffEntry.Side.NEW, diffEntry, oldTreeIterator, newTreeIterator)
diffFormatter.scan(oldTreeIterator, newTreeIterator)
}
fun format(ent: DiffEntry): DiffResult { if (rawOld == EntryContent.InvalidObjectBlob || rawNew == EntryContent.InvalidObjectBlob)
val fileHeader = diffFormatter.toFileHeader(ent) throw InvalidObjectException("Invalid object in diff format")
val rawOld = rawFileManager.getRawContent(DiffEntry.Side.OLD, ent) var diffResult: DiffResult = DiffResult.Text(diffEntry, emptyList())
val rawNew = rawFileManager.getRawContent(DiffEntry.Side.NEW, ent)
if (rawOld == EntryContent.InvalidObjectBlob || rawNew == EntryContent.InvalidObjectBlob) // If we can, generate text diff (if one of the files has never been a binary file)
throw InvalidObjectException("Invalid object in diff format") 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) return@use diffResult
val hasGeneratedTextDiff = canGenerateTextDiff(rawOld, rawNew) { oldRawText, newRawText ->
diffResult = DiffResult.Text(ent, format(fileHeader, oldRawText, newRawText))
} }
if (!hasGeneratedTextDiff) {
diffResult = DiffResult.NonText(ent, rawOld, rawNew)
}
return diffResult
} }
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
@ -221,6 +221,7 @@ sealed class DiffResult(
diffEntry: DiffEntry, diffEntry: DiffEntry,
val hunks: List<Hunk> val hunks: List<Hunk>
) : DiffResult(diffEntry) ) : DiffResult(diffEntry)
class TextSplit( class TextSplit(
diffEntry: DiffEntry, diffEntry: DiffEntry,
val hunks: List<SplitHunk> val hunks: List<SplitHunk>

View File

@ -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
}
}

View File

@ -1,8 +1,8 @@
package app.preferences package app.preferences
import app.extensions.defaultWindowPlacement import app.extensions.defaultWindowPlacement
import app.git.TextDiffType import app.viewmodels.TextDiffType
import app.git.textDiffTypeFromValue import app.viewmodels.textDiffTypeFromValue
import app.theme.ColorsScheme import app.theme.ColorsScheme
import app.theme.Theme import app.theme.Theme
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow

View File

@ -42,6 +42,7 @@ import app.theme.*
import app.ui.components.ScrollableLazyColumn import app.ui.components.ScrollableLazyColumn
import app.ui.components.SecondaryButton import app.ui.components.SecondaryButton
import app.viewmodels.DiffViewModel import app.viewmodels.DiffViewModel
import app.viewmodels.TextDiffType
import app.viewmodels.ViewDiffResult import app.viewmodels.ViewDiffResult
import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.DiffEntry
import java.io.FileInputStream import java.io.FileInputStream

View File

@ -1,9 +1,9 @@
package app.viewmodels package app.viewmodels
import app.extensions.delayedStateChange import app.extensions.delayedStateChange
import app.git.DiffManager
import app.git.RefreshType import app.git.RefreshType
import app.git.TabState import app.git.TabState
import app.git.diff.GetCommitDiffEntriesUseCase
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import org.eclipse.jgit.diff.DiffEntry 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( class CommitChangesViewModel @Inject constructor(
private val tabState: TabState, private val tabState: TabState,
private val diffManager: DiffManager, private val getCommitDiffEntriesUseCase: GetCommitDiffEntriesUseCase,
) { ) {
private val _commitChangesStatus = MutableStateFlow<CommitChangesStatus>(CommitChangesStatus.Loading) private val _commitChangesStatus = MutableStateFlow<CommitChangesStatus>(CommitChangesStatus.Loading)
val commitChangesStatus: StateFlow<CommitChangesStatus> = _commitChangesStatus val commitChangesStatus: StateFlow<CommitChangesStatus> = _commitChangesStatus
@ -26,7 +26,7 @@ class CommitChangesViewModel @Inject constructor(
delayMs = MIN_TIME_IN_MS_TO_SHOW_LOAD, delayMs = MIN_TIME_IN_MS_TO_SHOW_LOAD,
onDelayTriggered = { _commitChangesStatus.value = CommitChangesStatus.Loading } onDelayTriggered = { _commitChangesStatus.value = CommitChangesStatus.Loading }
) { ) {
val changes = diffManager.commitDiffEntries(git, commit) val changes = getCommitDiffEntriesUseCase(git, commit)
_commitChangesStatus.value = CommitChangesStatus.Loaded(commit, changes) _commitChangesStatus.value = CommitChangesStatus.Loaded(commit, changes)
} }

View File

@ -6,6 +6,7 @@ import app.exceptions.MissingDiffEntryException
import app.extensions.delayedStateChange import app.extensions.delayedStateChange
import app.git.* import app.git.*
import app.git.diff.DiffResult import app.git.diff.DiffResult
import app.git.diff.FormatDiffUseCase
import app.git.diff.Hunk import app.git.diff.Hunk
import app.preferences.AppSettings import app.preferences.AppSettings
import app.git.diff.GenerateSplitHunkFromDiffResultUseCase 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( class DiffViewModel @Inject constructor(
private val tabState: TabState, private val tabState: TabState,
private val diffManager: DiffManager, private val formatDiffUseCase: FormatDiffUseCase,
private val statusManager: StatusManager, private val statusManager: StatusManager,
private val settings: AppSettings, private val settings: AppSettings,
private val generateSplitHunkFromDiffResultUseCase: GenerateSplitHunkFromDiffResultUseCase, 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, delayMs = if (isFirstLoad) 0 else DIFF_MIN_TIME_IN_MS_TO_SHOW_LOAD,
onDelayTriggered = { _diffResult.value = ViewDiffResult.Loading(diffEntryType.filePath) } onDelayTriggered = { _diffResult.value = ViewDiffResult.Loading(diffEntryType.filePath) }
) { ) {
val diffFormat = diffManager.diffFormat(git, diffEntryType) val diffFormat = formatDiffUseCase(git, diffEntryType)
val diffEntry = diffFormat.diffEntry val diffEntry = diffFormat.diffEntry
if ( if (
diffTypeFlow.value == TextDiffType.SPLIT && diffTypeFlow.value == TextDiffType.SPLIT &&
@ -150,3 +151,17 @@ class DiffViewModel @Inject constructor(
settings.textDiffType = newDiffType settings.textDiffType = newDiffType
} }
} }
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")
}
}

View File

@ -5,8 +5,10 @@ import app.exceptions.MissingDiffEntryException
import app.extensions.filePath import app.extensions.filePath
import app.git.* import app.git.*
import app.git.diff.DiffResult import app.git.diff.DiffResult
import app.git.diff.FormatDiffUseCase
import app.preferences.AppSettings import app.preferences.AppSettings
import app.git.diff.GenerateSplitHunkFromDiffResultUseCase import app.git.diff.GenerateSplitHunkFromDiffResultUseCase
import app.git.diff.GetCommitDiffEntriesUseCase
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -15,7 +17,8 @@ import javax.inject.Inject
class HistoryViewModel @Inject constructor( class HistoryViewModel @Inject constructor(
private val tabState: TabState, private val tabState: TabState,
private val diffManager: DiffManager, private val formatDiffUseCase: FormatDiffUseCase,
private val getCommitDiffEntriesUseCase: GetCommitDiffEntriesUseCase,
private val settings: AppSettings, private val settings: AppSettings,
private val generateSplitHunkFromDiffResultUseCase: GenerateSplitHunkFromDiffResultUseCase, private val generateSplitHunkFromDiffResultUseCase: GenerateSplitHunkFromDiffResultUseCase,
) { ) {
@ -87,7 +90,7 @@ class HistoryViewModel @Inject constructor(
) { git -> ) { git ->
try { try {
val diffEntries = diffManager.commitDiffEntries(git, commit) val diffEntries = getCommitDiffEntriesUseCase(git, commit)
val diffEntry = diffEntries.firstOrNull { entry -> val diffEntry = diffEntries.firstOrNull { entry ->
entry.filePath == this.filePath entry.filePath == this.filePath
} }
@ -99,7 +102,7 @@ class HistoryViewModel @Inject constructor(
val diffEntryType = DiffEntryType.CommitDiff(diffEntry) val diffEntryType = DiffEntryType.CommitDiff(diffEntry)
val diffResult = diffManager.diffFormat(git, diffEntryType) val diffResult = formatDiffUseCase(git, diffEntryType)
val textDiffType = settings.textDiffType val textDiffType = settings.textDiffType
val formattedDiffResult = if (textDiffType == TextDiffType.SPLIT && diffResult is DiffResult.Text) { val formattedDiffResult = if (textDiffType == TextDiffType.SPLIT && diffResult is DiffResult.Text) {

View File

@ -1,7 +1,6 @@
package app.viewmodels package app.viewmodels
import app.git.DiffEntryType import app.git.DiffEntryType
import app.git.TextDiffType
import app.git.diff.DiffResult import app.git.diff.DiffResult
sealed interface ViewDiffResult { sealed interface ViewDiffResult {
@ -11,5 +10,5 @@ sealed interface ViewDiffResult {
object DiffNotFound : 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
} }

View File

@ -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() {
}
}