# HG changeset patch # User Artem Tikhomirov # Date 1321935957 -3600 # Node ID 8da7ade36c57607efda814489b7128ea0cb80edf # Parent 6d2c6b2469fcfea82a177d3cb664456c8dc7a1df Add specific IAE subclass to handle wrong (e.g. outdated after rollback) revisions diff -r 6d2c6b2469fc -r 8da7ade36c57 src/org/tmatesoft/hg/core/HgInvalidRevisionException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/HgInvalidRevisionException.java Tue Nov 22 05:25:57 2011 +0100 @@ -0,0 +1,115 @@ +/* + * 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.internal.Experimental; +import org.tmatesoft.hg.repo.HgRepository; + +/** + * Use of revision or local revision index that is not valid for a given revlog. + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +@SuppressWarnings("serial") +@Experimental(reason="1) Whether to use checked or runtime exception is not yet decided. 2) Perhaps, its use not bound to wrong arguments") +public class HgInvalidRevisionException extends IllegalArgumentException { + private Nodeid rev; + private Integer revIdx; + // next two make sense only when revIdx is present + private int rangeLeftBoundary = -1, rangeRightBoundary = -1; + + /** + * + * this exception is not expected to be initialized with another exception, although those who need to, + * may still use {@link #initCause(Throwable)} + * @param message optional description of the issue + * @param revision invalid revision, may be null if revisionIndex is used + * @param revisionIndex invalid revision index, may be null if not known and revision is supplied + */ + public HgInvalidRevisionException(String message, Nodeid revision, Integer revisionIndex) { + super(message); + assert revision != null || revisionIndex != null; + rev = revision; + revIdx = revisionIndex; + } + + public HgInvalidRevisionException(Nodeid revision) { + this(null, revision, null); + } + + public HgInvalidRevisionException(int revisionIndex) { + this(null, null, revisionIndex); + } + + public Nodeid getRevision() { + return rev; + } + + public Integer getRevisionIndex() { + return revIdx; + } + + public HgInvalidRevisionException setRevision(Nodeid revision) { + assert revision != null; + rev = revision; + return this; + } + + // int, not Integer is on purpose, not to clear exception completely + public HgInvalidRevisionException setRevisionIndex(int revisionIndex) { + revIdx = revisionIndex; + return this; + } + + public HgInvalidRevisionException setRevisionIndex(int revisionIndex, int rangeLeft, int rangeRight) { + revIdx = revisionIndex; + rangeLeftBoundary = rangeLeft; + rangeRightBoundary = rangeRight; + return this; + } + + @Override + public String getMessage() { + String msg = super.getMessage(); + if (msg != null) { + return msg; + } + StringBuilder sb = new StringBuilder(); + sb.append('['); + if (rev != null) { + sb.append(rev.shortNotation()); + sb.append(' '); + } + if (revIdx != null) { + String sr; + switch (revIdx) { + case HgRepository.BAD_REVISION : sr = "UNKNOWN"; break; + case HgRepository.TIP : sr = "TIP"; break; + case HgRepository.WORKING_COPY: sr = "WORKING-COPY"; break; + default : sr = revIdx.toString(); + } + if (rangeLeftBoundary != -1 || rangeRightBoundary != -1) { + sb.append(String.format("%s is not from [%d..%d]", sr, rangeLeftBoundary, rangeRightBoundary)); + } else { + sb.append(sr); + } + } + sb.append(']'); + return sb.toString(); + } +} diff -r 6d2c6b2469fc -r 8da7ade36c57 src/org/tmatesoft/hg/internal/RevlogStream.java --- a/src/org/tmatesoft/hg/internal/RevlogStream.java Tue Nov 22 04:02:37 2011 +0100 +++ b/src/org/tmatesoft/hg/internal/RevlogStream.java Tue Nov 22 05:25:57 2011 +0100 @@ -24,6 +24,7 @@ import java.util.zip.Inflater; import org.tmatesoft.hg.core.HgBadStateException; +import org.tmatesoft.hg.core.HgInvalidRevisionException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.repo.HgInternals; import org.tmatesoft.hg.repo.HgRepository; @@ -105,13 +106,13 @@ /** * @throws HgBadStateException if internal read operation failed */ - public byte[] nodeid(int revision) { + public byte[] nodeid(int revision) throws HgInvalidRevisionException { final int indexSize = revisionCount(); if (revision == TIP) { revision = indexSize - 1; } if (revision < 0 || revision >= indexSize) { - throw new IllegalArgumentException(Integer.toString(revision)); + throw new HgInvalidRevisionException(revision).setRevisionIndex(revision, 0, indexSize); } DataAccess daIndex = getIndexStream(); try { @@ -191,7 +192,7 @@ // should be possible to use TIP, ALL, or -1, -2, -n notation of Hg // ? boolean needsNodeid - public void iterate(int start, int end, boolean needData, Inspector inspector) { + public void iterate(int start, int end, boolean needData, Inspector inspector) throws HgInvalidRevisionException { initOutline(); final int indexSize = revisionCount(); if (indexSize == 0) { @@ -224,13 +225,16 @@ * @param needData whether inspector needs access to header only * @param inspector callback to process entries */ - public void iterate(int[] sortedRevisions, boolean needData, Inspector inspector) { + public void iterate(int[] sortedRevisions, boolean needData, Inspector inspector) throws HgInvalidRevisionException { final int indexSize = revisionCount(); if (indexSize == 0 || sortedRevisions.length == 0) { return; } - if (sortedRevisions[0] > indexSize || sortedRevisions[sortedRevisions.length - 1] > indexSize) { - throw new IllegalArgumentException(String.format("Can't iterate [%d, %d] in range [0..%d]", sortedRevisions[0], sortedRevisions[sortedRevisions.length - 1], indexSize)); + if (sortedRevisions[0] > indexSize) { + throw new HgInvalidRevisionException(String.format("Can't iterate [%d, %d] in range [0..%d]", sortedRevisions[0], sortedRevisions[sortedRevisions.length - 1], indexSize), null, sortedRevisions[0]); + } + if (sortedRevisions[sortedRevisions.length - 1] > indexSize) { + throw new HgInvalidRevisionException(String.format("Can't iterate [%d, %d] in range [0..%d]", sortedRevisions[0], sortedRevisions[sortedRevisions.length - 1], indexSize), null, sortedRevisions[sortedRevisions.length - 1]); } ReaderN1 r = new ReaderN1(needData, inspector); diff -r 6d2c6b2469fc -r 8da7ade36c57 src/org/tmatesoft/hg/repo/HgDataFile.java --- a/src/org/tmatesoft/hg/repo/HgDataFile.java Tue Nov 22 04:02:37 2011 +0100 +++ b/src/org/tmatesoft/hg/repo/HgDataFile.java Tue Nov 22 05:25:57 2011 +0100 @@ -33,6 +33,7 @@ import org.tmatesoft.hg.core.HgDataStreamException; import org.tmatesoft.hg.core.HgException; +import org.tmatesoft.hg.core.HgInvalidRevisionException; import org.tmatesoft.hg.core.HgLogCommand; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.DataAccess; @@ -177,7 +178,7 @@ // } /*XXX not sure distinct method contentWithFilters() is the best way to do, perhaps, callers shall add filters themselves?*/ - public void contentWithFilters(int revision, ByteChannel sink) throws HgDataStreamException, CancelledException { + public void contentWithFilters(int revision, ByteChannel sink) throws HgDataStreamException, CancelledException, HgInvalidRevisionException { if (revision == WORKING_COPY) { workingCopy(sink); // pass un-mangled sink } else { @@ -187,7 +188,7 @@ // for data files need to check heading of the file content for possible metadata // @see http://mercurial.selenic.com/wiki/FileFormats#data.2BAC8- - public void content(int revision, ByteChannel sink) throws HgDataStreamException, CancelledException { + public void content(int revision, ByteChannel sink) throws HgDataStreamException, CancelledException, HgInvalidRevisionException { if (revision == TIP) { revision = getLastRevision(); } @@ -198,7 +199,7 @@ return; } if (wrongLocalRevision(revision) || revision == BAD_REVISION) { - throw new IllegalArgumentException(String.valueOf(revision)); + throw new HgInvalidRevisionException(revision); } if (sink == null) { throw new IllegalArgumentException(); @@ -350,7 +351,7 @@ history(0, getLastRevision(), inspector); } - public void history(int start, int end, HgChangelog.Inspector inspector) { + public void history(int start, int end, HgChangelog.Inspector inspector) throws HgInvalidRevisionException { if (!exists()) { throw new IllegalStateException("Can't get history of invalid repository file node"); } diff -r 6d2c6b2469fc -r 8da7ade36c57 src/org/tmatesoft/hg/repo/HgInternals.java --- a/src/org/tmatesoft/hg/repo/HgInternals.java Tue Nov 22 04:02:37 2011 +0100 +++ b/src/org/tmatesoft/hg/repo/HgInternals.java Tue Nov 22 05:25:57 2011 +0100 @@ -25,6 +25,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; +import org.tmatesoft.hg.core.HgInvalidRevisionException; import org.tmatesoft.hg.core.SessionContext; import org.tmatesoft.hg.internal.Experimental; import org.tmatesoft.hg.internal.RelativePathRewrite; @@ -139,13 +140,15 @@ return rev < 0 && rev != TIP && rev != WORKING_COPY && rev != BAD_REVISION; } - // throws IllegalArgumentException if [start..end] range is not a subrange of [0..lastRevision] - public static void checkRevlogRange(int start, int end, int lastRevision) { + // 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) { - throw new IllegalArgumentException(String.format("Bad left range boundary %d in [0..%d]", start, lastRevision)); + final String m = String.format("Bad left range boundary %d in [0..%d]", start, lastRevision); + throw new HgInvalidRevisionException(m, null, start).setRevisionIndex(start, 0, lastRevision); } if (end < 0 || end > lastRevision) { - throw new IllegalArgumentException(String.format("Bad right range boundary %d in [0..%d]", end, lastRevision)); + final String m = String.format("Bad right range boundary %d in [0..%d]", end, lastRevision); + throw new HgInvalidRevisionException(m, null, end).setRevisionIndex(end, 0, lastRevision); } if (end < start) { throw new IllegalArgumentException(String.format("Bad range [%d..%d]", start, end)); diff -r 6d2c6b2469fc -r 8da7ade36c57 src/org/tmatesoft/hg/repo/Revlog.java --- a/src/org/tmatesoft/hg/repo/Revlog.java Tue Nov 22 04:02:37 2011 +0100 +++ b/src/org/tmatesoft/hg/repo/Revlog.java Tue Nov 22 05:25:57 2011 +0100 @@ -30,6 +30,7 @@ import org.tmatesoft.hg.core.HgBadStateException; import org.tmatesoft.hg.core.HgException; +import org.tmatesoft.hg.core.HgInvalidRevisionException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.ArrayHelper; import org.tmatesoft.hg.internal.DataAccess; @@ -86,7 +87,7 @@ return content.revisionCount() - 1; } - public final Nodeid getRevision(int revision) { + public final Nodeid getRevision(int revision) throws HgInvalidRevisionException { // XXX cache nodeids? Rather, if context.getCache(this).getRevisionMap(create == false) != null, use it return Nodeid.fromBinary(content.nodeid(revision), 0); } @@ -94,14 +95,14 @@ /** * FIXME need to be careful about (1) ordering of the revisions in the return list; (2) modifications (sorting) of the argument array */ - public final List getRevisions(int... revisions) { + public final List getRevisions(int... revisions) throws HgInvalidRevisionException { ArrayList rv = new ArrayList(revisions.length); Arrays.sort(revisions); getRevisionsInternal(rv, revisions); return rv; } - /*package-local*/ void getRevisionsInternal(final List retVal, int[] sortedRevs) { + /*package-local*/ void getRevisionsInternal(final List retVal, int[] sortedRevs) throws HgInvalidRevisionException { // once I have getRevisionMap and may find out whether it is avalable from cache, // may use it, perhaps only for small number of revisions content.iterate(sortedRevs, false, new RevlogStream.Inspector() { @@ -114,17 +115,19 @@ /** * Get local revision number (index) of the specified revision. + * If unsure, use {@link #isKnown(Nodeid)} to find out whether nodeid belongs to this revlog. * * For occasional queries, this method works with decent performance, despite its O(n/2) approach. * Alternatively, if you need to perform multiple queries (e.g. at least 15-20), {@link RevisionMap} may come handy. - * + * * @param nid revision to look up - * @return revision index, or {@link HgRepository#BAD_REVISION} if specified revision not found. + * @return revision local index in this revlog + * @throws HgInvalidRevisionException if supplied nodeid doesn't identify any revision from this revlog */ - public final int getLocalRevision(Nodeid nid) { + public final int getLocalRevision(Nodeid nid) throws HgInvalidRevisionException { int revision = content.findLocalRevisionNumber(nid); if (revision == BAD_REVISION) { - throw new IllegalArgumentException(String.format("%s doesn't represent a revision of %s", nid.toString(), this /*XXX HgDataFile.getPath might be more suitable here*/)); + throw new HgInvalidRevisionException(String.format("Bad revision of %s", this /*XXX HgDataFile.getPath might be more suitable here*/), nid, null); } return revision; } @@ -146,14 +149,14 @@ * Access to revision data as is (decompressed, but otherwise unprocessed, i.e. not parsed for e.g. changeset or manifest entries) * @param nodeid */ - protected void rawContent(Nodeid nodeid, ByteChannel sink) throws HgException, IOException, CancelledException { + protected void rawContent(Nodeid nodeid, ByteChannel sink) throws HgException, IOException, CancelledException, HgInvalidRevisionException { rawContent(getLocalRevision(nodeid), sink); } /** * @param revision - repo-local index of this file change (not a changelog revision number!) */ - protected void rawContent(int revision, ByteChannel sink) throws HgException, IOException, CancelledException { + protected void rawContent(int revision, ByteChannel sink) throws HgException, IOException, CancelledException, HgInvalidRevisionException { if (sink == null) { throw new IllegalArgumentException(); } @@ -170,11 +173,12 @@ * @param parentRevisions - int[2] to get local revision numbers of parents (e.g. {6, -1}) * @param parent1 - byte[20] or null, if parent's nodeid is not needed * @param parent2 - byte[20] or null, if second parent's nodeid is not needed - * @return + * @throws HgInvalidRevisionException + * @throws IllegalArgumentException */ - public void parents(int revision, int[] parentRevisions, byte[] parent1, byte[] parent2) { + public void parents(int revision, int[] parentRevisions, byte[] parent1, byte[] parent2) throws HgInvalidRevisionException { if (revision != TIP && !(revision >= 0 && revision < content.revisionCount())) { - throw new IllegalArgumentException(String.valueOf(revision)); + throw new HgInvalidRevisionException(revision); } if (parentRevisions == null || parentRevisions.length < 2) { throw new IllegalArgumentException(String.valueOf(parentRevisions)); @@ -221,7 +225,7 @@ } @Experimental - public void walk(int start, int end, final Revlog.Inspector inspector) { + public void walk(int start, int end, final Revlog.Inspector inspector) throws HgInvalidRevisionException { int lastRev = getLastRevision(); if (start == TIP) { start = lastRev;