Mercurial > hg4j
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 } |