I’ve spent a little more than a month working with Git now. I can honestly say that while there are many things that I like about Git, there are just as many things that I personally find to be a pain in the butt.
Submodules specifically have managed to be a thorn in my side on many occasions. While the concept of submodules is simple, figuring out how to actually work with them can be a chore. I say “figuring out” because not everything about working with submodules is well documented. I’ll cover two of the more difficult things to figure out: removing and updating submodules from your repository.
What are Submodules?
The concept of submodules is brilliant. It essentially allows you to attach an external repository inside another repository at a specific path. In order to illustrate the value of submodules, it will probably be helpful for me to explain how I am using them.
My profession is working with WordPress themes. Basically, I develop feature enhancements to the themes. I develop the code for these enhancements in modules that are completely contained in their own folder. This allows for the code to be easily added to other themes and also simplifies code updates/improvements as the code for specific features is consistent across all the themes that use that specific module.
Each theme that we produce is kept in its own Git repository. In addition, I’ve created a separate repository for each one of these feature modules. Rather than actually putting the feature module code directly into the theme repositories, I simply add the needed feature module repositories as submodules.
For example, we have a theme called FlexxBold. FlexxBold currently includes a total of seven submodules: billboard, contact-page-plugin, featured-images, feedburner-widget, file-utility, flexx-layout-editor, and tutorials. Since I’m using submodules, the code can be pulled directly from the relevant submodule repositories rather than requiring me to manually update each individual theme repository.
As I mentioned before, not everything in Git is easy to work with. There are four main functions you will need to understand in order to work with Git submodules. In order, you will need to know how to add, make use of, remove, and update submodules. I’ll cover each of those uses below.
Adding Submodules to a Git Repository
Fortunately, adding a submodule to a git repository is actually quite simple. For example, if I’m in the repository working directory of a new theme called SampleTheme and need to add the billboard repository to the path lib/billboard, I can do so with the following command:
[user@office SampleTheme]$ git submodule add git@mygithost:billboard lib/billboard Initialized empty Git repository in ~/git_dev/SampleTheme/lib/billboard/.git/ remote: Counting objects: 1006, done. remote: Compressing objects: 100% (978/978), done. remote: Total 1006 (delta 631), reused 0 (delta 0) Receiving objects: 100% (1006/1006), 408.22 KiB, done. Resolving deltas: 100% (631/631), done.
There are three main parts to this command:
git submodule add
– This simply tells Git that we are adding a submodule. This syntax will always remain the same.git@mygithost:billboard
– This is the external repository that is to be added as a submodule. The exact syntax will vary depending on the setup of the Git repository you are connecting to. You need to ensure that you have the ability to clone the given repository.lib/billboard
– This is the path where the submodule repository will be added to the main repository.
Let’s check to see how the repository is doing.
[user@office SampleTheme]$ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # new file: .gitmodules # new file: lib/billboard #
Notice how the supplied path was created and added to the changes to be committed. In addition, a new file called .gitmodules was created. This new file contains the details we supplied about the new submodule. Out of curiosity, let’s check out the contents of that new file:
[user@office SampleTheme]$ cat .gitmodules [submodule "lib/billboard"] path = lib/billboard url = git@mygithost:billboard
Being able to modify this file later will come in handy later.
All that is left to do now is to commit the changes and then push the commit to a remote system if necessary.
Using Submodules
Having submodules in a repository is great and all, but if I look in my repository, all I have is an empty folder rather than the actual contents of the submodule’s repository. In order to fill in the submodule’s path with the files from the external repository, you must first initialize the submodules and then update them.
Note: This has changed in newer versions of Git. Now the submodule’s repository will be cloned with master checked out. If that repository also has submodules, then your submodule’s submodules will have to be populated by following the steps below from within your project’s submodule directory (confusing yet?).
For instance, if you are working in project called phone-app
, add a submodule called graphics-lib
, and graphics-lib
has a submodule called renderer
, when you add graphics-lib
to phone-app
, the phone-app/graphics-lib
directory will be populated as a cloned repo but the phone-app/graphics-lib/renderer
directory will be empty. To populate phone-app/graphics-lib/renderer
, first change directories to phone-app/graphics-lib
and follow the instructions below.
First, we need to initialize the submodule(s). We can do that with the following command:
[user@office SampleTheme]$ git submodule init Submodule 'lib/billboard' (git@mygithost:billboard) registered for path 'lib/billboard'
Then we need to run the update in order to pull down the files.
[user@office SampleTheme]$ git submodule update Initialized empty Git repository in ~/git_dev/SampleTheme/lib/billboard/.git/ remote: Counting objects: 26, done. remote: Compressing objects: 100% (22/22), done. remote: Total 26 (delta 5), reused 0 (delta 0) Receiving objects: 100% (26/26), 17.37 KiB, done. Resolving deltas: 100% (5/5), done. Submodule path 'lib/billboard': checked out '1c407cb2315z0847facb57d79d680f88ca004332'
Looking in the lib/billboard directory now shows a nice listing of the needed files.
Removing Submodules
What happens if we need to remove a submodule? Maybe I made a mistake. It could also be that the design of the project has changed, and the submodules need to change with it. No problem, I’ll simply run git submodule rm [submodule path]
and everything will be great, right?
[user@office SampleTheme]$ git submodule rm lib/billboard error: pathspec 'rm' did not match any file(s) known to git. Did you forget to 'git add'? b8ff8f68eb56938b9b4bf993619218fa848c5848 lib/billboard (1.2.25)
Unfortunately, this is wrong. Git does not have a built in way to remove submodules. Hopefully this will be resolved in the future, because we now have to do submodule removal manually.
Sticking with the example, we’ll remove the lib/billboard module from SampleTheme. All the instructions will be run from the working directory of the SampleTheme repository. In order, we need to do the following:
- Remove the submodule’s entry in the .gitmodules file. Since lib/billboard is the only submodule in the SampleTheme repository, we can simply remove the file entirely by running
git rm .gitmodules
. If lib/billboard isn’t the only submodule, the .gitmodules file will have to be modified by hand. Open it up in vi, or your favorite text editor, and remove the three lines relevant to the submodule being removed. In this case, these lines will be removed:[submodule "lib/billboard"] path = lib/billboard url = git@mygithost:billboard
- Remove the submodule’s entry in the .git/config. This step isn’t strictly necessary, but it does keep your config file tidy and will help prevent problems in the future. The submodule’s entry in .git/config will only be present if you’ve run
git submodule init
on the repository. If you haven’t, you can skip this step. In this example, the following lines will be removed:[submodule "billboard"] url = git@mygithost:billboard
- Remove the path created for the submodule. This one is easy. Simply run
git rm --cached [plugin path]
. In this example, I will rungit rm --cached lib/billboard
. As I’ve seen noted elsewhere, do not put a trailing slash as the command will fail. For example, if I rungit rm --cached lib/billboard/
, I get an error:fatal: pathspec 'lib/billboard/' did not match any files
.[user@office SampleTheme]$ git rm --cached lib/billboard rm 'lib/billboard'
Updating Submodules
There is an aspect about submodules that some may not realize when first working with Git submodules. When you add the submodule, the most recent commit of the submodule is stored in the main repository’s index. That means that as the code in the submodule’s repository updates, the same code will still be pulled on the repositories relying on the submodule.
This makes a lot of sense when you consider how your code will have been tested and verified (or at least should be) against a specific version of the submodule code, but the main repository’s code may not work well with new submodule updates before the changes are tested.
Unfortunately, like removing submodules, Git does not make it clear how to update a submodule to a later commit. Fortunately though, it’s not that tough.
- Initialize the repository’s submodules by running
git submodule init
followed bygit submodule update
.[user@office SampleTheme]$ git submodule init Submodule 'lib/billboard' (git@mygithost:billboard) registered for path 'lib/billboard' [user@office SampleTheme]$ git submodule update Initialized empty Git repository in ~/git_dev/SampleTheme/lib/billboard/.git/ remote: Counting objects: 26, done. remote: Compressing objects: 100% (22/22), done. remote: Total 26 (delta 5), reused 0 (delta 0) Receiving objects: 100% (26/26), 17.37 KiB, done. Resolving deltas: 100% (5/5), done. Submodule path 'lib/billboard': checked out '1c407cb2315z0847facb57d79d680f88ca004332'
- Change into the submodule’s directory. In this example,
cd lib/billboard
.[user@office SampleTheme]$ cd lib/billboard [user@office SampleTheme/lib/billboard]$
- The submodule repositories added by
git submodule update
are “headless”. This means that they aren’t on a current branch. To fix this, we simply need to switch to a branch. In this example, that would be the master branch. We switch with the following command:git checkout master
.[user@office SampleTheme/lib/billboard]$ git status # Not currently on any branch. nothing to commit (working directory clean) [user@office SampleTheme/lib/billboard]$ git checkout master Previous HEAD position was b8ff8f6... re-ordering Switched to branch 'master' Your branch is behind 'origin/master' by 8 commits, and can be fast-forwarded. [user@office SampleTheme/lib/billboard]$ git status # On branch master # Your branch is behind 'origin/master' by 8 commits, and can be fast-forwarded. # nothing to commit (working directory clean)
- Next, we simply need to update the repository to ensure that we have the latest updates. We can do that with a pull:
git pull
.[user@office SampleTheme/lib/billboard]$ git pull remote: Counting objects: 31, done. remote: Compressing objects: 100% (24/24), done. remote: Total 24 (delta 15), reused 0 (delta 0) Unpacking objects: 100% (24/24), done. From mygithost:billboard b8ff8f6..5cab93f master -> origin/master * [new tag] 1.2.28 -> 1.2.28 From mygithost:billboard * [new tag] 1.2.26 -> 1.2.26 * [new tag] 1.2.27 -> 1.2.27 Updating c547e0d..5cab93f Fast-forward billboard.php | 109 ++++++++++++++- classes/view_gettingstarted.php | 107 ++++++++++++++ classes/view_gettingstarted_content.php | 38 +++++ css/admin.css | 26 ++++ history.txt | 22 +++- js/admin.js | 17 +++ lib/updater/get.php | 94 +++++++----- lib/updater/history.txt | 9 ++ lib/updater/updater.php | 241 ++++++++++++++++++------------- 9 files changed, 519 insertions(+), 144 deletions(-) create mode 100644 classes/view_gettingstarted.php create mode 100644 classes/view_gettingstarted_content.php create mode 100644 css/admin.css create mode 100644 js/admin.js create mode 100644 lib/updater/history.txt
- Now switch back to the root working directory of the repository. In our example, we are two directories in, so we run
cd ../..
.[user@office SampleTheme/lib/billboard]$ cd ../.. [user@office SampleTheme]$
- Everything is now ready to be commit and pushed back in (if there is a remote repository to push to that is). If you run
git status
, you’ll notice that the path to the submodule is listed as modified. This is what you should expect to see. Simply add the path to be commit and do a commit. When you do the commit, the index will update the commit string for the submodule.[user@office SampleTheme]$ git status # On branch master # Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: lib/billboard (new commits) # no changes added to commit (use "git add" and/or "git commit -a") [user@office SampleTheme]$ git add lib/billboard [user@office SampleTheme]$ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: lib/billboard #
Closing Thoughts
I’ve learned a lot in my short time with Git. Expect to see more details about working with Git in the future. I have a series of pitfalls I’ve discovered that I should organize together and post about. I’ve also created some really slick scripts to help me automate numerous things when working with the repositories that I’d like to share.
If there is anything specific you’d like to know about Git, please add a comment or contact me.
Did I help you?
Thanks a bunch for taking your time to document and explain this. It helped me out.
Best regards
Lasse
[…] Git Submodules: Adding, Using, Removing, Updating […]
This may seem like a novice question because well, it is…
If I have a branch off of master and perform the submodule removal steps on master, the submodule would still be available on the branch and would only get removed if I were to pull the master changes onto the branch. Is that correct??
Strangely enough, I’ve never had to do this. I ran a quick test, and each branch kept track of the submodule or lack thereof as expected when switching between them. I think that leaving the submodule details in
.git/config
would be for the best though since those details are for the repository as a whole and not a specific branch.Great!! Thanks for the quick followup!!
hi,
i try to push the submodule change, meaning i created a submodule under one folder but i can’t push the change,
got the following:
$ git push
warning: push.default is unset; its implicit value is changing in
Git 2.0 from ‘matching’ to ‘simple’. To squelch this message
and maintain the current behavior after the default changes, use:
git config –global push.default matching
To squelch this message and adopt the new behavior now, use:
git config –global push.default simple
See ‘git help config’ and search for ‘push.default’ for further information.
(the ‘simple’ mode was introduced in Git 1.7.11. Use the similar mode
‘current’ instead of ‘simple’ if you sometimes use older versions of Git)
Counting objects: 6, done.
Delta compression using up to 24 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 668 bytes | 0 bytes/s, done.
Total 5 (delta 1), reused 0 (delta 0)
remote: error: refusing to update checked out branch: refs/heads/dev
remote: error: By default, updating the current branch in a non-bare repository
remote: error: is denied, because it will make the index and work tree inconsist
ent
remote: error: with what you pushed, and will require ‘git reset –hard’ to matc
h
remote: error: the work tree to HEAD.
remote: error:
remote: error: You can set ‘receive.denyCurrentBranch’ configuration variable to
remote: error: ‘ignore’ or ‘warn’ in the remote repository to allow pushing into
remote: error: its current branch; however, this is not recommended unless you
remote: error: arranged to update its work tree to match what you pushed in some
remote: error: other way.
remote: error:
remote: error: To squelch this message and still keep the default behaviour, set
remote: error: ‘receive.denyCurrentBranch’ configuration variable to ‘refuse’.
To D:/Users/rma/centra/
! [remote rejected] dev -> dev (branch is currently checked out)
error: failed to push some refs to ‘D:/Users/rma/centra/’
what is the best way to solve this issue?
thanks in advance
I really don’t wish to be crass, but there are many existing answers for this issue readily available. I recommend that you read through them since “best” completely depends on what you have and what you want, neither of which I have any details about.
Thanks a lot
I like your post so much. It is so simple and clean. My confusion is resolved.
excellent post, very helpful, thanks so much !
[…] https://chrisjean.com/git-submodules-adding-using-removing-and-updating/ […]
Hey Chris.
A fantastic article. Thanks so much for the clear explanations.
Using submodules in my Vim bundles now with more confidence!
Thanks for this article – I’ve used it many times to remind myself how to deal with submodules.
One other thing which I use to update all of my submodules is
$ git submodule foreach git pull origin master
Maybe others will find this useful….
[…] https://chrisjean.com/git-submodules-adding-using-removing-and-updating/ […]
How would one grab a directory (and all of its subdirectories) from a repository–not the entire repository–and use only that in the desired submodule.
The use-case is the need to include a portion of an internal project into a external project (i.e. not all the code for the internal project is required (or permitted) to be included in the external project. The granularity is such that creating a repository for each sub component to be included would be both confusing and tedious.
Thank you in advance.
I know of no way to achieve what you are wanting to do without breaking that directory out into its own repository.
Thank you so much.You have made it well documented that even a beginner like me can understand
[…] مرتبط این موضوع در مستندات git – این پست وبلاگی که به توضیح تجربه واقعی استفاده […]
Thanks for this. It was helpful!!
I have a parent Repo AAA
I have another Repo BBB which has 4 branches summer, autumn, winter and spring
I added BBB as a submodule to AAA with below config
Branch summer of BBB to folder AAA/Seasons/summer
Branch autumn of BBB to folder AAA/Seasons/autumn
Branch winter of BBB to folder AAA/Seasons/winter
Branch spring of BBB to folder AAA/Seasons/spring
So my .gitmodules contains 4 entries to the same submodule but different branches of the submodule.
Now when I clone my parent repo and do a submodule update –init –recursive, will the data from all 4 branches of the submodule be populated in the respective folders?
If yes, is there a way i can populate data from only one branch of the submodule
If no, which branch of the submodule will it populate ?
You misunderstand how the
.gitmodules
file works. The.gitmodules
file is a tracked file. This means that each of the four branches will have their own.gitmodules
file with each file having the details specific to just that branch. So, when you init or update submodules in a branch, you’ll only get the submodules for that branch.Thanks! A nitpick: git … init with git … update can be shortened to a `git submodule update –init`. The command may have not existed back when the post was written, but now it does, so post may hopefully may be simplified a bit.
There’s also `git submodule deinit` now, but I just looked up how to use on StackOverflow, and it seems to be just as bad as without it (geez, why would they even added this command then? ¯\_(ツ)_/¯).