Bối cảnh: Tại sao git checkout lại “có vấn đề”?
Nếu bạn đã dùng Git đủ lâu, chắc chắn từng gõ git checkout vào một trong nhiều tình huống khác nhau mà không nhận ra chúng hoàn toàn khác nhau về mặt logic:
# Tình huống 1: Chuyển sang branch khác
git checkout main
# Tình huống 2: Tạo branch mới và chuyển sang
git checkout -b feature/login
# Tình huống 3: Hủy thay đổi chưa stage ở một file
git checkout -- src/auth.js
# Tình huống 4: Checkout về một commit cụ thể (detach HEAD)
git checkout a1b2c3d
Một lệnh, bốn hành vi hoàn toàn khác nhau. Tình huống 3 và 4 trông gần giống nhau về cú pháp nhưng kết quả lại hoàn toàn trái ngược. Mình đã từng thấy junior trong team gõ nhầm git checkout HEAD~1 thay vì git checkout -- . và bị detach HEAD mà không biết mình đang ở trạng thái gì.
Git 2.23 (tháng 8/2019) chính thức giới thiệu git switch và git restore để tách biệt trách nhiệm rõ ràng:
git switch— chỉ làm một việc: chuyển đổi branchgit restore— chỉ làm một việc: khôi phục file về trạng thái trước
Mỗi lệnh làm đúng một việc. Khi team có cả người mới lẫn người quen Git lâu năm, sự rõ ràng đó tiết kiệm được kha khá thời gian giải thích.
Cài đặt: Kiểm tra phiên bản Git trước
Hai lệnh này cần ít nhất Git 2.23 — ra đời tháng 8/2019. Kiểm tra trước:
git --version
# git version 2.43.0
Nếu phiên bản thấp hơn 2.23, cần cập nhật:
# Ubuntu/Debian
sudo add-apt-repository ppa:git-core/ppa
sudo apt update && sudo apt install git
# macOS (Homebrew)
brew upgrade git
# Arch Linux
sudo pacman -S git
# Windows (winget)
winget upgrade --id Git.Git
Sau khi update xong, kiểm tra lại:
git --version
# Phải >= 2.23
Cấu hình chi tiết: Dùng git switch và git restore thế nào?
1. git switch — Chuyển branch an toàn
Về cơ bản, git switch làm đúng cái phần chuyển branch của git checkout — không kèm gì thêm:
# Chuyển sang branch đã tồn tại
git switch main
git switch develop
# Tạo branch mới và chuyển sang (thay cho checkout -b)
git switch -c feature/user-auth
# Tạo branch từ một điểm cụ thể
git switch -c hotfix/payment-bug origin/main
# Chuyển về branch trước đó (giống cd -)
git switch -
Một điểm cộng lớn: nếu gõ tên branch không tồn tại, git switch báo lỗi rõ ràng thay vì làm điều gì đó không mong muốn:
git switch non-existent-branch
# fatal: invalid reference: non-existent-branch
Còn trường hợp detach HEAD — phải dùng flag --detach rõ ràng, không thể vô tình làm được nữa:
git switch --detach a1b2c3d
# HEAD is now at a1b2c3d...
2. git restore — Khôi phục file với quyền kiểm soát rõ ràng
Trong thực tế hàng ngày, git restore là lệnh mình dùng nhiều hơn — unstage nhầm file xảy ra thường xuyên hơn bạn nghĩ.
Hủy thay đổi chưa stage (working directory):
# Hủy thay đổi ở một file cụ thể
git restore src/auth.js
# Hủy tất cả thay đổi chưa stage
git restore .
Unstage file đã add nhầm:
# Unstage một file
git restore --staged src/config.js
# Unstage tất cả
git restore --staged .
Lưu ý quan trọng: --staged chỉ đưa file ra khỏi staging area. Thay đổi vẫn còn nguyên trong file — chỉ là không còn được đánh dấu để commit nữa.
Khôi phục file về một commit hoặc branch cụ thể:
# Lấy file từ branch khác (không cần checkout sang branch đó)
git restore --source=feature/new-api src/api.js
# Lấy file về trạng thái 3 commit trước
git restore --source=HEAD~3 src/database.js
# Lấy file từ một commit cụ thể
git restore --source=a1b2c3d src/utils.js
Dùng --source khi cần lấy một phiên bản file từ branch hoặc commit khác — không cần checkout sang đó, không ảnh hưởng gì đến branch hiện tại.
3. Kết hợp --staged và --worktree
Mặc định git restore chỉ tác động lên working tree. Muốn reset cả staged lẫn working tree về HEAD cùng lúc:
# Khôi phục cả staged và working tree về HEAD
git restore --staged --worktree src/auth.js
# Tương đương: git checkout HEAD -- src/auth.js (kiểu cũ)
4. So sánh nhanh: Lệnh cũ vs Lệnh mới
# === Chuyển branch ===
git checkout main # Cũ
git switch main # Mới
# === Tạo branch mới ===
git checkout -b feature/x # Cũ
git switch -c feature/x # Mới
# === Hủy thay đổi chưa stage ===
git checkout -- src/auth.js # Cũ
git restore src/auth.js # Mới
# === Unstage file ===
git reset HEAD src/config.js # Cũ
git restore --staged src/config.js # Mới
# === Lấy file từ commit khác ===
git checkout a1b2c3d -- src/utils.js # Cũ
git restore --source=a1b2c3d src/utils.js # Mới
Áp dụng vào team: chuyển đổi không gây xáo trộn
Setup alias để chuyển dần
Khi mình rollout cho team 8 người, không ai muốn thay đổi habit ngay. Cách mình dùng: setup alias để cả hai kiểu đều chạy được — nhưng doc nội bộ chỉ ghi lệnh mới:
# Thêm vào ~/.gitconfig
[alias]
sw = switch
swc = switch -c
rs = restore
rss = restore --staged
PR template và commit guide chỉ dùng lệnh mới. Không cần training hay thông báo gì thêm — khoảng 2 tháng sau, cả team đã tự chuyển mà không cần ai nhắc.
Verify sau khi restore
Restore xong, check ngay bằng git status:
# Hủy thay đổi xong, kiểm tra
git restore src/auth.js
git status
# Không còn thấy src/auth.js trong "Changes not staged for commit"
# Unstage xong, kiểm tra
git restore --staged src/config.js
git status
# src/config.js chuyển từ "Changes to be committed" sang "Changes not staged"
# Xem nội dung thực tế thay đổi chưa
git diff HEAD src/auth.js
Các trường hợp hay nhầm
Vài điểm mọi người hay hỏi khi mới chuyển sang:
- Có uncommitted changes thì không switch được — Git báo lỗi nếu thay đổi có thể bị ghi đè. Dùng
git stashtrước, hoặcgit switch --discard-changesđể bỏ luôn (nhưng cẩn thận với cái này). git restorekhông xóa được untracked files — file mới tạo chưa từng đượcgit addthì dùnggit clean -fd.- Remote tracking:
git switch -c feature origin/featuretự động setup upstream tracking.
# Chuyển branch khi muốn hủy luôn các thay đổi chưa commit
git switch --discard-changes develop
# Xóa untracked files và thư mục
git clean -fd
# Tạo branch local từ remote với tracking
git switch -c feature/api-v2 origin/feature/api-v2
Sau 2 tháng chuyển sang workflow này, số lần ai đó trong team vô tình detach HEAD hay mất thay đổi do nhầm lệnh giảm rõ rệt. Không phải vì mọi người cẩn thận hơn — cú pháp mới đơn giản là không cho phép lỗi đó xảy ra một cách vô tình. Thêm vào đó, đọc git command trong PR description hay commit message cũng rõ hơn hẳn.
git checkout vẫn còn đó và sẽ không bị xóa đi. Nhưng nếu đang bắt đầu dự án mới hoặc onboard thành viên mới, hãy dùng thẳng git switch và git restore ngay từ đầu. Ít thứ phải nhớ hơn, ít góc khuất hơn, và không bao giờ phải giải thích “detached HEAD state là gì” nữa.
