How can I check if I have any uncommitted changes in my git repository:
- Changes added to the index but not committed
- Untracked files
from a script?
git-status
seems to always retur开发者_C百科n zero with git version 1.6.4.2.
The key to reliably “scripting” Git is to use the ‘plumbing’ commands.
The developers take care when changing the plumbing commands to make sure they provide very stable interfaces (i.e. a given combination of repository state, stdin, command line options, arguments, etc. will produce the same output in all versions of Git where the command/option exists). New output variations in plumbing commands can be introduced via new options, but that can not introduce any problems for programs that have already been written against older versions (they would not be using the new options, since they did not exist (or at least were not used) at the time the script was written).
Unfortunately the ‘everyday’ Git commands are the ‘porcelain’ commands, so most Git users may not be familiar with with the plumbing commands. The distinction between porcelain and plumbing command is made in the main git manpage (see subsections titled High-level commands (porcelain) and Low-level commands (plumbing).
To find out about uncomitted changes, you will likely need git diff-index
(compare index (and maybe tracked bits of working tree) against some other treeish (e.g. HEAD
)), maybe git diff-files
(compare working tree against index), and possibly git ls-files
(list files; e.g. list untracked, unignored files).
(Note that in the below commands, HEAD --
is used instead of HEAD
because otherwise the command fails if there is a file named HEAD
.)
To check whether a repository has staged changes (not yet committed) use this:
git diff-index --quiet --cached HEAD --
- If it exits with
0
then there were no differences (1
means there were differences).
To check whether a working tree has changes that could be staged:
git diff-files --quiet
- The exit code is the same as for
git diff-index
(0
== no differences;1
== differences).
To check whether the combination of the index and the tracked files in the working tree have changes with respect to HEAD
:
git diff-index --quiet HEAD --
- This is like a combination of the previous two. One prime difference is that it will still report “no differences” if you have a staged change that you have “undone” in the working tree (gone back to the contents that are in
HEAD
). In this same situation, the two separate commands would both return reports of “differences present”.
You also mentioned untracked files. You might mean “untracked and unignored”, or you might mean just plain “untracked” (including ignored files). Either way, git ls-files
is the tool for the job:
For “untracked” (will include ignored files, if present):
git ls-files --others
For “untracked and unignored”:
git ls-files --exclude-standard --others
My first thought is to just check whether these commands have output:
test -z "$(git ls-files --others)"
- If it exits with
0
then there are no untracked files. If it exits with1
then there are untracked files.
There is a small chance that this will translate abnormal exits from git ls-files
into “no untracked files” reports (both result in non-zero exits of the above command). A bit more robust version might look like this:
u="$(git ls-files --others)" && test -z "$u"
- The idea is the same as the previous command, but it allows unexpected errors from
git ls-files
to propagate out. In this case a non-zero exit could mean “there are untracked files” or it could mean an error occurred. If you want the “error” results combined with the “no untracked files” result instead, usetest -n "$u"
(where exit of0
means “some untracked files”, and non-zero means error or “no untracked files”).
Another idea is to use --error-unmatch
to cause a non-zero exit when there are no untracked files. This also runs the risk of conflating “no untracked files” (exit 1
) with “an error occurred” (exit non-zero, but probably 128
). But checking for 0
vs. 1
vs. non-zero exit codes is probably fairly robust:
git ls-files --others --error-unmatch . >/dev/null 2>&1; ec=$?
if test "$ec" = 0; then
echo some untracked files
elif test "$ec" = 1; then
echo no untracked files
else
echo error from ls-files
fi
Any of the above git ls-files
examples can take --exclude-standard
if you want to consider only untracked and unignored files.
Great timing! I wrote a blog post about exactly this a few days ago, when I figured out how to add git status information to my prompt.
Here's what I do:
For dirty status:
# Returns "*" if the current git branch is dirty. function evil_git_dirty { [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]] && echo "*" }
For untracked files (Notice the
--porcelain
flag togit status
which gives you nice parse-able output):# Returns the number of untracked files function evil_git_num_untracked_files { expr `git status --porcelain 2>/dev/null| grep "^??" | wc -l` }
Although git diff --shortstat
is more convenient, you can also use git status --porcelain
for getting dirty files:
# Get number of files added to the index (but uncommitted)
expr $(git status --porcelain 2>/dev/null| grep "^M" | wc -l)
# Get number of files that are uncommitted and not added
expr $(git status --porcelain 2>/dev/null| grep "^ M" | wc -l)
# Get number of total uncommited files
expr $(git status --porcelain 2>/dev/null| egrep "^(M| M)" | wc -l)
Note: The 2>/dev/null
filters out the error messages so you can use these commands on non-git directories. (They'll simply return 0
for the file counts.)
Edit:
Here are the posts:
Adding Git Status Information to your Terminal Prompt
Improved Git-enabled Shell Prompt
Assuming you are on git 1.7.0 or later...
After reading all of the answers on this page and some experimenting, I think the method that hits the right combination of correctness and brevity is:
test -n "$(git status --porcelain)"
While git allows for a lot of nuance between what's tracked, ignore, untracked but unignored, and so on, I believe the typical use case is for automating build scripts, where you want to stop everything if your checkout isn't clean.
In that case, it makes sense to simulate what the programmer would do: type git status
and look at the output. But we don't want to rely on specific words showing up, so we use the --porcelain
mode introduced in 1.7.0; when enabled, a clean directory results in no output.
Then we use test -n
to see if there was any output or not.
This command will return 1 if the working directory is clean and 0 if there are changes to be committed. You can change the -n
to a -z
if you want the opposite. This is useful for chaining this to a command in a script. For example:
test -z "$(git status --porcelain)" || red-alert "UNCLEAN UNCLEAN"
This effectively says "either there are no changes to be made or set off an alarm"; this one-liner might be preferable to an if-statement depending on the script you are writing.
An implementation from VonC's answer:
if [[ -n $(git status --porcelain) ]]; then echo "repo is dirty"; fi
Had a look through a few of these answers... (and had various issues on *nix and windows, which was a requirement I had)... found the following worked well...
git diff --no-ext-diff --quiet --exit-code
To check the exit code in *nix
echo $?
#returns 1 if the repo has changes (0 if clean)
To check the exit code in window$
echo %errorlevel%
#returns 1 if the repos has changes (0 if clean)
Sourced from https://github.com/sindresorhus/pure/issues/115 Thanks to @paulirish on that post for sharing
Why not encapsulate 'git status
with a script which:
- will analyze the output of that command
- will return the appropriate error code based on what you need
That way, you can use that 'enhanced' status in your script.
As 0xfe mentions in his excellent answer, git status --porcelain
is instrumental in any script-based solution
--porcelain
Give the output in a stable, easy-to-parse format for scripts.
Currently this is identical to--short output
, but is guaranteed not to change in the future, making it safe for scripts.
One DIY possibility, updated to follow 0xfe's suggestion
#!/bin/sh
exit $(git status --porcelain | wc -l)
As noted by Chris Johnsen, this only works on Git 1.7.0 or newer.
You can also do
git describe --dirty
. It will append the word "-dirty" at the end if it detects a dirty working tree. According to git-describe(1)
:
--dirty[=<mark>]
Describe the working tree. It means describe HEAD and appends <mark> (-dirty by default) if
the working tree is dirty.
. Caveat: untracked files are not considered "dirty", because, as the manpage states, it only cares about the working tree.
I needed quite often a simple way to fail a build if at the end of execution there are any modified tracked files or any untracked files that are not ignored.
This is quite important for avoiding case where builds produce leftovers.
So far, the best command I ended up using looks like:
test -z "$(git status --porcelain | tee /dev/fd/2)" || \
{{ echo "ERROR: git unclean at the end, failing build." && return 1 }}
It may look bit complex and I would appreciate if anyone finds a shorted variant that this maintains desired behavior:
- no output and succcess exit code if evenything is in order
- exit code 1 if it fails
- error message on stderr explaining why it fails
- display list of files causing the failure, stderr again.
@eduard-wirch answer was quite complete, but as I wanted to check both at the same time, here is my final variant.
set -eu
u="$(git ls-files --others)"
if ! git diff-index --name-only --quiet HEAD -- || [ -z "${u:-}" ]; then
dirty="-dirty"
fi
When not executing using set -e or equivalent, we instead can do an u="$(git ls-files --others)" || exit 1
(or return if this works for a used function)
So untracked_files, is only set if the command succeeds properly.
after which, we can check for both properties, and set a variable (or whatever).
This is a more shell friendly variation for finding out if any untracked files exist in the repository:
# Works in bash and zsh
if [[ "$(git status --porcelain 2>/dev/null)" = *\?\?* ]]; then
echo untracked files
fi
This doesn't fork a second process, grep
, and doesn't need a check for if you are in a git repository or not. Which is handy for shell prompts, etc.
There may be a better combination of answers from this thread.. but this works for me... for your .gitconfig
's [alias]
section ...
# git untracked && echo "There are untracked files!"
untracked = ! git status --porcelain 2>/dev/null | grep -q "^??"
# git unclean && echo "There are uncommited changes!"
unclean = ! ! git diff --quiet --ignore-submodules HEAD > /dev/null 2>&1
# git dirty && echo "There are uncommitted changes OR untracked files!"
dirty = ! git untracked || git unclean
The simplest automatic test I use to detect dirty state = any changes including untracked files:
git add --all
git diff-index --exit-code HEAD
Remarks:
- Without
add --all
,diff-index
does not notice untracked files. - Normally I run
git reset
after testing error code to unstage everything back. - Consider
--quiet
instead of--exit-code
to avoid output.
Here is the best, cleanest way. The selected answer didn't work for me for some reason, it didn't pick up changes staged that were new files that weren't committed.
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
}
精彩评论