How do I programmatically determine if there are uncommitted changes?


In a Makefile, I'd like to perform certain actions if there are uncommitted changes (either in the working tree or the index). What's the cleanest and most efficient way to do that? A command that exits with a return value of zero in one case and non-zero in the other would suit my purposes.

I can run git status and pipe the output through grep, but I feel like there must be a better way.


UPDATE: the OP Daniel Stutzbach points out in the comments that this simple command git diff-index worked for him:

git update-index --refresh 
git diff-index --quiet HEAD --

A more precise option would be to test git status --porcelain=v1 2>/dev/null | wc -l, using the porcelain option.
See Myridium's answer.

(nornagon mentions in the comments that, if there are files that have been touched, but whose contents are the same as in the index, you'll need to run git update-index --refresh before git diff-index, otherwise diff-index will incorrectly report that the tree is dirty)

You can then see "How to check if a command succeeded?" if you are using it in a bash script:

git diff-index --quiet HEAD -- || echo "untracked"; // do something about it

Note: as commented by Anthony Sottile

git diff-index HEAD ... will fail on a branch which has no commits (such as a newly initialized repository).
One workaround I've found is git diff-index $(git write-tree) ...

And haridsv points out in the comments that git diff-files on a new file doesn't detect it as a diff.
The safer approach seems to be to run git add on the file spec first and then use git diff-index to see if anything got added to index before running git commit.

git add ${file_args} && \
git diff-index --cached --quiet HEAD || git commit -m '${commit_msg}'

And 6502 reports in the comments:

One problem I bumped in is that git diff-index will tell that there are differences when indeed there is none except for timestamps of the files.
Running git diff once solves the issue (surprisingly enough, git diff does actually change the content of the sandbox, meaning here .git/index)

These timestamp issues can also occur if git is running in docker.

Original answer:

"Programmatically" means never ever rely on porcelain commands.
Always rely on plumbing commands.

See also "Checking for a dirty index or untracked files with Git" for alternatives (like git status --porcelain)

You can take inspiration from the new "require_clean_work_tree function" which is written as we speak ;) (early October 2010)

require_clean_work_tree () {
    # Update the index
    git update-index -q --ignore-submodules --refresh
# Disallow unstaged changes in the working tree
if ! git diff-files --quiet --ignore-submodules --
    echo >&2 "cannot $1: you have unstaged changes."
    git diff-files --name-status -r --ignore-submodules -- >&2

# Disallow uncommitted changes in the index
if ! git diff-index --cached --quiet HEAD --ignore-submodules --
    echo >&2 "cannot $1: your index contains uncommitted changes."
    git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2

if [ $err = 1 ]
    echo >&2 "Please commit or stash them."
    exit 1


I ran into a merge conflict. How do I abort the merge?

How to push different local Git branches to Heroku/master