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

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

Т.е. Как мне выйти из этого

master A - B - C - D - E

к этому?

newbranch     C - D - E
             /
master A - B 

person Mark A. Nicolosi    schedule 27.10.2009    source источник
comment
Примечание: я задал противоположный вопрос здесь   -  person Benjol    schedule 16.12.2010
comment
eddmann.com/posts/ этот работает   -  person Sagar Naliyapara    schedule 25.04.2017
comment
Были ли здесь удалены комментарии? Я спрашиваю, потому что во время моего посещения этого вопроса раз в два месяца я всегда просматриваю этот комментарий.   -  person Tejas Kale    schedule 19.03.2018
comment
Боковой комментарий: Вопрос об очень простом случае. Чтение ответов и все остальное не делайте этого, потому что ... и лучшее решение ... и предупреждение с версией n + ... сразу после ответов (возможно, когда уже слишком поздно), мне кажется даже очень простым Операции не имеют прямых решений в git. Графический менеджер версий, в котором вы просто добавляете тег для новой ветки, не имея дела с тем, что мне кажется неясным и архаичным синтаксисом, был бы таким облегчением. Мое королевство и мои золотые значки тому, кто разветвляет git и начинает новый подход ;-) это срочно.   -  person mins    schedule 15.06.2020
comment
@mins Возможно, TortoiseGit на шаг ближе к тому, о чем вы просите. Прелесть Git в том, что у вас есть полный контроль на кончиках ваших пальцев, но это требует более глубокого понимания и необходимости, чтобы пользователь чувствовал себя комфортно при использовании интерфейса командной строки. Я использую Git уже несколько лет, но по-прежнему полагаюсь на ссылки на документацию и / или stackoverflow, когда мне нужно выполнять менее частые операции. Мне нравится работать с интерфейсом командной строки git (я использую GitBash), так как я могу делать все быстро. Однако я обращаюсь к TortoiseGit для разрешения конфликтов, поскольку мне это проще.   -  person robhem    schedule 01.06.2021
comment
Обязательно прочитайте первые десять ответов (или около того), так как лучшие не получают больше всего голосов.   -  person chb    schedule 25.06.2021


Ответы (15)


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

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

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

Перед этим вы можете сохранить незафиксированные изменения в своем тайнике, используя git stash. После завершения вы можете получить сохраненные незафиксированные изменения с помощью git stash pop

Переход в новую ветку

ВНИМАНИЕ! Этот метод работает, потому что вы создаете новую ветку с помощью первой команды: 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 просто указать хэш фиксации (или ссылку, например origin / master), к которой вы хотите вернуться на master (/ current ), например:

git reset --hard a1b2c3d4

* 1 Вы будете только терять коммиты из основной ветки, но не волнуйтесь, у вас будут эти коммиты в новой ветке!

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

