#!/bin/sh
set -eu
usage() {
cat <<-'EOF'
Usage:
repos [-e DIR...] [-v] [DIRECTORY]
repos -h
EOF
}
help() {
cat <<-'EOF'
Options:
-e DIR exclude the given directory from traversing
-v enable verbose mode
-h, --help show this message
DIRECTORY the folder to traverse
Traverse DIRECTORY looking for VCS repositores. As soon as
one is found, stop recursing, and emit its name alongside its
type.
On verbose mode, print the directories being visited.
Examples:
Show all repositories under ~/dev/, excluding a few directories:
$ repos -e ~/dev/go/ -e ~/dev/quicklisp/ ~/dev/
# ...
EOF
}
for flag in "$@"; do
case "$flag" in
(--)
break
;;
(--help)
usage
help
exit
;;
(*)
;;
esac
done
# Similar to urlencode but only for % and \n (and not \0).
array_encode() {
sed 's/%/%25/g' | sed -e :a -e '$!N; s/\n/%0A/; ta'
}
array_decode() {
sed -e 's/%0A/\
/g' -e 's/%25/%/g'
}
#
# CAVEAT:
# To avoid needing to keep decoding the array elements on every call to
# `arr_includes`, assume directory names don't contain newlines. This makes the
# current code scanning ~/dev/ from 8 seconds go to 1 second.
#
arr_push() {
ARR="$1"
ELT="$2"
if [ -n "$ARR" ]; then
printf '%s\n' "$ARR"
fi
printf '%s\n' "$ELT" # | array_encode (see CAVEAT)
}
arr_includes() {
ARR="$1"
ELT="$2"
printf '%s\n' "$ARR" | while read -r el; do
# if [ "$(printf '%s\n' "$el" | array_decode)" = "$ELT" ]; then (see CAVEAT)
if [ "$el" = "$ELT" ]; then
return 2
fi
done
if [ $? = 2 ]; then
return 0
else
return 1
fi
}
EXCLUDE=
VERBOSE=false
while getopts 'e:vh' flag; do
case "$flag" in
(e)
case "$OPTARG" in
*/)
ARG="$OPTARG"
;;
*)
ARG="$OPTARG/"
;;
esac
EXCLUDE="$(arr_push "$EXCLUDE" "$ARG")"
;;
(v)
VERBOSE=true
;;
(h)
usage
help
exit
;;
(*)
usage >&2
exit 2
;;
esac
done
shift $((OPTIND - 1))
is_repository() {
TYPE="$(vcs -C "$1" -t 2>/dev/null)"
if [ -n "$TYPE" ]; then
printf '%s\n' "$1"
else
return 1
fi
}
traverse_directory() {
if [ "$VERBOSE" = true ]; then
printf 'cur: %s\n' "$1" >&2
fi
if arr_includes "$EXCLUDE" "$1"; then
return
fi
if is_repository "$1"; then
return
fi
for d in "$1"/*; do
if [ "$VERBOSE" = true ]; then
printf 'cur: %s\n' "$d" >&2
fi
if [ ! -d "$d" ]; then
continue
fi
if arr_includes "$EXCLUDE" "$d/"; then
continue
fi
if ! is_repository "$d"; then
traverse_directory "$d"
fi
done
}
if [ -z "${1:-}" ]; then
set -- "$PWD"
fi
for dir in "$@"; do
traverse_directory "${dir%%/}"
done