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>tags/v3.0.0.201305080800-m7
- gitattributes support | - gitattributes support | ||||
- Recursive merge strategy | |||||
Support | Support | ||||
------- | ------- |
/* | |||||
* 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(); | |||||
} | |||||
} | |||||
} |
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; | ||||
@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 { | ||||
} | } | ||||
} | } | ||||
/** | |||||
* 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 { |
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 | ||||
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} |
/* | |||||
* 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; | |||||
} | |||||
} |
/* | /* | ||||
* 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 | ||||
/***/ 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; | ||||
/***/ 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; |
/* | /* | ||||
* 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 | ||||
/** 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 { | ||||
register(THEIRS); | register(THEIRS); | ||||
register(SIMPLE_TWO_WAY_IN_CORE); | register(SIMPLE_TWO_WAY_IN_CORE); | ||||
register(RESOLVE); | register(RESOLVE); | ||||
register(RECURSIVE); | |||||
} | } | ||||
/** | /** | ||||
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); | ||||
} | } | ||||
/** | /** | ||||
* Create a new merge instance. | * Create a new merge instance. | ||||
* | |||||
* | |||||
* @param db | * @param db | ||||
* repository database the merger will read from, and eventually | * repository database the merger will read from, and eventually | ||||
* write results back to. | * write results back to. |
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; | ||||
/** | /** | ||||
* Create an iterator to walk the merge base of two commits. | * Create an iterator to walk the merge base of two commits. | ||||
* | * | ||||
* @param aIdx | |||||
* index of the first commit in {@link #sourceObjects}. | |||||
* @param bIdx | |||||
* index of the second commit in {@link #sourceObjects}. | |||||
* @param a | |||||
* the first commit in {@link #sourceObjects}. | |||||
* @param b | |||||
* 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()); | ||||
} | } | ||||
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(sourceCommits[bIdx]); | |||||
walk.markStart(a); | |||||
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 | |||||
, sourceCommits[aIdx].name(), sourceCommits[bIdx].name() | |||||
, base.name(), base2.name())); | |||||
throw new NoMergeBaseException( | |||||
MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED, | |||||
MessageFormat.format( | |||||
JGitText.get().multipleMergeBasesFor, a.name(), b.name(), | |||||
base.name(), base2.name())); | |||||
} | } | ||||
return base; | return base; | ||||
} | } |
/* | |||||
* 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; | |||||
} | |||||
} |
/* | /* | ||||
* 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 | ||||
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; | ||||
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; | ||||
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>(); | ||||
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; | |||||
/** | |||||
* 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 DirCache dircache; | |||||
/** | |||||
* Directory cache | |||||
*/ | |||||
protected DirCache dircache; | |||||
private WorkingTreeIterator workingTreeIterator; | |||||
/** | |||||
* The iterator to access the working tree. If set to <code>null</code> this | |||||
* merger will not touch the working tree. | |||||
*/ | |||||
protected WorkingTreeIterator workingTreeIterator; | |||||
private MergeAlgorithm mergeAlgorithm; | |||||
/** | |||||
* our merge algorithm | |||||
*/ | |||||
protected MergeAlgorithm mergeAlgorithm; | |||||
/** | /** | ||||
* @param local | * @param local | ||||
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; | |||||
} | } | ||||
} | } | ||||
@Override | @Override | ||||
protected boolean mergeImpl() throws IOException { | protected boolean mergeImpl() throws IOException { | ||||
boolean implicitDirCache = false; | |||||
if (dircache == null) { | |||||
if (implicitDirCache) | |||||
dircache = getRepository().lockDirCache(); | dircache = getRepository().lockDirCache(); | ||||
implicitDirCache = true; | |||||
} | |||||
try { | try { | ||||
builder = dircache.builder(); | |||||
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; | |||||
} | |||||
return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1]); | |||||
} finally { | } finally { | ||||
if (implicitDirCache) | if (implicitDirCache) | ||||
dircache.unlock(); | dircache.unlock(); | ||||
/** | /** | ||||
* 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 | |||||
* the history of modified files. | |||||
* contained only stage 0. In case if inCore operation just clear the | |||||
* 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; | ||||
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 { | ||||
} | } | ||||
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); | ||||
} | } | ||||
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); | ||||
/** | /** | ||||
* 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 | |||||
* DirCache. If the DirCache is explicitly set the caller is responsible to | |||||
* lock it in advance. Finally the merger will call | |||||
* {@link DirCache#commit()} which requires that the DirCache is locked. If | |||||
* the {@link #mergeImpl()} returns without throwing an exception the lock | |||||
* will be released. In case of exceptions the caller is responsible to | |||||
* release the lock. | |||||
* not set explicitly and if this merger doesn't work in-core, this merger | |||||
* will implicitly get and lock a default DirCache. If the DirCache is | |||||
* explicitly set the caller is responsible to lock it in advance. Finally | |||||
* the merger will call {@link DirCache#commit()} which requires that the | |||||
* DirCache is locked. If the {@link #mergeImpl()} returns without throwing | |||||
* an exception the lock will be released. In case of exceptions the caller | |||||
* 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; | |||||
} | } | ||||
/** | /** | ||||
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; | |||||
} | |||||
} | |||||
} | } |
/* | |||||
* 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"; | |||||
} | |||||
} |
/* | /* | ||||
* 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 | ||||
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]); | |||||
} | } | ||||
} | } |