changeset 555:e623aa2ca526

Annotate: RevisionDescriptor provides extra knowledge about inspected/annotated revision
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 22 Feb 2013 19:03:25 +0100
parents a5fd757d1b5d
children e55f17a7a195
files src/org/tmatesoft/hg/internal/AnnotateFacility.java src/org/tmatesoft/hg/internal/FileAnnotation.java test/org/tmatesoft/hg/test/TestBlame.java
diffstat 3 files changed, 338 insertions(+), 225 deletions(-) [+]
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/internal/AnnotateFacility.java	Thu Feb 21 21:53:55 2013 +0100
+++ b/src/org/tmatesoft/hg/internal/AnnotateFacility.java	Fri Feb 22 19:03:25 2013 +0100
@@ -25,15 +25,18 @@
 
 import org.tmatesoft.hg.core.HgIterateDirection;
 import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.internal.AnnotateFacility.BlockData;
+import org.tmatesoft.hg.internal.AnnotateFacility.RevisionDescriptor.Recipient;
 import org.tmatesoft.hg.internal.DiffHelper.LineSequence;
 import org.tmatesoft.hg.internal.DiffHelper.LineSequence.ByteChain;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgInvalidStateException;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.Adaptable;
 import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.Pair;
 
 /**
+ * Facility with diff/annotate functionality.
  * 
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
@@ -55,6 +58,9 @@
 		pg.findMatchingBlocks(new BlameBlockInspector(insp, clogRevIndex1, clogRevIndex2));
 	}
 	
+	/**
+	 * Walk file history up to revision at given changeset and report changes for each revision
+	 */
 	public void annotate(HgDataFile df, int changelogRevisionIndex, BlockInspector insp, HgIterateDirection iterateOrder) {
 		if (!df.exists()) {
 			return;
@@ -107,20 +113,11 @@
 	}
 
 	/**
-	 * Annotate file revision, line by line. 
+	 * Annotates changes of the file against its parent(s). 
+	 * Unlike {@link #annotate(HgDataFile, int, BlockInspector, HgIterateDirection)}, doesn't
+	 * walk file history, looks at the specified revision only. Handles both parents (if merge revision).
 	 */
-	public void annotate(HgDataFile df, int changelogRevisionIndex, LineInspector insp) {
-		if (!df.exists()) {
-			return;
-		}
-		FileAnnotation fa = new FileAnnotation(insp);
-		annotate(df, changelogRevisionIndex, fa, HgIterateDirection.NewToOld);
-	}
-
-	/**
-	 * Annotates changes of the file against its parent(s)
-	 */
-	public void annotateChange(HgDataFile df, int changelogRevisionIndex, BlockInspector insp) {
+	public void annotateSingleRevision(HgDataFile df, int changelogRevisionIndex, BlockInspector insp) {
 		// TODO detect if file is text/binary (e.g. looking for chars < ' ' and not \t\r\n\f
 		int fileRevIndex = fileRevIndex(df, changelogRevisionIndex);
 		int[] fileRevParents = new int[2];
@@ -233,6 +230,14 @@
 		}
 	}
 
+	/**
+	 * Client's sink for revision differences.
+	 * 
+	 * When implemented, clients shall not expect new {@link Block blocks} instances in each call.
+	 * 
+	 * In case more information about annotated revision is needed, inspector instances may supply 
+	 * {@link RevisionDescriptor.Recipient} through {@link Adaptable}.  
+	 */
 	@Callback
 	public interface BlockInspector {
 		void same(EqualBlock block);
@@ -241,13 +246,6 @@
 		void deleted(DeleteBlock block);
 	}
 	
-	@Callback
-	public interface BlockInspectorEx extends BlockInspector { // XXX better name
-		// XXX perhaps, shall pass object instead of separate values for future extension?
-		void start(BlockData originContent, BlockData targetContent);
-		void done();
-	}
-	
 	/**
 	 * Represents content of a block, either as a sequence of bytes or a 
 	 * sequence of smaller blocks (lines), if appropriate (according to usage context).
@@ -276,14 +274,65 @@
 		byte[] asArray();
 	}
 	
+	/**
+	 * {@link BlockInspector} may optionally request extra information about revisions
+	 * being inspected, denoting itself as a {@link RevisionDescriptor.Recipient}. This class 
+	 * provides complete information about file revision under annotation now. 
+	 */
+	public interface RevisionDescriptor {
+		/**
+		 * @return complete source of the diff origin, never <code>null</code>
+		 */
+		BlockData origin();
+		/**
+		 * @return complete source of the diff target, never <code>null</code>
+		 */
+		BlockData target();
+		/**
+		 * @return changeset revision index of original file, or {@link HgRepository#NO_REVISION} if it's the very first revision
+		 */
+		int originChangesetIndex();
+		/**
+		 * @return changeset revision index of the target file
+		 */
+		int targetChangesetIndex();
+		/**
+		 * @return <code>true</code> if this revision is merge
+		 */
+		boolean isMerge();
+		/**
+		 * @return changeset revision index of the second, merged parent
+		 */
+		int mergeChangesetIndex();
+		/**
+		 * @return revision index of the change in file's revlog
+		 */
+		int fileRevisionIndex();
+
+		/**
+		 * Implement to indicate interest in {@link RevisionDescriptor}.
+		 * 
+		 * Note, instance of {@link RevisionDescriptor} is the same for 
+		 * {@link #start(RevisionDescriptor)} and {@link #done(RevisionDescriptor)} 
+		 * methods, and not necessarily a new one (i.e. <code>==</code>) for the next
+		 * revision announced.
+		 */
+		@Callback
+		public interface Recipient {
+			/**
+			 * Comes prior to any change {@link Block blocks}
+			 */
+			void start(RevisionDescriptor revisionDescription);
+			/**
+			 * Comes after all change {@link Block blocks} were dispatched
+			 */
+			void done(RevisionDescriptor revisionDescription);
+		}
+	}
+	
 	public interface Block {
 		int originChangesetIndex();
 		int targetChangesetIndex();
-//		boolean isMergeRevision();
-//		int fileRevisionIndex();
-//		int originFileRevisionIndex();
-//		String[] lines();
-//		byte[] data();
 	}
 	
 	public interface EqualBlock extends Block {
@@ -308,32 +357,19 @@
 	public interface ChangeBlock extends AddBlock, DeleteBlock {
 	}
 	
-	@Callback
-	public interface LineInspector {
-		/**
-		 * Not necessarily invoked sequentially by line numbers
-		 */
-		void line(int lineNumber, int changesetRevIndex, LineDescriptor ld);
-	}
-	
-	public interface LineDescriptor {
-		int totalLines();
-	}
-	
-
-
-	static class BlameBlockInspector extends DiffHelper.DeltaInspector<LineSequence> {
+	private static class BlameBlockInspector extends DiffHelper.DeltaInspector<LineSequence> {
 		private final BlockInspector insp;
 		private final int csetOrigin;
 		private final int csetTarget;
 		private EqualBlocksCollector p2MergeCommon;
 		private int csetMergeParent;
 		private IntVector mergeRanges;
-		private ContentBlock originContent, targetContent;
+		private final AnnotateRev annotatedRevision;
 
 		public BlameBlockInspector(BlockInspector inspector, int originCset, int targetCset) {
 			assert inspector != null;
 			insp = inspector;
+			annotatedRevision = new AnnotateRev();
 			csetOrigin = originCset;
 			csetTarget = targetCset;
 		}
@@ -347,19 +383,24 @@
 		@Override
 		public void begin(LineSequence s1, LineSequence s2) {
 			super.begin(s1, s2);
-			originContent = new ContentBlock(s1);
-			targetContent = new ContentBlock(s2);
-			if (insp instanceof BlockInspectorEx) {
-				((BlockInspectorEx) insp).start(originContent, targetContent);
+			ContentBlock originContent = new ContentBlock(s1);
+			ContentBlock targetContent = new ContentBlock(s2);
+			annotatedRevision.set(originContent, targetContent);
+			annotatedRevision.set(csetOrigin, csetTarget, p2MergeCommon != null ? csetMergeParent : NO_REVISION);
+			Recipient curious = Adaptable.Factory.getAdapter(insp, Recipient.class, null);
+			if (curious != null) {
+				curious.start(annotatedRevision);
 			}
 		}
 		
 		@Override
 		public void end() {
 			super.end();
-			if(insp instanceof BlockInspectorEx) {
-				((BlockInspectorEx) insp).done();
+			Recipient curious = Adaptable.Factory.getAdapter(insp, Recipient.class, null);
+			if (curious != null) {
+				curious.done(annotatedRevision);
 			}
+			p2MergeCommon = null;
 		}
 
 		@Override
@@ -387,7 +428,7 @@
 					// how many lines we may reported as changed (don't use more than in range unless it's the very last range)
 					final int s1LinesToBorrow = lastRange ? s1LinesLeft : Math.min(s1LinesLeft, rangeLen);
 					if (s1LinesToBorrow > 0) {
-						ChangeBlockImpl block = new ChangeBlockImpl(originContent, targetContent, s1Start, s1LinesToBorrow, rangeStart, rangeLen, s1Start, rangeStart);
+						ChangeBlockImpl block = getChangeBlock(s1Start, s1LinesToBorrow, rangeStart, rangeLen);
 						block.setOriginAndTarget(rangeOrigin, csetTarget);
 						insp.changed(block);
 						s1ConsumedLines += s1LinesToBorrow;
@@ -402,7 +443,7 @@
 					throw new HgInvalidStateException(String.format("Expected to process %d lines, but actually was %d", s1TotalLines, s1ConsumedLines));
 				}
 			} else {
-				ChangeBlockImpl block = new ChangeBlockImpl(originContent, targetContent, s1From, s1To-s1From, s2From, s2To - s2From, s1From, s2From);
+				ChangeBlockImpl block = getChangeBlock(s1From, s1To-s1From, s2From, s2To - s2From);
 				block.setOriginAndTarget(csetOrigin, csetTarget);
 				insp.changed(block);
 			}
@@ -433,24 +474,28 @@
 		
 		@Override
 		protected void deleted(int s2DeletePoint, int s1From, int s1To) {
-			ChangeBlockImpl block = new ChangeBlockImpl(originContent, null, s1From, s1To - s1From, -1, -1, -1, s2DeletePoint);
+			ChangeBlockImpl block = new ChangeBlockImpl(annotatedRevision.origin, null, s1From, s1To - s1From, -1, -1, -1, s2DeletePoint);
 			block.setOriginAndTarget(csetOrigin, csetTarget);
 			insp.deleted(block);
 		}
 
 		@Override
 		protected void unchanged(int s1From, int s2From, int length) {
-			EqualBlockImpl block = new EqualBlockImpl(s1From, s2From, length, targetContent);
+			EqualBlockImpl block = new EqualBlockImpl(s1From, s2From, length, annotatedRevision.target);
 			block.setOriginAndTarget(csetOrigin, csetTarget);
 			insp.same(block);
 		}
 		
 		private ChangeBlockImpl getAddBlock(int start, int len, int insPoint) {
-			return new ChangeBlockImpl(null, targetContent, -1, -1, start, len, insPoint, -1);
+			return new ChangeBlockImpl(null, annotatedRevision.target, -1, -1, start, len, insPoint, -1);
+		}
+		
+		private ChangeBlockImpl getChangeBlock(int start1, int end1, int start2, int end2) {
+			return new ChangeBlockImpl(annotatedRevision.origin, annotatedRevision.target, start1, end1-start1, start2, end2-start2, start1, start2);
 		}
 	}
 	
-	static class BlockImpl implements Block {
+	private static class BlockImpl implements Block {
 		
 		private int originCset;
 		private int targetCset;
@@ -472,7 +517,7 @@
 		}
 	}
 
-	static class EqualBlockImpl extends BlockImpl implements EqualBlock {
+	private static class EqualBlockImpl extends BlockImpl implements EqualBlock {
 		private final int start1, start2;
 		private final int length;
 		private final ContentBlock fullContent;
@@ -510,7 +555,7 @@
 		}
 	}
 	
-	static class ChangeBlockImpl extends BlockImpl implements ChangeBlock {
+	private static class ChangeBlockImpl extends BlockImpl implements ChangeBlock {
 		
 		private final ContentBlock oldContent;
 		private final ContentBlock newContent;
@@ -758,6 +803,49 @@
 		}
 	}
 
+	private static class AnnotateRev implements RevisionDescriptor {
+		public ContentBlock origin, target;
+		public int originCset, targetCset, mergeCset, fileRevIndex;
+		
+		public void set(ContentBlock o, ContentBlock t) {
+			origin = o;
+			target = t;
+		}
+		public void set(int o, int t, int m) {
+			originCset = o;
+			targetCset = t;
+			mergeCset = m;
+		}
+		
+		public BlockData origin() {
+			return origin;
+		}
+
+		public BlockData target() {
+			return target;
+		}
+
+		public int originChangesetIndex() {
+			return originCset;
+		}
+
+		public int targetChangesetIndex() {
+			return targetCset;
+		}
+
+		public boolean isMerge() {
+			return mergeCset != NO_REVISION;
+		}
+
+		public int mergeChangesetIndex() {
+			return mergeCset;
+		}
+
+		public int fileRevisionIndex() {
+			return fileRevIndex;
+		}
+	}
+
 	public static void main(String[] args) {
 		EqualBlocksCollector bc = new EqualBlocksCollector();
 		bc.match(-1, 5, 3);
--- a/src/org/tmatesoft/hg/internal/FileAnnotation.java	Thu Feb 21 21:53:55 2013 +0100
+++ b/src/org/tmatesoft/hg/internal/FileAnnotation.java	Fri Feb 22 19:03:25 2013 +0100
@@ -18,179 +18,202 @@
 
 import java.util.LinkedList;
 
-import org.tmatesoft.hg.internal.AnnotateFacility.AddBlock;
-import org.tmatesoft.hg.internal.AnnotateFacility.BlockData;
-import org.tmatesoft.hg.internal.AnnotateFacility.ChangeBlock;
-import org.tmatesoft.hg.internal.AnnotateFacility.DeleteBlock;
-import org.tmatesoft.hg.internal.AnnotateFacility.EqualBlock;
-import org.tmatesoft.hg.internal.AnnotateFacility.LineInspector;
-
+import org.tmatesoft.hg.core.HgIterateDirection;
+import org.tmatesoft.hg.internal.AnnotateFacility.*;
+import org.tmatesoft.hg.repo.HgDataFile;
 
 /**
+ * Produce output like 'hg annotate' does
  * 
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-public class FileAnnotation implements AnnotateFacility.BlockInspectorEx {
-		// blocks deleted in the target, as reported at the previous step
-		private LinkedList<DeleteBlock> deleted = new LinkedList<DeleteBlock>();
-		// blocks deleted in the origin, to become deletions in target at the next step
-		private LinkedList<DeleteBlock> newDeleted = new LinkedList<DeleteBlock>();
-		// keeps <startSeq1, startSeq2, len> of equal blocks, origin to target, from previous step
-		// XXX smth like IntSliceVector to access triples (or slices of any size, in fact)
-		// with easy indexing, e.g. #get(sliceIndex, indexWithinSlice)
-		// and vect.get(7,2) instead of vect.get(7*SIZEOF_SLICE+2)
-		private IntVector identical = new IntVector(20*3, 2*3);
-		// equal blocks of the current iteration, to be recalculated before next step
-		// to track line number (current target to ultimate target) mapping 
-		private IntVector newIdentical = new IntVector(20*3, 2*3);
-		
-		private boolean[] knownLines;
-		private final LineInspector delegate;
-		
-		public FileAnnotation(AnnotateFacility.LineInspector lineInspector) {
-			delegate = lineInspector;
-		}
-		
-		public void start(BlockData originContent, BlockData targetContent) {
-			if (knownLines == null) {
-				knownLines = new boolean[targetContent.elementCount()];
-			}
-		}
-
-//		private static void ppp(IntVector v) {
-//			for (int i = 0; i < v.size(); i+= 3) {
-//				int len = v.get(i+2);
-//				System.out.printf("[%d..%d) == [%d..%d);  ", v.get(i), v.get(i) + len, v.get(i+1), v.get(i+1) + len);
-//			}
-//			System.out.println();
-//		}
+public class FileAnnotation implements AnnotateFacility.BlockInspector, RevisionDescriptor.Recipient {
 
-		public void done() {
-			if (identical.size() > 0) {
-				// update line numbers of the intermediate target to point to ultimate target's line numbers
-				IntVector v = new IntVector(identical.size(), 2*3);
-				for (int i = 0; i < newIdentical.size(); i+= 3) {
-					int originLine = newIdentical.get(i);
-					int targetLine = newIdentical.get(i+1);
-					int length = newIdentical.get(i+2);
-					int startTargetLine = -1, startOriginLine = -1, c = 0;
-					for (int j = 0; j < length; j++) {
-						int lnInFinal = mapLineIndex(targetLine + j);
-						if (lnInFinal == -1 || (startTargetLine != -1 && lnInFinal != startTargetLine + c)) {
-							// the line is not among "same" in ultimate origin
-							// or belongs to another/next "same" chunk 
-							if (startOriginLine == -1) {
-								continue;
-							}
-							v.add(startOriginLine);
-							v.add(startTargetLine);
-							v.add(c);
-							c = 0;
-							startOriginLine = startTargetLine = -1;
-							// fall-through to check if it's not complete miss but a next chunk
+	@Experimental(reason="The line-by-line inspector likely to become part of core/command API")
+	@Callback
+	public interface LineInspector {
+		/**
+		 * Not necessarily invoked sequentially by line numbers
+		 */
+		void line(int lineNumber, int changesetRevIndex, LineDescriptor ld);
+	}
+
+	public interface LineDescriptor {
+		int totalLines();
+	}
+
+	/**
+	 * Annotate file revision, line by line.
+	 */
+	public static void annotate(HgDataFile df, int changelogRevisionIndex, LineInspector insp) {
+		if (!df.exists()) {
+			return;
+		}
+		FileAnnotation fa = new FileAnnotation(insp);
+		AnnotateFacility af = new AnnotateFacility();
+		af.annotate(df, changelogRevisionIndex, fa, HgIterateDirection.NewToOld);
+	}
+
+	// blocks deleted in the target, as reported at the previous step
+	private LinkedList<DeleteBlock> deleted = new LinkedList<DeleteBlock>();
+	// blocks deleted in the origin, to become deletions in target at the next step
+	private LinkedList<DeleteBlock> newDeleted = new LinkedList<DeleteBlock>();
+	// keeps <startSeq1, startSeq2, len> of equal blocks, origin to target, from previous step
+	// XXX smth like IntSliceVector to access triples (or slices of any size, in fact)
+	// with easy indexing, e.g. #get(sliceIndex, indexWithinSlice)
+	// and vect.get(7,2) instead of vect.get(7*SIZEOF_SLICE+2)
+	private IntVector identical = new IntVector(20 * 3, 2 * 3);
+	// equal blocks of the current iteration, to be recalculated before next step
+	// to track line number (current target to ultimate target) mapping 
+	private IntVector newIdentical = new IntVector(20 * 3, 2 * 3);
+
+	private boolean[] knownLines;
+	private final LineInspector delegate;
+
+	public FileAnnotation(LineInspector lineInspector) {
+		delegate = lineInspector;
+	}
+
+	public void start(RevisionDescriptor rd) {
+		if (knownLines == null) {
+			knownLines = new boolean[rd.target().elementCount()];
+		}
+	}
+
+	//		private static void ppp(IntVector v) {
+	//			for (int i = 0; i < v.size(); i+= 3) {
+	//				int len = v.get(i+2);
+	//				System.out.printf("[%d..%d) == [%d..%d);  ", v.get(i), v.get(i) + len, v.get(i+1), v.get(i+1) + len);
+	//			}
+	//			System.out.println();
+	//		}
+
+	public void done(RevisionDescriptor rd) {
+		if (identical.size() > 0) {
+			// update line numbers of the intermediate target to point to ultimate target's line numbers
+			IntVector v = new IntVector(identical.size(), 2 * 3);
+			for (int i = 0; i < newIdentical.size(); i += 3) {
+				int originLine = newIdentical.get(i);
+				int targetLine = newIdentical.get(i + 1);
+				int length = newIdentical.get(i + 2);
+				int startTargetLine = -1, startOriginLine = -1, c = 0;
+				for (int j = 0; j < length; j++) {
+					int lnInFinal = mapLineIndex(targetLine + j);
+					if (lnInFinal == -1 || (startTargetLine != -1 && lnInFinal != startTargetLine + c)) {
+						// the line is not among "same" in ultimate origin
+						// or belongs to another/next "same" chunk 
+						if (startOriginLine == -1) {
+							continue;
 						}
-						if (lnInFinal != -1) {
-							if (startOriginLine == -1) {
-								startOriginLine = originLine + j;
-								startTargetLine = lnInFinal;
-								c = 1;
-							} else {
-								assert lnInFinal == startTargetLine + c;
-								c++;
-							}
-						}
-					}
-					if (startOriginLine != -1) {
-						assert c > 0;
 						v.add(startOriginLine);
 						v.add(startTargetLine);
 						v.add(c);
+						c = 0;
+						startOriginLine = startTargetLine = -1;
+						// fall-through to check if it's not complete miss but a next chunk
+					}
+					if (lnInFinal != -1) {
+						if (startOriginLine == -1) {
+							startOriginLine = originLine + j;
+							startTargetLine = lnInFinal;
+							c = 1;
+						} else {
+							assert lnInFinal == startTargetLine + c;
+							c++;
+						}
 					}
 				}
-				newIdentical.clear();
-				identical = v;
-			} else {
-				IntVector li = newIdentical;
-				newIdentical = identical;
-				identical = li;
-			}
-			LinkedList<DeleteBlock> ld = newDeleted;
-			deleted.clear();
-			newDeleted = deleted;
-			deleted = ld;
-		}
-		
-		public void same(EqualBlock block) {
-			newIdentical.add(block.originStart());
-			newIdentical.add(block.targetStart());
-			newIdentical.add(block.length());
-		}
-
-		public void added(AddBlock block) {
-			for (int i = 0, ln = block.firstAddedLine(), x = block.totalAddedLines(); i < x; i++, ln++) {
-				int lnInFinal = mapLineIndex(ln);
-				if (lnInFinal != -1 && !knownLines[lnInFinal]) {
-					delegate.line(lnInFinal, block.targetChangesetIndex(), new LineDescriptor());
-					knownLines[lnInFinal] = true;
+				if (startOriginLine != -1) {
+					assert c > 0;
+					v.add(startOriginLine);
+					v.add(startTargetLine);
+					v.add(c);
 				}
 			}
+			newIdentical.clear();
+			identical = v;
+		} else {
+			IntVector li = newIdentical;
+			newIdentical = identical;
+			identical = li;
 		}
-		
-		public void changed(ChangeBlock block) {
-			deleted(block);
-			added(block);
+		LinkedList<DeleteBlock> ld = newDeleted;
+		deleted.clear();
+		newDeleted = deleted;
+		deleted = ld;
+	}
+
+	public void same(EqualBlock block) {
+		newIdentical.add(block.originStart());
+		newIdentical.add(block.targetStart());
+		newIdentical.add(block.length());
+	}
+
+	public void added(AddBlock block) {
+		for (int i = 0, ln = block.firstAddedLine(), x = block.totalAddedLines(); i < x; i++, ln++) {
+			int lnInFinal = mapLineIndex(ln);
+			if (lnInFinal != -1 && !knownLines[lnInFinal]) {
+				delegate.line(lnInFinal, block.targetChangesetIndex(), new LineDescriptorImpl());
+				knownLines[lnInFinal] = true;
+			}
+		}
+	}
+
+	public void changed(ChangeBlock block) {
+		deleted(block);
+		added(block);
+	}
+
+	public void deleted(DeleteBlock block) {
+		newDeleted.add(block);
+	}
+
+	// line - index in the target
+	private boolean isDeleted(int line) {
+		for (DeleteBlock b : deleted) {
+			if (b.firstRemovedLine() > line) {
+				break;
+			}
+			// line >= b.firstRemovedLine
+			if (b.firstRemovedLine() + b.totalRemovedLines() > line) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	// map target lines to the lines of the revision being annotated (the one that came first)
+	private int mapLineIndex(int ln) {
+		if (isDeleted(ln)) {
+			return -1;
+		}
+		if (identical.isEmpty()) {
+			return ln;
+		}
+		for (int i = 0; i < identical.size(); i += 3) {
+			final int originStart = identical.get(i);
+			if (originStart > ln) {
+				//					assert false;
+				return -1;
+			}
+			// ln >= b.originStart
+			final int length = identical.get(i + 2);
+			if (originStart + length > ln) {
+				int targetStart = identical.get(i + 1);
+				return targetStart + (ln - originStart);
+			}
+		}
+		//			assert false;
+		return -1;
+	}
+
+	private final class LineDescriptorImpl implements LineDescriptor {
+		LineDescriptorImpl() {
 		}
 
-		public void deleted(DeleteBlock block) {
-			newDeleted.add(block);
-		}
-		
-		// line - index in the target
-		private boolean isDeleted(int line) {
-			for (DeleteBlock b : deleted) {
-				if (b.firstRemovedLine() > line) {
-					break;
-				}
-				// line >= b.firstRemovedLine
-				if (b.firstRemovedLine() + b.totalRemovedLines() > line) {
-					return true;
-				}
-			}
-			return false;
+		public int totalLines() {
+			return FileAnnotation.this.knownLines.length;
 		}
-
-		// map target lines to the lines of the revision being annotated (the one that came first)
-		private int mapLineIndex(int ln) {
-			if (isDeleted(ln)) {
-				return -1;
-			}
-			if (identical.isEmpty()) {
-				return ln;
-			}
-			for (int i = 0; i < identical.size(); i += 3) {
-				final int originStart = identical.get(i);
-				if (originStart > ln) {
-//					assert false;
-					return -1;
-				}
-				// ln >= b.originStart
-				final int length = identical.get(i+2);
-				if (originStart + length > ln) {
-					int targetStart = identical.get(i+1);
-					return targetStart + (ln - originStart);
-				}
-			}
-//			assert false;
-			return -1;
-		}
-		
-		private final class LineDescriptor implements AnnotateFacility.LineDescriptor {
-			LineDescriptor() {
-			}
-			
-			public int totalLines() {
-				return FileAnnotation.this.knownLines.length;
-			}
-		}
-	}
\ No newline at end of file
+	}
+}
\ No newline at end of file
--- a/test/org/tmatesoft/hg/test/TestBlame.java	Thu Feb 21 21:53:55 2013 +0100
+++ b/test/org/tmatesoft/hg/test/TestBlame.java	Fri Feb 22 19:03:25 2013 +0100
@@ -33,14 +33,16 @@
 import org.junit.Test;
 import org.tmatesoft.hg.core.HgIterateDirection;
 import org.tmatesoft.hg.internal.AnnotateFacility;
-import org.tmatesoft.hg.internal.IntVector;
 import org.tmatesoft.hg.internal.AnnotateFacility.AddBlock;
 import org.tmatesoft.hg.internal.AnnotateFacility.Block;
 import org.tmatesoft.hg.internal.AnnotateFacility.BlockData;
 import org.tmatesoft.hg.internal.AnnotateFacility.ChangeBlock;
 import org.tmatesoft.hg.internal.AnnotateFacility.DeleteBlock;
 import org.tmatesoft.hg.internal.AnnotateFacility.EqualBlock;
-import org.tmatesoft.hg.internal.AnnotateFacility.LineDescriptor;
+import org.tmatesoft.hg.internal.FileAnnotation;
+import org.tmatesoft.hg.internal.FileAnnotation.LineDescriptor;
+import org.tmatesoft.hg.internal.FileAnnotation.LineInspector;
+import org.tmatesoft.hg.internal.IntVector;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgLookup;
 import org.tmatesoft.hg.repo.HgRepository;
@@ -63,7 +65,7 @@
 		final int checkChangeset = 539;
 		HgDataFile df = repo.getFileNode(fname);
 		ByteArrayOutputStream bos = new ByteArrayOutputStream();
-		new AnnotateFacility().annotateChange(df, checkChangeset, new DiffOutInspector(new PrintStream(bos)));
+		new AnnotateFacility().annotateSingleRevision(df, checkChangeset, new DiffOutInspector(new PrintStream(bos)));
 		LineGrepOutputParser gp = new LineGrepOutputParser("^@@.+");
 		ExecHelper eh = new ExecHelper(gp, null);
 		eh.run("hg", "diff", "-c", String.valueOf(checkChangeset), "-U", "0", fname);
@@ -83,7 +85,7 @@
 
 		for (int startChangeset : new int[] { 539, 541 /*, TIP */}) {
 			FileAnnotateInspector fa = new FileAnnotateInspector();
-			new AnnotateFacility().annotate(df, startChangeset, fa);
+			FileAnnotation.annotate(df, startChangeset, fa);
 			
 
 			op.reset();
@@ -159,18 +161,18 @@
 		AnnotateFacility af = new AnnotateFacility();
 		DiffOutInspector dump = new DiffOutInspector(System.out);
 		System.out.println("541 -> 543");
-		af.annotateChange(df, 543, dump);
+		af.annotateSingleRevision(df, 543, dump);
 		System.out.println("539 -> 541");
-		af.annotateChange(df, 541, dump);
+		af.annotateSingleRevision(df, 541, dump);
 		System.out.println("536 -> 539");
-		af.annotateChange(df, checkChangeset, dump);
+		af.annotateSingleRevision(df, checkChangeset, dump);
 		System.out.println("531 -> 536");
-		af.annotateChange(df, 536, dump);
+		af.annotateSingleRevision(df, 536, dump);
 		System.out.println(" -1 -> 531");
-		af.annotateChange(df, 531, dump);
+		af.annotateSingleRevision(df, 531, dump);
 		
 		FileAnnotateInspector fai = new FileAnnotateInspector();
-		af.annotate(df, 541, fai);
+		FileAnnotation.annotate(df, 541, fai);
 		for (int i = 0; i < fai.lineRevisions.length; i++) {
 			System.out.printf("%3d: LINE %d\n", fai.lineRevisions[i], i+1);
 		}
@@ -207,7 +209,7 @@
 		af.annotate(df, TIP, new LineDumpInspector(false), HgIterateDirection.NewToOld);
 		System.out.println();
 		FileAnnotateInspector fa = new FileAnnotateInspector();
-		af.annotate(df, TIP, fa);
+		FileAnnotation.annotate(df, TIP, fa);
 		for (int i = 0; i < fa.lineRevisions.length; i++) {
 			System.out.printf("%d: LINE %d\n", fa.lineRevisions[i], i+1);
 		}
@@ -309,7 +311,7 @@
 		}
 	}
 
-	private static class FileAnnotateInspector implements AnnotateFacility.LineInspector {
+	private static class FileAnnotateInspector implements LineInspector {
 		private int[] lineRevisions;
 		
 		FileAnnotateInspector() {