Implemented edit, drop & restore of Rebase interactive state even if started from external software

Fixes #143 and #65
This commit is contained in:
Abdelilah El Aissaoui 2023-07-01 21:50:53 +02:00
parent 1e012d759b
commit da9a5c1f17
No known key found for this signature in database
GPG Key ID: 7587FC860F594869
19 changed files with 482 additions and 275 deletions

View File

@ -0,0 +1,58 @@
package com.jetpackduba.gitnuro
import com.jetpackduba.gitnuro.di.TabScope
import com.jetpackduba.gitnuro.git.RefreshType
import com.jetpackduba.gitnuro.git.TabState
import com.jetpackduba.gitnuro.git.rebase.GetRebaseInteractiveStateUseCase
import com.jetpackduba.gitnuro.git.rebase.RebaseInteractiveState
import com.jetpackduba.gitnuro.git.repository.GetRepositoryStateUseCase
import com.jetpackduba.gitnuro.logging.printLog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.RepositoryState
import javax.inject.Inject
private const val TAG = "SharedRepositoryStateMa"
@TabScope
class SharedRepositoryStateManager @Inject constructor(
private val tabState: TabState,
private val getRebaseInteractiveStateUseCase: GetRebaseInteractiveStateUseCase,
private val getRepositoryStateUseCase: GetRepositoryStateUseCase,
tabScope: CoroutineScope,
) {
private val _repositoryState = MutableStateFlow(RepositoryState.SAFE)
val repositoryState = _repositoryState.asStateFlow()
private val _rebaseInteractiveState = MutableStateFlow<RebaseInteractiveState>(RebaseInteractiveState.None)
val rebaseInteractiveState = _rebaseInteractiveState.asStateFlow()
init {
tabScope.apply {
launch {
tabState.refreshFlowFiltered(RefreshType.ALL_DATA, RefreshType.REBASE_INTERACTIVE_STATE) {
updateRebaseInteractiveState(tabState.git)
}
}
launch {
tabState.refreshFlowFiltered(RefreshType.ALL_DATA, RefreshType.REPO_STATE) {
updateRepositoryState(tabState.git)
}
}
}
}
private suspend fun updateRepositoryState(git: Git) {
_repositoryState.value = getRepositoryStateUseCase(git)
}
private suspend fun updateRebaseInteractiveState(git: Git) {
val newRepositoryState = getRebaseInteractiveStateUseCase(git)
printLog(TAG, "Refreshing repository state $newRepositoryState")
_rebaseInteractiveState.value = newRepositoryState
}
}

View File

