以前、gitリポジトリで 最初の2つのコミットをつぶす の方法について尋ねました。
解決策はかなり興味深いものであり、実際にはgitの他のいくつかのようにマインドワーピングではありませんが、プロジェクトの開発に沿って手順を何度も繰り返す必要がある場合、それはいまだに傷のofのバッグです。
したがって、私はむしろ一度だけ痛みを経験し、その後標準の対話型リベースを永遠に使用できるようにしたいと思います。
私がやりたいのは、最初の目的のためだけに存在する空の初期コミットを持つことです。コードも何もありません。リベースのベースとなるようにスペースを占有するだけです。
私の質問は、既存のリポジトリがある場合、最初のコミットの前に新しい空のコミットを挿入し、他の全員を前に進めるにはどうすればよいですか?
副作用のない完全に空の新しいコミットを作成するには、おそらくGitの配管を直接使用するのが最適です。そのようにすることで、作業コピーやインデックスに触れたり、クリーンアップする一時的なブランチなどの副作用を回避できます。
コミットを作成するには、そのためのディレクトリツリーが必要なので、最初に空のツリーを作成します。
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 .:実際には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
になりました。その履歴は空のルートコミットを含むように書き換えられました。
注意:--Orphan
がcheckout
に切り替えられていないGitの古いバージョンでは、空のブランチを作成するために配管が必要です。
git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d
Aristotle PagaltzisとUweKleine-Königの回答とRichard Bronoskyのコメントのマージ。
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
(すべてを1か所に置くため)
アリストテレスの答えが好きです。しかし、大規模なリポジトリ(> 5000コミット)では、いくつかの理由でフィルターブランチがリベースよりも優れていることがわかりました1)より高速です2)マージの競合がある場合、人間の介入を必要としません。 3)タグを書き換えることができます-それらを保持します。 filter-branchが機能することに注意してください。これは、各コミットの内容について疑問がないためです。これは、この「リベース」の前とまったく同じです。
私の手順は次のとおりです。
# 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 replace
とgit filter-branch
を使用することは、git rebase
を使用するよりも優れたソリューションだと思います。
その背後にある考え方は次のとおりです。
git filter-branch
を実行します最初の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回実行すると、毎回コミットハッシュを変更しません。だから、ここに私の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
git rebase --root --onto $emptyrootcommit
簡単にトリックを行う必要があります
「git init」の直後に空のコミットを作成するのを忘れた場合、リポジトリの開始時に空のコミットを追加するために使用できる簡単なワンライナーがあります。
git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)
さて、ここに私が思いついたものがあります:
# 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
Kent の改善に基づくbash
スクリプトは次のとおりです。
master
だけでなく元のブランチをチェックアウトします。git checkout --Orphan
は分岐でのみ動作し、デタッチドヘッド状態では動作しないため、新しいルートをコミットするのに十分な時間チェックアウトしてから削除しました。filter-branch
の間に新しいルートコミットのハッシュを使用します(Kentは手動置換のためにプレースホルダーをそこに残しました)。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"
ルートコミットを切り替えるには:
最初に、最初に必要なコミットを作成します。
次に、次を使用してコミットの順序を切り替えます。
git rebase -i --root
以下のように、ルートコミットまでのコミットがエディターに表示されます。
1234古いルートメッセージを選択
ピック0294途中でコミット
ルートに置きたい5678コミットを選択
その後、最初の行に配置することにより、必要なコミットを最初に配置できます。例では:
ルートに置きたい5678コミットを選択
1234古いルートメッセージを選択
ピック0294途中でコミット
エディターを終了すると、コミット順序が変更されます。
PS:gitが使用するエディターを変更するには、次を実行します:
git config --global core.editorname_of_the_editor_program_you_want_to_use
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
は有用なヒントを提供します。