diff --relative: output paths as relative to the current subdirectory
[gitweb.git] / Documentation / howto / update-hook-example.txt
index dacaf17c2efe3044d6f7cc0b0ff616c6cb21a255..88765b55754488223cfe492a83afd1aed5380e61 100644 (file)
@@ -1,4 +1,4 @@
-From: Junio C Hamano <junkio@cox.net>
+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>
@@ -26,80 +26,147 @@ section of the documentation:
 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:
-
-       #!/bin/sh
-       # This is a sample hooks/update script, written by JC
-        # in his e-mail buffer, so naturally it is not tested
-        # but hopefully would convey the idea.
-
-       umask 002
-        case "$1" in
-        refs/tags/*)
-               # No overwriting an existing tag
-               if test -f "$GIT_DIR/$1"
-                then
-                       exit 1
-               fi
-               ;;
-       refs/heads/*)
-               # No rebasing or rewinding
-                if expr "$2" : '0*$' >/dev/null
-                then
-                       # creating a new branch
-                       ;
-               else
-                       # updating -- make sure it is a fast forward
-                       mb=`git-merge-base "$2" "$3"`
-                       case "$mb,$2" in
-                        "$2,$mb")
-                               ;; # fast forward -- happy
-                       *)
-                               exit 1 ;; # unhappy
-                       esac
-               fi
-               ;;
-       *)
-               # No funny refs allowed
-               exit 1
-               ;;
-       esac
-
-       # Is the user allowed to update it?
-       me=`id -u -n` ;# e.g. "junio"
-       while read head_pattern users
-        do
-               if expr "$1" : "$head_pattern" >/dev/null
-               then
-                       case " $users " in
-                       *" $me "*)
-                               exit 0 ;; # happy
-                       ' * ')
-                               exit 0 ;; # anybody
-                       esac
-               fi
-       done
-       exit 1
-
-For the sake of simplicity, I assumed that you keep something
-like this in $GIT_DIR/info/allowed-pushers file:
-
-       refs/heads/master       junio
+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
+        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 I can do
-master branch and make versioned tags.  And anybody can do
-tmp/blah branches.  This assumes all the users are in a single
-group that can write into $GIT_DIR/ and underneath.
-
-
-
-
-
-
-
+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.
 
+------------