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