git-daemon-verify: pick up remote_port too
[girocco.git] / shlib.sh
blob8f52ea072a52ec62f1dbf8c9d7d20b424da0bd8c
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"
188 ul512bin="$inc_basedir/bin/ulimit512"
189 if [ ! -x "$ul512bin" ] && [ -x "$inc_basedir/src/ulimit512" ]; then
190 ul512bin="$inc_basedir/src/ulimit512"
192 ebin="/bin/echo"
193 if [ ! -x "$ebin" ] && [ -x "/usr/bin/echo" ]; then
194 ebin="/usr/bin/echo"
196 if [ -x "$ul512bin" ]; then
197 tmpfile="$(mktemp /tmp/ul512-$$-XXXXXX)"
198 ec=999
199 { "$ul512bin" -f 0 "$ebin" test >"$tmpfile" || ec=$?; } >/dev/null 2>&1
200 rm -f "$tmpfile"
201 if [ "$ec" != 999 ] && [ "$ec" -gt 0 ]; then
202 printf 'var_xfsz_err=%s\n' "$ec"
207 # If basedir has been replaced, and shlib_vars.sh exists, get the config
208 # definitions from it rather than running Perl.
209 if [ "@basedir@" = '@'basedir'@' ] || ! [ -r "@basedir@/shlib_vars.sh" ]; then
210 # Import all the variables from Girocco::Config to the local environment,
211 eval "$(get_girocco_config_var_list)"
212 else
213 # Import the variables from shlib_vars.sh which avoids needlessly
214 # running another copy of Perl
215 . "@basedir@/shlib_vars.sh"
218 # git_add_config "some.var=value"
219 # every ' in value must be replaced with the 4-character sequence '\'' before
220 # calling this function or Git will barf. Will not be effective unless running
221 # Git version 1.7.3 or later.
222 git_add_config() {
223 GIT_CONFIG_PARAMETERS="${GIT_CONFIG_PARAMETERS:+$GIT_CONFIG_PARAMETERS }'$1'"
224 export GIT_CONFIG_PARAMETERS
227 # file of empty lines
228 mtlinesfile="$cfg_basedir/mtlinesfile"
229 # created by installer, but if not exists, set to /dev/null
230 [ -e "$mtlinesfile" ] && [ -f "$mtlinesfile" ] && [ -r "$mtlinesfile" ] ||
231 mtlinesfile='/dev/null'
233 # Make sure we have a reproducible environment by using a controlled HOME dir
234 XDG_CONFIG_HOME="$cfg_chroot/var/empty"
235 HOME="$cfg_chroot/etc/girocco"
236 TMPDIR="/tmp"
237 GIT_CONFIG_NOSYSTEM=1
238 GIT_ATTR_NOSYSTEM=1
239 GIT_NO_REPLACE_OBJECTS=1
240 GIT_TERMINAL_PROMPT=0
241 GIT_PAGER="cat"
242 PAGER="cat"
243 GIT_ASKPASS="$cfg_basedir/bin/git-askpass-password"
244 GIT_SVN_NOTTY=1
245 GIROCCO_SUPPRESS_AUTO_GC_UPDATE=1
246 GIT_SSH="$cfg_basedir/bin/git-ssh"
247 SVN_SSH="$cfg_basedir/bin/git-ssh"
248 export XDG_CONFIG_HOME
249 export HOME
250 export TMPDIR
251 export GIT_CONFIG_NOSYSTEM
252 export GIT_ATTR_NOSYSTEM
253 export GIT_NO_REPLACE_OBJECTS
254 export GIT_TERMINAL_PROMPT
255 export GIT_PAGER
256 export PAGER
257 export GIT_ASKPASS
258 export GIT_SVN_NOTTY
259 export GIROCCO_SUPPRESS_AUTO_GC_UPDATE
260 export GIT_SSH
261 export SVN_SSH
262 unset GIT_USER_AGENT
263 unset GIT_HTTP_USER_AGENT
264 if [ -n "$defined_cfg_git_client_ua" ]; then
265 GIT_USER_AGENT="$cfg_git_client_ua"
266 export GIT_USER_AGENT
268 unset GIT_CONFIG_PARAMETERS
269 unset GIROCCO_DIVERT_GIT_SVN_AUTO_GC
272 ## IMPORTANT!
274 ## Keep gitweb/gitweb_config.perl in sync with these git_add_config calls
275 ## Keep bin/git-shell-verify in sync with these git_add_config calls
277 git_add_config "core.ignoreCase=false"
278 git_add_config "core.pager=cat"
279 if [ -n "$cfg_git_no_mmap" ]; then
280 # Just like compiling with NO_MMAP
281 git_add_config "core.packedGitWindowSize=1m"
282 else
283 # Always use the 32-bit default (32m) even on 64-bit to avoid memory blowout
284 git_add_config "core.packedGitWindowSize=32m"
286 # Always use the 32-bit default (256m) even on 64-bit to avoid memory blowout
287 git_add_config "core.packedGitLimit=256m"
288 [ -z "$var_big_file_threshold" ] ||
289 git_add_config "core.bigFileThreshold=$var_big_file_threshold"
290 git_add_config "gc.auto=0"
291 git_add_config "gc.autodetach=false"
293 # Make sure any sendmail.pl config is always available
294 unset SENDMAIL_PL_HOST
295 unset SENDMAIL_PL_PORT
296 unset SENDMAIL_PL_NCBIN
297 unset SENDMAIL_PL_NCOPT
298 [ -z "$cfg_sendmail_pl_host" ] || { SENDMAIL_PL_HOST="$cfg_sendmail_pl_host" && export SENDMAIL_PL_HOST; }
299 [ -z "$cfg_sendmail_pl_port" ] || { SENDMAIL_PL_PORT="$cfg_sendmail_pl_port" && export SENDMAIL_PL_PORT; }
300 [ -z "$cfg_sendmail_pl_ncbin" ] || { SENDMAIL_PL_NCBIN="$cfg_sendmail_pl_ncbin" && export SENDMAIL_PL_NCBIN; }
301 [ -z "$cfg_sendmail_pl_ncopt" ] || { SENDMAIL_PL_NCOPT="$cfg_sendmail_pl_ncopt" && export SENDMAIL_PL_NCOPT; }
303 # Set PATH and PYTHON to the values set by Config.pm, if any
304 unset PYTHON
305 [ -z "$cfg_python" ] || { PYTHON="$cfg_python" && export PYTHON; }
306 [ -z "$cfg_path" ] || { orig_path="$PATH" && PATH="$cfg_path" && export PATH; }
308 # Extra GIT variables that generally ought to be cleared, but whose clearing
309 # could potentially interfere with the correct operation of hook scripts so
310 # they are segregated into a separate function for use as appropriate
311 clean_git_env() {
312 unset GIT_ALTERNATE_OBJECT_DIRECTORIES
313 unset GIT_CONFIG
314 unset GIT_DIR
315 unset GIT_GRAFT_FILE
316 unset GIT_INDEX_FILE
317 unset GIT_OBJECT_DIRECTORY
318 unset GIT_NAMESPACE
321 # We cannot use a git() {} or nc_openbsd() {} function to redirect git
322 # and nc_openbsd to the desired executables because when using
323 # "ENV_VAR=xxx func" the various /bin/sh implementations behave in various
324 # different and unexpected ways:
325 # a) treat "ENV_VAR=xxx" like a separate, preceding "export ENV_VAR=xxx"
326 # b) treat "ENV_VAR=xxx" like a separate, prededing "ENV_VAR=xxx"
327 # c) treat "ENV_VAR=xxx" like a temporary setting only while running func
328 # None of these are good. We want a temporary "export ENV_VAR=xxx"
329 # setting only while running func which none of the /bin/sh's do.
331 # Instead we'd like to use an alias that provides the desired behavior without
332 # any of the bad (a), (b) or (c) effects.
334 # However, unfortunately, some of the crazy /bin/sh implementations do not
335 # recognize alias expansions when preceded by variable assignments!
337 # So we are left with git() {} and nc_openbsd() {} functions and in the
338 # case of git() {} we can compensate for (b) and (c) failing to export
339 # but not (a) and (b) persisting the values so the caller will simply
340 # have to beware and explicitly unset any variables that should not persist
341 # beyond the function call itself.
343 _setexport_gitvars() {
344 [ z"${GIT_DIR+set}" != z"set" ] || export GIT_DIR
345 [ z"${GIT_SSL_NO_VERIFY+set}" != z"set" ] || export GIT_SSL_NO_VERIFY
346 [ z"${GIT_TRACE_PACKET+set}" != z"set" ] || export GIT_TRACE_PACKET
347 [ z"${GIT_USER_AGENT+set}" != z"set" ] || export GIT_USER_AGENT
348 [ z"${GIT_HTTP_USER_AGENT+set}" != z"set" ] || export GIT_HTTP_USER_AGENT
351 git() (
352 _setexport_gitvars
353 exec "$cfg_git_bin" "$@"
356 # git_ulimit behaves the same as git except that it runs git using ulimit512
357 # with the value of $cfg_max_file_size512 if that is set and greater than 0
359 git_ulimit() (
360 _setexport_gitvars
361 if [ "${cfg_max_file_size512:-0}" = "0" ]; then
362 exec "$cfg_git_bin" "$@"
363 else
364 exec "$cfg_basedir/bin/ulimit512" -i -f "$cfg_max_file_size512" -- "$cfg_git_bin" "$@"
368 # Since we do not yet require at least Git 1.8.5 this is a compatibility function
369 # that allows us to use git update-ref --stdin where supported and the slow shell
370 # script where not, but only the "delete" operation is currently supported.
371 git_updateref_stdin() {
372 if [ -n "$var_have_git_185" ]; then
373 git update-ref --stdin
374 else
375 while read -r _op _ref; do
376 case "$_op" in
377 delete)
378 git update-ref -d "$_ref"
381 echo "bad git_updateref_stdin op: $_op" >&2
382 exit 1
384 esac
385 done
389 # see comments for git() -- callers must explicitly export all variables
390 # intended for the commands these functions run before calling them
391 perl() { command "${var_perl_bin:-perl}" "$@"; }
392 gzip() { command "${var_gzip_bin:-gzip}" "$@"; }
394 nc_openbsd() { command "$var_nc_openbsd_bin" "$@"; }
396 list_packs() { command "$cfg_basedir/bin/list_packs" "$@"; }
398 readlink() { command "$cfg_basedir/bin/readlink" "$@"; }
400 strftime() { command "$cfg_basedir/bin/strftime" "$@"; }
402 # Some platforms' broken xargs runs the command always at least once even if
403 # there's no input unless given a special option. Automatically supply the
404 # option on those platforms by providing an xargs function.
405 xargs() { command xargs $var_xargs_r "$@"; }
407 _addrlist() {
408 _list=
409 for _addr in "$@"; do
410 [ -z "$_list" ] || _list="$_list, "
411 _list="$_list$_addr"
412 done
413 echo "$_list"
416 _sendmail() {
417 _mailer="${cfg_sendmail_bin:-/usr/sbin/sendmail}"
418 if [ -n "$cfg_sender" ]; then
419 "$_mailer" -i -f "$cfg_sender" "$@"
420 else
421 "$_mailer" -i "$@"
425 # First argument is an id WITHOUT surrounding '<' and '>' to use in a
426 # "References:" header. It may be "" to suppress the "References" header.
427 # Following arguments are just like mail function
428 mailref() {
429 _references=
430 if [ $# -ge 1 ]; then
431 _references="$1"
432 shift
434 _subject=
435 if [ "$1" = "-s" ]; then
436 shift
437 _subject="$1"
438 shift
441 echo "From: \"$cfg_name\" ($cfg_title) <$cfg_admin>"
442 echo "To: $(_addrlist "$@")"
443 [ -z "$_subject" ] || echo "Subject: $_subject"
444 echo "MIME-Version: 1.0"
445 echo "Content-Type: text/plain; charset=utf-8; format=fixed"
446 echo "Content-Transfer-Encoding: 8bit"
447 [ -z "$_references" ] || echo "References: <$_references>"
448 [ -n "$cfg_suppress_x_girocco" ] || echo "X-Girocco: $cfg_gitweburl"
449 echo "Auto-Submitted: auto-generated"
450 echo ""
452 } | _sendmail "$@"
455 # Usage: mail [-s <subject>] <addr> [<addr>...]
456 mail() {
457 mailref "" "$@"
460 # bang CMD... will execute the command with well-defined failure mode;
461 # set bang_action to string of the failed action ('clone', 'update', ...);
462 # re-define the bang_trap() function to do custom cleanup before bailing out
463 bang() {
464 bang_errcode=
465 bang_catch "$@"
466 [ "${bang_errcode:-0}" = "0" ] || bang_failed
469 bang_catch() {
470 bang_active=1
471 bang_cmd="$*"
472 # clean up bang_cmd for log
473 bang_cmd="${bang_cmd#eval }"
474 [ "${bang_cmd#git_ulimit }" = "$bang_cmd" ] ||
475 bang_cmd="git ${bang_cmd#git_ulimit }"
476 [ "${bang_cmd#git_fetch_q_progress }" = "$bang_cmd" ] ||
477 bang_cmd="git fetch ${bang_cmd#git_fetch_q_progress }"
478 [ "${bang_cmd#git fetch --progress }" = "$bang_cmd" ] ||
479 bang_cmd="git fetch ${bang_cmd#git fetch --progress }"
480 bang_errcode=0
481 if [ "${show_progress:-0}" != "0" ]; then
482 exec 3>&1
483 read -r bang_errcode <<-EOT || :
485 exec 4>&3 3>&1 1>&4 4>&-
486 { "$@" 3>&- || echo $? >&3; } 2>&1 | tee -i -a "$bang_log"
489 exec 3>&-
490 if [ -z "$bang_errcode" ] || [ "$bang_errcode" = "0" ]; then
491 # All right. Cool.
492 bang_active=
493 bang_cmd=
494 return;
496 else
497 if "$@" >>"$bang_log" 2>&1; then
498 # All right. Cool.
499 bang_active=
500 bang_cmd=
501 return;
502 else
503 bang_errcode="$?"
508 bang_failed() {
509 bang_active=
510 unset GIT_DIR
511 >.banged
512 cat "$bang_log" >.banglog
513 echo "" >>.banglog
514 echo "$bang_cmd failed with error code $bang_errcode" >>.banglog
515 ! [ -d htmlcache ] || { >htmlcache/changed; } 2>/dev/null || :
516 if [ "${show_progress:-0}" != "0" ]; then
517 echo ""
518 echo "$bang_cmd failed with error code $bang_errcode"
520 if [ -e .bangagain ]; then
521 git config --remove-section girocco.bang 2>/dev/null || :
522 rm -f .bangagain
524 bangcount="$(git config --int girocco.bang.count 2>/dev/null)" || :
525 bangcount=$(( ${bangcount:-0} + 1 ))
526 git config --int girocco.bang.count $bangcount
527 if [ $bangcount -eq 1 ]; then
528 git config girocco.bang.firstfail "$(TZ=UTC date "+%Y-%m-%d %T UTC")"
530 if [ $bangcount -ge $cfg_min_mirror_failure_message_count ] &&
531 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" != "true" ] &&
532 ! check_interval "girocco.bang.firstfail" $cfg_min_mirror_failure_message_interval; then
533 bangmailok="$(git config --bool gitweb.statusupdates 2>/dev/null || echo true)"
534 bangaddrs=
535 [ "$bangmailok" = "false" ] || [ -z "$mail" ] || bangaddrs="$mail"
536 [ -z "$cfg_admincc" ] || [ "$cfg_admincc" = "0" ] || [ -z "$cfg_admin" ] ||
537 if [ -z "$bangaddrs" ]; then bangaddrs="$cfg_admin"; else bangaddrs="$bangaddrs,$cfg_admin"; fi
538 rsubj=
539 [ $bangcount -le 1 ] || rsubj=" repeatedly"
540 [ -z "$bangaddrs" ] ||
542 echo "$bang_cmd failed with error code $bang_errcode"
543 echo ""
544 rsubj=
545 if [ $bangcount -gt 1 ]; then
546 echo "$bangcount consecutive update failures have occurred since $(config_get girocco.bang.firstfail)"
547 echo ""
549 echo "you will not receive any more notifications until recovery"
550 echo "this status message may be disabled on the project admin page"
551 echo ""
552 echo "Log follows:"
553 echo ""
554 cat "$bang_log"
555 } | mailref "update@$cfg_gitweburl/$proj.git" -s "[$cfg_name] $proj $bang_action failed$rsubj" "$bangaddrs"
556 git config --bool girocco.bang.messagesent true
558 bangthrottle=
559 [ $bangcount -lt 15 ] ||
560 check_interval "girocco.bang.firstfail" $(( $cfg_min_mirror_interval * 3 / 2 )) ||
561 bangthrottle=1
562 bang_trap $bangthrottle
563 [ -n "$bang_errcode" ] && [ "$bang_errcode" != "0" ] || bang_errcode=1
564 exit $bang_errcode
567 # bang_eval CMD... will evaluate the command with well-defined failure mode;
568 # Identical to bang CMD... except the command is eval'd instead of executed.
569 bang_eval() {
570 bang eval "$*"
573 bang_exit() {
574 # placeholder empty function that gets called
575 # when the bang_setup EXIT trap triggers
576 # can be replaced to avoid losing a pre bang_setup
577 # trap on EXIT
581 # Default bang settings:
582 bang_setup() {
583 bang_active=
584 bang_action="lame_programmer"
585 bang_trap() { :; }
586 bang_tmpdir="${TMPDIR:-/tmp}"
587 bang_tmpdir="${bang_tmpdir%/}"
588 bang_log="$(mktemp "${bang_tmpdir:-/tmp}/repomgr-XXXXXX")"
589 is_git_dir . || {
590 echo "bang_setup called with current directory not a git directory" >&2
591 exit 1
593 trap 'rm -f "$bang_log"; bang_exit' EXIT
594 trap '[ -z "$bang_active" ] || { bang_errcode=130; bang_failed; }; exit 130' INT
595 trap '[ -z "$bang_active" ] || { bang_errcode=143; bang_failed; }; exit 143' TERM
598 # Remove banged status
599 bang_reset() {
600 rm -f .banged .bangagain .banglog
601 git config --remove-section girocco.bang 2>/dev/null || :
604 # Check to see if banged status
605 is_banged() {
606 [ -e .banged ]
609 # Check to see if banged message was sent
610 was_banged_message_sent() {
611 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" = "true" ]
614 # Progress report - if show_progress is set, shows the given message.
615 progress() {
616 [ "${show_progress:-0}" = "0" ] || echo "$*"
619 # Project config accessors; must be run in project directory
620 config_get() {
621 case "$1" in
622 *.*)
623 git config "$1";;
625 git config "gitweb.$1";;
626 esac
629 config_set() {
630 git config "gitweb.$1" "$2" && chgrp $var_group config && chmod g+w config
633 config_set_raw() {
634 git config "$1" "$2" && chgrp $var_group config && chmod g+w config
637 config_get_date_seconds() {
638 _dt="$(config_get "$1")" || :
639 [ -n "$_dt" ] || return 1
640 _ds="$(perl -I@basedir@ -MGirocco::Util -e "print parse_any_date('$_dt')")"
641 [ -n "$_ds" ] || return 1
642 echo "$_ds"
645 # Tool for checking whether given number of seconds has not passed yet
646 check_interval() {
647 os="$(config_get_date_seconds "$1")" || return 1
648 ns="$(date +%s)"
649 [ $ns -lt $(($os+$2)) ]
652 # Check if we are running with effective root permissions
653 is_root() {
654 [ "$(id -u 2>/dev/null)" = "0" ]
657 # Check to see if the single argument (default ".") is a Git directory
658 is_git_dir() {
659 # Just like Git's test except we ignore GIT_OBJECT_DIRECTORY
660 # And we are slightly more picky (must be refs/.+ not refs/.*)
661 [ $# -ne 0 ] || set -- "."
662 [ -d "$1/objects" ] && [ -x "$1/objects" ] || return 1
663 [ -d "$1/refs" ] && [ -x "$1/refs" ] || return 1
664 if [ -L "$1/HEAD" ]; then
665 _hr="$(readlink "$1/HEAD")"
666 case "$_hr" in "refs/"?*) :;; *) return 1;; esac
668 [ -f "$1/HEAD" ] && [ -r "$1/HEAD" ] || return 1
669 read -r _hr <"$1/HEAD" || return 1
670 case "$_hr" in
671 $octet20*)
672 [ "${_hr#*[!0-9a-f]}" = "$_hr" ] || return 1
673 return 0;;
674 ref:refs/?*)
675 return 0;;
676 ref:*)
677 _hr="${_hr##ref:*[ $tab]}"
678 case "$_hr" in "refs/"?*) return 0;; esac
679 esac
680 return 1
683 # Check to see if the single argument (default ".") is a directory with no refs
684 is_empty_refs_dir() {
685 [ $# -ne 0 ] || set -- "."
686 if [ -s "$1/packed-refs" ]; then
687 # could be a packed-refs file with just a '# pack-refs ..." line
688 # null hash lines and peel lines do not count either
689 _refcnt="$(( $(LC_ALL=C sed <"$1/packed-refs" \
690 -e "/^00* /d" \
691 -e "/^$octet20$hexdig* refs\/[^ $tab]*\$/!d" | wc -l) ))"
692 [ "${_refcnt:-0}" -eq 0 ] || return 1
694 if [ -d "$1/refs" ]; then
695 # quick and dirty check, doesn't try to validate contents
696 # or ignore embedded symbolic refs
697 _refcnt="$(( $(find -L "$1/refs" -type f -print 2>/dev/null | head -n 1 | LC_ALL=C wc -l) ))"
698 [ "${_refcnt:-0}" -eq 0 ] || return 1
700 # last chance a detached HEAD (we ignore any linked working trees though)
701 [ -s "$1/HEAD" ] && read -r _hr <"$1/HEAD" && [ -n "$_hr" ] || return 0
702 [ "${_hr#*[!0-9a-f]}" != "$_hr" ] || [ "${_hr#*[!0]}" = "$_hr" ] || [ "${#_hr}" -lt 40 ] || return 1
703 return 0
706 # List all Git repositories, with given prefix if specified, one-per-line
707 # All project names starting with _ are always excluded from the result
708 get_repo_list() {
709 if [ -n "$1" ]; then
710 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group | LC_ALL=C grep "^$1"
711 else
712 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group
713 fi |
714 LC_ALL=C awk -F : 'substr($1,1,1) != "_" && $2 >= 65536 {print $1}'
717 # set the variable named by the first argument to the project part (i.e. WITH
718 # the trailing ".git" but WITHOUT the leading $cfg_reporoot) of the directory
719 # specified by the second argument.
720 # This function cannot be fooled by symbolic links.
721 # If the second argument is omitted (or empty) use $(pwd -P) instead.
722 # The directory specified by the second argument must exist.
723 v_get_proj_from_dir() {
724 [ -n "$2" ] || set -- "$1" "$(pwd -P)"
725 [ -d "$2" ] || return 1
726 case "$2" in
727 "$cfg_reporoot/"?*)
728 # Simple case that does not need any fancy footwork
729 _projpart="${2#$cfg_reporoot/}"
732 _absrr="$(cd "$cfg_reporoot" && pwd -P)"
733 _abspd="$(cd "$2" && pwd -P)"
734 case "$_abspd" in
735 "$_absrr/"?*)
736 # The normal case
737 _projpart="${_abspd#$_absrr/}"
740 # Must have been reached via a symbolic link
741 # Attempt to translate using the gitdir.list file
742 # If not found use a generic "_external" leader
743 # combined with just the trailing directory name
744 _projpart=
746 [ -f "$cfg_projlist_cache_dir/gitdir.list" ] &&
747 [ -s "$cfg_projlist_cache_dir/gitdir.list" ]
748 then
749 _projpart="$(LC_ALL=C awk -v fnd="$_abspd" \
750 <"$cfg_projlist_cache_dir/gitdir.list" \
751 'NF>=2{p=$1; sub(/^[^ \t]+[ \t]+/,"");
752 if ($0 == fnd) {print p ".git"; exit;}}')" || :
754 if [ -z "$_projpart" ]; then
755 _abspd="${_abspd%/}"
756 _abspd="${_abspd%/.git}"
757 _projpart="_external/${_abspd##*/}"
760 esac
761 esac
762 case "$_projpart" in *[!/]".git/worktrees/"?*)
763 _projpart="${_projpart%.git/worktrees/*}.git"
764 esac
765 eval "$1="'"$_projpart"'
768 # Returns success if "$1" does not exist or contains only blank lines and comments
769 # The parsing rules are in Git's sha1-file.c parse_alt_odb_entry function;
770 # the format for blank lines and comments has been the same since Git v0.99.5
771 is_empty_alternates_file() {
772 [ -n "$1" ] || return 0
773 [ -e "$1" ] && [ -f "$1" ] && [ -s "$1" ] || return 0
774 [ -r "$1" ] || return 1
775 LC_ALL=C awk <"$1" '!/^$/ && !/^#/ {exit 1}'
778 # Return success if the given project name has at least one immediate child fork
779 # that has a non-empty alternates file
780 has_forks_with_alternates() {
781 _prj="${1%.git}"
782 [ -n "$_prj" ] || return 1
783 [ -d "$cfg_reporoot/$_prj" ] || return 1
784 is_git_dir "$cfg_reporoot/$_prj.git" || return 1
786 get_repo_list "$_prj/[^/:][^/:]*:" |
787 while read -r _prjname && [ -n "$_prjname" ]; do
788 is_empty_alternates_file "$cfg_reporoot/$_prjname.git/objects/info/alternates" ||
789 exit 1 # will only exit implicit subshell created by '|'
790 done
791 then
792 return 1
794 return 0
797 # returns empty string and error for empty string otherwise one of
798 # m => normal Git mirror
799 # s => mirror from svn source
800 # d => mirror from darcs source
801 # b => mirror from bzr source
802 # h => mirror from hg source
803 # w => mirror from mediawiki source
804 # f => mirror from other fast-import source
805 # note that if the string is non-empty and none of s, d, b or h match the
806 # return will always be type m regardless of whether it's a valid Git URL
807 get_url_mirror_type() {
808 case "$1" in
810 return 1
812 svn://* | svn+http://* | svn+https://* | svn+file://* | svn+ssh://*)
813 echo 's'
815 darcs://* | darcs+http://* | darcs+https://*)
816 echo 'd'
818 bzr://*)
819 echo 'b'
821 hg+http://* | hg+https://* | hg+file://* | hg+ssh://* | hg::*)
822 echo 'h'
824 mediawiki::*)
825 echo 'w'
828 echo 'm'
830 esac
831 return 0
834 # returns false for empty string
835 # returns true if the passed in url is a mirror using git fast-import
836 is_gfi_mirror_url() {
837 [ -n "$1" ] || return 1
838 case "$(get_url_mirror_type "$1" 2>/dev/null || :)" in
839 d|b|h|w|f)
840 # darcs, bzr, hg and mediawiki mirrors use git fast-import
841 # and so do generic "f" fast-import mirrors
842 return 0
845 # Don't think git-svn currently uses git fast-import
846 # And Git mirrors certainly do not
847 return 1
849 esac
850 # assume it does not use git fast-import
851 return 1
854 # returns false for empty string
855 # returns true if the passed in url is a mirror using git-svn
856 is_svn_mirror_url() {
857 [ -n "$1" ] || return 1
858 [ "$(get_url_mirror_type "$1" 2>/dev/null || :)" = "s" ]
861 # returns mirror url for gitweb.baseurl of git directory
862 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
863 # will fail if the directory does not have .nofetch and gitweb.baseurl
864 # comes back empty -- otherwise .nofetch directories succeed with a "" return
865 # automatically strips any leading "disabled " prefix before returning result
866 get_mirror_url() {
867 _gitdir="${1:-.}"
868 # always return empty for non-mirrors
869 ! [ -e "$_gitdir/.nofetch" ] || return 0
870 _url="$(GIT_DIR="$_gitdir" config_get baseurl 2>/dev/null)" || :
871 _url="${_url##* }"
872 [ -n "$_url" ] || return 1
873 printf '%s\n' "$_url"
874 return 0
877 # returns get_url_mirror_type for gitweb.baseurl of git directory
878 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
879 # will fail if the directory does not have .nofetch and gitweb.baseurl
880 # comes back empty -- otherwise .nofetch directories succeed with a "" return
881 # automatically strips any leading "disabled " prefix before testing
882 get_mirror_type() {
883 _url="$(get_mirror_url "$@")" || return 1
884 [ -n "$_url" ] || return 0
885 get_url_mirror_type "$_url"
888 # returns true if the passed in git dir (defaults to ".") is a mirror using git fast-import
889 is_gfi_mirror() {
890 _url="$(get_mirror_url "$@")" || return 1
891 is_gfi_mirror_url "$_url"
894 # returns true if the passed in git dir (defaults to ".") is a mirror using git-svn
895 is_svn_mirror() {
896 _url="$(get_mirror_url "$@")" || return 1
897 is_svn_mirror_url "$_url"
900 # current directory must already be set to Git repository
901 # if girocco.headok is already true succeeds without doing anything
902 # if rev-parse --verify HEAD succeeds sets headok=true and succeeds
903 # otherwise tries to set HEAD to a symbolic ref to refs/heads/master
904 # then refs/heads/trunk and finally the first top-level head from
905 # refs/heads/* (i.e. only two slashes in the name) and finally any
906 # existing refs/heads. The first one to succeed wins and sets headok=true
907 # and then a successful exit. Otherwise headok is left unset with a failure exit
908 # We use the girocco.headok flag to make sure we only force a valid HEAD symref
909 # when the repository is being set up -- if the HEAD is later deleted (through
910 # a push or fetch --prune) that's no longer our responsibility to fix
911 check_and_set_head() {
912 [ "$(git config --bool girocco.headok 2>/dev/null || :)" != "true" ] || return 0
913 if git rev-parse --verify --quiet HEAD >/dev/null; then
914 git config --bool girocco.headok true
915 return 0
917 for _hr in refs/heads/master refs/heads/trunk; do
918 if git rev-parse --verify --quiet "$_hr" >/dev/null; then
919 _update_head_symref "$_hr"
920 return 0
922 done
923 git for-each-ref --format="%(refname)" refs/heads 2>/dev/null |
924 while read -r _hr; do
925 case "${_hr#refs/heads/}" in */*) :;; *)
926 _update_head_symref "$_hr"
927 exit 1 # exit subshell created by "|"
928 esac
929 done || return 0
930 _hr="$(git for-each-ref --format="%(refname)" refs/heads 2>/dev/null | head -n 1)" || :
931 if [ -n "$_hr" ]; then
932 _update_head_symref "$_hr"
933 return 0
935 return 1
937 _update_head_symref() {
938 git symbolic-ref HEAD "$1"
939 git config --bool girocco.headok true
940 ! [ -d htmlcache ] || { >htmlcache/changed; } 2>/dev/null || :
943 # current directory must already be set to Git repository
944 # if the directory needs to have gc run and .needsgc is not already set
945 # then .needsgc will be set triggering a "mini" gc at the next opportunity
946 # Girocco shouldn't generate any loose objects but we check for that anyway
947 check_and_set_needsgc() {
948 # If there's a .needspack file and ANY loose objects with a newer timestamp
949 # then also set .needsgc otherwise remove it. The only caller that may set
950 # .needspack is a mirror therefore we don't have to worry about removing a
951 # .needspack out from under a simultaneous creator. We always do this and
952 # do it first to try and avoid leaving a stale .needspack lying around.
953 if [ -e .needspack ]; then
954 _objfiles=
955 _objfiles="$(( $(find -L objects/$octet -maxdepth 1 -newer .needspack -name "$octet19*" -type f -print 2>/dev/null |
956 head -n 1 | LC_ALL=C wc -l) +0 ))"
957 if [ "${_objfiles:-0}" = "0" ]; then
958 rm -f .needspack
959 else
960 [ -e .needsgc ] || >.needsgc
963 ! [ -e .needsgc ] || return 0
964 _packs=
965 { _packs="$(list_packs --quiet --count --exclude-no-idx --exclude-keep objects/pack || :)" || :; } 2>/dev/null
966 if [ "${_packs:-0}" -ge 20 ]; then
967 >.needsgc
968 return 0
970 _logfiles=
971 { _logfiles="$(($(find -L reflogs -maxdepth 1 -type f -print | wc -l || :)+0))" || :; } 2>/dev/null
972 if [ "${_logfiles:-0}" -ge 50 ]; then
973 >.needsgc
974 return 0
976 # Truly git gc only checks the number of objects in the objects/17 directory
977 # We check for -ge 10 which should make the probability of having more than
978 # 5120 (20*256) loose objects present when there are less than 10 in
979 # objects/17 vanishingly small (20 is the threshold we use for pack files)
980 _objfiles=
981 ! [ -d objects/17 ] ||
982 { _objfiles="$(($(find -L objects/17 -type f -name "$octet19*" -print | wc -l || :)+0))" || :; } 2>/dev/null
983 if [ "${_objfiles:-0}" -ge 10 ]; then
984 >.needsgc
985 return 0
989 # current directory must already be set to Git repository
990 # remove any existing stale .lock files anywhere in the refs hierarchy
991 # mirror .lock files are considered "stale" after 60m whereas push projects
992 # need 12h for a .lock file to be considered stale.
993 clear_stale_ref_locks() {
994 # Quick sanity check just in case
995 [ -f HEAD ] && [ -s HEAD ] && [ -d objects ] && [ -d refs ] || return 1
996 _stale=60
997 [ ! -e .nofetch ] || _stale=720
998 # Clear any stale top-level ref locks
999 find . -maxdepth 1 -name '*?.lock' -type f -mmin +$_stale -exec rm -f '{}' + >/dev/null 2>&1 || :
1000 if [ -d worktrees ]; then
1001 # Clear any worktrees stale top-level ref locks
1002 find -H worktrees -mindepth 2 -maxdepth 2 -name '*?.lock' -type f -mmin +$_stale -exec rm -f '{}' + >/dev/null 2>&1 || :
1004 # Clear any stale ref locks within the refs hierarchy itself
1005 find -H refs -mindepth 1 -name '*?.lock' -type f -mmin +$_stale -exec rm -f '{}' + >/dev/null 2>&1 || :
1006 return 0
1009 # A well-known UTF-8 locale is required for some of the fast-import providers
1010 # in order to avoid mangling characters. Ideally we could use "POSIX.UTF-8"
1011 # but that is not reliably UTF-8 but rather usually US-ASCII.
1012 # We parse the output of `locale -a` and select a suitable UTF-8 locale at
1013 # install time and store that in $var_utf8_locale if one is found.
1014 # If we cannot find one in the `locale -a` output then we just use a well-known
1015 # UTF-8 locale and hope for the best. We set LC_ALL to our choice and export
1016 # it. We only set this temporarily when running the fast-import providers.
1017 set_utf8_locale() {
1018 LC_ALL="${var_utf8_locale:-en_US.UTF-8}"
1019 export LC_ALL
1022 # hg-fast-export | git fast-import with error handling in current directory GIT_DIR
1023 git_hg_fetch() (
1024 set_utf8_locale
1025 _python="${PYTHON:-python}"
1026 rm -f hg2git-marks.old hg2git-marks.new
1027 if [ -f hg2git-marks ] && [ -s hg2git-marks ]; then
1028 LC_ALL=C sed 's/^:\([^ ][^ ]*\) \([^ ][^ ]*\)$/\2 \1/' <hg2git-marks | {
1029 if [ -n "$var_have_git_185" ]; then
1030 git cat-file --batch-check=':%(rest) %(objectname)'
1031 else
1032 LC_ALL=C sed 's/^\([^ ][^ ]*\) \([^ ][^ ]*\)$/:\2 \1/'
1034 } | LC_ALL=C sed '/ missing$/d' >hg2git-marks.old
1035 if [ -n "$var_have_git_171" ] &&
1036 git rev-parse --quiet --verify refs/notes/hg >/dev/null; then
1037 if [ -z "$var_have_git_185" ] ||
1038 ! LC_ALL=C cmp -s hg2git-marks hg2git-marks.old; then
1039 _nm='hg-fast-export'
1040 GIT_AUTHOR_NAME="$_nm"
1041 GIT_COMMITTER_NAME="$_nm"
1042 GIT_AUTHOR_EMAIL="$_nm"
1043 GIT_COMMITTER_EMAIL="$_nm"
1044 export GIT_AUTHOR_NAME
1045 export GIT_COMMITTER_NAME
1046 export GIT_AUTHOR_EMAIL
1047 export GIT_COMMITTER_EMAIL
1048 git notes --ref=refs/notes/hg prune
1049 unset GIT_AUTHOR_NAME
1050 unset GIT_COMMITTER_NAME
1051 unset GIT_AUTHOR_EMAIL
1052 unset GIT_COMMITTER_EMAIL
1055 else
1056 >hg2git-marks.old
1058 _err1=
1059 _err2=
1060 exec 3>&1
1061 { read -r _err1 || :; read -r _err2 || :; } <<-EOT
1063 exec 4>&3 3>&1 1>&4 4>&-
1065 _e1=0
1066 _af="$(git config hg.authorsfile)" || :
1067 _cmd='GIT_DIR="$(pwd)" "$_python" "$cfg_basedir/bin/hg-fast-export.py" \
1068 --repo "$(pwd)/repo.hg" \
1069 --marks "$(pwd)/hg2git-marks.old" \
1070 --mapping "$(pwd)/hg2git-mapping" \
1071 --heads "$(pwd)/hg2git-heads" \
1072 --status "$(pwd)/hg2git-state" \
1073 -U unknown --force --flatten --hg-hash'
1074 [ -z "$_af" ] || _cmd="$_cmd"' --authors "$_af"'
1075 eval "$_cmd" 3>&- || _e1=$?
1076 echo $_e1 >&3
1079 _e2=0
1080 git_ulimit fast-import \
1081 --import-marks="$(pwd)/hg2git-marks.old" \
1082 --export-marks="$(pwd)/hg2git-marks.new" \
1083 --export-pack-edges="$(pwd)/gfi-packs" \
1084 --force 3>&- || _e2=$?
1085 echo $_e2 >&3
1089 exec 3>&-
1090 [ "$_err1" = 0 ] && [ "$_err2" = 0 ] || return 1
1091 mv -f hg2git-marks.new hg2git-marks
1092 rm -f hg2git-marks.old
1093 git for-each-ref --format='%(refname) %(objectname)' refs/heads |
1094 LC_ALL=C sed -e 's,^refs/heads/,:,' >hg2git-heads