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