Git best practices

Git используется повсеместно и нет смысла рассказывать что это и для каких целей предназначено. Тем не менее на своей практике я сталкиваюсь с одними и теми же ошибочными сценариями использования git в командах разного уровня зрелости. За последний год я несколько раз проводил воркшопы для стажеров и специалистов среднего уровня. Тема оказалась на редкость популярной. Это и послужило причиной создания данной статьи.

Git best practices

Git используется как хранилище исходного кода и было бы логично начать с общих рекомендаций касательно его написания.

Требования к коду

Поскольку мой профиль это инфраструктура, то и рекомендации относительно написания кода я хочу дать с уклоном в сторону инфры. Тем не менее актуальны они будут и во многих других случаях:

  • Пишите понятные комментарии. Не подчеркивайте очевидные моменты, но минимально достаточный для понимания объем должен присутствовать. Иногда на одну строчку кода можно увидеть десятки строчек комментариев. Если это необходимо для понимания, пишите.
  • Не пренебрегайте наличием README.md. Даже в самом начале работ можно написать базовую информацию о проекте/приложении и этого будет достаточно. По мере развития проекта будете дополнять. Если нужна объемная документация, создайте отдельный каталог, например как в репозитории проекта etcd 1:

  • Код покрыт тестами. Возможно вы скажете, что тесты это для кода приложений и ошибетесь. Инфраструктуру тоже можно и нужно покрывать тестами. Например для этого можно воспользоваться самописными сценариями в сочетании с docker-compose или взять готовые фреймворки.

Примечание: да, для тестирования инфраструктурного кода есть свои фреймворки. Наглядный пример – Ansible Molecule.

  • Если ваш проект будет содержать большой объем кода, подумайте с самого начала над приданием ему понятной структуры.

Примечание: некоторые инструменты, например Ansible, изначально рекомендуют определенную структуру для вашего проекта, см.  Ansible Directory Layout. Terraform рекомендует структуру для модулей – Terraform Standard Module Structure.

  • Используйте линтеры. Важность этого инструмента трудно переоценить, когда работаешь в команде и у каждого инженера есть свое видение относительно стиля написания кода. Договоритесь об этом с самого начала и установите понятные правила. Обычно линтер складывает в корень проекта файл с конфигурацией – некоторые правила можно поменять, а некоторые – проигнорировать.

Версионирование

Выпуская программное обеспечение необходимо решить вопрос версионирования.

Теги

Используйте теги для обозначения точек в истории коммитов. Теги не несут изменений в исходный код, но они могут содержать информацию о версии программного обеспечения, его состоянии (stable, release candidate, alpha, beta, etc…) и многом другом. Их можно и нужно использовать для именования артефактов.

Примечание: теги это ни что иное, как указатели на определенные коммиты.

Возникает вопрос какую схему версионирования применить, по какой логике наращивать версии. Благо за вас уже все придумали – смело используйте Semantic Versioning 2.

Примечание: тут вы можете сказать, что у вас свои рабочие процессы и вам нужна особая логика управления версиями. Не нужно изобретать велосипед, бесконечно подгоняя инструменты под ваши задачи. В конце концов Semantic Versioning включает в себя опыт множества инженеров по всему миру и уж лучше начать именно с него.

С тегами разобрались.

Релизы

Если у вас есть теги, можно выпускать релизы. Релиз это указатель на тег, который может быть снабжен расширенной текстовой информацией, например это может быть change log или список выполненных в рамках релиза задач. К релизу вы можете прикрепить файлы, например бинарные артефакты вашего приложения.

Теги и релизы похожи. Например тег с аннотацией также может содержать некоторое описание. Однако релизами вы не сможете управлять через git. Чтобы создать релиз, придется дернуть api или руками прокликать по веб-интерфейсу.

Примечание: а вы знали, что приложения наподобие GitHub, GitLab и др. называют системами ALM – Application Lifecycle Management? Теперь знаете :).

Создавать теги и релизы можно вручную, но всегда лучше делать это автоматически. Например можно включать нужную информацию в commit message и настроить все так, чтобы ваш CI это понимал. Для GitHub есть action с поддержкой Semantic Versioning 3, а вот пример использования – ansible-k8s-cluster/.github/workflows/release.yml. Так будут выглядеть ваши релизы:

GitLab также имеет подобные инструменты, посмотрите статью Publish npm packages to the GitLab Package Registry using semantic-release.

