# HG changeset patch # User Artem Tikhomirov # Date 1298496988 -3600 # Node ID 1a7a9a20e1f9a901d29df13f35f8c18f2a7d1343 # Parent a05145db4d0c6675004c61d5d73b5cc3a13478de Exceptions, javadoc. Initial cancel and progress support diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/core/HgBadStateException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/HgBadStateException.java Wed Feb 23 22:36:28 2011 +0100 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2011 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; + +/** + * hg4j's own internal error or unexpected state. + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +@SuppressWarnings("serial") +public class HgBadStateException extends RuntimeException { + +} diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/core/HgCatCommand.java --- a/src/org/tmatesoft/hg/core/HgCatCommand.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/core/HgCatCommand.java Wed Feb 23 22:36:28 2011 +0100 @@ -16,14 +16,17 @@ */ package org.tmatesoft.hg.core; +import static org.tmatesoft.hg.repo.HgInternals.wrongLocalRevision; import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION; import static org.tmatesoft.hg.repo.HgRepository.TIP; import java.io.FileNotFoundException; +import java.io.IOException; import org.tmatesoft.hg.repo.HgDataFile; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.util.ByteChannel; +import org.tmatesoft.hg.util.CancelledException; import org.tmatesoft.hg.util.Path; /** @@ -43,25 +46,59 @@ repo = hgRepo; } + /** + * File to read, required parameter + * @param fname path to a repository file, can't be null + * @return this for convenience + * @throws IllegalArgumentException if supplied fname is null or points to directory + */ public HgCatCommand file(Path fname) { + if (fname == null || fname.isDirectory()) { + throw new IllegalArgumentException(String.valueOf(fname)); + } file = fname; return this; } - // rev can't be WORKING_COPY (if allowed, need to implement in #execute()) + /** + * Invocation of this method clears revision set with {@link #revision(Nodeid)} or {@link #revision(int)} earlier. + * XXX rev can't be WORKING_COPY (if allowed, need to implement in #execute()) + * @param rev local revision number, non-negative, or one of predefined constants. Note, use of {@link HgRepository#BAD_REVISION}, + * although possible, makes little sense (command would fail if executed). + * @return this for convenience + */ public HgCatCommand revision(int rev) { + if (wrongLocalRevision(rev)) { + throw new IllegalArgumentException(String.valueOf(rev)); + } localRevision = rev; revision = null; return this; } + /** + * Select revision to read. Invocation of this method clears revision set with {@link #revision(int)} or {@link #revision(Nodeid)} earlier. + * + * @param nodeid - unique revision identifier, Note, use of null or {@link Nodeid#NULL} is senseless + * @return this for convenience + */ public HgCatCommand revision(Nodeid nodeid) { + if (nodeid != null && nodeid.isNull()) { + nodeid = null; + } revision = nodeid; localRevision = BAD_REVISION; return this; } - public void execute(ByteChannel sink) throws Exception /*TODO own exception type*/ { + /** + * Runs the command with current set of parameters and pipes data to provided sink. + * + * @param sink output channel to write data to. + * @throws HgDataStreamException + * @throws IllegalArgumentException when command arguments are incomplete or wrong + */ + public void execute(ByteChannel sink) throws HgDataStreamException, IOException, CancelledException { if (localRevision == BAD_REVISION && revision == null) { throw new IllegalArgumentException("Either local file revision number or nodeid shall be specified"); } @@ -69,11 +106,11 @@ throw new IllegalArgumentException("Name of the file is missing"); } if (sink == null) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Need an output channel"); } HgDataFile dataFile = repo.getFileNode(file); if (!dataFile.exists()) { - throw new FileNotFoundException(); + throw new HgDataStreamException(file.toString(), new FileNotFoundException(file.toString())); } int revToExtract; if (revision != null) { diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/core/HgChangeset.java --- a/src/org/tmatesoft/hg/core/HgChangeset.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/core/HgChangeset.java Wed Feb 23 22:36:28 2011 +0100 @@ -157,14 +157,14 @@ for (Path s : r.getModified()) { Nodeid nid = r.nodeidAfterChange(s); if (nid == null) { - throw new IllegalArgumentException(); + throw new HgBadStateException(); } modified.add(new FileRevision(repo, nid, s)); } for (Path s : r.getAdded()) { Nodeid nid = r.nodeidAfterChange(s); if (nid == null) { - throw new IllegalArgumentException(); + throw new HgBadStateException(); } added.add(new FileRevision(repo, nid, s)); } diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/core/HgDataStreamException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/HgDataStreamException.java Wed Feb 23 22:36:28 2011 +0100 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 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.HgDataFile; + +/** + * Any erroneous state with @link {@link HgDataFile} input/output, read/write operations + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +@SuppressWarnings("serial") +public class HgDataStreamException extends HgException { + + public HgDataStreamException(String message, Throwable cause) { + super(message, cause); + } +} diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/core/HgException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/HgException.java Wed Feb 23 22:36:28 2011 +0100 @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2011 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; + +/** + * Root class for all hg4j exceptions. + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +@SuppressWarnings("serial") +public class HgException extends Exception { + + public HgException(String reason) { + super(reason); + } + + public HgException(String reason, Throwable cause) { + super(reason, cause); + } + + public HgException(Throwable cause) { + super(cause); + } + +// /* XXX CONSIDER capability to pass extra information about errors */ +// public static class Status { +// public Status(String message, Throwable cause, int errorCode, Object extraData) { +// } +// } +} diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/core/HgLogCommand.java --- a/src/org/tmatesoft/hg/core/HgLogCommand.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/core/HgLogCommand.java Wed Feb 23 22:36:28 2011 +0100 @@ -66,9 +66,11 @@ } /** - * Limit search to specified user. Multiple user names may be specified. + * Limit search to specified user. Multiple user names may be specified. Once set, user names can't be + * cleared, use new command instance in such cases. * @param user - full or partial name of the user, case-insensitive, non-null. * @return this instance for convenience + * @throws IllegalArgumentException when argument is null */ public HgLogCommand user(String user) { if (user == null) { @@ -83,9 +85,11 @@ /** * Limit search to specified branch. Multiple branch specification possible (changeset from any of these - * would be included in result). If unspecified, all branches are considered. + * would be included in result). If unspecified, all branches are considered. There's no way to clean branch selection + * once set, create fresh new command instead. * @param branch - branch name, case-sensitive, non-null. * @return this instance for convenience + * @throws IllegalArgumentException when branch argument is null */ public HgLogCommand branch(String branch) { if (branch == null) { @@ -120,8 +124,8 @@ /** * Limit to specified subset of Changelog, [min(rev1,rev2), max(rev1,rev2)], inclusive. * Revision may be specified with {@link HgRepository#TIP} - * @param rev1 - * @param rev2 + * @param rev1 - local revision number + * @param rev2 - local revision number * @return this instance for convenience */ public HgLogCommand range(int rev1, int rev2) { @@ -291,7 +295,8 @@ /*package-local*/FileRevision(HgRepository hgRepo, Nodeid rev, Path p) { if (hgRepo == null || rev == null || p == null) { - throw new IllegalArgumentException(); + // since it's package local, it is our code to blame for non validated arguments + throw new HgBadStateException(); } repo = hgRepo; revision = rev; diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/core/HgManifestCommand.java --- a/src/org/tmatesoft/hg/core/HgManifestCommand.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/core/HgManifestCommand.java Wed Feb 23 22:36:28 2011 +0100 @@ -53,6 +53,13 @@ repo = hgRepo; } + /** + * Parameterize command to visit revisions [rev1..rev2]. + * @param rev1 - local revision number to start from. Non-negative. May be {@link HgRepository#TIP} (rev2 argument shall be {@link HgRepository#TIP} as well, then) + * @param rev2 - local revision number to end with, inclusive. Non-negative, greater or equal to rev1. May be {@link HgRepository#TIP}. + * @return this for convenience. + * @throws IllegalArgumentException if revision arguments are incorrect (see above). + */ public HgManifestCommand range(int rev1, int rev2) { // XXX if manifest range is different from that of changelog, need conversion utils (external?) boolean badArgs = rev1 == BAD_REVISION || rev2 == BAD_REVISION || rev1 == WORKING_COPY || rev2 == WORKING_COPY; @@ -88,6 +95,12 @@ return this; } + /** + * Runs the command. + * @param handler - callback to get the outcome + * @throws IllegalArgumentException if handler is null + * @throws ConcurrentModificationException if this command is already in use (running) + */ public void execute(Handler handler) { if (handler == null) { throw new IllegalArgumentException(); diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/core/HgRepoFacade.java --- a/src/org/tmatesoft/hg/core/HgRepoFacade.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/core/HgRepoFacade.java Wed Feb 23 22:36:28 2011 +0100 @@ -23,6 +23,14 @@ /** * Starting point for the library. + *

