Вставить коммит перед корневым коммитом в Git?
-
22-07-2019 - |
Вопрос
Я уже спрашивал раньше о том, как раздавите первые два коммита в репозитории git.
Хотя решения довольно интересные и на самом деле не такие ошеломляющие, как некоторые другие вещи в git, они все равно немного навредят, если вам нужно повторять процедуру много раз на протяжении разработки вашего проекта.
Итак, я бы предпочел пройти через боль только один раз, а затем иметь возможность вечно использовать стандартную интерактивную перебазировку.
То, что я хочу сделать, это создать пустой начальный коммит, который существует исключительно для того, чтобы быть первым.Ни кода, ничего.Просто занимает место, чтобы оно могло стать основой для перебазирования.
Тогда мой вопрос заключается в том, что, имея существующий репозиторий, как мне вставить новый пустой коммит перед первым и перенести всех остальных вперед?
Решение
ответ в середине 2017 года
Создание нового абсолютно пустого коммита без побочных эффектов, вероятно, лучше всего делать с помощью прямого подключения Git & # 8217; Это позволяет избежать каких-либо побочных эффектов: не трогать рабочую копию или индекс, нет временных ветвей для очистки и т. Д. Итак:
<Ол>Чтобы создать коммит, нам нужно дерево каталогов, поэтому сначала мы создаем пустое:
tree=`git hash-object -wt tree --stdin < /dev/null`
Теперь мы можем обернуть коммит вокруг него:
commit=`git commit-tree -m 'root commit' $tree`
И теперь мы можем переназначить это:
git rebase --onto $commit --root master
И это & # 8217; Вы можете переставить все это в одну строку, если вы достаточно хорошо знаете свою оболочку.
(N.B .: на практике я & # 8217; буду теперь использовать filter-branch
. Отредактирую это позже.)
Исторический ответ (на который ссылаются другие ответы)
Здесь & # 8217; более чистая реализация того же решения, так как оно работает без необходимости создавать дополнительный репозиторий, обходиться с удаленными устройствами и исправлять отдельную головку:
# 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
Вуаля, ты & # 8217; в результате master
его история была переписана с добавлением пустого корневого коммита.
Примечание: на старых версиях Git, в которых нет переключателя --orphan
на checkout
, вам понадобится сантехника для создания пустой ветви:
git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d
Другие советы
Слияние Аристотеля Пагальтзиса и Уве Кляйн-К & # 246; ответы Ниг и комментарий Ричарда Броноски.
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
(просто собрать все в одном месте)
Мне нравится ответ Аристотеля. Но обнаружил, что для большого репозитория (& Gt; 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' означают, что теги будут переписаны для указания на вновь созданные коммиты.
Я успешно использовал фрагменты ответа Аристотеля и Кента:
# 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
) в дополнение к тегам.
git rebase --root --onto $emptyrootcommit
должно получиться это легко
Я был взволнован и написал «идемпотентную» версию этого замечательного скрипта ... он всегда будет вставлять один и тот же пустой коммит, и если вы запустите его дважды, он не изменит ваши хеши коммитов каждый раз. Итак, вот мое мнение о 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
Ну, вот что я придумала:
# 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
Я думаю, что используя 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
Теперь все должно быть сделано ;)
Вот простая однострочная строка, которую можно использовать для добавления пустого коммита в начале репозитория, если вы забыли создать пустой коммит сразу после " git init ": р>
git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)
Вот мой bash
сценарий, основанный на Кентответ с улучшениями:
- он проверяет исходную ветвь, а не только
master
, когда закончите; - Я пытался избежать временной ветки, но
git checkout --orphan
работает только с веткой, а не с отделенным головным состоянием, поэтому он проверяется достаточно долго, чтобы создать новую корневую фиксацию, а затем удаляется; - он использует хэш новой корневой фиксации во время
filter-branch
(Кент оставил там заполнитель для замены вручную); - тот самый
filter-branch
операция перезаписывает только локальные ветви, но не удаленные - метаданные автора и коммиттера стандартизированы таким образом, что корневая фиксация идентична во всех репозиториях.
#!/bin/bash
# Save the current branch so we can check it out again later
INITIAL_BRANCH=`git symbolic-ref --short HEAD`
TEMP_BRANCH='newroot'
# Create a new temporary branch at a new root, and remove everything from the tree
git checkout --orphan "$TEMP_BRANCH"
git rm -rf .
# Commit this empty state with generic metadata that will not change - this should result in the same commit hash every time
export GIT_AUTHOR_NAME='nobody'
export GIT_AUTHOR_EMAIL='nobody@example.org'
export GIT_AUTHOR_DATE='2000-01-01T00:00:00+0000'
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
git commit --allow-empty -m 'empty root'
NEWROOT=`git rev-parse HEAD`
# Check out the commit we just made and delete the temporary branch
git checkout --detach "$NEWROOT"
git branch -D "$TEMP_BRANCH"
# Rewrite all the local branches to insert the new root commit, delete the
# original/* branches left behind, and check out the rewritten initial branch
git filter-branch --parent-filter "sed \"s/^\$/-p $NEWROOT/\"" --tag-name-filter cat -- --branches
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
git checkout "$INITIAL_BRANCH"
Чтобы переключить корневой коммит:
Сначала создайте коммит, который вы хотите первым.
Во-вторых, измените порядок коммитов, используя:
git rebase -i --root
Редактор будет отображаться с коммитами вплоть до корневого коммита, например:
выберите 1234 старое корневое сообщение
выбери 0294 коммит в середине
выберите 5678 коммит, который вы хотите поместить в корень
Затем вы можете поместить нужный коммит первым, поместив его в первую строку. В примере:
выберите 5678 коммит, который вы хотите поместить в корень
выберите 1234 старое корневое сообщение
выбери 0294 коммит в середине
Выйдите из редактора, порядок фиксации будет изменен.
PS: чтобы изменить редактор git, запустите:
git config --global core.editor name_of_the_editor_program_you_want_to_use
После ответа Аристотель Пагальцис и другие, но с использованием более простых команд
zsh% git checkout --orphan empty
Switched to a new branch 'empty'
zsh% git rm --cached -r .
zsh% git clean -fdx
zsh% git commit --allow-empty -m 'initial empty commit'
[empty (root-commit) 64ea894] initial empty commit
zsh% git checkout master
Switched to branch 'master'
zsh% git rebase empty
First, rewinding head to replay your work on top of it...
zsh% git branch -d empty
Deleted branch empty (was 64ea894).
Обратите внимание, что в вашем репо не должно быть локальных изменений, ожидающих принятия.
Думаю, Note git checkout --orphan
будет работать с новыми версиями git.
Обратите внимание, что большую часть времени git status
дает полезные советы.
Запустите новый репозиторий.
Установите дату обратно на желаемую дату начала.
Делайте все так, как вы хотите, чтобы вы это делали, настраивая системное время так, чтобы отражать, когда вы хотели, чтобы вы делали это таким образом. Извлекайте файлы из существующего репозитория по мере необходимости, чтобы избежать ненужного набора текста.
Когда вы доберетесь до сегодняшнего дня, поменяйте местами репозитории, и все готово.
Если вы просто сумасшедший (устоявшийся), но достаточно умный (вероятно, потому что вам нужно определенное количество умов, чтобы придумывать сумасшедшие идеи, подобные этой), вы будете писать сценарий процесса.
Это также сделает его более приятным, если вы решите, что прошлое должно было случиться как-то иначе через неделю.
Я знаю, что это сообщение старое, но эта страница первая, когда Googling & вставляет коммит git ".
Зачем делать простые вещи сложными?
У вас есть A-B-C, и вы хотите A-B-Z-C.
<Ол>git rebase -i trunk
(или что-нибудь до B) git add ..
git commit
(git commit --amend
который будет редактировать B, а не создавать Z) [Вы можете сделать столько git rebase --continue
, сколько хотите, чтобы вставить больше коммитов. Конечно, у вас могут возникнуть проблемы с шагом 5, но разрешение конфликта слияния с помощью git - это навык, который вам нужно иметь. Если нет, практикуйтесь!]
-
<Литий>
git rebase
Ол>
Просто, не правда ли?
Если вы понимаете <=>, добавление «корневого» коммита не должно быть проблемой.
Веселитесь с мерзавцем!