Refactored status to improve its performance and removed diff update handling
This commit is contained in:
parent
4108537825
commit
1e0660dca0
@ -0,0 +1,3 @@
|
||||
package app.exceptions
|
||||
|
||||
class MissingDiffEntryException(msg: String) : GitnuroException(msg)
|
@ -6,6 +6,7 @@ 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.StatusEntry
|
||||
import app.git.StatusType
|
||||
import app.theme.addFile
|
||||
import app.theme.conflictFile
|
||||
@ -30,6 +31,26 @@ val DiffEntry.parentDirectoryPath: String
|
||||
"${directoryPath}/"
|
||||
}
|
||||
|
||||
val StatusEntry.parentDirectoryPath: String
|
||||
get() {
|
||||
val pathSplit = this.filePath.split("/").toMutableList()
|
||||
pathSplit.removeLast()
|
||||
|
||||
val directoryPath = pathSplit.joinToString("/")
|
||||
|
||||
return if (directoryPath.isEmpty())
|
||||
""
|
||||
else
|
||||
"${directoryPath}/"
|
||||
}
|
||||
|
||||
val StatusEntry.fileName: String
|
||||
get() {
|
||||
val pathSplit = filePath.split("/")
|
||||
|
||||
return pathSplit.lastOrNull() ?: ""
|
||||
}
|
||||
|
||||
val DiffEntry.fileName: String
|
||||
get() {
|
||||
val path = if (this.changeType == DiffEntry.ChangeType.DELETE) {
|
||||
@ -62,6 +83,18 @@ val StatusType.icon: ImageVector
|
||||
}
|
||||
}
|
||||
|
||||
val DiffEntry.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")
|
||||
}
|
||||
}
|
||||
|
||||
val StatusType.iconColor: Color
|
||||
@Composable
|
||||
get() {
|
||||
@ -71,4 +104,17 @@ val StatusType.iconColor: Color
|
||||
StatusType.REMOVED -> MaterialTheme.colors.error
|
||||
StatusType.CONFLICTING -> MaterialTheme.colors.conflictFile
|
||||
}
|
||||
}
|
||||
|
||||
val DiffEntry.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")
|
||||
}
|
||||
}
|
@ -2,22 +2,24 @@ package app.git
|
||||
|
||||
import org.eclipse.jgit.diff.DiffEntry
|
||||
|
||||
sealed class DiffEntryType(val statusEntry: StatusEntry) {
|
||||
class CommitDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry)
|
||||
sealed class DiffEntryType() {
|
||||
class CommitDiff(val diffEntry: DiffEntry) : DiffEntryType()
|
||||
|
||||
sealed class UnstagedDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry)
|
||||
sealed class StagedDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry)
|
||||
sealed class UncommitedDiff(val statusEntry: StatusEntry) : DiffEntryType()
|
||||
|
||||
sealed class UnstagedDiff(statusEntry: StatusEntry) : UncommitedDiff(statusEntry)
|
||||
sealed class StagedDiff(statusEntry: StatusEntry) : UncommitedDiff(statusEntry)
|
||||
|
||||
/**
|
||||
* State used to represent staged changes when the repository state is not [org.eclipse.jgit.lib.RepositoryState.SAFE]
|
||||
*/
|
||||
class UnsafeStagedDiff(diffEntry: DiffEntry) : StagedDiff(diffEntry)
|
||||
class UnsafeStagedDiff(statusEntry: StatusEntry) : StagedDiff(statusEntry)
|
||||
/**
|
||||
* State used to represent unstaged changes when the repository state is not [org.eclipse.jgit.lib.RepositoryState.SAFE]
|
||||
*/
|
||||
class UnsafeUnstagedDiff(diffEntry: DiffEntry) : UnstagedDiff(diffEntry)
|
||||
class UnsafeUnstagedDiff(statusEntry: StatusEntry) : UnstagedDiff(statusEntry)
|
||||
|
||||
class SafeStagedDiff(diffEntry: DiffEntry) : StagedDiff(diffEntry)
|
||||
class SafeUnstagedDiff(diffEntry: DiffEntry) : UnstagedDiff(diffEntry)
|
||||
class SafeStagedDiff(statusEntry: StatusEntry) : StagedDiff(statusEntry)
|
||||
class SafeUnstagedDiff(statusEntry: StatusEntry) : UnstagedDiff(statusEntry)
|
||||
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package app.git
|
||||
|
||||
import app.di.HunkDiffGeneratorFactory
|
||||
import app.di.RawFileManagerFactory
|
||||
import app.exceptions.MissingDiffEntryException
|
||||
import app.extensions.fullData
|
||||
import app.git.diff.DiffResult
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -17,6 +18,7 @@ import org.eclipse.jgit.revwalk.RevWalk
|
||||
import org.eclipse.jgit.treewalk.AbstractTreeIterator
|
||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser
|
||||
import org.eclipse.jgit.treewalk.FileTreeIterator
|
||||
import org.eclipse.jgit.treewalk.filter.PathFilter
|
||||
import java.io.ByteArrayOutputStream
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -26,9 +28,9 @@ class DiffManager @Inject constructor(
|
||||
private val hunkDiffGeneratorFactory: HunkDiffGeneratorFactory,
|
||||
) {
|
||||
suspend fun diffFormat(git: Git, diffEntryType: DiffEntryType): DiffResult = withContext(Dispatchers.IO) {
|
||||
val diffEntry = diffEntryType.diffEntry
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
val repository = git.repository
|
||||
val diffEntry: DiffEntry
|
||||
|
||||
DiffFormatter(byteArrayOutputStream).use { formatter ->
|
||||
formatter.setRepository(repository)
|
||||
@ -39,6 +41,24 @@ class DiffManager @Inject constructor(
|
||||
if (diffEntryType is DiffEntryType.UnstagedDiff)
|
||||
formatter.scan(oldTree, newTree)
|
||||
|
||||
diffEntry = when (diffEntryType) {
|
||||
is DiffEntryType.CommitDiff -> {
|
||||
diffEntryType.diffEntry
|
||||
}
|
||||
is DiffEntryType.UncommitedDiff -> {
|
||||
val statusEntry = diffEntryType.statusEntry
|
||||
|
||||
val firstDiffEntry = git.diff()
|
||||
.setPathFilter(PathFilter.create(statusEntry.filePath))
|
||||
.setCached(diffEntryType is DiffEntryType.StagedDiff)
|
||||
.call()
|
||||
.firstOrNull()
|
||||
?: throw MissingDiffEntryException("Diff entry not found")
|
||||
|
||||
firstDiffEntry
|
||||
}
|
||||
}
|
||||
|
||||
formatter.format(diffEntry)
|
||||
|
||||
formatter.flush()
|
||||
|
@ -1,8 +1,5 @@
|
||||
package app.git
|
||||
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
@ -10,7 +7,6 @@ import app.di.RawFileManagerFactory
|
||||
import app.extensions.*
|
||||
import app.git.diff.Hunk
|
||||
import app.git.diff.LineType
|
||||
import app.theme.conflictFile
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
@ -38,14 +34,14 @@ class StatusManager @Inject constructor(
|
||||
return@withContext status.hasUncommittedChanges() || status.hasUntrackedChanges()
|
||||
}
|
||||
|
||||
suspend fun stage(git: Git, diffEntry: DiffEntry) = withContext(Dispatchers.IO) {
|
||||
if (diffEntry.changeType == DiffEntry.ChangeType.DELETE) {
|
||||
suspend fun stage(git: Git, statusEntry: StatusEntry) = withContext(Dispatchers.IO) {
|
||||
if (statusEntry.statusType == StatusType.REMOVED) {
|
||||
git.rm()
|
||||
.addFilepattern(diffEntry.filePath)
|
||||
.addFilepattern(statusEntry.filePath)
|
||||
.call()
|
||||
} else {
|
||||
git.add()
|
||||
.addFilepattern(diffEntry.filePath)
|
||||
.addFilepattern(statusEntry.filePath)
|
||||
.call()
|
||||
}
|
||||
}
|
||||
@ -180,9 +176,9 @@ class StatusManager @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun unstage(git: Git, diffEntry: DiffEntry) = withContext(Dispatchers.IO) {
|
||||
suspend fun unstage(git: Git, statusEntry: StatusEntry) = withContext(Dispatchers.IO) {
|
||||
git.reset()
|
||||
.addPath(diffEntry.filePath)
|
||||
.addPath(statusEntry.filePath)
|
||||
.call()
|
||||
}
|
||||
|
||||
@ -194,17 +190,17 @@ class StatusManager @Inject constructor(
|
||||
.call()
|
||||
}
|
||||
|
||||
suspend fun reset(git: Git, diffEntry: DiffEntry, staged: Boolean) = withContext(Dispatchers.IO) {
|
||||
suspend fun reset(git: Git, statusEntry: StatusEntry, staged: Boolean) = withContext(Dispatchers.IO) {
|
||||
if (staged) {
|
||||
git
|
||||
.reset()
|
||||
.addPath(diffEntry.filePath)
|
||||
.addPath(statusEntry.filePath)
|
||||
.call()
|
||||
}
|
||||
|
||||
git
|
||||
.checkout()
|
||||
.addPath(diffEntry.filePath)
|
||||
.addPath(statusEntry.filePath)
|
||||
.call()
|
||||
}
|
||||
|
||||
@ -221,7 +217,7 @@ class StatusManager @Inject constructor(
|
||||
.call()
|
||||
}
|
||||
|
||||
suspend fun getStaged(git: Git, currentBranch: Ref?, repositoryState: RepositoryState) =
|
||||
suspend fun getStaged(git: Git) =
|
||||
withContext(Dispatchers.IO) {
|
||||
|
||||
// TODO Test on an empty repository or with a non-default state like merging or rebasing
|
||||
@ -275,8 +271,8 @@ class StatusManager @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getStatusSummary(git: Git, currentBranch: Ref?, repositoryState: RepositoryState): StatusSummary {
|
||||
val staged = getStaged(git, currentBranch, repositoryState)
|
||||
suspend fun getStatusSummary(git: Git): StatusSummary {
|
||||
val staged = getStaged(git)
|
||||
val allChanges = staged.toMutableList()
|
||||
|
||||
val unstaged = getUnstaged(git)
|
||||
@ -326,7 +322,7 @@ class StatusManager @Inject constructor(
|
||||
}
|
||||
|
||||
|
||||
data class StatusEntry(val entry: String, val statusType: StatusType) {
|
||||
data class StatusEntry(val filePath: String, val statusType: StatusType) {
|
||||
val icon: ImageVector
|
||||
get() = statusType.icon
|
||||
|
||||
|
@ -50,15 +50,15 @@ class HunkDiffGenerator @AssistedInject constructor(
|
||||
if (rawOld == EntryContent.InvalidObjectBlob || rawNew == EntryContent.InvalidObjectBlob)
|
||||
throw InvalidObjectException("Invalid object in diff format")
|
||||
|
||||
var diffResult: DiffResult = DiffResult.Text(emptyList())
|
||||
var diffResult: DiffResult = DiffResult.Text(ent, emptyList())
|
||||
|
||||
// If we can, generate text diff (if one of the files has never been a binary file)
|
||||
val hasGeneratedTextDiff = canGenerateTextDiff(rawOld, rawNew) { oldRawText, newRawText ->
|
||||
diffResult = DiffResult.Text(format(fileHeader, oldRawText, newRawText))
|
||||
diffResult = DiffResult.Text(ent, format(fileHeader, oldRawText, newRawText))
|
||||
}
|
||||
|
||||
if (!hasGeneratedTextDiff) {
|
||||
diffResult = DiffResult.NonText(rawOld, rawNew)
|
||||
diffResult = DiffResult.NonText(ent, rawOld, rawNew)
|
||||
}
|
||||
|
||||
return diffResult
|
||||
@ -190,10 +190,17 @@ class HunkDiffGenerator @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
sealed class DiffResult {
|
||||
data class Text(val hunks: List<Hunk>) : DiffResult()
|
||||
data class NonText(
|
||||
sealed class DiffResult(
|
||||
val diffEntry: DiffEntry,
|
||||
) {
|
||||
class Text(
|
||||
diffEntry: DiffEntry,
|
||||
val hunks: List<Hunk>
|
||||
) : DiffResult(diffEntry)
|
||||
|
||||
class NonText(
|
||||
diffEntry: DiffEntry,
|
||||
val oldBinaryContent: EntryContent,
|
||||
val newBinaryContent: EntryContent,
|
||||
) : DiffResult()
|
||||
) : DiffResult(diffEntry)
|
||||
}
|
@ -197,7 +197,7 @@ fun CommitLogChanges(
|
||||
val textColor: Color
|
||||
val secondaryTextColor: Color
|
||||
|
||||
if (diffSelected?.diffEntry == diffEntry) {
|
||||
if (diffSelected is DiffEntryType.CommitDiff && diffSelected.diffEntry == diffEntry) {
|
||||
textColor = MaterialTheme.colors.primary
|
||||
secondaryTextColor = MaterialTheme.colors.halfPrimary
|
||||
} else {
|
||||
|
@ -7,9 +7,11 @@ import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.selection.DisableSelection
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -23,6 +25,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import app.git.DiffEntryType
|
||||
import app.git.EntryContent
|
||||
import app.git.StatusType
|
||||
import app.git.diff.DiffResult
|
||||
import app.git.diff.Hunk
|
||||
import app.git.diff.Line
|
||||
@ -33,6 +36,7 @@ import app.theme.unstageButton
|
||||
import app.ui.components.ScrollableLazyColumn
|
||||
import app.ui.components.SecondaryButton
|
||||
import app.viewmodels.DiffViewModel
|
||||
import app.viewmodels.ViewDiffResult
|
||||
import org.eclipse.jgit.diff.DiffEntry
|
||||
import java.io.FileInputStream
|
||||
import java.nio.file.Path
|
||||
@ -47,22 +51,33 @@ fun Diff(
|
||||
val diffResultState = diffViewModel.diffResult.collectAsState()
|
||||
val viewDiffResult = diffResultState.value ?: return
|
||||
|
||||
val diffEntryType = viewDiffResult.diffEntryType
|
||||
val diffEntry = diffEntryType.diffEntry
|
||||
val diffResult = viewDiffResult.diffResult
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.background(MaterialTheme.colors.background)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
DiffHeader(diffEntry, onCloseDiffView)
|
||||
if (diffResult is DiffResult.Text) {
|
||||
TextDiff(diffEntryType, diffViewModel, diffResult)
|
||||
} else if (diffResult is DiffResult.NonText) {
|
||||
NonTextDiff(diffResult)
|
||||
when (viewDiffResult) {
|
||||
ViewDiffResult.DiffNotFound -> { onCloseDiffView() }
|
||||
is ViewDiffResult.Loaded -> {
|
||||
val diffEntryType = viewDiffResult.diffEntryType
|
||||
val diffEntry = viewDiffResult.diffResult.diffEntry
|
||||
val diffResult = viewDiffResult.diffResult
|
||||
|
||||
DiffHeader(diffEntry, onCloseDiffView)
|
||||
|
||||
if (diffResult is DiffResult.Text) {
|
||||
TextDiff(diffEntryType, diffViewModel, diffResult)
|
||||
} else if (diffResult is DiffResult.NonText) {
|
||||
NonTextDiff(diffResult)
|
||||
}
|
||||
}
|
||||
ViewDiffResult.Loading -> {
|
||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,8 +190,9 @@ fun TextDiff(diffEntryType: DiffEntryType, diffViewModel: DiffViewModel, diffRes
|
||||
DisableSelection {
|
||||
HunkHeader(
|
||||
hunk = hunk,
|
||||
diffEntryType = diffEntryType,
|
||||
diffViewModel = diffViewModel,
|
||||
diffEntryType = diffEntryType,
|
||||
diffEntry =diffResult.diffEntry,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -200,6 +216,7 @@ fun HunkHeader(
|
||||
hunk: Hunk,
|
||||
diffEntryType: DiffEntryType,
|
||||
diffViewModel: DiffViewModel,
|
||||
diffEntry: DiffEntry,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@ -215,9 +232,12 @@ fun HunkHeader(
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
// Hunks options are only visible when repository is a normal state (not during merge/rebase)
|
||||
if (
|
||||
(diffEntryType is DiffEntryType.SafeStagedDiff || diffEntryType is DiffEntryType.SafeUnstagedDiff) &&
|
||||
diffEntryType.diffEntry.changeType == DiffEntry.ChangeType.MODIFY
|
||||
(diffEntryType is DiffEntryType.UncommitedDiff && // Added just to make smartcast work
|
||||
diffEntryType.statusEntry.statusType == StatusType.MODIFIED)
|
||||
) {
|
||||
val buttonText: String
|
||||
val color: Color
|
||||
@ -234,9 +254,9 @@ fun HunkHeader(
|
||||
backgroundButton = color,
|
||||
onClick = {
|
||||
if (diffEntryType is DiffEntryType.StagedDiff) {
|
||||
diffViewModel.unstageHunk(diffEntryType.diffEntry, hunk)
|
||||
diffViewModel.unstageHunk(diffEntry, hunk)
|
||||
} else {
|
||||
diffViewModel.stageHunk(diffEntryType.diffEntry, hunk)
|
||||
diffViewModel.stageHunk(diffEntry, hunk)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -45,16 +45,16 @@ import app.ui.context_menu.stagedEntriesContextMenuItems
|
||||
import app.ui.context_menu.unstagedEntriesContextMenuItems
|
||||
import app.viewmodels.StageStatus
|
||||
import app.viewmodels.StatusViewModel
|
||||
import org.eclipse.jgit.diff.DiffEntry
|
||||
import org.eclipse.jgit.lib.RepositoryState
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Composable
|
||||
fun UncommitedChanges(
|
||||
statusViewModel: StatusViewModel,
|
||||
selectedEntryType: DiffEntryType?,
|
||||
repositoryState: RepositoryState,
|
||||
onStagedDiffEntrySelected: (DiffEntry?) -> Unit,
|
||||
onUnstagedDiffEntrySelected: (DiffEntry) -> Unit,
|
||||
onStagedDiffEntrySelected: (StatusEntry?) -> Unit,
|
||||
onUnstagedDiffEntrySelected: (StatusEntry) -> Unit,
|
||||
) {
|
||||
val stageStatusState = statusViewModel.stageStatus.collectAsState()
|
||||
var commitMessage by remember { mutableStateOf(statusViewModel.savedCommitMessage) }
|
||||
@ -66,18 +66,6 @@ fun UncommitedChanges(
|
||||
if (stageStatus is StageStatus.Loaded) {
|
||||
staged = stageStatus.staged
|
||||
unstaged = stageStatus.unstaged
|
||||
|
||||
LaunchedEffect(staged) {
|
||||
if (selectedEntryType != null) {
|
||||
checkIfSelectedEntryShouldBeUpdated(
|
||||
selectedEntryType = selectedEntryType,
|
||||
staged = staged,
|
||||
unstaged = unstaged,
|
||||
onStagedDiffEntrySelected = onStagedDiffEntrySelected,
|
||||
onUnstagedDiffEntrySelected = onUnstagedDiffEntrySelected,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
staged = listOf()
|
||||
unstaged = listOf() // return empty lists if still loading
|
||||
@ -110,7 +98,7 @@ fun UncommitedChanges(
|
||||
title = "Staged",
|
||||
allActionTitle = "Unstage all",
|
||||
actionTitle = "Unstage",
|
||||
selectedEntryType = selectedEntryType,
|
||||
selectedEntryType = if(selectedEntryType is DiffEntryType.StagedDiff) selectedEntryType else null,
|
||||
actionColor = MaterialTheme.colors.unstageButton,
|
||||
statusEntries = staged,
|
||||
onDiffEntrySelected = onStagedDiffEntrySelected,
|
||||
@ -127,7 +115,7 @@ fun UncommitedChanges(
|
||||
},
|
||||
onAllAction = {
|
||||
statusViewModel.unstageAll()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
EntriesList(
|
||||
@ -137,20 +125,21 @@ fun UncommitedChanges(
|
||||
.fillMaxWidth(),
|
||||
title = "Unstaged",
|
||||
actionTitle = "Stage",
|
||||
selectedEntryType = if(selectedEntryType is DiffEntryType.UnstagedDiff) selectedEntryType else null,
|
||||
actionColor = MaterialTheme.colors.stageButton,
|
||||
statusEntries = unstaged,
|
||||
onDiffEntrySelected = onUnstagedDiffEntrySelected,
|
||||
onDiffEntryOptionSelected = {
|
||||
statusViewModel.stage(it)
|
||||
},
|
||||
onGenerateContextMenu = { diffEntry ->
|
||||
onGenerateContextMenu = { statusEntry ->
|
||||
unstagedEntriesContextMenuItems(
|
||||
statusEntry = diffEntry,
|
||||
statusEntry = statusEntry,
|
||||
onReset = {
|
||||
statusViewModel.resetUnstaged(diffEntry)
|
||||
statusViewModel.resetUnstaged(statusEntry)
|
||||
},
|
||||
onDelete = {
|
||||
statusViewModel.deleteFile(diffEntry)
|
||||
statusViewModel.deleteFile(statusEntry)
|
||||
}
|
||||
)
|
||||
},
|
||||
@ -158,7 +147,6 @@ fun UncommitedChanges(
|
||||
statusViewModel.stageAll()
|
||||
},
|
||||
allActionTitle = "Stage all",
|
||||
selectedEntryType = selectedEntryType
|
||||
)
|
||||
|
||||
Column(
|
||||
@ -392,46 +380,6 @@ fun ConfirmationButton(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: This logic should be part of the diffViewModel where it gets the latest version of the diffEntry
|
||||
fun checkIfSelectedEntryShouldBeUpdated(
|
||||
selectedEntryType: DiffEntryType,
|
||||
staged: List<StatusEntry>,
|
||||
unstaged: List<StatusEntry>,
|
||||
onStagedDiffEntrySelected: (DiffEntry?) -> Unit,
|
||||
onUnstagedDiffEntrySelected: (DiffEntry) -> Unit,
|
||||
) {
|
||||
val selectedDiffEntry = selectedEntryType.diffEntry
|
||||
val selectedEntryTypeNewId = selectedDiffEntry.newId.name()
|
||||
|
||||
if (selectedEntryType is DiffEntryType.StagedDiff) {
|
||||
val entryType =
|
||||
staged.firstOrNull { stagedEntry -> stagedEntry.diffEntry.newPath == selectedDiffEntry.newPath }?.diffEntry
|
||||
|
||||
if (
|
||||
entryType != null &&
|
||||
selectedEntryTypeNewId != entryType.newId.name()
|
||||
) {
|
||||
onStagedDiffEntrySelected(entryType)
|
||||
|
||||
} else if (entryType == null) {
|
||||
onStagedDiffEntrySelected(null)
|
||||
}
|
||||
} else if (selectedEntryType is DiffEntryType.UnstagedDiff) {
|
||||
val entryType = unstaged.firstOrNull { unstagedEntry ->
|
||||
if (selectedDiffEntry.changeType == DiffEntry.ChangeType.DELETE)
|
||||
unstagedEntry.diffEntry.oldPath == selectedDiffEntry.oldPath
|
||||
else
|
||||
unstagedEntry.diffEntry.newPath == selectedDiffEntry.newPath
|
||||
}
|
||||
|
||||
if (entryType != null) {
|
||||
onUnstagedDiffEntrySelected(entryType.diffEntry)
|
||||
} else
|
||||
onStagedDiffEntrySelected(null)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun EntriesList(
|
||||
@ -478,17 +426,19 @@ private fun EntriesList(
|
||||
.background(MaterialTheme.colors.background),
|
||||
) {
|
||||
itemsIndexed(statusEntries) { index, statusEntry ->
|
||||
val isEntrySelected = selectedEntryType?.diffEntry == diffEntry
|
||||
val isEntrySelected = selectedEntryType != null &&
|
||||
selectedEntryType is DiffEntryType.UncommitedDiff && // Added for smartcast
|
||||
selectedEntryType.statusEntry == statusEntry
|
||||
FileEntry(
|
||||
statusEntry = statusEntry,
|
||||
isSelected = isEntrySelected,
|
||||
actionTitle = actionTitle,
|
||||
actionColor = actionColor,
|
||||
onClick = {
|
||||
onDiffEntrySelected(diffEntry)
|
||||
onDiffEntrySelected(statusEntry)
|
||||
},
|
||||
onButtonClick = {
|
||||
onDiffEntryOptionSelected(diffEntry)
|
||||
onDiffEntryOptionSelected(statusEntry)
|
||||
},
|
||||
onGenerateContextMenu = onGenerateContextMenu,
|
||||
)
|
||||
@ -513,7 +463,7 @@ private fun FileEntry(
|
||||
actionColor: Color,
|
||||
onClick: () -> Unit,
|
||||
onButtonClick: () -> Unit,
|
||||
onGenerateContextMenu: (DiffEntry) -> List<ContextMenuItem>,
|
||||
onGenerateContextMenu: (StatusEntry) -> List<ContextMenuItem>,
|
||||
) {
|
||||
var active by remember { mutableStateOf(false) }
|
||||
|
||||
@ -545,7 +495,7 @@ private fun FileEntry(
|
||||
) {
|
||||
ContextMenuArea(
|
||||
items = {
|
||||
onGenerateContextMenu(diffEntry)
|
||||
onGenerateContextMenu(statusEntry)
|
||||
},
|
||||
) {
|
||||
Row(
|
||||
@ -564,9 +514,9 @@ private fun FileEntry(
|
||||
tint = statusEntry.iconColor,
|
||||
)
|
||||
|
||||
if(diffEntry.parentDirectoryPath.isNotEmpty()) {
|
||||
if(statusEntry.parentDirectoryPath.isNotEmpty()) {
|
||||
Text(
|
||||
text = diffEntry.parentDirectoryPath,
|
||||
text = statusEntry.parentDirectoryPath,
|
||||
modifier = Modifier.weight(1f, fill = false),
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
@ -576,7 +526,7 @@ private fun FileEntry(
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = diffEntry.fileName,
|
||||
text = statusEntry.fileName,
|
||||
modifier = Modifier.weight(1f, fill = false),
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
|
@ -2,15 +2,17 @@ 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 stagedEntriesContextMenuItems(
|
||||
diffEntry: DiffEntry,
|
||||
diffEntry: StatusEntry,
|
||||
onReset: () -> Unit,
|
||||
): List<ContextMenuItem> {
|
||||
return mutableListOf<ContextMenuItem>().apply {
|
||||
if (diffEntry.changeType != DiffEntry.ChangeType.ADD) {
|
||||
if (diffEntry.statusType != StatusType.ADDED) {
|
||||
add(
|
||||
ContextMenuItem(
|
||||
label = "Reset",
|
||||
|
@ -1,6 +1,7 @@
|
||||
package app.viewmodels
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import app.exceptions.MissingDiffEntryException
|
||||
import app.git.*
|
||||
import app.git.diff.DiffResult
|
||||
import app.git.diff.Hunk
|
||||
@ -15,7 +16,7 @@ class DiffViewModel @Inject constructor(
|
||||
private val statusManager: StatusManager,
|
||||
) {
|
||||
// TODO Maybe use a sealed class instead of a null to represent that a diff is not selected?
|
||||
private val _diffResult = MutableStateFlow<ViewDiffResult?>(null)
|
||||
private val _diffResult = MutableStateFlow<ViewDiffResult>(ViewDiffResult.Loading)
|
||||
val diffResult: StateFlow<ViewDiffResult?> = _diffResult
|
||||
|
||||
val lazyListState = MutableStateFlow(
|
||||
@ -25,18 +26,23 @@ class DiffViewModel @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
// TODO Cancel job if the user closed the diff view while loading
|
||||
fun updateDiff(diffEntryType: DiffEntryType) = tabState.runOperation(
|
||||
refreshType = RefreshType.NONE,
|
||||
) { git ->
|
||||
val oldDiffEntryType = _diffResult.value?.diffEntryType
|
||||
var oldDiffEntryType: DiffEntryType? = null
|
||||
val oldDiffResult = _diffResult.value
|
||||
|
||||
_diffResult.value = null
|
||||
if(oldDiffResult is ViewDiffResult.Loaded) {
|
||||
oldDiffEntryType = oldDiffResult.diffEntryType
|
||||
}
|
||||
|
||||
_diffResult.value = ViewDiffResult.Loading
|
||||
|
||||
// If it's a different file or different state (index or workdir), reset the scroll state
|
||||
if (oldDiffEntryType != null &&
|
||||
(oldDiffEntryType.diffEntry.oldPath != diffEntryType.diffEntry.oldPath ||
|
||||
oldDiffEntryType.diffEntry.newPath != diffEntryType.diffEntry.newPath ||
|
||||
oldDiffEntryType::class != diffEntryType::class)
|
||||
oldDiffEntryType is DiffEntryType.UncommitedDiff && diffEntryType is DiffEntryType.UncommitedDiff &&
|
||||
oldDiffEntryType.statusEntry.filePath != diffEntryType.statusEntry.filePath
|
||||
) {
|
||||
lazyListState.value = LazyListState(
|
||||
0,
|
||||
@ -44,13 +50,14 @@ class DiffViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
//TODO: Just a workaround when trying to diff binary files
|
||||
try {
|
||||
val hunks = diffManager.diffFormat(git, diffEntryType)
|
||||
_diffResult.value = ViewDiffResult(diffEntryType, hunks)
|
||||
val diffFormat = diffManager.diffFormat(git, diffEntryType)
|
||||
_diffResult.value = ViewDiffResult.Loaded(diffEntryType, diffFormat)
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
_diffResult.value = ViewDiffResult(diffEntryType, DiffResult.Text(emptyList()))
|
||||
if(ex is MissingDiffEntryException) {
|
||||
_diffResult.value = ViewDiffResult.DiffNotFound
|
||||
} else
|
||||
ex.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,4 +74,9 @@ class DiffViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
data class ViewDiffResult(val diffEntryType: DiffEntryType, val diffResult: DiffResult)
|
||||
|
||||
sealed interface ViewDiffResult {
|
||||
object Loading: ViewDiffResult
|
||||
object DiffNotFound: ViewDiffResult
|
||||
data class Loaded(val diffEntryType: DiffEntryType, val diffResult: DiffResult): ViewDiffResult
|
||||
}
|
||||
|
@ -56,8 +56,6 @@ class LogViewModel @Inject constructor(
|
||||
|
||||
val statusSummary = statusManager.getStatusSummary(
|
||||
git = git,
|
||||
currentBranch = currentBranch,
|
||||
repositoryState = repositoryManager.getRepositoryState(git),
|
||||
)
|
||||
|
||||
val hasUncommitedChanges =
|
||||
@ -163,8 +161,6 @@ class LogViewModel @Inject constructor(
|
||||
val statsSummary = if (hasUncommitedChanges) {
|
||||
statusManager.getStatusSummary(
|
||||
git = git,
|
||||
currentBranch = currentBranch,
|
||||
repositoryState = repositoryManager.getRepositoryState(git),
|
||||
)
|
||||
} else
|
||||
StatusSummary(0, 0, 0)
|
||||
|
@ -6,7 +6,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.diff.DiffEntry
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -27,16 +26,16 @@ class StatusViewModel @Inject constructor(
|
||||
|
||||
private var lastUncommitedChangesState = false
|
||||
|
||||
fun stage(diffEntry: DiffEntry) = tabState.runOperation(
|
||||
fun stage(statusEntry: StatusEntry) = tabState.runOperation(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
statusManager.stage(git, diffEntry)
|
||||
statusManager.stage(git, statusEntry)
|
||||
}
|
||||
|
||||
fun unstage(diffEntry: DiffEntry) = tabState.runOperation(
|
||||
fun unstage(statusEntry: StatusEntry) = tabState.runOperation(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
statusManager.unstage(git, diffEntry)
|
||||
statusManager.unstage(git, statusEntry)
|
||||
}
|
||||
|
||||
|
||||
@ -52,16 +51,16 @@ class StatusViewModel @Inject constructor(
|
||||
statusManager.stageAll(git)
|
||||
}
|
||||
|
||||
fun resetStaged(diffEntry: DiffEntry) = tabState.runOperation(
|
||||
fun resetStaged(statusEntry: StatusEntry) = tabState.runOperation(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
statusManager.reset(git, diffEntry, staged = true)
|
||||
statusManager.reset(git, statusEntry, staged = true)
|
||||
}
|
||||
|
||||
fun resetUnstaged(diffEntry: DiffEntry) = tabState.runOperation(
|
||||
fun resetUnstaged(statusEntry: StatusEntry) = tabState.runOperation(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
statusManager.reset(git, diffEntry, staged = false)
|
||||
statusManager.reset(git, statusEntry, staged = false)
|
||||
}
|
||||
|
||||
private suspend fun loadStatus(git: Git) {
|
||||
@ -69,9 +68,7 @@ class StatusViewModel @Inject constructor(
|
||||
|
||||
try {
|
||||
_stageStatus.value = StageStatus.Loading
|
||||
val repositoryState = repositoryManager.getRepositoryState(git)
|
||||
val currentBranchRef = branchesManager.currentBranchRef(git)
|
||||
val staged = statusManager.getStaged(git, currentBranchRef, repositoryState)
|
||||
val staged = statusManager.getStaged(git)
|
||||
val unstaged = statusManager.getUnstaged(git)
|
||||
|
||||
_stageStatus.value = StageStatus.Loaded(staged, unstaged)
|
||||
@ -141,10 +138,10 @@ class StatusViewModel @Inject constructor(
|
||||
mergeManager.abortMerge(git)
|
||||
}
|
||||
|
||||
fun deleteFile(diffEntry: DiffEntry) = tabState.runOperation(
|
||||
fun deleteFile(statusEntry: StatusEntry) = tabState.runOperation(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
val path = diffEntry.newPath
|
||||
val path = statusEntry.filePath
|
||||
|
||||
val fileToDelete = File(git.repository.directory.parent, path)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user