{"thread":{"id":"40666","subject":"[PATCH 0/3] bash-completion fixes for global git options handling","startedAt":"2015-10-28T17:21:22Z","lastAt":"2015-10-28T17:21:25Z","messageCount":4,"participants":["Peter Wu"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"272423","messageId":"1446052885-11173-1-git-send-email-peter@lekensteyn.nl","threadId":"40666","inReplyTo":null,"subject":"[PATCH 0/3] bash-completion fixes for global git options handling","fromName":"Peter Wu","fromEmail":"peter@lekensteyn.nl","sentAt":"2015-10-28T17:21:22Z","receivedAt":"2015-10-28T17:21:22Z","isPatch":true,"sender":{"key":"peter@lekensteyn.nl","avatar":"https://gravatar.com/avatar/efcf30db53f30b15e7c905ef37bd4dc44d87573bd92baf97cd707115d3fcd0c0?d=mp&s=160"},"body":"Hi,\n\nThese patches improve bash-completion when global git options are present.\nConsider this problem:\n\n    bash-4.3$ git -C ../linux sta<TAB>error: invalid key: alias.../linux\n\nThis happens because the current script is unaware of the -C option. In general,\nglobal options are not handled well (patch 001 fixes this).\n\nPatch 2 makes the completions more aware of the --git-dir option.\n\nPatch 3 builds on previous patches and makes completions aware of the -C option.\n\nKind regards,\nPeter\n\nPeter Wu (3):\n  completion: ignore git options for subcommand completion\n  completion: pass --git-dir to more commands\n  completion: handle git -C option\n\n contrib/completion/git-completion.bash | 119 ++++++++++++++++++++-------------\n 1 file changed, 72 insertions(+), 47 deletions(-)\n\n-- \n2.6.1\n"},{"id":"272426","messageId":"1446052885-11173-2-git-send-email-peter@lekensteyn.nl","threadId":"40666","inReplyTo":"1446052885-11173-1-git-send-email-peter@lekensteyn.nl","subject":"[PATCH 1/3] completion: ignore git options for subcommand completion","fromName":"Peter Wu","fromEmail":"peter@lekensteyn.nl","sentAt":"2015-10-28T17:21:23Z","receivedAt":"2015-10-28T17:21:23Z","isPatch":true,"sender":{"key":"peter@lekensteyn.nl","avatar":"https://gravatar.com/avatar/efcf30db53f30b15e7c905ef37bd4dc44d87573bd92baf97cd707115d3fcd0c0?d=mp&s=160"},"body":"Do not assume that the first option after \"git\" is a subcommand. This\nfixes completion such as:\n\n    # do not detect \"push\" as remote name\n    git --git-dir=git/.git push origin <TAB>\n    # do not overwrite \"--git-dir=...\" with the alias expansion\n    git --git-dir=git/.git gerrit-diff <TAB>\n\nSigned-off-by: Peter Wu <peter@lekensteyn.nl>\n---\n contrib/completion/git-completion.bash | 26 +++++++++++++-------------\n 1 file changed, 13 insertions(+), 13 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 482ca84..bd9ef4c 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -531,8 +531,8 @@ __git_complete_revlist ()\n \n __git_complete_remote_or_refspec ()\n {\n-\tlocal cur_=\"$cur\" cmd=\"${words[1]}\"\n-\tlocal i c=2 remote=\"\" pfx=\"\" lhs=1 no_complete_refspec=0\n+\tlocal cur_=\"$cur\" cmd=\"${words[command_word_index]}\"\n+\tlocal i c=$((command_word_index+1)) remote=\"\" pfx=\"\" lhs=1 no_complete_refspec=0\n \tif [ \"$cmd\" = \"remote\" ]; then\n \t\t((c++))\n \tfi\n@@ -788,7 +788,7 @@ __git_aliased_command ()\n # __git_find_on_cmdline requires 1 argument\n __git_find_on_cmdline ()\n {\n-\tlocal word subcommand c=1\n+\tlocal word subcommand c=$command_word_index\n \twhile [ $c -lt $cword ]; do\n \t\tword=\"${words[c]}\"\n \t\tfor subcommand in $1; do\n@@ -803,7 +803,7 @@ __git_find_on_cmdline ()\n \n __git_has_doubledash ()\n {\n-\tlocal c=1\n+\tlocal c=$command_word_index\n \twhile [ $c -lt $cword ]; do\n \t\tif [ \"--\" = \"${words[c]}\" ]; then\n \t\t\treturn 0\n@@ -826,8 +826,8 @@ __git_count_arguments ()\n {\n \tlocal word i c=0\n \n-\t# Skip \"git\" (first argument)\n-\tfor ((i=1; i < ${#words[@]}; i++)); do\n+\t# Skip \"git\" (first argument) and any options such as --git-dir.\n+\tfor ((i=command_word_index; i < ${#words[@]}; i++)); do\n \t\tword=\"${words[i]}\"\n \n \t\tcase \"$word\" in\n@@ -957,7 +957,7 @@ _git_bisect ()\n \n _git_branch ()\n {\n-\tlocal i c=1 only_local_ref=\"n\" has_r=\"n\"\n+\tlocal i c=$command_word_index only_local_ref=\"n\" has_r=\"n\"\n \n \twhile [ $c -lt $cword ]; do\n \t\ti=\"${words[c]}\"\n@@ -992,7 +992,7 @@ _git_branch ()\n \n _git_bundle ()\n {\n-\tlocal cmd=\"${words[2]}\"\n+\tlocal cmd=\"${words[command_word_index+1]}\"\n \tcase \"$cword\" in\n \t2)\n \t\t__gitcomp \"create list-heads verify unbundle\"\n@@ -1760,7 +1760,7 @@ _git_stage ()\n __git_config_get_set_variables ()\n {\n \tlocal prevword word config_file= c=$cword\n-\twhile [ $c -gt 1 ]; do\n+\twhile [ $c -gt $command_word_index ]; do\n \t\tword=\"${words[c]}\"\n \t\tcase \"$word\" in\n \t\t--system|--global|--local|--file=*)\n@@ -2516,7 +2516,7 @@ _git_svn ()\n \n _git_tag ()\n {\n-\tlocal i c=1 f=0\n+\tlocal i c=$command_word_index f=0\n \twhile [ $c -lt $cword ]; do\n \t\ti=\"${words[c]}\"\n \t\tcase \"$i\" in\n@@ -2562,7 +2562,7 @@ _git_whatchanged ()\n \n __git_main ()\n {\n-\tlocal i c=1 command __git_dir\n+\tlocal i c=1 command command_word_index __git_dir\n \n \twhile [ $c -lt $cword ]; do\n \t\ti=\"${words[c]}\"\n@@ -2573,7 +2573,7 @@ __git_main ()\n \t\t--help) command=\"help\"; break ;;\n \t\t-c|--work-tree|--namespace) ((c++)) ;;\n \t\t-*) ;;\n-\t\t*) command=\"$i\"; break ;;\n+\t\t*) command=\"$i\"; command_word_index=$c; break ;;\n \t\tesac\n \t\t((c++))\n \tdone\n@@ -2608,7 +2608,7 @@ __git_main ()\n \n \tlocal expansion=$(__git_aliased_command \"$command\")\n \tif [ -n \"$expansion\" ]; then\n-\t\twords[1]=$expansion\n+\t\twords[command_word_index]=$expansion\n \t\tcompletion_func=\"_git_${expansion//-/_}\"\n \t\tdeclare -f $completion_func >/dev/null && $completion_func\n \tfi\n-- \n2.6.1\n"},{"id":"272425","messageId":"1446052885-11173-3-git-send-email-peter@lekensteyn.nl","threadId":"40666","inReplyTo":"1446052885-11173-1-git-send-email-peter@lekensteyn.nl","subject":"[PATCH 2/3] completion: pass --git-dir to more commands","fromName":"Peter Wu","fromEmail":"peter@lekensteyn.nl","sentAt":"2015-10-28T17:21:24Z","receivedAt":"2015-10-28T17:21:24Z","isPatch":true,"sender":{"key":"peter@lekensteyn.nl","avatar":"https://gravatar.com/avatar/efcf30db53f30b15e7c905ef37bd4dc44d87573bd92baf97cd707115d3fcd0c0?d=mp&s=160"},"body":"The --git-dir option influences more commands, but was not applied\nduring completions. For example:\n\n    # previously empty because --git-dir was not passed to ls-remote\n    git --git-dir=git/.git config merge.o<TAB>\n\nAdd --git-dir to more git commands (but not for repo-independent\ncommands such as git help) and add a new internal \"__git_options\"\narray to store this option. In future patches, the -C option will also\nbe added.  (Alternatively, a new wrapper function can be added instead\nof duplicating `${__git_options[@]}` all over the place, but let's keep\nit simple for now.)\n\nAdd a variable and comments to __git_refs for clarity. (Note that\n`--git-dir` needs to be kept there because it may not be the same as\nthe current repo, e.g. via `git fetch /tmp/repo <TAB>`.)\n\nSigned-off-by: Peter Wu <peter@lekensteyn.nl>\n---\n contrib/completion/git-completion.bash | 52 ++++++++++++++++++++--------------\n 1 file changed, 30 insertions(+), 22 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex bd9ef4c..fdf0f16 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -282,10 +282,10 @@ __gitcomp_file ()\n __git_ls_files_helper ()\n {\n \tif [ \"$2\" == \"--committable\" ]; then\n-\t\tgit -C \"$1\" diff-index --name-only --relative HEAD\n+\t\tgit \"${__git_options[@]}\" -C \"$1\" diff-index --name-only --relative HEAD\n \telse\n \t\t# NOTE: $2 is not quoted in order to support multiple options\n-\t\tgit -C \"$1\" ls-files --exclude-standard $2\n+\t\tgit \"${__git_options[@]}\" -C \"$1\" ls-files --exclude-standard $2\n \tfi 2>/dev/null\n }\n \n@@ -315,7 +315,7 @@ __git_heads ()\n {\n \tlocal dir=\"$(__gitdir)\"\n \tif [ -d \"$dir\" ]; then\n-\t\tgit --git-dir=\"$dir\" for-each-ref --format='%(refname:short)' \\\n+\t\tgit \"${__git_options[@]}\" for-each-ref --format='%(refname:short)' \\\n \t\t\trefs/heads\n \t\treturn\n \tfi\n@@ -325,7 +325,7 @@ __git_tags ()\n {\n \tlocal dir=\"$(__gitdir)\"\n \tif [ -d \"$dir\" ]; then\n-\t\tgit --git-dir=\"$dir\" for-each-ref --format='%(refname:short)' \\\n+\t\tgit \"${__git_options[@]}\" for-each-ref --format='%(refname:short)' \\\n \t\t\trefs/tags\n \t\treturn\n \tfi\n@@ -336,8 +336,9 @@ __git_tags ()\n # by checkout for tracking branches\n __git_refs ()\n {\n-\tlocal i hash dir=\"$(__gitdir \"${1-}\")\" track=\"${2-}\"\n+\tlocal i hash dir=\"$(__gitdir \"${1-}\")\" track=\"${2-}\" repo\n \tlocal format refs\n+\t# Try refs from a local repository directory (e.g. \"../linux\")\n \tif [ -d \"$dir\" ]; then\n \t\tcase \"$cur\" in\n \t\trefs|refs/*)\n@@ -353,14 +354,15 @@ __git_refs ()\n \t\t\trefs=\"refs/tags refs/heads refs/remotes\"\n \t\t\t;;\n \t\tesac\n-\t\tgit --git-dir=\"$dir\" for-each-ref --format=\"%($format)\" \\\n-\t\t\t$refs\n+\t\tgit \"${__git_options[@]}\" --git-dir=\"$dir\" \\\n+\t\t\tfor-each-ref --format=\"%($format)\" $refs\n \t\tif [ -n \"$track\" ]; then\n \t\t\t# employ the heuristic used by git checkout\n \t\t\t# Try to find a remote branch that matches the completion word\n \t\t\t# but only output if the branch name is unique\n \t\t\tlocal ref entry\n-\t\t\tgit --git-dir=\"$dir\" for-each-ref --shell --format=\"ref=%(refname:short)\" \\\n+\t\t\tgit \"${__git_options[@]}\" --git-dir=\"$dir\" \\\n+\t\t\t\tfor-each-ref --shell --format=\"ref=%(refname:short)\" \\\n \t\t\t\t\"refs/remotes/\" | \\\n \t\t\twhile read -r entry; do\n \t\t\t\teval \"$entry\"\n@@ -372,9 +374,11 @@ __git_refs ()\n \t\tfi\n \t\treturn\n \tfi\n+\t# Try refs from a remote repository by name (e.g. \"origin\") or a URL\n+\trepo=\"${1-}\"\n \tcase \"$cur\" in\n \trefs|refs/*)\n-\t\tgit ls-remote \"$dir\" \"$cur*\" 2>/dev/null | \\\n+\t\tgit \"${__git_options[@]}\" ls-remote \"$repo\" \"$cur*\" 2>/dev/null | \\\n \t\twhile read -r hash i; do\n \t\t\tcase \"$i\" in\n \t\t\t*^{}) ;;\n@@ -384,8 +388,8 @@ __git_refs ()\n \t\t;;\n \t*)\n \t\techo \"HEAD\"\n-\t\tgit for-each-ref --format=\"%(refname:short)\" -- \\\n-\t\t\t\"refs/remotes/$dir/\" 2>/dev/null | sed -e \"s#^$dir/##\"\n+\t\tgit \"${__git_options[@]}\" for-each-ref --format=\"%(refname:short)\" -- \\\n+\t\t\t\"refs/remotes/$repo/\" 2>/dev/null | sed -e \"s#^$repo/##\"\n \t\t;;\n \tesac\n }\n@@ -403,7 +407,7 @@ __git_refs2 ()\n __git_refs_remotes ()\n {\n \tlocal i hash\n-\tgit ls-remote \"$1\" 'refs/heads/*' 2>/dev/null | \\\n+\tgit \"${__git_options[@]}\" ls-remote \"$1\" 'refs/heads/*' 2>/dev/null | \\\n \twhile read -r hash i; do\n \t\techo \"$i:refs/remotes/$1/${i#refs/heads/}\"\n \tdone\n@@ -413,7 +417,7 @@ __git_remotes ()\n {\n \tlocal d=\"$(__gitdir)\"\n \ttest -d \"$d/remotes\" && ls -1 \"$d/remotes\"\n-\tgit --git-dir=\"$d\" remote\n+\tgit \"${__git_options[@]}\" remote\n }\n \n __git_list_merge_strategies ()\n@@ -467,7 +471,7 @@ __git_complete_revlist_file ()\n \t\t*)   pfx=\"$ref:$pfx\" ;;\n \t\tesac\n \n-\t\t__gitcomp_nl \"$(git --git-dir=\"$(__gitdir)\" ls-tree \"$ls\" 2>/dev/null \\\n+\t\t__gitcomp_nl \"$(git \"${__git_options[@]}\" ls-tree \"$ls\" 2>/dev/null \\\n \t\t\t\t| sed '/^100... blob /{\n \t\t\t\t           s,^.*\t,,\n \t\t\t\t           s,$, ,\n@@ -744,7 +748,7 @@ __git_compute_porcelain_commands ()\n __git_get_config_variables ()\n {\n \tlocal section=\"$1\" i IFS=$'\\n'\n-\tfor i in $(git --git-dir=\"$(__gitdir)\" config --name-only --get-regexp \"^$section\\..*\" 2>/dev/null); do\n+\tfor i in $(git \"${__git_options[@]}\" config --name-only --get-regexp \"^$section\\..*\" 2>/dev/null); do\n \t\techo \"${i#$section.}\"\n \tdone\n }\n@@ -762,7 +766,7 @@ __git_aliases ()\n # __git_aliased_command requires 1 argument\n __git_aliased_command ()\n {\n-\tlocal word cmdline=$(git --git-dir=\"$(__gitdir)\" \\\n+\tlocal word cmdline=$(git \"${__git_options[@]}\" \\\n \t\tconfig --get \"alias.$1\")\n \tfor word in $cmdline; do\n \t\tcase \"$word\" in\n@@ -1133,7 +1137,7 @@ _git_commit ()\n \t\treturn\n \tesac\n \n-\tif git rev-parse --verify --quiet HEAD >/dev/null; then\n+\tif git \"${__git_options[@]}\" rev-parse --verify --quiet HEAD >/dev/null; then\n \t\t__git_complete_index_file \"--committable\"\n \telse\n \t\t# This is the first commit\n@@ -1425,7 +1429,7 @@ _git_log ()\n {\n \t__git_has_doubledash && return\n \n-\tlocal g=\"$(git rev-parse --git-dir 2>/dev/null)\"\n+\tlocal g=\"$(git \"${__git_options[@]}\" rev-parse --git-dir 2>/dev/null)\"\n \tlocal merge=\"\"\n \tif [ -f \"$g/MERGE_HEAD\" ]; then\n \t\tmerge=\"--merge\"\n@@ -1776,7 +1780,7 @@ __git_config_get_set_variables ()\n \t\tc=$((--c))\n \tdone\n \n-\tgit --git-dir=\"$(__gitdir)\" config $config_file --name-only --list 2>/dev/null\n+\tgit \"${__git_options[@]}\" config $config_file --name-only --list 2>/dev/null\n }\n \n _git_config ()\n@@ -1811,7 +1815,7 @@ _git_config ()\n \tremote.*.push)\n \t\tlocal remote=\"${prev#remote.}\"\n \t\tremote=\"${remote%.push}\"\n-\t\t__gitcomp_nl \"$(git --git-dir=\"$(__gitdir)\" \\\n+\t\t__gitcomp_nl \"$(git \"${__git_options[@]}\" \\\n \t\t\tfor-each-ref --format='%(refname):%(refname)' \\\n \t\t\trefs/heads)\"\n \t\treturn\n@@ -2389,7 +2393,7 @@ _git_stash ()\n \t\tshow,--*|drop,--*|branch,--*)\n \t\t\t;;\n \t\tshow,*|apply,*|drop,*|pop,*|branch,*)\n-\t\t\t__gitcomp_nl \"$(git --git-dir=\"$(__gitdir)\" stash list \\\n+\t\t\t__gitcomp_nl \"$(git \"${__git_options[@]}\" stash list \\\n \t\t\t\t\t| sed -n -e 's/:.*//p')\"\n \t\t\t;;\n \t\t*)\n@@ -2562,7 +2566,7 @@ _git_whatchanged ()\n \n __git_main ()\n {\n-\tlocal i c=1 command command_word_index __git_dir\n+\tlocal i c=1 command command_word_index __git_dir __git_options\n \n \twhile [ $c -lt $cword ]; do\n \t\ti=\"${words[c]}\"\n@@ -2578,6 +2582,10 @@ __git_main ()\n \t\t((c++))\n \tdone\n \n+\t__git_options=(\n+\t\t--git-dir=\"$(__gitdir)\"\n+\t)\n+\n \tif [ -z \"$command\" ]; then\n \t\tcase \"$cur\" in\n \t\t--*)   __gitcomp \"\n-- \n2.6.1\n"},{"id":"272424","messageId":"1446052885-11173-4-git-send-email-peter@lekensteyn.nl","threadId":"40666","inReplyTo":"1446052885-11173-1-git-send-email-peter@lekensteyn.nl","subject":"[PATCH 3/3] completion: handle git -C option","fromName":"Peter Wu","fromEmail":"peter@lekensteyn.nl","sentAt":"2015-10-28T17:21:25Z","receivedAt":"2015-10-28T17:21:25Z","isPatch":true,"sender":{"key":"peter@lekensteyn.nl","avatar":"https://gravatar.com/avatar/efcf30db53f30b15e7c905ef37bd4dc44d87573bd92baf97cd707115d3fcd0c0?d=mp&s=160"},"body":"Avoid the \"fatal: bad config file line 5 in config\" message and properly\ncomplete git commands having the \"-C\" option.\n\nBesides the trivial command parsing, __gitdir is rewritten to apply any\ndirectory changes requested via `git -C otherdir ...`.\n\nSigned-off-by: Peter Wu <peter@lekensteyn.nl>\n---\n contrib/completion/git-completion.bash | 45 +++++++++++++++++++++++-----------\n 1 file changed, 31 insertions(+), 14 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex fdf0f16..1646f61 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -34,24 +34,39 @@ case \"$COMP_WORDBREAKS\" in\n esac\n \n # __gitdir accepts 0 or 1 arguments (i.e., location)\n-# returns location of .git repo\n+# outputs location of .git repo if it exists, nothing otherwise.\n __gitdir ()\n {\n-\tif [ -z \"${1-}\" ]; then\n-\t\tif [ -n \"${__git_dir-}\" ]; then\n-\t\t\techo \"$__git_dir\"\n-\t\telif [ -n \"${GIT_DIR-}\" ]; then\n-\t\t\ttest -d \"${GIT_DIR-}\" || return 1\n-\t\t\techo \"$GIT_DIR\"\n-\t\telif [ -d .git ]; then\n-\t\t\techo .git\n+\tlocal gitdir=${1:-}\n+\n+\tif [ -z \"$gitdir\" ]; then\n+\t\t# Try the first matching --git-dir or GIT_DIR, print nothing if these\n+\t\t# directories are invalid.\n+\t\tfor gitdir in \"${__git_dir-}\" \"${GIT_DIR-}\"; do\n+\t\t\t[ -n \"$gitdir\" ] || continue\n+\t\t\tif [[ \"$gitdir\" != /* ]]; then\n+\t\t\t\tgitdir=\"${__git_cd:-.}/$gitdir\"\n+\t\t\tfi\n+\t\t\tif [ -d \"$gitdir\" ]; then\n+\t\t\t\techo \"$gitdir\"\n+\t\t\tfi\n+\t\t\treturn\n+\t\tdone\n+\n+\t\tif [ -d \"${__git_cd:-.}/.git\" ]; then\n+\t\t\techo \"${__git_cd:-.}/.git\"\n \t\telse\n-\t\t\tgit rev-parse --git-dir 2>/dev/null\n+\t\t\tgit -C \"$__git_cd\" rev-parse --git-dir 2>/dev/null\n \t\tfi\n-\telif [ -d \"$1/.git\" ]; then\n-\t\techo \"$1/.git\"\n \telse\n-\t\techo \"$1\"\n+\t\tif [[ \"$gitdir\" != /* ]]; then\n+\t\t\tgitdir=\"${__git_cd:-.}/$gitdir\"\n+\t\tfi\n+\t\tif [ -d \"$gitdir/.git\" ]; then\n+\t\t\techo \"$gitdir/.git\"\n+\t\telif [ -d \"$gitdir\" ]; then\n+\t\t\techo \"$gitdir\"\n+\t\tfi\n \tfi\n }\n \n@@ -2566,11 +2581,12 @@ _git_whatchanged ()\n \n __git_main ()\n {\n-\tlocal i c=1 command command_word_index __git_dir __git_options\n+\tlocal i c=1 command command_word_index __git_dir __git_options __git_cd=.\n \n \twhile [ $c -lt $cword ]; do\n \t\ti=\"${words[c]}\"\n \t\tcase \"$i\" in\n+\t\t-C)          ((c++)) ; __git_cd=\"${words[c]}\" ;;\n \t\t--git-dir=*) __git_dir=\"${i#--git-dir=}\" ;;\n \t\t--git-dir)   ((c++)) ; __git_dir=\"${words[c]}\" ;;\n \t\t--bare)      __git_dir=\".\" ;;\n@@ -2583,6 +2599,7 @@ __git_main ()\n \tdone\n \n \t__git_options=(\n+\t\t-C \"$__git_cd\"\n \t\t--git-dir=\"$(__gitdir)\"\n \t)\n \n-- \n2.6.1\n"}]}