#!/usr/bin/env bash unset CDPATH IFS=$' \t\n' if type gettext &>/dev/null; then _() { gettext "$@"; } else _() { echo "$@"; } fi print() { printf "$(_ "$1")\n" "${@:2}" } # low-ish level smh commands smh-root() ( local gitdir while true; do gitdir="$(git rev-parse --git-dir)" || return $? cd "$(git rev-parse --cdup)" || return $? if [[ "$gitdir" != */.git/modules/* ]]; then break fi cd .. || return $? done pwd ) smh-split() { local root root="$(smh-root)" || return $? local cwd files cwd_files=("$@") declare -i i=0 local smh_file cwd_file local gittop while read -r -d '' smh_file; do cwd_file="${cwd_files[$i]}" if [[ "$smh_file" = ../* ]] || ! [[ -e "$smh_file" ]]; then # let git print the error message git add "$cwd_file" return $? else if [[ -d "$cwd_file" ]]; then gittop="$((cd "$cwd_file"; git --show-toplevel))" else gittop="$((cd "$(dirname -- "$cwd_file")"; git --show-toplevel))" fi printf '%s\0' \ "$(realpath --no-symlinks --relative-to . "$gittop")" \ "$(realpath --no-symlinks --relative-to "$gittop" "$cwd_file")" fi i+=1 done < <(realpath -z --canonicalize-missing --no-symlinks --relative-to "$root" -- "${cwd_files[@]}") } cmd_split() { local args args=$(getopt -a "$0" -o Hz -- "$@") || return $? eval set -- "$args" local mode if [[ -t 1 ]]; then mode=human else mode=machine fi while true; do case "$1" in -H) shift; mode=human;; -z) shift; mode=machine;; --) shift; break;; esac done case "$mode" in machine) smh-split "$@" ;; human) set -o pipefail smh-split "$@" | xargs -0r printf '%q %q\n' ;; esac } smh-foreach- smh-foreach() { local cmd="$1" exec 3<&0 ( cd "$(smh-root)" || return $? export LC_ALL=C git submodule foreach --quiet --recursive pwd | sort --reverse pwd ) | xargs -0 realpath -zs --relative-to=. | while read -r -d '' path; do ( cd "$path" && sh -c "$cmd" ) <&3 3<&- || { print "Stopping at '%s'; script returned non-zero status." "$path" return 1 } done } # smh add smh-add--helper() { local file extra file="$(git rev-parse --git-path SMH_ADD)" read -a extra -r -d '' < "$file" rm -f "$file" exec git add "$@" "${extra[@]}" } smh-add() { local mode=batch local gitflags=() local args args=$(getopt -a "$0" -o vfipeuAN -l verbose,force,interactive,patch,edit,update,all,no-ignore-removal,no-all,ignore-removal,intent-to-add,refresh,ignore-errors -- "$@") || return $? eval set -- "$args" while true; do case "$1" in --verbose|-v) gitflags+=("$1");; --force|-f) gitflags+=("$1");; --interactive|-i) mode=interactive;; --patch|-p) mode=patch;; --edit|-e) mode=edit;; --update|-u) gitflags+=("$1");; -A|--all|-no-ignore-removal) gitflags+=("$1");; --no-all|--ignore-removal) gitflags+=("$1");; --intent-to-add|-N) gitflags+=("$1");; --refresh) gitflags+=("$1");; --ignore-errors) gitflags+=("$1");; --) shift; break;; esac shift done while read -r -d '' dir && read -r -d '' file; do printf '%s\0' "$file" >> "$(cd "$dir" && git rev-parse --git-path 'SMH_ADD')" done < <(smh-split "$@") local cmd case "$mode" in batch) printf -v cmd '%q ' git smh add--helper "${gitflags[@]}" -- smh-foreach "$cmd" ;; interactive) gitflags=(-i "${gitflags[@]}") printf -v cmd '%q ' git smh add--helper "${gitflags[@]}" -- printf -v cmd '%q ' git smh foreach "$cmd" sed -r '/^(7|q|qu|qui|quit)$/q' | script --return --quiet -c "$cmd" /dev/null ;; patch) gitflags=(-p "${gitflags[@]}") printf -v cmd '%q ' git smh add--helper "${gitflags[@]}" -- printf -v cmd '%q ' git smh foreach "$cmd" sed '/^q$/q' | script --return --quiet -c "$cmd" /dev/null ;; edit) gitflags=(-e "${gitflags[@]}") print 'not implemented: smh add --edit' >&2 return 4 ;; esac } # smh commit smh-commit--helper() { git commit "$@" && cd .. && git add "$OLDPWD" } smh-commit() { local cmd printf -v '%q ' git smh commit--helper "$@" smh-foreach "$cmd" } # smh push smh-push() { local cmd printf -v '%q ' git push "$@" smh-foreach "$cmd" } main() { local cmd="$1"; shift case "$cmd" in root|add|add--helper|commit|commit--helper|push) smh-"$cmd" "$@" ;; split) cmd_"$cmd" "$@" ;; *) print 'error: Unknown subcommand: %s' "$cmd" >&2 return 129 ;; esac } main "$@"