Переместите последние фиксации (ов) в новую ветку с помощью Git

Я хотел бы переместить последние несколько коммитов, которые я совершил, чтобы овладеть новой веткой и вернуть мастера обратно до того, как эти коммиты были сделаны. К сожалению, мой Git -fu еще недостаточно силен, любая помощь?

т.е. Как я могу перейти от этого

master A - B - C - D - E

к этому?

newbranch     C - D - E
             /
master A - B 
3467
задан Mark A. Nicolosi 27 окт. '09 в 6:07
источник поделиться
8 ответов

Переход к новой ветке

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

# Note: Any changes not committed will be lost.
git branch newbranch      # Create a new branch, saving the desired commits
git reset --hard HEAD~3   # Move master back by 3 commits (GONE from master)
git checkout newbranch    # Go to the new branch that still has the desired commits

Но удостоверьтесь, сколько коммитов вернуться. В качестве альтернативы вместо HEAD~3 вы можете просто указать хэш фиксации (или ссылку типа origin/master), которую вы хотите "вернуться обратно" в ветвь master (/current), например:

git reset --hard a1b2c3d4

* 1 только будет "потерять" фиксацию из главной ветки, но не беспокойтесь, у вас будут эти коммиты в newbranch!

ПРЕДУПРЕЖДЕНИЕ: При использовании Git версии 2.0 и более поздних версий, если позже git rebase новая ветка на исходной ветки (master), вам может понадобиться явная опция --no-fork-point во время rebase, чтобы избежать потери переносимых коммитов. Наличие branch.autosetuprebase always set делает это более вероятным. Подробнее см. John Mellor.

Переход к существующей ветке

ПРЕДУПРЕЖДЕНИЕ: Метод выше работает, потому что вы создаете новую ветвь с первой командой: git branch newbranch. Если вы хотите использовать существующую ветку, перед выполнением git reset --hard HEAD~3 вам необходимо объединить свои изменения в существующую ветку. Если вы сначала не объедините свои изменения, они будут потеряны. Итак, если вы работаете с существующей ветвью, это будет выглядеть так:

git checkout existingbranch
git merge master
git checkout master
git reset --hard HEAD~3 # Go back 3 commits. You *will* lose uncommitted work.
git checkout existingbranch
4382
ответ дан sykora 27 окт. '09 в 6:15
источник поделиться

Для тех, кто задается вопросом, почему он работает (как я был вначале):

Вы хотите вернуться на C и переместить D и E в новую ветку. Вот как это выглядит сначала:

A-B-C-D-E (HEAD)
        ↑
      master

После git branch newBranch:

    newBranch
        ↓
A-B-C-D-E (HEAD)
        ↑
      master

После git reset --hard HEAD~2:

    newBranch
        ↓
A-B-C-D-E (HEAD)
    ↑
  master

Так как ветка - это просто указатель, мастер указал на последнюю фиксацию. Когда вы создали newBranch, вы просто сделали новый указатель на последнюю фиксацию. Затем, используя git reset, вы переместили главный указатель назад на две фиксации. Но поскольку вы не перемещали newBranch, он все же указывает на фиксацию, которую она изначально сделала.

801
ответ дан Ryan Lundy 23 июля '11 в 1:37
источник поделиться

В общем...

В этом случае наилучшим вариантом является метод, открытый sykora. Но иногда это не самый простой и не общий метод. Для общего метода используйте git cherry-pick:

Чтобы достичь желаемого OP, это двухэтапный процесс:

Шаг 1 - примечание, которое фиксирует требуемый мастер на newbranch

Выполнить

git checkout master
git log

Обратите внимание, что хэши (скажем, 3) обязывают вас на newbranch. Здесь я буду использовать:
C commit: 9aa1233
D commit: 453ac3d
E commit: 612ecb3

Примечание.. Вы можете использовать первые семь символов или весь хеш фиксации

Шаг 2 - Поместите их на newbranch

git checkout newbranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233

ИЛИ (в git 1.7.2+, используйте диапазоны)

git checkout newbranch
git cherry-pick 612ecb3~1..9aa1233

git cherry-pick применяет эти три фиксации к newbranch.

315
ответ дан Ivan 07 февр. '12 в 19:58
источник поделиться

Еще один способ сделать это, используя только 2 команды. Также сохраняет ваше текущее рабочее дерево целым.

git checkout -b newbranch # switch to a new branch
git push . +HEAD~3:master # make master point to some older commit 

Возможность push to . - это хороший трюк.

Позднее редактирование: Теперь, когда я знаю о git branch -f, правильный способ сделать это:

git checkout -b newbranch # switch to a new branch
git branch -f master HEAD~3 # make master point to some older commit 

То же самое, но менее "волшебное"

235
ответ дан aragaer 26 марта '14 в 11:13
источник поделиться

Большинство предыдущих ответов опасно ошибочны!

НЕ делайте этого:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

Как в следующий раз, когда вы запустите git rebase (или git pull --rebase), эти 3 фиксации будут отброшены без изменений из newbranch! (см. пояснение ниже)

