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


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

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

master A - B - C - D - E

к этому?

newbranch     C - D - E
             /
master A - B 

3679


источник


Ответы:


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

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

# 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, просто укажите хэш фиксации (или ссылку как Происхождение / мастер ) вы хотите «вернуться обратно» на мастер (/ current), например:

git reset --hard a1b2c3d4

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

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

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

ПРЕДУПРЕЖДЕНИЕ: Метод выше работает, потому что вы создаете новая ветка с первой командой: 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

4670



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

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


834



В целом...

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

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

Шаг 1 - Заметка, которая фиксируется от мастера, которого вы хотите на newbranch

казнить

git checkout master
git log

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

Заметка: Вы можете использовать первые семь символов или   весь хеш фиксации

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

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

OR (на Git 1.7.2+, диапазоны использования)

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

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


335



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

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

Быть способным pushв .это хороший трюк.

Позднее редактирование: Теперь, когда я знаю о 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 

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


245



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

Не делай это:

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

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

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

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

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

Более подробно см. Определение --fork-pointв git rebase а также git merge-base Docs.


203



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

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)

27



To do this without rewriting history (i.e. if you've already pushed the commits):

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

Both branches can then be pushed without force!


18



Had just this situation:

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

I performed:

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

I expected that commit I would be the HEAD, but commit L is it now...

To be sure to land on the right spot in the history its easier to work with the hash of the commit

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

12