Mercurial > hg4j
comparison src/org/tmatesoft/hg/repo/HgManifest.java @ 415:ee8264d80747
Explicit constant for regular file flags, access to flags for a given file revision
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Thu, 22 Mar 2012 18:54:11 +0100 |
| parents | bb278ccf9866 |
| children | ccd7d25e5aea |
comparison
equal
deleted
inserted
replaced
| 414:bb278ccf9866 | 415:ee8264d80747 |
|---|---|
| 34 import org.tmatesoft.hg.core.Nodeid; | 34 import org.tmatesoft.hg.core.Nodeid; |
| 35 import org.tmatesoft.hg.internal.DataAccess; | 35 import org.tmatesoft.hg.internal.DataAccess; |
| 36 import org.tmatesoft.hg.internal.DigestHelper; | 36 import org.tmatesoft.hg.internal.DigestHelper; |
| 37 import org.tmatesoft.hg.internal.EncodingHelper; | 37 import org.tmatesoft.hg.internal.EncodingHelper; |
| 38 import org.tmatesoft.hg.internal.Experimental; | 38 import org.tmatesoft.hg.internal.Experimental; |
| 39 import org.tmatesoft.hg.internal.IntMap; | |
| 39 import org.tmatesoft.hg.internal.IterateControlMediator; | 40 import org.tmatesoft.hg.internal.IterateControlMediator; |
| 40 import org.tmatesoft.hg.internal.Lifecycle; | 41 import org.tmatesoft.hg.internal.Lifecycle; |
| 41 import org.tmatesoft.hg.internal.Pool2; | 42 import org.tmatesoft.hg.internal.Pool2; |
| 42 import org.tmatesoft.hg.internal.RevlogStream; | 43 import org.tmatesoft.hg.internal.RevlogStream; |
| 43 import org.tmatesoft.hg.util.CancelSupport; | 44 import org.tmatesoft.hg.util.CancelSupport; |
| 52 */ | 53 */ |
| 53 public class HgManifest extends Revlog { | 54 public class HgManifest extends Revlog { |
| 54 private RevisionMapper revisionMap; | 55 private RevisionMapper revisionMap; |
| 55 private EncodingHelper encodingHelper; | 56 private EncodingHelper encodingHelper; |
| 56 | 57 |
| 58 /** | |
| 59 * File flags recorded in manifest | |
| 60 */ | |
| 57 public enum Flags { | 61 public enum Flags { |
| 58 Exec, Link; // FIXME REVISIT consider REGULAR instead of null | 62 /** |
| 63 * Executable bit set | |
| 64 */ | |
| 65 Exec, | |
| 66 /** | |
| 67 * Symbolic link | |
| 68 */ | |
| 69 Link, | |
| 70 /** | |
| 71 * Regular file | |
| 72 */ | |
| 73 RegularFile; | |
| 59 | 74 |
| 60 static Flags parse(String flags) { | 75 static Flags parse(String flags) { |
| 61 if ("x".equalsIgnoreCase(flags)) { | 76 if ("x".equalsIgnoreCase(flags)) { |
| 62 return Exec; | 77 return Exec; |
| 63 } | 78 } |
| 64 if ("l".equalsIgnoreCase(flags)) { | 79 if ("l".equalsIgnoreCase(flags)) { |
| 65 return Link; | 80 return Link; |
| 66 } | 81 } |
| 67 if (flags == null) { | 82 if (flags == null) { |
| 68 return null; | 83 return RegularFile; |
| 69 } | 84 } |
| 70 throw new IllegalStateException(flags); | 85 throw new IllegalStateException(flags); |
| 71 } | 86 } |
| 72 | 87 |
| 73 static Flags parse(byte[] data, int start, int length) { | 88 static Flags parse(byte[] data, int start, int length) { |
| 74 if (length == 0) { | 89 if (length == 0) { |
| 75 return null; | 90 return RegularFile; |
| 76 } | 91 } |
| 77 if (length == 1) { | 92 if (length == 1) { |
| 78 if (data[start] == 'x') { | 93 if (data[start] == 'x') { |
| 79 return Exec; | 94 return Exec; |
| 80 } | 95 } |
| 90 if (this == Exec) { | 105 if (this == Exec) { |
| 91 return "x"; | 106 return "x"; |
| 92 } | 107 } |
| 93 if (this == Link) { | 108 if (this == Link) { |
| 94 return "l"; | 109 return "l"; |
| 110 } | |
| 111 if (this == RegularFile) { | |
| 112 return ""; | |
| 95 } | 113 } |
| 96 throw new IllegalStateException(toString()); | 114 throw new IllegalStateException(toString()); |
| 97 } | 115 } |
| 98 } | 116 } |
| 99 | 117 |
| 240 // XXX package-local, IntMap, and HgDataFile getFileRevisionAt(int... localChangelogRevisions) | 258 // XXX package-local, IntMap, and HgDataFile getFileRevisionAt(int... localChangelogRevisions) |
| 241 @Experimental(reason="@see #getFileRevision") | 259 @Experimental(reason="@see #getFileRevision") |
| 242 public Map<Integer, Nodeid> getFileRevisions(final Path file, int... changelogRevisionIndexes) throws HgInvalidRevisionException, HgInvalidControlFileException { | 260 public Map<Integer, Nodeid> getFileRevisions(final Path file, int... changelogRevisionIndexes) throws HgInvalidRevisionException, HgInvalidControlFileException { |
| 243 // TODO need tests | 261 // TODO need tests |
| 244 int[] manifestRevisionIndexes = toManifestRevisionIndexes(changelogRevisionIndexes, null); | 262 int[] manifestRevisionIndexes = toManifestRevisionIndexes(changelogRevisionIndexes, null); |
| 245 final HashMap<Integer,Nodeid> rv = new HashMap<Integer, Nodeid>(changelogRevisionIndexes.length); | 263 IntMap<Nodeid> resMap = new IntMap<Nodeid>(changelogRevisionIndexes.length); |
| 246 content.iterate(manifestRevisionIndexes, true, new RevlogStream.Inspector() { | 264 content.iterate(manifestRevisionIndexes, true, new FileLookupInspector(encodingHelper, file, resMap, null)); |
| 247 | 265 // IntMap to HashMap, |
| 248 public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgException { | 266 HashMap<Integer,Nodeid> rv = new HashMap<Integer, Nodeid>(); |
| 249 ByteArrayOutputStream bos = new ByteArrayOutputStream(); | 267 resMap.fill(rv); |
| 250 try { | |
| 251 byte b; | |
| 252 while (!data.isEmpty() && (b = data.readByte()) != '\n') { | |
| 253 if (b != 0) { | |
| 254 bos.write(b); | |
| 255 } else { | |
| 256 String fname = new String(bos.toByteArray()); | |
| 257 bos.reset(); | |
| 258 if (file.toString().equals(fname)) { | |
| 259 byte[] nid = new byte[40]; | |
| 260 data.readBytes(nid, 0, 40); | |
| 261 rv.put(linkRevision, Nodeid.fromAscii(nid, 0, 40)); | |
| 262 break; | |
| 263 } else { | |
| 264 data.skip(40); | |
| 265 } | |
| 266 // else skip to the end of line | |
| 267 while (!data.isEmpty() && (b = data.readByte()) != '\n') | |
| 268 ; | |
| 269 } | |
| 270 } | |
| 271 } catch (IOException ex) { | |
| 272 throw new HgException(ex); | |
| 273 } | |
| 274 } | |
| 275 }); | |
| 276 return rv; | 268 return rv; |
| 269 } | |
| 270 | |
| 271 /** | |
| 272 * {@link HgDataFile#getFlags(int)} is public API | |
| 273 * | |
| 274 * @param changesetRevIndex changeset revision index | |
| 275 * @param file path to look up | |
| 276 * @return one of predefined enum values, or null if file was not known in the specified revision | |
| 277 * FIXME EXCEPTIONS | |
| 278 * @throws HgInvalidControlFileException | |
| 279 * @throws HgInvalidRevisionException | |
| 280 */ | |
| 281 /*package-local*/ Flags extractFlags(int changesetRevIndex, Path file) throws HgInvalidRevisionException, HgInvalidControlFileException { | |
| 282 int manifestRevIdx = fromChangelog(changesetRevIndex); | |
| 283 IntMap<Flags> resMap = new IntMap<Flags>(2); | |
| 284 content.iterate(manifestRevIdx, manifestRevIdx, true, new FileLookupInspector(encodingHelper, file, null, resMap)); | |
| 285 return resMap.get(changesetRevIndex); | |
| 277 } | 286 } |
| 278 | 287 |
| 279 | 288 |
| 280 /** | 289 /** |
| 281 * @param changelogRevisionIndexes non-null | 290 * @param changelogRevisionIndexes non-null |
| 327 boolean end(int manifestRevision); | 336 boolean end(int manifestRevision); |
| 328 } | 337 } |
| 329 | 338 |
| 330 @Experimental(reason="Explore Path alternative for filenames and enum for flags") | 339 @Experimental(reason="Explore Path alternative for filenames and enum for flags") |
| 331 public interface Inspector2 extends Inspector { | 340 public interface Inspector2 extends Inspector { |
| 341 /** | |
| 342 * @param nid file revision | |
| 343 * @param fname file name | |
| 344 * @param flags one of {@link HgManifest.Flags} constants, not <code>null</code> | |
| 345 * @return <code>true</code> to continue iteration, <code>false</code> to stop | |
| 346 */ | |
| 332 boolean next(Nodeid nid, Path fname, Flags flags); | 347 boolean next(Nodeid nid, Path fname, Flags flags); |
| 333 } | 348 } |
| 334 | 349 |
| 335 /** | 350 /** |
| 336 * When Pool uses Strings directly, | 351 * When Pool uses Strings directly, |
| 465 // 'x' and 'l' for executable bits and symlinks? | 480 // 'x' and 'l' for executable bits and symlinks? |
| 466 // hg --debug manifest shows 644 for each regular file in my repo | 481 // hg --debug manifest shows 644 for each regular file in my repo |
| 467 // for cpython 0..10k, there are 4361062 flag checks, and there's only 1 unique flag | 482 // for cpython 0..10k, there are 4361062 flag checks, and there's only 1 unique flag |
| 468 flags = Flags.parse(data, x + nodeidLen, i-x-nodeidLen); | 483 flags = Flags.parse(data, x + nodeidLen, i-x-nodeidLen); |
| 469 } else { | 484 } else { |
| 470 flags = null; | 485 flags = Flags.RegularFile; |
| 471 } | 486 } |
| 472 boolean good2go; | 487 boolean good2go; |
| 473 if (inspector2 == null) { | 488 if (inspector2 == null) { |
| 474 String flagString = flags == null ? null : flags.nativeString(); | 489 String flagString = flags == Flags.RegularFile ? null : flags.nativeString(); |
| 475 good2go = inspector.next(nid, fname.toString(), flagString); | 490 good2go = inspector.next(nid, fname.toString(), flagString); |
| 476 } else { | 491 } else { |
| 477 good2go = inspector2.next(nid, fname, flags); | 492 good2go = inspector2.next(nid, fname, flags); |
| 478 } | 493 } |
| 479 if (!good2go) { | 494 if (!good2go) { |
| 604 repo.getContext().getLog().error(getClass(), ex, null); | 619 repo.getContext().getLog().error(getClass(), ex, null); |
| 605 } | 620 } |
| 606 } | 621 } |
| 607 } | 622 } |
| 608 } | 623 } |
| 624 | |
| 625 /** | |
| 626 * Look up specified file in possibly multiple manifest revisions, collect file revision and flags. | |
| 627 */ | |
| 628 private static class FileLookupInspector implements RevlogStream.Inspector { | |
| 629 | |
| 630 private final byte[] filenameAsBytes; | |
| 631 private final IntMap<Nodeid> csetIndex2FileRev; | |
| 632 private final IntMap<Flags> csetIndex2Flags; | |
| 633 | |
| 634 public FileLookupInspector(EncodingHelper eh, Path fileToLookUp, IntMap<Nodeid> csetIndex2FileRevMap, IntMap<Flags> csetIndex2FlagsMap) { | |
| 635 assert fileToLookUp != null; | |
| 636 // need at least one map for the inspector to make any sense | |
| 637 assert csetIndex2FileRevMap != null || csetIndex2FlagsMap != null; | |
| 638 csetIndex2FileRev = csetIndex2FileRevMap; | |
| 639 csetIndex2Flags = csetIndex2FlagsMap; | |
| 640 filenameAsBytes = eh.toManifest(fileToLookUp.toString()); | |
| 641 } | |
| 642 | |
| 643 public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgException { | |
| 644 ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
| 645 try { | |
| 646 byte b; | |
| 647 while (!data.isEmpty() && (b = data.readByte()) != '\n') { | |
| 648 if (b != 0) { | |
| 649 bos.write(b); | |
| 650 } else { | |
| 651 byte[] byteArray = bos.toByteArray(); | |
| 652 bos.reset(); | |
| 653 if (Arrays.equals(filenameAsBytes, byteArray)) { | |
| 654 if (csetIndex2FileRev != null) { | |
| 655 byte[] nid = new byte[40]; | |
| 656 data.readBytes(nid, 0, 40); | |
| 657 csetIndex2FileRev.put(linkRevision, Nodeid.fromAscii(nid, 0, 40)); | |
| 658 } else { | |
| 659 data.skip(40); | |
| 660 } | |
| 661 if (csetIndex2Flags != null) { | |
| 662 while (!data.isEmpty() && (b = data.readByte()) != '\n') { | |
| 663 bos.write(b); | |
| 664 } | |
| 665 Flags flags; | |
| 666 if (bos.size() == 0) { | |
| 667 flags = Flags.RegularFile; | |
| 668 } else { | |
| 669 flags = Flags.parse(bos.toByteArray(), 0, bos.size()); | |
| 670 } | |
| 671 csetIndex2Flags.put(linkRevision, flags); | |
| 672 } | |
| 673 break; | |
| 674 } else { | |
| 675 data.skip(40); | |
| 676 } | |
| 677 // else skip to the end of line | |
| 678 while (!data.isEmpty() && (b = data.readByte()) != '\n') | |
| 679 ; | |
| 680 } | |
| 681 } | |
| 682 } catch (IOException ex) { | |
| 683 throw new HgException(ex); // FIXME EXCEPTIONS | |
| 684 } | |
| 685 } | |
| 686 } | |
| 609 } | 687 } |