Sample use: + *

+ *  HgRepoFacade f = new HgRepoFacade();
+ *  f.initFrom(System.getenv("whatever.repo.location"));
+ *  HgStatusCommand statusCmd = f.createStatusCommand();
+ *  HgStatusCommand.Handler handler = ...;
+ *  statusCmd.execute(handler);
+ * 
* * @author Artem Tikhomirov * @author TMate Software Ltd. @@ -33,6 +41,11 @@ public HgRepoFacade() { } + /** + * @param hgRepo + * @return true on successful initialization + * @throws IllegalArgumentException when argument is null + */ public boolean init(HgRepository hgRepo) { if (hgRepo == null) { throw new IllegalArgumentException(); @@ -41,13 +54,29 @@ return !repo.isInvalid(); } - public boolean init() throws Exception /*FIXME RepoInitException*/ { + /** + * Tries to find repository starting from the current working directory. + * @return true if found valid repository + * @throws HgException in case of errors during repository initialization + */ + public boolean init() throws HgException { repo = new HgLookup().detectFromWorkingDir(); return repo != null && !repo.isInvalid(); } - public boolean initFrom(File repoLocation) throws Exception { - repo = new HgLookup().detect(repoLocation.getCanonicalPath()); + /** + * Looks up Mercurial repository starting from specified location and up to filesystem root. + * + * @param repoLocation path to any folder within structure of a Mercurial repository. + * @return true if found valid repository + * @throws HgException if there are errors accessing specified location + * @throws IllegalArgumentException if argument is null + */ + public boolean initFrom(File repoLocation) throws HgException { + if (repoLocation == null) { + throw new IllegalArgumentException(); + } + repo = new HgLookup().detect(repoLocation); return repo != null && !repo.isInvalid(); } diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/core/HgStatusCommand.java --- a/src/org/tmatesoft/hg/core/HgStatusCommand.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/core/HgStatusCommand.java Wed Feb 23 22:36:28 2011 +0100 @@ -17,6 +17,7 @@ package org.tmatesoft.hg.core; import static org.tmatesoft.hg.core.HgStatus.Kind.*; +import static org.tmatesoft.hg.repo.HgInternals.wrongLocalRevision; import static org.tmatesoft.hg.repo.HgRepository.*; import java.util.ConcurrentModificationException; @@ -93,15 +94,15 @@ } /** - * if set, either base:revision or base:workingdir + * If set, either base:revision or base:workingdir * to unset, pass {@link HgRepository#TIP} or {@link HgRepository#BAD_REVISION} - * @param revision - * @return + * @param revision - local revision number to base status from + * @return this for convenience + * @throws IllegalArgumentException when revision is negative or {@link HgRepository#WORKING_COPY} */ - public HgStatusCommand base(int revision) { - if (revision == WORKING_COPY) { - throw new IllegalArgumentException(); + if (revision == WORKING_COPY || wrongLocalRevision(revision)) { + throw new IllegalArgumentException(String.valueOf(revision)); } if (revision == BAD_REVISION) { revision = TIP; @@ -113,14 +114,15 @@ /** * Revision without base == --change * Pass {@link HgRepository#WORKING_COPY} or {@link HgRepository#BAD_REVISION} to reset - * @param revision - * @return + * @param revision - non-negative local revision number, or any of {@link HgRepository#BAD_REVISION}, {@link HgRepository#WORKING_COPY} or {@link HgRepository#TIP} + * @return this for convenience + * @throws IllegalArgumentException if local revision number doesn't specify legitimate revision. */ public HgStatusCommand revision(int revision) { if (revision == BAD_REVISION) { revision = WORKING_COPY; } - if (revision != TIP && revision != WORKING_COPY && revision < 0) { + if (wrongLocalRevision(revision)) { throw new IllegalArgumentException(String.valueOf(revision)); } endRevision = revision; @@ -131,14 +133,19 @@ * Shorthand for {@link #base(int) cmd.base(BAD_REVISION)}{@link #change(int) .revision(revision)} * * @param revision compare given revision against its parent - * @return + * @return this for convenience */ public HgStatusCommand change(int revision) { base(BAD_REVISION); return revision(revision); } - // pass null to reset + /** + * Limit status operation to certain sub-tree. + * + * @param pathMatcher - matcher to use, pass null/ to reset + * @return this for convenience + */ public HgStatusCommand match(Path.Matcher pathMatcher) { mediator.matcher = pathMatcher; return this; diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/core/Nodeid.java --- a/src/org/tmatesoft/hg/core/Nodeid.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/core/Nodeid.java Wed Feb 23 22:36:28 2011 +0100 @@ -41,7 +41,8 @@ /** * @param binaryRepresentation - array of exactly 20 bytes - * @param shallClone - true if array is subject to future modification and shall be copied, not referenced + * @param shallClone - true if array is subject to future modification and shall be copied, not referenced + * @throws IllegalArgumentException if supplied binary representation doesn't correspond to 20 bytes of sha1 digest */ public Nodeid(byte[] binaryRepresentation, boolean shallClone) { // 5 int fields => 32 bytes @@ -98,8 +99,14 @@ return binaryData.clone(); } - // primary difference with cons is handling of NULL id (this method returns constant) - // always makes a copy of an array passed + /** + * Factory for {@link Nodeid Nodeids}. + * Primary difference with cons is handling of NULL id (this method returns constant) and control over array + * duplication - this method always makes a copy of an array passed + * @param binaryRepresentation - byte array of a length at least offset + 20 + * @param offset - index in the array to start from + * @throws IllegalArgumentException when arguments don't select 20 bytes + */ public static Nodeid fromBinary(byte[] binaryRepresentation, int offset) { if (binaryRepresentation == null || binaryRepresentation.length - offset < 20) { throw new IllegalArgumentException(); @@ -117,6 +124,13 @@ return new Nodeid(b, false); } + /** + * Parse encoded representation. + * + * @param asciiRepresentation - encoded form of the Nodeid. + * @return object representation + * @throws IllegalArgumentException when argument doesn't match encoded form of 20-bytes sha1 digest. + */ public static Nodeid fromAscii(String asciiRepresentation) { if (asciiRepresentation.length() != 40) { throw new IllegalArgumentException(); @@ -124,6 +138,10 @@ // XXX is better impl for String possible? return fromAscii(asciiRepresentation.getBytes(), 0, 40); } + + /** + * Parse encoded representation. Similar to {@link #fromAscii(String)}. + */ public static Nodeid fromAscii(byte[] asciiRepresentation, int offset, int length) { if (length != 40) { throw new IllegalArgumentException(); diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/internal/ByteArrayChannel.java --- a/src/org/tmatesoft/hg/internal/ByteArrayChannel.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/internal/ByteArrayChannel.java Wed Feb 23 22:36:28 2011 +0100 @@ -49,7 +49,7 @@ } // TODO document what happens on write after toArray() in each case - public int write(ByteBuffer buffer) throws Exception { + public int write(ByteBuffer buffer) { int rv = buffer.remaining(); if (buffers == null) { target.put(buffer); diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/internal/FilterByteChannel.java --- a/src/org/tmatesoft/hg/internal/FilterByteChannel.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/internal/FilterByteChannel.java Wed Feb 23 22:36:28 2011 +0100 @@ -16,10 +16,12 @@ */ package org.tmatesoft.hg.internal; +import java.io.IOException; import java.nio.ByteBuffer; import java.util.Collection; import org.tmatesoft.hg.util.ByteChannel; +import org.tmatesoft.hg.util.CancelledException; /** * @@ -38,7 +40,7 @@ filters = filtersToApply.toArray(new Filter[filtersToApply.size()]); } - public int write(ByteBuffer buffer) throws Exception { + public int write(ByteBuffer buffer) throws IOException, CancelledException { final int srcPos = buffer.position(); ByteBuffer processed = buffer; for (Filter f : filters) { diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/repo/HgDataFile.java --- a/src/org/tmatesoft/hg/repo/HgDataFile.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/repo/HgDataFile.java Wed Feb 23 22:36:28 2011 +0100 @@ -16,18 +16,26 @@ */ package org.tmatesoft.hg.repo; +import static org.tmatesoft.hg.repo.HgInternals.wrongLocalRevision; +import static org.tmatesoft.hg.repo.HgRepository.*; import static org.tmatesoft.hg.repo.HgRepository.TIP; +import static org.tmatesoft.hg.repo.HgRepository.WORKING_COPY; +import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.TreeMap; +import org.tmatesoft.hg.core.HgDataStreamException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.FilterByteChannel; import org.tmatesoft.hg.internal.RevlogStream; import org.tmatesoft.hg.util.ByteChannel; +import org.tmatesoft.hg.util.CancelSupport; +import org.tmatesoft.hg.util.CancelledException; import org.tmatesoft.hg.util.Path; +import org.tmatesoft.hg.util.ProgressSupport; @@ -75,21 +83,28 @@ } /*XXX not sure applyFilters is the best way to do, perhaps, callers shall add filters themselves?*/ - public void content(int revision, ByteChannel sink, boolean applyFilters) throws /*TODO typed*/Exception { + public void content(int revision, ByteChannel sink, boolean applyFilters) throws HgDataStreamException, IOException, CancelledException { byte[] content = content(revision); + final CancelSupport cancelSupport = CancelSupport.Factory.get(sink); + final ProgressSupport progressSupport = ProgressSupport.Factory.get(sink); ByteBuffer buf = ByteBuffer.allocate(512); int left = content.length; + progressSupport.start(left); int offset = 0; + cancelSupport.checkCancelled(); ByteChannel _sink = applyFilters ? new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath())) : sink; do { buf.put(content, offset, Math.min(left, buf.remaining())); buf.flip(); + cancelSupport.checkCancelled(); // XXX I may not rely on returned number of bytes but track change in buf position instead. int consumed = _sink.write(buf); buf.compact(); offset += consumed; left -= consumed; + progressSupport.worked(consumed); } while (left > 0); + progressSupport.done(); // XXX shall specify whether #done() is invoked always or only if completed successfully. } // for data files need to check heading of the file content for possible metadata @@ -99,6 +114,9 @@ if (revision == TIP) { revision = getLastRevision(); } + if (wrongLocalRevision(revision) || revision == BAD_REVISION || revision == WORKING_COPY) { + throw new IllegalArgumentException(String.valueOf(revision)); + } byte[] data = super.content(revision); if (metadata == null) { metadata = new Metadata(); diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/repo/HgInternals.java --- a/src/org/tmatesoft/hg/repo/HgInternals.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/repo/HgInternals.java Wed Feb 23 22:36:28 2011 +0100 @@ -16,6 +16,8 @@ */ package org.tmatesoft.hg.repo; +import static org.tmatesoft.hg.repo.HgRepository.*; + import java.io.File; import java.net.InetAddress; import java.net.UnknownHostException; @@ -84,4 +86,9 @@ return username; } } + + // Convenient check of local revision number for validity (not all negative values are wrong as long as we use negative constants) + public static boolean wrongLocalRevision(int rev) { + return rev < 0 && rev != TIP && rev != WORKING_COPY && rev != BAD_REVISION; + } } diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/repo/HgLookup.java --- a/src/org/tmatesoft/hg/repo/HgLookup.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/repo/HgLookup.java Wed Feb 23 22:36:28 2011 +0100 @@ -17,24 +17,28 @@ package org.tmatesoft.hg.repo; import java.io.File; +import java.io.IOException; + +import org.tmatesoft.hg.core.HgException; /** + * Utility methods to find Mercurial repository at a given location * * @author Artem Tikhomirov * @author TMate Software Ltd. */ public class HgLookup { - public HgRepository detectFromWorkingDir() throws Exception { + public HgRepository detectFromWorkingDir() throws HgException { return detect(System.getProperty("user.dir")); } - public HgRepository detect(String location) throws Exception /*FIXME Exception type, RepoInitException? */ { + public HgRepository detect(String location) throws HgException { return detect(new File(location)); } // look up in specified location and above - public HgRepository detect(File location) throws Exception { + public HgRepository detect(File location) throws HgException { File dir = location; File repository; do { @@ -50,6 +54,11 @@ // return invalid repository return new HgRepository(location.getPath()); } - return new HgRepository(repository); + try { + String repoPath = repository.getParentFile().getCanonicalPath(); + return new HgRepository(repoPath, repository); + } catch (IOException ex) { + throw new HgException(location.toString(), ex); + } } } diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/repo/HgRepository.java --- a/src/org/tmatesoft/hg/repo/HgRepository.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/repo/HgRepository.java Wed Feb 23 22:36:28 2011 +0100 @@ -45,6 +45,7 @@ */ public final class HgRepository { + // if new constants added, consider fixing HgInternals#badLocalRevision public static final int TIP = -1; public static final int BAD_REVISION = Integer.MIN_VALUE; public static final int WORKING_COPY = -2; @@ -53,7 +54,7 @@ public static IllegalStateException notImplemented() { return new IllegalStateException("Not implemented"); } - + private final File repoDir; // .hg folder private final String repoLocation; private final DataAccessProvider dataAccess; @@ -79,10 +80,12 @@ normalizePath = null; } - HgRepository(File repositoryRoot) throws IOException { + HgRepository(String repositoryPath, File repositoryRoot) { assert ".hg".equals(repositoryRoot.getName()) && repositoryRoot.isDirectory(); + assert repositoryPath != null; + assert repositoryRoot != null; repoDir = repositoryRoot; - repoLocation = repositoryRoot.getParentFile().getCanonicalPath(); + repoLocation = repositoryPath; dataAccess = new DataAccessProvider(); final boolean runningOnWindows = System.getProperty("os.name").indexOf("Windows") != -1; if (runningOnWindows) { diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java --- a/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java Wed Feb 23 22:36:28 2011 +0100 @@ -262,7 +262,7 @@ ByteChannel check = new ByteChannel() { int x = 0; final boolean debug = false; // XXX may want to add global variable to allow clients to turn - public int write(ByteBuffer buffer) throws Exception { + public int write(ByteBuffer buffer) { for (int i = buffer.remaining(); i > 0; i--, x++) { if (data[x] != buffer.get()) { if (debug) { diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/util/Adaptable.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/util/Adaptable.java Wed Feb 23 22:36:28 2011 +0100 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2011 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.util; + +/** + * Extension mechanism. + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public interface Adaptable { + + T getAdapter(Class adapterClass); +} diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/util/ByteChannel.java --- a/src/org/tmatesoft/hg/util/ByteChannel.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/util/ByteChannel.java Wed Feb 23 22:36:28 2011 +0100 @@ -16,6 +16,7 @@ */ package org.tmatesoft.hg.util; +import java.io.IOException; import java.nio.ByteBuffer; /** @@ -30,5 +31,5 @@ // XXX does int return value makes any sense given buffer keeps its read state // not clear what retvalue should be in case some filtering happened inside write - i.e. return // number of bytes consumed in - int write(ByteBuffer buffer) throws Exception /*FIXME Exception type*/; + int write(ByteBuffer buffer) throws IOException, CancelledException; } diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/util/CancelSupport.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/util/CancelSupport.java Wed Feb 23 22:36:28 2011 +0100 @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2011 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.util; + +/** + * Mix-in for objects that support cancellation. + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public interface CancelSupport { + + /** + * This method is invoked to check if target had been brought to canceled state. Shall silently return if target is + * in regular state. + * @throws CancelledException when target internal state has been changed to canceled. + */ + void checkCancelled() throws CancelledException; + + + // Yeah, this factory class looks silly now, but perhaps in the future I'll need wrappers for other cancellation sources? + // just don't want to have general Utils class with methods like get() below + static class Factory { + + /** + * Obtain non-null cancel support object. + * + * @param target any object (or null) that might have cancel support + * @return target if it's capable checking cancellation status or no-op implementation that never cancels. + */ + public static CancelSupport get(Object target) { + if (target instanceof CancelSupport) { + return (CancelSupport) target; + } + if (target instanceof Adaptable) { + CancelSupport cs = ((Adaptable) target).getAdapter(CancelSupport.class); + if (cs != null) { + return cs; + } + } + return new CancelSupport() { + public void checkCancelled() { + } + }; + } + } +} diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/util/CancelledException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/util/CancelledException.java Wed Feb 23 22:36:28 2011 +0100 @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011 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.util; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +@SuppressWarnings("serial") +public class CancelledException extends Exception { + + public CancelledException() { + } +} diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/util/Path.java --- a/src/org/tmatesoft/hg/util/Path.java Tue Feb 22 15:49:26 2011 +0100 +++ b/src/org/tmatesoft/hg/util/Path.java Wed Feb 23 22:36:28 2011 +0100 @@ -31,6 +31,17 @@ path = p; } + /** + * Check if this is directory's path. + * Note, this method doesn't perform any file system operation. + * + * @return true when this path points to a directory + */ + public boolean isDirectory() { + // XXX simple logic for now. Later we may decide to have an explicit factory method to create directory paths + return path.charAt(path.length() - 1) == '/'; + } + public int length() { return path.length(); } diff -r a05145db4d0c -r 1a7a9a20e1f9 src/org/tmatesoft/hg/util/ProgressSupport.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/util/ProgressSupport.java Wed Feb 23 22:36:28 2011 +0100 @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2011 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.util; + +/** + * Mix-in to report progress of a long-running operation + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public interface ProgressSupport { + + public void start(int totalUnits); + public void worked(int units); + public void done(); + + static class Factory { + + /** + * @param target object that might be capable to report progress. Can be null + * @return support object extracted from target or an empty, no-op implementation + */ + public static ProgressSupport get(Object target) { + if (target instanceof ProgressSupport) { + return (ProgressSupport) target; + } + if (target instanceof Adaptable) { + ProgressSupport ps = ((Adaptable) target).getAdapter(ProgressSupport.class); + if (ps != null) { + return ps; + } + } + return new ProgressSupport() { + public void start(int totalUnits) { + } + public void worked(int units) { + } + public void done() { + } + }; + } + } +}