#!/usr/bin/env bash # # Common code shared by the Cogito toolkit. # Copyright (c) Petr Baudis, 2005 # # This file provides a library containing common code shared with all the # Cogito programs. _cg_cmd=${0##*/} die() { echo $_cg_cmd: $@ >&2 exit 1 } usage() { die "usage: $USAGE" } pager() { local line # Invoke pager only if there's any actual output if read -r line; then ( echo "$line"; cat; ) | LESS="R$LESS" ${PAGER:-less} $PAGER_FLAGS fi } mktemp() { if [ "$has_mktemp" ]; then $has_mktemp "$@" return fi dirarg= if [ x"$1" = x"-d" ]; then dirarg="-d" shift fi prefix= if [ x"$1" = x"-t" ]; then prefix=${TMPDIR:-/tmp}/ shift fi $(which mktemp) $dirarg $prefix"$1" } stat() { if [ "$1" != "-c" ] || [ "$2" != "%s" -a "$2" != "%i" ]; then echo "INTERNAL ERROR: Unsupported stat call $@" >&2 return 1 fi if [ "$has_stat" ]; then $has_stat "$@" return fi # It's always -c '%s' now. if [ "$2" = "%s" ]; then ls -l "$3" | awk '{ print $5 }' elif [ "$2" = "%i" ]; then ls -lid "$3" | awk '{ print $1 }' fi } readlink() { if [ "$has_readlink" ]; then $has_readlink "$@" return fi if [ "$1" = "-f" ]; then shift target="$(maynormpath "$1")" target="${target%/}" # -e will test the existence of the final target; therefore, # it will also protect against recursive symlinks and such [ -e "$target" ] || return 1 while true; do if ! [ -L "$target" ]; then echo "$target" return 0 fi target2="$(readlink "$target" 2>/dev/null)" || return 1 [ "$target2" ] || return 1 target="$(maynormpath "$target2" "$target"/..)" done return 42 fi line=$(ls -ld "$1" 2>/dev/null) || return 1 case "$line" in *-\>*) echo "${line#* -> }";; *) return 1;; esac return 0 } normpath() { local inp while read inp; do local path path2 path=() path2=() while [[ "$inp" == */* ]]; do path[${#path[@]}]="${inp%%/*}" inp="${inp#*/}" done path[${#path[@]}]="$inp" for (( i=0; $i < ${#path[@]}; i++ )); do [ "${path[$i]}" = "." ] && continue if [ "${path[$i]}" = ".." ]; then [ "${#path2[@]}" -gt 0 ] && unset path2[$((${#path2[@]} - 1))] continue fi path2[${#path2[@]}]="${path[$i]}" done for (( i=0; $i < ${#path2[@]}; i++ )); do echo -n "${path2[$i]}" [ $i -lt $((${#path2[@]} - 1)) ] && echo -n / done echo done } # maynormpath PATH [BASE] # If $PATH is relative, make it absolute wrt. $(pwd) or $BASE if specified. # Basically, call this instead of normpath() if $PATH can ever be absolute. maynormpath() { case "$1" in /*) echo "$1";; *) base="$2"; [ "$base" ] || base="$(pwd)" echo "$base/$1" | normpath esac } showdate() { local secs=$1 tzhours=${2:0:3} tzmins=${2:0:1}${2:3} format="$3" # bash doesn't like leading zeros [ "${tzhours:1:1}" = 0 ] && tzhours=${2:0:1}${2:2:1} secs=$(($secs + $tzhours * 3600 + $tzmins * 60)) [ "$format" ] || format="+%a, %d %b %Y %H:%M:%S $2" if [ "$has_gnudate" ]; then LANG=C $has_gnudate -ud "1970-01-01 UTC + $secs sec" "$format" else LANG=C date -u -r $secs "$format" fi } # Usage: tree_timewarp [--no-head-update] DIRECTION_STR ROLLBACK_BOOL BASE BRANCH tree_timewarp() { local no_head_update= if [ "$1" = "--no-head-update" ]; then no_head_update=1 shift fi local dirstr=$1; shift local rollback=$1; shift local base=$1; shift local branch=$1; shift local patchfile=$(mktemp -t gituncommit.XXXXXX) if [ "$rollback" ]; then cg-diff >$patchfile [ -s "$patchfile" ] && echo "Warning: uncommitted local changes, trying to bring them $dirstr" >&2 else # XXX: This may be suboptimal, but it is also non-trivial to keep # the adds/removes properly. So this is just a quick hack to get it # working without much fuss. cg-diff -r $branch >$patchfile fi git-read-tree -m "$branch" || die "$branch: bad commit" [ "$no_head_update" ] || echo "$branch" > $_git/HEAD # Kill gone files git-diff-tree -z -r $base $branch | xargs -0 bash -c ' while [ "$1" ]; do header="$1"; shift file="$1"; shift # match ":100755 000000 14d43b1abf... 000000000... D" if echo "$header" | egrep "^:([^ ][^ ]* ){4}D" >/dev/null; then rm -- "$file" fi done ' padding git-checkout-cache -f -a # FIXME: Can produce bogus "contains only garbage" messages. cat $patchfile | cg-patch rm $patchfile git-update-cache --refresh >/dev/null } update_index() { git-update-cache --refresh | sed 's/needs update$/locally modified/' } # Takes two object directories and checks if they are the same (symlinked # or so). is_same_repo() { local dir1="$1" dir2="$2" diff=1 # Originally, I wanted to compare readlink output, but that fails # in binding setup; it isn't likely the object database directories # themselves would be binded, but some trunk directories might. # So we just create a file inside and see if it appears on the # second side... if [ ! -w "$dir1" -o ! -w "$dir2" ]; then # ...except in readonly setups. [ "$(readlink -f "$dir1")" = "$(readlink -f "$dir2")" ] && diff=0 else n=$$ while [ -e "$dir1/.,,lnstest-$n" -o -e "$dir2/.,,lnstest-$n" ]; do n=$((n+1)) done touch "$dir1/.,,lnstest-$n" [ -e "$dir2/.,,lnstest-$n" ] && diff=0 rm "$dir1/.,,lnstest-$n" fi return $diff } print_help() { which "cg-$1" >/dev/null 2>&1 || exit 1 sed -n '/^USAGE=/,0s/.*"\(.*\)"/Usage: \1/p' < $(which cg-$1) echo sed -n '3,/^$/s/^# *//p' < $(which cg-$1) exit } for option in "$@"; do [ "$option" = -- ] && break if [ "$option" = "-h" -o "$option" = "--help" ]; then print_help ${_cg_cmd##cg-} fi done ARGS=("$@") ARGPOS=0 if [ -t 1 -a -e "$HOME/.cgrc" ]; then _cg_name=${_cg_cmd#cg-} _cg_defaults1="$(sed -n "/^$_cg_cmd/s/^$_cg_cmd //p" < $HOME/.cgrc)" _cg_defaults2="$(sed -n "/^$_cg_name/s/^$_cg_name //p" < $HOME/.cgrc)" ARGS=($_cg_defaults1 $_cg_defaults2 "${ARGS[@]}") fi optshift() { unset ARGS[$ARGPOS] ARGS=("${ARGS[@]}") [ -z "$1" -o -n "${ARGS[$ARGPOS]}" ] || die "option \`$1' requires an argument" } optfail() { die "unrecognized option \`${ARGS[$ARGPOS]}'" } optconflict() { die "conflicting option \`$CUROPT'" } optparse() { unset OPTARG if [ -z "$1" ]; then case ${ARGS[$ARGPOS]} in --) optshift; return 1 ;; -*) return 0 ;; *) while (( ++ARGPOS < ${#ARGS[@]} )); do [[ "${ARGS[$ARGPOS]}" == -- ]] && return 1 [[ "${ARGS[$ARGPOS]}" == -* ]] && return 0 done; return 1 ;; esac fi CUROPT=${ARGS[$ARGPOS]} local match=${1%=} minmatch=${2:-1} opt=$CUROPT o=$CUROPT val [[ "$1" == *= ]] && val=$match case $match in --*) [ "$val" ] && o=${o%%=*} [ ${#o} -ge $((2 + $minmatch)) -a \ "${match:0:${#o}}" = "$o" ] || return 1 [[ -n "$val" && "$opt" == *=?* ]] \ && ARGS[$ARGPOS]=${opt#*=} \ || optshift $val ;; -?) [[ "$o" == $match* ]] || return 1 [[ "$o" != -?-* || -n "$val" ]] || optfail ARGS[$ARGPOS]=${o#$match} [ "${ARGS[$ARGPOS]}" ] \ && { [ "$val" ] || ARGS[$ARGPOS]=-${ARGS[$ARGPOS]}; } \ || optshift $val ;; *) die "optparse cannot handle $1" ;; esac if [ "$val" ]; then OPTARG=${ARGS[$ARGPOS]} optshift fi } # Optional tools detection/stubbing # check_tool_presence NAME COMMAND EXENAME... # (use $cmd in COMMAND) check_tool() { cmdname="$1"; shift cmdtest="$1"; shift hasname="has_$cmdname" export $hasname= for exename in "$@"; do # We do our own $PATH iteration as it's faster than the fork() # of $(which), and this happens many times every time we # execute some cg tool. save_IFS="$IFS"; IFS=: for dir in $PATH; do IFS="$save_IFS" cmd="$dir/$exename" if [ -x "$cmd" ] && eval $cmdtest; then export $hasname="$cmd" break fi done IFS="$save_IFS" [ "$hasname" ] && break done 2>/dev/null } if ! [ "$__cogito_subsequent" ]; then export __cogito_subsequent=1 check_tool mktemp 'todel=$($cmd -t) && rm $todel' mktemp check_tool stat '$cmd -c %s / >/dev/null' stat gnustat gstat check_tool readlink '$cmd -f / >/dev/null' readlink check_tool gnudate '$cmd -Rud "1970-01-01 UTC" >/dev/null' date gnudate gdate fi _git=${GIT_DIR:-.git} if [ ! "$_git_repo_unneeded" ] && [ ! "$GIT_DIR" ] && [ ! -d $_git ]; then rootpath=. # while not / while [ ! -d $rootpath/.git ] && [ "$(stat -c %i $rootpath)" != "$(stat -c %i $rootpath/..)" ]; do rootpath=../$rootpath done if [ -d $rootpath/.git ]; then mainpath="$(echo "$(pwd)/$rootpath" | normpath)" _git_relpath=$(pwd)/ export _git_relpath=${_git_relpath:$((${#mainpath}+1))} cd "$rootpath" fi fi _git_objects="${GIT_OBJECT_DIRECTORY:-$_git/objects}" # Check if we have something to work on, unless the script can do w/o it. if [ ! "$_git_repo_unneeded" ]; then if [ ! -d "$_git" ]; then echo "There is no GIT repository here ($_git not found)" >&2 exit 1 elif [ ! -x "$_git" ]; then echo "You do not have permission to access this GIT repository" >&2 exit 1 fi _git_head=master [ -L "$_git/HEAD" ] && _git_head="$(basename "$(readlink "$_git/HEAD")")" [ -s "$_git/head-name" ] && _git_head="$(cat "$_git/head-name")" fi # Check if the script requires to be called from the workdir root. if [ "$_git_requires_root" ] && [ "$_git_relpath" ]; then echo "This command can be ran only from the project root" >&2 exit 1 fi # Backward compatibility hacks: # Fortunately none as of now.