
Lỡ tạo nhánh Git từ nhánh khác thay vì main — cách xử lý sạch
Lỡ git checkout -b khi đang đứng trên nhánh khác khiến nhánh mới cõng code chưa merge. Đây là cách phát hiện và dọn sạch nhánh Git sai gốc trước khi mở PR.
Bạn bắt đầu một task mới, gõ git checkout -b feature/ODOO-375, code ngon lành rồi mở PR vào main. Nhưng reviewer nhắn: "Sao PR này có cả code của ticket khác vậy?". Đây là lỗi tạo nhánh Git sai gốc — một trong những cái bẫy phổ biến nhất khi làm việc nhiều nhánh song song, và may là dọn rất gọn nếu bắt đúng bệnh.
Bài này ghi lại đúng tình huống vừa xảy ra với nhánh ODOO-375, kèm hai cách xử lý và bảng so sánh để bạn chọn cho hợp hoàn cảnh.
Vì sao nhánh lại "cõng" code của task khác?
Trong Git, git checkout -b <tên-nhánh> tạo nhánh mới từ vị trí bạn đang đứng, chứ không phải luôn từ main.
Đáng lẽ tách nhánh từ main, nhưng nếu lúc đó bạn đang đứng trên một nhánh feature khác chưa merge (nhánh của đồng nghiệp, hoặc task trước của chính bạn), thì nhánh mới sẽ thừa kế toàn bộ commit của nhánh kia.
Hậu quả khi mở PR vào main:
PR chứa lẫn code của một ticket không liên quan → reviewer thấy 2 task trộn vào nhau, khó review.
Nếu PR được merge, bạn vô tình kéo luôn code chưa duyệt của task kia vào
main.
Bắt sớm trước khi tạo PR là rẻ nhất. Dưới đây là cách phát hiện.
Cách phát hiện nhánh tách sai gốc
Chạy đúng một lệnh — nó liệt kê chính xác những commit mà PR sẽ mang vào main:
git log --oneline main..HEAD
Cách đọc: main..HEAD nghĩa là "những commit có trên nhánh hiện tại nhưng chưa có trên main". Nếu bạn thấy có commit của ticket khác (không phải task đang làm) nằm bên dưới các commit của mình → nhánh đã tách sai gốc.
Xác định "mốc cắt": đó là commit cuối cùng của nhánh kia, tức ngay trước commit đầu tiên của bạn. Ghi lại hash này — bạn sẽ cần nó cho Cách 1.
Muốn tập tay các lệnh rebase/cherry-pick bên dưới mà không sợ hỏng repo thật? Thử Git Playground — mô phỏng commit, nhánh và merge ngay trên trình duyệt.
Cách 1 — Rebase dọn tại chỗ (giữ nguyên tên nhánh)
Phù hợp khi bạn đã push nhánh / đã tạo PR và muốn giữ nguyên PR đó, chỉ dọn cho sạch.
git branch backup/<tên-nhánh> # backup an toàn, chạy TRƯỚC khi rebase
git rebase --onto main <mốc-cắt> # nhấc commit của mình đặt lên main
git log --oneline main..HEAD # KIỂM TRA: chỉ còn commit của task mình
git push --force-with-lease origin <tên-nhánh>
Giải thích từng bước:
git rebase --onto main <mốc-cắt>
Hãy tưởng tượng nhánh của bạn đang là một chồng commit xếp lên nhau: dưới cùng là code của nhánh kia, trên là code của bạn. Lệnh này làm đúng một việc: nhấc riêng phần code của bạn (mọi commit nằm sau<mốc-cắt>— chính là commit id từ điểm muốn cắt) ra, rồi đặt thẳng lênmain. Phần code của nhánh kia bị bỏ lại phía dưới, không đi theo nữa. Kết quả: nhánh của bạn giờ mọc thẳng từmain, chỉ còn đúng code của mình.
Trước: main → [code nhánh kia] → [code của bạn]
Sau: main → [code của bạn] (code nhánh kia đã bị bỏ lại)
git push --force-with-lease
Rebase đã "chép lại" các commit của bạn thành phiên bản mới → chúng mang mã hash mới, khác với bản đang nằm trên server. Vì lịch sử đã khác nhau, nếu push kiểu thường Git sẽ từ chối. Nên phải push đè lên bản cũ.
Nhưng thay vì --force (đè bất chấp), ta dùng --force-with-lease — bản "force có phanh": nó chỉ đè khi trên server đúng là bản bạn thấy lần cuối. Nếu có đồng nghiệp vừa push thêm gì đó lên nhánh chung, lệnh sẽ dừng lại và báo thay vì xóa mất công của họ.
git reset --hard backup/<tên-nhánh>(chỉ dùng khi có sự cố)
Nếu sau khi rebase mà bước kiểm tra ra kết quả lạ (thiếu commit, lộn xộn...), lệnh này đưa nhánh của bạn quay về y hệt lúc trước khi rebase — nhờ bản sao lưubackup/bạn đã tạo ở bước đầu. Coi như nút "hoàn tác", làm lại từ đầu.git branch -D backup/<tên-nhánh>(dọn dẹp)
Khi mọi thứ đã ổn, bản sao lưu không cần nữa. Lệnh này xóa nhánh backup cho gọn, tránh sau này nhìn danh sách nhánh bị rối.
Cách 2 — Nhánh mới từ main + cherry-pick (không cần force)
Phù hợp khi bạn chưa push, hoặc muốn nhánh sạch tinh và ngại force-push.
git checkout main
git pull # cập nhật main mới nhất
git checkout -b <tên-nhánh-mới> # nhánh mới, tách ĐÚNG từ main
git cherry-pick <nhiều commit id vd: f6a1b2c 9e8d7c6 1a2b3c4 > # nhặt từng commit của mình, đắp qua nhánh mới
git log --oneline main..HEAD # KIỂM TRA: chỉ còn commit của task mình
git push origin <tên-nhánh-mới> # push thường, KHÔNG cần force
Giải thích:
git cherry-pick <hash>: lấy đúng thay đổi của commit đó đắp sang nhánh hiện tại. Cherry-pick theo thứ tự cũ → mới để lịch sử đúng chiều.
vd:git cherry-pick f6a1b2c 9e8d7c6 1a2b3c4nhặt commit
f6a1b2c, rồi commit9e8d7c6, rồi commit1a2b3c4Push thường vì đây là nhánh hoàn toàn mới, không viết lại lịch sử của nhánh nào cả.
Sau khi PR mới chạy ổn, xoá nhánh cũ (nhánh còn lẫn code kia) để khỏi nhầm:
git push origin --delete <tên-nhánh-cũ>
git branch -D <tên-nhánh-cũ>
Chọn cách nào?
Cách 1 — Rebase tại chỗ | Cách 2 — Nhánh mới + cherry-pick | |
|---|---|---|
Giữ tên nhánh / PR cũ | ✅ Có | ❌ Tạo nhánh + PR mới |
Force-push | Cần ( | Không cần |
Nhánh cũ | Được dọn sạch luôn | Còn lại, phải xoá tay |
Rủi ro | Cao hơn chút (viết lại lịch sử) | Thấp, an toàn hơn |
Khi nào dùng | Nhánh đã push / đã mở PR và muốn giữ nguyên PR; hoặc chỉ mình bạn làm trên nhánh | Chưa push, muốn nhánh sạch tinh, hoặc nhánh có nhiều người cùng làm (né force-push đè commit người khác) |
Phòng bệnh cho lần sau
Luôn về
maintrước khi tách nhánh:git checkout main && git pullrồi mớigit checkout -b.Kiểm tra nhanh bằng prompt: cấu hình shell hiển thị tên nhánh hiện tại để không "tách nhầm trong vô thức".
Quen tay
git log --oneline main..HEADngay trước khi mở PR — 2 giây để chắc PR chỉ chứa đúng code của mình.
Kết luận
Tạo nhánh Git sai gốc nghe đáng sợ nhưng thực ra rất dễ dọn: một lệnh git log --oneline main..HEAD để phát hiện, rồi chọn rebase --onto (giữ PR) hoặc cherry-pick sang nhánh mới (an toàn hơn). Luôn tạo backup/ trước khi rebase, và kiểm tra lại trước khi force-push.
Bước tiếp theo: tập tay hai lệnh này trong Git Playground trước khi làm trên repo thật, để lúc "có biến" là xử lý gọn ngay.