Override bash completion for git clone - bash

builtin completion
The default completion for git clone (reproduced below) gives tab completion for --* options:
_git_clone ()
{
case "$cur" in
--*)
__gitcomp_builtin clone
return
;;
esac
}
bash-completion 1.x (old bash)
(for a concrete instance, macos high sierra + brew installed bash-completion / git)
In the bash-completion 1.x world, to override this I would (in .bashrc / .bash_profile) define my own _git_clone completion function:
# https://github.com/scop/bash-completion/blob/d2f14a7/bash_completion#L498
__ltrim_colon_completions() {
if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
# Remove colon-word prefix from COMPREPLY items
local colon_word=${1%"${1##*:}"}
local i=${#COMPREPLY[*]}
while [[ $((--i)) -ge 0 ]]; do
COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
done
fi
}
_git_clone() {
case "$cur" in
--*)
__gitcomp_builtin clone
return
;;
*)
argc=0
for word in "${words[#]}"; do
case "$word" in
git|clone|--*)
continue
;;
*)
argc=$((argc + 1))
;;
esac
done
if [ $argc -le 1 ]; then
__gitcomp "https://github.com/git/git https://github.com/python/cpython"
__ltrim_colon_completions "$cur"
fi
;;
esac
}
This works great:
(The sequence I typed here was git clone h<tab><tab>g<tab>)
$ git clone https://github.com/
//github.com/git/git //github.com/python/cpython
$ git clone https://github.com/git/git
bash-completion 2.x
(for a concrete instance: stock ubuntu bionic (18.04))
In bash-completion 2.x, the model is flipped to a dynamically loaded configuration. This means that when git is tab completed, __load_completion fires, finds the git completion at the path it is installed and sources it.
Defining my own _git_clone completion function in a .bashrc / .bash_profile is now useless as it gets clobber by the dynamically sourced completion file.
I can define my own git completion in this directory:
local -a dirs=( ${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions )
(for example ~/.local/share/bash-completion/completions/git.bash). However this turns off all other git completion!
How do I make my custom clone tab completion work under this model (and have the default completion continue to work)?
Unacceptable solution(s):
Modify system packaged files: /usr/share/bash-completion/completions/git. This file is managed by apt.

The official FAQ of bash-completion contains very interesting information.
First, if you are 100% sure your $BASH_COMPLETION_USER_DIR and $XDG_DATA_HOME environment variable are empty, what you specified in your original question is a good place to add your own bash-completion scripts:
~/.local/share/bash-completion/completions/git
To be noted .bash extension not necessary.
The fact is that bash-completion scripts are loaded thanks to the /etc/profile.d/bash_completion.sh file.
If you perform something in your .bashrc file, you would somehow break something in the loading chain.
Nevertheless, if you override existing completion function, you still need to ensure the loading order is correct.
So loading first bash-completion script is mandatory to ensure everything ends successfully.
You can easily perform it, adding this initial instruction at the beginning of your ~/.local/share/bash-completion/completions/git file:
# Ensures git bash-completion is loaded before overriding any function (be careful to avoid endless loop).
! complete -p git &> /dev/null && [ ${ENDLESS_LOOP_SAFEGUARD:-0} -eq 0 ] && ENDLESS_LOOP_SAFEGUARD=1 BASH_COMPLETION_USER_DIR=/dev/null _completion_loader git
First it checks if git bash-completion has already been loaded, and then if this is not the case, all the bash-completion git definition are loaded.
Edit: the ENDLESS_LOOP_SAFEGUARD trick allows to avoid endless loop when this is the first time bash completion is loading git part.
If needed, you can get the usage:
complete --help
complete: complete [-abcdefgjksuv] [-pr] [-DE] [-o
option] [-A action] [-G globpat] [-W wordlist] [-F function] [-C
command] [-X filterpat] [-P prefix] [-S suffix] [name ...]
Specify how arguments are to be completed by Readline.
For each NAME, specify how arguments are to be completed. If no options
are supplied, existing completion specifications are printed in a way that
allows them to be reused as input.
Options:
-p print existing completion specifications in a reusable format
-r remove a completion specification for each NAME, or, if no
NAMEs are supplied, all completion specifications
-D apply the completions and actions as the default for commands
without any specific completion defined
-E apply the completions and actions to "empty" commands --
completion attempted on a blank line
When completion is attempted, the actions are applied in the order the
uppercase-letter options are listed above. The -D option takes
precedence over -E.
Exit Status:
Returns success unless an invalid option is supplied or an error occurs.
Then, and only then, you can define whatever you want, including your old way to override git clone bash completion:
# Ensures git bash-completion is loaded before overriding any function (be careful to avoid endless loop).
! complete -p git &> /dev/null && [ ${ENDLESS_LOOP_SAFEGUARD:-0} -eq 0 ] && ENDLESS_LOOP_SAFEGUARD=1 BASH_COMPLETION_USER_DIR=/dev/null _completion_loader git
# https://github.com/scop/bash-completion/blob/d2f14a7/bash_completion#L498
__ltrim_colon_completions() {
if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
# Remove colon-word prefix from COMPREPLY items
local colon_word=${1%"${1##*:}"}
local i=${#COMPREPLY[*]}
while [[ $((--i)) -ge 0 ]]; do
COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
done
fi
}
_git_clone() {
case "$cur" in
--*)
__gitcomp_builtin clone
return
;;
*)
argc=0
for word in "${words[#]}"; do
case "$word" in
git|clone|--*)
continue
;;
*)
argc=$((argc + 1))
;;
esac
done
if [ $argc -le 1 ]; then
__gitcomp "https://github.com/git/git https://github.com/python/cpython"
__ltrim_colon_completions "$cur"
fi
;;
esac
}
Each time you perform change and want to check result, you just need to request bash-completion reload for git:
_completion_loader git
Such a way, you will never lost your change, because you let the package files untouched; and still can enhanced any existing bash-completion with your own functions.
Edit:
About your fear with _completion_loader function => , but after having checked the source code, this function exists since commit cad3abfc7, of 2015-07-15 20:53:05 so I guess it should be kept backward-compatible, but true without guarantee. I'll edit my anwer to propose some alternatives
As alternative, this should be another way to get your own git completion definition (to put at beginning of your own script):
# Ensures git bash-completion is loaded before overriding any function
# Be careful to avoid endless loop with dedicated $ENDLESS_LOOP_SAFEGUARD environment variable.
if ! complete -p git &> /dev/null && [ ${ENDLESS_LOOP_SAFEGUARD:-0} -eq 0 ]; then
# Trick: define $BASH_COMPLETION_USER_DIR environment variable here to ensure bash-completion rule are loaded once.
export BASH_COMPLETION_USER_DIR=/usr/share
complete -D git
unset BASH_COMPLETION_USER_DIR
ENDLESS_LOOP_SAFEGUARD=1 complete -D git
fi

