shlib.sh: provide a means to get only the 'var_...=' settings
[girocco/readme.git] / shlib.sh
blob33f5d93d4167f8039dbd54b3ac7c22896df29e8b
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_nc_openbsd_bin Full path to the netcat (nc) with -U support
95 # var_have_git_171 Set to 1 if git version >= 1.7.1 otherwise ''
96 # var_have_git_172 Set to 1 if git version >= 1.7.2 otherwise ''
97 # var_have_git_173 Set to 1 if git version >= 1.7.3 otherwise ''
98 # var_have_git_1710 Set to 1 if git version >= 1.7.10 otherwise ''
99 # var_have_git_185 Set to 1 if git version >= 1.8.5 otherwise ''
100 # var_have_git_210 Set to 1 if git version >= 2.1.0 otherwise ''
101 # var_have_git_235 Set to 1 if git version >= 2.3.5 otherwise ''
102 # var_have_git_260 Set to 1 if git version >= 2.6.0 otherwise ''
103 # var_have_git_2101 Set to 1 if git version >= 2.10.1 otherwise ''
104 # var_window_memory Value to use for repack --window-memory=
105 # var_big_file_threshold Value to use for core.bigFileThreshold
106 # var_redelta_threshold Recompute deltas if no more than this many objs
107 # var_upload_window If not "", pack.window to use for upload-pack
108 # var_log_window_size Value to use for git-svn --log-window-size=
109 # var_utf8_locale Value to use for a UTF-8 locale if available
110 # var_xargs_r A "-r" if xargs needs it to behave correctly
111 # var_du_exclude Option to exclude PATTERN from du if available
112 # var_du_follow Option to follow command line sym links if available
113 # var_xfsz_err Shell error code when child dies from SIGXFSZ
114 _cfg_vars="$(get_girocco_config_pm_var_list)"
115 eval "$_cfg_vars"
116 [ "$1" = "varonly" ] || printf '%s\n' "$_cfg_vars"
117 printf 'var_group=%s\n' "${cfg_owning_group:-$(id -gn)}"
118 perl - "$var_group" "$cfg_mirror_user" "$cfg_cgi_user" <<-'PERLPROG'
119 no warnings;
120 my $gid = getgrnam($ARGV[0]);
121 my $mid = getpwnam($ARGV[1]);
122 my $cid = getpwnam($ARGV[2]);
123 defined($gid) && $gid ne '' and print "var_group_gid=$gid\n";
124 defined($mid) && $mid ne '' and print "var_mirror_uid=$mid\n";
125 defined($cid) && $cid ne '' and print "var_cgi_uid=$cid\n";
126 PERLPROG
127 _gver="$("$cfg_git_bin" version 2>/dev/null |
128 LC_ALL=C sed -ne 's/^[^0-9]*\([0-9][0-9]*\(\.[0-9][0-9]*\)*\).*$/\1/p')"
129 printf 'var_git_ver=%s\n' "$_gver"
130 printf 'var_git_exec_path="%s"\n' "$("$cfg_git_bin" --exec-path 2>/dev/null)"
131 printf 'var_sh_bin="%s"\n' "$(_fcp "${cfg_posix_sh_bin:-/bin/sh}")"
132 printf 'var_perl_bin="%s"\n' "$(_fcp "${cfg_perl_bin:-$(unset -f perl; command -v perl)}")"
133 printf 'var_gzip_bin="%s"\n' "$(_fcp "${cfg_gzip_bin:-$(unset -f gzip; command -v gzip)}")"
134 printf 'var_nc_openbsd_bin="%s"\n' "$(_fcp "${cfg_nc_openbsd_bin:-$(unset -f nc; command -v nc)}")"
135 printf 'var_have_git_171=%s\n' "$([ $(vcmp "$_gver" 1.7.1) -ge 0 ] && echo 1)"
136 printf 'var_have_git_172=%s\n' "$([ $(vcmp "$_gver" 1.7.2) -ge 0 ] && echo 1)"
137 printf 'var_have_git_173=%s\n' "$([ $(vcmp "$_gver" 1.7.3) -ge 0 ] && echo 1)"
138 printf 'var_have_git_1710=%s\n' "$([ $(vcmp "$_gver" 1.7.10) -ge 0 ] && echo 1)"
139 printf 'var_have_git_185=%s\n' "$([ $(vcmp "$_gver" 1.8.5) -ge 0 ] && echo 1)"
140 printf 'var_have_git_210=%s\n' "$([ $(vcmp "$_gver" 2.1.0) -ge 0 ] && echo 1)"
141 printf 'var_have_git_235=%s\n' "$([ $(vcmp "$_gver" 2.3.5) -ge 0 ] && echo 1)"
142 printf 'var_have_git_260=%s\n' "$([ $(vcmp "$_gver" 2.6.0) -ge 0 ] && echo 1)"
143 printf 'var_have_git_2101=%s\n' "$([ $(vcmp "$_gver" 2.10.1) -ge 0 ] && echo 1)"
144 __girocco_conf="$GIROCCO_CONF"
145 [ -n "$__girocco_conf" ] || __girocco_conf="Girocco::Config"
146 [ -z "$basedir" ] || __girocco_extrainc="-I$basedir"
147 inc_basedir=@basedir@
148 [ "@basedir@" != '@'basedir'@' ] || inc_basedir="$PWD"
149 printf "var_window_memory=%s\n" \
150 "$(perl -I"$inc_basedir" $__girocco_extrainc -M$__girocco_conf \
151 -MGirocco::Util -e 'print calc_windowmemory')"
152 printf "var_big_file_threshold=%s\n" \
153 "$(perl -I"$inc_basedir" $__girocco_extrainc -M$__girocco_conf \
154 -MGirocco::Util -e 'print calc_bigfilethreshold')"
155 printf "var_redelta_threshold=%s\n" \
156 "$(perl -I"$inc_basedir" $__girocco_extrainc -M$__girocco_conf \
157 -MGirocco::Util -e 'print calc_redeltathreshold')"
158 if [ -n "$cfg_upload_pack_window" ] && [ "$cfg_upload_pack_window" -ge 2 ] &&
159 [ "$cfg_upload_pack_window" -le 50 ]; then
160 printf "var_upload_window=%s\n" "$cfg_upload_pack_window"
161 else
162 printf "var_upload_window=%s\n" ""
164 printf 'var_log_window_size=%s\n' "${cfg_svn_log_window_size:-250}"
165 # We parse the output of `locale -a` and select a suitable UTF-8 locale.
166 _guess_locale="$(locale -a | LC_ALL=C grep -viE '^(posix|c)(\..*)?$' |
167 LC_ALL=C grep -iE '\.utf-?8$' | LC_ALL=C sed -e 's/\.[Uu][Tt][Ff]-*8$//' |
168 LC_ALL=C sed -e '/en_US/ s/^/0 /; /en_US/ !s/^/1 /' | LC_ALL=C sort |
169 head -n 1 | LC_ALL=C cut -d ' ' -f 2)"
170 [ -z "$_guess_locale" ] || printf 'var_utf8_locale=%s.UTF-8\n' "$_guess_locale"
171 # On some broken platforms running xargs without -r and empty input runs the command
172 printf 'var_xargs_r=%s\n' "$(</dev/null command xargs printf %s -r)"
173 # The disk usage report produces better numbers if du has an exclude option
174 _x0="${0##*/}"
175 _x0="${_x0%?}?*"
176 for _duopt in --exclude -I; do
177 if _test="$(du $_duopt 's?lib.s*' $_duopt "$_x0" "$0" 2>/dev/null)" && [ -z "$_test" ]; then
178 printf 'var_du_exclude=%s\n' "$_duopt"
179 break
181 done
182 if _test="$(du -H "$0" 2>/dev/null)" && [ -n "$_test" ]; then
183 printf 'var_du_follow=%s\n' "-H"
184 break
186 ul512bin="$inc_basedir/bin/ulimit512"
187 if [ ! -x "$ul512bin" ] && [ -x "$inc_basedir/src/ulimit512" ]; then
188 ul512bin="$inc_basedir/src/ulimit512"
190 ebin="/bin/echo"
191 if [ ! -x "$ebin" ] && [ -x "/usr/bin/echo" ]; then
192 ebin="/usr/bin/echo"
194 if [ -x "$ul512bin" ]; then
195 tmpfile="$(mktemp /tmp/ul512-$$-XXXXXX)"
196 ec=999
197 { "$ul512bin" -f 0 "$ebin" test >"$tmpfile" || ec=$?; } >/dev/null 2>&1
198 rm -f "$tmpfile"
199 if [ "$ec" != 999 ] && [ "$ec" -gt 0 ]; then
200 printf 'var_xfsz_err=%s\n' "$ec"
205 # If basedir has been replaced, and shlib_vars.sh exists, get the config
206 # definitions from it rather than running Perl.
207 if [ "@basedir@" = '@'basedir'@' ] || ! [ -r "@basedir@/shlib_vars.sh" ]; then
208 # Import all the variables from Girocco::Config to the local environment,
209 eval "$(get_girocco_config_var_list)"
210 else
211 # Import the variables from shlib_vars.sh which avoids needlessly
212 # running another copy of Perl
213 . "@basedir@/shlib_vars.sh"
216 # git_add_config "some.var=value"
217 # every ' in value must be replaced with the 4-character sequence '\'' before
218 # calling this function or Git will barf. Will not be effective unless running
219 # Git version 1.7.3 or later.
220 git_add_config() {
221 GIT_CONFIG_PARAMETERS="${GIT_CONFIG_PARAMETERS:+$GIT_CONFIG_PARAMETERS }'$1'"
222 export GIT_CONFIG_PARAMETERS
225 # file of empty lines
226 mtlinesfile="$cfg_basedir/mtlinesfile"
227 # created by installer, but if not exists, set to /dev/null
228 [ -e "$mtlinesfile" ] && [ -f "$mtlinesfile" ] && [ -r "$mtlinesfile" ] ||
229 mtlinesfile='/dev/null'
231 # Make sure we have a reproducible environment by using a controlled HOME dir
232 XDG_CONFIG_HOME="$cfg_chroot/var/empty"
233 HOME="$cfg_chroot/etc/girocco"
234 TMPDIR="/tmp"
235 GIT_CONFIG_NOSYSTEM=1
236 GIT_ATTR_NOSYSTEM=1
237 GIT_NO_REPLACE_OBJECTS=1
238 GIT_TERMINAL_PROMPT=0
239 GIT_PAGER="cat"
240 PAGER="cat"
241 GIT_ASKPASS="$cfg_basedir/bin/git-askpass-password"
242 GIT_SVN_NOTTY=1
243 GIROCCO_SUPPRESS_AUTO_GC_UPDATE=1
244 GIT_SSH="$cfg_basedir/bin/git-ssh"
245 SVN_SSH="$cfg_basedir/bin/git-ssh"
246 export XDG_CONFIG_HOME
247 export HOME
248 export TMPDIR
249 export GIT_CONFIG_NOSYSTEM
250 export GIT_ATTR_NOSYSTEM
251 export GIT_NO_REPLACE_OBJECTS
252 export GIT_TERMINAL_PROMPT
253 export GIT_PAGER
254 export PAGER
255 export GIT_ASKPASS
256 export GIT_SVN_NOTTY
257 export GIROCCO_SUPPRESS_AUTO_GC_UPDATE
258 export GIT_SSH
259 export SVN_SSH
260 unset GIT_USER_AGENT
261 unset GIT_HTTP_USER_AGENT
262 if [ -n "$defined_cfg_git_client_ua" ]; then
263 GIT_USER_AGENT="$cfg_git_client_ua"
264 export GIT_USER_AGENT
266 unset GIT_CONFIG_PARAMETERS
267 unset GIROCCO_DIVERT_GIT_SVN_AUTO_GC
270 ## IMPORTANT!
272 ## Keep gitweb/gitweb_config.perl in sync with these git_add_config calls
273 ## Keep bin/git-shell-verify in sync with these git_add_config calls
275 git_add_config "core.ignoreCase=false"
276 git_add_config "core.pager=cat"
277 if [ -n "$cfg_git_no_mmap" ]; then
278 # Just like compiling with NO_MMAP
279 git_add_config "core.packedGitWindowSize=1m"
280 else
281 # Always use the 32-bit default (32m) even on 64-bit to avoid memory blowout
282 git_add_config "core.packedGitWindowSize=32m"
284 # Always use the 32-bit default (256m) even on 64-bit to avoid memory blowout
285 git_add_config "core.packedGitLimit=256m"
286 [ -z "$var_big_file_threshold" ] ||
287 git_add_config "core.bigFileThreshold=$var_big_file_threshold"
288 git_add_config "gc.auto=0"
289 git_add_config "gc.autodetach=false"
291 # Make sure any sendmail.pl config is always available
292 unset SENDMAIL_PL_HOST
293 unset SENDMAIL_PL_PORT
294 unset SENDMAIL_PL_NCBIN
295 unset SENDMAIL_PL_NCOPT
296 [ -z "$cfg_sendmail_pl_host" ] || { SENDMAIL_PL_HOST="$cfg_sendmail_pl_host" && export SENDMAIL_PL_HOST; }
297 [ -z "$cfg_sendmail_pl_port" ] || { SENDMAIL_PL_PORT="$cfg_sendmail_pl_port" && export SENDMAIL_PL_PORT; }
298 [ -z "$cfg_sendmail_pl_ncbin" ] || { SENDMAIL_PL_NCBIN="$cfg_sendmail_pl_ncbin" && export SENDMAIL_PL_NCBIN; }
299 [ -z "$cfg_sendmail_pl_ncopt" ] || { SENDMAIL_PL_NCOPT="$cfg_sendmail_pl_ncopt" && export SENDMAIL_PL_NCOPT; }
301 # Set PATH and PYTHON to the values set by Config.pm, if any
302 unset PYTHON
303 [ -z "$cfg_python" ] || { PYTHON="$cfg_python" && export PYTHON; }
304 [ -z "$cfg_path" ] || { orig_path="$PATH" && PATH="$cfg_path" && export PATH; }
306 # Extra GIT variables that generally ought to be cleared, but whose clearing
307 # could potentially interfere with the correct operation of hook scripts so
308 # they are segregated into a separate function for use as appropriate
309 clean_git_env() {
310 unset GIT_ALTERNATE_OBJECT_DIRECTORIES
311 unset GIT_CONFIG
312 unset GIT_DIR
313 unset GIT_GRAFT_FILE
314 unset GIT_INDEX_FILE
315 unset GIT_OBJECT_DIRECTORY
316 unset GIT_NAMESPACE
319 # We cannot use a git() {} or nc_openbsd() {} function to redirect git
320 # and nc_openbsd to the desired executables because when using
321 # "ENV_VAR=xxx func" the various /bin/sh implementations behave in various
322 # different and unexpected ways:
323 # a) treat "ENV_VAR=xxx" like a separate, preceding "export ENV_VAR=xxx"
324 # b) treat "ENV_VAR=xxx" like a separate, prededing "ENV_VAR=xxx"
325 # c) treat "ENV_VAR=xxx" like a temporary setting only while running func
326 # None of these are good. We want a temporary "export ENV_VAR=xxx"
327 # setting only while running func which none of the /bin/sh's do.
329 # Instead we'd like to use an alias that provides the desired behavior without
330 # any of the bad (a), (b) or (c) effects.
332 # However, unfortunately, some of the crazy /bin/sh implementations do not
333 # recognize alias expansions when preceded by variable assignments!
335 # So we are left with git() {} and nc_openbsd() {} functions and in the
336 # case of git() {} we can compensate for (b) and (c) failing to export
337 # but not (a) and (b) persisting the values so the caller will simply
338 # have to beware and explicitly unset any variables that should not persist
339 # beyond the function call itself.
341 _setexport_gitvars() {
342 [ z"${GIT_DIR+set}" != z"set" ] || export GIT_DIR
343 [ z"${GIT_SSL_NO_VERIFY+set}" != z"set" ] || export GIT_SSL_NO_VERIFY
344 [ z"${GIT_TRACE_PACKET+set}" != z"set" ] || export GIT_TRACE_PACKET
345 [ z"${GIT_USER_AGENT+set}" != z"set" ] || export GIT_USER_AGENT
346 [ z"${GIT_HTTP_USER_AGENT+set}" != z"set" ] || export GIT_HTTP_USER_AGENT
349 git() (
350 _setexport_gitvars
351 exec "$cfg_git_bin" "$@"
354 # git_ulimit behaves the same as git except that it runs git using ulimit512
355 # with the value of $cfg_max_file_size512 if that is set and greater than 0
357 git_ulimit() (
358 _setexport_gitvars
359 if [ "${cfg_max_file_size512:-0}" = "0" ]; then
360 exec "$cfg_git_bin" "$@"
361 else
362 exec "$cfg_basedir/bin/ulimit512" -i -f "$cfg_max_file_size512" -- "$cfg_git_bin" "$@"
366 # Since we do not yet require at least Git 1.8.5 this is a compatibility function
367 # that allows us to use git update-ref --stdin where supported and the slow shell
368 # script where not, but only the "delete" operation is currently supported.
369 git_updateref_stdin() {
370 if [ -n "$var_have_git_185" ]; then
371 git update-ref --stdin
372 else
373 while read -r _op _ref; do
374 case "$_op" in
375 delete)
376 git update-ref -d "$_ref"
379 echo "bad git_updateref_stdin op: $_op" >&2
380 exit 1
382 esac
383 done
387 # see comments for git() -- callers must explicitly export all variables
388 # intended for the commands these functions run before calling them
389 perl() { command "${var_perl_bin:-perl}" "$@"; }
390 gzip() { command "${var_gzip_bin:-gzip}" "$@"; }
392 nc_openbsd() { command "$var_nc_openbsd_bin" "$@"; }
394 list_packs() { command "$cfg_basedir/bin/list_packs" "$@"; }
396 readlink() { command "$cfg_basedir/bin/readlink" "$@"; }
398 strftime() { command "$cfg_basedir/bin/strftime" "$@"; }
400 # Some platforms' broken xargs runs the command always at least once even if
401 # there's no input unless given a special option. Automatically supply the
402 # option on those platforms by providing an xargs function.
403 xargs() { command xargs $var_xargs_r "$@"; }
405 _addrlist() {
406 _list=
407 for _addr in "$@"; do
408 [ -z "$_list" ] || _list="$_list, "
409 _list="$_list$_addr"
410 done
411 echo "$_list"
414 _sendmail() {
415 _mailer="${cfg_sendmail_bin:-/usr/sbin/sendmail}"
416 if [ -n "$cfg_sender" ]; then
417 "$_mailer" -i -f "$cfg_sender" "$@"
418 else
419 "$_mailer" -i "$@"
423 # First argument is an id WITHOUT surrounding '<' and '>' to use in a
424 # "References:" header. It may be "" to suppress the "References" header.
425 # Following arguments are just like mail function
426 mailref() {
427 _references=
428 if [ $# -ge 1 ]; then
429 _references="$1"
430 shift
432 _subject=
433 if [ "$1" = "-s" ]; then
434 shift
435 _subject="$1"
436 shift
439 echo "From: \"$cfg_name\" ($cfg_title) <$cfg_admin>"
440 echo "To: $(_addrlist "$@")"
441 [ -z "$_subject" ] || echo "Subject: $_subject"
442 echo "MIME-Version: 1.0"
443 echo "Content-Type: text/plain; charset=utf-8; format=fixed"
444 echo "Content-Transfer-Encoding: 8bit"
445 [ -z "$_references" ] || echo "References: <$_references>"
446 [ -n "$cfg_suppress_x_girocco" ] || echo "X-Girocco: $cfg_gitweburl"
447 echo "Auto-Submitted: auto-generated"
448 echo ""
450 } | _sendmail "$@"
453 # Usage: mail [-s <subject>] <addr> [<addr>...]
454 mail() {
455 mailref "" "$@"
458 # bang CMD... will execute the command with well-defined failure mode;
459 # set bang_action to string of the failed action ('clone', 'update', ...);
460 # re-define the bang_trap() function to do custom cleanup before bailing out
461 bang() {
462 bang_errcode=
463 bang_catch "$@"
464 [ "${bang_errcode:-0}" = "0" ] || bang_failed
467 bang_catch() {
468 bang_active=1
469 bang_cmd="$*"
470 # clean up bang_cmd for log
471 bang_cmd="${bang_cmd#eval }"
472 [ "${bang_cmd#git_ulimit }" = "$bang_cmd" ] ||
473 bang_cmd="git ${bang_cmd#git_ulimit }"
474 [ "${bang_cmd#git_fetch_q_progress }" = "$bang_cmd" ] ||
475 bang_cmd="git fetch ${bang_cmd#git_fetch_q_progress }"
476 [ "${bang_cmd#git fetch --progress }" = "$bang_cmd" ] ||
477 bang_cmd="git fetch ${bang_cmd#git fetch --progress }"
478 bang_errcode=0
479 if [ "${show_progress:-0}" != "0" ]; then
480 exec 3>&1
481 read -r bang_errcode <<-EOT || :
483 exec 4>&3 3>&1 1>&4 4>&-
484 { "$@" 3>&- || echo $? >&3; } 2>&1 | tee -i -a "$bang_log"
487 exec 3>&-
488 if [ -z "$bang_errcode" ] || [ "$bang_errcode" = "0" ]; then
489 # All right. Cool.
490 bang_active=
491 bang_cmd=
492 return;
494 else
495 if "$@" >>"$bang_log" 2>&1; then
496 # All right. Cool.
497 bang_active=
498 bang_cmd=
499 return;
500 else
501 bang_errcode="$?"
506 bang_failed() {
507 bang_active=
508 unset GIT_DIR
509 >.banged
510 cat "$bang_log" >.banglog
511 echo "" >>.banglog
512 echo "$bang_cmd failed with error code $bang_errcode" >>.banglog
513 ! [ -d htmlcache ] || { >htmlcache/changed; } 2>/dev/null || :
514 if [ "${show_progress:-0}" != "0" ]; then
515 echo ""
516 echo "$bang_cmd failed with error code $bang_errcode"
518 if [ -e .bangagain ]; then
519 git config --remove-section girocco.bang 2>/dev/null || :
520 rm -f .bangagain
522 bangcount="$(git config --int girocco.bang.count 2>/dev/null)" || :
523 bangcount=$(( ${bangcount:-0} + 1 ))
524 git config --int girocco.bang.count $bangcount
525 if [ $bangcount -eq 1 ]; then
526 git config girocco.bang.firstfail "$(TZ=UTC date "+%Y-%m-%d %T UTC")"
528 if [ $bangcount -ge $cfg_min_mirror_failure_message_count ] &&
529 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" != "true" ] &&
530 ! check_interval "girocco.bang.firstfail" $cfg_min_mirror_failure_message_interval; then
531 bangmailok="$(git config --bool gitweb.statusupdates 2>/dev/null || echo true)"
532 bangaddrs=
533 [ "$bangmailok" = "false" ] || [ -z "$mail" ] || bangaddrs="$mail"
534 [ -z "$cfg_admincc" ] || [ "$cfg_admincc" = "0" ] || [ -z "$cfg_admin" ] ||
535 if [ -z "$bangaddrs" ]; then bangaddrs="$cfg_admin"; else bangaddrs="$bangaddrs,$cfg_admin"; fi
536 rsubj=
537 [ $bangcount -le 1 ] || rsubj=" repeatedly"
538 [ -z "$bangaddrs" ] ||
540 echo "$bang_cmd failed with error code $bang_errcode"
541 echo ""
542 rsubj=
543 if [ $bangcount -gt 1 ]; then
544 echo "$bangcount consecutive update failures have occurred since $(config_get girocco.bang.firstfail)"
545 echo ""
547 echo "you will not receive any more notifications until recovery"
548 echo "this status message may be disabled on the project admin page"
549 echo ""
550 echo "Log follows:"
551 echo ""
552 cat "$bang_log"
553 } | mailref "update@$cfg_gitweburl/$proj.git" -s "[$cfg_name] $proj $bang_action failed$rsubj" "$bangaddrs"
554 git config --bool girocco.bang.messagesent true
556 bangthrottle=
557 [ $bangcount -lt 15 ] ||
558 check_interval "girocco.bang.firstfail" $(( $cfg_min_mirror_interval * 3 / 2 )) ||
559 bangthrottle=1
560 bang_trap $bangthrottle
561 [ -n "$bang_errcode" ] && [ "$bang_errcode" != "0" ] || bang_errcode=1
562 exit $bang_errcode
565 # bang_eval CMD... will evaluate the command with well-defined failure mode;
566 # Identical to bang CMD... except the command is eval'd instead of executed.
567 bang_eval() {
568 bang eval "$*"
571 bang_exit() {
572 # placeholder empty function that get called
573 # when the bang_setup EXIT trap triggers
574 # can be replaced to avoid losing a pre bang_setup
575 # trap on EXIT
579 # Default bang settings:
580 bang_setup() {
581 bang_active=
582 bang_action="lame_programmer"
583 bang_trap() { :; }
584 bang_tmpdir="${TMPDIR:-/tmp}"
585 bang_tmpdir="${bang_tmpdir%/}"
586 bang_log="$(mktemp "${bang_tmpdir:-/tmp}/repomgr-XXXXXX")"
587 is_git_dir . || {
588 echo "bang_setup called with current directory not a git directory" >&2
589 exit 1
591 trap 'rm -f "$bang_log"; bang_exit' EXIT
592 trap '[ -z "$bang_active" ] || { bang_errcode=130; bang_failed; }; exit 130' INT
593 trap '[ -z "$bang_active" ] || { bang_errcode=143; bang_failed; }; exit 143' TERM
596 # Remove banged status
597 bang_reset() {
598 rm -f .banged .bangagain .banglog
599 git config --remove-section girocco.bang 2>/dev/null || :
602 # Check to see if banged status
603 is_banged() {
604 [ -e .banged ]
607 # Check to see if banged message was sent
608 was_banged_message_sent() {
609 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" = "true" ]
612 # Progress report - if show_progress is set, shows the given message.
613 progress() {
614 [ "${show_progress:-0}" = "0" ] || echo "$*"
617 # Project config accessors; must be run in project directory
618 config_get() {
619 case "$1" in
620 *.*)
621 git config "$1";;
623 git config "gitweb.$1";;
624 esac
627 config_set() {
628 git config "gitweb.$1" "$2" && chgrp $var_group config && chmod g+w config
631 config_set_raw() {
632 git config "$1" "$2" && chgrp $var_group config && chmod g+w config
635 config_get_date_seconds() {
636 _dt="$(config_get "$1")" || :
637 [ -n "$_dt" ] || return 1
638 _ds="$(perl -I@basedir@ -MGirocco::Util -e "print parse_any_date('$_dt')")"
639 [ -n "$_ds" ] || return 1
640 echo "$_ds"
643 # Tool for checking whether given number of seconds has not passed yet
644 check_interval() {
645 os="$(config_get_date_seconds "$1")" || return 1
646 ns="$(date +%s)"
647 [ $ns -lt $(($os+$2)) ]
650 # Check if we are running with effective root permissions
651 is_root() {
652 [ "$(id -u 2>/dev/null)" = "0" ]
655 # Check to see if the single argument (default ".") is a Git directory
656 is_git_dir() {
657 # Just like Git's test except we ignore GIT_OBJECT_DIRECTORY
658 # And we are slightly more picky (must be refs/.+ not refs/.*)
659 [ $# -ne 0 ] || set -- "."
660 [ -d "$1/objects" ] && [ -x "$1/objects" ] || return 1
661 [ -d "$1/refs" ] && [ -x "$1/refs" ] || return 1
662 if [ -L "$1/HEAD" ]; then
663 _hr="$(readlink "$1/HEAD")"
664 case "$_hr" in "refs/"?*) :;; *) return 1;; esac
666 [ -f "$1/HEAD" ] && [ -r "$1/HEAD" ] || return 1
667 read -r _hr <"$1/HEAD" || return 1
668 case "$_hr" in
669 $octet20*)
670 [ "${_hr#*[!0-9a-f]}" = "$_hr" ] || return 1
671 return 0;;
672 ref:refs/?*)
673 return 0;;
674 ref:*)
675 _hr="${_hr##ref:*[ $tab]}"
676 case "$_hr" in "refs/"?*) return 0;; esac
677 esac
678 return 1
681 # Check to see if the single argument (default ".") is a directory with no refs
682 is_empty_refs_dir() {
683 [ $# -ne 0 ] || set -- "."
684 if [ -s "$1/packed-refs" ]; then
685 # could be a packed-refs file with just a '# pack-refs ..." line
686 # null hash lines and peel lines do not count either
687 _refcnt="$(( $(LC_ALL=C sed <"$1/packed-refs" \
688 -e "/^00* /d" \
689 -e "/^$octet20$hexdig* refs\/[^ $tab]*\$/!d" | wc -l) ))"
690 [ "${_refcnt:-0}" -eq 0 ] || return 1
692 if [ -d "$1/refs" ]; then
693 # quick and dirty check, doesn't try to validate contents
694 # or ignore embedded symbolic refs
695 _refcnt="$(( $(find -L "$1/refs" -type f -print 2>/dev/null | head -n 1 | LC_ALL=C wc -l) ))"
696 [ "${_refcnt:-0}" -eq 0 ] || return 1
698 # last chance a detached HEAD (we ignore any linked working trees though)
699 [ -s "$1/HEAD" ] && read -r _hr <"$1/HEAD" && [ -n "$_hr" ] || return 0
700 [ "${_hr#*[!0-9a-f]}" != "$_hr" ] || [ "${_hr#*[!0]}" = "$_hr" ] || [ "${#_hr}" -lt 40 ] || return 1
701 return 0
704 # List all Git repositories, with given prefix if specified, one-per-line
705 # All project names starting with _ are always excluded from the result
706 get_repo_list() {
707 if [ -n "$1" ]; then
708 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group | LC_ALL=C grep "^$1"
709 else
710 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group
711 fi |
712 LC_ALL=C awk -F : 'substr($1,1,1) != "_" && $2 >= 65536 {print $1}'
715 # set the variable named by the first argument to the project part (i.e. WITH
716 # the trailing ".git" but WITHOUT the leading $cfg_reporoot) of the directory
717 # specified by the second argument.
718 # This function cannot be fooled by symbolic links.
719 # If the second argument is omitted (or empty) use $(pwd -P) instead.
720 # The directory specified by the second argument must exist.
721 v_get_proj_from_dir() {
722 [ -n "$2" ] || set -- "$1" "$(pwd -P)"
723 [ -d "$2" ] || return 1
724 case "$2" in
725 "$cfg_reporoot/"?*)
726 # Simple case that does not need any fancy footwork
727 _projpart="${2#$cfg_reporoot/}"
730 _absrr="$(cd "$cfg_reporoot" && pwd -P)"
731 _abspd="$(cd "$2" && pwd -P)"
732 case "$_abspd" in
733 "$_absrr/"?*)
734 # The normal case
735 _projpart="${_abspd#$_absrr/}"
738 # Must have been reached via a symbolic link
739 # Attempt to translate using the gitdir.list file
740 # If not found use a generic "_external" leader
741 # combined with just the trailing directory name
742 _projpart=
744 [ -f "$cfg_projlist_cache_dir/gitdir.list" ] &&
745 [ -s "$cfg_projlist_cache_dir/gitdir.list" ]
746 then
747 _projpart="$(LC_ALL=C awk -v fnd="$_abspd" \
748 <"$cfg_projlist_cache_dir/gitdir.list" \
749 'NF>=2{p=$1; sub(/^[^ \t]+[ \t]+/,"");
750 if ($0 == fnd) {print p ".git"; exit;}}')" || :
752 if [ -z "$_projpart" ]; then
753 _abspd="${_abspd%/}"
754 _abspd="${_abspd%/.git}"
755 _projpart="_external/${_abspd##*/}"
758 esac
759 esac
760 case "$_projpart" in *[!/]".git/worktrees/"?*)
761 _projpart="${_projpart%.git/worktrees/*}.git"
762 esac
763 eval "$1="'"$_projpart"'
766 # Returns success if "$1" does not exist or contains only blank lines and comments
767 # The parsing rules are in Git's sha1-file.c parse_alt_odb_entry function;
768 # the format for blank lines and comments has been the same since Git v0.99.5
769 is_empty_alternates_file() {
770 [ -n "$1" ] || return 0
771 [ -e "$1" ] && [ -f "$1" ] && [ -s "$1" ] || return 0
772 [ -r "$1" ] || return 1
773 LC_ALL=C awk <"$1" '!/^$/ && !/^#/ {exit 1}'
776 # Return success if the given project name has at least one immediate child fork
777 # that has a non-empty alternates file
778 has_forks_with_alternates() {
779 _prj="${1%.git}"
780 [ -n "$_prj" ] || return 1
781 [ -d "$cfg_reporoot/$_prj" ] || return 1
782 is_git_dir "$cfg_reporoot/$_prj.git" || return 1
784 get_repo_list "$_prj/[^/:][^/:]*:" |
785 while read -r _prjname && [ -n "$_prjname" ]; do
786 is_empty_alternates_file "$cfg_reporoot/$_prjname.git/objects/info/alternates" ||
787 exit 1 # will only exit implicit subshell created by '|'
788 done
789 then
790 return 1
792 return 0
795 # returns empty string and error for empty string otherwise one of
796 # m => normal Git mirror
797 # s => mirror from svn source
798 # d => mirror from darcs source
799 # b => mirror from bzr source
800 # h => mirror from hg source
801 # w => mirror from mediawiki source
802 # f => mirror from other fast-import source
803 # note that if the string is non-empty and none of s, d, b or h match the
804 # return will always be type m regardless of whether it's a valid Git URL
805 get_url_mirror_type() {
806 case "$1" in
808 return 1
810 svn://* | svn+http://* | svn+https://* | svn+file://* | svn+ssh://*)
811 echo 's'
813 darcs://* | darcs+http://* | darcs+https://*)
814 echo 'd'
816 bzr://*)
817 echo 'b'
819 hg+http://* | hg+https://* | hg+file://* | hg+ssh://* | hg::*)
820 echo 'h'
822 mediawiki::*)
823 echo 'w'
826 echo 'm'
828 esac
829 return 0
832 # returns false for empty string
833 # returns true if the passed in url is a mirror using git fast-import
834 is_gfi_mirror_url() {
835 [ -n "$1" ] || return 1
836 case "$(get_url_mirror_type "$1" 2>/dev/null || :)" in
837 d|b|h|w|f)
838 # darcs, bzr, hg and mediawiki mirrors use git fast-import
839 # and so do generic "f" fast-import mirrors
840 return 0
843 # Don't think git-svn currently uses git fast-import
844 # And Git mirrors certainly do not
845 return 1
847 esac
848 # assume it does not use git fast-import
849 return 1
852 # returns false for empty string
853 # returns true if the passed in url is a mirror using git-svn
854 is_svn_mirror_url() {
855 [ -n "$1" ] || return 1
856 [ "$(get_url_mirror_type "$1" 2>/dev/null || :)" = "s" ]
859 # returns mirror url for gitweb.baseurl of git directory
860 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
861 # will fail if the directory does not have .nofetch and gitweb.baseurl
862 # comes back empty -- otherwise .nofetch directories succeed with a "" return
863 # automatically strips any leading "disabled " prefix before returning result
864 get_mirror_url() {
865 _gitdir="${1:-.}"
866 # always return empty for non-mirrors
867 ! [ -e "$_gitdir/.nofetch" ] || return 0
868 _url="$(GIT_DIR="$_gitdir" config_get baseurl 2>/dev/null)" || :
869 _url="${_url##* }"
870 [ -n "$_url" ] || return 1
871 printf '%s\n' "$_url"
872 return 0
875 # returns get_url_mirror_type for gitweb.baseurl of git directory
876 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
877 # will fail if the directory does not have .nofetch and gitweb.baseurl
878 # comes back empty -- otherwise .nofetch directories succeed with a "" return
879 # automatically strips any leading "disabled " prefix before testing
880 get_mirror_type() {
881 _url="$(get_mirror_url "$@")" || return 1
882 [ -n "$_url" ] || return 0
883 get_url_mirror_type "$_url"
886 # returns true if the passed in git dir (defaults to ".") is a mirror using git fast-import
887 is_gfi_mirror() {
888 _url="$(get_mirror_url "$@")" || return 1
889 is_gfi_mirror_url "$_url"
892 # returns true if the passed in git dir (defaults to ".") is a mirror using git-svn
893 is_svn_mirror() {
894 _url="$(get_mirror_url "$@")" || return 1
895 is_svn_mirror_url "$_url"
898 # current directory must already be set to Git repository
899 # if girocco.headok is already true succeeds without doing anything
900 # if rev-parse --verify HEAD succeeds sets headok=true and succeeds
901 # otherwise tries to set HEAD to a symbolic ref to refs/heads/master
902 # then refs/heads/trunk and finally the first top-level head from
903 # refs/heads/* (i.e. only two slashes in the name) and finally any
904 # existing refs/heads. The first one to succeed wins and sets headok=true
905 # and then a successful exit. Otherwise headok is left unset with a failure exit
906 # We use the girocco.headok flag to make sure we only force a valid HEAD symref
907 # when the repository is being set up -- if the HEAD is later deleted (through
908 # a push or fetch --prune) that's no longer our responsibility to fix
909 check_and_set_head() {
910 [ "$(git config --bool girocco.headok 2>/dev/null || :)" != "true" ] || return 0
911 if git rev-parse --verify --quiet HEAD >/dev/null; then
912 git config --bool girocco.headok true
913 return 0
915 for _hr in refs/heads/master refs/heads/trunk; do
916 if git rev-parse --verify --quiet "$_hr" >/dev/null; then
917 _update_head_symref "$_hr"
918 return 0
920 done
921 git for-each-ref --format="%(refname)" refs/heads 2>/dev/null |
922 while read -r _hr; do
923 case "${_hr#refs/heads/}" in */*) :;; *)
924 _update_head_symref "$_hr"
925 exit 1 # exit subshell created by "|"
926 esac
927 done || return 0
928 _hr="$(git for-each-ref --format="%(refname)" refs/heads 2>/dev/null | head -n 1)" || :
929 if [ -n "$_hr" ]; then
930 _update_head_symref "$_hr"
931 return 0
933 return 1
935 _update_head_symref() {
936 git symbolic-ref HEAD "$1"
937 git config --bool girocco.headok true
938 ! [ -d htmlcache ] || { >htmlcache/changed; } 2>/dev/null || :
941 # current directory must already be set to Git repository
942 # if the directory needs to have gc run and .needsgc is not already set
943 # then .needsgc will be set triggering a "mini" gc at the next opportunity
944 # Girocco shouldn't generate any loose objects but we check for that anyway
945 check_and_set_needsgc() {
946 # If there's a .needspack file and ANY loose objects with a newer timestamp
947 # then also set .needsgc otherwise remove it. The only caller that may set
948 # .needspack is a mirror therefore we don't have to worry about removing a
949 # .needspack out from under a simultaneous creator. We always do this and
950 # do it first to try and avoid leaving a stale .needspack lying around.
951 if [ -e .needspack ]; then
952 _objfiles=
953 _objfiles="$(( $(find -L objects/$octet -maxdepth 1 -newer .needspack -name "$octet19*" -type f -print 2>/dev/null |
954 head -n 1 | LC_ALL=C wc -l) +0 ))"
955 if [ "${_objfiles:-0}" = "0" ]; then
956 rm -f .needspack
957 else
958 [ -e .needsgc ] || >.needsgc
961 ! [ -e .needsgc ] || return 0
962 _packs=
963 { _packs="$(list_packs --quiet --count --exclude-no-idx --exclude-keep objects/pack || :)" || :; } 2>/dev/null
964 if [ "${_packs:-0}" -ge 20 ]; then
965 >.needsgc
966 return 0
968 _logfiles=
969 { _logfiles="$(($(find -L reflogs -maxdepth 1 -type f -print | wc -l || :)+0))" || :; } 2>/dev/null
970 if [ "${_logfiles:-0}" -ge 50 ]; then
971 >.needsgc
972 return 0
974 # Truly git gc only checks the number of objects in the objects/17 directory
975 # We check for -ge 10 which should make the probability of having more than
976 # 5120 (20*256) loose objects present when there are less than 10 in
977 # objects/17 vanishingly small (20 is the threshold we use for pack files)
978 _objfiles=
979 ! [ -d objects/17 ] ||
980 { _objfiles="$(($(find -L objects/17 -type f -name "$octet19*" -print | wc -l || :)+0))" || :; } 2>/dev/null
981 if [ "${_objfiles:-0}" -ge 10 ]; then
982 >.needsgc
983 return 0
987 # current directory must already be set to Git repository
988 # remove any existing stale .lock files anywhere in the refs hierarchy
989 # mirror .lock files are considered "stale" after 60m whereas push projects
990 # need 12h for a .lock file to be considered stale.
991 clear_stale_ref_locks() {
992 # Quick sanity check just in case
993 [ -f HEAD ] && [ -s HEAD ] && [ -d objects ] && [ -d refs ] || return 1
994 _stale=60
995 [ ! -e .nofetch ] || _stale=720
996 # Clear any stale top-level ref locks
997 find . -maxdepth 1 -name '*?.lock' -type f -mmin +$_stale -exec rm -f '{}' + >/dev/null 2>&1 || :
998 if [ -d worktrees ]; then
999 # Clear any worktrees stale top-level ref locks
1000 find -H worktrees -mindepth 2 -maxdepth 2 -name '*?.lock' -type f -mmin +$_stale -exec rm -f '{}' + >/dev/null 2>&1 || :
1002 # Clear any stale ref locks within the refs hierarchy itself
1003 find -H refs -mindepth 1 -name '*?.lock' -type f -mmin +$_stale -exec rm -f '{}' + >/dev/null 2>&1 || :
1004 return 0
1007 # A well-known UTF-8 locale is required for some of the fast-import providers
1008 # in order to avoid mangling characters. Ideally we could use "POSIX.UTF-8"
1009 # but that is not reliably UTF-8 but rather usually US-ASCII.
1010 # We parse the output of `locale -a` and select a suitable UTF-8 locale at
1011 # install time and store that in $var_utf8_locale if one is found.
1012 # If we cannot find one in the `locale -a` output then we just use a well-known
1013 # UTF-8 locale and hope for the best. We set LC_ALL to our choice and export
1014 # it. We only set this temporarily when running the fast-import providers.
1015 set_utf8_locale() {
1016 LC_ALL="${var_utf8_locale:-en_US.UTF-8}"
1017 export LC_ALL
1020 # hg-fast-export | git fast-import with error handling in current directory GIT_DIR
1021 git_hg_fetch() (
1022 set_utf8_locale
1023 _python="${PYTHON:-python}"
1024 rm -f hg2git-marks.old hg2git-marks.new
1025 if [ -f hg2git-marks ] && [ -s hg2git-marks ]; then
1026 LC_ALL=C sed 's/^:\([^ ][^ ]*\) \([^ ][^ ]*\)$/\2 \1/' <hg2git-marks | {
1027 if [ -n "$var_have_git_185" ]; then
1028 git cat-file --batch-check=':%(rest) %(objectname)'
1029 else
1030 LC_ALL=C sed 's/^\([^ ][^ ]*\) \([^ ][^ ]*\)$/:\2 \1/'
1032 } | LC_ALL=C sed '/ missing$/d' >hg2git-marks.old
1033 if [ -n "$var_have_git_171" ] &&
1034 git rev-parse --quiet --verify refs/notes/hg >/dev/null; then
1035 if [ -z "$var_have_git_185" ] ||
1036 ! LC_ALL=C cmp -s hg2git-marks hg2git-marks.old; then
1037 _nm='hg-fast-export'
1038 GIT_AUTHOR_NAME="$_nm"
1039 GIT_COMMITTER_NAME="$_nm"
1040 GIT_AUTHOR_EMAIL="$_nm"
1041 GIT_COMMITTER_EMAIL="$_nm"
1042 export GIT_AUTHOR_NAME
1043 export GIT_COMMITTER_NAME
1044 export GIT_AUTHOR_EMAIL
1045 export GIT_COMMITTER_EMAIL
1046 git notes --ref=refs/notes/hg prune
1047 unset GIT_AUTHOR_NAME
1048 unset GIT_COMMITTER_NAME
1049 unset GIT_AUTHOR_EMAIL
1050 unset GIT_COMMITTER_EMAIL
1053 else
1054 >hg2git-marks.old
1056 _err1=
1057 _err2=
1058 exec 3>&1
1059 { read -r _err1 || :; read -r _err2 || :; } <<-EOT
1061 exec 4>&3 3>&1 1>&4 4>&-
1063 _e1=0
1064 _af="$(git config hg.authorsfile)" || :
1065 _cmd='GIT_DIR="$(pwd)" "$_python" "$cfg_basedir/bin/hg-fast-export.py" \
1066 --repo "$(pwd)/repo.hg" \
1067 --marks "$(pwd)/hg2git-marks.old" \
1068 --mapping "$(pwd)/hg2git-mapping" \
1069 --heads "$(pwd)/hg2git-heads" \
1070 --status "$(pwd)/hg2git-state" \
1071 -U unknown --force --flatten --hg-hash'
1072 [ -z "$_af" ] || _cmd="$_cmd"' --authors "$_af"'
1073 eval "$_cmd" 3>&- || _e1=$?
1074 echo $_e1 >&3
1077 _e2=0
1078 git_ulimit fast-import \
1079 --import-marks="$(pwd)/hg2git-marks.old" \
1080 --export-marks="$(pwd)/hg2git-marks.new" \
1081 --export-pack-edges="$(pwd)/gfi-packs" \
1082 --force 3>&- || _e2=$?
1083 echo $_e2 >&3
1087 exec 3>&-
1088 [ "$_err1" = 0 ] && [ "$_err2" = 0 ] || return 1
1089 mv -f hg2git-marks.new hg2git-marks
1090 rm -f hg2git-marks.old
1091 git for-each-ref --format='%(refname) %(objectname)' refs/heads |
1092 LC_ALL=C sed -e 's,^refs/heads/,:,' >hg2git-heads