comparison src/org/tmatesoft/hg/repo/HgManifest.java @ 426:063b0663495a

HgManifest#getFileRevisions refactored into #walkFileRevisions to match pattern throught rest of the library
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 28 Mar 2012 19:34:37 +0200
parents 48f993aa2f41
children 12f668401613
comparison
equal deleted inserted replaced
425:48f993aa2f41 426:063b0663495a
21 21
22 import java.io.ByteArrayOutputStream; 22 import java.io.ByteArrayOutputStream;
23 import java.io.IOException; 23 import java.io.IOException;
24 import java.util.ArrayList; 24 import java.util.ArrayList;
25 import java.util.Arrays; 25 import java.util.Arrays;
26 import java.util.HashMap;
27 import java.util.Map;
28 26
29 import org.tmatesoft.hg.core.HgChangesetFileSneaker; 27 import org.tmatesoft.hg.core.HgChangesetFileSneaker;
30 import org.tmatesoft.hg.core.Nodeid; 28 import org.tmatesoft.hg.core.Nodeid;
31 import org.tmatesoft.hg.internal.Callback; 29 import org.tmatesoft.hg.internal.Callback;
32 import org.tmatesoft.hg.internal.DataAccess; 30 import org.tmatesoft.hg.internal.DataAccess;
33 import org.tmatesoft.hg.internal.DigestHelper; 31 import org.tmatesoft.hg.internal.DigestHelper;
34 import org.tmatesoft.hg.internal.EncodingHelper; 32 import org.tmatesoft.hg.internal.EncodingHelper;
35 import org.tmatesoft.hg.internal.Experimental;
36 import org.tmatesoft.hg.internal.IntMap; 33 import org.tmatesoft.hg.internal.IntMap;
37 import org.tmatesoft.hg.internal.IterateControlMediator; 34 import org.tmatesoft.hg.internal.IterateControlMediator;
38 import org.tmatesoft.hg.internal.Lifecycle; 35 import org.tmatesoft.hg.internal.Lifecycle;
39 import org.tmatesoft.hg.internal.Pool2; 36 import org.tmatesoft.hg.internal.Pool2;
40 import org.tmatesoft.hg.internal.RevlogStream; 37 import org.tmatesoft.hg.internal.RevlogStream;
48 * 45 *
49 * @see http://mercurial.selenic.com/wiki/Manifest 46 * @see http://mercurial.selenic.com/wiki/Manifest
50 * @author Artem Tikhomirov 47 * @author Artem Tikhomirov
51 * @author TMate Software Ltd. 48 * @author TMate Software Ltd.
52 */ 49 */
53 public class HgManifest extends Revlog { 50 public final class HgManifest extends Revlog {
54 private RevisionMapper revisionMap; 51 private RevisionMapper revisionMap;
55 private EncodingHelper encodingHelper; 52 private EncodingHelper encodingHelper;
56 53
57 /** 54 /**
58 * File flags recorded in manifest 55 * File flags recorded in manifest
241 return revisionMap.at(changesetRevisionIndex); 238 return revisionMap.at(changesetRevisionIndex);
242 } 239 }
243 240
244 /** 241 /**
245 * Extracts file revision as it was known at the time of given changeset. 242 * Extracts file revision as it was known at the time of given changeset.
246 * For more thorough details about file at specific changeset, use {@link HgChangesetFileSneaker}. 243 * <p>For more thorough details about file at specific changeset, use {@link HgChangesetFileSneaker}.
244 * <p>To visit few changesets for the same file, use {@link #walkFileRevisions(Path, Inspector, int...)}
247 * 245 *
246 * @see #walkFileRevisions(Path, Inspector, int...)
248 * @see HgChangesetFileSneaker 247 * @see HgChangesetFileSneaker
249 * @param changelogRevisionIndex local changeset index 248 * @param changelogRevisionIndex local changeset index
250 * @param file path to file in question 249 * @param file path to file in question
251 * @return file revision or <code>null</code> if manifest at specified revision doesn't list such file 250 * @return file revision or <code>null</code> if manifest at specified revision doesn't list such file
252 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> 251 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
255 // there's no need for HgDataFile to own this method, or get a delegate 254 // there's no need for HgDataFile to own this method, or get a delegate
256 // as most of HgDataFile API is using file revision indexes, and there's easy step from file revision index to 255 // as most of HgDataFile API is using file revision indexes, and there's easy step from file revision index to
257 // both file revision and changeset revision index. But there's no easy way to go from changesetRevisionIndex to 256 // both file revision and changeset revision index. But there's no easy way to go from changesetRevisionIndex to
258 // file revision (the task this method solves), exept for HgFileInformer 257 // file revision (the task this method solves), exept for HgFileInformer
259 // I feel methods dealing with changeset indexes shall be more exposed in HgChangelog and HgManifest API. 258 // I feel methods dealing with changeset indexes shall be more exposed in HgChangelog and HgManifest API.
260 return getFileRevisions(file, changelogRevisionIndex).get(changelogRevisionIndex);
261 }
262
263 // XXX package-local or better API
264 @Experimental(reason="Map as return value isn't that good")
265 public Map<Integer, Nodeid> getFileRevisions(final Path file, int... changelogRevisionIndexes) throws HgInvalidRevisionException, HgInvalidControlFileException {
266 // FIXME in fact, walk(Inspectr, path, int[]) might be better alternative than get()
267 // TODO need tests 259 // TODO need tests
268 int[] manifestRevisionIndexes = toManifestRevisionIndexes(changelogRevisionIndexes, null); 260 int manifestRevIndex = fromChangelog(changelogRevisionIndex);
269 IntMap<Nodeid> resMap = new IntMap<Nodeid>(changelogRevisionIndexes.length); 261 if (manifestRevIndex == BAD_REVISION) {
270 content.iterate(manifestRevisionIndexes, true, new FileLookupInspector(encodingHelper, file, resMap, null)); 262 return null;
271 // IntMap to HashMap, 263 }
272 HashMap<Integer,Nodeid> rv = new HashMap<Integer, Nodeid>(); 264 IntMap<Nodeid> resMap = new IntMap<Nodeid>(3);
273 resMap.fill(rv); 265 FileLookupInspector parser = new FileLookupInspector(encodingHelper, file, resMap, null);
274 return rv; 266 parser.walk(manifestRevIndex, content);
267 return resMap.get(changelogRevisionIndex);
268 }
269
270 /**
271 * Visit file revisions as they were recorded at the time of given changesets. Same file revision may be reported as many times as
272 * there are changesets that refer to that revision. Both {@link Inspector#begin(int, Nodeid, int)} and {@link Inspector#end(int)}
273 * with appropriate values are invoked around {@link Inspector#next(Nodeid, Path, Flags)} call for the supplied file
274 *
275 * <p>NOTE, this method doesn't respect return values from callback (i.e. to stop iteration), as it's lookup of a single file
276 * and canceling it seems superfluous. However, this may change in future and it's recommended to return <code>true</code> from
277 * all {@link Inspector} methods.
278 *
279 * @see #getFileRevision(int, Path)
280 * @param file path of interest
281 * @param inspector callback to receive details about selected file
282 * @param changelogRevisionIndexes changeset indexes to visit
283 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
284 */
285 public void walkFileRevisions(Path file, Inspector inspector, int... changelogRevisionIndexes) throws HgRuntimeException {
286 if (file == null || inspector == null || changelogRevisionIndexes == null) {
287 throw new IllegalArgumentException();
288 }
289 // TODO [post-1.0] need tests. There's Main#checkWalkFileRevisions that may be a starting point
290 int[] manifestRevIndexes = toManifestRevisionIndexes(changelogRevisionIndexes, null);
291 FileLookupInspector parser = new FileLookupInspector(encodingHelper, file, inspector);
292 parser.walk(manifestRevIndexes, content);
275 } 293 }
276 294
277 /** 295 /**
278 * Extract file {@link Flags flags} as they were recorded in appropriate manifest version. 296 * Extract file {@link Flags flags} as they were recorded in appropriate manifest version.
279 * 297 *
284 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> 302 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
285 */ 303 */
286 public Flags getFileFlags(int changesetRevIndex, Path file) throws HgInvalidRevisionException, HgInvalidControlFileException { 304 public Flags getFileFlags(int changesetRevIndex, Path file) throws HgInvalidRevisionException, HgInvalidControlFileException {
287 int manifestRevIdx = fromChangelog(changesetRevIndex); 305 int manifestRevIdx = fromChangelog(changesetRevIndex);
288 IntMap<Flags> resMap = new IntMap<Flags>(2); 306 IntMap<Flags> resMap = new IntMap<Flags>(2);
289 content.iterate(manifestRevIdx, manifestRevIdx, true, new FileLookupInspector(encodingHelper, file, null, resMap)); 307 FileLookupInspector parser = new FileLookupInspector(encodingHelper, file, null, resMap);
308 parser.walk(manifestRevIdx, content);
290 return resMap.get(changesetRevIndex); 309 return resMap.get(changesetRevIndex);
291 } 310 }
292 311
293 312
294 /** 313 /**
609 /** 628 /**
610 * Look up specified file in possibly multiple manifest revisions, collect file revision and flags. 629 * Look up specified file in possibly multiple manifest revisions, collect file revision and flags.
611 */ 630 */
612 private static class FileLookupInspector implements RevlogStream.Inspector { 631 private static class FileLookupInspector implements RevlogStream.Inspector {
613 632
633 private final Path filename;
614 private final byte[] filenameAsBytes; 634 private final byte[] filenameAsBytes;
615 private final IntMap<Nodeid> csetIndex2FileRev; 635 private final IntMap<Nodeid> csetIndex2FileRev;
616 private final IntMap<Flags> csetIndex2Flags; 636 private final IntMap<Flags> csetIndex2Flags;
637 private final Inspector delegate;
617 638
618 public FileLookupInspector(EncodingHelper eh, Path fileToLookUp, IntMap<Nodeid> csetIndex2FileRevMap, IntMap<Flags> csetIndex2FlagsMap) { 639 public FileLookupInspector(EncodingHelper eh, Path fileToLookUp, IntMap<Nodeid> csetIndex2FileRevMap, IntMap<Flags> csetIndex2FlagsMap) {
619 assert fileToLookUp != null; 640 assert fileToLookUp != null;
620 // need at least one map for the inspector to make any sense 641 // need at least one map for the inspector to make any sense
621 assert csetIndex2FileRevMap != null || csetIndex2FlagsMap != null; 642 assert csetIndex2FileRevMap != null || csetIndex2FlagsMap != null;
643 filename = fileToLookUp;
644 filenameAsBytes = eh.toManifest(fileToLookUp.toString());
645 delegate = null;
622 csetIndex2FileRev = csetIndex2FileRevMap; 646 csetIndex2FileRev = csetIndex2FileRevMap;
623 csetIndex2Flags = csetIndex2FlagsMap; 647 csetIndex2Flags = csetIndex2FlagsMap;
648 }
649
650 public FileLookupInspector(EncodingHelper eh, Path fileToLookUp, Inspector delegateInspector) {
651 assert fileToLookUp != null;
652 assert delegateInspector != null;
653 filename = fileToLookUp;
624 filenameAsBytes = eh.toManifest(fileToLookUp.toString()); 654 filenameAsBytes = eh.toManifest(fileToLookUp.toString());
655 delegate = delegateInspector;
656 csetIndex2FileRev = null;
657 csetIndex2Flags = null;
658 }
659
660 void walk(int manifestRevIndex, RevlogStream content) {
661 content.iterate(manifestRevIndex, manifestRevIndex, true, this);
662 }
663
664 void walk(int[] manifestRevIndexes, RevlogStream content) {
665 content.iterate(manifestRevIndexes, true, this);
625 } 666 }
626 667
627 public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) { 668 public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) {
628 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 669 ByteArrayOutputStream bos = new ByteArrayOutputStream();
629 try { 670 try {
633 bos.write(b); 674 bos.write(b);
634 } else { 675 } else {
635 byte[] byteArray = bos.toByteArray(); 676 byte[] byteArray = bos.toByteArray();
636 bos.reset(); 677 bos.reset();
637 if (Arrays.equals(filenameAsBytes, byteArray)) { 678 if (Arrays.equals(filenameAsBytes, byteArray)) {
638 if (csetIndex2FileRev != null) { 679 Nodeid fileRev = null;
680 Flags flags = null;
681 if (csetIndex2FileRev != null || delegate != null) {
639 byte[] nid = new byte[40]; 682 byte[] nid = new byte[40];
640 data.readBytes(nid, 0, 40); 683 data.readBytes(nid, 0, 40);
641 csetIndex2FileRev.put(linkRevision, Nodeid.fromAscii(nid, 0, 40)); 684 fileRev = Nodeid.fromAscii(nid, 0, 40);
642 } else { 685 } else {
643 data.skip(40); 686 data.skip(40);
644 } 687 }
645 if (csetIndex2Flags != null) { 688 if (csetIndex2Flags != null || delegate != null) {
646 while (!data.isEmpty() && (b = data.readByte()) != '\n') { 689 while (!data.isEmpty() && (b = data.readByte()) != '\n') {
647 bos.write(b); 690 bos.write(b);
648 } 691 }
649 Flags flags;
650 if (bos.size() == 0) { 692 if (bos.size() == 0) {
651 flags = Flags.RegularFile; 693 flags = Flags.RegularFile;
652 } else { 694 } else {
653 flags = Flags.parse(bos.toByteArray(), 0, bos.size()); 695 flags = Flags.parse(bos.toByteArray(), 0, bos.size());
654 } 696 }
655 csetIndex2Flags.put(linkRevision, flags); 697
698 }
699 if (delegate != null) {
700 assert flags != null;
701 assert fileRev != null;
702 delegate.begin(revisionNumber, Nodeid.fromBinary(nodeid, 0), linkRevision);
703 delegate.next(fileRev, filename, flags);
704 delegate.end(revisionNumber);
705
706 } else {
707 if (csetIndex2FileRev != null) {
708 csetIndex2FileRev.put(linkRevision, fileRev);
709 }
710 if (csetIndex2Flags != null) {
711 csetIndex2Flags.put(linkRevision, flags);
712 }
656 } 713 }
657 break; 714 break;
658 } else { 715 } else {
659 data.skip(40); 716 data.skip(40);
660 } 717 }