Cleanup and changed current branch priority if there are uncommited changes

This commit is contained in:
Abdelilah El Aissaoui 2024-02-01 10:03:21 +01:00
parent 1476295415
commit a7d40638ab
No known key found for this signature in database
GPG key ID: 7587FC860F594869
12 changed files with 96 additions and 835 deletions

View file

@ -1,15 +1,10 @@
package com.jetpackduba.gitnuro.git.graph package com.jetpackduba.gitnuro.git.graph
import com.jetpackduba.gitnuro.extensions.isTag
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.Ref import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.revwalk.RevCommit import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.revwalk.filter.RevFilter
import javax.inject.Inject import javax.inject.Inject
@ -19,9 +14,12 @@ class GenerateLogWalkUseCase @Inject constructor() {
firstCommit: RevCommit, firstCommit: RevCommit,
allRefs: List<Ref>, allRefs: List<Ref>,
stashes: List<RevCommit>, stashes: List<RevCommit>,
hasUncommittedChanges: Boolean,
commitsLimit: Int?,
): GraphCommitList2 = withContext(Dispatchers.IO) { ): GraphCommitList2 = withContext(Dispatchers.IO) {
val reservedLanes = mutableMapOf<Int, String>() val reservedLanes = mutableMapOf<Int, String>()
val graphNodes = mutableListOf<GraphNode2>() val graphNodes = mutableListOf<GraphNode2>()
var maxLane = 0
val availableCommitsToAdd = mutableMapOf<String, RevCommit>() val availableCommitsToAdd = mutableMapOf<String, RevCommit>()
@ -42,21 +40,21 @@ class GenerateLogWalkUseCase @Inject constructor() {
availableCommitsToAdd.putAll(commitsOfStashes) availableCommitsToAdd.putAll(commitsOfStashes)
var currentCommit = getNextCommit(availableCommitsToAdd.values.toList()) var currentCommit = getNextCommit(availableCommitsToAdd.values.toList())
if (hasUncommittedChanges) {
reservedLanes[0] = firstCommit.name
}
availableCommitsToAdd.remove(currentCommit?.name) availableCommitsToAdd.remove(currentCommit?.name)
while (currentCommit != null) { while (currentCommit != null && (commitsLimit == null || graphNodes.count() <= commitsLimit)) {
val lanes = getReservedLanes(reservedLanes, currentCommit.name) val lanes = getReservedLanes(reservedLanes, currentCommit.name)
val lane = lanes.first() val lane = lanes.first()
val forkingLanes = lanes - lane val forkingLanes = lanes - lane
val isStash = stashes.any { it == currentCommit } val isStash = stashes.any { it == currentCommit }
val parents = sortParentsByPriority(git, currentCommit).run { val parents = sortParentsByPriority(git, currentCommit)
if(isStash) { .filterStashParentsIfRequired(isStash)
filterNot { it.shortMessage.startsWith("index on") }
} else {
this
}
}
val parentsCount = parents.count() val parentsCount = parents.count()
val mergingLanes = mutableListOf<Int>() val mergingLanes = mutableListOf<Int>()
@ -72,24 +70,22 @@ class GenerateLogWalkUseCase @Inject constructor() {
} }
} }
val currentCommitName = currentCommit.name
val refs = refsByCommit(refsWithCommits, currentCommit) val refs = refsByCommit(refsWithCommits, currentCommit)
val graphNode = GraphNode2( val graphNode = createGraphNode(
currentCommitName, currentCommit = currentCommit,
currentCommit.shortMessage,
currentCommit.fullMessage,
currentCommit.authorIdent,
currentCommit.committerIdent,
currentCommit.parentCount,
isStash = isStash, isStash = isStash,
lane = lane, lane = lane,
forkingLanes = forkingLanes, forkingLanes = forkingLanes,
passingLanes = reservedLanes.keys.toList() - mergingLanes.toSet() - forkingLanes.toSet(), reservedLanes = reservedLanes,
mergingLanes = mergingLanes, mergingLanes = mergingLanes,
refs = refs, refs = refs
) )
if (lane > maxLane) {
maxLane = lane
}
graphNodes.add(graphNode) graphNodes.add(graphNode)
removeFromAllLanes(reservedLanes, graphNode.name) removeFromAllLanes(reservedLanes, graphNode.name)
@ -99,9 +95,32 @@ class GenerateLogWalkUseCase @Inject constructor() {
availableCommitsToAdd.remove(currentCommit?.name) availableCommitsToAdd.remove(currentCommit?.name)
} }
GraphCommitList2(graphNodes) GraphCommitList2(graphNodes, maxLane)
} }
private fun createGraphNode(
currentCommit: RevCommit,
isStash: Boolean,
lane: Int,
forkingLanes: List<Int>,
reservedLanes: MutableMap<Int, String>,
mergingLanes: MutableList<Int>,
refs: List<Ref>
) = 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( private fun refsByCommit(
refsWithCommits: List<Pair<RevCommit, Ref>>, refsWithCommits: List<Pair<RevCommit, Ref>>,
commit: RevCommit commit: RevCommit
@ -128,47 +147,18 @@ class GenerateLogWalkUseCase @Inject constructor() {
} else { } else {
parents.sortedBy { it.committerIdent.`when` } 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 List<RevCommit>.filterStashParentsIfRequired(isStash: Boolean): List<RevCommit> {
fun t(commit1: RevCommit, commit2: RevCommit, repository: Repository): RevCommit? { return if (isStash) {
return RevWalk(repository).use { walk -> filterNot {
walk.setRevFilter(RevFilter.MERGE_BASE) it.shortMessage.startsWith("index on") ||
walk.markStart(commit1) it.shortMessage.startsWith("untracked files on")
walk.markStart(commit2) }
} else {
walk.next() this
} }
} }
// 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? { fun getNextCommit(availableCommits: List<RevCommit>): RevCommit? {
return availableCommits.sortedByDescending { it.committerIdent.`when` }.firstOrNull() return availableCommits.sortedByDescending { it.committerIdent.`when` }.firstOrNull()
@ -222,4 +212,11 @@ class GenerateLogWalkUseCase @Inject constructor() {
.firstOrNull() ?: (sortedKeys.max() + 1) .firstOrNull() ?: (sortedKeys.max() + 1)
} }
} }
}
sealed interface ReservationType {
val hash: String
class ParentInSameLane(override val hash: String): ReservationType
class ParentInVariableLane(override val hash: String): ReservationType
} }

