Merge branch 'status_optimizations' into main

This commit is contained in:
Abdelilah El Aissaoui 2022-04-06 20:19:54 +02:00
commit 716d04df9a
18 changed files with 319 additions and 258 deletions

View File

@ -44,8 +44,6 @@ class App {
@Inject @Inject
lateinit var appPreferences: AppPreferences lateinit var appPreferences: AppPreferences
private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
init { init {
appComponent.inject(this) appComponent.inject(this)
} }
@ -92,7 +90,7 @@ class App {
} }
} }
} else { } else {
appScope.cancel("Closing app") appStateManager.cancelCoroutines()
this.exitApplication() this.exitApplication()
} }
} }
@ -151,7 +149,7 @@ class App {
} }
} }
private fun removeTab(key: Int) = appScope.launch(Dispatchers.IO) { private fun removeTab(key: Int) = appStateManager.appStateScope.launch(Dispatchers.IO) {
// Stop any running jobs // Stop any running jobs
val tabs = tabsFlow.value val tabs = tabsFlow.value
val tabToRemove = tabs.firstOrNull { it.key == key } ?: return@launch val tabToRemove = tabs.firstOrNull { it.key == key } ?: return@launch
@ -164,7 +162,7 @@ class App {
tabsFlow.value = tabsFlow.value.filter { tab -> tab.key != key } tabsFlow.value = tabsFlow.value.filter { tab -> tab.key != key }
} }
fun addTab(tabInformation: TabInformation) = appScope.launch(Dispatchers.IO) { fun addTab(tabInformation: TabInformation) = appStateManager.appStateScope.launch(Dispatchers.IO) {
tabsFlow.value = tabsFlow.value.toMutableList().apply { add(tabInformation) } tabsFlow.value = tabsFlow.value.toMutableList().apply { add(tabInformation) }
} }

View File

@ -19,7 +19,7 @@ class AppStateManager @Inject constructor(
val latestOpenedRepositoriesPaths: List<String> val latestOpenedRepositoriesPaths: List<String>
get() = _latestOpenedRepositoriesPaths get() = _latestOpenedRepositoriesPaths
private val appStateScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) // TODO Stop this when closing the app val appStateScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
val latestOpenedRepositoryPath: String val latestOpenedRepositoryPath: String
get() = _latestOpenedRepositoriesPaths.firstOrNull() ?: "" get() = _latestOpenedRepositoriesPaths.firstOrNull() ?: ""
@ -74,4 +74,8 @@ class AppStateManager @Inject constructor(
_latestOpenedRepositoriesPaths.addAll(repositories) _latestOpenedRepositoriesPaths.addAll(repositories)
} }
} }
fun cancelCoroutines() {
appStateScope.cancel("Closing app")
}
} }

View File

@ -0,0 +1,3 @@
package app.exceptions
class MissingDiffEntryException(msg: String) : GitnuroException(msg)

View File

