
Part 1では複数GitHubアカウントの使い分け方法を解説しました。今回は、よくある落とし穴「.gitignoreが効かない問題」と、巨大ファイルを履歴から完全に削除する方法について説明します。
トラブル2:.gitignoreが効かない!巨大ファイルのpushエラー
生徒:先生、またエラーが…pushできません!
user@server:~/project$ git push origin main
remote: error: File logs/access.log is 306.38 MB; this exceeds GitHub's file size limit of 100.00 MB
remote: error: File logs/error.log is 332.36 MB; this exceeds GitHub's file size limit of 100.00 MB
remote: error: GH001: Large files detected.
To github.com-first:myaccount/myrepo.git
! [remote rejected] main -> main (pre-receive hook declined)
先生:お、GitHubの100MBの制限に引っかかってるね。ログファイルをpushしようとしてる。
生徒:でも、.gitignore
に書いてるのに…
# ログファイル
/logs/
/logs/**
*.log
.gitignoreの罠:すでに追跡されているファイルには効かない
先生:あー、これは典型的な落とし穴だ。
生徒:え?何がダメなんですか?
先生:.gitignore
は「まだGitに追加されていないファイル」にしか効果がないんだよ。
生徒:え!?
先生:つまり、すでにgit add
してコミットしてしまったファイルは、後から.gitignore
に追加しても無視されないんだ。
生徒:そうだったんですか…知りませんでした。
先生:確認してみよう。
# ログファイルがGitに追跡されているか確認
user@server:~/project$ git ls-files | grep logs/
logs/access.log
logs/error.log
logs/access_geo.log
先生:ほら、ちゃんと追跡されてる。これを解除する必要があるよ。
解決策1:Gitの追跡から削除
先生:まず、ファイル自体は残したまま、Gitの追跡だけを解除しよう。
# logsディレクトリ全体をGitの追跡から削除(ファイルは残る)
user@server:~/project$ git rm -r --cached logs/
rm 'logs/test.log'
rm 'logs/access.log'
rm 'logs/error.log'
生徒:--cached
オプションって何ですか?
先生:これを付けると、ファイル自体は削除されず、Gitの追跡だけが解除されるんだ。付けないと、ファイル自体も削除されちゃう。
生徒:なるほど!
# 変更をコミット
user@server:~/project$ git add .gitignore
user@server:~/project$ git commit -m "Remove logs from Git tracking"
[main abc1234] Remove logs from Git tracking
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 logs/test.log
delete mode 100644 logs/access.log
delete mode 100644 logs/error.log
生徒:よし、これでpushできますね!
user@server:~/project$ git push origin main
remote: error: File logs/access.log is 306.38 MB; this exceeds GitHub's file size limit of 100.00 MB
# またエラー!?
生徒:え!?まだエラーが…
問題の本質:過去のコミット履歴に残っている
先生:あー、まだダメか。これは過去のコミット履歴に巨大ファイルが残ってるんだね。
生徒:過去の履歴?
先生:さっきのgit rm --cached
は「最新のコミットから削除」しただけで、過去のコミットにはまだ残ってるんだ。確認してみよう。
# 過去のコミットに含まれる大きなファイルを確認
user@server:~/project$ git rev-list --objects --all |
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' |
grep '^blob' |
sort -k3 -n -r |
head -10
blob xxx 348505050 logs/access_geo.log
blob yyy 321260037 logs/access.log
blob zzz 21400386 logs/error.log
blob aaa 53908 nginx.tmpl
...
生徒:うわ、300MBのファイルが残ってる…
先生:GitHubは履歴も含めた全体をチェックするから、過去のコミットから完全に削除する必要があるんだ。
解決策2:履歴から完全に削除
先生:git filter-branch
を使って、履歴から完全に削除しよう。
生徒:難しそう…
先生:大丈夫、一緒にやろう。
# 履歴から削除
user@server:~/project$ git filter-branch --force --index-filter
"git rm --cached --ignore-unmatch logs/access.log logs/access_geo.log logs/error.log"
--prune-empty --tag-name-filter cat -- --all
WARNING: git-filter-branch has a glut of gotchas...
Proceeding with filter-branch...
Rewrite abc1234... (1/15) (0 seconds passed, remaining 0 predicted)
rm 'logs/access.log'
rm 'logs/access_geo.log'
rm 'logs/error.log'
Rewrite def5678... (13/15) (1 seconds passed, remaining 0 predicted)
Ref 'refs/heads/main' was rewritten
生徒:なんか警告が出てますけど…
先生:git filter-branch
は古いツールだから警告が出るんだけど、今回は問題ないよ。最近はgit filter-repo
という新しいツールもあるけど、インストールが必要だから今回はこれで進めよう。
クリーンアップ
先生:削除したら、Gitの内部データも綺麗にする必要がある。
# 不要な参照を削除
user@server:~/project$ git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin
# reflogをクリア
user@server:~/project$ git reflog expire --expire=now --all
# ガベージコレクション(完全削除)
user@server:~/project$ git gc --prune=now --aggressive
Enumerating objects: 101, done.
Counting objects: 100% (101/101), done.
Delta compression using up to 8 threads
Compressing objects: 100% (94/94), done.
Writing objects: 100% (101/101), done.
Total 101 (delta 49), reused 0 (delta 0), pack-reused 0
生徒:これは何してるんですか?
先生:簡単に言うと、
for-each-ref
: 削除の痕跡も消すreflog expire
: Gitのログを完全にクリアgc --aggressive
: 不要なデータを完全削除して圧縮
って感じだね。
結果確認
先生:さあ、どのくらい小さくなったか見てみよう。
# リポジトリサイズを確認
user@server:~/project$ du -sh .git/
196K .git/
生徒:196KBになった!元々は数百MBあったのに!
# 大きなファイルがなくなったか確認
user@server:~/project$ git rev-list --objects --all |
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' |
grep '^blob' |
sort -k3 -n -r |
head -10
blob aaa 53908 nginx.tmpl
blob bbb 53790 nginx.tmpl
blob ccc 11788 README.md
blob ddd 8033 README.md
blob eee 3031 NOTES.md
...
先生:完璧!最大でも53KBのファイルしかない。300MBのログファイルは完全に消えたね。
強制プッシュ
先生:最後に、書き換えた履歴をGitHubに送るよ。
user@server:~/project$ git push -f origin main
Enumerating objects: 101, done.
Counting objects: 100% (101/101), done.
Delta compression using up to 8 threads
Compressing objects: 100% (94/94), done.
Writing objects: 100% (101/101), 180.50 KiB | 5.01 MiB/s, done.
Total 101 (delta 49), reused 101 (delta 49), pack-reused 0
To github.com-first:myaccount/myrepo.git
+ abc1234...def5678 main -> main (forced update)
生徒:成功しました!
先生:-f
(force)オプションは、履歴を書き換えた時に使うんだ。他の人と共有してるリポジトリでは注意が必要だけど、今回は新しいリポジトリだから問題ないよ。
次回のPart 3では、.gitignoreのベストプラクティスと、追跡削除の完全な手順をまとめます。