summaryrefslogtreecommitdiff
path: root/Documentation/howto/update-hook-example.txt
diff options
context:
space:
mode:
Diffstat (limited to 'Documentation/howto/update-hook-example.txt')
-rw-r--r--Documentation/howto/update-hook-example.txt172
1 files changed, 172 insertions, 0 deletions
diff --git a/Documentation/howto/update-hook-example.txt b/Documentation/howto/update-hook-example.txt
new file mode 100644
index 0000000000..88765b5575
--- /dev/null
+++ b/Documentation/howto/update-hook-example.txt
@@ -0,0 +1,172 @@
+From: Junio C Hamano <junkio@cox.net> and Carl Baldwin <cnb@fc.hp.com>
+Subject: control access to branches.
+Date: Thu, 17 Nov 2005 23:55:32 -0800
+Message-ID: <7vfypumlu3.fsf@assigned-by-dhcp.cox.net>
+Abstract: An example hooks/update script is presented to
+ implement repository maintenance policies, such as who can push
+ into which branch and who can make a tag.
+
+When your developer runs git-push into the repository,
+git-receive-pack is run (either locally or over ssh) as that
+developer, so is hooks/update script. Quoting from the relevant
+section of the documentation:
+
+ Before each ref is updated, if $GIT_DIR/hooks/update file exists
+ and executable, it is called with three parameters:
+
+ $GIT_DIR/hooks/update refname sha1-old sha1-new
+
+ The refname parameter is relative to $GIT_DIR; e.g. for the
+ master head this is "refs/heads/master". Two sha1 are the
+ object names for the refname before and after the update. Note
+ that the hook is called before the refname is updated, so either
+ sha1-old is 0{40} (meaning there is no such ref yet), or it
+ should match what is recorded in refname.
+
+So if your policy is (1) always require fast-forward push
+(i.e. never allow "git-push repo +branch:branch"), (2) you
+have a list of users allowed to update each branch, and (3) you
+do not let tags to be overwritten, then you can use something
+like this as your hooks/update script.
+
+[jc: editorial note. This is a much improved version by Carl
+since I posted the original outline]
+
+-- >8 -- beginning of script -- >8 --
+
+#!/bin/bash
+
+umask 002
+
+# If you are having trouble with this access control hook script
+# you can try setting this to true. It will tell you exactly
+# why a user is being allowed/denied access.
+
+verbose=false
+
+# Default shell globbing messes things up downstream
+GLOBIGNORE=*
+
+function grant {
+ $verbose && echo >&2 "-Grant- $1"
+ echo grant
+ exit 0
+}
+
+function deny {
+ $verbose && echo >&2 "-Deny- $1"
+ echo deny
+ exit 1
+}
+
+function info {
+ $verbose && echo >&2 "-Info- $1"
+}
+
+# Implement generic branch and tag policies.
+# - Tags should not be updated once created.
+# - Branches should only be fast-forwarded.
+case "$1" in
+ refs/tags/*)
+ [ -f "$GIT_DIR/$1" ] &&
+ deny >/dev/null "You can't overwrite an existing tag"
+ ;;
+ refs/heads/*)
+ # No rebasing or rewinding
+ if expr "$2" : '0*$' >/dev/null; then
+ info "The branch '$1' is new..."
+ else
+ # updating -- make sure it is a fast forward
+ mb=$(git-merge-base "$2" "$3")
+ case "$mb,$2" in
+ "$2,$mb") info "Update is fast-forward" ;;
+ *) deny >/dev/null "This is not a fast-forward update." ;;
+ esac
+ fi
+ ;;
+ *)
+ deny >/dev/null \
+ "Branch is not under refs/heads or refs/tags. What are you trying to do?"
+ ;;
+esac
+
+# Implement per-branch controls based on username
+allowed_users_file=$GIT_DIR/info/allowed-users
+username=$(id -u -n)
+info "The user is: '$username'"
+
+if [ -f "$allowed_users_file" ]; then
+ rc=$(cat $allowed_users_file | grep -v '^#' | grep -v '^$' |
+ while read head_pattern user_patterns; do
+ matchlen=$(expr "$1" : "$head_pattern")
+ if [ "$matchlen" == "${#1}" ]; then
+ info "Found matching head pattern: '$head_pattern'"
+ for user_pattern in $user_patterns; do
+ info "Checking user: '$username' against pattern: '$user_pattern'"
+ matchlen=$(expr "$username" : "$user_pattern")
+ if [ "$matchlen" == "${#username}" ]; then
+ grant "Allowing user: '$username' with pattern: '$user_pattern'"
+ fi
+ done
+ deny "The user is not in the access list for this branch"
+ fi
+ done
+ )
+ case "$rc" in
+ grant) grant >/dev/null "Granting access based on $allowed_users_file" ;;
+ deny) deny >/dev/null "Denying access based on $allowed_users_file" ;;
+ *) ;;
+ esac
+fi
+
+allowed_groups_file=$GIT_DIR/info/allowed-groups
+groups=$(id -G -n)
+info "The user belongs to the following groups:"
+info "'$groups'"
+
+if [ -f "$allowed_groups_file" ]; then
+ rc=$(cat $allowed_groups_file | grep -v '^#' | grep -v '^$' |
+ while read head_pattern group_patterns; do
+ matchlen=$(expr "$1" : "$head_pattern")
+ if [ "$matchlen" == "${#1}" ]; then
+ info "Found matching head pattern: '$head_pattern'"
+ for group_pattern in $group_patterns; do
+ for groupname in $groups; do
+ info "Checking group: '$groupname' against pattern: '$group_pattern'"
+ matchlen=$(expr "$groupname" : "$group_pattern")
+ if [ "$matchlen" == "${#groupname}" ]; then
+ grant "Allowing group: '$groupname' with pattern: '$group_pattern'"
+ fi
+ done
+ done
+ deny "None of the user's groups are in the access list for this branch"
+ fi
+ done
+ )
+ case "$rc" in
+ grant) grant >/dev/null "Granting access based on $allowed_groups_file" ;;
+ deny) deny >/dev/null "Denying access based on $allowed_groups_file" ;;
+ *) ;;
+ esac
+fi
+
+deny >/dev/null "There are no more rules to check. Denying access"
+
+-- >8 -- end of script -- >8 --
+
+This uses two files, $GIT_DIR/info/allowed-users and
+allowed-groups, to describe which heads can be pushed into by
+whom. The format of each file would look like this:
+
+ refs/heads/master junio
+ refs/heads/cogito$ pasky
+ refs/heads/bw/.* linus
+ refs/heads/tmp/.* .*
+ refs/tags/v[0-9].* junio
+
+With this, Linus can push or create "bw/penguin" or "bw/zebra"
+or "bw/panda" branches, Pasky can do only "cogito", and JC can
+do master branch and make versioned tags. And anybody can do
+tmp/blah branches.
+
+------------