Pull Requests with Rebase

Edward Thomson

We’re excited to roll out another way to integrate your pull requests in Azure Repos. Arriving in the Sprint 150 update is an option to rebase your pull request into the target branch. This lets you keep a linear commit history in your master branch, which many people think is an elegant way to visualize history.

Like tabs vs spaces, the way code gets integrated is the subject of heated debates on teams. Some people prefer merges, some people prefer rebase, and some people prefer a hybrid approach or even a “squash”. Azure Repos supports each of these scenarios:

Merge (no fast-forward)

This is the default integration strategy in Azure Repos, GitHub and most other Git providers. It emulates running git merge pr from the master branch. All the individual commits in the pull request branch are preserved as-is, and a new merge commit is created to unite the master branch and the pull request branch.

This strategy is helpful because it illustrates exactly how a developer (or developers) worked on a pull request, including each individual commit along the way. It gives the most insight into how a branch evolves, but since it preserves every commit is may be very verbose.

Squash commit

Squashing will take the tree that’s produced in a merge and creates a single new commit with those repository contents. It emulates running git merge pr --squash from the master branch. The resulting commit is not a merge commit; those individual commits that made up the pull request are discarded.

When this strategy is used, history is reminiscent of a centralized version control system. Each pull request becomes a single commit in master, and there are no merges, just a simple, straight, linear history. Individual commits are lost, which is best for teams that use “fix up” commits or do not carefully craft individual commits for review before pushing them.

Rebase

Rebase will take each individual commit in the pull request and cherry-pick them onto the master branch. It emulates running git rebase master on the pull reuqest branch, followed by git merge pr --ff-only on the master branch.

When this strategy is used, history is straight and linear, like it is with the “squash” option, but each individual commit is retained. This is useful for teams that practice careful commit hygeine, where each individual commit stands on its own.

Semi-linear merge

This strategy is the most exotic – it’s a mix of rebase and a merge. First, the commits in the pull request are rebased on top of the master branch. Then those rebased pull requests are merged into master branch. It emulates running git rebase master on the pull request branch, followed by git merge pr --no-ff on the master branch.

Some people think of this as the best of both worlds: individual commits are retained, so that you can see how the work evolved, but instead of just being rebased, a “merge bubble” is shown so that you can immediately see the work in each individual pull request.

Branch Policies

Many teams want to choose a pull request integration strategy and standardize on it for the whole team. Regardless of whether you prefer the rebase approach or the merge approach, it’s often helpful to use same approach for each pull request.

Thankfully, you can configure a branch policy to enforce your preferred integration strategy (or strategies). Selecting only one will mean that pull requests into master will always use a single strategy.

And, of course, these branch policies that limit the merge types for a pull request are the perfect companion to the other branch policies. One that’s indispensible, of course, is the build validation branch policy, where you can set up an Azure Pipelines build for your master branch, and your build and tests must succeed before a pull request can be merged (or squashed, or rebased) into master.

8 comments

Discussion is closed. Login to edit/delete existing comments.

  • Gaurav Kanoongo 0

    I was waiting for this semi-linear merge fearure since very long – thanks for introducing it.  

  • Phillips 0

    I find that enforcement of some of these strategies to miss the importance of commits. I find the appropriate answer is what will provide safety and communication. A merge commit allows for rollback of a collection of changes  A squash commit prevents crafting good separation of changes (atomic commits) 

    https://dev.to/jessekphillips/git-is-a-communication-tool–2j9k

  • Bart Sipes 0

    Any chance you can sync up the titles in the branch policies with what you have here? I assume that your blog post section of “Rebase” is the same as “Rebase and fast-forward” and the “Semi-linear merge” is the same as “Rebase with merge commit” but I’m not certain. For people who are new to git, it’d be nice if the descriptions can be consistent to help us understand what’s what. Thanks

  • Bruno Lima 0

    Recently, my team are using Squash in individual branch and rebase strategy my pull request to master. Thereby, the story of branch master is linear and straight besides keeping the history of branch clean 

  • Bruno Lima 0

    Recently, my team are using Squash in individual branch and rebase strategy in pull request to master. Thereby, the story of branch master is linear and straight besides keeping the history of branch clean 

  • Heath StewartMicrosoft employee 0

    In which strategies is the commit author(s) retained? If all commits in a squash are from the same author, is the author retained there as well? This information is often crucial to diagnosing certain types of defects that may require some context from the original author.

  • Kenneth Kin Lum 0

    I am wondering for Semi-linear merge, after the rightmost blue dot is already basing off from the tip of master, how is it possible yet another commit is created?

    For example, for a repo with one single file with the number 1 on line 1, and so forth all the way to line 100, having the number 100:

    left blue is changing 99 to 99 99
    right blue is changing 100 to 100 100

    rightmost grey (tip of master) is changing 2 to 2 2
    the one left to it is changing 1 to 1 1

    So if right blue is already based off from rightmost grey, then now that state of the file is:

    1 1
    2 2
    3
    4
     ...
    99 99
    100 100
    

    This already is the final state of everything. So how can “yet another state” be created? If yet another state is created, then mustn’t it be identical to the newest right blue?

  • Guillaume GABARD 0

    What about a “manual merge”?
    One of the valuable things I see with pull requests is that the work branch is like a sand box, where things can be fixed as reviewers and testers are sending feedback. At the end, when everything works as expected and I am ready to complete the pull request, I sometimes get a messy history that I don’t want to rebase as-is in the master.
    I would like to be able to clean my commits (merge some, reorder others, change messages,…) the same way I do with local commits before I push them.
    The four merge types described above do not offer this possibility: “Rebase” or “semi-linear merge” will push every single messy commit to the master, with no possibility to interact.
    It would be nice to be able to merge or rebase manually, and simply complete the pull-request with no action (“manual merge”), assuming everything has been done externally

Feedback usabilla icon