Git từ Cơ bản đến Nâng cao: Nắm vững Branch, Merge và Rebase để Cộng tác liền mạch

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

Thách thức trong Phát triển Cộng tác

Với tư cách là một kỹ sư IT trong các dự án phức tạp, cá nhân tôi đã chứng kiến sự hỗn loạn có thể xảy ra nếu không có hệ thống quản lý phiên bản phù hợp. Hãy tưởng tượng một nhóm các nhà phát triển cùng làm việc trên một cơ sở mã. Họ đồng thời thêm tính năng mới, sửa lỗi và tái cấu trúc mã hiện có. Nếu không có một hệ thống mạnh mẽ, công việc hàng ngày có thể trở thành một cơn ác mộng. Các nhà phát triển có thể vô tình ghi đè lên các thay đổi của nhau, gây ra lỗi không mong muốn hoặc vật lộn với một lịch sử commit rối rắm, như “mỳ spaghetti”.

Nhưng không chỉ là tránh thảm họa. Đó là việc tạo ra một môi trường nơi sự đổi mới có thể thực sự phát triển. Khi các nhà phát triển cảm thấy tự tin rằng các thay đổi của họ được cô lập và có thể được tích hợp một cách an toàn, họ sẽ làm việc hiệu quả hơn và mắc ít lỗi hơn. Đây là lúc Git, với mô hình phân nhánh linh hoạt của nó, thực sự tỏa sáng như một công cụ quan trọng.

Các Khái niệm Cốt lõi: Nền tảng cho Cộng tác Git

Git không chỉ là một trình theo dõi tệp; nó là một trình theo dõi nội dung được xây dựng dựa trên khái niệm đồ thị có hướng không chu trình (directed acyclic graph). Trong đồ thị này, các commit là các nút và các con trỏ cha định nghĩa lịch sử. Để điều hướng đồ thị này một cách hiệu quả, việc hiểu ba khái niệm cơ bản là rất quan trọng: branch (nhánh), merge (hợp nhất) và rebase (tái cơ sở).

Branch: Các dòng phát triển cô lập

Hãy nghĩ về một branch như một con trỏ nhẹ, có thể di chuyển đến một trong các commit của bạn. Khi bạn tạo một branch, về cơ bản bạn đang nói, “Tôi muốn làm một cái gì đó mới ở đây mà không ảnh hưởng đến dòng phát triển chính.” Sự cô lập này cực kỳ có giá trị. Nó cho phép bạn:

  • Thử nghiệm các tính năng mới mà không làm hỏng cơ sở mã đang hoạt động.
  • Làm việc đồng thời trên nhiều tính năng hoặc sửa lỗi. Ví dụ, một branch có thể giải quyết một lỗi nghiêm trọng trong khi một branch khác phát triển giao diện người dùng mới.
  • Chuẩn bị các bản phát hành mà không làm gián đoạn quá trình phát triển đang diễn ra.

Branch mặc định trong hầu hết các kho lưu trữ là main (hoặc trước đây là master). Tất cả các branch khác thường phân nhánh từ và cuối cùng hợp nhất trở lại dòng chính này.

Merge: Kết hợp các lịch sử phân kỳ

Merge là cách bạn đưa các thay đổi từ một branch này vào một branch khác. Đó là việc kết hợp hai hoặc nhiều lịch sử phát triển thành một luồng thống nhất. Khi bạn bắt đầu một thao tác merge, Git trước tiên xác định tổ tiên chung của hai branch. Sau đó, nó lấy các thay đổi được giới thiệu trong cả hai branch kể từ điểm chung đó và kết hợp chúng vào một commit mới, được gọi là “merge commit”. Merge commit này có hai commit cha duy nhất, biểu thị nơi các lịch sử đã phân kỳ.

Đặc điểm chính của merge là tính chất không phá hủy của nó. Nó giữ lại một cách tỉ mỉ lịch sử chính xác của các commit, cho thấy chính xác thời điểm và nơi các dòng phát triển phân nhánh và hội tụ. Sự minh bạch này giúp dễ dàng truy ngược lại các thay đổi và hiểu được sự tiến hóa của cơ sở mã, cung cấp một nhật ký kiểm toán rõ ràng.

Rebase: Viết lại Lịch sử để có một Trình tự tuyến tính

Rebase cung cấp một phương pháp thay thế để tích hợp các thay đổi từ một branch này sang một branch khác. Tuy nhiên, nó có một điểm khác biệt quan trọng: nó viết lại lịch sử. Khi bạn rebase Branch A lên Branch B, Git thực chất lấy tất cả các commit từ Branch A. Sau đó, nó “phát lại” chúng từng cái một lên trên commit mới nhất của Branch B, tạo ra các đối tượng commit hoàn toàn mới.

