我该如何在一个Shell脚本中编程来判断是否有更改发生?

9
我将尝试创建一个Bash脚本,以检测当前工作目录中是否有更改。我知道:
$ git status

返回一个类似于“没有可提交的内容”的消息。我想要做的是定义一个变量为真或假。这个布尔值将告诉我是否存在变更。

很明显,我不是bash脚本专家。我尝试了以下代码:

there_are_changes=$(git status | grep nothin)
echo $there_are_changes

但是它没有按预期工作。我该怎么办?


你有Python吗?如果有的话,像这样的脚本可能更容易用Python编写。 - amahfouz
可能是重复的问题:如何以编程方式确定是否存在未提交的更改? - stiemannkj1
我曾试图删除问题,但是我读到了非常非常非常有用的答案。我会保留它! - sensorario
3个回答

35

git-diff man page 文档中描述了两个相关的选项:

--quiet
Disable all output of the program. Implies --exit-code.

以及

--exit-code
Make the program exit with codes similar to diff(1). That is, it
exits with 1 if there were differences and 0 means no differences.

因此,一个健壮的方法是运行

git diff --quiet; nochanges=$?

如果没有更改,shell变量nochanges将等于0(即为真),否则将等于1(即为假)。

然后,您可以按照以下方式在条件语句中使用nochanges的值:

if [ $nochanges -eq 0 ]; then
    # there are no changes
else
    # there are changes
fi

或者,如果您不需要将退出状态存储在变量中,则可以执行:

或者,若无需将退出状态存于变量中,则可如下操作:

if git diff --quiet; then
    # there are no changes
else
    # there are changes
fi

由于git diff是Git的一个外部命令,如果您想以编程方式执行操作,应该使用Git的另一个内部命令git diff-index(它也有一个--quiet标志,但必须提供一个"tree-ish"参数):

if git diff-index --quiet HEAD; then
    # there are no changes
else
    # there are changes
fi

如下评论所指出,上述方法未覆盖未跟踪文件。为了也涵盖它们,您可以改用以下方法:

if [ -z "$(git status --porcelain)" ]; then
    # there are no changes
else
    # there are changes
fi

1
我之前不知道 git diff 命令中的 --quiet 参数,这真是太棒了! - jmervine
由于在设置为在非零退出代码后停止的脚本中,git diff --quiet; nochanges=$? 会停止执行,因此您可以改用 CHANGES=false; (git diff --quiet) || CHANGES=true - Alasdair McLeay
我们如何仅检查更改?即避免使用 else - Richard
这种方法无法检测未跟踪的文件。要检查创建/修改/删除,您应该检查git status --porcelain是否输出文本(例如通过if [[ -n "$(git status --porcelain)" ]]; then echo "things changed"; fi)。 - jeremysprofile
1
@jeremysprofile 好观点。我已经相应地修改了我的答案。 - jub0bs
@jub0bs,当我运行 git diff-index --quiet HEAD 时,即使我已经将所有文件都暂存了,我仍然会遇到未暂存的错误。取消暂存并重新暂存它们仍然会导致 git diff-index --quiet HEAD 错误。所以,Aaron 的解决方案是唯一有效的。 - SwiftiSwift

6
您可以使用 -n 表达式来检查变量是否已设置。
#!/bin/bash
CHANGESTOCOMMIT=$(git status | grep 'Changes to be com')
UNSTAGEDCHANGES=$(git status | grep 'Changes not staged')

# If there are staged changes:
if [ -n "$CHANGESTOCOMMIT" ]; then
    echo "Changes need to be committed"
fi
if [ -n "$UNSTAGEDCHANGES" ]; then
    echo "Changes made but not staged."
fi

Git跟踪已提交的暂存文件和未暂存的文件,因此您的脚本可能需要检查这两个选项(或不检查)。 -n 操作符用于检查变量是否已设置 - 如果为空,则返回false。
另一个选项是-z,如果为空则返回True(与-n的逻辑相反)。有关条件表达式的完整列表,请参见Bash参考手册