@ -2,14 +2,14 @@ package app.extensions
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Refresh
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.git.StatusEntry
import app.git.StatusType
import app.theme.addFile import app.theme.addFile
import app.theme.conflictFile
import app.theme.modifyFile import app.theme.modifyFile
import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.DiffEntry
@ -31,6 +31,26 @@ val DiffEntry.parentDirectoryPath: String
"${directoryPath}/" "${directoryPath}/"
} }
val StatusEntry.parentDirectoryPath: String
get() {
val pathSplit = this.filePath.split("/").toMutableList()
pathSplit.removeLast()
val directoryPath = pathSplit.joinToString("/")
return if (directoryPath.isEmpty())
""
else
"${directoryPath}/"
}
val StatusEntry.fileName: String
get() {
val pathSplit = filePath.split("/")
return pathSplit.lastOrNull() ?: ""
}
val DiffEntry.fileName: String val DiffEntry.fileName: String
get() { get() {
val path = if (this.changeType == DiffEntry.ChangeType.DELETE) { val path = if (this.changeType == DiffEntry.ChangeType.DELETE) {
@ -53,6 +73,16 @@ val DiffEntry.filePath: String
return path return path
} }
val StatusType.icon: ImageVector
get() {
return when (this) {
StatusType.ADDED -> Icons.Default.Add
StatusType.MODIFIED -> Icons.Default.Edit
StatusType.REMOVED -> Icons.Default.Delete
StatusType.CONFLICTING -> Icons.Default.Warning
}
}
val DiffEntry.icon: ImageVector val DiffEntry.icon: ImageVector
get() { get() {
return when (this.changeType) { return when (this.changeType) {
@ -65,6 +95,17 @@ val DiffEntry.icon: ImageVector
} }
} }
val StatusType.iconColor: Color
@Composable
get() {
return when (this) {
StatusType.ADDED -> MaterialTheme.colors.addFile
StatusType.MODIFIED -> MaterialTheme.colors.modifyFile
StatusType.REMOVED -> MaterialTheme.colors.error
StatusType.CONFLICTING -> MaterialTheme.colors.conflictFile
}
}
val DiffEntry.iconColor: Color val DiffEntry.iconColor: Color
@Composable @Composable
get() { get() {

View File

@ -2,4 +2,14 @@ package app.extensions
fun <T> List<T>?.countOrZero(): Int { fun <T> List<T>?.countOrZero(): Int {
return this?.count() ?: 0 return this?.count() ?: 0
}
fun <T> flatListOf(vararg lists: List<T>): List<T> {
val flatList = mutableListOf<T>()
for(list in lists) {
flatList.addAll(list)
}
return flatList
} }

View File

@ -2,22 +2,24 @@ package app.git
import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.DiffEntry
sealed class DiffEntryType(val diffEntry: DiffEntry) { sealed class DiffEntryType() {
class CommitDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry) class CommitDiff(val diffEntry: DiffEntry) : DiffEntryType()
sealed class UnstagedDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry) sealed class UncommitedDiff(val statusEntry: StatusEntry) : DiffEntryType()
sealed class StagedDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry)
sealed class UnstagedDiff(statusEntry: StatusEntry) : UncommitedDiff(statusEntry)
sealed class StagedDiff(statusEntry: StatusEntry) : UncommitedDiff(statusEntry)
/** /**
* State used to represent staged changes when the repository state is not [org.eclipse.jgit.lib.RepositoryState.SAFE] * State used to represent staged changes when the repository state is not [org.eclipse.jgit.lib.RepositoryState.SAFE]
*/ */
class UnsafeStagedDiff(diffEntry: DiffEntry) : StagedDiff(diffEntry) class UnsafeStagedDiff(statusEntry: StatusEntry) : StagedDiff(statusEntry)
/** /**
* State used to represent unstaged changes when the repository state is not [org.eclipse.jgit.lib.RepositoryState.SAFE] * State used to represent unstaged changes when the repository state is not [org.eclipse.jgit.lib.RepositoryState.SAFE]
*/ */
class UnsafeUnstagedDiff(diffEntry: DiffEntry) : UnstagedDiff(diffEntry) class UnsafeUnstagedDiff(statusEntry: StatusEntry) : UnstagedDiff(statusEntry)
class SafeStagedDiff(diffEntry: DiffEntry) : StagedDiff(diffEntry) class SafeStagedDiff(statusEntry: StatusEntry) : StagedDiff(statusEntry)
class SafeUnstagedDiff(diffEntry: DiffEntry) : UnstagedDiff(diffEntry) class SafeUnstagedDiff(statusEntry: StatusEntry) : UnstagedDiff(statusEntry)
} }

View File

@ -2,6 +2,7 @@ package app.git
import app.di.HunkDiffGeneratorFactory import app.di.HunkDiffGeneratorFactory
import app.di.RawFileManagerFactory import app.di.RawFileManagerFactory
import app.exceptions.MissingDiffEntryException
import app.extensions.fullData import app.extensions.fullData
import app.git.diff.DiffResult import app.git.diff.DiffResult
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -17,6 +18,7 @@ import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.treewalk.AbstractTreeIterator import org.eclipse.jgit.treewalk.AbstractTreeIterator
import org.eclipse.jgit.treewalk.CanonicalTreeParser import org.eclipse.jgit.treewalk.CanonicalTreeParser
import org.eclipse.jgit.treewalk.FileTreeIterator import org.eclipse.jgit.treewalk.FileTreeIterator
import org.eclipse.jgit.treewalk.filter.PathFilter
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import javax.inject.Inject import javax.inject.Inject
@ -26,9 +28,9 @@ class DiffManager @Inject constructor(
private val hunkDiffGeneratorFactory: HunkDiffGeneratorFactory, private val hunkDiffGeneratorFactory: HunkDiffGeneratorFactory,
) { ) {
suspend fun diffFormat(git: Git, diffEntryType: DiffEntryType): DiffResult = withContext(Dispatchers.IO) { suspend fun diffFormat(git: Git, diffEntryType: DiffEntryType): DiffResult = withContext(Dispatchers.IO) {
val diffEntry = diffEntryType.diffEntry
val byteArrayOutputStream = ByteArrayOutputStream() val byteArrayOutputStream = ByteArrayOutputStream()
val repository = git.repository val repository = git.repository
val diffEntry: DiffEntry
DiffFormatter(byteArrayOutputStream).use { formatter -> DiffFormatter(byteArrayOutputStream).use { formatter ->
formatter.setRepository(repository) formatter.setRepository(repository)
@ -39,6 +41,24 @@ class DiffManager @Inject constructor(
if (diffEntryType is DiffEntryType.UnstagedDiff) if (diffEntryType is DiffEntryType.UnstagedDiff)
formatter.scan(oldTree, newTree) formatter.scan(oldTree, newTree)
diffEntry = when (diffEntryType) {
is DiffEntryType.CommitDiff -> {
diffEntryType.diffEntry
}
is DiffEntryType.UncommitedDiff -> {
val statusEntry = diffEntryType.statusEntry
val firstDiffEntry = git.diff()
.setPathFilter(PathFilter.create(statusEntry.filePath))
.setCached(diffEntryType is DiffEntryType.StagedDiff)
.call()
.firstOrNull()
?: throw MissingDiffEntryException("Diff entry not found")
firstDiffEntry
}
}
formatter.format(diffEntry) formatter.format(diffEntry)
formatter.flush() formatter.flush()

View File

@ -1,8 +1,5 @@
package app.git package app.git
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Warning
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
@ -10,17 +7,18 @@ 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
import app.theme.conflictFile
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.Status
import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.diff.RawText import org.eclipse.jgit.diff.RawText
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit
import org.eclipse.jgit.dircache.DirCacheEntry import org.eclipse.jgit.dircache.DirCacheEntry
import org.eclipse.jgit.lib.* import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.submodule.SubmoduleStatusType import org.eclipse.jgit.lib.FileMode
import org.eclipse.jgit.treewalk.EmptyTreeIterator import org.eclipse.jgit.lib.ObjectInserter
import org.eclipse.jgit.lib.Repository
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.IOException import java.io.IOException
import java.nio.ByteBuffer import java.nio.ByteBuffer
@ -40,14 +38,14 @@ class StatusManager @Inject constructor(
return@withContext status.hasUncommittedChanges() || status.hasUntrackedChanges() return@withContext status.hasUncommittedChanges() || status.hasUntrackedChanges()
} }
suspend fun stage(git: Git, diffEntry: DiffEntry) = withContext(Dispatchers.IO) { suspend fun stage(git: Git, statusEntry: StatusEntry) = withContext(Dispatchers.IO) {
if (diffEntry.changeType == DiffEntry.ChangeType.DELETE) { if (statusEntry.statusType == StatusType.REMOVED) {
git.rm() git.rm()
.addFilepattern(diffEntry.filePath) .addFilepattern(statusEntry.filePath)
.call() .call()
} else { } else {
git.add() git.add()
.addFilepattern(diffEntry.filePath) .addFilepattern(statusEntry.filePath)
.call() .call()
} }
} }
@ -132,7 +130,7 @@ class StatusManager @Inject constructor(
// Restore previously removed lines to the index // Restore previously removed lines to the index
for (line in removedLines) { for (line in removedLines) {
// Check how many lines before this one have been deleted // Check how many lines before this one have been deleted
val previouslyRemovedLines = addedLines.count { it.newLineNumber <= line.newLineNumber } - 1 val previouslyRemovedLines = addedLines.count { it.newLineNumber < line.newLineNumber }
textLines.add(line.newLineNumber + linesAdded - previouslyRemovedLines, line.text.withoutLineEnding) textLines.add(line.newLineNumber + linesAdded - previouslyRemovedLines, line.text.withoutLineEnding)
linesAdded++ linesAdded++
} }
@ -182,9 +180,9 @@ class StatusManager @Inject constructor(
} }
} }
suspend fun unstage(git: Git, diffEntry: DiffEntry) = withContext(Dispatchers.IO) { suspend fun unstage(git: Git, statusEntry: StatusEntry) = withContext(Dispatchers.IO) {
git.reset() git.reset()
.addPath(diffEntry.filePath) .addPath(statusEntry.filePath)
.call() .call()
} }
@ -196,17 +194,17 @@ class StatusManager @Inject constructor(
.call() .call()
} }
suspend fun reset(git: Git, diffEntry: DiffEntry, staged: Boolean) = withContext(Dispatchers.IO) { suspend fun reset(git: Git, statusEntry: StatusEntry, staged: Boolean) = withContext(Dispatchers.IO) {
if (staged) { if (staged) {
git git
.reset() .reset()
.addPath(diffEntry.filePath) .addPath(statusEntry.filePath)
.call() .call()
} }
git git
.checkout() .checkout()
.addPath(diffEntry.filePath) .addPath(statusEntry.filePath)
.call() .call()
} }
@ -223,99 +221,86 @@ class StatusManager @Inject constructor(
.call() .call()
} }
suspend fun getStaged(git: Git, currentBranch: Ref?, repositoryState: RepositoryState) = suspend fun getStatus(git: Git) =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val statusEntries: List<StatusEntry> git
.status()
val status = git.diff()
.setShowNameAndStatusOnly(true).apply {
if (currentBranch == null && !repositoryState.isMerging && !repositoryState.isRebasing)
setOldTree(EmptyTreeIterator()) // Required if the repository is empty
setCached(true)
}
.call() .call()
statusEntries = if (repositoryState.isMerging || repositoryState.isRebasing) {
status.groupBy {
if (it.newPath != "/dev/null")
it.newPath
else
it.oldPath
}
.map {
val entries = it.value
val hasConflicts =
(entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing))
StatusEntry(entries.first(), isConflict = hasConflicts)
}
} else {
status.map {
StatusEntry(it, isConflict = false)
}
}
return@withContext statusEntries
} }
suspend fun getUnstaged(git: Git, repositoryState: RepositoryState) = withContext(Dispatchers.IO) { suspend fun getStaged(status: Status) =
val uninitializedSubmodules = submodulesManager.uninitializedSubmodules(git) withContext(Dispatchers.IO) {
val added = status.added.map {
return@withContext git StatusEntry(it, StatusType.ADDED)
.diff()
.call()
.filter {
!uninitializedSubmodules.containsKey(it.oldPath) // Filter out uninitialized modules directories
} }
.groupBy { val modified = status.changed.map {
if (it.oldPath != "/dev/null") StatusEntry(it, StatusType.MODIFIED)
it.oldPath
else
it.newPath
} }
.map { val removed = status.removed.map {
val entries = it.value StatusEntry(it, StatusType.REMOVED)
val hasConflicts =
(entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing))
StatusEntry(entries.first(), isConflict = hasConflicts)
} }
return@withContext flatListOf(
added,
modified,
removed,
)
}
suspend fun getUnstaged(status: Status) = withContext(Dispatchers.IO) {
// TODO Test uninitialized modules after the refactor
// val uninitializedSubmodules = submodulesManager.uninitializedSubmodules(git)
val added = status.untracked.map {
StatusEntry(it, StatusType.ADDED)
}
val modified = status.modified.map {
StatusEntry(it, StatusType.MODIFIED)
}
val removed = status.missing.map {
StatusEntry(it, StatusType.REMOVED)
}
val conflicting = status.conflicting.map {
StatusEntry(it, StatusType.CONFLICTING)
}
return@withContext flatListOf(
added,
modified,
removed,
conflicting,
)
} }
suspend fun getStatusSummary(git: Git, currentBranch: Ref?, repositoryState: RepositoryState): StatusSummary { suspend fun getStatusSummary(git: Git): StatusSummary {
val staged = getStaged(git, currentBranch, repositoryState) val status = getStatus(git)
val staged = getStaged(status)
val allChanges = staged.toMutableList() val allChanges = staged.toMutableList()
val unstaged = getUnstaged(git, repositoryState) val unstaged = getUnstaged(status)
allChanges.addAll(unstaged) allChanges.addAll(unstaged)
val groupedChanges = allChanges.groupBy { val groupedChanges = allChanges.groupBy {
if (it.diffEntry.newPath != "/dev/null")
it.diffEntry.newPath
else
it.diffEntry.oldPath
} }
val changesGrouped = groupedChanges.map { val changesGrouped = groupedChanges.map {
it.value it.value
}.flatten() }.flatten()
.groupBy { .groupBy {
it.diffEntry.changeType it.statusType
} }
val deletedCount = changesGrouped[DiffEntry.ChangeType.DELETE].countOrZero() val deletedCount = changesGrouped[StatusType.REMOVED].countOrZero()
val addedCount = changesGrouped[DiffEntry.ChangeType.ADD].countOrZero() val addedCount = changesGrouped[StatusType.ADDED].countOrZero()
val modifiedCount = changesGrouped[DiffEntry.ChangeType.MODIFY].countOrZero() + val modifiedCount = changesGrouped[StatusType.MODIFIED].countOrZero()
changesGrouped[DiffEntry.ChangeType.RENAME].countOrZero() + val conflictingCount = changesGrouped[StatusType.CONFLICTING].countOrZero()
changesGrouped[DiffEntry.ChangeType.COPY].countOrZero()
return StatusSummary( return StatusSummary(
modifiedCount = modifiedCount, modifiedCount = modifiedCount,
deletedCount = deletedCount, deletedCount = deletedCount,
addedCount = addedCount, addedCount = addedCount,
conflictingCount = conflictingCount,
) )
} }
@ -341,22 +326,30 @@ class StatusManager @Inject constructor(
} }
data class StatusEntry(val diffEntry: DiffEntry, val isConflict: Boolean) { data class StatusEntry(val filePath: String, val statusType: StatusType) {
val icon: ImageVector val icon: ImageVector
get() { get() = statusType.icon
return if (isConflict)
Icons.Default.Warning
else
diffEntry.icon
}
val iconColor: Color val iconColor: Color
@Composable @Composable
get() { get() = statusType.iconColor
return if (isConflict)
MaterialTheme.colors.conflictFile
else
diffEntry.iconColor
}
} }
data class StatusSummary(val modifiedCount: Int, val deletedCount: Int, val addedCount: Int) enum class StatusType {
ADDED,
MODIFIED,
REMOVED,
CONFLICTING,
}
data class StatusSummary(
val modifiedCount: Int,
val deletedCount: Int,
val addedCount: Int,
val conflictingCount: Int,
) {
val total = modifiedCount +
deletedCount +
addedCount +
conflictingCount
}

