Вопрос

На этот вопрос уже есть ответ здесь:

С git rebase --interactive <commit> вы можете объединить любое количество коммитов в один.

Это все замечательно, если только вы не хотите объединить коммиты в первоначальный коммит.Кажется, это невозможно сделать.

Есть ли способы добиться этого?


Умеренно связанные:

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

Если вы заинтересованы: мерзавец:как вставить коммит первым, сместив все остальные?

Это было полезно?

Решение

Обновление от июля 2012 г. (мерзавец 1.7.12+)

Теперь вы можете перенести все коммиты в корневой каталог и выбрать второй коммит. Y быть раздавленным первым X.

git rebase -i --root master

pick sha1 X
squash sha1 Y
pick sha1 Z
git rebase [-i] --root $tip

Эту команду теперь можно использовать для перезаписи всей истории, начиная с «$tip" вплоть до корневого коммита.

Видеть зафиксируйте df5df20c1308f936ea542c86df1e9c6974168472 на GitHub от Крис Уэбб (англ.arachsys).


Оригинальный ответ (февраль 2009 г.)

Я думаю, вы найдете разные рецепты для этого в вопросе SO "Как объединить первые два коммита репозитория git?"

Чарльз Бэйли при условии, что там больше всего подробный ответ, напоминая нам, что коммит представляет собой полное дерево (а не просто отличается от предыдущих состояний).
И здесь старый коммит («начальный коммит») и новый коммит (результат сжатия) не будут иметь общего предка.
Это значит, что ты не можешь»commit --amend"первоначальный коммит в новый, а затем перебазировать на новый первоначальный коммит историю предыдущего первоначального коммита (множество конфликтов)

(Последнее предложение уже не соответствует действительности с git rebase -i --root <aBranch>)

Скорее (с A исходный «первоначальный коммит» и B последующий коммит необходимо было вставить в первоначальный):

  1. Вернитесь к последнему коммиту, который мы хотим сформировать в качестве первоначального коммита (отсоединить HEAD):

    git checkout <sha1_for_B>
    
  2. Сбросьте указатель ветки на исходную фиксацию, но оставив индекс и рабочее дерево нетронутыми:

    git reset --soft <sha1_for_A>
    
  3. Измените исходное дерево, используя дерево из «B»:

    git commit --amend
    
  4. Временно пометьте этот новый первоначальный коммит (или вы можете запомнить новый коммит sha1 вручную):

    git tag tmp
    
  5. Вернитесь в исходную ветку (в этом примере предположим, что это master):

    git checkout master
    
  6. Воспроизведите все коммиты после B в новый первоначальный коммит:

    git rebase --onto tmp <sha1_for_B>
    
  7. Удалите временный тег:

    git tag -d tmp
    

Таким образом, "rebase --onto" не вызывает конфликтов во время слияния, поскольку перебазирует историю сделано после последний коммит (B), чтобы сжать его в первоначальный (который был A) к tmp (представляющий раздавленный новый первоначальный коммит):только тривиальные слияния с быстрой перемоткой вперед.

Это работает для "A-B", но и "A-...-...-...-B" (таким образом любое количество коммитов можно объединить с первоначальным)

Другие советы

Я переработал сценарий VonC, чтобы он делал все автоматически и ни о чем меня не просил.Вы передаете ему два коммита SHA1, и он объединяет все между ними в один коммит с именем «сжатая история»:

#!/bin/sh
# Go back to the last commit that we want
# to form the initial commit (detach HEAD)
git checkout $2

# reset the branch pointer to the initial commit (= $1),
# but leaving the index and working tree intact.
git reset --soft $1

# amend the initial tree using the tree from $2
git commit --amend -m "squashed history"

# remember the new commit sha1
TARGET=`git rev-list HEAD --max-count=1`

# go back to the original branch (assume master for this example)
git checkout master

# Replay all the commits after $2 onto the new initial commit
git rebase --onto $TARGET $2

Чего бы это ни стоило, я избегаю этой проблемы, всегда создавая первый «бездействующий» коммит, в котором единственное, что есть в репозитории, — это пустой .gitignore:

https://github.com/DarwinAwardWinner/git-custom-commands/blob/master/bin/git-myinit

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

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

git reset hash-of-first-commit
git add -A
git commit --amend

Сброс Git оставит рабочее дерево нетронутым, поэтому все останется на месте.Поэтому просто добавьте файлы с помощью команд git add и внесите эти изменения в первый коммит.Однако по сравнению с rebase -i вы потеряете возможность объединять комментарии git.

Это объединит второй коммит в первый:

A-B-C-... -> AB-C-...

git filter-branch --commit-filter '
    if [ "$GIT_COMMIT" = <sha1ofA> ];
    then
        skip_commit "$@";
    else
        git commit-tree "$@";
    fi
' HEAD

Сообщение фиксации для AB будет взято из B (хотя я бы предпочел из A).

Имеет тот же эффект, что и ответ Уве Кляйне-Кёнига, но работает и для неначальной буквы A.

Сжатие первого и второго коммита приведет к перезаписи первого коммита.Если у вас есть более одной ветки, основанной на первом коммите, вы должны отрезать эту ветку.

Рассмотрим следующий пример:

a---b---HEAD
 \
  \
   '---d

Сжатие a и b в новый коммит «ab» приведет к созданию двух разных деревьев, что в большинстве случаев нежелательно, поскольку git-слияние и git-перебазировать больше не будет работать в двух ветках.

ab---HEAD

a---d

Если вы действительно этого хотите, это можно сделать.Посмотри на git-фильтр-ветвь за мощный (и опасный) инструмент переписывания истории.

Для этого вы можете использовать git filter-branch.например

git filter-branch --parent-filter \
'if test $GIT_COMMIT != <sha1ofB>; then cat; fi'

Это приводит к тому, что AB-C выбрасывает журнал фиксации A.

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

git rebase HEAD^^ -i

Есть более простой способ сделать это.Предположим, вы находитесь на master ветвь

Создайте новую осиротевшую ветку, которая удалит всю историю коммитов:

$ git checkout --orphan new_branch

Добавьте исходное сообщение о коммите:

$ git commit -a

Избавьтесь от старой неслитой основной ветки:

$ git branch -D master

Переименуйте текущую ветку new_branch к master:

$ git branch -m master
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top