25

Is there any way of preventing files with merge conflict from getting committed in git? No one is going to commit files with conflict intentionally. But is there a way to prevent files from getting committed in git?

Does git have any settings or configurable values anywhere, where it can prevent files by looking for <<<<<<<, ======= or >>>>>>> symbols?

Daniel Böhmer
  • 13,443
  • 5
  • 33
  • 45
user3586664
  • 377
  • 1
  • 4
  • 10
  • 2
    add `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; }` to `.git/hooks/pre-commit`. That's keyboard-to-editbox but it should be substantially correct. – jthill Jun 13 '14 at 21:52
  • Can you tell me what the above condition does, since it does not work for me. I added the condition in pre-commit file and tried adding a new file in git bash, the file got added, then committed it which became successful as well. Let me know if i have missed something. – user3586664 Jun 16 '14 at 06:38
  • Does the git plugins provide this restriction? – user3586664 Jun 16 '14 at 06:56
  • @jthill pre-commit works as expected, but i used the same condition in pre-receive which fails. I commit the file using egit in eclipse but i have pre-receive in server, but on pushing the conflict file, the pre-receive hook gets called but still goes ahead with the push. I expected the file not to get pushed. – user3586664 Jun 20 '14 at 01:00
  • @jthill: That looks like a nice, concise solution. Why don't you post it as an answer? Comments may get deleted after a while. – sleske Nov 26 '14 at 13:02
  • @sleske partly because VonC's answer is more complete than mine, but mostly because as the comments on his answer make clear OP's problem isn't what to put in a hook, it's that Eclipse apparently uses yet another of those oh-so-pleasing-to-the-academic-sensibilities knockoffs that's oh-so-easy-to-just-drop-in that *doesn't actually implement git*. It's got everything but the actual support for nice, concise, ways to adapt to inevitable local quirks in the workflow. – jthill Nov 26 '14 at 13:23
  • 1
    @jthill: Would you post it as an answer anyway? Lots of people happening on this page will be using straight git, not Eclipse. VonC's answer doesn't work for me, unless I track down some Perl dependency. I also don't need the additional console.log and other checks. – ksenzee Dec 20 '14 at 00:01

5 Answers5

11

VonC's answer already explains the different kinds of hooks where you might want to check for merge commits.

If you just want a simple solution to avoid committing conflicts, that is already included with git in the default pre-commit hook sample. Just enable the hook by renaming .git/hooks/pre-commit.sample to .git/hooks/pre-commit. If you then try to commit a conflict:

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

Note:

The script uses git diff --check internally, which also checks for various white space problems - so you might get whitespace errors as well. You can also run git diff --check before committing to find problems. See the manpage of git diff for details and config options.

This is valid for git V2.0; no idea when it was introduced.

