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 |