Chapters ▾ 2nd Edition

8.2 Anpassa Git - Git‑attribut

Git‑attribut

Vissa av dessa inställningar kan också anges för en sökväg, så att Git tillämpar dem endast för en underkatalog eller en delmängd filer. Dessa sökvägsspecifika inställningar kallas Git‑attribut och sätts antingen i en .gitattributes‑fil i någon av dina kataloger (vanligen projektroten) eller i .git/info/attributes om du inte vill checka in attributfilen i projektet.

Med attribut kan du till exempel ange separata sammanslagningsstrategier för enskilda filer eller kataloger i projektet, tala om för Git hur den ska diffa icke‑textfiler eller låta Git filtrera innehåll innan du checkar in eller lägger ut det. I det här avsnittet lär du dig om några attribut du kan sätta på sökvägar i ditt Git‑projekt och ser några exempel på hur funktionen används i praktiken.

Binära filer

Ett smart trick du kan använda med Git‑attribut är att tala om för Git vilka filer som är binära (i fall där den annars inte kan lista ut det) och ge Git särskilda instruktioner om hur dessa filer ska hanteras. Till exempel kan vissa textfiler vara maskinskapade och svåra att diffa, medan vissa binära filer kan diffas. Du får se hur du talar om för Git vilket som är vilket.

Identifiera binära filer

Vissa filer ser ut som textfiler men ska i praktiken behandlas som binärdata. Till exempel innehåller Xcode‑projekt på macOS en fil som slutar på .pbxproj, som i praktiken är en JSON‑liknande dataset som IDE:n skriver till disk och som beskriver bygginställningar och liknande. Även om det tekniskt sett är en textfil (eftersom den är UTF‑8) vill du inte behandla den som sådan eftersom det i praktiken är en lättviktig databas – du kan inte sammanfoga innehållet om två personer ändrar den, och diffar är sällan hjälpsamma. Filen är tänkt att konsumeras av en maskin. I praktiken vill du behandla den som en binär fil.

För att tala om för Git att behandla alla pbxproj‑filer som binärdata, lägg till följande rad i din .gitattributes‑fil:

*.pbxproj binary

Nu försöker Git varken konvertera eller fixa CRLF‑problem; inte heller försöker den beräkna eller skriva ut en diff för ändringar i den här filen när du kör git show eller git diff på projektet.

Diffa binära filer

Du kan också använda Git‑attribut för att i praktiken diffa binära filer. Det gör du genom att tala om för Git hur den ska konvertera binärdata till ett textformat som kan jämföras med en vanlig diff.

Först använder vi tekniken för att lösa ett av de mest frustrerande problemen som finns: versionshantering av Microsoft Word‑dokument. Om du vill versionshantera Word‑dokument kan du lägga dem i ett Git‑kodförråd och checka in då och då; men vad hjälper det? Om du kör git diff som vanligt ser du bara något i stil med detta:

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

Du kan inte jämföra två versioner direkt om du inte lägger ut dem och granskar dem manuellt, eller hur? Det visar sig att du kan göra detta ganska bra med Git‑attribut. Lägg följande rad i din .gitattributes‑fil:

*.docx diff=word

Detta talar om för Git att alla filer som matchar mönstret (.docx) ska använda filtret “word” när du vill se en diff som innehåller ändringar. Vad är filtret “word”? Du måste ställa in det. Här konfigurerar du Git att använda programmet docx2txt för att konvertera Word‑dokument till läsbar text, som sedan diffas korrekt.

Först behöver du installera docx2txt; du kan ladda ner det från https://sourceforge.net/projects/docx2txt. Följ instruktionerna i INSTALL‑filen så att det hamnar någonstans där ditt skal hittar det. Sedan skriver du ett omslagsskript som konverterar utdata till formatet Git förväntar sig. Skapa en fil som ligger någonstans i din sökväg och heter docx2txt, och lägg in detta innehåll:

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

Glöm inte chmod a+x på den filen. Till sist konfigurerar du Git att använda det här skriptet:

$ git config diff.word.textconv docx2txt

Nu vet Git att om den försöker diffa två ögonblicksbilder och någon fil slutar på .docx, ska den köra de filerna genom “word”‑filtret, vilket i detta fall är programmet docx2txt. Detta ger i praktiken läsbara textversioner av dina Word‑filer innan diffen beräknas.

