view src/org/tmatesoft/hg/core/HgFileRevision.java @ 709:497e697636fc

Report merged lines as changed block if possible, not as a sequence of added/deleted blocks. To facilitate access to merge parent lines AddBlock got mergeLineAt() method that reports index of the line in the second parent (if any), while insertedAt() has been changed to report index in the first parent always
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 21 Aug 2013 16:23:27 +0200
parents 5050ee565bd1
children
line wrap: on
line source
/*
 * Copyright (c) 2011-2012 TMate Software Ltd
 *  
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * For information on how to redistribute this software under
 * the terms of a license other than GNU General Public License
 * contact TMate Software at support@hg4j.com
 */
package org.tmatesoft.hg.core;

import org.tmatesoft.hg.repo.HgDataFile;
import org.tmatesoft.hg.repo.HgManifest;
import org.tmatesoft.hg.repo.HgManifest.Flags;
import org.tmatesoft.hg.repo.HgRepository;
import org.tmatesoft.hg.repo.HgRuntimeException;
import org.tmatesoft.hg.util.ByteChannel;
import org.tmatesoft.hg.util.CancelledException;
import org.tmatesoft.hg.util.Pair;
import org.tmatesoft.hg.util.Path;

/**
 * Keeps together information about specific file revision
 * 
 * @author Artem Tikhomirov
 * @author TMate Software Ltd.
 */
public final class HgFileRevision {
	private final HgRepository repo;
	private final Nodeid revision;
	private final Path path;
	private Path origin;
	private Boolean isCopy = null; // null means not yet known
	private Pair<Nodeid, Nodeid> parents;
	private Flags flags; // null unless set/extracted

	/**
	 * New description of a file revision from a specific repository.
	 * 
	 * <p>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.  
	 * 
	 * @param hgRepo repository
	 * @param rev file revision
	 * @param manifestEntryFlags file flags at this revision (optional, may be null) 
	 * @param p path of the file at the given revision
	 */
	public HgFileRevision(HgRepository hgRepo, Nodeid rev, HgManifest.Flags manifestEntryFlags, Path p) {
		if (hgRepo == null || rev == null || p == null) {
			// since it's package local, it is our code to blame for non validated arguments
			throw new IllegalArgumentException();
		}
		repo = hgRepo;
		revision = rev;
		flags = manifestEntryFlags;
		path = p;
	}

	// this cons shall be used when we know whether p was a copy. Perhaps, shall pass Map<Path,Path> instead to stress orig argument is not optional  
	HgFileRevision(HgRepository hgRepo, Nodeid rev, HgManifest.Flags flags, Path p, Path orig) {
		this(hgRepo, rev, flags, p);
		isCopy = Boolean.valueOf(orig == null);
		origin = orig; 
	}
	
	HgFileRevision(HgDataFile fileNode, Nodeid fileRevision, Path origin) {
		this(fileNode.getRepo(), fileRevision, null, fileNode.getPath(), origin); 
	}
	
	public Path getPath() {
		return path;
	}

	/**
	 * Revision of the file
	 * @return never <code>null</code>
	 */
	public Nodeid getRevision() {
		return revision;
	}
	
	/**
	 * Extract flags of the file as recorded in the manifest for this file revision 
	 * @return whether regular file, executable or a symbolic link
	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
	 */
	public HgManifest.Flags getFileFlags() throws HgRuntimeException {
		if (flags == null) {
			/*
			 * Note, for uses other than HgManifestCommand or HgChangesetFileSneaker, when no flags come through the cons,
			 * it's possible to face next shortcoming:
			 * Imagine csetA and csetB, with corresponding manifestA and manifestB, the file didn't change (revision/nodeid is the same)
			 * but flag of the file has changed (e.g. became executable). Since HgFileRevision doesn't keep reference to 
			 * an actual manifest revision, but only file's, and it's likely the flags returned from this method would 
			 * yield result as from manifestA (i.e. no flag change in manifestB ever noticed).
			 */
			HgDataFile df = repo.getFileNode(path);
			int revIdx = df.getRevisionIndex(revision);
			flags = df.getFlags(revIdx);
		}
		return flags;
	}

	/**
	 * @return <code>true</code> if this file revision was created as a result of a copy/rename
	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
	 */
	public boolean wasCopied() throws HgRuntimeException {
		if (isCopy == null) {
			checkCopy();
		}
		return isCopy.booleanValue();
	}
	/**
	 * @return <code>null</code> if {@link #wasCopied()} is <code>false</code>, name of the copy source otherwise.
	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
	 */
	public Path getOriginIfCopy() throws HgRuntimeException {
		if (wasCopied()) {
			return origin;
		}
		return null;
	}

	/**
	 * Access revisions this file revision originates from.
	 * Note, these revisions are records in the file history, not that of the whole repository (aka changeset revisions) 
	 * In most cases, only one parent revision would be present, only for merge revisions one can expect both.
	 * 
	 * @return parent revisions of this file revision, with {@link Nodeid#NULL} for missing values.
	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
	 */
	public Pair<Nodeid, Nodeid> getParents() throws HgRuntimeException {
		if (parents == null) {
			HgDataFile fn = repo.getFileNode(path);
			int revisionIndex = fn.getRevisionIndex(revision);
			int[] pr = new int[2];
			byte[] p1 = new byte[20], p2 = new byte[20];
			// XXX Revlog#parents is not the best method to use here
			// need smth that gives Nodeids (piped through Pool<Nodeid> from repo's context)
			fn.parents(revisionIndex, pr, p1, p2);
			parents = new Pair<Nodeid, Nodeid>(Nodeid.fromBinary(p1, 0), Nodeid.fromBinary(p2, 0));
		}
		return parents;
	}

	/**
	 * Pipe content of this file revision into the sink
	 * @param sink accepts file revision content
	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
	 * @throws CancelledException if execution of the operation was cancelled
	 */
	public void putContentTo(ByteChannel sink) throws HgRuntimeException, CancelledException {
		HgDataFile fn = repo.getFileNode(path);
		int revisionIndex = fn.getRevisionIndex(revision);
		fn.contentWithFilters(revisionIndex, sink);
	}
	
	@Override
	public String toString() {
		return String.format("HgFileRevision(%s, %s)", getPath().toString(), revision.shortNotation());
	}

	private void checkCopy() throws HgRuntimeException {
		HgDataFile df = repo.getFileNode(path);
		int revIdx = df.getRevisionIndex(revision);
		if (df.isCopy(revIdx)) {
			isCopy = Boolean.TRUE;
			origin = df.getCopySource(revIdx).getPath();
			return;
		}
		isCopy = Boolean.FALSE;
	}
}