Mercurial > jhg
changeset 427:31a89587eb04
FIXMEs: consistent names, throws for commands and their handlers. Use of checked exceptions in hi-level api
line wrap: on
line diff
--- 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) {
--- 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);
--- 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<Nodeid> test = new HashSet<Nodeid>(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);
--- 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) { }
--- 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<HgStatus.Kind, List<Path>> data = new EnumMap<HgStatus.Kind, List<Path>>(HgStatus.Kind.class); final Map<Path, Path> copies = showCopies ? new HashMap<Path,Path>() : null; - public void handleStatus(HgStatus s) { + public void status(HgStatus s) { List<Path> l = data.get(s.getKind()); if (l == null) { l = new LinkedList<Path>(); @@ -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); }
--- 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 <int, Nodeid>. 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<int,Nodeid> 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
--- 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);
--- 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; + } }
--- 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. * - * <p>It's intentionally not a subclass of {@link HgException} to avoid get mixed with library own errors and be processed separately. + * <p>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. + * <p>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.
--- 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 (<em>runtime exception</em>) + * @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 {
--- 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()); }
--- 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 <code>true</code> 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(); }
--- 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: <E extends Exception>*/ { +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; + } }
--- 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 { /**
--- 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<HgException> extras = new ExceptionInfo<HgException>(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); }
--- 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 <code>null</code> 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. <em>Runtime exception</em> */ - 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. <em>Runtime exception</em> */ - public Pair<Nodeid, Nodeid> getParents() throws HgInvalidControlFileException { + public Pair<Nodeid, Nodeid> getParents() throws HgRuntimeException { if (parents == null) { HgDataFile fn = repo.getFileNode(path); int revisionIndex = fn.getRevisionIndex(revision);
--- 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<Nodeid> executeLite() throws HgRemoteConnectionException, HgInvalidControlFileException, CancelledException { - LinkedHashSet<Nodeid> result = new LinkedHashSet<Nodeid>(); - RepositoryComparator repoCompare = getComparator(); - for (BranchChain bc : getMissingBranches()) { - List<Nodeid> missing = repoCompare.visitBranches(bc); - HashSet<Nodeid> common = new HashSet<Nodeid>(); // 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<Nodeid> it = missing.iterator(); it.hasNext() && common.contains(it.next()); it.remove()) ; - result.addAll(missing); + public List<Nodeid> executeLite() throws HgException, CancelledException { + try { + LinkedHashSet<Nodeid> result = new LinkedHashSet<Nodeid>(); + RepositoryComparator repoCompare = getComparator(); + for (BranchChain bc : getMissingBranches()) { + List<Nodeid> missing = repoCompare.visitBranches(bc); + HashSet<Nodeid> common = new HashSet<Nodeid>(); // 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<Nodeid> it = missing.iterator(); it.hasNext() && common.contains(it.next()); it.remove()) ; + result.addAll(missing); + } + ArrayList<Nodeid> rv = new ArrayList<Nodeid>(result); + return rv; + } catch (HgRuntimeException ex) { + throw new HgLibraryFailureException(ex); } - ArrayList<Nodeid> rv = new ArrayList<Nodeid>(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(); }
--- /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(); + } +}
--- 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 <code>this</code> instance for convenience */ public HgLogCommand range(int rev1, int rev2) { @@ -159,13 +159,16 @@ * * @param nid changeset revision * @return <code>this</code> 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<HgChangeset> 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<HgChangeset> result = new LinkedList<HgChangeset>(); @@ -417,7 +403,7 @@ return Collections.unmodifiableList(result); } - public void next(HgChangeset changeset) { + public void cset(HgChangeset changeset) { result.add(changeset.clone()); } }
--- 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 <code>null</code> * @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<HgFileRevision> 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<HgFileRevision>(); } - 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<Path, LinkedList<HgFileRevision>> breakDown = new LinkedHashMap<Path, LinkedList<HgFileRevision>>(); - for (HgFileRevision fr : manifestContent) { - Path filePath = fr.getPath(); - Path dirPath = pathPool.parent(filePath); - LinkedList<HgFileRevision> revs = breakDown.get(dirPath); - if (revs == null) { - revs = new LinkedList<HgFileRevision>(); - breakDown.put(dirPath, revs); + try { + if (needDirs) { + LinkedHashMap<Path, LinkedList<HgFileRevision>> breakDown = new LinkedHashMap<Path, LinkedList<HgFileRevision>>(); + for (HgFileRevision fr : manifestContent) { + Path filePath = fr.getPath(); + Path dirPath = pathPool.parent(filePath); + LinkedList<HgFileRevision> revs = breakDown.get(dirPath); + if (revs == null) { + revs = new LinkedList<HgFileRevision>(); + 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; } } }
--- /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
--- 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<Nodeid> executeLite() throws HgRemoteConnectionException, HgInvalidControlFileException, CancelledException { + public List<Nodeid> 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(); }
--- /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. + * <p>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); + } +}
--- 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 <code>null</code> 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());
--- 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 <code>null</code> * @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;
--- 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; }
--- 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 } }
--- 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;
--- 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<String> usersPool) throws HgBadArgumentException { + /* package-local */void init(byte[] data, int offset, int length, Pool<String> 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);
--- /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); + } +}
--- 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. * * <p>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. */
--- 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())); }
--- 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) {
--- 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<HgFileRevision> revisions = new LinkedList<HgFileRevision>(); - private HgManifestCommand.Handler handler = new HgManifestCommand.Handler() { + private HgManifestHandler handler = new HgManifestHandler() { public void file(HgFileRevision fileRevision) { revisions.add(fileRevision);
--- 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<Path, List<Kind>> name2kinds = new TreeMap<Path, List<Kind>>(); private final Map<Path, Status> name2error = new LinkedHashMap<Path, Status>(); - public void handleStatus(HgStatus s) { + public void status(HgStatus s) { List<Path> l = kind2names.get(s.getKind()); if (l == null) { kind2names.put(s.getKind(), l = new LinkedList<Path>()); @@ -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); }