If I try to remove a branch from an Azure DevOps GIT repository, the Force Push permission is required, according to the error message that is shown:
However, allowing "force pushes" also allows rewriting history. This is even explicitly stated at the place these rights are configured.
In my understanding of Git, branches are nothing more than bookmarks/shortcuts to changes. In a way, they're a special kind of tag. If these are discarded at during a merge, no force push right is required.
I'd like to authorize people to be able to delete branches without giving them the right to mess with history. They shouldn't be able to edit/remove actual change set, but they can do all the want to these special kinds of tags. How do I configure that?
Or am I misunderstanding the amount of history one can rewrite by allowing force pushes? All I'd like preserved is what code change was checked in by whom. Can this history be broken by allowing force pushes?
CodePudding user response:
I'd like to authorize people to be able to delete branches without giving them the right to mess with history.
Based on the available permission options, you can't achieve exactly what you're asking, however, it turns out that there's probably a better way anyway. If tl;dr applies then skip to the bottom section: "Where you probably want to land".
Background, Q and A style:
Question: What is a "force push"?
We usually say "a force push rewrites history." Another way to think about this which may be more useful in the context of this question is: A "force push" is required anytime you wish to remove one or more commits from the branch's reachable history. If you aren't doing that, and the tip of the remote branch is reachable by the new tip commit you are pushing, then a regular push will suffice.
Question: Why are "force push" and "delete branches" lumped together into the same permission?
Consider a branch that reaches 3 commits: A-B-C
Add one commit: A-B-C-D
= regular push.
Remove one commit: A-B
= force push.
Replace one commit completely (i.e. remove one and add one commit): A-B-X
= force push.
Modify one commit slightly (i.e. remove one and add one commit): A-B-C'
= force push.
Remove all commits (i.e. delete the branch) = force push.
If we define a "force push" as "removing one or more commits from a branch", then deleting a branch is conceptually a subset of that (removing all commits), and there is a special syntax for deleting a branch: git push -d origin my-branch
or git push origin :my-branch
.
Now let's suppose we did what you asked: We could define "force push" as "remove one or more commits, but not all commits", and then separate the permissions such that you enable "delete a branch" but do not allow "force push". As pointed out in Romain Valeri's comment, there's nothing to stop someone from deleting a branch, and then re-using that branch name to push out a new commit- effectively achieving a "force push" anyway. That being said, if you added a third permission for "Create a new branch", then perhaps your desire would work. However, allowing people to delete a branch but not create a branch would likely cause more pain than it solves in most workflows. (This doesn't mean no one would find value in it though...)
Question: When is it OK to use "force push"?
This is highly dependent on your situation and preferences. Some people believe the answer is "never", though that strict dogma is probably in the minority view. The majority position is to disallow force pushing shared branches, and allowing force pushing otherwise. In general, and especially in private repos, it's pretty common for people to have their own personal branches that they are working on, and therefore regularly rebasing those branches against a shared target is encouraged, so force pushing personal branches would be considered a normal and frequent occurrence. In a private AzDO repo it therefore makes sense to allow both deleting and force pushing a branch, as long as it's a personal branch.
Where you probably want to land in Azure DevOps:
For all users that will contribute to any AzDO Git repos, by default, set:
Allow for "Contribute" and "Force Push".
Not set for everything else.
Note these settings will apply to most branches.
For specific protected branches, use branch policies. This will require a Pull Request (with many configurable options) in order to merge into protected branches, and effectively also prevents anyone from force pushing or deleting these branches.
For protected branches, if needed, you can optionally add additional security by turning off inheritance, and then setting explicit security if required. In that case you could add "Contribute" for the set of users that can complete PRs into the branch, but (probably) don't include "Force Push" this time1. Additional permission examples might include:
- You may have a specific set of automated build users that are allowed to "Bypass polices when pushing" so they can push commits without a Pull Request.
- Perhaps a group of people that are allowed to "Bypass policies when completing Pull Request" if they need to bypass a gated checkin, or use a certain type of merge that is not allowed by the branch policy.
- A set of Admins that are given "Manage Permissions", so in an emergency scenario where a force push is required, they can temporarily give someone the ability to "Force Push" and "Bypass polices when pushing", and then remove those 2 permissions right after the force push is done.
1 Note that once branch policies are enabled, in order to force push you must have "Allow" set for two permissions: "Force Push" and "Bypass polices when pushing". This means that it's OK for any user to have one or the other, as long as they don't have both. This is why you "probably" want to remove "force push" permission from everyone on protected branches, for sanity purposes, but it may not be actually necessary to do so.