Implement recursive merge strategy
Extend ResolveMerger with RecursiveMerger to merge two tips that have up to 200 bases. Bug: 380314 CQ: 6854 Change-Id: I6292bb7bda55c0242a448a94956f2d6a94fddbaa Also-by: Christian Halstrick <christian.halstrick@sap.com> Signed-off-by: Chris Aniszczyk <zx@twitter.com> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
parent
95ef1e83d0
commit
ab99b78ca0
|
@ -118,8 +118,6 @@ There are some missing features:
|
||||||
|
|
||||||
- gitattributes support
|
- gitattributes support
|
||||||
|
|
||||||
- Recursive merge strategy
|
|
||||||
|
|
||||||
|
|
||||||
Support
|
Support
|
||||||
-------
|
-------
|
||||||
|
|
|
@ -0,0 +1,578 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
|
||||||
|
* and other copyright owners as documented in the project's IP log.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available
|
||||||
|
* under the terms of the Eclipse Distribution License v1.0 which
|
||||||
|
* accompanies this distribution, is reproduced below, and is
|
||||||
|
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or
|
||||||
|
* without modification, are permitted provided that the following
|
||||||
|
* conditions are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above
|
||||||
|
* copyright notice, this list of conditions and the following
|
||||||
|
* disclaimer in the documentation and/or other materials provided
|
||||||
|
* with the distribution.
|
||||||
|
*
|
||||||
|
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
||||||
|
* names of its contributors may be used to endorse or promote
|
||||||
|
* products derived from this software without specific prior
|
||||||
|
* written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||||
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package org.eclipse.jgit.merge;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.dircache.DirCache;
|
||||||
|
import org.eclipse.jgit.dircache.DirCacheEditor;
|
||||||
|
import org.eclipse.jgit.dircache.DirCacheEntry;
|
||||||
|
import org.eclipse.jgit.errors.MissingObjectException;
|
||||||
|
import org.eclipse.jgit.errors.NoMergeBaseException;
|
||||||
|
import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
|
||||||
|
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||||
|
import org.eclipse.jgit.junit.TestRepository;
|
||||||
|
import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
|
||||||
|
import org.eclipse.jgit.lib.AnyObjectId;
|
||||||
|
import org.eclipse.jgit.lib.Constants;
|
||||||
|
import org.eclipse.jgit.lib.FileMode;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectLoader;
|
||||||
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.revwalk.RevBlob;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.eclipse.jgit.storage.file.FileRepository;
|
||||||
|
import org.eclipse.jgit.treewalk.FileTreeIterator;
|
||||||
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
|
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.experimental.theories.DataPoints;
|
||||||
|
import org.junit.experimental.theories.Theories;
|
||||||
|
import org.junit.experimental.theories.Theory;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
@RunWith(Theories.class)
|
||||||
|
public class RecursiveMergerTest extends RepositoryTestCase {
|
||||||
|
static int counter = 0;
|
||||||
|
|
||||||
|
@DataPoints
|
||||||
|
public static MergeStrategy[] strategiesUnderTest = new MergeStrategy[] {
|
||||||
|
MergeStrategy.RECURSIVE, MergeStrategy.RESOLVE };
|
||||||
|
|
||||||
|
public enum IndexState {
|
||||||
|
Bare, Missing, SameAsHead, SameAsOther, SameAsWorkTree, DifferentFromHeadAndOtherAndWorktree
|
||||||
|
}
|
||||||
|
|
||||||
|
@DataPoints
|
||||||
|
public static IndexState[] indexStates = IndexState.values();
|
||||||
|
|
||||||
|
public enum WorktreeState {
|
||||||
|
Bare, Missing, SameAsHead, DifferentFromHeadAndOther, SameAsOther;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DataPoints
|
||||||
|
public static WorktreeState[] worktreeStates = WorktreeState.values();
|
||||||
|
|
||||||
|
private TestRepository<FileRepository> db_t;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
db_t = new TestRepository<FileRepository>(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Theory
|
||||||
|
/**
|
||||||
|
* Merging m2,s2 from the following topology. In master and side different
|
||||||
|
* files are touched. No need to do a real content merge.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* m0--m1--m2
|
||||||
|
* \ \/
|
||||||
|
* \ /\
|
||||||
|
* s1--s2
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public void crissCrossMerge(MergeStrategy strategy, IndexState indexState,
|
||||||
|
WorktreeState worktreeState) throws Exception {
|
||||||
|
if (!validateStates(indexState, worktreeState))
|
||||||
|
return;
|
||||||
|
// fill the repo
|
||||||
|
BranchBuilder master = db_t.branch("master");
|
||||||
|
RevCommit m0 = master.commit().add("m", ",m0").message("m0").create();
|
||||||
|
RevCommit m1 = master.commit().add("m", "m1").message("m1").create();
|
||||||
|
db_t.getRevWalk().parseCommit(m1);
|
||||||
|
|
||||||
|
BranchBuilder side = db_t.branch("side");
|
||||||
|
RevCommit s1 = side.commit().parent(m0).add("s", "s1").message("s1")
|
||||||
|
.create();
|
||||||
|
RevCommit s2 = side.commit().parent(m1).add("m", "m1")
|
||||||
|
.message("s2(merge)").create();
|
||||||
|
RevCommit m2 = master.commit().parent(s1).add("s", "s1")
|
||||||
|
.message("m2(merge)").create();
|
||||||
|
|
||||||
|
Git git = Git.wrap(db);
|
||||||
|
git.checkout().setName("master").call();
|
||||||
|
modifyWorktree(worktreeState, "m", "side");
|
||||||
|
modifyWorktree(worktreeState, "s", "side");
|
||||||
|
modifyIndex(indexState, "m", "side");
|
||||||
|
modifyIndex(indexState, "s", "side");
|
||||||
|
|
||||||
|
ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
|
||||||
|
worktreeState == WorktreeState.Bare);
|
||||||
|
if (worktreeState != WorktreeState.Bare)
|
||||||
|
merger.setWorkingTreeIterator(new FileTreeIterator(db));
|
||||||
|
try {
|
||||||
|
boolean expectSuccess = true;
|
||||||
|
if (!(indexState == IndexState.Bare
|
||||||
|
|| indexState == IndexState.Missing
|
||||||
|
|| indexState == IndexState.SameAsHead || indexState == IndexState.SameAsOther))
|
||||||
|
// index is dirty
|
||||||
|
expectSuccess = false;
|
||||||
|
|
||||||
|
assertEquals(Boolean.valueOf(expectSuccess),
|
||||||
|
Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
|
||||||
|
assertEquals(MergeStrategy.RECURSIVE, strategy);
|
||||||
|
assertEquals("m1",
|
||||||
|
contentAsString(db, merger.getResultTreeId(), "m"));
|
||||||
|
assertEquals("s1",
|
||||||
|
contentAsString(db, merger.getResultTreeId(), "s"));
|
||||||
|
} catch (NoMergeBaseException e) {
|
||||||
|
assertEquals(MergeStrategy.RESOLVE, strategy);
|
||||||
|
assertEquals(e.getReason(),
|
||||||
|
MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Theory
|
||||||
|
/**
|
||||||
|
* Merging m2,s2 from the following topology. The same file is modified
|
||||||
|
* in both branches. The modifications should be mergeable. m2 and s2
|
||||||
|
* contain branch specific conflict resolutions. Therefore m2 and don't contain the same content.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* m0--m1--m2
|
||||||
|
* \ \/
|
||||||
|
* \ /\
|
||||||
|
* s1--s2
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public void crissCrossMerge_mergeable(MergeStrategy strategy,
|
||||||
|
IndexState indexState, WorktreeState worktreeState)
|
||||||
|
throws Exception {
|
||||||
|
if (!validateStates(indexState, worktreeState))
|
||||||
|
return;
|
||||||
|
|
||||||
|
BranchBuilder master = db_t.branch("master");
|
||||||
|
RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n")
|
||||||
|
.message("m0").create();
|
||||||
|
RevCommit m1 = master.commit()
|
||||||
|
.add("f", "1-master\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m1")
|
||||||
|
.create();
|
||||||
|
db_t.getRevWalk().parseCommit(m1);
|
||||||
|
|
||||||
|
BranchBuilder side = db_t.branch("side");
|
||||||
|
RevCommit s1 = side.commit().parent(m0)
|
||||||
|
.add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9-side\n").message("s1")
|
||||||
|
.create();
|
||||||
|
RevCommit s2 = side.commit().parent(m1)
|
||||||
|
.add("f", "1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n")
|
||||||
|
.message("s2(merge)").create();
|
||||||
|
RevCommit m2 = master
|
||||||
|
.commit()
|
||||||
|
.parent(s1)
|
||||||
|
.add("f", "1-master\n2\n3-res(master)\n4\n5\n6\n7\n8\n9-side\n")
|
||||||
|
.message("m2(merge)").create();
|
||||||
|
|
||||||
|
Git git = Git.wrap(db);
|
||||||
|
git.checkout().setName("master").call();
|
||||||
|
modifyWorktree(worktreeState, "f", "side");
|
||||||
|
modifyIndex(indexState, "f", "side");
|
||||||
|
|
||||||
|
ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
|
||||||
|
worktreeState == WorktreeState.Bare);
|
||||||
|
if (worktreeState != WorktreeState.Bare)
|
||||||
|
merger.setWorkingTreeIterator(new FileTreeIterator(db));
|
||||||
|
try {
|
||||||
|
boolean expectSuccess = true;
|
||||||
|
if (!(indexState == IndexState.Bare
|
||||||
|
|| indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
|
||||||
|
// index is dirty
|
||||||
|
expectSuccess = false;
|
||||||
|
else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
|
||||||
|
|| worktreeState == WorktreeState.SameAsOther)
|
||||||
|
expectSuccess = false;
|
||||||
|
assertEquals(Boolean.valueOf(expectSuccess),
|
||||||
|
Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
|
||||||
|
assertEquals(MergeStrategy.RECURSIVE, strategy);
|
||||||
|
if (!expectSuccess)
|
||||||
|
// if the merge was not successful skip testing the state of index and workingtree
|
||||||
|
return;
|
||||||
|
assertEquals(
|
||||||
|
"1-master\n2\n3-res(master)\n4\n5\n6\n7-res(side)\n8\n9-side",
|
||||||
|
contentAsString(db, merger.getResultTreeId(), "f"));
|
||||||
|
if (indexState != IndexState.Bare)
|
||||||
|
assertEquals(
|
||||||
|
"[f, mode:100644, content:1-master\n2\n3-res(master)\n4\n5\n6\n7-res(side)\n8\n9-side\n]",
|
||||||
|
indexState(RepositoryTestCase.CONTENT));
|
||||||
|
if (worktreeState != WorktreeState.Bare
|
||||||
|
&& worktreeState != WorktreeState.Missing)
|
||||||
|
assertEquals(
|
||||||
|
"1-master\n2\n3-res(master)\n4\n5\n6\n7-res(side)\n8\n9-side\n",
|
||||||
|
read("f"));
|
||||||
|
} catch (NoMergeBaseException e) {
|
||||||
|
assertEquals(MergeStrategy.RESOLVE, strategy);
|
||||||
|
assertEquals(e.getReason(),
|
||||||
|
MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Theory
|
||||||
|
/**
|
||||||
|
* Merging m2,s2 from the following topology. The same file is modified
|
||||||
|
* in both branches. The modifications are not automatically
|
||||||
|
* mergeable. m2 and s2 contain branch specific conflict resolutions.
|
||||||
|
* Therefore m2 and s2 don't contain the same content.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* m0--m1--m2
|
||||||
|
* \ \/
|
||||||
|
* \ /\
|
||||||
|
* s1--s2
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public void crissCrossMerge_nonmergeable(MergeStrategy strategy,
|
||||||
|
IndexState indexState, WorktreeState worktreeState)
|
||||||
|
throws Exception {
|
||||||
|
if (!validateStates(indexState, worktreeState))
|
||||||
|
return;
|
||||||
|
|
||||||
|
BranchBuilder master = db_t.branch("master");
|
||||||
|
RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n")
|
||||||
|
.message("m0").create();
|
||||||
|
RevCommit m1 = master.commit()
|
||||||
|
.add("f", "1-master\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m1")
|
||||||
|
.create();
|
||||||
|
db_t.getRevWalk().parseCommit(m1);
|
||||||
|
|
||||||
|
BranchBuilder side = db_t.branch("side");
|
||||||
|
RevCommit s1 = side.commit().parent(m0)
|
||||||
|
.add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9-side\n").message("s1")
|
||||||
|
.create();
|
||||||
|
RevCommit s2 = side.commit().parent(m1)
|
||||||
|
.add("f", "1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n")
|
||||||
|
.message("s2(merge)").create();
|
||||||
|
RevCommit m2 = master.commit().parent(s1)
|
||||||
|
.add("f", "1-master\n2\n3\n4\n5\n6\n7-conflict\n8\n9-side\n")
|
||||||
|
.message("m2(merge)").create();
|
||||||
|
|
||||||
|
Git git = Git.wrap(db);
|
||||||
|
git.checkout().setName("master").call();
|
||||||
|
modifyWorktree(worktreeState, "f", "side");
|
||||||
|
modifyIndex(indexState, "f", "side");
|
||||||
|
|
||||||
|
ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
|
||||||
|
worktreeState == WorktreeState.Bare);
|
||||||
|
if (worktreeState != WorktreeState.Bare)
|
||||||
|
merger.setWorkingTreeIterator(new FileTreeIterator(db));
|
||||||
|
try {
|
||||||
|
assertFalse(merger.merge(new RevCommit[] { m2, s2 }));
|
||||||
|
assertEquals(MergeStrategy.RECURSIVE, strategy);
|
||||||
|
if (indexState == IndexState.SameAsHead
|
||||||
|
&& worktreeState == WorktreeState.SameAsHead) {
|
||||||
|
assertEquals(
|
||||||
|
"[f, mode:100644, stage:1, content:1-master\n2\n3\n4\n5\n6\n7\n8\n9-side\n]"
|
||||||
|
+ "[f, mode:100644, stage:2, content:1-master\n2\n3\n4\n5\n6\n7-conflict\n8\n9-side\n]"
|
||||||
|
+ "[f, mode:100644, stage:3, content:1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n]",
|
||||||
|
indexState(RepositoryTestCase.CONTENT));
|
||||||
|
assertEquals(
|
||||||
|
"1-master\n2\n3\n4\n5\n6\n<<<<<<< OURS\n7-conflict\n=======\n7-res(side)\n>>>>>>> THEIRS\n8\n9-side\n",
|
||||||
|
read("f"));
|
||||||
|
}
|
||||||
|
} catch (NoMergeBaseException e) {
|
||||||
|
assertEquals(MergeStrategy.RESOLVE, strategy);
|
||||||
|
assertEquals(e.getReason(),
|
||||||
|
MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Theory
|
||||||
|
/**
|
||||||
|
* Merging m2,s2 which have three common predecessors.The same file is modified
|
||||||
|
* in all branches. The modifications should be mergeable. m2 and s2
|
||||||
|
* contain branch specific conflict resolutions. Therefore m2 and s2
|
||||||
|
* don't contain the same content.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* m1-----m2
|
||||||
|
* / \/ /
|
||||||
|
* / /\ /
|
||||||
|
* m0--o1 x
|
||||||
|
* \ \/ \
|
||||||
|
* \ /\ \
|
||||||
|
* s1-----s2
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public void crissCrossMerge_ThreeCommonPredecessors(MergeStrategy strategy,
|
||||||
|
IndexState indexState, WorktreeState worktreeState)
|
||||||
|
throws Exception {
|
||||||
|
if (!validateStates(indexState, worktreeState))
|
||||||
|
return;
|
||||||
|
|
||||||
|
BranchBuilder master = db_t.branch("master");
|
||||||
|
RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n")
|
||||||
|
.message("m0").create();
|
||||||
|
RevCommit m1 = master.commit()
|
||||||
|
.add("f", "1-master\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m1")
|
||||||
|
.create();
|
||||||
|
BranchBuilder side = db_t.branch("side");
|
||||||
|
RevCommit s1 = side.commit().parent(m0)
|
||||||
|
.add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9-side\n").message("s1")
|
||||||
|
.create();
|
||||||
|
BranchBuilder other = db_t.branch("other");
|
||||||
|
RevCommit o1 = other.commit().parent(m0)
|
||||||
|
.add("f", "1\n2\n3\n4\n5-other\n6\n7\n8\n9\n").message("o1")
|
||||||
|
.create();
|
||||||
|
|
||||||
|
RevCommit m2 = master
|
||||||
|
.commit()
|
||||||
|
.parent(s1)
|
||||||
|
.parent(o1)
|
||||||
|
.add("f",
|
||||||
|
"1-master\n2\n3-res(master)\n4\n5-other\n6\n7\n8\n9-side\n")
|
||||||
|
.message("m2(merge)").create();
|
||||||
|
|
||||||
|
RevCommit s2 = side
|
||||||
|
.commit()
|
||||||
|
.parent(m1)
|
||||||
|
.parent(o1)
|
||||||
|
.add("f",
|
||||||
|
"1-master\n2\n3\n4\n5-other\n6\n7-res(side)\n8\n9-side\n")
|
||||||
|
.message("s2(merge)").create();
|
||||||
|
|
||||||
|
Git git = Git.wrap(db);
|
||||||
|
git.checkout().setName("master").call();
|
||||||
|
modifyWorktree(worktreeState, "f", "side");
|
||||||
|
modifyIndex(indexState, "f", "side");
|
||||||
|
|
||||||
|
ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
|
||||||
|
worktreeState == WorktreeState.Bare);
|
||||||
|
if (worktreeState != WorktreeState.Bare)
|
||||||
|
merger.setWorkingTreeIterator(new FileTreeIterator(db));
|
||||||
|
try {
|
||||||
|
boolean expectSuccess = true;
|
||||||
|
if (!(indexState == IndexState.Bare
|
||||||
|
|| indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
|
||||||
|
// index is dirty
|
||||||
|
expectSuccess = false;
|
||||||
|
else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
|
||||||
|
|| worktreeState == WorktreeState.SameAsOther)
|
||||||
|
// workingtree is dirty
|
||||||
|
expectSuccess = false;
|
||||||
|
|
||||||
|
assertEquals(Boolean.valueOf(expectSuccess),
|
||||||
|
Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
|
||||||
|
assertEquals(MergeStrategy.RECURSIVE, strategy);
|
||||||
|
if (!expectSuccess)
|
||||||
|
// if the merge was not successful skip testing the state of index and workingtree
|
||||||
|
return;
|
||||||
|
assertEquals(
|
||||||
|
"1-master\n2\n3-res(master)\n4\n5-other\n6\n7-res(side)\n8\n9-side",
|
||||||
|
contentAsString(db, merger.getResultTreeId(), "f"));
|
||||||
|
if (indexState != IndexState.Bare)
|
||||||
|
assertEquals(
|
||||||
|
"[f, mode:100644, content:1-master\n2\n3-res(master)\n4\n5-other\n6\n7-res(side)\n8\n9-side\n]",
|
||||||
|
indexState(RepositoryTestCase.CONTENT));
|
||||||
|
if (worktreeState != WorktreeState.Bare
|
||||||
|
&& worktreeState != WorktreeState.Missing)
|
||||||
|
assertEquals(
|
||||||
|
"1-master\n2\n3-res(master)\n4\n5-other\n6\n7-res(side)\n8\n9-side\n",
|
||||||
|
read("f"));
|
||||||
|
} catch (NoMergeBaseException e) {
|
||||||
|
assertEquals(MergeStrategy.RESOLVE, strategy);
|
||||||
|
assertEquals(e.getReason(),
|
||||||
|
MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void modifyIndex(IndexState indexState, String path, String other)
|
||||||
|
throws Exception {
|
||||||
|
RevBlob blob;
|
||||||
|
switch (indexState) {
|
||||||
|
case Missing:
|
||||||
|
setIndex(null, path);
|
||||||
|
break;
|
||||||
|
case SameAsHead:
|
||||||
|
setIndex(contentId(Constants.HEAD, path), path);
|
||||||
|
break;
|
||||||
|
case SameAsOther:
|
||||||
|
setIndex(contentId(other, path), path);
|
||||||
|
break;
|
||||||
|
case SameAsWorkTree:
|
||||||
|
blob = db_t.blob(read(path));
|
||||||
|
setIndex(blob, path);
|
||||||
|
break;
|
||||||
|
case DifferentFromHeadAndOtherAndWorktree:
|
||||||
|
blob = db_t.blob(Integer.toString(counter++));
|
||||||
|
setIndex(blob, path);
|
||||||
|
break;
|
||||||
|
case Bare:
|
||||||
|
File file = new File(db.getDirectory(), "index");
|
||||||
|
if (!file.exists())
|
||||||
|
return;
|
||||||
|
db.close();
|
||||||
|
file.delete();
|
||||||
|
db = new FileRepository(db.getDirectory());
|
||||||
|
db_t = new TestRepository<FileRepository>(db);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setIndex(final ObjectId id, String path)
|
||||||
|
throws MissingObjectException, IOException {
|
||||||
|
DirCache lockedDircache;
|
||||||
|
DirCacheEditor dcedit;
|
||||||
|
|
||||||
|
lockedDircache = db.lockDirCache();
|
||||||
|
dcedit = lockedDircache.editor();
|
||||||
|
try {
|
||||||
|
if (id != null) {
|
||||||
|
final ObjectLoader contLoader = db.newObjectReader().open(id);
|
||||||
|
dcedit.add(new DirCacheEditor.PathEdit(path) {
|
||||||
|
@Override
|
||||||
|
public void apply(DirCacheEntry ent) {
|
||||||
|
ent.setFileMode(FileMode.REGULAR_FILE);
|
||||||
|
ent.setLength(contLoader.getSize());
|
||||||
|
ent.setObjectId(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
dcedit.add(new DirCacheEditor.DeletePath(path));
|
||||||
|
} finally {
|
||||||
|
dcedit.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObjectId contentId(String revName, String path) throws Exception {
|
||||||
|
RevCommit headCommit = db_t.getRevWalk().parseCommit(
|
||||||
|
db.resolve(revName));
|
||||||
|
db_t.parseBody(headCommit);
|
||||||
|
return db_t.get(headCommit.getTree(), path).getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
void modifyWorktree(WorktreeState worktreeState, String path, String other)
|
||||||
|
throws Exception {
|
||||||
|
FileOutputStream fos = null;
|
||||||
|
ObjectId bloblId;
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (worktreeState) {
|
||||||
|
case Missing:
|
||||||
|
new File(db.getWorkTree(), path).delete();
|
||||||
|
break;
|
||||||
|
case DifferentFromHeadAndOther:
|
||||||
|
write(new File(db.getWorkTree(), path),
|
||||||
|
Integer.toString(counter++));
|
||||||
|
break;
|
||||||
|
case SameAsHead:
|
||||||
|
bloblId = contentId(Constants.HEAD, path);
|
||||||
|
fos = new FileOutputStream(new File(db.getWorkTree(), path));
|
||||||
|
db.newObjectReader().open(bloblId).copyTo(fos);
|
||||||
|
break;
|
||||||
|
case SameAsOther:
|
||||||
|
bloblId = contentId(other, path);
|
||||||
|
fos = new FileOutputStream(new File(db.getWorkTree(), path));
|
||||||
|
db.newObjectReader().open(bloblId).copyTo(fos);
|
||||||
|
break;
|
||||||
|
case Bare:
|
||||||
|
if (db.isBare())
|
||||||
|
return;
|
||||||
|
File workTreeFile = db.getWorkTree();
|
||||||
|
db.getConfig().setBoolean("core", null, "bare", true);
|
||||||
|
db.getDirectory().renameTo(new File(workTreeFile, "test.git"));
|
||||||
|
db = new FileRepository(new File(workTreeFile, "test.git"));
|
||||||
|
db_t = new TestRepository<FileRepository>(db);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (fos != null)
|
||||||
|
fos.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validateStates(IndexState indexState,
|
||||||
|
WorktreeState worktreeState) {
|
||||||
|
if (worktreeState == WorktreeState.Bare
|
||||||
|
&& indexState != IndexState.Bare)
|
||||||
|
return false;
|
||||||
|
if (worktreeState != WorktreeState.Bare
|
||||||
|
&& indexState == IndexState.Bare)
|
||||||
|
return false;
|
||||||
|
if (worktreeState != WorktreeState.DifferentFromHeadAndOther
|
||||||
|
&& indexState == IndexState.SameAsWorkTree)
|
||||||
|
// would be a duplicate: the combination WorktreeState.X and
|
||||||
|
// IndexState.X already covered this
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String contentAsString(Repository r, ObjectId treeId, String path)
|
||||||
|
throws MissingObjectException, IOException {
|
||||||
|
TreeWalk tw = new TreeWalk(r);
|
||||||
|
tw.addTree(treeId);
|
||||||
|
tw.setFilter(PathFilter.create(path));
|
||||||
|
tw.setRecursive(true);
|
||||||
|
if (!tw.next())
|
||||||
|
return null;
|
||||||
|
AnyObjectId blobId = tw.getObjectId(0);
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
BufferedReader br = null;
|
||||||
|
ObjectReader or = r.newObjectReader();
|
||||||
|
try {
|
||||||
|
br = new BufferedReader(new InputStreamReader(or.open(blobId)
|
||||||
|
.openStream()));
|
||||||
|
String line;
|
||||||
|
boolean first = true;
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
if (!first)
|
||||||
|
result.append('\n');
|
||||||
|
result.append(line);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
} finally {
|
||||||
|
if (br != null)
|
||||||
|
br.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,7 +54,10 @@
|
||||||
import org.eclipse.jgit.api.MergeResult;
|
import org.eclipse.jgit.api.MergeResult;
|
||||||
import org.eclipse.jgit.api.MergeResult.MergeStatus;
|
import org.eclipse.jgit.api.MergeResult.MergeStatus;
|
||||||
import org.eclipse.jgit.api.errors.CheckoutConflictException;
|
import org.eclipse.jgit.api.errors.CheckoutConflictException;
|
||||||
|
import org.eclipse.jgit.api.errors.JGitInternalException;
|
||||||
import org.eclipse.jgit.dircache.DirCache;
|
import org.eclipse.jgit.dircache.DirCache;
|
||||||
|
import org.eclipse.jgit.errors.NoMergeBaseException;
|
||||||
|
import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
|
||||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||||
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
|
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
@ -72,6 +75,9 @@ public class ResolveMergerTest extends RepositoryTestCase {
|
||||||
@DataPoint
|
@DataPoint
|
||||||
public static MergeStrategy resolve = MergeStrategy.RESOLVE;
|
public static MergeStrategy resolve = MergeStrategy.RESOLVE;
|
||||||
|
|
||||||
|
@DataPoint
|
||||||
|
public static MergeStrategy recursive = MergeStrategy.RECURSIVE;
|
||||||
|
|
||||||
@Theory
|
@Theory
|
||||||
public void failingPathsShouldNotResultInOKReturnValue(
|
public void failingPathsShouldNotResultInOKReturnValue(
|
||||||
MergeStrategy strategy) throws Exception {
|
MergeStrategy strategy) throws Exception {
|
||||||
|
@ -396,6 +402,66 @@ public void checkMergeMergeableFilesWithTreeInIndex(MergeStrategy strategy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merging after criss-cross merges. In this case we merge together two
|
||||||
|
* commits which have two equally good common ancestors
|
||||||
|
*
|
||||||
|
* @param strategy
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Theory
|
||||||
|
public void checkMergeCrissCross(MergeStrategy strategy) throws Exception {
|
||||||
|
Git git = Git.wrap(db);
|
||||||
|
|
||||||
|
writeTrashFile("1", "1\n2\n3");
|
||||||
|
git.add().addFilepattern("1").call();
|
||||||
|
RevCommit first = git.commit().setMessage("added 1").call();
|
||||||
|
|
||||||
|
writeTrashFile("1", "1master\n2\n3");
|
||||||
|
RevCommit masterCommit = git.commit().setAll(true)
|
||||||
|
.setMessage("modified 1 on master").call();
|
||||||
|
|
||||||
|
writeTrashFile("1", "1master2\n2\n3");
|
||||||
|
git.commit().setAll(true)
|
||||||
|
.setMessage("modified 1 on master again").call();
|
||||||
|
|
||||||
|
git.checkout().setCreateBranch(true).setStartPoint(first)
|
||||||
|
.setName("side").call();
|
||||||
|
writeTrashFile("1", "1\n2\na\nb\nc\n3side");
|
||||||
|
RevCommit sideCommit = git.commit().setAll(true)
|
||||||
|
.setMessage("modified 1 on side").call();
|
||||||
|
|
||||||
|
writeTrashFile("1", "1\n2\n3side2");
|
||||||
|
git.commit().setAll(true)
|
||||||
|
.setMessage("modified 1 on side again").call();
|
||||||
|
|
||||||
|
MergeResult result = git.merge().setStrategy(strategy)
|
||||||
|
.include(masterCommit).call();
|
||||||
|
assertEquals(MergeStatus.MERGED, result.getMergeStatus());
|
||||||
|
result.getNewHead();
|
||||||
|
git.checkout().setName("master").call();
|
||||||
|
result = git.merge().setStrategy(strategy).include(sideCommit).call();
|
||||||
|
assertEquals(MergeStatus.MERGED, result.getMergeStatus());
|
||||||
|
|
||||||
|
// we have two branches which are criss-cross merged. Try to merge the
|
||||||
|
// tips. This should succeed with RecursiveMerge and fail with
|
||||||
|
// ResolveMerge
|
||||||
|
try {
|
||||||
|
MergeResult mergeResult = git.merge().setStrategy(strategy)
|
||||||
|
.include(git.getRepository().getRef("refs/heads/side"))
|
||||||
|
.call();
|
||||||
|
assertEquals(MergeStrategy.RECURSIVE, strategy);
|
||||||
|
assertEquals(MergeResult.MergeStatus.MERGED,
|
||||||
|
mergeResult.getMergeStatus());
|
||||||
|
assertEquals("1master2\n2\n3side2\n", read("1"));
|
||||||
|
} catch (JGitInternalException e) {
|
||||||
|
assertEquals(MergeStrategy.RESOLVE, strategy);
|
||||||
|
assertTrue(e.getCause() instanceof NoMergeBaseException);
|
||||||
|
assertEquals(((NoMergeBaseException) e.getCause()).getReason(),
|
||||||
|
MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Theory
|
@Theory
|
||||||
public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
|
public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
|
@ -288,6 +288,8 @@ mergeConflictOnNotes=Merge conflict on note {0}. base = {1}, ours = {2}, theirs
|
||||||
mergeStrategyAlreadyExistsAsDefault=Merge strategy "{0}" already exists as a default strategy
|
mergeStrategyAlreadyExistsAsDefault=Merge strategy "{0}" already exists as a default strategy
|
||||||
mergeStrategyDoesNotSupportHeads=merge strategy {0} does not support {1} heads to be merged into HEAD
|
mergeStrategyDoesNotSupportHeads=merge strategy {0} does not support {1} heads to be merged into HEAD
|
||||||
mergeUsingStrategyResultedInDescription=Merge of revisions {0} with base {1} using strategy {2} resulted in: {3}. {4}
|
mergeUsingStrategyResultedInDescription=Merge of revisions {0} with base {1} using strategy {2} resulted in: {3}. {4}
|
||||||
|
mergeRecursiveReturnedNoCommit=Merge returned no commit:\n Depth {0}\n Head one {1}\n Head two {2}
|
||||||
|
mergeRecursiveTooManyMergeBasesFor = "More than {0} merge bases for:\n a {1}\n b {2} found:\n count {3}"
|
||||||
minutesAgo={0} minutes ago
|
minutesAgo={0} minutes ago
|
||||||
missingAccesskey=Missing accesskey.
|
missingAccesskey=Missing accesskey.
|
||||||
missingConfigurationForKey=No value for key {0} found in configuration
|
missingConfigurationForKey=No value for key {0} found in configuration
|
||||||
|
@ -313,6 +315,7 @@ noApplyInDelete=No apply in delete
|
||||||
noClosingBracket=No closing {0} found for {1} at index {2}.
|
noClosingBracket=No closing {0} found for {1} at index {2}.
|
||||||
noHEADExistsAndNoExplicitStartingRevisionWasSpecified=No HEAD exists and no explicit starting revision was specified
|
noHEADExistsAndNoExplicitStartingRevisionWasSpecified=No HEAD exists and no explicit starting revision was specified
|
||||||
noHMACsupport=No {0} support: {1}
|
noHMACsupport=No {0} support: {1}
|
||||||
|
noMergeBase=No merge base could be determined. Reason={0}. {1}
|
||||||
noMergeHeadSpecified=No merge head specified
|
noMergeHeadSpecified=No merge head specified
|
||||||
noSuchRef=no such ref
|
noSuchRef=no such ref
|
||||||
notABoolean=Not a boolean: {0}
|
notABoolean=Not a boolean: {0}
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013, Christian Halstrick <christian.halstrick@sap.com>
|
||||||
|
* and other copyright owners as documented in the project's IP log.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available
|
||||||
|
* under the terms of the Eclipse Distribution License v1.0 which
|
||||||
|
* accompanies this distribution, is reproduced below, and is
|
||||||
|
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or
|
||||||
|
* without modification, are permitted provided that the following
|
||||||
|
* conditions are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above
|
||||||
|
* copyright notice, this list of conditions and the following
|
||||||
|
* disclaimer in the documentation and/or other materials provided
|
||||||
|
* with the distribution.
|
||||||
|
*
|
||||||
|
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
||||||
|
* names of its contributors may be used to endorse or promote
|
||||||
|
* products derived from this software without specific prior
|
||||||
|
* written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||||
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.eclipse.jgit.errors;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
|
import org.eclipse.jgit.merge.RecursiveMerger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown if a merge fails because no merge base could be determined.
|
||||||
|
*/
|
||||||
|
public class NoMergeBaseException extends IOException {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private MergeBaseFailureReason reason;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enum listing the different reason why no merge base could be
|
||||||
|
* determined.
|
||||||
|
*/
|
||||||
|
public static enum MergeBaseFailureReason {
|
||||||
|
/**
|
||||||
|
* Multiple merge bases have been found (e.g. the commits to be merged
|
||||||
|
* have multiple common predecessors) but the merge strategy doesn't
|
||||||
|
* support this (e.g. ResolveMerge)
|
||||||
|
*/
|
||||||
|
MULTIPLE_MERGE_BASES_NOT_SUPPORTED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of merge bases exceeds {@link RecursiveMerger#MAX_BASES}
|
||||||
|
*/
|
||||||
|
TOO_MANY_MERGE_BASES,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In order to find a single merge base it may required to merge
|
||||||
|
* together multiple common predecessors. If during these merges
|
||||||
|
* conflicts occur the merge fails with this reason
|
||||||
|
*/
|
||||||
|
CONFLICTS_DURING_MERGE_BASE_CALCULATION
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a NoMergeBase exception
|
||||||
|
*
|
||||||
|
* @param reason
|
||||||
|
* the reason why no merge base could be found
|
||||||
|
* @param message
|
||||||
|
* a text describing the problem
|
||||||
|
*/
|
||||||
|
public NoMergeBaseException(MergeBaseFailureReason reason, String message) {
|
||||||
|
super(MessageFormat.format(JGitText.get().noMergeBase,
|
||||||
|
reason.toString(), message));
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a NoMergeBase exception
|
||||||
|
*
|
||||||
|
* @param reason
|
||||||
|
* the reason why no merge base could be found
|
||||||
|
* @param message
|
||||||
|
* a text describing the problem
|
||||||
|
* @param why
|
||||||
|
* an exception causing this error
|
||||||
|
*/
|
||||||
|
public NoMergeBaseException(MergeBaseFailureReason reason, String message,
|
||||||
|
Throwable why) {
|
||||||
|
super(MessageFormat.format(JGitText.get().noMergeBase,
|
||||||
|
reason.toString(), message));
|
||||||
|
this.reason = reason;
|
||||||
|
initCause(why);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the reason why no merge base could be found
|
||||||
|
*/
|
||||||
|
public MergeBaseFailureReason getReason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2010, 2013 Sasa Zivkov <sasa.zivkov@sap.com>
|
* Copyright (C) 2010, 2013 Sasa Zivkov <sasa.zivkov@sap.com>
|
||||||
|
* Copyright (C) 2012, Research In Motion Limited
|
||||||
* and other copyright owners as documented in the project's IP log.
|
* and other copyright owners as documented in the project's IP log.
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available
|
* This program and the accompanying materials are made available
|
||||||
|
@ -349,6 +350,8 @@ public static JGitText get() {
|
||||||
/***/ public String mergeStrategyAlreadyExistsAsDefault;
|
/***/ public String mergeStrategyAlreadyExistsAsDefault;
|
||||||
/***/ public String mergeStrategyDoesNotSupportHeads;
|
/***/ public String mergeStrategyDoesNotSupportHeads;
|
||||||
/***/ public String mergeUsingStrategyResultedInDescription;
|
/***/ public String mergeUsingStrategyResultedInDescription;
|
||||||
|
/***/ public String mergeRecursiveReturnedNoCommit;
|
||||||
|
/***/ public String mergeRecursiveTooManyMergeBasesFor;
|
||||||
/***/ public String minutesAgo;
|
/***/ public String minutesAgo;
|
||||||
/***/ public String missingAccesskey;
|
/***/ public String missingAccesskey;
|
||||||
/***/ public String missingConfigurationForKey;
|
/***/ public String missingConfigurationForKey;
|
||||||
|
@ -374,6 +377,7 @@ public static JGitText get() {
|
||||||
/***/ public String noClosingBracket;
|
/***/ public String noClosingBracket;
|
||||||
/***/ public String noHEADExistsAndNoExplicitStartingRevisionWasSpecified;
|
/***/ public String noHEADExistsAndNoExplicitStartingRevisionWasSpecified;
|
||||||
/***/ public String noHMACsupport;
|
/***/ public String noHMACsupport;
|
||||||
|
/***/ public String noMergeBase;
|
||||||
/***/ public String noMergeHeadSpecified;
|
/***/ public String noMergeHeadSpecified;
|
||||||
/***/ public String noSuchRef;
|
/***/ public String noSuchRef;
|
||||||
/***/ public String notABoolean;
|
/***/ public String notABoolean;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2008-2009, Google Inc.
|
* Copyright (C) 2008-2009, Google Inc.
|
||||||
* Copyright (C) 2009, Matthias Sohn <matthias.sohn@sap.com>
|
* Copyright (C) 2009, Matthias Sohn <matthias.sohn@sap.com>
|
||||||
|
* Copyright (C) 2012, Research In Motion Limited
|
||||||
* and other copyright owners as documented in the project's IP log.
|
* and other copyright owners as documented in the project's IP log.
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available
|
* This program and the accompanying materials are made available
|
||||||
|
@ -66,9 +67,18 @@ public abstract class MergeStrategy {
|
||||||
/** Simple strategy to merge paths, without simultaneous edits. */
|
/** Simple strategy to merge paths, without simultaneous edits. */
|
||||||
public static final ThreeWayMergeStrategy SIMPLE_TWO_WAY_IN_CORE = new StrategySimpleTwoWayInCore();
|
public static final ThreeWayMergeStrategy SIMPLE_TWO_WAY_IN_CORE = new StrategySimpleTwoWayInCore();
|
||||||
|
|
||||||
/** Simple strategy to merge paths. It tries to merge also contents. Multiple merge bases are not supported */
|
/**
|
||||||
|
* Simple strategy to merge paths. It tries to merge also contents. Multiple
|
||||||
|
* merge bases are not supported
|
||||||
|
*/
|
||||||
public static final ThreeWayMergeStrategy RESOLVE = new StrategyResolve();
|
public static final ThreeWayMergeStrategy RESOLVE = new StrategyResolve();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive strategy to merge paths. It tries to merge also contents.
|
||||||
|
* Multiple merge bases are supported
|
||||||
|
*/
|
||||||
|
public static final ThreeWayMergeStrategy RECURSIVE = new StrategyRecursive();
|
||||||
|
|
||||||
private static final HashMap<String, MergeStrategy> STRATEGIES = new HashMap<String, MergeStrategy>();
|
private static final HashMap<String, MergeStrategy> STRATEGIES = new HashMap<String, MergeStrategy>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -76,6 +86,7 @@ public abstract class MergeStrategy {
|
||||||
register(THEIRS);
|
register(THEIRS);
|
||||||
register(SIMPLE_TWO_WAY_IN_CORE);
|
register(SIMPLE_TWO_WAY_IN_CORE);
|
||||||
register(RESOLVE);
|
register(RESOLVE);
|
||||||
|
register(RECURSIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,7 +114,8 @@ public static void register(final MergeStrategy imp) {
|
||||||
public static synchronized void register(final String name,
|
public static synchronized void register(final String name,
|
||||||
final MergeStrategy imp) {
|
final MergeStrategy imp) {
|
||||||
if (STRATEGIES.containsKey(name))
|
if (STRATEGIES.containsKey(name))
|
||||||
throw new IllegalArgumentException(MessageFormat.format(JGitText.get().mergeStrategyAlreadyExistsAsDefault, name));
|
throw new IllegalArgumentException(MessageFormat.format(
|
||||||
|
JGitText.get().mergeStrategyAlreadyExistsAsDefault, name));
|
||||||
STRATEGIES.put(name, imp);
|
STRATEGIES.put(name, imp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,8 @@
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
|
|
||||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||||
|
import org.eclipse.jgit.errors.NoMergeBaseException;
|
||||||
|
import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.lib.AnyObjectId;
|
import org.eclipse.jgit.lib.AnyObjectId;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
|
@ -186,19 +188,19 @@ public boolean merge(final AnyObjectId... tips) throws IOException {
|
||||||
/**
|
/**
|
||||||
* Create an iterator to walk the merge base of two commits.
|
* Create an iterator to walk the merge base of two commits.
|
||||||
*
|
*
|
||||||
* @param aIdx
|
* @param a
|
||||||
* index of the first commit in {@link #sourceObjects}.
|
* the first commit in {@link #sourceObjects}.
|
||||||
* @param bIdx
|
* @param b
|
||||||
* index of the second commit in {@link #sourceObjects}.
|
* the second commit in {@link #sourceObjects}.
|
||||||
* @return the new iterator
|
* @return the new iterator
|
||||||
* @throws IncorrectObjectTypeException
|
* @throws IncorrectObjectTypeException
|
||||||
* one of the input objects is not a commit.
|
* one of the input objects is not a commit.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* objects are missing or multiple merge bases were found.
|
* objects are missing or multiple merge bases were found.
|
||||||
*/
|
*/
|
||||||
protected AbstractTreeIterator mergeBase(final int aIdx, final int bIdx)
|
protected AbstractTreeIterator mergeBase(RevCommit a, RevCommit b)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
RevCommit base = getBaseCommit(aIdx, bIdx);
|
RevCommit base = getBaseCommit(a, b);
|
||||||
return (base == null) ? new EmptyTreeIterator() : openTree(base.getTree());
|
return (base == null) ? new EmptyTreeIterator() : openTree(base.getTree());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,18 +226,38 @@ public RevCommit getBaseCommit(final int aIdx, final int bIdx)
|
||||||
if (sourceCommits[bIdx] == null)
|
if (sourceCommits[bIdx] == null)
|
||||||
throw new IncorrectObjectTypeException(sourceObjects[bIdx],
|
throw new IncorrectObjectTypeException(sourceObjects[bIdx],
|
||||||
Constants.TYPE_COMMIT);
|
Constants.TYPE_COMMIT);
|
||||||
|
return getBaseCommit(sourceCommits[aIdx], sourceCommits[bIdx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the merge base of two commits.
|
||||||
|
*
|
||||||
|
* @param a
|
||||||
|
* the first commit in {@link #sourceObjects}.
|
||||||
|
* @param b
|
||||||
|
* the second commit in {@link #sourceObjects}.
|
||||||
|
* @return the merge base of two commits
|
||||||
|
* @throws IncorrectObjectTypeException
|
||||||
|
* one of the input objects is not a commit.
|
||||||
|
* @throws IOException
|
||||||
|
* objects are missing or multiple merge bases were found.
|
||||||
|
*/
|
||||||
|
protected RevCommit getBaseCommit(RevCommit a, RevCommit b)
|
||||||
|
throws IncorrectObjectTypeException, IOException {
|
||||||
walk.reset();
|
walk.reset();
|
||||||
walk.setRevFilter(RevFilter.MERGE_BASE);
|
walk.setRevFilter(RevFilter.MERGE_BASE);
|
||||||
walk.markStart(sourceCommits[aIdx]);
|
walk.markStart(a);
|
||||||
walk.markStart(sourceCommits[bIdx]);
|
walk.markStart(b);
|
||||||
final RevCommit base = walk.next();
|
final RevCommit base = walk.next();
|
||||||
if (base == null)
|
if (base == null)
|
||||||
return null;
|
return null;
|
||||||
final RevCommit base2 = walk.next();
|
final RevCommit base2 = walk.next();
|
||||||
if (base2 != null) {
|
if (base2 != null) {
|
||||||
throw new IOException(MessageFormat.format(JGitText.get().multipleMergeBasesFor
|
throw new NoMergeBaseException(
|
||||||
, sourceCommits[aIdx].name(), sourceCommits[bIdx].name()
|
MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED,
|
||||||
, base.name(), base2.name()));
|
MessageFormat.format(
|
||||||
|
JGitText.get().multipleMergeBasesFor, a.name(), b.name(),
|
||||||
|
base.name(), base2.name()));
|
||||||
}
|
}
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,274 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2012, Research In Motion Limited
|
||||||
|
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
|
||||||
|
* and other copyright owners as documented in the project's IP log.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available
|
||||||
|
* under the terms of the Eclipse Distribution License v1.0 which
|
||||||
|
* accompanies this distribution, is reproduced below, and is
|
||||||
|
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or
|
||||||
|
* without modification, are permitted provided that the following
|
||||||
|
* conditions are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above
|
||||||
|
* copyright notice, this list of conditions and the following
|
||||||
|
* disclaimer in the documentation and/or other materials provided
|
||||||
|
* with the distribution.
|
||||||
|
*
|
||||||
|
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
||||||
|
* names of its contributors may be used to endorse or promote
|
||||||
|
* products derived from this software without specific prior
|
||||||
|
* written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||||
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Contributors:
|
||||||
|
* George Young - initial API and implementation
|
||||||
|
* Christian Halstrick - initial API and implementation
|
||||||
|
*/
|
||||||
|
package org.eclipse.jgit.merge;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.dircache.DirCache;
|
||||||
|
import org.eclipse.jgit.dircache.DirCacheBuilder;
|
||||||
|
import org.eclipse.jgit.dircache.DirCacheEntry;
|
||||||
|
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||||
|
import org.eclipse.jgit.errors.NoMergeBaseException;
|
||||||
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
|
import org.eclipse.jgit.lib.CommitBuilder;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.eclipse.jgit.revwalk.filter.RevFilter;
|
||||||
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
|
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A three-way merger performing a content-merge if necessary across multiple
|
||||||
|
* bases using recursion
|
||||||
|
*
|
||||||
|
* This merger extends the resolve merger and does several things differently:
|
||||||
|
*
|
||||||
|
* - allow more than one merge base, up to a maximum
|
||||||
|
*
|
||||||
|
* - uses "Lists" instead of Arrays for chained types
|
||||||
|
*
|
||||||
|
* - recursively merges the merge bases together to compute a usable base
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RecursiveMerger extends ResolveMerger {
|
||||||
|
static Logger log = Logger.getLogger(RecursiveMerger.class.toString());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of merge bases. This merge will stop when the number
|
||||||
|
* of merge bases exceeds this value
|
||||||
|
*/
|
||||||
|
public final int MAX_BASES = 200;
|
||||||
|
|
||||||
|
private PersonIdent ident = new PersonIdent(db);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normal recursive merge when you want a choice of DirCache placement
|
||||||
|
* inCore
|
||||||
|
*
|
||||||
|
* @param local
|
||||||
|
* @param inCore
|
||||||
|
*/
|
||||||
|
protected RecursiveMerger(Repository local, boolean inCore) {
|
||||||
|
super(local, inCore);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normal recursive merge, implies not inCore
|
||||||
|
*
|
||||||
|
* @param local
|
||||||
|
*/
|
||||||
|
protected RecursiveMerger(Repository local) {
|
||||||
|
this(local, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single base commit for two given commits. If the two source commits
|
||||||
|
* have more than one base commit recursively merge the base commits
|
||||||
|
* together until you end up with a single base commit.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IncorrectObjectTypeException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected RevCommit getBaseCommit(RevCommit a, RevCommit b)
|
||||||
|
throws IncorrectObjectTypeException, IOException {
|
||||||
|
return getBaseCommit(a, b, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single base commit for two given commits. If the two source commits
|
||||||
|
* have more than one base commit recursively merge the base commits
|
||||||
|
* together until a virtual common base commit has been found.
|
||||||
|
*
|
||||||
|
* @param a
|
||||||
|
* the first commit to be merged
|
||||||
|
* @param b
|
||||||
|
* the second commit to be merged
|
||||||
|
* @param callDepth
|
||||||
|
* the callDepth when this method is called recursively
|
||||||
|
* @return the merge base of two commits
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IncorrectObjectTypeException
|
||||||
|
* one of the input objects is not a commit.
|
||||||
|
* @throws NoMergeBaseException
|
||||||
|
* too many merge bases are found or the computation of a common
|
||||||
|
* merge base failed (e.g. because of a conflict).
|
||||||
|
*/
|
||||||
|
protected RevCommit getBaseCommit(RevCommit a, RevCommit b, int callDepth)
|
||||||
|
throws IOException {
|
||||||
|
ArrayList<RevCommit> baseCommits = new ArrayList<RevCommit>();
|
||||||
|
walk.reset();
|
||||||
|
walk.setRevFilter(RevFilter.MERGE_BASE);
|
||||||
|
walk.markStart(a);
|
||||||
|
walk.markStart(b);
|
||||||
|
RevCommit c;
|
||||||
|
while ((c = walk.next()) != null)
|
||||||
|
baseCommits.add(c);
|
||||||
|
|
||||||
|
if (baseCommits.isEmpty())
|
||||||
|
return null;
|
||||||
|
if (baseCommits.size() == 1)
|
||||||
|
return baseCommits.get(0);
|
||||||
|
if (baseCommits.size() >= MAX_BASES)
|
||||||
|
throw new NoMergeBaseException(NoMergeBaseException.MergeBaseFailureReason.TOO_MANY_MERGE_BASES, MessageFormat.format(
|
||||||
|
JGitText.get().mergeRecursiveTooManyMergeBasesFor,
|
||||||
|
Integer.valueOf(MAX_BASES), a.name(), b.name(),
|
||||||
|
Integer.valueOf(baseCommits.size())));
|
||||||
|
|
||||||
|
// We know we have more than one base commit. We have to do merges now
|
||||||
|
// to determine a single base commit. We don't want to spoil the current
|
||||||
|
// dircache and working tree with the results of this intermediate
|
||||||
|
// merges. Therefore set the dircache to a new in-memory dircache and
|
||||||
|
// disable that we update the working-tree. We set this back to the
|
||||||
|
// original values once a single base commit is created.
|
||||||
|
RevCommit currentBase = baseCommits.get(0);
|
||||||
|
DirCache oldDircache = dircache;
|
||||||
|
boolean oldIncore = inCore;
|
||||||
|
WorkingTreeIterator oldWTreeIt = workingTreeIterator;
|
||||||
|
workingTreeIterator = null;
|
||||||
|
try {
|
||||||
|
dircache = dircacheFromTree(currentBase.getTree());
|
||||||
|
inCore = true;
|
||||||
|
|
||||||
|
List<RevCommit> parents = new ArrayList<RevCommit>();
|
||||||
|
parents.add(currentBase);
|
||||||
|
for (int commitIdx = 1; commitIdx < baseCommits.size(); commitIdx++) {
|
||||||
|
RevCommit nextBase = baseCommits.get(commitIdx);
|
||||||
|
if (commitIdx >= MAX_BASES)
|
||||||
|
throw new NoMergeBaseException(
|
||||||
|
NoMergeBaseException.MergeBaseFailureReason.TOO_MANY_MERGE_BASES,
|
||||||
|
MessageFormat.format(
|
||||||
|
JGitText.get().mergeRecursiveTooManyMergeBasesFor,
|
||||||
|
Integer.valueOf(MAX_BASES), a.name(), b.name(),
|
||||||
|
Integer.valueOf(baseCommits.size())));
|
||||||
|
parents.add(nextBase);
|
||||||
|
if (mergeTrees(
|
||||||
|
openTree(getBaseCommit(currentBase, nextBase,
|
||||||
|
callDepth + 1).getTree()),
|
||||||
|
currentBase.getTree(),
|
||||||
|
nextBase.getTree()))
|
||||||
|
currentBase = createCommitForTree(resultTree, parents);
|
||||||
|
else
|
||||||
|
throw new NoMergeBaseException(
|
||||||
|
NoMergeBaseException.MergeBaseFailureReason.CONFLICTS_DURING_MERGE_BASE_CALCULATION,
|
||||||
|
MessageFormat.format(
|
||||||
|
JGitText.get().mergeRecursiveTooManyMergeBasesFor,
|
||||||
|
Integer.valueOf(MAX_BASES), a.name(),
|
||||||
|
b.name(),
|
||||||
|
Integer.valueOf(baseCommits.size())));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
inCore = oldIncore;
|
||||||
|
dircache = oldDircache;
|
||||||
|
workingTreeIterator = oldWTreeIt;
|
||||||
|
}
|
||||||
|
return currentBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new commit by explicitly specifying the content tree and the
|
||||||
|
* parents. The commit message is not set and author/committer are set to
|
||||||
|
* the current user.
|
||||||
|
*
|
||||||
|
* @param tree
|
||||||
|
* the tree this commit should capture
|
||||||
|
* @param parents
|
||||||
|
* the list of parent commits
|
||||||
|
* @return a new (persisted) commit
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private RevCommit createCommitForTree(ObjectId tree, List<RevCommit> parents)
|
||||||
|
throws IOException {
|
||||||
|
CommitBuilder c = new CommitBuilder();
|
||||||
|
c.setParentIds(parents);
|
||||||
|
c.setTreeId(tree);
|
||||||
|
c.setAuthor(ident);
|
||||||
|
c.setCommitter(ident);
|
||||||
|
ObjectInserter odi = db.newObjectInserter();
|
||||||
|
ObjectId newCommitId = odi.insert(c);
|
||||||
|
odi.flush();
|
||||||
|
RevCommit ret = walk.lookupCommit(newCommitId);
|
||||||
|
walk.parseHeaders(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new in memory dircache which has the same content as a given
|
||||||
|
* tree.
|
||||||
|
*
|
||||||
|
* @param treeId
|
||||||
|
* the tree which should be used to fill the dircache
|
||||||
|
* @return a new in memory dircache
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private DirCache dircacheFromTree(ObjectId treeId) throws IOException {
|
||||||
|
DirCache ret = DirCache.newInCore();
|
||||||
|
DirCacheBuilder builder = ret.builder();
|
||||||
|
TreeWalk tw = new TreeWalk(db);
|
||||||
|
tw.addTree(treeId);
|
||||||
|
tw.setRecursive(true);
|
||||||
|
while (tw.next()) {
|
||||||
|
DirCacheEntry e = new DirCacheEntry(tw.getRawPath());
|
||||||
|
e.setFileMode(tw.getFileMode(0));
|
||||||
|
e.setObjectId(tw.getObjectId(0));
|
||||||
|
builder.add(e);
|
||||||
|
}
|
||||||
|
builder.finish();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>,
|
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>,
|
||||||
* Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com>
|
* Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com>
|
||||||
|
* Copyright (C) 2012, Research In Motion Limited
|
||||||
* and other copyright owners as documented in the project's IP log.
|
* and other copyright owners as documented in the project's IP log.
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available
|
* This program and the accompanying materials are made available
|
||||||
|
@ -80,6 +81,8 @@
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectReader;
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.revwalk.RevTree;
|
||||||
|
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
|
||||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
|
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
|
||||||
import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
|
import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
|
||||||
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
|
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
|
||||||
|
@ -104,7 +107,10 @@ public enum MergeFailureReason {
|
||||||
|
|
||||||
private NameConflictTreeWalk tw;
|
private NameConflictTreeWalk tw;
|
||||||
|
|
||||||
private String commitNames[];
|
/**
|
||||||
|
* string versions of a list of commit SHA1s
|
||||||
|
*/
|
||||||
|
protected String commitNames[];
|
||||||
|
|
||||||
private static final int T_BASE = 0;
|
private static final int T_BASE = 0;
|
||||||
|
|
||||||
|
@ -118,7 +124,10 @@ public enum MergeFailureReason {
|
||||||
|
|
||||||
private DirCacheBuilder builder;
|
private DirCacheBuilder builder;
|
||||||
|
|
||||||
private ObjectId resultTree;
|
/**
|
||||||
|
* merge result as tree
|
||||||
|
*/
|
||||||
|
protected ObjectId resultTree;
|
||||||
|
|
||||||
private List<String> unmergedPaths = new ArrayList<String>();
|
private List<String> unmergedPaths = new ArrayList<String>();
|
||||||
|
|
||||||
|
@ -134,13 +143,38 @@ public enum MergeFailureReason {
|
||||||
|
|
||||||
private boolean enterSubtree;
|
private boolean enterSubtree;
|
||||||
|
|
||||||
private boolean inCore;
|
/**
|
||||||
|
* Set to true if this merge should work in-memory. The repos dircache and
|
||||||
|
* workingtree are not touched by this method. Eventually needed files are
|
||||||
|
* created as temporary files and a new empty, in-memory dircache will be
|
||||||
|
* used instead the repo's one. Often used for bare repos where the repo
|
||||||
|
* doesn't even have a workingtree and dircache.
|
||||||
|
*/
|
||||||
|
protected boolean inCore;
|
||||||
|
|
||||||
private DirCache dircache;
|
/**
|
||||||
|
* Set to true if this merger should use the default dircache of the
|
||||||
|
* repository and should handle locking and unlocking of the dircache. If
|
||||||
|
* this merger should work in-core or if an explicit dircache was specified
|
||||||
|
* during construction then this field is set to false.
|
||||||
|
*/
|
||||||
|
protected boolean implicitDirCache;
|
||||||
|
|
||||||
private WorkingTreeIterator workingTreeIterator;
|
/**
|
||||||
|
* Directory cache
|
||||||
|
*/
|
||||||
|
protected DirCache dircache;
|
||||||
|
|
||||||
private MergeAlgorithm mergeAlgorithm;
|
/**
|
||||||
|
* The iterator to access the working tree. If set to <code>null</code> this
|
||||||
|
* merger will not touch the working tree.
|
||||||
|
*/
|
||||||
|
protected WorkingTreeIterator workingTreeIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* our merge algorithm
|
||||||
|
*/
|
||||||
|
protected MergeAlgorithm mergeAlgorithm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param local
|
* @param local
|
||||||
|
@ -153,11 +187,14 @@ protected ResolveMerger(Repository local, boolean inCore) {
|
||||||
ConfigConstants.CONFIG_KEY_ALGORITHM,
|
ConfigConstants.CONFIG_KEY_ALGORITHM,
|
||||||
SupportedAlgorithm.HISTOGRAM);
|
SupportedAlgorithm.HISTOGRAM);
|
||||||
mergeAlgorithm = new MergeAlgorithm(DiffAlgorithm.getAlgorithm(diffAlg));
|
mergeAlgorithm = new MergeAlgorithm(DiffAlgorithm.getAlgorithm(diffAlg));
|
||||||
commitNames = new String[] { "BASE", "OURS", "THEIRS" };
|
commitNames = new String[] { "BASE", "OURS", "THEIRS" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
||||||
this.inCore = inCore;
|
this.inCore = inCore;
|
||||||
|
|
||||||
if (inCore) {
|
if (inCore) {
|
||||||
|
implicitDirCache = false;
|
||||||
dircache = DirCache.newInCore();
|
dircache = DirCache.newInCore();
|
||||||
|
} else {
|
||||||
|
implicitDirCache = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,67 +207,11 @@ protected ResolveMerger(Repository local) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean mergeImpl() throws IOException {
|
protected boolean mergeImpl() throws IOException {
|
||||||
boolean implicitDirCache = false;
|
if (implicitDirCache)
|
||||||
|
|
||||||
if (dircache == null) {
|
|
||||||
dircache = getRepository().lockDirCache();
|
dircache = getRepository().lockDirCache();
|
||||||
implicitDirCache = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
builder = dircache.builder();
|
return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1]);
|
||||||
DirCacheBuildIterator buildIt = new DirCacheBuildIterator(builder);
|
|
||||||
|
|
||||||
tw = new NameConflictTreeWalk(db);
|
|
||||||
tw.addTree(mergeBase());
|
|
||||||
tw.addTree(sourceTrees[0]);
|
|
||||||
tw.addTree(sourceTrees[1]);
|
|
||||||
tw.addTree(buildIt);
|
|
||||||
if (workingTreeIterator != null)
|
|
||||||
tw.addTree(workingTreeIterator);
|
|
||||||
|
|
||||||
while (tw.next()) {
|
|
||||||
if (!processEntry(
|
|
||||||
tw.getTree(T_BASE, CanonicalTreeParser.class),
|
|
||||||
tw.getTree(T_OURS, CanonicalTreeParser.class),
|
|
||||||
tw.getTree(T_THEIRS, CanonicalTreeParser.class),
|
|
||||||
tw.getTree(T_INDEX, DirCacheBuildIterator.class),
|
|
||||||
(workingTreeIterator == null) ? null : tw.getTree(T_FILE, WorkingTreeIterator.class))) {
|
|
||||||
cleanUp();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (tw.isSubtree() && enterSubtree)
|
|
||||||
tw.enterSubtree();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!inCore) {
|
|
||||||
// No problem found. The only thing left to be done is to
|
|
||||||
// checkout all files from "theirs" which have been selected to
|
|
||||||
// go into the new index.
|
|
||||||
checkout();
|
|
||||||
|
|
||||||
// All content-merges are successfully done. If we can now write the
|
|
||||||
// new index we are on quite safe ground. Even if the checkout of
|
|
||||||
// files coming from "theirs" fails the user can work around such
|
|
||||||
// failures by checking out the index again.
|
|
||||||
if (!builder.commit()) {
|
|
||||||
cleanUp();
|
|
||||||
throw new IndexWriteException();
|
|
||||||
}
|
|
||||||
builder = null;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
builder.finish();
|
|
||||||
builder = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getUnmergedPaths().isEmpty() && !failed()) {
|
|
||||||
resultTree = dircache.writeTree(getObjectInserter());
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
resultTree = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
if (implicitDirCache)
|
if (implicitDirCache)
|
||||||
dircache.unlock();
|
dircache.unlock();
|
||||||
|
@ -279,14 +260,15 @@ private void createDir(File f) throws IOException {
|
||||||
/**
|
/**
|
||||||
* Reverts the worktree after an unsuccessful merge. We know that for all
|
* Reverts the worktree after an unsuccessful merge. We know that for all
|
||||||
* modified files the old content was in the old index and the index
|
* modified files the old content was in the old index and the index
|
||||||
* contained only stage 0. In case if inCore operation just clear
|
* contained only stage 0. In case if inCore operation just clear the
|
||||||
* the history of modified files.
|
* history of modified files.
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws CorruptObjectException
|
* @throws CorruptObjectException
|
||||||
* @throws NoWorkTreeException
|
* @throws NoWorkTreeException
|
||||||
*/
|
*/
|
||||||
private void cleanUp() throws NoWorkTreeException, CorruptObjectException, IOException {
|
private void cleanUp() throws NoWorkTreeException, CorruptObjectException,
|
||||||
|
IOException {
|
||||||
if (inCore) {
|
if (inCore) {
|
||||||
modifiedFiles.clear();
|
modifiedFiles.clear();
|
||||||
return;
|
return;
|
||||||
|
@ -298,7 +280,10 @@ private void cleanUp() throws NoWorkTreeException, CorruptObjectException, IOExc
|
||||||
while(mpathsIt.hasNext()) {
|
while(mpathsIt.hasNext()) {
|
||||||
String mpath=mpathsIt.next();
|
String mpath=mpathsIt.next();
|
||||||
DirCacheEntry entry = dc.getEntry(mpath);
|
DirCacheEntry entry = dc.getEntry(mpath);
|
||||||
FileOutputStream fos = new FileOutputStream(new File(db.getWorkTree(), mpath));
|
if (entry == null)
|
||||||
|
continue;
|
||||||
|
FileOutputStream fos = new FileOutputStream(new File(
|
||||||
|
db.getWorkTree(), mpath));
|
||||||
try {
|
try {
|
||||||
or.open(entry.getObjectId()).copyTo(fos);
|
or.open(entry.getObjectId()).copyTo(fos);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -610,6 +595,9 @@ private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isIndexDirty() {
|
private boolean isIndexDirty() {
|
||||||
|
if (inCore)
|
||||||
|
return false;
|
||||||
|
|
||||||
final int modeI = tw.getRawMode(T_INDEX);
|
final int modeI = tw.getRawMode(T_INDEX);
|
||||||
final int modeO = tw.getRawMode(T_OURS);
|
final int modeO = tw.getRawMode(T_OURS);
|
||||||
|
|
||||||
|
@ -623,7 +611,7 @@ private boolean isIndexDirty() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isWorktreeDirty(WorkingTreeIterator work) {
|
private boolean isWorktreeDirty(WorkingTreeIterator work) {
|
||||||
if (inCore || work == null)
|
if (work == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
final int modeF = tw.getRawMode(T_FILE);
|
final int modeF = tw.getRawMode(T_FILE);
|
||||||
|
@ -862,19 +850,20 @@ public boolean failed() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the DirCache which shall be used by this merger. If the DirCache is
|
* Sets the DirCache which shall be used by this merger. If the DirCache is
|
||||||
* not set explicitly this merger will implicitly get and lock a default
|
* not set explicitly and if this merger doesn't work in-core, this merger
|
||||||
* DirCache. If the DirCache is explicitly set the caller is responsible to
|
* will implicitly get and lock a default DirCache. If the DirCache is
|
||||||
* lock it in advance. Finally the merger will call
|
* explicitly set the caller is responsible to lock it in advance. Finally
|
||||||
* {@link DirCache#commit()} which requires that the DirCache is locked. If
|
* the merger will call {@link DirCache#commit()} which requires that the
|
||||||
* the {@link #mergeImpl()} returns without throwing an exception the lock
|
* DirCache is locked. If the {@link #mergeImpl()} returns without throwing
|
||||||
* will be released. In case of exceptions the caller is responsible to
|
* an exception the lock will be released. In case of exceptions the caller
|
||||||
* release the lock.
|
* is responsible to release the lock.
|
||||||
*
|
*
|
||||||
* @param dc
|
* @param dc
|
||||||
* the DirCache to set
|
* the DirCache to set
|
||||||
*/
|
*/
|
||||||
public void setDirCache(DirCache dc) {
|
public void setDirCache(DirCache dc) {
|
||||||
this.dircache = dc;
|
this.dircache = dc;
|
||||||
|
implicitDirCache = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -891,4 +880,73 @@ public void setDirCache(DirCache dc) {
|
||||||
public void setWorkingTreeIterator(WorkingTreeIterator workingTreeIterator) {
|
public void setWorkingTreeIterator(WorkingTreeIterator workingTreeIterator) {
|
||||||
this.workingTreeIterator = workingTreeIterator;
|
this.workingTreeIterator = workingTreeIterator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resolve conflict way of three way merging
|
||||||
|
*
|
||||||
|
* @param baseTree
|
||||||
|
* @param headTree
|
||||||
|
* @param mergeTree
|
||||||
|
* @return whether the trees merged cleanly
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected boolean mergeTrees(AbstractTreeIterator baseTree,
|
||||||
|
RevTree headTree, RevTree mergeTree) throws IOException {
|
||||||
|
|
||||||
|
builder = dircache.builder();
|
||||||
|
DirCacheBuildIterator buildIt = new DirCacheBuildIterator(builder);
|
||||||
|
|
||||||
|
tw = new NameConflictTreeWalk(db);
|
||||||
|
tw.addTree(baseTree);
|
||||||
|
tw.addTree(headTree);
|
||||||
|
tw.addTree(mergeTree);
|
||||||
|
tw.addTree(buildIt);
|
||||||
|
if (workingTreeIterator != null)
|
||||||
|
tw.addTree(workingTreeIterator);
|
||||||
|
|
||||||
|
while (tw.next()) {
|
||||||
|
if (!processEntry(
|
||||||
|
tw.getTree(T_BASE, CanonicalTreeParser.class),
|
||||||
|
tw.getTree(T_OURS, CanonicalTreeParser.class),
|
||||||
|
tw.getTree(T_THEIRS, CanonicalTreeParser.class),
|
||||||
|
tw.getTree(T_INDEX, DirCacheBuildIterator.class),
|
||||||
|
(workingTreeIterator == null) ? null : tw.getTree(T_FILE,
|
||||||
|
WorkingTreeIterator.class))) {
|
||||||
|
cleanUp();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (tw.isSubtree() && enterSubtree)
|
||||||
|
tw.enterSubtree();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inCore) {
|
||||||
|
// No problem found. The only thing left to be done is to
|
||||||
|
// checkout all files from "theirs" which have been selected to
|
||||||
|
// go into the new index.
|
||||||
|
checkout();
|
||||||
|
|
||||||
|
// All content-merges are successfully done. If we can now write the
|
||||||
|
// new index we are on quite safe ground. Even if the checkout of
|
||||||
|
// files coming from "theirs" fails the user can work around such
|
||||||
|
// failures by checking out the index again.
|
||||||
|
if (!builder.commit()) {
|
||||||
|
cleanUp();
|
||||||
|
throw new IndexWriteException();
|
||||||
|
}
|
||||||
|
builder = null;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
builder.finish();
|
||||||
|
builder = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getUnmergedPaths().isEmpty() && !failed()) {
|
||||||
|
resultTree = dircache.writeTree(getObjectInserter());
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
resultTree = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2012, Research In Motion Limited
|
||||||
|
* and other copyright owners as documented in the project's IP log.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available
|
||||||
|
* under the terms of the Eclipse Distribution License v1.0 which
|
||||||
|
* accompanies this distribution, is reproduced below, and is
|
||||||
|
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or
|
||||||
|
* without modification, are permitted provided that the following
|
||||||
|
* conditions are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above
|
||||||
|
* copyright notice, this list of conditions and the following
|
||||||
|
* disclaimer in the documentation and/or other materials provided
|
||||||
|
* with the distribution.
|
||||||
|
*
|
||||||
|
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
||||||
|
* names of its contributors may be used to endorse or promote
|
||||||
|
* products derived from this software without specific prior
|
||||||
|
* written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||||
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.eclipse.jgit.merge;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A three-way merge strategy performing a content-merge if necessary
|
||||||
|
*/
|
||||||
|
public class StrategyRecursive extends StrategyResolve {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ThreeWayMerger newMerger(Repository db) {
|
||||||
|
return new RecursiveMerger(db, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ThreeWayMerger newMerger(Repository db, boolean inCore) {
|
||||||
|
return new RecursiveMerger(db, inCore);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "recursive";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2009, Google Inc.
|
* Copyright (C) 2009, Google Inc.
|
||||||
|
* Copyright (C) 2012, Research In Motion Limited
|
||||||
* and other copyright owners as documented in the project's IP log.
|
* and other copyright owners as documented in the project's IP log.
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available
|
* This program and the accompanying materials are made available
|
||||||
|
@ -118,6 +119,6 @@ public boolean merge(final AnyObjectId... tips) throws IOException {
|
||||||
protected AbstractTreeIterator mergeBase() throws IOException {
|
protected AbstractTreeIterator mergeBase() throws IOException {
|
||||||
if (baseTree != null)
|
if (baseTree != null)
|
||||||
return openTree(baseTree);
|
return openTree(baseTree);
|
||||||
return mergeBase(0, 1);
|
return mergeBase(sourceCommits[0], sourceCommits[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue