Git Branching and Merging

(Bài viết thực hiện trên Linux/GNU)

1.Prerequisites:

  • Git commit graph / history (hay git log)
  • Working tree
  • Staging area

Trước khi bắt đầu về git branch tôi sẽ tìm hiểu hình trên một chút. Xét về cơ bản đó là các bước mà git ghi lại sự thay đổi của các các file. Tôi có working directory là một thư mục bình thường chứa các file mà đang thực hiện công việc cùng, repository là nơi sẽ ghi lại trạng thái của thư mục và file (Trạng thái được lưu lại có thể coi như là lịch sử thay đổi nội dung) và staging area là nơi chuẩn bị cho việc commit lên repository (cụ thể đây là local repository, đưa lên remote thì phải thêm một bước push lên nữa)

Oki. Let’s go.

Đầu tiên …

Thêm nội dung cho file

Xong khâu chuẩn bị. Tôi sẽ thực hiện add và commit đưa 2 file này vào folder .git

Tôi nhận ra ở đây có 1 điểm đặc biệt.

  • HEAD -> master

Git nói rằng tôi đang ở nhánh master, cái này tôi không tạo ra (theo trên) mà git tự động thực hiện việc tạo nhánh master (first branch) cho repository mới của tôi. Vậy làm sao tôi nhận ra được điều này? Đó là con trỏ HEAD, con trỏ HEAD trỏ ở đâu thì nhánh chúng ta đang đứng sẽ ở đây. Một điều đặc biệt nữa là dãy số bên cạnh commit. Dãy số này là kết quả của commit đã được mã hóa bằng SHA-1 dài 40 kí tự (160 bits = 20 bytes, mỗi byte trong hệ cơ số 16 biểu diễn 2 kí tự 00 -> FF )

2. Branch

a. Khái quát về branch

Branch như tên gọi của nó đã nói lên tất cả. Branch dùng để phân nhánh trong git. Branch cho phép chúng ta làm việc trên những phiên bản khác nhau của một files song song. Branch đã phân nhánh sẽ có thể tiến hành làm việc độc lập so với các nhánh khác trong cùng một repository. Branch đã phân nhánh có thể hợp nhất lại với một branch khác.

Những tính năng trên của branch có thể phục vụ cho nhiều mục đích khác nhau. Lấy một ví dụ tôi đang làm việc trên nhánh master, tôi muốn thử nghiệm một tính năng mới mà không muốn ảnh hưởng đến code chính thì tôi sẽ tạo ra một nhánh mới dev để thực hiện và sau khi xong thì sẽ merge lại vào nhánh master.

b. Tạo branch

Trước khi bắt đầu chúng ta đi vào một sơ đồ trực quan hơn sau đây.

Visualize git branch

Đó là tất cả những gì mình làm từ nãy đến giờ. Biểu đồ chỉ có duy nhất nhánh master, và giờ tôi sẽ tạo ra một vài nhánh nữa.

1a.

Điều gì đang diễn ra? Chúng ta đang ở branch master và cả 3 branch của chúng ra đều trỏ vào cùng một commit a392b673 . Vậy làm thế nào để chuyển từ branch master sang một branch khác? Thật không có gì ngẫu nhiên khi git đã chuẩn bị sẵn câu trả lời này cho bất cứ ai:

1b.
2a. Edit base1 và add controller_ip

Commit lại và chúng ta sẽ thấy ngay sự thay đổi (Git update working directory, update staging area).

1c.

Quan sát và so sánh với biểu 1b chúng ta thấy rằng con trở HEAD đang trỏ tại branch SSS và branch SSS đã di chuyển về phía trước sau khi commit trong khi branch master cùng aaa vẫn ở lại tại vị trí commit trước đó.

git log after checkout SSS

Làm tiếp tục tương tự với branch aaa, liệu nó có phải là sự lặp lại như trên hay là một điều gì đó hoàn toàn khác? Mời các bạn đoán xem 😉

