使用 Git 检查脏索引或未跟踪文件

Posted

技术标签:

【中文标题】使用 Git 检查脏索引或未跟踪文件【英文标题】:Checking for a dirty index or untracked files with Git 【发布时间】:2011-02-09 02:51:16 【问题描述】:

如何检查我的 git 存储库中是否有任何未提交的更改:

    已添加到索引但未提交的更改 未跟踪的文件

来自脚本?

git-status 在 git 版本 1.6.4.2 中似乎总是返回零。

【问题讨论】:

如果有未暂存的修改文件,git status 将返回 1。但总的来说,我发现 git 工具在返回状态方面并不是特别彻底。 EG git diff 是否有差异返回 0。 @intuited:如果您需要diff 来指示是否存在差异而不是命令是否成功运行,那么您需要使用--exit-code--quiet。 git 命令一般与返回零或非零退出码表示命令成功非常一致。 @Charles Bailey:嘿,太好了,我错过了那个选项。谢谢!我想我从来没有真正需要它来做到这一点,或者我可能会在手册页上搜索它。很高兴你纠正了我:) robert - 这对社区来说是一个非常令人困惑的话题(“瓷器”在不同情况下的使用方式不同并没有帮助)。您选择的答案忽略了 2.5 倍投票率更高的答案,该答案更健壮,并且遵循 git 设计。你看过@ChrisJ 的回答吗? @Robert - 我希望您可以考虑更改您接受的答案。您选择的那个依赖于更脆弱的 git 'porcelain' 命令;实际的正确答案(也有 2x 的赞成票)依赖于正确的 git 'plumbing' 命令。这个正确答案最初是由 Chris Johnsen 提出的。我提出这个问题是因为今天是我第三次不得不将某人推荐给这个页面,但我不能只指出答案,我已经解释了为什么接受的答案是次优/边界错误。谢谢! 【参考方案1】:

为什么不用一个脚本封装 'git status

将分析该命令的输出 将根据您的需要返回相应的错误代码

这样,您可以在脚本中使用“增强”状态。


正如0xfe 在他的excellent answer 中提到的那样,git status --porcelain 在任何基于脚本的解决方案中都非常有用

--porcelain

以稳定、易于解析的脚本格式提供输出。 目前这与--short output 相同,但保证将来不会更改,从而使其对脚本安全。

【讨论】:

可能是因为我很懒。我认为这是一个内置的,因为它似乎是一个经常遇到的用例。 我根据你的建议发布了一个解决方案,虽然我对它不是很满意。【参考方案2】:

好时机!几天前我写了一篇关于这个的博客文章,当时我想出了如何将 git 状态信息添加到我的提示符中。

这是我的工作:

    脏状态:

    # Returns "*" if the current git branch is dirty.
    function evil_git_dirty 
      [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]] && echo "*"
    
    

    对于未跟踪的文件(注意git status--porcelain 标志,它可以为您提供很好的可解析输出):

    # Returns the number of untracked files
    
    function evil_git_num_untracked_files 
      expr `git status --porcelain 2>/dev/null| grep "^??" | wc -l` 
    
    

虽然git diff --shortstat更方便,但你也可以使用git status --porcelain来获取脏文件:

# Get number of files added to the index (but uncommitted)
expr $(git status --porcelain 2>/dev/null| grep "^M" | wc -l)

# Get number of files that are uncommitted and not added
expr $(git status --porcelain 2>/dev/null| grep "^ M" | wc -l)

# Get number of total uncommited files
expr $(git status --porcelain 2>/dev/null| egrep "^(M| M)" | wc -l)

注意:2>/dev/null 过滤掉错误消息,因此您可以在非 git 目录上使用这些命令。 (他们将简单地返回 0 以获取文件计数。)

编辑

以下是帖子:

Adding Git Status Information to your Terminal Prompt

Improved Git-enabled Shell Prompt

【讨论】:

