git push: cách gửi thay đổi lên kho từ xa
git push gửi các commit cục bộ của nhánh hiện tại mà kho từ xa chưa có, và dịch chuyển con trỏ nhánh trên máy chủ tới commit mới nhất của bạn. Lần đầu công bố nhánh dùng git push -u origin tên-nhánh, sau đó chỉ cần git push. Nếu máy chủ có commit của người khác, hãy lấy chúng về bằng git pull, đừng force push.
Git push làm gì
Lệnh git push lấy các commit từ nhánh cục bộ mà máy chủ chưa có, gửi tới kho từ xa (thường tên là origin) và dịch chuyển con trỏ nhánh ở đó tới commit mới nhất của bạn. Bản thân tệp trong thư mục làm việc và các thay đổi chưa commit không được gửi đi: chỉ có lịch sử, tức là các commit, được chuyển.
Điều quan trọng cần hiểu: push không phải là «đồng bộ». Nó chỉ chạy theo một chiều: từ kho cục bộ lên kho từ xa. Để nhận thay đổi của người khác, cần git fetch hoặc git pull. Nhầm lẫn hai chiều này là nguyên nhân của gần như mọi vấn đề mà người mới hay gặp.
Một quy tắc khác của Git: mặc định push chỉ được phép theo kiểu «tiến nhanh» (fast-forward). Máy chủ sẽ nhận commit của bạn nếu chúng tiếp nối lịch sử đã có trên máy chủ. Nếu ai đó đã gửi commit của họ trước, push của bạn sẽ bị từ chối — đây là cơ chế bảo vệ, không phải lỗi.
Cú pháp lệnh
git push [<remote>] [<nhánh-cục-bộ>[:<nhánh-từ-xa>]]
Nếu gọi git push không có tham số, Git lấy nhánh hiện tại và gửi nó tới nhánh được cấu hình làm upstream. Dạng đầy đủ hữu ích khi upstream chưa được thiết lập, hoặc khi bạn muốn gửi nhánh dưới một tên khác:
git push origin main # gửi main cục bộ tới origin/main
git push origin feature/login # gửi nhánh feature/login
git push origin fix:hotfix-123 # gửi fix cục bộ thành hotfix-123 trên máy chủ
git push origin HEAD # gửi nhánh hiện tại với cùng tên
Lần đầu gửi thay đổi
Con đường điển hình từ dự án mới đến mã trên GitHub hay GitLab là như sau. Trước tiên tạo kho rỗng trên máy chủ, sau đó liên kết kho cục bộ với nó và gửi nhánh.
git init
git add .
git commit -m "Commit đầu tiên"
git branch -M main
git remote add origin git@github.com:user/project.git
git push -u origin main
Cờ -u (dạng đầy đủ --set-upstream) liên kết nhánh main cục bộ với origin/main. Sau đó trong nhánh này chỉ cần gõ git push và git pull, còn git status bắt đầu hiển thị bạn đang vượt trước hay tụt sau máy chủ bao nhiêu commit.
Để kiểm tra kho từ xa đang trỏ đi đâu, dùng git remote -v. Nếu địa chỉ sai, sửa bằng git remote set-url origin <địa-chỉ-mới>, đừng xóa và tạo lại kho.
Gửi một nhánh mới
Bạn đã tạo nhánh cho công việc, commit vài lần và gõ git push. Git trả lời:
fatal: The current branch feature/login has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin feature/login
Đây không phải lỗi, mà là gợi ý: nhánh chưa có cặp trên máy chủ. Chạy lệnh được đề xuất hoặc dạng ngắn git push -u origin feature/login. Để Git tự làm việc này cho mọi nhánh mới, bật cấu hình (có từ Git 2.37):
git config --global push.autoSetupRemote true
Vì sao git push bị từ chối: rejected, non-fast-forward
Tình huống thường gặp nhất khi làm việc nhóm:
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'github.com:user/project.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally.
Đồng nghiệp đã gửi commit của họ trước bạn. Lịch sử của bạn và lịch sử trên máy chủ đã rẽ nhánh, và nếu Git chấp nhận push của bạn, các commit của đồng nghiệp sẽ bị mất. Cách đúng là lấy thay đổi về trước và đặt commit của mình lên trên:
git pull --rebase # lấy commit của người khác và đặt commit của mình lên trên
# khi có xung đột: sửa tệp, git add, git rebase --continue
git push
Thay vì --rebase, có thể dùng git pull thông thường — khi đó xuất hiện commit gộp. Cả hai cách đều đúng; trong hầu hết nhóm, với commit cục bộ của mình người ta thích rebase hơn để lịch sử thẳng hàng. Điều quan trọng là đừng «giải quyết» lỗi từ chối bằng --force.
git push --force và --force-with-lease
Force push cần thiết khi bạn cố tình viết lại lịch sử nhánh của mình: đã dùng git commit --amend, git rebase hoặc gộp các commit trước khi review. Push thường sẽ không chấp nhận nhánh như vậy, vì các commit cũ trên máy chủ không còn là tổ tiên của commit mới.
git push --force-with-lease
--force-with-lease chỉ ghi đè nhánh trên máy chủ nếu nhánh đang ở đúng vị trí mà Git cục bộ lần cuối nhìn thấy (theo tham chiếu origin/<nhánh>). Nếu đồng nghiệp đã kịp push gì đó, lệnh sẽ từ chối và công việc của họ không bị xóa. Còn --force (-f) không kiểm tra gì và ghi đè im lặng.
Lưu ý: nếu chạy git fetch ngay trước force push, tham chiếu origin/<nhánh> được cập nhật, và cơ chế bảo vệ không còn hiệu lực — Git sẽ cho rằng bạn đã thấy các commit của người khác. Vì vậy đừng fetch rồi force push mà không xem trước những gì đã về.
| Lệnh | Tác dụng | Khi nào dùng |
|---|---|---|
git push | Chỉ fast-forward | Luôn là mặc định |
git push --force-with-lease | Ghi đè có kiểm tra | Nhánh riêng sau rebase hoặc amend |
git push --force | Ghi đè không kiểm tra | Hầu như không bao giờ; tuyệt đối không dùng với nhánh chung |
Không được force push vào nhánh chung (main, develop): những ai đã tải lịch sử cũ sẽ bị lệch so với máy chủ. Thường các nhánh này còn được bảo vệ bằng cài đặt của kho, và máy chủ sẽ trả về protected branch hook declined.
Gửi thẻ (tag)
Lệnh git push thông thường không gửi thẻ. Cần chỉ định rõ:
git tag -a v1.2.0 -m "Bản phát hành 1.2.0"
git push origin v1.2.0 # một thẻ
git push --tags # tất cả thẻ cục bộ
git push --follow-tags # commit và thẻ chú thích trỏ tới các commit đó
--follow-tags tiện hơn --tags: nó không gửi các thẻ nhẹ ngẫu nhiên, chỉ gửi thẻ chú thích và chỉ những thẻ có thể đạt tới từ các commit được gửi.
Xóa nhánh trên máy chủ
Việc xóa cũng được thực hiện qua push:
git push origin --delete feature/login
# cú pháp cũ, làm việc tương tự:
git push origin :feature/login
Nhánh cục bộ vẫn còn, có thể xóa bằng git branch -d feature/login. Còn đồng nghiệp sẽ vẫn thấy tham chiếu origin/feature/login cho đến khi họ chạy git fetch --prune.
Các cờ hữu ích của git push
| Cờ | Mục đích |
|---|---|
-u, --set-upstream | Ghi nhớ nhánh từ xa làm upstream |
-n, --dry-run | Hiển thị những gì sẽ được gửi, không gửi gì |
--all | Gửi tất cả nhánh cục bộ |
--tags / --follow-tags | Gửi thẻ |
-d, --delete | Xóa nhánh hoặc thẻ trên máy chủ |
--force-with-lease | Viết lại lịch sử an toàn |
-v, --verbose | Hiển thị chi tiết |
--no-verify | Bỏ qua hook pre-push (dùng khi hiểu rõ lý do) |
Lỗi thường gặp khi git push và cách sửa
Permission denied (publickey)
Máy chủ không chấp nhận khóa SSH của bạn. Kiểm tra khóa có tồn tại (ls ~/.ssh), phần công khai đã được thêm vào tài khoản và kết nối hoạt động: ssh -T git@github.com. Nếu remote dùng HTTPS, Git sẽ hỏi tên đăng nhập và token thay vì khóa — xem git remote -v.
Authentication failed khi dùng HTTPS
GitHub và nhiều dịch vụ khác từ lâu không chấp nhận mật khẩu tài khoản khi làm việc qua HTTPS. Cần token truy cập cá nhân hoặc chuyển sang SSH. Để lưu token có thể dùng credential helper: git config --global credential.helper store (lưu dạng văn bản thuần) hoặc trình quản lý thông tin đăng nhập của hệ điều hành.
src refspec main does not match any
Bạn không có nhánh với tên đó. Thường gặp nhất là kho mới chưa có commit nào, hoặc nhánh đang tên master. Kiểm tra bằng git branch, rồi commit hoặc đổi tên nhánh: git branch -M main.
Everything up-to-date dù có thay đổi
Bạn quên commit. git push gửi commit, không gửi tệp. Chạy git status, sau đó git add và git commit. Khả năng khác là bạn đang ở nhánh khác với nhánh mà bạn tưởng.
Tệp quá lớn
Các dịch vụ giới hạn kích thước tệp (trên GitHub là 100 MB). Nếu tệp như vậy đã lọt vào lịch sử, xóa nó trong commit mới là không đủ: cần viết lại lịch sử, hoặc gỡ nó khỏi các commit chưa gửi (git reset --soft, rồi commit lại). Với tệp nhị phân lớn, người ta dùng Git LFS.
Quy trình làm việc với git push
git switch -c feature/x— tạo nhánh cho công việc.- Commit từng bước nhỏ, hợp lý.
git fetchvàgit rebase origin/main— cập nhật nhánh chính mới nhất.git push -u origin feature/x— công bố nhánh.- Mở pull request; sau khi review — thêm commit và chạy
git pushthông thường. - Nếu đã phải viết lại lịch sử —
git push --force-with-lease.
Trước khi gửi, nên xem sẽ có gì lên máy chủ: git log origin/main..HEAD --oneline hiển thị các commit mà máy chủ chưa có.
Những gì nên cấu hình một lần
Vài thiết lập toàn cục giúp tránh phần lớn bất ngờ khi gửi. push.default quyết định git push không tham số sẽ làm gì. Giá trị mặc định ở các phiên bản hiện đại là simple: chỉ gửi nhánh hiện tại và chỉ tới nhánh cùng tên làm upstream. Thường không cần đổi, nhưng nên kiểm tra trong cấu hình cũ không còn matching — chế độ gửi mọi nhánh trùng tên cùng lúc.
git config --global push.default simple
git config --global push.autoSetupRemote true
git config --global push.followTags true
git config --global pull.rebase true
push.followTags đặt --follow-tags làm hành vi mặc định, còn pull.rebase giúp tránh các commit gộp thừa khi push bị từ chối và cần lấy thay đổi của người khác trước.
Hook pre-push
Tệp .git/hooks/pre-push chạy trước khi gửi. Nếu nó kết thúc với mã khác 0, push không diễn ra. Các nhóm thường chạy test hoặc linter trong đó để code hỏng không lên máy chủ. Chỉ nên bỏ qua hook bằng --no-verify khi bạn thật sự hiểu vì sao nó lỗi.
Tóm lại
git push công bố các commit của bạn lên kho từ xa và chỉ hoạt động theo một chiều. Lần đầu gửi nhánh hãy dùng -u; khi bị từ chối, trước tiên lấy thay đổi của người khác bằng pull; còn lịch sử nhánh riêng đã viết lại thì chỉ gửi bằng --force-with-lease. Làm việc với kho từ xa, push, pull và khóa SSH được hướng dẫn thực hành trong khóa học Git.
Câu hỏi thường gặp
Git push có gửi các thay đổi chưa commit không?
Không. Push chỉ gửi commit; thay đổi trong thư mục làm việc và index vẫn ở máy bạn cho đến khi bạn commit chúng.
Làm sao hoàn tác một lần git push?
Trong nhánh riêng có thể hoàn tác commit cục bộ bằng git reset rồi gửi bằng git push --force-with-lease. Trong nhánh chung an toàn hơn là tạo commit đảo ngược bằng git revert rồi push thông thường.
Có thể chỉ gửi một commit không?
Có, bằng hash của nó: git push origin abc1234:main sẽ gửi toàn bộ lịch sử đến commit này bao gồm cả nó, nhưng không gửi các commit sau đó. Không thể gửi một commit mà không kèm tổ tiên của nó.
git push khác git push origin main ở điểm nào?
Không có tham số, Git dùng upstream đã cấu hình của nhánh hiện tại; dạng đầy đủ chỉ rõ remote và nhánh, và hoạt động cả khi chưa cấu hình upstream.
Làm sao gửi tới hai kho cùng lúc?
Thêm remote thứ hai và gửi tới từng kho theo tên; hoặc thêm nhiều địa chỉ push cho origin bằng git remote set-url --add --push origin địa-chỉ (cần cả địa chỉ gốc, nếu không push chỉ đi tới địa chỉ thứ hai).
Chủ đề này được giảng chi tiết kèm thực hành trong khóa học “Git с нуля до уверенного разработчика”.
Đến khóa học