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

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


当前回答

更新: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
}

其他回答

使用python和GitPython包:

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

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

更新: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
}

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

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

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

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

您可以使用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

在Linux Ubuntu的bash终端上测试。

Shell脚本以编程方式解释git状态的输出

...并告诉你:

它有一个错误 它显示您的工作树是干净的(没有未提交的更改),或者 它显示您的工作树是脏的(您有未提交的更改)。

这里有一个很好的答案:Unix和Llinux:从脚本中确定Git工作目录是否干净。我的回答就是基于此。

我们将在git状态下使用——porcelain选项,因为它是由脚本解析的!

从man git状态(强调添加):

——瓷(= <版本>) 为脚本提供易于解析的输出格式。这类似于短的输出,但是无论用户配置如何,在Git版本之间都保持稳定。详情见下文。 version参数用于指定格式版本。这是可选的,默认为原始版本v1格式。

所以,这样做:

选项1

if output="$(git status --porcelain)" && [ -z "$output" ]; then
    echo "'git status --porcelain' had no errors AND the working directory" \
         "is clean."
else 
    echo "Working directory has UNCOMMITTED CHANGES."
fi

第一部分,如果output=$(git status——porcelain)将失败,如果git status——porcelain命令有错误,则跳转到else子句。第二部分&& [-z "$output"]测试输出变量是否包含空字符串(长度为零)。如果是,那么git状态是干净的,没有任何变化。

选项2

然而,通常我更喜欢的用法是用-n(非零)而不是-z(零)来否定测试,并像这样做:

if output="$(git status --porcelain)" && [ -n "$output" ]; then
    echo "'git status --porcelain' had no errors AND the working directory" \
         "is dirty (has UNCOMMITTED changes)."
    # Commit the changes here
    git add -A
    git commit -m "AUTOMATICALLY COMMITTING UNCOMMITTED CHANGES"
fi

选项3

一种更细粒度的方式来编写上面的第一个代码块是这样的:

if ! git_status_output="$(git status --porcelain)"; then
    # `git status` had an error
    error_code="$?"
    echo "'git status' had an error: $error_code" 
    # exit 1  # (optional)
elif [ -z "$git_status_output" ]; then
    # Working directory is clean
    echo "Working directory is clean."
else
    # Working directory has uncommitted changes.
    echo "Working directory has UNCOMMITTED CHANGES."
    # exit 2  # (optional)
fi

我已经测试了上面所有的代码,在不同的状态下复制和粘贴整个块到我的终端,它在所有3种情况下都能正常工作:

您的git status命令错误或拼写错误 Git状态是干净的(没有未提交的更改) Git状态是脏的(你有未提交的更改)

要强制'git status'有一个错误输出,只需将——porcelain选项拼写为——porcelain或其他东西,你会在最后看到这个输出:

'git status'有一个错误:0