[WIP] Created new implementation of how commits graph is generated

This commit is contained in:
Abdelilah El Aissaoui 2024-01-31 14:46:23 +01:00
parent ed86583f58
commit 2c8f8da9d8
No known key found for this signature in database
GPG key ID: 7587FC860F594869
10 changed files with 499 additions and 138 deletions

View file

@ -101,7 +101,7 @@ kotlin {
}
tasks.withType<KotlinCompile> {
kotlinOptions.allWarningsAsErrors = true
kotlinOptions.allWarningsAsErrors = false
kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
}

View file

@ -0,0 +1,189 @@
package com.jetpackduba.gitnuro.git.graph
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.revwalk.filter.RevFilter
import java.util.*
import javax.inject.Inject
class GenerateLogWalkUseCase @Inject constructor() {
suspend operator fun invoke(
git: Git,
firstCommit: RevCommit,
): GraphCommitList2 = withContext(Dispatchers.IO) {
val reservedLanes = mutableMapOf<Int, String>()
val graphNodes = mutableListOf<GraphNode2>()
val availableCommitsToAdd = mutableMapOf<String, RevCommit>()
var currentCommit: RevCommit? = firstCommit
do {
if (currentCommit == null) continue
val lanes = getReservedLanes(reservedLanes, currentCommit.name)
val lane = lanes.first()
val forkingLanes = lanes - lane
val parents = sortParentsByPriority(git, currentCommit)
val parentsCount = parents.count()
val mergingLanes = mutableListOf<Int>()
if (parentsCount == 1) {
reservedLanes[lane] = parents.first().name
} else if (parentsCount > 1) {
reservedLanes[lane] = parents.first().name
for (i in 1 until parentsCount) {
val availableLane = firstAvailableLane(reservedLanes)
reservedLanes[availableLane] = parents[i].name
mergingLanes.add(availableLane)
}
}
val graphNode = GraphNode2(
currentCommit.name,
currentCommit.shortMessage,
currentCommit.fullMessage,
currentCommit.authorIdent,
currentCommit.committerIdent,
currentCommit.parentCount,
isStash = false,
lane = lane,
forkingLanes = forkingLanes,
passingLanes = reservedLanes.keys.toList() - mergingLanes.toSet() - forkingLanes.toSet(),
mergingLanes = mergingLanes,
)
graphNodes.add(graphNode)
removeFromAllLanes(reservedLanes, graphNode.name)
availableCommitsToAdd.putAll(parents.map { it.name to it })
currentCommit = getNextCommit(availableCommitsToAdd.values.toList())
availableCommitsToAdd.remove(currentCommit?.name)
} while (currentCommit != null)
GraphCommitList2(graphNodes)
}
private fun sortParentsByPriority(git: Git, currentCommit: RevCommit): List<RevCommit> {
val parents = currentCommit
.parents
.map { git.repository.parseCommit(it) }
.toMutableList()
return if (parents.count() <= 1) {
parents
}
else if (parents.count() == 2) {
if (parents[0].parents.any { it.name == parents[1].name }) {
listOf(parents[1], parents[0])
} else if (parents[1].parents.any { it.name == parents[0].name }) {
parents
} else {
parents // TODO Sort by longer tree or detect the origin branch
}
} else {
parents.sortedBy { it.committerIdent.`when` }
}
// parents.sortedWith { o1, o2 ->
// TODO("Not yet implemented")
// }
// val parentsWithPriority = mapOf<RevCommit, Int>()
//
//
//
// if (currentCommit.name.startsWith("Merge")) {
// val originMergeParent = parents.firstOrNull {
// currentCommit.fullMessage.contains(it.shortMessage)
// }
//
// if (originMergeParent != null) {
// (parents - currentCommit) + currentCommit // this will remove the currentItem and re-add it to the end
// } else
// }
}
fun t(commit1: RevCommit, commit2: RevCommit, repository: Repository): RevCommit? {
return RevWalk(repository).use { walk ->
walk.setRevFilter(RevFilter.MERGE_BASE)
walk.markStart(commit1)
walk.markStart(commit2)
walk.next()
}
}
// fun getShortestParent(parent1: RevCommit, parent2: RevCommit) {
// val logParent1 = mutableListOf<String>(parent1.name)
// val logParent2 = mutableListOf<String>(parent2.name)
//
// var newParent1: RevCommit? = parent1
// var newParent2: RevCommit? = parent2
//
// while (newParent1 != null && newParent2 != null) {
// newParent1.pa
// }
// }
fun getNextCommit(availableCommits: List<RevCommit>): RevCommit? {
return availableCommits.sortedByDescending { it.committerIdent.`when` }.firstOrNull()
}
fun getReservedLanes(reservedLanes: Map<Int, String>, hash: String): List<Int> {
val reservedLanesFiltered = reservedLanes.entries
.asSequence()
.map { it.key to it.value }
.filter { (key, value) -> value == hash }
.sortedBy { it.first }
.toList()
return if (reservedLanesFiltered.isEmpty()) {
listOf(firstAvailableLane(reservedLanes))
} else {
reservedLanesFiltered.map { it.first }
}
}
fun removeFromAllLanes(reservedLanes: MutableMap<Int, String>, hash: String) {
val lanes = reservedLanes.entries.filter { it.value == hash }
for (lane in lanes) {
reservedLanes.remove(lane.key)
}
}
fun firstAvailableLane(reservedLanes: Map<Int, String>): Int {
val sortedKeys = reservedLanes.keys.sorted()
return if (sortedKeys.isEmpty() || sortedKeys.first() > 0) {
0
} else if (sortedKeys.count() == 1) {
val first = sortedKeys.first()
if (first == 0) {
1
} else {
0
}
} else {
sortedKeys.asSequence()
.zipWithNext { a, b ->
if (b - a > 1)
a + 1
else
null
}
.filterNotNull()
.firstOrNull() ?: (sortedKeys.max() + 1)
}
}
}

