#!/bin/bash usage () { echo "Usage:" `basename $0` \ "[-h|-u] [-b|-R [opt]] {-q|-c|-a} [-w ref] [-W ref] [-r ref] [-o] [-Q dir] [-t types] [--] command..."; } help () { usage cat < 0) { print "$deep,$when"; } ' } # TODO: git config -l -f .gitmodules can extract this information. # Split a .gitmodules file up into a single line per submodule, with \f as delimiter: dismember () { sed -e 's/^\[submodule.*\]$/\v/' <"$1" | tr '\n\v' '\f\n' | tr -d '\t' echo } # Extract a value from the resulting \f-delimited line: extract () { word="$1" shift val=`echo "$@" | tr '\f' '\n' | grep "^ *$word *=" | cut -d = -f 2` # Unquoted, to strip trailing/leading space and simplify any in between: echo $val } # Extract origin's URL in a git repo (under an entry in $QTROOT): getgit () { (cd $1 && git remote -v | grep -w ^origin | grep '(fetch)$' | tr ' ' '\t' | cut -f 2) } isremoteurl () { for url do if echo "$url" | grep -q '^[a-z][a-z0-9]*://' then return 0 fi done return 1 } # Iterate: src dst status branch # where src is a git repo, dst a local directory to affect (with # possibly spurious .git suffix) and status, branch are as in the # .gitmodules files. modules () { echo $QTROOT | tr ':' '\n' | while read root do gitroot=`getgit $root` if [ "$PARENT" ] then if [ "$BARE" ] then here=`basename $gitroot` else here=. fi echo $gitroot $here essential HEAD fi gitroot=`dirname $gitroot` # May need an option to not skip sub-modules of 3rdparty sub-modules: find $root -type d \( -name .git -o -name 3rdparty \) -prune -o \ -type f -name .gitmodules -print | while read src do here=`dirname $src` master=`cd "$here" && git branch | sed -ne '/^\*/ s/^\* *// p'` there=`getgit $here` there=`dirname $there` if [ "$there" ] then there="${there%/}/" else there="git://code.qt.io/qt/" fi here="$here/" here="${here#$root/}" if grep -q '^[ ]*initrepo *=' "$src" then getstatus () { init=`extract initrepo "$@"` if [ "$init" = true ] then echo essential else echo ignore fi } else getstatus () { extract status "$@"; } fi dismember "$src" | while read line do [ "$line" ] || continue # Other fields: depends, recommends, priority, project, qt path=`extract path "$line"` url=`extract url "$line"` url="${url#../}" url="${url%.git}.git" branch=`extract branch "$line"` [ "$branch" != . ] || branch="$master" status=`getstatus "$line"` qt=`extract qt "$line"` [ "$qt" = false ] && status=tool [ "$status" ] || status=clear isremoteurl "$url" || url="$there$url" echo "$url" "$here$path" "$status" "$branch" done done done | sort -u } gitdir () { if [ -f ".git" ] then while read label path do if [ "$label" == 'gitdir:' ] then echo "$path" break fi done < ".git" else echo ".git" fi } # Don't set QTROOT; let environment supply a default, if given. CHAT=maybe PARENT= GITDIR= RECURSE= REMOTES= REMOVES= ANN=yes TYPE='*' NEED= BARE= OPT= OFF= ON= while [ $# -gt 0 ] do case "$1" in -a|--announce) ANN=yes; shift ;; -b|--bare) BARE=yes; shift ;; -c|--chatty) CHAT=yes; shift ;; -g|--git) GITDIR=yes; shift ;; -h|--help) help; exit 0 ;; -o|--as-option) OPT=yes; shift ;; -p|--parent) PARENT=yes; shift ;; -q|--quiet) CHAT=; ANN=; shift ;; -Q|--qt-root) QTROOT="$2"; shift 2 ;; -r|--has-ref) NEED="$NEED $2"; shift 2 ;; -R|--recurse) shift # expr gets confused by -- if that's what $1 is, so protect: if expr ":$1" : '^:-' >/dev/null then RECURSE=+,late # no value given, use default elif RECURSE=$(recursor "$1") then shift else die "Bad value for --recurse (maybe you needed -- before it): $1" fi ;; -s|--has-remote) REMOTES="$REMOTES $2"; shift 2 ;; -S|--lacks-remote) REMOVES="$REMOVES $2"; shift 2 ;; -t|--type) TYPE="$2"; shift 2 ;; -u|--usage) usage; exit 0 ;; -w|--working) ON="$ON $2"; shift 2 ;; -W|--not-working) OFF="$OFF $2"; shift 2 ;; --) shift; break ;; -*) usage >&2; die "Unrecognised option: $1" ;; *) break ;; esac done [ -z "$OPT" -o -z "$GITDIR" ] || die "Conflict between --as-option and --git" if [ "$TYPE" = default ] then TYPE='essential|addon|preview|deprecated|tool' fi # match init-repository if [ "$QTROOT" ] then : elif [ "$BARE" ] then QTROOT=$HOME/work/Qt-dev else QTROOT=. fi typecheck () { val=`eval "case '$1' in $TYPE) echo good;; esac"` [ "$val" ] || return 1 } if [ "$CHAT" = maybe ] then if [ "$BARE" = yes ] then CHAT=yes else CHAT= fi fi mayberecurse () { if [ "${RECURSE#*,}" = "$1" -a "${2%.git}" = qt5 -a -d "$2" ] then ( # Sub-shell to contain the cd: if cd "$2" then shift 2 if [ "$CHAT" ] then if [ -z "$ANN" ] then VOX="-q -c" fi elif [ "$ANN" ] then VOX="-q -a" else VOX="-q" fi case "$RECURSE" in +,*) DOWN=" -R $RECURSE" ;; 1,*) DOWN= ;; *) DOWN=" -R $((${RECURSE%,*} - 1)),${RECURSE#*,}" ;; esac qteach $VOX${BARE:+ -b}${PARENT:+ -p}${OPT:+ -o}${NEED:+ -r "$NEED"}$DOWN -- "$@" else warn "Can't recurse $1 into $2" fi ) fi } if [ -z "$ANN" ] then announce () { true; } elif [ "$BARE" ] then announce () { banner "$1"; } else announce () { banner "$1" "(`git describe --all --dirty --always`)"; } fi if [ "$CHAT" ] then chat () { echo "$@"; } else chat () { true; } fi hasrem () { for rem in $REMOTES do git remote show $rem 2>/dev/null 1>&2 && continue chat "Lacks remote $rem" return 1 done return 0 } lackrem () { for rem in $REMOVES do git remote show $rem 2>/dev/null 1>&2 || continue chat "Has remote $rem" return 1 done return 0 } hasref () { for ref do git rev-parse -q --verify "$ref^{commit}" >/dev/null 2>&1 && continue chat "No $ref" return 1 done return 0 } # Note: check against HEAD, not work tree: isdetached () { git status | head -n 1 | \ grep -q '^\(HEAD detached at\|Not currently on\|interactive rebase in progress\)' \ || return 1 } isoff () { for ref in $OFF do if [ "$ref" = ".detached-HEAD." ] then if isdetached then chat "HEAD detached" return 1 fi continue fi hasref "$ref" || continue git diff --quiet "$ref" HEAD || continue # Only match branch if on, not just coincident: if git branch | tr -d '* ' | grep -q '^'"$ref"'$' then git branch | grep -q '^\* '"$ref"'$' || continue fi chat "On $ref" return 1 done return 0 } if [ "$ON" ] then ison () { for ref in $ON do if [ "$ref" = ".detached-HEAD." ] then if isdetached then return 0 fi continue fi if hasref "$ref" && git diff --quiet "$ref" HEAD then # if it's a branch, must be *on* it, not just coincident: if git branch | tr -d '* ' | grep -q '^'"$ref"'$' then git branch | grep -q '^\* '"$ref"'$' && return 0 else return 0 fi fi done chat "On none of $ON" return 1 } check () { hasrem && lackrem && hasref $NEED && isoff && ison; } else check () { hasrem && lackrem && hasref $NEED && isoff; } fi runin () { m="$1" shift cd "$m" announce "$m" if check then into=`gitdir` if [ -z "$GITDIR" -o -z "$into" ] then eval "$@" || return "$?" else cd "$into" && (eval "$@" || return "$?") fi fi } ST=0 export ORIGIN BRANCH modules | while read ORIGIN dir use BRANCH do typecheck "$use" || continue if [ "$BARE" ] then want="`basename $ORIGIN`" dir="${want%.git}.git" else dir="${dir%.git}" want="$dir/.git" fi mayberecurse early "$dir" "$@" if [ "$OPT" ] then announce "$dir" if ( cd "$dir" 2>/dev/null && ! check ) || ( eval "$@" "$dir" ) then : else err="$?" [ "$ST" -ge "$err" ] || ST="$err" warn "Failed ($err): $* $dir" fi elif [ -e "$want" ] then if (runin "$dir" "$@") then : else err="$?" [ "$ST" -ge "$err" ] || ST="$err" warn "Failed ($err) in $dir: $*" fi elif [ "$BARE" ] && isremoteurl "$ORIGIN" then chat "Skipping remote repo $ORIGIN" else chat "I lack $want for $dir from $ORIGIN" fi mayberecurse late "$dir" "$@" done exit $ST