2. Git hooks. Стандарти роботи з гітом. Rebase vs Merge. Squash merge та WIP commit

Відео версія
(YouTube - для зворотнього зв'язку)

TL; DR

Все що парсить код - в DEV залежностях ! Test runners, linters, debuggers, static analyzers, builders, minifiers...

Git hooks корисний. Почитати тут. Husky js - Зручний спосіб роботи з git hooks

Перевірка назв гілок.

.husky/pre-push
npx validate-branch-name

.validate-branch-namerc.json
{
    "pattern": "^(feature|fix|hotfix)/.+",
    "errorMsg": "Branch name validate failed please rename your current branch"
}

Перевірка назв комітів. Стандарти тут

.husky/commit-msg
npx --no -- commitlint --edit $1

commitlint.config.js
export default { 
  extends: [
    '@commitlint/config-conventional'
  ] 
};

lock файли (composer.lock для бека, yarn.lock|package-lock.json|pnpm-lock.yaml|bun.lockb для фронту) повинні бути в гіті!

Незалежно від того який пакетний менеджер обрали, npm, yarn, pnpm, bun - він повинен бути один на проєкт!

WIP commit - норма. Не соромся зберігатися!

Ревьюште (перевіряйте) самі себе. Навіть якщо є кому вас перевірити Не використовуй git add .; Вимкніть auto adding files in IDE

Стандарти та git hooks

Стандарти роблять код краще, роблять ввід нових співробітників легше, аналітику - легше, ревью - легше, сапорт та розробку - легше. Якщо ви ПМ, то замініть легше на бистріше. Якщо власник продукту, то на дешевше і якісніше.

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

Все що можна перевірити автоматично - можемо назвати стандартом. І тут постає питання, як і де перевіряти. Правильна версія - звісно на сервері. Перед деплоєм, перед ревью, при кожному коміті...

А зараз уявимо нормальний процес розробки. Розробник робить гілку для своєї задачі, code -> commit -> code -> commit -> code -> commit -> push. А далі черга з деплоїв, затримки на сервері й, повірте, розробник не візьметься за нову задачу поки вони не пройдуть - піде пити каву. Повернувшись дивишся що перший, 3й і 10й коміт не відповідає стандартам. А значить його не можна допускати в прод, не можна cherri-pick якийсь коміт, або не кажіть що у вас є стандарт.

І тут розробник лізе і редагує свої коміти, що складно, ненадійно довго і дорого. Дуже довго і дуже дорого.

І тут на допомогу приходять хуки гіта. Ми можемо запускати перевірку або навіть авто правки прямо перед комітом на локальній машині розробника.

Або перед будь-якою дією з гітом. Документація за посиланням.

Husky js

Якщо прочитали документацію знаєте що гіт хуки це звичайний набір команд скрипти які знаходяться в директорії гіту. Ніякої магії. Але ці команди треба якось помістити в цю директорію. Гіт не вміє комітити сам себе. Тому нам треба їх туди скопіювати та зробити це для кожного розробника на кожній його робочий машині. Тут можна накостиляти якийсь shell скрипт, зберігати файли в окремій директорії, і написати його запуск як обов'язковий при сетапі. Через деякий час ми дізнаємося, що розробники помітили що якщо пропустити один степ в сетапі, життя стає легше. Ніхто не каже що ви щось робите не ок. І тут ви дізнаєтесь, що дуже зручно впихнути такий скріпт, на хук, наприклад в пакетний менеджер. На якийсь його хук. post-install-cmd в composer.json на бекенді, або prepere в package.json на фронтенді'. Редагувати його ніхто не буде, бо з великою ймовірністю закомітить це в гіт, а слідкувати важко. І ось ви прийшли до готового рішення яке зветься husky js і робить все це за вас.

Будь ласка, не проходьте всі ці стадії, а одразу поставте хаскі. 2 команди та аж 4кб залежностей. А головне це загальновідомий інструмент про який знають розробники, а не ваше "творіння".

Але тут можна сказати агов - у мене на бекенді одне API, я взагалі не хочу нічого з js там бачити. І я погоджусь, я також не хочу. Але, на жаль, нормальної робочої альтернативи хаскі я не знайшов. Ок її замінити легко, але далі буде commit lint і його альтернативою навіть не пахне. Тому я тут здаюсь приймаю верховенство js у цьому питанні. І ставлю на laravel husky.

Про пакетні менеджери

Зайшовши на документацію husky побачите аж 4 варіанти встановлення. 4 пакетних менеджери npm, yarn, pnpm, bun. Я не буду вказувати який використовувати. Особисто я не працював лише з bun. В усіх є свої плюси, в основному це швидкість. І тут треба сказати, що швидкість важлива не для вас локально, а для деплою. Локально різницю у швидкості ви не відчуєте.

Я використовую npm, тому що він кричить про знайдені вразливості в залежностях, можливо й інші менеджери вже так роблять, і я просто роблю це по звичці. Почитайте, спробуйте, оберіть один.

І тут важливі 3 правила які ідуть один за одним.

lock файли (composer.lock для бекенду, yarn.lock|package-lock.json|pnpm-lock.yaml|bun.lockb для фронтенду) повинні бути в git!

Це очевидно, це написано в купі інструкцій, в кожній документації, але я особисто занадто часто бачив як розробники його ігнорують. ЗАНАДТО ЧАСТО і в занадто різних командах і проєктах. Якщо коротко пакетні менеджери ставлять залежності найновіші з поправкою на обмеження в json файлі. Саме обмеження версії й визначають символи тільди ~(patch), або каретки ^ (minor) перед версією пакета. Про те що ж за версії такі читайте тут. А в lock файлі зберігаються чіткі версії, і нормальний деплой процес гарантує що ваша локальна збірка і збірка на сервері однакова

Незалежно від того який пакетний менеджер обрали, npm, yarn, pnpm, bun - він повинен бути один на проєкт!

Це друга проблема яку я бачу занадто часто. Пів команди на npm хтось на один вичитав що yarn шустріший, а він не встигає, використовувати його. Або ж приходить "геній" розробник каже що до нього на проєкт були одні ідіоти ось він знає про pnpm і тепер всі використовують його ... а поряд лежить yarn.lock.. і зазвичай не просто лежить, а ще й оновлюється, і його зазвичай не ревьюшать "бо щось там автоматом підтягнулось" (а треба!)

Все що парсить код - в DEV залежностях ! Test runners, linters, debuggers, static analyzers, builders, minimizers... Все

Найголовніше по пакетному менеджеру - дивіться куди встановлюєте залежності. Розробники пакетів часто на це забивають і пропонують все ставити в прод. Більшість випадково, а хтось можливо зі злим наміром. Безпека вашої програми - це ваша проблема. Дивіться на це при ревью. І так у вас може бути не лише dev та prod . Та якщо є інші групи пакетів теж слідіть за ними. І на другому плані - звісно швидкість деплою, якщо пакет не треба його не качаймо, все просто.

Validate branch name

В рамках приклада налаштуємо 2 лінтера, всі пов'язані з гітом. Перший validate branch name. Дуже простий пакет, дуже просто працює - перевіряє чи назва бранчі відповідає заданому формату. Це важливо, бо полегшить пошук по гілках, мінімізує кількість помарок, полегшить прив'язку гіту до вашаго таск менеджеру. А також надасть змогу автоматично формувати правильні описи мерж реквестів (наприклад додаючи чекліст для ревью). Для моїх потреб поки треба лише префікс feature|fix|hotfix слеш і що завгодно. Ви ж можете додати, наприклад, номер задачі в трекері.

.validate-branch-namerc.json
{
    "pattern": "^(feature|fix|hotfix)/.+",
    "errorMsg": "Branch name validate failed please rename your current branch"
}

Має сенс перевіряти лише при пуші на сервер. Локально творіть що хочете.

.husky/pre-push
npx validate-branch-name

І так, вимоги до назв гілок ставляться, наприклад в gitlab, але в платних версіях.

Commit lint

Друге що раджу ставити одразу - перевірку назви коміт повідомлення. Плюси всі ті самі що й у назви бранчі плюс орієнтація в історії комітів стане трохи легшою.

Якщо у вас нема правил в компанії - раджу взяти commitlint config conventional. Стандарт розроблявся розумними людьми з підтримкою спільноти.

commitlint.config.js
export default { 
  extends: [
    '@commitlint/config-conventional'
  ] 
};

Для коміт повідомлень в гіті окремий хук commit-msg

.husky/commit-msg
npx --no -- commitlint --edit $1

WIP(work in progress) commit

Ситуації коли щось не дороблено, а треба відійти бувають дуже часто. Наприклад на вас летить іранський дрон камікадзе. І втрачати прогрес не хочеться. А зробити комміт й пушанути не дають, бо в гіт хуках купа всього відбувається. Тут на допомогу приходять WIP коміт та способи ігнорування гіт хуків.

Дуже простий алгоритм роботи з WIP комітом.

По завершенню роботи пушимо все на сервер.

git commit -m 'wip: some text' --no-verify # or just 'wip'  
git push --no-verify

Повернулись до роботи - скасовуємо WIP коміт.

git reset HEAD~

Наступний пуш обов'язково з -f опцією - force push.

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

А якщо DevOps - відруби перевірки на сервері якщо комміт WIP (і заборони мержити в прод звісно)

Merge vs Rebase vs Squash -> semi-linear

Почну з простого Squash мерж реквест. Робить з усієї вашої комітної творчості - один коміт. Зручно коли ви багато тупили, наробили всякі там "fix: code style", 'fix: after review' і так далі.

Про мержи спробую дуже простими словами. Простий мерж. Зливає вашу гілку з іншою залишаючи величезну таку історію злиття. Звідки хто і в якому порядку покомітно в таймлайні. Виходить така здоровезна карта метро, і тим більша чим більше розробників паралельно працюють.

Коли працюєте соло - мерж не проблема.

Ребейс же робить наче всі коміти зробилися послідовно один за одним одразу в бранчу, жодних гілок не було. В ребейза є великий мінус це конфлікти. Розробникам дуже легко зробити помилку й втратити свій або код напарника назавжди при ребейсі.

Для новачків - це просто пекло. А для вас як менеджера це втрачені гроші. І якщо ви запитаєте когось з крутих контор. Відповідь скоріше за все буде ребейс. Там ризики за невміння мінімізуються на етапі рекрутингу.

Але є третій варіант, і раджу його спробувати. В gitlab з коробки доступний semi-linear merge. Він вибудовує історію бранчі так, що всі мержі в неї йдуть послідовно. Наче працював один розробник. Таким чином історія гіта сприймається набагато легше. Нам доступні реверти мерж реквестів, і ми бачимо історію в форматі фіча за фічею, коміт за комітом.

З мінусів - гілка перед злиттям повинна відповідати гілкі в яку зливаємо. Це правиться тим же мержем (або ребейсом - рекомендовано).

Я спробую саме лінійну історію, але на жаль поки на проєкт сам ефекту я не побачу...

Settings => Merge requests => Merge method => Merge commit with semi-linear history

Почитати про варіанти злиття

Нотуємо в README.md

Тут все просто. Всі стандарти рекомендації, особливості роботи заносимо в README.

І пам'ятаємо - їх ніхто ніколи не прочитає. Документацію взагалі ніхто ніколи не читає. Окрім setup - все в сірій зоні. Про документацію на фронті та бекі буде окреме відео.

Але писати README обов'язково для того, щоб тикати ним розробників, які кричать "я не знав", "документації нема" або ж думають що лід якийсь самодур і придумав правила, бо на його останній роботі все було ідеально і ніяких перевірок не було (чому він не на тій ідеальній роботі зазвичай ніхто не зна).

README.md
### Branch naming
Requirement described in the file
```
.validate-branch-namerc.json
```
branch name auto validate on pre-push hook using
[validate-branch-name](https://www.npmjs.com/package/validate-branch-name
) package

### Commit convention
Each commit message must follow the [commit convention](https://www.conventionalcommits.org/)

Automatic message validation on commit-msg git hook.
All setting described in the file
```
commitlint.config.js
```

## Merge commit with semi-linear history

Use line git history. Read [documentation](https://docs.gitlab.com/ee/user/project/merge_requests/methods/#merge-commit-with-semi-linear-history)

Self-review

На останок пара рекомендації які покращать вас як розробника в рази.

Ревьюште (перевіряйте) самі себе. Навіть якщо є кому вас перевірити

Це здається легким і очевидним. Але ні. Ревьюшити себе дуже й дуже складно. Ви працювали багато часу, писали тести, вирішували тонну проблем. І ось можна нарешті створити той мерж реквест. Мозку вже неважливо, що там. Мозок вже переміг, він наче все пам'ятає і наче все там ок.

А ось що там не "ок" не побачить - це головна людина від якої залежить ваша кар'єра - ревьювер.

Особисто мені жодна порада не допомогла більше ніж саморевью.

Друга порада тісно пов'язана з першою.

Не використовуй git add .; Вимкніть auto adding files in IDE

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