悪いマージをどのように修正し、良いマージを修正されたマージにリプレイしますか?
-
08-07-2019 - |
質問
私はこれまで気づかずに、数回前に誤って不要なファイル(マージの解決中にfilename.orig
)をリポジトリにコミットしました。リポジトリ履歴からファイルを完全に削除したい。
最初にリポジトリに<=>が追加されないように変更履歴を書き換えることはできますか?
解決
あなたの状況が質問で説明されている状況でない場合は、このレシピを使用しないでください。このレシピは、不適切なマージを修正し、修正されたマージに適切なコミットを再生するためのものです。
filter-branch
は必要な処理を実行しますが、非常に複雑なコマンドであり、おそらくgit rebase
を使用して実行することを選択するでしょう。おそらく個人的な好みです。 rebase
は単一のやや複雑なコマンドで実行できますが、git commit --amend
ソリューションでは同等の論理演算を一度に1ステップずつ実行します。
次のレシピを試してください:
# create and check out a temporary branch at the location of the bad merge
git checkout -b tmpfix <sha1-of-merge>
# remove the incorrectly added file
git rm somefile.orig
# commit the amended merge
git commit --amend
# go back to the master branch
git checkout master
# replant the master branch onto the corrected merge
git rebase tmpfix
# delete the temporary branch
git branch -d tmpfix
(実際には一時的なブランチは必要ないことに注意してください、「デタッチされたHEAD」でこれを行うことができますが、<=>ステップによって生成されたコミットIDをメモして、< =>一時的なブランチ名を使用するのではなくコマンド。)
他のヒント
イントロ:5つのソリューションが利用可能です
元のポスターの状態:
誤って不要なファイルをリポジトリにコミットしました...いくつかのコミット 前...リポジトリ履歴からファイルを完全に削除したい。
それは
filename.orig
が決してなかったような変更履歴を書き換えることが可能 そもそもリポジトリに追加しましたか?
ファイルの履歴を完全に削除するにはさまざまな方法があります git:
- コミットの修正。
- ハードリセット(おそらくプラスリベース)。
- 非インタラクティブなリベース。
- インタラクティブなリベース。
- ブランチのフィルタリング。
元のポスターの場合、コミットを修正することは実際にはオプションではありません それ自体で、彼はその後いくつかの追加のコミットを行ったが、 完全性について、私はそれを行う方法も説明します。 以前のコミットを修正したい。
これらのソリューションにはすべて、変更/書き換え履歴/コミットが含まれます ある意味で別の方法で、コミットの古いコピーを持っている人は誰でもする必要があります 履歴を新しい履歴と再同期するための追加の作業。
解決策1:コミットの修正
以前に誤って変更(ファイルの追加など)を行った場合 コミットし、その変更の履歴がもう存在しないようにします。 前のコミットを修正してファイルを削除するだけです:
git rm <file>
git commit --amend --no-edit
ソリューション2:ハードリセット(場合によってはリベースを追加)
ソリューション#1のように、以前のコミットを削除したいだけなら、 また、単に親にハードリセットを行うオプションもあります。
git reset --hard HEAD^
そのコマンドは、ブランチを前の1番目の st にハードリセットします コミット。
、元のポスターのように、その後いくつかのコミットを行った場合 変更を取り消すコミット、まだハードリセットを使用できます 変更しますが、変更にはリベースの使用も含まれます。手順は次のとおりです 履歴をさかのぼってコミットを修正するために使用できます:
# Create a new branch at the commit you want to amend
git checkout -b temp <commit>
# Amend the commit
git rm <file>
git commit --amend --no-edit
# Rebase your previous branch onto this new commit, starting from the old-commit
git rebase --preserve-merges --onto temp <old-commit> master
# Verify your changes
git diff master@{1}
ソリューション3:非対話型リベース
これは、履歴からコミットを完全に削除する場合にのみ機能します:
# Create a new branch at the parent-commit of the commit that you want to remove
git branch temp <parent-commit>
# Rebase onto the parent-commit, starting from the commit-to-remove
git rebase --preserve-merges --onto temp <commit-to-remove> master
# Or use `-p` insteda of the longer `--preserve-merges`
git rebase -p --onto temp <commit-to-remove> master
# Verify your changes
git diff master@{1}
ソリューション4:インタラクティブなリベース
このソリューションにより、ソリューション#2および #3、つまり、コミットをすぐに変更するよりも履歴内で変更または削除する 前回のコミットですので、どのソリューションを使用するかはあなた次第です。 インタラクティブなリベースは、何百ものコミットをリベースするのには適していません。 パフォーマンス上の理由から、非インタラクティブなリベースまたはフィルターブランチを使用します そのような状況でのソリューション(以下を参照)。
インタラクティブなリベースを開始するには、次を使用します:
git rebase --interactive <commit-to-amend-or-remove>~
# Or `-i` instead of the longer `--interactive`
git rebase -i <commit-to-amend-or-remove>~
これにより、gitはコミット履歴を親の親に巻き戻します 変更または削除することをコミットします。その後、リストが表示されます gitが使用するように設定されているエディターで、逆の順序でコミットを巻き戻します(これは デフォルトではVim):
pick 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
pick 7668f34 Modify Bash config to use Homebrew recommended PATH
pick 475593a Add global .gitignore file for OS X
pick 1b7f496 Add alias for Dr Java to Bash config (OS X)
変更または削除するコミットは、このリストの一番上にあります。 削除するには、リスト内のその行を削除するだけです。それ以外の場合は、<!> quot; pick <!> quot;を置き換えます。と <!> quot; edit <!> quot;次のように、1 st 行に:
edit 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
次に、git rebase --continue
を入力します。コミットを完全に削除することを選択した場合、
その後、あなたがする必要があることすべて(検証以外、最終ステップを参照してください
このソリューション)。一方、コミットを変更する場合は、git
コミットを再適用し、リベースを一時停止します。
Stopped at 00ddaacab0a85d9989217dd9fe9e1b317ed069ac... Add symlinks
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
この時点で、ファイルを削除してコミットを修正してから、 リベース:
git rm <file>
git commit --amend --no-edit
git rebase --continue
それだけです。最後のステップとして、コミットを変更したか削除したか 完全に、他の予期しない変更がないことを確認することは常に良い考えです dによってあなたのブランチに作られましたリベースする前の状態でそれを取得します:
git diff master@{1}
ソリューション5:ブランチのフィルタリング
最後に、このソリューションは、すべての痕跡を完全に消去したい場合に最適です 履歴からのファイルの存在、および他の解決策はどれもまったくありません タスク。
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>'
これは、ルートコミットから開始して、すべてのコミットから<file>
を削除します。もし
代わりに、コミット範囲HEAD~5..HEAD
を書き直したいだけです。
で指摘されているように、追加の引数としてfilter-branch
に渡します
この回答:
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD
繰り返しますが、git filter-branch
が完了したら、通常は確認することをお勧めします
ブランチをその差分と比較することによって、他の予期しない変更がないこと
フィルタリング操作前の前の状態:
フィルターブランチの代替:BFG Repo Cleaner
BFGリポジトリクリーナーツールは--strip-blobs-bigger-than 1M
よりも高速に実行されると聞いたので、オプションとしても。 実行可能な代替手段として filter-branchドキュメントでも公式に言及されています:
git-filter-branchを使用すると、複雑なシェルスクリプトによる書き換えを行うことができます Gitの履歴を表示しますが、おそらく<!>#8217; あなたは<!>#8217;単に大きなファイルやパスワードのような不要なデータを削除しています。 これらの操作については、 BFGを検討してください。 JVMベースのRepo-Cleaner git-filter-branchの代替。通常は少なくとも10〜50倍高速 これらのユースケース、およびまったく異なる特性を持つ:
ファイルの特定のバージョンは、一度だけ正確に消去されます。 BFGは、git-filter-branchとは異なり、処理する機会を与えません ファイルは、どこでいつコミットされたかに基づいて異なります 歴史。この制約により、以下のコアパフォーマンスの利点が得られます。 BFG、および不良データをクレンジングするタスクに適しています-あなたはドン<!>#8217; t 悪いデータがどこにあるかを気にしなさい、あなたはただそれをなくなった。
デフォルトでは、BFGはマルチコアマシンを最大限に活用し、コミットファイルツリーを並行してクレンジングします。 git-filter-branch cleans ですが、順次コミット(つまり、シングルスレッド方式で) 独自の並列性を含むフィルターを書くことが可能 各コミットに対して実行されるスクリプト。
コマンドオプション git-filterブランチよりも制限が厳しく、 不要なデータを削除するタスク-例:<=>。
その他のリソース
それ以降何もコミットしていない場合は、git rm
ファイルとgit commit --amend
だけを入力してください。
お持ちの場合
git filter-branch \
--index-filter 'git rm --cached --ignore-unmatch path/to/file/filename.orig' merge-point..HEAD
は、merge-point
からHEAD
への各変更を通過し、filename.origを削除して、変更を書き換えます。 --ignore-unmatch
を使用すると、何らかの理由でfilename.origが変更から失われても、コマンドは失敗しません。これは、 git-filter-branch manページの例のセクションからの推奨方法です。 。
Windowsユーザーへの注意:ファイルパスにはスラッシュを使用する必要があります
これが最良の方法です:
http://github.com/guides/completely-remove-a -file-from-all-revisions
最初にファイルのコピーを必ずバックアップしてください。
編集
Neon による編集は、レビュー中に残念ながら拒否されました。
以下のネオンの投稿を参照してください。有用な情報が含まれている可能性があります!
E.g。誤ってgitリポジトリにコミットされたすべての*.gz
ファイルを削除するには:
$ du -sh .git ==> e.g. 100M
$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch *.gz' HEAD
$ git push origin master --force
$ rm -rf .git/refs/original/
$ git reflog expire --expire=now --all
$ git gc --prune=now
$ git gc --aggressive --prune=now
それでもまだうまくいきませんでしたか? (現在gitバージョン1.7.6.1です)
$ du -sh .git ==> e.g. 100M
マスターブランチは1つしかなかったので、理由はわかりません。とにかく、新しい空のベアgitリポジトリにプッシュすることで、ついにgitリポジトリを完全にクリーンアップしました。たとえば、
$ git init --bare /path/to/newcleanrepo.git
$ git push /path/to/newcleanrepo.git master
$ du -sh /path/to/newcleanrepo.git ==> e.g. 5M
(yes!)
次に、それを新しいディレクトリに複製し、その.gitフォルダをこのディレクトリに移動しました。例:
$ mv .git ../large_dot_git
$ git clone /path/to/newcleanrepo.git ../tmpdir
$ mv ../tmpdir/.git .
$ du -sh .git ==> e.g. 5M
(ええ、最終的にクリーンアップしました!)
すべてが正常であることを確認したら、../large_dot_git
および../tmpdir
ディレクトリを削除できます(念のため...数週間または1か月後に...)
Gitの履歴を書き換えるには、影響を受けるすべてのコミットIDを変更する必要があるため、プロジェクトで作業している全員がリポジトリの古いコピーを削除し、履歴を消去した後に新しいクローンを作成する必要があります。不便な人が多いほど、それを行う正当な理由が必要になります-余分なファイルは実際には問題を引き起こしていませんが、プロジェクトで作業しているのがあなただけなら、必要に応じてGitの履歴を更新してください!
できるだけ簡単にするために、 BFG Repo-Cleaner は、Git履歴からファイルを削除するために特別に設計されたgit-filter-branch
のよりシンプルで高速な代替手段です。ここでの作業を簡単にする1つの方法は、デフォルトで all の参照(すべてのタグ、ブランチなど)を実際に処理することですが、 10〜50x 速くなりました。
次の手順を注意深く実行する必要があります。 http://rtyley.github.com / bfg-repo-cleaner /#usage -ただし、コアビットはこれだけです。 BFG jar (Java 6以降が必要)で次のコマンドを実行します:
$ java -jar bfg.jar --delete-files filename.orig my-repo.git
リポジトリの履歴全体がスキャンされ、filename.orig
という名前のファイル( 最新コミット)は削除されます。これは、<=>を使用して同じことを行うよりもかなり簡単です!
完全開示:私はBFG Repo-Cleanerの著者です。
You should probably clone your repository first.
Remove your file from all branches history:
git filter-branch --tree-filter 'rm -f filename.orig' -- --all
Remove your file just from the current branch:
git filter-branch --tree-filter 'rm -f filename.orig' -- --HEAD
Lastly you should run to remove empty commits:
git filter-branch -f --prune-empty -- --all
それをCharles Baileyのソリューションに追加するために、git rebase -iを使用して、以前のコミットから不要なファイルを削除しましたが、それは魅力的なものでした。 手順:
# Pick your commit with 'e'
$ git rebase -i
# Perform as many removes as necessary
$ git rm project/code/file.txt
# amend the commit
$ git commit --amend
# continue with rebase
$ git rebase --continue
私が見つけた最も簡単な方法は、leontalbot
(コメントとして)によって提案されました。これは Anoopjohnが公開した投稿。答えとしてそれ自身のスペースの価値があると思います:
(bashスクリプトに変換しました)
#!/bin/bash
if [[ $1 == "" ]]; then
echo "Usage: $0 FILE_OR_DIR [remote]";
echo "FILE_OR_DIR: the file or directory you want to remove from history"
echo "if 'remote' argument is set, it will also push to remote repository."
exit;
fi
FOLDERNAME_OR_FILENAME=$1;
#The important part starts here: ------------------------
git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch $FOLDERNAME_OR_FILENAME" -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now
if [[ $2 == "remote" ]]; then
git push --all --force
fi
echo "Done."
すべてのクレジットはAnnopjohn
、およびそれを指摘するために<=>に割り当てられます。
注
スクリプトには検証が含まれていないことに注意してください。そのため、間違いを犯さないようにし、何か問題が発生した場合に備えてバックアップがあることを確認してください。私にとってはうまくいきましたが、あなたの状況ではうまくいかないかもしれません。注意して使用してください(何が起こっているのか知りたい場合はリンクをたどってください)。
間違いなく、git filter-branch
が道です。
残念ながら、タグ、reflogエントリ、リモートなどで参照できるため、filename.orig
をリポジトリから完全に削除するだけでは不十分です。
これらの参照もすべて削除してから、ガベージコレクターを呼び出すことをお勧めします。
git forget-blob filename.orig
クリーンアップする最新のコミットの場合、gitバージョン2.14.3(Apple Git-98)で試しました:
touch empty
git init
git add empty
git commit -m init
# 92K .git
du -hs .git
dd if=/dev/random of=./random bs=1m count=5
git add random
git commit -m mistake
# 5.1M .git
du -hs .git
git reset --hard HEAD^
git reflog expire --expire=now --all
git gc --prune=now
# 92K .git
du -hs .git
これは、 git filter-branch
の設計対象です。
次も使用できます。
git reset HEAD file/path