Gitのルートコミットの前にコミットを挿入しますか?
-
22-07-2019 - |
質問
最初の2つのコミットを破棄する方法について以前に質問しました gitリポジトリ内。
ソリューションはかなり興味深いものであり、実際にはgitの他の何かほどマインドワープではありませんが、プロジェクトの開発に沿って何度も手順を繰り返す必要がある場合、それはいまだに傷のofのバッグです。
だから、私はむしろ一度だけ痛みを経験し、標準の対話型リベースを永遠に使用できるようにしたいと思います。
したがって、私がやりたいことは、最初の目的のためだけに存在する空の初期コミットを行うことです。コードも何もありません。リベースのベースとなるようにスペースを占有するだけです。
次に、既存のリポジトリがある場合、最初のコミットの前に新しい空のコミットを挿入し、他の全員を前方にシフトするにはどうすればよいですか?
解決
2017年半ばの回答
副作用のない新しい完全に空のコミットを作成するには、おそらく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 .:実際には、I&#8217; dは 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
Voila、あなたは master
で履歴が書き換えられ、空のルートコミットが含まれるようになりました。
NB .: -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か所に置くため)
アリストテレスの答えが好きです。しかし、大きなリポジトリ(&gt; 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 rebase --root --onto $ emptyrootcommit
簡単にトリックを行う必要があります
私は興奮して、この素敵なスクリプトの「べき等」バージョンを作成しました...常に同じ空のコミットを挿入します。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
さて、ここに私が思いついたものがあります:
# 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"
このスクリプトをリスクなしで実行できます(これまでに実行したことがないアクションを実行する前にバックアップを行うことをお勧めします;))、結果が予期したものでない場合は、フォルダーに作成されたファイルを削除します< code> .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
今、すべてを行う必要があります;)
&quot; git init&quot;の直後に空のコミットを作成するのを忘れた場合、リポジトリの開始時に空のコミットを追加するために使用できるシンプルなワンライナーです:
git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)
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='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
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
は有用なヒントを提供します。
新しいリポジトリを開始します。
希望する開始日に日付を戻します。
あなたがやりたいと思うようにすべてを行い、そのようにやりたいと思ったときを反映するようにシステム時間を調整します。必要に応じて既存のリポジトリからファイルをプルして、不必要な入力を避けます。
今日になったら、リポジトリを入れ替えれば完了です。
単にクレイジー(確立)であるが、合理的に(おそらく、このようなクレイジーなアイデアを考え出すためにある程度のスマートが必要なため)インテリジェントである場合、プロセスをスクリプト化します。
それはまた、今から1週間後に過去を別の方法で発生させたいと判断したときに、より良いものになります。
この投稿は古いことは知っていますが、このページは、グーグルの「コミットgitの挿入」の最初のページです。
単純なものを複雑にする理由
A-B-Cがあり、A-B-Z-Cが必要です。
-
git rebase -i trunk
(またはBの前のもの) - B行で編集するピックを変更する
- 変更を加える:
git add ..
-
git commit
(git commit --amend
はBを編集し、Zは作成しません)
[ここに必要な数の git commit
を作成して、さらにコミットを挿入できます。もちろん、ステップ5で問題が発生する可能性がありますが、gitとのマージの競合を解決することは必要なスキルです。そうでない場合は、練習してください!]
-
git rebase --continue
簡単ですね。
git rebase
を理解している場合、「ルート」コミットを追加しても問題はありません。
gitをお楽しみください!