Fixed rebase interactive check being executed when changing between tabs even if not rebasing

This commit is contained in:
Abdelilah El Aissaoui 2023-08-28 17:58:37 +02:00
parent 804b83769f
commit c78d7f1c3d
No known key found for this signature in database
GPG Key ID: 7587FC860F594869
13 changed files with 218 additions and 128 deletions

View File

@ -20,6 +20,7 @@ object AppIcons {
const val DESCRIPTION = "description.svg" const val DESCRIPTION = "description.svg"
const val DONE = "done.svg" const val DONE = "done.svg"
const val DOWNLOAD = "download.svg" const val DOWNLOAD = "download.svg"
const val DRAG = "drag.svg"
const val DROPDOWN = "dropdown.svg" const val DROPDOWN = "dropdown.svg"
const val ERROR = "error.svg" const val ERROR = "error.svg"
const val EXPAND_MORE = "expand_more.svg" const val EXPAND_MORE = "expand_more.svg"

View File

@ -250,6 +250,17 @@ class TabState @Inject constructor(
newSelectedItem(SelectedItem.None) 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( fun newSelectedRef(objectId: ObjectId?) = runOperation(
refreshType = RefreshType.NONE, refreshType = RefreshType.NONE,
) { git -> ) { git ->

View File

@ -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<ObjectId?> = 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
}
}
}

View File

@ -3,51 +3,20 @@ package com.jetpackduba.gitnuro.git.rebase
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git 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.lib.RebaseTodoLine
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject import javax.inject.Inject
class GetRebaseLinesFullMessageUseCase @Inject constructor() { class GetRebaseLinesFullMessageUseCase @Inject constructor(
private val getCommitFromRebaseLineUseCase: GetCommitFromRebaseLineUseCase,
) {
suspend operator fun invoke( suspend operator fun invoke(
git: Git, git: Git,
rebaseTodoLines: List<RebaseTodoLine>, rebaseTodoLines: List<RebaseTodoLine>,
): Map<String, String> = withContext(Dispatchers.IO) { ): Map<String, String> = withContext(Dispatchers.IO) {
return@withContext rebaseTodoLines.associate { line -> return@withContext rebaseTodoLines.associate { line ->
val commit = getCommitFromLine(git, line) val commit = getCommitFromRebaseLineUseCase(git, line.commit, line.shortMessage)
val fullMessage = commit?.fullMessage ?: line.shortMessage val fullMessage = commit?.fullMessage ?: line.shortMessage
line.commit.name() to fullMessage line.commit.name() to fullMessage
} }
} }
private fun getCommitFromLine(git: Git, line: RebaseTodoLine): RevCommit? {
val resolvedList: List<ObjectId?> = 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
}
}
} }

View File

