Kapitel ▾ 2. Auflage

8.2 Anpassen von Git - Git-Attribute

Git-Attribute

Einige dieser Einstellungen können auch für einen Pfad festgelegt werden, sodass Git diese Einstellungen nur für ein Unterverzeichnis oder eine Teilmenge von Dateien anwendet. Diese pfadspezifischen Einstellungen werden als Git-Attribute bezeichnet und entweder in einer .gitattributes-Datei in einem Ihrer Verzeichnisse (normalerweise die Wurzel Ihres Projekts) oder in der Datei .git/info/attributes festgelegt, wenn Sie die Attributdatei nicht mit Ihrem Projekt committen möchten.

Mit Attributen können Sie beispielsweise separate Zusammenführungsstrategien für einzelne Dateien oder Verzeichnisse in Ihrem Projekt angeben, Git mitteilen, wie Nicht-Textdateien verglichen werden sollen, oder Git Inhalte filtern lassen, bevor Sie sie in Git einchecken oder daraus auschecken. In diesem Abschnitt erfahren Sie mehr über einige der Attribute, die Sie für Ihre Pfade in Ihrem Git-Projekt festlegen können, und sehen einige Beispiele für die praktische Anwendung dieser Funktion.

Binärdateien

Ein interessanter Trick, für den Sie Git-Attribute verwenden können, ist die Angabe, welche Dateien binär sind (in Fällen, in denen Git dies sonst nicht herausfinden kann), und Git spezielle Anweisungen zur Handhabung dieser Dateien zu geben. Einige Textdateien können beispielsweise maschinell generiert und nicht diffbar sein, während einige Binärdateien diffbar sein können. Sie werden sehen, wie Sie Git mitteilen, welche Dateien wie zu behandeln sind.

Identifizierung von Binärdateien

Einige Dateien sehen aus wie Textdateien, sollen aber für alle Zwecke als Binärdaten behandelt werden. Xcode-Projekte unter macOS enthalten beispielsweise eine Datei mit der Endung .pbxproj, die im Grunde ein JSON-Datensatz (JavaScript-Datenformat in reinem Text) ist, der von der IDE auf die Festplatte geschrieben wird und Ihre Build-Einstellungen usw. aufzeichnet. Obwohl es sich technisch um eine Textdatei handelt (da sie vollständig UTF-8 ist), möchten Sie sie nicht so behandeln, da es sich im Grunde um eine leichte Datenbank handelt – Sie können die Inhalte nicht zusammenführen, wenn zwei Personen sie ändern, und Diffs sind im Allgemeinen nicht hilfreich. Die Datei ist für die maschinelle Verarbeitung bestimmt. Im Wesentlichen möchten Sie sie wie eine Binärdatei behandeln.

Um Git mitzuteilen, dass alle pbxproj-Dateien als Binärdaten behandelt werden sollen, fügen Sie die folgende Zeile zu Ihrer .gitattributes-Datei hinzu:

*.pbxproj binary

Nun wird Git keine CRLF-Probleme konvertieren oder beheben; es wird auch nicht versuchen, einen Diff für Änderungen an dieser Datei zu berechnen oder auszugeben, wenn Sie git show oder git diff für Ihr Projekt ausführen.

Vergleich von Binärdateien

Sie können die Git-Attributfunktionalität auch verwenden, um Binärdateien effektiv zu vergleichen. Dies geschieht, indem Sie Git mitteilen, wie Ihre Binärdaten in ein Textformat konvertiert werden können, das über den normalen Diff verglichen werden kann.

Zuerst werden Sie diese Technik verwenden, um eines der ärgerlichsten Probleme der Menschheit zu lösen: die Versionskontrolle von Microsoft Word-Dokumenten. Wenn Sie Word-Dokumente versionieren möchten, können Sie sie in ein Git-Repository legen und sie gelegentlich committen; aber was bringt das? Wenn Sie normalerweise git diff ausführen, sehen Sie nur etwas wie das:

$ git diff
diff --git a/chapter1.docx b/chapter1.docx
index 88839c4..4afcb7c 100644
Binary files a/chapter1.docx and b/chapter1.docx differ

Sie können zwei Versionen nicht direkt vergleichen, es sei denn, Sie checken sie aus und scannen sie manuell, richtig? Es stellt sich heraus, dass Sie dies mit Git-Attributen ziemlich gut tun können. Fügen Sie die folgende Zeile zu Ihrer .gitattributes-Datei hinzu:

