如何在Bash脚本中检查本地Git仓库是否有更改?

283

有些脚本在检查更改时无法正确工作。

我尝试了这样:

VN=$(git describe --abbrev=7 HEAD 2>/dev/null)

git update-index -q --refresh
CHANGED=$(git diff-index --name-only HEAD --)
if [ ! -z $CHANGED ];
    then VN="$VN-mod"
fi

是否有一种布尔检查方法可以检测从上次提交以来是否有更改,或者我该如何测试本地仓库中是否有新的更改?

我正在为一个版本创建脚本而做所有这些事情(我在某个地方找到了这个脚本)。


3
“git status”有什么问题? - karlphillip
8
它进行了很多你实际上不需要的处理。 - Cascabel
7
这是一个“瓷器”命令,意思是:不适合在脚本中使用,因为其输出设计用于阅读,可能会因版本或本地化而发生变化。 - Andrew Spencer
17个回答

508

使用 git status 命令:

cd /git/directory
if [[ `git status --porcelain` ]]; then
  # Changes
else
  # No changes
fi

27
最佳答案,可惜来自“shell和git架构师”的回答得到更高的投票。 - jwg
6
这很棒,因为它考虑了未版本化的文件,并且使用了瓷质命令,这样应该更兼容不同的git版本。 - Jayd
58
忽略未跟踪的文件:如果 [[ git status --porcelain --untracked-files=no ]]; then - storm_m2138
7
检查本地更改 -> echo 已更改 else echo 未在本地更改 fi``` - Carlos Saltos
9
这段代码的作用是:执行命令 git status --porcelain,检查输出是否为空。如果不为空,则表示存在更改。 - bhathiya-perera
显示剩余5条评论

234

你现在的做法几乎可行:如果$CHANGED为空,你应该加上引号。使用-z测试空值,表示没有更改。你想表达的是:

if [ -n "$CHANGED" ]; then
    VN="$VN-mod"
fi

Git的中的一句话引用:
git update-index -q --refresh
test -z "$(git diff-index --name-only HEAD --)" ||
VN="$VN-dirty"

看起来你在复制内容时忘记了加上引用标识符。

当然,你也可以这样做:

if git diff-index --quiet HEAD --; then
    # No changes
else
    # Changes
fi

或者如果你只关心“有什么变化”这种情况:

if ! git diff-index --quiet HEAD --; then
    VN="$VN-mod"
fi

使用--quiet的好处是,Git可以在遇到单个差异时立即停止处理,因此它可能不必检查您的整个工作树。


2
嗨,那是一个问题中最好的答案之一,你给了我所有需要的信息,其他人可能也需要:)。我只需要最后一个,“有些东西已经改变” :),你是对的,我复制了它。 - kmindi
14
这个令人惊奇的bash自动补全脚本,似乎使用git diff --no-ext-diff --quiet --exit-code来确定未保存的状态。 - mjs
3
@mjs:这确实是寻找此类内容的好地方! --no-ext-diff 选项对于安全性很好(以防某人配置了外部差异驱动程序),但是 --exit-code 不应该是必要的,因为它已被 --quiet 包含。 - Cascabel
6
这对我没用,因为它没有报告未被跟踪的文件。 - Cookie
3
git diff-index 报告更改,即使只有文件修改时间已更改(而不是它们的内容)。如果您“touch”一个文件,则会报告修改,运行 git status 将重置。以下使用 git status 更好。 - Sampo
显示剩余4条评论

22

这也可以运作:

if [ $(git status --porcelain | wc -l) -eq "0" ]; then
  echo "   Git repo is clean."
else
  echo "   Git repo dirty. Quit."
  exit 1
fi

21

虽然Jefromi的答案不错,但我只是为了参考而发布这个回复。

从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
}

1
参考一下,如果我理解正确的话,$1 是一个字符串,用于命名您想要运行的任务,而 $2 是一个可选的字符串,用于在失败时包含自定义错误消息。例如,像这样调用它:require_clean_work_tree deploy "Skipping deploy, clean up your work tree or run dev-push" - Umbrella

11

我查看了数十个Stack Overflow的答案,但都没有满足我的期望。也就是说,如果符合以下条件之一,则应出现脚本错误:

  • 当前本地状态与origin/master有任何差异(包括:对现有文件的更改;新的未提交和已提交的文件以及文件删除),或者
  • master有提交尚未推送到origin/master

最终脚本可像这样使用:

if ! (git diff --exit-code origin/master..master > /dev/null) \
  || ! (git diff --exit-code master > /dev/null) \
  || ! [[ -z "$(git status --porcelain)" ]] ; then
  echo "Your local repo has some changes that aren't pushed to origin/master ."
  exit 1
fi

说明:

  • git status --porcelain将呈现尚未提交的文件列表。 用 [[ -z "$(...)" ]] 包装使其在命令返回非空字符串时出错。
  • git diff --exit-code master > /dev/null将在你当前提交的存储库状态与master分支不同的情况下出错。
  • git diff --exit-code origin/master..master > /dev/null将在你当前的master分支与origin/master不同的情况下出错。

如果您要确保您的存储库可能安全删除/清除,这很有用。

为什么更好?

  • 仅依赖于git diff --exit-code ...的答案将不会在您有新的未提交或未存储的文件时出错;
  • 仅依赖于git status --porcelain的答案将不会告诉您存在未推送到远程的提交。

如果一个新的仓库使用main,则在所有地方将master替换为main - Gabriel

8

git status 是你的好朋友

要使 git status 正常工作,需要先切换到 Git 目录:

cd c:/path/to/.git

设置一个变量来设置工作目录,这样您就不会出现“必须在工作树中运行此操作”错误:

WORKTREE=c:/path/to/worktree

git status的输出结果存储在Bash变量中

使用--porcelain参数,它保证以标准格式且可解析的方式输出:

CHANGED=$(git --work-tree=${WORKTREE} status --porcelain)

如果-n(非空),则表示我们有更改。
if [ -n "${CHANGED}" ]; then
  echo 'changed';

else
  echo 'not changed';
fi

1
这样做的好处是可以检测到未跟踪的文件。 - Luiz C.

8

我也遇到过类似的问题,但是我还需要检查是否添加了文件。因此我做了以下操作:

cd /local/repo
RUN=0
git diff --no-ext-diff --quiet --exit-code || RUN=1
if [ $RUN = 0 ]; then
    RUN=`git ls-files --exclude-standard --others| wc -l`
fi

if [ $RUN = 0 ]; then
    exit 0
fi

注意:--quiet选项意味着--exit-code,因此git diff --no-ext-diff --quiet应该足够(至少在最新的git上)。请参考:https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---quiet - Yuki Inoue

7

这个问题已经超过9年了。我不知道当时 man git-status 显示的是什么内容,但现在它显示的是:

--porcelain[=<version>]  
Give the output in an easy-to-parse format for scripts. This is similar to the 
short output, but will remain stable across Git versions and regardless of user 
configuration. See below for details.  

The version parameter is used to specify the format version. This is optional and 
defaults to the original version v1 format.  

这表明--porcelain参数非常适合用于测试存储库的更改状态。

关于OP的问题,即“是否有某种布尔检查可以检测自上次提交以来是否有更改,或者如何真正测试本地存储库中是否有新更改?”

我认为bash本身没有布尔数据类型,但这可能已经足够接近了:

[ -z "`git status --porcelain`" ] && echo "NULL-NO DIFFS" || echo "DIFFS EXIST"

这可以重新构建为一个脚本的if-then-else形式,或者在 git 存储库文件夹 中执行。否则,请使用-C选项以路径规范指定感兴趣的存储库:

git -C ~/path/to/MyGitRepo status --porcelain 

补充:

  1. 一些人建议使用-u, --untracked-file选项来避免报告忽略的文件状态。请注意,这会带来一个不幸的副作用: 新添加的文件也不会被列出状态。该选项在某些情况下很有用,但在使用之前请仔细考虑。

7

我的看法:

git status --porcelain | head -1

只返回一行而不是一长串列表


3

如果你只关心工作树的变化而不是索引,那么请使用下面的代码。提醒一下那些不太熟悉特定git术语的人,这意味着所有更改都已经被git add并且执行git diff将不会显示任何内容。(git diff --cached将显示您已经add但尚未commit的更改。)

if [[ $(git status -z | cut -z -b 2 | tr -d '[:space:]\000') ]]; then
  # Changes
else
  # No changes
fi

git status -z 是使用空字符分隔符而不是换行符的 --porcelaincut -z -b 2 从中获取每个字节的第二个字节(表示对工作树的更改)。tr -d '[:space:]\000' 删除所有空格和空字节,只留下对工作树的更改。除此之外,它与 this 相同,如果这是 empty,则 工作树中没有更改

这很有用,因为索引中的更改(您已经 git add)在某种意义上被“保存”了。您可以在工作树中进行操作并将其恢复。


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