I have been using git submodules for a while now and I was sometimes lost on how to use it correctly and efficiently. This post will guide you through the needs and usage of a Git submodule.

What is Git submodule?

Git submodule is a Git command that essentially allows external repositories to be embedded within a dedicated subdirectory of the source tree.

Why using Git submodule?

In my opinion there are several situations where using submodules is pretty useful:

  • When you want to separate a part of your code into another repository for other projects to use it and still be able tu use within your project as a dependency.
  • When you do NOT want to use a dependency manager (such as Composer for PHP). Using Composer for personal repositories can be overkill, submodules in that case is a fair choice.
  • When you do use a front end dependency manager type Bower. Bower is good but only get distribution files of each dependencies. It will never clone Git repositories (Therefor your dependencies are read-only).

Starting a repository with submodules

From scratch

Using a Git repository that do not have submodules yet.

Add a submodule

$ git submodule add git@github.com:shprink/BttrLazyLoading.git

From existing submodules

Using a Git repository that already have submodules registered.

Register submodules

$ git submodule init
Submodule 'lib/BttrLazyLoading' (git@github.com:shprink/BttrLazyLoading.git) registered for path 'lib/BttrLazyLoading'
Submodule 'lib/ShprinkOne' (git@github.com:shprink/Shprink-One.git) registered for path 'lib/ShprinkOne'

Checkout submodules

$ git submodule update
Cloning into 'lib/BttrLazyLoading'...
Submodule path 'lib/BttrLazyLoading': checked out '270b55e177ca555bb4fa559d0e663178ac5006a3'
Cloning into 'lib/ShprinkOne'...
Submodule path 'lib/ShprinkOne': checked out '0dddd0f3e24a473675022c7f79c6b1a27a095914'

From .gitmodule file

Using the .gitmodule file of another repository you can build your own by just running a shell script.

.gitmodule file

[submodule "lib/native"]
path = lib/native
url = git@github.com:shprink/BttrLazyLoading.git
[submodule "lib/mobile"]
path = lib/mobile
url = git@github.com:shprink/Shprink-One.git

Shell script

Create an empty file yourfilename.sh and paste the script below:

#!/bin/sh

set -e

git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
    while read path_key path
    do
        url_key=$(echo $path_key | sed 's/\.path/.url/')
        url=$(git config -f .gitmodules --get "$url_key")
        git submodule add $url $path
    done

Run the script by running the command:

$ sh yourfilename.sh

Daily routine

Now that your repository is ready, it is time to start working! Using submodules can increase the number of commands you will need to run, but not necessarily. I personally gained productivity using them.

Status

The git submodule status command let you see the latest commit hash and the branch currently checked out.

$ git submodule status
270b55e177ca555bb4fa559d0e663178ac5006a3 lib/BttrLazyLoading (heads/master)
0dddd0f3e24a473675022c7f79c6b1a27a095914 lib/ShprinkOne (heads/master)

Update

The git submodule update will update your submodules according to the latest source tree (Root repository) known. It means that if your source tree is not up to date, you can end up using old versions of your submodules. This is pretty annoying, especially when working with a team.

If you want to be always up to date it means that your team needs to be rigorous on updating the source tree on every submodule’s commit…

After experiencing this problematic I decided to use the git submodule foreach command instead. It simply loop over your submodules and execute something.

update master on all submodules

$ git submodule foreach 'git pull origin master'

Commit

Now that you have worked on one or several submodules, it is time to commit your changes. to make sure you are ready to share your work run git status on all submodule:

$ git submodule foreach 'git status'
Entering 'lib/BttrLazyLoading'
# On branch 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:   newFile.js
#
Entering 'lib/ShprinkOne'
# On branch master
nothing to commit (working directory clean)

Add your new files

$ git submodule foreach 'git add --all'

Commit your changes

$ git submodule foreach 'git commit -m "your commit message" || true'

Using || true makes sure that the loop will not stop when a repository with nothing to commit is reached. Otherwise you will get the following error message:

Entering 'lib/ShprinkOne'
# On branch master
nothing to commit (working directory clean)
Stopping at 'lib/ShprinkOne'; script returned non-zero status.

Push to your remote

$ git submodule foreach 'git push origin master'

Sources