Removed diff manager in favor of use cases
Also simplified hunk generator and raw file manager
This commit is contained in:
parent
270951fe66
commit
d3f2b4a23f
@ -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
|
|
||||||
}
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
64
src/main/kotlin/app/git/diff/FormatDiffUseCase.kt
Normal file
64
src/main/kotlin/app/git/diff/FormatDiffUseCase.kt
Normal 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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
51
src/main/kotlin/app/git/diff/GetCommitDiffEntriesUseCase.kt
Normal file
51
src/main/kotlin/app/git/diff/GetCommitDiffEntriesUseCase.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 &&
|
||||||
@ -149,4 +150,18 @@ class DiffViewModel @Inject constructor(
|
|||||||
fun changeTextDiffType(newDiffType: TextDiffType) {
|
fun changeTextDiffType(newDiffType: TextDiffType) {
|
||||||
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
}
|
}
|
@ -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() {
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user