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


243

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

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

从脚本?

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


3
如果存在未暂存的修改文件,则git status将返回1。但是总的来说,我发现git工具对于返回状态并不是特别彻底。EG git diff是否存在差异都返回0。
直觉

11
@intuited:如果需要diff指示是否存在差异而不是成功运行命令,则需要使用--exit-code--quiet。git命令通常与返回零或非零退出代码以指示命令成功非常一致。
CB Bailey 2010年

1
@查尔斯·贝利:嘿,太好了,我错过了这个选择。谢谢!我想我实际上并不需要它,或者我可能会为此寻找手册。很高兴您纠正了我:)
直觉地于2010年

1
罗伯特-对于社区来说,这是一个非常令人困惑的话题(对在不同情况下以不同方式使用“瓷器”并没有帮助)。您选择的答案会忽略2.5倍高投票权的答案,答案会更可靠,并遵循git设计。您看到@ChrisJ的答案了吗?
迈克,

1
@Robert-我希望您可以考虑更改您接受的答案。您选择的一个依赖于更脆弱的git'porcelain'命令;实际的正确答案(也需要2倍赞成票)取决于正确的git'plumbing'命令。正确答案最初是克里斯·约翰森(Chris Johnsen)提出的。我提起这个问题,是因为今天是我第三次不得不推荐某人访问此页面,但是我不能仅指向答案,我已经解释了为什么接受的答案是次优/边界错误。谢谢!
mike

Answers:


173

很好的时机!几天前,当我弄清楚如何在提示符下添加git status信息时,我写了一篇关于此的博客文章。

这是我的工作:

  1. 对于脏状态:

    # Returns "*" if the current git branch is dirty.
    function evil_git_dirty {
      [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]] && echo "*"
    }
  2. 对于未跟踪的文件(请注意提供可解析输出的--porcelain标记git status):

    # 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文件计数。)

编辑

这是帖子:

将Git状态信息添加到您的终端提示符

改进的启用Git的Shell提示符


8
值得注意的是,git bash补全带有一个shell函数,用于执行您的提示符-所做的几乎所有事情__git_ps1。它显示了分支名称,如果您正在进行重新设置,am-apply,merge或bisect的处理,则包括特殊处理。您可以将环境变量设置GIT_PS1_SHOWDIRTYSTATE为对未暂存的更改使用星号,对于暂存的更改使用加号。(我认为您也可以获取它来指示未跟踪的文件,并提供一些git-describe输出信息)
Cascabel 2010年

8
警告:git diff --shortstat如果索引中已有更改,将给出错误的否定。
Marko Topolnik

4
git status --porcelain首选,因为git diff --shortstat它将不会捕获新创建的空文件。您可以在任何干净的工作树中进行尝试:touch foo && git diff --shortstat
Campadrenalin 2014年

7
否-瓷器意味着输出是给人类的,并且很容易损坏!!请参阅@ChrisJohnsen的答案,该答案正确使用了稳定的,脚本友好的选项。
mike 2015年

7
来自git-status手册页的@mike关于瓷器选项:“以易于解析的格式为脚本提供输出。这类似于简短的输出,但是在Git版本和所有用户配置下都将保持稳定。 ”
itsadok 2015年

399

可靠地“脚本化” Git的关键是使用“管道”命令。

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

不幸的是,“日常” Git命令是“瓷器”命令,因此大多数Git用户可能并不熟悉管道命令。瓷器和管道命令之间的区别在主要的git联机帮助页中进行(请参阅标题为“ 高级命令(瓷器)”和“ 低级命令(管道)”的小节


为了找出未完成的更改,您可能需要git diff-index(将索引(和可能跟踪的工作树位)与某些其他树状结构(例如HEADgit diff-files进行比较),可能(将工作树与索引进行比较)以及可能的git ls-files(列表文件;例如未跟踪的列表) ,不可忽略的文件)。

(请注意,在下面的命令HEAD --中使用而不是,HEAD因为如果存在名为的文件,该命令将失败HEAD。)

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

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

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

git diff-files --quiet
  • 退出代码与相同git diff-index0= =无差异;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)混淆的风险。但是检查0vs. 1vs.非零退出代码可能相当健壮:

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


5
我想指出的是,它git ls-files --others提供了本地未跟踪的文件,而git status --porcelain可接受答案的给出了git存储库下的所有未跟踪的文件。我不是原始海报想要的,但是两者之间的区别很有趣。
Eric O Lebigot

