Я столкнулся с тем же самым загадочным вопросом. Я хотел превратить изменения из целого ряда последовательных коммитов в один коммит, но использование интерактивного перебазирования с помощью последовательности сквошов приводило к разочаровывающим конфликтам слияния. Это было некоторое время назад, и я не смог воспроизвести это в минимальном примере только сейчас. В любом случае, я знаю, как решить эту проблему, так что вот вам:
Я представлю два разных способа сделать то, что просил пост - один неуклюжий, но "менее страшный", потому что ясно, что именно вы делаете, и второй, который элегантен и использует только git
команды, но требует, чтобы вы доверяли вашему пониманию git
немного больше.
Настройка
Предположим, у вас есть идентификатор фиксации idA
в качестве начального состояния и идентификатор фиксации idB
в качестве конечного состояния, с кучей других фиксаций между ними. Тот факт, что вы хотите раздавить все промежуточные коммиты, говорит о том, что вам больше не важно, как вы попали из исходного состояния в конечное состояние - вам просто нужен коммит, который перемещает вас из точки idA
в точку idB
.
Предположим, что idB
соответствует текущему HEAD
, и скажем, что это также ваша master
ветвь. Мы собираемся создать новую ветвь с именем squashed
, которая имеет такое же содержимое рабочего дерева, что и master
, но имеет только один коммит после идентификатора коммита idA
.
Решение 1
git checkout master
(возможно, вы уже включены master
)
- Используйте
bash
или файловый менеджер для копирования всего рабочего дерева в другое место. Просто убедитесь, что вы не указали каталог .git
- это поведение по умолчанию в большинстве операционных систем. Итак, чтобы быть ясным - откройте папку репо, выберите все, скопируйте и вставьте ее в какую-то новую папку, на рабочем столе или в любом месте.
git checkout -b squashed idA
, чтобы создать новую ветвь с именем squashed
с idA
в качестве последнего коммита и сделать ее текущей ветвью.
- Вставьте содержимое
master
(которое вы поместили где-то еще в шаге 2) обратно в папку репо. Если ваш файловый менеджер спросит, попросите его заменить все файлы, которые были изменены.
- На верхнем уровне вашего репо
git add .
, а затем git commit
. Ваш новый коммит будет иметь все изменения, которые приведут вас от idA
до idB
.
Решение 2
git branch squashed idA # make a new branch called `squash` with `idA` as its latest commit
git checkout master # master is "idB" in this case.
git symbolic-ref HEAD refs/heads/squashed
git commit
И вуаля. Когда вы используете symbolic-ref
для перемещения HEAD к вершине ветви squash
, набор различий между состоянием рабочего дерева и новым местоположением HEAD вычисляется и разбивается на этапы.
Я хочу ответить на беспокойство, которое некоторые высказывали, что это «опасный», «низкоуровневый» хак git
. Я постараюсь объяснить, как это работает, чтобы вы чувствовали себя непринужденно. Ваша папка .git
- это место хранения всех версий файлов. Две папки в нем - objects
и refs
. Папка refs
содержит файлы, которые сообщают git названия ваших веток и тому, чему они соответствуют. Так, например, если вы откроете .git/refs/heads/master
, вы увидите идентификатор самого последнего коммита master
. Папка objects
содержит набор файлов в подпапках с шестнадцатеричными именами из двух символов. Объектами могут быть несколько разных вещей, включая патчи, коммиты и целые файлы. Также в верхнем уровне папки .git
находится файл index
. Это отличный пост о содержимом индексного файла. Что нужно знать для настоящего обсуждения, так это то, что индексный файл сообщает вам, какой объект (в папке objects
) соответствует последней зафиксированной версии каждого файла в текущей ветви и фиксации.
На этом фоне вот что решение делает: команда symbolic-ref
просто сообщает git
"внезапно", что вы находитесь на ветви squash
вместо ветви master
не касаясь рабочего дерева . Это означает, что все ваши файлы соответствуют состоянию master
, которое вы знаете и любите, но git
видит это как idA
с кучей незафиксированных изменений (то есть файл index
указывает, что текущий проверенный Все файлы имеют версию idA
, и это ссылка, по которой измеряются изменения рабочего дерева). Точные изменения, по сути, заставляют вас заявить idB
. Это именно то, что делает Решение 1 также. В этом случае из-за способа реализации symbolic-ref
все упомянутые изменения уже подготовлены (т. Е. git add
-ed), поэтому все, что вам нужно сделать, это git commit
. В этом нет ничего «опасного», потому что это не меняет master
или любой из objects
, за которым следит git
. Вы только что создали новый файл в папке refs
с именем squashed
и один новый файл в папке objects
, соответствующий этому новому комбинированному коммиту. Ваш master
находится там, где вы его оставили, у вас просто есть новая ветка с именем squashed
с тем же содержимым, но более короткой историей коммитов. Если вы не уверены, что происходит, будьте уверены, что вы можете git checkout master
, и он будет там, где вы его оставили.
Если вы хотите принять squashed
в качестве нового master
, вы можете продолжить и
git branch -m master old-master
git branch -m squashed master
И тогда у вас будет старая ветка со всеми лишними коммитами, сохраненными как old-master
, и master
будет именно тем, что вы хотели.
Надеюсь, это поможет!