【问题标题】:How do I programmatically determine if there are uncommitted changes?如何以编程方式确定是否有未提交的更改?
【发布时间】:2010-10-07 04:16:13
【问题描述】:

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

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

【问题讨论】:

标签: git


【解决方案1】:

更新:OP Daniel Stutzbach 指出in the comments 这个简单的命令git diff-index 对他有用:

git update-index --refresh 
git diff-index --quiet HEAD --

更精确的选择是使用porcelain option 测试git status --porcelain=v1 2>/dev/null | wc -l
Myridiumanswer

(nornagon提到in the comments,如果有文件被触及,但内容与索引相同,则需要在git diff-index之前运行git update-index --refresh,否则@987654349 @ 会错误地报告树是脏的)

如果您在 bash 脚本中使用它,则可以看到“How to check if a command succeeded?”:

git diff-index --quiet HEAD -- || echo "untracked"; // do something about it

注意:commented Anthony Sottile

git diff-index HEAD ... 将在没有提交的分支上失败(例如新初始化的存储库)。
我发现的一种解决方法是git diff-index $(git write-tree) ...

并且haridsv 指出in the comments 文件上的git diff-files 不会将其检测为差异。
更安全的方法似乎是先在文件规范上运行git add,然后使用git diff-index 在运行git commit 之前查看是否有任何内容添加到索引中。

git add ${file_args} && \
git diff-index --cached --quiet HEAD || git commit -m '${commit_msg}'

6502 在 cmets 中报告:

我遇到的一个问题是,git diff-index 会告诉我存在差异,而实际上除了文件的时间戳之外没有其他差异。
运行git diff 一次即可解决问题(令人惊讶的是,git diff 确实改变了沙箱的内容,这里的意思是.git/index

如果 git 是 running in docker,也会出现这些时间戳问题。


原答案:

“以编程方式”表示永远不要依赖porcelain commands
始终依赖plumbing commands

另请参阅“Checking for a dirty index or untracked files with Git”了解替代方案(如git status --porcelain

您可以从中汲取灵感from the new "require_clean_work_tree function" which is written as we speak ;)(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
}

【讨论】:

  • 脚本的“管道与瓷器”原则是Jakub Narębski反复向我提到的一个教训:“How to list all the log for current project in git ?”、“git: changelog day by day”、...
  • 点击你建议的一些链接后,我找到了我要找的东西:git diff-index --quiet HEAD
  • @DanielStutzbach:如果工作目录中有一个名为 HEAD 的文件,这可能会失败。最好使用git diff-index --quiet HEAD --
  • 然而git status --help 的手册指出:--porcelain 以易于解析的脚本格式提供输出。这与短输出类似,但在 Git 版本之间以及无论用户配置如何都将保持稳定。详情见下文。
  • @VonC 这真的没有意义。这样你就可以把所有的东西都扭转过来。 ——瓷器给你的印象是它很快就会坏掉。如果不是,它应该被称为管道,而不是瓷器。使用 --porcelain 会使您的脚本不会中断,这使其不是瓷器脚本;-)。如果你想让你的脚本中断,你不应该使用--porcelain!!。所以这完全无法理解,把所有人都甩了。
【解决方案2】:

虽然其他解决方案非常彻底,但如果您想要快速而肮脏的东西,请尝试以下方法:

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

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

【讨论】:

  • 为我工作。使用 -n 逆(你有变化)例如` if [[ -n $(git status -s) ]];然后... fi`
  • @EM git status 的返回码在这个测试中实际上被忽略了。它只查看输出。查看this bash related page 了解更多关于[[[ 以及如何在bash 中进行测试的信息。
  • 这几乎是正确的答案,但对于脚本,最好使用--porcelain 参数,如图here
  • 您可能想使用git status -s -uall 来包含未跟踪的文件。
  • @barfuin 它看起来像 git status 默认为 all。来自git status -h-u, --untracked-files[=<mode>]show untracked files, optional modes: all, normal, no. (Default: all)
【解决方案3】:

git diff --exit-code 如果有任何更改,将返回非零值; git diff --quiet 相同,没有输出。由于您要检查工作树和索引,请使用

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

或者

git diff --quiet HEAD

任何一个都会告诉您是否有未提交的更改已暂存。

【讨论】:

  • 那些不是等价的。单个命令git diff --quite HEAD 只会告诉您工作树是否干净,而不是索引是否干净。例如,如果file 在 HEAD~ 和 HEAD 之间更改,那么在git reset HEAD~ -- file 之后,即使索引中存在阶段性更改(wt == HEAD,但索引!= HEAD),它仍将退出 0。
  • 警告,这不会捕获使用 git rm、AFAICS 从暂存区删除的文件。
  • git diff --quiet && git diff --cached --quiet 未检测到新的(未跟踪的)文件。
【解决方案4】:

扩展@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 status -s "$file") 然后在else 子句git add "$file"; git commit -m "your autocommit process" "$file" 中使用它来自动提交单个文件
  • 如果你将git status -s 改为git status --porcelain ; git clean -nd,垃圾目录也会出现在这里,git status 看不到这些目录。
【解决方案5】:

有些答案既使事情变得过于复杂,也没有达到预期的结果。例如。接受的答案错过了未跟踪的文件。

使用提供的git status --porcelain,它设计为可机器解析,尽管有些人(错误地)在 cmets 中另有说明。如果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 版本的输出格式一致。


额外:计算脏文件

灵感来自this answer。你grepgit status --porcelain=v1 的行输出。每行的前两个字符表示特定文件的状态。在 grepping 之后,您可以通过将输出传送到计算行数的 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

【讨论】:

  • 对我在stackoverflow.com/a/6978402/6309 中介绍的瓷器选项的有趣使用。这似乎比我的回答更准确。赞成。
  • @VonC - 事实上,我检查了文档git-scm.com/docs/git-status 并注意到版本选项。已经有 v2,但它不是默认的。另外我知道其他人已经在 cmets 的其他地方引用了这个,但它提到了 --porcelain 选项:“以易于解析的格式为脚本提供输出。这类似于短输出,但无论用户配置如何,都会在 Git 版本中保持稳定。”
  • 是的,v2 已在 Git 2.11 (github.com/git/git/commit/…) 中引入,8 月。 2016.
【解决方案6】:

我创建了一些方便的 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'

【讨论】:

    【解决方案7】:

    使用 python 和 GitPython 包:

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

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

    【讨论】:

    • 这避免了其他 cmets 中提到的一些“时间戳”问题
    • 请注意,GitPython 也只是使用 git CLI。如果你设置了LOGLEVEL=DEBUG,你会看到它用来运行git diff的所有Popen命令
    • 假设包经过良好测试,如果/当 Git 发生变化时,让包维护者保持功能正常工作更可靠。 GitPython 的文档表明它可能会泄漏资源,这引起了我的担忧。
    【解决方案8】:

    如果工作树是“干净的”

    git ls-files --deleted --modified --others --exclude-standard
    

    什么都不返回。

    说明

    • --deleted 检查工作树中删除的文件
    • --modified 检查工作树中修改的文件
    • --others 检查添加到工作树中的文件
    • --exclude-standard 按照通常的.gitignore, .git/info/exclude ... 规则忽略

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

    【讨论】:

      【解决方案9】:

      正如其他答案所指出的那样,这样的命令就足够了:

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

      注意事项:此命令会忽略未跟踪的文件。

      【讨论】:

      • 正如 cmets 中指出的那样,这不会检测到新添加的文件
      • 不,它确实检测到新添加到索引文件中。刚刚检查过。
      • 查看问题。未跟踪的文件不是更改git addgit clean 救援
      【解决方案10】:

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

      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 status 是一个“瓷器”命令。不要在脚本中使用瓷器命令,因为它们可以在 git 版本之间更改。而是使用“管道”命令。
      • 我想如果你更新它以使用git status --porcelain (这是为此目的——一种你可以在脚本中解析的稳定格式),也可能使用 -z (空分隔而不是换行符?)你可以用这个想法做一些有用的事情。 @codyc4321 详见stackoverflow.com/questions/6976473/…
      猜你喜欢
      • 2021-03-14
      • 1970-01-01
      • 1970-01-01
      • 2011-02-18
      • 2016-09-16
      • 1970-01-01
      • 1970-01-01
      • 2010-11-11
      • 1970-01-01
      相关资源
      最近更新 更多