{"thread":{"id":"2570","subject":"[RFC] Using sticky directories to control access to branches.","startedAt":"2005-11-17T17:01:29Z","lastAt":"2005-12-02T09:35:11Z","messageCount":8,"participants":["Carl Baldwin","Junio C Hamano","Andreas Ericsson"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"12123","messageId":"20051117170129.GA14013@hpsvcnb.fc.hp.com","threadId":"2570","inReplyTo":null,"subject":"[RFC] Using sticky directories to control access to branches.","fromName":"Carl Baldwin","fromEmail":"cnb@fc.hp.com","sentAt":"2005-11-17T17:01:29Z","receivedAt":"2005-11-17T17:01:29Z","isPatch":false,"sender":{"key":"cnb@fc.hp.com","avatar":null},"body":"I had a thought this morning.  I wanted to use file permissions to\ncontrol access to push to a particular branch in a repository in order\nto implement some sort of per-branch policy.  This assumes that there is\na repository setup into which multiple users can push.\n\nMy goals were to be able to...\n\na) allow only one user (or possibly one group) access to modify a\n   branch.  Do this on per-branch basis.\nb) Freeze tags so that they *cannot* be accidentally updated by a push.\n\nMy plan to accomplish this was to set the sticky bit (chmod +t) on the\nrefs/heads and refs/tags directories so that users couldn't bypass\nfile-permissions by replacing the file with their own.  Then grant write\npermission to the owner (or possibly a group) to allow updates to that\nbranch.\n\nI started testing this with git v0.99.9i and found that git push\nactually creates a new file and replaces the old branch file with the\nnew one.  The consequence for me is that when I set the sticky bit on\nthe heads directory then all of the branches restrict access solely to\nthe owner of the file since only the owner will be able to replace it.\nThis precludes giving a group write access to the branch.  It also\nprecludes leaving most of the branches open to all users by default.\n\nNow, git was probably designed to do this on purpose because it is the\nsafest way to update a branch in an automic way.  But, I wonder if there\nis another way.  Maybe, git could make a temporary backup of the branch\ndoing something like this:\n\n% cp refs/heads/master{,.$randomstring}\n(on success, go ahead and edit refs/heads/master in place)\n(on success, remove refs/heads/master.$randomstring\n\nSomething like this could still be pretty safe but would allow\nper-branch access restrictions using unix file permissions.\n\nCarl\n\n-- \n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n Carl Baldwin                        Systems VLSI Laboratory\n Hewlett Packard Company\n MS 88                               work: 970 898-1523\n 3404 E. Harmony Rd.                 work: Carl.N.Baldwin@hp.com\n Fort Collins, CO 80525              home: Carl@ecBaldwin.net\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n"},{"id":"12201","messageId":"7vfypumlu3.fsf@assigned-by-dhcp.cox.net","threadId":"2570","inReplyTo":"20051117170129.GA14013@hpsvcnb.fc.hp.com","subject":"Re: [RFC] Using sticky directories to control access to branches.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2005-11-18T07:55:32Z","receivedAt":"2005-11-18T07:55:32Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Carl Baldwin <cnb@fc.hp.com> writes:\n\n> Now, git was probably designed to do this on purpose because it is the\n> safest way to update a branch in an automic way.\n\nYes.  How about using hooks/update?  The documentation for\nreceive-pack suggests the use of it for generating commit\nnotification e-mails, but this is more general mechanism.\n\nWhen your developer runs git-push into the repository,\ngit-receive-pack is run (either locally or over ssh) as that\ndeveloper, so is hooks/update script.  Quoting from the relevant\nsection of the documentation:\n\n    Before each ref is updated, if $GIT_DIR/hooks/update file exists\n    and executable, it is called with three parameters:\n\n           $GIT_DIR/hooks/update refname sha1-old sha1-new\n\n    The refname parameter is relative to $GIT_DIR; e.g. for the\n    master head this is \"refs/heads/master\".  Two sha1 are the\n    object names for the refname before and after the update.  Note\n    that the hook is called before the refname is updated, so either\n    sha1-old is 0{40} (meaning there is no such ref yet), or it\n    should match what is recorded in refname.\n\nSo if your policy is (1) always require fast-forward push\n(i.e. never allow \"git-push repo +branch:branch\"), (2) you\nhave a list of users allowed to update each branch, and (3) you\ndo not let tags to be overwritten, then:\n\n\t#!/bin/sh\n\t# This is a sample hooks/update script, written by JC\n        # in his e-mail buffer, so naturally it is not tested\n        # but hopefully would convey the idea.\n\n\tumask 002\n        case \"$1\" in\n        refs/tags/*)\n\t\t# No overwriting an existing tag\n        \tif test -f \"$GIT_DIR/$1\"\n                then\n                \texit 1\n\t\tfi\n\trefs/heads/*)\n        \t# No rebasing or rewinding\n                if expr \"$2\" : '0*$' >/dev/null\n                then\n                \t# creating a new branch\n\t\t\t;\n\t\telse\n                \t# updating -- make sure it is a fast forward\n        \t\tmb=`git-merge-base \"$2\" \"$3\"`\n\t\t\tcase \"$mb,$2\" in\n                        \"$2,$mb\")\n                        \t;; # fast forward -- happy\n\t\t\t*)\n                        \texit 1 ;; # unhappy\n\t\t\tesac\n\t\tfi\n\tesac\n\n\t# Is he allowed to update it?\n\tme=`id -u -n` ;# e.g. \"junio\"\n\twhile read head_pattern users\n        do\n\t\tif expr \"$1\" : \"$head_pattern\" >/dev/null\n\t\tthen\n\t\t\tcase \" $users \" in\n\t\t\t*\" $me \"*)\n                        \texit 0 ;; # happy\n\t\t\t'*')\n                        \texit 0 ;; # anybody\n\t\t\tesac\n\t\tfi\n\tdone\n\texit 1\n\nFor the sake of simplicity, I assumed that you keep something\nlike this in $GIT_DIR/info/allowed-pushers file:\n\n\trefs/heads/master\tjunio\n        refs/heads/cogito$\tpasky\n\trefs/heads/bw/\t\tlinus\n        refs/heads/tmp/\t\t*\n        refs/tags/v[0-9]*\tjunio\n\nWith , Linus can push or create \"bw/penguin\" or \"bw/zebra\" or\n\"bw/panda\" branches, Pasky can do only \"cogito\", and I can do\nmaster branch and make versioned tags.  And anybody can do\ntmp/blah branches.  This assumes all the users are in a single\ngroup that can write into $GIT_DIR/ and underneath.\n"},{"id":"12453","messageId":"20051121180133.GA28171@hpsvcnb.fc.hp.com","threadId":"2570","inReplyTo":"7vfypumlu3.fsf@assigned-by-dhcp.cox.net","subject":"Re: [RFC] Using sticky directories to control access to branches.","fromName":"Carl Baldwin","fromEmail":"cnb@fc.hp.com","sentAt":"2005-11-21T18:01:33Z","receivedAt":"2005-11-21T18:01:33Z","isPatch":false,"sender":{"key":"cnb@fc.hp.com","avatar":null},"body":"OK,\n\nTo follow-up on this.  Here is a final version of this script that was\nstarted by Junio.  I polished it and made it work the way I want it.\nHopefully, someone on the list will find it useful.\n\nHere is a basic description.  There are two files:\n.git/info/allowed-users\n.git/info/allowed-groups\n\nThe users file is checked, line by line, followed by the groups file.\nThe first line matching the ref to be updated is used.  Each line has\none regular expression to match the ref followed by one or more regular\nexpressions to match user (or group).  Space is the delimeter so the RE\ncannot contain a space.\n\nIf no line matches the ref then access is denied.  For this reason I\ntend to include the following line as the last line of allowed-groups as\nthe default since it will match any ref and any group.\n\n.* .*\n\nHere is the hook script...  Thanks to Junio for getting me started.  I\nrewrote the whole thing using my own style but much of the code is based\non his.\n\n#!/bin/bash\n\numask 002\n\nverbose=no\n\n# Default shell globbing messes things up later\nGLOBIGNORE=*\n\nfunction grant {\n  [ \"yes\" == \"$verbose\" ] && echo >&2 \"-Grant-\t\t$1\"\n  exit 0\n}\n\nfunction deny {\n  [ \"yes\" == \"$verbose\" ] && echo >&2 \"-Deny-\t\t$1\"\n  exit 1\n}\n\nfunction info {\n  [ \"yes\" == \"$verbose\" ] && echo >&2 \"-Info-\t\t$1\"\n}\n\n# Implement generic branch and tag policies.\n# - Tags should not be updated once created.\n# - Branches should only be fast-forwarded.\ncase \"$1\" in\n  refs/tags/*)\n    [ -f \"$GIT_DIR/$1\" ] && deny \"You can't overwrite an existing tag\"\n    ;;\n  refs/heads/*)\n    # No rebasing or rewinding\n    if expr \"$2\" : '0*$' >/dev/null; then\n      info \"The branch '$1' is new...\"\n    else\n      # updating -- make sure it is a fast forward\n      mb=$(git-merge-base \"$2\" \"$3\")\n      case \"$mb,$2\" in\n        \"$2,$mb\") info \"Update is fast-forward\" ;;\n        *)        deny  \"This is not a fast-forward update.\" ;;\n      esac\n    fi\n    ;;\n  *)\n    deny \"Branch is not under refs/heads or refs/tags.  What are you trying to do?\"\n    ;;\nesac\n\n# Implement per-branch controls based on username\nallowed_users_file=$GIT_DIR/info/allowed-users\nusername=$(id -u -n)\ninfo \"The user is: '$username'\"\n\nif [ -f \"$allowed_users_file\" ]; then\n  3<$allowed_users_file\n  while read -u 3 head_pattern user_patterns; do\n    matchlen=$(expr \"$1\" : \"$head_pattern\")\n    if [ \"$matchlen\" == \"${#1}\" ]; then\n      info \"Found matching head pattern: '$head_pattern'\"\n      for user_pattern in $user_patterns; do\n        info \"Checking user: '$username' against pattern: '$user_pattern'\"\n        matchlen=$(expr \"$username\" : \"$user_pattern\")\n        if [ \"$matchlen\" == \"${#username}\" ]; then\n          grant \"Allowing user: '$username' with pattern: '$user_pattern'\"\n        fi\n      done\n      deny \"The user is not in the access list for this branch\"\n    fi\n  done\nfi\n\nallowed_groups_file=$GIT_DIR/info/allowed-groups\ngroups=$(id -G -n)\ninfo \"The user belongs to the following groups:\"\ninfo \"'$groups'\"\n\nif [ -f \"$allowed_groups_file\" ]; then\n  4<$allowed_groups_file\n  while read -u 4 head_pattern group_patterns; do\n    matchlen=$(expr \"$1\" : \"$head_pattern\")\n    if [ \"$matchlen\" == \"${#1}\" ]; then\n      info \"Found matching head pattern: '$head_pattern'\"\n      for group_pattern in $group_patterns; do\n        for groupname in $groups; do\n          info \"Checking group: '$groupname' against pattern: '$group_pattern'\"\n          matchlen=$(expr \"$groupname\" : \"$group_pattern\")\n          if [ \"$matchlen\" == \"${#groupname}\" ]; then\n            grant \"Allowing group: '$groupname' with pattern: '$group_pattern'\"\n          fi\n        done\n      done\n      deny \"None of the user's groups are in the access list for this branch\"\n    fi\n  done\nfi\n\ndeny \"There are no more rules to check.  Denying access\"\n# End script here\n\nCarl\n\n-- \n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n Carl Baldwin                        Systems VLSI Laboratory\n Hewlett Packard Company\n MS 88                               work: 970 898-1523\n 3404 E. Harmony Rd.                 work: Carl.N.Baldwin@hp.com\n Fort Collins, CO 80525              home: Carl@ecBaldwin.net\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n"},{"id":"12460","messageId":"7vacfxsstz.fsf@assigned-by-dhcp.cox.net","threadId":"2570","inReplyTo":"20051121180133.GA28171@hpsvcnb.fc.hp.com","subject":"Re: [RFC] Using sticky directories to control access to branches.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2005-11-21T19:29:12Z","receivedAt":"2005-11-21T19:29:12Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Carl Baldwin <cnb@fc.hp.com> writes:\n\n> To follow-up on this.  Here is a final version of this script that was\n> started by Junio.  I polished it and made it work the way I want it.\n> Hopefully, someone on the list will find it useful.\n\nLooking good.  We might want to have collections of user\ncontributed hooks, maybe a new directory examples/ perhaps?\n"},{"id":"13051","messageId":"20051201154222.GB18993@hpsvcnb.fc.hp.com","threadId":"2570","inReplyTo":"20051121180133.GA28171@hpsvcnb.fc.hp.com","subject":"Re: [RFC] Using sticky directories to control access to branches.","fromName":"Carl Baldwin","fromEmail":"cnb@fc.hp.com","sentAt":"2005-12-01T15:42:22Z","receivedAt":"2005-12-01T15:42:22Z","isPatch":false,"sender":{"key":"cnb@fc.hp.com","avatar":null},"body":"I have tweaked the script a little.  I thought I'd send to the list in\ncase anyone was following this.  If not, then safely ignore this.\n\n#!/bin/bash\n\numask 002\n\n# If you are having trouble with this access control hook script you can try\n# setting this to true.  It will tell you exactly why a user is being\n# allowed/denied access.\nverbose=false\n\n# Default shell globbing messes things up downstream\nGLOBIGNORE=*\n\nfunction grant {\n  $verbose && echo >&2 \"-Grant-\t\t$1\"\n  echo grant\n  exit 0\n}\n\nfunction deny {\n  $verbose && echo >&2 \"-Deny-\t\t$1\"\n  echo deny\n  exit 1\n}\n\nfunction info {\n  $verbose && echo >&2 \"-Info-\t\t$1\"\n}\n\n# Implement generic branch and tag policies.\n# - Tags should not be updated once created.\n# - Branches should only be fast-forwarded.\ncase \"$1\" in\n  refs/tags/*)\n    [ -f \"$GIT_DIR/$1\" ] && deny >/dev/null \"You can't overwrite an existing tag\"\n    ;;\n  refs/heads/*)\n    # No rebasing or rewinding\n    if expr \"$2\" : '0*$' >/dev/null; then\n      info \"The branch '$1' is new...\"\n    else\n      # updating -- make sure it is a fast forward\n      mb=$(git-merge-base \"$2\" \"$3\")\n      case \"$mb,$2\" in\n        \"$2,$mb\") info \"Update is fast-forward\" ;;\n        *)        deny >/dev/null  \"This is not a fast-forward update.\" ;;\n      esac\n    fi\n    ;;\n  *)\n    deny >/dev/null \"Branch is not under refs/heads or refs/tags.  What are you trying to do?\"\n    ;;\nesac\n\n# Implement per-branch controls based on username\nallowed_users_file=$GIT_DIR/info/allowed-users\nusername=$(id -u -n)\ninfo \"The user is: '$username'\"\n\nif [ -f \"$allowed_users_file\" ]; then\n  rc=$(cat $allowed_users_file | grep -v '^#' | grep -v '^$' |\n    while read head_pattern user_patterns; do\n      matchlen=$(expr \"$1\" : \"$head_pattern\")\n      if [ \"$matchlen\" == \"${#1}\" ]; then\n        info \"Found matching head pattern: '$head_pattern'\"\n        for user_pattern in $user_patterns; do\n          info \"Checking user: '$username' against pattern: '$user_pattern'\"\n          matchlen=$(expr \"$username\" : \"$user_pattern\")\n          if [ \"$matchlen\" == \"${#username}\" ]; then\n            grant \"Allowing user: '$username' with pattern: '$user_pattern'\"\n          fi\n        done\n        deny \"The user is not in the access list for this branch\"\n      fi\n    done\n  )\n  case \"$rc\" in\n    grant) grant >/dev/null \"Granting access based on $allowed_users_file\" ;;\n    deny)  deny  >/dev/null \"Denying  access based on $allowed_users_file\" ;;\n    *) ;;\n  esac\nfi\n\nallowed_groups_file=$GIT_DIR/info/allowed-groups\ngroups=$(id -G -n)\ninfo \"The user belongs to the following groups:\"\ninfo \"'$groups'\"\n\nif [ -f \"$allowed_groups_file\" ]; then\n  rc=$(cat $allowed_groups_file | grep -v '^#' | grep -v '^$' |\n    while read head_pattern group_patterns; do\n      matchlen=$(expr \"$1\" : \"$head_pattern\")\n      if [ \"$matchlen\" == \"${#1}\" ]; then\n        info \"Found matching head pattern: '$head_pattern'\"\n        for group_pattern in $group_patterns; do\n          for groupname in $groups; do\n            info \"Checking group: '$groupname' against pattern: '$group_pattern'\"\n            matchlen=$(expr \"$groupname\" : \"$group_pattern\")\n            if [ \"$matchlen\" == \"${#groupname}\" ]; then\n              grant \"Allowing group: '$groupname' with pattern: '$group_pattern'\"\n            fi\n          done\n        done\n        deny \"None of the user's groups are in the access list for this branch\"\n      fi\n    done\n  )\n  case \"$rc\" in\n    grant) grant >/dev/null \"Granting access based on $allowed_groups_file\" ;;\n    deny)  deny  >/dev/null \"Denying  access based on $allowed_groups_file\" ;;\n    *) ;;\n  esac\nfi\n\ndeny >/dev/null \"There are no more rules to check.  Denying access\"\nOn Mon, Nov 21, 2005 at 11:01:33AM -0700, Carl Baldwin wrote:\n> OK,\n> \n> To follow-up on this.  Here is a final version of this script that was\n> started by Junio.  I polished it and made it work the way I want it.\n> Hopefully, someone on the list will find it useful.\n> \n> Here is a basic description.  There are two files:\n> .git/info/allowed-users\n> .git/info/allowed-groups\n> \n> The users file is checked, line by line, followed by the groups file.\n> The first line matching the ref to be updated is used.  Each line has\n> one regular expression to match the ref followed by one or more regular\n> expressions to match user (or group).  Space is the delimeter so the RE\n> cannot contain a space.\n> \n> If no line matches the ref then access is denied.  For this reason I\n> tend to include the following line as the last line of allowed-groups as\n> the default since it will match any ref and any group.\n> \n> .* .*\n> \n> Here is the hook script...  Thanks to Junio for getting me started.  I\n> rewrote the whole thing using my own style but much of the code is based\n> on his.\n> \n> #!/bin/bash\n> \n> umask 002\n> \n> verbose=no\n> \n> # Default shell globbing messes things up later\n> GLOBIGNORE=*\n> \n> function grant {\n>   [ \"yes\" == \"$verbose\" ] && echo >&2 \"-Grant-\t\t$1\"\n>   exit 0\n> }\n> \n> function deny {\n>   [ \"yes\" == \"$verbose\" ] && echo >&2 \"-Deny-\t\t$1\"\n>   exit 1\n> }\n> \n> function info {\n>   [ \"yes\" == \"$verbose\" ] && echo >&2 \"-Info-\t\t$1\"\n> }\n> \n> # Implement generic branch and tag policies.\n> # - Tags should not be updated once created.\n> # - Branches should only be fast-forwarded.\n> case \"$1\" in\n>   refs/tags/*)\n>     [ -f \"$GIT_DIR/$1\" ] && deny \"You can't overwrite an existing tag\"\n>     ;;\n>   refs/heads/*)\n>     # No rebasing or rewinding\n>     if expr \"$2\" : '0*$' >/dev/null; then\n>       info \"The branch '$1' is new...\"\n>     else\n>       # updating -- make sure it is a fast forward\n>       mb=$(git-merge-base \"$2\" \"$3\")\n>       case \"$mb,$2\" in\n>         \"$2,$mb\") info \"Update is fast-forward\" ;;\n>         *)        deny  \"This is not a fast-forward update.\" ;;\n>       esac\n>     fi\n>     ;;\n>   *)\n>     deny \"Branch is not under refs/heads or refs/tags.  What are you trying to do?\"\n>     ;;\n> esac\n> \n> # Implement per-branch controls based on username\n> allowed_users_file=$GIT_DIR/info/allowed-users\n> username=$(id -u -n)\n> info \"The user is: '$username'\"\n> \n> if [ -f \"$allowed_users_file\" ]; then\n>   3<$allowed_users_file\n>   while read -u 3 head_pattern user_patterns; do\n>     matchlen=$(expr \"$1\" : \"$head_pattern\")\n>     if [ \"$matchlen\" == \"${#1}\" ]; then\n>       info \"Found matching head pattern: '$head_pattern'\"\n>       for user_pattern in $user_patterns; do\n>         info \"Checking user: '$username' against pattern: '$user_pattern'\"\n>         matchlen=$(expr \"$username\" : \"$user_pattern\")\n>         if [ \"$matchlen\" == \"${#username}\" ]; then\n>           grant \"Allowing user: '$username' with pattern: '$user_pattern'\"\n>         fi\n>       done\n>       deny \"The user is not in the access list for this branch\"\n>     fi\n>   done\n> fi\n> \n> allowed_groups_file=$GIT_DIR/info/allowed-groups\n> groups=$(id -G -n)\n> info \"The user belongs to the following groups:\"\n> info \"'$groups'\"\n> \n> if [ -f \"$allowed_groups_file\" ]; then\n>   4<$allowed_groups_file\n>   while read -u 4 head_pattern group_patterns; do\n>     matchlen=$(expr \"$1\" : \"$head_pattern\")\n>     if [ \"$matchlen\" == \"${#1}\" ]; then\n>       info \"Found matching head pattern: '$head_pattern'\"\n>       for group_pattern in $group_patterns; do\n>         for groupname in $groups; do\n>           info \"Checking group: '$groupname' against pattern: '$group_pattern'\"\n>           matchlen=$(expr \"$groupname\" : \"$group_pattern\")\n>           if [ \"$matchlen\" == \"${#groupname}\" ]; then\n>             grant \"Allowing group: '$groupname' with pattern: '$group_pattern'\"\n>           fi\n>         done\n>       done\n>       deny \"None of the user's groups are in the access list for this branch\"\n>     fi\n>   done\n> fi\n> \n> deny \"There are no more rules to check.  Denying access\"\n> # End script here\n> \n> Carl\n> \n> -- \n> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n>  Carl Baldwin                        Systems VLSI Laboratory\n>  Hewlett Packard Company\n>  MS 88                               work: 970 898-1523\n>  3404 E. Harmony Rd.                 work: Carl.N.Baldwin@hp.com\n>  Fort Collins, CO 80525              home: Carl@ecBaldwin.net\n> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n> -\n> To unsubscribe from this list: send the line \"unsubscribe git\" in\n> the body of a message to majordomo@vger.kernel.org\n> More majordomo info at  http://vger.kernel.org/majordomo-info.html\n> \n\n-- \n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n Carl Baldwin                        Systems VLSI Laboratory\n Hewlett Packard Company\n MS 88                               work: 970 898-1523\n 3404 E. Harmony Rd.                 work: Carl.N.Baldwin@hp.com\n Fort Collins, CO 80525              home: Carl@ecBaldwin.net\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n"},{"id":"13084","messageId":"7vwtio9u8f.fsf@assigned-by-dhcp.cox.net","threadId":"2570","inReplyTo":"20051201154222.GB18993@hpsvcnb.fc.hp.com","subject":"Re: [RFC] Using sticky directories to control access to branches.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2005-12-02T01:13:20Z","receivedAt":"2005-12-02T01:13:20Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Carl Baldwin <cnb@fc.hp.com> writes:\n\n> I have tweaked the script a little.  I thought I'd send to the list in\n> case anyone was following this.  If not, then safely ignore this.\n\nIt _might_ make sense to have a contrib/examples subdirectory in\ngit.git project and keep a collection of examples like this.\n\nAnybody else interested?\n"},{"id":"13100","messageId":"439013F1.4070600@op5.se","threadId":"2570","inReplyTo":"7vwtio9u8f.fsf@assigned-by-dhcp.cox.net","subject":"Re: [RFC] Using sticky directories to control access to branches.","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2005-12-02T09:29:21Z","receivedAt":"2005-12-02T09:29:21Z","isPatch":false,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"Junio C Hamano wrote:\n> \n> It _might_ make sense to have a contrib/examples subdirectory in\n> git.git project and keep a collection of examples like this.\n> \n> Anybody else interested?\n> \n\nI am. Ten thousand brains usually do a better job than one in thinking \nup cool and nifty stuff. :)\n\n-- \nAndreas Ericsson                   andreas.ericsson@op5.se\nOP5 AB                             www.op5.se\nTel: +46 8-230225                  Fax: +46 8-230231\n"},{"id":"13101","messageId":"7vfypb7sfk.fsf@assigned-by-dhcp.cox.net","threadId":"2570","inReplyTo":"439013F1.4070600@op5.se","subject":"Re: [RFC] Using sticky directories to control access to branches.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2005-12-02T09:35:11Z","receivedAt":"2005-12-02T09:35:11Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Andreas Ericsson <ae@op5.se> writes:\n\n> Junio C Hamano wrote:\n>> It _might_ make sense to have a contrib/examples subdirectory in\n>> git.git project and keep a collection of examples like this.\n>> Anybody else interested?\n>>\n>\n> I am. Ten thousand brains usually do a better job than one in thinking \n> up cool and nifty stuff. :)\n\nGood.  Another place (and probably better one) is\nDocumentation/howto/.\n"}]}