# HG changeset patch # User Artem Tikhomirov # Date 1332952769 -7200 # Node ID 48f993aa2f41f769e055c46ce4404e0f50d7a4de # Parent 6437d261048a63400f4005fd419b5988cb1bd12e FIXMEs: exceptions, javadoc diff -r 6437d261048a -r 48f993aa2f41 cmdline/org/tmatesoft/hg/console/Main.java --- a/cmdline/org/tmatesoft/hg/console/Main.java Wed Mar 28 15:42:15 2012 +0200 +++ b/cmdline/org/tmatesoft/hg/console/Main.java Wed Mar 28 18:39:29 2012 +0200 @@ -38,6 +38,7 @@ import org.tmatesoft.hg.core.HgLogCommand; import org.tmatesoft.hg.core.HgManifestCommand; import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.internal.BasicSessionContext; import org.tmatesoft.hg.internal.ByteArrayChannel; import org.tmatesoft.hg.internal.DigestHelper; import org.tmatesoft.hg.internal.PathGlobMatcher; @@ -67,6 +68,7 @@ import org.tmatesoft.hg.util.Pair; import org.tmatesoft.hg.util.Path; import org.tmatesoft.hg.util.PathRewrite; +import org.tmatesoft.hg.util.ProgressSupport; /** * Various debug dumps. @@ -92,7 +94,8 @@ public static void main(String[] args) throws Exception { Main m = new Main(args); - m.checkFileFlags(); + m.checkSubProgress(); +// m.checkFileFlags(); // m.buildFileLog(); // m.testConsoleLog(); // m.testTreeTraversal(); @@ -114,6 +117,41 @@ // m.dumpCompleteManifestHigh(); // m.bunchOfTests(); } + + // no repo + private void checkSubProgress() { + ProgressSupport ps = new ProgressSupport() { + private int units; + + public void start(int totalUnits) { + units = totalUnits; + System.out.printf("%d:", totalUnits); + + } + public void worked(int wu) { + for (int i = 0; i < wu; i++) { + System.out.print(units-- == 0 ? '!' : '.'); + } + } + public void done() { + System.out.println("DONE"); + } + }; + ps.start(10); + ProgressSupport.Sub s1 = new ProgressSupport.Sub(ps, 3); + ProgressSupport.Sub s2 = new ProgressSupport.Sub(ps, 7); + s1.start(10); + s1.worked(1); + s1.worked(2); + s1.worked(3); + s1.worked(4); + s1.done(); + // + s2.start(5); + s2.worked(3); + s2.worked(2); + s2.done(); + } private void checkFileFlags() throws Exception { // ~/hg/test-flags repo @@ -218,7 +256,7 @@ private void testTreeTraversal() throws Exception { File repoRoot = hgRepo.getWorkingDir(); Path.Source pathSrc = new Path.SimpleSource(new PathRewrite.Composite(new RelativePathRewrite(repoRoot), hgRepo.getToRepoPathHelper())); - FileWalker w = new FileWalker(repoRoot, pathSrc); + FileWalker w = new FileWalker(new BasicSessionContext(null, null), repoRoot, pathSrc); int count = 0; final long start = System.currentTimeMillis(); while (w.hasNext()) { diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/core/HgException.java --- a/src/org/tmatesoft/hg/core/HgException.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgException.java Wed Mar 28 18:39:29 2012 +0200 @@ -21,7 +21,7 @@ import org.tmatesoft.hg.util.Path; /** - * Root class for all hg4j exceptions. + * Root class for all Hg4J checked exceptions. * * @author Artem Tikhomirov * @author TMate Software Ltd. diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/internal/DataAccess.java --- a/src/org/tmatesoft/hg/internal/DataAccess.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/internal/DataAccess.java Wed Mar 28 18:39:29 2012 +0200 @@ -121,7 +121,7 @@ } // XXX decide whether may or may not change position in the DataAccess - // FIXME exception handling is not right, just for the sake of quick test + // TODO REVISIT exception handling may not be right, initially just for the sake of quick test public byte[] byteArray() throws IOException { reset(); byte[] rv = new byte[length()]; diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/internal/DataAccessProvider.java --- a/src/org/tmatesoft/hg/internal/DataAccessProvider.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/internal/DataAccessProvider.java Wed Mar 28 18:39:29 2012 +0200 @@ -24,6 +24,7 @@ import java.nio.channels.FileChannel; import org.tmatesoft.hg.core.SessionContext; +import org.tmatesoft.hg.util.LogFacility; /** * @@ -39,13 +40,17 @@ public static final String CFG_PROPERTY_MAPIO_LIMIT = "hg4j.dap.mapio_limit"; public static final String CFG_PROPERTY_MAPIO_BUFFER_SIZE = "hg4j.dap.mapio_buffer"; public static final String CFG_PROPERTY_FILE_BUFFER_SIZE = "hg4j.dap.file_buffer"; + + private static final int DEFAULT_MAPIO_LIMIT = 100 * 1024; // 100 kB + private static final int DEFAULT_FILE_BUFFER = 8 * 1024; // 8 kB + private static final int DEFAULT_MAPIO_BUFFER = DEFAULT_MAPIO_LIMIT; // same as default boundary private final int mapioMagicBoundary; private final int bufferSize; private final SessionContext context; public DataAccessProvider(SessionContext ctx) { - this(ctx, getConfigOption(ctx, CFG_PROPERTY_MAPIO_LIMIT, 100 * 1024), getConfigOption(ctx, CFG_PROPERTY_FILE_BUFFER_SIZE, 8 * 1024)); + this(ctx, getConfigOption(ctx, CFG_PROPERTY_MAPIO_LIMIT, DEFAULT_MAPIO_LIMIT), getConfigOption(ctx, CFG_PROPERTY_FILE_BUFFER_SIZE, DEFAULT_FILE_BUFFER)); } private static int getConfigOption(SessionContext ctx, String optName, int defaultValue) { @@ -71,14 +76,15 @@ long flen = fc.size(); if (flen > mapioMagicBoundary) { // TESTS: bufLen of 1024 was used to test MemMapFileAccess - return new MemoryMapFileAccess(fc, flen, getConfigOption(context, CFG_PROPERTY_MAPIO_BUFFER_SIZE, 100*1024 /*same as default boundary*/)); + int mapioBufSize = getConfigOption(context, CFG_PROPERTY_MAPIO_BUFFER_SIZE, DEFAULT_MAPIO_BUFFER); + return new MemoryMapFileAccess(fc, flen, mapioBufSize, context.getLog()); } else { // XXX once implementation is more or less stable, // may want to try ByteBuffer.allocateDirect() to see // if there's any performance gain. boolean useDirectBuffer = false; // XXX might be another config option // TESTS: bufferSize of 100 was used to check buffer underflow states when readBytes reads chunks bigger than bufSize - return new FileAccess(fc, flen, bufferSize, useDirectBuffer); + return new FileAccess(fc, flen, bufferSize, useDirectBuffer, context.getLog()); } } catch (IOException ex) { // unlikely to happen, we've made sure file exists. @@ -89,14 +95,16 @@ private static class MemoryMapFileAccess extends DataAccess { private FileChannel fileChannel; + private long position = 0; // always points to buffer's absolute position in the file + private MappedByteBuffer buffer; private final long size; - private long position = 0; // always points to buffer's absolute position in the file private final int memBufferSize; - private MappedByteBuffer buffer; + private final LogFacility logFacility; - public MemoryMapFileAccess(FileChannel fc, long channelSize, int bufferSize) { + public MemoryMapFileAccess(FileChannel fc, long channelSize, int bufferSize, LogFacility log) { fileChannel = fc; size = channelSize; + logFacility = log; memBufferSize = bufferSize > channelSize ? (int) channelSize : bufferSize; // no reason to waste memory more than there's data } @@ -202,7 +210,7 @@ try { fileChannel.close(); } catch (IOException ex) { - StreamLogFacility.newDefault().debug(getClass(), ex, null); + logFacility.debug(getClass(), ex, null); } fileChannel = null; } @@ -212,13 +220,15 @@ // (almost) regular file access - FileChannel and buffers. private static class FileAccess extends DataAccess { private FileChannel fileChannel; - private final long size; private ByteBuffer buffer; private long bufferStartInFile = 0; // offset of this.buffer in the file. + private final long size; + private final LogFacility logFacility; - public FileAccess(FileChannel fc, long channelSize, int bufferSizeHint, boolean useDirect) { + public FileAccess(FileChannel fc, long channelSize, int bufferSizeHint, boolean useDirect, LogFacility log) { fileChannel = fc; size = channelSize; + logFacility = log; final int capacity = size < bufferSizeHint ? (int) size : bufferSizeHint; buffer = useDirect ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity); buffer.flip(); // or .limit(0) to indicate it's empty @@ -334,8 +344,7 @@ try { fileChannel.close(); } catch (IOException ex) { - // FIXME/TODO log facility can be obtained from session context - StreamLogFacility.newDefault().debug(getClass(), ex, null); + logFacility.debug(getClass(), ex, null); } fileChannel = null; } diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/internal/Internals.java --- a/src/org/tmatesoft/hg/internal/Internals.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/internal/Internals.java Wed Mar 28 18:39:29 2012 +0200 @@ -32,6 +32,7 @@ import org.tmatesoft.hg.core.SessionContext; import org.tmatesoft.hg.repo.HgInternals; +import org.tmatesoft.hg.repo.HgInvalidControlFileException; import org.tmatesoft.hg.repo.HgRepoConfig.ExtensionsSection; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.util.PathRewrite; @@ -80,18 +81,17 @@ private final boolean shallCacheRevlogsInRepo; public Internals(SessionContext ctx) { - this.sessionContext = ctx; + sessionContext = ctx; isCaseSensitiveFileSystem = !runningOnWindows(); Object p = ctx.getProperty(CFG_PROPERTY_REVLOG_STREAM_CACHE, true); shallCacheRevlogsInRepo = p instanceof Boolean ? ((Boolean) p).booleanValue() : Boolean.parseBoolean(String.valueOf(p)); } - public void parseRequires(HgRepository hgRepo, File requiresFile) { + public void parseRequires(HgRepository hgRepo, File requiresFile) throws HgInvalidControlFileException { try { new RequiresFile().parse(this, requiresFile); } catch (IOException ex) { - // FIXME EXCEPTIONS not quite sure error reading requires file shall be silently logged only. - HgInternals.getContext(hgRepo).getLog().error(getClass(), ex, null); + throw new HgInvalidControlFileException("Parse failed", ex, requiresFile); } } diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/internal/RepositoryComparator.java --- a/src/org/tmatesoft/hg/internal/RepositoryComparator.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/internal/RepositoryComparator.java Wed Mar 28 18:39:29 2012 +0200 @@ -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 @@ -487,7 +487,7 @@ toQuery.clear(); } if (rootIndex == -1) { - throw new HgInvalidStateException("Shall not happen, provided between output is correct"); // FIXME EXCEPTIONS + throw new HgInvalidStateException("Shall not happen, provided between output is correct"); } result[rootIndex] = branchRoot; boolean resultOk = true; diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/internal/RevlogStream.java --- a/src/org/tmatesoft/hg/internal/RevlogStream.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/internal/RevlogStream.java Wed Mar 28 18:39:29 2012 +0200 @@ -305,7 +305,7 @@ return revisionIndex; } - private void initOutline() { + private void initOutline() throws HgInvalidControlFileException { if (baseRevisions != null && baseRevisions.length > 0) { return; } diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/internal/StoragePathHelper.java --- a/src/org/tmatesoft/hg/internal/StoragePathHelper.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/internal/StoragePathHelper.java Wed Mar 28 18:39:29 2012 +0200 @@ -56,7 +56,7 @@ fncache = isFncache; dotencode = isDotencode; suffix2replace = Pattern.compile("\\.([id]|hg)/"); - csEncoder = fsEncoding.newEncoder(); // FIXME catch exception and rethrow as our's RT + csEncoder = fsEncoding.newEncoder(); byteEncodingBuf = ByteBuffer.allocate(Math.round(csEncoder.maxBytesPerChar()) + 1/*in fact, need ceil, hence +1*/); charEncodingBuf = CharBuffer.allocate(1); } diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/repo/HgDataFile.java --- a/src/org/tmatesoft/hg/repo/HgDataFile.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgDataFile.java Wed Mar 28 18:39:29 2012 +0200 @@ -29,7 +29,7 @@ import java.util.Arrays; import java.util.Collection; -import org.tmatesoft.hg.core.HgException; +import org.tmatesoft.hg.core.HgChangesetFileSneaker; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.DataAccess; import org.tmatesoft.hg.internal.FilterByteChannel; @@ -48,7 +48,12 @@ /** - * ? name:HgFileNode? + * Regular user data file stored in the repository. + * + *

