如何以编程方式确定是否存在未提交的更改?


226

在Makefile中,如果有未提交的更改(在工作树或索引中),我想执行某些操作。最干净,最有效的方法是什么?在一种情况下以零返回值而在另一种情况下以零返回值的退出命令将适合我的目的。

我可以运行git status输出并将其通过管道传输grep,但是我觉得必须有更好的方法。


Answers:


288

UPDATE:在OP 丹尼尔Stutzbach指出,在评论这个简单的命令git diff-index为他工作:

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

nornagon提到的意见,如果有已经触及文件,但其内容是一样的指数,你需要运行git update-index --refresh之前git diff-index,否则diff-index会错误地报告树是脏)

如果在bash脚本中使用命令,则可以看到“ 如何检查命令是否成功? ”:

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

注:如评论安东尼Sottile

git diff-index HEAD ...将在没有提交的分支(例如新初始化的存储库)上失败。
我发现的一种解决方法是git diff-index $(git write-tree) ...

haridsv指出在评论git diff-files上一个新的文件,不检测它作为一个差异。
较安全的方法似乎是先git add在文件规范上运行,然后再git diff-index查看运行前是否将任何内容添加到索引中git commit

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

6502在评论中报告:

我碰到的一个问题是,这git diff-index将告诉我们,除了文件的时间戳之外,实际上没有任何区别。
运行git diff一次即可解决问题(令人惊讶的是,git diff实际上确实更改了沙箱的内容,这意味着.git/index

如果git 在docker中运行,也会出现这些时间戳问题。


原始答案:

“以编程方式”意味着从不依赖瓷器命令
始终依靠管道命令

另请参见“ 使用Git检查脏索引或未跟踪的文件 ”以了解替代方法(如git status --porcelain

您可以从撰写本文时所写的新“ require_clean_work_tree函数 ”中获得启发;)(2010年10月上旬)

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
}

12
JakubNarębski反复向我提到了“管道与瓷器的脚本编写”课程:“ 如何在git中列出当前项目的所有日志? ”,“ git:日复一日地更改日志 ”,...
VonC

18
单击您建议的一些链接后,我找到了我想要的东西:git diff-index --quiet HEAD
Daniel Stutzbach

11
@DanielStutzbach:如果您HEAD在工作目录中有一个文件,则可能会失败。更好地使用git diff-index --quiet HEAD --
David Ongaro 2014年

7
但是手册中git status --help指出:-- porcelain 以易于解析的格式为脚本提供输出。这与简短的输出类似,但是无论用户配置如何,在Git版本之间都将保持稳定。有关详情,请参见下文。
Ed Randall

7
@VonC确实没有任何意义。这样,您就可以将所有内容扭转成逆向。--porcelain给您的印象是它很快就会破裂。如果不是,则应称为管道,而不是瓷器。使用--porcelain不会破坏您的脚本,这使其不会成为瓷器脚本;-)。如果您想破坏脚本,则不应使用--porcelain !!。因此,这完全令人无法理解,并把所有人抛弃了。
Xennex81 '16

104

虽然其他解决方案非常彻底,但是如果您想要快速又脏的东西,请尝试以下操作:

[[ -z $(git status -s) ]]

它只是检查状态摘要中是否有任何输出。


7
为我工作。将-n用作反函数(您有更改),例如`if [[-n $(git status -s)]]; 然后... fi`
亚伦

这行得通,但是您能说出[[ ... ]]语法的实际作用吗?我以前从未见过这样的东西。
GMA 2014年

2
git status在此测试中,@ EM的返回码实际上被忽略。它仅查看输出。看看这个庆典的相关页面上的详细信息[[[以及如何测试在bash作品。
Nepthar '16

2
这几乎是正确的答案,但对于脚本它更好地使用--porcelain参数如图所示这里
马里乌什Pawelski

2
您可能想要使用git status -s -uall包括未跟踪的文件。
barfuin

59

git diff --exit-code如有任何更改,将返回非零值;git diff --quiet一样,没有输出。由于您要检查工作树和索引,因此请使用

git diff --quiet && git diff --cached --quiet

要么

git diff --quiet HEAD

任何人都会告诉您是否存在未提交的更改。


6
这些不相等。单个命令git diff --quite HEAD只会告诉您工作树是否干净,而不是索引是否干净。例如,如果file在HEAD〜和HEAD之间进行了更改,则git reset HEAD~ -- file即使在索引中存在分阶段的更改(wt == HEAD,但索引!= HEAD),它也会退出0。
克里斯·约翰森

2
警告,这不会捕获使用git rm,AFAICS从临时区域中删除的文件。
nmr

24
无法检测到新的(未跟踪的)文件git diff --quiet && git diff --cached --quiet
4LegsDrivenCat 2015年

17

扩展@Nepthar的答案:

if [[ -z $(git status -s) ]]
then
  echo "tree is clean"
else
  echo "tree is dirty, please commit changes before running this"
  exit
fi

1
很好 我使用它通过测试来自动提交单个文件$(git status -s "$file"),然后在else条款中git add "$file"; git commit -m "your autocommit process" "$file"
toddkaufmann

如果你做的是git status -s一个git status --porcelain ; git clean -nd代替,垃圾目录将在这里也浮出水面,这是不可见的git status
ecmanaut

4

正如其他答案中所指出的那样,只要简单地执行以下命令即可:

git diff-index --quiet HEAD --

如果省略最后两个破折号,则如果文件名为,则命令将失败HEAD

例:

#!/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...

请注意:此命令将忽略未跟踪的文件。


2
正如对该答案的评论所指出的,这不会检测到新添加的文件
minexew

不,它会检测新添加到索引文件中。刚刚检查。
sanmai'1

见问题。未跟踪的文件不会更改git addgit clean进行救援
sanmai

4

我创建了一些方便的git别名来列出未暂存和暂存的文件:

git config --global alias.unstaged 'diff --name-only'
git config --global alias.staged 'diff --name-only --cached'

然后,您可以轻松地执行以下操作:

[[ -n "$(git unstaged)" ]] && echo unstaged files || echo NO unstaged files
[[ -n "$(git staged)" ]] && echo staged files || echo NO staged files

您可以通过在PATH被调用的某处创建脚本来使其更具可读性git-has

#!/bin/bash
[[ $(git "$@" | wc -c) -ne 0 ]]

现在,以上示例可以简化为:

git has unstaged && echo unstaged files || echo NO unstaged files
git has staged && echo staged files || echo NO staged files

为了完整起见,以下是未跟踪和忽略文件的相似别名:

git config --global alias.untracked 'ls-files --exclude-standard --others'
git config --global alias.ignored 'ls-files --exclude-standard --others --ignored'

2

使用python和GitPython包:

import git
git.Repo(path).is_dirty(untracked_files=True)

如果存储库不干净,则返回True


这避免了其他评论中提到的一些“时间戳”问题
Jason

1
请注意,GitPython也仅使用git CLI。如果设置LOGLEVEL=DEBUG,则将看到它运行所使用的所有Popen命令git diff
Jason

-3

这是最好,最干净的方法。

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
}

4
不,这不是最好的。git status是一个“瓷器”命令。不要在脚本中使用瓷器命令,因为它们可以在git版本之间进行更改。而是使用“管道”命令。
spuder

3
我认为,如果您更新它以使用git status --porcelain(这是为此目的–一种可以在脚本中解析的稳定格式),也可以使用-z(以空分隔而不是换行符),您可以对此做一些有用的事情。@ codyc4321 详情请参阅stackoverflow.com/questions/6976473/…–
msouth
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.