diff --git a/src/main/kotlin/app/extensions/DiffEntryExtensions.kt b/src/main/kotlin/app/extensions/DiffEntryExtensions.kt index a4b1ce2..a60a4c3 100644 --- a/src/main/kotlin/app/extensions/DiffEntryExtensions.kt +++ b/src/main/kotlin/app/extensions/DiffEntryExtensions.kt @@ -2,14 +2,13 @@ package app.extensions import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.Edit -import androidx.compose.material.icons.filled.Refresh +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.StatusType import app.theme.addFile +import app.theme.conflictFile import app.theme.modifyFile import org.eclipse.jgit.diff.DiffEntry @@ -53,27 +52,23 @@ val DiffEntry.filePath: String return path } -val DiffEntry.icon: ImageVector +val StatusType.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") + 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.iconColor: Color +val StatusType.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") + return when (this) { + StatusType.ADDED -> MaterialTheme.colors.addFile + StatusType.MODIFIED -> MaterialTheme.colors.modifyFile + StatusType.REMOVED -> MaterialTheme.colors.error + StatusType.CONFLICTING -> MaterialTheme.colors.conflictFile } } \ No newline at end of file diff --git a/src/main/kotlin/app/extensions/ListExtensions.kt b/src/main/kotlin/app/extensions/ListExtensions.kt index b3471d6..0806271 100644 --- a/src/main/kotlin/app/extensions/ListExtensions.kt +++ b/src/main/kotlin/app/extensions/ListExtensions.kt @@ -2,4 +2,14 @@ package app.extensions fun List?.countOrZero(): Int { return this?.count() ?: 0 +} + +fun flatListOf(vararg lists: List): List { + val flatList = mutableListOf() + + for(list in lists) { + flatList.addAll(list) + } + + return flatList } \ 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 f4d2ed3..271378d 100644 --- a/src/main/kotlin/app/git/DiffEntryType.kt +++ b/src/main/kotlin/app/git/DiffEntryType.kt @@ -2,7 +2,7 @@ package app.git import org.eclipse.jgit.diff.DiffEntry -sealed class DiffEntryType(val diffEntry: DiffEntry) { +sealed class DiffEntryType(val statusEntry: StatusEntry) { class CommitDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry) sealed class UnstagedDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry) diff --git a/src/main/kotlin/app/git/StatusManager.kt b/src/main/kotlin/app/git/StatusManager.kt index 8746fe1..f92264a 100644 --- a/src/main/kotlin/app/git/StatusManager.kt +++ b/src/main/kotlin/app/git/StatusManager.kt @@ -19,8 +19,6 @@ import org.eclipse.jgit.diff.RawText import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit import org.eclipse.jgit.dircache.DirCacheEntry import org.eclipse.jgit.lib.* -import org.eclipse.jgit.submodule.SubmoduleStatusType -import org.eclipse.jgit.treewalk.EmptyTreeIterator import java.io.ByteArrayInputStream import java.io.IOException import java.nio.ByteBuffer @@ -225,92 +223,79 @@ class StatusManager @Inject constructor( suspend fun getStaged(git: Git, currentBranch: Ref?, repositoryState: RepositoryState) = withContext(Dispatchers.IO) { - val statusEntries: List - val status = git.diff() - .setShowNameAndStatusOnly(true).apply { - if (currentBranch == null && !repositoryState.isMerging && !repositoryState.isRebasing) - setOldTree(EmptyTreeIterator()) // Required if the repository is empty - - setCached(true) - } + // TODO Test on an empty repository or with a non-default state like merging or rebasing + val statusResult = git + .status() .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) - } + val added = statusResult.added.map { + StatusEntry(it, StatusType.ADDED) + } + val modified = statusResult.changed.map { + StatusEntry(it, StatusType.MODIFIED) + } + val removed = statusResult.removed.map { + StatusEntry(it, StatusType.REMOVED) } - return@withContext statusEntries + return@withContext flatListOf( + added, + modified, + removed, + ) } - suspend fun getUnstaged(git: Git, repositoryState: RepositoryState) = withContext(Dispatchers.IO) { - val uninitializedSubmodules = submodulesManager.uninitializedSubmodules(git) + suspend fun getUnstaged(git: Git) = withContext(Dispatchers.IO) { + // TODO Test uninitialized modules after the refactor +// val uninitializedSubmodules = submodulesManager.uninitializedSubmodules(git) - return@withContext git - .diff() + val statusResult = git + .status() .call() - .filter { - !uninitializedSubmodules.containsKey(it.oldPath) // Filter out uninitialized modules directories - } - .groupBy { - if (it.oldPath != "/dev/null") - it.oldPath - else - it.newPath - } - .map { - val entries = it.value - val hasConflicts = - (entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing)) + val added = statusResult.untracked.map { + StatusEntry(it, StatusType.ADDED) + } + val modified = statusResult.modified.map { + StatusEntry(it, StatusType.MODIFIED) + } + val removed = statusResult.missing.map { + StatusEntry(it, StatusType.REMOVED) + } + val conflicting = statusResult.conflicting.map { + StatusEntry(it, StatusType.CONFLICTING) + } - StatusEntry(entries.first(), isConflict = hasConflicts) - } + return@withContext flatListOf( + added, + modified, + removed, + conflicting, + ) } suspend fun getStatusSummary(git: Git, currentBranch: Ref?, repositoryState: RepositoryState): StatusSummary { val staged = getStaged(git, currentBranch, repositoryState) val allChanges = staged.toMutableList() - val unstaged = getUnstaged(git, repositoryState) + val unstaged = getUnstaged(git) allChanges.addAll(unstaged) val groupedChanges = allChanges.groupBy { - if (it.diffEntry.newPath != "/dev/null") - it.diffEntry.newPath - else - it.diffEntry.oldPath + } val changesGrouped = groupedChanges.map { it.value }.flatten() .groupBy { - it.diffEntry.changeType + it.statusType } - val deletedCount = changesGrouped[DiffEntry.ChangeType.DELETE].countOrZero() - val addedCount = changesGrouped[DiffEntry.ChangeType.ADD].countOrZero() + val deletedCount = changesGrouped[StatusType.REMOVED].countOrZero() + val addedCount = changesGrouped[StatusType.ADDED].countOrZero() - val modifiedCount = changesGrouped[DiffEntry.ChangeType.MODIFY].countOrZero() + - changesGrouped[DiffEntry.ChangeType.RENAME].countOrZero() + - changesGrouped[DiffEntry.ChangeType.COPY].countOrZero() + val modifiedCount = changesGrouped[StatusType.MODIFIED].countOrZero() return StatusSummary( modifiedCount = modifiedCount, @@ -341,22 +326,20 @@ class StatusManager @Inject constructor( } -data class StatusEntry(val diffEntry: DiffEntry, val isConflict: Boolean) { +data class StatusEntry(val entry: String, val statusType: StatusType) { val icon: ImageVector - get() { - return if (isConflict) - Icons.Default.Warning - else - diffEntry.icon - } + get() = statusType.icon + val iconColor: Color @Composable - get() { - return if (isConflict) - MaterialTheme.colors.conflictFile - else - diffEntry.iconColor - } + get() = statusType.iconColor +} + +enum class StatusType { + ADDED, + MODIFIED, + REMOVED, + CONFLICTING, } data class StatusSummary(val modifiedCount: Int, val deletedCount: Int, val addedCount: Int) \ No newline at end of file diff --git a/src/main/kotlin/app/ui/UncommitedChanges.kt b/src/main/kotlin/app/ui/UncommitedChanges.kt index 03ceab5..68e6be9 100644 --- a/src/main/kotlin/app/ui/UncommitedChanges.kt +++ b/src/main/kotlin/app/ui/UncommitedChanges.kt @@ -112,7 +112,7 @@ fun UncommitedChanges( actionTitle = "Unstage", selectedEntryType = selectedEntryType, actionColor = MaterialTheme.colors.unstageButton, - diffEntries = staged, + statusEntries = staged, onDiffEntrySelected = onStagedDiffEntrySelected, onDiffEntryOptionSelected = { statusViewModel.unstage(it) @@ -138,14 +138,14 @@ fun UncommitedChanges( title = "Unstaged", actionTitle = "Stage", actionColor = MaterialTheme.colors.stageButton, - diffEntries = unstaged, + statusEntries = unstaged, onDiffEntrySelected = onUnstagedDiffEntrySelected, onDiffEntryOptionSelected = { statusViewModel.stage(it) }, onGenerateContextMenu = { diffEntry -> unstagedEntriesContextMenuItems( - diffEntry = diffEntry, + statusEntry = diffEntry, onReset = { statusViewModel.resetUnstaged(diffEntry) }, @@ -439,10 +439,10 @@ private fun EntriesList( title: String, actionTitle: String, actionColor: Color, - diffEntries: List, - onDiffEntrySelected: (DiffEntry) -> Unit, - onDiffEntryOptionSelected: (DiffEntry) -> Unit, - onGenerateContextMenu: (DiffEntry) -> List, + statusEntries: List, + onDiffEntrySelected: (StatusEntry) -> Unit, + onDiffEntryOptionSelected: (StatusEntry) -> Unit, + onGenerateContextMenu: (StatusEntry) -> List, onAllAction: () -> Unit, allActionTitle: String, selectedEntryType: DiffEntryType?, @@ -477,8 +477,7 @@ private fun EntriesList( .fillMaxSize() .background(MaterialTheme.colors.background), ) { - itemsIndexed(diffEntries) { index, statusEntry -> - val diffEntry = statusEntry.diffEntry + itemsIndexed(statusEntries) { index, statusEntry -> val isEntrySelected = selectedEntryType?.diffEntry == diffEntry FileEntry( statusEntry = statusEntry, @@ -494,7 +493,7 @@ private fun EntriesList( onGenerateContextMenu = onGenerateContextMenu, ) - if (index < diffEntries.size - 1) { + if (index < statusEntries.size - 1) { Divider(modifier = Modifier.fillMaxWidth()) } } @@ -517,7 +516,6 @@ private fun FileEntry( onGenerateContextMenu: (DiffEntry) -> List, ) { var active by remember { mutableStateOf(false) } - val diffEntry = statusEntry.diffEntry val textColor: Color val secondaryTextColor: Color diff --git a/src/main/kotlin/app/ui/context_menu/UnstagedEntriesContextMenu.kt b/src/main/kotlin/app/ui/context_menu/UnstagedEntriesContextMenu.kt index d49ed07..78fbb41 100644 --- a/src/main/kotlin/app/ui/context_menu/UnstagedEntriesContextMenu.kt +++ b/src/main/kotlin/app/ui/context_menu/UnstagedEntriesContextMenu.kt @@ -2,16 +2,18 @@ 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 unstagedEntriesContextMenuItems( - diffEntry: DiffEntry, + statusEntry: StatusEntry, onReset: () -> Unit, onDelete: () -> Unit, ): List { return mutableListOf().apply { - if (diffEntry.changeType != DiffEntry.ChangeType.ADD) { + if (statusEntry.statusType != StatusType.ADDED) { add( ContextMenuItem( label = "Reset", @@ -20,7 +22,7 @@ fun unstagedEntriesContextMenuItems( ) } - if (diffEntry.changeType != DiffEntry.ChangeType.DELETE) { + if (statusEntry.statusType != StatusType.REMOVED) { add( ContextMenuItem( label = "Delete file", diff --git a/src/main/kotlin/app/viewmodels/StatusViewModel.kt b/src/main/kotlin/app/viewmodels/StatusViewModel.kt index eed3378..ff5e504 100644 --- a/src/main/kotlin/app/viewmodels/StatusViewModel.kt +++ b/src/main/kotlin/app/viewmodels/StatusViewModel.kt @@ -72,7 +72,7 @@ class StatusViewModel @Inject constructor( val repositoryState = repositoryManager.getRepositoryState(git) val currentBranchRef = branchesManager.currentBranchRef(git) val staged = statusManager.getStaged(git, currentBranchRef, repositoryState) - val unstaged = statusManager.getUnstaged(git, repositoryState) + val unstaged = statusManager.getUnstaged(git) _stageStatus.value = StageStatus.Loaded(staged, unstaged) } catch (ex: Exception) {