Объединить или переустановить произвольно большое количество коммитов
Скажем, мой локальный git log
показывает:
739b36d3a314483a2d4a14268612cd955c6af9fb a
...
c42fff47a257b72ab3fabaa0bcc2be9cd50d5c89 x
c4149ba120b30955a9285ed721b795cd2b82dd65 y
dce99bcc4b79622d2658208d2371ee490dff7d28 z
Мой удаленный git log
показывает:
c4149ba120b30955a9285ed721b795cd2b82dd65 y
dce99bcc4b79622d2658208d2371ee490dff7d28 z
Какой самый простой способ добраться до этого (при условии сколь угодно большого количества локальных коммитов):
527b5810cfd8f45f18ae807af1fe1e54a0312bce a ... x
c4149ba120b30955a9285ed721b795cd2b82dd65 y
dce99bcc4b79622d2658208d2371ee490dff7d28 z
Ответы
Ответ 1
Один параметр - git rebase -i @{u}
. Я использую это достаточно часто, чтобы нарисовать его как git freebase
(поскольку он работает с коммитами, которые вы можете свободно переупаковывать).
Если вы не знакомы, @{u}
является ярлыком для @{upstream}
или "вверх по течению от текущей ветки".
Ответ 2
Иногда, вы не можете использовать интерактивную ребазу
Если количество промежуточных коммитов между A и X относительно невелико, вы можете получить просто, используя интерактивную перезагрузку:
git rebase -i origin/master
Однако, в моем личном опыте, использование интерактивного перезагрузки при большом количестве коммитов медленнее. Я выполнил интерактивную перезагрузку примерно на сто коммитов одновременно (на машине Windows с использованием Git Bash), и MSysgit потребовалось много времени для создания интерактивного редактора фиксации переадресации, который позволяет вам выбрать, какие операции вы хотите запустить, на какой фиксации, потому что, ну, список оказался очень большим.
В такой ситуации у вас есть несколько обходных решений.
Альтернатива №1: git reset
Смешанные и мягкие сбросы могут использоваться для изменения рабочего дерева или промежуточной области (соответственно) для агрегирования/сбора всех изменений между двумя коммитами A и Y, после чего вы можете совершать все изменения сразу как единый совершить.
Я приведу только пример мягкого reset, так как он уже покидает все, что вам поставлено, а если вы используете смешанный reset, вам придется вносить изменения в любом случае:
# You don't need to create the temp branch A if you
# copy the commit sha A down somewhere so that you can remember it.
git branch temp A
git reset --soft Y
git commit -m "Squash commits A through X"
# Verify that this new commit is equivalent to the final state at A
git diff A
Альтернатива №2: Использование патчей
Другой вариант - просто использовать патчи. Просто создайте diff-патч разницы между A и Y, затем примените патч как новый фиксатор поверх Y:
git diff y a > squash.patch
git checkout -b squash-branch y
git apply squash.patch
git commit -m "Squash commits A through X"
# Verify that this new commit is equivalent to the final state at A
git diff A
Как указано в @A-B-B в комментариях, это не будет работать, если есть двоичные файлы. git diff --binary
можно использовать для вывода diff для двоичных файлов в дополнение к текстовым файлам, но я не уверен, что эти различия могут затем также можно использовать в качестве патчей.
Ответ 3
"Самый простой" всегда немного сложнее. Интерактивная перебаза позволит вам скворовать все, и в некоторой степени "легко".
Еще один "простой" способ, который выглядит немного сложнее, заключается в использовании "squash merge" (который не является фактическим слиянием вообще, но использует тот же базовый код, что и git merge
, так что это делается с помощью та же команда). Скажем, вы находитесь на ветке devel
, вверх по течению которой находится origin/devel
. Сначала мы переименуем devel
в devel-full
, чтобы указать, что он имеет полную последовательность коммитов. Затем мы создадим новое отслеживание devel
origin/devel
и "squash-merge" devel-full
:
git branch -m devel devel-full
git checkout --track origin/devel
git merge --squash devel-full
git commit
Вам нужно отдельно git commit
, поскольку --squash
блокирует фиксацию, которую обычно выполняет git merge
.
Тем не менее третий легкий (?), но немного пугающий способ - проверить версию чата и зафиксировать его. Опять же, предполагая devel
, как и раньше, мы переместим "полное развитие" от имени ветки и создадим новую локальную ветвь, чтобы включить новую фиксацию. На этот раз вместо git merge --squash
мы используем две команды до git commit
:
git branch -m devel devel-full
git checkout --track origin/devel
git rm -rf . # assumes you're in the top directory
git checkout devel-full -- .
git commit
Расписание git rm -rf .
(в области индекса/промежуточной области) каждого отдельного файла, который нужно удалить, но затем git checkout devel-full -- .
сообщает git повторно заполнить область индексирования/промежуточной записи каждым файлом, который существует на кончике devel-full
. Таким образом, это означает "сделать дерево для следующего фиксации, выглядеть точно так же, как дерево для кончика devel-full
".
(Метод remove-and-re-create работает для одного случая, когда merge --squash
не работает: в частности, он работает для "замены" кончика одной ветки кончиком другого, даже если эти две ветки не являются и в противном случае merge --squash
на один шаг короче и, безусловно, не так страшно выглядит, по крайней мере!)
Оба этих других "простых" (?) способа оставляют вам ветку с полной историей развития. Если хочешь, отлично! Если нет, вы должны удалить его.