person Community    schedule 27.10.2009
comment
И, в частности, не пытайтесь вернуться дальше, чем точка, в которую вы в последний раз отправили коммиты в другой репозиторий, из которого кто-то другой мог вытащить. - person Greg Hewgill; 27.10.2009
comment
Хотите знать, можете ли вы объяснить, ПОЧЕМУ это работает? Для меня вы создаете новую ветку, удаляете 3 коммита из старой ветки, в которой все еще находитесь, а затем проверяете созданную ветку. Итак, как коммиты, которые вы удалили, волшебным образом появляются в новой ветке? - person Jonathan Dumaine; 03.08.2010
comment
@Jonathan Dumaine: Потому что я создал новую ветку перед удалением коммитов из старой ветки. Они все еще там в новой ветке. - person sykora; 04.08.2010
comment
Понятно. Ветвление делает клон текущего состояния. По какой-то причине я предполагал, что эти коммиты не будут клонированы, потому что они не были на удаленном сервере (слишком много git-svn, ха-ха). Спасибо. - person Jonathan Dumaine; 04.08.2010
comment
ветки в git - это просто маркеры, которые указывают на коммиты в истории, ничего не клонируется, не создается или не удаляется (кроме маркеров) - person knittl; 16.08.2010
comment
Но тогда как бы вы перенесли это в другой репозиторий? Мне отказывают, когда я делаю git push после git reset --hard HEAD~3. - person andrewrk; 15.12.2010
comment
Я бы заменил git reset --hard HEAD ~ 3 на git reset --hard origin / master. Тогда вам не нужно знать, на сколько коммитов вы опережаете мастера. Он синхронизирует ваш локальный мастер с мастером на вашем сервере. - person Tom Maeckelberghe; 09.06.2011
comment
Также обратите внимание: не делайте этого с незафиксированными изменениями в вашей рабочей копии! Это меня просто укусило! :( - person Adam Tuttle; 25.10.2011
comment
Чтобы снизить риск возврата мастера обратно к неправильной фиксации, этот более точно устанавливает ветвь. git reset --hard 1a0c9b, где 1a0c9b - это начало хэша фиксации, к которому вы хотите вернуться. - person Mazrick; 02.10.2012
comment
Есть ли причина, по которой за комментарий Мазрика не проголосовали больше? Похоже, это гораздо более надежный метод, чем подсчет количества коммитов. - person Michael Bylstra; 17.10.2012
comment
Почему git reset --hard рекомендуется так часто? Почему не git checkout head~3 . (обратите внимание на ., иначе вы попадете на ветку без головы)? (конечно, head~3 можно заменить на альтернативы, упомянутые выше). Кажется, checkout намного безопаснее, так как не уничтожит вашу историю. Я что-то пропустил? - person cboettig; 24.01.2013
comment
да, вы это сделали, скорее всего, люди случайно выбрали неправильную ветку, они хотят ЖЕСТКО удалить эти коммиты после того, как поместили их в другую ветку, другая ветка, скорее всего, в конечном итоге будет объединена с случайно зафиксированной, после просмотра и тестирования - person Dominic; 04.07.2013
comment
Если вы сначала не объедините свои изменения, они будут потеряны. - Это не совсем так. Коммиты только отсоединяются. Сборка мусора занимает очень много времени (что-то вроде ›1000 коммитов или около 10000?) Так что вы все равно можете их восстановить. Либо запомнив SHA, либо найдя их с помощью git reflog или git fsck --lost-found. Дополнительная информация о потерянных коммитах - person Sukima; 19.10.2013
comment
Под очень долгим временем я подразумеваю 30 дней, а НЕ ›1000 коммитов. - person Sukima; 19.10.2013
comment
Зачем вам делать ненужные проверки и мусорить свое дерево? git checkout -b newbranch; git branch -f master master~3 (или ..._ 2_). - person jthill; 19.10.2013
comment
Если я затем перейду на новую ветку и сделаю git rebase master, удалит ли эта фиксация новая ветка? Если бы я использовал merge вместо rebase, смог бы я этого избежать? - person Dustin; 03.04.2014
comment
Если вы хотите отправить основную ветку (или исходную ветку, которую вы откатили), используйте git push --force origin master. Если вы не набираете force, git отклонит push и попросит вас вытащить откатные коммиты из удаленной формы. - person mazikwyry; 07.08.2014
comment
Вот что я использовал: git push --force -u origin master, а затем git push -u origin newbranch. - person aliteralmind; 06.11.2014
comment
sykora, может быть, вы хотите посмотреть там мета-обсуждение: meta.stackoverflow.com/questions/298067/ - возможно, вы захотите расширить свой ответ, сославшись на очень аккуратную версию, которую @aragaer дал ниже (очень недооцененный вклад, как я думаю) - person santa; 30.06.2015
comment
Я хотел бы знать, как это сделать с новым программным обеспечением GitHub Desktop, если это возможно. Я кое-что знаю в командной строке, но git слишком сложен в текстовой среде. Теперь, когда доступно доступное бесплатное официальное решение на основе графического интерфейса пользователя, я хотел бы его использовать. - person Fabio Bracht; 01.09.2015
comment
Спасибо. Сработал для меня очень хорошо, только затем сбросил git stash apply, я перешел на правильную ветку и применил git stash pop! с этим я получаю свои изменения и куда я переписывался! - person NHTorres; 25.11.2015
comment
После того, как ты встанешь git push origin oldBranch --force - person MLProgrammer-CiM; 10.02.2016
comment
Этот ответ приводит к потере фиксации: в следующий раз, когда вы git rebase, 3 фиксации будут автоматически отменены из newbranch. См. мой ответ для получения подробной информации и более безопасных альтернатив. - person John Mellor; 07.04.2016
comment
Я протестировал процедуру с новым способом ветвления. Но это не сдвигает фиксацию. как кто-то указал, push --force необходим и потребует повторно прикрепить HEAD к хвосту фиксации. не так ли? (извините, если что-то пропустил из огромного количества комментариев :)) - person dragonmnl; 06.02.2017
comment
Следуя этим инструкциям, я потерял все коммиты, которые я пытался переместить. Теперь мне нужно делать работу заново. Сброс коммитов из мастера привел к изменению точки разветвления ветки, которая не имела собственных коммитов. - person Throw Away Account; 18.10.2017
comment
git push --force -u origin как для главной, так и для новой ветки должны быть частью ответа - person Andras Hatvani; 17.04.2018
comment
Я последовал этим инструкциям и подумал, что потерял все свои изменения, затем перешел на новую ветку, и они были там. На мгновение меня взорвали, пока я не понял, почему это сработало. Я тупой. Спасибо. - person Nathan Beach; 07.06.2018
comment
у меня работал только с созданием новой ветки. Невозможно было перейти в существующую ветку - person chainstair; 10.05.2019
comment
Это не очень хорошее решение, потому что в истории git все еще видно, что вы слились с мастером ... - person Black; 21.11.2019
comment
Я использовал этот пост так много раз, потому что я часто случайно фиксирую локальную главную ветку и никогда не могу вспомнить, как это сделать. Хотел бы я проголосовать десять раз. Еще раз спасибо за спасение ... - person Nathan Beach; 13.12.2019
comment
После того, как вы удалили коммиты из master, вы можете переключиться на последнюю ветку, используя git checkout - - person cpinamtz; 16.06.2020
comment
Я откатил изменения @ Rennex, так как опция --keep сохраняет изменения в перемещенных коммитах как незавершенные, что не является желаемым эффектом. Вместо этого это создает много беспорядка, если вы перемещаете несколько коммитов с большим количеством изменений. Если вы хотите сохранить незавершенные изменения, спрячьте их раньше. - person Елин Й.; 10.07.2020
comment
Используйте git reset --soft, чтобы сохранить изменения локально - person Martin H.; 11.12.2020
comment
после выбора вишни можно использовать интерактивную перестановку и изменить pick на drop, если вы хотите удалить коммиты из исходной ветки. особенно полезно, если ваши коммиты запутаны, и вы хотите переместить только некоторые конкретные коммиты. Однако вам придется форсировать толчок. - person Ayush Mandowara; 13.01.2021
comment
Если вы делаете это в существующей ветке, как это сделал я: Чтобы иметь возможность перебазировать head ~ 3 или в IDE (например, IntelliJ), перебазировать сюда. Важно НЕ извлекать из удаленного репо. Если вы сделаете это, другие будут коммиты в вашу собственную. Интересно, что можно было сделать в таком случае? Отдельные коммиты удаляются? Кроме того, я предполагаю, что OP не продвигал свои изменения. Если бы это было сделано, сброс удалял бы коммиты, которые уже были выполнены другими, и сломал бы их код, не так ли? Любой хороший ресурс, чтобы узнать, что можно было бы сделать, если бы элементы уже были отправлены? - person veritas; 01.04.2021

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

Вы хотите вернуться в 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

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

person Ryan Lundy    schedule 22.07.2011
comment
@Andrew Без --hard оставляет изменения в рабочем каталоге. В этом случае мы можем создать новую ветку и зафиксировать эти изменения в новой ветке. Устранит ли этот подход необходимость следить за тем, чтобы другие не воспользовались вашими изменениями? - person HRJ; 04.04.2013
comment
Мне также нужно было сделать git push origin master --force, чтобы изменение отобразилось в основном репозитории. - person Dženan; 26.11.2014
comment
Вероятно, хорошая идея использовать --force-with-lease в случае, если кто-то уже совершил транзакцию на основе вашей старой цепочки. - person Jacob Wang; 16.11.2015
comment
Этот ответ приводит к потере фиксации: в следующий раз, когда вы git rebase, 3 фиксации будут автоматически отменены из newbranch. См. мой ответ для получения подробной информации и более безопасных альтернатив. - person John Mellor; 07.04.2016
comment
@ Джон, это чепуха. Перебазирование без знания того, что вы делаете, приводит к потере коммитов. Если вы потеряли коммиты, мне вас жаль, но этот ответ не потерял ваши коммиты. Обратите внимание, что origin/master не отображается на диаграмме выше. Если вы нажмете на origin/master, а затем внесете указанные выше изменения, конечно, все получится забавно. Но это доктор, мне больно, когда я решаю такие проблемы. И это выходит за рамки исходного вопроса. Я предлагаю вам написать свой собственный вопрос, чтобы изучить свой сценарий, вместо того, чтобы угонять этот. - person Ryan Lundy; 07.04.2016
comment
@Kyralessa: Вопрос, заданный для newbranch, должен быть основан на их существующей локальной ветке (которая, как оказалось, называется master), подразумевая, что она должна отслеживать ее как локальный восходящий поток. После разделения 3 коммитов на newbranch, если существующая локальная ветвь позже будет изменена (например, новые коммиты добавляются локально или извлекаются из источника / мастера), то git rebase является правильным и разумным способом включения этих изменений из существующей локальной ветки. перейти в newbranch. (Слияние - допустимая альтернатива, но ведет к более сложной истории). - person John Mellor; 07.04.2016
comment
@ Джон, в своем ответе вы сказали, что не делайте этого! git branch -t newbranch. Вернитесь и прочтите ответы еще раз. Никто не предлагал этого сделать. - person Ryan Lundy; 07.04.2016
comment
@Kyralessa, конечно, но если вы посмотрите на диаграмму в вопросе, становится ясно, что они хотят, чтобы newbranch основывался на их существующей локальной master ветке. После выполнения принятого ответа, когда пользователь приступит к запуску git rebase в newbranch, git напомнит им, что они забыли установить ветку восходящего потока, поэтому они запустят git branch --set-upstream-to=master, затем git rebase и столкнутся с той же проблемой. С таким же успехом они могут использовать git branch -t newbranch. - person John Mellor; 07.04.2016
comment
@JohnMellor, вы правы, что вызов git rebase без аргументов включает параметр --fork-point, что может привести к потере фиксации. На мой взгляд это недоработка git rebase. Однако это не имеет ничего общего с моим ответом выше. Есть много обстоятельств, при которых git rebase без аргументов может вызвать проблемы. Мораль этой истории (к сожалению) - не вызывать git rebase без перехода в ветку, даже если эта ветка уже является восходящей. Удобство использования Git значительно улучшилось за последние восемь лет или около того, но это одна из областей, где можно было бы поработать больше. - person Ryan Lundy; 01.06.2018

