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

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

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

master A - B - C - D - E

к этому?

newbranch     C - D - E
             /
master A - B 

Ответ 1

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

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

git checkout existingbranch
git merge master         # Bring the commits here
git checkout master
git reset --keep HEAD~3  # Move master back by 3 commits.
git checkout existingbranch

Параметр --keep сохраняет любые незафиксированные изменения, которые могут быть у вас в несвязанных файлах, или отменяет их, если эти изменения придется перезаписать - аналогично тому, что делает git checkout. Если он прерывается, git stash внесите изменения и повторите попытку или используйте --hard, чтобы потерять изменения (даже из файлов, которые не изменились между фиксациями!)

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

Этот метод работает, создавая новую ветку с первой командой (git branch newbranch), но не переключаясь на нее. Затем мы откатываем текущую ветку (master) и переключаемся на новую ветку, чтобы продолжить работу.

git branch newbranch      # Create a new branch, containing all current commits
git reset --keep 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
# Warning: after this it not safe to do a rebase in newbranch without extra care.

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

git reset --keep a1b2c3d4

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

Ответ 2

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

Вы хотите вернуться на 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, он все же указывает на фиксацию, которую она изначально сделала.

Ответ 3

В общем...

В этом случае наилучшим вариантом является метод, открытый 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.

Ответ 4

Еще один способ сделать это, используя всего 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 к . хороший трюк, чтобы знать.

Ответ 5

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

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

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 [email protected]{2}
  • Сначала он отбрасывает 3 последних коммита (--keep похож на --hard, но более безопасен, так как он терпит неудачу, а не выбрасывает незафиксированные изменения).
  • Затем он отключает newbranch.
  • Затем он вишневый выбирает, что эти 3 берет обратно на newbranch. Поскольку они больше не ссылаются на ветку, это делает это с помощью git reflog: [email protected]{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- базы.

Ответ 6

Намного более простое решение с использованием 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 является более простым способом отката только одного коммита.

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

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

Ответ 7

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

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)

Ответ 8

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

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

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

Ответ 9

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

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

Ответ 10

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

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 Branch Master B

дающий

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

Ответ 11

1) Создайте новую ветку, которая переместит все ваши изменения в new_branch.

git checkout -b new_branch

2) Затем вернитесь к старой ветке.

git checkout master

3) сделать git rebase

git rebase -i <short-hash-of-B-commit>

4) Затем открытый редактор содержит информацию о последних 3 коммитах.

...
pick <C hash> C
pick <D hash> D
pick <E hash> E
...

5) Измените pick чтобы drop все эти 3 коммита. Затем сохраните и закройте редактор.

...
drop <C hash> C
drop <D hash> D
drop <E hash> E
...

6) Теперь последние 3 коммита удалены из текущей ветки (master). Теперь принудительно нажмите на ветку, со знаком + перед именем ветки.

git push origin +master

Ответ 12

Вы можете сделать это всего за 3 простых шага, которые я использовал.

1) Создайте новую ветку, где вы хотите зафиксировать последние обновления.

git branch <branch name>

2) Найти недавний коммит-идентификатор для коммита на новой ветке.

git log

3) Скопируйте это примечание id коммита, что Список последних коммитов находится сверху. так что вы можете найти свой коммит. Вы также найдете это через сообщение.

git cherry-pick d34bcef232f6c...

Вы также можете указать некоторый диапазон идентификатора коммита.

git cherry-pick d34bcef...86d2aec

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

Теперь вы можете нажать свой код

git push

Ответ 13

Если вам просто нужно переместить все ваши невыполненные коммиты в новую ветку, то вам просто нужно,

  1. создать новую ветку из текущей: git branch new-branch-name

  2. нажмите вашу новую ветку: git push origin new-branch-name

  3. вернуть вашу старую (текущую) ветвь в последнее git reset --hard origin/old-branch-name/стабильное состояние: git reset --hard origin/old-branch-name

Некоторые люди также имеют другие upstreams а не origin, они должны использовать соответствующие upstream