MN
KHÁM PHÁ VÀ TRẢI NGHIỆM CÁ NHÂN
MN

Mục Lục

1. Chức năng của lệnh Git Merge

Lệnh git merge trong Git được sử dụng để hợp nhất các thay đổi từ một nhánh khác vào nhánh hiện tại. Khi bạn thực hiện một git merge, Git sẽ kết hợp các thay đổi từ nhánh được chỉ định vào nhánh hiện tại, tạo ra một commit hợp nhất nếu cần thiết.

1.1. Các trường hợp sử dụng phổ biến:

  1. Hợp nhất nhánh phát triển vào nhánh chính:
    • Khi bạn phát triển một tính năng mới trên một nhánh phụ, sau khi hoàn thành, bạn sẽ sử dụng git merge để hợp nhất nhánh này vào nhánh chính (thường là main hoặc master).
  2. Hợp nhất nhánh mà không có xung đột:
    • Nếu các thay đổi trên cả hai nhánh không xung đột với nhau, Git sẽ tự động hợp nhất chúng và tạo ra một commit hợp nhất (merge commit).
  3. Hợp nhất nhánh có xung đột:
    • Nếu có xung đột giữa các thay đổi trên hai nhánh, Git sẽ dừng quá trình hợp nhất và yêu cầu bạn giải quyết xung đột thủ công. Sau khi xung đột được giải quyết, bạn sẽ hoàn tất quá trình bằng cách tạo một commit hợp nhất.

1.2. Các lệnh và tuỳ chọn phổ biến:

  • git merge <branch-name>: Hợp nhất nhánh <branch-name> vào nhánh hiện tại.
  • git merge --no-ff <branch-name>: Buộc tạo một merge commit ngay cả khi hợp nhất có thể được thực hiện bằng cách fast-forward.
  • git merge --abort: Nếu bạn muốn hủy bỏ một merge đang thực hiện (khi gặp xung đột chẳng hạn), lệnh này sẽ quay lại trạng thái trước khi merge bắt đầu.

Lệnh git merge giúp duy trì lịch sử phát triển của dự án rõ ràng và cho phép tích hợp các thay đổi từ các nhánh khác nhau một cách có tổ chức.

2. Lịch sử lệnh Git Merge

Lịch sử của lệnh git merge trong Git phản ánh quá trình phát triển của Git từ khi nó ra đời vào năm 2005 bởi Linus Torvalds. git merge là một trong những lệnh quan trọng nhất trong Git, được sử dụng để kết hợp các thay đổi từ các nhánh khác nhau trong một dự án.

2.1. Các cột mốc chính trong lịch sử của git merge:

  1. Git 1.0 (2005):
    • Lệnh git merge được giới thiệu từ phiên bản đầu tiên của Git. Ban đầu, nó được thiết kế để hợp nhất các nhánh trong một dự án mà không làm mất lịch sử các thay đổi.
  2. Merge Commit:
    • Từ những ngày đầu, lệnh git merge đã tạo ra một “merge commit” để ghi lại việc hợp nhất các thay đổi từ hai hoặc nhiều nhánh. Merge commit giữ lại lịch sử của cả hai nhánh, giúp theo dõi nguồn gốc của các thay đổi.
  3. Fast-Forward Merge:
    • Khi hợp nhất một nhánh vào nhánh khác mà không có thay đổi nào sau điểm chung cuối cùng, Git sẽ thực hiện một “fast-forward” merge. Điều này giúp tránh tạo ra một merge commit không cần thiết.
  4. –no-ff (No Fast-Forward) Option:
    • Phiên bản Git 1.6.5 (2009) giới thiệu tùy chọn --no-ff, cho phép người dùng buộc tạo một merge commit ngay cả khi có thể fast-forward. Điều này giúp duy trì lịch sử rõ ràng hơn, đặc biệt khi làm việc với các nhánh tính năng (feature branches).
  5. Conflict Handling:
    • Git từ đầu đã có khả năng phát hiện xung đột trong quá trình hợp nhất. Khi có xung đột, Git dừng quá trình merge và yêu cầu người dùng giải quyết chúng thủ công. Khả năng này đã được cải tiến qua các phiên bản, bao gồm các công cụ như git mergetool để hỗ trợ việc giải quyết xung đột.
  6. Git 2.x (2014 trở đi):
    • Nhiều cải tiến đã được thêm vào lệnh git merge, bao gồm khả năng hỗ trợ tốt hơn cho các merge phức tạp, cải thiện hiệu suất, và tích hợp tốt hơn với các công cụ CI/CD hiện đại.
  7. git merge --abort:
    • Giới thiệu trong các phiên bản sau này, lệnh git merge --abort giúp người dùng dễ dàng hủy bỏ một merge đang thực hiện nếu gặp vấn đề.

