summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xgit-smh212
1 files changed, 212 insertions, 0 deletions
diff --git a/git-smh b/git-smh
new file mode 100755
index 0000000..631f05a
--- /dev/null
+++ b/git-smh
@@ -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 "$@"