Git push отклонен после перебазировки ветки

920

Хорошо, я думал, что это был простой сценарий мерзавца, что я пропускаю?

У меня есть masterфилиал и featureфилиал. Я работаю над masterнекоторыми, над некоторыми feature, а потом еще над некоторыми master. Я получаю что-то вроде этого (лексикографический порядок подразумевает порядок коммитов):

A--B--C------F--G  (master)
       \    
        D--E  (feature)

У меня нет проблем с обновлением git push origin masterпульта master, а также с git push origin feature(когда он включен feature) для сохранения удаленной резервной копии моей featureработы. До сих пор у нас все хорошо.

Но теперь я хочу перебазировать featureповерх F--Gкоммитов на мастере, поэтому я git checkout featureи git rebase master. Все еще хорош. Теперь у нас есть:

A--B--C------F--G  (master)
                 \
                  D'--E'  (feature)

Проблема: в момент, когда я хочу сделать резервную копию новой перебазированной ветки featureсgit push origin feature , толчок отклоняется, поскольку дерево изменилось из-за перебазировки. Это можно решить только с помощью git push --force origin feature.

Я ненавижу использовать, --forceне будучи уверенным, что мне это нужно. Так мне это нужно? Есть ли перебазирования обязательно означает , что следующий pushдолжен быть --forceFUL?

Эта ветвь функций не используется другими разработчиками, поэтому де-факто у меня нет проблем с принудительным нажатием, я не собираюсь терять какие-либо данные, вопрос более концептуальный.

Ювал Адам
источник

Ответы:

682

Проблема заключается в том, что git pushпредполагается, что удаленная ветвь может быть быстро перенаправлена ​​в вашу локальную ветвь, то есть все различия между локальной и удаленной ветвями заключаются в том, что в конце есть несколько новых коммитов:

Z--X--R         <- origin/some-branch (can be fast-forwarded to Y commit)
       \        
        T--Y    <- some-branch

При выполнении git rebaseкоммитов D и E применяются к новой базе, и создаются новые коммиты. Это означает, что после перебазирования у вас есть что-то вроде этого:

A--B--C------F--G--D'--E'   <- feature-branch
       \  
        D--E                <- origin/feature-branch

В этой ситуации удаленная ветвь не может быть быстро переадресована на локальную. Хотя теоретически локальная ветвь может быть объединена с удаленной (очевидно, в этом случае она вам не нужна), но, поскольку она git pushвыполняет только быстрое слияние, она выдает и выдает ошибку.

И то, что можно сделать --force, это просто игнорировать состояние удаленной ветви и установить его в коммит, который вы вводите в него. Так что git push --force origin feature-branchпросто переопределяет origin/feature-branchс местным feature-branch.

На мой взгляд, перебазирование ветвей функций masterи принудительное возвращение их в удаленный репозиторий - это нормально, если вы единственный, кто работает в этой ветке.

KL-7
источник
68
Честно говоря, вытащить и объединить оригинальную версию ветки функций в перебазированную версию как бы устраняет саму идею перебазирования.
KL-7
24
Возможно, я вас не правильно понял, но если вы вытяните ветвь функции, перенесете ее на новую ветку master, вы не сможете принудительно вернуть ее назад, потому что удаленная версия ветви функции не может быть быстро перенаправлена ​​на новую (перебазированная) версия ветки функций. Это именно то, что ОП описал в своем вопросе. Если после перебазирования, но перед нажатием вы это сделаете git pull feature-branch, это извлечение создаст новый коммит слияния (путем слияния удаленных и локальных версий ветви компонента). Так что либо вы получаете ненужное слияние после ребазинга, либо нажимаете --force.
KL-7
6
Ах, я думаю, что понял. Вы описываете тот же подход, что и в ответе Марка Лонгаира. Но он генерирует коммит слияния. В некоторых случаях это может быть полезно, но я использую rebase в основном в своих собственных ветвях функций (следовательно, push --forceэто не проблема), чтобы сохранять историю коммитов линейной без каких-либо коммитов слияния вообще.
KL-7
11
Проблема с «принудительным нажатием» заключается в том, что вы действительно можете «потерять вещи» (предыдущие коммиты), то, что обычно НИКОГДА не должно быть возможным в любой системе контроля версий. По этой причине по крайней мере одна ветка «master-ish» должна иметь настройки, чтобы не принимать форс-толчки , чтобы ограничить потенциальный ущерб. (Назовите любое из следующего: сердитых / уволенных сотрудников, собственный идиотизм, усталость и переутомление «решений» ...).
Фрэнк Нок
13
--force-with-leaseкак предложил @hardev отличный вариант
augustorsouza
466

