How do I programmatically determine if there are uncommitted changes?
GitGit Problem Overview
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.
Git Solutions
Solution 1 - Git
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
err=0
# Disallow unstaged changes in the working tree
if ! git diff-files --quiet --ignore-submodules --
then
echo >&2 "cannot $1: you have unstaged changes."
git diff-files --name-status -r --ignore-submodules -- >&2
err=1
fi
# Disallow uncommitted changes in the index
if ! git diff-index --cached --quiet HEAD --ignore-submodules --
then
echo >&2 "cannot $1: your index contains uncommitted changes."
git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2
err=1
fi
if [ $err = 1 ]
then
echo >&2 "Please commit or stash them."
exit 1
fi
}
Solution 2 - Git
While the other solutions are very thorough, if you want something really quick and dirty, try something like this:
[[ -z $(git status -s) ]]
It just checks if there is any output in the status summary.
Solution 3 - Git
git diff --exit-code
will return nonzero if there are any changes; git diff --quiet
is the same with no output. Since you want to check for the working tree and the index, use
git diff --quiet && git diff --cached --quiet
Or
git diff --quiet HEAD
Either one will tell you if there are uncommitted changes that are staged or not.
Solution 4 - Git
Expanding on @Nepthar's answer:
if [[ -z $(git status -s) ]]
then
echo "tree is clean"
else
echo "tree is dirty, please commit changes before running this"
exit
fi
Solution 5 - Git
Some answers are both overcomplicating the matter and not achieving the desired result. E.g. the accepted answer misses untracked files.
Use the provided git status --porcelain
which is designed to be machine parseable despite some people (incorrectly) saying otherwise in the comments. If something shows up in git status
, then that's when I consider the working directory dirty. So I test for cleanliness with the test [ -z "$(git status --porcelain=v1 2>/dev/null)" ]
, which will also pass if run outside a git directory.
Minimum working example:
[ -z "$(git status --porcelain=v1 2>/dev/null)" ] && echo "git undirty"
Anything that shows up in git status
(as of now) will trigger this test correctly. The =v1
bit ensures a consistent output format across git versions.
Extra: counting dirty files
Inspired by this answer. You grep
the lines of git status --porcelain=v1
output. The first two characters of each line indicate what the status is of the particular file. After grepping, you count how many have that status by piping the output to wc -l
which counts the number of lines.
E.g. this script will print some information if run inside a git repository.
#!/bin/sh
GS=$(git status --porcelain=v1 2>/dev/null) # Exit code 128 if not in git directory. Unfortunately this exit code is a bit generic but it should work for most purposes.
if [ $? -ne 128 ]; then
function _count_git_pattern() {
echo "$(grep "^$1" <<< $GS | wc -l)"
}
echo "There are $(_count_git_pattern "??") untracked files."
echo "There are $(_count_git_pattern " M") unstaged, modified files."
echo "There are $(_count_git_pattern "M ") staged, modified files."
fi
Solution 6 - Git
I created some handy git aliases to list unstaged and staged files:
git config --global alias.unstaged 'diff --name-only'
git config --global alias.staged 'diff --name-only --cached'
Then you can easily do things like:
[[ -n "$(git unstaged)" ]] && echo unstaged files || echo NO unstaged files
[[ -n "$(git staged)" ]] && echo staged files || echo NO staged files
You can make it more readable by creating a script somewhere on your PATH
called git-has
:
#!/bin/bash
[[ $(git "$@" | wc -c) -ne 0 ]]
Now the above examples can be simplified to:
git has unstaged && echo unstaged files || echo NO unstaged files
git has staged && echo staged files || echo NO staged files
For completeness here are similar aliases for untracked and ignored files:
git config --global alias.untracked 'ls-files --exclude-standard --others'
git config --global alias.ignored 'ls-files --exclude-standard --others --ignored'
Solution 7 - Git
With python and the GitPython package:
import git
git.Repo(path).is_dirty(untracked_files=True)
Returns True
if repository is not clean
Solution 8 - Git
The working tree is "clean" if
git ls-files --deleted --modified --others --exclude-standard
returns nothing.
Explanation
--deleted
check for files deleted in the working tree--modified
check for files modified in the working tree--others
check for files added in the working tree--exclude-standard
ignore according to the usual.gitignore
,.git/info/exclude
... rules
That output is empty if the working tree is clean
Solution 9 - Git
As pointed in other answer, as simple as such command is sufficient:
git diff-index --quiet HEAD --
If you omit the last two dashes, the command would fail if you have a file named HEAD
.
Example:
#!/bin/bash
set -e
echo -n "Checking if there are uncommited changes... "
trap 'echo -e "\033[0;31mFAILED\033[0m"' ERR
git diff-index --quiet HEAD --
trap - ERR
echo -e "\033[0;32mAll set!\033[0m"
# continue as planned...
Word of caution: this command ignores untracked files.
Solution 10 - Git
Here is the best, cleanest way.
function git_dirty {
text=$(git status)
changed_text="Changes to be committed"
untracked_files="Untracked files"
dirty=false
if [[ ${text} = *"$changed_text"* ]];then
dirty=true
fi
if [[ ${text} = *"$untracked_files"* ]];then
dirty=true
fi
echo $dirty
}