Added uncommited files count to "uncommited changes" line in the log
Also improved log performance when a file has changed to only update the header and not the whole list
This commit is contained in:
parent
72e77f41fd
commit
fff18b7fef
5
src/main/kotlin/app/extensions/ListExtensions.kt
Normal file
5
src/main/kotlin/app/extensions/ListExtensions.kt
Normal file
@ -0,0 +1,5 @@
|
||||
package app.extensions
|
||||
|
||||
fun <T> List<T>?.countOrZero(): Int {
|
||||
return this?.count() ?: 0
|
||||
}
|
@ -98,8 +98,8 @@ class StatusManager @Inject constructor(
|
||||
val dirCache = repository.lockDirCache()
|
||||
val dirCacheEditor = dirCache.editor()
|
||||
var completedWithErrors = true
|
||||
try {
|
||||
|
||||
try {
|
||||
val rawFileManager = rawFileManagerFactory.create(git.repository)
|
||||
val entryContent = rawFileManager.getRawContent(DiffEntry.Side.NEW, diffEntry)
|
||||
|
||||
@ -140,8 +140,6 @@ class StatusManager @Inject constructor(
|
||||
dirCacheEditor.commit()
|
||||
|
||||
completedWithErrors = false
|
||||
|
||||
// loadStatus(git)
|
||||
} finally {
|
||||
if (completedWithErrors)
|
||||
dirCache.unlock()
|
||||
@ -208,8 +206,6 @@ class StatusManager @Inject constructor(
|
||||
.checkout()
|
||||
.addPath(diffEntry.filePath)
|
||||
.call()
|
||||
|
||||
// loadStatus(git)
|
||||
}
|
||||
|
||||
suspend fun unstageAll(git: Git) = withContext(Dispatchers.IO) {
|
||||
@ -227,8 +223,9 @@ class StatusManager @Inject constructor(
|
||||
|
||||
suspend fun getStaged(git: Git, currentBranch: Ref?, repositoryState: RepositoryState) =
|
||||
withContext(Dispatchers.IO) {
|
||||
return@withContext git
|
||||
.diff()
|
||||
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
|
||||
@ -236,22 +233,28 @@ class StatusManager @Inject constructor(
|
||||
setCached(true)
|
||||
}
|
||||
.call()
|
||||
// TODO: Grouping and fitlering allows us to remove duplicates when conflicts appear, requires more testing (what happens in windows? /dev/null is a unix thing)
|
||||
// TODO: Test if we should group by old path or new path
|
||||
.groupBy {
|
||||
|
||||
statusEntries = if(repositoryState.isMerging || repositoryState.isRebasing) {
|
||||
status.groupBy {
|
||||
if (it.newPath != "/dev/null")
|
||||
it.newPath
|
||||
else
|
||||
it.oldPath
|
||||
}
|
||||
.map {
|
||||
val entries = it.value
|
||||
.map {
|
||||
val entries = it.value
|
||||
|
||||
val hasConflicts =
|
||||
(entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing))
|
||||
val hasConflicts = (entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing))
|
||||
|
||||
StatusEntry(entries.first(), isConflict = hasConflicts)
|
||||
StatusEntry(entries.first(), isConflict = hasConflicts)
|
||||
}
|
||||
} else {
|
||||
status.map {
|
||||
StatusEntry(it, isConflict = false)
|
||||
}
|
||||
}
|
||||
|
||||
return@withContext statusEntries
|
||||
}
|
||||
|
||||
suspend fun getUnstaged(git: Git, repositoryState: RepositoryState) = withContext(Dispatchers.IO) {
|
||||
@ -274,6 +277,42 @@ class StatusManager @Inject constructor(
|
||||
StatusEntry(entries.first(), isConflict = hasConflicts)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getStatusSummary(git: Git, currentBranch: Ref?, repositoryState: RepositoryState): StatusSummary {
|
||||
val staged = getStaged(git, currentBranch, repositoryState)
|
||||
val allChanges = staged.toMutableList()
|
||||
println("Staged: $staged")
|
||||
|
||||
val unstaged = getUnstaged(git, repositoryState)
|
||||
println("Unstaged: $unstaged")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
val deletedCount = changesGrouped[DiffEntry.ChangeType.DELETE].countOrZero()
|
||||
val addedCount = changesGrouped[DiffEntry.ChangeType.ADD].countOrZero()
|
||||
|
||||
val modifiedCount = changesGrouped[DiffEntry.ChangeType.MODIFY].countOrZero() +
|
||||
changesGrouped[DiffEntry.ChangeType.RENAME].countOrZero() +
|
||||
changesGrouped[DiffEntry.ChangeType.COPY].countOrZero()
|
||||
|
||||
return StatusSummary(
|
||||
modifiedCount = modifiedCount,
|
||||
deletedCount = deletedCount,
|
||||
addedCount = addedCount,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -293,4 +332,6 @@ data class StatusEntry(val diffEntry: DiffEntry, val isConflict: Boolean) {
|
||||
else
|
||||
diffEntry.iconColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class StatusSummary(val modifiedCount: Int, val deletedCount: Int, val addedCount: Int)
|
@ -134,10 +134,5 @@ enum class RefreshType {
|
||||
NONE,
|
||||
ALL_DATA,
|
||||
ONLY_LOG,
|
||||
|
||||
/**
|
||||
* Requires to update the status if currently selected and update the log if there has been a change
|
||||
* in the "uncommited changes" state (if there were changes before but not anymore and vice-versa)
|
||||
*/
|
||||
UNCOMMITED_CHANGES,
|
||||
}
|
@ -14,6 +14,10 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
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.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
@ -22,6 +26,7 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.pointer.PointerIconDefaults
|
||||
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||
import androidx.compose.ui.res.painterResource
|
||||
@ -31,6 +36,7 @@ import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import app.extensions.*
|
||||
import app.git.StatusSummary
|
||||
import app.git.graph.GraphNode
|
||||
import app.theme.*
|
||||
import app.ui.SelectedItem
|
||||
@ -131,6 +137,7 @@ fun Log(
|
||||
UncommitedChangesLine(
|
||||
selected = selectedItem == SelectedItem.UncommitedChanges,
|
||||
hasPreviousCommits = commitList.isNotEmpty(),
|
||||
statusSummary = logStatus.statusSummary,
|
||||
graphWidth = graphWidth,
|
||||
weightMod = weightMod,
|
||||
repositoryState = repositoryState,
|
||||
@ -273,7 +280,8 @@ fun UncommitedChangesLine(
|
||||
graphWidth: Dp,
|
||||
weightMod: MutableState<Float>,
|
||||
onUncommitedChangesSelected: () -> Unit,
|
||||
repositoryState: RepositoryState
|
||||
repositoryState: RepositoryState,
|
||||
statusSummary: StatusSummary
|
||||
) {
|
||||
val textColor = if (selected) {
|
||||
MaterialTheme.colors.primary
|
||||
@ -287,8 +295,9 @@ fun UncommitedChangesLine(
|
||||
.clickable {
|
||||
onUncommitedChangesSelected()
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
UncommitedChangesGraphLine(
|
||||
UncommitedChangesGraphNode(
|
||||
modifier = Modifier
|
||||
.width(graphWidth),
|
||||
hasPreviousCommits = hasPreviousCommits,
|
||||
@ -304,29 +313,82 @@ fun UncommitedChangesLine(
|
||||
)
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(2f))
|
||||
|
||||
val text = when {
|
||||
repositoryState.isRebasing -> "Pending changes to rebase"
|
||||
repositoryState.isMerging -> "Pending changes to merge"
|
||||
else -> "Uncommited changes"
|
||||
}
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
fontStyle = FontStyle.Italic,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
fontSize = 14.sp,
|
||||
maxLines = 1,
|
||||
color = textColor,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(2f))
|
||||
val text = when {
|
||||
repositoryState.isRebasing -> "Pending changes to rebase"
|
||||
repositoryState.isMerging -> "Pending changes to merge"
|
||||
else -> "Uncommited changes"
|
||||
}
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
fontStyle = FontStyle.Italic,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
fontSize = 14.sp,
|
||||
maxLines = 1,
|
||||
color = textColor,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
LogStatusSummary(
|
||||
statusSummary = statusSummary,
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LogStatusSummary(statusSummary: StatusSummary, modifier: Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
) {
|
||||
if (statusSummary.modifiedCount > 0) {
|
||||
SummaryEntry(
|
||||
count = statusSummary.modifiedCount,
|
||||
icon = Icons.Default.Edit,
|
||||
color = MaterialTheme.colors.modifyFile,
|
||||
)
|
||||
}
|
||||
|
||||
if (statusSummary.addedCount > 0) {
|
||||
SummaryEntry(
|
||||
count = statusSummary.addedCount,
|
||||
icon = Icons.Default.Add,
|
||||
color = MaterialTheme.colors.addFile,
|
||||
)
|
||||
}
|
||||
|
||||
if (statusSummary.deletedCount > 0) {
|
||||
SummaryEntry(
|
||||
count = statusSummary.deletedCount,
|
||||
icon = Icons.Default.Delete,
|
||||
color = MaterialTheme.colors.deleteFile,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SummaryEntry(
|
||||
count: Int,
|
||||
icon: ImageVector,
|
||||
color: Color
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = count.toString(),
|
||||
color = MaterialTheme.colors.primaryTextColor,
|
||||
)
|
||||
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
tint = color,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -623,7 +685,7 @@ fun CommitNode(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UncommitedChangesGraphLine(
|
||||
fun UncommitedChangesGraphNode(
|
||||
modifier: Modifier = Modifier,
|
||||
hasPreviousCommits: Boolean,
|
||||
) {
|
||||
|
@ -16,6 +16,7 @@ class LogViewModel @Inject constructor(
|
||||
private val rebaseManager: RebaseManager,
|
||||
private val tagsManager: TagsManager,
|
||||
private val mergeManager: MergeManager,
|
||||
private val repositoryManager: RepositoryManager,
|
||||
private val tabState: TabState,
|
||||
) {
|
||||
private val _logStatus = MutableStateFlow<LogStatus>(LogStatus.Loading)
|
||||
@ -23,13 +24,23 @@ class LogViewModel @Inject constructor(
|
||||
val logStatus: StateFlow<LogStatus>
|
||||
get() = _logStatus
|
||||
|
||||
suspend fun loadLog(git: Git) {
|
||||
private suspend fun loadLog(git: Git) {
|
||||
_logStatus.value = LogStatus.Loading
|
||||
|
||||
val currentBranch = branchesManager.currentBranchRef(git)
|
||||
val log = logManager.loadLog(git, currentBranch)
|
||||
val hasUncommitedChanges = statusManager.hasUncommitedChanges(git)
|
||||
_logStatus.value = LogStatus.Loaded(hasUncommitedChanges, log, currentBranch)
|
||||
|
||||
val statsSummary = if (hasUncommitedChanges) {
|
||||
statusManager.getStatusSummary(
|
||||
git = git,
|
||||
currentBranch = currentBranch,
|
||||
repositoryState = repositoryManager.getRepositoryState(git),
|
||||
)
|
||||
} else
|
||||
StatusSummary(0, 0, 0)
|
||||
|
||||
_logStatus.value = LogStatus.Loaded(hasUncommitedChanges, log, currentBranch, statsSummary)
|
||||
}
|
||||
|
||||
fun checkoutCommit(revCommit: RevCommit) = tabState.safeProcessing(
|
||||
@ -56,7 +67,7 @@ class LogViewModel @Inject constructor(
|
||||
branchesManager.checkoutRef(git, ref)
|
||||
}
|
||||
|
||||
fun cherrypickCommit(revCommit: RevCommit) = tabState.safeProcessing (
|
||||
fun cherrypickCommit(revCommit: RevCommit) = tabState.safeProcessing(
|
||||
refreshType = RefreshType.ONLY_LOG,
|
||||
) { git ->
|
||||
mergeManager.cherryPickCommit(git, revCommit)
|
||||
@ -92,6 +103,37 @@ class LogViewModel @Inject constructor(
|
||||
tagsManager.deleteTag(git, tag)
|
||||
}
|
||||
|
||||
suspend fun refreshUncommitedChanges(git: Git) {
|
||||
uncommitedChangesLoadLog(git)
|
||||
}
|
||||
|
||||
private suspend fun uncommitedChangesLoadLog(git: Git) {
|
||||
val currentBranch = branchesManager.currentBranchRef(git)
|
||||
val hasUncommitedChanges = statusManager.hasUncommitedChanges(git)
|
||||
|
||||
val statsSummary = if (hasUncommitedChanges) {
|
||||
statusManager.getStatusSummary(
|
||||
git = git,
|
||||
currentBranch = currentBranch,
|
||||
repositoryState = repositoryManager.getRepositoryState(git),
|
||||
)
|
||||
} else
|
||||
StatusSummary(0, 0, 0)
|
||||
|
||||
val previousLogStatusValue = _logStatus.value
|
||||
|
||||
if(previousLogStatusValue is LogStatus.Loaded) {
|
||||
val newLogStatusValue = LogStatus.Loaded(
|
||||
hasUncommitedChanges = hasUncommitedChanges,
|
||||
plotCommitList = previousLogStatusValue.plotCommitList,
|
||||
currentBranch = currentBranch,
|
||||
statusSummary = statsSummary,
|
||||
)
|
||||
|
||||
_logStatus.value = newLogStatusValue
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun refresh(git: Git) {
|
||||
loadLog(git)
|
||||
}
|
||||
@ -105,6 +147,10 @@ class LogViewModel @Inject constructor(
|
||||
|
||||
sealed class LogStatus {
|
||||
object Loading : LogStatus()
|
||||
class Loaded(val hasUncommitedChanges: Boolean, val plotCommitList: GraphCommitList, val currentBranch: Ref?) :
|
||||
LogStatus()
|
||||
class Loaded(
|
||||
val hasUncommitedChanges: Boolean,
|
||||
val plotCommitList: GraphCommitList,
|
||||
val currentBranch: Ref?,
|
||||
val statusSummary: StatusSummary,
|
||||
) : LogStatus()
|
||||
}
|
@ -108,22 +108,6 @@ class StatusViewModel @Inject constructor(
|
||||
loadHasUncommitedChanges(git)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are uncommited changes and returns if the state has changed (
|
||||
*/
|
||||
suspend fun updateHasUncommitedChanges(git: Git): Boolean {
|
||||
val hadUncommitedChanges = this.lastUncommitedChangesState
|
||||
|
||||
loadStatus(git)
|
||||
loadHasUncommitedChanges(git)
|
||||
|
||||
val hasNowUncommitedChanges = this.lastUncommitedChangesState
|
||||
hasPreviousCommits = logManager.hasPreviousCommits(git)
|
||||
|
||||
// Return true to update the log only if the uncommitedChanges status has changed
|
||||
return (hasNowUncommitedChanges != hadUncommitedChanges)
|
||||
}
|
||||
|
||||
fun continueRebase() = tabState.safeProcessing(
|
||||
refreshType = RefreshType.ALL_DATA,
|
||||
) { git ->
|
||||
|
@ -75,7 +75,7 @@ class TabViewModel @Inject constructor(
|
||||
RefreshType.NONE -> println("Not refreshing...")
|
||||
RefreshType.ALL_DATA -> refreshRepositoryInfo()
|
||||
RefreshType.ONLY_LOG -> refreshLog()
|
||||
RefreshType.UNCOMMITED_CHANGES -> checkUncommitedChanges(false)
|
||||
RefreshType.UNCOMMITED_CHANGES -> checkUncommitedChanges()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -140,21 +140,18 @@ class TabViewModel @Inject constructor(
|
||||
).collect {
|
||||
if (!tabState.operationRunning) { // Only update if there isn't any process running
|
||||
println("Changes detected, loading status")
|
||||
checkUncommitedChanges(isFsChange = true)
|
||||
checkUncommitedChanges()
|
||||
|
||||
updateDiffEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun checkUncommitedChanges(isFsChange: Boolean = false) = tabState.runOperation(
|
||||
private suspend fun checkUncommitedChanges() = tabState.runOperation(
|
||||
refreshType = RefreshType.NONE,
|
||||
) { git ->
|
||||
val uncommitedChangesStateChanged = statusViewModel.updateHasUncommitedChanges(git)
|
||||
|
||||
// Update the log only if the uncommitedChanges status has changed
|
||||
if ((uncommitedChangesStateChanged && isFsChange) || !isFsChange)
|
||||
logViewModel.refresh(git)
|
||||
statusViewModel.refresh(git)
|
||||
logViewModel.refreshUncommitedChanges(git)
|
||||
|
||||
updateDiffEntry()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user