Объединить или перебазировать произвольно большое количество коммитов

Скажем, мой местный 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 на один шаг короче, и определенно не так страшно выглядит, по крайней мере!)

Оба этих "простых" (?) Способа оставляют вас с веткой с полной историей разработки. Если хочешь, отлично! Если нет, вы должны удалить его.

Другие вопросы по тегам