feat(git-lfs): add Git LFS status and sidecar bundling

Adds Git LFS support to the backend API and related UI.
The app now exposes commands to inspect, install, track and pull.
A sidecar git-lfs binary is bundled and a prep script is added.
This prepares the correct binary for each target platform.

- Expose Git LFS status and management commands in API
- Bundle and prepare a sidecar git-lfs binary for targets
- Update packaging, docs, and README with LFS notes
This commit is contained in:
Christoph Brandau
2026-08-17 20:27:54 +02:00
parent 6f2d4dd8c9
commit cd01df0108
18 changed files with 1444 additions and 16 deletions
+123 -1
View File
@@ -3,6 +3,7 @@
import {
AlertTriangle,
BookOpen,
Box,
Check,
ChevronRight,
CircleHelp,
@@ -468,6 +469,126 @@
},
];
deCategories.splice(deCategories.findIndex((category) => category.id === "remote") + 1, 0, {
id: "lfs",
label: "Git LFS",
description: "Große Binärdateien tracken, Objekte synchronisieren und bestehende Repositories sicher umstellen.",
sections: [
{
id: "lfs-overview",
title: "Was Git LFS macht",
summary: "Git LFS ersetzt große Dateien im Git-Verlauf durch kleine Zeigerdateien. Die eigentlichen Inhalte liegen im LFS-Speicher des Remotes und werden beim Checkout oder Pull passend geladen.",
steps: [
"Nutze LFS vor allem für große Binärdateien wie PSD-, Video-, Audio-, Modell- oder Archivdateien, die Git nicht sinnvoll als Text-Diff verwalten kann.",
"Gitty liefert die Git-LFS-Erweiterung in Desktop-Installern mit und zeigt Version, Filter sowie Pre-push-Hook im LFS-Dialog an.",
"Die LFS-Regeln stehen in .gitattributes und gehören deshalb wie normaler Quellcode in das Repository.",
],
},
{
id: "lfs-setup",
title: "Git LFS in Gitty einrichten",
summary: "Die Einrichtung gilt für das aktuell geöffnete Repository und verändert keine globalen Git-Einstellungen.",
steps: [
"Öffne im Repository das Menü Synchronisieren und wähle Git LFS.",
"Klicke auf LFS aktivieren, damit Gitty die lokalen Filter und den Pre-push-Hook einrichtet.",
"Füge ein Muster wie *.psd, Assets/** oder video.mp4 hinzu. Lockable markiert Dateien, die über einen kompatiblen LFS-Server gesperrt werden können.",
"Stage und committe anschließend .gitattributes zusammen mit den gewünschten Dateien.",
],
commands: [
{ command: "git lfs install --local", description: "LFS nur im aktuellen Repository aktivieren" },
{ command: "git lfs track \"*.psd\"", description: "Ein Dateimuster über Git LFS verwalten" },
{ command: "git add .gitattributes", description: "Die erzeugten Tracking-Regeln stagen" },
],
},
{
id: "lfs-sync",
title: "LFS-Objekte synchronisieren",
summary: "Ein normaler Pull in Gitty prüft nach erfolgreicher Git-Synchronisierung automatisch auf LFS und lädt benötigte Objekte mit demselben Remote und denselben Zugangsdaten. Ein zweiter Pull ist nicht nötig.",
steps: [
"Normales Push nutzt den LFS-Pre-push-Hook und lädt neue LFS-Objekte vor den Git-Referenzen hoch.",
"Objekte laden im LFS-Dialog ist ein manueller Reparatur- oder Aktualisierungsschritt, falls lokale Inhalte fehlen.",
"Cache bereinigen entfernt sicher nicht mehr benötigte lokale Objekte; aktuell verwendete und noch nicht gepushte Inhalte bleiben erhalten.",
],
commands: [
{ command: "git lfs status", description: "LFS-Zustand und ausstehende Änderungen prüfen" },
{ command: "git lfs pull", description: "Benötigte LFS-Objekte manuell laden" },
{ command: "git lfs prune", description: "Nicht mehr benötigte lokale LFS-Objekte bereinigen" },
{ command: "git lfs lock <datei>", description: "Eine als lockable markierte Datei auf einem kompatiblen Remote sperren" },
],
},
{
id: "lfs-migrate",
title: "Bestehende Dateien umstellen",
summary: "Ein neu hinzugefügtes Tracking-Muster schreibt vorhandene Commits nicht rückwirkend um. Aktuelle Dateien lassen sich neu normalisieren; eine vollständige Migration verändert dagegen die Historie.",
commands: [
{ command: "git add --renormalize .", description: "Aktuelle Dateien erneut durch die neuen LFS-Regeln führen" },
{ command: "git lfs migrate import --include=\"*.psd\" --everything", description: "Passende Dateien in der gesamten Historie nach LFS migrieren" },
],
note: "Vorsicht: git lfs migrate import schreibt Commit-Hashes um. Stimme die Migration mit allen Beteiligten ab, erstelle vorher ein Backup und rechne bei bereits veröffentlichten Branches mit einem koordinierten Force-Push.",
},
],
});
enCategories.splice(enCategories.findIndex((category) => category.id === "remote") + 1, 0, {
id: "lfs",
label: "Git LFS",
description: "Track large binary files, synchronize objects, and migrate existing repositories safely.",
sections: [
{
id: "lfs-overview",
title: "What Git LFS does",
summary: "Git LFS replaces large files in Git history with small pointer files. The actual content is stored in the remote's LFS storage and downloaded for the relevant checkout or pull.",
steps: [
"Use LFS mainly for large binary files such as PSDs, videos, audio, models, or archives that Git cannot usefully manage as text diffs.",
"Gitty bundles the Git LFS extension in desktop installers and displays its version, filters, and pre-push hook in the LFS dialog.",
"LFS rules live in .gitattributes, so commit them to the repository like regular source code.",
],
},
{
id: "lfs-setup",
title: "Set up Git LFS in Gitty",
summary: "Setup applies to the currently open repository and does not change global Git settings.",
steps: [
"Open the Sync menu in the repository and select Git LFS.",
"Select Activate LFS so Gitty configures the local filters and pre-push hook.",
"Add a pattern such as *.psd, Assets/**, or video.mp4. Lockable marks files that can be locked through a compatible LFS server.",
"Stage and commit .gitattributes together with the files you want to track.",
],
commands: [
{ command: "git lfs install --local", description: "Activate LFS only in the current repository" },
{ command: "git lfs track \"*.psd\"", description: "Manage a file pattern through Git LFS" },
{ command: "git add .gitattributes", description: "Stage the generated tracking rules" },
],
},
{
id: "lfs-sync",
title: "Synchronize LFS objects",
summary: "After a successful regular pull, Gitty automatically checks for LFS and downloads required objects with the same remote and credentials. A second pull is not necessary.",
steps: [
"A normal push uses the LFS pre-push hook to upload new LFS objects before Git references are published.",
"Pull objects in the LFS dialog is a manual repair or refresh action when local content is missing.",
"Prune cache safely removes unused local objects while retaining current and unpushed content.",
],
commands: [
{ command: "git lfs status", description: "Inspect LFS state and pending changes" },
{ command: "git lfs pull", description: "Download required LFS objects manually" },
{ command: "git lfs prune", description: "Remove unused local LFS objects" },
{ command: "git lfs lock <file>", description: "Lock a lockable file on a compatible remote" },
],
},
{
id: "lfs-migrate",
title: "Migrate existing files",
summary: "Adding a tracking pattern does not rewrite existing commits. Current files can be renormalized, while a complete migration changes repository history.",
commands: [
{ command: "git add --renormalize .", description: "Run current files through the new LFS rules again" },
{ command: "git lfs migrate import --include=\"*.psd\" --everything", description: "Move matching files to LFS throughout repository history" },
],
note: "Caution: git lfs migrate import rewrites commit hashes. Coordinate the migration with every contributor, create a backup first, and expect a coordinated force push for published branches.",
},
],
});
// Extended handbook chapters. Keeping these additions next to the shared data makes
// it straightforward to compare the German and English coverage section by section.
deCategories.find((category) => category.id === "start")?.sections.push(
@@ -1703,6 +1824,7 @@
{:else if category.id === "basics"}<GitCommitHorizontal size={17} aria-hidden="true" />
{:else if category.id === "branches"}<GitBranch size={17} aria-hidden="true" />
{:else if category.id === "remote"}<Cloud size={17} aria-hidden="true" />
{:else if category.id === "lfs"}<Box size={17} aria-hidden="true" />
{:else if category.id === "troubleshooting"}<Wrench size={17} aria-hidden="true" />
{:else if category.id === "workflows"}<ListChecks size={17} aria-hidden="true" />
{:else if category.id === "reference"}<Library size={17} aria-hidden="true" />
@@ -1715,7 +1837,7 @@
<div class="help-nav-tip">
<span class="help-tip-icon"><Lightbulb size={16} aria-hidden="true" /></span>
<span>{isGerman ? "Suche auch nach Befehlen wie" : "Try commands such as"} <code>rebase</code>, <code>stash</code> {isGerman ? "oder" : "or"} <code>reflog</code>.</span>
<span>{isGerman ? "Suche auch nach Befehlen wie" : "Try commands such as"} <code>rebase</code>, <code>lfs</code> {isGerman ? "oder" : "or"} <code>reflog</code>.</span>
</div>
</nav>