
Git internals là gì? Đó là phần lõi bên trong Git — cách nó lưu blob, tree và commit dưới dạng đối tượng có địa chỉ SHA-1, vì sao có vùng staging, và tại sao một commit đã mất vẫn có thể cứu lại được. Bài này bóc cấu trúc thật của kho mã nguồn theo sách Pro Git và tài liệu lệnh chính thức, thay vì chỉ lặp lại cách dùng git commit như hướng dẫn nhập môn.

Phần lớn người dùng Git coi nó là công cụ để lưu lịch sử thay đổi. Nhưng Git không lưu “các thay đổi” — nó lưu các ảnh chụp toàn bộ nội dung, rồi dùng con trỏ trỏ tới từng ảnh chụp. Hiểu đúng điều đó giải thết gần như mọi hành vi kỳ lạ: vùng staging có nghĩa gì, vì sao git commit không tự ghi thêm thay đổi, tại sao nhánh chỉ là một con trỏ di chuyển, và tại sao xoá nhánh không xoá ngay dữ liệu.
Đơn vị lưu trữ: ba loại đối tượng và cách chúng đánh địa nội dung
Theo chương Git Internals trong Pro Git, Git có đúng ba loại đối tượng cốt lõi. Mỗi đối tượng được đánh số bằng giá trị băm SHA-1 của chính nội dung nó, nên cùng một nội dung luôn cho cùng một địa chỉ, không có cách nào sửa mà không đổi địa chỉ.
- Blob. Chứa nội dung tệp thô, kèm tiêu đề dạng
blob kich-thuocphía trước, với kích thước tính bằng byte. SHA-1 được tính trên cả tiêu đề lẫn nội dung. - Tree. Một mục chứa danh sách các mục khác, mỗi mục gồm chế độ quyền, loại đối tượng, SHA-1 và tên tệp, ví dụ
100644 blob hash ten-tep. Tree là ảnh chụp một thư mục tại một thời điểm. - Commit. Trỏ tới một tree, có zero hoặc nhiều dòng
parent, kèmauthorvàcommittergồm tên, email, mốc thời gian, rồi một dòng trống và nội dung thông điệp commit.
Trên đĩa, các đối tượng rời rạc nằm trong .git/objects/, chia theo hai ký tự hex đầu tiên của SHA-1 thành thư mục con, 38 ký tự còn lại là tên tệp. Chính vì vậy hai nhánh có thể cùng chia sẻ tệp vật lý: chúng chỉ là những con trỏ khác nhau trỏ vào cùng một tập đối tượng.
Ba mức trạng thái khi một dòng code đi từ bàn phím tới lịch sử
Hiểu git add và git commit chính xác nghĩa là hiểu vùng staging tồn tại để làm gì. Theo tài liệu git-add, lệnh git add băm nội dung tệp, tạo một đối tượng blob, rồi ghi một mục vào tệp nhị phân .git/index. Tệp index này liệt kê cho từng tệp được theo dõi: đường dẫn, chế độ quyền, SHA-1, cùng các trường thống kê như thời điểm sửa đổi và kích thước.
Nói cách khác, index chính là bản dự thảo cho commit kế tiếp — nó không phải bản sao của thư mục làm việc, mà là một ảnh chụp thứ hai có thể khác hoàn toàn với thư mục đang làm việc.
| Mức | Thứ gì nằm ở đây | Lệnh xem nội dung | Lệnh chuyển sang mức kế |
|---|---|---|---|
| Thư mục làm việc | Tệp bạn đang sửa, chưa được theo dõi hoặc đã sửa | Trình soạn thảo, git status |
git add đưa vào index |
| Index (vùng staging) | Bản dự thảo của commit kế tiếp, đã có đối tượng blob | git diff --staged |
git commit ghi tree rồi commit |
| Lịch sử commit | Cây đối tượng bất biến, truy cập qua SHA-1 | git log, git show |
Không sửa được — tạo đối tượng mới |

