config: prefer core.compression=5
[girocco.git] / hooks / pre-receive
blob4c19a174f6fb3d0bd4eea905612d0b47a01743d9
1 #!/bin/sh
3 # Keep track of the last time we modified the object store
5 # Beware, we MAY be running in a chroot!
7 set -e
9 var_xargs_r=@var_xargs_r@
10 umask 002
12 if [ -x @perlbin@ ]; then
13 # We are NOT inside the chroot
14 basedir=@basedir@
15 list_packs() { command "$basedir/bin/list_packs" "$@"; }
18 # Some platforms' broken xargs runs the command always at least once even if
19 # there's no input unless given a special option. Automatically supply the
20 # option on those platforms by providing an xargs function.
21 xargs() { command xargs $var_xargs_r "$@"; }
23 git config gitweb.lastreceive "$(date '+%a, %d %b %Y %T %z')"
25 # Read the incoming refs and freshen old loose objects
26 # If we waited until post-receive a gc could have already nuked them
27 # We freshen the new ref in case it's being resurrected to protect it from gc
28 # We probably do not need to do it for new refs as Git tries to do that,
29 # but since we're already doing it for old refs (which Git does not do),
30 # it's almost no extra work for new refs, just in case. We also attempt
31 # to make all packs user and group writable so they can be touched later.
33 # Starting with Git v2.11.0 receive-pack packs/objects end up in a quarantine
34 # object directory that is just discarded immediately if pre-receive declines
35 # to accept the push. This is a good thing. However, it means that the
36 # incoming objects are NOT located in objects/... as GIT_OBJECT_DIRECTORY and
37 # GIT_QUARANTINE_PATH are both set to the quarantine objects directory and the
38 # original objects directory is appended to GIT_ALTERNATE_OBJECT_DIRECTORIES
39 # (but it will just be the absolute path to objects). The simple bottom line
40 # is that we should also try everything in the GIT_QUARANTINE_PATH directory if
41 # it's set.
42 [ -z "$GIT_QUARANTINE_PATH" ] || [ -d "$GIT_QUARANTINE_PATH" ] || unset GIT_QUARANTINE_PATH
44 # We also record changes to a ref log. We do it here rather than in
45 # post-receive so that we can guarantee all potential changes are recorded in
46 # the log before they take place. It's possible that the update hook will
47 # ultimately deny one or more updates but waiting until post-receive could
48 # result in updates being left out of the log.
50 octet='[0-9a-f][0-9a-f]'
51 octet4="$octet$octet$octet$octet"
52 octet20="$octet4$octet4$octet4$octet4$octet4"
53 _make_packs_ugw() {
54 find "$1" -maxdepth 1 -type f \! -perm -ug+w \
55 -name "pack-$octet20.pack" -print0 | \
56 xargs -0 chmod ug+w || :
57 } 2>/dev/null
58 _make_packs_ugw objects/pack
59 [ -z "$GIT_QUARANTINE_PATH" ] || _make_packs_ugw "$GIT_QUARANTINE_PATH/pack"
61 # Trigger a mini-gc if there are at least 20 packs present.
62 # Our current pack that contains this push's data will have a .keep while
63 # this hook is running so we do not use --exclude-keep here under the
64 # assumption that by the time a mini-gc starts the .keep will have been
65 # removed. If not, that's okay too, it just means the current pack will
66 # not take part in the mini-gc and will have to wait for the next one.
67 # The mini-gc code contains the logic to sort out small packs vs. non-small
68 # packs and which should be combined in what order so we do not need to
69 # do any more complicated testing here. We do include any "quarantined" packs
70 # in the count so that any needed gc is not delayed.
71 if ! [ -e .needsgc ]; then
72 packs=
73 { packs="$(list_packs --quiet --count --exclude-no-idx objects/pack || :)" || :; } 2>/dev/null
74 [ -n "$packs" ] || packs=0
75 if [ -n "$GIT_QUARANTINE_PATH" ] && [ -d "$GIT_QUARANTINE_PATH/pack" ]; then
76 { packsq="$(list_packs --quiet --count --exclude-no-idx "$GIT_QUARANTINE_PATH/pack" || :)" || :; } 2>/dev/null
77 [ -n "$packsq" ] || packsq=0
78 packs="$(( $packs + $packsq ))"
80 if [ "$packs" -ge 20 ]; then
81 >.needsgc
85 # Make sure we have a reflogs directory and abort the update if we cannot
86 # create one. Normally project creation will make sure this directory exists.
87 [ -d reflogs ] || mkdir -p reflogs >/dev/null 2>&1 || :
88 [ -d reflogs ]
90 # Multiple push operations could be occurring simultaneously so we need to
91 # guarantee they do not step on one another and we do this by generating a
92 # unique log file name. We use a seconds timestamp and the current process
93 # id and we guarantee that this process is kept alive for the entire second
94 # of the timestamp thereby guaranteeing that we are the only possible process
95 # that could use that pid during that particular second (ignoring leap seconds).
96 # To do this we need to sleep until the second turns over, grab the timestamp
97 # and then sleep until the second turns over again. This will introduce a
98 # guaranteed 2 second delay into every push. This should not generally be
99 # noticeable and does provide a limited throttle on excessive push DOS attacks.
100 # We always use UTC for the timestamp so that chroot and non-chroot match up.
101 # Log entries are the lines sent to the pre-receive hook with hhmmss prepended.
102 sleep 1
103 lognamets="$(TZ=UTC date '+%Y%m%d_%H%M%S')"
104 sleep 1
105 loghhmmss="${lognamets##*_}"
107 # We write to a temp ref log and then move it into place so that the reflogs
108 # collector can assume that log files with their final name are immutable
109 logname="reflogs/$lognamets.$$"
110 lognametmp="reflogs/tmp_$lognamets.$$"
112 while read -r old new ref; do
113 echo "$loghhmmss $old $new $ref" >&3
114 args=
115 if [ "$old" != "0000000000000000000000000000000000000000" ]; then
116 # freshen mod time on recently unref'd loose objects
117 fn="${old#??}"
118 shard="${old%$fn}"
119 args="$args 'objects/$shard/$fn'"
120 [ -z "$GIT_QUARANTINE_DIRECTORY" ] || args="$args '$GIT_QUARANTINE_DIRECTORY/$shard/$fn'"
122 if [ "$new" != "0000000000000000000000000000000000000000" ]; then
123 # prevent imminent pruning of a ref being resurrected
124 fn="${new#??}"
125 shard="${new%$fn}"
126 args="$args 'objects/$shard/$fn'"
127 [ -z "$GIT_QUARANTINE_DIRECTORY" ] || args="$args '$GIT_QUARANTINE_DIRECTORY/$shard/$fn'"
129 eval "chmod ug+w $args" 2>/dev/null || :
130 eval "touch -c $args" 2>/dev/null || :
131 done 3>"$lognametmp"
132 mv "$lognametmp" "$logname"
134 # While unlikely, it is conceivable that several ref updates have occurred that
135 # did not actually push any packs. In that case we could build up a large
136 # number of log files so request a mini gc if there are 50 or more of them now.
137 if ! [ -e .needsgc ]; then
138 logfiles=
139 { logfiles="$(($(find reflogs -maxdepth 1 -type f -print | wc -l || :)+0))" || :; } 2>/dev/null
140 if [ -n "$logfiles" ] && [ "$logfiles" -ge 50 ]; then
141 >.needsgc