Bạn có thể hình dung như thế này: “Tôi muốn làm cho nó trông như thể tôi bắt đầu công việc của mình trên branch tính năng này từ trạng thái mới nhất của branch mục tiêu, chứ không phải từ nơi tôi ban đầu phân nhánh.” Mục tiêu chính của rebase là duy trì một lịch sử dự án rõ ràng, tuyến tính. Điều này làm cho nhật ký commit gọn gàng hơn và dễ đọc hơn nhiều, vì nó tránh được các merge commit thừa có thể làm lộn xộn lịch sử.

Tuy nhiên, vì rebase thay đổi lịch sử, nên nó thường không được khuyến khích đối với các branch đã được đẩy lên một kho lưu trữ từ xa được chia sẻ. Làm như vậy có thể gây ra các vấn đề nghiêm trọng cho các cộng tác viên khác, những người có thể đã dựa trên công việc của họ trên các commit gốc, chưa được rebase.

Merge và Rebase: Khi nào nên sử dụng cái nào

Chủ đề này thường gây tranh luận và bối rối giữa các nhà phát triển. Dưới đây là một cách tiếp cận thực dụng mà tôi đã thấy hữu ích:

  • Sử dụng Merge khi: Bạn cần bảo toàn một lịch sử đầy đủ, chính xác của tất cả các lần merge và phân nhánh. Đây là lựa chọn an toàn hơn cho các branch công khai hoặc được chia sẻ vì nó không viết lại lịch sử. Phương pháp này thường được ưu tiên để tích hợp các branch tính năng đã hoàn thành vào main.
  • Sử dụng Rebase khi: Bạn muốn có một lịch sử sạch sẽ, tuyến tính, đặc biệt là trong các branch tính năng cục bộ của bạn trước khi chúng được chia sẻ. Rebase giúp tích hợp các thay đổi từ upstream vào branch tính năng của bạn, làm cho nó trông như thể nó được phân nhánh trực tiếp từ commit main mới nhất. Điều này thường dẫn đến các lần merge sạch hơn sau đó. Điều quan trọng là, không bao giờ rebase các commit đã được đẩy lên một kho lưu trữ công khai nếu người khác có thể đã dựa trên công việc của họ trên các commit đó. Điều này có thể làm hỏng lịch sử của họ và gây ra rắc rối cho nhóm.

Sau nhiều năm sử dụng cả hai chiến lược trong môi trường sản xuất, tôi nhận thấy rằng việc áp dụng cẩn thận các phương pháp này một cách nhất quán sẽ dẫn đến các lịch sử dự án ổn định và dễ hiểu. Đối với cộng tác nhóm, tôi thường ủng hộ việc merge các branch tính năng vào main. Tôi cũng khuyên nên sử dụng rebase nội bộ trong một branch tính năng để giữ cho nó được cập nhật với main trước khi merge cuối cùng.

Thực hành: Quy trình làm việc Phân nhánh Git

Hãy cùng thực hành với một vài ví dụ cụ thể. Chúng ta sẽ mô phỏng một dự án nhỏ để minh họa các khái niệm này trên thực tế.

1. Khởi tạo Kho lưu trữ

Đầu tiên, tạo một thư mục mới và khởi tạo một kho lưu trữ Git bên trong nó:


mkdir git_project
cd git_project
git init

echo "Initial content" > README.md # Nội dung ban đầu
git add README.md
git commit -m "Initial commit" # Commit ban đầu

2. Tạo và Chuyển đổi Branch

Bây giờ, hãy tạo một branch tính năng mới và chuyển sang nó. Điều này giúp cô lập công việc mới của bạn khỏi cơ sở mã chính:


git branch feature/add-auth
git checkout feature/add-auth

# Hoặc, kết hợp chúng thành một lệnh:
git checkout -b feature/add-auth

# Để xem các branch hiện tại của bạn và branch nào đang hoạt động:
git branch

Khi đã ở trên branch mới của bạn, hãy thực hiện một số thay đổi. Chẳng hạn, thêm một module xác thực:


echo "Auth feature code" > auth.py # Mã tính năng xác thực
git add auth.py
git commit -m "Implement basic authentication" # Triển khai xác thực cơ bản

3. Hợp nhất các Thay đổi (Merging Changes)

