Introduction
Git submodules are one of the most powerful yet underutilized features in Git. They solve a fundamental problem in modern web development: how to include external repositories in your project while maintaining version control, enabling customization, and keeping everything synchronized across your team. In this comprehensive guide, we’ll explore Git submodules in depth, using a real-world GrapeJS integration as our practical example.
What Are Git Submodules?
Git submodules allow you to include one Git repository as a subdirectory of another Git repository. Think of them as a way to “embed” external projects into your main project while keeping their version history completely separate.
-
The Core Concept
Unlike regular dependencies that you install via package managers, submodules maintain a direct link to a specific commit in an external repository. This gives you:
- Precise version control: Your project points to an exact commit, not a version range
- Independent development: The submodule has its own development lifecycle
-
When to Use Submodules
Submodules are ideal when you need to:
- Include external libraries that you want to customize or extend
- Maintain multiple related projects that share common components
- Have precise control over dependency versions
- Keep external code separate from your main codebase
- Contribute back to open-source projects while maintaining your modifications
Understanding Submodule Architecture
The Submodule “Pointer” System
This is the key concept that makes submodules powerful: instead of copying files, Git stores a reference (pointer) to a specific commit in the external repository.
Your main repository structure
your-project/
├── src/
├── external-lib/ ← Submodule directory
└── .gitmodules ← Submodule configuration
The .gitmodules file contains:
[submodule “external-lib”]
path = external-lib
url = https://github.com/example/external-lib.git
How Git Tracks Submodules
When you commit changes to your main repository, Git records:
- All your regular files and changes
- The exact commit hash that each submodule should point to
This means your repository doesn’t contain the submodule’s files – it contains instructions on where to find them and which version to use.
Real-World Example: Integrating GrapeJS
Let’s walk through a complete submodule implementation using GrapeJS, a popular web builder framework.
Step 1: Creating Your Controlled Copy
First, we need our own version of GrapeJS that we can control and customize:
Clone the original repository
git clone https://github.com/example/grapesjs.git
cd grapesjs
Step 2: Setting Up Your Repository
Create your own repository (GitLab, GitHub, etc.) and transfer the code:
Remove original remote
git remote remove origin
Add your repository as origin
git remote add origin https://gitlab.com/yourorg/grapejs.git
Push all branches and tags
git push -u origin --all
git push -u origin --tags
Step 3: Maintaining Upstream Connection
This is crucial for staying updated with the original project:
Add original repository as upstream
git remote add upstream https://github.com/example/grapejs.git
Verify your remote setup
git remote -v
You should see:
origin https://gitlab.com/yourorg/grapejs.git (fetch/push)
upstream https://github.com/example/grapesjs.git (fetch/push)
Step 4: Adding as Submodule
Now, in your main application repository:
cd /path/to/your/web-application
git submodule add https://gitlab.com/yourorg/grapejs.git
Commit the submodule addition
git add .
git commit -m "Add GrapeJS as submodule"
git push
Step 5: Working with the Submodule
Build the submodule
cd grapejs
npm install
npm run build
cd ..
Use in your application
// Import from your submodule
import grapesjs from './grapejs/dist/grapes.min.js';
const editor = grapesjs.init({
container: '#gjs',
height: '100vh',
plugins: ['gjs-blocks-basic']
});
Essential Submodule Commands
Initialize submodules after cloning
git submodule init
git submodule update
Clone repository with submodules in one command
git clone --recursive https://github.com/yourorg/your-project.git
Check submodule status
git submodule status
Update submodule to latest commit from its remote
git submodule update --remote
Managing Updates and Synchronization
Updating from Upstream
When the original GrapeJS has new features:
In your local grapejs repository (not the submodule)
cd /path/to/your/local/grapejs-repo
Fetch and merge upstream changes
git fetch upstream
git checkout master
git merge upstream/master
Push to your repository
git push origin master
Updating Your Application
In your main application
cd /path/to/your/web-application
Update submodule pointer to latest commit
git submodule update --remote grapejs
Commit the update
git add grapejs
git commit -m "Update GrapeJS submodule to latest version"
git push
Team Collaboration with Submodules
Setting Up New Team Members
When someone clones your project:
git clone https://gitlab.com/yourorg/web-application.git
cd web-application
Initialize and update submodules
git submodule init
git submodule update
Conclusion
Git submodules are a powerful tool for managing complex project dependencies. While they add complexity, they provide unmatched control over external code integration. The key to success with submodules is understanding their architecture and establishing clear workflows for your team.
Our GrapeJS integration example demonstrates how submodules can solve real-world problems: maintaining control over third-party libraries while staying current with upstream development.