View file

@ -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.
</L> */
class GraphCommitList : RevCommitList<GraphNode>() {
private var positionsAllocated = 0
private val freePositions = TreeSet<Int>()
private val activeLanes = HashSet<GraphLane>(32)
var maxLine = 0
private set
/** number of (child) commits on a lane */
private val laneLength = HashMap<GraphLane, Int?>(
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 }
}
}
}

View file

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

View file

@ -1,5 +0,0 @@
package com.jetpackduba.gitnuro.git.graph
const val INVALID_LANE_POSITION = -1
class GraphLane(var position: Int = 0)

View file

@ -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<GraphNode>()
private val NO_LANES = arrayOf<GraphLane>()
private val NO_LANE = GraphLane(INVALID_LANE_POSITION)
val NO_REFS = listOf<Ref>()
open class GraphNode(id: AnyObjectId?) : RevCommit(id), IGraphNode {
var forkingOffLanes: Array<GraphLane> = NO_LANES
var passingLanes: Array<GraphLane> = NO_LANES
var mergingLanes: Array<GraphLane> = NO_LANES
var lane: GraphLane = NO_LANE
var children: Array<GraphNode> = NO_CHILDREN
var refs: List<Ref> = 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<GraphLane>): Array<GraphLane> {
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
}
}

View file

@ -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<AnyObjectId, Set<Ref>>? = HashMap()
private var reverseRefMap: MutableMap<AnyObjectId, Set<Ref>>? = 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<Ref> {
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<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.
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<Ref> {
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
}
}
}

View file

@ -1,6 +0,0 @@
package com.jetpackduba.gitnuro.git.graph
interface IGraphNode {
val graphParentCount: Int
fun getGraphParent(nth: Int): GraphNode
}

