azure-devopsgit-flowsquash

Gitflow, Squash merged and AzDO branch compare


We are currently using Git flow as a branching strategy, and we are squashing our feature branches into develop. We are also squashing our release branches into main.

The AzDO branches page show differences in commits between develop -> main, which I expect, but it also shows major differences in the files, which when doubled checked in git CLI are erroneous.

Does anyone know how AzDO compares branches? And more importantly can it be configured?

I was expecting to see commit differences but no file changes.


Solution

  • I believe the Azure DevOps (AzDO) compare branches screen uses the same diff as the Pull Request view, which is diffing the merge-base of the two branches with the source branch. So for example, if you are comparing develop to main on the branches screen, the diff would be the same view you see if you create a PR of develop into main (which you probably wouldn't do in Git Flow), and would be equivalent to the following command (using Git Bash):

    git diff $(git merge-base develop main) develop
    

    The equivalent shorthand 3-dot diff syntax would be:

    git diff main...develop
    

    Presumably the command line diff syntax you're using (and preferring) is 2 dots (which is equivalent to zero dots):

    git diff main develop
    

    AFAIK it isn't possible to change the diff AzDO uses on the branches page, so you'll have to use command line or your favorite UI to see that diff.

    I believe at this point your specific questions have been answered, but this answer wouldn't be complete without discussing the reason why you like the no dots diff, and the reason AzDO uses the 3 dots diff.

    Motivation behind the 3-dot syntax

    It turns out that most people, most of the time, prefer the 3 dots diff in a Pull Request. Arguably, the branch compare screen is intended to be used in preparation for a merge (or PR) and so it makes sense to show the same diff on that screen too.

    This may not be the best view for your described scenario though, because your default view is going to be scary; it will look like you're about to change many files that you know aren't actually changing. In the PR view there is an option to "View merge commit" and that will show you what will actually change, if you complete the PR at the moment that merge commit was generated. As soon as the source or target branch changes, the merge commit will need to be re-calculated, and there is an option for that as well if needed.1 The changing of the target branch is the reason most people prefer the 3-dot syntax. Here's an example:

    Suppose,

    1. You create a new branch feature from the latest develop. At this moment feature and develop are identical, which means they are pointing to the same commit.
    2. You work on your feature branch for a few hours, and when you're done you have two beautiful commits that you are ready to merge into develop. You push your branch, and create a PR in AzDO to merge feature into develop.
    3. During the last few hours, 5 other PRs were completed into develop with the squash merge strategy, and now develop has 5 new commits on it that your branch doesn't know about.

    The PR uses the 3 dot syntax diff, which shows you only what changed on feature since you branched off of develop. This should match the changes you made and makes it easy to do a code review. If the PR used the regular diff syntax, you would see a straight diff between feature and develop which would include all of the changes in the other 5 commits that had nothing to do with the changes you made. This would be confusing to see and would be difficult to code review.

    Now, I'm guessing you agree with the previous paragraph for the scenario of merging a feature branch into develop, and on the branches "compare" page you would see the same thing when comparing feature to develop. But why then is the compare of develop (or release) to main showing many changes that are already on main? (And the same should be true when you create a PR of release into main.)

    The reason you're having this issue is due to a slight flaw in your workflow:

    We are also squashing our release branches into main.

    As a rule of thumb, you should never rewrite a shared branch.

    In Git Flow, the shared branches are develop, main, release, and hotfix. So this means any time the source branch of a Pull Request is one of those branches, you should not rewrite the branch. In AzDO, there are 4 merge strategies to choose from when completing a PR:

    1. Merge (no fast-forward)
    2. Squash commit
    3. Rebase and fast-forward
    4. Semi-linear merge (aka Rebase, merge)

    All but strategy #1 might rewrite the source branch, so that means when doing a PR where the source branch is one of the special shared branches, the only strategy you should complete the PR with is #1 (Merge). So, it's fine to use the squash strategy when completing feature branches into develop, and you can also use squash when completing bug fix branches into release or hotfix, but when PR'ing any of those 4 shared branches into another branch, you should not squash, or rebase. Because you have been squashing release into main for a while, you've been rewriting all of those commits every time which means many of the commits on develop aren't ever landing on main. Therefore the 3 dot syntax of "What have I changed on develop since I branched off of main?" has lots of commits in it- and consequently many more changes showing up that are already there. It probably gets worse every time you merge a release branch into main.

    The Fix

    The first thing you should do is merge main into develop with a regular merge. If you use a PR then you should select merge strategy #1, because the source branch, main, is a shared branch.

    From then on, follow the rule of thumb and this problem should mostly2 go away.

    Side Note

    Git Flow recommends using --no-ff for every merge, but that is actually only necessary for PRs that are merging in more than 1 commit. If you don't care about the individual commits, squashing is compatible with Git Flow as long as you never use it for merging any of the shared branches. In one of my Git Flow projects in Azure DevOps, we setup a branch policy on develop, release, and hotfix to only allow "Semi-Linear Merge" (which does a rebase and then forces a merge commit with --no-ff). If you prefer Squash you could do the same on those branches but lock it to Squash. However, anytime we do a merge where the source branch is one of the shared branches, e.g. release into develop, then we use the Override functionality so that certain people can complete those merges with the regular "Merge" strategy. I haven't figured out a clean way to enforce that rule for certain shared branches, though it would be nice if it were possible.


    1 In AzDO Pull Requests, the merge commit is automatically re-calculated anytime the source branch changes, but not when the target branch changes. If you wish to recalculate it after the target branch changes you can. At the moment you complete the PR it will also recalculate it if needed. Note this means it's possible to not know there are merge conflicts until the moment you attempt to complete the PR, if the target branch has changed and caused new merge conflicts since the last time the merge commit was generated.

    2 It's still possible to have the same problem under normal circumstances without violating the rule of thumb about never rewriting a shared branch. For example, suppose someone completed a PR into develop and then soon after realized that the change really ought to have gone into the release branch instead. So the change is cherry-picked over to a feature branch coming off of release and then PR'd into release. Now there are 2 different commits with the identical changes in them, and when release gets merged back into develop, that PR will show those changes even though they are already in develop, simply because they are (purposefully) in two different commits. This is one of the reasons for another rule of thumb in Git: "Avoid cherry-picking if it's possible to achieve the same thing with a merge instead." If you knew in advance that you needed the change on both develop and release, then you would merge that into release, and then merge release down to develop since you can do that anytime. Or, if possible, the change could branch off of the merge-base of develop and release (or even the original commit where the bug was introduced!), and then the new commit could be merged into both develop and release.