diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/SharedRepositoryStateManager.kt b/src/main/kotlin/com/jetpackduba/gitnuro/SharedRepositoryStateManager.kt new file mode 100644 index 0000000..dab2496 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/SharedRepositoryStateManager.kt @@ -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.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 + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/TabState.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/TabState.kt index 6347083..69da226 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/TabState.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/TabState.kt @@ -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.UncommitedChanges) val selectedItem: StateFlow = _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, } diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/TaskEvent.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/TaskEvent.kt index 53687f6..6c2568d 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/TaskEvent.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/TaskEvent.kt @@ -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 } \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/log/FindCommitUseCase.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/log/FindCommitUseCase.kt new file mode 100644 index 0000000..880b9b9 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/log/FindCommitUseCase.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/log/GetSpecificCommitMessageUseCase.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/log/GetSpecificCommitMessageUseCase.kt new file mode 100644 index 0000000..7706b4f --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/log/GetSpecificCommitMessageUseCase.kt @@ -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() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetRebaseAmendCommitIdUseCase.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetRebaseAmendCommitIdUseCase.kt new file mode 100644 index 0000000..dcb00b1 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetRebaseAmendCommitIdUseCase.kt @@ -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") + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetRebaseInteractiveStateUseCase.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetRebaseInteractiveStateUseCase.kt new file mode 100644 index 0000000..5eae9a3 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetRebaseInteractiveStateUseCase.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetRebaseInteractiveTodoLinesUseCase.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetRebaseInteractiveTodoLinesUseCase.kt new file mode 100644 index 0000000..12cdab1 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetRebaseInteractiveTodoLinesUseCase.kt @@ -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 = 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 + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/RebaseConstants.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/RebaseConstants.kt new file mode 100644 index 0000000..cc2e793 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/RebaseConstants.kt @@ -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" +} diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/RebaseInteractiveState.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/RebaseInteractiveState.kt new file mode 100644 index 0000000..bc24dcd --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/RebaseInteractiveState.kt @@ -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() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/StartRebaseInteractiveUseCase.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/StartRebaseInteractiveUseCase.kt index 3be3ad1..e850d8c 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/StartRebaseInteractiveUseCase.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/StartRebaseInteractiveUseCase.kt @@ -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 = - 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?) {} + 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 -> {} + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/RebaseInteractive.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/RebaseInteractive.kt index bb53ccb..e1a32d7 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/RebaseInteractive.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/RebaseInteractive.kt @@ -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( diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt index 6ff1537..258dc56 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt @@ -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 -> {} } } } diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/UncommitedChanges.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/UncommitedChanges.kt index b7128b5..c6703b3 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/UncommitedChanges.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/UncommitedChanges.kt @@ -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 val unstaged: List 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, diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/components/CheckboxText.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/components/CheckboxText.kt new file mode 100644 index 0000000..6f8c118 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/components/CheckboxText.kt @@ -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, + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt index fd1d697..e8a9383 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt @@ -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.NotSearching) val logSearchFilterResults: StateFlow = _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( diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RebaseInteractiveViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RebaseInteractiveViewModel.kt index a332dca..7b3f0eb 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RebaseInteractiveViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RebaseInteractiveViewModel.kt @@ -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.Loading) - val rebaseState: StateFlow = _rebaseState + private val _rebaseState = MutableStateFlow(RebaseInteractiveViewState.Loading) + val rebaseState: StateFlow = _rebaseState var rewordSteps = ArrayDeque() - var onRebaseComplete: () -> Unit = {} + init { + loadRebaseInteractiveData() + } private var interactiveHandlerContinue = object : InteractiveHandler { override fun prepareSteps(steps: MutableList) { 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?) {} - 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, val messages: Map) : RebaseInteractiveState - data class Failed(val error: String) : RebaseInteractiveState +sealed interface RebaseInteractiveViewState { + object Loading : RebaseInteractiveViewState + data class Loaded(val stepsList: List, val messages: Map) : RebaseInteractiveViewState + data class Failed(val error: String) : RebaseInteractiveViewState } data class RebaseLine( diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/StatusViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/StatusViewModel.kt index 32f6cd7..d53b292 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/StatusViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/StatusViewModel.kt @@ -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 = _searchFilterStaged val swapUncommitedChanges = appSettings.swapUncommitedChangesFlow + val rebaseInteractiveState = sharedRepositoryStateManager.rebaseInteractiveState private val _stageState = MutableStateFlow(StageState.Loading) @@ -123,6 +129,9 @@ class StatusViewModel @Inject constructor( private val _isAmend = MutableStateFlow(false) val isAmend: StateFlow = _isAmend + private val _isAmendRebaseInteractive = MutableStateFlow(false) + val isAmendRebaseInteractive: StateFlow = _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 } diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModel.kt index 8ecc9c5..a5de2fe 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModel.kt @@ -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, - private val rebaseInteractiveViewModelProvider: Provider, private val historyViewModelProvider: Provider, private val authorViewModelProvider: Provider, 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 = tabState.selectedItem var diffViewModel: DiffViewModel? = null - var rebaseInteractiveViewModel: RebaseInteractiveViewModel? = null - private set - private val _repositorySelectionStatus = MutableStateFlow(RepositorySelectionStatus.None) val repositorySelectionStatus: StateFlow get() = _repositorySelectionStatus + val repositoryState: StateFlow = sharedRepositoryStateManager.repositoryState + val rebaseInteractiveState: StateFlow = sharedRepositoryStateManager.rebaseInteractiveState + val processing: StateFlow = tabState.processing val credentialsState: StateFlow = credentialsStateManager.credentialsState @@ -97,9 +99,6 @@ class TabViewModel @Inject constructor( updateDiffEntry() } - private val _repositoryState = MutableStateFlow(RepositoryState.SAFE) - val repositoryState: StateFlow = _repositoryState - private val _blameState = MutableStateFlow(BlameState.None) val blameState: StateFlow = _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)) }