Git Merging vs. Rebasing

Trong quá trình làm việc với git, rebase là một lệnh không đơn giản mà những người mới làm quen với hệ quản lý phiên bản này nên hạn chế sử dụng. Tuy nhiên, nếu có thể sử dụng được lệnh này với sự cẩn trọng, các thành viên của đội phát triển sẽ cảm thấy "hạnh phúc" hơn rất nhiều. Trong bài viết này, chúng ta sẽ so sánh lệnh git rebase với câu lệnh có liên quan khác là git merge và sẽ xác định những tình huống khả dĩ mà bạn nên/cần sử dụng rebase trong quy trình làm việc với git.

1. Đặt vấn đề

Cả hai lệnh rebasemerge đều dùng để giải quyết cùng một vấn đề: tích hợp những thay đổi từ một nhánh vào một nhánh khác. Tuy nhiên chúng thực hiện điều này theo những cách rất khác nhau.

Giả sử, bạn bắt đầu checkout một topic branch có tên là feature để xây dựng chức năng mới cho hệ thống, trong khi đó một thành viên khác của team đã cập nhật thêm những commit mới vào integration branch. Để đơn giản, chúng ta sẽ lấy luôn nhánh master làm integration branch.

A forked commit history

Tình huống đặt ra là những commit mới ở nhánh master thì có liên quan đến chức năng mà bạn đang thực hiện. Để tích hợp những commit đó vào nhánh feature, bạn có thể thực hiện theo 2 cách: merge hoặc rebase.

2. Merge

Cách này khá đơn giản và nhanh chóng, bạn merge nhánh master vào nhánh đang làm việc bằng các thao tác:

git checkout feature
git merge master

Hoặc bạn có thể thực hiện nhanh hơn nữa, gộp 2 câu lệnh trên thành 1 lệnh:

git merge master feature

Với cách làm này, một commit merge mới sẽ xuất hiện ở lịch sử commit của nhánh feature, giống như một mối nối để ghép lại lịch sử của cả 2 nhánh. Bạn sẽ có một cấu trúc commit trông giống như này:

Merging master into the feature branch

merge là một thao tác dễ chịu vì nó được coi là một câu lệnh không mang tính phá hủy. Những nhánh đang tồn tại không bị thay đổi. Nó sẽ giúp người dùng tránh được nhánh hiểm họa tiềm ẩn nếu như sử dụng rebase.

Nói cách khác, topic branch sẽ có một commit ngoại lai mỗi khi bạn cần tích hợp những thay đổi từ integration branch. Trong ví dụ này, nếu nhánh master rất động, nó có thể làm nhiễu loạn lịch sử commit của nhánh feature khá nhiều. Trong khi bạn có thể giảm nhẹ vấn đề này với những tùy chọn git log nâng cao, nhìn chung nó sẽ gây ít nhiều khó khăn cho những developer khác để có thể hiểu được lịch sử phát triển của dự án.

3. Rebase

Như đã nói ở trên, cách khác để tích hợp những thay đổi ở nhánh master vào nhánh feature là sử dụng rebase:

git checkout feature
git rebase master

Thao tác này sẽ đưa toàn bộ những commit mới tạo ở nhánh feature nối tiếp vào "ngọn" của nhánh master, nhưng thay vì sử dụng một commit merge, nó sẽ viết lại lịch sử của project bằng cách tạo ra những commit mới ứng với mỗi commit ban đầu của nhánh feature.

Rebasing the feature branch onto master

Lợi ích chính của việc rebase là bạn sẽ nhận được một lịch sử commit rõ ràng, dễ theo dõi hơn. Đầu tiên, nó sẽ giúp loại bỏ những commit không cần thiết như khi sử dụng git merge. Tiếp theo, khi quan sát hình vẽ minh họa ở trên, bạn có thể thấy rằng rebase giúp tạo ra lịch sử commit có dạng tuyến tính, xuyên suốt project từ khi bắt đầu cho đến hiện tại. Khi đó chúng ta sẽ dễ dàng điều hướng, kiểm tra lịch sử project với những câu lệnh như git log, git bisect.

Tuy nhiên, có 2 điều cần phải thỏa hiệp đối với lịch sử commit kiểu này: độ an toàn và khả năng truy vết. Nếu bạn không tuân theo "nguyên tắc vàng" khi rebase, việc viết lại lịch sử của project có thể là thảm họa khó lường đối với quy trình cộng tác làm việc nhóm của bạn. Một điều ít quan trọng hơn, rebase sẽ làm mất đi ngữ cảnh mà commit merge cung cấp, từ đó bạn không biết được khi nào những thay đổi ở nhánh tích hợp được đưa vào nhánh chủ đề.

4. Interactive rebase

Đây là kĩ thuật giúp bạn chỉnh sửa commit khi chuyển tới nhánh mới. Nó hiệu quả và mạnh mẽ hơn thao tác tự động rebase đã nói đến ở trên vì hỗ trợ quản lý lịch sử commit của nhánh tốt hơn. Thông thường, kĩ thuật này được sử dụng để dọn dẹp, tinh chỉnh lại lịch sử của nhánh chủ đề trước khi merge vào nhánh tích hợp.

Để thực hiện thao tác này, chúng ta cần truyền thêm tham số -i vào câu lệnh git rebase:

git checkout feature
git rebase -i master

Text editor để chỉnh sửa nội dung các commit sẽ hiển thị, liệt kê tất cả những commit mà chuẩn bị được sắp xếp, chẳng hạn như:

pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3

Danh sách này sẽ định nghĩa chính xác xem nhánh chủ đề sẽ trông như thế nào sau khi việc rebase hoàn tất. Bằng cách thay đổi lệnh pick và/hoặc sắp xếp lại thứ tự của các commit, bạn có thể tạo ra lịch sử theo cách mong muốn. Ví dụ, nếu commit thứ 2 sử dụng để fix một lỗi nhỏ ở commit thứ nhất, bạn có thể thu gọn chúng thành 1 commit duy nhất với lệnh fixup:

pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3

Khi bạn lưu và đóng file chỉnh sửa commit lại, git sẽ thực hiện rebase dựa trên những chỉnh sửa này, đưa đến kết quả là lịch sử project nhìn sẽ như sau:

Squashing a commit with an interactive rebase

Việc loại bỏ những commit không đáng chú ý như này sẽ giúp cho lịch sử của nhánh chủ đề dễ hiểu, trong sáng hơn. Và đây cũng là sự khác biệt quan trọng giữa git rebasegit merge. Đối với lệnh git merge, đơn giản là bạn không làm được như vậy.

5. Tóm tắt

Nếu bạn cần một lịch sử gọn gàng, có dạng tuyến tính và không có sự xuất hiện của những commit merge, bạn có thể dùng git rebase thay vì git merge mỗi lần tích hợp những thay đổi của một nhánh vào một nhánh khác.

Ngược lại, nếu bạn muốn bảo toàn lịch sử đầy đủ của dự án và tránh những nguy cơ của việc viết lại những public commit, bạn có thể sử dụng git merge. Cả 2 câu lệnh này đều giúp chúng ta đạt được mục tiêu, tuy nhiên ít nhất đến bây giờ bạn cũng đã có khả năng để khai thác ưu điểm của git rebase. Tùy từng tình huống cụ thể mà bạn hãy vận dụng cho phù hợp nhé. Cảm ơn các bạn đã theo dõi bài viết.