# HG changeset patch # User Artem Tikhomirov # Date 1333034075 -7200 # Node ID 31a89587eb0448e73a4babb2e317fb49317f269d # Parent 063b0663495a4483ddd94aeec337b880d7792c3b FIXMEs: consistent names, throws for commands and their handlers. Use of checked exceptions in hi-level api diff -r 063b0663495a -r 31a89587eb04 cmdline/org/tmatesoft/hg/console/ChangesetDumpHandler.java --- a/cmdline/org/tmatesoft/hg/console/ChangesetDumpHandler.java Wed Mar 28 19:34:37 2012 +0200 +++ b/cmdline/org/tmatesoft/hg/console/ChangesetDumpHandler.java Thu Mar 29 17:14:35 2012 +0200 @@ -63,7 +63,7 @@ return this; } - public void next(HgChangeset changeset) { + public void cset(HgChangeset changeset) { try { final String s = print(changeset); if (reverseOrder) { diff -r 063b0663495a -r 31a89587eb04 cmdline/org/tmatesoft/hg/console/Log.java --- a/cmdline/org/tmatesoft/hg/console/Log.java Wed Mar 28 19:34:37 2012 +0200 +++ b/cmdline/org/tmatesoft/hg/console/Log.java Thu Mar 29 17:14:35 2012 +0200 @@ -20,6 +20,7 @@ import java.util.List; +import org.tmatesoft.hg.core.HgChangesetHandler; import org.tmatesoft.hg.core.HgFileRevision; import org.tmatesoft.hg.core.HgLogCommand; import org.tmatesoft.hg.repo.HgDataFile; @@ -119,7 +120,7 @@ return rv; } - private static final class Dump extends ChangesetDumpHandler implements HgLogCommand.FileHistoryHandler { + private static final class Dump extends ChangesetDumpHandler implements HgChangesetHandler.WithCopyHistory { public Dump(HgRepository hgRepo) { super(hgRepo); diff -r 063b0663495a -r 31a89587eb04 cmdline/org/tmatesoft/hg/console/Main.java --- a/cmdline/org/tmatesoft/hg/console/Main.java Wed Mar 28 19:34:37 2012 +0200 +++ b/cmdline/org/tmatesoft/hg/console/Main.java Thu Mar 29 17:14:35 2012 +0200 @@ -28,6 +28,7 @@ import java.util.Map; import org.junit.Assert; +import org.tmatesoft.hg.core.HgManifestHandler; import org.tmatesoft.hg.core.HgCallbackTargetException; import org.tmatesoft.hg.core.HgCatCommand; import org.tmatesoft.hg.core.HgChangeset; @@ -175,7 +176,7 @@ HgLogCommand cmd = new HgLogCommand(hgRepo); cmd.file("file1", false); cmd.execute(new HgChangesetTreeHandler() { - public void next(HgChangesetTreeHandler.TreeElement entry) { + public void treeElement(HgChangesetTreeHandler.TreeElement entry) { StringBuilder sb = new StringBuilder(); HashSet test = new HashSet(entry.childRevisions()); for (HgChangeset cc : entry.children()) { @@ -558,7 +559,7 @@ } private void dumpCompleteManifestHigh() throws Exception { - new HgManifestCommand(hgRepo).dirs(true).execute(new HgManifestCommand.Handler() { + new HgManifestCommand(hgRepo).dirs(true).execute(new HgManifestHandler() { public void begin(Nodeid manifestRevision) { System.out.println(">> " + manifestRevision); diff -r 063b0663495a -r 31a89587eb04 cmdline/org/tmatesoft/hg/console/Manifest.java --- a/cmdline/org/tmatesoft/hg/console/Manifest.java Wed Mar 28 19:34:37 2012 +0200 +++ b/cmdline/org/tmatesoft/hg/console/Manifest.java Thu Mar 29 17:14:35 2012 +0200 @@ -19,6 +19,7 @@ import static org.tmatesoft.hg.console.Options.asSet; import static org.tmatesoft.hg.repo.HgRepository.TIP; +import org.tmatesoft.hg.core.HgManifestHandler; import org.tmatesoft.hg.core.HgFileRevision; import org.tmatesoft.hg.core.HgManifestCommand; import org.tmatesoft.hg.core.Nodeid; @@ -45,7 +46,7 @@ } final boolean debug = cmdLineOpts.getBoolean("--debug"); final boolean verbose = cmdLineOpts.getBoolean("-v", "--verbose"); - HgManifestCommand.Handler h = new HgManifestCommand.Handler() { + HgManifestHandler h = new HgManifestHandler() { public void begin(Nodeid manifestRevision) { } diff -r 063b0663495a -r 31a89587eb04 cmdline/org/tmatesoft/hg/console/Status.java --- a/cmdline/org/tmatesoft/hg/console/Status.java Wed Mar 28 19:34:37 2012 +0200 +++ b/cmdline/org/tmatesoft/hg/console/Status.java Thu Mar 29 17:14:35 2012 +0200 @@ -74,7 +74,7 @@ final EnumMap> data = new EnumMap>(HgStatus.Kind.class); final Map copies = showCopies ? new HashMap() : null; - public void handleStatus(HgStatus s) { + public void status(HgStatus s) { List l = data.get(s.getKind()); if (l == null) { l = new LinkedList(); @@ -86,7 +86,7 @@ } } - public void handleError(Path file, org.tmatesoft.hg.util.Status s) { + public void error(Path file, org.tmatesoft.hg.util.Status s) { System.out.printf("FAILURE: %s %s\n", s.getMessage(), file); s.getException().printStackTrace(System.out); } diff -r 063b0663495a -r 31a89587eb04 design.txt --- a/design.txt Wed Mar 28 19:34:37 2012 +0200 +++ b/design.txt Thu Mar 29 17:14:35 2012 +0200 @@ -139,4 +139,10 @@ Unfortunately, Revision would be a nice name for a class . As long as I don't want to keep methods to access int/nodeid separately and not to stick to Revision struct only (to avoid massive instances of Revision when only one is sufficient), I'll need to name these separate methods anyway. Present opinion is that I don't need the object right now (will have to live with RevisionObject or RevisionDescriptor -once change my mind) \ No newline at end of file +once change my mind) + +Handlers (HgStatusHandler, HgManifestHandler, HgChangesetHandler, HgChangesetTreeHandler) +methods DO NOT throw CancelledException. cancellation is separate from processing logic. handlers can implements CancelSupport to become a source of cancellation, if necessary +methods DO throw HgCallbackTargetException to propagate own errors/exceptions +methods are supposed to silently pass HgRuntimeExceptions (although callback implementers may decide to wrap them into HgCallbackTargetException) +descriptive names for the methods, whenever possible (not bare #next) \ No newline at end of file diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/ChangesetTransformer.java --- a/src/org/tmatesoft/hg/core/ChangesetTransformer.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/ChangesetTransformer.java Thu Mar 29 17:14:35 2012 +0200 @@ -71,7 +71,7 @@ HgChangeset changeset = t.handle(revisionNumber, nodeid, cset); try { - handler.next(changeset); + handler.cset(changeset); cancelHelper.checkCancelled(); } catch (HgCallbackTargetException ex) { failure = ex.setRevision(nodeid).setRevisionIndex(revisionNumber); diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgBadArgumentException.java --- a/src/org/tmatesoft/hg/core/HgBadArgumentException.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgBadArgumentException.java Thu Mar 29 17:14:35 2012 +0200 @@ -34,4 +34,10 @@ public HgBadArgumentException(String message, Throwable cause) { super(message, cause); } + + @Override + public HgBadArgumentException setRevision(Nodeid r) { + super.setRevision(r); + return this; + } } diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgCallbackTargetException.java --- a/src/org/tmatesoft/hg/core/HgCallbackTargetException.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgCallbackTargetException.java Thu Mar 29 17:14:35 2012 +0200 @@ -29,9 +29,11 @@ * library's own {@link HgException} are rather obscure. Suggested approach is to wrap whatever exception user code produces with * {@link HgCallbackTargetException}, the only checked exception allowed out from a callback. * - *