In your .bashrc / .bash_profile, you can force loading the default completions for git before redefining the completion for git clone:
if ! complete -p git &> /dev/null
then
# Instead of hardcoding the name of the dynamic completion loader
# function, you can obtain it by parsing the output of 'complete -p -D'
_completion_loader git
fi
_git_clone() {
COMPREPLY=("My own completion for 'git clone'")
}
EDIT
A lazily loadable version of the above approach (that doesn't eagerly load the default bash completions for git) follows:
if ! complete -p git &> /dev/null
then
_my_git_clone()
{
COMPREPLY=("My own completion for 'git clone'")
}
# A placeholder for git completion that will load the real
# completion script on first use
_my_git_comp_stub()
{
# Remove the old completion handler (which should be this very function)
complete -r git
# and load the git bash completion
_completion_loader git
# Rebind _git_clone to our own version
eval 'function _git_clone() { _my_git_clone "$#"; }'
# Tell the completion machinery to retry the completion attempt
# using the updated completion handler
return 124
}
# Install a lazy loading handler for git completion
complete -o bashdefault -o default -o nospace -F _my_git_comp_stub git
fi

Note: sometime, you cannot "configure", but have to propose a patch.
For instance, Git 2.33 (Q3 2021) fixe the completion of git clone --rec* (as in '--recurse-submodules' or '--recursive')
See commit ca2d62b (16 Jul 2021) by Philippe Blain (phil-blain).
(Merged by Junio C Hamano -- gitster -- in commit fa8b225, 28 Jul 2021)
parse-options: don't complete option aliases by default
Signed-off-by: Philippe Blain
Since 'OPT_ALIAS' was created in 5c38742 (parse-options: don't emit , 2019-04-29, Git v2.22.0-rc1 -- merge) (parse-options: don't emit "ambiguous option" for aliases, 2019-04-29), 'git clone'(man) --git-completion-helper, which is used by the Bash completion script to list options accepted by clone (via '__gitcomp_builtin'), lists both '--recurse-submodules' and its alias '--recursive', which was not the case before since '--recursive' had the PARSE_OPT_HIDDEN flag set, and options with this flag are skipped by 'parse-options.c::show_gitcomp', which implements git --git-completion-helper.
This means that typing 'git clone --recurs<TAB>' will yield both '--recurse-submodules' and '--recursive', which is not ideal since both do the same thing, and so the completion should directly complete the canonical option.
At the point where 'show_gitcomp' is called in 'parse_options_step', 'preprocess_options' was already called in 'parse_options', so any aliases are now copies of the original options with a modified help text indicating they are aliases.
Helpfully, since 64cc539 ("parse-options: don't leak alias help messages", 2021-03-21, Git v2.32.0-rc0 -- merge listed in batch #7) these copies have the PARSE_OPT_FROM_ALIAS flag set, so check that flag early in 'show_gitcomp' and do not print them, unless the user explicitly requested that all completion be shown (by setting 'GIT_COMPLETION_SHOW_ALL').
After all, if we want to encourage the use of '--recurse-submodules' over '--recursive', we'd better just suggest the former.
The only other options alias is 'log' and friends' '--mailmap', which is an alias for '--use-mailmap', but the Bash completion helpers for these commands do not use '__gitcomp_builtin', and thus are unaffected by this change.
Test the new behavior in t9902-completion.sh.
As a side effect, this also tests the correct behavior of GIT_COMPLETION_SHOW_ALL, which was not tested before.
Note that since '__gitcomp_builtin' caches the options it shows, we need to re-source the completion script to clear that cache for the second test.

Related

using bash_completion with cli tool

I have a custom cli tool that i want to setup with bash_completion but want [tab][tab] to perform an enter action on cmdline.
my bash_completion file for wonder is:
_wonder()
{
local cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(compgen -W "audit nodes tools create debug delete update" -- $cur) )
}
complete -F _wonder wonder
currently:
$ wonder [tab] [tab]
returns:
ip-10-99-18-249:loco_dsl jasonparmar$ wonder
audit create debug delete nodes tools update
What I want is when i use the tools option from wonder:
$ wonder tools [tab] [tab]
I want the [tab][tab] to force an enter on the cmdline
How can i edit my bash_completion file for wonder to achieve this.
Is this even possible with bash_completion.
Thanks in advance.
If you don't mind using external tools to simulate keyboard input (such as xdotool), try adding this as the first line of your completion function:
(( COMP_CWORD > 1 )) && xdotool key Return
As noted by #RandomUser, completion executing a command is unexpected behavior. See, for instance: echo oops; wonder audit <tab><tab> and imagine rm -rf * instead of echo. Consider simply stopping the completion to indicate wonder accepts only one argument:
(( COMP_CWORD > 1 )) && return

autocomplete boost program options

after hours of search I ended up writing my first stackoverflow question.
I want to create a completion function for a bash script, so far so good ;).
This bash script calls other executables that have their own autocompletion.
Example:
$ my_script foo par
# calls /usr/local/libexec/my_script/foo par
Autocompleting the first parameter of my_script (in this case "foo") works, because the possible options are the files in the folder "/usr/local/libexec/my_script/".
Each program in this folder does have a working auto completion, which was a byproduct of using boost::program_options.
I now want to implement the auto completion for the next parameters of my_script by referencing to the auto completion of the program gooing to be called.
$ my_script foo <tab>
# should output possible options to the foo subcommand
# like /usr/local/libexec/my_script/foo <tab>
I've started by this answer Bash completion from another completion, but _command or _command_offset 1 does not seem to work for me.
How can I get the options of foo, and how can I use this in my_script?
My current /etc/bash_completion.d/my_script looks like the following
_my_script()
{
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
if [[ "$COMP_CWORD" == 1 ]]; then
# 1. param: for program to be loaded
for i in $( ls /usr/local/libexec/my_script/ ); do
opts="${opts} ${i} "
done
COMPREPLY=( $(compgen -W "${opts}" ${cur}) )
else
# next param: of the program to be loaded
# how do I get the options of "foo" here?
fi
return 0
}
complete -F _my_script my_script
As soon as I read your question, the completion of sudo and git came to my mind. They both have the similar behavior you desired. So I looked for their completion functions. Here are your missing lines:
local root_command=${COMP_WORDS[0]}
_command_offset 1
It's copied from /usr/share/bash-completion/completions/sudo in Ubuntu 16.04. I totally don't knows its meaning. But it works.

