Article summary
At some point, most developers run into a version of this problem: I need to add code to a repository, but I don’t want everyone who has access to that repo to know it exists. Git doesn’t make the solution to this problem obvious, but there’s a built-in solution: “Git Submodules.”
A submodule lets you treat another Git Repo as a dependency of your project, without intertwining its code with the main repo. The key detail of submodules that solves the code visibility problem is that access to a submodule is controlled independently of the parent repo. If a user does not have access to a child submodule in a parent repository, they will not be able to clone, view, or run it.
Scenario
I recently had to pull in Git Submodules into a project for a student competition that Atomic hosts each year. All atoms (Atomic Object employees) running the tournament needed access to “Tournament-only” bot implementations (easy/medium/hard/etc.), without exposing the bots’ logic to competitors at the competition.
To do that, the main game repo kept a submodule pointer in a directory-with-child-submodule folder instead of embedding any bot logic directly in the parent repo that would be provided to participants. This structure lets hosts with access pull and iterate on bot code in a child repository, while everyone else can still use the parent repo normally but cannot clone or inspect the hidden tournament bots.
How to
1) Initialize Parent Repo
mkdir parent-repo
cd parent-repo
git init
git add .
git commit -m "Initial commit of parent repo"
2) Initialize Child Repo
# In root directory of parent-repo
mkdir child-repo
cd child-repo
git init
# add a README or really anything
git add .
git commit -m "Initial commit of child repo"
Push both the parent-repo and child-repo to a remote. parent-repo can have any access permissions and limit `child-repo’s access to only individuals you want to access.
3) Add Child Repo as a Submodule of Parent
cd ../parent-repo
# declare child-repo as a submodule of parent-repo
git submodule add <CHILD_REPO_REMOTE_URL> child-repo
git commit -m "Add child repo as a submodule"
parent-repo will now contain a .gitmodules file and a directory entry that points at a specific commit in the child repo.
If you clone the repo on another machine, the submodule contents should not be present.
4) Cloning the Submodule
Anyone with access to child-repo can clone both the parent and child repo like so.
git clone <PARENT_REPO_REMOTE_URL>
cd parent-repo
git submodule init
git submodule update
or as a one-liner
git clone --recurse-submodules <PARENT_REPO_REMOTE_URL>
For users who don’t have access to the child repo, the parent clone still works, but submodule fetch/update fails (and the restricted code never appears).
Drawbacks to Consider
Submodules do have some draw backs, it is important to understand how they work before bringing them into your project.
Pinned commit in parent
The parent stores only the submodule’s target commit. If the child changes, someone must update the submodule pointer in the parent repo with a commit.
Extra clone steps
Anyone who needs the submodule must run git submodule init and git submodule update (or clone with --recurse-submodules).
Added Complexity
Submodules introduce another moving part to your repository structure. They work best when the boundary between parent and child code is very clear and unlikely to change frequently.
When a Submodule Makes Sense
Git submodules are a good fit when you need clear separation and independent access control between parts of a system.
Common use cases include restricting visibility of internal implementations (reference solutions for leet code problems) and sharing the same private code across multiple parent repositories. You can also use them for enforcing intentional updates to sensitive or critical dependencies.