summaryrefslogtreecommitdiff
path: root/git-smh
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@sbcglobal.net>2016-10-15 11:52:10 -0400
committerLuke Shumaker <lukeshu@sbcglobal.net>2016-10-15 11:52:10 -0400
commit4d5104757eeb06d3daa31295784d664a8204b632 (patch)
tree7b6d9c3af49d1b1a00073a8e1a8ae9e8d3533f95 /git-smh
parent479dae36ebcd194bc61b0f7d9298e87e65128db5 (diff)
this was sitting heregit-smh
Diffstat (limited to 'git-smh')
-rwxr-xr-xgit-smh255
1 files changed, 193 insertions, 62 deletions
diff --git a/git-smh b/git-smh
index 631f05a..0dbeb61 100755
--- a/git-smh
+++ b/git-smh
@@ -12,14 +12,50 @@ fi
print() {
printf "$(_ "$1")\n" "${@:2}"
}
-
+
+usage() {
+ local varname=use_$1
+ print "${!varname}" | sed 's|\$dashless|'"$dashless|g"
+}
+#
# low-ish level smh commands
-smh-root() (
+use_toplevel='usage: $dashless
+Find the most-parent git repository of (possibly nested) git
+submodules.
+
+That is, it is like a submodule-aware `git rev-parse --show-toplevel`.'
+cmd_toplevel() {
+ local mode
+ local args
+ if ! args=$(getopt -n "$dashless" -o h -- "$@"); then
+ mode=err
+ else
+ eval set -- "$args"
+ local mode=normal
+ while true; do
+ case "$1" in
+ -h) shift; mode=help;;
+ --) shift; break;;
+ esac
+ done
+ if [[ $# -gt 0 ]]; then
+ mode=err
+ fi
+ fi
+ case "$mode" in
+ err) usage toplevel; return 2;;
+ help) usage toplevel;;
+ normal) smh-toplevel;;
+ esac
+}
+smh-toplevel() (
local gitdir
+ local cdup
while true; do
gitdir="$(git rev-parse --git-dir)" || return $?
- cd "$(git rev-parse --cdup)" || return $?
+ cdup="$(git rev-parse --show-cdup)" || return $?
+ [[ -z "$cdup" ]] || cd "$cdup" || return $?
if [[ "$gitdir" != */.git/modules/* ]]; then
break
fi
@@ -28,9 +64,52 @@ smh-root() (
pwd
)
+use_split='$dashless [-H|-z] [--] <pathspec>...
+Split a list of filenames into submodule-path/file-path pairs.
+
+If neither -H nor -z is given, it will assume -H if stdout is a TTY,
+or -z otherwise.
+
+ -H human friendly; use spaces and newlines in the output
+ -z separate output with NUL character'
+cmd_split() {
+ local mode
+ local args
+ if ! args=$(getopt -n "$dashless" -o hHz -- "$@"); then
+ mode=err
+ else
+ eval set -- "$args"
+ local mode
+ if [[ -t 1 ]]; then
+ mode=human
+ else
+ mode=machine
+ fi
+ while true; do
+ case "$1" in
+ -h) shift; mode=help;;
+ -H) shift; [[ "$mode" = help ]] || mode=human;;
+ -z) shift; [[ "$mode" = help ]] || mode=machine;;
+ --) shift; break;;
+ esac
+ done
+ fi
+ case "$mode" in
+ err) usage split >&2; return 2;;
+ help) usage split;;
+ machine) smh-split "$@";;
+ human)
+ set -o pipefail
+ smh-split "$@" | xargs -0r printf '%q %q\n'
+ ;;
+ esac
+}
smh-split() {
- local root
- root="$(smh-root)" || return $?
+ if [[ $# = 0 ]]; then
+ return 0
+ fi
+ local toplevel
+ toplevel="$(smh-toplevel)" || return $?
local cwd files
cwd_files=("$@")
@@ -56,43 +135,51 @@ smh-split() {
"$(realpath --no-symlinks --relative-to "$gittop" "$cwd_file")"
fi
i+=1
- done < <(realpath -z --canonicalize-missing --no-symlinks --relative-to "$root" -- "${cwd_files[@]}")
+ done < <(realpath -z --canonicalize-missing --no-symlinks --relative-to "$toplevel" -- "${cwd_files[@]}")
}
-cmd_split() {
- local args
- args=$(getopt -a "$0" -o Hz -- "$@") || return $?
- eval set -- "$args"
+
+use_foreach='usage: $dashless <command>
+Run a command in the root of the repository, and in each submodule.
+
+This is similar to `git submodule foreach`, but with two important
+differences:
+ 1. It also runs the command in the parent repository
+ 2. It runs the command in the deepest repositories first, then the
+ parents. This is backward of `git submodule foreach`.
+
+Bugs:
+ 1. Does not yet set the $name, $path, $sha1 and $toplevel
+ variables that `git submodule foreach` does.'
+cmd_foreach() {
local mode
- if [[ -t 1 ]]; then
- mode=human
+ local args
+ if ! args=$(getopt -n "$dashless" -o h -- "$@"); then
+ mode=err
else
- mode=machine
+ eval set -- "$args"
+ local mode=normal
+ while true; do
+ case "$1" in
+ -h) shift; mode=help;;
+ --) shift; break;;
+ esac
+ done
+ if [[ $# -gt 0 ]]; then
+ mode=err
+ fi
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'
- ;;
+ err) usage foreach >&2; return 2;;
+ help) usage foreach;;
+ normal) smh-toplevel;;
esac
}
-
-smh-foreach-
smh-foreach() {
local cmd="$1"
exec 3<&0
(
- cd "$(smh-root)" || return $?
+ cd "$(smh-toplevel)" || return $?
export LC_ALL=C
git submodule foreach --quiet --recursive pwd | sort --reverse
pwd
@@ -106,8 +193,7 @@ smh-foreach() {
}
done
}
-
-
+#
# smh add
smh-add--helper() {
@@ -118,35 +204,60 @@ smh-add--helper() {
exec git add "$@" "${extra[@]}"
}
+use_add='usage: $dashless [options] [--] files
+Like `git add`, but intelligently changes directory to the correct
+submodule.
+
+ -i, --interactive interactive picking
+ -p, --patch select hunks interactively
+ -e, --edit edit current diff and apply (not currently implemented)
+
+ -v, --verbose be verbose
+ -f, --force allow adding otherwise ignored files
+ -u, --update update tracked files
+ -N, --intent-to-add record only the fact that the path will be added later
+ -A, --all add changes from all tracked and untracked files
+ --ignore-removal ignore paths removed in the working tree (same as --no-all)
+ --refresh don'\''t add, only refresh the index
+ --ignore-errors just skip files which cannot be added because of errors'
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 "$@")
+ if ! args=$(getopt -n "$dashless" -o hvfipeuAN -l verbose,force,interactive,patch,edit,update,all,no-ignore-removal,no-all,ignore-removal,intent-to-add,refresh,ignore-errors -- "$@"); then
+ mode=err
+ else
+ eval set -- "$args"
+ while true; do
+ case "$1" in
+ -h) mode=help;;
+ --interactive|-i) [[ "$mode" = help ]] || mode=interactive;;
+ --patch|-p) [[ "$mode" = help ]] || mode=patch;;
+ --edit|-e) [[ "$mode" = help ]] || mode=edit;;
+
+ --verbose|-v) gitflags+=("$1");;
+ --force|-f) gitflags+=("$1");;
+ --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
+ fi
+ if [[ "$mode" != err ]] && [[ "$mode" != help ]]; then
+ 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 "$@")
+ fi
local cmd
case "$mode" in
+ err) usage add >&2; return 2;;
+ help) usage add;;
batch)
printf -v cmd '%q ' git smh add--helper "${gitflags[@]}" --
smh-foreach "$cmd"
@@ -170,7 +281,7 @@ smh-add() {
;;
esac
}
-
+#
# smh commit
smh-commit--helper() {
@@ -178,35 +289,55 @@ smh-commit--helper() {
cd .. &&
git add "$OLDPWD"
}
+use_commit='usage: $dashless [options]
+Recursively commit in each submodule (and parent repository).
+All arguments are passed directly to `git commit`, so you should avoid
+using this command to add files.'
smh-commit() {
local cmd
printf -v '%q ' git smh commit--helper "$@"
smh-foreach "$cmd"
}
-
+#
# smh push
+use_push='usage: $dashless [options]
+Recursively push in each submodule (and parent repository).
+
+All arguments are passed directly to `git push`.'
smh-push() {
local cmd
printf -v '%q ' git push "$@"
smh-foreach "$cmd"
}
-
+#
main() {
+ dashless=$(basename "$0" | sed -e 's/-/ /')
local cmd="$1"; shift
case "$cmd" in
- root|add|add--helper|commit|commit--helper|push)
- smh-"$cmd" "$@"
+ -h)
+ local _dashless="$dashless"
+ for cmd in toplevel split foreach add commit push; do
+ dashless="$_dashless $cmd"
+ usage "$cmd"
+ done | grep -e '^usage: ' -e '^ or: ' | sed '2,$s/^usage: / or: /'
;;
- split)
+ toplevel|split|foreach)
cmd_"$cmd" "$@"
;;
+ add|commit|push)
+ smh-"$cmd" "$@"
+ ;;
+ add--helper|commit--helper)
+ smh-"$cmd" "$@"
+ ;;
*)
print 'error: Unknown subcommand: %s' "$cmd" >&2
- return 129
+ usage >&2
+ return 2
;;
esac
-}
+}
main "$@"