В основном...

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

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

Шаг 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.

person Ivan    schedule 07.02.2012
comment
если выбор вишни - это последние N коммитов, вы можете просто переместить свою главную ГОЛОВУ назад с git reset --hard HEAD~N - person Mike Graf; 07.12.2013
comment
Это очень хорошо работает, если вы случайно зафиксируете неправильную, не главную ветвь, когда вам нужно было создать новую ветку функции. - person julianc; 27.02.2014
comment
Информация о git cherry-pick хороша, но команды в этом посте не работают. 1) git checkout newbranch должен быть git checkout -b newbranch, поскольку newbranch еще не существует; 2) если вы проверяете newbranch из существующей основной ветки, в нее УЖЕ включены эти три коммита, поэтому их нет смысла выбирать. В конце концов, чтобы получить то, что хотел OP, вам все равно придется выполнить некоторую форму сброса --hard HEAD. - person JESii; 24.05.2014
comment
+1 за полезный подход в некоторых ситуациях. Это хорошо, если вы хотите перенести только свои собственные коммиты (которые перемежаются с другими) в новую ветку. - person Tyler V.; 01.10.2014
comment
Это лучший ответ. Таким образом вы можете переместить коммиты в любую ветку. - person skywinder; 05.11.2014
comment
Важен ли порядок сбора вишни? - person kon psych; 24.04.2015
comment
У меня не получилось. Сообщение об ошибке, ничего не добавлено для фиксации. Предыдущая выборка теперь пуста. Разрешить пусто или продолжить. В любом случае вроде не сработало, коммиты не перенесли. - person David; 02.09.2015
comment
Я не смог использовать диапазон выбора вишни, получил ошибку: не удалось применить c682e4b ... к одному из коммитов, но при использовании вишневого выбора для каждого коммита по одному, он работал нормально (мне пришлось сделать git cherry-pick --abort после ошибки). Комментирую, потому что думал, что это будет эквивалентно. Это на git версии 2.3.8 (Apple Git-58) - person Hudson; 31.10.2015
comment
Для референсов обычно можно использовать 3-5 гекситов; сколько бы ни потребовалось Git, чтобы однозначно идентифицировать фиксацию. Кроме того, вы можете использовать master~3..master для ссылки на последние 3 коммита на master вместо хешей. - person Zaz; 16.04.2016
comment
@konpsych Я считаю, что порядок важен. Вы хотите в первую очередь делать самые старые коммиты. - person Kevin; 28.12.2016
comment
Второй пример выбора вишни неверен. 612ecb3..9aa1233 означает все коммиты после, но исключая 612ecb3 до 9aa1233 включительно. Чтобы получить все три коммита, вам понадобится cherry-pick 612ecb3~1..9aa1233. - person Jason R. Coombs; 12.08.2017
comment
Right @ JasonR.Coombs Я отредактировал пример. Спасибо - person Ivan; 28.02.2018
comment
Разве выбор вишни не создает новые коммиты? Зачем вам это делать, если вы можете просто создать новую ветку от мастера и сбросить голову мастера назад. Так это было бы намного быстрее и дешевле. - person MIWMIB; 07.11.2018
comment
Это отлично работает, если вы хотите отвечать на коммиты поверх существующей ветки вместо того, чтобы создавать новую с неуместными коммитами. Ситуация, в которую я иногда попадал, работая над множеством функций параллельно. - person Hultner; 05.04.2019
comment
Мне это не удалось, но я делал их в правильном порядке, т.е. в хронологическом порядке коммитов, у меня сработало: git cherry-pick 9aa1233 453ac3d 612ecb3 - person PhiLho; 01.10.2019
comment
Это удивительная вещь, о которой я почему-то не знал до сих пор. Спасибо, мой добрый сэр. - person Shashank Saxena; 05.02.2020
comment
Я действительно хотел переместить коммиты в ветку, которая на самом деле много коммитов позади текущей ветки. Этот ответ помогает в этом случае. - person Mihir Luthra; 01.03.2020
comment
После того, как вы скопируете их с помощью cherry-pick, как лучше всего удалить их из исходной ветки? Я сделал это, отбросив коммиты через git rebase -i, но мне было интересно, есть ли предпочтительный способ. - person sparc_spread; 14.07.2020
comment
@PhiLho Спасибо! Я использовал ваш синтаксис - немного более явный, немного более безопасный - person Dogweather; 03.08.2020

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

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

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 фиксации после выполнения _14 _.

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

