scripts: purge use of test '-a' and '-o' ops and clean up
[girocco.git] / shlib.sh
blob88a3e1e35fb21c0f6ab6b0537d9d5af7cece5c67
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 # SHA-1 patterns
7 octet='[0-9a-f][0-9a-f]'
8 octet4="$octet$octet$octet$octet"
9 octet19="$octet4$octet4$octet4$octet4$octet$octet$octet"
10 octet20="$octet4$octet4$octet4$octet4$octet4"
11 nullsha="0000000000000000000000000000000000000000"
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 [ -z "$orig_path" ] || { PATH="$orig_path" && export PATH; }
54 perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf -le \
55 'foreach (sort {uc($a) cmp uc($b)} keys %Girocco::Config::) {
56 my $val = ${$Girocco::Config::{$_}}; defined($val) or $val="";
57 $val =~ s/([\\"\$\`])/\\$1/gos;
58 $val =~ s/(?:\r\n|\r|\n)$//os;
59 print "cfg_$_=\"$val\"";
60 print "defined_cfg_$_=",
61 (defined(${$Girocco::Config::{$_}})?"1":"");
65 # Returns full command path for "$1" if it's a valid command otherwise returns "$1"
66 _fcp() {
67 if _fp="$(command -v "$1" 2>/dev/null)"; then
68 printf '%s\n' "$_fp"
69 else
70 printf '%s\n' "$1"
74 get_girocco_config_var_list() (
75 # Same as get_girocco_config_pm_var_list except that
76 # the following variables (all starting with var_) are added:
77 # var_group cfg_owning_group if defined otherwise `id -gn`
78 # var_git_ver The version number part from `git version`
79 # var_git_exec_path The result of $cfg_git_bin --exec-dir
80 # var_sh_bin Full path to the posix sh interpreter to use
81 # var_perl_bin Full path to the perl interpreter to use
82 # var_gzip_bin Full path to the gzip executable to use
83 # var_nc_openbsd_bin Full path to the netcat (nc) with -U support
84 # var_have_git_171 Set to 1 if git version >= 1.7.1 otherwise ''
85 # var_have_git_172 Set to 1 if git version >= 1.7.2 otherwise ''
86 # var_have_git_173 Set to 1 if git version >= 1.7.3 otherwise ''
87 # var_have_git_1710 Set to 1 if git version >= 1.7.10 otherwise ''
88 # var_have_git_185 Set to 1 if git version >= 1.8.5 otherwise ''
89 # var_window_memory Value to use for repack --window-memory=
90 # var_big_file_threshold Value to use for core.bigFileThreshold
91 # var_redelta_threshold Recompute deltas if no more than this many objs
92 # var_upload_window If not "", pack.window to use for upload-pack
93 # var_log_window_size Value to use for git-svn --log-window-size=
94 # var_utf8_locale Value to use for a UTF-8 locale if available
95 # var_xargs_r A "-r" if xargs needs it to behave correctly
96 # var_du_exclude Option to exclude PATTERN from du if available
97 # var_du_follow Option to follow command line sym links if available
98 _cfg_vars="$(get_girocco_config_pm_var_list)"
99 eval "$_cfg_vars"
100 printf '%s\n' "$_cfg_vars"
101 printf 'var_group=%s\n' "${cfg_owning_group:-$(id -gn)}"
102 _gver="$("$cfg_git_bin" version 2>/dev/null |
103 LC_ALL=C sed -ne 's/^[^0-9]*\([0-9][0-9]*\(\.[0-9][0-9]*\)*\).*$/\1/p')"
104 printf 'var_git_ver=%s\n' "$_gver"
105 printf 'var_git_exec_path="%s"\n' "$("$cfg_git_bin" --exec-path 2>/dev/null)"
106 printf 'var_sh_bin="%s"\n' "$(_fcp "${cfg_posix_sh_bin:-/bin/sh}")"
107 printf 'var_perl_bin="%s"\n' "$(_fcp "${cfg_perl_bin:-$(unset -f perl; command -v perl)}")"
108 printf 'var_gzip_bin="%s"\n' "$(_fcp "${cfg_gzip_bin:-$(unset -f gzip; command -v gzip)}")"
109 printf 'var_nc_openbsd_bin="%s"\n' "$(_fcp "${cfg_nc_openbsd_bin:-$(unset -f nc; command -v nc)}")"
110 printf 'var_have_git_171=%s\n' "$([ $(vcmp "$_gver" 1.7.1) -ge 0 ] && echo 1)"
111 printf 'var_have_git_172=%s\n' "$([ $(vcmp "$_gver" 1.7.2) -ge 0 ] && echo 1)"
112 printf 'var_have_git_173=%s\n' "$([ $(vcmp "$_gver" 1.7.3) -ge 0 ] && echo 1)"
113 printf 'var_have_git_1710=%s\n' "$([ $(vcmp "$_gver" 1.7.10) -ge 0 ] && echo 1)"
114 printf 'var_have_git_185=%s\n' "$([ $(vcmp "$_gver" 1.8.5) -ge 0 ] && echo 1)"
115 __girocco_conf="$GIROCCO_CONF"
116 [ -n "$__girocco_conf" ] || __girocco_conf="Girocco::Config"
117 [ -z "$basedir" ] || __girocco_extrainc="-I$basedir"
118 printf "var_window_memory=%s\n" \
119 "$(perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf \
120 -MGirocco::Util -e 'print calc_windowmemory')"
121 printf "var_big_file_threshold=%s\n" \
122 "$(perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf \
123 -MGirocco::Util -e 'print calc_bigfilethreshold')"
124 printf "var_redelta_threshold=%s\n" \
125 "$(perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf \
126 -MGirocco::Util -e 'print calc_redeltathreshold')"
127 if [ -n "$cfg_upload_pack_window" ] && [ "$cfg_upload_pack_window" -ge 2 ] &&
128 [ "$cfg_upload_pack_window" -le 50 ]; then
129 printf "var_upload_window=%s\n" "$cfg_upload_pack_window"
130 else
131 printf "var_upload_window=%s\n" ""
133 printf 'var_log_window_size=%s\n' "${cfg_svn_log_window_size:-250}"
134 # We parse the output of `locale -a` and select a suitable UTF-8 locale.
135 _guess_locale="$(locale -a | LC_ALL=C grep -viE '^(posix|c)(\..*)?$' |
136 LC_ALL=C grep -iE '\.utf-?8$' | LC_ALL=C sed -e 's/\.[Uu][Tt][Ff]-*8$//' |
137 LC_ALL=C sed -e '/en_US/ s/^/0 /; /en_US/ !s/^/1 /' | LC_ALL=C sort |
138 head -n 1 | LC_ALL=C cut -d ' ' -f 2)"
139 [ -z "$_guess_locale" ] || printf 'var_utf8_locale=%s.UTF-8\n' "$_guess_locale"
140 # On some broken platforms running xargs without -r and empty input runs the command
141 printf 'var_xargs_r=%s\n' "$(</dev/null command xargs echo -r)"
142 # The disk usage report produces better numbers if du has an exclude option
143 _x0="$(basename "$0")"
144 _x0="${_x0%?}?*"
145 for _duopt in --exclude -I; do
146 if _test="$(du $_duopt 's?lib.s*' $_duopt "$_x0" "$0" 2>/dev/null)" && [ -z "$_test" ]; then
147 printf 'var_du_exclude=%s\n' "$_duopt"
148 break
150 done
151 if _test="$(du -H "$0" 2>/dev/null)" && [ -n "$_test" ]; then
152 printf 'var_du_follow=%s\n' "-H"
153 break
157 # If basedir has been replaced, and shlib_vars.sh exists, get the config
158 # definitions from it rather than running Perl.
159 if [ "@basedir@" = '@'basedir'@' ] || ! [ -r "@basedir@/shlib_vars.sh" ]; then
160 # Import all the variables from Girocco::Config to the local environment,
161 eval "$(get_girocco_config_var_list)"
162 else
163 # Import the variables from shlib_vars.sh which avoids needlessly
164 # running another copy of Perl
165 . "@basedir@/shlib_vars.sh"
168 # git_add_config "some.var=value"
169 # every ' in value must be replaced with the 4-character sequence '\'' before
170 # calling this function or Git will barf. Will not be effective unless running
171 # Git version 1.7.3 or later.
172 git_add_config() {
173 GIT_CONFIG_PARAMETERS="${GIT_CONFIG_PARAMETERS:+$GIT_CONFIG_PARAMETERS }'$1'"
174 export GIT_CONFIG_PARAMETERS
177 # Make sure we have a reproducible environment by using a controlled HOME dir
178 XDG_CONFIG_HOME="$cfg_chroot/var/empty"
179 HOME="$cfg_chroot/etc/girocco"
180 TMPDIR="/tmp"
181 GIT_CONFIG_NOSYSTEM=1
182 GIT_ATTR_NOSYSTEM=1
183 GIT_NO_REPLACE_OBJECTS=1
184 GIT_TERMINAL_PROMPT=0
185 GIT_ASKPASS="$cfg_basedir/bin/git-askpass-password"
186 export XDG_CONFIG_HOME
187 export HOME
188 export TMPDIR
189 export GIT_CONFIG_NOSYSTEM
190 export GIT_ATTR_NOSYSTEM
191 export GIT_NO_REPLACE_OBJECTS
192 export GIT_TERMINAL_PROMPT
193 export GIT_ASKPASS
194 unset GIT_USER_AGENT
195 unset GIT_HTTP_USER_AGENT
196 if [ -n "$defined_cfg_git_client_ua" ]; then
197 GIT_USER_AGENT="$cfg_git_client_ua"
198 export GIT_USER_AGENT
199 GIT_HTTP_USER_AGENT="$cfg_git_client_ua"
200 export GIT_HTTP_USER_AGENT
202 unset GIT_CONFIG_PARAMETERS
203 git_add_config "core.ignoreCase=false"
204 if [ -n "$cfg_git_no_mmap" ]; then
205 # Just like compiling with NO_MMAP
206 git_add_config "core.packedGitWindowSize=1m"
207 else
208 # Always use the 32-bit default (32m) even on 64-bit to avoid memory blowout
209 git_add_config "core.packedGitWindowSize=32m"
211 [ -z "$var_big_file_threshold" ] ||
212 git_add_config "core.bigFileThreshold=$var_big_file_threshold"
214 # Make sure any sendmail.pl config is always available
215 unset SENDMAIL_PL_HOST
216 unset SENDMAIL_PL_PORT
217 unset SENDMAIL_PL_NCBIN
218 unset SENDMAIL_PL_NCOPT
219 [ -z "$cfg_sendmail_pl_host" ] || { SENDMAIL_PL_HOST="$cfg_sendmail_pl_host" && export SENDMAIL_PL_HOST; }
220 [ -z "$cfg_sendmail_pl_port" ] || { SENDMAIL_PL_PORT="$cfg_sendmail_pl_port" && export SENDMAIL_PL_PORT; }
221 [ -z "$cfg_sendmail_pl_ncbin" ] || { SENDMAIL_PL_NCBIN="$cfg_sendmail_pl_ncbin" && export SENDMAIL_PL_NCBIN; }
222 [ -z "$cfg_sendmail_pl_ncopt" ] || { SENDMAIL_PL_NCOPT="$cfg_sendmail_pl_ncopt" && export SENDMAIL_PL_NCOPT; }
224 # Set PATH and PYTHON to the values set by Config.pm, if any
225 unset PYTHON
226 [ -z "$cfg_python" ] || { PYTHON="$cfg_python" && export PYTHON; }
227 [ -z "$cfg_path" ] || { orig_path="$PATH" && PATH="$cfg_path" && export PATH; }
229 # Extra GIT variables that generally ought to be cleared, but whose clearing
230 # could potentially interfere with the correct operation of hook scripts so
231 # they are segregated into a separate function for use as appropriate
232 clean_git_env() {
233 unset GIT_ALTERNATE_OBJECT_DIRECTORIES
234 unset GIT_CONFIG
235 unset GIT_DIR
236 unset GIT_GRAFT_FILE
237 unset GIT_INDEX_FILE
238 unset GIT_OBJECT_DIRECTORY
239 unset GIT_NAMESPACE
242 # We cannot use a git() {} or nc_openbsd() {} function to redirect git
243 # and nc_openbsd to the desired executables because when using
244 # "ENV_VAR=xxx func" the various /bin/sh implementations behave in various
245 # different and unexpected ways:
246 # a) treat "ENV_VAR=xxx" like a separate, preceding "export ENV_VAR=xxx"
247 # b) treat "ENV_VAR=xxx" like a separate, prededing "ENV_VAR=xxx"
248 # c) treat "ENV_VAR=xxx" like a temporary setting only while running func
249 # None of these are good. We want a temporary "export ENV_VAR=xxx"
250 # setting only while running func which none of the /bin/sh's do.
252 # Instead we'd like to use an alias that provides the desired behavior without
253 # any of the bad (a), (b) or (c) effects.
255 # However, unfortunately, some of the crazy /bin/sh implementations do not
256 # recognize alias expansions when preceded by variable assignments!
258 # So we are left with git() {} and nc_openbsd() {} functions and in the
259 # case of git() {} we can compensate for (b) and (c) failing to export
260 # but not (a) and (b) persisting the values so the caller will simply
261 # have to beware and explicitly unset any variables that should not persist
262 # beyond the function call itself.
264 git() (
265 [ z"${GIT_DIR+set}" != z"set" ] || export GIT_DIR
266 [ z"${GIT_SSL_NO_VERIFY+set}" != z"set" ] || export GIT_SSL_NO_VERIFY
267 [ z"${GIT_TRACE_PACKET+set}" != z"set" ] || export GIT_TRACE_PACKET
268 [ z"${GIT_USER_AGENT+set}" != z"set" ] || export GIT_USER_AGENT
269 [ z"${GIT_HTTP_USER_AGENT+set}" != z"set" ] || export GIT_HTTP_USER_AGENT
270 exec "$cfg_git_bin" "$@"
273 # Since we do not yet require at least Git 1.8.5 this is a compatibility function
274 # that allows us to use git update-ref --stdin where supported and the slow shell
275 # script where not, but only the "delete" operation is currently supported.
276 git_updateref_stdin() {
277 if [ -n "$var_have_git_185" ]; then
278 git update-ref --stdin
279 else
280 while read -r _op _ref; do
281 case "$_op" in
282 delete)
283 git update-ref -d "$_ref"
286 echo "bad git_updateref_stdin op: $_op" >&2
287 exit 1
289 esac
290 done
294 # see comments for git() -- callers must explicitly export all variables
295 # intended for the commands these functions run before calling them
296 perl() { command "${var_perl_bin:-perl}" "$@"; }
297 gzip() { command "${var_gzip_bin:-gzip}" "$@"; }
299 nc_openbsd() { command "$var_nc_openbsd_bin" "$@"; }
301 list_packs() { command "$cfg_basedir/bin/list_packs" "$@"; }
303 strftime() { command "$cfg_basedir/bin/strftime" "$@"; }
305 # Some platforms' broken xargs runs the command always at least once even if
306 # there's no input unless given a special option. Automatically supply the
307 # option on those platforms by providing an xargs function.
308 xargs() { command xargs $var_xargs_r "$@"; }
310 _addrlist() {
311 _list=
312 for _addr in "$@"; do
313 [ -z "$_list" ] || _list="$_list, "
314 _list="$_list$_addr"
315 done
316 echo "$_list"
319 _sendmail() {
320 _mailer="${cfg_sendmail_bin:-/usr/sbin/sendmail}"
321 if [ -n "$cfg_sender" ]; then
322 "$_mailer" -i -f "$cfg_sender" "$@"
323 else
324 "$_mailer" -i "$@"
328 # First argument is an id WITHOUT surrounding '<' and '>' to use in a
329 # "References:" header. It may be "" to suppress the "References" header.
330 # Following arguments are just like mail function
331 mailref() {
332 _references=
333 if [ $# -ge 1 ]; then
334 _references="$1"
335 shift
337 _subject=
338 if [ "$1" = "-s" ]; then
339 shift
340 _subject="$1"
341 shift
344 echo "From: \"$cfg_name\" ($cfg_title) <$cfg_admin>"
345 echo "To: $(_addrlist "$@")"
346 [ -z "$_subject" ] || echo "Subject: $_subject"
347 echo "MIME-Version: 1.0"
348 echo "Content-Type: text/plain; charset=UTF-8"
349 echo "Content-Transfer-Encoding: 8bit"
350 [ -z "$_references" ] || echo "References: <$_references>"
351 [ -n "$cfg_suppress_x_girocco" ] || echo "X-Girocco: $cfg_gitweburl"
352 echo "Auto-Submitted: auto-generated"
353 echo ""
355 } | _sendmail "$@"
358 # Usage: mail [-s <subject>] <addr> [<addr>...]
359 mail() {
360 mailref "" "$@"
363 # bang CMD... will execute the command with well-defined failure mode;
364 # set bang_action to string of the failed action ('clone', 'update', ...);
365 # re-define the bang_trap() function to do custom cleanup before bailing out
366 bang() {
367 bang_active=1
368 bang_cmd="$*"
369 bang_errcode=0
370 if [ -n "$show_progress" ]; then
371 exec 3>&1
372 read -r bang_errcode <<-EOT || :
374 exec 4>&3 3>&1 1>&4 4>&-
375 { "$@" 3>&- || echo $? >&3; } 2>&1 | tee -i -a "$bang_log"
378 exec 3>&-
379 if [ -z "$bang_errcode" ] || [ "$bang_errcode" = "0" ]; then
380 # All right. Cool.
381 bang_active=
382 bang_cmd=
383 return;
385 else
386 if "$@" >>"$bang_log" 2>&1; then
387 # All right. Cool.
388 bang_active=
389 bang_cmd=
390 return;
391 else
392 bang_errcode="$?"
395 bang_failed
398 bang_failed() {
399 bang_active=
400 unset GIT_DIR
401 >.banged
402 cat "$bang_log" >.banglog
403 echo "" >>.banglog
404 echo "$bang_cmd failed with error code $bang_errcode" >>.banglog
405 if [ -n "$show_progress" ]; then
406 echo ""
407 echo "$bang_cmd failed with error code $bang_errcode"
409 if [ -e .bangagain ]; then
410 git config --remove-section girocco.bang 2>/dev/null || :
411 rm -f .bangagain
413 bangcount="$(git config --int girocco.bang.count 2>/dev/null)" || :
414 bangcount=$(( ${bangcount:-0} + 1 ))
415 git config --int girocco.bang.count $bangcount
416 if [ $bangcount -eq 1 ]; then
417 git config girocco.bang.firstfail "$(TZ=UTC date "+%Y-%m-%d %T UTC")"
419 if [ $bangcount -ge $cfg_min_mirror_failure_message_count ] &&
420 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" != "true" ] &&
421 ! check_interval "girocco.bang.firstfail" $cfg_min_mirror_failure_message_interval; then
422 bangmailok="$(git config --bool gitweb.statusupdates 2>/dev/null || echo true)"
423 bangaddrs=
424 [ "$bangmailok" = "false" ] || [ -z "$mail" ] || bangaddrs="$mail"
425 [ -z "$cfg_admincc" ] || [ "$cfg_admincc" = "0" ] || [ -z "$cfg_admin" ] ||
426 if [ -z "$bangaddrs" ]; then bangaddrs="$cfg_admin"; else bangaddrs="$bangaddrs,$cfg_admin"; fi
427 rsubj=
428 [ $bangcount -le 1 ] || rsubj=" repeatedly"
429 [ -z "$bangaddrs" ] ||
431 echo "$bang_cmd failed with error code $bang_errcode"
432 echo ""
433 rsubj=
434 if [ $bangcount -gt 1 ]; then
435 echo "$bangcount consecutive update failures have occurred since $(config_get girocco.bang.firstfail)"
436 echo ""
438 echo "you will not receive any more notifications until recovery"
439 echo "this status message may be disabled on the project admin page"
440 echo ""
441 echo "Log follows:"
442 echo ""
443 cat "$bang_log"
444 } | mailref "update@$cfg_gitweburl/$proj.git" -s "[$cfg_name] $proj $bang_action failed$rsubj" "$bangaddrs"
445 git config --bool girocco.bang.messagesent true
447 bangthrottle=
448 [ $bangcount -lt 15 ] ||
449 check_interval "girocco.bang.firstfail" $(( $cfg_min_mirror_interval * 3 / 2 )) ||
450 bangthrottle=1
451 bang_trap $bangthrottle
452 [ -n "$bang_errcode" ] && [ "$bang_errcode" != "0" ] || bang_errcode=1
453 exit $bang_errcode
456 # bang_eval CMD... will evaluate the command with well-defined failure mode;
457 # Identical to bang CMD... except the command is eval'd instead of executed.
458 bang_eval() {
459 bang eval "$*"
462 # Default bang settings:
463 bang_setup() {
464 bang_active=
465 bang_action="lame_programmer"
466 bang_trap() { :; }
467 bang_tmpdir="${TMPDIR:-/tmp}"
468 bang_tmpdir="${bang_tmpdir%/}"
469 bang_log="$(mktemp "${bang_tmpdir:-/tmp}/repomgr-XXXXXX")"
470 is_git_dir . || {
471 echo "bang_setup called with current directory not a git directory" >&2
472 exit 1
474 trap 'rm -f "$bang_log"' EXIT
475 trap '[ -z "$bang_active" ] || { bang_errcode=130; bang_failed; }; exit 130' INT
476 trap '[ -z "$bang_active" ] || { bang_errcode=143; bang_failed; }; exit 143' TERM
479 # Remove banged status
480 bang_reset() {
481 rm -f .banged .bangagain .banglog
482 git config --remove-section girocco.bang 2>/dev/null || :
485 # Check to see if banged status
486 is_banged() {
487 [ -e .banged ]
490 # Check to see if banged message was sent
491 was_banged_message_sent() {
492 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" = "true" ]
495 # Progress report - if show_progress is set, shows the given message.
496 progress() {
497 [ -z "$show_progress" ] || echo "$*"
500 # Project config accessors; must be run in project directory
501 config_get() {
502 case "$1" in
503 *.*)
504 git config "$1";;
506 git config "gitweb.$1";;
507 esac
510 config_set() {
511 git config "gitweb.$1" "$2" && chgrp $var_group config && chmod g+w config
514 config_set_raw() {
515 git config "$1" "$2" && chgrp $var_group config && chmod g+w config
518 config_get_date_seconds() {
519 _dt="$(config_get "$1")" || :
520 [ -n "$_dt" ] || return 1
521 _ds="$(perl -I@basedir@ -MGirocco::Util -e "print parse_any_date('$_dt')")"
522 [ -n "$_ds" ] || return 1
523 echo "$_ds"
526 # Tool for checking whether given number of seconds has not passed yet
527 check_interval() {
528 os="$(config_get_date_seconds "$1")" || return 1
529 ns="$(date +%s)"
530 [ $ns -lt $(($os+$2)) ]
533 # Check if we are running with effective root permissions
534 is_root() {
535 [ "$(id -u 2>/dev/null)" = "0" ]
538 # Check to see if the single argument is a Git directory
539 is_git_dir() {
540 # Just like Git's test except we ignore GIT_OBJECT_DIRECTORY
541 # And we are slightly more picky (must be refs/.+ not refs/.*)
542 [ -d "$1/objects" ] && [ -x "$1/objects" ] || return 1
543 [ -d "$1/refs" ] && [ -x "$1/refs" ] || return 1
544 if [ -L "$1/HEAD" ]; then
545 _hr="$(readlink "$1/HEAD")"
546 case "$_hr" in "refs/"?*) :;; *) return 1;; esac
548 [ -f "$1/HEAD" ] && [ -r "$1/HEAD" ] || return 1
549 read -r _hr <"$1/HEAD" || return 1
550 case "$_hr" in
551 $octet20 | ref:refs/?*)
552 return 0;;
553 ref:*)
554 _hr="${_hr##ref:*[ $tab]}"
555 case "$_hr" in "refs/"?*) return 0;; esac
556 esac
557 return 1
560 # List all Git repositories, with given prefix if specified, one-per-line
561 # All project names starting with _ are always excluded from the result
562 get_repo_list() {
563 if [ -n "$1" ]; then
564 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group | LC_ALL=C grep "^$1"
565 else
566 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group
567 fi | while IFS=: read name id; do
568 [ $id -lt 65536 ] || case "$name" in _*) :;; ?*) echo "$name"; esac
569 done
572 # Return success if the given project name has any forks
573 has_forks() {
574 _prj="${1%.git}"
575 [ -n "$_prj" ] || return 1
576 [ -d "$cfg_reporoot/$_prj" ] || return 1
577 is_git_dir "$cfg_reporoot/$_prj.git" || return 1
578 test $(get_repo_list "$_prj/[^/:][^/:]*:" | LC_ALL=C wc -l) -gt 0
581 # returns empty string and error for empty string otherwise one of
582 # m => normal Git mirror
583 # s => mirror from svn source
584 # d => mirror from darcs source
585 # b => mirror from bzr source
586 # h => mirror from hg source
587 # w => mirror from mediawiki source
588 # f => mirror from other fast-import source
589 # note that if the string is non-empty and none of s, d, b or h match the
590 # return will always be type m regardless of whether it's a valid Git URL
591 get_url_mirror_type() {
592 case "$1" in
594 return 1
596 svn://* | svn+http://* | svn+https://* | svn+file://* | svn+ssh://*)
597 echo 's'
599 darcs://*)
600 echo 'd'
602 bzr://*)
603 echo 'b'
605 hg+http://* | hg+https://* | hg+file://* | hg+ssh://* | hg::*)
606 echo 'h'
608 mediawiki::*)
609 echo 'w'
612 echo 'm'
614 esac
615 return 0
618 # returns false for empty string
619 # returns true if the passed in url is a mirror using git fast-import
620 is_gfi_mirror_url() {
621 [ -n "$1" ] || return 1
622 case "$(get_url_mirror_type "$1" 2>/dev/null || :)" in
623 d|b|h|w|f)
624 # darcs, bzr, hg and mediawiki mirrors use git fast-import
625 # and so do generic "f" fast-import mirrors
626 return 0
629 # Don't think git-svn currently uses git fast-import
630 # And Git mirrors certainly do not
631 return 1
633 esac
634 # assume it does not use git fast-import
635 return 1
638 # returns false for empty string
639 # returns true if the passed in url is a mirror using git-svn
640 is_svn_mirror_url() {
641 [ -n "$1" ] || return 1
642 [ "$(get_url_mirror_type "$1" 2>/dev/null || :)" = "s" ]
645 # returns mirror url for gitweb.baseurl of git directory
646 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
647 # will fail if the directory does not have .nofetch and gitweb.baseurl
648 # comes back empty -- otherwise .nofetch directories succeed with a "" return
649 # automatically strips any leading "disabled " prefix before returning result
650 get_mirror_url() {
651 _gitdir="${1:-.}"
652 # always return empty for non-mirrors
653 ! [ -e "$_gitdir/.nofetch" ] || return 0
654 _url="$(GIT_DIR="$_gitdir" config_get baseurl 2>/dev/null)" || :
655 _url="${_url##* }"
656 [ -n "$_url" ] || return 1
657 printf '%s\n' "$_url"
658 return 0
661 # returns get_url_mirror_type for gitweb.baseurl of git directory
662 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
663 # will fail if the directory does not have .nofetch and gitweb.baseurl
664 # comes back empty -- otherwise .nofetch directories succeed with a "" return
665 # automatically strips any leading "disabled " prefix before testing
666 get_mirror_type() {
667 _url="$(get_mirror_url "$@")" || return 1
668 [ -n "$_url" ] || return 0
669 get_url_mirror_type "$_url"
672 # returns true if the passed in git dir (defaults to ".") is a mirror using git fast-import
673 is_gfi_mirror() {
674 _url="$(get_mirror_url "$@")" || return 1
675 is_gfi_mirror_url "$_url"
678 # returns true if the passed in git dir (defaults to ".") is a mirror using git-svn
679 is_svn_mirror() {
680 _url="$(get_mirror_url "$@")" || return 1
681 is_svn_mirror_url "$_url"
684 # current directory must already be set to Git repository
685 # if girocco.headok is already true succeeds without doing anything
686 # if rev-parse --verify HEAD succeeds sets headok=true and succeeds
687 # otherwise tries to set HEAD to a symbolic ref to refs/heads/master
688 # then refs/heads/trunk and finally the first top-level head from
689 # refs/heads/* (i.e. only two slashes in the name) and finally any
690 # existing refs/heads. The first one to succeed wins and sets headok=true
691 # and then a successful exit. Otherwise headok is left unset with a failure exit
692 # We use the girocco.headok flag to make sure we only force a valid HEAD symref
693 # when the repository is being set up -- if the HEAD is later deleted (through
694 # a push or fetch --prune) that's no longer our responsibility to fix
695 check_and_set_head() {
696 [ "$(git config --bool girocco.headok 2>/dev/null || :)" != "true" ] || return 0
697 if git rev-parse --verify --quiet HEAD >/dev/null; then
698 git config --bool girocco.headok true
699 return 0
701 for _hr in refs/heads/master refs/heads/trunk; do
702 if git rev-parse --verify --quiet "$_hr"; then
703 _update_head_symref "$_hr"
704 return 0
706 done
707 git for-each-ref --format="%(refname)" refs/heads 2>/dev/null |
708 while read -r _hr; do
709 case "${_hr#refs/heads/}" in */*) :;; *)
710 _update_head_symref "$_hr"
711 exit 1 # exit subshell created by "|"
712 esac
713 done || return 0
714 _hr="$(git for-each-ref --format="%(refname)" refs/heads 2>/dev/null | head -n 1)" || :
715 if [ -n "$_hr" ]; then
716 _update_head_symref "$_hr"
717 return 0
719 return 1
721 _update_head_symref() {
722 git symbolic-ref HEAD "$1"
723 git config --bool girocco.headok true
724 ! [ -d htmlcache ] || { >htmlcache/changed; } 2>/dev/null || :
727 # A well-known UTF-8 locale is required for some of the fast-import providers
728 # in order to avoid mangling characters. Ideally we could use "POSIX.UTF-8"
729 # but that is not reliably UTF-8 but rather usually US-ASCII.
730 # We parse the output of `locale -a` and select a suitable UTF-8 locale at
731 # install time and store that in $var_utf8_locale if one is found.
732 # If we cannot find one in the `locale -a` output then we just use a well-known
733 # UTF-8 locale and hope for the best. We set LC_ALL to our choice and export
734 # it. We only set this temporarily when running the fast-import providers.
735 set_utf8_locale() {
736 LC_ALL="${var_utf8_locale:-en_US.UTF-8}"
737 export LC_ALL
740 # hg-fast-export | git fast-import with error handling in current directory GIT_DIR
741 git_hg_fetch() (
742 set_utf8_locale
743 _python="${PYTHON:-python}"
744 rm -f hg2git-marks.old hg2git-marks.new
745 if [ -f hg2git-marks ] && [ -s hg2git-marks ]; then
746 LC_ALL=C sed 's/^:\([^ ][^ ]*\) \([^ ][^ ]*\)$/\2 \1/' <hg2git-marks | {
747 if [ -n "$var_have_git_185" ]; then
748 git cat-file --batch-check=':%(rest) %(objectname)'
749 else
750 LC_ALL=C sed 's/^\([^ ][^ ]*\) \([^ ][^ ]*\)$/:\2 \1/'
752 } | LC_ALL=C sed '/ missing$/d' >hg2git-marks.old
753 if [ -n "$var_have_git_171" ] &&
754 git rev-parse --quiet --verify refs/notes/hg >/dev/null; then
755 if [ -z "$var_have_git_185" ] ||
756 ! LC_ALL=C cmp -s hg2git-marks hg2git-marks.old; then
757 _nm='hg-fast-export'
758 GIT_AUTHOR_NAME="$_nm"
759 GIT_COMMITTER_NAME="$_nm"
760 GIT_AUTHOR_EMAIL="$_nm"
761 GIT_COMMITTER_EMAIL="$_nm"
762 export GIT_AUTHOR_NAME
763 export GIT_COMMITTER_NAME
764 export GIT_AUTHOR_EMAIL
765 export GIT_COMMITTER_EMAIL
766 git notes --ref=refs/notes/hg prune
767 unset GIT_AUTHOR_NAME
768 unset GIT_COMMITTER_NAME
769 unset GIT_AUTHOR_EMAIL
770 unset GIT_COMMITTER_EMAIL
773 else
774 >hg2git-marks.old
776 _err1=
777 _err2=
778 exec 3>&1
779 { read -r _err1 || :; read -r _err2 || :; } <<-EOT
781 exec 4>&3 3>&1 1>&4 4>&-
783 _e1=0
784 _af="$(git config hg.authorsfile)" || :
785 _cmd='GIT_DIR="$(pwd)" "$_python" "$cfg_basedir/bin/hg-fast-export.py" \
786 --repo "$(pwd)/repo.hg" \
787 --marks "$(pwd)/hg2git-marks.old" \
788 --mapping "$(pwd)/hg2git-mapping" \
789 --heads "$(pwd)/hg2git-heads" \
790 --status "$(pwd)/hg2git-state" \
791 -U unknown --force --flatten --hg-hash'
792 [ -z "$_af" ] || _cmd="$_cmd"' --authors "$_af"'
793 eval "$_cmd" 3>&- || _e1=$?
794 echo $_e1 >&3
797 _e2=0
798 git fast-import \
799 --import-marks="$(pwd)/hg2git-marks.old" \
800 --export-marks="$(pwd)/hg2git-marks.new" \
801 --export-pack-edges="$(pwd)/gfi-packs" \
802 --force 3>&- || _e2=$?
803 echo $_e2 >&3
807 exec 3>&-
808 [ "$_err1" = 0 ] && [ "$_err2" = 0 ] || return 1
809 mv -f hg2git-marks.new hg2git-marks
810 rm -f hg2git-marks.old
811 git for-each-ref --format='%(refname) %(objectname)' refs/heads |
812 LC_ALL=C sed -e 's,^refs/heads/,:,' >hg2git-heads