{"thread":{"id":"45480","subject":"[PATCHv2 01/14] completion: remove redundant __gitcomp_nl() options from _git_commit()","startedAt":"2017-03-23T15:29:44Z","lastAt":"2017-03-28T15:35:06Z","messageCount":22,"participants":["SZEDER Gábor","Junio C Hamano","Jeff King"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"315081","messageId":"20170323152924.23944-2-szeder.dev@gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"[PATCHv2 01/14] completion: remove redundant __gitcomp_nl() options from _git_commit()","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:11Z","receivedAt":"2017-03-23T15:29:44Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"Those two options are specifying the default values that\n__gitcomp_nl() would use anyway when invoked with no options at all.\n\nSigned-off-by: SZEDER Gábor <szeder.dev@gmail.com>\n---\n contrib/completion/git-completion.bash | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 66d84745c..91fda7ffb 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1216,7 +1216,7 @@ _git_commit ()\n {\n \tcase \"$prev\" in\n \t-c|-C)\n-\t\t__gitcomp_nl \"$(__git_refs)\" \"\" \"${cur}\"\n+\t\t__gitcomp_nl \"$(__git_refs)\"\n \t\treturn\n \t\t;;\n \tesac\n-- \n2.12.1.485.g1616aa492\n\n"},{"id":"315082","messageId":"20170323152924.23944-6-szeder.dev@gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"[PATCHv2 05/14] completion: support excluding full refs","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:15Z","receivedAt":"2017-03-23T15:29:47Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"Commit 49416ad22 (completion: support excluding refs, 2016-08-24) made\npossible to complete short refs with a '^' prefix.\n\nExtend the support to full refs to make completing '^refs/...' work.\n\nSigned-off-by: SZEDER Gábor <szeder.dev@gmail.com>\n---\n contrib/completion/git-completion.bash |  8 ++++----\n t/t9902-completion.sh                  | 31 +++++++++++++++++++++++++++++++\n 2 files changed, 35 insertions(+), 4 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 067dff823..882635f97 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -387,6 +387,10 @@ __git_refs ()\n \tfi\n \n \tif [ \"$list_refs_from\" = path ]; then\n+\t\tif [[ \"$cur_\" == ^* ]]; then\n+\t\t\tpfx=\"^\"\n+\t\t\tcur_=${cur_#^}\n+\t\tfi\n \t\tcase \"$cur_\" in\n \t\trefs|refs/*)\n \t\t\tformat=\"refname\"\n@@ -394,10 +398,6 @@ __git_refs ()\n \t\t\ttrack=\"\"\n \t\t\t;;\n \t\t*)\n-\t\t\tif [[ \"$cur_\" == ^* ]]; then\n-\t\t\t\tpfx=\"^\"\n-\t\t\t\tcur_=${cur_#^}\n-\t\t\tfi\n \t\t\tfor i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do\n \t\t\t\tif [ -e \"$dir/$i\" ]; then echo $pfx$i; fi\n \t\t\tdone\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex f641d99ec..e2b45f625 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -806,6 +806,37 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \ttest_cmp expected \"$actual\"\n '\n \n+test_expect_success '__git refs - exluding refs' '\n+\tcat >expected <<-EOF &&\n+\t^HEAD\n+\t^master\n+\t^matching-branch\n+\t^other/branch-in-other\n+\t^other/master-in-other\n+\t^matching-tag\n+\tEOF\n+\t(\n+\t\tcur=^ &&\n+\t\t__git_refs >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git refs - exluding full refs' '\n+\tcat >expected <<-EOF &&\n+\t^refs/heads/master\n+\t^refs/heads/matching-branch\n+\t^refs/remotes/other/branch-in-other\n+\t^refs/remotes/other/master-in-other\n+\t^refs/tags/matching-tag\n+\tEOF\n+\t(\n+\t\tcur=^refs/ &&\n+\t\t__git_refs >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n test_expect_success '__git_complete_refs - simple' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n-- \n2.12.1.485.g1616aa492\n\n"},{"id":"315083","messageId":"20170323152924.23944-5-szeder.dev@gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"[PATCHv2 04/14] completion: support completing fully qualified non-fast-forward refspecs","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:14Z","receivedAt":"2017-03-23T15:29:48Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"After 'git fetch <remote> <TAB>' our completion script offers refspecs\nthat will fetch to a local branch with the same name as in the remote\nrepository, e.g. 'master:master'.  This also completes\nnon-fast-forward refspecs, i.e. after a '+' prefix like\n'+master:master', and fully qualified refspecs, e.g.\n'refs/heads/master:refs/heads/master'.  However, it does not complete\nnon-fast-forward fully qualified refspecs (or fully qualified refspecs\nfollowing any other prefix, e.g. '--option=', though currently no git\ncommand supports such an option, but third party git commands might).\n\nThese refspecs are listed by the __git_refs2() function, which is just\na thin wrapper iterating over __git_refs()'s output, turning each\nlisted ref into a refspec.  Now, it's certainly possible to modify\n__git_refs2() and its callsite to pass an extra parameter containing\nonly the ref part of the current word to be completed (to follow suit\nof the previous commit) to deal with prefixed fully qualified refspecs\nas well.  Unfortunately, keeping the current behavior unchanged in the\n\"no extra parameter\" case brings in a bit of subtlety, which makes the\nresulting code ugly and compelled me to write a 8-line long comment in\nthe proof of concept.  Not good.  However, since the callsite has to\nbe modified for proper functioning anyway, we might as well leave\n__git_refs2() as is and introduce a new helper function without\nbackwards compatibility concerns.\n\nAdd the new function __git_complete_fetch_refspecs() that has all the\nnecessary parameters to do the right thing in all cases mentioned\nabove, including non-fast-forward fully qualified refspecs.  This new\nfunction can also easier benefit from optimizations coming later in\nthis patch series.\n\nSigned-off-by: SZEDER Gábor <szeder.dev@gmail.com>\n---\n contrib/completion/git-completion.bash | 21 ++++++++++-\n t/t9902-completion.sh                  | 68 ++++++++++++++++++++++++++++++++++\n 2 files changed, 88 insertions(+), 1 deletion(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex b897cba4b..067dff823 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -486,6 +486,7 @@ __git_complete_refs ()\n }\n \n # __git_refs2 requires 1 argument (to pass to __git_refs)\n+# Deprecated: use __git_complete_fetch_refspecs() instead.\n __git_refs2 ()\n {\n \tlocal i\n@@ -494,6 +495,24 @@ __git_refs2 ()\n \tdone\n }\n \n+# Completes refspecs for fetching from a remote repository.\n+# 1: The remote repository.\n+# 2: A prefix to be added to each listed refspec (optional).\n+# 3: The ref to be completed as a refspec instead of the current word to be\n+#    completed (optional)\n+# 4: A suffix to be appended to each listed refspec instead of the default\n+#    space (optional).\n+__git_complete_fetch_refspecs ()\n+{\n+\tlocal i remote=\"$1\" pfx=\"${2-}\" cur_=\"${3-$cur}\" sfx=\"${4- }\"\n+\n+\t__gitcomp_nl \"$(\n+\t\tfor i in $(__git_refs \"$remote\" \"\" \"\" \"$cur_\") ; do\n+\t\t\techo \"$i:$i\"\n+\t\tdone\n+\t\t)\" \"$pfx\" \"$cur_\" \"$sfx\"\n+}\n+\n # __git_refs_remotes requires 1 argument (to pass to ls-remote)\n __git_refs_remotes ()\n {\n@@ -686,7 +705,7 @@ __git_complete_remote_or_refspec ()\n \tcase \"$cmd\" in\n \tfetch)\n \t\tif [ $lhs = 1 ]; then\n-\t\t\t__gitcomp_nl \"$(__git_refs2 \"$remote\")\" \"$pfx\" \"$cur_\"\n+\t\t\t__git_complete_fetch_refspecs \"$remote\" \"$pfx\" \"$cur_\"\n \t\telse\n \t\t\t__git_complete_refs --pfx=\"$pfx\" --cur=\"$cur_\"\n \t\tfi\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 0a41ee1ea..f641d99ec 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -912,6 +912,74 @@ test_expect_success '__git_complete_refs - suffix' '\n \ttest_cmp expected out\n '\n \n+test_expect_success '__git_complete_fetch_refspecs - simple' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tHEAD:HEAD Z\n+\tbranch-in-other:branch-in-other Z\n+\tmaster-in-other:master-in-other Z\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_complete_fetch_refspecs other &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n+test_expect_success '__git_complete_fetch_refspecs - matching' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tbranch-in-other:branch-in-other Z\n+\tEOF\n+\t(\n+\t\tcur=br &&\n+\t\t__git_complete_fetch_refspecs other \"\" br &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n+test_expect_success '__git_complete_fetch_refspecs - prefix' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\t+HEAD:HEAD Z\n+\t+branch-in-other:branch-in-other Z\n+\t+master-in-other:master-in-other Z\n+\tEOF\n+\t(\n+\t\tcur=\"+\" &&\n+\t\t__git_complete_fetch_refspecs other \"+\" \"\"  &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n+test_expect_success '__git_complete_fetch_refspecs - fully qualified' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\trefs/heads/branch-in-other:refs/heads/branch-in-other Z\n+\trefs/heads/master-in-other:refs/heads/master-in-other Z\n+\trefs/tags/tag-in-other:refs/tags/tag-in-other Z\n+\tEOF\n+\t(\n+\t\tcur=refs/ &&\n+\t\t__git_complete_fetch_refspecs other \"\" refs/ &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n+test_expect_success '__git_complete_fetch_refspecs - fully qualified & prefix' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\t+refs/heads/branch-in-other:refs/heads/branch-in-other Z\n+\t+refs/heads/master-in-other:refs/heads/master-in-other Z\n+\t+refs/tags/tag-in-other:refs/tags/tag-in-other Z\n+\tEOF\n+\t(\n+\t\tcur=+refs/ &&\n+\t\t__git_complete_fetch_refspecs other + refs/ &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n test_expect_success 'teardown after ref completion' '\n \tgit branch -d matching-branch &&\n \tgit tag -d matching-tag &&\n-- \n2.12.1.485.g1616aa492\n\n"},{"id":"315084","messageId":"20170323152924.23944-8-szeder.dev@gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"[PATCHv2 07/14] completion: don't disambiguate short refs","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:17Z","receivedAt":"2017-03-23T15:29:51Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"When the completion script lists short refs it does so using the 'git\nfor-each-ref' format 'refname:short', which makes sure that all listed\nrefs are unambiguous.  While disambiguating refs is technically\ncorrect in this case, as opposed to the cases discussed in the\nprevious patch, this disambiguation involves several stat() syscalls\nfor each ref, thus, unfortunately, comes at a steep cost especially on\nWindows and/or when there are a lot of refs to be listed.  A user of\nGit for Windows reported[1] 'git checkout <TAB>' taking ~11 seconds in\na repository with just about 4000 refs.\n\nHowever, it's questionable whether ambiguous refs are really that bad\nto justify that much extra cost:\n\n  - Ambiguous refs are not that common,\n  - even if a repository contains ambiguous refs, they only hurt when\n    the user actually happens to want to do something with one of the\n    ambiguous refs, and\n  - the issue can be easily circumvented by renaming those ambiguous\n    refs.\n\n  - On the other hand, apparently not that many refs are needed to\n    make refs completion unacceptably slow on Windows,\n  - and this slowness bites each and every time the user attempts refs\n    completion, even when the repository doesn't contain any ambiguous\n    refs.\n  - Furthermore, circumventing the issue might not be possible or\n    might be considerably more difficult and requires various\n    trade-offs (e.g. working in a repository with only a few selected\n    important refs while keeping a separate repository with all refs\n    for reference).\n\nArguably, in this case the benefits of technical correctness are\nrather minor compared to the price we pay for it, and we are better\noff opting for performance over correctness.\n\nUse the 'git for-each-ref' format 'refname:strip=2' to list short refs\nto spare the substantial cost of disambiguating.\n\nThis speeds up refs completion considerably.  Uniquely completing a\nbranch in a repository with 100k local branches, all packed, best of\nfive:\n\n  On Linux, before:\n\n    $ time __git_complete_refs --cur=maste\n\n    real    0m1.662s\n    user    0m1.368s\n    sys     0m0.296s\n\n  After:\n\n    real    0m0.831s\n    user    0m0.808s\n    sys     0m0.028s\n\n  On Windows, before:\n\n    real    0m12.457s\n    user    0m1.016s\n    sys     0m0.092s\n\n  After:\n\n    real    0m1.480s\n    user    0m1.031s\n    sys     0m0.060s\n\n[1] - https://github.com/git-for-windows/git/issues/524\n\nSigned-off-by: SZEDER Gábor <szeder.dev@gmail.com>\n---\n contrib/completion/git-completion.bash | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex e129f674e..5ee35d530 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -401,7 +401,7 @@ __git_refs ()\n \t\t\tfor i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do\n \t\t\t\tif [ -e \"$dir/$i\" ]; then echo $pfx$i; fi\n \t\t\tdone\n-\t\t\tformat=\"refname:short\"\n+\t\t\tformat=\"refname:strip=2\"\n \t\t\trefs=\"refs/tags refs/heads refs/remotes\"\n \t\t\t;;\n \t\tesac\n@@ -412,7 +412,7 @@ __git_refs ()\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\t__git for-each-ref --shell --format=\"ref=%(refname:short)\" \\\n+\t\t\t__git for-each-ref --shell --format=\"ref=%(refname:strip=2)\" \\\n \t\t\t\t\"refs/remotes/\" | \\\n \t\t\twhile read -r entry; do\n \t\t\t\teval \"$entry\"\n@@ -437,7 +437,7 @@ __git_refs ()\n \t*)\n \t\tif [ \"$list_refs_from\" = remote ]; then\n \t\t\techo \"HEAD\"\n-\t\t\t__git for-each-ref --format=\"%(refname:short)\" \\\n+\t\t\t__git for-each-ref --format=\"%(refname:strip=2)\" \\\n \t\t\t\t\"refs/remotes/$remote/\" | sed -e \"s#^$remote/##\"\n \t\telse\n \t\t\t__git ls-remote \"$remote\" HEAD \\\n-- \n2.12.1.485.g1616aa492\n\n"},{"id":"315085","messageId":"20170323152924.23944-14-szeder.dev@gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"[PATCHv2 13/14] completion: fill COMPREPLY directly when completing fetch refspecs","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:23Z","receivedAt":"2017-03-23T15:29:55Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"The __git_complete_fetch_refspecs() has to iterate over __git_refs()'s\noutput anyway to turn the listed matching refs into refspecs, and it\nknows about the prefix and suffix that has to be added to each\nrefspec.\n\nModify this function to add the prefix and suffix to each refspec\nwhile iterating and feed the result, since it doesn't need further\nprocessing, to the new __gitcomp_direct() helper added in the previous\ncommit, because it should be faster when there are a lot of refspecs\nto list.\n\nSigned-off-by: SZEDER Gábor <szeder.dev@gmail.com>\n---\n contrib/completion/git-completion.bash | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 41e658931..86c7d93b8 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -536,11 +536,11 @@ __git_complete_fetch_refspecs ()\n {\n \tlocal i remote=\"$1\" pfx=\"${2-}\" cur_=\"${3-$cur}\" sfx=\"${4- }\"\n \n-\t__gitcomp_nl \"$(\n+\t__gitcomp_direct \"$(\n \t\tfor i in $(__git_refs \"$remote\" \"\" \"\" \"$cur_\") ; do\n-\t\t\techo \"$i:$i\"\n+\t\t\techo \"$pfx$i:$i$sfx\"\n \t\tdone\n-\t\t)\" \"$pfx\" \"$cur_\" \"$sfx\"\n+\t\t)\"\n }\n \n # __git_refs_remotes requires 1 argument (to pass to ls-remote)\n-- \n2.12.1.485.g1616aa492\n\n"},{"id":"315086","messageId":"20170323152924.23944-9-szeder.dev@gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"[PATCHv2 08/14] completion: let 'for-each-ref' and 'ls-remote' filter matching refs","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:18Z","receivedAt":"2017-03-23T15:30:00Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"When completing refs, several __git_refs() code paths list all the\nrefs from the refs/{heads,tags,remotes}/ hierarchy and then\n__gitcomp_nl() iterates over those refs in a shell loop to filter out\nrefs not matching the current ref to be completed.  This comes with a\nconsiderable performance penalty when a repository contains a lot of\nrefs but the current ref can be uniquely completed or when only a\nhandful of refs match the current ref.\n\nReduce the number of iterations in __gitcomp_nl() from the number of\nrefs to the number of matching refs by specifying appropriate globbing\npatterns to 'git for-each-ref' and 'git ls-remote' to list only those\nrefs that match the current ref to be completed.  However, do so only\nwhen the ref to match is explicitly given as parameter, because the\ncurrent word on the command line might contain a prefix like\n'--option=' or 'branch..'.  The __git_complete_refs() and\n__git_complete_fetch_refspecs() helpers introduced previously in this\npatch series already call __git_refs() specifying this current ref\nparameter, so all their callsites, i.e. all places in the completion\nscript doing refs completion, can benefit from this optimization.\n\nFurthermore, list only those symbolic and pseudo refs that match the\ncurrent ref to be completed.  Though it doesn't matter at all in\nitself performance-wise, it will allow us further significant\noptimizations later in this series.\n\nThis speeds up refs completion considerably when there are a lot of\nnon-matching refs to be filtered out.  Uniquely completing a branch in\na repository with 100k local branches, all packed, best of five:\n\n  On Linux, before:\n\n    $ time __git_complete_refs --cur=maste\n\n    real    0m0.831s\n    user    0m0.808s\n    sys     0m0.028s\n\n  After:\n\n    real    0m0.119s\n    user    0m0.104s\n    sys     0m0.008s\n\n  On Windows, before:\n\n    real    0m1.480s\n    user    0m1.031s\n    sys     0m0.060s\n\n  After:\n\n    real    0m0.377s\n    user    0m0.015s\n    sys     0m0.030s\n\nSigned-off-by: SZEDER Gábor <szeder.dev@gmail.com>\n---\n contrib/completion/git-completion.bash |  41 ++++++++---\n t/t9902-completion.sh                  | 124 +++++++++++++++++++++++++++++++++\n 2 files changed, 154 insertions(+), 11 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 5ee35d530..976f80598 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -355,7 +355,8 @@ __git_tags ()\n # 2: In addition to local refs, list unique branches from refs/remotes/ for\n #    'git checkout's tracking DWIMery (optional; ignored, if set but empty).\n # 3: Currently ignored.\n-# 4: The current ref to be completed (optional).\n+# 4: List only refs matching this word (optional; list all refs if unset or\n+#    empty).\n #\n # Use __git_complete_refs() instead.\n __git_refs ()\n@@ -364,6 +365,7 @@ __git_refs ()\n \tlocal list_refs_from=path remote=\"${1-}\"\n \tlocal format refs pfx\n \tlocal cur_=\"${4-$cur}\"\n+\tlocal match=\"${4-}\"\n \n \t__git_find_repo_path\n \tdir=\"$__git_repo_path\"\n@@ -390,23 +392,32 @@ __git_refs ()\n \t\tif [[ \"$cur_\" == ^* ]]; then\n \t\t\tpfx=\"^\"\n \t\t\tcur_=${cur_#^}\n+\t\t\tmatch=${match#^}\n \t\tfi\n \t\tcase \"$cur_\" in\n \t\trefs|refs/*)\n \t\t\tformat=\"refname\"\n-\t\t\trefs=\"${cur_%/*}\"\n+\t\t\trefs=(\"$match*\" \"$match*/**\")\n \t\t\ttrack=\"\"\n \t\t\t;;\n \t\t*)\n \t\t\tfor i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do\n-\t\t\t\tif [ -e \"$dir/$i\" ]; then echo $pfx$i; fi\n+\t\t\t\tcase \"$i\" in\n+\t\t\t\t$match*)\n+\t\t\t\t\tif [ -e \"$dir/$i\" ]; then\n+\t\t\t\t\t\techo $pfx$i\n+\t\t\t\t\tfi\n+\t\t\t\t\t;;\n+\t\t\t\tesac\n \t\t\tdone\n \t\t\tformat=\"refname:strip=2\"\n-\t\t\trefs=\"refs/tags refs/heads refs/remotes\"\n+\t\t\trefs=(\"refs/tags/$match*\" \"refs/tags/$match*/**\"\n+\t\t\t\t\"refs/heads/$match*\" \"refs/heads/$match*/**\"\n+\t\t\t\t\"refs/remotes/$match*\" \"refs/remotes/$match*/**\")\n \t\t\t;;\n \t\tesac\n \t\t__git_dir=\"$dir\" __git for-each-ref --format=\"$pfx%($format)\" \\\n-\t\t\t$refs\n+\t\t\t\"${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@@ -417,7 +428,7 @@ __git_refs ()\n \t\t\twhile read -r entry; do\n \t\t\t\teval \"$entry\"\n \t\t\t\tref=\"${ref#*/}\"\n-\t\t\t\tif [[ \"$ref\" == \"$cur_\"* ]]; then\n+\t\t\t\tif [[ \"$ref\" == \"$match\"* ]]; then\n \t\t\t\t\techo \"$ref\"\n \t\t\t\tfi\n \t\t\tdone | sort | uniq -u\n@@ -426,7 +437,7 @@ __git_refs ()\n \tfi\n \tcase \"$cur_\" in\n \trefs|refs/*)\n-\t\t__git ls-remote \"$remote\" \"$cur_*\" | \\\n+\t\t__git ls-remote \"$remote\" \"$match*\" | \\\n \t\twhile read -r hash i; do\n \t\t\tcase \"$i\" in\n \t\t\t*^{}) ;;\n@@ -436,12 +447,20 @@ __git_refs ()\n \t\t;;\n \t*)\n \t\tif [ \"$list_refs_from\" = remote ]; then\n-\t\t\techo \"HEAD\"\n+\t\t\tcase \"HEAD\" in\n+\t\t\t$match*)\techo \"HEAD\" ;;\n+\t\t\tesac\n \t\t\t__git for-each-ref --format=\"%(refname:strip=2)\" \\\n-\t\t\t\t\"refs/remotes/$remote/\" | sed -e \"s#^$remote/##\"\n+\t\t\t\t\"refs/remotes/$remote/$match*\" \\\n+\t\t\t\t\"refs/remotes/$remote/$match*/**\" | sed -e \"s#^$remote/##\"\n \t\telse\n-\t\t\t__git ls-remote \"$remote\" HEAD \\\n-\t\t\t\t\"refs/tags/*\" \"refs/heads/*\" \"refs/remotes/*\" |\n+\t\t\tlocal query_symref\n+\t\t\tcase \"HEAD\" in\n+\t\t\t$match*)\tquery_symref=\"HEAD\" ;;\n+\t\t\tesac\n+\t\t\t__git ls-remote \"$remote\" $query_symref \\\n+\t\t\t\t\"refs/tags/$match*\" \"refs/heads/$match*\" \\\n+\t\t\t\t\"refs/remotes/$match*\" |\n \t\t\twhile read -r hash i; do\n \t\t\t\tcase \"$i\" in\n \t\t\t\t*^{})\t;;\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex e2b45f625..cc9e741f9 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -555,6 +555,9 @@ test_expect_success '__git_refs - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/master\n \trefs/heads/matching-branch\n+\trefs/remotes/other/branch-in-other\n+\trefs/remotes/other/master-in-other\n+\trefs/tags/matching-tag\n \tEOF\n \t(\n \t\tcur=refs/heads/ &&\n@@ -620,6 +623,7 @@ test_expect_success '__git_refs - configured remote' '\n \n test_expect_success '__git_refs - configured remote - full refs' '\n \tcat >expected <<-EOF &&\n+\tHEAD\n \trefs/heads/branch-in-other\n \trefs/heads/master-in-other\n \trefs/tags/tag-in-other\n@@ -648,6 +652,7 @@ test_expect_success '__git_refs - configured remote - repo given on the command\n \n test_expect_success '__git_refs - configured remote - full refs - repo given on the command line' '\n \tcat >expected <<-EOF &&\n+\tHEAD\n \trefs/heads/branch-in-other\n \trefs/heads/master-in-other\n \trefs/tags/tag-in-other\n@@ -692,6 +697,7 @@ test_expect_success '__git_refs - URL remote' '\n \n test_expect_success '__git_refs - URL remote - full refs' '\n \tcat >expected <<-EOF &&\n+\tHEAD\n \trefs/heads/branch-in-other\n \trefs/heads/master-in-other\n \trefs/tags/tag-in-other\n@@ -837,6 +843,124 @@ test_expect_success '__git refs - exluding full refs' '\n \ttest_cmp expected \"$actual\"\n '\n \n+test_expect_success 'setup for filtering matching refs' '\n+\tgit branch matching/branch &&\n+\tgit tag matching/tag &&\n+\tgit -C otherrepo branch matching/branch-in-other &&\n+\tgit fetch --no-tags other &&\n+\trm -f .git/FETCH_HEAD\n+'\n+\n+test_expect_success '__git_refs - dont filter refs unless told so' '\n+\tcat >expected <<-EOF &&\n+\tHEAD\n+\tmaster\n+\tmatching-branch\n+\tmatching/branch\n+\tother/branch-in-other\n+\tother/master-in-other\n+\tother/matching/branch-in-other\n+\tmatching-tag\n+\tmatching/tag\n+\tEOF\n+\t(\n+\t\tcur=master &&\n+\t\t__git_refs >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_refs - only matching refs' '\n+\tcat >expected <<-EOF &&\n+\tmatching-branch\n+\tmatching/branch\n+\tmatching-tag\n+\tmatching/tag\n+\tEOF\n+\t(\n+\t\tcur=mat &&\n+\t\t__git_refs \"\" \"\" \"\" \"$cur\" >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_refs - only matching refs - full refs' '\n+\tcat >expected <<-EOF &&\n+\trefs/heads/matching-branch\n+\trefs/heads/matching/branch\n+\tEOF\n+\t(\n+\t\tcur=refs/heads/mat &&\n+\t\t__git_refs \"\" \"\" \"\" \"$cur\" >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_refs - only matching refs - remote on local file system' '\n+\tcat >expected <<-EOF &&\n+\tmaster-in-other\n+\tmatching/branch-in-other\n+\tEOF\n+\t(\n+\t\tcur=ma &&\n+\t\t__git_refs otherrepo \"\" \"\" \"$cur\" >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_refs - only matching refs - configured remote' '\n+\tcat >expected <<-EOF &&\n+\tmaster-in-other\n+\tmatching/branch-in-other\n+\tEOF\n+\t(\n+\t\tcur=ma &&\n+\t\t__git_refs other \"\" \"\" \"$cur\" >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_refs - only matching refs - remote - full refs' '\n+\tcat >expected <<-EOF &&\n+\trefs/heads/master-in-other\n+\trefs/heads/matching/branch-in-other\n+\tEOF\n+\t(\n+\t\tcur=refs/heads/ma &&\n+\t\t__git_refs other \"\" \"\" \"$cur\" >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_refs - only matching refs - checkout DWIMery' '\n+\tcat >expected <<-EOF &&\n+\tmatching-branch\n+\tmatching/branch\n+\tmatching-tag\n+\tmatching/tag\n+\tmatching/branch-in-other\n+\tEOF\n+\tfor remote_ref in refs/remotes/other/ambiguous \\\n+\t\trefs/remotes/remote/ambiguous \\\n+\t\trefs/remotes/remote/branch-in-remote\n+\tdo\n+\t\tgit update-ref $remote_ref master &&\n+\t\ttest_when_finished \"git update-ref -d $remote_ref\"\n+\tdone &&\n+\t(\n+\t\tcur=mat &&\n+\t\t__git_refs \"\" 1 \"\" \"$cur\" >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success 'teardown after filtering matching refs' '\n+\tgit branch -d matching/branch &&\n+\tgit tag -d matching/tag &&\n+\tgit update-ref -d refs/remotes/other/matching/branch-in-other &&\n+\tgit -C otherrepo branch -D matching/branch-in-other\n+'\n+\n test_expect_success '__git_complete_refs - simple' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n-- \n2.12.1.485.g1616aa492\n\n"},{"id":"315087","messageId":"20170323152924.23944-10-szeder.dev@gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"[PATCHv2 09/14] completion: let 'for-each-ref' strip the remote name from remote branches","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:19Z","receivedAt":"2017-03-23T15:30:02Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"The code listing unique remote branches for 'git checkout's tracking\nDWIMery uses a shell parameter expansion in a loop iterating over each\nlisted ref to remove the remote's name from the remote branches, i.e.\nthe leading path component from the short ref.  When listing refs from\na configured remote repository, '| sed s///' is used for the same\npurpose.\n\nLet 'git for-each-ref' strip one more leading path component from the\nrefs, i.e. use the format 'refname:strip=3' instead of '=2', making\nthat parameter expansion and 'sed' execution unnecessary.\n\nThis speeds up refs completion for 'git checkout'.  Uniquely\ncompleting a branch for 'git checkout maste<TAB>' in a repo with 100k\nremote branches, all packed, best of five:\n\n  On Linux, near the beginning of this series, for reference:\n\n    $ time __git_complete_refs --cur=maste --track\n\n    real    0m8.185s\n    user    0m6.896s\n    sys     0m1.616s\n\n  Before this patch:\n\n    real    0m2.714s\n    user    0m2.344s\n    sys     0m0.436s\n\n  After:\n\n    real    0m1.993s\n    user    0m1.740s\n    sys     0m0.304s\n\n  On Windows, near the beginning:\n\n    real    1m8.421s\n    user    0m7.591s\n    sys     0m3.557s\n\n  Before this patch:\n\n    real    0m8.191s\n    user    0m4.638s\n    sys     0m2.918s\n\n  After:\n\n    real    0m6.187s\n    user    0m3.358s\n    sys     0m2.121s\n\nSigned-off-by: SZEDER Gábor <szeder.dev@gmail.com>\n---\n contrib/completion/git-completion.bash | 7 +++----\n 1 file changed, 3 insertions(+), 4 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 976f80598..206eaf0ca 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -423,11 +423,10 @@ __git_refs ()\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\t__git for-each-ref --shell --format=\"ref=%(refname:strip=2)\" \\\n+\t\t\t__git for-each-ref --shell --format=\"ref=%(refname:strip=3)\" \\\n \t\t\t\t\"refs/remotes/\" | \\\n \t\t\twhile read -r entry; do\n \t\t\t\teval \"$entry\"\n-\t\t\t\tref=\"${ref#*/}\"\n \t\t\t\tif [[ \"$ref\" == \"$match\"* ]]; then\n \t\t\t\t\techo \"$ref\"\n \t\t\t\tfi\n@@ -450,9 +449,9 @@ __git_refs ()\n \t\t\tcase \"HEAD\" in\n \t\t\t$match*)\techo \"HEAD\" ;;\n \t\t\tesac\n-\t\t\t__git for-each-ref --format=\"%(refname:strip=2)\" \\\n+\t\t\t__git for-each-ref --format=\"%(refname:strip=3)\" \\\n \t\t\t\t\"refs/remotes/$remote/$match*\" \\\n-\t\t\t\t\"refs/remotes/$remote/$match*/**\" | sed -e \"s#^$remote/##\"\n+\t\t\t\t\"refs/remotes/$remote/$match*/**\"\n \t\telse\n \t\t\tlocal query_symref\n \t\t\tcase \"HEAD\" in\n-- \n2.12.1.485.g1616aa492\n\n"},{"id":"315088","messageId":"20170323152924.23944-11-szeder.dev@gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"[PATCHv2 10/14] completion: let 'for-each-ref' filter remote branches for 'checkout' DWIMery","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:20Z","receivedAt":"2017-03-23T15:30:03Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"The code listing unique remote branches for 'git checkout's tracking\nDWIMery outputs only remote branches that match the current word to be\ncompleted, but the filtering is done in a shell loop iterating over\nall remote refs.\n\nLet 'git for-each-ref' do the filtering, as it can do so much more\nefficiently and we can remove that shell loop entirely.\n\nThis speeds up refs completion for 'git checkout' considerably when\nthere are a lot of non-matching remote refs to be filtered out.\nUniquely completing a branch in a repository with 100k remote\nbranches, all packed, best of five:\n\n  On Linux, before:\n\n    $ time __git_complete_refs --cur=maste --track\n\n    real    0m1.993s\n    user    0m1.740s\n    sys     0m0.304s\n\n  After:\n\n    real    0m0.266s\n    user    0m0.248s\n    sys     0m0.012s\n\n  On Windows, before:\n\n    real    0m6.187s\n    user    0m3.358s\n    sys     0m2.121s\n\n  After:\n\n    real    0m0.750s\n    user    0m0.015s\n    sys     0m0.090s\n\nSigned-off-by: SZEDER Gábor <szeder.dev@gmail.com>\n---\n contrib/completion/git-completion.bash | 12 +++---------\n 1 file changed, 3 insertions(+), 9 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 206eaf0ca..394dcece6 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -422,15 +422,9 @@ __git_refs ()\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\t__git for-each-ref --shell --format=\"ref=%(refname:strip=3)\" \\\n-\t\t\t\t\"refs/remotes/\" | \\\n-\t\t\twhile read -r entry; do\n-\t\t\t\teval \"$entry\"\n-\t\t\t\tif [[ \"$ref\" == \"$match\"* ]]; then\n-\t\t\t\t\techo \"$ref\"\n-\t\t\t\tfi\n-\t\t\tdone | sort | uniq -u\n+\t\t\t__git for-each-ref --format=\"%(refname:strip=3)\" \\\n+\t\t\t\t\"refs/remotes/*/$match*\" \"refs/remotes/*/$match*/**\" | \\\n+\t\t\tsort | uniq -u\n \t\tfi\n \t\treturn\n \tfi\n-- \n2.12.1.485.g1616aa492\n\n"},{"id":"315089","messageId":"20170323152924.23944-15-szeder.dev@gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"[PATCHv2 14/14] completion: speed up branch and tag completion","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:24Z","receivedAt":"2017-03-23T15:30:05Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"Modify __git_heads() and __git_tags() and the few callsites they have,\nso we can let 'git for-each-ref' do all the hard work and these\nfunctions' output won't need any further processing or filtering\nbefore being handed over to Bash, resulting in faster branch and tag\ncompletion.  These are some of the same tricks used in the previous\ncommits to speed up refs completion, namely:\n\n  - Extend both functions to accept prefix, current word and suffix\n    positional parameters, all optional and all empty by default to\n    keep the parameterless behavior unaltered.\n\n  - Specify appropriate globbing patterns to 'git for-each-ref' to\n    list only branches or tags matching the given current word\n    parameter.\n\n  - Modify the 'git for-each-ref --format=<...>' to include the given\n    prefix and suffix.\n\n  - Adjust all callsites to specify the proper prefix, current word\n    and suffix parameters, and to fill COMPREPLY using\n    __gitcomp_direct().\n\nSigned-off-by: SZEDER Gábor <szeder.dev@gmail.com>\n---\n contrib/completion/git-completion.bash | 25 +++++++++++++++++++------\n 1 file changed, 19 insertions(+), 6 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 86c7d93b8..bd07d9a74 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -352,14 +352,27 @@ __git_index_files ()\n \tdone | sort | uniq\n }\n \n+# Lists branches from the local repository.\n+# 1: A prefix to be added to each listed branch (optional).\n+# 2: List only branches matching this word (optional; list all branches if\n+#    unset or empty).\n+# 3: A suffix to be appended to each listed branch (optional).\n __git_heads ()\n {\n-\t__git for-each-ref --format='%(refname:strip=2)' refs/heads\n+\tlocal pfx=\"${1-}\" cur_=\"${2-}\" sfx=\"${3-}\"\n+\n+\t__git for-each-ref --format=\"${pfx//\\%/%%}%(refname:strip=2)$sfx\" \\\n+\t\t\t\"refs/heads/$cur_*\" \"refs/heads/$cur_*/**\"\n }\n \n+# Lists tags from the local repository.\n+# Accepts the same positional parameters as __git_heads() above.\n __git_tags ()\n {\n-\t__git for-each-ref --format='%(refname:strip=2)' refs/tags\n+\tlocal pfx=\"${1-}\" cur_=\"${2-}\" sfx=\"${3-}\"\n+\n+\t__git for-each-ref --format=\"${pfx//\\%/%%}%(refname:strip=2)$sfx\" \\\n+\t\t\t\"refs/tags/$cur_*\" \"refs/tags/$cur_*/**\"\n }\n \n # Lists refs from the local (by default) or from a remote repository.\n@@ -1183,7 +1196,7 @@ _git_branch ()\n \t\t;;\n \t*)\n \t\tif [ $only_local_ref = \"y\" -a $has_r = \"n\" ]; then\n-\t\t\t__gitcomp_nl \"$(__git_heads)\"\n+\t\t\t__gitcomp_direct \"$(__git_heads \"\" \"$cur\" \" \")\"\n \t\telse\n \t\t\t__git_complete_refs\n \t\tfi\n@@ -2178,7 +2191,7 @@ _git_config ()\n \t\t;;\n \tbranch.*)\n \t\tlocal pfx=\"${cur%.*}.\" cur_=\"${cur#*.}\"\n-\t\t__gitcomp_nl \"$(__git_heads)\" \"$pfx\" \"$cur_\" \".\"\n+\t\t__gitcomp_direct \"$(__git_heads \"$pfx\" \"$cur_\" \".\")\"\n \t\t__gitcomp_nl_append $'autosetupmerge\\nautosetuprebase\\n' \"$pfx\" \"$cur_\"\n \t\treturn\n \t\t;;\n@@ -2824,7 +2837,7 @@ _git_tag ()\n \t\ti=\"${words[c]}\"\n \t\tcase \"$i\" in\n \t\t-d|-v)\n-\t\t\t__gitcomp_nl \"$(__git_tags)\"\n+\t\t\t__gitcomp_direct \"$(__git_tags \"\" \"$cur\" \" \")\"\n \t\t\treturn\n \t\t\t;;\n \t\t-f)\n@@ -2839,7 +2852,7 @@ _git_tag ()\n \t\t;;\n \t-*|tag)\n \t\tif [ $f = 1 ]; then\n-\t\t\t__gitcomp_nl \"$(__git_tags)\"\n+\t\t\t__gitcomp_direct \"$(__git_tags \"\" \"$cur\" \" \")\"\n \t\tfi\n \t\t;;\n \t*)\n-- \n2.12.1.485.g1616aa492\n\n"},{"id":"315090","messageId":"20170323152924.23944-13-szeder.dev@gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"[PATCHv2 12/14] completion: fill COMPREPLY directly when completing refs","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:22Z","receivedAt":"2017-03-23T15:30:11Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"__gitcomp_nl() iterates over all the possible completion words it gets\nas argument\n\n  - filtering matching words,\n  - appending a trailing space to each matching word (in all but two\n    cases),\n  - prepending a prefix to each matching word (when completing words\n    after e.g. '--option=<TAB>' or 'master..<TAB>'), and\n  - adding each matching word to the COMPREPLY array.\n\nThis takes a while when a lot of refs are passed to __gitcomp_nl().\n\nThe previous changes in this series ensure that __git_refs() lists\nonly refs matching the current word to be completed, making a second\nfiltering in __gitcomp_nl() redundant.\n\nAdding the necessary prefix and suffix could be done in __git_refs()\nas well:\n\n  - When refs come from 'git for-each-ref', then that prefix and\n    suffix could be added much more efficiently using a 'git\n    for-each-ref' format containing said prefix and suffix.  Care\n    should be taken, though, because that prefix might contain\n    'for-each-ref' format specifiers as part of the left hand side of\n    a '..' range or '...' symmetric difference notation or\n    fetch/push/etc. refspec, e.g. 'git log \"evil-%(refname)..br<TAB>'.\n    Doubling every '%' in the prefix will prevent 'git for-each-ref'\n    from interpolating any of those contained specifiers.\n  - When refs come from 'git ls-remote', then that prefix and suffix\n    can be added in the shell loop that has to process 'git\n    ls-remote's output anyway.\n  - Finally, the prefix and suffix can be added to that handful of\n    potentially matching symbolic and pseudo refs right away in the\n    shell loop listing them.\n\nAnd then all what is still left to do is to assign a bunch of\nnewline-separated words to a shell array, which can be done without a\nshell loop iterating over each word, basically making all of\n__gitcomp_nl() unnecessary for refs completion.\n\nAdd the helper function __gitcomp_direct() to fill the COMPREPLY array\nwith prefiltered and preprocessed words without any additional\nprocessing, without a shell loop, with just one single compound\nassignment.  Modify __git_refs() to accept prefix and suffix\nparameters and add them to each and every listed ref as described\nabove.  Modify __git_complete_refs() to pass the prefix and suffix\nparameters to __git_refs() and to feed __git_refs()'s output to\n__gitcomp_direct() instead of __gitcomp_nl().\n\nThis speeds up refs completion when there are a lot of refs matching\nthe current word to be completed.  Listing all branches for completion\nin a repo with 100k local branches, all packed, best of five:\n\n  On Linux, near the beginning of this series, for reference:\n\n    $ time __git_complete_refs\n\n    real    0m2.028s\n    user    0m1.692s\n    sys     0m0.344s\n\n  Before this patch:\n\n    real    0m1.135s\n    user    0m1.112s\n    sys     0m0.024s\n\n  After:\n\n    real    0m0.367s\n    user    0m0.352s\n    sys     0m0.020s\n\n  On Windows, near the beginning:\n\n    real    0m13.078s\n    user    0m1.609s\n    sys     0m0.060s\n\n  Before this patch:\n\n    real    0m2.093s\n    user    0m1.641s\n    sys     0m0.060s\n\n  After:\n\n    real    0m0.683s\n    user    0m0.203s\n    sys     0m0.076s\n\nSigned-off-by: SZEDER Gábor <szeder.dev@gmail.com>\n---\n contrib/completion/git-completion.bash | 54 +++++++++++++++++++++++++---------\n contrib/completion/git-completion.zsh  |  9 ++++++\n t/t9902-completion.sh                  | 27 +++++++++++++++++\n 3 files changed, 76 insertions(+), 14 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex d26312899..41e658931 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -213,6 +213,20 @@ _get_comp_words_by_ref ()\n }\n fi\n \n+# Fills the COMPREPLY array with prefiltered words without any additional\n+# processing.\n+# Callers must take care of providing only words that match the current word\n+# to be completed and adding any prefix and/or suffix (trailing space!), if\n+# necessary.\n+# 1: List of newline-separated matching completion words, complete with\n+#    prefix and suffix.\n+__gitcomp_direct ()\n+{\n+\tlocal IFS=$'\\n'\n+\n+\tCOMPREPLY=($1)\n+}\n+\n __gitcompappend ()\n {\n \tlocal x i=${#COMPREPLY[@]}\n@@ -354,18 +368,21 @@ __git_tags ()\n #    Can be the name of a configured remote, a path, or a URL.\n # 2: In addition to local refs, list unique branches from refs/remotes/ for\n #    'git checkout's tracking DWIMery (optional; ignored, if set but empty).\n-# 3: Currently ignored.\n+# 3: A prefix to be added to each listed ref (optional).\n # 4: List only refs matching this word (optional; list all refs if unset or\n #    empty).\n+# 5: A suffix to be appended to each listed ref (optional; ignored, if set\n+#    but empty).\n #\n # Use __git_complete_refs() instead.\n __git_refs ()\n {\n \tlocal i hash dir track=\"${2-}\"\n \tlocal list_refs_from=path remote=\"${1-}\"\n-\tlocal format refs pfx\n-\tlocal cur_=\"${4-$cur}\"\n+\tlocal format refs\n+\tlocal pfx=\"${3-}\" cur_=\"${4-$cur}\" sfx=\"${5-}\"\n \tlocal match=\"${4-}\"\n+\tlocal fer_pfx=\"${pfx//\\%/%%}\" # \"escape\" for-each-ref format specifiers\n \n \t__git_find_repo_path\n \tdir=\"$__git_repo_path\"\n@@ -390,7 +407,8 @@ __git_refs ()\n \n \tif [ \"$list_refs_from\" = path ]; then\n \t\tif [[ \"$cur_\" == ^* ]]; then\n-\t\t\tpfx=\"^\"\n+\t\t\tpfx=\"$pfx^\"\n+\t\t\tfer_pfx=\"$fer_pfx^\"\n \t\t\tcur_=${cur_#^}\n \t\t\tmatch=${match#^}\n \t\tfi\n@@ -405,7 +423,7 @@ __git_refs ()\n \t\t\t\tcase \"$i\" in\n \t\t\t\t$match*)\n \t\t\t\t\tif [ -e \"$dir/$i\" ]; then\n-\t\t\t\t\t\techo $pfx$i\n+\t\t\t\t\t\techo \"$pfx$i$sfx\"\n \t\t\t\t\tfi\n \t\t\t\t\t;;\n \t\t\t\tesac\n@@ -416,13 +434,13 @@ __git_refs ()\n \t\t\t\t\"refs/remotes/$match*\" \"refs/remotes/$match*/**\")\n \t\t\t;;\n \t\tesac\n-\t\t__git_dir=\"$dir\" __git for-each-ref --format=\"$pfx%($format)\" \\\n+\t\t__git_dir=\"$dir\" __git for-each-ref --format=\"$fer_pfx%($format)$sfx\" \\\n \t\t\t\"${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\t__git for-each-ref --format=\"%(refname:strip=3)\" \\\n+\t\t\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=3)$sfx\" \\\n \t\t\t\t--sort=\"refname:strip=3\" \\\n \t\t\t\t\"refs/remotes/*/$match*\" \"refs/remotes/*/$match*/**\" | \\\n \t\t\tuniq -u\n@@ -435,16 +453,16 @@ __git_refs ()\n \t\twhile read -r hash i; do\n \t\t\tcase \"$i\" in\n \t\t\t*^{}) ;;\n-\t\t\t*) echo \"$i\" ;;\n+\t\t\t*) echo \"$pfx$i$sfx\" ;;\n \t\t\tesac\n \t\tdone\n \t\t;;\n \t*)\n \t\tif [ \"$list_refs_from\" = remote ]; then\n \t\t\tcase \"HEAD\" in\n-\t\t\t$match*)\techo \"HEAD\" ;;\n+\t\t\t$match*)\techo \"${pfx}HEAD$sfx\" ;;\n \t\t\tesac\n-\t\t\t__git for-each-ref --format=\"%(refname:strip=3)\" \\\n+\t\t\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=3)$sfx\" \\\n \t\t\t\t\"refs/remotes/$remote/$match*\" \\\n \t\t\t\t\"refs/remotes/$remote/$match*/**\"\n \t\telse\n@@ -458,8 +476,8 @@ __git_refs ()\n \t\t\twhile read -r hash i; do\n \t\t\t\tcase \"$i\" in\n \t\t\t\t*^{})\t;;\n-\t\t\t\trefs/*)\techo \"${i#refs/*/}\" ;;\n-\t\t\t\t*)\techo \"$i\" ;;  # symbolic refs\n+\t\t\t\trefs/*)\techo \"$pfx${i#refs/*/}$sfx\" ;;\n+\t\t\t\t*)\techo \"$pfx$i$sfx\" ;;  # symbolic refs\n \t\t\t\tesac\n \t\t\tdone\n \t\tfi\n@@ -494,8 +512,7 @@ __git_complete_refs ()\n \t\tshift\n \tdone\n \n-\t__gitcomp_nl \"$(__git_refs \"$remote\" \"$track\" \"\" \"$cur_\")\" \\\n-\t\t\"$pfx\" \"$cur_\" \"$sfx\"\n+\t__gitcomp_direct \"$(__git_refs \"$remote\" \"$track\" \"$pfx\" \"$cur_\" \"$sfx\")\"\n }\n \n # __git_refs2 requires 1 argument (to pass to __git_refs)\n@@ -2997,6 +3014,15 @@ if [[ -n ${ZSH_VERSION-} ]]; then\n \t\tesac\n \t}\n \n+\t__gitcomp_direct ()\n+\t{\n+\t\temulate -L zsh\n+\n+\t\tlocal IFS=$'\\n'\n+\t\tcompset -P '*[=:]'\n+\t\tcompadd -Q -- ${=1} && _ret=0\n+\t}\n+\n \t__gitcomp_nl ()\n \t{\n \t\temulate -L zsh\ndiff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh\nindex e25541308..c3521fbfc 100644\n--- a/contrib/completion/git-completion.zsh\n+++ b/contrib/completion/git-completion.zsh\n@@ -67,6 +67,15 @@ __gitcomp ()\n \tesac\n }\n \n+__gitcomp_direct ()\n+{\n+\temulate -L zsh\n+\n+\tlocal IFS=$'\\n'\n+\tcompset -P '*[=:]'\n+\tcompadd -Q -- ${=1} && _ret=0\n+}\n+\n __gitcomp_nl ()\n {\n \temulate -L zsh\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex cc9e741f9..5ed28135b 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -400,6 +400,22 @@ test_expect_success '__gitdir - remote as argument' '\n \ttest_cmp expected \"$actual\"\n '\n \n+test_expect_success '__gitcomp_direct - puts everything into COMPREPLY as-is' '\n+\tsed -e \"s/Z$//g\" >expected <<-EOF &&\n+\twith-trailing-space Z\n+\twithout-trailing-spaceZ\n+\t--option Z\n+\t--option=Z\n+\t$invalid_variable_name Z\n+\tEOF\n+\t(\n+\t\tcur=should_be_ignored &&\n+\t\t__gitcomp_direct \"$(cat expected)\" &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n test_expect_success '__gitcomp - trailing space - options' '\n \ttest_gitcomp \"--re\" \"--dry-run --reuse-message= --reedit-message=\n \t\t--reset-author\" <<-EOF\n@@ -961,6 +977,17 @@ test_expect_success 'teardown after filtering matching refs' '\n \tgit -C otherrepo branch -D matching/branch-in-other\n '\n \n+test_expect_success '__git_refs - for-each-ref format specifiers in prefix' '\n+\tcat >expected <<-EOF &&\n+\tevil-%%-%42-%(refname)..master\n+\tEOF\n+\t(\n+\t\tcur=\"evil-%%-%42-%(refname)..mas\" &&\n+\t\t__git_refs \"\" \"\" \"evil-%%-%42-%(refname)..\" mas >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n test_expect_success '__git_complete_refs - simple' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n-- \n2.12.1.485.g1616aa492\n\n"},{"id":"315091","messageId":"20170323152924.23944-12-szeder.dev@gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"[PATCHv2 11/14] completion: let 'for-each-ref' sort remote branches for 'checkout' DWIMery","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:21Z","receivedAt":"2017-03-23T15:30:40Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"When listing unique remote branches for 'git checkout's tracking\nDWIMery, __git_refs() runs the classic '... |sort |uniq -u' pattern to\nfilter out duplicate remote branches.\n\nLet 'git for-each-ref' do the sorting, sparing the overhead of\nfork()+exec()ing 'sort' and a stage in the pipeline where potentially\nrelatively large amount of data can be passed between two subsequent\npipeline stages.\n\nThis speeds up refs completion for 'git checkout' a bit when a lot of\nremote branches match the current word to be completed.  Listing a\nsingle local and 100k remote branches, all packed, best of five:\n\n  On Linux, before:\n\n    $ time __git_complete_refs --track\n\n    real    0m1.856s\n    user    0m1.816s\n    sys     0m0.060s\n\n  After:\n\n    real    0m1.550s\n    user    0m1.512s\n    sys     0m0.060s\n\n  On Windows, before:\n\n    real    0m3.128s\n    user    0m2.155s\n    sys     0m0.183s\n\n  After:\n\n    real    0m2.781s\n    user    0m1.826s\n    sys     0m0.136s\n\nSigned-off-by: SZEDER Gábor <szeder.dev@gmail.com>\n---\n contrib/completion/git-completion.bash | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 394dcece6..d26312899 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -423,8 +423,9 @@ __git_refs ()\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\t__git for-each-ref --format=\"%(refname:strip=3)\" \\\n+\t\t\t\t--sort=\"refname:strip=3\" \\\n \t\t\t\t\"refs/remotes/*/$match*\" \"refs/remotes/*/$match*/**\" | \\\n-\t\t\tsort | uniq -u\n+\t\t\tuniq -u\n \t\tfi\n \t\treturn\n \tfi\n-- \n2.12.1.485.g1616aa492\n\n"},{"id":"315092","messageId":"20170323152924.23944-1-szeder.dev@gmail.com","threadId":"45480","inReplyTo":null,"subject":"[PATCHv2 00/14] completion: speed up refs completion","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:10Z","receivedAt":"2017-03-23T15:30:45Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"This series is the updated version of 'sg/completion-refs-speedup'.\nIt speeds up refs completion for large number of refs, partly by\ngiving up disambiguating ambiguous refs and partly by eliminating most\nof the shell processing between 'git for-each-ref' and 'ls-remote' and\nBash's completion facility.  The rest is a bit of preparatory\nreorganization, cleanup and bugfixes.\n\nChanges since v1:\n\n  - Patch 8 (let 'for-each-ref' and 'ls-remote' filter matching refs;\n    it was patch 7 in v1) was modified in two ways:\n    \n    * __git_refs() now does that filtering only when the ref to match\n      was explicitly given as parameter, as opposed to falling back to\n      the current word to be completed.  The current word might be\n      something like '--opt=maste', and in the fallback case we would\n      then list only refs matching '--opt=maste', which is of course\n      wrong.  Most of the subsequent patches had to be adjusted\n      because of conflicts.\n\n    * patch 11 (list only matching symbolic and pseudorefs when\n      completing refs) was squashed into patch 8.  There was no reason\n      to keep the two patches separate, and the docstring was\n      inconsistent between the two patches.\n\n  - Patch 12 now incorporates the squash! patch I sent out earlier\n    [1].\n\n  - Patch 4 (support completing fully qualified non-fast-forward\n    refspecs) is new, to fix a bug that is similar in nature to the\n    one fixed in patch 3.\n\n  - Patches 13 and 14 are new and make use of the new and faster\n    __gitcomp_direct() for branches, tags, and fetch refspecs.\n\n  - Some new tests run 'sed s/Z$//g'.  Remove that 'g', because there\n    is no point to ask to replace all instances of the match, when it\n    matches only at the end of line.\n\n  - A teardown test forgot to delete a branch.\n\n[1] - http://public-inbox.org/git/20170206181545.12869-1-szeder.dev@gmail.com/\n\nSZEDER Gábor (14):\n  completion: remove redundant __gitcomp_nl() options from _git_commit()\n  completion: wrap __git_refs() for better option parsing\n  completion: support completing full refs after '--option=refs/<TAB>'\n  completion: support completing fully qualified non-fast-forward\n    refspecs\n  completion: support excluding full refs\n  completion: don't disambiguate tags and branches\n  completion: don't disambiguate short refs\n  completion: let 'for-each-ref' and 'ls-remote' filter matching refs\n  completion: let 'for-each-ref' strip the remote name from remote\n    branches\n  completion: let 'for-each-ref' filter remote branches for 'checkout'\n    DWIMery\n  completion: let 'for-each-ref' sort remote branches for 'checkout'\n    DWIMery\n  completion: fill COMPREPLY directly when completing refs\n  completion: fill COMPREPLY directly when completing fetch refspecs\n  completion: speed up branch and tag completion\n\n contrib/completion/git-completion.bash | 252 +++++++++++++++------\n contrib/completion/git-completion.zsh  |   9 +\n t/t9902-completion.sh                  | 387 +++++++++++++++++++++++++++++++++\n 3 files changed, 577 insertions(+), 71 deletions(-)\n\n-- \n2.12.1.485.g1616aa492\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 56ededb09..bd07d9a74 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -352,14 +352,27 @@ __git_index_files ()\n \tdone | sort | uniq\n }\n \n+# Lists branches from the local repository.\n+# 1: A prefix to be added to each listed branch (optional).\n+# 2: List only branches matching this word (optional; list all branches if\n+#    unset or empty).\n+# 3: A suffix to be appended to each listed branch (optional).\n __git_heads ()\n {\n-\t__git for-each-ref --format='%(refname:strip=2)' refs/heads\n+\tlocal pfx=\"${1-}\" cur_=\"${2-}\" sfx=\"${3-}\"\n+\n+\t__git for-each-ref --format=\"${pfx//\\%/%%}%(refname:strip=2)$sfx\" \\\n+\t\t\t\"refs/heads/$cur_*\" \"refs/heads/$cur_*/**\"\n }\n \n+# Lists tags from the local repository.\n+# Accepts the same positional parameters as __git_heads() above.\n __git_tags ()\n {\n-\t__git for-each-ref --format='%(refname:strip=2)' refs/tags\n+\tlocal pfx=\"${1-}\" cur_=\"${2-}\" sfx=\"${3-}\"\n+\n+\t__git for-each-ref --format=\"${pfx//\\%/%%}%(refname:strip=2)$sfx\" \\\n+\t\t\t\"refs/tags/$cur_*\" \"refs/tags/$cur_*/**\"\n }\n \n # Lists refs from the local (by default) or from a remote repository.\n@@ -369,8 +382,8 @@ __git_tags ()\n # 2: In addition to local refs, list unique branches from refs/remotes/ for\n #    'git checkout's tracking DWIMery (optional; ignored, if set but empty).\n # 3: A prefix to be added to each listed ref (optional).\n-# 4: List only refs matching this word instead of the current word being\n-#    completed (optional; NOT ignored, if empty, but lists all refs).\n+# 4: List only refs matching this word (optional; list all refs if unset or\n+#    empty).\n # 5: A suffix to be appended to each listed ref (optional; ignored, if set\n #    but empty).\n #\n@@ -381,7 +394,8 @@ __git_refs ()\n \tlocal list_refs_from=path remote=\"${1-}\"\n \tlocal format refs\n \tlocal pfx=\"${3-}\" cur_=\"${4-$cur}\" sfx=\"${5-}\"\n-\tlocal fer_pfx=\"${pfx//\\%/%%}\"\n+\tlocal match=\"${4-}\"\n+\tlocal fer_pfx=\"${pfx//\\%/%%}\" # \"escape\" for-each-ref format specifiers\n \n \t__git_find_repo_path\n \tdir=\"$__git_repo_path\"\n@@ -409,26 +423,28 @@ __git_refs ()\n \t\t\tpfx=\"$pfx^\"\n \t\t\tfer_pfx=\"$fer_pfx^\"\n \t\t\tcur_=${cur_#^}\n+\t\t\tmatch=${match#^}\n \t\tfi\n \t\tcase \"$cur_\" in\n \t\trefs|refs/*)\n \t\t\tformat=\"refname\"\n-\t\t\trefs=(\"$cur_*\" \"$cur_*/**\")\n+\t\t\trefs=(\"$match*\" \"$match*/**\")\n \t\t\ttrack=\"\"\n \t\t\t;;\n \t\t*)\n \t\t\tfor i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do\n \t\t\t\tcase \"$i\" in\n-\t\t\t\t$cur_*)\tif [ -e \"$dir/$i\" ]; then\n+\t\t\t\t$match*)\n+\t\t\t\t\tif [ -e \"$dir/$i\" ]; then\n \t\t\t\t\t\techo \"$pfx$i$sfx\"\n \t\t\t\t\tfi\n \t\t\t\t\t;;\n \t\t\t\tesac\n \t\t\tdone\n \t\t\tformat=\"refname:strip=2\"\n-\t\t\trefs=(\"refs/tags/$cur_*\" \"refs/tags/$cur_*/**\"\n-\t\t\t\t\"refs/heads/$cur_*\" \"refs/heads/$cur_*/**\"\n-\t\t\t\t\"refs/remotes/$cur_*\" \"refs/remotes/$cur_*/**\")\n+\t\t\trefs=(\"refs/tags/$match*\" \"refs/tags/$match*/**\"\n+\t\t\t\t\"refs/heads/$match*\" \"refs/heads/$match*/**\"\n+\t\t\t\t\"refs/remotes/$match*\" \"refs/remotes/$match*/**\")\n \t\t\t;;\n \t\tesac\n \t\t__git_dir=\"$dir\" __git for-each-ref --format=\"$fer_pfx%($format)$sfx\" \\\n@@ -439,14 +455,14 @@ __git_refs ()\n \t\t\t# but only output if the branch name is unique\n \t\t\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=3)$sfx\" \\\n \t\t\t\t--sort=\"refname:strip=3\" \\\n-\t\t\t\t\"refs/remotes/*/$cur_*\" \"refs/remotes/*/$cur_*/**\" | \\\n+\t\t\t\t\"refs/remotes/*/$match*\" \"refs/remotes/*/$match*/**\" | \\\n \t\t\tuniq -u\n \t\tfi\n \t\treturn\n \tfi\n \tcase \"$cur_\" in\n \trefs|refs/*)\n-\t\t__git ls-remote \"$remote\" \"$cur_*\" | \\\n+\t\t__git ls-remote \"$remote\" \"$match*\" | \\\n \t\twhile read -r hash i; do\n \t\t\tcase \"$i\" in\n \t\t\t*^{}) ;;\n@@ -457,19 +473,19 @@ __git_refs ()\n \t*)\n \t\tif [ \"$list_refs_from\" = remote ]; then\n \t\t\tcase \"HEAD\" in\n-\t\t\t$cur_*)\techo \"${pfx}HEAD$sfx\" ;;\n+\t\t\t$match*)\techo \"${pfx}HEAD$sfx\" ;;\n \t\t\tesac\n \t\t\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=3)$sfx\" \\\n-\t\t\t\t\"refs/remotes/$remote/$cur_*\" \\\n-\t\t\t\t\"refs/remotes/$remote/$cur_*/**\"\n+\t\t\t\t\"refs/remotes/$remote/$match*\" \\\n+\t\t\t\t\"refs/remotes/$remote/$match*/**\"\n \t\telse\n \t\t\tlocal query_symref\n \t\t\tcase \"HEAD\" in\n-\t\t\t$cur_*)\tquery_symref=\"HEAD\" ;;\n+\t\t\t$match*)\tquery_symref=\"HEAD\" ;;\n \t\t\tesac\n \t\t\t__git ls-remote \"$remote\" $query_symref \\\n-\t\t\t\t\"refs/tags/$cur_*\" \"refs/heads/$cur_*\" \\\n-\t\t\t\t\"refs/remotes/$cur_*\" |\n+\t\t\t\t\"refs/tags/$match*\" \"refs/heads/$match*\" \\\n+\t\t\t\t\"refs/remotes/$match*\" |\n \t\t\twhile read -r hash i; do\n \t\t\t\tcase \"$i\" in\n \t\t\t\t*^{})\t;;\n@@ -513,6 +529,7 @@ __git_complete_refs ()\n }\n \n # __git_refs2 requires 1 argument (to pass to __git_refs)\n+# Deprecated: use __git_complete_fetch_refspecs() instead.\n __git_refs2 ()\n {\n \tlocal i\n@@ -521,6 +538,24 @@ __git_refs2 ()\n \tdone\n }\n \n+# Completes refspecs for fetching from a remote repository.\n+# 1: The remote repository.\n+# 2: A prefix to be added to each listed refspec (optional).\n+# 3: The ref to be completed as a refspec instead of the current word to be\n+#    completed (optional)\n+# 4: A suffix to be appended to each listed refspec instead of the default\n+#    space (optional).\n+__git_complete_fetch_refspecs ()\n+{\n+\tlocal i remote=\"$1\" pfx=\"${2-}\" cur_=\"${3-$cur}\" sfx=\"${4- }\"\n+\n+\t__gitcomp_direct \"$(\n+\t\tfor i in $(__git_refs \"$remote\" \"\" \"\" \"$cur_\") ; do\n+\t\t\techo \"$pfx$i:$i$sfx\"\n+\t\tdone\n+\t\t)\"\n+}\n+\n # __git_refs_remotes requires 1 argument (to pass to ls-remote)\n __git_refs_remotes ()\n {\n@@ -713,7 +748,7 @@ __git_complete_remote_or_refspec ()\n \tcase \"$cmd\" in\n \tfetch)\n \t\tif [ $lhs = 1 ]; then\n-\t\t\t__gitcomp_nl \"$(__git_refs2 \"$remote\")\" \"$pfx\" \"$cur_\"\n+\t\t\t__git_complete_fetch_refspecs \"$remote\" \"$pfx\" \"$cur_\"\n \t\telse\n \t\t\t__git_complete_refs --pfx=\"$pfx\" --cur=\"$cur_\"\n \t\tfi\n@@ -1161,7 +1196,7 @@ _git_branch ()\n \t\t;;\n \t*)\n \t\tif [ $only_local_ref = \"y\" -a $has_r = \"n\" ]; then\n-\t\t\t__gitcomp_nl \"$(__git_heads)\"\n+\t\t\t__gitcomp_direct \"$(__git_heads \"\" \"$cur\" \" \")\"\n \t\telse\n \t\t\t__git_complete_refs\n \t\tfi\n@@ -2156,7 +2191,7 @@ _git_config ()\n \t\t;;\n \tbranch.*)\n \t\tlocal pfx=\"${cur%.*}.\" cur_=\"${cur#*.}\"\n-\t\t__gitcomp_nl \"$(__git_heads)\" \"$pfx\" \"$cur_\" \".\"\n+\t\t__gitcomp_direct \"$(__git_heads \"$pfx\" \"$cur_\" \".\")\"\n \t\t__gitcomp_nl_append $'autosetupmerge\\nautosetuprebase\\n' \"$pfx\" \"$cur_\"\n \t\treturn\n \t\t;;\n@@ -2802,7 +2837,7 @@ _git_tag ()\n \t\ti=\"${words[c]}\"\n \t\tcase \"$i\" in\n \t\t-d|-v)\n-\t\t\t__gitcomp_nl \"$(__git_tags)\"\n+\t\t\t__gitcomp_direct \"$(__git_tags \"\" \"$cur\" \" \")\"\n \t\t\treturn\n \t\t\t;;\n \t\t-f)\n@@ -2817,7 +2852,7 @@ _git_tag ()\n \t\t;;\n \t-*|tag)\n \t\tif [ $f = 1 ]; then\n-\t\t\t__gitcomp_nl \"$(__git_tags)\"\n+\t\t\t__gitcomp_direct \"$(__git_tags \"\" \"$cur\" \" \")\"\n \t\tfi\n \t\t;;\n \t*)\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex be584c069..5ed28135b 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -571,6 +571,9 @@ test_expect_success '__git_refs - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/master\n \trefs/heads/matching-branch\n+\trefs/remotes/other/branch-in-other\n+\trefs/remotes/other/master-in-other\n+\trefs/tags/matching-tag\n \tEOF\n \t(\n \t\tcur=refs/heads/ &&\n@@ -636,6 +639,7 @@ test_expect_success '__git_refs - configured remote' '\n \n test_expect_success '__git_refs - configured remote - full refs' '\n \tcat >expected <<-EOF &&\n+\tHEAD\n \trefs/heads/branch-in-other\n \trefs/heads/master-in-other\n \trefs/tags/tag-in-other\n@@ -664,6 +668,7 @@ test_expect_success '__git_refs - configured remote - repo given on the command\n \n test_expect_success '__git_refs - configured remote - full refs - repo given on the command line' '\n \tcat >expected <<-EOF &&\n+\tHEAD\n \trefs/heads/branch-in-other\n \trefs/heads/master-in-other\n \trefs/tags/tag-in-other\n@@ -708,6 +713,7 @@ test_expect_success '__git_refs - URL remote' '\n \n test_expect_success '__git_refs - URL remote - full refs' '\n \tcat >expected <<-EOF &&\n+\tHEAD\n \trefs/heads/branch-in-other\n \trefs/heads/master-in-other\n \trefs/tags/tag-in-other\n@@ -861,6 +867,25 @@ test_expect_success 'setup for filtering matching refs' '\n \trm -f .git/FETCH_HEAD\n '\n \n+test_expect_success '__git_refs - dont filter refs unless told so' '\n+\tcat >expected <<-EOF &&\n+\tHEAD\n+\tmaster\n+\tmatching-branch\n+\tmatching/branch\n+\tother/branch-in-other\n+\tother/master-in-other\n+\tother/matching/branch-in-other\n+\tmatching-tag\n+\tmatching/tag\n+\tEOF\n+\t(\n+\t\tcur=master &&\n+\t\t__git_refs >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n test_expect_success '__git_refs - only matching refs' '\n \tcat >expected <<-EOF &&\n \tmatching-branch\n@@ -870,7 +895,7 @@ test_expect_success '__git_refs - only matching refs' '\n \tEOF\n \t(\n \t\tcur=mat &&\n-\t\t__git_refs >\"$actual\"\n+\t\t__git_refs \"\" \"\" \"\" \"$cur\" >\"$actual\"\n \t) &&\n \ttest_cmp expected \"$actual\"\n '\n@@ -882,7 +907,7 @@ test_expect_success '__git_refs - only matching refs - full refs' '\n \tEOF\n \t(\n \t\tcur=refs/heads/mat &&\n-\t\t__git_refs >\"$actual\"\n+\t\t__git_refs \"\" \"\" \"\" \"$cur\" >\"$actual\"\n \t) &&\n \ttest_cmp expected \"$actual\"\n '\n@@ -894,7 +919,7 @@ test_expect_success '__git_refs - only matching refs - remote on local file syst\n \tEOF\n \t(\n \t\tcur=ma &&\n-\t\t__git_refs otherrepo >\"$actual\"\n+\t\t__git_refs otherrepo \"\" \"\" \"$cur\" >\"$actual\"\n \t) &&\n \ttest_cmp expected \"$actual\"\n '\n@@ -906,7 +931,7 @@ test_expect_success '__git_refs - only matching refs - configured remote' '\n \tEOF\n \t(\n \t\tcur=ma &&\n-\t\t__git_refs other >\"$actual\"\n+\t\t__git_refs other \"\" \"\" \"$cur\" >\"$actual\"\n \t) &&\n \ttest_cmp expected \"$actual\"\n '\n@@ -918,7 +943,7 @@ test_expect_success '__git_refs - only matching refs - remote - full refs' '\n \tEOF\n \t(\n \t\tcur=refs/heads/ma &&\n-\t\t__git_refs other >\"$actual\"\n+\t\t__git_refs other \"\" \"\" \"$cur\" >\"$actual\"\n \t) &&\n \ttest_cmp expected \"$actual\"\n '\n@@ -940,7 +965,7 @@ test_expect_success '__git_refs - only matching refs - checkout DWIMery' '\n \tdone &&\n \t(\n \t\tcur=mat &&\n-\t\t__git_refs \"\" 1 >\"$actual\"\n+\t\t__git_refs \"\" 1 \"\" \"$cur\" >\"$actual\"\n \t) &&\n \ttest_cmp expected \"$actual\"\n '\n@@ -948,7 +973,8 @@ test_expect_success '__git_refs - only matching refs - checkout DWIMery' '\n test_expect_success 'teardown after filtering matching refs' '\n \tgit branch -d matching/branch &&\n \tgit tag -d matching/tag &&\n-\tgit update-ref -d refs/remotes/other/matching/branch-in-other\n+\tgit update-ref -d refs/remotes/other/matching/branch-in-other &&\n+\tgit -C otherrepo branch -D matching/branch-in-other\n '\n \n test_expect_success '__git_refs - for-each-ref format specifiers in prefix' '\n@@ -963,7 +989,7 @@ test_expect_success '__git_refs - for-each-ref format specifiers in prefix' '\n '\n \n test_expect_success '__git_complete_refs - simple' '\n-\tsed -e \"s/Z$//g\" >expected <<-EOF &&\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n \tmaster Z\n \tmatching-branch Z\n@@ -980,7 +1006,7 @@ test_expect_success '__git_complete_refs - simple' '\n '\n \n test_expect_success '__git_complete_refs - matching' '\n-\tsed -e \"s/Z$//g\" >expected <<-EOF &&\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tmatching-branch Z\n \tmatching-tag Z\n \tEOF\n@@ -993,7 +1019,7 @@ test_expect_success '__git_complete_refs - matching' '\n '\n \n test_expect_success '__git_complete_refs - remote' '\n-\tsed -e \"s/Z$//g\" >expected <<-EOF &&\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n \tbranch-in-other Z\n \tmaster-in-other Z\n@@ -1007,7 +1033,7 @@ test_expect_success '__git_complete_refs - remote' '\n '\n \n test_expect_success '__git_complete_refs - track' '\n-\tsed -e \"s/Z$//g\" >expected <<-EOF &&\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n \tmaster Z\n \tmatching-branch Z\n@@ -1026,7 +1052,7 @@ test_expect_success '__git_complete_refs - track' '\n '\n \n test_expect_success '__git_complete_refs - current word' '\n-\tsed -e \"s/Z$//g\" >expected <<-EOF &&\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tmatching-branch Z\n \tmatching-tag Z\n \tEOF\n@@ -1039,7 +1065,7 @@ test_expect_success '__git_complete_refs - current word' '\n '\n \n test_expect_success '__git_complete_refs - prefix' '\n-\tsed -e \"s/Z$//g\" >expected <<-EOF &&\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tv1.0..matching-branch Z\n \tv1.0..matching-tag Z\n \tEOF\n@@ -1068,6 +1094,74 @@ test_expect_success '__git_complete_refs - suffix' '\n \ttest_cmp expected out\n '\n \n+test_expect_success '__git_complete_fetch_refspecs - simple' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tHEAD:HEAD Z\n+\tbranch-in-other:branch-in-other Z\n+\tmaster-in-other:master-in-other Z\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_complete_fetch_refspecs other &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n+test_expect_success '__git_complete_fetch_refspecs - matching' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tbranch-in-other:branch-in-other Z\n+\tEOF\n+\t(\n+\t\tcur=br &&\n+\t\t__git_complete_fetch_refspecs other \"\" br &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n+test_expect_success '__git_complete_fetch_refspecs - prefix' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\t+HEAD:HEAD Z\n+\t+branch-in-other:branch-in-other Z\n+\t+master-in-other:master-in-other Z\n+\tEOF\n+\t(\n+\t\tcur=\"+\" &&\n+\t\t__git_complete_fetch_refspecs other \"+\" \"\"  &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n+test_expect_success '__git_complete_fetch_refspecs - fully qualified' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\trefs/heads/branch-in-other:refs/heads/branch-in-other Z\n+\trefs/heads/master-in-other:refs/heads/master-in-other Z\n+\trefs/tags/tag-in-other:refs/tags/tag-in-other Z\n+\tEOF\n+\t(\n+\t\tcur=refs/ &&\n+\t\t__git_complete_fetch_refspecs other \"\" refs/ &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n+test_expect_success '__git_complete_fetch_refspecs - fully qualified & prefix' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\t+refs/heads/branch-in-other:refs/heads/branch-in-other Z\n+\t+refs/heads/master-in-other:refs/heads/master-in-other Z\n+\t+refs/tags/tag-in-other:refs/tags/tag-in-other Z\n+\tEOF\n+\t(\n+\t\tcur=+refs/ &&\n+\t\t__git_complete_fetch_refspecs other + refs/ &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n test_expect_success 'teardown after ref completion' '\n \tgit branch -d matching-branch &&\n \tgit tag -d matching-tag &&\n"},{"id":"315093","messageId":"20170323152924.23944-4-szeder.dev@gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"[PATCHv2 03/14] completion: support completing full refs after '--option=refs/<TAB>'","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:13Z","receivedAt":"2017-03-23T15:30:47Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"Completing full refs currently only works when the full ref stands on\nin its own on the command line, but doesn't work when the current word\nto be completed contains a prefix before the full ref, e.g.\n'--option=refs/<TAB>' or 'master..refs/bis<TAB>'.\n\nThe reason is that __git_refs() looks at the current word to be\ncompleted ($cur) as a whole to decide whether it has to list full (if\nit starts with 'refs/') or short refs (otherwise).  However, $cur also\nholds said '--option=' or 'master..' prefixes, which of course throw\noff this decision.  Luckily, the default action is to list short refs,\nthat's why completing short refs happens to work even after a\n'master..<TAB>' prefix and similar cases.\n\nPass only the ref part of the current word to be completed to\n__git_refs() as a new positional parameter, so it can make the right\ndecision even if the whole current word contains some kind of a\nprefix.\n\nMake this new parameter the 4. positional parameter and leave the 3.\nas an ignored placeholder for now (it will be used later in this patch\nseries).\n\nSigned-off-by: SZEDER Gábor <szeder.dev@gmail.com>\n---\n contrib/completion/git-completion.bash | 21 ++++++++++++++-------\n t/t9902-completion.sh                  | 31 +++++++++++++++++++++++++++++++\n 2 files changed, 45 insertions(+), 7 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 0b90cfa54..b897cba4b 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -354,6 +354,8 @@ __git_tags ()\n #    Can be the name of a configured remote, a path, or a URL.\n # 2: In addition to local refs, list unique branches from refs/remotes/ for\n #    'git checkout's tracking DWIMery (optional; ignored, if set but empty).\n+# 3: Currently ignored.\n+# 4: The current ref to be completed (optional).\n #\n # Use __git_complete_refs() instead.\n __git_refs ()\n@@ -361,6 +363,7 @@ __git_refs ()\n \tlocal i hash dir track=\"${2-}\"\n \tlocal list_refs_from=path remote=\"${1-}\"\n \tlocal format refs pfx\n+\tlocal cur_=\"${4-$cur}\"\n \n \t__git_find_repo_path\n \tdir=\"$__git_repo_path\"\n@@ -384,14 +387,17 @@ __git_refs ()\n \tfi\n \n \tif [ \"$list_refs_from\" = path ]; then\n-\t\tcase \"$cur\" in\n+\t\tcase \"$cur_\" in\n \t\trefs|refs/*)\n \t\t\tformat=\"refname\"\n-\t\t\trefs=\"${cur%/*}\"\n+\t\t\trefs=\"${cur_%/*}\"\n \t\t\ttrack=\"\"\n \t\t\t;;\n \t\t*)\n-\t\t\t[[ \"$cur\" == ^* ]] && pfx=\"^\"\n+\t\t\tif [[ \"$cur_\" == ^* ]]; then\n+\t\t\t\tpfx=\"^\"\n+\t\t\t\tcur_=${cur_#^}\n+\t\t\tfi\n \t\t\tfor i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do\n \t\t\t\tif [ -e \"$dir/$i\" ]; then echo $pfx$i; fi\n \t\t\tdone\n@@ -411,16 +417,16 @@ __git_refs ()\n \t\t\twhile read -r entry; do\n \t\t\t\teval \"$entry\"\n \t\t\t\tref=\"${ref#*/}\"\n-\t\t\t\tif [[ \"$ref\" == \"$cur\"* ]]; then\n+\t\t\t\tif [[ \"$ref\" == \"$cur_\"* ]]; then\n \t\t\t\t\techo \"$ref\"\n \t\t\t\tfi\n \t\t\tdone | sort | uniq -u\n \t\tfi\n \t\treturn\n \tfi\n-\tcase \"$cur\" in\n+\tcase \"$cur_\" in\n \trefs|refs/*)\n-\t\t__git ls-remote \"$remote\" \"$cur*\" | \\\n+\t\t__git ls-remote \"$remote\" \"$cur_*\" | \\\n \t\twhile read -r hash i; do\n \t\t\tcase \"$i\" in\n \t\t\t*^{}) ;;\n@@ -475,7 +481,8 @@ __git_complete_refs ()\n \t\tshift\n \tdone\n \n-\t__gitcomp_nl \"$(__git_refs \"$remote\" \"$track\")\" \"$pfx\" \"$cur_\" \"$sfx\"\n+\t__gitcomp_nl \"$(__git_refs \"$remote\" \"$track\" \"\" \"$cur_\")\" \\\n+\t\t\"$pfx\" \"$cur_\" \"$sfx\"\n }\n \n # __git_refs2 requires 1 argument (to pass to __git_refs)\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 4e7f3076f..0a41ee1ea 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -775,6 +775,37 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \ttest_cmp expected \"$actual\"\n '\n \n+test_expect_success '__git_refs - after --opt=' '\n+\tcat >expected <<-EOF &&\n+\tHEAD\n+\tmaster\n+\tmatching-branch\n+\tother/branch-in-other\n+\tother/master-in-other\n+\tmatching-tag\n+\tEOF\n+\t(\n+\t\tcur=\"--opt=\" &&\n+\t\t__git_refs \"\" \"\" \"\" \"\" >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_refs - after --opt= - full refs' '\n+\tcat >expected <<-EOF &&\n+\trefs/heads/master\n+\trefs/heads/matching-branch\n+\trefs/remotes/other/branch-in-other\n+\trefs/remotes/other/master-in-other\n+\trefs/tags/matching-tag\n+\tEOF\n+\t(\n+\t\tcur=\"--opt=refs/\" &&\n+\t\t__git_refs \"\" \"\" \"\" refs/ >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n test_expect_success '__git_complete_refs - simple' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n-- \n2.12.1.485.g1616aa492\n\n"},{"id":"315094","messageId":"20170323152924.23944-3-szeder.dev@gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"[PATCHv2 02/14] completion: wrap __git_refs() for better option parsing","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:12Z","receivedAt":"2017-03-23T15:30:48Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"__git_refs() currently accepts two optional positional parameters: a\nremote and a flag for 'git checkout's tracking DWIMery.  To fix a\nminor bug, and, more importantly, for faster refs completion, this\nseries will add three more parameters: a prefix, the current word to\nbe completed and a suffix, i.e. the options accepted by __gitcomp() &\nfriends, and will change __git_refs() to list only refs matching that\ngiven current word and to add that given prefix and suffix to the\nlisted refs.\n\nHowever, __git_refs() is the helper function that is most likely used\nin users' custom completion scriptlets for their own git commands, and\nwe don't want to break those, so\n\n  - we can't change __git_refs()'s default output format, i.e. we\n    can't by default append a trailing space to every listed ref,\n    meaning that the suffix parameter containing the default trailing\n    space would have to be specified on every invocation, and\n\n  - we can't change the position of existing positional parameters\n    either, so there would have to be plenty of set-but-empty\n    placeholder positional parameters all over the completion script.\n\nFurthermore, with five positional parameters it would be really hard\nto remember which position means what.\n\nTo keep callsites simple, add the new wrapper function\n__git_complete_refs() around __git_refs(), which:\n\n  - instead of positional parameters accepts real '--opt=val'-style\n    options and with minimalistic option parsing translates them to\n    __git_refs()'s and __gitcomp_nl()'s positional parameters, and\n\n  - includes the '__gitcomp_nl \"$(__git_refs ...)\" ...' command\n    substitution to make its behavior match its name and the behavior\n    of other __git_complete_* functions, and to limit future changes\n    in this series to __git_refs() and this new wrapper function.\n\nCall this wrapper function instead of __git_refs() wherever possible\nthroughout the completion script, i.e. when __git_refs()'s output is\nfed to __gitcomp_nl() right away without further processing, which\nmeans all callsites except a single one in the __git_refs2() helper.\n\nSigned-off-by: SZEDER Gábor <szeder.dev@gmail.com>\n---\n contrib/completion/git-completion.bash | 102 ++++++++++++++++++++-----------\n t/t9902-completion.sh                  | 106 +++++++++++++++++++++++++++++++++\n 2 files changed, 173 insertions(+), 35 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 91fda7ffb..0b90cfa54 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -354,6 +354,8 @@ __git_tags ()\n #    Can be the name of a configured remote, a path, or a URL.\n # 2: In addition to local refs, list unique branches from refs/remotes/ for\n #    'git checkout's tracking DWIMery (optional; ignored, if set but empty).\n+#\n+# Use __git_complete_refs() instead.\n __git_refs ()\n {\n \tlocal i hash dir track=\"${2-}\"\n@@ -446,6 +448,36 @@ __git_refs ()\n \tesac\n }\n \n+# Completes refs, short and long, local and remote, symbolic and pseudo.\n+#\n+# Usage: __git_complete_refs [<option>]...\n+# --remote=<remote>: The remote to list refs from, can be the name of a\n+#                    configured remote, a path, or a URL.\n+# --track: List unique remote branches for 'git checkout's tracking DWIMery.\n+# --pfx=<prefix>: A prefix to be added to each ref.\n+# --cur=<word>: The current ref to be completed.  Defaults to the current\n+#               word to be completed.\n+# --sfx=<suffix>: A suffix to be appended to each ref instead of the default\n+#                 space.\n+__git_complete_refs ()\n+{\n+\tlocal remote track pfx cur_=\"$cur\" sfx=\" \"\n+\n+\twhile test $# != 0; do\n+\t\tcase \"$1\" in\n+\t\t--remote=*)\tremote=\"${1##--remote=}\" ;;\n+\t\t--track)\ttrack=\"yes\" ;;\n+\t\t--pfx=*)\tpfx=\"${1##--pfx=}\" ;;\n+\t\t--cur=*)\tcur_=\"${1##--cur=}\" ;;\n+\t\t--sfx=*)\tsfx=\"${1##--sfx=}\" ;;\n+\t\t*)\t\treturn 1 ;;\n+\t\tesac\n+\t\tshift\n+\tdone\n+\n+\t__gitcomp_nl \"$(__git_refs \"$remote\" \"$track\")\" \"$pfx\" \"$cur_\" \"$sfx\"\n+}\n+\n # __git_refs2 requires 1 argument (to pass to __git_refs)\n __git_refs2 ()\n {\n@@ -554,15 +586,15 @@ __git_complete_revlist_file ()\n \t*...*)\n \t\tpfx=\"${cur_%...*}...\"\n \t\tcur_=\"${cur_#*...}\"\n-\t\t__gitcomp_nl \"$(__git_refs)\" \"$pfx\" \"$cur_\"\n+\t\t__git_complete_refs --pfx=\"$pfx\" --cur=\"$cur_\"\n \t\t;;\n \t*..*)\n \t\tpfx=\"${cur_%..*}..\"\n \t\tcur_=\"${cur_#*..}\"\n-\t\t__gitcomp_nl \"$(__git_refs)\" \"$pfx\" \"$cur_\"\n+\t\t__git_complete_refs --pfx=\"$pfx\" --cur=\"$cur_\"\n \t\t;;\n \t*)\n-\t\t__gitcomp_nl \"$(__git_refs)\"\n+\t\t__git_complete_refs\n \t\t;;\n \tesac\n }\n@@ -649,21 +681,21 @@ __git_complete_remote_or_refspec ()\n \t\tif [ $lhs = 1 ]; then\n \t\t\t__gitcomp_nl \"$(__git_refs2 \"$remote\")\" \"$pfx\" \"$cur_\"\n \t\telse\n-\t\t\t__gitcomp_nl \"$(__git_refs)\" \"$pfx\" \"$cur_\"\n+\t\t\t__git_complete_refs --pfx=\"$pfx\" --cur=\"$cur_\"\n \t\tfi\n \t\t;;\n \tpull|remote)\n \t\tif [ $lhs = 1 ]; then\n-\t\t\t__gitcomp_nl \"$(__git_refs \"$remote\")\" \"$pfx\" \"$cur_\"\n+\t\t\t__git_complete_refs --remote=\"$remote\" --pfx=\"$pfx\" --cur=\"$cur_\"\n \t\telse\n-\t\t\t__gitcomp_nl \"$(__git_refs)\" \"$pfx\" \"$cur_\"\n+\t\t\t__git_complete_refs --pfx=\"$pfx\" --cur=\"$cur_\"\n \t\tfi\n \t\t;;\n \tpush)\n \t\tif [ $lhs = 1 ]; then\n-\t\t\t__gitcomp_nl \"$(__git_refs)\" \"$pfx\" \"$cur_\"\n+\t\t\t__git_complete_refs --pfx=\"$pfx\" --cur=\"$cur_\"\n \t\telse\n-\t\t\t__gitcomp_nl \"$(__git_refs \"$remote\")\" \"$pfx\" \"$cur_\"\n+\t\t\t__git_complete_refs --remote=\"$remote\" --pfx=\"$pfx\" --cur=\"$cur_\"\n \t\tfi\n \t\t;;\n \tesac\n@@ -1061,7 +1093,7 @@ _git_bisect ()\n \n \tcase \"$subcommand\" in\n \tbad|good|reset|skip|start)\n-\t\t__gitcomp_nl \"$(__git_refs)\"\n+\t\t__git_complete_refs\n \t\t;;\n \t*)\n \t\t;;\n@@ -1083,7 +1115,7 @@ _git_branch ()\n \n \tcase \"$cur\" in\n \t--set-upstream-to=*)\n-\t\t__gitcomp_nl \"$(__git_refs)\" \"\" \"${cur##--set-upstream-to=}\"\n+\t\t__git_complete_refs --cur=\"${cur##--set-upstream-to=}\"\n \t\t;;\n \t--*)\n \t\t__gitcomp \"\n@@ -1097,7 +1129,7 @@ _git_branch ()\n \t\tif [ $only_local_ref = \"y\" -a $has_r = \"n\" ]; then\n \t\t\t__gitcomp_nl \"$(__git_heads)\"\n \t\telse\n-\t\t\t__gitcomp_nl \"$(__git_refs)\"\n+\t\t\t__git_complete_refs\n \t\tfi\n \t\t;;\n \tesac\n@@ -1140,18 +1172,18 @@ _git_checkout ()\n \t*)\n \t\t# check if --track, --no-track, or --no-guess was specified\n \t\t# if so, disable DWIM mode\n-\t\tlocal flags=\"--track --no-track --no-guess\" track=1\n+\t\tlocal flags=\"--track --no-track --no-guess\" track_opt=\"--track\"\n \t\tif [ -n \"$(__git_find_on_cmdline \"$flags\")\" ]; then\n-\t\t\ttrack=''\n+\t\t\ttrack_opt=''\n \t\tfi\n-\t\t__gitcomp_nl \"$(__git_refs '' $track)\"\n+\t\t__git_complete_refs $track_opt\n \t\t;;\n \tesac\n }\n \n _git_cherry ()\n {\n-\t__gitcomp_nl \"$(__git_refs)\"\n+\t__git_complete_refs\n }\n \n _git_cherry_pick ()\n@@ -1166,7 +1198,7 @@ _git_cherry_pick ()\n \t\t__gitcomp \"--edit --no-commit --signoff --strategy= --mainline\"\n \t\t;;\n \t*)\n-\t\t__gitcomp_nl \"$(__git_refs)\"\n+\t\t__git_complete_refs\n \t\t;;\n \tesac\n }\n@@ -1216,7 +1248,7 @@ _git_commit ()\n {\n \tcase \"$prev\" in\n \t-c|-C)\n-\t\t__gitcomp_nl \"$(__git_refs)\"\n+\t\t__git_complete_refs\n \t\treturn\n \t\t;;\n \tesac\n@@ -1229,7 +1261,7 @@ _git_commit ()\n \t\t;;\n \t--reuse-message=*|--reedit-message=*|\\\n \t--fixup=*|--squash=*)\n-\t\t__gitcomp_nl \"$(__git_refs)\" \"\" \"${cur#*=}\"\n+\t\t__git_complete_refs --cur=\"${cur#*=}\"\n \t\treturn\n \t\t;;\n \t--untracked-files=*)\n@@ -1267,7 +1299,7 @@ _git_describe ()\n \t\t\t\"\n \t\treturn\n \tesac\n-\t__gitcomp_nl \"$(__git_refs)\"\n+\t__git_complete_refs\n }\n \n __git_diff_algorithms=\"myers minimal patience histogram\"\n@@ -1453,7 +1485,7 @@ _git_grep ()\n \t\t;;\n \tesac\n \n-\t__gitcomp_nl \"$(__git_refs)\"\n+\t__git_complete_refs\n }\n \n _git_help ()\n@@ -1621,7 +1653,7 @@ _git_merge ()\n \t\t\t--rerere-autoupdate --no-rerere-autoupdate --abort --continue\"\n \t\treturn\n \tesac\n-\t__gitcomp_nl \"$(__git_refs)\"\n+\t__git_complete_refs\n }\n \n _git_mergetool ()\n@@ -1646,7 +1678,7 @@ _git_merge_base ()\n \t\treturn\n \t\t;;\n \tesac\n-\t__gitcomp_nl \"$(__git_refs)\"\n+\t__git_complete_refs\n }\n \n _git_mv ()\n@@ -1684,7 +1716,7 @@ _git_notes ()\n \t,*)\n \t\tcase \"$prev\" in\n \t\t--ref)\n-\t\t\t__gitcomp_nl \"$(__git_refs)\"\n+\t\t\t__git_complete_refs\n \t\t\t;;\n \t\t*)\n \t\t\t__gitcomp \"$subcommands --ref\"\n@@ -1693,7 +1725,7 @@ _git_notes ()\n \t\t;;\n \tadd,--reuse-message=*|append,--reuse-message=*|\\\n \tadd,--reedit-message=*|append,--reedit-message=*)\n-\t\t__gitcomp_nl \"$(__git_refs)\" \"\" \"${cur#*=}\"\n+\t\t__git_complete_refs --cur=\"${cur#*=}\"\n \t\t;;\n \tadd,--*|append,--*)\n \t\t__gitcomp '--file= --message= --reedit-message=\n@@ -1712,7 +1744,7 @@ _git_notes ()\n \t\t-m|-F)\n \t\t\t;;\n \t\t*)\n-\t\t\t__gitcomp_nl \"$(__git_refs)\"\n+\t\t\t__git_complete_refs\n \t\t\t;;\n \t\tesac\n \t\t;;\n@@ -1750,10 +1782,10 @@ __git_complete_force_with_lease ()\n \t--*=)\n \t\t;;\n \t*:*)\n-\t\t__gitcomp_nl \"$(__git_refs)\" \"\" \"${cur_#*:}\"\n+\t\t__git_complete_refs --cur=\"${cur_#*:}\"\n \t\t;;\n \t*)\n-\t\t__gitcomp_nl \"$(__git_refs)\" \"\" \"$cur_\"\n+\t\t__git_complete_refs --cur=\"$cur_\"\n \t\t;;\n \tesac\n }\n@@ -1829,7 +1861,7 @@ _git_rebase ()\n \n \t\treturn\n \tesac\n-\t__gitcomp_nl \"$(__git_refs)\"\n+\t__git_complete_refs\n }\n \n _git_reflog ()\n@@ -1840,7 +1872,7 @@ _git_reflog ()\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\n \telse\n-\t\t__gitcomp_nl \"$(__git_refs)\"\n+\t\t__git_complete_refs\n \tfi\n }\n \n@@ -1986,7 +2018,7 @@ _git_config ()\n \t\treturn\n \t\t;;\n \tbranch.*.merge)\n-\t\t__gitcomp_nl \"$(__git_refs)\"\n+\t\t__git_complete_refs\n \t\treturn\n \t\t;;\n \tbranch.*.rebase)\n@@ -2460,7 +2492,7 @@ _git_remote ()\n \n _git_replace ()\n {\n-\t__gitcomp_nl \"$(__git_refs)\"\n+\t__git_complete_refs\n }\n \n _git_reset ()\n@@ -2473,7 +2505,7 @@ _git_reset ()\n \t\treturn\n \t\t;;\n \tesac\n-\t__gitcomp_nl \"$(__git_refs)\"\n+\t__git_complete_refs\n }\n \n _git_revert ()\n@@ -2489,7 +2521,7 @@ _git_revert ()\n \t\treturn\n \t\t;;\n \tesac\n-\t__gitcomp_nl \"$(__git_refs)\"\n+\t__git_complete_refs\n }\n \n _git_rm ()\n@@ -2597,7 +2629,7 @@ _git_stash ()\n \t\t\t;;\n \t\tbranch,*)\n \t\t\tif [ $cword -eq 3 ]; then\n-\t\t\t\t__gitcomp_nl \"$(__git_refs)\";\n+\t\t\t\t__git_complete_refs\n \t\t\telse\n \t\t\t\t__gitcomp_nl \"$(__git stash list \\\n \t\t\t\t\t\t| sed -n -e 's/:.*//p')\"\n@@ -2755,7 +2787,7 @@ _git_tag ()\n \t\tfi\n \t\t;;\n \t*)\n-\t\t__gitcomp_nl \"$(__git_refs)\"\n+\t\t__git_complete_refs\n \t\t;;\n \tesac\n \ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex d711bef24..4e7f3076f 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -775,6 +775,112 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \ttest_cmp expected \"$actual\"\n '\n \n+test_expect_success '__git_complete_refs - simple' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tHEAD Z\n+\tmaster Z\n+\tmatching-branch Z\n+\tother/branch-in-other Z\n+\tother/master-in-other Z\n+\tmatching-tag Z\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_complete_refs &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n+test_expect_success '__git_complete_refs - matching' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tmatching-branch Z\n+\tmatching-tag Z\n+\tEOF\n+\t(\n+\t\tcur=mat &&\n+\t\t__git_complete_refs &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n+test_expect_success '__git_complete_refs - remote' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tHEAD Z\n+\tbranch-in-other Z\n+\tmaster-in-other Z\n+\tEOF\n+\t(\n+\t\tcur=\n+\t\t__git_complete_refs --remote=other &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n+test_expect_success '__git_complete_refs - track' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tHEAD Z\n+\tmaster Z\n+\tmatching-branch Z\n+\tother/branch-in-other Z\n+\tother/master-in-other Z\n+\tmatching-tag Z\n+\tbranch-in-other Z\n+\tmaster-in-other Z\n+\tEOF\n+\t(\n+\t\tcur=\n+\t\t__git_complete_refs --track &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n+test_expect_success '__git_complete_refs - current word' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tmatching-branch Z\n+\tmatching-tag Z\n+\tEOF\n+\t(\n+\t\tcur=\"--option=mat\" &&\n+\t\t__git_complete_refs --cur=\"${cur#*=}\" &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n+test_expect_success '__git_complete_refs - prefix' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tv1.0..matching-branch Z\n+\tv1.0..matching-tag Z\n+\tEOF\n+\t(\n+\t\tcur=v1.0..mat &&\n+\t\t__git_complete_refs --pfx=v1.0.. --cur=mat &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n+test_expect_success '__git_complete_refs - suffix' '\n+\tcat >expected <<-EOF &&\n+\tHEAD.\n+\tmaster.\n+\tmatching-branch.\n+\tother/branch-in-other.\n+\tother/master-in-other.\n+\tmatching-tag.\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_complete_refs --sfx=. &&\n+\t\tprint_comp\n+\t) &&\n+\ttest_cmp expected out\n+'\n+\n test_expect_success 'teardown after ref completion' '\n \tgit branch -d matching-branch &&\n \tgit tag -d matching-tag &&\n-- \n2.12.1.485.g1616aa492\n\n"},{"id":"315095","messageId":"20170323152924.23944-7-szeder.dev@gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"[PATCHv2 06/14] completion: don't disambiguate tags and branches","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:29:16Z","receivedAt":"2017-03-23T15:30:51Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"When the completion script has to list only tags or only branches, it\nuses the 'git for-each-ref' format 'refname:short', which makes sure\nthat all listed tags and branches are unambiguous.  However,\ndisambiguating tags and branches in these cases is wrong, because:\n\n  - __git_tags(), the helper function listing possible tagname\n    arguments for 'git tag', lists an ambiguous tag\n    'refs/tags/ambiguous' as 'tags/ambiguous'.  Its only consumer,\n    'git tag' expects its tagname argument to be under 'refs/tags/',\n    thus it interprets that abgiguous tag as\n    'refs/tags/tags/ambiguous'.  Clearly wrong.\n\n  - __git_heads() lists possible branchname arguments for 'git branch'\n    and possible 'branch.<branchname>' configuration subsections.\n    Both of these expect branchnames to be under 'refs/heads/' and\n    misinterpret a disambiguated branchname like 'heads/ambiguous'.\n\nFurthermore, disambiguation involves several stat() syscalls for each\ntag or branch, thus comes at a steep cost especially on Windows and/or\nwhen there are a lot of tags or branches to be listed.\n\nUse the 'git for-each-ref' format 'refname:strip=2' instead of\n'refname:short' to avoid harmful disambiguation of tags and branches\nin __git_tags() and __git_heads().\n\nSigned-off-by: SZEDER Gábor <szeder.dev@gmail.com>\n---\n contrib/completion/git-completion.bash | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 882635f97..e129f674e 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -340,12 +340,12 @@ __git_index_files ()\n \n __git_heads ()\n {\n-\t__git for-each-ref --format='%(refname:short)' refs/heads\n+\t__git for-each-ref --format='%(refname:strip=2)' refs/heads\n }\n \n __git_tags ()\n {\n-\t__git for-each-ref --format='%(refname:short)' refs/tags\n+\t__git for-each-ref --format='%(refname:strip=2)' refs/tags\n }\n \n # Lists refs from the local (by default) or from a remote repository.\n-- \n2.12.1.485.g1616aa492\n\n"},{"id":"315096","messageId":"CAM0VKj=DgyctKB+m8qh=VG7fDJDf-d-95twqiZD0R1zXgSDJKg@mail.gmail.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"Re: [PATCHv2 00/14] completion: speed up refs completion","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-23T15:33:17Z","receivedAt":"2017-03-23T15:33:22Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Thu, Mar 23, 2017 at 4:29 PM, SZEDER Gábor <szeder.dev@gmail.com> wrote:\n> This series is the updated version of 'sg/completion-refs-speedup'.\n\nForgot to mention that v1 can be found here:\nhttp://public-inbox.org/git/20170203025405.8242-1-szeder.dev@gmail.com/T/\n\nGábor\n"},{"id":"315144","messageId":"xmqqshm3dezf.fsf@gitster.mtv.corp.google.com","threadId":"45480","inReplyTo":"20170323152924.23944-1-szeder.dev@gmail.com","subject":"Re: [PATCHv2 00/14] completion: speed up refs completion","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-23T18:28:52Z","receivedAt":"2017-03-23T18:29:07Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"SZEDER Gábor <szeder.dev@gmail.com> writes:\n\n> This series is the updated version of 'sg/completion-refs-speedup'.\n> It speeds up refs completion for large number of refs, partly by\n> giving up disambiguating ambiguous refs and partly by eliminating most\n> of the shell processing between 'git for-each-ref' and 'ls-remote' and\n> Bash's completion facility.  The rest is a bit of preparatory\n> reorganization, cleanup and bugfixes.\n>\n> Changes since v1:\n> ...\n> [1] - http://public-inbox.org/git/20170206181545.12869-1-szeder.dev@gmail.com/\n\nIt seems Jacob Keller was the only person who was excited about\nthese changes when v1 was posted?  It would be nice to see a bit\nmore enthusiasm from other folks who are invested in the completion\nscript, but you are the de-facto go-to person on the completion\nalready, so ... ;-)\n\nWill replace.  Let's advance this to 'next' soonish (say, by early\nnext week).\n\nThanks.\n\n\n"},{"id":"315326","messageId":"20170324193149.pa4lrsodgmgsvidl@sigill.intra.peff.net","threadId":"45480","inReplyTo":"20170323152924.23944-8-szeder.dev@gmail.com","subject":"Re: [PATCHv2 07/14] completion: don't disambiguate short refs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-24T19:31:50Z","receivedAt":"2017-03-24T19:38:36Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Mar 23, 2017 at 04:29:17PM +0100, SZEDER Gábor wrote:\n\n> However, it's questionable whether ambiguous refs are really that bad\n> to justify that much extra cost:\n\nIt's not clear to me that the existing completion actually does a good\njob with disambiguation anyway.\n\nIf I have a tag and a branch named \"foo\", then in theory doing:\n\n  git log fo<Tab>\n\nshould present me with \"heads/foo\" and \"tags/foo\" as options. But it\ndoesn't seem to; it just completes \"foo\".\n\nBut even if it did, those don't _start_ with foo, I have to go to some\nwork to back up anyway. I think we are better off just completing \"foo\"\nand letting the command complain that it's ambiguous.\n\nSo even leaving aside the performance tradeoff, that seems like a more\nsensible behavior anyway. And AFAICT, that's the behavior you'd get with\nyour patch (we'd get two \"foo\"s, but the completion is presumably smart\nenough to handle that.\n\n> This speeds up refs completion considerably.  Uniquely completing a\n> branch in a repository with 100k local branches, all packed, best of\n> five:\n> \n>   On Linux, before:\n> \n>     $ time __git_complete_refs --cur=maste\n> \n>     real    0m1.662s\n>     user    0m1.368s\n>     sys     0m0.296s\n> \n>   After:\n> \n>     real    0m0.831s\n>     user    0m0.808s\n>     sys     0m0.028s\n\nThis is nice, though I think faster ref storage is another way to attack\nthe problem. This is much simpler, though. :)\n\n-Peff\n"},{"id":"315327","messageId":"20170324194237.td2nsiyuatexuxo2@sigill.intra.peff.net","threadId":"45480","inReplyTo":"20170323152924.23944-9-szeder.dev@gmail.com","subject":"Re: [PATCHv2 08/14] completion: let 'for-each-ref' and 'ls-remote' filter matching refs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-24T19:42:37Z","receivedAt":"2017-03-24T19:42:46Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Mar 23, 2017 at 04:29:18PM +0100, SZEDER Gábor wrote:\n\n> When completing refs, several __git_refs() code paths list all the\n> refs from the refs/{heads,tags,remotes}/ hierarchy and then\n> __gitcomp_nl() iterates over those refs in a shell loop to filter out\n> refs not matching the current ref to be completed.  This comes with a\n> considerable performance penalty when a repository contains a lot of\n> refs but the current ref can be uniquely completed or when only a\n> handful of refs match the current ref.\n\nPart of this is due to outputting too many refs from for-each-ref, but\npart of it is that for-each-ref is not good at limiting the refs it\nlooks at internally. I have a patch for that, but it's not all that\nhelpful without Michael Haggerty's mmap-packed-refs work, so I'll get it\nincluded there.\n\nSo that makes this change doubly important. You get the immediate\nspeedups, and then you'll get another one when that work is merged\nlater.\n\n>  \t\tcase \"$cur_\" in\n>  \t\trefs|refs/*)\n>  \t\t\tformat=\"refname\"\n> -\t\t\trefs=\"${cur_%/*}\"\n> +\t\t\trefs=(\"$match*\" \"$match*/**\")\n>  \t\t\ttrack=\"\"\n\nWorking on the aforementioned patch, I noticed that for-each-ref's\nmatching is a little tricky due to its path semantics. So I wanted to\ndouble-check your patterns. :) I think these should do the right thing.\n\n-Peff\n"},{"id":"315328","messageId":"20170324195357.cv3pjt3stjg6cqjm@sigill.intra.peff.net","threadId":"45480","inReplyTo":"20170323152924.23944-12-szeder.dev@gmail.com","subject":"Re: [PATCHv2 11/14] completion: let 'for-each-ref' sort remote branches for 'checkout' DWIMery","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-24T19:53:58Z","receivedAt":"2017-03-24T19:54:04Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Mar 23, 2017 at 04:29:21PM +0100, SZEDER Gábor wrote:\n\n> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> index 394dcece6..d26312899 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -423,8 +423,9 @@ __git_refs ()\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\t__git for-each-ref --format=\"%(refname:strip=3)\" \\\n> +\t\t\t\t--sort=\"refname:strip=3\" \\\n>  \t\t\t\t\"refs/remotes/*/$match*\" \"refs/remotes/*/$match*/**\" | \\\n> -\t\t\tsort | uniq -u\n> +\t\t\tuniq -u\n\nI wonder if it would be worth adding a \"--unique\" option to\nfor-each-ref. I guess the definition of \"unique\" may change though (here\nyou'd want to do it on the sort key, but other cases may want unique\nbranch names sorted by time, which is less trivial).\n\n-Peff\n"},{"id":"315329","messageId":"20170324200130.jpn4uxanvpdogaag@sigill.intra.peff.net","threadId":"45480","inReplyTo":"xmqqshm3dezf.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCHv2 00/14] completion: speed up refs completion","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-24T20:01:30Z","receivedAt":"2017-03-24T20:01:37Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Mar 23, 2017 at 11:28:52AM -0700, Junio C Hamano wrote:\n\n> SZEDER Gábor <szeder.dev@gmail.com> writes:\n> \n> > This series is the updated version of 'sg/completion-refs-speedup'.\n> > It speeds up refs completion for large number of refs, partly by\n> > giving up disambiguating ambiguous refs and partly by eliminating most\n> > of the shell processing between 'git for-each-ref' and 'ls-remote' and\n> > Bash's completion facility.  The rest is a bit of preparatory\n> > reorganization, cleanup and bugfixes.\n> >\n> > Changes since v1:\n> > ...\n> > [1] - http://public-inbox.org/git/20170206181545.12869-1-szeder.dev@gmail.com/\n> \n> It seems Jacob Keller was the only person who was excited about\n> these changes when v1 was posted?  It would be nice to see a bit\n> more enthusiasm from other folks who are invested in the completion\n> script, but you are the de-facto go-to person on the completion\n> already, so ... ;-)\n> \n> Will replace.  Let's advance this to 'next' soonish (say, by early\n> next week).\n\nI'm far from an expert in the completion scripts, but I read over the\nwhole thing and it looked good to me. As usual with bash, the\noptimizations can make the code a bit non-intuitive, but I think the\nchanges are clearly explained and the performance results speak for\nthemselves.\n\n-Peff\n"},{"id":"315569","messageId":"CAM0VKjkMLdTzFyAdgnJsFGk4JpR-oBdnXRoeskRRSbsvT8TXcg@mail.gmail.com","threadId":"45480","inReplyTo":"20170324194237.td2nsiyuatexuxo2@sigill.intra.peff.net","subject":"Re: [PATCHv2 08/14] completion: let 'for-each-ref' and 'ls-remote' filter matching refs","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2017-03-28T15:34:21Z","receivedAt":"2017-03-28T15:35:06Z","isPatch":false,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Fri, Mar 24, 2017 at 8:42 PM, Jeff King <peff@peff.net> wrote:\n> On Thu, Mar 23, 2017 at 04:29:18PM +0100, SZEDER Gábor wrote:\n>>               case \"$cur_\" in\n>>               refs|refs/*)\n>>                       format=\"refname\"\n>> -                     refs=\"${cur_%/*}\"\n>> +                     refs=(\"$match*\" \"$match*/**\")\n>>                       track=\"\"\n>\n> Working on the aforementioned patch, I noticed that for-each-ref's\n> matching is a little tricky due to its path semantics. So I wanted to\n> double-check your patterns. :) I think these should do the right thing.\n\nYeah, I always thought that it's weird that globbing in for-each-ref\nbehaves differently from globbing in ls-remote or refspecs, but there\nis nothing we can do about it now.\n\nAnyway, this is why the tests added in this patch include e.g. both\n'matching-branch' and 'matching/branch'.\n"}]}