Git
Chapters ▾ 2nd Edition

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

Атрибуты Git

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

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

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

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

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

Некоторые файлы выглядят как текстовые, но работать с ними нужно как с бинарными данными. Например, на Mac в Xcode проекте содержится файл с расширением .pbxproj, который генерируется IDE, содержит различные настройки проекта и сохраняется в формате JSON (текстовое представление Javascript данных). Технически - это текстовый файл (потому как все данные в 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; вы можете скачать её здесь http://docx2txt.sourceforge.net. Следуйте инструкциям из файла 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, вы сможете использовать её для сравнения изменений метаданных изображения:

$ echo '*.png diff=exif' >> .gitattributes
$ 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 хэш не коммита, а самого объекта:

$ echo '*.txt ident' >> .gitattributes
$ 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'' применяется при извлечении.
Рисунок 144. Фильтр “smudge” применяется при извлечении.
Фильтр ``clean'' применяется при индексации.
Рисунок 145. Фильтр “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 атрибут, чтобы для вашего файла применялся созданный фильтр:

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

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

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

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

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

export-ignore

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

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

test/ export-ignore

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

export-subst

При создании архива так же доступна простая подстановка по ключевым словам. Git допускает размещение строк $Format:$ в любом файле с любой комбинацией --pretty=format кодов, большинство из которых вам известны по Разделу 2. Например, если вы хотите добавить файл с именем LAST_COMMIT, в который будет добавляться дата последнего коммита при создании архива, то сделайте следующее:

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

Теперь, при создании архива проекта командой git archive, в него будет включен файл со следующим содержанием:

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

Стратегии слияния

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

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

database.xml merge=ours

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

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

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

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

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