gc-git-svn.sh: set correct umask
[girocco.git] / shlib.sh
blob9e1e560841169e723bb42cb4da36b405455a7780
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"
265 git_add_config "gc.autodetach=false"
267 # Make sure any sendmail.pl config is always available
268 unset SENDMAIL_PL_HOST
269 unset SENDMAIL_PL_PORT
270 unset SENDMAIL_PL_NCBIN
271 unset SENDMAIL_PL_NCOPT
272 [ -z "$cfg_sendmail_pl_host" ] || { SENDMAIL_PL_HOST="$cfg_sendmail_pl_host" && export SENDMAIL_PL_HOST; }
273 [ -z "$cfg_sendmail_pl_port" ] || { SENDMAIL_PL_PORT="$cfg_sendmail_pl_port" && export SENDMAIL_PL_PORT; }
274 [ -z "$cfg_sendmail_pl_ncbin" ] || { SENDMAIL_PL_NCBIN="$cfg_sendmail_pl_ncbin" && export SENDMAIL_PL_NCBIN; }
275 [ -z "$cfg_sendmail_pl_ncopt" ] || { SENDMAIL_PL_NCOPT="$cfg_sendmail_pl_ncopt" && export SENDMAIL_PL_NCOPT; }
277 # Set PATH and PYTHON to the values set by Config.pm, if any
278 unset PYTHON
279 [ -z "$cfg_python" ] || { PYTHON="$cfg_python" && export PYTHON; }
280 [ -z "$cfg_path" ] || { orig_path="$PATH" && PATH="$cfg_path" && export PATH; }
282 # Extra GIT variables that generally ought to be cleared, but whose clearing
283 # could potentially interfere with the correct operation of hook scripts so
284 # they are segregated into a separate function for use as appropriate
285 clean_git_env() {
286 unset GIT_ALTERNATE_OBJECT_DIRECTORIES
287 unset GIT_CONFIG
288 unset GIT_DIR
289 unset GIT_GRAFT_FILE
290 unset GIT_INDEX_FILE
291 unset GIT_OBJECT_DIRECTORY
292 unset GIT_NAMESPACE
295 # We cannot use a git() {} or nc_openbsd() {} function to redirect git
296 # and nc_openbsd to the desired executables because when using
297 # "ENV_VAR=xxx func" the various /bin/sh implementations behave in various
298 # different and unexpected ways:
299 # a) treat "ENV_VAR=xxx" like a separate, preceding "export ENV_VAR=xxx"
300 # b) treat "ENV_VAR=xxx" like a separate, prededing "ENV_VAR=xxx"
301 # c) treat "ENV_VAR=xxx" like a temporary setting only while running func
302 # None of these are good. We want a temporary "export ENV_VAR=xxx"
303 # setting only while running func which none of the /bin/sh's do.
305 # Instead we'd like to use an alias that provides the desired behavior without
306 # any of the bad (a), (b) or (c) effects.
308 # However, unfortunately, some of the crazy /bin/sh implementations do not
309 # recognize alias expansions when preceded by variable assignments!
311 # So we are left with git() {} and nc_openbsd() {} functions and in the
312 # case of git() {} we can compensate for (b) and (c) failing to export
313 # but not (a) and (b) persisting the values so the caller will simply
314 # have to beware and explicitly unset any variables that should not persist
315 # beyond the function call itself.
317 git() (
318 [ z"${GIT_DIR+set}" != z"set" ] || export GIT_DIR
319 [ z"${GIT_SSL_NO_VERIFY+set}" != z"set" ] || export GIT_SSL_NO_VERIFY
320 [ z"${GIT_TRACE_PACKET+set}" != z"set" ] || export GIT_TRACE_PACKET
321 [ z"${GIT_USER_AGENT+set}" != z"set" ] || export GIT_USER_AGENT
322 [ z"${GIT_HTTP_USER_AGENT+set}" != z"set" ] || export GIT_HTTP_USER_AGENT
323 exec "$cfg_git_bin" "$@"
326 # Since we do not yet require at least Git 1.8.5 this is a compatibility function
327 # that allows us to use git update-ref --stdin where supported and the slow shell
328 # script where not, but only the "delete" operation is currently supported.
329 git_updateref_stdin() {
330 if [ -n "$var_have_git_185" ]; then
331 git update-ref --stdin
332 else
333 while read -r _op _ref; do
334 case "$_op" in
335 delete)
336 git update-ref -d "$_ref"
339 echo "bad git_updateref_stdin op: $_op" >&2
340 exit 1
342 esac
343 done
347 # see comments for git() -- callers must explicitly export all variables
348 # intended for the commands these functions run before calling them
349 perl() { command "${var_perl_bin:-perl}" "$@"; }
350 gzip() { command "${var_gzip_bin:-gzip}" "$@"; }
352 nc_openbsd() { command "$var_nc_openbsd_bin" "$@"; }
354 list_packs() { command "$cfg_basedir/bin/list_packs" "$@"; }
356 readlink() { command "$cfg_basedir/bin/readlink" "$@"; }
358 strftime() { command "$cfg_basedir/bin/strftime" "$@"; }
360 # Some platforms' broken xargs runs the command always at least once even if
361 # there's no input unless given a special option. Automatically supply the
362 # option on those platforms by providing an xargs function.
363 xargs() { command xargs $var_xargs_r "$@"; }
365 _addrlist() {
366 _list=
367 for _addr in "$@"; do
368 [ -z "$_list" ] || _list="$_list, "
369 _list="$_list$_addr"
370 done
371 echo "$_list"
374 _sendmail() {
375 _mailer="${cfg_sendmail_bin:-/usr/sbin/sendmail}"
376 if [ -n "$cfg_sender" ]; then
377 "$_mailer" -i -f "$cfg_sender" "$@"
378 else
379 "$_mailer" -i "$@"
383 # First argument is an id WITHOUT surrounding '<' and '>' to use in a
384 # "References:" header. It may be "" to suppress the "References" header.
385 # Following arguments are just like mail function
386 mailref() {
387 _references=
388 if [ $# -ge 1 ]; then
389 _references="$1"
390 shift
392 _subject=
393 if [ "$1" = "-s" ]; then
394 shift
395 _subject="$1"
396 shift
399 echo "From: \"$cfg_name\" ($cfg_title) <$cfg_admin>"
400 echo "To: $(_addrlist "$@")"
401 [ -z "$_subject" ] || echo "Subject: $_subject"
402 echo "MIME-Version: 1.0"
403 echo "Content-Type: text/plain; charset=utf-8; format=fixed"
404 echo "Content-Transfer-Encoding: 8bit"
405 [ -z "$_references" ] || echo "References: <$_references>"
406 [ -n "$cfg_suppress_x_girocco" ] || echo "X-Girocco: $cfg_gitweburl"
407 echo "Auto-Submitted: auto-generated"
408 echo ""
410 } | _sendmail "$@"
413 # Usage: mail [-s <subject>] <addr> [<addr>...]
414 mail() {
415 mailref "" "$@"
418 # bang CMD... will execute the command with well-defined failure mode;
419 # set bang_action to string of the failed action ('clone', 'update', ...);
420 # re-define the bang_trap() function to do custom cleanup before bailing out
421 bang() {
422 bang_errcode=
423 bang_catch "$@"
424 [ "${bang_errcode:-0}" = "0" ] || bang_failed
427 bang_catch() {
428 bang_active=1
429 bang_cmd="$*"
430 bang_errcode=0
431 if [ "${show_progress:-0}" != "0" ]; then
432 exec 3>&1
433 read -r bang_errcode <<-EOT || :
435 exec 4>&3 3>&1 1>&4 4>&-
436 { "$@" 3>&- || echo $? >&3; } 2>&1 | tee -i -a "$bang_log"
439 exec 3>&-
440 if [ -z "$bang_errcode" ] || [ "$bang_errcode" = "0" ]; then
441 # All right. Cool.
442 bang_active=
443 bang_cmd=
444 return;
446 else
447 if "$@" >>"$bang_log" 2>&1; then
448 # All right. Cool.
449 bang_active=
450 bang_cmd=
451 return;
452 else
453 bang_errcode="$?"
458 bang_failed() {
459 bang_active=
460 unset GIT_DIR
461 >.banged
462 cat "$bang_log" >.banglog
463 echo "" >>.banglog
464 echo "$bang_cmd failed with error code $bang_errcode" >>.banglog
465 ! [ -d htmlcache ] || { >htmlcache/changed; } 2>/dev/null || :
466 if [ "${show_progress:-0}" != "0" ]; then
467 echo ""
468 echo "$bang_cmd failed with error code $bang_errcode"
470 if [ -e .bangagain ]; then
471 git config --remove-section girocco.bang 2>/dev/null || :
472 rm -f .bangagain
474 bangcount="$(git config --int girocco.bang.count 2>/dev/null)" || :
475 bangcount=$(( ${bangcount:-0} + 1 ))
476 git config --int girocco.bang.count $bangcount
477 if [ $bangcount -eq 1 ]; then
478 git config girocco.bang.firstfail "$(TZ=UTC date "+%Y-%m-%d %T UTC")"
480 if [ $bangcount -ge $cfg_min_mirror_failure_message_count ] &&
481 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" != "true" ] &&
482 ! check_interval "girocco.bang.firstfail" $cfg_min_mirror_failure_message_interval; then
483 bangmailok="$(git config --bool gitweb.statusupdates 2>/dev/null || echo true)"
484 bangaddrs=
485 [ "$bangmailok" = "false" ] || [ -z "$mail" ] || bangaddrs="$mail"
486 [ -z "$cfg_admincc" ] || [ "$cfg_admincc" = "0" ] || [ -z "$cfg_admin" ] ||
487 if [ -z "$bangaddrs" ]; then bangaddrs="$cfg_admin"; else bangaddrs="$bangaddrs,$cfg_admin"; fi
488 rsubj=
489 [ $bangcount -le 1 ] || rsubj=" repeatedly"
490 [ -z "$bangaddrs" ] ||
492 echo "$bang_cmd failed with error code $bang_errcode"
493 echo ""
494 rsubj=
495 if [ $bangcount -gt 1 ]; then
496 echo "$bangcount consecutive update failures have occurred since $(config_get girocco.bang.firstfail)"
497 echo ""
499 echo "you will not receive any more notifications until recovery"
500 echo "this status message may be disabled on the project admin page"
501 echo ""
502 echo "Log follows:"
503 echo ""
504 cat "$bang_log"
505 } | mailref "update@$cfg_gitweburl/$proj.git" -s "[$cfg_name] $proj $bang_action failed$rsubj" "$bangaddrs"
506 git config --bool girocco.bang.messagesent true
508 bangthrottle=
509 [ $bangcount -lt 15 ] ||
510 check_interval "girocco.bang.firstfail" $(( $cfg_min_mirror_interval * 3 / 2 )) ||
511 bangthrottle=1
512 bang_trap $bangthrottle
513 [ -n "$bang_errcode" ] && [ "$bang_errcode" != "0" ] || bang_errcode=1
514 exit $bang_errcode
517 # bang_eval CMD... will evaluate the command with well-defined failure mode;
518 # Identical to bang CMD... except the command is eval'd instead of executed.
519 bang_eval() {
520 bang eval "$*"
523 # Default bang settings:
524 bang_setup() {
525 bang_active=
526 bang_action="lame_programmer"
527 bang_trap() { :; }
528 bang_tmpdir="${TMPDIR:-/tmp}"
529 bang_tmpdir="${bang_tmpdir%/}"
530 bang_log="$(mktemp "${bang_tmpdir:-/tmp}/repomgr-XXXXXX")"
531 is_git_dir . || {
532 echo "bang_setup called with current directory not a git directory" >&2
533 exit 1
535 trap 'rm -f "$bang_log"' EXIT
536 trap '[ -z "$bang_active" ] || { bang_errcode=130; bang_failed; }; exit 130' INT
537 trap '[ -z "$bang_active" ] || { bang_errcode=143; bang_failed; }; exit 143' TERM
540 # Remove banged status
541 bang_reset() {
542 rm -f .banged .bangagain .banglog
543 git config --remove-section girocco.bang 2>/dev/null || :
546 # Check to see if banged status
547 is_banged() {
548 [ -e .banged ]
551 # Check to see if banged message was sent
552 was_banged_message_sent() {
553 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" = "true" ]
556 # Progress report - if show_progress is set, shows the given message.
557 progress() {
558 [ "${show_progress:-0}" = "0" ] || echo "$*"
561 # Project config accessors; must be run in project directory
562 config_get() {
563 case "$1" in
564 *.*)
565 git config "$1";;
567 git config "gitweb.$1";;
568 esac
571 config_set() {
572 git config "gitweb.$1" "$2" && chgrp $var_group config && chmod g+w config
575 config_set_raw() {
576 git config "$1" "$2" && chgrp $var_group config && chmod g+w config
579 config_get_date_seconds() {
580 _dt="$(config_get "$1")" || :
581 [ -n "$_dt" ] || return 1
582 _ds="$(perl -I@basedir@ -MGirocco::Util -e "print parse_any_date('$_dt')")"
583 [ -n "$_ds" ] || return 1
584 echo "$_ds"
587 # Tool for checking whether given number of seconds has not passed yet
588 check_interval() {
589 os="$(config_get_date_seconds "$1")" || return 1
590 ns="$(date +%s)"
591 [ $ns -lt $(($os+$2)) ]
594 # Check if we are running with effective root permissions
595 is_root() {
596 [ "$(id -u 2>/dev/null)" = "0" ]
599 # Check to see if the single argument (default ".") is a Git directory
600 is_git_dir() {
601 # Just like Git's test except we ignore GIT_OBJECT_DIRECTORY
602 # And we are slightly more picky (must be refs/.+ not refs/.*)
603 [ $# -ne 0 ] || set -- "."
604 [ -d "$1/objects" ] && [ -x "$1/objects" ] || return 1
605 [ -d "$1/refs" ] && [ -x "$1/refs" ] || return 1
606 if [ -L "$1/HEAD" ]; then
607 _hr="$(readlink "$1/HEAD")"
608 case "$_hr" in "refs/"?*) :;; *) return 1;; esac
610 [ -f "$1/HEAD" ] && [ -r "$1/HEAD" ] || return 1
611 read -r _hr <"$1/HEAD" || return 1
612 case "$_hr" in
613 $octet20*)
614 [ "${_hr#*[!0-9a-f]}" = "$_hr" ] || return 1
615 return 0;;
616 ref:refs/?*)
617 return 0;;
618 ref:*)
619 _hr="${_hr##ref:*[ $tab]}"
620 case "$_hr" in "refs/"?*) return 0;; esac
621 esac
622 return 1
625 # Check to see if the single argument (default ".") is a directory with no refs
626 is_empty_refs_dir() {
627 [ $# -ne 0 ] || set -- "."
628 if [ -s "$1/packed-refs" ]; then
629 # could be a packed-refs file with just a '# pack-refs ..." line
630 # null hash lines and peel lines do not count either
631 _refcnt="$(( $(LC_ALL=C sed <"$1/packed-refs" \
632 -e "/^00* /d" \
633 -e "/^$octet20$hexdig* refs\/[^ $tab]*\$/!d" | wc -l) ))"
634 [ "${_refcnt:-0}" -eq 0 ] || return 1
636 if [ -d "$1/refs" ]; then
637 # quick and dirty check, doesn't try to validate contents
638 # or ignore embedded symbolic refs
639 _refcnt="$(( $(find -L "$1/refs" -type f -print 2>/dev/null | head -n 1 | LC_ALL=C wc -l) ))"
640 [ "${_refcnt:-0}" -eq 0 ] || return 1
642 # last chance a detached HEAD (we ignore any linked working trees though)
643 [ -s "$1/HEAD" ] && read -r _hr <"$1/HEAD" && [ -n "$_hr" ] || return 0
644 [ "${_hr#*[!0-9a-f]}" != "$_hr" ] || [ "${_hr#*[!0]}" = "$_hr" ] || [ "${#_hr}" -lt 40 ] || return 1
645 return 0
648 # List all Git repositories, with given prefix if specified, one-per-line
649 # All project names starting with _ are always excluded from the result
650 get_repo_list() {
651 if [ -n "$1" ]; then
652 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group | LC_ALL=C grep "^$1"
653 else
654 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group
655 fi | while IFS=: read name id; do
656 [ $id -lt 65536 ] || case "$name" in _*) :;; ?*) echo "$name"; esac
657 done
660 # set the variable named by the first argument to the project part (i.e. WITH
661 # the trailing ".git" but WITHOUT the leading $cfg_reporoot) of the directory
662 # specified by the second argument.
663 # This function cannot be fooled by symbolic links.
664 # If the second argument is omitted (or empty) use $(pwd -P) instead.
665 # The directory specified by the second argument must exist.
666 v_get_proj_from_dir() {
667 [ -n "$2" ] || set -- "$1" "$(pwd -P)"
668 [ -d "$2" ] || return 1
669 case "$2" in
670 "$cfg_reporoot/"?*)
671 # Simple case that does not need any fancy footwork
672 _projpart="${2#$cfg_reporoot/}"
675 _absrr="$(cd "$cfg_reporoot" && pwd -P)"
676 _abspd="$(cd "$2" && pwd -P)"
677 case "$_abspd" in
678 "$_absrr/"?*)
679 # The normal case
680 _projpart="${_abspd#$_absrr/}"
683 # Must have been reached via a symbolic link, but
684 # we have no way to know the source, so use a
685 # generic "_external" leader combined with just the
686 # trailing directory name
687 _abspd="${_abspd%/}"
688 _abspd="${_abspd%/.git}"
689 _projpart="_external/${_abspd##*/}"
691 esac
692 esac
693 eval "$1="'"$_projpart"'
696 # Returns success if "$1" does not exist or contains only blank lines and comments
697 # The parsing rules are in Git's sha1-file.c parse_alt_odb_entry function;
698 # the format for blank lines and comments has been the same since Git v0.99.5
699 is_empty_alternates_file() {
700 [ -n "$1" ] || return 0
701 [ -e "$1" ] && [ -f "$1" ] && [ -s "$1" ] || return 0
702 [ -r "$1" ] || return 1
703 LC_ALL=C awk <"$1" '!/^$/ && !/^#/ {exit 1}'
706 # Return success if the given project name has at least one immediate child fork
707 # that has a non-empty alternates file
708 has_forks_with_alternates() {
709 _prj="${1%.git}"
710 [ -n "$_prj" ] || return 1
711 [ -d "$cfg_reporoot/$_prj" ] || return 1
712 is_git_dir "$cfg_reporoot/$_prj.git" || return 1
714 get_repo_list "$_prj/[^/:][^/:]*:" |
715 while read -r _prjname && [ -n "$_prjname" ]; do
716 is_empty_alternates_file "$cfg_reporoot/$_prjname.git/objects/info/alternates" ||
717 exit 1 # will only exit implicit subshell created by '|'
718 done
719 then
720 return 1
722 return 0
725 # returns empty string and error for empty string otherwise one of
726 # m => normal Git mirror
727 # s => mirror from svn source
728 # d => mirror from darcs source
729 # b => mirror from bzr source
730 # h => mirror from hg source
731 # w => mirror from mediawiki source
732 # f => mirror from other fast-import source
733 # note that if the string is non-empty and none of s, d, b or h match the
734 # return will always be type m regardless of whether it's a valid Git URL
735 get_url_mirror_type() {
736 case "$1" in
738 return 1
740 svn://* | svn+http://* | svn+https://* | svn+file://* | svn+ssh://*)
741 echo 's'
743 darcs://* | darcs+http://* | darcs+https://*)
744 echo 'd'
746 bzr://*)
747 echo 'b'
749 hg+http://* | hg+https://* | hg+file://* | hg+ssh://* | hg::*)
750 echo 'h'
752 mediawiki::*)
753 echo 'w'
756 echo 'm'
758 esac
759 return 0
762 # returns false for empty string
763 # returns true if the passed in url is a mirror using git fast-import
764 is_gfi_mirror_url() {
765 [ -n "$1" ] || return 1
766 case "$(get_url_mirror_type "$1" 2>/dev/null || :)" in
767 d|b|h|w|f)
768 # darcs, bzr, hg and mediawiki mirrors use git fast-import
769 # and so do generic "f" fast-import mirrors
770 return 0
773 # Don't think git-svn currently uses git fast-import
774 # And Git mirrors certainly do not
775 return 1
777 esac
778 # assume it does not use git fast-import
779 return 1
782 # returns false for empty string
783 # returns true if the passed in url is a mirror using git-svn
784 is_svn_mirror_url() {
785 [ -n "$1" ] || return 1
786 [ "$(get_url_mirror_type "$1" 2>/dev/null || :)" = "s" ]
789 # returns mirror url for gitweb.baseurl of git directory
790 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
791 # will fail if the directory does not have .nofetch and gitweb.baseurl
792 # comes back empty -- otherwise .nofetch directories succeed with a "" return
793 # automatically strips any leading "disabled " prefix before returning result
794 get_mirror_url() {
795 _gitdir="${1:-.}"
796 # always return empty for non-mirrors
797 ! [ -e "$_gitdir/.nofetch" ] || return 0
798 _url="$(GIT_DIR="$_gitdir" config_get baseurl 2>/dev/null)" || :
799 _url="${_url##* }"
800 [ -n "$_url" ] || return 1
801 printf '%s\n' "$_url"
802 return 0
805 # returns get_url_mirror_type for gitweb.baseurl of git directory
806 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
807 # will fail if the directory does not have .nofetch and gitweb.baseurl
808 # comes back empty -- otherwise .nofetch directories succeed with a "" return
809 # automatically strips any leading "disabled " prefix before testing
810 get_mirror_type() {
811 _url="$(get_mirror_url "$@")" || return 1
812 [ -n "$_url" ] || return 0
813 get_url_mirror_type "$_url"
816 # returns true if the passed in git dir (defaults to ".") is a mirror using git fast-import
817 is_gfi_mirror() {
818 _url="$(get_mirror_url "$@")" || return 1
819 is_gfi_mirror_url "$_url"
822 # returns true if the passed in git dir (defaults to ".") is a mirror using git-svn
823 is_svn_mirror() {
824 _url="$(get_mirror_url "$@")" || return 1
825 is_svn_mirror_url "$_url"
828 # current directory must already be set to Git repository
829 # if girocco.headok is already true succeeds without doing anything
830 # if rev-parse --verify HEAD succeeds sets headok=true and succeeds
831 # otherwise tries to set HEAD to a symbolic ref to refs/heads/master
832 # then refs/heads/trunk and finally the first top-level head from
833 # refs/heads/* (i.e. only two slashes in the name) and finally any
834 # existing refs/heads. The first one to succeed wins and sets headok=true
835 # and then a successful exit. Otherwise headok is left unset with a failure exit
836 # We use the girocco.headok flag to make sure we only force a valid HEAD symref
837 # when the repository is being set up -- if the HEAD is later deleted (through
838 # a push or fetch --prune) that's no longer our responsibility to fix
839 check_and_set_head() {
840 [ "$(git config --bool girocco.headok 2>/dev/null || :)" != "true" ] || return 0
841 if git rev-parse --verify --quiet HEAD >/dev/null; then
842 git config --bool girocco.headok true
843 return 0
845 for _hr in refs/heads/master refs/heads/trunk; do
846 if git rev-parse --verify --quiet "$_hr" >/dev/null; then
847 _update_head_symref "$_hr"
848 return 0
850 done
851 git for-each-ref --format="%(refname)" refs/heads 2>/dev/null |
852 while read -r _hr; do
853 case "${_hr#refs/heads/}" in */*) :;; *)
854 _update_head_symref "$_hr"
855 exit 1 # exit subshell created by "|"
856 esac
857 done || return 0
858 _hr="$(git for-each-ref --format="%(refname)" refs/heads 2>/dev/null | head -n 1)" || :
859 if [ -n "$_hr" ]; then
860 _update_head_symref "$_hr"
861 return 0
863 return 1
865 _update_head_symref() {
866 git symbolic-ref HEAD "$1"
867 git config --bool girocco.headok true
868 ! [ -d htmlcache ] || { >htmlcache/changed; } 2>/dev/null || :
871 # current directory must already be set to Git repository
872 # if the directory needs to have gc run and .needsgc is not already set
873 # then .needsgc will be set triggering a "mini" gc at the next opportunity
874 # Girocco shouldn't generate any loose objects but we check for that anyway
875 check_and_set_needsgc() {
876 # If there's a .needspack file and ANY loose objects with a newer timestamp
877 # then also set .needsgc otherwise remove it. The only caller that may set
878 # .needspack is a mirror therefore we don't have to worry about removing a
879 # .needspack out from under a simultaneous creator. We always do this and
880 # do it first to try and avoid leaving a stale .needspack lying around.
881 if [ -e .needspack ]; then
882 _objfiles=
883 _objfiles="$(( $(find -L objects/$octet -maxdepth 1 -newer .needspack -name "$octet19*" -type f -print 2>/dev/null |
884 head -n 1 | LC_ALL=C wc -l) +0 ))"
885 if [ "${_objfiles:-0}" = "0" ]; then
886 rm -f .needspack
887 else
888 [ -e .needsgc ] || >.needsgc
891 ! [ -e .needsgc ] || return 0
892 _packs=
893 { _packs="$(list_packs --quiet --count --exclude-no-idx --exclude-keep objects/pack || :)" || :; } 2>/dev/null
894 if [ "${_packs:-0}" -ge 20 ]; then
895 >.needsgc
896 return 0
898 _logfiles=
899 { _logfiles="$(($(find -L reflogs -maxdepth 1 -type f -print | wc -l || :)+0))" || :; } 2>/dev/null
900 if [ "${_logfiles:-0}" -ge 50 ]; then
901 >.needsgc
902 return 0
904 # Truly git gc only checks the number of objects in the objects/17 directory
905 # We check for -ge 10 which should make the probability of having more than
906 # 5120 (20*256) loose objects present when there are less than 10 in
907 # objects/17 vanishingly small (20 is the threshold we use for pack files)
908 _objfiles=
909 ! [ -d objects/17 ] ||
910 { _objfiles="$(($(find -L objects/17 -type f -name "$octet19*" -print | wc -l || :)+0))" || :; } 2>/dev/null
911 if [ "${_objfiles:-0}" -ge 10 ]; then
912 >.needsgc
913 return 0
917 # A well-known UTF-8 locale is required for some of the fast-import providers
918 # in order to avoid mangling characters. Ideally we could use "POSIX.UTF-8"
919 # but that is not reliably UTF-8 but rather usually US-ASCII.
920 # We parse the output of `locale -a` and select a suitable UTF-8 locale at
921 # install time and store that in $var_utf8_locale if one is found.
922 # If we cannot find one in the `locale -a` output then we just use a well-known
923 # UTF-8 locale and hope for the best. We set LC_ALL to our choice and export
924 # it. We only set this temporarily when running the fast-import providers.
925 set_utf8_locale() {
926 LC_ALL="${var_utf8_locale:-en_US.UTF-8}"
927 export LC_ALL
930 # hg-fast-export | git fast-import with error handling in current directory GIT_DIR
931 git_hg_fetch() (
932 set_utf8_locale
933 _python="${PYTHON:-python}"
934 rm -f hg2git-marks.old hg2git-marks.new
935 if [ -f hg2git-marks ] && [ -s hg2git-marks ]; then
936 LC_ALL=C sed 's/^:\([^ ][^ ]*\) \([^ ][^ ]*\)$/\2 \1/' <hg2git-marks | {
937 if [ -n "$var_have_git_185" ]; then
938 git cat-file --batch-check=':%(rest) %(objectname)'
939 else
940 LC_ALL=C sed 's/^\([^ ][^ ]*\) \([^ ][^ ]*\)$/:\2 \1/'
942 } | LC_ALL=C sed '/ missing$/d' >hg2git-marks.old
943 if [ -n "$var_have_git_171" ] &&
944 git rev-parse --quiet --verify refs/notes/hg >/dev/null; then
945 if [ -z "$var_have_git_185" ] ||
946 ! LC_ALL=C cmp -s hg2git-marks hg2git-marks.old; then
947 _nm='hg-fast-export'
948 GIT_AUTHOR_NAME="$_nm"
949 GIT_COMMITTER_NAME="$_nm"
950 GIT_AUTHOR_EMAIL="$_nm"
951 GIT_COMMITTER_EMAIL="$_nm"
952 export GIT_AUTHOR_NAME
953 export GIT_COMMITTER_NAME
954 export GIT_AUTHOR_EMAIL
955 export GIT_COMMITTER_EMAIL
956 git notes --ref=refs/notes/hg prune
957 unset GIT_AUTHOR_NAME
958 unset GIT_COMMITTER_NAME
959 unset GIT_AUTHOR_EMAIL
960 unset GIT_COMMITTER_EMAIL
963 else
964 >hg2git-marks.old
966 _err1=
967 _err2=
968 exec 3>&1
969 { read -r _err1 || :; read -r _err2 || :; } <<-EOT
971 exec 4>&3 3>&1 1>&4 4>&-
973 _e1=0
974 _af="$(git config hg.authorsfile)" || :
975 _cmd='GIT_DIR="$(pwd)" "$_python" "$cfg_basedir/bin/hg-fast-export.py" \
976 --repo "$(pwd)/repo.hg" \
977 --marks "$(pwd)/hg2git-marks.old" \
978 --mapping "$(pwd)/hg2git-mapping" \
979 --heads "$(pwd)/hg2git-heads" \
980 --status "$(pwd)/hg2git-state" \
981 -U unknown --force --flatten --hg-hash'
982 [ -z "$_af" ] || _cmd="$_cmd"' --authors "$_af"'
983 eval "$_cmd" 3>&- || _e1=$?
984 echo $_e1 >&3
987 _e2=0
988 git fast-import \
989 --import-marks="$(pwd)/hg2git-marks.old" \
990 --export-marks="$(pwd)/hg2git-marks.new" \
991 --export-pack-edges="$(pwd)/gfi-packs" \
992 --force 3>&- || _e2=$?
993 echo $_e2 >&3
997 exec 3>&-
998 [ "$_err1" = 0 ] && [ "$_err2" = 0 ] || return 1
999 mv -f hg2git-marks.new hg2git-marks
1000 rm -f hg2git-marks.old
1001 git for-each-ref --format='%(refname) %(objectname)' refs/heads |
1002 LC_ALL=C sed -e 's,^refs/heads/,:,' >hg2git-heads