Subtree merging

An alternative to submodules is subtree merging. Subtree merging is a strategy that can be used while performing merges with Git. The subtree merge strategy is useful when merging a branch (or as we'll see in this recipe another project) into a subdirectory of a Git repository instead of the root directory. When using the subtree merge strategy, the history of the subproject is joined with the history of the super project, while the subproject's history can be kept clean except for commits indented to go upstream.

Getting ready

We'll use the same repositories as in the last recipe, and we'll reclone the super project to get rid of the submodule setup:

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

How to do it...

We'll add the subproject as a new remote and fetch the history:

$ git remote add lib_a git://github.com/dvaske/lib_a.git
$ git fetch lib_a
warning: no common commits
remote: Reusing existing pack: 18, done.
remote: Total 18 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (18/18), done.
From git://github.com/dvaske/lib_a
 * [new branch]      develop    -> lib_a/develop
 * [new branch]      master     -> lib_a/master
 * [new branch]      stable     -> lib_a/stable

We can now create a local branch, lib_a_master, which points to the same commit as the master branch in lib a (lib_a/master):

$ git checkout -b lib_a_master lib_a/master
Branch lib_a_master set up to track remote branch master from lib_a by rebasing.
Switched to a new branch 'lib_a_master'

We can check the content of our working tree using the following command:

$ ls
README.md  a.txt

If we switch back to the master branch, we should see the content of the super repository in our directory:

$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ ls
README.md  super.txt

Git changes branches and populates the working directory as normal even though the branches are originally from two different repositories. Now, we want to merge the history from lib_a into a subdirectory. First, we prepare a merge commit by merging with the ours strategy and make sure the commit isn't completed (we need to bring in all the files).

$ git merge -s ours --no-commit lib_a_master
Automatic merge went well; stopped before committing as requested

In short what the ours strategy tells Git to do the following: Merge in this branch, but keep the resulting tree the same as the tree on the tip of this branch. So, the branch is merged, but all the changes it introduced are discarded. In our previous command line, we also passed the --no-commit option. This option stops Git from completing the merge, but leaves the repository in a merging state. We can now add the content of the lib_a repository to the lib_a folder in the repository root. We do this with git read-tree to make sure the two trees are exactly the same as follows:

$ git read-tree --prefix=lib_a/ -u lib_a_master

Our current directory structure looks as follows:

$ tree
.
|-- README.md
|-- lib_a
|   |-- README.md
|   '-- a.txt
'-- super.txt

It is time to conclude the merge commit we started using the following command:

$ git commit -m 'Initial add of lib_a project'
[master 5066b7b] Initial add of lib_a project

Now the subproject is added. Next, we'll see how we can update the super project with new commits from the subproject and how to copy commits made in the super project to the subproject.

We need to add and commit some changes to the super project using the following command:

$ echo "Lib_a included!" >> super.txt
$ git add super.txt
$ git commit -m "Update super.txt"
[master 83ef9a4] Update super.txt
 1 file changed, 1 insertion(+)

Some changes are made to the subproject committed in the super project:

$ echo "The b file in lib_a" >> lib_a/b.txt
$ git add lib_a/b.txt
$ git commit -m "[LIB_A] Enhance lib_a with b.txt"
[master debe836] [LIB_A] Enhance lib_a with b.txt
 1 file changed, 1 insertion(+)
 create mode 100644 lib_a/b.txt

The current history looks like the following screenshot:

How to do it...

The merge can be seen in the previous screenshot and the two root commits of the repository, the original root commit and the root from lib_a.

Now, we will learn to integrate new commits into the super repository made in the subproject, lib_a. Normally, we would do this by checking out the lib_a_master branch and performing pull on this to get the latest commit from the remote repository. However, as we are working with example repositories in this recipe, no new commits are available on the master branch. Instead, we'll use the develop and stable branches from lib_a. We'll now integrate commits from the develop branch on lib_a. We do this directly using the lib_a/develop reference in the repository as follows:

$ git merge -m '[LIB_A] Update lib_a project to latest state' -s  subtree lib_a/develop
Merge made by the 'subtree' strategy.
 lib_a/a.txt | 2 ++
 1 file changed, 2 insertions(+)

Our master branch has now been updated with the commits from lib_a/develop as shown in the following screenshot:

How to do it...

Now, it is time to add the commits we made in the lib_a directory back to the lib_a project. First, we'll change the lib_a_master branch and merge that with lib_a/develop to be as up to date as possible:

$ git checkout lib_a_master
$ git merge lib_a/develop
Updating 0d96e7c..ab47aca
Fast-forward
 a.txt | 2 ++
 1 file changed, 2 insertions(+)

Now we are ready to merge changes from the super project to the subproject. In order not to merge the history of the super project to the subproject, we'll use the --squash option. This option stops Git from completing the merge, and unlike the previous case where we also stopped a merge from recording a commit, it does not leave the repository in a merging state. The state of the working directory and staging area are, however, set as though a real merge has happened.

$ git merge --squash -s subtree --no-commit master
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested

Now, we can record a commit with all the changes done in lib_a from the super project:

$ git commit -m 'Enhance lib_a with b.txt'
[lib_a_master 01e45f7] Enhance lib_a with b.txt
 1 file changed, 1 insertion(+)
 create mode 100644 b.txt

The history for the lib_a repository is seen in the following screenshot:

How to do it...

We can integrate more changes from lib_a/stable into the super project, but first we'll update the lib_a_master branch so we can integrate them from here:

$ git merge lib_a/stable
Merge made by the 'recursive' strategy.
 a.txt | 2 ++
 1 file changed, 2 insertions(+)

A new commit was added to the subproject, as shown in the following screenshot:

How to do it...

The last task is to integrate the new commit on lib_a_master to the master branch in the super repository. This is done as in the previous case with the subtree strategy option to git merge:

$ git checkout master
$ git merge -s subtree -m '[LIB_A] Update to latest state of lib_a' lib_a_master
Merge made by the 'subtree' strategy.
 lib_a/a.txt | 2 ++
 1 file changed, 2 insertions(+)

The resulting history is shown in the following screenshot:

How to do it...

How it works…

When using the subtree strategy, Git finds out which subtree in your repository, the branch you are trying to merge fits in. This is why we added the content of the lib_a repository with the read-tree command to make sure we got the exact same SHA-1 ID for the lib_a directory in the super project as the root tree in the lib_a project.

We can verify this by finding the SHA-1 of the lib_a tree in the super project in the commit where we merged in the subproject:

$ git ls-tree a3662eb94abf0105a25309653b5d2ce67a4028d2
100644 blob 456a5df638694a699fff7a7ff31a496630b12d01  README.md
040000 tree 7d66ad11cb22c6d101c7ac9c309f7dce25231394  lib_a
100644 blob c552dead26fdba634c91d35708f1cfc2c4b2a100  super.txt

The ID of the root tree at lib_a/master can be found out by using the following command:

$ git cat-file -p lib_a/master
tree 7d66ad11cb22c6d101c7ac9c309f7dce25231394
parent a7d76d9114941b9d35dd58e42f33ed7e32a9c134
author Aske Olsson <[email protected]> 1396553189 +0200
committer Aske Olsson <[email protected]> 1396553189 +0200

Fixes book title in README

See also

Another way of using subtree merging is with the git subtree command. This is not enabled by default in Git installations, but is distributed with Git since 1.7.11. You can see how to install and use it at the following links:

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

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