Разбить предыдущий коммит на несколько коммитов

Можно ли без создания ветки и выполнения сложной работы над новой веткой разбить один коммит на несколько разных коммитов после его фиксации в локальном репозитории?

21 ответ

Решение

git rebase -i сделаю это.

Сначала начните с чистого рабочего каталога: git status не должно показывать ожидающих изменений, удалений или дополнений.

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

$ git reset HEAD~

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

Если это было дальше назад в дереве, то

$ git rebase -i HEAD~3

где 3 это сколько коммитов обратно.

Если это было дальше в дереве, чем вы хотите сосчитать, то

$ git rebase -i 123abcd~

где 123abcd это SHA1 коммита, который вы хотите разделить.

Когда вы получите экран редактирования rebase, найдите коммит, который вы хотите разбить на части. В начале этой строки замените pick с edit (e коротко). Сохраните буфер и выйдите. Rebase теперь остановится сразу после коммита, который вы хотите отредактировать. Затем:

$ git reset HEAD~

Зафиксируйте фрагменты индивидуально обычным способом, выполнив столько коммитов, сколько вам нужно, затем

$ git rebase --continue

Из руководства git- rebase (раздел SPLITTING COMMITS)

В интерактивном режиме вы можете пометить коммиты с помощью действия "изменить". Однако это не обязательно означает, что git rebase ожидает, что результатом этого редактирования будет ровно один коммит. Действительно, вы можете отменить коммит или добавить другие коммиты. Это можно использовать для разделения коммита на две части:

  • Начать интерактивную перебазировку с git rebase -i <commit>^, где <commit> это коммит, который вы хотите разделить. Фактически, подойдет любой диапазон фиксации, если он содержит этот коммит.

  • Отметьте коммит, который вы хотите разделить, с помощью действия "изменить".

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

  • Теперь добавьте изменения в индекс, которые вы хотите иметь в первом коммите. Ты можешь использовать git add (возможно, в интерактивном режиме) или git gui (или оба), чтобы сделать это.

  • Зафиксируйте текущий текущий индекс с любым соответствующим сообщением.

  • Повторите последние два шага, пока ваше рабочее дерево не станет чистым.

  • Продолжить ребаз с git rebase --continue,

Предыдущие ответы охватывали использование git rebase -i редактировать коммит, который вы хотите разделить, и коммитить его по частям.

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

Получив коммит, который вы хотите разделить, используя rebase -i и пометить его для editУ вас есть два варианта.

  1. После использования git reset HEAD~, пройти через патчи индивидуально, используя git add -p выбрать те, которые вы хотите в каждом коммите

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

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

После использования rebase -i а также editиспользуя коммит, используйте

git reset --soft HEAD~

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

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

Как только вы будете довольны, установите / разархивируйте файлы по мере необходимости (мне нравится использовать git gui для этого) и зафиксировать изменения через пользовательский интерфейс или командную строку

git commit

Это первый сделанный коммит. Теперь вы хотите восстановить свою рабочую копию в состояние, в котором она находилась после разделения коммита, чтобы вы могли принять больше изменений для вашего следующего коммита. Чтобы найти sha1 коммита, который вы редактируете, используйте git status, В первых нескольких строках состояния вы увидите команду rebase, которая выполняется в данный момент, в которой вы можете найти sha1 вашего исходного коммита:

$ git status
interactive rebase in progress; onto be83b41
Last commands done (3 commands done):
   pick 4847406 US135756: add debugging to the file download code
   e 65dfb6a US135756: write data and download from remote
  (see more in file .git/rebase-merge/done)
...

В этом случае редактируемый мной коммит имеет sha1 65dfb6a, Зная это, я могу проверить содержимое этого коммита в моем рабочем каталоге, используя форму git checkout который принимает как фиксацию, так и местоположение файла. Здесь я использую . в качестве местоположения файла для замены всей рабочей копии:

git checkout 65dfb6a .

Не пропустите точку на конце!

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

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

Если вы хотите повторно использовать исходное сообщение о коммите для одного или нескольких коммитов, вы можете использовать его прямо из рабочих файлов rebase:

git commit --file .git/rebase-merge/message

Наконец, как только вы совершите все изменения,

git rebase --continue

продолжит и завершит операцию перебазирования.

Использование git rebase --interactive чтобы отредактировать предыдущий коммит, запустите git reset HEAD~, а потом git add -p добавить некоторые, затем сделать коммит, затем добавить еще несколько и сделать еще один коммит, столько раз, сколько вы хотите. Когда вы закончите, бегите git rebase --continueи у вас будут все коммиты split ранее в вашем стеке.

