Git
Chapters ▾ 2nd Edition

8.2 Настройка Git - Атрибуты Git

Атрибуты Git

Некоторые из настроек могут быть применены к каталогу, поэтому Git применяет их только к подкаталогам или набору файлов. Настройки, зависящие от пути, называются атрибутами и могут быть установлены либо в файле .gitattributes в любом из каталогов проекта (обычно, в корневом каталоге), либо в файле .git/info/attributes, если вы не хотите хранить их в репозитории вместе с вашим проектом.

Используя атрибуты, вы можете настраивать различные стратегии слияния для отдельных файлов или каталогов вашего проекта, указать Git как сравнивать бинарные файлы, настраивать фильтры добавления или извлечения данных из репозитория. В этом разделе вы узнаете о некоторых атрибутах, которые можно установить для заданных путей в вашем проекте и рассмотрите несколько практических примеров.

Бинарные файлы

Интересная возможность атрибутов Git, которая может вам пригодиться, заключается в указании файлов, которые следует считать бинарными (особенно, когда определить это не представляется возможным), и определении инструкций как именно Git должен обрабатывать эти файлы. Например, некоторые текстовые файлы могут генерироваться автоматически и отследить изменения в них невозможно, в то же время, отследить изменения некоторых бинарных файлов вполне возможно. Далее вы увидите как объяснить Git где какой файл.

Идентификация бинарных файлов

Некоторые файлы выглядят как текстовые, но работать с ними нужно как с бинарными данными. Например, проекты Xcode на macOS содержат файл с расширением .pbxproj, который по сути является набором данных JSON (текстовое представление данных JavaScript), сохранённым на диск IDE и содержащим различные настройки проекта. Технически — это текстовый файл (потому как все данные в UTF-8), фактически — легковесная база данных; отслеживание изменений в нём бесполезно, а слияние изменений — сделанных двумя людьми — невозможно, поэтому вы вряд ли захотите обрабатывать его как текстовый файл. Файл предназначен для чтения машиной, а не человеком. Вот почему его следует рассматривать как бинарный.

Чтобы Git начал обрабатывать все pbxproj файлы как бинарные, добавьте в файл .gitattributes следующую строку:

*.pbxproj binary

Теперь, Git не будет конвертировать или исправлять CRLF в этом файле; не будет пытаться определить изменения или выводить их на экран при выполнении команд git show или git diff.

Сравнение бинарных файлов

Возможности атрибутов Git так же можно использовать для эффективного сравнения бинарных файлов. Сделать это можно указав Git каким образом конвертировать бинарные данные в текстовый формат, чтобы затем сравнить их обычным способом.

Этот подход решает одну из самых досадных проблем, известных человечеству: контроль версий документов 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, что нужно использовать фильтр «word» при просмотре изменений файлов, соответствующих шаблону .docx. Что такое «word» фильтр? Вам следует его настроить. Нужно сконфигурировать Git на использование программы docx2txt для конвертации документов Word в текстовые, которые можно корректно сравнивать.

Для начала, нужно установить программу docx2txt; вы можете скачать её здесь https://sourceforge.net/projects/docx2txt. Следуйте инструкциям из файла INSTALL для её установки и настройки запуска из командной строки. Затем следует написать скрипт обёртку для конвертации вывода программы в формат, понятный Git. Создайте файл docx2txt в любом доступном для запуска месте и добавьте в него следующее содержимое (прим. пер.: применимо для Linux и Mac):

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

Не забудьте добавить права запуска для созданного файла. Наконец, настройте Git на использование созданного скрипта:

$ git config diff.word.textconv docx2txt

Теперь Git знает, что при сравнении двух коммитов, имена файлов в которых заканчиваются на .docx, он должен обработать эти файлы с помощью фильтра «word», который определён как программа docx2txt. Это позволяет автоматически генерировать текстовые версии файлов Word и только потом их сравнивать.

Для примера, первый раздел этой книги был сохранен в формате 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, вы сможете использовать её для конвертации изображений в текстовые метаданные, изменение которых будет являться текстовым представлением изменений изображений. Добавьте следующую строку в ваш файл .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. Основная проблема в Git — это невозможность изменять файлы с информацией о коммите после его совершения, так как Git сначала вычисляет контрольную сумму. Однако, вы можете добавить текст в файл после извлечения и убрать его перед добавлением файла в коммит. Атрибуты Git позволяют это сделать двумя способами.

Для начала, вы можете автоматически добавлять SHA-1-хеш объекта в поле $Id$. Если установить этот атрибут для одного или нескольких файлов, то каждый раз при извлечении ветки Git будет заменять это поле на SHA-1-хеш объекта. Важно заметить, что это SHA-1-хеш не коммита, а самого объекта. Добавьте следующую строку в ваш файл .gitattributes:

*.txt ident

Добавьте ссылку на $Id$ в тестовый файл:

$ echo '$Id$' > test.txt

При последующих извлечениях Git будет добавлять SHA-1-хеш объекта:

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

Однако, этот результат имеет ограниченное применение. Если вы использовали подстановку ключевых слов в CVS или Subversion, то вы могли включать метку времени, а SHA-1-хеш не так полезен, потому что вы не можете сказать какой из двух хешей старше, а какой новее просто взглянув на них.

Оказывается, вы можете написать свои фильтры для выполнения подстановок в файлах в момент коммита/извлечения. Эти фильтры называются «clean» и «smudge». В файле .gitattributes вы можете установить фильтр для конкретных путей, а затем указать скрипты для обработки файлов при извлечении («smudge», смотри Фильтр «smudge» применяется при извлечении) и при индексировании («clean», смотри Фильтр «clean» применяется при индексации). С помощью этих фильтров можно делать всевозможные операции.

Фильтр «smudge» применяется при извлечении
Рисунок 143. Фильтр «smudge» применяется при извлечении
Фильтр «clean» применяется при индексации
Рисунок 144. Фильтр «clean» применяется при индексации

Исходное сообщение коммита является простым примером как передать весь ваш код на С программе indent перед коммитом. Это можно настроить путём указания фильтра «indent» в файле .gitattributes для файлов *.c.

*.c filter=indent

Затем скажите Git что должен делать фильтр «indent» на стадиях smudge и clean:

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

В таком случае, Git будет обрабатывать файлы программой indent все файлы по маске *.c перед тем, как добавить их в индекс; и наоборот, обрабатывать эти же файлы программой cat при их извлечении. По сути, программа cat ничего не делает: она возвращает те же данные, что и получает на вход. Указанная комбинация позволяет эффективно обрабатывать файлы с исходным кодом на С программой indent перед коммитом.

Другой интересный пример — это подстановка ключевого слова $Date$ в стиле системы контроля ревизий. Чтобы правильно это реализовать, вам нужен простой скрипт, который получает имя файла, определяет дату последнего коммита и вставляет её в файл. Ниже приведен небольшой пример такого скрипта на 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$ и возвращает итоговый результат; вы можете написать аналогичный скрипт на любом языке. Назовите файл со скриптом, например, expand_date и сохраните в каталоге с программами. Теперь, нужно настроить Git фильтр (назовите его dater) и укажите ему использовать ваш скрипт expand_date при извлечении файлов. Вместе с этим, мы будем использовать регулярное выражение Perl для очистки перед коммитом:

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

Указанная Perl команда очищает любое значение в строке, где она видит $Date$, чтобы вернуть файл в изначальное состояние. Теперь фильтр готов и вы можете проверить его добавив ключевое слово $Date$ в файл и настроив Git атрибут, чтобы для вашего файла применялся созданный фильтр:

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

Если добавить в коммит последние изменения, а затем извлечь файл, то вы увидите корректную подстановку ключевого слова:

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

Как вы могли заметить, описанный подход предоставляет большие возможности. Однако, вам стоит быть осторожным, так как файл .gitattributes включается в коммит и распространяется вместе с проектом, а драйвер (в данном случае dater) нет, поэтому он не будет работать везде. Учитывайте это при разработке фильтров оставляя возможность работы без них — так вы сохраните проект в рабочем состоянии.

Экспорт репозитория

Атрибуты Git так же позволяют вам делать некоторые интересные вещи при экспорте вашего проекта.

export-ignore

Вы можете указать Git игнорировать определённые файлы и каталоги при создании архива. Если в вашем проекте есть файл или каталог, которые вам нужны, но вы не хотите включать их в архив при экспорте, то можно присвоить им атрибут export-ignore.

Например, у вас есть несколько файлов в каталоге test/ и совершенно нет смысла включать их в архив вашего проекта. В этом случае достаточно добавить следующую строку в файл .gitattributes:

test/ export-ignore

Теперь, при создании архива проекта командой git archive, каталог test/ не будет включен в архив.

export-subst

При создании архива так же доступна подстановка по ключевым словам и поддерживается использование форматирования git log для файлов, отмеченных атрибутом export-subst.

Например, если вы хотите добавить в проект файл с именем LAST_COMMIT, в который при экспорте будут автоматически сохраняться метаданные последнего коммита, то измените файлы .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 может делать простой перенос слов:

$ 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 не сливать изменения в определённых файлах в случае конфликта, при этом использовать вашу версию файла.

Это полезно в случае, когда ветка разошлась или у вас специализированная ветка, при этом вы хотите иметь возможность сливать изменения, но игнорировать определённые файлы. Предположим, что у вас есть файл с настройками базы данных database.xml, содержимое которого в разных ветках отличается, при этом вы хотите сливать изменения из другой ветки не меняя этот файл. Для этого нужно добавить следующий атрибут:

database.xml merge=ours

А затем определить фиктивную стратегию слияния ours, выполнив команду:

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

Если вы сольёте изменения из другой ветки, то вместо конфликта слияния для файла database.xml вы увидите что-то вроде этого:

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

В этом случае файл database.xml всегда остаётся неизменным.

scroll-to-top