view src/org/tmatesoft/hg/internal/RevisionDescendants.java @ 694:7efabe0cddcf

Speed up (a) file rename history to minimize file reads; (b) file.isCopy(int) to read metadata for few revisions at once (use pattern assumes earlier revisions are likely to be queried, too); (c) HgIgnore.isIgnored by caching matched initial fragments (to substitute more expensive Matcher.matches with cheaper HashMap.contains)
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Mon, 05 Aug 2013 17:42:10 +0200
parents 690e71d29bf6
children
line wrap: on
line source
/*
 * Copyright (c) 2012-2013 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.internal;

import java.util.ArrayList;
import java.util.BitSet;

import org.tmatesoft.hg.core.Nodeid;
import org.tmatesoft.hg.repo.HgChangelog;
import org.tmatesoft.hg.repo.HgInvalidStateException;
import org.tmatesoft.hg.repo.HgRepository;
import org.tmatesoft.hg.repo.HgRuntimeException;

/**
 * Represent indicators which revisions are descendants of the supplied root revision
 * This is sort of lightweight alternative to ParentWalker#childrenOf 
 * 
 * @author Artem Tikhomirov
 * @author TMate Software Ltd.
 */
public class RevisionDescendants {

	private final HgRepository repo;
	private final int rootRevIndex;
	private final int tipRevIndex; // this is the last revision we cache to
	private final BitSet descendants;
	private RevisionSet revset;

	// in fact, may be refactored to deal not only with changelog, but any revlog (not sure what would be the usecase, though)
	public RevisionDescendants(HgRepository hgRepo, int revisionIndex) throws HgRuntimeException {
		repo = hgRepo;
		rootRevIndex = revisionIndex;
		// even if tip moves, we still answer correctly for those isCandidate()
		tipRevIndex = repo.getChangelog().getLastRevision(); 
		if (revisionIndex < 0 || revisionIndex > tipRevIndex) {
			String m = "Revision to build descendants for shall be in range [%d,%d], not %d";
			throw new IllegalArgumentException(String.format(m, 0, tipRevIndex, revisionIndex));
		}
		descendants = new BitSet(tipRevIndex - rootRevIndex + 1);
	}
	
	public void build() throws HgRuntimeException {
		final BitSet result = descendants;
		result.set(0);
		if (rootRevIndex == tipRevIndex) {
			return;
		}
		repo.getChangelog().indexWalk(rootRevIndex+1, tipRevIndex, new HgChangelog.ParentInspector() {
			// TODO ParentRevisionInspector, with no parent nodeids, just indexes?

			private int i = 1; // above we start with revision next to rootRevIndex, which is at offset 0
			public void next(int revisionIndex, Nodeid revision, int parent1, int parent2, Nodeid nidParent1, Nodeid nidParent2) {
				int p1x = parent1 - rootRevIndex;
				int p2x = parent2 - rootRevIndex;
				boolean p1IsDescendant = false, p2IsDescendant = false;
				if (p1x >= 0) { // parent1 is among descendants candidates
					assert p1x < result.size();
					p1IsDescendant = result.get(p1x);
				}
				if (p2x >= 0) {
					assert p2x < result.size();
					p2IsDescendant = result.get(p2x);
				}
				//
				int rx = revisionIndex - rootRevIndex;
				if (rx != i) {
					throw new HgInvalidStateException(String.format("Sanity check failed. Revision %d. Expected:%d, was:%d", revisionIndex, rx, i));
				}
				// current revision is descendant if any of its parents is descendant
				result.set(rx, p1IsDescendant || p2IsDescendant);
				i++;
			}
		});
	}

	// deliberately doesn't allow TIP
	public boolean isCandidate(int revIndex) {
		return (revIndex >= rootRevIndex && revIndex <= tipRevIndex) ;
	}

	public boolean hasDescendants() { // isEmpty is better name?
		// bit at rootRevIndex is always set
		return descendants.nextSetBit(rootRevIndex+1) != -1;
	}

	/**
	 * Tells whether specified revision is on a descent line from the root revision.
	 * <p>NOTE, root revision itself is considered to be its own descendant.
	 * 
	 * @param revisionIndex revision index to check, shall pass {@link #isCandidate(int)}
	 * @return <code>true</code> if revision is descendant of or is the same as root revision
	 */
	public boolean isDescendant(int revisionIndex) {
		assert isCandidate(revisionIndex);
		int ix = revisionIndex - rootRevIndex;
		assert ix < descendants.size();
		return descendants.get(ix);
	}

	public RevisionSet asRevisionSet() {
		if (revset == null) {
			final ArrayList<Nodeid> revisions = new ArrayList<Nodeid>(descendants.cardinality());
			repo.getChangelog().indexWalk(rootRevIndex, tipRevIndex, new HgChangelog.RevisionInspector() {

				public void next(int revisionIndex, Nodeid revision, int linkedRevisionIndex) throws HgRuntimeException {
					if (isDescendant(revisionIndex)) {
						revisions.add(revision);
					}
				}
			});
			assert revisions.size() == descendants.cardinality();
			revset = new RevisionSet(revisions);
		}
		return revset;
	}
}