Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

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.

Докладніше в документації: git push

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.

Докладніше в документації: git worktree

Поки ви працюєте над задачею, 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» змушує оновлювати гілку перед злиттям.

Докладніше в документації: git rebase

Lock-файли змінюються майже при кожній зміні залежностей, і дві гілки, що додали по пакету, гарантовано конфліктують. Розв'язувати такий конфлікт вручну, редагуючи JSON, не можна: у composer.lock є content-hash - хеш вмісту composer.json, і ручне злиття майже завжди дає невідповідність або неузгоджене дерево залежностей.

Правильний спосіб для Composer:

  1. розв'язати конфлікт у composer.json - він короткий і зрозумілий людині;
  2. взяти composer.lock з основної гілки;
  3. повторити зміни своєї гілки командою 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: розв'язання конфліктів злиття