Вместо этого сделайте следующее:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
  • Сначала он отбрасывает 3 последних коммита (--keep похож на --hard, но более безопасен, так как он терпит неудачу, а не выбрасывает незафиксированные изменения).
  • Затем он отключает newbranch.
  • Затем он вишневый выбирает, что эти 3 берет обратно на newbranch. Поскольку они больше не ссылаются на ветку, это делает это с помощью git reflog: HEAD@{2} является фиксацией, которую HEAD используется для ссылки до 2-х операций назад, т.е. до того, как мы 1. проверили newbranch и 2. использовали git reset, чтобы отбросить 3 коммита.

Предупреждение: reflog включен по умолчанию, но если вы отключили его вручную (например, используя "голый" репозиторий git), вы не сможете получить 3 фиксации после запуска git reset --keep HEAD~3.

Альтернативой, которая не полагается на reflog, является:

# newbranch will omit the 3 most recent commits.
git checkout -b newbranch HEAD~3
git branch --set-upstream-to=oldbranch
# Cherry-picks the extra commits from oldbranch.
git cherry-pick ..oldbranch
# Discards the 3 most recent commits from oldbranch.
git branch --force oldbranch oldbranch~3

(если вы предпочитаете писать @{-1} - ранее выданную ветку - вместо oldbranch).


Техническое объяснение

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

Предположим, что вы разветвлялись от источника/хозяина, когда он содержал коммиты M1, M2, M3, а затем сделал три фиксации:

M1--M2--M3  <-- origin/master
         \
          T1--T2--T3  <-- topic

но затем кто-то переписывает историю с помощью принудительного толчка origin/master для удаления M2:

M1--M3'  <-- origin/master
 \
  M2--M3--T1--T2--T3  <-- topic

Используя ваш локальный рефлок, git rebase может видеть, что вы разветвлялись из более раннего воплощения ветки origin/master и, следовательно, коммиты M2 и M3 на самом деле не являются частью вашей ветки темы. Следовательно, он разумно предполагает, что, поскольку M2 был удален из ветки восходящего потока, вы больше не хотите его в своей ветки темы либо после того, как ветка темы будет пересоздана:

M1--M3'  <-- origin/master
     \
      T1'--T2'--T3'  <-- topic (rebased)

Такое поведение имеет смысл, и, как правило, это правильно делать при перезагрузке.

Итак, причина, по которой выполняются следующие команды:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

заключается в том, что они оставляют reflog в неправильном состоянии. git видит newbranch как разветвленную ветвь вверх по ревизии, которая включает в себя 3 коммита, затем reset --hard перезаписывает историю восходящего потока, чтобы удалить коммиты, и поэтому в следующий раз при запуске git rebase он отбрасывает их как любой другой фиксатор, который был удален из восходящего потока.

Но в этом конкретном случае мы хотим, чтобы эти 3 фиксации рассматривались как часть ветки темы. Чтобы достичь этого, нам нужно отбросить вверх по течению при более раннем пересмотре, который не включает 3 коммита. Это то, что мои предлагаемые решения делают, поэтому они оба оставляют reflog в правильном состоянии.

Подробнее см. определение --fork-point в git rebase и git merge- базы.

179
ответ дан John Mellor 07 апр. '16 в 1:38
источник поделиться

Это не "перемещает" их в техническом смысле, но имеет такой же эффект:

A--B--C  (branch-foo)
 \    ^-- I wanted them here!
  \
   D--E--F--G  (branch-bar)
      ^--^--^-- Opps wrong branch!

While on branch-bar:
$ git reset --hard D # remember the SHAs for E, F, G (or E and G for a range)

A--B--C  (branch-foo)
 \
  \
   D-(E--F--G) detached
   ^-- (branch-bar)

Switch to branch-foo
$ git cherry-pick E..G

A--B--C--E'--F'--G' (branch-foo)
 \   E--F--G detached (This can be ignored)
  \ /
   D--H--I (branch-bar)

Now you won't need to worry about the detached branch because it is basically
like they are in the trash can waiting for the day it gets garbage collected.
Eventually some time in the far future it will look like:

A--B--C--E'--F'--G'--L--M--N--... (branch-foo)
 \
  \
   D--H--I--J--K--.... (branch-bar)
26
ответ дан Sukima 19 окт. '13 в 17:12
источник поделиться

Чтобы сделать это без перезаписи истории (т.е. если вы уже нажали на коммиты):

git checkout master
git revert <commitID(s)>
git checkout -b new-branch
git cherry-pick <commitID(s)>

Обе ветки могут быть нажаты без усилий!

17
ответ дан teh_senaus 21 янв. '16 в 19:10
источник поделиться

Если бы такая ситуация:

Branch one: A B C D E F     J   L M  
                       \ (Merge)
Branch two:             G I   K     N

Я выполнил:

git branch newbranch 
git reset --hard HEAD~8 
git checkout newbranch

Я ожидал, что фиксация будет HEAD, но commit L это сейчас...

Чтобы приземлиться в нужном месте в истории, его легче работать с хешем фиксации

git branch newbranch 
git reset --hard #########
git checkout newbranch
11
ответ дан Darkglow 20 сент. '13 в 13:17
источник поделиться

Другие вопросы по меткам