@@ -834,6 +834,7 @@ __mj_ssh_write()
834834# sentinel is encountered.
835835__mj_ssh_read ()
836836{
837+ local IFS=$' \n '
837838 while read -r line < & 218; do
838839 if [[ $line == " ${__ssh_msg_sentinel} " ]]; then
839840 return
@@ -879,60 +880,79 @@ _ssh()
879880 __mj_ssh_read || { _dbg " read failed" ; __mj_ssh_stop; return 1; }
880881}
881882
883+ _new_test ()
884+ {
885+ local userhost=${1%%:* }
886+ local path=${1#*: }
882887
883- # things we want to backslash escape in scp paths
884- _scp_path_esc= ' [][(){}<>",:;^&!$=?`|\\ ' " ' " ' [:space:]] '
888+ local dir pfix
889+ [[ " $path " == * / ]] && { dir= " $path " ; pfix= ; } || { dir= $( dirname $path ) ; pfix= $( basename $path ) ; }
885890
886- # Complete remote files with ssh. If the first arg is -d, complete on dirs
887- # only. Returns paths escaped with three backslashes.
888- _ssh_list_files ()
889- {
891+ local cmd;
892+ read -r -d ' ' cmd << -EOF
893+ cd $dir && (
894+ ls -aF1dL $pfix */HEAD | sed -n 's|/HEAD[^/]*$||p';
895+ ls -aF1dL $pfix */.git | sed -n 's|/.git/$||p';
896+ ls -aF1dL $pfix * | sed -n 's|/$||p'
897+ ) 2>/dev/null | sort | uniq -c | sed -nE 's| *2 (.*)|\1 |p; s| *1 (.*)|\1/|p'
898+ EOF
899+
900+ echo " $cmd "
901+
902+ # grab all
890903 local IFS=$' \n '
904+ [[ $dir == " ." ]] && dir=
905+ echo " dir=$dir "
906+ local res=$( _ssh " $userhost " " $cmd " | sed ' s|^|' " $dir " ' |' )
907+ echo " $res "
908+ }
891909
892- # should we only return dirs?
893- local dirs_only
894- [[ $1 == -d ]] && { dirs_only=1; shift ; }
910+ # Find completions for <fragment> on <host>, return them in ${COMPREPLY[@]}
911+ #
912+ # _ssh_list_repos <url>:<fragment>
913+ #
914+ # Once the initial SSH connection is established, this is typically fast
915+ # (on order of 50msec, depending on the speed of your server.)
916+ #
917+ _ssh_list_repos ()
918+ {
919+ local IFS=$' \n '
895920
896921 # split url
897922 local userhost=${1%%:* }
898923 local path=${1#*: }
899- local sockdir=" ${2:- $HOME / .ssh} "
900-
901- # _dbg "cur=[$1] userhost=[$userhost] path=[$path] sockdir=[$sockdir]"
902924
903925 # prime the cached connection (as all subsequent invocations will be
904926 # in subshells and can't set the various __ssh_* variables with
905927 # connection reuse info)
906928 _ssh_ensure_started " $userhost " & > /dev/null
907929
908- local files
909- if [[ $dirs_only == 1 ]]; then
910- # escape problematic characters; remove non-dirs
911- files=$( _ssh " $userhost " \
912- command ls -aF1dL " $path *" 2> /dev/null | \
913- command sed -e ' s/' $_scp_path_esc ' /\\&/g' -e ' /[^\/]$/d' )
914-
915- # if only one dir remains, and it has no subdirs, append a space
916- # rather than a slash
917- local f=( $files )
918- if [[ ${# f[@]} == 1 ]]; then
919- # _dbg "checking subdir"
920- if [[ -z $( _ssh " $userhost " command ls -aF1dL " $f *" 2> /dev/null | sed -e ' /[^\/]$/d' ) ]]; then
921- files=" ${f%/ } "
922- # _dbg "end of hierarchy: files=[[$files]]"
923- fi
924- fi
925- else
926- # escape problematic characters; remove executables, aliases, pipes
927- # and sockets; add space at end of file names
928- files=$( _ssh " $userhost " \
929- command ls -aF1dL " $path *" 2> /dev/null | \
930- command sed -e ' s/' $_scp_path_esc ' /\\&/g' -e ' s/[*@|=]$//g' \
931- -e ' s/[^\/]$/& /g' )
932- fi
930+ # here we construct a list of directories containing git repositories
931+ # in addition to the list of _all_ directories. The algorithm:
932+ #
933+ # 1. list directories with HEAD (bare git dirs) and .git/ (workdirs)
934+ # 2. list all directories
935+ # 3. merge the above lists, sort them, and run unique -c which prints
936+ # out the number of times an entry has appeared. the directories
937+ # which are git dirs will have appeared _twice_ (once because they
938+ # were picked up by #1 above, once because of #2.
939+ # 4. for each dir that appeared twice, append a ' ' to the end signaling
940+ # the end of completion. otherwise, append a '/'.
941+ local cmd
942+ read -r -d ' ' cmd << -EOF
943+ (
944+ ls -aF1dL $path */HEAD | sed -n 's|/HEAD[^/]*$||p';
945+ ls -aF1dL $path */.git | sed -n 's|/.git/$||p';
946+ ls -aF1dL $path * | sed -n 's|/$||p'
947+ ) 2>/dev/null | sort | uniq -c | sed -nE 's| *2 (.*)|\1 |p; s| *1 (.*)|\1/|p'
948+ EOF
949+ # echo "$cmd"
950+
951+ # grab all
952+ local files=$( _ssh " $userhost " " $cmd " )
933953
934954 COMPREPLY=($files )
935- # echo "$files"
955+ # echo "=== $files=== "
936956}
937957
938958# test if we're completing a generic SSH URL, complete it if so, return 1
@@ -963,7 +983,7 @@ _complete_ssh_url()
963983
964984 # autocomplete the path
965985 start_spinner
966- _ssh_list_files -d " $cur "
986+ _ssh_list_repos " $cur "
967987 stop_spinner
968988
969989 # remember last few successful completions to offer to
0 commit comments