0
0
Gitdevops~7 mins

Submodules vs subtrees comparison in Git - CLI Comparison

Choose your learning style9 modes available
Introduction
Sometimes you want to include one project inside another. Git offers two ways: submodules and subtrees. Both help keep projects separate but linked, solving the problem of managing code dependencies across repositories.
When you want to include a library or tool from another repository inside your project without copying its code directly.
When you want to keep the included project’s history separate but still track its changes.
When you want to be able to update the included project independently from your main project.
When you want to avoid complex commands and prefer simpler workflows for including external code.
When you want to contribute back changes to the included project easily.
Commands
This command adds an external repository as a submodule inside your project at the path libs/lib. It links the external repo but keeps it separate.
Terminal
git submodule add https://github.com/example/lib.git libs/lib
Expected OutputExpected
Cloning into 'libs/lib'... remote: Enumerating objects: 100, done. remote: Counting objects: 100% (100/100), done. remote: Compressing objects: 100% (80/80), done. remote: Total 100 (delta 20), reused 50 (delta 10), pack-reused 0 Receiving objects: 100% (100/100), 10.00 KiB | 1.00 MiB/s, done. Resolving deltas: 100% (20/20), done.
This command initializes and fetches all submodules, making sure their content is downloaded and ready to use.
Terminal
git submodule update --init --recursive
Expected OutputExpected
Submodule 'libs/lib' (https://github.com/example/lib.git) registered for path 'libs/lib' Cloning into 'libs/lib'... remote: Enumerating objects: 100, done. remote: Counting objects: 100% (100/100), done. remote: Compressing objects: 100% (80/80), done. remote: Total 100 (delta 20), reused 50 (delta 10), pack-reused 0 Receiving objects: 100% (100/100), 10.00 KiB | 1.00 MiB/s, done. Resolving deltas: 100% (20/20), done.
--init - Initialize submodules that are not yet initialized
--recursive - Also initialize nested submodules inside submodules
This command adds the external repository as a subtree inside your project at libs/lib, copying its code and history into your project but keeping it merged.
Terminal
git subtree add --prefix=libs/lib https://github.com/example/lib.git main --squash
Expected OutputExpected
remote: Enumerating objects: 100, done. remote: Counting objects: 100% (100/100), done. remote: Compressing objects: 100% (80/80), done. remote: Total 100 (delta 20), reused 50 (delta 10), pack-reused 0 Receiving objects: 100% (100/100), 10.00 KiB | 1.00 MiB/s, done. Resolving deltas: 100% (20/20), done. From https://github.com/example/lib * branch main -> FETCH_HEAD Added dir 'libs/lib'
--prefix - Specifies the directory inside your project where the subtree will be placed
--squash - Combines all subtree commits into one to keep history simple
This command updates the subtree by pulling new changes from the external repository into your project.
Terminal
git subtree pull --prefix=libs/lib https://github.com/example/lib.git main --squash
Expected OutputExpected
remote: Enumerating objects: 10, done. remote: Counting objects: 100% (10/10), done. remote: Compressing objects: 100% (8/8), done. remote: Total 10 (delta 2), reused 5 (delta 1), pack-reused 0 Receiving objects: 100% (10/10), 1.00 KiB | 1.00 MiB/s, done. Resolving deltas: 100% (2/2), completed with 2 local objects. From https://github.com/example/lib * branch main -> FETCH_HEAD Squash commit -- not updating HEAD Updating 123abc..456def Fast-forward libs/lib/file.txt | 2 ++ 1 file changed, 2 insertions(+)
--prefix - Specifies the subtree directory to update
--squash - Keeps the update as a single commit
Key Concept

If you remember nothing else, remember: submodules keep projects separate and linked, requiring extra commands, while subtrees copy code inside your project for simpler use but merge histories.

Common Mistakes
Forgetting to run 'git submodule update --init' after cloning a repo with submodules
The submodule folders will be empty or missing, causing build or runtime errors
Always run 'git submodule update --init --recursive' after cloning to fetch submodule content
Trying to edit submodule files directly without committing in the submodule repo
Changes won't be tracked properly and can cause confusion or lost work
Enter the submodule directory, commit changes there, then update the main repo's submodule reference
Using subtree without the --prefix flag or with wrong directory path
The subtree code will be placed incorrectly, causing project structure issues
Always specify the correct --prefix path where you want the subtree code to live
Summary
Use 'git submodule add' to link an external repo inside your project without copying its code.
Run 'git submodule update --init --recursive' to fetch all submodule content after cloning.
Use 'git subtree add' with --prefix to copy external repo code inside your project with merged history.
Update subtrees with 'git subtree pull' to bring in changes from the external repo.
Submodules require extra commands to manage but keep histories separate; subtrees simplify usage by merging code.