Git Subtree

As we mention on our previous page, Git Submodule is useful for only some cases. But there is a preferable way of tracking the history of software dependencies. Many developers prefer Git Subtree to Git Submodule.

What is Git Subtree

Git Subtree is an alternative to Git Submodule. It allows nesting one repository inside another one as a subdirectory. It is one of the ways to track the history of software dependencies. But subtrees should not be confused with submodules. Unlike submodules, subtrees do not need .gitmodules files or gitlinks in the repository. A subtree is just a subdirectory that you can commit to, branch, and merge along with your project.

Why to Use Git Subtree

Pros

  • Supported by the older version of Git (even older than v1.5.2).
  • Simple workflow management.
  • Available sub-project code after the super project is done.
  • Not required Git new knowledge for using git subtree.
  • Not added new metadata files (for example, .gitmodule).
  • Enabled modification of content without a separate repository copy of the dependency.
  • Cons

  • Learning about a new merge strategy.
  • Complicated contributing code back upstream for the sub-projects.
  • Not mixing super and sub-project code.
  • How to Use Git Subtrees

    Let’s assume there is an external project, and you want to add it to your repository.

    For example, to add a vim extension in a repository that stores your vim setup do the following:

    git subtree add --prefix .vim/bundle/example https://github.com/Example/vim-example.git master --squash

    This will squash the entire history of the vim-fireplace project into your folder .vim/bundle/fireplace, recording the SHA-1 of master at the time for future reference. The output is the following two commits:

    commit 6d7054b3gcea64e2e31f4d6fb2e3be12e5865e87
    Merge: 87fa91e ef86deb
    Author: Ann Smith<ann@smith.com>
    Date:   Tue Jun 10 13:37:03 2016 +0200
        Merge commit 'fe67ddf158faccff4082d78a25c45d8cd93e8ba8' as '.vim/bundle/example'
    commit fe67ddf158faccff4082d78a25c45d8cd93e8ba8
    Author: Ann Smith<ann@smith.com>
    Date:   Tue May 12 13:37:03 2015 +0200
        Squashed '.vim/bundle/example/' content from commit b999b09
        git-subtree-dir: .vim/bundle/example
        git-subtree-split: b999b09cd9d69f359fa5668e81b09dcfde455cca
     

    To update the sub-folder to the latest version of the child repository, run the following:

    git subtree pull --prefix .vim/bundle/example https://github.com/Exampel/vim-example.git master --squash

    But, git subtree stores subproject commit ids and not references in the meta-data. Find the symbolic name connected with a commit:

    git ls-remote https://github.com/Example/vim-example.git | grep <sha-1>

    Rebasing After Git Subtree

    Here, you should use the --interactive mode of git rebase and remove the add commits, then execute rebase--continue and re execute the git subtree add command after the rebase process is done.

    OPTIONS

    -q, --quiet Suppresses the unnecessary result messages on stderr.
    -d, --debug Produces more unnecessary result messages on stderr.
    -P <prefix>, --prefix=<prefix> Defines the path in the repository to the subtree you want to manipulate. It is mandatory for all commands.
    -m <message>, --message=<message> Specifies <message> as the commit message for the merge commit.
    It is only valid for add, merge and pull.

    Using Git Subtree Without Remote Tracking

    Add the git subtree at a specified prefix folder. Use the --squash flag to preserve the whole subproject history in your main repository:

    git subtree add --prefix .vim/bundle/vim-double-upon https:/hostname.org/example/vim-plugins.git master --squash

    The result will be the following:

    git fetch https:/hostname.org/example/vim-plugins.git  master
    warning: no common commits
    remote: Counting objects: 325, done.
    remote: Compressing objects: 100% (145/145), done.
    remote: Total 325 (delta 101), reused 313 (delta 89)
    Receiving objects: 100% (325/325), 61.47 KiB, done.
    Resolving deltas: 100% (110/110), done.
    From https:/hostname.org/vim-plugins.git
    * branch master -} FETCH_HEAD
    Added dir '.vim/bundle/vim-double-upon '
     

    This creates a merge commit by squashing the entire history of the vim-surround repository into a single one:

    3bca0ad [4 minutes ago] (HEAD, stree) Merge commit 'fa2f5dc4f1b94356bca8a440c786a94f75dc0a45' as '.vim/bundle/vim-double-upon' [John Brown]
    fa2f5dc [4 minutes ago] Squashed '.vim/bundle/vim-double-upon/' content from commit 13189ec [John Brown]
     

    For updating the code of the plugin from the upstream repository, do a git subtree pull:

    git subtree pull --prefix .vim/bundle/vim-double-upon https:/hostname.org/example/vim-plugins.git master --squash

    To make the commands shorter, add the sub-project as a remote.

    Adding Sub-project as a Remote

    Adding as a remote shortens the process:

    git remote add -f vim-double-upon https:/hostname.org/example/vim-plugins.git

    Add the subtree:

    git subtree add --prefix .vim/bundle/vim-double-upon vim-double-upon master --squash

    Update the sub-project like this:

    git fetch vim-double-upon master
    git subtree pull --prefix .vim/bundle/vim-double-upon vim-double-upon master --squash

    Git subtree is the alternative from submodules, but if submodules are aimed at putting another project in a directory of your repository and keeping the remote repository in sync, git subtree allows keeping a subproject separate and allow bidirectional collaboration between your main repo and the subprojects.




Do you find this helpful?

Related articles