diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GenerateLogWalkUseCase.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GenerateLogWalkUseCase.kt index f6e3daf..018363e 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GenerateLogWalkUseCase.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GenerateLogWalkUseCase.kt @@ -1,15 +1,10 @@ package com.jetpackduba.gitnuro.git.graph -import com.jetpackduba.gitnuro.extensions.isTag import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.eclipse.jgit.api.Git -import org.eclipse.jgit.lib.Constants import org.eclipse.jgit.lib.Ref -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 javax.inject.Inject @@ -19,9 +14,12 @@ class GenerateLogWalkUseCase @Inject constructor() { firstCommit: RevCommit, allRefs: List, stashes: List, + hasUncommittedChanges: Boolean, + commitsLimit: Int?, ): GraphCommitList2 = withContext(Dispatchers.IO) { val reservedLanes = mutableMapOf() val graphNodes = mutableListOf() + var maxLane = 0 val availableCommitsToAdd = mutableMapOf() @@ -42,21 +40,21 @@ class GenerateLogWalkUseCase @Inject constructor() { availableCommitsToAdd.putAll(commitsOfStashes) var currentCommit = getNextCommit(availableCommitsToAdd.values.toList()) + + if (hasUncommittedChanges) { + reservedLanes[0] = firstCommit.name + } + availableCommitsToAdd.remove(currentCommit?.name) - while (currentCommit != null) { + while (currentCommit != null && (commitsLimit == null || graphNodes.count() <= commitsLimit)) { val lanes = getReservedLanes(reservedLanes, currentCommit.name) val lane = lanes.first() val forkingLanes = lanes - lane val isStash = stashes.any { it == currentCommit } - val parents = sortParentsByPriority(git, currentCommit).run { - if(isStash) { - filterNot { it.shortMessage.startsWith("index on") } - } else { - this - } - } + val parents = sortParentsByPriority(git, currentCommit) + .filterStashParentsIfRequired(isStash) val parentsCount = parents.count() val mergingLanes = mutableListOf() @@ -72,24 +70,22 @@ class GenerateLogWalkUseCase @Inject constructor() { } } - val currentCommitName = currentCommit.name val refs = refsByCommit(refsWithCommits, currentCommit) - val graphNode = GraphNode2( - currentCommitName, - currentCommit.shortMessage, - currentCommit.fullMessage, - currentCommit.authorIdent, - currentCommit.committerIdent, - currentCommit.parentCount, + val graphNode = createGraphNode( + currentCommit = currentCommit, isStash = isStash, lane = lane, forkingLanes = forkingLanes, - passingLanes = reservedLanes.keys.toList() - mergingLanes.toSet() - forkingLanes.toSet(), + reservedLanes = reservedLanes, mergingLanes = mergingLanes, - refs = refs, + refs = refs ) + if (lane > maxLane) { + maxLane = lane + } + graphNodes.add(graphNode) removeFromAllLanes(reservedLanes, graphNode.name) @@ -99,9 +95,32 @@ class GenerateLogWalkUseCase @Inject constructor() { availableCommitsToAdd.remove(currentCommit?.name) } - GraphCommitList2(graphNodes) + GraphCommitList2(graphNodes, maxLane) } + private fun createGraphNode( + currentCommit: RevCommit, + isStash: Boolean, + lane: Int, + forkingLanes: List, + reservedLanes: MutableMap, + mergingLanes: MutableList, + refs: List + ) = GraphNode2( + currentCommit.name, + currentCommit.shortMessage, + currentCommit.fullMessage, + currentCommit.authorIdent, + currentCommit.committerIdent, + currentCommit.parentCount, + isStash = isStash, + lane = lane, + forkingLanes = forkingLanes, + passingLanes = reservedLanes.keys.toList() - mergingLanes.toSet() - forkingLanes.toSet(), + mergingLanes = mergingLanes, + refs = refs, + ) + private fun refsByCommit( refsWithCommits: List>, commit: RevCommit @@ -128,47 +147,18 @@ class GenerateLogWalkUseCase @Inject constructor() { } else { parents.sortedBy { it.committerIdent.`when` } } - -// parents.sortedWith { o1, o2 -> -// TODO("Not yet implemented") -// } -// val parentsWithPriority = mapOf() -// -// -// -// 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 List.filterStashParentsIfRequired(isStash: Boolean): List { + return if (isStash) { + filterNot { + it.shortMessage.startsWith("index on") || + it.shortMessage.startsWith("untracked files on") + } + } else { + this } } -// fun getShortestParent(parent1: RevCommit, parent2: RevCommit) { -// val logParent1 = mutableListOf(parent1.name) -// val logParent2 = mutableListOf(parent2.name) -// -// var newParent1: RevCommit? = parent1 -// var newParent2: RevCommit? = parent2 -// -// while (newParent1 != null && newParent2 != null) { -// newParent1.pa -// } -// } fun getNextCommit(availableCommits: List): RevCommit? { return availableCommits.sortedByDescending { it.committerIdent.`when` }.firstOrNull() @@ -222,4 +212,11 @@ class GenerateLogWalkUseCase @Inject constructor() { .firstOrNull() ?: (sortedKeys.max() + 1) } } +} + +sealed interface ReservationType { + val hash: String + + class ParentInSameLane(override val hash: String): ReservationType + class ParentInVariableLane(override val hash: String): ReservationType } \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphCommitList.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphCommitList.kt deleted file mode 100644 index 8cbb8b2..0000000 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphCommitList.kt +++ /dev/null @@ -1,366 +0,0 @@ -package com.jetpackduba.gitnuro.git.graph - -import org.eclipse.jgit.internal.JGitText -import org.eclipse.jgit.lib.AnyObjectId -import org.eclipse.jgit.revwalk.RevCommit -import org.eclipse.jgit.revwalk.RevCommitList -import org.eclipse.jgit.revwalk.RevWalk -import java.text.MessageFormat -import java.util.* - -/** - * An ordered list of [GraphNode] subclasses. - * - * - * Commits are allocated into lanes as they enter the list, based upon their - * connections between descendant (child) commits and ancestor (parent) commits. - * - * - * The source of the list must be a [GraphWalk] - * and [.fillTo] must be used to populate the list. - * - * type of lane used by the application. - */ -class GraphCommitList : RevCommitList() { - private var positionsAllocated = 0 - private val freePositions = TreeSet() - private val activeLanes = HashSet(32) - - var maxLine = 0 - private set - - /** number of (child) commits on a lane */ - private val laneLength = HashMap( - 32 - ) - - override fun clear() { - super.clear() - positionsAllocated = 0 - freePositions.clear() - activeLanes.clear() - laneLength.clear() - } - - override fun source(revWalk: RevWalk) { - if (revWalk !is GraphWalk) throw ClassCastException( - MessageFormat.format( - JGitText.get().classCastNotA, - GraphWalk::class.java.name - ) - ) - - super.source(revWalk) - } - - private var parentId: AnyObjectId? = null - - private val graphCommit = UncommittedChangesGraphNode() - - fun addUncommittedChangesGraphCommit(parent: RevCommit) { - parentId = parent.id - graphCommit.lane = nextFreeLane() - } - - override fun enter(index: Int, currCommit: GraphNode) { - var isUncommittedChangesNodeParent = false - if (currCommit.id == parentId) { - graphCommit.graphParent = currCommit - currCommit.addChild(graphCommit, addFirst = true) - isUncommittedChangesNodeParent = true - } - - setupChildren(currCommit) - val nChildren = currCommit.childCount - if (nChildren == 0) { - currCommit.lane = nextFreeLane() - } else if (nChildren == 1 - && currCommit.children[0].graphParentCount < 2 - ) { - // Only one child, child has only us as their parent. - // Stay in the same lane as the child. - val graphNode: GraphNode = currCommit.children[0] - currCommit.lane = graphNode.lane - var len = laneLength[currCommit.lane] - len = if (len != null) Integer.valueOf(len.toInt() + 1) else Integer.valueOf(0) - - if (currCommit.lane.position != INVALID_LANE_POSITION) - laneLength[currCommit.lane] = len - } else { - // We look for the child lane the current commit should continue. - // Candidate lanes for this are those with children, that have the - // current commit as their first parent. - // There can be multiple candidate lanes. In that case the longest - // lane is chosen, as this is usually the lane representing the - // branch the commit actually was made on. - - // When there are no candidate lanes (i.e. the current commit has - // only children whose non-first parent it is) we place the current - // commit on a new lane. - - // The lane the current commit will be placed on: - var reservedLane: GraphLane? = null - var childOnReservedLane: GraphNode? = null - var lengthOfReservedLane = -1 - - - if (isUncommittedChangesNodeParent) { - val length = laneLength[graphCommit.lane] - if (length != null) { - reservedLane = graphCommit.lane - childOnReservedLane = graphCommit - lengthOfReservedLane = length - } - } else { - val children = currCommit.children.sortedBy { it.lane.position } - for (i in 0 until nChildren) { - val c: GraphNode = children[i] - if (c.getGraphParent(0) === currCommit) { - if (c.lane.position < 0) - println("c.lane.position is invalid (${c.lane.position})") - - val length = laneLength[c.lane] - - // we may be the first parent for multiple lines of - // development, try to continue the longest one - if (length != null && length > lengthOfReservedLane) { - reservedLane = c.lane - childOnReservedLane = c - lengthOfReservedLane = length - - break - } - } - } - } - if (reservedLane != null) { - currCommit.lane = reservedLane - laneLength[reservedLane] = Integer.valueOf(lengthOfReservedLane + 1) - handleBlockedLanes(index, currCommit, childOnReservedLane) - } else { - currCommit.lane = nextFreeLane() - handleBlockedLanes(index, currCommit, null) - } - - // close lanes of children, if there are no first parents that might - // want to continue the child lanes - for (i in 0 until nChildren) { - val graphNode = currCommit.children[i] - - val firstParent = graphNode.getGraphParent(0) - - if (firstParent.lane.position != INVALID_LANE_POSITION && firstParent.lane !== graphNode.lane) - closeLane(graphNode.lane) - } - } - - continueActiveLanes(currCommit) - - if (currCommit.parentCount == 0 && currCommit.lane.position == INVALID_LANE_POSITION) - closeLane(currCommit.lane) - - } - - private fun continueActiveLanes(currCommit: GraphNode) { - for (lane in activeLanes) { - if (lane !== currCommit.lane) { - currCommit.addPassingLane(lane) - } - } - } - - /** - * Sets up fork and merge information in the involved PlotCommits. - * Recognizes and handles blockades that involve forking or merging arcs. - * - * @param index - * the index of `currCommit` in the list - * @param currentNode - * @param childOnLane - * the direct child on the same lane as `currCommit`, - * may be null if `currCommit` is the first commit on - * the lane - */ - private fun handleBlockedLanes( - index: Int, currentNode: GraphNode, - childOnLane: GraphNode? - ) { - for (child in currentNode.children) { - if (child === childOnLane) continue // simple continuations of lanes are handled by - // continueActiveLanes() calls in enter() - - // Is the child a merge or is it forking off? - val childIsMerge = child.getGraphParent(0) !== currentNode - if (childIsMerge) { - var laneToUse = currentNode.lane - laneToUse = handleMerge( - index, currentNode, childOnLane, child, - laneToUse - ) - child.addMergingLane(laneToUse) - } else { - // We want to draw a forking arc in the child's lane. - // As an active lane, the child lane already continues - // (unblocked) up to this commit, we only need to mark it as - // forking off from the current commit. - val laneToUse = child.lane - currentNode.addForkingOffLane(laneToUse) - } - } - } - - // Handles the case where currCommit is a non-first parent of the child - private fun handleMerge( - index: Int, currCommit: GraphNode, - childOnLane: GraphNode?, child: GraphNode, laneToUse: GraphLane - ): GraphLane { - - // find all blocked positions between currCommit and this child - var newLaneToUse = laneToUse - var childIndex = index // useless initialization, should - // always be set in the loop below - val blockedPositions = BitSet() - for (r in index - 1 downTo 0) { - val graphNode: GraphNode? = get(r) - if (graphNode === child) { - childIndex = r - break - } - addBlockedPosition(blockedPositions, graphNode) - } - - // handle blockades - if (blockedPositions[newLaneToUse.position]) { - // We want to draw a merging arc in our lane to the child, - // which is on another lane, but our lane is blocked. - - // Check if childOnLane is beetween commit and the child we - // are currently processing - var needDetour = false - if (childOnLane != null) { - for (r in index - 1 downTo childIndex + 1) { - val graphNode: GraphNode? = get(r) - if (graphNode === childOnLane) { - needDetour = true - break - } - } - } - if (needDetour) { - // It is childOnLane which is blocking us. Repositioning - // our lane would not help, because this repositions the - // child too, keeping the blockade. - // Instead, we create a "detour lane" which gets us - // around the blockade. That lane has no commits on it. - newLaneToUse = nextFreeLane(blockedPositions) - currCommit.addForkingOffLane(newLaneToUse) - closeLane(newLaneToUse) - } else { - // The blockade is (only) due to other (already closed) - // lanes at the current lane's position. In this case we - // reposition the current lane. - // We are the first commit on this lane, because - // otherwise the child commit on this lane would have - // kept other lanes from blocking us. Since we are the - // first commit, we can freely reposition. - val newPos = getFreePosition(blockedPositions) - freePositions.add(newLaneToUse.position) - - newLaneToUse.position = newPos - } - } - - // Actually connect currCommit to the merge child - drawLaneToChild(index, child, newLaneToUse) - return newLaneToUse - } - - /** - * Connects the commit at commitIndex to the child, using the given lane. - * All blockades on the lane must be resolved before calling this method. - * - * @param commitIndex - * @param child - * @param laneToContinue - */ - private fun drawLaneToChild( - commitIndex: Int, child: GraphNode, - laneToContinue: GraphLane - ) { - for (index in commitIndex - 1 downTo 0) { - val graphNode: GraphNode? = get(index) - if (graphNode === child) break - graphNode?.addPassingLane(laneToContinue) - } - } - - private fun closeLane(lane: GraphLane) { - if (activeLanes.remove(lane)) { - laneLength.remove(lane) - freePositions.add(Integer.valueOf(lane.position)) - } - } - - private fun setupChildren(currCommit: GraphNode) { - val nParents = currCommit.parentCount - for (i in 0 until nParents) (currCommit.getParent(i) as GraphNode).addChild(currCommit) - } - - private fun nextFreeLane(blockedPositions: BitSet? = null): GraphLane { - val newPlotLane = GraphLane(position = getFreePosition(blockedPositions)) - activeLanes.add(newPlotLane) - laneLength[newPlotLane] = Integer.valueOf(1) - - return newPlotLane - } - - /** - * @param blockedPositions - * may be null - * @return a free lane position - */ - private fun getFreePosition(blockedPositions: BitSet?): Int { - if (freePositions.isEmpty()) return positionsAllocated++ - if (blockedPositions != null) { - for (pos in freePositions) if (!blockedPositions[pos]) { - freePositions.remove(pos) - return pos - } - return positionsAllocated++ - } - val min = freePositions.first() - freePositions.remove(min) - return min.toInt() - } - - private fun addBlockedPosition( - blockedPositions: BitSet, - graphNode: GraphNode? - ) { - if (graphNode != null) { - val lane = graphNode.lane - - // Positions may be blocked by a commit on a lane. - if (lane.position != INVALID_LANE_POSITION) { - blockedPositions.set(lane.position) - } - - // Positions may also be blocked by forking off and merging lanes. - // We don't consider passing lanes, because every passing lane forks - // off and merges at it ends. - for (graphLane in graphNode.forkingOffLanes) { - blockedPositions.set(graphLane.position) - } - - for (graphLane in graphNode.mergingLanes) { - blockedPositions.set(graphLane.position) - } - } - } - - fun calcMaxLine() { - if (this.isNotEmpty()) { - maxLine = this.maxOf { it.lane.position } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphCommitList2.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphCommitList2.kt index 5d0c1fc..606d029 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphCommitList2.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphCommitList2.kt @@ -1,8 +1,6 @@ package com.jetpackduba.gitnuro.git.graph -class GraphCommitList2(val nodes: List) : List by nodes { - var maxLine = 0 - private set - - -} \ No newline at end of file +data class GraphCommitList2( + val nodes: List, + val maxLane: Int +) : List by nodes \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphLane.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphLane.kt deleted file mode 100644 index 724909e..0000000 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphLane.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.jetpackduba.gitnuro.git.graph - -const val INVALID_LANE_POSITION = -1 - -class GraphLane(var position: Int = 0) \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphNode.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphNode.kt deleted file mode 100644 index 9ad79fc..0000000 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphNode.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.jetpackduba.gitnuro.git.graph - -import org.eclipse.jgit.lib.AnyObjectId -import org.eclipse.jgit.lib.Ref -import org.eclipse.jgit.revwalk.RevCommit - -private val NO_CHILDREN = arrayOf() -private val NO_LANES = arrayOf() -private val NO_LANE = GraphLane(INVALID_LANE_POSITION) -val NO_REFS = listOf() - -open class GraphNode(id: AnyObjectId?) : RevCommit(id), IGraphNode { - var forkingOffLanes: Array = NO_LANES - var passingLanes: Array = NO_LANES - var mergingLanes: Array = NO_LANES - var lane: GraphLane = NO_LANE - var children: Array = NO_CHILDREN - var refs: List = NO_REFS - var isStash: Boolean = false - - fun addForkingOffLane(graphLane: GraphLane) { - forkingOffLanes = addLane(graphLane, forkingOffLanes) - } - - fun addPassingLane(graphLane: GraphLane) { - passingLanes = addLane(graphLane, passingLanes) - } - - fun addMergingLane(graphLane: GraphLane) { - mergingLanes = addLane(graphLane, mergingLanes) - } - - fun addChild(c: GraphNode, addFirst: Boolean = false) { - when (val childrenCount = children.count()) { - 0 -> children = arrayOf(c) - 1 -> { - if (!c.id.equals(children[0].id)) { - children = if (addFirst) { - arrayOf(c, children[0]) - } else - arrayOf(children[0], c) - } - } - - else -> { - for (pc in children) - if (c.id.equals(pc.id)) - return - - val resultArray = if (addFirst) { - val childList = mutableListOf(c) - childList.addAll(children) - childList.toTypedArray() - } else { - children.copyOf(childrenCount + 1).run { - this[childrenCount] = c - requireNoNulls() - } - } - - children = resultArray - } - } - } - - val childCount: Int - get() { - return children.size - } - - override fun reset() { - forkingOffLanes = NO_LANES - passingLanes = NO_LANES - mergingLanes = NO_LANES - children = NO_CHILDREN - lane = NO_LANE - super.reset() - } - - private fun addLane(graphLane: GraphLane, lanes: Array): Array { - var newLines = lanes - - when (val linesCount = newLines.count()) { - 0 -> newLines = arrayOf(graphLane) - 1 -> newLines = arrayOf(newLines[0], graphLane) - else -> { - val n = newLines.copyOf(linesCount + 1).run { - this[linesCount] = graphLane - requireNoNulls() - } - - newLines = n - } - } - - return newLines - } - - override val graphParentCount: Int - get() = parentCount - - override fun getGraphParent(nth: Int): GraphNode { - return getParent(nth) as GraphNode - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphWalk.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphWalk.kt deleted file mode 100644 index f84f317..0000000 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/GraphWalk.kt +++ /dev/null @@ -1,191 +0,0 @@ -package com.jetpackduba.gitnuro.git.graph - -import org.eclipse.jgit.errors.MissingObjectException -import org.eclipse.jgit.internal.JGitText -import org.eclipse.jgit.lib.AnyObjectId -import org.eclipse.jgit.lib.Constants -import org.eclipse.jgit.lib.Ref -import org.eclipse.jgit.lib.Repository -import org.eclipse.jgit.revwalk.* -import java.io.IOException - -/** - * Specialized RevWalk to be used for load data in a structured way to be displayed in a graph of commits. - */ -class GraphWalk(private var repository: Repository?) : RevWalk(repository) { - private var additionalRefMap: MutableMap>? = HashMap() - private var reverseRefMap: MutableMap>? = null - - init { - super.sort(RevSort.TOPO, true) - } - - override fun dispose() { - super.dispose() - if (reverseRefMap != null) { - reverseRefMap?.clear() - reverseRefMap = null - } - if (additionalRefMap != null) { - additionalRefMap?.clear() - additionalRefMap = null - } - repository = null - } - - override fun sort(revSort: RevSort, use: Boolean) { - require(!(revSort == RevSort.TOPO && !use)) { - JGitText.get().topologicalSortRequired - } - - super.sort(revSort, use) - } - - override fun createCommit(id: AnyObjectId): RevCommit { - return GraphNode(id) - } - - fun createCommitFromStash(id: AnyObjectId): RevCommit { - return GraphNode(id).apply { - isStash = true - } - } - - override fun next(): RevCommit? { - val c = super.next() - val graphNode = c as GraphNode? - - if (graphNode != null) { - val refs = getRefs(graphNode) - - graphNode.refs = refs - } - - return graphNode - } - - private fun getRefs(commitId: AnyObjectId): List { - val repository = this.repository - var reverseRefMap = this.reverseRefMap - var additionalRefMap = this.additionalRefMap - if (reverseRefMap == null && repository != null && additionalRefMap != null) { - - reverseRefMap = repository.allRefsByPeeledObjectId - this.reverseRefMap = reverseRefMap - - for (entry in additionalRefMap.entries) { - val refsSet = reverseRefMap[entry.key] - var additional = entry.value.toMutableSet() - - if (refsSet != null) { - if (additional.size == 1) { - // It's an unmodifiable singleton set... - additional = HashSet(additional) - } - additional.addAll(refsSet) - } - reverseRefMap[entry.key] = additional - } - - additionalRefMap.clear() - additionalRefMap = null - - this.additionalRefMap = additionalRefMap - } - - requireNotNull(reverseRefMap) // This should never be null - - val refsSet = reverseRefMap[commitId] - ?: return NO_REFS - val tags = refsSet.toList() - - tags.sortedWith(GraphRefComparator()) - - return tags - } - - fun markStartAllRefs(prefix: String) { - repository?.let { repo -> - for (ref in repo.refDatabase.getRefsByPrefix(prefix)) { - if (ref.isSymbolic) continue - markStartRef(ref) - } - } - } - - fun markStartFromStashes(stashes: List) { - 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. - is RevTag -> { - if (refTarget.`object` is RevCommit) { - val commit = lookupCommit(refTarget.`object`) - markStart(commit) - } else { - println("Tag ${refTarget.tagName} is pointing to ${refTarget.`object`::class.simpleName}") - } - } - } - } catch (e: MissingObjectException) { - // Ignore missing Refs - } - } - - internal inner class GraphRefComparator : Comparator { - override fun compare(o1: Ref, o2: Ref): Int { - try { - val obj1 = parseAny(o1.objectId) - val obj2 = parseAny(o2.objectId) - val t1 = timeOf(obj1) - val t2 = timeOf(obj2) - if (t1 > t2) return -1 - if (t1 < t2) return 1 - } catch (e: IOException) { - // ignore - } - - var cmp = kind(o1) - kind(o2) - - if (cmp == 0) - cmp = o1.name.compareTo(o2.name) - - return cmp - } - - private fun timeOf(revObject: RevObject): Long { - if (revObject is RevCommit) return revObject.commitTime.toLong() - if (revObject is RevTag) { - try { - parseBody(revObject) - } catch (e: IOException) { - return 0 - } - val who = revObject.taggerIdent - return who?.getWhen()?.time ?: 0 - } - return 0 - } - - private fun kind(r: Ref): Int { - if (r.name.startsWith(Constants.R_TAGS)) return 0 - if (r.name.startsWith(Constants.R_HEADS)) return 1 - return if (r.name.startsWith(Constants.R_REMOTES)) 2 else 3 - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/IGraphNode.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/IGraphNode.kt deleted file mode 100644 index f47323b..0000000 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/IGraphNode.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.jetpackduba.gitnuro.git.graph - -interface IGraphNode { - val graphParentCount: Int - fun getGraphParent(nth: Int): GraphNode -} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/UncommittedChangesGraphNode.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/UncommittedChangesGraphNode.kt deleted file mode 100644 index 916abf5..0000000 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/graph/UncommittedChangesGraphNode.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.jetpackduba.gitnuro.git.graph - -import org.eclipse.jgit.lib.ObjectId - -class UncommittedChangesGraphNode : GraphNode(ObjectId(0, 0, 0, 0, 0)) { - - var graphParent: GraphNode? = null - - override val graphParentCount: Int - get() = 1 // Uncommitted changes can have a max of 1 parent commit - - override fun getGraphParent(nth: Int): GraphNode { - return requireNotNull(graphParent) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/log/GetLogUseCase.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/log/GetLogUseCase.kt index 924cfe4..f94cc24 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/log/GetLogUseCase.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/log/GetLogUseCase.kt @@ -2,77 +2,37 @@ 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.graph.GraphCommitList2 import com.jetpackduba.gitnuro.git.stash.GetStashListUseCase import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ensureActive import kotlinx.coroutines.withContext import org.eclipse.jgit.api.Git 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( 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) = + suspend operator fun invoke(git: Git, hasUncommittedChanges: Boolean, commitsLimit: Int?) = withContext(Dispatchers.IO) { val logList = git.log().setMaxCount(1).call().toList() - val allRefs = git.repository.refDatabase.refs.filterNot { it.name.startsWith(Constants.R_STASH) } // remove stash as it only returns the latest, we get all afterward + val firstCommit = logList.firstOrNull() + val allRefs = + git.repository.refDatabase.refs.filterNot { it.name.startsWith(Constants.R_STASH) } // remove stash as it only returns the latest, we get all afterward val stashes = getStashListUseCase(git) - return@withContext generateLogWalkUseCase(git, logList.first(), allRefs, stashes) -// 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 + return@withContext if (firstCommit == null) { + GraphCommitList2(emptyList(), 0) + } else { + generateLogWalkUseCase.invoke( + git, + firstCommit, + allRefs, + stashes, + hasUncommittedChanges, + commitsLimit + ) + } } - - private fun cachedGraphWalk(repository: Repository): GraphWalk { - val graphWalkCached = this.graphWalkCached - - return if (graphWalkCached != null) { - graphWalkCached - } else { - val newGraphWalk = GraphWalk(repository) - this.graphWalkCached = newGraphWalk - - newGraphWalk - } - } } \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt index 265c2bb..ae833fb 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt @@ -38,9 +38,7 @@ import androidx.compose.ui.unit.Dp 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 @@ -147,18 +145,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, @@ -177,7 +175,7 @@ private fun LogLoaded( if (graphWidth.value < CANVAS_MIN_WIDTH) graphWidth = CANVAS_MIN_WIDTH.dp val maxLinePosition = if (commitList.isNotEmpty()) - commitList.maxLine + commitList.maxLane else MIN_GRAPH_LANES @@ -309,7 +307,7 @@ private fun LogLoaded( suspend fun scrollToCommit( verticalScrollState: LazyListState, - commitList: GraphCommitList, + commitList: GraphCommitList2, commit: RevCommit?, ) { val index = commitList.indexOfFirst { it.name == commit?.name } @@ -321,7 +319,7 @@ suspend fun scrollToCommit( suspend fun scrollToUncommittedChanges( verticalScrollState: LazyListState, - commitList: GraphCommitList, + commitList: GraphCommitList2, ) { if (commitList.isNotEmpty()) verticalScrollState.scrollToItem(0) diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/LogDialog.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/LogDialog.kt index 28b9f14..34175e0 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/LogDialog.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/LogDialog.kt @@ -1,6 +1,5 @@ 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 diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt index 16c03bc..9281397 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt @@ -3,15 +3,12 @@ package com.jetpackduba.gitnuro.viewmodels import androidx.compose.foundation.ScrollState import androidx.compose.foundation.lazy.LazyListState import com.jetpackduba.gitnuro.extensions.delayedStateChange -import com.jetpackduba.gitnuro.extensions.shortName import com.jetpackduba.gitnuro.extensions.simpleName import com.jetpackduba.gitnuro.git.RefreshType 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 @@ -150,14 +147,14 @@ class LogViewModel @Inject constructor( val commitsLimit = if (appSettings.commitsLimitEnabled) { appSettings.commitsLimit } else - Int.MAX_VALUE + null val commitsLimitDisplayed = if (appSettings.commitsLimitEnabled) { appSettings.commitsLimit } else -1 - val log = getLogUseCase(git, currentBranch, hasUncommittedChanges, commitsLimit) + val log = getLogUseCase(git, hasUncommittedChanges, commitsLimit) _logStatus.value = LogStatus.Loaded(hasUncommittedChanges, log, currentBranch, statusSummary, commitsLimitDisplayed)