*.docx diff=word

Dies teilt Git mit, dass jede Datei, die diesem Muster (.docx) entspricht, den "word"-Filter verwenden soll, wenn Sie versuchen, einen Diff anzuzeigen, der Änderungen enthält. Was ist der "word"-Filter? Sie müssen ihn einrichten. Hier konfigurieren Sie Git so, dass das Programm docx2txt verwendet wird, um Word-Dokumente in lesbare Textdateien zu konvertieren, die dann ordnungsgemäß verglichen werden.

Zuerst müssen Sie docx2txt installieren. Sie können es von https://sourceforge.net/projects/docx2txt herunterladen. Befolgen Sie die Anweisungen in der Datei INSTALL, um es so zu platzieren, dass Ihre Shell es finden kann. Als Nächstes schreiben Sie ein Wrapper-Skript, um die Ausgabe in das von Git erwartete Format zu konvertieren. Erstellen Sie eine Datei irgendwo in Ihrem Pfad namens docx2txt und fügen Sie diesen Inhalt hinzu:

#!/bin/bash
docx2txt.pl "$1" -

Vergessen Sie nicht, diese Datei mit chmod a+x ausführbar zu machen. Schließlich können Sie Git konfigurieren, um dieses Skript zu verwenden:

$ git config diff.word.textconv docx2txt

Jetzt weiß Git, dass es die Dateien, die mit .docx enden, durch den "word"-Filter laufen lassen soll, der als docx2txt-Programm definiert ist, wenn es versucht, einen Diff zwischen zwei Snapshots durchzuführen. Dies konvertiert Ihre Word-Dateien effektiv in gut lesbare textbasierte Versionen, bevor versucht wird, sie zu vergleichen.

Hier ist ein Beispiel: Kapitel 1 dieses Buches wurde in das Word-Format konvertiert und in einem Git-Repository committet. Dann wurde ein neuer Absatz hinzugefügt. Hier ist, was git diff zeigt:

$ git diff
diff --git a/chapter1.docx b/chapter1.docx
index 0b013ca..ba25db5 100644
--- a/chapter1.docx
+++ b/chapter1.docx
@@ -2,6 +2,7 @@
 This chapter will be about getting started with Git. We will begin at the beginning by explaining some background on version control tools, then move on to how to get Git running on your system and finally how to get it setup to start working with. At the end of this chapter you should understand why Git is around, why you should use it and you should be all setup to do so.
 1.1. About Version Control
 What is "version control", and why should you care? Version control is a system that records changes to a file or set of files over time so that you can recall specific versions later. For the examples in this book you will use software source code as the files being version controlled, though in reality you can do this with nearly any type of file on a computer.
+Testing: 1, 2, 3.
 If you are a graphic or web designer and want to keep every version of an image or layout (which you would most certainly want to), a Version Control System (VCS) is a very wise thing to use. It allows you to revert files back to a previous state, revert the entire project back to a previous state, compare changes over time, see who last modified something that might be causing a problem, who introduced an issue and when, and more. Using a VCS also generally means that if you screw things up or lose files, you can easily recover. In addition, you get all this for very little overhead.
 1.1.1. Local Version Control Systems
 Many people's version-control method of choice is to copy files into another directory (perhaps a time-stamped directory, if they're clever). This approach is very common because it is so simple, but it is also incredibly error prone. It is easy to forget which directory you're in and accidentally write to the wrong file or copy over files you don't mean to.

Git teilt uns erfolgreich und prägnant mit, dass wir die Zeichenkette "Testing: 1, 2, 3." hinzugefügt haben, was korrekt ist. Es ist nicht perfekt – Formatierungsänderungen würden hier nicht angezeigt werden –, aber es funktioniert sicherlich.

Ein weiteres interessantes Problem, das Sie auf diese Weise lösen können, betrifft den Vergleich von Bilddateien. Eine Möglichkeit, dies zu tun, besteht darin, Bilddateien durch einen Filter zu leiten, der ihre EXIF-Informationen extrahiert – Metadaten, die mit den meisten Bildformaten aufgezeichnet werden. Wenn Sie das Programm exiftool herunterladen und installieren, können Sie es verwenden, um Ihre Bilder in Text über die Metadaten zu konvertieren, sodass der Diff zumindest eine textuelle Darstellung aller aufgetretenen Änderungen anzeigt. Fügen Sie die folgende Zeile zu Ihrer .gitattributes-Datei hinzu:

*.png diff=exif

Konfigurieren Sie Git, um dieses Tool zu verwenden

$ git config diff.exif.textconv exiftool

Wenn Sie ein Bild in Ihrem Projekt ersetzen und git diff ausführen, sehen Sie etwas wie das:

diff --git a/image.png b/image.png
index 88839c4..4afcb7c 100644
--- a/image.png
+++ b/image.png
@@ -1,12 +1,12 @@
 ExifTool Version Number         : 7.74
-File Size                       : 70 kB
-File Modification Date/Time     : 2009:04:21 07:02:45-07:00
+File Size                       : 94 kB
+File Modification Date/Time     : 2009:04:21 07:02:43-07:00
 File Type                       : PNG
 MIME Type                       : image/png
-Image Width                     : 1058
-Image Height                    : 889
+Image Width                     : 1056
+Image Height                    : 827
 Bit Depth                       : 8
 Color Type                      : RGB with Alpha

Sie können leicht erkennen, dass sowohl die Dateigröße als auch die Bildabmessungen geändert wurden.

Schlüsselwortexpansion

Entwickler, die mit SVN- oder CVS-Systemen vertraut sind, fordern oft eine Schlüsselwortexpansion im SVN- oder CVS-Stil. Das Hauptproblem dabei in Git ist, dass Sie eine Datei nicht mit Informationen über den Commit ändern können, nachdem Sie sie committet haben, da Git die Datei zuerst prüft. Sie können jedoch Text in eine Datei einfügen, wenn sie ausgecheckt wird, und ihn wieder entfernen, bevor sie zu einem Commit hinzugefügt wird. Git-Attribute bieten Ihnen zwei Möglichkeiten, dies zu tun.

Erstens können Sie die SHA-1-Prüfsumme eines Blobs automatisch in ein $Id$-Feld in der Datei einfügen. Wenn Sie dieses Attribut für eine Datei oder eine Reihe von Dateien festlegen, ersetzt Git beim nächsten Auschecken dieses Branches dieses Feld durch die SHA-1-Prüfsumme des Blobs. Wichtig ist, dass es sich nicht um die SHA-1-Prüfsumme des Commits handelt, sondern um die des Blobs selbst. Fügen Sie die folgende Zeile zu Ihrer .gitattributes-Datei hinzu:

*.txt ident

Fügen Sie einen $Id$-Referenz zu einer Testdatei hinzu

$ echo '$Id$' > test.txt

Wenn Sie diese Datei das nächste Mal auschecken, fügt Git die SHA-1-Prüfsumme des Blobs ein:

$ rm test.txt
$ git checkout -- test.txt
$ cat test.txt
$Id: 42812b7653c7b88933f8a9d6cad0ca16714b9bb3 $

Dieses Ergebnis ist jedoch von begrenztem Nutzen. Wenn Sie in CVS oder Subversion eine Schlüsselwortsubstitution verwendet haben, können Sie ein Datum einfügen – die SHA-1-Prüfsumme ist nicht sehr hilfreich, da sie ziemlich zufällig ist und man allein durch Hinsehen nicht erkennen kann, ob eine SHA-1-Prüfsumme älter oder neuer ist als eine andere.

Es stellt sich heraus, dass Sie Ihre eigenen Filter für die Ersetzung von Texten in Dateien beim Commit/Checkout schreiben können. Diese werden als "clean"- und "smudge"-Filter bezeichnet. In der Datei .gitattributes können Sie einen Filter für bestimmte Pfade festlegen und dann Skripte einrichten, die Dateien verarbeiten, kurz bevor sie ausgecheckt werden ("smudge", siehe Der "smudge"-Filter wird beim Auschecken ausgeführt) und kurz bevor sie gestaged werden ("clean", siehe Der "clean"-Filter wird beim Stagen von Dateien ausgeführt). Diese Filter können so eingestellt werden, dass sie alle möglichen lustigen Dinge tun.

The “smudge” filter is run on checkout
Abbildung 169. Der "smudge"-Filter wird beim Auschecken ausgeführt
The “clean” filter is run when files are staged
Abbildung 170. Der "clean"-Filter wird beim Stagen von Dateien ausgeführt

