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