How can I prevent non-fastforward pushes to selected branch(es) in git?
Asked Answered
B

7

9

I would like to protect my git repository so only non master branches can be overwritten. Is there a way to protect only selected branches?

Breathed answered 16/7, 2012 at 7:26 Comment(0)
J
4

You can use GitEnterprise to setup per-branch permissions (admin) to block non-fastforward pushes using fine-grained access permission.

And git config --system receive.denyNonFastForwards true will simply do the job if you need to block history changing for all branches.

Jenjena answered 16/7, 2012 at 8:8 Comment(9)
denyNonFastForwards is not the 'resolution' that you should use. Set a git hook to deny force pushes...That is the correct way to do this.Uropygium
@Uropygium Denying non-fastforward pushes is actually the way to go on GitHub/BitBucket . No new bicycles invented.Jenjena
If you deny all non fastforwards on every branch then how would you merge your feature branches in?Uropygium
@Eric, rebase local feature branch on top of the remote master and merge it into master. Always fast forward.Jenjena
Even for larger teams? I thought rebase should be used sparingly. Just because they are a physical rewrite of history. You are changing the original commit.Uropygium
@Uropygium Where is any rewrite in this case?Jenjena
Make a commit and track the commit ID. Rebase and then compare the commit id. The commit id is no longer the same. It is a new commit. A rewrite.Uropygium
@Uropygium this happens only in your local repo. If you now merge this rebased branch back into master and push it to the remote repo, it gonna be a safe fast-forward push with crystal clean history.Jenjena
Okay I think I understand the context that the "rewriting of commits is a bad idea" should be applied in. It isn't the blanket coverage of "all re writing of commits is bad". Rather, only rewriting commits that are on the remote. I recognized that before however I was informed that "all rewriting of commits" should be shunned. When they misinterpreted originally why rewriting of commits is bad. Just a different way to read between the lines! Thanks!!Uropygium
F
9

Here's an update hook (copy to hooks/update) that I wrote for my own use. This script by default denies all non-fast-forward updates but allows them for explicitly configured branches. It should be easy enough to invert it so that non-fast-forward updates are allowed for all but the master branch.

#!/bin/sh
#
# A hook script to block non-fast-forward updates for branches that haven't
# been explicitly configured to allow it. Based on update.sample.
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
#
# Config
# ------
# hooks.branch.<name>.allownonfastforward
#   This boolean sets whether non-fast-forward updates will be allowed for
#   branch <name>. By default they won't be.

# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"

# --- Safety check
if [ -z "$GIT_DIR" ]; then
        echo "Don't run this script from the command line." >&2
        echo " (if you want, you could supply GIT_DIR then run" >&2
        echo "  $0 <ref> <oldrev> <newrev>)" >&2
        exit 1
fi

if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
        echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
        exit 1
fi

# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
zero="0000000000000000000000000000000000000000"
if [ "$newrev" = "$zero" ]; then
        newrev_type=delete
else
        newrev_type=$(git cat-file -t $newrev)
fi