View file

@ -0,0 +1,8 @@
package com.jetpackduba.gitnuro.git.graph
class GraphCommitList2(val nodes: List<GraphNode2>) : List<GraphNode2> by nodes {
var maxLine = 0
private set
}

View file

@ -0,0 +1,21 @@
package com.jetpackduba.gitnuro.git.graph
import org.eclipse.jgit.lib.AnyObjectId
import org.eclipse.jgit.lib.PersonIdent
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.revwalk.RevCommit
data class GraphNode2(
val name: String,
val message: String,
val fullMessage: String,
val authorIdent: PersonIdent,
val committerIdent: PersonIdent,
val parentCount: Int,
val isStash: Boolean,
val lane: Int,
val passingLanes: List<Int>,
val forkingLanes: List<Int>,
val mergingLanes: List<Int>,
)

View file

@ -45,13 +45,19 @@ class GraphWalk(private var repository: Repository?) : RevWalk(repository) {
return GraphNode(id)
}
fun createCommitFromStash(id: AnyObjectId): RevCommit {
return GraphNode(id).apply {
isStash = true
}
}
override fun next(): RevCommit? {
val graphNode = super.next() as GraphNode?
val c = super.next()
val graphNode = c as GraphNode?
if (graphNode != null) {
val refs = getRefs(graphNode)
graphNode.isStash = refs.count() == 1 && refs.firstOrNull()?.name == "refs/stash"
graphNode.refs = refs
}
@ -107,10 +113,23 @@ class GraphWalk(private var repository: Repository?) : RevWalk(repository) {
}
}
fun markStartFromStashes(stashes: List<RevObject>) {
for (stash in stashes) {
markStartRevObject(createCommitFromStash(stash))
}
}
private fun markStartRef(ref: Ref) {
try {
val refTarget = parseAny(ref.leaf.objectId)
markStartRevObject(refTarget)
} catch (e: MissingObjectException) {
// Ignore missing Refs
}
}
private fun markStartRevObject(refTarget: RevObject) {
try {
when (refTarget) {
is RevCommit -> markStart(refTarget)
// RevTag case handles commits without branches but only tags.

View file

@ -1,8 +1,10 @@
package com.jetpackduba.gitnuro.git.log
import com.jetpackduba.gitnuro.git.graph.GenerateLogWalkUseCase
import com.jetpackduba.gitnuro.git.graph.GraphCommitList
import com.jetpackduba.gitnuro.git.graph.GraphWalk
import com.jetpackduba.gitnuro.git.stash.GetStashListUseCase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.withContext
@ -11,44 +13,53 @@ import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.lib.Repository
import javax.inject.Inject
import kotlin.math.log
class GetLogUseCase @Inject constructor() {
class GetLogUseCase @Inject constructor(
private val getStashListUseCase: GetStashListUseCase,
private val generateLogWalkUseCase: GenerateLogWalkUseCase,
) {
private var graphWalkCached: GraphWalk? = null
suspend operator fun invoke(git: Git, currentBranch: Ref?, hasUncommittedChanges: Boolean, commitsLimit: Int) =
withContext(Dispatchers.IO) {
val commitList = GraphCommitList()
val repositoryState = git.repository.repositoryState
if (currentBranch != null || repositoryState.isRebasing) { // Current branch is null when there is no log (new repo) or rebasing
val logList = git.log().setMaxCount(1).call().toList()
val walk = GraphWalk(git.repository)
walk.use {
// Without this, during rebase conflicts the graph won't show the HEAD commits (new commits created
// by the rebase)
walk.markStart(walk.lookupCommit(logList.first()))
walk.markStartAllRefs(Constants.R_HEADS)
walk.markStartAllRefs(Constants.R_REMOTES)
walk.markStartAllRefs(Constants.R_TAGS)
walk.markStartAllRefs(Constants.R_STASH)
if (hasUncommittedChanges)
commitList.addUncommittedChangesGraphCommit(logList.first())
commitList.source(walk)
commitList.fillTo(commitsLimit)
}
ensureActive()
}
commitList.calcMaxLine()
return@withContext commitList
val logList = git.log().setMaxCount(1).call().toList()
return@withContext generateLogWalkUseCase(git, logList.first())
// val commitList = GraphCommitList()
// val repositoryState = git.repository.repositoryState
//
// if (currentBranch != null || repositoryState.isRebasing) { // Current branch is null when there is no log (new repo) or rebasing
// val logList = git.log().setMaxCount(1).call().toList()
//
// val walk = GraphWalk(git.repository)
//
// walk.use {
// // Without this, during rebase conflicts the graph won't show the HEAD commits (new commits created
// // by the rebase)
// walk.markStart(walk.lookupCommit(logList.first()))
//
// walk.markStartAllRefs(Constants.R_HEADS)
// walk.markStartAllRefs(Constants.R_REMOTES)
// walk.markStartAllRefs(Constants.R_TAGS)
//// walk.markStartAllRefs(Constants.R_STASH)
//
// val stashes = getStashListUseCase(git)
// walk.markStartFromStashes(stashes)
//
// if (hasUncommittedChanges)
// commitList.addUncommittedChangesGraphCommit(logList.first())
//
// commitList.source(walk)
// commitList.fillTo(commitsLimit)
// }
//
// ensureActive()
//
// }
//
// commitList.calcMaxLine()
//
// return@withContext commitList
}
private fun cachedGraphWalk(repository: Repository): GraphWalk {

View file

@ -39,7 +39,9 @@ import androidx.compose.ui.unit.dp
import com.jetpackduba.gitnuro.AppIcons
import com.jetpackduba.gitnuro.extensions.*
import com.jetpackduba.gitnuro.git.graph.GraphCommitList
import com.jetpackduba.gitnuro.git.graph.GraphCommitList2
import com.jetpackduba.gitnuro.git.graph.GraphNode
import com.jetpackduba.gitnuro.git.graph.GraphNode2
import com.jetpackduba.gitnuro.git.workspace.StatusSummary
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
import com.jetpackduba.gitnuro.keybindings.matchesBinding
@ -145,18 +147,18 @@ private fun LogLoaded(
null
}
LaunchedEffect(verticalScrollState, commitList) {
launch {
logViewModel.focusCommit.collect { commit ->
scrollToCommit(verticalScrollState, commitList, commit)
}
}
launch {
logViewModel.scrollToUncommittedChanges.collect {
scrollToUncommittedChanges(verticalScrollState, commitList)
}
}
}
// LaunchedEffect(verticalScrollState, commitList) {
// launch {
// logViewModel.focusCommit.collect { commit ->
// scrollToCommit(verticalScrollState, commitList, commit)
// }
// }
// launch {
// logViewModel.scrollToUncommittedChanges.collect {
// scrollToUncommittedChanges(verticalScrollState, commitList)
// }
// }
// }
LogDialogs(
logViewModel,
@ -429,12 +431,12 @@ fun SearchFilter(
fun CommitsList(
scrollState: LazyListState,
hasUncommittedChanges: Boolean,
searchFilter: List<GraphNode>?,
searchFilter: List<GraphNode2>?,
selectedCommit: RevCommit?,
logStatus: LogStatus.Loaded,
repositoryState: RepositoryState,
selectedItem: SelectedItem,
commitList: GraphCommitList,
commitList: GraphCommitList2,
logViewModel: LogViewModel,
commitsLimit: Int,
onMerge: (Ref) -> Unit,
@ -485,19 +487,19 @@ fun CommitsList(
graphNode = graphNode,
isSelected = selectedCommit?.name == graphNode.name,
currentBranch = logStatus.currentBranch,
matchesSearchFilter = searchFilter?.contains(graphNode),
matchesSearchFilter = false, //searchFilter?.contains(graphNode),
horizontalScrollState = horizontalScrollState,
showCreateNewBranch = { onShowLogDialog(LogDialog.NewBranch(graphNode)) },
showCreateNewTag = { onShowLogDialog(LogDialog.NewTag(graphNode)) },
resetBranch = { onShowLogDialog(LogDialog.ResetBranch(graphNode)) },
onMergeBranch = onMerge,
onRebaseBranch = onRebase,
onRebaseInteractive = { logViewModel.rebaseInteractive(graphNode) },
onRebaseInteractive = { /*logViewModel.rebaseInteractive(graphNode)*/ },
onRevCommitSelected = { logViewModel.selectLogLine(graphNode) },
onChangeDefaultUpstreamBranch = { onShowLogDialog(LogDialog.ChangeDefaultBranch(it)) },
onDeleteStash = { logViewModel.deleteStash(graphNode) },
onApplyStash = { logViewModel.applyStash(graphNode) },
onPopStash = { logViewModel.popStash(graphNode) },
onDeleteStash = { /*logViewModel.deleteStash(graphNode)*/ },
onApplyStash = { /*logViewModel.applyStash(graphNode)*/ },
onPopStash = { /*logViewModel.popStash(graphNode)*/ },
)
}
@ -732,7 +734,7 @@ fun SummaryEntry(
private fun CommitLine(
graphWidth: Dp,
logViewModel: LogViewModel,
graphNode: GraphNode,
graphNode: GraphNode2,
isSelected: Boolean,
currentBranch: Ref?,
matchesSearchFilter: Boolean?,
@ -749,7 +751,7 @@ private fun CommitLine(
onChangeDefaultUpstreamBranch: (Ref) -> Unit,
horizontalScrollState: ScrollState,
) {
val isLastCommitOfCurrentBranch = currentBranch?.objectId?.name == graphNode.id.name
val isLastCommitOfCurrentBranch = currentBranch?.objectId?.name == graphNode.name
ContextMenu(
items = {
@ -777,7 +779,7 @@ private fun CommitLine(
modifier = Modifier
.clickable { onRevCommitSelected() }
) {
val nodeColor = colors[graphNode.lane.position % colors.size]
val nodeColor = colors[graphNode.lane/*.position*/ % colors.size]
Box {
Row(
@ -843,7 +845,7 @@ private fun CommitLine(
@Composable
fun CommitMessage(
commit: GraphNode,
commit: GraphNode2,
currentBranch: Ref?,
nodeColor: Color,
matchesSearchFilter: Boolean?,
@ -869,43 +871,43 @@ fun CommitMessage(
modifier = Modifier.padding(start = 16.dp)
) {
if (!commit.isStash) {
commit.refs.sortedWith { ref1, ref2 ->
if (ref1.isSameBranch(currentBranch)) {
-1
} else {
ref1.name.compareTo(ref2.name)
}
}.forEach { ref ->
if (ref.isTag) {
TagChip(
ref = ref,
color = nodeColor,
onCheckoutTag = { onCheckoutRef(ref) },
onDeleteTag = { onDeleteTag(ref) },
)
} else if (ref.isBranch) {
BranchChip(
ref = ref,
color = nodeColor,
currentBranch = currentBranch,
isCurrentBranch = ref.isSameBranch(currentBranch),
onCheckoutBranch = { onCheckoutRef(ref) },
onMergeBranch = { onMergeBranch(ref) },
onDeleteBranch = { onDeleteBranch(ref) },
onDeleteRemoteBranch = { onDeleteRemoteBranch(ref) },
onRebaseBranch = { onRebaseBranch(ref) },
onPullRemoteBranch = { onPullRemoteBranch(ref) },
onPushRemoteBranch = { onPushRemoteBranch(ref) },
onChangeDefaultUpstreamBranch = { onChangeDefaultUpstreamBranch(ref) },
)
}
}
// commit.refs.sortedWith { ref1, ref2 ->
// if (ref1.isSameBranch(currentBranch)) {
// -1
// } else {
// ref1.name.compareTo(ref2.name)
// }
// }.forEach { ref ->
// if (ref.isTag) {
// TagChip(
// ref = ref,
// color = nodeColor,
// onCheckoutTag = { onCheckoutRef(ref) },
// onDeleteTag = { onDeleteTag(ref) },
// )
// } else if (ref.isBranch) {
// BranchChip(
// ref = ref,
// color = nodeColor,
// currentBranch = currentBranch,
// isCurrentBranch = ref.isSameBranch(currentBranch),
// onCheckoutBranch = { onCheckoutRef(ref) },
// onMergeBranch = { onMergeBranch(ref) },
// onDeleteBranch = { onDeleteBranch(ref) },
// onDeleteRemoteBranch = { onDeleteRemoteBranch(ref) },
// onRebaseBranch = { onRebaseBranch(ref) },
// onPullRemoteBranch = { onPullRemoteBranch(ref) },
// onPushRemoteBranch = { onPushRemoteBranch(ref) },
// onChangeDefaultUpstreamBranch = { onChangeDefaultUpstreamBranch(ref) },
// )
// }
// }
}
}
val message = remember(commit.id.name) {
val message = commit.message /*remember(commit.id.name) {
commit.getShortMessageTrimmed()
}
}*/
Text(
text = message,
@ -957,12 +959,12 @@ fun SimpleDividerLog(modifier: Modifier) {
@Composable
fun CommitsGraph(
modifier: Modifier = Modifier,
plotCommit: GraphNode,
plotCommit: GraphNode2,
nodeColor: Color,
isSelected: Boolean,
) {
val passingLanes = plotCommit.passingLanes
val forkingOffLanes = plotCommit.forkingOffLanes
val forkingOffLanes = plotCommit.forkingLanes
val mergingLanes = plotCommit.mergingLanes
val density = LocalDensity.current.density
val laneWidthWithDensity = remember(density) {
@ -976,34 +978,34 @@ fun CommitsGraph(
contentAlignment = Alignment.CenterStart,
) {
val itemPosition = plotCommit.lane.position
val itemPosition = plotCommit.lane
Canvas(
modifier = Modifier.fillMaxSize()
) {
clipRect {
if (plotCommit.childCount > 0) {
drawLine(
color = colors[itemPosition % colors.size],
start = Offset(laneWidthWithDensity * (itemPosition + 1), this.center.y),
end = Offset(laneWidthWithDensity * (itemPosition + 1), 0f),
strokeWidth = 2f,
)
}
// if (plotCommit.childCount > 0) {
// drawLine(
// color = colors[itemPosition % colors.size],
// start = Offset(laneWidthWithDensity * (itemPosition + 1), this.center.y),
// end = Offset(laneWidthWithDensity * (itemPosition + 1), 0f),
// strokeWidth = 2f,
// )
// }
forkingOffLanes.forEach { plotLane ->
drawLine(
color = colors[plotLane.position % colors.size],
color = colors[plotLane % colors.size],
start = Offset(laneWidthWithDensity * (itemPosition + 1), this.center.y),
end = Offset(laneWidthWithDensity * (plotLane.position + 1), 0f),
end = Offset(laneWidthWithDensity * (plotLane + 1), 0f),
strokeWidth = 2f,
)
}
mergingLanes.forEach { plotLane ->
drawLine(
color = colors[plotLane.position % colors.size],
start = Offset(laneWidthWithDensity * (plotLane.position + 1), this.size.height),
color = colors[plotLane % colors.size],
start = Offset(laneWidthWithDensity * (plotLane + 1), this.size.height),
end = Offset(laneWidthWithDensity * (itemPosition + 1), this.center.y),
strokeWidth = 2f,
)
@ -1020,9 +1022,9 @@ fun CommitsGraph(
passingLanes.forEach { plotLane ->
drawLine(
color = colors[plotLane.position % colors.size],
start = Offset(laneWidthWithDensity * (plotLane.position + 1), 0f),
end = Offset(laneWidthWithDensity * (plotLane.position + 1), this.size.height),
color = colors[plotLane % colors.size],
start = Offset(laneWidthWithDensity * (plotLane + 1), 0f),
end = Offset(laneWidthWithDensity * (plotLane + 1), this.size.height),
strokeWidth = 2f,
)
}
@ -1042,7 +1044,7 @@ fun CommitsGraph(
@Composable
fun CommitNode(
modifier: Modifier = Modifier,
plotCommit: GraphNode,
plotCommit: GraphNode2,
color: Color,
) {
val author = plotCommit.authorIdent

View file

@ -1,12 +1,13 @@
package com.jetpackduba.gitnuro.ui.log
import com.jetpackduba.gitnuro.git.graph.GraphNode
import com.jetpackduba.gitnuro.git.graph.GraphNode2
import org.eclipse.jgit.lib.Ref
sealed interface LogDialog {
data object None : LogDialog
data class NewBranch(val graphNode: GraphNode) : LogDialog
data class NewTag(val graphNode: GraphNode) : LogDialog
data class ResetBranch(val graphNode: GraphNode) : LogDialog
data class NewBranch(val graphNode: GraphNode2) : LogDialog
data class NewTag(val graphNode: GraphNode2) : LogDialog
data class ResetBranch(val graphNode: GraphNode2) : LogDialog
data class ChangeDefaultBranch(val ref: Ref) : LogDialog
}

View file

@ -10,7 +10,9 @@ import com.jetpackduba.gitnuro.git.TabState
import com.jetpackduba.gitnuro.git.TaskEvent
import com.jetpackduba.gitnuro.git.branches.*
import com.jetpackduba.gitnuro.git.graph.GraphCommitList
import com.jetpackduba.gitnuro.git.graph.GraphCommitList2
import com.jetpackduba.gitnuro.git.graph.GraphNode
import com.jetpackduba.gitnuro.git.graph.GraphNode2
import com.jetpackduba.gitnuro.git.log.*
import com.jetpackduba.gitnuro.git.rebase.RebaseBranchUseCase
import com.jetpackduba.gitnuro.git.rebase.StartRebaseInteractiveUseCase
@ -30,6 +32,7 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.errors.CheckoutConflictException
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject
@ -189,29 +192,29 @@ class LogViewModel @Inject constructor(
)
}
fun checkoutCommit(revCommit: RevCommit) = tabState.safeProcessing(
fun checkoutCommit(revCommit: GraphNode2) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
title = "Commit checkout",
subtitle = "Checking out commit ${revCommit.name}",
) { git ->
checkoutCommitUseCase(git, revCommit)
// checkoutCommitUseCase(git, revCommit)
}
fun revertCommit(revCommit: RevCommit) = tabState.safeProcessing(
fun revertCommit(revCommit: GraphNode2) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
title = "Commit revert",
subtitle = "Reverting commit ${revCommit.name}",
refreshEvenIfCrashes = true,
) { git ->
revertCommitUseCase(git, revCommit)
// revertCommitUseCase(git, revCommit)
}
fun resetToCommit(revCommit: RevCommit, resetType: ResetType) = tabState.safeProcessing(
fun resetToCommit(revCommit: GraphNode2, resetType: ResetType) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
title = "Branch reset",
subtitle = "Reseting branch to commit ${revCommit.shortName}",
// subtitle = "Reseting branch to commit ${revCommit.shortName}",
) { git ->
resetToCommitUseCase(git, revCommit, resetType = resetType)
// resetToCommitUseCase(git, revCommit, resetType = resetType)
}
fun checkoutRef(ref: Ref) = tabState.safeProcessing(
@ -222,29 +225,29 @@ class LogViewModel @Inject constructor(
checkoutRefUseCase(git, ref)
}
fun cherrypickCommit(revCommit: RevCommit) = tabState.safeProcessing(
fun cherrypickCommit(revCommit: GraphNode2) = tabState.safeProcessing(
refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG,
title = "Cherry-pick",
subtitle = "Cherry-picking commit ${revCommit.shortName}",
// subtitle = "Cherry-picking commit ${revCommit.shortName}",
) { git ->
cherryPickCommitUseCase(git, revCommit)
// cherryPickCommitUseCase(git, revCommit)
}
fun createBranchOnCommit(branch: String, revCommit: RevCommit) = tabState.safeProcessing(
fun createBranchOnCommit(branch: String, revCommit: GraphNode2) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
title = "New branch",
subtitle = "Creating new branch \"$branch\" on commit ${revCommit.shortName}",
// subtitle = "Creating new branch \"$branch\" on commit ${revCommit.shortName}",
refreshEvenIfCrashesInteractive = { it is CheckoutConflictException },
) { git ->
createBranchOnCommitUseCase(git, branch, revCommit)
// createBranchOnCommitUseCase(git, branch, revCommit)
}
fun createTagOnCommit(tag: String, revCommit: RevCommit) = tabState.safeProcessing(
fun createTagOnCommit(tag: String, revCommit: GraphNode2) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
title = "New tag",
subtitle = "Creating new tag \"$tag\" on commit ${revCommit.shortName}",
// subtitle = "Creating new tag \"$tag\" on commit ${revCommit.shortName}",
) { git ->
createTagOnCommitUseCase(git, tag, revCommit)
// createTagOnCommitUseCase(git, tag, revCommit)
}
fun mergeBranch(ref: Ref) = tabState.safeProcessing(
@ -331,10 +334,12 @@ class LogViewModel @Inject constructor(
NONE_MATCHING_INDEX
}
fun selectLogLine(commit: GraphNode) = tabState.runOperation(
fun selectLogLine(commit: GraphNode2) = tabState.runOperation(
refreshType = RefreshType.NONE,
) {
tabState.newSelectedCommit(commit)
) { git ->
val oid = ObjectId.fromString(commit.name)
tabState.newSelectedCommit(git.repository.parseCommit(oid))
println("Commit SHA: ${commit.name}")
val searchValue = _logSearchFilterResults.value
if (searchValue is LogSearch.SearchResults) {
@ -371,7 +376,7 @@ class LogViewModel @Inject constructor(
var startingUiIndex = NONE_MATCHING_INDEX
if (matchingCommits.isNotEmpty()) {
_focusCommit.emit(matchingCommits.first())
// TODO _focusCommit.emit(matchingCommits.first())
startingUiIndex = FIRST_INDEX
}
@ -397,7 +402,7 @@ class LogViewModel @Inject constructor(
val newCommitToSelect = commits[newIndex - 1]
_logSearchFilterResults.value = logSearchFilterResultsValue.copy(index = newIndex)
_focusCommit.emit(newCommitToSelect)
//TODO _focusCommit.emit(newCommitToSelect)
}
suspend fun selectNextFilterCommit() {
@ -419,7 +424,7 @@ class LogViewModel @Inject constructor(
val newCommitToSelect = commits[index]
_logSearchFilterResults.value = logSearchFilterResultsValue.copy(index = newIndex)
_focusCommit.emit(newCommitToSelect)
// TODO _focusCommit.emit(newCommitToSelect)
}
fun showDialog(dialog: LogDialog) {
@ -449,7 +454,7 @@ sealed interface LogStatus {
data object Loading : LogStatus
class Loaded(
val hasUncommittedChanges: Boolean,
val plotCommitList: GraphCommitList,
val plotCommitList: GraphCommitList2,
val currentBranch: Ref?,
val statusSummary: StatusSummary,
val commitsLimit: Int,
@ -459,7 +464,7 @@ sealed interface LogStatus {
sealed interface LogSearch {
data object NotSearching : LogSearch
data class SearchResults(
val commits: List<GraphNode>,
val commits: List<GraphNode2>,
val index: Int,
val totalCount: Int = commits.count(),
) : LogSearch

View file

@ -0,0 +1,105 @@
package com.jetpackduba.gitnuro.git.graph
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*
class GenerateLogWalkUseCaseTest {
@Test
fun getReservedLane() {
val generateLogWalkUseCase = GenerateLogWalkUseCase()
val reservedLanes = mapOf(
0 to "*",
1 to "A",
2 to "B",
3 to "C",
4 to "D",
5 to "E",
6 to "F",
)
val reservedLane = generateLogWalkUseCase.getReservedLanes(reservedLanes, "A")
assertEquals(listOf(1), reservedLane)
}
@Test
fun getReservedLane_when_value_not_present() {
val generateLogWalkUseCase = GenerateLogWalkUseCase()
val reservedLanes = mapOf(
// 0 to "*",
1 to "A",
2 to "B",
3 to "C",
4 to "D",
5 to "E",
6 to "F",
)
val reservedLane = generateLogWalkUseCase.getReservedLanes(reservedLanes, "P")
assertEquals(listOf(0), reservedLane)
}
@Test
fun firstAvailableLane_without_first_item() {
val generateLogWalkUseCase = GenerateLogWalkUseCase()
val reservedLanes = mapOf(
// 0 to "*",
1 to "A",
2 to "B",
3 to "C",
// 4 to "D",
5 to "E",
6 to "F",
)
val firstAvailableLane = generateLogWalkUseCase.firstAvailableLane(reservedLanes)
assertEquals(0, firstAvailableLane)
}
@Test
fun firstAvailableLane_without_middle_item() {
val generateLogWalkUseCase = GenerateLogWalkUseCase()
val reservedLanes = mapOf(
0 to "*",
1 to "A",
2 to "B",
3 to "C",
// 4 to "D",
5 to "E",
6 to "F",
)
val firstAvailableLane = generateLogWalkUseCase.firstAvailableLane(reservedLanes)
assertEquals(4, firstAvailableLane)
}
@Test
fun firstAvailableLane_with_empty_reserved_lanes() {
val generateLogWalkUseCase = GenerateLogWalkUseCase()
val reservedLanes = mapOf<Int, String>()
val firstAvailableLane = generateLogWalkUseCase.firstAvailableLane(reservedLanes)
assertEquals(0, firstAvailableLane)
}
@Test
fun firstAvailableLane_without_single_non_zero() {
val generateLogWalkUseCase = GenerateLogWalkUseCase()
val reservedLanes = mapOf(
1 to "A",
)
val firstAvailableLane = generateLogWalkUseCase.firstAvailableLane(reservedLanes)
assertEquals(0, firstAvailableLane)
}
@Test
fun firstAvailableLane_without_2_keys_non_zero() {
val generateLogWalkUseCase = GenerateLogWalkUseCase()
val reservedLanes = mapOf(
1 to "A",
2 to "B",
)
val firstAvailableLane = generateLogWalkUseCase.firstAvailableLane(reservedLanes)
assertEquals(0, firstAvailableLane)
}
}