值得注意的是,git bash 补全附带了一个 shell 函数,用于执行您正在使用提示符执行的操作 - __git_ps1。它会显示分支名称,包括特殊处理,如果您正处于 rebase、am-apply、merge 或 bisect 的过程中。您可以设置环境变量GIT_PS1_SHOWDIRTYSTATE 以获得未分级更改的星号和分级更改的加号。 (我想你也可以让它指示未跟踪的文件,并给你一些git-describe 输出) 警告:如果索引中已有更改,git diff --shortstat 将给出假阴性。 git status --porcelain 更可取,因为git diff --shortstat 不会捕获新创建的空文件。您可以在任何干净的工作树中尝试它:touch foo && git diff --shortstat 否 - 瓷器意味着输出是为 人类 并且容易损坏的!!请参阅@ChrisJohnsen 的答案,它正确使用了稳定、脚本友好的选项。 @mike 来自 git-status 手册页关于瓷器选项:“以易于解析的格式为脚本提供输出。这类似于短输出,但将保持稳定Git 版本,无论用户配置如何。”【参考方案3】:

一种 DIY 可能性,已更新以遵循 0xfe 的建议

#!/bin/sh
exit $(git status --porcelain | wc -l) 

如 Chris Johnsen 所述,这只适用于 Git 1.7.0 或更高版本。

【讨论】:

这个问题是你不能在未来的版本中可靠地期望字符串'working directory clean'。 --porcelain 标志用于解析,因此更好的解决方案是:exit $(git status --porcelain | wc -l) @0xfe - 你知道--porcelain 标志是什么时候添加的吗?不适用于 1.6.4.2。 @Robert:然后试试git status --short git status --porcelaingit status --short 都是在 1.7.0 中引入的。 --porcelain 专门引入它是为了让git status --short 将来可以改变其格式。因此,git status --short 会遇到与git status 相同的问题(输出可能随时更改,因为它不是“管道”命令)。 @Chris,感谢您提供背景信息。我已经更新了答案以反映从 Git 1.7.0 开始执行此操作的最佳方式。【参考方案4】:

可靠地“编写”Git 的关键是使用“管道”命令。

开发人员在更改管道命令时要小心,以确保它们提供非常稳定的接口(即存储库状态、标准输入、命令行选项、参数等的给定组合将在所有版本的 Git 中产生相同的输出,其中命令/选项存在)。管道命令的新输出变化可以通过新选项引入,但这不会给已经针对旧版本编写的程序引入任何问题(它们不会使用新选项,因为它们不存在(或者至少是未使用)在编写脚本时)。

