Git LFS - Chí Phèo và nồi cháo hành của Thị Nở
Hắn vừa push vừa chửi. Bao giờ cũng thế, cứ rượu vào push code là hắn chửi. Bắt đầu hắn chửi Linus Torvalds. Có hề gì? Linux có của riêng nhà nào? Rồi hắn chửi Mark Zuckerberg…
Bài viết nằm trong nhóm nghiên cứu #hardcore của Ruby Vietnam.
Trước khi tìm hiểu về Packfile ta hãy xem qua cấu trúc dữ liệu của Git.
Như ta đã biết 🤔, Git là một key-value store, với các object có key SHA-1
.
Git có 3 loại objects chính là blob, tree và commit, mỗi loại object có một công dụng khác nhau.
Ta hãy lướt qua ví dụ dưới đây để tìm hiểu xem các objects này làm gì.
Giả sử ta có một Git project có cấu trúc thư mục như sau:
tree .
.
├── a.txt
└── lib
└── b.txt
Git lưu trữ toàn bộ dữ liệu trong thư mục .git
, các Git objects sẽ được lưu tại .git/objects/
dưới dạng binary.
tree .git/objects/
.git/objects
├── 25
│ └── f6d58e2f8207f01e50baf137c1d0a48f78875c
├── b0
│ └── b8a639f4b1f311771acd73fce1a9fec5cd9b47
├── b4
│ └── 9df1d24f68bfe416a3ce02309ff7782d3622a7
├── ba
│ └── 38b7dbf40c2fd2c6ed61dcf2ef61672632d42d
├── c5
│ └── 0ded571243897825481cc7301c3639773ac58f
├── cd
│ └── b89d86c483141673811399b7cc20419283623d
├── f7
│ └── d34ac248f8144e473958a3f4d7aea6c7274c92
├── info
└── pack
Project này đã có một commit là 25f6d58e2f8207f01e50baf137c1d0a48f78875c
, và như đã nói ở trên, commit trong Git là một object. Để xem nội dụng của một object ta có thể dùng lệnh git cat-file -p [SHA-1]
.
git cat-file -p 25f6d58e2f8207f01e50baf137c1d0a48f78875c
tree ba38b7dbf40c2fd2c6ed61dcf2ef61672632d42d
parent cdb89d86c483141673811399b7cc20419283623d
author Cẩm Huỳnh <email@tacgia.com> 1506278351 +0200
committer Cẩm Huỳnh <email@tacgia.com> 1506278351 +0200
add b
Nội dung của object cho thấy commit trỏ đến một tree object có SHA-1 là ba38b7dbf40c2fd2c6ed61dcf2ef61672632d42d
, có parent commit (commit trước đó) là cdb89d86c483141673811399b7cc20419283623d
, thông tin tác giả và người commit, và cuối cùng là message của commit.
Ta sẽ tiếp tục xem nội dung của tree object.
git cat-file -p ba38b7dbf40c2fd2c6ed61dcf2ef61672632d42d
100644 blob b0b8a639f4b1f311771acd73fce1a9fec5cd9b47 a.txt
040000 tree f7d34ac248f8144e473958a3f4d7aea6c7274c92 lib
Ta thấy tree object này chứa một file a.txt
là một blob
(file) và lib
là một tree
(thư mục). Tiếp tục truy vào xem nội dung của blob trên ta thấy object này chứa toàn bộ nội dung của file a.txt
.
git cat-file -p b0b8a639f4b1f311771acd73fce1a9fec5cd9b47
Hello world from a
Dựng lại commit object của chúng ta dưới dạng cây, nó sẽ có hình thù như sau.
Với ví dụ trên, ta rút ra công dụng của 3 loại objects lần lượt là:
blob: lưu trữ nội dung của file.
tree: lưu trữ cây thư mục.
commit: lưu trữ các thông tin commit như tác giả, ngày giờ commit, và tree mà nó trỏ tới.
Với cấu trúc này Git có thể lưu toàn bộ version trong project của bạn một cách dễ dàng.
Giả sử ta sửa nội dung file a.txt
thành Hello from the other side
, khi tạo commit các thao tác của Git lần lượt là:
Tạo ra một blob
object có nội dụng là Hello from the other side
.
Tạo ra một tree
object chứa blob
object được tạo ra cho a.txt
và giữ nguyên thông tin của tree object cho lib
(vì nó không đổi).
Tạo ra một commit
object trỏ đến tree
được tạo ra.
Cấu trúc dữ liệu trên tuy giải quyết vấn đề lưu trữ của Git, nhưng đời luôn cho ta những vấn đề khác.
Giả sử file a.txt
hiện đang có 200,000 dòng, giờ ta cần thêm 1 dòng có nội dung Hello, can you hear me?
vào cuối file.
Theo như cách làm đã được mô tả ở trên, Git sẽ tạo ra một blob
object mới gồm 200,001 dòng, và tạo tree và commit như thông thường. Thành thử ra chỉ với việc thêm 1 dòng, Git đã phải lưu trữ thêm 200,001 dòng dữ liệu. Như vậy thì không được hay lắm cho bộ nhớ.
Và packfile được sinh ra để giải cứu nhân loại.
Packfile sẽ được sinh ra mỗi khi bạn git push
lên remote hoặc gom ve chai với git gc
trên local machine.
Công dụng của Packfile:
Như đã nói Packfile được sinh ra khi chạy git gc
. Vậy ta hãy thử chạy nó cho project.
git gc
Counting objects: 10, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (10/10), done.
Total 10 (delta 0), reused 0 (delta 0)
tree .git/objects/
.git/objects
├── info
│ └── packs
└── pack
├── pack-362927142ab7ba8157829e7f044768c0f9beb047.idx
└── pack-362927142ab7ba8157829e7f044768c0f9beb047.pack
Ồ, sau khi git gc
thì Bố ơi, objects đi đâu thế?
Thật ra thì objects không biến đi đâu cả mà đã được gom vào packfile trong .git/objects/packs
, bao gồm file .idx
và .pack
.
Ta hãy xem packfile đó chứa thông tin gì?
git verify-pack -v .git/objects/pack/pack-362927142ab7ba8157829e7f044768c0f9beb047.idx
662f8069da4f0b2c275658cc8e0f106fb1fe4a53 commit 233 164 12
eeea7aa059a347b0a65ae80921361b25712f8d75 commit 189 138 176
[S] a43ed97bc49bbb5d0dea412cfba84b1bb2df8021 blob 405 53 314
b49df1d24f68bfe416a3ce02309ff7782d3622a7 blob 19 29 367
035821c97042669f86ec82335e997b2f31c008ea tree 63 74 396
f7d34ac248f8144e473958a3f4d7aea6c7274c92 tree 33 44 470
6be958cca4e99fd7656d09d9e7e0f259069ba3d2 tree 63 73 514
[T] d160120d63700bf12e03a2d7222e9d442a83d7ff blob 7 18 587 1 a43ed97bc49bbb5d0dea412cfba84b1bb2df8021
non delta: 7 objects
chain length = 1: 1 object
.git/objects/pack/pack-3f60df1e6f910c5ecd9e0693df0bdffb758a3e62.pack: ok
Hãy xem 2 blob được đánh dấu [T]
và [S]
, đây là version trước và sau khi thêm vào dòng mới của file a.txt
.
Thông tin cột thứ 3 cho biết rằng [S]
có kích thước file là 405 bytes và [T]
là 7 bytes, đồng thời cột cuối của [T]
trỏ đến SHA-1 của [S]
. Có nghĩa blob object [T]
chỉ lưu delta/diff (sự thay đổi), còn [S]
sẽ lưu toàn bộ nội dung file.
Vì sao Git lưu delta ở blob [T]
chứ không phải [S]
? Lý do là để tối ưu hoá tốc độ truy cập, vì thường version sau cùng sẽ được truy cập nhiều nhất.
Các thông tin trong bài viết này chủ yếu được tham khảo ở Git book V2 - chương Git Internals. Chương này cung cấp các kiến thức cơ bản trước khi bạn vào đọc source code của Git.
Mình cũng có vẽ một mind map mà các bạn có thể sử dụng khi đọc sách.
Hắn vừa push vừa chửi. Bao giờ cũng thế, cứ rượu vào push code là hắn chửi. Bắt đầu hắn chửi Linus Torvalds. Có hề gì? Linux có của riêng nhà nào? Rồi hắn chửi Mark Zuckerberg…
DOM là cách thông thường và gần như là chuẩn mực để parse file XML, tuy nhiên với những file XML lớn thì dùng DOM không thực sự hiệu quả. Bài viết giới thiệu cách dùng SAX parser với Saxy.
Bài viết giới thiệu giải thuật The Drunken Bishop của OpenSSH.
Ảnh mind map bị die link rồi bạn ơi