Tuần 1 - Ngày 2: Staging Area Chi Tiết và Nghệ Thuật Commit
Mục tiêu học tập
- Nắm vững
git add,git restore --staged,git diff,git diff --staged - Viết commit message tốt theo Conventional Commits
- Hiểu nguyên tắc atomic commit
- Sử dụng
git logđể đọc lịch sử
1. Staging Area — Bộ lọc trước khi commit
Staging area (còn gọi là Index hay Cache) là vùng trung gian giữa Working Directory và Repository. Đây là nơi bạn chọn lọc những thay đổi nào sẽ đi vào commit tiếp theo.
Tình huống thực tế
Working Directory:
- src/auth.js ← fix bug login (liên quan ticket AUTH-42)
- src/dashboard.js ← refactor UI (việc khác, ticket UI-15)
- README.md ← update docs (chưa xong)
Bạn muốn commit ticket AUTH-42 trước mà không commit UI-15 hay docs dở.
→ git add src/auth.js
→ git commit -m "fix: resolve login token expiry bug (AUTH-42)"
Staging Area lúc commit chỉ có src/auth.js.
2. Các lệnh cốt lõi
git add — Đưa thay đổi vào staging
# Stage một file cụ thể
git add src/auth.js
# Stage nhiều file
git add src/auth.js src/utils.js
# Stage tất cả file trong thư mục hiện tại (cẩn thận)
git add .
# Stage tất cả file đã track (bỏ qua untracked)
git add -u
# Stage tất cả thay đổi và untracked files
git add -A
# Interactive staging — chọn từng hunk (đoạn code)
git add -p src/auth.js
# → Git hỏi: Stage this hunk? (y/n/s/q/?)
# y = yes, n = no, s = split nhỏ hơn, q = quit
git add -p là công cụ mạnh mẽ: cho phép bạn stage từng block thay đổi trong một file, giúp tạo commit nguyên tử dù bạn đã thay đổi nhiều thứ trong cùng file.
git restore — Bỏ thay đổi
# Discard thay đổi trong working directory (KHÔNG thể undo!)
git restore src/auth.js
# Unstage file (đưa về unstaged nhưng GIỮ thay đổi trong working dir)
git restore --staged src/auth.js
# Unstage tất cả
git restore --staged .
# Khôi phục về trạng thái của commit cụ thể
git restore --source=HEAD~2 src/auth.js
Cảnh báo:
git restore <file>(không có--staged) xoá thay đổi trong working directory vĩnh viễn (không vào trash). Chỉ dùng khi chắc chắn không cần.
git diff — Xem thay đổi
# Xem thay đổi CHƯA staged (working dir vs staging area)
git diff
# Xem thay đổi ĐÃ staged (staging area vs HEAD commit)
git diff --staged
# hoặc
git diff --cached # tương đương
# Xem diff giữa hai commit
git diff abc123 def456
# Xem diff giữa hai branches
git diff main feature/auth
# Chỉ xem tên file thay đổi (không xem nội dung)
git diff --name-only
git diff --stat
# Diff của một file cụ thể
git diff src/auth.js
Nhớ quy tắc:
git diff= working directory vs staginggit diff --staged= staging vs last commitgit diff HEAD= working directory vs last commit (unstaged + staged)
3. git commit — Lưu snapshot
# Commit với message ngắn
git commit -m "feat: add user login"
# Mở editor để viết message dài (subject + body)
git commit
# Stage tất cả tracked files và commit (bỏ qua untracked)
git commit -am "fix: correct typo in auth module"
# ⚠️ Dùng -am cẩn thận — có thể commit thứ không muốn
# Xem sẽ commit những gì (dry run)
git diff --staged --stat
4. Conventional Commits — Viết commit message đúng cách
Conventional Commits là một convention phổ biến, được nhiều tool tự động hóa dựa vào (changelog, version bump, CI).
Format
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Các type phổ biến
| Type | Khi nào dùng |
|---|---|
feat | Thêm tính năng mới |
fix | Sửa bug |
docs | Thay đổi documentation |
style | Format code, không thay đổi logic |
refactor | Tái cấu trúc code, không fix bug hay add feature |
test | Thêm hoặc sửa test |
chore | Cập nhật build, dependency, tooling |
perf | Cải thiện performance |
ci | Thay đổi CI/CD config |
revert | Revert commit trước |
Ví dụ thực tế
# Ngắn gọn — đủ cho hầu hết trường hợp
git commit -m "feat(auth): add JWT refresh token endpoint"
git commit -m "fix(api): handle null user in profile route"
git commit -m "docs: update deployment guide for production"
git commit -m "chore: upgrade eslint to v9"
# Có body — khi cần giải thích lý do
git commit -m "$(cat <<'EOF'
refactor(db): migrate connection pool to pg-pool v3
pg-pool v2 has a memory leak when connections timeout.
v3 fixes the issue and adds better error handling.
Closes #234
EOF
)"
# Breaking change — thêm ! sau type
git commit -m "feat(api)!: change response format to JSON:API spec"
# → Báo hiệu MAJOR version bump trong Semantic Versioning
Quy tắc viết description tốt
# BAD — không rõ thay đổi gì
fix bug
update file
WIP
# BAD — quá dài, không phải imperative
Fixed the bug where users couldn't login when their session expired
# GOOD — ngắn, imperative, rõ ràng
fix: handle expired session in login flow
feat: add dark mode toggle to settings page
perf: cache database queries in product listing
# Quy tắc: dùng động từ imperative (add, fix, update, remove)
# không phải "added", "fixes", "updating"
5. Atomic Commits — Nguyên tắc quan trọng
Mỗi commit nên làm đúng một việc và làm hoàn chỉnh việc đó.
Tại sao atomic commits quan trọng?
# BAD — một commit làm nhiều thứ không liên quan
"feat: add login page, fix navbar bug, update README, refactor utils"
Hậu quả:
- Khó review (reviewer phải hiểu 4 context khác nhau)
- Khó revert (nếu revert commit này, cả 4 thứ bị revert)
- git bisect không hoạt động đúng
- Lịch sử không đọc được như documentation
# GOOD — mỗi commit là một đơn vị logic
commit 1: "feat: add login page with form validation"
commit 2: "fix: correct active state in navbar"
commit 3: "docs: update README with new auth setup"
commit 4: "refactor: extract common utils to shared module"
Pattern: commit nhiều lần thay vì ít lần
# Workflow nguyên tử
# 1. Làm xong một việc nhỏ → commit ngay
git add src/auth/login.js
git commit -m "feat(auth): add login form component"
# 2. Viết test cho việc đó → commit
git add src/auth/login.test.js
git commit -m "test(auth): add tests for login form validation"
# 3. Sửa CSS → commit
git add src/styles/auth.css
git commit -m "style(auth): add login page responsive styles"
6. git log — Đọc lịch sử
# Log cơ bản
git log
# Một dòng mỗi commit
git log --oneline
# Graph view — thấy được nhánh
git log --oneline --graph --all
# Log của một file cụ thể
git log --oneline src/auth.js
# Log theo tác giả
git log --author="Giang"
# Log theo thời gian
git log --since="2024-01-01" --until="2024-06-30"
# Log với diff (patch)
git log -p src/auth.js
# Số lượng commit gần nhất
git log -5 --oneline
# Tìm commit chứa keyword trong message
git log --grep="AUTH-42"
# Format tùy chỉnh
git log --pretty=format:"%h %an %ar %s"
# %h = short hash, %an = author name, %ar = relative time, %s = subject
# Xem chi tiết một commit cụ thể
git show abc1234
git show HEAD # commit hiện tại
git show HEAD~1 # commit trước đó
7. .gitignore — Loại trừ file không muốn track
# .gitignore — đặt ở root project
node_modules/ # thư mục dependency
.env # secrets — KHÔNG BAO GIỜ commit
.env.local
.env.*.local
dist/ # build output
build/
*.log # log files
.DS_Store # macOS metadata
Thumbs.db # Windows thumbnail cache
.idea/ # IDE config (JetBrains)
.vscode/ # VS Code settings (trừ khi muốn share team)
coverage/ # test coverage reports
*.pyc # Python compiled
__pycache__/
.pytest_cache/
target/ # Java/Rust build output
*.class
# Kiểm tra file có bị ignore không
git check-ignore -v node_modules/lodash
# Xem tất cả ignored files
git status --ignored
# Force add file bị ignore (hiếm khi cần)
git add -f .env.example
Câu hỏi ôn tập
-
Sự khác biệt giữa
git diffvàgit diff --stagedlà gì?Xem đáp án
git diffso sánh working directory với staging area — hiện thay đổi chưa được stage.git diff --staged(hoặc--cached) so sánh staging area với HEAD commit — hiện thay đổi đã staged và sẽ vào commit tiếp theo. Nếu muốn xem tất cả thay đổi so với HEAD (cả staged lẫn unstaged), dùnggit diff HEAD. -
Lệnh nào để stage chỉ một phần thay đổi trong file (không stage cả file)?
Xem đáp án
git add -p <file>(hoặc--patch). Git chia file thành các "hunks" và hỏi từng cái:yđể stage,nđể skip,sđể split nhỏ hơn,qđể thoát. Đây là công cụ quan trọng để tạo atomic commits dù bạn đã thay đổi nhiều thứ trong cùng một file. -
Commit message
"fix bug in login"vi phạm Conventional Commits ở điểm nào?Xem đáp án
Vi phạm hai điểm: (1) Thiếu type prefix — phải là
fix:theo format<type>: <description>. (2) Description dùng noun phrase thay vì imperative verb — nên là"fix: resolve login token expiry bug"không phải"fix bug in login". Ngoài ra, "bug" quá mơ hồ — nên mô tả cụ thể hơn để lịch sử có thể đọc như documentation. -
Tại sao atomic commits quan trọng khi debug bằng
git bisect?Xem đáp án
git bisectdùng binary search để tìm commit đầu tiên gây ra bug — nó checkout từng commit và chạy test. Nếu commit lớn chứa nhiều thay đổi không liên quan, khi bisect tìm ra "commit này là bad", bạn vẫn không biết phần nào trong đó gây bug. Atomic commits (mỗi commit làm đúng một việc) đảm bảo khi bisect tìm được commit, bạn biết ngay chính xác thay đổi nào là nguyên nhân. -
git restore src/app.jsvàgit restore --staged src/app.jskhác nhau thế nào?Xem đáp án
git restore src/app.js(không có flag) xoá thay đổi trong working directory — file trở về trạng thái của staging area. Không thể undo — thay đổi bị mất vĩnh viễn (không qua trash).git restore --staged src/app.jsunstage file — đưa thay đổi từ staging area về unstaged, nhưng giữ nguyên thay đổi trong working directory. An toàn hơn nhiều, chỉ ảnh hưởng đến Index.
Bài tập thực hành
# Setup
mkdir git-commit-lab && cd git-commit-lab
git init
git config user.name "Your Name"
git config user.email "you@example.com"
# 1. Tạo vài files
echo "# App" > README.md
echo "console.log('hello')" > app.js
echo "PORT=3000" > .env
echo "*.log\n.env\nnode_modules/" > .gitignore
# 2. Quan sát git status
git status # thấy gì?
# 3. Stage README.md và commit
git add README.md
git status # README staged, app.js untracked, .env ignored
# 4. Thử diff
git diff # không có gì (working = staging cho README)
echo "console.log('world')" >> app.js
git diff # thấy thay đổi app.js (unstaged)
git add app.js
git diff # không có gì
git diff --staged # thấy cả README.md và app.js staged
# 5. Commit atomic
git commit -m "feat: add initial app files"
# 6. Unstage và thử git add -p
echo "// auth function" >> app.js
echo "// utils function" >> app.js
git add -p app.js # thử stage từng hunk riêng
# 7. Xem log
git log --oneline
git log --oneline --graph --all
Tài liệu tham khảo chính thức
Tiếp theo: Ngày 3 — Branching