Có hai cách chính để nhúng repo B vào thư mục con của repo A:
- Submodule: A chỉ lưu con trỏ (URL trong
.gitmodules+ SHA commit của B). Code B không nằm trong lịch sử A; người clone phải kéo thêm. - Subtree: A chứa bản copy thật của code B (thường squash thành 1 commit), merge vào thư mục con. Clone A là đủ, không cần lệnh đặc biệt.
Trước khi chọn, hỏi: có thật cần nhúng không? Nếu B là package Python/collection Ansible thì cài qua package manager (uv add, ansible-galaxy) thường đơn giản hơn cả hai.
Mô hình lưu trữ
flowchart LR A["repo app"] -->|".gitmodules + SHA"| SM["vendor/lib (submodule)"] SM -.->|"clone riêng"| B["repo lib"] A2["repo app"] -->|"commit chứa file thật"| ST["vendor/lib (subtree)"] B -->|"git subtree pull"| ST ST -->|"git subtree push"| B
So sánh
| Tiêu chí | Submodule | Subtree |
|---|---|---|
| Repo cha lưu gì | URL + SHA cố định | Toàn bộ file trong thư mục con |
| Clone | Cần --recurse-submodules hoặc submodule update --init |
git clone thường là đủ |
| Ghim version | Rõ ràng: đúng 1 SHA | Theo commit merge gần nhất (xem log) |
| Cập nhật từ upstream | git submodule update --remote rồi commit SHA mới |
git subtree pull --prefix=... --squash |
| Sửa code nhúng và đẩy ngược | Commit trong thư mục submodule, push repo con | git subtree push / split, chậm khi lịch sử dài |
| Người dùng cần biết lệnh đặc biệt | Có, dễ quên --init |
Hầu như không |
| Dung lượng repo cha | Nhỏ | Tăng theo code nhúng |
| CI | Phải bật checkout submodule, cần quyền đọc repo con | Không cần gì thêm |
| Có sẵn | Git core | git subtree nằm trong contrib, đa số bản phân phối có sẵn |
Lệnh submodule
| Mục đích | Lệnh |
|---|---|
Thêm submodule, theo dõi branch main |
git submodule add -b main https://github.com/org/lib.git vendor/lib |
| Clone repo kèm submodule | git clone --recurse-submodules URL |
| Đã clone thường, kéo submodule về | git submodule update --init --recursive |
Xem SHA đang ghim (+ = lệch, - = chưa init) |
git submodule status |
| Nâng submodule lên đầu branch upstream | git submodule update --remote vendor/lib → git add vendor/lib && git commit |
| Xem commit mới của submodule trong diff | git diff --submodule=log |
| Chạy lệnh trong mọi submodule | git submodule foreach 'git log -1 --oneline' |
pull/switch tự cập nhật submodule |
git config --global submodule.recurse true |
| Đổi URL (vd chuyển sang SSH) | Sửa .gitmodules → git submodule sync --recursive |
| Gỡ submodule | git submodule deinit -f vendor/lib && git rm -f vendor/lib, sau đó xoá .git/modules/vendor/lib |
Submodule sau update ở trạng thái detached HEAD. Muốn sửa code trong đó: cd vendor/lib && git switch main trước, nếu không commit dễ bị "lạc" khi update lần sau.
GitHub Actions: actions/checkout cần with: submodules: recursive (repo con private thì cần thêm token có quyền đọc), xem GitHub Actions cheatsheet.
Lệnh subtree
| Mục đích | Lệnh |
|---|---|
| Nhúng repo vào thư mục con (gộp lịch sử thành 1 commit) | git subtree add --prefix=vendor/lib https://github.com/org/lib.git main --squash |
| Kéo bản mới từ upstream | git subtree pull --prefix=vendor/lib https://github.com/org/lib.git main --squash |
Đẩy thay đổi trong vendor/lib lên branch của repo lib |
git subtree push --prefix=vendor/lib https://github.com/org/lib.git from-app |
| Tách lịch sử thư mục con thành branch riêng | git subtree split --prefix=vendor/lib -b lib-export |
Luôn dùng cùng một kiểu (--squash hoặc không) cho mọi lần add/pull của một prefix. Đặt remote cho gọn: git remote add lib https://github.com/org/lib.git rồi dùng lib main thay cho URL.
Khi nào dùng
- Submodule: cần ghim chính xác version, repo con lớn hoặc cập nhật độc lập, nhiều repo cha cùng dùng và team quen lệnh submodule. Ví dụ: bộ YANG model của vendor, firmware SDK.
- Subtree: muốn người clone không phải biết gì thêm, code nhúng nhỏ và ít khi đẩy ngược. Ví dụ: bộ template Jinja2 dùng chung, script tiện ích.
- Không dùng cả hai: khi B có thể phát hành thành package (PyPI, Ansible collection, Helm chart); khi chỉ cần một vài file thì copy và ghi rõ nguồn.
- Tách một thư mục trong monorepo ra repo riêng (giữ lịch sử):
git subtree splitlà cách nhanh không cần tool ngoài.