Middle: питання на співбесіді з теми «Командна робота»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
5 питань
Після rebase чи commit --amend історія локальної гілки розходиться з віддаленою, і звичайний git push відмовляється. git push --force перезаписує віддалену гілку вашою версією безумовно.
Чим це небезпечно: якщо колега встиг відправити в цю гілку свої коміти, force push їх знищить на сервері - ви навіть не побачите, що вони там були.
--force-with-lease перезаписує гілку, лише якщо вона на сервері досі в тому стані, який ви бачили під час останнього fetch. Якщо хтось додав коміти - push відхиляється, і ви спершу інтегруєте їхні зміни.
git rebase main
git push --force-with-lease
Пастка: «той стан, що ви бачили» - це ваш origin/feature. Якщо ви зробили git fetch (чи IDE зробила його у фоні), не подивившись, що прийшло, lease вважатиме нові чужі коміти «баченими». Суворіший варіант - --force-if-includes (Git 2.30+), який вимагає, щоб віддалені коміти були інтегровані в вашу гілку.
Правила команди:
- Force push лише у свої гілки. У
main,develop, релізні гілки - ніколи. - Захищені гілки на GitHub/GitLab забороняють force push і вимагають pull request з рев'ю.
- Якщо над гілкою працює кілька людей - домовитися перед переписуванням історії або обійтися merge.
GitHub Flow - найпоширеніший для веб-застосунків:
mainзавжди готовий до деплою;- кожна задача - коротка гілка від
main; - pull request, рев'ю, CI → злиття в
main→ деплой.
Простий і добре працює з безперервним розгортанням.
Git Flow - для продуктів з версіями й релізами:
main- лише релізи,develop- інтеграція;feature/*відdevelop,release/*для підготовки версії,hotfix/*відmainдля термінових виправлень.
Підходить, коли одночасно підтримують кілька версій (бібліотеки, коробкове ПЗ, мобільні застосунки з рев'ю в сторах). Для веб-сервісу з кількома деплоями на день - надто важкий: багато злиттів, довгоживучі гілки.
Trunk-based development - усі інтегрують зміни в головну гілку дуже часто, щодня чи кілька разів на день:
- гілки живуть години, а не тижні (або коміт одразу в trunk);
- незавершені фічі ховають за feature flags, а не тримають у гілці;
- потрібні сильні автотести й CI.
Мінімум конфліктів злиття і швидкий зворотний зв'язок, але потребує дисципліни й інфраструктури.
Як обирати: веб-продукт з частими деплоями - GitHub Flow чи trunk-based. Кілька підтримуваних версій - щось ближче до Git Flow. Головне - не модель, а короткоживучі гілки: чим довше гілка живе окремо, тим болючіше її злиття.
Worktree - додатковий робочий каталог, прив'язаний до того самого репозиторію. Кожен worktree має свою гілку й свої файли, а історія, об'єкти й налаштування спільні.
git worktree add ../app-hotfix hotfix/invoice-rounding
git worktree add -b review/pr-412 ../app-review origin/feature/export
git worktree list
git worktree remove ../app-hotfix
Сценарій: ви посеред великої задачі з десятком змінених файлів, і терміново потрібно виправити баг у продакшені.
- через
stash: сховати зміни, перемкнутися, виправити, повернутися, дістати зі stash. Каталогvendor/іnode_modules/можуть не відповідати гілці, запущенийnpm run devперезбирає не той код, IDE переіндексовує проєкт; - через worktree: відкрити окремий каталог з гілкою виправлення. Основна робота лишається недоторканою, можна навіть паралельно запустити тести в обох каталогах.
Коли worktree зручний:
- термінові виправлення без переривання поточної роботи;
- рев'ю pull request - перевірити чужу гілку локально, не чіпаючи свою;
- порівняння поведінки двох версій поруч;
- довгі операції (повний прогін тестів, збирання) в одній гілці, поки працюєте в іншій;
- паралельна робота кількох агентів чи скриптів над різними гілками одного репозиторію.
Обмеження:
- одна гілка - один worktree: не можна перемкнутися на гілку, вже відкриту в іншому worktree (Git захищає від двох незалежних змін однієї гілки);
- ігноровані файли не спільні:
vendor/,node_modules/,.envу новому каталозі відсутні - їх доведеться встановити чи скопіювати; - для Laravel: окремий каталог означає окремий
.env; якщо обидва worktree використовують ту саму базу, міграції однієї гілки зачеплять іншу; - видаляйте worktree через
git worktree remove, а неrm -rf- інакше лишаються записи (git worktree pruneїх прибирає).
Порівняно з другим клоном worktree не дублює історію (економить місце й час) і бачить локальні гілки й коміти основного репозиторію без fetch.
Поки ви працюєте над задачею, main просувається. Чим довше гілка живе окремо, тим більше конфліктів і тим вищий ризик, що код, який працює в гілці, зламається після злиття. Тому гілку періодично оновлюють.
Варіант 1 - злити main у гілку:
git fetch origin
git merge origin/main
- історія гілки не переписується - безпечно, навіть якщо в гілці працюють кілька людей;
- push без force;
- конфлікти розв'язуються один раз;
- в історії з'являються коміти злиття
Merge branch 'main' into feature- при squash-злитті pull request вони зникнуть.
Варіант 2 - rebase на main:
git fetch origin
git rebase origin/main
git push --force-with-lease
- лінійна історія: гілка виглядає так, ніби її почали щойно;
- коміти отримують нові хеші, потрібен force push;
- конфлікти розв'язуються для кожного коміту окремо - на довгій гілці це може повторюватися (допомагає
rerere); - небезпечно, якщо гілкою користуються інші: їхні локальні копії розійдуться з вашою.
Як обрати:
| Ситуація | Що краще |
|---|---|
| особиста гілка, коміти ще не рев'ювали | rebase |
| у гілці працюють кілька людей | merge |
| pull request уже на рев'ю | merge - рецензенти бачать, що змінилося після їхніх коментарів (після rebase GitHub гірше показує різницю) |
злиття в main буде через squash |
будь-який, історія гілки все одно зникне |
Як часто: невеликими кроками - щодня чи перед кожним push, а не раз на тиждень. Конфлікт у двох файлах розв'язати легше, ніж у двадцяти.
Найкращий захист від болісного оновлення - короткоживучі гілки: задача на 1-3 дні дає значно менше розбіжностей, ніж гілка, що живе місяць.
На GitHub кнопка «Update branch» у pull request робить merge (чи rebase, якщо обрати). Branch protection з вимогою «Require branches to be up to date» змушує оновлювати гілку перед злиттям.
Lock-файли змінюються майже при кожній зміні залежностей, і дві гілки, що додали по пакету, гарантовано конфліктують. Розв'язувати такий конфлікт вручну, редагуючи JSON, не можна: у composer.lock є content-hash - хеш вмісту composer.json, і ручне злиття майже завжди дає невідповідність або неузгоджене дерево залежностей.
Правильний спосіб для Composer:
- розв'язати конфлікт у
composer.json- він короткий і зрозумілий людині; - взяти
composer.lockз основної гілки; - повторити зміни своєї гілки командою Composer:
git checkout --theirs composer.lock # при rebase - --ours, див. нижче
composer update vendor/added-package # лише пакети, які додала ваша гілка
# або, якщо змінювали лише composer.json без нових пакетів:
composer update --lock
git add composer.json composer.lock
composer update лише для конкретних пакетів, а не всіх: повний composer update оновить усе дерево, і pull request раптом міститиме десятки чужих оновлень.
Для npm:
git checkout --theirs package-lock.json
npm install # перебудує lock з урахуванням вашого package.json
npm також вміє сам розв'язувати конфлікти в package-lock.json: якщо запустити npm install при конфлікті лише в lock-файлі, він об'єднує обидві версії.
Увага до --ours і --theirs при rebase: під час rebase ролі міняються місцями - --ours означає гілку, на яку переносите (тобто main), а --theirs - ваші коміти. Тому при rebase версія main - це --ours.
Як зменшити кількість конфліктів:
- оновлювати залежності окремими невеликими pull request, а не в гілках задач;
- автоматизовані оновлення (Dependabot, Renovate) з частим злиттям;
- перед роботою над задачею оновити гілку з
main.
Перевірка після розв'язання:
composer validate # чи відповідає lock-файл composer.json
composer install # чи встановлюється дерево
php artisan test
Чого не робити: додавати lock-файли в .gitignore, щоб «не конфліктували». Для застосунку lock-файл - гарантія, що продакшен, CI і колеги отримують однакові версії пакетів.
Докладніше в документації: Composer: розв'язання конфліктів злиття