48

I'm working on a fairly large git repo with a couple of thousand (remote) branches. I am used to using auto-completion (using [TAB]) in the console (Git Bash in that case), so I unconsciously do that for git commands, too.

e.g. I'd type

git checkout task[TAB]

with the effect that the console stalls for often minutes. Is there a way to limit auto-completion to local branches only?

Manuela Hutter
  • 823
  • 9
  • 16
  • 2
    Note: since 2011, you have (with Git 2.13, Q2 2017) `git checkout --no-guess ..., or export GIT_COMPLETION_CHECKOUT_NO_GUESS=1`: both would disable branch completion. See [my answer below](http://stackoverflow.com/a/43747486/6309). – VonC May 02 '17 at 21:25

9 Answers9

32

With Git 2.13 (Q2 2017), you can disable (some of) the branch completion.

git checkout --no-guess ...
# or:
export GIT_COMPLETION_CHECKOUT_NO_GUESS=1

See commit 60e71bb (21 Apr 2017) by Jeff King (peff).
(Merged by Junio C Hamano -- gitster -- in commit b439747, 01 May 2017)

As documented in contrib/completion/git-completion.bash now:

You can set the following environment variables to influence the behavior of the completion routines:

GIT_COMPLETION_CHECKOUT_NO_GUESS

When set to "1", do not include "DWIM" suggestions in git-checkout completion (e.g., completing "foo" when "origin/foo" exists).

Note: DWIM is short for Do What I Mean, where a system attempts to anticipate what users intend to do, correcting trivial errors automatically rather than blindly executing users' explicit but potentially incorrect inputs.

completion: optionally disable checkout DWIM

When we complete branch names for "git checkout", we also complete remote branch names that could trigger the DWIM behavior. Depending on your workflow and project, this can be either convenient or annoying.

For instance, my clone of gitster.git contains 74 local "jk/*" branches, but origin contains another 147.
When I want to checkout a local branch but can't quite remember the name, tab completion shows me 251 entries. And worse, for a topic that has been picked up for pu, the upstream branch name is likely to be similar to mine, leading to a high probability that I pick the wrong one and accidentally create a new branch.


Note: "picked up for pu": see a What's cooking in git.git: it starts with:

Commits prefixed with '-' are only in 'pu' (proposed updates) while commits prefixed with '+' are in 'next'.

This is part of the Git Workflow Graduation process.

pu (proposed updates) is an integration branch for things that are not quite ready for inclusion yet


This patch adds a way for the user to tell the completion code not to include DWIM suggestions for checkout.
This can already be done by typing:

git checkout --no-guess jk/<TAB>

but that's rather cumbersome.

The downside, of course, is that you no longer get completion support when you do want to invoke the DWIM behavior.
But depending on your workflow, that may not be a big loss (for instance, in git.git I am much more likely to want to detach, so I'd type "git checkout origin/jk/<TAB>" anyway).

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • @sehe I have added references and documentation on the "pu" part. – VonC May 05 '17 at 19:55
  • 2
    Sadly creating an alias which uses this option internally does not seem to work. Only providing the option directly on the CLI results in only local branch completion. :( – Sascha Wolf Sep 19 '17 at 07:36
  • I believe as of now the best solution (which seems to fully answer OP's request) is `GIT_COMPLETION_CHECKOUT_NO_GUESS=1` while using `git switch` since 2.23 @VonC – lajarre May 06 '20 at 09:25
26

I'm assuming that you are using the git-completion.bash script, and that you only care about git checkout.

To accomplish this, I just changed one line in the definition of the _git_checkout () function in git-completion.bash:

<       __gitcomp_nl "$(__git_refs '' $track)"
---
>       __gitcomp_nl "$(__git_heads '' $track)"

My understanding is that this only affects the tab-completion action (because of its location within the * case of the switch-case statement).

Ilya
  • 5,533
  • 2
  • 29
  • 57
erik.weathers
  • 821
  • 1
  • 8
  • 13
  • 3
    Steps: `sudoedit /usr/share/bash-completion/completions/git`, then change the line mentioned in the @erik.weathers's answer, reopen bash and voilà! Thanks a lot. – mschayna Nov 20 '14 at 12:09
  • 1
    I agree with @andre.orvalho, this is definitely the better solution now. – Joel Pearson Feb 22 '15 at 22:41
  • 3
    I actually added a new method: __git_rco (). rco (or remote checkout) uses the old behavior with `refs`. __git_checkout () now just does `heads`. I have an alias rco = checkout. This way, you can still get the old behavior if you want. – Matt Hughes Jun 12 '15 at 18:50
  • I added an extra case where `"")` uses `__git_heads` but `*)` uses `__git_refs`. This makes it so it only suggests heads but if you have something typed out already (i.e. `t`) it will give me any matching ref (i.e. `tag/sometag`). If this continued to be too much, it could also match on `tag/` -> `tag/sometags` etc. – arcyqwerty Dec 15 '15 at 20:07
  • If you installed git-completion via brew, it's located here: `/usr/local/etc/bash_completion.d/git-completion.bash` – Toland Hon Apr 21 '16 at 18:15
11

If you installed git-completion via homebrew, it's located here: /usr/local/etc/bash_completion.d/git-completion.bash

Following erik.weathers' answer above, I made the following change so autocompletion can work for both local and remote based on the current prefix. By default, it'll only search local, but if I specify origin/… it'll know I want to search remote branches too.

In the _git_checkout () method, change

    __gitcomp_nl "$(__git_refs '' $track)"

to:

    # only search local branches instead of remote branches if origin isn't specified
    if [[ $cur == "origin/"* ]]; then
        __gitcomp_nl "$(__git_refs '' $track)"
    else
        __gitcomp_nl "$(__git_heads '' $track)"
    fi

Of course, you can change origin to something else or you can have it search through through a list of remote prefixes if you have more than 1.

Community
  • 1
  • 1
Toland Hon
  • 4,549
  • 2
  • 32
  • 36
10

You can hack /etc/bash_completion.d/git

You'll need to edit __git_refs ()

Note that the change in behaviour will apply every where (so even with git push/pull where you might not want it to). You could of course, make a copy of the function or pass an extra parameter, but I leave that to you

sehe
  • 374,641
  • 47
  • 450
  • 633
  • 5
    Yeah, so it turns out that tags *and* remote branches are slow. I changed `refs="refs/tags refs/heads refs/remotes"` to only `refs="refs/heads"` and commented out the whole "Try to find a remote branch that matches the completion word" section. Not pretty, but WFM :-) – Manuela Hutter Jul 11 '11 at 11:18
  • Mmm. you aren't by any chance using a git repository over the network? I've never seen _that pathetic_ performance with git (however: `git add` is notoriously slow in windows) – sehe Jul 11 '11 at 11:20
  • No, I'm using git locally. It's just a friggin' huge repo, I guess. `git for-each-ref | wc -l` returns 11000 ... – Manuela Hutter Jul 11 '11 at 11:32
  • But yes, Git is noticeably slower on Windows than on *nix/Mac. – Manuela Hutter Jul 11 '11 at 11:33
  • Any idea how to do this for zsh? – Galder Zamarreño Aug 29 '12 at 09:49
  • @GalderZamarreño I got this working by modifying /usr/share/zsh/4.3.11/functions/_git – c.apolzon Sep 07 '12 at 06:50
4

You could think that you just the local branches with the alias co and all the branches with the complete command checkout.

You could perform the following. In your .bashrc, you redefine the _git_checkout() function. You let this function unchanged, except the end:

if [ $command -eq "co" ]; then
  __gitcomp "$(__git_refs_local '' $track)"
else
  __gitcomp "$(__git_refs '' $track)"
fi

Then, you just have to define a new function, __git_refs_local, where you remove the remote stuff.

Jérôme
  • 2,640
  • 3
  • 26
  • 39
  • 1
    `-eq` is for comparing numbers, the comparison should be `if [ $command = "co" ]; then`. – pR0Ps Sep 24 '14 at 15:22
3

Carey Metcalfe wrote a blog post containing a solution that also edits the auto-completion function, but with slightly newer code than other answers. He also defines an alias checkoutr that keeps the old auto-complete behavior in case it’s ever needed.

In short, first create the checkoutr alias with this command:

git config --global alias.checkoutr checkout

Then find git-completion.bash, copy the _git_checkout function into your shell’s RC file so that it gets redefined, and inside that function, replace this line:

__git_complete_refs $track_opt

with the following lines:

if [ "$command" = "checkoutr" ]; then
    __git_complete_refs $track_opt
else
    __gitcomp_direct "$(__git_heads "" "$cur" " ")"
fi

See the blog post for more details and potential updates to the code.

Rory O'Kane
  • 29,210
  • 11
  • 96
  • 131
2

Modifying $(brew --prefix)/etc/bash_completion.d/git-completion.bash is not a good idea because it will be overwritten every time you update Git through Homebrew.

Combining all the answers I overwrite only _git_checkout function from the completion file in my .bash_profile after sourcing the completion file:

_git_checkout ()
{
  __git_has_doubledash && return

  case "$cur" in
    --conflict=*)
      __gitcomp "diff3 merge" "" "${cur##--conflict=}"
      ;;
    --*)
      __gitcomp "
      --quiet --ours --theirs --track --no-track --merge
      --conflict= --orphan --patch
      "
      ;;
    *)
      # check if --track, --no-track, or --no-guess was specified
      # if so, disable DWIM mode
      local flags="--track --no-track --no-guess" track=1
      if [ -n "$(__git_find_on_cmdline "$flags")" ]; then
        track=''
      fi
      # only search local branches instead of remote branches if origin isn't
      # specified
      if [[ $cur == "origin/"* ]]; then
        __gitcomp_nl "$(__git_refs '' $track)"
      else
        __gitcomp_nl "$(__git_heads '' $track)"
      fi
      ;;
  esac
}
Juliusz Gonera
  • 4,658
  • 5
  • 32
  • 35
