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

我可以运行git状态并通过grep管道输出,但我觉得一定有更好的方法。


当前回答

使用python和GitPython包:

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

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

其他回答

虽然其他的解决方案非常彻底,但如果你想要一些非常快速和肮脏的东西,可以试试这样的方法:

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

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

更新:OP Daniel Stutzbach在评论中指出,这个简单的命令git diff-index对他有用:

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

一个更精确的选项是测试git状态——porcelain=v1 2>/dev/null | wc -l,使用瓷选项。 请看米里亚姆的回答。

(nornagon在评论中提到,如果有文件被触碰过,但其内容与索引中的内容相同,你需要在git diff-index之前运行git update-index——refresh,否则diff-index将错误地报告树是脏的)

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

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

注:正如Anthony Sottile所评论的

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

haridsv在评论中指出,git在新文件上的diff-files不会将其检测为diff。 更安全的方法似乎是先在文件规范上运行git add,然后在运行git commit之前使用git diffi -index查看是否有任何东西被添加到索引中。

git添加${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
}

我创建了一些方便的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'

正如在另一个回答中指出的那样,这样简单的命令就足够了:

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...

警告:此命令忽略未跟踪的文件。

有些答案会使问题过于复杂,或者没有达到预期的结果。例如,接受的答案错过了未跟踪的文件。

您可以使用git status——porcelain=v1并以编程方式解析输出。如果有一些未提交的更改,则输出为空,否则输出不为空。

一个posix兼容的最小工作示例:

[ -z "$(git status --porcelain=v1 2>/dev/null)" ] && echo "No uncommitted changes."

如果在git存储库外部运行,仍然会显示No uncommitted changes。

细节

The option --porcelain gives a machine-parseable output. The option specification --porcelain=v1 fixes the output version of the machine-parseable output, so that your script will never break under a future git update. As of my writing, you can check https://git-scm.com/docs/git-status for information about other version options, like --porcelain=v2. You may be able to do more advanced scripting with versions beyond v1. The 2>/dev/null is there so that git status will fail silently, if at all (i.e., if run outside of a git repository). As of this writing, the command git status ... will return exit code 128 if it is not inside a git repository. You can check explicitly for this exit code if you want a third option besides "uncommitted changes" or "no uncommitted changes".


额外:统计脏文件

受到这个答案的启发。你grep的git状态行瓷器=v1输出。每行的前两个字符表示特定文件的状态。在grepping之后,通过将输出输出输送到wc -l来计算有多少行处于这种状态,wc -l计算行数。

您可以用这种方式编写一些更高级的行为,或者选择您认为合格的“未提交的更改”。

例如,如果在git存储库中运行,这个脚本将打印一些信息。

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