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 }