# 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

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

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

Подробнее см. Определение --fork-point в git rebase и git merge-base docs.

person John Mellor    schedule 06.04.2016
comment
Я думаю, что я был бы более удовлетворен этим ответом, если бы вы могли продемонстрировать, каким будет неправильное состояние reflog по сравнению с правильным состоянием в этой ситуации. - person Makoto; 07.04.2016
comment
В вашем сценарии, если я сохраню M2 и M3 в своей тематической ветке, а затем попытаюсь объединить свою тематическую ветку обратно в основную, тогда я не буду повторно вводить изменения, которые кто-то пытался удалить (например, M2)? Казалось бы, это нежелательное поведение, не так ли? Для меня имеет смысл, что если кто-то собирается переписать историю, вам нужно, чтобы ветка темы отслеживала новую историю, чтобы вы не отменяли перезапись истории при слиянии. - person Gordon Bean; 07.04.2016
comment
Этот ответ гласит: НЕ делайте этого! над тем, что никто не предлагал делать. - person Ryan Lundy; 17.04.2016
comment
Есть ли способ исправить это, если я уже использовал этот метод stackoverflow.com/a/1628584/2423194 и выполнял git push --force, чтобы удалить коммиты, которые я уже переместил в новую ветку? - person Rock Lee; 18.05.2016
comment
Большинство людей не переписывают опубликованную историю, особенно на master. Так что нет, они не ошибаются. - person Walf; 14.09.2016
comment
@Makoto, после выполнения действий НЕ делайте этого git reflog newbranch будет иметь одну запись db8f2fe newbranch@{0}: branch: Created from oldbranch, где db8f2fe - это хэш T3, и, что более важно, git reflog oldbranch будет перечислять точные хеши T1, T2 и T3. Вместе ясно, что коммиты T1, T2, T3 уже были частью старой ветки, когда новая ветвь отделилась от нее, поэтому они будут неожиданно удалены из новой ветки к следующему git rebase. Следовательно, git merge-base --fork-point newbranch oldbranch также не сможет найти точку разветвления (вместо вывода хэша M3). - person John Mellor; 16.09.2016
comment
@Makoto, если вместо этого вы выполните один из моих альтернативных шагов, git reflog newbranch начнется с 4b242c2 newbranch@{3}: branch: Created from HEAD, где 4b242c2 - это хэш M3, за которым следуют 3 записи для выбора вишен. И git reflog oldbranch перечислит T1, T2 и T3, но у них там будут другие хэши, чем в newbranch, поскольку они были скопированы в newbranch путем выбора вишни (в частности, выбор вишни обновляет метку времени - при условии, что она была длиннее, чем секунду с момента создания коммита, что приводит к получению нового хэша, даже если это перемотка вперед). - person John Mellor; 16.09.2016
comment
@GordonBean, да, хорошо, что git rebase удаляет M2 из вашей тематической ветки, когда M2 удаляется путем перезаписи из origin / master. Это просто означает, что вы должны быть осторожны в этом конкретном случае (разделение существующих коммитов из локальной ветки на зависимую локальную ветку), поскольку git не может различить эти два случая. - person John Mellor; 16.09.2016
comment
@Kyralessa, -t, о котором вы говорите в git branch, происходит неявно, если у вас установлен git config --global branch.autosetuprebase always. Даже если вы этого не сделаете, я уже объяснил вам, что та же проблема возникает, если вы настраиваете отслеживание после выполнения этих команд, как OP, вероятно, намеревается сделать, учитывая их вопрос. - person John Mellor; 16.09.2016
comment
@RockLee, да, общий способ исправить такие ситуации - создать новую ветку (newbranch2) из ​​безопасной начальной точки, а затем выбрать все коммиты, которые вы хотите сохранить (от badnewbranch до newbranch2). Выбор вишни даст коммитам новые хэши, так что вы сможете безопасно перебазировать newbranch2 (и теперь можете удалить badnewbranch). - person John Mellor; 16.09.2016
comment
@Walf, вы неправильно поняли: git rebase спроектирован таким образом, чтобы быть устойчивым к переписыванию истории восходящего потока. К сожалению, побочные эффекты такой устойчивости затрагивают всех, даже если ни они, ни их апстрим никогда не переписывают историю. - person John Mellor; 16.09.2016
comment
Хорошо, я предпочитаю метод cherry-pick, поскольку невозможно потерять коммиты (всегда отображается в git log). Однако, если история никогда не переписывается, как возникает ситуация с потерей фиксации? - person Walf; 16.09.2016
comment
Примечание: чтобы проверить, какие коммиты включить, вы можете использовать git reflog, который дает ссылку на HEAD @ {xxx}. - person Dan Smart; 06.10.2016
comment
Учитывая, что многие люди всегда устанавливают autosetuprebase, ваши предупреждения очень приветствуются. Спасибо, Джон Меллор. @sykora и Kyralessa, я думаю, стоит отметить принятый ответ о том, когда это опасно (всегда, если autosetuprebase установлен в вашем .gitconfig, что многие люди делают!). Выше есть по крайней мере один комментарий от Рока Ли, который последовал принятому ответу и, похоже, был сожжен. - person aggieNick02; 14.01.2017
comment
@Walf, история в этом случае переписывается на git reset --keep. Итак, в этом примере вы сами переписываете историю, отбрасывая коммиты, и вот как они теряются при перебазировании. - person mgiuffrida; 24.03.2017
comment
Вы также можете использовать --no-fork-point при запуске git rebase. - person torek; 24.03.2017
comment
Опасность, которой здесь избегают, состоит в том, что коммиты newbranch будут потеряны при перебазировании. Если newbranch объединяется с oldbranch до того, как произойдет перебазирование, остается ли это опасным? - person Nathan Hinchey; 16.06.2017
comment
Я пробовал это, чтобы поместить слияние в другую ветку, но тогда Cherrypick не выбирает другие коммиты в слиянии (только верхний), по крайней мере, я так думаю. Однако кажется, что после сброса я могу просто git checkout ‹commit› (с фиксацией, полученной из журнала ссылок), а затем создать новую ветку оттуда. как ты думаешь? - person krthie; 06.09.2017
comment
если вы используете метод выбора вишни, вам необходимо затем сбросить удаленный и объединить newbranch. Я не знаю команд git для этого, мне проще просто отредактировать .git/config - person Michael Johnston; 23.09.2017
comment
Жаль, что это не принятый ответ. Я выполнил шаги из принятого ответа и потерял 6 коммитов, как вы описали! - person Throw Away Account; 18.10.2017
comment
Вместо использования относительного формата ревизии просто возьмите текущую ревизию HEAD, например. aaaaaa, новый HEAD для главного bbbbbb и ревизия после bbbbbb, например cccccc, затем сделайте git reset --keep bbbbbb; git checkout -t -b newbranch; git cherry-pick cccccc..aaaaaa - person char101; 15.11.2017
comment
Ваша «альтернатива, которая не полагается на reflog» не взаимодействует с git push: она выдает ошибки независимо от того, какую ветку я пытаюсь отправить в удаленное репо: старую или новую. Пальцы вниз - person Eugene Gr. Philippov; 05.07.2018
comment
@ Евгений Гр. Филиппов То же самое, git постоянно говорил мне, что мне нужно git push, что, в свою очередь, говорит мне, что все уже обновлено и, следовательно, ничего не делает. Затем git сообщает мне git push, цикл повторяется. Я уверен, что он назван git неспроста. Мне пришлось использовать git push origin для принуждения к сотрудничеству. - person Klaws; 02.12.2019

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

