git log, diff, show và blame: cách đọc lịch sử dự án trong Git
git log liệt kê các commit và lọc theo tác giả, ngày, tệp, nội dung; git diff so sánh thư mục làm việc, index và bất kỳ commit nào; git show hiển thị đầy đủ một commit; git blame cho biết commit nào sửa dòng nào lần cuối trong tệp.
Bốn lệnh bao quát gần như mọi câu hỏi về lịch sử dự án. git log trả lời «chuyện gì đã xảy ra», git diff — «chính xác cái gì đã thay đổi», git show — «trong một commit cụ thể có gì», git blame — «ai và khi nào đã viết dòng này». Dưới đây là các cách làm việc thực tế với từng lệnh, kèm những cờ được dùng hằng ngày trong phát triển.
git log: danh sách commit
Không có tham số, git log hiển thị các commit của nhánh hiện tại từ mới đến cũ: hash SHA-1 đầy đủ, tác giả, thời gian và thông điệp. Kết quả được hiển thị qua pager (thường là less): phím cách — trang tiếp theo, /chữ — tìm kiếm, q — thoát.
commit 7be41d0c9a2e4f1b8d6c3e5a7f9b0c2d4e6f8a1b
Author: Anna Petrova <anna@example.com>
Date: Mon Oct 5 14:22:10 2026 +0300
feat: cache phản hồi API
Không cần sao chép toàn bộ hash: Git hiểu mọi tiền tố duy nhất từ 4 ký tự trở lên, thực tế chỉ cần 7.
Hiển thị gọn: --oneline
git log --oneline -10
# 7be41d0 (HEAD -> main, origin/main) feat: cache phản hồi API
# 1c02e8f fix: timeout khi gọi yêu cầu
# 9a4b3c2 refactor: tách client thành module riêng
Tham số -10 (hoặc -n 10) giới hạn số commit. Trong ngoặc là các con trỏ: HEAD, nhánh cục bộ và nhánh từ xa, thẻ.
Đồ thị nhánh: git log --graph
Yêu cầu «git graph» thường có nghĩa chính là điều này: hiển thị các nhánh rẽ và gộp ngay trong terminal.
git log --oneline --graph --all
# * 3e1f0aa (feature/auth) feat: đăng nhập bằng token
# | * 7be41d0 (HEAD -> main) feat: cache phản hồi API
# |/
# * 1c02e8f fix: timeout khi gọi yêu cầu
# * 5d2c7b1 Merge branch 'feature/search'
# |\
# | * 8f3a9e0 feat: tìm kiếm
# |/
# * 9a4b3c2 refactor: tách client thành module riêng
Cờ --all hiển thị mọi nhánh, không chỉ nhánh hiện tại. Nên tạo một alias: git config --global alias.lg "log --oneline --graph --all --decorate", sau đó chỉ cần gõ git lg.
Định dạng riêng: --pretty=format
git log --pretty=format:"%h %ad %an %s" --date=short
# 7be41d0 2026-10-05 Anna Petrova feat: cache phản hồi API
Các ký hiệu chính: %h — hash rút gọn, %H — hash đầy đủ, %an — tên tác giả, %ae — email của tác giả, %ad — ngày tác giả, %ar — thời gian tương đối («2 days ago»), %s — tiêu đề thông điệp.
Lọc lịch sử trong git log
Theo tác giả và ngày
git log --author="Anna"
git log --since="2 weeks ago" --until="2026-10-01"
git log --since=yesterday --oneline
--author nhận biểu thức chính quy và tìm theo tên lẫn email. Git hiểu ngày ở dạng tự do lẫn dạng ISO.
Theo nội dung thông điệp
git log --grep="fix" -i --oneline
-i làm cho tìm kiếm không phân biệt chữ hoa, chữ thường. Nếu nhóm bạn ghi mã công việc vào thông điệp commit, cách này giúp tìm nhanh mọi commit thuộc một công việc.
Theo tệp hoặc thư mục
git log --oneline -- src/api/client.py
git log --follow -p -- src/api/client.py
Dấu gạch ngang kép ngăn cách đường dẫn với nhánh. --follow theo dõi lịch sử của tệp qua các lần đổi tên (chỉ áp dụng cho một tệp), còn -p hiển thị bản vá của mỗi commit.
Theo đoạn code đã thay đổi: tìm «cái kìm»
git log -S "retry_count" --oneline # commit mà số lần xuất hiện của chuỗi bị thay đổi
git log -G "timeout\s*=" --oneline # commit có dòng khớp biểu thức chính quy trong diff
-S tìm commit mà một hàm hay biến được thêm vào hoặc bị xóa — rất hữu ích khi cần biết một đoạn code đã biến đi đâu.
Khoảng commit
git log main..feature # có trong feature nhưng không có trong main
git log origin/main..HEAD # commit cục bộ của tôi, chưa gửi đi
git log --no-merges # bỏ các commit gộp
git log --stat # tệp nào thay đổi và bao nhiêu dòng
git diff: cái gì đã thay đổi
Điều quan trọng nhất cần hiểu về git diff là nó so sánh hai trạng thái nào. Nhầm lẫn ở đây là nguyên nhân của câu hỏi «sao diff trống, tôi đã sửa rất nhiều mà».
| Lệnh | So sánh |
|---|---|
git diff | thư mục làm việc với index (thay đổi chưa được chuẩn bị) |
git diff --staged (--cached) | index với commit cuối (điều gì sẽ vào commit) |
git diff HEAD | thư mục làm việc với commit cuối (tất cả cùng lúc) |
git diff A B | hai commit, nhánh hoặc thẻ |
git diff A...B | thay đổi trong B kể từ khi tách khỏi A |
Sau khi chạy git add, lệnh git diff thông thường sẽ trống — các thay đổi đã nằm trong index, hãy xem --staged. Tệp mới chưa từng được add thì diff hoàn toàn không hiển thị.
Cách đọc kết quả diff
diff --git a/config.py b/config.py
index 3b18e51..a1c4f02 100644
--- a/config.py
+++ b/config.py
@@ -10,7 +10,7 @@ DEFAULTS = {
"host": "localhost",
- "timeout": 5,
+ "timeout": 30,
"retries": 3,
Dòng bắt đầu bằng - là dòng đã bị xóa, + là dòng được thêm, còn lại là ngữ cảnh. Tiêu đề @@ -10,7 +10,7 @@ nghĩa là đoạn này bắt đầu ở dòng 10 và chiếm 7 dòng trong cả phiên bản cũ lẫn mới.
Các cờ hữu ích của git diff
git diff --stat main feature # tóm tắt theo tệp
git diff --name-only HEAD~3 # chỉ tên các tệp đã thay đổi
git diff --word-diff # thay đổi theo từng từ, tiện cho văn bản
git diff -w # bỏ qua thay đổi về khoảng trắng
git diff main feature -- src/ # giới hạn trong một thư mục
Trước mỗi commit nên xem git diff --staged: đây là cơ hội cuối cùng để phát hiện một câu lệnh print gỡ lỗi hay một tệp thêm nhầm.
git show: toàn bộ một commit
git show hiển thị siêu dữ liệu của commit và diff so với cha của nó. Không có tham số — commit cuối cùng.
git show 7be41d0
git show HEAD~2 --stat # không có bản vá, chỉ tóm tắt
git show v1.2.0 # thẻ: chú thích và commit
git show main:src/config.py # tệp đúng như trong commit
Dạng cuối <revision>:<đường-dẫn> đặc biệt hữu ích: bạn có thể xem phiên bản cũ của tệp mà không cần chuyển sang nó. Chuyển hướng kết quả vào tệp là có thể khôi phục nội dung đã xóa mà không cần checkout.
git blame: ai đã sửa dòng này
git blame tệp hiển thị cho từng dòng commit cuối cùng sửa nó, tác giả và thời gian. Tên lệnh nghe như đổ lỗi, nhưng thực chất đây là công cụ tìm ngữ cảnh: tìm commit, đọc thông điệp và hiểu vì sao lại viết như vậy.
git blame -L 40,60 src/api/client.py
# 1c02e8f3 (Anna Petrova 2026-09-28 11:04:51 +0300 41) timeout = settings.timeout
# 9a4b3c2e (Ivan Smirnov 2026-09-14 18:30:02 +0300 42) session = build_session()
-L 40,60— chỉ một khoảng dòng; cũng có thể dùng-L :tên_hàm.-w— bỏ qua thay đổi khoảng trắng, để không rơi vào commit định dạng lại code.-C— theo dõi các dòng được chuyển từ tệp khác.--ignore-rev <hash>hoặc tệp.git-blame-ignore-revs— bỏ qua các commit hàng loạt như tự động định dạng.
Kịch bản điều tra điển hình: git blame tìm ra commit, git show hiển thị toàn bộ commit đó, còn git log theo tệp cho biết các thay đổi lân cận.
Di chuyển trong quá khứ: HEAD~ và HEAD^
Commit có thể được tham chiếu không chỉ bằng hash. HEAD~1 — cha của commit hiện tại, HEAD~3 — ông của ba đời. HEAD^2 — cha thứ hai của commit gộp. Tên nhánh, thẻ, nhánh từ xa cũng dùng được: origin/main~2. Để xem toàn bộ dự án tại một commit cũ, dùng git switch --detach <hash>, quay lại bằng git switch -.
Lịch sử tốt và lịch sử xấu
Tất cả các lệnh này chỉ hữu ích khi lịch sử có ý nghĩa. Thông điệp kiểu «fix», «wip», «sửa thêm» khiến git log --grep và git blame trở nên vô dụng. Một commit tốt là một thay đổi logic với tiêu đề giải thích điều gì và vì sao: fix: tăng timeout API lên 30 giây do cổng trung gian chậm. Sau nửa năm, chính commit như vậy sẽ được blame tìm thấy và tiết kiệm cho bạn một giờ.
Thực hành: điều tra lỗi qua lịch sử
Giả sử sau một bản phát hành, các yêu cầu API bắt đầu bị timeout. Đây là cách phối hợp các lệnh để tìm nguyên nhân trong vài phút.
- Xem những gì đã vào bản phát hành:
git log v1.4.0..v1.5.0 --oneline. Danh sách ngắn, nhưng theo tiêu đề thì thủ phạm chưa rõ. - Thu hẹp theo tệp:
git log v1.4.0..v1.5.0 --oneline -- src/api/. Còn lại hai commit. - Tìm trong code:
git log -S "timeout" --oneline -- src/api/tìm ra commit đã thay đổi giá trị timeout. - Xem trọn commit:
git show 1c02e8f. Trong diff thấy timeout bị giảm từ 30 xuống 5 giây, còn thông điệp giải thích là «tăng tốc phản hồi UI». - Kiểm tra ai đã chạm vào dòng này:
git blame -L :build_session src/api/client.py.
Sau đó là việc kỹ thuật: khôi phục giá trị, đưa timeout vào cấu hình, hoặc hoàn tác commit bằng git revert. Nếu có hàng trăm commit và không thể tìm thủ phạm bằng mắt, git bisect sẽ giúp — đó là tìm kiếm nhị phân trong lịch sử, chỉ sau vài bước sẽ chỉ ra commit đã làm thay đổi hành vi.
Cấu hình giúp việc dễ hơn
git config --global log.date iso # định dạng ngày trong log
git config --global diff.colorMoved zebra # làm nổi bật dòng được di chuyển trong diff
git config --global core.pager "less -FRX" # không mở pager cho kết quả ngắn
Nếu kết quả cần xử lý bằng script, hãy tắt màu và pager: git --no-pager log --format="%H %s" --no-color. Khi xử lý bằng máy, dùng hash đầy đủ %H: tiền tố ngắn có thể trở nên mơ hồ trong kho lớn theo thời gian.
Tóm lại
git log với các bộ lọc --oneline, --graph, --author, -S và đường dẫn trả lời câu hỏi «chuyện gì đã xảy ra». git diff so sánh thư mục làm việc, index và các commit — điều quan trọng là nhớ nó so sánh cái gì mặc định. git show mở ra một commit hoặc phiên bản cũ của tệp, còn git blame dẫn bạn từ dòng code tới quyết định đã tạo ra nó. Đọc lịch sử và điều hướng giữa các commit được hướng dẫn thực hành trong khóa học Git.
Câu hỏi thường gặp
Làm sao thoát khỏi git log?
Nhấn q — đầu ra được mở trong pager less; để in mà không dùng pager, dùng git --no-pager log hoặc cờ -n với số nhỏ.
Làm sao xem lịch sử của một tệp đã bị xóa?
Chạy git log --oneline -- đường/dẫn/tệp (có dấu gạch ngang kép), còn nội dung trước khi xóa xem bằng git show <hash>^:đường/dẫn/tệp.
Làm sao so sánh nhánh cục bộ với nhánh từ xa?
Trước tiên git fetch, sau đó git diff origin/main hoặc git log origin/main..HEAD để xem các commit chưa gửi của bạn.
Có công cụ xem lịch sử bằng đồ họa không?
Có, Git đi kèm gitk, còn đồ thị lịch sử cũng có sẵn trong VS Code, các IDE JetBrains và giao diện web của GitHub và GitLab.
Vì sao git diff không hiển thị gì?
Rất có thể các thay đổi đã được add vào index bằng git add — hãy xem git diff --staged; còn tệp mới chưa theo dõi thì git diff không hiển thị.
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