От переводчика: в этой статье нет описания команд git, она подразумевает, что вы уже знакомы с ним. Здесь описывается вполне здравый, на мой взгляд, подход к содержанию публичной истории в чистоте и порядке.
Если вы не понимаете, что побудило сделать git именно таким, то вас ждут страдания. Используя множество флагов (--flag), вы сможете заставить git работать так, как по вашему мнению он должен работать, вместо того, чтобы работать так, как git того хочет. Это как забивать гвозди отверткой. Работа делается, но хуже, медленнее, да и отвертка портится.
Рассмотрим, как разваливается обычный подход к разработке с git.
Отпочковываем ветку от master, работаем, сливаем обратно, когда закончили.
Большую часть времени это работает, как и ожидается, потому как master меняется после того, как вы сделали ответвление (Имеется в виду, что в master коммитят ваши коллеги — прим. переводчика.). Однажды вы сливаете ветку feature в master, но master не менялась. Вместо коммита слияния (merge commit) git просто передвигает указатель master на последний коммит, происходит fast forward.
Для пояснения механизма fast forward я позаимствовал картинку из одной известной статьи. Прим. переводчика.
К несчастью ваша ветка feature содержала промежуточные коммиты — частые коммиты, что бекапят работу, но захватывают код в нерабочем состоянии. Теперь эти коммиты неотличимы от стабильных коммитов в master. Вы можете с легкостью откатиться в этакое бедствие.
Итак, вы добавляется новое правило: «Использовать --no-ff при слиянии веток feature». Это решает проблему и вы двигаетесь дальше.
Затем в один прекрасный день вы обнаруживаете критичный баг в продакшене и вам требуется отследить момент, когда он появился. Вы запускаете bisect, но постоянно попадаете на промежуточные коммиты. Вы сдаетесь и ищете руками.
Вы локализуете баг вплоть до файла. Запускаете blame, чтобы увидеть, изменения за последние 48 часов. Вы знаете, что это невозможно, но blame сообщает, что файл не изменялся несколько недель. Выясняется, что blame выдает время исходного коммита вместо времени слияния ветки (логично, ведь merge commit пуст — прим. переводчика). Ваш первый промежуточный коммит изменил этот файл несколько недель назад, но изменение было влито только сегодня.
Костыль no-ff, поломанный bisect и невнятность blame — симптомы того, что вы забиваете гвозди отверткой.
Переосмысление контроля версий
Контроль версий нужен для двух вещей.
Первая — для помощи в написании кода. Есть необходимость синхронизировать правки со своей командой и регулярно бекапить свою работу.
Вторая причина — это конфигурационное управление. Включает в себя управление параллельной разработкой. Например, работа над следующей релизной версией и параллельные баг-фиксы существующей продакшн версии. Конфигурационное управление подразумевает возможность узнать, когда что-либо было изменено. Бесценный инструмент для диагностирования ошибок.
Традиционно эти две причины вступают в конфликт.
При разработке некой функциональности вам понадобятся регулярные промежуточные коммиты. Однако, эти коммиты обычно ломают билд.
В совершенном мире каждое изменение истории версий лаконично и стабильно. Здесь нет промежуточных коммитов, что создают помехи. Здесь нет гигантских коммитов на 10 000 строк. Опрятная история позволяет откатывать правки или перекидывать их между ветками с помощью cherry-pick. Опрятную историю проще изучать и анализировать. Однако, поддержание чистоты истории подразумевает доведение всех правок до идеального состояния.
Так какой подход выбираете вы? Частые коммиты или опрятную историю?
Если вы работаете вдвоем над предрелизным стартапом, опрятная история подкупает несильно. Вы можете коммитить все подряд в master и выпускать релизы когда вам вздумается.
Как только значимость изменений увеличится, будь то рост команды разработки или размера пользовательской базы, вам понадобятся инструменты для поддержания порядка. Сюда входят автоматическое тестирование, code review и опрятная история.
Ветки feature выглядят удачным компромиссом. Они разрешают простые проблемы параллельной разработки. Вы думаете об интеграции в наименее важный момент времени, когда пишите код, но это поможет вам на некоторое время.
Когда ваш проект достаточно разрастется, простой подход branch / commit / merge развалится. Время применения клейкой ленты закончилось. Вам нужна опрятная история изменений.
Git революционен, потому что он дает вам лучшее от двух миров. Можно делать частые коммиты в процессе разработки и чистить историю по окончании. Если это ваш подход, то умолчания git представляются более осмысленными (имеется в виду fast-forward по умолчанию при слиянии веток — прим. переводчика).
Последовательность действий
Думайте о ветках в разрезе двух категорий: публичные ветки и приватные.
Публичные ветки — это официальная история проекта. Коммит в публичную ветку должен быть лаконичным, атомарным и иметь хорошее описание. Он должен быть линейным. Он должен быть неизменен. Публичные ветки это master и release.
Приватная ветка для себя. Это ваш черновик на время решения задачи.
Наиболее безопасно хранить приватные ветки локально. Если вам требуется сделать push для синхронизации рабочего и домашнего компьютеров, например, сообщите вашим коллегам, что ветка ваша и что не стоит на нее опираться.
Не стоит вливать приватную ветку в публичную простейшим merge. Сперва подчистите вашу ветку инструментами вроде reset, rebase, merge --squash и commit --amend.
Представьте себя писателем, а коммиты главами книги. Писатели не публикуют черновики. Майкл Крайтон сказал: «Великие книги не написаны — они переписаны».
Если вы пришли с других VCS, изменение истории вам покажется табу. Вы исходите из того, что любой коммит высечен в камне. Следуя этой логике, нужно убрать «undo» из текстовых редакторов.
Прагматики заботятся о правках только до тех пор пока эти правки не становятся надоедливыми. Для конфигурационного управления нам важны только глобальные изменения. Промежуточные коммиты всего-лишь легковесный буфер с возможностью отмены.
Если рассматривать историю как нечто незапятнанное, то fast-forward слияние не только безопасно но и предпочтительно. Оно поддерживает линейность истории, ее проще отслеживать.
Единственный оставшийся аргумент за --no-ff — это документирование. Можно использовать коммиты слияния для ассоциации с последней версией продакшн кода. Это антипаттерн. Используйте теги.
Рекомендации и примеры
Я использую 3 простых подхода в зависимости от размера изменения, времени работы над ним и того, как далеко ветка ушла в сторону.
Быстрая правка
Большую часть времени чистка это всего лишь squash коммит.
Допустим, я создал ветвь feature и сделал несколько промежуточных коммитов в течении часа.
git checkout -b private_feature_branch
touch file1.txt
git add file1.txt
git commit -am "WIP"
Как только я закончил, вместо простого merge, делаю следущее:
git checkout master
git merge --squash private_feature_branch
git commit -v
Затем трачу минуту на написание более подробного комментария к коммиту.
Правка побольше
Временами реализация фичи разрастается в многодневный проект с множеством мелких коммитов.
Я решаю, что моя правка должна быть разделена на более мелкие части, так что squash слишком грубый инструмент. (В качестве повседневного правила я спрашиваю себя: «Легко ли будет сделать code review?»)
Если мои промежуточные коммиты были логичным движением вперед, то можно использовать rebase в интерактивном режиме.
Интерактивный режим могуч. Вы можете использовать его для редактирования старых коммитов, разделения или упорядочивания оных и, в данном случае, для объединения нескольких.
В ветке feature:
git rebase --interactive master
Откроется редактор со списком коммитов. Каждая строка это: команда, которая будет выполнена, SHA1 хэш и комментарий к коммиту. Внизу есть список возможных команд.
По умолчанию у каждого коммита стоит «pick», что означает «коммит не изменять».
pick ccd6e62 Work on back button
pick 1c83feb Bug fixes
pick f9d0c33 Start work on toolbar
Меняю команду на «squash», которая объединяет текущий коммит с предыдущим.
pick ccd6e62 Work on back button
squash 1c83feb Bug fixes
pick f9d0c33 Start work on toolbar
Сохраняю, теперь другой редактор запрашивает комментарий к объединенному коммиту. Все, готово.
Несостоявшиеся ветки
Возможно ветка feature просуществовала длительное время и в нее сливались другие ветки для поддержания ее актуальности. История сложна и запутана. Простейшее решение взять грубый diff и создать новую ветку.
git checkout master
git checkout -b cleaned_up_branch
git merge --squash private_feature_branch
git reset
Теперь рабочая директория полна моих правок и никакого наследия предыдущей ветки. Теперь берем и ручками добавляем и коммитим правки.
Резюмируем
Если вы боретесь с умолчаниями в git, спросите себя почему.
Считайте публичную историю неизменной, атомарной и легко прослеживаемой.
Считайте приватную историю изменяемой и гибкой.
Порядок действий таков:
- Создаем приватное ответвление от публичной ветки.
- Методично коммитим работу в эту приватную ветку.
- Как только код достиг совершенства, приводим историю в порядок.
- Сливаем упорядоченную ветку обратно в публичную.
Автор: MastaEx