Mercurial > jhg
diff src/org/tmatesoft/hg/core/HgMergeCommand.java @ 707:42b88709e41d
Merge: support 'unresolved' resolution with MergeStateBuilder
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Fri, 16 Aug 2013 19:22:59 +0200 |
parents | cd5c87d96315 |
children | 4ffc17c0b534 |
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/core/HgMergeCommand.java Fri Aug 16 14:54:09 2013 +0200 +++ b/src/org/tmatesoft/hg/core/HgMergeCommand.java Fri Aug 16 19:22:59 2013 +0200 @@ -36,6 +36,7 @@ import org.tmatesoft.hg.internal.Transaction; import org.tmatesoft.hg.internal.WorkingDirFileWriter; import org.tmatesoft.hg.repo.HgChangelog; +import org.tmatesoft.hg.repo.HgManifest; import org.tmatesoft.hg.repo.HgParentChildMap; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.repo.HgRepositoryLock; @@ -86,9 +87,11 @@ final DirstateBuilder dirstateBuilder = new DirstateBuilder(implRepo); dirstateBuilder.fillFrom(new DirstateReader(implRepo, new Path.SimpleSource(repo.getSessionContext().getPathFactory(), cacheFiles))); final HgChangelog clog = repo.getChangelog(); - dirstateBuilder.parents(clog.getRevision(firstCset), clog.getRevision(secondCset)); + final Nodeid headCset1 = clog.getRevision(firstCset); + dirstateBuilder.parents(headCset1, clog.getRevision(secondCset)); // MergeStateBuilder mergeStateBuilder = new MergeStateBuilder(implRepo); + mergeStateBuilder.prepare(headCset1); ManifestRevision m1, m2, ma; m1 = new ManifestRevision(cacheRevs, cacheFiles).init(repo, firstCset); @@ -105,37 +108,38 @@ fileRevBase = ma.contains(f) ? ma.nodeid(f) : null; if (fileRevA.equals(fileRevB)) { HgFileRevision fr = new HgFileRevision(repo, fileRevA, m1.flags(f), f); - resolver.presentState(f, fr, fr); + resolver.presentState(f, fr, fr, null); mediator.same(fr, resolver); } else if (fileRevBase == fileRevA) { assert fileRevBase != null; HgFileRevision frBase = new HgFileRevision(repo, fileRevBase, ma.flags(f), f); HgFileRevision frSecond= new HgFileRevision(repo, fileRevB, m2.flags(f), f); - resolver.presentState(f, frBase, frSecond); + resolver.presentState(f, frBase, frSecond, frBase); mediator.fastForwardB(frBase, frSecond, resolver); } else if (fileRevBase == fileRevB) { assert fileRevBase != null; HgFileRevision frBase = new HgFileRevision(repo, fileRevBase, ma.flags(f), f); HgFileRevision frFirst = new HgFileRevision(repo, fileRevA, m1.flags(f), f); - resolver.presentState(f, frFirst, frBase); + resolver.presentState(f, frFirst, frBase, frBase); mediator.fastForwardA(frBase, frFirst, resolver); } else { HgFileRevision frBase = fileRevBase == null ? null : new HgFileRevision(repo, fileRevBase, ma.flags(f), f); HgFileRevision frFirst = new HgFileRevision(repo, fileRevA, m1.flags(f), f); HgFileRevision frSecond= new HgFileRevision(repo, fileRevB, m2.flags(f), f); - resolver.presentState(f, frFirst, frSecond); + resolver.presentState(f, frFirst, frSecond, frBase); mediator.resolve(frBase, frFirst, frSecond, resolver); } } else { // m2 doesn't contain the file, either new in m1, or deleted in m2 HgFileRevision frFirst = new HgFileRevision(repo, m1.nodeid(f), m1.flags(f), f); - resolver.presentState(f, frFirst, null); if (ma.contains(f)) { // deleted in m2 HgFileRevision frBase = new HgFileRevision(repo, ma.nodeid(f), ma.flags(f), f); + resolver.presentState(f, frFirst, null, frBase); mediator.onlyA(frBase, frFirst, resolver); } else { // new in m1 + resolver.presentState(f, frFirst, null, null); mediator.newInA(frFirst, resolver); } } @@ -147,13 +151,14 @@ } HgFileRevision frSecond= new HgFileRevision(repo, m2.nodeid(f), m2.flags(f), f); // file in m2 is either new or deleted in m1 - resolver.presentState(f, null, frSecond); if (ma.contains(f)) { // deleted in m1 HgFileRevision frBase = new HgFileRevision(repo, ma.nodeid(f), ma.flags(f), f); + resolver.presentState(f, null, frSecond, frBase); mediator.onlyB(frBase, frSecond, resolver); } else { // new in m2 + resolver.presentState(f, null, frSecond, null); mediator.newInB(frSecond, resolver); } resolver.apply(); @@ -162,9 +167,11 @@ transaction.commit(); } catch (HgRuntimeException ex) { transaction.rollback(); + mergeStateBuilder.abandon(); throw ex; } catch (HgIOException ex) { transaction.rollback(); + mergeStateBuilder.abandon(); throw ex; } } catch (HgRuntimeException ex) { @@ -255,33 +262,70 @@ * @throws IOException propagated exceptions from content */ public void use(InputStream content) throws IOException; + /** + * Do not use this file for resolution. Marks the file for deletion, if appropriate. + */ public void forget(HgFileRevision rev); - public void unresolved(); // record the file for later processing by 'hg resolve' + /** + * Record the file for later processing by 'hg resolve'. It's required + * that processed file present in both trunks. We need two file revisions + * to put an entry into merge/state file. + * + * XXX Perhaps, shall take two HgFileRevision arguments to facilitate + * extra control over what goes into merge/state and to ensure this method + * is not invoked when there are no conflicting revisions. + */ + public void unresolved(); } /** - * Base mediator implementation, with regular resolution + * Base mediator implementation, with regular resolution. + * Subclasses shall implement {@link #resolve(HgFileRevision, HgFileRevision, HgFileRevision, Resolver)} and + * may optionally provide extra logic (e.g. ask user) for other cases. */ @Experimental(reason="Provisional API. Work in progress") - public abstract class MediatorBase implements Mediator { + public abstract static class MediatorBase implements Mediator { + /** + * Implementation keeps this revision + */ public void same(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { resolver.use(rev); } + /** + * Implementation keeps file revision from first/left/A trunk. + * Subclasses may opt to {@link Resolver#forget(HgFileRevision) delete} it as it's done in second/right/B trunk. + */ public void onlyA(HgFileRevision base, HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { resolver.use(rev); } + /** + * Implementation restores file from second/right/B trunk. + * Subclasses may ask user to decide if it's necessary to do that + */ public void onlyB(HgFileRevision base, HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { resolver.use(rev); } + /** + * Implementation keeps this revision + */ public void newInA(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { resolver.use(rev); } + /** + * Implementation adds this revision. Subclasses my let user decide if it's necessary to add the file + */ public void newInB(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { resolver.use(rev); } + /** + * Implementation keeps latest revision + */ public void fastForwardA(HgFileRevision base, HgFileRevision first, Resolver resolver) throws HgCallbackTargetException { resolver.use(first); } + /** + * Implementation keeps latest revision + */ public void fastForwardB(HgFileRevision base, HgFileRevision second, Resolver resolver) throws HgCallbackTargetException { resolver.use(second); } @@ -295,6 +339,7 @@ private boolean changedDirstate; private HgFileRevision revA; private HgFileRevision revB; + private HgFileRevision revBase; private Path file; // resolutions: private HgFileRevision resolveUse, resolveForget; @@ -312,14 +357,15 @@ if (changedDirstate) { dirstateBuilder.serialize(tr); } - mergeStateBuilder.serialize(tr); + mergeStateBuilder.serialize(); } - void presentState(Path p, HgFileRevision revA, HgFileRevision revB) { + void presentState(Path p, HgFileRevision revA, HgFileRevision revB, HgFileRevision base) { assert revA != null || revB != null; file = p; this.revA = revA; this.revB = revB; + revBase = base; resolveUse = resolveForget = null; resolveContent = null; resolveMarkUnresolved = false; @@ -327,9 +373,18 @@ void apply() throws HgIOException, HgRuntimeException { if (resolveMarkUnresolved) { - mergeStateBuilder.unresolved(file); + HgFileRevision c = revBase; + if (revBase == null) { + // fake revision, null parent + c = new HgFileRevision(repo.getRepo(), Nodeid.NULL, HgManifest.Flags.RegularFile, file); + } + mergeStateBuilder.unresolved(file, revA, revB, c, revA.getFileFlags()); + changedDirstate = true; + dirstateBuilder.recordMergedExisting(file, revA.getPath()); } else if (resolveForget != null) { - if (resolveForget == revA) { + // it revision to forget comes from second/B trunk, shall record it as removed + // only when corresponding file in first/A trunk is missing (merge:_forgetremoved()) + if (resolveForget == revA || (resolveForget == revB && revA == null)) { changedDirstate = true; dirstateBuilder.recordRemoved(file); } @@ -381,10 +436,8 @@ assert resolveUse == null; assert resolveForget == null; try { - // cache new contents just to fail fast if there are troubles with content - final FileUtils fileUtils = new FileUtils(repo.getLog(), this); - resolveContent = fileUtils.createTempFile(); - fileUtils.write(content, resolveContent); + resolveContent = FileUtils.createTempFile(); + new FileUtils(repo.getLog(), this).write(content, resolveContent); } finally { content.close(); } @@ -404,6 +457,9 @@ } public void unresolved() { + if (revA == null || revB == null) { + throw new UnsupportedOperationException("To mark conflict as unresolved need two revisions"); + } resolveMarkUnresolved = true; } }