Function definiton with read is blocking the shell

I'm trying to use the following function definition in Bash for setting session-wise git-environment variables. If I source this, my Bash blocks, I can only kil it with CTRL+D and then I got a unusable shell back (no commands possible). I guess, something with the read is blocking. The original version even had a "while true; do" in it, but that was even worse wreaking havoc the CPU.
So, two questions arise:
How can I reach the goal of having this function working properly?
Why does the problem pop up when only defining the function, but not calling it?
function git() {
echo "Running BETTER git..."
if [ -z "$GIT_COMMITTER_NAME" ]; then
echo -n "Git User: "
read -e UNAME
if [ "$UNAME" == "user1" ] -o [ "$UNAME" == "user2" ]; then
echo "Hello $UNAME, you will be set as committer for this git session"
else
echo "Invalid User"
fi
GIT_COMMITTER_NAME=$UNAME
export GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL=$UNAME#company.com
export GIT_COMMITTER_EMAIL
GIT_AUTHOR_NAME=$GIT_COMMITTER_NAME
export GIT_AUTHOR_NAME
GIT_AUTHOR_EMAIL=$GIT_COMMITTER_EMAIL
export GIT_AUTHOR_EMAIL
fi
echo " using git user: $GIT_AUTHOR_NAME / $GIT_AUTHOR_EMAIL"
/usr/bin/git "$#"
}
Just asking the 2nd question has brought myself to the problem's root: Why is just the definition of the function causing troubles? Because git actually is called in my environment:
Changing
export PS1=$Color_Off'$(git branch &>/dev/null;\
into
export PS1=$Color_Off'$(/usr/bin/git branch &>/dev/null;\
is solving the issue.
$PS1 is evaluated on every command in the bash, so on every prompt, a git command is issued, thus calling my defined function.
EDIT: I also had to replace all occurrences of "git " with "/usr/bin/git " in my /etc/bash_completion.d/git-prompt. Now I can alias "git" with that function and it works properly.