2b. Chúng ta dễ dàng nhận ra dòng controller_ip được thêm bên nhánh SSS không xuất hiện ở đây, đó là minh chứng cho khả năng hoạt động độc lập của các branch trong git (so sánh với 2a).

Thêm vào server_ip, dùng git diff để kiểm tra sự thay đổi.

Sau khi thực hiện một số thay đổi thì chúng ta sẽ add và commit lại file base1 tại branch aaa này.

Tips:

1d.

Đây là biểu đồ nhánh sau khi commit thành công trên branch aaa. So sánh với hình 1c thì chúng ta dễ dàng nhận ra sự khác nhau trong cách tổ chức của 2 biểu đồ. Vậy tại sao lại có sự khác nhau này và sự khác nhau này đến từ đâu? Có lẽ sẽ dành ra một bài khác để nói rõ hơn về vấn đề này, nhưng về cơ bản thì muốn đường đi của nhánh sẽ dựa vào mối quan hệ giữa các commit để xác định.

Chúng ta dễ thấy ở đây , ban đầu chúng ta đang ở branch master A, commit -> di chuyển đến B, và nếu tiếp tục commit thì cứ mỗi lần commit contrỏ HEAD sẽ di chuyển tuần tự đến C, D, E nhưng ở đây chúng ta thấy không chỉ A -> B -> C -> D -> E mà còn A -> B -> F -> G và A -> B -> H -> I -> J, tại sao?. Vì tại B chúng ta tạo ra 2 nhánh mới mà 2 nhánh đó dùng chung một commit, và như đã nói ở trên, hướng đi của nhánh dựa hoàn toàn vào tham chiếu với commit ở ngay trước đó, tại A chúng ta có duy nhất 1 nhánh, nên sau khi commit con trỏ HEAD di chuyển tịnh tiến đến điểm B, nhưng tại B chúng ta có 3 nhánh, 3 nhánh này không có quan hệ gì với nhau mà chỉ tham chiếu đến điểm A trước đó cho nên với mỗi commit lại tỏa đi một hướng khác nhau. Chỉ đơn giản có vậy.

Và tiếp theo, xin được trở lại với ví dụ của chúng ta, mọi thứ diễn ra y chang, không hơn không kém.

Oki. Bây giờ chúng ta đến với merge trong git.

3. Merge

3.1 fast-forward merge

Ở đây HEAD đang trỏ đến branch SSS, SSS có một tham chiếu trực tiếp đến branch master cho nên khi merge SSS và master thì chúng ta gọi đó là fast-forward merge. Ok, here we go.

Kiểm tra thay đổi khi chúng ta merge 2 nhánh vào, nếu merge thành công chúng ta sẽ có thêm dòng code controller_ip kia.

Kiểm tra lại, chúng ta thấy SSS branch đã được merge vào master. Ok, vậy điều gì xảy ra khi chúng ta merge aaa branch vào master. Cùng xem nhé:

Biểu đồ nhánh không có vẻ không có gì quá đặc biệt, vì sự đặc biệt đã được vứt hết vào đây:

Tại sao phải dùng ‘recursive’ strategy để merge mà không phải fast-forward?

3.2 Recursive merge (Three-way merge)

Cùng xem lại 2 hình này.

SSS tham chiếu trực tiếp tới master nên khi merge vào master sẽ là fast-forward.

aaa không tham chiếu trực tiếp tới master, nên phải dùng ‘recursive’ strategy để merge vào master, cụ thể là đầu tiên Git nhìn vào commit a392b673, sau đó là 2 commit con của commit a392b673 (2 commit mới nhất của hai nhánh master và aaa), và từ 2 commit này git mới bắt đầu tiến hành merge vào làm một. Đó là lý do mà git gọi đây là ‘recursive’ strategy merge.

Vẫn đang còn những vấn đề liên quan như handle conflict nhưng có lẽ bài viết đã khá dài, tôi sẽ update vào một dịp khác.