Fixed rebase interactive check being executed when changing between tabs even if not rebasing
This commit is contained in:
parent
804b83769f
commit
c78d7f1c3d
@ -20,6 +20,7 @@ object AppIcons {
|
|||||||
const val DESCRIPTION = "description.svg"
|
const val DESCRIPTION = "description.svg"
|
||||||
const val DONE = "done.svg"
|
const val DONE = "done.svg"
|
||||||
const val DOWNLOAD = "download.svg"
|
const val DOWNLOAD = "download.svg"
|
||||||
|
const val DRAG = "drag.svg"
|
||||||
const val DROPDOWN = "dropdown.svg"
|
const val DROPDOWN = "dropdown.svg"
|
||||||
const val ERROR = "error.svg"
|
const val ERROR = "error.svg"
|
||||||
const val EXPAND_MORE = "expand_more.svg"
|
const val EXPAND_MORE = "expand_more.svg"
|
||||||
|
@ -250,6 +250,17 @@ class TabState @Inject constructor(
|
|||||||
newSelectedItem(SelectedItem.None)
|
newSelectedItem(SelectedItem.None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun newSelectedCommit(revCommit: RevCommit?) = runOperation(
|
||||||
|
refreshType = RefreshType.NONE,
|
||||||
|
) { _ ->
|
||||||
|
if (revCommit == null) {
|
||||||
|
newSelectedItem(SelectedItem.None)
|
||||||
|
} else {
|
||||||
|
val newSelectedItem = SelectedItem.Commit(revCommit)
|
||||||
|
newSelectedItem(newSelectedItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun newSelectedRef(objectId: ObjectId?) = runOperation(
|
fun newSelectedRef(objectId: ObjectId?) = runOperation(
|
||||||
refreshType = RefreshType.NONE,
|
refreshType = RefreshType.NONE,
|
||||||
) { git ->
|
) { git ->
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package com.jetpackduba.gitnuro.git.rebase
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.errors.AmbiguousObjectException
|
||||||
|
import org.eclipse.jgit.lib.AbbreviatedObjectId
|
||||||
|
import org.eclipse.jgit.lib.ObjectId
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetCommitFromRebaseLineUseCase @Inject constructor() {
|
||||||
|
operator fun invoke(git: Git, commit: AbbreviatedObjectId, shortMessage: String): RevCommit? {
|
||||||
|
val resolvedList: List<ObjectId?> = try {
|
||||||
|
listOf(git.repository.resolve("${commit.name()}^{commit}"))
|
||||||
|
} catch (ex: AmbiguousObjectException) {
|
||||||
|
ex.candidates.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedList.isEmpty()) {
|
||||||
|
println("Commit search failed for line $commit - $shortMessage")
|
||||||
|
return null
|
||||||
|
} else if (resolvedList.count() == 1) {
|
||||||
|
val resolvedId = resolvedList.firstOrNull()
|
||||||
|
|
||||||
|
return if (resolvedId == null)
|
||||||
|
null
|
||||||
|
else
|
||||||
|
git.repository.parseCommit(resolvedId)
|
||||||
|
} else {
|
||||||
|
println("Multiple matching commits for line $commit - $shortMessage")
|
||||||
|
for (candidateId in resolvedList) {
|
||||||
|
val candidateCommit = git.repository.parseCommit(candidateId)
|
||||||
|
if (shortMessage == candidateCommit.shortMessage)
|
||||||
|
return candidateCommit
|
||||||
|
}
|
||||||
|
|
||||||
|
println("None of the matching commits has a matching short message")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,51 +3,20 @@ package com.jetpackduba.gitnuro.git.rebase
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.errors.AmbiguousObjectException
|
|
||||||
import org.eclipse.jgit.lib.ObjectId
|
|
||||||
import org.eclipse.jgit.lib.RebaseTodoLine
|
import org.eclipse.jgit.lib.RebaseTodoLine
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetRebaseLinesFullMessageUseCase @Inject constructor() {
|
class GetRebaseLinesFullMessageUseCase @Inject constructor(
|
||||||
|
private val getCommitFromRebaseLineUseCase: GetCommitFromRebaseLineUseCase,
|
||||||
|
) {
|
||||||
suspend operator fun invoke(
|
suspend operator fun invoke(
|
||||||
git: Git,
|
git: Git,
|
||||||
rebaseTodoLines: List<RebaseTodoLine>,
|
rebaseTodoLines: List<RebaseTodoLine>,
|
||||||
): Map<String, String> = withContext(Dispatchers.IO) {
|
): Map<String, String> = withContext(Dispatchers.IO) {
|
||||||
return@withContext rebaseTodoLines.associate { line ->
|
return@withContext rebaseTodoLines.associate { line ->
|
||||||
val commit = getCommitFromLine(git, line)
|
val commit = getCommitFromRebaseLineUseCase(git, line.commit, line.shortMessage)
|
||||||
val fullMessage = commit?.fullMessage ?: line.shortMessage
|
val fullMessage = commit?.fullMessage ?: line.shortMessage
|
||||||
line.commit.name() to fullMessage
|
line.commit.name() to fullMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCommitFromLine(git: Git, line: RebaseTodoLine): RevCommit? {
|
|
||||||
val resolvedList: List<ObjectId?> = try {
|
|
||||||
listOf(git.repository.resolve("${line.commit.name()}^{commit}"))
|
|
||||||
} catch (ex: AmbiguousObjectException) {
|
|
||||||
ex.candidates.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resolvedList.isEmpty()) {
|
|
||||||
println("Commit search failed for line ${line.commit} - ${line.shortMessage}")
|
|
||||||
return null
|
|
||||||
} else if (resolvedList.count() == 1) {
|
|
||||||
val resolvedId = resolvedList.firstOrNull()
|
|
||||||
|
|
||||||
return if (resolvedId == null)
|
|
||||||
null
|
|
||||||
else
|
|
||||||
git.repository.parseCommit(resolvedId)
|
|
||||||
} else {
|
|
||||||
println("Multiple matching commits for line ${line.commit} - ${line.shortMessage}")
|
|
||||||
for (candidateId in resolvedList) {
|
|
||||||
val candidateCommit = git.repository.parseCommit(candidateId)
|
|
||||||
if (line.shortMessage == candidateCommit.shortMessage)
|
|
||||||
return candidateCommit
|
|
||||||
}
|
|
||||||
|
|
||||||
println("None of the matching commits has a matching short message")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,32 +1,40 @@
|
|||||||
package com.jetpackduba.gitnuro.ui
|
package com.jetpackduba.gitnuro.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.focus.onFocusEvent
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.jetpackduba.gitnuro.AppIcons
|
import com.jetpackduba.gitnuro.AppIcons
|
||||||
import com.jetpackduba.gitnuro.ui.components.AdjustableOutlinedTextField
|
import com.jetpackduba.gitnuro.theme.backgroundSelected
|
||||||
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
import com.jetpackduba.gitnuro.ui.components.*
|
||||||
import com.jetpackduba.gitnuro.ui.components.ScrollableLazyColumn
|
|
||||||
import com.jetpackduba.gitnuro.ui.components.gitnuroDynamicViewModel
|
|
||||||
import com.jetpackduba.gitnuro.viewmodels.RebaseAction
|
import com.jetpackduba.gitnuro.viewmodels.RebaseAction
|
||||||
import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewState
|
|
||||||
import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewModel
|
import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewModel
|
||||||
|
import com.jetpackduba.gitnuro.viewmodels.RebaseInteractiveViewState
|
||||||
import com.jetpackduba.gitnuro.viewmodels.RebaseLine
|
import com.jetpackduba.gitnuro.viewmodels.RebaseLine
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RebaseInteractive(
|
fun RebaseInteractive(
|
||||||
rebaseInteractiveViewModel: RebaseInteractiveViewModel = gitnuroDynamicViewModel(),
|
rebaseInteractiveViewModel: RebaseInteractiveViewModel = gitnuroViewModel(),
|
||||||
) {
|
) {
|
||||||
val rebaseState = rebaseInteractiveViewModel.rebaseState.collectAsState()
|
val rebaseState = rebaseInteractiveViewModel.rebaseState.collectAsState()
|
||||||
val rebaseStateValue = rebaseState.value
|
val rebaseStateValue = rebaseState.value
|
||||||
|
val selectedItem by rebaseInteractiveViewModel.selectedItem.collectAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(rebaseInteractiveViewModel) {
|
||||||
|
rebaseInteractiveViewModel.loadRebaseInteractiveData()
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -39,6 +47,10 @@ fun RebaseInteractive(
|
|||||||
RebaseStateLoaded(
|
RebaseStateLoaded(
|
||||||
rebaseInteractiveViewModel,
|
rebaseInteractiveViewModel,
|
||||||
rebaseStateValue,
|
rebaseStateValue,
|
||||||
|
selectedItem,
|
||||||
|
onFocusLine = {
|
||||||
|
rebaseInteractiveViewModel.selectLine(it)
|
||||||
|
},
|
||||||
onCancel = {
|
onCancel = {
|
||||||
rebaseInteractiveViewModel.cancel()
|
rebaseInteractiveViewModel.cancel()
|
||||||
},
|
},
|
||||||
@ -56,6 +68,8 @@ fun RebaseInteractive(
|
|||||||
fun RebaseStateLoaded(
|
fun RebaseStateLoaded(
|
||||||
rebaseInteractiveViewModel: RebaseInteractiveViewModel,
|
rebaseInteractiveViewModel: RebaseInteractiveViewModel,
|
||||||
rebaseState: RebaseInteractiveViewState.Loaded,
|
rebaseState: RebaseInteractiveViewState.Loaded,
|
||||||
|
selectedItem: SelectedItem,
|
||||||
|
onFocusLine: (RebaseLine) -> Unit,
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val stepsList = rebaseState.stepsList
|
val stepsList = rebaseState.stepsList
|
||||||
@ -75,7 +89,11 @@ fun RebaseStateLoaded(
|
|||||||
RebaseCommit(
|
RebaseCommit(
|
||||||
rebaseLine = rebaseTodoLine,
|
rebaseLine = rebaseTodoLine,
|
||||||
message = rebaseState.messages[rebaseTodoLine.commit.name()],
|
message = rebaseState.messages[rebaseTodoLine.commit.name()],
|
||||||
|
isSelected = selectedItem is SelectedItem.Commit && selectedItem.revCommit.id.startsWith(
|
||||||
|
rebaseTodoLine.commit
|
||||||
|
),
|
||||||
isFirst = stepsList.first() == rebaseTodoLine,
|
isFirst = stepsList.first() == rebaseTodoLine,
|
||||||
|
onFocusLine = { onFocusLine(rebaseTodoLine) },
|
||||||
onActionChanged = { newAction ->
|
onActionChanged = { newAction ->
|
||||||
rebaseInteractiveViewModel.onCommitActionChanged(rebaseTodoLine.commit, newAction)
|
rebaseInteractiveViewModel.onCommitActionChanged(rebaseTodoLine.commit, newAction)
|
||||||
},
|
},
|
||||||
@ -114,11 +132,15 @@ fun RebaseStateLoaded(
|
|||||||
fun RebaseCommit(
|
fun RebaseCommit(
|
||||||
rebaseLine: RebaseLine,
|
rebaseLine: RebaseLine,
|
||||||
isFirst: Boolean,
|
isFirst: Boolean,
|
||||||
|
isSelected: Boolean,
|
||||||
message: String?,
|
message: String?,
|
||||||
|
onFocusLine: () -> Unit,
|
||||||
onActionChanged: (RebaseAction) -> Unit,
|
onActionChanged: (RebaseAction) -> Unit,
|
||||||
onMessageChanged: (String) -> Unit,
|
onMessageChanged: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val action = rebaseLine.rebaseAction
|
val action = rebaseLine.rebaseAction
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
var newMessage by remember(rebaseLine.commit.name(), action) {
|
var newMessage by remember(rebaseLine.commit.name(), action) {
|
||||||
if (action == RebaseAction.REWORD) {
|
if (action == RebaseAction.REWORD) {
|
||||||
mutableStateOf(message ?: rebaseLine.shortMessage) /* if reword, use the value from the map (if possible)*/
|
mutableStateOf(message ?: rebaseLine.shortMessage) /* if reword, use the value from the map (if possible)*/
|
||||||
@ -128,20 +150,45 @@ fun RebaseCommit(
|
|||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
.height(IntrinsicSize.Min)
|
.height(IntrinsicSize.Min)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.onFocusEvent {
|
||||||
|
if (it.hasFocus) {
|
||||||
|
onFocusLine()
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.clickable { onFocusLine() }
|
||||||
|
.run {
|
||||||
|
if (isSelected) {
|
||||||
|
background(MaterialTheme.colors.backgroundSelected)
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(AppIcons.DRAG),
|
||||||
|
contentDescription = "Drag line",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.pointerHoverIcon(resizePointerIconNorth),
|
||||||
|
)
|
||||||
|
|
||||||
ActionDropdown(
|
ActionDropdown(
|
||||||
action,
|
action,
|
||||||
isFirst = isFirst,
|
isFirst = isFirst,
|
||||||
|
onActionDropDownClicked = onFocusLine,
|
||||||
onActionChanged = onActionChanged,
|
onActionChanged = onActionChanged,
|
||||||
)
|
)
|
||||||
|
|
||||||
AdjustableOutlinedTextField(
|
AdjustableOutlinedTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.heightIn(min = 40.dp),
|
.heightIn(min = 40.dp)
|
||||||
|
.focusRequester(focusRequester),
|
||||||
enabled = action == RebaseAction.REWORD,
|
enabled = action == RebaseAction.REWORD,
|
||||||
value = newMessage,
|
value = newMessage,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
@ -163,12 +210,16 @@ fun RebaseCommit(
|
|||||||
fun ActionDropdown(
|
fun ActionDropdown(
|
||||||
action: RebaseAction,
|
action: RebaseAction,
|
||||||
isFirst: Boolean,
|
isFirst: Boolean,
|
||||||
|
onActionDropDownClicked: () -> Unit,
|
||||||
onActionChanged: (RebaseAction) -> Unit,
|
onActionChanged: (RebaseAction) -> Unit,
|
||||||
) {
|
) {
|
||||||
var showDropDownMenu by remember { mutableStateOf(false) }
|
var showDropDownMenu by remember { mutableStateOf(false) }
|
||||||
Box {
|
Box {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = { showDropDownMenu = true },
|
onClick = {
|
||||||
|
showDropDownMenu = true
|
||||||
|
onActionDropDownClicked()
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(120.dp)
|
.width(120.dp)
|
||||||
.height(40.dp)
|
.height(40.dp)
|
||||||
|
@ -14,10 +14,7 @@ import androidx.compose.ui.focus.FocusRequester
|
|||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.key.onKeyEvent
|
import androidx.compose.ui.input.key.onKeyEvent
|
||||||
import androidx.compose.ui.input.pointer.PointerIcon
|
|
||||||
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.jetpackduba.gitnuro.AppConstants
|
import com.jetpackduba.gitnuro.AppConstants
|
||||||
import com.jetpackduba.gitnuro.LocalTabScope
|
import com.jetpackduba.gitnuro.LocalTabScope
|
||||||
@ -26,7 +23,6 @@ import com.jetpackduba.gitnuro.git.DiffEntryType
|
|||||||
import com.jetpackduba.gitnuro.git.rebase.RebaseInteractiveState
|
import com.jetpackduba.gitnuro.git.rebase.RebaseInteractiveState
|
||||||
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
|
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
|
||||||
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
||||||
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
|
||||||
import com.jetpackduba.gitnuro.ui.components.SecondaryButton
|
import com.jetpackduba.gitnuro.ui.components.SecondaryButton
|
||||||
import com.jetpackduba.gitnuro.ui.components.gitnuroDynamicViewModel
|
import com.jetpackduba.gitnuro.ui.components.gitnuroDynamicViewModel
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.*
|
import com.jetpackduba.gitnuro.ui.dialogs.*
|
||||||
@ -40,7 +36,6 @@ import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi
|
|||||||
import org.jetbrains.compose.splitpane.HorizontalSplitPane
|
import org.jetbrains.compose.splitpane.HorizontalSplitPane
|
||||||
import org.jetbrains.compose.splitpane.SplitterScope
|
import org.jetbrains.compose.splitpane.SplitterScope
|
||||||
import org.jetbrains.compose.splitpane.rememberSplitPaneState
|
import org.jetbrains.compose.splitpane.rememberSplitPaneState
|
||||||
import java.awt.Cursor
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RepositoryOpenPage(
|
fun RepositoryOpenPage(
|
||||||
@ -54,7 +49,6 @@ fun RepositoryOpenPage(
|
|||||||
val blameState by tabViewModel.blameState.collectAsState()
|
val blameState by tabViewModel.blameState.collectAsState()
|
||||||
val showHistory by tabViewModel.showHistory.collectAsState()
|
val showHistory by tabViewModel.showHistory.collectAsState()
|
||||||
val showAuthorInfo by tabViewModel.showAuthorInfo.collectAsState()
|
val showAuthorInfo by tabViewModel.showAuthorInfo.collectAsState()
|
||||||
val rebaseInteractiveState by tabViewModel.rebaseInteractiveState.collectAsState()
|
|
||||||
|
|
||||||
var showNewBranchDialog by remember { mutableStateOf(false) }
|
var showNewBranchDialog by remember { mutableStateOf(false) }
|
||||||
var showStashWithMessageDialog by remember { mutableStateOf(false) }
|
var showStashWithMessageDialog by remember { mutableStateOf(false) }
|
||||||
@ -131,9 +125,6 @@ fun RepositoryOpenPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (rebaseInteractiveState == RebaseInteractiveState.AwaitingInteraction) {
|
|
||||||
RebaseInteractive()
|
|
||||||
} else {
|
|
||||||
val currentTabInformation = LocalTabScope.current
|
val currentTabInformation = LocalTabScope.current
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Menu(
|
Menu(
|
||||||
@ -166,7 +157,6 @@ fun RepositoryOpenPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(
|
Spacer(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -179,31 +169,6 @@ fun RepositoryOpenPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun RebaseInteractiveStartedExternally(
|
|
||||||
onCancelRebaseInteractive: () -> Unit,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
"Rebase interactive started externally or Gitnuro (or this repository's tab)\nhas been restarted during the rebase.",
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
fontWeight = FontWeight.Medium,
|
|
||||||
style = MaterialTheme.typography.body1,
|
|
||||||
)
|
|
||||||
PrimaryButton(
|
|
||||||
modifier = Modifier.padding(top = 8.dp),
|
|
||||||
text = "Abort rebase interactive",
|
|
||||||
onClick = onCancelRebaseInteractive,
|
|
||||||
backgroundColor = MaterialTheme.colors.error,
|
|
||||||
textColor = MaterialTheme.colors.onError,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BottomInfoBar(tabViewModel: TabViewModel) {
|
private fun BottomInfoBar(tabViewModel: TabViewModel) {
|
||||||
val userInfo by tabViewModel.authorInfoSimple.collectAsState()
|
val userInfo by tabViewModel.authorInfoSimple.collectAsState()
|
||||||
@ -288,10 +253,19 @@ fun MainContentView(
|
|||||||
repositoryState: RepositoryState,
|
repositoryState: RepositoryState,
|
||||||
blameState: BlameState,
|
blameState: BlameState,
|
||||||
) {
|
) {
|
||||||
|
val rebaseInteractiveState by tabViewModel.rebaseInteractiveState.collectAsState()
|
||||||
|
|
||||||
|
println("Rebase interactive state is $rebaseInteractiveState")
|
||||||
|
|
||||||
HorizontalSplitPane(
|
HorizontalSplitPane(
|
||||||
splitPaneState = rememberSplitPaneState(initialPositionPercentage = 0.20f)
|
splitPaneState = rememberSplitPaneState(initialPositionPercentage = 0.20f)
|
||||||
) {
|
) {
|
||||||
first(minSize = 180.dp) {
|
val size = if (rebaseInteractiveState == RebaseInteractiveState.AwaitingInteraction)
|
||||||
|
1.dp
|
||||||
|
else
|
||||||
|
180.dp
|
||||||
|
|
||||||
|
first(minSize = size) {
|
||||||
SidePanel()
|
SidePanel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +282,9 @@ fun MainContentView(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
if (blameState is BlameState.Loaded && !blameState.isMinimized) {
|
if (rebaseInteractiveState == RebaseInteractiveState.AwaitingInteraction) {
|
||||||
|
RebaseInteractive()
|
||||||
|
} else if (blameState is BlameState.Loaded && !blameState.isMinimized) {
|
||||||
Blame(
|
Blame(
|
||||||
filePath = blameState.filePath,
|
filePath = blameState.filePath,
|
||||||
blameResult = blameState.blameResult,
|
blameResult = blameState.blameResult,
|
||||||
@ -391,6 +367,7 @@ fun MainContentView(
|
|||||||
onHistoryFile = { tabViewModel.fileHistory(it) }
|
onHistoryFile = { tabViewModel.fileHistory(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is SelectedItem.CommitBasedItem -> {
|
is SelectedItem.CommitBasedItem -> {
|
||||||
CommitChanges(
|
CommitChanges(
|
||||||
selectedItem = selectedItem,
|
selectedItem = selectedItem,
|
||||||
@ -403,6 +380,7 @@ fun MainContentView(
|
|||||||
onHistory = { tabViewModel.fileHistory(it) },
|
onHistory = { tabViewModel.fileHistory(it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectedItem.None -> {}
|
SelectedItem.None -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -425,7 +403,7 @@ fun SplitterScope.repositorySplitter() {
|
|||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.markAsHandle()
|
.markAsHandle()
|
||||||
.pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR)))
|
.pointerHoverIcon(resizePointerIconEast)
|
||||||
.background(Color.Transparent)
|
.background(Color.Transparent)
|
||||||
.width(8.dp)
|
.width(8.dp)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.jetpackduba.gitnuro.ui
|
||||||
|
|
||||||
|
import androidx.compose.ui.input.pointer.PointerIcon
|
||||||
|
import java.awt.Cursor
|
||||||
|
|
||||||
|
val resizePointerIconEast = PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR))
|
||||||
|
val resizePointerIconNorth = PointerIcon(Cursor(Cursor.N_RESIZE_CURSOR))
|
@ -50,6 +50,7 @@ import com.jetpackduba.gitnuro.ui.dialogs.NewBranchDialog
|
|||||||
import com.jetpackduba.gitnuro.ui.dialogs.NewTagDialog
|
import com.jetpackduba.gitnuro.ui.dialogs.NewTagDialog
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.ResetBranchDialog
|
import com.jetpackduba.gitnuro.ui.dialogs.ResetBranchDialog
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.SetDefaultUpstreamBranchDialog
|
import com.jetpackduba.gitnuro.ui.dialogs.SetDefaultUpstreamBranchDialog
|
||||||
|
import com.jetpackduba.gitnuro.ui.resizePointerIconEast
|
||||||
import com.jetpackduba.gitnuro.viewmodels.LogSearch
|
import com.jetpackduba.gitnuro.viewmodels.LogSearch
|
||||||
import com.jetpackduba.gitnuro.viewmodels.LogStatus
|
import com.jetpackduba.gitnuro.viewmodels.LogStatus
|
||||||
import com.jetpackduba.gitnuro.viewmodels.LogViewModel
|
import com.jetpackduba.gitnuro.viewmodels.LogViewModel
|
||||||
@ -917,7 +918,7 @@ fun DividerLog(modifier: Modifier, graphWidth: Dp) {
|
|||||||
.padding(start = graphWidth)
|
.padding(start = graphWidth)
|
||||||
.width(DIVIDER_WIDTH.dp)
|
.width(DIVIDER_WIDTH.dp)
|
||||||
.then(modifier)
|
.then(modifier)
|
||||||
.pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR)))
|
.pointerHoverIcon(resizePointerIconEast)
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -333,7 +333,7 @@ class LogViewModel @Inject constructor(
|
|||||||
fun selectLogLine(commit: GraphNode) = tabState.runOperation(
|
fun selectLogLine(commit: GraphNode) = tabState.runOperation(
|
||||||
refreshType = RefreshType.NONE,
|
refreshType = RefreshType.NONE,
|
||||||
) {
|
) {
|
||||||
tabState.newSelectedItem(SelectedItem.Commit(commit))
|
tabState.newSelectedCommit(commit)
|
||||||
|
|
||||||
val searchValue = _logSearchFilterResults.value
|
val searchValue = _logSearchFilterResults.value
|
||||||
if (searchValue is LogSearch.SearchResults) {
|
if (searchValue is LogSearch.SearchResults) {
|
||||||
|
@ -5,13 +5,13 @@ import com.jetpackduba.gitnuro.exceptions.RebaseCancelledException
|
|||||||
import com.jetpackduba.gitnuro.git.RefreshType
|
import com.jetpackduba.gitnuro.git.RefreshType
|
||||||
import com.jetpackduba.gitnuro.git.TabState
|
import com.jetpackduba.gitnuro.git.TabState
|
||||||
import com.jetpackduba.gitnuro.git.rebase.*
|
import com.jetpackduba.gitnuro.git.rebase.*
|
||||||
|
import com.jetpackduba.gitnuro.git.repository.GetRepositoryStateUseCase
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler
|
import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler
|
||||||
import org.eclipse.jgit.lib.AbbreviatedObjectId
|
import org.eclipse.jgit.lib.AbbreviatedObjectId
|
||||||
import org.eclipse.jgit.lib.RebaseTodoLine
|
import org.eclipse.jgit.lib.RebaseTodoLine
|
||||||
import org.eclipse.jgit.lib.RebaseTodoLine.Action
|
import org.eclipse.jgit.lib.RebaseTodoLine.Action
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val TAG = "RebaseInteractiveViewMo"
|
private const val TAG = "RebaseInteractiveViewMo"
|
||||||
@ -19,18 +19,17 @@ private const val TAG = "RebaseInteractiveViewMo"
|
|||||||
class RebaseInteractiveViewModel @Inject constructor(
|
class RebaseInteractiveViewModel @Inject constructor(
|
||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
private val getRebaseLinesFullMessageUseCase: GetRebaseLinesFullMessageUseCase,
|
private val getRebaseLinesFullMessageUseCase: GetRebaseLinesFullMessageUseCase,
|
||||||
|
private val getCommitFromRebaseLineUseCase: GetCommitFromRebaseLineUseCase,
|
||||||
private val getRebaseInteractiveTodoLinesUseCase: GetRebaseInteractiveTodoLinesUseCase,
|
private val getRebaseInteractiveTodoLinesUseCase: GetRebaseInteractiveTodoLinesUseCase,
|
||||||
private val abortRebaseUseCase: AbortRebaseUseCase,
|
private val abortRebaseUseCase: AbortRebaseUseCase,
|
||||||
private val resumeRebaseInteractiveUseCase: ResumeRebaseInteractiveUseCase,
|
private val resumeRebaseInteractiveUseCase: ResumeRebaseInteractiveUseCase,
|
||||||
|
private val getRepositoryStateUseCase: GetRepositoryStateUseCase,
|
||||||
) {
|
) {
|
||||||
private lateinit var commit: RevCommit
|
|
||||||
private val _rebaseState = MutableStateFlow<RebaseInteractiveViewState>(RebaseInteractiveViewState.Loading)
|
private val _rebaseState = MutableStateFlow<RebaseInteractiveViewState>(RebaseInteractiveViewState.Loading)
|
||||||
val rebaseState: StateFlow<RebaseInteractiveViewState> = _rebaseState
|
val rebaseState: StateFlow<RebaseInteractiveViewState> = _rebaseState
|
||||||
var rewordSteps = ArrayDeque<RebaseLine>()
|
|
||||||
|
|
||||||
init {
|
val selectedItem = tabState.selectedItem
|
||||||
loadRebaseInteractiveData()
|
var rewordSteps = ArrayDeque<RebaseLine>()
|
||||||
}
|
|
||||||
|
|
||||||
private var interactiveHandlerContinue = object : InteractiveHandler {
|
private var interactiveHandlerContinue = object : InteractiveHandler {
|
||||||
override fun prepareSteps(steps: MutableList<RebaseTodoLine>) {
|
override fun prepareSteps(steps: MutableList<RebaseTodoLine>) {
|
||||||
@ -64,9 +63,16 @@ class RebaseInteractiveViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadRebaseInteractiveData() = tabState.safeProcessing(
|
fun loadRebaseInteractiveData() = tabState.safeProcessing(
|
||||||
refreshType = RefreshType.NONE,
|
refreshType = RefreshType.NONE,
|
||||||
) { git ->
|
) { git ->
|
||||||
|
val state = getRepositoryStateUseCase(git)
|
||||||
|
|
||||||
|
if (!state.isRebasing) {
|
||||||
|
_rebaseState.value = RebaseInteractiveViewState.Loading
|
||||||
|
return@safeProcessing
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val lines = getRebaseInteractiveTodoLinesUseCase(git)
|
val lines = getRebaseInteractiveTodoLinesUseCase(git)
|
||||||
val messages = getRebaseLinesFullMessageUseCase(tabState.git, lines)
|
val messages = getRebaseLinesFullMessageUseCase(tabState.git, lines)
|
||||||
@ -78,7 +84,17 @@ class RebaseInteractiveViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isSameRebase = isSameRebase(rebaseLines, _rebaseState.value)
|
||||||
|
|
||||||
|
if (!isSameRebase) {
|
||||||
_rebaseState.value = RebaseInteractiveViewState.Loaded(rebaseLines, messages)
|
_rebaseState.value = RebaseInteractiveViewState.Loaded(rebaseLines, messages)
|
||||||
|
val firstLine = rebaseLines.firstOrNull()
|
||||||
|
|
||||||
|
if (firstLine != null) {
|
||||||
|
val fullCommit = getCommitFromRebaseLineUseCase(git, firstLine.commit, firstLine.shortMessage)
|
||||||
|
tabState.newSelectedCommit(fullCommit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
if (ex is RebaseCancelledException) {
|
if (ex is RebaseCancelledException) {
|
||||||
@ -90,10 +106,25 @@ class RebaseInteractiveViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isSameRebase(rebaseLines: List<RebaseLine>, state: RebaseInteractiveViewState): Boolean {
|
||||||
|
if (state is RebaseInteractiveViewState.Loaded) {
|
||||||
|
val stepsList = state.stepsList
|
||||||
|
|
||||||
|
if (rebaseLines.count() != stepsList.count()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return rebaseLines.map { it.commit.name() } == stepsList.map { it.commit.name() }
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
fun continueRebaseInteractive() = tabState.safeProcessing(
|
fun continueRebaseInteractive() = tabState.safeProcessing(
|
||||||
refreshType = RefreshType.ALL_DATA,
|
refreshType = RefreshType.ALL_DATA,
|
||||||
) { git ->
|
) { git ->
|
||||||
resumeRebaseInteractiveUseCase(git, interactiveHandlerContinue)
|
resumeRebaseInteractiveUseCase(git, interactiveHandlerContinue)
|
||||||
|
_rebaseState.value = RebaseInteractiveViewState.Loading
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onCommitMessageChanged(commit: AbbreviatedObjectId, message: String) {
|
fun onCommitMessageChanged(commit: AbbreviatedObjectId, message: String) {
|
||||||
@ -139,6 +170,12 @@ class RebaseInteractiveViewModel @Inject constructor(
|
|||||||
refreshType = RefreshType.ALL_DATA,
|
refreshType = RefreshType.ALL_DATA,
|
||||||
) { git ->
|
) { git ->
|
||||||
abortRebaseUseCase(git)
|
abortRebaseUseCase(git)
|
||||||
|
_rebaseState.value = RebaseInteractiveViewState.Loading
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectLine(line: RebaseLine) = tabState.safeProcessing(refreshType = RefreshType.NONE) { git ->
|
||||||
|
val fullCommit = getCommitFromRebaseLineUseCase(git, line.commit, line.shortMessage)
|
||||||
|
tabState.newSelectedCommit(fullCommit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,13 +397,7 @@ class TabViewModel @Inject constructor(
|
|||||||
fun selectCommit(commit: RevCommit) = tabState.runOperation(
|
fun selectCommit(commit: RevCommit) = tabState.runOperation(
|
||||||
refreshType = RefreshType.NONE,
|
refreshType = RefreshType.NONE,
|
||||||
) {
|
) {
|
||||||
tabState.newSelectedItem(SelectedItem.Commit(commit))
|
tabState.newSelectedCommit(commit)
|
||||||
}
|
|
||||||
|
|
||||||
fun selectUncommitedChanges() = tabState.runOperation(
|
|
||||||
refreshType = RefreshType.NONE,
|
|
||||||
) {
|
|
||||||
tabState.newSelectedItem(SelectedItem.UncommitedChanges, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fileHistory(filePath: String) {
|
fun fileHistory(filePath: String) {
|
||||||
|
@ -16,9 +16,9 @@ class TabViewModelsHolder @Inject constructor(
|
|||||||
cloneViewModel: CloneViewModel,
|
cloneViewModel: CloneViewModel,
|
||||||
settingsViewModel: SettingsViewModel,
|
settingsViewModel: SettingsViewModel,
|
||||||
sidePanelViewModel: SidePanelViewModel,
|
sidePanelViewModel: SidePanelViewModel,
|
||||||
|
rebaseInteractiveViewModel: RebaseInteractiveViewModel,
|
||||||
// Dynamic VM
|
// Dynamic VM
|
||||||
private val diffViewModelProvider: Provider<DiffViewModel>,
|
private val diffViewModelProvider: Provider<DiffViewModel>,
|
||||||
private val rebaseInteractiveViewModelProvider: Provider<RebaseInteractiveViewModel>,
|
|
||||||
private val historyViewModelProvider: Provider<HistoryViewModel>,
|
private val historyViewModelProvider: Provider<HistoryViewModel>,
|
||||||
private val authorViewModelProvider: Provider<AuthorViewModel>,
|
private val authorViewModelProvider: Provider<AuthorViewModel>,
|
||||||
private val changeDefaultUpstreamBranchViewModelProvider: Provider<ChangeDefaultUpstreamBranchViewModel>,
|
private val changeDefaultUpstreamBranchViewModelProvider: Provider<ChangeDefaultUpstreamBranchViewModel>,
|
||||||
@ -33,13 +33,13 @@ class TabViewModelsHolder @Inject constructor(
|
|||||||
commitChangesViewModel::class to commitChangesViewModel,
|
commitChangesViewModel::class to commitChangesViewModel,
|
||||||
cloneViewModel::class to cloneViewModel,
|
cloneViewModel::class to cloneViewModel,
|
||||||
settingsViewModel::class to settingsViewModel,
|
settingsViewModel::class to settingsViewModel,
|
||||||
|
rebaseInteractiveViewModel::class to rebaseInteractiveViewModel,
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO Call this when required
|
// TODO Call this when required
|
||||||
fun dynamicViewModel(type: KClass<*>): Any {
|
fun dynamicViewModel(type: KClass<*>): Any {
|
||||||
return when(type) {
|
return when(type) {
|
||||||
DiffViewModel::class -> diffViewModelProvider.get()
|
DiffViewModel::class -> diffViewModelProvider.get()
|
||||||
RebaseInteractiveViewModel::class -> rebaseInteractiveViewModelProvider.get()
|
|
||||||
HistoryViewModel::class -> historyViewModelProvider.get()
|
HistoryViewModel::class -> historyViewModelProvider.get()
|
||||||
AuthorViewModel::class -> authorViewModelProvider.get()
|
AuthorViewModel::class -> authorViewModelProvider.get()
|
||||||
ChangeDefaultUpstreamBranchViewModel::class -> changeDefaultUpstreamBranchViewModelProvider.get()
|
ChangeDefaultUpstreamBranchViewModel::class -> changeDefaultUpstreamBranchViewModelProvider.get()
|
||||||
|
1
src/main/resources/drag.svg
Normal file
1
src/main/resources/drag.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M160-390v-60h640v60H160Zm0-120v-60h640v60H160Z"/></svg>
|
After Width: | Height: | Size: 152 B |
Loading…
Reference in New Issue
Block a user