2

Whenever I tab-complete a file path after a git command in bash, e.g. git add, I have to press <TAB> multiple times to complete to the actual filename, depending on how many subfolders there are up to the file.

Example: I want to add the file my/example/file, then I enter:

git add <TAB>
git add my/<TAB>
git add my/example/<TAB>
git add my/example/file

This is the case even when my/example/file is the only changed file and hence the only current tab-completion option. In this case bash could as well complete the full path with the first <TAB> already, but it doesn't.

fish actually does this by default:

git add <TAB>
git add my/example/file

Is there a way to configure this behavior in bash as well?

I am on Ubuntu 20.04 using its default bash completions.

3
  • This is not inherent "bash" behaviour, but the behaviour of a specific completion script. There's different ones for git available for bash, and we don't know which one you're using. I'm not a bash expert, sadly, so I don't know how to figure out from which file the completion script is loaded, but I'm guessing you haven't installed any fancy bash enhancement toolkits, so this is probably your Linux distro's default scripts. Could you tell us which distro (and what version of said distro) you're on? Aug 8, 2022 at 15:49
  • (I've honestly also don't know how to do that for my default shell, zsh, either, but if you liked fish's autosuggestions style, maybe try zsh + zsh-autosuggestions, which can be installed simply by running mkdir -p ~/.zsh && git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions && echo 'source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh >> ~/.zshrc', or probably simply through your distro's package manager) Aug 8, 2022 at 16:05
  • 1
    @MarcusMüller Thanks for your comment. I am indeed using my distro's default bash completion scripts, which is Ubuntu 20.04. I have updated my question accordingly. Aug 8, 2022 at 16:25

1 Answer 1

1

One way to accomplish this would be to copy the git completion script, /usr/share/bash-completion/completions/git, to ~/.local/share/bash-completion/completions/ and modify it to complete with full index paths:

mkdir -p ~/.local/share/bash-completion/completions
cd ~/.local/share/bash-completion/completions

cp /usr/share/bash-completion/completions/git .

# See diff below
patch -lp0 </tmp/index_paths.diff
echo GIT_COMPLETION_FULL_INDEX_PATHS=1 >>~/.bashrc

exec bash

As far as I can tell the changes required are:

  1. Modify __git_index_files() so that it outputs full index paths rather than just the first path component.

  2. In __git_complete_index_file(), stop using __gitcomp_file_direct to set COMPREPLY because it uses compopt -o filenames, which only outputs basenames. This option was also taking care of shell quoting, so do that manually now.

Note that __git_complete_index_file is used for the completion of several other git commands other than add such as clean, commit, and rm, so the full path completion will apply to those too. Here's the diff of my attempt at these changes, which adds the functionality behind the shell variable GIT_COMPLETION_FULL_INDEX_PATHS:

--- git
+++ git
@@ -39,6 +39,11 @@
 #     When set to "1", do not include "DWIM" suggestions in git-checkout
 #     and git-switch completion (e.g., completing "foo" when "origin/foo"
 #     exists).
+#
+#   GIT_COMPLETION_FULL_INDEX_PATHS
+#
+#     Normally index path completions return only the next path component. When
+#     set to "1", the whole path will be completed.
 
 case "$COMP_WORDBREAKS" in
 *:*) : great ;;
@@ -435,6 +440,19 @@
    __gitcomp_nl_append "$@"
 }
 
+# Shell quotes each word and fills the COMPREPLY array.
+# 1: List of newline-separated completion words.
+__gitcomp_quote_direct ()
+{
+   local IFS=$'\n'
+   local quoted="$1"
+   [[ -n $1 ]] && quoted=$(printf '%q\n' $1)
+
+   COMPREPLY=($quoted)
+
+   compopt +o nospace 2>/dev/null || true
+}
+
 # Fills the COMPREPLY array with prefiltered paths without any additional
 # processing.
 # Callers must take care of providing only paths that match the current path
@@ -503,10 +521,12 @@
 __git_index_files ()
 {
    local root="$2" match="$3"
+   local field=1
+   [ "$GIT_COMPLETION_FULL_INDEX_PATHS" = "1" ] && field=0
 
    __git_ls_files_helper "$root" "$1" "$match" |
    awk -F / -v pfx="${2//\\/\\\\}" '{
-       paths[$1] = 1
+       paths[$f] = 1
    }
    END {
        for (p in paths) {
@@ -518,19 +538,13 @@
 
            # The path is quoted.
            p = dequote(p)
-           if (p == "")
-               continue
 
-           # Even when a directory name itself does not contain
-           # any special characters, it will still be quoted if
-           # any of its (stripped) trailing path components do.
-           # Because of this we may have seen the same directory
-           # both quoted and unquoted.
-           if (p in paths)
-               # We have seen the same directory unquoted,
-               # skip it.
-               continue
-           else
+           # When not using full index paths, p in paths is checked
+           # because the dequoted directory name may already be in
+           # paths. This is the case when the directory name itself
+           # does not contain special characters, but a (stripped)
+           # trailing path component does.
+           if (p != "" && (f == 0 || !(p in paths)))
                print pfx p
        }
    }
@@ -573,7 +587,7 @@
            out = out p
 
        return out
-   }'
+   }' "f=$field"
 }
 
 # __git_complete_index_file requires 1 argument:
@@ -595,7 +609,11 @@
        cur_="$dequoted_word"
    esac
 
-   __gitcomp_file_direct "$(__git_index_files "$1" "$pfx" "$cur_")"
+   if [ "$GIT_COMPLETION_FULL_INDEX_PATHS" = "1" ]; then
+       __gitcomp_quote_direct "$(__git_index_files "$1" "$pfx" "$cur_")"
+   else
+       __gitcomp_file_direct "$(__git_index_files "$1" "$pfx" "$cur_")"
+   fi
 }
 
 # Lists branches from the local repository.
1
  • Brilliant, that works like a charm! Thanks a lot for taking your time to post this great solution. Sep 11, 2022 at 18:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .