it-roy-ru.com

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

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

То есть Как я могу пойти от этого

master A - B - C - D - E

к этому?

newbranch     C - D - E
             /
master A - B 
4108
Mark A. Nicolosi

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

ПРЕДУПРЕЖДЕНИЕ: Этот метод работает, потому что вы создаете новую ветку с первой командой: 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 (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 Вы будете only «проигрывать» коммиты из ветки master, но не волнуйтесь, эти коммиты будут в новой ветке!

ПРЕДУПРЕЖДЕНИЕ: В 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
5244
sykora

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

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

893
Ryan Lundy

В общем...

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

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

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

Execute

git checkout master
git log

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

372
Ivan

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

275
aragaer

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

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

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

Предположим, вы разветвляли 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 как разветвленную ветку upstream в ревизии, которая включает в себя 3 коммита, затем reset --hard переписывает историю восходящего потока для удаления коммитов, и поэтому в следующий раз, когда вы запускаете git rebase, он отбрасывает их, как и любой другой коммит, который был удален из вверх по течению.

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

Для получения дополнительной информации см. Определение --fork-point в git rebase и git merge-base docs.

251
John Mellor

Гораздо проще решение с использованием git stash

Если: 

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

Тогда следующее гораздо проще (начиная с ветви master, в которой три ошибочных коммита):

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

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

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

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

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

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

Цели

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

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

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

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

46
Slam

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

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)
28
Sukima

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

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

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

21
teh_senaus

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

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

Я ожидал этого коммита, я был бы ГОЛОВОМ, но коммит Л теперь ...

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

git branch newbranch 
git reset --hard #########
git checkout newbranch
12
Darkglow

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's hash> C
pick <D's hash> D
pick <E's hash> E
...

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

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

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

git Push Origin +master
2
rashok

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

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
1
user3070485

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

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

git branch <branch name> 

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

git log

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

git cherry-pick d34bcef232f6c... 

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

git cherry-pick d34bcef...86d2aec

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

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

git Push

0
Pankaj Kumar