#!/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