completion: respect 'git -C <path>'

'git -C <path>' option(s) on the command line should be taken into account during completion, because - like '--git-dir=<path>', it can lead us to a different repository, - a few git commands executed in the completion script do care about in which directory they are executed, and - the command for which we are providing completion might care about in which directory it will be executed. However, unlike '--git-dir=<path>', the '-C <path>' option can be specified multiple times and their effect is cumulative, so we can't just store a single '<path>' in a variable. Nor can we simply concatenate a path from '-C <path1> -C <path2> ...', because e.g. (in an arguably pathological corner case) a relative path might be followed by an absolute path. Instead, store all '-C <path>' options word by word in the $__git_C_args array in the main git completion function, and pass this array, if present, to 'git rev-parse --absolute-git-dir' when discovering the repository in __gitdir(), and let it take care of multiple options, relative paths, absolute paths and everything. Also pass all '-C <path> options via the $__git_C_args array to those git executions which require a worktree and for which it matters from which directory they are executed from. There are only three such cases: - 'git diff-index' and 'git ls-files' in __git_ls_files_helper() used for git-aware filename completion, and - the 'git ls-tree' used for completing the 'ref:path' notation. The other git commands executed in the completion script don't need these '-C <path>' options, because __gitdir() already took those options into account. It would not hurt them, either, but let's not induce unnecessary code churn. Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

