Mercurial > jhg
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 706:cd5c87d96315 | 707:42b88709e41d |
|---|---|
| 34 import org.tmatesoft.hg.internal.MergeStateBuilder; | 34 import org.tmatesoft.hg.internal.MergeStateBuilder; |
| 35 import org.tmatesoft.hg.internal.Pool; | 35 import org.tmatesoft.hg.internal.Pool; |
| 36 import org.tmatesoft.hg.internal.Transaction; | 36 import org.tmatesoft.hg.internal.Transaction; |
| 37 import org.tmatesoft.hg.internal.WorkingDirFileWriter; | 37 import org.tmatesoft.hg.internal.WorkingDirFileWriter; |
| 38 import org.tmatesoft.hg.repo.HgChangelog; | 38 import org.tmatesoft.hg.repo.HgChangelog; |
| 39 import org.tmatesoft.hg.repo.HgManifest; | |
| 39 import org.tmatesoft.hg.repo.HgParentChildMap; | 40 import org.tmatesoft.hg.repo.HgParentChildMap; |
| 40 import org.tmatesoft.hg.repo.HgRepository; | 41 import org.tmatesoft.hg.repo.HgRepository; |
| 41 import org.tmatesoft.hg.repo.HgRepositoryLock; | 42 import org.tmatesoft.hg.repo.HgRepositoryLock; |
| 42 import org.tmatesoft.hg.repo.HgRevisionMap; | 43 import org.tmatesoft.hg.repo.HgRevisionMap; |
| 43 import org.tmatesoft.hg.repo.HgRuntimeException; | 44 import org.tmatesoft.hg.repo.HgRuntimeException; |
| 84 | 85 |
| 85 Internals implRepo = Internals.getInstance(repo); | 86 Internals implRepo = Internals.getInstance(repo); |
| 86 final DirstateBuilder dirstateBuilder = new DirstateBuilder(implRepo); | 87 final DirstateBuilder dirstateBuilder = new DirstateBuilder(implRepo); |
| 87 dirstateBuilder.fillFrom(new DirstateReader(implRepo, new Path.SimpleSource(repo.getSessionContext().getPathFactory(), cacheFiles))); | 88 dirstateBuilder.fillFrom(new DirstateReader(implRepo, new Path.SimpleSource(repo.getSessionContext().getPathFactory(), cacheFiles))); |
| 88 final HgChangelog clog = repo.getChangelog(); | 89 final HgChangelog clog = repo.getChangelog(); |
| 89 dirstateBuilder.parents(clog.getRevision(firstCset), clog.getRevision(secondCset)); | 90 final Nodeid headCset1 = clog.getRevision(firstCset); |
| 91 dirstateBuilder.parents(headCset1, clog.getRevision(secondCset)); | |
| 90 // | 92 // |
| 91 MergeStateBuilder mergeStateBuilder = new MergeStateBuilder(implRepo); | 93 MergeStateBuilder mergeStateBuilder = new MergeStateBuilder(implRepo); |
| 94 mergeStateBuilder.prepare(headCset1); | |
| 92 | 95 |
| 93 ManifestRevision m1, m2, ma; | 96 ManifestRevision m1, m2, ma; |
| 94 m1 = new ManifestRevision(cacheRevs, cacheFiles).init(repo, firstCset); | 97 m1 = new ManifestRevision(cacheRevs, cacheFiles).init(repo, firstCset); |
| 95 m2 = new ManifestRevision(cacheRevs, cacheFiles).init(repo, secondCset); | 98 m2 = new ManifestRevision(cacheRevs, cacheFiles).init(repo, secondCset); |
| 96 ma = new ManifestRevision(cacheRevs, cacheFiles).init(repo, ancestorCset); | 99 ma = new ManifestRevision(cacheRevs, cacheFiles).init(repo, ancestorCset); |
| 103 fileRevA = m1.nodeid(f); | 106 fileRevA = m1.nodeid(f); |
| 104 fileRevB = m2.nodeid(f); | 107 fileRevB = m2.nodeid(f); |
| 105 fileRevBase = ma.contains(f) ? ma.nodeid(f) : null; | 108 fileRevBase = ma.contains(f) ? ma.nodeid(f) : null; |
| 106 if (fileRevA.equals(fileRevB)) { | 109 if (fileRevA.equals(fileRevB)) { |
| 107 HgFileRevision fr = new HgFileRevision(repo, fileRevA, m1.flags(f), f); | 110 HgFileRevision fr = new HgFileRevision(repo, fileRevA, m1.flags(f), f); |
| 108 resolver.presentState(f, fr, fr); | 111 resolver.presentState(f, fr, fr, null); |
| 109 mediator.same(fr, resolver); | 112 mediator.same(fr, resolver); |
| 110 } else if (fileRevBase == fileRevA) { | 113 } else if (fileRevBase == fileRevA) { |
| 111 assert fileRevBase != null; | 114 assert fileRevBase != null; |
| 112 HgFileRevision frBase = new HgFileRevision(repo, fileRevBase, ma.flags(f), f); | 115 HgFileRevision frBase = new HgFileRevision(repo, fileRevBase, ma.flags(f), f); |
| 113 HgFileRevision frSecond= new HgFileRevision(repo, fileRevB, m2.flags(f), f); | 116 HgFileRevision frSecond= new HgFileRevision(repo, fileRevB, m2.flags(f), f); |
| 114 resolver.presentState(f, frBase, frSecond); | 117 resolver.presentState(f, frBase, frSecond, frBase); |
| 115 mediator.fastForwardB(frBase, frSecond, resolver); | 118 mediator.fastForwardB(frBase, frSecond, resolver); |
| 116 } else if (fileRevBase == fileRevB) { | 119 } else if (fileRevBase == fileRevB) { |
| 117 assert fileRevBase != null; | 120 assert fileRevBase != null; |
| 118 HgFileRevision frBase = new HgFileRevision(repo, fileRevBase, ma.flags(f), f); | 121 HgFileRevision frBase = new HgFileRevision(repo, fileRevBase, ma.flags(f), f); |
| 119 HgFileRevision frFirst = new HgFileRevision(repo, fileRevA, m1.flags(f), f); | 122 HgFileRevision frFirst = new HgFileRevision(repo, fileRevA, m1.flags(f), f); |
| 120 resolver.presentState(f, frFirst, frBase); | 123 resolver.presentState(f, frFirst, frBase, frBase); |
| 121 mediator.fastForwardA(frBase, frFirst, resolver); | 124 mediator.fastForwardA(frBase, frFirst, resolver); |
| 122 } else { | 125 } else { |
| 123 HgFileRevision frBase = fileRevBase == null ? null : new HgFileRevision(repo, fileRevBase, ma.flags(f), f); | 126 HgFileRevision frBase = fileRevBase == null ? null : new HgFileRevision(repo, fileRevBase, ma.flags(f), f); |
| 124 HgFileRevision frFirst = new HgFileRevision(repo, fileRevA, m1.flags(f), f); | 127 HgFileRevision frFirst = new HgFileRevision(repo, fileRevA, m1.flags(f), f); |
| 125 HgFileRevision frSecond= new HgFileRevision(repo, fileRevB, m2.flags(f), f); | 128 HgFileRevision frSecond= new HgFileRevision(repo, fileRevB, m2.flags(f), f); |
| 126 resolver.presentState(f, frFirst, frSecond); | 129 resolver.presentState(f, frFirst, frSecond, frBase); |
| 127 mediator.resolve(frBase, frFirst, frSecond, resolver); | 130 mediator.resolve(frBase, frFirst, frSecond, resolver); |
| 128 } | 131 } |
| 129 } else { | 132 } else { |
| 130 // m2 doesn't contain the file, either new in m1, or deleted in m2 | 133 // m2 doesn't contain the file, either new in m1, or deleted in m2 |
| 131 HgFileRevision frFirst = new HgFileRevision(repo, m1.nodeid(f), m1.flags(f), f); | 134 HgFileRevision frFirst = new HgFileRevision(repo, m1.nodeid(f), m1.flags(f), f); |
| 132 resolver.presentState(f, frFirst, null); | |
| 133 if (ma.contains(f)) { | 135 if (ma.contains(f)) { |
| 134 // deleted in m2 | 136 // deleted in m2 |
| 135 HgFileRevision frBase = new HgFileRevision(repo, ma.nodeid(f), ma.flags(f), f); | 137 HgFileRevision frBase = new HgFileRevision(repo, ma.nodeid(f), ma.flags(f), f); |
| 138 resolver.presentState(f, frFirst, null, frBase); | |
| 136 mediator.onlyA(frBase, frFirst, resolver); | 139 mediator.onlyA(frBase, frFirst, resolver); |
| 137 } else { | 140 } else { |
| 138 // new in m1 | 141 // new in m1 |
| 142 resolver.presentState(f, frFirst, null, null); | |
| 139 mediator.newInA(frFirst, resolver); | 143 mediator.newInA(frFirst, resolver); |
| 140 } | 144 } |
| 141 } | 145 } |
| 142 resolver.apply(); | 146 resolver.apply(); |
| 143 } // for m1 files | 147 } // for m1 files |
| 145 if (m1.contains(f)) { | 149 if (m1.contains(f)) { |
| 146 continue; | 150 continue; |
| 147 } | 151 } |
| 148 HgFileRevision frSecond= new HgFileRevision(repo, m2.nodeid(f), m2.flags(f), f); | 152 HgFileRevision frSecond= new HgFileRevision(repo, m2.nodeid(f), m2.flags(f), f); |
| 149 // file in m2 is either new or deleted in m1 | 153 // file in m2 is either new or deleted in m1 |
| 150 resolver.presentState(f, null, frSecond); | |
| 151 if (ma.contains(f)) { | 154 if (ma.contains(f)) { |
| 152 // deleted in m1 | 155 // deleted in m1 |
| 153 HgFileRevision frBase = new HgFileRevision(repo, ma.nodeid(f), ma.flags(f), f); | 156 HgFileRevision frBase = new HgFileRevision(repo, ma.nodeid(f), ma.flags(f), f); |
| 157 resolver.presentState(f, null, frSecond, frBase); | |
| 154 mediator.onlyB(frBase, frSecond, resolver); | 158 mediator.onlyB(frBase, frSecond, resolver); |
| 155 } else { | 159 } else { |
| 156 // new in m2 | 160 // new in m2 |
| 161 resolver.presentState(f, null, frSecond, null); | |
| 157 mediator.newInB(frSecond, resolver); | 162 mediator.newInB(frSecond, resolver); |
| 158 } | 163 } |
| 159 resolver.apply(); | 164 resolver.apply(); |
| 160 } | 165 } |
| 161 resolver.serializeChanged(transaction); | 166 resolver.serializeChanged(transaction); |
| 162 transaction.commit(); | 167 transaction.commit(); |
| 163 } catch (HgRuntimeException ex) { | 168 } catch (HgRuntimeException ex) { |
| 164 transaction.rollback(); | 169 transaction.rollback(); |
| 170 mergeStateBuilder.abandon(); | |
| 165 throw ex; | 171 throw ex; |
| 166 } catch (HgIOException ex) { | 172 } catch (HgIOException ex) { |
| 167 transaction.rollback(); | 173 transaction.rollback(); |
| 174 mergeStateBuilder.abandon(); | |
| 168 throw ex; | 175 throw ex; |
| 169 } | 176 } |
| 170 } catch (HgRuntimeException ex) { | 177 } catch (HgRuntimeException ex) { |
| 171 throw new HgLibraryFailureException(ex); | 178 throw new HgLibraryFailureException(ex); |
| 172 } finally { | 179 } finally { |
| 253 * | 260 * |
| 254 * @param content New content to replace current revision, shall not be <code>null</code> | 261 * @param content New content to replace current revision, shall not be <code>null</code> |
| 255 * @throws IOException propagated exceptions from content | 262 * @throws IOException propagated exceptions from content |
| 256 */ | 263 */ |
| 257 public void use(InputStream content) throws IOException; | 264 public void use(InputStream content) throws IOException; |
| 265 /** | |
| 266 * Do not use this file for resolution. Marks the file for deletion, if appropriate. | |
| 267 */ | |
| 258 public void forget(HgFileRevision rev); | 268 public void forget(HgFileRevision rev); |
| 259 public void unresolved(); // record the file for later processing by 'hg resolve' | 269 /** |
| 270 * Record the file for later processing by 'hg resolve'. It's required | |
| 271 * that processed file present in both trunks. We need two file revisions | |
| 272 * to put an entry into merge/state file. | |
| 273 * | |
| 274 * XXX Perhaps, shall take two HgFileRevision arguments to facilitate | |
| 275 * extra control over what goes into merge/state and to ensure this method | |
| 276 * is not invoked when there are no conflicting revisions. | |
| 277 */ | |
| 278 public void unresolved(); | |
| 260 } | 279 } |
| 261 | 280 |
| 262 /** | 281 /** |
| 263 * Base mediator implementation, with regular resolution | 282 * Base mediator implementation, with regular resolution. |
| 283 * Subclasses shall implement {@link #resolve(HgFileRevision, HgFileRevision, HgFileRevision, Resolver)} and | |
| 284 * may optionally provide extra logic (e.g. ask user) for other cases. | |
| 264 */ | 285 */ |
| 265 @Experimental(reason="Provisional API. Work in progress") | 286 @Experimental(reason="Provisional API. Work in progress") |
| 266 public abstract class MediatorBase implements Mediator { | 287 public abstract static class MediatorBase implements Mediator { |
| 288 /** | |
| 289 * Implementation keeps this revision | |
| 290 */ | |
| 267 public void same(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { | 291 public void same(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { |
| 268 resolver.use(rev); | 292 resolver.use(rev); |
| 269 } | 293 } |
| 294 /** | |
| 295 * Implementation keeps file revision from first/left/A trunk. | |
| 296 * Subclasses may opt to {@link Resolver#forget(HgFileRevision) delete} it as it's done in second/right/B trunk. | |
| 297 */ | |
| 270 public void onlyA(HgFileRevision base, HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { | 298 public void onlyA(HgFileRevision base, HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { |
| 271 resolver.use(rev); | 299 resolver.use(rev); |
| 272 } | 300 } |
| 301 /** | |
| 302 * Implementation restores file from second/right/B trunk. | |
| 303 * Subclasses may ask user to decide if it's necessary to do that | |
| 304 */ | |
| 273 public void onlyB(HgFileRevision base, HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { | 305 public void onlyB(HgFileRevision base, HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { |
| 274 resolver.use(rev); | 306 resolver.use(rev); |
| 275 } | 307 } |
| 308 /** | |
| 309 * Implementation keeps this revision | |
| 310 */ | |
| 276 public void newInA(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { | 311 public void newInA(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { |
| 277 resolver.use(rev); | 312 resolver.use(rev); |
| 278 } | 313 } |
| 314 /** | |
| 315 * Implementation adds this revision. Subclasses my let user decide if it's necessary to add the file | |
| 316 */ | |
| 279 public void newInB(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { | 317 public void newInB(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { |
| 280 resolver.use(rev); | 318 resolver.use(rev); |
| 281 } | 319 } |
| 320 /** | |
| 321 * Implementation keeps latest revision | |
| 322 */ | |
| 282 public void fastForwardA(HgFileRevision base, HgFileRevision first, Resolver resolver) throws HgCallbackTargetException { | 323 public void fastForwardA(HgFileRevision base, HgFileRevision first, Resolver resolver) throws HgCallbackTargetException { |
| 283 resolver.use(first); | 324 resolver.use(first); |
| 284 } | 325 } |
| 326 /** | |
| 327 * Implementation keeps latest revision | |
| 328 */ | |
| 285 public void fastForwardB(HgFileRevision base, HgFileRevision second, Resolver resolver) throws HgCallbackTargetException { | 329 public void fastForwardB(HgFileRevision base, HgFileRevision second, Resolver resolver) throws HgCallbackTargetException { |
| 286 resolver.use(second); | 330 resolver.use(second); |
| 287 } | 331 } |
| 288 } | 332 } |
| 289 | 333 |
| 293 private final DirstateBuilder dirstateBuilder; | 337 private final DirstateBuilder dirstateBuilder; |
| 294 private final MergeStateBuilder mergeStateBuilder; | 338 private final MergeStateBuilder mergeStateBuilder; |
| 295 private boolean changedDirstate; | 339 private boolean changedDirstate; |
| 296 private HgFileRevision revA; | 340 private HgFileRevision revA; |
| 297 private HgFileRevision revB; | 341 private HgFileRevision revB; |
| 342 private HgFileRevision revBase; | |
| 298 private Path file; | 343 private Path file; |
| 299 // resolutions: | 344 // resolutions: |
| 300 private HgFileRevision resolveUse, resolveForget; | 345 private HgFileRevision resolveUse, resolveForget; |
| 301 private File resolveContent; | 346 private File resolveContent; |
| 302 private boolean resolveMarkUnresolved; | 347 private boolean resolveMarkUnresolved; |
| 310 | 355 |
| 311 void serializeChanged(Transaction tr) throws HgIOException { | 356 void serializeChanged(Transaction tr) throws HgIOException { |
| 312 if (changedDirstate) { | 357 if (changedDirstate) { |
| 313 dirstateBuilder.serialize(tr); | 358 dirstateBuilder.serialize(tr); |
| 314 } | 359 } |
| 315 mergeStateBuilder.serialize(tr); | 360 mergeStateBuilder.serialize(); |
| 316 } | 361 } |
| 317 | 362 |
| 318 void presentState(Path p, HgFileRevision revA, HgFileRevision revB) { | 363 void presentState(Path p, HgFileRevision revA, HgFileRevision revB, HgFileRevision base) { |
| 319 assert revA != null || revB != null; | 364 assert revA != null || revB != null; |
| 320 file = p; | 365 file = p; |
| 321 this.revA = revA; | 366 this.revA = revA; |
| 322 this.revB = revB; | 367 this.revB = revB; |
| 368 revBase = base; | |
| 323 resolveUse = resolveForget = null; | 369 resolveUse = resolveForget = null; |
| 324 resolveContent = null; | 370 resolveContent = null; |
| 325 resolveMarkUnresolved = false; | 371 resolveMarkUnresolved = false; |
| 326 } | 372 } |
| 327 | 373 |
| 328 void apply() throws HgIOException, HgRuntimeException { | 374 void apply() throws HgIOException, HgRuntimeException { |
| 329 if (resolveMarkUnresolved) { | 375 if (resolveMarkUnresolved) { |
| 330 mergeStateBuilder.unresolved(file); | 376 HgFileRevision c = revBase; |
| 377 if (revBase == null) { | |
| 378 // fake revision, null parent | |
| 379 c = new HgFileRevision(repo.getRepo(), Nodeid.NULL, HgManifest.Flags.RegularFile, file); | |
| 380 } | |
| 381 mergeStateBuilder.unresolved(file, revA, revB, c, revA.getFileFlags()); | |
| 382 changedDirstate = true; | |
| 383 dirstateBuilder.recordMergedExisting(file, revA.getPath()); | |
| 331 } else if (resolveForget != null) { | 384 } else if (resolveForget != null) { |
| 332 if (resolveForget == revA) { | 385 // it revision to forget comes from second/B trunk, shall record it as removed |
| 386 // only when corresponding file in first/A trunk is missing (merge:_forgetremoved()) | |
| 387 if (resolveForget == revA || (resolveForget == revB && revA == null)) { | |
| 333 changedDirstate = true; | 388 changedDirstate = true; |
| 334 dirstateBuilder.recordRemoved(file); | 389 dirstateBuilder.recordRemoved(file); |
| 335 } | 390 } |
| 336 } else if (resolveUse != null) { | 391 } else if (resolveUse != null) { |
| 337 if (resolveUse != revA) { | 392 if (resolveUse != revA) { |
| 379 throw new IllegalArgumentException(); | 434 throw new IllegalArgumentException(); |
| 380 } | 435 } |
| 381 assert resolveUse == null; | 436 assert resolveUse == null; |
| 382 assert resolveForget == null; | 437 assert resolveForget == null; |
| 383 try { | 438 try { |
| 384 // cache new contents just to fail fast if there are troubles with content | 439 resolveContent = FileUtils.createTempFile(); |
| 385 final FileUtils fileUtils = new FileUtils(repo.getLog(), this); | 440 new FileUtils(repo.getLog(), this).write(content, resolveContent); |
| 386 resolveContent = fileUtils.createTempFile(); | |
| 387 fileUtils.write(content, resolveContent); | |
| 388 } finally { | 441 } finally { |
| 389 content.close(); | 442 content.close(); |
| 390 } | 443 } |
| 391 // do not care deleting file in case of failure to allow analyze of the issue | 444 // do not care deleting file in case of failure to allow analyze of the issue |
| 392 } | 445 } |
| 402 assert resolveContent == null; | 455 assert resolveContent == null; |
| 403 resolveForget = rev; | 456 resolveForget = rev; |
| 404 } | 457 } |
| 405 | 458 |
| 406 public void unresolved() { | 459 public void unresolved() { |
| 460 if (revA == null || revB == null) { | |
| 461 throw new UnsupportedOperationException("To mark conflict as unresolved need two revisions"); | |
| 462 } | |
| 407 resolveMarkUnresolved = true; | 463 resolveMarkUnresolved = true; |
| 408 } | 464 } |
| 409 } | 465 } |
| 410 } | 466 } |
