Annual Open Tech Conference - ISsoft Insights 2021. June 19. Learn more.
×Закрыть

Коли дрібниці важливі. Пишемо коміти в Git

Усім привіт, мене звуть Ілля, я Software Engineer у Dev.Pro. Ця стаття — про коміти та їхню надважливу функцію у житті розробника. Вона стане у пригоді тим, хто хотів би навести лад у git history і changelogs, а заодно поліпшити комунікацію.

In case of fire git commit → git push → git out.

Цей колись смішний жарт бачила, мабуть, кожна людина, що має хоч якийсь стосунок до IT. Коміти — це цінна штука, але водночас буденна, хоча так було не завжди. Ще кілька років тому заливати зі схрещеними пальцями апдейти по FTP було так само поширеною практикою, як сьогодні «коміт → пуш → додомку». І якщо з останніми двома кроками все більш-менш зрозуміло, то про те, як робити класні коміти, нерідко виникають суперечки.

Ілюстрація Аліни Самолюк

Основи

Отже, я вирішив поглянути на те, що відбувається у світі стосовно комітів, почитати, що пишуть розумні люди, і порівняти це з власним досвідом. Виявилось, що «гарний коміт» описують набором порад, який має плюс-мінус однаковий вигляд:

  1. Відділяйте заголовок коміта від його тіла порожнім рядком.
  2. Пишіть заголовок, що містить не більше як 50 символів.
  3. Починайте заголовок з великої літери.
  4. Не ставте крапку в кінці заголовку.
  5. Не пишіть у рядку тіла коміта більше ніж 72 символи.
  6. Позбудьтесь непотрібних знаків пунктуації.
  7. Додавайте тіло коміта тільки за потреби: текст має пояснювати зміст змін.
  8. Використовуйте імператив у заголовку.

Приклади того, яким має бути коміт згідно з цими рекомендаціями, ретельно розібрані у цій статті.

Більшість рекомендацій тісно пов’язані з правилами англійської граматики та командами git format-patch та send-email. Наприклад, порожній другий рядок означатиме, що перший — це заголовок листа, а все інше — сам текст повідомлення. Правило про довжину рядка залишилось на пам’ять від старих терміналів з обмеженням ширини у 80 символів і відповідного робочого етикету. А правило про імператив у заголовку виведено для того, щоб успадкувати тон автоматичних комітів, які генеруються самим Git під час merge/revert операцій.

Список ніби зрозумілий, і навіть є мотивація за кожним із цих пунктів. Але ось проблема: ці правила були описані у 2008 році. Тобто 12 років тому. Для ІТ — ціла ера, чи не так?

Як це працює сьогодні

За цей час зазнало змін майже все. І точно змінився набір інструментів, які використовують команди під час розробки: починаючи від каналів комунікації, як-от Zoom і Slack, і до IDE, які сьогодні можуть значно більше, ніж 2008-го. Змістився і фокус уваги кодера: більше не потрібно шукати правди в коментах до коду або в тексті коміту: процес розробки став доволі цілісною штукою з вибудованою інфраструктурою. Середньостатистичний аутсорс-проєкт радше користується JIRA і Bitbucket, а не розсилкою патчів електронною поштою. Так що, якщо потрібно щось прочитати чи дізнатись, я просто відкриваю тікет і читаю. Або вікі, або ще щось, що під’єднане до інфраструктури проєкту.

Звісно, для маленьких проєктів, які люди пишуть для себе і не планують нікому показувати, найголовнішим, найімовірніше, залишиться критерій зручності для автора. Цікавіше поговорити про роботу з іншими людьми. Мій перший вчитель в IT навчав так: «Пишіть код таким чином, ніби після вас його буде підтримувати людина з маніакальними садистськими нахилами». Ми сміялися з цього: ну хіба ж може таке бути, щоб в IT працювали маніяки? Тут же всі такі гарні, розумні й милі!

Виявилось, що «маніакальні» здібності можуть прокинутись і в тобі самому, особливо тоді, коли бачиш коміт, в якому автор змінив 12 файлів в ядрі й лаконічно написав WIP. Яка саме робота була «in progress» в той момент, коли цей коміт з’явився, і які цей код розв’язує задачі, доведеться здогадуватись без пояснень. Схоже трапляється через те, що немає жодних домовленостей щодо того, якими мають бути коміти. Щоб такого не було, треба це узгоджувати.

У кожному конкретному випадку домовленості можуть трохи відрізнятися, але принцип буде схожим: коміту потрібен формат. Якщо він відповідає цьому формату — коміт ок, якщо ні — не ок. Постає питання: як визначити цей формат і як перевіряти коміт на відповідність.

Є кілька опцій:

  • Щоб не вигадувати черговий велосипед (хіба нам своїх мало?) можна взяти за основу той самий формат, яким користується більшість, або ваш основний фреймворк. Наприклад, пишете на Angular — подивіться, як вони комітять код свого фреймворку, може, в них є політика або гайдлайн.
  • Можна пошукати якийсь відомий підхід з хорошою документацією. Наприклад, Conventionalcommits.org. Він добре описаний, має достатню кількість посилань на різний допоміжний тулінг, а ще його автори хоча й пишуть, що базують концепцію на попередньому пункті, але все ж не прив’язуються ні до якого фреймворку. Так що його можна використовувати будь-де, не витрачаючи час на відповіді на закономірні запитання типу «чому у нас проєкт на PHP, а conventional commit — ангулярівський?».
  • Виходити з можливостей своєї інфраструктури. Майже всі користуються JIRA, чи не так? А точніше навіть JIRA+Bitbucket. У них є такий зручний інструмент — смарт-коміти. Якщо коротко, то одним комітом можна затрекати час, залишити коментар до відповідного тікета і змінити йому статус. Усе, що треба — дотримуватись формату й налаштувати цю фічу на своєму проєкті.

Усі вищенаведені варіанти — про формат заголовку. Перевірка на відповідність формату виконується за допомогою pre-commit hooks, які порівнюють текст із певною регуляркою. Наразі одним із найпопулярніших git hook runner + linter рішень є поєднання husky і commitlint.

В результаті матимемо husky pre-commit hook, який буде спрацьовувати щоразу, коли автор хоче зробити коміт, commitlint, який буде дивитись на свої конфіги й перевірятиме, чи відповідає текст коміту усім зазначеним правилам.

Такий простий сетап вирішує цілу низку питань: від загальної невизначеності щодо формату комітів до конкретних проблем з боку CI. Наприклад, одного разу специфічні для Windows символи, що потрапили в коміт-меседж із домашньої машини одного із тіммейтів, виявились сильнішими за build-скрипт. Зрозуміло, що це можна вирішити різними способами, але чи не краще розв’язувати проблеми саме там, де вони виникають?

Коли проблеми вирішено, варто перейти до питань автоматизації релізів. Це також дуже зручно робити, якщо у вас є домовленість про формат комітів. Припустимо, ви узгодили, що є задачі трьох типів: fix, task, improvement. Також ви погодили, що формат коміту матиме таку схему: type(scope?): subject, де type — це тип задачі, scope — домен або частина проєкту, до якого вона належить. І subject — це, власне, те, які зміни відбулися. Додавши до цього інструменти автоматизації, отримаємо зручні й вичерпні release notes для кожної нової версії. Вони матимуть приблизно такий вигляд:

Гарно, правда? Цей слайд взятий з чудової презентації Mario Nebl.

Замість висновків

Для мене найбільшою цінністю гарного коміта є номер задачі в таск-трекері. Вся інформація — там (працюю разом із чудовою командою бізнес-аналітиків, люблю їх). Гарно описане завдання, та ще й з коментарями, пояснить все набагато краще, ніж дуже вдалий коміт.

У коміті можна написати, що саме змінилося, а із задачі зрозуміло чому. Знаючи причини, на ситуацію можна подивитися з іншого ракурсу, і майже завжди цей ракурс буде вигіднішим. Тому коли я відкриваю анотації до файлу і з коміта розумію, де можна подивитись всю інформацію, то економлю купу часу, мені не треба ходити дізнаватись усю історію — вона доступна майже одразу.

Хочеться також відзначити, що, поки гарний заголовок спрощує життя колегам, тіло коміта спрощує життя самому кодеру. Над завданням можна працювати і декілька днів, втратити контекст, відриватися на більш важливі речі, а потім повернутись до оформлення пул-реквесту і сидіти видивлятись у своїх комітах, що ж саме змінилось. На щастя, тіло коміт-меседжа автоматично підтягується в текстове поле і це суттєво полегшує завдання автору пул-реквесту: гарний опис майже готовий, вся інформація під рукою. Це ніби прості речі, але вони дуже корисні на фінальній стадії роботи над завданням.

Отже, якісний коміт у 2020-му це:

  1. Обговорений і закріплений формат.
  2. Pre-commit хуки і лінтер.
  3. Номер задачі в таск-трекері в заголовку коміта.
  4. Список змін у тілі коміта.


