Понимание" git pull --rebase "против" git rebase"

согласно моему пониманию git pull --rebase origin master, это должно быть эквивалентом выполнения следующих команд:

(from branch master):  $ git fetch origin
(from branch master):  $ git rebase origin/master

кажется, я нашел случай, когда это работает не так, как ожидалось. В моей рабочей области у меня есть следующая настройка:

  • филиала origin/master справки филиала master на пульт origin
  • филиала master настройки к трассе origin/master и за мастер по несколько совершает.
  • филиала feature настроен для отслеживания локальной ветви master и вперед of master на несколько коммитов.

иногда я потеряю коммиты, выполнив следующую последовательность шагов

(from branch master):  $ git pull --rebase
(from branch master):  $ git checkout feature
(from branch feature): $ git pull --rebase

в этот момент несколько коммитов впереди я был на feature уже потеряли. Теперь, если я сброшу свою позицию, а вместо этого сделаю следующее:

(from branch feature): $ git reset --hard [email protected]{2} # rewind to before second git pull
(from branch feature): $ git rebase master

коммиты были применены правильно, и мой новый commits on feature по-прежнему присутствуют. Это, кажется, прямо противоречит моему пониманию того, как git pull работает, если git fetch . делает что-то более странное, чем я ожидал.

к сожалению, это не 100% воспроизводимость для всех коммитов. Однако, когда он работает для фиксации, он работает каждый раз.

Примечание: мой git pull --rebase здесь на самом деле следует читать как --rebase=preserve, если это важно. У меня есть следующее в моем ~/.gitconfig:

[pull]
    rebase = preserve

1 ответов


(Edit, 30 нояб. 2016: см. Также ответ to почему git rebase отбрасывает мои коммиты?. Теперь практически точно известно, что это связано с опцией fork-point.)

здесь are несколько различий между руководством и pullна основе git rebase (меньше теперь в 2.7, чем было в версиях git до на git merge-base). И я подозреваю, что ваши автоматические слияния могут быть вовлечены. Это немного сложно. конечно, но тот факт, что ваша локальная ветвь следует за вашей другой локальной ветвью, которая получает перезагрузку, довольно наводит на размышления. Между тем, старый git pull скрипт также недавно был переписан на C, поэтому сложнее увидеть, что он делает (хотя вы можете установить переменную среды GIT_TRACE to 1 чтобы git показывал Вам команды, когда он запускает их внутри).

в любом случае, здесь есть два или три ключевых элемента (в зависимости от того, как вы считаете и разделяете их, я сделаю это в 3):

  • git pull работает git fetch, либо git merge или git rebase в инструкции, но когда он работает git rebase он использует новое машинное оборудование форк-пункта для того чтобы"взять от переднего ребасе".

  • , когда git rebase запускается без аргументов, у него есть особый случай, который вызывает механизм fork-point. При запуске с аргументами механизм fork-point отключается, если явно не запрашивается с помощью --fork-point.

  • , когда git rebase поручено сохранить слияния, он использует интерактивный код перебазирования (неинтерактивно). Я не уверен, что это действительно имеет значение здесь (следовательно, "может быть вовлечено" выше). Обычно он сглаживает слияния, и только интерактивный скрипт перебазирования имеет код для их сохранения (этот код фактически повторно выполняет слияния, поскольку нет другого способа справиться с ними).

самый важный пункт здесь (наверняка) код вилки. Этот код использует reflog для обработки случаев, лучше всего показанных на чертеже части графика фиксации.

в нормальном (не требуется материал точки вилки) случае перебазирования у вас есть что-то вроде этого:

... - A - B - C - D - E   <-- origin/foo
            \
              I - J - K   <-- foo

здесь A и B являются фиксациями, которые у вас были, когда вы начали свою ветку (так что B - это слияние-базы), C через E новые коммиты, которые вы взяли с пульта дистанционного управления через git fetch и I через K сами себе совершает. Перебазирование копирует код I через K, прикрепляя первую копию к E, второй к-копии -I, а третий-копия-J.

ГИТ выясняет-или привык, во всяком случае -, который совершает копирование с помощью git rev-list origin/foo..foo, то есть, используя имя вашей текущей ветви (foo), чтобы найти K и работать в обратном направлении, и название его вверх по течению (origin/foo), чтобы найти E и работать в обратном направлении. Обратный марш останавливается на база слияния, в этом случае B, и скопированный результат выглядит так:

... - A - B - C - D - E   <-- origin/foo
           \            \
            \             I' - J' - K'   <-- foo
             \
              I - J - K   [[email protected]{1}: reflog for foo]

проблема с этим методом возникает, когда upstream -origin/foo вот - это сама rebased. Скажем, например, что на origin кто-то силой толкнул, так что B была заменена новой копией B' С другой формулировкой фиксации (и, возможно, другое дерево, но, мы надеемся, ничего, что влияет на наш IдоK). Отправная точка теперь выглядит это:

          B' - C - D - E    <-- origin/foo
        /
... - A - B   <-- [origin/[email protected]{n}]
            \
              I - J - K   <-- foo

используя git rev-list origin/foo..foo, мы бы выбрали commits B, I, J и K для копирования и попробуйте вставить их после E как обычно; но мы не хочу скопировать B как это действительно пришло из origin и был заменен собственной копией B'.

какой код вилки не смотреть на reflog для origin чтобы увидеть, если B был доступен в некоторое время. То есть, он проверяет не только origin/master (нахождение E и сканирование обратно в B' а то A), а также origin/[email protected]{1} (указывая прямо на B, возможно, в зависимости от того, как часто вы бегаете git fetch), origin/[email protected]{2} и так далее. Любые коммиты на foo, что составляет с любой origin/[email protected]{n} включены для рассмотрения в поиске наименьшего общего узла предка в графике (т. е. все они рассматриваются как варианты, чтобы стать базой слияния, которая git merge-base печать).

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


в вашем случае, у вас есть три имена ветвей (и, следовательно, три reflogs) участвуют:

  • origin/master, который обновляется git fetch (первый шаг git pull а филиал master)
  • master, который обновляется как вами (через обычные коммиты), так и git rebase (второй шаг git pull), и
  • feature, который обновляется как вами (через обычные коммиты), так и git rebase (второй шаг второй git pull: вы "извлекаете" из себя, нет-op, затем rebase feature on master).

как перебазирует бежать с --preserve-merges (отсюда невзаимосвязанные интерактивный режим) и --onto new-tip fork-point, где fork-point commit ID найден путем запуска git merge-base --fork-point upstream-name HEAD. The upstream-name для первого перебазирования составляет origin/master (ну, refs/remotes/origin/master) и upstream-name второй перебазировать составляет master (refs/heads/master).

этой должны все просто получается. Если ваш график фиксации в начале всего процесса похож на то, что вы описали:

... - A - B   <-- master, origin/master
            \
              I - J - K   <-- feature

потом первый fetch вносит некоторые коммиты и делает origin/master укажите на новую подсказку:

              C - D - E   <-- origin/master
            /
... - A - B   <-- master, origin/[email protected]{1}
            \
              I - J - K   <-- feature

и первая ребаза затем не находит ничего, чтобы скопировать (слияние-база master и B-B=fork-point (master, origin / master) - это просто B так что копировать нечего), давая:

              C - D - E   <-- master, origin/master
            /
... - A - B   <-- [email protected]{1}, origin/[email protected]{1}
            \
              I - J - K   <-- feature

вторая выборка от себя и no-op / пропущена полностью, оставив это в качестве входа во вторую ребазу. The --onto цель master что такое commit E и точка развилки HEAD (feature) и master также совершить B, оставив совершает I через K скопировать после E как обычно.

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