feat(git): add upstream tracking and local-only branch UI
This release adds upstream tracking for branches and a local-only indicator in the UI. The Git integration now exposes upstreams and supports publish flows. This enables publishing and remote-tracking configuration. - Introduces upstream tracking and local-only branch UI markers - Updates toolbar to reflect publish-local state and status indicators - Bumps version to 2026.8.3 and updates changelog
This commit is contained in:
+31
-6
@@ -4,7 +4,7 @@
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { open as openDialog } from "@tauri-apps/plugin-dialog";
|
||||
import { check, type DownloadEvent, type Update } from "@tauri-apps/plugin-updater";
|
||||
import { AlertCircle, Cherry, Download, FolderOpen, GitBranch, GitMerge, LoaderCircle, Plus, Search, Star, X } from "@lucide/svelte";
|
||||
import { AlertCircle, Cherry, CloudOff, Download, FolderOpen, GitBranch, GitMerge, LoaderCircle, Plus, Search, Star, X } from "@lucide/svelte";
|
||||
import { beginFrontendShutdown } from "./lib/telemetry";
|
||||
|
||||
import TitleBar from "./lib/TitleBar.svelte";
|
||||
@@ -506,7 +506,11 @@
|
||||
$: canCompare = hasRepository && compareFrom.length > 0 && compareTo.length > 0 && compareFrom !== compareTo && !isBusy;
|
||||
$: localBranches = branches.filter((b) => !b.remote);
|
||||
$: localBranchNames = localBranches.map((b) => b.name);
|
||||
$: localBranchUpstreams = Object.fromEntries(
|
||||
localBranches.flatMap((branch) => branch.upstream ? [[branch.name, branch.upstream]] : []),
|
||||
) as Record<string, string>;
|
||||
$: remoteBranches = branches.filter((b) => b.remote);
|
||||
$: currentBranchIsLocalOnly = Boolean(status?.current_branch) && !status?.upstream;
|
||||
$: repoSearchTerm = repoSearch.trim().toLowerCase();
|
||||
$: openRepoRows = repoTabs.filter((repo) => repoMatchesSearch(repo, repoSearchTerm));
|
||||
$: recentRepoRows = recentRepoPaths
|
||||
@@ -4445,6 +4449,7 @@
|
||||
{operation}
|
||||
ahead={status?.ahead ?? 0}
|
||||
behind={status?.behind ?? 0}
|
||||
localOnly={currentBranchIsLocalOnly}
|
||||
language={appLanguage}
|
||||
editorName={editorToolName}
|
||||
terminalName={terminalToolName}
|
||||
@@ -4896,9 +4901,22 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="sync-stats" aria-label="Sync state">
|
||||
{#if status?.upstream}<span title="Upstream">{status.upstream}</span>{/if}
|
||||
<strong>{status?.ahead ?? 0} ahead</strong>
|
||||
<strong>{status?.behind ?? 0} behind</strong>
|
||||
{#if currentBranchIsLocalOnly}
|
||||
<span
|
||||
class="sync-local-only"
|
||||
title={appLanguage === "de"
|
||||
? "Dieser Branch existiert nur auf diesem Computer. Veröffentlichen richtet den Remote-Branch ein."
|
||||
: "This branch exists only on this computer. Publish configures its remote branch."}
|
||||
>
|
||||
<CloudOff size={11} aria-hidden="true" />
|
||||
<strong>{appLanguage === "de" ? "Nur lokal" : "Local only"}</strong>
|
||||
<small>{appLanguage === "de" ? "noch nicht veröffentlicht" : "not published yet"}</small>
|
||||
</span>
|
||||
{:else}
|
||||
{#if status?.upstream}<span title="Upstream">{status.upstream}</span>{/if}
|
||||
<strong>{status?.ahead ?? 0} ahead</strong>
|
||||
<strong>{status?.behind ?? 0} behind</strong>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4991,6 +5009,7 @@
|
||||
{commits}
|
||||
{selectedCommitHash}
|
||||
{localBranchNames}
|
||||
{localBranchUpstreams}
|
||||
remoteBranchNames={remoteBranches.map((branch) => branch.name)}
|
||||
activeBranch={status?.current_branch ?? ""}
|
||||
activeUpstream={status?.upstream ?? ""}
|
||||
@@ -5027,8 +5046,14 @@
|
||||
<span class="workspace-status-spacer"></span>
|
||||
{#if workspaceActive}
|
||||
<span class="workspace-branch"><GitBranch size={12} aria-hidden="true" />{status?.current_branch ?? "No branch"}</span>
|
||||
<span class="ahead">↑ {status?.ahead ?? 0}</span>
|
||||
<span class="behind">↓ {status?.behind ?? 0}</span>
|
||||
{#if currentBranchIsLocalOnly}
|
||||
<span class="workspace-local-only" title={appLanguage === "de" ? "Branch noch nicht veröffentlicht" : "Branch not published yet"}>
|
||||
<CloudOff size={11} aria-hidden="true" />{appLanguage === "de" ? "Nur lokal" : "Local only"}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="ahead">↑ {status?.ahead ?? 0}</span>
|
||||
<span class="behind">↓ {status?.behind ?? 0}</span>
|
||||
{/if}
|
||||
<span class:active={autoRefreshEnabled} class="workspace-auto">Auto <i aria-hidden="true"></i></span>
|
||||
{/if}
|
||||
{#if appVersion}<span class="app-version" title={`Gitty version ${appVersion}`}>Gitty v{appVersion}</span>{/if}
|
||||
|
||||
+111
-1
@@ -736,6 +736,29 @@
|
||||
}
|
||||
.repo-action-count.behind { color: #7aacff; }
|
||||
.repo-action-count.ahead { color: #e0a040; }
|
||||
.repo-action.sync-primary.publish-local {
|
||||
color: #f0bd6b;
|
||||
background: linear-gradient(180deg, rgba(224,160,64,.1), rgba(224,160,64,.045));
|
||||
}
|
||||
.repo-action.sync-primary.publish-local:hover:not(:disabled) {
|
||||
color: #ffd48c;
|
||||
background: rgba(224,160,64,.14);
|
||||
}
|
||||
.repo-action-local-marker {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
border: 1px dashed rgba(240,189,107,.5);
|
||||
border-radius: 4px;
|
||||
color: #f0bd6b;
|
||||
background: rgba(224,160,64,.08);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 7.5px;
|
||||
font-weight: 900;
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
.repo-toolbar-divider {
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
@@ -1670,6 +1693,27 @@
|
||||
.sync-stats strong:first-of-type { color: #e0a040; background: rgba(224,160,64,0.13); }
|
||||
.sync-stats strong:last-of-type { color: #7aacff; background: rgba(122,172,255,0.13); }
|
||||
.sync-stats span { color: var(--color-ink-dim); background: rgba(94,110,156,0.13); }
|
||||
.sync-stats .sync-local-only {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 3px 7px;
|
||||
border: 1px dashed rgba(224,160,64,.38);
|
||||
color: #f0bd6b;
|
||||
background: rgba(224,160,64,.09);
|
||||
}
|
||||
.sync-stats .sync-local-only strong {
|
||||
padding: 0;
|
||||
color: #f0bd6b;
|
||||
background: transparent;
|
||||
font-size: 10.5px;
|
||||
font-weight: 850;
|
||||
}
|
||||
.sync-stats .sync-local-only small {
|
||||
color: var(--color-ink-faint);
|
||||
font-size: 9px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.top-section {
|
||||
display: grid;
|
||||
@@ -2513,6 +2557,12 @@
|
||||
min-width: 0;
|
||||
min-height: 20px;
|
||||
}
|
||||
.branch-ref-cluster {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
margin-left: -10px;
|
||||
}
|
||||
.compact-ref-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -2536,7 +2586,7 @@
|
||||
flex: 0 1 auto;
|
||||
max-width: 22px;
|
||||
height: 20px;
|
||||
margin-left: -10px;
|
||||
margin-left: 0;
|
||||
padding: 0 5px;
|
||||
border-color: color-mix(in srgb, var(--ref-lane-color, #69a7ff) 34%, transparent);
|
||||
border-left-width: 2px;
|
||||
@@ -2552,6 +2602,29 @@
|
||||
background 140ms ease,
|
||||
box-shadow 140ms ease;
|
||||
}
|
||||
.branch-ref-cluster.local-only .compact-ref-chip.branch {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
.compact-ref-local-marker {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
height: 20px;
|
||||
margin-left: -1px;
|
||||
padding: 0 5px 0 4px;
|
||||
border: 1px dashed rgba(240,189,107,.58);
|
||||
border-left-style: solid;
|
||||
border-radius: 0 5px 5px 0;
|
||||
color: #f0bd6b;
|
||||
background: linear-gradient(90deg, rgba(224,160,64,.13), rgba(224,160,64,.06));
|
||||
font-family: var(--font-mono);
|
||||
font-size: 7.5px;
|
||||
font-weight: 900;
|
||||
letter-spacing: .05em;
|
||||
line-height: 1;
|
||||
box-shadow: inset 1px 0 0 rgba(240,189,107,.2);
|
||||
}
|
||||
.compact-ref-branch-icon {
|
||||
flex: 0 0 auto;
|
||||
color: var(--ref-lane-color, #69a7ff);
|
||||
@@ -2713,6 +2786,12 @@
|
||||
font-size: 8px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.commit-ref-detail-item small.local-only {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
color: #d99532;
|
||||
}
|
||||
|
||||
.commit-files {
|
||||
display: grid;
|
||||
@@ -5905,6 +5984,17 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s
|
||||
.workspace-health.clean > span { background: #2da44e; }
|
||||
.workspace-statusbar .ahead { color: #d9891b; }
|
||||
.workspace-statusbar .behind { color: var(--color-primary); }
|
||||
.workspace-statusbar .workspace-local-only {
|
||||
padding: 2px 6px;
|
||||
border: 1px dashed rgba(224,160,64,.42);
|
||||
border-radius: 4px;
|
||||
color: #f0bd6b;
|
||||
background: rgba(224,160,64,.08);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 9px;
|
||||
font-weight: 850;
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
.workspace-auto i { width: 7px; height: 7px; border-radius: 50%; background: var(--color-ink-faint); }
|
||||
.workspace-auto.active i { background: #2da44e; }
|
||||
.app-version {
|
||||
@@ -6140,6 +6230,13 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s
|
||||
linear-gradient(90deg, color-mix(in srgb, var(--ref-lane-color, #315fd6) 14%, #f7faff), #f7faff);
|
||||
}
|
||||
|
||||
:root[data-theme="light"] .compact-ref-local-marker {
|
||||
border-color: rgba(154,82,0,.46);
|
||||
color: #8b5207;
|
||||
background: linear-gradient(90deg, rgba(217,137,27,.14), rgba(217,137,27,.06));
|
||||
box-shadow: inset 1px 0 0 rgba(154,82,0,.14);
|
||||
}
|
||||
|
||||
:root[data-theme="light"] .compact-ref-chip.current {
|
||||
border-color: color-mix(in srgb, var(--ref-lane-color, #315fd6) 58%, rgba(49,95,214,.2));
|
||||
color: #18345f;
|
||||
@@ -6709,6 +6806,19 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s
|
||||
color: #0755c8;
|
||||
font-weight: 800;
|
||||
}
|
||||
:root[data-theme="light"] .repo-action.sync-primary.publish-local,
|
||||
:root[data-theme="light"] .repo-action-local-marker,
|
||||
:root[data-theme="light"] .workspace-statusbar .workspace-local-only {
|
||||
color: #8b5207;
|
||||
border-color: rgba(154,82,0,.4);
|
||||
background: rgba(217,137,27,.09);
|
||||
}
|
||||
:root[data-theme="light"] .sync-stats .sync-local-only {
|
||||
border-color: rgba(154,82,0,.36);
|
||||
color: #8b5207;
|
||||
background: rgba(217,137,27,.09);
|
||||
}
|
||||
:root[data-theme="light"] .sync-stats .sync-local-only strong { color: #8b5207; }
|
||||
|
||||
/* AI pre-commit review --------------------------------------------------- */
|
||||
.commit-review-button {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
ChevronDown,
|
||||
Code2,
|
||||
CloudDownload,
|
||||
CloudOff,
|
||||
Download,
|
||||
FolderOpen,
|
||||
GitCompare,
|
||||
@@ -21,6 +22,7 @@
|
||||
export let operation: string = "";
|
||||
export let ahead: number = 0;
|
||||
export let behind: number = 0;
|
||||
export let localOnly: boolean = false;
|
||||
export let language: "en" | "de" = "en";
|
||||
export let editorName: string = "Editor";
|
||||
export let terminalName: string = "Terminal";
|
||||
@@ -45,6 +47,12 @@
|
||||
let toolbarElement: HTMLDivElement;
|
||||
|
||||
$: isGerman = language === "de";
|
||||
$: pushLabel = localOnly ? (isGerman ? "Veröffentlichen" : "Publish") : "Push";
|
||||
$: pushTitle = localOnly
|
||||
? (isGerman
|
||||
? "Dieser Branch existiert nur lokal. Veröffentlichen erstellt den Remote-Branch und richtet das Tracking ein."
|
||||
: "This branch exists only locally. Publish creates the remote branch and configures tracking.")
|
||||
: "Push";
|
||||
|
||||
function runHistoryAction(action: () => void) {
|
||||
historyOpen = false;
|
||||
@@ -103,10 +111,13 @@
|
||||
|
||||
<button
|
||||
class="repo-action sync-primary"
|
||||
class:publish-local={localOnly}
|
||||
onclick={onPush}
|
||||
disabled={!hasRepository || isBusy}
|
||||
title="Push"
|
||||
aria-label={ahead > 0
|
||||
title={pushTitle}
|
||||
aria-label={localOnly
|
||||
? pushTitle
|
||||
: ahead > 0
|
||||
? `Push, ${ahead} ${isGerman ? (ahead === 1 ? "lokaler Commit voraus" : "lokale Commits voraus") : (ahead === 1 ? "commit ahead" : "commits ahead")}`
|
||||
: "Push"}
|
||||
>
|
||||
@@ -115,8 +126,12 @@
|
||||
{:else}
|
||||
<Upload size={15} aria-hidden="true" />
|
||||
{/if}
|
||||
<span class="repo-action-label">Push</span>
|
||||
{#if ahead > 0}<span class="repo-action-count ahead">↑{ahead}</span>{/if}
|
||||
<span class="repo-action-label">{pushLabel}</span>
|
||||
{#if localOnly}
|
||||
<span class="repo-action-local-marker"><CloudOff size={9} aria-hidden="true" />{isGerman ? "NUR LOKAL" : "LOCAL"}</span>
|
||||
{:else if ahead > 0}
|
||||
<span class="repo-action-count ahead">↑{ahead}</span>
|
||||
{/if}
|
||||
</button>
|
||||
<div class="repo-history-wrap">
|
||||
<button class="repo-action" type="button" onclick={() => { syncOpen = !syncOpen; historyOpen = false; }} disabled={!hasRepository || isBusy} aria-label={isGerman ? "Sync-Optionen" : "Sync options"} aria-haspopup="menu">
|
||||
|
||||
@@ -1361,6 +1361,45 @@
|
||||
label: "Neu in Gitty",
|
||||
description: "Änderungen seit der letzten veröffentlichten Version und wichtige Neuerungen früherer Releases.",
|
||||
sections: [
|
||||
{
|
||||
id: "changelog-2026-8-3",
|
||||
title: "Version 2026.8.3",
|
||||
summary: "Dieses Release verbindet Gitty enger mit deinen Entwicklungswerkzeugen und macht Branches, Historie und Vergleiche deutlich leistungsfähiger.",
|
||||
steps: [
|
||||
"Externe Tools: Editor, Diff-Tool, Merge-Tool, Terminal und Dateimanager lassen sich in den neu gestalteten Einstellungen erkennen, auswählen und individuell konfigurieren.",
|
||||
"VS Code, JetBrains-IDEs, Beyond Compare und weitere unterstützte Programme werden in einem eigenen Fenster geöffnet; dokumentierte Ergebnis-Codes werden beim Schließen korrekt behandelt.",
|
||||
"Git Notes: Commits erhalten lokale Notizen, ohne ihre Historie umzuschreiben. Notizen lassen sich bearbeiten, löschen sowie gezielt vom Remote abrufen oder dorthin übertragen.",
|
||||
"Vollständiger Branch-Vergleich: Lokale Branches, Remote-Branches und Commits können direkt ausgewählt und dateiweise im Side-by-Side-Diff verglichen werden.",
|
||||
"Remote-Branches lassen sich im Kontextmenü sicher umbenennen. Gitty schützt dabei vorhandene Ziel-Branches und zwischenzeitlich geänderte Remote-Stände.",
|
||||
"Der überarbeitete Commit-Graph zeigt Branches kompakter, reduziert überladene Commit-Zeilen und blendet zusätzliche Flag-Details beim Darüberfahren ein.",
|
||||
"Noch nicht veröffentlichte Branches sind als „Nur lokal“ erkennbar – in der Werkzeugleiste, Repository-Übersicht, Statuszeile und direkt an der Branch-Flag. Die erste Push-Aktion heißt passend „Veröffentlichen“.",
|
||||
"Branch-Sichtbarkeit, feinere Graph-Verbindungen und ein kleineres Mindestmaß des Verlaufsbereichs verbessern die Übersicht bei großen Repositories.",
|
||||
"Die neue Befehlspalette öffnet Aktionen, Dateien und Commits schneller; asynchrone Git-Befehle halten Gitty auch bei langsameren Operationen reaktionsfähig.",
|
||||
],
|
||||
note: "Der Branch-Vergleich zeigt die vollständig festgeschriebenen Zustände der beiden Branch-Spitzen. Nicht commitete Änderungen im Arbeitsverzeichnis sind nicht enthalten.",
|
||||
},
|
||||
{
|
||||
id: "changelog-2026-8-2",
|
||||
title: "Version 2026.8.2",
|
||||
summary: "Dieses Release stabilisiert die Darstellung komplexer Verläufe und verbessert die Veröffentlichung neuer Gitty-Versionen.",
|
||||
steps: [
|
||||
"Branch-Farben bleiben über Eltern-Lanes hinweg stabil, sodass sich Linien in längeren und verzweigten Historien leichter verfolgen lassen.",
|
||||
"Release-Artefakte werden automatisch und ohne doppelte Dateien an das passende Gitea-Release angehängt.",
|
||||
"Beim Beenden der Anwendung wird die Telemetrie zuverlässiger abgeschlossen.",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "changelog-2026-8-1",
|
||||
title: "Version 2026.8.1",
|
||||
summary: "Dieses Release macht große Commit-Verläufe und die Historie einzelner Dateien leichter zugänglich und verbessert die Paketverteilung.",
|
||||
steps: [
|
||||
"Die Commit-Historie lädt ältere Einträge seitenweise nach und ist nicht mehr auf die erste Ergebnismenge begrenzt.",
|
||||
"Die Dateihistorie öffnet sich aus dem Explorer-Kontextmenü in einem eigenen, größeren Dialog statt in einem dauerhaft belegten Seitenbereich.",
|
||||
"Dialoge reagieren konsistenter auf die Escape-Taste.",
|
||||
"Windows- und Ubuntu-Releases sowie der AUR-Paketablauf wurden erweitert und robuster gemacht.",
|
||||
"Die Arch-Linux-Anleitung verwendet jetzt das AUR-Paket gitty-desktop; SSH-Einrichtung, Zeitlimits und Wiederholungsversuche wurden verbessert.",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "changelog-2026-07-22",
|
||||
title: "Version 2026.07.22",
|
||||
@@ -1411,6 +1450,45 @@
|
||||
label: "What's new",
|
||||
description: "Changes since the latest published version and notable additions from earlier releases.",
|
||||
sections: [
|
||||
{
|
||||
id: "changelog-2026-8-3",
|
||||
title: "Version 2026.8.3",
|
||||
summary: "This release connects Gitty more closely with your development tools and makes branches, history, and comparisons substantially more capable.",
|
||||
steps: [
|
||||
"External tools: editors, diff tools, merge tools, terminals, and file managers can be detected, selected, and customized in the redesigned settings.",
|
||||
"VS Code, JetBrains IDEs, Beyond Compare, and other supported applications open in a separate window; documented result codes are handled correctly when they close.",
|
||||
"Git Notes: attach local notes to commits without rewriting history. Notes can be edited, deleted, fetched from a remote, or pushed explicitly.",
|
||||
"Complete branch comparison: choose local branches, remote branches, or commits and inspect every changed file in a side-by-side diff.",
|
||||
"Remote branches can be renamed safely from the context menu. Gitty protects existing destination branches and remote branches that changed after the last fetch.",
|
||||
"The redesigned commit graph presents branches more compactly, reduces crowded commit rows, and reveals additional flag details on hover.",
|
||||
"Unpublished branches are clearly marked as Local only in the toolbar, repository summary, status bar, and on the graph flag. Their first push is labeled Publish.",
|
||||
"Branch visibility controls, refined graph connectors, and a smaller minimum history width improve navigation in large repositories.",
|
||||
"The new command palette opens actions, files, and commits faster; asynchronous Git commands keep Gitty responsive during slower operations.",
|
||||
],
|
||||
note: "Branch comparison uses the fully committed state at each branch tip. Uncommitted working-tree changes are not included.",
|
||||
},
|
||||
{
|
||||
id: "changelog-2026-8-2",
|
||||
title: "Version 2026.8.2",
|
||||
summary: "This release stabilizes complex history rendering and improves publication of new Gitty versions.",
|
||||
steps: [
|
||||
"Branch colors remain stable across parent lanes, making longer and branching histories easier to follow.",
|
||||
"Release artifacts are attached to the matching Gitea release automatically without uploading duplicates.",
|
||||
"Telemetry cleanup completes more reliably while the application is shutting down.",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "changelog-2026-8-1",
|
||||
title: "Version 2026.8.1",
|
||||
summary: "This release makes large commit histories and individual file histories easier to access and improves package distribution.",
|
||||
steps: [
|
||||
"Commit history loads older entries page by page instead of stopping after the initial result set.",
|
||||
"File history opens from the explorer context menu in a dedicated larger dialog instead of occupying a permanent workspace panel.",
|
||||
"Dialogs respond more consistently to the Escape key.",
|
||||
"Windows and Ubuntu publishing plus the AUR package workflow were expanded and made more robust.",
|
||||
"The Arch Linux guide now uses the gitty-desktop AUR package; SSH setup, timeouts, and retry handling were improved.",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "changelog-2026-07-22",
|
||||
title: "Version 2026.07.22",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Check, Cherry, ChevronDown, ChevronRight, EllipsisVertical, GitBranch, GitMerge, LoaderCircle, RotateCcw, StickyNote, Tag, X } from "@lucide/svelte";
|
||||
import { Check, Cherry, ChevronDown, ChevronRight, CloudOff, EllipsisVertical, GitBranch, GitMerge, LoaderCircle, RotateCcw, StickyNote, Tag, X } from "@lucide/svelte";
|
||||
import type { FileStatusKind, GitCommit, GitCommitFile } from "../types";
|
||||
|
||||
interface GraphSegment {
|
||||
@@ -30,6 +30,7 @@
|
||||
kind: CommitRefKind;
|
||||
current: boolean;
|
||||
trackedRemote: string;
|
||||
localOnly: boolean;
|
||||
representedBranches: string[];
|
||||
}
|
||||
|
||||
@@ -57,6 +58,7 @@
|
||||
interface Props {
|
||||
commits: GitCommit[];
|
||||
localBranchNames: string[];
|
||||
localBranchUpstreams: Record<string, string>;
|
||||
remoteBranchNames: string[];
|
||||
activeBranch: string;
|
||||
activeUpstream: string;
|
||||
@@ -84,6 +86,7 @@
|
||||
let {
|
||||
commits = [],
|
||||
localBranchNames = [],
|
||||
localBranchUpstreams = {},
|
||||
remoteBranchNames = [],
|
||||
activeBranch = "",
|
||||
activeUpstream = "",
|
||||
@@ -541,20 +544,17 @@
|
||||
return !localBranchNameSet.has(label) && /(?:^|\/)HEAD(?:\s*->|$)/.test(ref);
|
||||
}
|
||||
|
||||
function branchNameWithoutRemote(branch: string): string {
|
||||
const slash = branch.indexOf("/");
|
||||
return slash === -1 ? branch : branch.slice(slash + 1);
|
||||
}
|
||||
|
||||
function remoteName(branch: string): string {
|
||||
return branch.split("/", 1)[0] ?? branch;
|
||||
}
|
||||
|
||||
function configuredUpstreamForBranch(local: string): string {
|
||||
return localBranchUpstreams[local] ?? (local === activeBranch ? activeUpstream : "");
|
||||
}
|
||||
|
||||
function matchingRemoteBranch(local: string, remoteBranches: string[]): string {
|
||||
if (local === activeBranch && activeUpstream && remoteBranches.includes(activeUpstream)) {
|
||||
return activeUpstream;
|
||||
}
|
||||
return remoteBranches.find((remote) => branchNameWithoutRemote(remote) === local) ?? "";
|
||||
const configuredUpstream = configuredUpstreamForBranch(local);
|
||||
return configuredUpstream && remoteBranches.includes(configuredUpstream) ? configuredUpstream : "";
|
||||
}
|
||||
|
||||
function compareBranchDecorations(left: CommitBranchDecoration, right: CommitBranchDecoration): number {
|
||||
@@ -594,6 +594,7 @@
|
||||
|
||||
const remainingRemote = [...remote];
|
||||
const branches: CommitBranchDecoration[] = [...local].map((label) => {
|
||||
const configuredUpstream = configuredUpstreamForBranch(label);
|
||||
const trackedRemote = matchingRemoteBranch(label, remainingRemote);
|
||||
if (trackedRemote) remainingRemote.splice(remainingRemote.indexOf(trackedRemote), 1);
|
||||
return {
|
||||
@@ -601,6 +602,7 @@
|
||||
kind: "local",
|
||||
current: label === activeBranch,
|
||||
trackedRemote,
|
||||
localOnly: !configuredUpstream,
|
||||
representedBranches: trackedRemote ? [label, trackedRemote] : [label],
|
||||
};
|
||||
});
|
||||
@@ -611,6 +613,7 @@
|
||||
kind: "head",
|
||||
current: true,
|
||||
trackedRemote: "",
|
||||
localOnly: false,
|
||||
representedBranches: [],
|
||||
});
|
||||
}
|
||||
@@ -620,6 +623,7 @@
|
||||
kind: "remote" as const,
|
||||
current: false,
|
||||
trackedRemote: "",
|
||||
localOnly: false,
|
||||
representedBranches: [label],
|
||||
})));
|
||||
|
||||
@@ -657,6 +661,7 @@
|
||||
}
|
||||
|
||||
function branchDecorationTitle(branch: CommitBranchDecoration): string {
|
||||
if (branch.localOnly) return `${branch.label} · Local only — not published yet`;
|
||||
if (branch.trackedRemote) return `${branch.label} · up to date with ${branch.trackedRemote}`;
|
||||
const status = branchStatusLabel(branch);
|
||||
if (status) return `${branch.label} · ${status}`;
|
||||
@@ -851,19 +856,26 @@
|
||||
<div class="commit-ref-area">
|
||||
<div class="commit-ref-strip" aria-label="Commit references">
|
||||
{#if refSummary.primaryBranch}
|
||||
<span
|
||||
class="compact-ref-chip branch"
|
||||
class:current={refSummary.primaryBranch.current}
|
||||
class:remote={refSummary.primaryBranch.kind === "remote"}
|
||||
title={branchDecorationTitle(refSummary.primaryBranch)}
|
||||
>
|
||||
<GitBranch class="compact-ref-branch-icon" size={10} aria-hidden="true" />
|
||||
<span>{refSummary.primaryBranch.label}</span>
|
||||
{#if branchStatusLabel(refSummary.primaryBranch)}
|
||||
<small class:up-to-date={Boolean(refSummary.primaryBranch.trackedRemote)}>
|
||||
{#if refSummary.primaryBranch.trackedRemote}<Check size={9} aria-hidden="true" />{/if}
|
||||
{branchStatusLabel(refSummary.primaryBranch).replace(/^✓\s*/, "")}
|
||||
</small>
|
||||
<span class="branch-ref-cluster" class:local-only={refSummary.primaryBranch.localOnly}>
|
||||
<span
|
||||
class="compact-ref-chip branch"
|
||||
class:current={refSummary.primaryBranch.current}
|
||||
class:remote={refSummary.primaryBranch.kind === "remote"}
|
||||
title={branchDecorationTitle(refSummary.primaryBranch)}
|
||||
>
|
||||
<GitBranch class="compact-ref-branch-icon" size={10} aria-hidden="true" />
|
||||
<span>{refSummary.primaryBranch.label}</span>
|
||||
{#if branchStatusLabel(refSummary.primaryBranch)}
|
||||
<small class:up-to-date={Boolean(refSummary.primaryBranch.trackedRemote)}>
|
||||
{#if refSummary.primaryBranch.trackedRemote}<Check size={9} aria-hidden="true" />{/if}
|
||||
{branchStatusLabel(refSummary.primaryBranch).replace(/^✓\s*/, "")}
|
||||
</small>
|
||||
{/if}
|
||||
</span>
|
||||
{#if refSummary.primaryBranch.localOnly}
|
||||
<span class="compact-ref-local-marker" title="This branch exists only locally and has not been published yet">
|
||||
<CloudOff size={9} aria-hidden="true" />LOCAL
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
@@ -899,6 +911,7 @@
|
||||
<i aria-hidden="true"></i>{branch.label}
|
||||
{#if branch.current}<small>Current</small>{/if}
|
||||
{#if branch.trackedRemote}<small>{branch.trackedRemote}</small>{/if}
|
||||
{#if branch.localOnly}<small class="local-only"><CloudOff size={9} aria-hidden="true" />Local only</small>{/if}
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -129,6 +129,7 @@ export interface GitBranch {
|
||||
name: string;
|
||||
current: boolean;
|
||||
remote: boolean;
|
||||
upstream: string | null;
|
||||
}
|
||||
|
||||
export interface GitWorktree {
|
||||
|
||||
Reference in New Issue
Block a user