Hợp nhất hoặc merge — là hành động kết hợp hai nhánh trong Git, gộp các thay đổi từ nhánh này sang nhánh khác. Trong phát triển hiện đại, merge là cách tiêu chuẩn để tích hợp nhánh tính năng vào nhánh chính của dự án. Theo GitHub Octoverse 2024, hơn 15 triệu merge được thực hiện mỗi ngày. Merge là cơ chế chính của công việc cộng tác, cho phép kết hợp nỗ lực của nhiều nhà phát triển thành một sản phẩm duy nhất.
Các Điểm Chính
Merge trong Git là thao tác kết hợp hai hoặc nhiều lịch sử phát triển thành một. Khi một nhà phát triển hợp nhất một nhánh, Git tự động tìm tổ tiên chung (base commit) và tạo một merge commit mới bao gồm các thay đổi từ cả hai nhánh. Three-way merge là thuật toán tiêu chuẩn so sánh ba trạng thái: tổ tiên chung, nhánh thứ nhất và nhánh thứ hai.
Quá trình merge bắt đầu với lệnh git merge. Git xác định điểm phân nhánh và áp dụng tuần tự các thay đổi từ nhánh nguồn lên nhánh đích. Nếu các thay đổi không xung đột, Git thực hiện fast-forward hoặc tạo merge commit tùy theo cài đặt. Fast-forward là tình huống nhánh đích chỉ đơn giản di chuyển đến các commit của nhánh nguồn.
# Chuyển sang nhánh đích và hợp nhất
git checkout main
git merge feature/payment-module
# Hợp nhất với no-fast-forward rõ ràng
git merge --no-ff feature/payment-module
# Hủy merge nếu xung đột quá phức tạp
git merge --abort
Cờ --no-ff (no fast-forward) buộc tạo merge commit ngay cả khi fast-forward có thể thực hiện. Điều này bảo tồn thông tin rằng các thay đổi được thực hiện trong một nhánh riêng biệt. Nhiều nhóm ưa chuộng cách tiếp cận này để duy trì lịch sử phân nhánh một cách rõ ràng.
Git có ba chiến lược hợp nhất nhánh chính, mỗi chiến lược phù hợp với một tình huống cụ thể. Việc chọn chiến lược phụ thuộc vào văn hóa nhóm và yêu cầu về sự sạch sẽ của lịch sử dự án.
| Chiến lược | Kết quả | Khi nào dùng |
|---|---|---|
| Standard merge | merge commit + toàn bộ lịch sử | nhóm coi trọng lịch sử đầy đủ |
| Squash merge | một commit, lịch sử nén lại | nhánh tính năng có nhiều commit nhỏ |
| Rebase merge | lịch sử tuyến tính, không merge commit | nhánh tính năng cá nhân, trước khi tạo PR |
Standard merge tạo merge commit với hai cha. Toàn bộ lịch sử được bảo tồn, nhưng đồ thị phân nhánh trở nên phức tạp hơn. Squash merge kết hợp tất cả các commit của nhánh tính năng thành một và áp dụng lên nhánh đích — lịch sử trở nên tuyến tính và sạch sẽ, nhưng thông tin về các giai đoạn trung gian bị mất.
Rebase, mặc dù không phải là merge hoàn chỉnh, đạt được kết quả tương tự — các thay đổi từ một nhánh được di chuyển lên nhánh khác. Sự khác biệt là lịch sử được viết lại: các commit của nhánh tính năng được tạo lại trên commit mới nhất của nhánh đích. Điều này tạo ra lịch sử hoàn toàn tuyến tính, nhưng yêu cầu force push khi đẩy lên.
Xung đột merge phát sinh khi cùng một dòng của tệp được thay đổi ở hai nhánh. Git không thể tự động xác định phiên bản nào cần giữ và cần sự can thiệp của nhà phát triển. Xung đột được hiển thị trong tệp bằng các marker đặc biệt: <<<<<<<, =======, >>>>>>>.
Quá trình giải quyết xung đột bao gồm nhiều bước. Đầu tiên, nhà phát triển mở tệp xung đột và chọn thủ công các thay đổi cần thiết. Điều quan trọng là không chỉ chọn một phiên bản, mà phải hiểu logic của cả hai thay đổi và đưa ra quyết định đúng đắn. Sau khi chỉnh sửa tệp, các marker xung đột được loại bỏ và các thay đổi được thêm vào staging area qua git add.
# Xem danh sách tệp xung đột
git status
# Khởi động mergetool (ví dụ: VS Code, IntelliJ)
git mergetool
# Sau khi giải quyết tất cả xung đột
git add .
git merge --continue
# Hoặc hủy bỏ merge hoàn toàn
git merge --abort
Sử dụng công cụ merge trực quan tăng tốc đáng kể việc giải quyết xung đột. VS Code, IntelliJ IDEA và GitKraken cung cấp giao diện với ba bảng: nhánh hiện tại, nhánh đến và kết quả. Công cụ git mergetool tự động mở trình soạn thảo đã cấu hình cho mỗi tệp xung đột.
Cách tốt nhất để tránh xung đột phức tạp là đồng bộ hóa thường xuyên nhánh tính năng với nhánh chính. Nếu nhà phát triển merge main vào nhánh của mình mỗi ngày một lần, các xung đột sẽ nhỏ và dễ giải quyết. Tích lũy thay đổi trong một tuần đảm bảo xung đột phức tạp với rủi ro lỗi cao.
Rebase và merge là hai cách kết hợp thay đổi, và việc lựa chọn giữa chúng thường gây tranh luận trong nhóm. Rebase di chuyển các commit từ nhánh này lên nhánh khác, viết lại lịch sử. Merge tạo một merge commit mới, bảo tồn lịch sử phân nhánh. Mỗi cách tiếp cận đều có ưu điểm và hạn chế.
Rebase phù hợp khi nhà phát triển đang làm việc trên nhánh tính năng cục bộ và muốn có lịch sử tuyến tính sạch sẽ trước khi tạo Pull Request. Sau rebase, tất cả các commit được sắp xếp tuần tự mà không có merge commit không cần thiết. Tuy nhiên, rebase yêu cầu force push và không áp dụng được cho các nhánh có nhiều người cùng làm việc.
Quy tắc vàng của Git: không sử dụng rebase trên các commit đã được đẩy lên kho lưu trữ chung. Điều này đảm bảo lịch sử trong nhánh chung không thay đổi và các nhà phát triển khác sẽ không gặp commit trùng lặp hoặc bị mất. Để tích hợp nhánh tính năng vào nhánh chính, hãy sử dụng merge qua Pull Request.
Quy trình merge đúng đắn là nền tảng của phát triển ổn định. Trong công việc nhóm hiện đại, việc hợp nhất không được thực hiện qua console, mà qua Pull Request trên GitHub hoặc Merge Request trong GitLab. Một PR trải qua đánh giá mã, kiểm tra CI tự động và chỉ sau đó mới được hợp nhất vào nhánh chính.
Thực hành đầu tiên — chỉ hợp nhất sau khi tất cả các kiểm tra đã qua. Pipeline CI phải xây dựng dự án, chạy kiểm thử và xác minh chất lượng mã. Nếu ít nhất một kiểm tra thất bại, merge bị chặn. Các nền tảng hiện đại (GitHub, GitLab) có bảo vệ tích hợp: branch protection rules tự động chặn merge nếu CI thất bại.
Thực hành thứ hai — không bao giờ hợp nhất mã hỏng. Trước khi merge, nhà phát triển phải đảm bảo các thay đổi của mình không làm hỏng bản dựng hoặc làm suy giảm chức năng hiện có. Kiểm thử tự động và đánh giá mã phục vụ cho mục đích này.
Thực hành thứ ba — dọn dẹp nhánh tính năng sau khi hợp nhất. Một nhánh đã được hợp nhất cần được xóa. Điều này ngăn ngừa nhầm lẫn và sự lộn xộn trong kho lưu trữ. GitHub tự động đề xuất xóa nhánh sau khi merge PR và cài đặt kho lưu trữ có thể được cấu hình để tự động xóa.
Câu Hỏi Thường Gặp
Merge là sự kết hợp hai nhánh Git thành một. Các thay đổi từ một nhánh được chuyển sang nhánh khác thông qua hợp nhất ba chiều (three-way merge). Kết quả được ghi lại trong một merge commit mới có hai commit cha. Merge commit bảo tồn thông tin về những nhánh nào đã được hợp nhất.
Merge tạo một merge commit mới, bảo tồn lịch sử phân nhánh. Rebase viết lại lịch sử bằng cách di chuyển commit lên nhánh khác mà không tạo merge commit. Rebase tạo lịch sử tuyến tính nhưng yêu cầu force push. Merge an toàn hơn cho nhánh chia sẻ, rebase tốt hơn cho nhánh cá nhân.
Mở tệp xung đột, tìm các marker <<<<<<<, ======= và >>>>>>>, chọn các thay đổi cần thiết và xóa marker. Thêm tệp qua git add và hoàn tất merge bằng git merge --continue. Sử dụng git mergetool để giải quyết trực quan trong VS Code hoặc IntelliJ IDEA.
Pull Request (hoặc Merge Request) là bắt buộc khi hợp nhất nhánh tính năng vào nhánh chính của dự án. Một PR trải qua đánh giá mã của đồng nghiệp và kiểm tra CI tự động. Đây là tiêu chuẩn của phát triển hiện đại. Push trực tiếp vào nhánh chính bị cấm trong hầu hết các dự án.
Squash merge kết hợp tất cả các commit của nhánh tính năng thành một trước khi hợp nhất. Điều này tạo ra lịch sử sạch sẽ của nhánh chính không có commit nháp trung gian. Sử dụng squash merge khi nhánh tính năng chứa nhiều commit tiện ích (wip, fixes) và không cần bảo tồn tất cả các bước trung gian trong lịch sử.
Tổng Kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm