find: locate bits of ourselves symbolically
[girocco.git] / shlib.sh
blob5d649ac056603be8b88b79cd94a2bc02f28d77a0
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 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" ] || [ -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 unset orig_path
46 get_girocco_config_pm_var_list() (
47 # Export all the variables from Girocco::Config to suitable var= lines
48 # prefixing them with 'cfg_'. E.g. $cfg_admin is admin's mail address now
49 # and also setting a 'defined_cfg_' prefix to 1 if they are not undef.
50 __girocco_conf="$GIROCCO_CONF"
51 [ -n "$__girocco_conf" ] || __girocco_conf="Girocco::Config"
52 [ -z "$basedir" ] || __girocco_extrainc="-I$basedir"
53 [ -z "$orig_path" ] || { PATH="$orig_path" && export PATH; }
54 perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf -le \
55 'foreach (sort {uc($a) cmp uc($b)} keys %Girocco::Config::) {
56 my $val = ${$Girocco::Config::{$_}}; defined($val) or $val="";
57 $val =~ s/([\\"\$\`])/\\$1/gos;
58 $val =~ s/(?:\r\n|\r|\n)$//os;
59 print "cfg_$_=\"$val\"";
60 print "defined_cfg_$_=",
61 (defined(${$Girocco::Config::{$_}})?"1":"");
65 # Returns full command path for "$1" if it's a valid command otherwise returns "$1"
66 _fcp() {
67 if _fp="$(command -v "$1" 2>/dev/null)"; then
68 printf '%s\n' "$_fp"
69 else
70 printf '%s\n' "$1"
74 get_girocco_config_var_list() (
75 # Same as get_girocco_config_pm_var_list except that
76 # the following variables (all starting with var_) are added:
77 # var_group cfg_owning_group if defined otherwise `id -gn`
78 # var_git_ver The version number part from `git version`
79 # var_git_exec_path The result of $cfg_git_bin --exec-dir
80 # var_sh_bin Full path to the posix sh interpreter to use
81 # var_perl_bin Full path to the perl interpreter to use
82 # var_gzip_bin Full path to the gzip executable to use
83 # var_nc_openbsd_bin Full path to the netcat (nc) with -U support
84 # var_have_git_171 Set to 1 if git version >= 1.7.1 otherwise ''
85 # var_have_git_172 Set to 1 if git version >= 1.7.2 otherwise ''
86 # var_have_git_173 Set to 1 if git version >= 1.7.3 otherwise ''
87 # var_have_git_1710 Set to 1 if git version >= 1.7.10 otherwise ''
88 # var_have_git_185 Set to 1 if git version >= 1.8.5 otherwise ''
89 # var_have_git_210 Set to 1 if git version >= 2.1.0 otherwise ''
90 # var_have_git_260 Set to 1 if git version >= 2.6.0 otherwise ''
91 # var_have_git_2101 Set to 1 if git version >= 2.10.1 otherwise ''
92 # var_window_memory Value to use for repack --window-memory=
93 # var_big_file_threshold Value to use for core.bigFileThreshold
94 # var_redelta_threshold Recompute deltas if no more than this many objs
95 # var_upload_window If not "", pack.window to use for upload-pack
96 # var_log_window_size Value to use for git-svn --log-window-size=
97 # var_utf8_locale Value to use for a UTF-8 locale if available
98 # var_xargs_r A "-r" if xargs needs it to behave correctly
99 # var_du_exclude Option to exclude PATTERN from du if available
100 # var_du_follow Option to follow command line sym links if available
101 _cfg_vars="$(get_girocco_config_pm_var_list)"
102 eval "$_cfg_vars"
103 printf '%s\n' "$_cfg_vars"
104 printf 'var_group=%s\n' "${cfg_owning_group:-$(id -gn)}"
105 _gver="$("$cfg_git_bin" version 2>/dev/null |
106 LC_ALL=C sed -ne 's/^[^0-9]*\([0-9][0-9]*\(\.[0-9][0-9]*\)*\).*$/\1/p')"
107 printf 'var_git_ver=%s\n' "$_gver"
108 printf 'var_git_exec_path="%s"\n' "$("$cfg_git_bin" --exec-path 2>/dev/null)"
109 printf 'var_sh_bin="%s"\n' "$(_fcp "${cfg_posix_sh_bin:-/bin/sh}")"
110 printf 'var_perl_bin="%s"\n' "$(_fcp "${cfg_perl_bin:-$(unset -f perl; command -v perl)}")"
111 printf 'var_gzip_bin="%s"\n' "$(_fcp "${cfg_gzip_bin:-$(unset -f gzip; command -v gzip)}")"
112 printf 'var_nc_openbsd_bin="%s"\n' "$(_fcp "${cfg_nc_openbsd_bin:-$(unset -f nc; command -v nc)}")"
113 printf 'var_have_git_171=%s\n' "$([ $(vcmp "$_gver" 1.7.1) -ge 0 ] && echo 1)"
114 printf 'var_have_git_172=%s\n' "$([ $(vcmp "$_gver" 1.7.2) -ge 0 ] && echo 1)"
115 printf 'var_have_git_173=%s\n' "$([ $(vcmp "$_gver" 1.7.3) -ge 0 ] && echo 1)"
116 printf 'var_have_git_1710=%s\n' "$([ $(vcmp "$_gver" 1.7.10) -ge 0 ] && echo 1)"
117 printf 'var_have_git_185=%s\n' "$([ $(vcmp "$_gver" 1.8.5) -ge 0 ] && echo 1)"
118 printf 'var_have_git_210=%s\n' "$([ $(vcmp "$_gver" 2.1.0) -ge 0 ] && echo 1)"
119 printf 'var_have_git_260=%s\n' "$([ $(vcmp "$_gver" 2.6.0) -ge 0 ] && echo 1)"
120 printf 'var_have_git_2101=%s\n' "$([ $(vcmp "$_gver" 2.10.1) -ge 0 ] && echo 1)"
121 __girocco_conf="$GIROCCO_CONF"
122 [ -n "$__girocco_conf" ] || __girocco_conf="Girocco::Config"
123 [ -z "$basedir" ] || __girocco_extrainc="-I$basedir"
124 printf "var_window_memory=%s\n" \
125 "$(perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf \
126 -MGirocco::Util -e 'print calc_windowmemory')"
127 printf "var_big_file_threshold=%s\n" \
128 "$(perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf \
129 -MGirocco::Util -e 'print calc_bigfilethreshold')"
130 printf "var_redelta_threshold=%s\n" \
131 "$(perl -I@basedir@ $__girocco_extrainc -M$__girocco_conf \
132 -MGirocco::Util -e 'print calc_redeltathreshold')"
133 if [ -n "$cfg_upload_pack_window" ] && [ "$cfg_upload_pack_window" -ge 2 ] &&
134 [ "$cfg_upload_pack_window" -le 50 ]; then
135 printf "var_upload_window=%s\n" "$cfg_upload_pack_window"
136 else
137 printf "var_upload_window=%s\n" ""
139 printf 'var_log_window_size=%s\n' "${cfg_svn_log_window_size:-250}"
140 # We parse the output of `locale -a` and select a suitable UTF-8 locale.
141 _guess_locale="$(locale -a | LC_ALL=C grep -viE '^(posix|c)(\..*)?$' |
142 LC_ALL=C grep -iE '\.utf-?8$' | LC_ALL=C sed -e 's/\.[Uu][Tt][Ff]-*8$//' |
143 LC_ALL=C sed -e '/en_US/ s/^/0 /; /en_US/ !s/^/1 /' | LC_ALL=C sort |
144 head -n 1 | LC_ALL=C cut -d ' ' -f 2)"
145 [ -z "$_guess_locale" ] || printf 'var_utf8_locale=%s.UTF-8\n' "$_guess_locale"
146 # On some broken platforms running xargs without -r and empty input runs the command
147 printf 'var_xargs_r=%s\n' "$(</dev/null command xargs printf %s -r)"
148 # The disk usage report produces better numbers if du has an exclude option
149 _x0="$(basename "$0")"
150 _x0="${_x0%?}?*"
151 for _duopt in --exclude -I; do
152 if _test="$(du $_duopt 's?lib.s*' $_duopt "$_x0" "$0" 2>/dev/null)" && [ -z "$_test" ]; then
153 printf 'var_du_exclude=%s\n' "$_duopt"
154 break
156 done
157 if _test="$(du -H "$0" 2>/dev/null)" && [ -n "$_test" ]; then
158 printf 'var_du_follow=%s\n' "-H"
159 break
163 # If basedir has been replaced, and shlib_vars.sh exists, get the config
164 # definitions from it rather than running Perl.
165 if [ "@basedir@" = '@'basedir'@' ] || ! [ -r "@basedir@/shlib_vars.sh" ]; then
166 # Import all the variables from Girocco::Config to the local environment,
167 eval "$(get_girocco_config_var_list)"
168 else
169 # Import the variables from shlib_vars.sh which avoids needlessly
170 # running another copy of Perl
171 . "@basedir@/shlib_vars.sh"
174 # git_add_config "some.var=value"
175 # every ' in value must be replaced with the 4-character sequence '\'' before
176 # calling this function or Git will barf. Will not be effective unless running
177 # Git version 1.7.3 or later.
178 git_add_config() {
179 GIT_CONFIG_PARAMETERS="${GIT_CONFIG_PARAMETERS:+$GIT_CONFIG_PARAMETERS }'$1'"
180 export GIT_CONFIG_PARAMETERS
183 # Make sure we have a reproducible environment by using a controlled HOME dir
184 XDG_CONFIG_HOME="$cfg_chroot/var/empty"
185 HOME="$cfg_chroot/etc/girocco"
186 TMPDIR="/tmp"
187 GIT_CONFIG_NOSYSTEM=1
188 GIT_ATTR_NOSYSTEM=1
189 GIT_NO_REPLACE_OBJECTS=1
190 GIT_TERMINAL_PROMPT=0
191 GIT_PAGER="cat"
192 PAGER="cat"
193 GIT_ASKPASS="$cfg_basedir/bin/git-askpass-password"
194 GIT_SVN_NOTTY=1
195 export XDG_CONFIG_HOME
196 export HOME
197 export TMPDIR
198 export GIT_CONFIG_NOSYSTEM
199 export GIT_ATTR_NOSYSTEM
200 export GIT_NO_REPLACE_OBJECTS
201 export GIT_TERMINAL_PROMPT
202 export GIT_PAGER
203 export PAGER
204 export GIT_ASKPASS
205 export GIT_SVN_NOTTY
206 unset GIT_USER_AGENT
207 unset GIT_HTTP_USER_AGENT
208 if [ -n "$defined_cfg_git_client_ua" ]; then
209 GIT_USER_AGENT="$cfg_git_client_ua"
210 export GIT_USER_AGENT
211 GIT_HTTP_USER_AGENT="$cfg_git_client_ua"
212 export GIT_HTTP_USER_AGENT
214 unset GIT_CONFIG_PARAMETERS
215 git_add_config "core.ignoreCase=false"
216 git_add_config "core.pager=cat"
217 if [ -n "$cfg_git_no_mmap" ]; then
218 # Just like compiling with NO_MMAP
219 git_add_config "core.packedGitWindowSize=1m"
220 else
221 # Always use the 32-bit default (32m) even on 64-bit to avoid memory blowout
222 git_add_config "core.packedGitWindowSize=32m"
224 [ -z "$var_big_file_threshold" ] ||
225 git_add_config "core.bigFileThreshold=$var_big_file_threshold"
227 # Make sure any sendmail.pl config is always available
228 unset SENDMAIL_PL_HOST
229 unset SENDMAIL_PL_PORT
230 unset SENDMAIL_PL_NCBIN
231 unset SENDMAIL_PL_NCOPT
232 [ -z "$cfg_sendmail_pl_host" ] || { SENDMAIL_PL_HOST="$cfg_sendmail_pl_host" && export SENDMAIL_PL_HOST; }
233 [ -z "$cfg_sendmail_pl_port" ] || { SENDMAIL_PL_PORT="$cfg_sendmail_pl_port" && export SENDMAIL_PL_PORT; }
234 [ -z "$cfg_sendmail_pl_ncbin" ] || { SENDMAIL_PL_NCBIN="$cfg_sendmail_pl_ncbin" && export SENDMAIL_PL_NCBIN; }
235 [ -z "$cfg_sendmail_pl_ncopt" ] || { SENDMAIL_PL_NCOPT="$cfg_sendmail_pl_ncopt" && export SENDMAIL_PL_NCOPT; }
237 # Set PATH and PYTHON to the values set by Config.pm, if any
238 unset PYTHON
239 [ -z "$cfg_python" ] || { PYTHON="$cfg_python" && export PYTHON; }
240 [ -z "$cfg_path" ] || { orig_path="$PATH" && PATH="$cfg_path" && export PATH; }
242 # Extra GIT variables that generally ought to be cleared, but whose clearing
243 # could potentially interfere with the correct operation of hook scripts so
244 # they are segregated into a separate function for use as appropriate
245 clean_git_env() {
246 unset GIT_ALTERNATE_OBJECT_DIRECTORIES
247 unset GIT_CONFIG
248 unset GIT_DIR
249 unset GIT_GRAFT_FILE
250 unset GIT_INDEX_FILE
251 unset GIT_OBJECT_DIRECTORY
252 unset GIT_NAMESPACE
255 # We cannot use a git() {} or nc_openbsd() {} function to redirect git
256 # and nc_openbsd to the desired executables because when using
257 # "ENV_VAR=xxx func" the various /bin/sh implementations behave in various
258 # different and unexpected ways:
259 # a) treat "ENV_VAR=xxx" like a separate, preceding "export ENV_VAR=xxx"
260 # b) treat "ENV_VAR=xxx" like a separate, prededing "ENV_VAR=xxx"
261 # c) treat "ENV_VAR=xxx" like a temporary setting only while running func
262 # None of these are good. We want a temporary "export ENV_VAR=xxx"
263 # setting only while running func which none of the /bin/sh's do.
265 # Instead we'd like to use an alias that provides the desired behavior without
266 # any of the bad (a), (b) or (c) effects.
268 # However, unfortunately, some of the crazy /bin/sh implementations do not
269 # recognize alias expansions when preceded by variable assignments!
271 # So we are left with git() {} and nc_openbsd() {} functions and in the
272 # case of git() {} we can compensate for (b) and (c) failing to export
273 # but not (a) and (b) persisting the values so the caller will simply
274 # have to beware and explicitly unset any variables that should not persist
275 # beyond the function call itself.
277 git() (
278 [ z"${GIT_DIR+set}" != z"set" ] || export GIT_DIR
279 [ z"${GIT_SSL_NO_VERIFY+set}" != z"set" ] || export GIT_SSL_NO_VERIFY
280 [ z"${GIT_TRACE_PACKET+set}" != z"set" ] || export GIT_TRACE_PACKET
281 [ z"${GIT_USER_AGENT+set}" != z"set" ] || export GIT_USER_AGENT
282 [ z"${GIT_HTTP_USER_AGENT+set}" != z"set" ] || export GIT_HTTP_USER_AGENT
283 exec "$cfg_git_bin" "$@"
286 # Since we do not yet require at least Git 1.8.5 this is a compatibility function
287 # that allows us to use git update-ref --stdin where supported and the slow shell
288 # script where not, but only the "delete" operation is currently supported.
289 git_updateref_stdin() {
290 if [ -n "$var_have_git_185" ]; then
291 git update-ref --stdin
292 else
293 while read -r _op _ref; do
294 case "$_op" in
295 delete)
296 git update-ref -d "$_ref"
299 echo "bad git_updateref_stdin op: $_op" >&2
300 exit 1
302 esac
303 done
307 # see comments for git() -- callers must explicitly export all variables
308 # intended for the commands these functions run before calling them
309 perl() { command "${var_perl_bin:-perl}" "$@"; }
310 gzip() { command "${var_gzip_bin:-gzip}" "$@"; }
312 nc_openbsd() { command "$var_nc_openbsd_bin" "$@"; }
314 list_packs() { command "$cfg_basedir/bin/list_packs" "$@"; }
316 strftime() { command "$cfg_basedir/bin/strftime" "$@"; }
318 # Some platforms' broken xargs runs the command always at least once even if
319 # there's no input unless given a special option. Automatically supply the
320 # option on those platforms by providing an xargs function.
321 xargs() { command xargs $var_xargs_r "$@"; }
323 _addrlist() {
324 _list=
325 for _addr in "$@"; do
326 [ -z "$_list" ] || _list="$_list, "
327 _list="$_list$_addr"
328 done
329 echo "$_list"
332 _sendmail() {
333 _mailer="${cfg_sendmail_bin:-/usr/sbin/sendmail}"
334 if [ -n "$cfg_sender" ]; then
335 "$_mailer" -i -f "$cfg_sender" "$@"
336 else
337 "$_mailer" -i "$@"
341 # First argument is an id WITHOUT surrounding '<' and '>' to use in a
342 # "References:" header. It may be "" to suppress the "References" header.
343 # Following arguments are just like mail function
344 mailref() {
345 _references=
346 if [ $# -ge 1 ]; then
347 _references="$1"
348 shift
350 _subject=
351 if [ "$1" = "-s" ]; then
352 shift
353 _subject="$1"
354 shift
357 echo "From: \"$cfg_name\" ($cfg_title) <$cfg_admin>"
358 echo "To: $(_addrlist "$@")"
359 [ -z "$_subject" ] || echo "Subject: $_subject"
360 echo "MIME-Version: 1.0"
361 echo "Content-Type: text/plain; charset=UTF-8"
362 echo "Content-Transfer-Encoding: 8bit"
363 [ -z "$_references" ] || echo "References: <$_references>"
364 [ -n "$cfg_suppress_x_girocco" ] || echo "X-Girocco: $cfg_gitweburl"
365 echo "Auto-Submitted: auto-generated"
366 echo ""
368 } | _sendmail "$@"
371 # Usage: mail [-s <subject>] <addr> [<addr>...]
372 mail() {
373 mailref "" "$@"
376 # bang CMD... will execute the command with well-defined failure mode;
377 # set bang_action to string of the failed action ('clone', 'update', ...);
378 # re-define the bang_trap() function to do custom cleanup before bailing out
379 bang() {
380 bang_active=1
381 bang_cmd="$*"
382 bang_errcode=0
383 if [ -n "$show_progress" ]; then
384 exec 3>&1
385 read -r bang_errcode <<-EOT || :
387 exec 4>&3 3>&1 1>&4 4>&-
388 { "$@" 3>&- || echo $? >&3; } 2>&1 | tee -i -a "$bang_log"
391 exec 3>&-
392 if [ -z "$bang_errcode" ] || [ "$bang_errcode" = "0" ]; then
393 # All right. Cool.
394 bang_active=
395 bang_cmd=
396 return;
398 else
399 if "$@" >>"$bang_log" 2>&1; then
400 # All right. Cool.
401 bang_active=
402 bang_cmd=
403 return;
404 else
405 bang_errcode="$?"
408 bang_failed
411 bang_failed() {
412 bang_active=
413 unset GIT_DIR
414 >.banged
415 cat "$bang_log" >.banglog
416 echo "" >>.banglog
417 echo "$bang_cmd failed with error code $bang_errcode" >>.banglog
418 if [ -n "$show_progress" ]; then
419 echo ""
420 echo "$bang_cmd failed with error code $bang_errcode"
422 if [ -e .bangagain ]; then
423 git config --remove-section girocco.bang 2>/dev/null || :
424 rm -f .bangagain
426 bangcount="$(git config --int girocco.bang.count 2>/dev/null)" || :
427 bangcount=$(( ${bangcount:-0} + 1 ))
428 git config --int girocco.bang.count $bangcount
429 if [ $bangcount -eq 1 ]; then
430 git config girocco.bang.firstfail "$(TZ=UTC date "+%Y-%m-%d %T UTC")"
432 if [ $bangcount -ge $cfg_min_mirror_failure_message_count ] &&
433 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" != "true" ] &&
434 ! check_interval "girocco.bang.firstfail" $cfg_min_mirror_failure_message_interval; then
435 bangmailok="$(git config --bool gitweb.statusupdates 2>/dev/null || echo true)"
436 bangaddrs=
437 [ "$bangmailok" = "false" ] || [ -z "$mail" ] || bangaddrs="$mail"
438 [ -z "$cfg_admincc" ] || [ "$cfg_admincc" = "0" ] || [ -z "$cfg_admin" ] ||
439 if [ -z "$bangaddrs" ]; then bangaddrs="$cfg_admin"; else bangaddrs="$bangaddrs,$cfg_admin"; fi
440 rsubj=
441 [ $bangcount -le 1 ] || rsubj=" repeatedly"
442 [ -z "$bangaddrs" ] ||
444 echo "$bang_cmd failed with error code $bang_errcode"
445 echo ""
446 rsubj=
447 if [ $bangcount -gt 1 ]; then
448 echo "$bangcount consecutive update failures have occurred since $(config_get girocco.bang.firstfail)"
449 echo ""
451 echo "you will not receive any more notifications until recovery"
452 echo "this status message may be disabled on the project admin page"
453 echo ""
454 echo "Log follows:"
455 echo ""
456 cat "$bang_log"
457 } | mailref "update@$cfg_gitweburl/$proj.git" -s "[$cfg_name] $proj $bang_action failed$rsubj" "$bangaddrs"
458 git config --bool girocco.bang.messagesent true
460 bangthrottle=
461 [ $bangcount -lt 15 ] ||
462 check_interval "girocco.bang.firstfail" $(( $cfg_min_mirror_interval * 3 / 2 )) ||
463 bangthrottle=1
464 bang_trap $bangthrottle
465 [ -n "$bang_errcode" ] && [ "$bang_errcode" != "0" ] || bang_errcode=1
466 exit $bang_errcode
469 # bang_eval CMD... will evaluate the command with well-defined failure mode;
470 # Identical to bang CMD... except the command is eval'd instead of executed.
471 bang_eval() {
472 bang eval "$*"
475 # Default bang settings:
476 bang_setup() {
477 bang_active=
478 bang_action="lame_programmer"
479 bang_trap() { :; }
480 bang_tmpdir="${TMPDIR:-/tmp}"
481 bang_tmpdir="${bang_tmpdir%/}"
482 bang_log="$(mktemp "${bang_tmpdir:-/tmp}/repomgr-XXXXXX")"
483 is_git_dir . || {
484 echo "bang_setup called with current directory not a git directory" >&2
485 exit 1
487 trap 'rm -f "$bang_log"' EXIT
488 trap '[ -z "$bang_active" ] || { bang_errcode=130; bang_failed; }; exit 130' INT
489 trap '[ -z "$bang_active" ] || { bang_errcode=143; bang_failed; }; exit 143' TERM
492 # Remove banged status
493 bang_reset() {
494 rm -f .banged .bangagain .banglog
495 git config --remove-section girocco.bang 2>/dev/null || :
498 # Check to see if banged status
499 is_banged() {
500 [ -e .banged ]
503 # Check to see if banged message was sent
504 was_banged_message_sent() {
505 [ "$(git config --bool girocco.bang.messagesent 2>/dev/null || :)" = "true" ]
508 # Progress report - if show_progress is set, shows the given message.
509 progress() {
510 [ -z "$show_progress" ] || echo "$*"
513 # Project config accessors; must be run in project directory
514 config_get() {
515 case "$1" in
516 *.*)
517 git config "$1";;
519 git config "gitweb.$1";;
520 esac
523 config_set() {
524 git config "gitweb.$1" "$2" && chgrp $var_group config && chmod g+w config
527 config_set_raw() {
528 git config "$1" "$2" && chgrp $var_group config && chmod g+w config
531 config_get_date_seconds() {
532 _dt="$(config_get "$1")" || :
533 [ -n "$_dt" ] || return 1
534 _ds="$(perl -I@basedir@ -MGirocco::Util -e "print parse_any_date('$_dt')")"
535 [ -n "$_ds" ] || return 1
536 echo "$_ds"
539 # Tool for checking whether given number of seconds has not passed yet
540 check_interval() {
541 os="$(config_get_date_seconds "$1")" || return 1
542 ns="$(date +%s)"
543 [ $ns -lt $(($os+$2)) ]
546 # Check if we are running with effective root permissions
547 is_root() {
548 [ "$(id -u 2>/dev/null)" = "0" ]
551 # Check to see if the single argument is a Git directory
552 is_git_dir() {
553 # Just like Git's test except we ignore GIT_OBJECT_DIRECTORY
554 # And we are slightly more picky (must be refs/.+ not refs/.*)
555 [ -d "$1/objects" ] && [ -x "$1/objects" ] || return 1
556 [ -d "$1/refs" ] && [ -x "$1/refs" ] || return 1
557 if [ -L "$1/HEAD" ]; then
558 _hr="$(readlink "$1/HEAD")"
559 case "$_hr" in "refs/"?*) :;; *) return 1;; esac
561 [ -f "$1/HEAD" ] && [ -r "$1/HEAD" ] || return 1
562 read -r _hr <"$1/HEAD" || return 1
563 case "$_hr" in
564 $octet20*)
565 [ "${_hr#*[!0-9a-f]}" = "$_hr" ] || return 1
566 return 0;;
567 ref:refs/?*)
568 return 0;;
569 ref:*)
570 _hr="${_hr##ref:*[ $tab]}"
571 case "$_hr" in "refs/"?*) return 0;; esac
572 esac
573 return 1
576 # List all Git repositories, with given prefix if specified, one-per-line
577 # All project names starting with _ are always excluded from the result
578 get_repo_list() {
579 if [ -n "$1" ]; then
580 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group | LC_ALL=C grep "^$1"
581 else
582 LC_ALL=C cut -d : -f 1,3 "$cfg_chroot"/etc/group
583 fi | while IFS=: read name id; do
584 [ $id -lt 65536 ] || case "$name" in _*) :;; ?*) echo "$name"; esac
585 done
588 # Return success if the given project name has at least one immediate child fork
589 # that has a non-zero length alternates file
590 has_forks_with_alternates() {
591 _prj="${1%.git}"
592 [ -n "$_prj" ] || return 1
593 [ -d "$cfg_reporoot/$_prj" ] || return 1
594 is_git_dir "$cfg_reporoot/$_prj.git" || return 1
596 get_repo_list "$_prj/[^/:][^/:]*:" |
597 while read -r _prjname && [ -n "$_prjname" ]; do
598 ! [ -s "$cfg_reporoot/$_prjname.git/objects/info/alternates" ] ||
599 exit 1 # will only exit implicit subshell created by '|'
600 done
601 then
602 return 1
604 return 0
607 # returns empty string and error for empty string otherwise one of
608 # m => normal Git mirror
609 # s => mirror from svn source
610 # d => mirror from darcs source
611 # b => mirror from bzr source
612 # h => mirror from hg source
613 # w => mirror from mediawiki source
614 # f => mirror from other fast-import source
615 # note that if the string is non-empty and none of s, d, b or h match the
616 # return will always be type m regardless of whether it's a valid Git URL
617 get_url_mirror_type() {
618 case "$1" in
620 return 1
622 svn://* | svn+http://* | svn+https://* | svn+file://* | svn+ssh://*)
623 echo 's'
625 darcs://*)
626 echo 'd'
628 bzr://*)
629 echo 'b'
631 hg+http://* | hg+https://* | hg+file://* | hg+ssh://* | hg::*)
632 echo 'h'
634 mediawiki::*)
635 echo 'w'
638 echo 'm'
640 esac
641 return 0
644 # returns false for empty string
645 # returns true if the passed in url is a mirror using git fast-import
646 is_gfi_mirror_url() {
647 [ -n "$1" ] || return 1
648 case "$(get_url_mirror_type "$1" 2>/dev/null || :)" in
649 d|b|h|w|f)
650 # darcs, bzr, hg and mediawiki mirrors use git fast-import
651 # and so do generic "f" fast-import mirrors
652 return 0
655 # Don't think git-svn currently uses git fast-import
656 # And Git mirrors certainly do not
657 return 1
659 esac
660 # assume it does not use git fast-import
661 return 1
664 # returns false for empty string
665 # returns true if the passed in url is a mirror using git-svn
666 is_svn_mirror_url() {
667 [ -n "$1" ] || return 1
668 [ "$(get_url_mirror_type "$1" 2>/dev/null || :)" = "s" ]
671 # returns mirror url for gitweb.baseurl of git directory
672 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
673 # will fail if the directory does not have .nofetch and gitweb.baseurl
674 # comes back empty -- otherwise .nofetch directories succeed with a "" return
675 # automatically strips any leading "disabled " prefix before returning result
676 get_mirror_url() {
677 _gitdir="${1:-.}"
678 # always return empty for non-mirrors
679 ! [ -e "$_gitdir/.nofetch" ] || return 0
680 _url="$(GIT_DIR="$_gitdir" config_get baseurl 2>/dev/null)" || :
681 _url="${_url##* }"
682 [ -n "$_url" ] || return 1
683 printf '%s\n' "$_url"
684 return 0
687 # returns get_url_mirror_type for gitweb.baseurl of git directory
688 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
689 # will fail if the directory does not have .nofetch and gitweb.baseurl
690 # comes back empty -- otherwise .nofetch directories succeed with a "" return
691 # automatically strips any leading "disabled " prefix before testing
692 get_mirror_type() {
693 _url="$(get_mirror_url "$@")" || return 1
694 [ -n "$_url" ] || return 0
695 get_url_mirror_type "$_url"
698 # returns true if the passed in git dir (defaults to ".") is a mirror using git fast-import
699 is_gfi_mirror() {
700 _url="$(get_mirror_url "$@")" || return 1
701 is_gfi_mirror_url "$_url"
704 # returns true if the passed in git dir (defaults to ".") is a mirror using git-svn
705 is_svn_mirror() {
706 _url="$(get_mirror_url "$@")" || return 1
707 is_svn_mirror_url "$_url"
710 # current directory must already be set to Git repository
711 # if girocco.headok is already true succeeds without doing anything
712 # if rev-parse --verify HEAD succeeds sets headok=true and succeeds
713 # otherwise tries to set HEAD to a symbolic ref to refs/heads/master
714 # then refs/heads/trunk and finally the first top-level head from
715 # refs/heads/* (i.e. only two slashes in the name) and finally any
716 # existing refs/heads. The first one to succeed wins and sets headok=true
717 # and then a successful exit. Otherwise headok is left unset with a failure exit
718 # We use the girocco.headok flag to make sure we only force a valid HEAD symref
719 # when the repository is being set up -- if the HEAD is later deleted (through
720 # a push or fetch --prune) that's no longer our responsibility to fix
721 check_and_set_head() {
722 [ "$(git config --bool girocco.headok 2>/dev/null || :)" != "true" ] || return 0
723 if git rev-parse --verify --quiet HEAD >/dev/null; then
724 git config --bool girocco.headok true
725 return 0
727 for _hr in refs/heads/master refs/heads/trunk; do
728 if git rev-parse --verify --quiet "$_hr"; then
729 _update_head_symref "$_hr"
730 return 0
732 done
733 git for-each-ref --format="%(refname)" refs/heads 2>/dev/null |
734 while read -r _hr; do
735 case "${_hr#refs/heads/}" in */*) :;; *)
736 _update_head_symref "$_hr"
737 exit 1 # exit subshell created by "|"
738 esac
739 done || return 0
740 _hr="$(git for-each-ref --format="%(refname)" refs/heads 2>/dev/null | head -n 1)" || :
741 if [ -n "$_hr" ]; then
742 _update_head_symref "$_hr"
743 return 0
745 return 1
747 _update_head_symref() {
748 git symbolic-ref HEAD "$1"
749 git config --bool girocco.headok true
750 ! [ -d htmlcache ] || { >htmlcache/changed; } 2>/dev/null || :
753 # A well-known UTF-8 locale is required for some of the fast-import providers
754 # in order to avoid mangling characters. Ideally we could use "POSIX.UTF-8"
755 # but that is not reliably UTF-8 but rather usually US-ASCII.
756 # We parse the output of `locale -a` and select a suitable UTF-8 locale at
757 # install time and store that in $var_utf8_locale if one is found.
758 # If we cannot find one in the `locale -a` output then we just use a well-known
759 # UTF-8 locale and hope for the best. We set LC_ALL to our choice and export
760 # it. We only set this temporarily when running the fast-import providers.
761 set_utf8_locale() {
762 LC_ALL="${var_utf8_locale:-en_US.UTF-8}"
763 export LC_ALL
766 # hg-fast-export | git fast-import with error handling in current directory GIT_DIR
767 git_hg_fetch() (
768 set_utf8_locale
769 _python="${PYTHON:-python}"
770 rm -f hg2git-marks.old hg2git-marks.new
771 if [ -f hg2git-marks ] && [ -s hg2git-marks ]; then
772 LC_ALL=C sed 's/^:\([^ ][^ ]*\) \([^ ][^ ]*\)$/\2 \1/' <hg2git-marks | {
773 if [ -n "$var_have_git_185" ]; then
774 git cat-file --batch-check=':%(rest) %(objectname)'
775 else
776 LC_ALL=C sed 's/^\([^ ][^ ]*\) \([^ ][^ ]*\)$/:\2 \1/'
778 } | LC_ALL=C sed '/ missing$/d' >hg2git-marks.old
779 if [ -n "$var_have_git_171" ] &&
780 git rev-parse --quiet --verify refs/notes/hg >/dev/null; then
781 if [ -z "$var_have_git_185" ] ||
782 ! LC_ALL=C cmp -s hg2git-marks hg2git-marks.old; then
783 _nm='hg-fast-export'
784 GIT_AUTHOR_NAME="$_nm"
785 GIT_COMMITTER_NAME="$_nm"
786 GIT_AUTHOR_EMAIL="$_nm"
787 GIT_COMMITTER_EMAIL="$_nm"
788 export GIT_AUTHOR_NAME
789 export GIT_COMMITTER_NAME
790 export GIT_AUTHOR_EMAIL
791 export GIT_COMMITTER_EMAIL
792 git notes --ref=refs/notes/hg prune
793 unset GIT_AUTHOR_NAME
794 unset GIT_COMMITTER_NAME
795 unset GIT_AUTHOR_EMAIL
796 unset GIT_COMMITTER_EMAIL
799 else
800 >hg2git-marks.old
802 _err1=
803 _err2=
804 exec 3>&1
805 { read -r _err1 || :; read -r _err2 || :; } <<-EOT
807 exec 4>&3 3>&1 1>&4 4>&-
809 _e1=0
810 _af="$(git config hg.authorsfile)" || :
811 _cmd='GIT_DIR="$(pwd)" "$_python" "$cfg_basedir/bin/hg-fast-export.py" \
812 --repo "$(pwd)/repo.hg" \
813 --marks "$(pwd)/hg2git-marks.old" \
814 --mapping "$(pwd)/hg2git-mapping" \
815 --heads "$(pwd)/hg2git-heads" \
816 --status "$(pwd)/hg2git-state" \
817 -U unknown --force --flatten --hg-hash'
818 [ -z "$_af" ] || _cmd="$_cmd"' --authors "$_af"'
819 eval "$_cmd" 3>&- || _e1=$?
820 echo $_e1 >&3
823 _e2=0
824 git fast-import \
825 --import-marks="$(pwd)/hg2git-marks.old" \
826 --export-marks="$(pwd)/hg2git-marks.new" \
827 --export-pack-edges="$(pwd)/gfi-packs" \
828 --force 3>&- || _e2=$?
829 echo $_e2 >&3
833 exec 3>&-
834 [ "$_err1" = 0 ] && [ "$_err2" = 0 ] || return 1
835 mv -f hg2git-marks.new hg2git-marks
836 rm -f hg2git-marks.old
837 git for-each-ref --format='%(refname) %(objectname)' refs/heads |
838 LC_ALL=C sed -e 's,^refs/heads/,:,' >hg2git-heads