防止在git中提交具有合并冲突的文件

27

有没有办法防止在Git中提交冲突的文件?没有人会故意提交有冲突的文件。但是,有没有一种方法可以防止Git提交这些文件?

Git是否具有任何设置或可配置的值,可以通过查找<<<<<<<=======>>>>>>>符号来防止文件被提交?


2
git rev-parse -q --verify HEAD && sed -n '/^<<<<<<< /Q1' $(git diff --diff-filter=M --name-only HEAD $(git write-tree)) || { echo "It looks like rain."; exit 1; } 添加到 .git/hooks/pre-commit 中。这是从键盘到编辑框的过程,但应该基本正确。 - jthill
Git插件是否提供这种限制? - user3586664
@jthill pre-commit正常工作,但我在pre-receive中使用了相同的条件,结果失败了。我在eclipse中使用egit提交文件,但服务器上有pre-receive,在推送冲突文件时,pre-receive钩子被调用,但仍然继续推送。我希望该文件不会被推送。 - user3586664
@jthill:看起来这是一个不错、简洁的解决方案。为什么不把它发布为答案呢?评论可能会在一段时间后被删除。 - sleske
1
@jthill:你能否无论如何将它发布为答案?许多人会直接使用git,而不是Eclipse。VonC的答案对我不起作用,除非我追踪一些Perl依赖项。我也不需要额外的console.log和其他检查。 - ksenzee
显示剩余2条评论
5个回答

12

VonC的回答已经解释了可能需要检查合并提交的不同种类钩子。

如果你只想要一个简单的解决方案来避免提交冲突,git中默认的pre-commit hook示例已经包含了这个功能。只需通过将.git/hooks/pre-commit.sample重命名为.git/hooks/pre-commit启用这个hook。然后,如果你试图提交一个冲突:

$ git commit -am "Fix crash in frobnicate"
src/foo.c:239: leftover conflict marker

注意:

该脚本在内部使用git diff --check,它还会检查各种空格问题 - 所以您可能也会遇到空格错误。您也可以在提交之前运行git diff --check以查找问题。有关详细信息和配置选项,请参见git diff的手册。

这适用于git V2.0;不知道是何时引入的。


