UbisamBase 플랫폼 위에서 도는 플러그인. 지금까지 만든 것을 그대로 담는다. - 폴더 비교 / 파일 비교(부분 수용·워드랩·찾기) / 3-Way 병합 / Git 조작 - 비교 엔진: histogram 정렬 · 이동 블록 감지 · 무시 규칙 · 13개 언어 토크나이저 - doc/ 에 작업 지시서와 진행 기록 빌드 산출물(bin/obj)은 추적하지 않는다 - 플랫폼 런타임이 빌드 때마다 D:\UbisamPlatform\bin 에서 복사되어 들어오므로 저장소에 담을 이유가 없다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
134 lines
5.3 KiB
C#
134 lines
5.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using MergeHub.Models;
|
|
|
|
namespace MergeHub.Services
|
|
{
|
|
/// <summary>
|
|
/// 커밋 목록에 레인을 배정해 브랜치 그래프를 만든다.
|
|
///
|
|
/// 원리: "레인"은 아직 그려지지 않은 부모 커밋 하나를 기다리는 세로줄이다.
|
|
/// 목록을 위에서 아래로 훑으면서
|
|
/// 1) 지금 커밋을 기다리던 레인들을 찾아 그 중 왼쪽 것을 이 커밋의 레인으로 삼고,
|
|
/// 2) 나머지는 그 레인으로 합류시키고(사선),
|
|
/// 3) 첫 부모는 같은 레인이 이어받고, 두 번째 부모부터는 새 레인으로 분기시킨다.
|
|
/// 커밋은 이미 시간순(topological)으로 정렬되어 들어온다는 전제다.
|
|
/// </summary>
|
|
public static class CommitGraphBuilder
|
|
{
|
|
/// <summary>레인 색은 이 개수로 순환한다. 실제 색상값은 화면(CommitGraphCell)이 정한다.</summary>
|
|
public const int ColorCount = 6;
|
|
|
|
public static IReadOnlyList<CommitNode> Build(IReadOnlyList<CommitInfo> commits)
|
|
{
|
|
var nodes = new List<CommitNode>(commits.Count);
|
|
|
|
// lanes[i] = 그 레인이 기다리는 커밋 SHA (없으면 null)
|
|
var lanes = new List<string?>();
|
|
var laneColors = new List<int>();
|
|
var maxLanes = 1;
|
|
var nextColor = 0;
|
|
|
|
foreach (var commit in commits)
|
|
{
|
|
// 이 행에 "위에서 내려온" 상태를 먼저 찍어둔다.
|
|
var top = new List<string?>(lanes);
|
|
|
|
var myLanes = new List<int>();
|
|
for (var i = 0; i < lanes.Count; i++)
|
|
if (lanes[i] == commit.Sha) myLanes.Add(i);
|
|
|
|
int lane;
|
|
if (myLanes.Count == 0)
|
|
{
|
|
// 아무도 기다리지 않던 커밋 = 새 가지의 시작(브랜치 팁)
|
|
lane = TakeFreeLane(lanes, laneColors, ref nextColor);
|
|
lanes[lane] = commit.Sha;
|
|
myLanes.Add(lane);
|
|
}
|
|
else
|
|
{
|
|
lane = myLanes[0];
|
|
}
|
|
|
|
var colorIndex = laneColors[lane];
|
|
|
|
// 이 커밋을 기다리던 레인은 전부 비운다.
|
|
foreach (var i in myLanes) lanes[i] = null;
|
|
|
|
// 첫 부모는 같은 레인이 이어받고, 나머지는 새 레인으로 분기한다.
|
|
for (var p = 0; p < commit.ParentShas.Count; p++)
|
|
{
|
|
var parent = commit.ParentShas[p];
|
|
|
|
if (p == 0)
|
|
{
|
|
lanes[lane] = parent;
|
|
continue;
|
|
}
|
|
|
|
// 이미 그 부모를 기다리는 레인이 있으면 그리로 합친다.
|
|
var existing = lanes.IndexOf(parent);
|
|
if (existing >= 0) continue;
|
|
|
|
var branchLane = TakeFreeLane(lanes, laneColors, ref nextColor);
|
|
lanes[branchLane] = parent;
|
|
}
|
|
|
|
var edges = new List<GraphEdge>();
|
|
|
|
// (1) 위에서 내려온 선들
|
|
for (var i = 0; i < top.Count; i++)
|
|
{
|
|
if (top[i] is null) continue;
|
|
|
|
if (myLanes.Contains(i))
|
|
edges.Add(new GraphEdge(i, lane, laneColors[i])); // 이 커밋으로 합류
|
|
else if (i < lanes.Count && lanes[i] is not null)
|
|
edges.Add(new GraphEdge(i, i, laneColors[i])); // 그냥 통과
|
|
}
|
|
|
|
// (2) 이 커밋에서 새로 시작하는 선들 (첫 부모의 수직 연장 + 분기)
|
|
for (var i = 0; i < lanes.Count; i++)
|
|
{
|
|
if (lanes[i] is null) continue;
|
|
|
|
// 위에서 이미 내려온 레인은 (1)에서 그렸다.
|
|
if (i < top.Count && top[i] is not null && !myLanes.Contains(i)) continue;
|
|
|
|
// 이 커밋의 레인이 위에서도 이어져 왔다면 (1)의 i→lane 선과 겹친다.
|
|
if (i == lane && lane < top.Count && top[lane] is not null) continue;
|
|
|
|
edges.Add(new GraphEdge(lane, i, laneColors[i]));
|
|
}
|
|
|
|
if (lanes.Count > maxLanes) maxLanes = lanes.Count;
|
|
if (top.Count > maxLanes) maxLanes = top.Count;
|
|
|
|
nodes.Add(new CommitNode(commit, new GraphRow(lane, colorIndex, edges, commit.IsMerge)));
|
|
}
|
|
|
|
// 열 너비를 전체에서 가장 넓은 행에 맞춘다.
|
|
foreach (var node in nodes) node.Graph.LaneCount = maxLanes;
|
|
|
|
return nodes;
|
|
}
|
|
|
|
/// <summary>비어 있는 레인을 재사용하고, 없으면 하나 늘린다.</summary>
|
|
private static int TakeFreeLane(List<string?> lanes, List<int> laneColors, ref int nextColor)
|
|
{
|
|
for (var i = 0; i < lanes.Count; i++)
|
|
{
|
|
if (lanes[i] is not null) continue;
|
|
|
|
laneColors[i] = nextColor++ % ColorCount;
|
|
return i;
|
|
}
|
|
|
|
lanes.Add(null);
|
|
laneColors.Add(nextColor++ % ColorCount);
|
|
return lanes.Count - 1;
|
|
}
|
|
}
|
|
}
|