是的,对于这个问题来说,踩并没有什么意义。@Jubobs的回答更简洁明了,但我给你点了个赞。 - jmervine
我把“-z”和“-n”搞混了,所以我的脚本出现了逻辑错误。现在已经修复了。 - Aaron D
1
那个命令替换不够优雅; git diff --quiet 已经提供了用户所需的功能。 - jub0bs
1
你说得对,你的解决方案更加优雅适用于这种情况。问题是如何在bash中检查变量是否设置为true或false,但是你的解决方案完全避开了需要使用grep的步骤。 - Aaron D
@AaronD 谢谢!这解决了我的问题,即使我已经编辑过已经暂存的文件,运行 git diff-index --quiet HEAD 仍然会出现未暂存错误。取消暂存并重新暂存它们仍然会给我带来 git diff-index --quiet HEAD 的错误。所以 Aaron 的解决方案是唯一有效的。 - SwiftiSwift

-2
使用此脚本,您可以检查未在提交前暂存的更改。如果有更改,则脚本将中止并退出。这适用于任何情况,并且是比Aaron D的答案更安全的检查方式,因为如果您使用其他语言进行git操作,则该检查将失败,并且即使您实际上有未暂存的文件,也会显示您没有未暂存的文件。
因此,请使用以下命令:
#!/bin/sh

# Check for changes not staged for commit
git diff --cached | grep -v ^$ > /dev/null

# If there are changes, exit with an error code
if [ $? -ne 0 ]; then
    echo "There are changes not staged for commit."
    exit 1
fi

# Check for changes to already staged files
git diff --cached --name-only | while read file; do
    if [ -f "$file" ]; then
        # Check if there are unstaged changes to the file
        if git diff --name-only "$file" | grep -v ^$ > /dev/null; then
            echo "There are unstaged changes to the file '$file'."
            exit 1
        else
            echo "Good to go! Commit now"
            exit 0
        fi
    fi
done

说明:

提供的代码是一个预提交钩子,它检查未暂存的更改、已经暂存的文件的更改以及已经暂存的文件的未暂存更改。

该钩子首先使用 git diff --cached 命令检查未暂存的更改。如果有任何更改,钩子将打印一条消息并退出错误代码。

如果没有未暂存的更改,则钩子将使用 git diff --cached --name-only 命令检查已经暂存的文件的更改。如果有任何修改过的文件,钩子将打印一条消息并退出错误代码。

然后,钩子将检查已修改的文件中是否有任何未暂存的更改。如果有任何未暂存的更改,钩子将打印一条消息并退出错误代码。

如果没有对已经暂存的文件进行更改或者已经暂存的文件的未暂存更改,钩子将以成功代码退出。 通常情况下,该钩子将检查您的文件是否有未提交的更改。如果有任何更改,钩子将打印一条消息并退出错误代码。这将防止您提交未暂存的更改。

以下是如何使用此钩子的示例:

  1. 创建一个名为* git/hooks/pre-commit的文件
  2. 将上面的代码复制到文件中
  3. 使文件可执行。
  4. 下一次尝试提交时,pre-commit钩子将检查未暂存的更改,已暂存文件的更改以及已暂存文件的未暂存更改。 如果有任何更改,钩子将打印一条消息并退出错误代码。

我实际上使用它来避免将未通过Flutter单元测试的代码推送到git中,就像这样:

#!/bin/sh

# Check for changes not staged for commit
git diff --cached | grep -v ^$ > /dev/null

# If there are changes, exit with an error code
if [ $? -ne 0 ]; then
    echo "There are no changes or some changes are not staged for commit."
    git status
    exit 1
fi

# Check for changes to already staged files
git diff --cached --name-only | while read file; do
    if [ -f "$file" ]; then
        # Check if there are unstaged changes to the file
        if git diff --name-only "$file" | grep -v ^$ > /dev/null; then
            echo "There are unstaged changes to the file '$file'."
            exit 1
        else 
            flutter test

            # If the unit tests fail, exit with a non-zero exit code.
            if [[ $? -ne 0 ]]; then
                echo "Unit tests failed. Aborting commit."
                exit 1
            fi
        fi
    fi
done

为什么测试"$?"来判断命令是否成功是一种反模式? - tripleee

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