P.S. Я працюю у великій команді, хуки у нас були не завжди, й можу на власному досвіді стверджувати, що рішення про використання їх у репозиторії було дуже важливим. Ми почали писати однакові коміти, і відпала купа дивних проблем. Тож, якщо ви ще не мали нагоди їх застосовувати або якраз думаєте про це, дуже рекомендую. Часу на інтеграцію обмаль, а вплив і зростання якості важко переоцінити!

👍НравитсяПонравилось2
В избранноеВ избранном6
Подписаться на автора
LinkedIn

Похожие статьи




Підписуйтесь: Soundcloud | Google Podcast | YouTube


29 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

Еще часто советуют глаголы писать в императиве в заголовке коммита.

  • „Fix #1234”
  • „Handle file locks”
  • „Add the service XYZ”

вместо Fixed #1234, Handled file locks, Added the service XYZ.

> git out

У меня такая футболка есть.

А вообще, я бы добавил LKML как образец места с массой хороших описаний.

А ещё — никогда не объединять в один коммит принципиально разнородные действия (форматирование, рефакторинг, изменение функциональности), если это не 20-строчник.

Сам за це завжди воюю. Інакше в історії потім повно «small fix» чи «added new class», що, крім того, що не за форматом, так ще й аж ніяк не інформативно.

к слову, кому интересно, есть писать в спец. формате git коммтиты, то у jira есть инструментарий интеграции и очень красиво выходит управлять задачами из git коммита

Там можно не только коммит к задаче подвязать, но и время внести, изменить статус задачи и т.д.

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

Совершенно верно, вы полностью правы, это стоит денег.

если добавить номер Github issue в коммит сообщение в виде #issue_number, то коммит и Github issue красиво свяжутся вместе

В Jira так теж можна, і в більшості інших пристойних баг-трекерах :)

6. Позбудьтесь непотрібних знаків пунктуації.

*радуется по-фоголовски*

А можно попросить показать команды из жизненной практики?
Например — оказалось, что кто-то запушил секреты в репозиторий. Мы тегаем все те ветки как clean
Как теперь их удалить по тегу вместе с историей — чтоб в репозитории нигде секрет не светился?
Спасибо.

Если секреты утекли в репозиторий — нужно менять, а не гитом упражняться.

Если стали выпадать волосы — надо голову рубить, а не лысиной сверкать.

у випадку з сікретами краще вже голову рубати. гіт така штука, що можна відновити будь-що. скільки би разів ви не старались затерти.

Нет, я конечно понимаю, что секреты надо ротировать в любом случае. Но тут вопрос спортивного плана — раз уж мы заговорили о гите. Как удалить историю коммитов по тегу ветки?
Лично я уверен, что решение есть.

Есть конечно. Переписывание истории называется. Гугли git rebase interactive и git force push. Но секреты все равно уже скомпрометированы.

Но разве я просил слова для гугления? Я просил готовый пример. Зачем предлагать человеку то, что ему не нужно?

Учись гуглить — гуру вырастешь

Я учился гуглить когда гугла еще не существовало. Теперь я гуру и умею гуглить. Так что я предлагаю тебе стать моим послушником.
Первое испытание — нагугли мне решение примера.

что именно тут имеется в виду?
удалить любые коммиты, которые светились в ветке BRANCH_1?

Удалить историю коммитов из всех веток, которые помечены тегом clean.

про «ветки, помеченные тегом clean» не совсем понятно, тег может быть только один.
может, подойдет поудалять определенный файл/файлы из истории?
[upd] а, ну, да, ниже уже про git filter-branch написали

аля

```
# ~/dotfiles/gitflow/changeStringsInFileInCurrentBranch.sh «abc.txt» «my_password» «********»
git filter-branch -f —tree-filter «if [ -f $1 ];then sed -i s/$2/$3/g $1;fi»
```
и в конце git push -f —all
.... и дополнительно обязательно поменять все секреты

НО — поскольку ваш код скорее всего либо в Gitlab либо в Github, соответственно вы уберете секрет в гит истории, но в истории пулл реквестов Gitlab/Github будет хранить историю позора, и все-равно можно будет посмотреть на значение секрета :))))

Смысл в том, чтоб вычистить историю.

в чем отличие между "удалить информацию из истории«(что и делает git filter-branch, а git push -f —all затем принудительно применяет на сервере) и «вычистить историю»?

Поясніть, будь ласка, хтось про «git out», бо вперше бачу таку команду і не можу її ніде знайти.

Це гра слів :-D
git out == get out, в разі пожежі запушався і вали :-D

Подписаться на комментарии