X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=topgit.git;a=blobdiff_plain;f=tg.sh;h=1f259e3a03d60dad30bac2a34253cd9a78a1d7ca;hp=b2b9191c0ae847952be9217fa11d7061fca33f12;hb=b8b7a716e893c5452abe1bc838c49eda628513af;hpb=54f24fdf5691baa3dcc7252098efd13cc4fc1620 diff --git a/tg.sh b/tg.sh index b2b9191..1f259e3 100644 --- a/tg.sh +++ b/tg.sh @@ -3,6 +3,7 @@ # (c) Petr Baudis 2008 # GPLv2 +TG_VERSION=0.8 ## Auxiliary functions @@ -17,16 +18,37 @@ die() exit 1 } +# cat_file "topic:file" +# Like `git cat-file blob $1`, but topics '(i)' and '(w)' means index and worktree +cat_file() +{ + arg="$1" + case "$arg" in + '(w):'*) + arg=$(echo "$arg" | tail --bytes=+5) + cat "$arg" + return + ;; + '(i):'*) + # ':file' means cat from index + arg=$(echo "$arg" | tail --bytes=+5) + git cat-file blob ":$arg" + ;; + *) + git cat-file blob "$arg" + esac +} + # setup_hook NAME setup_hook() { - hook_call="\"\$(tg --hooks-path)\"/$1 \"\$@\"" + hook_call="\"\$($tg --hooks-path)\"/$1 \"\$@\"" if [ -f "$git_dir/hooks/$1" ] && fgrep -q "$hook_call" "$git_dir/hooks/$1"; then # Another job well done! return fi - # Prepare incanation + # Prepare incantation if [ -x "$git_dir/hooks/$1" ]; then hook_call="$hook_call"' || exit $?' else @@ -63,8 +85,8 @@ measure_branch() _bname="$1"; _base="$2" [ -n "$_base" ] || _base="refs/top-bases/$_bname" # The caller should've verified $name is valid - _commits="$(git rev-list "$_bname" ^"$_base" | wc -l)" - _nmcommits="$(git rev-list --no-merges "$_bname" ^"$_base" | wc -l)" + _commits="$(git rev-list "$_bname" ^"$_base" -- | wc -l)" + _nmcommits="$(git rev-list --no-merges "$_bname" ^"$_base" -- | wc -l)" if [ $_commits -gt 1 ]; then _suffix="commits" else @@ -77,7 +99,7 @@ measure_branch() # Whether B1 is a superset of B2. branch_contains() { - [ -z "$(git rev-list ^"$1" "$2")" ] + [ -z "$(git rev-list --max-count=1 ^"$1" "$2" --)" ] } # ref_exists REF @@ -94,6 +116,16 @@ has_remote() [ -n "$base_remote" ] && ref_exists "remotes/$base_remote/$1" } +branch_annihilated() +{ + _name="$1"; + + # use the merge base in case the base is ahead. + mb="$(git merge-base "refs/top-bases/$_name" "$_name")"; + + test "$(git rev-parse "$mb^{tree}")" = "$(git rev-parse "$_name^{tree}")"; +} + # recurse_deps CMD NAME [BRANCHPATH...] # Recursively eval CMD on all dependencies of NAME. # CMD can refer to $_name for queried branch name, @@ -104,6 +136,7 @@ has_remote() # of the whole function. # If recurse_deps() hits missing dependencies, it will append # them to space-separated $missing_deps list and skip them. +# remote dependencies are processed if no_remotes is unset. recurse_deps() { _cmd="$1"; shift @@ -111,12 +144,17 @@ recurse_deps() _depchain="$*" _depsfile="$(mktemp -t tg-depsfile.XXXXXX)" - # Check also our base against remote base. Checking our head - # against remote head has to be done in the helper. - if has_remote "top-bases/$_name"; then + # If no_remotes is unset check also our base against remote base. + # Checking our head against remote head has to be done in the helper. + if test -z "$no_remotes" && has_remote "top-bases/$_name"; then echo "refs/remotes/$base_remote/top-bases/$_name" >>"$_depsfile" fi - git cat-file blob "$_name:.topdeps" >>"$_depsfile" + + # if the branch was annihilated, there exists no .topdeps file + if ! branch_annihilated "$_name"; then + #TODO: handle nonexisting .topdeps? + git cat-file blob "$_name:.topdeps" >>"$_depsfile"; + fi; _ret=0 while read _dep; do @@ -189,7 +227,7 @@ needs_update() # branch_empty NAME branch_empty() { - [ -z "$(git diff-tree "refs/top-bases/$1" "$1" | fgrep -v " .top")" ] + [ -z "$(git diff-tree "refs/top-bases/$1" "$1" -- | fgrep -v " .top")" ] } # switch_to_base NAME [SEED] @@ -209,6 +247,12 @@ switch_to_base() do_help() { if [ -z "$1" ] ; then + # This is currently invoked in all kinds of circumstances, + # including when the user made a usage error. Should we end up + # providing more than a short help message, then we should + # differentiate. + # Petr's comment: http://marc.info/?l=git&m=122718711327376&w=2 + ## Build available commands list for help output cmds= @@ -222,50 +266,117 @@ do_help() sep="|" done - echo "TopGit v0.2 - A different patch queue manager" - echo "Usage: tg ($cmds|help) ..." - elif [ -r "@sharedir@/tg-$1.txt" ] ; then - cat "@sharedir@/tg-$1.txt" + echo "TopGit v$TG_VERSION - A different patch queue manager" + echo "Usage: tg [-r REMOTE] ($cmds|help) ..." + elif [ -r "@cmddir@"/tg-$1 ] ; then + setup_pager + @cmddir@/tg-$1 -h 2>&1 || : + echo + if [ -r "@sharedir@/tg-$1.txt" ] ; then + cat "@sharedir@/tg-$1.txt" + fi else echo "`basename $0`: no help for $1" 1>&2 + do_help + exit 1 fi } +## Pager stuff + +# isatty FD +isatty() +{ + test -t $1 +} + +# setup_pager +# Spawn pager process and redirect the rest of our output to it +setup_pager() +{ + isatty 1 || return 0 + + # TG_PAGER = GIT_PAGER | PAGER | less + # NOTE: GIT_PAGER='' is significant + TG_PAGER=${GIT_PAGER-${PAGER-less}} + + [ -z "$TG_PAGER" -o "$TG_PAGER" = "cat" ] && return 0 + + + # now spawn pager + export LESS=${LESS:-FRSX} # as in pager.c:pager_preexec() + + _pager_fifo_dir="$(mktemp -t -d tg-pager-fifo.XXXXXX)" + _pager_fifo="$_pager_fifo_dir/0" + mkfifo -m 600 "$_pager_fifo" + + "$TG_PAGER" < "$_pager_fifo" & + exec > "$_pager_fifo" # dup2(pager_fifo.in, 1) + + # this is needed so e.g. `git diff` will still colorize it's output if + # requested in ~/.gitconfig with color.diff=auto + export GIT_PAGER_IN_USE=1 + + # atexit(close(1); wait pager) + trap "exec >&-; rm \"$_pager_fifo\"; rmdir \"$_pager_fifo_dir\"; wait" EXIT +} + +## Startup + +[ -d "@cmddir@" ] || + die "No command directory: '@cmddir@'" ## Initial setup set -e git_dir="$(git rev-parse --git-dir)" root_dir="$(git rev-parse --show-cdup)"; root_dir="${root_dir:-.}" +# Make sure root_dir doesn't end with a trailing slash. +root_dir="${root_dir%/}" base_remote="$(git config topgit.remote 2>/dev/null)" || : +tg="tg" # make sure merging the .top* files will always behave sanely setup_ours setup_hook "pre-commit" -[ -d "@cmddir@" ] || - die "No command directory: '@cmddir@'" - ## Dispatch # We were sourced from another script for our utility functions; -# this is set by hooks. -[ -z "$tg__include" ] || return 0 +# this is set by hooks. Skip the rest of the file. A simple return doesn't +# work as expected in every shell. See http://bugs.debian.org/516188 +if [ -z "$tg__include" ]; then + +if [ "$1" = "-r" ]; then + shift + if [ -z "$1" ]; then + echo "Option -r requires an argument." >&2 + do_help + exit 1 + fi + base_remote="$1"; shift + tg="$tg -r $base_remote" +fi cmd="$1" -[ -n "$cmd" ] || die "He took a duck in the face at two hundred and fifty knots" +[ -n "$cmd" ] || { do_help; exit 1; } shift case "$cmd" in help|--help|-h) do_help "$1" - exit 1;; + exit 0;; --hooks-path) # Internal command echo "@hooksdir@";; *) [ -r "@cmddir@"/tg-$cmd ] || { echo "Unknown subcommand: $cmd" >&2 + do_help exit 1 } . "@cmddir@"/tg-$cmd;; esac + +fi + +# vim:noet