Използвайте Git Rebase, за да почистите историята на коммитовете

  • Git rebase ви позволява да пренаписвате и подреждате историята, за да създадете чиста и линейна последователност от коммити.
  • Интерактивното пребазиране улеснява изтриването, сливането или пренареждането на комити, елиминирайки празни тестове и комити.
  • Пренаписването на историята изисква координиране с екипа и внимателно използване на принудителното изпращане, за да се избегне загубата на работата на други хора.
  • Инструменти като git reflog и git pull --rebase ви помагат да работите с rebase безопасно и обратимо.

git пребазиране

Ако използвате Git от известно време, вероятно сте попадали на история, пълна с безполезни коммити, съобщения „WIP“, бързи тестове или дори празни коммити, създадени само за да задействат hook или pipeline. В този момент поглеждате лога и си мислите: „Никой не може да разбере това“Добрата новина е, че не сте сами: това се случва на всички нас и затова е подкрепата. git пребазиране.

Интересното е, че когато се използва правилно, git rebase ви позволява да "почистите" историята на коммитоветеНаправете го линейно, без шум и много по-лесно за преглед. Можете да изтривате тестови коммити, да обединявате няколко в един, да ги пренареждате, да коригирате съобщенията за коммити и дори да премахвате промени отдалечено чрез принудително изпращане. Нека да разгледаме, с конкретни примери, как да използвате rebase, за да трансформирате историята си от пълен хаос в нещо организирано и четливо.

Какво точно е Git Rebase и защо влияе на историята?

Когато говорим за изпреварване, буквално говорим за промяна на началната точка на клонGit взема коммитите от вашия клон и ги „възпроизвежда“ един по един на друга база (обикновено клонът основен или актуализиран отдалечен клон). Все едно сте се върнали назад във времето и сте започнали работата си от различен commit, но сте запазили промените.

Представете си, че вашият проект има тази история в главния клон: А – Б – ВСъздавате клон с функционалност и след това правите два commita: D - EМеждувременно, в main, някой добавя нов commit FБез пребазиране, вашите клонове биха изглеждали така:

A---B---C---F (main)
A---B---C---D---E (feature)

Ако направите класическо сливане на вашия feature клон в main, това, което получавате, е допълнителен merge commit, който често генерира заплетена история с няколко пресичащи се линии. С rebase, от друга страна, Git взема вашите коммити D и E и ги прилага отново към F:

A---B---C---F---D'---E' (feature reescrita)

Тези Ре и Ми Това не са точно същите комити като D и E: те са „копия“ с нови идентификатори (хешове). Ето защо казваме rebase. пренаписва историятаРезултатът е по-линеен лог, без междинни сливания, които само добавят шум.

git пребазиране

Защо трябва да имате чиста история на комитите

Организираният запис не е само въпрос на естетика. Чистият дневник значително улеснява ежедневната работа.Можете да видите с един поглед какво е направено, кога и защо, без да се налага да прескачате през празни сливания или съобщения за „бързи поправки“ без контекст.

Когато клоновете се сливат чрез постоянно сливане от main, се получават куп коммити като „Сливане на клон 'main' с feature-x“, които Те не предоставят реална информация за промените в кодаТова усложнява задачи като:

  • Намерете коммита, в който е въведена грешка, използвайки инструменти за сравняване на файловезащото историята е пълна с неподходящи сливания.
  • Преглед на заявки за изтеглянезащото трябва да прескачате между излишни коммити, за да видите какво всъщност се е променило.
  • Разбиране на еволюцията на дадена функцияособено ако е разработен в рамките на много малки тестови изпълнения.

Git rebase е идеалният инструмент за „полиране“ на целия този шум, преди да споделите клона с останалата част от екипа. Все едно да пуснеш историята си в пералнята.Запазвате важните промени, добре групирани и с ясни послания.

Ключови разлики между git merge и git rebase

За да разберете напълно какво правите, когато използвате rebase, си струва да сравните поведението му с това на git mergeИ двете служат за интегриране на промените, но по различни начини и с важни последици за протокола.

Con се сливатGit създава нов merge commit, който има два родителски клона: върха на вашия клон и върха на клона, с който се сливате. Получената история запазва точно оригиналната последователност от коммитиНо може да се окаже пълно с паралелни разклонения и сливания.

Con пребазаВместо да създава merge commit, Git взема вашите commit-и и ги прилага един по един към новата база данни. Това генерира нови коммити с нови хешоведори ако промените в кода са едни и същи.

На практика:

  • Обединяване Той запазва историята точно такава, каквато се е случила, с всичките ѝ кръстосани връзки и сливания.
  • Ребаза Това генерира линейна и чиста история, сякаш всичко се е случило по права линия.

