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

StackOverflow https://stackoverflow.com/questions/1628563

Вопрос

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

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

master A - B - C - D - E

к этому?

newbranch     C - D - E
             /
master A - B 
Это было полезно?

Решение

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

ПРЕДУПРЕЖДЕНИЕ: Этот метод работает, потому что вы создаете новую ветвь с помощью первой команды: git branch newbranch.Если вы хотите переместить коммиты в существующий филиал перед выполнением вам необходимо объединить ваши изменения с существующей веткой git reset --hard HEAD~3 (см. Переход в существующий филиал ниже). Если вы сначала не объедините свои изменения, они будут потеряны.

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

# 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 (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

Но обязательно убедитесь, сколько коммитов нужно вернуть.В качестве альтернативы, вы можете вместо HEAD~3, просто укажите хэш фиксации (или ссылку , подобную происхождение / мастер) вы хотите "вернуться назад" к мастер (/текущая) ветвь, например:

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

Поскольку ветвь - это просто указатель, мастер указал на последнюю фиксацию.Когда вы сделали Новая ветвь, вы просто создали новый указатель на последнюю фиксацию.Затем, используя git reset вы переместили мастер указатель назад на два коммита.Но поскольку ты не двигался Новая ветвь, он по-прежнему указывает на фиксацию, которую он сделал изначально.

В общем...

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

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

Шаг 1 - Обратите внимание, какие коммиты из master вы хотите получить на newbranch

Выполнить

git checkout master
git log

Обратите внимание на хэши (скажем, 3) коммитов, которые вы хотите использовать newbranch.Здесь я буду использовать:
C фиксация: 9aa1233
D фиксировать: 453ac3d
E совершить: 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 вишневый сбор применяет эти три коммита к 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 Для . это хороший трюк, который нужно знать.

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

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

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 повторный журнал: HEAD@{2} является ли фиксация, которая HEAD используется для обозначения 2 операций назад, т. е.прежде чем мы 1.проверено newbranch и 2.использованный git reset чтобы отменить 3 коммита.

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

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

Использование вашего локального рефлога, git rebase можно видеть, что вы разветвлялись на более раннее воплощение ветки origin / master, и, следовательно, коммиты M2 и M3 на самом деле не являются частью вашей ветки topic.Следовательно, разумно предположить, что, поскольку 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 и git слияние-база Документы.

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

Вот гораздо более простой подход к фиксации в неправильной ветке.Начиная с ветки master это имеет три ошибочных коммита:

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

Когда это использовать?

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

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

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

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

Чего это не дает

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

Цели

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

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

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

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

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

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)>

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

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

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

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

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

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

git push origin +master

Как я могу отказаться от этого

A - B - C - D - E 
                |
                master

к этому?

A - B - C - D - E 
    |           |
    master      newbranch

С помощью двух команд

  • ветвь git -m master новая ветвь

отдающий

A - B - C - D - E 
                |
                newbranch

и

  • мастер ветви git B

отдающий

A - B - C - D - E
    |           |
    master      newbranch

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

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

git branch <branch name>

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

git log

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

git cherry-pick d34bcef232f6c...

вы также можете предоставить некоторую информацию об идентификаторе фиксации.

git cherry-pick d34bcef...86d2aec

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

Теперь вы можете ввести свой код

git push

Если вам просто нужно переместить все ваши неопушенный берет на себя обязательства по новый филиал, тогда вам просто нужно,

  1. Создать a новый филиал из текущего :git branch new-branch-name

  2. толкать ваш новый филиал: git push origin new-branch-name

  3. вернуться ваш старая (текущая) ветвь к последнему нажатию / стабильному состоянию: git reset --hard origin/old-branch-name

У некоторых людей также есть другие upstreams вместо того , чтобы origin, они должны использовать соответствующие upstream

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top