This is an in-progress translation.
To help translate the book, please fork the book at GitHub and push your contributions.

Git attributen

Een aantal van deze instellingen kan ook gedaan worden voor een pad, zodat Git die instellingen alleen toepast om een submap of subset van bestanden. Deze pad-specifieke instellingen worden Git attributen genoemd en worden in een .gitattribute bestand in een van je mappen (normaliter in de hoofdmap van je project) of in het .git/attributes bestand als je niet wilt dat het attributes bestand gecommit wordt met je project.

Door attributes te gebruiken kun je dingen doen als het specificeren van aparte samenvoeg strategieën voor individuele bestanden of mappen in je project, Git vertellen hoe hij niet-tekst bestanden kan diff’en, of Git inhoud laten filteren voordat je het in- of uitchecked van Git. In deze sectie zul je iets leren over de attributen die je kun instellen op de paden in je Git project en een paar voorbeelden zien hoe je deze eigenschap in de praktijk gebruikt.

Binaire bestanden

Een stoere truc waarvoor je Git attributen kunt gebruiken is het vertellen aan Git welke bestanden binair zijn (in die gevallen waarin hij het niet zelf kan uitvinden) en Git dan speciale instructies geven hoe die bestanden te behandelen. Bijvoorbeeld, sommige tekstbestanden worden gegenereerd en zijn niet te diff’en, of sommige binaire bestanden kunnen wel gediff’ed worden – je zult zien hoe je Git verteld welke soort het is.

Binaire bestanden identificeren

Sommige bestanden zien eruit als tekstbestanden, maar moeten toch behandeld worden als binaire gegevens. Bijvoorbeeld, Xcode projecten op de Mac bevatten een bestand dat eindigt in .pbxproj, wat eigenlijk een JSON (platte tekst javascript gegevens formaat) gegevensset is, dat geschreven wordt naar de schijf door de IDE, waarin je bouw instellingen opgeslagen zijn enzovoorts. Alhoewel het technisch een tekstbestand is, omdat het volledig ASCII is, zul je het niet als zodanig willen behandelen omdat het eigenlijk een lichtgewicht gegevensbank is – je kunt de inhoud niet samenvoegen als twee mensen het gewijzigd hebben, en diffs zijn over het algemeen niet behulpzaam. Het bestand is bedoeld om geconsumeerd te worden door een machine. In essentie wil je het behandelen als een binair bestand.

Om Git te vertellen dat hij alle pbxproj bestanden als binaire gegevens moet behandelen, voeg je de volgende regel toe aan je .gitattributes bestand:

*.pbxproj -crlf -diff

Nu zal Git niet proberen om CRLF problemen te veranderen of te repareren; noch zal het proberen een diff te berekenen of te tonen voor de veranderingen in dit bestand als je git show of git diff uitvoert op je project. In de 1.6 serie van Git, kun je ook een macro gebruiken die meegeleverd wordt, en die -crlf -diff betekend:

*.pbxproj binary

Binaire bestanden diff’en

In de 1.6 serie van Git, kun je de functionaliteit van Git attributen gebruiken om binaire bestanden effectief te diff’en. Je doet dit door Git te vertellen hoe het binaire gegevens naar tekst formaat moet omzetten, die dan via de normale diff vergeleken kan worden.

Omdat dit een erg stoer en weinig gebruikte eigenschap is, zal ik een paar voorbeelden laten zien. Eerst zul je deze techniek gebruiken om een van de meest irritante problemen van deze mensheid op te lossen: Word documenten versie beheren. Iedereen weet dat Word een van de meest erge editors is die er te vinden is; maar, vreemd genoeg, gebruikt iedereen het. Als je Word documenten wil beheren, kun je ze in een Git repository stoppen en eens in de zoveel tijd committen; maar waar is dat goed voor? Als je git diff op een normale manier uitvoert, zie je alleen zoiets als dit:

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

Je kunt twee versies niet direct vergelijken, tenzij je ze uitchecked en ze handmatig doorloopt, toch? Het blijkt dat je dit redelijk goed kunt doen door Git attributen te gebruiken. Stop de volgende regel in je .gitattributes bestand:

*.doc diff=word

Dit verteld Git dat ieder bestand dat dit patroon past (.doc) het “word” filter zou moeten gebruiken als je een diff probeert te bekijken, die veranderingen bevat. Wat is het “word” filter? Je zult het moeten instellen. Hier zul je Git configureren om het strings programma te gebruiken om Word documenten in leesbare tekstbestanden om te vormen, die het dan fatsoenlijk kan diff’en:

$ git config diff.word.textconv strings

Nu weet Git dat als het een diff probeert te doen tussen twee snapshots, en een van de bestanden eindigt in .doc, dan zou het deze bestanden door het “word” filter moeten halen, wat is gedefinieerd als het strings programma. Dit maakt effectief twee tekst-gebaseerde versies van je Word bestanden, alvorens ze proberen te diff’en.

Hier is een voorbeeld. Ik heb Hoofdstuk 1 van dit boek in Git gestopt, wat tekst aan een paragraaf toegevoegd, en het document bewaard. Daarna heb ik git diff uitgevoerd om te zien wat er gewijzigd is:

$ git diff
diff --git a/chapter1.doc b/chapter1.doc
index c1c8a0a..b93c9e4 100644
--- a/chapter1.doc
+++ b/chapter1.doc
@@ -8,7 +8,8 @@ re going to cover Version Control Systems (VCS) and Git basics
 re going to cover how to get it and set it up for the first time if you don
 t already have it on your system.
 In Chapter Two we will go over basic Git usage - how to use Git for the 80%
-s going on, modify stuff and contribute changes. If the book spontaneously
+s going on, modify stuff and contribute changes. If the book spontaneously
+Let's see if this works.

Git verteld me succesvol en beknopt dat ik de tekst “Let’s see if this works” heb toegevoegd, wat correct is. Het is niet perfect – het voegt een serie willekeurig spul aan het einde toe – maar het werkt wel. Als je een Word-naar-gewone-tekst omvormer kunt vinden of schrijven die goed genoeg werkt, dan zal die oplossing waarschijnlijk zeer effectief zijn. Maar, strings is op de meeste Mac en Linux machines beschikbaar, dus dit kan een goede eerste poging zijn om dit te gebruiken bij andere binaire formaten.

Een ander interessant probleem dat je hiermee kunt oplossen in het diff’en van beeldbestanden. Een manier om dit te doen is JPEG bestanden door een filter te halen dat hun EXIF informatie eruit peutert – metadata die wordt opgeslagen met de meeste beeldbestanden. Als je het exiftool programma download en installeert, kun je het gebruiken om je plaatjes naar tekst over de metadata om te zetten, zodat de diff op z’n minst een tekstuele representatie van eventuele wijzigingen laat zien:

$ echo '*.png diff=exif' >> .gitattributes
$ git config diff.exif.textconv exiftool

Als je een plaatje in je project veranderd en git diff uitvoert, dan zie je zoiets als dit:

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

Je kunt eenvoudig zien dat zowel de bestandsgrootte als de beeld dimensies gewijzigd zijn.

Keyword expansie

Keyword expansie zoals in SVN of CVS wordt vaak gevraagd door ontwikkelaars, die gewend zijn aan die systemen. Het grote probleem in Git is dat je een bestand niet mag wijzigen met informatie over de commit, nadat je het gecommit hebt, omdat Git eerst de checksum van het bestand maakt. Maar, je kunt tekst in een bestand injecteren zodra het uitgechecked wordt en opnieuw verwijderen voordat het aan een commit toegevoegd wordt. Met Git attributen zijn er twee manieren om dit te doen.

Als eerste kun je de SHA-1 checksum van een blob automatisch in een $Id$ veld in het bestand stoppen. Als je dit attribuut op een bestand of serie bestanden insteld, dan zal Git de volgende keer dat je die branch uitchecked dat veld vervangen met de SHA-1 van de blob. Het is belangrijk om op te merken dat het niet de SHA van de commit is, maar van de blob zelf:

$ echo '*.txt ident' >> .gitattributes
$ echo '$Id$' > test.txt

De volgende keer dat je dit bestand uitchecked, injecteert Git de SHA van de blob:

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

Maar, het resultaat is slechts beperkt bruikbaar. Als je sleutelwoord vervanging in CVS of Subversion gebruikt hebt, kun je een tijdstempel toevoegen – de SHA is niet zo bruikbaar, omdat het vrij willekeurig is en je kunt niet zeggen of een SHA ouder of nieuwer is dan een andere.

Het blijkt dat je je eigen filters voor het doen van vervanging bij commit/checkout kunt schrijven. Dit zijn de “clean” en “smudge” filters. In het .gitattributes bestand, kun je een filter op bepaalde paden instellen en dan scripts instellen die bestanden bewerkt vlak voordat ze gecommit worden (“clea”, zie Figuur 7-2) en vlak voordat ze uitgechecked worden (“smudge”, zie Figuur 7-3). De filters kunnen ingesteld worden zodat ze allerlei leuke dingen doen.


Figuur 7-2. Het “smudge” filter wordt bij checkout uitgevoerd.


Figuur 7-3. Het “clean” filter wordt uitgevoerd zodra bestanden worden gestaged.

De originele commit boodschap voor deze functionaliteit geeft een eenvoudig voorbeeld hoe je al je C broncode door het indent programma kunt halen alvorens te committen. Je kunt het instellen door het filter attribuut in je .gitattributes bestand te veranderen zodat *.c bestanden door het “inden” filter gehaald worden:

*.c     filter=indent

Vervolgens vertel je Git wat het “indent” filter doet bij smudge en clean:

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

