Сквош первых двух коммитов в Git?

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

Это все замечательно, если только вы не хотите сдавить коммиты в начальный коммит. Это кажется невозможным.

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


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

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

Если вам интересно: git: как вставить коммит в качестве первого, сдвинув все остальные?

9 ответов

Решение

Обновление июль 2012 ( git 1.7.12+)

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

git rebase -i --root master

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

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

Смотрите https://github.com/git/git/commit/df5df20c1308f936ea542c86df1e9c6974168472 от Chris Webb ( arachsys )


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

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

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

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

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

  1. Вернитесь к последнему коммиту, который мы хотим сформировать в начальный коммит (detach 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 msgstr "не вызывает конфликтов при слиянии, так как перебирает историю, сделанную после последнего коммита (B) быть раздавленным в начальный (который был A) чтобы tmp (представляет сдавленный новый начальный коммит): только тривиальные быстрые слияния.

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

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

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

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

Я переработал сценарий 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

Для чего бы это ни стоило, я избегаю этой проблемы, всегда создавая "no-op" первый коммит, в котором единственной вещью в репозитории является пустой.gitignore:

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

Таким образом, нет никаких причин возиться с первым коммитом.

Это раздавит второй коммит в первый:

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-merge и git-rebase больше не будут работать в двух ветвях.

ab---HEAD

a---d

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

Для этого вы можете использовать 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
Другие вопросы по тегам