TL;DR: nếu .gitignore không hoạt động, gần như luôn là file đó đã được track. Git chỉ ignore những file nó chưa từng thấy — một file nằm trong index miễn nhiễm với mọi rule bạn viết. Kiểm chứng bằng git check-ignore -v <path>: im lặng nghĩa là path đã được track và không rule nào áp dụng. Sửa bằng git rm --cached <path>, commit, và rule bắt đầu có hiệu lực từ commit đó trở đi. Thứ tự rule, bẫy phủ định và những manh mối giả từ VS Code giải thích các trường hợp còn lại.
Vì sao .gitignore không hoạt động?
Một cơ chế che phủ đa số trường hợp: file đã được commit trước khi rule tồn tại. .gitignore không phải bộ lọc giấu file khỏi git — nó là rule về những gì git add nên nhặt từ trạng thái untracked. Một khi file đã vào index, git theo dõi mọi thay đổi nội dung của nó mãi mãi cho tới khi bạn chủ động untrack. Sửa .gitignore sau sự kiện không đổi gì với file đã track — vì vậy chuỗi kinh điển “commit .env, hoảng, thêm .env vào .gitignore, commit lần nữa” vẫn mang secret đi cùng mỗi lần push.
Cái này cắn ai cũng dính vì git gần như phổ biến hoàn toàn — khảo sát Stack Overflow 2022 ghi nhận tỉ lệ dùng Git trên 93% developer chuyên nghiệp (survey.stackoverflow.co) — và mỗi người trong số đó sớm muộn cũng viết một rule cho file mình đã commit tuần trước.
Phần còn lại của bài nói về các trường hợp thiểu số: sai thứ tự rule, bẫy phủ định, edge case thư mục và câu hỏi IDE. Nhưng hãy chạy health check ở mục kế trước — hơn chín trên mười lần nó chấm dứt cuộc điều tra.
Làm sao biết rule gitignore nào đang khớp với một file?
git check-ignore là công cụ chẩn đoán, và sự im lặng của nó chính là chẩn đoán:
# In rule khớp + file + số dòng khi có rule áp dụng
git check-ignore -v debug.log
# .gitignore:3:*.log debug.log
# KHÔNG in gì khi không rule nào áp dụng — file đã được track (hoặc chưa có rule)
git check-ignore -v src/.env
# (im lặng = .gitignore không hề ignore path này, dù bạn viết gì)
# Exit code: 0 = bị ignore, 1 = không — viết script được
git check-ignore -q debug.log && echo "ignored" || echo "tracked or unruly" Đọc output của -v: nguồn ignore (.gitignore, .git/info/exclude, hoặc file ignore toàn cục của bạn), rồi số-dòng:pattern, rồi path. Nếu một rule ở dưới gây bất ngờ, nhớ thứ tự ưu tiên: rule khớp cuối cùng thắng, nên !important.log đặt sau *.log sẽ đưa riêng file đó trở lại.
Vì sao gitignore không ăn với file đã commit?
Untrack file, giữ nó trên đĩa, commit thao tác gỡ:
# Untrack một file (bản local còn nguyên — --cached chỉ đụng vào index)
git rm --cached .env
git commit -m "stop tracking .env"
# Xác nhận rule ignore giờ đã áp dụng
git check-ignore -v .env Từ commit này trở đi, .gitignore làm chủ path đó: chỉnh sửa nó không còn hiện trong git status, và git add . sẽ không nhặt lại. Nhưng file vẫn nằm trong history — nếu đó là secret, gỡ khỏi commit mới nhất chưa đủ. Xoay vòng credential là cách sửa duy nhất thật sự; viết lại history chỉ là che mặt (và hoàn tác commit cuối chỉ giúp khi commit xấu vẫn là đỉnh).
Với một repo đầy rác được commit từ trước — build output, rác editor, node_modules lọt vào từ sớm — untrack hàng loạt chỉ là hai dòng:
git rm -r --cached .
git add .
git commit -m "apply .gitignore to tracked files" Lệnh này dựng lại index theo rule hiện hành: path bị ignore rơi ra, mọi thứ còn lại được thêm lại nguyên vẹn. Diff trông ngoạn mục (hàng nghìn dòng xóa) nhưng không xóa gì khỏi đĩa. Nếu mục tiêu của bạn là xóa hẳn những file đó thay vì chỉ untrack, đó là địa phận của git clean -fdx — xem git remove untracked files, safely.
Git chỉ ignore những file nó chưa từng thấy. Một file đã nằm trong index miễn nhiễm với
.gitignore— không rule nào bạn viết khiến nó “quên” được.
Vì sao gitignore không ăn với cả thư mục?
Ba cái bẫy riêng của thư mục:
1. Dấu gạch cuối là ý định, không phải cơ chế khớp. build và build/ đều khớp một thư mục, nhưng build/ ghi rõ bạn chỉ muốn khớp thư mục — một file tên build sẽ sống sót qua nó. Tuy nhiên sự đối xứng gãy ở phần re-include (bẫy kế tiếp).
2. Phủ định không cứu được file bên trong thư mục đã loại trừ. Tài liệu git nói thẳng: “It is not possible to re-include a file if a parent directory of that file is excluded” (git-scm.com/docs/gitignore). Đây là quyết định vì hiệu năng — git bỏ qua nguyên khối thư mục bị loại trừ thay vì đi vào từng bước. Nên thế này không hoạt động:
build/
!build/keep.me # rule chết — git không bao giờ nhìn vào trong build/ Cách sửa là loại trừ nội dung, không phải thư mục:
build/*
!build/keep.me # hoạt động — bản thân build/ vẫn mở để soi 3. File .gitignore lồng nhau thắng trong phạm vi của nó. Rule trong subdir/.gitignore đè file gốc cho mọi path dưới subdir. Khi git check-ignore -v nêu một nguồn ignore bạn không ngờ tới, thường là vì lý do này.
Vì sao gitignore không hoạt động trong VS Code?
Gần như không bao giờ là lỗi của VS Code. Khung Source Control của editor đọc cùng index mà git đọc, nên triệu chứng giống hệt: file đã được commit từ trước, và khởi động lại IDE không đổi được index. Hai sự thật gần-VS-Code đáng biết:
- Mờ trong Explorer = bị ignore; cam/vàng = được track và có thay đổi. Một file vẫn hiện là modified sau khi bạn ignore nó chính là xác nhận nó được track — chạy cách sửa
git rm --cachedở trên. - Một
.gitignorekhông xuất hiện trong danh sách changed nghĩa là rule đang ăn — nó chưa bao giờ hiện lên dạng untracked từ đầu. Mọi người hay báo “VS Code ignores my gitignore” chỉ vìgit statustrên CLI bất đồng với khung SCM bị lỗi thời; reload cửa sổ (Cmd/Ctrl+Shift+P→ “Reload Window”) trước khi đổ thừa git.
.gitignore vs .git/info/exclude vs global: cái nào khi nào?
| File | Phạm vi | Được commit? | Dùng cho |
|---|---|---|---|
.gitignore (repo) | Mọi ai clone | Có | Build output, dependencies, .env — rule dùng chung |
.git/info/exclude | Chỉ clone của bạn | Không | Rác cá nhân: .scratch/, rác editor |
core.excludesFile (global) | Mọi repo của bạn | Không | Rác hệ điều hành: .DS_Store, Thumbs.db, *.swp |
.gitignore + phủ định | Repo | Có | Re-include các ngoại lệ cho file config đã track |
File global là thứ đa số developer không bao giờ đặt mà nên đặt:
git config --global core.excludesFile ~/.gitignore_global
printf '.DS_Store\nThumbs.db\n*.swp\n' >> ~/.gitignore_global Nội quy đáng copy: rule nào có lợi cho cả team thì thuộc về repo; rule nào chỉ có lợi cho bạn thì thuộc về exclude hoặc file global. Commit rule ignore cá nhân là cách các file .gitignore phình tới 300 dòng và không ai biết nửa nào còn đáng sống.
Làm sao bỏ qua thay đổi của một file đã track?
Đôi khi bạn muốn giữ một file đã track (template config, file cài đặt IDE) nhưng muốn chỉnh sửa local ngừng hiện trong git status. Hai cờ của git update-index, và không cờ nào thuộc về workflow của team:
git update-index --skip-worktree config/local.dev # chỉnh sửa local đi vào im lặng
git update-index --no-skip-worktree config/local.dev # ...và quay lại --skip-worktree là cờ defended được — nó nói “bản local của tôi cố tình đi lệch.” Người họ hàng --assume-unchanged là một lời hứa hiệu năng (“file này sẽ không đổi”), không phải cơ chế ignore, và git có thể âm thầm phá lời hứa đó. Cả hai cờ đều gãy to ngay lúc pull khi upstream cũng đã đổi file — câu trả lời bền là config chỉ-local qua exclude ngay từ lúc tạo, hoặc một file template (config.example) mà git track và bạn tự copy ra.
Health check gitignore trong 30 giây
git check-ignore -v <path> # rule nào? (im lặng = đã track, không rule)
git ls-files --error-unmatch <path> # nó có được track không?
git rm --cached <path> # untrack, giữ trên đĩa
git commit -m "stop tracking <path>"
git check-ignore -v <path> # rule giờ hiện ra Chẩn đoán, untrack, xác minh. Mẫu số chung đằng sau mọi báo cáo “gitignore not working” là cùng một file đội hai chiếc mũ — được track ở một bên index, bị ignore ở bên kia — và một cờ --cached tháo chiếc mũ thứ hai ra.
FAQ
Vì sao .gitignore không hoạt động?
Trong đa số trường hợp, file đã được track từ trước. Git chỉ ignore file chưa từng thấy; một file nằm trong index miễn nhiễm với .gitignore cho đến khi bạn untrack nó bằng git rm --cached rồi commit.
Làm sao biết rule gitignore nào khớp với một file?
Chạy git check-ignore -v <path>. Nó in đúng dòng .gitignore và số dòng, hoặc im lặng khi file đã được track và không rule nào áp dụng.
git rm --cached có xóa file local của tôi không?
Không. --cached chỉ xóa file khỏi index; bản trên đĩa vẫn nguyên. Commit thao tác gỡ và file trở thành untracked, từ đó .gitignore mới có hiệu lực.
Vì sao gitignore không thể đưa lại file bên trong một thư mục bị ignore?
Vì hiệu năng, Git bỏ qua toàn bộ thư mục bị loại trừ. Theo tài liệu git, không thể re-include một file nếu thư mục cha của nó đã bị loại trừ.
— mrsaynothing
— mrsaynothing
Ghi chú thực địa về AI, Linux và self-hosting.
Thảo luận bài này trên dev.to dev.to ↗
Nhận how-to tiếp theo qua email
Một email mỗi bài viết. Sửa xong rồi đi tiếp.
cái này là gì?Ollama không dùng GPU? Sửa trên Linux, Windows và WSL
Thích kiểu viết này? Tôi build như vậy để kiếm sống. thuê tôi