Git push bị rejected: cách sửa non-fast-forward

Nội dung

Bài viết giải thích vì sao git push bị rejected, cách đọc lỗi non-fast-forward và quy trình xử lý an toàn trước khi push lại.

Bạn vừa gặp lỗi git push bị rejected với thông báo kiểu non-fast-forward hoặc fetch first? Đừng vội dùng --force. Đây là lỗi rất thường gặp khi nhánh remote đã có commit mới mà máy bạn chưa có.

Trong bài này, mình sẽ đi theo hướng an toàn: hiểu lỗi, kiểm tra khác biệt giữa local và remote, chọn pull/rebase đúng tình huống, rồi push lại mà không làm mất code của người khác. Cùng xử lý từng bước nhé!

git push bị rejected do non-fast-forward
Lỗi push rejected thường xảy ra khi remote đã có commit mới mà local chưa cập nhật.

Tóm tắt nhanh

  • Lỗi push rejected thường do remote branch có commit mới mà local branch chưa có.
  • Hãy chạy git fetch và so sánh HEAD..origin/branch trước khi sửa.
  • Nếu branch cá nhân, git pull --rebase thường giúp lịch sử commit gọn hơn.
  • Nếu branch dùng chung, ưu tiên cách ít rủi ro: fetch, review, merge hoặc rebase theo quy ước team.
  • Chỉ dùng --force-with-lease khi bạn hiểu rõ hậu quả và đang rewrite branch phù hợp.

Git push bị rejected là gì?

Git push bị rejected nghĩa là Git từ chối cập nhật nhánh remote vì thao tác push của bạn không thể đi tiếp từ lịch sử hiện tại trên remote. Trường hợp phổ biến nhất là lỗi non-fast-forward: remote có commit mới mà local chưa kéo về.

Theo tài liệu chính thức của Git, git push có thể bị từ chối khi cập nhật remote ref không phải là fast-forward, tức commit bạn muốn push không chứa lịch sử mới nhất của remote [1]. GitHub cũng giải thích lỗi non-fast-forward theo hướng tương tự: bạn cần tích hợp thay đổi từ remote trước khi push lại [2].

Ví dụ bạn đang ở nhánh feature/login. Bạn commit một thay đổi local, nhưng trong lúc đó đồng đội cũng merge một commit khác vào cùng nhánh remote. Khi bạn push, Git không biết nên đặt commit của bạn lên trên commit remote theo cách nào, nên nó dừng lại để bạn xử lý có chủ đích.

Sơ đồ non-fast-forward khi push bị từ chối
Non-fast-forward xảy ra khi lịch sử local và remote đã lệch nhau.

Vì sao Git báo non-fast-forward?

Lý do cốt lõi là local branch của bạn đang thiếu commit trên remote branch. Git bảo vệ remote branch bằng cách không cho bạn ghi đè lịch sử mới nếu chưa tích hợp nó vào local.

Các tình huống hay gặp gồm:

  • Đồng đội đã push commit mới lên cùng branch trước bạn.
  • Bạn sửa trực tiếp file trên GitHub/GitLab rồi quên pull về máy.
  • CI hoặc bot tạo commit tự động trên remote branch.
  • Bạn vừa rebase hoặc amend commit local, làm lịch sử local khác remote.
  • Branch local đang trỏ tới upstream không đúng.

Điểm quan trọng là lỗi này không có nghĩa code của bạn hỏng. Nó chỉ nói rằng Git cần bạn quyết định cách ghép hai lịch sử lại. Nếu xử lý bình tĩnh, đây là thao tác bình thường trong workflow team.

Bạn đang đọc bài viết thuộc chuyên mục Lập trình của VietnamTutor — nơi mình chia sẻ các lỗi Git thực tế theo hướng dễ kiểm tra, dễ sửa và giảm rủi ro mất code.

Kiểm tra gì trước khi sửa?

Trước khi pull, rebase hay force push, bạn nên fetch remote và xem chính xác local đang khác remote ở đâu. Bước này giúp bạn tránh sửa theo cảm tính.

Chạy lần lượt:

# Lấy thông tin mới nhất từ remote nhưng chưa merge vào code local
git fetch origin

# Xem branch hiện tại và upstream
git status -sb

# Xem commit remote có mà local chưa có
git log --oneline HEAD..origin/main

# Xem commit local có mà remote chưa có
git log --oneline origin/main..HEAD

Thay main bằng branch thật của bạn, ví dụ feature/login. Tài liệu git fetch mô tả rõ fetch chỉ tải object/ref từ remote, chưa tự merge vào working tree [3]. Vì vậy đây là bước kiểm tra khá an toàn.

Nếu git status -sb cho thấy branch của bạn đang aheadbehind cùng lúc, nghĩa là hai bên đã diverge. Khi đó, bạn cần merge hoặc rebase thay vì push thẳng.

