Объединить или перебазировать произвольно большое количество коммитов
Скажем, мой местный git log
показывает:
739b36d3a314483a2d4a14268612cd955c6af9fb a
...
c42fff47a257b72ab3fabaa0bcc2be9cd50d5c89 x
c4149ba120b30955a9285ed721b795cd2b82dd65 y
dce99bcc4b79622d2658208d2371ee490dff7d28 z
Мой пульт git log
показывает:
c4149ba120b30955a9285ed721b795cd2b82dd65 y
dce99bcc4b79622d2658208d2371ee490dff7d28 z
Какой самый простой способ добраться до этого (при условии сколь угодно большого числа локальных коммитов):
527b5810cfd8f45f18ae807af1fe1e54a0312bce a ... x
c4149ba120b30955a9285ed721b795cd2b82dd65 y
dce99bcc4b79622d2658208d2371ee490dff7d28 z
3 ответа
Один вариант git rebase -i @{u}
, Я использую это достаточно часто, чтобы я назвал его как git freebase
(поскольку он работает на коммитах, которые вы можете свободно перебазировать).
Если ты не знаком, @{u}
это ярлык для @{upstream}
или "вверх по течению от текущей ветви".
Иногда вы не хотите использовать интерактивную ребазу
Если число промежуточных коммитов между A и X относительно невелико, вы можете просто обойтись, используя интерактивный перебазирование:
git rebase -i origin/master
Однако,по моему личному опыту, использование интерактивного перебазирования для большого количества коммитов является медленным. Я запустил интерактивную перебазировку примерно на сотне коммитов одновременно (на машине с Windows, использующей Git Bash), и msysgit потребовалось много времени, чтобы создать интерактивный редактор фиксации перебазирования, который позволяет вам выбирать, какие операции вы хотите запускать на какой коммит, потому что, ну, список оказался очень большим.
В такой ситуации у вас есть пара обходных путей.
Альтернатива № 1:git reset
Смешанный и мягкий сброс может использоваться для изменения вашего рабочего дерева или промежуточной области (соответственно), чтобы агрегировать / собирать все изменения между двумя коммитами A и Y, после чего вы можете зафиксировать все модификации одновременно как один коммит.
Я приведу только пример мягкого сброса, так как он уже оставляет все подготовленным для вас, а если вы используете смешанный сброс, вам все равно придется вносить изменения:
# 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
Как отмечает @ABB в комментариях, это не будет работать, если в него включены двоичные файлы.git diff --binary
может быть использован для вывода diff для двоичных файлов в дополнение к текстовым файлам, но я не уверен, что эти diff-файлы затем могут быть использованы как патчи.
"Самый простой" всегда немного сложнее. Интерактивная перебазировка позволит вам раздавить все, и в некотором смысле это "легко".
Другой "простой" способ, который выглядит немного сложным, состоит в использовании "слияния сквоша" (которое вообще не является фактическим слиянием, но использует тот же основной код, что и git merge
так что это делается с помощью той же команды). Допустим, вы на ветке devel
чей вверх по течению origin/devel
, Сначала мы переименуем devel
в devel-full
чтобы указать, что это тот, с полной последовательностью коммитов. Тогда мы создадим новый devel
отслеживание origin/devel
и "сквош-слияние" 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 .
планирует (в области index/staging) каждый удаляемый файл, но затем git checkout devel-full -- .
говорит git повторно заполнять область индекса / промежуточной области каждым файлом, который существует в конце devel-full
, Так что это означает "сделать дерево для следующего коммита, выглядеть точно так же, как дерево для кончика devel-full
".
(Метод удаления и повторного создания работает для одного случая, когда merge --squash
не: в частности, он работает для "замены" кончика одной ветви на кончик другой, даже если две ветви не связаны и, следовательно, не могут быть объединены. Иначе merge --squash
на один шаг короче, и определенно не так страшно выглядит, по крайней мере!)
Оба этих "простых" (?) Способа оставляют вас с веткой с полной историей разработки. Если хочешь, отлично! Если нет, вы должны удалить его.