Sync with v2.0.5
[git] / Documentation / howto / update-hook-example.txt
1 From: Junio C Hamano <gitster@pobox.com> 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.
8 Content-type: text/asciidoc
9
10 How to use the update hook
11 ==========================
12
13 When your developer runs git-push into the repository,
14 git-receive-pack is run (either locally or over ssh) as that
15 developer, so is hooks/update script.  Quoting from the relevant
16 section 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
30 So if your policy is (1) always require fast-forward push
31 (i.e. never allow "git-push repo +branch:branch"), (2) you
32 have a list of users allowed to update each branch, and (3) you
33 do not let tags to be overwritten, then you can use something
34 like this as your hooks/update script.
35
36 [jc: editorial note.  This is a much improved version by Carl
37 since I posted the original outline]
38
39 ----------------------------------------------------
40 #!/bin/bash
41
42 umask 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
48 verbose=false
49
50 # Default shell globbing messes things up downstream
51 GLOBIGNORE=*
52
53 function grant {
54   $verbose && echo >&2 "-Grant-         $1"
55   echo grant
56   exit 0
57 }
58
59 function deny {
60   $verbose && echo >&2 "-Deny-          $1"
61   echo deny
62   exit 1
63 }
64
65 function info {
66   $verbose && echo >&2 "-Info-          $1"
67 }
68
69 # Implement generic branch and tag policies.
70 # - Tags should not be updated once created.
71 # - Branches should only be fast-forwarded unless their pattern starts with '+'
72 case "$1" in
73   refs/tags/*)
74     git rev-parse --verify -q "$1" &&
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
82       # updating -- make sure it is a fast-forward
83       mb=$(git-merge-base "$2" "$3")
84       case "$mb,$2" in
85         "$2,$mb") info "Update is fast-forward" ;;
86         *)        noff=y; info "This is not a fast-forward update.";;
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     ;;
94 esac
95
96 # Implement per-branch controls based on username
97 allowed_users_file=$GIT_DIR/info/allowed-users
98 username=$(id -u -n)
99 info "The user is: '$username'"
100
101 if test -f "$allowed_users_file"
102 then
103   rc=$(cat $allowed_users_file | grep -v '^#' | grep -v '^$' |
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
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
123       done
124       deny "The user is not in the access list for this branch"
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
132 fi
133
134 allowed_groups_file=$GIT_DIR/info/allowed-groups
135 groups=$(id -G -n)
136 info "The user belongs to the following groups:"
137 info "'$groups'"
138
139 if test -f "$allowed_groups_file"
140 then
141   rc=$(cat $allowed_groups_file | grep -v '^#' | grep -v '^$' |
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
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
162         done
163       done
164       deny "None of the user's groups are in the access list for this branch"
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
172 fi
173
174 deny >/dev/null "There are no more rules to check.  Denying access"
175 ----------------------------------------------------
176
177 This uses two files, $GIT_DIR/info/allowed-users and
178 allowed-groups, to describe which heads can be pushed into by
179 whom.  The format of each file would look like this:
180
181     refs/heads/master   junio
182     +refs/heads/pu      junio
183     refs/heads/cogito$  pasky
184     refs/heads/bw/.*    linus
185     refs/heads/tmp/.*   .*
186     refs/tags/v[0-9].*  junio
187
188 With this, Linus can push or create "bw/penguin" or "bw/zebra"
189 or "bw/panda" branches, Pasky can do only "cogito", and JC can
190 do master and pu branches and make versioned tags.  And anybody
191 can do tmp/blah branches. The '+' sign at the pu record means
192 that JC can make non-fast-forward pushes on it.