# HG changeset patch # User Artem Tikhomirov # Date 1332438851 -3600 # Node ID ee8264d80747b04bf9b8539af90d9e0a3fbba594 # Parent bb278ccf986645fcb3930e0c2e29b5d2110b709c Explicit constant for regular file flags, access to flags for a given file revision diff -r bb278ccf9866 -r ee8264d80747 cmdline/org/tmatesoft/hg/console/Main.java --- a/cmdline/org/tmatesoft/hg/console/Main.java Wed Mar 21 20:51:12 2012 +0100 +++ b/cmdline/org/tmatesoft/hg/console/Main.java Thu Mar 22 18:54:11 2012 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 TMate Software Ltd + * Copyright (c) 2011-2012 TMate Software Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -93,6 +93,7 @@ public static void main(String[] args) throws Exception { Main m = new Main(args); + m.checkFileFlags(); // m.buildFileLog(); // m.testConsoleLog(); // m.testTreeTraversal(); @@ -111,10 +112,21 @@ // m.testStatusInternals(); // m.catCompleteHistory(); // m.dumpCompleteManifestLow(); - m.dumpCompleteManifestHigh(); +// m.dumpCompleteManifestHigh(); // m.bunchOfTests(); } + private void checkFileFlags() throws Exception { + // ~/hg/test-flags repo + // TODO transform to a test once I keep test-flags in test-repos.jar + HgDataFile link = hgRepo.getFileNode("file-link"); + HgDataFile exec = hgRepo.getFileNode("file-exec"); + HgDataFile file = hgRepo.getFileNode("regular-file"); + System.out.println("Link: " + link.getFlags(TIP)); + System.out.println("Exec: " + exec.getFlags(TIP)); + System.out.println("File: " + file.getFlags(TIP)); + } + private void buildFileLog() throws Exception { HgLogCommand cmd = new HgLogCommand(hgRepo); cmd.file("file1", false); diff -r bb278ccf9866 -r ee8264d80747 cmdline/org/tmatesoft/hg/console/Manifest.java --- a/cmdline/org/tmatesoft/hg/console/Manifest.java Wed Mar 21 20:51:12 2012 +0100 +++ b/cmdline/org/tmatesoft/hg/console/Manifest.java Thu Mar 22 18:54:11 2012 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2011 TMate Software Ltd + * Copyright (c) 2010-2012 TMate Software Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,8 +20,11 @@ import static org.tmatesoft.hg.repo.HgRepository.TIP; import org.tmatesoft.hg.core.HgFileRevision; +import org.tmatesoft.hg.core.HgInvalidControlFileException; +import org.tmatesoft.hg.core.HgInvalidRevisionException; import org.tmatesoft.hg.core.HgManifestCommand; import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.repo.HgManifest; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.util.Path; @@ -49,14 +52,30 @@ public void dir(Path p) { } public void file(HgFileRevision fileRevision) { - if (debug) { - System.out.print(fileRevision.getRevision());; + try { + if (debug) { + System.out.print(fileRevision.getRevision());; + } + if (debug || verbose) { + HgManifest.Flags flags = fileRevision.getFileFlags(); + Object s; + if (flags == HgManifest.Flags.RegularFile) { + s = Integer.toOctalString(0644); + } else if (flags == HgManifest.Flags.Exec) { + s = Integer.toOctalString(0755); + } else if (flags == HgManifest.Flags.Link) { + s = "lnk"; + } else { + s = String.valueOf(flags); + } + System.out.printf(" %s ", s); + } + System.out.println(fileRevision.getPath()); + } catch (HgInvalidControlFileException e) { + e.printStackTrace(); + } catch (HgInvalidRevisionException e) { + e.printStackTrace(); } - if (debug || verbose) { - System.out.print(" 644"); // FIXME real flags! - System.out.print(" "); - } - System.out.println(fileRevision.getPath()); } public void end(Nodeid manifestRevision) { diff -r bb278ccf9866 -r ee8264d80747 src/org/tmatesoft/hg/core/HgChangeset.java --- a/src/org/tmatesoft/hg/core/HgChangeset.java Wed Mar 21 20:51:12 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgChangeset.java Thu Mar 22 18:54:11 2012 +0100 @@ -272,7 +272,7 @@ if (nid == null) { throw new HgException(String.format("For the file %s recorded as modified couldn't find revision after change", s)); } - modified.add(new HgFileRevision(repo, nid, s, null)); + modified.add(new HgFileRevision(repo, nid, null, s, null)); } final Map copied = r.getCopied(); for (Path s : r.getAdded()) { @@ -280,7 +280,7 @@ if (nid == null) { throw new HgException(String.format("For the file %s recorded as added couldn't find revision after change", s)); } - added.add(new HgFileRevision(repo, nid, s, copied.get(s))); + added.add(new HgFileRevision(repo, nid, null, s, copied.get(s))); } for (Path s : r.getRemoved()) { // with Path from getRemoved, may just copy diff -r bb278ccf9866 -r ee8264d80747 src/org/tmatesoft/hg/core/HgFileInformer.java --- a/src/org/tmatesoft/hg/core/HgFileInformer.java Wed Mar 21 20:51:12 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgFileInformer.java Thu Mar 22 18:54:11 2012 +0100 @@ -18,6 +18,7 @@ import org.tmatesoft.hg.internal.ManifestRevision; import org.tmatesoft.hg.repo.HgDataFile; +import org.tmatesoft.hg.repo.HgManifest; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.util.Path; import org.tmatesoft.hg.util.Status; @@ -121,6 +122,7 @@ return checkResult; } Nodeid toExtract = null; + HgManifest.Flags extractRevFlags = null; String phaseMsg = "Extract manifest revision failed"; try { if (cachedManifest == null) { @@ -130,6 +132,7 @@ // cachedManifest shall be meaningful - changelog.getRevisionIndex() above ensures we've got version that exists. } toExtract = cachedManifest.nodeid(file); + extractRevFlags = cachedManifest.flags(file); phaseMsg = "Follow copy/rename failed"; if (toExtract == null && followRenames) { while (toExtract == null && dataFile.isCopy()) { @@ -137,6 +140,7 @@ file = dataFile.getCopySourceName(); dataFile = repo.getFileNode(file); toExtract = cachedManifest.nodeid(file); + extractRevFlags = cachedManifest.flags(file); } } } catch (HgException ex) { @@ -144,7 +148,7 @@ return checkResult; } if (toExtract != null) { - fileRevision = new HgFileRevision(repo, toExtract, dataFile.getPath()); + fileRevision = new HgFileRevision(repo, toExtract, extractRevFlags, dataFile.getPath()); checkResult = new Status(Status.Kind.OK, String.format("File %s, revision %s found at changeset %s", dataFile.getPath(), toExtract.shortNotation(), cset.shortNotation())); return checkResult; } diff -r bb278ccf9866 -r ee8264d80747 src/org/tmatesoft/hg/core/HgFileRevision.java --- a/src/org/tmatesoft/hg/core/HgFileRevision.java Wed Mar 21 20:51:12 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgFileRevision.java Thu Mar 22 18:54:11 2012 +0100 @@ -17,6 +17,8 @@ package org.tmatesoft.hg.core; import org.tmatesoft.hg.repo.HgDataFile; +import org.tmatesoft.hg.repo.HgManifest; +import org.tmatesoft.hg.repo.HgManifest.Flags; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.util.ByteChannel; import org.tmatesoft.hg.util.CancelledException; @@ -36,20 +38,30 @@ private Path origin; private Boolean isCopy = null; // null means not yet known private Pair parents; + private Flags flags; // null unless set/extracted - public HgFileRevision(HgRepository hgRepo, Nodeid rev, Path p) { + /** + * FIXME has to be public? + * + * @param hgRepo repository + * @param rev file revision + * @param manifestEntryFlags file flags at this revision (optional, may be null) + * @param p path of the file at the given revision + */ + public HgFileRevision(HgRepository hgRepo, Nodeid rev, HgManifest.Flags manifestEntryFlags, Path p) { if (hgRepo == null || rev == null || p == null) { // since it's package local, it is our code to blame for non validated arguments throw new IllegalArgumentException(); } repo = hgRepo; revision = rev; + flags = manifestEntryFlags; path = p; } // this cons shall be used when we know whether p was a copy. Perhaps, shall pass Map instead to stress orig argument is not optional - HgFileRevision(HgRepository hgRepo, Nodeid rev, Path p, Path orig) { - this(hgRepo, rev, p); + HgFileRevision(HgRepository hgRepo, Nodeid rev, HgManifest.Flags flags, Path p, Path orig) { + this(hgRepo, rev, flags, p); isCopy = Boolean.valueOf(orig == null); origin = orig; } @@ -61,6 +73,20 @@ public Nodeid getRevision() { return revision; } + + /** + * Executable or symbolic link, or null if regular file + * @throws HgInvalidRevisionException if supplied nodeid doesn't identify any revision from this revlog + * @throws HgInvalidControlFileException if access to revlog index/data entry failed + */ + public HgManifest.Flags getFileFlags() throws HgInvalidControlFileException, HgInvalidRevisionException { + if (flags == null) { + HgDataFile df = repo.getFileNode(path); + int revIdx = df.getRevisionIndex(revision); + flags = df.getFlags(revIdx); + } + return flags; + } public boolean wasCopied() throws HgException { if (isCopy == null) { diff -r bb278ccf9866 -r ee8264d80747 src/org/tmatesoft/hg/core/HgLogCommand.java --- a/src/org/tmatesoft/hg/core/HgLogCommand.java Wed Mar 21 20:51:12 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgLogCommand.java Thu Mar 22 18:54:11 2012 +0100 @@ -235,8 +235,8 @@ // even if we do not follow history, report file rename do { if (handler instanceof FileHistoryHandler) { - HgFileRevision src = new HgFileRevision(repo, fileNode.getCopySourceRevision(), fileNode.getCopySourceName()); - HgFileRevision dst = new HgFileRevision(repo, fileNode.getRevision(0), fileNode.getPath(), src.getPath()); + HgFileRevision src = new HgFileRevision(repo, fileNode.getCopySourceRevision(), null, fileNode.getCopySourceName()); + HgFileRevision dst = new HgFileRevision(repo, fileNode.getRevision(0), null, fileNode.getPath(), src.getPath()); try { ((FileHistoryHandler) handler).copy(src, dst); } catch (HgCallbackTargetException.Wrap ex) { diff -r bb278ccf9866 -r ee8264d80747 src/org/tmatesoft/hg/core/HgManifestCommand.java --- a/src/org/tmatesoft/hg/core/HgManifestCommand.java Wed Mar 21 20:51:12 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgManifestCommand.java Thu Mar 22 18:54:11 2012 +0100 @@ -187,7 +187,7 @@ if (matcher != null && !matcher.accept(fname)) { return true; } - HgFileRevision fr = new HgFileRevision(repo, nid, fname); + HgFileRevision fr = new HgFileRevision(repo, nid, flags, fname); if (needDirs) { manifestContent.add(fr); } else { diff -r bb278ccf9866 -r ee8264d80747 src/org/tmatesoft/hg/internal/EncodingHelper.java --- a/src/org/tmatesoft/hg/internal/EncodingHelper.java Wed Mar 21 20:51:12 2012 +0100 +++ b/src/org/tmatesoft/hg/internal/EncodingHelper.java Thu Mar 22 18:54:11 2012 +0100 @@ -17,13 +17,17 @@ package org.tmatesoft.hg.internal; import java.nio.ByteBuffer; +import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; +import org.tmatesoft.hg.core.SessionContext; + /** * Keep all encoding-related issues in the single place + * NOT thread-safe (encoder and decoder requires synchronized access) * @author Artem Tikhomirov * @author TMate Software Ltd. */ @@ -34,10 +38,12 @@ * http://mercurial.808500.n3.nabble.com/Unicode-support-request-td3430704.html */ + private final SessionContext sessionContext; private final CharsetEncoder encoder; private final CharsetDecoder decoder; - EncodingHelper(Charset fsEncoding) { + EncodingHelper(Charset fsEncoding, SessionContext ctx) { + sessionContext = ctx; decoder = fsEncoding.newDecoder(); encoder = fsEncoding.newEncoder(); } @@ -46,16 +52,40 @@ try { return decoder.decode(ByteBuffer.wrap(data, start, length)).toString(); } catch (CharacterCodingException ex) { + sessionContext.getLog().error(getClass(), ex, String.format("Use of charset %s failed, resort to system default", charset().name())); // resort to system-default return new String(data, start, length); } } - public String fromDirstate(byte[] data, int start, int length) throws CharacterCodingException { + /** + * @return byte representation of the string directly comparable to bytes in manifest + */ + public byte[] toManifest(String s) { + if (s == null) { + // perhaps, can return byte[0] in this case? + throw new IllegalArgumentException(); + } + try { + // synchonized(encoder) { + ByteBuffer bb = encoder.encode(CharBuffer.wrap(s)); + // } + byte[] rv = new byte[bb.remaining()]; + bb.get(rv, 0, rv.length); + return rv; + } catch (CharacterCodingException ex) { + sessionContext.getLog().error(getClass(), ex, String.format("Use of charset %s failed, resort to system default", charset().name())); + // resort to system-default + return s.getBytes(); + } + } + + public String fromDirstate(byte[] data, int start, int length) throws CharacterCodingException { // FIXME perhaps, log is enough, and charset() may be private? return decoder.decode(ByteBuffer.wrap(data, start, length)).toString(); } public Charset charset() { return encoder.charset(); } + } diff -r bb278ccf9866 -r ee8264d80747 src/org/tmatesoft/hg/internal/IntMap.java --- a/src/org/tmatesoft/hg/internal/IntMap.java Wed Mar 21 20:51:12 2012 +0100 +++ b/src/org/tmatesoft/hg/internal/IntMap.java Thu Mar 22 18:54:11 2012 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 TMate Software Ltd + * Copyright (c) 2011-2012 TMate Software Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,8 +16,13 @@ */ package org.tmatesoft.hg.internal; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; import java.util.NoSuchElementException; +import org.tmatesoft.hg.core.Nodeid; + /** * Map implementation that uses plain int keys and performs with log n effectiveness. @@ -140,8 +145,73 @@ size -= count; } } + + // document iterator is non-modifying (neither remove() nor setValue() works) + // perhaps, may also implement Iterable to use nice for() + public Iterator> entryIterator() { + class E implements Map.Entry { + private Integer key; + private V value; + + public Integer getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + void init(Integer k, V v) { + key = k; + value = v; + } + } + + return new Iterator>() { + private int i = 0; + private final E entry = new E(); + private final int _size; + private final int[] _keys; + private final Object[] _values; + + { + _size = IntMap.this.size; + _keys = IntMap.this.keys; + _values = IntMap.this.values; + } + + public boolean hasNext() { + return i < _size; + } + + public Entry next() { + if (i >= _size) { + throw new NoSuchElementException(); + } + @SuppressWarnings("unchecked") + V val = (V) _values[i]; + entry.init(_keys[i], val); + i++; + return entry; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } - + public Map fill(Map map) { + for (Iterator> it = entryIterator(); it.hasNext(); ) { + Map.Entry next = it.next(); + map.put(next.getKey(), next.getValue()); + } + return map; + } // copy of Arrays.binarySearch, with upper search limit as argument private static int binarySearch(int[] a, int high, int key) { diff -r bb278ccf9866 -r ee8264d80747 src/org/tmatesoft/hg/internal/Internals.java --- a/src/org/tmatesoft/hg/internal/Internals.java Wed Mar 21 20:51:12 2012 +0100 +++ b/src/org/tmatesoft/hg/internal/Internals.java Thu Mar 22 18:54:11 2012 +0100 @@ -169,7 +169,7 @@ } public EncodingHelper buildFileNameEncodingHelper() { - return new EncodingHelper(getFileEncoding()); + return new EncodingHelper(getFileEncoding(), sessionContext); } private Charset getFileEncoding() { diff -r bb278ccf9866 -r ee8264d80747 src/org/tmatesoft/hg/internal/ManifestRevision.java --- a/src/org/tmatesoft/hg/internal/ManifestRevision.java Wed Mar 21 20:51:12 2012 +0100 +++ b/src/org/tmatesoft/hg/internal/ManifestRevision.java Thu Mar 22 18:54:11 2012 +0100 @@ -57,7 +57,8 @@ } public HgManifest.Flags flags(Path fname) { - return flagsMap.get(fname); + HgManifest.Flags f = flagsMap.get(fname); + return f == null ? HgManifest.Flags.RegularFile : f; } /** @@ -85,8 +86,9 @@ nid = idsPool.unify(nid); } idsMap.put(fname, nid); - if (flags != null) { - // TreeMap$Entry takes 32 bytes. No reason to keep null for such price + if (flags != HgManifest.Flags.RegularFile) { + // TreeMap$Entry takes 32 bytes. No reason to keep regular file attribute (in fact, no flags state) + // for such price // Alternatively, Map> might come as a solution // however, with low rate of elements with flags this would consume more memory // than two distinct maps (sizeof(Pair) == 16). diff -r bb278ccf9866 -r ee8264d80747 src/org/tmatesoft/hg/repo/HgDataFile.java --- a/src/org/tmatesoft/hg/repo/HgDataFile.java Wed Mar 21 20:51:12 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgDataFile.java Thu Mar 22 18:54:11 2012 +0100 @@ -528,13 +528,36 @@ throw new UnsupportedOperationException(); // XXX REVISIT, think over if Exception is good (clients would check isCopy() anyway, perhaps null is sufficient?) } + /** + * + * @return revision this file was copied from + * @throws HgInvalidControlFileException if access to revlog or file metadata failed + * @throws UnsupportedOperationException if this file doesn't represent a copy ({@link #isCopy()} was false) + */ public Nodeid getCopySourceRevision() throws HgInvalidControlFileException { if (isCopy()) { return Nodeid.fromAscii(metadata.find(0, "copyrev")); // XXX reuse/cache Nodeid } throw new UnsupportedOperationException(); } +/* FIXME + public Nodeid getRevisionAtChangeset(int changesetRevision) { + } + public HgManifest.Flags getFlagsAtChangeset(int changesetRevisionIndex) { + } +*/ + + /** + * FIXME EXCEPTIONS + * @throws HgInvalidControlFileException + * @throws HgInvalidRevisionException + */ + public HgManifest.Flags getFlags(int fileRevisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException { + int changesetRevIndex = getChangesetRevisionIndex(fileRevisionIndex); + return getRepo().getManifest().extractFlags(changesetRevIndex, getPath()); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(getClass().getSimpleName()); diff -r bb278ccf9866 -r ee8264d80747 src/org/tmatesoft/hg/repo/HgManifest.java --- a/src/org/tmatesoft/hg/repo/HgManifest.java Wed Mar 21 20:51:12 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgManifest.java Thu Mar 22 18:54:11 2012 +0100 @@ -36,6 +36,7 @@ import org.tmatesoft.hg.internal.DigestHelper; import org.tmatesoft.hg.internal.EncodingHelper; import org.tmatesoft.hg.internal.Experimental; +import org.tmatesoft.hg.internal.IntMap; import org.tmatesoft.hg.internal.IterateControlMediator; import org.tmatesoft.hg.internal.Lifecycle; import org.tmatesoft.hg.internal.Pool2; @@ -54,8 +55,22 @@ private RevisionMapper revisionMap; private EncodingHelper encodingHelper; + /** + * File flags recorded in manifest + */ public enum Flags { - Exec, Link; // FIXME REVISIT consider REGULAR instead of null + /** + * Executable bit set + */ + Exec, + /** + * Symbolic link + */ + Link, + /** + * Regular file + */ + RegularFile; static Flags parse(String flags) { if ("x".equalsIgnoreCase(flags)) { @@ -65,14 +80,14 @@ return Link; } if (flags == null) { - return null; + return RegularFile; } throw new IllegalStateException(flags); } static Flags parse(byte[] data, int start, int length) { if (length == 0) { - return null; + return RegularFile; } if (length == 1) { if (data[start] == 'x') { @@ -93,6 +108,9 @@ if (this == Link) { return "l"; } + if (this == RegularFile) { + return ""; + } throw new IllegalStateException(toString()); } } @@ -242,40 +260,31 @@ public Map getFileRevisions(final Path file, int... changelogRevisionIndexes) throws HgInvalidRevisionException, HgInvalidControlFileException { // TODO need tests int[] manifestRevisionIndexes = toManifestRevisionIndexes(changelogRevisionIndexes, null); - final HashMap rv = new HashMap(changelogRevisionIndexes.length); - content.iterate(manifestRevisionIndexes, true, new RevlogStream.Inspector() { - - public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - try { - byte b; - while (!data.isEmpty() && (b = data.readByte()) != '\n') { - if (b != 0) { - bos.write(b); - } else { - String fname = new String(bos.toByteArray()); - bos.reset(); - if (file.toString().equals(fname)) { - byte[] nid = new byte[40]; - data.readBytes(nid, 0, 40); - rv.put(linkRevision, Nodeid.fromAscii(nid, 0, 40)); - break; - } else { - data.skip(40); - } - // else skip to the end of line - while (!data.isEmpty() && (b = data.readByte()) != '\n') - ; - } - } - } catch (IOException ex) { - throw new HgException(ex); - } - } - }); + IntMap resMap = new IntMap(changelogRevisionIndexes.length); + content.iterate(manifestRevisionIndexes, true, new FileLookupInspector(encodingHelper, file, resMap, null)); + // IntMap to HashMap, + HashMap rv = new HashMap(); + resMap.fill(rv); return rv; } + /** + * {@link HgDataFile#getFlags(int)} is public API + * + * @param changesetRevIndex changeset revision index + * @param file path to look up + * @return one of predefined enum values, or null if file was not known in the specified revision + * FIXME EXCEPTIONS + * @throws HgInvalidControlFileException + * @throws HgInvalidRevisionException + */ + /*package-local*/ Flags extractFlags(int changesetRevIndex, Path file) throws HgInvalidRevisionException, HgInvalidControlFileException { + int manifestRevIdx = fromChangelog(changesetRevIndex); + IntMap resMap = new IntMap(2); + content.iterate(manifestRevIdx, manifestRevIdx, true, new FileLookupInspector(encodingHelper, file, null, resMap)); + return resMap.get(changesetRevIndex); + } + /** * @param changelogRevisionIndexes non-null @@ -329,6 +338,12 @@ @Experimental(reason="Explore Path alternative for filenames and enum for flags") public interface Inspector2 extends Inspector { + /** + * @param nid file revision + * @param fname file name + * @param flags one of {@link HgManifest.Flags} constants, not null + * @return true to continue iteration, false to stop + */ boolean next(Nodeid nid, Path fname, Flags flags); } @@ -467,11 +482,11 @@ // for cpython 0..10k, there are 4361062 flag checks, and there's only 1 unique flag flags = Flags.parse(data, x + nodeidLen, i-x-nodeidLen); } else { - flags = null; + flags = Flags.RegularFile; } boolean good2go; if (inspector2 == null) { - String flagString = flags == null ? null : flags.nativeString(); + String flagString = flags == Flags.RegularFile ? null : flags.nativeString(); good2go = inspector.next(nid, fname.toString(), flagString); } else { good2go = inspector2.next(nid, fname, flags); @@ -606,4 +621,67 @@ } } } + + /** + * Look up specified file in possibly multiple manifest revisions, collect file revision and flags. + */ + private static class FileLookupInspector implements RevlogStream.Inspector { + + private final byte[] filenameAsBytes; + private final IntMap csetIndex2FileRev; + private final IntMap csetIndex2Flags; + + public FileLookupInspector(EncodingHelper eh, Path fileToLookUp, IntMap csetIndex2FileRevMap, IntMap csetIndex2FlagsMap) { + assert fileToLookUp != null; + // need at least one map for the inspector to make any sense + assert csetIndex2FileRevMap != null || csetIndex2FlagsMap != null; + csetIndex2FileRev = csetIndex2FileRevMap; + csetIndex2Flags = csetIndex2FlagsMap; + filenameAsBytes = eh.toManifest(fileToLookUp.toString()); + } + + public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + byte b; + while (!data.isEmpty() && (b = data.readByte()) != '\n') { + if (b != 0) { + bos.write(b); + } else { + byte[] byteArray = bos.toByteArray(); + bos.reset(); + if (Arrays.equals(filenameAsBytes, byteArray)) { + if (csetIndex2FileRev != null) { + byte[] nid = new byte[40]; + data.readBytes(nid, 0, 40); + csetIndex2FileRev.put(linkRevision, Nodeid.fromAscii(nid, 0, 40)); + } else { + data.skip(40); + } + if (csetIndex2Flags != null) { + while (!data.isEmpty() && (b = data.readByte()) != '\n') { + bos.write(b); + } + Flags flags; + if (bos.size() == 0) { + flags = Flags.RegularFile; + } else { + flags = Flags.parse(bos.toByteArray(), 0, bos.size()); + } + csetIndex2Flags.put(linkRevision, flags); + } + break; + } else { + data.skip(40); + } + // else skip to the end of line + while (!data.isEmpty() && (b = data.readByte()) != '\n') + ; + } + } + } catch (IOException ex) { + throw new HgException(ex); // FIXME EXCEPTIONS + } + } + } } diff -r bb278ccf9866 -r ee8264d80747 src/org/tmatesoft/hg/repo/HgMergeState.java --- a/src/org/tmatesoft/hg/repo/HgMergeState.java Wed Mar 21 20:51:12 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgMergeState.java Thu Mar 22 18:54:11 2012 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 TMate Software Ltd + * Copyright (c) 2011-2012 TMate Software Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -127,12 +127,12 @@ Path p1fname = pathPool.path(r[3]); Nodeid nidP1 = m1.nodeid(p1fname); Nodeid nidCA = nodeidPool.unify(Nodeid.fromAscii(r[5])); - HgFileRevision p1 = new HgFileRevision(repo, nidP1, p1fname); + HgFileRevision p1 = new HgFileRevision(repo, nidP1, m1.flags(p1fname), p1fname); HgFileRevision ca; if (nidCA == nidP1 && r[3].equals(r[4])) { ca = p1; } else { - ca = new HgFileRevision(repo, nidCA, pathPool.path(r[4])); + ca = new HgFileRevision(repo, nidCA, null, pathPool.path(r[4])); } HgFileRevision p2; if (!wcp2.isNull() || !r[6].equals(r[4])) { @@ -142,7 +142,7 @@ assert false : "There's not enough information (or I don't know where to look) in merge/state to find out what's the second parent"; nidP2 = NULL; } - p2 = new HgFileRevision(repo, nidP2, p2fname); + p2 = new HgFileRevision(repo, nidP2, m2.flags(p2fname), p2fname); } else { // no second parent known. no idea what to do here, assume linear merge, use common ancestor as parent p2 = ca; diff -r bb278ccf9866 -r ee8264d80747 src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java --- a/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java Wed Mar 21 20:51:12 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java Thu Mar 22 18:54:11 2012 +0100 @@ -559,7 +559,7 @@ if ((dirstateFileMode & S_IXUSR) == S_IXUSR) { return checkFlagsEqual(f, HgManifest.Flags.Exec); } - return checkFlagsEqual(f, null); // no flags + return checkFlagsEqual(f, HgManifest.Flags.RegularFile); // no flags } /** diff -r bb278ccf9866 -r ee8264d80747 test/org/tmatesoft/hg/test/TestIntMap.java --- a/test/org/tmatesoft/hg/test/TestIntMap.java Wed Mar 21 20:51:12 2012 +0100 +++ b/test/org/tmatesoft/hg/test/TestIntMap.java Thu Mar 22 18:54:11 2012 +0100 @@ -18,6 +18,11 @@ import static org.junit.Assert.assertEquals; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + import org.junit.Test; import org.tmatesoft.hg.internal.IntMap; @@ -59,4 +64,25 @@ } assertEquals(m.size(), actualCount); } + + @Test + public void testIterators() { + IntMap m = new IntMap(20); + for (int i = 0; i <= 30; i+= 5) { + m.put(i, Boolean.TRUE); + } + HashMap hm = new HashMap(); + for (Iterator> it = m.entryIterator(); it.hasNext(); ) { + Entry next = it.next(); + hm.put(next.getKey(), next.getValue()); + } + assertEquals(m.size(), hm.size()); + for (int i = 0; i <= 30; i++) { + assertEquals(m.get(i), hm.get(i)); + } + // + HashMap hm2 = new HashMap(); + m.fill(hm2); + assertEquals(hm, hm2); + } }