# HG changeset patch # User Artem Tikhomirov # Date 1330194916 -3600 # Node ID deb64baaa4127b0cf6de6ae787812d1aeecba34c # Parent c76c57f6b961308180b12ac5637fa9cc1c84a6a3# Parent fdc1db8f7f614704cf93169cb95d145b770c3511 Merge fixes for issue 25 from smartgit3 branch diff -r fdc1db8f7f61 -r deb64baaa412 .hgtags --- a/.hgtags Sat Feb 25 19:31:57 2012 +0100 +++ b/.hgtags Sat Feb 25 19:35:16 2012 +0100 @@ -1,2 +1,3 @@ c2601c0b4a1fd5940054eec95b92ea719e66cb78 v0.5.0 fc8bc2f1edbe876c66c8fcbfc054bb836c733b06 v0.7.0 +f52ca9530774436ce5a9192e7c5a825a5018b65d v0.8.0 diff -r fdc1db8f7f61 -r deb64baaa412 build.xml --- a/build.xml Sat Feb 25 19:31:57 2012 +0100 +++ b/build.xml Sat Feb 25 19:35:16 2012 +0100 @@ -145,6 +145,7 @@ + diff -r fdc1db8f7f61 -r deb64baaa412 design.txt --- a/design.txt Sat Feb 25 19:31:57 2012 +0100 +++ b/design.txt Sat Feb 25 19:35:16 2012 +0100 @@ -44,6 +44,7 @@ delta merge DataAccess - collect debug info (buffer misses, file size/total read operations) to find out better strategy to buffer size detection. Compare performance. +RevlogStream - inflater buffer (and other buffers) size may be too small for repositories out there (i.e. inflater buffer of 512 bytes for 200k revision) Parameterize StatusCollector to produce copy only when needed. And HgDataFile.metadata perhaps should be moved to cacheable place? diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/core/HgCallbackTargetException.java --- a/src/org/tmatesoft/hg/core/HgCallbackTargetException.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgCallbackTargetException.java Sat Feb 25 19:35:16 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 @@ -29,6 +29,7 @@ * {@link RuntimeException} subclass, {@link Wrap}. Then, unwrap and re-throw with checked {@link HgCallbackTargetException}. * * FIXME REVISIT perhaps, shall just throw HgCallbackTargetException from any handler, and do not catch anything in commands at all? + * FIXME decide whether shall root at HgException ("throws HgException, HgCallbackTargetException" looks a bit odd now) * * @author Artem Tikhomirov * @author TMate Software Ltd. diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/core/HgCatCommand.java --- a/src/org/tmatesoft/hg/core/HgCatCommand.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgCatCommand.java Sat Feb 25 19:35:16 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 @@ -20,7 +20,6 @@ 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 java.nio.ByteBuffer; @@ -132,10 +131,17 @@ * 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 HgBadArgumentException if no target file node found + * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgInvalidFileException if access to file in working directory failed + * @throws HgException in case of some other library issue + * @throws CancelledException if execution of the operation was cancelled + * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (runtime exception) * @throws IllegalArgumentException when command arguments are incomplete or wrong */ - public void execute(ByteChannel sink) throws HgDataStreamException, HgInvalidControlFileException, CancelledException { + public void execute(ByteChannel sink) throws HgException, CancelledException { + // XXX perhaps, IAE together with HgBadArgumentException is not the best idea if (revisionIndex == BAD_REVISION && revision == null && cset == null) { throw new IllegalArgumentException("File revision, corresponing local number, or a changset nodeid shall be specified"); } @@ -147,7 +153,8 @@ } HgDataFile dataFile = repo.getFileNode(file); if (!dataFile.exists()) { - throw new HgDataStreamException(file, new FileNotFoundException(file.toString())); + // TODO may benefit from repo.getStoragePath to print revlog location in addition to human-friendly file path + throw new HgBadArgumentException(String.format("File %s not found in the repository", file), null).setFileName(file); } int revToExtract; if (cset != null) { diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/core/HgDataStreamException.java --- a/src/org/tmatesoft/hg/core/HgDataStreamException.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgDataStreamException.java Sat Feb 25 19:35:16 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 @@ -21,12 +21,14 @@ /** * Any erroneous state with @link {@link HgDataFile} input/output, read/write operations - * FIXME/REVISIT if HgInvalidControlFileExceptio and HgInvalidFileException is not sufficient? Is there real need for all 3? + * + * @deprecated {@link HgInvalidControlFileException} and {@link HgInvalidFileException} deemed sufficient * * @author Artem Tikhomirov * @author TMate Software Ltd. */ @SuppressWarnings("serial") +@Deprecated public class HgDataStreamException extends HgException { public HgDataStreamException(Path file, String message, Throwable cause) { diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/core/HgException.java --- a/src/org/tmatesoft/hg/core/HgException.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgException.java Sat Feb 25 19:35:16 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 @@ -61,12 +61,15 @@ return getRevisionIndex(); } - public HgException setRevisionIndex(int rev) { revNumber = rev; return this; } + public boolean isRevisionIndexSet() { + return revNumber != BAD_REVISION; + } + /** * @deprecated use {@link #setRevisionIndex(int)} */ @@ -86,6 +89,10 @@ revision = r; return this; } + + public boolean isRevisionSet() { + return revision != null; + } /** * @return non-null only if file name was set at construction time @@ -101,7 +108,7 @@ protected void appendDetails(StringBuilder sb) { if (filename != null) { - sb.append("file:'"); + sb.append("path:'"); sb.append(filename); sb.append('\''); sb.append(';'); diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/core/HgFileInformer.java --- a/src/org/tmatesoft/hg/core/HgFileInformer.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgFileInformer.java Sat Feb 25 19:35:16 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,7 +18,6 @@ import org.tmatesoft.hg.internal.ManifestRevision; import org.tmatesoft.hg.repo.HgDataFile; -import org.tmatesoft.hg.repo.HgInternals; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.util.Path; import org.tmatesoft.hg.util.Status; @@ -122,6 +121,7 @@ return checkResult; } Nodeid toExtract = null; + String phaseMsg = "Extract manifest revision failed"; try { if (cachedManifest == null) { int csetRev = repo.getChangelog().getRevisionIndex(cset); @@ -130,6 +130,7 @@ // cachedManifest shall be meaningful - changelog.getRevisionIndex() above ensures we've got version that exists. } toExtract = cachedManifest.nodeid(file); + phaseMsg = "Follow copy/rename failed"; if (toExtract == null && followRenames) { while (toExtract == null && dataFile.isCopy()) { renamed = true; @@ -138,12 +139,8 @@ toExtract = cachedManifest.nodeid(file); } } - } catch (HgInvalidControlFileException ex) { - checkResult = new Status(Status.Kind.ERROR, "", ex); - return checkResult; - } catch (HgDataStreamException ex) { - checkResult = new Status(Status.Kind.ERROR, "Follow copy/rename failed", ex); - HgInternals.getContext(repo).getLog().warn(getClass(), ex, checkResult.getMessage()); + } catch (HgException ex) { + checkResult = new Status(Status.Kind.ERROR, phaseMsg, ex); return checkResult; } if (toExtract != null) { diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/core/HgFileRevision.java --- a/src/org/tmatesoft/hg/core/HgFileRevision.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgFileRevision.java Sat Feb 25 19:35:16 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 @@ -99,13 +99,13 @@ return parents; } - public void putContentTo(ByteChannel sink) throws HgDataStreamException, HgInvalidControlFileException, CancelledException { + public void putContentTo(ByteChannel sink) throws HgException, CancelledException { HgDataFile fn = repo.getFileNode(path); int revisionIndex = fn.getRevisionIndex(revision); fn.contentWithFilters(revisionIndex, sink); } - private void checkCopy() throws HgInvalidControlFileException, HgDataStreamException { + private void checkCopy() throws HgException { HgDataFile fn = repo.getFileNode(path); if (fn.isCopy()) { if (fn.getRevision(0).equals(revision)) { diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/core/HgInvalidControlFileException.java --- a/src/org/tmatesoft/hg/core/HgInvalidControlFileException.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgInvalidControlFileException.java Sat Feb 25 19:35:16 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 @@ -19,17 +19,20 @@ import java.io.File; import org.tmatesoft.hg.internal.Experimental; +import org.tmatesoft.hg.util.Path; /** * WORK IN PROGRESS * * Subclass of {@link HgInvalidFileException} to indicate failure to deal with one of Mercurial control files * (most likely those under .hg/, but also those residing in the repository, with special meaning to the Mercurial, like .hgtags or .hgignore) + * + * XXX Perhaps, HgInvalidRevlogException? * @author Artem Tikhomirov * @author TMate Software Ltd. */ @SuppressWarnings("serial") -@Experimental(reason="WORK IN PROGRESS. Name is likely to change. Path argument to be added?") +@Experimental(reason="WORK IN PROGRESS. Name is likely to change") public class HgInvalidControlFileException extends HgInvalidFileException { public HgInvalidControlFileException(String message, Throwable th, File file) { @@ -44,7 +47,16 @@ @Override public HgInvalidControlFileException setRevision(Nodeid r) { - super.setRevision(r); - return this; + return (HgInvalidControlFileException) super.setRevision(r); + } + + @Override + public HgInvalidControlFileException setRevisionIndex(int rev) { + return (HgInvalidControlFileException) super.setRevisionIndex(rev); + } + + @Override + public HgInvalidControlFileException setFileName(Path name) { + return (HgInvalidControlFileException) super.setFileName(name); } } diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/core/HgInvalidFileException.java --- a/src/org/tmatesoft/hg/core/HgInvalidFileException.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgInvalidFileException.java Sat Feb 25 19:35:16 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 @@ -44,11 +44,11 @@ public HgInvalidFileException(String message, Throwable th, File file) { super(message, th); - localFile = file; + localFile = file; // allows null } public HgInvalidFileException setFile(File file) { - assert file != null; + assert file != null; // doesn't allow null not to clear file accidentally localFile = file; return this; } @@ -64,6 +64,8 @@ protected void appendDetails(StringBuilder sb) { super.appendDetails(sb); if (localFile != null) { + sb.append(';'); + sb.append(' '); sb.append(" file:"); sb.append(localFile.getPath()); sb.append(','); diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/core/HgInvalidRevisionException.java --- a/src/org/tmatesoft/hg/core/HgInvalidRevisionException.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgInvalidRevisionException.java Sat Feb 25 19:35:16 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 @@ -16,8 +16,9 @@ */ package org.tmatesoft.hg.core; +import static org.tmatesoft.hg.repo.HgRepository.*; + import org.tmatesoft.hg.internal.Experimental; -import org.tmatesoft.hg.repo.HgRepository; /** * Use of revision or revision local index that is not valid for a given revlog. @@ -29,7 +30,7 @@ @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; + private Integer revIdx = BAD_REVISION; // next two make sense only when revIdx is present private int rangeLeftBoundary = -1, rangeRightBoundary = -1; @@ -75,7 +76,7 @@ revIdx = revisionIndex; return this; } - + public HgInvalidRevisionException setRevisionIndex(int revisionIndex, int rangeLeft, int rangeRight) { revIdx = revisionIndex; rangeLeftBoundary = rangeLeft; @@ -83,6 +84,14 @@ return this; } + public boolean isRevisionSet() { + return rev != null; + } + + public boolean isRevisionIndexSet() { + return revIdx != BAD_REVISION; + } + @Override public String getMessage() { String msg = super.getMessage(); @@ -98,9 +107,9 @@ 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; + case BAD_REVISION : sr = "UNKNOWN"; break; + case TIP : sr = "TIP"; break; + case WORKING_COPY: sr = "WORKING-COPY"; break; default : sr = revIdx.toString(); } if (rangeLeftBoundary != -1 || rangeRightBoundary != -1) { diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/core/HgLogCommand.java --- a/src/org/tmatesoft/hg/core/HgLogCommand.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgLogCommand.java Sat Feb 25 19:35:16 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 @@ -186,15 +186,12 @@ /** * Similar to {@link #execute(org.tmatesoft.hg.repo.RawChangeset.Inspector)}, collects and return result as a list. */ - public List execute() throws HgDataStreamException { + public List execute() throws HgException { CollectHandler collector = new CollectHandler(); try { execute(collector); - } catch (HgException ex) { + } catch (CancelledException ex) { // can't happen as long as our CollectHandler doesn't throw any exception - throw new HgBadStateException(ex.getCause()); - } catch (CancelledException ex) { - // can't happen, see above throw new HgBadStateException(ex); } return collector.getChanges(); diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/internal/DataAccessProvider.java --- a/src/org/tmatesoft/hg/internal/DataAccessProvider.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/internal/DataAccessProvider.java Sat Feb 25 19:35:16 2012 +0100 @@ -317,6 +317,7 @@ try { fileChannel.close(); } catch (IOException ex) { + // FIXME/TODO log facility can be obtained from session context StreamLogFacility.newDefault().debug(getClass(), ex, null); } fileChannel = null; diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/internal/RevlogDump.java --- a/src/org/tmatesoft/hg/internal/RevlogDump.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/internal/RevlogDump.java Sat Feb 25 19:35:16 2012 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2011 TMate Software Ltd + * Copyright (c) 2010-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 diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/internal/RevlogStream.java --- a/src/org/tmatesoft/hg/internal/RevlogStream.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/internal/RevlogStream.java Sat Feb 25 19:35:16 2012 +0100 @@ -56,6 +56,7 @@ private int[] baseRevisions; private boolean inline = false; private final File indexFile; + private File dataFile; private final DataAccessProvider dataAccess; // if we need anything else from HgRepo, might replace DAP parameter with HgRepo and query it for DAP. @@ -71,11 +72,34 @@ } /*package*/ DataAccess getDataStream() { - final String indexName = indexFile.getName(); - File dataFile = new File(indexFile.getParentFile(), indexName.substring(0, indexName.length() - 1) + "d"); - return dataAccess.create(dataFile); + return dataAccess.create(getDataFile()); } + /** + * Constructs file object that corresponds to .d revlog counterpart. + * Note, it's caller responsibility to ensure this file makes any sense (i.e. check {@link #inline} attribute) + */ + private File getDataFile() { + if (dataFile == null) { + final String indexName = indexFile.getName(); + dataFile = new File(indexFile.getParentFile(), indexName.substring(0, indexName.length() - 1) + "d"); + } + return dataFile; + } + + // initialize exception with the file where revlog structure information comes from + public HgInvalidControlFileException initWithIndexFile(HgInvalidControlFileException ex) { + return ex.setFile(indexFile); + } + + // initialize exception with the file where revlog data comes from + public HgInvalidControlFileException initWithDataFile(HgInvalidControlFileException ex) { + // exceptions are usually raised after read attepmt, hence inline shall be initialized + // although honest approach is to call #initOutline() first + return ex.setFile(inline ? indexFile : getDataFile()); + } + + public int revisionCount() { initOutline(); return baseRevisions.length; diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/repo/HgDataFile.java --- a/src/org/tmatesoft/hg/repo/HgDataFile.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgDataFile.java Sat Feb 25 19:35:16 2012 +0100 @@ -31,9 +31,9 @@ import java.util.Collections; import java.util.List; -import org.tmatesoft.hg.core.HgDataStreamException; import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.HgInvalidControlFileException; +import org.tmatesoft.hg.core.HgInvalidFileException; import org.tmatesoft.hg.core.HgInvalidRevisionException; import org.tmatesoft.hg.core.HgLogCommand; import org.tmatesoft.hg.core.Nodeid; @@ -93,23 +93,27 @@ * @param nodeid revision of the file * * @return size of the file content at the given revision - * @throws HgInvalidRevisionException if supplied nodeid doesn't identify any revision from this revlog - * @throws HgDataStreamException if attempt to access file metadata failed + * @throws HgInvalidRevisionException if supplied nodeid doesn't identify any revision from this revlog (runtime exception) * @throws HgInvalidControlFileException if access to revlog index/data entry failed */ - public int length(Nodeid nodeid) throws HgDataStreamException, HgInvalidControlFileException, HgInvalidRevisionException { - return length(getRevisionIndex(nodeid)); + public int length(Nodeid nodeid) throws HgInvalidControlFileException, HgInvalidRevisionException { + try { + return length(getRevisionIndex(nodeid)); + } catch (HgInvalidControlFileException ex) { + throw ex.isRevisionSet() ? ex : ex.setRevision(nodeid); + } catch (HgInvalidRevisionException ex) { + throw ex.isRevisionSet() ? ex : ex.setRevision(nodeid); + } } /** * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, only {@link HgRepository#TIP} makes sense. * @return size of the file content at the revision identified by local revision number. - * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog - * @throws HgDataStreamException if attempt to access file metadata failed + * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (runtime exception) * @throws HgInvalidControlFileException if access to revlog index/data entry failed */ - public int length(int fileRevisionIndex) throws HgDataStreamException, HgInvalidControlFileException, HgInvalidRevisionException { - // TODO support WORKING_COPY constant + public int length(int fileRevisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException { + // FIXME support WORKING_COPY constant if (metadata == null || !metadata.checked(fileRevisionIndex)) { checkAndRecordMetadata(fileRevisionIndex); } @@ -126,11 +130,12 @@ * as if it would be refreshed in the working copy, i.e. its corresponding revision * (XXX according to dirstate? file tip?) is read from the repository, and filters repo -> working copy get applied. * - * @param sink where to pipe content to - * @throws HgDataStreamException to indicate troubles reading repository file + * @param sink content consumer + * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgInvalidFileException if access to file in working directory failed * @throws CancelledException if execution of the operation was cancelled */ - public void workingCopy(ByteChannel sink) throws HgDataStreamException, HgInvalidControlFileException, CancelledException { + public void workingCopy(ByteChannel sink) throws HgException, CancelledException { File f = getRepo().getFile(this); if (f.exists()) { final CancelSupport cs = CancelSupport.Factory.get(sink); @@ -150,7 +155,7 @@ buf.compact(); } } catch (IOException ex) { - throw new HgDataStreamException(getPath(), ex); + throw new HgInvalidFileException("Working copy read failed", ex, f); } finally { progress.done(); if (fc != null) { @@ -165,7 +170,8 @@ final Pair wcParents = getRepo().getWorkingCopyParents(); Nodeid p = wcParents.first().isNull() ? wcParents.second() : wcParents.first(); if (p.isNull()) { - // no dirstate parents - no content + // no dirstate parents - no content + // XXX what if it's repository with no dirstate? Shall I use TIP then? return; } final HgChangelog clog = getRepo().getChangelog(); @@ -184,49 +190,39 @@ } } -// 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. -// } - - /*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, HgInvalidControlFileException, CancelledException, HgInvalidRevisionException { - if (revision == WORKING_COPY) { + /** + * Access content of a file revision + * XXX not sure distinct method contentWithFilters() is the best way to do, perhaps, callers shall add filters themselves? + * + * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, {@link HgRepository#TIP} and {@link HgRepository#WORKING_COPY} make sense. + * @param sink content consumer + * + * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgInvalidFileException if access to file in working directory failed + * @throws CancelledException if execution of the operation was cancelled + * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (runtime exception) + */ + public void contentWithFilters(int fileRevisionIndex, ByteChannel sink) throws HgException, CancelledException, HgInvalidRevisionException { + if (fileRevisionIndex == WORKING_COPY) { workingCopy(sink); // pass un-mangled sink } else { - content(revision, new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath()))); + content(fileRevisionIndex, new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath()))); } } /** + * Retrieve content of specific revision. Content is provided as is, without any filters (e.g. keywords, eol, etc.) applied. + * For filtered content, use {@link #contentWithFilters(int, ByteChannel)}. * * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, {@link HgRepository#TIP} and {@link HgRepository#WORKING_COPY} make sense. - * @param sink - * @throws HgDataStreamException FIXME EXCEPTIONS + * @param sink content consumer + * * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws HgInvalidFileException if access to file in working directory failed * @throws CancelledException if execution of the operation was cancelled - * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog + * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (runtime exception) */ - public void content(int fileRevisionIndex, ByteChannel sink) throws HgDataStreamException, HgInvalidControlFileException, CancelledException, HgInvalidRevisionException { + public void content(int fileRevisionIndex, ByteChannel sink) throws HgException, CancelledException, HgInvalidRevisionException { // for data files need to check heading of the file content for possible metadata // @see http://mercurial.selenic.com/wiki/FileFormats#data.2BAC8- if (fileRevisionIndex == TIP) { @@ -255,19 +251,22 @@ insp = new ContentPipe(sink, metadata.dataOffset(fileRevisionIndex), lf); } else { // do not know if there's metadata - insp = new MetadataInspector(metadata, lf, getPath(), new ContentPipe(sink, 0, lf)); + insp = new MetadataInspector(metadata, lf, new ContentPipe(sink, 0, lf)); } insp.checkCancelled(); super.content.iterate(fileRevisionIndex, fileRevisionIndex, true, insp); try { insp.checkFailed(); // XXX is there real need to throw IOException from ContentPipe? - } catch (HgDataStreamException ex) { - throw ex; + } catch (HgInvalidControlFileException ex) { + ex = ex.setFileName(getPath()); + throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(fileRevisionIndex); } catch (IOException ex) { - throw new HgDataStreamException(getPath(), ex).setRevisionIndex(fileRevisionIndex); + HgInvalidControlFileException e = new HgInvalidControlFileException("Revision content access failed", ex, null); + throw content.initWithIndexFile(e).setFileName(getPath()).setRevisionIndex(fileRevisionIndex); } catch (HgException ex) { // shall not happen, unless we changed ContentPipe or its subclass - throw new HgDataStreamException(getPath(), ex.getClass().getName(), ex); + HgInvalidControlFileException e = new HgInvalidControlFileException("Revision content access failed", ex, null); + throw content.initWithIndexFile(e).setFileName(getPath()).setRevisionIndex(fileRevisionIndex); } } @@ -460,11 +459,11 @@ } /** - * - * @return - * @throws HgDataStreamException if attempt to access file metadata failed + * Tells whether this file originates from another repository file + * @return true if this file is a copy of another from the repository + * @throws HgInvalidControlFileException if access to revlog or file metadata failed */ - public boolean isCopy() throws HgDataStreamException { + public boolean isCopy() throws HgInvalidControlFileException { if (metadata == null || !metadata.checked(0)) { checkAndRecordMetadata(0); } @@ -478,17 +477,17 @@ * Get name of the file this one was copied from. * * @return name of the file origin - * @throws HgDataStreamException if attempt to access file metadata failed + * @throws HgInvalidControlFileException if access to revlog or file metadata failed * @throws UnsupportedOperationException if this file doesn't represent a copy ({@link #isCopy()} was false) */ - public Path getCopySourceName() throws HgDataStreamException { + public Path getCopySourceName() throws HgInvalidControlFileException { if (isCopy()) { return Path.create(metadata.find(0, "copy")); } throw new UnsupportedOperationException(); // XXX REVISIT, think over if Exception is good (clients would check isCopy() anyway, perhaps null is sufficient?) } - public Nodeid getCopySourceRevision() throws HgDataStreamException { + public Nodeid getCopySourceRevision() throws HgInvalidControlFileException { if (isCopy()) { return Nodeid.fromAscii(metadata.find(0, "copyrev")); // XXX reuse/cache Nodeid } @@ -504,7 +503,7 @@ return sb.toString(); } - private void checkAndRecordMetadata(int localRev) throws HgDataStreamException { + private void checkAndRecordMetadata(int localRev) throws HgInvalidControlFileException { // content() always initializes metadata. // FIXME this is expensive way to find out metadata, distinct RevlogStream.Iterator would be better. // Alternatively, may parameterize MetadataContentPipe to do prepare only. @@ -520,7 +519,10 @@ } catch (CancelledException ex) { // it's ok, we did that } catch (HgInvalidControlFileException ex) { - throw new HgDataStreamException(getPath(), ex); + throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(localRev); + } catch (HgException ex) { + // metadata comes from the content, hence initWithDataFile + throw content.initWithDataFile(new HgInvalidControlFileException(null, ex, null)); } } @@ -616,12 +618,10 @@ private static class MetadataInspector extends ErrorHandlingInspector implements RevlogStream.Inspector { private final Metadata metadata; private final RevlogStream.Inspector delegate; - private final Path fname; // need these only for error reporting private final LogFacility log; - public MetadataInspector(Metadata _metadata, LogFacility logFacility, Path file, RevlogStream.Inspector chain) { + public MetadataInspector(Metadata _metadata, LogFacility logFacility, RevlogStream.Inspector chain) { metadata = _metadata; - fname = file; log = logFacility; delegate = chain; setCancelSupport(CancelSupport.Factory.get(chain)); @@ -648,12 +648,12 @@ } } catch (IOException ex) { recordFailure(ex); - } catch (HgDataStreamException ex) { - recordFailure(ex.setRevisionIndex(revisionNumber)); + } catch (HgInvalidControlFileException ex) { + recordFailure(ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(revisionNumber)); } } - private int parseMetadata(DataAccess data, final int daLength, ArrayList _metadata) throws IOException, HgDataStreamException { + private int parseMetadata(DataAccess data, final int daLength, ArrayList _metadata) throws IOException, HgInvalidControlFileException { int lastEntryStart = 2; int lastColon = -1; // XXX in fact, need smth like ByteArrayBuilder, similar to StringBuilder, @@ -705,7 +705,7 @@ // data.isEmpty is not reliable, renamed files of size==0 keep only metadata if (!metadataIsComplete) { // XXX perhaps, worth a testcase (empty file, renamed, read or ask ifCopy - throw new HgDataStreamException(fname, "Metadata is not closed properly", null); + throw new HgInvalidControlFileException("Metadata is not closed properly", null, null); } return lastEntryStart; } diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/repo/HgRepository.java --- a/src/org/tmatesoft/hg/repo/HgRepository.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgRepository.java Sat Feb 25 19:35:16 2012 +0100 @@ -25,7 +25,7 @@ import java.util.HashMap; import java.util.List; -import org.tmatesoft.hg.core.HgDataStreamException; +import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.HgInvalidControlFileException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.core.SessionContext; @@ -164,7 +164,7 @@ } catch (CancelledException ex) { // IGNORE, can't happen, we did not configure cancellation getContext().getLog().debug(getClass(), ex, null); - } catch (HgDataStreamException ex) { + } catch (HgException ex) { getContext().getLog().error(getClass(), ex, null); // FIXME need to react } catch (IOException ex) { diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/repo/HgStatusCollector.java --- a/src/org/tmatesoft/hg/repo/HgStatusCollector.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgStatusCollector.java Sat Feb 25 19:35:16 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 @@ -28,7 +28,6 @@ import java.util.TreeSet; import org.tmatesoft.hg.core.HgBadStateException; -import org.tmatesoft.hg.core.HgDataStreamException; import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.HgInvalidControlFileException; import org.tmatesoft.hg.core.Nodeid; @@ -290,11 +289,11 @@ return rv; } - /*package-local*/static Path getOriginIfCopy(HgRepository hgRepo, Path fname, Collection originals, int originalChangelogRevision) throws HgDataStreamException, HgInvalidControlFileException { + /*package-local*/static Path getOriginIfCopy(HgRepository hgRepo, Path fname, Collection originals, int originalChangelogRevision) throws HgException { HgDataFile df = hgRepo.getFileNode(fname); if (!df.exists()) { String msg = String.format("Didn't find file '%s' in the repo. Perhaps, bad storage name conversion?", fname); - throw new HgDataStreamException(fname, msg, null).setRevisionIndex(originalChangelogRevision); + throw new HgException(msg).setFileName(fname).setRevisionIndex(originalChangelogRevision); } while (df.isCopy()) { Path original = df.getCopySourceName(); diff -r fdc1db8f7f61 -r deb64baaa412 src/org/tmatesoft/hg/repo/Revlog.java --- a/src/org/tmatesoft/hg/repo/Revlog.java Sat Feb 25 19:31:57 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/Revlog.java Sat Feb 25 19:35:16 2012 +0100 @@ -177,25 +177,54 @@ } /** - * Access to revision data as is (decompressed, but otherwise unprocessed, i.e. not parsed for e.g. changeset or manifest entries) - * @param nodeid + * Access to revision data as is, equivalent to rawContent(getRevisionIndex(nodeid), sink) + * + * @param nodeid revision to retrieve + * @param sink data destination + * + * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog + * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws CancelledException if content retrieval operation was cancelled + * + * @see #rawContent(int, ByteChannel) */ - protected void rawContent(Nodeid nodeid, ByteChannel sink) throws HgException, IOException, CancelledException, HgInvalidRevisionException { + protected void rawContent(Nodeid nodeid, ByteChannel sink) throws HgInvalidControlFileException, CancelledException, HgInvalidRevisionException { rawContent(getRevisionIndex(nodeid), sink); } /** - * @param fileRevisionIndex - index of this file change (not a changelog revision index), non-negative. From predefined constants, only {@link HgRepository#TIP} makes sense. - * FIXME is it necessary to have IOException along with HgException here? + * Access to revision data as is (decompressed, but otherwise unprocessed, i.e. not parsed for e.g. changeset or manifest entries). + * + * @param fileRevisionIndex index of this revlog change (not a changelog revision index), non-negative. From predefined constants, only {@link HgRepository#TIP} makes sense. + * @param sink data destination + * + * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog + * @throws HgInvalidControlFileException if access to revlog index/data entry failed + * @throws CancelledException if content retrieval operation was cancelled */ - protected void rawContent(int fileRevisionIndex, ByteChannel sink) throws HgException, IOException, CancelledException, HgInvalidRevisionException { + protected void rawContent(int fileRevisionIndex, ByteChannel sink) throws HgInvalidControlFileException, CancelledException, HgInvalidRevisionException { if (sink == null) { throw new IllegalArgumentException(); } - ContentPipe insp = new ContentPipe(sink, 0, repo.getContext().getLog()); - insp.checkCancelled(); - content.iterate(fileRevisionIndex, fileRevisionIndex, true, insp); - insp.checkFailed(); + try { + ContentPipe insp = new ContentPipe(sink, 0, repo.getContext().getLog()); + insp.checkCancelled(); + content.iterate(fileRevisionIndex, fileRevisionIndex, true, insp); + insp.checkFailed(); + } catch (IOException ex) { + HgInvalidControlFileException e = new HgInvalidControlFileException(String.format("Access to revision %d content failed", fileRevisionIndex), ex, null); + e.setRevisionIndex(fileRevisionIndex); + // FIXME e.setFileName(content.getIndexFile() or this.getHumanFriendlyPath()) - shall decide whether + // protected abstract getPath() with impl in HgDataFile, HgManifest and HgChangelog or path is data of either Revlog or RevlogStream + // Do the same (add file name) below + throw e; + } catch (HgInvalidControlFileException ex) { + throw ex; + } catch (HgException ex) { + HgInvalidControlFileException e = new HgInvalidControlFileException(ex.getClass().getSimpleName(), ex, null); + e.setRevisionIndex(fileRevisionIndex); + throw e; + } } /** @@ -585,6 +614,7 @@ failure = ex; } + // TODO consider if IOException in addition to HgException is of any real utility public void checkFailed() throws HgException, IOException, CancelledException { if (failure == null) { return;