Bạn muốn bỏ hiệu lực của một commit bật tính năng, nhưng sau commit ấy nhóm đã cập nhật tài liệu. Quay cả cây file về snapshot cũ sẽ không thể hiện đúng yêu cầu giữ phần tài liệu mới. Git revert cho phép ghi một commit mới đảo tác động của commit được chọn, còn lịch sử trước đó vẫn có thể được xem lại.
Bài kiểm tra bằng Git 2.54.0, đối chiếu tài liệu ngày 09/11/2026. Toàn bộ ví dụ chạy trong repository tạm có dữ liệu giả, không thao tác checkout hiện tại, không fetch hoặc push và không sửa branch trên remote. Ví dụ chỉ dùng commit thường, không dùng merge commit.
Dựng ba commit có mục đích tách biệt
Chạy khối sau trong Bash. Các lệnh Git đều chỉ rõ repository tạm bằng -C:
set -eu
demo_repo=$(mktemp -d /tmp/aedev-revert-demo.XXXXXX)
git init -q --template= "$demo_repo"
git -C "$demo_repo" config user.name 'AeDev Demo'
git -C "$demo_repo" config user.email '[email protected]'
git -C "$demo_repo" config commit.gpgsign false
git -C "$demo_repo" config core.hooksPath /dev/null
printf 'feature=off\n' > "$demo_repo/config.txt"
printf 'initial documentation\n' > "$demo_repo/README.md"
git -C "$demo_repo" add config.txt README.md
git -C "$demo_repo" commit -qm 'Create baseline'
printf 'feature=on\n' > "$demo_repo/config.txt"
git -C "$demo_repo" add config.txt
git -C "$demo_repo" commit -qm 'Enable feature'
feature_commit=$(git -C "$demo_repo" rev-parse HEAD)
printf 'updated documentation\n' > "$demo_repo/README.md"
git -C "$demo_repo" add README.md
git -C "$demo_repo" commit -qm 'Update documentation'
printf 'Demo repository: %s\n' "$demo_repo"
git -C "$demo_repo" log --oneline -3
Commit thứ hai chỉ sửa config; commit thứ ba chỉ sửa README. Việc tách file cố ý giúp quan sát tác dụng của revert mà không thêm bài toán giải conflict. Biến feature_commit giữ chính xác ID của commit cần đảo thay vì dựa vào vị trí HEAD sẽ thay đổi khi có commit mới.
Đảo đúng commit đã chọn
Chạy tiếp trong cùng shell, với hai biến vừa tạo vẫn còn:
before_count=$(git -C "$demo_repo" rev-list --count HEAD)
git -C "$demo_repo" revert --no-edit "$feature_commit"
after_count=$(git -C "$demo_repo" rev-list --count HEAD)
test "$after_count" -eq "$((before_count + 1))"
test "$(cat "$demo_repo/config.txt")" = 'feature=off'
test "$(cat "$demo_repo/README.md")" = 'updated documentation'
test -z "$(git -C "$demo_repo" status --porcelain)"
git -C "$demo_repo" log --oneline -4
Các kiểm tra đã đạt: số commit tăng từ 3 lên 4, feature trở về off, README vẫn chứa nội dung cập nhật và working tree sạch. Commit Enable feature vẫn nằm trong lịch sử. Hash cụ thể sẽ khác ở lần chạy của bạn, nên kiểm tra theo quan hệ và nội dung thay vì sao chép một hash từ ví dụ.
Theo git-revert, lệnh ghi commit mới để đảo tác động của thay đổi cũ. Tùy chọn no-edit tránh mở trình soạn thảo message trong fixture. Nó không làm lệnh trở thành một phép xem trước; nếu áp dụng thành công, commit mới đã được tạo.
Vì sao kết quả không giống lấy lại toàn bộ snapshot cũ?
Yêu cầu ở đây là đảo phần thay đổi bật tính năng, trong khi vẫn giữ cập nhật tài liệu độc lập. Revert làm việc trên trạng thái hiện tại để áp dụng phần đảo ấy. Nó không có nghĩa mọi file phải giống trước commit mục tiêu, vì các commit khác có thể mang thay đổi cần giữ.
Điều này cũng có nghĩa không nên chỉ nhìn số commit mới rồi cho rằng chức năng sản phẩm đã đúng. Sau khi đảo một commit thực tế, chạy kiểm tra liên quan và đọc diff của commit revert. Một tính năng có thể phụ thuộc thay đổi ở các commit khác mà fixture nhỏ không mô tả.
Khi các thay đổi đến sau đụng cùng một vùng
Ví dụ dùng hai file riêng nên không phát sinh conflict. Nếu commit sau cũng sửa config, Git có thể không áp dụng được phần đảo tự động. Khi đó cần đọc trạng thái, giải quyết nội dung theo ý định hiện tại rồi tiếp tục bằng quy trình được tài liệu hướng dẫn; nếu quyết định dừng lượt revert đang conflict, dùng đường abort tương ứng.
Bài chưa kiểm thử conflict, abort, nhiều commit hoặc merge mainline. Đặc biệt không áp dụng máy móc ví dụ commit thường vào merge commit, vì việc chọn parent cần được hiểu riêng. Trước khi dùng trong repository thật, xác nhận working tree sạch và commit mục tiêu đúng với vấn đề cần xử lý.
Hãy thực hành lại fixture rồi đọc cả log lẫn hai file. Khi bạn giải thích được vì sao lịch sử có bốn commit nhưng README vẫn mới, bạn đã phân biệt được “đảo một thay đổi” với “quay mọi thứ về một thời điểm”.


