aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2022-10-21 00:24:53 -0300
committerEuAndreh <eu@euandre.org>2022-10-21 00:24:55 -0300
commit8d9df7203a9bc81114a63ec9294b73487edfde79 (patch)
treec43f3f1123e888492dd03dfa6ccbf45fc2f0f9ce
parentMakefile: Exclude global gitignore file from check-fixme target (diff)
downloaddotfiles-8d9df7203a9bc81114a63ec9294b73487edfde79.tar.gz
dotfiles-8d9df7203a9bc81114a63ec9294b73487edfde79.tar.xz
bin/repos: Add new working utility
This will be the base replacement for mr(1) (A.K.A. myrepos). Instead of having to feed what repositories to track or not manually to the ~/.mrconfig file, dynamically discover them with this new utility.
-rwxr-xr-xbin/repos187
1 files changed, 187 insertions, 0 deletions
diff --git a/bin/repos b/bin/repos
new file mode 100755
index 0000000..27e0879
--- /dev/null
+++ b/bin/repos
@@ -0,0 +1,187 @@
+#!/bin/sh
+set -eu
+
+
+usage() {
+ cat <<-'EOF'
+ Usage:
+ repos [-v] [DIRECTORY]
+ repos -h
+ EOF
+}
+
+help() {
+ cat <<-'EOF'
+
+
+ Options:
+ -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'
+}
+
+
+#
+# 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
+ echo "$ARR"
+ fi
+ echo "$ELT" # | array_encode
+}
+
+arr_includes() {
+ ARR="$1"
+ ELT="$2"
+ echo "$ARR" | while read -r el; do
+ # if [ "$(printf '%s\n' "$el" | array_decode)" = "$ELT" ]; then
+ 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() {
+ if [ -e "$1"/.git ]; then
+ printf 'git:%s\n' "$1"
+ return 0
+ elif [ -e "$1"/.hg ]; then
+ printf 'mercurial:%s\n' "$1"
+ return 0
+ elif [ -e "$1"/.bk ]; then
+ printf 'bitkeeper:%s\n' "$1"
+ return 0
+ elif [ -e "$1"/_darcs ]; then
+ printf 'darcs:%s\n' "$1"
+ return 0
+ elif [ -e "$1"/CVS/ ]; then
+ printf 'cvs:%s\n' "$1"
+ return 0
+ elif [ -e "$1"/"$(basename "$1")".fossil ]; then
+ printf 'fossil:%s\n' "$1"
+ return 0
+ fi
+ return 1
+}
+
+
+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