Вместо использования -f или --force разработчики должны использовать

--force-with-lease

Почему? Потому что он проверяет удаленную ветку на наличие изменений, что является хорошей идеей. Давайте представим, что Джеймс и Лиза работают над одной и той же веткой функций, а Лиза выдвинула коммит. Джеймс теперь перебазирует свою местную ветку и отклоняется при попытке подтолкнуть. Конечно, Джеймс думает, что это связано с перебазированием и использует --force, и переписал бы все изменения Лизы. Если бы Джеймс использовал --force-with-lease, он бы получил предупреждение о том, что кто-то другой совершил коммиты. Я не понимаю, почему кто-то будет использовать --force вместо --force-with-lease при нажатии после ребазинга.

Hardev
источник
33
Отличное объяснение. git push --force-with-leaseспас мне кучу.
ckib16
5
Это полезный комментарий, но на самом деле это не ответ на вопрос.
Даллин
4
Это ответ, перебазировка на освоение / разработку создает проблему, именно поэтому существует --force-with-lease.
Тамир Даниэли
3
Это должен быть принятый ответ. Решает в точности описанную проблему - принудительное толкание без форсирования, если кто-то другой совершил за это время.
Лузиан,
3
Я думаю, что и принятый ответ, и этот ответ на вопрос. Принятый ответ объясняет, почему вы должны заставить. И это объясняет, почему --force-with-leaseрешается проблема использования--force
Jeff Appareti
48

Я бы использовал вместо "checkout -b", и это легче понять.

git checkout myFeature
git rebase master
git push origin --delete myFeature
git push origin myFeature

когда вы удаляете, вы не можете нажать на выходящую ветку с другим идентификатором SHA. Я удаляю только удаленную ветку в этом случае.

Эдди Эрнандес
источник
6
Это прекрасно работает, особенно если у вашей команды есть ловушка git, которая отклоняет все команды git push --force.
Райан Темс
1
спасибо за то что сработало хорошо. Вот подробности, которые я прочитал, чтобы лучше понять. Это очень полезно, когда вы не хотите или не можете сделать принудительный толчок. Удаление удаленных филиалов и перебазирование
RajKon
5
Это имеет тот же результат push --force, что и способ предотвращения мерзавцев --force. Таким образом, я не думаю, что это когда-либо хорошая идея - либо репозиторий разрешает push --force, либо по уважительной причине отключает его. Ответ Наби более уместен, если --forceон отключен в удаленном репо, так как он не рискует потерять коммиты от других разработчиков или иным образом вызвать проблемы.
Логан Пикап
19

Одно из решений этой проблемы - сделать то, что делает сценарий слияния msysGit: после слияния слить в старый заголовокfeature с -s ours. В итоге вы получите граф фиксации:

A--B--C------F--G (master)
       \         \
        \         D'--E' (feature)
         \           /
          \       --
           \    /
            D--E (old-feature)

... и ваш толчок featureбудет ускоренной.

Другими словами, вы можете сделать:

git checkout feature
git branch old-feature
git rebase master
git merge -s ours old-feature
git push origin feature

(Не проверено, но я думаю, что это правильно ...)

