Das Problem
restic und borg haben
keine Option, .gitignore-Dateien zu lesen. Wer ein Verzeichnis mit
Quellcode sichert, sichert entweder jedes node_modules, jede virtuelle
Umgebung und jeden Build-Ordner mit oder pflegt Exclude-Muster von Hand.
Die Anfrage ist alt:
- restic/restic#1514, "Use .gitignore (or other VCS) for excluding files", hat 70 Reaktionen und 27 Kommentare und ist seit Dezember 2017 offen. Im Thread stimmte ein restic-Beitragender für "an easy way to use a suitable external tool", statt die Funktion in restic einzubauen.
- restic/restic#4227, der Pull Request mit einer Umsetzung, hat 20 Reaktionen und ist seit März 2023 ohne Review offen.
- borgbackup/borg#641 hat 24 Reaktionen; borg#937, "exclude files specified in .gitignore", wurde ohne Umsetzung geschlossen.
Die dort geposteten Umgehungen leiten rg --files oder fd in
restic backup --files-from-verbatim. Sie werten Ignore-Dateien mit einer
anderen Implementierung aus als Git, und jede gelistete Datei wird zu einem
Snapshot-Pfad. Die Pfadliste ändert sich also, sobald eine Datei hinzukommt
oder wegfällt (restic#2246
beschreibt das daraus folgende Problem bei der Erkennung des
Eltern-Snapshots). Ein anderer Ansatz schreibt CACHEDIR.TAG-Dateien in
ignorierte Verzeichnisse. Das verändert die Working Trees und kann keine
einzelnen Dateien ausschließen.
Wie es funktioniert
gitignore-excludes durchläuft
die angegebenen Verzeichnisse und führt in jedem Verzeichnis, das .git
enthält, diesen Befehl aus:
git ls-files -z --others --ignored --exclude-standard --directory
Das Ergebnis folgt damit den Regeln von Git selbst, einschließlich
verschachtelter .gitignore-Dateien, Negation (!keep.log),
.git/info/exclude und core.excludesFile. Versionierte Dateien werden nie
ausgeschlossen, auch wenn ein Muster auf sie passt. Ein vollständig
ignoriertes Verzeichnis wird zu einer Zeile, und der Durchlauf steigt nicht
hinein. Jeder Pfad wird als absolutes, maskiertes Muster geschrieben, das nur
auf diesen Pfad passt. Für restic werden *, ?, [ und \ maskiert, und
$ wird zu $$, weil restic Umgebungsvariablen in Exclude-Dateien
ersetzt. Für borg werden die Pfade zu wörtlichen Pfadpräfixen pp:, Namen mit
Leerraum am Ende zu fm:-Mustern mit maskierten Zeichen.
--keep GLOB behält ignorierte Einträge, deren Name passt, zum Beispiel
.env*; ein ignoriertes Verzeichnis mit einer behaltenen Datei wird dann
Eintrag für Eintrag ausgeschlossen.
Beispiel aus dem README:
$ gitignore-excludes ~/code > /tmp/code-excludes.txt
gitignore-excludes: 11 working trees, 27 ignored paths
$ restic backup ~/code --exclude-file /tmp/code-excludes.txt
$ head -4 /tmp/code-excludes.txt
# generated by gitignore-excludes; regenerate before each backup
/home/me/code/api/.venv
/home/me/code/api/src/api/__pycache__
/home/me/code/web/node_modules
Bei einem Ordner mit 11 Git-Repositories sank das restic-Backup von 4253 Dateien (86,6 MiB) auf 604 Dateien (1,1 MiB). Das Werkzeug braucht Python 3.10 oder neuer und git, sonst keine Abhängigkeiten.
Tests
Die Tests bauen einen Fixture-Baum mit zwei Repositories, eines davon im
anderen verschachtelt. Er enthält verschachtelte .gitignore-Dateien, ein
negiertes Muster, eine versionierte Datei, auf die ein Ignore-Muster passt,
ignorierte Verzeichnisse, ein Verzeichnis außerhalb jedes Repositorys und
Dateinamen mit *, ?, [, \, $ sowie Leerzeichen am Anfang oder Ende.
Die Unit-Tests prüfen, dass der Scan genau die erwartete Menge an Pfaden
liefert, dass ein Repository in einem ignorierten Verzeichnis nicht
gesondert durchsucht wird, wie --keep ein ignoriertes Verzeichnis aufteilt
und die Maskierung für beide Formate.
Die End-to-End-Tests sichern den Fixture-Baum mit den echten Programmen
restic und borg und der erzeugten Exclude-Datei und vergleichen den Inhalt
des Snapshots mit den Dateien, die Git nicht ignoriert. Der restic-Test läuft
zusätzlich mit --keep '.env*' und setzt HOME auf einen Platzhalter, um zu
prüfen, dass ein $HOME in einem Dateinamen nicht ersetzt wird. Das CI führt
die Tests auf Python 3.10 bis 3.13 mit restic 0.19.1 und borg 1.4.5 aus.
Grenzen
- Ignorierte Dateien sind oft Geheimnisse wie
.env-Dateien und Keystores, die es womöglich nirgends sonst gibt. Das README empfiehlt, einmal mit-f plainzu laufen, die Liste zu lesen und--keep-Muster zu ergänzen. - Die Liste enthält konkrete Pfade und muss vor jedem Backup neu erzeugt werden.
- Werkzeug und Backup-Befehl müssen dieselben Wurzelpfade bekommen. Symlinks werden nicht verfolgt.
- Nur Git-Working-Trees werden behandelt. Ein Repository in einem ignorierten Verzeichnis wird mit diesem ausgeschlossen.
- Dateinamen mit Zeilenumbruch lassen sich nicht exakt schreiben; der
Zeilenumbruch wird zu
?. - Getestet ist es nur unter Linux.
Code und Installationsanleitung liegen unter github.com/Jwrede/gitignore-excludes.