[WIP] started refactor of status
This commit is contained in:
parent
6b8a7d14a6
commit
4108537825
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user