Марк Лонгэйр
источник
27
Я полагаю, что наиболее распространенная причина использования git rebase(вместо слияния masterобратно с вашей веткой функций) - создание чистой истории линейных коммитов. С вашим подходом история становится еще хуже. И поскольку перебазирование создает новые коммиты без какой-либо ссылки на их предыдущие версии, я даже не уверен, что результат этого слияния будет адекватным.
KL-7
6
@ KL-7: Суть в том merge -s ours, что он искусственно добавляет родительскую ссылку к предыдущей версии. Конечно, история не выглядит чистой, но спрашивающий, кажется, особенно обеспокоен необходимостью толкать featureветку, и это обходит это. Если вы хотите перебазировать, это более или менее один или другой. :) В целом, я думаю, это интересно, что проект msysgit делает это ....
Марк Лонгэйр
@ KL-7: Кстати, я добавил ваш ответ, который, безусловно, является правильным - я просто подумал, что это тоже может быть интересно.
Марк Лонгэйр
Это определенно интересно, по крайней мере, для меня. Спасибо. Я уже видел oursстратегию, но думал, что она применима только к конфликтным ситуациям, автоматически решая их, используя изменения в нашей ветке. Оказалось, это работает по-другому. И работать таким образом, это очень полезно, если вам нужна перебазированная версия (например, для сопровождающего репо, чтобы применять ее корректно master), но вы хотите избежать принудительного нажатия (если по какой-то причине многие другие ppl используют вашу ветку функций).
KL-7
15

Может быть, а может и нет, что в этой ветке есть только один разработчик, который сейчас (после ребазинга) не соответствует источнику / функции.

В качестве такового я бы предложил использовать следующую последовательность:

git rebase master
git checkout -b feature_branch_2
git push origin feature_branch_2

Да, новая ветка, это должно решить эту проблему без --force, что, на мой взгляд, является основным недостатком git.

JAR.JAR.beans
источник
3
Извините, но: «Продолжать генерировать ветки», чтобы избежать принудительного переопределения существующих, не помогает ни «одиноким разработчикам функций» (которые могут переопределять), ни нескольким людям, работающим над веткой функций (необходимо сообщить, что эта ветвь «увеличивает» и говорит переехать, ребята). - Это больше похоже на ручное управление версиями («thesis_00.doc, thesis_01.doc, ...») в системе управления версиями ...
Фрэнк Нок,
2
Кроме того, это не помогает, когда у вас открыт github PR на одном имени ветви, вам нужно будет создать новый PR для нового имени ветви, которое вы нажали.
gprasant
1
@frankee Половина правды из моего опыта. для одинокого разработчика, да, просто принудительное нажатие достаточно легко, но это привычка, которая может укусить вас позже. + новый разработчик только что присоединился? или, может быть, какая-то система CI, которая не использует --hard reset? для совместной работы, я думаю, что достаточно легко сообщить имя нового филиала, это можно легко записать в сценарии + для команды, я бы предложил перебазировать локально или когда филиал готов к слиянию, а не во время повседневной работы дополнительная фиксация является менее трудной, чем работа с конфликтами перебазирования / слияния в результате.
JAR.JAR.beans
@gprasant для пиара, опять же, я думаю, что было бы неправильно перебазировать, я бы на самом деле хотел увидеть одиночные коммиты с исправлениями пиара. Перебазирование (сквош) должно произойти только позже, как часть слияния с мастером, и когда пиар закончен и готов (поэтому не нужно открывать новый пиар).
JAR.JAR.beans
13

Мой способ избежать принудительного нажатия - создать новую ветку и продолжить работу над этой новой веткой и после некоторой стабильности удалить старую ветку, которая была перебазирована:

  • Перебазирование проверенной ветки локально
  • Ветвление из перебазированной ветки в новую ветку
  • Раздвигая эту ветку как новую ветку к удаленной. и удаление старой ветки на удаленной