@ -3,6 +3,7 @@ package com.jetpackduba.gitnuro.git
import com.jetpackduba.gitnuro.managers.ErrorsManager
import com.jetpackduba.gitnuro.di.TabScope
import com.jetpackduba.gitnuro.extensions.delayedStateChange
import com.jetpackduba.gitnuro.git.log.FindCommitUseCase
import com.jetpackduba.gitnuro.logging.printError
import com.jetpackduba.gitnuro.managers.newErrorNow
import com.jetpackduba.gitnuro.ui.SelectedItem
@ -10,6 +11,7 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.RepositoryState
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject
@ -33,6 +35,7 @@ sealed interface ProcessingState {
class TabState @Inject constructor(
val errorsManager: ErrorsManager,
private val scope: CoroutineScope,
private val findCommitUseCase: FindCommitUseCase,
) {
private val _selectedItem = MutableStateFlow<SelectedItem>(SelectedItem.UncommitedChanges)
val selectedItem: StateFlow<SelectedItem> = _selectedItem
@ -247,15 +250,16 @@ class TabState @Inject constructor(
if (objectId == null) {
newSelectedItem(SelectedItem.None)
} else {
val commit = findCommit(git, objectId)
val newSelectedItem = SelectedItem.Ref(commit)
newSelectedItem(newSelectedItem)
_taskEvent.emit(TaskEvent.ScrollToGraphItem(newSelectedItem))
}
}
val commit = findCommitUseCase(git, objectId)
private fun findCommit(git: Git, objectId: ObjectId): RevCommit {
return git.repository.parseCommit(objectId)
if(commit == null) {
newSelectedItem(SelectedItem.None)
} else {
val newSelectedItem = SelectedItem.Ref(commit)
newSelectedItem(newSelectedItem)
_taskEvent.emit(TaskEvent.ScrollToGraphItem(newSelectedItem))
}
}
}
suspend fun newSelectedItem(selectedItem: SelectedItem, scrollToItem: Boolean = false) {
@ -300,4 +304,5 @@ enum class RefreshType {
UNCOMMITED_CHANGES,
UNCOMMITED_CHANGES_AND_LOG,
REMOTES,
REBASE_INTERACTIVE_STATE,
}

View File

@ -4,6 +4,5 @@ import com.jetpackduba.gitnuro.ui.SelectedItem
import org.eclipse.jgit.revwalk.RevCommit
sealed interface TaskEvent {
data class RebaseInteractive(val revCommit: RevCommit) : TaskEvent
data class ScrollToGraphItem(val selectedItem: SelectedItem) : TaskEvent
}

View File

@ -0,0 +1,27 @@
package com.jetpackduba.gitnuro.git.log
import com.jetpackduba.gitnuro.logging.printError
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject
private const val TAG = "FindCommitUseCase"
class FindCommitUseCase @Inject constructor() {
suspend operator fun invoke(git: Git, commitId: String): RevCommit? = withContext(Dispatchers.IO) {
val objectId = ObjectId.fromString(commitId)
return@withContext invoke(git, objectId)
}
suspend operator fun invoke(git: Git, commitId: ObjectId): RevCommit? = withContext(Dispatchers.IO) {
return@withContext try {
git.repository.parseCommit(commitId)
} catch (ex: Exception) {
printError(TAG, "Commit $commitId not found", ex)
null
}
}
}

View File

@ -0,0 +1,14 @@
package com.jetpackduba.gitnuro.git.log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import javax.inject.Inject
class GetSpecificCommitMessageUseCase @Inject constructor(
private val findCommitUseCase: FindCommitUseCase,
) {
suspend operator fun invoke(git: Git, commitId: String): String = withContext(Dispatchers.IO) {
return@withContext findCommitUseCase(git, commitId)?.fullMessage.orEmpty()
}
}

View File

@ -0,0 +1,22 @@
package com.jetpackduba.gitnuro.git.rebase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.RebaseCommand
import java.io.File
import javax.inject.Inject
class GetRebaseAmendCommitIdUseCase @Inject constructor() {
suspend operator fun invoke(git: Git): String? = withContext(Dispatchers.IO) {
val repository = git.repository
val amendFile = File(repository.directory, "${RebaseCommand.REBASE_MERGE}/${RebaseConstants.AMEND}")
if (!amendFile.exists()) {
return@withContext null
}
return@withContext amendFile.readText().removeSuffix("\n").removeSuffix("\r\n")
}
}

View File

@ -0,0 +1,29 @@
package com.jetpackduba.gitnuro.git.rebase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import java.io.File
import javax.inject.Inject
class GetRebaseInteractiveStateUseCase @Inject constructor(
private val getRebaseAmendCommitIdUseCase: GetRebaseAmendCommitIdUseCase,
) {
suspend operator fun invoke(git: Git): RebaseInteractiveState = withContext(Dispatchers.IO) {
val repository = git.repository
val rebaseMergeDir = File(repository.directory, RebaseConstants.REBASE_MERGE)
val doneFile = File(rebaseMergeDir, RebaseConstants.DONE)
val stoppedShaFile = File(rebaseMergeDir, RebaseConstants.STOPPED_SHA)
return@withContext when {
!rebaseMergeDir.exists() -> RebaseInteractiveState.None
doneFile.exists() || stoppedShaFile.exists() -> {
val commitId: String? = getRebaseAmendCommitIdUseCase(git)
RebaseInteractiveState.ProcessingCommits(commitId)
}
else -> RebaseInteractiveState.AwaitingInteraction
}
}
}

View File

@ -0,0 +1,24 @@
package com.jetpackduba.gitnuro.git.rebase
import com.jetpackduba.gitnuro.logging.printDebug
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.RebaseCommand
import org.eclipse.jgit.lib.RebaseTodoLine
import javax.inject.Inject
private const val TAG = "GetRebaseInteractiveTod"
class GetRebaseInteractiveTodoLinesUseCase @Inject constructor() {
suspend operator fun invoke(git: Git): List<RebaseTodoLine> = withContext(Dispatchers.IO) {
val repository = git.repository
val filePath = "${RebaseCommand.REBASE_MERGE}/${RebaseConstants.GIT_REBASE_TODO}"
val lines = repository.readRebaseTodo(filePath, false)
printDebug(TAG, "There are ${lines.count()} lines")
return@withContext lines
}
}

View File

@ -0,0 +1,10 @@
package com.jetpackduba.gitnuro.git.rebase
object RebaseConstants {
const val GIT_REBASE_TODO = "git-rebase-todo"
const val REBASE_MERGE = "rebase-merge" //$NON-NLS-1$
const val DONE = "done"
const val STOPPED_SHA = "stopped-sha"
const val AMEND = "amend"
}

View File

@ -0,0 +1,9 @@
package com.jetpackduba.gitnuro.git.rebase
sealed interface RebaseInteractiveState {
object None : RebaseInteractiveState
object AwaitingInteraction : RebaseInteractiveState
data class ProcessingCommits(val commitToAmendId: String?) : RebaseInteractiveState {
val isCurrentStepAmenable: Boolean = !commitToAmendId.isNullOrBlank()
}
}

View File

@ -1,7 +1,6 @@
package com.jetpackduba.gitnuro.git.rebase
import com.jetpackduba.gitnuro.exceptions.UncommitedChangesDetectedException
import com.jetpackduba.gitnuro.logging.printDebug
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
@ -9,40 +8,33 @@ import org.eclipse.jgit.api.RebaseCommand
import org.eclipse.jgit.api.RebaseResult
import org.eclipse.jgit.lib.RebaseTodoLine
import org.eclipse.jgit.revwalk.RevCommit
import java.io.File
import javax.inject.Inject
private const val GIT_REBASE_TODO = "git-rebase-todo"
private const val TAG = "StartRebaseInteractiveU"
class StartRebaseInteractiveUseCase @Inject constructor() {
suspend operator fun invoke(
git: Git,
interactiveHandler: RebaseCommand.InteractiveHandler,
commit: RevCommit,
stop: Boolean
): List<RebaseTodoLine> =
withContext(Dispatchers.IO) {
val rebaseResult = git.rebase()
.runInteractively(interactiveHandler, stop)
.setOperation(RebaseCommand.Operation.BEGIN)
.setUpstream(commit)
.call()
) = withContext(Dispatchers.IO) {
when (rebaseResult.status) {
RebaseResult.Status.FAILED -> throw UncommitedChangesDetectedException("Rebase interactive failed.")
RebaseResult.Status.UNCOMMITTED_CHANGES, RebaseResult.Status.CONFLICTS -> throw UncommitedChangesDetectedException(
"You can't have uncommited changes before starting a rebase interactive"
)
else -> {}
}
val repository = git.repository
val lines = repository.readRebaseTodo("${RebaseCommand.REBASE_MERGE}/$GIT_REBASE_TODO", false)
printDebug(TAG, "There are ${lines.count()} lines")
return@withContext lines
val interactiveHandler = object : RebaseCommand.InteractiveHandler {
override fun prepareSteps(steps: MutableList<RebaseTodoLine>?) {}
override fun modifyCommitMessage(message: String?): String = ""
}
val rebaseResult = git.rebase()
.runInteractively(interactiveHandler, true)
.setOperation(RebaseCommand.Operation.BEGIN)
.setUpstream(commit)
.call()
when (rebaseResult.status) {
RebaseResult.Status.FAILED -> throw UncommitedChangesDetectedException("Rebase interactive failed.")
RebaseResult.Status.UNCOMMITTED_CHANGES, RebaseResult.Status.CONFLICTS -> throw UncommitedChangesDetectedException(
"You can't have uncommited changes before starting a rebase interactive"
)
else -> {}
}
}
}

View File

@ -15,16 +15,15 @@ import com.jetpackduba.gitnuro.AppIcons
import com.jetpackduba.gitnuro.ui.components.AdjustableOutlinedTextField
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
import com.jetpackduba.gitnuro.ui.components.ScrollableLazyColumn
import com.jetpackduba.gitnuro.ui.components.gitnuroDynamicViewModel
import com.jetpackduba.gitnuro.viewmodels.RebaseAction
import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveState
import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewState
import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewModel
import com.jetpackduba.gitnuro.viewmodels.RebaseLine
import org.eclipse.jgit.lib.RebaseTodoLine
import org.eclipse.jgit.lib.RebaseTodoLine.Action
@Composable
fun RebaseInteractive(
rebaseInteractiveViewModel: RebaseInteractiveViewModel,
rebaseInteractiveViewModel: RebaseInteractiveViewModel = gitnuroDynamicViewModel(),
) {
val rebaseState = rebaseInteractiveViewModel.rebaseState.collectAsState()
val rebaseStateValue = rebaseState.value
@ -35,8 +34,8 @@ fun RebaseInteractive(
.fillMaxSize(),
) {
when (rebaseStateValue) {
is RebaseInteractiveState.Failed -> {}
is RebaseInteractiveState.Loaded -> {
is RebaseInteractiveViewState.Failed -> {}
is RebaseInteractiveViewState.Loaded -> {
RebaseStateLoaded(
rebaseInteractiveViewModel,
rebaseStateValue,
@ -46,7 +45,7 @@ fun RebaseInteractive(
)
}
RebaseInteractiveState.Loading -> {
RebaseInteractiveViewState.Loading -> {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
}
@ -56,7 +55,7 @@ fun RebaseInteractive(
@Composable
fun RebaseStateLoaded(
rebaseInteractiveViewModel: RebaseInteractiveViewModel,
rebaseState: RebaseInteractiveState.Loaded,
rebaseState: RebaseInteractiveViewState.Loaded,
onCancel: () -> Unit,
) {
val stepsList = rebaseState.stepsList
@ -220,6 +219,7 @@ fun ActionDropdown(
val firstItemActions = listOf(
RebaseAction.PICK,
RebaseAction.REWORD,
RebaseAction.DROP,
)
val actions = listOf(

View File

@ -18,11 +18,12 @@ import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.*
import androidx.compose.ui.unit.dp
import com.jetpackduba.gitnuro.AppConstants
import com.jetpackduba.gitnuro.LocalTabScope
import com.jetpackduba.gitnuro.extensions.handMouseClickable
import com.jetpackduba.gitnuro.git.DiffEntryType
import com.jetpackduba.gitnuro.git.rebase.RebaseInteractiveState
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
import com.jetpackduba.gitnuro.keybindings.matchesBinding
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
@ -53,6 +54,7 @@ fun RepositoryOpenPage(
val blameState by tabViewModel.blameState.collectAsState()
val showHistory by tabViewModel.showHistory.collectAsState()
val showAuthorInfo by tabViewModel.showAuthorInfo.collectAsState()
val rebaseInteractiveState by tabViewModel.rebaseInteractiveState.collectAsState()
var showNewBranchDialog by remember { mutableStateOf(false) }
var showStashWithMessageDialog by remember { mutableStateOf(false) }
@ -129,14 +131,8 @@ fun RepositoryOpenPage(
}
}
) {
val rebaseInteractiveViewModel = tabViewModel.rebaseInteractiveViewModel
if (repositoryState == RepositoryState.REBASING_INTERACTIVE && rebaseInteractiveViewModel != null) {
RebaseInteractive(rebaseInteractiveViewModel)
} else if (repositoryState == RepositoryState.REBASING_INTERACTIVE) {
RebaseInteractiveStartedExternally(
onCancelRebaseInteractive = { tabViewModel.cancelRebaseInteractive() }
)
if (rebaseInteractiveState == RebaseInteractiveState.AwaitingInteraction) {
RebaseInteractive()
} else {
val currentTabInformation = LocalTabScope.current
Column(modifier = Modifier.weight(1f)) {
@ -366,45 +362,48 @@ fun MainContentView(
modifier = Modifier
.fillMaxHeight()
) {
val safeSelectedItem = selectedItem
if (safeSelectedItem == SelectedItem.UncommitedChanges) {
UncommitedChanges(
selectedEntryType = diffSelected,
repositoryState = repositoryState,
onStagedDiffEntrySelected = { diffEntry ->
tabViewModel.minimizeBlame()
when (selectedItem) {
SelectedItem.UncommitedChanges -> {
UncommitedChanges(
selectedEntryType = diffSelected,
repositoryState = repositoryState,
onStagedDiffEntrySelected = { diffEntry ->
tabViewModel.minimizeBlame()
tabViewModel.newDiffSelected = if (diffEntry != null) {
if (repositoryState == RepositoryState.SAFE)
DiffEntryType.SafeStagedDiff(diffEntry)
else
DiffEntryType.UnsafeStagedDiff(diffEntry)
} else {
null
}
},
onUnstagedDiffEntrySelected = { diffEntry ->
tabViewModel.minimizeBlame()
tabViewModel.newDiffSelected = if (diffEntry != null) {
if (repositoryState == RepositoryState.SAFE)
DiffEntryType.SafeStagedDiff(diffEntry)
tabViewModel.newDiffSelected = DiffEntryType.SafeUnstagedDiff(diffEntry)
else
DiffEntryType.UnsafeStagedDiff(diffEntry)
} else {
null
}
},
onUnstagedDiffEntrySelected = { diffEntry ->
tabViewModel.minimizeBlame()
if (repositoryState == RepositoryState.SAFE)
tabViewModel.newDiffSelected = DiffEntryType.SafeUnstagedDiff(diffEntry)
else
tabViewModel.newDiffSelected = DiffEntryType.UnsafeUnstagedDiff(diffEntry)
},
onBlameFile = { tabViewModel.blameFile(it) },
onHistoryFile = { tabViewModel.fileHistory(it) }
)
} else if (safeSelectedItem is SelectedItem.CommitBasedItem) {
CommitChanges(
selectedItem = safeSelectedItem,
diffSelected = diffSelected,
onDiffSelected = { diffEntry ->
tabViewModel.minimizeBlame()
tabViewModel.newDiffSelected = DiffEntryType.CommitDiff(diffEntry)
},
onBlame = { tabViewModel.blameFile(it) },
onHistory = { tabViewModel.fileHistory(it) },
)
tabViewModel.newDiffSelected = DiffEntryType.UnsafeUnstagedDiff(diffEntry)
},
onBlameFile = { tabViewModel.blameFile(it) },
onHistoryFile = { tabViewModel.fileHistory(it) }
)
}
is SelectedItem.CommitBasedItem -> {
CommitChanges(
selectedItem = selectedItem,
diffSelected = diffSelected,
onDiffSelected = { diffEntry ->
tabViewModel.minimizeBlame()
tabViewModel.newDiffSelected = DiffEntryType.CommitDiff(diffEntry)
},
onBlame = { tabViewModel.blameFile(it) },
onHistory = { tabViewModel.fileHistory(it) },
)
}
SelectedItem.None -> {}
}
}
}

View File

@ -29,11 +29,11 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import com.jetpackduba.gitnuro.AppIcons
import com.jetpackduba.gitnuro.extensions.*
import com.jetpackduba.gitnuro.git.DiffEntryType
import com.jetpackduba.gitnuro.git.rebase.RebaseInteractiveState
import com.jetpackduba.gitnuro.git.workspace.StatusEntry
import com.jetpackduba.gitnuro.git.workspace.StatusType
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
@ -66,14 +66,19 @@ fun UncommitedChanges(
val stagedListState by statusViewModel.stagedLazyListState.collectAsState()
val unstagedListState by statusViewModel.unstagedLazyListState.collectAsState()
val isAmend by statusViewModel.isAmend.collectAsState()
val isAmendRebaseInteractive by statusViewModel.isAmendRebaseInteractive.collectAsState()
val committerDataRequestState = statusViewModel.committerDataRequestState.collectAsState()
val committerDataRequestStateValue = committerDataRequestState.value
val rebaseInteractiveState = statusViewModel.rebaseInteractiveState.collectAsState().value
val showSearchStaged by statusViewModel.showSearchStaged.collectAsState()
val searchFilterStaged by statusViewModel.searchFilterStaged.collectAsState()
val showSearchUnstaged by statusViewModel.showSearchUnstaged.collectAsState()
val searchFilterUnstaged by statusViewModel.searchFilterUnstaged.collectAsState()
val isAmenableRebaseInteractive =
repositoryState.isRebasing && rebaseInteractiveState is RebaseInteractiveState.ProcessingCommits && rebaseInteractiveState.isCurrentStepAmenable
val staged: List<StatusEntry>
val unstaged: List<StatusEntry>
val isLoading: Boolean
@ -247,9 +252,9 @@ fun UncommitedChanges(
statusViewModel.updateCommitMessage(it)
},
enabled = !repositoryState.isRebasing,
enabled = !repositoryState.isRebasing || isAmenableRebaseInteractive,
label = {
val text = if (repositoryState.isRebasing) {
val text = if (repositoryState.isRebasing && !isAmenableRebaseInteractive) {
"Commit message (read-only)"
} else {
"Write your commit message here"
@ -275,15 +280,20 @@ fun UncommitedChanges(
onMerge = { doCommit() }
)
repositoryState.isRebasing -> RebasingButtons(
canContinue = staged.isNotEmpty() || unstaged.isNotEmpty(),
repositoryState.isRebasing && rebaseInteractiveState is RebaseInteractiveState.ProcessingCommits -> RebasingButtons(
canContinue = staged.isNotEmpty() || unstaged.isNotEmpty() || (isAmenableRebaseInteractive && isAmendRebaseInteractive && commitMessage.isNotEmpty()),
haveConflictsBeenSolved = unstaged.isEmpty(),
onAbort = {
statusViewModel.abortRebase()
statusViewModel.updateCommitMessage("")
},
onContinue = { statusViewModel.continueRebase() },
onContinue = { statusViewModel.continueRebase(commitMessage) },
onSkip = { statusViewModel.skipRebase() },
isAmendable = rebaseInteractiveState.isCurrentStepAmenable,
isAmend = isAmendRebaseInteractive,
onAmendChecked = { isAmend ->
statusViewModel.amendRebaseInteractive(isAmend)
}
)
repositoryState.isCherryPicking -> CherryPickingButtons(
@ -338,31 +348,11 @@ fun UncommitedChangesButtons(
"Commit"
Column {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.handMouseClickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
) {
onAmendChecked(!isAmend)
}
) {
Checkbox(
checked = isAmend,
onCheckedChange = {
onAmendChecked(!isAmend)
},
modifier = Modifier
.padding(all = 8.dp)
.size(12.dp)
)
Text(
"Amend previous commit",
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onBackground,
)
}
CheckboxText(
value = isAmend,
onCheckedChange = { onAmendChecked(!isAmend) },
text = "Amend previous commit"
)
Row(
modifier = Modifier
.padding(top = 2.dp)
@ -439,40 +429,52 @@ fun CherryPickingButtons(
@Composable
fun RebasingButtons(
canContinue: Boolean,
isAmendable: Boolean,
isAmend: Boolean,
onAmendChecked: (Boolean) -> Unit,
haveConflictsBeenSolved: Boolean,
onAbort: () -> Unit,
onContinue: () -> Unit,
onSkip: () -> Unit,
) {
Row(
modifier = Modifier.fillMaxWidth()
) {
AbortButton(
modifier = Modifier
.weight(1f)
.padding(end = 4.dp),
onClick = onAbort
)
if (canContinue) {
ConfirmationButton(
text = "Continue",
modifier = Modifier
.weight(1f)
.padding(start = 4.dp),
enabled = haveConflictsBeenSolved,
onClick = onContinue,
)
} else {
ConfirmationButton(
text = "Skip",
modifier = Modifier
.weight(1f)
.padding(end = 4.dp),
onClick = onSkip,
Column {
if (isAmendable) {
CheckboxText(
value = isAmend,
onCheckedChange = { onAmendChecked(!isAmend) },
text = "Amend previous commit"
)
}
Row(
modifier = Modifier.fillMaxWidth()
) {
AbortButton(
modifier = Modifier
.weight(1f)
.padding(end = 4.dp),
onClick = onAbort
)
if (canContinue) {
ConfirmationButton(
text = "Continue",
modifier = Modifier
.weight(1f)
.padding(start = 4.dp),
enabled = haveConflictsBeenSolved,
onClick = onContinue,
)
} else {
ConfirmationButton(
text = "Skip",
modifier = Modifier
.weight(1f)
.padding(start = 4.dp),
onClick = onSkip,
)
}
}
}
}
@ -759,18 +761,6 @@ private fun FileEntry(
}
}
@Stable
val BottomReversed = object : Arrangement.Vertical {
override fun Density.arrange(
totalSize: Int,
sizes: IntArray,
outPositions: IntArray
) = placeRightOrBottom(totalSize, sizes, outPositions, reverseInput = true)
override fun toString() = "Arrangement#BottomReversed"
}
internal fun placeRightOrBottom(
totalSize: Int,
size: IntArray,

View File

@ -0,0 +1,45 @@
package com.jetpackduba.gitnuro.ui.components
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Checkbox
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.jetpackduba.gitnuro.extensions.handMouseClickable
@Composable
fun CheckboxText(
value: Boolean,
onCheckedChange: () -> Unit,
text: String,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.handMouseClickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = onCheckedChange,
)
) {
Checkbox(
checked = value,
onCheckedChange = { onCheckedChange() },
modifier = Modifier
.padding(all = 8.dp)
.size(12.dp)
)
Text(
text,
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onBackground,
)
}
}

View File

@ -13,6 +13,7 @@ import com.jetpackduba.gitnuro.git.graph.GraphCommitList
import com.jetpackduba.gitnuro.git.graph.GraphNode
import com.jetpackduba.gitnuro.git.log.*
import com.jetpackduba.gitnuro.git.rebase.RebaseBranchUseCase
import com.jetpackduba.gitnuro.git.rebase.StartRebaseInteractiveUseCase
import com.jetpackduba.gitnuro.git.remote_operations.DeleteRemoteBranchUseCase
import com.jetpackduba.gitnuro.git.remote_operations.PullFromSpecificBranchUseCase
import com.jetpackduba.gitnuro.git.remote_operations.PushToSpecificBranchUseCase
@ -65,6 +66,7 @@ class LogViewModel @Inject constructor(
private val createTagOnCommitUseCase: CreateTagOnCommitUseCase,
private val deleteTagUseCase: DeleteTagUseCase,
private val rebaseBranchUseCase: RebaseBranchUseCase,
private val startRebaseInteractiveUseCase: StartRebaseInteractiveUseCase,
private val tabState: TabState,
private val appSettings: AppSettings,
private val tabScope: CoroutineScope,
@ -97,7 +99,6 @@ class LogViewModel @Inject constructor(
val verticalListState = MutableStateFlow(LazyListState(0, 0))
val horizontalListState = MutableStateFlow(ScrollState(0))
private val _logSearchFilterResults = MutableStateFlow<LogSearch>(LogSearch.NotSearching)
val logSearchFilterResults: StateFlow<LogSearch> = _logSearchFilterResults
@ -428,10 +429,10 @@ class LogViewModel @Inject constructor(
_logSearchFilterResults.value = LogSearch.NotSearching
}
fun rebaseInteractive(revCommit: RevCommit) = tabState.runOperation(
refreshType = RefreshType.NONE
) {
tabState.emitNewTaskEvent(TaskEvent.RebaseInteractive(revCommit))
fun rebaseInteractive(revCommit: RevCommit) = tabState.safeProcessing(
refreshType = RefreshType.REBASE_INTERACTIVE_STATE,
) { git ->
startRebaseInteractiveUseCase(git, revCommit)
}
fun deleteRemoteBranch(branch: Ref) = tabState.safeProcessing(

View File

@ -4,10 +4,7 @@ import com.jetpackduba.gitnuro.exceptions.InvalidMessageException
import com.jetpackduba.gitnuro.exceptions.RebaseCancelledException
import com.jetpackduba.gitnuro.git.RefreshType
import com.jetpackduba.gitnuro.git.TabState
import com.jetpackduba.gitnuro.git.rebase.AbortRebaseUseCase
import com.jetpackduba.gitnuro.git.rebase.GetRebaseLinesFullMessageUseCase
import com.jetpackduba.gitnuro.git.rebase.ResumeRebaseInteractiveUseCase
import com.jetpackduba.gitnuro.git.rebase.StartRebaseInteractiveUseCase
import com.jetpackduba.gitnuro.git.rebase.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler
@ -22,21 +19,23 @@ private const val TAG = "RebaseInteractiveViewMo"
class RebaseInteractiveViewModel @Inject constructor(
private val tabState: TabState,
private val getRebaseLinesFullMessageUseCase: GetRebaseLinesFullMessageUseCase,
private val startRebaseInteractiveUseCase: StartRebaseInteractiveUseCase,
private val getRebaseInteractiveTodoLinesUseCase: GetRebaseInteractiveTodoLinesUseCase,
private val abortRebaseUseCase: AbortRebaseUseCase,
private val resumeRebaseInteractiveUseCase: ResumeRebaseInteractiveUseCase,
) {
private lateinit var commit: RevCommit
private val _rebaseState = MutableStateFlow<RebaseInteractiveState>(RebaseInteractiveState.Loading)
val rebaseState: StateFlow<RebaseInteractiveState> = _rebaseState
private val _rebaseState = MutableStateFlow<RebaseInteractiveViewState>(RebaseInteractiveViewState.Loading)
val rebaseState: StateFlow<RebaseInteractiveViewState> = _rebaseState
var rewordSteps = ArrayDeque<RebaseLine>()
var onRebaseComplete: () -> Unit = {}
init {
loadRebaseInteractiveData()
}
private var interactiveHandlerContinue = object : InteractiveHandler {
override fun prepareSteps(steps: MutableList<RebaseTodoLine>) {
val rebaseState = _rebaseState.value
if (rebaseState !is RebaseInteractiveState.Loaded) {
if (rebaseState !is RebaseInteractiveViewState.Loaded) {
throw Exception("prepareSteps called when rebaseState is not Loaded") // Should never happen, just in case
}
@ -56,7 +55,7 @@ class RebaseInteractiveViewModel @Inject constructor(
val step = rewordSteps.removeLastOrNull() ?: return commit
val rebaseState = _rebaseState.value
if (rebaseState !is RebaseInteractiveState.Loaded) {
if (rebaseState !is RebaseInteractiveViewState.Loaded) {
throw Exception("modifyCommitMessage called when rebaseState is not Loaded") // Should never happen, just in case
}
@ -65,19 +64,11 @@ class RebaseInteractiveViewModel @Inject constructor(
}
}
suspend fun startRebaseInteractive(revCommit: RevCommit) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
showError = true
private fun loadRebaseInteractiveData() = tabState.safeProcessing(
refreshType = RefreshType.NONE,
) { git ->
this@RebaseInteractiveViewModel.commit = revCommit
val interactiveHandler = object : InteractiveHandler {
override fun prepareSteps(steps: MutableList<RebaseTodoLine>?) {}
override fun modifyCommitMessage(message: String?): String = ""
}
try {
val lines = startRebaseInteractiveUseCase(git, interactiveHandler, revCommit, true)
val lines = getRebaseInteractiveTodoLinesUseCase(git)
val messages = getRebaseLinesFullMessageUseCase(tabState.git, lines)
val rebaseLines = lines.map {
RebaseLine(
@ -87,7 +78,7 @@ class RebaseInteractiveViewModel @Inject constructor(
)
}
_rebaseState.value = RebaseInteractiveState.Loaded(rebaseLines, messages)
_rebaseState.value = RebaseInteractiveViewState.Loaded(rebaseLines, messages)
} catch (ex: Exception) {
if (ex is RebaseCancelledException) {
@ -102,17 +93,13 @@ class RebaseInteractiveViewModel @Inject constructor(
fun continueRebaseInteractive() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
try {
resumeRebaseInteractiveUseCase(git, interactiveHandlerContinue)
} finally {
onRebaseComplete()
}
resumeRebaseInteractiveUseCase(git, interactiveHandlerContinue)
}
fun onCommitMessageChanged(commit: AbbreviatedObjectId, message: String) {
val rebaseState = _rebaseState.value
if (rebaseState !is RebaseInteractiveState.Loaded)
if (rebaseState !is RebaseInteractiveViewState.Loaded)
return
val messagesMap = rebaseState.messages.toMutableMap()
@ -124,7 +111,7 @@ class RebaseInteractiveViewModel @Inject constructor(
fun onCommitActionChanged(commit: AbbreviatedObjectId, rebaseAction: RebaseAction) {
val rebaseState = _rebaseState.value
if (rebaseState !is RebaseInteractiveState.Loaded)
if (rebaseState !is RebaseInteractiveViewState.Loaded)
return
val newStepsList =
@ -149,17 +136,17 @@ class RebaseInteractiveViewModel @Inject constructor(
}
fun cancel() = tabState.runOperation(
refreshType = RefreshType.REPO_STATE
refreshType = RefreshType.ALL_DATA,
) { git ->
abortRebaseUseCase(git)
}
}
sealed interface RebaseInteractiveState {
object Loading : RebaseInteractiveState
data class Loaded(val stepsList: List<RebaseLine>, val messages: Map<String, String>) : RebaseInteractiveState
data class Failed(val error: String) : RebaseInteractiveState
sealed interface RebaseInteractiveViewState {
object Loading : RebaseInteractiveViewState
data class Loaded(val stepsList: List<RebaseLine>, val messages: Map<String, String>) : RebaseInteractiveViewState
data class Failed(val error: String) : RebaseInteractiveViewState
}
data class RebaseLine(

View File

@ -2,6 +2,7 @@ package com.jetpackduba.gitnuro.viewmodels
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.ui.text.input.TextFieldValue
import com.jetpackduba.gitnuro.SharedRepositoryStateManager
import com.jetpackduba.gitnuro.extensions.delayedStateChange
import com.jetpackduba.gitnuro.extensions.isMerging
import com.jetpackduba.gitnuro.extensions.isReverting
@ -12,9 +13,9 @@ import com.jetpackduba.gitnuro.git.author.LoadAuthorUseCase
import com.jetpackduba.gitnuro.git.author.SaveAuthorUseCase
import com.jetpackduba.gitnuro.git.log.CheckHasPreviousCommitsUseCase
import com.jetpackduba.gitnuro.git.log.GetLastCommitMessageUseCase
import com.jetpackduba.gitnuro.git.rebase.AbortRebaseUseCase
import com.jetpackduba.gitnuro.git.rebase.ContinueRebaseUseCase
import com.jetpackduba.gitnuro.git.rebase.SkipRebaseUseCase
import com.jetpackduba.gitnuro.git.log.GetSpecificCommitMessageUseCase
import com.jetpackduba.gitnuro.git.rebase.*
import com.jetpackduba.gitnuro.git.repository.GetRepositoryStateUseCase
import com.jetpackduba.gitnuro.git.repository.ResetRepositoryStateUseCase
import com.jetpackduba.gitnuro.git.workspace.*
import com.jetpackduba.gitnuro.models.AuthorInfo
@ -52,6 +53,10 @@ class StatusViewModel @Inject constructor(
private val doCommitUseCase: DoCommitUseCase,
private val loadAuthorUseCase: LoadAuthorUseCase,
private val saveAuthorUseCase: SaveAuthorUseCase,
private val getRepositoryStateUseCase: GetRepositoryStateUseCase,
private val getRebaseAmendCommitIdUseCase: GetRebaseAmendCommitIdUseCase,
private val sharedRepositoryStateManager: SharedRepositoryStateManager,
private val getSpecificCommitMessageUseCase: GetSpecificCommitMessageUseCase,
private val tabScope: CoroutineScope,
private val appSettings: AppSettings,
) {
@ -68,6 +73,7 @@ class StatusViewModel @Inject constructor(
val searchFilterStaged: StateFlow<TextFieldValue> = _searchFilterStaged
val swapUncommitedChanges = appSettings.swapUncommitedChangesFlow
val rebaseInteractiveState = sharedRepositoryStateManager.rebaseInteractiveState
private val _stageState = MutableStateFlow<StageState>(StageState.Loading)
@ -123,6 +129,9 @@ class StatusViewModel @Inject constructor(
private val _isAmend = MutableStateFlow(false)
val isAmend: StateFlow<Boolean> = _isAmend
private val _isAmendRebaseInteractive = MutableStateFlow(false)
val isAmendRebaseInteractive: StateFlow<Boolean> = _isAmendRebaseInteractive
init {
tabScope.launch {
tabState.refreshFlowFiltered(
@ -262,6 +271,14 @@ class StatusViewModel @Inject constructor(
}
}
fun amendRebaseInteractive(isAmend: Boolean) {
_isAmendRebaseInteractive.value = isAmend
if (isAmend && savedCommitMessage.message.isEmpty()) {
takeMessageFromAmendCommit()
}
}
fun commit(message: String) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
@ -272,9 +289,18 @@ class StatusViewModel @Inject constructor(
} else
message
val personIdent = getPersonIdent(git)
doCommitUseCase(git, commitMessage, amend, personIdent)
updateCommitMessage("")
_isAmend.value = false
}
private suspend fun getPersonIdent(git: Git): PersonIdent? {
val author = loadAuthorUseCase(git)
val personIdent = if (
return if (
author.name.isNullOrEmpty() && author.globalName.isNullOrEmpty() ||
author.email.isNullOrEmpty() && author.globalEmail.isNullOrEmpty()
) {
@ -299,10 +325,6 @@ class StatusViewModel @Inject constructor(
}
} else
null
doCommitUseCase(git, commitMessage, amend, personIdent)
updateCommitMessage("")
_isAmend.value = false
}
suspend fun refresh(git: Git) = withContext(Dispatchers.IO) {
@ -326,9 +348,25 @@ class StatusViewModel @Inject constructor(
return (hasNowUncommitedChanges != hadUncommitedChanges)
}
fun continueRebase() = tabState.safeProcessing(
fun continueRebase(message: String) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
val repositoryState = sharedRepositoryStateManager.repositoryState.value
val rebaseInteractiveState = sharedRepositoryStateManager.rebaseInteractiveState.value
if (
repositoryState == RepositoryState.REBASING_INTERACTIVE &&
rebaseInteractiveState is RebaseInteractiveState.ProcessingCommits &&
rebaseInteractiveState.isCurrentStepAmenable &&
isAmendRebaseInteractive.value
) {
val amendCommitId = getRebaseAmendCommitIdUseCase(git)
if (!amendCommitId.isNullOrBlank()) {
doCommitUseCase(git, message, true, getPersonIdent(git))
}
}
continueRebaseUseCase(git)
}
@ -373,6 +411,22 @@ class StatusViewModel @Inject constructor(
_commitMessageChangesFlow.emit(savedCommitMessage.message)
}
private fun takeMessageFromAmendCommit() = tabState.runOperation(
refreshType = RefreshType.NONE,
) { git ->
val rebaseInteractiveState = rebaseInteractiveState.value
if (rebaseInteractiveState !is RebaseInteractiveState.ProcessingCommits) {
return@runOperation
}
val commitId = rebaseInteractiveState.commitToAmendId ?: return@runOperation
val message = getSpecificCommitMessageUseCase(git, commitId)
savedCommitMessage = savedCommitMessage.copy(message = message)
persistMessage()
_commitMessageChangesFlow.emit(savedCommitMessage.message)
}
fun onRejectCommitterData() {
this._committerDataRequestState.value = CommitterDataRequestState.Reject
}

View File

@ -1,11 +1,13 @@
package com.jetpackduba.gitnuro.viewmodels
import com.jetpackduba.gitnuro.SharedRepositoryStateManager
import com.jetpackduba.gitnuro.credentials.CredentialsAccepted
import com.jetpackduba.gitnuro.credentials.CredentialsState
import com.jetpackduba.gitnuro.credentials.CredentialsStateManager
import com.jetpackduba.gitnuro.git.*
import com.jetpackduba.gitnuro.git.branches.CreateBranchUseCase
import com.jetpackduba.gitnuro.git.rebase.AbortRebaseUseCase
import com.jetpackduba.gitnuro.git.rebase.GetRebaseInteractiveStateUseCase
import com.jetpackduba.gitnuro.git.rebase.RebaseInteractiveState
import com.jetpackduba.gitnuro.git.repository.GetRepositoryStateUseCase
import com.jetpackduba.gitnuro.git.repository.InitLocalRepositoryUseCase
import com.jetpackduba.gitnuro.git.repository.OpenRepositoryUseCase
@ -54,7 +56,6 @@ class TabViewModel @Inject constructor(
private val openRepositoryUseCase: OpenRepositoryUseCase,
private val openSubmoduleRepositoryUseCase: OpenSubmoduleRepositoryUseCase,
private val diffViewModelProvider: Provider<DiffViewModel>,
private val rebaseInteractiveViewModelProvider: Provider<RebaseInteractiveViewModel>,
private val historyViewModelProvider: Provider<HistoryViewModel>,
private val authorViewModelProvider: Provider<AuthorViewModel>,
private val tabState: TabState,
@ -65,9 +66,10 @@ class TabViewModel @Inject constructor(
private val createBranchUseCase: CreateBranchUseCase,
private val stashChangesUseCase: StashChangesUseCase,
private val stageUntrackedFileUseCase: StageUntrackedFileUseCase,
private val abortRebaseUseCase: AbortRebaseUseCase,
private val openFilePickerUseCase: OpenFilePickerUseCase,
private val openUrlInBrowserUseCase: OpenUrlInBrowserUseCase,
private val getRebaseInteractiveStateUseCase: GetRebaseInteractiveStateUseCase,
private val sharedRepositoryStateManager: SharedRepositoryStateManager,
private val tabsManager: TabsManager,
private val tabScope: CoroutineScope,
) {
@ -76,13 +78,13 @@ class TabViewModel @Inject constructor(
val selectedItem: StateFlow<SelectedItem> = tabState.selectedItem
var diffViewModel: DiffViewModel? = null
var rebaseInteractiveViewModel: RebaseInteractiveViewModel? = null
private set
private val _repositorySelectionStatus = MutableStateFlow<RepositorySelectionStatus>(RepositorySelectionStatus.None)
val repositorySelectionStatus: StateFlow<RepositorySelectionStatus>
get() = _repositorySelectionStatus
val repositoryState: StateFlow<RepositoryState> = sharedRepositoryStateManager.repositoryState
val rebaseInteractiveState: StateFlow<RebaseInteractiveState> = sharedRepositoryStateManager.rebaseInteractiveState
val processing: StateFlow<ProcessingState> = tabState.processing
val credentialsState: StateFlow<CredentialsState> = credentialsStateManager.credentialsState
@ -97,9 +99,6 @@ class TabViewModel @Inject constructor(
updateDiffEntry()
}
private val _repositoryState = MutableStateFlow(RepositoryState.SAFE)
val repositoryState: StateFlow<RepositoryState> = _repositoryState
private val _blameState = MutableStateFlow<BlameState>(BlameState.None)
val blameState: StateFlow<BlameState> = _blameState
@ -123,28 +122,8 @@ class TabViewModel @Inject constructor(
init {
tabScope.run {
launch {
tabState.refreshData.collect { refreshType ->
when (refreshType) {
RefreshType.NONE -> printLog(TAG, "Not refreshing...")
RefreshType.REPO_STATE -> refreshRepositoryState()
else -> {}
}
}
}
launch {
tabState.taskEvent.collect { taskEvent ->
when (taskEvent) {
is TaskEvent.RebaseInteractive -> onRebaseInteractive(taskEvent)
else -> { /*Nothing to do here*/
}
}
}
}
launch {
tabState.refreshFlowFiltered(RefreshType.ALL_DATA, RefreshType.REPO_STATE)
{
loadRepositoryState(tabState.git)
tabState.refreshFlowFiltered(RefreshType.ALL_DATA, RefreshType.REPO_STATE) {
loadAuthorInfo(tabState.git)
}
}
@ -156,19 +135,6 @@ class TabViewModel @Inject constructor(
}
}
private fun refreshRepositoryState() = tabState.safeProcessing(
refreshType = RefreshType.NONE,
) { git ->
loadRepositoryState(git)
}
private suspend fun onRebaseInteractive(taskEvent: TaskEvent.RebaseInteractive) {
rebaseInteractiveViewModel = rebaseInteractiveViewModelProvider.get()
rebaseInteractiveViewModel?.startRebaseInteractive(taskEvent.revCommit)
rebaseInteractiveViewModel?.onRebaseComplete = {
rebaseInteractiveViewModel = null
}
}
/**
* To make sure the tab opens the new repository with a clean state,
@ -220,16 +186,6 @@ class TabViewModel @Inject constructor(
}
}
private suspend fun loadRepositoryState(git: Git) = withContext(Dispatchers.IO) {
val newRepoState = getRepositoryStateUseCase(git)
printLog(TAG, "Refreshing repository state $newRepoState")
_repositoryState.value = newRepoState
loadAuthorInfo(git)
onRepositoryStateChanged(newRepoState)
}
private fun loadAuthorInfo(git: Git) {
val config = git.repository.config
config.load()
@ -250,13 +206,6 @@ class TabViewModel @Inject constructor(
authorViewModel = null
}
private fun onRepositoryStateChanged(newRepoState: RepositoryState) {
if (newRepoState != RepositoryState.REBASING_INTERACTIVE && rebaseInteractiveViewModel != null) {
rebaseInteractiveViewModel?.cancel()
rebaseInteractiveViewModel = null
}
}
private suspend fun watchRepositoryChanges(git: Git) = tabScope.launch(Dispatchers.IO) {
val ignored = git.status().call().ignoredNotInIndex.toList()
var asyncJob: Job? = null
@ -470,13 +419,6 @@ class TabViewModel @Inject constructor(
Desktop.getDesktop().open(git.repository.workTree)
}
fun cancelRebaseInteractive() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
abortRebaseUseCase(git)
rebaseInteractiveViewModel = null // shouldn't be necessary but just to make sure
}
fun gpgCredentialsAccepted(password: String) {
credentialsStateManager.updateState(CredentialsAccepted.GpgCredentialsAccepted(password))
}