Giải phẫu Git


Nếu bạn là developer chắc chắn bạn đã dùng git, không những thế bạn còn dùng chúng hàng ngày luôn. vậy có bao giờ bạn tự hỏi git nó được cấu tạo như nào hay không. Chuyện gì nếu ta commit code, hay merge code xử lí như thế nào?
Trong bài hôm nay chúng ta hãy cùng "giải phẫu" git nhé, một phần mềm mang tính cách mạng trong giới lập trình. Hãy chắc chắn bạn đã nằm lòng cách sử dụng một số lệnh cơ bản như git commit, git branch, git merge nhé.
Tất cả mọi thứ là hash
Đúng vậy bạn không nghe nhầm đâu, thế giới bên trong git chỉ toàn là hash và hash, chính code của bạn cũng là hash đó. Bạn hãy nắm rõ ý tưởng này nhé vì nó liên quan đến phần tiếp.
Git Object - blob, tree và commit
Trước tiên sơ lược qua về git, thì thành phần cơ bản nhất của git là object. Object ở đây có thể là blob hoặc tree hoặc commit. Và tất nhiên, tất cả chúng đều đã được định danh bằng 1 mã hash.
Blob
binary large object: chứa nội dung code của chúng ta. Blob khác file ở chỗ file thì có chứa thêm meta data (như ngày tạo hay tên file), còn blob thì không, nó chỉ chứa nội dung file là raw byte
Blob được định danh bằng 1 mã hash SHA-1 bằng cách hash từ content của blob
Tree
Ánh xạ từ cấu trúc thư mục code cùa chúng ta
Cũng được định danh bằng mã hash SHA-1
Content của nó trỏ đến mã hash của blob hay tree khác. Tưởng tượng rằng blob là nút lá, còn tree là một nút khác nút lá ở trên cây
ở ví dụ trên, tree tương đương với 1 file system với root dir có 1 file /test.js và 1 thư mục con /docs. Thư mục /docs có 2 file /docs/pic.png và /docs/1.txt.
Commit
Commit thì quá quen rồi, nó là kết quả của câu lệnh git commit ta dùng hàng ngày. Commit giống như snapshot, ghi lại thời trạng thái của toàn bộ thư mục tại thời điểm đó.
Commit gồm có 1 pointer trỏ đến hash của root tree, author - người commit, message - nội dung commit message, và thời gian commit. Đặc biệt nhất commit còn trỏ đến 1 hoặc nhiều commit gần nhất của nó (trường hợp nhiều chính là khi ta merge code) gọi là commit cha
Tất nhiên nó vẫn được định danh bằng hash SHA-1
Mỗi commit là toàn bộ snapshot khi ta git commit, chứ không đơn giản là chỉ chứa những sự thay đổi so với lần commit trước đó
Góc giải đáp
Đến đây, sẽ có bạn thắc mắc, nếu commit mỗi lần đều chứa toàn bộ snapshot tại thời điểm đó, vậy ta sẽ phải lưu rất rất nhiều data mỗi lần commit chứ nhỉ.
OK hãy cùng đi luôn vào 1 ví dụ nhé.
Ở trên cây thư mục trên, giả sử ta đổi nội dung file 1.txt có content là HELLO WORLD thành HELLO WORLD! Ở đây có thể thấy tree đã thay đổi và blob đã thay đổi (màu đỏ)
Thoạt nhìn có thể thấy là commit mới lưu rất nhiều data, nhưng nếu nhìn kĩ thì ta thấy, các phần không thay đổi vẫn được giữ nguyên.
Kết luận, nếu object không đổi, ta không cần tạo thêm clone của object đó mà vẫn giữ nguyên
Recap
Blob: nội dung của file
Tree: ánh xạ thư mục, trỏ đến các blob và tree khác
Commit: snapshot của working tree
Giả sử tôi có 1 file có content "Hello world" và bạn cũng có 1 file tương tự như vậy. Vậy mã hash blob của nó là như nhau vì đơn giản nó hash từ nội dung của file
Giả sử tôi có 1 folder chứa folder con và các files khác. Bạn cũng có 1 folder y nguyên như vậy từ tên file cho đến cấu trúc,.. Vậy mã hash tree là giống nhau
Nhưng nếu bạn commit, tôi cũng commit thì khả năng cao mã hash là khác nhau vì nó hash từ các nôi dung Tác giả, commit message, thời gian commit
Branch
Branch chỉ đơn giản là 1 cách định danh do người dùng tạo ra để trỏ đến commit.
Branch sẽ trỏ tới commit mới nhất.
Khi ta dùng git branch, cả 2 branch cùng trỏ tới 1 commit.
Con trỏ HEAD cho biết ta đang làm việc ở branch nào. Thông thường nó sẽ trỏ tới branch, khi ta checkout về 1 commit trong quá khứ thì HEAD sẽ trỏ trực tiếp tới commit
Cách hoạt động
Branch hiện tại là master, đang trỏ tới commit mới nhất.
Khi ta "git branch test", git tạo thêm con trỏ branch test. 2 con trỏ cùng trỏ vào commit mới nhất.
Khi ta làm việc trên master, HEAD trỏ trực tiếp tới master
khi "git checkout test", HEAD trỏ về test
khi ta git commit trên nhánh test, test trỏ tới commit mới nhất, HEAD vẫn trỏ về test
git check out master, HEAD lại trỏ về master
git commit trên master, master trỏ tới commit mới nhất.
Cách git lưu lại sự thay đổi
Khi ta làm việc với source code tức là ta đang làm việc với working dir (chính là folder chứa folder .git)
Sau khi chúng ta thay đổi code, ta muốn lưu lại code đó, thì lúc này nó sẽ được lưu trữ trên Repository.
Tuy nhiên code không bay thằng từ Working dir đến Repo, mà nó phải qua một vùng trung gian là Index hay Staging Area
File trong working dir có 2 trạng thái: Tracked và Untracked. Tracked là các file đã được commit hoặc nằm ở staging area. Untracked là các file còn lại.
Test lý thuyết
Lý thuyết đủ rồi, giờ hãy kiểm chứng lại bằng thực nghiệm nhé.
Đầu tiên mình sẽ init 1 repo có tên là repo_1 bằng lệnh
git init repo_1
Để kiểm tra kết của câu lệnh trên, ta có thể sử dụng lệnh tree /f .git. Kết quả là cấu trúc các file có trong foler .git.
Hãy tạo 1 file bất kì trong folder, ví dụ lệnh: echo hello > new_file.txt
Tiếp đến ta add rồi commit file vừa rồi. Sau đó chạy lại lệnh tree /f .git, thu được kết quả
Đã có rất nhiều thay đổi. Ở đây bạn chú ý rằng trong foler objects các hash có cùng 2 kí tự đầu sẽ chung 1 folder và có tên là 2 kí tự đó.
Ta để ý folder logs là nơi ghi lại lịch sử các thao tác với repo như là commit, merge, rebase, reset,...; folder refs chứa các con trỏ branch và tag và folder objects chứa.
Cảm ơn các bạn đã theo dõi!
Subscribe to my newsletter
Read articles from Huy Nguyen directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Huy Nguyen
Huy Nguyen
I am a software engineer with 4 years of experience in developing web applications. My expertise lies in backend development, and I have a deep interest in problem-solving, algorithms, system design, and databases. I am always eager to learn and embrace challenging projects, striving to deliver applications that exceed user expectations. I also love sharing my knowledge and learning from others to foster mutual growth and improvement