Implemented amend previous commit

This commit is contained in:
Abdelilah El Aissaoui 2022-02-05 03:01:03 +01:00
parent e473d29167
commit ac21b59f6c
7 changed files with 136 additions and 25 deletions

View File

@ -18,6 +18,7 @@ Right now you CAN:
- Commit. - Commit.
- Reset commits. - Reset commits.
- Revert commits. - Revert commits.
- Amend previous commit.
- Merge. - Merge.
- Rebase. - Rebase.
- Create and delete branches locally. - Create and delete branches locally.

View File

@ -70,9 +70,38 @@ class LogManager @Inject constructor(
.setRef(revCommit.name) .setRef(revCommit.name)
.call() .call()
} }
suspend fun latestMessage(git: Git): String = withContext(Dispatchers.IO) {
try {
val log = git.log().setMaxCount(1).call()
val latestCommitNode = log.firstOrNull()
return@withContext if(latestCommitNode == null)
""
else
latestCommitNode.fullMessage
} catch (ex: Exception) {
ex.printStackTrace()
return@withContext ""
}
}
suspend fun hasPreviousCommits(git: Git): Boolean = withContext(Dispatchers.IO) {
try {
val log = git.log().setMaxCount(1).call()
val latestCommitNode = log.firstOrNull()
return@withContext latestCommitNode != null
} catch (ex: Exception) {
ex.printStackTrace()
return@withContext false
}
}
} }
// TODO Move this to
enum class ResetType { enum class ResetType {
SOFT, SOFT,
MIXED, MIXED,

View File

@ -188,10 +188,11 @@ class StatusManager @Inject constructor(
.call() .call()
} }
suspend fun commit(git: Git, message: String) = withContext(Dispatchers.IO) { suspend fun commit(git: Git, message: String, amend: Boolean) = withContext(Dispatchers.IO) {
git.commit() git.commit()
.setMessage(message) .setMessage(message)
.setAllowEmpty(false) .setAllowEmpty(false)
.setAmend(amend)
.call() .call()
} }

View File

@ -122,6 +122,8 @@ class TabState @Inject constructor(
if (showError) if (showError)
errorsManager.addError(newErrorNow(ex, ex.localizedMessage)) errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
} finally { } finally {
operationRunning = false
if (refreshType != RefreshType.NONE && (!hasProcessFailed || refreshEvenIfCrashes)) if (refreshType != RefreshType.NONE && (!hasProcessFailed || refreshEvenIfCrashes))
_refreshData.emit(refreshType) _refreshData.emit(refreshType)
} }

View File

@ -10,6 +10,8 @@ import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
@ -33,6 +35,8 @@ import app.git.StatusEntry
import app.theme.* import app.theme.*
import app.ui.components.ScrollableLazyColumn import app.ui.components.ScrollableLazyColumn
import app.ui.components.SecondaryButton import app.ui.components.SecondaryButton
import app.ui.context_menu.DropDownContent
import app.ui.context_menu.DropDownContentData
import app.ui.context_menu.stagedEntriesContextMenuItems import app.ui.context_menu.stagedEntriesContextMenuItems
import app.ui.context_menu.unstagedEntriesContextMenuItems import app.ui.context_menu.unstagedEntriesContextMenuItems
import app.viewmodels.StageStatus import app.viewmodels.StageStatus
@ -75,12 +79,14 @@ fun UncommitedChanges(
unstaged = listOf() // return empty lists if still loading unstaged = listOf() // return empty lists if still loading
} }
val doCommit = { val doCommit = { amend: Boolean ->
statusViewModel.commit(commitMessage) statusViewModel.commit(commitMessage, amend)
onStagedDiffEntrySelected(null) onStagedDiffEntrySelected(null)
statusViewModel.newCommitMessage = "" statusViewModel.newCommitMessage = ""
} }
val canCommit = commitMessage.isNotEmpty() && staged.isNotEmpty() val canCommit = commitMessage.isNotEmpty() && staged.isNotEmpty()
val canAmend = (commitMessage.isNotEmpty() || staged.isNotEmpty()) && statusViewModel.hasPreviousCommits
Column { Column {
AnimatedVisibility( AnimatedVisibility(
@ -91,7 +97,6 @@ fun UncommitedChanges(
LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
} }
EntriesList( EntriesList(
modifier = Modifier modifier = Modifier
.padding(start = 8.dp, end = 8.dp, bottom = 4.dp) .padding(start = 8.dp, end = 8.dp, bottom = 4.dp)
@ -169,7 +174,7 @@ fun UncommitedChanges(
.weight(weight = 1f, fill = true) .weight(weight = 1f, fill = true)
.onPreviewKeyEvent { .onPreviewKeyEvent {
if (it.isCtrlPressed && it.key == Key.Enter && canCommit) { if (it.isCtrlPressed && it.key == Key.Enter && canCommit) {
doCommit() doCommit(false)
true true
} else } else
false false
@ -185,7 +190,7 @@ fun UncommitedChanges(
repositoryState.isMerging -> MergeButtons( repositoryState.isMerging -> MergeButtons(
haveConflictsBeenSolved = unstaged.isEmpty(), haveConflictsBeenSolved = unstaged.isEmpty(),
onAbort = { statusViewModel.abortMerge() }, onAbort = { statusViewModel.abortMerge() },
onMerge = { doCommit() } onMerge = { doCommit(false) }
) )
repositoryState.isRebasing -> RebasingButtons( repositoryState.isRebasing -> RebasingButtons(
canContinue = staged.isNotEmpty() || unstaged.isNotEmpty(), canContinue = staged.isNotEmpty() || unstaged.isNotEmpty(),
@ -194,27 +199,86 @@ fun UncommitedChanges(
onContinue = { statusViewModel.continueRebase() }, onContinue = { statusViewModel.continueRebase() },
onSkip = { statusViewModel.skipRebase() }, onSkip = { statusViewModel.skipRebase() },
) )
else -> { else -> UncommitedChangesButtons(
Button( canCommit = canCommit,
modifier = Modifier canAmend = canAmend,
.fillMaxWidth(), onCommit = { amend -> doCommit(amend) },
onClick = doCommit, )
enabled = canCommit,
shape = RectangleShape,
) {
Text(
text = "Commit",
fontSize = 14.sp,
)
}
}
} }
} }
} }
} }
@Composable
fun UncommitedChangesButtons(
canCommit: Boolean,
canAmend: Boolean,
onCommit: (Boolean) -> Unit
) {
var showDropDownMenu by remember { mutableStateOf(false) }
Row(
modifier = Modifier
.padding(top = 2.dp)
) {
Button(
modifier = Modifier
.weight(1f)
.height(40.dp),
onClick = { onCommit(false) },
enabled = canCommit,
shape = RectangleShape,
) {
Text(
text = "Commit",
fontSize = 14.sp,
)
}
Spacer(
modifier = Modifier
.width(1.dp)
.height(40.dp),
)
Box(
modifier = Modifier
.height(40.dp)
.background(MaterialTheme.colors.primary)
.clickable { showDropDownMenu = true },
) {
Icon(
Icons.Default.ArrowDropDown,
contentDescription = null,
tint = MaterialTheme.colors.inversePrimaryTextColor,
modifier = Modifier
.padding(horizontal = 8.dp)
.align(Alignment.Center),
)
DropdownMenu(
onDismissRequest = {
showDropDownMenu = false
},
content = {
DropDownContent(
enabled = canAmend,
dropDownContentData = DropDownContentData(
label = "Amend previous commit",
icon = null,
onClick = { onCommit(true) }
),
onDismiss = { showDropDownMenu = false }
)
},
expanded = showDropDownMenu,
)
}
// }
}
}
@Composable @Composable
fun MergeButtons( fun MergeButtons(
haveConflictsBeenSolved: Boolean, haveConflictsBeenSolved: Boolean,

View File

@ -8,8 +8,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@Composable @Composable
fun DropDownContent(dropDownContentData: DropDownContentData, onDismiss: () -> Unit) { fun DropDownContent(
dropDownContentData: DropDownContentData,
enabled: Boolean = true,
onDismiss: () -> Unit,
) {
DropdownMenuItem( DropdownMenuItem(
enabled = enabled,
onClick = { onClick = {
dropDownContentData.onClick() dropDownContentData.onClick()
onDismiss() onDismiss()

View File

@ -17,6 +17,7 @@ class StatusViewModel @Inject constructor(
private val repositoryManager: RepositoryManager, private val repositoryManager: RepositoryManager,
private val rebaseManager: RebaseManager, private val rebaseManager: RebaseManager,
private val mergeManager: MergeManager, private val mergeManager: MergeManager,
private val logManager: LogManager,
) { ) {
private val _stageStatus = MutableStateFlow<StageStatus>(StageStatus.Loaded(listOf(), listOf())) private val _stageStatus = MutableStateFlow<StageStatus>(StageStatus.Loaded(listOf(), listOf()))
val stageStatus: StateFlow<StageStatus> = _stageStatus val stageStatus: StateFlow<StageStatus> = _stageStatus
@ -29,6 +30,8 @@ class StatusViewModel @Inject constructor(
_commitMessage.value = value _commitMessage.value = value
} }
var hasPreviousCommits = true // When false, disable "amend previous commit"
private var lastUncommitedChangesState = false private var lastUncommitedChangesState = false
fun stage(diffEntry: DiffEntry) = tabState.runOperation( fun stage(diffEntry: DiffEntry) = tabState.runOperation(
@ -89,10 +92,15 @@ class StatusViewModel @Inject constructor(
lastUncommitedChangesState = statusManager.hasUncommitedChanges(git) lastUncommitedChangesState = statusManager.hasUncommitedChanges(git)
} }
fun commit(message: String) = tabState.safeProcessing( fun commit(message: String, amend: Boolean) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
) { git -> ) { git ->
statusManager.commit(git, message) val commitMessage = if(amend && message.isBlank()) {
logManager.latestMessage(git)
} else
message
statusManager.commit(git, commitMessage, amend)
} }
suspend fun refresh(git: Git) = withContext(Dispatchers.IO) { suspend fun refresh(git: Git) = withContext(Dispatchers.IO) {
@ -110,6 +118,7 @@ class StatusViewModel @Inject constructor(
loadHasUncommitedChanges(git) loadHasUncommitedChanges(git)
val hasNowUncommitedChanges = this.lastUncommitedChangesState val hasNowUncommitedChanges = this.lastUncommitedChangesState
hasPreviousCommits = logManager.hasPreviousCommits(git)
// Return true to update the log only if the uncommitedChanges status has changed // Return true to update the log only if the uncommitedChanges status has changed
return (hasNowUncommitedChanges != hadUncommitedChanges) return (hasNowUncommitedChanges != hadUncommitedChanges)