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