1
@phunehehe:您需要提供一个pathspec --error-unmatch。尝试(例如)git ls-files --other --error-unmatch --exclude-standard .(注意尾随时间,它指的是cwd;在工作树的顶级目录中运行它)。
克里斯·约翰森

8
@phs:您可能需要git update-index -q --refresh在进行操作之前diff-index避免由于stat(2)信息不匹配而导致的某些“误报”。
克里斯·约翰森

1
我被git update-index需要咬了!如果某些东西未经修改便触碰文件,则这一点很重要。
Nakedible 2014年

2
@RobertSiemer“本地”是指您当前目录下的文件,该文件可能位于其主git存储库之下。该--porcelain解决方案列出了整个git存储库中找到的所有未跟踪文件(减去git忽略的文件),即使您位于其子目录之一内。
Eric O Lebigot

139

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

阅读完本页上的所有答案并进行了一些实验后,我认为达到正确性和简洁性的正确方法是:

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

虽然git在跟踪,忽略,未跟踪但未被忽略等之间允许有很多细微差别,但我相信典型的用例是使构建脚本自动化,如果结帐不干净,您希望在其中停止所有操作。

在那种情况下,模拟程序员将要做的事情是有意义的:键入git status并查看输出。但是我们不想依赖显示的特定单词,因此我们使用--porcelain1.7.0中引入的模式。启用后,干净目录不会产生任何输出。

然后我们用test -n看是否有任何输出。

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

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

这实际上表示“要么不进行任何更改,要么发出警报”;根据您正在编写的脚本,单行代码可能比if语句更可取。


1
对我来说,所有其他命令在Linux和Windows之间的相同表示上给出不同的结果。此命令在两个方面都给了我相同的输出。
阿达莎(Adarsha)

6
感谢您回答问题,不要一再乱问,永远不要提供明确的答案。
NateS

对于手动部署脚本,请将其与结合使用test -n "$(git diff origin/$branch)",以帮助防止在部署中允许本地提交
Erik Aronesty

14

从实现VonC的回答:

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

8

仔细查看了其中的一些答案...(并且在* 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在该帖子上的分享


4

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

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

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


作为0xFE的他提到了出色的答案git status --porcelain在任何基于脚本的解决方案工具

--porcelain

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


因为我很懒。我认为这是内置的,因为这似乎是一个经常遇到的用例。
罗伯特·蒙提亚努

我根据您的建议发布了一个解决方案,尽管我对此不太满意。
罗伯特·蒙提亚努

4

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

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

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


6
这样做的问题是,您不能在将来的版本中可靠地期望字符串“工作目录干净”。--porcelain标志用于解析,因此更好的解决方案是:exit $(git status --porcelain | wc -l)
0xfe 2010年

@ 0xfe-您知道何时--porcelain添加标志吗?不适用于1.6.4.2。
罗伯特·蒙提亚努

@罗伯特:git status --short然后尝试。
VonC

3
git status --porcelaingit status --short在1.7.0中引入。--porcelain专门引入它是为了允许git status --short将来更改其格式。因此,git status --short将遭受与git status(将输出随时更改,因为它不是“管道”命令)相同的问题。
克里斯·约翰森

@Chris,感谢您提供背景信息。我已经更新了答案,以反映从Git 1.7.0开始的最佳方法。
罗伯特·蒙蒂亚努

2

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

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

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

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

它可能看起来有点复杂,如果有人发现可以保持所需行为的简短变体,我将不胜感激:

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

2

@ 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。

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


1

这是一种对外壳更友好的变体,用于查找存储库中是否存在任何未跟踪的文件:

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

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


1

你也可以

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.

。注意:未跟踪的文件不视为“脏文件”,因为如联机帮助页所述,它仅关心工作树。


0

这个线程可能会有更好的答案组合。.但这对我有用...在您.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

0

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

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

注意:

  • 如果没有,add --all diff-index则不会注意到未跟踪的文件。
  • 通常,我会git reset在测试错误代码后运行,以使一切恢复原状。

这个问题特别是“来自脚本” ...更改索引以测试脏状态不是一个好主意。
ScottJ

@ScottJ,当解决问题时,并不是每个人都严格要求是否可以修改索引。考虑一下自动化作业的情况,该作业涉及带有版本号的自动补丁源并创建标签-您需要确保的是绝对没有其他本地修改被保留(无论它们是否在索引中)。到目前为止,这是对包括未跟踪文件在内的任何更改的可靠测试。
uvsmtid

-3

这是最好,最干净的方法。选定的答案由于某种原因对我不起作用,它没有获取已上演的未提交新文件的更改。

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
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.