Hướng dẫn sử dụng BFG Repo-Cleaner: Xóa dữ liệu nhạy cảm và thu gọn dung lượng Git Repository siêu tốc

Git tutorial - IT technology blog
Git tutorial - IT technology blog

Bối cảnh: Khi Repository Git “phình” ra và chứa thứ không nên có

Tình huống này chắc nhiều anh em đã gặp: một ngày đẹp trời review lại repo cũ, phát hiện ra file .env với API key thật nằm chễm chệ trong git history từ 6 tháng trước. Hoặc đơn giản hơn là repo đang 200MB dù code thực tế chỉ vài chục MB — vì ai đó từng commit nhầm file video, dataset, hay backup SQL.

Xóa file khỏi working tree rồi commit lại không giải quyết được vấn đề. Git lưu toàn bộ history — file đó vẫn nằm trong object database, vẫn có thể checkout lại bằng commit hash cũ. Người biết dùng git log --allgit checkout vẫn lấy được nội dung đó bình thường.

Tại sao git filter-branch không phải lựa chọn tốt?

Giải pháp cổ điển là git filter-branch — lệnh built-in của Git để rewrite history. Vấn đề là nó cực kỳ chậm. Mình từng chạy git filter-branch trên một repo 500MB với khoảng 3.000 commits, mất gần 2 tiếng. Với repository hàng chục nghìn commits thì tính bằng ngày.

BFG Repo-Cleaner ra đời để giải quyết đúng điểm đau này. Theo benchmark của tác giả, BFG nhanh hơn git filter-branch từ 10 đến 720 lần tùy trường hợp. Viết bằng Scala, chạy đa luồng, xử lý object graph song song — đó là lý do tốc độ chênh lệch lớn đến vậy.

Cài đặt BFG Repo-Cleaner

Yêu cầu: Java Runtime

BFG chạy trên JVM, cần Java 8 trở lên:

java -version
# java version "17.0.8" hoặc tương tự là OK

Nếu chưa có Java:

# Ubuntu/Debian
sudo apt install default-jre

# macOS (Homebrew)
brew install openjdk

# Windows (Chocolatey)
choco install openjdk

Tải BFG JAR

BFG là một file JAR duy nhất, không cần cài đặt phức tạp:

# Tải bản 1.14.0 (bản stable mới nhất)
wget https://repo1.maven.org/maven2/com/madgag/bfg/1.14.0/bfg-1.14.0.jar -O bfg.jar

# Kiểm tra
java -jar bfg.jar --version

Để tiện dùng hàng ngày, thêm alias vào shell config:

# Thêm vào ~/.bashrc hoặc ~/.zshrc
alias bfg='java -jar /path/to/bfg.jar'

source ~/.bashrc

Cấu hình chi tiết: Các tình huống thực tế

Bước chuẩn bị: Clone mirror repository

BFG hoạt động trực tiếp trên bare repository. Trước khi làm bất cứ điều gì, clone dạng mirror về máy local:

# Clone dạng bare (mirror) — KHÔNG dùng clone thông thường
git clone --mirror [email protected]:username/my-repo.git my-repo.git

# Kiểm tra thư mục bare
ls my-repo.git/

Làm trên bare clone an toàn hơn — repo gốc trên remote không bị ảnh hưởng cho đến khi bạn chủ động force push. Đây là safety net quan trọng.

Tình huống 1: Xóa file nhạy cảm đã lỡ commit

Use case phổ biến nhất. Ví dụ lỡ commit file .env, secrets.json, hay private key:

cd my-repo.git

# Xóa một file cụ thể khỏi toàn bộ history
java -jar bfg.jar --delete-files .env

# Xóa nhiều file (glob pattern)
java -jar bfg.jar --delete-files '*.pem'
java -jar bfg.jar --delete-files '{.env,.env.local,.env.production}'

# Sau khi BFG chạy xong, bắt buộc phải chạy bước này
git reflog expire --expire=now --all
git gc --prune=now --aggressive

Lệnh git gc mới là bước thực sự giải phóng dung lượng — BFG chỉ đánh dấu objects là orphan, gc mới xóa chúng khỏi disk.

Tình huống 2: Xóa file lớn để thu gọn dung lượng repo

Mình từng gặp case repo của một dự án machine learning — ai đó commit nhầm folder dataset 1.5GB, repo phình từ 50MB lên 1.6GB. BFG xử lý chuyện này rất gọn:

# Xóa tất cả blob lớn hơn 50MB khỏi history
java -jar bfg.jar --strip-blobs-bigger-than 50M

# Hoặc giới hạn nghiêm hơn
java -jar bfg.jar --strip-blobs-bigger-than 10M

Lưu ý quan trọng: BFG tự động bảo vệ commit mới nhất (HEAD). Nếu file lớn vẫn còn trong HEAD, cần xóa thủ công trước:

# Xóa file khỏi HEAD trước khi chạy BFG
git rm --cached dataset.zip
git commit -m "chore: remove accidentally committed large file"

# Sau đó mới chạy BFG
java -jar bfg.jar --strip-blobs-bigger-than 50M

Tình huống 3: Thay thế password hoặc token hardcode trong code

Đôi khi không phải xóa cả file mà chỉ cần thay thế chuỗi nhạy cảm — ví dụ API key hardcode trong file config:

# Tạo file chứa các chuỗi cần thay thế (mỗi dòng một chuỗi)
cat > passwords.txt << 'EOF'
sk-ant-api03-xxx-yyy-zzz
ghp_xxxxxxxxxxxxxxxxxxxx
AKIAIOSFODNN7EXAMPLE
EOF

# BFG thay thế tất cả các chuỗi trên bằng ***REMOVED***
java -jar bfg.jar --replace-text passwords.txt

BFG scan toàn bộ history và thay thế chính xác các chuỗi đó. Cấu trúc file và các nội dung khác giữ nguyên.

Tình huống 4: Xóa toàn bộ thư mục khỏi history

# Xóa thư mục logs/ và node_modules/ khỏi toàn bộ history
java -jar bfg.jar --delete-folders logs
java -jar bfg.jar --delete-folders node_modules

# Xóa nhiều thư mục cùng lúc
java -jar bfg.jar --delete-folders '{logs,tmp,cache,__pycache__}'

Kiểm tra & Đưa lên Remote

Verify kết quả trước khi push

BFG in ra report sau mỗi lần chạy — đọc kỹ để biết bao nhiêu commit bị rewrite và file nào đã bị xử lý. Ngoài ra kiểm tra thêm bằng tay:

# Kiểm tra file nhạy cảm đã biến mất khỏi history chưa
git log --all --full-history -- .env
# Không có output = đã xóa thành công

# So sánh dung lượng trước và sau
git count-objects -vH

# Tìm chuỗi nhạy cảm trong toàn bộ history
git log -p --all | grep -n "sk-ant-api03"
# Không có kết quả = đã sạch

Force push lên remote

Sau khi verify xong, push lên remote. Bước này rewrite history trên remote — cần thông báo cho cả team trước, vì local repo của mọi người sẽ lỗi sync:

cd my-repo.git

# Push tất cả branches và tags
git push --force

Mỗi thành viên trong team cần đồng bộ lại:

# Cách an toàn nhất: re-clone
git clone [email protected]:username/my-repo.git

# Hoặc nếu có local changes muốn giữ lại
git fetch --all
git reset --hard origin/main

Rotate credentials ngay lập tức

Đây là điểm nhiều người hay bỏ qua: dù đã xóa khỏi git history, bạn vẫn phải revoke và tạo lại tất cả credentials đã bị expose. GitHub, GitLab, và hầu hết các platform đều có crawler tự động quét public repo — không ai biết credentials đó đã bị thu thập chưa từ lúc nó public đến lúc bạn xóa. Revoke trước, clean sau, theo thứ tự đó:

  • GitHub Token: Settings → Developer settings → Personal access tokens → Delete
  • AWS Key: IAM → Access keys → Deactivate → Delete
  • Anthropic API Key: console.anthropic.com → API Keys → Revoke

Chặn tái diễn bằng .gitignore

Thêm ngay vào .gitignore để không ai commit nhầm lần nữa:

# .gitignore
.env
.env.local
.env.*.local
*.pem
*.key
*.p12
secrets/
config/database.yml

Trong team 8 người mình quản lý, sau khi xảy ra sự cố lỡ commit file .env một lần, mình bổ sung thêm git hook để block commit các file khớp pattern nhạy cảm — kết hợp với pre-commit check. Từ đó đến nay chưa xảy ra lần nào nữa. Cơ chế tự động ngăn lỗi ngay tại chỗ hiệu quả hơn nhiều so với review sau.

BFG hay git filter-repo?

Từ Git 2.24+, có thêm git filter-repo — tool Python được Git chính thức khuyến nghị thay thế git filter-branch, nhanh hơn filter-branch và linh hoạt hơn BFG. Nhưng BFG vẫn giữ lợi thế trong nhiều tình huống:

  • BFG: Cú pháp đơn giản hơn cho các use case phổ biến (xóa file, replace text, strip large blobs). Không phụ thuộc Python. Một JAR duy nhất, chạy được ngay.
  • git filter-repo: Phù hợp hơn cho thao tác phức tạp như restructure repo, tách subdirectory thành repo riêng, hay filter theo author.

Nếu mục tiêu chỉ là xóa file nhạy cảm hoặc shrink dung lượng repo, BFG vẫn là lựa chọn nhanh và ít rủi ro nhất — cài trong 2 phút, chạy trong vài giây.

Share: