Git submodules are amazingly useful. Because they provide a way for you to connect external, separate git repositories they can be used to organize your vim scripts, your dotfiles, or even a whole mediawiki deployment.
As incredibly useful as git submodules are, they can also be a bit confusing to use. This goal of this article is to walk you through the most common git submodule tasks: adding, removing and updating. We’ll also review briefly how to make changes to code you have checked out as a submodule.
I’ve created some practice repositories. Fork submodule-practice if you’d like to follow along. We’ll used these test repositories as submodules:
I’ve used version 2.3.0 of the git client for these examples. If you’re seeing something different, check your version with git --version
.
Initializing a repository with submodules
First, let’s clone our practice repository:
[skade ;) ~/Work] christie$ git clone git@github.com:christi3k/submodule-practice.git Cloning into 'submodule-practice'... remote: Counting objects: 63, done. remote: Compressing objects: 100% (16/16), done. remote: Total 63 (delta 9), reused 0 (delta 0), pack-reused 47 Receiving objects: 100% (63/63), 6.99 KiB | 0 bytes/s, done. Resolving deltas: 100% (25/25), done. Checking connectivity... done.
And then cd into the working directory:
christie$ cd submodule-practice/
Currently, this project has two submodules: furry-octo-nemesis and psychic-avenger.
When we run ls we see directories for these submodules:
[skade ;) ~/Work/submodule-practice (master)] christie$ ll ▕ drwxrwxr-x▏christie:christie│4 min │ 4K│furry-octo-nemesis ▕ drwxrwxr-x▏christie:christie│4 min │ 4K│psychic-avenger ▕ -rw-rw-r--▏christie:christie│4 min │ 29B│README.md ▕ -rw-rw-r--▏christie:christie│4 min │ 110B│README.mediawiki
But if we run ls for either submodule directory we see they are empty. This is because the submodules have not yet been initialized or updated.
[skade ;) ~/Work/submodule-practice (master)] christie$ git submodule init Submodule 'furry-octo-nemesis' (git@github.com:christi3k/furry-octo-nemesis.git) registered for path 'furry-octo-nemesis' Submodule 'psychic-avenger' (git@github.com:christi3k/psychic-avenger.git) registered for path 'psychic-avenger'
git submodule init
copies the submodule names, urls and other details from .gitmodules to .git/config, which is where git looks for config details it should apply to your working copy.
git submodule init
does not update or otherwise alter information in .git/config. If you have changed .gitmodules for any submodule already initialized, you’ll need to deinit
and init
the submodule again for changes to be reflected in .git/config.
You can initialize specific submodules by specifying their name:
git submodule init psychich-avenger
At this point you could customized git submodule urls for use in your local checkout by editing them in .git/config before proceeding to git submodule update.
Now let’s actually checkout the submodules with submodule update:
skade ;) ~/Work/submodule-practice (master)] christie$ git submodule update --recursive Cloning into 'furry-octo-nemesis'... remote: Counting objects: 6, done. remote: Total 6 (delta 0), reused 0 (delta 0), pack-reused 6 Receiving objects: 100% (6/6), done. Checking connectivity... done. Submodule path 'furry-octo-nemesis': checked out '1c4b231fa0bcfd5ce8b8a2773c6616689032d353' Cloning into 'psychic-avenger'... remote: Counting objects: 25, done. remote: Compressing objects: 100% (9/9), done. remote: Total 25 (delta 1), reused 0 (delta 0), pack-reused 15 Receiving objects: 100% (25/25), done. Resolving deltas: 100% (3/3), done. Checking connectivity... done. Submodule path 'psychic-avenger': checked out '169c5c56154f58fd745352c4f30aa0d4a1d7a88e'
Note: The --recursive
flag tells git to recurse into submodule directories and run update on any submodules those submodules include. It’s not needed for this example, but I’ve included it here anyway since it’s common for projects to have nested submodules.
Now when we run ls on either directory, we see they now contain our submodule’s files:
[skade ;) ~/Work/submodule-practice (master)] christie$ ls furry-octo-nemesis/ ▕ -rw-rw-r--▏42 sec │ 52B│README.md [skade ;) ~/Work/submodule-practice (master)] christie$ ls psychic-avenger/ ▕ -rw-rw-r--▏46 sec │ 133B│README.md ▕ -rw-rw-r--▏46 sec │ 0B│other.txt
Note: It’s possible to run init and update in one command with git submodule update --init --recursive
Adding a git submodule
We’ll start in the working directory of submodule-practice.
To add a submodule, use:
git submodule add <git-url>
Let’s try adding sample project scaling-octo-wallhack as a submodule.
[2495][skade ;) ~/Work/submodule-practice (master)] christie$ git submodule add git@github.com:christi3k/scaling-octo-wallhack.git Cloning into 'scaling-octo-wallhack'... remote: Counting objects: 19, done. remote: Compressing objects: 100% (8/8), done. remote: Total 19 (delta 1), reused 0 (delta 0), pack-reused 9 Receiving objects: 100% (19/19), done. Resolving deltas: 100% (3/3), done. Checking connectivity... done.
Note: If you want the submodule to be cloned into a directory other than ‘scaling-octo-wallhack’ then you need to specify a directory to clone into as you would when cloning any other project. For example, this command will clone psychic-avenger to the subdirectory submodules:
christie$ git submodule add git@github.com:christi3k/scaling-octo-wallhack.git submodules/scaling-octo-wallhack
Let’s see what git status tells us:
[skade ;) ~/Work/submodule-practice (master +)] christie$ git status On branch master Your branch is up-to-date with 'origin/master'. Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: .gitmodules new file: scaling-octo-wallhack
And running ls we see that there are files in scaling-octo-wallhack directory:
[skade ;) ~/Work/submodule-practice (master +)] christie$ ll scaling-octo-wallhack/ ▕ -rw-rw-r--▏christie:christie│< min │ 180B│README.md ▕ -rw-rw-r--▏christie:christie│< min │ 0B│cutting-edge-changes.txt ▕ -rw-rw-r--▏christie:christie│< min │ 0B│file1.txt ▕ -rw-rw-r--▏christie:christie│< min │ 0B│file2.txt
Specifying a branch
When you add a git submodule, git makes some assumptions for you. It sets up a remote repository to the submodule called ‘origin’ and it checksout the ‘master’ branch for you. In many cases you may no want to use the master branch. Luckily, this is easy to change.
There are two methods to specific which branch of the submodule should be checked out by your project.
Method 1: Specify a branch in .gitmodules
Here’s what the modified section of .gitmodules looks like for scaling-octo-wallhack:
[submodule "scaling-octo-wallhack"] path = scaling-octo-wallhack url = git@github.com:christi3k/scaling-octo-wallhack.git branch = REL_1
Be sure to save .gitmodules and then run git submodule update --remote
:
[skade ;( ~/Work/submodule-practice (master *+)] christie$ git submodule update --remote Submodule path 'psychic-avenger': checked out 'fba086dbb321109e5cd2d9d1bc3b59478dacf6ee' Submodule path 'scaling-octo-wallhack': checked out '88d66d5ecc58d2ab82fec4fea06ffbfd2c55fd7d'
Method 2: Checkout specific branch in submodule directory
In the submodule directory, checkout the branch you want with git checkout origin/branch-name
:
[skade ;) ~/Work/submodule-practice/scaling-octo-wallhack ((b49591a...))] christie$ git checkout origin/REL_1 Previous HEAD position was b49591a... Cutting-edge changes. HEAD is now at 88d66d5... Prep Release 1.
Either method will result will yield the following from git status
:
[skade ;) ~/Work/submodule-practice (master *+)] christie$ git status On branch master Your branch is up-to-date with 'origin/master'. Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: .gitmodules new file: scaling-octo-wallhack Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: scaling-octo-wallhack (new commits)
Now let’s stage and commit the changes:
[skade ;) ~/Work/submodule-practice (master *+)] christie$ git add scaling-octo-wallhack [skade ;) ~/Work/submodule-practice (master +)] christie$ git commit -m "Add scaling-octo-wallhack submodule, REL_1." [master 4a97a6f] Add scaling-octo-wallhack submodule, REL_1. 2 files changed, 4 insertions(+) create mode 160000 scaling-octo-wallhack
And don’t forget to push them to our remote repository so they are available for others:
[skade ;) ~/Work/submodule-practice (master)] christie$ git push -n origin master To git@github.com:christi3k/submodule-practice.git 7e6d09e..4a97a6f master -> master
(Note the -n flag means ‘dry run’, that is ‘do everything except actually send the updates.’ I recommend using this when available with commands that have potentially destructive results, including push and merge.)
Looks good, do it for real now:
[skade ;) ~/Work/submodule-practice (master)] christie$ git push origin master Counting objects: 3, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 439 bytes | 0 bytes/s, done. Total 3 (delta 2), reused 0 (delta 0) To git@github.com:christi3k/submodule-practice.git 7e6d09e..4a97a6f master -> master
Removing a git submodule
Removing a submodule is a bit trickier than adding one.
Deinitialize
First, deinit the submodule with git submodule deinit
:
[skade ;) ~/Work/submodule-practice (master)] christie$ git submodule deinit psychic-avenger Cleared directory 'psychic-avenger' Submodule 'psychic-avenger' (git@github.com:christi3k/psychic-avenger.git) unregistered for path 'psychic-avenger'
This command removes the submodule’s confg entries in .git/config and .gitmodules and it removes files from the submodule’s working directory. This command will delete untracked files, even when they are listed in .gitignore.
Note: You can also use this command if you simply want to prevent having a local checkout of the submodule in your working tree, without actually removing the submodule from your main project.
Let’s check our work:
[skade ;) ~/Work/submodule-practice (master)] christie$ git status On branch master Your branch is up-to-date with 'origin/master'. nothing to commit, working directory clean
This shows no changes because git submodule deinit
only makes changes to our local working copy.
Running ls we also see the directories are still present:
[skade ;) ~/Work/submodule-practice (master)] christie$ ll ▕ drwxrwxr-x▏christie:christie│4 day │ 4K│furry-octo-nemesis ▕ drwxrwxr-x▏christie:christie│16 sec │ 4K│psychic-avenger ▕ -rw-rw-r--▏christie:christie│4 day │ 29B│README.md ▕ -rw-rw-r--▏christie:christie│4 day │ 110B│README.mediawiki [skade ;) ~/Work/submodule-practice (master)]
Remove with git rm
To actually remove the submodule from your project’s repository, use git rm
:
[skade ;) ~/Work/submodule-practice (master)] christie$ git rm psychic-avenger rm 'psychic-avenger'
Let’s check our work:
[skade ;) ~/Work/submodule-practice (master +)] christie$ git status On branch master Your branch is up-to-date with 'origin/master'. Changes to be committed: (use "git reset HEAD ..." to unstage) modified: .gitmodules deleted: psychic-avenger
These changes have been staged by git automatically, so to see what has changed about .gitmodules, use --cached
flag or its alias --staged
:
[skade ;) ~/Work/submodule-practice (master +)] christie$ git diff --cached diff --git a/.gitmodules b/.gitmodules index dec1204..e531507 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "furry-octo-nemesis"] path = furry-octo-nemesis url = git@github.com:christi3k/furry-octo-nemesis.git -[submodule "psychic-avenger"] - path = psychic-avenger - url = git@github.com:christi3k/psychic-avenger.git diff --git a/psychic-avenger b/psychic-avenger deleted file mode 160000 index fdd4b36..0000000 --- a/psychic-avenger +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fdd4b366458757940d7692b61e22f4d1b21c825a
So we see that in .gitmodules, lines related to psychic-avenger have been removed and that the psychic-avenger directory and commit hash has also been removed.
And a directory listing shows the files are no longer in our working directory:
christie$ ll ▕ drwxrwxr-x▏christie:christie│4 day │ 4K│furry-octo-nemesis ▕ -rw-rw-r--▏christie:christie│4 day │ 29B│README.md ▕ -rw-rw-r--▏christie:christie│4 day │ 110B│README.mediawiki
Removing all reference to the submodule (optional)
For whatever reason, git does not remove all trace of the submodule even after these commands. To completely remove all reference, you need to also delete the .git/modules entry to really have it be gone:
[skade ;) ~/Work/submodule-practice (master)] christie$ rm -rf .git/modules/psychic-avenger
Note: This probably optional for most use-cases. The only time you might run into trouble if you leave this reference is if you later add a submodule of the same name. In that case, git will complain and ask you to pick a different name or to simply checkout the submodule from the remote source it already knows about.
Also, be careful with rm -rf because it doesn’t prompt you for a confirmation and there’s no dry-run flag.
Commit changes
Now we commit our changes:
[skade ;) ~/Work/submodule-practice (master +)] christie$ git commit -m "Remove psychic-avenger submodule." [master 7833c1c] Remove psychic-avenger submodule. 2 files changed, 4 deletions(-) delete mode 160000 psychic-avenger
Looks good, let’s push our changes:
[skade ;) ~/Work/submodule-practice (master)] christie$ git push -n origin master To git@github.com:christi3k/submodule-practice.git d89b5cb..7833c1c master -> master
Looks good, let’s do it for real:
[skade ;) ~/Work/submodule-practice (master)] christie$ git push origin master Counting objects: 3, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 402 bytes | 0 bytes/s, done. Total 3 (delta 1), reused 0 (delta 0) To git@github.com:christi3k/submodule-practice.git d89b5cb..7833c1c master -> master
Updating submodules within your project
The simplest use case for updating submodules within your project is when you simply want to pull in the most recent version of that submodule or want to change to a different branch.
There are two methods for updating modules.
Method 1: Specify a branch in .gitmodules and use git submodule update --remote
Using this method, you first need to ensure that the branch you want to use is specified for each submodule in .gitmodules
.
Let’s take a look at the .gitmodules file for our sample project:
[submodule "furry-octo-nemesis"] path = furry-octo-nemesis url = git@github.com:christi3k/furry-octo-nemesis.git [submodule "psychic-avenger"] path = psychic-avenger url = git@github.com:christi3k/psychic-avenger.git branch = RELEASE_E [submodule "scaling-octo-wallhack"] path = scaling-octo-wallhack url = git@github.com:christi3k/scaling-octo-wallhack.git
The submodule psychic-avenger is set to checkout branch RELEASE_E and both furry-octo-nemesis and scaling-octo-wallhack will checkout master because no branch is specified.
Edit .gitsubmodules
To change the branch that is checked out, update the value of branch:
[submodule "scaling-octo-wallhack"] path = scaling-octo-wallhack url = git@github.com:christi3k/scaling-octo-wallhack.git brach = REL_2
Now scaling-octo-wallhack is set to checkout the REL_2 branch.
Update with git submodule update –remote
[skade ;) ~/Work/submodule-practice (master *)] christie$ git submodule update --remote Submodule path 'scaling-octo-wallhack': checked out 'e845f5431119b527b7cde1ad138a373c5b2d4ec1'
And if we cd into scaling-octo-wallhack and run branch -vva
we confirm we’ve checked out the REL_2 branch:
[skade ;) ~/Work/submodule-practice/scaling-octo-wallhack ((e845f54...))] christie$ git branch -vva * (detached from e845f54) e845f54 Release 2. master b49591a [origin/master] Cutting-edge changes. remotes/origin/HEAD -> origin/master remotes/origin/REL_1 88d66d5 Prep Release 1. remotes/origin/REL_2 e845f54 Release 2. remotes/origin/master b49591a Cutting-edge changes.
Method 2: git fetch and git checkout within submodule
First, change into the directory of the submodule you wish to update.
fetch from remote repository
Then run git fetch origin
to grab any new commits:
[skade ;) ~/Work/submodule-practice/scaling-octo-wallhack ((b49591a...))] christie$ git fetch origin remote: Counting objects: 3, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0 Unpacking objects: 100% (3/3), done. From github.com:christi3k/scaling-octo-wallhack e845f54..1cc1044 REL_2 -> origin/REL_2
Here was can see that the last commit for the REL_2 branch changed from e845f54 to 1cc1044.
Running branch -vva
confirms this and that we haven’t changed which commit is checked out yet:
[skade ;) ~/Work/submodule-practice/scaling-octo-wallhack ((88d66d5...))] christie$ git branch -vva * (detached from 88d66d5) 88d66d5 Prep Release 1. master b49591a [origin/master] Cutting-edge changes. remotes/origin/HEAD -> origin/master remotes/origin/REL_1 88d66d5 Prep Release 1. remotes/origin/REL_2 1cc1044 Hotfix for Release 2 branch. remotes/origin/master b49591a Cutting-edge changes.
Checkout branch
So now we can re-checkout the REL_2 remote branch:
[skade ;) ~/Work/submodule-practice/scaling-octo-wallhack ((88d66d5...))] christie$ git checkout origin/REL_2 Previous HEAD position was 88d66d5... Prep Release 1. HEAD is now at 1cc1044... Hotfix for Release 2 branch.
Let’s check our work with branch -vva
:
[skade ;) ~/Work/submodule-practice/scaling-octo-wallhack ((1cc1044...))] christie$ git branch -vva * (detached from origin/REL_2) 1cc1044 Hotfix for Release 2 branch. master b49591a [origin/master] Cutting-edge changes. remotes/origin/HEAD -> origin/master remotes/origin/REL_1 88d66d5 Prep Release 1. remotes/origin/REL_2 1cc1044 Hotfix for Release 2 branch. remotes/origin/master b49591a Cutting-edge changes.
Commiting the changes
Moving back to our main project directory, let’s check our work with git status && git diff
:
[skade ;) ~/Work/submodule-practice (master *)] christie$ git status && git diff On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: scaling-octo-wallhack (new commits) no changes added to commit (use "git add" and/or "git commit -a") diff --git a/scaling-octo-wallhack b/scaling-octo-wallhack index 88d66d5..1cc1044 160000 --- a/scaling-octo-wallhack +++ b/scaling-octo-wallhack @@ -1 +1 @@ -Subproject commit 88d66d5ecc58d2ab82fec4fea06ffbfd2c55fd7d +Subproject commit 1cc104418a6a24b9a3cc227df4ebaf707ea23b49
Notice that there are no changes to .gitmodules with this method. Instead, we’ve simply changed the commit hash that the super project is pointing to for this submodule.
Now let’s add, commit and push our changes:
[skade ;) ~/Work/submodule-practice (master *)] christie$ git add scaling-octo-wallhack [skade ;) ~/Work/submodule-practice (master +)] christie$ git commit -m "Updating to current REL_2." [master 5ddbe87] Updating to current REL_2. 1 file changed, 1 insertion(+), 1 deletion(-) [skade ;) ~/Work/submodule-practice (master)] christie$ git push -n origin master To git@github.com:christi3k/submodule-practice.git 4a97a6f..5ddbe87 master -> master [skade ;) ~/Work/submodule-practice (master)] christie$ git push origin master Counting objects: 2, done. Delta compression using up to 4 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (2/2), 261 bytes | 0 bytes/s, done. Total 2 (delta 1), reused 0 (delta 0) To git@github.com:christi3k/submodule-practice.git 4a97a6f..5ddbe87 master -> master
what’s the difference between git submodule update and git submodule update –remote?
Note: git submodule update –remote looks at the value you have in .gitmodules for branch. If there isn’t a value there, it assumes master. git submodule update looks at your repository has for the commit of the submodule project and checks that commit out. Both checkout to a detached state by default unless you specify –merge or –rebase.
These two commands have the ability to step on each other. If you have checked out a specific commit in the submodule directory, it’s possible for it to be different than the commit that would be checked out by git submdoule update –remote specificied in the branch value of .gitmodules.
Likewise, simply looking at the branch value in .gitmodules does not guarentee that’s the branch you have checked out for the submodule. When in doubt, cd to the submodule directory and run git branch -vva
. git branch -vva is your friend!
When a subbmodule has been removed
When a submodule has been removed from a repository, what’s the best way to update your working directory to reflect this change?
The answer is that it depends on whether or not you have local, untracked files in the submodule directory that you want to keep.
Method 1: deinit and then fetch and merge
Use this method if you want to completely remove the submodule directory even if you have local, untracked files in it.
Note: In the following examples, we’re working in another checkout of our submodule-practice.
First, use git submodule deinit
to deinitialize the submodule:
[skade ;) ~/Work/submodule-elsewhere (master *)] christie$ git submodule deinit psychic-avenger error: the following file has local modifications: psychic-avenger (use --cached to keep the file, or -f to force removal) Submodule work tree 'psychic-avenger' contains local modifications; use '-f' to discard them
We have untracked changes, so we need to use -f to remove them:
[skade ;( ~/Work/submodule-elsewhere (master *)] christie$ git submodule deinit -f psychic-avenger Cleared directory 'psychic-avenger' Submodule 'psychic-avenger' (git@github.com:christi3k/psychic-avenger.git) unregistered for path 'psychic-avenger'
Now fetch changes from the remote repository and merge them:
[skade ;) ~/Work/submodule-elsewhere (master)] christie$ git fetch origin remote: Counting objects: 3, done. remote: Compressing objects: 100% (1/1), done. remote: Total 3 (delta 2), reused 3 (delta 2), pack-reused 0 Unpacking objects: 100% (3/3), done. From github.com:christi3k/submodule-practice 666af5d..6038c72 master -> origin/master [skade ;) ~/Work/submodule-elsewhere (master)] christie$ git merge origin/master Updating 666af5d..6038c72 Fast-forward .gitmodules | 3 --- psychic-avenger | 1 - 2 files changed, 4 deletions(-) delete mode 160000 psychic-avenger
Running ls on our project directory shows that the all of psychic-avenger’s files have been removed:
[skade ;) ~/Work/submodule-elsewhere (master)] christie$ ll ▕ drwxrwxr-x▏christie:christie│3 hour│ 4K│furry-octo-nemesis ▕ drwxrwxr-x▏christie:christie│5 min │ 4K│scaling-octo-wallhack ▕ -rw-rw-r--▏christie:christie│3 hour│ 29B│README.md ▕ -rw-rw-r--▏christie:christie│3 hour│ 110B│README.mediawiki
Method 2: fetch and merge and clean up as needed
Use this method if you have local, untracked (and/or ignored) changes that you want to keep, or if you want to remove files manually.
First, fetch changes from the remote repository and merge them with your local branch:
[skade ;) ~/Work/submodule-elsewhere (master)] christie$ git fetch origin remote: Counting objects: 3, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0 Unpacking objects: 100% (3/3), done. From github.com:christi3k/submodule-practice d89b5cb..7833c1c master -> origin/master [skade ;) ~/Work/submodule-elsewhere (master)] christie$ git merge origin/master Updating d89b5cb..7833c1c warning: unable to rmdir psychic-avenger: Directory not empty Fast-forward .gitmodules | 3 --- psychic-avenger | 1 - 2 files changed, 4 deletions(-) delete mode 160000 psychic-avenger
Note the warning “unable to rm dir…” and let’s check our work:
[skade ;) ~/Work/submodule-elsewhere (master)] christie$ git status On branch master Your branch is up-to-date with 'origin/master'. Untracked files: (use "git add ..." to include in what will be committed) psychic-avenger/ nothing added to commit but untracked files present (use "git add" to track)
No uncommited or staged changes, but the directory that was our submodule psychic-avenger is now untracked. Running ls shows that there are still files in the directory, too:
[skade ;( ~/Work/submodule-elsewhere (master)] christie$ ll psychic-avenger/ ▕ -rw-rw-r--▏christie:christie│30 min │ 192B│README.md
Now you can clean up files as you like. In this example we’ll delete the entire psychic-avenger directory:
[skade ;) ~/Work/submodule-elsewhere (master)] christie$ rm -rf psychic-avenger
Working on projects checked out as submodules
Working on projects checked out as submodules is rather straight-forward, particularly if you are comfortable with git branching and make liberal use of git branch -vva.
Let’s pretend that scaling-octo-wallhack is an extension that I’m developing for my project submodule-practice. I want to work on the project while it’s checked out as a submodule because doing so makes it easy to test the extension within my larger project.
Create a working branch
First switch the the branch that you want to use as the base for your work. I’m going to use local tracking branch master, which I’ll first ensure is up to date with the remote origin/master:
[skade ;) ~/Work/submodule-practice/scaling-octo-wallhack ((1cc1044...))] christie$ git branch -vva * (detached from origin/REL_2) 1cc1044 Hotfix for Release 2 branch. master b49591a [origin/master] Cutting-edge changes. remotes/origin/HEAD -> origin/master remotes/origin/REL_1 88d66d5 Prep Release 1. remotes/origin/REL_2 1cc1044 Hotfix for Release 2 branch. remotes/origin/master b49591a Cutting-edge changes. [skade ;) ~/Work/submodule-practice/scaling-octo-wallhack ((b49591a...))] christie$ git checkout master Switched to branch 'master' Your branch is up-to-date with 'origin/master'.
If master had not been up-to-date with orgin/master, I would have merged.
Next, let’s create a tracking branch for this awesome feature we’re going to work on:
[skade ;) ~/Work/submodule-practice/scaling-octo-wallhack (master)] christie$ git checkout -b awesome-feature Switched to a new branch 'awesome-feature' [skade ;) ~/Work/submodule-practice/scaling-octo-wallhack (awesome-feature)] christie$ git branch -vva * awesome-feature b49591a Cutting-edge changes. master b49591a [origin/master] Cutting-edge changes. remotes/origin/HEAD -> origin/master remotes/origin/REL_1 88d66d5 Prep Release 1. remotes/origin/REL_2 1cc1044 Hotfix for Release 2 branch. remotes/origin/master b49591a Cutting-edge changes.
Do some work, add and commit changes
No we’ll do some work on the feature, add and commit that work:
[skade ;) ~/Work/submodule-practice/scaling-octo-wallhack (awesome-feature)] christie$ touch awesome_feature.txt [skade ;) ~/Work/submodule-practice/scaling-octo-wallhack (awesome-feature)] christie$ git add awesome_feature.txt [skade ;) ~/Work/submodule-practice/scaling-octo-wallhack (awesome-feature +)] christie$ git commit -m "first round of work on awesome feature" [awesome-feature 005994b] first round of work on awesome feature 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 awesome_feature.txt
Push to remote repository
Now we’ll push that to our remost repository so others can contribute:
[skade ;) ~/Work/submodule-practice/scaling-octo-wallhack (awesome-feature)] christie$ git push -n origin awesome-feature To git@github.com:christi3k/scaling-octo-wallhack.git * [new branch] awesome-feature -> awesome-feature [skade ;) ~/Work/submodule-practice/scaling-octo-wallhack (awesome-feature)] christie$ git push origin awesome-feature Counting objects: 2, done. Delta compression using up to 4 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (2/2), 265 bytes | 0 bytes/s, done. Total 2 (delta 1), reused 0 (delta 0) To git@github.com:christi3k/scaling-octo-wallhack.git * [new branch] awesome-feature -> awesome-feature
Switch back to remote branch, headless checkout
If we’d like to switch back to a remote branch, we can:
[skade ;) ~/Work/submodule-practice/scaling-octo-wallhack (awesome-feature)] christie$ git checkout origin/REL_2 Note: checking out 'origin/REL_2'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b new_branch_name HEAD is now at 1cc1044... Hotfix for Release 2 branch.
Using this new branch to collaborate
To try this awesome feature in another checkout, use git fetch:
[skade ;) ~/Work/submodule-elsewhere/scaling-octo-wallhack ((1cc1044...))] christie$ git fetch origin remote: Counting objects: 2, done. remote: Compressing objects: 100% (1/1), done. remote: Total 2 (delta 1), reused 2 (delta 1), pack-reused 0 Unpacking objects: 100% (2/2), done. From github.com:christi3k/scaling-octo-wallhack * [new branch] awesome-feature -> origin/awesome-feature
If you just want to try the feature, checkout orgin/branch:
[2724][skade ;) ~/Work/submodule-elsewhere/scaling-octo-wallhack ((1cc1044...))] christie$ git checkout origin/awesome-feature Previous HEAD position was 1cc1044... Hotfix for Release 2 branch. HEAD is now at 005994b... first round of work on awesome feature
If you plan to work on the feature, create a tracking branch:
[2725][skade ;) ~/Work/submodule-elsewhere/scaling-octo-wallhack ((005994b...))] christie$ git checkout -b awesome-feature Switched to a new branch 'awesome-feature'
Acknowledgements
Thanks GPHemsley for helping me figure out git submodules within the context of our MozillaWiki work. I couldn’t have written this post without those conversations or the notes I took during them.
Additional Resources
- Mastering git submodules
- git-submodule documentation
- Submodules chapter of Git book
Update 19 Feb: Fixed typos and added ‘Additional Resources’ section.