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

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

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

master A - B - C - D - E

для этого?

newbranch     C - D - E
             /
master A - B 

11 ответов


переход на новую ветку

предупреждение: этот метод работает, потому что вы создаете новую ветку с первой команды: git branch newbranch. Если вы хотите переместить фиксации в существующий филиал перед выполнением необходимо объединить изменения в существующую ветку--4--> (см. перемещение в существующую ветку ниже). если вы не объедините свои изменения сначала, они будут потерянный.

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

# 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) вы хотите "вернуться обратно" на мастер (/current) филиал, e.g:

git reset --hard a1b2c3d4

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

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

перемещение в существующую ветку

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

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

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

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


В Целом...

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

для достижения того, что OP хочет, его 2-шаговый процесс:

Шаг 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.


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


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

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

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

Предположим, вы разветвили 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

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

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

для получения более подробной информации см. Определение --fork-point на git rebase и git merge-base документы.


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

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)

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

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

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


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

если:

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

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

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

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

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

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

что это не

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

цели

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

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

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

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


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

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

Я ожидал, что commit I будет главой, но commit L-это сейчас...

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

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

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 to drop во всех этих 3 совершает. Затем сохраните и закройте редактор.

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

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

git push origin +master

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

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

git branch <branch name>

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

git log

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

git cherry-pick d34bcef232f6c...

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

git cherry-pick d34bcef...86d2aec

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

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

git push