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

Junior: питання на співбесіді з теми «Історія й гілки»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

5 питань

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

git merge main (у своїй гілці) створює коміт злиття з двома батьками. Історія показує, як було насправді: дві гілки розвивалися паралельно і зійшлися.

git rebase main переписує коміти гілки так, ніби вона почалася з останнього коміту main: кожен коміт застосовується заново й отримує новий хеш. Історія стає лінійною, без комітів злиття.

git switch feature
git rebase main          # перебудувати feature поверх свіжого main
git switch main
git merge --ff-only feature   # тепер злиття - просте перемотування

Що обирати:

  • Rebase - для власної гілки, щоб підтягнути свіжий main і отримати чисту історію перед pull request.
  • Merge - для злиття спільних гілок і коли важливо зберегти, як саме йшла робота.

Головне правило rebase: не переписувати коміти, які вже опубліковані й використовуються іншими. Після rebase гілку доведеться відправляти з --force-with-lease, а в колег, що працювали поверх старих комітів, історія розійдеться.

Конфлікти бувають і там, і там. Але при rebase їх розв'язують по коміту: якщо гілка з 10 комітів зачіпає те саме місце, конфлікт може з'явитися кілька разів.

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

Спосіб залежить від того, чи коміт уже відправлено в спільний репозиторій.

Коміт ще локальний - git reset пересуває гілку назад:

git reset --soft HEAD~1    # коміт скасовано, зміни лишилися в індексі (staged)
git reset HEAD~1           # (--mixed) зміни лишилися у файлах, але не staged
git reset --hard HEAD~1    # коміт і зміни видалено повністю

Найчастіше потрібен --soft чи --mixed: виправити повідомлення, додати забутий файл, розбити коміт. --hard знищує незбережену роботу.

Виправити лише останній коміт - git commit --amend: змінити повідомлення чи додати файли.

Коміт уже в спільній гілці - git revert створює новий коміт, що скасовує зміни старого. Історія не переписується, тож колегам нічого не ламається:

git revert a1b2c3d
git push

Головна відмінність: reset переписує історію (коміт ніби зник), revert додає до неї (коміт є, і є його скасування). Для main, з якого вже розгорнуто прод, - лише revert.

Для окремих файлів є git restore: git restore app/User.php - відкинути незакомічені зміни у файлі, git restore --staged app/User.php - прибрати з індексу.

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

git log без параметрів виводить довгий список комітів, у якому важко зорієнтуватися. Кілька параметрів роблять його зручним інструментом.

Компактний вигляд і граф гілок:

git log --oneline                     # хеш і заголовок в один рядок
git log --oneline --graph --all       # граф усіх гілок
git log --oneline -10                 # лише останні 10

Фільтри:

Параметр Що показує
--author="Olena" коміти автора
--since="2 weeks ago" за період (--until - до дати)
--grep="invoice" коміти, в повідомленні яких є слово
-- app/Models/User.php коміти, що змінювали файл
--follow -- file.php історія файлу з урахуванням перейменувань
main..feature коміти гілки feature, яких ще немає в main
-S "calculateTax" коміти, що додали чи видалили цей рядок коду

Зміни разом з історією:

git log -p -- app/Services/Billing.php   # diff кожного коміту
git log --stat                            # які файли й скільки рядків змінено

git show - один об'єкт детально:

git show a1b2c3d                 # повідомлення коміту й diff
git show HEAD~2 --stat           # коміт, на два раніше від поточного
git show main:config/app.php     # вміст файлу в гілці main, не перемикаючись

Власний формат:

git log --pretty=format:"%h %ad %an %s" --date=short

Практичні поради:

  • -S (пошук «кирками», pickaxe) - найшвидший спосіб з'ясувати, коли з'явилася чи зникла функція;
  • main..feature перед злиттям показує, що саме потрапить у main;
  • часто вживані варіанти варто оформити як аліас: git config --global alias.lg "log --oneline --graph --all".

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

git commit --amend замінює останній коміт новим: з виправленим повідомленням, доданими файлами чи обома змінами.

Виправити повідомлення:

git commit --amend -m "Fix invoice total rounding"

Додати забутий файл:

git add tests/Feature/InvoiceTest.php
git commit --amend --no-edit   # зберегти попереднє повідомлення

Змінити автора (наприклад, коміт зроблено з неправильною поштою):

git commit --amend --reset-author --no-edit

Що відбувається насправді: Git не змінює старий коміт - коміти незмінні. Він створює новий коміт з іншим хешем і переставляє на нього гілку. Старий коміт лишається в репозиторії (його видно в git reflog), доки його не прибере збирач сміття.

Коли --amend безпечний: коміт ще не відправлено у спільний репозиторій (git push) або гілка особиста й ніхто на неї не спирається.

Коли не можна: коміт уже в спільній гілці (main, гілка колеги). Після --amend локальна історія розходиться з віддаленою:

! [rejected]  main -> main (non-fast-forward)

Щоб відправити, доведеться робити force push - і колеги, що вже отримали старий коміт, матимуть конфлікти й дублікати. У спільній гілці замість --amend - новий коміт з виправленням.

У своїй гілці pull request після --amend використовуйте git push --force-with-lease - він не перезапише чужих змін, якщо вони з'явилися на сервері.

Виправити не останній, а давніший коміт - через git commit --fixup <хеш> і git rebase -i --autosquash, а не --amend.

Якщо --amend зроблено помилково: git reset --soft HEAD@{1} повертає гілку на попередній коміт, а зміни лишаються в індексі.

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

git cherry-pick бере зміни з указаного коміту й застосовує їх як новий коміт у поточній гілці.

git switch release/2.4
git cherry-pick a1b2c3d

Новий коміт має той самий diff і повідомлення, але інший хеш: в нього інший батько й час створення.

Типові сценарії:

  • виправлення в реліз: баг виправлено в main, і те саме виправлення потрібне в гілці підтримуваної версії;
  • коміт не в тій гілці: зроблено коміт у main замість гілки задачі - перенести його в потрібну гілку, а з main прибрати;
  • врятувати частину роботи з гілки, яку не будуть зливати.

Кілька комітів:

git cherry-pick a1b2c3d e4f5g6h      # окремі коміти
git cherry-pick main~3..main         # діапазон (без main~3)

Корисні параметри:

Параметр Що робить
-x додає в повідомлення рядок (cherry picked from commit ...) - видно походження
--no-commit (-n) застосувати зміни без коміту, щоб об'єднати кілька в один
--continue / --abort продовжити після конфлікту чи скасувати

Конфлікти розв'язуються як при злитті: виправити файли, git add, git cherry-pick --continue.

Чому не варто зловживати:

  • дублікати в історії: той самий зміст у двох комітах з різними хешами. Коли гілки згодом зіллються, Git зазвичай розпізнає однакові зміни, але інколи це дає конфлікти;
  • заміна злиттю: якщо доводиться переносити десятки комітів, правильніше злити гілку чи переглянути процес роботи з гілками.

Порада: для виправлень у кількох версіях використовуйте -x - тоді з історії релізної гілки видно, звідки прийшла кожна зміна.

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