It's intentionally not a subclass of {@link HgException} to avoid get mixed with library own errors and be processed separately. + *

It's intentionally not a subclass of {@link HgException} to avoid get mixed with library own errors and be processed separately. * - * FIXME REVISIT shall just throw HgCallbackTargetException from any handler, and do not catch anything in commands at all. + *

Top-level API handlers ({@link HgStatusHandler}, {@link HgManifestHandler}, {@link HgChangesetHandler}, etc) allow to throw + * HgCallbackTargetException from their methods. Exceptions throws this way are not handled in corresponding commands, except for + * revision or file name specification, unless already set. The, these exceptions go straight to the command caller. * * @author Artem Tikhomirov * @author TMate Software Ltd. diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgCatCommand.java --- a/src/org/tmatesoft/hg/core/HgCatCommand.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgCatCommand.java Thu Mar 29 17:14:35 2012 +0200 @@ -24,10 +24,8 @@ import java.nio.ByteBuffer; import org.tmatesoft.hg.repo.HgDataFile; -import org.tmatesoft.hg.repo.HgInvalidControlFileException; -import org.tmatesoft.hg.repo.HgInvalidFileException; -import org.tmatesoft.hg.repo.HgInvalidRevisionException; import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.repo.HgRuntimeException; import org.tmatesoft.hg.util.Adaptable; import org.tmatesoft.hg.util.ByteChannel; import org.tmatesoft.hg.util.CancelSupport; @@ -136,11 +134,8 @@ * @param sink output channel to write data to. * * @throws HgBadArgumentException if no target file node found - * @throws HgInvalidControlFileException if access to revlog index/data entry failed - * @throws HgInvalidFileException if access to file in working directory failed - * @throws HgException in case of some other library issue - * @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 HgException subclass thereof to indicate specific issue with the command arguments or repository state + * @throws CancelledException if execution of the command was cancelled * @throws IllegalArgumentException when command arguments are incomplete or wrong */ public void execute(ByteChannel sink) throws HgException, CancelledException { @@ -154,49 +149,53 @@ if (sink == null) { throw new IllegalArgumentException("Need an output channel"); } - HgDataFile dataFile = repo.getFileNode(file); - if (!dataFile.exists()) { - // TODO may benefit from repo.getStoragePath to print revlog location in addition to human-friendly file path - throw new HgBadArgumentException(String.format("File %s not found in the repository", file), null).setFileName(file); - } - int revToExtract; - if (cset != null) { - int csetRev = repo.getChangelog().getRevisionIndex(cset); - Nodeid toExtract = null; - do { - // TODO post-1.0 perhaps, HgChangesetFileSneaker may come handy? - toExtract = repo.getManifest().getFileRevision(csetRev, file); + try { + HgDataFile dataFile = repo.getFileNode(file); + if (!dataFile.exists()) { + // TODO may benefit from repo.getStoragePath to print revlog location in addition to human-friendly file path + throw new HgPathNotFoundException(String.format("File %s not found in the repository", file), file); + } + int revToExtract; + if (cset != null) { + int csetRev = repo.getChangelog().getRevisionIndex(cset); + Nodeid toExtract = null; + do { + // TODO post-1.0 perhaps, HgChangesetFileSneaker may come handy? + toExtract = repo.getManifest().getFileRevision(csetRev, file); + if (toExtract == null) { + if (dataFile.isCopy()) { + file = dataFile.getCopySourceName(); + dataFile = repo.getFileNode(file); + } else { + break; + } + } + } while (toExtract == null); if (toExtract == null) { - if (dataFile.isCopy()) { - file = dataFile.getCopySourceName(); - dataFile = repo.getFileNode(file); - } else { - break; - } + String m = String.format("File %s nor its origins were known at repository's %s revision", file, cset.shortNotation()); + throw new HgPathNotFoundException(m, file).setRevision(cset); } - } while (toExtract == null); - if (toExtract == null) { - // TODO explicit FileNotFoundException? - throw new HgBadArgumentException(String.format("File %s nor its origins were not known at repository %s revision", file, cset.shortNotation()), null); + revToExtract = dataFile.getRevisionIndex(toExtract); + } else if (revision != null) { + revToExtract = dataFile.getRevisionIndex(revision); + } else { + revToExtract = revisionIndex; } - revToExtract = dataFile.getRevisionIndex(toExtract); - } else if (revision != null) { - revToExtract = dataFile.getRevisionIndex(revision); - } else { - revToExtract = revisionIndex; + ByteChannel sinkWrap; + if (getCancelSupport(null, false) == null) { + // no command-specific cancel helper, no need for extra proxy + // sink itself still may supply CS + sinkWrap = sink; + } else { + // try CS from sink, if any. at least there is CS from command + CancelSupport cancelHelper = getCancelSupport(sink, true); + cancelHelper.checkCancelled(); + sinkWrap = new ByteChannelProxy(sink, cancelHelper); + } + dataFile.contentWithFilters(revToExtract, sinkWrap); + } catch (HgRuntimeException ex) { + throw new HgLibraryFailureException(ex); } - ByteChannel sinkWrap; - if (getCancelSupport(null, false) == null) { - // no command-specific cancel helper, no need for extra proxy - // sink itself still may supply CS - sinkWrap = sink; - } else { - // try CS from sink, if any. at least there is CS from command - CancelSupport cancelHelper = getCancelSupport(sink, true); - cancelHelper.checkCancelled(); - sinkWrap = new ByteChannelProxy(sink, cancelHelper); - } - dataFile.contentWithFilters(revToExtract, sinkWrap); } private static class ByteChannelProxy implements ByteChannel, Adaptable { diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgChangeset.java --- a/src/org/tmatesoft/hg/core/HgChangeset.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgChangeset.java Thu Mar 29 17:14:35 2012 +0200 @@ -21,10 +21,9 @@ import java.util.List; import java.util.Map; -import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; import org.tmatesoft.hg.repo.HgChangelog; +import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; import org.tmatesoft.hg.repo.HgInvalidStateException; -import org.tmatesoft.hg.repo.HgInvalidControlFileException; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.repo.HgRuntimeException; import org.tmatesoft.hg.repo.HgStatusCollector; @@ -201,7 +200,7 @@ return deletedFiles; } - public boolean isMerge() throws HgInvalidControlFileException { + public boolean isMerge() throws HgRuntimeException { // p1 == -1 and p2 != -1 is legitimate case return !(getFirstParentRevision().isNull() || getSecondParentRevision().isNull()); } diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgChangesetFileSneaker.java --- a/src/org/tmatesoft/hg/core/HgChangesetFileSneaker.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgChangesetFileSneaker.java Thu Mar 29 17:14:35 2012 +0200 @@ -18,7 +18,6 @@ import org.tmatesoft.hg.internal.ManifestRevision; import org.tmatesoft.hg.repo.HgDataFile; -import org.tmatesoft.hg.repo.HgInvalidControlFileException; import org.tmatesoft.hg.repo.HgInvalidStateException; import org.tmatesoft.hg.repo.HgManifest; import org.tmatesoft.hg.repo.HgRepository; @@ -92,16 +91,21 @@ /** * Shortcut to perform {@link #check(Path)} and {@link #exists()}. Result of the check may be accessed via {@link #getCheckStatus()}. + * Errors during the check, if any, are reported through exception. * * @param file name of the file in question * @return true if file is known at the selected changeset. + * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state * @throws IllegalArgumentException if {@link #changeset(Nodeid)} not specified or file argument is bad. - * @throws HgInvalidControlFileException if access to revlog index/data entry failed */ - public boolean checkExists(Path file) throws HgInvalidControlFileException { + public boolean checkExists(Path file) throws HgException { check(file); - if (!checkResult.isOk() && checkResult.getException() instanceof HgInvalidControlFileException) { - throw (HgInvalidControlFileException) checkResult.getException(); + // next seems reasonable, however renders boolean return value useless. perhaps void or distinct method? +// if (checkResult.isOk() && !exists()) { +// throw new HgPathNotFoundException(checkResult.getMessage(), file); +// } + if (!checkResult.isOk() && checkResult.getException() instanceof HgRuntimeException) { + throw new HgLibraryFailureException((HgRuntimeException) checkResult.getException()); } return checkResult.isOk() && exists(); } diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgChangesetHandler.java --- a/src/org/tmatesoft/hg/core/HgChangesetHandler.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgChangesetHandler.java Thu Mar 29 17:14:35 2012 +0200 @@ -17,7 +17,7 @@ package org.tmatesoft.hg.core; import org.tmatesoft.hg.internal.Callback; -import org.tmatesoft.hg.util.CancelledException; +import org.tmatesoft.hg.util.Path; /** * Callback to process {@link HgChangeset changesets}. @@ -26,11 +26,31 @@ * @author TMate Software Ltd. */ @Callback -public interface HgChangesetHandler/*XXX perhaps, shall parameterize with exception clients can throw, like: */ { +public interface HgChangesetHandler { + /** - * @param changeset not necessarily a distinct instance each time, {@link HgChangeset#clone() clone()} if need a copy. + * @param changeset descriptor of a change, not necessarily a distinct instance each time, {@link HgChangeset#clone() clone()} if need a copy. * @throws HgCallbackTargetException wrapper for any exception user code may produce - * @throws CancelledException if handler is not interested in more changesets and iteration shall stop */ - void next(HgChangeset changeset) throws HgCallbackTargetException, CancelledException; + void cset(HgChangeset changeset) throws HgCallbackTargetException; + + + /** + * When {@link HgLogCommand} is executed against file, handler passed to {@link HgLogCommand#execute(HgChangesetHandler)} may optionally + * implement this interface to get information about file renames. Method {@link #copy(HgFileRevision, HgFileRevision)} would + * get invoked prior any changeset of the original file (if file history being followed) is reported via {@link #cset(HgChangeset)}. + * + * For {@link HgLogCommand#file(Path, boolean)} with renamed file path and follow argument set to false, + * {@link #copy(HgFileRevision, HgFileRevision)} would be invoked for the first copy/rename in the history of the file, but not + * followed by any changesets. + */ + @Callback + public interface WithCopyHistory extends HgChangesetHandler { + // XXX perhaps, should distinguish copy from rename? And what about merged revisions and following them? + + /** + * @throws HgCallbackTargetException wrapper object for any exception user code may produce + */ + void copy(HgFileRevision from, HgFileRevision to) throws HgCallbackTargetException; + } } diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgChangesetTreeHandler.java --- a/src/org/tmatesoft/hg/core/HgChangesetTreeHandler.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgChangesetTreeHandler.java Thu Mar 29 17:14:35 2012 +0200 @@ -19,7 +19,6 @@ import java.util.Collection; import org.tmatesoft.hg.internal.Callback; -import org.tmatesoft.hg.util.CancelledException; import org.tmatesoft.hg.util.Pair; /** @@ -36,9 +35,8 @@ * @param entry access to various pieces of information about current tree node. Instances might be * reused across calls and shall not be kept by client's code * @throws HgCallbackTargetException wrapper for any exception user code may produce - * @throws CancelledException if execution of the operation was cancelled */ - public void next(HgChangesetTreeHandler.TreeElement entry) throws HgCallbackTargetException, CancelledException; + public void treeElement(HgChangesetTreeHandler.TreeElement entry) throws HgCallbackTargetException; interface TreeElement { /** diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgException.java --- a/src/org/tmatesoft/hg/core/HgException.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgException.java Thu Mar 29 17:14:35 2012 +0200 @@ -27,19 +27,19 @@ * @author TMate Software Ltd. */ @SuppressWarnings("serial") -public class HgException extends Exception { +public abstract class HgException extends Exception { protected final ExceptionInfo extras = new ExceptionInfo(this); - public HgException(String reason) { + protected HgException(String reason) { super(reason); } - public HgException(String reason, Throwable cause) { + protected HgException(String reason, Throwable cause) { super(reason, cause); } - public HgException(Throwable cause) { + protected HgException(Throwable cause) { super(cause); } diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgFileRevision.java --- a/src/org/tmatesoft/hg/core/HgFileRevision.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgFileRevision.java Thu Mar 29 17:14:35 2012 +0200 @@ -17,11 +17,10 @@ package org.tmatesoft.hg.core; import org.tmatesoft.hg.repo.HgDataFile; -import org.tmatesoft.hg.repo.HgInvalidControlFileException; -import org.tmatesoft.hg.repo.HgInvalidRevisionException; import org.tmatesoft.hg.repo.HgManifest; import org.tmatesoft.hg.repo.HgManifest.Flags; import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.repo.HgRuntimeException; import org.tmatesoft.hg.util.ByteChannel; import org.tmatesoft.hg.util.CancelledException; import org.tmatesoft.hg.util.Pair; @@ -78,10 +77,9 @@ /** * 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 + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public HgManifest.Flags getFileFlags() throws HgInvalidControlFileException, HgInvalidRevisionException { + public HgManifest.Flags getFileFlags() throws HgRuntimeException { if (flags == null) { HgDataFile df = repo.getFileNode(path); int revIdx = df.getRevisionIndex(revision); @@ -112,8 +110,9 @@ * In most cases, only one parent revision would be present, only for merge revisions one can expect both. * * @return parent revisions of this file revision, with {@link Nodeid#NULL} for missing values. + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ - public Pair getParents() throws HgInvalidControlFileException { + public Pair getParents() throws HgRuntimeException { if (parents == null) { HgDataFile fn = repo.getFileNode(path); int revisionIndex = fn.getRevisionIndex(revision); diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgIncomingCommand.java --- a/src/org/tmatesoft/hg/core/HgIncomingCommand.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgIncomingCommand.java Thu Mar 29 17:14:35 2012 +0200 @@ -29,12 +29,12 @@ import org.tmatesoft.hg.internal.RepositoryComparator.BranchChain; import org.tmatesoft.hg.repo.HgBundle; import org.tmatesoft.hg.repo.HgChangelog; +import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; import org.tmatesoft.hg.repo.HgInvalidControlFileException; -import org.tmatesoft.hg.repo.HgInvalidFileException; -import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; import org.tmatesoft.hg.repo.HgInvalidStateException; import org.tmatesoft.hg.repo.HgRemoteRepository; import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.repo.HgRuntimeException; import org.tmatesoft.hg.util.CancelledException; import org.tmatesoft.hg.util.ProgressSupport; @@ -103,34 +103,36 @@ * * @return list of nodes present at remote and missing locally * @throws HgRemoteConnectionException when failed to communicate with remote repository - * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state * @throws CancelledException if execution of the command was cancelled */ - public List executeLite() throws HgRemoteConnectionException, HgInvalidControlFileException, CancelledException { - LinkedHashSet result = new LinkedHashSet(); - RepositoryComparator repoCompare = getComparator(); - for (BranchChain bc : getMissingBranches()) { - List missing = repoCompare.visitBranches(bc); - HashSet common = new HashSet(); // ordering is irrelevant - repoCompare.collectKnownRoots(bc, common); - // missing could only start with common elements. Once non-common, rest is just distinct branch revision trails. - for (Iterator it = missing.iterator(); it.hasNext() && common.contains(it.next()); it.remove()) ; - result.addAll(missing); + public List executeLite() throws HgException, CancelledException { + try { + LinkedHashSet result = new LinkedHashSet(); + RepositoryComparator repoCompare = getComparator(); + for (BranchChain bc : getMissingBranches()) { + List missing = repoCompare.visitBranches(bc); + HashSet common = new HashSet(); // ordering is irrelevant + repoCompare.collectKnownRoots(bc, common); + // missing could only start with common elements. Once non-common, rest is just distinct branch revision trails. + for (Iterator it = missing.iterator(); it.hasNext() && common.contains(it.next()); it.remove()) ; + result.addAll(missing); + } + ArrayList rv = new ArrayList(result); + return rv; + } catch (HgRuntimeException ex) { + throw new HgLibraryFailureException(ex); } - ArrayList rv = new ArrayList(result); - return rv; } /** * Full information about incoming changes * - * @throws HgRemoteConnectionException when failed to communicate with remote repository - * @throws HgInvalidControlFileException if access to revlog index/data entry failed - * @throws HgInvalidFileException to indicate failure working with locally downloaded changes in a bundle file * @throws HgCallbackTargetException to re-throw exception from the handler + * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state * @throws CancelledException if execution of the command was cancelled */ - public void executeFull(final HgChangesetHandler handler) throws HgRemoteConnectionException, HgInvalidControlFileException, HgInvalidFileException, HgCallbackTargetException, CancelledException { + public void executeFull(final HgChangesetHandler handler) throws HgCallbackTargetException, HgException, CancelledException { if (handler == null) { throw new IllegalArgumentException("Delegate can't be null"); } @@ -161,6 +163,8 @@ } }); transformer.checkFailure(); + } catch (HgRuntimeException ex) { + throw new HgLibraryFailureException(ex); } finally { ps.done(); } diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgLibraryFailureException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/HgLibraryFailureException.java Thu Mar 29 17:14:35 2012 +0200 @@ -0,0 +1,40 @@ +/* + * Copyright (c) 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 + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.core; + +import org.tmatesoft.hg.repo.HgRuntimeException; + +/** + * Sole purpose of this exception is to wrap unexpected errors from the library implementation and + * propagate them to clients of hi-level API for graceful (and explicit) processing. + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +@SuppressWarnings("serial") +public class HgLibraryFailureException extends HgException { + + public HgLibraryFailureException(HgRuntimeException cause) { + super(cause); + assert cause != null; + } + + @Override + public HgRuntimeException getCause() { + return (HgRuntimeException) super.getCause(); + } +} diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgLogCommand.java --- a/src/org/tmatesoft/hg/core/HgLogCommand.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgLogCommand.java Thu Mar 29 17:14:35 2012 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2012 TMate Software Ltd +s * 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 @@ -36,7 +36,6 @@ import org.tmatesoft.hg.repo.HgDataFile; import org.tmatesoft.hg.repo.HgInternals; import org.tmatesoft.hg.repo.HgInvalidControlFileException; -import org.tmatesoft.hg.repo.HgInvalidRevisionException; import org.tmatesoft.hg.repo.HgInvalidStateException; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.repo.HgRuntimeException; @@ -136,8 +135,9 @@ /** * Limit to specified subset of Changelog, [min(rev1,rev2), max(rev1,rev2)], inclusive. * Revision may be specified with {@link HgRepository#TIP} - * @param rev1 - revision local index - * @param rev2 - revision local index + * + * @param rev1 - local index of start changeset revision + * @param rev2 - index of end changeset revision * @return this instance for convenience */ public HgLogCommand range(int rev1, int rev2) { @@ -159,13 +159,16 @@ * * @param nid changeset revision * @return this for convenience - * @throws HgInvalidRevisionException if supplied nodeid doesn't identify any revision from this revlog - * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgBadArgumentException if failed to find supplied changeset revision */ - public HgLogCommand changeset(Nodeid nid) throws HgInvalidControlFileException, HgInvalidRevisionException { + public HgLogCommand changeset(Nodeid nid) throws HgBadArgumentException { // XXX perhaps, shall support multiple (...) arguments and extend #execute to handle not only range, but also set of revisions. - final int csetRevIndex = repo.getChangelog().getRevisionIndex(nid); - return range(csetRevIndex, csetRevIndex); + try { + final int csetRevIndex = repo.getChangelog().getRevisionIndex(nid); + return range(csetRevIndex, csetRevIndex); + } catch (HgRuntimeException ex) { + throw new HgBadArgumentException("Can't find revision", ex).setRevision(nid); + } } /** @@ -189,7 +192,9 @@ /** * Similar to {@link #execute(HgChangesetHandler)}, collects and return result as a list. - * @throws HgException FIXME EXCEPTIONS + * + * @see #execute(HgChangesetHandler) + * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state */ public List execute() throws HgException { CollectHandler collector = new CollectHandler(); @@ -213,9 +218,8 @@ * Iterate over range of changesets configured in the command. * * @param handler callback to process changesets. - * @throws HgCallbackTargetException wrapper for any exception user callback code may produce - * @throws HgInvalidControlFileException if access to revlog index/data entry failed - * @throws HgException in case of some other library issue + * @throws HgCallbackTargetException propagated exception from the handler + * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state * @throws CancelledException if execution of the command was cancelled * @throws IllegalArgumentException when inspector argument is null * @throws ConcurrentModificationException if this log command instance is already running @@ -241,15 +245,18 @@ } else { progressHelper.start(-1/*XXX enum const, or a dedicated method startUnspecified(). How about startAtLeast(int)?*/); HgDataFile fileNode = repo.getFileNode(file); + if (!fileNode.exists()) { + throw new HgPathNotFoundException(String.format("File %s not found in the repository", file), file); + } fileNode.history(startRev, endRev, this); csetTransform.checkFailure(); if (fileNode.isCopy()) { // even if we do not follow history, report file rename do { - if (handler instanceof FileHistoryHandler) { + if (handler instanceof HgChangesetHandler.WithCopyHistory) { HgFileRevision src = new HgFileRevision(repo, fileNode.getCopySourceRevision(), null, fileNode.getCopySourceName()); HgFileRevision dst = new HgFileRevision(repo, fileNode.getRevision(0), null, fileNode.getPath(), src.getPath()); - ((FileHistoryHandler) handler).copy(src, dst); + ((HgChangesetHandler.WithCopyHistory) handler).copy(src, dst); } if (limit > 0 && count >= limit) { // if limit reach, follow is useless. @@ -263,8 +270,8 @@ } while (followHistory && fileNode.isCopy()); } } -// } catch (HgRuntimeException ex) { -// FIXME wrap with checked HgRuntime subclass + } catch (HgRuntimeException ex) { + throw new HgLibraryFailureException(ex); } finally { csetTransform = null; progressHelper.done(); @@ -275,9 +282,8 @@ * Tree-wise iteration of a file history, with handy access to parent-child relations between changesets. * * @param handler callback to process changesets. - * @throws HgCallbackTargetException to re-throw exception from the handler - * @throws HgInvalidControlFileException if access to revlog index/data entry failed - * @throws HgException in case of some other library issue + * @throws HgCallbackTargetException propagated exception from the handler + * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state * @throws CancelledException if execution of the command was cancelled * @throws IllegalArgumentException if command is not satisfied with its arguments * @throws ConcurrentModificationException if this log command instance is already running @@ -345,7 +351,7 @@ // XXX shall sort completeHistory according to changeset numbers? for (int i = 0; i < completeHistory.length; i++ ) { final HistoryNode n = completeHistory[i]; - handler.next(ei.init(n)); + handler.treeElement(ei.init(n)); ph2.worked(1); cancelHelper.checkCancelled(); } @@ -390,26 +396,6 @@ } - /** - * When {@link HgLogCommand} is executed against file, handler passed to {@link HgLogCommand#execute(HgChangesetHandler)} may optionally - * implement this interface to get information about file renames. Method {@link #copy(HgFileRevision, HgFileRevision)} would - * get invoked prior any changeset of the original file (if file history being followed) is reported via {@link #next(HgChangeset)}. - * - * For {@link HgLogCommand#file(Path, boolean)} with renamed file path and follow argument set to false, - * {@link #copy(HgFileRevision, HgFileRevision)} would be invoked for the first copy/rename in the history of the file, but not - * followed by any changesets. - * - * @author Artem Tikhomirov - * @author TMate Software Ltd. - */ - public interface FileHistoryHandler extends HgChangesetHandler { // FIXME move to stanalone class file, perhaps? - // XXX perhaps, should distinguish copy from rename? And what about merged revisions and following them? - /** - * @throws HgCallbackTargetException wrapper object for any exception user code may produce - */ - void copy(HgFileRevision from, HgFileRevision to) throws HgCallbackTargetException; - } - public static class CollectHandler implements HgChangesetHandler { private final List result = new LinkedList(); @@ -417,7 +403,7 @@ return Collections.unmodifiableList(result); } - public void next(HgChangeset changeset) { + public void cset(HgChangeset changeset) { result.add(changeset.clone()); } } diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgManifestCommand.java --- a/src/org/tmatesoft/hg/core/HgManifestCommand.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgManifestCommand.java Thu Mar 29 17:14:35 2012 +0200 @@ -25,10 +25,12 @@ import java.util.LinkedList; import java.util.List; -import org.tmatesoft.hg.internal.Callback; import org.tmatesoft.hg.repo.HgManifest; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.repo.HgManifest.Flags; +import org.tmatesoft.hg.repo.HgRuntimeException; +import org.tmatesoft.hg.util.CancelSupport; +import org.tmatesoft.hg.util.CancelledException; import org.tmatesoft.hg.util.Path; import org.tmatesoft.hg.util.PathPool; import org.tmatesoft.hg.util.PathRewrite; @@ -45,7 +47,7 @@ private final HgRepository repo; private Path.Matcher matcher; private int startRev = 0, endRev = TIP; - private Handler visitor; + private HgManifestHandler visitor; private boolean needDirs = false; private final Mediator mediator = new Mediator(); @@ -97,12 +99,16 @@ } /** - * Runs the command. + * With all parameters set, execute the command. + * * @param handler - callback to get the outcome + * @throws HgCallbackTargetException propagated exception from the handler + * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state + * @throws CancelledException if execution of the command was cancelled * @throws IllegalArgumentException if handler is null * @throws ConcurrentModificationException if this command is already in use (running) */ - public void execute(Handler handler) throws HgException { + public void execute(HgManifestHandler handler) throws HgCallbackTargetException, HgException, CancelledException { if (handler == null) { throw new IllegalArgumentException(); } @@ -111,25 +117,17 @@ } try { visitor = handler; - mediator.start(); + mediator.start(getCancelSupport(handler, true)); repo.getManifest().walk(startRev, endRev, mediator); + mediator.checkFailure(); + } catch (HgRuntimeException ex) { + throw new HgLibraryFailureException(ex); } finally { mediator.done(); visitor = null; } } - /** - * Callback to walk file/directory tree of a revision - */ - @Callback - public interface Handler { // FIXME TLC - void begin(Nodeid manifestRevision); - void dir(Path p); // optionally invoked (if walker was configured to spit out directories) prior to any files from this dir and subdirs - void file(HgFileRevision fileRevision); // XXX allow to check p is invalid (df.exists()) - void end(Nodeid manifestRevision); - } - // I'd rather let HgManifestCommand implement HgManifest.Inspector directly, but this pollutes API alot private class Mediator implements HgManifest.Inspector { // file names are likely to repeat in each revision, hence caching of Paths. @@ -138,61 +136,111 @@ private PathPool pathPool; private List manifestContent; private Nodeid manifestNodeid; + private Exception failure; + private CancelSupport cancelHelper; - public void start() { + public void start(CancelSupport cs) { + assert cs != null; // Manifest keeps normalized paths pathPool = new PathPool(new PathRewrite.Empty()); + cancelHelper = cs; } public void done() { manifestContent = null; pathPool = null; } + + private void recordFailure(HgCallbackTargetException ex) { + failure = ex; + } + private void recordCancel(CancelledException ex) { + failure = ex; + } + + public void checkFailure() throws HgCallbackTargetException, CancelledException { + // TODO post-1.0 perhaps, can combine this code (record/checkFailure) for reuse in more classes (e.g. in Revlog) + if (failure instanceof HgCallbackTargetException) { + HgCallbackTargetException ex = (HgCallbackTargetException) failure; + failure = null; + throw ex; + } + if (failure instanceof CancelledException) { + CancelledException ex = (CancelledException) failure; + failure = null; + throw ex; + } + } public boolean begin(int manifestRevision, Nodeid nid, int changelogRevision) { if (needDirs && manifestContent == null) { manifestContent = new LinkedList(); } - visitor.begin(manifestNodeid = nid); - return true; + try { + visitor.begin(manifestNodeid = nid); + cancelHelper.checkCancelled(); + return true; + } catch (HgCallbackTargetException ex) { + recordFailure(ex); + return false; + } catch (CancelledException ex) { + recordCancel(ex); + return false; + } } public boolean end(int revision) { - if (needDirs) { - LinkedHashMap> breakDown = new LinkedHashMap>(); - for (HgFileRevision fr : manifestContent) { - Path filePath = fr.getPath(); - Path dirPath = pathPool.parent(filePath); - LinkedList revs = breakDown.get(dirPath); - if (revs == null) { - revs = new LinkedList(); - breakDown.put(dirPath, revs); + try { + if (needDirs) { + LinkedHashMap> breakDown = new LinkedHashMap>(); + for (HgFileRevision fr : manifestContent) { + Path filePath = fr.getPath(); + Path dirPath = pathPool.parent(filePath); + LinkedList revs = breakDown.get(dirPath); + if (revs == null) { + revs = new LinkedList(); + breakDown.put(dirPath, revs); + } + revs.addLast(fr); } - revs.addLast(fr); + for (Path dir : breakDown.keySet()) { + visitor.dir(dir); + cancelHelper.checkCancelled(); + for (HgFileRevision fr : breakDown.get(dir)) { + visitor.file(fr); + } + } + manifestContent.clear(); } - for (Path dir : breakDown.keySet()) { - visitor.dir(dir); - for (HgFileRevision fr : breakDown.get(dir)) { - visitor.file(fr); - } - } - manifestContent.clear(); + visitor.end(manifestNodeid); + cancelHelper.checkCancelled(); + return true; + } catch (HgCallbackTargetException ex) { + recordFailure(ex); + return false; + } catch (CancelledException ex) { + recordCancel(ex); + return false; + } finally { + manifestNodeid = null; } - visitor.end(manifestNodeid); - manifestNodeid = null; - return true; } public boolean next(Nodeid nid, Path fname, Flags flags) { if (matcher != null && !matcher.accept(fname)) { return true; } - HgFileRevision fr = new HgFileRevision(repo, nid, flags, fname); - if (needDirs) { - manifestContent.add(fr); - } else { - visitor.file(fr); + try { + HgFileRevision fr = new HgFileRevision(repo, nid, flags, fname); + if (needDirs) { + manifestContent.add(fr); + } else { + visitor.file(fr); + } + return true; + } catch (HgCallbackTargetException ex) { + recordFailure(ex); + return false; } - return true; } } } diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgManifestHandler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/HgManifestHandler.java Thu Mar 29 17:14:35 2012 +0200 @@ -0,0 +1,64 @@ +/* + * 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 + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.core; + +import org.tmatesoft.hg.internal.Callback; +import org.tmatesoft.hg.util.Path; + +/** + * Callback to walk file/directory tree of a revision + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +@Callback +public interface HgManifestHandler { + /** + * Indicates start of manifest revision. Subsequent {@link #file(HgFileRevision)} and {@link #dir(Path)} come + * from the specified manifest revision until {@link #end(Nodeid)} with the matching revision is invoked. + * + * @param manifestRevision unique identifier of the manifest revision + * @throws HgCallbackTargetException wrapper for any exception user code may produce + */ + void begin(Nodeid manifestRevision) throws HgCallbackTargetException; + + /** + * If walker is configured to spit out directories, indicates files from specified directories are about to be reported. + * Comes prior to any files from this directory and subdirectories + * + * @param path directory known in the manifest + * @throws HgCallbackTargetException wrapper for any exception user code may produce + */ + void dir(Path path) throws HgCallbackTargetException; + + /** + * Reports a file revision entry in the manifest + * + * @param fileRevision description of the file revision + * @throws HgCallbackTargetException wrapper for any exception user code may produce + */ + void file(HgFileRevision fileRevision) throws HgCallbackTargetException; + + /** + * Indicates all files from the manifest revision have been reported. + * Closes {@link #begin(Nodeid)} with the same revision that came before. + * + * @param manifestRevision unique identifier of the manifest revision + * @throws HgCallbackTargetException wrapper for any exception user code may produce + */ + void end(Nodeid manifestRevision) throws HgCallbackTargetException; +} \ No newline at end of file diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgOutgoingCommand.java --- a/src/org/tmatesoft/hg/core/HgOutgoingCommand.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgOutgoingCommand.java Thu Mar 29 17:14:35 2012 +0200 @@ -25,6 +25,7 @@ import org.tmatesoft.hg.repo.HgInvalidControlFileException; import org.tmatesoft.hg.repo.HgRemoteRepository; import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.repo.HgRuntimeException; import org.tmatesoft.hg.util.CancelSupport; import org.tmatesoft.hg.util.CancelledException; import org.tmatesoft.hg.util.ProgressSupport; @@ -95,14 +96,16 @@ * * @return list on local nodes known to be missing at remote server * @throws HgRemoteConnectionException when failed to communicate with remote repository - * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state * @throws CancelledException if execution of the command was cancelled */ - public List executeLite() throws HgRemoteConnectionException, HgInvalidControlFileException, CancelledException { + public List executeLite() throws HgRemoteConnectionException, HgException, CancelledException { final ProgressSupport ps = getProgressSupport(null); try { ps.start(10); return getComparator(new ProgressSupport.Sub(ps, 5), getCancelSupport(null, true)).getLocalOnlyRevisions(); + } catch (HgRuntimeException ex) { + throw new HgLibraryFailureException(ex); } finally { ps.done(); } @@ -112,12 +115,12 @@ * Complete information about outgoing changes * * @param handler delegate to process changes + * @throws HgCallbackTargetException propagated exception from the handler * @throws HgRemoteConnectionException when failed to communicate with remote repository - * @throws HgInvalidControlFileException if access to revlog index/data entry failed - * @throws HgCallbackTargetException to re-throw exception from the handler + * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state * @throws CancelledException if execution of the command was cancelled */ - public void executeFull(final HgChangesetHandler handler) throws HgRemoteConnectionException, HgInvalidControlFileException, HgCallbackTargetException, CancelledException { + public void executeFull(final HgChangesetHandler handler) throws HgCallbackTargetException, HgException, CancelledException { if (handler == null) { throw new IllegalArgumentException("Delegate can't be null"); } @@ -129,6 +132,8 @@ inspector.limitBranches(branches); getComparator(new ProgressSupport.Sub(ps, 1), cs).visitLocalOnlyRevisions(inspector); inspector.checkFailure(); + } catch (HgRuntimeException ex) { + throw new HgLibraryFailureException(ex); } finally { ps.done(); } diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgPathNotFoundException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/HgPathNotFoundException.java Thu Mar 29 17:14:35 2012 +0200 @@ -0,0 +1,36 @@ +/* + * Copyright (c) 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 + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.core; + +import org.tmatesoft.hg.util.Path; + +/** + * Indicates supplied path/location was is missing in the repository or specific revision. + *

Use {@link #getFileName()} to access name of the missing file + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +@SuppressWarnings("serial") +public class HgPathNotFoundException extends HgException { + + public HgPathNotFoundException(String message, Path missingPath) { + super(message, null); + assert missingPath != null; + setFileName(missingPath); + } +} diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgStatus.java --- a/src/org/tmatesoft/hg/core/HgStatus.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgStatus.java Thu Mar 29 17:14:35 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 @@ -20,7 +20,6 @@ import java.util.Date; import org.tmatesoft.hg.internal.ChangelogHelper; -import org.tmatesoft.hg.repo.HgInvalidControlFileException; import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; import org.tmatesoft.hg.util.Path; @@ -72,7 +71,7 @@ /** * @return null if author for the change can't be deduced (e.g. for clean files it's senseless) */ - public String getModificationAuthor() throws HgInvalidControlFileException { + public String getModificationAuthor() { RawChangeset cset = logHelper.findLatestChangeWith(path); if (cset == null) { if (kind == Kind.Modified || kind == Kind.Added || kind == Kind.Removed /*&& RightBoundary is TIP*/) { @@ -85,7 +84,7 @@ return null; } - public Date getModificationDate() throws HgInvalidControlFileException { + public Date getModificationDate() { RawChangeset cset = logHelper.findLatestChangeWith(path); if (cset == null) { File localFile = new File(logHelper.getRepo().getWorkingDir(), path.toString()); diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgStatusCommand.java --- a/src/org/tmatesoft/hg/core/HgStatusCommand.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgStatusCommand.java Thu Mar 29 17:14:35 2012 +0200 @@ -25,6 +25,7 @@ import org.tmatesoft.hg.internal.ChangelogHelper; import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.repo.HgRuntimeException; import org.tmatesoft.hg.repo.HgStatusCollector; import org.tmatesoft.hg.repo.HgStatusInspector; import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector; @@ -162,13 +163,14 @@ * Perform status operation according to parameters set. * * @param statusHandler callback to get status information - * @throws HgCallbackTargetException wrapper for any exception user callback code may produce + * @throws HgCallbackTargetException propagated exception from the handler + * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state * @throws CancelledException if execution of the command was cancelled * @throws IOException FIXME EXCEPTIONS WTF it's doing here if there are (further unspecified) errors while walking working copy * @throws IllegalArgumentException if handler is null * @throws ConcurrentModificationException if this command already runs (i.e. being used from another thread) */ - public void execute(HgStatusHandler statusHandler) throws HgCallbackTargetException, CancelledException, HgException, IOException { + public void execute(HgStatusHandler statusHandler) throws HgCallbackTargetException, HgException, CancelledException, IOException { if (statusHandler == null) { throw new IllegalArgumentException(); } @@ -197,6 +199,8 @@ // this is our exception, thrown from Mediator. // next check shall throw original cause of the stop - either HgCallbackTargetException or original CancelledException mediator.checkFailure(); + } catch (HgRuntimeException ex) { + throw new HgLibraryFailureException(ex); } finally { mediator.done(); } @@ -264,7 +268,7 @@ private void dispatch(HgStatus s) { try { - handler.handleStatus(s); + handler.status(s); handlerCancelSupport.checkCancelled(); } catch (HgCallbackTargetException ex) { failure = ex; @@ -317,7 +321,7 @@ public void invalid(Path fname, Exception err) { try { - handler.handleError(fname, new Status(Status.Kind.ERROR, "Failed to get file status", err)); + handler.error(fname, new Status(Status.Kind.ERROR, "Failed to get file status", err)); handlerCancelSupport.checkCancelled(); } catch (HgCallbackTargetException ex) { failure = ex; diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgStatusHandler.java --- a/src/org/tmatesoft/hg/core/HgStatusHandler.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgStatusHandler.java Thu Mar 29 17:14:35 2012 +0200 @@ -28,18 +28,20 @@ @Callback public interface HgStatusHandler { - /** #next() as in HgChangesetHandler? - * FIXME perhaps, handle() is better name? If yes, rename method in HgChangesetHandler, too, to make them similar. - * void next(HgStatus s); + /** + * Report status of the next file + * + * @param s file status descriptor * @throws HgCallbackTargetException wrapper for any exception user code may produce */ - void handleStatus(HgStatus s) throws HgCallbackTargetException; + void status(HgStatus s) throws HgCallbackTargetException; /** * Report non-critical error processing single file during status operation + * * @param file name of the file that caused the trouble * @param s error description object * @throws HgCallbackTargetException wrapper for any exception user code may produce */ - void handleError(Path file, Status s) throws HgCallbackTargetException; + void error(Path file, Status s) throws HgCallbackTargetException; } diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/core/HgUpdateConfigCommand.java --- a/src/org/tmatesoft/hg/core/HgUpdateConfigCommand.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/core/HgUpdateConfigCommand.java Thu Mar 29 17:14:35 2012 +0200 @@ -27,7 +27,6 @@ import org.tmatesoft.hg.internal.Experimental; import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.repo.HgInternals; -import org.tmatesoft.hg.repo.HgInvalidFileException; import org.tmatesoft.hg.repo.HgRepository; /** @@ -112,6 +111,11 @@ throw new UnsupportedOperationException(); } + /** + * Perform config file update + * + * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state + */ public void execute() throws HgException { try { ConfigFile cfg = new ConfigFile(); @@ -132,7 +136,8 @@ } cfg.writeTo(configFile); } catch (IOException ex) { - throw new HgInvalidFileException("Failed to update configuration file", ex, configFile); + String m = String.format("Failed to update configuration file %s", configFile); + throw new HgBadArgumentException(m, ex); // TODO [post-1.0] better exception, it's not bad argument case } } diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/repo/HgBundle.java --- a/src/org/tmatesoft/hg/repo/HgBundle.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgBundle.java Thu Mar 29 17:14:35 2012 +0200 @@ -19,7 +19,6 @@ import java.io.File; import java.io.IOException; -import org.tmatesoft.hg.core.HgBadArgumentException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.core.SessionContext; import org.tmatesoft.hg.internal.ByteArrayChannel; @@ -170,7 +169,7 @@ return false; } catch (IOException ex) { throw new HgInvalidFileException("Invalid bundle file", ex, bundleFile); // TODO post-1.0 revisit exception handling - } catch (HgBadArgumentException ex) { + } catch (HgInvalidDataFormatException ex) { throw new HgInvalidControlFileException("Invalid bundle file", ex, bundleFile); } return true; diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/repo/HgChangelog.java --- a/src/org/tmatesoft/hg/repo/HgChangelog.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgChangelog.java Thu Mar 29 17:14:35 2012 +0200 @@ -30,7 +30,6 @@ import java.util.Map; import java.util.TimeZone; -import org.tmatesoft.hg.core.HgBadArgumentException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.Callback; import org.tmatesoft.hg.internal.DataAccess; @@ -227,7 +226,7 @@ } } - /*package*/ static RawChangeset parse(DataAccess da) throws IOException, HgBadArgumentException { + /*package*/ static RawChangeset parse(DataAccess da) throws IOException, HgInvalidDataFormatException { byte[] data = da.byteArray(); RawChangeset rv = new RawChangeset(); rv.init(data, 0, data.length, null); @@ -235,18 +234,17 @@ } // @param usersPool - it's likely user names get repeated again and again throughout repository. can be null - // FIXME replace HgBadArgumentException with HgInvalidDataFormatException or HgInvalidControlFileException - /* package-local */void init(byte[] data, int offset, int length, Pool usersPool) throws HgBadArgumentException { + /* package-local */void init(byte[] data, int offset, int length, Pool usersPool) throws HgInvalidDataFormatException { final int bufferEndIndex = offset + length; final byte lineBreak = (byte) '\n'; int breakIndex1 = indexOf(data, lineBreak, offset, bufferEndIndex); if (breakIndex1 == -1) { - throw new HgBadArgumentException("Bad Changeset data", null); + throw new HgInvalidDataFormatException("Bad Changeset data"); } Nodeid _nodeid = Nodeid.fromAscii(data, 0, breakIndex1); int breakIndex2 = indexOf(data, lineBreak, breakIndex1 + 1, bufferEndIndex); if (breakIndex2 == -1) { - throw new HgBadArgumentException("Bad Changeset data", null); + throw new HgInvalidDataFormatException("Bad Changeset data"); } String _user = new String(data, breakIndex1 + 1, breakIndex2 - breakIndex1 - 1); if (usersPool != null) { @@ -254,12 +252,12 @@ } int breakIndex3 = indexOf(data, lineBreak, breakIndex2 + 1, bufferEndIndex); if (breakIndex3 == -1) { - throw new HgBadArgumentException("Bad Changeset data", null); + throw new HgInvalidDataFormatException("Bad Changeset data"); } String _timeString = new String(data, breakIndex2 + 1, breakIndex3 - breakIndex2 - 1); int space1 = _timeString.indexOf(' '); if (space1 == -1) { - throw new HgBadArgumentException(String.format("Bad Changeset data: %s in [%d..%d]", "time string", breakIndex2+1, breakIndex3), null); + throw new HgInvalidDataFormatException(String.format("Bad Changeset data: %s in [%d..%d]", "time string", breakIndex2+1, breakIndex3)); } int space2 = _timeString.indexOf(' ', space1 + 1); if (space2 == -1) { @@ -305,7 +303,7 @@ } } if (breakIndex4 == -1 || breakIndex4 >= bufferEndIndex) { - throw new HgBadArgumentException("Bad Changeset data", null); + throw new HgInvalidDataFormatException("Bad Changeset data"); } } else { breakIndex4--; @@ -317,7 +315,7 @@ } catch (UnsupportedEncodingException ex) { _comment = ""; // Could hardly happen - throw new HgBadArgumentException("Bad Changeset data", ex); + throw new HgInvalidDataFormatException("Bad Changeset data", ex); } // change this instance at once, don't leave it partially changes in case of error this.manifest = _nodeid; @@ -381,9 +379,8 @@ // XXX there's no guarantee for Changeset.Callback that distinct instance comes each time, consider instance reuse inspector.next(revisionNumber, Nodeid.fromBinary(nodeid, 0), cset); progressHelper.worked(1); - } catch (HgBadArgumentException ex) { - // see below about better exception - throw new HgInvalidControlFileException("Failed reading changelog", ex, null).setRevisionIndex(revisionNumber); + } catch (HgInvalidDataFormatException ex) { + throw ex.setRevisionIndex(revisionNumber); } catch (IOException ex) { // XXX need better exception, perhaps smth like HgChangelogException (extends HgInvalidControlFileException) throw new HgInvalidControlFileException("Failed reading changelog", ex, null).setRevisionIndex(revisionNumber); diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/repo/HgInvalidDataFormatException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/repo/HgInvalidDataFormatException.java Thu Mar 29 17:14:35 2012 +0200 @@ -0,0 +1,36 @@ +/* + * Copyright (c) 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 + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.repo; + +/** + * Indicates broken, unknown or otherwise bad data structure. + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +@SuppressWarnings("serial") +public class HgInvalidDataFormatException extends HgRuntimeException { + // IMPLEMENTATION NOTE. Perhaps, this might be intermediate class between HgRuntimeException and HgInvalidFileException + + public HgInvalidDataFormatException(String message) { + super(message, null); + } + + public HgInvalidDataFormatException(String message, Throwable cause) { + super(message, cause); + } +} diff -r 063b0663495a -r 31a89587eb04 src/org/tmatesoft/hg/repo/HgRuntimeException.java --- a/src/org/tmatesoft/hg/repo/HgRuntimeException.java Wed Mar 28 19:34:37 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRuntimeException.java Thu Mar 29 17:14:35 2012 +0200 @@ -17,6 +17,7 @@ package org.tmatesoft.hg.repo; import org.tmatesoft.hg.core.HgException; +import org.tmatesoft.hg.core.HgLibraryFailureException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.ExceptionInfo; import org.tmatesoft.hg.util.Path; @@ -29,9 +30,10 @@ * 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. + * follows different exception strategy, namely checked exceptions rooted at {@link HgException}. * * @see HgException + * @see HgLibraryFailureException * @author Artem Tikhomirov * @author TMate Software Ltd. */ diff -r 063b0663495a -r 31a89587eb04 test/org/tmatesoft/hg/test/MapTagsToFileRevisions.java --- a/test/org/tmatesoft/hg/test/MapTagsToFileRevisions.java Wed Mar 28 19:34:37 2012 +0200 +++ b/test/org/tmatesoft/hg/test/MapTagsToFileRevisions.java Thu Mar 29 17:14:35 2012 +0200 @@ -427,7 +427,7 @@ final HgLogCommand logCommand = new HgLogCommand(repository); logCommand.file(targetPath, true); logCommand.execute(new HgChangesetHandler() { - public void next(HgChangeset changeset) { + public void cset(HgChangeset changeset) { if (changeset.getAffectedFiles().contains(targetPath)) { System.out.println(changeset.getRevisionIndex() + " " + changeSetRevisionToTags.get(changeset.getNodeid())); } diff -r 063b0663495a -r 31a89587eb04 test/org/tmatesoft/hg/test/TestHistory.java --- a/test/org/tmatesoft/hg/test/TestHistory.java Wed Mar 28 19:34:37 2012 +0200 +++ b/test/org/tmatesoft/hg/test/TestHistory.java Thu Mar 29 17:14:35 2012 +0200 @@ -30,10 +30,11 @@ import org.junit.Rule; import org.junit.Test; import org.tmatesoft.hg.core.HgChangeset; +import org.tmatesoft.hg.core.HgChangesetHandler; +import org.tmatesoft.hg.core.HgChangesetHandler.WithCopyHistory; import org.tmatesoft.hg.core.HgFileRevision; import org.tmatesoft.hg.core.HgLogCommand; import org.tmatesoft.hg.core.HgLogCommand.CollectHandler; -import org.tmatesoft.hg.core.HgLogCommand.FileHistoryHandler; import org.tmatesoft.hg.repo.HgLookup; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.test.LogOutputParser.Record; @@ -93,7 +94,7 @@ changelogParser.reset(); eh.run("hg", "log", "--debug", "--follow", f.toString()); - class H extends CollectHandler implements FileHistoryHandler { + class H extends CollectHandler implements HgChangesetHandler.WithCopyHistory { boolean copyReported = false; boolean fromMatched = false; public void copy(HgFileRevision from, HgFileRevision to) { diff -r 063b0663495a -r 31a89587eb04 test/org/tmatesoft/hg/test/TestManifest.java --- a/test/org/tmatesoft/hg/test/TestManifest.java Wed Mar 28 19:34:37 2012 +0200 +++ b/test/org/tmatesoft/hg/test/TestManifest.java Thu Mar 29 17:14:35 2012 +0200 @@ -28,6 +28,7 @@ import org.junit.Rule; import org.junit.Test; +import org.tmatesoft.hg.core.HgManifestHandler; import org.tmatesoft.hg.core.HgFileRevision; import org.tmatesoft.hg.core.HgManifestCommand; import org.tmatesoft.hg.core.Nodeid; @@ -50,7 +51,7 @@ private ManifestOutputParser manifestParser; private ExecHelper eh; final LinkedList revisions = new LinkedList(); - private HgManifestCommand.Handler handler = new HgManifestCommand.Handler() { + private HgManifestHandler handler = new HgManifestHandler() { public void file(HgFileRevision fileRevision) { revisions.add(fileRevision); diff -r 063b0663495a -r 31a89587eb04 test/org/tmatesoft/hg/test/TestStatus.java --- a/test/org/tmatesoft/hg/test/TestStatus.java Wed Mar 28 19:34:37 2012 +0200 +++ b/test/org/tmatesoft/hg/test/TestStatus.java Thu Mar 29 17:14:35 2012 +0200 @@ -182,7 +182,7 @@ private final Map> name2kinds = new TreeMap>(); private final Map name2error = new LinkedHashMap(); - public void handleStatus(HgStatus s) { + public void status(HgStatus s) { List l = kind2names.get(s.getKind()); if (l == null) { kind2names.put(s.getKind(), l = new LinkedList()); @@ -196,7 +196,7 @@ k.add(s.getKind()); } - public void handleError(Path file, Status s) { + public void error(Path file, Status s) { name2error.put(file, s); }