person aragaer    schedule 26.03.2014
comment
Текущий каталог. Я думаю, это сработает, только если вы находитесь в верхнем каталоге. - person aragaer; 28.03.2014
comment
Местный толчок вызывает улыбку, но если подумать, чем он отличается от git branch -f здесь? - person jthill; 05.08.2014
comment
@GerardSexton . - нынешний директор. git может нажимать REMOTES или GIT URL. path to local directory поддерживается синтаксис URL-адресов Git. См. Раздел URL-адреса GIT в git help clone. - person weakish; 25.11.2014
comment
Я не знаю, почему это не оценивается выше. Очень просто и без небольшой, но потенциальной опасности git reset --hard. - person Godsmith; 06.02.2015
comment
@Godsmith Я предполагаю, что люди предпочитают три простые команды двум немного более непонятным командам. Кроме того, ответы, получившие наибольшее количество голосов, получают больше голосов, так как они отображаются первыми. - person JS_Riddler; 22.10.2015
comment
Я согласен с @Godsmith! Почему этот рейтинг не выше? Часть о том, как заставить мастера указывать на предыдущую фиксацию, определенно лучший вариант, который я видел здесь. Спасибо @aragaer - person hasse; 25.11.2015
comment
Отличный ответ! Вы также можете использовать @~3 вместо HEAD~3. - person Zaz; 16.04.2016
comment
Сработало для меня ... Но может ли кто-нибудь из вас объяснить, что вы предпочитаете этот метод reset --hard? Спасибо! - person henry; 10.07.2016
comment
Это никак не меняет рабочее дерево - любые изменения, которые вы внесли в свой код, остаются нетронутыми - вам даже не нужно ничего фиксировать. - person aragaer; 11.07.2016
comment
Согласен, очень просто. И снова, как сказал @ Dženan в предыдущем ответе, не забудьте сделать git push origin master --force также сохранить изменения удаленно. - person Rotem; 09.12.2018
comment
Вот ссылка на команду git branch git-scm.com/docs/git-branch. И чтобы уточнить, с помощью команды git branch -f master HEAD~3 это не обязательно должно быть master. - person rince; 16.01.2019
comment
Небольшое предупреждение: git branch -f master <something> сбросит информацию отслеживания в главной ветви. Таким образом, если вы установили восходящий поток master на что-нибудь, кроме origin/master, этот параметр теперь будет потерян. - person joeytwiddle; 26.09.2019
comment
+1 это отлично сработало для меня после того, как я дополнительно сделал git push origin master --force и git push --set-upstream origin <newbranch> - person Huioon Kim; 03.12.2019
comment
У меня отлично сработало. Мне нужно было git push origin ___ --force - на самом деле я был веткой разработки, и она отлично там работала - person Prasanth; 29.07.2020
comment
git push --force необходимо для репликации в репо - person Tarmac; 06.04.2021
comment
+ перед refspec работает как --force для этого refspec. - person aragaer; 07.04.2021

