Mercurial > jhg
comparison src/org/tmatesoft/hg/repo/HgBundle.java @ 673:545b1d4cc11d
Refactor HgBundle.GroupElement (clear experimental mark), resolve few technical debt issues
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Fri, 12 Jul 2013 20:14:24 +0200 |
parents | fba85bc1dfb8 |
children |
comparison
equal
deleted
inserted
replaced
672:d2552e6a5af6 | 673:545b1d4cc11d |
---|---|
15 * contact TMate Software at support@hg4j.com | 15 * contact TMate Software at support@hg4j.com |
16 */ | 16 */ |
17 package org.tmatesoft.hg.repo; | 17 package org.tmatesoft.hg.repo; |
18 | 18 |
19 import java.io.File; | 19 import java.io.File; |
20 import java.io.FileInputStream; | |
21 import java.io.IOException; | 20 import java.io.IOException; |
21 import java.io.InputStream; | |
22 import java.util.ConcurrentModificationException; | 22 import java.util.ConcurrentModificationException; |
23 | 23 |
24 import org.tmatesoft.hg.core.HgIOException; | 24 import org.tmatesoft.hg.core.HgIOException; |
25 import org.tmatesoft.hg.core.Nodeid; | 25 import org.tmatesoft.hg.core.Nodeid; |
26 import org.tmatesoft.hg.core.SessionContext; | 26 import org.tmatesoft.hg.core.SessionContext; |
27 import org.tmatesoft.hg.internal.ByteArrayChannel; | 27 import org.tmatesoft.hg.internal.ByteArrayChannel; |
28 import org.tmatesoft.hg.internal.ByteArrayDataAccess; | 28 import org.tmatesoft.hg.internal.ByteArrayDataAccess; |
29 import org.tmatesoft.hg.internal.Callback; | 29 import org.tmatesoft.hg.internal.Callback; |
30 import org.tmatesoft.hg.internal.ChangesetParser; | |
30 import org.tmatesoft.hg.internal.DataAccess; | 31 import org.tmatesoft.hg.internal.DataAccess; |
32 import org.tmatesoft.hg.internal.DataAccessInputStream; | |
31 import org.tmatesoft.hg.internal.DataAccessProvider; | 33 import org.tmatesoft.hg.internal.DataAccessProvider; |
32 import org.tmatesoft.hg.internal.DataSerializer; | |
33 import org.tmatesoft.hg.internal.DigestHelper; | 34 import org.tmatesoft.hg.internal.DigestHelper; |
34 import org.tmatesoft.hg.internal.EncodingHelper; | 35 import org.tmatesoft.hg.internal.EncodingHelper; |
35 import org.tmatesoft.hg.internal.Experimental; | 36 import org.tmatesoft.hg.internal.Experimental; |
36 import org.tmatesoft.hg.internal.FileUtils; | |
37 import org.tmatesoft.hg.internal.InflaterDataAccess; | 37 import org.tmatesoft.hg.internal.InflaterDataAccess; |
38 import org.tmatesoft.hg.internal.Internals; | 38 import org.tmatesoft.hg.internal.Internals; |
39 import org.tmatesoft.hg.internal.Lifecycle; | 39 import org.tmatesoft.hg.internal.Lifecycle; |
40 import org.tmatesoft.hg.internal.Patch; | 40 import org.tmatesoft.hg.internal.Patch; |
41 import org.tmatesoft.hg.repo.HgChangelog.ChangesetParser; | |
42 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; | 41 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; |
43 import org.tmatesoft.hg.util.Adaptable; | 42 import org.tmatesoft.hg.util.Adaptable; |
44 import org.tmatesoft.hg.util.CancelledException; | 43 import org.tmatesoft.hg.util.CancelledException; |
45 | 44 |
46 /** | 45 /** |
52 * @author TMate Software Ltd. | 51 * @author TMate Software Ltd. |
53 */ | 52 */ |
54 @Experimental(reason="API is not stable") | 53 @Experimental(reason="API is not stable") |
55 public class HgBundle { | 54 public class HgBundle { |
56 | 55 |
57 private final File bundleFile; | 56 final File bundleFile; |
58 private final DataAccessProvider accessProvider; | 57 private final DataAccessProvider accessProvider; |
59 private final SessionContext ctx; | 58 final SessionContext ctx; |
60 private final EncodingHelper fnDecorer; | 59 private final EncodingHelper fnDecorer; |
61 private Lifecycle.BasicCallback flowControl; | 60 private Lifecycle.BasicCallback flowControl; |
62 | 61 |
63 HgBundle(SessionContext sessionContext, DataAccessProvider dap, File bundle) { | 62 HgBundle(SessionContext sessionContext, DataAccessProvider dap, File bundle) { |
64 ctx = sessionContext; | 63 ctx = sessionContext; |
108 /** | 107 /** |
109 * Get changes recorded in the bundle that are missing from the supplied repository. | 108 * Get changes recorded in the bundle that are missing from the supplied repository. |
110 * @param hgRepo repository that shall possess base revision for this bundle | 109 * @param hgRepo repository that shall possess base revision for this bundle |
111 * @param inspector callback to get each changeset found | 110 * @param inspector callback to get each changeset found |
112 */ | 111 */ |
113 public void changes(final HgRepository hgRepo, final HgChangelog.Inspector inspector) throws HgRuntimeException { | 112 public void changes(final HgRepository hgRepo, final HgChangelog.Inspector inspector) throws HgIOException, HgRuntimeException { |
114 Inspector bundleInsp = new Inspector() { | 113 Inspector bundleInsp = new Inspector() { |
115 DigestHelper dh = new DigestHelper(); | 114 DigestHelper dh = new DigestHelper(); |
116 boolean emptyChangelog = true; | 115 boolean emptyChangelog = true; |
117 private DataAccess prevRevContent; | 116 private DataAccess prevRevContent; |
118 private int revisionIndex; | 117 private int revisionIndex; |
119 private ChangesetParser csetBuilder; | 118 private ChangesetParser csetBuilder; |
120 | 119 |
121 public void changelogStart() { | 120 public void changelogStart() { |
122 emptyChangelog = true; | 121 emptyChangelog = true; |
123 revisionIndex = 0; | 122 revisionIndex = 0; |
124 csetBuilder = new ChangesetParser(hgRepo, true); | 123 csetBuilder = new ChangesetParser(hgRepo, new HgChangelog.RawCsetFactory(true)); |
125 } | 124 } |
126 | 125 |
127 public void changelogEnd() { | 126 public void changelogEnd() { |
128 if (emptyChangelog) { | 127 if (emptyChangelog) { |
129 throw new IllegalStateException("No changelog group in the bundle"); // XXX perhaps, just be silent and/or log? | 128 throw new IllegalStateException("No changelog group in the bundle"); // XXX perhaps, just be silent and/or log? |
151 a6f39e595b2b54f56304470269a936ead77f5725 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 30bd389788464287cee22ccff54c330a4b715de5 a6f39e595b2b54f56304470269a936ead77f5725; patches:3 | 150 a6f39e595b2b54f56304470269a936ead77f5725 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 30bd389788464287cee22ccff54c330a4b715de5 a6f39e595b2b54f56304470269a936ead77f5725; patches:3 |
152 fd4f2c98995beb051070630c272a9be87bef617d 30bd389788464287cee22ccff54c330a4b715de5 0000000000000000000000000000000000000000 fd4f2c98995beb051070630c272a9be87bef617d; patches:3 | 151 fd4f2c98995beb051070630c272a9be87bef617d 30bd389788464287cee22ccff54c330a4b715de5 0000000000000000000000000000000000000000 fd4f2c98995beb051070630c272a9be87bef617d; patches:3 |
153 | 152 |
154 To recreate 30bd..e5, one have to take content of 9429..e0, not its p1 f1db..5e | 153 To recreate 30bd..e5, one have to take content of 9429..e0, not its p1 f1db..5e |
155 */ | 154 */ |
156 public boolean element(GroupElement ge) throws HgRuntimeException { | 155 public boolean element(GroupElement ge) throws IOException, HgRuntimeException { |
157 emptyChangelog = false; | 156 emptyChangelog = false; |
158 HgChangelog changelog = hgRepo.getChangelog(); | 157 HgChangelog changelog = hgRepo.getChangelog(); |
159 try { | 158 try { |
160 if (prevRevContent == null) { | 159 if (prevRevContent == null) { |
161 if (ge.firstParent().isNull() && ge.secondParent().isNull()) { | 160 if (ge.firstParent().isNull() && ge.secondParent().isNull()) { |
170 // extra byte[] (inside ByteArrayChannel) duplication just for the sake of subsequent ByteArrayDataChannel wrap. | 169 // extra byte[] (inside ByteArrayChannel) duplication just for the sake of subsequent ByteArrayDataChannel wrap. |
171 prevRevContent = new ByteArrayDataAccess(bac.toArray()); | 170 prevRevContent = new ByteArrayDataAccess(bac.toArray()); |
172 } | 171 } |
173 } | 172 } |
174 // | 173 // |
175 byte[] csetContent = ge.apply(prevRevContent); | 174 byte[] csetContent = ge.patch().apply(prevRevContent, -1); |
176 dh = dh.sha1(ge.firstParent(), ge.secondParent(), csetContent); // XXX ge may give me access to byte[] content of nodeid directly, perhaps, I don't need DH to be friend of Nodeid? | 175 dh = dh.sha1(ge.firstParent(), ge.secondParent(), csetContent); // XXX ge may give me access to byte[] content of nodeid directly, perhaps, I don't need DH to be friend of Nodeid? |
177 if (!ge.node().equalsTo(dh.asBinary())) { | 176 if (!ge.node().equalsTo(dh.asBinary())) { |
178 throw new HgInvalidStateException(String.format("Integrity check failed on %s, node: %s", bundleFile, ge.node().shortNotation())); | 177 throw new HgInvalidStateException(String.format("Integrity check failed on %s, node: %s", bundleFile, ge.node().shortNotation())); |
179 } | 178 } |
180 ByteArrayDataAccess csetDataAccess = new ByteArrayDataAccess(csetContent); | 179 RawChangeset cs = csetBuilder.parse(csetContent); |
181 RawChangeset cs = csetBuilder.parse(csetDataAccess); | |
182 inspector.next(revisionIndex++, ge.node(), cs); | 180 inspector.next(revisionIndex++, ge.node(), cs); |
183 prevRevContent.done(); | 181 prevRevContent.done(); |
184 prevRevContent = csetDataAccess.reset(); | 182 prevRevContent = new ByteArrayDataAccess(csetContent); |
185 } catch (CancelledException ex) { | 183 } catch (CancelledException ex) { |
186 return false; | 184 return false; |
187 } catch (IOException ex) { | |
188 throw new HgInvalidFileException("Invalid bundle file", ex, bundleFile); // TODO post-1.0 revisit exception handling | |
189 } catch (HgInvalidDataFormatException ex) { | 185 } catch (HgInvalidDataFormatException ex) { |
190 throw new HgInvalidControlFileException("Invalid bundle file", ex, bundleFile); | 186 throw new HgInvalidControlFileException("Invalid bundle file", ex, bundleFile); |
191 } | 187 } |
192 return true; | 188 return true; |
193 } | 189 } |
215 void fileStart(String name) throws HgRuntimeException; | 211 void fileStart(String name) throws HgRuntimeException; |
216 | 212 |
217 void fileEnd(String name) throws HgRuntimeException; | 213 void fileEnd(String name) throws HgRuntimeException; |
218 | 214 |
219 /** | 215 /** |
220 * XXX desperately need exceptions here | |
221 * @param element data element, instance might be reused, don't keep a reference to it or its raw data | 216 * @param element data element, instance might be reused, don't keep a reference to it or its raw data |
222 * @return <code>true</code> to continue | 217 * @return <code>true</code> to continue |
223 */ | 218 * @throws IOException propagated exception from {@link GroupElement#data()} |
224 boolean element(GroupElement element) throws HgRuntimeException; | 219 * @throws HgRuntimeException propagated exception (subclass thereof) to indicate issues with the library. <em>Runtime exception</em> |
220 */ | |
221 boolean element(GroupElement element) throws IOException, HgRuntimeException; | |
225 } | 222 } |
226 | 223 |
227 /** | 224 /** |
228 * @param inspector callback to visit changelog entries | 225 * @param inspector callback to visit changelog entries |
229 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> | 226 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
230 * @throws IllegalArgumentException if inspector argument is null | 227 * @throws IllegalArgumentException if inspector argument is null |
231 */ | 228 */ |
232 public void inspectChangelog(Inspector inspector) throws HgRuntimeException { | 229 public void inspectChangelog(Inspector inspector) throws HgIOException, HgRuntimeException { |
233 if (inspector == null) { | 230 if (inspector == null) { |
234 throw new IllegalArgumentException(); | 231 throw new IllegalArgumentException(); |
235 } | 232 } |
236 final Lifecycle lifecycle = lifecycleSetUp(inspector); | 233 final Lifecycle lifecycle = lifecycleSetUp(inspector); |
237 DataAccess da = null; | 234 DataAccess da = null; |
238 try { | 235 try { |
239 da = getDataStream(); | 236 da = getDataStream(); |
240 internalInspectChangelog(da, inspector); | 237 internalInspectChangelog(da, inspector); |
241 } catch (IOException ex) { | 238 } catch (IOException ex) { |
242 throw new HgInvalidFileException("Bundle.inspectChangelog failed", ex, bundleFile); | 239 throw new HgIOException("Failed to inspect changelog in the bundle", ex, bundleFile); |
243 } finally { | 240 } finally { |
244 if (da != null) { | 241 if (da != null) { |
245 da.done(); | 242 da.done(); |
246 } | 243 } |
247 lifecycleTearDown(lifecycle); | 244 lifecycleTearDown(lifecycle); |
251 /** | 248 /** |
252 * @param inspector callback to visit manifest entries | 249 * @param inspector callback to visit manifest entries |
253 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> | 250 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
254 * @throws IllegalArgumentException if inspector argument is null | 251 * @throws IllegalArgumentException if inspector argument is null |
255 */ | 252 */ |
256 public void inspectManifest(Inspector inspector) throws HgRuntimeException { | 253 public void inspectManifest(Inspector inspector) throws HgIOException, HgRuntimeException { |
257 if (inspector == null) { | 254 if (inspector == null) { |
258 throw new IllegalArgumentException(); | 255 throw new IllegalArgumentException(); |
259 } | 256 } |
260 final Lifecycle lifecycle = lifecycleSetUp(inspector); | 257 final Lifecycle lifecycle = lifecycleSetUp(inspector); |
261 DataAccess da = null; | 258 DataAccess da = null; |
265 return; | 262 return; |
266 } | 263 } |
267 skipGroup(da); // changelog | 264 skipGroup(da); // changelog |
268 internalInspectManifest(da, inspector); | 265 internalInspectManifest(da, inspector); |
269 } catch (IOException ex) { | 266 } catch (IOException ex) { |
270 throw new HgInvalidFileException("Bundle.inspectManifest failed", ex, bundleFile); | 267 throw new HgIOException("Failed to inspect manifest in the bundle", ex, bundleFile); |
271 } finally { | 268 } finally { |
272 if (da != null) { | 269 if (da != null) { |
273 da.done(); | 270 da.done(); |
274 } | 271 } |
275 lifecycleTearDown(lifecycle); | 272 lifecycleTearDown(lifecycle); |
279 /** | 276 /** |
280 * @param inspector callback to visit file entries | 277 * @param inspector callback to visit file entries |
281 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> | 278 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
282 * @throws IllegalArgumentException if inspector argument is null | 279 * @throws IllegalArgumentException if inspector argument is null |
283 */ | 280 */ |
284 public void inspectFiles(Inspector inspector) throws HgRuntimeException { | 281 public void inspectFiles(Inspector inspector) throws HgIOException, HgRuntimeException { |
285 if (inspector == null) { | 282 if (inspector == null) { |
286 throw new IllegalArgumentException(); | 283 throw new IllegalArgumentException(); |
287 } | 284 } |
288 final Lifecycle lifecycle = lifecycleSetUp(inspector); | 285 final Lifecycle lifecycle = lifecycleSetUp(inspector); |
289 DataAccess da = null; | 286 DataAccess da = null; |
297 return; | 294 return; |
298 } | 295 } |
299 skipGroup(da); // manifest | 296 skipGroup(da); // manifest |
300 internalInspectFiles(da, inspector); | 297 internalInspectFiles(da, inspector); |
301 } catch (IOException ex) { | 298 } catch (IOException ex) { |
302 throw new HgInvalidFileException("Bundle.inspectFiles failed", ex, bundleFile); | 299 throw new HgIOException("Failed to inspect files in the bundle", ex, bundleFile); |
303 } finally { | 300 } finally { |
304 if (da != null) { | 301 if (da != null) { |
305 da.done(); | 302 da.done(); |
306 } | 303 } |
307 lifecycleTearDown(lifecycle); | 304 lifecycleTearDown(lifecycle); |
311 /** | 308 /** |
312 * @param inspector visit complete bundle (changelog, manifest and file entries) | 309 * @param inspector visit complete bundle (changelog, manifest and file entries) |
313 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> | 310 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
314 * @throws IllegalArgumentException if inspector argument is null | 311 * @throws IllegalArgumentException if inspector argument is null |
315 */ | 312 */ |
316 public void inspectAll(Inspector inspector) throws HgRuntimeException { | 313 public void inspectAll(Inspector inspector) throws HgIOException, HgRuntimeException { |
317 if (inspector == null) { | 314 if (inspector == null) { |
318 throw new IllegalArgumentException(); | 315 throw new IllegalArgumentException(); |
319 } | 316 } |
320 final Lifecycle lifecycle = lifecycleSetUp(inspector); | 317 final Lifecycle lifecycle = lifecycleSetUp(inspector); |
321 DataAccess da = null; | 318 DataAccess da = null; |
329 if (flowControl.isStopped()) { | 326 if (flowControl.isStopped()) { |
330 return; | 327 return; |
331 } | 328 } |
332 internalInspectFiles(da, inspector); | 329 internalInspectFiles(da, inspector); |
333 } catch (IOException ex) { | 330 } catch (IOException ex) { |
334 throw new HgInvalidFileException("Bundle.inspectAll failed", ex, bundleFile); | 331 throw new HgIOException("Failed to inspect bundle", ex, bundleFile); |
335 } finally { | 332 } finally { |
336 if (da != null) { | 333 if (da != null) { |
337 da.done(); | 334 da.done(); |
338 } | 335 } |
339 lifecycleTearDown(lifecycle); | 336 lifecycleTearDown(lifecycle); |
451 da.skip(len - 4); // sizeof(int) | 448 da.skip(len - 4); // sizeof(int) |
452 len = da.isEmpty() ? 0 : da.readInt(); | 449 len = da.isEmpty() ? 0 : da.readInt(); |
453 } | 450 } |
454 } | 451 } |
455 | 452 |
456 @Experimental(reason="Cumbersome API, rawData and apply with byte[] perhaps need replacement with ByteChannel/ByteBuffer, and better Exceptions. Perhaps, shall split into interface and impl") | 453 /** |
457 public static class GroupElement { | 454 * Describes single element (a.k.a. chunk) of the group, either changelog, manifest or a file. |
455 */ | |
456 public static final class GroupElement { | |
458 private final byte[] header; // byte[80] takes 120 bytes, 4 Nodeids - 192 | 457 private final byte[] header; // byte[80] takes 120 bytes, 4 Nodeids - 192 |
459 private final DataAccess dataAccess; | 458 private final DataAccess dataAccess; |
459 private final Nodeid deltaBase; | |
460 private Patch patches; | 460 private Patch patches; |
461 private final Nodeid deltaBase; | 461 |
462 | |
463 GroupElement(byte[] fourNodeids, Nodeid deltaBaseRev, DataAccess rawDataAccess) { | 462 GroupElement(byte[] fourNodeids, Nodeid deltaBaseRev, DataAccess rawDataAccess) { |
464 assert fourNodeids != null && fourNodeids.length == 80; | 463 assert fourNodeids != null && fourNodeids.length == 80; |
465 header = fourNodeids; | 464 header = fourNodeids; |
466 deltaBase = deltaBaseRev; | 465 deltaBase = deltaBaseRev; |
467 dataAccess = rawDataAccess; | 466 dataAccess = rawDataAccess; |
505 */ | 504 */ |
506 public Nodeid patchBase() { | 505 public Nodeid patchBase() { |
507 return deltaBase == null ? firstParent() : deltaBase; | 506 return deltaBase == null ? firstParent() : deltaBase; |
508 } | 507 } |
509 | 508 |
510 public byte[] rawDataByteArray() throws IOException { // XXX IOException or HgInvalidFileException? | 509 /** |
511 return rawData().byteArray(); | 510 * Read data of the group element. |
512 } | 511 * Note, {@link InputStream streams} obtained from several calls to this method |
513 | 512 * can't be read simultaneously. |
514 public byte[] apply(byte[] baseContent) throws IOException { | 513 * |
515 return apply(new ByteArrayDataAccess(baseContent)); | 514 * @return stream to access content of this group element, never <code>null</code> |
516 } | 515 */ |
517 | 516 public InputStream data() { |
518 /*package-local*/ DataAccess rawData() { | 517 return new DataAccessInputStream(dataAccess); |
519 return dataAccess; | |
520 } | 518 } |
521 | 519 |
522 /*package-local*/ Patch patch() throws IOException { | 520 /*package-local*/ Patch patch() throws IOException { |
523 if (patches == null) { | 521 if (patches == null) { |
524 dataAccess.reset(); | 522 dataAccess.reset(); |
526 patches.read(dataAccess); | 524 patches.read(dataAccess); |
527 } | 525 } |
528 return patches; | 526 return patches; |
529 } | 527 } |
530 | 528 |
531 /*package-local*/ byte[] apply(DataAccess baseContent) throws IOException { | |
532 return patch().apply(baseContent, -1); | |
533 } | |
534 | |
535 public String toString() { | 529 public String toString() { |
536 int patchCount; | 530 int patchCount; |
537 try { | 531 try { |
538 patchCount = patch().count(); | 532 patchCount = patch().count(); |
539 } catch (IOException ex) { | 533 } catch (IOException ex) { |
541 patchCount = -1; | 535 patchCount = -1; |
542 } | 536 } |
543 return String.format("%s %s %s %s; patches:%d\n", node().shortNotation(), firstParent().shortNotation(), secondParent().shortNotation(), cset().shortNotation(), patchCount); | 537 return String.format("%s %s %s %s; patches:%d\n", node().shortNotation(), firstParent().shortNotation(), secondParent().shortNotation(), cset().shortNotation(), patchCount); |
544 } | 538 } |
545 } | 539 } |
546 | |
547 @Experimental(reason="Work in progress, not an API") | |
548 public class BundleSerializer implements DataSerializer.DataSource { | |
549 | |
550 public void serialize(DataSerializer out) throws HgIOException, HgRuntimeException { | |
551 FileInputStream fis = null; | |
552 try { | |
553 fis = new FileInputStream(HgBundle.this.bundleFile); | |
554 byte[] buffer = new byte[8*1024]; | |
555 int r; | |
556 while ((r = fis.read(buffer, 0, buffer.length)) > 0) { | |
557 out.write(buffer, 0, r); | |
558 } | |
559 | |
560 } catch (IOException ex) { | |
561 throw new HgIOException("Failed to serialize bundle", HgBundle.this.bundleFile); | |
562 } finally { | |
563 new FileUtils(HgBundle.this.ctx.getLog(), this).closeQuietly(fis, HgBundle.this.bundleFile); | |
564 } | |
565 } | |
566 | |
567 public int serializeLength() throws HgRuntimeException { | |
568 return Internals.ltoi(HgBundle.this.bundleFile.length()); | |
569 } | |
570 } | |
571 } | 540 } |