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

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

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

132737 次浏览

如果有任何变化,git diff --exit-code将返回非零;git diff --quiet是相同的,没有输出。由于您希望检查工作树和索引,请使用

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

git diff --quiet HEAD

其中任何一个都将告诉您是否存在暂存的未提交更改。

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

git update-index --refresh
git diff-index --quiet HEAD --
一个更精确的选择是测试git status --porcelain=v1 2>/dev/null | wc -l,使用porcelain选项
参见Myridium回答

(nornagon提到在评论中,如果有文件被触碰过,但其内容与索引中的内容相同,则需要在git diff-index之前运行git update-index --refresh,否则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 commit之前使用git diff-index查看是否有任何东西被添加到索引

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中运行,这些时间戳问题也会发生。


最初的回答:

< p >“Programmatically"意味着永远不要依赖瓷器命令
始终依赖管道命令

参见&;使用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) ]]

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

使用python和GitPython包:

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

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

扩展@Nepthar的回答:

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

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

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

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

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

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
}

我创建了一些方便的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 status --porcelain,即设计为机器可解析,尽管有些人(错误地)在评论中表示相反。如果在git status中出现了一些东西,那么我就认为工作目录是脏的。所以我用[ -z "$(git status --porcelain=v1 2>/dev/null)" ]测试洁净度,如果在git目录外运行,它也会通过。

最小工作实例:

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

git status中出现的任何内容(到目前为止)都将正确触发此测试。=v1位确保git版本之间的输出格式一致。


额外:统计脏文件

灵感来自这个答案。你grep git status --porcelain=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

工作树是“干净的”;如果

git ls-files \
--deleted \
--modified \
--others \
--exclude-standard \
-- :/

返回什么。

解释

  • --deleted检查工作树中删除的文件
  • --modified检查工作树中修改的文件
  • --others检查工作树中添加的文件
  • --exclude-standard忽略根据通常的.gitignore.git/info/exclude…规则
  • -- :/路径规范的一切,如果不是运行在存储库的根

如果工作树是干净的,则输出为空

在Linux Ubuntu的bash终端上测试。

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

...并告诉你:

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

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

我们将在git status中使用--porcelain选项,因为它打算由脚本解析!

来自man git status(强调添加):

--porcelain[=<version>]

易于解析的脚本格式中给出输出。这类似于短输出,但将在Git版本之间保持稳定,无论用户配置如何。参见下面的细节。

version参数用于指定格式版本。这是可选的,默认为原始版本v1格式。

所以,这样做:

选项1

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

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

选项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种情况下都能正常工作:

  1. 你的git status命令是错误的或拼写错误
  2. git status是干净的(没有未提交的更改)
  3. git status是脏的(你有未提交的更改)

要强制输出'git status' had an error,只需将--porcelain选项拼写为--porcelainn或其他东西,你就会在最后看到这个输出:

'git status' had an error: 0