git-shell-verify: synchronize settings with shlib.sh
[girocco.git] / shlib.sh
blob2072a9291b53d1bda0beeaeaf1bb003bd862f59b
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_log_window_size Value to use for git-svn --log-window-size=
81 # var_utf8_locale Value to use for a UTF-8 locale if available
82 # var_xargs_r A "-r" if xargs needs it to behave correctly
83 # var_du_exclude Option to exclude PATTERN from du if available
84 # var_du_follow Option to follow command line sym links if available
85 _cfg_vars="$(get_girocco_config_pm_var_list)"
86 eval "$_cfg_vars"
87 printf '%s\n' "$_cfg_vars"
88 printf 'var_group=%s\n' "${cfg_owning_group:-$(id -gn)}"
89 _gver="$("$cfg_git_bin" version 2>/dev/null | \
90 LC_ALL=C sed -ne 's/^[^0-9]*\([0-9][0-9]*\(\.[0-9][0-9]*\)*\).*$/\1/p')"
91 printf 'var_git_ver=%s\n' "$_gver"
92 printf 'var_git_exec_path="%s"\n' "$("$cfg_git_bin" --exec-path 2>/dev/null)"
93 printf 'var_sh_bin="%s"\n' "${cfg_posix_sh_bin:-/bin/sh}"
94 printf 'var_perl_bin="%s"\n' "${cfg_perl_bin:-$(unset -f perl; command -v perl)}"
95 printf 'var_gzip_bin="%s"\n' "${cfg_gzip_bin:-$(unset -f gzip; command -v gzip)}"
96 printf 'var_have_git_171=%s\n' "$([ $(vcmp "$_gver" 1.7.1) -ge 0 ] && echo 1)"
97 printf 'var_have_git_172=%s\n' "$([ $(vcmp "$_gver" 1.7.2) -ge 0 ] && echo 1)"
98 printf 'var_have_git_173=%s\n' "$([ $(vcmp "$_gver" 1.7.3) -ge 0 ] && echo 1)"
99 printf 'var_have_git_1710=%s\n' "$([ $(vcmp "$_gver" 1.7.10) -ge 0 ] && echo 1)"
100 printf 'var_have_git_185=%s\n' "$([ $(vcmp "$_gver" 1.8.5) -ge 0 ] && echo 1)"
101 __girocco_conf="$GIROCCO_CONF"
102 [ -n "$__girocco_conf" ] || __girocco_conf="Girocco::Config"
103 [ -z "$basedir" ] || __girocco_extrainc="-I$basedir"
104 printf "var_window_memory=%s\n" \
105 "$(perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf \
106 -MGirocco::Util -e 'print calc_windowmemory')"
107 printf "var_big_file_threshold=%s\n" \
108 "$(perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf \
109 -MGirocco::Util -e 'print calc_bigfilethreshold')"
110 printf "var_redelta_threshold=%s\n" \
111 "$(perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf \
112 -MGirocco::Util -e 'print calc_redeltathreshold')"
113 printf 'var_log_window_size=%s\n' "${cfg_svn_log_window_size:-250}"
114 # We parse the output of `locale -a` and select a suitable UTF-8 locale.
115 _guess_locale="$(locale -a | LC_ALL=C grep -viE '^(posix|c)(\..*)?$' | \
116 LC_ALL=C grep -iE '\.utf-?8$' | LC_ALL=C sed -e 's/\.[Uu][Tt][Ff]-*8$//' | \
117 LC_ALL=C sed -e '/en_US/ s/^/0 /; /en_US/ !s/^/1 /' | LC_ALL=C sort | \
118 head -n 1 | LC_ALL=C cut -d ' ' -f 2)"
119 [ -z "$_guess_locale" ] || printf 'var_utf8_locale=%s.UTF-8\n' "$_guess_locale"
120 # On some broken platforms running xargs without -r and empty input runs the command
121 printf 'var_xargs_r=%s\n' "$(: | command xargs echo -r)"
122 # The disk usage report produces better numbers if du has an exclude option
123 _x0="$(basename "$0")"
124 _x0="${_x0%?}?*"
125 for _duopt in --exclude -I; do
126 if _test="$(du $_duopt 's?lib.s*' $_duopt "$_x0" "$0" 2>/dev/null)" && [ -z "$_test" ]; then
127 printf 'var_du_exclude=%s\n' "$_duopt"
128 break
130 done
131 if _test="$(du -H "$0" 2>/dev/null)" && [ -n "$_test" ]; then
132 printf 'var_du_follow=%s\n' "-H"
133 break
137 # If basedir has been replaced, and shlib_vars.sh exists, get the config
138 # definitions from it rather than running Perl.
139 if [ "@basedir@" = '@'basedir'@' ] || ! [ -r "@basedir@/shlib_vars.sh" ]; then
140 # Import all the variables from Girocco::Config to the local environment,
141 eval "$(get_girocco_config_var_list)"
142 else
143 # Import the variables from shlib_vars.sh which avoids needlessly
144 # running another copy of Perl
145 . "@basedir@/shlib_vars.sh"
148 # git_add_config "some.var=value"
149 # every ' in value must be replaced with the 4-character sequence '\'' before
150 # calling this function or Git will barf. Will not be effective unless running
151 # Git version 1.7.3 or later.
152 git_add_config() {
153 GIT_CONFIG_PARAMETERS="${GIT_CONFIG_PARAMETERS:+$GIT_CONFIG_PARAMETERS }'$1'"
154 export GIT_CONFIG_PARAMETERS
157 # Make sure we have a reproducible environment by using a controlled HOME dir
158 XDG_CONFIG_HOME="$cfg_chroot/var/empty"
159 HOME="$cfg_chroot/etc/girocco"
160 TMPDIR="/tmp"
161 GIT_CONFIG_NOSYSTEM=1
162 GIT_ATTR_NOSYSTEM=1
163 GIT_NO_REPLACE_OBJECTS=1
164 GIT_TERMINAL_PROMPT=0
165 GIT_ASKPASS="$cfg_basedir/bin/git-askpass-password"
166 export XDG_CONFIG_HOME
167 export HOME
168 export TMPDIR
169 export GIT_CONFIG_NOSYSTEM
170 export GIT_ATTR_NOSYSTEM
171 export GIT_NO_REPLACE_OBJECTS
172 export GIT_TERMINAL_PROMPT
173 export GIT_ASKPASS
174 unset GIT_USER_AGENT
175 unset GIT_HTTP_USER_AGENT
176 if [ -n "$defined_cfg_git_client_ua" ]; then
177 GIT_USER_AGENT="$cfg_git_client_ua"
178 export GIT_USER_AGENT
179 GIT_HTTP_USER_AGENT="$cfg_git_client_ua"
180 export GIT_HTTP_USER_AGENT
182 unset GIT_CONFIG_PARAMETERS
183 git_add_config "core.ignoreCase=false"
184 if [ -n "$cfg_git_no_mmap" ]; then
185 # Just like compiling with NO_MMAP
186 git_add_config "core.packedGitWindowSize=1m"
187 else
188 # Always use the 32-bit default (32m) even on 64-bit to avoid memory blowout
189 git_add_config "core.packedGitWindowSize=32m"
191 [ -z "$var_big_file_threshold" ] ||
192 git_add_config "core.bigFileThreshold=$var_big_file_threshold"
194 # Extra GIT variables that generally ought to be cleared, but whose clearing
195 # could potentially interfere with the correct operation of hook scripts so
196 # they are segregated into a separate function for use as appropriate
197 clean_git_env() {
198 unset GIT_ALTERNATE_OBJECT_DIRECTORIES
199 unset GIT_CONFIG
200 unset GIT_DIR
201 unset GIT_GRAFT_FILE
202 unset GIT_INDEX_FILE
203 unset GIT_OBJECT_DIRECTORY
204 unset GIT_NAMESPACE
207 # We cannot use a git() {} or nc_openbsd() {} function to redirect git
208 # and nc_openbsd to the desired executables because when using
209 # "ENV_VAR=xxx func" the various /bin/sh implementations behave in various
210 # different and unexpected ways:
211 # a) treat "ENV_VAR=xxx" like a separate, preceding "export ENV_VAR=xxx"
212 # b) treat "ENV_VAR=xxx" like a separate, prededing "ENV_VAR=xxx"
213 # c) treat "ENV_VAR=xxx" like a temporary setting only while running func
214 # None of these are good. We want a temporary "export ENV_VAR=xxx"
215 # setting only while running func which none of the /bin/sh's do.
217 # Instead we'd like to use an alias that provides the desired behavior without
218 # any of the bad (a), (b) or (c) effects.
220 # However, unfortunately, some of the crazy /bin/sh implementations do not
221 # recognize alias expansions when preceded by variable assignments!
223 # So we are left with git() {} and nc_openbsd() {} functions and in the
224 # case of git() {} we can compensate for (b) and (c) failing to export
225 # but not (a) and (b) persisting the values so the caller will simply
226 # have to beware and explicitly unset any variables that should not persist
227 # beyond the function call itself.
229 git() (
230 [ "${GIT_DIR+set}" = "set" ] && export GIT_DIR
231 [ "${GIT_SSL_NO_VERIFY+set}" = "set" ] && export GIT_SSL_NO_VERIFY
232 [ "${GIT_TRACE_PACKET+set}" = "set" ] && export GIT_TRACE_PACKET
233 [ "${GIT_USER_AGENT+set}" = "set" ] && export GIT_USER_AGENT
234 [ "${GIT_HTTP_USER_AGENT+set}" = "set" ] && export GIT_HTTP_USER_AGENT
235 exec "$cfg_git_bin" "$@"
238 # see comments for git() -- callers must explicitly export all variables
239 # intended for the commands these functions run before calling them
240 perl() { command "${var_perl_bin:-perl}" "$@"; }
241 gzip() { command "${var_gzip_bin:-gzip}" "$@"; }
243 nc_openbsd() { "$cfg_nc_openbsd_bin" "$@"; }
245 list_packs() { command "$cfg_basedir/bin/list_packs" "$@"; }
247 strftime() { command "$cfg_basedir/bin/strftime" "$@"; }
249 # Some platforms' broken xargs runs the command always at least once even if
250 # there's no input unless given a special option. Automatically supply the
251 # option on those platforms by providing an xargs function.
252 xargs() { command xargs $var_xargs_r "$@"; }
254 _addrlist() {
255 _list=
256 for _addr in "$@"; do
257 [ -z "$_list" ] || _list="$_list, "
258 _list="$_list$_addr"
259 done
260 echo "$_list"
263 _sendmail() {
264 _mailer="${cfg_sendmail_bin:-/usr/sbin/sendmail}"
265 if [ -n "$cfg_sender" ]; then
266 "$_mailer" -i -f "$cfg_sender" "$@"
267 else
268 "$_mailer" -i "$@"
272 mail() {
273 _subject=
274 if [ "$1" = "-s" ]; then
275 shift
276 _subject="$1"
277 shift
280 echo "From: \"$cfg_name\" ($cfg_title) <$cfg_admin>"
281 echo "To: $(_addrlist "$@")"
282 [ -z "$_subject" ] || echo "Subject: $_subject"
283 echo "MIME-Version: 1.0"
284 echo "Content-Type: text/plain; charset=utf-8"
285 echo "Content-Transfer-Encoding: 8bit"
286 [ -n "$cfg_suppress_x_girocco" ] || echo "X-Girocco: $cfg_gitweburl"
287 echo "Auto-Submitted: auto-generated"
288 echo ""
290 } | _sendmail "$@"
293 # bang CMD... will execute the command with well-defined failure mode;
294 # set bang_action to string of the failed action ('clone', 'update', ...);
295 # re-define the bang_trap() function to do custom cleanup before bailing out
296 bang() {
297 bang_active=1
298 bang_cmd="$*"
299 bang_errcode=0
300 if [ -n "$show_progress" ]; then
301 exec 3>&1
302 read -r bang_errcode <<-EOT || :
304 exec 4>&3 3>&1 1>&4 4>&-
305 { "$@" 3>&- || echo $? >&3; } 2>&1 | tee -i -a "$bang_log"
308 exec 3>&-
309 if [ -z "$bang_errcode" ] || [ "$bang_errcode" = "0" ]; then
310 # All right. Cool.
311 bang_active=
312 bang_cmd=
313 return;
315 else
316 if "$@" >>"$bang_log" 2>&1; then
317 # All right. Cool.
318 bang_active=
319 bang_cmd=
320 return;
321 else
322 bang_errcode="$?"
325 bang_failed
328 bang_failed() {
329 bang_active=
330 unset GIT_DIR
331 touch .banged
332 cat "$bang_log" > .banglog
333 echo "" >> .banglog
334 echo "$bang_cmd failed with error code $bang_errcode" >> .banglog
335 if [ -n "$show_progress" ]; then
336 echo ""
337 echo "$bang_cmd failed with error code $bang_errcode"
339 if [ -e .bangagain ]; then
340 git config --remove-section girocco.bang 2>/dev/null || :
341 rm -f .bangagain
343 bangcount="$(git config --int girocco.bang.count 2>/dev/null || :)"
344 : ${bangcount:=0}
345 bangcount=$(( $bangcount + 1 ))
346 git config --int girocco.bang.count $bangcount
347 if [ $bangcount -eq 1 ]; then
348 git config girocco.bang.firstfail "$(TZ=UTC date "+%Y-%m-%d %T UTC")"
350 if [ $bangcount -ge $cfg_min_mirror_failure_message_count ] && \
351 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" != "true" ] && \
352 ! check_interval "girocco.bang.firstfail" $cfg_min_mirror_failure_message_interval; then
353 bangmailok="$(git config --bool gitweb.statusupdates 2>/dev/null || echo true)"
354 bangaddrs=''
355 [ "$bangmailok" = "false" -o -z "$mail" ] || bangaddrs="$mail"
356 [ -z "$cfg_admincc" -o "$cfg_admincc" = "0" -o -z "$cfg_admin" ] ||
357 if [ -z "$bangaddrs" ]; then bangaddrs="$cfg_admin"; else bangaddrs="$bangaddrs,$cfg_admin"; fi
358 rsubj=
359 [ $bangcount -le 1 ] || rsubj=" repeatedly"
360 [ -z "$bangaddrs" ] ||
362 echo "$bang_cmd failed with error code $bang_errcode"
363 echo ""
364 rsubj=
365 if [ $bangcount -gt 1 ]; then
366 echo "$bangcount consecutive update failures have occurred since $(config_get girocco.bang.firstfail)"
367 echo ""
369 echo "you will not receive any more notifications until recovery"
370 echo "this status message may be disabled on the project admin page"
371 echo ""
372 echo "Log follows:"
373 echo ""
374 cat "$bang_log"
375 } | mail -s "[$cfg_name] $proj $bang_action failed$rsubj" "$bangaddrs"
376 git config --bool girocco.bang.messagesent true
378 bangthrottle=
379 [ $bangcount -lt 15 ] || \
380 check_interval "girocco.bang.firstfail" $(( $cfg_min_mirror_interval * 3 / 2 )) || \
381 bangthrottle=1
382 bang_trap $bangthrottle
383 [ -n "$bang_errcode" ] && [ "$bang_errcode" != "0" ] || bang_errcode=1
384 exit $bang_errcode
387 # bang_eval CMD... will evaluate the command with well-defined failure mode;
388 # Identical to bang CMD... except the command is eval'd instead of executed.
389 bang_eval() {
390 bang eval "$*"
393 # Default bang settings:
394 bang_setup() {
395 bang_active=
396 bang_action="lame_programmer"
397 bang_trap() { :; }
398 bang_log="$(mktemp -t repomgr-XXXXXX)"
399 is_git_dir . || {
400 echo "bang_setup called with current directory not a git directory" >&2
401 exit 1
403 trap 'rm -f "$bang_log"' EXIT
404 trap '[ -z "$bang_active" ] || { bang_errcode=130; bang_failed; }; exit 130' INT
405 trap '[ -z "$bang_active" ] || { bang_errcode=143; bang_failed; }; exit 143' TERM
408 # Remove banged status
409 bang_reset() {
410 rm -f .banged .bangagain .banglog
411 git config --remove-section girocco.bang 2>/dev/null || :
414 # Check to see if banged status
415 is_banged() {
416 [ -e .banged ]
419 # Check to see if banged message was sent
420 was_banged_message_sent() {
421 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" = "true" ]
424 # Progress report - if show_progress is set, shows the given message.
425 progress() {
426 [ ! -n "$show_progress" ] || echo "$@"
429 # Project config accessors; must be run in project directory
430 config_get() {
431 case "$1" in
432 *.*)
433 git config "$1";;
435 git config "gitweb.$1";;
436 esac
439 config_set() {
440 git config "gitweb.$1" "$2" && chgrp $var_group config && chmod g+w config
443 config_set_raw() {
444 git config "$1" "$2" && chgrp $var_group config && chmod g+w config
447 config_get_date_seconds() {
448 _dt="$(config_get "$1" || :)"
449 [ -n "$_dt" ] || return 1
450 _ds="$(perl -I@basedir@ -MGirocco::Util -e "print parse_any_date('$_dt')")"
451 [ -n "$_ds" ] || return 1
452 echo "$_ds"
455 # Tool for checking whether given number of seconds has not passed yet
456 check_interval() {
457 os="$(config_get_date_seconds "$1")" || return 1
458 ns="$(date +%s)"
459 [ $ns -lt $(($os+$2)) ]
462 # Check if we are running with effective root permissions
463 is_root() {
464 [ "$(id -u 2>/dev/null)" = "0" ]
467 # Check to see if the single argument is a Git directory
468 is_git_dir() {
469 # Just like Git's test except we ignore GIT_OBJECT_DIRECTORY
470 # And we are slightly more picky (must be refs/.+ not refs/.*)
471 [ -d "$1/objects" -a -x "$1/objects" ] || return 1
472 [ -d "$1/refs" -a -x "$1/refs" ] || return 1
473 if [ -L "$1/HEAD" ]; then
474 _hr="$(readlink "$1/HEAD")"
475 case "$_hr" in "refs/"?*) :;; *) return 1;; esac
477 [ -f "$1/HEAD" -a -r "$1/HEAD" ] || return 1
478 read -r _hr <"$1/HEAD" || return 1
479 case "$_hr" in
480 $octet20 | ref:refs/?*)
481 return 0;;
482 ref:*)
483 _hr="${_hr##ref:*[ $tab]}"
484 case "$_hr" in "refs/"?*) return 0;; esac
485 esac
486 return 1
489 # List all Git repositories, with given prefix if specified, one-per-line
490 # All project names starting with _ are always excluded from the result
491 get_repo_list() {
492 if [ -n "$1" ]; then
493 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group | LC_ALL=C grep "^$1"
494 else
495 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group
496 fi | while IFS=: read name id; do
497 [ $id -lt 65536 ] || case "$name" in _*) :;; ?*) echo "$name"; esac
498 done
501 # Return success if the given project name has any forks
502 has_forks() {
503 _prj="${1%.git}"
504 [ -n "$_prj" ] || return 1
505 [ -d "$cfg_reporoot/$_prj" ] || return 1
506 is_git_dir "$cfg_reporoot/$_prj.git" || return 1
507 test $(get_repo_list "$_prj/[^/][^/]*:" | LC_ALL=C wc -l) -gt 0
510 # returns empty string and error for empty string otherwise one of
511 # m => normal Git mirror
512 # s => mirror from svn source
513 # d => mirror from darcs source
514 # b => mirror from bzr source
515 # h => mirror from hg source
516 # w => mirror from mediawiki source
517 # f => mirror from other fast-import source
518 # note that if the string is non-empty and none of s, d, b or h match the
519 # return will always be type m regardless of whether it's a valid Git URL
520 get_url_mirror_type() {
521 case "$1" in
523 return 1
525 svn://* | svn+http://* | svn+https://* | svn+file://* | svn+ssh://*)
526 echo 's'
528 darcs://*)
529 echo 'd'
531 bzr://*)
532 echo 'b'
534 hg+http://* | hg+https://* | hg+file://* | hg+ssh://* | hg::*)
535 echo 'h'
537 mediawiki::*)
538 echo 'w'
541 echo 'm'
543 esac
544 return 0
547 # returns false for empty string
548 # returns true if the passed in url is a mirror using git fast-import
549 is_gfi_mirror_url() {
550 [ -n "$1" ] || return 1
551 case "$(get_url_mirror_type "$1" 2>/dev/null || :)" in
552 d|b|h|w|f)
553 # darcs, bzr, hg and mediawiki mirrors use git fast-import
554 # and so do generic "f" fast-import mirrors
555 return 0
558 # Don't think git-svn currently uses git fast-import
559 # And Git mirrors certainly do not
560 return 1
562 esac
563 # assume it does not use git fast-import
564 return 1
567 # returns false for empty string
568 # returns true if the passed in url is a mirror using git-svn
569 is_svn_mirror_url() {
570 [ -n "$1" ] || return 1
571 [ "$(get_url_mirror_type "$1" 2>/dev/null || :)" = "s" ]
574 # returns mirror url for gitweb.baseurl of git directory
575 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
576 # will fail if the directory does not have .nofetch and gitweb.baseurl
577 # comes back empty -- otherwise .nofetch directories succeed with a "" return
578 # automatically strips any leading "disabled " prefix before returning result
579 get_mirror_url() {
580 _gitdir="${1:-.}"
581 # always return empty for non-mirrors
582 [ ! -e "$_gitdir/.nofetch" ] || return 0
583 _url="$(GIT_DIR="$_gitdir" config_get baseurl 2>/dev/null || :)"
584 _url="${_url##* }"
585 [ -n "$_url" ] || return 1
586 printf '%s\n' "$_url"
587 return 0
590 # returns get_url_mirror_type for gitweb.baseurl of git directory
591 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
592 # will fail if the directory does not have .nofetch and gitweb.baseurl
593 # comes back empty -- otherwise .nofetch directories succeed with a "" return
594 # automatically strips any leading "disabled " prefix before testing
595 get_mirror_type() {
596 _url="$(get_mirror_url "$@")" || return 1
597 get_url_mirror_type "$_url"
600 # returns true if the passed in git dir (defaults to ".") is a mirror using git fast-import
601 is_gfi_mirror() {
602 _url="$(get_mirror_url "$@")" || return 1
603 is_gfi_mirror_url "$_url"
606 # returns true if the passed in git dir (defaults to ".") is a mirror using git-svn
607 is_svn_mirror() {
608 _url="$(get_mirror_url "$@")" || return 1
609 is_svn_mirror_url "$_url"
612 # current directory must already be set to Git repository
613 # if girocco.headok is already true succeeds without doing anything
614 # if rev-parse --verify HEAD succeeds sets headok=true and succeeds
615 # otherwise tries to set HEAD to a symbolic ref to refs/heads/master
616 # then refs/heads/trunk and finally the first top-level head from
617 # refs/heads/* (i.e. only two slashes in the name) and finally any
618 # existing refs/heads. The first one to succeed wins and sets headok=true
619 # and then a successful exit. Otherwise headok is left unset with a failure exit
620 # We use the girocco.headok flag to make sure we only force a valid HEAD symref
621 # when the repository is being set up -- if the HEAD is later deleted (through
622 # a push or fetch --prune) that's no longer our responsibility to fix
623 check_and_set_head() {
624 [ "$(git config --bool girocco.headok 2>/dev/null || :)" != "true" ] || return 0
625 if git rev-parse --verify --quiet HEAD >/dev/null; then
626 git config --bool girocco.headok true
627 return 0
629 for _hr in refs/heads/master refs/heads/trunk; do
630 if git rev-parse --verify --quiet "$_hr"; then
631 _update_head_symref "$_hr"
632 return 0
634 done
635 git for-each-ref --format="%(refname)" refs/heads 2>/dev/null |
636 while read -r _hr; do
637 case "${_hr#refs/heads/}" in */*) :;; *)
638 _update_head_symref "$_hr"
639 exit 1 # exit subshell created by "|"
640 esac
641 done || return 0
642 _hr="$(git for-each-ref --format="%(refname)" refs/heads 2>/dev/null | head -n 1 || :)"
643 if [ -n "$_hr" ]; then
644 _update_head_symref "$_hr"
645 return 0
647 return 1
649 _update_head_symref() {
650 git symbolic-ref HEAD "$1"
651 git config --bool girocco.headok true
652 ! [ -d htmlcache ] || { >htmlcache/changed; } 2>/dev/null || :
655 # A well-known UTF-8 locale is required for some of the fast-import providers
656 # in order to avoid mangling characters. Ideally we could use "POSIX.UTF-8"
657 # but that is not reliably UTF-8 but rather usually US-ASCII.
658 # We parse the output of `locale -a` and select a suitable UTF-8 locale at
659 # install time and store that in $var_utf8_locale if one is found.
660 # If we cannot find one in the `locale -a` output then we just use a well-known
661 # UTF-8 locale and hope for the best. We set LC_ALL to our choice and export
662 # it. We only set this temporarily when running the fast-import providers.
663 set_utf8_locale() {
664 LC_ALL="${var_utf8_locale:-en_US.UTF-8}"
665 export LC_ALL
668 # hg-fast-export | git fast-import with error handling in current directory GIT_DIR
669 git_hg_fetch() (
670 set_utf8_locale
671 _python="${PYTHON:-python}"
672 rm -f hg2git-marks.old hg2git-marks.new
673 if [ -f hg2git-marks -a -s hg2git-marks ]; then
674 LC_ALL=C sed 's/^:\([^ ][^ ]*\) \([^ ][^ ]*\)$/\2 \1/' <hg2git-marks | {
675 if [ -n "$var_have_git_185" ]; then
676 git cat-file --batch-check=':%(rest) %(objectname)'
677 else
678 LC_ALL=C sed 's/^\([^ ][^ ]*\) \([^ ][^ ]*\)$/:\2 \1/'
680 } | LC_ALL=C sed '/ missing$/d' >hg2git-marks.old
681 if [ -n "$var_have_git_171" ] && \
682 git rev-parse --quiet --verify refs/notes/hg >/dev/null; then
683 if [ -z "$var_have_git_185" ] || \
684 ! LC_ALL=C cmp -s hg2git-marks hg2git-marks.old; then
685 _nm='hg-fast-export'
686 GIT_AUTHOR_NAME="$_nm"
687 GIT_COMMITTER_NAME="$_nm"
688 GIT_AUTHOR_EMAIL="$_nm"
689 GIT_COMMITTER_EMAIL="$_nm"
690 export GIT_AUTHOR_NAME
691 export GIT_COMMITTER_NAME
692 export GIT_AUTHOR_EMAIL
693 export GIT_COMMITTER_EMAIL
694 git notes --ref=refs/notes/hg prune
695 unset GIT_AUTHOR_NAME
696 unset GIT_COMMITTER_NAME
697 unset GIT_AUTHOR_EMAIL
698 unset GIT_COMMITTER_EMAIL
701 else
702 >hg2git-marks.old
704 _err1=
705 _err2=
706 exec 3>&1
707 { read -r _err1 || :; read -r _err2 || :; } <<-EOT
709 exec 4>&3 3>&1 1>&4 4>&-
711 _e1=0
712 _af="$(git config hg.authorsfile || :)"
713 _cmd='GIT_DIR="$(pwd)" "$_python" "$cfg_basedir/bin/hg-fast-export.py" \
714 --repo "$(pwd)/repo.hg" \
715 --marks "$(pwd)/hg2git-marks.old" \
716 --mapping "$(pwd)/hg2git-mapping" \
717 --heads "$(pwd)/hg2git-heads" \
718 --status "$(pwd)/hg2git-state" \
719 -U unknown --force --flatten --hg-hash'
720 [ -z "$_af" ] || _cmd="$_cmd"' --authors "$_af"'
721 eval "$_cmd" 3>&- || _e1=$?
722 echo $_e1 >&3
723 } | \
725 _e2=0
726 git fast-import \
727 --import-marks="$(pwd)/hg2git-marks.old" \
728 --export-marks="$(pwd)/hg2git-marks.new" \
729 --export-pack-edges="$(pwd)/gfi-packs" \
730 --force 3>&- || _e2=$?
731 echo $_e2 >&3
735 exec 3>&-
736 [ "$_err1" = 0 -a "$_err2" = 0 ] || return 1
737 mv -f hg2git-marks.new hg2git-marks
738 rm -f hg2git-marks.old
739 git for-each-ref --format='%(refname) %(objectname)' refs/heads | \
740 LC_ALL=C sed -e 's,^refs/heads/,:,' >hg2git-heads