Ответвление раздавленных веток - PullRequest
0 голосов
/ 02 августа 2020

Предположим, у меня есть следующая история git: главная ветка, начинающаяся с фиксации A, ветка feature-1, ответвленная от A с фиксациями B и C, и вторая ветвь функции feature-1, созданный на основе коммита C с коммитами D и E.

master     A
            \
feature-1    B--C
                 \
feature-2         D--E

Теперь предположим, что коммит C был протестирован и готов к слиянию, поэтому мы используем git switch master; git merge feature-1 --squash.

master     A------C'
            \    /
feature-1    B--C
                 \
feature-2         D--E

История для мастера хороша и чиста, только коммиты A и C', но если теперь мы хотим сравнить master и feature-2 (например, git log master..feature-2) в итоге мы видим все коммиты из feature-1, которые уже были объединены.

Вопрос 1: Есть ли простой способ получить sh историю для feature-2 чтобы соответствовать сжатому слиянию? Что, если история немного сложнее, и после точки ветвления C на feature-1 было больше коммитов, которые были squa sh -молоты с мастером?

Вопрос 2: Предполагая, что переписывать историю сложно (или это может быть утомительно сделано с помощью git rebase -i; у меня более двух коммитов в каждой ветке), есть ли способ просмотреть только те коммиты в feature-2, которые не были ' t squa sh - объединены с мастером? При выполнении запроса на перенос на github или bitbucket для feature-2 -> master, есть ли способ перечислить только эти действительно новые коммиты?

Ответы [ 2 ]

1 голос
/ 03 августа 2020

Теперь предположим, что коммит C был протестирован и готов к слиянию, поэтому мы используем git switch master; git merge feature-1 --squash.

master     A------C'
            \    /
feature-1    B--C
                 \
feature-2         D--E

Этот рисунок не совсем правильный: он должен читаться так, как я нарисовал ниже. Обратите внимание: я также переместил имена вправо по причинам, которые вскоре станут понятнее. Я также назвал squa sh commit BC, что является попыткой прояснить, что есть один коммит, который делает то, что B-и- C делали вместе.

То, что вы нарисовали, было настоящее слияние (хотя вы назвали фиксацию слияния C'). Как сказал Мэтт , «squa sh merge» вовсе не является слиянием.

A--BC   <-- master
 \
  B--C   <-- feature-1
      \
       D--E   <-- feature-2

На этом этапе почти нет причин для сохраните имя feature-1. Если вы удалите его, мы можем перерисовать график следующим образом:

A--BC   <-- master
 \
  B--C--D--E   <-- feature-2

Обратите внимание, что все коммиты A-B-C-D-E находятся в ветке feature-2 (независимо от того, удаляем ли мы имя feature-1); commit BC есть только на master.

Основная причина сохранения имени feature-1 заключается в том, что оно идентифицирует фиксацию C, что упрощает копирование коммитов D и E (и никакие другие) в новые и улучшенные коммиты D'-E'.

Вопрос 1: Есть ли простой способ сквашить sh историю для feature-2, чтобы соответствовать сжатое слияние?

Мне не совсем понятно, что вы подразумеваете под «squa sh история». Однако после выполнения приведенного выше git merge --squash снимок в фиксации BC будет соответствовать (точно) снимку в фиксации C, поэтому выполняется:

git switch feature-2 && git rebase --onto master feature-1

(обратите внимание на --onto здесь 1 ) сообщит Git о копировании коммитов D и E (только) с копиями, идущими после фиксации BC, например:

      D'-E'  <-- feature-2 (HEAD)
     /
A--BC   <-- master
 \
  B--C   <-- feature-1
      \
       D--E   [abandoned]

Теперь это безопасно чтобы удалить имя feature-1, так как нам больше не нужно что-то запоминать ha sh ID фиксации C. Если мы перестанем рисовать в заброшенных коммитах, мы получим:

A--BC   <-- master
     \
      D'-E'  <-- feature-2

, что может быть тем, что вы хотели.

1 Обычно git rebase занимает одно имя или фиксацию ha sh ID. Затем он:

  1. перечисляет некоторый набор коммитов для копирования, используя фиксацию ha sh ID в качестве ограничителя;
  2. делает эквивалент git switch --detach при фиксации ha sh ID;
  3. копирует коммиты, перечисленные на шаге 1;
  4. перемещает имя ветки, которое вы использовали до шага 2, чтобы указать на последний коммит, скопированный на шаге 3; и
  5. выполняет эквивалент git switch обратно к имени ветки, только что перемещенному на шаге 4.

Когда не с использованием --onto, фиксация ha sh Идентификаторы на шагах 1 и 2 - одинаковые . При использовании --onto идентификаторы фиксации ha sh на шагах 1 и 2 являются или, по крайней мере, могут быть разными . Таким образом, с --onto мы можем сказать Git: Только копировать некоторые коммиты, а не много коммитов.

В частности, без --onto мы скопируйте все коммиты, которые достижимы из HEAD, но не достижимы из (одиночного) аргумента, и копии будут go в (одиночный) аргумент. С помощью --onto мы можем сказать: Копировать коммиты достижимы из HEAD, но не из указанного мной ограничителя, в место, указанное моим отдельным аргументом --onto. В этом случае это позволяет нам сказать не пытайтесь копировать B и C.

С другой стороны, вы также можете просто запустить:

git switch master             # if needed - you're probably already there
git merge --squash feature-2

, если вам просто нужно одно сква sh -слияние цепочки DE:

A--BC--DE   <-- master (HEAD)
 \
  B--C   <-- feature-1
      \
       D--E   <-- feature-2

Это git merge --squash будет обычно go плавно, потому что, как и обычный git merge, git merge --squash начинается с:

  • нахождения базы слияния (A в в этом случае);
  • сравнение базы слияния с текущей фиксацией (BC, потому что HEAD равно master, что определяет фиксацию BC); и
  • сравнение базы слияния с указанной фиксацией (E, потому что feature-2 имена фиксации E).

Первая разница показывает, что сделал B+C, потому что снимок BC совпадает с C s, а второй показывает, что сделал B+C+D+E, потому что снимок E является результатом B плюс C плюс D плюс E. Таким образом, если D и / или E конкретно un не сделали что-то, что сделали B и / или C, два набора изменений, скорее всего, объединятся автоматически.

(Обратите внимание, что перебазирование здесь всегда происходит плавно, даже если D и / или E что-то отменит.)

Разница между squa sh -not-really-a-merge и реальным слияние ограничено окончательной фиксацией: у squa sh есть фиксация с единственным родителем, в данном случае BC, в то время как реальное слияние имеет фиксацию с двумя родителями. В этом случае реальное слияние даст вам BC в качестве одного родителя и E в качестве другого. Вы, вероятно, захотите сначала удалить коммиты B и C, если вам нравится BC squa sh -merge.

Что, если история немного сложнее и после точки ветвления C на feature-1 было больше коммитов, которые были объединены squa sh в master?

Как всегда, фокус в том, чтобы нарисовать реальный график. Мы можем начать с этого:

A   <-- master
 \
  B--C--F--G   <-- feature-1
      \
       D--E   <-- feature-2

, которое после git switch master && git merge --squash feature-1 дает:

A--BCFG   <-- master
 \
  B--C--F--G   <-- feature-1
      \
       D--E   <-- feature-2

Теперь можно использовать:

git switch feature-2 && git rebase --onto master feature-1

Обратите внимание, что это та же команда, которую мы использовали в более ранней ситуации. В ней говорится (сравните с шагами в сноске 1 выше):

  1. Вывести список коммитов доступен из feature-2 (где мы находимся после git switch) , но не из feature-1. Коммиты, доступные из feature-2, - A-B-C-D-E, а коммиты, доступные из feature-1, - A-B-C-F-G. Вычитание A-B-C-F-G из A-B-C-D-E оставляет D-E.

  2. Перейти на отдельную HEAD по адресу master, т.е. зафиксировать BCFG.

  3. Скопируйте коммиты, перечисленные на шаге 1 , т.е. D и E.

  4. Янк имя ветки (feature-2) туда, где мы сейчас (commit E').

  5. Сделайте эквивалент git switch feature-2 снова.

Результат:

        D'-E'  <-- feature-2 (HEAD)
       /
A--BCFG   <-- master
 \
  B--C--F--G   <-- feature-1
      \
       D--E   [abandoned]

, после чего можно безопасно удалить имя feature-1: нам больше не нужен простой способ найти фиксацию C через фиксацию G больше.

Вопрос 2: Предполагая, что перезапись истории сложна (или может быть утомительно выполнена с помощью git rebase -i; У меня более двух коммитов в каждой ветке) ...

Как вы можете видеть выше, это не обязательно правильное предположение. Насколько сложна перебазировка, зависит от того, сколько конфликтов слияния возникает с каждой копируемой фиксацией, что зависит от того, что произошло после последней общей фиксации (C на рисунках выше). Тем не менее:

... есть ли способ просмотреть только коммиты в feature-2, которые не были squa sh - объединены в master?

Команда git log имеет простой синтаксис для этого, если у вас все еще есть имя feature-1, идентифицирующее соответствующую фиксацию, как на различных рисунках выше:

git log feature-1..feature-2

делает именно это. Этот синтаксис означает, что все коммиты достижимы, начиная с feature-2 и работая в обратном направлении, за вычетом всех коммитов, достижимых, начиная с feature-1 и работая в обратном направлении. Обратите внимание, что это тот же набор коммитов, который мы скопировали с нашим git rebase операций в примерах выше. 2

При выполнении запроса на вытягивание на github или bitbucket для feature-2 -> master, есть ли способ перечислить только эти действительно новые коммиты ?

Нет, потому что в этих системах нет эквивалентного синтаксиса. Однако, если вы используете rebase для копирования только желаемых коммитов и force-pu sh, чтобы репозиторий GitHub или Bitbucket совпадал, они покажут то, что вы хотели.

2 Выше не упоминается тот факт, что git rebase намеренно пропускает определенные коммиты на шаге 1 по умолчанию. В вашем случае нет никаких коммитов, которые следует здесь опускать, поэтому это не совсем актуально, но стоит упомянуть:

  • По умолчанию git rebase пропускает все слияние совершает.
  • По замыслу (и нет возможности остановить это), git rebase также использует те же вычисления, которые git cherry или git log --cherry-pick использовали бы для исключения из копирования любых коммитов, чьи patch-id соответствует фиксации в вышестоящем наборе коммитов. (Этот набор трудно определить, не вдаваясь в подробности того, как работает разностная нотация A...B symri c.) В вашем случае это тоже не имеет значения, потому что подобный вид сопоставления идентификатора патча здесь крайне маловероятен. Это больше предназначено для случая, когда кто-то из апстрима намеренно использовал git cherry-pick для копирования одного или нескольких ваших коммитов в ветку, на которой вы собираетесь выполнить перебазирование.
  • В некоторых случаях - но опять же не ваш - git rebase по умолчанию запускает git merge --fork-point, чтобы найти коммиты, которые следует пропустить, и это может дать удивительные результаты.

Исторически документация по перебазированию не упоминала их, вероятно, потому, что они не все так часто. В вашем случае они не должны всплывать. Последняя версия документации по перебазированию значительно улучшена.

0 голосов
/ 02 августа 2020

в простом сценарии, если вы просто rebase свой feature-2 на master, у вас будут только непустые коммиты.

в более сложном сценарии я бы предложил сделать следующее:

git switch feature-2
git merge origin/master
git reset --soft origin/master
git commit -m 'feature 2'

это приведет к единственной фиксации, которая содержит все изменения из feature-2, эффективно сжатые поверх master

...