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