Nabi
источник
1
Почему нет любви к этому варианту? Это определенно самый чистый, самый простой, самый безопасный.
cdmo
Поскольку у меня есть около 200 систем, отслеживающих имя ветки, и оно должно быть определенным именем для задачи, и если я начну делать ветку, переименовывает каждое нажатие, я схожу с ума.
Тамир Даниэли
@TamirDaniely Я не пробовал, но решает ли ваша проблема удаление старой ветви (из удаленного) перед тем, как нажать и нажать новую ветку с тем же старым именем?
Наби
2
@Nabi Это именно то, что делает --force-with-lease, за исключением того, что он также проверяет, что нет новых коммитов, которые не являются вашими.
Тамир Даниэли
12

Другие ответили на ваш вопрос. Если вы перебазируете ветку, вам нужно будет принудительно нажать на эту ветку.

Rebase и общий репозиторий обычно не ладят. Это переписывает историю. Если другие используют эту ветку или имеют ответвления от этой ветви, то ребаз будет довольно неприятным.

В целом, rebase хорошо работает для местного филиала. Удаленное управление ветками лучше всего работает с явными слияниями (--no-ff).

Мы также избегаем слияния мастера в ветку функций. Вместо этого мы перебазируем на master, но с новым именем ветви (например, добавив суффикс версии). Это позволяет избежать проблемы перебазирования в общем хранилище.

Билл Дверь
источник
5
Не могли бы вы добавить пример, пожалуйста?
Thermech
8

Что не так с git merge masterна featureветке? Это сохранит работу, которую вы имели, сохранив ее отдельно от основной ветки.

A--B--C------F--G
       \         \
        D--E------H

Изменить: Ах, извините, не прочитал вашу проблему. Вам понадобится сила, поскольку вы выполнили rebase. Все команды, которые изменяют историю, будут нуждаться в --forceаргументе. Это отказоустойчивый, чтобы предотвратить потерю работы (старые Dи Eбудут потеряны).

Итак, вы выполнили операцию, git rebaseкоторая заставила дерево выглядеть (хотя частично скрыто Dи Eбольше не находится в именованной ветви):

A--B--C------F--G
       \         \
        D--E      D'--E'

Таким образом, при попытке протолкнуть новую featureветку (с D'и E'в ней), вы потеряете Dи E.

Bouke
источник
3
В этом нет ничего плохого, и я знаю, что это сработает. Это просто не то, что мне нужно. Как я уже сказал, вопрос скорее концептуальный, чем практический.
Ювал Адам
4

Для меня работает следующие простые шаги:

1. git checkout myFeature
2. git rebase master
3. git push --force-with-lease
4. git branch -f master HEAD
5. git checkout master
6. git pull

После всего вышеперечисленного, мы также можем удалить ветку myFeature, выполнив следующую команду:

git push origin --delete myFeature
Нирадж Хеде
источник
3

Следующие работы для меня:

git push -f origin branch_name

и это не удаляет любой мой код.

Но, если вы хотите избежать этого, вы можете сделать следующее:

git checkout master
git pull --rebase
git checkout -b new_branch_name

тогда вы можете выбрать все свои коммиты в новую ветку. git cherry-pick COMMIT ID а затем нажмите вашу новую ветку.

Сохаир Ахмад
источник
5
-fэто псевдоним для --force, которого вопрос пытается избежать, если это возможно.
epochengine
1

Поскольку ОП понимает проблему, просто ищет более хорошее решение ...

Как насчет этого как практики?

  • Имейте в реальной ветке разработки функций (где вы никогда не делаете ребаз и форс-пуш, чтобы ваши коллеги-разработчики не ненавидели вас). Здесь регулярно берут эти изменения с основного слияния. История МессьеДа, сложнее, но жизнь проста, и никто не вмешивается в его работу.

  • Иметь вторую ветвь разработки функций, где один член команды функций регулярно выталкивает все функции, фактически перебазированные, действительно форсированные. Так что почти чисто на основе довольно недавнего мастер-коммита. По завершении функции нажмите эту ветку сверху мастера.

Для этого метода уже может быть имя шаблона.

Фрэнк Нок
источник