From 89ccc114cbbee7e9d2a42498408b2b82e8ff4b08 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Fri, 9 Sep 2016 01:34:45 +1200 Subject: Further refinements/extensions to Git completion --- README.markdown | 2 +- bash/bash_completion.d/git.bash | 189 ++++++++++++++++++++++++++++------------ 2 files changed, 132 insertions(+), 59 deletions(-) diff --git a/README.markdown b/README.markdown index 3dab1845..2d943a9e 100644 --- a/README.markdown +++ b/README.markdown @@ -229,7 +229,7 @@ files, for things I really do get tired of typing repeatedly: * Bash builtins: commands, help topics, shell options, variables, etc. * `find(1)`'s more portable options * `ftp(1)` hostnames from `~/.netrc` -* `git(1)` subcommands, branch or tag names, and addable files +* `git(1)` subcommands, remotes, branches, tags, and addable files * `gpg(1)` long options * `make(1)` targets read from a `Makefile` * `man(1)` page titles diff --git a/bash/bash_completion.d/git.bash b/bash/bash_completion.d/git.bash index d974f655..798f04e8 100644 --- a/bash/bash_completion.d/git.bash +++ b/bash/bash_completion.d/git.bash @@ -1,5 +1,92 @@ # Some simple completion for Git _git() { + + # Subcommands for this function to stack words onto COMPREPLY; if the first + # argument is not given, the rest of the function is reached + case $1 in + + # No argument; continue normal completion + '') ;; + + # Symbolic references, remote or local + refs) + local ref + while IFS= read -r ref ; do + [[ -n $ref ]] || continue + ref=${ref#refs/*/} + case $ref in + "${COMP_WORDS[COMP_CWORD]}"*) + COMPREPLY[${#COMPREPLY[@]}]=$ref + ;; + esac + done < <(git for-each-ref \ + --format '%(refname)' \ + 2>/dev/null) + return + ;; + + # Remote names + remotes) + local remote + while IFS= read -r remote ; do + case $remote in + '') continue ;; + "${COMP_WORDS[COMP_CWORD]}"*) + COMPREPLY[${#COMPREPLY[@]}]=$remote + ;; + esac + done < <(git remote 2>/dev/null) + return + ;; + + # Git aliases + aliases) + local alias + while IFS= read -r alias ; do + alias=${alias#alias.} + alias=${alias%% *} + case $alias in + '') continue ;; + "${COMP_WORDS[COMP_CWORD]}"*) + COMPREPLY[${#COMPREPLY[@]}]=$alias + ;; + esac + done < <(git config \ + --get-regexp '^alias\.' \ + 2>/dev/null) + return + ;; + + # Git subcommands + subcommands) + local execpath + execpath=$(git --exec-path) || return + local path + for path in "$execpath"/git-"${COMP_WORDS[COMP_CWORD]}"* ; do + [[ -f $path ]] || continue + [[ -x $path ]] || continue + COMPREPLY[${#COMPREPLY[@]}]=${path#"$execpath"/git-} + done + return + ;; + + # Untracked files + untracked_files) + local file + while IFS= read -rd '' file ; do + [[ -n $file ]] || continue + COMPREPLY[${#COMPREPLY[@]}]=$file + done < <(git ls-files \ + --directory \ + --exclude-standard \ + --no-empty-directory \ + --others \ + -z \ + -- "${COMP_WORDS[COMP_CWORD]}"'*' \ + 2>/dev/null) + return + ;; + esac # Try to find the index of the Git subcommand local -i sci i @@ -20,16 +107,10 @@ _git() { esac done - # Complete initial subcommand - if ((sci == COMP_CWORD)) || [[ ${COMP_WORDS[sci]} == 'help' ]] ; then - local ep - ep=$(git --exec-path) || return - local path - for path in "$ep"/git-"${COMP_WORDS[COMP_CWORD]}"* ; do - [[ -f $path ]] || continue - [[ -x $path ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=${path#"$ep"/git-} - done + # Complete initial subcommand or alias + if ((sci == COMP_CWORD)) ; then + _git subcommands + _git aliases return fi @@ -38,61 +119,53 @@ _git() { # Complete with untracked, unignored files add) - local file - while IFS= read -rd '' file ; do - [[ -n $file ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$file - done < <(git ls-files \ - --directory \ - --exclude-standard \ - --no-empty-directory \ - --others \ - -z \ - -- "${COMP_WORDS[COMP_CWORD]}"'*' \ - 2>/dev/null) + _git untracked_files + return + ;; + + # Help on real subcommands (not aliases) + help) + _git subcommands + return ;; # Complete with remote subcommands and then remote names remote) local word - while IFS= read -r word ; do - [[ -n $word ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$word - done < <( - if ((COMP_CWORD - sci > 1)) ; then - git remote 2>/dev/null - else - compgen -W ' - add - get-url - prune - remove - rename - set-branches - set-head - set-url - show - update - ' -- "${COMP_WORDS[COMP_CWORD]}" - fi - ) + if ((CWORD == 1)) ; then + while IFS= read -r word ; do + [[ -n $word ]] || continue + COMPREPLY[${#COMPREPLY[@]}]=$word + done < <(compgen -W ' + add + get-url + prune + remove + rename + set-branches + set-head + set-url + show + update + ' -- "${COMP_WORDS[COMP_CWORD]}") + else + _git remotes + fi + return ;; - # Complete with ref names - *) - local ref - while IFS= read -r ref ; do - [[ -n $ref ]] || continue - ref=${ref#refs/*/} - case $ref in - "${COMP_WORDS[COMP_CWORD]}"*) - COMPREPLY[${#COMPREPLY[@]}]=$ref - ;; - esac - done < <(git for-each-ref \ - --format '%(refname)' \ - 2>/dev/null) - return + # Complete with remotes and then refs + fetch|pull|push) + if ((CWORD == 1)) ; then + _git remotes + else + _git refs + fi + ;; + + # Commands for which I'm likely to want a ref + branch|checkout|merge|rebase|tag) + _git refs ;; esac } -- cgit v1.2.3