Die ursprüngliche Commit-Nachricht für diese Funktion gibt ein einfaches Beispiel für die Ausführung Ihres gesamten C-Quellcodes durch das indent-Programm vor dem Commit. Sie können dies einrichten, indem Sie das Filterattribut in Ihrer .gitattributes-Datei festlegen, um \*.c-Dateien mit dem Filter "indent" zu filtern:

*.c filter=indent

Teilen Sie Git dann mit, was der Filter "indent" beim Smudge- und Clean-Vorgang tut:

$ git config --global filter.indent.clean indent
$ git config --global filter.indent.smudge cat

In diesem Fall führt Git die Dateien, die mit *.c übereinstimmen, vor dem Staging durch das indent-Programm und dann vor dem Auschecken auf die Festplatte durch das cat-Programm. Das cat-Programm tut im Wesentlichen nichts: Es gibt dieselben Daten aus, die es erhält. Diese Kombination filtert effektiv alle C-Quellcodedateien vor dem Commit durch indent.

Ein weiteres interessantes Beispiel ist die $Date$-Schlüsselwortexpansion im RCS-Stil. Um dies richtig zu machen, benötigen Sie ein kleines Skript, das einen Dateinamen entgegennimmt, das letzte Commit-Datum für dieses Projekt ermittelt und das Datum in die Datei einfügt. Hier ist ein kleines Ruby-Skript, das dies tut:

#! /usr/bin/env ruby
data = STDIN.read
last_date = `git log --pretty=format:"%ad" -1`
puts data.gsub('$Date$', '$Date: ' + last_date.to_s + '$')

Das Skript ermittelt lediglich das neueste Commit-Datum aus dem Befehl git log, fügt es in alle $Date$-Strings ein, die es in stdin sieht, und gibt die Ergebnisse aus – dies sollte in jeder Sprache, mit der Sie sich am wohlsten fühlen, einfach zu bewerkstelligen sein. Sie können diese Datei expand_date nennen und in Ihren Pfad legen. Jetzt müssen Sie einen Filter in Git einrichten (nennen Sie ihn dater) und ihm mitteilen, dass er Ihren expand_date-Filter zum Smudgen der Dateien beim Auschecken verwenden soll. Sie verwenden einen Perl-Ausdruck, um dies beim Commit zu bereinigen:

$ git config filter.dater.smudge expand_date
$ git config filter.dater.clean 'perl -pe "s/\\\$Date[^\\\$]*\\\$/\\\$Date\\\$/"'

Dieses Perl-Snippet entfernt alles, was es in einem $Date$-String sieht, um zum Ausgangspunkt zurückzukehren. Jetzt, da Ihr Filter bereit ist, können Sie ihn testen, indem Sie ein Git-Attribut für diese Datei einrichten, das den neuen Filter aktiviert, und eine Datei mit Ihrem $Date$-Schlüsselwort erstellen:

date*.txt filter=dater
$ echo '# $Date$' > date_test.txt

Wenn Sie diese Änderungen committen und die Datei erneut auschecken, sehen Sie, dass das Schlüsselwort richtig ersetzt wurde:

$ git add date_test.txt .gitattributes
$ git commit -m "Test date expansion in Git"
$ rm date_test.txt
$ git checkout date_test.txt
$ cat date_test.txt
# $Date: Tue Apr 21 07:26:52 2009 -0700$

Sie können sehen, wie leistungsfähig diese Technik für angepasste Anwendungen sein kann. Sie müssen jedoch vorsichtig sein, da die Datei .gitattributes committet und mit dem Projekt weitergegeben wird, der Treiber (in diesem Fall dater) jedoch nicht, sodass er nicht überall funktioniert. Wenn Sie diese Filter entwerfen, sollten sie in der Lage sein, fehlerhaft zu scheitern und das Projekt weiterhin ordnungsgemäß funktionieren zu lassen.

Exportieren Ihres Repositorys

Git-Attributdaten ermöglichen es Ihnen auch, interessante Dinge beim Exportieren eines Archivs Ihres Projekts zu tun.

export-ignore

Sie können Git mitteilen, bestimmte Dateien oder Verzeichnisse beim Generieren eines Archivs nicht zu exportieren. Wenn es ein Unterverzeichnis oder eine Datei gibt, die Sie nicht in Ihre Archivdatei aufnehmen möchten, aber in Ihr Projekt einchecken möchten, können Sie diese Dateien über das Attribut export-ignore bestimmen.

Nehmen wir beispielsweise an, Sie haben einige Testdateien in einem Unterverzeichnis test/, und es macht keinen Sinn, sie in den Tarball-Export Ihres Projekts aufzunehmen. Sie können die folgende Zeile zu Ihrer Git-Attributdatei hinzufügen:

test/ export-ignore

Wenn Sie nun git archive ausführen, um einen Tarball Ihres Projekts zu erstellen, wird dieses Verzeichnis nicht in das Archiv aufgenommen.

export-subst

Beim Exportieren von Dateien für die Bereitstellung können Sie die Formatierungs- und Schlüsselwortexpansionsverarbeitung von git log auf ausgewählte Teile von Dateien anwenden, die mit dem Attribut export-subst gekennzeichnet sind.

Wenn Sie beispielsweise eine Datei namens LAST_COMMIT in Ihr Projekt aufnehmen möchten und Metadaten über den letzten Commit automatisch in diese Datei eingefügt werden sollen, wenn git archive ausgeführt wird, können Sie Ihre .gitattributes- und LAST_COMMIT-Dateien wie folgt einrichten:

LAST_COMMIT export-subst
$ echo 'Last commit date: $Format:%cd by %aN$' > LAST_COMMIT
$ git add LAST_COMMIT .gitattributes
$ git commit -am 'adding LAST_COMMIT file for archives'

Wenn Sie git archive ausführen, sieht der Inhalt der archivierten Datei wie folgt aus:

$ git archive HEAD | tar xCf ../deployment-testing -
$ cat ../deployment-testing/LAST_COMMIT
Last commit date: Tue Apr 21 08:38:48 2009 -0700 by Scott Chacon

Die Ersetzungen können beispielsweise die Commit-Nachricht und alle git notes umfassen, und git log kann einfache Zeilenumbrüche durchführen.

$ echo '$Format:Last commit: %h by %aN at %cd%n%+w(76,6,9)%B$' > LAST_COMMIT
$ git commit -am 'export-subst uses git log'\''s custom formatter

git archive uses git log'\''s `pretty=format:` processor
directly, and strips the surrounding `$Format:` and `$`
markup from the output.
'
$ git archive @ | tar xfO - LAST_COMMIT
Last commit: 312ccc8 by Jim Hill at Fri May 8 09:14:04 2015 -0700
       export-subst uses git log's custom formatter

         git archive uses git log's `pretty=format:` processor directly, and
         strips the surrounding `$Format:` and `$` markup from the output.

Das resultierende Archiv eignet sich für die Bereitstellungsarbeit, ist aber wie jedes exportierte Archiv nicht für die weitere Entwicklungsarbeit geeignet.

Merge-Strategien

Sie können Git-Attribute auch verwenden, um Git mitzuteilen, dass es für bestimmte Dateien in Ihrem Projekt unterschiedliche Merge-Strategien verwenden soll. Eine sehr nützliche Option besteht darin, Git mitzuteilen, dass es bestimmte Dateien nicht versuchen soll, wenn sie Konflikte aufweisen, sondern stattdessen Ihre Seite des Merges über die eines anderen verwenden soll.

Dies ist hilfreich, wenn ein Branch in Ihrem Projekt abgewichen oder spezialisiert ist, Sie aber Änderungen von ihm zurückmergen möchten und bestimmte Dateien ignorieren möchten. Angenommen, Sie haben eine Datenbank-Einstellungsdatei namens database.xml, die sich in zwei Branches unterscheidet, und Sie möchten Ihren anderen Branch ohne Probleme mit der Datenbankdatei zusammenführen. Sie können ein Attribut wie folgt einrichten:

database.xml merge=ours

Und dann eine Dummy-ours-Merge-Strategie definieren mit:

$ git config --global merge.ours.driver true

Wenn Sie den anderen Branch zusammenführen, sehen Sie anstelle von Merge-Konflikten mit der Datei database.xml etwas wie das:

$ git merge topic
Auto-merging database.xml
Merge made by recursive.

In diesem Fall bleibt database.xml in der Version, die Sie ursprünglich hatten.