# HG changeset patch # User Artem Tikhomirov # Date 1330955451 -3600 # Node ID 2747b0723867c73ccd95a1ce522e2f32a87ce91a # Parent 1fcc7f7b6d6546c1261b39701ebe7ffce5277a87 FIXMEs: work on exceptions and javadoc diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/core/ChangesetTransformer.java --- a/src/org/tmatesoft/hg/core/ChangesetTransformer.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/core/ChangesetTransformer.java Mon Mar 05 14:50:51 2012 +0100 @@ -108,7 +108,8 @@ changeset = new HgChangeset(statusCollector, pp); changeset.setParentHelper(pw); } - + + // FIXME document instance reuse policy HgChangeset handle(int revisionNumber, Nodeid nodeid, RawChangeset cset) { changeset.init(revisionNumber, nodeid, cset); return changeset; diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/core/HgBadStateException.java --- a/src/org/tmatesoft/hg/core/HgBadStateException.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgBadStateException.java Mon Mar 05 14:50:51 2012 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 TMate Software Ltd + * Copyright (c) 2011-2012 TMate Software Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ /** * hg4j's own internal error or unexpected state. + * XXX unless there's anything additional, there's not too much value in this class * * @author Artem Tikhomirov * @author TMate Software Ltd. @@ -25,11 +26,6 @@ @SuppressWarnings("serial") public class HgBadStateException extends RuntimeException { - // FIXME quick-n-dirty fix, don't allow exceptions without a cause - public HgBadStateException() { - super("Internal error"); - } - public HgBadStateException(String message) { super(message); } diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/core/HgCallbackTargetException.java --- a/src/org/tmatesoft/hg/core/HgCallbackTargetException.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgCallbackTargetException.java Mon Mar 05 14:50:51 2012 +0100 @@ -67,7 +67,7 @@ sb.append("Original exception thrown: "); sb.append(getCause().getClass().getName()); sb.append(" at "); - appendDetails(sb); + extras.appendDetails(sb); return sb.toString(); } diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/core/HgChangeset.java --- a/src/org/tmatesoft/hg/core/HgChangeset.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgChangeset.java Mon Mar 05 14:50:51 2012 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 TMate Software Ltd + * Copyright (c) 2011-2012 TMate Software Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -78,18 +78,52 @@ } } + /** + * Index of the changeset in local repository. Note, this number is relevant only for local repositories/operations, use + * {@link Nodeid nodeid} to uniquely identify a revision. + * + * @return index of the changeset revision + */ + public int getRevisionIndex() { + return revNumber; + } + + /** + * @deprecated use {@link #getRevisionIndex()} + */ + @Deprecated public int getRevision() { return revNumber; } + + /** + * Unique identity of this changeset revision + * @return revision identifier, never null + */ public Nodeid getNodeid() { return nodeid; } + + /** + * Name of the user who made this commit + * @return author of the commit, never null + */ public String getUser() { return changeset.user(); } + + /** + * Commit description + * @return content of the corresponding field in changeset record; empty string if none specified. + */ public String getComment() { return changeset.comment(); } + + /** + * Name of the branch this commit was made in. Returns "default" for main branch. + * @return name of the branch, non-empty string + */ public String getBranch() { return changeset.branch(); } @@ -100,10 +134,26 @@ public HgDate getDate() { return new HgDate(changeset.date().getTime(), changeset.timezone()); } + + /** + * Indicates revision of manifest that tracks state of repository at the moment of this commit. + * Note, may be {@link Nodeid#NULL} in certain scenarios (e.g. first changeset in an empty repository, usually by bogus tools) + * + * @return revision identifier, never null + */ public Nodeid getManifestRevision() { return changeset.manifest(); } + /** + * Lists names of files affected by this commit, as recorded in the changeset itself. Unlike {@link #getAddedFiles()}, + * {@link #getModifiedFiles()} and {@link #getRemovedFiles()}, this method doesn't analyze actual changes done + * in the commit, rather extracts value from the changeset record. + * + * List returned by this method may be empty, while aforementioned methods may produce non-empty result. + * + * @return list of filenames, never null + */ public List getAffectedFiles() { // reports files as recorded in changelog. Note, merge revisions may have no // files listed, and thus this method would return empty list, while @@ -116,21 +166,42 @@ return rv; } - public List getModifiedFiles() throws HgInvalidControlFileException { + /** + * Figures out files and specific revisions thereof that were modified in this commit + * + * @return revisions of files modified in this commit + * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgException in case of some other library issue + */ + public List getModifiedFiles() throws HgException { if (modifiedFiles == null) { initFileChanges(); } return modifiedFiles; } - public List getAddedFiles() throws HgInvalidControlFileException { + /** + * Figures out files added in this commit + * + * @return revisions of files added in this commit + * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgException in case of some other library issue + */ + public List getAddedFiles() throws HgException { if (addedFiles == null) { initFileChanges(); } return addedFiles; } - public List getRemovedFiles() throws HgInvalidControlFileException { + /** + * Figures out files that were deleted as part of this commit + * + * @return revisions of files deleted in this commit + * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgException in case of some other library issue + */ + public List getRemovedFiles() throws HgException { if (deletedFiles == null) { initFileChanges(); } @@ -175,6 +246,9 @@ return Nodeid.fromBinary(parent2, 0); } + /** + * Create a copy of this changeset + */ @Override public HgChangeset clone() { try { @@ -186,7 +260,7 @@ } } - private /*synchronized*/ void initFileChanges() throws HgInvalidControlFileException { + private /*synchronized*/ void initFileChanges() throws HgException { ArrayList deleted = new ArrayList(); ArrayList modified = new ArrayList(); ArrayList added = new ArrayList(); @@ -196,7 +270,7 @@ for (Path s : r.getModified()) { Nodeid nid = r.nodeidAfterChange(s); if (nid == null) { - throw new HgBadStateException(); + throw new HgException(String.format("For the file %s recorded as modified couldn't find revision after change", s)); } modified.add(new HgFileRevision(repo, nid, s, null)); } @@ -204,7 +278,7 @@ for (Path s : r.getAdded()) { Nodeid nid = r.nodeidAfterChange(s); if (nid == null) { - throw new HgBadStateException(); + throw new HgException(String.format("For the file %s recorded as added couldn't find revision after change", s)); } added.add(new HgFileRevision(repo, nid, s, copied.get(s))); } diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/core/HgChangesetTreeHandler.java --- a/src/org/tmatesoft/hg/core/HgChangesetTreeHandler.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgChangesetTreeHandler.java Mon Mar 05 14:50:51 2012 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 TMate Software Ltd + * Copyright (c) 2011-2012 TMate Software Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,7 +22,10 @@ import org.tmatesoft.hg.util.Pair; /** - * + * Handler to iterate file history (generally, any revlog) with access to parent-child relations between changesets. + * + * @see HgLogCommand#execute(HgChangesetTreeHandler) + * * @author Artem Tikhomirov * @author TMate Software Ltd. */ @@ -30,55 +33,60 @@ /** * @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 HgException allows implementers propagate errors from {@link TreeElement} or other parts of the library. * @throws HgCallbackTargetException.Wrap wrapper object for any exception user code may produce. Wrapped exception would get re-thrown with {@link HgCallbackTargetException} * @throws CancelledException if execution of the operation was cancelled */ - public void next(HgChangesetTreeHandler.TreeElement entry) throws HgCallbackTargetException.Wrap, CancelledException; + public void next(HgChangesetTreeHandler.TreeElement entry) throws HgException, HgCallbackTargetException.Wrap, CancelledException; interface TreeElement { /** * Revision of the revlog being iterated. For example, when walking file history, return value represents file revisions. * * @return revision of the revlog being iterated. + * @throws HgException to indicate failure dealing with Mercurial data */ - public Nodeid fileRevision(); + public Nodeid fileRevision() throws HgException; /** * @return changeset associated with the current revision - * @throws HgException indicates failure dealing with Mercurial data + * @throws HgException to indicate failure dealing with Mercurial data */ public HgChangeset changeset() throws HgException; /** * Lightweight alternative to {@link #changeset()}, identifies changeset in which current file node has been modified - * @return changeset {@link Nodeid} + * @return changeset {@link Nodeid revision} + * @throws HgException to indicate failure dealing with Mercurial data */ - public Nodeid changesetRevision(); + public Nodeid changesetRevision() throws HgException; /** * Node, these are not necessarily in direct relation to parents of changeset from {@link #changeset()} * @return changesets that correspond to parents of the current file node, either pair element may be null. - * @throws HgException indicates failure dealing with Mercurial data + * @throws HgException to indicate failure dealing with Mercurial data */ public Pair parents() throws HgException; /** * Lightweight alternative to {@link #parents()}, give {@link Nodeid nodeids} only * @return two values, neither is null, use {@link Nodeid#isNull()} to identify parent not set + * @throws HgException to indicate failure dealing with Mercurial data */ - public Pair parentRevisions(); + public Pair parentRevisions() throws HgException; /** * Changes that originate from the given change and bear it as their parent. * @return collection (possibly empty) of immediate children of the change - * @throws HgException indicates failure dealing with Mercurial data + * @throws HgException to indicate failure dealing with Mercurial data */ public Collection children() throws HgException; /** * Lightweight alternative to {@link #children()}. * @return never null + * @throws HgException to indicate failure dealing with Mercurial data */ - public Collection childRevisions(); + public Collection childRevisions() throws HgException; } } \ No newline at end of file diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/core/HgException.java --- a/src/org/tmatesoft/hg/core/HgException.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgException.java Mon Mar 05 14:50:51 2012 +0100 @@ -16,8 +16,7 @@ */ package org.tmatesoft.hg.core; -import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION; - +import org.tmatesoft.hg.internal.ExceptionInfo; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.util.Path; @@ -29,10 +28,8 @@ */ @SuppressWarnings("serial") public class HgException extends Exception { - - protected int revNumber = BAD_REVISION; - protected Nodeid revision; - protected Path filename; + + protected final ExceptionInfo extras = new ExceptionInfo(this); public HgException(String reason) { super(reason); @@ -50,7 +47,7 @@ * @return not {@link HgRepository#BAD_REVISION} only when revision index was supplied at the construction time */ public int getRevisionIndex() { - return revNumber; + return extras.getRevisionIndex(); } /** @@ -62,12 +59,11 @@ } public HgException setRevisionIndex(int rev) { - revNumber = rev; - return this; + return extras.setRevisionIndex(rev); } public boolean isRevisionIndexSet() { - return revNumber != BAD_REVISION; + return extras.isRevisionIndexSet(); } /** @@ -82,48 +78,26 @@ * @return non-null only when revision was supplied at construction time */ public Nodeid getRevision() { - return revision; + return extras.getRevision(); } public HgException setRevision(Nodeid r) { - revision = r; - return this; + return extras.setRevision(r); } public boolean isRevisionSet() { - return revision != null; + return extras.isRevisionSet(); } /** * @return non-null only if file name was set at construction time */ public Path getFileName() { - return filename; + return extras.getFileName(); } public HgException setFileName(Path name) { - filename = name; - return this; - } - - protected void appendDetails(StringBuilder sb) { - if (filename != null) { - sb.append("path:'"); - sb.append(filename); - sb.append('\''); - sb.append(';'); - sb.append(' '); - } - sb.append("rev:"); - if (revNumber != BAD_REVISION) { - sb.append(revNumber); - if (revision != null) { - sb.append(':'); - } - } - if (revision != null) { - sb.append(revision.shortNotation()); - } + return extras.setFileName(name); } @Override @@ -131,7 +105,7 @@ StringBuilder sb = new StringBuilder(super.toString()); sb.append(' '); sb.append('('); - appendDetails(sb); + extras.appendDetails(sb); sb.append(')'); return sb.toString(); } diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/core/HgInvalidFileException.java --- a/src/org/tmatesoft/hg/core/HgInvalidFileException.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgInvalidFileException.java Mon Mar 05 14:50:51 2012 +0100 @@ -36,20 +36,18 @@ @SuppressWarnings("serial") public class HgInvalidFileException extends HgException { - private File localFile; - public HgInvalidFileException(String message, Throwable th) { super(message, th); } public HgInvalidFileException(String message, Throwable th, File file) { super(message, th); - localFile = file; // allows null + extras.setFile(file); // allows null } public HgInvalidFileException setFile(File file) { assert file != null; // doesn't allow null not to clear file accidentally - localFile = file; + extras.setFile(file); return this; } @@ -57,23 +55,6 @@ * @return file object that causes troubles, or null if specific file is unknown */ public File getFile() { - return localFile; - } - - @Override - protected void appendDetails(StringBuilder sb) { - super.appendDetails(sb); - if (localFile != null) { - sb.append(';'); - sb.append(' '); - sb.append(" file:"); - sb.append(localFile.getPath()); - sb.append(','); - if (localFile.exists()) { - sb.append("EXISTS"); - } else { - sb.append("DOESN'T EXIST"); - } - } + return extras.getFile(); } } diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/core/HgLogCommand.java --- a/src/org/tmatesoft/hg/core/HgLogCommand.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgLogCommand.java Mon Mar 05 14:50:51 2012 +0100 @@ -513,22 +513,22 @@ } void populate(HgChangeset cs) { - cachedChangesets.put(cs.getRevision(), cs); + cachedChangesets.put(cs.getRevisionIndex(), cs); } - private HgChangeset[] get(int... changelogRevisionNumber) throws HgInvalidControlFileException { - HgChangeset[] rv = new HgChangeset[changelogRevisionNumber.length]; - IntVector misses = new IntVector(changelogRevisionNumber.length, -1); - for (int i = 0; i < changelogRevisionNumber.length; i++) { - if (changelogRevisionNumber[i] == -1) { + private HgChangeset[] get(int... changelogRevisionIndex) throws HgException { + HgChangeset[] rv = new HgChangeset[changelogRevisionIndex.length]; + IntVector misses = new IntVector(changelogRevisionIndex.length, -1); + for (int i = 0; i < changelogRevisionIndex.length; i++) { + if (changelogRevisionIndex[i] == -1) { rv[i] = null; continue; } - HgChangeset cached = cachedChangesets.get(changelogRevisionNumber[i]); + HgChangeset cached = cachedChangesets.get(changelogRevisionIndex[i]); if (cached != null) { rv[i] = cached; } else { - misses.add(changelogRevisionNumber[i]); + misses.add(changelogRevisionIndex[i]); } } if (misses.size() > 0) { @@ -537,20 +537,23 @@ repo.getChangelog().range(this, changesets2read); for (int changeset2read : changesets2read) { HgChangeset cs = cachedChangesets.get(changeset2read); - if (cs == null) { - throw new HgBadStateException(); + if (cs == null) { + throw new HgException(String.format("Can't get changeset for revision %d", changeset2read)); + } + // HgChangelog.range may reorder changesets according to their order in the changelog + // thus need to find original index + boolean sanity = false; + for (int i = 0; i < changelogRevisionIndex.length; i++) { + if (changelogRevisionIndex[i] == cs.getRevisionIndex()) { + rv[i] = cs; + sanity = true; + break; } - // HgChangelog.range may reorder changesets according to their order in the changelog - // thus need to find original index - boolean sanity = false; - for (int i = 0; i < changelogRevisionNumber.length; i++) { - if (changelogRevisionNumber[i] == cs.getRevision()) { - rv[i] = cs; - sanity = true; - break; - } - } - assert sanity; + } + if (!sanity) { + HgInternals.getContext(repo).getLog().error(getClass(), "Index of revision %d:%s doesn't match any of requested", cs.getRevisionIndex(), cs.getNodeid().shortNotation()); + } + assert sanity; } } return rv; @@ -568,14 +571,14 @@ populate(cs.clone()); } - public Nodeid changesetRevision() { + public Nodeid changesetRevision() throws HgException { if (changesetRevision == null) { changesetRevision = getRevision(historyNode.changeset); } return changesetRevision; } - public Pair parentRevisions() { + public Pair parentRevisions() throws HgException { if (parentRevisions == null) { HistoryNode p; final Nodeid p1, p2; @@ -594,7 +597,7 @@ return parentRevisions; } - public Collection childRevisions() { + public Collection childRevisions() throws HgException { if (childRevisions != null) { return childRevisions; } @@ -611,19 +614,13 @@ } // reading nodeid involves reading index only, guess, can afford not to optimize multiple reads - private Nodeid getRevision(int changelogRevisionNumber) { - // XXX pipe through pool + private Nodeid getRevision(int changelogRevisionNumber) throws HgInvalidControlFileException { + // TODO [post-1.0] pipe through pool HgChangeset cs = cachedChangesets.get(changelogRevisionNumber); if (cs != null) { return cs.getNodeid(); } else { - try { - return repo.getChangelog().getRevision(changelogRevisionNumber); - } catch (HgException ex) { - HgInternals.getContext(repo).getLog().error(getClass(), ex, null); - // FIXME propagate, perhaps? - return Nodeid.NULL; // FIXME this is quick-n-dirty hack to move forward with introducing exceptions - } + return repo.getChangelog().getRevision(changelogRevisionNumber); } } } diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/internal/ExceptionInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/internal/ExceptionInfo.java Mon Mar 05 14:50:51 2012 +0100 @@ -0,0 +1,136 @@ +/* + * 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.internal; + +import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION; + +import java.io.File; + +import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.util.Path; + +/** + * Extras to record with exception to describe it better. + * XXX perhaps, not only with exception, may utilize it with status object? + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class ExceptionInfo { + protected final T owner; + protected int revNumber = BAD_REVISION; + protected Nodeid revision; + protected Path filename; + protected File localFile; + + /** + * @param owner instance to return from setters + */ + public ExceptionInfo(T owner) { + this.owner = owner; + } + + /** + * @return not {@link HgRepository#BAD_REVISION} only when revision index was supplied at the construction time + */ + public int getRevisionIndex() { + return revNumber; + } + + public T setRevisionIndex(int rev) { + revNumber = rev; + return owner; + } + + public boolean isRevisionIndexSet() { + return revNumber != BAD_REVISION; + } + + /** + * @return non-null only when revision was supplied at construction time + */ + public Nodeid getRevision() { + return revision; + } + + public T setRevision(Nodeid r) { + revision = r; + return owner; + } + + public boolean isRevisionSet() { + return revision != null; + } + + /** + * @return non-null only if file name was set at construction time + */ + public Path getFileName() { + return filename; + } + + public T setFileName(Path name) { + filename = name; + return owner; + } + + public T setFile(File file) { + localFile = file; + return owner; + } + + /** + * @return file object that causes troubles, or null if specific file is unknown + */ + public File getFile() { + return localFile; + } + + public StringBuilder appendDetails(StringBuilder sb) { + if (filename != null) { + sb.append("path:'"); + sb.append(filename); + sb.append('\''); + sb.append(';'); + sb.append(' '); + } + sb.append("rev:"); + if (revNumber != BAD_REVISION) { + sb.append(revNumber); + if (revision != null) { + sb.append(':'); + } + } + if (revision != null) { + sb.append(revision.shortNotation()); + } + if (localFile != null) { + sb.append(';'); + sb.append(' '); + sb.append(" file:"); + sb.append(localFile.getPath()); + sb.append(','); + if (localFile.exists()) { + sb.append("EXISTS"); + } else { + sb.append("DOESN'T EXIST"); + } + } + return sb; + } +} diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/repo/HgInternals.java --- a/src/org/tmatesoft/hg/repo/HgInternals.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgInternals.java Mon Mar 05 14:50:51 2012 +0100 @@ -128,17 +128,18 @@ return new FileWalker(repoRoot, pathSrc, workindDirScope); } - // expose othewise package-local information primarily to use in our own o.t.hg.core package + // expose otherwise package-local information primarily to use in our own o.t.hg.core package public static SessionContext getContext(HgRepository repo) { return repo.getContext(); } // Convenient check of revision index for validity (not all negative values are wrong as long as we use negative constants) - public static boolean wrongRevisionIndex(int rev) { + public static boolean wrongRevisionIndex(int rev) { // FIXME guess, usages shall throw HgInvalidRevision. \ + // TODO Another method to check,throw and expand TIP at once return rev < 0 && rev != TIP && rev != WORKING_COPY && rev != BAD_REVISION; } - + // throws HgInvalidRevisionException or IllegalArgumentException if [start..end] range is not a subrange of [0..lastRevision] public static void checkRevlogRange(int start, int end, int lastRevision) throws HgInvalidRevisionException { if (start < 0 || start > lastRevision) { diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/repo/HgManifest.java --- a/src/org/tmatesoft/hg/repo/HgManifest.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgManifest.java Mon Mar 05 14:50:51 2012 +0100 @@ -30,6 +30,7 @@ import org.tmatesoft.hg.core.HgBadStateException; import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.HgInvalidControlFileException; +import org.tmatesoft.hg.core.HgInvalidRevisionException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.DataAccess; import org.tmatesoft.hg.internal.DigestHelper; @@ -129,9 +130,11 @@ * @param start changelog (not manifest!) revision to begin with * @param end changelog (not manifest!) revision to stop, inclusive. * @param inspector manifest revision visitor, can't be null + * @throws HgInvalidRevisionException if start or end specify non-existent revision index + * @throws IllegalArgumentException if start or end is not a revision index * @throws HgInvalidControlFileException if access to revlog index/data entry failed */ - public void walk(int start, int end, final Inspector inspector) throws /*FIXME HgInvalidRevisionException,*/ HgInvalidControlFileException { + public void walk(int start, int end, final Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException { if (inspector == null) { throw new IllegalArgumentException(); } @@ -139,13 +142,13 @@ int manifestFirst, manifestLast, i = 0; do { manifestFirst = fromChangelog(csetFirst+i); - if (manifestFirst == -1) { + if (manifestFirst == BAD_REVISION) { inspector.begin(BAD_REVISION, NULL, csetFirst+i); inspector.end(BAD_REVISION); } i++; - } while (manifestFirst == -1 && csetFirst+i <= csetLast); - if (manifestFirst == -1) { + } while (manifestFirst == BAD_REVISION && csetFirst+i <= csetLast); + if (manifestFirst == BAD_REVISION) { getRepo().getContext().getLog().info(getClass(), "None of changesets [%d..%d] have associated manifest revision", csetFirst, csetLast); // we ran through all revisions in [start..end] and none of them had manifest. // we reported that to inspector and proceeding is done now. @@ -154,13 +157,13 @@ i = 0; do { manifestLast = fromChangelog(csetLast-i); - if (manifestLast == -1) { + if (manifestLast == BAD_REVISION) { inspector.begin(BAD_REVISION, NULL, csetLast-i); inspector.end(BAD_REVISION); } i++; - } while (manifestLast == -1 && csetLast-i >= csetFirst); - if (manifestLast == -1) { + } while (manifestLast == BAD_REVISION && csetLast-i >= csetFirst); + if (manifestLast == BAD_REVISION) { // hmm, manifestFirst != -1 here, hence there's i from [csetFirst..csetLast] for which manifest entry exists, // and thus it's impossible to run into manifestLast == -1. Nevertheless, never hurts to check. throw new HgBadStateException(String.format("Manifest %d-%d(!) for cset range [%d..%d] ", manifestFirst, manifestLast, csetFirst, csetLast)); @@ -183,8 +186,10 @@ * * @param inspector manifest revision visitor, can't be null * @param revisionIndexes local indexes of changesets to visit, non-null + * @throws HgInvalidRevisionException if argument specifies non-existent revision index + * @throws HgInvalidControlFileException if access to revlog index/data entry failed */ - public void walk(final Inspector inspector, int... revisionIndexes) throws HgInvalidControlFileException{ + public void walk(final Inspector inspector, int... revisionIndexes) throws HgInvalidRevisionException, HgInvalidControlFileException { if (inspector == null || revisionIndexes == null) { throw new IllegalArgumentException(); } @@ -194,10 +199,14 @@ // /** - * Tells manifest revision number that corresponds to the given changeset. - * @return manifest revision index, or -1 if changeset has no associated manifest (cset records NULL nodeid for manifest) + * Tells manifest revision number that corresponds to the given changeset. May return {@link HgRepository#BAD_REVISION} + * if changeset has no associated manifest (cset records NULL nodeid for manifest). + * @return manifest revision index, non-negative, or {@link HgRepository#BAD_REVISION}. + * @throws HgInvalidRevisionException if method argument specifies non-existent revision index + * @throws IllegalArgumentException if argument is not a revision index + * @throws HgInvalidControlFileException if access to revlog index/data entry failed */ - /*package-local*/ int fromChangelog(int changesetRevisionIndex) throws HgInvalidControlFileException { + /*package-local*/ int fromChangelog(int changesetRevisionIndex) throws HgInvalidRevisionException, HgInvalidControlFileException { if (HgInternals.wrongRevisionIndex(changesetRevisionIndex)) { throw new IllegalArgumentException(String.valueOf(changesetRevisionIndex)); } @@ -218,16 +227,18 @@ * @param changelogRevisionIndex local changeset index * @param file path to file in question * @return file revision or null if manifest at specified revision doesn't list such file + * @throws HgInvalidRevisionException if method argument specifies non-existent revision index + * @throws HgInvalidControlFileException if access to revlog index/data entry failed */ @Experimental(reason="Perhaps, HgDataFile shall own this method, or get a delegate?") - public Nodeid getFileRevision(int changelogRevisionIndex, final Path file) throws HgInvalidControlFileException{ + public Nodeid getFileRevision(int changelogRevisionIndex, final Path file) throws HgInvalidRevisionException, HgInvalidControlFileException { return getFileRevisions(file, changelogRevisionIndex).get(changelogRevisionIndex); } // XXX package-local, IntMap, and HgDataFile getFileRevisionAt(int... localChangelogRevisions) @Experimental(reason="@see #getFileRevision") - public Map getFileRevisions(final Path file, int... changelogRevisionIndexes) throws HgInvalidControlFileException{ - // FIXME need tests + public Map getFileRevisions(final Path file, int... changelogRevisionIndexes) throws HgInvalidRevisionException, HgInvalidControlFileException { + // TODO need tests int[] manifestRevisionIndexes = toManifestRevisionIndexes(changelogRevisionIndexes, null); final HashMap rv = new HashMap(changelogRevisionIndexes.length); content.iterate(manifestRevisionIndexes, true, new RevlogStream.Inspector() { @@ -267,14 +278,17 @@ /** * @param changelogRevisionIndexes non-null * @param inspector may be null if reporting of missing manifests is not needed + * @throws HgInvalidRevisionException if arguments specify non-existent revision index + * @throws IllegalArgumentException if any index argument is not a revision index + * @throws HgInvalidControlFileException if access to revlog index/data entry failed */ - private int[] toManifestRevisionIndexes(int[] changelogRevisionIndexes, Inspector inspector) throws HgInvalidControlFileException { + private int[] toManifestRevisionIndexes(int[] changelogRevisionIndexes, Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException { int[] manifestRevs = new int[changelogRevisionIndexes.length]; boolean needsSort = false; int j = 0; for (int i = 0; i < changelogRevisionIndexes.length; i++) { final int manifestRevisionIndex = fromChangelog(changelogRevisionIndexes[i]); - if (manifestRevisionIndex == -1) { + if (manifestRevisionIndex == BAD_REVISION) { if (inspector != null) { inspector.begin(BAD_REVISION, NULL, changelogRevisionIndexes[i]); inspector.end(BAD_REVISION); @@ -498,24 +512,32 @@ private static class RevisionMapper implements RevlogStream.Inspector, Lifecycle { - private final int changelogRevisions; + private final int changelogRevisionCount; private int[] changelog2manifest; private final HgRepository repo; public RevisionMapper(HgRepository hgRepo) { repo = hgRepo; - changelogRevisions = repo.getChangelog().getRevisionCount(); + changelogRevisionCount = repo.getChangelog().getRevisionCount(); } - // respects TIP - public int at(int revisionNumber) { - if (revisionNumber == TIP) { - revisionNumber = changelogRevisions - 1; + /** + * Get index of manifest revision that corresponds to specified changeset + * @param changesetRevisionIndex non-negative index of changelog revision, or {@link HgRepository#TIP} + * @return index of manifest revision, or {@link HgRepository#BAD_REVISION} if changeset doesn't reference a valid manifest + * @throws HgInvalidRevisionException if method argument specifies non-existent revision index + */ + public int at(int changesetRevisionIndex) throws HgInvalidRevisionException { + if (changesetRevisionIndex == TIP) { + changesetRevisionIndex = changelogRevisionCount - 1; + } + if (changesetRevisionIndex >= changelogRevisionCount) { + throw new HgInvalidRevisionException(changesetRevisionIndex); } if (changelog2manifest != null) { - return changelog2manifest[revisionNumber]; + return changelog2manifest[changesetRevisionIndex]; } - return revisionNumber; + return changesetRevisionIndex; } // XXX likely can be replaced with Revlog.RevisionInspector @@ -524,12 +546,12 @@ // next assertion is not an error, rather assumption check, which is too development-related to be explicit exception - // I just wonder if there are manifests that have two entries pointing to single changeset. It seems unrealistic, though - // changeset records one and only one manifest nodeid - assert changelog2manifest[linkRevision] == -1 : String.format("revision:%d, link:%d, already linked to revision:%d", revisionNumber, linkRevision, changelog2manifest[linkRevision]); + assert changelog2manifest[linkRevision] == BAD_REVISION : String.format("revision:%d, link:%d, already linked to revision:%d", revisionNumber, linkRevision, changelog2manifest[linkRevision]); changelog2manifest[linkRevision] = revisionNumber; } else { if (revisionNumber != linkRevision) { - changelog2manifest = new int[changelogRevisions]; - Arrays.fill(changelog2manifest, -1); + changelog2manifest = new int[changelogRevisionCount]; + Arrays.fill(changelog2manifest, BAD_REVISION); for (int i = 0; i < revisionNumber; changelog2manifest[i] = i, i++) ; changelog2manifest[linkRevision] = revisionNumber; @@ -538,14 +560,14 @@ } public void start(int count, Callback callback, Object token) { - if (count != changelogRevisions) { - assert count < changelogRevisions; // no idea what to do if manifest has more revisions than changelog + if (count != changelogRevisionCount) { + assert count < changelogRevisionCount; // no idea what to do if manifest has more revisions than changelog // the way how manifest may contain more revisions than changelog, as I can imagine, is a result of // some kind of an import tool (e.g. from SVN or CVS), that creates manifest and changelog independently. // Note, it's pure guess, I didn't see such repository yet (although the way manifest revisions // in cpython repo are numbered makes me think aforementioned way) - changelog2manifest = new int[changelogRevisions]; - Arrays.fill(changelog2manifest, -1); + changelog2manifest = new int[changelogRevisionCount]; + Arrays.fill(changelog2manifest, BAD_REVISION); } } @@ -556,7 +578,7 @@ // I assume there'd be not too many revisions we don't know manifest of ArrayList undefinedChangelogRevision = new ArrayList(); for (int i = 0; i < changelog2manifest.length; i++) { - if (changelog2manifest[i] == -1) { + if (changelog2manifest[i] == BAD_REVISION) { undefinedChangelogRevision.add(i); } } diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/repo/HgRemoteRepository.java --- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java Mon Mar 05 14:50:51 2012 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 TMate Software Ltd + * Copyright (c) 2011-2012 TMate Software Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -232,12 +232,12 @@ assert currRange == null; assert currRangeList == null; if (!rangeItr.hasNext()) { - throw new HgBadStateException(); + throw new HgBadStateException("Internal error"); } rv.put(rangeItr.next(), Collections.emptyList()); } else { if (currRange == null || currRangeList == null) { - throw new HgBadStateException(); + throw new HgBadStateException("Internal error"); } // indicate next range value is needed currRange = null; @@ -248,7 +248,7 @@ possiblyEmptyNextLine = false; if (currRange == null) { if (!rangeItr.hasNext()) { - throw new HgBadStateException(); + throw new HgBadStateException("Internal error"); } currRange = rangeItr.next(); currRangeList = new LinkedList(); diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/repo/HgRepository.java --- a/src/org/tmatesoft/hg/repo/HgRepository.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgRepository.java Mon Mar 05 14:50:51 2012 +0100 @@ -55,7 +55,7 @@ // if new constants added, consider fixing HgInternals#wrongRevisionIndex public static final int TIP = -3; - public static final int BAD_REVISION = Integer.MIN_VALUE; + public static final int BAD_REVISION = Integer.MIN_VALUE; // XXX INVALID_REVISION? public static final int WORKING_COPY = -2; public static final String DEFAULT_BRANCH_NAME = "default"; diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/repo/HgStatusCollector.java --- a/src/org/tmatesoft/hg/repo/HgStatusCollector.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgStatusCollector.java Mon Mar 05 14:50:51 2012 +0100 @@ -16,8 +16,7 @@ */ package org.tmatesoft.hg.repo; -import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION; -import static org.tmatesoft.hg.repo.HgRepository.TIP; +import static org.tmatesoft.hg.repo.HgRepository.*; import java.util.Collection; import java.util.Collections; @@ -30,6 +29,7 @@ import org.tmatesoft.hg.core.HgBadStateException; import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.HgInvalidControlFileException; +import org.tmatesoft.hg.core.HgInvalidRevisionException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.IntMap; import org.tmatesoft.hg.internal.ManifestRevision; @@ -184,30 +184,51 @@ scope = scopeMatcher == null ? new Path.Matcher.Any() : scopeMatcher; } - // hg status --change - public void change(int rev, HgStatusInspector inspector) throws /*FIXME HInvalidRevisionException,*/ HgInvalidControlFileException { + /** + * 'hg status --change REV' command counterpart. + * + * @throws HgInvalidRevisionException if argument specifies non-existent revision index + * @throws HgInvalidControlFileException if access to revlog index/data entry failed + */ + public void change(int revisionIndex, HgStatusInspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException { int[] parents = new int[2]; - repo.getChangelog().parents(rev, parents, null, null); - walk(parents[0], rev, inspector); + repo.getChangelog().parents(revisionIndex, parents, null, null); + walk(parents[0], revisionIndex, inspector); } - // rev1 and rev2 are changelog revision numbers, argument order matters. - // Either rev1 or rev2 may be -1 to indicate comparison to empty repository (XXX this is due to use of - // parents in #change(), I believe. Perhaps, need a constant for this? Otherwise this hidden knowledge gets - // exposed to e.g. Record - public void walk(int rev1, int rev2, HgStatusInspector inspector) throws /*FIXME HInvalidRevisionException,*/ HgInvalidControlFileException { + /** + * Parameters rev1 and rev2 are changelog revision indexes, shall not be the same. Argument order matters. + * FIXME Either rev1 or rev2 may be -1 to indicate comparison to empty repository (this is due to use of + * parents in #change(), I believe. Perhaps, need a constant for this? Otherwise this hidden knowledge gets + * exposed to e.g. Record + * XXX cancellation? + * + * @param rev1 from changeset index, non-negative or {@link HgRepository#TIP} + * @param rev2 to changeset index, non-negative or {@link HgRepository#TIP} + * @param inspector callback for status information + * @throws HgInvalidRevisionException if any argument specifies non-existent revision index + * @throws IllegalArgumentException inspector other incorrect argument values + * @throws HgInvalidControlFileException if access to revlog index/data entry failed + */ + public void walk(int rev1, int rev2, HgStatusInspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException { if (rev1 == rev2) { throw new IllegalArgumentException(); } if (inspector == null) { throw new IllegalArgumentException(); } - final int lastManifestRevision = repo.getChangelog().getLastRevision(); + final int lastChangelogRevision = repo.getChangelog().getLastRevision(); if (rev1 == TIP) { - rev1 = lastManifestRevision; + rev1 = lastChangelogRevision; } if (rev2 == TIP) { - rev2 = lastManifestRevision; + rev2 = lastChangelogRevision; + } + if (rev1 != -1 && (HgInternals.wrongRevisionIndex(rev1) || rev1 == WORKING_COPY || rev1 == BAD_REVISION || rev1 > lastChangelogRevision)) { + throw new HgInvalidRevisionException(rev1); + } + if (rev2 != -1 && (HgInternals.wrongRevisionIndex(rev2) || rev2 == WORKING_COPY || rev2 == BAD_REVISION || rev2 > lastChangelogRevision)) { + throw new HgInvalidRevisionException(rev2); } if (inspector instanceof Record) { ((Record) inspector).init(rev1, rev2, this); @@ -233,12 +254,12 @@ // which going to be read anyway if (need1) { minRev = rev1; - maxRev = rev1 < lastManifestRevision-5 ? rev1+5 : lastManifestRevision; + maxRev = rev1 < lastChangelogRevision-5 ? rev1+5 : lastChangelogRevision; initCacheRange(minRev, maxRev); } if (need2) { minRev = rev2; - maxRev = rev2 < lastManifestRevision-5 ? rev2+5 : lastManifestRevision; + maxRev = rev2 < lastChangelogRevision-5 ? rev2+5 : lastChangelogRevision; initCacheRange(minRev, maxRev); } } @@ -283,7 +304,16 @@ } } - public Record status(int rev1, int rev2) throws /*FIXME HInvalidRevisionException,*/ HgInvalidControlFileException { + /** + * Collects status between two revisions, changes from rev1 up to rev2. + * + * @param rev1 from changeset index + * @param rev2 to changeset index + * @return information object that describes change between the revisions + * @throws HgInvalidRevisionException if any argument specifies non-existent revision index + * @throws HgInvalidControlFileException if access to revlog index/data entry failed + */ + public Record status(int rev1, int rev2) throws HgInvalidRevisionException, HgInvalidControlFileException { Record rv = new Record(); walk(rev1, rev2, rv); return rv; diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/repo/HgSubrepoLocation.java --- a/src/org/tmatesoft/hg/repo/HgSubrepoLocation.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgSubrepoLocation.java Mon Mar 05 14:50:51 2012 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 TMate Software Ltd + * Copyright (c) 2011-2012 TMate Software Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -88,7 +88,7 @@ public HgRepository getRepo() throws HgInvalidFileException { if (kind != Kind.Hg) { - throw new HgBadStateException(); + throw new HgBadStateException(String.format("Unsupported subrepository %s", kind)); } return new HgLookup().detect(new File(owner.getWorkingDir(), source)); } diff -r 1fcc7f7b6d65 -r 2747b0723867 src/org/tmatesoft/hg/repo/Revlog.java --- a/src/org/tmatesoft/hg/repo/Revlog.java Mon Feb 27 19:38:44 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/Revlog.java Mon Mar 05 14:50:51 2012 +0100 @@ -396,7 +396,7 @@ private void assertSortedIndex(int x) { if (x < 0) { - throw new HgBadStateException(); + throw new HgBadStateException(String.format("Bad index", x)); } }