2.2. Tầm quan trọng của lệnh git merge:

git merge đóng vai trò quan trọng trong việc quản lý và duy trì lịch sử của dự án. Nó giúp các nhóm phát triển có thể làm việc song song trên các nhánh khác nhau, sau đó hợp nhất các thay đổi lại với nhau một cách an toàn và có tổ chức. Qua thời gian, Git và lệnh git merge đã trở thành một phần không thể thiếu trong quy trình làm việc của hầu hết các nhà phát triển phần mềm.

3. Chiến lược hợp nhất Ours/Theirs trong Git Merge cho sự tồn tại của file

Bài toán: Nếu nhánh source-branch có file a1.txt, a2.txt, a3.txt, s.txt. Nhánh target-branch có a1.txt, a2.txt, a3.txt, t.txt. Cách dùng git merge để merge source-branch vào target-branch thoả mãn kết quả có nhánh mới có các file: a1.txt, a2.txt, a3.txt, s.txt và không có t.txt?

Để thực hiện quá trình git merge mà đảm bảo kết quả chỉ có các file từ source-branch (a1.txt, a2.txt, a3.txt, s.txt) và loại bỏ file t.txt có trong target-branch, bạn có thể làm theo các bước sau:

3.1. Tạo một nhánh tạm thời từ target-branch

Bắt đầu bằng việc tạo một nhánh tạm thời từ target-branch để đảm bảo bạn có thể quay lại nếu cần thiết:

git checkout target-branch
git checkout -b temp-branch

3.2. Reset nhánh temp-branch về nhánh source-branch

Sử dụng lệnh git reset --hard để làm cho nhánh temp-branch khớp hoàn toàn với source-branch, giữ nguyên các thay đổi của source-branch:

git reset --hard source-branch

3.3. Thực hiện merge theo chiến lược “ours”

Bây giờ, merge nhánh target-branch vào nhánh temp-branch nhưng chỉ giữ lại các thay đổi từ temp-branch:

git merge -s ours target-branch

3.4. Commit và kết quả

Sau khi hoàn tất, kết quả của nhánh temp-branch sẽ bao gồm tất cả các file từ source-branch và loại bỏ file t.txt từ target-branch.

Bạn cần commit các thay đổi này:

git commit -m "Merged source-branch into temp-branch, keeping only files from source-branch"

3.5. Kiểm tra kết quả và đổi tên nhánh (nếu cần)

Lúc này, bạn có thể kiểm tra các file trong nhánh temp-branch. Nó sẽ chỉ có các file từ source-branch và không có t.txt.

Nếu bạn muốn, bạn có thể đổi tên nhánh temp-branch thành target-branch để tiếp tục làm việc:

git branch -M target-branch

3.6. Đẩy thay đổi lên remote (nếu cần)

Cuối cùng, nếu bạn đang làm việc với một kho lưu trữ từ xa (remote repository), hãy đẩy thay đổi lên:

git push -f origin target-branch

3.7. Tóm tắt quá trình:

  • Mục tiêu: Bạn muốn hợp nhất nhánh source-branch vào target-branch sao cho kết quả chỉ giữ lại các file từ source-branch và loại bỏ các file không có trong source-branch.
  • Kết quả: Sau khi thực hiện các bước trên, nhánh cuối cùng sẽ chỉ có các file a1.txt, a2.txt, a3.txt, và s.txt.

Cách tiếp cận này đảm bảo rằng chỉ các file từ source-branch được giữ lại và loại bỏ t.txt từ target-branch.

4. Chiến lược hợp nhất Ours/Theirs trong Git Merge cho sự khác biệt nội dung file

