]> git.ipfire.org Git - thirdparty/git.git/blame - Documentation/howto/update-hook-example.txt
Merge branch 'cb/httpd-test-fix-port'
[thirdparty/git.git] / Documentation / howto / update-hook-example.txt
CommitLineData
59eb68aa 1From: Junio C Hamano <gitster@pobox.com> and Carl Baldwin <cnb@fc.hp.com>
13cfdfd5
JH
2Subject: control access to branches.
3Date: Thu, 17 Nov 2005 23:55:32 -0800
4Message-ID: <7vfypumlu3.fsf@assigned-by-dhcp.cox.net>
5Abstract: 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.
8
9When your developer runs git-push into the repository,
10git-receive-pack is run (either locally or over ssh) as that
11developer, so is hooks/update script. Quoting from the relevant
12section of the documentation:
13
14 Before each ref is updated, if $GIT_DIR/hooks/update file exists
15 and executable, it is called with three parameters:
16
17 $GIT_DIR/hooks/update refname sha1-old sha1-new
18
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.
25
26So if your policy is (1) always require fast-forward push
27(i.e. never allow "git-push repo +branch:branch"), (2) you
28have a list of users allowed to update each branch, and (3) you
dc5f9239
JH
29do not let tags to be overwritten, then you can use something
30like this as your hooks/update script.
31
32[jc: editorial note. This is a much improved version by Carl
33since I posted the original outline]
34
35-- >8 -- beginning of script -- >8 --
36
37#!/bin/bash
38
39umask 002
40
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.
44
45verbose=false
46
47# Default shell globbing messes things up downstream
48GLOBIGNORE=*
49
50function grant {
51 $verbose && echo >&2 "-Grant- $1"
52 echo grant
53 exit 0
54}
55
56function deny {
57 $verbose && echo >&2 "-Deny- $1"
58 echo deny
59 exit 1
60}
61
62function info {
63 $verbose && echo >&2 "-Info- $1"
64}
65
66# Implement generic branch and tag policies.
67# - Tags should not be updated once created.
f9a08f61 68# - Branches should only be fast-forwarded unless their pattern starts with '+'
dc5f9239
JH
69case "$1" in
70 refs/tags/*)
df79b9fd 71 git rev-parse --verify -q "$1" &&
dc5f9239
JH
72 deny >/dev/null "You can't overwrite an existing tag"
73 ;;
74 refs/heads/*)
75 # No rebasing or rewinding
76 if expr "$2" : '0*$' >/dev/null; then
77 info "The branch '$1' is new..."
78 else
a75d7b54 79 # updating -- make sure it is a fast-forward
dc5f9239
JH
80 mb=$(git-merge-base "$2" "$3")
81 case "$mb,$2" in
82 "$2,$mb") info "Update is fast-forward" ;;
f9a08f61 83 *) noff=y; info "This is not a fast-forward update.";;
dc5f9239
JH
84 esac
85 fi
86 ;;
87 *)
88 deny >/dev/null \
89 "Branch is not under refs/heads or refs/tags. What are you trying to do?"
90 ;;
91esac
92
93# Implement per-branch controls based on username
94allowed_users_file=$GIT_DIR/info/allowed-users
95username=$(id -u -n)
96info "The user is: '$username'"
97
f9a08f61
DP
98if test -f "$allowed_users_file"
99then
dc5f9239 100 rc=$(cat $allowed_users_file | grep -v '^#' | grep -v '^$' |
f9a08f61
DP
101 while read heads user_patterns
102 do
103 # does this rule apply to us?
104 head_pattern=${heads#+}
105 matchlen=$(expr "$1" : "${head_pattern#+}")
106 test "$matchlen" = ${#1} || continue
107
108 # if non-ff, $heads must be with the '+' prefix
109 test -n "$noff" &&
110 test "$head_pattern" = "$heads" && continue
111
112 info "Found matching head pattern: '$head_pattern'"
113 for user_pattern in $user_patterns; do
114 info "Checking user: '$username' against pattern: '$user_pattern'"
115 matchlen=$(expr "$username" : "$user_pattern")
116 if test "$matchlen" = "${#username}"
117 then
118 grant "Allowing user: '$username' with pattern: '$user_pattern'"
119 fi
120 done
121 deny "The user is not in the access list for this branch"
dc5f9239
JH
122 done
123 )
124 case "$rc" in
125 grant) grant >/dev/null "Granting access based on $allowed_users_file" ;;
126 deny) deny >/dev/null "Denying access based on $allowed_users_file" ;;
127 *) ;;
128 esac
129fi
130
131allowed_groups_file=$GIT_DIR/info/allowed-groups
132groups=$(id -G -n)
133info "The user belongs to the following groups:"
134info "'$groups'"
135
f9a08f61
DP
136if test -f "$allowed_groups_file"
137then
dc5f9239 138 rc=$(cat $allowed_groups_file | grep -v '^#' | grep -v '^$' |
f9a08f61
DP
139 while read heads group_patterns
140 do
141 # does this rule apply to us?
142 head_pattern=${heads#+}
143 matchlen=$(expr "$1" : "${head_pattern#+}")
144 test "$matchlen" = ${#1} || continue
145
146 # if non-ff, $heads must be with the '+' prefix
147 test -n "$noff" &&
148 test "$head_pattern" = "$heads" && continue
149
150 info "Found matching head pattern: '$head_pattern'"
151 for group_pattern in $group_patterns; do
152 for groupname in $groups; do
153 info "Checking group: '$groupname' against pattern: '$group_pattern'"
154 matchlen=$(expr "$groupname" : "$group_pattern")
155 if test "$matchlen" = "${#groupname}"
156 then
157 grant "Allowing group: '$groupname' with pattern: '$group_pattern'"
158 fi
dc5f9239 159 done
f9a08f61
DP
160 done
161 deny "None of the user's groups are in the access list for this branch"
dc5f9239
JH
162 done
163 )
164 case "$rc" in
165 grant) grant >/dev/null "Granting access based on $allowed_groups_file" ;;
166 deny) deny >/dev/null "Denying access based on $allowed_groups_file" ;;
167 *) ;;
168 esac
169fi
170
171deny >/dev/null "There are no more rules to check. Denying access"
172
173-- >8 -- end of script -- >8 --
174
175This uses two files, $GIT_DIR/info/allowed-users and
176allowed-groups, to describe which heads can be pushed into by
177whom. The format of each file would look like this:
13cfdfd5 178
8ae67495 179 refs/heads/master junio
f9a08f61 180 +refs/heads/pu junio
13cfdfd5 181 refs/heads/cogito$ pasky
8ae67495
VJ
182 refs/heads/bw/.* linus
183 refs/heads/tmp/.* .*
184 refs/tags/v[0-9].* junio
13cfdfd5
JH
185
186With this, Linus can push or create "bw/penguin" or "bw/zebra"
dc5f9239 187or "bw/panda" branches, Pasky can do only "cogito", and JC can
f9a08f61
DP
188do master and pu branches and make versioned tags. And anybody
189can do tmp/blah branches. The '+' sign at the pu record means
190that JC can make non-fast-forward pushes on it.
13cfdfd5 191
dc5f9239 192------------