不幸的是,“日常”Git 命令是“瓷器”命令,因此大多数 Git 用户可能不熟悉管道命令。瓷器和管道命令之间的区别主要在git manpage 中进行(参见标题为High-level commands (porcelain) 和Low-level commands (plumbing) 的小节。


要了解未提交的更改,您可能需要git diff-index(比较索引(可能是工作树的跟踪位)与其他一些树状结构(例如HEAD)),可能需要git diff-files(比较工作树与index),可能还有git ls-files(列出文件;例如列出未跟踪、未忽略的文件)。

(请注意,在下面的命令中,HEAD -- 被用来代替 HEAD,否则如果有一个名为 HEAD 的文件,则使用命令 fails。)

要检查存储库是否已暂存更改(尚未提交),请使用:

git diff-index --quiet --cached HEAD --
如果它以0 退出,则没有差异(1 表示存在差异)。

检查工作树是否有可以暂存的更改:

git diff-files --quiet
退出代码与git diff-index 相同(0 == 无差异;1 == 差异)。

检查工作树中索引和跟踪文件的组合是否相对于HEAD有变化:

git diff-index --quiet HEAD --
这就像前两者的组合。一个主要区别是,如果您在工作树中有“撤消”的分阶段更改(返回到HEAD 中的内容),它仍然会报告“无差异”。在同样的情况下,两个单独的命令都会返回“存在差异”的报告。

您还提到了未跟踪的文件。您的意思可能是“未跟踪和未忽略”,或者您可能只是简单的“未跟踪”(包括被忽略的文件)。无论哪种方式,git ls-files 都是工作的工具:

对于“未跟踪”(将包括被忽略的文件,如果存在):

git ls-files --others

对于“未跟踪和未忽略”:

git ls-files --exclude-standard --others

我的第一个想法是检查这些命令是否有输出:

test -z "$(git ls-files --others)"
如果它以0 退出,则没有未跟踪的文件。如果它以1 退出,则存在未跟踪的文件。

这很有可能会将来自git ls-files 的异常退出转换为“没有未跟踪的文件”报告(两者都导致上述命令的非零退出)。更强大的版本可能如下所示:

u="$(git ls-files --others)" && test -z "$u"
这个想法与前面的命令相同,但它允许来自git ls-files 的意外错误传播出去。在这种情况下,非零退出可能意味着“有未跟踪的文件”或者可能意味着发生了错误。如果您希望“错误”结果与“无未跟踪文件”结果相结合,请使用test -n "$u"(其中0 的退出表示“一些未跟踪文件”,非零表示错误或“没有未跟踪文件”) .

另一个想法是在没有未跟踪的文件时使用--error-unmatch 导致非零退出。这也存在将“没有未跟踪的文件”(退出1)与“发生错误”(退出非零,但可能是128)混为一谈的风险。但是检查01 与非零退出代码可能相当稳健:

git ls-files --others --error-unmatch . >/dev/null 2>&1; ec=$?
if test "$ec" = 0; then
    echo some untracked files
elif test "$ec" = 1; then
    echo no untracked files
else
    echo error from ls-files
fi

如果您只想考虑未跟踪和未忽略的文件,则上述任何 git ls-files 示例都可以采用 --exclude-standard

【讨论】:

我想指出 git ls-files --others 提供 local 未跟踪的文件,而接受答案的 git status --porcelain 提供了 git 下的所有未跟踪文件存储库。我不是原始海报想要的,但两者之间的区别很有趣。 @phunehehe:您需要提供带有--error-unmatch 的路径规范。尝试(例如)git ls-files --other --error-unmatch --exclude-standard .(注意尾随句点,它指的是 cwd;从工作树的***目录运行它)。 @phs:您可能需要在diff-index 之前加上git update-index -q --refresh,以避免由于stat(2) 信息不匹配而导致的一些“误报”。 我被git update-index 的需求给咬了!如果某些东西未经修改就触及文件,这一点很重要。 @RobertSiemer “本地”是指您的 当前目录 下的文件,它可能位于其主 git 存储库下方--porcelain 解决方案列出了在 整个 git 存储库 中找到的所有未跟踪文件(减去 git 忽略的文件),即使您位于其子目录之一中也是如此。【参考方案5】:

假设您使用的是 git 1.7.0 或更高版本...

在阅读了此页面上的所有答案并进行了一些实验后,我认为正确结合简洁的方法是:

test -n "$(git status --porcelain)"

虽然 git 允许在跟踪的内容、忽略的内容、未跟踪但未忽略的内容等之间存在很多细微差别,但我相信典型的用例是自动化构建脚本,如果你的结帐不干净,你想停止一切.

在这种情况下,模拟程序员会做什么是有意义的:输入git status 并查看输出。但是我们不想依赖特定的单词出现,所以我们使用 1.7.0 中引入的--porcelain 模式;启用后,干净的目录不会导致输出。

然后我们用test -n看有没有输出。

如果工作目录是干净的,此命令将返回 1,如果有更改要提交,则返回 0。如果您想要相反,您可以将-n 更改为-z。这对于将其链接到脚本中的命令很有用。例如:

test -z "$(git status --porcelain)" || red-alert "UNCLEAN UNCLEAN"

这实际上是说“要么不做任何改变,要么触发警报”;根据您编写的脚本,这种单行语句可能比 if 语句更可取。

【讨论】:

对我来说,所有其他命令在 Linux 和 Windows 之间的相同代表上给出了不同的结果。这个命令给了我相同的输出。 感谢您回答这个问题,不要胡言乱语,从不提供明确的答案。 对于手动部署脚本,将其与 test -n "$(git diff origin/$branch)" 结合使用,以帮助防止在部署中允许本地提交 我对这个答案的看法。我认为您使用“模拟用户输入git status”方法直截了当。【参考方案6】:

这是一个更适合 shell 的变体,用于找出存储库中是否存在 任何 未跟踪的文件:

# Works in bash and zsh
if [[ "$(git status --porcelain 2>/dev/null)" = *\?\?* ]]; then
  echo untracked files
fi

这不会派生第二个进程grep,并且不需要检查您是否在 git 存储库中。这对于 shell 提示等很方便。

【讨论】:

【参考方案7】:

来自VonC 的回答的实现:

if [[ -n $(git status --porcelain) ]]; then echo "repo is dirty"; fi

【讨论】:

【参考方案8】:

可能有来自这个线程的更好的答案组合......但这对我有用......对于你的.gitconfig[alias]部分......

          # git untracked && echo "There are untracked files!"
untracked = ! git status --porcelain 2>/dev/null | grep -q "^??"
          # git unclean && echo "There are uncommited changes!"
  unclean = ! ! git diff --quiet --ignore-submodules HEAD > /dev/null 2>&1
          # git dirty && echo "There are uncommitted changes OR untracked files!"
    dirty = ! git untracked || git unclean

【讨论】:

【参考方案9】:

我用来检测脏状态的最简单的自动测试 = 任何更改,包括未跟踪的文件

git add --all
git diff-index --exit-code HEAD

备注:

没有add --alldiff-index 不会注意到未跟踪的文件。 通常我在测试错误代码后运行 git reset 以取消暂存所有内容。 考虑--quiet 而不是--exit-code 以避免输出。

【讨论】:

这个问题特别是“来自脚本”......改变索引只是为了测试脏状态不是一个好主意。 @ScottJ,当它解决问题时,并不是每个人都必须严格限制索引是否可以修改。考虑自动化作业的情况,它是关于使用版本号自动修补源并制作标签 - 您需要确保绝对没有其他本地修改会妨碍(无论它们是否在索引中)。到目前为止,这是对包括未跟踪文件在内的任何更改的可靠测试。【参考方案10】:

你也可以这样做

git describe --dirty

。如果它检测到脏工作树,它将在末尾附加单词“-dirty”。根据git-describe(1)

   --dirty[=<mark>]
       Describe the working tree. It means describe HEAD and appends <mark> (-dirty by default) if
       the working tree is dirty.

。警告:未跟踪的文件不被视为“脏”,因为正如手册页所述,它只关心工作树。

【讨论】:

【参考方案11】:

浏览了其中一些答案...(并且在 *nix 和 windows 上遇到了各种问题,这是我的要求)...发现以下效果很好...

git diff --no-ext-diff --quiet --exit-code

检查 *nix 中的退出代码

echo $?   
#returns 1 if the repo has changes (0 if clean)

检查window$中的退出代码

echo %errorlevel% 
#returns 1 if the repos has changes (0 if clean) 

来自https://github.com/sindresorhus/pure/issues/115 感谢@paulirish 在该帖子上的分享

【讨论】:

很好的简短回答。很高兴您还提到了如何在 Linux 和 Windows 中检查退出代码,这节省了另一个 Google 搜索。附注:您可以省略--exit-code,因为--quiet 暗示了它。 如果你的工作副本是脏的,这会返回错误的答案,但你已经暂存了所有的更改以进行提交。 git diff 为空,但工作副本不干净。【参考方案12】:

这是最好、最干净的方法。由于某种原因,所选答案对我不起作用,它没有选择暂存的未提交的新文件的更改。

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

【讨论】:

【参考方案13】:

如果在执行结束时有任何修改过的跟踪文件或任何未被忽略的未跟踪文件,我经常需要一种简单的方法来使构建失败。

这对于避免构建产生剩余的情况非常重要。

到目前为止,我最终使用的最佳命令如下所示:

 test -z "$(git status --porcelain | tee /dev/fd/2)" || \
      echo "ERROR: git unclean at the end, failing build." && return 1 

它可能看起来有点复杂,如果有人找到短路变体,我将不胜感激 这保持了所需的行为:

如果一切正常,则无输出和成功退出代码 如果失败则退出代码 1 stderr 上的错误消息解释了它失败的原因 显示导致失败的文件列表,再次stderr。

【讨论】:

【参考方案14】:

@eduard-wirch 的答案非常完整,但由于我想同时检查两者,这是我的最终变体。

        set -eu

        u="$(git ls-files --others)"
        if ! git diff-index --name-only --quiet HEAD -- || [ -z "$u:-" ]; then
            dirty="-dirty"
        fi

当不使用 set -e 或等效项执行时,我们可以改为执行 u="$(git ls-files --others)" || exit 1(如果这适用于已使用的函数,则返回)

所以 untracked_files,只有在命令正确成功的情况下才会设置。

之后,我们可以检查这两个属性,并设置一个变量(或其他)。

【讨论】:

以上是关于使用 Git 检查脏索引或未跟踪文件的主要内容,如果未能解决你的问题,请参考以下文章

git之commit

git追踪文件对文件进行版本控制-git基础

git 使用操作

git commit -m 与 git commit -am的区别

用于一次性添加(不提交)所有新文件的 Git 命令 [重复]

git status 在内部的工作方式与 git diff 在显示未跟踪文件方面有何不同?