Mercurial > jhg
comparison hg4j/src/main/java/org/tmatesoft/hg/repo/HgBundle.java @ 213:6ec4af642ba8 gradle
Project uses Gradle for build - actual changes
| author | Alexander Kitaev <kitaev@gmail.com> |
|---|---|
| date | Tue, 10 May 2011 10:52:53 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 212:edb2e2829352 | 213:6ec4af642ba8 |
|---|---|
| 1 /* | |
| 2 * Copyright (c) 2011 TMate Software Ltd | |
| 3 * | |
| 4 * This program is free software; you can redistribute it and/or modify | |
| 5 * it under the terms of the GNU General Public License as published by | |
| 6 * the Free Software Foundation; version 2 of the License. | |
| 7 * | |
| 8 * This program is distributed in the hope that it will be useful, | |
| 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 11 * GNU General Public License for more details. | |
| 12 * | |
| 13 * For information on how to redistribute this software under | |
| 14 * the terms of a license other than GNU General Public License | |
| 15 * contact TMate Software at support@hg4j.com | |
| 16 */ | |
| 17 package org.tmatesoft.hg.repo; | |
| 18 | |
| 19 import static org.tmatesoft.hg.core.Nodeid.NULL; | |
| 20 | |
| 21 import java.io.File; | |
| 22 import java.io.IOException; | |
| 23 import java.util.LinkedList; | |
| 24 import java.util.List; | |
| 25 | |
| 26 import org.tmatesoft.hg.core.HgBadStateException; | |
| 27 import org.tmatesoft.hg.core.HgException; | |
| 28 import org.tmatesoft.hg.core.Nodeid; | |
| 29 import org.tmatesoft.hg.internal.ByteArrayChannel; | |
| 30 import org.tmatesoft.hg.internal.ByteArrayDataAccess; | |
| 31 import org.tmatesoft.hg.internal.DataAccess; | |
| 32 import org.tmatesoft.hg.internal.DataAccessProvider; | |
| 33 import org.tmatesoft.hg.internal.DigestHelper; | |
| 34 import org.tmatesoft.hg.internal.InflaterDataAccess; | |
| 35 import org.tmatesoft.hg.internal.RevlogStream; | |
| 36 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; | |
| 37 import org.tmatesoft.hg.util.CancelledException; | |
| 38 | |
| 39 /** | |
| 40 * @see http://mercurial.selenic.com/wiki/BundleFormat | |
| 41 * | |
| 42 * @author Artem Tikhomirov | |
| 43 * @author TMate Software Ltd. | |
| 44 */ | |
| 45 public class HgBundle { | |
| 46 | |
| 47 private final File bundleFile; | |
| 48 private final DataAccessProvider accessProvider; | |
| 49 | |
| 50 HgBundle(DataAccessProvider dap, File bundle) { | |
| 51 accessProvider = dap; | |
| 52 bundleFile = bundle; | |
| 53 } | |
| 54 | |
| 55 private DataAccess getDataStream() throws IOException { | |
| 56 DataAccess da = accessProvider.create(bundleFile); | |
| 57 byte[] signature = new byte[6]; | |
| 58 if (da.length() > 6) { | |
| 59 da.readBytes(signature, 0, 6); | |
| 60 if (signature[0] == 'H' && signature[1] == 'G' && signature[2] == '1' && signature[3] == '0') { | |
| 61 if (signature[4] == 'G' && signature[5] == 'Z') { | |
| 62 return new InflaterDataAccess(da, 6, da.length() - 6); | |
| 63 } | |
| 64 if (signature[4] == 'B' && signature[5] == 'Z') { | |
| 65 throw HgRepository.notImplemented(); | |
| 66 } | |
| 67 if (signature[4] != 'U' || signature[5] != 'N') { | |
| 68 throw new HgBadStateException("Bad bundle signature:" + new String(signature)); | |
| 69 } | |
| 70 // "...UN", fall-through | |
| 71 } else { | |
| 72 da.reset(); | |
| 73 } | |
| 74 } | |
| 75 return da; | |
| 76 } | |
| 77 | |
| 78 private int uses = 0; | |
| 79 public HgBundle link() { | |
| 80 uses++; | |
| 81 return this; | |
| 82 } | |
| 83 public void unlink() { | |
| 84 uses--; | |
| 85 if (uses == 0 && bundleFile != null) { | |
| 86 bundleFile.deleteOnExit(); | |
| 87 } | |
| 88 } | |
| 89 public boolean inUse() { | |
| 90 return uses > 0; | |
| 91 } | |
| 92 | |
| 93 /** | |
| 94 * Get changes recorded in the bundle that are missing from the supplied repository. | |
| 95 * @param hgRepo repository that shall possess base revision for this bundle | |
| 96 * @param inspector callback to get each changeset found | |
| 97 */ | |
| 98 public void changes(final HgRepository hgRepo, final HgChangelog.Inspector inspector) throws HgException, IOException { | |
| 99 Inspector bundleInsp = new Inspector() { | |
| 100 DigestHelper dh = new DigestHelper(); | |
| 101 boolean emptyChangelog = true; | |
| 102 private DataAccess prevRevContent; | |
| 103 private int revisionIndex; | |
| 104 | |
| 105 public void changelogStart() { | |
| 106 emptyChangelog = true; | |
| 107 revisionIndex = 0; | |
| 108 } | |
| 109 | |
| 110 public void changelogEnd() { | |
| 111 if (emptyChangelog) { | |
| 112 throw new IllegalStateException("No changelog group in the bundle"); // XXX perhaps, just be silent and/or log? | |
| 113 } | |
| 114 } | |
| 115 | |
| 116 /* | |
| 117 * Despite that BundleFormat wiki says: "Each Changelog entry patches the result of all previous patches | |
| 118 * (the previous, or parent patch of a given patch p is the patch that has a node equal to p's p1 field)", | |
| 119 * it seems not to hold true. Instead, each entry patches previous one, regardless of whether the one | |
| 120 * before is its parent (i.e. ge.firstParent()) or not. | |
| 121 * | |
| 122 Actual state in the changelog.i | |
| 123 Index Offset Flags Packed Actual Base Rev Link Rev Parent1 Parent2 nodeid | |
| 124 50: 9212 0 209 329 48 50 49 -1 f1db8610da62a3e0beb8d360556ee1fd6eb9885e | |
| 125 51: 9421 0 278 688 48 51 50 -1 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 | |
| 126 52: 9699 0 154 179 52 52 50 -1 30bd389788464287cee22ccff54c330a4b715de5 | |
| 127 53: 9853 0 133 204 52 53 51 52 a6f39e595b2b54f56304470269a936ead77f5725 | |
| 128 54: 9986 0 156 182 54 54 52 -1 fd4f2c98995beb051070630c272a9be87bef617d | |
| 129 | |
| 130 Excerpt from bundle (nodeid, p1, p2, cs): | |
| 131 f1db8610da62a3e0beb8d360556ee1fd6eb9885e 26e3eeaa39623de552b45ee1f55c14f36460f220 0000000000000000000000000000000000000000 f1db8610da62a3e0beb8d360556ee1fd6eb9885e; patches:4 | |
| 132 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 f1db8610da62a3e0beb8d360556ee1fd6eb9885e 0000000000000000000000000000000000000000 9429c7bd1920fab164a9d2b621d38d57bcb49ae0; patches:3 | |
| 133 > 30bd389788464287cee22ccff54c330a4b715de5 f1db8610da62a3e0beb8d360556ee1fd6eb9885e 0000000000000000000000000000000000000000 30bd389788464287cee22ccff54c330a4b715de5; patches:3 | |
| 134 a6f39e595b2b54f56304470269a936ead77f5725 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 30bd389788464287cee22ccff54c330a4b715de5 a6f39e595b2b54f56304470269a936ead77f5725; patches:3 | |
| 135 fd4f2c98995beb051070630c272a9be87bef617d 30bd389788464287cee22ccff54c330a4b715de5 0000000000000000000000000000000000000000 fd4f2c98995beb051070630c272a9be87bef617d; patches:3 | |
| 136 | |
| 137 To recreate 30bd..e5, one have to take content of 9429..e0, not its p1 f1db..5e | |
| 138 */ | |
| 139 public boolean element(GroupElement ge) { | |
| 140 emptyChangelog = false; | |
| 141 HgChangelog changelog = hgRepo.getChangelog(); | |
| 142 try { | |
| 143 if (prevRevContent == null) { | |
| 144 if (NULL.equals(ge.firstParent()) && NULL.equals(ge.secondParent())) { | |
| 145 prevRevContent = new ByteArrayDataAccess(new byte[0]); | |
| 146 } else { | |
| 147 final Nodeid base = ge.firstParent(); | |
| 148 if (!changelog.isKnown(base) /*only first parent, that's Bundle contract*/) { | |
| 149 throw new IllegalStateException(String.format("Revision %s needs a parent %s, which is missing in the supplied repo %s", ge.node().shortNotation(), base.shortNotation(), hgRepo.toString())); | |
| 150 } | |
| 151 ByteArrayChannel bac = new ByteArrayChannel(); | |
| 152 changelog.rawContent(base, bac); // FIXME get DataAccess directly, to avoid | |
| 153 // extra byte[] (inside ByteArrayChannel) duplication just for the sake of subsequent ByteArrayDataChannel wrap. | |
| 154 prevRevContent = new ByteArrayDataAccess(bac.toArray()); | |
| 155 } | |
| 156 } | |
| 157 // | |
| 158 byte[] csetContent = ge.apply(prevRevContent); | |
| 159 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? | |
| 160 if (!ge.node().equalsTo(dh.asBinary())) { | |
| 161 throw new IllegalStateException("Integrity check failed on " + bundleFile + ", node:" + ge.node()); | |
| 162 } | |
| 163 ByteArrayDataAccess csetDataAccess = new ByteArrayDataAccess(csetContent); | |
| 164 RawChangeset cs = RawChangeset.parse(csetDataAccess); | |
| 165 inspector.next(revisionIndex++, ge.node(), cs); | |
| 166 prevRevContent.done(); | |
| 167 prevRevContent = csetDataAccess.reset(); | |
| 168 } catch (CancelledException ex) { | |
| 169 return false; | |
| 170 } catch (Exception ex) { | |
| 171 throw new HgBadStateException(ex); // FIXME | |
| 172 } | |
| 173 return true; | |
| 174 } | |
| 175 | |
| 176 public void manifestStart() {} | |
| 177 public void manifestEnd() {} | |
| 178 public void fileStart(String name) {} | |
| 179 public void fileEnd(String name) {} | |
| 180 | |
| 181 }; | |
| 182 inspectChangelog(bundleInsp); | |
| 183 } | |
| 184 | |
| 185 public void dump() throws IOException { | |
| 186 Dump dump = new Dump(); | |
| 187 inspectAll(dump); | |
| 188 System.out.println("Total files:" + dump.names.size()); | |
| 189 for (String s : dump.names) { | |
| 190 System.out.println(s); | |
| 191 } | |
| 192 } | |
| 193 | |
| 194 // callback to minimize amount of Strings and Nodeids instantiated | |
| 195 public interface Inspector { | |
| 196 void changelogStart(); | |
| 197 | |
| 198 void changelogEnd(); | |
| 199 | |
| 200 void manifestStart(); | |
| 201 | |
| 202 void manifestEnd(); | |
| 203 | |
| 204 void fileStart(String name); | |
| 205 | |
| 206 void fileEnd(String name); | |
| 207 | |
| 208 /** | |
| 209 * XXX desperately need exceptions here | |
| 210 * @param element data element, instance might be reused, don't keep a reference to it or its raw data | |
| 211 * @return <code>true</code> to continue | |
| 212 */ | |
| 213 boolean element(GroupElement element); | |
| 214 } | |
| 215 | |
| 216 public static class Dump implements Inspector { | |
| 217 public final LinkedList<String> names = new LinkedList<String>(); | |
| 218 | |
| 219 public void changelogStart() { | |
| 220 System.out.println("Changelog group"); | |
| 221 } | |
| 222 | |
| 223 public void changelogEnd() { | |
| 224 } | |
| 225 | |
| 226 public void manifestStart() { | |
| 227 System.out.println("Manifest group"); | |
| 228 } | |
| 229 | |
| 230 public void manifestEnd() { | |
| 231 } | |
| 232 | |
| 233 public void fileStart(String name) { | |
| 234 names.add(name); | |
| 235 System.out.println(name); | |
| 236 } | |
| 237 | |
| 238 public void fileEnd(String name) { | |
| 239 } | |
| 240 | |
| 241 public boolean element(GroupElement ge) { | |
| 242 try { | |
| 243 System.out.printf(" %s %s %s %s; patches:%d\n", ge.node(), ge.firstParent(), ge.secondParent(), ge.cset(), ge.patches().size()); | |
| 244 } catch (Exception ex) { | |
| 245 ex.printStackTrace(); // FIXME | |
| 246 } | |
| 247 return true; | |
| 248 } | |
| 249 } | |
| 250 | |
| 251 public void inspectChangelog(Inspector inspector) throws IOException { | |
| 252 if (inspector == null) { | |
| 253 throw new IllegalArgumentException(); | |
| 254 } | |
| 255 DataAccess da = getDataStream(); | |
| 256 try { | |
| 257 internalInspectChangelog(da, inspector); | |
| 258 } finally { | |
| 259 da.done(); | |
| 260 } | |
| 261 } | |
| 262 | |
| 263 public void inspectManifest(Inspector inspector) throws IOException { | |
| 264 if (inspector == null) { | |
| 265 throw new IllegalArgumentException(); | |
| 266 } | |
| 267 DataAccess da = getDataStream(); | |
| 268 try { | |
| 269 if (da.isEmpty()) { | |
| 270 return; | |
| 271 } | |
| 272 skipGroup(da); // changelog | |
| 273 internalInspectManifest(da, inspector); | |
| 274 } finally { | |
| 275 da.done(); | |
| 276 } | |
| 277 } | |
| 278 | |
| 279 public void inspectFiles(Inspector inspector) throws IOException { | |
| 280 if (inspector == null) { | |
| 281 throw new IllegalArgumentException(); | |
| 282 } | |
| 283 DataAccess da = getDataStream(); | |
| 284 try { | |
| 285 if (da.isEmpty()) { | |
| 286 return; | |
| 287 } | |
| 288 skipGroup(da); // changelog | |
| 289 if (da.isEmpty()) { | |
| 290 return; | |
| 291 } | |
| 292 skipGroup(da); // manifest | |
| 293 internalInspectFiles(da, inspector); | |
| 294 } finally { | |
| 295 da.done(); | |
| 296 } | |
| 297 } | |
| 298 | |
| 299 public void inspectAll(Inspector inspector) throws IOException { | |
| 300 if (inspector == null) { | |
| 301 throw new IllegalArgumentException(); | |
| 302 } | |
| 303 DataAccess da = getDataStream(); | |
| 304 try { | |
| 305 internalInspectChangelog(da, inspector); | |
| 306 internalInspectManifest(da, inspector); | |
| 307 internalInspectFiles(da, inspector); | |
| 308 } finally { | |
| 309 da.done(); | |
| 310 } | |
| 311 } | |
| 312 | |
| 313 private void internalInspectChangelog(DataAccess da, Inspector inspector) throws IOException { | |
| 314 if (da.isEmpty()) { | |
| 315 return; | |
| 316 } | |
| 317 inspector.changelogStart(); | |
| 318 readGroup(da, inspector); | |
| 319 inspector.changelogEnd(); | |
| 320 } | |
| 321 | |
| 322 private void internalInspectManifest(DataAccess da, Inspector inspector) throws IOException { | |
| 323 if (da.isEmpty()) { | |
| 324 return; | |
| 325 } | |
| 326 inspector.manifestStart(); | |
| 327 readGroup(da, inspector); | |
| 328 inspector.manifestEnd(); | |
| 329 } | |
| 330 | |
| 331 private void internalInspectFiles(DataAccess da, Inspector inspector) throws IOException { | |
| 332 while (!da.isEmpty()) { | |
| 333 int fnameLen = da.readInt(); | |
| 334 if (fnameLen <= 4) { | |
| 335 break; // null chunk, the last one. | |
| 336 } | |
| 337 byte[] fnameBuf = new byte[fnameLen - 4]; | |
| 338 da.readBytes(fnameBuf, 0, fnameBuf.length); | |
| 339 String name = new String(fnameBuf); | |
| 340 inspector.fileStart(name); | |
| 341 readGroup(da, inspector); | |
| 342 inspector.fileEnd(name); | |
| 343 } | |
| 344 } | |
| 345 | |
| 346 private static void readGroup(DataAccess da, Inspector inspector) throws IOException { | |
| 347 int len = da.readInt(); | |
| 348 boolean good2go = true; | |
| 349 while (len > 4 && !da.isEmpty() && good2go) { | |
| 350 byte[] nb = new byte[80]; | |
| 351 da.readBytes(nb, 0, 80); | |
| 352 int dataLength = len - 84 /* length field + 4 nodeids */; | |
| 353 byte[] data = new byte[dataLength]; | |
| 354 da.readBytes(data, 0, dataLength); | |
| 355 DataAccess slice = new ByteArrayDataAccess(data); // XXX in fact, may pass a slicing DataAccess. | |
| 356 // Just need to make sure that we seek to proper location afterwards (where next GroupElement starts), | |
| 357 // regardless whether that slice has read it or not. | |
| 358 GroupElement ge = new GroupElement(nb, slice); | |
| 359 good2go = inspector.element(ge); | |
| 360 slice.done(); // BADA doesn't implement done(), but it could (e.g. free array) | |
| 361 /// and we'd better tell it we are not going to use it any more. However, it's important to ensure Inspector | |
| 362 // implementations out there do not retain GroupElement.rawData() | |
| 363 len = da.isEmpty() ? 0 : da.readInt(); | |
| 364 } | |
| 365 // need to skip up to group end if inspector told he don't want to continue with the group, | |
| 366 // because outer code may try to read next group immediately as we return back. | |
| 367 while (len > 4 && !da.isEmpty()) { | |
| 368 da.skip(len - 4 /* length field */); | |
| 369 len = da.isEmpty() ? 0 : da.readInt(); | |
| 370 } | |
| 371 } | |
| 372 | |
| 373 private static void skipGroup(DataAccess da) throws IOException { | |
| 374 int len = da.readInt(); | |
| 375 while (len > 4 && !da.isEmpty()) { | |
| 376 da.skip(len - 4); // sizeof(int) | |
| 377 len = da.isEmpty() ? 0 : da.readInt(); | |
| 378 } | |
| 379 } | |
| 380 | |
| 381 public static class GroupElement { | |
| 382 private final byte[] header; // byte[80] takes 120 bytes, 4 Nodeids - 192 | |
| 383 private final DataAccess dataAccess; | |
| 384 private List<RevlogStream.PatchRecord> patches; | |
| 385 | |
| 386 GroupElement(byte[] fourNodeids, DataAccess rawDataAccess) { | |
| 387 assert fourNodeids != null && fourNodeids.length == 80; | |
| 388 header = fourNodeids; | |
| 389 dataAccess = rawDataAccess; | |
| 390 } | |
| 391 | |
| 392 public Nodeid node() { | |
| 393 return Nodeid.fromBinary(header, 0); | |
| 394 } | |
| 395 | |
| 396 public Nodeid firstParent() { | |
| 397 return Nodeid.fromBinary(header, 20); | |
| 398 } | |
| 399 | |
| 400 public Nodeid secondParent() { | |
| 401 return Nodeid.fromBinary(header, 40); | |
| 402 } | |
| 403 | |
| 404 public Nodeid cset() { // cs seems to be changeset | |
| 405 return Nodeid.fromBinary(header, 60); | |
| 406 } | |
| 407 | |
| 408 public DataAccess rawData() { | |
| 409 return dataAccess; | |
| 410 } | |
| 411 | |
| 412 public List<RevlogStream.PatchRecord> patches() throws IOException { | |
| 413 if (patches == null) { | |
| 414 dataAccess.reset(); | |
| 415 LinkedList<RevlogStream.PatchRecord> p = new LinkedList<RevlogStream.PatchRecord>(); | |
| 416 while (!dataAccess.isEmpty()) { | |
| 417 RevlogStream.PatchRecord pr = RevlogStream.PatchRecord.read(dataAccess); | |
| 418 p.add(pr); | |
| 419 } | |
| 420 patches = p; | |
| 421 } | |
| 422 return patches; | |
| 423 } | |
| 424 | |
| 425 public byte[] apply(DataAccess baseContent) throws IOException { | |
| 426 return RevlogStream.apply(baseContent, -1, patches()); | |
| 427 } | |
| 428 } | |
| 429 } |