Изборът не е „едното е по-добро от другото“, а За коя ситуация е по-подходящ всеки от тях?За да се комбинират вече споделени и затворени клонове, сливането обикновено е по-безопасно. За да се поддържат локалните работещи клонове организирани или преди отваряне на заявка за изтегляне, rebase е много полезен.

сливане срещу пребазиране

Случаи, в които rebase блести: актуализиране и полиране на клонове

Има два сценария, в които повечето разработчици използват rebase ежедневно: поддържайте работния си клон актуален с main y почистете коммитовете, преди да ги споделитеНека ги разгледаме по-подробно.

Актуализирайте вашия feature branch с последните промени от main

Работите върху нова функция във вашия клон, но междувременно колегите ви все още правят коммити в основенАко искате да интегрирате промените си, една от възможностите е да:

git checkout tu-rama-feature
git merge main

Това работи, но генерира merge commit всеки път, когато синхронизирате, което в крайна сметка причинява проблеми. история, пълна с повтарящи се сливанияАко обаче го направите:

git checkout tu-rama-feature
git rebase main

Git ще премести вашите commit-и върху последното състояние на main, сякаш сте стартирали клона след тези промени. Ще получите линейна история, без междинни сливания (merge commits)и прегледът ще бъде по-ясен.

Почистете коммитите си, преди да отворите заявка за изтегляне (pull request).

Много често се случва, по време на разработката на дадена функция, да се сблъскате с коммити като „корекция на печатни грешки“, „тестване на конвейера“, „още промени в входа“ и т.н. Това е добре, докато работите, но Това не е видът история, която искате да покажете. когато отворите PR.

Интерактивното презареждане ви позволява реорганизирайте и групирайте тези комити в нещо по-логично. Например, вместо това:

- WIP: añadir login
- Más cambios login
- Corregir tests login
- Arreglar typo variable

Можете да получите един-единствен, добре описан commit:

- Añadir funcionalidad completa de login de usuario con tests

Това „полиране“ на историята прави разбирането много по-лесно за рецензентите. какво допринася всеки коммит и опростява отстраняването на неизправности в бъдеще.

Интерактивно пребазиране за пренаписване на историята

Основната версия на rebase просто премества вашите коммити към друга база. Но перлата в короната е интерактивна свръхбаза, което отваря редактор със списък с последните коммити, за да можете да решите какво да правите с всеки един от тях.

Типичният начин да започнете това е като укажете колко коммита назад искате да „докоснете“ от текущия си HEAD. Например:

git rebase -i HEAD~6

Тази команда казва на Git: „вземете последните 6 коммита от този клон и подгответе интерактивно ребазиране за мен.“ Git ще отвори вашия редактор по подразбиране (Vim, Nano, VS Code и т.н.) с нещо подобно:

pick 7ed9c6e update version
pick ecb7ef3 empty commit 1
pick a323615 empty commit 2
pick 2c3d41d empty commit 3
pick d53c00f empty commit 4
pick 22dcc79 empty commit 5

# Пребазиране на 549dd76..22dcc79 върху 549dd76 (6 команди)
#
# Команди:
#p, мотика = използвай commit
# r, преформулиране = Използвайте commit, но редактирайте съобщението
# e, редактиране = Използвайте commit и stop, за да го промените
# s, тиква = сливане в предишния commit
# f, поправка = като squash, но отхвърляне на съобщението
# x, изпълнител = изпълнение на командата на shell
# b, break = спиране на пребазирането тук
# d, капка = изтриване на commit
#…

Обърнете внимание на една важна подробност: Редът в този файл е обратен на този, който виждате в git лога.Във файла първият посочен commit (7ed9c6e) е най-старият от шестте, а последният (22dcc79) е най-новият. Това е ключово за избягване на объркване, когато изтривате или пренареждате commit-и.

Премахване на тестови или празни комити от локалната история

Да предположим, че за да тествате Git hook, сте правили празни commit-и с опцията –allow-empty, Например:

git commit -m "commit with no changes" --allow-empty

Тази опция ви позволява да създадете commit, дори когато няма промени във файловете, което е полезно за тестване, но Това претрупва историята с коммити, които нямат реална стойност.Представете си, че вашият лог изглежда така:

git log --pretty=oneline --abbrev-commit

И получавате:

22dcc79 (HEAD -> main, origin/main, origin/HEAD) empty commit 5
d53c00f empty commit 4
2c3d41d empty commit 3
a323615 empty commit 2
ecb7ef3 empty commit 1
7ed9c6e update version

