web-dev-qa-db-ja.com

Gitリポジトリのコミット履歴から大きなファイルを削除/削除するにはどうすればいいですか?

ときどき私がDVDリッピングをウェブサイトのプロジェクトに落とし込み、それから不用意にgit commit -a -m ...にして、そしてレポを2.2ギグで膨らませました。次回編集を行い、ビデオファイルを削除し、すべてをコミットしましたが、圧縮ファイルは歴史上、リポジトリにまだ残っています。

それらのコミットからブランチを始めて、あるブランチを別のブランチにリベースできることを私は知っています。しかし、大きなファイルが履歴に表示されず、ガベージコレクションの手順で削除されないようにするために、2つのコミットをマージするにはどうすればいいですか。

590
culebrón

Git履歴から不要なファイルを削除するために特別に設計されたgit-filter-branchに代わる、シンプルで高速な BFGレポクリーナー を使用してください。

注意深く 使用法の指示 に従ってください、コア部分はちょうどこれです:

$ Java -jar bfg.jar --strip-blobs-bigger-than 100M my-repo.git

サイズが100MBを超えるファイル(これは最新コミットには含まれていません)、Gitリポジトリの履歴から削除されます。その後、git gcを使用してデッドデータを消去できます。

$ git gc --Prune=now --aggressive

BFGは通常git-filter-branchを実行するより少なくとも 10-50x 速く、一般的に使いやすくなっています。

完全公開:私はBFGレポクリーナーの作者です。

505
Roberto Tyley

あなたが他の開発者に歴史を公開しているなら、あなたがやりたいことは非常に破壊的です。履歴を修復した後に必要な手順については、 git rebaseのドキュメントの「上流からの回復」 を参照してください。

少なくとも2つの選択肢があります:git filter-branchと対話式リベースです。どちらも以下で説明します。

git filter-branchを使う

私はSubversionインポートからの大きなバイナリテストデータで同様の問題を抱えていて、 gitリポジトリからデータを削除する について書きました。

あなたのgit履歴が次のようになっているとします。

$ git lola --name-status
* f772d66 (HEAD, master) Login page
| A     login.html
* cb14efd Remove DVD-rip
| D     oops.iso
* ce36c98 Careless
| A     oops.iso
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

git lola は非標準ですが非常に便利な別名です。 --name-statusスイッチを使うと、各コミットに関連してツリーの変更を見ることができます。

「不注意な」コミット(そのSHA1オブジェクト名はce36c98)では、ファイルoops.isoは誤って追加され、次のコミットで削除されたDVDリッピングcb14efdです。前述のブログ投稿に記載されている手法を使用して、実行するコマンドは次のとおりです。

git filter-branch --Prune-empty -d /dev/shm/scratch \
  --index-filter "git rm --cached -f --ignore-unmatch oops.iso" \
  --tag-name-filter cat -- --all

オプション:

  • --Prune-emptyは、フィルター操作の結果として空になったコミット(、つまり、ツリーを変更しない)を削除します。典型的な場合では、このオプションはよりきれいな歴史を生み出します。
  • -dは、フィルタリングされた履歴を構築するために使用するためにまだ存在していない一時ディレクトリを指定します。最新のLinuxディストリビューションを使用している場合は、 /dev/shm内のツリー)を指定すると実行が速くなります
  • --index-filterはメインイベントであり、履歴の各ステップでインデックスに対して実行されます。 oops.isoは見つかったところならどこでも削除しますが、すべてのコミットに存在するわけではありません。コマンドgit rm --cached -f --ignore-unmatch oops.isoは、DVD-ripが存在する場合はそれを削除し、それ以外の場合は失敗しません。
  • --tag-name-filterはタグ名を書き換える方法を記述します。 catのフィルタは恒等操作です。上記のサンプルのようにあなたのリポジトリはタグを持っていないかもしれませんが、私は完全な一般性のためにこのオプションを含めました。
  • --git filter-branchのオプションの終わりを指定します
  • --allに続く--はすべての参照の省略形です。上記のサンプルのように、あなたのリポジトリは1つのref(master)しか持っていないかもしれませんが、私は完全な一般性のためにこのオプションを含めました。

