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
|
||||||
import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler
|
import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler
|
||||||
import org.eclipse.jgit.api.RebaseResult
|
import org.eclipse.jgit.api.RebaseResult
|
||||||
|
import org.eclipse.jgit.lib.RebaseTodoLine
|
||||||
import org.eclipse.jgit.lib.Ref
|
import org.eclipse.jgit.lib.Ref
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommitList
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk
|
||||||
import javax.inject.Inject
|
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) {
|
suspend fun rebaseBranch(git: Git, ref: Ref) = withContext(Dispatchers.IO) {
|
||||||
val rebaseResult = git.rebase()
|
val rebaseResult = git.rebase()
|
||||||
@ -50,4 +55,46 @@ class RebaseManager @Inject constructor() {
|
|||||||
.setUpstream(commit)
|
.setUpstream(commit)
|
||||||
.call()
|
.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,
|
rebaseState: RebaseInteractiveState.Loaded,
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
@ -80,9 +79,13 @@ fun RebaseStateLoaded(
|
|||||||
items(rebaseState.stepsList) { rebaseTodoLine ->
|
items(rebaseState.stepsList) { rebaseTodoLine ->
|
||||||
RebaseCommit(
|
RebaseCommit(
|
||||||
rebaseLine = rebaseTodoLine,
|
rebaseLine = rebaseTodoLine,
|
||||||
|
message = rebaseState.messages[rebaseTodoLine.commit.name()],
|
||||||
onActionChanged = { newAction ->
|
onActionChanged = { newAction ->
|
||||||
rebaseInteractiveViewModel.onCommitActionChanged(rebaseTodoLine.commit, newAction)
|
rebaseInteractiveViewModel.onCommitActionChanged(rebaseTodoLine.commit, newAction)
|
||||||
}
|
},
|
||||||
|
onMessageChanged = { newMessage ->
|
||||||
|
rebaseInteractiveViewModel.onCommitMessageChanged(rebaseTodoLine.commit, newMessage)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,7 +111,20 @@ fun RebaseStateLoaded(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RebaseCommit(rebaseLine: RebaseTodoLine, onActionChanged: (Action) -> Unit) {
|
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(
|
Row(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
@ -120,9 +136,13 @@ fun RebaseCommit(rebaseLine: RebaseTodoLine, onActionChanged: (Action) -> Unit)
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.height(48.dp),
|
.heightIn(min = 48.dp),
|
||||||
value = rebaseLine.shortMessage,
|
enabled = rebaseLine.action == Action.REWORD,
|
||||||
onValueChange = {},
|
value = newMessage,
|
||||||
|
onValueChange = {
|
||||||
|
newMessage = it
|
||||||
|
onMessageChanged(it)
|
||||||
|
},
|
||||||
colors = TextFieldDefaults.textFieldColors(backgroundColor = MaterialTheme.colors.background),
|
colors = TextFieldDefaults.textFieldColors(backgroundColor = MaterialTheme.colors.background),
|
||||||
textStyle = TextStyle.Default.copy(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
textStyle = TextStyle.Default.copy(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||||
)
|
)
|
||||||
@ -167,10 +187,8 @@ fun ActionDropdown(
|
|||||||
val actions = listOf(
|
val actions = listOf(
|
||||||
Action.PICK,
|
Action.PICK,
|
||||||
Action.REWORD,
|
Action.REWORD,
|
||||||
// RebaseTodoLine.Action.EDIT,
|
|
||||||
Action.SQUASH,
|
Action.SQUASH,
|
||||||
Action.FIXUP,
|
Action.FIXUP,
|
||||||
// RebaseTodoLine.Action.COMMENT,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package app.viewmodels
|
package app.viewmodels
|
||||||
|
|
||||||
|
import app.exceptions.InvalidMessageException
|
||||||
import app.git.RebaseManager
|
import app.git.RebaseManager
|
||||||
import app.git.RefreshType
|
import app.git.RefreshType
|
||||||
import app.git.TabState
|
import app.git.TabState
|
||||||
@ -23,7 +24,7 @@ class RebaseInteractiveViewModel @Inject constructor(
|
|||||||
|
|
||||||
private var interactiveHandler = object : InteractiveHandler {
|
private var interactiveHandler = object : InteractiveHandler {
|
||||||
override fun prepareSteps(steps: MutableList<RebaseTodoLine>?) {
|
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 {
|
override fun modifyCommitMessage(commit: String?): String {
|
||||||
@ -35,21 +36,33 @@ class RebaseInteractiveViewModel @Inject constructor(
|
|||||||
refreshType = RefreshType.NONE,
|
refreshType = RefreshType.NONE,
|
||||||
) { git ->
|
) { git ->
|
||||||
rebaseManager.rebaseInteractive(git, interactiveHandler, revCommit)
|
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(
|
fun continueRebaseInteractive() = tabState.runOperation(
|
||||||
refreshType = RefreshType.ONLY_LOG,
|
refreshType = RefreshType.ONLY_LOG,
|
||||||
) { git ->
|
) { 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(
|
rebaseManager.rebaseInteractive(
|
||||||
git = git,
|
git = git,
|
||||||
interactiveHandler = object : InteractiveHandler {
|
interactiveHandler = object : InteractiveHandler {
|
||||||
override fun prepareSteps(steps: MutableList<RebaseTodoLine>?) {
|
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()) {
|
for (step in steps ?: emptyList()) {
|
||||||
val foundStep = newSteps.firstOrNull { it.commit.name() == step.commit.name() }
|
val foundStep = newSteps.firstOrNull { it.commit.name() == step.commit.name() }
|
||||||
@ -60,21 +73,37 @@ class RebaseInteractiveViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun modifyCommitMessage(commit: String?): String {
|
override fun modifyCommitMessage(commit: String): String {
|
||||||
return commit.orEmpty()
|
val step = rewordSteps.removeLast()
|
||||||
|
|
||||||
|
return rebaseState.messages[step.commit.name()]
|
||||||
|
?: throw InvalidMessageException("Message for commit $commit is unexpectedly null")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
commit = revCommit
|
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) {
|
fun onCommitActionChanged(commit: AbbreviatedObjectId, action: Action) {
|
||||||
val rebaseState = _rebaseState.value
|
val rebaseState = _rebaseState.value
|
||||||
|
|
||||||
if (rebaseState !is RebaseInteractiveState.Loaded)
|
if (rebaseState !is RebaseInteractiveState.Loaded)
|
||||||
return
|
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 {
|
val stepIndex = newStepsList.indexOfFirst {
|
||||||
it.commit == commit
|
it.commit == commit
|
||||||
@ -85,7 +114,7 @@ class RebaseInteractiveViewModel @Inject constructor(
|
|||||||
val newTodoLine = RebaseTodoLine(action, step.commit, step.shortMessage)
|
val newTodoLine = RebaseTodoLine(action, step.commit, step.shortMessage)
|
||||||
newStepsList[stepIndex] = newTodoLine
|
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 {
|
sealed interface RebaseInteractiveState {
|
||||||
object Loading : 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
|
data class Failed(val error: String) : RebaseInteractiveState
|
||||||
object Finished : RebaseInteractiveState
|
object Finished : RebaseInteractiveState
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user