Есть те, кто утверждает, что вы должны сохранить все слияния: это представляет фактический процесс разработки. Используемый вами метод «очистки» набора коммитов таким образом, чтобы история была красивее и отражала идеализированный процесс разработки, должен облегчить жизнь будущим людям (возможно, даже будущим вам ) ) когда вам нужно исправить ошибки, которые были в прошлом тех будущих людей, которые могли бы стать вашим собственным будущим, в настоящий момент.
Все это - значительный объем работы, и отдача от нее неясна. И все же я сам предпочитаю этот метод «предварительной очистки истории», когда я могу себе это позволить.
Теперь давайте добавим и это:
git rebase --strategy-option=ours feature --onto master <divergent_ref>
Вы должны быть очень осторожными с -X ours
: Git следует простым текстовым правилам, которые не будут работать во всех ситуациях. (Если у вас есть автоматизированные тесты, рассмотрите возможность добавления --exec
для их запуска.)
Обычно в этой ситуации я просто запоминаю и повторяю каждое разрешение конфликта. Но, конечно, есть лучший способ?
Вы можете использовать git rerere
здесь, иногда. В основном --onto
- это то, что вам нужно (см. ответ Шверна ).
Есть несколько вещей, которые необходимо знать, прежде чем начать этот путь:
Во-первых, помните, что git rebase
по существу означает копировать некоторые коммиты, как если бы это было git cherry-pick
. Некоторые операции перебазирования действительно используют git cherry-pick
. Один - самая старая форма rebase - использует git format-patch
и git am
. В целом это работает не так хорошо, но работает быстрее. Поскольку он не использует git cherry-pick
, я думаю, что git rerere
также не будет применяться. Но добавление --strategy-option
вызывает вариант выбора вишни; добавление -m
или -i
или --exec
.
вишня является слиянием!
После перечисления некоторых идентификаторов коммитов ha sh, rebase начинает свою реальную работу с отсоединения HEAD
в коммите, к которому должны быть добавлены копии. Затем он делает копии. После того, как выбранные коммиты были скопированы, операция rebase записывает идентификатор ha sh последнего скопированного или HEAD
commit в название ветки, в которой вы работали, когда все это запускало.
Но мы также должны взглянуть на набор коммитов, которые должны быть скопированы. В первом предложении выше написано , перечислив некоторые коммиты ha sh ID . Какие га sh идентификаторы? Вот где приходит --onto
.
Обычный git rebase
дает вам одну ручку управления, которая документация git rebase
вызывает upstream
. Эта одна ручка управления выбирает и копирует и , куда помещать копии . Использование git rebase --onto
дает вам отдельную ручку для , куда помещать копии , что освобождает аргумент upstream
, чтобы вы могли более тщательно выбирать, что копировать.
Аргумент upstream
обычно является и , куда помещать копии и , что не , чтобы скопировать , с что копировать список определяется по результату git rev-list <em>upstream</em>..HEAD
, более или менее. Мы увидим это в действии ниже. Но более или менее здесь важен: документация по rebase цитирует эту запись A..B
как то, как она определяет, что копировать, а что не копировать, но на самом деле она меньше. Опять же, мы увидим больше об этом ниже.
Как express что, я думаю, ваша настоящая проблема здесь
В любом случае, давайте нарисуем то, что я считаю вашей реальной проблемой- со временем, как несколько разных моментальных снимков:
E--F--G <-- feature1
/
...--o--*--o--o--*--o--o <-- mainline
\
A--B--C--D <-- feature2
Вот так все и начинается: идет какой-то проект с какой-то основной веткой и разрабатываются две функции. Но теперь выясняется, что feature2
зависит от чего-то в feature1
. Итак, теперь вы хотите сделать ребаз, то есть скопировать некоторые коммиты из feature2
, чтобы они появились в feature1
. На этом этапе этот ребаз легко вызывается:
git checkout feature2; git rebase feature1
уже выбирает правильные коммиты и правильную точку копирования. Если это копирование выполняется посредством cherry-pick, каждая копия выполняется механизмом слияния, который включает сохранение разрешений конфликтов с помощью git rerere
, если установлено rerere.enabled
. (Если установлено , а не , Git не сохраняет ни конфликты, ни их разрешения.)
Конечный результат:
A'-B'-C'-D' <-- feature2
/
E--F--G <-- feature1
/
...--o--*--o--o--*--o--o <-- mainline
\
A--B--C--D [abandoned]
Новая цепочка коммитов, A'-B'-C'-D'
, очень похоже на вашу оригинальную цепочку, но идентификаторы ha sh отличаются. Поскольку на самом деле никто не смотрит на идентификаторы ha sh, а исходная цепочка A-B-C-D
теперь невидима для обычных операций git log
, никто никогда не обращает внимания на переход - но это реально. И это собирается укусить вас по-другому.
Теперь, когда вы перебазировали ваш feature2
поверх feature1
, кто-то еще (или, может быть, даже вы ) перебазирует feature1
. Результат:
A'-B'-C'-D' <-- feature2
/
E--F--G
/
...--o--*--o--o--*--o--o <-- mainline
\
E'-F'-G' <-- feature1
(я перестал рисовать в A-B-C-D
, так как они больше не нужны.) Обратите внимание, как E-F-G
предполагается отказаться, но на самом деле это не так.
Обычный git checkout feature2; git rebase feature1
выберет для копирования коммиты E-F-G-A'-B'-C'-D'
. Используя --onto
, вы можете запустить git rebase --onto feature1 <em>hash-of-commit-G</em>
to tell
git rebase *do not copy commits E-F-G
.
Но на самом деле, внутри git rebase
есть удобная функция: она автоматически исключает некоторые коммиты из своего списка коммитов. Я уже упоминал об этом выше, в части более или менее ... фактически меньше . Документация по перебазированию фактически говорит это:
[Копируемые коммиты] представляют собой тот же набор коммитов, который был бы показан git log <upstream>..HEAD
; или git log 'fork_point'..HEAD
, если --fork-point
активен (см. описание на --fork-point
ниже); или git log HEAD
, если указан параметр --root
.
Но это не так! По умолчанию git rebase
также исключает:
- любой коммит слияния и
- любой коммит в
<em>upstream</em>..HEAD
, для которого его git patch-id
соответствует патчу -ID коммита, который находится в HEAD..<em>upstream</em>
.
Предположим, что тот, кто скопировал E-F-G
в E'-F'-G'
, не должен был разрешать конфликты слияния или что-либо еще. В этом случае копии , E'
- G'
будут иметь те же git patch-id
, что и соответствующие им оригинал . Так что git rebase
отбросит эти коммиты даже без --onto
.
(в документации также упоминается точка ветвления, которая использует собственный рефлог Git для upstream
если вы этого не сделали, выберите значение --onto
, однако режим точки разворота (1) не всегда активен и (2) не всегда правильный, когда активен . как и сам выбор точки разветвления обманывает меня: я думаю, что он закапывает слишком много магии c. Кроме того, поскольку он использует reflogs, он завершается ошибкой, если истек срок действия критической записи reflog. Но это все равно в стороне. )
В этом конкретном случае все идет не так, как надо, когда тот, кто копирует feature1
, должен был модифицировать один из своих скопированных коммитов, так что трюк с идентификатором патча не удался. В этом случае использование --onto
- это путь к go: он устраняет проблему без каких-либо дополнительных ошибок.
Но у вас может быть другая проблема. В частности, предположим, что пока вы работаете с feature2
, а кто-то еще работает с feature1
, они понимают то же самое, что вы сделали, или слышите или видите одно из изменений ваших коммитов, и они добавляют new совершить это частично , но не полностью, исправить то, что вы делаете, но по-другому? Тогда, возможно, они имеют:
A'-B'-C'-D' <-- feature2
/
E--F--G
/
...--o--*--o--o--*--o--o <-- mainline
\
H-E'-F'-G' <-- feature1
, где H
больше похож на один из ваших оригинальных A-B-C-D
коммитов, чем на ваш обновленный A'-B'-C'
коммитов. В этом случае вы можете вернуть свою серию A-B-C-D
. Commit D
почти наверняка все еще находится в вашем Git, помнятом как feature2@{<em>number</em>}
. (Точное число зависит от того, сколько обновлений вы сделали для feature
с тех пор.) Или, конечно, вы можете делать то, что я делаю, то есть сохранять исходный указатель feature2
, создавая feature2.0
, feature2.1
и т. Д. Давайте нарисуем его обратно, как feature2.0
, и переименуем feature
в feature2.1
:
A'-B'-C'-D' <-- feature2.1
/
E--F--G
/
...--o--*--o--o--*--o--o <-- mainline
\ \
\ H-E'-F'-G' <-- feature1
\
A--B--C--D <-- feature2.0
Если H
очень близко к одному из ваших первых четырех коммитов - так что он либо имеет тот же идентификатор патча, либо вы можете использовать drop
в интерактивной перебазировке - вы можете использовать его в качестве источника , Если вам нужно было разрешить некоторые конфликты раньше, git rerere
сделает это. Теперь мы можем сделать:
git checkout -b feature2 feature2.0
git rebase -i feature1
(интерактивная перебазировка позволяет делать «отбрасывание» и принудительно собирать вишню; если хотите, используйте git rebase -m
, чтобы принудительно собирать вишню без интерактивности). Если все пойдет хорошо и при условии, что H
== C
как бы, мы получим:
A'-B'-C'-D' <-- feature2.1
/
E--F--G
/
...--o--*--o--o--*--o--o mai... A"-B"-D" <-- feature2
\ \ /
\ H-E'-F'-G' <-- feature1
\
A--B--C--D <-- feature2.0
rerere
, возможно, был полезен с точки зрения сохранения разрешения конфликта слияния для D
, и автоматическое обнаружение идентификатора патча может выдать вам коммит C
.
Просмотр rebase в виде серии cherry-picks
Обычное слияние работает, находя база слияния - общий общий коммит между двумя ветвями - и выполнение двух различий. Разница между базой слияния и каждой веткой подсказывает нам, кто что изменил:
I--J <-- ours (HEAD)
/
...--G--H
\
K--L <-- theirs
Сравнение снимка коммита H
с J
говорит нам, что мы изменено, на ветке ours
; сравнение H
с L
говорит нам, что они изменились; и git merge
объединяет изменения. В случае конфликтов (расширенная) стратегия-опция -X ours
или -X theirs
указывает Git разрешить конфликт автоматически , выбрав H
-vs- J
(«наш») или H
-vs- L
("их").
Для обычного слияния окончательный коммит после разрешения всего представляет собой коммит слияния с родителями J
и L
, в этом порядке (сначала наш, потом их).
Вишневое дерево берет нормальное слияние и подрывает его. Вместо поиска общего коммита база слияния - это просто выбранный коммит parent commit.
Когда мы перебазирование и копирование первого коммита имеет смысл:
...--o--o--*--o--H <-- mainline, HEAD (detached)
\
A--B--C <-- feature
Теперь мы копируем коммит A
. Здесь "наш" - H
: коммит наконечника mainline
, на который HEAD
указывает непосредственно (отсоединен). Основа псевдо-слияния - коммит *
: точка, в которой A
впервые расходится. Таким образом, мы будем различать *
против H
, чтобы увидеть, что "мы" изменились, и *
против A
, чтобы увидеть, что "они" изменились. Тогда мы объединим эти различия: это вернет нас H
, плюс все, что мы сделали в A
. Мы зафиксируем результат и сделаем A'
, копию A
:
A' <-- HEAD
/
...--o--o--*--o--H <-- mainline
\
A--B--C <-- feature
(Финальный коммит вишневого пика - обычный коммит без слияния.)
Но теперь мы скопируем B
. Его родитель A
, поэтому мы будем использовать A
для базы слияния. Мы рассмотрим A
против A'
, чтобы увидеть, что "мы" изменились, и A
против B
, чтобы увидеть, что "они" изменились. Если мы слепо примем «наши» - то есть A
против A'
- в конфликте, мы можем потерять важные изменения с B
. Может быть, они нам не нужны - может быть, *
-vs- H
уже содержали их. Но, возможно, мы это сделаем.
В любом случае, когда все это будет сделано, мы получим:
A'-B' <-- HEAD
/
...--o--o--*--o--H <-- mainline
\
A--B--C <-- feature
, и мы готовы к вишне C
, как и раньше. Когда это будет сделано, Git будет восстанавливать имя feature
с C
и вместо него указать C'
(и повторно присоединить HEAD
):
A'-B'-C' <-- feature (HEAD)
/
...--o--o--*--o--H <-- mainline
\
A--B--C [abandoned]
, и это наша перебазировка .
Так как все это теоретически, давайте просто сделаем выводы сейчас
Реальные моменты, о которых следует помнить:
- rebase copy (некоторые) коммиты, как будто или фактически
git cherry-pick
; - , реальный ключ к минимизации работы - это выбор правильных коммитов для копирования;
- иногда это означает
--onto
и иногда это может даже означать возвращение к более ранней копии вашей собственной работы.
(на самом деле я сам не использую git rerere
и не уверен, использует ли cherry-pick его автоматически. Если нет, вы можете использовать его вручную.)