Начало
» Основни познания
»
Как да поправите „Git Push Rejected: Non-FastForward“ без загуба на промени
Как да поправите „Git Push Rejected: Non-FastForward“ без загуба на промени
Отхвърлянето без превъртане напред е защита на историята от Git, а не изтриване на локалната ви работа. Обикновено това означава, че отдалеченият клон се е преместил напред след последната синхронизация на локалния ви клон, така че актуализирането на отдалечения с текущия ви клон би отхвърлило вече съществуващите там коммитове. Настоящата документация на GitHub описва същата ситуация: първо извличане на промените от горния клон, интегриране на локално и след това публикуване отново.
Най-безопасният общ работен процес е: защитете всяка некомитирана работа, извлечете отдалечения клон без да променяте текущия си клон, проверете как са се разминали историите, изберете сливане или пребазиране, разрешени конфликти, ако е необходимо, и публикувайте отново. Не започвайте с git push --forceили git reset --hardсамо за да накарате грешката да изчезне.
Какво всъщност означава „без бързо превъртане напред“?
Да предположим, че вашият локален mainклон съдържа commit L, докато отдалеченият origin/mainе преминал към различен commit R. Ако нито един от commits не е предшественик на другия, историите са се разминали. Нормалното push действие не може просто да премести указателя на отдалечения клон към , Lбез да го накара Rда изчезне от видимата история на този клон.
Превъртането напред е различно: новият съвет за клон е наследник на стария, така че преместването на клона напред запазва всичко, което вече е достъпно от него. Официалното git pushръководство дефинира това правило за предци и предупреждава, че --forceто деактивира нормалната защита и може да доведе до загуба на отдалечени коммити.
Отхвърляне без превъртане напред означава, че Git отказва да замени отдалечената история, която вашият локален клон все още не съдържа.
Преди да предприемете каквото и да е: безопасно ли е документирана вашата локална работа?
Изпълнете:
git status
Ако работното дърво е чисто, текущите ви промени вече са представени чрез коммити и можете да създадете допълнителна препратка за безопасност, преди да интегрирате отдалеченото:
git branch backup-before-sync
Тозият клон сочи към текущия commit, така че имате лесно име за състоянието преди интеграция.
Ако сте модифицирали или не сте проследили файлове, които не са commit-вани, изберете един от тези подходи, преди да изтеглите или пребазирате:
Коммитирайте ги, ако работата е логически готова за commit.
Приберете ги, ако работата е недовършена git stash push -u -m "before non-fast-forward fix":.
Опцията -uвключва непроследени файлове. Официалната документация на git-stash обяснява, че stash записва работната директория и състоянието на индекса и възстановява чисто работно дърво. Игнорираните файлове не се включват от -u; -aби включвал и игнорирани файлове, но това рядко е необходимо за този проблем.
Важно: ако git statusпоказва файлове, които не можете да си позволите да загубите, не изпълнявайте git reset --hard. Тази команда може да отхвърли некомитираните промени в работното дърво.
Стъпка 1: Трябва ли да използвате git pullведнага?
Можете, но „първо извличане“ е по-лесно за разсъждение . Документите на GitHub git fetchизтеглят отдалечена работа и актуализират клоновете за отдалечено проследяване, без да сливат тези промени в текущия ви клон. Това го прави полезна диагностична стъпка, защото можете да проверите ситуацията, преди да промените локалната история.
След извличането, клонът remote-tracking origin/mainпредставлява отдалеченото състояние, което току-що извлякохте. Командата log ви позволява да видите комити, достъпни само от вашата локална страна и само от отдалечената страна.
Ако вашият локален клон няма уникални коммити и просто изостава, често можете да го превъртите напред:
git merge --ff-only origin/main
Тогава няма какво да се съгласува; вашият клон просто се движи напред.
Стъпка 2: Сливане или пребазиране – кое да изберете?
И двата могат да запазят промените ви. Разликата е във формата на получената история.
Ситуация
Обикновено избират
Защо
Споделен клон, например mainкъдето вашите локални коммити може вече да са известни на други
Сливане
Запазва съществуващите идентичности на комитите и не презаписва локалните ви комити.
Вашите комити са локални/частни и искате линейна история
Пребазиране
Възпроизвежда локалните ви коммити върху актуализирания отдалечен клон.
Не сте сигурни и искате най-малко пренаписване на историята
Сливане
По-лесно е за обяснение и по-безопасно е за споделената история.
Сливане на маршрут
git fetch origin
git merge origin/main
Ако няма конфликти, Git завършва интеграцията. Ако клоновете наистина се разминават, резултатът може да включва merge commit.
Пребазиране на маршрута
git fetch origin
git rebase origin/main
Rebase взема коммитите, които са уникални за текущия ви клон, и ги прилага отново върху origin/main. Документацията за rebase на Git го описва като трансплантиране на серия от коммити върху различна начална точка. Тъй като тези коммити получават нови идентификатори на коммити, rebase е най-добре да се използва за локална работа, която все още не е била споделена като публична история.
Ако предпочитате пряк път, git pull --rebase origin mainизвършва извличане, последвано от пребазиране, докато git pull --no-rebase origin mainизрично избира поведение при сливане. За ситуация на възстановяване, командите separate fetchи merge/ rebaseчесто са по-ясни, защото можете първо да проверите отдалеченото състояние.
След като отдалечените промени бъдат извлечени, интегрирайте ги целенасочено – чрез сливане или пребазиране – вместо да ги презаписвате.
Стъпка 3: Какво трябва да направите, ако Git съобщи за конфликт?
Конфликтът не означава, че промените ви са изчезнали. Това означава, че Git не може автоматично да реши как да комбинира промените в едно и също съдържание.
Първо проверете състоянието:
git status
Отворете всеки конфликтиращ файл, решете какво трябва да бъде крайното съдържание, премахнете маркерите за конфликт и след това поставете разрешения файл на етап:
git add path/to/file
Ако сте избрали сливане, завършете сливането, след като всички конфликти бъдат индексирани:
git commit
Ако сте избрали rebase, продължете да възпроизвеждате вашите commits:
git rebase --continue
Ако пребазирането върви зле и искате да върнете клона в състоянието му преди пребазирането, използвайте:
git rebase --abort
Настоящото ръководство за пребазиране на Git документира специално --continueи --abortза тази цел. Не използвайте git rebase --skip, за да премахнете конфликт, освен ако действително не възнамерявате да пропуснете възпроизвеждането на commit-а.
Първо разрешете съдържанието, поставете коригирания файл на етап и след това завършете сливането или продължете с пребазирането.
Стъпка 4: Кога е безопасно да се напъне отново?
Преди да изпратите, проверете работещото дърво и скорошната история:
git status
git log --oneline --graph --decorate -n 12
След това натиснете нормално:
git push origin main
Ако сте слили отдалечения клон или сте пребазирали само коммити, които никога преди не са били публикувани, нормалното публикуване обикновено би трябвало да е правилната операция, защото новият отдалечен съвет ще бъде предшественик на вашия локален съвет.
След като вашият клон съдържа както отдалечената работа, така и планираните локални промени, нормално натискане може безопасно да придвижи отдалечената работа.
Кога трябва да използвате --force-with-lease?
Използвайте го само когато умишлено сте пренаписали история, която вече съществува на отдалечената сървърна платформа — например, сте пребазирали клон на функция, който преди това сте публикували и сега трябва да замените старата последователност от коммити на този клон.
git push --force-with-lease origin feature-branch
Официалната git pushдокументация обяснява защо това е по-безопасно от обикновеното --force: lease проверява дали отдалечената препратка все още има очакваната стойност. Ако друг човек е публикувал нова работа след състоянието, на което сте базирали пренаписването си, принудителната актуализация се отхвърля, вместо сляпо да се презаписват неговите коммити.
Тази защита не е причина за рутинно използване на принудително изпращане. Избягвайте принудителното изпращане на споделени клонове, освен mainако вашият екип изрично не позволява пренаписване на историята. Защитата на клоновете от страна на сървъра може също да отхвърли принудителното изпращане, независимо от вашата локална команда.
Не замествайте това:
git push --force origin main
Plain --forceдеактивира нормалната проверка за безопасност без превъртане напред и може да презапише отдалечени коммити. Собствената документация на Git предупреждава, че това може да доведе до загуба на коммити от отдалеченото хранилище.
Ами ако вече сте изпълнили грешна команда?
Git често все още има достатъчно локална история, за да възстанови предишна позиция на клон. Изпълнете:
git reflog
Reflogs записват последните актуализации на локалните референции, включително предишни стойности на HEAD. Ако намерите commit-а, който е представлявал вашия клон преди погрешното reset или rebase, създайте rescue клон, вместо веднага да го преместите mainотново:
git branch rescue-work <commit-id>
Сега проверете rescue-workи възстановете необходимите ви коммити. Официалната документация на git-reflog описва reflog-овете като записи за това къде са сочили съвети за клонове и други препратки преди това.
Защо git pullпонякога се създаваше merge commit?
git pullпърво извлича, след което интегрира избрания upstream клон. В зависимост от опциите и конфигурацията на хранилището, тази интеграция може да бъде сливане или пребазиране. Ако искате предвидимо поведение при отстраняване на отказ, който не е свързан с превъртане напред, посочете изрично намерението си:
# Preserve branch history with a merge
git pull --no-rebase origin main
# Replay private local commits on top
git pull --rebase origin main
За най-голяма видимост, използвайте git fetch originпърво и бягайте git merge origin/mainили git rebase origin/mainпоотделно.
Ами ако отдалеченият коммит е нежелан?
Не приемайте, че „нежелано“ означава, че е безопасно да се изтрие. Първо го проверете:
Ако коммитът принадлежи на друг разработчик, бот, актуализатор на зависимости или е направена промяна в уеб интерфейса, интегрирайте го или го върнете през нормалната история. Ако вашият екип умишлено се е съгласил да замени отдалечената история, тогава може да е подходящо защитено принудително прехвърляне – но това е решение, свързано с историята на хранилището, а не стандартното решение за грешка, която не е свързана с превъртане напред.
Контролен списък за безопасни решения
Некоммитирани файлове? Коммитирайте ги или ги съхранете преди интегрирането.
Нуждаете се от точка за безопасност? Създайте резервен клон в текущия commit.
Дистанционното е сменено? Стартирайте, git fetch originпреди да решите какво да правите.
Споделена история? Предпочитате сливане (merge), ако искате да избегнете пренаписване на комити.
Частни локални комити? Rebase може да поддържа историята линейна.
Конфликт? Разрешете файла, поставете го в индекс, след което завършете сливането или продължете с пребазирането.
Вече публикувана история, умишлено пребазирана? Помислете за --force-with-lease, а не за просто --force.
Долен ред
Съобщението „non-fastforward“ е предпазна бариера, която ви казва, че отдалеченият клон съдържа история, която предложеното от вас превъртане не запазва. Решението не е да се преодолее тази бариера. Защитете локалната си работа, извлечете отдалечения клон, проверете дивергенцията, интегрирайте я със сливане или пребазиране, решете конфликтите умишлено и превъртете отново.
Ако си спомняте едно правило, направете го следното: изтеглете и разберете, преди да принудите . Това запазва както промените ви, така и работата, която вече е направена на отдалеченото устройство – и превръща плашещото отхвърляне на push в рутинен проблем със синхронизацията на Git.