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

Nội dung

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 tất merge hoặc rebase an toàn.

Bạn vừa chạy git pull để lấy code mới, nhưng terminal báo CONFLICT và repo đứng im? Đừng vội chạy reset --hard hay copy file từ nơi khác đè lên nhé. Đây là lúc cần xử lý chậm một chút để không mất code của mình hoặc của người trong team.

Trong bài này, mình sẽ hướng dẫn bạn xử lý git pull bị conflict theo quy trình an toàn: kiểm tra trạng thái, đọc conflict markers, sửa file, test lại, rồi hoàn tất merge hoặc rebase. Bài này thuộc series xử lý lỗi Git của VietnamTutor và là bài đi sâu từ pillar Lỗi Git thường gặp.

Git pull bị conflict và cách xử lý an toàn
Git pull bị conflict cần được xử lý theo đúng trạng thái merge hoặc rebase.

Tóm tắt nhanh

  • Git pull bị conflict nghĩa là Git đã tải code mới nhưng không tự hợp nhất được một số thay đổi.
  • Việc đầu tiên cần làm là chạy git status, không chạy lệnh xóa thay đổi ngay.
  • Nếu pull theo merge, sau khi sửa file bạn dùng git add rồi git commit.
  • Nếu pull theo rebase, sau khi sửa file bạn dùng git add rồi git rebase --continue.
  • Nếu chưa chắc cách sửa, dùng git merge --abort hoặc git rebase --abort để quay lại trạng thái trước đó.

Git pull bị conflict là gì?

Git pull bị conflict là tình huống Git đã fetch code từ remote, nhưng không thể tự động hợp nhất thay đổi đó vào branch hiện tại. Thường là vì cùng một dòng, cùng một file, hoặc cùng một phần logic đã bị sửa khác nhau ở local và remote.

Về bản chất, git pull không phải một lệnh đơn giản. Theo tài liệu Git, git pull sẽ lấy dữ liệu từ remote rồi tích hợp vào branch hiện tại bằng merge hoặc rebase, tùy cấu hình của bạn [1]. Vì vậy conflict sau khi pull có thể là conflict của merge hoặc conflict của rebase.

Bạn có thể thấy thông báo kiểu này:

git pull origin main
# Git tải code mới từ remote
Auto-merging app/config/payment.php
CONFLICT (content): Merge conflict in app/config/payment.php
Automatic merge failed; fix conflicts and then commit the result.

Thông báo này không có nghĩa là code đã mất. Git chỉ cần bạn quyết định phiên bản cuối cùng của file.

Trạng thái git pull bị conflict khi local và remote cùng sửa một file
Conflict xuất hiện khi Git không tự quyết định được phiên bản cuối cùng của file.

Vì sao chạy git pull lại sinh conflict?

Conflict thường xuất hiện khi local và remote cùng thay đổi một vùng code theo hai hướng khác nhau. Git có thể tự merge nhiều thay đổi độc lập, nhưng với thay đổi đè lên nhau thì Git dừng lại để bạn chọn.

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

  • Bạn sửa cùng một dòng với đồng nghiệp trên branch main.
  • Bạn sửa file cấu hình, remote cũng vừa đổi cấu hình đó.
  • Một người đổi tên file, người khác sửa nội dung file cũ.
  • File lock như package-lock.json hoặc yarn.lock thay đổi ở nhiều branch.
  • Bạn để local thay đổi quá lâu rồi mới pull.

Ví dụ thực tế: bạn đang sửa phí vận chuyển trong file payment.php. Trong lúc đó, người khác thêm logic mã giảm giá vào đúng đoạn thanh toán. Khi bạn pull, Git không biết kết quả cuối nên ghép thế nào.

GitHub Docs cũng mô tả conflict như tình huống có thay đổi cạnh tranh giữa các branch; với conflict trên command line, bạn cần resolve local trước khi có thể push tiếp [2].

Cần kiểm tra gì trước khi sửa conflict?

Trước khi sửa file, bạn cần biết repo đang ở trạng thái merge hay rebase, file nào đang conflict, và local còn thay đổi nào chưa được xử lý. Lệnh quan trọng nhất lúc này là git status.

git status
# Xem branch hiện tại, file conflict và hướng dẫn Git đề xuất

Tài liệu Git cho biết git status hiển thị trạng thái working tree, index, file staged, unstaged, untracked và cả các đường dẫn đang unmerged [3]. Khi đang conflict, output có thể như sau:

On branch feature/checkout
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   app/config/payment.php

Ba dòng quan trọng cần đọc là:

  • fix conflicts and run "git commit": bạn đang ở flow merge.
  • use "git merge --abort": có thể hủy merge nếu chưa muốn sửa.
  • both modified: cả hai phía cùng sửa file.

Nếu output nói rebase in progress, flow sẽ khác một chút. Lúc đó bạn không commit merge trực tiếp, mà dùng git rebase --continue sau khi sửa file.

Kiểm tra git status trước khi xử lý git pull bị conflict
Luôn đọc git status trước khi sửa conflict để biết đúng flow.

Cách xử lý git pull conflict từng bước

Quy trình an toàn là mở file conflict, xóa conflict markers sau khi đã chọn logic đúng, stage file, test lại, rồi hoàn tất merge hoặc rebase. Đừng chỉ xóa markers cho sạch nếu bạn chưa hiểu hai phía đang thay đổi gì.

Bước 1: Mở file bị conflict

Trong file, Git sẽ chèn markers giống như sau:

<<<<<<< HEAD
$shippingFee = 25000;
=======
$discountCode = $request->input('discount_code');
$shippingFee = 30000;
>>>>>>> origin/main

Phần trên HEAD là code ở branch hiện tại của bạn. Phần dưới origin/main là code từ remote vừa được pull về.

Bước 2: Quyết định kết quả cuối cùng

Ở ví dụ trên, chọn một phía là chưa đủ. Bạn cần giữ cả logic mã giảm giá và phí vận chuyển đúng:

$discountCode = $request->input('discount_code');
$shippingFee = 25000;

Sau khi sửa xong, file không được còn các chuỗi <<<<<<<, =======, >>>>>>>. Mình khuyên bạn search nhanh trong repo để tránh commit sót markers.

grep -R "<<<<<<<" .
# Nếu không có output, thường là không còn conflict marker

Bước 3: Stage file đã sửa

git add app/config/payment.php
git status
# Kiểm tra file đã chuyển khỏi nhóm unmerged paths chưa

Khi bạn chạy git add, bạn đang nói với Git rằng conflict của file đó đã được resolve. Nếu còn nhiều file conflict, xử lý từng file một. Đừng add toàn bộ bằng git add . khi bạn chưa xem hết file thay đổi.

Bước 4: Test lại trước khi commit

Đây là bước nhiều người bỏ qua. Với project web, tối thiểu bạn nên chạy test hoặc lệnh kiểm tra phù hợp:

# Ví dụ dự án Node.js
npm test

# Ví dụ dự án PHP/Laravel
php artisan test

Nếu conflict nằm trong file lock như package-lock.json, cách an toàn thường là kiểm tra lại dependency, install lại, rồi chạy test. Với các workflow deploy như Docker hoặc Kubernetes, bạn cũng nên kiểm tra file cấu hình liên quan; có thể xem thêm bài Docker Compose best practices nếu conflict nằm trong cấu hình môi trường.

Bước 5: Hoàn tất merge

git commit
# Git thường tự tạo message merge, bạn có thể giữ hoặc chỉnh cho rõ

Sau đó kiểm tra lại lịch sử:

git log --oneline --graph --decorate -5
git status

Nếu git status báo working tree clean, bạn đã xử lý xong conflict. Cùng xem một case thực tế để dễ hình dung hơn.

Quy trình xử lý git pull bị conflict từ kiểm tra đến commit
Quy trình xử lý conflict nên đi từ kiểm tra đến test, không nhảy thẳng vào reset.

User case thực tế: hai người cùng sửa file cấu hình

Trong dự án thật, conflict hiếm khi chỉ là chọn dòng A hoặc dòng B; thường bạn phải ghép đúng ý định của cả hai thay đổi. Đây là ví dụ gần với tình huống team website hay gặp.

Giả sử bạn đang làm website bán hàng. Bạn phụ trách giảm phí ship cho đơn hàng nội thành. Đồng nghiệp phụ trách thêm mã giảm giá. Cả hai cùng sửa file checkout.php.

Code local của bạn:

// Tính phí ship nội thành
$shippingFee = $customer->city === 'HCM' ? 15000 : 30000;
$total = $subtotal + $shippingFee;

Code từ remote:

// Áp dụng mã giảm giá
$discount = CouponService::calculate($request->coupon, $subtotal);
$total = $subtotal - $discount + $shippingFee;

Nếu bạn chỉ chọn code của mình, tính năng mã giảm giá mất. Nếu chỉ chọn remote, phí ship nội thành mất. Kết quả đúng có thể là:

// Tính phí ship nội thành
$shippingFee = $customer->city === 'HCM' ? 15000 : 30000;