Важно: обратите внимание, что вы можете поиграть и внести все необходимые изменения, и вам не нужно беспокоиться о потере старых изменений, потому что вы всегда можете запустить git reflog чтобы найти точку в вашем проекте, которая содержит изменения, которые вы хотите, (давайте назовем это a8c4ab), а потом git reset a8c4ab,

Вот серия команд, чтобы показать, как это работает:

mkdir git-test; cd git-test; git init

Теперь добавьте файл A

vi A

добавьте эту строку:

one

git commit -am one

затем добавьте эту строку в A:

two

git commit -am two

затем добавьте эту строку в A:

three

git commit -am three

Теперь файл A выглядит так:

one
two
three

и наш git log выглядит следующим образом (ну, я использую git log --pretty=oneline --pretty="%h %cn %cr ---- %s"

bfb8e46 Rose Perrone 4 seconds ago ---- three
2b613bc Rose Perrone 14 seconds ago ---- two
9aac58f Rose Perrone 24 seconds ago ---- one

Допустим, мы хотим разделить второй коммит, two,

git rebase --interactive HEAD~2

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

pick 2b613bc two
pick bfb8e46 three

Поменяй первый pick для e отредактировать этот коммит.

git reset HEAD~

git diff показывает нам, что мы только что сделали unstaged сделанный нами коммит для второго коммита:

diff --git a/A b/A
index 5626abf..814f4a4 100644
--- a/A
+++ b/A
@@ -1 +1,2 @@
 one
+two

Давайте внесем это изменение и добавим "и третье" к этой строке в файле A,

git add .

Это обычно точка во время интерактивной перебазировки, где мы запускаем git rebase --continueпотому что мы обычно просто хотим вернуться в наш стек коммитов, чтобы отредактировать предыдущий коммит. Но на этот раз мы хотим создать новый коммит. Итак, мы побежим git commit -am 'two and a third', Теперь мы редактируем файл A и добавьте строку two and two thirds,

git add .git commit -am 'two and two thirds'git rebase --continue

У нас есть конфликт с нашим обязательством, threeИтак, давайте решим это:

Мы изменим

one
<<<<<<< HEAD
two and a third
two and two thirds
=======
two
three
>>>>>>> bfb8e46... three

в

one
two and a third
two and two thirds
three

git add .; git rebase --continue

Теперь наш git log -p выглядит так:

commit e59ca35bae8360439823d66d459238779e5b4892
Author: Rose Perrone <roseperrone@fake.com>
Date:   Sun Jul 7 13:57:00 2013 -0700

    three

diff --git a/A b/A
index 5aef867..dd8fb63 100644
--- a/A
+++ b/A
@@ -1,3 +1,4 @@
 one
 two and a third
 two and two thirds
+three

commit 4a283ba9bf83ef664541b467acdd0bb4d770ab8e
Author: Rose Perrone <roseperrone@fake.com>
Date:   Sun Jul 7 14:07:07 2013 -0700

    two and two thirds

diff --git a/A b/A
index 575010a..5aef867 100644
--- a/A
+++ b/A
@@ -1,2 +1,3 @@
 one
 two and a third
+two and two thirds

commit 704d323ca1bc7c45ed8b1714d924adcdc83dfa44
Author: Rose Perrone <roseperrone@fake.com>
Date:   Sun Jul 7 14:06:40 2013 -0700

    two and a third

diff --git a/A b/A
index 5626abf..575010a 100644
--- a/A
+++ b/A
@@ -1 +1,2 @@
 one
+two and a third

commit 9aac58f3893488ec643fecab3c85f5a2f481586f
Author: Rose Perrone <roseperrone@fake.com>
Date:   Sun Jul 7 13:56:40 2013 -0700

    one

diff --git a/A b/A
new file mode 100644
index 0000000..5626abf
--- /dev/null
+++ b/A
@@ -0,0 +1 @@
+one

git rebase --interactive может быть использован для разделения коммита на коммиты меньшего размера. В документации Git по rebase есть краткое пошаговое описание процесса - Splitting Commits:

В интерактивном режиме вы можете пометить коммиты с помощью действия "изменить". Однако это не обязательно означает, что git rebase ожидает, что результатом этого редактирования будет ровно один коммит. Действительно, вы можете отменить коммит или добавить другие коммиты. Это можно использовать для разделения коммита на две части:

  • Начать интерактивную перебазировку с git rebase -i <commit>^, где <commit> это коммит, который вы хотите разделить. Фактически, подойдет любой диапазон фиксации, если он содержит этот коммит.

  • Пометьте коммит, который вы хотите разделить, с помощью действия "изменить".

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

  • Теперь добавьте изменения в индекс, которые вы хотите иметь в первом коммите. Ты можешь использовать git add (возможно, в интерактивном режиме) или git gui (или оба), чтобы сделать это.

  • Зафиксируйте текущий текущий индекс с любым соответствующим сообщением.

  • Повторите последние два шага, пока ваше рабочее дерево не станет чистым.

  • Продолжить ребаз с git rebase --continue,

Если вы не совсем уверены, что промежуточные ревизии являются согласованными (они компилируются, проходят тестовый набор и т. Д.), Вам следует использовать git stash чтобы скрыть еще не зафиксированные изменения после каждой фиксации, протестировать и изменить фиксацию, если необходимы исправления.

Другой подход к разделению коммита — вызвать

      git rebase -i <branch or commit>

отметьте коммит, который вы хотите разделить, какeditа затем продублируйте эту строку . Это покажет вам коммит дважды во время перебазирования. При редактировании первого вы удаляете все, что хотели бы иметь во втором. Когда закончите (запустите тесты, если применимо)git add -A,git commit --amend(отрегулируйте сообщение о фиксации здесь) иgit rebase --continue. Сейчас вы находитесь на втором коммите, который просто вносит изменения, которые вы только что удалили из первого.

Я думаю, это намного проще, чем тщательно использовать git add -p.

Теперь в последней версии TortoiseGit для Windows вы можете сделать это очень легко.

Откройте диалоговое окно rebase, настройте его и выполните следующие шаги.

  • Щелкните правой кнопкой мыши на коммите, который вы хотите разделить, и выберите "Edit"(среди выбора, сквоша, удаления...).
  • Нажмите "Start"начать перебазирование.
  • Как только он прибудет в коммит для разделения, отметьте "Edit/Split"кнопка и нажмите"Amend"напрямую. Откроется диалоговое окно фиксации.
    Редактировать / Разделить коммит
  • Отмените выбор файлов, которые вы хотите поместить в отдельный коммит.
  • Отредактируйте сообщение о коммите и нажмите "commit".
  • Пока не появятся файлы для фиксации, диалог фиксации будет открываться снова и снова. Когда больше нет файла для фиксации, он все равно спросит вас, хотите ли вы добавить еще один коммит.

Очень полезно, спасибо TortoiseGit!

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

git rebase -i <sha1_before_split>
# mark the targeted commit with 'edit'
git reset HEAD^
git add ...
git commit -m "First part"
git add ...
git commit -m "Second part"
git rebase --continue

Кредиты на сообщение в блоге Эммануэля Бернарда.

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

      git reset --patch HEAD^

вместо git reset HEAD^. Эта команда позволяет вам сбрасывать только те блоки, которые вам нужны.

После того, как вы выбрали фрагменты, которые хотите сбросить, у вас будут поэтапные фрагменты, которые сбросят изменения в предыдущем коммите после того, как вы сделаете это.

      git commit --amend --no-edit

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

      git add .
git commit -m "new commit"

Не по теме факт:

В ртутном у них есть hg split - вторая особенность после hg absorb Я бы хотел увидеть в git.

Вы можете сделать интерактивный ребаз git rebase -i, Справочная страница содержит именно то, что вы хотите:

http://git-scm.com/docs/git-rebase

Обратите внимание, что есть также git reset --soft HEAD^, Это похоже на git reset (который по умолчанию --mixed) но он сохраняет содержимое индекса. Так что если вы добавили / удалили файлы, они уже есть в индексе.

Оказывается очень полезным в случае гигантских коммитов.

Проще всего обойтись без интерактивного перебазирования - это (вероятно) создать новую ветвь, начинающуюся с коммита до того, который вы хотите разделить, cherry-pick -n за коммит, сбросить, спрятать, зафиксировать перемещение файла, повторно применить кэш и зафиксируйте изменения, а затем либо объединитесь с предыдущей веткой, либо выберите те коммиты, которые последовали. (Затем переключите прежнее имя ветки на текущий заголовок.) (Вероятно, лучше последовать совету MBO и сделать интерактивную перебазировку.)

Вот как разбить одну фиксацию в IntelliJ IDEA, PyCharm, PhpStorm и т. Д.

  1. В окне журнала контроля версий выберите фиксацию, которую вы хотите разделить, щелкните правой кнопкой мыши и выберите Interactively Rebase from Here

  2. отметьте тот, который вы хотите разделить, как edit нажмите Start Rebasing

  3. Вы должны увидеть размещенный желтый тег, означающий, что HEAD установлен для этой фиксации. Щелкните правой кнопкой мыши на этом коммите, выберитеUndo Commit

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

Прошло уже более 8 лет, но, возможно, кому-то это все равно пригодится. Я смог обойтись безrebase -i. Идея состоит в том, чтобы привести git в то же состояние, в котором он был до того, как выgit commit:

# first rewind back (mind the dot,
# though it can be any valid path,
# for instance if you want to apply only a subset of the commit)
git reset --hard <previous-commit> .

# apply the changes
git checkout <commit-you-want-to-split>

# we're almost there, but the changes are in the index at the moment,
# hence one more step (exactly as git gently suggests):
# (use "git reset HEAD <file>..." to unstage)
git reset

После этого вы увидите этот блестящий Unstaged changes after reset:и ваше репо находится в таком состоянии, как будто вы собираетесь зафиксировать все эти файлы. С этого момента вы можете легко зафиксировать его снова, как обычно. Надеюсь, это поможет.

Большинство существующих ответов предлагают использовать интерактивное перебазирование - git rebase -iили похожие. Для тех, кто, как я, страдает фобией «интерактивных» подходов и любит держаться за поручень при спуске по лестнице, вот альтернатива.

Скажите, что ваша история выглядит как … —> P –> Q –> R –> … –> Z = mybranch, и вы хотите разделить P –> Q на два коммита, чтобы в итоге получить P –> Q1 –> Q' –> R' –> … Z' = mybranch, где состояние кода при, R'и т. д. идентично, R, так далее.

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

      git checkout mybranch
git checkout -b mybranch-backup

Сначала проверьте P (фиксация перед тем, где вы хотите разделить), и создайте новую ветку для работы с

      git checkout P
git checkout -b mybranch-splitting

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

      git checkout Q file1.txt file2.txt
[…edit, stage commit with “git add”, etc…]
git commit -m "Refactored the widgets"

Обратите внимание на хэш этого коммита, как. Теперь проверьте полное состояние на отдельном HEAD в Q1, зафиксируйте это (создание) и подтяните к нему рабочую ветку:

      git checkout Q
git reset --soft Q1
git commit -m "Added unit tests for widgets"
git branch -f mybranch-splitting

Ты сейчас на mybranch-splitting в Q', и он должен иметь точно такое же состояние кода, как и было. Теперь переустановите исходную ветку (из Q к Z) на это:

      git rebase --onto HEAD Q mybranch

Теперь должно выглядеть … P -> Q1 –> Q' –> R' –> … Z', как ты и хотел. Поэтому, убедившись, что все работает правильно, вы можете удалить свою рабочую ветку и резервную копию и (при необходимости) нажать перезаписанный mybranchвверх по течению. Если его уже толкнули, вам нужно будет толкнуть с силой, и все обычные предостережения относительно толкания с силой применяются.

      git push --force mybranch
git branch -d mybranch-splitting mybranch-backup

Я сделал это с помощью rebase. Редактирование фиксации не работает для меня, поскольку она уже выбирает файлы фиксации и позволяет вам вносить в нее изменения, но я хотел добавить все файлы как неотслеживаемые файлы, чтобы я мог просто выбрать некоторые из них. Шаги были:

  1. git rebase -i HEAD~5 (Я хотел разделить пятый последний коммит в моей истории)
  2. Скопируйте целевой идентификатор фиксации (он понадобится вам позже)
  3. Отметьте фиксацию с помощью dуронить его; добавить bстрока сразу после фиксации, чтобы остановить процесс перебазирования и продолжить его позже. Даже если это последняя фиксация, это дает вам возможность просто git rebase --abort и сбросить все, если что-то пойдет не так.
  4. Когда перебазирование достигнет точки останова, используйте git cherry-pick -n <COMMIT ID>. Это выберет изменения фиксации, не выбирая саму фиксацию, оставляя их не отслеживаемыми.
  5. Добавьте нужные файлы в первую фиксацию (или используйте git add -i и патч, чтобы вы могли добавлять определенные куски)
  6. Зафиксируйте свои изменения.
  7. Решите, что делать с оставшимися изменениями. В моем случае я хотел, чтобы они были в конце истории, и не было конфликтов, поэтому я сделал git stash, но вы также можете просто зафиксировать их.
  8. git rebase --continue выбрать дополнительные изменения

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

Я написал скрипт, чтобы сделать это проще. Скопируйте содержимое в файл с именемgit-splitв папку, которая находится у вас$PATHи сделать его исполняемым. Тогда вы сможете использоватьgit splitдля запуска этого скрипта. См. комментарии в скрипте, чтобы узнать, как использовать:

      #!/usr/bin/env zsh

# Use `git split` to split a commit into two commits.
# 
# First, use `git checkout --patch HEAD~` to stage changes that you
# want to split from the HEAD commit into a separate commit. Then run
# `git split` (this script) and enter a commit message for the new commit.

set -e

git commit --fixup=HEAD --quiet
git revert --no-commit HEAD
git revert --quit
if ! git commit $@; then
    git cherry-pick --no-commit HEAD
    git reset --soft HEAD~
    exit 0
fi
git -c sequence.editor=: rebase --autosquash --autostash --interactive HEAD~3

Если у вас есть это:

A - B <- mybranch

Где вы зафиксировали некоторый контент в коммите B:

/modules/a/file1
/modules/a/file2
/modules/b/file3
/modules/b/file4

Но вы хотите разделить B на C - D и получить такой результат:

A - C - D <-mybranch

Вы можете, например, разделить контент (контент из разных каталогов в разных коммитах)...

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

git checkout mybranch
git reset --hard A

Создайте первый коммит (C):

git checkout B /modules/a
git add -u
git commit -m "content of /modules/a"

Создайте второй коммит (D):

git checkout B /modules/b
git add -u
git commit -m "content of /modules/b"

Я думаю, что лучший способ я использую git rebase -i, Я создал видео, чтобы показать шаги для разделения коммита: https://www.youtube.com/watch?v=3EzOz7e1ADI

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

  1. Оформите коммит перед тем, который вы хотите разделить, при этом создается тег HEAD.
  2. Черри выберите свой коммит и ответьте «Нет» на вопрос, хотите ли вы совершить коммит немедленно.
  3. Создайте столько коммитов, сколько вам нужно, из изменений.
  4. Cherry выбирает все последующие коммиты, отвечает «Да» на вопрос, сообщение о коммите сохранится.
  5. Теперь создайте новую ветку из HEAD, щелкнув правой кнопкой мыши.
  6. Вернитесь к исходной ветке
  7. Сбросьте ветку до последнего общего коммита с помощью харда, исходные коммиты исчезнут.
  8. Перебазируйте новую в исходную ветку (находясь в исходной ветке, щелкните правой кнопкой мыши новую, перебазируйте)
  9. Новую ветку теперь можно удалить.

Сделанный. Говорю вам, это выглядит сложнее, чем есть на самом деле.

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

Иногда вы не хотите терять сообщение о фиксации, связанное с разделенной фиксацией. Если вы совершили какие-то изменения, которые хотите разделить, вы можете:

  1. Отредактируйте изменения, которые вы хотите удалить из файла (т.е. удалите строки или измените файлы, чтобы они соответствовали первой фиксации). Вы можете использовать комбинацию выбранного вами редактора и чтобы вернуть некоторые изменения в текущее дерево.
  2. Зафиксируйте это редактирование как новую фиксацию с чем-то вроде , поэтому у вас будет исходная фиксация в истории, а также у вас будет еще одна фиксация с внесенными вами изменениями, поэтому файлы в текущей HEAD выглядят так, как будто вы хотите, чтобы они были в первой фиксации после разделения.
      000aaa Original commit
000bbb removal of things that should be changed later
  1. Отменить редактирование с помощью , это создаст откатную фиксацию. Файлы будут выглядеть так же, как при исходной фиксации, а ваша история теперь будет выглядеть так:
      000aaa Original commit
000bbb removal of things that should be changed later
000ccc Revert "removal of things that should be changed later" (assuming you didn't edit commit message immediately)
  1. Теперь вы можете сжать / исправить первые два коммита в один с помощью , при желании изменить фиксацию отката, если вы ранее не давали ей значимого сообщения о фиксации. Вы должны остаться с
      000ddd Original commit, but without some content that is changed later
000eee Things that should be changed later
Другие вопросы по тегам