1 == Гроссмейстерство Git ==
3 Теперь вы уже должны уметь ориентироваться в страницах *git help* и понимать почти всё. Однако точный выбор команды, необходимой для решения конкретной проблемы, может быть утомительным. Возможно, я сберегу вам немного времени: ниже приведены рецепты, пригодившиеся мне в прошлом.
5 === Релизы исходников ===
7 В моих проектах Git управляет в точности теми файлами, которые я собираюсь архивировать и пускать в релиз. Чтобы создать тарбол с исходниками, я выполняю:
9 $ git archive --format=tar --prefix=proj-1.2.3/ HEAD
11 === Сохранение изменений ===
13 В некоторых проектах может быть трудоёмко оповещать Git отдельно о каждом добавлении, удалении и переименовании файла. Вместо этого вы можете выполнить команды
18 Git просмотрит файлы в текущем каталоге и сам позаботится о деталях. Вместо второй команды add, выполните *git commit -a*, если вы собираетесь сразу сделать коммит. См. в *git help ignore*, чтобы узнать как указать файлы, которые должны игнорироваться.
20 Вы можете выполнить все это одним махом:
22 $ git ls-files -d -m -o -z | xargs -0 git update-index --add --remove
24 Опции *-z* и *-0* предотвращают неверную обработку файловых имен, содержащих специальные символы. Поскольку эта команда добавляет игнорируемые файлы, вы возможно захотите использовать опции -x или -X.
26 === Мой коммит слишком велик ===
28 Вы пренебрегали коммитами слишком долго? Яростно писали код и вспомнили об управлении исходниками только сейчас? Внесли ряд несвязанных изменений, потому что это ваш стиль?
30 Нет поводов для беспокойства. Выполните
34 Для каждой вашей правки Git покажет измененный участок кода и спросит, должно ли это изменение попасть в следующий коммит. Отвечайте «y» или «n». У вас есть идругие варианты, например отложить выбор; введите "?" чтобы узнать больше.
36 Когда закончите, выполните
40 для внесения именно тех правок, что вы выбрали («буферизованных» изменений). Убедитесь, что вы не указали опцию *-a*, иначе Git закоммитит все изменения.
42 Что делать, если вы изменили множество файлов во многих местах? Проверка каждого отдельного изменения становится удручающей рутиной. В этом случае используйте *git add -i*. Её интерфейс не так прост, но более гибок. В несколько нажатий кнопок можно добавить или убрать из буфера несколько файлов одновременно, либо просмотреть и выбрать изменения лишь в отдельных файлах. Как вариант, запустите *git commit \--interactive*, который автоматически сделает коммит когда вы закончите.
44 === Индекс — буферная зона Git ===
46 До сих пор мы избегали знаменитого «индекса» Git, но теперь мы должны рассмотреть её, для пояснения вышесказанного. Индекс это временный буфер. Git редко перемещает данные непосредственно между вашим проектом и его историей. Вместо этого Git сначала записывает данные в индекс, а уж затем копирует их из индекса по месту назначения.
48 Например, *commit -a* на самом деле двухэтапный процесс. Сначала слепок текущего состояния каждого из отслеживаемых файлов помещается в индекс. Затем слепок, находящийся в индексе, записывается в историю. Коммит без опции *-a* выполняет только второй шаг, и имеет смысл только после выполнения команд, изменяющих индекс, таких как *git add*.
50 Обычно мы можем не обращать внимания на индекс и делать вид, что взаимодействуем напрямую с историей. Но в данном случае мы хотим более тонкого контроля, поэтому работаем с индексом. Мы помещаем слепок некоторых (но не всех) наших изменений в индекс, после чего окончательно записываем этот аккуратно сформированный слепок.
52 === Не теряй «головы» ===
54 Тег HEAD (англ. «голова», прим. пер.) — как курсор, который обычно указывает на последний коммит, продвигаясь с каждым новым коммитом. Некоторые команды Git позволяют перемещать этот курсор. Например,
58 переместит HEAD на три коммита назад. Теперь все команды Git будут работать так, как будто вы не делали последних трех коммитов, хотя файлы останутся в текущем состоянии. В справке описано несколько способов использования этого приёма.
60 Но как вернуться назад в будущее? Ведь предыдущие коммиты о нем ничего не знают.
62 Если у вас есть SHA1 предыдущей «головы», то:
66 Но допустим, вы никогда его не записывали. Не беспокойтесь: для комнад такого рода Git сохраняет оригинальный HEAD как тег под названием ORIG_HEAD, и вы можете вернуться надёжно и безопасно:
70 === Охота за «головами» ===
72 Предположим ORIG_HEAD недостаточно. К примеру, вы только что осознали, что допустили громадную ошибку, и вам нужно вернуться к древнему коммиту в давно забытой ветке.
74 По умолчанию Git хранит коммиты не меньше двух недель, даже если вы приказали уничтожить содержащую их ветку. Проблема в нахождении подходящего хеша. Вы можете просмотреть все хеши в .git/objects и методом проб и ошибок найти нужный. Но есть путь значительно легче.
76 Git записывает все хеши коммитов в .git/logs. В подкатлоге refs содержится полная история активности на всех ветках, а файл HEAD содержит каждое значение хеша, которое когда-либо принимал HEAD. Последнее можно использовать чтобы найти хеши коммитов на случайно обрубленных ветках.
78 Команда reflog предоставляет удобный интерфейс работы с этими логами. Используйте
82 Вместо копирования хешей из reflog, попробуйте
84 $ git checkout "@{10 minutes ago}" # 10 минут назад, прим. пер.
86 Или сделайте чекаут пятого с конца из посещенных коммитов с помощью
90 См. раздел «Specifying Revisions» в *git help rev-parse*, для дополнительной информации.
92 Вы можете захотеть установить более долгий период сохранения удаляемых коммитов. Например,
94 $ git config gc.pruneexpire "30 days"
96 означает, что в удаленные коммиты будут окончательно потеряны только по прошествии 30 дней и после запуска *git gc*.
98 Также вы можете захотеть отключить автоматический вызов *git gc*:
100 $ git config gc.auto 0
102 В этом случае коммиты будут удаляться только когда вы будете запускать *git gc* вручную.
104 === Git как основа ===
106 Дизайн Git, в истинном духе UNIX, может быть легко использован как низкоуровневый компонент других программ: GUI, веб-интерфейсов, альтернативных интерфейсов командной строки, инструментов управления патчами, импортирования, преобразования, и т.д. Многие команды Git на самом деле - скрипты, стоящие на плечах гигантов. Небольшой доработкой вы можете переделать Git на свой вкус.
108 Простейший трюк — использование алиасов Git для выполнения часто используемых команд:
110 $ git config --global alias.co checkout
111 $ git config --global --get-regexp alias # отображает текущие алиасы
113 $ git co foo # то-же, что и «git checkout foo»
115 Также можно выводить текущую ветку в приглашении командной строки или заголовке окна терминала.
119 $ git symbolic-ref HEAD
121 выводит название текущей ветки. На практике вы скорее всего захотите убрать «refs/heads/» и сообщения об ошибках:
123 $ git symbolic-ref HEAD 2> /dev/null | cut -b 12-
125 Подкаталог +contrib+ это целая сокровищница инструментов, построенных на Git. Со временем некоторые из них могут становиться официальными командами. Под Debian и Ubuntu этот каталог находится в +/usr/share/doc/git-core/contrib+.
127 Ещё один из популярных и часто используемых инструментов — workdir/git-new-workdir. Этот скрипт создает с помощью символических ссылок новый рабочий каталог, имеющий общую историю с оригинальным хранилищем:
129 $ git-new-workdir существующее/хранилище новый/каталог
131 Новый каталог и файлы в ней можно воспринимать как клон, с той разницей, что два дерева автоматически остаются синхронизированными ввиду обще истории. Нет необходимости в *merge*, *push* и *pull*.
133 === Рискованные трюки ===
135 Нынешний Git делает случайное уничтожение данных очень сложным.
136 Но если вы знаете, что делаете, вы можете обойти защиту для распространённых команд.
138 *Checkout*: Наличие незакоммиченных изменений прерывает выполнение checkout. Чтобы перейти к нужному коммиту, даже уничтожив свои изменения, используйте «заставляющий» (force, прим. пер.) флаг *-f*:
140 $ git checkout -f HEAD^
142 С другой стороны, если вы укажете checkot конкретные пути, проверки на безопасность не будет: указанные файлы молча перезаписшутся. Будьте осторожны при использовании checkout таким образом.
144 *Reset*: сброс также прерывается при наличии незакоммиченных изменений. Чтобы заставить его сработать, запустите
146 $ git reset --hard 1b6d
148 *Branch*: Удаление ветки не выполняется, если должно привести к потере изменений. Для принудительного удаления введите
150 $ git branch -D мёртвая_ветка # вместо -d
152 Аналогично, попытка перезаписи ветки путем перемещения будет прервано если может привести к потере данных. Для принудительного перемещений ветки введите
154 $ git branch -M источник цель #вместо -m
156 В отличии от checkout и reset, эти две команды дают отсрочку в удалении данных. Изменения остаются в каталоге .git и могут быть возвращены восстановлениеем нужного хеша из .git/logs (см. выше раздел «Охота за «головами»). По умолчанию они будут храниться по крайней мере две недели.
158 *Clean*: Некоторые команды не будут выполняться, если они могут повредить неотслеживаемые файлы. Если вы уверены, что все неотслеживаемые файлы и каталоги не нужны, то безжалостно удаляйте их командой
162 В следующий раз эта досадная команда выполнится!
164 === Предотвращаем плохие коммиты ===
166 История многих моих проектов полна глупых ошибок. Самое ужасное это проблема недостающих файлов, вызванная забытым *git add*. К счастью, я пока не терял критичных данных при таких случайных пропусках, потому что я редко удаляю оригинальные рабочие каталоги. Обычно я замечаю ошибку несколько коммитов спустя, так что единственный вред это небольшая потеря истории и глупое чувство вины.
168 Также я регулярно совершаю менее серьёзный проступок: завершающие пробелы. Несмотря на безвредность, я не хотел бы, чтобы это появлялось в публичных записях.
170 И наконец я беспокоюсь о неразрешенных конфликтах слияний, хотя пока они не приносили вреда. Обычно я замечаю их при сборке проекта, но иногда могу проглядеть.
172 Если бы я только поставил защиту от дурака, используя _хук_, который бы предупреждал меня об этих проблемах:
175 $ cp pre-commit.sample pre-commit # В старых версиях Git: chmod +x pre-commit
177 Теперь Git отменит коммит, если обнаружит лишние пробелы или неразрешенные конфликты.
179 Для этого руководства я в конце концов добавил следующее в начало хука *pre-commit*, чтобы защититься от своей рассеянности:
181 if git ls-files -o | grep '\.txt$'; then
182 echo FAIL! Неотслеживаемые .txt файлы.
186 Несколько операций Git поддерживают хуки, см. *git help hooks*. Вы можете добавить хуки, которые будут сообщать о грамматических ошибках в описаниях коммитов, добавлять новые файлы, расставлять абзацные отступы, прибавлять запись к веб-странице, проигрывать звуки и т.д.
188 Мы использовали пример хука *post-update* раньше, при обсуждении использования Git через http; это заставило Git запускать этот скрипт при каждом перемещении «головы». Пример скрипта *post-update* обновлял несколько файлов, которые нужны Git для связи через не считающиеся с ним средства сообщения, вроде HTTP.