Giả sử bạn đã hoàn thành tính năng xác thực của mình và muốn tích hợp nó vào branch main. Đầu tiên, hãy chuyển về main và đảm bảo nó được cập nhật (mặc dù trong ví dụ cục bộ này, nó sẽ được cập nhật sẵn).


git checkout main

Merge Fast-Forward

Nếu branch main chưa thay đổi kể từ khi bạn tạo branch tính năng của mình, Git có thể thực hiện một thao tác merge “fast-forward”. Nó chỉ đơn giản là di chuyển con trỏ main trực tiếp đến commit mới nhất của branch tính năng của bạn, tích hợp các thay đổi một cách hiệu quả.


git merge feature/add-auth

Nếu thành công, bạn sẽ thấy một thông báo như Updating a3b4c5d..e6f7g8h Fast-forward. Tại thời điểm này, tệp auth.py hiện đã là một phần của branch main của bạn.

Merge Ba Chiều với Giải quyết Xung đột

Thường xuyên hơn trong các kịch bản thực tế, branch main sẽ phát triển độc lập. Hãy cùng mô phỏng tình huống đó.


# Quay lại main và thêm một commit mới
git checkout main
echo "New header for website" > header.html # Tiêu đề mới cho trang web
git add header.html
git commit -m "Add website header" # Thêm tiêu đề trang web

# Quay lại branch tính năng và thực hiện một thay đổi khác
git checkout feature/add-auth
echo "User management components" > users.py # Các thành phần quản lý người dùng
git add users.py
git commit -m "Add user management" # Thêm quản lý người dùng

Bây giờ, hãy cố ý tạo một xung đột. Chúng ta sẽ sửa đổi cùng một dòng trong README.md trên cả hai branch.


# Trên feature/add-auth, cập nhật README
echo "Auth project with user management" > README.md # Dự án xác thực với quản lý người dùng
git add README.md
git commit -m "Update README for auth feature" # Cập nhật README cho tính năng xác thực

# Trên main, cập nhật README khác
git checkout main
echo "Main project documentation" > README.md # Tài liệu dự án chính
git add README.md
git commit -m "Update README for main project" # Cập nhật README cho dự án chính

# Bây giờ, hãy thử merge feature/add-auth vào main
git merge feature/add-auth

Git sẽ báo cáo một xung đột merge, cho biết nó không thể tự động giải quyết các thay đổi. Mở README.md trong trình soạn thảo văn bản của bạn:


# README.md sẽ trông giống như thế này, với các dấu hiệu xung đột:
<<<<<<< HEAD
Main project documentation
=======
Auth project with user management
>>>>>>> feature/add-auth

Chỉnh sửa thủ công README.md để giải quyết xung đột. Bạn có thể chọn một phiên bản, kết hợp các phần, hoặc viết nội dung hoàn toàn mới. Ví dụ:


# README.md đã được giải quyết, kết hợp cả hai thay đổi
Auth project with user management and main documentation

Sau đó, thêm tệp đã giải quyết vào khu vực dàn dựng (stage) và hoàn tất merge commit:


git add README.md
git commit -m "Merge feature/add-auth into main, resolving README conflict" # Merge feature/add-auth vào main, giải quyết xung đột README

4. Tái cơ sở các Thay đổi (Rebasing Changes)

Hãy tạo một branch tính năng mới để mô phỏng thao tác rebase. Chúng ta sẽ bắt đầu từ main.


git checkout main
git branch feature/new-design
git checkout feature/new-design

echo "New design system setup" > design.css # Thiết lập hệ thống thiết kế mới
git add design.css
git commit -m "Initial design system" # Hệ thống thiết kế ban đầu

echo "Refined button styles" >> design.css # Các kiểu nút được tinh chỉnh
git add design.css
git commit -m "Add button styles" # Thêm kiểu nút

Bây giờ, hãy tưởng tượng main đã nhận được một số cập nhật quan trọng trong khi bạn đang làm việc chăm chỉ trên feature/new-design. Chẳng hạn, một bản sửa lỗi nghiêm trọng:


git checkout main
echo "Crucial bug fix" > fix.py # Sửa lỗi quan trọng
git add fix.py
git commit -m "Critical bug fix applied to main" # Đã áp dụng sửa lỗi quan trọng vào main

Để đưa các thay đổi từ main này vào branch feature/new-design của bạn, nhưng không tạo merge commit, bạn sẽ thực hiện rebase:


git checkout feature/new-design
git rebase main

Git sẽ tạm thời “tua lại” các commit của feature/new-design của bạn. Sau đó, nó áp dụng commit mới của branch main, và cuối cùng, áp dụng lại các commit tính năng của bạn lên trên. Branch tính năng của bạn bây giờ trông như thể nó được phân nhánh trực tiếp từ commit main mới nhất.