// Áp dụng mã giảm giá
$discount = CouponService::calculate($request->coupon, $subtotal);

// Tổng tiền cuối cùng
$total = $subtotal - $discount + $shippingFee;

Đây là lý do mình không khuyên bạn dùng máy móc --ours hoặc --theirs cho conflict nghiệp vụ. Hai option đó có ích khi bạn chắc chắn muốn lấy một phía, nhưng với logic thanh toán, migration database, file cấu hình production hoặc file bảo mật, bạn cần đọc và test.

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 workflow thực chiến để developer xử lý lỗi trong dự án thật, không chỉ học lệnh rời rạc.

Nếu git pull –rebase bị conflict thì làm sao?

Nếu conflict xảy ra khi pull rebase, bạn vẫn sửa file và chạy git add, nhưng bước cuối là git rebase --continue thay vì git commit. Đây là khác biệt dễ nhầm nhất.

Ví dụ:

git pull --rebase origin main
Auto-merging app/config/payment.php
CONFLICT (content): Merge conflict in app/config/payment.php
error: could not apply 2a3b4c5... Update checkout fee

Flow xử lý:

# 1. Xem file đang conflict
git status

# 2. Sửa file trong editor

# 3. Đánh dấu đã resolve
git add app/config/payment.php

# 4. Tiếp tục rebase
git rebase --continue

Nếu commit tiếp theo lại conflict, Git sẽ dừng tiếp. Bạn lặp lại quy trình: sửa file, git add, rồi git rebase --continue.

Tài liệu Git cảnh báo rebase là chế độ có thể rewrite history, vì vậy không nên dùng bừa với history đã publish nếu bạn chưa hiểu tác động [1]. Với branch cá nhân trước khi tạo pull request, rebase thường ổn. Với branch chung nhiều người cùng push, hãy thống nhất convention trong team.

So sánh xử lý conflict khi git pull merge và git pull rebase
Pull merge và pull rebase giống nhau ở bước sửa file, khác nhau ở bước hoàn tất.

Khi nào nên abort thay vì cố sửa?

Bạn nên abort khi chưa hiểu conflict, khi file liên quan tới thanh toán/bảo mật/dữ liệu, hoặc khi local còn nhiều thay đổi chưa commit khiến trạng thái khó kiểm soát. Abort không phải thất bại; đó là cách quay lại điểm an toàn.

Nếu đang merge conflict:

git merge --abort

Nếu đang rebase conflict:

git rebase --abort

Tài liệu Git merge nói git merge --abort có thể dùng để dừng merge và cố gắng khôi phục trạng thái trước merge; tài liệu cũng cảnh báo merge khi có thay đổi local phức tạp có thể khiến việc quay lại khó hơn [4]. Vì vậy trước khi pull, nếu bạn đang sửa dở nhiều file, hãy commit tạm hoặc stash trước.

Ví dụ an toàn trước khi pull:

git status

# Nếu đang sửa dở nhiều file, stash trước
git stash push -m "WIP before pulling main"

# Sau đó mới pull
git pull origin main

Nếu bạn đang làm trong môi trường nhiều branch hoặc deploy phức tạp, nên ghi lại branch hiện tại trước khi sửa:

git branch --show-current
git log --oneline -3

Hai dòng này giúp bạn biết mình đang đứng ở đâu nếu cần nhờ người khác review.

Cách tránh git pull conflict lần sau

Không thể tránh conflict 100%, nhưng bạn có thể giảm đáng kể bằng cách pull thường xuyên, chia commit nhỏ, tránh cùng sửa một file lớn và thống nhất workflow merge/rebase trong team. Conflict ít hơn nghĩa là review nhanh hơn, deploy đỡ rủi ro hơn.

Mình khuyên bạn áp dụng checklist này:

  • Pull trước khi bắt đầu làm task mới.
  • Commit nhỏ theo từng ý nghĩa, không gom quá nhiều thay đổi vào một commit.
  • Không để branch cá nhân lệch quá lâu so với main.
  • Trao đổi trước khi sửa file nhạy cảm như payment, auth, migration, config production.
  • Với file lock, thống nhất cách regenerate thay vì sửa tay từng dòng.
  • Trước khi merge pull request, chạy test và đọc lại diff.

Nếu bạn đang học Git theo lỗi thực tế, hãy lưu bài pillar Lỗi Git thường gặp. Series này sẽ đi từng lỗi một: conflict, commit nhầm, push rejected, detached HEAD, rebase lỗi và các tình huống recovery.

