Git Submodule vs Git Subtree: Đừng để thư viện dùng chung trở thành ‘hố đen’ dự án

Git tutorial - IT technology blog
Git tutorial - IT technology blog

Cú điện thoại lúc 2 giờ sáng và cái bẫy ‘Copy-Paste’

Chuông điện thoại reo lúc 2 giờ sáng thường chẳng bao giờ báo tin vui. Đầu dây bên kia là ông Lead với giọng hốt hoảng: “Server sập rồi, module thanh toán lỗi logic, check gấp em ơi!”.

Sau 30 phút rà soát, mình phát hiện một sự thật cay đắng. Một đồng nghiệp đã copy-paste thư viện payment-utils từ dự án cũ sang dự án này. Tiện tay, bạn ấy sửa thêm 10 dòng logic để chạy kịp deadline. Tuy nhiên, khi thư viện gốc được fix bug ở dự án khác, dự án hiện tại vẫn dùng bản lỗi. Logic bị sai lệch giữa các môi trường khiến hệ thống treo cứng. Cái giá của việc ‘tiện tay’ copy code chính là một đêm trắng fix bug trong hoảng loạn.

Đây là kịch bản kinh điển khi team thiếu quy trình quản lý các thành phần tái sử dụng (reusable components). Thay vì copy-paste thủ công, Git cung cấp hai vũ khí hạng nặng: Git SubmoduleGit Subtree. Mỗi công cụ đều có ưu nhược điểm riêng. Nếu không hiểu rõ bản chất, bạn rất dễ tự “bắn vào chân mình”.

1. Git Submodule: ‘Con trỏ’ đến một commit cụ thể

Hãy tưởng tượng Submodule giống như một shortcut trên Windows. Nó không thực sự chứa code của thư viện bên trong repo chính. Thay vào đó, nó chỉ lưu một đường dẫn và ID của commit (SHA) cụ thể mà bạn đang sử dụng.

Cách triển khai thực tế

Giả sử bạn có dự án web-app và muốn tích hợp thư viện auth-service từ repo khác:

# Thêm submodule vào dự án
git submodule add https://github.com/user/auth-service.git lib/auth-service

# Git sẽ tạo file .gitmodules để theo dõi
git status

Lúc này, folder lib/auth-service sẽ rỗng nếu đồng nghiệp của bạn clone repo về lần đầu. Để kéo code thư viện, họ phải chạy lệnh:

git submodule update --init --recursive

Ưu và nhược điểm

  • Ưu điểm: Repo chính rất nhẹ vì không lưu lịch sử commit của thư viện. Bạn có thể cố định (pin) thư viện ở một version nhất định, đảm bảo app ổn định dù thư viện gốc có thay đổi.
  • Nhược điểm: Cực kỳ phiền phức khi làm việc nhóm. Chỉ cần một người quên push code ở submodule mà đã push repo chính, pipeline CI/CD sẽ báo lỗi ngay lập tức.

2. Git Subtree: ‘Hòa nhập nhưng không hòa tan’

Khác với Submodule, Subtree thực sự bê nguyên code và toàn bộ lịch sử commit của thư viện nhúng thẳng vào repo chính. Nó giống như việc bạn xây một căn nhà nhỏ nằm trọn trong khuôn viên biệt thự của mình.

Lệnh thực thi

Để thêm một thư viện bằng Subtree, mình thường dùng lệnh:

git subtree add --prefix lib/auth-service https://github.com/user/auth-service.git main --squash

Tham số --squash là chìa khóa ở đây. Nó giúp gom hàng trăm commit của thư viện thành một commit duy nhất. Việc này giữ cho git log của dự án chính luôn sạch sẽ, dễ theo dõi.

Tại sao Subtree lại được ưa chuộng?

  • Thân thiện với team: Mọi người không cần học lệnh Git đặc biệt. Họ cứ clone repo về là có đủ code để chạy ngay.
  • Quản lý tập trung: Bạn có thể sửa code thư viện ngay trong repo chính. Sau đó, bạn dùng lệnh push ngược lại repo gốc nếu muốn đóng góp bản vá.
  • Nhược điểm: Dung lượng repo chính sẽ tăng lên do phải gánh thêm lịch sử commit. Ngoài ra, các câu lệnh subtree pull thường khá dài và khó nhớ.

So sánh: Khi nào chọn kiếm, khi nào chọn cung?

Dựa trên kinh nghiệm thực chiến tại các dự án lớn, đây là bảng so sánh giúp bạn quyết định nhanh:

Tiêu chí Git Submodule Git Subtree
Cấu trúc repo Chỉ lưu link (commit SHA) Lưu trực tiếp code + history
Trải nghiệm team Phức tạp, dễ gây lỗi “missing commit” Đơn giản, như folder bình thường
Khả năng chỉnh sửa Phải chuyển sang repo con Sửa trực tiếp tại chỗ
Trường hợp dùng Thư viện bên thứ 3, ít khi sửa Module nội bộ, cần update liên tục

Bài học xương máu từ những lần ‘bay màu’ code

Làm việc với Git nâng cao không tránh khỏi những lúc sai lầm. Mình từng có lần sửa lỗi gấp ở một submodule rồi dùng git push --force để dọn dẹp lịch sử cho đẹp. Hậu quả là mình đè mất toàn bộ code quan trọng của đồng nghiệp vừa push lên trước đó. Từ đó, mình rút ra quy tắc: Tuyệt đối không dùng --force khi chưa hiểu rõ cấu trúc cây thư mục.

Nếu dự án dùng npm, composer hay pip, hãy ưu tiên các trình quản lý gói này cho thư viện bên thứ ba. Chỉ nên dùng Submodule hoặc Subtree khi bạn cần quản lý các module do chính team mình phát triển và muốn chia sẻ giữa nhiều dự án khác nhau.

Quy trình chuẩn khi dùng Subtree cho module nội bộ:

  1. Thêm: Đưa module vào thư mục /shared.
  2. Sửa: Nếu phát hiện bug, hãy sửa trực tiếp trong project đang làm.
  3. Đẩy: Dùng git subtree push để cập nhật bản fix về repo gốc cho các dự án khác sử dụng.
# Ví dụ đẩy code từ subtree về repo gốc
git subtree push --prefix=lib/auth-service https://github.com/user/auth-service.git main

Tạm kết

Quản lý code không chỉ là add, commitpush. Khi dự án phình to, việc tách nhỏ các thành phần sẽ giúp hệ thống dễ bảo trì hơn. Đừng đợi đến lúc 2 giờ sáng phải thức trắng vì mớ code copy-paste hỗn độn. Hãy chọn một phương án quản lý thông minh ngay từ hôm nay!

Share: