git-commit --amend
[git.git] / Documentation / howto / update-hook-example.txt
blob3a33696f004493ac55124ece8b3088673b553c3c
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 --
37 #!/bin/bash
39 umask 002
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.
45 verbose=false
47 # Default shell globbing messes things up downstream
48 GLOBIGNORE=*
50 function grant {
51   $verbose && echo >&2 "-Grant-         $1"
52   echo grant
53   exit 0
56 function deny {
57   $verbose && echo >&2 "-Deny-          $1"
58   echo deny
59   exit 1
62 function info {
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.
69 case "$1" in
70   refs/tags/*)
71     [ -f "$GIT_DIR/$1" ] &&
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
79       # updating -- make sure it is a fast forward
80       mb=$(git-merge-base "$2" "$3")
81       case "$mb,$2" in
82         "$2,$mb") info "Update is fast-forward" ;;
83         *)        deny >/dev/null  "This is not a fast-forward update." ;;
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     ;;
91 esac
93 # Implement per-branch controls based on username
94 allowed_users_file=$GIT_DIR/info/allowed-users
95 username=$(id -u -n)
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'"
109           fi
110         done
111         deny "The user is not in the access list for this branch"
112       fi
113     done
114   )
115   case "$rc" in
116     grant) grant >/dev/null "Granting access based on $allowed_users_file" ;;
117     deny)  deny  >/dev/null "Denying  access based on $allowed_users_file" ;;
118     *) ;;
119   esac
122 allowed_groups_file=$GIT_DIR/info/allowed-groups
123 groups=$(id -G -n)
124 info "The user belongs to the following groups:"
125 info "'$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'"
139             fi
140           done
141         done
142         deny "None of the user's groups are in the access list for this branch"
143       fi
144     done
145   )
146   case "$rc" in
147     grant) grant >/dev/null "Granting access based on $allowed_groups_file" ;;
148     deny)  deny  >/dev/null "Denying  access based on $allowed_groups_file" ;;
149     *) ;;
150   esac
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
164         refs/heads/tmp/         *
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
170 tmp/blah branches.
172 ------------