changeset 596:43cfa08ff3fd

HgBlameFacility refactoring: extract code to build file history that spans renames
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 02 May 2013 19:23:53 +0200
parents 92c3ad9c2a51
children c895b5556937
files src/org/tmatesoft/hg/core/HgLogCommand.java src/org/tmatesoft/hg/internal/FileHistory.java src/org/tmatesoft/hg/internal/FileRevisionHistoryChunk.java src/org/tmatesoft/hg/internal/ReverseIterator.java src/org/tmatesoft/hg/repo/HgBlameFacility.java
diffstat 5 files changed, 371 insertions(+), 234 deletions(-) [+]
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/core/HgLogCommand.java	Thu May 02 19:23:35 2013 +0200
+++ b/src/org/tmatesoft/hg/core/HgLogCommand.java	Thu May 02 19:23:53 2013 +0200
@@ -30,7 +30,6 @@
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.Set;
 import java.util.TreeSet;
 
@@ -42,6 +41,7 @@
 import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.internal.Lifecycle;
 import org.tmatesoft.hg.internal.LifecycleProxy;
+import org.tmatesoft.hg.internal.ReverseIterator;
 import org.tmatesoft.hg.repo.HgChangelog;
 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
 import org.tmatesoft.hg.repo.HgDataFile;
@@ -446,12 +446,7 @@
 		}
 		
 		public Iterable<BatchRecord> iterate(final boolean reverse) {
-			return new Iterable<BatchRecord>() {
-				
-				public Iterator<BatchRecord> iterator() {
-					return reverse ? new ReverseIterator<BatchRecord>(batch) : batch.iterator();
-				}
-			};
+			return reverse ? ReverseIterator.reversed(batch) : batch;
 		}
 		
 		// alternative would be dispatch(HgChangelog.Inspector) and dispatchReverse()
@@ -563,24 +558,6 @@
 		progressHelper.done();
 	}
 	
-	private static class ReverseIterator<E> implements Iterator<E> {
-		private final ListIterator<E> listIterator;
-		
-		public ReverseIterator(List<E> list) {
-			listIterator = list.listIterator(list.size());
-		}
-
-		public boolean hasNext() {
-			return listIterator.hasPrevious();
-		}
-		public E next() {
-			return listIterator.previous();
-		}
-		public void remove() {
-			listIterator.remove();
-		}
-	}
-
 	/**
 	 * Utility to build sequence of file renames
 	 */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/FileHistory.java	Thu May 02 19:23:53 2013 +0200
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 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 static org.tmatesoft.hg.core.HgIterateDirection.NewToOld;
+
+import java.util.Collections;
+import java.util.LinkedList;
+
+import org.tmatesoft.hg.core.HgIterateDirection;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgRepository;
+
+/**
+ * History of a file, with copy/renames, and corresponding revision information.
+ * Facility for file history iteration. 
+ * 
+ * FIXME [post-1.1] Utilize in HgLogCommand and anywhere else we need to follow file history
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class FileHistory {
+	
+	private LinkedList<FileRevisionHistoryChunk> fileCompleteHistory = new LinkedList<FileRevisionHistoryChunk>();
+	private final HgDataFile df;
+	private final int csetTo;
+	private final int csetFrom;
+	
+	public FileHistory(HgDataFile file, int fromChangeset, int toChangeset) {
+		df = file;
+		csetFrom = fromChangeset;
+		csetTo = toChangeset;
+	}
+	
+	public int getStartChangeset() {
+		return csetFrom;
+	}
+	
+	public int getEndChangeset() {
+		return csetTo;
+	}
+
+	public void build() {
+		assert fileCompleteHistory.isEmpty();
+		HgDataFile currentFile = df;
+		final int changelogRevIndexEnd = csetTo;
+		final int changelogRevIndexStart = csetFrom;
+		int fileLastClogRevIndex = changelogRevIndexEnd;
+		FileRevisionHistoryChunk nextChunk = null;
+		fileCompleteHistory.clear(); // just in case, #build() is not expected to be called more than once
+		do {
+			FileRevisionHistoryChunk fileHistory = new FileRevisionHistoryChunk(currentFile);
+			fileHistory.init(fileLastClogRevIndex);
+			fileHistory.linkTo(nextChunk);
+			fileCompleteHistory.addFirst(fileHistory); // to get the list in old-to-new order
+			nextChunk = fileHistory;
+			if (fileHistory.changeset(0) > changelogRevIndexStart && currentFile.isCopy()) {
+				// fileHistory.changeset(0) is the earliest revision we know about so far,
+				// once we get to revisions earlier than the requested start, stop digging.
+				// The reason there's NO == (i.e. not >=) because:
+				// (easy): once it's equal, we've reached our intended start
+				// (hard): if changelogRevIndexStart happens to be exact start of one of renames in the 
+				// chain of renames (test-annotate2 repository, file1->file1a->file1b, i.e. points 
+				// to the very start of file1a or file1 history), presence of == would get us to the next 
+				// chunk and hence changed parents of present chunk's first element. Our annotate alg 
+				// relies on parents only (i.e. knows nothing about 'last iteration element') to find out 
+				// what to compare, and hence won't report all lines of 'last iteration element' (which is the
+				// first revision of the renamed file) as "added in this revision", leaving gaps in annotate
+				HgRepository repo = currentFile.getRepo();
+				Nodeid originLastRev = currentFile.getCopySourceRevision();
+				currentFile = repo.getFileNode(currentFile.getCopySourceName());
+				fileLastClogRevIndex = currentFile.getChangesetRevisionIndex(currentFile.getRevisionIndex(originLastRev));
+				// XXX perhaps, shall fail with meaningful exception if new file doesn't exist (.i/.d not found for whatever reason)
+				// or source revision is missing?
+			} else {
+				fileHistory.chopAtChangeset(changelogRevIndexStart);
+				currentFile = null; // stop iterating
+			}
+		} while (currentFile != null && fileLastClogRevIndex > changelogRevIndexStart);
+		// fileCompleteHistory is in (origin, intermediate target, ultimate target) order
+	}
+	
+	public Iterable<FileRevisionHistoryChunk> iterate(HgIterateDirection order) {
+		if (order == NewToOld) {
+			return ReverseIterator.reversed(fileCompleteHistory);
+		}
+		return Collections.unmodifiableList(fileCompleteHistory);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/FileRevisionHistoryChunk.java	Thu May 02 19:23:53 2013 +0200
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 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 static org.tmatesoft.hg.core.HgIterateDirection.OldToNew;
+import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
+import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.LinkedList;
+
+import org.tmatesoft.hg.core.HgIterateDirection;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgRepository;
+
+/**
+ * Piece of file history, identified by path, limited to file revisions from range [chop..init] of changesets, 
+ * can be linked to another piece.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public final class FileRevisionHistoryChunk {
+	private final HgDataFile df;
+	// change ancestry, sequence of file revisions
+	private IntVector fileRevsToVisit;
+	// parent pairs of complete file history
+	private IntVector fileParentRevs;
+	// map file revision to changelog revision (sparse array, only file revisions to visit are set)
+	private int[] file2changelog;
+	private int originChangelogRev = BAD_REVISION, originFileRev = BAD_REVISION;
+	private int csetRangeStart = NO_REVISION, csetRangeEnd = BAD_REVISION; 
+	
+
+	public FileRevisionHistoryChunk(HgDataFile file) {
+		df = file;
+	}
+	
+	/**
+	 * @return file at this specific chunk of history (i.e. its path may be different from the paths of other chunks)
+	 */
+	public HgDataFile getFile() {
+		return df;
+	}
+	
+	/**
+	 * @return changeset this file history chunk was chopped at, or {@link HgRepository#NO_REVISION} if none specified
+	 */
+	public int getStartChangeset() {
+		return csetRangeStart;
+	}
+	
+	/**
+	 * @return changeset this file history chunk ends at
+	 */
+	public int getEndChangeset() {
+		return csetRangeEnd;
+	}
+	
+	public void init(int changelogRevisionIndex) {
+		csetRangeEnd = changelogRevisionIndex;
+		// XXX df.indexWalk(0, fileRevIndex, ) might be more effective
+		Nodeid fileRev = df.getRepo().getManifest().getFileRevision(changelogRevisionIndex, df.getPath());
+		int fileRevIndex = df.getRevisionIndex(fileRev);
+		int[] fileRevParents = new int[2];
+		fileParentRevs = new IntVector((fileRevIndex+1) * 2, 0);
+		fileParentRevs.add(NO_REVISION, NO_REVISION); // parents of fileRevIndex == 0
+		for (int i = 1; i <= fileRevIndex; i++) {
+			df.parents(i, fileRevParents, null, null);
+			fileParentRevs.add(fileRevParents[0], fileRevParents[1]);
+		}
+		// fileRevsToVisit keep file change ancestry from new to old
+		fileRevsToVisit = new IntVector(fileRevIndex + 1, 0);
+		// keep map of file revision to changelog revision
+		file2changelog = new int[fileRevIndex+1];
+		// only elements worth visit would get mapped, so there would be unfilled areas in the file2changelog,
+		// prevent from error (make it explicit) by bad value
+		Arrays.fill(file2changelog, BAD_REVISION);
+		LinkedList<Integer> queue = new LinkedList<Integer>();
+		BitSet seen = new BitSet(fileRevIndex + 1);
+		queue.add(fileRevIndex);
+		do {
+			int x = queue.removeFirst();
+			if (seen.get(x)) {
+				continue;
+			}
+			seen.set(x);
+			fileRevsToVisit.add(x);
+			file2changelog[x] = df.getChangesetRevisionIndex(x);
+			int p1 = fileParentRevs.get(2*x);
+			int p2 = fileParentRevs.get(2*x + 1);
+			if (p1 != NO_REVISION) {
+				queue.addLast(p1);
+			}
+			if (p2 != NO_REVISION) {
+				queue.addLast(p2);
+			}
+		} while (!queue.isEmpty());
+		// make sure no child is processed before we handled all (grand-)parents of the element
+		fileRevsToVisit.sort(false);
+	}
+	
+	public void linkTo(FileRevisionHistoryChunk target) {
+		// assume that target.init() has been called already 
+		if (target == null) {
+			return;
+		}
+		target.originFileRev = fileRevsToVisit.get(0); // files to visit are new to old
+		target.originChangelogRev = changeset(target.originFileRev);
+	}
+
+	/**
+	 * Mark revision closest(ceil) to specified as the very first one (no parents) 
+	 */
+	public void chopAtChangeset(int firstChangelogRevOfInterest) {
+		csetRangeStart = firstChangelogRevOfInterest;
+		if (firstChangelogRevOfInterest == 0) {
+			return; // nothing to do
+		}
+		int i = 0, x = fileRevsToVisit.size(), fileRev = BAD_REVISION;
+		// fileRevsToVisit is new to old, greater numbers to smaller
+		while (i < x && changeset(fileRev = fileRevsToVisit.get(i)) >= firstChangelogRevOfInterest) {
+			i++;
+		}
+		assert fileRev != BAD_REVISION; // there's at least 1 revision in fileRevsToVisit
+		if (i == x && changeset(fileRev) != firstChangelogRevOfInterest) {
+			assert false : "Requested changeset shall belong to the chunk";
+			return;
+		}
+		fileRevsToVisit.trimTo(i); // no need to iterate more
+		// pretend fileRev got no parents
+		fileParentRevs.set(fileRev * 2, NO_REVISION);
+		fileParentRevs.set(fileRev, NO_REVISION);
+	}
+
+	public int[] fileRevisions(HgIterateDirection iterateOrder) {
+		// fileRevsToVisit is { r10, r7, r6, r5, r0 }, new to old
+		int[] rv = fileRevsToVisit.toArray();
+		if (iterateOrder == OldToNew) {
+			// reverse return value
+			for (int a = 0, b = rv.length-1; a < b; a++, b--) {
+				int t = rv[b];
+				rv[b] = rv[a];
+				rv[a] = t;
+			}
+		}
+		return rv;
+	}
+	
+	public int changeset(int fileRevIndex) {
+		return file2changelog[fileRevIndex];
+	}
+	
+	public void fillFileParents(int fileRevIndex, int[] fileParents) {
+		if (fileRevIndex == 0 && originFileRev != BAD_REVISION) {
+			// this chunk continues another file
+			assert originFileRev != NO_REVISION;
+			fileParents[0] = originFileRev;
+			fileParents[1] = NO_REVISION;
+			return;
+		}
+		fileParents[0] = fileParentRevs.get(fileRevIndex * 2);
+		fileParents[1] = fileParentRevs.get(fileRevIndex * 2 + 1);
+	}
+	
+	public void fillCsetParents(int fileRevIndex, int[] csetParents) {
+		if (fileRevIndex == 0 && originFileRev != BAD_REVISION) {
+			assert originFileRev != NO_REVISION;
+			csetParents[0] = originChangelogRev;
+			csetParents[1] = NO_REVISION; // I wonder if possible to start a copy with two parents?
+			return;
+		}
+		int fp1 = fileParentRevs.get(fileRevIndex * 2);
+		int fp2 = fileParentRevs.get(fileRevIndex * 2 + 1);
+		csetParents[0] = fp1 == NO_REVISION ? NO_REVISION : changeset(fp1);
+		csetParents[1] = fp2 == NO_REVISION ? NO_REVISION : changeset(fp2);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/ReverseIterator.java	Thu May 02 19:23:53 2013 +0200
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 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.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class ReverseIterator<E> implements Iterator<E> {
+	private final ListIterator<E> listIterator;
+	
+	public ReverseIterator(List<E> list) {
+		listIterator = list.listIterator(list.size());
+	}
+
+	public boolean hasNext() {
+		return listIterator.hasPrevious();
+	}
+	public E next() {
+		return listIterator.previous();
+	}
+	public void remove() {
+		listIterator.remove();
+	}
+
+	public static <T> Iterable<T> reversed(final List<T> list) {
+		return new Iterable<T>() {
+
+			public Iterator<T> iterator() {
+				return new ReverseIterator<T>(list);
+			}
+		};
+	}
+}
\ No newline at end of file
--- a/src/org/tmatesoft/hg/repo/HgBlameFacility.java	Thu May 02 19:23:35 2013 +0200
+++ b/src/org/tmatesoft/hg/repo/HgBlameFacility.java	Thu May 02 19:23:53 2013 +0200
@@ -16,15 +16,10 @@
  */
 package org.tmatesoft.hg.repo;
 
-import static org.tmatesoft.hg.core.HgIterateDirection.NewToOld;
 import static org.tmatesoft.hg.core.HgIterateDirection.OldToNew;
 import static org.tmatesoft.hg.repo.HgInternals.wrongRevisionIndex;
-import static org.tmatesoft.hg.repo.HgRepository.*;
-
-import java.util.Arrays;
-import java.util.BitSet;
-import java.util.Collections;
-import java.util.LinkedList;
+import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION;
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
 
 import org.tmatesoft.hg.core.HgCallbackTargetException;
 import org.tmatesoft.hg.core.HgIterateDirection;
@@ -32,7 +27,8 @@
 import org.tmatesoft.hg.internal.BlameHelper;
 import org.tmatesoft.hg.internal.Callback;
 import org.tmatesoft.hg.internal.Experimental;
-import org.tmatesoft.hg.internal.IntVector;
+import org.tmatesoft.hg.internal.FileHistory;
+import org.tmatesoft.hg.internal.FileRevisionHistoryChunk;
 import org.tmatesoft.hg.util.Adaptable;
 
 /**
@@ -89,72 +85,23 @@
 		if (!df.exists()) {
 			return;
 		}
+		FileHistory fileHistory = new FileHistory(df, changelogRevIndexStart, changelogRevIndexEnd);
+		fileHistory.build();
 		BlameHelper bh = new BlameHelper(insp, 10);
-		HgDataFile currentFile = df;
-		int fileLastClogRevIndex = changelogRevIndexEnd;
-		FileRevisionHistoryChunk nextChunk = null;
-		LinkedList<FileRevisionHistoryChunk> fileCompleteHistory = new LinkedList<FileRevisionHistoryChunk>();
-		do {
-			FileRevisionHistoryChunk fileHistory = new FileRevisionHistoryChunk(currentFile);
-			fileHistory.init(fileLastClogRevIndex);
-			fileHistory.linkTo(nextChunk);
-			fileCompleteHistory.addFirst(fileHistory); // to get the list in old-to-new order
-			nextChunk = fileHistory;
-			bh.useFileUpTo(currentFile, fileLastClogRevIndex);
-			if (fileHistory.changeset(0) > changelogRevIndexStart && currentFile.isCopy()) {
-				// fileHistory.changeset(0) is the earliest revision we know about so far,
-				// once we get to revisions earlier than the requested start, stop digging.
-				// The reason there's NO == (i.e. not >=) because:
-				// (easy): once it's equal, we've reached our intended start
-				// (hard): if changelogRevIndexStart happens to be exact start of one of renames in the 
-				// chain of renames (test-annotate2 repository, file1->file1a->file1b, i.e. points 
-				// to the very start of file1a or file1 history), presence of == would get us to the next 
-				// chunk and hence changed parents of present chunk's first element. Our annotate alg 
-				// relies on parents only (i.e. knows nothing about 'last iteration element') to find out 
-				// what to compare, and hence won't report all lines of 'last iteration element' (which is the
-				// first revision of the renamed file) as "added in this revision", leaving gaps in annotate
-				HgRepository repo = currentFile.getRepo();
-				Nodeid originLastRev = currentFile.getCopySourceRevision();
-				currentFile = repo.getFileNode(currentFile.getCopySourceName());
-				fileLastClogRevIndex = currentFile.getChangesetRevisionIndex(currentFile.getRevisionIndex(originLastRev));
-				// XXX perhaps, shall fail with meaningful exception if new file doesn't exist (.i/.d not found for whatever reason)
-				// or source revision is missing?
-			} else {
-				fileHistory.chopAtChangeset(changelogRevIndexStart);
-				currentFile = null; // stop iterating
-			}
-		} while (currentFile != null && fileLastClogRevIndex > changelogRevIndexStart);
-		// fileCompleteHistory is in (origin, intermediate target, ultimate target) order
-
+		for (FileRevisionHistoryChunk fhc : fileHistory.iterate(OldToNew)) {
+			// iteration order is not important here
+			bh.useFileUpTo(fhc.getFile(), fhc.getEndChangeset());
+		}
 		int[] fileClogParentRevs = new int[2];
 		int[] fileParentRevs = new int[2];
-		if (iterateOrder == NewToOld) {
-			Collections.reverse(fileCompleteHistory);
-		}
-		boolean shallFilterStart = changelogRevIndexStart != 0; // no reason if complete history is walked
-		for (FileRevisionHistoryChunk fileHistory : fileCompleteHistory) {
-			for (int fri : fileHistory.fileRevisions(iterateOrder)) {
-				int clogRevIndex = fileHistory.changeset(fri);
-				if (shallFilterStart) {
-					if (iterateOrder == NewToOld) {
-						// clogRevIndex decreases
-						if (clogRevIndex < changelogRevIndexStart) {
-							break;
-						}
-						// fall-through, clogRevIndex is in the [start..end] range
-					} else { // old to new
-						// the way we built fileHistory ensures we won't walk past changelogRevIndexEnd
-						// here we ensure we start from the right one, the one indicated with changelogRevIndexStart
-						if (clogRevIndex < changelogRevIndexStart) {
-							continue;
-						} else {
-							shallFilterStart = false; // once boundary is crossed, no need to check
-							// fall-through
-						}
-					}
-				}
-				fileHistory.fillFileParents(fri, fileParentRevs);
-				fileHistory.fillCsetParents(fri, fileClogParentRevs);
+		for (FileRevisionHistoryChunk fhc : fileHistory.iterate(iterateOrder)) {
+			for (int fri : fhc.fileRevisions(iterateOrder)) {
+				int clogRevIndex = fhc.changeset(fri);
+				// the way we built fileHistory ensures we won't walk past [changelogRevIndexStart..changelogRevIndexEnd]
+				assert clogRevIndex >= changelogRevIndexStart;
+				assert clogRevIndex <= changelogRevIndexEnd;
+				fhc.fillFileParents(fri, fileParentRevs);
+				fhc.fillCsetParents(fri, fileClogParentRevs);
 				bh.annotateChange(fri, clogRevIndex, fileParentRevs, fileClogParentRevs);
 			}
 		}
@@ -198,13 +145,6 @@
 	}
 	
 	/**
-	 * No need to keep "Block" prefix as long as there's only one {@link Inspector}
-	 */
-	@Deprecated
-	public interface BlockInspector extends Inspector {
-	}
-	
-	/**
 	 * Represents content of a block, either as a sequence of bytes or a 
 	 * sequence of smaller blocks (lines), if appropriate (according to usage context).
 	 * 
@@ -353,135 +293,4 @@
 		Nodeid fileRev = df.getRepo().getManifest().getFileRevision(csetRevIndex, df.getPath());
 		return df.getRevisionIndex(fileRev);
 	}
-	
-	private static class FileRevisionHistoryChunk {
-		private final HgDataFile df;
-		// change ancestry, sequence of file revisions
-		private IntVector fileRevsToVisit;
-		// parent pairs of complete file history
-		private IntVector fileParentRevs;
-		// map file revision to changelog revision (sparse array, only file revisions to visit are set)
-		private int[] file2changelog;
-		private int originChangelogRev = BAD_REVISION, originFileRev = BAD_REVISION;
-
-		public FileRevisionHistoryChunk(HgDataFile file) {
-			df = file;
-		}
-		
-		public void init(int changelogRevisionIndex) {
-			// XXX df.indexWalk(0, fileRevIndex, ) might be more effective
-			int fileRevIndex = fileRevIndex(df, changelogRevisionIndex);
-			int[] fileRevParents = new int[2];
-			fileParentRevs = new IntVector((fileRevIndex+1) * 2, 0);
-			fileParentRevs.add(NO_REVISION, NO_REVISION); // parents of fileRevIndex == 0
-			for (int i = 1; i <= fileRevIndex; i++) {
-				df.parents(i, fileRevParents, null, null);
-				fileParentRevs.add(fileRevParents[0], fileRevParents[1]);
-			}
-			// fileRevsToVisit keep file change ancestry from new to old
-			fileRevsToVisit = new IntVector(fileRevIndex + 1, 0);
-			// keep map of file revision to changelog revision
-			file2changelog = new int[fileRevIndex+1];
-			// only elements worth visit would get mapped, so there would be unfilled areas in the file2changelog,
-			// prevent from error (make it explicit) by bad value
-			Arrays.fill(file2changelog, BAD_REVISION);
-			LinkedList<Integer> queue = new LinkedList<Integer>();
-			BitSet seen = new BitSet(fileRevIndex + 1);
-			queue.add(fileRevIndex);
-			do {
-				int x = queue.removeFirst();
-				if (seen.get(x)) {
-					continue;
-				}
-				seen.set(x);
-				fileRevsToVisit.add(x);
-				file2changelog[x] = df.getChangesetRevisionIndex(x);
-				int p1 = fileParentRevs.get(2*x);
-				int p2 = fileParentRevs.get(2*x + 1);
-				if (p1 != NO_REVISION) {
-					queue.addLast(p1);
-				}
-				if (p2 != NO_REVISION) {
-					queue.addLast(p2);
-				}
-			} while (!queue.isEmpty());
-			// make sure no child is processed before we handled all (grand-)parents of the element
-			fileRevsToVisit.sort(false);
-		}
-		
-		public void linkTo(FileRevisionHistoryChunk target) {
-			// assume that target.init() has been called already 
-			if (target == null) {
-				return;
-			}
-			target.originFileRev = fileRevsToVisit.get(0); // files to visit are new to old
-			target.originChangelogRev = changeset(target.originFileRev);
-		}
-
-		/**
-		 * Mark revision closest(ceil) to specified as the very first one (no parents) 
-		 */
-		public void chopAtChangeset(int firstChangelogRevOfInterest) {
-			if (firstChangelogRevOfInterest == 0) {
-				return; // nothing to do
-			}
-			int i = 0, x = fileRevsToVisit.size(), fileRev = BAD_REVISION;
-			// fileRevsToVisit is new to old, greater numbers to smaller
-			while (i < x && changeset(fileRev = fileRevsToVisit.get(i)) >= firstChangelogRevOfInterest) {
-				i++;
-			}
-			assert fileRev != BAD_REVISION; // there's at least 1 revision in fileRevsToVisit
-			if (i == x && changeset(fileRev) != firstChangelogRevOfInterest) {
-				assert false : "Requested changeset shall belong to the chunk";
-				return;
-			}
-			fileRevsToVisit.trimTo(i); // no need to iterate more
-			// pretend fileRev got no parents
-			fileParentRevs.set(fileRev * 2, NO_REVISION);
-			fileParentRevs.set(fileRev, NO_REVISION);
-		}
-
-		public int[] fileRevisions(HgIterateDirection iterateOrder) {
-			// fileRevsToVisit is { r10, r7, r6, r5, r0 }, new to old
-			int[] rv = fileRevsToVisit.toArray();
-			if (iterateOrder == OldToNew) {
-				// reverse return value
-				for (int a = 0, b = rv.length-1; a < b; a++, b--) {
-					int t = rv[b];
-					rv[b] = rv[a];
-					rv[a] = t;
-				}
-			}
-			return rv;
-		}
-		
-		public int changeset(int fileRevIndex) {
-			return file2changelog[fileRevIndex];
-		}
-		
-		public void fillFileParents(int fileRevIndex, int[] fileParents) {
-			if (fileRevIndex == 0 && originFileRev != BAD_REVISION) {
-				// this chunk continues another file
-				assert originFileRev != NO_REVISION;
-				fileParents[0] = originFileRev;
-				fileParents[1] = NO_REVISION;
-				return;
-			}
-			fileParents[0] = fileParentRevs.get(fileRevIndex * 2);
-			fileParents[1] = fileParentRevs.get(fileRevIndex * 2 + 1);
-		}
-		
-		public void fillCsetParents(int fileRevIndex, int[] csetParents) {
-			if (fileRevIndex == 0 && originFileRev != BAD_REVISION) {
-				assert originFileRev != NO_REVISION;
-				csetParents[0] = originChangelogRev;
-				csetParents[1] = NO_REVISION; // I wonder if possible to start a copy with two parents?
-				return;
-			}
-			int fp1 = fileParentRevs.get(fileRevIndex * 2);
-			int fp2 = fileParentRevs.get(fileRevIndex * 2 + 1);
-			csetParents[0] = fp1 == NO_REVISION ? NO_REVISION : changeset(fp1);
-			csetParents[1] = fp2 == NO_REVISION ? NO_REVISION : changeset(fp2);
-		}
-	}
 }