Establishing a git workflow with VSTS and Visual Studio - git rebase
In this post I will look at git rebase in Visual Studio, in particular rebasing a working branch onto master and at what happens to each commit. This post will not make a case for rebase over alternatives, though the findings will contribute to the workflow that will be decided upon.
All posts in this series on establishing a git workflow for VSTS and Visual Studio are:
- Establishing a git workflow with VSTS and Visual Studio
- Configuring a VSTS repo from the Github sample repo
- Git merge
- Git rebase (this post)
Recap on setup
If you have just landed on this post then a quick word on set up. This post builds on the git_workflow_sample_setup available on Github. For this post the sample has been cloned and pushed to VSTS. The previous posts describe the sample and steps to clone, reconfigure and push to VSTS from Visual Studio.
What is the expected end state?
Given the current commit history for master and the merge_sample branch as:
Relying on Visual Studio’s integrated git tooling, the end result expectation for source, (shown below), is the same as for merge and is:
- the Hello World program cleanly merged
- the project xml to be cleanly merged with the new HelloWorld and Translator class files
And how did we get there?
While the end result for source code is the same as for merge, a rebase replays commits editing each when there is change and resulting in a new commit being created in place. Again my incorrect view around commits being time based, once led to an expectation in terms of commit history on the merge_branch as:
This came about through my use of git log, which by default presents commit history in reverse chronological order. I will later show how this is not the case.
Let us rebase
The intended action is a rebase the merge_branch working branch onto master. The language here is very different to that of merge where the action is merge from master into the merge_branch, and despite the wording, all activities takes place on the branch.
First, verify merge_branch is the checked out or active branch by navigating to the Home view, select Branches and then double-click the branch or alternatively right-click on the branch to select Checkout as shown below.
To rebase onto master, right-click on merge_branch and select Rebase Onto. Then in the inline popup, select master as the Onto Branch and click Rebase. The inline popup changes to a merge conflicts warning, stating there is one conflict.
Before addressing the rebase conflict it is worth taking a moment to look at the current changes (shown below) by navigating to the Home view and selecting Changes.
Here we only see the pending changes due to merge conflict that must be resolved manually. This is very different to merge and unfortunately viewing the branch commit history does not reveal anything further however, git bash gives a sense of what a rebase does, and here we can see the auto-merged changes that were previously staged when merging, have been staged and committed into a new commit.
The dreaded merge conflict
The actions and concerns around resolving the rebase merge conflict are sufficiently similar to that of merge that they will not be covered here again. Instead please see the previous post Git merge.
Note, once the code merge is accepted and all conflicts are resolved that unlike the merge where a Commit Merge action was required, click View Changes at the Resolve Conflicts view and then click Continue under Rebase In Progress.
The end state
The expected end state for the code conflict merge has been arrived at, and just as with merge, the tooling only gets us so far and manual intervention inevitable. This problem is only around conflicting sections of code, for example the tooling successfully auto merges the xml project file.
And how did we get here?
Looking at the commit history using the Visual Studio's branch history and branch commit history illustration below, it is clear the commit history is not in chronological order. The commit history is very different to that of merge, and for the rebase action can be summarised as:
- Save off all commits on the target merge_branch
- Clear the target merge_branch
- Write all commits (unchanged) from master to the target merge_branch since the branches diverged
- Replay each saved commit in chronological order to the target merge_branch, and for each commit perform a diff against the last commit written to the target merge_branch.
If there is a change and if the change can be auto-merged then stage the commit the change into a new commit.
If however, there is a conflict then raise a merge conflict action. The change is saved to the working directory and must be resolved manually.
Once resolved the change is staged and committed into a new commit through the rebase continue action.
Furthermore, unlike merge where a final commit was created (with two parents) containing all of the changes, the rebase action results in a single parent commits, with each of the original merge_branch commits being recreated as they are replayed. Each commit may contain identical code to the original commit, or auto or manually merged code based on the original commit and the previously written commit.
The git rebase flow presented by Visual Studio's Team Explorer views is easy to follow. Insofar as resolving conflicts, just as with merge, it is possible to break code. Furthermore, Visual Studio masks what is going on behind the scenes
For the simple case being presented there is not much to choose from between merge and rebase, however when there are multiple conflicting changes across multiple commits then merge does offer advantages, and these will be looked into further in subsequent posts on commit squashing.
The end state for commit history on the merge_branch is acceptable for the branch and would be the ideal end state when pushing to master. Again this will be further explored in a subsequent post on pull requests.