Đối với bất kỳ lập trình viên nào làm việc với Git, có lẽ không có thông báo nào gây "đứng hình" hơn dòng chữ: CONFLICT (content): Merge conflict in [tên-file]
. Đó là khoảnh khắc mà cỗ máy Git, vốn dĩ rất thông minh, phải dừng lại và nói: "Này, tôi không biết phải làm gì với hai phiên bản khác nhau của cùng một đoạn code này. Bạn quyết định đi!".
Merge conflict (xung đột khi hợp nhất) không phải là lỗi, mà là một phần tự nhiên và tất yếu của quy trình làm việc nhóm. Nó là dấu hiệu cho thấy team của bạn đang tích cực đóng góp và phát triển sản phẩm. Tuy nhiên, nếu không hiểu rõ bản chất và cách xử lý, nó có thể trở thành một cơn ác mộng, làm chậm tiến độ và gây ra những lỗi không đáng có.
Vì vậy, bài viết này được tạo ra để giúp bạn biến nỗi sợ hãi mang tên "merge conflict" thành một kỹ năng được xử lý một cách chuyên nghiệp và tự tin.
Merge Conflict là gì và tại sao nó xảy ra?
Hãy tưởng tượng bạn và một đồng nghiệp cùng được giao nhiệm vụ sửa một tài liệu văn bản.
- Bạn sửa câu đầu tiên của đoạn văn thứ hai.
- Đồng nghiệp của bạn cũng sửa chính xác câu đầu tiên của đoạn văn thứ hai, nhưng theo một cách khác.
Khi cả hai cùng nộp lại bản sửa đổi, người quản lý sẽ bối rối. Họ không biết nên chọn phiên bản của bạn, của đồng nghiệp, hay kết hợp cả hai.
Trong Git, tình huống này xảy ra khi:
Git không thể tự động quyết định cách hợp nhất hai nhánh (branch) lại với nhau vì có những thay đổi chồng chéo trên cùng một (hoặc nhiều) dòng code trong cùng một file.
Các nguyên nhân phổ biến nhất bao gồm:
- Hai người cùng chỉnh sửa một dòng code trên hai nhánh khác nhau.
- Một người xóa một file trong khi người khác đang chỉnh sửa nó.
- Cả hai người cùng thêm một file có tên giống hệt nhau nhưng nội dung khác nhau.
Giải thích một "hiện trường" Merge Conflict
Khi conflict xảy ra, Git sẽ không hoàn thành việc merge. Thay vào đó, nó sẽ đánh dấu các file bị xung đột và chèn vào đó những "đánh dấu conflict" (conflict markers) đặc biệt. Nếu bạn mở file bị conflict, bạn sẽ thấy một đoạn code tương tự như sau:
// Đoạn code không bị conflict
function sayHello() {
<<<<<<< HEAD
console.log("Hello, World!");
=======
console.log("Xin chào, Thế giới!");
>>>>>>> feature/add-vietnamese-greeting
}
// Đoạn code khác không bị conflict
Hãy cùng "giải phẫu" các đánh dấu này:
<<<<<<< HEAD
: Đây là điểm bắt đầu của những thay đổi từ nhánh hiện tại của bạn (nhánh bạn đang đứng và thực hiện lệnhgit merge
).HEAD
là con trỏ chỉ đến commit mới nhất của nhánh hiện tại.=======
: Đây là "ranh giới" phân chia hai phiên bản code bị xung đột.>>>>>>> feature/add-vietnamese-greeting
: Đây là điểm kết thúc của những thay đổi đến từ nhánh bạn đang cố gắng merge vào (trong ví dụ này là nhánhfeature/add-vietnamese-greeting
).
Nhiệm vụ của bạn là trở thành "trọng tài": xem xét cả hai phiên bản, quyết định phiên bản cuối cùng sẽ trông như thế nào, và sau đó xóa bỏ toàn bộ các đánh dấu đặc biệt này.
Quy trình xử lý Merge Conflict chuẩn 4 bước (Command Line)
Khi đối mặt với conflict, đừng hoảng sợ. Hãy bình tĩnh làm theo quy trình sau:
Bước 1: Xác định các file bị Conflict
Sau khi thực hiện git merge
hoặc git pull
và nhận được thông báo lỗi, lệnh đầu tiên bạn nên gõ là:
git status
Git sẽ liệt kê rõ ràng các file đang ở trạng thái unmerged paths
. Đây chính là danh sách những "bệnh nhân" bạn cần chữa trị.
On branch main
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: index.js
Bước 2: Mở file và ra quyết định
Mở từng file bị conflict bằng trình soạn thảo code (code editor) yêu thích của bạn. Bây giờ là lúc bạn phải đưa ra quyết định dựa trên logic và sự trao đổi với đồng nghiệp (nếu cần):
- Giữ lại phiên bản của bạn (
HEAD
): Xóa mọi thứ từ=======
đến>>>>>>> ...
. - Chấp nhận phiên bản của nhánh kia: Xóa mọi thứ từ
<<<<<<< HEAD
đến=======
. - Kết hợp cả hai: Đây là trường hợp phổ biến nhất. Bạn sẽ phải chỉnh sửa thủ công, chọn lọc những phần code hợp lý từ cả hai phiên bản và kết hợp chúng thành một đoạn code hoàn chỉnh, chạy đúng logic.
Ví dụ, sau khi giải quyết, file index.js
có thể trông như thế này:
// Giả sử chúng ta quyết định giữ lại cả hai và hiển thị chúng
function sayHello() {
console.log('Hello, World!')
console.log('Xin chào, Thế giới!')
}
Quan trọng: Sau khi chỉnh sửa, hãy đảm bảo bạn đã xóa toàn bộ các dòng đánh dấu <<<<<<<
, =======
, và >>>>>>>
.
Bước 3: Đánh dấu Conflict đã được giải quyết
Sau khi đã dọn dẹp xong một file, bạn cần báo cho Git biết rằng bạn đã xử lý xong nó.
git add [tên-file-đã-sửa]
# Ví dụ:
git add index.js
Lệnh git add
trong ngữ cảnh này không phải để thêm file mới, mà là để "đánh dấu" file đó đã được giải quyết xong và sẵn sàng cho commit.
Bước 4: Hoàn tất quá trình Merge
Khi bạn đã git add
tất cả các file bị conflict, hãy kiểm tra lại bằng git status
. Bạn sẽ thấy các file đó nằm trong mục "Changes to be committed".
Bây giờ, chỉ cần tạo một commit để hoàn tất quá trình merge:
git commit
Khi bạn chạy lệnh này, Git sẽ tự động mở một trình soạn thảo văn bản với một thông điệp commit mặc định, thường là Merge branch '[tên-nhánh]'
. Bạn có thể giữ nguyên thông điệp này hoặc chỉnh sửa nếu muốn. Lưu và đóng file đó lại là xong!
Vậy là bạn đã thành công xử lý merge conflict.
💡 Lối thoát an toàn: Nếu bạn cảm thấy quá rối và muốn quay lại trạng thái trước khi merge, hãy dùng lệnh "thần kỳ" này:
git merge --abort
Sử dụng công cụ đồ họa (GUI) - Cách dễ dàng hơn
Hầu hết các lập trình viên hiện đại đều sử dụng các công cụ có giao diện đồ họa, và chúng làm cho việc xử lý conflict trở nên trực quan và dễ dàng hơn rất nhiều.
VS Code - Người bạn đồng hành đắc lực
VS Code là một trong những công cụ hỗ trợ xử lý conflict tốt nhất. Khi bạn mở một file bị conflict, nó sẽ tự động nhận diện và cung cấp các lựa chọn ngay trên đầu đoạn code xung đột:
- Accept Current Change: Giữ lại phiên bản của bạn (
HEAD
). - Accept Incoming Change: Giữ lại phiên bản từ nhánh kia.
- Accept Both Changes: Giữ lại cả hai phiên bản, cái này nối tiếp cái kia.
- Compare Changes: Mở một giao diện so sánh song song (side-by-side) cực kỳ trực quan, giúp bạn dễ dàng đưa ra quyết định.
Sau khi click chọn, VS Code sẽ tự động dọn dẹp các conflict marker. Việc của bạn chỉ là lưu file lại, sau đó vào mục Source Control (biểu tượng nhánh cây), stage
(tương đương git add
) và commit
.
Các công cụ khác
Các công cụ như GitKraken, Sourcetree, GitHub Desktop cũng cung cấp những giao diện "merge tool" mạnh mẽ, cho phép bạn so sánh và chọn lựa từng dòng code một cách trực quan.
Bí kíp phòng tránh Merge Conflict
Xử lý conflict là một kỹ năng, nhưng phòng tránh nó còn là một nghệ thuật.
- Giao tiếp thường xuyên: Luôn nói chuyện với team về những gì bạn đang làm, đặc biệt khi bạn sắp sửa "đụng" vào những file cốt lõi, dùng chung.
- Pull thường xuyên: Trước khi bắt đầu code một tính năng mới hoặc đẩy code lên, hãy luôn
git pull
(hoặcgit pull --rebase
) từ nhánh chính (nhưmain
hoặcdevelop
) để cập nhật những thay đổi mới nhất. - Giữ cho các nhánh sống ngắn và nhỏ: Đừng tạo ra một nhánh tính năng và "om" nó trong nhiều tuần. Các Pull Request (PR) càng nhỏ, càng tập trung vào một vấn đề duy nhất thì càng ít khả năng gây conflict và dễ dàng review hơn.
- Sử dụng
git pull --rebase
: Thay vìgit pull
(tạo merge commit),git pull --rebase
sẽ đặt các commit của bạn lên trên đỉnh của các commit mới nhất từ remote. Điều này giúp lịch sử commit thẳng và sạch hơn, và các conflict (nếu có) sẽ được giải quyết trên từng commit của bạn, dễ quản lý hơn.
Kết luận: Thay đổi tư duy
Thay vì xem merge conflict là một chướng ngại vật, hãy xem nó là một cơ hội để dừng lại, "đồng bộ hóa với đồng đội" và đảm bảo rằng phiên bản code cuối cùng là sự kết hợp tốt nhất từ những nỗ lực của cả nhóm.
Bằng cách hiểu rõ bản chất, nắm vững quy trình xử lý trên cả command line lẫn công cụ đồ họa, và áp dụng các chiến lược phòng tránh thông minh, bạn sẽ không còn phải "toát mồ hôi" mỗi khi Git báo conflict nữa.
Chúc mừng, bạn vừa chinh phục được một trong những kỹ năng quan trọng nhất của một lập trình viên hiện đại!