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 } |