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 }