From f60580750b52b6b7b4e91b38d4443422eeece092 Mon Sep 17 00:00:00 2001 From: Abdelilah El Aissaoui Date: Sat, 16 Apr 2022 04:52:53 +0200 Subject: [PATCH] Implemented reword on rebase interactive --- .../app/exceptions/InvalidMessageException.kt | 3 ++ src/main/kotlin/app/git/RebaseManager.kt | 51 ++++++++++++++++++- .../app/ui/dialogs/RebaseInteractiveDialog.kt | 36 +++++++++---- .../viewmodels/RebaseInteractiveViewModel.kt | 51 +++++++++++++++---- 4 files changed, 119 insertions(+), 22 deletions(-) create mode 100644 src/main/kotlin/app/exceptions/InvalidMessageException.kt diff --git a/src/main/kotlin/app/exceptions/InvalidMessageException.kt b/src/main/kotlin/app/exceptions/InvalidMessageException.kt new file mode 100644 index 0000000..bce5e9a --- /dev/null +++ b/src/main/kotlin/app/exceptions/InvalidMessageException.kt @@ -0,0 +1,3 @@ +package app.exceptions + +class InvalidMessageException(msg: String) : GitnuroException(msg) \ No newline at end of file diff --git a/src/main/kotlin/app/git/RebaseManager.kt b/src/main/kotlin/app/git/RebaseManager.kt index 8f0ede5..619188c 100644 --- a/src/main/kotlin/app/git/RebaseManager.kt +++ b/src/main/kotlin/app/git/RebaseManager.kt @@ -7,11 +7,16 @@ import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.RebaseCommand import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler import org.eclipse.jgit.api.RebaseResult +import org.eclipse.jgit.lib.RebaseTodoLine import org.eclipse.jgit.lib.Ref import org.eclipse.jgit.revwalk.RevCommit +import org.eclipse.jgit.revwalk.RevCommitList +import org.eclipse.jgit.revwalk.RevWalk import javax.inject.Inject -class RebaseManager @Inject constructor() { +class RebaseManager @Inject constructor( + private val branchesManager: BranchesManager, +) { suspend fun rebaseBranch(git: Git, ref: Ref) = withContext(Dispatchers.IO) { val rebaseResult = git.rebase() @@ -19,7 +24,7 @@ class RebaseManager @Inject constructor() { .setUpstream(ref.objectId) .call() - if(rebaseResult.status == RebaseResult.Status.UNCOMMITTED_CHANGES) { + if (rebaseResult.status == RebaseResult.Status.UNCOMMITTED_CHANGES) { throw UncommitedChangesDetectedException("Rebase failed, the repository contains uncommited changes.") } } @@ -50,4 +55,46 @@ class RebaseManager @Inject constructor() { .setUpstream(commit) .call() } + + suspend fun rebaseLinesFullMessage( + git: Git, + rebaseTodoLines: List, + commit: RevCommit + ): Map = withContext(Dispatchers.IO) { + val revWalk = RevWalk(git.repository) + markCurrentBranchAsStart(revWalk, git) + + val revCommitList = RevCommitList() + revCommitList.source(revWalk) + revCommitList.fillTo(commit, Int.MAX_VALUE) + + val commitsList = revCommitList.toList() + + return@withContext rebaseTodoLines.associate { rebaseLine -> + val fullMessage = getFullMessage(rebaseLine, commitsList) ?: rebaseLine.shortMessage + rebaseLine.commit.name() to fullMessage + } + } + + + private fun getFullMessage( + rebaseTodoLine: RebaseTodoLine, + commitsList: List + ): String? { + val abbreviatedIdLength = rebaseTodoLine.commit.name().count() + + return commitsList.firstOrNull { + it.abbreviate(abbreviatedIdLength).name() == rebaseTodoLine.commit.name() + }?.fullMessage + } + + private suspend fun markCurrentBranchAsStart(revWalk: RevWalk, git: Git) { + val currentBranch = branchesManager.currentBranchRef(git) ?: throw Exception("Null current branch") + val refTarget = revWalk.parseAny(currentBranch.leaf.objectId) + + if (refTarget is RevCommit) + revWalk.markStart(refTarget) + else + throw Exception("Ref target is not a RevCommit") + } } \ No newline at end of file diff --git a/src/main/kotlin/app/ui/dialogs/RebaseInteractiveDialog.kt b/src/main/kotlin/app/ui/dialogs/RebaseInteractiveDialog.kt index 25688a5..cdf1cf4 100644 --- a/src/main/kotlin/app/ui/dialogs/RebaseInteractiveDialog.kt +++ b/src/main/kotlin/app/ui/dialogs/RebaseInteractiveDialog.kt @@ -66,7 +66,6 @@ fun RebaseStateLoaded( rebaseState: RebaseInteractiveState.Loaded, onCancel: () -> Unit, ) { - Column( modifier = Modifier.fillMaxSize() ) { @@ -80,9 +79,13 @@ fun RebaseStateLoaded( items(rebaseState.stepsList) { rebaseTodoLine -> RebaseCommit( rebaseLine = rebaseTodoLine, + message = rebaseState.messages[rebaseTodoLine.commit.name()], onActionChanged = { newAction -> rebaseInteractiveViewModel.onCommitActionChanged(rebaseTodoLine.commit, newAction) - } + }, + onMessageChanged = { newMessage -> + rebaseInteractiveViewModel.onCommitMessageChanged(rebaseTodoLine.commit, newMessage) + }, ) } } @@ -108,8 +111,21 @@ fun RebaseStateLoaded( } @Composable -fun RebaseCommit(rebaseLine: RebaseTodoLine, onActionChanged: (Action) -> Unit) { - Row ( +fun RebaseCommit( + rebaseLine: RebaseTodoLine, + message: String?, + onActionChanged: (Action) -> Unit, + onMessageChanged: (String) -> Unit, +) { + val action = rebaseLine.action + var newMessage by remember(rebaseLine.commit.name(), action) { + if(action == Action.REWORD) { + mutableStateOf(message ?: rebaseLine.shortMessage) /* if reword, use the value from the map (if possible)*/ } else + mutableStateOf(rebaseLine.shortMessage) // If it's not reword, use the original shortMessage + + } + + Row( modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) ) { ActionDropdown( @@ -120,9 +136,13 @@ fun RebaseCommit(rebaseLine: RebaseTodoLine, onActionChanged: (Action) -> Unit) OutlinedTextField( modifier = Modifier .weight(1f) - .height(48.dp), - value = rebaseLine.shortMessage, - onValueChange = {}, + .heightIn(min = 48.dp), + enabled = rebaseLine.action == Action.REWORD, + value = newMessage, + onValueChange = { + newMessage = it + onMessageChanged(it) + }, colors = TextFieldDefaults.textFieldColors(backgroundColor = MaterialTheme.colors.background), textStyle = TextStyle.Default.copy(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor), ) @@ -167,10 +187,8 @@ fun ActionDropdown( val actions = listOf( Action.PICK, Action.REWORD, -// RebaseTodoLine.Action.EDIT, Action.SQUASH, Action.FIXUP, -// RebaseTodoLine.Action.COMMENT, ) diff --git a/src/main/kotlin/app/viewmodels/RebaseInteractiveViewModel.kt b/src/main/kotlin/app/viewmodels/RebaseInteractiveViewModel.kt index 3ca3be1..d9c7fe3 100644 --- a/src/main/kotlin/app/viewmodels/RebaseInteractiveViewModel.kt +++ b/src/main/kotlin/app/viewmodels/RebaseInteractiveViewModel.kt @@ -1,5 +1,6 @@ package app.viewmodels +import app.exceptions.InvalidMessageException import app.git.RebaseManager import app.git.RefreshType import app.git.TabState @@ -23,7 +24,7 @@ class RebaseInteractiveViewModel @Inject constructor( private var interactiveHandler = object : InteractiveHandler { override fun prepareSteps(steps: MutableList?) { - _rebaseState.value = RebaseInteractiveState.Loaded(steps?.reversed() ?: emptyList()) + _rebaseState.value = RebaseInteractiveState.Loaded(steps?.reversed() ?: emptyList(), emptyMap()) } override fun modifyCommitMessage(commit: String?): String { @@ -35,21 +36,33 @@ class RebaseInteractiveViewModel @Inject constructor( refreshType = RefreshType.NONE, ) { git -> rebaseManager.rebaseInteractive(git, interactiveHandler, revCommit) + + val rebaseState = _rebaseState.value + + if (rebaseState is RebaseInteractiveState.Loaded) { + val messages = rebaseManager.rebaseLinesFullMessage(git, rebaseState.stepsList, revCommit) + _rebaseState.value = rebaseState.copy(messages = messages) + } } fun continueRebaseInteractive() = tabState.runOperation( refreshType = RefreshType.ONLY_LOG, ) { git -> + val rebaseState = _rebaseState.value + if (rebaseState !is RebaseInteractiveState.Loaded) { + println("continueRebaseInteractive called when rebaseState is not Loaded") + return@runOperation // Should never happen, just in case + } + + val newSteps = rebaseState.stepsList + val rewordSteps = ArrayDeque(newSteps.filter { it.action == Action.REWORD }) + rebaseManager.rebaseInteractive( git = git, interactiveHandler = object : InteractiveHandler { override fun prepareSteps(steps: MutableList?) { - 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() } @@ -60,21 +73,37 @@ class RebaseInteractiveViewModel @Inject constructor( } } - override fun modifyCommitMessage(commit: String?): String { - return commit.orEmpty() + override fun modifyCommitMessage(commit: String): String { + val step = rewordSteps.removeLast() + + return rebaseState.messages[step.commit.name()] + ?: throw InvalidMessageException("Message for commit $commit is unexpectedly null") } }, commit = revCommit ) } + 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) + if (rebaseState !is RebaseInteractiveState.Loaded) return - val newStepsList = rebaseState.stepsList.toMutableList() // Change the list reference to update the flow with .toList() + val newStepsList = + rebaseState.stepsList.toMutableList() // Change the list reference to update the flow with .toList() val stepIndex = newStepsList.indexOfFirst { it.commit == commit @@ -85,7 +114,7 @@ class RebaseInteractiveViewModel @Inject constructor( val newTodoLine = RebaseTodoLine(action, step.commit, step.shortMessage) newStepsList[stepIndex] = newTodoLine - _rebaseState.value = RebaseInteractiveState.Loaded(newStepsList) + _rebaseState.value = rebaseState.copy(stepsList = newStepsList) } } } @@ -93,7 +122,7 @@ class RebaseInteractiveViewModel @Inject constructor( sealed interface RebaseInteractiveState { object Loading : RebaseInteractiveState - data class Loaded(val stepsList: List) : RebaseInteractiveState + data class Loaded(val stepsList: List, val messages: Map) : RebaseInteractiveState data class Failed(val error: String) : RebaseInteractiveState object Finished : RebaseInteractiveState } \ No newline at end of file