Fix commit graph rendering for branching history
Track active lanes via explicit state (openLanes set) rather than relying on PlotCommit.getPassingLanes() which is not available. Draw '-' between passing lanes and a commit when the commit's parent is on a lane to the left (branch convergence), giving output like: * <- C (lane 0) | * <- D (lane 0 passing, D on lane 1, no convergence) * | <- C (lane 0, lane 1 passing) |-* <- B (lane 1, converges to A on lane 0) * <- A (lane 0) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -36,8 +36,10 @@ import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Service
|
||||
@@ -349,17 +351,25 @@ public class GitService
|
||||
plotCommitList.source(plotWalk);
|
||||
plotCommitList.fillTo(Integer.MAX_VALUE);
|
||||
|
||||
int maxLanes = 0;
|
||||
for (PlotCommit<PlotLane> pc : plotCommitList)
|
||||
{
|
||||
if (pc.getLane() != null && pc.getLane().getPosition() + 1 > maxLanes)
|
||||
maxLanes = pc.getLane().getPosition() + 1;
|
||||
}
|
||||
// Track which lane positions are currently "open" (started but parent not yet seen)
|
||||
Set<Integer> openLanes = new LinkedHashSet<>();
|
||||
|
||||
List<CommitInfo> commits = new ArrayList<>();
|
||||
for (PlotCommit<PlotLane> pc : plotCommitList)
|
||||
{
|
||||
String graphLine = buildGraphLine(pc, maxLanes);
|
||||
int lane = pc.getLane() != null ? pc.getLane().getPosition() : 0;
|
||||
openLanes.add(lane); // ensure this lane is marked active
|
||||
|
||||
String graphLine = buildGraphLine(pc, new LinkedHashSet<>(openLanes));
|
||||
|
||||
// Advance lane state: close this lane, open parent lanes
|
||||
openLanes.remove(lane);
|
||||
for (RevCommit parent : pc.getParents())
|
||||
{
|
||||
if (parent instanceof PlotCommit<?> pp && pp.getLane() != null)
|
||||
openLanes.add(pp.getLane().getPosition());
|
||||
}
|
||||
|
||||
List<String> parents = Arrays.stream(pc.getParents())
|
||||
.map(p -> p.getId().abbreviate(7).name())
|
||||
.toList();
|
||||
@@ -382,27 +392,46 @@ public class GitService
|
||||
}
|
||||
}
|
||||
|
||||
private String buildGraphLine(PlotCommit<PlotLane> pc, int maxLanes)
|
||||
private String buildGraphLine(PlotCommit<PlotLane> pc, Set<Integer> activeLanes)
|
||||
{
|
||||
int lanePos = pc.getLane() != null ? pc.getLane().getPosition() : 0;
|
||||
int width = Math.max(maxLanes, lanePos + 1);
|
||||
char[] line = new char[width];
|
||||
Arrays.fill(line, ' ');
|
||||
int commitLane = pc.getLane() != null ? pc.getLane().getPosition() : 0;
|
||||
|
||||
// Mark passing-through lanes
|
||||
for (int i = 0; i < pc.getChildCount(); i++)
|
||||
// Find the leftmost parent lane that is to the left of this commit's lane.
|
||||
// Dashes are drawn from that lane to this commit to show convergence.
|
||||
int mergeFromLane = commitLane;
|
||||
for (RevCommit parent : pc.getParents())
|
||||
{
|
||||
PlotCommit child = (PlotCommit) pc.getChild(i);
|
||||
if (child.getLane() != null)
|
||||
if (parent instanceof PlotCommit<?> pp && pp.getLane() != null)
|
||||
{
|
||||
int childPos = child.getLane().getPosition();
|
||||
if (childPos < width)
|
||||
line[childPos] = '|';
|
||||
int pLane = pp.getLane().getPosition();
|
||||
if (pLane < commitLane)
|
||||
mergeFromLane = Math.min(mergeFromLane, pLane);
|
||||
}
|
||||
}
|
||||
|
||||
line[lanePos] = '*';
|
||||
return new String(line);
|
||||
int width = activeLanes.stream().mapToInt(Integer::intValue).max().orElse(0) + 1;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < width; i++)
|
||||
{
|
||||
if (i == commitLane)
|
||||
sb.append('*');
|
||||
else if (activeLanes.contains(i))
|
||||
sb.append('|');
|
||||
else
|
||||
sb.append(' ');
|
||||
|
||||
if (i < width - 1)
|
||||
{
|
||||
// Draw '-' between mergeFromLane and commitLane to show branch convergence
|
||||
if (i >= mergeFromLane && i < commitLane)
|
||||
sb.append('-');
|
||||
else
|
||||
sb.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString().stripTrailing();
|
||||
}
|
||||
|
||||
public List<DiffInfo> getCommitDiff(String name, String commitHash) throws IOException
|
||||
|
||||
@@ -349,6 +349,53 @@ class GitServiceTest
|
||||
assertNotNull(commits.getFirst().graphLine());
|
||||
}
|
||||
|
||||
@Test
|
||||
void listCommitsGraphLineShowsBranchingCorrectly() throws GitAPIException, IOException
|
||||
{
|
||||
// Create repo with two branches from initial commit: main (C) and feature (B)
|
||||
gitService.cloneRepository(bareRemote.toUri().toString(), "myrepo");
|
||||
|
||||
// Make commit C on main
|
||||
Files.writeString(worktreePath.resolve("myrepo/main.txt"), "main");
|
||||
gitService.stageFiles("myrepo", List.of("main.txt"));
|
||||
gitService.commit("myrepo", "C");
|
||||
|
||||
// Create feature branch from initial commit (A) and make commit B
|
||||
gitService.createAndCheckoutBranch("myrepo", "feature");
|
||||
// Rewind feature to initial commit
|
||||
try (Git git = Git.open(worktreePath.resolve("myrepo").toFile()))
|
||||
{
|
||||
var initialCommit = gitService.listCommits("myrepo").stream()
|
||||
.filter(c -> c.message().equals("Initial commit"))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
git.reset().setMode(org.eclipse.jgit.api.ResetCommand.ResetType.HARD)
|
||||
.setRef(initialCommit.hash()).call();
|
||||
}
|
||||
Files.writeString(worktreePath.resolve("myrepo/feature.txt"), "feature");
|
||||
gitService.stageFiles("myrepo", List.of("feature.txt"));
|
||||
gitService.commit("myrepo", "B");
|
||||
|
||||
// Switch back to main
|
||||
gitService.checkoutBranch("myrepo", "master".equals(gitService.listBranches("myrepo").stream()
|
||||
.filter(b -> b.equals("master") || b.equals("main")).findFirst().orElse("master"))
|
||||
? "master" : "main");
|
||||
|
||||
var commits = gitService.listCommits("myrepo");
|
||||
|
||||
// Lane-0 commits should have graph lines starting with '*'
|
||||
var lane0Commits = commits.stream()
|
||||
.filter(c -> c.graphLine().startsWith("*"))
|
||||
.toList();
|
||||
// Lane-1 commit (B) should have '|' then '-' then '*'
|
||||
var lane1Commits = commits.stream()
|
||||
.filter(c -> c.graphLine().startsWith("|-"))
|
||||
.toList();
|
||||
|
||||
assertFalse(lane0Commits.isEmpty(), "Expected commits on lane 0");
|
||||
assertFalse(lane1Commits.isEmpty(), "Expected branching commit with |-* pattern");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCommitDiffReturnsDiffs() throws GitAPIException, IOException
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user