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

git git-branch branching-and-merging


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

Т.е.как я могу от этого отказаться.

master A - B - C - D - E

к этому?

newbranch     C - D - E
             /
master A - B 



Answer 1 Duc Filan


Переход к существующему филиалу

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

git checkout existingbranch
git merge master         # Bring the commits here
git checkout master
git reset --keep HEAD~3  # Move master back by 3 commits.
git checkout existingbranch

Опция --keep сохраняет любые незафиксированные изменения, которые могут быть у вас в несвязанных файлах, или отменяется, если эти изменения необходимо будет перезаписать - аналогично тому, что делает git checkout . Если это --hard git stash свои изменения и повторите попытку, или используйте --hard, чтобы потерять изменения (даже из файлов, которые не изменились между фиксациями!)

Переезд в новый филиал

Этот метод работает, создавая новую ветку с первой командой ( git branch newbranch ), но не переключаясь на нее. Затем мы откатываем текущую ветку (master) и переключаемся на новую ветку, чтобы продолжить работу.

git branch newbranch      # Create a new branch, containing all current commits
git reset --keep HEAD~3   # Move master back by 3 commits (Make sure you know how many commits you need to go back)
git checkout newbranch    # Go to the new branch that still has the desired commits
# Warning: after this it's not safe to do a rebase in newbranch without extra care.

Но удостоверьтесь, сколько человек вернется. В качестве альтернативы, вместо HEAD~3 , вы можете просто предоставить хеш коммита (или ссылку, такую ​​как origin/master ), к которой вы хотите вернуться, например:

git reset --keep a1b2c3d4

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




Answer 2 Ryan Lundy


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

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




Answer 3 Ivan


В общем...

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

Для достижения того,что хочет ОП,это двухступенчатый процесс:

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

Execute

git checkout master
git log

Обратите внимание на хеши (скажем, 3) newbranch вы хотите на 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.




Answer 4 John Mellor


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

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

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

В следующий раз, когда вы запустите git rebase (или git pull --rebase ), эти 3 коммита будут newbranch отброшены из newbranch ! (см. объяснение ниже)

Вместо этого сделайте это:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
  • Сначала он отбрасывает 3 самых последних фиксаций ( --keep , как --hard , но безопаснее, так как не удается , а не выбрасывайте неподтвержденных изменений).
  • Тогда он порождает newbranch .
  • Затем он выбирает эти 3 newbranch обратно на новую ветку . Поскольку на них больше не ссылается ветвь, он делает это с помощью git reflog : HEAD@{2} - это коммит, который HEAD использовал для ссылки на 2 операции назад, то есть до того, как мы 1. проверили newbranch и 2. использовали git reset чтобы отменить 3 коммитов.

Предупреждение: reflog включен по умолчанию, но если вы отключили его вручную (например, с помощью «чистого» хранилища git), вы не сможете вернуть 3 коммитов после запуска git reset --keep HEAD~3 .

Альтернатива,которая не зависит от рефлога:

# 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

но потом кто-то переписывает историю,надавливая на источник/мастер,чтобы удалить М2:

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

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

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

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




Answer 5 aragaer


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