悪いマージをどのように修正し、良いコミットを修正されたマージに再生しますか?


407

filename.origマージの解決中に)不要なファイルを数回前に自分のリポジトリに誤ってコミットしましたが、今まで気付かなかった。リポジトリの履歴からファイルを完全に削除したい。

filename.origそもそもリポジトリに追加されなかった変更履歴を書き換えることはできますか?



回答:


297

質問に記載されている状況と異なる場合は、このレシピを使用しないでください。このレシピは、不適切なマージを修正し、修正をマージに反映します。

けれどもfilter-branch、あなたがやりたいだろう、それは非常に複雑なコマンドであると私はおそらくでこれを行うことを選択しますgit rebase。それはおそらく個人的な好みです。ソリューションはfilter-branch単一の少し複雑なコマンドでrebase実行できますが、ソリューションは同等の論理演算を一度に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」を使用してこれを行うことができますが、一時的なブランチを使用するのではなく、git commit --amendステップに生成されたコミットIDをメモしてgit rebaseコマンドに提供する必要があります。名前。)


6
思いませんgit rebase -i速く、まだ使いやすいようになりますか?$ git rebase -i <sh1-of-merge>正しいものを「編集」としてマークします$ git rm somefile.orig $ git commit --amend $ git rebase --continueしかし、何らかの理由で、まだ最後のどこかにそのファイルがあります私がやった時。おそらく何かが欠けています。
Wernight

12
git rebase -i特に、実行するrebase-y操作が複数ある場合は非常に便利ですが、実際に誰かの肩をポイントしていないときに、エディターで何をしているのかを確認できる場合を正確に説明するのは大変なことです。私はvimを使用していますが、「ggjcesquash <Esc> jddjp:wq」や「現在の2行目以降に最上行を移動し、4行目の最初の単語を「編集」に変更して、今すぐ保存して、終了」は実際の手順よりもすぐに複雑に見えます。通常、いくつかの--amendand --continueアクションも発生します。
CBベイリー

3
私はこれを行いましたが、同じメッセージで、修正されたものの上に新しいコミットが再適用されました。どうやらgitは、不要なファイルを含む古い修正されていないコミットと、他のブランチからの修正されたコミットとの間で3ウェイマージを行ったため、古いコミットの上に新しいコミットを作成し、ファイルを再適用しました。

6
@UncleCJ:ファイルはマージコミットで追加されましたか?これは重要。このレシピは、悪いマージコミットに対処するように設計されています。不要なファイルが履歴の通常のコミットで追加された場合は機能しません。
CBベイリー

1
smartgitを使用し、ターミナルをまったく使用しない方法に驚きました!レシピをありがとう!
cregox、2011

209

はじめに:5つのソリューションが利用可能

元のポスターは次のように述べています:

不要なファイルを誤ってリポジトリにコミットしました...数回前にコミットしました...リポジトリの履歴からファイルを完全に削除したいと思います。

filename.origそもそもリポジトリに追加されなかった変更履歴を書き換えることはできますか?

gitからファイルの履歴を完全に削除するには、さまざまな方法があります。

  1. コミットの修正。
  2. ハードリセット(おそらく、リベース)。
  3. 非インタラクティブなリベース。
  4. インタラクティブなリベース。
  5. ブランチのフィルタリング。

オリジナルのポスターの場合、彼は後でいくつかの追加のコミットを行ったので、コミットを修正すること自体は実際にはオプションではありませんが、完全を期すために、必要な人のために、それを行う方法についても説明します以前のコミットを修正します。

これらのソリューションはすべて、何らかの方法で履歴/コミットを変更/再書き込みする必要があるため、コミットの古いコピーを持っている人は、新しい履歴と履歴を再同期するために追加の作業を行う必要があります。


解決策1:コミットの修正

以前のコミットで誤って変更(ファイルの追加など)を行い、その変更の履歴を残したくない場合は、以前のコミットを修正してファイルを削除するだけです。

git rm <file>
git commit --amend --no-edit

解決策2:ハードリセット(おそらく、リベースに加えて)

ソリューション#1と同様に、以前のコミットを削除したいだけの場合は、親をハードリセットするだけのオプションもあります。

git reset --hard HEAD^

このコマンドは、ブランチを前の最初の親コミットにハードリセットします。

しかしながら、元のポスターのように、変更を元に戻すコミットの後にいくつかのコミットを行った場合でも、ハードリセットを使用して変更を加えることができますが、これにはリベースの使用も含まれます。履歴をさらに遡ってコミットを修正するために使用できる手順は次のとおりです。

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