Quy trình kiểm tra trước khi sửa lỗi push rejected
Fetch và so sánh commit trước giúp bạn chọn cách sửa đúng.

Cách sửa lỗi push rejected an toàn

Cách sửa an toàn nhất là tích hợp commit remote vào local trước, kiểm tra lại, rồi push. Bạn có thể dùng merge hoặc rebase tùy workflow của team.

Cách 1: Pull bằng merge

Nếu team của bạn chấp nhận merge commit, cách dễ hiểu nhất là:

# Kéo commit remote về và merge vào branch hiện tại
git pull origin main

# Nếu có conflict, sửa file rồi commit merge
git status

# Push lại sau khi local đã chứa commit remote
git push origin main

git pull về bản chất là fetch rồi tích hợp thay đổi vào branch hiện tại, tùy cấu hình có thể merge hoặc rebase [4]. Với người mới, merge dễ nhìn vì Git tạo commit riêng ghi nhận việc ghép lịch sử.

Cách 2: Pull bằng rebase

Nếu branch là nhánh cá nhân hoặc team muốn lịch sử tuyến tính, bạn có thể dùng:

# Đặt commit local của bạn lên trên commit mới từ remote
git pull --rebase origin main

# Nếu có conflict: sửa file, add lại, rồi tiếp tục rebase
git add .
git rebase --continue

# Sau khi rebase xong, push lại
git push origin main

git rebase sẽ chuyển các commit local của bạn sang nền lịch sử mới hơn [5]. Cách này cho lịch sử gọn, nhưng bạn cần cẩn thận nếu branch đã chia sẻ cho nhiều người.

Cách chọn nhanh

Tình huốngNên dùngLý do
Branch dùng chung, nhiều người pushgit pull hoặc theo quy định teamÍt gây bất ngờ cho người khác
Branch cá nhân, chưa ai dựa vào lịch sử của bạngit pull --rebaseLịch sử commit gọn và dễ review
Remote có commit lạgit fetch rồi review logKhông merge vội khi chưa hiểu thay đổi
Bạn vừa amend/rebase commit đã pushCân nhắc --force-with-leaseChỉ dùng khi branch phù hợp để rewrite
Bảng chọn merge hoặc rebase khi push bị từ chối
Merge dễ hiểu hơn, rebase giúp lịch sử gọn hơn khi dùng đúng tình huống.

Khi nào được force push?

Force push chỉ nên dùng khi bạn cố ý rewrite lịch sử của branch và chắc chắn không ghi đè commit của người khác. Nếu cần dùng, --force-with-lease thường an toàn hơn --force vì nó kiểm tra remote có thay đổi ngoài dự kiến hay không.

Ví dụ sau khi rebase branch cá nhân đã từng push:

# Cẩn thận: chỉ dùng khi bạn hiểu branch này có thể rewrite
git push --force-with-lease origin feature/login

Tài liệu git push có mô tả --force-with-lease như một cơ chế bảo vệ bằng cách yêu cầu remote ref vẫn đang ở giá trị bạn kỳ vọng [1]. Nói đơn giản: nếu ai đó đã push commit mới lên remote sau lần fetch gần nhất của bạn, lệnh này sẽ không ghi đè mù quáng.

Mình khuyên bạn không dùng force push trên main, develop hoặc branch release nếu không có quy trình rõ. Với các nhánh quan trọng, hãy dùng pull request, protected branch và review thay vì sửa trực tiếp.

Cảnh báo dùng force-with-lease khi push bị từ chối
Force-with-lease có lớp kiểm tra an toàn hơn force push thông thường.

Làm sao phòng tránh lỗi này?

Bạn không thể loại bỏ hoàn toàn lỗi push rejected trong môi trường nhiều người, nhưng có thể giảm đáng kể bằng workflow rõ ràng. Thói quen tốt là fetch/pull thường xuyên, làm branch nhỏ và tránh sửa trực tiếp trên remote.

  • Trước khi bắt đầu làm việc: chạy git fetch hoặc git pull --rebase theo quy ước team.
  • Trước khi push: kiểm tra git status -sb để biết branch đang ahead/behind.
  • Làm branch ngắn, push sớm, mở pull request nhỏ.
  • Không amend/rebase commit đã push lên branch dùng chung.
  • Dùng protected branch cho main để tránh push trực tiếp.

Nếu bạn đang xử lý thêm conflict sau khi pull, có thể đọc tiếp bài Git pull bị conflict. Nếu lỡ commit nhầm trước khi push, bài Git commit nhầm file sẽ phù hợp hơn. Còn nếu cần hiểu rõ lệnh undo, xem thêm Git reset, revert, restore.