Гораздо более простое решение с использованием 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.

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

  • Он не оставляет случайных временных ветвей, загромождающих ваше дерево.
  • Он не сохраняет ошибочные сообщения о фиксации, поэтому вам нужно добавить новое сообщение о фиксации в эту новую фиксацию.
  • Обновлять! Используйте стрелку вверх для прокрутки буфера команд, чтобы повторно применить предыдущую фиксацию с ее сообщением о фиксации (спасибо @ARK)

Цели

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

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

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

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

person Slam    schedule 01.05.2018
comment
Спасибо, я так рад, что так много прочитал, чтобы добраться сюда, потому что для меня это тоже довольно распространенный вариант использования. Неужели мы такие нетипичные? - person Jim Mack; 10.09.2018
comment
Я думаю, что мы совершенно типичны, и ой, что я совершил по ошибке, это наиболее распространенный вариант использования, когда необходимо отменить несколько или меньше коммитов. К счастью, это решение настолько простое, что я его сейчас запомнил. - person Slam; 11.09.2018
comment
Это должен быть принятый ответ. Это просто, легко понять и легко запомнить - person Sina Madani; 28.11.2018
comment
Это может быть решение, но оно не делает именно то, что было запрошено, потому что теряет последние 3 фиксации. - person Jean Paul; 24.06.2019
comment
OP не указал, что сообщения фиксации должны быть сохранены. Код не теряется. - person Slam; 16.08.2019
comment
Я не думаю, что заначка необходима. Я просто обходился без него и работал хорошо. - person A Campos; 20.08.2019
comment
Вы также можете легко вернуть свои сообщения фиксации, если они у вас есть в вашей истории CLI (командной строки). У меня были команды git add и git commit, которые я использовал, поэтому все, что мне нужно было сделать, это нажать стрелку вверх, ввести несколько раз и бум! Все вернулось, но теперь на правой ветке. - person Luke Gedeon; 19.09.2019
comment
Спасибо, у меня это сработало отлично, потому что я хочу переместить только последнюю фиксацию без предыдущих коммитов в другую ветку. По сути, я совершил ошибку, отдав предпочтение мастеру вместо разработки, а мастер опережает разработку с изменениями, слияние которых меня сейчас не интересует. - person quarac; 19.12.2019
comment
Чтобы сохранить сообщения фиксации, вы можете просто предварительно выполнить git log и скопировать их в текстовый файл. Позже добавьте все это в новое сообщение :) - person ARK; 31.01.2020

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

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)
person Sukima    schedule 19.10.2013
comment
Разве вы не можете использовать rebase для того же? - person Bergi; 29.06.2015
comment
Да, вы также можете использовать rebase в отдельной ветке в приведенном выше сценарии. - person Sukima; 30.06.2015

