27

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
  • 14,463
  • 5
  • 36
  • 46
user3586664
  • 397
  • 1
  • 4
  • 12
  • 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

12

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
  • 81,358
  • 34
  • 189
  • 227
  • 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,262,500
  • 529
  • 4,410
  • 5,250
  • 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 "<<<<" pattern in the file. The problem is, using git bash the file is not getting committed, which is as expected. But on using git plugin for eclipse the restriction fails, which means the file gets committed and pushed as well. Is there anyway to get this restriction applied on git plugin? – 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' -n -e '/^>>\+ /q1' $(git diff --name-only HEAD^ HEAD $(git write-tree)) || { echo "Git conflict (<<<<) or (>>>>) 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,617
  • 5
  • 48
  • 62
1

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
  • 1,337
  • 23
  • 34
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