Вставить коммит перед корневым коммитом в Git? - PullRequest
197 голосов
/ 14 марта 2009

Я уже спрашивал о том, как раздавить первые два коммита в репозитории git.

Хотя решения довольно интересные и не настолько изнурительные, как некоторые другие вещи в git, они все еще являются частью мешающего всем, если вам нужно повторять эту процедуру много раз по мере развития вашего проекта ,

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

То, что я хочу сделать, это иметь пустой начальный коммит, который существует исключительно с целью быть первым. Нет кода, нет ничего. Просто занимая место, чтобы стать базой для перебазирования.

Тогда у меня возникает вопрос, имея существующее хранилище, как мне вставить новый пустой коммит перед первым и переместить всех остальных вперед?

Ответы [ 14 ]

280 голосов
/ 15 марта 2009

Ответ на середину 2017 года

Создание нового полностью пустого коммита без побочных эффектов, вероятно, лучше всего делать напрямую с использованием сантехники Git. Это позволяет избежать каких-либо побочных эффектов: не трогать рабочую копию или указатель, не удалять временные ветви и т. Д. Итак:

  1. Чтобы создать коммит, нам нужно дерево каталогов, поэтому сначала мы создаем пустое:

    tree=`git hash-object -wt tree --stdin < /dev/null`
    
  2. Теперь мы можем обернуть коммит вокруг него:

    commit=`git commit-tree -m 'root commit' $tree`
    
  3. И теперь мы можем сделать это:

    git rebase --onto $commit --root master
    

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

(N.B .: на практике я бы сейчас использовал filter-branch. Отредактирую это позже.)


Исторический ответ (на который ссылаются другие ответы)

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

# first you need a new empty branch; let's call it `newroot`
git checkout --orphan newroot
git rm -rf .

# then you apply the same steps
git commit --allow-empty -m 'root commit'
git rebase --onto newroot --root master
git branch -d newroot

Вуаля, вы оказались на master с его историей, переписанной с включением пустого корневого коммита.


Примечание: на старых версиях Git, в которых отсутствует переключатель --orphan на checkout, вам понадобится сантехника для создания пустой ветви:

git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d
28 голосов
/ 16 марта 2012

Слияние ответов Аристотеля Пагальциса и Уве Кляйн-Кенига и комментария Ричарда Броноски.

git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d
# touch .gitignore && git add .gitignore # if necessary
git commit --allow-empty -m 'initial'
git rebase --onto newroot --root master
git branch -d newroot

(просто собрать все в одном месте)

11 голосов
/ 29 марта 2013

Мне нравится ответ Аристотеля. Но обнаружил, что для большого репозитория (> 5000 коммитов) ветвь фильтра работает лучше, чем ребаз по нескольким причинам 1) это быстрее 2) это не требует вмешательства человека, когда есть конфликт слияния. 3) он может перезаписывать теги - сохраняя их. Обратите внимание, что фильтр-ветвь работает, потому что нет никаких сомнений относительно содержимого каждого коммита - он точно такой же, как и до этого 'rebase'.

Мои шаги:

# first you need a new empty branch; let's call it `newroot`
git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d

# then you apply the same steps
git commit --allow-empty -m 'root commit'

# then use filter-branch to rebase everything on newroot
git filter-branch --parent-filter 'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat master

Обратите внимание, что параметры '--tag-name-filter cat' означают, что теги будут переписаны для указания на вновь созданные коммиты.

5 голосов
/ 18 апреля 2014

Я успешно использовал кусочки ответа Аристотеля и Кента:

# first you need a new empty branch; let's call it `newroot`
git checkout --orphan newroot
git rm -rf .
git commit --allow-empty -m 'root commit'
git filter-branch --parent-filter \
'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat -- --all
# clean up
git checkout master
git branch -D newroot
# make sure your branches are OK first before this...
git for-each-ref --format="%(refname)" refs/original/ | \
xargs -n 1 git update-ref -d

Это также переписывает все ветви (не только master) в дополнение к тегам.

3 голосов
/ 06 февраля 2013

Я был взволнован и написал «идемпотентную» версию этого замечательного скрипта ... он всегда будет вставлять один и тот же пустой коммит, и если вы запустите его дважды, он не изменит ваши хеши коммитов каждый раз. Итак, вот мой взгляд на git-insert-empty-root :

#!/bin/sh -ev
# idempotence achieved!
tmp_branch=__tmp_empty_root
git symbolic-ref HEAD refs/heads/$tmp_branch
git rm --cached -r . || true
git clean -f -d
touch -d '1970-01-01 UTC' .
GIT_COMMITTER_DATE='1970-01-01T00:00:00 +0000' git commit \
  --date='1970-01-01T00:00:00 +0000' --allow-empty -m 'initial'
git rebase --committer-date-is-author-date --onto $tmp_branch --root master
git branch -d $tmp_branch

Стоит ли дополнительная сложность? возможно нет, но я буду использовать это.

Это ДОЛЖНО также позволять выполнять эту операцию над несколькими клонированными копиями репозитория, и в конечном итоге с одинаковыми результатами, поэтому они все еще совместимы ... тестирование ... да, это работает, но нужно также удалить и снова добавьте пульты, например:

git remote rm origin
git remote add --track master user@host:path/to/repo
3 голосов
/ 17 января 2011

git rebase --root --onto $emptyrootcommit

должен сделать трюк легко

2 голосов
/ 10 декабря 2017

Вот простая однострочная строка, которую можно использовать для добавления пустого коммита в начале репозитория, если вы забыли создать пустой коммит сразу после «git init»:

git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)
2 голосов
/ 31 мая 2015

Я думаю, что использование git replace и git filter-branch является лучшим решением, чем использование git rebase:

  • лучшее исполнение
  • проще и менее рискованно (вы можете проверить свой результат на каждом шаге и отменить то, что вы сделали ...)
  • хорошо работает с несколькими ветками с гарантированными результатами

Идея заключается в следующем:

  • Создать новый пустой коммит далеко в прошлом
  • Заменить старый корневой коммит на точно такой же, за исключением того, что новый корневой коммит добавляется как родительский
  • Убедитесь, что все в порядке и выполните команду git filter-branch
  • Еще раз проверьте, что все в порядке и очистите ненужные файлы git.

Вот скрипт для 2 первых шагов:

#!/bin/bash
root_commit_sha=$(git rev-list --max-parents=0 HEAD)
git checkout --force --orphan new-root
find . -path ./.git -prune -o -exec rm -rf {} \; 2> /dev/null
git add -A
GIT_COMMITTER_DATE="2000-01-01T12:00:00" git commit --date==2000-01-01T12:00:00 --allow-empty -m "empty root commit"
new_root_commit_sha=$(git rev-parse HEAD)

echo "The commit '$new_root_commit_sha' will be added before existing root commit '$root_commit_sha'..."

parent="parent $new_root_commit_sha"
replacement_commit=$(
 git cat-file commit $root_commit_sha | sed "s/author/$parent\nauthor/" |
 git hash-object -t commit -w --stdin
) || return 3
git replace "$root_commit_sha" "$replacement_commit"

Вы можете запустить этот сценарий без риска (даже если создание резервной копии перед выполнением действия, которое вы никогда не делали ранее, является хорошей идеей;)), и если результат не тот, который ожидается, просто удалите файлы, созданные в папке .git/refs/replace и попробуйте еще раз;)

Убедившись, что состояние хранилища соответствует ожидаемому, выполните следующую команду, чтобы обновить историю всех ветвей :

git filter-branch -- --all

Теперь вы должны увидеть 2 истории: старую и новую (дополнительную информацию см. В справке по filter-branch). Вы можете сравнить 2 и еще раз проверить, все ли в порядке. Если вы удовлетворены, удалите ненужные файлы:

rm -rf ./.git/refs/original
rm -rf ./.git/refs/replace

Вы можете вернуться в ветку master и удалить временную ветку:

git checkout master
git branch -D new-root

Теперь все должно быть сделано;)

2 голосов
/ 14 марта 2009

Ну, вот что я придумала:

# Just setting variables on top for clarity.
# Set this to the path to your original repository.
ORIGINAL_REPO=/path/to/original/repository

# Create a new repository…
mkdir fun
cd fun
git init
# …and add an initial empty commit to it
git commit --allow-empty -m "The first evil."

# Add the original repository as a remote
git remote add previous $ORIGINAL_REPO
git fetch previous

# Get the hash for the first commit in the original repository
FIRST=`git log previous/master --pretty=format:%H  --reverse | head -1`
# Cherry-pick it
git cherry-pick $FIRST
# Then rebase the remainder of the original branch on top of the newly 
# cherry-picked, previously first commit, which is happily the second 
# on this branch, right after the empty one.
git rebase --onto master master previous/master

# rebase --onto leaves your head detached, I don't really know why)
# So now you overwrite your master branch with the newly rebased tree.
# You're now kinda done.
git branch -f master
git checkout master
# But do clean up: remove the remote, you don't need it anymore
git remote rm previous
1 голос
/ 18 февраля 2017

Чтобы переключить корневой коммит:

Сначала создайте коммит, который вы хотите первым.

Во-вторых, измените порядок коммитов, используя:

git rebase -i --root

Редактор будет отображаться с коммитами до момента фиксации root, например:

выбрать 1234 старого корневого сообщения

выбрать 0294 коммит в середине

выберите 5678 коммитов, которые вы хотите поместить в корень

Затем вы можете поместить нужный коммит первым, поместив его в первую строку. В примере:

выберите 5678 коммитов, которые вы хотите поместить в корень

выбрать 1234 старого корневого сообщения

выбор 0294 коммит в середине

Выйдите из редактора, порядок фиксации будет изменен.

PS: Чтобы изменить редактор, используемый git, запустите:

git config --global core.editor name_of_the_editor_program_you_want_to_use

...