2
我得到的预提交挂钩示例涉及提交的文件名中的非ASCII字符。你能给出无冲突示例的内容吗? - Jānis Elmeris
@Janis:没错,这就是正确的方法。该脚本调用了“git diff-index --check”命令,它会检查冲突标记。你可以试试看 :-). - sleske
1
我知道你很久以前就对此做出了反应,但是在我的pre-commit.sample中没有git diff-index --check;即使激活了它,也无法检测到剩余的合并标记 :( - Confused Merlin
@ConfusedMerlin:嗯,预设的 pre-commit hook 仍然包含 git diff-index --check 命令:https://github.com/git/git/blob/master/templates/hooks--pre-commit.sample。尝试创建一个新的 repo(git init)。如果你仍然没有得到 pre-commit sample hook,请提出一个新的问题 :-)。 - sleske
4
啊,我发现它就在我的终端之外,唉。但无论如何,即使启用了那个钩子,我仍然可以愉快地提交合并标记 :( 所以,是的,应该有一个新问题。 - Confused Merlin
@ConfusedMerlin,请提供新问题的链接。 - Mohit Kanwar

7
您可以使用pre-commit hook,但请注意,git commit --no-verify将有效地忽略它。
通常我会放置一个pre-receive hook以便在(更)中央的位置控制推送的内容。
但是pre-commmit允许更及时地检测(在开发周期的早期)。
这里是另一个示例(除了jthill评论之外),使用perl。
它使用git diff-index -p -M --cached HEAD,即git diff-index而不是git diff
我留下了此挂钩执行的其他一些控件,以展示您可以在此类脚本中执行的检查类型。
#!/usr/bin/perl

use Modern::Perl;
use File::Basename;

my $nb_errors = 0;
my $filepath;
for my $l ( split '\n', `git diff-index -p -M --cached HEAD` ) {
    if ( $l =~ /^diff --git a\/([^ ]*) .*$/ ) {
        $filepath = $1;
    }
    given ( $l ) {
        # if there is a file called *.log, stop
        when ( /\.log/ ) {
            say "$filepath contains console.log ($l)";
            $nb_errors++;
        }
        # check if there is a warn, that could be unconditionnal, but just warn, don't stop
        when ( /^[^(\#|\-)]+warn/ ) {
            # stay silent if we're in a template, a "warn" here is fair, it's usually a message or a css class
            unless ($filepath =~ /\.tt$/) {
            say "$filepath contains warn ($l)";
            }
        }
        # check if there is a ` introduced, that is a mysqlism in databases. Can be valid so don't stop, just warn
        when (/^\+.*`.*/) {
            say "$filepath contains a ` ($l)";
        }
        # check if there is a merge conflict marker and just warn (we probably could stop if there is one)
        when ( m/^<<<<<</ or m/^>>>>>>/ or m/^======/ ) {
            say "$filepath contains $& ($l)";
        }
    }
}

if ( $nb_errors ) {
    say "\nAre you sure you want to commit ?";
    say "You can commit with the --no-verify argument";
    exit 1;
}
exit 0;

使用Eclipse的git插件,我能够提交文件并推送,同时在钩子文件夹中使用pre-commit和pre-push文件。但是使用git bash时,文件无法提交或推送。难道git插件不提供限制功能吗? - user3586664
如果您使用git add和git commit,文件将在git bash中提交。 - VonC
也许我没有表达清楚,我的命令是在文件中遇到"<<<<"模式时阻止文件被提交。问题是,在使用Git Bash时,文件不会被提交,这是预期的结果。但是在使用Eclipse的Git插件时,限制失败了,这意味着文件被提交并推送了。有没有办法在Git插件上应用此限制? - user3586664
@user3586664 但是客户端钩子可能尚未得到支持:http://dev.eclipse.org/mhonarc/lists/egit-dev/msg02578.html - VonC
@user3586664 这段代码用于从 git diff 的输出中分离出文件路径:如果输出以 (^) diff --git a/ 开始,则其余部分是文件路径(直到下一个空格)。 - VonC
显示剩余5条评论

3

一个简单的方法是使用预提交挂钩,在这里进行了改编,以使其更加谨慎和彻底:

#!/bin/sh

changed=$(git diff --cached --name-only)

if [[ -z "$changed" ]]; then
    exit 0
fi

echo $changed | xargs egrep '^[><=]{7}( |$)' -H -I --line-number

# If the egrep command has any hits - echo a warning and exit with non-zero status.
if [ $? == 0 ]; then
    echo "WARNING: You have merge markers in the above files. Fix them before committing."
    echo "         If these markers are intentional, you can force the commit with the --no-verify argument."
    exit 1
fi

请不要忘记将钩子设为可执行 (chmod u+x pre-commit)!
我已经将这个内容放在了github上:https://github.com/patrickvacek/git-reject-binaries-and-large-files/blob/master/pre-commit

1

客户端解决方案是使用git pre-commit hooks,当您提交时(合并是一种提交类型),它允许您执行特定命令,如果失败,则无法合并,直到解决为止。

pre-commit是管理git pre-commit hooks的简单和标准化的方法。

在您的存储库根目录中创建一个名为.pre-commit-config.yaml的文件 具有以下内容

# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.0.1
    hooks:
    -   id: check-merge-conflict
安装 pre-commit

(首选方式是)使用 pipx 或在非虚拟环境中执行此操作,因为它是一个 CLI 应用程序。

python3 -m pip install pre-commit 

安装钩子。
pre-commit install # you only do this once per "git clone"

下一次合并时,如果检测到冲突标记,它会发出警告并阻止您继续。
$ git merge --continue
[INFO] Checking merge-conflict files only.
Check for merge conflicts................................................Failed
- hook id: check-merge-conflict
- exit code: 1

Merge conflict string "<<<<<<< " found in README.rst:1
Merge conflict string "=======
" found in README.rst:3
Merge conflict string ">>>>>>> " found in README.rst:5

0

我添加了一个单元测试,以便遍历解决方案目录中的所有文件,查找冲突标记字符串

[TestClass]
public class SolutionValidationTests
{
    [TestMethod]
    public void CheckForMergeConflicts()
    {
        var solutionValidationScripts = new SolutionValidationScripts();
        var mergeConflictCheckOkay = solutionValidationScripts.CheckForGitMergeConflict();
        Assert.IsTrue(mergeConflictCheckOkay);
    }
}

解决方案验证脚本在下面单独定义:

public class SolutionValidationScripts
{
    public bool CheckForGitMergeConflict()
    {
        var failCount = 0;
        System.Diagnostics.Debug.WriteLine($"{DateTime.Now.ToString(@"dd-MMM-yyyy HH:mm:ss")}: Starting");

        var currentDirectory = System.IO.Directory.GetCurrentDirectory();
        var sourceFolder = "";

        // Change to suit the build location of your solution
        sourceFolder = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(System.IO.Directory.GetCurrentDirectory())));
        // break up the string so this file doesn't get flagged in the test
        string searchWord = "<<<<<<< " + "HEAD";

        List<string> allFiles = new List<string>();
        AddFileNamesToList(sourceFolder, allFiles);
        for (int i = 0; i < allFiles.Count; i++)
        {
            // 35 sec
            var fileName = allFiles[i];
            string contents = File.ReadAllText(fileName);
            if (contents.Contains(searchWord))
            {
                // For faster result.. no need to continue once a problem is found
                // throwing an exception here actually works better than an early return to help resolve the issue
                throw new Exception(fileName);
            }
        }
        return (failCount == 0);
    }

    private void AddFileNamesToList(string sourceDir, List<string> allFiles)
    {
        string[] fileEntries = Directory.GetFiles(sourceDir);
        foreach (string fileName in fileEntries)
        {
            allFiles.Add(fileName);
        }

        //Recursion    
        string[] subdirectoryEntries = Directory.GetDirectories(sourceDir);
        foreach (string item in subdirectoryEntries)
        {
            // Avoid "reparse points"
            if ((File.GetAttributes(item) & FileAttributes.ReparsePoint) != FileAttributes.ReparsePoint)
            {
                AddFileNamesToList(item, allFiles);
            }
        }
    }
}

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