rong Git, chiến lược hợp nhất “Ours” và “Theirs” được sử dụng để giải quyết xung đột trong quá trình hợp nhất (merge). Cả hai chiến lược này cho phép bạn chọn giữ lại các thay đổi từ một nhánh cụ thể khi có xung đột giữa các nhánh.

4.1. Chiến lược “Ours”

  • Mô tả: Khi sử dụng chiến lược “Ours”, Git sẽ giữ lại tất cả các thay đổi từ nhánh hiện tại của bạn (nhánh mà bạn đang làm việc) và bỏ qua các thay đổi từ nhánh được hợp nhất vào.
  • Cách sử dụng: Chiến lược này thường được sử dụng khi bạn muốn bỏ qua toàn bộ thay đổi từ nhánh kia trong quá trình hợp nhất.
  • Ví dụ:
git merge -s ours feature-branch

Trong ví dụ này, nếu có bất kỳ xung đột nào, Git sẽ giữ lại các thay đổi từ nhánh hiện tại và bỏ qua các thay đổi từ feature-branch.

4.2. Chiến lược “Theirs”

  • Mô tả: Khi sử dụng chiến lược “Theirs”, Git sẽ giữ lại tất cả các thay đổi từ nhánh được hợp nhất vào và bỏ qua các thay đổi từ nhánh hiện tại.
  • Cách sử dụng: Chiến lược này hữu ích khi bạn muốn bỏ qua toàn bộ thay đổi trong nhánh hiện tại và chấp nhận toàn bộ thay đổi từ nhánh được hợp nhất.
  • Ví dụ:
git merge -s theirs feature-branch
  • Chiến lược “Theirs” không được hỗ trợ trực tiếp bằng một tùy chọn của lệnh git merge, nhưng có thể thực hiện bằng cách giải quyết thủ công các xung đột và chọn tất cả các thay đổi từ nhánh kia.

4.3. Lưu ý:

  • Các chiến lược này chủ yếu được sử dụng khi có xung đột (conflict) giữa các thay đổi từ hai nhánh.
  • Khi hợp nhất với -s ours hoặc thủ công với “theirs”, bạn đang chọn không kết hợp các thay đổi từ cả hai nhánh mà thay vào đó chỉ giữ lại thay đổi từ một nhánh cụ thể.

4.4. Tiếng Việt:

  • Ours: Chiến lược “của chúng ta” – giữ lại tất cả các thay đổi từ nhánh hiện tại.
  • Theirs: Chiến lược “của họ” – giữ lại tất cả các thay đổi từ nhánh được hợp nhất vào.

4.5. Khi nào nên sử dụng?

  • Ours: Khi bạn chắc chắn rằng các thay đổi từ nhánh hiện tại là chính xác và bạn không muốn các thay đổi từ nhánh kia can thiệp vào.
  • Theirs: Khi bạn muốn chấp nhận hoàn toàn các thay đổi từ nhánh khác và bỏ qua những thay đổi cũ trên nhánh hiện tại.

5. Chiến lược hợp nhất cần Review khác biệt trong Git Merge

Khi thực hiện git merge và có xung đột trong các file, Git yêu cầu bạn phải xem xét và giải quyết các xung đột đó trước khi hoàn tất việc hợp nhất. Quá trình này được gọi là “three-way merge” hoặc “manual conflict resolution.” Dưới đây là cách chiến lược này hoạt động:

5.1. Three-Way Merge (Hợp nhất ba chiều)

  • Mô tả: Đây là chiến lược mặc định khi Git gặp xung đột trong quá trình hợp nhất. Git sẽ cố gắng tự động hợp nhất các thay đổi từ hai nhánh (nhánh gốc và nhánh được hợp nhất) và sẽ thông báo cho bạn nếu không thể thực hiện việc này mà không gây xung đột.
  • Cách hoạt động: Git sẽ sử dụng “common ancestor” (tổ tiên chung) của hai nhánh làm cơ sở để xác định các thay đổi từ mỗi nhánh. Khi có sự khác biệt, Git sẽ yêu cầu bạn xem xét và quyết định cách giải quyết.
  • Khi có xung đột:
    • Git sẽ đánh dấu các vùng xung đột trong các file có vấn đề, cho phép bạn xem xét từng thay đổi và chọn cách xử lý.
    • Các vùng xung đột thường được đánh dấu bằng các dấu <<<<<<, ======, >>>>>>, cho biết sự khác biệt giữa các nhánh.
  • Ví dụ về xung đột:
<<<<<<< HEAD
Đây  nội dung trong nhánh hiện tại (HEAD).
=======
Đây  nội dung trong nhánh được hợp nhất.
>>>>>>> feature-branch

Trong đoạn ví dụ trên:

  • HEAD: Nội dung từ nhánh hiện tại.
  • feature-branch: Nội dung từ nhánh được hợp nhất vào.

5.2. Quy trình giải quyết xung đột thủ công

Khi Git thông báo có xung đột, bạn cần thực hiện các bước sau:

Bước 1: Kiểm tra các file có xung đột

git status

Git sẽ liệt kê các file có xung đột mà bạn cần xem xét.

Bước 2: Mở các file có xung đột và xem xét

Mở từng file và tìm các vùng xung đột (được đánh dấu bằng các dấu <<<<<<, ======, >>>>>>). Sau đó, bạn cần quyết định giữ lại phần nào hoặc kết hợp nội dung từ cả hai phần.

Bước 3: Xóa các dấu hiệu xung đột và lưu file

Sau khi chỉnh sửa, bạn cần xóa các dấu hiệu xung đột và lưu file.

Bước 4: Đánh dấu file đã được giải quyết

Sau khi giải quyết xong xung đột, bạn cần thông báo cho Git biết rằng xung đột đã được giải quyết:

git add <file>

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

Khi tất cả các xung đột đã được giải quyết và các file được đánh dấu, bạn hoàn tất việc merge bằng lệnh commit:

git commit

5.3. Chiến lược khác: Git mergetool

  • Mô tả: Thay vì giải quyết xung đột thủ công, bạn có thể sử dụng một công cụ hợp nhất (merge tool) tích hợp với Git như kdiff3, meld, vimdiff… Công cụ này giúp bạn xem và chọn các thay đổi dễ dàng hơn.
  • Cách sử dụng:
git mergetool

Git sẽ mở công cụ hợp nhất và bạn có thể giải quyết xung đột thông qua giao diện đồ họa hoặc giao diện dòng lệnh.

5.4. Tóm tắt

  • Three-Way Merge: Là chiến lược hợp nhất mặc định khi Git gặp xung đột, yêu cầu bạn phải xem xét và giải quyết các thay đổi.
  • Mergetool: Sử dụng công cụ hợp nhất để hỗ trợ quá trình giải quyết xung đột dễ dàng hơn.
  • Giải quyết thủ công: Mở file, xem xét xung đột, chọn các thay đổi phù hợp, sau đó lưu và hoàn tất hợp nhất.

Cả hai phương pháp này đều giúp bạn xem xét cẩn thận các thay đổi trong file trước khi chấp nhận chúng trong quá trình hợp nhất.

6. Các thuật ngữ trong Git Merge

Ngoài “Three-Way Merge,” còn có một số thuật ngữ khác trong Git liên quan đến quá trình hợp nhất và giải quyết xung đột mà bạn có thể gặp:

6.1. Fast-Forward Merge

  • Mô tả: Fast-Forward Merge xảy ra khi nhánh được hợp nhất là một nhánh con trực tiếp của nhánh hiện tại, nghĩa là không có bất kỳ thay đổi nào trên nhánh hiện tại kể từ khi nhánh được hợp nhất được tách ra. Trong trường hợp này, Git chỉ cần di chuyển con trỏ của nhánh hiện tại lên phía trước đến commit mới nhất trên nhánh kia mà không cần tạo ra một commit hợp nhất mới.
  • Ví dụ:
git merge feature-branch

Nếu không có thay đổi nào khác trên nhánh hiện tại, Git sẽ thực hiện Fast-Forward Merge.

