Как оформить серию коммитов Git, чтобы её приняли в любой проект

Добрый день, коллеги! Доказывать, что нужно использовать систему контроля версий, уже давно не нужно. И Git занял тут лидирующую позицию, стремительно вытеснив SVN. Но это инструмент, а инструментом нужно уметь пользоваться, чтобы добиться лучших результатов. Как топором, один человек сможет просто срубить дерево, а другой из этого дерева сможет сделать великолепную скульптуру. Так и с помощью Git, один человек сможет просто не потерять результаты своего труда за день, а другие смогут организовать совместную работу над проектом нескольких сотен человек. Да так, что о любой строчке кода можно будет и через пять лет сказать, откуда она взялась и для чего нужна.

Постараюсь рассказать для начинающих и не очень разработчиков, как оформлять свои коммиты, чтобы их максимально быстро и без претензий принимали в любые проекты, как опенсорсные так и коммерческие.

Заголовок

Начнем с самой важной, на мой взгляд, части, которая не требует погружения в технические детали: заголовок коммита. Это первая строка в commit message, которая отделена пустой строкой от остального текста. Человеку, который только пишет код, может быть сразу не очевидна важность заголовка. Но те люди, которые следят за репозиторием, выпускают релизы и работают с основными ветками точно будут очень благодарны за качественные заголовки. Дело в том, что при работе с большим количеством коммитов никто не заглядывает в тело сообщения и, тем более, в код. В основном смотрят списки, которые можно получить командой:

git log --pretty=oneline
Пример вывода git commit --pretty=oneline
Пример вывода 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 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 строчек мало кому под силу. Так что несмотря на то, что с точки зрения менеджеров это одна задача, вам будет удобнее эту задачу декомпозировать:

  1. Добавить шаблон микросервиса.

  2. Добавить пару базовых методов и работу с базой.

  3. Добавить взаимодействие с сервисом X.

  4. Добавить еще методов.

  5. Добавить обработку каких-то сложных случаев и дополнительных параметров в первые два метода.

Можно конечно все это сделать одним коммитом. Но скорее всего за один день это сделать не получится, так что встанет вопрос о сохранении промежуточной работы. Во-вторых, начальник захочет отслеживать ваш прогресс и если делаете отдельными коммитами — это будет гораздо проще. Да и самому удобно наметить какой-то план работы и на каждый пункт этого плана делать коммит.

Бывают ситуации, когда вы вошли в раж и починили три бага и реализовали пару фич и только потом вспомнили, что эти изменения надо еще и куда-то послать. В этом случае поможет интерактивный коммит. Подробно останавливаться на нем не буду, так как иначе статья получится слишком большая. Запускается командой:

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-коммиты вы как минимум не сможете послать в проекты, которые принимают изменения через список рассылки.

Заключение

Пожалуйста, оформляйте свои коммиты должным образом. Так вы покажете свой высокий профессионализм и сильно упростите жизнь коллегам.

Source link

Добавить комментарий

Ваш адрес email не будет опубликован.