Ngay sau khi lệnh git commit hoàn tất, nó tạo một tree từ nội dung index hiện tại, rồi tạo đối tượng commit trỏ tới tree đó cùng các parent phù hợp, theo đúng mô tả của git-commit. Đây cũng là lý do một tệp đã sửa nhưng chưa git add sẽ không bao giờ nằm trong commit dù bạn có bấm commit liên tục.

HEAD, nhánh và reflog: vì sao cứu lại được commit đã mất
Theo chương Git References trong Pro Git, tệp .git/HEAD thường chỉ chứa một dòng ref: refs/heads/ten-nhanh, tức một con trỏ gián tiếp. Nhánh, thẻ và các ref khác đều chỉ là những tệp chứa một SHA-1. Khi bạn kiểm tra trực tiếp một thẻ hoặc một commit cụ thể, HEAD chứa thẳng SHA-1 và trạng thái đó gọi là HEAD tách rời.
Điểm mấu chốt nằm ở .git/logs/HEAD: Git ghi lại mọi lần HEAD được cập nhật cùng với người thực hiện và thời điểm. Nhờ đó ngay cả sau khi đã viết lịch sử, git reflog vẫn liệt kê các SHA-1 cũ của HEAD và bạn có thể tạo một ref trỏ tới chúng để lấy lại commit đã xoá.
Cơ chế này giải thích một sự thật ít người biết: nhánh bị xoá thường không làm mất dữ liệu ngay lập tức. Các đối tượng vẫn còn cho tới khi một thao tác bảo trì dọn chúng đi.
Bảo trì: đóng gói, nén delta và dọn rác
Đối tượng rời rạc tiêu tốn chỗ và tra cứu chậm. git gc gom các đối tượng rời rạc vào một tệp pack kèm tệp chỉ mục, nén chúng bằng cơ chế delta như packfiles, rồi loại bỏ những đối tượng không còn tới được từ bất kỳ ref nào quá hạn cấu hình gc.pruneExpire. Cơ chế delta lưu một đối tượng làm gốc và các đối tượng khác dưới dạng chênh lệch so với gốc, nên hai phiên bản gần giống nhau chỉ tốn chi phí bằng một.
Hai lệnh nên nhớ khi điều tra sự cố kho mã nguồn:
git count-objects— báo cáo số đối tượng rời rạc và kích thước tệp pack, cho biết đã chạy gc hay chưa.git fsck— liệt kê các đối tượng treo lơ lửng, tức không còn tới được từ ref nào;git prunemới là lệnh thực sự xoá chúng khỏi cơ sở dữ liệu đối tượng.
Ba cấu hình hay bị hiểu nhầm
- Clone nông.
git clone --depth Ntạo kho chỉ có N commit gần nhất, ghi các SHA-1 bị cắt vào.git/shallow; lệnh này ngầm định--single-branchtrừ khi bạn ghi đè. Repo nông không phải repo đầy đủ, các lệnh cần lịch sử sẽ báo lỗi. - Worktree liên kết.
git worktree addtạo thư mục làm việc mới có HEAD và index riêng trong.git/worktrees/ID/nhưng dùng chung cơ sở dữ liệu đối tượng và tệp cấu hình với worktree chính. Nhờ vậy có thể mở hai nhánh song song mà không cần nhân bản kho. - SHA-1 và địa chỉ nội dung. Do địa chỉ sinh ra từ nội dung, hai nhánh cùng nội dung dùng chung đối tượng, và bất kỳ thay đổi nào cũng tạo đối tượng mới. Đây là lý do kho mã nguồn có thể phình to mà lịch sử không bao giờ bị ghi đè theo nghĩa thông thường.
Nguồn tham khảo: Pro Git — Git Internals: Git Objects, Pro Git — Packfiles, Pro Git — Maintenance and Data Recovery, cùng tài liệu lệnh git-add, git-commit, git-fsck, git-clone và git-worktree.
