@@ -94,6 +94,70 @@ __git ()
9494 ${__git_dir: +--git-dir=" $__git_dir " } " $@ " 2> /dev/null
9595}
9696
97+ # Removes backslash escaping, single quotes and double quotes from a word,
98+ # stores the result in the variable $dequoted_word.
99+ # 1: The word to dequote.
100+ __git_dequote ()
101+ {
102+ local rest=" $1 " len ch
103+
104+ dequoted_word=" "
105+
106+ while test -n " $rest " ; do
107+ len=${# dequoted_word}
108+ dequoted_word=" $dequoted_word ${rest%% [\\\'\"]* } "
109+ rest=" ${rest: $((${# dequoted_word} -$len ))} "
110+
111+ case " ${rest: 0: 1} " in
112+ \\ )
113+ ch=" ${rest: 1: 1} "
114+ case " $ch " in
115+ $' \n ' )
116+ ;;
117+ * )
118+ dequoted_word=" $dequoted_word$ch "
119+ ;;
120+ esac
121+ rest=" ${rest: 2} "
122+ ;;
123+ \' )
124+ rest=" ${rest: 1} "
125+ len=${# dequoted_word}
126+ dequoted_word=" $dequoted_word ${rest%% \' * } "
127+ rest=" ${rest: $((${# dequoted_word} -$len +1))} "
128+ ;;
129+ \" )
130+ rest=" ${rest: 1} "
131+ while test -n " $rest " ; do
132+ len=${# dequoted_word}
133+ dequoted_word=" $dequoted_word ${rest%% [\\\"]* } "
134+ rest=" ${rest: $((${# dequoted_word} -$len ))} "
135+ case " ${rest: 0: 1} " in
136+ \\ )
137+ ch=" ${rest: 1: 1} "
138+ case " $ch " in
139+ \" |\\ |\$ |\` )
140+ dequoted_word=" $dequoted_word$ch "
141+ ;;
142+ $' \n ' )
143+ ;;
144+ * )
145+ dequoted_word=" $dequoted_word \\ $ch "
146+ ;;
147+ esac
148+ rest=" ${rest: 2} "
149+ ;;
150+ \" )
151+ rest=" ${rest: 1} "
152+ break
153+ ;;
154+ esac
155+ done
156+ ;;
157+ esac
158+ done
159+ }
160+
97161# The following function is based on code from:
98162#
99163# bash_completion - programmable completion functions for bash 3.2+
@@ -346,6 +410,24 @@ __gitcomp_nl ()
346410 __gitcomp_nl_append " $@ "
347411}
348412
413+ # Fills the COMPREPLY array with prefiltered paths without any additional
414+ # processing.
415+ # Callers must take care of providing only paths that match the current path
416+ # to be completed and adding any prefix path components, if necessary.
417+ # 1: List of newline-separated matching paths, complete with all prefix
418+ # path componens.
419+ __gitcomp_file_direct ()
420+ {
421+ local IFS=$' \n '
422+
423+ COMPREPLY=($1 )
424+
425+ # use a hack to enable file mode in bash < 4
426+ compopt -o filenames +o nospace 2> /dev/null ||
427+ compgen -f /non-existing-dir/ > /dev/null ||
428+ true
429+ }
430+
349431# Generates completion reply with compgen from newline-separated possible
350432# completion filenames.
351433# It accepts 1 to 3 arguments:
@@ -365,7 +447,8 @@ __gitcomp_file ()
365447
366448 # use a hack to enable file mode in bash < 4
367449 compopt -o filenames +o nospace 2> /dev/null ||
368- compgen -f /non-existing-dir/ > /dev/null
450+ compgen -f /non-existing-dir/ > /dev/null ||
451+ true
369452}
370453
371454# Execute 'git ls-files', unless the --committable option is specified, in
@@ -375,10 +458,12 @@ __gitcomp_file ()
375458__git_ls_files_helper ()
376459{
377460 if [ " $2 " == " --committable" ]; then
378- __git -C " $1 " diff-index --name-only --relative HEAD
461+ __git -C " $1 " -c core.quotePath=false diff-index \
462+ --name-only --relative HEAD -- " ${3// \\ / \\\\ } *"
379463 else
380464 # NOTE: $2 is not quoted in order to support multiple options
381- __git -C " $1 " ls-files --exclude-standard $2
465+ __git -C " $1 " -c core.quotePath=false ls-files \
466+ --exclude-standard $2 -- " ${3// \\ / \\\\ } *"
382467 fi
383468}
384469
@@ -389,12 +474,103 @@ __git_ls_files_helper ()
389474# If provided, only files within the specified directory are listed.
390475# Sub directories are never recursed. Path must have a trailing
391476# slash.
477+ # 3: List only paths matching this path component (optional).
392478__git_index_files ()
393479{
394- local root=" ${2-.} " file
480+ local root=" $2 " match= " $3 "
395481
396- __git_ls_files_helper " $root " " $1 " |
397- cut -f1 -d/ | sort | uniq
482+ __git_ls_files_helper " $root " " $1 " " $match " |
483+ awk -F / -v pfx=" ${2// \\ / \\\\ } " ' {
484+ paths[$1] = 1
485+ }
486+ END {
487+ for (p in paths) {
488+ if (substr(p, 1, 1) != "\"") {
489+ # No special characters, easy!
490+ print pfx p
491+ continue
492+ }
493+
494+ # The path is quoted.
495+ p = dequote(p)
496+ if (p == "")
497+ continue
498+
499+ # Even when a directory name itself does not contain
500+ # any special characters, it will still be quoted if
501+ # any of its (stripped) trailing path components do.
502+ # Because of this we may have seen the same direcory
503+ # both quoted and unquoted.
504+ if (p in paths)
505+ # We have seen the same directory unquoted,
506+ # skip it.
507+ continue
508+ else
509+ print pfx p
510+ }
511+ }
512+ function dequote(p, bs_idx, out, esc, esc_idx, dec) {
513+ # Skip opening double quote.
514+ p = substr(p, 2)
515+
516+ # Interpret backslash escape sequences.
517+ while ((bs_idx = index(p, "\\")) != 0) {
518+ out = out substr(p, 1, bs_idx - 1)
519+ esc = substr(p, bs_idx + 1, 1)
520+ p = substr(p, bs_idx + 2)
521+
522+ if ((esc_idx = index("abtvfr\"\\", esc)) != 0) {
523+ # C-style one-character escape sequence.
524+ out = out substr("\a\b\t\v\f\r\"\\",
525+ esc_idx, 1)
526+ } else if (esc == "n") {
527+ # Uh-oh, a newline character.
528+ # We cant reliably put a pathname
529+ # containing a newline into COMPREPLY,
530+ # and the newline would create a mess.
531+ # Skip this path.
532+ return ""
533+ } else {
534+ # Must be a \nnn octal value, then.
535+ dec = esc * 64 + \
536+ substr(p, 1, 1) * 8 + \
537+ substr(p, 2, 1)
538+ out = out sprintf("%c", dec)
539+ p = substr(p, 3)
540+ }
541+ }
542+ # Drop closing double quote, if there is one.
543+ # (There isnt any if this is a directory, as it was
544+ # already stripped with the trailing path components.)
545+ if (substr(p, length(p), 1) == "\"")
546+ out = out substr(p, 1, length(p) - 1)
547+ else
548+ out = out p
549+
550+ return out
551+ }'
552+ }
553+
554+ # __git_complete_index_file requires 1 argument:
555+ # 1: the options to pass to ls-file
556+ #
557+ # The exception is --committable, which finds the files appropriate commit.
558+ __git_complete_index_file ()
559+ {
560+ local dequoted_word pfx=" " cur_
561+
562+ __git_dequote " $cur "
563+
564+ case " $dequoted_word " in
565+ ?* /* )
566+ pfx=" ${dequoted_word%/* } /"
567+ cur_=" ${dequoted_word##*/ } "
568+ ;;
569+ * )
570+ cur_=" $dequoted_word "
571+ esac
572+
573+ __gitcomp_file_direct " $( __git_index_files " $1 " " $pfx " " $cur_ " ) "
398574}
399575
400576# Lists branches from the local repository.
@@ -713,26 +889,6 @@ __git_complete_revlist_file ()
713889 esac
714890}
715891
716-
717- # __git_complete_index_file requires 1 argument:
718- # 1: the options to pass to ls-file
719- #
720- # The exception is --committable, which finds the files appropriate commit.
721- __git_complete_index_file ()
722- {
723- local pfx=" " cur_=" $cur "
724-
725- case " $cur_ " in
726- ?* /* )
727- pfx=" ${cur_%/* } "
728- cur_=" ${cur_##*/ } "
729- pfx=" ${pfx} /"
730- ;;
731- esac
732-
733- __gitcomp_file " $( __git_index_files " $1 " ${pfx: +" $pfx " } ) " " $pfx " " $cur_ "
734- }
735-
736892__git_complete_file ()
737893{
738894 __git_complete_revlist_file
@@ -3232,6 +3388,15 @@ if [[ -n ${ZSH_VERSION-} ]]; then
32323388 compadd -Q -S " ${4- } " -p " ${2-} " -- ${=1} && _ret=0
32333389 }
32343390
3391+ __gitcomp_file_direct ()
3392+ {
3393+ emulate -L zsh
3394+
3395+ local IFS=$' \n '
3396+ compset -P ' *[=:]'
3397+ compadd -Q -f -- ${=1} && _ret=0
3398+ }
3399+
32353400 __gitcomp_file ()
32363401 {
32373402 emulate -L zsh
0 commit comments