-----
3.8 Phân nhánh
3.8.1
Nhánh là gì?
Ở các phần trước, bạn đã biết cách lấy một kho chứa từ trên
mạng về, thực hiện thêm thông tin vào kho chứa (ví dụ lập trình thêm một chức
năng cho dự án), sau đó đẩy ngược lên mạng để chia sẻ cho các thành viên còn lại.
Để cập nhật kết quả mới nhất của các thành viên khác, bạn sẽ
sử dụng lệnh $ git pull để kéo các thông tin mới từ remote repo về, trộn vào local
repo. Cách làm này sẽ gặp trục trặc khi mã nguồn của thành viên khác làm cho
chương trình của bạn bị lỗi. Hoặc đoạn mã của bạn có lỗi mà bạn lại đẩy (push)
lên remote repo. Nói chung là có khá nhiều hạn chế nếu tất cả các thành viên
cùng lập trình trên nhánh master.
Phần này sẽ giới thiệu một giải pháp, giúp bạn khắc phục được
những nhược điểm trên, để quá trình làm dự án được hiệu quả hơn. Đó là phân
nhánh trong Git.
Đọc tài liệu tiếng Anh:
Định nghĩa phân nhánh: branching means you diverge from the
main line of development and continue to do work without messing with that main
line.
Phân nhánh (branching) là việc tạo ra một nhánh mới từ luồng
công việc chính, nhờ vậy các công việc được thực hiện độc lập trên nhánh mới mà
không làm ảnh hưởng đến luồng chính.
Trong Git việc tạo nhánh không mất nhiều thời gian và có rất
nhiều ưu điểm trong quá trình sử dụng. Vì vậy, Git khuyến khích các lập trình
viên thường xuyên tạo và trộn nhánh trong quá trình phát triển dự án.
Để hiểu về phân nhánh, cần tìm hiểu kĩ hơn về việc tổ chức
lưu trữ dữ liệu của Git. Như đã đề cập ở các phần trước, Git không lưu trữ chuỗi
các sự thay đổi mà nó lưu trữ chuỗi các ảnh của dự án (các snapshot). Mỗi khi
commit (lưu lại trạng thái hiện tại của dự án trong Git), Git sẽ “chụp một ảnh”
ghi lại nội dung của tất cả tập tin tại thời điểm đó và tạo một tham chiếu tới
“ảnh” vừa chụp. Những tập tin không có thay đổi sẽ không cần “chụp ảnh”, mà chỉ
cần tạo tham chiếu tới tập tin gốc (để tiết kiệm không gian lưu trữ). Xem hình
minh họa,
Khi thực hiện commit, Git sẽ tạo ra một “đối tượng commit”,
lưu trong kho chứa Git, gồm các thông tin sau:
– Một con trỏ, trỏ tới snapshot chứa các nội dung (tập tin, thư mục) mà bạn đã
stage
– Tên của tác giả (author), email, và thông điệp đi kèm
(message)
– Có thể có: không, một hoặc nhiều các con trỏ, trỏ tới
commit cha của commit đó. Commit lần đầu không có cha, commit bình thường có một
commit cha, các commit được tạo ra từ việc tích hợp (merge) hai hay nhiều nhánh
sẽ có nhiều commit cha.
Để dễ hiểu, giả sử bạn có một kho chứa gồm ba tập tin
(README, test.rb và LICENSE), khi bạn thực hiện stage ba tập tin,
$ git add README test.rb LICENSE
Git sẽ thực hiện việc “băm” từng tập tin để tạo ra mã SHA-1,
đây là tên mới của tập tin, được sử dụng để thao tác trong Git, Git không quan
tâm tới tên thật của mỗi tập tin. Phiên bản (nội dung) hiện tại của tập tin được
lưu vào kho chứa của Git, mã SHA-1 được thêm vào khu vực stage. Git gọi mỗi
phiên bản của một tập tin trong kho chứa là một blob.
[Blob is an abbreviation for “binary large object”. When we
git add a file such as example_file.txt , git creates a blob object containing
the contents of the file. Blobs are therefore the git object type for storing
files.]
Khi chạy lệnh commit,
$ git commit -m 'initial commit of my project'
Git sẽ “băm” tất cả các thư mục của dự án, mỗi thư mục tạo
thành một đối tượng, các đối tượng này được đặt tên theo mã SHA-1, các đối tượng
được tổ chức theo dạng cây, thư mục gốc tương đương với nút gốc của cây. Cây
này được lưu trong kho chứa của Git. Trong “đối tượng commit” có một con trỏ,
trỏ tới nút gốc của cây, nhờ vậy Git có thể dễ dàng khôi phục được các “ảnh” của
dự án khi cần.
Lúc này kho chứa Git sẽ chứa năm đối tượng sau (xem hình
minh họa),
Năm đối tượng đó là: ba blob (5b1d3, 911eb, cba0a) chứa nội
dung của ba tập tin; một đối tượng chứa thông tin về cây thư mục (92ec2); một
“đối tượng commit” (98ca9) chứa con trỏ, trỏ tới nút gốc của cây thư mục, và
các thông tin về tác giả (author), người commit (committer), thông điệp
(message).
Có thể hiểu nôm na, một ảnh (snapshot) của dự án sẽ gồm cây
thư mục và các blob tương ứng.
Sau khi thực hiện thay đổi trên dự án, rồi commit, thì
commit sau sẽ có một con trỏ, trỏ tới commit trước đó (commit cha). Sau hai lần
commit, lịch sử của dự án sẽ có dạng như sau, (commit đầu tiên có SHA-1 là
98ca9, lần thứ hai là 34ac2, lần thứ ba là f30ab)
Lab 27. Khảo sát một số đối tượng (commit,
tree và blob) trong git.
– Tạo một kho chứa trên máy cục bộ (ví dụ tên là example_objects)
– Tạo một tập tin (example_file.txt) trong kho chứa, thực hiện
add và commit
– Mở thư mục .git/objects sẽ thấy các đối tượng của git được
tạo ra, để ý ba đối tượng đầu tiên (ví dụ: 3d, b2, e6).
– Sử dụng lệnh $ git cat-file –t <mã SHA-1> để kiểm
tra xem loại của ba đối tượng đầu tiên? (kiểm tra xem là: commit, tree, hay blob).
Ví dụ:
$ git cat-file -t
3d67701726ea1086341586101f272b9a029c9510
commit
(nhớ nối thêm 2 kí tự đầu tiên của thư mục vào mã SHA-1, ở
ví dụ trên đã thực hiện nối “3d” vào “67701726ea1086341586101f272b9a029c9510”).
– Sử dụng lệnh $ git cat-file –p <mã SHA-1> để xem nội
dung của các tập tin đi kèm với mã SHA-1. Mã SHA-1 của tập tin example_file.txt
trên máy của bạn là gì?
Ví dụ:
$ git cat-file -p b25d4c8f7e67444504c50e428d23d981bae9f087
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 example_file.txt
– Thêm nội dung cho tập tin example_file.txt, thực hiện
commit, sử dụng $git cat-file –p để xem lại nội dung của tập tin example_file.txt.
Tham khảo: https://matthew-brett.github.io/curious-git/git_object_types.html
Trong Git, nhánh là một con trỏ có thể di chuyển được, nó trỏ tới một trong các commit ở trên. Khi tạo một kho chứa, và thực hiện commit lần đầu là bạn đã tạo ra nhánh mặc định có tên là master, đây là nhánh bình thường như các nhánh sẽ tạo sau này, nhánh master sẽ trỏ tới commit bạn vừa thực hiện. Mỗi khi bạn thực hiện một commit, con trỏ của nhánh master sẽ tự động di chuyển về phía trước.
Hình dưới đây là nhánh master và lịch sử commit của nó, (v1.0
là con trỏ của tag, master là con trỏ nhánh, HEAD là con trỏ cho biết nhánh hiện
thời mà bạn đang làm việc)
3.8.2
Tạo nhánh mới
Tạo nhánh mới chính là việc tạo ra một con trỏ mới, trỏ tới
commit hiện tại và đặt cho con trỏ mới một cái tên. Để tạo nhánh mới sử dụng lệnh
$ git branch <tên nhánh>.
Ví dụ, tạo nhánh mới có tên là testing,
$ git branch testing
Khi đó, kho chứa sẽ có hai nhánh (master và testing), cùng
trỏ tới commit mới nhất (f30ab), và có cùng lịch sử commit. Xem hình minh họa,
Tại một thời điểm, kho chứa có thể có rất nhiều nhánh. Để biết
được hiện thời bạn đang ở nhánh nào? Git sử dụng thêm một con trỏ khác có tên
là HEAD. HEAD là một con trỏ, nó trỏ đến nhánh hiện hành trên kho chứa cục bộ
(nhánh bạn đang làm việc). Khi tạo nhánh mới con trỏ HEAD không tự trỏ tới
nhánh vừa tạo, mà nó vẫn nằm trên nhánh hiện thời, người dùng cần phải thực hiện
lệnh chuyển nhánh một cách tường minh.
Xem hình minh họa về con trỏ HEAD,
Để biết được con trỏ HEAD đang nằm trên nhánh nào? sử dụng lệnh
$ git log với tham số --decorate
Ví dụ, con trỏ HEAD đang trỏ tới nhánh master,
$ git log --oneline --decorate
a89362e (HEAD -> master,
tag: v1.4-lw, tag: v1.3, tag: v1.2, tag: v1.1, origin/master, origin/HEAD,
testing, phienban1) Update testdesktop.txt
c2aa729 Add files via upload
a31e6f1 conglg them mot tap tin
abaf964 commit lan 1 amend
6b49bd0 add noi dung 83_git commit amend thêm m?i
9a99d67 them mot tap tin 8thang3
0cf8354 (tag: v1.4) doi ten tap tin
a6c7b42 doi ten
3.8.3
Chuyển nhánh
Để chuyển qua một nhánh khác (nhánh có tồn tại), cũng có
nghĩa là cho con trỏ HEAD trỏ vào nhánh khác, sử dụng lệnh $ git checkout <tên nhánh>
Ví dụ, chuyển sang nhánh testing và kiểm tra bằng lệnh $ git
log --decorate
$ git checkout testing
Switched to branch 'testing'
$ git log --oneline --decorate
a89362e (HEAD ->
testing, tag: v1.4-lw, tag: v1.3, tag: v1.2, tag: v1.1, origin/master,
origin/HEAD, phienban1, master) Update testdesktop.txt
c2aa729 Add files via upload
a31e6f1 conglg them mot tap tin
abaf964 commit lan 1 amend
6b49bd0 add noi dung 83_git commit amend thêm m?i
9a99d67 them mot tap tin 8thang3
0cf8354 (tag: v1.4) doi ten tap tin
Xem hình minh họa,
Việc chuyển nhánh này có ý nghĩ gì? hãy thay đổi nội dung của
một tập tin, rồi thực hiện commit.
$ git add *
$ git commit -m "test chuyen nhanh"
[testing cd69866] test chuyen nhanh
1 file changed, 1
insertion(+)
$ git log --oneline --decorate
cd69866 (HEAD ->
testing) test chuyen nhanh
a89362e (tag: v1.4-lw, tag: v1.3, tag: v1.2, tag: v1.1,
origin/master, origin/HEAD, phienban1, master) Update testdesktop.txt
c2aa729 Add files via upload
Sau lệnh commit, nhánh hiện thời (con trỏ HEAD đang trỏ tới
testing) sẽ tiến về phía trước một bước.
Xem hình minh họa,
Để ý sẽ thấy, sau khi commit, nhánh testing tiến về phía một
bước, trong khi nhánh master vẫn trỏ tới commit tại thời điểm bạn thực hiện lệnh
$ git checkout để chuyển nhánh.
Giờ sẽ chuyển sang nhánh master,
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
Sau lệnh $ git checkout master, Git sẽ thực hiện hai việc: một
là chuyển con trỏ HEAD quay trở lại nhánh master, hai là phục hồi (revert) các
tập tin trong thư mục làm việc trở về thời điểm mà con trỏ master đang trỏ tới
(phục hồi snapshot). Điều này cũng có nghĩa là các thay đổi bạn thực hiện từ thời
điểm này sẽ được tách ra theo một luồng khác (so với nhánh testing), các thay đổi
khi bạn thực hiện trên nhánh testing đã bị “tua lại” để bạn có thể phát triển dự
án theo một hướng khác.
Xem hình minh họa,
Chú ý: thao tác chuyển nhánh sẽ làm thay đổi các tập tin tại
thư mục làm việc trong kho chứa. Nếu bạn thực hiện chuyển qua các nhánh cũ hơn,
thư mục làm việc cũng được phục hồi lại trạng thái tại thời điểm mà bạn thực hiện
commit cuối cùng trên nhánh. Nếu Git không thực hiện được việc phục hồi lại trạng
thái các tập tin này, cũng có nghĩa là việc chuyển nhánh sẽ không được thực hiện.
Giờ sẽ tạo một số thay đổi trong các tập tin và commit thêm
một lần nữa,
$ git add *
Maxsys@DESKTOP-7LPDOL6 MINGW64 /e/langbiang (master)
$ git commit -m "thay doi tren master branch"
[master 47ed23e] thay doi tren master branch
1 file changed, 1
insertion(+)
$ git log --oneline --decorate
47ed23e (HEAD ->
master) thay doi tren master branch
a89362e (tag: v1.4-lw, tag: v1.3, tag: v1.2, tag: v1.1,
origin/master, origin/HEAD, phienban1) Update testdesktop.txt
c2aa729 Add files via upload
Bây giờ lịch sử của dự án đã được tách ra thành hai nhánh
riêng biệt là master và testing.
Xem hình minh họa,
Tới thời điểm này, bạn đã thực hiện việc tạo và chuyển sang
một nhánh mới (testing), thực hiện thay đổi trên dự án, commit, rồi sau đó chuyển
ngược lại nhánh chính (master), cũng thực hiện thay đổi và commit trên nhánh
master. Sự thay đổi trên tập tin và commit đều được thực hiện một cách biệt lập
trên hai nhánh riêng biệt. Trong quá trình làm việc, bạn có thể di chuyển qua lại
giữa các nhánh để làm việc và tích hợp các nhánh lại khi cần thiết.
Có thể sử dụng lệnh $ git log --oneline --decorate --graph
--all để xem lịch sử các commit, các nhánh và lịch sử chia nhánh.
Ví dụ,
* 47ed23e (HEAD -> master) thay doi tren master branch
| * cd69866 (testing) test chuyen nhanh
|/
* a89362e (tag: v1.4-lw, tag: v1.3, tag: v1.2, tag: v1.1,
origin/master, origin/HEAD, phienban1) Update testdesktop.txt
* c2aa729 Add files via upload
* a31e6f1 conglg them mot tap tin
* abaf964 commit lan 1 amend
* 6b49bd0 add noi dung 83_git commit amend thêm m?i
* 9a99d67 them mot tap tin 8thang3
* 0cf8354 (tag: v1.4) doi ten tap tin
Một nhánh trong Git
là một tập tin chứa 40 kí tự, đó là giá trị SHA-1 của commit mà nhánh đó trỏ tới.
Do vậy, việc tạo và hủy nhánh được thực hiện rất nhanh chóng, chỉ là thao tác
ghi/xóa vào tập tin 41 kí tự (40 kí tự của SHA-1 và một dòng mới).
Lab 28. Thực hiện các yêu cầu sau về
tạo nhánh.
–
Tạo một local repo với ba tập tin bất kì
– Thực hiện thay đổi tên các tập tin và commit ba lần với
các message khác nhau
– Tạo nhánh mới với tên bất kì (ví dụ testing)
– Chuyển sang nhánh testing, thực hiện thay đổi tập tin
trong local repo, thực hiện commit một lần
– Chuyển lại về nhánh master, thực hiện thay đổi tập tin
trong local repo, thực hiện commit một lần
– Điền giá trị SHA-1 của mỗi commit vào bảng sau:
SHA-1 của commit
lần 4 trên nhánh master
|
|||
SHA-1 của commit
lần 1
|
SHA-1 của commit
lần 2
|
SHA-1 của commit
lần 3
|
|
SHA-1 của commit
lần 1 trên nhánh testing
|
|||
</////11
-----------
Cập nhật [27/05/2020]
-----------
Xem thêm: Làm web (12) - Git: phân nhánh căn bản