In dit geval zal Git, als je bestanden commit die op *.c passen, ze door het indent programma halen alvorens ze te committen, en ze door het cat programma halen alvorens ze op de schijf uit te checken. Het cat programma is eigenlijk een no-op: het spuugt dezelfde gegevens uit als dat het inneemt. Deze combinatie zal effectief alle C broncode bestanden door indent filteren alvorens te committen.

Een ander interessant voorbeeld is $Date$ sleutelwoord expansie, in RCS stijl. Om dit goed te doen, moet je een klein script hebben dat een bestandsnaam pakt, de laatste commit datum voor dit project uitvogelt, en de datum in een bestand toevoegt. Hier volgt een klein Ruby script dat dat doet:

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

Het enige dat het script doet is de laatste commit datum uit het git log commando halen, het in iedere $Date$ tekst stoppen die het in stdin ziet, en de resultaten afdrukken – het moet eenvoudig te doen zijn in welke taal je je ook thuisvoelt. Je kunt dit bestand expand_date noemen en het in je pad stoppen. Nu moet je een filter in Git instellen (noem het dater), en het vertellen je expand_date filter te gebruiken om de bestanden tijdens checkout te ‘smudgen’. Je zult een Perl expressie gebruiken om dat op te ruimen tijdens een commit:

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

Dit Perl stukje haalt alles weg dat het in en $Date$ tekst ziet, om terug te komen vanwaar je gekomen bent. Nu je filter klaar is, kun je het testen door een bestand aan te maken met je $Date$ sleutelwoord en dan een Git attribuut voor dat bestand in te stellen, die het nieuwe filter gebruikt.

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

Als je die veranderingen commit en het bestand opnieuw uitchecked, zul je zien dat het sleutelwoord vervangen is:

$ git add date_test.txt .gitattributes
$ git commit -m "Testing 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$

Je kunt wel zien hoe krachtig deze techniek is voor gebruik in eigengemaakte toepassingen. Je moet wel voorzichtig zijn, om dat het .gitattributes bestand ook gecommit wordt en meegestuurd wordt met het project, maar het filter (in dit geval dater) niet; dus het zal niet overal werken. Als je deze filters ontwerpt, zouden ze in staat moeten zijn om netjes te falen en het project nog steeds goed te laten werken.

Je repository exporteren

De Git attribute gegevens staan je ook toe om interessante dingen te doen als je een archief van je project exporteert.

export-ignore

Je kunt Git vertellen dat sommige bestanden of mappen niet geëxporteerd moeten worden als een archief gegenereerd wordt. Als er een submap of bestand is waarvan je niet wil dat het wordt meegenomen in je archief bestand, maar dat je wel in je project ingechecked wil hebben, dan kun je die bestanden bepalen met behulp van het export-ignore attribuut.

Bijvoorbeeld, stel dat je wat testbestanden in een test/ submap hebt, en dat het geen zin heeft om die in de tarball export van je project mee te nemen. Je kunt dan de volgende regel in je Git attributes bestand toevoegen:

test/ export-ignore

Als je nu git archive uitvoert om een tarball van je project te maken, zal die map niet meegenomen worden in het archief.

export-subst

Iets anders dat je kunt doen met je archieven is eenvoudige sleutelwoord vervanging. Git staat je toe om de tekst $Format:$ in ieder bestand met ieder van de --pretty=format formaat afkortingen te zetten, waarvan je er al veel zag in Hoofdstuk 2. Bijvoorbeeld, als je een bestand genaamd LAST_COMMIT wilt meenemen in je project, en de laatste commit datum was hierin automatisch geïnjecteerd toen git archive bezig was, kun je het bestand als volgt instellen:

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

Als je git archive uitvoert, zal de inhoud van dat bestand als mensen het archief bestand openen er zo uit zien:

$ cat LAST_COMMIT
Last commit date: $Format:Tue Apr 21 08:38:48 2009 -0700$

Samenvoeg strategieën

Je kunt Git attributen ook gebruiken om Git te vertellen dat het verschillende samenvoeg strategieën moet gebruiken voor specifieke bestanden in je project. Een erg handige optie is Git te vertellen dat het niet moet proberen bepaalde bestanden samen te voegen als ze conflicten hebben, maar jouw versie moeten gebruiken in plaats van andermans versie.

Dit is handig als een branch in je project af is geweken of gespecialiseerd is, maar je in staat wil zijn om veranderingen daarvan terug samen te voegen, en je wilt bepaalde bestanden negeren. Stel dat je een gegevensbank instellingen bestand hebt dat database.xml heet en dat in twee branches verschillend is, en je wilt in je andere branch samenvoegen zonder het gegevensbank bestand te verprutsen. Je kunt dan een attribuut als volgt instellen:

database.xml merge=ours

Als je in de andere branch samenvoegt, dan zul je in plaats van samenvoeg conflicten met je database.xml bestand zoiets als dit zien:

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

In dit geval blijft database.xml staan op welke versie je origineel ook had.