sleske
  • 77,633
  • 33
  • 182
  • 219
  • 2
    The pre-commit hook sample I get is about non-ASCII characters in the committed filenames. Can you give the content of the no-conflict sample? – Jānis Elmeris Oct 30 '15 at 17:48
  • @Janis: That's the right one. The script invokes `git diff-index --check`, which will check for conflict markers. Just try it :-). – sleske Oct 31 '15 at 22:18
  • 1
    I know, quite some time passed since you did react to this, but... in my pre-commit.sample there is no git diff-index --check; nor does it detect leftover merge makers when activated anyway :( – Confused Merlin Feb 08 '16 at 13:22
  • @ConfusedMerlin: Well, the default sample pre-commit hook still contains `git diff-index --check`: https://github.com/git/git/blob/master/templates/hooks--pre-commit.sample . Try creating a fresh repo ( `git init` ). If you still don't get the pre-commit sample hook, ask a new question :-). – sleske Feb 08 '16 at 13:47
  • 4
    ah, found it just one line outside my terminal *sigh*; anyway, i can happily commit merge-markers even with that hook enables :( So, yea, a new question it should be. – Confused Merlin Feb 08 '16 at 13:56
  • @ConfusedMerlin link to the new question please – Mohit Kanwar Apr 09 '20 at 12:47
6

You can use a pre-commit hook, but be aware that a git commit --no-verify would effectively ignore that.

I generally put a pre-receive hook in order to control in a (more) central point what is being pushed.

But a pre-commmit allows for a more timely detection (earlier in the development cycle).

Here is another example (in addition of jthill's comment), in perl.
It uses git diff-index -p -M --cached HEAD, that is git diff-index instead of git diff.
I have left a few other controls done by this hook, just to showcase the kind of checks you can do in such a script.

#!/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;
Community
  • 1
  • 1
VonC
  • 1,129,465
  • 480
  • 4,036
  • 4,755
  • Using git plugin for eclipse i was able to commit the file and push as well, with pre-commit and pre-push files in hooks folder. But file doesn't get committed or pushed using git bash. Doesn't git plugin provide the restriction feature? – user3586664 Jun 17 '14 at 16:36
  • @user3586664 the file would be committed in git bash if you use git add and git commit. – VonC Jun 17 '14 at 16:45
  • may be i was not clear, i had commands to stop files from getting committed when it encounters "<<< – user3586664 Jun 17 '14 at 18:15
  • @user3586664 it is possible the EGit/JGit doesn't fully support git hooks yet: http://stackoverflow.com/a/6236026/6309 – VonC Jun 17 '14 at 18:55
  • @user3586664 server-side hooks seem supported though: http://dev.eclipse.org/mhonarc/lists/jgit-dev/msg01983.html or http://dev.eclipse.org/mhonarc/lists/jgit-dev/msg02492.html – VonC Jun 17 '14 at 19:11
  • @user3586664 but client-side hooks might not be supported yet: http://dev.eclipse.org/mhonarc/lists/egit-dev/msg02578.html – VonC Jun 17 '14 at 19:14
  • Can you explain this condition in the code if ( $l =~ /^diff --git a\/([^ ]*) .*$/ ) – user3586664 Jun 23 '14 at 05:25
  • @user3586664 it is for isolating the filepath from the `git diff` output: if said output starts (`^`) with `diff --git a/`, then the rest is the filepath (until the next space) – VonC Jun 23 '14 at 05:45
  • i tried the following in pre-receive file in my server but my pre-commit on my local git repository works without any issues "#!/bin/bash git rev-parse -q --verify HEAD && sed -n -e '/^<>\+ /q1' $(git diff --name-only HEAD^ HEAD $(git write-tree)) || { echo "Git conflict (<<<>>>) exists in one of the committed files."; exit 1; }" but get the error like "remote: sed: can't read /App/abc/TestFile.java:No such file or directory" Can you help me in resolving it? – user3586664 Jun 23 '14 at 08:36
  • @user3586664 that would be more readable as a separate question (with a link back to this answer) – VonC Jun 23 '14 at 08:37
3

A straightforward approach using a pre-commit hook, adapted from here but improved to be a bit more careful and thorough:

#!/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

Don't forget to make the hook executable (chmod u+x pre-commit)!

I've since put this on github: https://github.com/patrickvacek/git-reject-binaries-and-large-files/blob/master/pre-commit

pattivacek
  • 5,366
  • 4
  • 49
  • 60
0

I added a unit test to go through all files in the solution directory for the conflict marker string

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

SolutionValidationScripts defined separately below:

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);
            }
        }
    }
}
Richard Long
  • 281
  • 3
  • 3
0

A client side solution is using git pre-commit hooks which allow you to execute specific commands when you commit (a merge is a type of commit) and if they fail, then you cannot merge until it's resolved.

pre-commit is a simple way and standardised to manage git pre-commit hooks.

In the root of your repository create a file called .pre-commit-config.yaml With the following

# 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
Install pre-commit

(preferred way is to) use pipx or do this outside a virtual env since it's a CLI application

python3 -m pip install pre-commit 
Install the hooks
pre-commit install # you only do this once per "git clone"

Next time you merge if there are conflict markers detected it will warn you and prevent you from continuing

$ 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
Mark
  • 868
  • 16
  • 27