Git with the flow, dude!

Published: Sep 16, 2016 by luxagen

This evening I received an e-mail from one of my developers:

Found this interesting article on git model. What do you think?

My heart sank. Did yours? Maybe you’re lucky enough not to know exactly where that link leads.

You see, a fellow Git champion sent me this article some time ago now and we had an in-depth discussion about the exact nature of its manifold offences against common sense and practicality. When CCed on my reply to the developer this evening, he expressed (at first) the kind of sentiments usually identified with pitchfork-wielding mobs, but then directed me to this rebuttal piece.

I agree with everything there up to the title “Anti-Gitflow”: it does make the project’s history completely unreadable, the master/develop split is redundant, and it is needlessly complex. Let me expand on a few items I think were missed.

Used for git, designed for Subversion

Gitflow claims to take advantage of Git’s cheap branching, and this is true in one sense but not in another. “Cheap branching” covers the ease of both creating branches and switching between them, and in the latter sense, Gitflow assumes that there’s little day-to-day cost in maintaining a collection of immortal and long-lived branches that staff routinely switch between. For exactly the same reason, though, it fails to take full advantage of Git in the former sense.

In other words, the kind of “branch museum” Gitflow promotes reflects a mentality dating from the days when creating and deleting branches took significant time; with Git, there’s simply no need for this array of parallel histories stretching vertiginously into the past.

A one-track mind

This segues neatly into where I part ways with the author of “considered harmful”: linear history. Let me admit right now that as a longtime Subversion user, I clung to a linear-history workflow for a long time even after switching to Git. I believed that it made for a much clearer history, and encouraged proper cleanup – squashing changes into units of work and rewriting commit messages – before rebasing onto master and fast-forwarding. Two things changed my mind.

The first was having it pointed out to me that there’s potential value in being able to follow the train of thought involved in the development of a feature by perusing its branch history, but that rebasing makes it difficult to group related commits into a big-picture view of the product’s evolution. Branching a feature from master and merging back in balances these concerns by putting a conceptual “box” around the feature while maintaining its history.

The second was the discovery of git rebase’s --preserve-merges switch, which removed my fear that using branches would pre-emptively sabotage any future tidying.

The rebase/merge spectrum

At one end of the rebase/merge spectrum, you have the author of the anti-Gitflow rebuttal, in whose eyes merge commits are to be used sparingly if at all, and linear history is the best kind. I used to be one of these people.

At the other end, you have software organisations steeped in a long history of inadequate tooling. I think you know the kind I mean; it’s the sort of place where the answer to every question about how to build or deploy product X is read the Word document, stupid. It’s the sort of place where effective management of software complexity is such a distant memory that asking how a piece of code is meant to work yields only one answer — step through it with a debugger — delivered with a faintly bewildered tone, as if one must be stupid or naïve to think that code could be understood any other way.

The clues that Gitflow evolved in this type of organisation are there to be found if you read carefully; as already mentioned, the assumption that more than one branch should be immortal is in itself a legacy of poorer times, but there are other signs.

Specifically, that releases are done from their own branches instead of from master is a big red flag; it tells me that this organisation suffers from a release process so ritual-bound, time-consuming, and lacking in sensible automation that putting pushes to master on hold while releasing would have an unacceptable impact on developer productivity. It also implies that rebasing the work-in-progress branch on the fixup commits required for a particular release is not an option. Why not? Does no-one in the development team know how to use git pull --rebase?

Despite the vast superiority of modern version-control systems to what came before, organisations like this tend still to regard it as a “big save button in the sky” — as a former coworker of long experience once put it to me, to my mute horror — for the simple reason that making constructive use of a project’s preserved history has, until quite recently, been impractical.

This is the history tree of such an organisation; they might be branching and merging like there’s no tomorrow, but I wouldn’t know where to start with this mess, and I’ve done some pretty advanced things with Git. Although familiarity probably helps to some degree, I have to assume that the team can’t really make much more sense of it than I can, and that a great deal of mental effort and perhaps actual paperwork is required to track all of this well enough to get the job done. I’d be very surprised if this history were ever seriously used for fault analysis beyond allowing the developer to track down the tag for a broken release.

In short, organisations like this believe that rebases are to be used sparingly if at all.

Faith in the middle

I now think that both of these viewpoints are too extreme and that rebase and merge should be used in harmony. In my personal work, I use what one friend calls a fast service/slow service model (named for the scheduling habits of commuter trains): single units of work go directly onto master, and larger features are branched and merged after appropriate tidying.

The result is mostly a neat series of loops separated by linear sections, and it seems to me to be a good point on the rebase/merge spectrum. In my paid work, I embellish this a bit: while one or two of the branches we use are technically long-lived, they behave more like multiple short-lived branches of the same name because we forcibly move them to new commits whenever necessary.

The nightmare of Gitflow’s spaghetti history is thus avoided, and any time we want to preserve a sequence of events in a clear way, we can merge and/or rebase branches to produce something that’s semantically useful rather than merely a slavish record of the eternal battle between human ingenuity and human frailty.

See here for more Gitflow critique.