Git: питання на співбесіді рівня Middle
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
35 питань
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) з можливістю переходити до попередньої ревізії рядка.
Після 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: розв'язання конфліктів злиття
У великому монорепозиторії робочий каталог може містити сотні тисяч файлів, з яких розробнику потрібні кілька каталогів. Sparse-checkout лишає в робочому каталозі лише вказані шляхи - решта файлів є в історії, але не на диску.
git clone --filter=blob:none --sparse https://github.com/acme/monorepo.git
cd monorepo
git sparse-checkout set apps/billing packages/ui
git sparse-checkout add packages/auth
git sparse-checkout list
git sparse-checkout disable # повернути всі файли
Режим cone (за замовчуванням) - шаблони лише на рівні каталогів:
- до робочого каталогу потрапляють файли в корені репозиторію, вказані каталоги цілком і файли в їхніх батьківських каталогах (без вкладених підкаталогів);
- Git перевіряє шляхи значно швидше, ніж у режимі довільних шаблонів у стилі
.gitignore, тому для великих репозиторіїв cone рекомендований.
Що це дає:
git status,git checkout,git switchпрацюють з меншою кількістю файлів - значно швидше;- IDE індексує лише потрібний код;
- менше місця на диску.
Найкраще - разом з частковим клоном (--filter=blob:none): тоді вміст файлів поза sparse-набором навіть не завантажується з сервера.
Як інші операції поводяться з невидимими файлами:
- коміти, злиття й rebase працюють з усім деревом - Git знає про всі файли;
- конфлікт у файлі поза sparse-набором - Git тимчасово розміщує файл у робочому каталозі для розв'язання;
git grepіgit log -- pathможуть шукати й поза набором.
Типове застосування:
- монорепозиторій: розробник фронтенду бачить лише
apps/webі спільні пакети; - CI: збирання конкретного сервісу без виписування всього репозиторію - разом з
--depth 1і--filter; - документація в репозиторії з великим кодом.
Обмеження:
- інструменти, яким потрібні файли поза набором (наприклад, Composer з
path-репозиторіями на сусідні пакети, збирачі, що шукають конфіги в корені), ламаються - набір має містити всі залежності; - для невеликого проєкту накладні витрати не виправдані.
Обидва способи зменшують обсяг завантаження, але обрізають різні речі.
Поверхневий клон (--depth 1) обрізає історію: комітів до певної глибини просто немає.
Частковий клон (--filter) завантажує всю історію комітів, але не всі об'єкти - пропущені догружаються з сервера на вимогу:
git clone --filter=blob:none https://github.com/acme/app.git # blobless
git clone --filter=tree:0 https://github.com/acme/app.git # treeless
git clone --filter=blob:limit=1m https://github.com/acme/app.git # без файлів понад 1 МБ
| Поверхневий | Без blob-ів (blob:none) |
Без дерев (tree:0) |
|
|---|---|---|---|
| коміти | лише останні | усі | усі |
| дерева (каталоги) | лише останні | усі | на вимогу |
| вміст файлів | лише останні | лише для checkout, решта на вимогу | на вимогу |
git log |
обрізаний | повний | повний |
git log -p, blame |
обмежені | працюють, догружаючи файли | повільні, багато догрузок |
| для кого | CI, одноразові збирання | розробники | CI, якому потрібна історія комітів |
Чому blobless клон - добрий вибір для розробника великого репозиторію:
- історія повна:
log,merge-base,describe, перемикання гілок працюють як звичайно; - старі версії великих файлів не завантажуються, поки не знадобляться;
git blameчи перегляд старого коміту догружають потрібні об'єкти - помітна, але одноразова затримка.
Чому поверхневий клон - для CI: найменший обсяг і жодних мережевих запитів пізніше. Але все, що потребує історії, не працює.
Treeless клон економить найбільше серед часткових, але операції з історією файлів роблять багато запитів до сервера - для щоденної роботи зазвичай повільно.
Що потрібно:
- підтримка на сервері (GitHub, GitLab, Bitbucket підтримують);
- доступ до сервера при операціях, що потребують відсутніх об'єктів - офлайн частина команд не працюватиме;
- не поєднувати бездумно з
--depth- зазвичай обирають одне.
Разом зі sparse-checkout частковий клон дає найкращий ефект для монорепозиторію: не завантажуються ні старі версії, ні файли з чужих каталогів.
Докладніше в документації: GitHub Blog: partial clone і shallow clone
Кілька Laravel-застосунків використовують спільний код: модуль авторизації, клієнт API, набір Blade-компонентів. Є три основні способи.
1. Submodule - посилання на коміт іншого репозиторію:
git submodule add git@github.com:acme/shared.git packages/shared
- точна версія, окрема історія, окремі права доступу;
- незручно: додаткові кроки при клонуванні й оновленні, detached HEAD, легко забути оновити вказівник.
2. Subtree - код копіюється в репозиторій разом з історією:
git subtree add --prefix=packages/shared git@github.com:acme/shared.git main --squash
git subtree pull --prefix=packages/shared git@github.com:acme/shared.git main --squash
git subtree push --prefix=packages/shared git@github.com:acme/shared.git feature-x
- для інших учасників це просто звичайні файли - клон працює без додаткових кроків;
- зміни можна відправити назад в оригінальний репозиторій;
- команди довгі, легко змішати в одному коміті зміни спільного й основного коду;
- історія основного репозиторію росте.
3. Composer-пакет - стандартний спосіб для PHP:
{
"repositories": [
{ "type": "vcs", "url": "git@github.com:acme/shared.git" }
],
"require": { "acme/shared": "^2.1" }
}
- семантичні версії й діапазони, власні залежності пакета, автозавантаження, сервіс-провайдер Laravel;
- оновлення -
composer update acme/shared, аcomposer.lockфіксує точну версію; - приватні пакети - через Private Packagist, Satis чи
vcs-репозиторій з доступом.
Для локальної розробки пакета разом із застосунком - path-репозиторій:
{ "repositories": [{ "type": "path", "url": "../shared" }] }
Composer створює символьне посилання - зміни в пакеті видно одразу.
Порівняння:
| Submodule | Subtree | Composer | |
|---|---|---|---|
| версіонування | коміт | коміт | семантичні версії |
| залежності пакета | ні | ні | так |
| простота для команди | низька | висока | висока |
| зміни в обидва боки | так | так (subtree push) |
у репозиторії пакета |
Для PHP-коду Composer майже завжди кращий. Submodule і subtree доречні для того, що не є PHP-пакетом: документація, спільні конфіги, ресурси. А якщо спільний код змінюється разом із застосунками постійно, - можливо, вам потрібен монорепозиторій.
Монорепозиторій - кілька проєктів (застосунки, пакети, сервіси) в одному репозиторії. Polyrepo - кожен проєкт в окремому репозиторії.
Переваги монорепозиторію:
- атомарні зміни: змінити API пакета й усіх його споживачів одним комітом і одним pull request. У polyrepo це кілька узгоджених релізів у правильному порядку;
- немає пекла версій: усі проєкти завжди використовують поточну версію спільного коду;
- спільні інструменти: одна конфігурація Pint, PHPStan, CI, однакові правила;
- видимість: легко знайти всі використання функції, зробити рефакторинг по всьому коду;
- простіший онбординг: один клон - увесь контекст.
Проблеми монорепозиторію:
- розмір і продуктивність Git:
status,clone,checkoutповільнішають (лікується частковим клоном, sparse-checkout, fsmonitor); - CI: запускати все на кожну зміну - дорого. Потрібні інструменти, що визначають, які проєкти зачеплені зміною (Nx, Turborepo, Bazel, Pants чи власні скрипти по
git diff); - права доступу: Git не обмежує доступ до каталогів - бачать усі все (CODEOWNERS лише керує рев'ю);
- зв'язність: легко створити залежності, яких не мало б бути, - потрібні правила меж між модулями.
Переваги polyrepo:
- незалежні релізи, власний темп і власні інструменти кожної команди;
- чіткі межі й права доступу;
- маленькі швидкі репозиторії.
Проблеми polyrepo:
- зміни через кілька репозиторіїв - кілька pull request, порядок випусків, тимчасова несумісність;
- розбіжність версій: сервіси застрягають на старих версіях спільних пакетів;
- дублювання конфігурацій CI, лінтерів, шаблонів.
Гібрид, яким користується Laravel: розробка в монорепозиторії laravel/framework, а компоненти автоматично розділяються в окремі репозиторії лише для читання (illuminate/database, illuminate/support), щоб їх можна було встановити окремо.
Як обрати:
- кілька тісно пов'язаних проєктів однієї команди (застосунок, адмінка, спільні пакети) - монорепозиторій дає більше, ніж коштує;
- незалежні продукти різних команд з різним циклом випусків - polyrepo;
- ключове питання: як часто зміна в одному проєкті вимагає зміни в іншому. Часто - монорепозиторій, рідко - окремі репозиторії.
З часом у репозиторії накопичуються незапаковані об'єкти (кожен новий коміт створює окремі файли), недосяжні об'єкти (після rebase, reset, видалення гілок) і багато pack-файлів після кожного fetch. Це сповільнює операції й займає місце.
git gc (garbage collection):
- пакує окремі об'єкти в pack-файли з дельта-стисненням;
- видаляє недосяжні об'єкти, старші за термін (за замовчуванням 2 тижні, і лише ті, на які не посилається reflog - а reflog зберігає записи 90 днів, недосяжні - 30);
- упаковує посилання в
packed-refs, оновлює commit-graph.
git gc # звичайне прибирання
git gc --aggressive # повільне глибоке перепакування - рідко потрібне
git gc --prune=now # видалити недосяжні об'єкти негайно
git gc --auto Git запускає сам після деяких команд (commit, merge, fetch), коли незапакованих об'єктів понад 6700 чи pack-файлів понад 50. Тому вручну запускати gc зазвичай не потрібно.
Проблема автоматичного gc у великих репозиторіях: він запускається посеред роботи й може блокувати на хвилини.
git maintenance - сучасна заміна: обслуговування у фоні за розкладом:
git maintenance start
Реєструє репозиторій і задачі в системному планувальнику (launchd на macOS, systemd чи cron на Linux, Task Scheduler на Windows):
| Задача | Частота | Що робить |
|---|---|---|
prefetch |
щогодини | фоновий fetch у спеціальні ref-и - ваш git fetch потім майже миттєвий |
commit-graph |
щогодини | оновлює граф комітів для швидкого log і злиттів |
loose-objects |
щодня | пакує окремі об'єкти |
incremental-repack |
щодня | поступово об'єднує pack-файли без повного перепакування |
git maintenance run --task=gc # вручну конкретну задачу
git maintenance stop
Коли запускати вручну:
- після видалення великих файлів з історії (
git filter-repo) -git gc --prune=now, щоб звільнити місце; - після масового імпорту чи міграції репозиторію;
- для невеликого проєкту нічого робити не треба - автоматичного
gcдостатньо.
На сервері (GitHub, GitLab) обслуговування виконує хостинг.
Питання рівня Middle з реальних технічних співбесід - 35 питань у 7 темах, розібраних із відповідями. Нижче - теми цього рівня та сусідні рівні, якщо хочете звузити або розширити підготовку.
Готуєтесь до співбесіди не просто так: зараз на сайті 78 відкритих вакансій рівня Middle. Переглянути вакансії