Mercurial > hg4j
comparison src/org/tmatesoft/hg/repo/HgDataFile.java @ 396:0ae53c32ecef
Straighten out exceptions thrown when file access failed - three is too much
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Thu, 23 Feb 2012 01:06:24 +0100 |
| parents | b015f3918120 |
| children | a57a21aa4190 |
comparison
equal
deleted
inserted
replaced
| 395:4732fffff58a | 396:0ae53c32ecef |
|---|---|
| 29 import java.util.Arrays; | 29 import java.util.Arrays; |
| 30 import java.util.Collection; | 30 import java.util.Collection; |
| 31 import java.util.Collections; | 31 import java.util.Collections; |
| 32 import java.util.List; | 32 import java.util.List; |
| 33 | 33 |
| 34 import org.tmatesoft.hg.core.HgDataStreamException; | |
| 35 import org.tmatesoft.hg.core.HgException; | 34 import org.tmatesoft.hg.core.HgException; |
| 36 import org.tmatesoft.hg.core.HgInvalidControlFileException; | 35 import org.tmatesoft.hg.core.HgInvalidControlFileException; |
| 36 import org.tmatesoft.hg.core.HgInvalidFileException; | |
| 37 import org.tmatesoft.hg.core.HgInvalidRevisionException; | 37 import org.tmatesoft.hg.core.HgInvalidRevisionException; |
| 38 import org.tmatesoft.hg.core.HgLogCommand; | 38 import org.tmatesoft.hg.core.HgLogCommand; |
| 39 import org.tmatesoft.hg.core.Nodeid; | 39 import org.tmatesoft.hg.core.Nodeid; |
| 40 import org.tmatesoft.hg.internal.DataAccess; | 40 import org.tmatesoft.hg.internal.DataAccess; |
| 41 import org.tmatesoft.hg.internal.FilterByteChannel; | 41 import org.tmatesoft.hg.internal.FilterByteChannel; |
| 91 * Handy shorthand for {@link #length(int) length(getRevisionIndex(nodeid))} | 91 * Handy shorthand for {@link #length(int) length(getRevisionIndex(nodeid))} |
| 92 * | 92 * |
| 93 * @param nodeid revision of the file | 93 * @param nodeid revision of the file |
| 94 * | 94 * |
| 95 * @return size of the file content at the given revision | 95 * @return size of the file content at the given revision |
| 96 * @throws HgInvalidRevisionException if supplied nodeid doesn't identify any revision from this revlog | 96 * @throws HgInvalidRevisionException if supplied nodeid doesn't identify any revision from this revlog (<em>runtime exception</em>) |
| 97 * @throws HgDataStreamException if attempt to access file metadata failed | |
| 98 * @throws HgInvalidControlFileException if access to revlog index/data entry failed | 97 * @throws HgInvalidControlFileException if access to revlog index/data entry failed |
| 99 */ | 98 */ |
| 100 public int length(Nodeid nodeid) throws HgDataStreamException, HgInvalidControlFileException, HgInvalidRevisionException { | 99 public int length(Nodeid nodeid) throws HgInvalidControlFileException, HgInvalidRevisionException { |
| 101 return length(getRevisionIndex(nodeid)); | 100 try { |
| 101 return length(getRevisionIndex(nodeid)); | |
| 102 } catch (HgInvalidControlFileException ex) { | |
| 103 throw ex.isRevisionSet() ? ex : ex.setRevision(nodeid); | |
| 104 } catch (HgInvalidRevisionException ex) { | |
| 105 throw ex.isRevisionSet() ? ex : ex.setRevision(nodeid); | |
| 106 } | |
| 102 } | 107 } |
| 103 | 108 |
| 104 /** | 109 /** |
| 105 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, only {@link HgRepository#TIP} makes sense. | 110 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, only {@link HgRepository#TIP} makes sense. |
| 106 * @return size of the file content at the revision identified by local revision number. | 111 * @return size of the file content at the revision identified by local revision number. |
| 107 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog | 112 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (<em>runtime exception</em>) |
| 108 * @throws HgDataStreamException if attempt to access file metadata failed | |
| 109 * @throws HgInvalidControlFileException if access to revlog index/data entry failed | 113 * @throws HgInvalidControlFileException if access to revlog index/data entry failed |
| 110 */ | 114 */ |
| 111 public int length(int fileRevisionIndex) throws HgDataStreamException, HgInvalidControlFileException, HgInvalidRevisionException { | 115 public int length(int fileRevisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException { |
| 112 // TODO support WORKING_COPY constant | 116 // FIXME support WORKING_COPY constant |
| 113 if (metadata == null || !metadata.checked(fileRevisionIndex)) { | 117 if (metadata == null || !metadata.checked(fileRevisionIndex)) { |
| 114 checkAndRecordMetadata(fileRevisionIndex); | 118 checkAndRecordMetadata(fileRevisionIndex); |
| 115 } | 119 } |
| 116 final int dataLen = content.dataLength(fileRevisionIndex); | 120 final int dataLen = content.dataLength(fileRevisionIndex); |
| 117 if (metadata.known(fileRevisionIndex)) { | 121 if (metadata.known(fileRevisionIndex)) { |
| 124 * Reads content of the file from working directory. If file present in the working directory, its actual content without | 128 * Reads content of the file from working directory. If file present in the working directory, its actual content without |
| 125 * any filters is supplied through the sink. If file does not exist in the working dir, this method provides content of a file | 129 * any filters is supplied through the sink. If file does not exist in the working dir, this method provides content of a file |
| 126 * as if it would be refreshed in the working copy, i.e. its corresponding revision | 130 * as if it would be refreshed in the working copy, i.e. its corresponding revision |
| 127 * (XXX according to dirstate? file tip?) is read from the repository, and filters repo -> working copy get applied. | 131 * (XXX according to dirstate? file tip?) is read from the repository, and filters repo -> working copy get applied. |
| 128 * | 132 * |
| 129 * @param sink where to pipe content to | 133 * @param sink content consumer |
| 130 * @throws HgDataStreamException to indicate troubles reading repository file | 134 * @throws HgInvalidControlFileException if access to revlog index/data entry failed |
| 135 * @throws HgInvalidFileException if access to file in working directory failed | |
| 131 * @throws CancelledException if execution of the operation was cancelled | 136 * @throws CancelledException if execution of the operation was cancelled |
| 132 */ | 137 */ |
| 133 public void workingCopy(ByteChannel sink) throws HgDataStreamException, HgInvalidControlFileException, CancelledException { | 138 public void workingCopy(ByteChannel sink) throws HgException, CancelledException { |
| 134 File f = getRepo().getFile(this); | 139 File f = getRepo().getFile(this); |
| 135 if (f.exists()) { | 140 if (f.exists()) { |
| 136 final CancelSupport cs = CancelSupport.Factory.get(sink); | 141 final CancelSupport cs = CancelSupport.Factory.get(sink); |
| 137 final ProgressSupport progress = ProgressSupport.Factory.get(sink); | 142 final ProgressSupport progress = ProgressSupport.Factory.get(sink); |
| 138 final long flength = f.length(); | 143 final long flength = f.length(); |
| 148 int consumed = sink.write(buf); | 153 int consumed = sink.write(buf); |
| 149 progress.worked(flength > Integer.MAX_VALUE ? 1 : consumed); | 154 progress.worked(flength > Integer.MAX_VALUE ? 1 : consumed); |
| 150 buf.compact(); | 155 buf.compact(); |
| 151 } | 156 } |
| 152 } catch (IOException ex) { | 157 } catch (IOException ex) { |
| 153 throw new HgDataStreamException(getPath(), ex); | 158 throw new HgInvalidFileException("Working copy read failed", ex, f); |
| 154 } finally { | 159 } finally { |
| 155 progress.done(); | 160 progress.done(); |
| 156 if (fc != null) { | 161 if (fc != null) { |
| 157 try { | 162 try { |
| 158 fc.close(); | 163 fc.close(); |
| 163 } | 168 } |
| 164 } else { | 169 } else { |
| 165 final Pair<Nodeid, Nodeid> wcParents = getRepo().getWorkingCopyParents(); | 170 final Pair<Nodeid, Nodeid> wcParents = getRepo().getWorkingCopyParents(); |
| 166 Nodeid p = wcParents.first().isNull() ? wcParents.second() : wcParents.first(); | 171 Nodeid p = wcParents.first().isNull() ? wcParents.second() : wcParents.first(); |
| 167 if (p.isNull()) { | 172 if (p.isNull()) { |
| 168 // no dirstate parents - no content | 173 // no dirstate parents - no content |
| 174 // XXX what if it's repository with no dirstate? Shall I use TIP then? | |
| 169 return; | 175 return; |
| 170 } | 176 } |
| 171 final HgChangelog clog = getRepo().getChangelog(); | 177 final HgChangelog clog = getRepo().getChangelog(); |
| 172 // common case to avoid searching complete changelog for nodeid match | 178 // common case to avoid searching complete changelog for nodeid match |
| 173 final Nodeid tipRev = clog.getRevision(TIP); | 179 final Nodeid tipRev = clog.getRevision(TIP); |
| 182 final int fileRevIndex = getRevisionIndex(fileRev); | 188 final int fileRevIndex = getRevisionIndex(fileRev); |
| 183 contentWithFilters(fileRevIndex, sink); | 189 contentWithFilters(fileRevIndex, sink); |
| 184 } | 190 } |
| 185 } | 191 } |
| 186 | 192 |
| 187 // public void content(int revision, ByteChannel sink, boolean applyFilters) throws HgDataStreamException, IOException, CancelledException { | 193 /** |
| 188 // byte[] content = content(revision); | 194 * Access content of a file revision |
| 189 // final CancelSupport cancelSupport = CancelSupport.Factory.get(sink); | 195 * XXX not sure distinct method contentWithFilters() is the best way to do, perhaps, callers shall add filters themselves? |
| 190 // final ProgressSupport progressSupport = ProgressSupport.Factory.get(sink); | 196 * |
| 191 // ByteBuffer buf = ByteBuffer.allocate(512); | 197 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, {@link HgRepository#TIP} and {@link HgRepository#WORKING_COPY} make sense. |
| 192 // int left = content.length; | 198 * @param sink content consumer |
| 193 // progressSupport.start(left); | 199 * |
| 194 // int offset = 0; | 200 * @throws HgInvalidControlFileException if access to revlog index/data entry failed |
| 195 // cancelSupport.checkCancelled(); | 201 * @throws HgInvalidFileException if access to file in working directory failed |
| 196 // ByteChannel _sink = applyFilters ? new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath())) : sink; | 202 * @throws CancelledException if execution of the operation was cancelled |
| 197 // do { | 203 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (<em>runtime exception</em>) |
| 198 // buf.put(content, offset, Math.min(left, buf.remaining())); | 204 */ |
| 199 // buf.flip(); | 205 public void contentWithFilters(int fileRevisionIndex, ByteChannel sink) throws HgException, CancelledException, HgInvalidRevisionException { |
| 200 // cancelSupport.checkCancelled(); | 206 if (fileRevisionIndex == WORKING_COPY) { |
| 201 // // XXX I may not rely on returned number of bytes but track change in buf position instead. | |
| 202 // int consumed = _sink.write(buf); | |
| 203 // buf.compact(); | |
| 204 // offset += consumed; | |
| 205 // left -= consumed; | |
| 206 // progressSupport.worked(consumed); | |
| 207 // } while (left > 0); | |
| 208 // progressSupport.done(); // XXX shall specify whether #done() is invoked always or only if completed successfully. | |
| 209 // } | |
| 210 | |
| 211 /*XXX not sure distinct method contentWithFilters() is the best way to do, perhaps, callers shall add filters themselves?*/ | |
| 212 public void contentWithFilters(int revision, ByteChannel sink) throws HgDataStreamException, HgInvalidControlFileException, CancelledException, HgInvalidRevisionException { | |
| 213 if (revision == WORKING_COPY) { | |
| 214 workingCopy(sink); // pass un-mangled sink | 207 workingCopy(sink); // pass un-mangled sink |
| 215 } else { | 208 } else { |
| 216 content(revision, new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath()))); | 209 content(fileRevisionIndex, new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath()))); |
| 217 } | 210 } |
| 218 } | 211 } |
| 219 | 212 |
| 220 /** | 213 /** |
| 214 * Retrieve content of specific revision. Content is provided as is, without any filters (e.g. keywords, eol, etc.) applied. | |
| 215 * For filtered content, use {@link #contentWithFilters(int, ByteChannel)}. | |
| 221 * | 216 * |
| 222 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, {@link HgRepository#TIP} and {@link HgRepository#WORKING_COPY} make sense. | 217 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, {@link HgRepository#TIP} and {@link HgRepository#WORKING_COPY} make sense. |
| 223 * @param sink | 218 * @param sink content consumer |
| 224 * @throws HgDataStreamException FIXME EXCEPTIONS | 219 * |
| 225 * @throws HgInvalidControlFileException if access to revlog index/data entry failed | 220 * @throws HgInvalidControlFileException if access to revlog index/data entry failed |
| 221 * @throws HgInvalidFileException if access to file in working directory failed | |
| 226 * @throws CancelledException if execution of the operation was cancelled | 222 * @throws CancelledException if execution of the operation was cancelled |
| 227 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog | 223 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (<em>runtime exception</em>) |
| 228 */ | 224 */ |
| 229 public void content(int fileRevisionIndex, ByteChannel sink) throws HgDataStreamException, HgInvalidControlFileException, CancelledException, HgInvalidRevisionException { | 225 public void content(int fileRevisionIndex, ByteChannel sink) throws HgException, CancelledException, HgInvalidRevisionException { |
| 230 // for data files need to check heading of the file content for possible metadata | 226 // for data files need to check heading of the file content for possible metadata |
| 231 // @see http://mercurial.selenic.com/wiki/FileFormats#data.2BAC8- | 227 // @see http://mercurial.selenic.com/wiki/FileFormats#data.2BAC8- |
| 232 if (fileRevisionIndex == TIP) { | 228 if (fileRevisionIndex == TIP) { |
| 233 fileRevisionIndex = getLastRevision(); | 229 fileRevisionIndex = getLastRevision(); |
| 234 } | 230 } |
| 253 insp = new ContentPipe(sink, 0, lf); | 249 insp = new ContentPipe(sink, 0, lf); |
| 254 } else if (metadata.known(fileRevisionIndex)) { | 250 } else if (metadata.known(fileRevisionIndex)) { |
| 255 insp = new ContentPipe(sink, metadata.dataOffset(fileRevisionIndex), lf); | 251 insp = new ContentPipe(sink, metadata.dataOffset(fileRevisionIndex), lf); |
| 256 } else { | 252 } else { |
| 257 // do not know if there's metadata | 253 // do not know if there's metadata |
| 258 insp = new MetadataInspector(metadata, lf, getPath(), new ContentPipe(sink, 0, lf)); | 254 insp = new MetadataInspector(metadata, lf, new ContentPipe(sink, 0, lf)); |
| 259 } | 255 } |
| 260 insp.checkCancelled(); | 256 insp.checkCancelled(); |
| 261 super.content.iterate(fileRevisionIndex, fileRevisionIndex, true, insp); | 257 super.content.iterate(fileRevisionIndex, fileRevisionIndex, true, insp); |
| 262 try { | 258 try { |
| 263 insp.checkFailed(); // XXX is there real need to throw IOException from ContentPipe? | 259 insp.checkFailed(); // XXX is there real need to throw IOException from ContentPipe? |
| 264 } catch (HgDataStreamException ex) { | 260 } catch (HgInvalidControlFileException ex) { |
| 265 throw ex; | 261 ex = ex.setFileName(getPath()); |
| 262 throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(fileRevisionIndex); | |
| 266 } catch (IOException ex) { | 263 } catch (IOException ex) { |
| 267 throw new HgDataStreamException(getPath(), ex).setRevisionIndex(fileRevisionIndex); | 264 HgInvalidControlFileException e = new HgInvalidControlFileException("Revision content access failed", ex, null); |
| 265 throw content.initWithIndexFile(e).setFileName(getPath()).setRevisionIndex(fileRevisionIndex); | |
| 268 } catch (HgException ex) { | 266 } catch (HgException ex) { |
| 269 // shall not happen, unless we changed ContentPipe or its subclass | 267 // shall not happen, unless we changed ContentPipe or its subclass |
| 270 throw new HgDataStreamException(getPath(), ex.getClass().getName(), ex); | 268 HgInvalidControlFileException e = new HgInvalidControlFileException("Revision content access failed", ex, null); |
| 269 throw content.initWithIndexFile(e).setFileName(getPath()).setRevisionIndex(fileRevisionIndex); | |
| 271 } | 270 } |
| 272 } | 271 } |
| 273 | 272 |
| 274 private static class HistoryNode { | 273 private static class HistoryNode { |
| 275 int changeset; | 274 int changeset; |
| 458 int changelogRevision = getChangesetRevisionIndex(getRevisionIndex(nid)); | 457 int changelogRevision = getChangesetRevisionIndex(getRevisionIndex(nid)); |
| 459 return getRepo().getChangelog().getRevision(changelogRevision); | 458 return getRepo().getChangelog().getRevision(changelogRevision); |
| 460 } | 459 } |
| 461 | 460 |
| 462 /** | 461 /** |
| 463 * | 462 * Tells whether this file originates from another repository file |
| 464 * @return | 463 * @return <code>true</code> if this file is a copy of another from the repository |
| 465 * @throws HgDataStreamException if attempt to access file metadata failed | 464 * @throws HgInvalidControlFileException if access to revlog or file metadata failed |
| 466 */ | 465 */ |
| 467 public boolean isCopy() throws HgDataStreamException { | 466 public boolean isCopy() throws HgInvalidControlFileException { |
| 468 if (metadata == null || !metadata.checked(0)) { | 467 if (metadata == null || !metadata.checked(0)) { |
| 469 checkAndRecordMetadata(0); | 468 checkAndRecordMetadata(0); |
| 470 } | 469 } |
| 471 if (!metadata.known(0)) { | 470 if (!metadata.known(0)) { |
| 472 return false; | 471 return false; |
| 476 | 475 |
| 477 /** | 476 /** |
| 478 * Get name of the file this one was copied from. | 477 * Get name of the file this one was copied from. |
| 479 * | 478 * |
| 480 * @return name of the file origin | 479 * @return name of the file origin |
| 481 * @throws HgDataStreamException if attempt to access file metadata failed | 480 * @throws HgInvalidControlFileException if access to revlog or file metadata failed |
| 482 * @throws UnsupportedOperationException if this file doesn't represent a copy ({@link #isCopy()} was false) | 481 * @throws UnsupportedOperationException if this file doesn't represent a copy ({@link #isCopy()} was false) |
| 483 */ | 482 */ |
| 484 public Path getCopySourceName() throws HgDataStreamException { | 483 public Path getCopySourceName() throws HgInvalidControlFileException { |
| 485 if (isCopy()) { | 484 if (isCopy()) { |
| 486 return Path.create(metadata.find(0, "copy")); | 485 return Path.create(metadata.find(0, "copy")); |
| 487 } | 486 } |
| 488 throw new UnsupportedOperationException(); // XXX REVISIT, think over if Exception is good (clients would check isCopy() anyway, perhaps null is sufficient?) | 487 throw new UnsupportedOperationException(); // XXX REVISIT, think over if Exception is good (clients would check isCopy() anyway, perhaps null is sufficient?) |
| 489 } | 488 } |
| 490 | 489 |
| 491 public Nodeid getCopySourceRevision() throws HgDataStreamException { | 490 public Nodeid getCopySourceRevision() throws HgInvalidControlFileException { |
| 492 if (isCopy()) { | 491 if (isCopy()) { |
| 493 return Nodeid.fromAscii(metadata.find(0, "copyrev")); // XXX reuse/cache Nodeid | 492 return Nodeid.fromAscii(metadata.find(0, "copyrev")); // XXX reuse/cache Nodeid |
| 494 } | 493 } |
| 495 throw new UnsupportedOperationException(); | 494 throw new UnsupportedOperationException(); |
| 496 } | 495 } |
| 502 sb.append(getPath()); | 501 sb.append(getPath()); |
| 503 sb.append(')'); | 502 sb.append(')'); |
| 504 return sb.toString(); | 503 return sb.toString(); |
| 505 } | 504 } |
| 506 | 505 |
| 507 private void checkAndRecordMetadata(int localRev) throws HgDataStreamException { | 506 private void checkAndRecordMetadata(int localRev) throws HgInvalidControlFileException { |
| 508 // content() always initializes metadata. | 507 // content() always initializes metadata. |
| 509 // FIXME this is expensive way to find out metadata, distinct RevlogStream.Iterator would be better. | 508 // FIXME this is expensive way to find out metadata, distinct RevlogStream.Iterator would be better. |
| 510 // Alternatively, may parameterize MetadataContentPipe to do prepare only. | 509 // Alternatively, may parameterize MetadataContentPipe to do prepare only. |
| 511 // For reference, when throwing CancelledException, hg status -A --rev 3:80 takes 70 ms | 510 // For reference, when throwing CancelledException, hg status -A --rev 3:80 takes 70 ms |
| 512 // however, if we just consume buffer instead (buffer.position(buffer.limit()), same command takes ~320ms | 511 // however, if we just consume buffer instead (buffer.position(buffer.limit()), same command takes ~320ms |
| 518 } | 517 } |
| 519 }); | 518 }); |
| 520 } catch (CancelledException ex) { | 519 } catch (CancelledException ex) { |
| 521 // it's ok, we did that | 520 // it's ok, we did that |
| 522 } catch (HgInvalidControlFileException ex) { | 521 } catch (HgInvalidControlFileException ex) { |
| 523 throw new HgDataStreamException(getPath(), ex); | 522 throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(localRev); |
| 523 } catch (HgException ex) { | |
| 524 // metadata comes from the content, hence initWithDataFile | |
| 525 throw content.initWithDataFile(new HgInvalidControlFileException(null, ex, null)); | |
| 524 } | 526 } |
| 525 } | 527 } |
| 526 | 528 |
| 527 private static final class MetadataEntry { | 529 private static final class MetadataEntry { |
| 528 private final String entry; | 530 private final String entry; |
| 614 } | 616 } |
| 615 | 617 |
| 616 private static class MetadataInspector extends ErrorHandlingInspector implements RevlogStream.Inspector { | 618 private static class MetadataInspector extends ErrorHandlingInspector implements RevlogStream.Inspector { |
| 617 private final Metadata metadata; | 619 private final Metadata metadata; |
| 618 private final RevlogStream.Inspector delegate; | 620 private final RevlogStream.Inspector delegate; |
| 619 private final Path fname; // need these only for error reporting | |
| 620 private final LogFacility log; | 621 private final LogFacility log; |
| 621 | 622 |
| 622 public MetadataInspector(Metadata _metadata, LogFacility logFacility, Path file, RevlogStream.Inspector chain) { | 623 public MetadataInspector(Metadata _metadata, LogFacility logFacility, RevlogStream.Inspector chain) { |
| 623 metadata = _metadata; | 624 metadata = _metadata; |
| 624 fname = file; | |
| 625 log = logFacility; | 625 log = logFacility; |
| 626 delegate = chain; | 626 delegate = chain; |
| 627 setCancelSupport(CancelSupport.Factory.get(chain)); | 627 setCancelSupport(CancelSupport.Factory.get(chain)); |
| 628 } | 628 } |
| 629 | 629 |
| 646 if (delegate != null) { | 646 if (delegate != null) { |
| 647 delegate.next(revisionNumber, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, nodeid, data); | 647 delegate.next(revisionNumber, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, nodeid, data); |
| 648 } | 648 } |
| 649 } catch (IOException ex) { | 649 } catch (IOException ex) { |
| 650 recordFailure(ex); | 650 recordFailure(ex); |
| 651 } catch (HgDataStreamException ex) { | 651 } catch (HgInvalidControlFileException ex) { |
| 652 recordFailure(ex.setRevisionIndex(revisionNumber)); | 652 recordFailure(ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(revisionNumber)); |
| 653 } | 653 } |
| 654 } | 654 } |
| 655 | 655 |
| 656 private int parseMetadata(DataAccess data, final int daLength, ArrayList<MetadataEntry> _metadata) throws IOException, HgDataStreamException { | 656 private int parseMetadata(DataAccess data, final int daLength, ArrayList<MetadataEntry> _metadata) throws IOException, HgInvalidControlFileException { |
| 657 int lastEntryStart = 2; | 657 int lastEntryStart = 2; |
| 658 int lastColon = -1; | 658 int lastColon = -1; |
| 659 // XXX in fact, need smth like ByteArrayBuilder, similar to StringBuilder, | 659 // XXX in fact, need smth like ByteArrayBuilder, similar to StringBuilder, |
| 660 // which can't be used here because we can't convert bytes to chars as we read them | 660 // which can't be used here because we can't convert bytes to chars as we read them |
| 661 // (there might be multi-byte encoding), and we need to collect all bytes before converting to string | 661 // (there might be multi-byte encoding), and we need to collect all bytes before converting to string |
| 703 } | 703 } |
| 704 } | 704 } |
| 705 // data.isEmpty is not reliable, renamed files of size==0 keep only metadata | 705 // data.isEmpty is not reliable, renamed files of size==0 keep only metadata |
| 706 if (!metadataIsComplete) { | 706 if (!metadataIsComplete) { |
| 707 // XXX perhaps, worth a testcase (empty file, renamed, read or ask ifCopy | 707 // XXX perhaps, worth a testcase (empty file, renamed, read or ask ifCopy |
| 708 throw new HgDataStreamException(fname, "Metadata is not closed properly", null); | 708 throw new HgInvalidControlFileException("Metadata is not closed properly", null, null); |
| 709 } | 709 } |
| 710 return lastEntryStart; | 710 return lastEntryStart; |
| 711 } | 711 } |
| 712 | 712 |
| 713 @Override | 713 @Override |
