1 From: Junio C Hamano <junkio@cox.net> and Carl Baldwin <cnb@fc.hp.com>
2 Subject: control access to branches.
3 Date: Thu, 17 Nov 2005 23:55:32 -0800
4 Message-ID: <7vfypumlu3.fsf@assigned-by-dhcp.cox.net>
5 Abstract: An example hooks/update script is presented to
6 implement repository maintenance policies, such as who can push
7 into which branch and who can make a tag.
9 When your developer runs git-push into the repository,
10 git-receive-pack is run (either locally or over ssh) as that
11 developer, so is hooks/update script. Quoting from the relevant
12 section of the documentation:
14 Before each ref is updated, if $GIT_DIR/hooks/update file exists
15 and executable, it is called with three parameters:
17 $GIT_DIR/hooks/update refname sha1-old sha1-new
19 The refname parameter is relative to $GIT_DIR; e.g. for the
20 master head this is "refs/heads/master". Two sha1 are the
21 object names for the refname before and after the update. Note
22 that the hook is called before the refname is updated, so either
23 sha1-old is 0{40} (meaning there is no such ref yet), or it
24 should match what is recorded in refname.
26 So if your policy is (1) always require fast-forward push
27 (i.e. never allow "git-push repo +branch:branch"), (2) you
28 have a list of users allowed to update each branch, and (3) you
29 do not let tags to be overwritten, then you can use something
30 like this as your hooks/update script.
32 [jc: editorial note. This is a much improved version by Carl
33 since I posted the original outline]
35 -- >8 -- beginning of script -- >8 --
41 # If you are having trouble with this access control hook script
42 # you can try setting this to true. It will tell you exactly
43 # why a user is being allowed/denied access.
47 # Default shell globbing messes things up downstream
51 $verbose && echo >&2 "-Grant- $1"
57 $verbose && echo >&2 "-Deny- $1"
63 $verbose && echo >&2 "-Info- $1"
66 # Implement generic branch and tag policies.
67 # - Tags should not be updated once created.
68 # - Branches should only be fast-forwarded.
71 [ -f "$GIT_DIR/$1" ] &&
72 deny >/dev/null "You can't overwrite an existing tag"
75 # No rebasing or rewinding
76 if expr "$2" : '0*$' >/dev/null; then
77 info "The branch '$1' is new..."
79 # updating -- make sure it is a fast forward
80 mb=$(git-merge-base "$2" "$3")
82 "$2,$mb") info "Update is fast-forward" ;;
83 *) deny >/dev/null "This is not a fast-forward update." ;;
89 "Branch is not under refs/heads or refs/tags. What are you trying to do?"
93 # Implement per-branch controls based on username
94 allowed_users_file=$GIT_DIR/info/allowed-users
96 info "The user is: '$username'"
98 if [ -f "$allowed_users_file" ]; then
99 rc=$(cat $allowed_users_file | grep -v '^#' | grep -v '^$' |
100 while read head_pattern user_patterns; do
101 matchlen=$(expr "$1" : "$head_pattern")
102 if [ "$matchlen" == "${#1}" ]; then
103 info "Found matching head pattern: '$head_pattern'"
104 for user_pattern in $user_patterns; do
105 info "Checking user: '$username' against pattern: '$user_pattern'"
106 matchlen=$(expr "$username" : "$user_pattern")
107 if [ "$matchlen" == "${#username}" ]; then
108 grant "Allowing user: '$username' with pattern: '$user_pattern'"
111 deny "The user is not in the access list for this branch"
116 grant) grant >/dev/null "Granting access based on $allowed_users_file" ;;
117 deny) deny >/dev/null "Denying access based on $allowed_users_file" ;;
122 allowed_groups_file=$GIT_DIR/info/allowed-groups
124 info "The user belongs to the following groups:"
127 if [ -f "$allowed_groups_file" ]; then
128 rc=$(cat $allowed_groups_file | grep -v '^#' | grep -v '^$' |
129 while read head_pattern group_patterns; do
130 matchlen=$(expr "$1" : "$head_pattern")
131 if [ "$matchlen" == "${#1}" ]; then
132 info "Found matching head pattern: '$head_pattern'"
133 for group_pattern in $group_patterns; do
134 for groupname in $groups; do
135 info "Checking group: '$groupname' against pattern: '$group_pattern'"
136 matchlen=$(expr "$groupname" : "$group_pattern")
137 if [ "$matchlen" == "${#groupname}" ]; then
138 grant "Allowing group: '$groupname' with pattern: '$group_pattern'"
142 deny "None of the user's groups are in the access list for this branch"
147 grant) grant >/dev/null "Granting access based on $allowed_groups_file" ;;
148 deny) deny >/dev/null "Denying access based on $allowed_groups_file" ;;
153 deny >/dev/null "There are no more rules to check. Denying access"
155 -- >8 -- end of script -- >8 --
157 This uses two files, $GIT_DIR/info/allowed-users and
158 allowed-groups, to describe which heads can be pushed into by
159 whom. The format of each file would look like this:
161 refs/heads/master junio
162 refs/heads/cogito$ pasky
163 refs/heads/bw/.* linus
165 refs/tags/v[0-9].* junio
167 With this, Linus can push or create "bw/penguin" or "bw/zebra"
168 or "bw/panda" branches, Pasky can do only "cogito", and JC can
169 do master branch and make versioned tags. And anybody can do