Как разделить последний коммит на два в Git

277

У меня есть две рабочие ветки, master и forum, и я только что внес несколько изменений в ветку forum , которые я хотел бы добавить в master . Но, к сожалению, коммит, который я хочу выбрать, также содержит некоторые модификации, которые мне не нужны.

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

Я пытался сделать

git reset --hard HEAD^

который удалил все изменения, поэтому мне пришлось вернуться с

git reset ORIG_HEAD

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

Якуб Арнольд
источник

Ответы:

332

Вы должны использовать индекс. После смешанного сброса (« git reset HEAD ^») добавьте первый набор изменений в индекс, а затем зафиксируйте их. Тогда совершите остальное.

Вы можете использовать « git add », чтобы поместить все изменения, сделанные в файле, в индекс. Если вы не хотите ставить все изменения, сделанные в файле, только некоторые из них, вы можете использовать «git add -p».

Давайте посмотрим на пример. Предположим, у меня есть файл myfile, который содержит следующий текст:

something
something else
something again

Я изменил его в моем последнем коммите, так что теперь это выглядит так:

1
something
something else
something again
2

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

Сначала я возвращаюсь к родителю HEAD, но я хочу сохранить изменения в файловой системе, поэтому я использую «git reset» без аргументов (что будет делать так называемый «смешанный» сброс):

$ git reset HEAD^
myfile: locally modified
$ cat myfile
1
something
something else
something again
2

Теперь я использую «git add -p», чтобы добавить изменения, которые я хочу зафиксировать в индексе (= я их ставлю). «git add -p» - это интерактивный инструмент, который спрашивает вас о том, какие изменения в файле следует добавить в индекс.

$ git add -p myfile
diff --git a/myfile b/myfile
index 93db4cb..2f113ce 100644
--- a/myfile
+++ b/myfile
@@ -1,3 +1,5 @@
+1
 something
 something else
 something again
+2
Stage this hunk [y,n,a,d,/,s,e,?]? s    # split this section into two!
Split into 2 hunks.
@@ -1,3 +1,4 @@
+1
 something
 something else
 something again
Stage this hunk [y,n,a,d,/,j,J,g,e,?]? y  # yes, I want to stage this
@@ -1,3 +2,4 @@
 something
 something else
 something again
+2
Stage this hunk [y,n,a,d,/,K,g,e,?]? n   # no, I don't want to stage this

Затем я фиксирую это первое изменение:

$ git commit -m "Added first line"
[master cef3d4e] Added first line
 1 files changed, 1 insertions(+), 0 deletions(-)

Теперь я могу зафиксировать все остальные изменения (а именно цифру «2», вставленную в последнюю строку):

$ git commit -am "Added last line"
[master 5e284e6] Added last line
 1 files changed, 1 insertions(+), 0 deletions(-)

Давайте проверим журнал, чтобы увидеть, какие коммиты у нас есть:

$ git log -p -n2 | cat
Commit 5e284e652f5e05a47ad8883d9f59ed9817be59d8
Author: ...
Date: ...

    Added last line

Diff --git a/myfile b/myfile
Index f9e1a67..2f113ce 100644
--- a/myfile
+++ b/myfile
@@ -2,3 +2,4 @@
 something
 something else
 something again
+2

Commit cef3d4e0298dd5d279a911440bb72d39410e7898
Author: ...
Date: ...

    Added first line

Diff --git a/myfile b/myfile
Index 93db4cb..f9e1a67 100644
--- a/myfile
+++ b/myfile
@@ -1,3 +1,4 @@
+1
 something
 something else
 something again
hcs42
источник
1
В течение последних полутора недель я медленно привыкаю к ​​git из Mercurial, и есть удобная команда быстрого доступа, git reset [--patch|-p] <commit>которую вы можете использовать, чтобы избавить вас от необходимости git add -pпосле перезагрузки. Я прав? Использование git 1.7.9.5.
Trojjer
2
Вот еще немного об этой технике, включая перебазирование, если это был более старый коммит, или вам нужно изменить N коммитов на M коммитов: emmanuelbernard.com/blog/2014/04/14/… .
Крис Вестин
84

Цели:

  • Я хочу разделить прошлый коммит ( splitme) на два.
  • Я хочу сохранить сообщение коммита .

План:

  1. перебазировать интерактив с предыдущего splitme.
  2. править splitme.
  3. Сброс файлов для разделения на второй коммит.
  4. Исправьте коммит, сохраняя сообщение, измените при необходимости.
  5. Добавьте обратно файлы, выделенные из первого коммита.
  6. Зафиксируйте с новым сообщением.
  7. Продолжить ребаз.

Шаги перебазирования (1 и 7) можно пропустить, если splitmeэто самый последний коммит.

git rebase -i splitme^
# mark splitme commit with 'e'
git reset HEAD^ -- $files
git commit --amend
git add $files
git commit -m "commit with just some files"
git rebase --continue

Если бы я хотел, чтобы сплит-файлы были зафиксированы в первую очередь, я бы затем перебазировал -i и изменил порядок

