Сквош первых двух коммитов в 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
последующий коммит нужно было раздавить до исходного):
Вернитесь к последнему коммиту, который мы хотим сформировать в начальный коммит (detach HEAD):
git checkout <sha1_for_B>
Сбросьте указатель ветви на начальный коммит, но индекс и рабочее дерево останутся без изменений:
git reset --soft <sha1_for_A>
Изменить исходное дерево, используя дерево из "B":
git commit --amend
Временно пометьте этот новый начальный коммит (или вы можете запомнить новый коммит sha1 вручную):
git tag tmp
Вернитесь к исходной ветке (для этого примера предположим master):
git checkout master
Повторите все коммиты после B на новый начальный коммит:
git rebase --onto tmp <sha1_for_B>
Удалить временный тег:
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