Git Rebase: хто він такий і в чому його сила
Привіт, я Ганна Ліхтман — Lead Software Engineer, фанатка чистого коду, чистої архітектури та, як слідство, чистої історії комітів. Я людина, яка не раз виглядала отак 🫠, коли відкривала Git history у чужому Merge Request.
За роки роботи я бачила багато — від «commit1», «commit2», «final-final-fixed», до «try again pls» і «now it should work for real». І повірте, нічого так не псує настрій, як ціла стрічка незрозумілих змін, особливо коли ти reviewer, а в тебе п’ять хвилин до стендапу.
Мене часто запитують:
«Навіщо той Rebase? У нас і так все працює».
І я щоразу усміхаюсь і думаю:
«Поки ти не натрапив на гілку зі 132 комітами, де п’ять із них — це ‘fixed typo’, ти просто не бачив справжнього болю».
У цій статті ми поговоримо про Git Rebase не як про страшний ритуал для обраних, а як про інструмент, який може врятувати ваш Git history від перетворення на хаос.
Тут ви дізнаєтесь:
- Чи справді існують погані історії в Git — і як вони виглядають.
- Навіщо нам потрібен Rebase, якщо «і так усе працює» (спойлер: не завжди).
- Що таке інтерактивний Rebase — і чому це не страшно.
- Як на практиці привести свої коміти до ладу, щоб рев’ювери не проклинали вас ночами.
Це не буде суха документація. Це буде щира, жива розмова про один із найпотужніших інструментів Git — з реальними прикладами, сценаріями з життя й порадами, які ви зможете використати вже завтра. Готові? Поїхали!
Не існує поганих історій... чи все ж є?
Ні, я зараз не про Git як систему — не про те, як його створив Лінус Торвальдс у пориві розчарування, і не про легендарні апдейти 2005 року.
Я зараз про іншу історію — вашу Git-історію. Ту, що живе всередині кожної гілки вашого репозиторію. Ту, яка іноді лякає більше, ніж продакшн без моніторингу.
У типовому проєкті є кілька гілок:
- main, develop, feature/awesome-new-thing
- і звісно — наші улюблені hotfix/hotfix/fix-final-really-final-2
І все б нічого, але відкриваєш Git history — і там... Git-apocalypse.
Ти дивишся на ці коміти, і в тебе одне питання:
«А хто це писав? Я? Та ні... не може бути. Я ж нормальна людина».
І тут починається класика:
- «Fixed something»
- «Changed stuff»
- «Refactored thing»
- «Updated code again because why not»
І от з цим добром ти йдеш на code review.
😰 Коли приходить рев’ювер...
У GitLab є прекрасна фіча — перегляд комітів по одному. Ідея проста: кожен коміт має бути атомарним — маленьким, логічним і незалежним.
Тобто, можна читати твої зміни поетапно, ніби книгу з коротких розділів. Але що робити, коли в тебе:
- 3 нормальні коміти;
- 5 штук «Fixed after review»;
- 17 «Updated something»;
- 40 штук, які вже й сам не пам’ятаєш?
Reviewer починає перегляд, натискає стрілочку... ще одну... ще одну... І десь на десятому коміті розуміє, що логіки тут не більше, ніж у сюжеті останнього сезону «Гри престолів» (фанати, сорян...).
🧹 Що ж із цим робити?
Тут з’являється перша думка:
«А давайте все засквошимо! Одна галочка — і 112 комітів перетворюються на один. Перемога!»
Так, Squash у GitLab — це непоганий emergency-план. Але, зізнаємось, це не завжди елегантно. Бо в такому випадку зникає не тільки хаос — зникає і цінна історія. Навіть хороша.
Особливо якщо вона була... ну, бодай трохи структурована.
І ось тут варто згадати про випадки, коли нам навпаки потрібні атомарні коміти. Уявімо: ви створюєте login modal form.
Що ви зробите? Правильно — дрібні логічні кроки:
- feat(kit): create button
- feat(kit): create input
- feat(kit): create modal
- feat(login): create login form — де ви зібрали всі попередні елементи.
Це виглядає чисто, передбачувано, і що важливо — гнучко. Бо якщо завтра продакт скаже «Мені ця кнопка не подобається — прибери її», вам не треба розгрібати монолітний commit з тисячами рядків. Ви просто видаляєте окремий atomic commit або робите revert.
Але... Потім на сцену виходить рев’ювер із коментарем:
«А можна border radius модалки зробити ще трішки більш округлим, бо ці кути різали мені душу».
І ви такі:
- fix(kit): update modal border-radius
- fix(kit): fix of modal border
- fix(kit): final fix
- fix(kit): actually final
- fix(kit): one more time i promise
І ось у вас уже 10 комітів, половина з яких — просто патчі, що забруднюють історію.
Ви починаєте шукати варіанти:
- Сквошити все? Можна, але втратите сенс і гнучкість.
- Залишити як є? (спойлер: Reviewer втратить віру в людство)
- Створити ще один commit on top? А далі ще один? І ще?..
А може, варто зробити Rebase?
💡 Ось тут на сцену і виходить справжній головний герой цієї історії — Git Rebase: той, хто допоможе вам прибрати шум, залишивши зміст.
Готові дізнатись, як це працює і чому це не страшно? Погнали.
🔁 Що таке Git Rebase і навіщо він потрібен?
Якщо Git — це історія змін у коді, то Rebase — це сама машина часу. Ви не просто повертаєтесь у минуле. Ви можете переписати його.
Кожен коміт у Git — це нода, а HEAD — це та мітка, де ви зараз перебуваєте. Зазвичай ми працюємо собі на останньому коміті в гілці — і все ок.
Але з Rebase ми можемо:
- перемістити коміти в інше місце в історії;
- «перепригнути» на master чи інший бранч;
- і навіть повністю переписати послідовність комітів, наче це чернетка в Google Docs.
Це не просто мікроманіпуляція над історією — це спосіб зробити її лінійною, чистою і читабельною. Бо, як кажуть: «Чистий код — це добре. А чиста історія комітів — ще краще».
🧭 Навіщо взагалі цей Rebase?
Подивімося на реальну ситуацію.
У вас є master (червоний), і з нього створено гілку first (синій). Ви працюєте над фічею, все чудово. Але master живе своїм життям: інші команди пушать, створюють баги (хе-хе), фіксять баги, додають апдейти.
І ось настає момент істини: потрібно підтягнути master у свій бранч. Що робити?
Варіант 1: Merge
Ми просто робимо git merge master, і...
Git створює додатковий коміт Merge master into first.
Стає більше вузлів. З’являється «гребінка». І якщо ви це робите щотижня, а фіча тягнеться кілька тижнів — гілка перетворюється на спагеті з мітками Merge, Merge, Merge...
Варіант 2: Rebase
Замість того, щоб створювати нові вузли, Rebase бере ваші коміти — і переписує їх так, ніби ви від самого початку почали роботу від найновішого master (! меджик).
🤹♀️ Rebase — це не про «магію», а про контроль
- Ви зберігаєте лінійну історію.
- Відкат змін — легкий і безпечний.
- Reviewer бачить чітку послідовність логічних комітів.
- Git Blame працює краще.
- А історія читається, як технічна новела.
І знаєте що? Навіть SOLID про це говорить.
S — Single Responsibility Principle.
І якщо вже кожен клас має відповідати тільки за свою відповідальність, то нехай і кожен коміт відповідає за конкретну зміну, а не за «ну ми тут все злили в master — дивіться самі».
Наступним кроком ми глянемо, як Rebase стає ще потужнішим, якщо зробити його інтерактивним. Там і squash, і reorder, і rename — там усе в наших руках (му-ха-ха-ха!).
👉 Готові? Пірнаємо в інтерактивний Rebase далі.
🎮 Додаємо інтерактивності
Окей, ми вже знаємо, що Rebase — це круто. Але інтерактивний Rebase — це вже рівень богів, де можна:
- перейменовувати коміти;
- міняти їх місцями;
- об’єднувати, викидати, редагувати;
- і навіть виконувати команди між ними.
Звучить як чаклунство (поза межами Хогвартсу), правда?
Як його запускати?
Дуже просто:
git rebase -i HEAD~N
або
git rebase --interactive HEAD~N
Де N — кількість останніх комітів, з якими ми хочемо попрацювати.
Уявіть, що ви кажете Git:
«Покажи мені останні N кроків — я подивлюсь, що з ними зробити».
Що нам дає цей режим?
Git відкриває перед нами список останніх комітів і пропонує набір команд.
І тепер ми можемо:
- pick — залишити як є;
- reword — змінити назву коміту («refactor login logic» звучить краще, ніж «oops»);
- edit — залізти в коміт, щось підправити й зберегти;
- squash — об’єднати з попереднім комітом (і змінити повідомлення);
- fixup — об’єднати з попереднім, але без зміни опису (Git мовчить, як ніндзя);
- drop — викинути коміт геть («якщо тебе не було, то й не треба»).
Все це допомагає нам навести порядок у власній гілці, або навіть у тій, що походить з master.
А що ще є в інтерактивному режимі?
Git пропонує ще кілька команд для глибшої роботи з історією:
exec — виконає shell-команду після конкретного коміту. Наприклад, запустити тести, скрипт, чи навіть щось божевільне типу say «Commit is done!» (на свій страх і ризик).
break — зупинить Rebase на цьому кроці. Можна використати як контрольну точку: «Отут я хочу зупинитись і подивитись, що відбувається».
label / reset label — маркує певні місця в історії. Особливо корисно, якщо ви розбиваєте великий Rebase на логічні блоки (наприклад, «Коміти для UI», «Коміти для API», тощо).
merge — ну тут все ясно, це Git, не Excel — воно зіллється, якщо попросити ввічливо 😌
Але є одне ЗОЛОТЕ правило:
Перед тим як робити Rebase, зробіть бекап.
Один раз забув — і ти вже не девелопер, а детектив, який намагається зрозуміти, куди зникли твої коміти, і чому git reflog виглядає як список рішень, про які ти шкодуєш.
Тому:
git branch backup-before-rebase
Це ваша «кнопка ПАНІКА».
А ще — не соромтесь git rebase —abort або git reflog — вони існують, щоб рятувати.
І пам’ятайте: всі помиляються, але тільки найсильніші роблять бекап перед тим, як почати магію.
Далі переходимо до практики — покажемо все це в дії: як саме застосувати інтерактивний Rebase, що відбувається при squash, як змінити коміт, і коли це все реально працює краще, ніж здається.
🛠 Рухаємось до практики
Пам’ятаєте нашу задачу з самого початку — створення login модалки?
Тепер уявімо, що до цієї акуратної структури почали додаватись фікси, хотфікси, хотфікси до фіксів, «майже фінальні зміни» — і виглядати це почало отак:
О ні. Вийшла класика: «фічу робили три дні, фіксили — три тижні». А ми ж хочемо зберегти історію чистою, а не перетворити її на архів хаосу.
Тож, настав час зайти в історію з редакторським скальпелем — і використати Git Rebase, щоб все упорядкувати красиво.
Перший крок: визначити діапазон
Куди нам перемістити наш HEAD?
Ми хочемо пройтись по кількох останніх комітах — для цього використовуємо:
git rebase -i HEAD~N
де N — кількість комітів, з якими хочемо працювати.
В ідеалі ми такі: HEAD~16, бо думаємо: «Та в мене там напевно штук 16...»
А Git такий:
fatal: invalid upstream ’HEAD~16′
Окей, чесно — не вгадаємо. Тому викликаємо нашого найкращого помічника:
git log --graph --decorate --pretty=oneline --abbrev-commit
І він покаже нам все — красиво, зрозуміло, з кольоровими вузлами.
Виявляється, у нас лише 4 коміти — а ми вже хотіли мандрувати до Палеоліту 🙃
Запускаємо Rebase
Окей, запускаємо:
git rebase -i HEAD~4
Git відкриє список із наших чотирьох комітів у редакторі:
А тепер пояснимо, що значить pick і edit:
- pick — беремо коміт як є. Нічого не міняємо. Просто приймаємо.
Це як натиснути «Оформити замовлення» без змін до кошика. - edit — зупиняємось на цьому коміті, залізаємо всередину, змінюємо щось у коді, додаємо, видаляємо — і «зшиваємо» назад. Ця опція — як сказати офіціанту: «Я зараз сам на кухню, хочу трохи приправити».
✂️ Наш кейс: редагуємо border radius у модалці
Ми знаходимось на останньому коміті, але хочемо змінити border-radius, який був зроблений ще у create modal.
Міняємо pick на edit (або просто e) навпроти потрібного коміту:
Далі ми натискаємо Ctrl+С і внизу пишеться згадка, що робити, щоби просто вийти з Vim:
Але ми пишемо :wq — це означає, що ми вийдемо та збережемо все, що ми наробили. Бачимо, що зупинилися на create modal:
Тепер Git «зупиняє час» — ми переносимось у минуле, прямо в той коміт. Комітів, які були після — більше не існує (поки що).
Бо це все було в наступних комітах, ми перемістились в історії. І тут навіть Git нам підказує, що ми можемо використати amend після того, як все зробимо й будемо задоволені змінами:
Тож нам треба було змінити border radius. Змінюємо з 6 на 8, і бачимо, що в нас зʼявився коміт, що ми зробили якусь зміну:
Після цього в терміналі:
git add .
git commit --amend
--amend
не створює новий коміт, а додає зміни до поточного.
Так, ніби ви відредагували вже надіслане повідомлення, і ніхто не помітив. Git ще дасть вам можливість оновити опис коміту — або залишити все як є.
Amend бере всі зміни, які ви зробили й додали через git add, і чіпляє їх до того HEAD, до того коміту, де ми зараз знаходимось. При цьому воно не створює новий коміт. Git нас запитає, чи дійсно ми збираємось робити цей коміт (ось такий він турботливий).
Ми підтверджуємо, натискаємо Ctrl+С, :wq і маємо підтвердження наших змін.
Ще раз повторимо те саме, але якщо нас попросять змінити назву кнопки.
Переходимо по інтерактивному ребейзу edit до кнопки:
Нам знову підкажуть, що ми зупинилися на Button:
І в коміті пропаде наша остання модалочка. Ми переходимо в Button і бачимо, що цей компонент у нас більш продуманий, готовий, але якби тут був інший Title — ми б його змінили. В нашому випадку можемо, знову ж таки, змінити border radius:
Знову прописуємо git commit --amend
, підтверджуємо зміни. Готово, ми молодці!
🌀 Друга задача: а змініть це знову...
І тут ви ще не встигли допити каву, як приходить новий запит:
«Слухай, а все ж таки давай border-radius зробимо знову 6px?»
Знайомо? Таких «передумали» в проєктах більше, ніж комітів із назвою final_final_last_fix_3.
Окей, ви відкриваєте Modal, міняєте значення з 8 назад на 6...
І тільки потім усвідомлюєте: «А наш HEAD зараз не там, де треба».
Що пішло не так?
Замість того, щоб зробити rebase і зупинитись на потрібному коміті (наприклад, create modal), ви просто внесли зміни в останній — LoginForm.
І все... Тепер ці правки приклеїлись до не того місця.
Якби це були лише пару пікселів — можна просто натиснути Ctrl+Z, махнути рукою і зробити все по новій.
Але в реальних задачах це може бути:
- багатогодинна правка стилів;
- складна бізнес-логіка;
- або... hotfix в проді (так, і таке буває).
Варіант порятунку #1: git stash
Можна сховати зміни ось так:
git stash
Це як скласти речі в коробку, позначити «не викидати!» і відкласти. Потім:
- робимо rebase на потрібний коміт;
- і дістаємо зміни:
git stash pop
І вуаля — зміни застосовані вже до того коміту, куди й треба.
Варіант порятунку #2: fixup + autosquash
Але ми підемо цікавішим шляхом. Трошки розумнішим. Трошки чарівним.
Спершу — дивимось на хеш коміту create modal, щоб прив’язатись саме до нього:
git log --graph --decorate --pretty=oneline --abbrev-commit
Нехай потрібний коміт має хеш 3b95ef3.
Додаємо fixup-коміт
Ми вже зробили зміну (повернули radius до 6), і тепер:
git add .
git commit --fixup=amend:3b95ef3
🔍 Цей фокус створює новий коміт, який прив’язується до існуючого і каже:
«Я — поправка до коміту 3b95ef3, приклей мене туди, будь ласка».
Звичайний git rebase -i HEAD~5
покаже вам окремий fixup-коміт.
Але ми хочемо, щоби Git сам зробив squash і приклеїв його без зайвих рухів. Тому додаємо прапорець —autosquash:
git rebase -i --autosquash HEAD~5
Тепер fixup акуратно перемістився туди, куди треба — прямо під коміт модалки, і вже з командою fixup, а не pick.
Вам лишається лише зберегти (:wq), і все злилось красиво.
Перевірка
Знову перевіряємо історію:
git log --graph --decorate --pretty=oneline --abbrev-commit
І бачимо: все чисто, красиво, комітів знову 4 — а не 5 з лишком. Mission complete.
💡 Порада: якщо знаєте, що будете часто робити fixup, можете одразу запускати інтерактивний Rebase з —autosquash. Воно просто працює.
Третя задача: наводимо фінальний блиск — drop і reword
Ну що, ми вже:
- редагували коміти через edit;
- допилювали їх через amend;
- навіть влаштували autosquash із fixup.
Тепер час навести остаточний порядок і відкинути зайве, залишивши лише найкраще. А саме — потренуємось із drop і reword.
Сценарій: компонент іконки
Маємо окрему гілку для IconComponent.
Усе було б добре, але поки ми працювали, хтось уже влив цілу колекцію іконок у master.
Тепер наша одна скромна іконка не просто непотрібна — вона може викликати конфлікт при злитті. Тож ми вирішуємо: видалити її коміт, але при цьому залишити створений компонент.
Ми як завжди запускаємо наш інтерактивний Rebase:
git rebase -i HEAD~2
Бачимо, що один із комітів — це додавання іконки у public. Саме його нам і треба прибрати.
Замість pick пишемо:
drop 1feaf78 feat(public): add check icon
або коротше:
d 1feaf78 feat(public): add check icon
Зберігаємось (:wq) — і... все!
Бувай, коміте. Тебе тут ніколи й не було 😌
Перейменовуємо коміт — команда reword
Тепер наш єдиний коміт звучить скромно: feat(kit): create icon component. Але ми, як відповідальні розробники, згадуємо:
«Ой, ми ж забули вказати номер таски!»
Знову запускаємо:
git rebase -i HEAD~1
І змінюємо команду з pick на:
reword ba2f44d feat(kit): create icon component
або просто:
r ba2f44d feat(kit): create icon component
Зберігаємось (:wq), і Git каже:
«Окей, друже, а тепер скажи, як це має називатись?»
Ми додаємо таску в кінець:
feat(kit): create icon component — TASK-195
Знову :wq, Git задоволений. І ми — теж 🙌
Ми навчилися не просто натискати на кнопки, а редагувати, переписувати й чистити Git-історію так, ніби ми сценаристи власного коду.
Тепер, коли хтось скаже «там трохи брудна історія» — ви не просто зрозумієте, що саме брудне, а ще й знатимете, як це виправити красиво.
Висновок: Git Rebase — це не страшно, це мистецтво
Окей, ми з вами пройшли повний шлях.
Від моменту, коли Git-історія виглядає як хаос після дедлайну — до стану, коли кожен коміт виглядає як акуратна цеглинка в стіні архітектури. Ми:
- розібрались, чому «погана історія» — це реально проблема, а не вигадка перфекціоністів;
- навчилися edit-ити, squash-ити, fixup-ити, reword-ити і навіть drop-ати, коли треба;
- врятували себе від незручних комітів типу fix_fix_last_final2;
- і на завершення — перетворили хаос у чітку, логічну розповідь, яку не соромно показати на code review.
А головне — ми перестали боятись Rebase, і навіть почали трошечки... кайфувати від нього ✨
☕ То для чого це все?
- Щоб вас поважали не лише за чистий код, а й за чисту історію.
- Щоб ваші pull request-и читались легко.
- Щоб ваш reviewer не плакав в подушку, гортаючи 113 неясних комітів.
- Щоб, коли хтось скаже: «а можна змінити щось у середині?», — ви відповіли:
«Звісно. Зараз зробимо git rebase -i — і все буде красиво».
🔥 Наостанок:
- Робити Rebase — не страшно. Не робити бекапи перед ним — страшно.
- Git — це інструмент. Але у ваших руках він стає інструментом контролю, а не джерелом стресу.
- І пам’ятайте: чиста історія — як хороший стиль в одязі. Начебто дрібниця, але помічають усі.
Тож вперед — не просто кодуйте, творіть історію, яку приємно читати.
Більше інформації про Git Rebase — у моєму вебінарі.
До нових HEADів, друзі 🫶
І нехай --autosquash
завжди буде з вами.
59 коментарів
Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.