Middle: питання на співбесіді з теми «Історія й гілки»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
5 питань
git rebase -i відкриває список комітів гілки, де кожному можна задати дію. Так історію з «wip», «fix typo», «ще фікс» перетворюють на кілька змістовних комітів.
git rebase -i main
pick a1b2c3d Add invoice model
squash d4e5f6a fix typo
pick 9f8e7d6 Send invoice email
fixup 1a2b3c4 forgot import
reword 5d6e7f8 wip
Основні дії:
pick- лишити коміт як є.reword- змінити повідомлення.squash- злити з попереднім, об'єднавши повідомлення.fixup- злити з попереднім, відкинувши повідомлення.edit- зупинитися на коміті, щоб змінити його чи розбити на кілька.dropабо видалити рядок - прибрати коміт.- Переставити рядки - змінити порядок комітів.
Зручний прийом - fixup-коміти:
git commit --fixup=a1b2c3d # «виправлення до коміту a1b2c3d»
git rebase -i --autosquash main # сам поставить fixup у потрібне місце
Навіщо: рев'юеру легше читати кілька логічних комітів, ніж двадцять випадкових; git bisect і git blame працюють точніше; коміт можна відкотити цілком.
Обережно: це переписування історії. Лише для своєї гілки, а потім - git push --force-with-lease. Якщо щось пішло не так, git rebase --abort повертає все як було, а після завершення врятує git reflog.
Багато команд обходяться без цього, вливаючи PR через squash merge - тоді вся гілка стає одним комітом у main.
git bisect шукає коміт, що вніс помилку, двійковим пошуком. Ви називаєте один «поганий» коміт (де баг є) і один «хороший» (де його ще не було), а Git щоразу переходить на середину проміжку й питає, чи є там баг.
git bisect start
git bisect bad # поточний коміт - зламаний
git bisect good v2.3.0 # у цьому релізі все працювало
# Git перемикається на коміт посередині
# перевіряєте й кажете:
git bisect good # або
git bisect bad
# ...через кілька кроків:
# a1b2c3d is the first bad commit
git bisect reset # повернутися, звідки почали
Серед 1000 комітів винуватця знайдено за ~10 перевірок.
Автоматично - якщо є команда, що відповідає «добре/погано» кодом виходу:
git bisect run php artisan test --filter=InvoiceTotalTest
Код 0 - коміт хороший, 1-127 (крім 125) - поганий, 125 - пропустити (наприклад, коміт не збирається).
Що допомагає bisect:
- Невеликі коміти, кожен з яких працює. Якщо коміт «напівготовий» і падає з іншої причини, bisect заплутається - такі коміти пропускають (
git bisect skip). - Тест, що відтворює баг, - його пишуть першим, а потім ганяють через
bisect run.
Знайшовши коміт, видно не лише рядок, а й контекст: повідомлення, задачу, автора - часто це пояснює, чому баг з'явився.
Звичайний git rebase main переносить усі коміти поточної гілки, яких немає в main. Але інколи потрібно перенести лише частину комітів чи змінити основу на зовсім іншу гілку. Для цього є --onto:
git rebase --onto <нова-основа> <стара-основа> <гілка>
Git бере коміти з діапазону стара-основа..гілка і відтворює їх поверх нова-основа.
Сценарій 1 - гілку створено не від тієї гілки. Гілку feature почали від develop, а треба було від main:
main ── A
\
develop B ── C
\
feature D ── E
git rebase --onto main develop feature
main ── A ── D' ── E' (feature)
Коміти B і C з develop у feature не потрапили.
Сценарій 2 - залежні pull request. Гілка part-2 побудована поверх part-1. Після злиття part-1 через squash її коміти в main мають інші хеші, і звичайний git rebase main намагатиметься застосувати їх повторно з конфліктами:
git rebase --onto main part-1 part-2
Переноситься лише власна робота part-2.
Сценарій 3 - видалити коміти з середини гілки:
git rebase --onto HEAD~5 HEAD~3
Коміти HEAD~4 і HEAD~3 зникають, решта переноситься на HEAD~5.
Що пам'ятати:
- як і будь-який rebase,
--ontoстворює нові коміти з новими хешами - для вже опублікованої спільної гілки це переписування історії; - якщо заплуталися -
git rebase --abortпід час процесу чиgit reflogпісля нього; --update-refs(Git 2.38+) оновлює й проміжні гілки в ланцюжку - зручно для стопки залежних гілок.
Докладніше в документації: git rebase: перенесення гілки з --onto
Злити гілку feature у main можна трьома способами, і від вибору залежить вигляд історії.
Fast-forward - можливий, коли main не просунувся після створення feature. Git просто пересуває вказівник main на останній коміт feature, нового коміту не створюється:
до: main ── A після: A ── B ── C (main, feature)
\
B ── C (feature)
Історія лінійна, але факт існування гілки зникає: не видно, які коміти були однією задачею.
--no-ff - завжди створює коміт злиття, навіть коли fast-forward можливий:
git merge --no-ff feature
A ────────── M (main)
\ /
B ── C ── (feature)
Видно межі задачі; відкотити всю функціональність можна одним git revert -m 1 M. Ціна - більше комітів злиття в історії.
Squash - усі зміни гілки стають одним новим комітом в main:
git merge --squash feature
git commit -m "Add invoice export"
Історія main чиста: одна задача - один коміт. Але:
- проміжні коміти гілки в
mainне потрапляють - деталізація дляgit bisectіblameвтрачається; - Git не вважає
featureзлитою (git branch --mergedїї не покаже), аgit branch -d featureвідмовиться її видаляти; - якщо продовжити роботу в тій самій гілці, наступний squash може дати конфлікти з уже злитими змінами.
Порівняння:
| Fast-forward | --no-ff |
Squash | |
|---|---|---|---|
| коміт злиття | ні | так | ні |
проміжні коміти в main |
так | так | ні |
| межі задачі видно | ні | так | так (один коміт) |
Налаштування: git config merge.ff only дозволяє лише fast-forward (злиття впаде, якщо воно неможливе), pull.ff only - те саме для git pull. На GitHub і GitLab спосіб злиття pull request обирається в налаштуваннях репозиторію.
git blame показує для кожного рядка файлу, в якому коміті й ким він востаннє змінений:
git blame app/Services/Billing.php
git blame -L 40,60 app/Services/Billing.php # лише рядки 40-60
a1b2c3d4 (Olena 2026-03-14 41) $total = round($subtotal * 1.2, 2);
Мета - не «кого звинуватити», а «чому так»: знайдений хеш веде до коміту з повідомленням, pull request і обговоренням, де видно причину рішення.
git show a1b2c3d4
Проблема: шумні коміти. Масове форматування (Pint, Prettier), перейменування чи перенесення коду роблять автором кожного рядка «коміт форматування», і справжня історія ховається.
Що допомагає:
| Параметр | Що робить |
|---|---|
-w |
ігнорує зміни пробілів |
-M |
розпізнає рядки, переміщені в межах файлу |
-C |
розпізнає рядки, перенесені з інших файлів того самого коміту (-C -C -C - з будь-якого коміту) |
--ignore-rev <хеш> |
пропустити конкретний коміт і показати попереднього автора |
Файл ігнорованих комітів - рішення для всієї команди:
# .git-blame-ignore-revs
# Pint: форматування всього проєкту
3f9e1a2b7c...
git config blame.ignoreRevsFile .git-blame-ignore-revs
GitHub автоматично враховує файл .git-blame-ignore-revs у корені репозиторію у своєму інтерфейсі blame.
Коли рядок змінювався багато разів:
git log -L 40,60:app/Services/Billing.php- вся історія змін саме цього фрагмента, з diff кожного кроку;git log -S "1.2"- коміти, що додали чи прибрали конкретний вираз;- в IDE blame вбудований («Annotate with Git Blame» у PhpStorm) з можливістю переходити до попередньої ревізії рядка.