Tóm lại, lỗi push rejected không đáng sợ nếu bạn không vội force push. Hãy fetch trước, xem commit nào đang lệch, chọn merge hoặc rebase theo workflow, rồi push lại. Bạn sẽ thấy lỗi này thực ra là một cơ chế bảo vệ khá hữu ích của Git!

Nguồn tham khảo

  1. Git Documentation: git-push
  2. GitHub Docs: Dealing with non-fast-forward errors
  3. Git Documentation: git-fetch
  4. Git Documentation: git-pull
  5. Git Documentation: git-rebase
  6. Git Documentation: git-merge

Các câu hỏi thường gặp

Git push bị rejected có làm mất code không?

Không. Lỗi rejected chỉ có nghĩa Git chưa cho bạn cập nhật remote. Code local vẫn còn, trừ khi bạn tự chạy lệnh ghi đè như reset hard hoặc force push sai cách.

Nên dùng git pull hay git pull –rebase?

Nếu branch dùng chung, hãy theo quy ước team. Nếu branch cá nhân và muốn lịch sử gọn, git pull –rebase thường phù hợp. Điều quan trọng là kiểm tra remote trước khi tích hợp.

Có nên dùng git push –force để sửa non-fast-forward không?

Không nên dùng mặc định. Force push có thể ghi đè commit của người khác. Nếu thật sự cần rewrite branch cá nhân, hãy cân nhắc git push –force-with-lease và fetch trước đó.

Lỗi fetch first khác gì non-fast-forward?

Hai thông báo thường nói cùng một vấn đề: remote có commit mới mà local chưa có. Git yêu cầu bạn fetch/pull và tích hợp thay đổi trước khi push lại.

Sau khi pull bị conflict thì làm gì?

Hãy mở các file conflict, chọn nội dung đúng, chạy git add rồi tiếp tục merge hoặc rebase. Nếu chưa chắc, bạn có thể abort thao tác hiện tại trước khi thử lại.

Tú Anh

Cây bút chính tại VietnamTutor

Bài viết cùng chuyên mục

.gitignore không hoạt động: nguyên nhân và cách sửa

Bài viết giúp bạn kiểm tra vì sao .gitignore không hoạt động, sửa lỗi file đã tracked và dùng git check-ignore để debug pattern.

Git reset revert restore: chọn lệnh đúng

Bài viết so sánh git reset, git revert và git restore theo mục đích sử dụng: sửa staging area, khôi phục file, undo commit chưa push

Git commit vào nhánh sai: cách chuyển an toàn

Bài viết hướng dẫn xử lý git commit vào nhánh sai theo từng tình huống: commit chưa push, đã push, nhiều commit liên tiếp hoặc branch

TypeScript cho website doanh nghiệp: API, form và lỗi

TypeScript cho website doanh nghiệp đáng dùng khi bạn cần kiểm soát API contract, form schema, CMS payload và cấu hình môi trường. Bài này giúp

React Server Components performance: khi nào nên dùng?

React Server Components performance không phải phép màu. Bài này giúp bạn biết khi nào RSC giảm JavaScript thật, khi nào làm kiến trúc phức tạp

Git commit nhầm file: bỏ file khỏi commit an toàn

Bài viết hướng dẫn xử lý git commit nhầm file theo từng tình huống: chưa commit, đã commit chưa push, đã push lên remote, hoặc lỡ

Git reflog: khôi phục commit đã mất an toàn

Bài viết hướng dẫn dùng git reflog để khôi phục commit đã mất sau reset, rebase, amend hoặc xóa nhánh. Bạn sẽ biết cách đọc reflog,

Git pull bị conflict: cách sửa không mất code

Bài viết hướng dẫn cách xử lý git pull bị conflict theo từng bước: kiểm tra trạng thái, sửa file xung đột, test lại và hoàn

Next.js production performance: chọn SSR, SSG, ISR hay Edge

Bài viết giúp developer và tech lead chọn cách render phù hợp để tối ưu Next.js production performance mà không làm kiến trúc phức tạp quá

Nâng cấp Laravel 13: Checklist 10 bước cần kiểm tra

Hướng dẫn nâng cấp Laravel 13 chi tiết với checklist 10 bước. Từ kiểm tra PHP 8.3, cập nhật dependencies, đến xử lý lỗi thường gặp

Hardening Laravel production: Checklist bảo mật cần làm

Checklist hardening Laravel production toàn diện. Từ cấu hình server, database, SSL đến security headers, rate limiting và monitoring.

Authentication và authorization trong Laravel: Cách phân biệt

Hướng dẫn chi tiết cách xây dựng hệ thống Authentication (xác thực) và Authorization (phân quyền) trong Laravel với Breeze, Fortify, Sanctum, Policies và Gates.