Skip to content

Commit 6debb46

Browse files
committed
Even faster completion over SSH
1 parent f0ed68d commit 6debb46

1 file changed

Lines changed: 59 additions & 39 deletions

File tree

git-clone-completions.bash

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)