[WIP] started refactor of status

This commit is contained in:
Abdelilah El Aissaoui 2022-04-06 17:49:38 +02:00
parent 6b8a7d14a6
commit 4108537825
7 changed files with 97 additions and 109 deletions

View File

@ -2,14 +2,13 @@ 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.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
@ -53,27 +52,23 @@ val DiffEntry.filePath: String
return path return path
} }
val DiffEntry.icon: ImageVector val StatusType.icon: ImageVector
get() { get() {
return when (this.changeType) { return when (this) {
DiffEntry.ChangeType.ADD -> Icons.Default.Add StatusType.ADDED -> Icons.Default.Add
DiffEntry.ChangeType.MODIFY -> Icons.Default.Edit StatusType.MODIFIED -> Icons.Default.Edit
DiffEntry.ChangeType.DELETE -> Icons.Default.Delete StatusType.REMOVED -> Icons.Default.Delete
DiffEntry.ChangeType.COPY -> Icons.Default.Add StatusType.CONFLICTING -> Icons.Default.Warning
DiffEntry.ChangeType.RENAME -> Icons.Default.Refresh
else -> throw NotImplementedError("Unexpected ChangeType")
} }
} }
val DiffEntry.iconColor: Color val StatusType.iconColor: Color
@Composable @Composable
get() { get() {
return when (this.changeType) { return when (this) {
DiffEntry.ChangeType.ADD -> MaterialTheme.colors.addFile StatusType.ADDED -> MaterialTheme.colors.addFile
DiffEntry.ChangeType.MODIFY -> MaterialTheme.colors.modifyFile StatusType.MODIFIED -> MaterialTheme.colors.modifyFile
DiffEntry.ChangeType.DELETE -> MaterialTheme.colors.error StatusType.REMOVED -> MaterialTheme.colors.error
DiffEntry.ChangeType.COPY -> MaterialTheme.colors.addFile StatusType.CONFLICTING -> MaterialTheme.colors.conflictFile
DiffEntry.ChangeType.RENAME -> MaterialTheme.colors.modifyFile
else -> throw NotImplementedError("Unexpected ChangeType")
} }
} }

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,7 +2,7 @@ package app.git
import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.DiffEntry
sealed class DiffEntryType(val diffEntry: DiffEntry) { sealed class DiffEntryType(val statusEntry: StatusEntry) {
class CommitDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry) class CommitDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry)
sealed class UnstagedDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry) sealed class UnstagedDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry)

View File

