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