手帳

  • 2025-10-20
  • IT系

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のベストプラクティスと、追跡削除の完全な手順をまとめます。