[WIP] Created new implementation of how commits graph is generated
This commit is contained in:
parent
ed86583f58
commit
2c8f8da9d8
10 changed files with 499 additions and 138 deletions
|
@ -101,7 +101,7 @@ kotlin {
|
|||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
kotlinOptions.allWarningsAsErrors = true
|
||||
kotlinOptions.allWarningsAsErrors = false
|
||||
kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
||||
}
|
|
@ -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>,
|
||||
)
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue