Как перебазировать ветку против мастера после того, как родитель раздавлен и зафиксирован? - PullRequest
1 голос
/ 28 июня 2019

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

  1. Отправить запрос на слияние для филиала cool-feature-A
  2. Создать новую ветку, основанную на cool-feature-A, с именем cool-feature-B. Начните разработку в этой ветке.
  3. Коллега одобряет мой запрос на слияние для cool-feature-A.
  4. Я перебрасываю cool-feature-B против master (что безболезненно) и продолжаю развитие.

Проблема возникает, если коллега выполняет слияние сквоша на шаге 3. Это переписывает большие куски истории, которые присутствуют в cool-feature-B. Когда я доберусь до шага 4, меня ждет мир боли слияния.

Как я могу избежать этого?

1 Ответ

3 голосов
/ 28 июня 2019

По сути, вы должны сказать Git: Я хочу перебазировать cool-feature-B против master, но я хочу скопировать набор коммитов, отличный от тех, которые вы обычно вычисляетездесь. Самый простой способ сделать это - использовать --onto.То есть, как правило, вы запускаете, как вы сказали:

git checkout cool-feature-B
git rebase master

, но вам нужно будет сделать:

git rebase --onto master cool-feature-A

до , вы удалите свою собственную ветку-имя / метка cool-feature-A.

Вы всегда можете сделать это, даже если они используют обычное слияние. Это не помешает, за исключением того, что вы должны ввестипобольше и помните (как вам нравится), что вам понадобится этот --onto, которому позже понадобится правильное имя или хеш-идентификатор.

(Если вы можете получить хеш-идентификатор коммита, к которому cool-feature-A баллов в данный момент в reflog для вашего апстрима cool-feature-B, вы можете использовать функцию --fork-point, чтобы Git автоматически вычислял это для вас позже, при условии, что срок действия записи reflog не истек. Но это, вероятно, сложнее,в общем, чем просто делать это вручную. Плюс у него есть вся эта «предоставленная» часть.)

Почему это так

Начнем, как обычно, с графических рисунков.Изначально у вас есть эта настройка в вашем собственном репозитории:

...--A   <-- master, origin/master
      \
       B--C   <-- cool-feature-A
           \
            D   <-- cool-feature-B

После того, как вы запустите git push origin cool-feature-A cool-feature-B, вы получите:

...--A   <-- master, origin/master
      \
       B--C   <-- cool-feature-A, origin/cool-feature-A
           \
            D   <-- cool-feature-B, origin/cool-feature-B

Обратите внимание, что все, что мы здесь сделали, это добавили два origin/ имен (два имени для удаленного слежения): в их хранилище, в origin они получили коммиты B, C и D и установили свои *Запоминаемые имена 1046 * cool-feature-A и cool-feature-B фиксируют коммиты C и D соответственно, поэтому ваш собственный Git добавил ваш origin/ варианты этих двух имен.

Еслиони (кем бы «они» ни были - люди, которые управляют хранилищем в origin) выполняют быстрое слияние, они передвигают своего мастера до точки фиксации C.(Обратите внимание, что веб-интерфейс GitHub не имеет кнопки для быстрого слияния. Я не использовал веб-интерфейс GitLab; он может отличаться по-разному.) Если они вызываютреальное слияние - это то, что по умолчанию делает веб-страница GitHub «слить сейчас»;опять же, я не знаю, что GitLab делает здесь - они сделают новый коммит слияния E:

...--A------E
      \    /
       B--C
           \
            D

(здесь я намеренно удалил все именапоскольку их не совсем совпадают с вашими).Они, вероятно, удалят (или, возможно, даже никогда не будут созданы) свое имя cool-feature-A.В любом случае, вы можете иметь свое собственное Git fast-forward ваше master имя при обновлении ваших origin/* имен:

...--A------E   <-- master, origin/master
      \    /
       B--C   <-- cool-feature-A [, origin/cool-feature-A if it exists]
           \
            D   <-- cool-feature-B, origin/cool-feature-B

или:

...--A
      \
       B--C   <-- cool-feature-A, master, origin/master [, origin/cool-feature-A]
           \
            D   <-- cool-feature-B, origin/cool-feature-B

Независимо от того, удаляете ли вы свое имя cool-feature-A сейчас - для удобства на последующих рисунках, скажем, вы делаете - если вы запустите:

git checkout cool-feature-B
git rebase master

Git теперь будет перечислять список коммитов, доступных с D - D, затем C, затем B и т. Д. - и вычтите список коммитов, достижимых из master: E (если он существует), затем A (если E существует) и C (существует или нет E существует), затем B и так далее.Результатом вычитания будет просто фиксация D.

Теперь ваш Git копирует коммиты в этом списке, так что новые копии появляются после подсказки master: то есть после E, если они сделалиреальное слияние, или после C, если они произвели ускоренное слияние.Так что Git либо копирует D в новый коммит D', который идет после E:

              D'   <-- cool-feature-B
             /
...--A------E   <-- master, origin/master
      \    /
       B--C
           \
            D   <-- origin/cool-feature-B

, либо оставляет D в одиночку, потому что он уже идет после C (так что ничего новогорисовать).

Хитрые части возникают, когда они используют любой эквивалент GitLab - это нажимаемые кнопки "сквош и слияние" или "перебазировать и объединить" в GitHub.Я пропущу случай «перебазирования и слияния» (который обычно не вызывает проблем, потому что перебаз Git тоже проверяет идентификаторы патчей) и перейду непосредственно к сложному случаю, «сквошу и слиянию».Как вы правильно заметили, это делает новый и другой , single, commit.Когда вы вносите этот новый коммит в свой собственный репозиторий, например, после git fetch, вы получаете:

...--A--X   <-- master, origin/master
      \
       B--C   <-- cool-feature-A [, origin/cool-feature-A]
           \
            D   <-- cool-feature-B, origin/cool-feature-B

where X является результатом создания нового коммита, чей снимок будет соответствовать слиянию E (если они должны были сделать слияние E), но чей (единственный) родительский объект является существующим коммитом A,Таким образом, история - список коммитов, перечисленных при работе в обратном направлении - от X - это просто X, затем A, тогда все коммиты предшествуют A.

Если вы запустите обычный git rebase master в то время как на cool-feature-B, Git:

  1. перечисляет коммиты, достижимые с D: D, C, B, A, ...;
  2. перечисляет коммиты, достижимые из X: X, A, ...;
  3. вычитает набор в шаге 2 из набора в шаге 1, оставляя D, C, B;
  4. копирует эти коммиты (в обратном порядке), чтобы они следовали после X.

Обратите внимание, что оба шага 2 и 3 используютслово master для поиска коммитов: коммиты для копирования на шаге 2 - это те, которые недоступны с master.Место для размещения копий для шага 3 - после кончика master.

Но если вы запустите:

git rebase --onto master cool-feature-A

, у вас есть использование Git различные элементы в шагах 2 и 3:

  • Список коммитов для копирования, начиная с шага 2, исходит из cool-feature-A..cool-feature-B: вычтите C-B-A-... из D-C-B-A-....Это оставляет просто коммит D.
  • Место для размещения копий, на шаге 3, происходит от --onto master: поместите их после X.

Так что теперь Gitтолько копирует D в D', после чего git rebase возвращает имя cool-feature-B, чтобы указать D':

          D'  <-- cool-feature-B
         /
...--A--X   <-- master, origin/master
      \
       B--C   <-- cool-feature-A [, origin/cool-feature-A]
           \
            D   <-- origin/cool-feature-B

, что вы и хотели.

Если бы они - люди, контролирующие репозиторий GitLab - использовали истинное слияние или ускоренную перемотку вперед, а не слияние вообще, это все бы работало: ваш Git перечислил бы D -on-в обратном направлении, но удалите C -в обратном направлении из списка, оставив только D для копирования;и затем Git скопирует D так, чтобы он шел после E (истинный случай слияния) или C (случай ускоренной перемотки вперед).Случай ускоренной перемотки «скопируйте D так, чтобы он находился там, где он уже есть», умело не стал бы копировать вообще и просто оставил все на месте.

...