tikhomirov@231: /* tikhomirov@396: * Copyright (c) 2011-2012 TMate Software Ltd tikhomirov@231: * tikhomirov@231: * This program is free software; you can redistribute it and/or modify tikhomirov@231: * it under the terms of the GNU General Public License as published by tikhomirov@231: * the Free Software Foundation; version 2 of the License. tikhomirov@231: * tikhomirov@231: * This program is distributed in the hope that it will be useful, tikhomirov@231: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@231: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@231: * GNU General Public License for more details. tikhomirov@231: * tikhomirov@231: * For information on how to redistribute this software under tikhomirov@231: * the terms of a license other than GNU General Public License tikhomirov@231: * contact TMate Software at support@hg4j.com tikhomirov@231: */ tikhomirov@231: package org.tmatesoft.hg.core; tikhomirov@231: tikhomirov@231: import org.tmatesoft.hg.repo.HgDataFile; tikhomirov@415: import org.tmatesoft.hg.repo.HgManifest; tikhomirov@415: import org.tmatesoft.hg.repo.HgManifest.Flags; tikhomirov@231: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@427: import org.tmatesoft.hg.repo.HgRuntimeException; tikhomirov@231: import org.tmatesoft.hg.util.ByteChannel; tikhomirov@231: import org.tmatesoft.hg.util.CancelledException; tikhomirov@345: import org.tmatesoft.hg.util.Pair; tikhomirov@231: import org.tmatesoft.hg.util.Path; tikhomirov@231: tikhomirov@231: /** tikhomirov@231: * Keeps together information about specific file revision tikhomirov@231: * tikhomirov@231: * @author Artem Tikhomirov tikhomirov@231: * @author TMate Software Ltd. tikhomirov@231: */ tikhomirov@249: public final class HgFileRevision { tikhomirov@231: private final HgRepository repo; tikhomirov@231: private final Nodeid revision; tikhomirov@231: private final Path path; tikhomirov@316: private Path origin; tikhomirov@316: private Boolean isCopy = null; // null means not yet known tikhomirov@345: private Pair parents; tikhomirov@415: private Flags flags; // null unless set/extracted tikhomirov@316: tikhomirov@415: /** tikhomirov@431: * New description of a file revision from a specific repository. tikhomirov@431: * tikhomirov@431: *

Although this constructor is public, and clients can use it to construct own file revisions to pass e.g. to commands, its use is discouraged. tikhomirov@415: * tikhomirov@415: * @param hgRepo repository tikhomirov@415: * @param rev file revision tikhomirov@415: * @param manifestEntryFlags file flags at this revision (optional, may be null) tikhomirov@415: * @param p path of the file at the given revision tikhomirov@415: */ tikhomirov@415: public HgFileRevision(HgRepository hgRepo, Nodeid rev, HgManifest.Flags manifestEntryFlags, Path p) { tikhomirov@231: if (hgRepo == null || rev == null || p == null) { tikhomirov@231: // since it's package local, it is our code to blame for non validated arguments tikhomirov@316: throw new IllegalArgumentException(); tikhomirov@231: } tikhomirov@231: repo = hgRepo; tikhomirov@231: revision = rev; tikhomirov@415: flags = manifestEntryFlags; tikhomirov@231: path = p; tikhomirov@231: } tikhomirov@316: tikhomirov@316: // this cons shall be used when we know whether p was a copy. Perhaps, shall pass Map instead to stress orig argument is not optional tikhomirov@415: HgFileRevision(HgRepository hgRepo, Nodeid rev, HgManifest.Flags flags, Path p, Path orig) { tikhomirov@415: this(hgRepo, rev, flags, p); tikhomirov@316: isCopy = Boolean.valueOf(orig == null); tikhomirov@316: origin = orig; tikhomirov@316: } tikhomirov@231: tikhomirov@518: HgFileRevision(HgDataFile fileNode, Nodeid fileRevision, Path origin) { tikhomirov@514: this(fileNode.getRepo(), fileRevision, null, fileNode.getPath(), origin); tikhomirov@514: } tikhomirov@514: tikhomirov@231: public Path getPath() { tikhomirov@231: return path; tikhomirov@231: } tikhomirov@364: tikhomirov@514: /** tikhomirov@514: * Revision of the file tikhomirov@514: * @return never null tikhomirov@514: */ tikhomirov@231: public Nodeid getRevision() { tikhomirov@231: return revision; tikhomirov@231: } tikhomirov@415: tikhomirov@415: /** tikhomirov@444: * Extract flags of the file as recorded in the manifest for this file revision tikhomirov@444: * @return whether regular file, executable or a symbolic link tikhomirov@427: * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception tikhomirov@415: */ tikhomirov@427: public HgManifest.Flags getFileFlags() throws HgRuntimeException { tikhomirov@415: if (flags == null) { tikhomirov@444: /* tikhomirov@444: * Note, for uses other than HgManifestCommand or HgChangesetFileSneaker, when no flags come through the cons, tikhomirov@444: * it's possible to face next shortcoming: tikhomirov@444: * Imagine csetA and csetB, with corresponding manifestA and manifestB, the file didn't change (revision/nodeid is the same) tikhomirov@444: * but flag of the file has changed (e.g. became executable). Since HgFileRevision doesn't keep reference to tikhomirov@444: * an actual manifest revision, but only file's, and it's likely the flags returned from this method would tikhomirov@444: * yield result as from manifestA (i.e. no flag change in manifestB ever noticed). tikhomirov@444: */ tikhomirov@415: HgDataFile df = repo.getFileNode(path); tikhomirov@415: int revIdx = df.getRevisionIndex(revision); tikhomirov@415: flags = df.getFlags(revIdx); tikhomirov@415: } tikhomirov@415: return flags; tikhomirov@415: } tikhomirov@364: tikhomirov@628: /** tikhomirov@628: * @return true if this file revision was created as a result of a copy/rename tikhomirov@628: * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception tikhomirov@628: */ tikhomirov@628: public boolean wasCopied() throws HgRuntimeException { tikhomirov@316: if (isCopy == null) { tikhomirov@316: checkCopy(); tikhomirov@316: } tikhomirov@316: return isCopy.booleanValue(); tikhomirov@316: } tikhomirov@316: /** tikhomirov@316: * @return null if {@link #wasCopied()} is false, name of the copy source otherwise. tikhomirov@628: * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception tikhomirov@316: */ tikhomirov@628: public Path getOriginIfCopy() throws HgRuntimeException { tikhomirov@316: if (wasCopied()) { tikhomirov@316: return origin; tikhomirov@316: } tikhomirov@316: return null; tikhomirov@316: } tikhomirov@316: tikhomirov@345: /** tikhomirov@345: * Access revisions this file revision originates from. tikhomirov@345: * Note, these revisions are records in the file history, not that of the whole repository (aka changeset revisions) tikhomirov@345: * In most cases, only one parent revision would be present, only for merge revisions one can expect both. tikhomirov@345: * tikhomirov@345: * @return parent revisions of this file revision, with {@link Nodeid#NULL} for missing values. tikhomirov@427: * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception tikhomirov@345: */ tikhomirov@427: public Pair getParents() throws HgRuntimeException { tikhomirov@345: if (parents == null) { tikhomirov@345: HgDataFile fn = repo.getFileNode(path); tikhomirov@367: int revisionIndex = fn.getRevisionIndex(revision); tikhomirov@345: int[] pr = new int[2]; tikhomirov@345: byte[] p1 = new byte[20], p2 = new byte[20]; tikhomirov@345: // XXX Revlog#parents is not the best method to use here tikhomirov@345: // need smth that gives Nodeids (piped through Pool from repo's context) tikhomirov@367: fn.parents(revisionIndex, pr, p1, p2); tikhomirov@345: parents = new Pair(Nodeid.fromBinary(p1, 0), Nodeid.fromBinary(p2, 0)); tikhomirov@345: } tikhomirov@345: return parents; tikhomirov@345: } tikhomirov@345: tikhomirov@628: /** tikhomirov@628: * Pipe content of this file revision into the sink tikhomirov@628: * @param sink accepts file revision content tikhomirov@628: * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception tikhomirov@628: * @throws CancelledException if execution of the operation was cancelled tikhomirov@628: */ tikhomirov@628: public void putContentTo(ByteChannel sink) throws HgRuntimeException, CancelledException { tikhomirov@231: HgDataFile fn = repo.getFileNode(path); tikhomirov@367: int revisionIndex = fn.getRevisionIndex(revision); tikhomirov@367: fn.contentWithFilters(revisionIndex, sink); tikhomirov@231: } tikhomirov@519: tikhomirov@519: @Override tikhomirov@519: public String toString() { tikhomirov@519: return String.format("HgFileRevision(%s, %s)", getPath().toString(), revision.shortNotation()); tikhomirov@519: } tikhomirov@231: tikhomirov@628: private void checkCopy() throws HgRuntimeException { tikhomirov@689: HgDataFile df = repo.getFileNode(path); tikhomirov@689: int revIdx = df.getRevisionIndex(revision); tikhomirov@689: if (df.isCopy(revIdx)) { tikhomirov@689: isCopy = Boolean.TRUE; tikhomirov@689: origin = df.getCopySource(revIdx).getPath(); tikhomirov@689: return; tikhomirov@316: } tikhomirov@316: isCopy = Boolean.FALSE; tikhomirov@316: } tikhomirov@231: }