Git
Chapters ▾ 2nd Edition

8.2 Настройване на Git - Git атрибути

Git атрибути

Някои от настройките на Git може да се прилагат към конкретен път, така че да важат за определена поддиректория или набор от файлове. Този вид настройки се наричат Git атрибути и се задават или във файл с име .gitattributes в една от директориите (основно главната) или във файла .git/info/attributes.

Използвайки атрибути можете да правите неща като например отделни стратегии за сливане за индивидуални файлове или директории в проекта, да указвате на Git как да прави diff на файлове, които не са текстови, или да накарате Git да филтрира съдържание преди да ви го извлече или индексира. Тук ще разгледаме малко примери за използване на атрибути.

Двоични файлове

Един хубав трик, който можете да приложите, е да кажете на Git кои файлове са двоични (в случай, че по някаква причина не се разпознават като такива) и да подадете специални инструкции за тяхната обработка. Например, някои текстови файлове може да са машинно генерирани и да не може да им се направи diff, докато за някои други двоични файлове това е възможно. Ето как да кажете на Git кой какъв е.

Идентифициране на двоични файлове

Някои файлове изглеждат като текстови, но може да искате данните в тях се третират като двоични. Например, Xcode проектите в Mac съдържат файл с окончание .pbxproj, който всъщност е JSON (текстов формат за структуриране на данни) информация записана на диска от IDE средата и съхранява настройки за компилиране и други данни. Въпреки, че технически това е текстов файл (изцяло UTF-8), не искате да го третирате като такъв, понеже той играе ролята на олекотена база данни — не можете да слеете модификациите, ако двама души го променят и в общи линни diff информацията не е особено полезна. Файлът просто е предназначен да се обработва от машината. Реално, искате да го третирате като двоичен.

За да кажете на Git, че искате да третира всички pbxproj файлове като двоични, добавете това във вашия .gitattributes файл:

*.pbxproj binary

Сега Git няма да се опитва да конвертира или коригира CRLF проблеми, нито ще се опитва да изчислява или печата diff за промените в такива файлове при изпълнение на git show или git diff.

Diff за двоични файлове

Можете обаче да накарате Git да прави diff на двоични файлове. Това става, като указвате на системата да конвертира двоичната информация в текст, който да се съпоставя с нормална diff операция.

Ще използваме тази техника за решаване на един от най-досадните известни проблеми: контрол на версиите за Microsoft Word документи. За мнозина Word е един от най-ужасните редактори, но така или иначе продължава да се ползва масово. Ако искате да контролирате Word документи, можете да ги съберете в Git хранилище и да къмитвате от време на време, но каква полза получавате с това? Ако нормално изпълните git diff, виждате нещо такова:

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

Не можете директно да съпоставите две версии, докато не ги извлечете и разгледате на ръка. Оказва се, че можете да правите това добре с Git атрибути. Сложете следния ред в .gitattributes файла:

*.docx diff=word

Това указва на Git, че ако файлът отговаря на тази маска (.docx), то той трябва да използва филтъра “word”, при опит за конструиране на diff. Какво е “word” филтър? Ще трябва да го настроите. В случая, ще използваме програмата docx2txt за да конвертираме Word документите в четими текстови файлове, за които могат да се правят четими diff-ове.

Първо инсталираме docx2txt; налична е на http://docx2txt.sourceforge.net. Следваме инструкциите в INSTALL файла за да я сложим на място, в което шелът да я намира. След това, ще напишем кратък wrapper скрипт за конвертиране на изхода във формат, който Git очаква. Създайте файл достъпен в пътя ви с име docx2txt, съдържащ:

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

След това изпълнете chmod a+x за него. Последно, конфигурираме Git да използва скрипта:

$ git config diff.word.textconv docx2txt

Сега Git знае, че ако се опитва да направи diff между два snapshot-а и някой от файловете е с окончание .docx, той трябва да го прекара през “word” филтъра, дефиниран като docx2txt програмата. Това на практика произвежда текстови версии на Word документите преди да опита да ги съпостави за diff.

Ето пример: Глава 1 от тази книга е конвертирана в Word формат и къмитната в Git хранилище. След това е добавен нов параграф. Ето изхода от 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 успешно забелязва промяната и сбито ни казва, че сме добавили стринга “Testing: 1, 2, 3.”. Разбира се, това не е перфектно — промените по форматирането няма да се покажат.

Друг интересен проблем е съпоставянето на файлове с изображения. Един начин да правите това е да прекарате изображение през филтър, който извлича EXIF информацията от него — това са метаданни съхранявани с повечето формати за изображения. Ако изтеглите и инсталирате програмата exiftool, може да я ползвате за да извличате метаданните, така че diff поне ще ви покаже в текстов вид направените промени. Сложете реда отдолу в .gitattributes файла:

*.png diff=exif

Настройте Git да използва инструмента:

$ git config diff.exif.textconv exiftool

Ако замените изображение в проекта и изпълните git diff, виждате нещо подобно:

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

Така поне можем да видим, че са променени размерите на изображението и големината на файла.

Попълване на ключови думи

SVN- или CVS-style попълването на ключови думи често се използва от разработчиците използващи тези системи. Основният проблем с това в Git е, че не можете да вмъкнете файл с информация за къмита след като сте къмитнали, защото Git първо изчислява чексума за файла. Обаче, можете да вмъквате текст във файл, когато файлът е извлечен и да го премахнете преди добавянето в къмит. Git атрибутите предлагат два начина за това.

Първо, можете да вмъкнете SHA-1 чексумата на обект в $Id$ поле във файла автоматично. Ако зададете този атрибут за файл или множество файлове, тогава следващия път когато извлечете съдържанието на такъв клон Git ще замени това поле с SHA-1 стойността на обекта. Важно е да се помни, че това не е SHA-1 на къмита, а на самия blob обект. Сложете такъв ред в .gitattributes:

*.txt ident

Добавете $Id$ референция в пробен файл:

$ echo '$Id$' > test.txt

Следващия път, когато извлечете този файл, Git вмъква SHA-1 на blob обекта:

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

Обаче, това е с ограничена полза. Ако сте използвали заместване на ключови думи в CVS или Subversion, можете да вмъквате datestamp — SHA-1 стойността не е толкова полезна, защото е случайна и само гледайки я не можете да кажете дали е по-стара или по-нова от друга такава.

Оказва се, че можете да пишете свои собствени филтри за правене на замествания във файлове по време на къмитване или извличане. Тези фитри са известни като “clean” и “smudge” филтри. Във файла .gitattributes, можете да настроите филтър за определени пътища и след това да направите скриптове, които да обработват файловете непосредствено преди да се извлекат (“smudge”, вижте Филтърът “smudge” се изпълнява по време на извличане.) и непосредствено преди да се индексират (“clean”, вижте Филтърът “clean” се изпълнява по време на индексиране на файлове.). С тези филтри могат да се правят най-различни забавни неща.

Филтърът ``smudge'' се изпълнява по време на извличане.
Figure 144. Филтърът “smudge” се изпълнява по време на извличане.
Филтърът ``clean'' се изпълнява по време на индексиране на файлове.
Figure 145. Филтърът “clean” се изпълнява по време на индексиране на файлове.

Оригиналното къмит съобщение за тази функция дава прост пример за вмъкване на отстъпи в C сорс код преди къмитване. Можете да направите такъв филтър с атрибут в .gitattributes файла за да обработите всички *.c файлове с филтъра “indent”:

*.c filter=indent

След това, казваме на Git какво ще прави филтъра “indent” при операциите smudge и clean:

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

В този случай, когато къмитнете файлове с окончание *.c, Git ще ги прекара през програмата indent преди да ги индексира и също така ще ги прекара през cat програмата, когато ги извлича обратно на диска. Програмата cat по същество не върши нищо освен да печата на екрана данните, които е получила. Ефективно тази комбинация филтрира всички C сорс файлове през indent преди къмитване.

