Git LFS: cách lưu tệp lớn trong kho Git
Git LFS (Large File Storage) là tiện ích mở rộng chính thức của Git, lưu các tệp nhị phân lớn trên một máy chủ riêng và chỉ đưa vào kho các tệp con trỏ nhỏ. Clone nhanh hơn, lịch sử không phình ra, còn thao tác với tệp vẫn quen thuộc: git add, commit và push.
Git LFS là gì và vì sao cần
Git LFS (Large File Storage) là tiện ích mở rộng chính thức của Git để lưu các tệp lớn: video, bản thiết kế PSD, bộ dữ liệu, mô hình mạng nơ-ron, tài nguyên game. Thay vì đưa chính tệp vào commit, Git chỉ đưa một con trỏ văn bản khoảng 130 byte, còn nội dung được gửi tới kho LFS riêng trên máy chủ. Khi checkout, tiện ích thay con trỏ bằng tệp thật trong thư mục làm việc, nên với bạn mọi thứ vẫn giống Git thông thường.
Vấn đề mà git lfs giải quyết nằm ở cách Git hoạt động. Mỗi phiên bản của mỗi tệp được lưu mãi mãi trong lịch sử, và khi clone, toàn bộ lịch sử được tải về. Nếu bạn đã đổi một đoạn video 200 MB mười lần, kho đã nặng khoảng 2 GB, và mỗi lập trình viên mới phải tải cả mười phiên bản dù chỉ cần bản mới nhất. Cơ chế nén và delta hiệu quả với văn bản thì gần như vô dụng với tệp nhị phân.
Với LFS, khi clone chỉ tải các phiên bản của tệp lớn ứng với commit hiện tại. Các phiên bản cũ vẫn nằm trên máy chủ và được tải khi bạn chuyển sang commit cũ.
Git LFS hoạt động bên trong ra sao
Cơ chế dựa trên bộ lọc của Git — clean và smudge. Khi bạn git add một tệp mà LFS theo dõi, bộ lọc clean chạy: nó đưa nội dung vào bộ nhớ đệm cục bộ .git/lfs/objects và chuyển con trỏ vào index. Khi checkout, bộ lọc smudge làm ngược lại: tìm đối tượng theo hash trong bộ nhớ đệm (hoặc tải từ máy chủ) và ghi tệp thật ra đĩa.
Con trỏ có dạng như sau:
version https://git-lfs.github.com/spec/v1
oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393
size 209715200
Ba dòng: phiên bản đặc tả, SHA-256 của nội dung và kích thước tính bằng byte. Chính những dòng này được lưu trong lịch sử Git, vì vậy lịch sử vẫn nhẹ. Khi git push, một hook pre-push chạy: trước tiên các đối tượng mới được tải lên qua API của LFS, sau đó các commit được gửi bằng giao thức Git thông thường.
Những tệp nào được chuyển cho LFS do tệp .gitattributes ở gốc kho quyết định. Tệp này được commit cùng mã nguồn, nên các quy tắc giống nhau với cả nhóm.
Cách cài đặt Git LFS
Trên Git cho Windows, tiện ích đã có sẵn trong bộ cài. Trên macOS và Linux, cài bằng trình quản lý gói:
# macOS (Homebrew)
brew install git-lfs
# Debian / Ubuntu
sudo apt install git-lfs
# Fedora
sudo dnf install git-lfs
Sau khi cài, chạy một lần cho người dùng:
git lfs install
Lệnh ghi bộ lọc LFS vào ~/.gitconfig toàn cục và cài hook vào kho hiện tại nếu bạn đang đứng trong kho. Để kiểm tra mọi thứ đã sẵn sàng, dùng git lfs version; còn cấu hình chi tiết (địa chỉ máy chủ, đường dẫn bộ nhớ đệm) hiển thị bằng git lfs env.
Cách bắt đầu theo dõi tệp lớn
Kịch bản điển hình cho dự án mới:
git lfs track "*.psd"
git lfs track "*.mp4"
git lfs track "models/**"
git add .gitattributes
git add design/banner.psd
git commit -m "Thêm bản thiết kế banner qua LFS"
git push origin main
Dấu nháy quanh mẫu là bắt buộc: không có chúng, shell tự mở rộng *.psd thành danh sách tệp trong thư mục hiện tại và quy tắc sẽ sai. Sau git lfs track, trong .gitattributes xuất hiện dòng:
*.psd filter=lfs diff=lfs merge=lfs -text
Thuộc tính -text tắt việc chuyển đổi ký tự xuống dòng, vốn sẽ làm hỏng tệp nhị phân. Thứ tự rất quan trọng: quy tắc track phải có trước, rồi mới git add tệp. Nếu tệp đã được thêm theo cách thông thường trước khi có quy tắc, nó vẫn nằm trong lịch sử như một blob bình thường.
Kiểm tra tệp thực sự đã nằm trong LFS
git lfs ls-files
4d7a214614 * design/banner.psd
9c1e0b7a3f * video/intro.mp4
git lfs status
git lfs ls-files hiển thị các tệp của commit hiện tại được lưu trong LFS. Dấu sao nghĩa là nội dung đã được tải về, dấu gạch ngang nghĩa là trong bản làm việc chỉ có con trỏ. Danh sách mẫu theo dõi hiển thị bằng git lfs track không kèm tham số, còn bỏ quy tắc bằng git lfs untrack "*.psd".
Clone và lấy tệp
Lệnh git clone thông thường hoạt động trong suốt với LFS: nếu tiện ích đã cài, tệp lớn được tải tự động. Nếu đồng nghiệp chưa cài LFS, trong bản làm việc của họ sẽ là các con trỏ văn bản thay cho hình ảnh — đây là triệu chứng phổ biến nhất của «sao thay vì PSD lại là một đoạn chữ». Cách khắc phục là cài đặt và chạy:
git lfs install
git lfs pull
git lfs pull tải các đối tượng của commit hiện tại và đặt chúng vào thư mục làm việc. Còn git lfs fetch chỉ tải về bộ nhớ đệm mà không đụng tới tệp. Với cờ --all, nó tải đối tượng cho mọi tham chiếu, hữu ích khi cần sao lưu đầy đủ hoặc chuyển sang máy chủ khác.
Clone mà không tải tệp lớn
Trong CI, hoặc khi chỉ cần mã nguồn, tải hàng gigabyte tài nguyên là không cần thiết. Biến môi trường sau sẽ tắt bộ lọc smudge:
GIT_LFS_SKIP_SMUDGE=1 git clone https://example.com/team/game.git
cd game
git lfs pull --include="textures/ui/**"
Kho được clone với các con trỏ, sau đó bạn chọn tải những tệp cần thiết qua --include (cũng có --exclude). Cách này tăng tốc đáng kể các bản build trong pipeline.
Chuyển tệp đã có sẵn vào Git LFS
Tình huống thường gặp: kho đã phình to, các tệp lớn nằm trong lịch sử. Lệnh git lfs track không tác động tới quá khứ — nó chỉ ảnh hưởng tới các commit mới. Để viết lại lịch sử, dùng git lfs migrate.
Trước hết xem thứ gì đang chiếm dung lượng:
git lfs migrate info --everything --top=10
Sau đó chuyển các loại cần thiết trong mọi nhánh:
git lfs migrate import --include="*.psd,*.mp4" --everything
git push --force --all
git push --force --tags
Việc chuyển đổi viết lại mọi commit có các tệp này, nghĩa là mọi hash đều thay đổi. Đây là câu chuyện giống như rebase các nhánh đã công bố: cần báo trước cho tất cả thành viên, sau khi push ép buộc họ phải clone lại kho hoặc đặt lại nhánh của mình một cách cứng. Trước khi chuyển, hãy sao lưu (git clone --mirror).
Nếu không thể viết lại lịch sử, có cách nhẹ hơn: git lfs migrate import --no-rewrite đường/dẫn/tệp. Lệnh này tạo một commit mới đưa tệp vào LFS kể từ thời điểm đó. Các phiên bản cũ vẫn nằm trong lịch sử, nhưng kho ngừng phình ra.
Khóa tệp: bảo vệ khỏi xung đột với tệp nhị phân
Hai nhà thiết kế cùng sửa một PSD — Git không thể gộp thay đổi của họ, và một phần công việc sẽ bị mất. Với những tệp như vậy, LFS có cơ chế khóa. Đánh dấu loại tệp là có thể khóa:
git lfs track "*.psd" --lockable
Trong .gitattributes sẽ có thêm thuộc tính lockable, và các tệp như vậy trong bản làm việc sẽ chỉ đọc cho đến khi bạn khóa chúng:
git lfs lock design/banner.psd
git lfs locks
design/banner.psd anna ID:123
# sau khi push thay đổi
git lfs unlock design/banner.psd
Khi tệp đang bị khóa, lần push thay đổi của người khác vào tệp đó sẽ bị từ chối. Khóa do máy chủ quản lý, nên chúng hoạt động trên các dịch vụ Git lớn, nhưng trên máy chủ tự xây dựng có thể không có. Quản trị viên có thể gỡ khóa của người khác bằng git lfs unlock --force.
Đối tượng LFS được lưu ở đâu và tốn bao nhiêu
Các đối tượng LFS nằm trên máy chủ hỗ trợ LFS API. Các dịch vụ Git phổ biến và giải pháp tự triển khai, cũng như các máy chủ riêng biệt, đều hỗ trợ điều này. Các dịch vụ lưu trữ đám mây có hạn mức dung lượng và băng thông LFS, khác nhau giữa các dịch vụ và gói cước — hãy kiểm tra điều kiện hiện hành của nền tảng bạn dùng trước khi đưa lên đó bộ dữ liệu hàng trăm gigabyte.
Lưu ý rằng băng thông được tính mỗi lần tải về: một CI clone kho có tệp LFS sau mỗi commit có thể làm cạn hạn mức nhanh hơn người. Hãy lưu bộ nhớ đệm .git/lfs giữa các lần build, hoặc dùng GIT_LFS_SKIP_SMUDGE.
Bộ nhớ đệm cục bộ cũng phình ra theo thời gian. Lệnh git lfs prune xóa các đối tượng cũ đã có trên máy chủ và không cần cho các commit hiện tại và gần đây.
Khi nào nên dùng Git LFS, khi nào không
| Tình huống | Có nên dùng LFS? | Ghi chú |
|---|---|---|
| Tài nguyên game, bản thiết kế, âm thanh, video | Có | Trường hợp kinh điển: tệp nhị phân thay đổi thường xuyên |
| Mô hình ML và bộ dữ liệu cỡ vừa đi kèm mã | Có, cân nhắc hạn mức | Với dữ liệu rất lớn, nên dùng công cụ quản lý phiên bản dữ liệu chuyên dụng |
| Sản phẩm build đã biên dịch | Không | Nên đặt ở kho artifact hoặc phần release, không đặt trong Git |
| Phụ thuộc (node_modules, vendor) | Không | Được cài bởi trình quản lý gói, hãy thêm vào .gitignore |
| Tệp văn bản lớn (dump SQL, CSV) | Tùy trường hợp | Git lưu văn bản bằng delta khá hiệu quả; LFS cần khi tệp rất lớn và hầu như không đọc được trong diff |
| Bí mật và khóa riêng | Không bao giờ | LFS không mã hóa dữ liệu, đây không phải kho bí mật |
Lỗi thường gặp và cách sửa
Trong bản làm việc là một con trỏ văn bản thay vì tệp
LFS chưa được cài, hoặc bản clone được tạo với GIT_LFS_SKIP_SMUDGE=1. Chạy git lfs install và git lfs pull.
Tệp lọt vào lịch sử không qua LFS
Quy tắc track được thêm sau git add. Nếu commit chưa được gửi đi, gỡ tệp khỏi index (git rm --cached tệp), đảm bảo quy tắc có trong .gitattributes, rồi thêm lại. Nếu đã gửi — dùng git lfs migrate import. Tìm các tệp như vậy giúp git lfs fsck --pointers.
Push bị từ chối vì kích thước tệp
Nhiều dịch vụ giới hạn kích thước một đối tượng trong Git thông thường. Thông báo kiểu «this exceeds the file size limit» nghĩa là tệp đáng lẽ phải được LFS xử lý. Hãy viết lại commit cuối hoặc lịch sử như đã mô tả ở trên — commit đơn giản «xóa tệp» không giúp được, vì tệp vẫn nằm trong các commit trước.
Sau git lfs migrate, đồng nghiệp gặp lỗi
Nhánh cục bộ của họ dựa trên các hash cũ. An toàn nhất là clone lại kho; nếu có công việc chưa đẩy lên, chuyển nó sang nhánh mới bằng cherry-pick.
Bảng tra cứu lệnh Git LFS
| Lệnh | Tác dụng |
|---|---|
git lfs install | Thiết lập bộ lọc và hook |
git lfs track "*.đuôi" | Thêm mẫu vào .gitattributes |
git lfs untrack "*.đuôi" | Gỡ mẫu |
git lfs ls-files | Các tệp LFS của commit hiện tại |
git lfs pull | Tải và đặt các đối tượng vào thư mục làm việc |
git lfs fetch --all | Tải đối tượng của mọi tham chiếu vào bộ nhớ đệm |
git lfs migrate import --include="..." --everything | Chuyển tệp từ lịch sử vào LFS |
git lfs lock / unlock / locks | Làm việc với khóa |
git lfs prune | Dọn bộ nhớ đệm cục bộ khỏi các đối tượng cũ |
git lfs env | Chẩn đoán cấu hình |
Tóm lại
Git LFS chuyển các tệp nhị phân lớn ra khỏi lịch sử vào một kho riêng, để commit chỉ chứa con trỏ nhẹ. Quy tắc chính: cấu hình git lfs track trước commit đầu tiên có tệp lớn, commit .gitattributes và theo dõi hạn mức của dịch vụ lưu trữ. Nếu tệp đã nằm trong lịch sử, git lfs migrate sẽ giúp, nhưng đó là việc viết lại lịch sử với đầy đủ hệ quả. Làm việc với lịch sử, rebase và các công cụ nâng cao khác của Git được hướng dẫn thực hành trong khóa học Git.
Câu hỏi thường gặp
Git LFS có miễn phí không?
Bản thân tiện ích là mã nguồn mở và miễn phí. Bạn có thể phải trả tiền cho dung lượng và băng thông ở dịch vụ Git nếu vượt hạn mức gói miễn phí.
Tệp bao nhiêu MB thì nên dùng LFS?
Không có ngưỡng cứng; thường người ta đưa vào LFS các tệp nhị phân từ vài megabyte và thay đổi thường xuyên. Tệp văn bản dù lớn, Git vẫn lưu hiệu quả mà không cần LFS.
Có thể bỏ Git LFS và đưa tệp về Git thông thường không?
Có, bằng lệnh git lfs migrate export --include="*.psd" --everything, lệnh này viết lại lịch sử để đưa tệp trở lại. Sau đó cần xóa quy tắc khỏi .gitattributes và push ép buộc.
git diff có hiển thị thay đổi của tệp trong LFS không?
Git chỉ hiển thị thay đổi của con trỏ, không phải nội dung, vì tệp là nhị phân. Với hình ảnh và tài liệu, việc so sánh được thực hiện bằng công cụ bên ngoài.
Mọi thành viên trong nhóm có cần LFS không?
Có. Nếu không, người không cài LFS sẽ có con trỏ thay vì tệp trong bản làm việc, và commit của họ có thể vô tình thêm tệp lớn ngoài LFS.
Chủ đề này được giảng chi tiết kèm thực hành trong khóa học “Git с нуля до уверенного разработчика”.
Đến khóa học