@@ -94,6 +94,70 @@ __git ()
94
94
${__git_dir: +--git-dir=" $__git_dir " } " $@ " 2> /dev/null
95
95
}
96
96
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
+
97
161
# The following function is based on code from:
98
162
#
99
163
# bash_completion - programmable completion functions for bash 3.2+
@@ -346,6 +410,24 @@ __gitcomp_nl ()
346
410
__gitcomp_nl_append " $@ "
347
411
}
348
412
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
+
349
431
# Generates completion reply with compgen from newline-separated possible
350
432
# completion filenames.
351
433
# It accepts 1 to 3 arguments:
@@ -365,7 +447,8 @@ __gitcomp_file ()
365
447
366
448
# use a hack to enable file mode in bash < 4
367
449
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
369
452
}
370
453
371
454
# Execute 'git ls-files', unless the --committable option is specified, in
@@ -375,10 +458,12 @@ __gitcomp_file ()
375
458
__git_ls_files_helper ()
376
459
{
377
460
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// \\ / \\\\ } *"
379
463
else
380
464
# 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// \\ / \\\\ } *"
382
467
fi
383
468
}
384
469
@@ -389,12 +474,103 @@ __git_ls_files_helper ()
389
474
# If provided, only files within the specified directory are listed.
390
475
# Sub directories are never recursed. Path must have a trailing
391
476
# slash.
477
+ # 3: List only paths matching this path component (optional).
392
478
__git_index_files ()
393
479
{
394
- local root=" ${2-.} " file
480
+ local root=" $2 " match= " $3 "
395
481
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_ " ) "
398
574
}
399
575
400
576
# Lists branches from the local repository.
@@ -713,26 +889,6 @@ __git_complete_revlist_file ()
713
889
esac
714
890
}
715
891
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
-
736
892
__git_complete_file ()
737
893
{
738
894
__git_complete_revlist_file
@@ -3232,6 +3388,15 @@ if [[ -n ${ZSH_VERSION-} ]]; then
3232
3388
compadd -Q -S " ${4- } " -p " ${2-} " -- ${=1} && _ret=0
3233
3389
}
3234
3390
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
+
3235
3400
__gitcomp_file ()
3236
3401
{
3237
3402
emulate -L zsh
0 commit comments