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.Loading) val rebaseState: StateFlow = _rebaseState var rewordSteps = ArrayDeque() private var cancelled = false private var completed = false private var interactiveHandler = object : InteractiveHandler { override fun prepareSteps(steps: MutableList) = 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, val messages: Map) : RebaseInteractiveState data class Failed(val error: String) : RebaseInteractiveState }