Вопрос

Я уже спрашивал раньше о том, как раздавите первые два коммита в репозитории 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)
  • изменить выбор для редактирования в строке B
  • внесите изменения: git add ..
  • git commit (git commit --amend который будет редактировать B, а не создавать Z)
  • [Вы можете сделать столько git rebase --continue, сколько хотите, чтобы вставить больше коммитов. Конечно, у вас могут возникнуть проблемы с шагом 5, но разрешение конфликта слияния с помощью git - это навык, который вам нужно иметь. Если нет, практикуйтесь!]

      <Литий> git rebase

      Просто, не правда ли?

      Если вы понимаете <=>, добавление «корневого» коммита не должно быть проблемой.

      Веселитесь с мерзавцем!

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top