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