Git cherry-pick

Xem thử

Vấn đề cần giải quyết

Một bản sửa lỗi thanh toán vừa được merge vào main. Khách hàng đang dùng bản release/1.0, và cần đúng bản sửa đó, không cần các tính năng mới khác trên main. Merge cả main sang release thì kéo theo mọi thứ. git cherry-pick sao chép một hoặc vài commit cụ thể sang nhánh hiện tại, đúng thứ bạn cần.

Sau bài này, bạn có thể

  • Cherry-pick một hoặc nhiều commit và hiểu commit mới khác commit gốc ở đâu.
  • Đọc đúng cú pháp dải a..b và a^..b.
  • Xử lý xung đột bằng --continue, --abort và --skip.

Tiên quyết: bài Git merge và Git Workflow.

Dùng cơ bản

bash
git switch release/1.0
git cherry-pick a1b2c3d

Git tạo một commit mới trên nhánh hiện tại với cùng nội dung thay đổi nhưng hash khác.

Cách đọc hình. Nửa trên: main có A, B, C; feature có A, B, D, E, tách từ B. Bạn đang ở main và chạy git cherry-pick D. Nửa dưới: main có thêm D' trên C, cùng thay đổi với D nhưng hash khác. Nhánh feature không đổi. Cherry-pick sao chép thay đổi thành commit mới, không di chuyển commit gốc.

Thử và quan sát

Hãy dự đoán: bạn cherry-pick bản sửa lỗi M2 từ main sang nhánh release. Commit trên release có cùng hash với M2 không? Chạy mô phỏng, rồi bấm biến thể gặp xung đột.

Đang tải Animation…

Bảng bước (bản đọc tĩnh của mô phỏng)

BướcLệnhĐiều xảy ra
1git switch release/1.0Chuyển sang nhánh đích. cherry-pick luôn áp commit lên nhánh hiện tại.
2git cherry-pick M2Git áp thay đổi của M2 thành một commit mới M2' trên release. M2 vẫn ở main và M1 không bị mang theo.
3git log --oneline -1 release/1.0M2 và M2' mang cùng thông điệp nhưng hash khác nhau, nên đây là hai commit riêng biệt.

Biến thể: Cherry-pick gặp xung đột

BướcLệnhĐiều xảy ra
1git cherry-pick M2M2 sửa cùng vùng với R1 nên Git dừng giữa chừng và đánh dấu xung đột. Chưa có commit nào được tạo.
2git add pay.jsSau khi sửa tệp xung đột, đánh dấu đã giải quyết bằng git add.
3git cherry-pick --continueHoàn tất commit M2'. Nếu muốn hủy cả quá trình, dùng --abort; nếu chỉ bỏ commit này và đi tiếp, dùng --skip.

Giải đáp. Không. Commit mới M2' có cùng thay đổi và thông điệp nhưng hash khác, vì cha của nó khác (R1 thay vì M1). M1 không bị mang theo. Khi có xung đột, Git dừng giữa chừng; bạn sửa tệp, git add, rồi git cherry-pick --continue.

Cherry-pick nhiều commit

bash
git cherry-pick a1b2c3d f4e5d6c
git cherry-pick a1b2c3d..f4e5d6c
git cherry-pick a1b2c3d^..f4e5d6c

Cách đọc hình. Chuỗi commit C1, C2 (a), C3, C4 (b). a..b chọn các commit sau a, tức C3 và C4. a^..b chọn từ chính a, tức C2, C3 và C4. Hãy thêm ^ sau a nếu bạn muốn lấy cả commit đầu.

Các tùy chọn hữu ích

bash
git cherry-pick -x <hash>
git cherry-pick -n <hash>
git cherry-pick -e <hash>

-x ghi dòng "cherry picked from" vào thông điệp, -n áp thay đổi nhưng chưa commit để gộp, -e cho sửa thông điệp trước khi commit.

Khi gặp xung đột

bash
git add <file>
git cherry-pick --continue
git cherry-pick --abort
git cherry-pick --skip

Sau khi sửa tệp xung đột và git add, --continue hoàn tất commit. --abort hủy cả quá trình và quay lại trạng thái trước. --skip chỉ bỏ commit đang xử lý và đi tiếp.

Tình huống thực tế

  • Hotfix: đưa một bản vá từ main sang nhánh release cũ.
  • Cứu commit: lấy một commit tốt từ nhánh sắp bỏ.
  • Chọn lọc: đưa đúng một tính năng sang nhánh khác.

Lạm dụng cherry-pick dễ tạo commit trùng lặp trên nhiều nhánh, gây rối lịch sử. Hãy dùng có chủ đích, không thay cho merge hay rebase thông thường.

Hiểu lầm thường gặp

  • "Cherry-pick di chuyển commit." Nó sao chép; commit gốc vẫn ở nhánh cũ.
  • "a..b gồm cả a." Phải viết a^..b để gồm a.
  • "--abort và --skip như nhau." --abort hủy tất cả, --skip chỉ bỏ một commit.

Tự kiểm tra

  1. Commit tạo bởi cherry-pick có hash giống commit gốc không?
  2. a..b chọn những commit nào, và a^..b thì sao?
  3. Cherry-pick bị xung đột, bạn có những lối ra nào?

Đáp án gợi ý. (1) Không, vì cha khác nên hash khác. (2) a..b bắt đầu sau a; a^..b gồm cả a. (3) Sửa rồi --continue, hủy bằng --abort, hoặc bỏ commit đó bằng --skip.

Điều cần nhớ

  • Cherry-pick sao chép thay đổi thành commit mới, không di chuyển commit gốc.
  • Thêm ^ sau a nếu muốn lấy cả commit đầu của dải.
  • Có ba lối ra khi xung đột: --continue, --abort, --skip.
  • Bài sau: gitk và git show.

Nguồn tham khảo

Pro Git, chương 5.3 "Maintaining a Project" (git-scm.com/book); tài liệu git cherry-pick.