View file

@ -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)
}
}

View file

@ -2,77 +2,37 @@ package com.jetpackduba.gitnuro.git.log
import com.jetpackduba.gitnuro.git.graph.GenerateLogWalkUseCase import com.jetpackduba.gitnuro.git.graph.GenerateLogWalkUseCase
import com.jetpackduba.gitnuro.git.graph.GraphCommitList import com.jetpackduba.gitnuro.git.graph.GraphCommitList2
import com.jetpackduba.gitnuro.git.graph.GraphWalk
import com.jetpackduba.gitnuro.git.stash.GetStashListUseCase import com.jetpackduba.gitnuro.git.stash.GetStashListUseCase
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Constants import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.lib.Repository
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.log
class GetLogUseCase @Inject constructor( class GetLogUseCase @Inject constructor(
private val getStashListUseCase: GetStashListUseCase, private val getStashListUseCase: GetStashListUseCase,
private val generateLogWalkUseCase: GenerateLogWalkUseCase, private val generateLogWalkUseCase: GenerateLogWalkUseCase,
) { ) {
private var graphWalkCached: GraphWalk? = null suspend operator fun invoke(git: Git, hasUncommittedChanges: Boolean, commitsLimit: Int?) =
suspend operator fun invoke(git: Git, currentBranch: Ref?, hasUncommittedChanges: Boolean, commitsLimit: Int) =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val logList = git.log().setMaxCount(1).call().toList() 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) 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)
//
// return@withContext if (firstCommit == null) {
// if (hasUncommittedChanges) GraphCommitList2(emptyList(), 0)
// commitList.addUncommittedChangesGraphCommit(logList.first()) } else {
// generateLogWalkUseCase.invoke(
// commitList.source(walk) git,
// commitList.fillTo(commitsLimit) firstCommit,
// } allRefs,
// stashes,
// ensureActive() hasUncommittedChanges,
// commitsLimit
// } )
// }
// commitList.calcMaxLine()
//
// return@withContext commitList
} }
private fun cachedGraphWalk(repository: Repository): GraphWalk {
val graphWalkCached = this.graphWalkCached
return if (graphWalkCached != null) {
graphWalkCached
} else {
val newGraphWalk = GraphWalk(repository)
this.graphWalkCached = newGraphWalk
newGraphWalk
}
}
} }

View file

