16

For a Git script I'm writing, I'd like to have a programmatic way to check whether there is anything at all staged. i.e. I want a positive result if anything at all is staged, and negative if nothing is staged.

Bonus points: A way to programmatically check whether there is anything new in the working tree that can be staged.

Ram Rachum
  • 84,019
  • 84
  • 236
  • 374
  • Possible duplicate of [How do I show the changes which have been staged?](http://stackoverflow.com/questions/1587846/how-do-i-show-the-changes-which-have-been-staged) – Whymarrh Nov 28 '16 at 00:27
  • You can use various flags to `git diff` to test what's staged/can be staged. – Whymarrh Nov 28 '16 at 00:27
  • 1
    Not *quite* a duplicate, since `--exit-code` isn't mentioned there, and is counter-intuitive. – o11c Nov 28 '16 at 00:30

3 Answers3

18

You're looking for:

git diff --cached --quiet

(or replace --quiet with --exit-code if you still want output)

o11c
  • 15,265
  • 4
  • 50
  • 75
6

The short format output of the git status command1 gives an output that can be used in a programmatic way:

Short Format

In the short-format, the status of each path is shown as one of these forms

XY PATH
XY ORIG_PATH -> PATH

where ORIG_PATH is where the renamed/copied contents came from. ORIG_PATH is only shown when the entry is renamed or copied. The XY is a two-letter status code.

Further down, an older (more-quotable) version of the documentation2 says:

For paths with merge conflicts, X and Y show the modification states of each side of the merge. For paths that do not have merge conflicts, X shows the status of the index, and Y shows the status of the work tree. For untracked paths, XY are ??. Other status codes can be interpreted as follows:

  • ' ' = unmodified
  • M = modified
  • T = file type changed (regular file, symbolic link or submodule)2
  • A = added
  • D = deleted
  • R = renamed
  • C = copied (if config option status.renames is set to "copies")2
  • U = updated but unmerged

Ignored files are not listed, unless --ignored option is in effect, in which case XY are !!.

The short format is obtained by using git status -s. This should give you all entries that have been staged so far:

git status -s | grep "^[MTADRCU]"

You can also add the -c flag to grep to count the lines instead of printing them.

Bonus: Anything new to the working tree that hasn't been staged yet (is still untracked) can be found with:

git status -s | grep "^??"

1 This text is also available as the helppage at git status --help.
2 The up-to-date documentation version is less succinct, owing to more explicit language around merge conflicts. I have marked codes that were added or modified in a Git version after the quoted version.

Michael
  • 8,362
  • 6
  • 61
  • 88
J.Baoby
  • 2,167
  • 2
  • 11
  • 17
  • If you want a line per untracked file rather than the summarized view for new folders, tack on `--untracked-files=all`. You can also save a couple CPU cycles with `--no-ahead-behind` to skip checking for commits. – Michael Feb 07 '19 at 17:45
0

This is basically a practical example of J.Baoby's answer. I run something like this in a loop over a set of repositories for a summary of uncommitted files in each.

function ordered_count() {
    # modified from https://superuser.com/a/529837/23156
    # echo "$data" | ordered_count -c foo -c bar
    awk -F' ?-c ' '
    BEGIN { split("'"$*"'", pattern) }
    { for (i = 2; pattern[i]; ++i) if (match($0, pattern[i])) ++count[i] }
    END { for (i = 2; pattern[i]; ++i) print count[i] }
    '
}

# The `$@` is so you can add extra args, e.g. `-- important/path`
gitstatus=$(git status --short --untracked-files=all --no-ahead-behind $@)

# Staged changes
mapfile -t staged_added_removed_changed < <(
    echo "$gitstatus" |
    ordered_count \
        -c "^A" \
        -c "^D" \
        -c "^[MRU]"
)

# Unstaged changes
mapfile -t unstaged_added_removed_changed < <(
    echo "$gitstatus" |
    ordered_count \
        -c '^([ C]A|\\?\\?)' \
        -c "^ D" \
        -c "^ [MRU]"
)

# Do stuff with the arrays
Michael
  • 8,362
  • 6
  • 61
  • 88