[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.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
}
}

View File

@ -2,4 +2,14 @@ package app.extensions
fun <T> List<T>?.countOrZero(): Int {
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
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)

View File

@ -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<StatusEntry>
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)

View File

@ -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<StatusEntry>,
onDiffEntrySelected: (DiffEntry) -> Unit,
onDiffEntryOptionSelected: (DiffEntry) -> Unit,
onGenerateContextMenu: (DiffEntry) -> List<ContextMenuItem>,
statusEntries: List<StatusEntry>,
onDiffEntrySelected: (StatusEntry) -> Unit,
onDiffEntryOptionSelected: (StatusEntry) -> Unit,
onGenerateContextMenu: (StatusEntry) -> List<ContextMenuItem>,
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<ContextMenuItem>,
) {
var active by remember { mutableStateOf(false) }
val diffEntry = statusEntry.diffEntry
val textColor: Color
val secondaryTextColor: Color

View File

@ -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<ContextMenuItem> {
return mutableListOf<ContextMenuItem>().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",

View File

@ -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) {