A quick submodule how-to

When developing a software project, you sometimes find yourself in a situation where you need to use another project as a subpart of your project. This other project can be anything from the another project you are developing to a third-party library. You want to keep the projects separate even though you need to use one project from the other. Git has a mechanism for this kind of project dependency called submodules. The basic idea is that you can clone another Git repository into your project as a subdirectory, but keep the commits from the two repositories separate, as shown in the following diagram:

A quick submodule how-to

Getting ready

We'll start by cloning an example repository to be used as the super project:

$ git clone https://github.com/dvaske/super.git
$ cd super

How to do it...

  1. We'll add a subproject, lib_a, to the super project as a Git submodule:
    $ git submodule add git://github.com/dvaske/lib_a.git lib_a
    Cloning into 'lib_a'...
    remote: Counting objects: 18, done.
    remote: Compressing objects: 100% (14/14), done.
    remote: Total 18 (delta 4), reused 17 (delta 3)
    Receiving objects: 100% (18/18), done.
    Resolving deltas: 100% (4/4), done.
    Checking connectivity... done.
    
  2. Let's check git status using the following command:
    $ 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)
    
      new file:   .gitmodules
      new file:   lib_a
    
  3. We can take a closer look at the two files in the Git index; .gitmodules is a regular file, so we can use cat:
    $ cat .gitmodules
    [submodule "lib_a"]
      path = lib_a
      url = git://github.com/dvaske/lib_a.git
    $ git diff --cached lib_a
    diff --git a/lib_a b/lib_a
    new file mode 160000
    index 0000000..0d96e7c
    --- /dev/null
    +++ b/lib_a
    @@ -0,0 +1 @@
    +Subproject commit 0d96e7cfc4d4db64002e63af0f7325d33bdaf84f
    

    The .gitmodules files contains information about all the submodules registered in the repository. The lib_a file stores which commit the submodule's HEAD is pointing to when added to the super project. Whenever the submodule is updated with new commits (created locally or fetched), the super project will state the submodule as changed while running git status. If the changes to the submodule can be accepted, the submodule revision in the super project is updated by adding the submodule file and committing this to the super project.

  4. We'll update the submodule, lib_a, to the latest change on the develop branch using the following command:
    $ cd lib_a
    $ git checkout develop
    Branch develop set up to track remote branch develop from origin by rebasing.
    Switched to a new branch 'develop'
    $ cd ..
    $ git status
    On branch master
    Your branch is ahead of 'origin/master' by 1 commit.
      (use "git push" to publish your local commits)
    
    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:   lib_a (new commits)
    
    no changes added to commit (use "git add" and/or "git commit -a")
    
  5. Let's just check whether there are any updates to the submodule:
    $ git submodule update
    Submodule path 'lib_a': checked out '0d96e7cfc4d4db64002e63af0f7325d33bdaf84f'
    
  6. Oops! Now we actually reset our submodule to the state as described in the file for that submodule. We need to switch to the submodule again, check develop, and create a commit in the super project this time:
    $ cd lib_a
    $ git status
    HEAD detached at 0d96e7c
    nothing to commit, working directory clean
    $ git checkout develop
    Previous HEAD position was 0d96e7c... Fixes book title in README
    Switched to branch 'develop'
    Your branch is up-to-date with 'origin/develop'.
    $ cd ..
    $ git status
    On branch master
    Your branch is ahead of 'origin/master' by 1 commit.
      (use "git push" to publish your local commits)
    
    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:   lib_a (new commits)
    
    no changes added to commit (use "git add" and/or "git commit -a")
    $ git add lib_a
    $ git commit -m 'Updated lib_a to newest version'
    [master 4d371bb] Updated lib_a to newest version
     1 file changed, 1 insertion(+), 1 deletion(-)
    

    Notice that the submodule is on default in a detached head state, which means that HEAD is pointing directly to a commit instead of a branch. You can still edit the submodule and record commits. However, if you perform a submodule update in the super repository without first committing a new submodule state, your changes can be hard to find. So, always remember to checkout or create a branch while switching to a submodule to work, then you can just checkout the branch again and get your changes back. Since Git Version 1.8.2, it has been possible to make submodules track a branch rather than a single commit. Git 1.8.2 was released on the March 13, 2013, and you can check your version by running git --version.

  7. To make Git track the branch of a submodule rather than a specific commit, we need to write the name of the branch we want to track in the .gitmodules file for the submodule; here we'll use the stable branch:
    $ git config -f .gitmodules submodule.lib_a.branch stable
    $ cat .gitmodules
    [submodule "lib_a"]
      path = lib_a
      url = git://github.com/dvaske/lib_a.git
      branch = stable
    
  8. We can now add and commit the submodule, and then try to update it using the following command:
    $ git add .gitmodules
    $ git commit -m 'Make lib_a module track its stable branch'
    [master bf9b9ba] Make lib_a module track its stable branch
     1 file changed, 1 insertion(+)
    $ git submodule update --remote
    Submodule path 'lib_a': checked out '8176a16db21a48a0969e18a51f2c2fb1869418fb'
    $ git status
    On branch master
    Your branch is ahead of 'origin/master' by 3 commits.
      (use "git push" to publish your local commits)
    
    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:   lib_a (new commits)
    
    no changes added to commit (use "git add" and/or "git commit -a")
    

The submodule is still in the detached HEAD state. However, when updating the submodule with git submodule update --remote, changes from the submodule's remote repository will be fetched and the submodule will be updated to the latest commit on the branch it is tracking. We still need to record a commit to the super repository, specifying the state of the submodule.

There's more…

When you are cloning a repository that contains one or more submodules, you need to explicitly fetch them after the clone. We can try this with our newly created submodule repository:

$ git clone super super_clone
Cloning into 'super_clone'...
done.

Now, initialize and update the submodules:

$ cd super_clone
$ git submodule init
Submodule 'lib_a' (git://github.com/dvaske/lib_a.git) registered for path 'lib_a'
$ git submodule update --remote
Cloning into 'lib_a'...
remote: Counting objects: 18, done.
remote: Compressing objects: 100% (14/14), done.
remote: Total 18 (delta 4), reused 17 (delta 3)
Receiving objects: 100% (18/18), done.
Resolving deltas: 100% (4/4), done.
Checking connectivity... done.
Submodule path 'lib_a': checked out '8176a16db21a48a0969e18a51f2c2fb1869418fb'

The repository is ready for development!

Tip

When cloning the repository, the submodules can be initialized and updated directly after the clone if the --recursive or --recurse-submodules option is given.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset