8

In a 'git merge ' I would like any difference, even if not normally a merge conflict, to be considered a merge conflict. Then, with 'git mergetool' I can see and resolve each and every difference. I tried specifying '* -merge' in .gitattributes but that doesn't seem to have worked:

$ git checkout master
Switched to branch 'master'
$ ls
foo.c
$ git merge add-on
Updating a628824..2219552
Fast-forward
  0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 bar.c
$ cat .gitattributes 
* -merge
$ ls
bar.c   foo.c

For the above 'git merge add-on' I expected a merge conflict for 'bar.c' with no base version, no local version and a remote version. [edit] As suggested in one answer, a merge didn't occur above. Here is a case where I forced a merge, still no desired merge conflict:

$ git merge --no-ff add-on
Merge made by the 'recursive' strategy.
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 bing.c

Note that in above 'bing.c' is actually empty; but, that isn't the problem because providing a non-empty file still gets merged. [edit 2] I tried --no-commit with this result:

$ git merge --no-ff --no-commit add-on
Automatic merge went well; stopped before committing as requested
$ git status
# On branch master
# Changes to be committed:
#
#   new file:   boom.c
#
$ git mergetool
No files need merging
$ cat .gitattributes 
* -merge

What am I missing? Is there a way to confirm that '.gitattributes' is getting used/read?

GoZoner
  • 67,920
  • 20
  • 95
  • 145

3 Answers3

6

You didn't merge. Your command git merge add-on executed a "fast-forward", which means it just moved the branch head. This is because your add-on branch was descended from the tip of your master branch already, so no merge was needed. If you run git log you'll see there's no merge commit.

Basically, it looked like this before the merge:

              master
             /
o---o---o---o           add-on
             \         /
              o---o---o

and the merge just moved the master pointer to the end of the line:

                          master, add-on
                         /
o---o---o---o---o---o---o

If you want to force a merge, pass the --no-ff flag, as in git merge --no-ff add-on.


Upon further reflection, the merge attribute won't do what you want. This only applies to file-level merges, which means both sides of the merge has changes to the specific file. If only one side has changes (which is your case), no file-level merge is done and the changed file is accepted unconditionally.

Your best bet is probably to use git merge --no-ff --no-commit add-on to produce the merge but don't actually commit. You can now inspect the results and tweak them to your satisfaction before committing the merge. If you want to accept changes on a per-hunk basis you can do something like git reset to reset the index and then git add -p to do per-hunk staging.

Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • 2
    Thanks. I tried again with 'git merge --no-ff add-on' - still no desired merge conflict. – GoZoner Apr 12 '12 at 21:07
  • @GoZoner: There's no merge conflict because there were no file-level merges. Only one side of the merge had any changes to the file, so that side was chosen unconditionally. The `merge` attribute only applies when there's a file-level merge (e.g. both sides had changes and a merge driver needs to be invoked). – Lily Ballard Apr 12 '12 at 21:50
  • Right, but based on the git attributes documentation for 'merge unset', I expect that a merge conflict would be forced to occur. Perhaps '* -merge' isn't the right way to get my desired behavior (every difference, even if normally merge-able, produces a conflict that I can then resolve explicitly with a 3-way merge - so that I can verify that the merge is semantically correct, not just textually correct). – GoZoner Apr 12 '12 at 22:14
  • 1
    @GoZoner: From the manpage: "The attribute merge affects how three versions of a file are merged when a *file-level merge* is necessary during git merge" (emphasis mine). You don't have a file-level merge. I updated my answer already with an alternative suggestion. – Lily Ballard Apr 12 '12 at 22:16
  • Yes, see it now and have confirmed. – GoZoner Apr 12 '12 at 22:47
3

You may want to try:

git merge --no-commit

Which will make GIT not commit the merge changes. Alternatively if you want it to commit when there are no conflict, but not to lose the source branch, it's no-ff:

git merge --no-ff

Both can be used if necessary.

More info here: Why does git fast-forward merges by default?

Community
  • 1
  • 1
yamen
  • 15,390
  • 3
  • 42
  • 52
  • Thanks. I tried again with 'git merge --no-ff add-on' - still no desired merge conflict. – GoZoner Apr 12 '12 at 21:07
  • And did you try `--no-commit`? The fact that it doesn't commit gives you a chance to choose what to accept and what not to accept. You still won't get a *conflict* necessarily, because there is none. – yamen Apr 12 '12 at 21:10
  • I'll try but note that the help for gitattributes states "Unset: Take the version from the current branch as the tentative merge result, and declare that the merge has conflicts. This is suitable for binary files that do not have a well-defined merge semantics." Declaring conflicts ought to stop the merge prior to any commit. – GoZoner Apr 12 '12 at 21:16
2

I have a similar desire and have been unable to do it with the automatic tools. Here is a semi automated solution:

From master, merge add-on and manually resolve changes:

Start with:

git merge --no-ff --no-commit add-on

Then, for each changed file do:

git show master:path/to/file > from_master
git show add-on/to/file > from_add-on
kdiff3 --qall from_master from_add-on -o path/to/file
git add path/to/file

Finally,

git commit

Why this 90% solution?

This solution merges even in "obvious situations" and it doesn't force me to set .gitattributes so I can make "manual merge" behavior dependent on who I am merging from and why rather than on which files I am merging.

Eponymous
  • 6,143
  • 4
  • 43
  • 43