Друг интересен пример е с $Date$ keyword expansion израза, RCS стил. За да направите това правилно, нуждаете се от малък скрипт, който взема име на файл, намира последната дата на къмит за този проект и я вмъква във файла. Ето малък Ruby скрипт, който прави това:

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

Скриптът взема датата на последния къмит от командата git log, прикрепя я към всеки $Date$ стринг, който вижда на stdin и печата резултатите — би трябвало да може лесно да се направи с произволен език за програмиране. Можете да дадете на скрипта име expand_date и да го вмъкнете в пътя си. Следващата стъпка е да направите филтър в Git (наречен dater) и да му укажете да използва вашия expand_date скрипт по време на checkout на файлове. Ще използваме регулярен израз на Perl за clean операцията по време на къмитване:

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

Тази отрязък код премахва всичко, което намери в $Date$ стринг за да получите обратно данните си. Сега можем да тестваме филтъра създавайки Git атрибут, който активира филтъра и файл с ключова дума $Date$:

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

Ако сега къмитнете тези промени и извлечете файла отново, ще видите ключовата дума заместена със съответната дата:

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

Можете да видите колко мощна може да е тази техника за специализирани приложения. Все пак трябва да се внимава, защото файлът .gitattributes се къмитва и разпраща заедно с проекта, но не и съответния филтър (в този случай dater), който на друга машина няма да работи. Проектирайки тези филтри, те трябва да могат да правят fail gracefully, така че проектът да работи правилно.

Експорт на хранилище

Атрибутите на Git позволяват да правите и други интересни неща, когато архивирате или експортирате проект.

export-ignore

Можете да кажете на Git да не експортира определени файлове или директории, когато генерира архив. Ако има поддиректория или файл, които не искате да влизат в архива, но да присъстват в самия проект, можете да ги укажете чрез атрибута export-ignore.

Например, имате тестови файлове в поддиректория test/ и не желаете да влизат в архива на проекта. Можете да зададете това със следния ред във вашия Git attributes файл:

test/ export-ignore

Сега, когато изпълните git archive, за да създадете tarball архив на проекта, тази директория няма да влиза в архива.

export-subst

Когато експортирате файлове за внедряване, можете да прилагате правилата за форматиране и разширяване на ключови думи на git log към избрани части от файлове маркирани с атрибута export-subst.

Например, ако искате да вмъкнете файл с име LAST_COMMIT и съдържащ метаданните от последния къмит в проекта си автоматично, когато изпълните git archive, можете да го направите със следните .gitattributes и LAST_COMMIT файлове:

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'

Когато пуснете git archive, съдържанието на архивния файл ще изглежда така:

$ 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

Заместванията могат например да включват къмит съобщението и наличните git notes и git log може да прави word wrapping:

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

Полученият архив е подходящ за внедряване, но подобно на всеки експортиран архив, не е подходящ за бъдеща програмистка работа.

Стратегии за сливане

Можете също така да изполвате Git атрибути за да укажете на Git да използва различни merge стратегии за специфични файлове в проекта. Една много полезна възможност е например да укажете, че не искате Git да опитва да слива определени файлове, когато при тях има конфликти, а вместо това директно да използва вашата версия на тези файлове с предимство.

Това е полезно, ако даден клон от проекта ви се е разклонил или е специализиран такъв, но искате да можете да сливате промените от него и да игнорирате определени файлове. Да кажем, че имате файл database.xml с конфигурация за достъп до база данни, който е различен за два клона и искате да слеете другия клон без да намесвате този файл в сливането. Можете да направите такъв атрибут:

database.xml merge=ours

След което създавате лъжлива merge стратегия ours:

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

Ако сега слеете другия клон, вместо да получите конфликт при сливането за database.xml, ще видите нещо такова:

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

В този случай database.xml си остава в оригиналната версия.