View File

@ -50,15 +50,15 @@ class HunkDiffGenerator @AssistedInject constructor(
if (rawOld == EntryContent.InvalidObjectBlob || rawNew == EntryContent.InvalidObjectBlob) if (rawOld == EntryContent.InvalidObjectBlob || rawNew == EntryContent.InvalidObjectBlob)
throw InvalidObjectException("Invalid object in diff format") throw InvalidObjectException("Invalid object in diff format")
var diffResult: DiffResult = DiffResult.Text(emptyList()) var diffResult: DiffResult = DiffResult.Text(ent, emptyList())
// If we can, generate text diff (if one of the files has never been a binary file) // If we can, generate text diff (if one of the files has never been a binary file)
val hasGeneratedTextDiff = canGenerateTextDiff(rawOld, rawNew) { oldRawText, newRawText -> val hasGeneratedTextDiff = canGenerateTextDiff(rawOld, rawNew) { oldRawText, newRawText ->
diffResult = DiffResult.Text(format(fileHeader, oldRawText, newRawText)) diffResult = DiffResult.Text(ent, format(fileHeader, oldRawText, newRawText))
} }
if (!hasGeneratedTextDiff) { if (!hasGeneratedTextDiff) {
diffResult = DiffResult.NonText(rawOld, rawNew) diffResult = DiffResult.NonText(ent, rawOld, rawNew)
} }
return diffResult return diffResult
@ -190,10 +190,17 @@ class HunkDiffGenerator @AssistedInject constructor(
} }
} }
sealed class DiffResult { sealed class DiffResult(
data class Text(val hunks: List<Hunk>) : DiffResult() val diffEntry: DiffEntry,
data class NonText( ) {
class Text(
diffEntry: DiffEntry,
val hunks: List<Hunk>
) : DiffResult(diffEntry)
class NonText(
diffEntry: DiffEntry,
val oldBinaryContent: EntryContent, val oldBinaryContent: EntryContent,
val newBinaryContent: EntryContent, val newBinaryContent: EntryContent,
) : DiffResult() ) : DiffResult(diffEntry)
} }

