From 906887a73586a7fa2ffdeaa8041e045cfbcfd7e2 Mon Sep 17 00:00:00 2001 From: Dmitry Fink Date: Tue, 21 Sep 2010 09:37:01 -0700 Subject: [PATCH] Extend merge support for bare repositories Optional inCore parameter to Resolver/Strategy will instruct it to perform all the operations in memory and avoid modifying working folder even if there is one. Change-Id: I5b873dead3682f79110f58d7806e43f50bcc5045 --- .../org/eclipse/jgit/merge/MergeStrategy.java | 14 ++ .../org/eclipse/jgit/merge/ResolveMerger.java | 123 ++++++++++++------ .../eclipse/jgit/merge/StrategyOneSided.java | 5 + .../eclipse/jgit/merge/StrategyResolve.java | 8 +- .../merge/StrategySimpleTwoWayInCore.java | 7 + .../jgit/merge/ThreeWayMergeStrategy.java | 3 + .../eclipse/jgit/merge/ThreeWayMerger.java | 12 ++ 7 files changed, 133 insertions(+), 39 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java index 28347e869..507e148ca 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java @@ -143,4 +143,18 @@ public static synchronized MergeStrategy[] get() { * @return the new merge instance which implements this strategy. */ public abstract Merger newMerger(Repository db); + + /** + * Create a new merge instance. + * + * @param db + * repository database the merger will read from, and eventually + * write results back to. + * @param inCore + * the merge will happen in memory, working folder will not be + * modified, in case of a non-trivial merge that requires manual + * resolution, the merger will fail. + * @return the new merge instance which implements this strategy. + */ + public abstract Merger newMerger(Repository db, boolean inCore); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java index 42f9849ee..ecec033fc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java @@ -128,6 +128,8 @@ public enum MergeFailureReason { private boolean enterSubtree; + private boolean inCore; + private DirCache dircache; private WorkingTreeIterator workingTreeIt; @@ -135,11 +137,24 @@ public enum MergeFailureReason { /** * @param local + * @param inCore */ - protected ResolveMerger(Repository local) { + protected ResolveMerger(Repository local, boolean inCore) { super(local); commitNames = new String[] { "BASE", "OURS", "THEIRS" }; oi = getObjectInserter(); + this.inCore = inCore; + + if (inCore) { + dircache = DirCache.newInCore(); + } + } + + /** + * @param local + */ + protected ResolveMerger(Repository local) { + this(local, false); } @Override @@ -178,22 +193,26 @@ protected boolean mergeImpl() throws IOException { tw.enterSubtree(); } - // 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; + if (!inCore) { + // 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; + + // 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(); + } else { + builder.finish(); + builder = null; + } - // 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(); if (getUnmergedPathes().isEmpty()) { resultTree = dircache.writeTree(oi); return true; @@ -234,13 +253,19 @@ private void createDir(File f) throws IOException { /** * 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 - * contained only stage 0 + * contained only stage 0. In case if inCore operation just clear + * the history of modified files. * * @throws IOException * @throws CorruptObjectException * @throws NoWorkTreeException */ private void cleanUp() throws NoWorkTreeException, CorruptObjectException, IOException { + if (inCore) { + modifiedFiles.clear(); + return; + } + DirCache dc = db.readDirCache(); ObjectReader or = db.getObjectDatabase().newReader(); Iterator mpathsIt=modifiedFiles.iterator(); @@ -391,14 +416,17 @@ private boolean processEntry(CanonicalTreeParser base, } if (nonTree(modeO) && nonTree(modeT)) { - // We are going to update the worktree. Make sure the worktree is - // not modified - if (work != null - && (!nonTree(work.getEntryRawMode()) || work.isModified( - index.getDirCacheEntry(), true, true, db.getFS()))) { - failingPathes.put(tw.getPathString(), - MergeFailureReason.DIRTY_WORKTREE); - return false; + if (!inCore) { + // We are going to update the worktree. Make sure the worktree + // is not modified + if (work != null + && (!nonTree(work.getEntryRawMode()) || work + .isModified(index.getDirCacheEntry(), true, + true, db.getFS()))) { + failingPathes.put(tw.getPathString(), + MergeFailureReason.DIRTY_WORKTREE); + return false; + } } if (!contentMerge(base, ours, theirs)) { @@ -421,24 +449,41 @@ private boolean contentMerge(CanonicalTreeParser base, getRawText(ours.getEntryObjectId(), db), getRawText(theirs.getEntryObjectId(), db)); - File workTree = db.getWorkTree(); - if (workTree == null) - // TODO: This should be handled by WorkingTreeIterators which - // support write operations - throw new UnsupportedOperationException(); + File of = null; + FileOutputStream fos; + if (!inCore) { + File workTree = db.getWorkTree(); + if (workTree == null) + // TODO: This should be handled by WorkingTreeIterators which + // support write operations + throw new UnsupportedOperationException(); - File of = new File(workTree, tw.getPathString()); - FileOutputStream fos = new FileOutputStream(of); - try { - fmt.formatMerge(fos, result, Arrays.asList(commitNames), - Constants.CHARACTER_ENCODING); - } finally { - fos.close(); + of = new File(workTree, tw.getPathString()); + fos = new FileOutputStream(of); + try { + fmt.formatMerge(fos, result, Arrays.asList(commitNames), + Constants.CHARACTER_ENCODING); + } finally { + fos.close(); + } } + else if (!result.containsConflicts()) { + // When working inCore, only trivial merges can be handled, + // so we generate objects only in conflict free cases + of = File.createTempFile("merge_", "_temp", null); + fos = new FileOutputStream(of); + try { + fmt.formatMerge(fos, result, Arrays.asList(commitNames), + Constants.CHARACTER_ENCODING); + } finally { + fos.close(); + } + } + if (result.containsConflicts()) { // a conflict occured, the file will contain conflict markers // the index will be populated with the three stages and only the - // workdir contains the halfways merged content + // workdir (if used) contains the halfways merged content add(tw.getRawPath(), base, DirCacheEntry.STAGE_1); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3); @@ -457,6 +502,8 @@ private boolean contentMerge(CanonicalTreeParser base, is)); } finally { is.close(); + if (inCore) + of.delete(); } builder.add(dce); return true; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java index c941af948..34bc9f5e4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java @@ -84,6 +84,11 @@ public Merger newMerger(final Repository db) { return new OneSide(db, treeIndex); } + @Override + public Merger newMerger(final Repository db, boolean inCore) { + return new OneSide(db, treeIndex); + } + static class OneSide extends Merger { private final int treeIndex; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java index 2885625bf..e37635eb5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java @@ -49,9 +49,15 @@ * A three-way merge strategy performing a content-merge if necessary */ public class StrategyResolve extends ThreeWayMergeStrategy { + @Override public ThreeWayMerger newMerger(Repository db) { - return new ResolveMerger(db); + return new ResolveMerger(db, false); + } + + @Override + public ThreeWayMerger newMerger(Repository db, boolean inCore) { + return new ResolveMerger(db, inCore); } @Override diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java index 29342a730..b8bd4758c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java @@ -83,6 +83,12 @@ public ThreeWayMerger newMerger(final Repository db) { return new InCoreMerger(db); } + @Override + public ThreeWayMerger newMerger(Repository db, boolean inCore) { + // This class is always inCore, so ignore the parameter + return newMerger(db); + } + private static class InCoreMerger extends ThreeWayMerger { private static final int T_BASE = 0; @@ -193,4 +199,5 @@ public ObjectId getResultTreeId() { return resultTree; } } + } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java index 343d8973e..c71590bb1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java @@ -49,4 +49,7 @@ public abstract class ThreeWayMergeStrategy extends MergeStrategy { @Override public abstract ThreeWayMerger newMerger(Repository db); + + @Override + public abstract ThreeWayMerger newMerger(Repository db, boolean inCore); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java index bb23d0ee8..9350adf6e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java @@ -66,6 +66,18 @@ protected ThreeWayMerger(final Repository local) { super(local); } + /** + * Create a new merge instance for a repository. + * + * @param local + * the repository this merger will read and write data on. + * @param inCore + * perform the merge in core with no working folder involved + */ + protected ThreeWayMerger(final Repository local, boolean inCore) { + this(local); + } + /** * Set the common ancestor tree. *