git fetch và git pull: khác nhau thế nào và nên chọn cái nào
git fetch chỉ tải các commit mới từ máy chủ và cập nhật nhánh từ xa như origin/main, không đụng tới nhánh hiện tại hay tệp trong thư mục làm việc. git pull làm đúng việc đó và sau đó gộp ngay vào nhánh hiện tại bằng merge hoặc rebase. Fetch luôn an toàn, còn pull tiện khi bạn sẵn sàng nhận thay đổi của người khác ngay.
Câu trả lời ngắn gọn
git fetch tải các commit mới từ máy chủ và cập nhật tham chiếu nhánh từ xa (origin/main, origin/feature-x), nhưng không thay đổi nhánh hiện tại cũng như tệp trong thư mục làm việc. git pull là git fetch cộng với việc gộp ngay phần đã tải vào nhánh hiện tại: bằng merge (mặc định) hoặc bằng rebase.
Từ đó có quy tắc thực tế: fetch có thể chạy bất cứ lúc nào, nó không phá gì cả. Pull thay đổi nhánh của bạn và có thể kết thúc bằng xung đột, vì vậy nên chạy khi thư mục làm việc sạch và bạn biết trước điều gì sắp đến.
Nhánh từ xa nằm ở đâu
Để thấy rõ sự khác biệt, cần hiểu có hai loại nhánh trong kho cục bộ. Nhánh cục bộ — main, feature-x — do commit của bạn di chuyển. Nhánh theo dõi từ xa — origin/main — là các dấu trang «máy chủ trông như thế nào lúc đồng bộ lần cuối». Không thể di chuyển chúng bằng tay; chúng chỉ được cập nhật khi giao tiếp với máy chủ: fetch, pull và push.
git branch -a
* main
remotes/origin/HEAD -> origin/main
remotes/origin/main
remotes/origin/feature-x
git status so sánh nhánh của bạn với dấu trang origin/main, chứ không phải với máy chủ đang chạy. Vì vậy thông báo «Your branch is up to date with 'origin/main'» có thể sai: nếu lâu rồi bạn không fetch, Git đơn giản là chưa biết về các commit mới của đồng nghiệp.
git fetch làm gì
$ git fetch
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
Unpacking objects: 100% (4/4), done.
From github.com:team/project
3f2a1c9..8b7d4e2 main -> origin/main
Dòng cuối cho thấy điều quan trọng nhất: tham chiếu origin/main đã chuyển từ 3f2a1c9 sang 8b7d4e2. Nhánh main của bạn vẫn đứng yên. Bây giờ có thể thoải mái xem những gì đã đến:
git status # Your branch is behind 'origin/main' by 2 commits
git log main..origin/main --oneline # các commit bạn chưa có
git diff main origin/main # file nào sẽ thay đổi
Một số biến thể hữu ích của fetch:
git fetch --all— lấy thay đổi từ mọi remote (hữu ích khi form có cảoriginvàupstream);git fetch --prune(-p) — xóa các dấu trang nhánh cục bộ mà máy chủ đã không còn;git fetch origin main— chỉ lấy một nhánh;git fetch --tags— lấy thêm tất cả thẻ.
Để prune luôn hoạt động, chỉ cần bật một lần: git config --global fetch.prune true.
git pull làm gì
git pull thực hiện hai bước liên tiếp. Đầu tiên là fetch cho upstream của nhánh hiện tại, sau đó gộp origin/<nhánh> vào nhánh của bạn. Về bản chất, điều này tương đương với:
git fetch origin
git merge origin/main # hoặc git rebase origin/main khi pull --rebase
Kết quả gộp phụ thuộc vào cách hai lịch sử phân nhánh:
- bạn không có commit mới — xảy ra fast-forward, nhánh chỉ đơn giản được dịch tới trước;
- có commit mới ở cả bạn và máy chủ — với merge sẽ tạo commit gộp, với rebase các commit của bạn được đặt lại lên trên commit từ máy chủ;
- cùng một dòng bị sửa ở cả hai phía — xung đột, cần giải quyết bằng tay.
So sánh git fetch và git pull
| git fetch | git pull | |
|---|---|---|
| Tải commit về | Có | Có |
| Cập nhật origin/* | Có | Có |
| Thay đổi nhánh hiện tại | Không | Có |
| Thay đổi tệp trong thư mục làm việc | Không | Có |
| Có thể gây xung đột | Không | Có |
| Cần thư mục làm việc sạch | Không | Nên có |
| Ảnh hưởng tới nhánh khác | Cập nhật mọi dấu trang đang theo dõi | Chỉ gộp vào nhánh hiện tại |
| Khi nào dùng | Xem có gì thay đổi; trước rebase; trong script | Nhanh chóng bắt kịp máy chủ trong nhánh của mình |
git pull: merge hay rebase
Từ Git 2.27, khi lịch sử phân nhánh mà chưa có cấu hình rõ ràng, pull sẽ cảnh báo, còn ở các phiên bản mới hơn nó dừng với lỗi «Need to specify how to reconcile divergent branches» thay vì tự merge. Git yêu cầu bạn chọn chiến lược một lần:
git config --global pull.rebase false # merge (hành vi mặc định trước đây)
git config --global pull.rebase true # rebase
git config --global pull.ff only # chỉ fast-forward, nếu không thì từ chối
Merge giữ lịch sử đúng như nó đã có và thêm một commit «Merge branch 'main' of github.com:...». Nếu mỗi ngày pull vào nhánh có commit cục bộ, lịch sử sẽ đầy những commit gộp kỹ thuật như vậy.
Rebase đặt lại các commit chưa gửi của bạn lên trên các commit mới từ máy chủ. Lịch sử vẫn thẳng hàng, còn xung đột được giải quyết từng commit một. Với commit cục bộ chưa công bố, đây thường là lựa chọn tốt nhất. Rebase chỉ nguy hiểm với commit đã gửi, mà người khác đang dựa vào.
ff only là chế độ nghiêm ngặt nhất: pull chỉ thành công khi bạn không có commit riêng. Nếu lịch sử đã phân nhánh, bạn phải tự quyết định. Nhiều lập trình viên có kinh nghiệm dùng chế độ này cho nhánh chính.
Nếu có thay đổi chưa commit trong thư mục làm việc mà vẫn cần pull ngay, dùng git pull --rebase --autostash: Git sẽ cất tạm thay đổi, thực hiện rebase rồi khôi phục lại.
Tình huống điển hình: fetch, xem, rồi gộp
Cách làm «fetch trước» cho bạn quyền kiểm soát những gì vào nhánh của mình. Đặc biệt hữu ích trước khi tạo pull request và sau một thời gian dài vắng mặt.
git fetch
git log --oneline --graph HEAD origin/main # hai nhánh đã phân kỳ thế nào
git rebase origin/main # hoặc git merge origin/main
git push
Một tình huống phổ biến khác là cập nhật nhánh tính năng bằng main mới mà không cần chuyển sang main:
git switch feature-x
git fetch
git rebase origin/main
Ở đây pull không phù hợp: git pull trong nhánh feature sẽ lấy thay đổi từ upstream của chính nhánh đó (origin/feature-x), không phải từ nhánh chính. Dĩ nhiên có thể viết git pull --rebase origin main, nhưng cách dùng fetch rõ ràng hơn.
Đồng bộ với máy chủ: giữ nhánh luôn cập nhật
Khi nói «git sync», người ta thường hiểu chu trình «lấy việc của người khác — gửi việc của mình». Trong Git thuần không có lệnh sync riêng (nút Sync trong IDE và các ứng dụng GUI thực chất làm pull rồi push). Thủ công, chu trình an toàn như sau:
git status— đảm bảo mọi thứ đã commit hoặc đã cất bằnggit stash;git pull --rebase— lấy thay đổi và đặt các commit của mình lên trên;- giải quyết xung đột nếu có: sửa tệp,
git add,git rebase --continue; - chạy test;
git push.
Đồng bộ một fork với kho gốc có thể làm như sau:
git remote add upstream https://github.com/original/project.git
git fetch upstream
git switch main
git merge --ff-only upstream/main
git push origin main
Các vấn đề thường gặp với git pull
Your local changes would be overwritten by merge
Pull muốn thay đổi các tệp mà bạn đang sửa và chưa commit. Có thể: commit, cất tạm (git stash, rồi pull và git stash pop) hoặc dùng --autostash. Không nên xóa thay đổi chỉ để pull.
Xung đột sau khi pull
Git dừng lại và hiển thị các tệp xung đột trong git status. Mở chúng, chọn phương án đúng giữa các dấu <<<<<<< và >>>>>>>, sau đó git add và git commit (khi merge) hoặc git rebase --continue (khi rebase). Nếu đổi ý — git merge --abort hoặc git rebase --abort sẽ đưa mọi thứ về trạng thái trước khi pull.
There is no tracking information for the current branch
Nhánh chưa có upstream nên Git không biết lấy từ đâu. Chỉ định rõ: git pull origin main, hoặc thiết lập liên kết một lần: git branch --set-upstream-to=origin/main.
refusing to merge unrelated histories
Kho cục bộ và kho từ xa được tạo độc lập, ví dụ trên máy chủ đã tạo sẵn README khi khởi tạo kho. Nếu chắc chắn muốn ghép lại: git pull origin main --allow-unrelated-histories.
Pull xong muốn quay lại
Nếu merge sau pull đã hoàn tất mà kết quả không ổn, đưa nhánh về trạng thái trước đó: git reset --hard ORIG_HEAD. Lệnh này xóa thay đổi chưa commit, nên hãy kiểm tra git status trước. Trong trường hợp xấu nhất, vị trí cũ của nhánh có thể tìm trong git reflog.
Nên chọn gì
- Muốn biết có gì mới mà không thay đổi gì —
git fetch. - Làm việc trong nhánh riêng và chỉ muốn bắt kịp máy chủ —
git pull --rebase(hoặc pull vớipull.rebase trueđã cấu hình). - Cập nhật nhánh chính mà không có commit riêng —
git pull --ff-only. - Cần gộp
mainvào nhánh feature —git fetch, sau đógit rebase origin/mainhoặcgit merge origin/main. - Viết script hoặc CI — dùng fetch và các lệnh rõ ràng, không dùng pull.
Điều này trông như thế nào trên đồ thị commit
Giả sử bạn và đồng nghiệp cùng bắt đầu từ commit C. Bạn tạo commit cục bộ D, còn đồng nghiệp đẩy E lên máy chủ. Sau git fetch, kho của bạn biết cả hai nhánh: main trỏ tới D, origin/main trỏ tới E, và chúng có tổ tiên chung là C.
D <- main (HEAD)
/
A---B---C
\
E <- origin/main
Sau đó, cách gộp quyết định mọi thứ. git merge origin/main tạo commit M với hai cha là D và E. git rebase origin/main sao chép D lên trên E thành commit mới D' với hash khác, và lịch sử trở thành một đường thẳng. git pull chỉ thực hiện các bước này một cách tự động, không có khoảng dừng. Chính khoảng dừng đó — cơ hội nhìn đồ thị trước khi gộp — là giá trị chính của fetch tách riêng.
Fetch và pull trong IDE
Trong VS Code, IntelliJ IDEA và các trình soạn thảo khác, nút có tên khác nhau, nhưng bên dưới là cùng các lệnh. «Fetch» là git fetch, «Pull» hoặc «Update Project» là pull với chiến lược đã chọn trong cài đặt (merge hoặc rebase), «Sync Changes» là pull rồi push. Trước khi bấm Sync trong nhánh chung, nên biết chiến lược nào đang được chọn, nếu không lịch sử sẽ bất ngờ có thêm commit gộp. Nhiều IDE cũng tự fetch ngầm định kỳ, vì vậy bộ đếm commit đến trên thanh trạng thái thường khá chính xác.
Tóm lại
git fetch cập nhật những gì bạn biết về máy chủ, còn git pull áp dụng thông tin đó vào nhánh hiện tại. Fetch an toàn bất cứ lúc nào, pull tiện nhưng thay đổi nhánh và có thể mang theo xung đột, vì vậy hãy đặt chiến lược một lần và giữ thư mục làm việc sạch. Sự khác biệt giữa push, pull và fetch, cùng việc làm việc với remote và GitHub, được hướng dẫn thực hành trong khóa học Git.
Câu hỏi thường gặp
Có cần chạy git fetch trước git pull không?
Không, pull tự thực hiện fetch ở bước đầu. Fetch riêng cần khi bạn muốn xem thay đổi trước rồi mới quyết định gộp thế nào.
git fetch có thể xóa thay đổi của tôi không?
Không. Fetch chỉ thay đổi tham chiếu từ xa như origin/main, không đụng tới nhánh cục bộ, index hay thư mục làm việc.
git pull có cập nhật mọi nhánh không?
Không, pull chỉ gộp thay đổi vào nhánh hiện tại. Các dấu trang origin/* của nhánh khác được cập nhật nhờ fetch, nhưng bản thân các nhánh cục bộ vẫn đứng yên.
Nên chạy git fetch bao lâu một lần?
Trước khi bắt đầu làm việc, trước khi tạo pull request và trước khi push. Nhiều IDE tự fetch ở nền, điều này an toàn.
FETCH_HEAD là gì?
Đó là tham chiếu nội bộ mà fetch ghi lại commit vừa tải về. Ví dụ, sau git fetch origin main có thể chạy git merge FETCH_HEAD.
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