Checklist tránh git pull bị conflict trong workflow làm việc nhóm
Conflict ít hơn khi team thống nhất workflow và chia thay đổi nhỏ hơn.

Nguồn tham khảo

  1. Git Documentation: git-pull
  2. GitHub Docs: Resolving a merge conflict using the command line
  3. Git Documentation: git-status
  4. Git Documentation: git-merge
  5. Git Documentation: git-diff

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

Git pull bị conflict có làm mất code không?

Thông thường là không. Git chỉ dừng lại vì không tự merge được. Code local và code remote vẫn nằm trong file conflict markers để bạn chọn hoặc ghép lại.

Sau khi sửa conflict xong cần chạy lệnh gì?

Nếu đang pull merge, bạn chạy git add với file đã sửa rồi git commit. Nếu đang pull rebase, bạn chạy git add rồi git rebase –continue.

Có nên dùng git merge –abort khi bị conflict không?

Có, nếu bạn chưa hiểu conflict hoặc cần quay lại trạng thái trước khi pull. Nhưng nếu trước đó working tree có nhiều thay đổi phức tạp, hãy đọc kỹ git status và backup trước.

Nên chọn ours hay theirs khi xử lý conflict?

Chỉ nên chọn ours hoặc theirs khi bạn chắc chắn muốn lấy toàn bộ một phía. Với logic nghiệp vụ như thanh toán, phân quyền, migration hoặc cấu hình production, nên đọc và ghép thủ công.

Git pull –rebase bị conflict khác gì pull thường?

Bước sửa file giống nhau, nhưng bước hoàn tất khác nhau. Pull thường dùng git commit sau khi git add. Pull rebase dùng git rebase –continue sau khi git add.

Làm sao biết còn conflict markers trong code?

Bạn có thể search các chuỗi <<<<<<<, ======= hoặc >>>>>>> trong repo. Nếu còn marker, không nên commit.

Tóm lại: Khi git pull bị conflict, việc đúng không phải là hoảng hay reset ngay. Hãy đọc git status, hiểu file nào đang conflict, sửa logic cuối cùng, test lại rồi mới commit hoặc rebase continue. Nếu chưa chắc, abort để quay lại điểm an toàn.

Bạn từng gặp conflict trong file nào khó xử lý nhất: file cấu hình, file lock hay logic nghiệp vụ? Để lại comment nhé, mình sẽ dùng các tình huống thực tế đó để viết tiếp các bài trong series Git.

Tú Anh

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

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

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.

Bảo mật Laravel: 10 lỗi phổ biến và cách phòng tránh

Hướng dẫn 10 lỗi bảo mật phổ biến nhất trong Laravel và cách phòng tránh hiệu quả. Từ XSS, SQL injection đến authentication vulnerabilities.

Migration PHP Attributes Laravel 13: Hướng Dẫn Chi Tiết

Cách chuyển đổi từ protected properties sang PHP Attributes trong Laravel 13 với hướng dẫn từng bước và code examples chi tiết.

Laravel 13 có gì mới? Các thay đổi cần biết

Laravel 13 ra mắt ngày 17/3/2026 với PHP 8.3, PHP Attributes, AI SDK và nhiều cải tiến. Khám phá chi tiết các tính năng mới của

Kubernetes cho người mới: Hướng dẫn nhập môn

Kubernetes (K8s) là nền tảng container orchestration phổ biến nhất hiện nay. Bài hướng dẫn này sẽ giúp bạn hiểu Kubernetes là gì, kiến trúc cơ

Docker Compose best practices: 10 cách cấu hình tốt hơn

Docker Compose giúp bạn quản lý multi-container applications dễ dàng hơn. Bài viết này tổng hợp 10 best practices quan trọng nhất để sử dụng Docker

Lỗ hổng RCE (CVE-2025-55182) trên React, Next.js?

Cảnh báo khẩn cấp: React2Shell (CVE-2025-55182) gây RCE nghiêm trọng cho React/Next.js. Nắm cơ chế, dấu hiệu & phòng thủ cấp bách để bảo vệ ứng

Lập trình viên: Xây doanh nghiệp một người, kiếm 10.000 USD/tháng

Lập trình viên: Khám phá khung làm việc để xây dựng doanh nghiệp một người, kiếm 10.000 USD/tháng. Biến kỹ năng code thành cỗ máy tiền,

Dead-Letter Queue (DLQ) là gì? Cách hoạt động và code mẫu

Dead-Letter Queue giúp cô lập tin nhắn lỗi khỏi luồng chính. Tìm hiểu cách DLQ hoạt động, retry, redrive và triển khai với Amazon SQS, RabbitMQ.