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