Note, most methods accept index in the file's revision history, not that of changelog. Easy way to obtain + * changeset revision index from file's is to use {@link #getChangesetRevisionIndex(int)}. To obtain file's revision + * index for a given changeset, {@link HgManifest#getFileRevision(int, Path)} or {@link HgChangesetFileSneaker} may + * come handy. * * @author Artem Tikhomirov * @author TMate Software Ltd. @@ -88,10 +93,9 @@ * @param nodeid revision of the file * * @return size of the file content at the given revision - * @throws HgInvalidRevisionException if supplied nodeid doesn't identify any revision from this revlog (runtime exception) - * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public int getLength(Nodeid nodeid) throws HgInvalidControlFileException, HgInvalidRevisionException { + public int getLength(Nodeid nodeid) throws HgRuntimeException { try { return getLength(getRevisionIndex(nodeid)); } catch (HgInvalidControlFileException ex) { @@ -104,10 +108,9 @@ /** * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, only {@link HgRepository#TIP} makes sense. * @return size of the file content at the revision identified by local revision number. - * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (runtime exception) - * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public int getLength(int fileRevisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException { + public int getLength(int fileRevisionIndex) throws HgRuntimeException { if (wrongRevisionIndex(fileRevisionIndex) || fileRevisionIndex == BAD_REVISION) { throw new HgInvalidRevisionException(fileRevisionIndex); } @@ -145,11 +148,10 @@ * e.g. from another branch), no content would be supplied. * * @param sink content consumer - * @throws HgInvalidControlFileException if access to revlog index/data entry failed - * @throws HgInvalidFileException if access to file in working directory failed * @throws CancelledException if execution of the operation was cancelled + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public void workingCopy(ByteChannel sink) throws HgException, CancelledException { + public void workingCopy(ByteChannel sink) throws CancelledException, HgRuntimeException { File f = getRepo().getFile(this); if (f.exists()) { final CancelSupport cs = CancelSupport.Factory.get(sink); @@ -234,12 +236,10 @@ * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, {@link HgRepository#TIP} and {@link HgRepository#WORKING_COPY} make sense. * @param sink content consumer * - * @throws HgInvalidControlFileException if access to revlog index/data entry failed - * @throws HgInvalidFileException if access to file in working directory failed * @throws CancelledException if execution of the operation was cancelled - * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (runtime exception) + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public void contentWithFilters(int fileRevisionIndex, ByteChannel sink) throws HgException, CancelledException, HgInvalidRevisionException { + public void contentWithFilters(int fileRevisionIndex, ByteChannel sink) throws CancelledException, HgRuntimeException { if (fileRevisionIndex == WORKING_COPY) { workingCopy(sink); // pass un-mangled sink } else { @@ -254,12 +254,10 @@ * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, {@link HgRepository#TIP} and {@link HgRepository#WORKING_COPY} make sense. * @param sink content consumer * - * @throws HgInvalidControlFileException if access to revlog index/data entry failed - * @throws HgInvalidFileException if access to file in working directory failed * @throws CancelledException if execution of the operation was cancelled - * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (runtime exception) + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public void content(int fileRevisionIndex, ByteChannel sink) throws HgException, CancelledException, HgInvalidRevisionException { + public void content(int fileRevisionIndex, ByteChannel sink) throws CancelledException, HgRuntimeException { // for data files need to check heading of the file content for possible metadata // @see http://mercurial.selenic.com/wiki/FileFormats#data.2BAC8- if (fileRevisionIndex == TIP) { @@ -293,35 +291,33 @@ insp.checkCancelled(); super.content.iterate(fileRevisionIndex, fileRevisionIndex, true, insp); try { - insp.checkFailed(); // XXX is there real need to throw IOException from ContentPipe? + insp.checkFailed(); } catch (HgInvalidControlFileException ex) { ex = ex.setFileName(getPath()); throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(fileRevisionIndex); } catch (IOException ex) { HgInvalidControlFileException e = new HgInvalidControlFileException("Revision content access failed", ex, null); throw content.initWithIndexFile(e).setFileName(getPath()).setRevisionIndex(fileRevisionIndex); - } catch (HgException ex) { - // shall not happen, unless we changed ContentPipe or its subclass - HgInvalidControlFileException e = new HgInvalidControlFileException("Revision content access failed", ex, null); - throw content.initWithIndexFile(e).setFileName(getPath()).setRevisionIndex(fileRevisionIndex); } } - - // FIXME is that still needed? - public void history(HgChangelog.Inspector inspector) throws HgInvalidControlFileException { + + /** + * Walk complete change history of the file. + * @param inspector callback to visit changesets + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception + */ + public void history(HgChangelog.Inspector inspector) throws HgRuntimeException { history(0, getLastRevision(), inspector); } /** - * - * @param start local revision index - * @param end local revision index - * @param inspector - * FIXME EXCEPTIONS - * @throws HgInvalidRevisionException - * @throws HgInvalidControlFileException + * Walk subset of the file's change history. + * @param start revision local index, inclusive; non-negative or {@link HgRepository#TIP} + * @param end revision local index, inclusive; non-negative or {@link HgRepository#TIP} + * @param inspector callback to visit changesets + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public void history(int start, int end, HgChangelog.Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException { + public void history(int start, int end, HgChangelog.Inspector inspector) throws HgRuntimeException { if (!exists()) { throw new IllegalStateException("Can't get history of invalid repository file node"); } @@ -361,10 +357,9 @@ * For a given revision of the file (identified with revision index), find out index of the corresponding changeset. * * @return changeset revision index - * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog - * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public int getChangesetRevisionIndex(int revision) throws HgInvalidControlFileException, HgInvalidRevisionException { + public int getChangesetRevisionIndex(int revision) throws HgRuntimeException { return content.linkRevision(revision); } @@ -373,10 +368,9 @@ * * @param nid revision of the file * @return changeset revision - * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog - * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public Nodeid getChangesetRevision(Nodeid nid) throws HgInvalidControlFileException, HgInvalidRevisionException { + public Nodeid getChangesetRevision(Nodeid nid) throws HgRuntimeException { int changelogRevision = getChangesetRevisionIndex(getRevisionIndex(nid)); return getRepo().getChangelog().getRevision(changelogRevision); } @@ -384,9 +378,9 @@ /** * Tells whether this file originates from another repository file * @return true if this file is a copy of another from the repository - * @throws HgInvalidControlFileException if access to revlog or file metadata failed + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public boolean isCopy() throws HgInvalidControlFileException { + public boolean isCopy() throws HgRuntimeException { if (metadata == null || !metadata.checked(0)) { checkAndRecordMetadata(0); } @@ -400,10 +394,9 @@ * Get name of the file this one was copied from. * * @return name of the file origin - * @throws HgInvalidControlFileException if access to revlog or file metadata failed - * @throws UnsupportedOperationException if this file doesn't represent a copy ({@link #isCopy()} was false) + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public Path getCopySourceName() throws HgInvalidControlFileException { + public Path getCopySourceName() throws HgRuntimeException { if (isCopy()) { return Path.create(metadata.find(0, "copy")); } @@ -413,10 +406,9 @@ /** * * @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) + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public Nodeid getCopySourceRevision() throws HgInvalidControlFileException { + public Nodeid getCopySourceRevision() throws HgRuntimeException { if (isCopy()) { return Nodeid.fromAscii(metadata.find(0, "copyrev")); // XXX reuse/cache Nodeid } @@ -427,11 +419,9 @@ * Get file flags recorded in the manifest * @param fileRevisionIndex - revision local index, non-negative, or {@link HgRepository#TIP}. * @see HgManifest#getFileFlags(int, Path) - * FIXME EXCEPTIONS - * @throws HgInvalidControlFileException - * @throws HgInvalidRevisionException + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public HgManifest.Flags getFlags(int fileRevisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException { + public HgManifest.Flags getFlags(int fileRevisionIndex) throws HgRuntimeException { int changesetRevIndex = getChangesetRevisionIndex(fileRevisionIndex); return getRepo().getManifest().getFileFlags(changesetRevIndex, getPath()); } @@ -462,9 +452,6 @@ // it's ok, we did that } catch (HgInvalidControlFileException ex) { throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(localRev); - } catch (HgException ex) { - // metadata comes from the content, hence initWithDataFile - throw content.initWithDataFile(new HgInvalidControlFileException(null, ex, null)); } } @@ -654,10 +641,10 @@ } @Override - public void checkFailed() throws HgException, IOException, CancelledException { + public void checkFailed() throws HgRuntimeException, IOException, CancelledException { super.checkFailed(); if (delegate instanceof ErrorHandlingInspector) { - // XXX need to add ErrorDestination and pass it around (much like CancelSupport get passed) + // TODO need to add ErrorDestination (ErrorTarget/Acceptor?) and pass it around (much like CancelSupport get passed) // so that delegate would be able report its failures directly to caller without this hack ((ErrorHandlingInspector) delegate).checkFailed(); } diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/repo/HgInternals.java --- a/src/org/tmatesoft/hg/repo/HgInternals.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgInternals.java Wed Mar 28 18:39:29 2012 +0200 @@ -139,7 +139,7 @@ // Impl note: simple source is enough as files in the working dir are all unique // even if they might get reused (i.e. after FileIterator#reset() and walking once again), // path caching is better to be done in the code which knows that path are being reused - return new FileWalker(repoRoot, pathSrc, workindDirScope); + return new FileWalker(repo.getContext(), repoRoot, pathSrc, workindDirScope); } // expose otherwise package-local information primarily to use in our own o.t.hg.core package @@ -149,8 +149,8 @@ // Convenient check of revision index for validity (not all negative values are wrong as long as we use negative constants) - public static boolean wrongRevisionIndex(int rev) { // FIXME guess, usages shall throw HgInvalidRevision. \ - // TODO Another method to check,throw and expand TIP at once + public static boolean wrongRevisionIndex(int rev) { + // TODO Another method to check,throw and expand TIP at once (check[Revision|Revlog]Index() return rev < 0 && rev != TIP && rev != WORKING_COPY && rev != BAD_REVISION && rev != NO_REVISION; } diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/repo/HgLookup.java --- a/src/org/tmatesoft/hg/repo/HgLookup.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgLookup.java Wed Mar 28 18:39:29 2012 +0200 @@ -80,9 +80,9 @@ } } - public HgBundle loadBundle(File location) throws HgRuntimeException/*FIXME need checked exception for can't find*/ { + public HgBundle loadBundle(File location) throws HgRepositoryNotFoundException { if (location == null || !location.canRead()) { - throw new HgInvalidFileException(String.format("Can't read file %s", location == null ? null : location.getPath()), null, location); + throw new HgRepositoryNotFoundException(String.format("Can't read file %s", location)).setLocation(String.valueOf(location)); } return new HgBundle(getContext(), new DataAccessProvider(getContext()), location).link(); } diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/repo/HgManifest.java --- a/src/org/tmatesoft/hg/repo/HgManifest.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgManifest.java Wed Mar 28 18:39:29 2012 +0200 @@ -44,7 +44,9 @@ /** - * + * Representation of Mercurial manifest file (list of file names and their revisions in a particular changeset) + * + * @see http://mercurial.selenic.com/wiki/Manifest * @author Artem Tikhomirov * @author TMate Software Ltd. */ @@ -222,15 +224,14 @@ * if changeset has no associated manifest (cset records NULL nodeid for manifest). * @return manifest revision index, non-negative, or {@link HgRepository#BAD_REVISION}. * @throws HgInvalidRevisionException if method argument specifies non-existent revision index - * @throws IllegalArgumentException if argument is not a revision index * @throws HgInvalidControlFileException if access to revlog index/data entry failed */ /*package-local*/ int fromChangelog(int changesetRevisionIndex) throws HgInvalidRevisionException, HgInvalidControlFileException { if (HgInternals.wrongRevisionIndex(changesetRevisionIndex)) { - throw new IllegalArgumentException(String.valueOf(changesetRevisionIndex)); + throw new HgInvalidRevisionException(changesetRevisionIndex); } if (changesetRevisionIndex == HgRepository.WORKING_COPY || changesetRevisionIndex == HgRepository.BAD_REVISION) { - throw new IllegalArgumentException("Can't use constants like WORKING_COPY or BAD_REVISION"); + throw new HgInvalidRevisionException("Can't use constants like WORKING_COPY or BAD_REVISION", null, changesetRevisionIndex); } // revisionNumber == TIP is processed by RevisionMapper if (revisionMap == null) { @@ -248,8 +249,7 @@ * @param changelogRevisionIndex local changeset index * @param file path to file in question * @return file revision or null if manifest at specified revision doesn't list such file - * @throws HgInvalidRevisionException if method argument specifies non-existent revision index - * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ public Nodeid getFileRevision(int changelogRevisionIndex, final Path file) throws HgInvalidRevisionException, HgInvalidControlFileException { // there's no need for HgDataFile to own this method, or get a delegate @@ -281,9 +281,7 @@ * @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 + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ public Flags getFileFlags(int changesetRevIndex, Path file) throws HgInvalidRevisionException, HgInvalidControlFileException { int manifestRevIdx = fromChangelog(changesetRevIndex); diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/repo/HgRemoteRepository.java --- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java Wed Mar 28 18:39:29 2012 +0200 @@ -48,6 +48,7 @@ import org.tmatesoft.hg.core.HgBadArgumentException; import org.tmatesoft.hg.core.HgRemoteConnectionException; +import org.tmatesoft.hg.core.HgRepositoryNotFoundException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.core.SessionContext; @@ -326,7 +327,7 @@ * (there's no header like HG10?? with the server output, though, * as one may expect according to http://mercurial.selenic.com/wiki/BundleFormat) */ - public HgBundle getChanges(List roots) throws HgRemoteConnectionException, HgInvalidFileException { + public HgBundle getChanges(List roots) throws HgRemoteConnectionException, HgRuntimeException { List _roots = roots.isEmpty() ? Collections.singletonList(Nodeid.NULL) : roots; StringBuilder sb = new StringBuilder(20 + _roots.size() * 41); sb.append("roots="); @@ -355,6 +356,8 @@ throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("changegroup").setServerInfo(getLocation()); } catch (IOException ex) { throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getLocation()); + } catch (HgRepositoryNotFoundException ex) { + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getLocation()); } } diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/repo/HgRepository.java --- a/src/org/tmatesoft/hg/repo/HgRepository.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRepository.java Wed Mar 28 18:39:29 2012 +0200 @@ -25,7 +25,6 @@ import java.util.HashMap; import java.util.List; -import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.core.SessionContext; import org.tmatesoft.hg.internal.ByteArrayChannel; @@ -128,7 +127,10 @@ impl = null; } - HgRepository(SessionContext ctx, String repositoryPath, File repositoryRoot) { + /** + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception + */ + HgRepository(SessionContext ctx, String repositoryPath, File repositoryRoot) throws HgRuntimeException { assert ".hg".equals(repositoryRoot.getName()) && repositoryRoot.isDirectory(); assert repositoryPath != null; assert repositoryRoot != null; @@ -193,9 +195,6 @@ } catch (CancelledException ex) { // IGNORE, can't happen, we did not configure cancellation getContext().getLog().debug(getClass(), ex, null); - } catch (HgException ex) { - getContext().getLog().error(getClass(), ex, null); - // FIXME EXCEPTIONS need to react } catch (IOException ex) { // UnsupportedEncodingException can't happen (UTF8) // only from readGlobal. Need to reconsider exceptions thrown from there: diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/repo/HgRuntimeException.java --- a/src/org/tmatesoft/hg/repo/HgRuntimeException.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRuntimeException.java Wed Mar 28 18:39:29 2012 +0200 @@ -16,19 +16,22 @@ */ package org.tmatesoft.hg.repo; +import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.ExceptionInfo; import org.tmatesoft.hg.util.Path; /** - * Almost any method in Hg4J low-level API may throw subclass of this exception to indicate unexpected - * state/condition encountered, flawed data or IO error. Since most cases can't be handled in a reasonable - * manner (other than catch all exceptions and tell client something went wrong), and propagating all possible - * exceptions up through API is dubious task, low-level exceptions are made runtime, rooting at this single class. + * Almost any method in Hg4J low-level API (@link org.tmatesoft.hg.repo} may throw subclass of this exception + * to indicate unexpected state/condition encountered, flawed data or IO error. + * Since most cases can't be handled in a reasonable manner (other than catch all exceptions and tell client + * something went wrong), and propagating all possible exceptions up through API is dubious task, low-level + * exceptions are made runtime, rooting at this single class. * *

Hi-level api, {@link org.tmatesoft.hg.core}, where interaction with user-supplied values is more explicit, * may follow different exception strategy. * + * @see HgException * @author Artem Tikhomirov * @author TMate Software Ltd. */ diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/repo/HgStatusCollector.java --- a/src/org/tmatesoft/hg/repo/HgStatusCollector.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgStatusCollector.java Wed Mar 28 18:39:29 2012 +0200 @@ -26,7 +26,6 @@ import java.util.Map; import java.util.TreeSet; -import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.IntMap; import org.tmatesoft.hg.internal.ManifestRevision; @@ -298,7 +297,7 @@ } else { inspector.added(copyTarget); } - } catch (HgException ex) { + } catch (HgInvalidFileException ex) { // record exception to a mediator and continue, // for a single file not to be irresolvable obstacle for a status operation inspector.invalid(r2fname, ex); @@ -335,11 +334,11 @@ return rv; } - /*package-local*/static Path getOriginIfCopy(HgRepository hgRepo, Path fname, Collection originals, int originalChangelogRevision) throws HgException { + /*package-local*/static Path getOriginIfCopy(HgRepository hgRepo, Path fname, Collection originals, int originalChangelogRevision) throws HgInvalidFileException { HgDataFile df = hgRepo.getFileNode(fname); if (!df.exists()) { String msg = String.format("Didn't find file '%s' in the repo. Perhaps, bad storage name conversion?", fname); - throw new HgException(msg).setFileName(fname).setRevisionIndex(originalChangelogRevision); + throw new HgInvalidFileException(msg, null).setFileName(fname).setRevisionIndex(originalChangelogRevision); } while (df.isCopy()) { Path original = df.getCopySourceName(); diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java --- a/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java Wed Mar 28 18:39:29 2012 +0200 @@ -30,8 +30,8 @@ import java.util.Set; import java.util.TreeSet; -import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.core.SessionContext; import org.tmatesoft.hg.internal.ByteArrayChannel; import org.tmatesoft.hg.internal.Experimental; import org.tmatesoft.hg.internal.FilterByteChannel; @@ -162,7 +162,7 @@ */ public void walk(int baseRevision, HgStatusInspector inspector) throws IOException, CancelledException, HgRuntimeException { if (HgInternals.wrongRevisionIndex(baseRevision) || baseRevision == BAD_REVISION) { - throw new IllegalArgumentException(String.valueOf(baseRevision)); + throw new HgInvalidRevisionException(baseRevision); } if (getDirstateImpl() == null) { getDirstate(); @@ -346,7 +346,7 @@ inspector.clean(df.getPath()); } } - } catch (HgException ex) { + } catch (HgRuntimeException ex) { repo.getContext().getLog().warn(getClass(), ex, null); inspector.invalid(fname, ex); } @@ -381,7 +381,7 @@ inspector.copied(getPathPool().path(origin), fname); return; } - } catch (HgException ex) { + } catch (HgInvalidFileException ex) { // report failure and continue status collection inspector.invalid(fname, ex); } @@ -435,7 +435,7 @@ } else { inspector.modified(fname); } - } catch (HgException ex) { + } catch (HgRuntimeException ex) { repo.getContext().getLog().warn(getClass(), ex, null); inspector.invalid(fname, ex); } @@ -456,7 +456,7 @@ // The question is whether original Hg treats this case (same content, different parents and hence nodeids) as 'modified' or 'clean' } - private boolean areTheSame(FileInfo f, HgDataFile dataFile, Nodeid revision) throws HgException, HgInvalidFileException { + private boolean areTheSame(FileInfo f, HgDataFile dataFile, Nodeid revision) throws HgInvalidFileException { // XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison ByteArrayChannel bac = new ByteArrayChannel(); try { @@ -611,7 +611,7 @@ // final Path[] dirs = f.toArray(new Path[d.size()]); if (d.isEmpty()) { final Path[] files = f.toArray(new Path[f.size()]); - FileIterator fi = new FileListIterator(hgRepo.getWorkingDir(), files); + FileIterator fi = new FileListIterator(hgRepo.getContext(), hgRepo.getWorkingDir(), files); return new HgWorkingCopyStatusCollector(hgRepo, fi); } // @@ -648,8 +648,10 @@ private int index; private RegularFileInfo nextFile; private final boolean execCap, linkCap; + private final SessionContext sessionContext; - public FileListIterator(File startDir, Path... files) { + public FileListIterator(SessionContext ctx, File startDir, Path... files) { + sessionContext = ctx; dir = startDir; paths = files; reset(); @@ -659,7 +661,7 @@ public void reset() { index = -1; - nextFile = new RegularFileInfo(execCap, linkCap); + nextFile = new RegularFileInfo(sessionContext, execCap, linkCap); } public boolean hasNext() { diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/repo/Revlog.java --- a/src/org/tmatesoft/hg/repo/Revlog.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/Revlog.java Wed Mar 28 18:39:29 2012 +0200 @@ -16,9 +16,7 @@ */ package org.tmatesoft.hg.repo; -import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION; -import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; -import static org.tmatesoft.hg.repo.HgRepository.TIP; +import static org.tmatesoft.hg.repo.HgRepository.*; import java.io.IOException; import java.nio.ByteBuffer; @@ -29,7 +27,6 @@ import java.util.LinkedList; import java.util.List; -import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.ArrayHelper; import org.tmatesoft.hg.internal.DataAccess; @@ -80,11 +77,19 @@ return repo; } - public final int getRevisionCount() { + /** + * @return total number of revisions kept in this revlog + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception + */ + public final int getRevisionCount() throws HgRuntimeException { return content.revisionCount(); } - public final int getLastRevision() { + /** + * @return index of last known revision, a.k.a. {@link HgRepository#TIP} + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception + */ + public final int getLastRevision() throws HgRuntimeException { return content.revisionCount() - 1; } @@ -94,18 +99,22 @@ * @param revision index of the entry in this revlog, may be {@link HgRepository#TIP} * @return revision nodeid of the entry * - * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog - * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public final Nodeid getRevision(int revision) throws HgInvalidRevisionException, HgInvalidControlFileException { + public final Nodeid getRevision(int revision) throws HgRuntimeException { // XXX cache nodeids? Rather, if context.getCache(this).getRevisionMap(create == false) != null, use it return Nodeid.fromBinary(content.nodeid(revision), 0); } /** - * FIXME need to be careful about (1) ordering of the revisions in the return list; (2) modifications (sorting) of the argument array + * Effective alternative to map few revision indexes to corresponding nodeids at once. + *

Note, there are few aspects to be careful about when using this method

+ * @return list of mapped revisions in no particular order + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public final List getRevisions(int... revisions) throws HgInvalidRevisionException, HgInvalidControlFileException { + public final List getRevisions(int... revisions) throws HgRuntimeException { ArrayList rv = new ArrayList(revisions.length); Arrays.sort(revisions); getRevisionsInternal(rv, revisions); @@ -132,10 +141,9 @@ * * @param nid revision to look up * @return revision local index in this revlog - * @throws HgInvalidRevisionException if supplied nodeid doesn't identify any revision from this revlog - * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public final int getRevisionIndex(Nodeid nid) throws HgInvalidControlFileException, HgInvalidRevisionException { + public final int getRevisionIndex(Nodeid nid) throws HgRuntimeException { int revision = content.findRevisionIndex(nid); if (revision == BAD_REVISION) { // using toString() to identify revlog. HgDataFile.toString includes path, HgManifest and HgChangelog instances @@ -151,9 +159,9 @@ * * @param nodeid * @return true if revision is part of this revlog - * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public final boolean isKnown(Nodeid nodeid) throws HgInvalidControlFileException { + public final boolean isKnown(Nodeid nodeid) throws HgRuntimeException { final int rn = content.findRevisionIndex(nodeid); if (BAD_REVISION == rn) { return false; @@ -208,11 +216,7 @@ // Do the same (add file name) below throw e; } catch (HgInvalidControlFileException ex) { - throw ex; - } catch (HgException ex) { - HgInvalidControlFileException e = new HgInvalidControlFileException(ex.getClass().getSimpleName(), ex, null); - e.setRevisionIndex(revisionIndex); - throw e; + throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(revisionIndex); } } @@ -223,11 +227,10 @@ * @param parentRevisions - int[2] to get local revision numbers of parents (e.g. {6, -1}), {@link HgRepository#NO_REVISION} indicates parent not set * @param parent1 - byte[20] or null, if parent's nodeid is not needed * @param parent2 - byte[20] or null, if second parent's nodeid is not needed - * @throws HgInvalidRevisionException - * @throws HgInvalidControlFileException FIXME EXCEPTIONS - * @throws IllegalArgumentException + * @throws IllegalArgumentException if passed arrays can't fit requested data + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public void parents(int revision, int[] parentRevisions, byte[] parent1, byte[] parent2) throws HgInvalidRevisionException, HgInvalidControlFileException { + public void parents(int revision, int[] parentRevisions, byte[] parent1, byte[] parent2) throws HgRuntimeException, IllegalArgumentException { if (revision != TIP && !(revision >= 0 && revision < content.revisionCount())) { throw new HgInvalidRevisionException(revision); } @@ -275,9 +278,19 @@ } } } - + + /** + * EXPERIMENTAL CODE, DO NOT USE + * + * Alternative revlog iteration + * + * @param start + * @param end + * @param inspector + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception + */ @Experimental - public void walk(int start, int end, final Revlog.Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException { + public void walk(int start, int end, final Revlog.Inspector inspector) throws HgRuntimeException { int lastRev = getLastRevision(); if (start == TIP) { start = lastRev; @@ -325,7 +338,7 @@ } /* - * XXX think over if it's better to do either: + * FIXME think over if it's better to do either: * pw = getChangelog().new ParentWalker(); pw.init() and pass pw instance around as needed * or * add Revlog#getParentWalker(), static class, make cons() and #init package-local, and keep SoftReference to allow walker reuse. @@ -598,9 +611,7 @@ failure = ex; } - // FIXME is HgException of any use here now? - // TODO consider if IOException in addition to HgException is of any real utility - public void checkFailed() throws HgException, IOException, CancelledException { + public void checkFailed() throws HgRuntimeException, IOException, CancelledException { if (failure == null) { return; } @@ -610,8 +621,8 @@ if (failure instanceof CancelledException) { throw (CancelledException) failure; } - if (failure instanceof HgException) { - throw (HgException) failure; + if (failure instanceof HgRuntimeException) { + throw (HgRuntimeException) failure; } throw new HgInvalidStateException(failure.toString()); } @@ -641,7 +652,7 @@ logFacility = log; } - protected void prepare(int revisionNumber, DataAccess da) throws HgException, IOException { + protected void prepare(int revisionNumber, DataAccess da) throws IOException { if (offset > 0) { // save few useless reset/rewind operations da.seek(offset); } @@ -689,8 +700,6 @@ recordFailure(ex); } catch (CancelledException ex) { recordFailure(ex); - } catch (HgException ex) { - recordFailure(ex); } } } diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/util/FileWalker.java --- a/src/org/tmatesoft/hg/util/FileWalker.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/util/FileWalker.java Wed Mar 28 18:39:29 2012 +0200 @@ -20,6 +20,7 @@ import java.util.LinkedList; import java.util.NoSuchElementException; +import org.tmatesoft.hg.core.SessionContext; import org.tmatesoft.hg.internal.Internals; /** @@ -36,11 +37,12 @@ private final LinkedList fileQueue; private final Path.Matcher scope; private final boolean execCap, linkCap; + private final SessionContext sessionContext; private RegularFileInfo nextFile; private Path nextPath; - public FileWalker(File dir, Path.Source pathFactory) { - this(dir, pathFactory, null); + public FileWalker(SessionContext ctx, File dir, Path.Source pathFactory) { + this(ctx, dir, pathFactory, null); } /** @@ -51,7 +53,8 @@ * also whether directories shall be traversed or not (Paths it gets in {@link Path.Matcher#accept(Path)} may * point to directories) */ - public FileWalker(File dir, Path.Source pathFactory, Path.Matcher scopeMatcher) { + public FileWalker(SessionContext ctx, File dir, Path.Source pathFactory, Path.Matcher scopeMatcher) { + sessionContext = ctx; startDir = dir; pathHelper = pathFactory; dirQueue = new LinkedList(); @@ -66,7 +69,7 @@ fileQueue.clear(); dirQueue.clear(); dirQueue.add(startDir); - nextFile = new RegularFileInfo(supportsExecFlag(), supportsLinkFlag()); + nextFile = new RegularFileInfo(sessionContext, supportsExecFlag(), supportsLinkFlag()); nextPath = null; } diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/util/ProgressSupport.java --- a/src/org/tmatesoft/hg/util/ProgressSupport.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/util/ProgressSupport.java Wed Mar 28 18:39:29 2012 +0200 @@ -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 @@ -26,7 +26,7 @@ // -1 for unspecified? public void start(int totalUnits); - public void worked(int units); + public void worked(int units); // fraction of totalUnits from #start(int) // XXX have to specify whether PS implementors may expect #done regardless of job completion (i.e. in case of cancellation) public void done(); @@ -53,10 +53,12 @@ } class Sub implements ProgressSupport { + private int perChildWorkUnitMultiplier; // to multiply child ps units + private int perChildWorkUnitDivisor; // to scale down to parent ps units + private int unitsConsumed; // parent ps units consumed so far + private int fraction = 0; // leftovers of previous not completely consumed work units private final ProgressSupport ps; - private int total; - private int units; - private int psUnits; + private final int psUnits; // total parent ps units public Sub(ProgressSupport parent, int parentUnits) { if (parent == null) { @@ -67,23 +69,27 @@ } public void start(int totalUnits) { - total = totalUnits; +// perChildWorkUnit = (psUnits*100) / totalUnits; + perChildWorkUnitDivisor = 10 * totalUnits; + perChildWorkUnitMultiplier = psUnits * perChildWorkUnitDivisor / totalUnits; + } public void worked(int worked) { - // FIXME fine-grained subprogress report. now only report at about 50% - if (psUnits > 1 && units < total/2 && units+worked > total/2) { - ps.worked(psUnits/2); - psUnits -= psUnits/2; + int x = fraction + worked * perChildWorkUnitMultiplier; + int u = x / perChildWorkUnitDivisor; + fraction = x % perChildWorkUnitDivisor; + if (u > 0) { + ps.worked(u); + unitsConsumed += u; } - units += worked; } public void done() { - ps.worked(psUnits); + ps.worked(psUnits - unitsConsumed); } } - + interface Target { T set(ProgressSupport ps); } diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/util/RegularFileInfo.java --- a/src/org/tmatesoft/hg/util/RegularFileInfo.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/util/RegularFileInfo.java Wed Mar 28 18:39:29 2012 +0200 @@ -24,6 +24,7 @@ import java.nio.channels.ClosedChannelException; import java.nio.channels.ReadableByteChannel; +import org.tmatesoft.hg.core.SessionContext; import org.tmatesoft.hg.internal.StreamLogFacility; /** @@ -36,14 +37,14 @@ private final RegularFileStats fileFlagsHelper; // null if both supportsLink and supportExec are false private File file; - public RegularFileInfo() { - this(false, false); + public RegularFileInfo(SessionContext ctx) { + this(ctx, false, false); } - public RegularFileInfo(boolean supportExecFlag, boolean supportSymlink) { + public RegularFileInfo(SessionContext ctx, boolean supportExecFlag, boolean supportSymlink) { supportsLink = supportSymlink; supportsExec = supportExecFlag; if (supportSymlink || supportExecFlag) { - fileFlagsHelper = new RegularFileStats(); + fileFlagsHelper = new RegularFileStats(ctx); } else { fileFlagsHelper = null; } diff -r 6437d261048a -r 48f993aa2f41 src/org/tmatesoft/hg/util/RegularFileStats.java --- a/src/org/tmatesoft/hg/util/RegularFileStats.java Wed Mar 28 15:42:15 2012 +0200 +++ b/src/org/tmatesoft/hg/util/RegularFileStats.java Wed Mar 28 18:39:29 2012 +0200 @@ -30,6 +30,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.tmatesoft.hg.core.SessionContext; import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.internal.ProcessExecHelper; @@ -54,6 +55,7 @@ private final List command; private final ProcessExecHelper execHelper; private final Matcher linkMatcher, execMatcher; + private final SessionContext sessionContext; // directory name to (short link name -> link target) @@ -62,7 +64,8 @@ private Map> dir2execs = new TreeMap>(); - RegularFileStats() { + RegularFileStats(SessionContext ctx) { + sessionContext = ctx; if (Internals.runningOnWindows()) { // XXX this implementation is not yet tested against any Windows repository, // only against sample dir listings. As long as Mercurial doesn't handle Windows @@ -88,7 +91,14 @@ execHelper = new ProcessExecHelper(); } + /** + * Fails silently indicating false for both x and l in case interaction with file system failed + * @param f file to check, doesn't need to exist + */ public void init(File f) { + isExec = isSymlink = false; + symlinkValue = null; + // // can't check isFile because Java would say false for a symlink with non-existing target if (f.isDirectory()) { // perhaps, shall just collect stats for all files and set false to exec/link flags? @@ -122,22 +132,22 @@ } dir2links.put(dirName, links); dir2execs.put(dirName, execs); + isExec = execs.contains(fileName); + isSymlink = links.containsKey(fileName); + if (isSymlink) { + symlinkValue = links.get(fileName); + } else { + symlinkValue = null; + } } catch (InterruptedException ex) { + sessionContext.getLog().warn(getClass(), ex, String.format("Failed to detect flags for %s", f)); // try again? ensure not too long? stop right away? - // FIXME EXCEPTIONS - throw new RuntimeException(); + // IGNORE, keep isExec and isSymlink false } catch (IOException ex) { - // FIXME EXCEPTIONS perhaps, fail silently indicating false for both x and l? - throw new RuntimeException(); + sessionContext.getLog().warn(getClass(), ex, String.format("Failed to detect flags for %s", f)); + // IGNORE, keep isExec and isSymlink false } } - isExec = execs.contains(fileName); - isSymlink = links.containsKey(fileName); - if (isSymlink) { - symlinkValue = links.get(fileName); - } else { - symlinkValue = null; - } } public boolean isExecutable() {