2

I have a git repository with one branch that I would like fast forward merging disabled for all users.

I can run the following command: git config branch.mybranch.mergeoptions "--no-ff" but that only applies to me and not any other developers.

Is there a way to set that branch option in some file that is committed to the repository so that it will apply to all developers?

Liam
  • 27,717
  • 28
  • 128
  • 190
Barry
  • 23
  • 2
  • How are you syncing your git repositories? – Liam Nov 18 '19 at 13:13
  • @Liam Most developers accessing the repo will be either using git command line, tortoisegit or visual studio, but that is not guaranteed – Barry Nov 18 '19 at 13:25
  • You're misunderstanding my question. How are you synching these, not how do you use the repo? Do you have a master repo, Github, something else? – Liam Nov 18 '19 at 13:29
  • Sorry, it is a private repo on bitbucket – Barry Nov 18 '19 at 13:32
  • Does this answer your question? [Git: Prevent fast-forward merges only when merging external branches into master](https://stackoverflow.com/questions/12829398/git-prevent-fast-forward-merges-only-when-merging-external-branches-into-master) – Liam Nov 18 '19 at 13:59
  • If I'm understanding what you're trying to achieve here; I'd say a better approach to this would be to have a restricted master branch that only one (or more) people have permissions to push to. Then user with additional permissions merges in feature branches. – Liam Nov 18 '19 at 14:01
  • See here for [more info on how to do that](https://stackoverflow.com/questions/38864405/how-to-restrict-access-to-master-branch-on-git) – Liam Nov 18 '19 at 14:04
  • @Liam, thanks but I don't think either option in that link would be suitable. The reason behind this is to have a pre-release branch that developers merge into their own branch for testing and it's good they merge their branch back into the pre-release branch - ideally without fast forward - ready for the next branch to be tested. It is required that this should be kept separate to master which is more restricted and required pull request approval to merge. – Barry Nov 18 '19 at 14:22
  • You cannot control *my* repositories. I can clone yours (well, I can if I have access to them) and after that my clones are *mine*, to do with as I will. You can only control *your* repositories. The tools for controlling your own repositories are a bit crude; see [Mark Adelsberger's answer](https://stackoverflow.com/a/58916651/1256452). – torek Nov 18 '19 at 14:40
  • Of course you can do what you like with your repository, but if I can add a .gitignore and a .gitattributes file to a branch to specify commit behaviour it seems reasonable to me that I might be able to add a file to specify merge behaviour – Barry Nov 18 '19 at 17:47

1 Answers1

3

There's not a perfect way to do it. You can use hooks (described below) to push devs in the right direction; if you expect that devs will go along with the no-ff policy but just want to help them avoid pushing mistakes, that should be fine. But if you need truly strict enforcement then you may just have to use access controls that limit who can push to the branch.

In general to enforce a policy about how a branch moves you would use hooks. Information about using hooks with bitbucket can be found here: https://confluence.atlassian.com/bitbucketserver/using-repository-hooks-776639836.html

So the way that would work is, the developer might locally be able to do a fast-forward; but you would write a hook that recognized and rejected the fast-forward to that branch when they try to push it to the remote. You let your developers know you're doing this, and then any sensible developer will apply the --no-ff configuration locally so that they won't accidentally make local commits that they have to waste time undoing. You can even put a script in the repo that updates the local configuration to make that easy. (But you can't enforce that it must be run locally - which is why you depend on the hook.)

However, at the time of a push, it's not generally possible to tell if a fast-forward merge occurred[1]. The best you can do is reject pushes where it seems likely that a fast-forward occurred, and accept those where it seems likely that no fast-forward occurred.

So there are two things you could look for. Probably the most "bang for your buck" is to require that all commits on the branch (traversing first parents only) be a merge commit. So your hook could run something like git rev-list --first-parent --no-merges on the range of commits from the old ref to the proposed updated ref. If this produced any output, then the push is rejected.

In addition to rejecting fast-forward merges, this would also reject commits made directly to the branch. That may be what you want, though if an exception case arises it may be a hassle to deal with. Regardless, commits made directly to the branch are generally indistinguishable from what you typically get from fast-forward merges.

Technically it would still be possible for someone to do something like this

x -- x -- o <--(target_branch)
          |\
          | ----- M <-(other_branch)
          \      /
           A -- B 

and then fast-forward target_branch onto other_branch without triggering the hook.

Another thing you could do is to have the hook reject the push if the first-parent commits of the target branch would include any commit that was ever pushed to a different branch in the remote. On its own this wouldn't prevent someone creating a local branch and fast-forwarding onto it without ever having pushed it. It could be used as an extra check along with the first "merges only" check. But it would only catch the occasional edge case, and it would be more complicated to write and more expensive to run - so it's less likely to be worthwhile.


[1] Technically from the remote's perspective the way a branch moves on push is normally expected to be a fast-forward. It may be a fast-forward onto a merge that was created in the local repo, but to the remote itself that still "looks like" a fast forward, except in the case of force pushing.

Mark Adelsberger
  • 42,148
  • 4
  • 35
  • 52