Git Nâng Cao: Workflow Tiết Kiệm Hàng Giờ Mỗi Tuần
Interactive rebase, cherry-pick, bisect, worktree, reflog rescue, và các chiến lược branching thực sự hiệu quả. Các lệnh Git tôi dùng hàng ngày mà hầu hết lập trình viên không biết tồn tại.
Hầu hết lập trình viên học năm lệnh Git và dừng lại ở đó. add, commit, push, pull, merge. Có thể thêm checkout và branch nếu họ cảm thấy phiêu lưu. Điều đó giúp bạn qua năm đầu tiên. Sau đó branch của bạn có 47 commit với message kiểu "fix" và "wip" và "please work," bạn vô tình reset thứ gì đó không nên, và bạn dành 40 phút trên Stack Overflow để cố hoàn tác một merge lỗi.
Tôi đã sử dụng Git nhiều năm. Không phải qua loa — sử dụng nặng. Nhiều branch, nhiều repo, nhiều cộng tác viên, cả ngày, mỗi ngày. Những gì sau đây là các lệnh và workflow tôi thực sự sử dụng. Không phải những cái trông đẹp trong tutorial. Mà là những cái tiết kiệm thời gian thực cho tôi, mỗi tuần.
Interactive Rebase: Dọn Dẹp Trước Khi Ai Nhìn Thấy#
Branch của bạn có mười hai commit. Một nửa là "fix typo." Một cái nói "undo previous commit." Một cái khác nói "actually fix it this time." Bạn sắp mở PR. Không ai cần nhìn thấy lịch sử đó.
Interactive rebase là cách bạn viết lại lịch sử trên branch trước khi chia sẻ. Nó cho phép bạn squash commit lại, sửa message, sắp xếp lại, hoặc xóa hoàn toàn.
Lệnh Cơ Bản#
git rebase -i HEAD~5Lệnh này mở editor hiển thị 5 commit cuối của bạn, cũ nhất trước:
pick a1b2c3d Add user authentication endpoint
pick d4e5f6g Fix typo in auth middleware
pick h7i8j9k Add rate limiting
pick l0m1n2o Fix rate limit bug
pick p3q4r5s Update auth testsMỗi dòng bắt đầu với một lệnh. Đổi pick thành một trong các lệnh sau:
squash(hoặcs) — Gộp commit này vào commit phía trên, kết hợp messagefixup(hoặcf) — Giống squash, nhưng bỏ message của commit nàyreword(hoặcr) — Giữ commit nhưng đổi messagedrop(hoặcd) — Xóa commit này hoàn toànedit(hoặce) — Tạm dừng rebase tại commit này để bạn có thể sửa đổi
Một Phiên Dọn Dẹp Thực Tế#
Đây là những gì tôi thực sự làm. Lịch sử lộn xộn ở trên trở thành:
pick a1b2c3d Add user authentication endpoint
fixup d4e5f6g Fix typo in auth middleware
pick h7i8j9k Add rate limiting
fixup l0m1n2o Fix rate limit bug
pick p3q4r5s Update auth testsLưu và đóng. Giờ bạn có ba commit sạch thay vì năm. Fix typo được gộp vào commit auth. Fix rate limit bug được gộp vào commit rate limit. Reviewer PR của bạn thấy một tiến trình sạch, logic.
Sắp Xếp Lại Commit#
Bạn có thể di chuyển các dòng theo nghĩa đen. Nếu commit test nên đứng trước commit rate limiting, chỉ cần di chuyển dòng:
pick a1b2c3d Add user authentication endpoint
pick p3q4r5s Update auth tests
pick h7i8j9k Add rate limitingGit sẽ replay commit theo thứ tự mới. Nếu có conflict, nó sẽ tạm dừng và cho bạn giải quyết.
Mẹo Autosquash#
Nếu bạn biết một commit là fix cho commit trước đó, đánh dấu ngay khi commit:
git commit --fixup=a1b2c3dLệnh này tạo commit với message fixup! Add user authentication endpoint. Sau đó khi rebase:
git rebase -i --autosquash HEAD~5Git tự động sắp xếp lại các fixup commit ngay dưới target và đánh dấu chúng là fixup. Bạn chỉ cần lưu và đóng. Không cần chỉnh sửa thủ công.
Tôi dùng cái này liên tục. Đây là cách nhanh nhất để iterate trên branch trong khi giữ lịch sử cuối cùng sạch.
Quy Tắc Vàng#
Không bao giờ rebase commit đã được push lên branch chia sẻ. Nếu người khác đã dựa công việc trên những commit đó, viết lại lịch sử sẽ gây ra vấn đề thực sự. Rebase feature branch của riêng bạn trước khi merge. Không bao giờ rebase main.
Nếu bạn đã push feature branch và cần rebase:
git push --force-with-leaseFlag --force-with-lease an toàn hơn --force. Nó từ chối push nếu ai đó đã push lên cùng branch kể từ lần fetch cuối của bạn. Nó không ngăn tất cả vấn đề, nhưng bắt được vấn đề phổ biến nhất.
Cherry-Pick: Chuyển Commit Chính Xác#
Cherry-pick lấy một commit cụ thể từ branch này và áp dụng vào branch khác. Không phải merge. Không phải rebase. Chỉ một commit, được áp dụng sạch sẽ.
Khi Nào Tôi Thực Sự Dùng#
Kịch bản phổ biến nhất: tôi sửa bug trên feature branch, nhưng fix đó cũng cần đến main hoặc release branch ngay bây giờ. Tôi không muốn merge toàn bộ feature branch. Tôi chỉ muốn đúng một fix đó.
# Tìm hash commit của fix
git log --oneline feature/user-auth
# a1b2c3d Fix null pointer in session validation
# Chuyển sang main và cherry-pick
git checkout main
git cherry-pick a1b2c3dXong. Fix đã ở trên main như một commit mới với cùng thay đổi.
Cherry-Pick Không Commit#
Đôi khi bạn muốn áp dụng thay đổi nhưng chưa commit. Có thể bạn muốn kết hợp nhiều cherry-pick thành một commit, hoặc sửa đổi thay đổi nhẹ:
git cherry-pick --no-commit a1b2c3dThay đổi được stage nhưng không commit. Bạn có thể sửa đổi, thêm thay đổi khác, rồi commit khi sẵn sàng.
Cherry-Pick Phạm Vi#
Cần nhiều commit liên tiếp? Dùng cú pháp phạm vi:
git cherry-pick a1b2c3d..f6g7h8iLệnh này cherry-pick mọi thứ sau a1b2c3d đến và bao gồm f6g7h8i. Lưu ý a1b2c3d bị loại trừ. Nếu bạn muốn bao gồm nó:
git cherry-pick a1b2c3d^..f6g7h8iXử Lý Conflict#
Conflict cherry-pick hoạt động giống merge conflict. Khi xảy ra:
# Git sẽ thông báo có conflict
# Sửa file conflict, sau đó:
git add .
git cherry-pick --continueHoặc nếu bạn đổi ý:
git cherry-pick --abortMột điều cần chú ý: commit cherry-pick tạo hash commit mới. Nếu bạn sau đó merge branch gốc, Git thường đủ thông minh để xử lý trùng lặp. Nhưng nếu bạn cherry-pick quyết liệt giữa các branch sẽ merge sau, bạn có thể thấy conflict không mong đợi. Hãy dùng nó phẫu thuật, không phải như chiến lược merge.
Git Bisect: Tìm Kiếm Nhị Phân Cho Bug#
Có thứ gì đó hỏng. Bạn biết nó hoạt động hai tuần trước. Đã có 200 commit kể từ đó. Commit nào gây hỏng?
Bạn có thể kiểm tra từng commit thủ công. Hoặc bạn có thể dùng git bisect, sử dụng tìm kiếm nhị phân để tìm chính xác commit gây hỏng trong log2(n) bước. Cho 200 commit, đó là khoảng 7-8 lần kiểm tra thay vì 200.
Cách Thủ Công#
# Bắt đầu bisect
git bisect start
# Đánh dấu commit hiện tại là bad (bug tồn tại ở đây)
git bisect bad
# Đánh dấu commit đã biết tốt (bug chưa tồn tại ở đây)
git bisect good v2.1.0Git checkout một commit giữa good và bad. Test nó. Sau đó:
# Nếu bug tồn tại tại commit này:
git bisect bad
# Nếu bug không tồn tại tại commit này:
git bisect goodGit thu hẹp phạm vi một nửa mỗi lần. Sau 7-8 bước, nó cho bạn biết:
a1b2c3d4e5f6g7h is the first bad commit
commit a1b2c3d4e5f6g7h
Author: Some Developer <dev@example.com>
Date: Tue Feb 18 14:23:01 2026 +0300
Refactor session handling to use async middlewareGiờ bạn biết chính xác commit nào đưa bug vào. Khi xong:
git bisect resetLệnh này đưa bạn trở lại nơi bạn bắt đầu.
Cách Tự Động (Đây Mới Là Sức Mạnh Thực Sự)#
Nếu bạn có test phát hiện bug, bạn có thể tự động hóa toàn bộ:
git bisect start
git bisect bad HEAD
git bisect good v2.1.0
git bisect run npm test -- --grep "session validation"Git sẽ tự động checkout commit, chạy test, và đánh dấu good hoặc bad dựa trên exit code. Zero là good, khác zero là bad. Đi ra ngoài, quay lại, và nó cho bạn biết chính xác commit.
Bạn có thể dùng bất kỳ script nào:
git bisect run ./test-regression.shTrong đó test-regression.sh là:
#!/bin/bash
npm run build 2>/dev/null || exit 125 # 125 nghĩa là "bỏ qua commit này"
npm test -- --grep "session" || exit 1 # 1 nghĩa là "bad"
exit 0 # 0 nghĩa là "good"Exit code 125 đặc biệt — nó bảo bisect bỏ qua commit đó (hữu ích nếu commit không biên dịch được). Đây là tính năng có vẻ ngách cho đến khi bạn cần, và khi đó nó tiết kiệm cả buổi chiều.
Một Phiên Bisect Thực Tế#
Đây là cách nó trông trong thực tế:
$ git bisect start
$ git bisect bad HEAD
$ git bisect good abc1234
Bisecting: 97 revisions left to test after this (roughly 7 steps)
[def5678...] Commit message here
$ npm test -- --grep "login flow"
# Test fail
$ git bisect bad
Bisecting: 48 revisions left to test after this (roughly 6 steps)
[ghi9012...] Another commit message
$ npm test -- --grep "login flow"
# Test pass
$ git bisect good
Bisecting: 24 revisions left to test after this (roughly 5 steps)
...
# Sau ~7 lần lặp:
abc1234def5678ghi is the first bad commitBảy bước để tìm kim trong đống rơm 200 commit.
Git Worktree: Nhiều Branch, Không Cần Stash#
Đây là tính năng Git ít được sử dụng nhất. Tôi tin rằng hầu hết lập trình viên không biết nó tồn tại.
Vấn đề: bạn đang deep trong feature branch. File thay đổi khắp nơi. Rồi ai đó nói "bạn có thể xem cái bug production này nhanh không?" Bạn có ba lựa chọn:
- Stash mọi thứ, chuyển branch, sửa, chuyển lại, pop stash. Hy vọng không có gì sai.
- Commit công việc dở dang với message "wip". Xấu nhưng chức năng.
- Clone repo lần nữa vào thư mục khác.
Hoặc lựa chọn 4: worktree.
Worktree Là Gì#
Worktree là thư mục làm việc thứ hai (hoặc thứ ba, hoặc thứ tư) liên kết với cùng repository. Mỗi worktree có branch riêng, file làm việc riêng, index riêng. Nhưng chúng chia sẻ cùng dữ liệu .git, nên bạn không nhân đôi toàn bộ repo.
Thêm Worktree#
# Bạn đang trên feature/user-auth, deep trong công việc
# Cần sửa bug trên main:
git worktree add ../hotfix-session mainLệnh này tạo thư mục mới ../hotfix-session với main đã checkout. Thư mục hiện tại của bạn vẫn y nguyên. Không stash, không commit, không gián đoạn.
cd ../hotfix-session
# Sửa bug
git add .
git commit -m "Fix null pointer in session validation"
git push origin main
cd ../my-project
# Tiếp tục làm feature như không có gì xảy raTạo Branch Mới Trong Worktree#
git worktree add ../hotfix-nav -b hotfix/nav-crash mainLệnh này tạo worktree VÀ tạo branch mới hotfix/nav-crash dựa trên main.
Quản Lý Worktree#
# Liệt kê tất cả worktree
git worktree list
# /home/dev/my-project abc1234 [feature/user-auth]
# /home/dev/hotfix-session def5678 [main]
# Xóa worktree khi xong
git worktree remove ../hotfix-session
# Nếu thư mục đã bị xóa:
git worktree pruneTại Sao Tốt Hơn Stash#
Stash ổn cho chuyển ngữ cảnh nhanh. Nhưng worktree tốt hơn cho bất cứ thứ gì mất hơn năm phút:
- IDE của bạn vẫn mở trên feature branch. Không reindex, không mất vị trí cuộn.
- Bạn có thể chạy test ở cả hai thư mục cùng lúc.
- Không có rủi ro stash conflict hoặc quên mình đã stash gì.
- Bạn có thể chạy dev server lâu dài ở một worktree và clean build ở worktree khác.
Tôi thường giữ hai hoặc ba worktree active: feature branch chính, worktree main cho kiểm tra nhanh, và đôi khi worktree review nơi tôi checkout PR của người khác.
Một Lưu Ý#
Bạn không thể checkout cùng branch ở hai worktree. Đó là theo thiết kế — nó ngăn bạn tạo thay đổi conflict cho cùng branch ở hai nơi. Nếu bạn thử, Git sẽ từ chối.
Reflog: Nút Undo Cho Mọi Thứ#
Bạn hard reset và mất commit. Bạn xóa branch. Bạn rebase và mọi thứ sai lầm khủng khiếp. Bạn nghĩ công việc đã mất.
Không mất đâu. Git hầu như không bao giờ thực sự xóa bất cứ thứ gì. Reflog là lưới an toàn của bạn.
Reflog Là Gì#
Mỗi khi HEAD di chuyển — mỗi commit, checkout, rebase, reset, merge — Git ghi lại trong reflog. Đó là log mọi nơi HEAD đã đến, theo thứ tự.
git reflogOutput:
a1b2c3d (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
f4e5d6c HEAD@{1}: commit: Add payment processing
b7a8c9d HEAD@{2}: commit: Update user dashboard
e0f1g2h HEAD@{3}: commit: Fix auth token refresh
i3j4k5l HEAD@{4}: checkout: moving from feature/payments to mainMỗi entry có index (HEAD@{0}, HEAD@{1}, v.v.) và mô tả điều gì đã xảy ra.
Khôi Phục Sau Hard Reset#
Bạn vô tình chạy git reset --hard HEAD~3 và mất ba commit. Chúng ngay trong reflog:
# Xem những gì bạn mất
git reflog
# Commit trước reset là HEAD@{1}
git reset --hard f4e5d6cCả ba commit trở lại. Khủng hoảng đã được ngăn chặn.
Khôi Phục Branch Đã Xóa#
Bạn xóa branch có công việc chưa merge:
git branch -D feature/experimental
# Ôi không, cái đó có hai tuần công việcCommit vẫn tồn tại. Tìm chúng:
git reflog | grep "feature/experimental"
# Hoặc xem qua reflog cho commit cuối trên branch đó
# Tìm thấy rồi. Tạo lại branch tại commit đó:
git branch feature/experimental a1b2c3dBranch trở lại, với tất cả commit.
Khôi Phục Sau Rebase Lỗi#
Bạn rebase và mọi thứ sai. Conflict khắp nơi, commit sai, hỗn loạn:
# Reflog cho thấy bạn ở đâu trước rebase
git reflog
# a1b2c3d HEAD@{0}: rebase (finish): ...
# ...
# f4e5d6c HEAD@{5}: rebase (start): checkout main
# b7a8c9d HEAD@{6}: commit: Your last good commit
# Quay lại trước rebase
git reset --hard b7a8c9dBạn trở lại chính xác nơi bạn ở trước khi rebase bắt đầu. Như thể nó chưa bao giờ xảy ra.
Lưới An Toàn 30 Ngày#
Mặc định, Git giữ entry reflog trong 30 ngày (90 ngày cho commit reachable). Sau đó, chúng có thể bị garbage collect. Vậy bạn có một tháng để nhận ra mình đã mắc lỗi. Trong thực tế, đây là quá đủ.
Bạn có thể kiểm tra thời hạn:
git config gc.reflogExpire
# mặc định: 90.days.ago (cho reachable)
git config gc.reflogExpireUnreachable
# mặc định: 30.days.ago (cho unreachable)Nếu bạn lo lắng, tăng nó lên:
git config --global gc.reflogExpireUnreachable "180.days.ago"Quy Tắc Cá Nhân#
Trước bất kỳ thao tác hủy hoại nào — hard reset, force push, xóa branch — tôi chạy git log --oneline -10 trước. Tôi ghi nhớ HEAD hiện tại. Mất hai giây và đã cứu tôi nhiều lần khỏi hoảng sợ không cần thiết.
Stash Đúng Cách: Không Chỉ git stash#
Hầu hết mọi người dùng stash như thế này:
git stash
# làm gì đó
git stash popHoạt động, nhưng tương đương với việc ném mọi thứ vào hộp ghi "đồ." Khi bạn có ba stash, bạn không biết cái nào là cái nào.
Đặt Tên Stash#
git stash push -m "WIP: user auth form validation"Giờ khi liệt kê stash:
git stash list
# stash@{0}: On feature/auth: WIP: user auth form validation
# stash@{1}: On main: Quick fix attempt for nav bug
# stash@{2}: On feature/payments: Experiment with Stripe webhooksBạn thấy chính xác mỗi stash chứa gì.
Bao Gồm File Chưa Được Theo Dõi#
Mặc định, git stash chỉ stash file đã tracked. File mới bạn chưa add sẽ bị bỏ lại:
# Stash mọi thứ, bao gồm file mới
git stash push --include-untracked -m "WIP: new auth components"
# Hoặc bao gồm cả file bị ignore (hiếm khi cần)
git stash push --all -m "Full workspace snapshot"Tôi dùng --include-untracked gần như mỗi lần. Để file mới lại khi chuyển branch gây nhầm lẫn.
Stash Một Phần#
Đây là cái hầu hết mọi người không biết. Bạn có thể stash file cụ thể:
# Stash chỉ file cụ thể
git stash push -m "Just the auth changes" src/auth/ src/middleware.tsHoặc dùng patch mode để stash hunk cụ thể trong file:
git stash push --patch -m "Partial: only the validation logic"Git sẽ đi qua từng thay đổi một cách tương tác và hỏi bạn muốn stash không. y cho có, n cho không, s để chia hunk thành phần nhỏ hơn.
Apply vs Pop#
# Pop: áp dụng và xóa khỏi danh sách stash
git stash pop stash@{2}
# Apply: áp dụng nhưng giữ trong danh sách stash
git stash apply stash@{2}Tôi dùng apply khi không chắc stash sẽ áp dụng sạch. Nếu có conflict, stash được giữ lại. Với pop, nếu có conflict, stash vẫn ở trong danh sách (nhiều người không biết điều này), nhưng tôi thích ý định rõ ràng của apply.
Xem Nội Dung Stash#
# Xem file nào thay đổi trong stash
git stash show stash@{0}
# Xem diff đầy đủ
git stash show -p stash@{0}Tạo Branch Từ Stash#
Nếu stash đã phát triển thành thứ gì đó lớn hơn:
git stash branch feature/auth-validation stash@{0}Lệnh này tạo branch mới từ commit nơi bạn ban đầu stash, áp dụng stash, và xóa nó. Sạch sẽ.
Chiến Lược Branching: Cái Nào Thực Sự Hiệu Quả#
Có ba chiến lược branching chính thống. Mỗi cái có bối cảnh tỏa sáng và bối cảnh gây đau khổ.
Gitflow#
Kinh điển. main, develop, feature/*, release/*, hotfix/*. Được tạo bởi Vincent Driessen năm 2010.
# Feature branch
git checkout -b feature/user-auth develop
# ... làm việc ...
git checkout develop
git merge --no-ff feature/user-auth
# Release branch
git checkout -b release/2.1.0 develop
# ... fix cuối cùng ...
git checkout main
git merge --no-ff release/2.1.0
git tag -a v2.1.0 -m "Release 2.1.0"
git checkout develop
git merge --no-ff release/2.1.0
# Hotfix
git checkout -b hotfix/session-fix main
# ... fix ...
git checkout main
git merge --no-ff hotfix/session-fix
git checkout develop
git merge --no-ff hotfix/session-fixKhi nào hiệu quả: Ứng dụng mobile, phần mềm desktop, bất cứ thứ gì có release có tên và hỗ trợ nhiều phiên bản đồng thời. Nếu bạn ship v2.1 và v3.0 và cần patch cả hai, Gitflow xử lý được.
Khi nào không: Ứng dụng web với continuous deployment. Nếu bạn deploy lên production 5 lần một ngày, ceremony của release branch và develop branch là overhead thuần. Hầu hết team web áp dụng Gitflow kết thúc với develop branch mãi mãi bị hỏng và release branch không ai hiểu.
GitHub Flow#
Đơn giản. Bạn có main. Bạn tạo feature branch. Bạn mở PR. Bạn merge vào main. Bạn deploy main.
git checkout -b feature/user-auth main
# ... làm việc ...
git push origin feature/user-auth
# Mở PR, được review, merge
# main luôn deployableKhi nào hiệu quả: Team nhỏ đến trung bình shipping ứng dụng web. Continuous deployment. Nếu main luôn được deploy, đây là tất cả bạn cần. Đây là cái trang này sử dụng.
Khi nào không: Khi bạn cần duy trì nhiều phiên bản release, hoặc khi bạn có chu kỳ QA dài trước deployment. GitHub Flow giả định main đến production nhanh.
Trunk-Based Development#
Mọi người commit vào main ("trunk") trực tiếp hoặc qua branch tồn tại rất ngắn (ít hơn một ngày). Không có feature branch chạy lâu.
# Branch tồn tại ngắn (merge cùng ngày)
git checkout -b fix/auth-token main
# ... thay đổi nhỏ, tập trung ...
git push origin fix/auth-token
# PR được review và merge trong vài giờ, không phải vài ngàyKhi nào hiệu quả: Team hiệu suất cao với CI/CD tốt, bộ test toàn diện, và feature flag. Google, Meta, và hầu hết công ty tech lớn sử dụng trunk-based development. Nó buộc thay đổi nhỏ, tăng dần và loại bỏ merge hell.
Khi nào không: Team không có test coverage tốt hoặc CI. Nếu merge vào trunk nghĩa là deploy code chưa test, bạn sẽ phá production liên tục. Bạn cũng cần feature flag cho bất cứ thứ gì mất hơn một ngày để build:
# Feature flag trong code
if (featureFlags.isEnabled('new-checkout-flow')) {
renderNewCheckout();
} else {
renderLegacyCheckout();
}Khuyến Nghị Của Tôi#
Cho hầu hết team phát triển web: bắt đầu với GitHub Flow. Nó đơn giản, hoạt động, và không cần tooling ngoài những gì GitHub/GitLab đã cung cấp.
Nếu team phát triển quá 15-20 kỹ sư và bạn deploy nhiều lần trong ngày, xem xét trunk-based development với feature flag. Đầu tư vào hạ tầng feature flag tự hoàn vốn nhờ giảm merge conflict và iterate nhanh hơn.
Nếu bạn shipping phần mềm có phiên bản (ứng dụng mobile, công cụ CLI, thư viện): Gitflow hoặc phiên bản đơn giản hóa. Bạn thực sự cần những release branch đó.
Đừng chọn chiến lược vì blog nào đó nói nó tốt nhất. Chọn cái phù hợp với cách bạn thực sự ship.
Git Hook: Tự Động Hóa Những Thứ Bạn Hay Quên#
Git hook là script chạy tự động tại các điểm cụ thể trong workflow Git. Chúng local trên máy bạn (không push lên remote), nghĩa là bạn cần cách chia sẻ chúng với team.
Những Hook Thực Sự Quan Trọng#
pre-commit — Chạy trước mỗi commit. Dùng cho lint và format:
#!/bin/bash
# .git/hooks/pre-commit
# Chạy ESLint chỉ trên file đã stage
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|tsx)$')
if [ -n "$STAGED_FILES" ]; then
echo "Running ESLint on staged files..."
npx eslint $STAGED_FILES --quiet
if [ $? -ne 0 ]; then
echo "ESLint failed. Fix errors before committing."
exit 1
fi
fi
# Chạy Prettier trên file đã stage
if [ -n "$STAGED_FILES" ]; then
echo "Running Prettier..."
npx prettier --check $STAGED_FILES
if [ $? -ne 0 ]; then
echo "Prettier check failed. Run 'npx prettier --write' first."
exit 1
fi
ficommit-msg — Xác nhận format commit message. Hoàn hảo cho enforcing conventional commit:
#!/bin/bash
# .git/hooks/commit-msg
COMMIT_MSG=$(cat "$1")
PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{1,72}"
if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
echo "Invalid commit message format."
echo "Expected: type(scope): description"
echo "Example: feat(auth): add session refresh endpoint"
echo ""
echo "Valid types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert"
exit 1
fipre-push — Chạy trước khi push. Dùng cho test:
#!/bin/bash
# .git/hooks/pre-push
echo "Running tests before push..."
npm test
if [ $? -ne 0 ]; then
echo "Tests failed. Push aborted."
exit 1
fiHusky vs Native Hook#
Native hook nằm trong .git/hooks/. Vấn đề: thư mục .git không được Git theo dõi, nên bạn không thể chia sẻ hook qua repo. Mọi người phải setup thủ công.
Husky giải quyết vấn đề này. Nó lưu cấu hình hook trong repo và setup .git/hooks tự động khi npm install:
npx husky initLệnh này tạo thư mục .husky/. Thêm hook dưới dạng file:
# .husky/pre-commit
npx lint-stagedKết hợp với lint-staged, bạn có kiểm tra pre-commit nhanh, có mục tiêu:
{
"lint-staged": {
"*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.css": ["prettier --write"],
"*.json": ["prettier --write"]
}
}Cái này chạy ESLint và Prettier chỉ trên file bạn đang commit. Không phải toàn bộ codebase. Nhanh.
Khi Nào Bỏ Qua Hook#
Đôi khi bạn cần commit mà không chạy hook. Hotfix khẩn cấp, commit work-in-progress trên branch riêng:
git commit --no-verify -m "WIP: debugging production issue"Dùng tiết kiệm. Nếu bạn thấy mình bỏ qua hook thường xuyên, hook của bạn có lẽ quá chậm hoặc quá nghiêm ngặt.
Alias Tiết Kiệm Thời Gian#
File .gitconfig của tôi đã phát triển qua nhiều năm. Đây là các alias đã tồn tại — những cái tôi thực sự dùng hàng ngày, không phải những cái tôi thêm vì chúng trông hay.
Pretty Log#
git log mặc định rất dài dòng. Cái này cho bạn view gọn, nhiều màu, dạng graph:
git config --global alias.lg "log --oneline --graph --all --decorate"Sử dụng:
git lg
# * a1b2c3d (HEAD -> main) Fix session validation
# | * d4e5f6g (feature/payments) Add Stripe integration
# | * h7i8j9k Update payment models
# |/
# * l0m1n2o Merge PR #42
# * p3q4r5s Add user dashboardTôi chạy cái này 20 lần một ngày. Đây là cách nhanh nhất để hiểu trạng thái repository.
Undo Commit Cuối#
Giữ thay đổi, chỉ undo commit:
git config --global alias.undo "reset HEAD~1 --mixed"Sử dụng:
git undo
# Commit đã mất, nhưng tất cả thay đổi vẫn trong working directoryTôi dùng khi commit quá sớm, quên file, hoặc muốn tái cấu trúc thay đổi.
Unstage Mọi Thứ#
git config --global alias.unstage "reset HEAD --"Sử dụng:
git unstage src/auth/session.ts
# File được unstage nhưng thay đổi được giữLiệt Kê Tất Cả Alias#
Vì bạn sẽ quên mình đã setup gì:
git config --global alias.aliases "config --get-regexp ^alias\\."Thêm Alias Tôi Dùng#
# Xem những gì bạn sắp commit
git config --global alias.staged "diff --staged"
# Status ngắn
git config --global alias.st "status -sb"
# Amend mà không đổi message
git config --global alias.amend "commit --amend --no-edit"
# Xem commit cuối
git config --global alias.last "log -1 HEAD --stat"
# Pull với rebase thay vì merge
git config --global alias.up "pull --rebase --autostash"
# Xóa branch đã merge vào main
git config --global alias.cleanup "!git branch --merged main | grep -v '^[ *]*main$' | xargs git branch -d"Alias up đặc biệt tốt. pull --rebase giữ lịch sử linear thay vì tạo merge commit cho mỗi pull. --autostash tự động stash và khôi phục thay đổi nếu bạn có dirty file. Đây là điều pull nên hoạt động mặc định.
Alias cleanup xóa branch local đã merge vào main. Theo thời gian, bạn tích lũy hàng chục branch cũ. Chạy cái này hàng tuần.
Các Lệnh Tôi Dùng Hàng Ngày#
Đây không phải alias hay tính năng nâng cao. Chỉ là các lệnh tôi chạy liên tục mà nhiều lập trình viên dường như không biết.
Lệnh Log Tối Thượng#
git log --oneline --graph --allHiển thị mọi branch, mọi merge, toàn bộ topology repo trong view gọn. Đây là thứ đầu tiên tôi chạy khi pull thay đổi. Nó trả lời "chuyện gì đang xảy ra trong repo này?"
Diff Thay Đổi Đã Stage#
git diff --stagedHiển thị những gì sắp được commit. Không phải thay đổi trong working directory — mà là những gì thực sự đã stage. Tôi luôn chạy cái này trước khi commit. Luôn luôn. Nó bắt được bao gồm vô tình, debug statement, console.log không nên có.
# Xem thay đổi chưa stage
git diff
# Xem thay đổi đã stage
git diff --staged
# Xem cả hai
git diff HEADXem Commit Cụ Thể#
git show a1b2c3dHiển thị diff đầy đủ của một commit. Hữu ích khi review lịch sử, hiểu commit thực sự thay đổi gì.
# Chỉ xem file nào thay đổi
git show --stat a1b2c3d
# Xem file cụ thể tại commit cụ thể
git show a1b2c3d:src/auth/session.tsCái cuối cực kỳ hữu ích. Bạn có thể xem bất kỳ file nào tại bất kỳ thời điểm nào trong lịch sử mà không cần checkout commit đó.
Blame Với Phạm Vi Dòng#
git blame -L 42,60 src/auth/session.tsCho thấy ai đã sửa đổi dòng 42-60 lần cuối. Hữu ích hơn blame toàn bộ file, thường quá choáng ngợp.
# Hiển thị cả commit message, không chỉ hash
git blame -L 42,60 --show-name src/auth/session.ts
# Bỏ qua thay đổi whitespace (rất hữu ích)
git blame -w -L 42,60 src/auth/session.ts
# Hiển thị commit trước commit bị blame (đào sâu hơn)
git log --follow -p -- src/auth/session.tsFlag -w quan trọng. Không có nó, blame sẽ gán dòng cho người cuối cùng format lại file, hiếm khi là người bạn đang tìm.
Tìm Chuỗi Xuyên Suốt Lịch Sử#
# Tìm khi nào hàm được thêm hoặc xóa
git log -S "validateSession" --oneline
# Tìm khi nào mẫu regex xuất hiện
git log -G "session.*timeout" --oneline-S (hay "pickaxe") tìm commit nơi số lần xuất hiện của chuỗi thay đổi. -G tìm commit nơi diff khớp regex. Cả hai mạnh mẽ cho khảo cổ — tìm hiểu khi nào thứ gì đó được giới thiệu hoặc bị xóa.
Xem Gì Thay Đổi Giữa Hai Điểm#
# Gì thay đổi giữa hai branch
git diff main..feature/auth
# Gì thay đổi trên branch này kể từ khi tách khỏi main
git diff main...feature/auth
# Chỉ liệt kê file thay đổi
git diff main...feature/auth --name-only
# View thống kê (file + insertion/deletion)
git diff main...feature/auth --statHai chấm vs ba chấm quan trọng. Hai chấm hiển thị sự khác biệt giữa đầu hai branch. Ba chấm hiển thị gì thay đổi ở phía phải kể từ khi tách khỏi phía trái. Ba chấm thường là những gì bạn muốn khi review feature branch.
Dọn File Không Được Theo Dõi#
# Xem gì sẽ bị xóa (chạy thử)
git clean -n
# Xóa file không tracked
git clean -f
# Xóa file và thư mục không tracked
git clean -fd
# Xóa file không tracked và bị ignore (lựa chọn hạt nhân)
git clean -fdxLuôn chạy với -n trước. git clean -fdx sẽ xóa node_modules, .env, build artifact — mọi thứ không được Git theo dõi. Hữu ích cho khởi đầu thực sự mới, nhưng hủy hoại.
Khôi Phục File Đơn Lẻ Từ Branch Khác#
# Lấy phiên bản main branch của file mà không chuyển branch
git restore --source main -- src/config/database.tsHoặc từ commit cụ thể:
git restore --source a1b2c3d -- src/config/database.tsCái này sạch hơn cú pháp cũ git checkout main -- path/to/file, và nó không ảnh hưởng HEAD.
Kết Hợp Tất Cả: Một Workflow Thực Tế#
Đây là cách một ngày thường trông như thế nào với những công cụ này:
# Sáng: kiểm tra chuyện gì đang xảy ra
git lg
git fetch --all
# Bắt đầu feature
git checkout -b feature/session-refresh main
# Làm việc, commit từng bước
git add -p # Stage hunk cụ thể, không phải toàn bộ file
git commit -m "Add token refresh endpoint"
git commit -m "Add refresh token rotation"
git commit -m "Fix: handle expired refresh tokens"
git commit -m "Add integration tests"
# Bị gián đoạn: cần sửa bug production
git worktree add ../hotfix main
cd ../hotfix
# ... fix, commit, push, PR merged ...
cd ../my-project
git worktree remove ../hotfix
# Quay lại feature
git commit -m "Fix edge case in token validation"
# Sẵn sàng cho PR: dọn lịch sử
git rebase -i main
# Squash các fix commit, viết lại cho rõ ràng
# Push và mở PR
git push -u origin feature/session-refresh
# Có gì đó hỏng ở staging? Tìm commit nào:
git bisect start
git bisect bad HEAD
git bisect good main
git bisect run npm test
# Oops, tôi hard-reset nhầm
git reflog
git reset --hard HEAD@{2}Mỗi lệnh này mất vài giây. Cùng nhau, chúng tiết kiệm hàng giờ. Không phải giờ giả thuyết — giờ thực, mỗi tuần, mà nếu không tôi sẽ dành cho việc gỡ rối Git, tìm bug thủ công, hoặc mất việc do chuyển ngữ cảnh.
Git là công cụ thưởng cho chiều sâu. Những cơ bản giúp bạn qua ngày. Nhưng các lệnh trong bài viết này là thứ phân biệt "tôi dùng Git" và "Git thực sự làm tôi nhanh hơn." Học chúng từng bước. Chọn một kỹ thuật mới tuần này và dùng cho đến khi nó thành phản xạ. Rồi chọn cái khác.
Bản thân tương lai của bạn, đang nhìn bug production lúc 11 giờ đêm, sẽ cảm ơn bạn vì biết git bisect.