git push

Xem thử

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

Bạn đã commit xong và gõ git push nhưng nhận về ! [rejected] main -> main (fetch first). Bạn lo mình làm hỏng gì. Thực ra Git đang bảo vệ công việc của đồng đội: máy chủ có commit mà bạn chưa có, và nếu nhận commit của bạn nó sẽ phải bỏ commit đó. Hiểu cơ chế này giúp bạn xử lý bình tĩnh và đúng cách.

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

  • Dùng git push và -u để đẩy nhánh lên máy chủ và thiết lập upstream.
  • Giải thích vì sao push bị từ chối và xử lý an toàn bằng pull --rebase.
  • Biết khi nào có thể dùng --force-with-lease và khi nào tuyệt đối không.

Tiên quyết: bài git fetch và git rebase.

Dùng cơ bản

bash
git push origin main
git push origin feature-login

Push thành công khi máy chủ chỉ cần "đi tiếp" từ vị trí hiện tại tới commit của bạn, gọi là fast-forward.

Thử và quan sát

Hãy dự đoán: bạn có commit C4, đồng đội vừa push C5 lên cùng nhánh. Khi bạn git push, điều gì xảy ra, và cách xử lý an toàn là gì? Chạy mô phỏng, rồi thử biến thể đẩy một nhánh mới với -u.

Đ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 push origin mainRemote đã có C5 mà bạn chưa có. Nếu nhận C4, nó sẽ phải bỏ C5, nên Git từ chối để bảo vệ công việc của đồng đội.
2git pull --rebase origin mainLấy C5 về rồi áp lại C4 của bạn lên trên thành C4' với hash mới. Lịch sử giờ là một đường thẳng.
3git push origin mainRemote chỉ cần đi tiếp từ C5 tới C4' nên push được chấp nhận, là fast-forward.

Biến thể: Đẩy nhánh mới với -u

BướcLệnhĐiều xảy ra
1git switch -c feature-login && git commit -m "Form đăng nhập"Tạo nhánh mới và commit F1 trên máy bạn. Remote chưa biết nhánh này.
2git push -u origin feature-loginPush tạo nhánh trên remote. Cờ -u ghi lại upstream: nhánh cục bộ theo dõi origin/feature-login.
3git commit -m "Thêm nút gửi"Commit tiếp F2 ở máy bạn.
4git pushNhờ upstream đã ghi, không cần chỉ rõ đích nữa. Git biết đẩy lên origin/feature-login.

Giải đáp. Push bị từ chối vì hai lịch sử rẽ nhánh từ C3: máy chủ không thể đi từ C5 tới C4 mà không bỏ C5. Cách an toàn là git pull --rebase origin main, xếp commit của bạn lên trên C5 thành C4' rồi git push, lúc này là fast-forward. Ở biến thể, cờ -u ghi lại upstream để các lần sau chỉ cần git push.

bash
git push -u origin feature-login
git push

Cờ -u (--set-upstream) chỉ cần dùng một lần cho mỗi nhánh. Với tag:

bash
git push origin v1.0.0
git push origin --tags

Khi bị từ chối

text
! [rejected]  main -> main (fetch first)

Bị từ chối là Git bảo vệ công việc của người khác, không phải lỗi. Xử lý:

bash
git pull --rebase origin main
git push origin main

Nguyên tắc: đồng bộ trước rồi push, đừng force push.

Force push: cẩn trọng

bash
git push --force-with-lease origin feature

Force push ghi đè nhánh trên máy chủ bằng lịch sử cục bộ. --force-with-lease an toàn hơn --force vì nó kiểm tra máy chủ còn đúng như lần fetch cuối của bạn trước khi ghi đè.

Đang tải sơ đồ…

Cách đọc hình. Git hỏi: nhánh trên máy chủ có còn đúng như origin/feature bạn thấy ở lần fetch cuối không? Nếu đúng thì không ai push thêm, nên Git ghi đè bằng lịch sử của bạn. Nếu không thì có người đã push, và Git từ chối để bạn fetch và xem lại.

Không bao giờ force push lên nhánh chung như main. Chỉ push lên nhánh mình sở hữu; với nhánh chung, luôn pull hoặc rebase trước khi push.

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

  • "Push bị từ chối nghĩa là tôi làm sai." Nó chỉ nghĩa là máy chủ đã đi trước. Hãy đồng bộ rồi push lại.
  • "Dùng --force cho nhanh." Nó có thể xóa commit của đồng đội; ít nhất hãy dùng --force-with-lease.
  • "Push cũng đẩy tag." Không, tag phải được push riêng hoặc dùng --tags.

Tự kiểm tra

  1. Vì sao Git từ chối push khi máy chủ có commit bạn chưa có?
  2. Cờ -u làm gì và cần dùng mấy lần cho mỗi nhánh?
  3. --force-with-lease an toàn hơn --force ở điểm nào?

Đáp án gợi ý. (1) Nhận commit của bạn sẽ buộc bỏ commit của người khác. (2) Ghi lại upstream; chỉ một lần cho mỗi nhánh. (3) Nó từ chối ghi đè nếu máy chủ đã khác với lần fetch cuối của bạn.

Điều cần nhớ

  • Push thành công khi máy chủ chỉ cần fast-forward.
  • Bị từ chối: git pull --rebase rồi push lại.
  • -u ghi upstream một lần; tag phải push riêng.
  • Không force push lên nhánh chung; nếu buộc phải force, dùng --force-with-lease.
  • Bài sau: git pull.

Nguồn tham khảo

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