From c78d7f1c3de695aeac1151f2dc44779e900797c8 Mon Sep 17 00:00:00 2001 From: Abdelilah El Aissaoui Date: Mon, 28 Aug 2023 17:58:37 +0200 Subject: [PATCH] Fixed rebase interactive check being executed when changing between tabs even if not rebasing --- .../kotlin/com/jetpackduba/gitnuro/Icons.kt | 1 + .../com/jetpackduba/gitnuro/git/TabState.kt | 11 ++ .../rebase/GetCommitFromRebaseLineUseCase.kt | 40 +++++++ .../GetRebaseLinesFullMessageUseCase.kt | 39 +------ .../gitnuro/ui/RebaseInteractive.kt | 69 +++++++++-- .../jetpackduba/gitnuro/ui/RepositoryOpen.kt | 108 +++++++----------- .../gitnuro/ui/ResizePointerIcon.kt | 7 ++ .../com/jetpackduba/gitnuro/ui/log/Log.kt | 3 +- .../gitnuro/viewmodels/LogViewModel.kt | 2 +- .../viewmodels/RebaseInteractiveViewModel.kt | 53 +++++++-- .../gitnuro/viewmodels/TabViewModel.kt | 8 +- .../gitnuro/viewmodels/TabViewModelsHolder.kt | 4 +- src/main/resources/drag.svg | 1 + 13 files changed, 218 insertions(+), 128 deletions(-) create mode 100644 src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetCommitFromRebaseLineUseCase.kt create mode 100644 src/main/kotlin/com/jetpackduba/gitnuro/ui/ResizePointerIcon.kt create mode 100644 src/main/resources/drag.svg diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/Icons.kt b/src/main/kotlin/com/jetpackduba/gitnuro/Icons.kt index 466eef3..6ff652f 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/Icons.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/Icons.kt @@ -20,6 +20,7 @@ object AppIcons { const val DESCRIPTION = "description.svg" const val DONE = "done.svg" const val DOWNLOAD = "download.svg" + const val DRAG = "drag.svg" const val DROPDOWN = "dropdown.svg" const val ERROR = "error.svg" const val EXPAND_MORE = "expand_more.svg" diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/TabState.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/TabState.kt index 823e89b..84bd76e 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/TabState.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/TabState.kt @@ -250,6 +250,17 @@ class TabState @Inject constructor( newSelectedItem(SelectedItem.None) } + fun newSelectedCommit(revCommit: RevCommit?) = runOperation( + refreshType = RefreshType.NONE, + ) { _ -> + if (revCommit == null) { + newSelectedItem(SelectedItem.None) + } else { + val newSelectedItem = SelectedItem.Commit(revCommit) + newSelectedItem(newSelectedItem) + } + } + fun newSelectedRef(objectId: ObjectId?) = runOperation( refreshType = RefreshType.NONE, ) { git -> diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetCommitFromRebaseLineUseCase.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetCommitFromRebaseLineUseCase.kt new file mode 100644 index 0000000..007e4cf --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetCommitFromRebaseLineUseCase.kt @@ -0,0 +1,40 @@ +package com.jetpackduba.gitnuro.git.rebase + +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.errors.AmbiguousObjectException +import org.eclipse.jgit.lib.AbbreviatedObjectId +import org.eclipse.jgit.lib.ObjectId +import org.eclipse.jgit.revwalk.RevCommit +import javax.inject.Inject + +class GetCommitFromRebaseLineUseCase @Inject constructor() { + operator fun invoke(git: Git, commit: AbbreviatedObjectId, shortMessage: String): RevCommit? { + val resolvedList: List = try { + listOf(git.repository.resolve("${commit.name()}^{commit}")) + } catch (ex: AmbiguousObjectException) { + ex.candidates.toList() + } + + if (resolvedList.isEmpty()) { + println("Commit search failed for line $commit - $shortMessage") + return null + } else if (resolvedList.count() == 1) { + val resolvedId = resolvedList.firstOrNull() + + return if (resolvedId == null) + null + else + git.repository.parseCommit(resolvedId) + } else { + println("Multiple matching commits for line $commit - $shortMessage") + for (candidateId in resolvedList) { + val candidateCommit = git.repository.parseCommit(candidateId) + if (shortMessage == candidateCommit.shortMessage) + return candidateCommit + } + + println("None of the matching commits has a matching short message") + return null + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetRebaseLinesFullMessageUseCase.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetRebaseLinesFullMessageUseCase.kt index d6595b6..505146c 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetRebaseLinesFullMessageUseCase.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/rebase/GetRebaseLinesFullMessageUseCase.kt @@ -3,51 +3,20 @@ package com.jetpackduba.gitnuro.git.rebase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.eclipse.jgit.api.Git -import org.eclipse.jgit.errors.AmbiguousObjectException -import org.eclipse.jgit.lib.ObjectId import org.eclipse.jgit.lib.RebaseTodoLine -import org.eclipse.jgit.revwalk.RevCommit import javax.inject.Inject -class GetRebaseLinesFullMessageUseCase @Inject constructor() { +class GetRebaseLinesFullMessageUseCase @Inject constructor( + private val getCommitFromRebaseLineUseCase: GetCommitFromRebaseLineUseCase, +) { suspend operator fun invoke( git: Git, rebaseTodoLines: List, ): Map = withContext(Dispatchers.IO) { return@withContext rebaseTodoLines.associate { line -> - val commit = getCommitFromLine(git, line) + val commit = getCommitFromRebaseLineUseCase(git, line.commit, line.shortMessage) val fullMessage = commit?.fullMessage ?: line.shortMessage line.commit.name() to fullMessage } } - - private fun getCommitFromLine(git: Git, line: RebaseTodoLine): RevCommit? { - val resolvedList: List = try { - listOf(git.repository.resolve("${line.commit.name()}^{commit}")) - } catch (ex: AmbiguousObjectException) { - ex.candidates.toList() - } - - if (resolvedList.isEmpty()) { - println("Commit search failed for line ${line.commit} - ${line.shortMessage}") - return null - } else if (resolvedList.count() == 1) { - val resolvedId = resolvedList.firstOrNull() - - return if (resolvedId == null) - null - else - git.repository.parseCommit(resolvedId) - } else { - println("Multiple matching commits for line ${line.commit} - ${line.shortMessage}") - for (candidateId in resolvedList) { - val candidateCommit = git.repository.parseCommit(candidateId) - if (line.shortMessage == candidateCommit.shortMessage) - return candidateCommit - } - - println("None of the matching commits has a matching short message") - return null - } - } } \ 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 e1a32d7..6d26b74 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/RebaseInteractive.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/RebaseInteractive.kt @@ -1,32 +1,40 @@ package com.jetpackduba.gitnuro.ui import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.items import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusEvent import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp 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.theme.backgroundSelected +import com.jetpackduba.gitnuro.ui.components.* import com.jetpackduba.gitnuro.viewmodels.RebaseAction -import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewState import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewModel +import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewState import com.jetpackduba.gitnuro.viewmodels.RebaseLine @Composable fun RebaseInteractive( - rebaseInteractiveViewModel: RebaseInteractiveViewModel = gitnuroDynamicViewModel(), + rebaseInteractiveViewModel: RebaseInteractiveViewModel = gitnuroViewModel(), ) { val rebaseState = rebaseInteractiveViewModel.rebaseState.collectAsState() val rebaseStateValue = rebaseState.value + val selectedItem by rebaseInteractiveViewModel.selectedItem.collectAsState() + + LaunchedEffect(rebaseInteractiveViewModel) { + rebaseInteractiveViewModel.loadRebaseInteractiveData() + } Box( modifier = Modifier @@ -39,6 +47,10 @@ fun RebaseInteractive( RebaseStateLoaded( rebaseInteractiveViewModel, rebaseStateValue, + selectedItem, + onFocusLine = { + rebaseInteractiveViewModel.selectLine(it) + }, onCancel = { rebaseInteractiveViewModel.cancel() }, @@ -56,6 +68,8 @@ fun RebaseInteractive( fun RebaseStateLoaded( rebaseInteractiveViewModel: RebaseInteractiveViewModel, rebaseState: RebaseInteractiveViewState.Loaded, + selectedItem: SelectedItem, + onFocusLine: (RebaseLine) -> Unit, onCancel: () -> Unit, ) { val stepsList = rebaseState.stepsList @@ -75,7 +89,11 @@ fun RebaseStateLoaded( RebaseCommit( rebaseLine = rebaseTodoLine, message = rebaseState.messages[rebaseTodoLine.commit.name()], + isSelected = selectedItem is SelectedItem.Commit && selectedItem.revCommit.id.startsWith( + rebaseTodoLine.commit + ), isFirst = stepsList.first() == rebaseTodoLine, + onFocusLine = { onFocusLine(rebaseTodoLine) }, onActionChanged = { newAction -> rebaseInteractiveViewModel.onCommitActionChanged(rebaseTodoLine.commit, newAction) }, @@ -114,11 +132,15 @@ fun RebaseStateLoaded( fun RebaseCommit( rebaseLine: RebaseLine, isFirst: Boolean, + isSelected: Boolean, message: String?, + onFocusLine: () -> Unit, onActionChanged: (RebaseAction) -> Unit, onMessageChanged: (String) -> Unit, ) { val action = rebaseLine.rebaseAction + val focusRequester = remember { FocusRequester() } + var newMessage by remember(rebaseLine.commit.name(), action) { if (action == RebaseAction.REWORD) { mutableStateOf(message ?: rebaseLine.shortMessage) /* if reword, use the value from the map (if possible)*/ @@ -128,20 +150,45 @@ fun RebaseCommit( Row( modifier = Modifier - .padding(horizontal = 16.dp, vertical = 8.dp) .height(IntrinsicSize.Min) .fillMaxWidth() + .onFocusEvent { + if (it.hasFocus) { + onFocusLine() + focusRequester.requestFocus() + } + } + .clickable { onFocusLine() } + .run { + if (isSelected) { + background(MaterialTheme.colors.backgroundSelected) + } else { + this + } + } + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, ) { + Icon( + painterResource(AppIcons.DRAG), + contentDescription = "Drag line", + modifier = Modifier + .size(24.dp) + .pointerHoverIcon(resizePointerIconNorth), + ) + ActionDropdown( action, isFirst = isFirst, + onActionDropDownClicked = onFocusLine, onActionChanged = onActionChanged, ) AdjustableOutlinedTextField( modifier = Modifier .weight(1f) - .heightIn(min = 40.dp), + .heightIn(min = 40.dp) + .focusRequester(focusRequester), enabled = action == RebaseAction.REWORD, value = newMessage, onValueChange = { @@ -163,12 +210,16 @@ fun RebaseCommit( fun ActionDropdown( action: RebaseAction, isFirst: Boolean, + onActionDropDownClicked: () -> Unit, onActionChanged: (RebaseAction) -> Unit, ) { var showDropDownMenu by remember { mutableStateOf(false) } Box { TextButton( - onClick = { showDropDownMenu = true }, + onClick = { + showDropDownMenu = true + onActionDropDownClicked() + }, modifier = Modifier .width(120.dp) .height(40.dp) diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt index 258dc56..a2bc8b0 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt @@ -14,10 +14,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.key.onKeyEvent -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.dp import com.jetpackduba.gitnuro.AppConstants import com.jetpackduba.gitnuro.LocalTabScope @@ -26,7 +23,6 @@ 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 import com.jetpackduba.gitnuro.ui.components.SecondaryButton import com.jetpackduba.gitnuro.ui.components.gitnuroDynamicViewModel import com.jetpackduba.gitnuro.ui.dialogs.* @@ -40,7 +36,6 @@ import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi import org.jetbrains.compose.splitpane.HorizontalSplitPane import org.jetbrains.compose.splitpane.SplitterScope import org.jetbrains.compose.splitpane.rememberSplitPaneState -import java.awt.Cursor @Composable fun RepositoryOpenPage( @@ -54,7 +49,6 @@ 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) } @@ -131,39 +125,35 @@ fun RepositoryOpenPage( } } ) { - if (rebaseInteractiveState == RebaseInteractiveState.AwaitingInteraction) { - RebaseInteractive() - } else { - val currentTabInformation = LocalTabScope.current - Column(modifier = Modifier.weight(1f)) { - Menu( - modifier = Modifier - .padding( - vertical = 4.dp - ) - .fillMaxWidth(), - onCreateBranch = { showNewBranchDialog = true }, - onStashWithMessage = { showStashWithMessageDialog = true }, - onOpenAnotherRepository = { - val repo = tabViewModel.openDirectoryPicker() + val currentTabInformation = LocalTabScope.current + Column(modifier = Modifier.weight(1f)) { + Menu( + modifier = Modifier + .padding( + vertical = 4.dp + ) + .fillMaxWidth(), + onCreateBranch = { showNewBranchDialog = true }, + onStashWithMessage = { showStashWithMessageDialog = true }, + onOpenAnotherRepository = { + val repo = tabViewModel.openDirectoryPicker() - if (repo != null) { - tabViewModel.openAnotherRepository(repo, currentTabInformation) - } - }, - onQuickActions = { showQuickActionsDialog = true }, - onShowSettingsDialog = onShowSettingsDialog - ) + if (repo != null) { + tabViewModel.openAnotherRepository(repo, currentTabInformation) + } + }, + onQuickActions = { showQuickActionsDialog = true }, + onShowSettingsDialog = onShowSettingsDialog + ) - RepoContent( - tabViewModel = tabViewModel, - diffSelected = diffSelected, - selectedItem = selectedItem, - repositoryState = repositoryState, - blameState = blameState, - showHistory = showHistory, - ) - } + RepoContent( + tabViewModel = tabViewModel, + diffSelected = diffSelected, + selectedItem = selectedItem, + repositoryState = repositoryState, + blameState = blameState, + showHistory = showHistory, + ) } } } @@ -179,31 +169,6 @@ fun RepositoryOpenPage( } } -@Composable -fun RebaseInteractiveStartedExternally( - onCancelRebaseInteractive: () -> Unit, -) { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text( - "Rebase interactive started externally or Gitnuro (or this repository's tab)\nhas been restarted during the rebase.", - textAlign = TextAlign.Center, - fontWeight = FontWeight.Medium, - style = MaterialTheme.typography.body1, - ) - PrimaryButton( - modifier = Modifier.padding(top = 8.dp), - text = "Abort rebase interactive", - onClick = onCancelRebaseInteractive, - backgroundColor = MaterialTheme.colors.error, - textColor = MaterialTheme.colors.onError, - ) - } -} - @Composable private fun BottomInfoBar(tabViewModel: TabViewModel) { val userInfo by tabViewModel.authorInfoSimple.collectAsState() @@ -288,10 +253,19 @@ fun MainContentView( repositoryState: RepositoryState, blameState: BlameState, ) { + val rebaseInteractiveState by tabViewModel.rebaseInteractiveState.collectAsState() + + println("Rebase interactive state is $rebaseInteractiveState") + HorizontalSplitPane( splitPaneState = rememberSplitPaneState(initialPositionPercentage = 0.20f) ) { - first(minSize = 180.dp) { + val size = if (rebaseInteractiveState == RebaseInteractiveState.AwaitingInteraction) + 1.dp + else + 180.dp + + first(minSize = size) { SidePanel() } @@ -308,7 +282,9 @@ fun MainContentView( modifier = Modifier .fillMaxSize() ) { - if (blameState is BlameState.Loaded && !blameState.isMinimized) { + if (rebaseInteractiveState == RebaseInteractiveState.AwaitingInteraction) { + RebaseInteractive() + } else if (blameState is BlameState.Loaded && !blameState.isMinimized) { Blame( filePath = blameState.filePath, blameResult = blameState.blameResult, @@ -391,6 +367,7 @@ fun MainContentView( onHistoryFile = { tabViewModel.fileHistory(it) } ) } + is SelectedItem.CommitBasedItem -> { CommitChanges( selectedItem = selectedItem, @@ -403,6 +380,7 @@ fun MainContentView( onHistory = { tabViewModel.fileHistory(it) }, ) } + SelectedItem.None -> {} } } @@ -425,7 +403,7 @@ fun SplitterScope.repositorySplitter() { Box( Modifier .markAsHandle() - .pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR))) + .pointerHoverIcon(resizePointerIconEast) .background(Color.Transparent) .width(8.dp) .fillMaxHeight() diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/ResizePointerIcon.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/ResizePointerIcon.kt new file mode 100644 index 0000000..5fd80b0 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/ResizePointerIcon.kt @@ -0,0 +1,7 @@ +package com.jetpackduba.gitnuro.ui + +import androidx.compose.ui.input.pointer.PointerIcon +import java.awt.Cursor + +val resizePointerIconEast = PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR)) +val resizePointerIconNorth = PointerIcon(Cursor(Cursor.N_RESIZE_CURSOR)) \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt index 0747995..da82e03 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt @@ -50,6 +50,7 @@ import com.jetpackduba.gitnuro.ui.dialogs.NewBranchDialog import com.jetpackduba.gitnuro.ui.dialogs.NewTagDialog import com.jetpackduba.gitnuro.ui.dialogs.ResetBranchDialog import com.jetpackduba.gitnuro.ui.dialogs.SetDefaultUpstreamBranchDialog +import com.jetpackduba.gitnuro.ui.resizePointerIconEast import com.jetpackduba.gitnuro.viewmodels.LogSearch import com.jetpackduba.gitnuro.viewmodels.LogStatus import com.jetpackduba.gitnuro.viewmodels.LogViewModel @@ -917,7 +918,7 @@ fun DividerLog(modifier: Modifier, graphWidth: Dp) { .padding(start = graphWidth) .width(DIVIDER_WIDTH.dp) .then(modifier) - .pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR))) + .pointerHoverIcon(resizePointerIconEast) ) { Box( modifier = Modifier diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt index d2c8a3c..398b90a 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt @@ -333,7 +333,7 @@ class LogViewModel @Inject constructor( fun selectLogLine(commit: GraphNode) = tabState.runOperation( refreshType = RefreshType.NONE, ) { - tabState.newSelectedItem(SelectedItem.Commit(commit)) + tabState.newSelectedCommit(commit) val searchValue = _logSearchFilterResults.value if (searchValue is LogSearch.SearchResults) { diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RebaseInteractiveViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RebaseInteractiveViewModel.kt index 7b3f0eb..2fb75d0 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RebaseInteractiveViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RebaseInteractiveViewModel.kt @@ -5,13 +5,13 @@ import com.jetpackduba.gitnuro.exceptions.RebaseCancelledException import com.jetpackduba.gitnuro.git.RefreshType import com.jetpackduba.gitnuro.git.TabState import com.jetpackduba.gitnuro.git.rebase.* +import com.jetpackduba.gitnuro.git.repository.GetRepositoryStateUseCase import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler import org.eclipse.jgit.lib.AbbreviatedObjectId import org.eclipse.jgit.lib.RebaseTodoLine import org.eclipse.jgit.lib.RebaseTodoLine.Action -import org.eclipse.jgit.revwalk.RevCommit import javax.inject.Inject private const val TAG = "RebaseInteractiveViewMo" @@ -19,18 +19,17 @@ private const val TAG = "RebaseInteractiveViewMo" class RebaseInteractiveViewModel @Inject constructor( private val tabState: TabState, private val getRebaseLinesFullMessageUseCase: GetRebaseLinesFullMessageUseCase, + private val getCommitFromRebaseLineUseCase: GetCommitFromRebaseLineUseCase, private val getRebaseInteractiveTodoLinesUseCase: GetRebaseInteractiveTodoLinesUseCase, private val abortRebaseUseCase: AbortRebaseUseCase, private val resumeRebaseInteractiveUseCase: ResumeRebaseInteractiveUseCase, + private val getRepositoryStateUseCase: GetRepositoryStateUseCase, ) { - private lateinit var commit: RevCommit private val _rebaseState = MutableStateFlow(RebaseInteractiveViewState.Loading) val rebaseState: StateFlow = _rebaseState - var rewordSteps = ArrayDeque() - init { - loadRebaseInteractiveData() - } + val selectedItem = tabState.selectedItem + var rewordSteps = ArrayDeque() private var interactiveHandlerContinue = object : InteractiveHandler { override fun prepareSteps(steps: MutableList) { @@ -64,9 +63,16 @@ class RebaseInteractiveViewModel @Inject constructor( } } - private fun loadRebaseInteractiveData() = tabState.safeProcessing( + fun loadRebaseInteractiveData() = tabState.safeProcessing( refreshType = RefreshType.NONE, ) { git -> + val state = getRepositoryStateUseCase(git) + + if (!state.isRebasing) { + _rebaseState.value = RebaseInteractiveViewState.Loading + return@safeProcessing + } + try { val lines = getRebaseInteractiveTodoLinesUseCase(git) val messages = getRebaseLinesFullMessageUseCase(tabState.git, lines) @@ -78,7 +84,17 @@ class RebaseInteractiveViewModel @Inject constructor( ) } - _rebaseState.value = RebaseInteractiveViewState.Loaded(rebaseLines, messages) + val isSameRebase = isSameRebase(rebaseLines, _rebaseState.value) + + if (!isSameRebase) { + _rebaseState.value = RebaseInteractiveViewState.Loaded(rebaseLines, messages) + val firstLine = rebaseLines.firstOrNull() + + if (firstLine != null) { + val fullCommit = getCommitFromRebaseLineUseCase(git, firstLine.commit, firstLine.shortMessage) + tabState.newSelectedCommit(fullCommit) + } + } } catch (ex: Exception) { if (ex is RebaseCancelledException) { @@ -90,10 +106,25 @@ class RebaseInteractiveViewModel @Inject constructor( } } + private fun isSameRebase(rebaseLines: List, state: RebaseInteractiveViewState): Boolean { + if (state is RebaseInteractiveViewState.Loaded) { + val stepsList = state.stepsList + + if (rebaseLines.count() != stepsList.count()) { + return false + } + + return rebaseLines.map { it.commit.name() } == stepsList.map { it.commit.name() } + } + + return false + } + fun continueRebaseInteractive() = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> resumeRebaseInteractiveUseCase(git, interactiveHandlerContinue) + _rebaseState.value = RebaseInteractiveViewState.Loading } fun onCommitMessageChanged(commit: AbbreviatedObjectId, message: String) { @@ -139,6 +170,12 @@ class RebaseInteractiveViewModel @Inject constructor( refreshType = RefreshType.ALL_DATA, ) { git -> abortRebaseUseCase(git) + _rebaseState.value = RebaseInteractiveViewState.Loading + } + + fun selectLine(line: RebaseLine) = tabState.safeProcessing(refreshType = RefreshType.NONE) { git -> + val fullCommit = getCommitFromRebaseLineUseCase(git, line.commit, line.shortMessage) + tabState.newSelectedCommit(fullCommit) } } diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModel.kt index 0adc661..be5a41e 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModel.kt @@ -397,13 +397,7 @@ class TabViewModel @Inject constructor( fun selectCommit(commit: RevCommit) = tabState.runOperation( refreshType = RefreshType.NONE, ) { - tabState.newSelectedItem(SelectedItem.Commit(commit)) - } - - fun selectUncommitedChanges() = tabState.runOperation( - refreshType = RefreshType.NONE, - ) { - tabState.newSelectedItem(SelectedItem.UncommitedChanges, true) + tabState.newSelectedCommit(commit) } fun fileHistory(filePath: String) { diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModelsHolder.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModelsHolder.kt index b313131..be0eb24 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModelsHolder.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModelsHolder.kt @@ -16,9 +16,9 @@ class TabViewModelsHolder @Inject constructor( cloneViewModel: CloneViewModel, settingsViewModel: SettingsViewModel, sidePanelViewModel: SidePanelViewModel, + rebaseInteractiveViewModel: RebaseInteractiveViewModel, // Dynamic VM private val diffViewModelProvider: Provider, - private val rebaseInteractiveViewModelProvider: Provider, private val historyViewModelProvider: Provider, private val authorViewModelProvider: Provider, private val changeDefaultUpstreamBranchViewModelProvider: Provider, @@ -33,13 +33,13 @@ class TabViewModelsHolder @Inject constructor( commitChangesViewModel::class to commitChangesViewModel, cloneViewModel::class to cloneViewModel, settingsViewModel::class to settingsViewModel, + rebaseInteractiveViewModel::class to rebaseInteractiveViewModel, ) // TODO Call this when required fun dynamicViewModel(type: KClass<*>): Any { return when(type) { DiffViewModel::class -> diffViewModelProvider.get() - RebaseInteractiveViewModel::class -> rebaseInteractiveViewModelProvider.get() HistoryViewModel::class -> historyViewModelProvider.get() AuthorViewModel::class -> authorViewModelProvider.get() ChangeDefaultUpstreamBranchViewModel::class -> changeDefaultUpstreamBranchViewModelProvider.get() diff --git a/src/main/resources/drag.svg b/src/main/resources/drag.svg new file mode 100644 index 0000000..f3f8364 --- /dev/null +++ b/src/main/resources/drag.svg @@ -0,0 +1 @@ + \ No newline at end of file