6.2. Recursive Merge

  • Mô tả: Đây là chiến lược hợp nhất mặc định khi Git cần hợp nhất hai nhánh có thay đổi độc lập. Git sẽ tìm “common ancestor” (tổ tiên chung) và thực hiện một “Three-Way Merge.” Nếu có nhiều tổ tiên chung, Git sẽ thực hiện hợp nhất các tổ tiên trước khi tiếp tục hợp nhất với các nhánh chính. Phương pháp này giúp tạo ra một hợp nhất chính xác hơn khi có nhiều nhánh phân nhánh từ một commit chung.
  • Ví dụ: Được sử dụng tự động bởi Git khi không có điều kiện để thực hiện Fast-Forward Merge.

6.3. Octopus Merge

  • Mô tả: Octopus Merge được sử dụng khi bạn muốn hợp nhất nhiều hơn hai nhánh cùng một lúc. Chiến lược này thường được sử dụng trong các tình huống không có xung đột lớn, vì nếu có quá nhiều xung đột, quá trình hợp nhất có thể phức tạp và khó kiểm soát.
  • Ví dụ:
git merge branch1 branch2 branch3

Trong ví dụ này, Git sẽ hợp nhất ba nhánh (branch1, branch2, branch3) vào nhánh hiện tại.

6.4. Ours Merge Strategy

  • Mô tả: Chiến lược này cho phép bạn thực hiện một merge và giữ lại tất cả các thay đổi từ nhánh hiện tại, bỏ qua các thay đổi từ nhánh được hợp nhất vào, ngay cả khi có xung đột. Chiến lược này hữu ích khi bạn muốn bỏ qua hoàn toàn các thay đổi từ một nhánh nhất định.
  • Ví dụ:
git merge -s ours branch-to-ignore

6.5. Rebase

  • Mô tả: Mặc dù không phải là một loại hợp nhất (merge), rebase là một kỹ thuật kết hợp lịch sử commit của một nhánh vào một nhánh khác bằng cách áp dụng lại các commit của nhánh hiện tại trên đầu của một nhánh khác. Rebase giúp giữ cho lịch sử commit rõ ràng và tuyến tính hơn.
  • Ví dụ:
git rebase main

Sau khi rebase, nhánh hiện tại sẽ có tất cả các commit của nhánh main, và các commit của nhánh hiện tại sẽ được đặt lên trên.

6.6. Cherry-Pick

  • Mô tả: Cherry-Pick là một cách để chọn và áp dụng một hoặc nhiều commit từ một nhánh khác vào nhánh hiện tại mà không cần hợp nhất toàn bộ nhánh. Kỹ thuật này rất hữu ích khi bạn chỉ muốn đưa một số thay đổi cụ thể từ một nhánh sang nhánh khác.
  • Ví dụ:
git cherry-pick <commit-hash>

6.7. Squash Merge

  • Mô tả: Squash Merge kết hợp tất cả các commit từ nhánh được hợp nhất vào một commit duy nhất khi hợp nhất với nhánh hiện tại. Điều này giúp giữ cho lịch sử commit sạch sẽ và dễ theo dõi, đặc biệt khi bạn muốn giảm số lượng commit không cần thiết.
  • Ví dụ:
git merge --squash feature-branch

6.8. Tóm tắt

  • Three-Way Merge: Phương pháp hợp nhất chính, sử dụng khi có sự thay đổi trên cả hai nhánh.
  • Fast-Forward Merge: Di chuyển con trỏ của nhánh hiện tại lên phía trước mà không tạo ra commit mới.
  • Recursive Merge: Sử dụng khi có nhiều tổ tiên chung, đặc biệt trong các nhánh phân nhánh phức tạp.
  • Octopus Merge: Hợp nhất nhiều nhánh cùng lúc.
  • Ours Merge Strategy: Bỏ qua các thay đổi từ nhánh khác và giữ lại thay đổi từ nhánh hiện tại.
  • Rebase: Áp dụng lại các commit từ nhánh hiện tại lên trên đầu của một nhánh khác.
  • Cherry-Pick: Áp dụng một commit cụ thể từ một nhánh khác.
  • Squash Merge: Gộp tất cả các commit từ một nhánh vào một commit duy nhất.

Những thuật ngữ này cung cấp các cách tiếp cận khác nhau trong việc quản lý và hợp nhất các thay đổi từ nhiều nhánh trong Git.