gc-git-svn: arrange for `git-svn fetch` to run gc-git-svn.sh
[girocco.git] / shlib.sh
blob6dcd4320bb983b62b4ab09f81f896f1b86b62075
1 #!/bin/sh
3 # This is generic shell library for all the scripts used by Girocco;
4 # most importantly, it introduces all the $cfg_* shell variables.
6 # hash patterns
7 hexdig='[0-9a-f]'
8 octet="$hexdig$hexdig"
9 octet4="$octet$octet$octet$octet"
10 octet19="$octet4$octet4$octet4$octet4$octet$octet$octet"
11 octet20="$octet4$octet4$octet4$octet4$octet4"
12 # tab (single \t between single quotes)
13 tab=' '
15 # set a sane umask that never excludes any user or group permissions
16 umask $(printf '0%03o' $(( $(umask) & ~0770 )) )
18 vcmp() {
19 # Compare $1 to $2 each of which must match \d+(\.\d+)*
20 # An empty string ('') for $1 or $2 is treated like 0
21 # Outputs:
22 # -1 if $1 < $2
23 # 0 if $1 = $2
24 # 1 if $1 > $2
25 # Note that `vcmp 1.8 1.8.0.0.0.0` correctly outputs 0.
26 while
27 _a="${1%%.*}"
28 _b="${2%%.*}"
29 [ -n "$_a" ] || [ -n "$_b" ]
31 if [ "${_a:-0}" -lt "${_b:-0}" ]; then
32 echo -1
33 return
34 elif [ "${_a:-0}" -gt "${_b:-0}" ]; then
35 echo 1
36 return
38 _a2="${1#$_a}"
39 _b2="${2#$_b}"
40 set -- "${_a2#.}" "${_b2#.}"
41 done
42 echo 0
45 unset orig_path
46 get_girocco_config_pm_var_list() (
47 # Export all the variables from Girocco::Config to suitable var= lines
48 # prefixing them with 'cfg_'. E.g. $cfg_admin is admin's mail address now
49 # and also setting a 'defined_cfg_' prefix to 1 if they are not undef.
50 __girocco_conf="$GIROCCO_CONF"
51 [ -n "$__girocco_conf" ] || __girocco_conf="Girocco::Config"
52 [ -z "$basedir" ] || __girocco_extrainc="-I$basedir"
53 inc_basedir=@basedir@
54 [ "@basedir@" != '@'basedir'@' ] || inc_basedir="$PWD"
55 [ -z "$orig_path" ] || { PATH="$orig_path" && export PATH; }
56 perl -I"$inc_basedir" $__girocco_extrainc -M$__girocco_conf -le \
57 'foreach (sort {uc($a) cmp uc($b)} keys %Girocco::Config::) {
58 my $val = ${$Girocco::Config::{$_}}; defined($val) or $val="";
59 $val =~ s/([\\"\$\`])/\\$1/gos;
60 $val =~ s/(?:\r\n|\r|\n)$//os;
61 print "cfg_$_=\"$val\"";
62 print "defined_cfg_$_=",
63 (defined(${$Girocco::Config::{$_}})?"1":"");
67 # Returns full command path for "$1" if it's a valid command otherwise returns "$1"
68 _fcp() {
69 if _fp="$(command -v "$1" 2>/dev/null)"; then
70 printf '%s\n' "$_fp"
71 else
72 printf '%s\n' "$1"
76 get_girocco_config_var_list() (
77 # Same as get_girocco_config_pm_var_list except that
78 # the following variables (all starting with var_) are added:
79 # var_group cfg_owning_group if defined otherwise `id -gn`
80 # var_group_gid group id number of $var_group
81 # var_mirror_uid user id number of $cfg_mirror_user
82 # var_cgi_uid user id number of $cfg_cgi_user
83 # var_git_ver The version number part from `git version`
84 # var_git_exec_path The result of $cfg_git_bin --exec-dir
85 # var_sh_bin Full path to the posix sh interpreter to use
86 # var_perl_bin Full path to the perl interpreter to use
87 # var_gzip_bin Full path to the gzip executable to use
88 # var_nc_openbsd_bin Full path to the netcat (nc) with -U support
89 # var_have_git_171 Set to 1 if git version >= 1.7.1 otherwise ''
90 # var_have_git_172 Set to 1 if git version >= 1.7.2 otherwise ''
91 # var_have_git_173 Set to 1 if git version >= 1.7.3 otherwise ''
92 # var_have_git_1710 Set to 1 if git version >= 1.7.10 otherwise ''
93 # var_have_git_185 Set to 1 if git version >= 1.8.5 otherwise ''
94 # var_have_git_210 Set to 1 if git version >= 2.1.0 otherwise ''
95 # var_have_git_235 Set to 1 if git version >= 2.3.5 otherwise ''
96 # var_have_git_260 Set to 1 if git version >= 2.6.0 otherwise ''
97 # var_have_git_2101 Set to 1 if git version >= 2.10.1 otherwise ''
98 # var_window_memory Value to use for repack --window-memory=
99 # var_big_file_threshold Value to use for core.bigFileThreshold
100 # var_redelta_threshold Recompute deltas if no more than this many objs
101 # var_upload_window If not "", pack.window to use for upload-pack
102 # var_log_window_size Value to use for git-svn --log-window-size=
103 # var_utf8_locale Value to use for a UTF-8 locale if available
104 # var_xargs_r A "-r" if xargs needs it to behave correctly
105 # var_du_exclude Option to exclude PATTERN from du if available
106 # var_du_follow Option to follow command line sym links if available
107 _cfg_vars="$(get_girocco_config_pm_var_list)"
108 eval "$_cfg_vars"
109 printf '%s\n' "$_cfg_vars"
110 printf 'var_group=%s\n' "${cfg_owning_group:-$(id -gn)}"
111 perl - "$var_group" "$cfg_mirror_user" "$cfg_cgi_user" <<-'PERLPROG'
112 no warnings;
113 my $gid = getgrnam($ARGV[0]);
114 my $mid = getpwnam($ARGV[1]);
115 my $cid = getpwnam($ARGV[2]);
116 defined($gid) && $gid ne '' and print "var_group_gid=$gid\n";
117 defined($mid) && $mid ne '' and print "var_mirror_uid=$mid\n";
118 defined($cid) && $cid ne '' and print "var_cgi_uid=$cid\n";
119 PERLPROG
120 _gver="$("$cfg_git_bin" version 2>/dev/null |
121 LC_ALL=C sed -ne 's/^[^0-9]*\([0-9][0-9]*\(\.[0-9][0-9]*\)*\).*$/\1/p')"
122 printf 'var_git_ver=%s\n' "$_gver"
123 printf 'var_git_exec_path="%s"\n' "$("$cfg_git_bin" --exec-path 2>/dev/null)"
124 printf 'var_sh_bin="%s"\n' "$(_fcp "${cfg_posix_sh_bin:-/bin/sh}")"
125 printf 'var_perl_bin="%s"\n' "$(_fcp "${cfg_perl_bin:-$(unset -f perl; command -v perl)}")"
126 printf 'var_gzip_bin="%s"\n' "$(_fcp "${cfg_gzip_bin:-$(unset -f gzip; command -v gzip)}")"
127 printf 'var_nc_openbsd_bin="%s"\n' "$(_fcp "${cfg_nc_openbsd_bin:-$(unset -f nc; command -v nc)}")"
128 printf 'var_have_git_171=%s\n' "$([ $(vcmp "$_gver" 1.7.1) -ge 0 ] && echo 1)"
129 printf 'var_have_git_172=%s\n' "$([ $(vcmp "$_gver" 1.7.2) -ge 0 ] && echo 1)"
130 printf 'var_have_git_173=%s\n' "$([ $(vcmp "$_gver" 1.7.3) -ge 0 ] && echo 1)"
131 printf 'var_have_git_1710=%s\n' "$([ $(vcmp "$_gver" 1.7.10) -ge 0 ] && echo 1)"
132 printf 'var_have_git_185=%s\n' "$([ $(vcmp "$_gver" 1.8.5) -ge 0 ] && echo 1)"
133 printf 'var_have_git_210=%s\n' "$([ $(vcmp "$_gver" 2.1.0) -ge 0 ] && echo 1)"
134 printf 'var_have_git_235=%s\n' "$([ $(vcmp "$_gver" 2.3.5) -ge 0 ] && echo 1)"
135 printf 'var_have_git_260=%s\n' "$([ $(vcmp "$_gver" 2.6.0) -ge 0 ] && echo 1)"
136 printf 'var_have_git_2101=%s\n' "$([ $(vcmp "$_gver" 2.10.1) -ge 0 ] && echo 1)"
137 __girocco_conf="$GIROCCO_CONF"
138 [ -n "$__girocco_conf" ] || __girocco_conf="Girocco::Config"
139 [ -z "$basedir" ] || __girocco_extrainc="-I$basedir"
140 inc_basedir=@basedir@
141 [ "@basedir@" != '@'basedir'@' ] || inc_basedir="$PWD"
142 printf "var_window_memory=%s\n" \
143 "$(perl -I"$inc_basedir" $__girocco_extrainc -M$__girocco_conf \
144 -MGirocco::Util -e 'print calc_windowmemory')"
145 printf "var_big_file_threshold=%s\n" \
146 "$(perl -I"$inc_basedir" $__girocco_extrainc -M$__girocco_conf \
147 -MGirocco::Util -e 'print calc_bigfilethreshold')"
148 printf "var_redelta_threshold=%s\n" \
149 "$(perl -I"$inc_basedir" $__girocco_extrainc -M$__girocco_conf \
150 -MGirocco::Util -e 'print calc_redeltathreshold')"
151 if [ -n "$cfg_upload_pack_window" ] && [ "$cfg_upload_pack_window" -ge 2 ] &&
152 [ "$cfg_upload_pack_window" -le 50 ]; then
153 printf "var_upload_window=%s\n" "$cfg_upload_pack_window"
154 else
155 printf "var_upload_window=%s\n" ""
157 printf 'var_log_window_size=%s\n' "${cfg_svn_log_window_size:-250}"
158 # We parse the output of `locale -a` and select a suitable UTF-8 locale.
159 _guess_locale="$(locale -a | LC_ALL=C grep -viE '^(posix|c)(\..*)?$' |
160 LC_ALL=C grep -iE '\.utf-?8$' | LC_ALL=C sed -e 's/\.[Uu][Tt][Ff]-*8$//' |
161 LC_ALL=C sed -e '/en_US/ s/^/0 /; /en_US/ !s/^/1 /' | LC_ALL=C sort |
162 head -n 1 | LC_ALL=C cut -d ' ' -f 2)"
163 [ -z "$_guess_locale" ] || printf 'var_utf8_locale=%s.UTF-8\n' "$_guess_locale"
164 # On some broken platforms running xargs without -r and empty input runs the command
165 printf 'var_xargs_r=%s\n' "$(</dev/null command xargs printf %s -r)"
166 # The disk usage report produces better numbers if du has an exclude option
167 _x0="${0##*/}"
168 _x0="${_x0%?}?*"
169 for _duopt in --exclude -I; do
170 if _test="$(du $_duopt 's?lib.s*' $_duopt "$_x0" "$0" 2>/dev/null)" && [ -z "$_test" ]; then
171 printf 'var_du_exclude=%s\n' "$_duopt"
172 break
174 done
175 if _test="$(du -H "$0" 2>/dev/null)" && [ -n "$_test" ]; then
176 printf 'var_du_follow=%s\n' "-H"
177 break
181 # If basedir has been replaced, and shlib_vars.sh exists, get the config
182 # definitions from it rather than running Perl.
183 if [ "@basedir@" = '@'basedir'@' ] || ! [ -r "@basedir@/shlib_vars.sh" ]; then
184 # Import all the variables from Girocco::Config to the local environment,
185 eval "$(get_girocco_config_var_list)"
186 else
187 # Import the variables from shlib_vars.sh which avoids needlessly
188 # running another copy of Perl
189 . "@basedir@/shlib_vars.sh"
192 # git_add_config "some.var=value"
193 # every ' in value must be replaced with the 4-character sequence '\'' before
194 # calling this function or Git will barf. Will not be effective unless running
195 # Git version 1.7.3 or later.
196 git_add_config() {
197 GIT_CONFIG_PARAMETERS="${GIT_CONFIG_PARAMETERS:+$GIT_CONFIG_PARAMETERS }'$1'"
198 export GIT_CONFIG_PARAMETERS
201 # file of empty lines
202 mtlinesfile="$cfg_basedir/mtlinesfile"
203 # created by installer, but if not exists, set to /dev/null
204 [ -e "$mtlinesfile" ] && [ -f "$mtlinesfile" ] && [ -r "$mtlinesfile" ] ||
205 mtlinesfile='/dev/null'
207 # Make sure we have a reproducible environment by using a controlled HOME dir
208 XDG_CONFIG_HOME="$cfg_chroot/var/empty"
209 HOME="$cfg_chroot/etc/girocco"
210 TMPDIR="/tmp"
211 GIT_CONFIG_NOSYSTEM=1
212 GIT_ATTR_NOSYSTEM=1
213 GIT_NO_REPLACE_OBJECTS=1
214 GIT_TERMINAL_PROMPT=0
215 GIT_PAGER="cat"
216 PAGER="cat"
217 GIT_ASKPASS="$cfg_basedir/bin/git-askpass-password"
218 GIT_SVN_NOTTY=1
219 GIROCCO_SUPPRESS_AUTO_GC_UPDATE=1
220 GIT_SSH="$cfg_basedir/bin/git-ssh"
221 SVN_SSH="$cfg_basedir/bin/git-ssh"
222 export XDG_CONFIG_HOME
223 export HOME
224 export TMPDIR
225 export GIT_CONFIG_NOSYSTEM
226 export GIT_ATTR_NOSYSTEM
227 export GIT_NO_REPLACE_OBJECTS
228 export GIT_TERMINAL_PROMPT
229 export GIT_PAGER
230 export PAGER
231 export GIT_ASKPASS
232 export GIT_SVN_NOTTY
233 export GIROCCO_SUPPRESS_AUTO_GC_UPDATE
234 export GIT_SSH
235 export SVN_SSH
236 unset GIT_USER_AGENT
237 unset GIT_HTTP_USER_AGENT
238 if [ -n "$defined_cfg_git_client_ua" ]; then
239 GIT_USER_AGENT="$cfg_git_client_ua"
240 export GIT_USER_AGENT
242 unset GIT_CONFIG_PARAMETERS
243 unset GIROCCO_DIVERT_GIT_SVN_AUTO_GC
246 ## IMPORTANT!
248 ## Keep gitweb/gitweb_config.perl in sync with these git_add_config calls
249 ## Keep bin/git-shell-verify in sync with these git_add_config calls
251 git_add_config "core.ignoreCase=false"
252 git_add_config "core.pager=cat"
253 if [ -n "$cfg_git_no_mmap" ]; then
254 # Just like compiling with NO_MMAP
255 git_add_config "core.packedGitWindowSize=1m"
256 else
257 # Always use the 32-bit default (32m) even on 64-bit to avoid memory blowout
258 git_add_config "core.packedGitWindowSize=32m"
260 # Always use the 32-bit default (256m) even on 64-bit to avoid memory blowout
261 git_add_config "core.packedGitLimit=256m"
262 [ -z "$var_big_file_threshold" ] ||
263 git_add_config "core.bigFileThreshold=$var_big_file_threshold"
264 git_add_config "gc.auto=0"
266 # Make sure any sendmail.pl config is always available
267 unset SENDMAIL_PL_HOST
268 unset SENDMAIL_PL_PORT
269 unset SENDMAIL_PL_NCBIN
270 unset SENDMAIL_PL_NCOPT
271 [ -z "$cfg_sendmail_pl_host" ] || { SENDMAIL_PL_HOST="$cfg_sendmail_pl_host" && export SENDMAIL_PL_HOST; }
272 [ -z "$cfg_sendmail_pl_port" ] || { SENDMAIL_PL_PORT="$cfg_sendmail_pl_port" && export SENDMAIL_PL_PORT; }
273 [ -z "$cfg_sendmail_pl_ncbin" ] || { SENDMAIL_PL_NCBIN="$cfg_sendmail_pl_ncbin" && export SENDMAIL_PL_NCBIN; }
274 [ -z "$cfg_sendmail_pl_ncopt" ] || { SENDMAIL_PL_NCOPT="$cfg_sendmail_pl_ncopt" && export SENDMAIL_PL_NCOPT; }
276 # Set PATH and PYTHON to the values set by Config.pm, if any
277 unset PYTHON
278 [ -z "$cfg_python" ] || { PYTHON="$cfg_python" && export PYTHON; }
279 [ -z "$cfg_path" ] || { orig_path="$PATH" && PATH="$cfg_path" && export PATH; }
281 # Extra GIT variables that generally ought to be cleared, but whose clearing
282 # could potentially interfere with the correct operation of hook scripts so
283 # they are segregated into a separate function for use as appropriate
284 clean_git_env() {
285 unset GIT_ALTERNATE_OBJECT_DIRECTORIES
286 unset GIT_CONFIG
287 unset GIT_DIR
288 unset GIT_GRAFT_FILE
289 unset GIT_INDEX_FILE
290 unset GIT_OBJECT_DIRECTORY
291 unset GIT_NAMESPACE
294 # We cannot use a git() {} or nc_openbsd() {} function to redirect git
295 # and nc_openbsd to the desired executables because when using
296 # "ENV_VAR=xxx func" the various /bin/sh implementations behave in various
297 # different and unexpected ways:
298 # a) treat "ENV_VAR=xxx" like a separate, preceding "export ENV_VAR=xxx"
299 # b) treat "ENV_VAR=xxx" like a separate, prededing "ENV_VAR=xxx"
300 # c) treat "ENV_VAR=xxx" like a temporary setting only while running func
301 # None of these are good. We want a temporary "export ENV_VAR=xxx"
302 # setting only while running func which none of the /bin/sh's do.
304 # Instead we'd like to use an alias that provides the desired behavior without
305 # any of the bad (a), (b) or (c) effects.
307 # However, unfortunately, some of the crazy /bin/sh implementations do not
308 # recognize alias expansions when preceded by variable assignments!
310 # So we are left with git() {} and nc_openbsd() {} functions and in the
311 # case of git() {} we can compensate for (b) and (c) failing to export
312 # but not (a) and (b) persisting the values so the caller will simply
313 # have to beware and explicitly unset any variables that should not persist
314 # beyond the function call itself.
316 git() (
317 [ z"${GIT_DIR+set}" != z"set" ] || export GIT_DIR
318 [ z"${GIT_SSL_NO_VERIFY+set}" != z"set" ] || export GIT_SSL_NO_VERIFY
319 [ z"${GIT_TRACE_PACKET+set}" != z"set" ] || export GIT_TRACE_PACKET
320 [ z"${GIT_USER_AGENT+set}" != z"set" ] || export GIT_USER_AGENT
321 [ z"${GIT_HTTP_USER_AGENT+set}" != z"set" ] || export GIT_HTTP_USER_AGENT
322 exec "$cfg_git_bin" "$@"
325 # Since we do not yet require at least Git 1.8.5 this is a compatibility function
326 # that allows us to use git update-ref --stdin where supported and the slow shell
327 # script where not, but only the "delete" operation is currently supported.
328 git_updateref_stdin() {
329 if [ -n "$var_have_git_185" ]; then
330 git update-ref --stdin
331 else
332 while read -r _op _ref; do
333 case "$_op" in
334 delete)
335 git update-ref -d "$_ref"
338 echo "bad git_updateref_stdin op: $_op" >&2
339 exit 1
341 esac
342 done
346 # see comments for git() -- callers must explicitly export all variables
347 # intended for the commands these functions run before calling them
348 perl() { command "${var_perl_bin:-perl}" "$@"; }
349 gzip() { command "${var_gzip_bin:-gzip}" "$@"; }
351 nc_openbsd() { command "$var_nc_openbsd_bin" "$@"; }
353 list_packs() { command "$cfg_basedir/bin/list_packs" "$@"; }
355 readlink() { command "$cfg_basedir/bin/readlink" "$@"; }
357 strftime() { command "$cfg_basedir/bin/strftime" "$@"; }
359 # Some platforms' broken xargs runs the command always at least once even if
360 # there's no input unless given a special option. Automatically supply the
361 # option on those platforms by providing an xargs function.
362 xargs() { command xargs $var_xargs_r "$@"; }
364 _addrlist() {
365 _list=
366 for _addr in "$@"; do
367 [ -z "$_list" ] || _list="$_list, "
368 _list="$_list$_addr"
369 done
370 echo "$_list"
373 _sendmail() {
374 _mailer="${cfg_sendmail_bin:-/usr/sbin/sendmail}"
375 if [ -n "$cfg_sender" ]; then
376 "$_mailer" -i -f "$cfg_sender" "$@"
377 else
378 "$_mailer" -i "$@"
382 # First argument is an id WITHOUT surrounding '<' and '>' to use in a
383 # "References:" header. It may be "" to suppress the "References" header.
384 # Following arguments are just like mail function
385 mailref() {
386 _references=
387 if [ $# -ge 1 ]; then
388 _references="$1"
389 shift
391 _subject=
392 if [ "$1" = "-s" ]; then
393 shift
394 _subject="$1"
395 shift
398 echo "From: \"$cfg_name\" ($cfg_title) <$cfg_admin>"
399 echo "To: $(_addrlist "$@")"
400 [ -z "$_subject" ] || echo "Subject: $_subject"
401 echo "MIME-Version: 1.0"
402 echo "Content-Type: text/plain; charset=utf-8; format=fixed"
403 echo "Content-Transfer-Encoding: 8bit"
404 [ -z "$_references" ] || echo "References: <$_references>"
405 [ -n "$cfg_suppress_x_girocco" ] || echo "X-Girocco: $cfg_gitweburl"
406 echo "Auto-Submitted: auto-generated"
407 echo ""
409 } | _sendmail "$@"
412 # Usage: mail [-s <subject>] <addr> [<addr>...]
413 mail() {
414 mailref "" "$@"
417 # bang CMD... will execute the command with well-defined failure mode;
418 # set bang_action to string of the failed action ('clone', 'update', ...);
419 # re-define the bang_trap() function to do custom cleanup before bailing out
420 bang() {
421 bang_errcode=
422 bang_catch "$@"
423 [ "${bang_errcode:-0}" = "0" ] || bang_failed
426 bang_catch() {
427 bang_active=1
428 bang_cmd="$*"
429 bang_errcode=0
430 if [ "${show_progress:-0}" != "0" ]; then
431 exec 3>&1
432 read -r bang_errcode <<-EOT || :
434 exec 4>&3 3>&1 1>&4 4>&-
435 { "$@" 3>&- || echo $? >&3; } 2>&1 | tee -i -a "$bang_log"
438 exec 3>&-
439 if [ -z "$bang_errcode" ] || [ "$bang_errcode" = "0" ]; then
440 # All right. Cool.
441 bang_active=
442 bang_cmd=
443 return;
445 else
446 if "$@" >>"$bang_log" 2>&1; then
447 # All right. Cool.
448 bang_active=
449 bang_cmd=
450 return;
451 else
452 bang_errcode="$?"
457 bang_failed() {
458 bang_active=
459 unset GIT_DIR
460 >.banged
461 cat "$bang_log" >.banglog
462 echo "" >>.banglog
463 echo "$bang_cmd failed with error code $bang_errcode" >>.banglog
464 ! [ -d htmlcache ] || { >htmlcache/changed; } 2>/dev/null || :
465 if [ "${show_progress:-0}" != "0" ]; then
466 echo ""
467 echo "$bang_cmd failed with error code $bang_errcode"
469 if [ -e .bangagain ]; then
470 git config --remove-section girocco.bang 2>/dev/null || :
471 rm -f .bangagain
473 bangcount="$(git config --int girocco.bang.count 2>/dev/null)" || :
474 bangcount=$(( ${bangcount:-0} + 1 ))
475 git config --int girocco.bang.count $bangcount
476 if [ $bangcount -eq 1 ]; then
477 git config girocco.bang.firstfail "$(TZ=UTC date "+%Y-%m-%d %T UTC")"
479 if [ $bangcount -ge $cfg_min_mirror_failure_message_count ] &&
480 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" != "true" ] &&
481 ! check_interval "girocco.bang.firstfail" $cfg_min_mirror_failure_message_interval; then
482 bangmailok="$(git config --bool gitweb.statusupdates 2>/dev/null || echo true)"
483 bangaddrs=
484 [ "$bangmailok" = "false" ] || [ -z "$mail" ] || bangaddrs="$mail"
485 [ -z "$cfg_admincc" ] || [ "$cfg_admincc" = "0" ] || [ -z "$cfg_admin" ] ||
486 if [ -z "$bangaddrs" ]; then bangaddrs="$cfg_admin"; else bangaddrs="$bangaddrs,$cfg_admin"; fi
487 rsubj=
488 [ $bangcount -le 1 ] || rsubj=" repeatedly"
489 [ -z "$bangaddrs" ] ||
491 echo "$bang_cmd failed with error code $bang_errcode"
492 echo ""
493 rsubj=
494 if [ $bangcount -gt 1 ]; then
495 echo "$bangcount consecutive update failures have occurred since $(config_get girocco.bang.firstfail)"
496 echo ""
498 echo "you will not receive any more notifications until recovery"
499 echo "this status message may be disabled on the project admin page"
500 echo ""
501 echo "Log follows:"
502 echo ""
503 cat "$bang_log"
504 } | mailref "update@$cfg_gitweburl/$proj.git" -s "[$cfg_name] $proj $bang_action failed$rsubj" "$bangaddrs"
505 git config --bool girocco.bang.messagesent true
507 bangthrottle=
508 [ $bangcount -lt 15 ] ||
509 check_interval "girocco.bang.firstfail" $(( $cfg_min_mirror_interval * 3 / 2 )) ||
510 bangthrottle=1
511 bang_trap $bangthrottle
512 [ -n "$bang_errcode" ] && [ "$bang_errcode" != "0" ] || bang_errcode=1
513 exit $bang_errcode
516 # bang_eval CMD... will evaluate the command with well-defined failure mode;
517 # Identical to bang CMD... except the command is eval'd instead of executed.
518 bang_eval() {
519 bang eval "$*"
522 # Default bang settings:
523 bang_setup() {
524 bang_active=
525 bang_action="lame_programmer"
526 bang_trap() { :; }
527 bang_tmpdir="${TMPDIR:-/tmp}"
528 bang_tmpdir="${bang_tmpdir%/}"
529 bang_log="$(mktemp "${bang_tmpdir:-/tmp}/repomgr-XXXXXX")"
530 is_git_dir . || {
531 echo "bang_setup called with current directory not a git directory" >&2
532 exit 1
534 trap 'rm -f "$bang_log"' EXIT
535 trap '[ -z "$bang_active" ] || { bang_errcode=130; bang_failed; }; exit 130' INT
536 trap '[ -z "$bang_active" ] || { bang_errcode=143; bang_failed; }; exit 143' TERM
539 # Remove banged status
540 bang_reset() {
541 rm -f .banged .bangagain .banglog
542 git config --remove-section girocco.bang 2>/dev/null || :
545 # Check to see if banged status
546 is_banged() {
547 [ -e .banged ]
550 # Check to see if banged message was sent
551 was_banged_message_sent() {
552 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" = "true" ]
555 # Progress report - if show_progress is set, shows the given message.
556 progress() {
557 [ "${show_progress:-0}" = "0" ] || echo "$*"
560 # Project config accessors; must be run in project directory
561 config_get() {
562 case "$1" in
563 *.*)
564 git config "$1";;
566 git config "gitweb.$1";;
567 esac
570 config_set() {
571 git config "gitweb.$1" "$2" && chgrp $var_group config && chmod g+w config
574 config_set_raw() {
575 git config "$1" "$2" && chgrp $var_group config && chmod g+w config
578 config_get_date_seconds() {
579 _dt="$(config_get "$1")" || :
580 [ -n "$_dt" ] || return 1
581 _ds="$(perl -I@basedir@ -MGirocco::Util -e "print parse_any_date('$_dt')")"
582 [ -n "$_ds" ] || return 1
583 echo "$_ds"
586 # Tool for checking whether given number of seconds has not passed yet
587 check_interval() {
588 os="$(config_get_date_seconds "$1")" || return 1
589 ns="$(date +%s)"
590 [ $ns -lt $(($os+$2)) ]
593 # Check if we are running with effective root permissions
594 is_root() {
595 [ "$(id -u 2>/dev/null)" = "0" ]
598 # Check to see if the single argument (default ".") is a Git directory
599 is_git_dir() {
600 # Just like Git's test except we ignore GIT_OBJECT_DIRECTORY
601 # And we are slightly more picky (must be refs/.+ not refs/.*)
602 [ $# -ne 0 ] || set -- "."
603 [ -d "$1/objects" ] && [ -x "$1/objects" ] || return 1
604 [ -d "$1/refs" ] && [ -x "$1/refs" ] || return 1
605 if [ -L "$1/HEAD" ]; then
606 _hr="$(readlink "$1/HEAD")"
607 case "$_hr" in "refs/"?*) :;; *) return 1;; esac
609 [ -f "$1/HEAD" ] && [ -r "$1/HEAD" ] || return 1
610 read -r _hr <"$1/HEAD" || return 1
611 case "$_hr" in
612 $octet20*)
613 [ "${_hr#*[!0-9a-f]}" = "$_hr" ] || return 1
614 return 0;;
615 ref:refs/?*)
616 return 0;;
617 ref:*)
618 _hr="${_hr##ref:*[ $tab]}"
619 case "$_hr" in "refs/"?*) return 0;; esac
620 esac
621 return 1
624 # Check to see if the single argument (default ".") is a directory with no refs
625 is_empty_refs_dir() {
626 [ $# -ne 0 ] || set -- "."
627 if [ -s "$1/packed-refs" ]; then
628 # could be a packed-refs file with just a '# pack-refs ..." line
629 # null hash lines and peel lines do not count either
630 _refcnt="$(( $(LC_ALL=C sed <"$1/packed-refs" \
631 -e "/^00* /d" \
632 -e "/^$octet20$hexdig* refs\/[^ $tab]*\$/!d" | wc -l) ))"
633 [ "${_refcnt:-0}" -eq 0 ] || return 1
635 if [ -d "$1/refs" ]; then
636 # quick and dirty check, doesn't try to validate contents
637 # or ignore embedded symbolic refs
638 _refcnt="$(( $(find -L "$1/refs" -type f -print 2>/dev/null | head -n 1 | LC_ALL=C wc -l) ))"
639 [ "${_refcnt:-0}" -eq 0 ] || return 1
641 # last chance a detached HEAD (we ignore any linked working trees though)
642 [ -s "$1/HEAD" ] && read -r _hr <"$1/HEAD" && [ -n "$_hr" ] || return 0
643 [ "${_hr#*[!0-9a-f]}" != "$_hr" ] || [ "${_hr#*[!0]}" = "$_hr" ] || [ "${#_hr}" -lt 40 ] || return 1
644 return 0
647 # List all Git repositories, with given prefix if specified, one-per-line
648 # All project names starting with _ are always excluded from the result
649 get_repo_list() {
650 if [ -n "$1" ]; then
651 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group | LC_ALL=C grep "^$1"
652 else
653 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group
654 fi | while IFS=: read name id; do
655 [ $id -lt 65536 ] || case "$name" in _*) :;; ?*) echo "$name"; esac
656 done
659 # set the variable named by the first argument to the project part (i.e. WITH
660 # the trailing ".git" but WITHOUT the leading $cfg_reporoot) of the directory
661 # specified by the second argument.
662 # This function cannot be fooled by symbolic links.
663 # If the second argument is omitted (or empty) use $(pwd -P) instead.
664 # The directory specified by the second argument must exist.
665 v_get_proj_from_dir() {
666 [ -n "$2" ] || set -- "$1" "$(pwd -P)"
667 [ -d "$2" ] || return 1
668 case "$2" in
669 "$cfg_reporoot/"?*)
670 # Simple case that does not need any fancy footwork
671 _projpart="${2#$cfg_reporoot/}"
674 _absrr="$(cd "$cfg_reporoot" && pwd -P)"
675 _abspd="$(cd "$2" && pwd -P)"
676 case "$_abspd" in
677 "$_absrr/"?*)
678 # The normal case
679 _projpart="${_abspd#$_absrr/}"
682 # Must have been reached via a symbolic link, but
683 # we have no way to know the source, so use a
684 # generic "_external" leader combined with just the
685 # trailing directory name
686 _abspd="${_abspd%/}"
687 _abspd="${_abspd%/.git}"
688 _projpart="_external/${_abspd##*/}"
690 esac
691 esac
692 eval "$1="'"$_projpart"'
695 # Returns success if "$1" does not exist or contains only blank lines and comments
696 # The parsing rules are in Git's sha1-file.c parse_alt_odb_entry function;
697 # the format for blank lines and comments has been the same since Git v0.99.5
698 is_empty_alternates_file() {
699 [ -n "$1" ] || return 0
700 [ -e "$1" ] && [ -f "$1" ] && [ -s "$1" ] || return 0
701 [ -r "$1" ] || return 1
702 LC_ALL=C awk <"$1" '!/^$/ && !/^#/ {exit 1}'
705 # Return success if the given project name has at least one immediate child fork
706 # that has a non-empty alternates file
707 has_forks_with_alternates() {
708 _prj="${1%.git}"
709 [ -n "$_prj" ] || return 1
710 [ -d "$cfg_reporoot/$_prj" ] || return 1
711 is_git_dir "$cfg_reporoot/$_prj.git" || return 1
713 get_repo_list "$_prj/[^/:][^/:]*:" |
714 while read -r _prjname && [ -n "$_prjname" ]; do
715 is_empty_alternates_file "$cfg_reporoot/$_prjname.git/objects/info/alternates" ||
716 exit 1 # will only exit implicit subshell created by '|'
717 done
718 then
719 return 1
721 return 0
724 # returns empty string and error for empty string otherwise one of
725 # m => normal Git mirror
726 # s => mirror from svn source
727 # d => mirror from darcs source
728 # b => mirror from bzr source
729 # h => mirror from hg source
730 # w => mirror from mediawiki source
731 # f => mirror from other fast-import source
732 # note that if the string is non-empty and none of s, d, b or h match the
733 # return will always be type m regardless of whether it's a valid Git URL
734 get_url_mirror_type() {
735 case "$1" in
737 return 1
739 svn://* | svn+http://* | svn+https://* | svn+file://* | svn+ssh://*)
740 echo 's'
742 darcs://* | darcs+http://* | darcs+https://*)
743 echo 'd'
745 bzr://*)
746 echo 'b'
748 hg+http://* | hg+https://* | hg+file://* | hg+ssh://* | hg::*)
749 echo 'h'
751 mediawiki::*)
752 echo 'w'
755 echo 'm'
757 esac
758 return 0
761 # returns false for empty string
762 # returns true if the passed in url is a mirror using git fast-import
763 is_gfi_mirror_url() {
764 [ -n "$1" ] || return 1
765 case "$(get_url_mirror_type "$1" 2>/dev/null || :)" in
766 d|b|h|w|f)
767 # darcs, bzr, hg and mediawiki mirrors use git fast-import
768 # and so do generic "f" fast-import mirrors
769 return 0
772 # Don't think git-svn currently uses git fast-import
773 # And Git mirrors certainly do not
774 return 1
776 esac
777 # assume it does not use git fast-import
778 return 1
781 # returns false for empty string
782 # returns true if the passed in url is a mirror using git-svn
783 is_svn_mirror_url() {
784 [ -n "$1" ] || return 1
785 [ "$(get_url_mirror_type "$1" 2>/dev/null || :)" = "s" ]
788 # returns mirror url for gitweb.baseurl of git directory
789 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
790 # will fail if the directory does not have .nofetch and gitweb.baseurl
791 # comes back empty -- otherwise .nofetch directories succeed with a "" return
792 # automatically strips any leading "disabled " prefix before returning result
793 get_mirror_url() {
794 _gitdir="${1:-.}"
795 # always return empty for non-mirrors
796 ! [ -e "$_gitdir/.nofetch" ] || return 0
797 _url="$(GIT_DIR="$_gitdir" config_get baseurl 2>/dev/null)" || :
798 _url="${_url##* }"
799 [ -n "$_url" ] || return 1
800 printf '%s\n' "$_url"
801 return 0
804 # returns get_url_mirror_type for gitweb.baseurl of git directory
805 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
806 # will fail if the directory does not have .nofetch and gitweb.baseurl
807 # comes back empty -- otherwise .nofetch directories succeed with a "" return
808 # automatically strips any leading "disabled " prefix before testing
809 get_mirror_type() {
810 _url="$(get_mirror_url "$@")" || return 1
811 [ -n "$_url" ] || return 0
812 get_url_mirror_type "$_url"
815 # returns true if the passed in git dir (defaults to ".") is a mirror using git fast-import
816 is_gfi_mirror() {
817 _url="$(get_mirror_url "$@")" || return 1
818 is_gfi_mirror_url "$_url"
821 # returns true if the passed in git dir (defaults to ".") is a mirror using git-svn
822 is_svn_mirror() {
823 _url="$(get_mirror_url "$@")" || return 1
824 is_svn_mirror_url "$_url"
827 # current directory must already be set to Git repository
828 # if girocco.headok is already true succeeds without doing anything
829 # if rev-parse --verify HEAD succeeds sets headok=true and succeeds
830 # otherwise tries to set HEAD to a symbolic ref to refs/heads/master
831 # then refs/heads/trunk and finally the first top-level head from
832 # refs/heads/* (i.e. only two slashes in the name) and finally any
833 # existing refs/heads. The first one to succeed wins and sets headok=true
834 # and then a successful exit. Otherwise headok is left unset with a failure exit
835 # We use the girocco.headok flag to make sure we only force a valid HEAD symref
836 # when the repository is being set up -- if the HEAD is later deleted (through
837 # a push or fetch --prune) that's no longer our responsibility to fix
838 check_and_set_head() {
839 [ "$(git config --bool girocco.headok 2>/dev/null || :)" != "true" ] || return 0
840 if git rev-parse --verify --quiet HEAD >/dev/null; then
841 git config --bool girocco.headok true
842 return 0
844 for _hr in refs/heads/master refs/heads/trunk; do
845 if git rev-parse --verify --quiet "$_hr" >/dev/null; then
846 _update_head_symref "$_hr"
847 return 0
849 done
850 git for-each-ref --format="%(refname)" refs/heads 2>/dev/null |
851 while read -r _hr; do
852 case "${_hr#refs/heads/}" in */*) :;; *)
853 _update_head_symref "$_hr"
854 exit 1 # exit subshell created by "|"
855 esac
856 done || return 0
857 _hr="$(git for-each-ref --format="%(refname)" refs/heads 2>/dev/null | head -n 1)" || :
858 if [ -n "$_hr" ]; then
859 _update_head_symref "$_hr"
860 return 0
862 return 1
864 _update_head_symref() {
865 git symbolic-ref HEAD "$1"
866 git config --bool girocco.headok true
867 ! [ -d htmlcache ] || { >htmlcache/changed; } 2>/dev/null || :
870 # current directory must already be set to Git repository
871 # if the directory needs to have gc run and .needsgc is not already set
872 # then .needsgc will be set triggering a "mini" gc at the next opportunity
873 # Girocco shouldn't generate any loose objects but we check for that anyway
874 check_and_set_needsgc() {
875 # If there's a .needspack file and ANY loose objects with a newer timestamp
876 # then also set .needsgc otherwise remove it. The only caller that may set
877 # .needspack is a mirror therefore we don't have to worry about removing a
878 # .needspack out from under a simultaneous creator. We always do this and
879 # do it first to try and avoid leaving a stale .needspack lying around.
880 if [ -e .needspack ]; then
881 _objfiles=
882 _objfiles="$(( $(find -L objects/$octet -maxdepth 1 -newer .needspack -name "$octet19*" -type f -print 2>/dev/null |
883 head -n 1 | LC_ALL=C wc -l) +0 ))"
884 if [ "${_objfiles:-0}" = "0" ]; then
885 rm -f .needspack
886 else
887 [ -e .needsgc ] || >.needsgc
890 ! [ -e .needsgc ] || return 0
891 _packs=
892 { _packs="$(list_packs --quiet --count --exclude-no-idx --exclude-keep objects/pack || :)" || :; } 2>/dev/null
893 if [ "${_packs:-0}" -ge 20 ]; then
894 >.needsgc
895 return 0
897 _logfiles=
898 { _logfiles="$(($(find -L reflogs -maxdepth 1 -type f -print | wc -l || :)+0))" || :; } 2>/dev/null
899 if [ "${_logfiles:-0}" -ge 50 ]; then
900 >.needsgc
901 return 0
903 # Truly git gc only checks the number of objects in the objects/17 directory
904 # We check for -ge 10 which should make the probability of having more than
905 # 5120 (20*256) loose objects present when there are less than 10 in
906 # objects/17 vanishingly small (20 is the threshold we use for pack files)
907 _objfiles=
908 ! [ -d objects/17 ] ||
909 { _objfiles="$(($(find -L objects/17 -type f -name "$octet19*" -print | wc -l || :)+0))" || :; } 2>/dev/null
910 if [ "${_objfiles:-0}" -ge 10 ]; then
911 >.needsgc
912 return 0
916 # A well-known UTF-8 locale is required for some of the fast-import providers
917 # in order to avoid mangling characters. Ideally we could use "POSIX.UTF-8"
918 # but that is not reliably UTF-8 but rather usually US-ASCII.
919 # We parse the output of `locale -a` and select a suitable UTF-8 locale at
920 # install time and store that in $var_utf8_locale if one is found.
921 # If we cannot find one in the `locale -a` output then we just use a well-known
922 # UTF-8 locale and hope for the best. We set LC_ALL to our choice and export
923 # it. We only set this temporarily when running the fast-import providers.
924 set_utf8_locale() {
925 LC_ALL="${var_utf8_locale:-en_US.UTF-8}"
926 export LC_ALL
929 # hg-fast-export | git fast-import with error handling in current directory GIT_DIR
930 git_hg_fetch() (
931 set_utf8_locale
932 _python="${PYTHON:-python}"
933 rm -f hg2git-marks.old hg2git-marks.new
934 if [ -f hg2git-marks ] && [ -s hg2git-marks ]; then
935 LC_ALL=C sed 's/^:\([^ ][^ ]*\) \([^ ][^ ]*\)$/\2 \1/' <hg2git-marks | {
936 if [ -n "$var_have_git_185" ]; then
937 git cat-file --batch-check=':%(rest) %(objectname)'
938 else
939 LC_ALL=C sed 's/^\([^ ][^ ]*\) \([^ ][^ ]*\)$/:\2 \1/'
941 } | LC_ALL=C sed '/ missing$/d' >hg2git-marks.old
942 if [ -n "$var_have_git_171" ] &&
943 git rev-parse --quiet --verify refs/notes/hg >/dev/null; then
944 if [ -z "$var_have_git_185" ] ||
945 ! LC_ALL=C cmp -s hg2git-marks hg2git-marks.old; then
946 _nm='hg-fast-export'
947 GIT_AUTHOR_NAME="$_nm"
948 GIT_COMMITTER_NAME="$_nm"
949 GIT_AUTHOR_EMAIL="$_nm"
950 GIT_COMMITTER_EMAIL="$_nm"
951 export GIT_AUTHOR_NAME
952 export GIT_COMMITTER_NAME
953 export GIT_AUTHOR_EMAIL
954 export GIT_COMMITTER_EMAIL
955 git notes --ref=refs/notes/hg prune
956 unset GIT_AUTHOR_NAME
957 unset GIT_COMMITTER_NAME
958 unset GIT_AUTHOR_EMAIL
959 unset GIT_COMMITTER_EMAIL
962 else
963 >hg2git-marks.old
965 _err1=
966 _err2=
967 exec 3>&1
968 { read -r _err1 || :; read -r _err2 || :; } <<-EOT
970 exec 4>&3 3>&1 1>&4 4>&-
972 _e1=0
973 _af="$(git config hg.authorsfile)" || :
974 _cmd='GIT_DIR="$(pwd)" "$_python" "$cfg_basedir/bin/hg-fast-export.py" \
975 --repo "$(pwd)/repo.hg" \
976 --marks "$(pwd)/hg2git-marks.old" \
977 --mapping "$(pwd)/hg2git-mapping" \
978 --heads "$(pwd)/hg2git-heads" \
979 --status "$(pwd)/hg2git-state" \
980 -U unknown --force --flatten --hg-hash'
981 [ -z "$_af" ] || _cmd="$_cmd"' --authors "$_af"'
982 eval "$_cmd" 3>&- || _e1=$?
983 echo $_e1 >&3
986 _e2=0
987 git fast-import \
988 --import-marks="$(pwd)/hg2git-marks.old" \
989 --export-marks="$(pwd)/hg2git-marks.new" \
990 --export-pack-edges="$(pwd)/gfi-packs" \
991 --force 3>&- || _e2=$?
992 echo $_e2 >&3
996 exec 3>&-
997 [ "$_err1" = 0 ] && [ "$_err2" = 0 ] || return 1
998 mv -f hg2git-marks.new hg2git-marks
999 rm -f hg2git-marks.old
1000 git for-each-ref --format='%(refname) %(objectname)' refs/heads |
1001 LC_ALL=C sed -e 's,^refs/heads/,:,' >hg2git-heads