9

In my zsh shell, I am dynamically changing prompt depending on whether I am inside git repository or not. I am using following git command to check:

if $(git rev-parse --is-inside-work-tree >/dev/null 2>&1); then
...

now I also want to distinguish whether current directory is being ignored by git. So I have added one more check to my if statement:

if $(git rev-parse --is-inside-work-tree >/dev/null 2>&1) && ! $(git check-ignore . >/dev/null 2>&1); then
...

This works fine, but I was wondering whether I could simplify this into one git command. Since the prompt is refreshed on every ENTER, it tends to slow down the shell noticeably on some slower machines.

UPDATE

The accepted solution from @Stephen Kitt works great, except in following situation:

I am using repository across filesystems. Lets say git resides at /.git (because I want to track my config files in /etc), but I also want to track some files in /var/foo, which is a different partition/filesystem.

When I am located at / and execute following command, everything works as expected, and I get return code 1 (because /var/foo is being tracked):

# git check-ignore -q /var/foo

But when I am located anywhere in /var, the same command fails with error code 128 and following error message:

# git check-ignore -q /var/foo
fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).

But I think this is only problem with the check-ignore command. Otherwise git seems to work fine across filesystem. I can track files in /var/foo fine.

The expected behavior should be that git check-ignore -q /var/foo returns 1, and git check-ignore -q /var/bar returns 0, if it is not being tracked.

how can I fix this problem?

0

2 Answers 2

15
+250

git check-ignore . will fail with exit code 128 if . isn’t in a git repository (or any other error occurs), and with exit code 1 only if the path isn’t ignored. So you can check for the latter only:

git check-ignore -q . 2>/dev/null; if [ "$?" -ne "1" ]; then ... 

Inside the then, you’re handling the case where . is ignored or not in a git repository.

To make this work across file system boundaries, set GIT_DISCOVERY_ACROSS_FILESYSTEM to true:

GIT_DISCOVERY_ACROSS_FILESYSTEM=true git check-ignore -q . 2>/dev/null; if [ "$?" -ne "1" ]; then ...
2
12

First it should be:

if
  git rev-parse --is-inside-work-tree >/dev/null 2>&1 &&
    ! git check-ignore . >/dev/null 2>&1
then...

Command substitution ($(...)) is to retrieve the output of a command. Here, there's no output as you're redirecting it to /dev/null and if there were, it would be taken as a command to execute (and check its exit status).

Note that the second command is only run if the first one succeeded.

@StephenKitt has shown how to factorize those two commands into one git command, but if that still doesn't help wrt performance, you could always cache the information in an associative array.

typeset -A is_git # declared globally in your ~/.zshrc
if (( ! $+is_git[$PWD] )); then
  git check-ignore -q . 2> /dev/null
  is_git[$PWD]=$(( $? == 1 ))
fi

if (( $is_git[$PWD] )); then...

That cache could become stale if you rename directories, delete .git directories, or change your list of ignored files. You can invalidate it with is_git=() or by restarting zsh with exec zsh.

Another option would be to make that check only when you enter a directory using the chpwd hook:

check_git() {
  git check-ignore -q . 2> /dev/null
  (( is_git = $? == 1 ))
}
chpwd_functions+=(check_git)

And then, in your prompt routine, it's just a matter of:

if (( is_git )); then...
1
  • thank you. I have accepted the other answer, but your answer was also very helpful. Jan 4, 2021 at 13:16

You must log in to answer this question.

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