SZEDER Gábor committed Feb 3, 2017 at 03:48 UTC 80ac0744b180f815aca190059705c0aed80d16f9
2 files changed +101 -5
contrib/completion/git-completion.bash
+14 -5
@@ -39,7 +39,11 @@ esac
39 __gitdir ()
40 {
41 if [ -z "${1-}" ]; then
42 - if [ -n "${__git_dir-}" ]; then
42 + if [ -n "${__git_C_args-}" ]; then
43 + git "${__git_C_args[@]}" \
44 + ${__git_dir:+--git-dir="$__git_dir"} \
45 + rev-parse --absolute-git-dir 2>/dev/null
46 + elif [ -n "${__git_dir-}" ]; then
47 test -d "$__git_dir" || return 1
48 echo "$__git_dir"
49 elif [ -n "${GIT_DIR-}" ]; then
@@ -286,10 +290,10 @@ __git_ls_files_helper ()
290 local dir="$(__gitdir)"
291
292 if [ "$2" == "--committable" ]; then
289 - git --git-dir="$dir" -C "$1" diff-index --name-only --relative HEAD
293 + git ${__git_C_args:+"${__git_C_args[@]}"} --git-dir="$dir" -C "$1" diff-index --name-only --relative HEAD
294 else
295 # NOTE: $2 is not quoted in order to support multiple options
292 - git --git-dir="$dir" -C "$1" ls-files --exclude-standard $2
296 + git ${__git_C_args:+"${__git_C_args[@]}"} --git-dir="$dir" -C "$1" ls-files --exclude-standard $2
297 fi 2>/dev/null
298 }
299
@@ -519,7 +523,7 @@ __git_complete_revlist_file ()
523 *) pfx="$ref:$pfx" ;;
524 esac
525
522 - __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" 2>/dev/null \
526 + __gitcomp_nl "$(git ${__git_C_args:+"${__git_C_args[@]}"} --git-dir="$(__gitdir)" ls-tree "$ls" 2>/dev/null \
527 | sed '/^100... blob /{
528 s,^.* ,,
529 s,$, ,
@@ -2792,6 +2796,7 @@ _git_worktree ()
2796 __git_main ()
2797 {
2798 local i c=1 command __git_dir
2799 + local __git_C_args C_args_count=0
2800
2801 while [ $c -lt $cword ]; do
2802 i="${words[c]}"
@@ -2800,7 +2805,11 @@ __git_main ()
2805 --git-dir) ((c++)) ; __git_dir="${words[c]}" ;;
2806 --bare) __git_dir="." ;;
2807 --help) command="help"; break ;;
2803 - -c|-C|--work-tree|--namespace) ((c++)) ;;
2808 + -c|--work-tree|--namespace) ((c++)) ;;
2809 + -C) __git_C_args[C_args_count++]=-C
2810 + ((c++))
2811 + __git_C_args[C_args_count++]="${words[c]}"
2812 + ;;
2813 -*) ;;
2814 *) command="$i"; break ;;
2815 esac
t/t9902-completion.sh
+87
@@ -211,6 +211,87 @@ test_expect_success '__gitdir - $GIT_DIR set while .git directory in parent' '
211 test_cmp expected "$actual"
212 '
213
214 +test_expect_success '__gitdir - from command line while "git -C"' '
215 + echo "$ROOT/.git" >expected &&
216 + (
217 + __git_dir="$ROOT/.git" &&
218 + __git_C_args=(-C otherrepo) &&
219 + __gitdir >"$actual"
220 + ) &&
221 + test_cmp expected "$actual"
222 +'
223 +
224 +test_expect_success '__gitdir - relative dir from command line and "git -C"' '
225 + echo "$ROOT/otherrepo/.git" >expected &&
226 + (
227 + cd subdir &&
228 + __git_dir="otherrepo/.git" &&
229 + __git_C_args=(-C ..) &&
230 + __gitdir >"$actual"
231 + ) &&
232 + test_cmp expected "$actual"
233 +'
234 +
235 +test_expect_success '__gitdir - $GIT_DIR set while "git -C"' '
236 + echo "$ROOT/.git" >expected &&
237 + (
238 + GIT_DIR="$ROOT/.git" &&
239 + export GIT_DIR &&
240 + __git_C_args=(-C otherrepo) &&
241 + __gitdir >"$actual"
242 + ) &&
243 + test_cmp expected "$actual"
244 +'
245 +
246 +test_expect_success '__gitdir - relative dir in $GIT_DIR and "git -C"' '
247 + echo "$ROOT/otherrepo/.git" >expected &&
248 + (
249 + cd subdir &&
250 + GIT_DIR="otherrepo/.git" &&
251 + export GIT_DIR &&
252 + __git_C_args=(-C ..) &&
253 + __gitdir >"$actual"
254 + ) &&
255 + test_cmp expected "$actual"
256 +'
257 +
258 +test_expect_success '__gitdir - "git -C" while .git directory in cwd' '
259 + echo "$ROOT/otherrepo/.git" >expected &&
260 + (
261 + __git_C_args=(-C otherrepo) &&
262 + __gitdir >"$actual"
263 + ) &&
264 + test_cmp expected "$actual"
265 +'
266 +
267 +test_expect_success '__gitdir - "git -C" while cwd is a .git directory' '
268 + echo "$ROOT/otherrepo/.git" >expected &&
269 + (
270 + cd .git &&
271 + __git_C_args=(-C .. -C otherrepo) &&
272 + __gitdir >"$actual"
273 + ) &&
274 + test_cmp expected "$actual"
275 +'
276 +
277 +test_expect_success '__gitdir - "git -C" while .git directory in parent' '
278 + echo "$ROOT/otherrepo/.git" >expected &&
279 + (
280 + cd subdir &&
281 + __git_C_args=(-C .. -C otherrepo) &&
282 + __gitdir >"$actual"
283 + ) &&
284 + test_cmp expected "$actual"
285 +'
286 +
287 +test_expect_success '__gitdir - non-existing path in "git -C"' '
288 + (
289 + __git_C_args=(-C non-existing) &&
290 + test_must_fail __gitdir >"$actual"
291 + ) &&
292 + test_must_be_empty "$actual"
293 +'
294 +
295 test_expect_success '__gitdir - non-existing path in $__git_dir' '
296 (
297 __git_dir="non-existing" &&
@@ -785,6 +866,12 @@ test_expect_success 'checkout completes ref names' '
866 EOF
867 '
868
869 +test_expect_success 'git -C <path> checkout uses the right repo' '
870 + test_completion "git -C subdir -C subsubdir -C .. -C ../otherrepo checkout b" <<-\EOF
871 + branch-in-other Z
872 + EOF
873 +'
874 +
875 test_expect_success 'show completes all refs' '
876 test_completion "git show m" <<-\EOF
877 master Z