diff --git a/src/main/kotlin/app/exceptions/MissingDiffEntryException.kt b/src/main/kotlin/app/exceptions/MissingDiffEntryException.kt new file mode 100644 index 0000000..294a3ee --- /dev/null +++ b/src/main/kotlin/app/exceptions/MissingDiffEntryException.kt @@ -0,0 +1,3 @@ +package app.exceptions + +class MissingDiffEntryException(msg: String) : GitnuroException(msg) \ No newline at end of file diff --git a/src/main/kotlin/app/extensions/DiffEntryExtensions.kt b/src/main/kotlin/app/extensions/DiffEntryExtensions.kt index a60a4c3..e037eeb 100644 --- a/src/main/kotlin/app/extensions/DiffEntryExtensions.kt +++ b/src/main/kotlin/app/extensions/DiffEntryExtensions.kt @@ -6,6 +6,7 @@ import androidx.compose.material.icons.filled.* import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import app.git.StatusEntry import app.git.StatusType import app.theme.addFile import app.theme.conflictFile @@ -30,6 +31,26 @@ val DiffEntry.parentDirectoryPath: String "${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 get() { val path = if (this.changeType == DiffEntry.ChangeType.DELETE) { @@ -62,6 +83,18 @@ val StatusType.icon: ImageVector } } +val DiffEntry.icon: ImageVector + get() { + return when (this.changeType) { + DiffEntry.ChangeType.ADD -> Icons.Default.Add + DiffEntry.ChangeType.MODIFY -> Icons.Default.Edit + DiffEntry.ChangeType.DELETE -> Icons.Default.Delete + DiffEntry.ChangeType.COPY -> Icons.Default.Add + DiffEntry.ChangeType.RENAME -> Icons.Default.Refresh + else -> throw NotImplementedError("Unexpected ChangeType") + } + } + val StatusType.iconColor: Color @Composable get() { @@ -71,4 +104,17 @@ val StatusType.iconColor: Color StatusType.REMOVED -> MaterialTheme.colors.error StatusType.CONFLICTING -> MaterialTheme.colors.conflictFile } + } + +val DiffEntry.iconColor: Color + @Composable + get() { + return when (this.changeType) { + DiffEntry.ChangeType.ADD -> MaterialTheme.colors.addFile + DiffEntry.ChangeType.MODIFY -> MaterialTheme.colors.modifyFile + DiffEntry.ChangeType.DELETE -> MaterialTheme.colors.error + DiffEntry.ChangeType.COPY -> MaterialTheme.colors.addFile + DiffEntry.ChangeType.RENAME -> MaterialTheme.colors.modifyFile + else -> throw NotImplementedError("Unexpected ChangeType") + } } \ No newline at end of file diff --git a/src/main/kotlin/app/git/DiffEntryType.kt b/src/main/kotlin/app/git/DiffEntryType.kt index 271378d..a5d967b 100644 --- a/src/main/kotlin/app/git/DiffEntryType.kt +++ b/src/main/kotlin/app/git/DiffEntryType.kt @@ -2,22 +2,24 @@ package app.git import org.eclipse.jgit.diff.DiffEntry -sealed class DiffEntryType(val statusEntry: StatusEntry) { - class CommitDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry) +sealed class DiffEntryType() { + class CommitDiff(val diffEntry: DiffEntry) : DiffEntryType() - sealed class UnstagedDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry) - sealed class StagedDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry) + sealed class UncommitedDiff(val statusEntry: StatusEntry) : DiffEntryType() + + 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] */ - 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] */ - class UnsafeUnstagedDiff(diffEntry: DiffEntry) : UnstagedDiff(diffEntry) + class UnsafeUnstagedDiff(statusEntry: StatusEntry) : UnstagedDiff(statusEntry) - class SafeStagedDiff(diffEntry: DiffEntry) : StagedDiff(diffEntry) - class SafeUnstagedDiff(diffEntry: DiffEntry) : UnstagedDiff(diffEntry) + class SafeStagedDiff(statusEntry: StatusEntry) : StagedDiff(statusEntry) + class SafeUnstagedDiff(statusEntry: StatusEntry) : UnstagedDiff(statusEntry) } diff --git a/src/main/kotlin/app/git/DiffManager.kt b/src/main/kotlin/app/git/DiffManager.kt index eb1abe6..95c89c2 100644 --- a/src/main/kotlin/app/git/DiffManager.kt +++ b/src/main/kotlin/app/git/DiffManager.kt @@ -2,6 +2,7 @@ package app.git import app.di.HunkDiffGeneratorFactory import app.di.RawFileManagerFactory +import app.exceptions.MissingDiffEntryException import app.extensions.fullData import app.git.diff.DiffResult 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.CanonicalTreeParser import org.eclipse.jgit.treewalk.FileTreeIterator +import org.eclipse.jgit.treewalk.filter.PathFilter import java.io.ByteArrayOutputStream import javax.inject.Inject @@ -26,9 +28,9 @@ class DiffManager @Inject constructor( private val hunkDiffGeneratorFactory: HunkDiffGeneratorFactory, ) { suspend fun diffFormat(git: Git, diffEntryType: DiffEntryType): DiffResult = withContext(Dispatchers.IO) { - val diffEntry = diffEntryType.diffEntry val byteArrayOutputStream = ByteArrayOutputStream() val repository = git.repository + val diffEntry: DiffEntry DiffFormatter(byteArrayOutputStream).use { formatter -> formatter.setRepository(repository) @@ -39,6 +41,24 @@ class DiffManager @Inject constructor( 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 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.flush() diff --git a/src/main/kotlin/app/git/StatusManager.kt b/src/main/kotlin/app/git/StatusManager.kt index f92264a..6f1cf3a 100644 --- a/src/main/kotlin/app/git/StatusManager.kt +++ b/src/main/kotlin/app/git/StatusManager.kt @@ -1,8 +1,5 @@ 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.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector @@ -10,7 +7,6 @@ import app.di.RawFileManagerFactory import app.extensions.* import app.git.diff.Hunk import app.git.diff.LineType -import app.theme.conflictFile import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.eclipse.jgit.api.Git @@ -38,14 +34,14 @@ class StatusManager @Inject constructor( return@withContext status.hasUncommittedChanges() || status.hasUntrackedChanges() } - suspend fun stage(git: Git, diffEntry: DiffEntry) = withContext(Dispatchers.IO) { - if (diffEntry.changeType == DiffEntry.ChangeType.DELETE) { + suspend fun stage(git: Git, statusEntry: StatusEntry) = withContext(Dispatchers.IO) { + if (statusEntry.statusType == StatusType.REMOVED) { git.rm() - .addFilepattern(diffEntry.filePath) + .addFilepattern(statusEntry.filePath) .call() } else { git.add() - .addFilepattern(diffEntry.filePath) + .addFilepattern(statusEntry.filePath) .call() } } @@ -180,9 +176,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() - .addPath(diffEntry.filePath) + .addPath(statusEntry.filePath) .call() } @@ -194,17 +190,17 @@ class StatusManager @Inject constructor( .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) { git .reset() - .addPath(diffEntry.filePath) + .addPath(statusEntry.filePath) .call() } git .checkout() - .addPath(diffEntry.filePath) + .addPath(statusEntry.filePath) .call() } @@ -221,7 +217,7 @@ class StatusManager @Inject constructor( .call() } - suspend fun getStaged(git: Git, currentBranch: Ref?, repositoryState: RepositoryState) = + suspend fun getStaged(git: Git) = withContext(Dispatchers.IO) { // TODO Test on an empty repository or with a non-default state like merging or rebasing @@ -275,8 +271,8 @@ class StatusManager @Inject constructor( ) } - suspend fun getStatusSummary(git: Git, currentBranch: Ref?, repositoryState: RepositoryState): StatusSummary { - val staged = getStaged(git, currentBranch, repositoryState) + suspend fun getStatusSummary(git: Git): StatusSummary { + val staged = getStaged(git) val allChanges = staged.toMutableList() val unstaged = getUnstaged(git) @@ -326,7 +322,7 @@ class StatusManager @Inject constructor( } -data class StatusEntry(val entry: String, val statusType: StatusType) { +data class StatusEntry(val filePath: String, val statusType: StatusType) { val icon: ImageVector get() = statusType.icon diff --git a/src/main/kotlin/app/git/diff/HunkDiffGenerator.kt b/src/main/kotlin/app/git/diff/HunkDiffGenerator.kt index 7bc021e..3da8feb 100644 --- a/src/main/kotlin/app/git/diff/HunkDiffGenerator.kt +++ b/src/main/kotlin/app/git/diff/HunkDiffGenerator.kt @@ -50,15 +50,15 @@ class HunkDiffGenerator @AssistedInject constructor( if (rawOld == EntryContent.InvalidObjectBlob || rawNew == EntryContent.InvalidObjectBlob) 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) val hasGeneratedTextDiff = canGenerateTextDiff(rawOld, rawNew) { oldRawText, newRawText -> - diffResult = DiffResult.Text(format(fileHeader, oldRawText, newRawText)) + diffResult = DiffResult.Text(ent, format(fileHeader, oldRawText, newRawText)) } if (!hasGeneratedTextDiff) { - diffResult = DiffResult.NonText(rawOld, rawNew) + diffResult = DiffResult.NonText(ent, rawOld, rawNew) } return diffResult @@ -190,10 +190,17 @@ class HunkDiffGenerator @AssistedInject constructor( } } -sealed class DiffResult { - data class Text(val hunks: List) : DiffResult() - data class NonText( +sealed class DiffResult( + val diffEntry: DiffEntry, +) { + class Text( + diffEntry: DiffEntry, + val hunks: List + ) : DiffResult(diffEntry) + + class NonText( + diffEntry: DiffEntry, val oldBinaryContent: EntryContent, val newBinaryContent: EntryContent, - ) : DiffResult() + ) : DiffResult(diffEntry) } \ No newline at end of file diff --git a/src/main/kotlin/app/ui/CommitChanges.kt b/src/main/kotlin/app/ui/CommitChanges.kt index 9707cad..3f2d657 100644 --- a/src/main/kotlin/app/ui/CommitChanges.kt +++ b/src/main/kotlin/app/ui/CommitChanges.kt @@ -197,7 +197,7 @@ fun CommitLogChanges( val textColor: Color val secondaryTextColor: Color - if (diffSelected?.diffEntry == diffEntry) { + if (diffSelected is DiffEntryType.CommitDiff && diffSelected.diffEntry == diffEntry) { textColor = MaterialTheme.colors.primary secondaryTextColor = MaterialTheme.colors.halfPrimary } else { diff --git a/src/main/kotlin/app/ui/Diff.kt b/src/main/kotlin/app/ui/Diff.kt index 8b22925..bc1b6b8 100644 --- a/src/main/kotlin/app/ui/Diff.kt +++ b/src/main/kotlin/app/ui/Diff.kt @@ -7,9 +7,11 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.selection.DisableSelection import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.IconButton +import androidx.compose.material.LinearProgressIndicator import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -23,6 +25,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import app.git.DiffEntryType import app.git.EntryContent +import app.git.StatusType import app.git.diff.DiffResult import app.git.diff.Hunk import app.git.diff.Line @@ -33,6 +36,7 @@ import app.theme.unstageButton import app.ui.components.ScrollableLazyColumn import app.ui.components.SecondaryButton import app.viewmodels.DiffViewModel +import app.viewmodels.ViewDiffResult import org.eclipse.jgit.diff.DiffEntry import java.io.FileInputStream import java.nio.file.Path @@ -47,22 +51,33 @@ fun Diff( val diffResultState = diffViewModel.diffResult.collectAsState() val viewDiffResult = diffResultState.value ?: return - val diffEntryType = viewDiffResult.diffEntryType - val diffEntry = diffEntryType.diffEntry - val diffResult = viewDiffResult.diffResult - Column( modifier = Modifier .padding(8.dp) .background(MaterialTheme.colors.background) .fillMaxSize() ) { - DiffHeader(diffEntry, onCloseDiffView) - if (diffResult is DiffResult.Text) { - TextDiff(diffEntryType, diffViewModel, diffResult) - } else if (diffResult is DiffResult.NonText) { - NonTextDiff(diffResult) + when (viewDiffResult) { + ViewDiffResult.DiffNotFound -> { onCloseDiffView() } + is ViewDiffResult.Loaded -> { + val diffEntryType = viewDiffResult.diffEntryType + 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 { HunkHeader( hunk = hunk, - diffEntryType = diffEntryType, diffViewModel = diffViewModel, + diffEntryType = diffEntryType, + diffEntry =diffResult.diffEntry, ) } } @@ -200,6 +216,7 @@ fun HunkHeader( hunk: Hunk, diffEntryType: DiffEntryType, diffViewModel: DiffViewModel, + diffEntry: DiffEntry, ) { Row( modifier = Modifier @@ -215,9 +232,12 @@ fun HunkHeader( ) Spacer(modifier = Modifier.weight(1f)) + + // Hunks options are only visible when repository is a normal state (not during merge/rebase) if ( (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 color: Color @@ -234,9 +254,9 @@ fun HunkHeader( backgroundButton = color, onClick = { if (diffEntryType is DiffEntryType.StagedDiff) { - diffViewModel.unstageHunk(diffEntryType.diffEntry, hunk) + diffViewModel.unstageHunk(diffEntry, hunk) } else { - diffViewModel.stageHunk(diffEntryType.diffEntry, hunk) + diffViewModel.stageHunk(diffEntry, hunk) } } ) diff --git a/src/main/kotlin/app/ui/UncommitedChanges.kt b/src/main/kotlin/app/ui/UncommitedChanges.kt index 68e6be9..9965da5 100644 --- a/src/main/kotlin/app/ui/UncommitedChanges.kt +++ b/src/main/kotlin/app/ui/UncommitedChanges.kt @@ -45,16 +45,16 @@ import app.ui.context_menu.stagedEntriesContextMenuItems import app.ui.context_menu.unstagedEntriesContextMenuItems import app.viewmodels.StageStatus import app.viewmodels.StatusViewModel -import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.lib.RepositoryState +import kotlin.reflect.KClass @Composable fun UncommitedChanges( statusViewModel: StatusViewModel, selectedEntryType: DiffEntryType?, repositoryState: RepositoryState, - onStagedDiffEntrySelected: (DiffEntry?) -> Unit, - onUnstagedDiffEntrySelected: (DiffEntry) -> Unit, + onStagedDiffEntrySelected: (StatusEntry?) -> Unit, + onUnstagedDiffEntrySelected: (StatusEntry) -> Unit, ) { val stageStatusState = statusViewModel.stageStatus.collectAsState() var commitMessage by remember { mutableStateOf(statusViewModel.savedCommitMessage) } @@ -66,18 +66,6 @@ fun UncommitedChanges( if (stageStatus is StageStatus.Loaded) { staged = stageStatus.staged unstaged = stageStatus.unstaged - - LaunchedEffect(staged) { - if (selectedEntryType != null) { - checkIfSelectedEntryShouldBeUpdated( - selectedEntryType = selectedEntryType, - staged = staged, - unstaged = unstaged, - onStagedDiffEntrySelected = onStagedDiffEntrySelected, - onUnstagedDiffEntrySelected = onUnstagedDiffEntrySelected, - ) - } - } } else { staged = listOf() unstaged = listOf() // return empty lists if still loading @@ -110,7 +98,7 @@ fun UncommitedChanges( title = "Staged", allActionTitle = "Unstage all", actionTitle = "Unstage", - selectedEntryType = selectedEntryType, + selectedEntryType = if(selectedEntryType is DiffEntryType.StagedDiff) selectedEntryType else null, actionColor = MaterialTheme.colors.unstageButton, statusEntries = staged, onDiffEntrySelected = onStagedDiffEntrySelected, @@ -127,7 +115,7 @@ fun UncommitedChanges( }, onAllAction = { statusViewModel.unstageAll() - } + }, ) EntriesList( @@ -137,20 +125,21 @@ fun UncommitedChanges( .fillMaxWidth(), title = "Unstaged", actionTitle = "Stage", + selectedEntryType = if(selectedEntryType is DiffEntryType.UnstagedDiff) selectedEntryType else null, actionColor = MaterialTheme.colors.stageButton, statusEntries = unstaged, onDiffEntrySelected = onUnstagedDiffEntrySelected, onDiffEntryOptionSelected = { statusViewModel.stage(it) }, - onGenerateContextMenu = { diffEntry -> + onGenerateContextMenu = { statusEntry -> unstagedEntriesContextMenuItems( - statusEntry = diffEntry, + statusEntry = statusEntry, onReset = { - statusViewModel.resetUnstaged(diffEntry) + statusViewModel.resetUnstaged(statusEntry) }, onDelete = { - statusViewModel.deleteFile(diffEntry) + statusViewModel.deleteFile(statusEntry) } ) }, @@ -158,7 +147,6 @@ fun UncommitedChanges( statusViewModel.stageAll() }, allActionTitle = "Stage all", - selectedEntryType = selectedEntryType ) 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, - unstaged: List, - 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) @Composable private fun EntriesList( @@ -478,17 +426,19 @@ private fun EntriesList( .background(MaterialTheme.colors.background), ) { itemsIndexed(statusEntries) { index, statusEntry -> - val isEntrySelected = selectedEntryType?.diffEntry == diffEntry + val isEntrySelected = selectedEntryType != null && + selectedEntryType is DiffEntryType.UncommitedDiff && // Added for smartcast + selectedEntryType.statusEntry == statusEntry FileEntry( statusEntry = statusEntry, isSelected = isEntrySelected, actionTitle = actionTitle, actionColor = actionColor, onClick = { - onDiffEntrySelected(diffEntry) + onDiffEntrySelected(statusEntry) }, onButtonClick = { - onDiffEntryOptionSelected(diffEntry) + onDiffEntryOptionSelected(statusEntry) }, onGenerateContextMenu = onGenerateContextMenu, ) @@ -513,7 +463,7 @@ private fun FileEntry( actionColor: Color, onClick: () -> Unit, onButtonClick: () -> Unit, - onGenerateContextMenu: (DiffEntry) -> List, + onGenerateContextMenu: (StatusEntry) -> List, ) { var active by remember { mutableStateOf(false) } @@ -545,7 +495,7 @@ private fun FileEntry( ) { ContextMenuArea( items = { - onGenerateContextMenu(diffEntry) + onGenerateContextMenu(statusEntry) }, ) { Row( @@ -564,9 +514,9 @@ private fun FileEntry( tint = statusEntry.iconColor, ) - if(diffEntry.parentDirectoryPath.isNotEmpty()) { + if(statusEntry.parentDirectoryPath.isNotEmpty()) { Text( - text = diffEntry.parentDirectoryPath, + text = statusEntry.parentDirectoryPath, modifier = Modifier.weight(1f, fill = false), maxLines = 1, softWrap = false, @@ -576,7 +526,7 @@ private fun FileEntry( ) } Text( - text = diffEntry.fileName, + text = statusEntry.fileName, modifier = Modifier.weight(1f, fill = false), maxLines = 1, softWrap = false, diff --git a/src/main/kotlin/app/ui/context_menu/StagedEntriesContextMenu.kt b/src/main/kotlin/app/ui/context_menu/StagedEntriesContextMenu.kt index 899fd50..b9d2f10 100644 --- a/src/main/kotlin/app/ui/context_menu/StagedEntriesContextMenu.kt +++ b/src/main/kotlin/app/ui/context_menu/StagedEntriesContextMenu.kt @@ -2,15 +2,17 @@ package app.ui.context_menu import androidx.compose.foundation.ContextMenuItem import androidx.compose.foundation.ExperimentalFoundationApi +import app.git.StatusEntry +import app.git.StatusType import org.eclipse.jgit.diff.DiffEntry @OptIn(ExperimentalFoundationApi::class) fun stagedEntriesContextMenuItems( - diffEntry: DiffEntry, + diffEntry: StatusEntry, onReset: () -> Unit, ): List { return mutableListOf().apply { - if (diffEntry.changeType != DiffEntry.ChangeType.ADD) { + if (diffEntry.statusType != StatusType.ADDED) { add( ContextMenuItem( label = "Reset", diff --git a/src/main/kotlin/app/viewmodels/DiffViewModel.kt b/src/main/kotlin/app/viewmodels/DiffViewModel.kt index 18550d5..8d1e5c4 100644 --- a/src/main/kotlin/app/viewmodels/DiffViewModel.kt +++ b/src/main/kotlin/app/viewmodels/DiffViewModel.kt @@ -1,6 +1,7 @@ package app.viewmodels import androidx.compose.foundation.lazy.LazyListState +import app.exceptions.MissingDiffEntryException import app.git.* import app.git.diff.DiffResult import app.git.diff.Hunk @@ -15,7 +16,7 @@ class DiffViewModel @Inject constructor( 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(null) + private val _diffResult = MutableStateFlow(ViewDiffResult.Loading) val diffResult: StateFlow = _diffResult val lazyListState = MutableStateFlow( @@ -25,18 +26,23 @@ class DiffViewModel @Inject constructor( ) ) + // TODO Cancel job if the user closed the diff view while loading fun updateDiff(diffEntryType: DiffEntryType) = tabState.runOperation( refreshType = RefreshType.NONE, ) { 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 (oldDiffEntryType != null && - (oldDiffEntryType.diffEntry.oldPath != diffEntryType.diffEntry.oldPath || - oldDiffEntryType.diffEntry.newPath != diffEntryType.diffEntry.newPath || - oldDiffEntryType::class != diffEntryType::class) + oldDiffEntryType is DiffEntryType.UncommitedDiff && diffEntryType is DiffEntryType.UncommitedDiff && + oldDiffEntryType.statusEntry.filePath != diffEntryType.statusEntry.filePath ) { lazyListState.value = LazyListState( 0, @@ -44,13 +50,14 @@ class DiffViewModel @Inject constructor( ) } - //TODO: Just a workaround when trying to diff binary files try { - val hunks = diffManager.diffFormat(git, diffEntryType) - _diffResult.value = ViewDiffResult(diffEntryType, hunks) + val diffFormat = diffManager.diffFormat(git, diffEntryType) + _diffResult.value = ViewDiffResult.Loaded(diffEntryType, diffFormat) } catch (ex: Exception) { - ex.printStackTrace() - _diffResult.value = ViewDiffResult(diffEntryType, DiffResult.Text(emptyList())) + if(ex is MissingDiffEntryException) { + _diffResult.value = ViewDiffResult.DiffNotFound + } else + ex.printStackTrace() } } @@ -67,4 +74,9 @@ class DiffViewModel @Inject constructor( } } -data class ViewDiffResult(val diffEntryType: DiffEntryType, val diffResult: DiffResult) \ No newline at end of file + +sealed interface ViewDiffResult { + object Loading: ViewDiffResult + object DiffNotFound: ViewDiffResult + data class Loaded(val diffEntryType: DiffEntryType, val diffResult: DiffResult): ViewDiffResult +} diff --git a/src/main/kotlin/app/viewmodels/LogViewModel.kt b/src/main/kotlin/app/viewmodels/LogViewModel.kt index 98e4f06..e2faaf8 100644 --- a/src/main/kotlin/app/viewmodels/LogViewModel.kt +++ b/src/main/kotlin/app/viewmodels/LogViewModel.kt @@ -56,8 +56,6 @@ class LogViewModel @Inject constructor( val statusSummary = statusManager.getStatusSummary( git = git, - currentBranch = currentBranch, - repositoryState = repositoryManager.getRepositoryState(git), ) val hasUncommitedChanges = @@ -163,8 +161,6 @@ class LogViewModel @Inject constructor( val statsSummary = if (hasUncommitedChanges) { statusManager.getStatusSummary( git = git, - currentBranch = currentBranch, - repositoryState = repositoryManager.getRepositoryState(git), ) } else StatusSummary(0, 0, 0) diff --git a/src/main/kotlin/app/viewmodels/StatusViewModel.kt b/src/main/kotlin/app/viewmodels/StatusViewModel.kt index ff5e504..0b58084 100644 --- a/src/main/kotlin/app/viewmodels/StatusViewModel.kt +++ b/src/main/kotlin/app/viewmodels/StatusViewModel.kt @@ -6,7 +6,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withContext import org.eclipse.jgit.api.Git -import org.eclipse.jgit.diff.DiffEntry import java.io.File import javax.inject.Inject @@ -27,16 +26,16 @@ class StatusViewModel @Inject constructor( private var lastUncommitedChangesState = false - fun stage(diffEntry: DiffEntry) = tabState.runOperation( + fun stage(statusEntry: StatusEntry) = tabState.runOperation( refreshType = RefreshType.UNCOMMITED_CHANGES, ) { 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, ) { git -> - statusManager.unstage(git, diffEntry) + statusManager.unstage(git, statusEntry) } @@ -52,16 +51,16 @@ class StatusViewModel @Inject constructor( statusManager.stageAll(git) } - fun resetStaged(diffEntry: DiffEntry) = tabState.runOperation( + fun resetStaged(statusEntry: StatusEntry) = tabState.runOperation( refreshType = RefreshType.UNCOMMITED_CHANGES, ) { 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, ) { git -> - statusManager.reset(git, diffEntry, staged = false) + statusManager.reset(git, statusEntry, staged = false) } private suspend fun loadStatus(git: Git) { @@ -69,9 +68,7 @@ class StatusViewModel @Inject constructor( try { _stageStatus.value = StageStatus.Loading - val repositoryState = repositoryManager.getRepositoryState(git) - val currentBranchRef = branchesManager.currentBranchRef(git) - val staged = statusManager.getStaged(git, currentBranchRef, repositoryState) + val staged = statusManager.getStaged(git) val unstaged = statusManager.getUnstaged(git) _stageStatus.value = StageStatus.Loaded(staged, unstaged) @@ -141,10 +138,10 @@ class StatusViewModel @Inject constructor( mergeManager.abortMerge(git) } - fun deleteFile(diffEntry: DiffEntry) = tabState.runOperation( + fun deleteFile(statusEntry: StatusEntry) = tabState.runOperation( refreshType = RefreshType.UNCOMMITED_CHANGES, ) { git -> - val path = diffEntry.newPath + val path = statusEntry.filePath val fileToDelete = File(git.repository.directory.parent, path)