しばらくの間、歴史は次のようになりました。

$ git lola --name-status
* 8e0a11c (HEAD, master) Login page
| A     login.html
* e45ac59 Careless
| A     other.html
| * f772d66 (refs/original/refs/heads/master) Login page
| | A   login.html
| * cb14efd Remove DVD-rip
| | D   oops.iso
| * ce36c98 Careless
|/
|   A   oops.iso
|   A   other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

新しい "Careless"コミットはother.htmlのみを追加し、 "Remove DVD-rip"コミットはもうマスターブランチにはないことに注意してください。 refs/original/refs/heads/masterというラベルの付いたブランチには、間違いを犯した場合の元のコミットが含まれています。削除するには、 “リポジトリを縮小するためのチェックリスト” の手順に従います。

$ git update-ref -d refs/original/refs/heads/master
$ git reflog expire --expire=now --all
$ git gc --Prune=now

もっと簡単な方法としては、リポジトリを複製して不要な部分を破棄します。

$ cd ~/src
$ mv repo repo.old
$ git clone file:///home/user/src/repo.old repo

file:///...クローンURLを使用すると、ハードリンクのみを作成するのではなく、オブジェクトをコピーします。

今あなたの歴史は:

$ git lola --name-status
* 8e0a11c (HEAD, master) Login page
| A     login.html
* e45ac59 Careless
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

最初の2つのコミット(「インデックス」と「管理ページ」)のSHA1オブジェクト名は、フィルター操作によってそれらのコミットが変更されないため、変わりません。 「不注意」はoops.isoを失い、「ログインページ」は新しい親を得たので、彼らのSHA1変化しました。

インタラクティブリベース

の歴史を持つ:

$ git lola --name-status
* f772d66 (HEAD, master) Login page
| A     login.html
* cb14efd Remove DVD-rip
| D     oops.iso
* ce36c98 Careless
| A     oops.iso
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

あなたはそれを追加したことがないかのように「不注意」からoops.isoを削除したいと思うでしょう、そして「Remove DVD-rip」はあなたにとって無駄です。そのため、インタラクティブなリベースを行うための私たちの計画は、「管理者ページ」を維持し、「不注意」を編集し、「DVDリッピングを削除」を破棄することです。

$ git rebase -i 5af4522を実行すると、以下の内容でエディタが起動します。

pick ce36c98 Careless
pick cb14efd Remove DVD-rip
pick f772d66 Login page

# Rebase 5af4522..f772d66 onto 5af4522
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using Shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

私たちの計画を実行して、私たちはそれをに修正します。

edit ce36c98 Careless
pick f772d66 Login page

# Rebase 5af4522..f772d66 onto 5af4522
# ...

つまり、 "Remove DVD-rip"で行を削除し、 "Careless"の操作をeditではなくpickに変更します。

エディタを保存して終了すると、コマンドプロンプトに次のメッセージが表示されます。

Stopped at ce36c98... Careless
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

メッセージが示すように、私たちは編集したい「不注意な」コミットをしているので、2つのコマンドを実行します。

$ git rm --cached oops.iso
$ git commit --amend -C HEAD
$ git rebase --continue

1つ目は、問題のあるファイルをインデックスから削除します。 2番目は更新されたインデックスになるように“不注意”を修正または修正し、-C HEADはgitに古いコミットメッセージを再利用するように指示します。最後に、git rebase --continueは残りのリベース操作を進めます。

これは以下の歴史を与えます:

$ git lola --name-status
* 93174be (HEAD, master) Login page
| A     login.html
* a570198 Careless
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

それはあなたが欲しいものです。

510
Greg Bacon

この単純だが強力なコマンドを使用しないのはなぜですか。

git filter-branch --tree-filter 'rm -f DVD-rip' HEAD

--tree-filterオプションは、プロジェクトをチェックアウトするたびに指定されたコマンドを実行してから結果を再コミットします。この場合は、DVD-ripというファイルを、存在するかどうかにかかわらず、すべてのスナップショットから削除します。

このリンクを参照してください。

148
Gary Gauh

