tikhomirov@107: /* tikhomirov@107: * Copyright (c) 2011 TMate Software Ltd tikhomirov@107: * tikhomirov@107: * This program is free software; you can redistribute it and/or modify tikhomirov@107: * it under the terms of the GNU General Public License as published by tikhomirov@107: * the Free Software Foundation; version 2 of the License. tikhomirov@107: * tikhomirov@107: * This program is distributed in the hope that it will be useful, tikhomirov@107: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@107: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@107: * GNU General Public License for more details. tikhomirov@107: * tikhomirov@107: * For information on how to redistribute this software under tikhomirov@107: * the terms of a license other than GNU General Public License tikhomirov@130: * contact TMate Software at support@hg4j.com tikhomirov@107: */ tikhomirov@107: package org.tmatesoft.hg.core; tikhomirov@107: tikhomirov@148: import static org.tmatesoft.hg.repo.HgInternals.wrongLocalRevision; tikhomirov@107: import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION; tikhomirov@107: import static org.tmatesoft.hg.repo.HgRepository.TIP; tikhomirov@107: tikhomirov@107: import java.io.FileNotFoundException; tikhomirov@322: import java.io.IOException; tikhomirov@322: import java.nio.ByteBuffer; tikhomirov@107: tikhomirov@107: import org.tmatesoft.hg.repo.HgDataFile; tikhomirov@107: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@322: import org.tmatesoft.hg.util.Adaptable; tikhomirov@115: import org.tmatesoft.hg.util.ByteChannel; tikhomirov@322: import org.tmatesoft.hg.util.CancelSupport; tikhomirov@148: import org.tmatesoft.hg.util.CancelledException; tikhomirov@133: import org.tmatesoft.hg.util.Path; tikhomirov@107: tikhomirov@107: /** tikhomirov@131: * Command to obtain content of a file, 'hg cat' counterpart. tikhomirov@107: * tikhomirov@107: * @author Artem Tikhomirov tikhomirov@107: * @author TMate Software Ltd. tikhomirov@107: */ tikhomirov@215: public class HgCatCommand extends HgAbstractCommand { tikhomirov@107: tikhomirov@107: private final HgRepository repo; tikhomirov@107: private Path file; tikhomirov@107: private int localRevision = TIP; tikhomirov@107: private Nodeid revision; tikhomirov@232: private Nodeid cset; tikhomirov@107: tikhomirov@131: public HgCatCommand(HgRepository hgRepo) { tikhomirov@107: repo = hgRepo; tikhomirov@107: } tikhomirov@107: tikhomirov@148: /** tikhomirov@148: * File to read, required parameter tikhomirov@148: * @param fname path to a repository file, can't be null tikhomirov@148: * @return this for convenience tikhomirov@148: * @throws IllegalArgumentException if supplied fname is null or points to directory tikhomirov@148: */ tikhomirov@131: public HgCatCommand file(Path fname) { tikhomirov@148: if (fname == null || fname.isDirectory()) { tikhomirov@148: throw new IllegalArgumentException(String.valueOf(fname)); tikhomirov@148: } tikhomirov@107: file = fname; tikhomirov@107: return this; tikhomirov@107: } tikhomirov@107: tikhomirov@148: /** tikhomirov@232: * Select specific local revision of the file to cat. Note, revision numbering is of particular file, not that of tikhomirov@232: * repository (i.e. revision 0 means initial content of the file, irrespective of changeset revision at the time of commit) tikhomirov@232: * tikhomirov@148: * Invocation of this method clears revision set with {@link #revision(Nodeid)} or {@link #revision(int)} earlier. tikhomirov@232: * tikhomirov@148: * XXX rev can't be WORKING_COPY (if allowed, need to implement in #execute()) tikhomirov@148: * @param rev local revision number, non-negative, or one of predefined constants. Note, use of {@link HgRepository#BAD_REVISION}, tikhomirov@148: * although possible, makes little sense (command would fail if executed). tikhomirov@148: * @return this for convenience tikhomirov@148: */ tikhomirov@131: public HgCatCommand revision(int rev) { tikhomirov@148: if (wrongLocalRevision(rev)) { tikhomirov@148: throw new IllegalArgumentException(String.valueOf(rev)); tikhomirov@148: } tikhomirov@107: localRevision = rev; tikhomirov@107: revision = null; tikhomirov@232: cset = null; tikhomirov@107: return this; tikhomirov@107: } tikhomirov@107: tikhomirov@148: /** tikhomirov@232: * Select revision to read. Note, this revision is file revision (i.e. the one from manifest), not the changeset revision. tikhomirov@232: * tikhomirov@232: * Invocation of this method clears revision set with {@link #revision(int)} or {@link #revision(Nodeid)} earlier. tikhomirov@148: * tikhomirov@232: * @param nodeid - unique file revision identifier, Note, use of null or {@link Nodeid#NULL} is senseless tikhomirov@148: * @return this for convenience tikhomirov@148: */ tikhomirov@131: public HgCatCommand revision(Nodeid nodeid) { tikhomirov@148: if (nodeid != null && nodeid.isNull()) { tikhomirov@148: nodeid = null; tikhomirov@148: } tikhomirov@107: revision = nodeid; tikhomirov@107: localRevision = BAD_REVISION; tikhomirov@232: cset = null; tikhomirov@232: return this; tikhomirov@232: } tikhomirov@248: tikhomirov@248: /** tikhomirov@248: * Parameterize the command from file revision object. tikhomirov@248: * tikhomirov@248: * @param fileRev file revision to cat tikhomirov@248: * @return this for convenience tikhomirov@248: */ tikhomirov@248: public HgCatCommand revision(HgFileRevision fileRev) { tikhomirov@248: return file(fileRev.getPath()).revision(fileRev.getRevision()); tikhomirov@248: } tikhomirov@232: tikhomirov@232: /** tikhomirov@232: * Select whatever revision of the file that was actual at the time of the specified changeset. Unlike {@link #revision(int)} or {@link #revision(Nodeid)}, this method tikhomirov@232: * operates in terms of repository global revisions (aka changesets). tikhomirov@232: * tikhomirov@232: * Invocation of this method clears local file revisions selection. tikhomirov@232: * tikhomirov@232: * @param nodeid changeset revision tikhomirov@232: * @return this for convenience tikhomirov@232: */ tikhomirov@232: public HgCatCommand changeset(Nodeid nodeid) { tikhomirov@232: localRevision = BAD_REVISION; tikhomirov@232: revision = null; tikhomirov@232: cset = nodeid; tikhomirov@107: return this; tikhomirov@107: } tikhomirov@107: tikhomirov@148: /** tikhomirov@148: * Runs the command with current set of parameters and pipes data to provided sink. tikhomirov@148: * tikhomirov@148: * @param sink output channel to write data to. tikhomirov@148: * @throws HgDataStreamException tikhomirov@148: * @throws IllegalArgumentException when command arguments are incomplete or wrong tikhomirov@148: */ tikhomirov@354: public void execute(ByteChannel sink) throws HgDataStreamException, HgInvalidControlFileException, CancelledException { tikhomirov@232: if (localRevision == BAD_REVISION && revision == null && cset == null) { tikhomirov@232: throw new IllegalArgumentException("File revision, corresponing local number, or a changset nodeid shall be specified"); tikhomirov@107: } tikhomirov@107: if (file == null) { tikhomirov@107: throw new IllegalArgumentException("Name of the file is missing"); tikhomirov@107: } tikhomirov@115: if (sink == null) { tikhomirov@148: throw new IllegalArgumentException("Need an output channel"); tikhomirov@107: } tikhomirov@107: HgDataFile dataFile = repo.getFileNode(file); tikhomirov@107: if (!dataFile.exists()) { tikhomirov@215: throw new HgDataStreamException(file, new FileNotFoundException(file.toString())); tikhomirov@107: } tikhomirov@115: int revToExtract; tikhomirov@232: if (cset != null) { tikhomirov@232: int csetRev = repo.getChangelog().getLocalRevision(cset); tikhomirov@232: Nodeid toExtract = null; tikhomirov@232: do { tikhomirov@232: toExtract = repo.getManifest().getFileRevision(csetRev, file); tikhomirov@232: if (toExtract == null) { tikhomirov@232: if (dataFile.isCopy()) { tikhomirov@232: file = dataFile.getCopySourceName(); tikhomirov@232: dataFile = repo.getFileNode(file); tikhomirov@232: } else { tikhomirov@232: break; tikhomirov@232: } tikhomirov@232: } tikhomirov@232: } while (toExtract == null); tikhomirov@232: if (toExtract == null) { tikhomirov@248: throw new HgBadStateException(String.format("File %s nor its origins were not known at repository %s revision", file, cset.shortNotation())); tikhomirov@232: } tikhomirov@232: revToExtract = dataFile.getLocalRevision(toExtract); tikhomirov@232: } else if (revision != null) { tikhomirov@115: revToExtract = dataFile.getLocalRevision(revision); tikhomirov@107: } else { tikhomirov@115: revToExtract = localRevision; tikhomirov@107: } tikhomirov@322: ByteChannel sinkWrap; tikhomirov@322: if (getCancelSupport(null, false) == null) { tikhomirov@322: // no command-specific cancel helper, no need for extra proxy tikhomirov@322: // sink itself still may supply CS tikhomirov@322: sinkWrap = sink; tikhomirov@322: } else { tikhomirov@322: // try CS from sink, if any. at least there is CS from command tikhomirov@322: CancelSupport cancelHelper = getCancelSupport(sink, true); tikhomirov@322: cancelHelper.checkCancelled(); tikhomirov@322: sinkWrap = new ByteChannelProxy(sink, cancelHelper); tikhomirov@322: } tikhomirov@322: dataFile.contentWithFilters(revToExtract, sinkWrap); tikhomirov@322: } tikhomirov@322: tikhomirov@322: private static class ByteChannelProxy implements ByteChannel, Adaptable { tikhomirov@322: private final ByteChannel delegate; tikhomirov@322: private final CancelSupport cancelHelper; tikhomirov@322: tikhomirov@322: public ByteChannelProxy(ByteChannel _delegate, CancelSupport cs) { tikhomirov@322: assert _delegate != null; tikhomirov@322: delegate = _delegate; tikhomirov@322: cancelHelper = cs; tikhomirov@322: } tikhomirov@322: public int write(ByteBuffer buffer) throws IOException, CancelledException { tikhomirov@322: return delegate.write(buffer); tikhomirov@322: } tikhomirov@322: tikhomirov@322: public T getAdapter(Class adapterClass) { tikhomirov@322: if (CancelSupport.class == adapterClass) { tikhomirov@322: return adapterClass.cast(cancelHelper); tikhomirov@322: } tikhomirov@322: if (delegate instanceof Adaptable) { tikhomirov@322: return ((Adaptable) delegate).getAdapter(adapterClass); tikhomirov@322: } tikhomirov@322: if (adapterClass.isInstance(delegate)) { tikhomirov@322: return adapterClass.cast(delegate); tikhomirov@322: } tikhomirov@322: return null; tikhomirov@322: } tikhomirov@107: } tikhomirov@107: }