Ключевые слова:git, (найти похожие документы)
From: Logus
Date: Mon, 6 Nov 2010 17:02:14 +0000 (UTC)
Subject: Правила хорошего тона при работе с git в многопользовательском окружении
Оригинал: http://evasive.ru/articles/git_kung-fu.html
Данное повествование подразумевает знакомство читателей с такими
командами git как: add, pull, push, commit.
В случае многопользовательской работы с репозиторием зачастую
получается такая ситуация: сделали мы git pull, правим код, коммитим,
хотим пропихнуть код в общий репозиторий, а тут-то нам и говорят: фигу
вам, а не push -- там уже всё поменялось. В итоге приходится делать git
pull, получается вынужденный автоматический merge и с этим уродливым
merge (который был по сути и необязателен!) мы запихиваем наши
изменения обратно в общий репозиторий. История изменений получается не
самая красивая.
Ниже будет рассказано о том, как же можно избежать подобных конфузов и
прослыть знатоком git kung-fu.
Рассовываем всё по карманам или git stash
Существует замечательная команда: git stash. Дословно, stash --
припрятывать, тайник. Команда позволяет сохранять локальные
незакоммиченные изменения, сбрасывая при этом состояние рабочей копии
до HEAD'а. То есть, сказав git stash мы "припрятываем" все локальные
изменения, получая чистую рабочую копию, в которую можно смело делать
git pull не опасаясь возникновения ненужных merge'й.
После получения из репозитория-origin'а всех новых изменений мы можем
смело "достать" "припрятанные" изменения и наложить их на текущую
рабочую копию командой git stash apply.
Убедившись в успешном наложении изменений, можно сделать долгожданный
git add -u, git commit и git push, после чего (или сразу после git
stash apply) можно со спокойной совестью грохать сохранённые изменения
командой git stash drop.
Более подробно о команде stash:
git stash : добавить текущие незакоммиченные изменения в стек изменений
и сбросить текущую рабочую копию до HEAD'а репозитория;
git stash list : показать все изменения в стеке;
git stash show : показать последнее измененеие в стеке (патч);
git stash apply : применить последнее изменение из стека к текущей
рабочей копии;
git stash drop : удалить последнее изменение в стеке;
git stash pop : применить последнее изменение из стека к текущей
рабочей копии и удалить его из стека;
git stash clear : очистить стек изменений.
Полный синтаксис смотреть командой git help stash (или man git-stash).
Не стесняемся ветвиться
Ещё более удобной возможностью одновременной работы над множеством
разных фич является использованием бранчей в git. Бранчи там
реализованы просто божественно, использовать их -- одно удовольствие.
Как это делаю я: когда надо закрыть какой-нибудь баг, или реализовать
определённую фичу, я просто завожу под это дело отдельный бранч. Бранч
можно создать двумя способами, какой из них использовать -- вопрос
персональных предпочтений. Расскажу об обоих по порядку.
Создаём branch
Первый вариант создания -- команда git branch имя_бранча. Создаёт
локальный бранч с указанным именем. Просмотреть существующие бранчи в
репозитории можно командой git branch (можно добавить опцию -v для
вывода также текущего HEAD'а каждого из бранчей).
После создания бранча надо переключиться на него, то есть, привести
рабочую копию в соответствии с HEAD'ом этого бранча и указать
репозиторию, что все действия необходимо вести именно с этим бранчем.
Такое переключение осуществляется командой git checkout имя_бранча.
Для создания нового бранча и переключения на него можно воспользоваться
удобной опцией команды checkout, а именно: git checkout -b
имя_нового_бранча -- такой вызов одновременно создаст новый бранч и
переключится на него.
Основная ветка репозитория традиционно называется master, и вернуться к
ней из любого бранча можно также, как и переключение на любой другой
бранч, то есть git checkout master.
Сливаем ветки
Работа с бранчем рано или поздно должна завершиться переносом всех
изменений из этого бранча в master. Для начала рассмотрим простейший
вариант: есть основная ветка master с ревизиями a-b-c, мы создаём бранч
bug1, вносим в него изменения и коммитим их: в бранче bug1 порядок
ревизий стал a-b-c-d. Сейчас, чтобы перенести ревизию d в бранч master,
мы выполняем следующую последовательность команд:
git checkout master # переключились на бранч master
git merge bug1 # выполняем слияние с бранчем bug1
Так как последовательность изменений из бранча bug1 однозначно
накладывается на последовательность из бранча master, выполняемый merge
будет определён как fast-forward, то есть история будет без лишних
ветвлений, а итоговая последовательность изменений в master'е выглядит
так: a-b-c-d.
После успешного наложения всех изменений из бранча bug1 его можно
безболезненно удалить командой git branch -d bug1. Полезной
особенностью этой команды является проверка на наличие всех изменений
удаляемой ветки в master'е -- если изменения перенесены не все, команда
откажется удалять ветку.
Трансплантация истории
Рассмотрим более сложный, но более близкий к реальности вариант:
параллельное внесение изменений в основную ветку репозитория и
отдельном бранче. Исходные данные аналогичны предыдущему примеру:
master с историей изменений a-b-c, и ветка bug1 с добавленным
изменением d, то есть история для неё a-b-c-d.
Предположим, пока мы занимались работой над багом, кто-то другой успел
реализовать какую-нибудь фичу и пропихнуть её в основной репозиторий.
Теперь, переключившись из бранча bug1 в master, и выполнив в master'е
git pull, мы получаем в нём такую историю: a-b-c-E, где E -- это коммит
с реализацией той фичи, которую успел закоммитить другой программист.
Если сейчас находясь в ветке master выполнить git merge bug1, мы
получим историю изменений раздваивающуюся после коммита c на 2 ветви и
заканчивающуюся автоматическим слиянием веток, родителями этого
merge-коммита будут ревизии E (из master'а) и d (из бранча bug1).
Избежать подобной ветвистости помогает волшебная команда git rebase.
git rebase -- инструмент для "пересадки" веток истории изменений. В
нашем примере, имея в master историю a-b-c-E, мы можем "пересадить"
кусок истории c-d из бранча bug1 после коммита E. Проделать это можно
так:
git checkout bug1 # переключаемся на бранч bug1
git rebase master # пересадить историю изменений
Остановимся на последней команде подробнее. git rebase берёт ветку
истории из текущего бранча, отличающуюся от истории бранча, указанного
в качестве параметра (master), и "пересаживает" эту ветку поверх
подтянутой истории (master'а). То есть, история изменений для бранча
bug1 после выполнения этой команды будет выглядеть так: a-b-c-E-D.
Необходимо обратить внимание на то, что получившееся изменение D
отличается от исходного изменения d своим идентификатором, хотя и
сохраняет такие атрибуты как дата, текст и патч. То есть, это уже
фактически другая ревизия, а с номерами ревизий "играть" по-хорошему
допускается только локально.
Имея в master'е историю a-b-c-E, а в бранче bug1 историю a-b-c-E-D,
несложно выполнить fast-forward merge, сохранив "прямую" историю без
ненужных ветвлений:
git checkout master
git merge bug1
Результатом получаем master с красивой историей a-b-c-E-D.
Более подробная информация с картинками есть в увлекательной
документации: git help rebase (или man git-rebase).