(私がこの問題に対して私が見た最良の答えは、次のとおりです。 https://stackoverflow.com/a/42544963/714112 Googleの検索ランキングで上位に表示されますが、他の検索ランキングでは表示されません)。

????超高速シェルワンライナー

このシェルスクリプトは、リポジトリ内のすべてのBLOBオブジェクトを最小のものから最大のものへとソートして表示します。

私のサンプルリポジトリでは、ここで見つけた他のものよりも 100倍高速に実行されました
私の信頼できるAthlon II X 4システムでは、それはLinux Kernel repositoryを5,622,155個のオブジェクトを1分以上で処理します。

基本スクリプト

git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk '/^blob/ {print substr($0,6)}' \
| sort --numeric-sort --key=2 \
| cut --complement --characters=13-40 \
| numfmt --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest

上記のコードを実行すると、Nice 人間が読める形式の出力 のようになります。

...
0d99bb931299  530KiB path/to/some-image.jpg
2ba44098e28f   12MiB path/to/hires-image.png
bd1741ddce0d   63MiB path/to/some-video-1080p.mp4

????高速ファイル削除

aから到達可能なすべてのコミットからファイルbHEADを削除したいとすると、次のコマンドを使用できます。

git filter-branch --index-filter 'git rm --cached --ignore-unmatch a b' HEAD
53

これらのコマンドは私の場合はうまくいきました:

git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch oops.iso' --Prune-empty --tag-name-filter cat -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --Prune=now
git gc --aggressive --Prune=now

上記のバージョンとは少し違います。

これをgithub/bitbucketにプッシュする必要がある人(私はこれをbitbucketでテストしただけです)

# WARNING!!!
# this will rewrite completely your bitbucket refs
# will delete all branches that you didn't have in your local

git Push --all --Prune --force

# Once you pushed, all your teammates need to clone repository again
# git pull will not work
33
Kostanos

SOでほとんどすべての答えを試した後、私はようやく私のレポジトリにある大きなファイルをすばやく削除して削除するこの宝石を見つけ、私は再び同期することができました。 http://www.zyxware.com/articles/ 4027 /ローカルおよびリモートのgitリポジトリからファイルを完全に削除する方法

ローカルの作業フォルダにCDをコピーして、次のコマンドを実行します。

git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch FOLDERNAME" -- --all

fOLDERNAMEを指定のgitリポジトリから削除したいファイルまたはフォルダに置き換えます。

これが完了したら、次のコマンドを実行してローカルリポジトリをクリーンアップします。

rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --Prune=now
git gc --aggressive --Prune=now

それでは、すべての変更をリモートリポジトリにプッシュします。

git Push --all --force

これでリモートリポジトリがクリーンアップされます。

28
Justin

このコマンドは非常に破壊的になる可能性があることに注意してください。より多くの人々がレポに取り組んでいるならば、彼ら全員は新しい木を引かなければならないでしょう。あなたの目標がサイズを縮小しないことであるならば、3つの中央のコマンドは必要ではありません。フィルタブランチが削除されたファイルのバックアップを作成し、それが長期間そこに残ることができるからです。

$ git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch YOURFILENAME" HEAD
$ rm -rf .git/refs/original/ 
$ git reflog expire --all 
$ git gc --aggressive --Prune
$ git Push Origin master --force
9
mkljun

私は here と同じ問題に遭遇しましたが、私は この提案 に従うことで解決しましたが、git filter-branch --tree-filter 'rm -f path/to/file' HEADは私にとってはうまくいきました。

Pro-gitの本には 歴史の書き換え に関する章全体があります - filter-branch /各コミットからのファイルの削除 セクションを見てください。

9
Thorsten Lorenz

あなたのコミットがツリー全体を通るのではなく最近のものであることを知っているならば、次のようにしてください:git filter-branch --tree-filter 'rm LARGE_FILE.Zip' HEAD~10..HEAD

8
Soheil

私は自分のサイトのginormous * .jpaバックアップを誤って保存していたbitbucketアカウントでこれに遭遇しました。

git filter-branch --Prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch MY-BIG-DIRECTORY-OR-FILE' --tag-name-filter cat -- --all

履歴を完全に書き換えるには、問題のフォルダをMY-BIG-DIRECTORYに再配置します( tags を含む)。

出典: http://naleid.com/blog/2012/01/17/finding-and-purging-big-files-from-git-history

5
lfender6445

私は基本的にこの答えにあったことをしました: https://stackoverflow.com/a/11032521/1286423

(歴史のために、ここにコピー&ペーストします)

$ git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch YOURFILENAME" HEAD
$ rm -rf .git/refs/original/ 
$ git reflog expire --all 
$ git gc --aggressive --Prune
$ git Push Origin master --force

私は物事の名前を変更したり動かしたりするのが好きなのでうまくいきませんでした。そのため、いくつかの大きなファイルが名前が変更されたフォルダーにありました、そして私はgcがそれらのファイルを指すtreeオブジェクトの中の参照のためにそれらのファイルへの参照を削除できなかったと思います。本当にそれを殺すための私の究極の解決策は、

# First, apply what's in the answer linked in the front
# and before doing the gc --Prune --aggressive, do:

# Go back at the Origin of the repository
git checkout -b newinit <sha1 of first commit>
# Create a parallel initial commit
git commit --amend
# go back on the master branch that has big file
# still referenced in history, even though 
# we thought we removed them.
git checkout master
# rebase on the newinit created earlier. By reapply patches,
# it will really forget about the references to hidden big files.
git rebase newinit

# Do the previous part (checkout + rebase) for each branch
# still connected to the original initial commit, 
# so we remove all the references.

# Remove the .git/logs folder, also containing references
# to commits that could make git gc not remove them.
rm -rf .git/logs/

# Then you can do a garbage collection,
# and the hidden files really will get gc'ed
git gc --Prune --aggressive

私のリポジトリ(.git)は32MBから388KBに変更されました。フィルタブランチでさえきれいにすることができませんでした。

3
Dolanor

branch filterコマンドを使用してこれを行うことができます。

git filter-branch --tree-filter 'rm -rf path/to/your/file' HEAD

3
John Foley

git filter-branchは、コミット履歴から巨大なファイルを削除するために使用できる強力なコマンドです。ファイルはしばらくの間保持され、Gitは次のガベージコレクションでそれを削除します。下記はコミット履歴からファイルを削除する までの全プロセスです 。安全のために、最初に新しいブランチでコマンドを実行します。

# Do it in a new testing branch
$ git checkout -b test

# Remove file-name from every commit on the new branch
# --index-filter, rewrite index without checking out
# --cached, remove it from index but not include working tree
# --ignore-unmatch, ignore if files to be removed are absent in a commit
# HEAD, execute the specified command for each commit reached from HEAD by parent link

$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch file-name' HEAD

# The output is OK, reset it to the prior branch master
$ git checkout master
$ git reset --soft test

# Remove test branch
$ git branch -rm test

# Push it with force
$ git Push --force Origin master
1
zhangyu12

Git Extensions を使用してください。これはUIツールです。それは、リポジトリ内のlageファイルを見つけてそれらを透過的に削除することを可能にする "Find large files"という名前のプラグインを持っています。

このツールを使用する前に 'git filter-branch'を使用しないでください。 'filter-branch'によって削除されたファイルを見つけることができません(Altough 'filter-branch'はリポジトリパックファイルからファイルを完全に削除しません)。 。

1
Nir

この問題に遭遇したとき、gitは私たちの歴史の中にそのファイルが一度存在したことを記憶しているので、git rmだけでは十分ではないでしょう。

さらに悪いことには、BLOBを参照するとgitガベージコレクタがスペースをクリーンアップできなくなるため、リベースは簡単ではありません。これにはリモート参照とreflog参照が含まれます。

これらの参照をすべて削除することを試みる小さなスクリプトであるgit forget-blobをまとめ、次にgit filter-branchを使用してブランチ内のすべてのコミットを書き換えます。

BLOBが完全に参照されなくなったら、git gcはそれを取り除きます。

使い方はとても簡単ですgit forget-blob file-to-forget。あなたはここでより多くの情報を得ることができます

https://ownyourbits.com/2017/01/18/completely-remove-a-file-from-a-git-repository-with-git-forget-blob/

Stack Overflowからの回答といくつかのブログエントリのおかげで、私はこれをまとめました。彼らへのクレジット!

1
nachoparker