変更または削除するコミットは、このリストの一番上に表示されます。削除するには、リストからその行を削除します。それ以外の場合は、次のように、1目の「pick」を「edit」に置き換えます。

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

それでおしまい。最後のステップとして、コミットを変更したか完全に削除したかに関係なく、リベース前の状態と比較して、ブランチに他の予期しない変更が加えられていないことを常に確認することをお勧めします。

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

この場合も、filter-branchが完了した後は、通常、ブランチをフィルタリング操作前の以前の状態と比較して、予期しない変更が他にないことを確認することをお勧めします。

git diff master@{1}

フィルターブランチ代替:BFGレポクリーナー

BFG Repo Cleanerツールはよりも高速に実行されると聞きました。そのgit filter-branchため、オプションとして確認することもできます。フィルターブランチのドキュメントでは、実行可能な代替策として公式に言及されています

git-filter-branchを使用すると、Gitの履歴をシェルスクリプトで複雑に書き換えることができますが、大きなファイルやパスワードなどの不要なデータを単に削除する場合は、この柔軟性は必要ありません。これらの操作については、git-filter-branchに代わるJVMベースのBFG Repo-Cleanerを検討することをお勧めします。これらのユースケースでは通常、少なくとも10倍から50倍高速で、まったく異なる特性があります。

  • ファイルの特定のバージョンは1回だけクリーンアップされます。BFGは、git-filter-branchとは異なり、履歴内のどこでまたはいつコミットされたかに基づいてファイルを異なる方法で処理する機会を提供しません。この制約は、BFGのコアパフォーマンス上の利点を与え、不良データをクレンジングするタスクに適している-あなたは気にしないところ悪いデータがあり、あなたはそれがしたいなくなって

  • デフォルトでは、BFGはマルチコアマシンを最大限に活用して、コミットファイルツリーを並行してクレンジングします。git-filter-branchはコミットを順次(つまり、シングルスレッド方式で)クリーンアップ しますが、各コミットに対して実行されるスクリプトで、独自の並列処理を含むフィルターを作成すること可能です。

  • コマンドオプションは、はるかに制限はgit-フィルタ分岐よりも、ちょうど例えばDATA-不要の除去作業に専念します:--strip-blobs-bigger-than 1M

追加のリソース

  1. Pro Git§6.4 Gitツール-履歴の書き換え
  2. git-filter-branch(1)マニュアルページ
  3. git-commit(1)マニュアルページ
  4. git-reset(1)マニュアルページ
  5. git-rebase(1)マニュアルページ
  6. BFGレポクリーナー作成者自身からのこの回答も参照してください)。

filter-branchハッシュの再計算原因?大きなファイルをフィルター処理する必要があるレポでチームが作業する場合、どのようにすれば誰もが同じレポの状態になるようにできますか?
YakovL 2018

@YakovL。すべてがハッシュを再計算します。実際にはコミットは不変です。まったく新しい履歴が作成され、ブランチポインターがその履歴に移動します。全員が同じ履歴を持つことを保証する唯一の方法は、ハードリセットです。
マッド物理学者

118

それ以降何もコミットしていない場合は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ユーザーへの注意:ファイルのパスにスラッシュを使用する必要があります


3
ありがとう!git filter-branchは私にとってはうまくいきましたが、回答として与えられたリベースの例ではうまくいきませんでした:ステップは機能しているように見えましたが、プッシュは失敗しました。プルした後、正常にプッシュされましたが、ファイルはまだ残っていました。リベースの手順をやり直そうとしたところ、マージの競合が発生し、混乱を招きました。私は少し異なるfilter-branchコマンドを使用しましたが、「An Improvement Method」の1つはここにあります:github.com/guides/completely-remove-a-file-from-all-revisions git filter-branch -f --index- filter 'git update-index --remove filename' <introduction-revision-sha1> .. HEAD
atomicules

1
どちらが改善された方法かわかりません。のGit公式ドキュメントはgit-filter-branch最初のものを与えるようです。
2010年

5
zyxware.com/articles/4027/…をチェックしてください。これが最も完全で簡単な解決策ですfilter-branch
leontalbot '28

2
@atomicules、ローカルリポジトリをリモートリポジトリにプッシュしようとする場合、ローカルにはない変更があるため、gitは最初にリモートからプルすることを要求します。--forceフラグを使用してリモートにプッシュすることができます-リモートからファイルを完全に削除します。ただし、注意してください。ファイル以外のものを強制的に上書きしないようにしてください。
sol0mka

1
Windows を使用するとき"ではなく'、使用することを忘れないでください。そうしないと、役に立たないフレーズの「不良リビジョン」エラーが発生します。
2017年

49

これが最善の方法です:http :
//github.com/guides/completely-remove-a-file-from-all-revisions

