Git: Как сделать ребаз для конкретного коммита?

156

Я хотел бы перебазировать к конкретному коммиту, а не к HEAD другой ветки:

A --- B --- C          master
 \
  \-- D                topic

в

A --- B --- C          master
       \
        \-- D          topic

вместо того

A --- B --- C          master
             \
              \-- D    topic

Как я могу этого достичь?

Ондра Жижка
источник
4
Вы пробовали делать git checkout Bперед запуском git rebase?
Петер-Поль ван Гемерден
Нет, это должно помочь? Я думаю, что только ссылки rebaseкоманды имеют значение.
Ондра Жижка

Ответы:

98

Вы можете избежать использования параметра --onto, сделав временную ветвь для коммита, который вам нравится, и затем используйте rebase в его простой форме:

git branch temp master^
git checkout topic
git rebase temp
git branch -d temp
Адам Димитрук
источник
5
Мне больше нравится этот RISC-подобный подход :) Постараюсь. Спасибо.
Ондра Жижка
10
Интересно, почему это не работает для меня, в несколько ином сценарии . Я хочу, чтобы группа пропустила pep8 и была основана на мастере . git rebase temp(когда в группе ) отказывается с "Текущий группы филиалов в актуальном состоянии."
Алоис Махдал
4
Это решение не будет работать для сценария, в котором тема уже была переназначена на master, но вы хотите перебазировать ее на предка master. В этом случае вы должны использовать, git rebase --onto <target> <from> <to>чтобы вы могли указать коммит <from>.
Мирзмастер
Это, безусловно, самый простой вариант, если вы хотите перебазировать на тот же коммит, на котором основана ветвь.
Эш
1
Кажется, я работаю, но GitLab говорит что-то еще. Если бы у меня было 10 коммитов позади, 5 коммитов вперед, я ожидал, что будет 8 коммитов позади, 5 коммитов вперед после получения 2 коммитов. Но вместо этого он добавляет больше
коммитов
68

Вы даже можете принять прямой подход:

git checkout topic
git rebase <commitB>
r0hitsharma
источник
6
Для меня это не совсем то, что задумано. Насколько я могу судить, он пытается переориентироваться на «последнего общего предка» topicи commitB.
Дэн Ленски
2
@ DanLenski, это не то, как работает rebase. Цитируя документы , It works by going to the common ancestor of the two branches (the one you’re on and the one you’re rebasing onto), getting the diff introduced by each commit of the branch you’re on, saving those diffs to temporary files, resetting the current branch to the same commit as the branch you are rebasing onto, and finally applying each change in turn. я попробовал это снова теперь, и, казалось, работал отлично.
rhhitsharma
1
Супер просто и работает! Теперь у меня есть: commitB_from_master-> topicCommit1-> topicCommit2.
Мартин Коничек
Это не сработало для меня. Перед этим GitLab сказал: «n обязуется вперед». И теперь, он говорит, что "м коммит вперед", где m > n.
ROMANIA_engineer
48

Используйте опцию «на»:

git rebase --onto master^ D^ D
Адам Димитрук
источник
2
Dи D^был бы хэш последнего и предпоследнего коммита "темы"?
Ондра Жижка
39
Синтаксис похож на git rebase --onto <new-parent> <old-parent>. См. Установка указателя родителя git на другого родителя . В вашем случае <new-parent> - это B, а <old-parent> - это A.
jsz
7
Я всегда использую 3 аргумента: desitnation, start и end of commits для перебазирования.
Адам Димитрук
14
Это сработало для меня:git rebase --onto <commit-ID> master
4
Комментарий @ jsz является верным, вопреки комментарию Саймона Юга, наоборот: git rebase --onto master <commit-ID-of-old-parent>и для ОП git rebase --onto B A.
Габорист
19

Комментарий jsz выше спас меня от тонны боли, поэтому вот пошаговый получатель, основанный на нем, который я использовал, чтобы перебазировать / переместить любой коммит поверх любого другого коммита:

  1. Найдите предыдущую точку ветвления ветви, которую нужно перебазировать (переместить) - назовите ее старым родителем. В приведенном выше примере это A
  2. Найдите коммит, поверх которого вы хотите переместить ветку - назовите его новым родителем. На экзамене это Б
  3. Вы должны быть на своей ветке (той, которую вы перемещаете):
  4. Примените ваш ребаз: git rebase --onto <new parent> <old parent>

В приведенном выше примере это так же просто, как:

   git checkout topic
   git rebase --onto B A
Нестор Миляев
источник
6
Это должен быть правильный ответ. Кроме того, что я использую git rebase --onto B master, см. Мой ответ для более подробного объяснения.
Зак Моррис
Это не сработало для меня. Я выбрал 2 последовательных коммита (последний от мастера, который был в текущей ветви, и первый от мастера, которого не было в текущей ветви). Я начал с 100 позади - 10 вперед и вместо 99 позади - 10 вперед, теперь у меня 105 позади - 13 впереди.
ROMANIA_engineer
Жаль слышать, что это не сработало. Похоже, ваши ветви сильно разошлись - я бы посоветовал сначала раздавить, прежде чем пытаться перебазировать ветви с таким количеством различий.
Нестор Миляев
11

Тема Решение

Правильная команда для ответа на заданный вопрос может быть любой из следующих (при условии, что ветвь topicуже извлечена):

git rebase --onto B master
git rebase --onto master~1 master
git rebase --onto B A
git rebase --onto B C
git rebase --onto B

Если topicне отмечено, вы просто добавляете topicкоманду (кроме последней) следующим образом:

git rebase --onto B master topic

