Skip to content

Commit a1fbe26

Browse files
newrengitster
authored andcommitted
completion: avoid user confusion in non-cone mode
It is tempting to think of "files and directories" of the current directory as valid inputs to the add and set subcommands of git sparse-checkout. However, in non-cone mode, they often aren't and using them as potential completions leads to *many* forms of confusion: Issue #1. It provides the *wrong* files and directories. For git sparse-checkout add we always want to add files and directories not currently in our sparse checkout, which means we want file and directories not currently present in the current working tree. Providing the files and directories currently present is thus always wrong. For git sparse-checkout set we have a similar problem except in the subset of cases where we are trying to narrow our checkout to a strict subset of what we already have. That is not a very common scenario, especially since it often does not even happen to be true for the first use of the command; for years we required users to create a sparse-checkout via git sparse-checkout init git sparse-checkout set <args...> (or use a clone option that did the init step for you at clone time). The init command creates a minimal sparse-checkout with just the top-level directory present, meaning the set command has to be used to expand the checkout. Thus, only in a special and perhaps unusual cases would any of the suggestions from normal file and directory completion be appropriate. Issue #2: Suggesting patterns that lead to warnings is unfriendly. If the user specifies any regular file and omits the leading '/', then the sparse-checkout command will warn the user that their command is problematic and suggest they use a leading slash instead. Issue #3: Completion gets confused by leading '/', and provides wrong paths. Users often want to anchor their patterns to the toplevel of the repository, especially when listing individual files. There are a number of reasons for this, but notably even sparse-checkout encourages them to do so (as noted above). However, if users do so (via adding a leading '/' to their pattern), then bash completion will interpret the leading slash not as a request for a path at the toplevel of the repository, but as a request for a path at the root of the filesytem. That means at best that completion cannot help with such paths, and if it does find any completions, they are almost guaranteed to be wrong. Issue #4: Suggesting invalid patterns from subdirectories is unfriendly. There is no per-directory equivalent to .gitignore with sparse-checkouts. There is only a single worktree-global $GIT_DIR/info/sparse-checkout file. As such, paths to files must be specified relative to the toplevel of a repository. Providing suggestions of paths that are relative to the current working directory, as bash completion defaults to, is wrong when the current working directory is not the worktree toplevel directory. Issue #5: Paths with special characters will be interpreted incorrectly The entries in the sparse-checkout file are patterns, not paths. While most paths also qualify as patterns (though even in such cases it would be better for users to not use them directly but prefix them with a leading '/'), there are a variety of special characters that would need special escaping beyond the normal shell escaping: '*', '?', '\', '[', ']', and any leading '#' or '!'. If completion suggests any such paths, users will likely expect them to be treated as an exact path rather than as a pattern that might match some number of files other than 1. However, despite the first four issues, we can note that _if_ users are using tab completion, then they are probably trying to specify a path in the index. As such, we transform their argument into a top-level-rooted pattern that matches such a file. For example, if they type: git sparse-checkout add Make<TAB> we could "complete" to git sparse-checkout add /Makefile or, if they ran from the Documentation/technical/ subdirectory: git sparse-checkout add m<TAB> we could "complete" it to: git sparse-checkout add /Documentation/technical/multi-pack-index.txt Note in both cases I use "complete" in quotes, because we actually add characters both before and after the argument in question, so we are kind of abusing "bash completions" to be "bash completions AND beginnings". The fifth issue is a bit stickier, especially when you consider that we not only need to deal with escaping issues because of special meanings of patterns in sparse-checkout & gitignore files, but also that we need to consider escaping issues due to ls-files needing to sometimes quote or escape characters, and because the shell needs to escape some characters. The multiple interacting forms of escaping could get ugly; this patch makes no attempt to do so and simply documents that we decided to not deal with those corner cases for now but at least get the common cases right. Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 7c3595b commit a1fbe26

File tree

2 files changed

+96
-2
lines changed

2 files changed

+96
-2
lines changed

contrib/completion/git-completion.bash

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3113,6 +3113,93 @@ __gitcomp_directories ()
31133113
fi
31143114
}
31153115