@ -38,9 +38,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.jetpackduba.gitnuro.AppIcons import com.jetpackduba.gitnuro.AppIcons
import com.jetpackduba.gitnuro.extensions.* 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.GraphCommitList2
import com.jetpackduba.gitnuro.git.graph.GraphNode
import com.jetpackduba.gitnuro.git.graph.GraphNode2 import com.jetpackduba.gitnuro.git.graph.GraphNode2
import com.jetpackduba.gitnuro.git.workspace.StatusSummary import com.jetpackduba.gitnuro.git.workspace.StatusSummary
import com.jetpackduba.gitnuro.keybindings.KeybindingOption import com.jetpackduba.gitnuro.keybindings.KeybindingOption
@ -147,18 +145,18 @@ private fun LogLoaded(
null null
} }
// LaunchedEffect(verticalScrollState, commitList) { LaunchedEffect(verticalScrollState, commitList) {
// launch { launch {
// logViewModel.focusCommit.collect { commit -> logViewModel.focusCommit.collect { commit ->
// scrollToCommit(verticalScrollState, commitList, commit) scrollToCommit(verticalScrollState, commitList, commit)
// } }
// } }
// launch { launch {
// logViewModel.scrollToUncommittedChanges.collect { logViewModel.scrollToUncommittedChanges.collect {
// scrollToUncommittedChanges(verticalScrollState, commitList) scrollToUncommittedChanges(verticalScrollState, commitList)
// } }
// } }
// } }
LogDialogs( LogDialogs(
logViewModel, logViewModel,
@ -177,7 +175,7 @@ private fun LogLoaded(
if (graphWidth.value < CANVAS_MIN_WIDTH) graphWidth = CANVAS_MIN_WIDTH.dp if (graphWidth.value < CANVAS_MIN_WIDTH) graphWidth = CANVAS_MIN_WIDTH.dp
val maxLinePosition = if (commitList.isNotEmpty()) val maxLinePosition = if (commitList.isNotEmpty())
commitList.maxLine commitList.maxLane
else else
MIN_GRAPH_LANES MIN_GRAPH_LANES
@ -309,7 +307,7 @@ private fun LogLoaded(
suspend fun scrollToCommit( suspend fun scrollToCommit(
verticalScrollState: LazyListState, verticalScrollState: LazyListState,
commitList: GraphCommitList, commitList: GraphCommitList2,
commit: RevCommit?, commit: RevCommit?,
) { ) {
val index = commitList.indexOfFirst { it.name == commit?.name } val index = commitList.indexOfFirst { it.name == commit?.name }
@ -321,7 +319,7 @@ suspend fun scrollToCommit(
suspend fun scrollToUncommittedChanges( suspend fun scrollToUncommittedChanges(
verticalScrollState: LazyListState, verticalScrollState: LazyListState,
commitList: GraphCommitList, commitList: GraphCommitList2,
) { ) {
if (commitList.isNotEmpty()) if (commitList.isNotEmpty())
verticalScrollState.scrollToItem(0) verticalScrollState.scrollToItem(0)

View file

@ -1,6 +1,5 @@
package com.jetpackduba.gitnuro.ui.log package com.jetpackduba.gitnuro.ui.log
import com.jetpackduba.gitnuro.git.graph.GraphNode
import com.jetpackduba.gitnuro.git.graph.GraphNode2 import com.jetpackduba.gitnuro.git.graph.GraphNode2
import org.eclipse.jgit.lib.Ref import org.eclipse.jgit.lib.Ref

View file

@ -3,15 +3,12 @@ package com.jetpackduba.gitnuro.viewmodels
import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import com.jetpackduba.gitnuro.extensions.delayedStateChange import com.jetpackduba.gitnuro.extensions.delayedStateChange
import com.jetpackduba.gitnuro.extensions.shortName
import com.jetpackduba.gitnuro.extensions.simpleName import com.jetpackduba.gitnuro.extensions.simpleName
import com.jetpackduba.gitnuro.git.RefreshType import com.jetpackduba.gitnuro.git.RefreshType
import com.jetpackduba.gitnuro.git.TabState import com.jetpackduba.gitnuro.git.TabState
import com.jetpackduba.gitnuro.git.TaskEvent import com.jetpackduba.gitnuro.git.TaskEvent
import com.jetpackduba.gitnuro.git.branches.* 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.GraphCommitList2
import com.jetpackduba.gitnuro.git.graph.GraphNode
import com.jetpackduba.gitnuro.git.graph.GraphNode2 import com.jetpackduba.gitnuro.git.graph.GraphNode2
import com.jetpackduba.gitnuro.git.log.* import com.jetpackduba.gitnuro.git.log.*
import com.jetpackduba.gitnuro.git.rebase.RebaseBranchUseCase import com.jetpackduba.gitnuro.git.rebase.RebaseBranchUseCase
@ -150,14 +147,14 @@ class LogViewModel @Inject constructor(
val commitsLimit = if (appSettings.commitsLimitEnabled) { val commitsLimit = if (appSettings.commitsLimitEnabled) {
appSettings.commitsLimit appSettings.commitsLimit
} else } else
Int.MAX_VALUE null
val commitsLimitDisplayed = if (appSettings.commitsLimitEnabled) { val commitsLimitDisplayed = if (appSettings.commitsLimitEnabled) {
appSettings.commitsLimit appSettings.commitsLimit
} else } else
-1 -1
val log = getLogUseCase(git, currentBranch, hasUncommittedChanges, commitsLimit) val log = getLogUseCase(git, hasUncommittedChanges, commitsLimit)
_logStatus.value = _logStatus.value =
LogStatus.Loaded(hasUncommittedChanges, log, currentBranch, statusSummary, commitsLimitDisplayed) LogStatus.Loaded(hasUncommittedChanges, log, currentBranch, statusSummary, commitsLimitDisplayed)