最初に必ずファイルのコピーをバックアップしてください。

編集

残念ながら、Neonによる編集はレビュー中に却下されました。
以下のNeonsの投稿をご覧ください。役立つ情報が含まれている可能性があります。


たとえば、*.gz誤ってgitリポジトリにコミットされたすべてのファイルを削除するには:

$ 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 repoを新しい空の裸のgitリポジトリにプッシュすることで本当にクリーンアップしました。

$ git init --bare /path/to/newcleanrepo.git
$ git push /path/to/newcleanrepo.git master
$ du -sh /path/to/newcleanrepo.git ==> e.g. 5M 

(はい!)

次に、それを新しいディレクトリに複製し、その.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
これは、「それでもまだうまくいかなかった」という前に私にとってはうまくいきました。コメント
shadi 2017

すばらしい答えですが--prune-empty、filter-branchコマンドに追加することをお勧めします。
ideasman42

27

Git履歴を書き換えると、影響を受けるすべてのコミットIDを変更する必要があるため、プロジェクトで作業しているすべてのユーザーは、リポジトリの古いコピーを削除し、履歴をクリーンアップした後に新しいクローンを作成する必要があります。より多くのそれは不便人々 、より多くのあなたがそれを行うには十分な理由が必要-あなたの余分なファイルが問題の原因は本当にありませんが、場合にのみ、あなたプロジェクトに取り組んでいるあなたが望むなら、あなたにもGitの履歴をクリーンアップするかもしれませんに!

できるだけ簡単にするために、Git履歴からファイルを削除するために特別に設計されたものよりもシンプルで高速なBFG Repo-Cleanerを使用することをお勧めしgit-filter-branchます。ここでの生活を楽にする1つの方法は、デフォルトですべての参照(すべてのタグ、ブランチなど)を実際に処理することですが、10〜50倍にもなります。高速です。

:あなたは慎重にここでの手順に従ってくださいhttp://rtyley.github.com/bfg-repo-cleaner/#usage -しかし、コアビットはちょうどこのです:ダウンロードBFGジャーを(Javaの6以上が必要です)と、このコマンドを実行します:

$ java -jar bfg.jar --delete-files filename.orig my-repo.git

リポジトリ履歴全体がスキャンされ、名前が付けられたファイルfilename.orig最新の commitにないもの)が削除されます。これはgit-filter-branch、同じことをするよりもかなり簡単です!

完全な開示:私はBFG Repo-Cleanerの作成者です。


4
これは優れたツールです。単一のコマンドで、非常に明確な出力が生成され、すべての古いコミットが新しいものに一致するログファイルが提供されます。Javaのインストールは好きではありませんが、これは価値があります。
mikemaccana 14

これは私にとってはうまくいった唯一のことですが、私はgit filter-branchを正しく機能させていなかったので、それはそうです。:-)
Kevin LaBranche 2017年

14
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

1
すべての回答はフィルターブランチトラックにあるようですが、これは履歴内のすべてのブランチをクリーンアップする方法を強調しています。
Cameron Lowell Palmer、

4

それを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

4

私が見つけた最も簡単な方法は、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、そしてleontalbotそれを指摘するために行きます。

注意

スクリプトには検証が含まれていないことに注意してください。そのため、間違いを犯さないようにしてください。また、問題が発生した場合に備えてバックアップを作成してください。それは私にとってはうまくいったが、あなたの状況ではうまくいかないかもしれない。注意して使用してください(何が起こっているのか知りたい場合は、リンクに従ってください)。


3

間違いなく、git filter-branch行く方法です。

残念filename.origながら、タグ、reflogエントリ、リモートなどから引き続き参照される可能性があるため、リポジトリから完全に削除するだけでは不十分です。

これらの参照もすべて削除してから、ガベージコレクタを呼び出すことをお勧めします。この Webサイトのgit forget-blobスクリプトを使用して、これらすべてを1つのステップで実行できます。

git forget-blob filename.orig


1

それがクリーンアップしたい最新のコミットである場合、私は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 reflog expire --expire=now --all; git gc --prune=now行うのは非常に悪いことです。ディスク容量が
足り

ご指摘いただきありがとうございます。私のリポジトリは多くの大きなバイナリファイルと共に提出され、リポジトリは毎晩完全にバックアップされます。だから私はそれから少しでも欲しかった;)
clarkttfu '12 / 12/19


-1

次のものも使用できます。

git reset HEAD file/path


3
ファイルがコミットに追加されている場合、これはファイルをインデックスから削除することすらせず、インデックスをファイルのHEADバージョンにリセットするだけです。
CBベイリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.