Strong Opinion on git Workflow


Strong Opinion

Whether working in trunk-based style or feature-branch style, the main (or trunk, also known as master) branch, along with any release branches if doing that thing, SHOULD NOT have merge-commits.

Reasons

I do not like to see merge commits in the main/trunk branch. I prefer a straight, unblemished history line.

* d589966 -(17 hours ago) (HEAD->main, origin/main,origin/HEAD)
* 5f3c420 -(17 hours ago) Merge branch ‘main’
|\
| * 4650c10 -(17 hours ago)
| * ae51497 -(17 hours ago)
| * b45fa11 -(18 hours ago)
* | d532336 -(17 hours ago)
|/
* 3d7bb50 -(18 hours ago)
* 6851949 -(18 hours ago)
* 28a191b -(19 hours ago)
* 01f6be2 -(19 hours ago)

Merge commits make reviewing the history more difficult for humans and source control tools to understand, explore, and resolve issues. Other than the visual part, they introduce complexities to reverting any issues that come up which cross the merge-commit line.

gitconfig

I have these settings in my global .gitconfig to fail any merges that are not fast-forward. This forces me to resolve those with a proper rebase before merging into main.

[merge]
    ff = only
[pull]
    ff = only

Workflow

The workflow I use to resolve such problems is:

  • Create a branch at the current state git branch wip
  • Reset the main branch upstream head either by resetting it then pulling or fetching then resetting it.
  • Change to the wip branch git checkout wip
  • Rebase wip onto main get rebase main (resolve any merge conflicts the usual way)
  • Change to the main branch git checkout main
  • Merge the wip branch into main (since it will fast forward now) git merge wip
  • Delete the wip branch git branch -d wip

scripts.sh

As with anything that I do more than a couple of times, I have automated this workflow. The scripts below are in the set that I source into my current terminal. When I git pull and it fails because it cannot fast forward (see the .gitconfig settings above), I can just repo_wip_rebase.

The script stops at the rebase step if there are merge conflicts to resolve. I use the normal git mergetool and get rebase --continue to handle those and then repo_wip_merge to finish up.

Note that when there are no merge conflicts, repo_wip_rebase does all the work.

function repo_wip_merge()
{
    git checkout main || return $?
    git merge –ff-only wip || return $?
    git branch -d wip || return $?
}

function repo_wip_rebase()
{
    git branch wip || return $?
    git reset –hard origin/main || return $?
    git checkout wip || return $?
    git rebase main || return $?
    # if there are merge conflicts, it stops here. Resolve them and then do this separately
    repo_wip_merge
}

It is possible to do this as get aliases instead so that the commands might be: git wip_rebase.

I tend not to create git aliases for two reasons. The most important is to keep custom stuff clearly custom. I do not want to get in the habit of using git xx short cuts and erode my memory of the raw git commands. Having scripts with the prefix repo_ makes it clear to my brain that this is a custom thing. The second reason is that having these in a script separate from my home folder that is under source control is easier for me to manage.

Coda

I know there are other options for both of these. This is what works for me.

I hold no strong opinions on how you should automate your work. I only hold that you should automate as much as you can in whatever way works for you.