feat(history): highlight ahead/behind vs active upstream
Repo status updates for the management view are now refreshed in the background after switching, without blocking the initial open flow. The history graph and hover labels now distinguish local vs upstream branches, visually marking commits that are ahead or behind. - Add background fetch helper and shared status update logic - Pass active upstream into HistoryPanel and render sync styling
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
commits: GitCommit[];
|
||||
localBranchNames: string[];
|
||||
activeBranch: string;
|
||||
activeUpstream: string;
|
||||
repositoryKey: string;
|
||||
hasRepository: boolean;
|
||||
isBusy: boolean;
|
||||
@@ -47,6 +48,7 @@
|
||||
commits = [],
|
||||
localBranchNames = [],
|
||||
activeBranch = "",
|
||||
activeUpstream = "",
|
||||
repositoryKey = "",
|
||||
hasRepository = false,
|
||||
isBusy = false,
|
||||
@@ -201,7 +203,13 @@
|
||||
return [...new Set(values.filter(Boolean))];
|
||||
}
|
||||
|
||||
let graphBranchNames = $derived(uniqueStrings([...localBranchNames, activeUpstream].filter(Boolean)));
|
||||
let graphBranchNameSet = $derived(new Set(graphBranchNames));
|
||||
|
||||
function branchIsVisible(branch: string): boolean {
|
||||
if (branch === activeUpstream && activeUpstream) {
|
||||
return !activeBranch || !hiddenGraphBranches.has(activeBranch);
|
||||
}
|
||||
return !hiddenGraphBranches.has(branch);
|
||||
}
|
||||
|
||||
@@ -210,7 +218,7 @@
|
||||
}
|
||||
|
||||
function commitHoverBranchLabels(commit: GitCommit, row: GraphRow | undefined): string[] {
|
||||
const directBranches = localBranchRefs(commit);
|
||||
const directBranches = graphBranchRefs(commit);
|
||||
if (directBranches.length > 0) return directBranches;
|
||||
|
||||
const containingBranches = row?.branchLabels ?? [];
|
||||
@@ -219,7 +227,7 @@
|
||||
}
|
||||
|
||||
function commitHoverTitle(commit: GitCommit, row: GraphRow | undefined): string {
|
||||
const directBranches = localBranchRefs(commit);
|
||||
const directBranches = graphBranchRefs(commit);
|
||||
if (directBranches.length > 0) return `Branches: ${directBranches.join(", ")}`;
|
||||
|
||||
const containingBranches = row?.branchLabels ?? [];
|
||||
@@ -231,8 +239,17 @@
|
||||
return segment.branches.length === 0 || segment.branches.some(branchIsVisible);
|
||||
}
|
||||
|
||||
function syncClassForBranches(labels: string[]): "" | "ahead" | "behind" {
|
||||
if (!activeBranch || !activeUpstream) return "";
|
||||
const hasLocal = labels.includes(activeBranch);
|
||||
const hasRemote = labels.includes(activeUpstream);
|
||||
if (hasLocal && !hasRemote) return "ahead";
|
||||
if (hasRemote && !hasLocal) return "behind";
|
||||
return "";
|
||||
}
|
||||
|
||||
function branchesAreVisible(branches: string[]): boolean {
|
||||
if (localBranchNames.length === 0) return true;
|
||||
if (graphBranchNames.length === 0) return true;
|
||||
return branches.some(branchIsVisible);
|
||||
}
|
||||
|
||||
@@ -264,7 +281,7 @@
|
||||
const membership = new Map<string, Set<string>>();
|
||||
|
||||
for (const commit of items) {
|
||||
for (const branch of localBranchRefs(commit)) {
|
||||
for (const branch of graphBranchRefs(commit)) {
|
||||
const stack = [commit.hash];
|
||||
const seen = new Set<string>();
|
||||
|
||||
@@ -289,7 +306,7 @@
|
||||
return new Map(
|
||||
items.map((commit) => [
|
||||
commit.hash,
|
||||
localBranchNames.filter((branch) => membership.get(commit.hash)?.has(branch)),
|
||||
graphBranchNames.filter((branch) => membership.get(commit.hash)?.has(branch)),
|
||||
]),
|
||||
);
|
||||
}
|
||||
@@ -407,6 +424,18 @@
|
||||
return labels;
|
||||
}
|
||||
|
||||
function graphBranchRefs(commit: GitCommit): string[] {
|
||||
const seen = new Set<string>();
|
||||
const labels: string[] = [];
|
||||
for (const ref of commit.refs) {
|
||||
const label = refLabel(ref);
|
||||
if (!graphBranchNameSet.has(label) || seen.has(label)) continue;
|
||||
seen.add(label);
|
||||
labels.push(label);
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
function visibleRefs(commit: GitCommit): string[] {
|
||||
return commit.refs.filter((ref) => !localBranchNameSet.has(refLabel(ref)));
|
||||
}
|
||||
@@ -440,7 +469,7 @@
|
||||
$effect(() => {
|
||||
const defaultBranch = activeBranch && localBranchNames.includes(activeBranch)
|
||||
? activeBranch
|
||||
: (localBranchNames[0] ?? "");
|
||||
: (graphBranchNames[0] ?? "");
|
||||
const defaultFilterKey = `${repositoryKey}::${defaultBranch}`;
|
||||
|
||||
if (!defaultBranch) {
|
||||
@@ -512,8 +541,11 @@
|
||||
{@const row = graphRows[rowIndex]}
|
||||
{@const hoverBranchRefs = commitHoverBranchLabels(item, row)}
|
||||
{@const otherRefs = visibleRefs(item)}
|
||||
{@const rowSyncClass = syncClassForBranches(row?.branchLabels ?? [])}
|
||||
<article
|
||||
class="commit-row graph-row"
|
||||
class:graph-ahead-row={rowSyncClass === "ahead"}
|
||||
class:graph-behind-row={rowSyncClass === "behind"}
|
||||
class:merge-row={item.parents.length > 1}
|
||||
class:root-row={item.parents.length === 0}
|
||||
class:tip-row={item.refs.length > 0}
|
||||
@@ -524,6 +556,8 @@
|
||||
{#each row.top as seg}
|
||||
<path
|
||||
class:hidden-branch={!segmentIsVisible(seg)}
|
||||
class:graph-segment-ahead={syncClassForBranches(seg.branches) === "ahead"}
|
||||
class:graph-segment-behind={syncClassForBranches(seg.branches) === "behind"}
|
||||
d={graphPath(seg, 0, 50)}
|
||||
stroke={seg.color}
|
||||
stroke-width="2.2"
|
||||
@@ -533,6 +567,8 @@
|
||||
{#each row.bottom as seg}
|
||||
<path
|
||||
class:hidden-branch={!segmentIsVisible(seg)}
|
||||
class:graph-segment-ahead={syncClassForBranches(seg.branches) === "ahead"}
|
||||
class:graph-segment-behind={syncClassForBranches(seg.branches) === "behind"}
|
||||
d={graphPath(seg, 50, 100)}
|
||||
stroke={seg.color}
|
||||
stroke-width="2.2"
|
||||
@@ -545,13 +581,20 @@
|
||||
class:merge={item.parents.length > 1}
|
||||
class:tip={item.refs.length > 0}
|
||||
class:hidden-branch={!rowGraphIsVisible(row)}
|
||||
class:ahead={rowSyncClass === "ahead"}
|
||||
class:behind={rowSyncClass === "behind"}
|
||||
title={commitHoverTitle(item, row)}
|
||||
style={`left:${graphColX(row.dotCol)}px; --dot-color:${row.dotColor}`}
|
||||
></span>
|
||||
{#if hoverBranchRefs.length > 0}
|
||||
<div class="graph-hover-branches" style={`left:${graphColX(row.dotCol) + 13}px`}>
|
||||
{#each hoverBranchRefs as branch}
|
||||
<span title={branch}>
|
||||
<span
|
||||
class:remote={branch === activeUpstream}
|
||||
class:ahead={activeBranch === branch && rowSyncClass === "ahead"}
|
||||
class:behind={activeUpstream === branch && rowSyncClass === "behind"}
|
||||
title={branch}
|
||||
>
|
||||
<GitBranch size={10} aria-hidden="true" />
|
||||
{branch}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user