Let’s talk subtrees. This, to me, is a close tie with bisect for the most impressive tool Git has out there. Subtrees allow you to have subprojects within a subdirectory of the main project. You can commit them with the parent directory, make a branch from subtrees, and even do subtree merges with the parent directory. Subtrees have been available in the stock version of Git since version 1.7.11, May 2012.
There have been comparisons of subtrees to submodules, but for most cases, subtrees take the cake:
- You can make commits to the parent directory (main project) with the subtree’s history, or you can make a change just in the subtree and push its changes independent of the parent directory up to its own repository.
- Once you push a commit up to the remote of the parent directory, the subtree comes with it. So if we clone the parent directory’s remote, we will also automatically get the subtree with it.
- Simple to use, no need to learn anything new (unless you count commands with “subtree” before them), and you can just ignore the subtrees as well.
So let’s walk through a scenario: We have a team of developers who are working on a Console Application in C# called Greetings that greets a user in different languages. Its repo is located both on Visual Studio Online and our local machine as GreetingsRepo. It has a dependency, SayHi, that does the heavy-lifting of translations. SayHi also has repos on Visual Studio Online and locally, SayHiRepo. What we ultimately want to do is allow others developers to clone GreetingsRepo and already have SayHi in the project so that they can improve Greetings or even just make changes in SayHi and push them to SayHiRepo. We’ll use subtrees to make our local SayHiRepo a subtree for GreetingsRepo. A diagram of the two repos is shown below:
First, let’s go into our local GreetingsRepo and add the SayHiRepo that is on Visual Studio Online (we will call it SayHiRemoteRepo) as a remote so that it’s less typing to push/pull:
$ git remote add <remote name> <remote URL>
Next, we will add a subtree to Greetings with an add command:
$ git subtree add –-prefix=<new folder> <remote> <branch>
One thing to note is that all of the history in SayHiRepo is also pulled down into our repo. If you’d rather have a cleaner history, you can squash all of the commits into one single commit by using the –-squash command when adding the subtree instead (we are keeping the history as-is in our example):
$ git subtree add –-prefix=<new folder> <remote> <branch> –-squash
Almost by magic, the solution file for SayHi is now in our Greetings project! The subdirectory appears as a folder in the repo (next to our Greetings project). We can make edits to either solution.
If we make changes in Greetings.cs and SayHi.cs, commit to the parent directory, and push to GreetingsRepo on Visual Studio Online, the changes and the subtree (as well as its history) will appear there:
Note: I would recommend creating a branch before making changes in your subdirectory (to avoid making master complicated).
But what if I wanted to just make a change in SayHi.sln and push that change up to SayHiRemoteRepo? You can with subtree push. If I make a change just in SayHi.cs and make a commit in the parent directory, we can then push using:
$ git subtree push –-prefix=<subtree folder> <remote> <branch>
The coolest thing about subtree push is that it automatically filters or “splits” the commits that don’t relate to the sub-directory. So it only pushed to SayHiRemoteRepo the changes that dealt with SayHi (including the commit that changed Greeting.cs and SayHi.cs):
To update our subdirectory, SayHiRepo, we pull changes from our remote, SayHiRemoteRepo:
$ git subtree pull –-prefix=<subtree folder> <remote> <branch>
So did subtrees work if we want to clone our main project and have a subdirectory included in it? Take a look:
And there you have it, friends. Subtrees are a great way to maintain good workflow, cause little disturbances in your main project, and are simple to use. Try it out for yourselves!