3116+
# In non-cone mode, the arguments to {set,add} are supposed to be
3117+
# patterns, relative to the toplevel directory. These can be any kind
3118+
# of general pattern, like 'subdir/*.c' and we can't complete on all
3119+
# of those. However, if the user presses Tab to get tab completion, we
3120+
# presume that they are trying to provide a pattern that names a specific
3121+
# path.
3122+
__gitcomp_slash_leading_paths ()
3123+
{
3124+
local dequoted_word pfx="" cur_ toplevel
3125+
3126+
# Since we are dealing with a sparse-checkout, subdirectories may not
3127+
# exist in the local working copy. Therefore, we want to run all
3128+
# ls-files commands relative to the repository toplevel.
3129+
toplevel="$(git rev-parse --show-toplevel)/"
3130+
3131+
__git_dequote "$cur"
3132+
3133+
# If the paths provided by the user already start with '/', then
3134+
# they are considered relative to the toplevel of the repository
3135+
# already. If they do not start with /, then we need to adjust
3136+
# them to start with the appropriate prefix.
3137+
case "$cur" in
3138+
/*)
3139+
cur="${cur:1}"
3140+
;;
3141+
*)
3142+
pfx="$(__git rev-parse --show-prefix)"
3143+
esac
3144+
3145+
# Since sparse-index is limited to cone-mode, in non-cone-mode the
3146+
# list of valid paths is precisely the cached files in the index.
3147+
#
3148+
# NEEDSWORK:
3149+
# 1) We probably need to take care of cases where ls-files
3150+
# responds with special quoting.
3151+
# 2) We probably need to take care of cases where ${cur} has
3152+
# some kind of special quoting.
3153+
# 3) On top of any quoting from 1 & 2, we have to provide an extra
3154+
# level of quoting for any paths that contain a '*', '?', '\',
3155+
# '[', ']', or leading '#' or '!' since those will be
3156+
# interpreted by sparse-checkout as something other than a
3157+
# literal path character.
3158+
# Since there are two types of quoting here, this might get really
3159+
# complex. For now, just punt on all of this...
3160+
completions="$(__git -C "${toplevel}" -c core.quotePath=false \
3161+
ls-files --cached -- "${pfx}${cur}*" \
3162+
| sed -e s%^%/% -e 's%$% %')"
3163+
# Note, above, though that we needed all of the completions to be
3164+
# prefixed with a '/', and we want to add a space so that bash
3165+
# completion will actually complete an entry and let us move on to
3166+
# the next one.
3167+
3168+
# Return what we've found.
3169+
if test -n "$completions"; then
3170+
# We found some completions; return them
3171+
local IFS=$'\n'
3172+
COMPREPLY=($completions)
3173+
else
3174+
# Do NOT fall back to bash-style all-local-files-and-dirs
3175+
# when we find no match. Such options are worse than
3176+
# useless:
3177+
# 1. "git sparse-checkout add" needs paths that are NOT
3178+
# currently in the working copy. "git
3179+
# sparse-checkout set" does as well, except in the
3180+
# special cases when users are only trying to narrow
3181+
# their sparse checkout to a subset of what they
3182+
# already have.
3183+
#
3184+
# 2. A path like '.config' is ambiguous as to whether
3185+
# the user wants all '.config' files throughout the
3186+
# tree, or just the one under the current directory.
3187+
# It would result in a warning from the
3188+
# sparse-checkout command due to this. As such, all
3189+
# completions of paths should be prefixed with a
3190+
# '/'.
3191+
#
3192+
# 3. We don't want paths prefixed with a '/' to
3193+
# complete files in the system root directory, we
3194+
# want it to complete on files relative to the
3195+
# repository root.
3196+
#
3197+
# As such, make sure that NO completions are offered rather
3198+
# than falling back to bash's default completions.
3199+
COMPREPLY=( "" )
3200+
fi
3201+
}
3202+
31163203
_git_sparse_checkout ()
31173204
{
31183205
local subcommands="list init set disable add reapply"
@@ -3138,6 +3225,8 @@ _git_sparse_checkout ()
31383225
fi
31393226
if [[ "$using_cone" == "true" ]]; then
31403227
__gitcomp_directories
3228+
else
3229+
__gitcomp_slash_leading_paths
31413230
fi
31423231
esac
31433232
}

t/t9902-completion.sh

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,7 +1571,7 @@ test_expect_success FUNNYNAMES,!CYGWIN 'cone mode sparse-checkout completes dire
15711571
)
15721572
'
15731573

1574-
test_expect_success 'non-cone mode sparse-checkout uses bash completion' '
1574+
test_expect_success 'non-cone mode sparse-checkout gives rooted paths' '
15751575
# reset sparse-checkout repo to non-cone mode
15761576
git -C sparse-checkout sparse-checkout disable &&
15771577
git -C sparse-checkout sparse-checkout set --no-cone &&
@@ -1581,7 +1581,12 @@ test_expect_success 'non-cone mode sparse-checkout uses bash completion' '
15811581
# expected to be empty since we have not configured
15821582
# custom completion for non-cone mode
15831583
test_completion "git sparse-checkout set f" <<-\EOF
1584-
1584+
/folder1/0/1/t.txt Z
1585+
/folder1/expected Z
1586+
/folder1/out Z
1587+
/folder1/out_sorted Z
1588+
/folder2/0/t.txt Z
1589+
/folder3/t.txt Z
15851590
EOF
15861591
)
15871592
'

0 commit comments

Comments
 (0)