Parsing git log with readarray bash script

I'm parsing the output of "git log -1 --name-status" to grab the file extension for each file changed in the last commit. You can see a working example on http://goo.gl/Ms11a2
Once I got it working on tutorialspoint, I tried to run it in the git post-commit hook, but readarray line (#9) is throwing a syntax error.
.git/hooks/post-commit: 9: .git/hooks/post-commit: Syntax error: redirection unexpected
Can you guys point me in the right direction? What am I doing wrong and why the heck is it working on http://goo.gl/Ms11a2 and not the post-commit? Excuse my ignorance... I don't have much experience with shell scripting.
#!/bin/sh
### git-stats hook (begin) ###
# Copy last commit hash to clipboard on commit
commit_hash=$(git rev-parse HEAD)
repo_url=$(git config --get remote.origin.url)
commit_date=$(git log -1 --format=%cd)
commit_changes=$(git log -1 --name-status)
readarray -t log_lines <<< "$commit_changes"
fileRegex='(.*)\.'
file_changed_start=6;
file_changed_line="${log_lines[file_changed_start]}"
declare -A languages;
# Step through each line that passes the regex
while [[ "${file_changed_line}" =~ ${fileRegex} ]]; do
# Store the file extension
parsePathAndFilename="${BASH_REMATCH[0]}"
fileExtention=${file_changed_line#${parsePathAndFilename}};
# Add 1 to the language that is already defined
let languages[${fileExtention}]++;
file_changed_start=$(($file_changed_start + 1))
file_changed_line="${log_lines[file_changed_start]}"
done
# Create the JSON output
fileTypes="{";
fileTypeTotal=${#languages[#])};
assocKeys=(${!languages[#]});
for changed in "${!languages[#]}"
do
if [[ "$changed" == "${assocKeys[fileTypeTotal-1]}" ]]
then
fileTypes+="\"$changed\" : ${languages[$changed]}";
else
fileTypes+="\"$changed\" : ${languages[$changed]},";
fi
done
# close off the json
fileTypes+="}";
echo $fileTypes
commit_data="\"{ \"date\": \"$commit_date\", \"url\": \"$repo_url\", \"hash\": \"$commit_hash\", \"languages": \"$fileTypes\" }\""
git-stats --record "${commit_data}"
### git-stats hook (end) ###
The "here-string" syntax (<<<) is a bash extension; it is not present in basic shells. (It's not strictly bash; many other shells use it, too.) The same is true of arrays, and the readarray command.
So you need to make sure that you are using bash to run the script. Your shebang line (#!/bin/sh) tells the system to use a basic shell, and if the basic shell on your system is not bash, it may well not have <<<.
Try changing the shebang line to
#!/bin/bash
(Make sure that is the correct path to bash. You can verify with which bash.)

Accessing bash completions for specific commands programmatically

I'm trying to write a small command launcher application, and would like to use bash's tab completions in my own completion system. I've been able to get a list of completions for general commands using compgen -abck.
However, I would also like to get completions for specific commands: for instance, the input git p should display completion for git's commands.
Is there any way I can use compgen to do this? If not, are there any other ways I can get a list of completions programmatically?
[EDIT: To clarify, I'm not trying to provide completion to bash - my app is a GUI command launcher. I'd simply like to use bash's existing completions in my own app.]
I don't really know how it works, but the awesome window manager uses the following Lua code for getting access to bash completion's result:
https://github.com/awesomeWM/awesome/blob/master/lib/awful/completion.lua#L119
Via complete -p we find complete -o bashdefault -o default -o nospace -F _git git. We remember "_git" for later.
The length of "git l" is 5, so we set COMP_COUNT=6. We are completing the first argument to "git", so COMP_CWORD=1.
All together we use the following script:
__print_completions() {
printf '%s\n' "${COMPREPLY[#]}"
}
# load bash-completion functions
source /etc/bash_completion
# load git's completion function
_completion_loader git
COMP_WORDS=(git l)
COMP_LINE='git l'
COMP_POINT=6
COMP_CWORD=1
_git
__print_completions
Output: "log"
Check in the /etc/bash_completion.d/ directory. This is where the different command completion scripts stay.
Quite an old question, but in the mean time I've implemented a script that handles this to reuse completions with ZSH
Here a simple but working example in bash :
function foo_completion()
{
local currentWord=${COMP_WORDS[COMP_CWORD]}
local completionList=""
case "${COMP_CWORD}" in
"1")
completionList="command1 command2 command3";
;;
"2")
completionList="param1 param2 param3";
;;
esac
COMPREPLY=( $( compgen -W "${completionList}" -- ${currentWord} ) )
}
complete -F foo_completion foo
With this kind of code, you will get commandN completed when you type "foo c" + tab and paramN completed when you type "foo command1 p" + tab
You can compute the completion list from the command help.
my2c

Resources