Implemented reword on rebase interactive
This commit is contained in:
parent
cdf7d61045
commit
f60580750b
@ -0,0 +1,3 @@
|
||||
package app.exceptions
|
||||
|
||||
class InvalidMessageException(msg: String) : GitnuroException(msg)
|
@ -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")
|
||||
}
|
||||
}
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user