Implemented reword on rebase interactive

This commit is contained in:
Abdelilah El Aissaoui 2022-04-16 04:52:53 +02:00
parent cdf7d61045
commit f60580750b
4 changed files with 119 additions and 22 deletions

View File

@ -0,0 +1,3 @@
package app.exceptions
class InvalidMessageException(msg: String) : GitnuroException(msg)

View File

@ -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<RebaseTodoLine>,
commit: RevCommit
): Map<String, String> = withContext(Dispatchers.IO) {
val revWalk = RevWalk(git.repository)
markCurrentBranchAsStart(revWalk, git)
val revCommitList = RevCommitList<RevCommit>()
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<RevCommit>
): 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")
}
}

View File

@ -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,
)

View File

@ -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<RebaseTodoLine>?) {
_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<RebaseTodoLine>(newSteps.filter { it.action == Action.REWORD })
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() }
@ -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<RebaseTodoLine>) : RebaseInteractiveState
data class Loaded(val stepsList: List<RebaseTodoLine>, val messages: Map<String, String>) : RebaseInteractiveState
data class Failed(val error: String) : RebaseInteractiveState
object Finished : RebaseInteractiveState
}