shlib.sh: make sure sendmail.pl config is set
[girocco.git] / shlib.sh
blobcd97ada6d85562f2d73d8711f97d7a4c0fd07c87
1 #!/bin/sh
3 # This is generic shell library for all the scripts used by Girocco;
4 # most importantly, it introduces all the $cfg_* shell variables.
6 # SHA-1 patterns
7 octet='[0-9a-f][0-9a-f]'
8 octet4="$octet$octet$octet$octet"
9 octet19="$octet4$octet4$octet4$octet4$octet$octet$octet"
10 octet20="$octet4$octet4$octet4$octet4$octet4"
11 nullsha="0000000000000000000000000000000000000000"
12 # tab
13 tab="$(printf '\t')"
15 # set a sane umask that never excludes any user or group permissions
16 umask $(printf '0%03o' $(( $(umask) & ~0770 )) )
18 vcmp() {
19 # Compare $1 to $2 each of which must match \d+(\.\d+)*
20 # An empty string ('') for $1 or $2 is treated like 0
21 # Outputs:
22 # -1 if $1 < $2
23 # 0 if $1 = $2
24 # 1 if $1 > $2
25 # Note that `vcmp 1.8 1.8.0.0.0.0` correctly outputs 0.
26 while
27 _a="${1%%.*}"
28 _b="${2%%.*}"
29 [ -n "$_a" -o -n "$_b" ]
31 if [ "${_a:-0}" -lt "${_b:-0}" ]; then
32 echo -1
33 return
34 elif [ "${_a:-0}" -gt "${_b:-0}" ]; then
35 echo 1
36 return
38 _a2="${1#$_a}"
39 _b2="${2#$_b}"
40 set -- "${_a2#.}" "${_b2#.}"
41 done
42 echo 0
45 get_girocco_config_pm_var_list() {
46 # Export all the variables from Girocco::Config to suitable var= lines
47 # prefixing them with 'cfg_'. E.g. $cfg_admin is admin's mail address now
48 # and also setting a 'defined_cfg_' prefix to 1 if they are not undef.
49 __girocco_conf="$GIROCCO_CONF"
50 [ -n "$__girocco_conf" ] || __girocco_conf="Girocco::Config"
51 [ -z "$basedir" ] || __girocco_extrainc="-I$basedir"
52 perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf -le \
53 'foreach (sort {uc($a) cmp uc($b)} keys %Girocco::Config::) {
54 my $val = ${$Girocco::Config::{$_}}; defined($val) or $val="";
55 $val =~ s/([\\"\$\`])/\\$1/gos;
56 $val =~ s/(?:\r\n|\r|\n)$//os;
57 print "cfg_$_=\"$val\"";
58 print "defined_cfg_$_=",
59 (defined(${$Girocco::Config::{$_}})?"1":"");
63 get_girocco_config_var_list() (
64 # Same as get_girocco_config_pm_var_list except that
65 # the following variables (all starting with var_) are added:
66 # var_group cfg_owning_group if defined otherwise `id -gn`
67 # var_git_ver The version number part from `git version`
68 # var_git_exec_path The result of $cfg_git_bin --exec-dir
69 # var_sh_bin Full path to the posix sh interpreter to use
70 # var_perl_bin Full path to the perl interpreter to use
71 # var_gzip_bin Full path to the gzip executable to use
72 # var_have_git_171 Set to 1 if git version >= 1.7.1 otherwise ''
73 # var_have_git_172 Set to 1 if git version >= 1.7.2 otherwise ''
74 # var_have_git_173 Set to 1 if git version >= 1.7.3 otherwise ''
75 # var_have_git_1710 Set to 1 if git version >= 1.7.10 otherwise ''
76 # var_have_git_185 Set to 1 if git version >= 1.8.5 otherwise ''
77 # var_window_memory Value to use for repack --window-memory=
78 # var_big_file_threshold Value to use for core.bigFileThreshold
79 # var_redelta_threshold Recompute deltas if no more than this many objs
80 # var_upload_window If not "", pack.window to use for upload-pack
81 # var_log_window_size Value to use for git-svn --log-window-size=
82 # var_utf8_locale Value to use for a UTF-8 locale if available
83 # var_xargs_r A "-r" if xargs needs it to behave correctly
84 # var_du_exclude Option to exclude PATTERN from du if available
85 # var_du_follow Option to follow command line sym links if available
86 _cfg_vars="$(get_girocco_config_pm_var_list)"
87 eval "$_cfg_vars"
88 printf '%s\n' "$_cfg_vars"
89 printf 'var_group=%s\n' "${cfg_owning_group:-$(id -gn)}"
90 _gver="$("$cfg_git_bin" version 2>/dev/null | \
91 LC_ALL=C sed -ne 's/^[^0-9]*\([0-9][0-9]*\(\.[0-9][0-9]*\)*\).*$/\1/p')"
92 printf 'var_git_ver=%s\n' "$_gver"
93 printf 'var_git_exec_path="%s"\n' "$("$cfg_git_bin" --exec-path 2>/dev/null)"
94 printf 'var_sh_bin="%s"\n' "${cfg_posix_sh_bin:-/bin/sh}"
95 printf 'var_perl_bin="%s"\n' "${cfg_perl_bin:-$(unset -f perl; command -v perl)}"
96 printf 'var_gzip_bin="%s"\n' "${cfg_gzip_bin:-$(unset -f gzip; command -v gzip)}"
97 printf 'var_have_git_171=%s\n' "$([ $(vcmp "$_gver" 1.7.1) -ge 0 ] && echo 1)"
98 printf 'var_have_git_172=%s\n' "$([ $(vcmp "$_gver" 1.7.2) -ge 0 ] && echo 1)"
99 printf 'var_have_git_173=%s\n' "$([ $(vcmp "$_gver" 1.7.3) -ge 0 ] && echo 1)"
100 printf 'var_have_git_1710=%s\n' "$([ $(vcmp "$_gver" 1.7.10) -ge 0 ] && echo 1)"
101 printf 'var_have_git_185=%s\n' "$([ $(vcmp "$_gver" 1.8.5) -ge 0 ] && echo 1)"
102 __girocco_conf="$GIROCCO_CONF"
103 [ -n "$__girocco_conf" ] || __girocco_conf="Girocco::Config"
104 [ -z "$basedir" ] || __girocco_extrainc="-I$basedir"
105 printf "var_window_memory=%s\n" \
106 "$(perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf \
107 -MGirocco::Util -e 'print calc_windowmemory')"
108 printf "var_big_file_threshold=%s\n" \
109 "$(perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf \
110 -MGirocco::Util -e 'print calc_bigfilethreshold')"
111 printf "var_redelta_threshold=%s\n" \
112 "$(perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf \
113 -MGirocco::Util -e 'print calc_redeltathreshold')"
114 if [ -n "$cfg_upload_pack_window" ] && [ "$cfg_upload_pack_window" -ge 2 ] && \
115 [ "$cfg_upload_pack_window" -le 50 ]; then
116 printf "var_upload_window=%s\n" "$cfg_upload_pack_window"
117 else
118 printf "var_upload_window=%s\n" ""
120 printf 'var_log_window_size=%s\n' "${cfg_svn_log_window_size:-250}"
121 # We parse the output of `locale -a` and select a suitable UTF-8 locale.
122 _guess_locale="$(locale -a | LC_ALL=C grep -viE '^(posix|c)(\..*)?$' | \
123 LC_ALL=C grep -iE '\.utf-?8$' | LC_ALL=C sed -e 's/\.[Uu][Tt][Ff]-*8$//' | \
124 LC_ALL=C sed -e '/en_US/ s/^/0 /; /en_US/ !s/^/1 /' | LC_ALL=C sort | \
125 head -n 1 | LC_ALL=C cut -d ' ' -f 2)"
126 [ -z "$_guess_locale" ] || printf 'var_utf8_locale=%s.UTF-8\n' "$_guess_locale"
127 # On some broken platforms running xargs without -r and empty input runs the command
128 printf 'var_xargs_r=%s\n' "$(: | command xargs echo -r)"
129 # The disk usage report produces better numbers if du has an exclude option
130 _x0="$(basename "$0")"
131 _x0="${_x0%?}?*"
132 for _duopt in --exclude -I; do
133 if _test="$(du $_duopt 's?lib.s*' $_duopt "$_x0" "$0" 2>/dev/null)" && [ -z "$_test" ]; then
134 printf 'var_du_exclude=%s\n' "$_duopt"
135 break
137 done
138 if _test="$(du -H "$0" 2>/dev/null)" && [ -n "$_test" ]; then
139 printf 'var_du_follow=%s\n' "-H"
140 break
144 # If basedir has been replaced, and shlib_vars.sh exists, get the config
145 # definitions from it rather than running Perl.
146 if [ "@basedir@" = '@'basedir'@' ] || ! [ -r "@basedir@/shlib_vars.sh" ]; then
147 # Import all the variables from Girocco::Config to the local environment,
148 eval "$(get_girocco_config_var_list)"
149 else
150 # Import the variables from shlib_vars.sh which avoids needlessly
151 # running another copy of Perl
152 . "@basedir@/shlib_vars.sh"
155 # git_add_config "some.var=value"
156 # every ' in value must be replaced with the 4-character sequence '\'' before
157 # calling this function or Git will barf. Will not be effective unless running
158 # Git version 1.7.3 or later.
159 git_add_config() {
160 GIT_CONFIG_PARAMETERS="${GIT_CONFIG_PARAMETERS:+$GIT_CONFIG_PARAMETERS }'$1'"
161 export GIT_CONFIG_PARAMETERS
164 # Make sure we have a reproducible environment by using a controlled HOME dir
165 XDG_CONFIG_HOME="$cfg_chroot/var/empty"
166 HOME="$cfg_chroot/etc/girocco"
167 TMPDIR="/tmp"
168 GIT_CONFIG_NOSYSTEM=1
169 GIT_ATTR_NOSYSTEM=1
170 GIT_NO_REPLACE_OBJECTS=1
171 GIT_TERMINAL_PROMPT=0
172 GIT_ASKPASS="$cfg_basedir/bin/git-askpass-password"
173 export XDG_CONFIG_HOME
174 export HOME
175 export TMPDIR
176 export GIT_CONFIG_NOSYSTEM
177 export GIT_ATTR_NOSYSTEM
178 export GIT_NO_REPLACE_OBJECTS
179 export GIT_TERMINAL_PROMPT
180 export GIT_ASKPASS
181 unset GIT_USER_AGENT
182 unset GIT_HTTP_USER_AGENT
183 if [ -n "$defined_cfg_git_client_ua" ]; then
184 GIT_USER_AGENT="$cfg_git_client_ua"
185 export GIT_USER_AGENT
186 GIT_HTTP_USER_AGENT="$cfg_git_client_ua"
187 export GIT_HTTP_USER_AGENT
189 unset GIT_CONFIG_PARAMETERS
190 git_add_config "core.ignoreCase=false"
191 if [ -n "$cfg_git_no_mmap" ]; then
192 # Just like compiling with NO_MMAP
193 git_add_config "core.packedGitWindowSize=1m"
194 else
195 # Always use the 32-bit default (32m) even on 64-bit to avoid memory blowout
196 git_add_config "core.packedGitWindowSize=32m"
198 [ -z "$var_big_file_threshold" ] ||
199 git_add_config "core.bigFileThreshold=$var_big_file_threshold"
201 # Make sure any sendmail.pl config is always available
202 unset SENDMAIL_PL_HOST
203 unset SENDMAIL_PL_PORT
204 unset SENDMAIL_PL_NCBIN
205 unset SENDMAIL_PL_NCOPT
206 [ -z "$cfg_sendmail_pl_host" ] || { SENDMAIL_PL_HOST="$cfg_sendmail_pl_host" && export SENDMAIL_PL_HOST; }
207 [ -z "$cfg_sendmail_pl_port" ] || { SENDMAIL_PL_PORT="$cfg_sendmail_pl_port" && export SENDMAIL_PL_PORT; }
208 [ -z "$cfg_sendmail_pl_ncbin" ] || { SENDMAIL_PL_NCBIN="$cfg_sendmail_pl_ncbin" && export SENDMAIL_PL_NCBIN; }
209 [ -z "$cfg_sendmail_pl_ncopt" ] || { SENDMAIL_PL_NCOPT="$cfg_sendmail_pl_ncopt" && export SENDMAIL_PL_NCOPT; }
211 # Extra GIT variables that generally ought to be cleared, but whose clearing
212 # could potentially interfere with the correct operation of hook scripts so
213 # they are segregated into a separate function for use as appropriate
214 clean_git_env() {
215 unset GIT_ALTERNATE_OBJECT_DIRECTORIES
216 unset GIT_CONFIG
217 unset GIT_DIR
218 unset GIT_GRAFT_FILE
219 unset GIT_INDEX_FILE
220 unset GIT_OBJECT_DIRECTORY
221 unset GIT_NAMESPACE
224 # We cannot use a git() {} or nc_openbsd() {} function to redirect git
225 # and nc_openbsd to the desired executables because when using
226 # "ENV_VAR=xxx func" the various /bin/sh implementations behave in various
227 # different and unexpected ways:
228 # a) treat "ENV_VAR=xxx" like a separate, preceding "export ENV_VAR=xxx"
229 # b) treat "ENV_VAR=xxx" like a separate, prededing "ENV_VAR=xxx"
230 # c) treat "ENV_VAR=xxx" like a temporary setting only while running func
231 # None of these are good. We want a temporary "export ENV_VAR=xxx"
232 # setting only while running func which none of the /bin/sh's do.
234 # Instead we'd like to use an alias that provides the desired behavior without
235 # any of the bad (a), (b) or (c) effects.
237 # However, unfortunately, some of the crazy /bin/sh implementations do not
238 # recognize alias expansions when preceded by variable assignments!
240 # So we are left with git() {} and nc_openbsd() {} functions and in the
241 # case of git() {} we can compensate for (b) and (c) failing to export
242 # but not (a) and (b) persisting the values so the caller will simply
243 # have to beware and explicitly unset any variables that should not persist
244 # beyond the function call itself.
246 git() (
247 [ "${GIT_DIR+set}" = "set" ] && export GIT_DIR
248 [ "${GIT_SSL_NO_VERIFY+set}" = "set" ] && export GIT_SSL_NO_VERIFY
249 [ "${GIT_TRACE_PACKET+set}" = "set" ] && export GIT_TRACE_PACKET
250 [ "${GIT_USER_AGENT+set}" = "set" ] && export GIT_USER_AGENT
251 [ "${GIT_HTTP_USER_AGENT+set}" = "set" ] && export GIT_HTTP_USER_AGENT
252 exec "$cfg_git_bin" "$@"
255 # see comments for git() -- callers must explicitly export all variables
256 # intended for the commands these functions run before calling them
257 perl() { command "${var_perl_bin:-perl}" "$@"; }
258 gzip() { command "${var_gzip_bin:-gzip}" "$@"; }
260 nc_openbsd() { "$cfg_nc_openbsd_bin" "$@"; }
262 list_packs() { command "$cfg_basedir/bin/list_packs" "$@"; }
264 strftime() { command "$cfg_basedir/bin/strftime" "$@"; }
266 # Some platforms' broken xargs runs the command always at least once even if
267 # there's no input unless given a special option. Automatically supply the
268 # option on those platforms by providing an xargs function.
269 xargs() { command xargs $var_xargs_r "$@"; }
271 _addrlist() {
272 _list=
273 for _addr in "$@"; do
274 [ -z "$_list" ] || _list="$_list, "
275 _list="$_list$_addr"
276 done
277 echo "$_list"
280 _sendmail() {
281 _mailer="${cfg_sendmail_bin:-/usr/sbin/sendmail}"
282 if [ -n "$cfg_sender" ]; then
283 "$_mailer" -i -f "$cfg_sender" "$@"
284 else
285 "$_mailer" -i "$@"
289 mail() {
290 _subject=
291 if [ "$1" = "-s" ]; then
292 shift
293 _subject="$1"
294 shift
297 echo "From: \"$cfg_name\" ($cfg_title) <$cfg_admin>"
298 echo "To: $(_addrlist "$@")"
299 [ -z "$_subject" ] || echo "Subject: $_subject"
300 echo "MIME-Version: 1.0"
301 echo "Content-Type: text/plain; charset=utf-8"
302 echo "Content-Transfer-Encoding: 8bit"
303 [ -n "$cfg_suppress_x_girocco" ] || echo "X-Girocco: $cfg_gitweburl"
304 echo "Auto-Submitted: auto-generated"
305 echo ""
307 } | _sendmail "$@"
310 # bang CMD... will execute the command with well-defined failure mode;
311 # set bang_action to string of the failed action ('clone', 'update', ...);
312 # re-define the bang_trap() function to do custom cleanup before bailing out
313 bang() {
314 bang_active=1
315 bang_cmd="$*"
316 bang_errcode=0
317 if [ -n "$show_progress" ]; then
318 exec 3>&1
319 read -r bang_errcode <<-EOT || :
321 exec 4>&3 3>&1 1>&4 4>&-
322 { "$@" 3>&- || echo $? >&3; } 2>&1 | tee -i -a "$bang_log"
325 exec 3>&-
326 if [ -z "$bang_errcode" ] || [ "$bang_errcode" = "0" ]; then
327 # All right. Cool.
328 bang_active=
329 bang_cmd=
330 return;
332 else
333 if "$@" >>"$bang_log" 2>&1; then
334 # All right. Cool.
335 bang_active=
336 bang_cmd=
337 return;
338 else
339 bang_errcode="$?"
342 bang_failed
345 bang_failed() {
346 bang_active=
347 unset GIT_DIR
348 touch .banged
349 cat "$bang_log" > .banglog
350 echo "" >> .banglog
351 echo "$bang_cmd failed with error code $bang_errcode" >> .banglog
352 if [ -n "$show_progress" ]; then
353 echo ""
354 echo "$bang_cmd failed with error code $bang_errcode"
356 if [ -e .bangagain ]; then
357 git config --remove-section girocco.bang 2>/dev/null || :
358 rm -f .bangagain
360 bangcount="$(git config --int girocco.bang.count 2>/dev/null || :)"
361 : ${bangcount:=0}
362 bangcount=$(( $bangcount + 1 ))
363 git config --int girocco.bang.count $bangcount
364 if [ $bangcount -eq 1 ]; then
365 git config girocco.bang.firstfail "$(TZ=UTC date "+%Y-%m-%d %T UTC")"
367 if [ $bangcount -ge $cfg_min_mirror_failure_message_count ] && \
368 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" != "true" ] && \
369 ! check_interval "girocco.bang.firstfail" $cfg_min_mirror_failure_message_interval; then
370 bangmailok="$(git config --bool gitweb.statusupdates 2>/dev/null || echo true)"
371 bangaddrs=''
372 [ "$bangmailok" = "false" -o -z "$mail" ] || bangaddrs="$mail"
373 [ -z "$cfg_admincc" -o "$cfg_admincc" = "0" -o -z "$cfg_admin" ] ||
374 if [ -z "$bangaddrs" ]; then bangaddrs="$cfg_admin"; else bangaddrs="$bangaddrs,$cfg_admin"; fi
375 rsubj=
376 [ $bangcount -le 1 ] || rsubj=" repeatedly"
377 [ -z "$bangaddrs" ] ||
379 echo "$bang_cmd failed with error code $bang_errcode"
380 echo ""
381 rsubj=
382 if [ $bangcount -gt 1 ]; then
383 echo "$bangcount consecutive update failures have occurred since $(config_get girocco.bang.firstfail)"
384 echo ""
386 echo "you will not receive any more notifications until recovery"
387 echo "this status message may be disabled on the project admin page"
388 echo ""
389 echo "Log follows:"
390 echo ""
391 cat "$bang_log"
392 } | mail -s "[$cfg_name] $proj $bang_action failed$rsubj" "$bangaddrs"
393 git config --bool girocco.bang.messagesent true
395 bangthrottle=
396 [ $bangcount -lt 15 ] || \
397 check_interval "girocco.bang.firstfail" $(( $cfg_min_mirror_interval * 3 / 2 )) || \
398 bangthrottle=1
399 bang_trap $bangthrottle
400 [ -n "$bang_errcode" ] && [ "$bang_errcode" != "0" ] || bang_errcode=1
401 exit $bang_errcode
404 # bang_eval CMD... will evaluate the command with well-defined failure mode;
405 # Identical to bang CMD... except the command is eval'd instead of executed.
406 bang_eval() {
407 bang eval "$*"
410 # Default bang settings:
411 bang_setup() {
412 bang_active=
413 bang_action="lame_programmer"
414 bang_trap() { :; }
415 bang_log="$(mktemp -t repomgr-XXXXXX)"
416 is_git_dir . || {
417 echo "bang_setup called with current directory not a git directory" >&2
418 exit 1
420 trap 'rm -f "$bang_log"' EXIT
421 trap '[ -z "$bang_active" ] || { bang_errcode=130; bang_failed; }; exit 130' INT
422 trap '[ -z "$bang_active" ] || { bang_errcode=143; bang_failed; }; exit 143' TERM
425 # Remove banged status
426 bang_reset() {
427 rm -f .banged .bangagain .banglog
428 git config --remove-section girocco.bang 2>/dev/null || :
431 # Check to see if banged status
432 is_banged() {
433 [ -e .banged ]
436 # Check to see if banged message was sent
437 was_banged_message_sent() {
438 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" = "true" ]
441 # Progress report - if show_progress is set, shows the given message.
442 progress() {
443 [ ! -n "$show_progress" ] || echo "$@"
446 # Project config accessors; must be run in project directory
447 config_get() {
448 case "$1" in
449 *.*)
450 git config "$1";;
452 git config "gitweb.$1";;
453 esac
456 config_set() {
457 git config "gitweb.$1" "$2" && chgrp $var_group config && chmod g+w config
460 config_set_raw() {
461 git config "$1" "$2" && chgrp $var_group config && chmod g+w config
464 config_get_date_seconds() {
465 _dt="$(config_get "$1" || :)"
466 [ -n "$_dt" ] || return 1
467 _ds="$(perl -I@basedir@ -MGirocco::Util -e "print parse_any_date('$_dt')")"
468 [ -n "$_ds" ] || return 1
469 echo "$_ds"
472 # Tool for checking whether given number of seconds has not passed yet
473 check_interval() {
474 os="$(config_get_date_seconds "$1")" || return 1
475 ns="$(date +%s)"
476 [ $ns -lt $(($os+$2)) ]
479 # Check if we are running with effective root permissions
480 is_root() {
481 [ "$(id -u 2>/dev/null)" = "0" ]
484 # Check to see if the single argument is a Git directory
485 is_git_dir() {
486 # Just like Git's test except we ignore GIT_OBJECT_DIRECTORY
487 # And we are slightly more picky (must be refs/.+ not refs/.*)
488 [ -d "$1/objects" -a -x "$1/objects" ] || return 1
489 [ -d "$1/refs" -a -x "$1/refs" ] || return 1
490 if [ -L "$1/HEAD" ]; then
491 _hr="$(readlink "$1/HEAD")"
492 case "$_hr" in "refs/"?*) :;; *) return 1;; esac
494 [ -f "$1/HEAD" -a -r "$1/HEAD" ] || return 1
495 read -r _hr <"$1/HEAD" || return 1
496 case "$_hr" in
497 $octet20 | ref:refs/?*)
498 return 0;;
499 ref:*)
500 _hr="${_hr##ref:*[ $tab]}"
501 case "$_hr" in "refs/"?*) return 0;; esac
502 esac
503 return 1
506 # List all Git repositories, with given prefix if specified, one-per-line
507 # All project names starting with _ are always excluded from the result
508 get_repo_list() {
509 if [ -n "$1" ]; then
510 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group | LC_ALL=C grep "^$1"
511 else
512 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group
513 fi | while IFS=: read name id; do
514 [ $id -lt 65536 ] || case "$name" in _*) :;; ?*) echo "$name"; esac
515 done
518 # Return success if the given project name has any forks
519 has_forks() {
520 _prj="${1%.git}"
521 [ -n "$_prj" ] || return 1
522 [ -d "$cfg_reporoot/$_prj" ] || return 1
523 is_git_dir "$cfg_reporoot/$_prj.git" || return 1
524 test $(get_repo_list "$_prj/[^/][^/]*:" | LC_ALL=C wc -l) -gt 0
527 # returns empty string and error for empty string otherwise one of
528 # m => normal Git mirror
529 # s => mirror from svn source
530 # d => mirror from darcs source
531 # b => mirror from bzr source
532 # h => mirror from hg source
533 # w => mirror from mediawiki source
534 # f => mirror from other fast-import source
535 # note that if the string is non-empty and none of s, d, b or h match the
536 # return will always be type m regardless of whether it's a valid Git URL
537 get_url_mirror_type() {
538 case "$1" in
540 return 1
542 svn://* | svn+http://* | svn+https://* | svn+file://* | svn+ssh://*)
543 echo 's'
545 darcs://*)
546 echo 'd'
548 bzr://*)
549 echo 'b'
551 hg+http://* | hg+https://* | hg+file://* | hg+ssh://* | hg::*)
552 echo 'h'
554 mediawiki::*)
555 echo 'w'
558 echo 'm'
560 esac
561 return 0
564 # returns false for empty string
565 # returns true if the passed in url is a mirror using git fast-import
566 is_gfi_mirror_url() {
567 [ -n "$1" ] || return 1
568 case "$(get_url_mirror_type "$1" 2>/dev/null || :)" in
569 d|b|h|w|f)
570 # darcs, bzr, hg and mediawiki mirrors use git fast-import
571 # and so do generic "f" fast-import mirrors
572 return 0
575 # Don't think git-svn currently uses git fast-import
576 # And Git mirrors certainly do not
577 return 1
579 esac
580 # assume it does not use git fast-import
581 return 1
584 # returns false for empty string
585 # returns true if the passed in url is a mirror using git-svn
586 is_svn_mirror_url() {
587 [ -n "$1" ] || return 1
588 [ "$(get_url_mirror_type "$1" 2>/dev/null || :)" = "s" ]
591 # returns mirror url for gitweb.baseurl of git directory
592 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
593 # will fail if the directory does not have .nofetch and gitweb.baseurl
594 # comes back empty -- otherwise .nofetch directories succeed with a "" return
595 # automatically strips any leading "disabled " prefix before returning result
596 get_mirror_url() {
597 _gitdir="${1:-.}"
598 # always return empty for non-mirrors
599 [ ! -e "$_gitdir/.nofetch" ] || return 0
600 _url="$(GIT_DIR="$_gitdir" config_get baseurl 2>/dev/null || :)"
601 _url="${_url##* }"
602 [ -n "$_url" ] || return 1
603 printf '%s\n' "$_url"
604 return 0
607 # returns get_url_mirror_type for gitweb.baseurl of git directory
608 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
609 # will fail if the directory does not have .nofetch and gitweb.baseurl
610 # comes back empty -- otherwise .nofetch directories succeed with a "" return
611 # automatically strips any leading "disabled " prefix before testing
612 get_mirror_type() {
613 _url="$(get_mirror_url "$@")" || return 1
614 get_url_mirror_type "$_url"
617 # returns true if the passed in git dir (defaults to ".") is a mirror using git fast-import
618 is_gfi_mirror() {
619 _url="$(get_mirror_url "$@")" || return 1
620 is_gfi_mirror_url "$_url"
623 # returns true if the passed in git dir (defaults to ".") is a mirror using git-svn
624 is_svn_mirror() {
625 _url="$(get_mirror_url "$@")" || return 1
626 is_svn_mirror_url "$_url"
629 # current directory must already be set to Git repository
630 # if girocco.headok is already true succeeds without doing anything
631 # if rev-parse --verify HEAD succeeds sets headok=true and succeeds
632 # otherwise tries to set HEAD to a symbolic ref to refs/heads/master
633 # then refs/heads/trunk and finally the first top-level head from
634 # refs/heads/* (i.e. only two slashes in the name) and finally any
635 # existing refs/heads. The first one to succeed wins and sets headok=true
636 # and then a successful exit. Otherwise headok is left unset with a failure exit
637 # We use the girocco.headok flag to make sure we only force a valid HEAD symref
638 # when the repository is being set up -- if the HEAD is later deleted (through
639 # a push or fetch --prune) that's no longer our responsibility to fix
640 check_and_set_head() {
641 [ "$(git config --bool girocco.headok 2>/dev/null || :)" != "true" ] || return 0
642 if git rev-parse --verify --quiet HEAD >/dev/null; then
643 git config --bool girocco.headok true
644 return 0
646 for _hr in refs/heads/master refs/heads/trunk; do
647 if git rev-parse --verify --quiet "$_hr"; then
648 _update_head_symref "$_hr"
649 return 0
651 done
652 git for-each-ref --format="%(refname)" refs/heads 2>/dev/null |
653 while read -r _hr; do
654 case "${_hr#refs/heads/}" in */*) :;; *)
655 _update_head_symref "$_hr"
656 exit 1 # exit subshell created by "|"
657 esac
658 done || return 0
659 _hr="$(git for-each-ref --format="%(refname)" refs/heads 2>/dev/null | head -n 1 || :)"
660 if [ -n "$_hr" ]; then
661 _update_head_symref "$_hr"
662 return 0
664 return 1
666 _update_head_symref() {
667 git symbolic-ref HEAD "$1"
668 git config --bool girocco.headok true
669 ! [ -d htmlcache ] || { >htmlcache/changed; } 2>/dev/null || :
672 # A well-known UTF-8 locale is required for some of the fast-import providers
673 # in order to avoid mangling characters. Ideally we could use "POSIX.UTF-8"
674 # but that is not reliably UTF-8 but rather usually US-ASCII.
675 # We parse the output of `locale -a` and select a suitable UTF-8 locale at
676 # install time and store that in $var_utf8_locale if one is found.
677 # If we cannot find one in the `locale -a` output then we just use a well-known
678 # UTF-8 locale and hope for the best. We set LC_ALL to our choice and export
679 # it. We only set this temporarily when running the fast-import providers.
680 set_utf8_locale() {
681 LC_ALL="${var_utf8_locale:-en_US.UTF-8}"
682 export LC_ALL
685 # hg-fast-export | git fast-import with error handling in current directory GIT_DIR
686 git_hg_fetch() (
687 set_utf8_locale
688 _python="${PYTHON:-python}"
689 rm -f hg2git-marks.old hg2git-marks.new
690 if [ -f hg2git-marks -a -s hg2git-marks ]; then
691 LC_ALL=C sed 's/^:\([^ ][^ ]*\) \([^ ][^ ]*\)$/\2 \1/' <hg2git-marks | {
692 if [ -n "$var_have_git_185" ]; then
693 git cat-file --batch-check=':%(rest) %(objectname)'
694 else
695 LC_ALL=C sed 's/^\([^ ][^ ]*\) \([^ ][^ ]*\)$/:\2 \1/'
697 } | LC_ALL=C sed '/ missing$/d' >hg2git-marks.old
698 if [ -n "$var_have_git_171" ] && \
699 git rev-parse --quiet --verify refs/notes/hg >/dev/null; then
700 if [ -z "$var_have_git_185" ] || \
701 ! LC_ALL=C cmp -s hg2git-marks hg2git-marks.old; then
702 _nm='hg-fast-export'
703 GIT_AUTHOR_NAME="$_nm"
704 GIT_COMMITTER_NAME="$_nm"
705 GIT_AUTHOR_EMAIL="$_nm"
706 GIT_COMMITTER_EMAIL="$_nm"
707 export GIT_AUTHOR_NAME
708 export GIT_COMMITTER_NAME
709 export GIT_AUTHOR_EMAIL
710 export GIT_COMMITTER_EMAIL
711 git notes --ref=refs/notes/hg prune
712 unset GIT_AUTHOR_NAME
713 unset GIT_COMMITTER_NAME
714 unset GIT_AUTHOR_EMAIL
715 unset GIT_COMMITTER_EMAIL
718 else
719 >hg2git-marks.old
721 _err1=
722 _err2=
723 exec 3>&1
724 { read -r _err1 || :; read -r _err2 || :; } <<-EOT
726 exec 4>&3 3>&1 1>&4 4>&-
728 _e1=0
729 _af="$(git config hg.authorsfile || :)"
730 _cmd='GIT_DIR="$(pwd)" "$_python" "$cfg_basedir/bin/hg-fast-export.py" \
731 --repo "$(pwd)/repo.hg" \
732 --marks "$(pwd)/hg2git-marks.old" \
733 --mapping "$(pwd)/hg2git-mapping" \
734 --heads "$(pwd)/hg2git-heads" \
735 --status "$(pwd)/hg2git-state" \
736 -U unknown --force --flatten --hg-hash'
737 [ -z "$_af" ] || _cmd="$_cmd"' --authors "$_af"'
738 eval "$_cmd" 3>&- || _e1=$?
739 echo $_e1 >&3
740 } | \
742 _e2=0
743 git fast-import \
744 --import-marks="$(pwd)/hg2git-marks.old" \
745 --export-marks="$(pwd)/hg2git-marks.new" \
746 --export-pack-edges="$(pwd)/gfi-packs" \
747 --force 3>&- || _e2=$?
748 echo $_e2 >&3
752 exec 3>&-
753 [ "$_err1" = 0 -a "$_err2" = 0 ] || return 1
754 mv -f hg2git-marks.new hg2git-marks
755 rm -f hg2git-marks.old
756 git for-each-ref --format='%(refname) %(objectname)' refs/heads | \
757 LC_ALL=C sed -e 's,^refs/heads/,:,' >hg2git-heads