git rebase -i splitme^
# swap order of splitme and 'just some files'
Spazm
источник
1
git reset HEAD^был недостающий кусок головоломки. Хорошо работает с -pтоже. Спасибо!
Мариус Гедминас
10
Важно отметить -- $filesаргумент git reset. С пройденными путями git resetвосстанавливает эти файлы до состояния указанной фиксации, но не изменяет никакие фиксации. Если вы уйдете с пути, вы «потеряете» коммит, который вы хотите изменить на следующем шаге.
маркеры дуэлянтов
2
Этот метод избавляет вас от необходимости копировать и вставлять ваше первое сообщение о коммите снова, по сравнению с принятым ответом.
Кэлвин
Также: если вы хотите сбросить все файлы, просто используйте git reset HEAD^ -- .. Крайне удивительно, это не совсем поведение git reset HEAD^.
allidoiswin
52

Чтобы изменить текущий коммит на два коммита, вы можете сделать что-то вроде следующего.

Либо:

git reset --soft HEAD^

Это отменяет последний коммит, но оставляет все готовым. Затем вы можете удалить некоторые файлы:

git reset -- file.file

При желании переназначить части этих файлов:

git add -p file.file

Сделайте новый первый коммит:

git commit

Этап и зафиксировать остальные изменения во второй фиксации:

git commit -a

Или:

Отмените и удалите все изменения из последнего коммита:

git reset HEAD^

Выборочно этап первого раунда изменений:

git add -p

Commit:

git commit

Зафиксируйте остальные изменения:

git commit -a

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

CB Bailey
источник
22

Запустите git gui, выберите переключатель «Изменить последний коммит» и отмените (коммит> Unstage From Commit или Ctrl- U) изменения, которые вы не хотите вносить в первый коммит. Я думаю, что это самый простой способ сделать это.

Еще одна вещь, которую вы можете сделать, это выбрать изменения без фиксации ( git cherry-pick -n), а затем вручную или с помощью git guiвыбора нужных изменений перед фиксацией.

Михаил Крелин - хакер
источник
15
git reset HEAD^

- жесткое то, что убивает ваши изменения.

semanticart
источник
13

Я удивлен, что никто не предложил git cherry-pick -n forum. Это отстраивает изменения от последнего forumкоммита, но не фиксирует их - тогда вы можете resetотменить изменения, которые вам не нужны, и зафиксировать то, что вы хотите сохранить.

dahlbyk
источник
3

Метод двойного обратного сквоша

  1. Сделайте еще один коммит, который удалит нежелательные изменения. (Если это для файла, это действительно просто: git checkout HEAD~1 -- files with unwanted changesи git commit. Если нет, файлы со смешанными изменениями могут быть частично подготовлены git reset fileи git add -p fileкак промежуточный шаг.) Назовите это обратным .
  2. git revert HEAD- Сделайте еще один коммит, который добавляет нежелательные изменения. Это двойной возврат
  3. Из 2 коммитов, которые вы сейчас сделали, спрессуйте первый на коммит для split ( git rebase -i HEAD~3). Этот коммит теперь освобождается от нежелательных изменений, так как они находятся во втором коммите.

Преимущества

  • Сохраняет сообщение коммита
  • Работает, даже если коммит для split не последний. Требуется только, чтобы нежелательные изменения не конфликтовали с последующими коммитами
user2394284
источник
1

Так как вы собираете вишню, вы можете:

  1. cherry-pickэто с --no-commitдобавленной опцией.
  2. resetи использовать add --patch, add --editили просто addпоставить то, что вы хотите сохранить.
  3. commit постановочные изменения.
    • Чтобы повторно использовать исходное сообщение о коммите, вы можете добавить --reuse-message=<old-commit-ref>или --reedit-message=<old-commit-ref>опции к commitкоманде.
  4. Подумайте о внесенных изменениях reset --hard.

Другой способ, сохранение или редактирование исходного сообщения о коммите:

  1. cherry-pick оригинальный коммит как обычно.
  2. Отмените изменения, которые вы не хотите, и используйте addдля постановки аннулирования.
    • Этот шаг будет легким, если вы удаляете то, что вы добавили, но немного сложнее, если вы добавляете то, что удалили, или отменяете изменение.
  3. commit --amend чтобы изменить направление на выбранный вишней коммит.
    • Вы снова получите то же самое сообщение о коммите, которое вы можете сохранить или изменить при необходимости.
ADTC
источник
0

Это может быть другое решение, предназначенное для случаев, когда существует огромный коммит, и небольшое количество файлов необходимо перенести в новый коммит. Это будет работать, если набор <path>файлов должен быть извлечен из последнего коммита в HEAD и все перемещены в новый коммит. Если требуется несколько коммитов, могут использоваться другие решения.

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

git reset HEAD^ <path>

$ git status
On branch <your-branch>
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   <path>

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   <path>

Чтобы понять, что произойдет (стрелка и комментарии не являются частью команды):

git diff --cached   -> show staged changes to revert <path> to before HEAD
git diff            -> show unstaged changes to add current <path> changes

Отменить <path>изменения в последнем коммите:

git commit --amend  -> reverts changes on HEAD by amending with staged changes

Создать новый коммит с <path>изменениями:

git commit -a -m "New Commit" -> adds new commit with unstaged changes

В результате создается новый коммит, содержащий изменения, извлеченные из последнего коммита.

Хосе Сифуэнтес
источник