Сжать первые два коммита в Git?
С git rebase --interactive <commit>
его помощью вы можете объединить любое количество коммитов в один.
Это все замечательно, если вы не хотите сквошировать коммиты в начальный коммит. Это кажется невозможным.
Есть ли способы этого добиться?
Умеренно связанные:
Что касается связанного с этим вопроса, мне удалось придумать другой подход к необходимости раздавить первый коммит, что, в общем, сделать его вторым.
Если интересно: git: как вставить коммит первым, сдвинув все остальные?
Ответов (9)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
" до корневого коммита.
См. Commit df5df20c1308f936ea542c86df1e9c6974168472 на GitHub от Криса Уэбба ( arachsys
) .
Оригинальный ответ (февраль 2009 г.)
Я полагаю, вы найдете разные рецепты для этого в вопросе SO " Как мне объединить первые два коммита репозитория git? "
Чарльз Бейли дал там наиболее подробный ответ , напомнив нам, что фиксация - это полное дерево (а не просто отличия от предыдущих состояний).
И здесь старая фиксация («начальная фиксация») и новая фиксация (результат сжатия) не будут иметь общего предка.
Это означает, что вы не можете " commit --amend
" первоначальную фиксацию в новую, а затем переустановить на новую исходную фиксацию истории предыдущей первоначальной фиксации (много конфликтов)
(Это последнее предложение уже неверно git rebase -i --root <aBranch>
)
Скорее (с A
исходной «начальной фиксацией», а B
последующую фиксацию нужно было сжать с исходной):
Вернитесь к последней фиксации, которую мы хотим сформировать начальную фиксацию (отсоедините 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
" не приводит к конфликтам во время слияния, так как он перемещает историю, созданную после последней фиксации ( B
), которая должна быть сжата в исходную (которая была A
), чтобы tmp
(представляя сжатую новую первоначальную фиксацию): тривиальная перемотка вперед только сливается.
Это работает для " A-B
", но также и для " A-...-...-...-B
" (таким образом можно втиснуть любое количество коммитов в исходный)
Это превратит вторую фиксацию в первую:
A-B-C-... -> AB-C-...
git filter-branch --commit-filter '
if [ "$GIT_COMMIT" = <sha1ofA> ];
then
skip_commit "[email protected]";
else
git commit-tree "[email protected]";
fi
' HEAD
Сообщение о фиксации для AB будет взято из B (хотя я бы предпочел из A).
Имеет тот же эффект, что и ответ Уве Кляйне-Кенига, но работает и для не начального A.
Как бы то ни было, я избегаю этой проблемы, всегда создавая первую фиксацию «без операции», в которой единственное, что находится в репозитории, - это пустой .gitignore:
https://github.com/DarwinAwardWinner/git-custom-commands/blob/master/bin/git-myinit
Таким образом, нет причин возиться с первой фиксацией.
Есть более простой способ сделать это. Предположим, вы на master
ветке
Создайте новую осиротевшую ветку, которая удалит всю историю коммитов:
$ git checkout --orphan new_branch
Добавьте свое первоначальное сообщение о фиксации:
$ git commit -a
Избавьтесь от старой неслитой основной ветки:
$ git branch -D master
Переименуйте текущую ветку new_branch
в master
:
$ git branch -m master
Если вы просто хотите объединить все коммиты в одну начальную фиксацию, просто сбросьте репозиторий и измените первую фиксацию:
git reset hash-of-first-commit
git add -A
git commit --amend
Git reset оставит рабочее дерево нетронутым, поэтому все по-прежнему там. Так что просто добавьте файлы с помощью команд git add и внесите эти изменения в первую фиксацию. Однако по сравнению с rebase -i вы потеряете возможность объединять комментарии git.
Сжатие первой и второй фиксации приведет к перезаписи первой фиксации. Если у вас есть более одной ветки, основанной на первом коммите, вы должны отрезать эту ветку.
Рассмотрим следующий пример:
a---b---HEAD
\
\
'---d
Сжатие a и b в новую фиксацию «ab» приведет к появлению двух разных деревьев, что в большинстве случаев нежелательно, поскольку git-merge и git-rebase больше не будут работать в двух ветвях.
ab---HEAD
a---d
Если вы действительно этого хотите, это можно сделать. Посмотрите на git-filter-branch мощный (и опасный) инструмент для переписывания истории.
Я переработал скрипт 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