Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git bash-completion with filename support?

is there a bash-completion script that supports filename completion? I use mostly mercurial and there I can type:

hg diff test/test_<tab>

and it will show/complete all modified test files. It works for most subcommands, i.e. hg add <tab><tab> will only list untracked files. It is really handy.

The bash script from git contrib seams to not support this. Are there any alternatives, or how do you work with git on the command line?

Edit 2015

git-completion.bash supports full filename completion since ~1.8.2

like image 379
olt Avatar asked May 17 '11 17:05

olt


People also ask

Does bash have tab completion?

The programmable completion feature in Bash permits typing a partial command, then pressing the [Tab] key to auto-complete the command sequence. [1] If multiple completions are possible, then [Tab] lists them all. Let's see how it works. Tab completion also works for variables and path names.

What is git auto completion?

The git version control system is a popular choice among developers when working with source code. Git is automatically installed on every Mac by default, but you might want to enable the git tab autocomplete feature to help you automatically complete commands and branch names.

What is bash auto completion?

Bash completion is a bash function that allows you to auto complete commands or arguments by typing partially commands or arguments, then pressing the [Tab] key. This will help you when writing the bash command in terminal.


2 Answers

So, let's see how the Mercurial bash completion script does this.

This is the important part:

_hg_status()
{
    local files="$(_hg_cmd status -n$1 .)"
    local IFS=$'\n'
    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$files' -- "$cur"))
}

It gets called here:

_hg_command_specific()
{
    case "$cmd" in 
    [...]
    diff)
        _hg_status "mar"
    ;;
    [...]
    esac
    return 0
}

Thus, it is simply a call of hg status -nmar, and using the output as a list of files for completion.

I think it would not be too hard to patch something similar into the git completion script - we would have to modify __git_diff here to not do a plain filename + branch completion, but calling git status instead.


The commands

git status --porcelain | grep '^.[^ ?]' | cut -b 4-

(for git diff --cached) and

git status --porcelain | grep '^[^ ?]' | cut -b 4-

(for git diff) seem to output the right thing (if there are no renames).

They both are not useful when diffing to anything other than HEAD, though.

A more general way would be to use

git diff --relative --name-only [--cached] [commit1] [commit2]]

where commit1 and commit2 (and maybe --cached) come from the already given diff command line.


I implemented the idea outlined above in bash, and patched into git-completion.bash. If you don't want to change your git-completion.bash, add these two functions to some bash file and source it after the original git-completion.bash. It should now work with commands like

git diff -- <tab>
git diff --cached -- <tab>
git diff HEAD^^ -- <tab>
git diff origin/master master -- <tab>

I submitted this as a patch to the git mailing list, let's see what results from this. (I'll update this answer as I get feedback there.)

# Completion for the file argument for git diff.
# It completes only files actually changed. This might be useful
# as completion for other commands as well.
#
# The idea comes from the bash completion for Mercurial (hg),
# which does something similar (but more simple, only difference of
# working directory to HEAD and/or index, if I understand right).
# It (the idea) was brought to us by the question
#      http://stackoverflow.com/q/6034472/600500
#  from "olt".
__git_complete_changed_files()
{
  #
  # We use "git diff --name-only --relative" to generate the list,
  # but this needs the same --cached and <commit> arguments as the
  # command line being constructed.
  #


    # first grab arguments like --cached and any commit arguments.

    local -a args=()
    local finish=false

    for (( i=1 ; i < cword ; i++)) do
    local current_arg=${words[$i]}
    #  echo checking $current_arg >&2
       case $current_arg in
           --cached)
               args+=( $current_arg )
               ;;
           --)
               # finish parsing arguments, the rest are file names
               break
               ;;
           -*)
               # other options are ignored
               ;;
           *)
               if git cat-file -e $current_arg 2> /dev/null
               then
                   case $( git cat-file -t $current_arg ) in
                       commit|tag)
                       # commits and tags are added to the command line.
                           args+=( $current_arg )
                           # echo adding $current_arg >&2
                           ;;
                       *)
                   esac
               fi
               ;;
       esac
    done

    # now we can call `git diff`

    COMPREPLY=( $( compgen \
        -W "$( git diff --name-only --relative "${args[@]}" -- )" -- $cur ) )
}

_git_diff ()
{
    if __git_has_doubledash
    then
        # complete for the file part: only changed files
        __git_complete_changed_files
    else
    case "$cur" in
    --*)
        __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
            --base --ours --theirs --no-index
            $__git_diff_common_options
            "
        return
        ;;
    esac
    __git_complete_revlist_file
    fi
}

Update: Looks like this patch is not wanted in this form, as the current way to complete files is more useful for people which want to check whether there are changes in some subdirectory (e.g. completing when the diff output could be empty). It might be accepted if linked to some configuration variable (with the default being the current behavior). Also, the indenting should be adapted to the standard (see the answer from Junio C Hamano).

I might take another go on it, but can't guarantee this for the near future. If someone else want to do, feel free to take my code, change it and submit it again.

like image 93
Paŭlo Ebermann Avatar answered Oct 22 '22 11:10

Paŭlo Ebermann


This solves the git diff <tab> problem for me, put the following in .bashrc:

alias gid='git diff'
__gdiff () {
    local cur prev opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    opts=$(git status --porcelain | grep '^.[^ ?]' | cut -b 4-)

    case "${prev}" in
        gid)
            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
            ;;
    esac
}
complete -F __gdiff gid

And then do gid <tab> instead of git diff <tab>. It may be simplified, but seems to work well as a quick fix.

like image 3
Kjell Andreassen Avatar answered Oct 22 '22 11:10

Kjell Andreassen