{"thread":{"id":"63039","subject":"[PATCH 0/3] completion: fix bugs with slashes in remote names","startedAt":"2025-03-02T07:45:29Z","lastAt":"2025-04-08T18:55:57Z","messageCount":44,"participants":["David Mandelberg via GitGitGadget","Phillip Wood","David Mandelberg","phillip.wood123@gmail.com","SZEDER Gábor","Todd Zullinger","Junio C Hamano","D. Ben Knoble"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"513357","messageId":"pull.1901.git.git.1740901525.gitgitgadget@gmail.com","threadId":"63039","inReplyTo":null,"subject":"[PATCH 0/3] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-03-02T07:45:22Z","receivedAt":"2025-03-02T07:45:29Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Previously, some calls to for-each-ref passed fixed numbers of path\ncomponents to strip from refs, assuming that remote names had no slashes in\nthem. This made completions like:\n\ngit push github/dseomn :com\n\nResult in:\n\ngit push github/dseomn :dseomn/completion-remote-slash\n\nWith this patch, it instead results in:\n\ngit push github/dseomn :completion-remote-slash\n\nIn addition, the change to __git_dwim_remote_heads() restricts it to only\nlist remote branches, not HEAD. I think that actually improves the\ncompletion where it's used though, since HEAD doesn't seem to be valid\nanyway:\n\n$ git switch HEAD fatal: a branch is expected, got 'HEAD'\n\nDavid Mandelberg (3):\n  completion: add helper to escape strings for fnmatch\n  completion: add helper to count path components\n  completion: fix bugs with slashes in remote names\n\n contrib/completion/git-completion.bash |  40 ++++-\n t/t9902-completion.sh                  | 213 ++++++++++++++++++++++---\n 2 files changed, 225 insertions(+), 28 deletions(-)\n\n\nbase-commit: cb0ae672aeabefca9704477ea8018ac94f523970\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1901%2Fdseomn%2Fcompletion-remote-slash-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1901/dseomn/completion-remote-slash-v1\nPull-Request: https://github.com/git/git/pull/1901\n-- \ngitgitgadget\n"},{"id":"513358","messageId":"c8594d311741e884680fab8f9cc15eb881774163.1740901525.git.gitgitgadget@gmail.com","threadId":"63039","inReplyTo":"pull.1901.git.git.1740901525.gitgitgadget@gmail.com","subject":"[PATCH 1/3] completion: add helper to escape strings for fnmatch","fromName":"David Mandelberg via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-03-02T07:45:23Z","receivedAt":"2025-03-02T07:45:29Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"From: David Mandelberg <david@mandelberg.org>\n\nA follow-up commit will use this to escape parts of ref names for\nfor-each-ref.\n\nSigned-off-by: David Mandelberg <david@mandelberg.org>\n---\n contrib/completion/git-completion.bash | 12 ++++++++++++\n t/t9902-completion.sh                  |  7 +++++++\n 2 files changed, 19 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 413911be3be..cb10f818a81 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -234,6 +234,18 @@ __git_dequote ()\n \tdone\n }\n \n+# Escapes special characters in a string to pass to fnmatch(3)\n+# 1: String to escape.\n+__git_escape_fnmatch ()\n+{\n+\tlocal s=\"$1\"\n+\ts=${s//\\\\/\\\\\\\\}\n+\ts=${s//\\?/\\\\\\?}\n+\ts=${s//\\*/\\\\\\*}\n+\ts=${s//\\[/\\\\\\[}\n+\tprintf '%s\\n' \"$s\"\n+}\n+\n # The following function is based on code from:\n #\n #   bash_completion - programmable completion functions for bash 3.2+\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 51bd7508376..c5e91622876 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -455,6 +455,13 @@ test_expect_success '__git_dequote - open double quote' '\n '\n \n \n+test_expect_success '__git_escape_fnmatch' '\n+\techo '\\''foo\\\\\\?\\*\\['\\'' >expected &&\n+\t__git_escape_fnmatch '\\''foo\\?*['\\'' >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+'\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-- \ngitgitgadget\n\n"},{"id":"513359","messageId":"4fab893d9fddd65b20d280edfb8cdf6ed8bfa295.1740901525.git.gitgitgadget@gmail.com","threadId":"63039","inReplyTo":"pull.1901.git.git.1740901525.gitgitgadget@gmail.com","subject":"[PATCH 2/3] completion: add helper to count path components","fromName":"David Mandelberg via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-03-02T07:45:24Z","receivedAt":"2025-03-02T07:45:30Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"From: David Mandelberg <david@mandelberg.org>\n\nA follow-up commit will use this with for-each-ref to strip the right\nnumber of path components from refnames.\n\nSigned-off-by: David Mandelberg <david@mandelberg.org>\n---\n contrib/completion/git-completion.bash | 11 +++++++++++\n t/t9902-completion.sh                  | 26 ++++++++++++++++++++++++++\n 2 files changed, 37 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex cb10f818a81..17c044f7d60 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -246,6 +246,17 @@ __git_escape_fnmatch ()\n \tprintf '%s\\n' \"$s\"\n }\n \n+# Prints the number of slash-separated components in a path.\n+# 1: Path to count components of.\n+__git_count_path_components ()\n+{\n+\tlocal path=\"$1\"\n+\tlocal relative=\"${path#/}\"\n+\trelative=\"${relative%/}\"\n+\tlocal slashes=\"/${relative//[^\\/]}\"\n+\techo \"${#slashes}\"\n+}\n+\n # The following function is based on code from:\n #\n #   bash_completion - programmable completion functions for bash 3.2+\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex c5e91622876..0219408358c 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -462,6 +462,32 @@ test_expect_success '__git_escape_fnmatch' '\n '\n \n \n+test_expect_success '__git_count_path_components - no slashes' '\n+\techo 1 >expected &&\n+\t__git_count_path_components a >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_count_path_components - relative' '\n+\techo 3 >expected &&\n+\t__git_count_path_components a/b/c >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+\n+'\n+\n+test_expect_success '__git_count_path_components - absolute' '\n+\techo 3 >expected &&\n+\t__git_count_path_components /a/b/c >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_count_path_components - trailing slash' '\n+\techo 3 >expected &&\n+\t__git_count_path_components a/b/c/ >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+'\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-- \ngitgitgadget\n\n"},{"id":"513360","messageId":"95ffa62df6ce394249a8ddabb84fb2b517825fe3.1740901525.git.gitgitgadget@gmail.com","threadId":"63039","inReplyTo":"pull.1901.git.git.1740901525.gitgitgadget@gmail.com","subject":"[PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-03-02T07:45:25Z","receivedAt":"2025-03-02T07:45:31Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"From: David Mandelberg <david@mandelberg.org>\n\nPreviously, some calls to for-each-ref passed fixed numbers of path\ncomponents to strip from refs, assuming that remote names had no slashes\nin them. This made completions like:\n\ngit push github/dseomn :com<Tab>\n\nResult in:\n\ngit push github/dseomn :dseomn/completion-remote-slash\n\nWith this patch, it instead results in:\n\ngit push github/dseomn :completion-remote-slash\n\nIn addition, the change to __git_dwim_remote_heads() restricts it to\nonly list remote branches, not HEAD. I think that actually improves the\ncompletion where it's used though, since HEAD doesn't seem to be valid\nanyway:\n\n$ git switch HEAD\nfatal: a branch is expected, got 'HEAD'\n\nSigned-off-by: David Mandelberg <david@mandelberg.org>\n---\n contrib/completion/git-completion.bash |  17 ++-\n t/t9902-completion.sh                  | 180 ++++++++++++++++++++++---\n 2 files changed, 169 insertions(+), 28 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 17c044f7d60..5f4407be37f 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -807,11 +807,15 @@ __git_dwim_remote_heads ()\n \t# employ the heuristic used by git checkout and git switch\n \t# Try to find a remote branch that cur_es the completion word\n \t# but only output if the branch name is unique\n-\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=3)$sfx\" \\\n-\t\t--sort=\"refname:strip=3\" \\\n-\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n-\t\t\"refs/remotes/*/$cur_*\" \"refs/remotes/*/$cur_*/**\" | \\\n-\tuniq -u\n+\tlocal remote\n+\tfor remote in $(__git_remotes); do\n+\t\tlocal base=\"$(__git_escape_fnmatch \"refs/remotes/$remote\")\"\n+\t\tlocal strip=\"$(__git_count_path_components \"$base\")\"\n+\t\t__git for-each-ref \\\n+\t\t\t--format=\"$fer_pfx%(refname:strip=$strip)$sfx\" \\\n+\t\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n+\t\t\t\"$base/$cur_*\" \"$base/$cur_*/**\"\n+\tdone | sort | uniq -u\n }\n \n # Lists refs from the local (by default) or from a remote repository.\n@@ -917,7 +921,8 @@ __git_refs ()\n \t\t\tcase \"HEAD\" in\n \t\t\t$match*|$umatch*)\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\tlocal strip=\"$(__git_count_path_components \"refs/remotes/$remote\")\"\n+\t\t\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=$strip)$sfx\" \\\n \t\t\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n \t\t\t\t\"refs/remotes/$remote/$match*\" \\\n \t\t\t\t\"refs/remotes/$remote/$match*/**\"\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 0219408358c..0c41ef9d5b1 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -149,7 +149,8 @@ fi\n test_expect_success 'setup for __git_find_repo_path/__gitdir tests' '\n \tmkdir -p subdir/subsubdir &&\n \tmkdir -p non-repo &&\n-\tgit init -b main otherrepo\n+\tgit init -b main otherrepo &&\n+\tgit init -b main slashrepo\n '\n \n test_expect_success '__git_find_repo_path - from command line (through $__git_dir)' '\n@@ -681,6 +682,13 @@ test_expect_success 'setup for ref completion' '\n \t) &&\n \tgit remote add other \"$ROOT/otherrepo/.git\" &&\n \tgit fetch --no-tags other &&\n+\t(\n+\t\tcd slashrepo &&\n+\t\tgit commit --allow-empty -m initial &&\n+\t\tgit branch -m main branch/with/slash\n+\t) &&\n+\tgit remote add remote/with/slash \"$ROOT/slashrepo/.git\" &&\n+\tgit fetch --no-tags remote/with/slash &&\n \trm -f .git/FETCH_HEAD &&\n \tgit init thirdrepo\n '\n@@ -693,6 +701,8 @@ test_expect_success '__git_refs - simple' '\n \tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n \tEOF\n \t(\n@@ -709,6 +719,8 @@ test_expect_success '__git_refs - full refs' '\n \trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n+\trefs/remotes/remote/with/slash/HEAD\n+\trefs/remotes/remote/with/slash/branch/with/slash\n \trefs/tags/matching-tag\n \tEOF\n \t(\n@@ -774,6 +786,19 @@ test_expect_success '__git_refs - configured remote' '\n \ttest_cmp expected \"$actual\"\n '\n \n+test_expect_success '__git_refs - configured remote - with slash' '\n+\tcat >expected <<-EOF &&\n+\tHEAD\n+\tHEAD\n+\tbranch/with/slash\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_refs remote/with/slash >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n test_expect_success '__git_refs - configured remote - full refs' '\n \tcat >expected <<-EOF &&\n \tHEAD\n@@ -916,17 +941,19 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \tother/ambiguous\n \tother/branch-in-other\n \tother/main-in-other\n-\tremote/ambiguous\n-\tremote/branch-in-remote\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/ambiguous\n+\tremote/with/slash/branch-in-remote\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n-\tHEAD\n \tbranch-in-other\n \tbranch-in-remote\n+\tbranch/with/slash\n \tmain-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+\t\trefs/remotes/remote/with/slash/ambiguous \\\n+\t\trefs/remotes/remote/with/slash/branch-in-remote\n \tdo\n \t\tgit update-ref $remote_ref main &&\n \t\ttest_when_finished \"git update-ref -d $remote_ref\" || return 1\n@@ -946,6 +973,8 @@ test_expect_success '__git_refs - after --opt=' '\n \tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n \tEOF\n \t(\n@@ -962,6 +991,8 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n+\trefs/remotes/remote/with/slash/HEAD\n+\trefs/remotes/remote/with/slash/branch/with/slash\n \trefs/tags/matching-tag\n \tEOF\n \t(\n@@ -979,6 +1010,8 @@ test_expect_success '__git refs - excluding refs' '\n \t^other/HEAD\n \t^other/branch-in-other\n \t^other/main-in-other\n+\t^remote/with/slash/HEAD\n+\t^remote/with/slash/branch/with/slash\n \t^matching-tag\n \tEOF\n \t(\n@@ -995,6 +1028,8 @@ test_expect_success '__git refs - excluding full refs' '\n \t^refs/remotes/other/HEAD\n \t^refs/remotes/other/branch-in-other\n \t^refs/remotes/other/main-in-other\n+\t^refs/remotes/remote/with/slash/HEAD\n+\t^refs/remotes/remote/with/slash/branch/with/slash\n \t^refs/tags/matching-tag\n \tEOF\n \t(\n@@ -1022,6 +1057,8 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n \tother/branch-in-other\n \tother/main-in-other\n \tother/matching/branch-in-other\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n \tmatching/tag\n \tEOF\n@@ -1142,6 +1179,8 @@ test_expect_success '__git_complete_refs - simple' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tmatching-tag Z\n \tEOF\n \t(\n@@ -1180,6 +1219,20 @@ test_expect_success '__git_complete_refs - remote' '\n \ttest_cmp expected out\n '\n \n+test_expect_success '__git_complete_refs - remote - with slash' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tHEAD Z\n+\tHEAD Z\n+\tbranch/with/slash Z\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_complete_refs --remote=remote/with/slash &&\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@@ -1188,9 +1241,11 @@ test_expect_success '__git_complete_refs - track' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tmatching-tag Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain-in-other Z\n \tEOF\n \t(\n@@ -1235,6 +1290,8 @@ test_expect_success '__git_complete_refs - suffix' '\n \tother/HEAD.\n \tother/branch-in-other.\n \tother/main-in-other.\n+\tremote/with/slash/HEAD.\n+\tremote/with/slash/branch/with/slash.\n \tmatching-tag.\n \tEOF\n \t(\n@@ -1260,6 +1317,20 @@ test_expect_success '__git_complete_fetch_refspecs - simple' '\n \ttest_cmp expected out\n '\n \n+test_expect_success '__git_complete_fetch_refspecs - with slash' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tHEAD:HEAD Z\n+\tHEAD:HEAD Z\n+\tbranch/with/slash:branch/with/slash Z\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_complete_fetch_refspecs remote/with/slash &&\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@@ -1340,8 +1411,8 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n \n test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n \ttest_completion \"git switch \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1487,8 +1558,8 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1496,6 +1567,8 @@ test_expect_success 'git checkout - completes refs and unique remote branches fo\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1515,8 +1588,8 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n \n test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1525,8 +1598,8 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n \n test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1549,14 +1622,16 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1564,6 +1639,8 @@ test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1,\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1576,14 +1653,16 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1591,6 +1670,8 @@ test_expect_success 'git checkout - a later --guess overrides previous --no-gues\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1603,6 +1684,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1616,6 +1699,8 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1623,8 +1708,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \ttest_config checkout.guess true &&\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1632,6 +1717,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1639,8 +1726,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \ttest_config checkout.guess false &&\n \ttest_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1648,6 +1735,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1661,6 +1750,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1673,6 +1764,8 @@ test_expect_success 'git switch - with --detach, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1685,6 +1778,8 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1857,6 +1952,8 @@ test_expect_success 'git switch - with -d, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1869,6 +1966,8 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1877,11 +1976,15 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n \ttest_completion \"git switch -t \" <<-\\EOF\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1890,11 +1993,15 @@ test_expect_success 'git checkout - with --track, complete only remote branches'\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n \ttest_completion \"git checkout -t \" <<-\\EOF\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1914,6 +2021,8 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1926,6 +2035,8 @@ test_expect_success 'git switch - with -c, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1938,6 +2049,8 @@ test_expect_success 'git switch - with -C, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1950,6 +2063,8 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1962,6 +2077,8 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1974,6 +2091,8 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1986,6 +2105,8 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1998,6 +2119,8 @@ test_expect_success 'git checkout - with -b, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2010,6 +2133,8 @@ test_expect_success 'git checkout - with -B, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2022,6 +2147,8 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2034,6 +2161,8 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2046,6 +2175,8 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2058,13 +2189,15 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -c \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2073,8 +2206,8 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n \n test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -C \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2111,8 +2244,8 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n \n test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -b \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2121,8 +2254,8 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n \n test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -B \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2159,8 +2292,8 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n \n test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git switch --orphan \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2175,8 +2308,8 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n \n test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git checkout --orphan \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2192,6 +2325,8 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2206,7 +2341,8 @@ test_expect_success 'git restore completes modified files' '\n test_expect_success 'teardown after ref completion' '\n \tgit branch -d matching-branch &&\n \tgit tag -d matching-tag &&\n-\tgit remote remove other\n+\tgit remote remove other &&\n+\tgit remote remove remote/with/slash\n '\n \n \n-- \ngitgitgadget\n"},{"id":"513366","messageId":"c03192bc-68d3-4645-9bd3-93a338a7496f@gmail.com","threadId":"63039","inReplyTo":"95ffa62df6ce394249a8ddabb84fb2b517825fe3.1740901525.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-03-02T14:17:40Z","receivedAt":"2025-03-02T14:17:45Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi David\n\nOn 02/03/2025 07:45, David Mandelberg via GitGitGadget wrote:\n> From: David Mandelberg <david@mandelberg.org>\n> \n> Previously, some calls to for-each-ref passed fixed numbers of path\n> components to strip from refs, assuming that remote names had no slashes\n> in them. This made completions like:\n> \n> git push github/dseomn :com<Tab>\n> \n> Result in:\n> \n> git push github/dseomn :dseomn/completion-remote-slash\n> \n> With this patch, it instead results in:\n> \n> git push github/dseomn :completion-remote-slash\n\nThis sounds like a useful improvement and I like the idea, but I think \nrunning \"git for-each-ref\" once for each remote is not going to scale \nvery well for people who have a lot of remotes. I think it would be \nbetter to try and strip \"refs/remote/$remote/\" outside of \"git \nfor-each-ref\". I've not tested it but I think something like\n\nlocal sed_cmd=\nlocal remote\n# ref names and therefore remote names cannot contain '*?[]^' so we\n# only need to escape '.$/'. Using 'sort -r' means that if there is a\n# remote called \"github\" and another called \"github/alice\" we will try\n# and strip \"github/alice\" first.\nfor remote in $(__git_remotes | sort -r)\ndo\n\tremote=\"${remote//./\\\\./}\"\n\tremote=\"${remote//\\$/\\\\\\$/}\"\n\tremote=\"${remote//\\//\\\\\\//}\"\n\tsed_cmd=\"${sed_cmd} -e s/^refs\\/remotes\\/$remote\\/// -e t\"\ndone\n__git for-each-ref --format=\"$fer_pfx$sfx\" \\\n\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n\t\"refs/remotes/*/$cur_*\" \"refs/remotes/*/$cur_*/**\" |\nsed $sed_cmd | sort -u\n\nshould work and means we're only forking three extra processes no matter \nhow many remotes the user has. I'm not sure if it changes the output \norder when GIT_COMPLETION_IGNORE_CASE is set though.\n\nBest Wishes\n\nPhillip\n\n> In addition, the change to __git_dwim_remote_heads() restricts it to\n> only list remote branches, not HEAD. I think that actually improves the\n> completion where it's used though, since HEAD doesn't seem to be valid\n> anyway:\n> \n> $ git switch HEAD\n> fatal: a branch is expected, got 'HEAD'\n> \n> Signed-off-by: David Mandelberg <david@mandelberg.org>\n> ---\n>   contrib/completion/git-completion.bash |  17 ++-\n>   t/t9902-completion.sh                  | 180 ++++++++++++++++++++++---\n>   2 files changed, 169 insertions(+), 28 deletions(-)\n> \n> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> index 17c044f7d60..5f4407be37f 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -807,11 +807,15 @@ __git_dwim_remote_heads ()\n>   \t# employ the heuristic used by git checkout and git switch\n>   \t# Try to find a remote branch that cur_es the completion word\n>   \t# but only output if the branch name is unique\n> -\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=3)$sfx\" \\\n> -\t\t--sort=\"refname:strip=3\" \\\n> -\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n> -\t\t\"refs/remotes/*/$cur_*\" \"refs/remotes/*/$cur_*/**\" | \\\n> -\tuniq -u\n> +\tlocal remote\n> +\tfor remote in $(__git_remotes); do\n> +\t\tlocal base=\"$(__git_escape_fnmatch \"refs/remotes/$remote\")\"\n> +\t\tlocal strip=\"$(__git_count_path_components \"$base\")\"\n> +\t\t__git for-each-ref \\\n> +\t\t\t--format=\"$fer_pfx%(refname:strip=$strip)$sfx\" \\\n> +\t\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n> +\t\t\t\"$base/$cur_*\" \"$base/$cur_*/**\"\n> +\tdone | sort | uniq -u\n>   }\n>   \n>   # Lists refs from the local (by default) or from a remote repository.\n> @@ -917,7 +921,8 @@ __git_refs ()\n>   \t\t\tcase \"HEAD\" in\n>   \t\t\t$match*|$umatch*)\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\tlocal strip=\"$(__git_count_path_components \"refs/remotes/$remote\")\"\n> +\t\t\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=$strip)$sfx\" \\\n>   \t\t\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n>   \t\t\t\t\"refs/remotes/$remote/$match*\" \\\n>   \t\t\t\t\"refs/remotes/$remote/$match*/**\"\n> diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\n> index 0219408358c..0c41ef9d5b1 100755\n> --- a/t/t9902-completion.sh\n> +++ b/t/t9902-completion.sh\n> @@ -149,7 +149,8 @@ fi\n>   test_expect_success 'setup for __git_find_repo_path/__gitdir tests' '\n>   \tmkdir -p subdir/subsubdir &&\n>   \tmkdir -p non-repo &&\n> -\tgit init -b main otherrepo\n> +\tgit init -b main otherrepo &&\n> +\tgit init -b main slashrepo\n>   '\n>   \n>   test_expect_success '__git_find_repo_path - from command line (through $__git_dir)' '\n> @@ -681,6 +682,13 @@ test_expect_success 'setup for ref completion' '\n>   \t) &&\n>   \tgit remote add other \"$ROOT/otherrepo/.git\" &&\n>   \tgit fetch --no-tags other &&\n> +\t(\n> +\t\tcd slashrepo &&\n> +\t\tgit commit --allow-empty -m initial &&\n> +\t\tgit branch -m main branch/with/slash\n> +\t) &&\n> +\tgit remote add remote/with/slash \"$ROOT/slashrepo/.git\" &&\n> +\tgit fetch --no-tags remote/with/slash &&\n>   \trm -f .git/FETCH_HEAD &&\n>   \tgit init thirdrepo\n>   '\n> @@ -693,6 +701,8 @@ test_expect_success '__git_refs - simple' '\n>   \tother/HEAD\n>   \tother/branch-in-other\n>   \tother/main-in-other\n> +\tremote/with/slash/HEAD\n> +\tremote/with/slash/branch/with/slash\n>   \tmatching-tag\n>   \tEOF\n>   \t(\n> @@ -709,6 +719,8 @@ test_expect_success '__git_refs - full refs' '\n>   \trefs/remotes/other/HEAD\n>   \trefs/remotes/other/branch-in-other\n>   \trefs/remotes/other/main-in-other\n> +\trefs/remotes/remote/with/slash/HEAD\n> +\trefs/remotes/remote/with/slash/branch/with/slash\n>   \trefs/tags/matching-tag\n>   \tEOF\n>   \t(\n> @@ -774,6 +786,19 @@ test_expect_success '__git_refs - configured remote' '\n>   \ttest_cmp expected \"$actual\"\n>   '\n>   \n> +test_expect_success '__git_refs - configured remote - with slash' '\n> +\tcat >expected <<-EOF &&\n> +\tHEAD\n> +\tHEAD\n> +\tbranch/with/slash\n> +\tEOF\n> +\t(\n> +\t\tcur= &&\n> +\t\t__git_refs remote/with/slash >\"$actual\"\n> +\t) &&\n> +\ttest_cmp expected \"$actual\"\n> +'\n> +\n>   test_expect_success '__git_refs - configured remote - full refs' '\n>   \tcat >expected <<-EOF &&\n>   \tHEAD\n> @@ -916,17 +941,19 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n>   \tother/ambiguous\n>   \tother/branch-in-other\n>   \tother/main-in-other\n> -\tremote/ambiguous\n> -\tremote/branch-in-remote\n> +\tremote/with/slash/HEAD\n> +\tremote/with/slash/ambiguous\n> +\tremote/with/slash/branch-in-remote\n> +\tremote/with/slash/branch/with/slash\n>   \tmatching-tag\n> -\tHEAD\n>   \tbranch-in-other\n>   \tbranch-in-remote\n> +\tbranch/with/slash\n>   \tmain-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> +\t\trefs/remotes/remote/with/slash/ambiguous \\\n> +\t\trefs/remotes/remote/with/slash/branch-in-remote\n>   \tdo\n>   \t\tgit update-ref $remote_ref main &&\n>   \t\ttest_when_finished \"git update-ref -d $remote_ref\" || return 1\n> @@ -946,6 +973,8 @@ test_expect_success '__git_refs - after --opt=' '\n>   \tother/HEAD\n>   \tother/branch-in-other\n>   \tother/main-in-other\n> +\tremote/with/slash/HEAD\n> +\tremote/with/slash/branch/with/slash\n>   \tmatching-tag\n>   \tEOF\n>   \t(\n> @@ -962,6 +991,8 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n>   \trefs/remotes/other/HEAD\n>   \trefs/remotes/other/branch-in-other\n>   \trefs/remotes/other/main-in-other\n> +\trefs/remotes/remote/with/slash/HEAD\n> +\trefs/remotes/remote/with/slash/branch/with/slash\n>   \trefs/tags/matching-tag\n>   \tEOF\n>   \t(\n> @@ -979,6 +1010,8 @@ test_expect_success '__git refs - excluding refs' '\n>   \t^other/HEAD\n>   \t^other/branch-in-other\n>   \t^other/main-in-other\n> +\t^remote/with/slash/HEAD\n> +\t^remote/with/slash/branch/with/slash\n>   \t^matching-tag\n>   \tEOF\n>   \t(\n> @@ -995,6 +1028,8 @@ test_expect_success '__git refs - excluding full refs' '\n>   \t^refs/remotes/other/HEAD\n>   \t^refs/remotes/other/branch-in-other\n>   \t^refs/remotes/other/main-in-other\n> +\t^refs/remotes/remote/with/slash/HEAD\n> +\t^refs/remotes/remote/with/slash/branch/with/slash\n>   \t^refs/tags/matching-tag\n>   \tEOF\n>   \t(\n> @@ -1022,6 +1057,8 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n>   \tother/branch-in-other\n>   \tother/main-in-other\n>   \tother/matching/branch-in-other\n> +\tremote/with/slash/HEAD\n> +\tremote/with/slash/branch/with/slash\n>   \tmatching-tag\n>   \tmatching/tag\n>   \tEOF\n> @@ -1142,6 +1179,8 @@ test_expect_success '__git_complete_refs - simple' '\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tmatching-tag Z\n>   \tEOF\n>   \t(\n> @@ -1180,6 +1219,20 @@ test_expect_success '__git_complete_refs - remote' '\n>   \ttest_cmp expected out\n>   '\n>   \n> +test_expect_success '__git_complete_refs - remote - with slash' '\n> +\tsed -e \"s/Z$//\" >expected <<-EOF &&\n> +\tHEAD Z\n> +\tHEAD Z\n> +\tbranch/with/slash Z\n> +\tEOF\n> +\t(\n> +\t\tcur= &&\n> +\t\t__git_complete_refs --remote=remote/with/slash &&\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> @@ -1188,9 +1241,11 @@ test_expect_success '__git_complete_refs - track' '\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tmatching-tag Z\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain-in-other Z\n>   \tEOF\n>   \t(\n> @@ -1235,6 +1290,8 @@ test_expect_success '__git_complete_refs - suffix' '\n>   \tother/HEAD.\n>   \tother/branch-in-other.\n>   \tother/main-in-other.\n> +\tremote/with/slash/HEAD.\n> +\tremote/with/slash/branch/with/slash.\n>   \tmatching-tag.\n>   \tEOF\n>   \t(\n> @@ -1260,6 +1317,20 @@ test_expect_success '__git_complete_fetch_refspecs - simple' '\n>   \ttest_cmp expected out\n>   '\n>   \n> +test_expect_success '__git_complete_fetch_refspecs - with slash' '\n> +\tsed -e \"s/Z$//\" >expected <<-EOF &&\n> +\tHEAD:HEAD Z\n> +\tHEAD:HEAD Z\n> +\tbranch/with/slash:branch/with/slash Z\n> +\tEOF\n> +\t(\n> +\t\tcur= &&\n> +\t\t__git_complete_fetch_refspecs remote/with/slash &&\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> @@ -1340,8 +1411,8 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n>   \n>   test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n>   \ttest_completion \"git switch \" <<-\\EOF\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain Z\n>   \tmain-in-other Z\n>   \tmatching-branch Z\n> @@ -1487,8 +1558,8 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n>   test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n>   \ttest_completion \"git checkout \" <<-\\EOF\n>   \tHEAD Z\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain Z\n>   \tmain-in-other Z\n>   \tmatching-branch Z\n> @@ -1496,6 +1567,8 @@ test_expect_success 'git checkout - completes refs and unique remote branches fo\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1515,8 +1588,8 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n>   \n>   test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n>   \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain Z\n>   \tmain-in-other Z\n>   \tmatching-branch Z\n> @@ -1525,8 +1598,8 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n>   \n>   test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n>   \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain Z\n>   \tmain-in-other Z\n>   \tmatching-branch Z\n> @@ -1549,14 +1622,16 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n>   test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n>   \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n>   \tHEAD Z\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain Z\n>   \tmain-in-other Z\n>   \tmatching-branch Z\n> @@ -1564,6 +1639,8 @@ test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1,\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1576,14 +1653,16 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n>   test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n>   \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n>   \tHEAD Z\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain Z\n>   \tmain-in-other Z\n>   \tmatching-branch Z\n> @@ -1591,6 +1670,8 @@ test_expect_success 'git checkout - a later --guess overrides previous --no-gues\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1603,6 +1684,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1616,6 +1699,8 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1623,8 +1708,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n>   \ttest_config checkout.guess true &&\n>   \ttest_completion \"git checkout \" <<-\\EOF\n>   \tHEAD Z\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain Z\n>   \tmain-in-other Z\n>   \tmatching-branch Z\n> @@ -1632,6 +1717,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1639,8 +1726,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n>   \ttest_config checkout.guess false &&\n>   \ttest_completion \"git checkout --guess \" <<-\\EOF\n>   \tHEAD Z\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain Z\n>   \tmain-in-other Z\n>   \tmatching-branch Z\n> @@ -1648,6 +1735,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1661,6 +1750,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1673,6 +1764,8 @@ test_expect_success 'git switch - with --detach, complete all references' '\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1685,6 +1778,8 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1857,6 +1952,8 @@ test_expect_success 'git switch - with -d, complete all references' '\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1869,6 +1966,8 @@ test_expect_success 'git checkout - with -d, complete only references' '\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1877,11 +1976,15 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   \ttest_completion \"git switch -t \" <<-\\EOF\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1890,11 +1993,15 @@ test_expect_success 'git checkout - with --track, complete only remote branches'\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   \ttest_completion \"git checkout -t \" <<-\\EOF\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1914,6 +2021,8 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1926,6 +2035,8 @@ test_expect_success 'git switch - with -c, complete all references' '\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1938,6 +2049,8 @@ test_expect_success 'git switch - with -C, complete all references' '\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1950,6 +2063,8 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1962,6 +2077,8 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1974,6 +2091,8 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1986,6 +2105,8 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -1998,6 +2119,8 @@ test_expect_success 'git checkout - with -b, complete all references' '\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -2010,6 +2133,8 @@ test_expect_success 'git checkout - with -B, complete all references' '\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -2022,6 +2147,8 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -2034,6 +2161,8 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -2046,6 +2175,8 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -2058,13 +2189,15 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n>   test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n>   \ttest_completion \"git switch -c \" <<-\\EOF\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain Z\n>   \tmain-in-other Z\n>   \tmatching-branch Z\n> @@ -2073,8 +2206,8 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n>   \n>   test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n>   \ttest_completion \"git switch -C \" <<-\\EOF\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain Z\n>   \tmain-in-other Z\n>   \tmatching-branch Z\n> @@ -2111,8 +2244,8 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n>   \n>   test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n>   \ttest_completion \"git checkout -b \" <<-\\EOF\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain Z\n>   \tmain-in-other Z\n>   \tmatching-branch Z\n> @@ -2121,8 +2254,8 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n>   \n>   test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n>   \ttest_completion \"git checkout -B \" <<-\\EOF\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain Z\n>   \tmain-in-other Z\n>   \tmatching-branch Z\n> @@ -2159,8 +2292,8 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n>   \n>   test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n>   \ttest_completion \"git switch --orphan \" <<-\\EOF\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain Z\n>   \tmain-in-other Z\n>   \tmatching-branch Z\n> @@ -2175,8 +2308,8 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n>   \n>   test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n>   \ttest_completion \"git checkout --orphan \" <<-\\EOF\n> -\tHEAD Z\n>   \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>   \tmain Z\n>   \tmain-in-other Z\n>   \tmatching-branch Z\n> @@ -2192,6 +2325,8 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n>   \tother/HEAD Z\n>   \tother/branch-in-other Z\n>   \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>   \tEOF\n>   '\n>   \n> @@ -2206,7 +2341,8 @@ test_expect_success 'git restore completes modified files' '\n>   test_expect_success 'teardown after ref completion' '\n>   \tgit branch -d matching-branch &&\n>   \tgit tag -d matching-tag &&\n> -\tgit remote remove other\n> +\tgit remote remove other &&\n> +\tgit remote remove remote/with/slash\n>   '\n>   \n>   \n\n"},{"id":"513370","messageId":"2323bb52-f43d-4f40-8955-4c648677a93e@mandelberg.org","threadId":"63039","inReplyTo":"c03192bc-68d3-4645-9bd3-93a338a7496f@gmail.com","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-02T20:34:07Z","receivedAt":"2025-03-02T20:34:09Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Op 2025-03-02 om 09:17 schreef Phillip Wood:\n> Hi David\n> \n> On 02/03/2025 07:45, David Mandelberg via GitGitGadget wrote:\n>> From: David Mandelberg <david@mandelberg.org>\n>>\n>> Previously, some calls to for-each-ref passed fixed numbers of path\n>> components to strip from refs, assuming that remote names had no slashes\n>> in them. This made completions like:\n>>\n>> git push github/dseomn :com<Tab>\n>>\n>> Result in:\n>>\n>> git push github/dseomn :dseomn/completion-remote-slash\n>>\n>> With this patch, it instead results in:\n>>\n>> git push github/dseomn :completion-remote-slash\n> \n> This sounds like a useful improvement and I like the idea, but I think \n> running \"git for-each-ref\" once for each remote is not going to scale \n> very well for people who have a lot of remotes. I think it would be \n> better to try and strip \"refs/remote/$remote/\" outside of \"git for-each- \n> ref\". I've not tested it but I think something like\n\nGood point, I hadn't thought of that. Do you have a rough estimate of \nwhat \"a lot of remotes\" is? 100ish, maybe? I'd like to do some testing \nto get actual performance numbers before trying to optimize this, \nbecause I think the optimization has some drawbacks, see below.\n\nIf optimization is needed, another approach is to parallelize the forks:\n\n{\n\tlocal fer_pids=\n\tfor ...\n\tdo\n\t\t__git for-each-ref ... &\n\t\tfer_pids=\"$fer_pids $!\"\n\tdone\n\ttest -z \"$fer_pids\" || wait $fer_pids\n} | sort | uniq -u\n\nThat might cause spikes in cpu/memory/disk usage that aren't ideal though.\n\n> local sed_cmd=\n> local remote\n> # ref names and therefore remote names cannot contain '*?[]^' so we\n> # only need to escape '.$/'. Using 'sort -r' means that if there is a\n> # remote called \"github\" and another called \"github/alice\" we will try\n> # and strip \"github/alice\" first.\n> for remote in $(__git_remotes | sort -r)\n> do\n>      remote=\"${remote//./\\\\./}\"\n>      remote=\"${remote//\\$/\\\\\\$/}\"\n>      remote=\"${remote//\\//\\\\\\//}\"\n\nJust FYI since it took me hours to figure this out myself: I think this \nwould break tests on macos because of an old version of bash that \nhandles backslashes weirdly. I think removing the double quotes would \nwork around that issue, and be safe because word splitting doesn't \nhappen in assignments.\n\n>      sed_cmd=\"${sed_cmd} -e s/^refs\\/remotes\\/$remote\\/// -e t\"\n> done\n\nMostly just a note to myself if I end up using this idea: I think a \nspace in $remote would break this because bash would split up the arg to \nsed. There's probably some way to fix that with extra escaping though?\n\n> __git for-each-ref --format=\"$fer_pfx$sfx\" \\\n>      ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n>      \"refs/remotes/*/$cur_*\" \"refs/remotes/*/$cur_*/**\" |\n\nThis would search for $cur_ in the wrong place because * only matches \none path component, right? Changing to ** might help, but then it would \nmatch in places more it shouldn't.\n\n> sed $sed_cmd | sort -u\n> \n> should work and means we're only forking three extra processes no matter \n> how many remotes the user has. I'm not sure if it changes the output \n> order when GIT_COMPLETION_IGNORE_CASE is set though.\n> \n> Best Wishes\n> \n> Phillip\n\n"},{"id":"513412","messageId":"65d903c0-6b4b-4a55-b7e4-4a277417f0f1@gmail.com","threadId":"63039","inReplyTo":"2323bb52-f43d-4f40-8955-4c648677a93e@mandelberg.org","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-03-03T16:36:24Z","receivedAt":"2025-03-03T16:36:29Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi David\n\nOn 02/03/2025 20:34, David Mandelberg wrote:\n> Op 2025-03-02 om 09:17 schreef Phillip Wood:\n>> On 02/03/2025 07:45, David Mandelberg via GitGitGadget wrote:\n>>> From: David Mandelberg <david@mandelberg.org>\n>>>\n>> This sounds like a useful improvement and I like the idea, but I think \n>> running \"git for-each-ref\" once for each remote is not going to scale \n>> very well for people who have a lot of remotes. I think it would be \n>> better to try and strip \"refs/remote/$remote/\" outside of \"git for- \n>> each- ref\". I've not tested it but I think something like\n> \n> Good point, I hadn't thought of that. Do you have a rough estimate of \n> what \"a lot of remotes\" is? 100ish, maybe?\n\nI'm not really sure what a \"large\" number looks like for people who \ntrack a lot of repositories. On windows forking is pretty slow (our \nshell-based test suite is incredibly slow on that platform) so I think \nit makes sense to try and avoid adding new processes.\n\n> I'd like to do some testing \n> to get actual performance numbers before trying to optimize this, \n> because I think the optimization has some drawbacks, see below.\n> \n> If optimization is needed, another approach is to parallelize the forks:\n> \n> {\n>      local fer_pids=\n>      for ...\n>      do\n>          __git for-each-ref ... &\n>          fer_pids=\"$fer_pids $!\"\n>      done\n>      test -z \"$fer_pids\" || wait $fer_pids\n> } | sort | uniq -u\n> \n> That might cause spikes in cpu/memory/disk usage that aren't ideal though.\n\nYes, if there were a 100 remotes that's a bit of a fork-bomb.\n\n>> local sed_cmd=\n>> local remote\n>> # ref names and therefore remote names cannot contain '*?[]^' so we\n>> # only need to escape '.$/'. Using 'sort -r' means that if there is a\n>> # remote called \"github\" and another called \"github/alice\" we will try\n>> # and strip \"github/alice\" first.\n>> for remote in $(__git_remotes | sort -r)\n>> do\n>>      remote=\"${remote//./\\\\./}\"\n>>      remote=\"${remote//\\$/\\\\\\$/}\"\n>>      remote=\"${remote//\\//\\\\\\//}\"\n> \n> Just FYI since it took me hours to figure this out myself: I think this \n> would break tests on macos because of an old version of bash that \n> handles backslashes weirdly. I think removing the double quotes would \n> work around that issue, and be safe because word splitting doesn't \n> happen in assignments.\n\nThanks, I'm not familiar with bash's extensions to parameter \nsubstitution. The completions can also but used under zsh \n(git-completion.zsh is pretty much abandoned I think) but it looks like \nbash and zsh agree on this expansion.\n\n>>      sed_cmd=\"${sed_cmd} -e s/^refs\\/remotes\\/$remote\\/// -e t\"\n>> done\n> \n> Mostly just a note to myself if I end up using this idea: I think a \n> space in $remote would break this because bash would split up the arg to \n> sed. There's probably some way to fix that with extra escaping though?\n\nI should have said in the comment above that ref names cannot contain \nspace, tab, newline etc. so we don't have to worry about the shell's \nword splitting splitting the patterns. They cannot contain backslashes \neither.\n\n>> __git for-each-ref --format=\"$fer_pfx$sfx\" \\\n>>      ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n>>      \"refs/remotes/*/$cur_*\" \"refs/remotes/*/$cur_*/**\" |\n> \n> This would search for $cur_ in the wrong place because * only matches \n> one path component, right? Changing to ** might help, but then it would \n> match in places more it shouldn't.\n\nOh, good point I'd missed that. Could we change sed to run with '-n' and \ngenerate an expression that looks like\n\n     -e s|^refs/remotes/$remote/\\($cur_\\)|\\1|p -e t\n\nwith the appropriate escaping of $remote and $cur_ so that we only print \nthe output of for-each-ref that matches $cur_?\n\nBest Wishes\n\nPhillip\n\n>> sed $sed_cmd | sort -u\n>>\n>> should work and means we're only forking three extra processes no \n>> matter how many remotes the user has. I'm not sure if it changes the \n>> output order when GIT_COMPLETION_IGNORE_CASE is set though.\n>>\n>> Best Wishes\n>>\n>> Phillip\n> \n\n"},{"id":"513423","messageId":"42dfc06e-8eb1-46da-9971-9d102f1390ad@mandelberg.org","threadId":"63039","inReplyTo":"65d903c0-6b4b-4a55-b7e4-4a277417f0f1@gmail.com","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-03T19:19:05Z","receivedAt":"2025-03-03T19:19:07Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Op 2025-03-03 om 11:36 schreef phillip.wood123@gmail.com:\n> I'm not really sure what a \"large\" number looks like for people who \n> track a lot of repositories. On windows forking is pretty slow (our \n> shell-based test suite is incredibly slow on that platform) so I think \n> it makes sense to try and avoid adding new processes.\n\nThanks, I didn't know about forking being slow on Windows.\n\nDo you know if we need to worry about hitting the max number or length \nof arguments to a command? \nhttps://pubs.opengroup.org/onlinepubs/9799919799/basedefs/limits.h.html \nunder _POSIX_ARG_MAX says that at least 4KiB should be supported, which \nseems low enough to maybe cause trouble, but it's a minimum. \nhttps://www.in-ulm.de/~mascheck/various/argmax/ lists a bunch of values \nfor different platforms, and from a brief scan it looks like all the \nremotely-modern platforms have values that are probably large enough for \nthis.\n\n>> I'd like to do some testing to get actual performance numbers before \n>> trying to optimize this, because I think the optimization has some \n>> drawbacks, see below.\n>>\n>> If optimization is needed, another approach is to parallelize the forks:\n>>\n>> {\n>>      local fer_pids=\n>>      for ...\n>>      do\n>>          __git for-each-ref ... &\n>>          fer_pids=\"$fer_pids $!\"\n>>      done\n>>      test -z \"$fer_pids\" || wait $fer_pids\n>> } | sort | uniq -u\n>>\n>> That might cause spikes in cpu/memory/disk usage that aren't ideal \n>> though.\n> \n> Yes, if there were a 100 remotes that's a bit of a fork-bomb.\n\nI also realized after sending that email that it could run into issues \nwith the number of available PIDs too. It could be improved by limiting \nto n child processes at a time and waiting for one to finish before \nstarting the next, but that's more complication. I think your solution \nbelow is better.\n\n>>> local sed_cmd=\n>>> local remote\n>>> # ref names and therefore remote names cannot contain '*?[]^' so we\n>>> # only need to escape '.$/'. Using 'sort -r' means that if there is a\n>>> # remote called \"github\" and another called \"github/alice\" we will try\n>>> # and strip \"github/alice\" first.\n>>> for remote in $(__git_remotes | sort -r)\n>>> do\n>>>      remote=\"${remote//./\\\\./}\"\n>>>      remote=\"${remote//\\$/\\\\\\$/}\"\n>>>      remote=\"${remote//\\//\\\\\\//}\"\n>>\n>> Just FYI since it took me hours to figure this out myself: I think \n>> this would break tests on macos because of an old version of bash that \n>> handles backslashes weirdly. I think removing the double quotes would \n>> work around that issue, and be safe because word splitting doesn't \n>> happen in assignments.\n> \n> Thanks, I'm not familiar with bash's extensions to parameter \n> substitution. The completions can also but used under zsh (git- \n> completion.zsh is pretty much abandoned I think) but it looks like bash \n> and zsh agree on this expansion.\n\nThe tests don't automatically run under zsh though, right? Maybe I \nshould try to figure out how to do that in a separate patch...\n\n>>>      sed_cmd=\"${sed_cmd} -e s/^refs\\/remotes\\/$remote\\/// -e t\"\n>>> done\n>>\n>> Mostly just a note to myself if I end up using this idea: I think a \n>> space in $remote would break this because bash would split up the arg \n>> to sed. There's probably some way to fix that with extra escaping though?\n> \n> I should have said in the comment above that ref names cannot contain \n> space, tab, newline etc. so we don't have to worry about the shell's \n> word splitting splitting the patterns. They cannot contain backslashes \n> either.\n\nGood to know. Is https://git-scm.com/docs/git-check-ref-format the place \nI should have read to understand what escaping is needed and what isn't \nbecause of the limitations on ref names?\n\n>>> __git for-each-ref --format=\"$fer_pfx$sfx\" \\\n>>>      ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n>>>      \"refs/remotes/*/$cur_*\" \"refs/remotes/*/$cur_*/**\" |\n>>\n>> This would search for $cur_ in the wrong place because * only matches \n>> one path component, right? Changing to ** might help, but then it \n>> would match in places more it shouldn't.\n> \n> Oh, good point I'd missed that. Could we change sed to run with '-n' and \n> generate an expression that looks like\n> \n>      -e s|^refs/remotes/$remote/\\($cur_\\)|\\1|p -e t\n> \n> with the appropriate escaping of $remote and $cur_ so that we only print \n> the output of for-each-ref that matches $cur_?\n\nI think that would work. I was hoping to avoid sed, and I was thinking \nabout using shell's ${parameter#word} to strip and test for matching \nprefixes. But I can't think of a way to do that with \nGIT_COMPLETION_IGNORE_CASE. Modern bash has ${parameter@L} to lowercase \na string, but I don't think the version of bash on macos has that. With \nsed it would just be a matter of adding the i flag if needed.\n\nI don't think this is an issue, but do we need to worry about putting \nremote names and $cur_ on the command line where any other user on the \nsame system can read them? $cur_ was already on the command line before \nthis patch, and remote names appear on other git command lines all the \ntime, so I doubt they're meant to be secret.\n\nIs a Helped-by trailer the right way to credit you for your suggestions \non this?\n"},{"id":"513424","messageId":"Z8YEIo28bLS+t+20@szeder.dev","threadId":"63039","inReplyTo":"2323bb52-f43d-4f40-8955-4c648677a93e@mandelberg.org","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2025-03-03T19:33:54Z","receivedAt":"2025-03-03T19:33:59Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Sun, Mar 02, 2025 at 03:34:07PM -0500, David Mandelberg wrote:\n> Op 2025-03-02 om 09:17 schreef Phillip Wood:\n> > Hi David\n> > \n> > On 02/03/2025 07:45, David Mandelberg via GitGitGadget wrote:\n> > > From: David Mandelberg <david@mandelberg.org>\n> > > \n> > > Previously, some calls to for-each-ref passed fixed numbers of path\n> > > components to strip from refs, assuming that remote names had no slashes\n> > > in them. This made completions like:\n> > > \n> > > git push github/dseomn :com<Tab>\n> > > \n> > > Result in:\n> > > \n> > > git push github/dseomn :dseomn/completion-remote-slash\n> > > \n> > > With this patch, it instead results in:\n> > > \n> > > git push github/dseomn :completion-remote-slash\n> > \n> > This sounds like a useful improvement and I like the idea, but I think\n> > running \"git for-each-ref\" once for each remote is not going to scale\n> > very well for people who have a lot of remotes. I think it would be\n> > better to try and strip \"refs/remote/$remote/\" outside of \"git for-each-\n> > ref\". I've not tested it but I think something like\n> \n> Good point, I hadn't thought of that. Do you have a rough estimate of what\n> \"a lot of remotes\" is? 100ish, maybe?\n\n5.\n\nIn Git for Windows fork()-ing subshells and fork()+exec()-ing\nprocesses is rather costly, about an order of magnitude slower than on\nLinux.  The rough equivalent of the body of your loop, with two\nsubshells and a git process:\n\n  time { a=$(echo 1) ; b=$(echo 2) ; git for-each-ref >/dev/null ; }\n\ntakes on average 0.17s on a windows box I have access to (with fully\npacked refs, and merely 4 refs in total).  So guess at about 4-5\nremotes it would take over a second to react to my TABs...\n\nI would rather try to go in the opposite direction to see whether 'git\nfor-each-ref' could be taught to strip the \"refs/remote/$remote/\"\nprefix with a format specifier option like '%(refname:strip=remote)'.\n\nThat would surely be faster than any shell filtering we might come up\nwith, and would also save us from the trouble of escaping glob and/or\nregex metacharacters for shell/sed pattern matching.\n\n> I'd like to do some testing to get\n> actual performance numbers before trying to optimize this, because I think\n> the optimization has some drawbacks, see below.\n> \n> If optimization is needed, another approach is to parallelize the forks:\n> \n> {\n> \tlocal fer_pids=\n> \tfor ...\n> \tdo\n> \t\t__git for-each-ref ... &\n> \t\tfer_pids=\"$fer_pids $!\"\n> \tdone\n> \ttest -z \"$fer_pids\" || wait $fer_pids\n> } | sort | uniq -u\n> \n> That might cause spikes in cpu/memory/disk usage that aren't ideal though.\n\nPlease don't do this, the completion script must not forkbomb the\nsystem.\n\n"},{"id":"513425","messageId":"Z8YGY1QMaIsQTXoS@teonanacatl.net","threadId":"63039","inReplyTo":"42dfc06e-8eb1-46da-9971-9d102f1390ad@mandelberg.org","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"Todd Zullinger","fromEmail":"tmz@pobox.com","sentAt":"2025-03-03T19:43:31Z","receivedAt":"2025-03-03T19:43:34Z","isPatch":true,"sender":{"key":"tmz@pobox.com","avatar":"https://avatars.githubusercontent.com/u/806319?v=4"},"body":"David Mandelberg wrote:\n> I think that would work. I was hoping to avoid sed, and I was thinking about\n> using shell's ${parameter#word} to strip and test for matching prefixes. But\n> I can't think of a way to do that with GIT_COMPLETION_IGNORE_CASE. Modern\n> bash has ${parameter@L} to lowercase a string, but I don't think the version\n> of bash on macos has that. With sed it would just be a matter of adding the\n> i flag if needed.\n\nI haven't followed this discussion closely, but you can\nalso use ${parameter,,} to do parameter case modification.\n\nSadly, it looks like those were added in bash-4.0, so Apple\nwill have kept their users from benefiting from that to\navoid GPLv3 code. :/\n\nI don't have an ancient bash-3.x like MacOS to test, but\nmaybe they've backported some features?\n\nThough if you do end up using this, ${parameter,,} will work\nin a much wider range of bash version that ${parameter@L}\nwhich covers various releases in the OS's we aim to target\n(e.g. RHEL 8.x, which has bash-4.4).\n\n-- \nTodd\n"},{"id":"513426","messageId":"ef5bbdad-2e40-4101-ad48-487019028d45@mandelberg.org","threadId":"63039","inReplyTo":"Z8YEIo28bLS+t+20@szeder.dev","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-03T19:49:24Z","receivedAt":"2025-03-03T19:49:25Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Op 2025-03-03 om 14:33 schreef SZEDER Gábor:\n> On Sun, Mar 02, 2025 at 03:34:07PM -0500, David Mandelberg wrote:\n>> Good point, I hadn't thought of that. Do you have a rough estimate of what\n>> \"a lot of remotes\" is? 100ish, maybe?\n> \n> 5.\n> \n> In Git for Windows fork()-ing subshells and fork()+exec()-ing\n> processes is rather costly, about an order of magnitude slower than on\n> Linux.  The rough equivalent of the body of your loop, with two\n> subshells and a git process:\n> \n>    time { a=$(echo 1) ; b=$(echo 2) ; git for-each-ref >/dev/null ; }\n> \n> takes on average 0.17s on a windows box I have access to (with fully\n> packed refs, and merely 4 refs in total).  So guess at about 4-5\n> remotes it would take over a second to react to my TABs...\n\nOh wow, that's much slower than I expected, thanks for the numbers!\n\n> I would rather try to go in the opposite direction to see whether 'git\n> for-each-ref' could be taught to strip the \"refs/remote/$remote/\"\n> prefix with a format specifier option like '%(refname:strip=remote)'.\n> \n> That would surely be faster than any shell filtering we might come up\n> with, and would also save us from the trouble of escaping glob and/or\n> regex metacharacters for shell/sed pattern matching.\n\nThat doesn't fix the issue of needing to anchor the $cur_ part of the \npattern after each refs/remote/$remote/ prefix. It might be possible to \nuse something like %(refname:strip=remote) to find all remote branches \nwith the remote stripped, then filter them for $cur_ in the shell or \nwith sed though.\n\nWhat are the expectations around mixing versions of git itself and files \nin contrib? Can a single patch series add a feature to for-each-ref and \nuse it in git-completion.bash, or does the feature in for-each-ref need \nto exist for some time before git-completion.bash can use it?\n"},{"id":"513432","messageId":"xmqqtt89vovv.fsf@gitster.g","threadId":"63039","inReplyTo":"65d903c0-6b4b-4a55-b7e4-4a277417f0f1@gmail.com","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-03-03T20:29:56Z","receivedAt":"2025-03-03T20:30:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"phillip.wood123@gmail.com writes:\n\n>>      local fer_pids=\n>>      for ...\n>>      do\n>>          __git for-each-ref ... &\n>>          fer_pids=\"$fer_pids $!\"\n>>      done\n>>      test -z \"$fer_pids\" || wait $fer_pids\n>> } | sort | uniq -u\n>> That might cause spikes in cpu/memory/disk usage that aren't ideal\n>> though.\n>\n> Yes, if there were a 100 remotes that's a bit of a fork-bomb.\n\nAnd they are all competing for a shared common resource for read\naccess.\n\nI wonder if a single invocation of for-each-ref for refs/remotes/\nhierarchy would be sufficient?  That is, the calling shell script\ncan set up a shell function \"foo\" and then\n\n    eval $(for-each-ref --format='foo %(refname)' --shell refs/remotes/)\n\nand \"foo\" can do its thing avoiding as much forks as possible.  As\nyou are allowed bash-ism in completion script, you do not have to\nlimit yourself to POSIX parameter substitution but can use substring\nexpansion ${parameter:offset:length} and pattern substitution\n${parameter/pattern/string} and other kind of bash exotics, as well\nas shell arrays (e.g., define an array __git__remote_branches[] for\neach remote, which may contain the remote-tracking branches for the\nremote).\n\n\n\n"},{"id":"513586","messageId":"d5860dbe1e6a149d72739af3271369b3@mandelberg.org","threadId":"63039","inReplyTo":"pull.1901.git.git.1740901525.gitgitgadget@gmail.com","subject":"[PATCH v2 0/2] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-05T00:07:27Z","receivedAt":"2025-03-05T00:07:29Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Thanks everyone for all the reviews! I rewrote __git_dwim_remote_heads\nto fork a constant number of times instead of a multiple of the number\nof remotes. There are still a handful of forks, but I think they can\nmostly be parallelized.\n\nI tried to find a pure shell implementation like a few of you mentioned,\nbut I couldn't think of one that would work in both bash 3.2 (the\nversion on macOS, I think?) and zsh. The only thing I found in\nhttps://www.bashcookbook.com/bashinfo/source/bash-3.2/doc/bash.html that\nlooked like it would work to implement GIT_COMPLETION_IGNORE_CASE was\nthe nocasematch arg to shopt, but\nhttps://zsh.sourceforge.io/Doc/Release/zsh_14.html#index_split-13_fn_letter-S\ndoesn't list shopt at all.\n\nOne unintentional difference with this approach is that $cur_ is no\nlonger treated as a glob pattern. Was it supposed to be in the first\nplace though, or was that just because of missing escaping? I.e., this\nno longer works to complete unique-remote-branch:\n\ngit checkout unique-remote-br*<Tab>\n\nIn my original commit message, I mentioned that HEAD was no longer\nlisted, but I think I was wrong about that. I think HEAD went away in a\nbunch of the tests because I added another remote, so HEAD was no longer\nunique. Maybe HEAD should be filtered out too, but that seems like a\nseparate issue for a separate patch.\n\nDavid Mandelberg (2):\n  completion: add helper to count path components\n  completion: fix bugs with slashes in remote names\n\n contrib/completion/git-completion.bash |  49 +++++-\n t/t9902-completion.sh                  | 206 ++++++++++++++++++++++---\n 2 files changed, 226 insertions(+), 29 deletions(-)\n\n-- \n2.47.2\n\n"},{"id":"513587","messageId":"416a3f1afbacb75ea2e24de29fab8f3a@mandelberg.org","threadId":"63039","inReplyTo":"d5860dbe1e6a149d72739af3271369b3@mandelberg.org","subject":"[PATCH v2 1/2] completion: add helper to count path components","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-05T00:08:25Z","receivedAt":"2025-03-05T00:08:28Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"A follow-up commit will use this with for-each-ref to strip the right\nnumber of path components from refnames.\n\nSigned-off-by: David Mandelberg <david@mandelberg.org>\n---\n contrib/completion/git-completion.bash | 11 +++++++++++\n t/t9902-completion.sh                  | 26 ++++++++++++++++++++++++++\n 2 files changed, 37 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 413911be3b..5fdc71208e 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -234,6 +234,17 @@ __git_dequote ()\n \tdone\n }\n \n+# Prints the number of slash-separated components in a path.\n+# 1: Path to count components of.\n+__git_count_path_components ()\n+{\n+\tlocal path=\"$1\"\n+\tlocal relative=\"${path#/}\"\n+\trelative=\"${relative%/}\"\n+\tlocal slashes=\"/${relative//[^\\/]}\"\n+\techo \"${#slashes}\"\n+}\n+\n # The following function is based on code from:\n #\n #   bash_completion - programmable completion functions for bash 3.2+\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 51bd750837..015289c776 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -455,6 +455,32 @@ test_expect_success '__git_dequote - open double quote' '\n '\n \n \n+test_expect_success '__git_count_path_components - no slashes' '\n+\techo 1 >expected &&\n+\t__git_count_path_components a >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_count_path_components - relative' '\n+\techo 3 >expected &&\n+\t__git_count_path_components a/b/c >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+\n+'\n+\n+test_expect_success '__git_count_path_components - absolute' '\n+\techo 3 >expected &&\n+\t__git_count_path_components /a/b/c >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_count_path_components - trailing slash' '\n+\techo 3 >expected &&\n+\t__git_count_path_components a/b/c/ >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+'\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-- \n2.47.2\n"},{"id":"513588","messageId":"187a63ad2f4a66b644187a8201eadfed@mandelberg.org","threadId":"63039","inReplyTo":"d5860dbe1e6a149d72739af3271369b3@mandelberg.org","subject":"[PATCH v2 2/2] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-05T00:09:28Z","receivedAt":"2025-03-05T00:09:30Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Previously, some calls to for-each-ref passed fixed numbers of path\ncomponents to strip from refs, assuming that remote names had no slashes\nin them. This made completions like:\n\ngit push github/dseomn :com<Tab>\n\nResult in:\n\ngit push github/dseomn :dseomn/completion-remote-slash\n\nWith this patch, it instead results in:\n\ngit push github/dseomn :completion-remote-slash\n\nSigned-off-by: David Mandelberg <david@mandelberg.org>\n---\n contrib/completion/git-completion.bash |  38 +++++-\n t/t9902-completion.sh                  | 180 ++++++++++++++++++++++---\n 2 files changed, 189 insertions(+), 29 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 5fdc71208e..450fabc901 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -790,16 +790,39 @@ __git_tags ()\n __git_dwim_remote_heads ()\n {\n \tlocal pfx=\"${1-}\" cur_=\"${2-}\" sfx=\"${3-}\"\n-\tlocal fer_pfx=\"${pfx//\\%/%%}\" # \"escape\" for-each-ref format specifiers\n \n \t# employ the heuristic used by git checkout and git switch\n \t# Try to find a remote branch that cur_es the completion word\n \t# but only output if the branch name is unique\n-\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=3)$sfx\" \\\n-\t\t--sort=\"refname:strip=3\" \\\n-\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n-\t\t\"refs/remotes/*/$cur_*\" \"refs/remotes/*/$cur_*/**\" | \\\n-\tuniq -u\n+\tlocal awk_script='\n+\tfunction casemap(s) {\n+\t\tif (ENVIRON[\"IGNORE_CASE\"])\n+\t\t\treturn tolower(s)\n+\t\telse\n+\t\t\treturn s\n+\t}\n+\tBEGIN {\n+\t\tsplit(ENVIRON[\"REMOTES\"], remotes, /\\n/)\n+\t\tfor (i in remotes)\n+\t\t\tremotes[i] = \"refs/remotes/\" casemap(remotes[i])\n+\t\tcur_ = casemap(ENVIRON[\"CUR_\"])\n+\t}\n+\t{\n+\t\tref_case = casemap($0)\n+\t\tfor (i in remotes) {\n+\t\t\tif (index(ref_case, remotes[i] \"/\" cur_) == 1) {\n+\t\t\t\tbranch = substr($0, length(remotes[i] \"/\") + 1)\n+\t\t\t\tprint ENVIRON[\"PFX\"] branch ENVIRON[\"SFX\"]\n+\t\t\t\tbreak\n+\t\t\t}\n+\t\t}\n+\t}\n+\t'\n+\t__git for-each-ref --format='%(refname)' |\n+\t\tPFX=\"$pfx\" SFX=\"$sfx\" CUR_=\"$cur_\" \\\n+\t\t\tIGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \\\n+\t\t\tREMOTES=\"$(__git_remotes | sort -r)\" awk \"$awk_script\" |\n+\t\tsort | uniq -u\n }\n \n # Lists refs from the local (by default) or from a remote repository.\n@@ -905,7 +928,8 @@ __git_refs ()\n \t\t\tcase \"HEAD\" in\n \t\t\t$match*|$umatch*)\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\tlocal strip=\"$(__git_count_path_components \"refs/remotes/$remote\")\"\n+\t\t\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=$strip)$sfx\" \\\n \t\t\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n \t\t\t\t\"refs/remotes/$remote/$match*\" \\\n \t\t\t\t\"refs/remotes/$remote/$match*/**\"\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 015289c776..343b8cd191 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -149,7 +149,8 @@ fi\n test_expect_success 'setup for __git_find_repo_path/__gitdir tests' '\n \tmkdir -p subdir/subsubdir &&\n \tmkdir -p non-repo &&\n-\tgit init -b main otherrepo\n+\tgit init -b main otherrepo &&\n+\tgit init -b main slashrepo\n '\n \n test_expect_success '__git_find_repo_path - from command line (through $__git_dir)' '\n@@ -674,6 +675,13 @@ test_expect_success 'setup for ref completion' '\n \t) &&\n \tgit remote add other \"$ROOT/otherrepo/.git\" &&\n \tgit fetch --no-tags other &&\n+\t(\n+\t\tcd slashrepo &&\n+\t\tgit commit --allow-empty -m initial &&\n+\t\tgit branch -m main branch/with/slash\n+\t) &&\n+\tgit remote add remote/with/slash \"$ROOT/slashrepo/.git\" &&\n+\tgit fetch --no-tags remote/with/slash &&\n \trm -f .git/FETCH_HEAD &&\n \tgit init thirdrepo\n '\n@@ -686,6 +694,8 @@ test_expect_success '__git_refs - simple' '\n \tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n \tEOF\n \t(\n@@ -702,6 +712,8 @@ test_expect_success '__git_refs - full refs' '\n \trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n+\trefs/remotes/remote/with/slash/HEAD\n+\trefs/remotes/remote/with/slash/branch/with/slash\n \trefs/tags/matching-tag\n \tEOF\n \t(\n@@ -767,6 +779,19 @@ test_expect_success '__git_refs - configured remote' '\n \ttest_cmp expected \"$actual\"\n '\n \n+test_expect_success '__git_refs - configured remote - with slash' '\n+\tcat >expected <<-EOF &&\n+\tHEAD\n+\tHEAD\n+\tbranch/with/slash\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_refs remote/with/slash >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n test_expect_success '__git_refs - configured remote - full refs' '\n \tcat >expected <<-EOF &&\n \tHEAD\n@@ -909,17 +934,19 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \tother/ambiguous\n \tother/branch-in-other\n \tother/main-in-other\n-\tremote/ambiguous\n-\tremote/branch-in-remote\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/ambiguous\n+\tremote/with/slash/branch-in-remote\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n-\tHEAD\n \tbranch-in-other\n \tbranch-in-remote\n+\tbranch/with/slash\n \tmain-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+\t\trefs/remotes/remote/with/slash/ambiguous \\\n+\t\trefs/remotes/remote/with/slash/branch-in-remote\n \tdo\n \t\tgit update-ref $remote_ref main &&\n \t\ttest_when_finished \"git update-ref -d $remote_ref\" || return 1\n@@ -939,6 +966,8 @@ test_expect_success '__git_refs - after --opt=' '\n \tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n \tEOF\n \t(\n@@ -955,6 +984,8 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n+\trefs/remotes/remote/with/slash/HEAD\n+\trefs/remotes/remote/with/slash/branch/with/slash\n \trefs/tags/matching-tag\n \tEOF\n \t(\n@@ -972,6 +1003,8 @@ test_expect_success '__git refs - excluding refs' '\n \t^other/HEAD\n \t^other/branch-in-other\n \t^other/main-in-other\n+\t^remote/with/slash/HEAD\n+\t^remote/with/slash/branch/with/slash\n \t^matching-tag\n \tEOF\n \t(\n@@ -988,6 +1021,8 @@ test_expect_success '__git refs - excluding full refs' '\n \t^refs/remotes/other/HEAD\n \t^refs/remotes/other/branch-in-other\n \t^refs/remotes/other/main-in-other\n+\t^refs/remotes/remote/with/slash/HEAD\n+\t^refs/remotes/remote/with/slash/branch/with/slash\n \t^refs/tags/matching-tag\n \tEOF\n \t(\n@@ -1015,6 +1050,8 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n \tother/branch-in-other\n \tother/main-in-other\n \tother/matching/branch-in-other\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n \tmatching/tag\n \tEOF\n@@ -1135,6 +1172,8 @@ test_expect_success '__git_complete_refs - simple' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tmatching-tag Z\n \tEOF\n \t(\n@@ -1173,6 +1212,20 @@ test_expect_success '__git_complete_refs - remote' '\n \ttest_cmp expected out\n '\n \n+test_expect_success '__git_complete_refs - remote - with slash' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tHEAD Z\n+\tHEAD Z\n+\tbranch/with/slash Z\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_complete_refs --remote=remote/with/slash &&\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@@ -1181,9 +1234,11 @@ test_expect_success '__git_complete_refs - track' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tmatching-tag Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain-in-other Z\n \tEOF\n \t(\n@@ -1228,6 +1283,8 @@ test_expect_success '__git_complete_refs - suffix' '\n \tother/HEAD.\n \tother/branch-in-other.\n \tother/main-in-other.\n+\tremote/with/slash/HEAD.\n+\tremote/with/slash/branch/with/slash.\n \tmatching-tag.\n \tEOF\n \t(\n@@ -1253,6 +1310,20 @@ test_expect_success '__git_complete_fetch_refspecs - simple' '\n \ttest_cmp expected out\n '\n \n+test_expect_success '__git_complete_fetch_refspecs - with slash' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tHEAD:HEAD Z\n+\tHEAD:HEAD Z\n+\tbranch/with/slash:branch/with/slash Z\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_complete_fetch_refspecs remote/with/slash &&\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@@ -1333,8 +1404,8 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n \n test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n \ttest_completion \"git switch \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1480,8 +1551,8 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1489,6 +1560,8 @@ test_expect_success 'git checkout - completes refs and unique remote branches fo\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1508,8 +1581,8 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n \n test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1518,8 +1591,8 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n \n test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1542,14 +1615,16 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1557,6 +1632,8 @@ test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1,\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1569,14 +1646,16 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1584,6 +1663,8 @@ test_expect_success 'git checkout - a later --guess overrides previous --no-gues\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1596,6 +1677,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1609,6 +1692,8 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1616,8 +1701,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \ttest_config checkout.guess true &&\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1625,6 +1710,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1632,8 +1719,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \ttest_config checkout.guess false &&\n \ttest_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1641,6 +1728,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1654,6 +1743,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1666,6 +1757,8 @@ test_expect_success 'git switch - with --detach, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1678,6 +1771,8 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1850,6 +1945,8 @@ test_expect_success 'git switch - with -d, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1862,6 +1959,8 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1870,11 +1969,15 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n \ttest_completion \"git switch -t \" <<-\\EOF\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1883,11 +1986,15 @@ test_expect_success 'git checkout - with --track, complete only remote branches'\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n \ttest_completion \"git checkout -t \" <<-\\EOF\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1907,6 +2014,8 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1919,6 +2028,8 @@ test_expect_success 'git switch - with -c, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1931,6 +2042,8 @@ test_expect_success 'git switch - with -C, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1943,6 +2056,8 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1955,6 +2070,8 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1967,6 +2084,8 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1979,6 +2098,8 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1991,6 +2112,8 @@ test_expect_success 'git checkout - with -b, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2003,6 +2126,8 @@ test_expect_success 'git checkout - with -B, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2015,6 +2140,8 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2027,6 +2154,8 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2039,6 +2168,8 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2051,13 +2182,15 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -c \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2066,8 +2199,8 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n \n test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -C \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2104,8 +2237,8 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n \n test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -b \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2114,8 +2247,8 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n \n test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -B \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2152,8 +2285,8 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n \n test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git switch --orphan \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2168,8 +2301,8 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n \n test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git checkout --orphan \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2185,6 +2318,8 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2199,7 +2334,8 @@ test_expect_success 'git restore completes modified files' '\n test_expect_success 'teardown after ref completion' '\n \tgit branch -d matching-branch &&\n \tgit tag -d matching-tag &&\n-\tgit remote remove other\n+\tgit remote remove other &&\n+\tgit remote remove remote/with/slash\n '\n \n \n-- \n2.47.2\n"},{"id":"513616","messageId":"fa70c8336f836ebb5b9c196fe291d357@mandelberg.org","threadId":"63039","inReplyTo":"187a63ad2f4a66b644187a8201eadfed@mandelberg.org","subject":"Re: [PATCH v2 2/2] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-05T20:50:20Z","receivedAt":"2025-03-05T20:50:21Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"David Mandelberg schreef op 2025-03-04 19:09:\n> Previously, some calls to for-each-ref passed fixed numbers of path\n> components to strip from refs, assuming that remote names had no slashes\n> in them. This made completions like:\n> \n> git push github/dseomn :com<Tab>\n> \n> Result in:\n> \n> git push github/dseomn :dseomn/completion-remote-slash\n> \n> With this patch, it instead results in:\n> \n> git push github/dseomn :completion-remote-slash\n> \n> Signed-off-by: David Mandelberg <david@mandelberg.org>\n> ---\n>  contrib/completion/git-completion.bash |  38 +++++-\n>  t/t9902-completion.sh                  | 180 ++++++++++++++++++++++---\n>  2 files changed, 189 insertions(+), 29 deletions(-)\n> \n> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> index 5fdc71208e..450fabc901 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -790,16 +790,39 @@ __git_tags ()\n>  __git_dwim_remote_heads ()\n>  {\n>  \tlocal pfx=\"${1-}\" cur_=\"${2-}\" sfx=\"${3-}\"\n> -\tlocal fer_pfx=\"${pfx//\\%/%%}\" # \"escape\" for-each-ref format specifiers\n>  \n>  \t# employ the heuristic used by git checkout and git switch\n>  \t# Try to find a remote branch that cur_es the completion word\n>  \t# but only output if the branch name is unique\n> -\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=3)$sfx\" \\\n> -\t\t--sort=\"refname:strip=3\" \\\n> -\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n> -\t\t\"refs/remotes/*/$cur_*\" \"refs/remotes/*/$cur_*/**\" | \\\n> -\tuniq -u\n> +\tlocal awk_script='\n> +\tfunction casemap(s) {\n> +\t\tif (ENVIRON[\"IGNORE_CASE\"])\n> +\t\t\treturn tolower(s)\n> +\t\telse\n> +\t\t\treturn s\n> +\t}\n> +\tBEGIN {\n> +\t\tsplit(ENVIRON[\"REMOTES\"], remotes, /\\n/)\n> +\t\tfor (i in remotes)\n> +\t\t\tremotes[i] = \"refs/remotes/\" casemap(remotes[i])\n> +\t\tcur_ = casemap(ENVIRON[\"CUR_\"])\n> +\t}\n> +\t{\n> +\t\tref_case = casemap($0)\n> +\t\tfor (i in remotes) {\n> +\t\t\tif (index(ref_case, remotes[i] \"/\" cur_) == 1) {\n> +\t\t\t\tbranch = substr($0, length(remotes[i] \"/\") + 1)\n> +\t\t\t\tprint ENVIRON[\"PFX\"] branch ENVIRON[\"SFX\"]\n> +\t\t\t\tbreak\n> +\t\t\t}\n> +\t\t}\n> +\t}\n> +\t'\n> +\t__git for-each-ref --format='%(refname)' |\n> +\t\tPFX=\"$pfx\" SFX=\"$sfx\" CUR_=\"$cur_\" \\\n> +\t\t\tIGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \\\n> +\t\t\tREMOTES=\"$(__git_remotes | sort -r)\" awk \"$awk_script\" |\n> +\t\tsort | uniq -u\n\nI realized that this sends refs to awk that it doesn't need to. I'll\napply this diff to the next version of this patch:\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 450fabc901..c9d014070c 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -818,7 +818,7 @@ __git_dwim_remote_heads ()\n                }\n        }\n        '\n-       __git for-each-ref --format='%(refname)' |\n+       __git for-each-ref --format='%(refname)' 'refs/remotes/**' |\n                PFX=\"$pfx\" SFX=\"$sfx\" CUR_=\"$cur_\" \\\n                        IGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \\\n                        REMOTES=\"$(__git_remotes | sort -r)\" awk \"$awk_script\" |\n\n>  }\n>  \n>  # Lists refs from the local (by default) or from a remote repository.\n> @@ -905,7 +928,8 @@ __git_refs ()\n>  \t\t\tcase \"HEAD\" in\n>  \t\t\t$match*|$umatch*)\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\tlocal strip=\"$(__git_count_path_components \"refs/remotes/$remote\")\"\n> +\t\t\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=$strip)$sfx\" \\\n>  \t\t\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n>  \t\t\t\t\"refs/remotes/$remote/$match*\" \\\n>  \t\t\t\t\"refs/remotes/$remote/$match*/**\"\n> diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\n> index 015289c776..343b8cd191 100755\n> --- a/t/t9902-completion.sh\n> +++ b/t/t9902-completion.sh\n> @@ -149,7 +149,8 @@ fi\n>  test_expect_success 'setup for __git_find_repo_path/__gitdir tests' '\n>  \tmkdir -p subdir/subsubdir &&\n>  \tmkdir -p non-repo &&\n> -\tgit init -b main otherrepo\n> +\tgit init -b main otherrepo &&\n> +\tgit init -b main slashrepo\n>  '\n>  \n>  test_expect_success '__git_find_repo_path - from command line (through $__git_dir)' '\n> @@ -674,6 +675,13 @@ test_expect_success 'setup for ref completion' '\n>  \t) &&\n>  \tgit remote add other \"$ROOT/otherrepo/.git\" &&\n>  \tgit fetch --no-tags other &&\n> +\t(\n> +\t\tcd slashrepo &&\n> +\t\tgit commit --allow-empty -m initial &&\n> +\t\tgit branch -m main branch/with/slash\n> +\t) &&\n> +\tgit remote add remote/with/slash \"$ROOT/slashrepo/.git\" &&\n> +\tgit fetch --no-tags remote/with/slash &&\n>  \trm -f .git/FETCH_HEAD &&\n>  \tgit init thirdrepo\n>  '\n> @@ -686,6 +694,8 @@ test_expect_success '__git_refs - simple' '\n>  \tother/HEAD\n>  \tother/branch-in-other\n>  \tother/main-in-other\n> +\tremote/with/slash/HEAD\n> +\tremote/with/slash/branch/with/slash\n>  \tmatching-tag\n>  \tEOF\n>  \t(\n> @@ -702,6 +712,8 @@ test_expect_success '__git_refs - full refs' '\n>  \trefs/remotes/other/HEAD\n>  \trefs/remotes/other/branch-in-other\n>  \trefs/remotes/other/main-in-other\n> +\trefs/remotes/remote/with/slash/HEAD\n> +\trefs/remotes/remote/with/slash/branch/with/slash\n>  \trefs/tags/matching-tag\n>  \tEOF\n>  \t(\n> @@ -767,6 +779,19 @@ test_expect_success '__git_refs - configured remote' '\n>  \ttest_cmp expected \"$actual\"\n>  '\n>  \n> +test_expect_success '__git_refs - configured remote - with slash' '\n> +\tcat >expected <<-EOF &&\n> +\tHEAD\n> +\tHEAD\n> +\tbranch/with/slash\n> +\tEOF\n> +\t(\n> +\t\tcur= &&\n> +\t\t__git_refs remote/with/slash >\"$actual\"\n> +\t) &&\n> +\ttest_cmp expected \"$actual\"\n> +'\n> +\n>  test_expect_success '__git_refs - configured remote - full refs' '\n>  \tcat >expected <<-EOF &&\n>  \tHEAD\n> @@ -909,17 +934,19 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n>  \tother/ambiguous\n>  \tother/branch-in-other\n>  \tother/main-in-other\n> -\tremote/ambiguous\n> -\tremote/branch-in-remote\n> +\tremote/with/slash/HEAD\n> +\tremote/with/slash/ambiguous\n> +\tremote/with/slash/branch-in-remote\n> +\tremote/with/slash/branch/with/slash\n>  \tmatching-tag\n> -\tHEAD\n>  \tbranch-in-other\n>  \tbranch-in-remote\n> +\tbranch/with/slash\n>  \tmain-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> +\t\trefs/remotes/remote/with/slash/ambiguous \\\n> +\t\trefs/remotes/remote/with/slash/branch-in-remote\n>  \tdo\n>  \t\tgit update-ref $remote_ref main &&\n>  \t\ttest_when_finished \"git update-ref -d $remote_ref\" || return 1\n> @@ -939,6 +966,8 @@ test_expect_success '__git_refs - after --opt=' '\n>  \tother/HEAD\n>  \tother/branch-in-other\n>  \tother/main-in-other\n> +\tremote/with/slash/HEAD\n> +\tremote/with/slash/branch/with/slash\n>  \tmatching-tag\n>  \tEOF\n>  \t(\n> @@ -955,6 +984,8 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n>  \trefs/remotes/other/HEAD\n>  \trefs/remotes/other/branch-in-other\n>  \trefs/remotes/other/main-in-other\n> +\trefs/remotes/remote/with/slash/HEAD\n> +\trefs/remotes/remote/with/slash/branch/with/slash\n>  \trefs/tags/matching-tag\n>  \tEOF\n>  \t(\n> @@ -972,6 +1003,8 @@ test_expect_success '__git refs - excluding refs' '\n>  \t^other/HEAD\n>  \t^other/branch-in-other\n>  \t^other/main-in-other\n> +\t^remote/with/slash/HEAD\n> +\t^remote/with/slash/branch/with/slash\n>  \t^matching-tag\n>  \tEOF\n>  \t(\n> @@ -988,6 +1021,8 @@ test_expect_success '__git refs - excluding full refs' '\n>  \t^refs/remotes/other/HEAD\n>  \t^refs/remotes/other/branch-in-other\n>  \t^refs/remotes/other/main-in-other\n> +\t^refs/remotes/remote/with/slash/HEAD\n> +\t^refs/remotes/remote/with/slash/branch/with/slash\n>  \t^refs/tags/matching-tag\n>  \tEOF\n>  \t(\n> @@ -1015,6 +1050,8 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n>  \tother/branch-in-other\n>  \tother/main-in-other\n>  \tother/matching/branch-in-other\n> +\tremote/with/slash/HEAD\n> +\tremote/with/slash/branch/with/slash\n>  \tmatching-tag\n>  \tmatching/tag\n>  \tEOF\n> @@ -1135,6 +1172,8 @@ test_expect_success '__git_complete_refs - simple' '\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tmatching-tag Z\n>  \tEOF\n>  \t(\n> @@ -1173,6 +1212,20 @@ test_expect_success '__git_complete_refs - remote' '\n>  \ttest_cmp expected out\n>  '\n>  \n> +test_expect_success '__git_complete_refs - remote - with slash' '\n> +\tsed -e \"s/Z$//\" >expected <<-EOF &&\n> +\tHEAD Z\n> +\tHEAD Z\n> +\tbranch/with/slash Z\n> +\tEOF\n> +\t(\n> +\t\tcur= &&\n> +\t\t__git_complete_refs --remote=remote/with/slash &&\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> @@ -1181,9 +1234,11 @@ test_expect_success '__git_complete_refs - track' '\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tmatching-tag Z\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain-in-other Z\n>  \tEOF\n>  \t(\n> @@ -1228,6 +1283,8 @@ test_expect_success '__git_complete_refs - suffix' '\n>  \tother/HEAD.\n>  \tother/branch-in-other.\n>  \tother/main-in-other.\n> +\tremote/with/slash/HEAD.\n> +\tremote/with/slash/branch/with/slash.\n>  \tmatching-tag.\n>  \tEOF\n>  \t(\n> @@ -1253,6 +1310,20 @@ test_expect_success '__git_complete_fetch_refspecs - simple' '\n>  \ttest_cmp expected out\n>  '\n>  \n> +test_expect_success '__git_complete_fetch_refspecs - with slash' '\n> +\tsed -e \"s/Z$//\" >expected <<-EOF &&\n> +\tHEAD:HEAD Z\n> +\tHEAD:HEAD Z\n> +\tbranch/with/slash:branch/with/slash Z\n> +\tEOF\n> +\t(\n> +\t\tcur= &&\n> +\t\t__git_complete_fetch_refspecs remote/with/slash &&\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> @@ -1333,8 +1404,8 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n>  \n>  test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n>  \ttest_completion \"git switch \" <<-\\EOF\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain Z\n>  \tmain-in-other Z\n>  \tmatching-branch Z\n> @@ -1480,8 +1551,8 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n>  test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n>  \ttest_completion \"git checkout \" <<-\\EOF\n>  \tHEAD Z\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain Z\n>  \tmain-in-other Z\n>  \tmatching-branch Z\n> @@ -1489,6 +1560,8 @@ test_expect_success 'git checkout - completes refs and unique remote branches fo\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1508,8 +1581,8 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n>  \n>  test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n>  \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain Z\n>  \tmain-in-other Z\n>  \tmatching-branch Z\n> @@ -1518,8 +1591,8 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n>  \n>  test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n>  \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain Z\n>  \tmain-in-other Z\n>  \tmatching-branch Z\n> @@ -1542,14 +1615,16 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n>  test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n>  \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n>  \tHEAD Z\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain Z\n>  \tmain-in-other Z\n>  \tmatching-branch Z\n> @@ -1557,6 +1632,8 @@ test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1,\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1569,14 +1646,16 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n>  test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n>  \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n>  \tHEAD Z\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain Z\n>  \tmain-in-other Z\n>  \tmatching-branch Z\n> @@ -1584,6 +1663,8 @@ test_expect_success 'git checkout - a later --guess overrides previous --no-gues\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1596,6 +1677,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1609,6 +1692,8 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1616,8 +1701,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n>  \ttest_config checkout.guess true &&\n>  \ttest_completion \"git checkout \" <<-\\EOF\n>  \tHEAD Z\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain Z\n>  \tmain-in-other Z\n>  \tmatching-branch Z\n> @@ -1625,6 +1710,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1632,8 +1719,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n>  \ttest_config checkout.guess false &&\n>  \ttest_completion \"git checkout --guess \" <<-\\EOF\n>  \tHEAD Z\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain Z\n>  \tmain-in-other Z\n>  \tmatching-branch Z\n> @@ -1641,6 +1728,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1654,6 +1743,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1666,6 +1757,8 @@ test_expect_success 'git switch - with --detach, complete all references' '\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1678,6 +1771,8 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1850,6 +1945,8 @@ test_expect_success 'git switch - with -d, complete all references' '\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1862,6 +1959,8 @@ test_expect_success 'git checkout - with -d, complete only references' '\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1870,11 +1969,15 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  \ttest_completion \"git switch -t \" <<-\\EOF\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1883,11 +1986,15 @@ test_expect_success 'git checkout - with --track, complete only remote branches'\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  \ttest_completion \"git checkout -t \" <<-\\EOF\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1907,6 +2014,8 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1919,6 +2028,8 @@ test_expect_success 'git switch - with -c, complete all references' '\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1931,6 +2042,8 @@ test_expect_success 'git switch - with -C, complete all references' '\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1943,6 +2056,8 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1955,6 +2070,8 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1967,6 +2084,8 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1979,6 +2098,8 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -1991,6 +2112,8 @@ test_expect_success 'git checkout - with -b, complete all references' '\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -2003,6 +2126,8 @@ test_expect_success 'git checkout - with -B, complete all references' '\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -2015,6 +2140,8 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -2027,6 +2154,8 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -2039,6 +2168,8 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -2051,13 +2182,15 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n>  test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n>  \ttest_completion \"git switch -c \" <<-\\EOF\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain Z\n>  \tmain-in-other Z\n>  \tmatching-branch Z\n> @@ -2066,8 +2199,8 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n>  \n>  test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n>  \ttest_completion \"git switch -C \" <<-\\EOF\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain Z\n>  \tmain-in-other Z\n>  \tmatching-branch Z\n> @@ -2104,8 +2237,8 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n>  \n>  test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n>  \ttest_completion \"git checkout -b \" <<-\\EOF\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain Z\n>  \tmain-in-other Z\n>  \tmatching-branch Z\n> @@ -2114,8 +2247,8 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n>  \n>  test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n>  \ttest_completion \"git checkout -B \" <<-\\EOF\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain Z\n>  \tmain-in-other Z\n>  \tmatching-branch Z\n> @@ -2152,8 +2285,8 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n>  \n>  test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n>  \ttest_completion \"git switch --orphan \" <<-\\EOF\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain Z\n>  \tmain-in-other Z\n>  \tmatching-branch Z\n> @@ -2168,8 +2301,8 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n>  \n>  test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n>  \ttest_completion \"git checkout --orphan \" <<-\\EOF\n> -\tHEAD Z\n>  \tbranch-in-other Z\n> +\tbranch/with/slash Z\n>  \tmain Z\n>  \tmain-in-other Z\n>  \tmatching-branch Z\n> @@ -2185,6 +2318,8 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n>  \tother/HEAD Z\n>  \tother/branch-in-other Z\n>  \tother/main-in-other Z\n> +\tremote/with/slash/HEAD Z\n> +\tremote/with/slash/branch/with/slash Z\n>  \tEOF\n>  '\n>  \n> @@ -2199,7 +2334,8 @@ test_expect_success 'git restore completes modified files' '\n>  test_expect_success 'teardown after ref completion' '\n>  \tgit branch -d matching-branch &&\n>  \tgit tag -d matching-tag &&\n> -\tgit remote remove other\n> +\tgit remote remove other &&\n> +\tgit remote remove remote/with/slash\n>  '\n"},{"id":"513717","messageId":"2bed28ca-b106-474d-9bac-db377bc51949@gmail.com","threadId":"63039","inReplyTo":"fa70c8336f836ebb5b9c196fe291d357@mandelberg.org","subject":"Re: [PATCH v2 2/2] completion: fix bugs with slashes in remote names","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-03-06T16:35:26Z","receivedAt":"2025-03-06T16:35:31Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi David\n\nOn 05/03/2025 20:50, David Mandelberg wrote:\n> David Mandelberg schreef op 2025-03-04 19:09:\n>> Previously, some calls to for-each-ref passed fixed numbers of path\n>> components to strip from refs, assuming that remote names had no slashes\n>> in them. This made completions like:\n>>\n>> git push github/dseomn :com<Tab>\n>>\n>> Result in:\n>>\n>> git push github/dseomn :dseomn/completion-remote-slash\n>>\n>> With this patch, it instead results in:\n>>\n>> git push github/dseomn :completion-remote-slash\n>>\n>> Signed-off-by: David Mandelberg <david@mandelberg.org>\n>> ---\n>>   contrib/completion/git-completion.bash |  38 +++++-\n>>   t/t9902-completion.sh                  | 180 ++++++++++++++++++++++---\n>>   2 files changed, 189 insertions(+), 29 deletions(-)\n>>\n>> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n>> index 5fdc71208e..450fabc901 100644\n>> --- a/contrib/completion/git-completion.bash\n>> +++ b/contrib/completion/git-completion.bash\n>> @@ -790,16 +790,39 @@ __git_tags ()\n>>   __git_dwim_remote_heads ()\n>>   {\n>>   \tlocal pfx=\"${1-}\" cur_=\"${2-}\" sfx=\"${3-}\"\n>> -\tlocal fer_pfx=\"${pfx//\\%/%%}\" # \"escape\" for-each-ref format specifiers\n>>   \n>>   \t# employ the heuristic used by git checkout and git switch\n>>   \t# Try to find a remote branch that cur_es the completion word\n>>   \t# but only output if the branch name is unique\n>> -\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=3)$sfx\" \\\n>> -\t\t--sort=\"refname:strip=3\" \\\n>> -\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n>> -\t\t\"refs/remotes/*/$cur_*\" \"refs/remotes/*/$cur_*/**\" | \\\n>> -\tuniq -u\n>> +\tlocal awk_script='\n>> +\tfunction casemap(s) {\n>> +\t\tif (ENVIRON[\"IGNORE_CASE\"])\n>> +\t\t\treturn tolower(s)\n>> +\t\telse\n>> +\t\t\treturn s\n>> +\t}\n>> +\tBEGIN {\n>> +\t\tsplit(ENVIRON[\"REMOTES\"], remotes, /\\n/)\n>> +\t\tfor (i in remotes)\n>> +\t\t\tremotes[i] = \"refs/remotes/\" casemap(remotes[i])\n>> +\t\tcur_ = casemap(ENVIRON[\"CUR_\"])\n>> +\t}\n>> +\t{\n>> +\t\tref_case = casemap($0)\n>> +\t\tfor (i in remotes) {\n>> +\t\t\tif (index(ref_case, remotes[i] \"/\" cur_) == 1) {\n>> +\t\t\t\tbranch = substr($0, length(remotes[i] \"/\") + 1)\n>> +\t\t\t\tprint ENVIRON[\"PFX\"] branch ENVIRON[\"SFX\"]\n>> +\t\t\t\tbreak\n>> +\t\t\t}\n>> +\t\t}\n>> +\t}\n>> +\t'\n>> +\t__git for-each-ref --format='%(refname)' |\n>> +\t\tPFX=\"$pfx\" SFX=\"$sfx\" CUR_=\"$cur_\" \\\n>> +\t\t\tIGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \\\n>> +\t\t\tREMOTES=\"$(__git_remotes | sort -r)\" awk \"$awk_script\" |\n>> +\t\tsort | uniq -u\n\nI wonder if we can use \"sort -u\" here and drop uniq. It isn't quite the \nsame as it will remove entries that are equal according to the current \nlocale but I don't think that should matter.\n\n> I realized that this sends refs to awk that it doesn't need to. I'll\n> apply this diff to the next version of this patch:\n\nWith that tweak this looks good. If there are no glob characters then \n\"git for-each-ref\" does a prefix match so strictly speaking you don't \nneed the '**' but I don't think it matters in practice. I had one \nthought below but if you don't feel like spending more that on this I \nthink what you have here is fine.\n\nThe filtering is O(number of remote refs * number of remotes). If we \ncould sort the list of remotes and remote refs in the same order then we \ncan reduce this to O(number of remote refs + number of remotes) by doing \n(in pseudo code)\n\n     for ($ref in $remote_refs) {\n\twhile (!starts_with($ref, \"refs/remotes/$remote[$i]\"))\n\t\t$i++;\n\tif (starts_with($ref, \"refs/remotes/$remote[$i]/$cur_)\n\t\tprint $ref;\n     }\n\nI think we can get \"git for-each-ref --sort=-refname\" and \"sort -r\" to \nagree on a sorting order by setting LC_COLLATE=C, otherwise \"sort\" will \nsort according the the current locale whereas git sorts by bytes.\n\nBest Wishes\n\nPhillip\n\n> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> index 450fabc901..c9d014070c 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -818,7 +818,7 @@ __git_dwim_remote_heads ()\n>                  }\n>          }\n>          '\n> -       __git for-each-ref --format='%(refname)' |\n> +       __git for-each-ref --format='%(refname)' 'refs/remotes/**' |\n>                  PFX=\"$pfx\" SFX=\"$sfx\" CUR_=\"$cur_\" \\\n>                          IGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \\\n>                          REMOTES=\"$(__git_remotes | sort -r)\" awk \"$awk_script\" |\n> \n>>   }\n>>   \n>>   # Lists refs from the local (by default) or from a remote repository.\n>> @@ -905,7 +928,8 @@ __git_refs ()\n>>   \t\t\tcase \"HEAD\" in\n>>   \t\t\t$match*|$umatch*)\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\tlocal strip=\"$(__git_count_path_components \"refs/remotes/$remote\")\"\n>> +\t\t\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=$strip)$sfx\" \\\n>>   \t\t\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n>>   \t\t\t\t\"refs/remotes/$remote/$match*\" \\\n>>   \t\t\t\t\"refs/remotes/$remote/$match*/**\"\n>> diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\n>> index 015289c776..343b8cd191 100755\n>> --- a/t/t9902-completion.sh\n>> +++ b/t/t9902-completion.sh\n>> @@ -149,7 +149,8 @@ fi\n>>   test_expect_success 'setup for __git_find_repo_path/__gitdir tests' '\n>>   \tmkdir -p subdir/subsubdir &&\n>>   \tmkdir -p non-repo &&\n>> -\tgit init -b main otherrepo\n>> +\tgit init -b main otherrepo &&\n>> +\tgit init -b main slashrepo\n>>   '\n>>   \n>>   test_expect_success '__git_find_repo_path - from command line (through $__git_dir)' '\n>> @@ -674,6 +675,13 @@ test_expect_success 'setup for ref completion' '\n>>   \t) &&\n>>   \tgit remote add other \"$ROOT/otherrepo/.git\" &&\n>>   \tgit fetch --no-tags other &&\n>> +\t(\n>> +\t\tcd slashrepo &&\n>> +\t\tgit commit --allow-empty -m initial &&\n>> +\t\tgit branch -m main branch/with/slash\n>> +\t) &&\n>> +\tgit remote add remote/with/slash \"$ROOT/slashrepo/.git\" &&\n>> +\tgit fetch --no-tags remote/with/slash &&\n>>   \trm -f .git/FETCH_HEAD &&\n>>   \tgit init thirdrepo\n>>   '\n>> @@ -686,6 +694,8 @@ test_expect_success '__git_refs - simple' '\n>>   \tother/HEAD\n>>   \tother/branch-in-other\n>>   \tother/main-in-other\n>> +\tremote/with/slash/HEAD\n>> +\tremote/with/slash/branch/with/slash\n>>   \tmatching-tag\n>>   \tEOF\n>>   \t(\n>> @@ -702,6 +712,8 @@ test_expect_success '__git_refs - full refs' '\n>>   \trefs/remotes/other/HEAD\n>>   \trefs/remotes/other/branch-in-other\n>>   \trefs/remotes/other/main-in-other\n>> +\trefs/remotes/remote/with/slash/HEAD\n>> +\trefs/remotes/remote/with/slash/branch/with/slash\n>>   \trefs/tags/matching-tag\n>>   \tEOF\n>>   \t(\n>> @@ -767,6 +779,19 @@ test_expect_success '__git_refs - configured remote' '\n>>   \ttest_cmp expected \"$actual\"\n>>   '\n>>   \n>> +test_expect_success '__git_refs - configured remote - with slash' '\n>> +\tcat >expected <<-EOF &&\n>> +\tHEAD\n>> +\tHEAD\n>> +\tbranch/with/slash\n>> +\tEOF\n>> +\t(\n>> +\t\tcur= &&\n>> +\t\t__git_refs remote/with/slash >\"$actual\"\n>> +\t) &&\n>> +\ttest_cmp expected \"$actual\"\n>> +'\n>> +\n>>   test_expect_success '__git_refs - configured remote - full refs' '\n>>   \tcat >expected <<-EOF &&\n>>   \tHEAD\n>> @@ -909,17 +934,19 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n>>   \tother/ambiguous\n>>   \tother/branch-in-other\n>>   \tother/main-in-other\n>> -\tremote/ambiguous\n>> -\tremote/branch-in-remote\n>> +\tremote/with/slash/HEAD\n>> +\tremote/with/slash/ambiguous\n>> +\tremote/with/slash/branch-in-remote\n>> +\tremote/with/slash/branch/with/slash\n>>   \tmatching-tag\n>> -\tHEAD\n>>   \tbranch-in-other\n>>   \tbranch-in-remote\n>> +\tbranch/with/slash\n>>   \tmain-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>> +\t\trefs/remotes/remote/with/slash/ambiguous \\\n>> +\t\trefs/remotes/remote/with/slash/branch-in-remote\n>>   \tdo\n>>   \t\tgit update-ref $remote_ref main &&\n>>   \t\ttest_when_finished \"git update-ref -d $remote_ref\" || return 1\n>> @@ -939,6 +966,8 @@ test_expect_success '__git_refs - after --opt=' '\n>>   \tother/HEAD\n>>   \tother/branch-in-other\n>>   \tother/main-in-other\n>> +\tremote/with/slash/HEAD\n>> +\tremote/with/slash/branch/with/slash\n>>   \tmatching-tag\n>>   \tEOF\n>>   \t(\n>> @@ -955,6 +984,8 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n>>   \trefs/remotes/other/HEAD\n>>   \trefs/remotes/other/branch-in-other\n>>   \trefs/remotes/other/main-in-other\n>> +\trefs/remotes/remote/with/slash/HEAD\n>> +\trefs/remotes/remote/with/slash/branch/with/slash\n>>   \trefs/tags/matching-tag\n>>   \tEOF\n>>   \t(\n>> @@ -972,6 +1003,8 @@ test_expect_success '__git refs - excluding refs' '\n>>   \t^other/HEAD\n>>   \t^other/branch-in-other\n>>   \t^other/main-in-other\n>> +\t^remote/with/slash/HEAD\n>> +\t^remote/with/slash/branch/with/slash\n>>   \t^matching-tag\n>>   \tEOF\n>>   \t(\n>> @@ -988,6 +1021,8 @@ test_expect_success '__git refs - excluding full refs' '\n>>   \t^refs/remotes/other/HEAD\n>>   \t^refs/remotes/other/branch-in-other\n>>   \t^refs/remotes/other/main-in-other\n>> +\t^refs/remotes/remote/with/slash/HEAD\n>> +\t^refs/remotes/remote/with/slash/branch/with/slash\n>>   \t^refs/tags/matching-tag\n>>   \tEOF\n>>   \t(\n>> @@ -1015,6 +1050,8 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n>>   \tother/branch-in-other\n>>   \tother/main-in-other\n>>   \tother/matching/branch-in-other\n>> +\tremote/with/slash/HEAD\n>> +\tremote/with/slash/branch/with/slash\n>>   \tmatching-tag\n>>   \tmatching/tag\n>>   \tEOF\n>> @@ -1135,6 +1172,8 @@ test_expect_success '__git_complete_refs - simple' '\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tmatching-tag Z\n>>   \tEOF\n>>   \t(\n>> @@ -1173,6 +1212,20 @@ test_expect_success '__git_complete_refs - remote' '\n>>   \ttest_cmp expected out\n>>   '\n>>   \n>> +test_expect_success '__git_complete_refs - remote - with slash' '\n>> +\tsed -e \"s/Z$//\" >expected <<-EOF &&\n>> +\tHEAD Z\n>> +\tHEAD Z\n>> +\tbranch/with/slash Z\n>> +\tEOF\n>> +\t(\n>> +\t\tcur= &&\n>> +\t\t__git_complete_refs --remote=remote/with/slash &&\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>> @@ -1181,9 +1234,11 @@ test_expect_success '__git_complete_refs - track' '\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tmatching-tag Z\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain-in-other Z\n>>   \tEOF\n>>   \t(\n>> @@ -1228,6 +1283,8 @@ test_expect_success '__git_complete_refs - suffix' '\n>>   \tother/HEAD.\n>>   \tother/branch-in-other.\n>>   \tother/main-in-other.\n>> +\tremote/with/slash/HEAD.\n>> +\tremote/with/slash/branch/with/slash.\n>>   \tmatching-tag.\n>>   \tEOF\n>>   \t(\n>> @@ -1253,6 +1310,20 @@ test_expect_success '__git_complete_fetch_refspecs - simple' '\n>>   \ttest_cmp expected out\n>>   '\n>>   \n>> +test_expect_success '__git_complete_fetch_refspecs - with slash' '\n>> +\tsed -e \"s/Z$//\" >expected <<-EOF &&\n>> +\tHEAD:HEAD Z\n>> +\tHEAD:HEAD Z\n>> +\tbranch/with/slash:branch/with/slash Z\n>> +\tEOF\n>> +\t(\n>> +\t\tcur= &&\n>> +\t\t__git_complete_fetch_refspecs remote/with/slash &&\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>> @@ -1333,8 +1404,8 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n>>   \n>>   test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n>>   \ttest_completion \"git switch \" <<-\\EOF\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain Z\n>>   \tmain-in-other Z\n>>   \tmatching-branch Z\n>> @@ -1480,8 +1551,8 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n>>   test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n>>   \ttest_completion \"git checkout \" <<-\\EOF\n>>   \tHEAD Z\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain Z\n>>   \tmain-in-other Z\n>>   \tmatching-branch Z\n>> @@ -1489,6 +1560,8 @@ test_expect_success 'git checkout - completes refs and unique remote branches fo\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1508,8 +1581,8 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n>>   \n>>   test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n>>   \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain Z\n>>   \tmain-in-other Z\n>>   \tmatching-branch Z\n>> @@ -1518,8 +1591,8 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n>>   \n>>   test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n>>   \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain Z\n>>   \tmain-in-other Z\n>>   \tmatching-branch Z\n>> @@ -1542,14 +1615,16 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>>   test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n>>   \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n>>   \tHEAD Z\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain Z\n>>   \tmain-in-other Z\n>>   \tmatching-branch Z\n>> @@ -1557,6 +1632,8 @@ test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1,\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1569,14 +1646,16 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>>   test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n>>   \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n>>   \tHEAD Z\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain Z\n>>   \tmain-in-other Z\n>>   \tmatching-branch Z\n>> @@ -1584,6 +1663,8 @@ test_expect_success 'git checkout - a later --guess overrides previous --no-gues\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1596,6 +1677,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1609,6 +1692,8 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1616,8 +1701,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n>>   \ttest_config checkout.guess true &&\n>>   \ttest_completion \"git checkout \" <<-\\EOF\n>>   \tHEAD Z\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain Z\n>>   \tmain-in-other Z\n>>   \tmatching-branch Z\n>> @@ -1625,6 +1710,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1632,8 +1719,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n>>   \ttest_config checkout.guess false &&\n>>   \ttest_completion \"git checkout --guess \" <<-\\EOF\n>>   \tHEAD Z\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain Z\n>>   \tmain-in-other Z\n>>   \tmatching-branch Z\n>> @@ -1641,6 +1728,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1654,6 +1743,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1666,6 +1757,8 @@ test_expect_success 'git switch - with --detach, complete all references' '\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1678,6 +1771,8 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1850,6 +1945,8 @@ test_expect_success 'git switch - with -d, complete all references' '\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1862,6 +1959,8 @@ test_expect_success 'git checkout - with -d, complete only references' '\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1870,11 +1969,15 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   \ttest_completion \"git switch -t \" <<-\\EOF\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1883,11 +1986,15 @@ test_expect_success 'git checkout - with --track, complete only remote branches'\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   \ttest_completion \"git checkout -t \" <<-\\EOF\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1907,6 +2014,8 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1919,6 +2028,8 @@ test_expect_success 'git switch - with -c, complete all references' '\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1931,6 +2042,8 @@ test_expect_success 'git switch - with -C, complete all references' '\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1943,6 +2056,8 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1955,6 +2070,8 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1967,6 +2084,8 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1979,6 +2098,8 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -1991,6 +2112,8 @@ test_expect_success 'git checkout - with -b, complete all references' '\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -2003,6 +2126,8 @@ test_expect_success 'git checkout - with -B, complete all references' '\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -2015,6 +2140,8 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -2027,6 +2154,8 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -2039,6 +2168,8 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -2051,13 +2182,15 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>>   test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n>>   \ttest_completion \"git switch -c \" <<-\\EOF\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain Z\n>>   \tmain-in-other Z\n>>   \tmatching-branch Z\n>> @@ -2066,8 +2199,8 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n>>   \n>>   test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n>>   \ttest_completion \"git switch -C \" <<-\\EOF\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain Z\n>>   \tmain-in-other Z\n>>   \tmatching-branch Z\n>> @@ -2104,8 +2237,8 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n>>   \n>>   test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n>>   \ttest_completion \"git checkout -b \" <<-\\EOF\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain Z\n>>   \tmain-in-other Z\n>>   \tmatching-branch Z\n>> @@ -2114,8 +2247,8 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n>>   \n>>   test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n>>   \ttest_completion \"git checkout -B \" <<-\\EOF\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain Z\n>>   \tmain-in-other Z\n>>   \tmatching-branch Z\n>> @@ -2152,8 +2285,8 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n>>   \n>>   test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n>>   \ttest_completion \"git switch --orphan \" <<-\\EOF\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain Z\n>>   \tmain-in-other Z\n>>   \tmatching-branch Z\n>> @@ -2168,8 +2301,8 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n>>   \n>>   test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n>>   \ttest_completion \"git checkout --orphan \" <<-\\EOF\n>> -\tHEAD Z\n>>   \tbranch-in-other Z\n>> +\tbranch/with/slash Z\n>>   \tmain Z\n>>   \tmain-in-other Z\n>>   \tmatching-branch Z\n>> @@ -2185,6 +2318,8 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n>>   \tother/HEAD Z\n>>   \tother/branch-in-other Z\n>>   \tother/main-in-other Z\n>> +\tremote/with/slash/HEAD Z\n>> +\tremote/with/slash/branch/with/slash Z\n>>   \tEOF\n>>   '\n>>   \n>> @@ -2199,7 +2334,8 @@ test_expect_success 'git restore completes modified files' '\n>>   test_expect_success 'teardown after ref completion' '\n>>   \tgit branch -d matching-branch &&\n>>   \tgit tag -d matching-tag &&\n>> -\tgit remote remove other\n>> +\tgit remote remove other &&\n>> +\tgit remote remove remote/with/slash\n>>   '\n> \n\n"},{"id":"513719","messageId":"7f00dc07-5905-4571-84f4-e50c39867818@mandelberg.org","threadId":"63039","inReplyTo":"2bed28ca-b106-474d-9bac-db377bc51949@gmail.com","subject":"Re: [PATCH v2 2/2] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-06T17:12:46Z","receivedAt":"2025-03-06T17:12:49Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Op 2025-03-06 om 11:35 schreef Phillip Wood:\n> Hi David\n> \n> On 05/03/2025 20:50, David Mandelberg wrote:\n>> David Mandelberg schreef op 2025-03-04 19:09:\n>>> Previously, some calls to for-each-ref passed fixed numbers of path\n>>> components to strip from refs, assuming that remote names had no slashes\n>>> in them. This made completions like:\n>>>\n>>> git push github/dseomn :com<Tab>\n>>>\n>>> Result in:\n>>>\n>>> git push github/dseomn :dseomn/completion-remote-slash\n>>>\n>>> With this patch, it instead results in:\n>>>\n>>> git push github/dseomn :completion-remote-slash\n>>>\n>>> Signed-off-by: David Mandelberg <david@mandelberg.org>\n>>> ---\n>>>   contrib/completion/git-completion.bash |  38 +++++-\n>>>   t/t9902-completion.sh                  | 180 ++++++++++++++++++++++---\n>>>   2 files changed, 189 insertions(+), 29 deletions(-)\n>>>\n>>> diff --git a/contrib/completion/git-completion.bash b/contrib/ \n>>> completion/git-completion.bash\n>>> index 5fdc71208e..450fabc901 100644\n>>> --- a/contrib/completion/git-completion.bash\n>>> +++ b/contrib/completion/git-completion.bash\n>>> @@ -790,16 +790,39 @@ __git_tags ()\n>>>   __git_dwim_remote_heads ()\n>>>   {\n>>>       local pfx=\"${1-}\" cur_=\"${2-}\" sfx=\"${3-}\"\n>>> -    local fer_pfx=\"${pfx//\\%/%%}\" # \"escape\" for-each-ref format \n>>> specifiers\n>>>       # employ the heuristic used by git checkout and git switch\n>>>       # Try to find a remote branch that cur_es the completion word\n>>>       # but only output if the branch name is unique\n>>> -    __git for-each-ref --format=\"$fer_pfx%(refname:strip=3)$sfx\" \\\n>>> -        --sort=\"refname:strip=3\" \\\n>>> -        ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n>>> -        \"refs/remotes/*/$cur_*\" \"refs/remotes/*/$cur_*/**\" | \\\n>>> -    uniq -u\n>>> +    local awk_script='\n>>> +    function casemap(s) {\n>>> +        if (ENVIRON[\"IGNORE_CASE\"])\n>>> +            return tolower(s)\n>>> +        else\n>>> +            return s\n>>> +    }\n>>> +    BEGIN {\n>>> +        split(ENVIRON[\"REMOTES\"], remotes, /\\n/)\n>>> +        for (i in remotes)\n>>> +            remotes[i] = \"refs/remotes/\" casemap(remotes[i])\n>>> +        cur_ = casemap(ENVIRON[\"CUR_\"])\n>>> +    }\n>>> +    {\n>>> +        ref_case = casemap($0)\n>>> +        for (i in remotes) {\n>>> +            if (index(ref_case, remotes[i] \"/\" cur_) == 1) {\n>>> +                branch = substr($0, length(remotes[i] \"/\") + 1)\n>>> +                print ENVIRON[\"PFX\"] branch ENVIRON[\"SFX\"]\n>>> +                break\n>>> +            }\n>>> +        }\n>>> +    }\n>>> +    '\n>>> +    __git for-each-ref --format='%(refname)' |\n>>> +        PFX=\"$pfx\" SFX=\"$sfx\" CUR_=\"$cur_\" \\\n>>> +            IGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \\\n>>> +            REMOTES=\"$(__git_remotes | sort -r)\" awk \"$awk_script\" |\n>>> +        sort | uniq -u\n> \n> I wonder if we can use \"sort -u\" here and drop uniq. It isn't quite the \n> same as it will remove entries that are equal according to the current \n> locale but I don't think that should matter.\n\nThat's what I thought when I first saw that, but `uniq -u` removes \nduplicates:\n\n$ printf 'a\\nb\\na\\n' | sort | uniq -u\nb\n$ printf 'a\\nb\\na\\n' | sort -u\na\nb\n  >> I realized that this sends refs to awk that it doesn't need to. I'll\n>> apply this diff to the next version of this patch:\n> \n> With that tweak this looks good. If there are no glob characters then \n> \"git for-each-ref\" does a prefix match so strictly speaking you don't \n> need the '**' but I don't think it matters in practice. I had one \n> thought below but if you don't feel like spending more that on this I \n> think what you have here is fine.\n> \n> The filtering is O(number of remote refs * number of remotes). If we \n> could sort the list of remotes and remote refs in the same order then we \n> can reduce this to O(number of remote refs + number of remotes) by doing \n> (in pseudo code)\n> \n>      for ($ref in $remote_refs) {\n>      while (!starts_with($ref, \"refs/remotes/$remote[$i]\"))\n>          $i++;\n>      if (starts_with($ref, \"refs/remotes/$remote[$i]/$cur_)\n>          print $ref;\n>      }\n> \n> I think we can get \"git for-each-ref --sort=-refname\" and \"sort -r\" to \n> agree on a sorting order by setting LC_COLLATE=C, otherwise \"sort\" will \n> sort according the the current locale whereas git sorts by bytes.\n\nOn my desktop, a similar-ish loop takes about 0.1s with 1000*1000 \niterations. 1000 remote branches seems plausible but on the high side to \nme, and 1000 remotes seems very high to me. Do you still think it's \nworth optimizing? I do think your solution would work, but I think it \nwould take a decent amount of testing to avoid the collation issues you \nmentioned, and off-by-one errors and the like. So I'd rather not do it \nunless there's a practical performance advantage, not just theoretical. \n(Which is what I thought about minimizing forks too, before I learned \nthat it was a practical issue.)\n\n$ time seq 1 1000 | LONG=\"$(seq 1 1000)\" awk 'BEGIN { \nsplit(ENVIRON[\"LONG\"], long, /\\n/); } { for (i in long) { if ($0 == \nlong[i]) { print; }  } }' > /dev/null\n\nreal    0m0,092s\nuser    0m0,067s\nsys     0m0,028s\n\n> Best Wishes\n> \n> Phillip\n> \n>> diff --git a/contrib/completion/git-completion.bash b/contrib/ \n>> completion/git-completion.bash\n>> index 450fabc901..c9d014070c 100644\n>> --- a/contrib/completion/git-completion.bash\n>> +++ b/contrib/completion/git-completion.bash\n>> @@ -818,7 +818,7 @@ __git_dwim_remote_heads ()\n>>                  }\n>>          }\n>>          '\n>> -       __git for-each-ref --format='%(refname)' |\n>> +       __git for-each-ref --format='%(refname)' 'refs/remotes/**' |\n>>                  PFX=\"$pfx\" SFX=\"$sfx\" CUR_=\"$cur_\" \\\n>>                          IGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \\\n>>                          REMOTES=\"$(__git_remotes | sort -r)\" awk \n>> \"$awk_script\" |\n>>\n>>>   }\n>>>   # Lists refs from the local (by default) or from a remote repository.\n>>> @@ -905,7 +928,8 @@ __git_refs ()\n>>>               case \"HEAD\" in\n>>>               $match*|$umatch*)    echo \"${pfx}HEAD$sfx\" ;;\n>>>               esac\n>>> -            __git for-each-ref -- \n>>> format=\"$fer_pfx%(refname:strip=3)$sfx\" \\\n>>> +            local strip=\"$(__git_count_path_components \"refs/ \n>>> remotes/$remote\")\"\n>>> +            __git for-each-ref -- \n>>> format=\"$fer_pfx%(refname:strip=$strip)$sfx\" \\\n>>>                   ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n>>>                   \"refs/remotes/$remote/$match*\" \\\n>>>                   \"refs/remotes/$remote/$match*/**\"\n>>> diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\n>>> index 015289c776..343b8cd191 100755\n>>> --- a/t/t9902-completion.sh\n>>> +++ b/t/t9902-completion.sh\n>>> @@ -149,7 +149,8 @@ fi\n>>>   test_expect_success 'setup for __git_find_repo_path/__gitdir tests' '\n>>>       mkdir -p subdir/subsubdir &&\n>>>       mkdir -p non-repo &&\n>>> -    git init -b main otherrepo\n>>> +    git init -b main otherrepo &&\n>>> +    git init -b main slashrepo\n>>>   '\n>>>   test_expect_success '__git_find_repo_path - from command line \n>>> (through $__git_dir)' '\n>>> @@ -674,6 +675,13 @@ test_expect_success 'setup for ref completion' '\n>>>       ) &&\n>>>       git remote add other \"$ROOT/otherrepo/.git\" &&\n>>>       git fetch --no-tags other &&\n>>> +    (\n>>> +        cd slashrepo &&\n>>> +        git commit --allow-empty -m initial &&\n>>> +        git branch -m main branch/with/slash\n>>> +    ) &&\n>>> +    git remote add remote/with/slash \"$ROOT/slashrepo/.git\" &&\n>>> +    git fetch --no-tags remote/with/slash &&\n>>>       rm -f .git/FETCH_HEAD &&\n>>>       git init thirdrepo\n>>>   '\n>>> @@ -686,6 +694,8 @@ test_expect_success '__git_refs - simple' '\n>>>       other/HEAD\n>>>       other/branch-in-other\n>>>       other/main-in-other\n>>> +    remote/with/slash/HEAD\n>>> +    remote/with/slash/branch/with/slash\n>>>       matching-tag\n>>>       EOF\n>>>       (\n>>> @@ -702,6 +712,8 @@ test_expect_success '__git_refs - full refs' '\n>>>       refs/remotes/other/HEAD\n>>>       refs/remotes/other/branch-in-other\n>>>       refs/remotes/other/main-in-other\n>>> +    refs/remotes/remote/with/slash/HEAD\n>>> +    refs/remotes/remote/with/slash/branch/with/slash\n>>>       refs/tags/matching-tag\n>>>       EOF\n>>>       (\n>>> @@ -767,6 +779,19 @@ test_expect_success '__git_refs - configured \n>>> remote' '\n>>>       test_cmp expected \"$actual\"\n>>>   '\n>>> +test_expect_success '__git_refs - configured remote - with slash' '\n>>> +    cat >expected <<-EOF &&\n>>> +    HEAD\n>>> +    HEAD\n>>> +    branch/with/slash\n>>> +    EOF\n>>> +    (\n>>> +        cur= &&\n>>> +        __git_refs remote/with/slash >\"$actual\"\n>>> +    ) &&\n>>> +    test_cmp expected \"$actual\"\n>>> +'\n>>> +\n>>>   test_expect_success '__git_refs - configured remote - full refs' '\n>>>       cat >expected <<-EOF &&\n>>>       HEAD\n>>> @@ -909,17 +934,19 @@ test_expect_success '__git_refs - unique remote \n>>> branches for git checkout DWIMer\n>>>       other/ambiguous\n>>>       other/branch-in-other\n>>>       other/main-in-other\n>>> -    remote/ambiguous\n>>> -    remote/branch-in-remote\n>>> +    remote/with/slash/HEAD\n>>> +    remote/with/slash/ambiguous\n>>> +    remote/with/slash/branch-in-remote\n>>> +    remote/with/slash/branch/with/slash\n>>>       matching-tag\n>>> -    HEAD\n>>>       branch-in-other\n>>>       branch-in-remote\n>>> +    branch/with/slash\n>>>       main-in-other\n>>>       EOF\n>>>       for remote_ref in refs/remotes/other/ambiguous \\\n>>> -        refs/remotes/remote/ambiguous \\\n>>> -        refs/remotes/remote/branch-in-remote\n>>> +        refs/remotes/remote/with/slash/ambiguous \\\n>>> +        refs/remotes/remote/with/slash/branch-in-remote\n>>>       do\n>>>           git update-ref $remote_ref main &&\n>>>           test_when_finished \"git update-ref -d $remote_ref\" || return 1\n>>> @@ -939,6 +966,8 @@ test_expect_success '__git_refs - after --opt=' '\n>>>       other/HEAD\n>>>       other/branch-in-other\n>>>       other/main-in-other\n>>> +    remote/with/slash/HEAD\n>>> +    remote/with/slash/branch/with/slash\n>>>       matching-tag\n>>>       EOF\n>>>       (\n>>> @@ -955,6 +984,8 @@ test_expect_success '__git_refs - after --opt= - \n>>> full refs' '\n>>>       refs/remotes/other/HEAD\n>>>       refs/remotes/other/branch-in-other\n>>>       refs/remotes/other/main-in-other\n>>> +    refs/remotes/remote/with/slash/HEAD\n>>> +    refs/remotes/remote/with/slash/branch/with/slash\n>>>       refs/tags/matching-tag\n>>>       EOF\n>>>       (\n>>> @@ -972,6 +1003,8 @@ test_expect_success '__git refs - excluding refs' '\n>>>       ^other/HEAD\n>>>       ^other/branch-in-other\n>>>       ^other/main-in-other\n>>> +    ^remote/with/slash/HEAD\n>>> +    ^remote/with/slash/branch/with/slash\n>>>       ^matching-tag\n>>>       EOF\n>>>       (\n>>> @@ -988,6 +1021,8 @@ test_expect_success '__git refs - excluding full \n>>> refs' '\n>>>       ^refs/remotes/other/HEAD\n>>>       ^refs/remotes/other/branch-in-other\n>>>       ^refs/remotes/other/main-in-other\n>>> +    ^refs/remotes/remote/with/slash/HEAD\n>>> +    ^refs/remotes/remote/with/slash/branch/with/slash\n>>>       ^refs/tags/matching-tag\n>>>       EOF\n>>>       (\n>>> @@ -1015,6 +1050,8 @@ test_expect_success '__git_refs - do not filter \n>>> refs unless told so' '\n>>>       other/branch-in-other\n>>>       other/main-in-other\n>>>       other/matching/branch-in-other\n>>> +    remote/with/slash/HEAD\n>>> +    remote/with/slash/branch/with/slash\n>>>       matching-tag\n>>>       matching/tag\n>>>       EOF\n>>> @@ -1135,6 +1172,8 @@ test_expect_success '__git_complete_refs - \n>>> simple' '\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       matching-tag Z\n>>>       EOF\n>>>       (\n>>> @@ -1173,6 +1212,20 @@ test_expect_success '__git_complete_refs - \n>>> remote' '\n>>>       test_cmp expected out\n>>>   '\n>>> +test_expect_success '__git_complete_refs - remote - with slash' '\n>>> +    sed -e \"s/Z$//\" >expected <<-EOF &&\n>>> +    HEAD Z\n>>> +    HEAD Z\n>>> +    branch/with/slash Z\n>>> +    EOF\n>>> +    (\n>>> +        cur= &&\n>>> +        __git_complete_refs --remote=remote/with/slash &&\n>>> +        print_comp\n>>> +    ) &&\n>>> +    test_cmp expected out\n>>> +'\n>>> +\n>>>   test_expect_success '__git_complete_refs - track' '\n>>>       sed -e \"s/Z$//\" >expected <<-EOF &&\n>>>       HEAD Z\n>>> @@ -1181,9 +1234,11 @@ test_expect_success '__git_complete_refs - \n>>> track' '\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       matching-tag Z\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main-in-other Z\n>>>       EOF\n>>>       (\n>>> @@ -1228,6 +1283,8 @@ test_expect_success '__git_complete_refs - \n>>> suffix' '\n>>>       other/HEAD.\n>>>       other/branch-in-other.\n>>>       other/main-in-other.\n>>> +    remote/with/slash/HEAD.\n>>> +    remote/with/slash/branch/with/slash.\n>>>       matching-tag.\n>>>       EOF\n>>>       (\n>>> @@ -1253,6 +1310,20 @@ test_expect_success \n>>> '__git_complete_fetch_refspecs - simple' '\n>>>       test_cmp expected out\n>>>   '\n>>> +test_expect_success '__git_complete_fetch_refspecs - with slash' '\n>>> +    sed -e \"s/Z$//\" >expected <<-EOF &&\n>>> +    HEAD:HEAD Z\n>>> +    HEAD:HEAD Z\n>>> +    branch/with/slash:branch/with/slash Z\n>>> +    EOF\n>>> +    (\n>>> +        cur= &&\n>>> +        __git_complete_fetch_refspecs remote/with/slash &&\n>>> +        print_comp\n>>> +    ) &&\n>>> +    test_cmp expected out\n>>> +'\n>>> +\n>>>   test_expect_success '__git_complete_fetch_refspecs - matching' '\n>>>       sed -e \"s/Z$//\" >expected <<-EOF &&\n>>>       branch-in-other:branch-in-other Z\n>>> @@ -1333,8 +1404,8 @@ test_expect_success \n>>> '__git_complete_worktree_paths with -C' '\n>>>   test_expect_success 'git switch - with no options, complete local \n>>> branches and unique remote branch names for DWIM logic' '\n>>>       test_completion \"git switch \" <<-\\EOF\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main Z\n>>>       main-in-other Z\n>>>       matching-branch Z\n>>> @@ -1480,8 +1551,8 @@ test_expect_success 'git-bisect - existing view \n>>> subcommand is recognized and ena\n>>>   test_expect_success 'git checkout - completes refs and unique \n>>> remote branches for DWIM' '\n>>>       test_completion \"git checkout \" <<-\\EOF\n>>>       HEAD Z\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main Z\n>>>       main-in-other Z\n>>>       matching-branch Z\n>>> @@ -1489,6 +1560,8 @@ test_expect_success 'git checkout - completes \n>>> refs and unique remote branches fo\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1508,8 +1581,8 @@ test_expect_success 'git switch - with \n>>> GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n>>>   test_expect_success 'git switch - --guess overrides \n>>> GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and \n>>> unique remote names for DWIM logic' '\n>>>       GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch \n>>> --guess \" <<-\\EOF\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main Z\n>>>       main-in-other Z\n>>>       matching-branch Z\n>>> @@ -1518,8 +1591,8 @@ test_expect_success 'git switch - --guess \n>>> overrides GIT_COMPLETION_CHECKOUT_NO_G\n>>>   test_expect_success 'git switch - a later --guess overrides \n>>> previous --no-guess, complete local and remote unique branches for \n>>> DWIM' '\n>>>       test_completion \"git switch --no-guess --guess \" <<-\\EOF\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main Z\n>>>       main-in-other Z\n>>>       matching-branch Z\n>>> @@ -1542,14 +1615,16 @@ test_expect_success 'git checkout - with \n>>> GIT_COMPLETION_NO_GUESS=1 only complete\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>>   test_expect_success 'git checkout - --guess overrides \n>>> GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches \n>>> for DWIM' '\n>>>       GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git \n>>> checkout --guess \" <<-\\EOF\n>>>       HEAD Z\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main Z\n>>>       main-in-other Z\n>>>       matching-branch Z\n>>> @@ -1557,6 +1632,8 @@ test_expect_success 'git checkout - --guess \n>>> overrides GIT_COMPLETION_NO_GUESS=1,\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1569,14 +1646,16 @@ test_expect_success 'git checkout - with -- \n>>> no-guess, only completes refs' '\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>>   test_expect_success 'git checkout - a later --guess overrides \n>>> previous --no-guess, complete refs and unique remote branches for \n>>> DWIM' '\n>>>       test_completion \"git checkout --no-guess --guess \" <<-\\EOF\n>>>       HEAD Z\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main Z\n>>>       main-in-other Z\n>>>       matching-branch Z\n>>> @@ -1584,6 +1663,8 @@ test_expect_success 'git checkout - a later -- \n>>> guess overrides previous --no-gues\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1596,6 +1677,8 @@ test_expect_success 'git checkout - a later -- \n>>> no-guess overrides previous --gues\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1609,6 +1692,8 @@ test_expect_success 'git checkout - with \n>>> checkout.guess = false, only completes\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1616,8 +1701,8 @@ test_expect_success 'git checkout - with \n>>> checkout.guess = true, completes refs a\n>>>       test_config checkout.guess true &&\n>>>       test_completion \"git checkout \" <<-\\EOF\n>>>       HEAD Z\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main Z\n>>>       main-in-other Z\n>>>       matching-branch Z\n>>> @@ -1625,6 +1710,8 @@ test_expect_success 'git checkout - with \n>>> checkout.guess = true, completes refs a\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1632,8 +1719,8 @@ test_expect_success 'git checkout - a later -- \n>>> guess overrides previous checkout.\n>>>       test_config checkout.guess false &&\n>>>       test_completion \"git checkout --guess \" <<-\\EOF\n>>>       HEAD Z\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main Z\n>>>       main-in-other Z\n>>>       matching-branch Z\n>>> @@ -1641,6 +1728,8 @@ test_expect_success 'git checkout - a later -- \n>>> guess overrides previous checkout.\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1654,6 +1743,8 @@ test_expect_success 'git checkout - a later -- \n>>> no-guess overrides previous checko\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1666,6 +1757,8 @@ test_expect_success 'git switch - with -- \n>>> detach, complete all references' '\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1678,6 +1771,8 @@ test_expect_success 'git checkout - with -- \n>>> detach, complete only references' '\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1850,6 +1945,8 @@ test_expect_success 'git switch - with -d, \n>>> complete all references' '\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1862,6 +1959,8 @@ test_expect_success 'git checkout - with -d, \n>>> complete only references' '\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1870,11 +1969,15 @@ test_expect_success 'git switch - with -- \n>>> track, complete only remote branches' '\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>       test_completion \"git switch -t \" <<-\\EOF\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1883,11 +1986,15 @@ test_expect_success 'git checkout - with -- \n>>> track, complete only remote branches'\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>       test_completion \"git checkout -t \" <<-\\EOF\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1907,6 +2014,8 @@ test_expect_success 'git checkout - with --no- \n>>> track, complete only local referen\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1919,6 +2028,8 @@ test_expect_success 'git switch - with -c, \n>>> complete all references' '\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1931,6 +2042,8 @@ test_expect_success 'git switch - with -C, \n>>> complete all references' '\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1943,6 +2056,8 @@ test_expect_success 'git switch - with -c and \n>>> --track, complete all references'\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1955,6 +2070,8 @@ test_expect_success 'git switch - with -C and \n>>> --track, complete all references'\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1967,6 +2084,8 @@ test_expect_success 'git switch - with -c and \n>>> --no-track, complete all reference\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1979,6 +2098,8 @@ test_expect_success 'git switch - with -C and \n>>> --no-track, complete all reference\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -1991,6 +2112,8 @@ test_expect_success 'git checkout - with -b, \n>>> complete all references' '\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -2003,6 +2126,8 @@ test_expect_success 'git checkout - with -B, \n>>> complete all references' '\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -2015,6 +2140,8 @@ test_expect_success 'git checkout - with -b and \n>>> --track, complete all references\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -2027,6 +2154,8 @@ test_expect_success 'git checkout - with -B and \n>>> --track, complete all references\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -2039,6 +2168,8 @@ test_expect_success 'git checkout - with -b and \n>>> --no-track, complete all referen\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -2051,13 +2182,15 @@ test_expect_success 'git checkout - with -B \n>>> and --no-track, complete all referen\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>>   test_expect_success 'git switch - for -c, complete local branches \n>>> and unique remote branches' '\n>>>       test_completion \"git switch -c \" <<-\\EOF\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main Z\n>>>       main-in-other Z\n>>>       matching-branch Z\n>>> @@ -2066,8 +2199,8 @@ test_expect_success 'git switch - for -c, \n>>> complete local branches and unique rem\n>>>   test_expect_success 'git switch - for -C, complete local branches \n>>> and unique remote branches' '\n>>>       test_completion \"git switch -C \" <<-\\EOF\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main Z\n>>>       main-in-other Z\n>>>       matching-branch Z\n>>> @@ -2104,8 +2237,8 @@ test_expect_success 'git switch - for -C with \n>>> --no-track, complete local branche\n>>>   test_expect_success 'git checkout - for -b, complete local branches \n>>> and unique remote branches' '\n>>>       test_completion \"git checkout -b \" <<-\\EOF\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main Z\n>>>       main-in-other Z\n>>>       matching-branch Z\n>>> @@ -2114,8 +2247,8 @@ test_expect_success 'git checkout - for -b, \n>>> complete local branches and unique r\n>>>   test_expect_success 'git checkout - for -B, complete local branches \n>>> and unique remote branches' '\n>>>       test_completion \"git checkout -B \" <<-\\EOF\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main Z\n>>>       main-in-other Z\n>>>       matching-branch Z\n>>> @@ -2152,8 +2285,8 @@ test_expect_success 'git checkout - for -B with \n>>> --no-track, complete local branc\n>>>   test_expect_success 'git switch - with --orphan completes local \n>>> branch names and unique remote branch names' '\n>>>       test_completion \"git switch --orphan \" <<-\\EOF\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main Z\n>>>       main-in-other Z\n>>>       matching-branch Z\n>>> @@ -2168,8 +2301,8 @@ test_expect_success 'git switch - --orphan with \n>>> branch already provided complete\n>>>   test_expect_success 'git checkout - with --orphan completes local \n>>> branch names and unique remote branch names' '\n>>>       test_completion \"git checkout --orphan \" <<-\\EOF\n>>> -    HEAD Z\n>>>       branch-in-other Z\n>>> +    branch/with/slash Z\n>>>       main Z\n>>>       main-in-other Z\n>>>       matching-branch Z\n>>> @@ -2185,6 +2318,8 @@ test_expect_success 'git checkout - --orphan \n>>> with branch already provided comple\n>>>       other/HEAD Z\n>>>       other/branch-in-other Z\n>>>       other/main-in-other Z\n>>> +    remote/with/slash/HEAD Z\n>>> +    remote/with/slash/branch/with/slash Z\n>>>       EOF\n>>>   '\n>>> @@ -2199,7 +2334,8 @@ test_expect_success 'git restore completes \n>>> modified files' '\n>>>   test_expect_success 'teardown after ref completion' '\n>>>       git branch -d matching-branch &&\n>>>       git tag -d matching-tag &&\n>>> -    git remote remove other\n>>> +    git remote remove other &&\n>>> +    git remote remove remote/with/slash\n>>>   '\n>>\n> \n\n"},{"id":"513722","messageId":"b87cd755-b297-47ef-a1ad-8e06091333b8@gmail.com","threadId":"63039","inReplyTo":"7f00dc07-5905-4571-84f4-e50c39867818@mandelberg.org","subject":"Re: [PATCH v2 2/2] completion: fix bugs with slashes in remote names","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-03-06T17:39:32Z","receivedAt":"2025-03-06T17:39:37Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi David\n\nOn 06/03/2025 17:12, David Mandelberg wrote:\n> Op 2025-03-06 om 11:35 schreef Phillip Wood:\n>\n>>>> +    __git for-each-ref --format='%(refname)' |\n>>>> +        PFX=\"$pfx\" SFX=\"$sfx\" CUR_=\"$cur_\" \\\n>>>> +            IGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \\\n>>>> +            REMOTES=\"$(__git_remotes | sort -r)\" awk \"$awk_script\" |\n>>>> +        sort | uniq -u\n>>\n>> I wonder if we can use \"sort -u\" here and drop uniq. It isn't quite \n>> the same as it will remove entries that are equal according to the \n>> current locale but I don't think that should matter.\n> \n> That's what I thought when I first saw that, but `uniq -u` removes \n> duplicates:\n> \n> $ printf 'a\\nb\\na\\n' | sort | uniq -u\n> b\n> $ printf 'a\\nb\\na\\n' | sort -u\n> a\n> b\n\nOh, thanks for pointing that out\n\n>   >> I realized that this sends refs to awk that it doesn't need to. I'll\n>>> apply this diff to the next version of this patch:\n>>\n>> With that tweak this looks good. If there are no glob characters then \n>> \"git for-each-ref\" does a prefix match so strictly speaking you don't \n>> need the '**' but I don't think it matters in practice. I had one \n>> thought below but if you don't feel like spending more that on this I \n>> think what you have here is fine.\n>>\n>> The filtering is O(number of remote refs * number of remotes). If we \n>> could sort the list of remotes and remote refs in the same order then \n>> we can reduce this to O(number of remote refs + number of remotes) by \n>> doing (in pseudo code)\n>>\n>>      for ($ref in $remote_refs) {\n>>      while (!starts_with($ref, \"refs/remotes/$remote[$i]\"))\n>>          $i++;\n>>      if (starts_with($ref, \"refs/remotes/$remote[$i]/$cur_)\n>>          print $ref;\n>>      }\n>>\n>> I think we can get \"git for-each-ref --sort=-refname\" and \"sort -r\" to \n>> agree on a sorting order by setting LC_COLLATE=C, otherwise \"sort\" \n>> will sort according the the current locale whereas git sorts by bytes.\n> \n> On my desktop, a similar-ish loop takes about 0.1s with 1000*1000 \n> iterations. 1000 remote branches seems plausible but on the high side to \n> me, and 1000 remotes seems very high to me. Do you still think it's \n> worth optimizing? I do think your solution would work, but I think it \n> would take a decent amount of testing to avoid the collation issues you \n> mentioned, and off-by-one errors and the like. So I'd rather not do it \n> unless there's a practical performance advantage, not just theoretical. \n> (Which is what I thought about minimizing forks too, before I learned \n> that it was a practical issue.)\n\nThanks for running a benchmark. I'm fine with leaving it as is, it is \ndefinitely simpler and easier to understand. We can always revisit the \nfiltering in the future if it turns out to be a too slow for some reason.\n\nBest Wishes\n\nPhillip\n\n> $ time seq 1 1000 | LONG=\"$(seq 1 1000)\" awk 'BEGIN \n> { split(ENVIRON[\"LONG\"], long, /\\n/); } { for (i in long) { if ($0 == \n> long[i]) { print; }  } }' > /dev/null\n> \n> real    0m0,092s\n> user    0m0,067s\n> sys     0m0,028s\n> \n>> Best Wishes\n>>\n>> Phillip\n>>\n>>> diff --git a/contrib/completion/git-completion.bash b/contrib/ \n>>> completion/git-completion.bash\n>>> index 450fabc901..c9d014070c 100644\n>>> --- a/contrib/completion/git-completion.bash\n>>> +++ b/contrib/completion/git-completion.bash\n>>> @@ -818,7 +818,7 @@ __git_dwim_remote_heads ()\n>>>                  }\n>>>          }\n>>>          '\n>>> -       __git for-each-ref --format='%(refname)' |\n>>> +       __git for-each-ref --format='%(refname)' 'refs/remotes/**' |\n>>>                  PFX=\"$pfx\" SFX=\"$sfx\" CUR_=\"$cur_\" \\\n>>>                          IGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \\\n>>>                          REMOTES=\"$(__git_remotes | sort -r)\" awk \n>>> \"$awk_script\" |\n>>>\n>>>>   }\n>>>>   # Lists refs from the local (by default) or from a remote repository.\n>>>> @@ -905,7 +928,8 @@ __git_refs ()\n>>>>               case \"HEAD\" in\n>>>>               $match*|$umatch*)    echo \"${pfx}HEAD$sfx\" ;;\n>>>>               esac\n>>>> -            __git for-each-ref -- \n>>>> format=\"$fer_pfx%(refname:strip=3)$sfx\" \\\n>>>> +            local strip=\"$(__git_count_path_components \"refs/ \n>>>> remotes/$remote\")\"\n>>>> +            __git for-each-ref -- \n>>>> format=\"$fer_pfx%(refname:strip=$strip)$sfx\" \\\n>>>>                   ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n>>>>                   \"refs/remotes/$remote/$match*\" \\\n>>>>                   \"refs/remotes/$remote/$match*/**\"\n>>>> diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\n>>>> index 015289c776..343b8cd191 100755\n>>>> --- a/t/t9902-completion.sh\n>>>> +++ b/t/t9902-completion.sh\n>>>> @@ -149,7 +149,8 @@ fi\n>>>>   test_expect_success 'setup for __git_find_repo_path/__gitdir tests' '\n>>>>       mkdir -p subdir/subsubdir &&\n>>>>       mkdir -p non-repo &&\n>>>> -    git init -b main otherrepo\n>>>> +    git init -b main otherrepo &&\n>>>> +    git init -b main slashrepo\n>>>>   '\n>>>>   test_expect_success '__git_find_repo_path - from command line \n>>>> (through $__git_dir)' '\n>>>> @@ -674,6 +675,13 @@ test_expect_success 'setup for ref completion' '\n>>>>       ) &&\n>>>>       git remote add other \"$ROOT/otherrepo/.git\" &&\n>>>>       git fetch --no-tags other &&\n>>>> +    (\n>>>> +        cd slashrepo &&\n>>>> +        git commit --allow-empty -m initial &&\n>>>> +        git branch -m main branch/with/slash\n>>>> +    ) &&\n>>>> +    git remote add remote/with/slash \"$ROOT/slashrepo/.git\" &&\n>>>> +    git fetch --no-tags remote/with/slash &&\n>>>>       rm -f .git/FETCH_HEAD &&\n>>>>       git init thirdrepo\n>>>>   '\n>>>> @@ -686,6 +694,8 @@ test_expect_success '__git_refs - simple' '\n>>>>       other/HEAD\n>>>>       other/branch-in-other\n>>>>       other/main-in-other\n>>>> +    remote/with/slash/HEAD\n>>>> +    remote/with/slash/branch/with/slash\n>>>>       matching-tag\n>>>>       EOF\n>>>>       (\n>>>> @@ -702,6 +712,8 @@ test_expect_success '__git_refs - full refs' '\n>>>>       refs/remotes/other/HEAD\n>>>>       refs/remotes/other/branch-in-other\n>>>>       refs/remotes/other/main-in-other\n>>>> +    refs/remotes/remote/with/slash/HEAD\n>>>> +    refs/remotes/remote/with/slash/branch/with/slash\n>>>>       refs/tags/matching-tag\n>>>>       EOF\n>>>>       (\n>>>> @@ -767,6 +779,19 @@ test_expect_success '__git_refs - configured \n>>>> remote' '\n>>>>       test_cmp expected \"$actual\"\n>>>>   '\n>>>> +test_expect_success '__git_refs - configured remote - with slash' '\n>>>> +    cat >expected <<-EOF &&\n>>>> +    HEAD\n>>>> +    HEAD\n>>>> +    branch/with/slash\n>>>> +    EOF\n>>>> +    (\n>>>> +        cur= &&\n>>>> +        __git_refs remote/with/slash >\"$actual\"\n>>>> +    ) &&\n>>>> +    test_cmp expected \"$actual\"\n>>>> +'\n>>>> +\n>>>>   test_expect_success '__git_refs - configured remote - full refs' '\n>>>>       cat >expected <<-EOF &&\n>>>>       HEAD\n>>>> @@ -909,17 +934,19 @@ test_expect_success '__git_refs - unique \n>>>> remote branches for git checkout DWIMer\n>>>>       other/ambiguous\n>>>>       other/branch-in-other\n>>>>       other/main-in-other\n>>>> -    remote/ambiguous\n>>>> -    remote/branch-in-remote\n>>>> +    remote/with/slash/HEAD\n>>>> +    remote/with/slash/ambiguous\n>>>> +    remote/with/slash/branch-in-remote\n>>>> +    remote/with/slash/branch/with/slash\n>>>>       matching-tag\n>>>> -    HEAD\n>>>>       branch-in-other\n>>>>       branch-in-remote\n>>>> +    branch/with/slash\n>>>>       main-in-other\n>>>>       EOF\n>>>>       for remote_ref in refs/remotes/other/ambiguous \\\n>>>> -        refs/remotes/remote/ambiguous \\\n>>>> -        refs/remotes/remote/branch-in-remote\n>>>> +        refs/remotes/remote/with/slash/ambiguous \\\n>>>> +        refs/remotes/remote/with/slash/branch-in-remote\n>>>>       do\n>>>>           git update-ref $remote_ref main &&\n>>>>           test_when_finished \"git update-ref -d $remote_ref\" || \n>>>> return 1\n>>>> @@ -939,6 +966,8 @@ test_expect_success '__git_refs - after --opt=' '\n>>>>       other/HEAD\n>>>>       other/branch-in-other\n>>>>       other/main-in-other\n>>>> +    remote/with/slash/HEAD\n>>>> +    remote/with/slash/branch/with/slash\n>>>>       matching-tag\n>>>>       EOF\n>>>>       (\n>>>> @@ -955,6 +984,8 @@ test_expect_success '__git_refs - after --opt= - \n>>>> full refs' '\n>>>>       refs/remotes/other/HEAD\n>>>>       refs/remotes/other/branch-in-other\n>>>>       refs/remotes/other/main-in-other\n>>>> +    refs/remotes/remote/with/slash/HEAD\n>>>> +    refs/remotes/remote/with/slash/branch/with/slash\n>>>>       refs/tags/matching-tag\n>>>>       EOF\n>>>>       (\n>>>> @@ -972,6 +1003,8 @@ test_expect_success '__git refs - excluding \n>>>> refs' '\n>>>>       ^other/HEAD\n>>>>       ^other/branch-in-other\n>>>>       ^other/main-in-other\n>>>> +    ^remote/with/slash/HEAD\n>>>> +    ^remote/with/slash/branch/with/slash\n>>>>       ^matching-tag\n>>>>       EOF\n>>>>       (\n>>>> @@ -988,6 +1021,8 @@ test_expect_success '__git refs - excluding \n>>>> full refs' '\n>>>>       ^refs/remotes/other/HEAD\n>>>>       ^refs/remotes/other/branch-in-other\n>>>>       ^refs/remotes/other/main-in-other\n>>>> +    ^refs/remotes/remote/with/slash/HEAD\n>>>> +    ^refs/remotes/remote/with/slash/branch/with/slash\n>>>>       ^refs/tags/matching-tag\n>>>>       EOF\n>>>>       (\n>>>> @@ -1015,6 +1050,8 @@ test_expect_success '__git_refs - do not \n>>>> filter refs unless told so' '\n>>>>       other/branch-in-other\n>>>>       other/main-in-other\n>>>>       other/matching/branch-in-other\n>>>> +    remote/with/slash/HEAD\n>>>> +    remote/with/slash/branch/with/slash\n>>>>       matching-tag\n>>>>       matching/tag\n>>>>       EOF\n>>>> @@ -1135,6 +1172,8 @@ test_expect_success '__git_complete_refs - \n>>>> simple' '\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       matching-tag Z\n>>>>       EOF\n>>>>       (\n>>>> @@ -1173,6 +1212,20 @@ test_expect_success '__git_complete_refs - \n>>>> remote' '\n>>>>       test_cmp expected out\n>>>>   '\n>>>> +test_expect_success '__git_complete_refs - remote - with slash' '\n>>>> +    sed -e \"s/Z$//\" >expected <<-EOF &&\n>>>> +    HEAD Z\n>>>> +    HEAD Z\n>>>> +    branch/with/slash Z\n>>>> +    EOF\n>>>> +    (\n>>>> +        cur= &&\n>>>> +        __git_complete_refs --remote=remote/with/slash &&\n>>>> +        print_comp\n>>>> +    ) &&\n>>>> +    test_cmp expected out\n>>>> +'\n>>>> +\n>>>>   test_expect_success '__git_complete_refs - track' '\n>>>>       sed -e \"s/Z$//\" >expected <<-EOF &&\n>>>>       HEAD Z\n>>>> @@ -1181,9 +1234,11 @@ test_expect_success '__git_complete_refs - \n>>>> track' '\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       matching-tag Z\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main-in-other Z\n>>>>       EOF\n>>>>       (\n>>>> @@ -1228,6 +1283,8 @@ test_expect_success '__git_complete_refs - \n>>>> suffix' '\n>>>>       other/HEAD.\n>>>>       other/branch-in-other.\n>>>>       other/main-in-other.\n>>>> +    remote/with/slash/HEAD.\n>>>> +    remote/with/slash/branch/with/slash.\n>>>>       matching-tag.\n>>>>       EOF\n>>>>       (\n>>>> @@ -1253,6 +1310,20 @@ test_expect_success \n>>>> '__git_complete_fetch_refspecs - simple' '\n>>>>       test_cmp expected out\n>>>>   '\n>>>> +test_expect_success '__git_complete_fetch_refspecs - with slash' '\n>>>> +    sed -e \"s/Z$//\" >expected <<-EOF &&\n>>>> +    HEAD:HEAD Z\n>>>> +    HEAD:HEAD Z\n>>>> +    branch/with/slash:branch/with/slash Z\n>>>> +    EOF\n>>>> +    (\n>>>> +        cur= &&\n>>>> +        __git_complete_fetch_refspecs remote/with/slash &&\n>>>> +        print_comp\n>>>> +    ) &&\n>>>> +    test_cmp expected out\n>>>> +'\n>>>> +\n>>>>   test_expect_success '__git_complete_fetch_refspecs - matching' '\n>>>>       sed -e \"s/Z$//\" >expected <<-EOF &&\n>>>>       branch-in-other:branch-in-other Z\n>>>> @@ -1333,8 +1404,8 @@ test_expect_success \n>>>> '__git_complete_worktree_paths with -C' '\n>>>>   test_expect_success 'git switch - with no options, complete local \n>>>> branches and unique remote branch names for DWIM logic' '\n>>>>       test_completion \"git switch \" <<-\\EOF\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main Z\n>>>>       main-in-other Z\n>>>>       matching-branch Z\n>>>> @@ -1480,8 +1551,8 @@ test_expect_success 'git-bisect - existing \n>>>> view subcommand is recognized and ena\n>>>>   test_expect_success 'git checkout - completes refs and unique \n>>>> remote branches for DWIM' '\n>>>>       test_completion \"git checkout \" <<-\\EOF\n>>>>       HEAD Z\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main Z\n>>>>       main-in-other Z\n>>>>       matching-branch Z\n>>>> @@ -1489,6 +1560,8 @@ test_expect_success 'git checkout - completes \n>>>> refs and unique remote branches fo\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1508,8 +1581,8 @@ test_expect_success 'git switch - with \n>>>> GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n>>>>   test_expect_success 'git switch - --guess overrides \n>>>> GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and \n>>>> unique remote names for DWIM logic' '\n>>>>       GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch \n>>>> --guess \" <<-\\EOF\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main Z\n>>>>       main-in-other Z\n>>>>       matching-branch Z\n>>>> @@ -1518,8 +1591,8 @@ test_expect_success 'git switch - --guess \n>>>> overrides GIT_COMPLETION_CHECKOUT_NO_G\n>>>>   test_expect_success 'git switch - a later --guess overrides \n>>>> previous --no-guess, complete local and remote unique branches for \n>>>> DWIM' '\n>>>>       test_completion \"git switch --no-guess --guess \" <<-\\EOF\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main Z\n>>>>       main-in-other Z\n>>>>       matching-branch Z\n>>>> @@ -1542,14 +1615,16 @@ test_expect_success 'git checkout - with \n>>>> GIT_COMPLETION_NO_GUESS=1 only complete\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>>   test_expect_success 'git checkout - --guess overrides \n>>>> GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches \n>>>> for DWIM' '\n>>>>       GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git \n>>>> checkout --guess \" <<-\\EOF\n>>>>       HEAD Z\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main Z\n>>>>       main-in-other Z\n>>>>       matching-branch Z\n>>>> @@ -1557,6 +1632,8 @@ test_expect_success 'git checkout - --guess \n>>>> overrides GIT_COMPLETION_NO_GUESS=1,\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1569,14 +1646,16 @@ test_expect_success 'git checkout - with -- \n>>>> no-guess, only completes refs' '\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>>   test_expect_success 'git checkout - a later --guess overrides \n>>>> previous --no-guess, complete refs and unique remote branches for \n>>>> DWIM' '\n>>>>       test_completion \"git checkout --no-guess --guess \" <<-\\EOF\n>>>>       HEAD Z\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main Z\n>>>>       main-in-other Z\n>>>>       matching-branch Z\n>>>> @@ -1584,6 +1663,8 @@ test_expect_success 'git checkout - a later -- \n>>>> guess overrides previous --no-gues\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1596,6 +1677,8 @@ test_expect_success 'git checkout - a later -- \n>>>> no-guess overrides previous --gues\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1609,6 +1692,8 @@ test_expect_success 'git checkout - with \n>>>> checkout.guess = false, only completes\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1616,8 +1701,8 @@ test_expect_success 'git checkout - with \n>>>> checkout.guess = true, completes refs a\n>>>>       test_config checkout.guess true &&\n>>>>       test_completion \"git checkout \" <<-\\EOF\n>>>>       HEAD Z\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main Z\n>>>>       main-in-other Z\n>>>>       matching-branch Z\n>>>> @@ -1625,6 +1710,8 @@ test_expect_success 'git checkout - with \n>>>> checkout.guess = true, completes refs a\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1632,8 +1719,8 @@ test_expect_success 'git checkout - a later -- \n>>>> guess overrides previous checkout.\n>>>>       test_config checkout.guess false &&\n>>>>       test_completion \"git checkout --guess \" <<-\\EOF\n>>>>       HEAD Z\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main Z\n>>>>       main-in-other Z\n>>>>       matching-branch Z\n>>>> @@ -1641,6 +1728,8 @@ test_expect_success 'git checkout - a later -- \n>>>> guess overrides previous checkout.\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1654,6 +1743,8 @@ test_expect_success 'git checkout - a later -- \n>>>> no-guess overrides previous checko\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1666,6 +1757,8 @@ test_expect_success 'git switch - with -- \n>>>> detach, complete all references' '\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1678,6 +1771,8 @@ test_expect_success 'git checkout - with -- \n>>>> detach, complete only references' '\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1850,6 +1945,8 @@ test_expect_success 'git switch - with -d, \n>>>> complete all references' '\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1862,6 +1959,8 @@ test_expect_success 'git checkout - with -d, \n>>>> complete only references' '\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1870,11 +1969,15 @@ test_expect_success 'git switch - with -- \n>>>> track, complete only remote branches' '\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>       test_completion \"git switch -t \" <<-\\EOF\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1883,11 +1986,15 @@ test_expect_success 'git checkout - with -- \n>>>> track, complete only remote branches'\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>       test_completion \"git checkout -t \" <<-\\EOF\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1907,6 +2014,8 @@ test_expect_success 'git checkout - with --no- \n>>>> track, complete only local referen\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1919,6 +2028,8 @@ test_expect_success 'git switch - with -c, \n>>>> complete all references' '\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1931,6 +2042,8 @@ test_expect_success 'git switch - with -C, \n>>>> complete all references' '\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1943,6 +2056,8 @@ test_expect_success 'git switch - with -c and \n>>>> --track, complete all references'\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1955,6 +2070,8 @@ test_expect_success 'git switch - with -C and \n>>>> --track, complete all references'\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1967,6 +2084,8 @@ test_expect_success 'git switch - with -c and \n>>>> --no-track, complete all reference\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1979,6 +2098,8 @@ test_expect_success 'git switch - with -C and \n>>>> --no-track, complete all reference\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -1991,6 +2112,8 @@ test_expect_success 'git checkout - with -b, \n>>>> complete all references' '\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -2003,6 +2126,8 @@ test_expect_success 'git checkout - with -B, \n>>>> complete all references' '\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -2015,6 +2140,8 @@ test_expect_success 'git checkout - with -b \n>>>> and --track, complete all references\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -2027,6 +2154,8 @@ test_expect_success 'git checkout - with -B \n>>>> and --track, complete all references\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -2039,6 +2168,8 @@ test_expect_success 'git checkout - with -b \n>>>> and --no-track, complete all referen\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -2051,13 +2182,15 @@ test_expect_success 'git checkout - with -B \n>>>> and --no-track, complete all referen\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>>   test_expect_success 'git switch - for -c, complete local branches \n>>>> and unique remote branches' '\n>>>>       test_completion \"git switch -c \" <<-\\EOF\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main Z\n>>>>       main-in-other Z\n>>>>       matching-branch Z\n>>>> @@ -2066,8 +2199,8 @@ test_expect_success 'git switch - for -c, \n>>>> complete local branches and unique rem\n>>>>   test_expect_success 'git switch - for -C, complete local branches \n>>>> and unique remote branches' '\n>>>>       test_completion \"git switch -C \" <<-\\EOF\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main Z\n>>>>       main-in-other Z\n>>>>       matching-branch Z\n>>>> @@ -2104,8 +2237,8 @@ test_expect_success 'git switch - for -C with \n>>>> --no-track, complete local branche\n>>>>   test_expect_success 'git checkout - for -b, complete local \n>>>> branches and unique remote branches' '\n>>>>       test_completion \"git checkout -b \" <<-\\EOF\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main Z\n>>>>       main-in-other Z\n>>>>       matching-branch Z\n>>>> @@ -2114,8 +2247,8 @@ test_expect_success 'git checkout - for -b, \n>>>> complete local branches and unique r\n>>>>   test_expect_success 'git checkout - for -B, complete local \n>>>> branches and unique remote branches' '\n>>>>       test_completion \"git checkout -B \" <<-\\EOF\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main Z\n>>>>       main-in-other Z\n>>>>       matching-branch Z\n>>>> @@ -2152,8 +2285,8 @@ test_expect_success 'git checkout - for -B \n>>>> with --no-track, complete local branc\n>>>>   test_expect_success 'git switch - with --orphan completes local \n>>>> branch names and unique remote branch names' '\n>>>>       test_completion \"git switch --orphan \" <<-\\EOF\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main Z\n>>>>       main-in-other Z\n>>>>       matching-branch Z\n>>>> @@ -2168,8 +2301,8 @@ test_expect_success 'git switch - --orphan \n>>>> with branch already provided complete\n>>>>   test_expect_success 'git checkout - with --orphan completes local \n>>>> branch names and unique remote branch names' '\n>>>>       test_completion \"git checkout --orphan \" <<-\\EOF\n>>>> -    HEAD Z\n>>>>       branch-in-other Z\n>>>> +    branch/with/slash Z\n>>>>       main Z\n>>>>       main-in-other Z\n>>>>       matching-branch Z\n>>>> @@ -2185,6 +2318,8 @@ test_expect_success 'git checkout - --orphan \n>>>> with branch already provided comple\n>>>>       other/HEAD Z\n>>>>       other/branch-in-other Z\n>>>>       other/main-in-other Z\n>>>> +    remote/with/slash/HEAD Z\n>>>> +    remote/with/slash/branch/with/slash Z\n>>>>       EOF\n>>>>   '\n>>>> @@ -2199,7 +2334,8 @@ test_expect_success 'git restore completes \n>>>> modified files' '\n>>>>   test_expect_success 'teardown after ref completion' '\n>>>>       git branch -d matching-branch &&\n>>>>       git tag -d matching-tag &&\n>>>> -    git remote remove other\n>>>> +    git remote remove other &&\n>>>> +    git remote remove remote/with/slash\n>>>>   '\n>>>\n>>\n> \n\n"},{"id":"513728","messageId":"CALnO6CBDjQgc2TNaXbBWiUNqWV=RgrL2Ry-7HzRu3Ozuqazzvw@mail.gmail.com","threadId":"63039","inReplyTo":"42dfc06e-8eb1-46da-9971-9d102f1390ad@mandelberg.org","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2025-03-06T20:24:39Z","receivedAt":"2025-03-06T20:24:52Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"On Mon, Mar 3, 2025 at 2:25 PM David Mandelberg <david@mandelberg.org> wrote:\n>\n> Op 2025-03-03 om 11:36 schreef phillip.wood123@gmail.com:\n> >>> local sed_cmd=\n> >>> local remote\n> >>> # ref names and therefore remote names cannot contain '*?[]^' so we\n> >>> # only need to escape '.$/'. Using 'sort -r' means that if there is a\n> >>> # remote called \"github\" and another called \"github/alice\" we will try\n> >>> # and strip \"github/alice\" first.\n> >>> for remote in $(__git_remotes | sort -r)\n> >>> do\n> >>>      remote=\"${remote//./\\\\./}\"\n> >>>      remote=\"${remote//\\$/\\\\\\$/}\"\n> >>>      remote=\"${remote//\\//\\\\\\//}\"\n> >>\n> >> Just FYI since it took me hours to figure this out myself: I think\n> >> this would break tests on macos because of an old version of bash that\n> >> handles backslashes weirdly. I think removing the double quotes would\n> >> work around that issue, and be safe because word splitting doesn't\n> >> happen in assignments.\n> >\n> > Thanks, I'm not familiar with bash's extensions to parameter\n> > substitution. The completions can also but used under zsh (git-\n> > completion.zsh is pretty much abandoned I think) but it looks like bash\n> > and zsh agree on this expansion.\n\nI don't think \"abandoned\" is the right characterization—at least, it's\nthe completion script that Homebrew-installed Git users will get for\nZsh, and it does re-use the Bash completion scripts (which therefore\nought to stay portable between both if possible, otherwise we get\nstuff like 8776470cf3 (completion: repair config completion for Zsh,\n2025-01-06)). The Zsh script has seen some fixups over time (such as\nmy own 3c20acdf46 (completion: zsh: stop leaking local cache variable,\n2024-04-30)).\n\n>\n> The tests don't automatically run under zsh though, right? Maybe I\n> should try to figure out how to do that in a separate patch...\n\nCorrect—which is how bugs sneak in ;)\n\nI'm willing to manually test the patch if I can understand how to\nreproduce the issue—it sounds like having a remote name with a slash\nis sufficient?\n\nI started trying to test Zsh completion a while back when working on\none of the patches mentioned above; I got as far as this hack [1],\nwhich is to say, not very far at all.\n\n[1]: https://github.com/git/git/commit/d8918195f18a503aa1a42fed3c66a0af8d04131f\n\n--\nD. Ben Knoble\n"},{"id":"513729","messageId":"CALnO6CBjigppCYBTd049osHrcA=snaGrYk4YW0m+tzHqYAaikg@mail.gmail.com","threadId":"63039","inReplyTo":"Z8YGY1QMaIsQTXoS@teonanacatl.net","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2025-03-06T20:26:30Z","receivedAt":"2025-03-06T20:26:44Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"On Mon, Mar 3, 2025 at 2:43 PM Todd Zullinger <tmz@pobox.com> wrote:\n>\n> David Mandelberg wrote:\n> > I think that would work. I was hoping to avoid sed, and I was thinking about\n> > using shell's ${parameter#word} to strip and test for matching prefixes. But\n> > I can't think of a way to do that with GIT_COMPLETION_IGNORE_CASE. Modern\n> > bash has ${parameter@L} to lowercase a string, but I don't think the version\n> > of bash on macos has that. With sed it would just be a matter of adding the\n> > i flag if needed.\n>\n> I haven't followed this discussion closely, but you can\n> also use ${parameter,,} to do parameter case modification.\n>\n> Sadly, it looks like those were added in bash-4.0, so Apple\n> will have kept their users from benefiting from that to\n> avoid GPLv3 code. :/\n>\n> I don't have an ancient bash-3.x like MacOS to test, but\n> maybe they've backported some features?\n\nI can confirm Apple's /bin/bash doesn't have ${x,,}\n\n>\n> Though if you do end up using this, ${parameter,,} will work\n> in a much wider range of bash version that ${parameter@L}\n> which covers various releases in the OS's we aim to target\n> (e.g. RHEL 8.x, which has bash-4.4).\n>\n> --\n> Todd\n>\n\n\n-- \nD. Ben Knoble\n"},{"id":"513730","messageId":"xmqqldthhp3b.fsf@gitster.g","threadId":"63039","inReplyTo":"CALnO6CBDjQgc2TNaXbBWiUNqWV=RgrL2Ry-7HzRu3Ozuqazzvw@mail.gmail.com","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-03-06T20:38:16Z","receivedAt":"2025-03-06T20:38:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"D. Ben Knoble\" <ben.knoble@gmail.com> writes:\n\n> On Mon, Mar 3, 2025 at 2:25 PM David Mandelberg <david@mandelberg.org> wrote:\n>>\n>> > Thanks, I'm not familiar with bash's extensions to parameter\n>> > substitution. The completions can also but used under zsh (git-\n>> > completion.zsh is pretty much abandoned I think) but it looks like bash\n>> > and zsh agree on this expansion.\n>\n> I don't think \"abandoned\" is the right characterization—at least, it's\n> the completion script that Homebrew-installed Git users will get for\n> Zsh, and it does re-use the Bash completion scripts (which therefore\n> ought to stay portable between both if possible, otherwise we get\n> stuff like 8776470cf3 (completion: repair config completion for Zsh,\n> 2025-01-06)). The Zsh script has seen some fixups over time (such as\n> my own 3c20acdf46 (completion: zsh: stop leaking local cache variable,\n> 2024-04-30)).\n\nThanks for clarifying.\n\n>> The tests don't automatically run under zsh though, right? Maybe I\n>> should try to figure out how to do that in a separate patch...\n>\n> Correct—which is how bugs sneak in ;)\n>\n> I'm willing to manually test the patch if I can understand how to\n> reproduce the issue—it sounds like having a remote name with a slash\n> is sufficient?\n>\n> I started trying to test Zsh completion a while back when working on\n> one of the patches mentioned above; I got as far as this hack [1],\n> which is to say, not very far at all.\n\nThanks.\n"},{"id":"513731","messageId":"xmqqh645hopj.fsf@gitster.g","threadId":"63039","inReplyTo":"95ffa62df6ce394249a8ddabb84fb2b517825fe3.1740901525.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-03-06T20:46:32Z","receivedAt":"2025-03-06T20:46:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"David Mandelberg via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This made completions like:\n>\n> git push github/dseomn :com<Tab>\n>\n> Result in:\n>\n> git push github/dseomn :dseomn/completion-remote-slash\n\nApparently I am late to the party, but I have to wonder how\nprevalent it is to use such a name for remotes.\n\nBack in 2005 when I designed the remote namespaces and\nremote-tracking branches, the code may have been loose not to\ncomplain, but it certainly was not an intended way the remote names\nand remote-tracking hiearchies were to be used.  Being able to say\n\"refs/remote/*/master\" and learn where everybody's 'master' branch\npoints at with a single globbing was a powerful thing, but with\nrefs/remotes/github/dseomn/master thrown into the mix, that would\nbreak down miserably.  As a remote name, we still do use \"is that a\nfile on the filesystem?\" to see if it is a local file:// URL\n(i.e. \"git fetch github/dseomn\" may be fetching from a subdirectory\ntwo levels down); in retrospect, we might have been better off if we\nsaid \"well with a slash it must be a local directory and not remote\nnickname\" to prevent such usage pattern.\n\nAnyway, this is not something we'd do lightly, but we may want to\nsee if we should tighten the naming rules for remote nicknames and\nthe mapping from nicknames to remote-tracking hiearchy, at Git 3.0\nversion boundary.\n\n"},{"id":"513743","messageId":"f68f4c5f-1bbc-4c4a-95c1-63799af6991d@gmail.com","threadId":"63039","inReplyTo":"CALnO6CBDjQgc2TNaXbBWiUNqWV=RgrL2Ry-7HzRu3Ozuqazzvw@mail.gmail.com","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-03-07T10:34:45Z","receivedAt":"2025-03-07T10:34:49Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Ben\n\nOn 06/03/2025 20:24, D. Ben Knoble wrote:\n> On Mon, Mar 3, 2025 at 2:25 PM David Mandelberg <david@mandelberg.org> wrote:\n>> Op 2025-03-03 om 11:36 schreef phillip.wood123@gmail.com:\n >>\n>>> Thanks, I'm not familiar with bash's extensions to parameter\n>>> substitution. The completions can also but used under zsh (git-\n>>> completion.zsh is pretty much abandoned I think) but it looks like bash\n>>> and zsh agree on this expansion.\n> \n> I don't think \"abandoned\" is the right characterization—at least, it's\n> the completion script that Homebrew-installed Git users will get for\n> Zsh, and it does re-use the Bash completion scripts (which therefore\n> ought to stay portable between both if possible, otherwise we get\n> stuff like 8776470cf3 (completion: repair config completion for Zsh,\n> 2025-01-06)). The Zsh script has seen some fixups over time (such as\n> my own 3c20acdf46 (completion: zsh: stop leaking local cache variable,\n> 2024-04-30)).\n\nThanks for clarifying that, I'd misunderstood the relationship between \ngit-completion.zsh and git-completion.bash.\n\nBest Wishes\n\nPhillip\n\n"},{"id":"513789","messageId":"05bf397e-4bc2-4255-87b5-925c80667f4c@mandelberg.org","threadId":"63039","inReplyTo":"xmqqh645hopj.fsf@gitster.g","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-07T20:11:32Z","receivedAt":"2025-03-07T20:11:34Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Op 2025-03-06 om 15:46 schreef Junio C Hamano:\n> Apparently I am late to the party, but I have to wonder how\n> prevalent it is to use such a name for remotes.\n\n From a quick search, it looks like I'm not the only one, but I don't \nknow how common it is: https://github.com/desktop/desktop/issues/3618\n\nI'm not sure if I'm remembering right, but I think I got the idea from \nseeing slashes in branch names to organize a hierarchy of branches, and \nrealizing I could do the same with remotes. Was it intentional to allow \nslash in branch names, or is that a similar situation?\n\n> As a remote name, we still do use \"is that a\n> file on the filesystem?\" to see if it is a local file:// URL\n> (i.e. \"git fetch github/dseomn\" may be fetching from a subdirectory\n> two levels down);\n\nThat's a good reason not to use slashes in remotes that I didn't know \nabout, thanks!\n\n> Anyway, this is not something we'd do lightly, but we may want to\n> see if we should tighten the naming rules for remote nicknames and\n> the mapping from nicknames to remote-tracking hiearchy, at Git 3.0\n> version boundary.\n\nWhat does that mean for this patch now? Do you want to fix the \ncompletion for the currently (unintentionally) supported remote names, \nor leave it broken to discourage their use? Fixing the completion might \nalso give anybody reading the completion script the idea that slashes in \nremote names are somewhat sanctioned.\n"},{"id":"513794","messageId":"xmqqtt84d0dz.fsf@gitster.g","threadId":"63039","inReplyTo":"05bf397e-4bc2-4255-87b5-925c80667f4c@mandelberg.org","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-03-07T20:57:44Z","receivedAt":"2025-03-07T20:57:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"David Mandelberg <david@mandelberg.org> writes:\n\n> I'm not sure if I'm remembering right, but I think I got the idea from\n> seeing slashes in branch names to organize a hierarchy of branches,\n> and realizing I could do the same with remotes. Was it intentional to\n> allow slash in branch names, or is that a similar situation?\n\nReference names were hierarchical pretty much from the beginning.\n\n>> Anyway, this is not something we'd do lightly, but we may want to\n>> see if we should tighten the naming rules for remote nicknames and\n>> the mapping from nicknames to remote-tracking hiearchy, at Git 3.0\n>> version boundary.\n>\n> What does that mean for this patch now? Do you want to fix the\n> completion for the currently (unintentionally) supported remote names,\n> or leave it broken to discourage their use? Fixing the completion\n> might also give anybody reading the completion script the idea that\n> slashes in remote names are somewhat sanctioned.\n\nIt probably would not mean much either way, and certainly not\nsomething we should worry about during pre-release feature freeze.\n\nThe \"fix\" may take quite different shapes.  Remote nick/name may\nstill be allowed but the default fetch refspec left by \"git clone\"\nwould start using the refs/remotes/nick-name/ hierarchy instead of\nthe refs/remotes/nick/name hierarchy, for example, but as long as\nthis round of completion fixes properly read the configuration to\nlearn the remote-tracking hierarchy by reading the configured value\nof \"remote.nick/name.fetch\", such a change would not break the code\nin the completion script at all.  Or remote nick/name may outright\nbanned and people are encouraged to use nick-name instead, in which\ncase even if the completion code is prepared to accept remote names\nwith slashes in it, as long as it still works correctly with remote\nnames with no slashes in it, it will keep working in such a future.\n\nOr the \"fix\" may be to declare \"there is nothing to fix, if people\nwant to use\n\n    [remote \"nick/name\"] fetch = +refs/heads/*:refs/remotes/nickname/*\n    [remote \"nocknyme\"] fetch = +refs/heads/*:refs/remotes/nock/nyme/*\n\nthat's their choice, and the completion code would do the right thing\nanyway\".\n\nI didn't read your patches, so the situation may be different and\nwith any shape of \"fix\", or without any \"fix\", there might still be\nneed for further polishing.  But hopefully you got the idea.  For\nthis particular patch, there is nothing urgently needed.  Well, we\nare right now in the pre-release feature freeze anyway ;-).\n\nThanks.\n"},{"id":"513802","messageId":"1192682c-b68d-429d-9852-15ab627d711f@mandelberg.org","threadId":"63039","inReplyTo":"xmqqtt84d0dz.fsf@gitster.g","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-07T21:38:51Z","receivedAt":"2025-03-07T21:38:53Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Op 2025-03-07 om 15:57 schreef Junio C Hamano:\n> It probably would not mean much either way, and certainly not\n> something we should worry about during pre-release feature freeze.\n> \n> The \"fix\" may take quite different shapes.  Remote nick/name may\n> still be allowed but the default fetch refspec left by \"git clone\"\n> would start using the refs/remotes/nick-name/ hierarchy instead of\n> the refs/remotes/nick/name hierarchy, for example, but as long as\n> this round of completion fixes properly read the configuration to\n> learn the remote-tracking hierarchy by reading the configured value\n> of \"remote.nick/name.fetch\", such a change would not break the code\n> in the completion script at all.  Or remote nick/name may outright\n> banned and people are encouraged to use nick-name instead, in which\n> case even if the completion code is prepared to accept remote names\n> with slashes in it, as long as it still works correctly with remote\n> names with no slashes in it, it will keep working in such a future.\n> \n> Or the \"fix\" may be to declare \"there is nothing to fix, if people\n> want to use\n> \n>      [remote \"nick/name\"] fetch = +refs/heads/*:refs/remotes/nickname/*\n>      [remote \"nocknyme\"] fetch = +refs/heads/*:refs/remotes/nock/nyme/*\n> \n> that's their choice, and the completion code would do the right thing\n> anyway\".\n> \n> I didn't read your patches, so the situation may be different and\n> with any shape of \"fix\", or without any \"fix\", there might still be\n> need for further polishing.  But hopefully you got the idea.  For\n> this particular patch, there is nothing urgently needed.  Well, we\n> are right now in the pre-release feature freeze anyway ;-).\n> \n> Thanks.\n\nThanks for the explanation!\n\n> As a remote name, we still do use \"is that a\n> file on the filesystem?\" to see if it is a local file:// URL\n> (i.e. \"git fetch github/dseomn\" may be fetching from a subdirectory\n> two levels down)\n\nBtw, I just tested it, and I think this is an issue for remote names \nwithout slashes too:\n\n/tmp/tmp.sVAhhZuEzA$ git init foo\nInitialized empty Git repository in /tmp/tmp.sVAhhZuEzA/foo/.git/\n/tmp/tmp.sVAhhZuEzA$ cd foo\n/tmp/tmp.sVAhhZuEzA/foo$ git init --bare origin\nInitialized empty Git repository in /tmp/tmp.sVAhhZuEzA/foo/origin/\n/tmp/tmp.sVAhhZuEzA/foo$ git commit --allow-empty -m 'test'\n[main (root-commit) 24c92a9] test\n/tmp/tmp.sVAhhZuEzA/foo$ git push origin main\nEnumerating objects: 2, done.\nCounting objects: 100% (2/2), done.\nWriting objects: 100% (2/2), 162 bytes | 162.00 KiB/s, done.\nTotal 2 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)\nTo origin\n  * [new branch]      main -> main\n"},{"id":"513803","messageId":"xmqq4j04cy6t.fsf@gitster.g","threadId":"63039","inReplyTo":"1192682c-b68d-429d-9852-15ab627d711f@mandelberg.org","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-03-07T21:45:14Z","receivedAt":"2025-03-07T21:45:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"David Mandelberg <david@mandelberg.org> writes:\n\n>> As a remote name, we still do use \"is that a\n>> file on the filesystem?\" to see if it is a local file:// URL\n>> (i.e. \"git fetch github/dseomn\" may be fetching from a subdirectory\n>> two levels down)\n>\n> Btw, I just tested it, and I think this is an issue for remote names\n> without slashes too:\n\nYes, and that is an intended way local repositories are discovered\nand used.\n\nThe problem with slashes is that the mistake surface becomes a lot\nlarger.  \"ls<RETURN>\" would show you \"origin\" immediately in the\ncurrent working directory, but you wouldn't know if origin has\norigin/foo or origin/bar unless you look.  Limiting the local\nfilesystem reference to something like \"only the ones that has no\nslashes, or begin with ../ or /\" would make it less likely that you\nmeant to push to somewhere and instead push to a local directory.\n\n"},{"id":"513804","messageId":"e06caa06-0176-4340-958e-4c43650b40e3@mandelberg.org","threadId":"63039","inReplyTo":"CALnO6CBDjQgc2TNaXbBWiUNqWV=RgrL2Ry-7HzRu3Ozuqazzvw@mail.gmail.com","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-07T21:46:09Z","receivedAt":"2025-03-07T21:46:11Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Op 2025-03-06 om 15:24 schreef D. Ben Knoble:\n> I'm willing to manually test the patch if I can understand how to\n> reproduce the issue—it sounds like having a remote name with a slash\n> is sufficient?\n\nYup. I was able to reproduce it with these commands:\n\n/tmp/tmp.zOjfmdMx1i$ git init foo\nInitialized empty Git repository in /tmp/tmp.zOjfmdMx1i/foo/.git/\n/tmp/tmp.zOjfmdMx1i$ cd foo\n/tmp/tmp.zOjfmdMx1i/foo$ git remote add with/slash /path/does/not/matter\n/tmp/tmp.zOjfmdMx1i/foo$ git commit -m 'test' --allow-empty\n[main (root-commit) 4b95a99] test\n/tmp/tmp.zOjfmdMx1i/foo$ git update-ref refs/remotes/with/slash/main 4b95a99\n\nThen type:\n\ngit push with/slash :ma<Tab>\n"},{"id":"514217","messageId":"CALnO6CBurvi=gXzSdYDqSWDj9+R_uXY0rMZvz+dVKa+Bcfe=6A@mail.gmail.com","threadId":"63039","inReplyTo":"e06caa06-0176-4340-958e-4c43650b40e3@mandelberg.org","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2025-03-13T17:40:45Z","receivedAt":"2025-03-13T17:40:59Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"On Fri, Mar 7, 2025 at 4:46 PM David Mandelberg <david@mandelberg.org> wrote:\n>\n> Op 2025-03-06 om 15:24 schreef D. Ben Knoble:\n> > I'm willing to manually test the patch if I can understand how to\n> > reproduce the issue—it sounds like having a remote name with a slash\n> > is sufficient?\n>\n> Yup. I was able to reproduce it with these commands:\n>\n> /tmp/tmp.zOjfmdMx1i$ git init foo\n> Initialized empty Git repository in /tmp/tmp.zOjfmdMx1i/foo/.git/\n> /tmp/tmp.zOjfmdMx1i$ cd foo\n> /tmp/tmp.zOjfmdMx1i/foo$ git remote add with/slash /path/does/not/matter\n> /tmp/tmp.zOjfmdMx1i/foo$ git commit -m 'test' --allow-empty\n> [main (root-commit) 4b95a99] test\n> /tmp/tmp.zOjfmdMx1i/foo$ git update-ref refs/remotes/with/slash/main 4b95a99\n>\n> Then type:\n>\n> git push with/slash :ma<Tab>\n\nThanks; in Zsh the results after :<Tab> are HEAD and main. After\n:ma<Tab>, just main. The results with _no_ colon are HEAD, main,\nwith/slash/main. I think that's all the expected results?\n\nMy test procedure was to\n\ngit fetch <your PR tip>\nmv /usr/local/share/zsh/site-functions/git-completion.bash{,.bak}\ngit show FETCH_HEAD:contrib/completion/git-completion.bash  >\n/usr/local/share/zsh/site-functions/git-completion.bash\n# adjust startup files to use `compinit -D` to avoid caches\n<your test procedure>\n\n-- \nD. Ben Knoble\n"},{"id":"514229","messageId":"68251fa5-ec70-4eb4-80df-111ec3843536@mandelberg.org","threadId":"63039","inReplyTo":"CALnO6CBurvi=gXzSdYDqSWDj9+R_uXY0rMZvz+dVKa+Bcfe=6A@mail.gmail.com","subject":"Re: [PATCH 3/3] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-13T20:19:13Z","receivedAt":"2025-03-13T20:19:16Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Op 2025-03-13 om 13:40 schreef D. Ben Knoble:\n> On Fri, Mar 7, 2025 at 4:46 PM David Mandelberg <david@mandelberg.org> wrote:\n>>\n>> Op 2025-03-06 om 15:24 schreef D. Ben Knoble:\n>>> I'm willing to manually test the patch if I can understand how to\n>>> reproduce the issue—it sounds like having a remote name with a slash\n>>> is sufficient?\n>>\n>> Yup. I was able to reproduce it with these commands:\n>>\n>> /tmp/tmp.zOjfmdMx1i$ git init foo\n>> Initialized empty Git repository in /tmp/tmp.zOjfmdMx1i/foo/.git/\n>> /tmp/tmp.zOjfmdMx1i$ cd foo\n>> /tmp/tmp.zOjfmdMx1i/foo$ git remote add with/slash /path/does/not/matter\n>> /tmp/tmp.zOjfmdMx1i/foo$ git commit -m 'test' --allow-empty\n>> [main (root-commit) 4b95a99] test\n>> /tmp/tmp.zOjfmdMx1i/foo$ git update-ref refs/remotes/with/slash/main 4b95a99\n>>\n>> Then type:\n>>\n>> git push with/slash :ma<Tab>\n> \n> Thanks; in Zsh the results after :<Tab> are HEAD and main. After\n> :ma<Tab>, just main. The results with _no_ colon are HEAD, main,\n> with/slash/main. I think that's all the expected results?\n\nThat sounds right to me, thanks for testing it!\n"},{"id":"514296","messageId":"1587533591c81d38977e62165784f8eb@mandelberg.org","threadId":"63039","inReplyTo":"pull.1901.git.git.1740901525.gitgitgadget@gmail.com","subject":"[PATCH v3 0/2] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-14T19:40:58Z","receivedAt":"2025-03-14T19:41:00Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"It's been a bit over a week since the last version of this, with some\ndiscussion, but I don't think any requests for changes. So here's an\nupdated version with the minor performance change I mentioned in\nhttps://lore.kernel.org/git/fa70c8336f836ebb5b9c196fe291d357@mandelberg.org/\n\nDavid Mandelberg (2):\n  completion: add helper to count path components\n  completion: fix bugs with slashes in remote names\n\n contrib/completion/git-completion.bash |  49 +++++-\n t/t9902-completion.sh                  | 206 ++++++++++++++++++++++---\n 2 files changed, 226 insertions(+), 29 deletions(-)\n\n-- \n2.47.2\n"},{"id":"514297","messageId":"769439687ea2fe89942a9e28822671ed@mandelberg.org","threadId":"63039","inReplyTo":"1587533591c81d38977e62165784f8eb@mandelberg.org","subject":"[PATCH v3 1/2] completion: add helper to count path components","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-14T19:41:56Z","receivedAt":"2025-03-14T19:41:58Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"A follow-up commit will use this with for-each-ref to strip the right\nnumber of path components from refnames.\n\nSigned-off-by: David Mandelberg <david@mandelberg.org>\n---\n contrib/completion/git-completion.bash | 11 +++++++++++\n t/t9902-completion.sh                  | 26 ++++++++++++++++++++++++++\n 2 files changed, 37 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 413911be3b..5fdc71208e 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -234,6 +234,17 @@ __git_dequote ()\n \tdone\n }\n \n+# Prints the number of slash-separated components in a path.\n+# 1: Path to count components of.\n+__git_count_path_components ()\n+{\n+\tlocal path=\"$1\"\n+\tlocal relative=\"${path#/}\"\n+\trelative=\"${relative%/}\"\n+\tlocal slashes=\"/${relative//[^\\/]}\"\n+\techo \"${#slashes}\"\n+}\n+\n # The following function is based on code from:\n #\n #   bash_completion - programmable completion functions for bash 3.2+\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 51bd750837..015289c776 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -455,6 +455,32 @@ test_expect_success '__git_dequote - open double quote' '\n '\n \n \n+test_expect_success '__git_count_path_components - no slashes' '\n+\techo 1 >expected &&\n+\t__git_count_path_components a >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_count_path_components - relative' '\n+\techo 3 >expected &&\n+\t__git_count_path_components a/b/c >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+\n+'\n+\n+test_expect_success '__git_count_path_components - absolute' '\n+\techo 3 >expected &&\n+\t__git_count_path_components /a/b/c >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_count_path_components - trailing slash' '\n+\techo 3 >expected &&\n+\t__git_count_path_components a/b/c/ >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+'\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-- \n2.47.2\n"},{"id":"514298","messageId":"c80d20d93c92422437150084f402ac36@mandelberg.org","threadId":"63039","inReplyTo":"1587533591c81d38977e62165784f8eb@mandelberg.org","subject":"[PATCH v3 2/2] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-14T19:43:06Z","receivedAt":"2025-03-14T19:43:08Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Previously, some calls to for-each-ref passed fixed numbers of path\ncomponents to strip from refs, assuming that remote names had no slashes\nin them. This made completions like:\n\ngit push github/dseomn :com<Tab>\n\nResult in:\n\ngit push github/dseomn :dseomn/completion-remote-slash\n\nWith this patch, it instead results in:\n\ngit push github/dseomn :completion-remote-slash\n\nSigned-off-by: David Mandelberg <david@mandelberg.org>\n---\n contrib/completion/git-completion.bash |  38 +++++-\n t/t9902-completion.sh                  | 180 ++++++++++++++++++++++---\n 2 files changed, 189 insertions(+), 29 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 5fdc71208e..c9d014070c 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -790,16 +790,39 @@ __git_tags ()\n __git_dwim_remote_heads ()\n {\n \tlocal pfx=\"${1-}\" cur_=\"${2-}\" sfx=\"${3-}\"\n-\tlocal fer_pfx=\"${pfx//\\%/%%}\" # \"escape\" for-each-ref format specifiers\n \n \t# employ the heuristic used by git checkout and git switch\n \t# Try to find a remote branch that cur_es the completion word\n \t# but only output if the branch name is unique\n-\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=3)$sfx\" \\\n-\t\t--sort=\"refname:strip=3\" \\\n-\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n-\t\t\"refs/remotes/*/$cur_*\" \"refs/remotes/*/$cur_*/**\" | \\\n-\tuniq -u\n+\tlocal awk_script='\n+\tfunction casemap(s) {\n+\t\tif (ENVIRON[\"IGNORE_CASE\"])\n+\t\t\treturn tolower(s)\n+\t\telse\n+\t\t\treturn s\n+\t}\n+\tBEGIN {\n+\t\tsplit(ENVIRON[\"REMOTES\"], remotes, /\\n/)\n+\t\tfor (i in remotes)\n+\t\t\tremotes[i] = \"refs/remotes/\" casemap(remotes[i])\n+\t\tcur_ = casemap(ENVIRON[\"CUR_\"])\n+\t}\n+\t{\n+\t\tref_case = casemap($0)\n+\t\tfor (i in remotes) {\n+\t\t\tif (index(ref_case, remotes[i] \"/\" cur_) == 1) {\n+\t\t\t\tbranch = substr($0, length(remotes[i] \"/\") + 1)\n+\t\t\t\tprint ENVIRON[\"PFX\"] branch ENVIRON[\"SFX\"]\n+\t\t\t\tbreak\n+\t\t\t}\n+\t\t}\n+\t}\n+\t'\n+\t__git for-each-ref --format='%(refname)' 'refs/remotes/**' |\n+\t\tPFX=\"$pfx\" SFX=\"$sfx\" CUR_=\"$cur_\" \\\n+\t\t\tIGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \\\n+\t\t\tREMOTES=\"$(__git_remotes | sort -r)\" awk \"$awk_script\" |\n+\t\tsort | uniq -u\n }\n \n # Lists refs from the local (by default) or from a remote repository.\n@@ -905,7 +928,8 @@ __git_refs ()\n \t\t\tcase \"HEAD\" in\n \t\t\t$match*|$umatch*)\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\tlocal strip=\"$(__git_count_path_components \"refs/remotes/$remote\")\"\n+\t\t\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=$strip)$sfx\" \\\n \t\t\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n \t\t\t\t\"refs/remotes/$remote/$match*\" \\\n \t\t\t\t\"refs/remotes/$remote/$match*/**\"\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 015289c776..343b8cd191 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -149,7 +149,8 @@ fi\n test_expect_success 'setup for __git_find_repo_path/__gitdir tests' '\n \tmkdir -p subdir/subsubdir &&\n \tmkdir -p non-repo &&\n-\tgit init -b main otherrepo\n+\tgit init -b main otherrepo &&\n+\tgit init -b main slashrepo\n '\n \n test_expect_success '__git_find_repo_path - from command line (through $__git_dir)' '\n@@ -674,6 +675,13 @@ test_expect_success 'setup for ref completion' '\n \t) &&\n \tgit remote add other \"$ROOT/otherrepo/.git\" &&\n \tgit fetch --no-tags other &&\n+\t(\n+\t\tcd slashrepo &&\n+\t\tgit commit --allow-empty -m initial &&\n+\t\tgit branch -m main branch/with/slash\n+\t) &&\n+\tgit remote add remote/with/slash \"$ROOT/slashrepo/.git\" &&\n+\tgit fetch --no-tags remote/with/slash &&\n \trm -f .git/FETCH_HEAD &&\n \tgit init thirdrepo\n '\n@@ -686,6 +694,8 @@ test_expect_success '__git_refs - simple' '\n \tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n \tEOF\n \t(\n@@ -702,6 +712,8 @@ test_expect_success '__git_refs - full refs' '\n \trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n+\trefs/remotes/remote/with/slash/HEAD\n+\trefs/remotes/remote/with/slash/branch/with/slash\n \trefs/tags/matching-tag\n \tEOF\n \t(\n@@ -767,6 +779,19 @@ test_expect_success '__git_refs - configured remote' '\n \ttest_cmp expected \"$actual\"\n '\n \n+test_expect_success '__git_refs - configured remote - with slash' '\n+\tcat >expected <<-EOF &&\n+\tHEAD\n+\tHEAD\n+\tbranch/with/slash\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_refs remote/with/slash >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n test_expect_success '__git_refs - configured remote - full refs' '\n \tcat >expected <<-EOF &&\n \tHEAD\n@@ -909,17 +934,19 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \tother/ambiguous\n \tother/branch-in-other\n \tother/main-in-other\n-\tremote/ambiguous\n-\tremote/branch-in-remote\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/ambiguous\n+\tremote/with/slash/branch-in-remote\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n-\tHEAD\n \tbranch-in-other\n \tbranch-in-remote\n+\tbranch/with/slash\n \tmain-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+\t\trefs/remotes/remote/with/slash/ambiguous \\\n+\t\trefs/remotes/remote/with/slash/branch-in-remote\n \tdo\n \t\tgit update-ref $remote_ref main &&\n \t\ttest_when_finished \"git update-ref -d $remote_ref\" || return 1\n@@ -939,6 +966,8 @@ test_expect_success '__git_refs - after --opt=' '\n \tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n \tEOF\n \t(\n@@ -955,6 +984,8 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n+\trefs/remotes/remote/with/slash/HEAD\n+\trefs/remotes/remote/with/slash/branch/with/slash\n \trefs/tags/matching-tag\n \tEOF\n \t(\n@@ -972,6 +1003,8 @@ test_expect_success '__git refs - excluding refs' '\n \t^other/HEAD\n \t^other/branch-in-other\n \t^other/main-in-other\n+\t^remote/with/slash/HEAD\n+\t^remote/with/slash/branch/with/slash\n \t^matching-tag\n \tEOF\n \t(\n@@ -988,6 +1021,8 @@ test_expect_success '__git refs - excluding full refs' '\n \t^refs/remotes/other/HEAD\n \t^refs/remotes/other/branch-in-other\n \t^refs/remotes/other/main-in-other\n+\t^refs/remotes/remote/with/slash/HEAD\n+\t^refs/remotes/remote/with/slash/branch/with/slash\n \t^refs/tags/matching-tag\n \tEOF\n \t(\n@@ -1015,6 +1050,8 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n \tother/branch-in-other\n \tother/main-in-other\n \tother/matching/branch-in-other\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n \tmatching/tag\n \tEOF\n@@ -1135,6 +1172,8 @@ test_expect_success '__git_complete_refs - simple' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tmatching-tag Z\n \tEOF\n \t(\n@@ -1173,6 +1212,20 @@ test_expect_success '__git_complete_refs - remote' '\n \ttest_cmp expected out\n '\n \n+test_expect_success '__git_complete_refs - remote - with slash' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tHEAD Z\n+\tHEAD Z\n+\tbranch/with/slash Z\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_complete_refs --remote=remote/with/slash &&\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@@ -1181,9 +1234,11 @@ test_expect_success '__git_complete_refs - track' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tmatching-tag Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain-in-other Z\n \tEOF\n \t(\n@@ -1228,6 +1283,8 @@ test_expect_success '__git_complete_refs - suffix' '\n \tother/HEAD.\n \tother/branch-in-other.\n \tother/main-in-other.\n+\tremote/with/slash/HEAD.\n+\tremote/with/slash/branch/with/slash.\n \tmatching-tag.\n \tEOF\n \t(\n@@ -1253,6 +1310,20 @@ test_expect_success '__git_complete_fetch_refspecs - simple' '\n \ttest_cmp expected out\n '\n \n+test_expect_success '__git_complete_fetch_refspecs - with slash' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tHEAD:HEAD Z\n+\tHEAD:HEAD Z\n+\tbranch/with/slash:branch/with/slash Z\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_complete_fetch_refspecs remote/with/slash &&\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@@ -1333,8 +1404,8 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n \n test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n \ttest_completion \"git switch \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1480,8 +1551,8 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1489,6 +1560,8 @@ test_expect_success 'git checkout - completes refs and unique remote branches fo\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1508,8 +1581,8 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n \n test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1518,8 +1591,8 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n \n test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1542,14 +1615,16 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1557,6 +1632,8 @@ test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1,\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1569,14 +1646,16 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1584,6 +1663,8 @@ test_expect_success 'git checkout - a later --guess overrides previous --no-gues\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1596,6 +1677,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1609,6 +1692,8 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1616,8 +1701,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \ttest_config checkout.guess true &&\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1625,6 +1710,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1632,8 +1719,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \ttest_config checkout.guess false &&\n \ttest_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1641,6 +1728,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1654,6 +1743,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1666,6 +1757,8 @@ test_expect_success 'git switch - with --detach, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1678,6 +1771,8 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1850,6 +1945,8 @@ test_expect_success 'git switch - with -d, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1862,6 +1959,8 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1870,11 +1969,15 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n \ttest_completion \"git switch -t \" <<-\\EOF\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1883,11 +1986,15 @@ test_expect_success 'git checkout - with --track, complete only remote branches'\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n \ttest_completion \"git checkout -t \" <<-\\EOF\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1907,6 +2014,8 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1919,6 +2028,8 @@ test_expect_success 'git switch - with -c, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1931,6 +2042,8 @@ test_expect_success 'git switch - with -C, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1943,6 +2056,8 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1955,6 +2070,8 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1967,6 +2084,8 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1979,6 +2098,8 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1991,6 +2112,8 @@ test_expect_success 'git checkout - with -b, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2003,6 +2126,8 @@ test_expect_success 'git checkout - with -B, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2015,6 +2140,8 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2027,6 +2154,8 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2039,6 +2168,8 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2051,13 +2182,15 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -c \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2066,8 +2199,8 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n \n test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -C \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2104,8 +2237,8 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n \n test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -b \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2114,8 +2247,8 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n \n test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -B \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2152,8 +2285,8 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n \n test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git switch --orphan \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2168,8 +2301,8 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n \n test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git checkout --orphan \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2185,6 +2318,8 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2199,7 +2334,8 @@ test_expect_success 'git restore completes modified files' '\n test_expect_success 'teardown after ref completion' '\n \tgit branch -d matching-branch &&\n \tgit tag -d matching-tag &&\n-\tgit remote remove other\n+\tgit remote remove other &&\n+\tgit remote remove remote/with/slash\n '\n \n \n-- \n2.47.2\n"},{"id":"514324","messageId":"xmqqo6y3dzno.fsf@gitster.g","threadId":"63039","inReplyTo":"c80d20d93c92422437150084f402ac36@mandelberg.org","subject":"Re: [PATCH v3 2/2] completion: fix bugs with slashes in remote names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-03-14T22:18:35Z","receivedAt":"2025-03-14T22:18:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"David Mandelberg <david@mandelberg.org> writes:\n\n> +\t__git for-each-ref --format='%(refname)' 'refs/remotes/**' |\n\nJust a bit of curiosity, but how would this be different from using\n\n\t__git for-each-ref --format='%(refname)' refs/remotes/\n\ni.e. the prefix match, here?  Does the wildmatcher clever enough to\noptimize \"**\" away and make it just to iterate over refs/remotes/\nhierarchy without filtering anything?\n"},{"id":"514329","messageId":"015b89f8-63eb-4203-8d43-47c9a6d65cda@mandelberg.org","threadId":"63039","inReplyTo":"xmqqo6y3dzno.fsf@gitster.g","subject":"Re: [PATCH v3 2/2] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-14T22:37:53Z","receivedAt":"2025-03-14T22:37:55Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Op 2025-03-14 om 18:18 schreef Junio C Hamano:\n> David Mandelberg <david@mandelberg.org> writes:\n> \n>> +\t__git for-each-ref --format='%(refname)' 'refs/remotes/**' |\n> \n> Just a bit of curiosity, but how would this be different from using\n> \n> \t__git for-each-ref --format='%(refname)' refs/remotes/\n> \n> i.e. the prefix match, here?  Does the wildmatcher clever enough to\n> optimize \"**\" away and make it just to iterate over refs/remotes/\n> hierarchy without filtering anything?\n\nOh right, I lost track of that bit. When I wrote that, I didn't know \nthat for-each-ref did prefix matching. Phillip Wood pointed it out:\n\n> If there are no glob characters then \"git for-each-ref\" does a prefix match so strictly speaking you don't need the '**' but I don't think it matters in practice.\n\nBut I forgot about it when I sent v3.\n\nDo you want me to change it? Should I send a v4 now, or just make a \nfixup commit locally so I don't forget it when I send a v4 in the future?\n"},{"id":"514331","messageId":"xmqq34ffdwnl.fsf@gitster.g","threadId":"63039","inReplyTo":"015b89f8-63eb-4203-8d43-47c9a6d65cda@mandelberg.org","subject":"Re: [PATCH v3 2/2] completion: fix bugs with slashes in remote names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-03-14T23:23:26Z","receivedAt":"2025-03-14T23:23:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"David Mandelberg <david@mandelberg.org> writes:\n\n> Op 2025-03-14 om 18:18 schreef Junio C Hamano:\n>> David Mandelberg <david@mandelberg.org> writes:\n>> \n>>> +\t__git for-each-ref --format='%(refname)' 'refs/remotes/**' |\n>> Just a bit of curiosity, but how would this be different from using\n>> \t__git for-each-ref --format='%(refname)' refs/remotes/\n>> i.e. the prefix match, here?  Does the wildmatcher clever enough to\n>> optimize \"**\" away and make it just to iterate over refs/remotes/\n>> hierarchy without filtering anything?\n>\n> Oh right, I lost track of that bit. When I wrote that, I didn't know\n> that for-each-ref did prefix matching. Phillip Wood pointed it out:\n>\n>> If there are no glob characters then \"git for-each-ref\" does a prefix match so strictly speaking you don't need the '**' but I don't think it matters in practice.\n>\n> But I forgot about it when I sent v3.\n>\n> Do you want me to change it? Should I send a v4 now, or just make a\n> fixup commit locally so I don't forget it when I send a v4 in the\n> future?\n\nEntirely up to you.  I do not plan to perform an in-detail review on\nthe topic (I do not consider myself quite qualified), but others who\nsaw earlier rounds of the patch may want to say \"now this is perfect\"\nor \"now this is perfect with this and that changes\", and if you do not\nthink the differenes between v3 and your upcoming v4 are small enough,\nit may be more efficient to wait for a bit to collect more comments.\n\nThanks.\n"},{"id":"514536","messageId":"d6945541-c106-49a2-8072-2a4e1b8903eb@gmail.com","threadId":"63039","inReplyTo":"xmqq34ffdwnl.fsf@gitster.g","subject":"Re: [PATCH v3 2/2] completion: fix bugs with slashes in remote names","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-03-18T17:02:03Z","receivedAt":"2025-03-18T17:02:10Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 14/03/2025 23:23, Junio C Hamano wrote:\n> \n> Entirely up to you.  I do not plan to perform an in-detail review on\n> the topic (I do not consider myself quite qualified), but others who\n> saw earlier rounds of the patch may want to say \"now this is perfect\"\n> or \"now this is perfect with this and that changes\", and if you do not\n> think the differenes between v3 and your upcoming v4 are small enough,\n> it may be more efficient to wait for a bit to collect more comments.\n\nI'm not an expert on our completion script but from a general shell \nscripting perspective this version looks fine to me.\n\nBest Wishes\n\nPhillip\n"},{"id":"514540","messageId":"xmqq4izq45c3.fsf@gitster.g","threadId":"63039","inReplyTo":"d6945541-c106-49a2-8072-2a4e1b8903eb@gmail.com","subject":"Re: [PATCH v3 2/2] completion: fix bugs with slashes in remote names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-03-18T17:27:24Z","receivedAt":"2025-03-18T17:27:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 14/03/2025 23:23, Junio C Hamano wrote:\n>> Entirely up to you.  I do not plan to perform an in-detail review on\n>> the topic (I do not consider myself quite qualified), but others who\n>> saw earlier rounds of the patch may want to say \"now this is perfect\"\n>> or \"now this is perfect with this and that changes\", and if you do not\n>> think the differenes between v3 and your upcoming v4 are small enough,\n>> it may be more efficient to wait for a bit to collect more comments.\n>\n> I'm not an expert on our completion script but from a general shell\n> scripting perspective this version looks fine to me.\n\nOK, then let me see if I can squash in before merging the topic to\n'next'.\n"},{"id":"514883","messageId":"17274df2746d304db876ebd82ad8d932@mandelberg.org","threadId":"63039","inReplyTo":"pull.1901.git.git.1740901525.gitgitgadget@gmail.com","subject":"[PATCH v4 0/2] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-23T21:04:51Z","receivedAt":"2025-03-23T21:04:53Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Another minor change, as suggested by\nhttps://lore.kernel.org/git/xmqqo6y3dzno.fsf@gitster.g/\n\nI'm not sure what the expectations are around updating patches that are\nscheduled to be merged to next but haven't been merged yet. If it's too\nlate, I'm happy with the previous v3, or I could send a separate patch.\n\nDavid Mandelberg (2):\n  completion: add helper to count path components\n  completion: fix bugs with slashes in remote names\n\n contrib/completion/git-completion.bash |  49 +++++-\n t/t9902-completion.sh                  | 206 ++++++++++++++++++++++---\n 2 files changed, 226 insertions(+), 29 deletions(-)\n\n-- \n2.47.2\n"},{"id":"514884","messageId":"dcfec0ed2ca2ce26f523e9531f5d3e76@mandelberg.org","threadId":"63039","inReplyTo":"17274df2746d304db876ebd82ad8d932@mandelberg.org","subject":"[PATCH v4 1/2] completion: add helper to count path components","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-23T21:05:46Z","receivedAt":"2025-03-23T21:05:48Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"A follow-up commit will use this with for-each-ref to strip the right\nnumber of path components from refnames.\n\nSigned-off-by: David Mandelberg <david@mandelberg.org>\n---\n contrib/completion/git-completion.bash | 11 +++++++++++\n t/t9902-completion.sh                  | 26 ++++++++++++++++++++++++++\n 2 files changed, 37 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 413911be3b..5fdc71208e 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -234,6 +234,17 @@ __git_dequote ()\n \tdone\n }\n \n+# Prints the number of slash-separated components in a path.\n+# 1: Path to count components of.\n+__git_count_path_components ()\n+{\n+\tlocal path=\"$1\"\n+\tlocal relative=\"${path#/}\"\n+\trelative=\"${relative%/}\"\n+\tlocal slashes=\"/${relative//[^\\/]}\"\n+\techo \"${#slashes}\"\n+}\n+\n # The following function is based on code from:\n #\n #   bash_completion - programmable completion functions for bash 3.2+\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 51bd750837..015289c776 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -455,6 +455,32 @@ test_expect_success '__git_dequote - open double quote' '\n '\n \n \n+test_expect_success '__git_count_path_components - no slashes' '\n+\techo 1 >expected &&\n+\t__git_count_path_components a >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_count_path_components - relative' '\n+\techo 3 >expected &&\n+\t__git_count_path_components a/b/c >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+\n+'\n+\n+test_expect_success '__git_count_path_components - absolute' '\n+\techo 3 >expected &&\n+\t__git_count_path_components /a/b/c >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n+test_expect_success '__git_count_path_components - trailing slash' '\n+\techo 3 >expected &&\n+\t__git_count_path_components a/b/c/ >\"$actual\" &&\n+\ttest_cmp expected \"$actual\"\n+'\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-- \n2.47.2\n"},{"id":"514885","messageId":"e12bfdb6a864eecaf11bfb6bdb3f7bd4@mandelberg.org","threadId":"63039","inReplyTo":"17274df2746d304db876ebd82ad8d932@mandelberg.org","subject":"[PATCH v4 2/2] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-03-23T21:06:53Z","receivedAt":"2025-03-23T21:06:55Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Previously, some calls to for-each-ref passed fixed numbers of path\ncomponents to strip from refs, assuming that remote names had no slashes\nin them. This made completions like:\n\ngit push github/dseomn :com<Tab>\n\nResult in:\n\ngit push github/dseomn :dseomn/completion-remote-slash\n\nWith this patch, it instead results in:\n\ngit push github/dseomn :completion-remote-slash\n\nSigned-off-by: David Mandelberg <david@mandelberg.org>\n---\n contrib/completion/git-completion.bash |  38 +++++-\n t/t9902-completion.sh                  | 180 ++++++++++++++++++++++---\n 2 files changed, 189 insertions(+), 29 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 5fdc71208e..e3d88b0672 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -790,16 +790,39 @@ __git_tags ()\n __git_dwim_remote_heads ()\n {\n \tlocal pfx=\"${1-}\" cur_=\"${2-}\" sfx=\"${3-}\"\n-\tlocal fer_pfx=\"${pfx//\\%/%%}\" # \"escape\" for-each-ref format specifiers\n \n \t# employ the heuristic used by git checkout and git switch\n \t# Try to find a remote branch that cur_es the completion word\n \t# but only output if the branch name is unique\n-\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=3)$sfx\" \\\n-\t\t--sort=\"refname:strip=3\" \\\n-\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n-\t\t\"refs/remotes/*/$cur_*\" \"refs/remotes/*/$cur_*/**\" | \\\n-\tuniq -u\n+\tlocal awk_script='\n+\tfunction casemap(s) {\n+\t\tif (ENVIRON[\"IGNORE_CASE\"])\n+\t\t\treturn tolower(s)\n+\t\telse\n+\t\t\treturn s\n+\t}\n+\tBEGIN {\n+\t\tsplit(ENVIRON[\"REMOTES\"], remotes, /\\n/)\n+\t\tfor (i in remotes)\n+\t\t\tremotes[i] = \"refs/remotes/\" casemap(remotes[i])\n+\t\tcur_ = casemap(ENVIRON[\"CUR_\"])\n+\t}\n+\t{\n+\t\tref_case = casemap($0)\n+\t\tfor (i in remotes) {\n+\t\t\tif (index(ref_case, remotes[i] \"/\" cur_) == 1) {\n+\t\t\t\tbranch = substr($0, length(remotes[i] \"/\") + 1)\n+\t\t\t\tprint ENVIRON[\"PFX\"] branch ENVIRON[\"SFX\"]\n+\t\t\t\tbreak\n+\t\t\t}\n+\t\t}\n+\t}\n+\t'\n+\t__git for-each-ref --format='%(refname)' refs/remotes/ |\n+\t\tPFX=\"$pfx\" SFX=\"$sfx\" CUR_=\"$cur_\" \\\n+\t\t\tIGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \\\n+\t\t\tREMOTES=\"$(__git_remotes | sort -r)\" awk \"$awk_script\" |\n+\t\tsort | uniq -u\n }\n \n # Lists refs from the local (by default) or from a remote repository.\n@@ -905,7 +928,8 @@ __git_refs ()\n \t\t\tcase \"HEAD\" in\n \t\t\t$match*|$umatch*)\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\tlocal strip=\"$(__git_count_path_components \"refs/remotes/$remote\")\"\n+\t\t\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=$strip)$sfx\" \\\n \t\t\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n \t\t\t\t\"refs/remotes/$remote/$match*\" \\\n \t\t\t\t\"refs/remotes/$remote/$match*/**\"\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 015289c776..343b8cd191 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -149,7 +149,8 @@ fi\n test_expect_success 'setup for __git_find_repo_path/__gitdir tests' '\n \tmkdir -p subdir/subsubdir &&\n \tmkdir -p non-repo &&\n-\tgit init -b main otherrepo\n+\tgit init -b main otherrepo &&\n+\tgit init -b main slashrepo\n '\n \n test_expect_success '__git_find_repo_path - from command line (through $__git_dir)' '\n@@ -674,6 +675,13 @@ test_expect_success 'setup for ref completion' '\n \t) &&\n \tgit remote add other \"$ROOT/otherrepo/.git\" &&\n \tgit fetch --no-tags other &&\n+\t(\n+\t\tcd slashrepo &&\n+\t\tgit commit --allow-empty -m initial &&\n+\t\tgit branch -m main branch/with/slash\n+\t) &&\n+\tgit remote add remote/with/slash \"$ROOT/slashrepo/.git\" &&\n+\tgit fetch --no-tags remote/with/slash &&\n \trm -f .git/FETCH_HEAD &&\n \tgit init thirdrepo\n '\n@@ -686,6 +694,8 @@ test_expect_success '__git_refs - simple' '\n \tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n \tEOF\n \t(\n@@ -702,6 +712,8 @@ test_expect_success '__git_refs - full refs' '\n \trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n+\trefs/remotes/remote/with/slash/HEAD\n+\trefs/remotes/remote/with/slash/branch/with/slash\n \trefs/tags/matching-tag\n \tEOF\n \t(\n@@ -767,6 +779,19 @@ test_expect_success '__git_refs - configured remote' '\n \ttest_cmp expected \"$actual\"\n '\n \n+test_expect_success '__git_refs - configured remote - with slash' '\n+\tcat >expected <<-EOF &&\n+\tHEAD\n+\tHEAD\n+\tbranch/with/slash\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_refs remote/with/slash >\"$actual\"\n+\t) &&\n+\ttest_cmp expected \"$actual\"\n+'\n+\n test_expect_success '__git_refs - configured remote - full refs' '\n \tcat >expected <<-EOF &&\n \tHEAD\n@@ -909,17 +934,19 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \tother/ambiguous\n \tother/branch-in-other\n \tother/main-in-other\n-\tremote/ambiguous\n-\tremote/branch-in-remote\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/ambiguous\n+\tremote/with/slash/branch-in-remote\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n-\tHEAD\n \tbranch-in-other\n \tbranch-in-remote\n+\tbranch/with/slash\n \tmain-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+\t\trefs/remotes/remote/with/slash/ambiguous \\\n+\t\trefs/remotes/remote/with/slash/branch-in-remote\n \tdo\n \t\tgit update-ref $remote_ref main &&\n \t\ttest_when_finished \"git update-ref -d $remote_ref\" || return 1\n@@ -939,6 +966,8 @@ test_expect_success '__git_refs - after --opt=' '\n \tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n \tEOF\n \t(\n@@ -955,6 +984,8 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n+\trefs/remotes/remote/with/slash/HEAD\n+\trefs/remotes/remote/with/slash/branch/with/slash\n \trefs/tags/matching-tag\n \tEOF\n \t(\n@@ -972,6 +1003,8 @@ test_expect_success '__git refs - excluding refs' '\n \t^other/HEAD\n \t^other/branch-in-other\n \t^other/main-in-other\n+\t^remote/with/slash/HEAD\n+\t^remote/with/slash/branch/with/slash\n \t^matching-tag\n \tEOF\n \t(\n@@ -988,6 +1021,8 @@ test_expect_success '__git refs - excluding full refs' '\n \t^refs/remotes/other/HEAD\n \t^refs/remotes/other/branch-in-other\n \t^refs/remotes/other/main-in-other\n+\t^refs/remotes/remote/with/slash/HEAD\n+\t^refs/remotes/remote/with/slash/branch/with/slash\n \t^refs/tags/matching-tag\n \tEOF\n \t(\n@@ -1015,6 +1050,8 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n \tother/branch-in-other\n \tother/main-in-other\n \tother/matching/branch-in-other\n+\tremote/with/slash/HEAD\n+\tremote/with/slash/branch/with/slash\n \tmatching-tag\n \tmatching/tag\n \tEOF\n@@ -1135,6 +1172,8 @@ test_expect_success '__git_complete_refs - simple' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tmatching-tag Z\n \tEOF\n \t(\n@@ -1173,6 +1212,20 @@ test_expect_success '__git_complete_refs - remote' '\n \ttest_cmp expected out\n '\n \n+test_expect_success '__git_complete_refs - remote - with slash' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tHEAD Z\n+\tHEAD Z\n+\tbranch/with/slash Z\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_complete_refs --remote=remote/with/slash &&\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@@ -1181,9 +1234,11 @@ test_expect_success '__git_complete_refs - track' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tmatching-tag Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain-in-other Z\n \tEOF\n \t(\n@@ -1228,6 +1283,8 @@ test_expect_success '__git_complete_refs - suffix' '\n \tother/HEAD.\n \tother/branch-in-other.\n \tother/main-in-other.\n+\tremote/with/slash/HEAD.\n+\tremote/with/slash/branch/with/slash.\n \tmatching-tag.\n \tEOF\n \t(\n@@ -1253,6 +1310,20 @@ test_expect_success '__git_complete_fetch_refspecs - simple' '\n \ttest_cmp expected out\n '\n \n+test_expect_success '__git_complete_fetch_refspecs - with slash' '\n+\tsed -e \"s/Z$//\" >expected <<-EOF &&\n+\tHEAD:HEAD Z\n+\tHEAD:HEAD Z\n+\tbranch/with/slash:branch/with/slash Z\n+\tEOF\n+\t(\n+\t\tcur= &&\n+\t\t__git_complete_fetch_refspecs remote/with/slash &&\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@@ -1333,8 +1404,8 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n \n test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n \ttest_completion \"git switch \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1480,8 +1551,8 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1489,6 +1560,8 @@ test_expect_success 'git checkout - completes refs and unique remote branches fo\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1508,8 +1581,8 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n \n test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1518,8 +1591,8 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n \n test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1542,14 +1615,16 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1557,6 +1632,8 @@ test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1,\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1569,14 +1646,16 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1584,6 +1663,8 @@ test_expect_success 'git checkout - a later --guess overrides previous --no-gues\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1596,6 +1677,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1609,6 +1692,8 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1616,8 +1701,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \ttest_config checkout.guess true &&\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1625,6 +1710,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1632,8 +1719,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \ttest_config checkout.guess false &&\n \ttest_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -1641,6 +1728,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1654,6 +1743,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1666,6 +1757,8 @@ test_expect_success 'git switch - with --detach, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1678,6 +1771,8 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1850,6 +1945,8 @@ test_expect_success 'git switch - with -d, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1862,6 +1959,8 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1870,11 +1969,15 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n \ttest_completion \"git switch -t \" <<-\\EOF\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1883,11 +1986,15 @@ test_expect_success 'git checkout - with --track, complete only remote branches'\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n \ttest_completion \"git checkout -t \" <<-\\EOF\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1907,6 +2014,8 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1919,6 +2028,8 @@ test_expect_success 'git switch - with -c, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1931,6 +2042,8 @@ test_expect_success 'git switch - with -C, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1943,6 +2056,8 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1955,6 +2070,8 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1967,6 +2084,8 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1979,6 +2098,8 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -1991,6 +2112,8 @@ test_expect_success 'git checkout - with -b, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2003,6 +2126,8 @@ test_expect_success 'git checkout - with -B, complete all references' '\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2015,6 +2140,8 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2027,6 +2154,8 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2039,6 +2168,8 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2051,13 +2182,15 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -c \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2066,8 +2199,8 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n \n test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -C \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2104,8 +2237,8 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n \n test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -b \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2114,8 +2247,8 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n \n test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -B \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2152,8 +2285,8 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n \n test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git switch --orphan \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2168,8 +2301,8 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n \n test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git checkout --orphan \" <<-\\EOF\n-\tHEAD Z\n \tbranch-in-other Z\n+\tbranch/with/slash Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n@@ -2185,6 +2318,8 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n \tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n+\tremote/with/slash/HEAD Z\n+\tremote/with/slash/branch/with/slash Z\n \tEOF\n '\n \n@@ -2199,7 +2334,8 @@ test_expect_success 'git restore completes modified files' '\n test_expect_success 'teardown after ref completion' '\n \tgit branch -d matching-branch &&\n \tgit tag -d matching-tag &&\n-\tgit remote remove other\n+\tgit remote remove other &&\n+\tgit remote remove remote/with/slash\n '\n \n \n-- \n2.47.2\n"},{"id":"515868","messageId":"5012fd2b-a68b-4267-aa4b-9dd04510986b@gmail.com","threadId":"63039","inReplyTo":"17274df2746d304db876ebd82ad8d932@mandelberg.org","subject":"Re: [PATCH v4 0/2] completion: fix bugs with slashes in remote names","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-04-08T14:18:38Z","receivedAt":"2025-04-08T14:18:49Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi David\n\nOn 23/03/2025 21:04, David Mandelberg wrote:\n> Another minor change, as suggested by\n> https://lore.kernel.org/git/xmqqo6y3dzno.fsf@gitster.g/\n> \n> I'm not sure what the expectations are around updating patches that are\n> scheduled to be merged to next but haven't been merged yet. If it's too\n> late, I'm happy with the previous v3, or I could send a separate patch.\n\nUnless a patch series has already been merged to next which in not the \ncase here sending a new round of patches like this is the right thing to \ndo. The range-diff (which you can add to your cover letter by passing \n\"--range-diff\" to \"git format-patch\") below looks good to me\n\nSorry for the slow response and thanks for working on this\n\nPhillip\n\n1:  5f4f1eb105 = 1:  58d036a048 completion: add helper to count path \ncomponents\n2:  28c0f80345 ! 2:  0383e1c608 completion: fix bugs with slashes in \nremote names\n     @@ contrib/completion/git-completion.bash: __git_tags ()\n      +          }\n      +  }\n      +  '\n     -+  __git for-each-ref --format='%(refname)' 'refs/remotes/**' |\n     ++  __git for-each-ref --format='%(refname)' refs/remotes/ |\n      +          PFX=\"$pfx\" SFX=\"$sfx\" CUR_=\"$cur_\" \\\n      +                  IGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \\\n      +                  REMOTES=\"$(__git_remotes | sort -r)\" awk \n\"$awk_script\" |\n\n\n> David Mandelberg (2):\n>    completion: add helper to count path components\n>    completion: fix bugs with slashes in remote names\n> \n>   contrib/completion/git-completion.bash |  49 +++++-\n>   t/t9902-completion.sh                  | 206 ++++++++++++++++++++++---\n>   2 files changed, 226 insertions(+), 29 deletions(-)\n> \n\n"},{"id":"515894","messageId":"aa5d0cf9-885c-4aa2-93bf-d8cbf715ccab@mandelberg.org","threadId":"63039","inReplyTo":"5012fd2b-a68b-4267-aa4b-9dd04510986b@gmail.com","subject":"Re: [PATCH v4 0/2] completion: fix bugs with slashes in remote names","fromName":"David Mandelberg","fromEmail":"david@mandelberg.org","sentAt":"2025-04-08T18:55:55Z","receivedAt":"2025-04-08T18:55:57Z","isPatch":true,"sender":{"key":"david@mandelberg.org","avatar":"https://avatars.githubusercontent.com/u/15314936?v=4"},"body":"Op 2025-04-08 om 10:18 schreef Phillip Wood:\n> Unless a patch series has already been merged to next which in not the \n> case here sending a new round of patches like this is the right thing to \n> do.\n\nMakes sense, thanks!\n\n> The range-diff (which you can add to your cover letter by passing \n> \"--range-diff\" to \"git format-patch\") below looks good to me\n\nOh, nice. I only learned about range-diff recently and it's my new \nfavorite git command. I'll use `git format-patch --range-diff` in the \nfuture.\n"}]}