Added first version of interactive rebase
This commit is contained in:
parent
08d0323e48
commit
14eb5f8c9c
@ -5,8 +5,10 @@ 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.api.RebaseCommand
|
import org.eclipse.jgit.api.RebaseCommand
|
||||||
|
import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler
|
||||||
import org.eclipse.jgit.api.RebaseResult
|
import org.eclipse.jgit.api.RebaseResult
|
||||||
import org.eclipse.jgit.lib.Ref
|
import org.eclipse.jgit.lib.Ref
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RebaseManager @Inject constructor() {
|
class RebaseManager @Inject constructor() {
|
||||||
@ -39,4 +41,13 @@ class RebaseManager @Inject constructor() {
|
|||||||
.setOperation(RebaseCommand.Operation.SKIP)
|
.setOperation(RebaseCommand.Operation.SKIP)
|
||||||
.call()
|
.call()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun rebaseInteractive(git: Git, interactiveHandler: InteractiveHandler, commit: RevCommit) {
|
||||||
|
//TODO Check possible rebase errors by checking the result
|
||||||
|
git.rebase()
|
||||||
|
.runInteractively(interactiveHandler)
|
||||||
|
.setOperation(RebaseCommand.Operation.BEGIN)
|
||||||
|
.setUpstream(commit)
|
||||||
|
.call()
|
||||||
|
}
|
||||||
}
|
}
|
@ -4,14 +4,11 @@ import app.ErrorsManager
|
|||||||
import app.di.TabScope
|
import app.di.TabScope
|
||||||
import app.newErrorNow
|
import app.newErrorNow
|
||||||
import app.ui.SelectedItem
|
import app.ui.SelectedItem
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
@ -127,7 +124,15 @@ class TabState @Inject constructor(
|
|||||||
if (showError)
|
if (showError)
|
||||||
errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
|
errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
|
||||||
} finally {
|
} finally {
|
||||||
|
launch {
|
||||||
|
// Add a slight delay because sometimes the file watcher takes a few moments to notify a change in the
|
||||||
|
// filesystem, therefore notifying late and being operationRunning already false (which leads to a full
|
||||||
|
// refresh because there have been changes in the git dir). This can be easily triggered by interactive
|
||||||
|
// rebase.
|
||||||
|
delay(500)
|
||||||
operationRunning = false
|
operationRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (refreshType != RefreshType.NONE && (!hasProcessFailed || refreshEvenIfCrashes))
|
if (refreshType != RefreshType.NONE && (!hasProcessFailed || refreshEvenIfCrashes))
|
||||||
_refreshData.emit(refreshType)
|
_refreshData.emit(refreshType)
|
||||||
|
@ -11,6 +11,7 @@ fun logContextMenu(
|
|||||||
onRevertCommit: () -> Unit,
|
onRevertCommit: () -> Unit,
|
||||||
onCherryPickCommit: () -> Unit,
|
onCherryPickCommit: () -> Unit,
|
||||||
onResetBranch: () -> Unit,
|
onResetBranch: () -> Unit,
|
||||||
|
onRebaseInteractive: () -> Unit,
|
||||||
) = listOf(
|
) = listOf(
|
||||||
ContextMenuItem(
|
ContextMenuItem(
|
||||||
label = "Checkout commit",
|
label = "Checkout commit",
|
||||||
@ -24,6 +25,10 @@ fun logContextMenu(
|
|||||||
label = "Create tag",
|
label = "Create tag",
|
||||||
onClick = onCreateNewTag
|
onClick = onCreateNewTag
|
||||||
),
|
),
|
||||||
|
ContextMenuItem(
|
||||||
|
label = "Rebase interactive",
|
||||||
|
onClick = onRebaseInteractive
|
||||||
|
),
|
||||||
ContextMenuItem(
|
ContextMenuItem(
|
||||||
label = "Revert commit",
|
label = "Revert commit",
|
||||||
onClick = onRevertCommit
|
onClick = onRevertCommit
|
||||||
|
176
src/main/kotlin/app/ui/dialogs/RebaseInteractiveDialog.kt
Normal file
176
src/main/kotlin/app/ui/dialogs/RebaseInteractiveDialog.kt
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
package app.ui.dialogs
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
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.text.TextStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import app.theme.primaryTextColor
|
||||||
|
import app.ui.components.PrimaryButton
|
||||||
|
import app.viewmodels.RebaseInteractiveState
|
||||||
|
import app.viewmodels.RebaseInteractiveViewModel
|
||||||
|
import org.eclipse.jgit.lib.RebaseTodoLine
|
||||||
|
import org.eclipse.jgit.lib.RebaseTodoLine.Action
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun RebaseInteractiveDialog(
|
||||||
|
rebaseInteractiveViewModel: RebaseInteractiveViewModel,
|
||||||
|
revCommit: RevCommit,
|
||||||
|
onClose: () -> Unit,
|
||||||
|
) {
|
||||||
|
val rebaseState = rebaseInteractiveViewModel.rebaseState.collectAsState()
|
||||||
|
val rebaseStateValue = rebaseState.value
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
rebaseInteractiveViewModel.revCommit = revCommit
|
||||||
|
rebaseInteractiveViewModel.startRebaseInteractive()
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialDialog {
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colors.background)
|
||||||
|
.fillMaxSize(0.8f),
|
||||||
|
) {
|
||||||
|
when (rebaseStateValue) {
|
||||||
|
is RebaseInteractiveState.Failed -> {}
|
||||||
|
RebaseInteractiveState.Finished -> onClose()
|
||||||
|
is RebaseInteractiveState.Loaded -> {
|
||||||
|
RebaseStateLoaded(
|
||||||
|
rebaseInteractiveViewModel,
|
||||||
|
rebaseStateValue,
|
||||||
|
onCancel = onClose,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RebaseInteractiveState.Loading -> {
|
||||||
|
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RebaseStateLoaded(
|
||||||
|
rebaseInteractiveViewModel: RebaseInteractiveViewModel,
|
||||||
|
rebaseState: RebaseInteractiveState.Loaded,
|
||||||
|
onCancel: () -> Unit,
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Rebase interactive",
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
modifier = Modifier.padding(all = 16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
LazyColumn(modifier = Modifier.weight(1f)) {
|
||||||
|
items(rebaseState.stepsList) { rebaseTodoLine ->
|
||||||
|
RebaseCommit(
|
||||||
|
rebaseLine = rebaseTodoLine,
|
||||||
|
onActionChanged = { newAction ->
|
||||||
|
rebaseInteractiveViewModel.onCommitActionChanged(rebaseTodoLine.commit, newAction)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
onClick = {
|
||||||
|
onCancel()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
PrimaryButton(
|
||||||
|
onClick = {
|
||||||
|
rebaseInteractiveViewModel.continueRebaseInteractive()
|
||||||
|
},
|
||||||
|
text = "Complete rebase"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RebaseCommit(rebaseLine: RebaseTodoLine, onActionChanged: (Action) -> Unit) {
|
||||||
|
Row (
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
ActionDropdown(
|
||||||
|
rebaseLine.action,
|
||||||
|
onActionChanged = onActionChanged,
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(48.dp),
|
||||||
|
value = rebaseLine.shortMessage,
|
||||||
|
onValueChange = {},
|
||||||
|
colors = TextFieldDefaults.textFieldColors(backgroundColor = MaterialTheme.colors.background),
|
||||||
|
textStyle = TextStyle.Default.copy(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ActionDropdown(
|
||||||
|
action: Action,
|
||||||
|
onActionChanged: (Action) -> Unit,
|
||||||
|
) {
|
||||||
|
var showDropDownMenu by remember { mutableStateOf(false) }
|
||||||
|
Box {
|
||||||
|
PrimaryButton(
|
||||||
|
onClick = { showDropDownMenu = true },
|
||||||
|
modifier = Modifier
|
||||||
|
.width(120.dp)
|
||||||
|
.height(48.dp)
|
||||||
|
.padding(end = 8.dp),
|
||||||
|
text = action.toToken()
|
||||||
|
)
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showDropDownMenu,
|
||||||
|
onDismissRequest = { showDropDownMenu = false },
|
||||||
|
) {
|
||||||
|
for (dropDownOption in actions) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
showDropDownMenu = false
|
||||||
|
onActionChanged(dropDownOption)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(dropDownOption.toToken())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val actions = listOf(
|
||||||
|
Action.PICK,
|
||||||
|
Action.REWORD,
|
||||||
|
// RebaseTodoLine.Action.EDIT,
|
||||||
|
Action.SQUASH,
|
||||||
|
Action.FIXUP,
|
||||||
|
// RebaseTodoLine.Action.COMMENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
@ -90,7 +89,7 @@ fun Log(
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val logStatusState = logViewModel.logStatus.collectAsState()
|
val logStatusState = logViewModel.logStatus.collectAsState()
|
||||||
val logStatus = logStatusState.value
|
val logStatus = logStatusState.value
|
||||||
val showLogDialog = remember { mutableStateOf<LogDialog>(LogDialog.None) }
|
val showLogDialog by logViewModel.logDialog.collectAsState()
|
||||||
|
|
||||||
val selectedCommit = if (selectedItem is SelectedItem.CommitBasedItem) {
|
val selectedCommit = if (selectedItem is SelectedItem.CommitBasedItem) {
|
||||||
selectedItem.revCommit
|
selectedItem.revCommit
|
||||||
@ -124,8 +123,8 @@ fun Log(
|
|||||||
LogDialogs(
|
LogDialogs(
|
||||||
logViewModel,
|
logViewModel,
|
||||||
currentBranch = logStatus.currentBranch,
|
currentBranch = logStatus.currentBranch,
|
||||||
onResetShowLogDialog = { showLogDialog.value = LogDialog.None },
|
onResetShowLogDialog = { logViewModel.showDialog(LogDialog.None) },
|
||||||
showLogDialog = showLogDialog.value,
|
showLogDialog = showLogDialog,
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@ -169,7 +168,7 @@ fun Log(
|
|||||||
logViewModel = logViewModel,
|
logViewModel = logViewModel,
|
||||||
graphWidth = graphWidth,
|
graphWidth = graphWidth,
|
||||||
onShowLogDialog = { dialog ->
|
onShowLogDialog = { dialog ->
|
||||||
showLogDialog.value = dialog
|
logViewModel.showDialog(dialog)
|
||||||
})
|
})
|
||||||
|
|
||||||
DividerLog(
|
DividerLog(
|
||||||
@ -346,6 +345,7 @@ fun MessagesList(
|
|||||||
resetBranch = { onShowLogDialog(LogDialog.ResetBranch(graphNode)) },
|
resetBranch = { onShowLogDialog(LogDialog.ResetBranch(graphNode)) },
|
||||||
onMergeBranch = { ref -> onShowLogDialog(LogDialog.MergeBranch(ref)) },
|
onMergeBranch = { ref -> onShowLogDialog(LogDialog.MergeBranch(ref)) },
|
||||||
onRebaseBranch = { ref -> onShowLogDialog(LogDialog.RebaseBranch(ref)) },
|
onRebaseBranch = { ref -> onShowLogDialog(LogDialog.RebaseBranch(ref)) },
|
||||||
|
onRebaseInteractive = { onShowLogDialog(LogDialog.RebaseInteractive(graphNode)) },
|
||||||
onRevCommitSelected = { logViewModel.selectLogLine(graphNode) },
|
onRevCommitSelected = { logViewModel.selectLogLine(graphNode) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -459,6 +459,13 @@ fun LogDialogs(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is LogDialog.RebaseInteractive -> {
|
||||||
|
RebaseInteractiveDialog(
|
||||||
|
revCommit = showLogDialog.revCommit,
|
||||||
|
rebaseInteractiveViewModel = checkNotNull(logViewModel.rebaseInteractiveViewModel), // Never null, value should be set before showing dialog
|
||||||
|
onClose = onResetShowLogDialog,
|
||||||
|
)
|
||||||
|
}
|
||||||
LogDialog.None -> {
|
LogDialog.None -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -635,6 +642,7 @@ fun CommitLine(
|
|||||||
onMergeBranch: (Ref) -> Unit,
|
onMergeBranch: (Ref) -> Unit,
|
||||||
onRebaseBranch: (Ref) -> Unit,
|
onRebaseBranch: (Ref) -> Unit,
|
||||||
onRevCommitSelected: () -> Unit,
|
onRevCommitSelected: () -> Unit,
|
||||||
|
onRebaseInteractive: () -> Unit,
|
||||||
) {
|
) {
|
||||||
ContextMenuArea(
|
ContextMenuArea(
|
||||||
items = {
|
items = {
|
||||||
@ -644,6 +652,7 @@ fun CommitLine(
|
|||||||
onCreateNewTag = showCreateNewTag,
|
onCreateNewTag = showCreateNewTag,
|
||||||
onRevertCommit = { logViewModel.revertCommit(graphNode) },
|
onRevertCommit = { logViewModel.revertCommit(graphNode) },
|
||||||
onCherryPickCommit = { logViewModel.cherrypickCommit(graphNode) },
|
onCherryPickCommit = { logViewModel.cherrypickCommit(graphNode) },
|
||||||
|
onRebaseInteractive = onRebaseInteractive,
|
||||||
onResetBranch = { resetBranch() },
|
onResetBranch = { resetBranch() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -2,6 +2,7 @@ package app.ui.log
|
|||||||
|
|
||||||
import app.git.graph.GraphNode
|
import app.git.graph.GraphNode
|
||||||
import org.eclipse.jgit.lib.Ref
|
import org.eclipse.jgit.lib.Ref
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
|
|
||||||
sealed class LogDialog {
|
sealed class LogDialog {
|
||||||
object None : LogDialog()
|
object None : LogDialog()
|
||||||
@ -10,4 +11,5 @@ sealed class LogDialog {
|
|||||||
data class ResetBranch(val graphNode: GraphNode) : LogDialog()
|
data class ResetBranch(val graphNode: GraphNode) : LogDialog()
|
||||||
data class MergeBranch(val ref: Ref) : LogDialog()
|
data class MergeBranch(val ref: Ref) : LogDialog()
|
||||||
data class RebaseBranch(val ref: Ref) : LogDialog()
|
data class RebaseBranch(val ref: Ref) : LogDialog()
|
||||||
|
data class RebaseInteractive(val revCommit: RevCommit) : LogDialog()
|
||||||
}
|
}
|
@ -5,6 +5,7 @@ import app.git.*
|
|||||||
import app.git.graph.GraphCommitList
|
import app.git.graph.GraphCommitList
|
||||||
import app.git.graph.GraphNode
|
import app.git.graph.GraphNode
|
||||||
import app.ui.SelectedItem
|
import app.ui.SelectedItem
|
||||||
|
import app.ui.log.LogDialog
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
@ -13,6 +14,7 @@ import org.eclipse.jgit.api.Git
|
|||||||
import org.eclipse.jgit.lib.Ref
|
import org.eclipse.jgit.lib.Ref
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents when the search filter is not being used or the results list is empty
|
* Represents when the search filter is not being used or the results list is empty
|
||||||
@ -34,9 +36,13 @@ class LogViewModel @Inject constructor(
|
|||||||
private val mergeManager: MergeManager,
|
private val mergeManager: MergeManager,
|
||||||
private val remoteOperationsManager: RemoteOperationsManager,
|
private val remoteOperationsManager: RemoteOperationsManager,
|
||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
|
private val rebaseInteractiveViewModelProvider: Provider<RebaseInteractiveViewModel>
|
||||||
) {
|
) {
|
||||||
private val _logStatus = MutableStateFlow<LogStatus>(LogStatus.Loading)
|
private val _logStatus = MutableStateFlow<LogStatus>(LogStatus.Loading)
|
||||||
|
|
||||||
|
var rebaseInteractiveViewModel: RebaseInteractiveViewModel? = null
|
||||||
|
private set
|
||||||
|
|
||||||
val logStatus: StateFlow<LogStatus>
|
val logStatus: StateFlow<LogStatus>
|
||||||
get() = _logStatus
|
get() = _logStatus
|
||||||
|
|
||||||
@ -45,6 +51,9 @@ class LogViewModel @Inject constructor(
|
|||||||
private val _focusCommit = MutableSharedFlow<GraphNode>()
|
private val _focusCommit = MutableSharedFlow<GraphNode>()
|
||||||
val focusCommit: SharedFlow<GraphNode> = _focusCommit
|
val focusCommit: SharedFlow<GraphNode> = _focusCommit
|
||||||
|
|
||||||
|
private val _logDialog = MutableStateFlow<LogDialog>(LogDialog.None)
|
||||||
|
val logDialog: StateFlow<LogDialog> = _logDialog
|
||||||
|
|
||||||
val lazyListState = MutableStateFlow(
|
val lazyListState = MutableStateFlow(
|
||||||
LazyListState(
|
LazyListState(
|
||||||
0,
|
0,
|
||||||
@ -302,6 +311,15 @@ class LogViewModel @Inject constructor(
|
|||||||
_focusCommit.emit(newCommitToSelect)
|
_focusCommit.emit(newCommitToSelect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showDialog(dialog: LogDialog) {
|
||||||
|
rebaseInteractiveViewModel = if(dialog is LogDialog.RebaseInteractive) {
|
||||||
|
rebaseInteractiveViewModelProvider.get()
|
||||||
|
} else
|
||||||
|
null
|
||||||
|
|
||||||
|
_logDialog.value = dialog
|
||||||
|
}
|
||||||
|
|
||||||
fun closeSearch() {
|
fun closeSearch() {
|
||||||
_logSearchFilterResults.value = LogSearch.NotSearching
|
_logSearchFilterResults.value = LogSearch.NotSearching
|
||||||
}
|
}
|
||||||
|
99
src/main/kotlin/app/viewmodels/RebaseInteractiveViewModel.kt
Normal file
99
src/main/kotlin/app/viewmodels/RebaseInteractiveViewModel.kt
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package app.viewmodels
|
||||||
|
|
||||||
|
import app.git.RebaseManager
|
||||||
|
import app.git.RefreshType
|
||||||
|
import app.git.TabState
|
||||||
|
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
|
||||||
|
|
||||||
|
class RebaseInteractiveViewModel @Inject constructor(
|
||||||
|
private val tabState: TabState,
|
||||||
|
private val rebaseManager: RebaseManager,
|
||||||
|
) {
|
||||||
|
lateinit var revCommit: RevCommit
|
||||||
|
|
||||||
|
private val _rebaseState = MutableStateFlow<RebaseInteractiveState>(RebaseInteractiveState.Loading)
|
||||||
|
val rebaseState: StateFlow<RebaseInteractiveState> = _rebaseState
|
||||||
|
|
||||||
|
private var interactiveHandler = object : InteractiveHandler {
|
||||||
|
override fun prepareSteps(steps: MutableList<RebaseTodoLine>?) {
|
||||||
|
_rebaseState.value = RebaseInteractiveState.Loaded(steps?.reversed() ?: emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun modifyCommitMessage(commit: String?): String {
|
||||||
|
return commit.orEmpty() // we don't care about this since it's not called
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startRebaseInteractive() = tabState.runOperation(
|
||||||
|
refreshType = RefreshType.NONE,
|
||||||
|
) { git ->
|
||||||
|
rebaseManager.rebaseInteractive(git, interactiveHandler, revCommit)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun continueRebaseInteractive() = tabState.runOperation(
|
||||||
|
refreshType = RefreshType.ONLY_LOG,
|
||||||
|
) { git ->
|
||||||
|
rebaseManager.rebaseInteractive(
|
||||||
|
git = git,
|
||||||
|
interactiveHandler = object : InteractiveHandler {
|
||||||
|
override fun prepareSteps(steps: MutableList<RebaseTodoLine>?) {
|
||||||
|
val rebaseState = _rebaseState.value
|
||||||
|
|
||||||
|
if(rebaseState !is RebaseInteractiveState.Loaded)
|
||||||
|
return
|
||||||
|
|
||||||
|
val newSteps = rebaseState.stepsList
|
||||||
|
|
||||||
|
for (step in steps ?: emptyList()) {
|
||||||
|
val foundStep = newSteps.firstOrNull { it.commit.name() == step.commit.name() }
|
||||||
|
|
||||||
|
if (foundStep != null) {
|
||||||
|
step.action = foundStep.action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun modifyCommitMessage(commit: String?): String {
|
||||||
|
return commit.orEmpty()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
commit = revCommit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onCommitActionChanged(commit: AbbreviatedObjectId, action: Action) {
|
||||||
|
val rebaseState = _rebaseState.value
|
||||||
|
|
||||||
|
if(rebaseState !is RebaseInteractiveState.Loaded)
|
||||||
|
return
|
||||||
|
|
||||||
|
val newStepsList = rebaseState.stepsList.toMutableList() // Change the list reference to update the flow with .toList()
|
||||||
|
|
||||||
|
val stepIndex = newStepsList.indexOfFirst {
|
||||||
|
it.commit == commit
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stepIndex >= 0) {
|
||||||
|
val step = newStepsList[stepIndex]
|
||||||
|
val newTodoLine = RebaseTodoLine(action, step.commit, step.shortMessage)
|
||||||
|
newStepsList[stepIndex] = newTodoLine
|
||||||
|
|
||||||
|
_rebaseState.value = RebaseInteractiveState.Loaded(newStepsList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sealed interface RebaseInteractiveState {
|
||||||
|
object Loading : RebaseInteractiveState
|
||||||
|
data class Loaded(val stepsList: List<RebaseTodoLine>) : RebaseInteractiveState
|
||||||
|
data class Failed(val error: String) : RebaseInteractiveState
|
||||||
|
object Finished : RebaseInteractiveState
|
||||||
|
}
|
@ -255,6 +255,7 @@ class TabViewModel @Inject constructor(
|
|||||||
|
|
||||||
private fun updateDiffEntry() {
|
private fun updateDiffEntry() {
|
||||||
val diffSelected = diffSelected.value
|
val diffSelected = diffSelected.value
|
||||||
|
println("Update diff entry $diffSelected")
|
||||||
|
|
||||||
if (diffSelected != null) {
|
if (diffSelected != null) {
|
||||||
diffViewModel.updateDiff(diffSelected)
|
diffViewModel.updateDiff(diffSelected)
|
||||||
|
Loading…
Reference in New Issue
Block a user