Как я могу разделить коммит Git, похороненный в истории?

292

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

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

Бен
источник

Ответы:

450

В справочной странице rebase есть руководство по разделению коммитов . Краткое резюме:

  • Выполните интерактивное обновление, включая целевой коммит (например git rebase -i <commit-to-split>^ branch), и отметьте его для редактирования.

  • Когда rebase достигнет этого коммита, используйте git reset HEAD^для сброса перед коммитом, но сохраняйте свое рабочее дерево нетронутым.

  • Добавляйте изменения постепенно и фиксируйте их, делая столько коммитов, сколько пожелаете. add -pможет быть полезно добавить только некоторые изменения в данный файл. Используйте, commit -c ORIG_HEADесли вы хотите повторно использовать исходное сообщение о коммите для определенного коммита.

  • Если вы хотите протестировать то, что вы делаете (хорошая идея!), Используйте, git stashчтобы скрыть часть, которую вы не сделали (или stash --keep-indexдаже прежде, чем вы это сделаете), протестируйте, а затем git stash popверните остальное в рабочее дерево. Продолжайте делать коммиты до тех пор, пока не получите все коммиты изменений, т.е. получите чистое дерево работ.

  • Выполните, git rebase --continueчтобы продолжить применение коммитов после коммита, разделенного сейчас.

Cascabel
источник
17
... но не делайте этого, если вы уже выдвинули историю после фиксации разделения.
wilhelmtell
29
@wilhelmtell: я опустил свой обычный шаблон «потенциально опасный; см.« восстановление после восходящего поворота »», потому что ОП явно сказал, что он не выдвигал эту историю.
Каскабель
2
и ты отлично прочитал. Я пытался избежать «шаблона», когда я указал, что это еще не общая история :) В любом случае, у меня был успех с вашим предложением. Это большая боль, если заняться этим после свершившегося факта. Я усвоил здесь урок, и это для того, чтобы убедиться, что коммиты вставлены правильно для начала!
Бен
2
Первый шаг может быть лучше сформулирован как git rebase -i <sha1_of_the_commit_to_split>^ branch. И git guiэто хороший инструмент для задачи расщепления, который можно использовать для добавления разных частей файла в разные коммиты.
Цян Сюй
3
@QiangXu: Первое - разумное предложение. Во-вторых, именно поэтому я и предложил git add -p, что может сделать больше, чем git guiможет в этом отделе (особенно редактирование фрагментов, постановка всего, начиная с текущего фрагмента, и поиск фрагментов с помощью регулярных выражений).
Каскабель
3

Вот как это сделать с Magit .

Скажите, что вы хотите изменить ed417ae; он содержит два несвязанных изменения и скрыт под одним или несколькими коммитами. Нажмите, llчтобы показать журнал, и перейдите к ed417ae:

начальный журнал

Затем нажмите, rчтобы открыть всплывающее окно

всплывающее окно

и mизменить коммит в точке.

Обратите внимание на @то, как теперь существует коммит, который вы хотите разделить - это означает, что HEAD теперь находится на этом коммите:

изменение коммита

Мы хотим переместить HEAD к родителю, поэтому перейдите к родителю (47e18b3), нажмите x( magit-reset-quicklyпривязано, oесли вы используете evil-magit) и введите, чтобы сказать «да, я имел в виду фиксацию в точке». Ваш журнал должен выглядеть так:

журнал после сброса

Теперь нажмите, qчтобы перейти в обычный статус Magit, затем используйте обычную uкоманду unstage, чтобы удалить из сценария то, что не происходит в первом коммите, зафиксировать cостальные как обычно, затем изменить sи cпропустить то, что происходит во втором коммите, и когда это будет сделано: нажмите, rчтобы открыть всплывающее окно

всплывающее окно

и еще один, rчтобы продолжить, и все готово! llсейчас показывает:

все сделано журнал

unhammer
источник
1

Чтобы разделить коммит <commit>и добавить новый коммит до этого и сохранить дату автора <commit>, выполните следующие действия:

  1. Изменить коммит до <commit>

    git rebase -i <commit>^^
    

    NB: возможно, это также понадобится для редактирования <commit>.

  2. Вишневый коврик <commit>в указатель

    git cherry-pick -n <commit>
    
  3. Интерактивный сброс ненужных изменений из индекса и сброс рабочего дерева

    git reset -p && git checkout-index -f -a
    

    В качестве альтернативы, просто скопируйте ненужные изменения в интерактивном режиме: git stash push -p -m "tmp other changes"

  4. Внесите другие изменения (если есть) и создайте новый коммит

    git commit -m "upd something" .
    

    При желании повторите пункты 2-4, чтобы добавить дополнительные промежуточные коммиты.

  5. Продолжить ребазинг

    git rebase --continue
    
Рувим
источник
0

Существует более быстрая версия, если вы хотите извлечь содержимое только из одного файла. Это быстрее, потому что интерактивная перебазировка на самом деле больше не является интерактивной (и, конечно, еще быстрее, если вы хотите извлечь из последнего коммита, тогда вообще не нужно перебазировать)

  1. Используйте свой редактор и удалите строки, из которых вы хотите извлечь the_file. Закрыть the_file. Это единственное, что вам нужно, все остальное - просто команды git.
  2. Этап удаления в индексе:

    git  add  the_file
    
  3. Восстановите только что удаленные строки обратно в файл, не влияя на индекс !

    git show HEAD:./the_file > the_file
    
  4. «SHA1» - это коммит, из которого вы хотите извлечь строки:

    git commit -m 'fixup! SHA1' 
    
  5. Создайте второй, совершенно новый коммит с содержимым для извлечения, восстановленным на шаге 3:

    git commit -m 'second and new commit' the_file 
    
  6. Не редактируйте, не останавливайте / продолжайте - просто примите все:

    git rebase --autosquash -i SHA1~1
    

Конечно, еще быстрее, когда коммит для извлечения является последним коммитом:

4. git commit -C HEAD --amend
5. git commit -m 'second and new commit' thefile
6. no rebase, nothing

Если вы используете, magitто шаги 4, 5 и 6 представляют собой одно действие: фиксация, мгновенное исправление

Марш
источник
-2

Если вы еще не нажали, просто используйте git rebase. Более того, используйте git rebase -iдля перемещения коммитов в интерактивном режиме. Вы можете переместить оскорбительный коммит на передний план, затем разделить его, как вам нравится, и переместить патчи назад (при необходимости).

Гинтаутас Миляускас
источник
14
Там нет необходимости перемещать его в любом месте. Разделите это там, где оно есть.
Каскабель
1
К сожалению, это не работает для меня, потому что часть истории после коммита зависит от него, поэтому я немного ограничен. Тем не менее, это был бы мой первый выбор.
Бен
@Ben: ничего страшного - коммиты потом не нужно будет менять вообще (при условии, что вы сохраняете все изменения, а не выбрасываете некоторые из них). Более подробная информация здесь - stackoverflow.com/questions/1440050/…
Ether