case "$refname","$newrev_type" in
        refs/tags/*,commit)
                # un-annotated tag
                ;;
        refs/tags/*,delete)
                # delete tag
                ;;
        refs/tags/*,tag)
                # annotated tag
                ;;
        refs/heads/*,commit)
                # branch
                # git rev-list doesn't print anything on fast-forward updates
                if test $(git rev-list "$newrev".."$oldrev"); then
                        branch=${refname##refs/heads/}
                        nonfastforwardallowed=$(git config --bool hooks.branch."$branch".allownonfastforward)

                        if [ "$nonfastforwardallowed" != "true" ]; then
                                echo "hooks/update: Non-fast-forward updates are not allowed for branch $branch"
                                exit 1
                        fi
                fi
                ;;
        refs/heads/*,delete)
                # delete branch
                ;;
        refs/remotes/*,commit)
                # tracking branch
                ;;
        refs/remotes/*,delete)
                # delete tracking branch
                ;;
        *)
                # Anything else (is there anything else?)
                echo "hooks/update: Unknown type of update to ref $refname of type $newrev_type" >&2
                exit 1
                ;;
esac

# --- Finished
exit 0
Finegrained answered 2/4, 2013 at 9:33 Comment(2)
It would be useful if you could amend the script so that non-fast-forward updates are allowed for all but the master branchCochineal
See my post below for a slight modification to this script that allows for wild-carding of allowed branchesHutment
J
4

You can use GitEnterprise to setup per-branch permissions (admin) to block non-fastforward pushes using fine-grained access permission.

And git config --system receive.denyNonFastForwards true will simply do the job if you need to block history changing for all branches.

Jenjena answered 16/7, 2012 at 8:8 Comment(9)
denyNonFastForwards is not the 'resolution' that you should use. Set a git hook to deny force pushes...That is the correct way to do this.Uropygium
@Uropygium Denying non-fastforward pushes is actually the way to go on GitHub/BitBucket . No new bicycles invented.Jenjena
If you deny all non fastforwards on every branch then how would you merge your feature branches in?Uropygium
@Eric, rebase local feature branch on top of the remote master and merge it into master. Always fast forward.Jenjena
Even for larger teams? I thought rebase should be used sparingly. Just because they are a physical rewrite of history. You are changing the original commit.Uropygium
@Uropygium Where is any rewrite in this case?Jenjena
Make a commit and track the commit ID. Rebase and then compare the commit id. The commit id is no longer the same. It is a new commit. A rewrite.Uropygium
@Uropygium this happens only in your local repo. If you now merge this rebased branch back into master and push it to the remote repo, it gonna be a safe fast-forward push with crystal clean history.Jenjena
Okay I think I understand the context that the "rewriting of commits is a bad idea" should be applied in. It isn't the blanket coverage of "all re writing of commits is bad". Rather, only rewriting commits that are on the remote. I recognized that before however I was informed that "all rewriting of commits" should be shunned. When they misinterpreted originally why rewriting of commits is bad. Just a different way to read between the lines! Thanks!!Uropygium
H
1

You can prevent non-fast-forward updates by configuring denyNonFastForwards

git config --system receive.denyNonFastForwards true

But it applies for all branches. For more info please refer ProGit

Hamamelidaceous answered 16/7, 2012 at 7:44 Comment(0)
S
0

This SO answer will give you what you're looking for. Just edit it to apply to the master branch instead:

#!/bin/sh
# lock the master branch for pushing
refname="$1"

if [ "$refname" = "refs/heads/master" ]
then
    echo "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    echo "You cannot push to the master branch."
    echo "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    exit 1
fi
exit 0

Update:
This will prevent all pushes to the master branch, including fast-forward.

Stob answered 16/7, 2012 at 7:42 Comment(2)
This will prevent any pushes to the selected branch. Fast-forward pushes should be allowed.Jenjena
Here, someone figured out how to detect forced-ness in update scripts. Long explanation, final snippet still needs to be done...Gennie
I
0

I think it depends on what you use on server side to access your repository. There are some server applications which support per-branch permissions, like Gerrit or Gitlab (however, i'm not sure if Gitlab supports your usecase). Gerrit supports it, since i use a similiar workflow in my company.

Maybe Gitolite also supports it (that's what Gitlab uses under the hood), which is easier to setup, but doesn't have a webinterface like Gerrit or Gitlab.

Additional comment: GitEnterprise, as suggested, is also a good solution, my suggestions however are suitable, if you have your own server (which is common in many companies).

Inlaid answered 16/7, 2012 at 10:0 Comment(0)
M
0

If you would be allowed to modify your server, then this will enable fast-forwarding on server.

ssh ip 'echo $"[receive]
    denyDeletes = false
    denyNonFastForwards = false" >> /path/to/repo/config'
#then git push -f origin master
Mechling answered 8/4, 2014 at 17:8 Comment(0)
H
0

Here is a modification of the Tanu Kaskinen script to allow for wildcarding of branch names. We use branches with names starting with "d/" to designate "development" branches. I wanted a way to allow non-fast-forward updates for these d/ branches:

   refs/heads/*,commit)
            # branch
            # git rev-list doesn't print anything on fast-forward updates

            if [[ $(git rev-list "$newrev".."$oldrev") ]]; then
                    branch=${refname##refs/heads/}
                    if [[ "$branch" =~ ^d/ ]] ; then
                      echo "Non-fast-forward update allowed on d/ branch"
                      nonfastforwardallowed="true";
                    else
                      #look for a specific config setting
                      nonfastforwardallowed=$(git config --bool  hooks.branch."$branch".allownonfastforward)
                    fi


                    if [ "$nonfastforwardallowed" != "true" ]; then
                            echo "hooks/update: Non-fast-forward updates are not allowed for branch $branch"
                            exit 1
                    fi
            fi
Hutment answered 16/3, 2020 at 20:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.