Добрый день, коллеги! Доказывать, что нужно использовать систему контроля версий, уже давно не нужно. И Git занял тут лидирующую позицию, стремительно вытеснив SVN. Но это инструмент, а инструментом нужно уметь пользоваться, чтобы добиться лучших результатов. Как топором, один человек сможет просто срубить дерево, а другой из этого дерева сможет сделать великолепную скульптуру. Так и с помощью Git, один человек сможет просто не потерять результаты своего труда за день, а другие смогут организовать совместную работу над проектом нескольких сотен человек. Да так, что о любой строчке кода можно будет и через пять лет сказать, откуда она взялась и для чего нужна.
Постараюсь рассказать для начинающих и не очень разработчиков, как оформлять свои коммиты, чтобы их максимально быстро и без претензий принимали в любые проекты, как опенсорсные так и коммерческие.
Заголовок
Начнем с самой важной, на мой взгляд, части, которая не требует погружения в технические детали: заголовок коммита. Это первая строка в commit message, которая отделена пустой строкой от остального текста. Человеку, который только пишет код, может быть сразу не очевидна важность заголовка. Но те люди, которые следят за репозиторием, выпускают релизы и работают с основными ветками точно будут очень благодарны за качественные заголовки. Дело в том, что при работе с большим количеством коммитов никто не заглядывает в тело сообщения и, тем более, в код. В основном смотрят списки, которые можно получить командой:
git log --pretty=oneline
Многие графические инструменты для работы с Git-ом также по умолчанию показывают только заголовки. В таком режиме просмотра хочется прочитав минимальное количество слов понять, что это за коммит. Отсюда получаем набор правил, который более-менее одинаков во всех проектах.
Длина
На многих ресурсах написано, что заголовки желательно делать не более 50 символов, но в такую длину довольно сложно уложиться. Лично меня напрягают заголовки длиннее 80 символов. Наверное, самое часто встречающееся ограничение — это 72 символа. Иногда в заголовок требуют включать какую-либо метаинформацию, например, ID задачи в жире:
MYPROJECT-3244: Add API for password change
Такая метаинформация бессознательно пропускается
И уж точно не стоит писать в заголовок только ссылку на внешний ресурс, ID задачи в жире или на постановку задачи в вики. Только из прочтения заголовка коммита должно быть понятно, к чему он относится.
И еще не должно быть предположения, что порядок коммитов останется таким же, в каком вы его послали. Не нужно ссылаться на предыдущие коммиты.
Примеры, как делать не нужно:
fix bug
new feature
Small fix to that API
Implement API for password change
issue43
http://jira.example.com/BACKEND-34345
In some cases ‘prev’ pointer is equal to ‘next’ so the check for the list length returns wrong values which leads to out of bounds errors in function has_access
return 0 instead of 1 in has_access
Грамматика
Заголовки коммитов следует писать аналогично заголовкам газет, потому что цель у них примерно одна и та же: чтобы читатель, увидев заголовок беглым взглядом, смог понять, о чем эта статья/коммит. Так что не стоит использовать сложные языковые конструкции, следует стараться использовать минимум знаков препинания. Если пишем на английком языке — не нужно использовать артикли, а глаголы должны быть в повелительном наклонении (imperative mood). Точку в конце ставить не нужно:
-
I’ve added API for password change -
The API for a password change has been added -
Added API for password change -
This commit adds API for password change -
Add new API. This API changes password. -
Add API for password change
Уникальность
И, наверное, самое важное свойство, которым должен обладать заголовок коммита: он не должен повторяться хотя бы несколько месяцев. Понятно, что вы не сможете каждый раз искать дубли по всей истории, но делать один за другим несколько коммитов с одинаковыми заголовками точно не стоит. Дело в том, что часто, чтобы посмотреть наличие какого-то коммита в ветке, проще всего просто поискать по заголовку и такой подход дублирующиеся заголовки сломают. Да и при просмотре истории в однострочном режиме видеть одинаковые коммиты совсем не хочется.
Иногда возникают ситуации, когда починили какой-то баг, а он не исправился и возникает желание во второй раз написать такой же заголовок. Постарайтесь, пожалуйста, так не делать, этим вы сильно облегчите жизнь другим участникам проекта и особенно — ответственному за репозиторий.
Сначала писать заголовки очень сложно. Но это будет гораздо проще, если правильно разбивать свои изменения на коммиты, о чем поговорим чуть позже. Да и нужно расшевелить свою гуманитарную половину
Commit message
Написать хороший заголовок лучше чем ничего (то есть плохой заголовок). Но если в проекте много участников, сформировалась значительная кодовая база и проект планируется поддерживать еще долгое время (больше полугода). То нужно писать и тело commit message.
Основное правило — не нужно писать, что делает коммит, нужно писать, для чего он это делает. То есть если это фикс бага — конкретно о том, что это фикс бага будет указано в заголовке. А в теле коммита нужно поподробнее описать, что это за баг, когда он проявляется и, самое главное, почему ваши правки в коде этот баг исправляют.
Ссылки не должны заменять текст коммита. Если баг описан в жире, то вместо того, чтобы просто вставлять ссылку на жиру, надо кратко пересказать, что там написано, прямо в теле commit message.
По оформлению: не следует писать все в одну строку, желательно переносить по границе 72 символов. Так же следует использовать повелительное наклонение в предложениях, где описано, что делает этот коммит.
Пример хорошего commit message (из проекта libvirt):
Автор
Самое главное, чего не следует делать, — это посылать свои коммиты с разных адресов. Так как кому-то может потребоваться посмотреть список ваших коммитов, и если вы посылаете с разных адресов — это будет делать неудобно. К счастью в Git можно указать отдельный адрес для каждого репозитория:
git config user.name "Ivan Ivanov"
git config user.email ivan.ivanov@example.com
Эти команды нужно запускать из папки репозитория, который вы хотите настроить.
Что включать в одну серию
В одну крайне желательно включать только те коммиты, которые никогда не потребуется вливать частями. То есть не следует смешивать срочные фиксы багов и реализацию экспериментальных фич. Так как перед релизом может возникнуть необходимость влить фиксы багов, а вот фичи оставить до лучших времен.
Так же серия коммитов должна оставить после себя проект в полностью рабочем состоянии, насколько этом возможно. Абсолютно точно она не должна сломать компиляцию, тесты, линтер и нарушить какие-то другие соглашения, принятые в проекте в надежде, что следующая ваша серия это исправит.
Как делить на коммиты
При делении своих изменений на коммиты не следует смешивать изменения разных типов. Но и делать слишком мелкие коммиты тоже не нужно. Постараюсь описать несколько ситуаций, из которых станет более понятна идея.
Ревью
Вы решили исправить баг, но внезапно оказалось, что ругается линтер из-за слишком длинной строчки. Вы решили переименовать переменную, так как её название было излишне длинным. Тут возникает соблазн сделать все одним коммитом. Но ревьюверам такой merge request с одним коммитом будет очень сложно смотреть, так как из сотен строк, которые просто переименовывают переменную, нужно будет отыскать пару-тройку строк, которые действительно исправляют логику. В идеале это надо оформить в виде двух коммитов — первый переименовывает, второй — исправляет баг. Но вообще удобнее, если баг чинится одним коммитом.
Перенос изменений
В процессе реализации новой фичи вы нашли серьезный баг в безопасности. Не дожидаясь конца отладки новой фичи вы исправили этот баг, после доделали фичу и сделали коммит. И вот так лучше не делать, так как правку серьезного бага скорее всего захотят влить и в старые ветки уже выпущенных релизов или побыстрее влить в мастер в случае облачного продукта. А с фичами спешки скорее всего нет. Если все сделать в одном коммите, то придется вместе с фиксом бага обязательно вливать новую фичу, которая потребует провести полный цикл тестирования.
Декомпозиция
Вам поручили сделать новую фичу, и пусть это даже новый небольшой микросервис в отдельном репозитории. Но все мы люди, и выдать за один раз кусок кода размером в 5000 строчек мало кому под силу. Так что несмотря на то, что с точки зрения менеджеров это одна задача, вам будет удобнее эту задачу декомпозировать:
-
Добавить шаблон микросервиса.
-
Добавить пару базовых методов и работу с базой.
-
Добавить взаимодействие с сервисом X.
-
Добавить еще методов.
-
Добавить обработку каких-то сложных случаев и дополнительных параметров в первые два метода.
-
...
Можно конечно все это сделать одним коммитом. Но скорее всего за один день это сделать не получится, так что встанет вопрос о сохранении промежуточной работы. Во-вторых, начальник захочет отслеживать ваш прогресс и если делаете отдельными коммитами — это будет гораздо проще. Да и самому удобно наметить какой-то план работы и на каждый пункт этого плана делать коммит.
Бывают ситуации, когда вы вошли в раж и починили три бага и реализовали пару фич и только потом вспомнили, что эти изменения надо еще и куда-то послать. В этом случае поможет интерактивный коммит. Подробно останавливаться на нем не буду, так как иначе статья получится слишком большая. Запускается командой:
git commit --interactive
Позволяет добавить в разные коммиты даже изменения в одном файле.
Абсолютное зло, которое ни в коем случае нельзя допускать
-
fix compilation
-
fix tests
-
fix linter
Это коммиты, которые чинят предыдущие коммиты в этой же серии. Никому не интересен тот факт, что вы при написании кода допустили ошибку и сразу же её исправили, интересен только конечный результат. Всё это лучше проверить и исправить до того, как будет сделан коммит. Если забыли и закоммитили а оказалось, что тесты не проходят, то от таких коммитов надо избавляться. Каким образом — в следующей главе.
Редактирование коммитов
Предупреждение — редактировать коммиты можно только до тех пор, пока кто-то другой их себе не взял. Например, если вы сделали несколько коммитов, но не запушили это в общий репозиторий. Или уже запушили, но никто вашу ветку себе не брал и ничего не дорабатывал поверх.
В такой ситуации нельзя редактировать коммит B. Если вы уже делали git push
для вашей ветки, то после редактирования коммитов git push
надо делать с опцией —force
. Да, и категорически запрещено редактировать коммиты в мастере. Вернемся к самому редактированию.
Самый простой случай — вы нашли ошибку в последнем коммите. В этом случае после исправления нужно для каждого измененного и добавленного файла сделать git add
и потом git commit --amend
. Там же можно будет отредактировать commit message.
И немного более сложный случай — когда нужно внести правки в несколько коммитов. В этом случае нужно правки к каждому коммиту оформить в виде отдельного коммита и потом сделать git rebase --interactive
. Там надо переместить коммиты-фиксы после коммитов, которые они исправляют и указать команду fixup:
Как обновлять из mainstream
Часто работа над какой-то фичей ведется долгое время и вам может, например, понадобиться функция, которую добавили в проект уже после того, как вы начали работу над вашей фичей. В этом случае нужно подтянуть свежие изменения в свою ветку.
Пожалуйста, не пользуйтесь для этого IDE и командой git pull! Никто не понимает, что при этом происходит и постоянно делают что-то не то.
Вообще есть 2 способа добавить свежие изменения из mainstream в вашу ветку:
Rebase — переносит ваши изменения поверх mainstream. История получается максимально чистая, но коммиты меняются. То есть если кто-то воспользовался вашей веткой, а вы ее поребейсили — у вашего товарища могут возникнуть проблемы.
Merge — выбирая путь merge приходится отказаться от редактирования коммитов и считать, что как только вы набрали git commit
— коммит высечен в камне, и рано или поздно окажется в мастере.
Тема выбора merge или rebase очень холиварная. По опыту очень мало людей могут правильно использовать merge. Начинающие пользователи git вообще не понимают, что это такое, и часто пишут код и чинят баги внутри merge-коммитов, а на ежедневных совещаниях (стендапах) можно услышать "я вчера мержил весь день ветки".
Если совсем кратко: при использовании rebase будет чистая история, но нельзя организовать работку нескольких человек над фичей в отдельной ветке. При merge наоборот: любой коммит, косой-кривой, с ошибками и ломающий все на свете навсегда впечатывается в историю но зато несколько человек могут работать над фичей в отдельной ветке.
Правильное использование rebase:
git fetch # скачиваем изменения с сервера
# надо находиться в ветке, которую вы хотите обновить
# вместо origin/master нужно вписать вашу mainstream-ветку
git rebase origin/master
# далее, если возник конфликт, исправляем его и делаем
git add file-with-conflict.go
git rebase --continue
# повторяем до тех пор, пока не появится сообщение
# Successfully rebased and updated ...
Правильное использование merge:
git fetch # скачиваем изменения с сервера
# надо находиться в ветке, которую вы хотите обновить
# вместо origin/master нужно вписать вашу mainstream ветку
git merge origin/master
# далее, если возник конфликт
git add file-with-conflict.go
git commit
На мой взгляд, более выгодно привыкнуть делать rebase, так как его вам никто не запретит. А вот merge-коммиты вы как минимум не сможете послать в проекты, которые принимают изменения через список рассылки.
Заключение
Пожалуйста, оформляйте свои коммиты должным образом. Так вы покажете свой высокий профессионализм и сильно упростите жизнь коллегам.
Автор:
guryanov