hashes: be tolerant of longer hash values
[girocco/readme.git] / shlib.sh
blobeb757ac7a7b409d802409b8446141f6f654efae9
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 any forks
589 has_forks() {
590 _prj="${1%.git}"
591 [ -n "$_prj" ] || return 1
592 [ -d "$cfg_reporoot/$_prj" ] || return 1
593 is_git_dir "$cfg_reporoot/$_prj.git" || return 1
594 test $(get_repo_list "$_prj/[^/:][^/:]*:" | LC_ALL=C wc -l) -gt 0
597 # returns empty string and error for empty string otherwise one of
598 # m => normal Git mirror
599 # s => mirror from svn source
600 # d => mirror from darcs source
601 # b => mirror from bzr source
602 # h => mirror from hg source
603 # w => mirror from mediawiki source
604 # f => mirror from other fast-import source
605 # note that if the string is non-empty and none of s, d, b or h match the
606 # return will always be type m regardless of whether it's a valid Git URL
607 get_url_mirror_type() {
608 case "$1" in
610 return 1
612 svn://* | svn+http://* | svn+https://* | svn+file://* | svn+ssh://*)
613 echo 's'
615 darcs://*)
616 echo 'd'
618 bzr://*)
619 echo 'b'
621 hg+http://* | hg+https://* | hg+file://* | hg+ssh://* | hg::*)
622 echo 'h'
624 mediawiki::*)
625 echo 'w'
628 echo 'm'
630 esac
631 return 0
634 # returns false for empty string
635 # returns true if the passed in url is a mirror using git fast-import
636 is_gfi_mirror_url() {
637 [ -n "$1" ] || return 1
638 case "$(get_url_mirror_type "$1" 2>/dev/null || :)" in
639 d|b|h|w|f)
640 # darcs, bzr, hg and mediawiki mirrors use git fast-import
641 # and so do generic "f" fast-import mirrors
642 return 0
645 # Don't think git-svn currently uses git fast-import
646 # And Git mirrors certainly do not
647 return 1
649 esac
650 # assume it does not use git fast-import
651 return 1
654 # returns false for empty string
655 # returns true if the passed in url is a mirror using git-svn
656 is_svn_mirror_url() {
657 [ -n "$1" ] || return 1
658 [ "$(get_url_mirror_type "$1" 2>/dev/null || :)" = "s" ]
661 # returns mirror url for gitweb.baseurl of git directory
662 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
663 # will fail if the directory does not have .nofetch and gitweb.baseurl
664 # comes back empty -- otherwise .nofetch directories succeed with a "" return
665 # automatically strips any leading "disabled " prefix before returning result
666 get_mirror_url() {
667 _gitdir="${1:-.}"
668 # always return empty for non-mirrors
669 ! [ -e "$_gitdir/.nofetch" ] || return 0
670 _url="$(GIT_DIR="$_gitdir" config_get baseurl 2>/dev/null)" || :
671 _url="${_url##* }"
672 [ -n "$_url" ] || return 1
673 printf '%s\n' "$_url"
674 return 0
677 # returns get_url_mirror_type for gitweb.baseurl of git directory
678 # (GIT_DIR) passed in as the argument (which defaults to "." if omitted)
679 # will fail if the directory does not have .nofetch and gitweb.baseurl
680 # comes back empty -- otherwise .nofetch directories succeed with a "" return
681 # automatically strips any leading "disabled " prefix before testing
682 get_mirror_type() {
683 _url="$(get_mirror_url "$@")" || return 1
684 [ -n "$_url" ] || return 0
685 get_url_mirror_type "$_url"
688 # returns true if the passed in git dir (defaults to ".") is a mirror using git fast-import
689 is_gfi_mirror() {
690 _url="$(get_mirror_url "$@")" || return 1
691 is_gfi_mirror_url "$_url"
694 # returns true if the passed in git dir (defaults to ".") is a mirror using git-svn
695 is_svn_mirror() {
696 _url="$(get_mirror_url "$@")" || return 1
697 is_svn_mirror_url "$_url"
700 # current directory must already be set to Git repository
701 # if girocco.headok is already true succeeds without doing anything
702 # if rev-parse --verify HEAD succeeds sets headok=true and succeeds
703 # otherwise tries to set HEAD to a symbolic ref to refs/heads/master
704 # then refs/heads/trunk and finally the first top-level head from
705 # refs/heads/* (i.e. only two slashes in the name) and finally any
706 # existing refs/heads. The first one to succeed wins and sets headok=true
707 # and then a successful exit. Otherwise headok is left unset with a failure exit
708 # We use the girocco.headok flag to make sure we only force a valid HEAD symref
709 # when the repository is being set up -- if the HEAD is later deleted (through
710 # a push or fetch --prune) that's no longer our responsibility to fix
711 check_and_set_head() {
712 [ "$(git config --bool girocco.headok 2>/dev/null || :)" != "true" ] || return 0
713 if git rev-parse --verify --quiet HEAD >/dev/null; then
714 git config --bool girocco.headok true
715 return 0
717 for _hr in refs/heads/master refs/heads/trunk; do
718 if git rev-parse --verify --quiet "$_hr"; then
719 _update_head_symref "$_hr"
720 return 0
722 done
723 git for-each-ref --format="%(refname)" refs/heads 2>/dev/null |
724 while read -r _hr; do
725 case "${_hr#refs/heads/}" in */*) :;; *)
726 _update_head_symref "$_hr"
727 exit 1 # exit subshell created by "|"
728 esac
729 done || return 0
730 _hr="$(git for-each-ref --format="%(refname)" refs/heads 2>/dev/null | head -n 1)" || :
731 if [ -n "$_hr" ]; then
732 _update_head_symref "$_hr"
733 return 0
735 return 1
737 _update_head_symref() {
738 git symbolic-ref HEAD "$1"
739 git config --bool girocco.headok true
740 ! [ -d htmlcache ] || { >htmlcache/changed; } 2>/dev/null || :
743 # A well-known UTF-8 locale is required for some of the fast-import providers
744 # in order to avoid mangling characters. Ideally we could use "POSIX.UTF-8"
745 # but that is not reliably UTF-8 but rather usually US-ASCII.
746 # We parse the output of `locale -a` and select a suitable UTF-8 locale at
747 # install time and store that in $var_utf8_locale if one is found.
748 # If we cannot find one in the `locale -a` output then we just use a well-known
749 # UTF-8 locale and hope for the best. We set LC_ALL to our choice and export
750 # it. We only set this temporarily when running the fast-import providers.
751 set_utf8_locale() {
752 LC_ALL="${var_utf8_locale:-en_US.UTF-8}"
753 export LC_ALL
756 # hg-fast-export | git fast-import with error handling in current directory GIT_DIR
757 git_hg_fetch() (
758 set_utf8_locale
759 _python="${PYTHON:-python}"
760 rm -f hg2git-marks.old hg2git-marks.new
761 if [ -f hg2git-marks ] && [ -s hg2git-marks ]; then
762 LC_ALL=C sed 's/^:\([^ ][^ ]*\) \([^ ][^ ]*\)$/\2 \1/' <hg2git-marks | {
763 if [ -n "$var_have_git_185" ]; then
764 git cat-file --batch-check=':%(rest) %(objectname)'
765 else
766 LC_ALL=C sed 's/^\([^ ][^ ]*\) \([^ ][^ ]*\)$/:\2 \1/'
768 } | LC_ALL=C sed '/ missing$/d' >hg2git-marks.old
769 if [ -n "$var_have_git_171" ] &&
770 git rev-parse --quiet --verify refs/notes/hg >/dev/null; then
771 if [ -z "$var_have_git_185" ] ||
772 ! LC_ALL=C cmp -s hg2git-marks hg2git-marks.old; then
773 _nm='hg-fast-export'
774 GIT_AUTHOR_NAME="$_nm"
775 GIT_COMMITTER_NAME="$_nm"
776 GIT_AUTHOR_EMAIL="$_nm"
777 GIT_COMMITTER_EMAIL="$_nm"
778 export GIT_AUTHOR_NAME
779 export GIT_COMMITTER_NAME
780 export GIT_AUTHOR_EMAIL
781 export GIT_COMMITTER_EMAIL
782 git notes --ref=refs/notes/hg prune
783 unset GIT_AUTHOR_NAME
784 unset GIT_COMMITTER_NAME
785 unset GIT_AUTHOR_EMAIL
786 unset GIT_COMMITTER_EMAIL
789 else
790 >hg2git-marks.old
792 _err1=
793 _err2=
794 exec 3>&1
795 { read -r _err1 || :; read -r _err2 || :; } <<-EOT
797 exec 4>&3 3>&1 1>&4 4>&-
799 _e1=0
800 _af="$(git config hg.authorsfile)" || :
801 _cmd='GIT_DIR="$(pwd)" "$_python" "$cfg_basedir/bin/hg-fast-export.py" \
802 --repo "$(pwd)/repo.hg" \
803 --marks "$(pwd)/hg2git-marks.old" \
804 --mapping "$(pwd)/hg2git-mapping" \
805 --heads "$(pwd)/hg2git-heads" \
806 --status "$(pwd)/hg2git-state" \
807 -U unknown --force --flatten --hg-hash'
808 [ -z "$_af" ] || _cmd="$_cmd"' --authors "$_af"'
809 eval "$_cmd" 3>&- || _e1=$?
810 echo $_e1 >&3
813 _e2=0
814 git fast-import \
815 --import-marks="$(pwd)/hg2git-marks.old" \
816 --export-marks="$(pwd)/hg2git-marks.new" \
817 --export-pack-edges="$(pwd)/gfi-packs" \
818 --force 3>&- || _e2=$?
819 echo $_e2 >&3
823 exec 3>&-
824 [ "$_err1" = 0 ] && [ "$_err2" = 0 ] || return 1
825 mv -f hg2git-marks.new hg2git-marks
826 rm -f hg2git-marks.old
827 git for-each-ref --format='%(refname) %(objectname)' refs/heads |
828 LC_ALL=C sed -e 's,^refs/heads/,:,' >hg2git-heads