В качестве альтернативы, проверьте ветку сначала с:

git checkout topic

Перебазировать любую строку коммитов в целевой коммит

Базовая форма команды, которая нам нужна, взята из документации:

git rebase --onto <Target> [<Upstream> [<Branch>]]

<Branch>не является обязательным, и все, что он делает, это проверяет ветвь, указанную перед выполнением остальной команды. Если вы уже отметили ветку, которую хотите перебазировать, вам это не нужно. Обратите внимание, что вы должны были указать <Upstream>, чтобы указать, <Branch>или git будет думать, что вы указываете<Upstream> .

<Target>это коммит, к которому мы прикрепим нашу строку коммитов. Предоставляя имя ветки, вы просто указываете коммит заголовка этой ветки. <Target>может быть любым коммитом, который не будет содержаться в строке коммитов, которые будут перемещены. Например:

A --- B --- C --- D         master
      \
       \-- X --- Y --- Z    feature

Чтобы переместить всю ветвь функции, вы не можете выбрать X, Y, Zили , featureкак<Target> так как те все коммиты внутри группы перемещения.

<Upstream>особенный, потому что это может означать две разные вещи. Если это коммит, который является предком извлеченной ветви, то он служит точкой вырезания. В этом примере я представил, это было бы ничего , что не C, Dили master. Все коммиты после, <Upstream>пока глава извлеченной ветки не будут перемещены.

Однако если if <Upstream>не является предком, то git выполняет резервное копирование цепочки из указанного коммита до тех пор, пока if не найдет общего предка с извлеченной ветвью (и не прекратит работу, если не может его найти). В нашем случае, <Upstream>из B, C, Dили masterбудет весь результат фиксации , Bвыступающим в качестве точки разреза. <Upstream>сама по себе является необязательной командой, и если она не указана, то git просматривает родителя извлеченной ветви, что эквивалентно вводу master.

Теперь, когда git выбрал коммиты, которые он будет вырезать и перемещать, он применяет их для того, чтобы <Target>пропустить все, которые уже применены к цели.

Интересные примеры и результаты

Используя эту отправную точку:

A --- B --- C --- D --- E         master
            \
             \-- X --- Y --- Z    feature
  • git rebase --onto D A feature
    Будет ли применяться фиксаций B, C, X, Y, Zсовершить Dи в конечном итоге пропуск Bи Cпотому , что они уже были применены.

  • git rebase --onto C X feature
    Будет применять коммиты Yи Zкоммитить C, эффективно удаляя коммитX

Исаак Браун
источник
4

Более простое решение git rebase <SHA1 of B> topic. Это работает независимо от того, где вы HEADнаходитесь.

Мы можем подтвердить это поведение из git rebase doc

<upstream>Восходящая ветка для сравнения. Может быть любой допустимый коммит , а не просто существующее имя ветки. По умолчанию настроен восходящий поток для текущей ветви.


Вы можете подумать, что произойдет, если я упомяну о SHA1 topicтоже в приведенной выше команде?

git rebase <SHA1 of B> <SHA1 of topic>

Это также будет работать, но rebase не будет Topicуказывать на новую созданную ветку и HEADбудет в отдельном состоянии. Отсюда вы должны вручную удалить старую Topicветку и создать новую ссылку на новую ветку, созданную rebase.

Number945
источник
3

Я использовал смесь решений, описанных выше:

$ git branch temp <specific sha1>
$ git rebase --onto temp master topic
$ git branch -d temp

Мне было намного легче читать и понимать. Принятое решение привело меня к конфликту слияний (слишком ленив, чтобы исправить вручную):

$ git rebase temp
First, rewinding head to replay your work on top of it...
Applying: <git comment>
Using index info to reconstruct a base tree...
M       pom.xml
.git/rebase-apply/patch:10: trailing whitespace.
    <some code>
.git/rebase-apply/patch:17: trailing whitespace.
        <some other code>
warning: 2 lines add whitespace errors.
Falling back to patching base and 3-way merge...
Auto-merging pom.xml
CONFLICT (content): Merge conflict in pom.xml
error: Failed to merge in the changes.
Patch failed at 0001 <git comment>
The copy of the patch that failed is found in: .git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
малат
источник
1
то же самое здесь, у нескольких файлов были конфликты, когда я использовал 2 самых популярных ответа (т.е. от r0hitsharma и Dymitruk)
Oliver
3

Поскольку перебазирование является настолько фундаментальным, вот расширение ответа Нестора Миляева . Объединение комментариев jsz и Simon South из ответа Адама Димитрука дает команду, которая работает на topicветви независимо от того, ветвится ли она из masterкоммита ветви Aили C:

git checkout topic
git rebase --onto <commit-B> <pre-rebase-A-or-post-rebase-C-or-base-branch-name>

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

Примеры:

# if topic branches from master commit A:
git checkout topic
git rebase --onto <commit-B> <commit-A>
# if topic branches from master commit C:
git checkout topic
git rebase --onto <commit-B> <commit-C>
# regardless of whether topic branches from master commit A or C:
git checkout topic
git rebase --onto <commit-B> master

Поэтому последняя команда - это та, которую я обычно использую.

Зак Моррис
источник
-2

Есть другой способ сделать это, или если вы хотите вернуться к более чем одному коммиту.

Вот пример возврата к nколичеству коммитов:

git branch topic master~n

Ради этого вопроса это также можно сделать:

git branch topic master~1

Команда отлично работает на git version 2.7.4. Не проверял ни на одной другой версии.

Талха Ашраф
источник
Вы неверно истолковали это как вопрос о ветвлении? Это на самом деле вопрос о перебазировании.
NetherGranite