Gitnuro/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RebaseInteractiveViewModel.kt
2023-03-08 01:07:58 +01:00

174 lines
6.3 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.AbortRebaseUseCase
import com.jetpackduba.gitnuro.git.rebase.GetRebaseLinesFullMessageUseCase
import com.jetpackduba.gitnuro.git.rebase.ResumeRebaseInteractiveUseCase
import com.jetpackduba.gitnuro.git.rebase.StartRebaseInteractiveUseCase
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
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 startRebaseInteractiveUseCase: StartRebaseInteractiveUseCase,
private val abortRebaseUseCase: AbortRebaseUseCase,
private val resumeRebaseInteractiveUseCase: ResumeRebaseInteractiveUseCase,
) {
private val rebaseInteractiveMutex = Mutex(true)
private val _rebaseState = MutableStateFlow<RebaseInteractiveState>(RebaseInteractiveState.Loading)
val rebaseState: StateFlow<RebaseInteractiveState> = _rebaseState
var rewordSteps = ArrayDeque<RebaseTodoLine>()
private var cancelled = false
private var completed = false
private var interactiveHandler = object : InteractiveHandler {
override fun prepareSteps(steps: MutableList<RebaseTodoLine>) = runBlocking {
println("prepareSteps started")
tabState.refreshData(RefreshType.REPO_STATE)
val messages = getRebaseLinesFullMessageUseCase(tabState.git, steps)
_rebaseState.value = RebaseInteractiveState.Loaded(steps, messages)
println("prepareSteps mutex lock")
rebaseInteractiveMutex.lock()
if (cancelled) {
throw RebaseCancelledException("Rebase cancelled due to user request")
}
val rebaseState = _rebaseState.value
if (rebaseState !is RebaseInteractiveState.Loaded) {
throw Exception("prepareSteps called when rebaseState is not Loaded") // Should never happen, just in case
}
val newSteps = rebaseState.stepsList
rewordSteps = ArrayDeque(newSteps.filter { it.action == Action.REWORD })
steps.clear()
steps.addAll(newSteps)
println("prepareSteps finished")
}
override fun modifyCommitMessage(commit: String): String = runBlocking {
// This can be called when there aren't any reword steps if squash is used.
val step = rewordSteps.removeLastOrNull() ?: return@runBlocking commit
val rebaseState = _rebaseState.value
if (rebaseState !is RebaseInteractiveState.Loaded) {
throw Exception("modifyCommitMessage called when rebaseState is not Loaded") // Should never happen, just in case
}
return@runBlocking rebaseState.messages[step.commit.name()]
?: throw InvalidMessageException("Message for commit $commit is unexpectedly null")
}
}
suspend fun startRebaseInteractive(revCommit: RevCommit) = tabState.runOperation(
refreshType = RefreshType.ALL_DATA,
showError = true
) { git ->
try {
startRebaseInteractiveUseCase(git, interactiveHandler, revCommit)
completed = true
} catch (ex: Exception) {
if (ex is RebaseCancelledException) {
println("Rebase cancelled")
} else {
cancel()
throw ex
}
}
}
fun continueRebaseInteractive() = tabState.runOperation(
refreshType = RefreshType.ONLY_LOG,
) {
rebaseInteractiveMutex.unlock()
}
fun onCommitMessageChanged(commit: AbbreviatedObjectId, message: String) {
val rebaseState = _rebaseState.value
if (rebaseState !is RebaseInteractiveState.Loaded)
return
val messagesMap = rebaseState.messages.toMutableMap()
messagesMap[commit.name()] = message
_rebaseState.value = rebaseState.copy(messages = messagesMap)
}
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 = rebaseState.copy(stepsList = newStepsList)
}
}
fun cancel() = tabState.runOperation(
refreshType = RefreshType.REPO_STATE
) { git ->
if (!cancelled && !completed) {
abortRebaseUseCase(git)
cancelled = true
rebaseInteractiveMutex.unlock()
}
}
fun resumeRebase() = tabState.runOperation(
showError = true,
refreshType = RefreshType.NONE,
) { git ->
try {
resumeRebaseInteractiveUseCase(git, interactiveHandler)
completed = true
} catch (ex: Exception) {
if (ex is RebaseCancelledException) {
println("Rebase cancelled")
} else {
cancel()
throw ex
}
}
}
}
sealed interface RebaseInteractiveState {
object Loading : RebaseInteractiveState
data class Loaded(val stepsList: List<RebaseTodoLine>, val messages: Map<String, String>) : RebaseInteractiveState
data class Failed(val error: String) : RebaseInteractiveState
}