Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Добрый день, коллеги! Доказывать, что нужно использовать систему контроля версий, уже давно не нужно. И Git занял тут лидирующую позицию, стремительно вытеснив SVN. Но это инструмент, а инструментом нужно уметь пользоваться, чтобы добиться лучших результатов. Как топором, один человек сможет просто срубить дерево, а другой из этого дерева сможет сделать великолепную скульптуру. Так и с помощью Git, один человек сможет просто не потерять результаты своего труда за день, а другие смогут организовать совместную работу над проектом нескольких сотен человек. Да так, что о любой строчке кода можно будет и через пять лет сказать, откуда она взялась и для чего нужна.
Постараюсь рассказать для начинающих и не очень разработчиков, как оформлять свои коммиты, чтобы их максимально быстро и без претензий принимали в любые проекты, как опенсорсные так и коммерческие.
Заголовок
Начнем с самой важной, на мой взгляд, части, которая не требует погружения в технические детали: заголовок коммита. Это первая строка в commit message, которая отделена пустой строкой от остального текста. Человеку, который только пишет код, может быть сразу не очевидна важность заголовка. Но те люди, которые следят за репозиторием, выпускают релизы и работают с основными ветками точно будут очень благодарны за качественные заголовки. Дело в том, что при работе с большим количеством коммитов никто не заглядывает в тело сообщения и, тем более, в код. В основном смотрят списки, которые можно получить командой:
git commit --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 changeThe API for a password change has been addedAdded API for password changeThis commit adds API for password changeAdd 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-коммиты вы как минимум не сможете послать в проекты, которые принимают изменения через список рассылки.
Заключение
Пожалуйста, оформляйте свои коммиты должным образом. Так вы покажете свой высокий профессионализм и сильно упростите жизнь коллегам.