Чтобы сделать это без перезаписи истории (т.е. если вы уже отправили коммиты):

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

Обе ветви можно толкать без силы!

person teh_senaus    schedule 21.01.2016
comment
Но тогда вам придется иметь дело со сценарием возврата, который, в зависимости от ваших обстоятельств, может быть намного сложнее. Если вы отмените фиксацию в ветке, Git по-прежнему будет видеть эти фиксации как выполненные, поэтому, чтобы отменить это, вам нужно отменить откат. Это сжигает немало людей, особенно когда они отменяют слияние и пытаются снова слить ветку, только чтобы обнаружить, что Git считает, что он уже слил эту ветку (что полностью верно). - person Makoto; 07.04.2016
comment
Вот почему я выбираю коммиты в конце на новую ветку. Таким образом, git видит их как новые коммиты, что решает вашу проблему. - person teh_senaus; 07.04.2016
comment
Это более опасно, чем может показаться на первый взгляд, поскольку вы изменяете состояние истории репозитория, не понимая последствий этого состояния. - person Makoto; 07.04.2016
comment
Я не слежу за вашим аргументом - суть этого ответа в том, что вы не меняете историю, а просто добавляете новые коммиты (которые эффективно отменяют повтор изменений). Эти новые коммиты можно отправлять и объединять как обычно. - person teh_senaus; 08.04.2016

Самый простой способ сделать это:

1. Переименуйте ветку master в свою newbranch (при условии, что вы находитесь в ветке master):

git branch -m newbranch

2. Создайте master ветку из желаемой фиксации:

git checkout -b master <seven_char_commit_id>

например git checkout -b master a34bc22

person Saikat    schedule 21.03.2020
comment
Мне нравится это решение, потому что вам не нужно переписывать заголовок / описание коммита git. - person Vulpo; 12.06.2020
comment
Разве это не портит удаленные восходящие ветки? Разве newbranch теперь не указывает на origin/master? - person kraxor; 30.04.2021

Была вот такая ситуация:

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

Я ожидал, что коммит я буду ГОЛОВАМ, но коммит L это сейчас ...

Чтобы быть уверенным, что вы попали в нужное место в истории, проще работать с хешем коммита.

git branch newbranch 
git reset --hard #########
git checkout newbranch
person Darkglow    schedule 20.09.2013

Как мне выйти из этого

A - B - C - D - E 
                |
                master

к этому?

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

С двумя командами

  • git branch -m master newbranch

давая

A - B - C - D - E 
                |
                newbranch

а также

  • git branch master B

давая

A - B - C - D - E
    |           |
    master      newbranch
person Ivan    schedule 16.05.2019
comment
Да, это работает и довольно просто. Графический интерфейс Sourcetree немного сбит с толку изменениями, внесенными в оболочку git, но после выборки все снова в порядке. - person lars k.; 24.05.2019
comment
Да они как в вопросе. Первая пара диаграмм должна быть эквивалентна диаграммам в вопросе, просто перерисована так, как я хотел бы для иллюстрации в ответе. В основном переименуйте основную ветвь как newbranch и создайте новую основную ветку там, где вы хотите. - person Ivan; 16.07.2019

Если вам просто нужно переместить все ваши неопубликованные коммиты в новую ветку, вам просто нужно,

  1. создать новую ветку из текущей: git branch new-branch-name

  2. нажмите на вашу новую ветку: git push origin new-branch-name

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

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

person Shamsul Arefin Sajib    schedule 17.06.2019

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

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

git branch <branch name>

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

git log

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

git cherry-pick d34bcef232f6c...

вы также можете указать код фиксации.

git cherry-pick d34bcef...86d2aec

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

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

git push

person pankaj    schedule 25.05.2018

1) Создайте новую ветку, которая перемещает все ваши изменения в new_branch.

git checkout -b new_branch

2) Затем вернитесь в старую ветку.

git checkout master

3) Сделайте git rebase

git rebase -i <short-hash-of-B-commit>

4) Затем открытый редактор содержит информацию о последних трех фиксациях.

...
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
person rashok    schedule 01.06.2018

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

Вы можете просто передать хэш коммита, который вы хотите разместить в HEAD, или, другими словами, коммит, который вы хотите сделать последним, через:

(Обратите внимание на хеш фиксации)

Чтобы этого избежать:

1) git checkout master

2) git branch <feature branch> master

3) git reset --hard <commit hash>

4) git push -f origin master
person Quesofat    schedule 24.11.2020