Магия Git. Гроссмейстерство Git |
- Statistics
- Participants
- Translate into Russian
- Translation result
- Translated in draft, editing and proof-reading required.
== Гроссмейстерство Git ==
Эта претенциозно названная глава является собранием приемов работы с Git, которые я не смог отнести к другим главам.
=== Релизы исходников ===
В моих проектах Git управляет только теми файлами, которые я собираюсь архивировать и пускать в релиз. Чтобы создать тарбол с исходниками, я выполняю:
$ git archive --format=tar --prefix=proj-1.2.3/ HEAD
=== Зафиксируйте изменения ===
Вручную сообщать Git о том, что вы добавили, удалили или переименовали файлы, может стать непростой задачей
в некоторых проектах. Вместо этого вы можете выполнить команды:
$ git add .
$ git add -u
Git просмотрит файлы в текущем каталоге и обработает изменения сам. Вместо второй команды add, выполните *git commit -a*, если вы хотите также сделать коммит с изменениями. В *git help ignore* можно посмотреть, как указать, какие файлы должны игнорироваться.
Вы можете выполнить все это в один прием:
$ git ls-files -d -m -o -z | xargs -0 git update-index --add --remove
Опции *-z* и *-0* предотвращают побочные эффекты от файловых имен, содержащих специальные символы. Поскольку эта команда добавляет игнорируемые файлы, вы можете использовать опции `-x` или `-X`.
=== Слишком большой коммит ===
Вы пренебрегали коммитами слишком долго? Яростно писали код и вспомнили о контроле исходников только сейчас? Внесли ряд несвязанных изменений, потому что это ваш стиль?
Никаких проблем. Выполните:
$ git add -p
Для каждого внесенного изменения Git покажет измененный участок кода и спросит, должно ли это изменение пройти в следующем коммите. Отвечаем "y" или "n". Если вы хотите сделать что-то другое, например отложить выбор, введите "?" чтобы получить дополнительную информацию.
Как только все будет готово, выполните:
$ git commit
для коммита именно тех изменений, которые вы выбрали ('staged' изменения). Убедитесь, что вы не указали опцию *-a*, в противном случае Git добавит в коммит все изменения.
Что делать, если вы изменили множество файлов во многих местах? Просмотр каждого отдельного изменения - удручающая задача. В этом случае используйте *git add -i*, чей интерфейс менее прост, но более гибок. При помощи нескольких нажатий кнопок можно добавить на этап или убрать с этапа несколько файлов одновременно, либо просмотреть и выделить изменения в отдельных файлах. Как вариант, запустите *git commit \--interactive*, который автоматически сделает коммит после того, как вы закончите.
==== Этапные изменения ====
До сих пор мы избегали такой известной части Git, как 'index', но теперь мы должны разобраться с ней, чтобы пояснить вышесказанное. Индекс представляет собой временную область. Git редко перемещает данные непосредственно между вашим проектом и его историей. Вместо этого, Git сначала записывает данные в индекс, а уж затем копирует данные из индекса по месту назначения.
Например, *commit -a* это на самом деле двухэтапный процесс. Сначала снапшот текущего состояния отслеживаемых файлов помещается в индекс. Затем снапшот, находящийся в индексе, записывается в историю. Коммит без опции *-a* выполняет только второй этап, и имеет смысл только после выполнения
команд, которые изменяют индекс, например *git add*.
Обычно мы можем не обращать внимания на индекс и считать, что взаимодействуем с историей напрямую. Но в такого рода сложных случаях нам нужен усиленный контроль над тем, что записывается в историю, и мы вынуждены работать с индексом. Мы помещаем снапшот только части наших изменений в индекс, а потом записываем этот аккуратно сформированный снапшот.
=== Не теряй HEAD ===
Тег HEAD - как курсор. В нормальном состоянии он указывает на последний коммит, продвигаясь вместе с каждым новым коммитом. Есть команды Git, которые позволяют перемещать этот тег. Например:
$ git reset HEAD~3
переместит HEAD на три коммита назад. Теперь все команды Git будут работать так, как будто вы не делали последних трех коммитов, хотя файлы останутся в текущем состоянии. В справке описаны некоторые методы использования этого эффекта.
Но как вернуться назад в будущее? Ведь предыдущие коммиты о нем ничего не знают.
Если у вас есть SHA1 оригинального HEAD, то:
$ git reset SHA1
Но предположим, что вы никогда его не записывали. Тут тоже беспокоиться не стоит. Для комнад такого рода Git сохраняет оригинальный HEAD как тег под названием ORIG_HEAD, и вы можете вернуться безопасно и без проблем:
$ git reset ORIG_HEAD
=== Охота за HEAD'ами ===
Предположим ORIG_HEAD недостаточно. Предположим, что вы только что осознали, что допустили громадную ошибку, и вам нужно вернуться в очень старый коммит давно забытой ветки.
По умолчанию Git хранит коммиты по крайней мере в течении двух недель, даже если вы сказали
ему уничтожить ветку с ними. Проблема в нахождении подходящего хеша.
Вы можете просмотреть хеши в `.git/objects` и
методом проб и ошибок найти нужный. Но есть путь значительно легче.
Git записывает все хеши коммитов в `.git/logs`. В папке `refs` содержится история активности на всех ветках, а файл `HEAD` содержит каждое значение хеша, которое когда-либо принимал HEAD. Второе можно использовать чтобы найти хеши коммитов на случайно обрубленных ветках.
Команда reflog предоставляет удобный интерфейс работы с этими логами. Используйте:
$ git reflog
Вместа копипейста хешей из reflog, попробуйте:
$ git checkout "@{10 minutes ago}"
Или сделайте чекаут пятого из последних посещенных коммитов с помощью:
$ git checkout "@{5}"
Смотрите секцию "Specifying Revisions" *git help rev-parse*, если вам нужна дополнительная информация.
Вам может потребоваться установить более долгий период сохранения удаляемых коммитов.
Например, выполнение:
$ git config gc.pruneexpire "30 days"
означает, что в удаленные коммиты будут окончательно потеряны только после того, как пройдут 30 дней с момента удаления, и будет запущена *git gc*.
Также вам может потребоваться отключить автоматическое выполнение *git gc*:
$ git config gc.auto 0
После этого коммиты будут удаляться только когда вы будете запускать *git gc* самостоятельно.
=== Git как основа ===
Дизайн Git'a, разработанный в UNIX стиле, позволяет использовать Git как низкоуровневый компонент других программ: GUI, веб-интерфейсов, альтернативных командных строк, инструментов управления патчами, импортирования, преобразования, и т.д. На самом деле, многие команды Git - сами по себе скрипты, стоящие на плечах гигантов.
Немного поигравшись с Git, вы можете заставить его удовлетворять многие ваши потребности.
Простейший трюк - использование алиасов Git для выполнения часто
используемых команд:
$ git config --global alias.co checkout
$ git config --global --get-regexp alias # отображает текущие алиасы
alias.co checkout
$ git co foo # то-же, что 'git checkout foo'
Также можно выводить текущую ветку в командную строку или название окна терминала.
Запуск
$ git symbolic-ref HEAD
выводит название текущей ветки. Для практического использования вы скорее всего захотите убрать "refs/heads/" и сообщения об ошибках:
$ git symbolic-ref HEAD 2> /dev/null | cut -b 12-
Папка `contrib` это целая сокровищница инструментов, построенных на Git.
Со временем некоторые из них могут становиться официальными командами. Под Debian и
Ubuntu эта папка находится в `/usr/share/doc/git-core/contrib`.
`workdir/git-new-workdir` - один из популярных и часто используемых инструментов. С помощью хитрых симлинков этот скрипт создает новую рабочую папку, которая будет иметь общую историю с оригинальным репозиторием:
$ git-new-workdir an/existing/repo new/directory
Можно думать о новой папке и о файлах в ней, как о клоне, за исключением того, что так как история общая, два дерева автоматически синхронизуются. Нет необходимости в *merge*, *push* и *pull*.
=== Опасные трюки ===
Случайно уничтожить данные очень сложно с сегодняшними версиями Git.
Но если вы знаете, что делаете, вы можете обойти защиту для общих
команд.
*Checkout*: Незакоммиченные изменения не дают сделать чекаут. Чтобы все-таки сделать чекаут нужного коммита, уничтожив свои изменения, используется флаг *-f*:
$ git checkout -f COMMIT
С другой стороны, если вы укажете отдельный путь для чекаута, то проверки на безопасность произведены не будут, и по указанные путям все будет переписываться без каких-либо сообщений. Будьте осторожны, если вы используете чекаут таким образом.
*Reset*: Ресет также нельзя провести, если есть незакоммиченные изменения. Чтобы обойти это, запустите:
$ git reset --hard [COMMIT]
*Branch*: Удаление ветки не проходит, если приводит к потере изменений. Для принудительного удаления используйте:
$ git branch -D BRANCH # вместо -d
Аналогично, попытка перезаписи ветки путем перемещения не пройдет, если какие-то данные будут потеряны. Чтобы принудительно переместить ветку, введите:
$ git branch -M [SOURCE] TARGET #вместо -m
В отличии от чекаута и ресета, эти две команды задерживают удаление данных. Изменения остаются в папке .git и могут быть восстановлены с помощью нужного хеша из `.git/logs`(смотрите параграф "Охота за HEAD'ами" выше).
По умолчанию они будут храниться по крайней мере две недели.
*Clean*: Некоторые команды не будут выполняться, если они могут повредить неотслеживаемые файлы. Если вы уверены, что все неотслеживаемые файлы и папки вам не нужны, то безжалостно удаляйте их командой:
$ git clean -f -d
В следующий раз эта надоедливая команда выполнится!
=== Улучшаем свой публичный образ ===
История многих моих проектов полна глупых ошибок. Самое ужасное это кучи недостающих файлов, которые появляются, когда забываешь выполнить *git add*. К счастью, я пока не терял важных файлов из-за того, что пропускал их, потому что я редко удаляю оригинальные рабочие папки. Обычно я замечаю ошибку несколько коммитов спустя, так что единственный вред это отсутствующая история и осознание своей вины.
Также я регулярно совершаю(и коммичу) меньшее зло - завершающие пробелы. Несмотря на безвредность, я не хотел бы, чтобы это появлялось в публичных записях.
И наконец, я беспокоюсь о неразрешенных конфликтах, хотя пока они не приносили вреда. Обычно я замечаю их во время билда, но в некоторых случаях могу проглядеть.
Если бы я только поставил защиту от дурака, используя хук, который бы предупреждал меня об этих проблемах...
