如何检查当前分支是否没有需要提交的内容?

274

目标是获取一个可以在shell命令中评估的明确状态。

我尝试了git status,但它总是返回0,即使有要提交的项。

git status
echo $?  #this is always 0

我有一个想法,但我认为这是一个相当糟糕的想法。

if [ git status | grep -i -c "[a-z]"> 2 ];
then
 code for change...
else
  code for nothing change...
fi

还有其他方式吗?


使用以下解决方案进行更新,请参见Mark Longair的帖子

我尝试了这个,但它引起了一个问题。

if [ -z $(git status --porcelain) ];
then
    echo "IT IS CLEAN"
else
    echo "PLEASE COMMIT YOUR CHANGE FIRST!!!"
    echo git status
fi

我遇到了以下错误:[: ??: 预期二元运算符

现在,我正在查看手册并尝试使用git diff。

===================以下是我的代码,希望能得到更好的答案======================

#if [ `git status | grep -i -c "$"` -lt 3 ];
# change to below code,although the above code is simple, but I think it is not strict logical
if [ `git diff --cached --exit-code HEAD^ > /dev/null && (git ls-files --other --exclude-standard --directory | grep -c -v '/$')` ];
then
        echo "PLEASE COMMIT YOUR CHANGE FIRST!!!"
    exit 1

else
    exit 0
fi

4
在更新的部分中,似乎您并没有按照eckes他的回答中建议的去做 - 他说,你需要在$(git status --porcelain)周围加上双引号。另外,如果您想在消息中放入感叹号,则需要使用单引号而不是双引号 - 也就是说,应该是echo 'PLEASE COMMIT YOUR CHANGE FIRST!!!'而不是 - Mark Longair
7
正如马克所说:“你需要在 $(git status --porcelain) 周围加上双引号”,就像我告诉过你的一样! - eckes
2
如果这个问题不包含答案的部分,那么它会更有用。 - oberlies
1
@9nix00,按照指示编辑并修复你上面的shell脚本中的错误:错误:if [ -z $(some command) ]修复:if [ -z "$(some command)" ] - MarcH
你的第二次尝试使用 git status --porcelain 几乎是正确的。 你只是在调用 git status 时缺少了一些双引号。第一行应该是 if [ -z "$(git status --porcelain)" ]。 双引号确保命令输出(可能包含空格)被视为 -z 测试的单个参数。 - Chris B
11个回答

320

与测试git status --porcelain的输出是否为空相比,另一种选择是分别测试您关心的每个条件。例如,您可能并不总是关心在git status的输出中是否有未跟踪的文件。

例如,要查看是否存在本地未暂存的更改,可以查看以下命令的返回代码:

git diff --exit-code

要检查是否有已暂存但未提交的更改,您可以使用以下命令的返回代码:

git diff --cached --exit-code

最后,如果你想知道工作树中是否有任何未跟踪且未被忽略的文件,你可以测试以下命令输出是否为空:

git ls-files --other --exclude-standard --directory

更新:你在下面问是否可以更改该命令以在输出中排除目录。你可以通过添加--no-empty-directory来排除空目录,但要排除该输出中的所有目录,我认为您需要过滤输出,例如使用以下方法:

git ls-files --other --exclude-standard --directory | egrep -v '/$'
< p > -v 参数用于egrep命令,意味着只输出不匹配模式的行,而该模式匹配以/结尾的任何行。 < /p >

我学到了这些技巧,但遇到了一个问题。就是使用 git ls-files --other --exclude-standard --directory 命令可以获取包括目录在内的列表。有没有办法排除这些目录呢? - 9nix00
1
是的,这就是我想要的。我更新了我的帖子以添加新的脚本代码。我认为你的建议虽然代码更多但逻辑更严格 lol... 希望能出现更好的答案。 - 9nix00
3
这段话的意思是:在git-diff手册中有这样一句话:“使程序退出代码类似于diff(1)。也就是说,如果有差异,则退出状态码为1,而0表示没有差异。” - Mark Longair
只是想指出,它至少从2007年[13da0fc0](https://github.com/git/git/blame/13da0fc092b8cf082eda2f16971c75903aa5aefc/diff.c#L3788)就已经存在了,非常适用于shell脚本,并且完全兼容旧版本的git。 - albfan
奇怪的失败。我的测试是git diff --exit-code || echo OK或更复杂的变体。我应该得到不同的答案。 - Charles Merriam
14
--quiet选项(隐含--exit-code)还会使输出内容静默,适用于只需要退出码的用户。 - phs

189

git status 的返回值只是告诉你 git status 的退出码,而不是有没有修改需要提交。

如果你想要一个更适合计算机阅读的版本的 git status 输出,可以尝试使用

git status --porcelain

更多关于 git status 的信息请参考描述。

示例用法(脚本只是简单测试是否有任何输出,无需解析):

if [ -n "$(git status --porcelain)" ]; then
  echo "there are changes";
else
  echo "no changes";
fi
请注意,您需要引用要测试的字符串,即 git status --porcelain 的输出。有关测试构造的更多提示,请参阅高级Bash脚本指南(第字符串比较节)。

你好,你提供了一个很好的建议,我尝试了一下,但在脚本中出现了一些问题,我进行了改进。如果我们使用这个if [ -z $(git status --porcelain) ];会出现一些错误,[: ??: binary operator expected。我查找了手册并使用了这个if [ -z $(git status --short) ];它可以工作,谢谢! - 9nix00
抱歉,仍然存在问题。当提交是干净的时候,使用porcelain和short都可以。但是当提交不干净时,会导致错误。[: ??: 预期二进制运算符。我认为也许我们应该尝试使用base64对其进行编码。让我试试!下载base64命令工具.... 哈哈 - 9nix00
当提交不干净时,它会引起问题。 - 9nix00
2
很好的解决方案。为了增加鲁棒性,您可以在命令替换中添加 || echo no,以便如果 git status 失败,则不会错误地报告工作区干净。此外,您的代码是(值得称赞的)符合 POSIX 标准的,但由于您链接到 bash 指南,让我补充一下,如果您使用 bash 的 [[ ... ]] 而不是 POSIX 兼容的 [ ... ],则不需要对命令替换进行双引号引用(尽管这样做没有害处):[[ -z $(git status --porcelain) ]] - mklement0
@eckes 几天前我开始了一个新的仓库,我添加了一个提交,我尝试编写一些 pre-commit-hook 并检查要提交的内容,但你所说的在我的情况下不起作用。 - alinsoar

47

如果你和我一样,你想知道以下几点:

1)已有文件是否有更改 2)新添加的文件 3)删除的文件

并且明确不想知道 4)未被跟踪的文件。

以下代码可以满足以上需求:

git status --untracked-files=no --porcelain

这是我用bash编写的代码,如果仓库干净就退出脚本。它使用未跟踪文件选项的短版本:

[[ -z $(git status -uno --porcelain) ]] && echo "this branch is clean, no need to push..." && kill -SIGINT $$;

4
对于—untracked-files=no给出+1赞同。我认为您的测试可以简化为[[ -z $(git status --untracked-files=no --porcelain) ]]。除非有基本问题,否则git status不应该写入stderr - 然后您确实希望看到该输出。(如果您想在这种情况下获得更可靠的行为,请将“|| echo no”附加到命令替换中,以便清洁度测试仍然失败)。字符串比较/“-z”操作符可以处理多行字符串 - 不需要使用“tail”。 - mklement0
2
感谢@mklement0,甚至更短:[[ -z $(git status -u no --porcelain) ]] - moodboom
1
更正:我的简短版本实际上只是检查名为“no”的文件的状态!嗡嗡声。应该是: [[ -z $(git status -uno --porcelain) ]] - moodboom
2
我很感激您的跟进;那是一个微妙的错误 - 教训是带有可选参数的_short_选项必须直接附加参数,中间不能有空格。将更正后的短版本直接合并到您的答案中如何? - mklement0

16

我会对此进行测试:

git diff --quiet --cached

或者更明确地说:

git diff --quiet --exit-code --cached

其中:

--exit-code

使程序以类似于diff(1)的代码退出。也就是说,如果有差异,则以1退出,0表示没有差异。

--quiet

禁用程序的所有输出。意味着--exit-code。


10

可以将git status --porcelain与简单的grep结合起来进行测试。

if git status --porcelain | grep .; then
    echo Repo is dirty
else
    echo Repo is clean
fi

有时我会把它用作简单的一行代码:

# pull from origin if our repo is clean
git status --porcelain | grep . || git pull origin master

在您的grep命令中添加-qs以使其静音。


2
+1 的优雅分:如果 git status 失败(例如,存储库损坏),则您的测试将错误地报告一个“干净”的工作区。一种选择是使用 git status --porcelain 2>&1,但如果您使用 -q 和 grep,则会“吃掉”错误消息。 (处理它会失去优雅:(git status --porcelain || echo err) | grep -q . - mklement0
1
或者,你可以写成:test -z "$(git status --porcelain)" || git pull origin master - VasiliNovikov

6
从Git源代码中,有一个包含以下内容的sh脚本。
require_clean_work_tree () {
    git rev-parse --verify HEAD >/dev/null || exit 1
    git update-index -q --ignore-submodules --refresh
    err=0

    if ! git diff-files --quiet --ignore-submodules
    then
        echo >&2 "Cannot $1: You have unstaged changes."
        err=1
    fi

    if ! git diff-index --cached --quiet --ignore-submodules HEAD --
    then
        if [ $err = 0 ]
        then
            echo >&2 "Cannot $1: Your index contains uncommitted changes."
        else
            echo >&2 "Additionally, your index contains uncommitted changes."
        fi
        err=1
    fi

    if [ $err = 1 ]
    then
        test -n "$2" && echo >&2 "$2"
        exit 1
    fi
}

这个片段展示了如何使用 git diff-filesgit diff-index 来查找之前已知文件是否有任何更改。但它无法让你找出新添加到工作树中的未知文件。


这段代码运行良好,除了新文件。我们可以添加以下内容:如果 ! git ls-files --other --exclude-standard --directory | grep -c -v '/$' 那么 退出 0
否则 echo "请提交您的新文件,如果您不想添加它,请将其添加到git-ignore文件中。" 退出 1 fi
- 9nix00
只需使用 if [ -n "$(git ls-files --others --exclude-standard)" ],无需任何额外的管道或过滤即可检测未跟踪的文件。 - Arrowmaster

5

虽然我有点晚加入讨论,但是如果你只需要在git status --porcelain返回空值时具有退出码0,否则退出码!= 0,可以尝试这个:

exit $( git status --porcelain | wc -l )

这将使行数成为退出代码,但当行数超过255行时可能会出现问题。因此,
exit $( git status --porcelain | head -255 | wc -l )

会考虑到这一点的;)

1
如果输出的行数超过255行,这基本上将是未定义的。 - tripleee
非常好,谢谢! - Skeeve

5

我在脚本中使用这个命令:

  • 当一切干净时,返回0
  • 当有差异或未跟踪的文件时,返回1

    [ -z "$(git status --porcelain)" ]


2
使用 if ! git diff --quiet; then 更加简洁和高效(我认为)。换句话说,使用退出代码而不是标准输出。 - Alexander Mills
1
@AlexanderMills git diff --quiet 对于缓存的更改与 git status --porcelain 的行为不同。 - Martin von Wittich

4

当涉及到git子模块时,我发现所有答案都不太有用。

经过一番小调查,我找到了最佳解决方案,简单可靠:

git commit --dry-run && echo "Things to commit" || echo "Clean repo"

如果任何更改不仅影响索引,还需要考虑更新子模块,则可以添加-a参数。


很好:我使用了 git commit -a --dry-run --short|wc -l。如果不是0,我知道还有一些工作正在进行中。 - VonC

0

不太美观,但是有效:

git status | grep -qF 'working directory clean' || echo "DIRTY"

不确定消息是否与语言环境有关,因此最好在前面加上LANG=C


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接