Gitnuro/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RebaseInteractiveViewModel.kt

197 lines
6.7 KiB
Kotlin

package com.jetpackduba.gitnuro.viewmodels
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.*
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"
class RebaseInteractiveViewModel @Inject constructor(
private val tabState: TabState,
private val getRebaseLinesFullMessageUseCase: GetRebaseLinesFullMessageUseCase,
private val getRebaseInteractiveTodoLinesUseCase: GetRebaseInteractiveTodoLinesUseCase,
private val abortRebaseUseCase: AbortRebaseUseCase,
private val resumeRebaseInteractiveUseCase: ResumeRebaseInteractiveUseCase,
) {
private lateinit var commit: RevCommit
private val _rebaseState = MutableStateFlow<RebaseInteractiveViewState>(RebaseInteractiveViewState.Loading)
val rebaseState: StateFlow<RebaseInteractiveViewState> = _rebaseState
var rewordSteps = ArrayDeque<RebaseLine>()
init {
loadRebaseInteractiveData()
}
private var interactiveHandlerContinue = object : InteractiveHandler {
override fun prepareSteps(steps: MutableList<RebaseTodoLine>) {
val rebaseState = _rebaseState.value
if (rebaseState !is RebaseInteractiveViewState.Loaded) {
throw Exception("prepareSteps called when rebaseState is not Loaded") // Should never happen, just in case
}
val newSteps = rebaseState.stepsList.toMutableList()
rewordSteps = ArrayDeque(newSteps.filter { it.rebaseAction == RebaseAction.REWORD })
val newRebaseTodoLines = newSteps
.filter { it.rebaseAction != RebaseAction.DROP } // Remove dropped lines
.map { it.toRebaseTodoLine() }
steps.clear()
steps.addAll(newRebaseTodoLines)
}
override fun modifyCommitMessage(commit: String): String {
// This can be called when there aren't any reword steps if squash is used.
val step = rewordSteps.removeLastOrNull() ?: return commit
val rebaseState = _rebaseState.value
if (rebaseState !is RebaseInteractiveViewState.Loaded) {
throw Exception("modifyCommitMessage called when rebaseState is not Loaded") // Should never happen, just in case
}
return rebaseState.messages[step.commit.name()]
?: throw InvalidMessageException("Message for commit $commit is unexpectedly null")
}
}
private fun loadRebaseInteractiveData() = tabState.safeProcessing(
refreshType = RefreshType.NONE,
) { git ->
try {
val lines = getRebaseInteractiveTodoLinesUseCase(git)
val messages = getRebaseLinesFullMessageUseCase(tabState.git, lines)
val rebaseLines = lines.map {
RebaseLine(
it.action.toRebaseAction(),
it.commit,
it.shortMessage,
)
}
_rebaseState.value = RebaseInteractiveViewState.Loaded(rebaseLines, messages)
} catch (ex: Exception) {
if (ex is RebaseCancelledException) {
println("Rebase cancelled")
} else {
cancel()
throw ex
}
}
}
fun continueRebaseInteractive() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
resumeRebaseInteractiveUseCase(git, interactiveHandlerContinue)
}
fun onCommitMessageChanged(commit: AbbreviatedObjectId, message: String) {
val rebaseState = _rebaseState.value
if (rebaseState !is RebaseInteractiveViewState.Loaded)
return
val messagesMap = rebaseState.messages.toMutableMap()
messagesMap[commit.name()] = message
_rebaseState.value = rebaseState.copy(messages = messagesMap)
}
fun onCommitActionChanged(commit: AbbreviatedObjectId, rebaseAction: RebaseAction) {
val rebaseState = _rebaseState.value
if (rebaseState !is RebaseInteractiveViewState.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 = RebaseLine(
rebaseAction,
step.commit,
step.shortMessage
)
newStepsList[stepIndex] = newTodoLine
_rebaseState.value = rebaseState.copy(stepsList = newStepsList)
}
}
fun cancel() = tabState.runOperation(
refreshType = RefreshType.ALL_DATA,
) { git ->
abortRebaseUseCase(git)
}
}
sealed interface RebaseInteractiveViewState {
object Loading : RebaseInteractiveViewState
data class Loaded(val stepsList: List<RebaseLine>, val messages: Map<String, String>) : RebaseInteractiveViewState
data class Failed(val error: String) : RebaseInteractiveViewState
}
data class RebaseLine(
val rebaseAction: RebaseAction,
val commit: AbbreviatedObjectId,
val shortMessage: String
) {
fun toRebaseTodoLine(): RebaseTodoLine {
return RebaseTodoLine(
rebaseAction.toAction(),
commit,
shortMessage
)
}
}
enum class RebaseAction(val displayName: String) {
PICK("Pick"),
REWORD("Reword"),
SQUASH("Squash"),
FIXUP("Fixup"),
EDIT("Edit"),
DROP("Drop"),
COMMENT("Comment");
fun toAction(): Action {
return when (this) {
PICK -> Action.PICK
REWORD -> Action.REWORD
SQUASH -> Action.SQUASH
FIXUP -> Action.FIXUP
EDIT -> Action.EDIT
COMMENT -> Action.COMMENT
DROP -> throw NotImplementedError("To action should not be called when the RebaseAction is DROP")
}
}
}
fun Action.toRebaseAction(): RebaseAction {
return when (this) {
Action.PICK -> RebaseAction.PICK
Action.REWORD -> RebaseAction.REWORD
Action.EDIT -> RebaseAction.EDIT
Action.SQUASH -> RebaseAction.SQUASH
Action.FIXUP -> RebaseAction.FIXUP
Action.COMMENT -> RebaseAction.COMMENT
}
}