@ -1,32 +1,40 @@
package com.jetpackduba.gitnuro.ui package com.jetpackduba.gitnuro.ui
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.graphics.Color
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.jetpackduba.gitnuro.AppIcons import com.jetpackduba.gitnuro.AppIcons
import com.jetpackduba.gitnuro.ui.components.AdjustableOutlinedTextField import com.jetpackduba.gitnuro.theme.backgroundSelected
import com.jetpackduba.gitnuro.ui.components.PrimaryButton import com.jetpackduba.gitnuro.ui.components.*
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.RebaseAction
import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewState
import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewModel import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewModel
import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewState
import com.jetpackduba.gitnuro.viewmodels.RebaseLine import com.jetpackduba.gitnuro.viewmodels.RebaseLine
@Composable @Composable
fun RebaseInteractive( fun RebaseInteractive(
rebaseInteractiveViewModel: RebaseInteractiveViewModel = gitnuroDynamicViewModel(), rebaseInteractiveViewModel: RebaseInteractiveViewModel = gitnuroViewModel(),
) { ) {
val rebaseState = rebaseInteractiveViewModel.rebaseState.collectAsState() val rebaseState = rebaseInteractiveViewModel.rebaseState.collectAsState()
val rebaseStateValue = rebaseState.value val rebaseStateValue = rebaseState.value
val selectedItem by rebaseInteractiveViewModel.selectedItem.collectAsState()
LaunchedEffect(rebaseInteractiveViewModel) {
rebaseInteractiveViewModel.loadRebaseInteractiveData()
}
Box( Box(
modifier = Modifier modifier = Modifier
@ -39,6 +47,10 @@ fun RebaseInteractive(
RebaseStateLoaded( RebaseStateLoaded(
rebaseInteractiveViewModel, rebaseInteractiveViewModel,
rebaseStateValue, rebaseStateValue,
selectedItem,
onFocusLine = {
rebaseInteractiveViewModel.selectLine(it)
},
onCancel = { onCancel = {
rebaseInteractiveViewModel.cancel() rebaseInteractiveViewModel.cancel()
}, },
@ -56,6 +68,8 @@ fun RebaseInteractive(
fun RebaseStateLoaded( fun RebaseStateLoaded(
rebaseInteractiveViewModel: RebaseInteractiveViewModel, rebaseInteractiveViewModel: RebaseInteractiveViewModel,
rebaseState: RebaseInteractiveViewState.Loaded, rebaseState: RebaseInteractiveViewState.Loaded,
selectedItem: SelectedItem,
onFocusLine: (RebaseLine) -> Unit,
onCancel: () -> Unit, onCancel: () -> Unit,
) { ) {
val stepsList = rebaseState.stepsList val stepsList = rebaseState.stepsList
@ -75,7 +89,11 @@ fun RebaseStateLoaded(
RebaseCommit( RebaseCommit(
rebaseLine = rebaseTodoLine, rebaseLine = rebaseTodoLine,
message = rebaseState.messages[rebaseTodoLine.commit.name()], message = rebaseState.messages[rebaseTodoLine.commit.name()],
isSelected = selectedItem is SelectedItem.Commit && selectedItem.revCommit.id.startsWith(
rebaseTodoLine.commit
),
isFirst = stepsList.first() == rebaseTodoLine, isFirst = stepsList.first() == rebaseTodoLine,
onFocusLine = { onFocusLine(rebaseTodoLine) },
onActionChanged = { newAction -> onActionChanged = { newAction ->
rebaseInteractiveViewModel.onCommitActionChanged(rebaseTodoLine.commit, newAction) rebaseInteractiveViewModel.onCommitActionChanged(rebaseTodoLine.commit, newAction)
}, },
@ -114,11 +132,15 @@ fun RebaseStateLoaded(
fun RebaseCommit( fun RebaseCommit(
rebaseLine: RebaseLine, rebaseLine: RebaseLine,
isFirst: Boolean, isFirst: Boolean,
isSelected: Boolean,
message: String?, message: String?,
onFocusLine: () -> Unit,
onActionChanged: (RebaseAction) -> Unit, onActionChanged: (RebaseAction) -> Unit,
onMessageChanged: (String) -> Unit, onMessageChanged: (String) -> Unit,
) { ) {
val action = rebaseLine.rebaseAction val action = rebaseLine.rebaseAction
val focusRequester = remember { FocusRequester() }
var newMessage by remember(rebaseLine.commit.name(), action) { var newMessage by remember(rebaseLine.commit.name(), action) {
if (action == RebaseAction.REWORD) { if (action == RebaseAction.REWORD) {
mutableStateOf(message ?: rebaseLine.shortMessage) /* if reword, use the value from the map (if possible)*/ mutableStateOf(message ?: rebaseLine.shortMessage) /* if reword, use the value from the map (if possible)*/
@ -128,20 +150,45 @@ fun RebaseCommit(
Row( Row(
modifier = Modifier modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
.height(IntrinsicSize.Min) .height(IntrinsicSize.Min)
.fillMaxWidth() .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( ActionDropdown(
action, action,
isFirst = isFirst, isFirst = isFirst,
onActionDropDownClicked = onFocusLine,
onActionChanged = onActionChanged, onActionChanged = onActionChanged,
) )
AdjustableOutlinedTextField( AdjustableOutlinedTextField(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.heightIn(min = 40.dp), .heightIn(min = 40.dp)
.focusRequester(focusRequester),
enabled = action == RebaseAction.REWORD, enabled = action == RebaseAction.REWORD,
value = newMessage, value = newMessage,
onValueChange = { onValueChange = {
@ -163,12 +210,16 @@ fun RebaseCommit(
fun ActionDropdown( fun ActionDropdown(
action: RebaseAction, action: RebaseAction,
isFirst: Boolean, isFirst: Boolean,
onActionDropDownClicked: () -> Unit,
onActionChanged: (RebaseAction) -> Unit, onActionChanged: (RebaseAction) -> Unit,
) { ) {
var showDropDownMenu by remember { mutableStateOf(false) } var showDropDownMenu by remember { mutableStateOf(false) }
Box { Box {
TextButton( TextButton(
onClick = { showDropDownMenu = true }, onClick = {
showDropDownMenu = true
onActionDropDownClicked()
},
modifier = Modifier modifier = Modifier
.width(120.dp) .width(120.dp)
.height(40.dp) .height(40.dp)

View File

@ -14,10 +14,7 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.onKeyEvent 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.input.pointer.pointerHoverIcon
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.jetpackduba.gitnuro.AppConstants import com.jetpackduba.gitnuro.AppConstants
import com.jetpackduba.gitnuro.LocalTabScope 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.git.rebase.RebaseInteractiveState
import com.jetpackduba.gitnuro.keybindings.KeybindingOption import com.jetpackduba.gitnuro.keybindings.KeybindingOption
import com.jetpackduba.gitnuro.keybindings.matchesBinding 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.SecondaryButton
import com.jetpackduba.gitnuro.ui.components.gitnuroDynamicViewModel import com.jetpackduba.gitnuro.ui.components.gitnuroDynamicViewModel
import com.jetpackduba.gitnuro.ui.dialogs.* 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.HorizontalSplitPane
import org.jetbrains.compose.splitpane.SplitterScope import org.jetbrains.compose.splitpane.SplitterScope
import org.jetbrains.compose.splitpane.rememberSplitPaneState import org.jetbrains.compose.splitpane.rememberSplitPaneState
import java.awt.Cursor
@Composable @Composable
fun RepositoryOpenPage( fun RepositoryOpenPage(
@ -54,7 +49,6 @@ fun RepositoryOpenPage(
val blameState by tabViewModel.blameState.collectAsState() val blameState by tabViewModel.blameState.collectAsState()
val showHistory by tabViewModel.showHistory.collectAsState() val showHistory by tabViewModel.showHistory.collectAsState()
val showAuthorInfo by tabViewModel.showAuthorInfo.collectAsState() val showAuthorInfo by tabViewModel.showAuthorInfo.collectAsState()
val rebaseInteractiveState by tabViewModel.rebaseInteractiveState.collectAsState()
var showNewBranchDialog by remember { mutableStateOf(false) } var showNewBranchDialog by remember { mutableStateOf(false) }
var showStashWithMessageDialog by remember { mutableStateOf(false) } var showStashWithMessageDialog by remember { mutableStateOf(false) }
@ -131,9 +125,6 @@ fun RepositoryOpenPage(
} }
} }
) { ) {
if (rebaseInteractiveState == RebaseInteractiveState.AwaitingInteraction) {
RebaseInteractive()
} else {
val currentTabInformation = LocalTabScope.current val currentTabInformation = LocalTabScope.current
Column(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.weight(1f)) {
Menu( Menu(
@ -166,7 +157,6 @@ fun RepositoryOpenPage(
} }
} }
} }
}
Spacer( Spacer(
modifier = Modifier modifier = Modifier
@ -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 @Composable
private fun BottomInfoBar(tabViewModel: TabViewModel) { private fun BottomInfoBar(tabViewModel: TabViewModel) {
val userInfo by tabViewModel.authorInfoSimple.collectAsState() val userInfo by tabViewModel.authorInfoSimple.collectAsState()
@ -288,10 +253,19 @@ fun MainContentView(
repositoryState: RepositoryState, repositoryState: RepositoryState,
blameState: BlameState, blameState: BlameState,
) { ) {
val rebaseInteractiveState by tabViewModel.rebaseInteractiveState.collectAsState()
println("Rebase interactive state is $rebaseInteractiveState")
HorizontalSplitPane( HorizontalSplitPane(
splitPaneState = rememberSplitPaneState(initialPositionPercentage = 0.20f) splitPaneState = rememberSplitPaneState(initialPositionPercentage = 0.20f)
) { ) {
first(minSize = 180.dp) { val size = if (rebaseInteractiveState == RebaseInteractiveState.AwaitingInteraction)
1.dp
else
180.dp
first(minSize = size) {
SidePanel() SidePanel()
} }
@ -308,7 +282,9 @@ fun MainContentView(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
) { ) {
if (blameState is BlameState.Loaded && !blameState.isMinimized) { if (rebaseInteractiveState == RebaseInteractiveState.AwaitingInteraction) {
RebaseInteractive()
} else if (blameState is BlameState.Loaded && !blameState.isMinimized) {
Blame( Blame(
filePath = blameState.filePath, filePath = blameState.filePath,
blameResult = blameState.blameResult, blameResult = blameState.blameResult,
@ -391,6 +367,7 @@ fun MainContentView(
onHistoryFile = { tabViewModel.fileHistory(it) } onHistoryFile = { tabViewModel.fileHistory(it) }
) )
} }
is SelectedItem.CommitBasedItem -> { is SelectedItem.CommitBasedItem -> {
CommitChanges( CommitChanges(
selectedItem = selectedItem, selectedItem = selectedItem,
@ -403,6 +380,7 @@ fun MainContentView(
onHistory = { tabViewModel.fileHistory(it) }, onHistory = { tabViewModel.fileHistory(it) },
) )
} }
SelectedItem.None -> {} SelectedItem.None -> {}
} }
} }
@ -425,7 +403,7 @@ fun SplitterScope.repositorySplitter() {
Box( Box(
Modifier Modifier
.markAsHandle() .markAsHandle()
.pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR))) .pointerHoverIcon(resizePointerIconEast)
.background(Color.Transparent) .background(Color.Transparent)
.width(8.dp) .width(8.dp)
.fillMaxHeight() .fillMaxHeight()

View File

@ -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))

View File

@ -50,6 +50,7 @@ import com.jetpackduba.gitnuro.ui.dialogs.NewBranchDialog
import com.jetpackduba.gitnuro.ui.dialogs.NewTagDialog import com.jetpackduba.gitnuro.ui.dialogs.NewTagDialog
import com.jetpackduba.gitnuro.ui.dialogs.ResetBranchDialog import com.jetpackduba.gitnuro.ui.dialogs.ResetBranchDialog
import com.jetpackduba.gitnuro.ui.dialogs.SetDefaultUpstreamBranchDialog import com.jetpackduba.gitnuro.ui.dialogs.SetDefaultUpstreamBranchDialog
import com.jetpackduba.gitnuro.ui.resizePointerIconEast
import com.jetpackduba.gitnuro.viewmodels.LogSearch import com.jetpackduba.gitnuro.viewmodels.LogSearch
import com.jetpackduba.gitnuro.viewmodels.LogStatus import com.jetpackduba.gitnuro.viewmodels.LogStatus
import com.jetpackduba.gitnuro.viewmodels.LogViewModel import com.jetpackduba.gitnuro.viewmodels.LogViewModel
@ -917,7 +918,7 @@ fun DividerLog(modifier: Modifier, graphWidth: Dp) {
.padding(start = graphWidth) .padding(start = graphWidth)
.width(DIVIDER_WIDTH.dp) .width(DIVIDER_WIDTH.dp)
.then(modifier) .then(modifier)
.pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR))) .pointerHoverIcon(resizePointerIconEast)
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier

View File

@ -333,7 +333,7 @@ class LogViewModel @Inject constructor(
fun selectLogLine(commit: GraphNode) = tabState.runOperation( fun selectLogLine(commit: GraphNode) = tabState.runOperation(
refreshType = RefreshType.NONE, refreshType = RefreshType.NONE,
) { ) {
tabState.newSelectedItem(SelectedItem.Commit(commit)) tabState.newSelectedCommit(commit)
val searchValue = _logSearchFilterResults.value val searchValue = _logSearchFilterResults.value
if (searchValue is LogSearch.SearchResults) { if (searchValue is LogSearch.SearchResults) {

View File

@ -5,13 +5,13 @@ import com.jetpackduba.gitnuro.exceptions.RebaseCancelledException
import com.jetpackduba.gitnuro.git.RefreshType import com.jetpackduba.gitnuro.git.RefreshType
import com.jetpackduba.gitnuro.git.TabState import com.jetpackduba.gitnuro.git.TabState
import com.jetpackduba.gitnuro.git.rebase.* import com.jetpackduba.gitnuro.git.rebase.*
import com.jetpackduba.gitnuro.git.repository.GetRepositoryStateUseCase
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler
import org.eclipse.jgit.lib.AbbreviatedObjectId import org.eclipse.jgit.lib.AbbreviatedObjectId
import org.eclipse.jgit.lib.RebaseTodoLine import org.eclipse.jgit.lib.RebaseTodoLine
import org.eclipse.jgit.lib.RebaseTodoLine.Action import org.eclipse.jgit.lib.RebaseTodoLine.Action
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject import javax.inject.Inject
private const val TAG = "RebaseInteractiveViewMo" private const val TAG = "RebaseInteractiveViewMo"
@ -19,18 +19,17 @@ private const val TAG = "RebaseInteractiveViewMo"
class RebaseInteractiveViewModel @Inject constructor( class RebaseInteractiveViewModel @Inject constructor(
private val tabState: TabState, private val tabState: TabState,
private val getRebaseLinesFullMessageUseCase: GetRebaseLinesFullMessageUseCase, private val getRebaseLinesFullMessageUseCase: GetRebaseLinesFullMessageUseCase,
private val getCommitFromRebaseLineUseCase: GetCommitFromRebaseLineUseCase,
private val getRebaseInteractiveTodoLinesUseCase: GetRebaseInteractiveTodoLinesUseCase, private val getRebaseInteractiveTodoLinesUseCase: GetRebaseInteractiveTodoLinesUseCase,
private val abortRebaseUseCase: AbortRebaseUseCase, private val abortRebaseUseCase: AbortRebaseUseCase,
private val resumeRebaseInteractiveUseCase: ResumeRebaseInteractiveUseCase, private val resumeRebaseInteractiveUseCase: ResumeRebaseInteractiveUseCase,
private val getRepositoryStateUseCase: GetRepositoryStateUseCase,
) { ) {
private lateinit var commit: RevCommit
private val _rebaseState = MutableStateFlow<RebaseInteractiveViewState>(RebaseInteractiveViewState.Loading) private val _rebaseState = MutableStateFlow<RebaseInteractiveViewState>(RebaseInteractiveViewState.Loading)
val rebaseState: StateFlow<RebaseInteractiveViewState> = _rebaseState val rebaseState: StateFlow<RebaseInteractiveViewState> = _rebaseState
var rewordSteps = ArrayDeque<RebaseLine>()
init { val selectedItem = tabState.selectedItem
loadRebaseInteractiveData() var rewordSteps = ArrayDeque<RebaseLine>()
}
private var interactiveHandlerContinue = object : InteractiveHandler { private var interactiveHandlerContinue = object : InteractiveHandler {
override fun prepareSteps(steps: MutableList<RebaseTodoLine>) { override fun prepareSteps(steps: MutableList<RebaseTodoLine>) {
@ -64,9 +63,16 @@ class RebaseInteractiveViewModel @Inject constructor(
} }
} }
private fun loadRebaseInteractiveData() = tabState.safeProcessing( fun loadRebaseInteractiveData() = tabState.safeProcessing(
refreshType = RefreshType.NONE, refreshType = RefreshType.NONE,
) { git -> ) { git ->
val state = getRepositoryStateUseCase(git)
if (!state.isRebasing) {
_rebaseState.value = RebaseInteractiveViewState.Loading
return@safeProcessing
}
try { try {
val lines = getRebaseInteractiveTodoLinesUseCase(git) val lines = getRebaseInteractiveTodoLinesUseCase(git)
val messages = getRebaseLinesFullMessageUseCase(tabState.git, lines) val messages = getRebaseLinesFullMessageUseCase(tabState.git, lines)
@ -78,7 +84,17 @@ class RebaseInteractiveViewModel @Inject constructor(
) )
} }
val isSameRebase = isSameRebase(rebaseLines, _rebaseState.value)
if (!isSameRebase) {
_rebaseState.value = RebaseInteractiveViewState.Loaded(rebaseLines, messages) _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) { } catch (ex: Exception) {
if (ex is RebaseCancelledException) { if (ex is RebaseCancelledException) {
@ -90,10 +106,25 @@ class RebaseInteractiveViewModel @Inject constructor(
} }
} }
private fun isSameRebase(rebaseLines: List<RebaseLine>, 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( fun continueRebaseInteractive() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
) { git -> ) { git ->
resumeRebaseInteractiveUseCase(git, interactiveHandlerContinue) resumeRebaseInteractiveUseCase(git, interactiveHandlerContinue)
_rebaseState.value = RebaseInteractiveViewState.Loading
} }
fun onCommitMessageChanged(commit: AbbreviatedObjectId, message: String) { fun onCommitMessageChanged(commit: AbbreviatedObjectId, message: String) {
@ -139,6 +170,12 @@ class RebaseInteractiveViewModel @Inject constructor(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
) { git -> ) { git ->
abortRebaseUseCase(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)
} }
} }

View File

@ -397,13 +397,7 @@ class TabViewModel @Inject constructor(
fun selectCommit(commit: RevCommit) = tabState.runOperation( fun selectCommit(commit: RevCommit) = tabState.runOperation(
refreshType = RefreshType.NONE, refreshType = RefreshType.NONE,
) { ) {
tabState.newSelectedItem(SelectedItem.Commit(commit)) tabState.newSelectedCommit(commit)
}
fun selectUncommitedChanges() = tabState.runOperation(
refreshType = RefreshType.NONE,
) {
tabState.newSelectedItem(SelectedItem.UncommitedChanges, true)
} }
fun fileHistory(filePath: String) { fun fileHistory(filePath: String) {

View File

@ -16,9 +16,9 @@ class TabViewModelsHolder @Inject constructor(
cloneViewModel: CloneViewModel, cloneViewModel: CloneViewModel,
settingsViewModel: SettingsViewModel, settingsViewModel: SettingsViewModel,
sidePanelViewModel: SidePanelViewModel, sidePanelViewModel: SidePanelViewModel,
rebaseInteractiveViewModel: RebaseInteractiveViewModel,
// Dynamic VM // Dynamic VM
private val diffViewModelProvider: Provider<DiffViewModel>, private val diffViewModelProvider: Provider<DiffViewModel>,
private val rebaseInteractiveViewModelProvider: Provider<RebaseInteractiveViewModel>,
private val historyViewModelProvider: Provider<HistoryViewModel>, private val historyViewModelProvider: Provider<HistoryViewModel>,
private val authorViewModelProvider: Provider<AuthorViewModel>, private val authorViewModelProvider: Provider<AuthorViewModel>,
private val changeDefaultUpstreamBranchViewModelProvider: Provider<ChangeDefaultUpstreamBranchViewModel>, private val changeDefaultUpstreamBranchViewModelProvider: Provider<ChangeDefaultUpstreamBranchViewModel>,
@ -33,13 +33,13 @@ class TabViewModelsHolder @Inject constructor(
commitChangesViewModel::class to commitChangesViewModel, commitChangesViewModel::class to commitChangesViewModel,
cloneViewModel::class to cloneViewModel, cloneViewModel::class to cloneViewModel,
settingsViewModel::class to settingsViewModel, settingsViewModel::class to settingsViewModel,
rebaseInteractiveViewModel::class to rebaseInteractiveViewModel,
) )
// TODO Call this when required // TODO Call this when required
fun dynamicViewModel(type: KClass<*>): Any { fun dynamicViewModel(type: KClass<*>): Any {
return when(type) { return when(type) {
DiffViewModel::class -> diffViewModelProvider.get() DiffViewModel::class -> diffViewModelProvider.get()
RebaseInteractiveViewModel::class -> rebaseInteractiveViewModelProvider.get()
HistoryViewModel::class -> historyViewModelProvider.get() HistoryViewModel::class -> historyViewModelProvider.get()
AuthorViewModel::class -> authorViewModelProvider.get() AuthorViewModel::class -> authorViewModelProvider.get()
ChangeDefaultUpstreamBranchViewModel::class -> changeDefaultUpstreamBranchViewModelProvider.get() ChangeDefaultUpstreamBranchViewModel::class -> changeDefaultUpstreamBranchViewModelProvider.get()

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M160-390v-60h640v60H160Zm0-120v-60h640v60H160Z"/></svg>

After

Width:  |  Height:  |  Size: 152 B