View File

@ -197,7 +197,7 @@ fun CommitLogChanges(
val textColor: Color val textColor: Color
val secondaryTextColor: Color val secondaryTextColor: Color
if (diffSelected?.diffEntry == diffEntry) { if (diffSelected is DiffEntryType.CommitDiff && diffSelected.diffEntry == diffEntry) {
textColor = MaterialTheme.colors.primary textColor = MaterialTheme.colors.primary
secondaryTextColor = MaterialTheme.colors.halfPrimary secondaryTextColor = MaterialTheme.colors.halfPrimary
} else { } else {

View File

@ -7,9 +7,11 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.selection.DisableSelection import androidx.compose.foundation.text.selection.DisableSelection
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -23,6 +25,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import app.git.DiffEntryType import app.git.DiffEntryType
import app.git.EntryContent import app.git.EntryContent
import app.git.StatusType
import app.git.diff.DiffResult import app.git.diff.DiffResult
import app.git.diff.Hunk import app.git.diff.Hunk
import app.git.diff.Line import app.git.diff.Line
@ -33,6 +36,7 @@ import app.theme.unstageButton
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.ViewDiffResult
import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.DiffEntry
import java.io.FileInputStream import java.io.FileInputStream
import java.nio.file.Path import java.nio.file.Path
@ -47,22 +51,33 @@ fun Diff(
val diffResultState = diffViewModel.diffResult.collectAsState() val diffResultState = diffViewModel.diffResult.collectAsState()
val viewDiffResult = diffResultState.value ?: return val viewDiffResult = diffResultState.value ?: return
val diffEntryType = viewDiffResult.diffEntryType
val diffEntry = diffEntryType.diffEntry
val diffResult = viewDiffResult.diffResult
Column( Column(
modifier = Modifier modifier = Modifier
.padding(8.dp) .padding(8.dp)
.background(MaterialTheme.colors.background) .background(MaterialTheme.colors.background)
.fillMaxSize() .fillMaxSize()
) { ) {
DiffHeader(diffEntry, onCloseDiffView) when (viewDiffResult) {
if (diffResult is DiffResult.Text) { ViewDiffResult.DiffNotFound -> { onCloseDiffView() }
TextDiff(diffEntryType, diffViewModel, diffResult) is ViewDiffResult.Loaded -> {
} else if (diffResult is DiffResult.NonText) { val diffEntryType = viewDiffResult.diffEntryType
NonTextDiff(diffResult) val diffEntry = viewDiffResult.diffResult.diffEntry
val diffResult = viewDiffResult.diffResult
DiffHeader(diffEntry, onCloseDiffView)
if (diffResult is DiffResult.Text) {
TextDiff(diffEntryType, diffViewModel, diffResult)
} else if (diffResult is DiffResult.NonText) {
NonTextDiff(diffResult)
}
}
ViewDiffResult.Loading -> {
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
}
} }
} }
} }
@ -175,8 +190,9 @@ fun TextDiff(diffEntryType: DiffEntryType, diffViewModel: DiffViewModel, diffRes
DisableSelection { DisableSelection {
HunkHeader( HunkHeader(
hunk = hunk, hunk = hunk,
diffEntryType = diffEntryType,
diffViewModel = diffViewModel, diffViewModel = diffViewModel,
diffEntryType = diffEntryType,
diffEntry =diffResult.diffEntry,
) )
} }
} }
@ -200,6 +216,7 @@ fun HunkHeader(
hunk: Hunk, hunk: Hunk,
diffEntryType: DiffEntryType, diffEntryType: DiffEntryType,
diffViewModel: DiffViewModel, diffViewModel: DiffViewModel,
diffEntry: DiffEntry,
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@ -215,9 +232,12 @@ fun HunkHeader(
) )
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
// Hunks options are only visible when repository is a normal state (not during merge/rebase)
if ( if (
(diffEntryType is DiffEntryType.SafeStagedDiff || diffEntryType is DiffEntryType.SafeUnstagedDiff) && (diffEntryType is DiffEntryType.SafeStagedDiff || diffEntryType is DiffEntryType.SafeUnstagedDiff) &&
diffEntryType.diffEntry.changeType == DiffEntry.ChangeType.MODIFY (diffEntryType is DiffEntryType.UncommitedDiff && // Added just to make smartcast work
diffEntryType.statusEntry.statusType == StatusType.MODIFIED)
) { ) {
val buttonText: String val buttonText: String
val color: Color val color: Color
@ -234,9 +254,9 @@ fun HunkHeader(
backgroundButton = color, backgroundButton = color,
onClick = { onClick = {
if (diffEntryType is DiffEntryType.StagedDiff) { if (diffEntryType is DiffEntryType.StagedDiff) {
diffViewModel.unstageHunk(diffEntryType.diffEntry, hunk) diffViewModel.unstageHunk(diffEntry, hunk)
} else { } else {
diffViewModel.stageHunk(diffEntryType.diffEntry, hunk) diffViewModel.stageHunk(diffEntry, hunk)
} }
} }
) )

View File

@ -45,16 +45,16 @@ import app.ui.context_menu.stagedEntriesContextMenuItems
import app.ui.context_menu.unstagedEntriesContextMenuItems import app.ui.context_menu.unstagedEntriesContextMenuItems
import app.viewmodels.StageStatus import app.viewmodels.StageStatus
import app.viewmodels.StatusViewModel import app.viewmodels.StatusViewModel
import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.lib.RepositoryState import org.eclipse.jgit.lib.RepositoryState
import kotlin.reflect.KClass
@Composable @Composable
fun UncommitedChanges( fun UncommitedChanges(
statusViewModel: StatusViewModel, statusViewModel: StatusViewModel,
selectedEntryType: DiffEntryType?, selectedEntryType: DiffEntryType?,
repositoryState: RepositoryState, repositoryState: RepositoryState,
onStagedDiffEntrySelected: (DiffEntry?) -> Unit, onStagedDiffEntrySelected: (StatusEntry?) -> Unit,
onUnstagedDiffEntrySelected: (DiffEntry) -> Unit, onUnstagedDiffEntrySelected: (StatusEntry) -> Unit,
) { ) {
val stageStatusState = statusViewModel.stageStatus.collectAsState() val stageStatusState = statusViewModel.stageStatus.collectAsState()
var commitMessage by remember { mutableStateOf(statusViewModel.savedCommitMessage) } var commitMessage by remember { mutableStateOf(statusViewModel.savedCommitMessage) }
@ -66,18 +66,6 @@ fun UncommitedChanges(
if (stageStatus is StageStatus.Loaded) { if (stageStatus is StageStatus.Loaded) {
staged = stageStatus.staged staged = stageStatus.staged
unstaged = stageStatus.unstaged unstaged = stageStatus.unstaged
LaunchedEffect(staged) {
if (selectedEntryType != null) {
checkIfSelectedEntryShouldBeUpdated(
selectedEntryType = selectedEntryType,
staged = staged,
unstaged = unstaged,
onStagedDiffEntrySelected = onStagedDiffEntrySelected,
onUnstagedDiffEntrySelected = onUnstagedDiffEntrySelected,
)
}
}
} else { } else {
staged = listOf() staged = listOf()
unstaged = listOf() // return empty lists if still loading unstaged = listOf() // return empty lists if still loading
@ -110,9 +98,9 @@ fun UncommitedChanges(
title = "Staged", title = "Staged",
allActionTitle = "Unstage all", allActionTitle = "Unstage all",
actionTitle = "Unstage", actionTitle = "Unstage",
selectedEntryType = selectedEntryType, selectedEntryType = if(selectedEntryType is DiffEntryType.StagedDiff) selectedEntryType else null,
actionColor = MaterialTheme.colors.unstageButton, actionColor = MaterialTheme.colors.unstageButton,
diffEntries = staged, statusEntries = staged,
onDiffEntrySelected = onStagedDiffEntrySelected, onDiffEntrySelected = onStagedDiffEntrySelected,
onDiffEntryOptionSelected = { onDiffEntryOptionSelected = {
statusViewModel.unstage(it) statusViewModel.unstage(it)
@ -127,7 +115,7 @@ fun UncommitedChanges(
}, },
onAllAction = { onAllAction = {
statusViewModel.unstageAll() statusViewModel.unstageAll()
} },
) )
EntriesList( EntriesList(
@ -137,20 +125,21 @@ fun UncommitedChanges(
.fillMaxWidth(), .fillMaxWidth(),
title = "Unstaged", title = "Unstaged",
actionTitle = "Stage", actionTitle = "Stage",
selectedEntryType = if(selectedEntryType is DiffEntryType.UnstagedDiff) selectedEntryType else null,
actionColor = MaterialTheme.colors.stageButton, actionColor = MaterialTheme.colors.stageButton,
diffEntries = unstaged, statusEntries = unstaged,
onDiffEntrySelected = onUnstagedDiffEntrySelected, onDiffEntrySelected = onUnstagedDiffEntrySelected,
onDiffEntryOptionSelected = { onDiffEntryOptionSelected = {
statusViewModel.stage(it) statusViewModel.stage(it)
}, },
onGenerateContextMenu = { diffEntry -> onGenerateContextMenu = { statusEntry ->
unstagedEntriesContextMenuItems( unstagedEntriesContextMenuItems(
diffEntry = diffEntry, statusEntry = statusEntry,
onReset = { onReset = {
statusViewModel.resetUnstaged(diffEntry) statusViewModel.resetUnstaged(statusEntry)
}, },
onDelete = { onDelete = {
statusViewModel.deleteFile(diffEntry) statusViewModel.deleteFile(statusEntry)
} }
) )
}, },
@ -158,7 +147,6 @@ fun UncommitedChanges(
statusViewModel.stageAll() statusViewModel.stageAll()
}, },
allActionTitle = "Stage all", allActionTitle = "Stage all",
selectedEntryType = selectedEntryType
) )
Column( Column(
@ -392,46 +380,6 @@ fun ConfirmationButton(
} }
} }
// TODO: This logic should be part of the diffViewModel where it gets the latest version of the diffEntry
fun checkIfSelectedEntryShouldBeUpdated(
selectedEntryType: DiffEntryType,
staged: List<StatusEntry>,
unstaged: List<StatusEntry>,
onStagedDiffEntrySelected: (DiffEntry?) -> Unit,
onUnstagedDiffEntrySelected: (DiffEntry) -> Unit,
) {
val selectedDiffEntry = selectedEntryType.diffEntry
val selectedEntryTypeNewId = selectedDiffEntry.newId.name()
if (selectedEntryType is DiffEntryType.StagedDiff) {
val entryType =
staged.firstOrNull { stagedEntry -> stagedEntry.diffEntry.newPath == selectedDiffEntry.newPath }?.diffEntry
if (
entryType != null &&
selectedEntryTypeNewId != entryType.newId.name()
) {
onStagedDiffEntrySelected(entryType)
} else if (entryType == null) {
onStagedDiffEntrySelected(null)
}
} else if (selectedEntryType is DiffEntryType.UnstagedDiff) {
val entryType = unstaged.firstOrNull { unstagedEntry ->
if (selectedDiffEntry.changeType == DiffEntry.ChangeType.DELETE)
unstagedEntry.diffEntry.oldPath == selectedDiffEntry.oldPath
else
unstagedEntry.diffEntry.newPath == selectedDiffEntry.newPath
}
if (entryType != null) {
onUnstagedDiffEntrySelected(entryType.diffEntry)
} else
onStagedDiffEntrySelected(null)
}
}
@OptIn(ExperimentalAnimationApi::class, ExperimentalFoundationApi::class) @OptIn(ExperimentalAnimationApi::class, ExperimentalFoundationApi::class)
@Composable @Composable
private fun EntriesList( private fun EntriesList(
@ -439,10 +387,10 @@ private fun EntriesList(
title: String, title: String,
actionTitle: String, actionTitle: String,
actionColor: Color, actionColor: Color,
diffEntries: List<StatusEntry>, statusEntries: List<StatusEntry>,
onDiffEntrySelected: (DiffEntry) -> Unit, onDiffEntrySelected: (StatusEntry) -> Unit,
onDiffEntryOptionSelected: (DiffEntry) -> Unit, onDiffEntryOptionSelected: (StatusEntry) -> Unit,
onGenerateContextMenu: (DiffEntry) -> List<ContextMenuItem>, onGenerateContextMenu: (StatusEntry) -> List<ContextMenuItem>,
onAllAction: () -> Unit, onAllAction: () -> Unit,
allActionTitle: String, allActionTitle: String,
selectedEntryType: DiffEntryType?, selectedEntryType: DiffEntryType?,
@ -477,24 +425,25 @@ private fun EntriesList(
.fillMaxSize() .fillMaxSize()
.background(MaterialTheme.colors.background), .background(MaterialTheme.colors.background),
) { ) {
itemsIndexed(diffEntries) { index, statusEntry -> itemsIndexed(statusEntries) { index, statusEntry ->
val diffEntry = statusEntry.diffEntry val isEntrySelected = selectedEntryType != null &&
val isEntrySelected = selectedEntryType?.diffEntry == diffEntry selectedEntryType is DiffEntryType.UncommitedDiff && // Added for smartcast
selectedEntryType.statusEntry == statusEntry
FileEntry( FileEntry(
statusEntry = statusEntry, statusEntry = statusEntry,
isSelected = isEntrySelected, isSelected = isEntrySelected,
actionTitle = actionTitle, actionTitle = actionTitle,
actionColor = actionColor, actionColor = actionColor,
onClick = { onClick = {
onDiffEntrySelected(diffEntry) onDiffEntrySelected(statusEntry)
}, },
onButtonClick = { onButtonClick = {
onDiffEntryOptionSelected(diffEntry) onDiffEntryOptionSelected(statusEntry)
}, },
onGenerateContextMenu = onGenerateContextMenu, onGenerateContextMenu = onGenerateContextMenu,
) )
if (index < diffEntries.size - 1) { if (index < statusEntries.size - 1) {
Divider(modifier = Modifier.fillMaxWidth()) Divider(modifier = Modifier.fillMaxWidth())
} }
} }
@ -514,10 +463,9 @@ private fun FileEntry(
actionColor: Color, actionColor: Color,
onClick: () -> Unit, onClick: () -> Unit,
onButtonClick: () -> Unit, onButtonClick: () -> Unit,
onGenerateContextMenu: (DiffEntry) -> List<ContextMenuItem>, onGenerateContextMenu: (StatusEntry) -> List<ContextMenuItem>,
) { ) {
var active by remember { mutableStateOf(false) } var active by remember { mutableStateOf(false) }
val diffEntry = statusEntry.diffEntry
val textColor: Color val textColor: Color
val secondaryTextColor: Color val secondaryTextColor: Color
@ -547,7 +495,7 @@ private fun FileEntry(
) { ) {
ContextMenuArea( ContextMenuArea(
items = { items = {
onGenerateContextMenu(diffEntry) onGenerateContextMenu(statusEntry)
}, },
) { ) {
Row( Row(
@ -566,9 +514,9 @@ private fun FileEntry(
tint = statusEntry.iconColor, tint = statusEntry.iconColor,
) )
if(diffEntry.parentDirectoryPath.isNotEmpty()) { if(statusEntry.parentDirectoryPath.isNotEmpty()) {
Text( Text(
text = diffEntry.parentDirectoryPath, text = statusEntry.parentDirectoryPath,
modifier = Modifier.weight(1f, fill = false), modifier = Modifier.weight(1f, fill = false),
maxLines = 1, maxLines = 1,
softWrap = false, softWrap = false,
@ -578,7 +526,7 @@ private fun FileEntry(
) )
} }
Text( Text(
text = diffEntry.fileName, text = statusEntry.fileName,
modifier = Modifier.weight(1f, fill = false), modifier = Modifier.weight(1f, fill = false),
maxLines = 1, maxLines = 1,
softWrap = false, softWrap = false,

View File

@ -2,15 +2,17 @@ package app.ui.context_menu
import androidx.compose.foundation.ContextMenuItem import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import app.git.StatusEntry
import app.git.StatusType
import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.DiffEntry
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
fun stagedEntriesContextMenuItems( fun stagedEntriesContextMenuItems(
diffEntry: DiffEntry, diffEntry: StatusEntry,
onReset: () -> Unit, onReset: () -> Unit,
): List<ContextMenuItem> { ): List<ContextMenuItem> {
return mutableListOf<ContextMenuItem>().apply { return mutableListOf<ContextMenuItem>().apply {
if (diffEntry.changeType != DiffEntry.ChangeType.ADD) { if (diffEntry.statusType != StatusType.ADDED) {
add( add(
ContextMenuItem( ContextMenuItem(
label = "Reset", label = "Reset",

View File

@ -2,16 +2,18 @@ package app.ui.context_menu
import androidx.compose.foundation.ContextMenuItem import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import app.git.StatusEntry
import app.git.StatusType
import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.DiffEntry
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
fun unstagedEntriesContextMenuItems( fun unstagedEntriesContextMenuItems(
diffEntry: DiffEntry, statusEntry: StatusEntry,
onReset: () -> Unit, onReset: () -> Unit,
onDelete: () -> Unit, onDelete: () -> Unit,
): List<ContextMenuItem> { ): List<ContextMenuItem> {
return mutableListOf<ContextMenuItem>().apply { return mutableListOf<ContextMenuItem>().apply {
if (diffEntry.changeType != DiffEntry.ChangeType.ADD) { if (statusEntry.statusType != StatusType.ADDED) {
add( add(
ContextMenuItem( ContextMenuItem(
label = "Reset", label = "Reset",
@ -20,7 +22,7 @@ fun unstagedEntriesContextMenuItems(
) )
} }
if (diffEntry.changeType != DiffEntry.ChangeType.DELETE) { if (statusEntry.statusType != StatusType.REMOVED) {
add( add(
ContextMenuItem( ContextMenuItem(
label = "Delete file", label = "Delete file",

View File

@ -590,6 +590,14 @@ fun LogStatusSummary(statusSummary: StatusSummary, modifier: Modifier) {
color = MaterialTheme.colors.deleteFile, color = MaterialTheme.colors.deleteFile,
) )
} }
if (statusSummary.conflictingCount > 0) {
SummaryEntry(
count = statusSummary.conflictingCount,
icon = Icons.Default.Warning,
color = MaterialTheme.colors.conflictFile,
)
}
} }
} }

View File

@ -1,6 +1,7 @@
package app.viewmodels package app.viewmodels
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import app.exceptions.MissingDiffEntryException
import app.git.* import app.git.*
import app.git.diff.DiffResult import app.git.diff.DiffResult
import app.git.diff.Hunk import app.git.diff.Hunk
@ -14,8 +15,7 @@ class DiffViewModel @Inject constructor(
private val diffManager: DiffManager, private val diffManager: DiffManager,
private val statusManager: StatusManager, private val statusManager: StatusManager,
) { ) {
// TODO Maybe use a sealed class instead of a null to represent that a diff is not selected? private val _diffResult = MutableStateFlow<ViewDiffResult>(ViewDiffResult.Loading)
private val _diffResult = MutableStateFlow<ViewDiffResult?>(null)
val diffResult: StateFlow<ViewDiffResult?> = _diffResult val diffResult: StateFlow<ViewDiffResult?> = _diffResult
val lazyListState = MutableStateFlow( val lazyListState = MutableStateFlow(
@ -25,18 +25,23 @@ class DiffViewModel @Inject constructor(
) )
) )
// TODO Cancel job if the user closed the diff view while loading
fun updateDiff(diffEntryType: DiffEntryType) = tabState.runOperation( fun updateDiff(diffEntryType: DiffEntryType) = tabState.runOperation(
refreshType = RefreshType.NONE, refreshType = RefreshType.NONE,
) { git -> ) { git ->
val oldDiffEntryType = _diffResult.value?.diffEntryType var oldDiffEntryType: DiffEntryType? = null
val oldDiffResult = _diffResult.value
_diffResult.value = null if(oldDiffResult is ViewDiffResult.Loaded) {
oldDiffEntryType = oldDiffResult.diffEntryType
}
_diffResult.value = ViewDiffResult.Loading
// If it's a different file or different state (index or workdir), reset the scroll state // If it's a different file or different state (index or workdir), reset the scroll state
if (oldDiffEntryType != null && if (oldDiffEntryType != null &&
(oldDiffEntryType.diffEntry.oldPath != diffEntryType.diffEntry.oldPath || oldDiffEntryType is DiffEntryType.UncommitedDiff && diffEntryType is DiffEntryType.UncommitedDiff &&
oldDiffEntryType.diffEntry.newPath != diffEntryType.diffEntry.newPath || oldDiffEntryType.statusEntry.filePath != diffEntryType.statusEntry.filePath
oldDiffEntryType::class != diffEntryType::class)
) { ) {
lazyListState.value = LazyListState( lazyListState.value = LazyListState(
0, 0,
@ -44,13 +49,14 @@ class DiffViewModel @Inject constructor(
) )
} }
//TODO: Just a workaround when trying to diff binary files
try { try {
val hunks = diffManager.diffFormat(git, diffEntryType) val diffFormat = diffManager.diffFormat(git, diffEntryType)
_diffResult.value = ViewDiffResult(diffEntryType, hunks) _diffResult.value = ViewDiffResult.Loaded(diffEntryType, diffFormat)
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() if(ex is MissingDiffEntryException) {
_diffResult.value = ViewDiffResult(diffEntryType, DiffResult.Text(emptyList())) _diffResult.value = ViewDiffResult.DiffNotFound
} else
ex.printStackTrace()
} }
} }
@ -67,4 +73,9 @@ class DiffViewModel @Inject constructor(
} }
} }
data class ViewDiffResult(val diffEntryType: DiffEntryType, val diffResult: DiffResult)
sealed interface ViewDiffResult {
object Loading: ViewDiffResult
object DiffNotFound: ViewDiffResult
data class Loaded(val diffEntryType: DiffEntryType, val diffResult: DiffResult): ViewDiffResult
}

View File

@ -56,18 +56,14 @@ class LogViewModel @Inject constructor(
val statusSummary = statusManager.getStatusSummary( val statusSummary = statusManager.getStatusSummary(
git = git, git = git,
currentBranch = currentBranch,
repositoryState = repositoryManager.getRepositoryState(git),
) )
val hasUncommitedChanges = val hasUncommitedChanges = statusSummary.total > 0
statusSummary.addedCount + statusSummary.deletedCount + statusSummary.modifiedCount > 0
val log = logManager.loadLog(git, currentBranch, hasUncommitedChanges) val log = logManager.loadLog(git, currentBranch, hasUncommitedChanges)
_logStatus.value = LogStatus.Loaded(hasUncommitedChanges, log, currentBranch, statusSummary) _logStatus.value = LogStatus.Loaded(hasUncommitedChanges, log, currentBranch, statusSummary)
// Remove search filter if the log has been updated // Remove search filter if the log has been updated
// TODO: Should we just update the search instead of closing it?
_logSearchFilterResults.value = LogSearch.NotSearching _logSearchFilterResults.value = LogSearch.NotSearching
} }
@ -163,11 +159,9 @@ class LogViewModel @Inject constructor(
val statsSummary = if (hasUncommitedChanges) { val statsSummary = if (hasUncommitedChanges) {
statusManager.getStatusSummary( statusManager.getStatusSummary(
git = git, git = git,
currentBranch = currentBranch,
repositoryState = repositoryManager.getRepositoryState(git),
) )
} else } else
StatusSummary(0, 0, 0) StatusSummary(0, 0, 0, 0)
val previousLogStatusValue = _logStatus.value val previousLogStatusValue = _logStatus.value

View File

@ -6,7 +6,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.diff.DiffEntry
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@ -27,16 +26,16 @@ class StatusViewModel @Inject constructor(
private var lastUncommitedChangesState = false private var lastUncommitedChangesState = false
fun stage(diffEntry: DiffEntry) = tabState.runOperation( fun stage(statusEntry: StatusEntry) = tabState.runOperation(
refreshType = RefreshType.UNCOMMITED_CHANGES, refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git -> ) { git ->
statusManager.stage(git, diffEntry) statusManager.stage(git, statusEntry)
} }
fun unstage(diffEntry: DiffEntry) = tabState.runOperation( fun unstage(statusEntry: StatusEntry) = tabState.runOperation(
refreshType = RefreshType.UNCOMMITED_CHANGES, refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git -> ) { git ->
statusManager.unstage(git, diffEntry) statusManager.unstage(git, statusEntry)
} }
@ -52,16 +51,16 @@ class StatusViewModel @Inject constructor(
statusManager.stageAll(git) statusManager.stageAll(git)
} }
fun resetStaged(diffEntry: DiffEntry) = tabState.runOperation( fun resetStaged(statusEntry: StatusEntry) = tabState.runOperation(
refreshType = RefreshType.UNCOMMITED_CHANGES, refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git -> ) { git ->
statusManager.reset(git, diffEntry, staged = true) statusManager.reset(git, statusEntry, staged = true)
} }
fun resetUnstaged(diffEntry: DiffEntry) = tabState.runOperation( fun resetUnstaged(statusEntry: StatusEntry) = tabState.runOperation(
refreshType = RefreshType.UNCOMMITED_CHANGES, refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git -> ) { git ->
statusManager.reset(git, diffEntry, staged = false) statusManager.reset(git, statusEntry, staged = false)
} }
private suspend fun loadStatus(git: Git) { private suspend fun loadStatus(git: Git) {
@ -69,10 +68,9 @@ class StatusViewModel @Inject constructor(
try { try {
_stageStatus.value = StageStatus.Loading _stageStatus.value = StageStatus.Loading
val repositoryState = repositoryManager.getRepositoryState(git) val status = statusManager.getStatus(git)
val currentBranchRef = branchesManager.currentBranchRef(git) val staged = statusManager.getStaged(status)
val staged = statusManager.getStaged(git, currentBranchRef, repositoryState) val unstaged = statusManager.getUnstaged(status)
val unstaged = statusManager.getUnstaged(git, repositoryState)
_stageStatus.value = StageStatus.Loaded(staged, unstaged) _stageStatus.value = StageStatus.Loaded(staged, unstaged)
} catch (ex: Exception) { } catch (ex: Exception) {
@ -141,10 +139,10 @@ class StatusViewModel @Inject constructor(
mergeManager.abortMerge(git) mergeManager.abortMerge(git)
} }
fun deleteFile(diffEntry: DiffEntry) = tabState.runOperation( fun deleteFile(statusEntry: StatusEntry) = tabState.runOperation(
refreshType = RefreshType.UNCOMMITED_CHANGES, refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git -> ) { git ->
val path = diffEntry.newPath val path = statusEntry.filePath
val fileToDelete = File(git.repository.directory.parent, path) val fileToDelete = File(git.repository.directory.parent, path)