Потяните с rebase до указанного c коммита - PullRequest
0 голосов
/ 01 февраля 2020

Это вариант старого вопроса с поворотом перебазировки.

Я хочу сделать git pull --rebase, но только до указанного c коммита. Это не получение указанного c коммита, это получение до указанного c коммита. Удаленный мастер выглядит следующим образом.

A<-B<-C<-D<-E<-F<-HEAD (Remote master HEAD)

Предположим, что моя локальная ветвь функции HEAD указывает на G, которая указывает на D:

A<-B<-C<-D<-G<-HEAD (Current local feature branch HEAD).

Я хочу подтянуть к E с ребазом, чтобы моя ветвь в конечном итоге выглядела так:

A<-B<-C<-D<-E<-G<-HEAD (local feature branch end goal).

Однако это всего лишь особый случай. Я хочу выбрать любой подходящий коммит ha sh, а не только второй, последний, как в примере выше.

Естественно, я бы хотел, чтобы ha sh для коммита E совпадал с удаленным мастером на конец операции. Я придерживаюсь этого мнения, потому что некоторые типы интерактивного редактирования ребаз могут привести к тому, что это свойство исчезнет.

Ответы [ 3 ]

3 голосов
/ 01 февраля 2020

Получить изменения с пульта:

git fetch origin

Перебазировать на удаленную версию мастера, игнорируя некоторое количество коммитов:

git rebase origin/master~<n>

, где <n> - это число коммиты из кончика master, которые вы хотите игнорировать.

Если у вас есть идентификатор коммита, на который вы хотите выполнить перебазирование, вы можете использовать его вместо:

git rebase <commit-id>
2 голосов
/ 01 февраля 2020

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.

  • Иногда это не удобно , Я на самом деле считаю, что это неудобно чаще, чем удобно. В этом случае просто не используйте его .

0 голосов
/ 01 февраля 2020

попробуйте интерактивную перебазировку:

git rebase -i e3f8704

e3f8704 - ваш код коммита sh.

...