Управление изменениями

Начнем с наиболее распространенных сценариев.

Скажите нет пушам в мастер

Пушить напрямую в мастер является плохой практикой. Этому есть множество причин, например:

  • При отправке некорректных изменений как минимум получите битую мастер-ветку,
  • Как максимум ваш CI подеплоит битое приложение в prod/staging/test/etc…,
  • Вы лишаете себя и команду возможности обсудить изменения в комментариях к PR (MR – для кого терминология GitLab ближе),
  • Вы не сможете докидывать изменения, не загаживая при этом мастер,
  • Вас ждет ужасная история коммитов в мастере. Тяжело будет откатывать изменения, ровно как и работать в команде, а уж тем более масштабировать проект

Отдельно стоит сказать о force push. Никогда его не используйте, за исключением известных сценариев 4 и с полным осознанием возможных последствий.

Если нужно вернуть прежнее состояние, то есть более безопасные инструменты отката изменений, например git revert. В целом мне неизвестны сценарии, когда частое использование git push –force было бы реально оправданным.

Один pr – одно изменение

Речь о логическом изменении внутри одного pr. Если возможно раздробить вашу задачу на несколько небольших изолированных изменений, которые будет легко вмерджить без последствий, то сделайте именно так. Чем меньше изменения и чаще деплой, тем лучше.

Примечание: банальный пример – у вас моно репозиторий и ваша задача подразумевает изменения во фронте и бэке. Выкатите сначала изменения на бэке, оттестируйте, потом занимайтесь фронтом. Идея в том, чтобы деплоить минимальными порциями. Предположим вы таки выкатили фронт и бэк враз. Фронт оказался кривой и надо откатиться. С бэком все хорошо, но вы вынуждены откатывать и его тоже, либо делать сложный git revert.

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

Один pr – одна задача

Отправлять изменения из разных задач в один pr плохая идея, даже если эти изменения очень удобно смотрятся вместе. На то есть несколько причин:

  • если нужно будет откатиться только по одной задаче, это будет сложно,
  • если одна задача зависнет в обсуждениях, вторая в этом случае повиснет мертвым грузом,
  • вы лишаете себя красивого и понятного дерева коммитов (см. следующую главу)

В целом данная рекомендация также является следствием подхода “деплой чаще, но меньшим объемом”.

Номер задачи в commit message

Этот пункт в комментариях не нуждается. Очень удобно, когда commit message содержит еще и номер задачи, к которой от относится. Многие сервисы автоматически делают ссылки кликабельными. GitHub в их числе (обратите внимание на аккуратные ссылки):

А GitLab имеет интеграцию с Jira 5:

Refer to Jira issues by ID in GitLab commits and merge requests.

Если следовать всем рекомендациям выше, то на данный момент каждый коммит в вашем мастере содержит небольшие изолированные изменения, понятный commit message и в нем же кликабельную ссылку на соответствующую задачу. А что, красиво.

Merge vs Rebase

О Merge и Rebase сказано уже очень много и маловероятно, что у меня получится сделать это доступнее для понимания. Прочтите лучше статьи Merging vs. Rebasing – и git rebase.

От себя я посоветую держать ваши ветки всегда в актуальном состоянии, регулярно подтягивая изменения из master. Какой инструмент использовать – Merge или Rebase – уже полностью ваше дело.

Squash and merge

Это еще одна опция, призванная держать основную ветку в чистоте и порядке. Привыкните организовывать рабочий процесс таким образом, чтобы изменения из веток вливались в мастер одним единственным коммитом. Большинство общеизвестных сервисов позволяют жестко определить алгоритм действий при слиянии веток, например в GitHub можно принудительно разрешить только Squash and merge:

В GitLab такая функция также присутствует.

IDE vs shell

Любители IDE будут смеяться, но я все операции с гитом выполняю в консоли, вручную вбивая нужные команды:

Выглядит старомодно в век интерфейсов:

Но от коллег-любителей IDE я слышал фразы “я случайно удалил 10 веток в ориджине”. От представителей старой школы, работающих в консоли, я такого не встречал.

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

Выводы делайте сами. Я хочу посоветовать больше времени уделять получению базовых знаний. Идеально начать например с чтения статьи Git remote, а по вопросу внедрения лучших практик можете обратиться ко мне. Успехов!

Яндекс.Метрика