Nomenclature alert: I wouldn't recommend calling this a "linear history" because without context that almost always means not having merge commits at all.
What you're trying to achieve is typically called a "semi-linear merge" or "rebase, merge" strategy. This provides feature "bubbles", and those bubbles appear linearly in the log. Note that some people allow skipping the merge commit when the feature branch has exactly one commit, because functionally there is no information loss in the resulting graph. You should decide going in whether you will allow this. (My personal preference is to not allow it for consistency sake. But that means we end up with a lot of tiny one commit + one merge commit bubbles, which doesn't bother me.)
As for how to achieve this, I don't recommend the alias idea as you presented. There are just too many scenarios where you might want to fast-forward merge in your day to day work in your local repo.
Instead, I would first lean towards enforcing the policy on the server side, and if that's not possible then you could try using hooks. For server side implementation, here's an answer which discusses popular tools and their capabilities regarding this strategy. (Currently both Azure DevOps and Bitbucket can enforce this out of the box.) If you're using a tool that doesn't have built in support, consider implementing some sort of gated checkin that validates the source branch is fully up to date with the target before allowing the merge to complete (or the push). There are many ways to do that check, one of which might be (using Bash):
# return the target branch tip commit if it passes, nothing otherwise:
git rev-list <source-branch> | grep $(git rev-parse <target-branch>)
Note regarding this statement:
We all need to commit to the main branch independently of others, so solutions based on limiting access to the main branch or code review are not really helpful.
You can still enforce a Pull Request completion strategy without requiring other people to approve your code. For example in Azure DevOps, you would require a PR to merge a branch into main
, and then you would only allow semi-linear merge. AzDO will rebase it for you automatically (if needed), and then create the merge commit, and you still don't need to involve another person. I'm pretty sure Bitbucket has this same capability out of the box. With GL or GH you would require the MR/PR, but allow self completion, and turn on the gated checkin to only allow you to complete it if it's fully up to date.
If you're merging into main
locally and pushing directly the remote main
, the other mechanism would be using Git hooks. Perhaps the pre-merge
or the pre-push
hooks would work best. Inside of the hook, you could look at the HEAD commit of main
, and make sure that the following are true:
- It is a merge commit.
- That the merge-base of the two parent commits (
@^1
and @^2
) equals the same commit as the first parent (@^1
).
Side Note: It's fine to enforce this strategy when merging feature branches into a shared branch. However, if you use a workflow that has more than one shared branch, you don't want to rebase a shared branch onto another shared branch, so make sure you use just plain old merge when you're doing those merges.
git merge
that enforces a fast-forward situation, but then makes a commit anyway. For the Git developers, such a workflow is just not sensible enough to be supported out of the box. – Iummain
locally and then pushingmain
? – Yongyonifile:///
) repositories – Logging