Här är ett exempel: kapitel 1 i den här boken konverterades till Word‑format och incheckades i ett Git‑kodförråd. Sedan lades ett nytt stycke till. Så här visar git diff:

$ 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 talar kort och gott om att vi lade till strängen “Testing: 1, 2, 3.”, vilket stämmer. Det är inte perfekt – formateringsändringar syns inte här – men det fungerar absolut.

Ett annat intressant problem du kan lösa på detta sätt är att diffa bildfiler. Ett sätt är att köra bildfiler genom ett filter som extraherar deras EXIF‑information – metadata som lagras med de flesta bildformat. Om du laddar ner och installerar programmet exiftool kan du använda det för att konvertera dina bilder till text om metadata, så att diffen åtminstone visar en textrepresentation av ändringar som skett. Lägg följande rad i din .gitattributes‑fil:

*.png diff=exif

Konfigurera Git att använda detta verktyg:

$ git config diff.exif.textconv exiftool

Om du ersätter en bild i ditt projekt och kör git diff ser du något i stil med detta:

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

Du ser lätt att filstorleken och bilddimensionerna har ändrats.

Nyckelordsexpansion

Nyckelordsexpansion i SVN‑ eller CVS‑stil efterfrågas ofta av utvecklare som är vana vid de systemen. Huvudproblemet i Git är att du inte kan modifiera en fil med information om incheckningen efter att du checkat in, eftersom Git först beräknar filens kontrollsumma. Du kan däremot injicera text i en fil när den läggs ut och ta bort den igen innan den köas. Git‑attribut ger dig två sätt att göra detta.

Först kan du automatiskt injicera SHA‑1‑kontrollsumman för en blob i ett $Id$‑fält i filen. Om du sätter detta attribut på en fil eller uppsättning filer kommer Git nästa gång du växlar till den grenen att ersätta fältet med blob‑SHA‑1:an. Det är viktigt att notera att det inte är SHA‑1:an för incheckningen, utan för blobben själv. Lägg följande rad i din .gitattributes‑fil:

*.txt ident

Lägg till en $Id$‑referens i en testfil:

$ echo '$Id$' > test.txt

Nästa gång du lägger ut filen injicerar Git blob‑SHA‑1:an:

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

Detta resultat har dock begränsad nytta. Om du har använt nyckelordssubstitution i CVS eller Subversion kan du inkludera en datumstämpel – SHA‑1:an är inte särskilt hjälpsam, eftersom den är ganska slumpmässig och du kan inte se om en SHA‑1 är äldre eller nyare än en annan bara genom att titta på dem.

Det visar sig att du kan skriva egna filter för att göra substitutioner i filer vid incheckning/utläggning. Dessa kallas “clean”‑ och “smudge”‑filter. I .gitattributes‑filen kan du sätta ett filter för vissa sökvägar och sedan sätta upp skript som bearbetar filer strax innan de läggs ut (“smudge”, se “smudge”‑filtret körs vid utläggning) och strax innan de köas (“clean”, se “clean”‑filtret körs när filer köas). Dessa filter kan ställas in för att göra många olika saker.

`smudge`‑filtret körs vid utläggning
Figur 167. “smudge”‑filtret körs vid utläggning
`clean`‑filtret körs när filer köas
Figur 168. “clean”‑filtret körs när filer köas

Det ursprungliga incheckningsmeddelandet för denna funktion ger ett enkelt exempel på att köra all C‑källkod genom programmet indent innan incheckning. Du kan ställa in det genom att sätta filterattributet i din .gitattributes‑fil så att \*.c‑filer filtreras med “indent”‑filtret:

*.c filter=indent

Berätta sedan för Git vad “indent”‑filtret gör vid smudge och ren:

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

I detta fall kommer Git, när du checkar in filer som matchar *.c, att köra dem genom indent innan den köar dem och sedan köra dem genom cat innan den lägger ut dem tillbaka till disk. Programmet cat gör i praktiken ingenting: det spottar ut samma data som det får in. Den här kombinationen filtrerar alltså all C‑källkod genom indent innan incheckning.

Ett annat intressant exempel är $Date$‑nyckelordsexpansion i RCS‑stil. För att göra detta korrekt behöver du ett litet skript som tar ett filnamn, tar reda på senaste incheckningsdatum för projektet och stoppar in datumet i filen. Här är ett litet Ruby‑skript som gör det:

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

Skriptet gör bara att ta senaste incheckningsdatum från git log, stoppa in det i alla $Date$‑strängar det ser på stdin och skriva ut resultatet – det bör vara enkelt att göra i vilket språk du än föredrar. Du kan kalla filen expand_date och lägga den i din sökväg. Nu behöver du sätta upp ett filter i Git (kalla det dater) och tala om för det att använda filtret expand_date för att smudge‑bearbeta filerna vid utläggning. Du använder ett Perl‑uttryck för att städa upp detta vid incheckning:

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

Den här Perl‑snutten tar bort allt den ser i en $Date$‑sträng, för att komma tillbaka till utgångsläget. Nu när filtret är klart kan du testa det genom att sätta upp ett Git‑attribut för den filen som aktiverar filtret och skapa en fil med ditt $Date$‑nyckelord:

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

Om du checkar in ändringarna och lägger ut filen igen ser du att nyckelordet ersätts korrekt:

$ 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$

Du ser hur kraftfull tekniken kan vara för anpassade tillämpningar. Du måste dock vara försiktig, eftersom .gitattributes‑filen checkas in och skickas runt med projektet, men drivrutinen (i det här fallet dater) gör det inte, så det fungerar inte överallt. När du utformar dessa filter bör de kunna misslyckas snyggt och ändå låta projektet fungera korrekt.

Exportera ditt kodförråd

Git‑attributdata gör det också möjligt att göra intressanta saker när du exporterar ett arkiv av ditt projekt.

export-ignore

Du kan tala om för Git att inte exportera vissa filer eller kataloger när den skapar ett arkiv. Om det finns en underkatalog eller fil som du inte vill inkludera i arkivfilen men som du vill checka in i projektet kan du bestämma dessa filer via attributet export-ignore.

Till exempel, säg att du har testfiler i en underkatalog test/ och att det inte är rimligt att inkludera dem i tar‑exporten av ditt projekt. Du kan lägga följande rad i din Git‑attributfil:

test/ export-ignore

Nu kommer den katalogen inte att inkluderas i arkivet när du kör git archive.

export-subst

När du exporterar filer för driftsättning kan du tillämpa git log‑formatering och nyckelordsexpansion på utvalda delar av filer som markerats med attributet export-subst.

Om du till exempel vill inkludera en fil som heter LAST_COMMIT i projektet, och automatiskt injicera metadata om den senaste incheckningen när git archive körs, kan du till exempel sätta upp dina .gitattributes‑ och LAST_COMMIT‑filer så här:

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'

När du kör git archive kommer innehållet i den arkiverade filen att se ut så här:

$ 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

Substitutionerna kan till exempel inkludera incheckningsmeddelandet och eventuella git notes, och git log kan göra enkel radbrytning:

$ 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.

Det resulterande arkivet lämpar sig för driftsättning, men som vilket exporterat arkiv som helst är det inte lämpat för fortsatt utvecklingsarbete.

Sammanslagningsstrategier

Du kan också använda Git‑attribut för att tala om för Git att använda olika sammanslagningsstrategier för specifika filer i ditt projekt. Ett mycket användbart alternativ är att tala om för Git att inte försöka sammanfoga vissa filer när de har konflikter, utan i stället använda din sida av sammanslagningen framför någon annans.

Det här är hjälpsamt om en gren i projektet har glidit isär eller är specialiserad, men du vill kunna sammanfoga in ändringar från den och samtidigt ignorera vissa filer. Säg att du har en databasinställningsfil som heter database.xml som är olika i två grenar, och du vill sammanfoga den andra grenen utan att stöka till databasfilen. Du kan ställa in ett attribut så här:

database.xml merge=ours

Och sedan definiera en tom "ours"‑sammanslagningsstrategi med:

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

Om du sammanfogar den andra grenen ser du i stället för sammanslagningskonflikter i database.xml något som detta:

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

I det här fallet behåller database.xml den version du redan hade.