]> source.dussan.org Git - jgit.git/commitdiff
Allow file mode conflicts in virtual base commit on recursive merge. 30/178730/4
authorMarija Savtchouk <mariasavtchouk@google.com>
Thu, 1 Apr 2021 14:52:26 +0000 (15:52 +0100)
committerMarija Savtchouk <mariasavtchouk@google.com>
Tue, 6 Apr 2021 08:33:04 +0000 (09:33 +0100)
Similar to https://git.eclipse.org/r/c/jgit/jgit/+/175166, ignore
path that have conflicts on attributes, so that the virtual base could
be used by RecursiveMerger.

Change-Id: I99c95445a305558d55bbb9c9e97446caaf61c154
Signed-off-by: Marija Savtchouk <mariasavtchouk@google.com>
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java
org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java

index eecf25be905d295147972021154977d13b4aac38..6cbb4a89b218e609bae3c13c0686bb32f091c0fc 100644 (file)
@@ -1648,6 +1648,82 @@ public class MergerTest extends RepositoryTestCase {
                                indexState(CONTENT));
        }
 
+       /**
+        * Merging two commits when files have equal content, but conflicting file mode
+        * in the virtual ancestor.
+        *
+        * <p>
+        * This test has the same set up as
+        * {@code checkFileDirMergeConflictInVirtualAncestor_NoConflictInChildren}, only
+        * with the mode conflict in A1 and A2.
+        */
+       @Theory
+       public void checkModeMergeConflictInVirtualAncestor(MergeStrategy strategy) throws Exception {
+               if (!strategy.equals(MergeStrategy.RECURSIVE)) {
+                       return;
+               }
+
+               Git git = Git.wrap(db);
+
+               // master
+               writeTrashFile("c", "initial file");
+               git.add().addFilepattern("c").call();
+               RevCommit commitI = git.commit().setMessage("Initial commit").call();
+
+               File a = writeTrashFile("a", "content in Ancestor");
+               git.add().addFilepattern("a").call();
+               RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
+
+               a = writeTrashFile("a", "content in Child 1 (commited on master)");
+               git.add().addFilepattern("a").call();
+               // commit C1M
+               git.commit().setMessage("Child 1 on master").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
+               // "a" becomes executable in A2
+               a = writeTrashFile("a", "content in Ancestor");
+               a.setExecutable(true);
+               git.add().addFilepattern("a").call();
+               RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
+
+               // second branch
+               git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
+               a = writeTrashFile("a", "content in Child 2 (commited on second-branch)");
+               git.add().addFilepattern("a").call();
+               // commit C2S
+               git.commit().setMessage("Child 2 on second-branch").call();
+
+               // Merge branch-to-merge into second-branch
+               MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
+               assertEquals(mergeResult.getNewHead(), null);
+               assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
+               // Resolve the conflict manually, merge "a" as non-executable
+               a = writeTrashFile("a", "merge conflict resolution");
+               a.setExecutable(false);
+               git.add().addFilepattern("a").call();
+               RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict").call();
+
+               // Merge branch-to-merge into master
+               git.checkout().setName("master").call();
+               mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
+               assertEquals(mergeResult.getNewHead(), null);
+               assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
+
+               // Resolve the conflict manually - merge "a" as non-executable
+               a = writeTrashFile("a", "merge conflict resolution");
+               a.setExecutable(false);
+               git.add().addFilepattern("a").call();
+               // commit C4M
+               git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
+
+               // Merge C4M (second-branch) into master (C3S)
+               // Conflict in virtual base should be here, but there are no conflicts in
+               // children
+               mergeResult = git.merge().include(commitC3S).call();
+               assertEquals(mergeResult.getMergeStatus(), MergeStatus.MERGED);
+
+       }
+
        private void writeSubmodule(String path, ObjectId commit)
                        throws IOException, ConfigInvalidException {
                addSubmoduleToIndex(path, commit);
index 4bfb38d286947235a0f55cbcd5a8ca94006f6300..b01125898145287033b5525ac63d4c850838daa3 100644 (file)
@@ -644,15 +644,18 @@ public class ResolveMerger extends ThreeWayMerger {
                                }
                                return true;
                        }
-                       // FileModes are not mergeable. We found a conflict on modes.
-                       // For conflicting entries we don't know lastModified and
-                       // length.
-                       add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
-                       add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
-                       add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
-                       unmergedPaths.add(tw.getPathString());
-                       mergeResults.put(tw.getPathString(),
-                                       new MergeResult<>(Collections.<RawText> emptyList()));
+                       if (!ignoreConflicts) {
+                               // FileModes are not mergeable. We found a conflict on modes.
+                               // For conflicting entries we don't know lastModified and
+                               // length.
+                               // This path can be skipped on ignoreConflicts, so the caller
+                               // could use virtual commit.
+                               add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+                               add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
+                               add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
+                               unmergedPaths.add(tw.getPathString());
+                               mergeResults.put(tw.getPathString(), new MergeResult<>(Collections.<RawText>emptyList()));
+                       }
                        return true;
                }