среда, 4 июня 2008 г.

Пара Git трюков

Git - яркий представитель модных ныне распределенных систем контроля версий. Проще говоря то, что хранит все ваши исходники, формирует патчи, показывает вам, что вы делали неделю назад, и кто тот засранец, который так криво написал вот этот кусок (частенько оказывается, что это вы сами :) ). Иными словами если вас достали бесконечные папки project.old, project.old2, project.work и т.д. то подобные игрушки как раз для вас, рекомендую Git, хотя другие (bzr, hg, darcs) даже не пробовал.

Не буду тут разводить туториал, как-нибудь в другой раз, просто расскажу про пару приемчиков.

Отложить текущие наработки

Вы делаете какую-то мега классную фичу, процесс в самом разгаре и тут бац, срочно надо исправить баг. Как вы обычно поступаете?


$ git checkout -b undone_work (1)
$ git commit -a -m "незаконченная фича" (2)
$ git checkout master (3)
... правим баг ...
$ git commit -a -m "Исправлен баг"
$ git checkout undone_work (4)
$ git reset --soft HEAD^ (5)
  1. Создаете временную ветку
  2. комитите туда то что наработали
  3. переходите на ветку, где надо исправить баг, исправляем, коммитим
  4. возвращаемся на временную ветку с нашей недоделаной работой
  5. отменяем последний коммит, т.к. он безсмысленен сам по себе
Работать оно, конечно, работает, но у Git есть более удобное средство. git-stash. В уже описанном выше случае все действия сведутся к:

$ git stash (1)
... правим баг ...
$ git commit -a -m "Исправлен баг" (2)
$ git stash pop (3)
  1. откладываем текущие наработки. Файлы вернуться в состояние HEAD (т.е. последний коммит текущей ветке)
  2. исправляем баг
  3. возвращаем наши отложенные изменения и продолжаем работу
Как видно, всё намного проще, а главное понятнее :) При этом если исправление бага затронет те же файлы, что мы хачили делая новую супер фичу, то Git сначала попытается разрулить конфликт сам, а если не выйдет предложит это пользователю (в общем как и при git merge)

git-stash умеет класть на полку и доставать оттуда несколько недоделаных работ.


$ git-stash (1)
$ git-checkout another_branch
$ git-stash
$ git-stash list (2)
$ git-stash pop stash@{_номер_} (3)
  1. несколько раз в разных местах вызываем git-stash
  2. просматриваем все что наоткладывали
  3. вернуть недоделку и продолжить над ней работу можно при помощи git-stash apply stash@{_номер_недоделки_}.

Возврат потерянных данных

Git, наверно, можно познавать вечно :). Недавно узнал, как пользоваться reflog. Оказывается Git записывает у себя все ваши действия, даже переходы между ветками! Доступ прошлым состояним можно получить через HEAD@{}. HEAD в Git всегда указывает на ваше текущее дерево. Переключаясь между ветками вы переносите HEAD (ну можно представить что HEAD - это считывающая головка ползающая по диску). То, где находились на прошлом шаге (например до переключения на другую ветку) можно получить через HEAD@{1}, на позапрошлом - HEAD@{2} и т.д. Т.е. в Git можно добраться не только до истории комитов!

Например, чтобы показать мощь этого инструмента - жёстко удалим коммит, так что в истории по git-log его уже не будет, но всё же попробуем его вытащить обратно:

... правим файл...
$ git-commit -a -m "Исправлен файл А" (1)
$ git-reset --hard HEAD^  (2)
$ git-log (3)
$ git-show HEAD@{1} (4)
$ git-reset --hard HEAD@{1} (5)
  1. Сделали необдуманый комит
  2. Поняли что всё сдали не так и отменили все изменения. Опция --hard откатывает назад не только текущее дерево, но и историю комитов. HEAD^ ссылается на предыдущий коммит в текущей ветке (не путать с HEAD@{}, который является ссылка на историю самого HEAD).
  3. Убеждаемся, что в истории комитов отмененного комита нет
  4. И тут все-таки решаем, что доля рационального в нашем комите все же была. Как его вытащить? На прошлом шаге HEAD как раз указывал на то, что мы сейчас пытаемся достать. Посмотрим так ли это? Комманда git-show просто показывает сообщение комита и его diff, т.е. убеждаемся что мы нашли наш потерянный коммит.
  5. Тут для примера мы просто назад восстанавливаем дерево на то состояние, которое нам бы хотелось.

Т.е. иными словами всё, что когда-то было зафиксировано в Git можно достать, правда до тех пор, пока вы не сделаете git-gc --prune, который убивает всю ненужную историю изменений, оставляя только историю коммитов. Также можно ссылаться на прошлое состояние не только HEAD, но и конкретной ветки, для ветки master это будет master@{}

Вот собственно и все, но Git бесконечен, как только открою для себя еще что-то новое - обязательно напишу.