gc.sh: do not lose a detached HEAD
[girocco/readme.git] / shlib.sh
blobf47d13410297854c73cae74bbbf9d5d3c9708f6c
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 printf %s -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_PAGER="cat"
186 PAGER="cat"
187 GIT_ASKPASS="$cfg_basedir/bin/git-askpass-password"
188 GIT_SVN_NOTTY=1
189 export XDG_CONFIG_HOME
190 export HOME
191 export TMPDIR
192 export GIT_CONFIG_NOSYSTEM
193 export GIT_ATTR_NOSYSTEM
194 export GIT_NO_REPLACE_OBJECTS
195 export GIT_TERMINAL_PROMPT
196 export GIT_PAGER
197 export PAGER
198 export GIT_ASKPASS
199 export GIT_SVN_NOTTY
200 unset GIT_USER_AGENT
201 unset GIT_HTTP_USER_AGENT
202 if [ -n "$defined_cfg_git_client_ua" ]; then
203 GIT_USER_AGENT="$cfg_git_client_ua"
204 export GIT_USER_AGENT
205 GIT_HTTP_USER_AGENT="$cfg_git_client_ua"
206 export GIT_HTTP_USER_AGENT
208 unset GIT_CONFIG_PARAMETERS
209 git_add_config "core.ignoreCase=false"
210 git_add_config "core.pager=cat"
211 if [ -n "$cfg_git_no_mmap" ]; then
212 # Just like compiling with NO_MMAP
213 git_add_config "core.packedGitWindowSize=1m"
214 else
215 # Always use the 32-bit default (32m) even on 64-bit to avoid memory blowout
216 git_add_config "core.packedGitWindowSize=32m"
218 [ -z "$var_big_file_threshold" ] ||
219 git_add_config "core.bigFileThreshold=$var_big_file_threshold"
221 # Make sure any sendmail.pl config is always available
222 unset SENDMAIL_PL_HOST
223 unset SENDMAIL_PL_PORT
224 unset SENDMAIL_PL_NCBIN
225 unset SENDMAIL_PL_NCOPT
226 [ -z "$cfg_sendmail_pl_host" ] || { SENDMAIL_PL_HOST="$cfg_sendmail_pl_host" && export SENDMAIL_PL_HOST; }
227 [ -z "$cfg_sendmail_pl_port" ] || { SENDMAIL_PL_PORT="$cfg_sendmail_pl_port" && export SENDMAIL_PL_PORT; }
228 [ -z "$cfg_sendmail_pl_ncbin" ] || { SENDMAIL_PL_NCBIN="$cfg_sendmail_pl_ncbin" && export SENDMAIL_PL_NCBIN; }
229 [ -z "$cfg_sendmail_pl_ncopt" ] || { SENDMAIL_PL_NCOPT="$cfg_sendmail_pl_ncopt" && export SENDMAIL_PL_NCOPT; }
231 # Set PATH and PYTHON to the values set by Config.pm, if any
232 unset PYTHON
233 [ -z "$cfg_python" ] || { PYTHON="$cfg_python" && export PYTHON; }
234 [ -z "$cfg_path" ] || { orig_path="$PATH" && PATH="$cfg_path" && export PATH; }
236 # Extra GIT variables that generally ought to be cleared, but whose clearing
237 # could potentially interfere with the correct operation of hook scripts so
238 # they are segregated into a separate function for use as appropriate
239 clean_git_env() {
240 unset GIT_ALTERNATE_OBJECT_DIRECTORIES
241 unset GIT_CONFIG
242 unset GIT_DIR
243 unset GIT_GRAFT_FILE
244 unset GIT_INDEX_FILE
245 unset GIT_OBJECT_DIRECTORY
246 unset GIT_NAMESPACE
249 # We cannot use a git() {} or nc_openbsd() {} function to redirect git
250 # and nc_openbsd to the desired executables because when using
251 # "ENV_VAR=xxx func" the various /bin/sh implementations behave in various
252 # different and unexpected ways:
253 # a) treat "ENV_VAR=xxx" like a separate, preceding "export ENV_VAR=xxx"
254 # b) treat "ENV_VAR=xxx" like a separate, prededing "ENV_VAR=xxx"
255 # c) treat "ENV_VAR=xxx" like a temporary setting only while running func
256 # None of these are good. We want a temporary "export ENV_VAR=xxx"
257 # setting only while running func which none of the /bin/sh's do.
259 # Instead we'd like to use an alias that provides the desired behavior without
260 # any of the bad (a), (b) or (c) effects.
262 # However, unfortunately, some of the crazy /bin/sh implementations do not
263 # recognize alias expansions when preceded by variable assignments!
265 # So we are left with git() {} and nc_openbsd() {} functions and in the
266 # case of git() {} we can compensate for (b) and (c) failing to export
267 # but not (a) and (b) persisting the values so the caller will simply
268 # have to beware and explicitly unset any variables that should not persist
269 # beyond the function call itself.
271 git() (
272 [ z"${GIT_DIR+set}" != z"set" ] || export GIT_DIR
273 [ z"${GIT_SSL_NO_VERIFY+set}" != z"set" ] || export GIT_SSL_NO_VERIFY
274 [ z"${GIT_TRACE_PACKET+set}" != z"set" ] || export GIT_TRACE_PACKET
275 [ z"${GIT_USER_AGENT+set}" != z"set" ] || export GIT_USER_AGENT
276 [ z"${GIT_HTTP_USER_AGENT+set}" != z"set" ] || export GIT_HTTP_USER_AGENT
277 exec "$cfg_git_bin" "$@"
280 # Since we do not yet require at least Git 1.8.5 this is a compatibility function
281 # that allows us to use git update-ref --stdin where supported and the slow shell
282 # script where not, but only the "delete" operation is currently supported.
283 git_updateref_stdin() {
284 if [ -n "$var_have_git_185" ]; then
285 git update-ref --stdin
286 else
287 while read -r _op _ref; do
288 case "$_op" in
289 delete)
290 git update-ref -d "$_ref"
293 echo "bad git_updateref_stdin op: $_op" >&2
294 exit 1
296 esac
297 done
301 # see comments for git() -- callers must explicitly export all variables
302 # intended for the commands these functions run before calling them
303 perl() { command "${var_perl_bin:-perl}" "$@"; }
304 gzip() { command "${var_gzip_bin:-gzip}" "$@"; }
306 nc_openbsd() { command "$var_nc_openbsd_bin" "$@"; }
308 list_packs() { command "$cfg_basedir/bin/list_packs" "$@"; }
310 strftime() { command "$cfg_basedir/bin/strftime" "$@"; }
312 # Some platforms' broken xargs runs the command always at least once even if
313 # there's no input unless given a special option. Automatically supply the
314 # option on those platforms by providing an xargs function.
315 xargs() { command xargs $var_xargs_r "$@"; }
317 _addrlist() {
318 _list=
319 for _addr in "$@"; do
320 [ -z "$_list" ] || _list="$_list, "
321 _list="$_list$_addr"
322 done
323 echo "$_list"
326 _sendmail() {
327 _mailer="${cfg_sendmail_bin:-/usr/sbin/sendmail}"
328 if [ -n "$cfg_sender" ]; then
329 "$_mailer" -i -f "$cfg_sender" "$@"
330 else
331 "$_mailer" -i "$@"
335 # First argument is an id WITHOUT surrounding '<' and '>' to use in a
336 # "References:" header. It may be "" to suppress the "References" header.
337 # Following arguments are just like mail function
338 mailref() {
339 _references=
340 if [ $# -ge 1 ]; then
341 _references="$1"
342 shift
344 _subject=
345 if [ "$1" = "-s" ]; then
346 shift
347 _subject="$1"
348 shift
351 echo "From: \"$cfg_name\" ($cfg_title) <$cfg_admin>"
352 echo "To: $(_addrlist "$@")"
353 [ -z "$_subject" ] || echo "Subject: $_subject"
354 echo "MIME-Version: 1.0"
355 echo "Content-Type: text/plain; charset=UTF-8"
356 echo "Content-Transfer-Encoding: 8bit"
357 [ -z "$_references" ] || echo "References: <$_references>"
358 [ -n "$cfg_suppress_x_girocco" ] || echo "X-Girocco: $cfg_gitweburl"
359 echo "Auto-Submitted: auto-generated"
360 echo ""
362 } | _sendmail "$@"
365 # Usage: mail [-s <subject>] <addr> [<addr>...]
366 mail() {
367 mailref "" "$@"
370 # bang CMD... will execute the command with well-defined failure mode;
371 # set bang_action to string of the failed action ('clone', 'update', ...);
372 # re-define the bang_trap() function to do custom cleanup before bailing out
373 bang() {
374 bang_active=1
375 bang_cmd="$*"
376 bang_errcode=0
377 if [ -n "$show_progress" ]; then
378 exec 3>&1
379 read -r bang_errcode <<-EOT || :
381 exec 4>&3 3>&1 1>&4 4>&-
382 { "$@" 3>&- || echo $? >&3; } 2>&1 | tee -i -a "$bang_log"
385 exec 3>&-
386 if [ -z "$bang_errcode" ] || [ "$bang_errcode" = "0" ]; then
387 # All right. Cool.
388 bang_active=
389 bang_cmd=
390 return;
392 else
393 if "$@" >>"$bang_log" 2>&1; then
394 # All right. Cool.
395 bang_active=
396 bang_cmd=
397 return;
398 else
399 bang_errcode="$?"
402 bang_failed
405 bang_failed() {
406 bang_active=
407 unset GIT_DIR
408 >.banged
409 cat "$bang_log" >.banglog
410 echo "" >>.banglog
411 echo "$bang_cmd failed with error code $bang_errcode" >>.banglog
412 if [ -n "$show_progress" ]; then
413 echo ""
414 echo "$bang_cmd failed with error code $bang_errcode"
416 if [ -e .bangagain ]; then
417 git config --remove-section girocco.bang 2>/dev/null || :
418 rm -f .bangagain
420 bangcount="$(git config --int girocco.bang.count 2>/dev/null)" || :
421 bangcount=$(( ${bangcount:-0} + 1 ))
422 git config --int girocco.bang.count $bangcount
423 if [ $bangcount -eq 1 ]; then
424 git config girocco.bang.firstfail "$(TZ=UTC date "+%Y-%m-%d %T UTC")"
426 if [ $bangcount -ge $cfg_min_mirror_failure_message_count ] &&
427 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" != "true" ] &&
428 ! check_interval "girocco.bang.firstfail" $cfg_min_mirror_failure_message_interval; then
429 bangmailok="$(git config --bool gitweb.statusupdates 2>/dev/null || echo true)"
430 bangaddrs=
431 [ "$bangmailok" = "false" ] || [ -z "$mail" ] || bangaddrs="$mail"
432 [ -z "$cfg_admincc" ] || [ "$cfg_admincc" = "0" ] || [ -z "$cfg_admin" ] ||
433 if [ -z "$bangaddrs" ]; then bangaddrs="$cfg_admin"; else bangaddrs="$bangaddrs,$cfg_admin"; fi
434 rsubj=
435 [ $bangcount -le 1 ] || rsubj=" repeatedly"
436 [ -z "$bangaddrs" ] ||
438 echo "$bang_cmd failed with error code $bang_errcode"
439 echo ""
440 rsubj=
441 if [ $bangcount -gt 1 ]; then
442 echo "$bangcount consecutive update failures have occurred since $(config_get girocco.bang.firstfail)"
443 echo ""
445 echo "you will not receive any more notifications until recovery"
446 echo "this status message may be disabled on the project admin page"
447 echo ""
448 echo "Log follows:"
449 echo ""
450 cat "$bang_log"
451 } | mailref "update@$cfg_gitweburl/$proj.git" -s "[$cfg_name] $proj $bang_action failed$rsubj" "$bangaddrs"
452 git config --bool girocco.bang.messagesent true
454 bangthrottle=
455 [ $bangcount -lt 15 ] ||
456 check_interval "girocco.bang.firstfail" $(( $cfg_min_mirror_interval * 3 / 2 )) ||
457 bangthrottle=1
458 bang_trap $bangthrottle
459 [ -n "$bang_errcode" ] && [ "$bang_errcode" != "0" ] || bang_errcode=1
460 exit $bang_errcode
463 # bang_eval CMD... will evaluate the command with well-defined failure mode;
464 # Identical to bang CMD... except the command is eval'd instead of executed.
465 bang_eval() {
466 bang eval "$*"
469 # Default bang settings:
470 bang_setup() {
471 bang_active=
472 bang_action="lame_programmer"
473 bang_trap() { :; }
474 bang_tmpdir="${TMPDIR:-/tmp}"
475 bang_tmpdir="${bang_tmpdir%/}"
476 bang_log="$(mktemp "${bang_tmpdir:-/tmp}/repomgr-XXXXXX")"
477 is_git_dir . || {
478 echo "bang_setup called with current directory not a git directory" >&2
479 exit 1
481 trap 'rm -f "$bang_log"' EXIT
482 trap '[ -z "$bang_active" ] || { bang_errcode=130; bang_failed; }; exit 130' INT
483 trap '[ -z "$bang_active" ] || { bang_errcode=143; bang_failed; }; exit 143' TERM
486 # Remove banged status
487 bang_reset() {
488 rm -f .banged .bangagain .banglog
489 git config --remove-section girocco.bang 2>/dev/null || :
492 # Check to see if banged status
493 is_banged() {
494 [ -e .banged ]
497 # Check to see if banged message was sent
498 was_banged_message_sent() {
499 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" = "true" ]
502 # Progress report - if show_progress is set, shows the given message.
503 progress() {
504 [ -z "$show_progress" ] || echo "$*"
507 # Project config accessors; must be run in project directory
508 config_get() {
509 case "$1" in
510 *.*)
511 git config "$1";;
513 git config "gitweb.$1";;
514 esac
517 config_set() {
518 git config "gitweb.$1" "$2" && chgrp $var_group config && chmod g+w config
521 config_set_raw() {
522 git config "$1" "$2" && chgrp $var_group config && chmod g+w config
525 config_get_date_seconds() {
526 _dt="$(config_get "$1")" || :
527 [ -n "$_dt" ] || return 1
528 _ds="$(perl -I@basedir@ -MGirocco::Util -e "print parse_any_date('$_dt')")"
529 [ -n "$_ds" ] || return 1
530 echo "$_ds"
533 # Tool for checking whether given number of seconds has not passed yet
534 check_interval() {
535 os="$(config_get_date_seconds "$1")" || return 1
536 ns="$(date +%s)"
537 [ $ns -lt $(($os+$2)) ]
540 # Check if we are running with effective root permissions
541 is_root() {
542 [ "$(id -u 2>/dev/null)" = "0" ]
545 # Check to see if the single argument is a Git directory
546 is_git_dir() {
547 # Just like Git's test except we ignore GIT_OBJECT_DIRECTORY
548 # And we are slightly more picky (must be refs/.+ not refs/.*)
549 [ -d "$1/objects" ] && [ -x "$1/objects" ] || return 1
550 [ -d "$1/refs" ] && [ -x "$1/refs" ] || return 1
551 if [ -L "$1/HEAD" ]; then
552 _hr="$(readlink "$1/HEAD")"
553 case "$_hr" in "refs/"?*) :;; *) return 1;; esac
555 [ -f "$1/HEAD" ] && [ -r "$1/HEAD" ] || return 1
556 read -r _hr <"$1/HEAD" || return 1
557 case "$_hr" in
558 $octet20 | ref:refs/?*)
559 return 0;;
560 ref:*)
561 _hr="${_hr##ref:*[ $tab]}"
562 case "$_hr" in "refs/"?*) return 0;; esac
563 esac
564 return 1
567 # List all Git repositories, with given prefix if specified, one-per-line
568 # All project names starting with _ are always excluded from the result
569 get_repo_list() {
570 if [ -n "$1" ]; then
571 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group | LC_ALL=C grep "^$1"
572 else
573 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group
574 fi | while IFS=: read name id; do
575 [ $id -lt 65536 ] || case "$name" in _*) :;; ?*) echo "$name"; esac
576 done
579 # Return success if the given project name has any forks
580 has_forks() {
581 _prj="${1%.git}"
582 [ -n "$_prj" ] || return 1
583 [ -d "$cfg_reporoot/$_prj" ] || return 1
584 is_git_dir "$cfg_reporoot/$_prj.git" || return 1
585 test $(get_repo_list "$_prj/[^/:][^/:]*:" | LC_ALL=C wc -l) -gt 0
588 # returns empty string and error for empty string otherwise one of
589 # m => normal Git mirror
590 # s => mirror from svn source
591 # d => mirror from darcs source
592 # b => mirror from bzr source
593 # h => mirror from hg source
594 # w => mirror from mediawiki source
595 # f => mirror from other fast-import source
596 # note that if the string is non-empty and none of s, d, b or h match the
597 # return will always be type m regardless of whether it's a valid Git URL
598 get_url_mirror_type() {
599 case "$1" in
601 return 1
603 svn://* | svn+http://* | svn+https://* | svn+file://* | svn+ssh://*)
604 echo 's'
606 darcs://*)
607 echo 'd'
609 bzr://*)
610 echo 'b'
612 hg+http://* | hg+https://* | hg+file://* | hg+ssh://* | hg::*)
613 echo 'h'
615 mediawiki::*)
616 echo 'w'
619 echo 'm'
621 esac
622 return 0
625 # returns false for empty string
626 # returns true if the passed in url is a mirror using git fast-import
627 is_gfi_mirror_url() {
628 [ -n "$1" ] || return 1
629 case "$(get_url_mirror_type "$1" 2>/dev/null || :)" in
630 d|b|h|w|f)
631 # darcs, bzr, hg and mediawiki mirrors use git fast-import
632 # and so do generic "f" fast-import mirrors
633 return 0
636 # Don't think git-svn currently uses git fast-import
637 # And Git mirrors certainly do not
638 return 1
640 esac
641 # assume it does not use git fast-import
642 return 1
645 # returns false for empty string
646 # returns true if the passed in url is a mirror using git-svn
647 is_svn_mirror_url() {
648 [ -n "$1" ] || return 1
649 [ "$(get_url_mirror_type "$1" 2>/dev/null || :)" = "s" ]
652 # returns mirror url for gitweb.baseurl of git directory
653 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
654 # will fail if the directory does not have .nofetch and gitweb.baseurl
655 # comes back empty -- otherwise .nofetch directories succeed with a "" return
656 # automatically strips any leading "disabled " prefix before returning result
657 get_mirror_url() {
658 _gitdir="${1:-.}"
659 # always return empty for non-mirrors
660 ! [ -e "$_gitdir/.nofetch" ] || return 0
661 _url="$(GIT_DIR="$_gitdir" config_get baseurl 2>/dev/null)" || :
662 _url="${_url##* }"
663 [ -n "$_url" ] || return 1
664 printf '%s\n' "$_url"
665 return 0
668 # returns get_url_mirror_type for gitweb.baseurl of git directory
669 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
670 # will fail if the directory does not have .nofetch and gitweb.baseurl
671 # comes back empty -- otherwise .nofetch directories succeed with a "" return
672 # automatically strips any leading "disabled " prefix before testing
673 get_mirror_type() {
674 _url="$(get_mirror_url "$@")" || return 1
675 [ -n "$_url" ] || return 0
676 get_url_mirror_type "$_url"
679 # returns true if the passed in git dir (defaults to ".") is a mirror using git fast-import
680 is_gfi_mirror() {
681 _url="$(get_mirror_url "$@")" || return 1
682 is_gfi_mirror_url "$_url"
685 # returns true if the passed in git dir (defaults to ".") is a mirror using git-svn
686 is_svn_mirror() {
687 _url="$(get_mirror_url "$@")" || return 1
688 is_svn_mirror_url "$_url"
691 # current directory must already be set to Git repository
692 # if girocco.headok is already true succeeds without doing anything
693 # if rev-parse --verify HEAD succeeds sets headok=true and succeeds
694 # otherwise tries to set HEAD to a symbolic ref to refs/heads/master
695 # then refs/heads/trunk and finally the first top-level head from
696 # refs/heads/* (i.e. only two slashes in the name) and finally any
697 # existing refs/heads. The first one to succeed wins and sets headok=true
698 # and then a successful exit. Otherwise headok is left unset with a failure exit
699 # We use the girocco.headok flag to make sure we only force a valid HEAD symref
700 # when the repository is being set up -- if the HEAD is later deleted (through
701 # a push or fetch --prune) that's no longer our responsibility to fix
702 check_and_set_head() {
703 [ "$(git config --bool girocco.headok 2>/dev/null || :)" != "true" ] || return 0
704 if git rev-parse --verify --quiet HEAD >/dev/null; then
705 git config --bool girocco.headok true
706 return 0
708 for _hr in refs/heads/master refs/heads/trunk; do
709 if git rev-parse --verify --quiet "$_hr"; then
710 _update_head_symref "$_hr"
711 return 0
713 done
714 git for-each-ref --format="%(refname)" refs/heads 2>/dev/null |
715 while read -r _hr; do
716 case "${_hr#refs/heads/}" in */*) :;; *)
717 _update_head_symref "$_hr"
718 exit 1 # exit subshell created by "|"
719 esac
720 done || return 0
721 _hr="$(git for-each-ref --format="%(refname)" refs/heads 2>/dev/null | head -n 1)" || :
722 if [ -n "$_hr" ]; then
723 _update_head_symref "$_hr"
724 return 0
726 return 1
728 _update_head_symref() {
729 git symbolic-ref HEAD "$1"
730 git config --bool girocco.headok true
731 ! [ -d htmlcache ] || { >htmlcache/changed; } 2>/dev/null || :
734 # A well-known UTF-8 locale is required for some of the fast-import providers
735 # in order to avoid mangling characters. Ideally we could use "POSIX.UTF-8"
736 # but that is not reliably UTF-8 but rather usually US-ASCII.
737 # We parse the output of `locale -a` and select a suitable UTF-8 locale at
738 # install time and store that in $var_utf8_locale if one is found.
739 # If we cannot find one in the `locale -a` output then we just use a well-known
740 # UTF-8 locale and hope for the best. We set LC_ALL to our choice and export
741 # it. We only set this temporarily when running the fast-import providers.
742 set_utf8_locale() {
743 LC_ALL="${var_utf8_locale:-en_US.UTF-8}"
744 export LC_ALL
747 # hg-fast-export | git fast-import with error handling in current directory GIT_DIR
748 git_hg_fetch() (
749 set_utf8_locale
750 _python="${PYTHON:-python}"
751 rm -f hg2git-marks.old hg2git-marks.new
752 if [ -f hg2git-marks ] && [ -s hg2git-marks ]; then
753 LC_ALL=C sed 's/^:\([^ ][^ ]*\) \([^ ][^ ]*\)$/\2 \1/' <hg2git-marks | {
754 if [ -n "$var_have_git_185" ]; then
755 git cat-file --batch-check=':%(rest) %(objectname)'
756 else
757 LC_ALL=C sed 's/^\([^ ][^ ]*\) \([^ ][^ ]*\)$/:\2 \1/'
759 } | LC_ALL=C sed '/ missing$/d' >hg2git-marks.old
760 if [ -n "$var_have_git_171" ] &&
761 git rev-parse --quiet --verify refs/notes/hg >/dev/null; then
762 if [ -z "$var_have_git_185" ] ||
763 ! LC_ALL=C cmp -s hg2git-marks hg2git-marks.old; then
764 _nm='hg-fast-export'
765 GIT_AUTHOR_NAME="$_nm"
766 GIT_COMMITTER_NAME="$_nm"
767 GIT_AUTHOR_EMAIL="$_nm"
768 GIT_COMMITTER_EMAIL="$_nm"
769 export GIT_AUTHOR_NAME
770 export GIT_COMMITTER_NAME
771 export GIT_AUTHOR_EMAIL
772 export GIT_COMMITTER_EMAIL
773 git notes --ref=refs/notes/hg prune
774 unset GIT_AUTHOR_NAME
775 unset GIT_COMMITTER_NAME
776 unset GIT_AUTHOR_EMAIL
777 unset GIT_COMMITTER_EMAIL
780 else
781 >hg2git-marks.old
783 _err1=
784 _err2=
785 exec 3>&1
786 { read -r _err1 || :; read -r _err2 || :; } <<-EOT
788 exec 4>&3 3>&1 1>&4 4>&-
790 _e1=0
791 _af="$(git config hg.authorsfile)" || :
792 _cmd='GIT_DIR="$(pwd)" "$_python" "$cfg_basedir/bin/hg-fast-export.py" \
793 --repo "$(pwd)/repo.hg" \
794 --marks "$(pwd)/hg2git-marks.old" \
795 --mapping "$(pwd)/hg2git-mapping" \
796 --heads "$(pwd)/hg2git-heads" \
797 --status "$(pwd)/hg2git-state" \
798 -U unknown --force --flatten --hg-hash'
799 [ -z "$_af" ] || _cmd="$_cmd"' --authors "$_af"'
800 eval "$_cmd" 3>&- || _e1=$?
801 echo $_e1 >&3
804 _e2=0
805 git fast-import \
806 --import-marks="$(pwd)/hg2git-marks.old" \
807 --export-marks="$(pwd)/hg2git-marks.new" \
808 --export-pack-edges="$(pwd)/gfi-packs" \
809 --force 3>&- || _e2=$?
810 echo $_e2 >&3
814 exec 3>&-
815 [ "$_err1" = 0 ] && [ "$_err2" = 0 ] || return 1
816 mv -f hg2git-marks.new hg2git-marks
817 rm -f hg2git-marks.old
818 git for-each-ref --format='%(refname) %(objectname)' refs/heads |
819 LC_ALL=C sed -e 's,^refs/heads/,:,' >hg2git-heads