Вставьте фиксацию перед корневым фиксацией в Git?

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

Несмотря на то, что решения довольно интересны и на самом деле не так деформируются как некоторые другие вещи в git, они по-прежнему являются немного мешков с пресловутой болью, если вам нужно многократно повторять процедуру по разработке ваш проект.

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

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

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

Ответ 1

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

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

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

    tree=`git hash-object -wt tree --stdin < /dev/null`
    
  • Теперь мы можем обернуть фиксацию вокруг него:

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

    git rebase --onto $commit --root master
    

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

(N.B.: на практике Id теперь использует filter-branch. Будет редактировать это позже.)


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

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

# first you need a new empty branch; let 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

Voila, вы попали на master, и его история была перезаписана, чтобы включить пустую корневую фиксацию.


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

git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d

Ответ 2

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

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

(просто чтобы положить все в одном месте)

Ответ 3

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

Мои шаги:

# first you need a new empty branch; let 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" означают, что теги будут переписаны, чтобы указать на вновь созданные коммиты.

Ответ 4

git rebase --root --onto $emptyrootcommit

должен легко выполнить трюк

Ответ 5

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

# 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

Ответ 6

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

# first you need a new empty branch; let 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) в дополнение к тегам.

Ответ 7

Я взволновался и написал "idempotent" версию этого приятного script... он всегда будет вставлять ту же пустую фиксацию, и если вы запускаете ее дважды, она не меняет ваши хэши фиксации каждый раз. Итак, здесь я беру 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 [email protected]:path/to/repo

Ответ 8

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

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

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

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

Ниже приведено script для двух первых шагов:

#!/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"

Вы можете запустить этот script без риска (даже если делать резервную копию перед тем, как делать действия, которые вы никогда не делали раньше, это хорошая идея;)), и если результат не тот, который ожидался, просто удалите файлы, созданные в папку .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

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

Ответ 9

Здесь мой bash script на основе Kent отвечает с улучшениями:

  • он проверяет исходную ветвь, а не только master, когда это делается;
  • Я пытался избежать временной ветки, но git checkout --orphan работает только с ветвью, а не с отключенным состоянием, поэтому он проверил достаточно долго, чтобы сделать новую фиксацию root и затем удален;
  • он использует хэш нового корневого фиксации во время 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='[email protected]'
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"

Ответ 10

Чтобы переключить корневую фиксацию:

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

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

git rebase -i -root

Редактор появится с коммитами до момента фиксации корня, например:

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

выбрать 0294 Конец в середине

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

Затем вы можете сначала перенести фиксацию, поставив ее в первую строку. В примере:

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

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

выбрать 0294 Конец в середине

Выход из редактора изменит порядок фиксации.

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

git config --global core.editor name_of_the_editor_program_you_want_to_use

Ответ 11

Следующий ответ Aristotle Pagaltzis и другие, но используя более простые команды

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).

Обратите внимание, что ваше репо не должно содержать никаких локальных изменений, ожидающих совершения.
Примечание git checkout --orphan будет работать в новых версиях git, я думаю.
Обратите внимание, что большую часть времени git status дает полезные подсказки.

Ответ 12

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

git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)

Ответ 13

Запустите новый репозиторий.

Установите дату до нужной даты начала.

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

Когда вы доберетесь до сегодняшнего дня, замените хранилища, и все готово.

Если вы просто сумасшедший (установленный), но разумно умный (вероятно, потому что у вас должно быть определенное количество умений, чтобы придумать сумасшедшие идеи вроде этого), вы будете script процесс.

Это также станет лучше, если вы решите, что прошлое прошло через неделю.

Ответ 14

Я знаю, что этот пост старый, но эта страница является первой, когда Googling "вставляет commit git".

Почему упрощаются простые вещи?

У вас есть A-B-C, и вы хотите A-B-Z-C.

  • git rebase -i trunk (или что-либо до B)
  • изменить выбор для редактирования на линии В
  • внесите изменения: git add ..
  • git commit (git commit --amend, который будет редактировать B и не создавать Z)

[Вы можете сделать столько git commit, сколько хотите, чтобы вставить больше коммитов. Конечно, у вас могут быть проблемы с шагом 5, но разрешение конфликта слияния с git - это навык, который вы должны иметь. Если нет, попрактикуйтесь!]

  1. git rebase --continue

Простой, не так ли?

Если вы понимаете git rebase, добавление фиксации "root" не должно быть проблемой.

Получайте удовольствие от git!