0

FWW here is a hack to __git_complete_refs that does the trick

__git_complete_refs ()

{ local remote track pfx cur_="$cur" sfx=" "

while test $# != 0; do
    case "$1" in
    --remote=*) remote="${1##--remote=}" ;;
    --track)    track="yes" ;;
    --pfx=*)    pfx="${1##--pfx=}" ;;
    --cur=*)    cur_="${1##--cur=}" ;;
    --sfx=*)    sfx="${1##--sfx=}" ;;
    *)      return 1 ;;
    esac
    shift
done
echo cur_ $cur_ > a
 if [[  $GIT_COMPLETION_CHECKOUT_NO_GUESS != 1 || $cur_ == "origin"* ]]; then
    __gitcomp_direct "$(__git_refs "$remote" "$track" "$pfx" "$cur_" "$sfx")"
  else
    __gitcomp_direct "$(__git_heads  "" "$cur_")"
  fi

}

SzS
  • 161
  • 1
  • 4
0

I'm not using Git Bash myself, but if this is the same as mentioned in http://tekrat.com/2008/04/30/bash-autocompletion-git-super-lazy-goodness/, you should be able to replace git branch -a with a plain git branch in

_complete_git() {
  if [ -d .git ]; then
    branches=`git branch -a | cut -c 3-`
    tags=`git tag`
    cur="${COMP_WORDS[COMP_CWORD]}"
    COMPREPLY=( $(compgen -W "${branches} ${tags}" -- ${cur}) )
  fi
}
complete -F _complete_git git checkout

(in your .profile or similar) and get what you want.

Frank Schmitt
  • 30,195
  • 12
  • 73
  • 107
  • Hm. This function would be an addition to standard Git behavior (at least I don't have that function anywhere). What I want is to do less than the default. Editing `__git_refs()` seems to do the trick. – Manuela Hutter Jul 11 '11 at 11:44