1 == Гроссмейстерство Git ==
\r
3 Эта претенциозно названная глава является собранием приемов работы с Git, которые я не смог отнести к другим главам.
\r
5 === Релизы исходников ===
\r
7 В моих проектах Git управляет только теми файлами, которые я собираюсь архивировать и пускать в релиз.
8 Чтобы создать тарбол с исходниками, я выполняю:
\r
10 $ git archive --format=tar --prefix=proj-1.2.3/ HEAD
\r
12 === Сохранение изменений ===
\r
14 Вручную сообщать Git о том, что вы добавили, удалили или переименовали файлы, может стать непростой задачей
15 в некоторых проектах. Вместо этого вы можете выполнить команды:
\r
20 Git просмотрит файлы в текущем каталоге и обработает изменения сам. Вместо второй команды add, выполните *git commit -a*, если вы хотите также сделать коммит с изменениями. В *git help ignore* можно посмотреть, как указать, какие файлы должны игнорироваться.
\r
22 Вы можете выполнить все это в один прием:
\r
24 $ git ls-files -d -m -o -z | xargs -0 git update-index --add --remove
\r
26 Опции *-z* и *-0* предотвращают побочные эффекты от файловых имен, содержащих специальные символы. Поскольку эта команда добавляет игнорируемые файлы, вы можете использовать опции `-x` или `-X`.
\r
28 === Слишком большой коммит ===
\r
30 Вы пренебрегали коммитами слишком долго? Яростно писали код и вспомнили о контроле исходников только сейчас? Внесли ряд несвязанных изменений, потому что это ваш стиль?
\r
32 Никаких проблем. Выполните:
\r
36 Для каждого внесенного изменения Git покажет измененный участок кода и спросит, должно ли это изменение пройти в следующем коммите. Отвечаем "y" или "n". Если вы хотите сделать что-то другое, например отложить выбор, введите "?" чтобы получить дополнительную информацию.
\r
38 Как только все будет готово, выполните:
\r
42 для коммита именно тех изменений, которые вы выбрали ('staged' изменения). Убедитесь, что вы не указали опцию *-a*, в противном случае Git добавит в коммит все изменения.
\r
44 Что делать, если вы изменили множество файлов во многих местах? Просмотр каждого отдельного изменения - удручающая задача. В этом случае используйте *git add -i*, чей интерфейс менее прост, но более гибок. При помощи нескольких нажатий кнопок можно добавить на этап или убрать с этапа несколько файлов одновременно, либо просмотреть и выделить изменения в отдельных файлах. Как вариант, запустите *git commit \--interactive*, который автоматически сделает коммит после того, как вы закончите.
\r
46 ==== Этапные изменения ====
\r
48 До сих пор мы избегали такой известной части Git, как 'index', но теперь мы должны разобраться с ней, чтобы пояснить вышесказанное. Индекс представляет собой временную область. Git редко перемещает данные непосредственно между вашим проектом и его историей. Вместо этого, Git сначала записывает данные в индекс, а уж затем копирует данные из индекса по месту назначения.
\r
50 Например, *commit -a* это на самом деле двухэтапный процесс. Сначала снапшот текущего состояния отслеживаемых файлов помещается в индекс. Затем снапшот, находящийся в индексе, записывается в историю. Коммит без опции *-a* выполняет только второй этап, и имеет смысл только после выполнения команд, которые изменяют индекс, например *git add*.
\r
52 Обычно мы можем не обращать внимания на индекс и считать, что взаимодействуем с историей напрямую. Но в такого рода сложных случаях нам нужен усиленный контроль над тем, что записывается в историю, и мы вынуждены работать с индексом. Мы помещаем снапшот только части наших изменений в индекс, а потом записываем этот аккуратно сформированный снапшот.
\r
54 === Не теряй HEAD ===
\r
56 Тег HEAD - как курсор. В нормальном состоянии он указывает на последний коммит, продвигаясь вместе с каждым новым коммитом. Есть команды Git, которые позволяют перемещать этот тег. Например:
\r
60 переместит HEAD на три коммита назад. Теперь все команды Git будут работать так, как будто вы не делали последних трех коммитов, хотя файлы останутся в текущем состоянии. В справке описаны некоторые методы использования этого эффекта.
\r
62 Но как вернуться назад в будущее? Ведь предыдущие коммиты о нем ничего не знают.
\r
64 Если у вас есть SHA1 оригинального HEAD, то:
\r
68 Но предположим, что вы никогда его не записывали. Тут тоже беспокоиться не стоит. Для комнад такого рода Git сохраняет оригинальный HEAD как тег под названием ORIG_HEAD, и вы можете вернуться безопасно и без проблем:
\r
70 $ git reset ORIG_HEAD
\r
72 === Охота за HEAD'ами ===
\r
74 Предположим ORIG_HEAD недостаточно. Предположим, что вы только что осознали, что допустили громадную ошибку, и вам нужно вернуться в очень старый коммит давно забытой ветки.
\r
76 По умолчанию Git хранит коммиты по крайней мере в течении двух недель, даже если вы сказали ему уничтожить ветку с ними. Проблема в нахождении подходящего хеша.
\r
78 Вы можете просмотреть хеши в `.git/objects` и методом проб и ошибок найти нужный. Но есть путь значительно легче.
\r
80 Git записывает все хеши коммитов в `.git/logs`. В папке `refs` содержится история активности на всех ветках, а файл `HEAD` содержит каждое значение хеша, которое когда-либо принимал HEAD. Второе можно использовать чтобы найти хеши коммитов на случайно обрубленных ветках.
\r
82 Команда reflog предоставляет удобный интерфейс работы с этими логами. Используйте:
\r
86 Вместа копипейста хешей из reflog, попробуйте:
\r
88 $ git checkout "@{10 minutes ago}"
\r
90 Или сделайте чекаут пятого из последних посещенных коммитов с помощью:
\r
92 $ git checkout "@{5}"
\r
94 Смотрите секцию "Specifying Revisions" *git help rev-parse*, если вам нужна дополнительная информация.
\r
95 Вам может потребоваться установить более долгий период сохранения удаляемых коммитов.
\r
97 Например, выполнение:
\r
99 $ git config gc.pruneexpire "30 days"
\r
101 означает, что в удаленные коммиты будут окончательно потеряны только после того, как пройдут 30 дней с момента удаления, и будет запущена *git gc*.
\r
103 Также вам может потребоваться отключить автоматическое выполнение *git gc*:
\r
105 $ git config gc.auto 0
\r
107 После этого коммиты будут удаляться только когда вы будете запускать *git gc* самостоятельно.
\r
109 === Git как основа ===
\r
111 Дизайн Git'a, разработанный в UNIX стиле, позволяет использовать Git как низкоуровневый компонент других программ: GUI, веб-интерфейсов, альтернативных командных строк, инструментов управления патчами, импортирования, преобразования, и т.д. На самом деле, многие команды Git - сами по себе скрипты, стоящие на плечах гигантов.
\r
112 Немного поигравшись с Git, вы можете заставить его удовлетворять многие ваши потребности.
\r
114 Простейший трюк - использование алиасов Git для выполнения часто
\r
116 используемых команд:
\r
118 $ git config --global alias.co checkout
\r
119 $ git config --global --get-regexp alias # отображает текущие алиасы
\r
121 $ git co foo # то-же, что 'git checkout foo'
\r
123 Также можно выводить текущую ветку в командную строку или название окна терминала.
\r
127 $ git symbolic-ref HEAD
\r
129 выводит название текущей ветки. Для практического использования вы
130 скорее всего захотите убрать "refs/heads/" и сообщения об ошибках:
\r
132 $ git symbolic-ref HEAD 2> /dev/null | cut -b 12-
\r
134 Папка `contrib` это целая сокровищница инструментов, построенных на Git.
\r
135 Со временем некоторые из них могут становиться официальными командами. Под Debian и Ubuntu эта папка находится в `/usr/share/doc/git-core/contrib`.
\r
137 `workdir/git-new-workdir` - один из популярных и часто используемых инструментов. С помощью хитрых симлинков этот скрипт создает новую рабочую папку, которая будет иметь общую историю с оригинальным репозиторием:
\r
139 $ git-new-workdir an/existing/repo new/directory
\r
141 Можно думать о новой папке и о файлах в ней, как о клоне, за исключением того, что так как история общая,
142 два дерева автоматически синхронизуются. Нет необходимости в *merge*, *push* и *pull*.
\r
144 === Опасные трюки ===
\r
146 Случайно уничтожить данные очень сложно с сегодняшними версиями Git.
\r
147 Но если вы знаете, что делаете, вы можете обойти защиту для общих команд.
\r
149 *Checkout*: Незакоммиченные изменения не дают сделать чекаут. Чтобы все-таки сделать чекаут нужного коммита, уничтожив свои изменения, используется флаг *-f*:
\r
151 $ git checkout -f COMMIT
\r
153 С другой стороны, если вы укажете отдельный путь для чекаута, то проверки на безопасность произведены не будут, и по указанные путям все будет переписываться без каких-либо сообщений. Будьте осторожны, если вы используете чекаут таким образом.
\r
155 *Reset*: Ресет также нельзя провести, если есть незакоммиченные изменения. Чтобы обойти это, запустите:
\r
157 $ git reset --hard [COMMIT]
\r
159 *Branch*: Удаление ветки не проходит, если приводит к потере изменений. Для принудительного удаления используйте:
\r
161 $ git branch -D BRANCH # вместо -d
\r
163 Аналогично, попытка перезаписи ветки путем перемещения не пройдет, если какие-то данные будут потеряны. Чтобы принудительно переместить ветку, введите:
\r
165 $ git branch -M [SOURCE] TARGET #вместо -m
\r
167 В отличии от чекаута и ресета, эти две команды задерживают удаление данных. Изменения остаются в папке .git и могут быть восстановлены с помощью нужного хеша из `.git/logs`(смотрите параграф "Охота за HEAD'ами" выше).
\r
169 По умолчанию они будут храниться по крайней мере две недели.
\r
171 *Clean*: Некоторые команды не будут выполняться, если они могут повредить неотслеживаемые файлы. Если вы уверены, что все неотслеживаемые файлы и папки вам не нужны, то безжалостно удаляйте их командой:
\r
175 В следующий раз эта надоедливая команда выполнится!
\r
177 === Улучшаем свой публичный образ ===
\r
179 История многих моих проектов полна глупых ошибок. Самое ужасное это кучи недостающих файлов, которые появляются, когда забываешь выполнить *git add*. К счастью, я пока не терял важных файлов из-за того, что пропускал их, потому что я редко удаляю оригинальные рабочие папки. Обычно я замечаю ошибку несколько коммитов спустя, так что единственный вред это отсутствующая история и осознание своей вины.
\r
181 Также я регулярно совершаю(и коммичу) меньшее зло - завершающие пробелы. Несмотря на безвредность, я не хотел бы, чтобы это появлялось в публичных записях.
\r
183 И наконец, я беспокоюсь о неразрешенных конфликтах, хотя пока они не приносили вреда. Обычно я замечаю их во время билда, но в некоторых случаях могу проглядеть.
\r
184 Если бы я только поставил защиту от дурака, используя хук, который бы предупреждал меня об этих проблемах...
\r
187 $ cp pre-commit.sample pre-commit # В старых версиях Git: chmod +x pre-commit
\r
189 Теперь Git отменяет коммит, если обнаружены ненужные пробелы или неразрешенные конфликты.
\r
190 Для этого руководства я в конце концов добавил следующее в начало *pre-commit* хука, чтобы защититься от своей рассеянности:
\r
192 if git ls-files -o | grep '\.txt$'; then
\r
193 echo FAIL! Неотслеживаемые .txt файлы.
\r
197 Несколько операций Git поддерживают хуки, смотрите *git help hooks*. Вы можете добавить хуки, которые будут сообщать о грамматических ошибках в комментариях к коммиту, добавлять новые файлы, делать отступы перед параграфами, добавлять записи на веб-страничку, проигрывать звуки, в общем, делать что угодно...
\r
199 Мы встречались с *post-update* хуком раньше, когда обсуждали Git через http. Этот хук обновлял несколько файлов, которые необходимы для ненативнох видов коммуникации с Git.