В този сценарий искате да запазите само полезния commit „update version“ и да премахнете всички останали. празен коммит които бяха само тестове. За да направите това, изпълнявате интерактивното пребазиране на последните 6 коммита:

git rebase -i HEAD~6

Редакторът се отваря с 6-те коммита. Целта ви е да премахнете редовете, съответстващи на празните коммити. Тоест, оставяте само нещо подобно:

pick 7ed9c6e update version

# Пребазиране на 549dd76..22dcc79 върху 549dd76 (6 команди)
# … останалите коментари …

Както е посочено в помощната секция на самия файл, Ако изтриете ред, този коммит ще изчезне от историята. В новата, пренаписана версия, при запазване и затваряне на редактора, Git ще възпроизведе само коммита „актуализирана версия“ и ще отхвърли останалите.

Качване на нова история в Origin: чрез принудително изпращане

Досега всички промени при пребазиране са направени във вашето локално копие на хранилището. Ако искате това почистване да се отрази и в отдалеченото хранилище (например в произход/основен), ще трябва да презапишете отдалечената история с новата.

Това се прави с принудително натисканеЧесто срещан начин да се обозначи това е чрез използване на знака "+" пред името на клона при публикуване:

git push origin +main

Тозият знак плюс казва на Git, че Игнорирайте отклонението в историята и презапишете отдалечения клон с вашата пренаписана версия. Ако просто сте се опитали да направите:

git push origin main

Git ще ви предупреди, че вашата локална история не е директен наследник на отдалечената (защото сте пренаписали commits с rebase) и ще отхвърли push-ването, за да избегне загуба на данни.

Важно е да се разбере, че след този принудителен тласък, Изтритите коммити вече няма да бъдат достъпни от origin/mainТе може все още да са в reflogs или локални копия от други колеги, но от практическа гледна точка вие сте изчистили отдалечената история.

Сериозно предупреждение: пренаписването на споделени клонове не е игра

Пренаписването на историята звучи чудесно за организиране на всичко, но има ключово последствие: когато създавате нови коммити с нови хешове, Отблъскваш всеки, който вече е базирал работата си на старите коммити.Ето защо съществува известното златно правило:

Не пребазирайте клонове, които вече са споделени и активно използвани от други.

На практика това означава, че е безопасно да се използва rebase върху:

  • Локални клонове на функции които все още не сте публикували или които само вие използвате.
  • Последни ангажименти тези, от които знаеш, че никой друг не зависи още.

И е доста лоша идея да го правим относно:

  • основен, разработващ или който и да е „официален“ клон от хранилището, което служи като основа за няколко души.
  • Споделени работни клонове където има повече от един разработчик, който прави коммити.

Ако пренапишете историята на споделен клон и след това извършите принудително изпращане, другите членове на екипа ще открият това Клоновете му сочат към коммити, които вече не съществуват в отдалечената среда.Те ще трябва да извършат по-сложни операции (като например пребазиране на новата история или нулиране), за да поправят бъркотията.

Силово натискане: по-добре с предпазен колан

В много случаи, след пребазиране, ще трябва да наложите push. Класическият вариант е:

git push --force origin tu-rama

Този вариант обаче презаписва всичко на дистанционното управление без да пита. За да се избегне случайно презаписване на работата на други хора, Git предлага много по-добра опция: –сила-с-лизинг.

Когато го направите:

git push --force-with-lease origin tu-rama

Git първо проверява това Дистанционното е все още в състоянието, в което си го мислеше. Кога сте направили последното си изтегляне или получаване (pull или fetch). Ако засече, че някой е публикувал нови коммити в този клон оттогава, публикуването се отхвърля и не се принуждава, като по този начин се предотвратява презаписването на промените на други хора.

Идеята е да се комбинират rebase и force push, винаги с хладнокръвие: само в клонове, които контролирате и използване, когато е възможно, на –force-with-lease като допълнителен слой сигурност.

Какво да направите, когато пребазирането се обърка: reflog на помощ

Случвало се е на всички нас: стартирате интерактивно пребазиране, случайно изтриете или промените нещо, разрешите конфликт неправилно и изведнъж Изглежда си загубил част от работата сиПреди да се паникьосвате, не забравяйте, че Git има коз в ръкава си: git рефлог.

Reflog е локален запис на всички движения в HEAD клона: нови коммити, нулирания, ребазирания, сливания… Благодарение на него можете да локализирате къде е бил вашият клон, преди да го объркате, и да се върнете към тази точка с хардуерно нулиране.

Типичният поток би бил:

git reflog

Там ще видите списък със записи с нещо подобно:

abc1234 HEAD@{0}: rebase terminado
def5678 HEAD@{1}: checkout: moving from main to main
...

Идентифицирате коммита, в който сте били преди да започнете ребазирането (например def5678) и му се връщаш с:

git reset --hard def5678

По този начин, Вие напълно отменяте наслагването и се връщате към предишното състояние. Можете също така да прекратите текущо презареждане (ако сте по средата на процеса и не сте го завършили) просто с:

git rebase --abort

Тази команда оставя вашия клон такъв, какъвто е бил точно преди стартиране на текущото пребазиране, идеално за случаите, когато се появят конфликти, които не искате, или виждате, че не е подходящ момент да продължите.

Git pull срещу git pull –rebase: малки разлики, голямо влияние

Друг момент, който често поражда съмнения, е разликата между git pull нормално и git издърпване --rebaseИ двете актуализират локалния ви клон от отдалеченото устройство, но го правят по различни начини.

Когато бягате:

git pull

Git извършва две стъпки: git извличане да се въведат промените от дистанционното и след това git merge да ги комбинирате с текущия си клон. Това може да създаде допълнителен merge commit, ако имате локални commit-ове над отдалечения клон, което отново история с ненужни сливания.

Ако обаче изпълните:

git pull --rebase

Git извършва извличането и след това Репликирайте локалните си коммити върху актуализираната версия на отдалеченото устройство.Резултатът е подобен на този, ако бяхте го направили git fetch последвано от git rebase origin/mainи поддържа по-чиста, по-линейна история.

Ето защо много екипи конфигурират Git да използва rebase по подразбиране, когато правят pull-ове. Това е лесен начин да избягвайте автоматични сливания (merge commits) които не допринасят много.

Изтриване на файлове или изображения от историята: обща идея

Понякога проблемът не е грозен коммит, а файл, който никога не е трябвало да достигне до хранилището: грешно изображение, неправилни идентификационни данни, огромни файлове и т.н. Изкушението е да отидете в GitHub и да потърсите иконата на кошчето, но ако тя изглежда деактивирана и показва съобщения като „трябва да сте на клон“, това е така, защото GitHub не ви позволява да изтривате историята по този начин..

Правилният начин обикновено включва използване на пренаписване на историята от вашата локална машина (с интерактивно пребазиране или инструменти като git филтър-репо) и след това да извършите принудително изпращане. В GitHub Desktop можете също да управлявате клонове и коммити, но операцията на напълно премахнете файл от всички комити Това включва пренаписване на историята по подобен начин на това, което виждаме тук.

Накратко, ако качите грешно изображение или чувствителен файл и искате той да „изчезне“ от историята, простото му изтриване в последния commit не е достатъчно: Коммитите, където е въведено, трябва да бъдат пренаписани. и след това насилете натискането. Това е деликатна операция и трябва да се извърши спокойно и в координация с вашия екип.

Практичен поток за практикуване на изпреварване без счупване на нещо

Най-добрият начин да се усъвършенствате в изпреварването е да практикувате в контролирана среда, без страх да не попречите на работата на другите. Един прост подход би бил:

  1. Създайте нов клон от main с git checkout -b my-tests-branch.
  2. Направете няколко малки промени (това могат да бъдат тривиални промени или тестови файлове).
  3. Симулирайте основното развитие, като правите нови коммити там (или помолите някой друг да го направи).
  4. Върнете се в тестовия си клон и го изпълнете git rebase main за да видите как вашите коммити са препозиционирани.
  5. Опитайте a git rebase -i HEAD~3 за да комбинирате, преименувате или изтриете някой от последните коммити.

С това упражнение ще видите как Променете историята преди и след товаКак Git реагира на конфликти и как можете да се върнете към предишни състояния, използвайки reflog, ако е необходимо. Колкото повече практика имате локално, толкова по-уверени ще бъдете, когато прилагате тези техники към реални клонове на проекти.

Овладяването на Git rebase ви позволява да трансформирате хаотична история, пълна с празни commit-и, ненужни сливания и безсмислени съобщения, в ясна и четлива поредица от значителни промени. Чрез използване на интерактивно rebase, изтриване на тестови commit-и, групиране на разпръсната работа, линейно актуализиране на клонове и разчитане на инструменти като reflog и forced push-ове с `--force-with-lease`, можете да поддържате хранилището си в много по-здравословно и по-професионално състояние, стига да не забравяте да прилагате тези техники само към клонове под ваш контрол и да уведомявате екипа, преди да пренапишете споделената история.

История на версиите в Диск: как да я използвате и най-добри практики
Свързана статия:
История на версиите в Диск: разширена употреба и най-добри практики