Rebase với Giải quyết Xung đột

Xung đột cũng có thể phát sinh trong quá trình rebase. Nếu chúng xảy ra, Git sẽ tạm dừng quá trình rebase và thông báo rõ ràng cho bạn. Bạn giải quyết các xung đột này giống như khi merge: chỉnh sửa các tệp bị xung đột, sau đó sử dụng git add để dàn dựng chúng. Tuy nhiên, thay vì git commit, bạn sử dụng git rebase --continue để tiếp tục rebase.


# Mô phỏng xung đột trong quá trình rebase:
# (Giả sử bạn đã thực hiện một thay đổi gây xung đột trên feature/new-design
# và sau đó đã cố gắng rebase lên một branch main với một thay đổi tương tự)

# Nếu xung đột xảy ra trong quá trình rebase, hãy giải quyết nó và sau đó:
git add <conflicted-file> # Thêm tệp bị xung đột
git rebase --continue

# Để hủy bỏ rebase và quay lại trạng thái trước khi nó bắt đầu:
git rebase --abort

Rebase Tương tác (Kỹ thuật Nâng cao)

Rebase tương tác (git rebase -i) là một công cụ mạnh mẽ để dọn dẹp lịch sử commit của bạn *trước khi* đẩy lên một kho lưu trữ được chia sẻ. Nó cung cấp quyền kiểm soát chi tiết, cho phép bạn:

  • Squash: Kết hợp nhiều commit nhỏ thành một commit duy nhất, có ý nghĩa hơn.
  • Reword: Thay đổi các thông báo commit hiện có để rõ ràng hơn.
  • Edit: Sửa đổi nội dung của một commit, hoặc thậm chí chia một commit thành hai.
  • Reorder: Thay đổi thứ tự các commit.
  • Drop: Xóa hoàn toàn các commit khỏi lịch sử của bạn.

Giả sử bạn đã thực hiện một số commit nhỏ, tăng dần trên feature/new-design và muốn kết hợp chúng thành một commit duy nhất, mạch lạc trước khi merge. Bạn có thể bắt đầu một rebase tương tác như sau:


git log --oneline
# Sao chép hash commit của commit *trước* các commit bạn muốn rebase.
# Để rebase 3 commit gần nhất, ví dụ:
git rebase -i HEAD~3

Lệnh này sẽ mở một trình soạn thảo hiển thị danh sách các commit. Sau đó, bạn có thể thay đổi pick thành squash (hoặc `s`) cho các commit bạn muốn kết hợp với commit phía trên nó. Sử dụng reword (hoặc `r`) để thay đổi một thông báo, v.v. Lưu và thoát trình soạn thảo; Git sau đó sẽ thực hiện các thao tác được chỉ định trên lịch sử của bạn.

Kết luận: Lịch sử Rõ ràng, Quy trình làm việc Mượt mà hơn

Nắm vững các khả năng phân nhánh, hợp nhất và tái cơ sở của Git không chỉ là ghi nhớ các lệnh. Đó là việc áp dụng một quy trình làm việc tích cực thúc đẩy sự rõ ràng, tăng cường cộng tác và đảm bảo khả năng bảo trì các dự án của bạn. Các branch hỗ trợ phát triển song song, merge tích hợp lịch sử một cách liền mạch và rebase dọn dẹp chúng một cách tỉ mỉ để có một trình tự tuyến tính, dễ theo dõi hơn.

Hành trình của tôi qua nhiều môi trường sản xuất đã liên tục củng cố một sự thật: một lịch sử Git rõ ràng và được quản lý tốt là một tài sản vô giá.

Khả năng nhanh chóng hiểu được sự phát triển của một dự án, xác định chính xác thời điểm và nơi các thay đổi được giới thiệu, và hoàn nguyên hoặc tích hợp công việc một cách an toàn là tối quan trọng. Bằng cách áp dụng một cách chu đáo các kỹ thuật Git này – đặc biệt là hiểu được ý nghĩa của việc viết lại lịch sử bằng rebase so với việc bảo toàn nó bằng merge – bạn có thể cải thiện đáng kể hiệu quả phát triển của nhóm và sự ổn định tổng thể của cơ sở mã của bạn.

Khi bạn cảm thấy thoải mái khi sử dụng các công cụ này và thực hành thường xuyên, bạn sẽ thấy trải nghiệm phát triển cộng tác của mình trở nên mượt mà hơn, dễ đoán hơn và cuối cùng là thú vị hơn rất nhiều.

Share: