TL; DR
Ответ Калума Халпина правильный: вы должны разбить ваш git pull
на две отдельные составляющие команды: git fetch
, а затем git rebase
. Это, вероятно, заслуживает большего объяснения.
Длинный
Позвольте мне немного перерисовать ваши графики, чтобы они выглядели (я думаю, в любом случае) немного яснее. Я собираюсь заменить внутренние стрелки, указывающие назад, на более простые соединительные черты, потому что мне нужно нарисовать некоторые «стрелки», которые указывают вверх и влево или вниз и влево, и это не очень хорошо работает Вот. (У меня есть символы рисования стрелок в моей системе, которые не всегда отображаются в других веб-браузерах в других системах.)
На пульте у нас есть:
A--B--C--D--E--F <-- master
То есть есть цепочка коммитов с некоторыми большими уродливыми идентификаторами ha sh, которые мы для удобства заменили буквами, которые заканчиваются коммитом F
. Их Git имя master
содержит га sh ID коммита F
. (Имя master
может быть или не быть текущей веткой на пульте: это не имеет значения для наших целей, поэтому нам не нужно рисовать здесь специальное имя HEAD
.)
Между тем, у нас это локально:
A--B--C--D--G <-- feature (HEAD)
т.е. мы и они совместно используем коммиты A
через D
, включая их связь, и наше имя Git feature
содержит га sh Идентификатор коммита G
, который указывает на D
.
Я хочу подтянуть к E с ребазой, чтобы моя ветвь в конечном итоге выглядела как:
A--B--C--D--E--G <-- feature (HEAD)
Однако это всего лишь особый случай. Я хочу выбрать любой подходящий коммит ha sh, а не просто второй, последний, как в примере выше.
Вам нужно сделать избегать git pull
command.
То, что делает git pull
, запускает две more-basi c Git команды: сначала git fetch
, затем вторая команда на ваш выбор. Вторая команда обычно git merge
, но если вы используете --rebase
или другие различные методы конфигурации, вы можете вместо этого запустить git rebase
. То, что вы не можете сделать, это передать правильные аргументы git rebase
, где под "правильными аргументами" я подразумеваю те, которые решают данную проблему.
Давайте запустить git fetch
первым. Это наш Git позвонить их Git. Их Git сообщает нашему Git о его различных ветвях, тегах и других подобных именах, включая тот факт, что их master
идентифицирует commit F
. Наш Git проверяет хранилище и обнаруживает, что у нас нет коммита F
, поэтому наш Git просит его получить. Затем их Git также предлагает совершить E
- отправитель имеет , чтобы предложить все, что нам нужно, и нам понадобится E
для хранения F
- и наш Git просит об этом тоже. Они предлагают D
, но у нас уже есть это, поэтому мы говорим им остановиться на этом.
Теперь они создают так называемый тонкий пакет , который содержит то, что нам нужно для добавления коммитов E
и F
в наш репозиторий. Это «подсчет объектов» и «сжатие объектов», которые вы видите, когда запускаете git fetch
, или любую команду, которая запускает git fetch
. Они отправляют нам тонкий пакет, наш Git берет этот тонкий пакет и исправляет его так, что его можно использовать, и теперь у нас есть их коммиты, которых у нас нет, что нам нужно завершить sh git fetch
-ing.
Если нам больше ничего не нужно, наш Git говорит 'kthanksbye своим Git и продолжает обновлять наши имена удаленного отслеживания . В наших Git именах origin/master
и так далее: это где наши Git помнят, что их Git сказали их га sh идентификаторов были, для их имен филиалов, последние время, когда мы говорили с ними. Пока у нас есть коммиты, которые помнят их ветки , наш Git может соответственно обновлять наши имена для удаленного отслеживания, так что это делает. Это оставляет нам:
A--B--C--D--G <-- feature (HEAD)
\
E--F <-- origin/master
(есть указательная стрелка вверх и влево, идущая от E
до D
, нарисованная без использования стрелки символы.)
Теперь мы готовы запустить git rebase
.
Если мы только что запустили git rebase
, как git pull
делает
Если мы запустили git rebase origin/master
- git pull
, на самом деле использует `git rebase, но это делает то же самое - Git будет:
- выводит список коммитов, доступных с нашей текущей
HEAD
: G
, D
, C
и т. Д .; - выводит список коммитов, доступных с
origin/master
: F
, E
, D
, C
и т. Д .; - удалить из первого списка все элементы из второго списка;
- удалить все дополнительные коммиты, которые должны не копировать; 1
- расположить список в правильном порядке (в обратном порядке от внутреннего обратного порядка Git); и
- начинают копировать эти коммиты, один за другим, как если бы они были
git cherry-pick
. 2
, так как этот список состоит только из коммита G
, это тот коммит, который мы скопируем. Но: Где эта копия go?
Обычный git rebase
помещает копию, или, если имеется несколько коммитов для копирования, копирует множественное число, сразу после фиксации назван в командной строке . Так как git rebase
как выполняется git pull
names commit F
- фиксация, на которую наши origin/master
очки, после git fetch
обновили нашу origin/master
для соответствия origin
s master
- мы получим :
A--B--C--D--G
\
E--F <-- origin/master
\
G'
в результате. (Я удалил некоторые имена, потому что на данный момент имена не очень интересны, и git rebase
тоже будет с ними возиться; мы просто еще не нарисовали эту часть.)
Если бы было больше коммитов, например, G-H-I
, мы бы получили G'-H'-I'
в нижнем ряду. Во всех случаях оригинал фиксирует, предварительно скопировав, остается. На этом этапе git rebase
завершает свою работу, перемещая имя , к которому прикреплен наш HEAD
, чтобы указать на окончательный скопированный коммит: 3
A--B--C--D--G [was `feature`, now abandoned]
\
E--F <-- origin/master
\
G' <-- feature (HEAD)
1 В зависимости от аргумента git rebase
обычно включает все коммиты слияния плюс любые коммиты, для которых git patch-id
вычисляет идентичные идентификаторы патчей. Часть вычисления идентификатора патча немного сложна для описания: она включает использование git rev-list --left-right
с оператором симметрии c разности тройных точек. Для многих возвратов ни одно из этих вопросов не имеет значения, поэтому у меня просто есть эта сноска.
2 Некоторые типы git rebase
буквально запускаются git cherry-pick
. Другие, включая стандартный по умолчанию и один, запускаемый git pull
, используют более быстрый, но более хитрый механизм, включающий git format-patch
и git am
, который может пропустить операции переименования. Вы можете принудительно выполнить перебазирование, чтобы использовать более медленный, но более точный метод выбора вишни, добавив -m
, или выполнив интерактивную перебазировку, или добавив другие параметры. Однако в этом редко есть какая-либо реальная необходимость.
3 Технически Git запускал каждую из операций копирования с отсоединенным HEAD , в котором HEAD
указывает непосредственно к копии, как только копия сделана. Но, конечно, git rebase
начинает с сохранения факта вложения - того факта, что HEAD
был присоединен к feature
, - так что, когда перебазирование заканчивается, Git знает, чтобы (1) переместиться feature
и (2) повторно прикрепите HEAD
.
Что вы хотите сделать вместо этого: укажите, где находятся копии go
Если вы запустите git rebase
самостоятельно вы можете выбрать аргумент, который git rebase
вызывает upstream . Когда git pull
делает это, он дает git rebase
идентификатор ha sh, на который указывает ваш обновленный origin/master
. Если вы сделаете это с:
git rebase origin/master
, вы дадите ему имя origin/master
, которое будет преобразовано в тот же идентификатор ha sh, и вы получите результат, который мы видели.
Но если вы do запускаете это вручную, , вы можете ввести идентификатор ha sh или что-нибудь еще, что называет коммит, который вы хотите. Это сообщает git rebase
, где находятся копии go.
В этом случае, затем, если вы называете commit E
любым способом - raw ha sh ID или origin/master^
или origin/master~
сработает - ваш git rebase
скопирует G
в G'
, который следует после E
:
A--B--C--D--G [was `feature`, now abandoned]
\
E--F <-- origin/master
\
G' <-- feature (HEAD)
, и вы получите желаемый результат.
Для вас теперь доступна еще одна ручка управления
Когда вы запускаете git rebase
вручную, вместо того, чтобы git pull
делал это за вас, вы получаете еще одну опцию. Посмотрите еще раз на перечисленный выше список пунктов, где git rebase
выясняет, кто обязуется копировать. Если вы запускаете:
git rebase <upstream>
Git перечисляет коммиты, как если бы: 4
git log <upstream>..HEAD
(как показано в the git rebase
документация ; при необходимости добавьте --fork-point
и см. сноску 4).
Затем он копирует перечисленные коммиты, используя upstream
в качестве цели для копирования. Но что, если у вас есть, например:
...--B--C--D--E--F <-- branch (HEAD)
\
G--H--I <-- origin/master
, где commit D
был аварийным хакерским исправлением, которое вы сделали, чтобы можно было писать коммиты E
и F
, а между тем кто-то написал реальное исправление как commit G
, H
и / или I
?
В то время как git rebase
пытается уметь пропускать коммиты, которые уже находятся в восходящем потоке - например, если commit G
соответствует commit D
, git rebase
знает, что не нужно копировать D
- это работает не во всех случаях. В частности, обычно не будет работать, чтобы удалить аварийное «исправление», которое фактически просто отключило или устранило функцию, вместо того, чтобы действительно исправить ошибку в функции.
Вы можете использовать git rebase -i
чтобы справиться с этим, но задолго до git rebase -i
было git rebase --onto
. С --onto
вы получаете отщепление целевого выбора из ограничивающего восходящего аргумента.
То есть, на этой диаграмме мы хотим, чтобы в результате мы скопировали только фиксирует E
и F
, оставляя D
- наше экстренное исправление, которое не совсем верно - позади. Чтобы сообщить Git об этом, мы используем git rebase --onto
:
git rebase --onto origin/master <hash-of-D>
или:
git rebase --onto origin/master branch~2
Наш upstream
аргумент теперь именует commit D
, Это коммит , а не для копирования (или какой-либо из предыдущих коммитов).
Если мы запустили git rebase
, как это, но без аргумента --onto
, Git (a) не будет копировать D
, но тогда (b) разместит копии E
и F
сразу после D
. В результате мы не хотим (нарисуйте это, чтобы увидеть). Но когда мы добавляем --onto origin/master
, это говорит rebase помещать копии после коммита I
. Результат:
...--B--C--D--E--F [abandoned]
\
G--H--I <-- origin/master
\
E'-F' <-- branch (HEAD)
Все коммиты D-E-F
отброшены, в пользу новых и улучшенных E'-F'
коммитов. Нам не нужно вручную отбрасывать D
, поскольку наши git rebase
аргументы сделали это для нас. Перерисовка цепочки с невидимыми оставленными коммитами дает нам:
...--B--C--G--H--I <-- origin/master
\
E'-F' <-- branch (HEAD)
, и если никто, кроме нас, никогда не знал о коммитах E
и F
, мы можем просто притвориться , что мы Только написал новые копии, а не оригиналы: никто (кроме нас) никогда не узнает. 5
4 Попробуйте сами! Вы получите список идентификаторов ha sh, по одному на строку. Они выходят в предпочтительном порядке Git - назад - что не подходит для git rebase
, и они не пропускают коммиты, которые git rebase
пропустит. Тем не менее, фактически, rebase использует внутренне git rev-list
, просто добавляет множество опций: --no-merges
для удаления коммитов слияния и --topo-order --reverse
для получения правильного порядка. Наконец, есть немного волхвов c для исключения коммитов, которые имеют те же идентификаторы патчей, что и коммиты на стороне восходящего потока, как отмечено в сноске 1. Это включает использование трехточечного синтаксиса <upstream>...HEAD
и добавление --right-only --cherry-pick
. Когда rebase был сценарием оболочки, это было легко найти; теперь, когда он был переписан в коде C, его гораздо сложнее выяснить.
Когда действует опция точки разветвления, параметр <upstream>
здесь заменяется результатом git merge-base --fork-point
, который использует ваш origin/master
reflog, чтобы угадать, следует ли пропустить некоторые коммиты. См., Например, Выбор редакции в git с использованием точки разворота и Что означает `git rebase --fork-point master`? .
Я все еще несколько не уверен, что режим с точечной точкой является правильным по умолчанию (иногда это может быть удивительно), и я пока не уверен, использует ли новая опция Git 2.24 --keep-base
базовую точку слияния с типом вилки, или реальную объединить базу. Но обратите внимание, что если вы перебазируете, используя что-то, что не является name - например , если ваш аргумент rebase upstream
является идентификатором ha sh, который отключает режим точки разветвления, поскольку основание точки разветвления вычисляется путем сканирования рефлога, и только names есть reflogs.
5 И мы, вероятно, забудем. Кто может вспомнить raw ха sh идентификаторы?
Выводы
Git на самом деле все о совершает . Имена ветвей, когда вы их используете - и когда Git использует их - как раз для того, чтобы помочь вам найти коммит last в некоторой ветке. Коммиты навсегда замораживаются и (в основном) постоянны (если вы не можете найти их, по филиалам или другим именам, они в конечном итоге go удаляются).
Названия ветвей move . Названия веток давайте и Git найдем коммиты. Они движутся предсказуемым образом, добавляя коммитов к ветви. Некоторые операции, такие как git rebase
или git reset
, перемещают их большими внезапными способами и, возможно, «менее естественными» способами, а не просто продвигаются, чтобы включать больше коммитов.
git rebase
означает копирование коммитов. Мы создаем новые и улучшенные копии с различными идентификаторами ha sh и делаем имя ветви, указывающее на последний скопированный коммит. Оригиналы не могут быть изменены, но если вы переместите ветку name , любой, кто не сохранил идентификаторы ha sh оригиналов, не сможет найти оригиналы.
git fetch
- это две вещи: получение новых коммитов от другого Git и обновление имен удаленного слежения, таких как origin/master
основанный на том, что находится в том другом Git. Если мы получили новые коммиты, все, что нам нужно помнить их на этом этапе, - это имена для удаленного отслеживания, поэтому нам обычно нужна вторая команда.
git pull
предназначен для удобства: он запускает git fetch
, затем запускает вторую команду, обычно git merge
.
Иногда это не удобно , Я на самом деле считаю, что это неудобно чаще, чем удобно. В этом случае просто не используйте его .