Я хотел бы переместить последние несколько коммитов, которые я совершил, чтобы овладеть новой веткой и вернуть мастера обратно до того, как эти коммиты были сделаны. К сожалению, мой Git -fu еще недостаточно силен, любая помощь?
т.е. Как я могу перейти от этого
master A - B - C - D - E
к этому?
newbranch C - D - E
/
master A - B
Если нет других обстоятельств, это может быть легко сделано путем ветвления и откат.
# 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 только будет "потерять" фиксацию из главной ветки, но не беспокойтесь, у вас будут эти коммиты в newbranch!
ПРЕДУПРЕЖДЕНИЕ: При использовании Git версии 2.0 и более поздних версий, если позже git rebase
новая ветка на исходной ветки (master
), вам может понадобиться явная опция --no-fork-point
во время rebase, чтобы избежать потери переносимых коммитов. Наличие branch.autosetuprebase always
set делает это более вероятным. Подробнее см. John Mellor.
ПРЕДУПРЕЖДЕНИЕ: Метод выше работает, потому что вы создаете новую ветвь с первой командой: 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
Для тех, кто задается вопросом, почему он работает (как я был вначале):
Вы хотите вернуться на 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, он все же указывает на фиксацию, которую она изначально сделала.
В этом случае наилучшим вариантом является метод, открытый sykora. Но иногда это не самый простой и не общий метод. Для общего метода используйте git cherry-pick:
Чтобы достичь желаемого OP, это двухэтапный процесс:
newbranch
Выполнить
git checkout master
git log
Обратите внимание, что хэши (скажем, 3) обязывают вас на newbranch
. Здесь я буду использовать:
C commit: 9aa1233
D commit: 453ac3d
E commit: 612ecb3
Примечание.. Вы можете использовать первые семь символов или весь хеш фиксации
newbranch
git checkout newbranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233
git checkout newbranch
git cherry-pick 612ecb3~1..9aa1233
git cherry-pick применяет эти три фиксации к newbranch.
Еще один способ сделать это, используя только 2 команды. Также сохраняет ваше текущее рабочее дерево целым.
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 -f
, правильный способ сделать это:
git checkout -b newbranch # switch to a new branch
git branch -f master HEAD~3 # make master point to some older commit
То же самое, но менее "волшебное"
НЕ делайте этого:
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}
--keep
похож на --hard
, но более безопасен, так как он терпит неудачу, а не выбрасывает незафиксированные изменения).newbranch
.newbranch
. Поскольку они больше не ссылаются на ветку, это делает это с помощью git 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
но затем кто-то переписывает историю с помощью принудительного толчка 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- базы.
Это не "перемещает" их в техническом смысле, но имеет такой же эффект:
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)
rebase
для того же?
rebase
в отдельной ветке в сценарии выше.
Чтобы сделать это без перезаписи истории (т.е. если вы уже нажали на коммиты):
git checkout master
git revert <commitID(s)>
git checkout -b new-branch
git cherry-pick <commitID(s)>
Обе ветки могут быть нажаты без усилий!
Если бы такая ситуация:
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