diff options
-rwxr-xr-x | git-smh | 212 |
1 files changed, 212 insertions, 0 deletions
@@ -0,0 +1,212 @@ +#!/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 "$@" |