Переместить последние фиксации в новую ветвь с помощью 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
что это делает, по номеру строки
- отменяет последние три коммита (и их сообщения) к
master
, но оставляет все файлы нетронутыми - скрывает все изменения рабочего файла, делая
master
рабочее дерево равно головке~3 состояния - переключается на существующую ветку
newbranch
- применяет скрытые изменения к вашему рабочему каталогу и очищает тайник!--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