Tệp .gitignore: cách loại trừ tệp khỏi Git và gỡ tệp đã thêm
Tệp .gitignore ở gốc kho liệt kê các mẫu tệp và thư mục mà Git không theo dõi: phụ thuộc, sản phẩm build, nhật ký, khóa bí mật. Nó chỉ áp dụng cho tệp chưa được theo dõi, vì vậy tệp đã commit cần được gỡ khỏi index bằng git rm --cached.
Cơ chế git ignore cho Git biết: «đừng động vào các tệp này, đừng hiển thị chúng trong git status và đừng thêm chúng bằng git add .». Các quy tắc được viết trong tệp văn bản .gitignore, được commit cùng dự án và áp dụng cho cả nhóm. Lưu ý quan trọng nhất: việc bỏ qua chỉ có tác dụng với các tệp mà Git chưa theo dõi.
Thường thêm gì vào .gitignore
- Phụ thuộc:
node_modules/,vendor/,.venv/— trình quản lý gói sẽ khôi phục chúng. - Kết quả build:
dist/,build/,*.class,__pycache__/. - Nhật ký và tệp tạm:
*.log,tmp/,*.swp. - Bí mật và cấu hình cục bộ:
.env, khóa, cấu hình trên máy cá nhân. - Rác của hệ điều hành và trình soạn thảo:
.DS_Store,Thumbs.db,.idea/— tuy nhiên tốt hơn nên đưa chúng vào bộ bỏ qua toàn cục, sẽ nói ở phần dưới.
Quy tắc đơn giản: trong kho chỉ nên có những gì con người viết, không có những gì chương trình sinh ra hoặc phụ thuộc vào từng máy.
Cách tạo .gitignore
Đây là tệp văn bản thông thường ở gốc kho. Hãy tạo nó, ghi các mẫu mỗi dòng một mẫu và commit:
cat > .gitignore <<'EOF'
# phụ thuộc
node_modules/
# build
dist/
# nhật ký
*.log
# bí mật
.env
EOF
git add .gitignore
git commit -m "chore: thêm .gitignore"
Các mẫu có sẵn cho nhiều ngôn ngữ và framework nằm trong kho github/gitignore; GitHub và GitLab cho phép chọn mẫu khi tạo dự án. Hãy dùng chúng làm nền và bỏ bớt những gì không cần.
Cú pháp mẫu trong .gitignore
| Mẫu | Bỏ qua |
|---|---|
*.log | mọi tệp có đuôi .log ở bất kỳ thư mục nào |
build/ | thư mục build ở mọi cấp (dấu gạch chéo cuối = chỉ thư mục) |
/build | chỉ build ở gốc (dấu gạch chéo đầu gắn mẫu với thư mục chứa .gitignore) |
docs/*.pdf | pdf nằm trực tiếp trong docs, nhưng không nằm trong docs/old/ |
docs/**/*.pdf | pdf trong docs và mọi thư mục con |
**/temp | temp trong bất kỳ thư mục nào |
!keep.log | ngoại lệ: không bỏ qua keep.log |
file?.txt | ? — một ký tự bất kỳ |
[ab].txt | a.txt hoặc b.txt |
# chú thích | dòng chú thích |
Lưu ý quan trọng: mẫu không có dấu gạch chéo bên trong (config.json) áp dụng ở mọi cấp, còn mẫu có dấu gạch chéo ở đầu hoặc giữa (/config.json, src/config.json) được tính theo thư mục chứa tệp .gitignore đó.
Ngoại lệ với dấu chấm than
*.log
!important.log
Cái bẫy: không thể «khôi phục» một tệp nếu thư mục cha của nó bị bỏ qua. Git không đi vào các thư mục bị bỏ qua, nên cách này sẽ không hoạt động:
logs/
!logs/keep.txt # không hoạt động
Cách đúng là bỏ qua nội dung, không bỏ qua thư mục:
logs/*
!logs/keep.txt
Chính kỹ thuật này được dùng để giữ một thư mục rỗng trong kho: Git không lưu thư mục trống, nên người ta đặt vào đó tệp .gitkeep và chỉ cho phép tệp này.
Có thể đặt quy tắc bỏ qua ở đâu khác
| Vị trí | Áp dụng cho | Có vào kho không |
|---|---|---|
.gitignore ở gốc và các thư mục con | cả nhóm | có |
.git/info/exclude | chỉ bản clone này | không |
tệp toàn cục core.excludesFile | mọi kho của bạn | không |
Rác của hệ điều hành và IDE nên đưa vào bộ bỏ qua toàn cục để không làm bẩn .gitignore chung:
git config --global core.excludesFile ~/.gitignore_global
echo ".DS_Store" >> ~/.gitignore_global
echo ".idea/" >> ~/.gitignore_global
Nếu core.excludesFile chưa được đặt, Git sẽ đọc ~/.config/git/ignore khi tệp này tồn tại.
Vì sao .gitignore không hoạt động
Phàn nàn phổ biến nhất: «đã thêm tệp vào .gitignore, nhưng Git vẫn hiển thị thay đổi». Nguyên nhân gần như luôn giống nhau — tệp đã được theo dõi. Việc bỏ qua không ảnh hưởng tới các tệp đã từng vào commit.
Để kiểm tra quy tắc nào đang có hiệu lực (hoặc vì sao không có hiệu lực), dùng git check-ignore:
git check-ignore -v logs/app.log
# .gitignore:3:*.log logs/app.log
Kết quả cho biết tệp và dòng quy tắc. Nếu không có kết quả, tệp không bị bỏ qua. Một nguyên nhân lỗi khác là khoảng trắng ở cuối dòng mẫu hoặc sai mã hóa tệp (ví dụ UTF-16 khi tạo qua PowerShell).
Gỡ tệp đã commit khỏi kho: git rm --cached
Lệnh git rm xóa tệp khỏi index và khỏi đĩa. Với cờ --cached — chỉ khỏi index: tệp vẫn nằm trên máy bạn nhưng không còn được theo dõi.
echo ".env" >> .gitignore
git rm --cached .env
git commit -m "chore: .env không còn được theo dõi"
git push
Với thư mục cần thêm -r:
git rm -r --cached node_modules/
Nếu có nhiều thứ và không rõ điều gì đã vào kho, có thể dựng lại index hoàn toàn: git rm -r --cached ., sau đó git add . và commit. Trước khi commit, kiểm tra git status — ở đó sẽ hiển thị các tệp đã bị gỡ khỏi index.
Chú ý: bí mật vẫn nằm trong lịch sử
git rm --cached loại tệp khỏi các commit sau, nhưng không khỏi các commit cũ. Nếu mật khẩu hoặc token đã lọt vào kho, ai có bản clone đều có thể thấy nó trong lịch sử. Bí mật đó phải được coi là đã bị lộ và cần cấp lại. Có thể viết lại lịch sử bằng công cụ như git filter-repo, nhưng thao tác này làm thay đổi hash của mọi commit sau đó và cần thống nhất với cả nhóm.
Một hệ quả khác: khi đồng đội chạy git pull, tệp bị gỡ khỏi index bằng --cached sẽ bị xóa khỏi đĩa của họ. Với .env, hãy báo trước cho nhóm hoặc giữ trong kho một tệp mẫu .env.example.
git rm: xóa tệp
git rm old.py # xóa khỏi đĩa và khỏi index
git rm -r legacy/ # xóa thư mục
git rm -n '*.tmp' # chạy thử: chỉ hiển thị sẽ xóa gì
git rm -f changed.py # xóa tệp có thay đổi chưa commit
Nếu xóa tệp bằng rm thông thường, Git sẽ thấy nó là «deleted», và việc xóa phải được thêm qua git add hoặc git rm. git rm làm cả hai bước cùng lúc. Không có -f, nó sẽ từ chối xóa tệp có chỉnh sửa chưa commit — đây là biện pháp bảo vệ để không mất công việc.
git mv: đổi tên và di chuyển
git mv utils.py helpers.py
git mv helpers.py src/helpers.py
git mv là cách viết gọn của mv + git rm đường dẫn cũ + git add đường dẫn mới. Git không lưu sự kiện đổi tên: nó suy ra điều đó từ độ giống nhau của nội dung khi hiển thị git status, git log --follow và git diff. Vì vậy đổi tên bằng git mv và bằng mv thông thường rồi git add -A cho kết quả giống nhau.
Gợi ý thực tế: hãy đổi tên và sửa nội dung tệp trong các commit khác nhau. Nếu tệp thay đổi quá nhiều cùng lúc với việc đổi tên, Git có thể không nhận ra nó là cùng một tệp, và lịch sử với --follow sẽ bị đứt.
Để chỉ đổi chữ hoa, chữ thường trên Windows và macOS (hệ tệp không phân biệt hoa thường), dùng git mv readme.md README.md — đổi tên đơn thuần trong trình quản lý tệp có thể không được Git ghi nhận.
Thứ tự quy tắc và độ ưu tiên
Trong một tệp, các quy tắc được đọc từ trên xuống dưới, và quy tắc khớp cuối cùng sẽ thắng. Vì vậy ngoại lệ !keep.log phải đặt dưới quy tắc *.log, nếu không nó sẽ bị che. Giữa các tệp, ưu tiên như sau: .gitignore trong thư mục con mạnh hơn tệp ở gốc, còn bộ bỏ qua toàn cục và .git/info/exclude có độ ưu tiên thấp nhất.
Ví dụ .gitignore cho dự án web
Dưới đây là một cấu hình thực tế cho dự án Node.js có backend Python. Chú ý các chú thích: sau nửa năm sẽ không ai nhớ vì sao có mỗi quy tắc.
# --- phụ thuộc ---
node_modules/
.venv/
# --- build và cache ---
/dist
/build
__pycache__/
*.pyc
.pytest_cache/
coverage/
# --- nhật ký ---
*.log
npm-debug.log*
# --- môi trường và bí mật ---
.env
.env.*
!.env.example
# --- tệp người dùng tải lên, nhưng giữ lại thư mục ---
uploads/*
!uploads/.gitkeep
Ở đây dùng hai kỹ thuật ngoại lệ: .env.* che mọi biến thể môi trường cục bộ, nhưng mẫu .env.example vẫn nằm trong kho; còn thư mục uploads tồn tại trong bản clone nhờ .gitkeep.
Những gì không nên bỏ qua
Bỏ qua quá tay cũng có hại. Các tệp khóa phụ thuộc — package-lock.json, poetry.lock, composer.lock — trong ứng dụng thường được commit: chúng đảm bảo mọi lập trình viên và CI cài cùng phiên bản gói. Cấu hình định dạng dùng chung (.editorconfig, cấu hình linter) cũng phải nằm trong kho.
Trường hợp gây tranh cãi là cấu hình IDE. Thư mục .vscode/ thường bị bỏ qua hoàn toàn, nhưng đôi khi người ta commit danh sách extension khuyến nghị và tác vụ build: .vscode/*, rồi !.vscode/extensions.json. Quyết định thuộc về nhóm; điều quan trọng là không đưa đường dẫn cá nhân và khóa riêng vào kho chung.
Tình huống: lỡ commit node_modules
Lỗi kinh điển của người mới: chạy git add . trước khi có .gitignore, và hàng chục nghìn tệp lọt vào commit. Cách sửa gồm ba bước:
echo "node_modules/" >> .gitignore
git rm -r --cached node_modules
git commit -m "chore: gỡ node_modules khỏi kho"
Nếu commit chưa được gửi lên máy chủ, tốt hơn là viết lại nó để các tệp nặng không còn nằm trong lịch sử: git reset --soft HEAD~1, sau đó chạy các lệnh trên và tạo commit mới. Khi đã push, tệp nằm trong lịch sử và dung lượng clone sẽ không giảm nếu không viết lại lịch sử.
Để tránh lặp lại, hãy tạo .gitignore trước lần git add đầu tiên và thói quen xem git status trước khi commit: danh sách tệp mới dài bất thường là dấu hiệu nên dừng lại.
Tóm lại
Tệp .gitignore là tệp đầu tiên nên commit trong dự án mới: phụ thuộc, sản phẩm build, nhật ký và bí mật không cần có trong kho. Việc bỏ qua chỉ áp dụng cho tệp chưa được theo dõi, nên tệp đã thêm phải gỡ bằng git rm --cached, còn quy tắc được kiểm tra bằng git check-ignore -v. Để xóa và đổi tên, dùng git rm và git mv để thay đổi được đưa ngay vào index. Làm việc với index và kho đầu tiên được hướng dẫn thực hành trong khóa học Git.
Câu hỏi thường gặp
Có cần commit chính tệp .gitignore không?
Có, để quy tắc áp dụng cho toàn nhóm. Các ngoại lệ cá nhân thì đặt vào .git/info/exclude hoặc bộ bỏ qua toàn cục.
Có thể có nhiều tệp .gitignore không?
Có, bạn có thể đặt .gitignore vào bất kỳ thư mục con nào; các quy tắc của nó áp dụng tương đối với thư mục đó và bổ sung cho quy tắc ở gốc.
Làm sao thêm một tệp đang bị bỏ qua vào commit?
Dùng git add -f đường/dẫn/tệp; sau đó tệp được theo dõi và .gitignore không còn ảnh hưởng tới nó.
Làm sao xem tất cả các tệp bị bỏ qua trong dự án?
Chạy git status --ignored hoặc git ls-files --others --ignored --exclude-standard.
Có thể tạm thời ngừng theo dõi thay đổi của một tệp đã commit không?
Có, bằng git update-index --skip-worktree tệp, nhưng đây là thiết lập cục bộ và không thay thế được .gitignore.
Chủ đề này được giảng chi tiết kèm thực hành trong khóa học “Git с нуля до уверенного разработчика”.
Đến khóa học