git checkout / git switch

Xem thử

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

Có hai lệnh mà bài hướng dẫn cũ dùng lẫn lộn: git checkout để chuyển nhánh, và cũng git checkout để bỏ thay đổi trong một tệp. Một lệnh làm hai việc khác hẳn nhau nên rất dễ gõ nhầm và mất công việc. Từ Git 2.23, hai việc được tách thành git switch và git restore.

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

  • Chuyển và tạo nhánh bằng git switch (và nhận ra git checkout cũ).
  • Giải thích chuyển nhánh làm gì với HEAD, Staging Area và Working Directory.
  • Hiểu detached HEAD và cách giữ lại commit tạo ở đó.

Tiên quyết: bài Tổng quan git branch.

Chuyển nhánh

bash
git checkout feature-login
git switch feature-login

Dòng đầu là cách cũ; dòng sau là cách mới, khuyến nghị vì rõ ràng và an toàn hơn.

Thử và quan sát

Hãy dự đoán: hai nhánh có app.js khác nhau. Khi bạn git switch sang nhánh kia, nội dung app.js trên đĩa có đổi không? Chạy mô phỏng, rồi thử biến thể xem một commit cũ.

Đ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 feature-loginHEAD chuyển sang feature-login. Git thay Working Directory và Staging Area bằng snapshot của F1, nên app.js đổi sang v2.
2git switch mainQuay lại main: app.js trở về v1. Không có commit nào được tạo hay thay đổi trong cả hai lần chuyển.
3git switch -c hotfix v1.0.0Cờ -c tạo nhánh mới ngay từ tag v1.0.0 rồi chuyển sang đó. Nội dung quay về snapshot của bản phát hành.

Biến thể: Xem commit cũ (detached HEAD)

BướcLệnhĐiều xảy ra
1git checkout 5b1a0e2Chuyển thẳng tới một commit thay vì một nhánh. HEAD tách khỏi mọi nhánh: đây là detached HEAD.
2git commit -m "Thử nghiệm"Commit mới X được tạo và HEAD đi theo nó, nhưng không nhánh nào trỏ tới X.
3git switch -c thu-nghiemTạo nhánh ngay khi đang ở đó để giữ X. Nếu chuyển đi mà chưa tạo nhánh, X sẽ không còn tên nào trỏ tới.

Giải đáp. Có. Chuyển nhánh dời HEAD sang nhánh đích và cập nhật Working Directory cùng Staging Area khớp snapshot của nhánh đó, nên app.js đổi theo. Không có commit nào được tạo hay thay đổi. Cờ -c tạo nhánh mới ngay và có thể chỉ rõ điểm xuất phát.

bash
git switch -c feature-new
git switch -c hotfix v1.0.0
git switch -c fix a1b2c3d

Dòng đầu tạo nhánh từ vị trí hiện tại, dòng hai từ tag, dòng ba từ một commit. Cách cũ tương đương là git checkout -b feature-new.

Vì sao Git tách checkout thành hai lệnh

checkout bị "quá tải": nó vừa chuyển nhánh vừa khôi phục tệp. Nay hai việc tách riêng:

Việc cần làmLệnh hiện đạiLệnh cũ
Chuyển nhánhgit switch <nhánh>git checkout <nhánh>
Tạo và chuyển nhánhgit switch -c <tên>git checkout -b <tên>
Bỏ thay đổi ở một tệpgit restore <tệp>git checkout -- <tệp>
Lấy tệp từ commit khácgit restore --source=HEAD~2 <tệp>git checkout HEAD~2 -- <tệp>

Nhớ switch là cho nhánh, restore là cho tệp và bạn ít bị nhầm hơn.

Detached HEAD

bash
git checkout a1b2c3d

Chuyển thẳng tới một commit thay vì một nhánh đưa bạn vào trạng thái detached HEAD: HEAD trỏ thẳng vào commit. Bạn xem được và thậm chí commit được, nhưng commit mới không thuộc nhánh nào. Biến thể ở mô phỏng cho thấy cách giữ lại: tạo nhánh ngay bằng git switch -c ten-nhanh. Nếu chuyển đi mà chưa tạo nhánh, không còn tên nào trỏ tới commit đó, tuy vẫn cứu được trong một thời gian qua reflog.

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

  • "Chuyển nhánh là sao chép mã." Chỉ HEAD dời và tệp được cập nhật theo snapshot.
  • "Detached HEAD là lỗi." Nó là trạng thái hợp lệ để xem lịch sử, chỉ nguy hiểm nếu bạn commit mà không tạo nhánh.
  • "Chuyển nhánh luôn giữ được thay đổi chưa commit." Nếu tệp khác nhau giữa hai nhánh, Git từ chối; bạn cần commit hoặc stash trước.

Tự kiểm tra

  1. Chuyển nhánh làm thay đổi những vùng nào của Git?
  2. Lệnh nào tạo nhánh mới từ tag v1.0.0 và chuyển sang đó?
  3. Bạn đang ở detached HEAD và vừa commit. Làm sao giữ commit đó?

Đáp án gợi ý. (1) HEAD, Staging Area và Working Directory; lịch sử không đổi. (2) git switch -c hotfix v1.0.0. (3) git switch -c ten-nhanh ngay khi đang ở đó.

Điều cần nhớ

  • Switch dời HEAD và cập nhật tệp, không đụng lịch sử.
  • switch cho nhánh, restore cho tệp.
  • Detached HEAD: tạo nhánh để giữ commit mới.
  • Bài sau: git merge, gộp công việc giữa các nhánh.

Nguồn tham khảo

Tài liệu git-switch và git-restore (git-scm.com/docs); Pro Git, chương 3.1 (git-scm.com/book).