Переместить самые последние коммиты в новую ветку с помощью Git - PullRequest
4342 голосов
/ 27 октября 2009

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

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

master A - B - C - D - E

к этому?

newbranch     C - D - E
             /
master A - B 

Ответы [ 13 ]

5521 голосов
/ 27 октября 2009

Переезд в новую ветку

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

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

# 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 (Make sure you know how many commits you need to go back)
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 Вы будете только"терять" коммиты из основной ветки, но не волнуйтесь, у вас будут эти коммиты в новой ветке!

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

Переезд в существующую ветку

Если вы хотите переместить ваши коммиты в существующую ветку , это будет выглядеть так:

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
935 голосов
/ 23 июля 2011

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

Вы хотите вернуться к 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

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

392 голосов
/ 07 февраля 2012

В общем ...

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

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

Шаг 1 - Обратите внимание, какой коммит от мастера вы хотите на newbranch

Выполнить

git checkout master
git log

Обратите внимание на хеши (скажем, 3) коммитов, которые вы хотите newbranch. Здесь я буду использовать:
C commit: 9aa1233
D коммит: 453ac3d
E коммит: 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.

288 голосов
/ 26 марта 2014

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

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

Старая версия - до того, как я узнал о git branch -f

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

Способность push к . - хороший трюк, чтобы знать.

266 голосов
/ 07 апреля 2016

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

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

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's 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, которая использует локальный журнал reflog, чтобы попытаться быть устойчивым к принудительному выталкиванию восходящей ветви.

Предположим, вы разветвились от origin / master, когда он содержал коммиты 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

Используя ваш локальный reflog, 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-base документы.

67 голосов
/ 01 мая 2018

гораздо более простое решение с использованием git stash

Вот гораздо более простой подход к фиксации в неправильной ветке. Начиная с ветви master с тремя ошибочными коммитами:

git reset HEAD~3
git stash
git checkout newbranch
git stash pop

Когда это использовать?

  • Если ваша основная цель - откат master
  • Вы хотите сохранить изменения файла
  • Вам нет дела до сообщений о ошибочных коммитах
  • Вы еще не нажали
  • Вы хотите, чтобы это было легко запомнить
  • Вам не нужны такие сложности, как временные / новые ветки, поиск и копирование хэшей коммитов и другие головные боли

Что это делает, по номеру строки

  1. Отменяет последние три коммита (и их сообщения) на master, но оставляет все рабочие файлы без изменений
  2. Сбрасывает все изменения рабочего файла, делая рабочее дерево master точно равным состоянию HEAD ~ 3
  3. Переключение на существующую ветку newbranch
  4. Применяет сохраненные изменения к вашему рабочему каталогу и очищает хранилище

Теперь вы можете использовать git add и git commit, как обычно. Все новые коммиты будут добавлены в newbranch.

Чего это не делает

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

Цель

ОП заявил, что цель состояла в том, чтобы "вернуть мастера до того, как эти коммиты были сделаны" без потери изменений, и это решение делает это.

Я делаю это, по крайней мере, один раз в неделю, когда я делаю новые коммиты на master вместо develop. Обычно у меня есть только один коммит для отката, в этом случае использование git reset HEAD^ в строке 1 - более простой способ отката только одного коммита.

Не делайте этого, если вы отправили изменения мастера вверх по течению

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

29 голосов
/ 19 октября 2013

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

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)
23 голосов
/ 21 января 2016

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

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

После этого обе ветви можно без усилий толкнуть!

13 голосов
/ 20 сентября 2013

Была только такая ситуация:

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

Я ожидал, что коммит я буду ГОЛОВОМ, но коммит L это сейчас ...

Чтобы быть уверенным, что вы попали в нужное место в истории, проще работать с хэшем коммита

git branch newbranch 
git reset --hard #########
git checkout newbranch
2 голосов
/ 16 мая 2019

Как мне выйти из этого

A - B - C - D - E 
                |
                master

к этому?

A - B - C - D - E 
    |           |
    master      newbranch

С двумя командами

  • git branch -m master newbranch

дает

A - B - C - D - E 
                |
                newbranch

и

  • мастер веток git B

1026 * дает *

A - B - C - D - E
    |           |
    master      newbranch
...