[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.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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,3 +3,13 @@ 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
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -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)
|
@ -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
|
||||||
|
@ -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",
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user