@ -19,8 +19,6 @@ 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.*
import org.eclipse.jgit.submodule.SubmoduleStatusType
import org.eclipse.jgit.treewalk.EmptyTreeIterator
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.IOException import java.io.IOException
import java.nio.ByteBuffer import java.nio.ByteBuffer
@ -225,92 +223,79 @@ class StatusManager @Inject constructor(
suspend fun getStaged(git: Git, currentBranch: Ref?, repositoryState: RepositoryState) = suspend fun getStaged(git: Git, currentBranch: Ref?, repositoryState: RepositoryState) =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val statusEntries: List<StatusEntry>
val status = git.diff() // TODO Test on an empty repository or with a non-default state like merging or rebasing
.setShowNameAndStatusOnly(true).apply { val statusResult = git
if (currentBranch == null && !repositoryState.isMerging && !repositoryState.isRebasing) .status()
setOldTree(EmptyTreeIterator()) // Required if the repository is empty
setCached(true)
}
.call() .call()
statusEntries = if (repositoryState.isMerging || repositoryState.isRebasing) { val added = statusResult.added.map {
status.groupBy { StatusEntry(it, StatusType.ADDED)
if (it.newPath != "/dev/null") }
it.newPath val modified = statusResult.changed.map {
else StatusEntry(it, StatusType.MODIFIED)
it.oldPath }
} val removed = statusResult.removed.map {
.map { StatusEntry(it, StatusType.REMOVED)
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 return@withContext flatListOf(
added,
modified,
removed,
)
} }
suspend fun getUnstaged(git: Git, repositoryState: RepositoryState) = withContext(Dispatchers.IO) { suspend fun getUnstaged(git: Git) = withContext(Dispatchers.IO) {
val uninitializedSubmodules = submodulesManager.uninitializedSubmodules(git) // TODO Test uninitialized modules after the refactor
// val uninitializedSubmodules = submodulesManager.uninitializedSubmodules(git)
return@withContext git val statusResult = git
.diff() .status()
.call() .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 = val added = statusResult.untracked.map {
(entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing)) 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 { suspend fun getStatusSummary(git: Git, currentBranch: Ref?, repositoryState: RepositoryState): StatusSummary {
val staged = getStaged(git, currentBranch, repositoryState) val staged = getStaged(git, currentBranch, repositoryState)
val allChanges = staged.toMutableList() val allChanges = staged.toMutableList()
val unstaged = getUnstaged(git, repositoryState) val unstaged = getUnstaged(git)
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() +
changesGrouped[DiffEntry.ChangeType.COPY].countOrZero()
return StatusSummary( return StatusSummary(
modifiedCount = modifiedCount, 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 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 enum class StatusType {
diffEntry.iconColor ADDED,
} MODIFIED,
REMOVED,
CONFLICTING,
} }
data class StatusSummary(val modifiedCount: Int, val deletedCount: Int, val addedCount: Int) data class StatusSummary(val modifiedCount: Int, val deletedCount: Int, val addedCount: Int)

View File

@ -112,7 +112,7 @@ fun UncommitedChanges(
actionTitle = "Unstage", actionTitle = "Unstage",
selectedEntryType = selectedEntryType, selectedEntryType = selectedEntryType,
actionColor = MaterialTheme.colors.unstageButton, actionColor = MaterialTheme.colors.unstageButton,
diffEntries = staged, statusEntries = staged,
onDiffEntrySelected = onStagedDiffEntrySelected, onDiffEntrySelected = onStagedDiffEntrySelected,
onDiffEntryOptionSelected = { onDiffEntryOptionSelected = {
statusViewModel.unstage(it) statusViewModel.unstage(it)
@ -138,14 +138,14 @@ fun UncommitedChanges(
title = "Unstaged", title = "Unstaged",
actionTitle = "Stage", actionTitle = "Stage",
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 = { diffEntry ->
unstagedEntriesContextMenuItems( unstagedEntriesContextMenuItems(
diffEntry = diffEntry, statusEntry = diffEntry,
onReset = { onReset = {
statusViewModel.resetUnstaged(diffEntry) statusViewModel.resetUnstaged(diffEntry)
}, },
@ -439,10 +439,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,8 +477,7 @@ 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?.diffEntry == diffEntry val isEntrySelected = selectedEntryType?.diffEntry == diffEntry
FileEntry( FileEntry(
statusEntry = statusEntry, statusEntry = statusEntry,
@ -494,7 +493,7 @@ private fun EntriesList(
onGenerateContextMenu = onGenerateContextMenu, onGenerateContextMenu = onGenerateContextMenu,
) )
if (index < diffEntries.size - 1) { if (index < statusEntries.size - 1) {
Divider(modifier = Modifier.fillMaxWidth()) Divider(modifier = Modifier.fillMaxWidth())
} }
} }
@ -517,7 +516,6 @@ private fun FileEntry(
onGenerateContextMenu: (DiffEntry) -> List<ContextMenuItem>, onGenerateContextMenu: (DiffEntry) -> 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

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

@ -72,7 +72,7 @@ class StatusViewModel @Inject constructor(
val repositoryState = repositoryManager.getRepositoryState(git) val repositoryState = repositoryManager.getRepositoryState(git)
val currentBranchRef = branchesManager.currentBranchRef(git) val currentBranchRef = branchesManager.currentBranchRef(git)
val staged = statusManager.getStaged(git, currentBranchRef, repositoryState) val staged = statusManager.getStaged(git, currentBranchRef, repositoryState)
val unstaged = statusManager.getUnstaged(git, repositoryState) val unstaged = statusManager.getUnstaged(git)
_stageStatus.value = StageStatus.Loaded(staged, unstaged) _stageStatus.value = StageStatus.Loaded(staged, unstaged)
} catch (ex: Exception) { } catch (ex: Exception) {