fixed up several broken URLs (minor but annoying)
[gitolite.git] / contrib / hooks / repo-specific / save-push-signatures
blob664f5aaf214eafc6f70f572a74e2da39b31aa74a
1 #!/bin/sh
3 # ----------------------------------------------------------------------
4 # post-receive hook to adopt push certs into 'refs/push-certs'
6 # Collects the cert blob on push and saves it, then, if a certain number of
7 # signed pushes have been seen, processes all the "saved" blobs in one go,
8 # adding them to the special ref 'refs/push-certs'. This is done in a way
9 # that allows searching for all the certs pertaining to one specific branch
10 # (thanks to Junio Hamano for this idea plus general brainstorming).
12 # The "collection" happens only if $GIT_PUSH_CERT_NONCE_STATUS = OK; again,
13 # thanks to Junio for pointing this out; see [1]
15 # [1]: https://groups.google.com/forum/#!topic/gitolite/7cSrU6JorEY
17 # WARNINGS:
18 # Does not check that GIT_PUSH_CERT_STATUS = "G". If you want to check that
19 # and FAIL the push, you'll have to write a simple pre-receive hook
20 # (post-receive is not the place for that; see 'man githooks').
22 # Gitolite users: failing the hook cannot be done as a VREF because git does
23 # not set those environment variables in the update hook. You'll have to
24 # write a trivial pre-receive hook and add that in.
26 # Relevant gitolite doc links:
27 # repo-specific environment variables
28 # http://gitolite.com/gitolite/dev-notes.html#appendix-1-repo-specific-environment-variables
29 # repo-specific hooks
30 # http://gitolite.com/gitolite/non-core.html#repo-specific-hooks
31 # http://gitolite.com/gitolite/cookbook.html#v36-variation-repo-specific-hooks
33 # Environment:
34 # GIT_PUSH_CERT_NONCE_STATUS should be "OK" (as mentioned above)
36 # GL_OPTIONS_GPC_PENDING (optional; defaults to 1). This is the number of
37 # git push certs that should be waiting in order to trigger the post
38 # processing. You can set it within gitolite like so:
40 # repo foo bar # or maybe just 'repo @all'
41 # option ENV.GPC_PENDING = 5
43 # Setup:
44 # Set up this code as a post-receive hook for whatever repos you need to.
45 # Then arrange to have the environment variable GL_OPTION_GPC_PENDING set to
46 # some number, as shown above. (This is only required if you need it to be
47 # greater than 1.) It could of course be different for different repos.
48 # Also see "Invocation" section below.
50 # Invocation:
51 # Normally via git (see 'man githooks'), once it is setup as a post-receive
52 # hook.
54 # However, if you set the "pending" limit high, and want to periodically
55 # "clean up" pending certs without necessarily waiting for the counter to
56 # trip, do the following (untested):
58 # RB=$(gitolite query-rc GL_REPO_BASE)
59 # for r in $(gitolite list-phy-repos)
60 # do
61 # cd $RB/$repo.git
62 # unset GL_OPTIONS_GPC_PENDING # if it is set higher up
63 # hooks/post-receive post_process
64 # done
66 # That will take care of it.
68 # Using without gitolite:
69 # Just set GL_OPTIONS_GPC_PENDING within the script (maybe read it from git
70 # config). Everything else is independent of gitolite.
72 # ----------------------------------------------------------------------
73 # make it work on BSD also (but NOT YET TESTED on FreeBSD!)
74 uname_s=`uname -s`
75 if [ "$uname_s" = "Linux" ]
76 then
77 _lock() { flock "$@"; }
78 else
79 _lock() { lockf -k "$@"; }
80 # I'm assuming other BSDs also have this; I only have FreeBSD.
83 # ----------------------------------------------------------------------
84 # standard stuff
85 die() { echo "$@" >&2; exit 1; }
86 warn() { echo "$@" >&2; }
88 # ----------------------------------------------------------------------
89 # if there are no arguments, we're running as a "post-receive" hook
90 if [ -z "$1" ]
91 then
92 # ignore if it may be a replay attack
93 [ "$GIT_PUSH_CERT_NONCE_STATUS" = "OK" ] || exit 1
94 # I don't think "exit 1" does anything in a post-receive anyway, so that's
95 # just a symbolic gesture!
97 # note the lock file used
98 _lock .gpc.lock $0 cat_blob
100 # if you want to initiate the post-processing ONLY from outside (for
101 # example via cron), comment out the next line.
102 exec $0 post_process
105 # ----------------------------------------------------------------------
106 # the 'post_process' part; see "Invocation" section in the doc at the top
107 if [ "$1" = "post_process" ]
108 then
109 # this is the same lock file as above
110 _lock .gpc.lock $0 count_and_rotate $$
112 [ -d git-push-certs.$$ ] || exit 0
114 # but this is a different one
115 _lock .gpc.ref.lock $0 update_ref $$
117 exit 0
120 # ----------------------------------------------------------------------
121 # other values for "$1" are internal use only
123 if [ "$1" = "cat_blob" ]
124 then
125 mkdir -p git-push-certs
126 git cat-file blob $GIT_PUSH_CERT > git-push-certs/$GIT_PUSH_CERT
127 echo $GIT_PUSH_CERT >> git-push-certs/.blob.list
130 if [ "$1" = "count_and_rotate" ]
131 then
132 count=$(ls git-push-certs | wc -l)
133 if test $count -ge ${GL_OPTIONS_GPC_PENDING:-1}
134 then
135 # rotate the directory
136 mv git-push-certs git-push-certs.$2
140 if [ "$1" = "update_ref" ]
141 then
142 # use a different index file for all this
143 GIT_INDEX_FILE=push_certs_index; export GIT_INDEX_FILE
145 # prepare the special ref to receive commits
146 # historically this hook put the certs in a ref named refs/push-certs
147 # however, git does *NOT* replicate single-level refs
148 # trying to push them explicitly causes this error:
149 # remote: error: refusing to create funny ref 'refs/push-certs' remotely
150 # https://lore.kernel.org/git/robbat2-20211115T063838-612792475Z@orbis-terrarum.net/
152 # As a good-enough solution, use the namespace of meta/ for the refs.
153 # This is already used in other systems:
154 # - kernel.org refs/meta/cgit
155 # - gerrit refs/meta/config
156 # - GitBlit reflog: refs/meta/gitblit https://www.gitblit.com/administration.html#H12
157 # - cc-utils refs/meta/ci
158 # - JGit refs/meta/push-certs https://www.ibm.com/docs/en/radfws/9.6.1?topic=SSRTLW_9.6.1/org.eclipse.egit.doc/help/JGit/New_and_Noteworthy/4.1/4.1.htm
160 # To migrate from old to new, for each repo:
161 # git update-ref refs/meta/push-certs refs/push-certs
162 PUSH_CERTS_EXTRA_REFS='' PUSH_CERTS='' # These vars will be populated after checks.
163 # others vars are temp
164 _OLD_PUSH_CERTS=refs/push-certs
165 _NEW_PUSH_CERTS=refs/meta/push-certs
166 _OLD_PUSH_CERTS_EXISTS=0
167 _NEW_PUSH_CERTS_EXISTS=0
168 git show-ref --verify --quiet -- "$_OLD_PUSH_CERTS" && _OLD_PUSH_CERTS_EXISTS=1
169 git show-ref --verify --quiet -- "$_NEW_PUSH_CERTS" && _NEW_PUSH_CERTS_EXISTS=1
170 case "${_OLD_PUSH_CERTS_EXISTS}${_NEW_PUSH_CERTS_EXISTS}" in
171 # neither or new only:
172 # let's push to the NEW name only
173 '00'|'01') PUSH_CERTS=$_NEW_PUSH_CERTS ;;
174 # old-only: stick to the same, the migration is opt-in
175 '10') PUSH_CERTS=$_OLD_PUSH_CERTS ;;
176 # Both: Push to the old name, duplicate to the new name
177 '11') PUSH_CERTS=$_OLD_PUSH_CERTS PUSH_CERTS_EXTRA_REFS=$_NEW_PUSH_CERTS ;;
178 esac
179 # cleanup vars
180 unset _OLD_PUSH_CERTS_EXISTS _NEW_PUSH_CERTS_EXISTS _OLD_PUSH_CERTS _NEW_PUSH_CERTS
182 if git rev-parse -q --verify $PUSH_CERTS >/dev/null
183 then
184 git read-tree $PUSH_CERTS
185 else
186 git read-tree --empty
187 T=$(git write-tree)
188 C=$(echo 'start' | git commit-tree $T)
189 for _ref in $PUSH_CERTS $PUSH_CERTS_EXTRA_REFS ; do
190 git update-ref "${_ref}" "${C}"
191 done
194 # for each cert blob...
195 for b in `cat git-push-certs.$2/.blob.list`
197 cf=git-push-certs.$2/$b
199 # it's highly unlikely that the blob got GC-ed already but write it
200 # back anyway, just in case
201 B=$(git hash-object -w $cf)
203 # bit of a sanity check
204 [ "$B" = "$b" ] || warn "this should not happen: $B is not equal to $b"
206 # for each ref described within the cert, update the index
207 for ref in `cat $cf | egrep '^[a-f0-9]+ [a-f0-9]+ refs/' | cut -f3 -d' '`
209 git update-index --add --cacheinfo 100644,$b,$ref
210 # we're using the ref name as a "fake" filename, so people can,
211 # for example, 'git log refs/push-certs -- refs/heads/master', to
212 # see all the push certs pertaining to the master branch. This
213 # idea came from Junio Hamano, the git maintainer (I certainly
214 # don't deal with git plumbing enough to have thought of it!)
215 done
217 T=$(git write-tree)
218 C=$( git commit-tree -p $PUSH_CERTS $T < $cf )
219 for _ref in $PUSH_CERTS $PUSH_CERTS_EXTRA_REFS ; do
220 git update-ref "${_ref}" "${C}"
221 done
223 rm -f $cf
224 done
225 rm -f git-push-certs.$2/.blob.list
226 rmdir git-push-certs.$2