changeset 629:5f52074707b2

Diff/blame methods as command, their residence in HgDataFile was a mistake
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 22 May 2013 16:46:15 +0200
parents 6526d8adbc0f
children 72c979555cb8
files src/org/tmatesoft/hg/core/HgAnnotateCommand.java src/org/tmatesoft/hg/core/HgBlameInspector.java src/org/tmatesoft/hg/core/HgDiffCommand.java src/org/tmatesoft/hg/internal/BlameHelper.java src/org/tmatesoft/hg/internal/FileAnnotation.java src/org/tmatesoft/hg/repo/HgBlameInspector.java src/org/tmatesoft/hg/repo/HgDataFile.java test/org/tmatesoft/hg/test/TestBlame.java
diffstat 8 files changed, 416 insertions(+), 256 deletions(-) [+]
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/core/HgAnnotateCommand.java	Wed May 22 15:52:31 2013 +0200
+++ b/src/org/tmatesoft/hg/core/HgAnnotateCommand.java	Wed May 22 16:46:15 2013 +0200
@@ -20,12 +20,12 @@
 
 import java.util.Arrays;
 
+import org.tmatesoft.hg.core.HgBlameInspector.BlockData;
 import org.tmatesoft.hg.internal.Callback;
 import org.tmatesoft.hg.internal.CsetParamKeeper;
 import org.tmatesoft.hg.internal.FileAnnotation;
 import org.tmatesoft.hg.internal.FileAnnotation.LineDescriptor;
 import org.tmatesoft.hg.internal.FileAnnotation.LineInspector;
-import org.tmatesoft.hg.repo.HgBlameInspector.BlockData;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgRuntimeException;
@@ -116,7 +116,10 @@
 			final int changesetStart = followRename ? 0 : df.getChangesetRevisionIndex(0);
 			Collector c = new Collector(cancellation);
 			FileAnnotation fa = new FileAnnotation(c);
-			df.annotate(changesetStart, annotateRevision.get(), fa, HgIterateDirection.NewToOld);
+			HgDiffCommand cmd = new HgDiffCommand(repo);
+			cmd.file(df).order(HgIterateDirection.NewToOld);
+			cmd.range(changesetStart, annotateRevision.get());
+			cmd.executeAnnotate(fa);
 			progress.worked(1);
 			c.throwIfCancelled();
 			cancellation.checkCancelled();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/HgBlameInspector.java	Wed May 22 16:46:15 2013 +0200
@@ -0,0 +1,189 @@
+/*
+ * 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.core;
+
+import org.tmatesoft.hg.core.HgCallbackTargetException;
+import org.tmatesoft.hg.internal.Callback;
+import org.tmatesoft.hg.internal.Experimental;
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.util.Adaptable;
+
+/**
+ * Client's sink for revision differences, diff/annotate functionality.
+ * 
+ * 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}.  
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ * @since 1.1
+ */
+@Callback
+@Experimental(reason="Unstable API")
+public interface HgBlameInspector {
+
+	void same(EqualBlock block) throws HgCallbackTargetException;
+	void added(AddBlock block) throws HgCallbackTargetException;
+	void changed(ChangeBlock block) throws HgCallbackTargetException;
+	void deleted(DeleteBlock block) throws HgCallbackTargetException;
+	
+	/**
+	 * Represents content of a block, either as a sequence of bytes or a 
+	 * sequence of smaller blocks (lines), if appropriate (according to usage context).
+	 * 
+	 * This approach allows line-by-line access to content data along with complete byte sequence for the whole block, i.e.
+	 * <pre>
+	 *    BlockData bd = addBlock.addedLines()
+	 *    // bd describes data from the addition completely.
+	 *    // elements of the BlockData are lines
+	 *    bd.elementCount() == addBlock.totalAddedLines();
+	 *    // one cat obtain complete addition with
+	 *    byte[] everythingAdded = bd.asArray();
+	 *    // or iterate line by line
+	 *    for (int i = 0; i < bd.elementCount(); i++) {
+	 *    	 byte[] lineContent = bd.elementAt(i);
+	 *       String line = new String(lineContent, fileEncodingCharset);
+	 *    }
+	 *    where bd.elementAt(0) is the line at index addBlock.firstAddedLine() 
+	 * </pre> 
+	 * 
+	 * LineData or ChunkData? 
+	 */
+	public interface BlockData {
+		BlockData elementAt(int index);
+		int elementCount();
+		byte[] asArray();
+	}
+	
+	/**
+	 * {@link HgBlameInspector} 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 target file's revlog
+		 */
+		int fileRevisionIndex();
+
+		/**
+		 * @return file object under blame (target file)
+		 */
+		HgDataFile file();
+
+		/**
+		 * 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) throws HgCallbackTargetException;
+			/**
+			 * Comes after all change {@link Block blocks} were dispatched
+			 */
+			void done(RevisionDescriptor revisionDescription) throws HgCallbackTargetException;
+		}
+	}
+	
+	/**
+	 * Each change block comes from a single origin, blocks that are result of a merge
+	 * have {@link #originChangesetIndex()} equal to {@link RevisionDescriptor#mergeChangesetIndex()}.
+	 */
+	public interface Block {
+		int originChangesetIndex();
+		int targetChangesetIndex();
+	}
+	
+	public interface EqualBlock extends Block {
+		int originStart();
+		int targetStart();
+		int length();
+		BlockData content();
+	}
+	
+	public interface AddBlock extends Block {
+		/**
+		 * @return line index in the origin where this block is inserted
+		 */
+		int insertedAt();  
+		/**
+		 * @return line index of the first added line in the target revision
+		 */
+		int firstAddedLine();
+		/**
+		 * @return number of added lines in this block
+		 */
+		int totalAddedLines();
+		/**
+		 * @return content of added lines
+		 */
+		BlockData addedLines();
+	}
+	public interface DeleteBlock extends Block {
+		/**
+		 * @return line index in the target revision were this deleted block would be
+		 */
+		int removedAt();
+		/**
+		 * @return line index of the first removed line in the original revision
+		 */
+		int firstRemovedLine();
+		/**
+		 * @return number of deleted lines in this block
+		 */
+		int totalRemovedLines();
+		/**
+		 * @return content of deleted lines
+		 */
+		BlockData removedLines();
+	}
+	public interface ChangeBlock extends AddBlock, DeleteBlock {
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/HgDiffCommand.java	Wed May 22 16:46:15 2013 +0200
@@ -0,0 +1,171 @@
+/*
+ * 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.core;
+
+import static org.tmatesoft.hg.repo.HgInternals.wrongRevisionIndex;
+import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION;
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import org.tmatesoft.hg.internal.BlameHelper;
+import org.tmatesoft.hg.internal.Experimental;
+import org.tmatesoft.hg.internal.FileHistory;
+import org.tmatesoft.hg.internal.FileRevisionHistoryChunk;
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgInternals;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgRuntimeException;
+import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * 'hg diff' counterpart, with similar, diff-based, functionality
+ * 
+ * @since 1.1
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@Experimental(reason="#execute* methods might get renamed")
+public class HgDiffCommand extends HgAbstractCommand<HgDiffCommand> {
+
+	private final HgRepository repo;
+	private HgDataFile df;
+	private int clogRevIndexStart, clogRevIndexEnd;
+	private int clogRevIndexToParents;
+	private HgIterateDirection iterateDirection = HgIterateDirection.NewToOld;
+
+	public HgDiffCommand(HgRepository hgRepo) {
+		repo = hgRepo;
+	}
+	
+	public HgDiffCommand file(Path file) {
+		df = repo.getFileNode(file);
+		return this;
+	}
+
+	public HgDiffCommand file(HgDataFile file) {
+		df = file;
+		return this;
+	}
+
+	public HgDiffCommand range(int changelogRevIndexStart, int changelogRevIndexEnd) {
+		clogRevIndexStart = changelogRevIndexStart;
+		clogRevIndexEnd = changelogRevIndexEnd;
+		return this;
+	}
+	
+	// FIXME javadoc when needed and difference with range
+	public HgDiffCommand changeset(int changelogRevIndex) {
+		clogRevIndexToParents = changelogRevIndex;
+		return this;
+	}
+
+	// FIXME javadoc when needed
+	public HgDiffCommand order(HgIterateDirection order) {
+		iterateDirection = order;
+		return this;
+	}
+
+	// FIXME progress and cancellation
+	
+	/**
+	 * mimic 'hg diff -r clogRevIndex1 -r clogRevIndex2'
+	 */
+	public void executeDiff(HgBlameInspector insp) throws HgCallbackTargetException, CancelledException, HgException {
+		try {
+			int fileRevIndex1 = fileRevIndex(df, clogRevIndexStart);
+			int fileRevIndex2 = fileRevIndex(df, clogRevIndexEnd);
+			BlameHelper bh = new BlameHelper(insp);
+			bh.prepare(df, clogRevIndexStart, clogRevIndexEnd);
+			bh.diff(fileRevIndex1, clogRevIndexStart, fileRevIndex2, clogRevIndexEnd);
+		} catch (HgRuntimeException ex) {
+			throw new HgLibraryFailureException(ex);
+		}
+	}
+
+	/**
+	 * Walk file history range and report changes (diff) for each revision
+	 */
+	public void executeAnnotate(HgBlameInspector insp) throws HgCallbackTargetException, CancelledException, HgException {
+		if (wrongRevisionIndex(clogRevIndexStart) || wrongRevisionIndex(clogRevIndexEnd)) {
+			throw new IllegalArgumentException();
+		}
+		// FIXME check file and range are set
+		try {
+			// Note, changelogRevIndexEnd may be TIP, while the code below doesn't tolerate constants
+			//
+			int lastRevision = repo.getChangelog().getLastRevision();
+			if (clogRevIndexEnd == TIP) {
+				clogRevIndexEnd = lastRevision;
+			}
+			HgInternals.checkRevlogRange(clogRevIndexStart, clogRevIndexEnd, lastRevision);
+			if (!df.exists()) {
+				return;
+			}
+			BlameHelper bh = new BlameHelper(insp);
+			FileHistory fileHistory = bh.prepare(df, clogRevIndexStart, clogRevIndexEnd);
+	
+			int[] fileClogParentRevs = new int[2];
+			int[] fileParentRevs = new int[2];
+			for (FileRevisionHistoryChunk fhc : fileHistory.iterate(iterateDirection)) {
+				for (int fri : fhc.fileRevisions(iterateDirection)) {
+					int clogRevIndex = fhc.changeset(fri);
+					// the way we built fileHistory ensures we won't walk past [changelogRevIndexStart..changelogRevIndexEnd]
+					assert clogRevIndex >= clogRevIndexStart;
+					assert clogRevIndex <= clogRevIndexEnd;
+					fhc.fillFileParents(fri, fileParentRevs);
+					fhc.fillCsetParents(fri, fileClogParentRevs);
+					bh.annotateChange(fri, clogRevIndex, fileParentRevs, fileClogParentRevs);
+				}
+			}
+		} catch (HgRuntimeException ex) {
+			throw new HgLibraryFailureException(ex);
+		}
+	}
+
+	/**
+	 * Annotates changes of the file against its parent(s). 
+	 * Unlike {@link #annotate(HgDataFile, int, Inspector, HgIterateDirection)}, doesn't
+	 * walk file history, looks at the specified revision only. Handles both parents (if merge revision).
+	 */
+	public void executeAnnotateSingleRevision(HgBlameInspector insp) throws HgCallbackTargetException, CancelledException, HgException {
+		try {
+			int changelogRevisionIndex = clogRevIndexToParents;
+			// 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];
+			df.parents(fileRevIndex, fileRevParents, null, null);
+			if (changelogRevisionIndex == TIP) {
+				changelogRevisionIndex = df.getChangesetRevisionIndex(fileRevIndex);
+			}
+			int[] fileClogParentRevs = new int[2];
+			fileClogParentRevs[0] = fileRevParents[0] == NO_REVISION ? NO_REVISION : df.getChangesetRevisionIndex(fileRevParents[0]);
+			fileClogParentRevs[1] = fileRevParents[1] == NO_REVISION ? NO_REVISION : df.getChangesetRevisionIndex(fileRevParents[1]);
+			BlameHelper bh = new BlameHelper(insp);
+			int clogIndexStart = fileClogParentRevs[0] == NO_REVISION ? (fileClogParentRevs[1] == NO_REVISION ? 0 : fileClogParentRevs[1]) : fileClogParentRevs[0];
+			bh.prepare(df, clogIndexStart, changelogRevisionIndex);
+			bh.annotateChange(fileRevIndex, changelogRevisionIndex, fileRevParents, fileClogParentRevs);
+		} catch (HgRuntimeException ex) {
+			throw new HgLibraryFailureException(ex);
+		}
+	}
+
+
+	private static int fileRevIndex(HgDataFile df, int csetRevIndex) throws HgRuntimeException {
+		Nodeid fileRev = df.getRepo().getManifest().getFileRevision(csetRevIndex, df.getPath());
+		return df.getRevisionIndex(fileRev);
+	}
+}
--- a/src/org/tmatesoft/hg/internal/BlameHelper.java	Wed May 22 15:52:31 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/BlameHelper.java	Wed May 22 16:46:15 2013 +0200
@@ -25,13 +25,8 @@
 import org.tmatesoft.hg.core.HgCallbackTargetException;
 import org.tmatesoft.hg.internal.DiffHelper.LineSequence;
 import org.tmatesoft.hg.internal.DiffHelper.LineSequence.ByteChain;
-import org.tmatesoft.hg.repo.HgBlameInspector.Block;
-import org.tmatesoft.hg.repo.HgBlameInspector.BlockData;
-import org.tmatesoft.hg.repo.HgBlameInspector.ChangeBlock;
-import org.tmatesoft.hg.repo.HgBlameInspector.EqualBlock;
-import org.tmatesoft.hg.repo.HgBlameInspector.RevisionDescriptor;
-import org.tmatesoft.hg.repo.HgBlameInspector.RevisionDescriptor.Recipient;
-import org.tmatesoft.hg.repo.HgBlameInspector;
+import org.tmatesoft.hg.core.HgBlameInspector;
+import org.tmatesoft.hg.core.HgBlameInspector.*;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgInvalidStateException;
 import org.tmatesoft.hg.repo.HgRuntimeException;
@@ -253,7 +248,7 @@
 			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);
+			RevisionDescriptor.Recipient curious = Adaptable.Factory.getAdapter(insp, RevisionDescriptor.Recipient.class, null);
 			if (curious != null) {
 				try {
 					curious.start(annotatedRevision);
@@ -269,7 +264,7 @@
 			if (shallStop()) {
 				return;
 			}
-			Recipient curious = Adaptable.Factory.getAdapter(insp, Recipient.class, null);
+			RevisionDescriptor.Recipient curious = Adaptable.Factory.getAdapter(insp, RevisionDescriptor.Recipient.class, null);
 			if (curious != null) {
 				try {
 					curious.done(annotatedRevision);
--- a/src/org/tmatesoft/hg/internal/FileAnnotation.java	Wed May 22 15:52:31 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/FileAnnotation.java	Wed May 22 16:46:15 2013 +0200
@@ -19,8 +19,8 @@
 
 import org.tmatesoft.hg.core.HgCallbackTargetException;
 import org.tmatesoft.hg.core.HgIterateDirection;
-import org.tmatesoft.hg.repo.HgBlameInspector;
-import org.tmatesoft.hg.repo.HgBlameInspector.RevisionDescriptor;
+import org.tmatesoft.hg.core.HgBlameInspector;
+import org.tmatesoft.hg.core.HgBlameInspector.RevisionDescriptor;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgInvalidStateException;
 import org.tmatesoft.hg.repo.HgRuntimeException;
@@ -54,7 +54,7 @@
 			return;
 		}
 		FileAnnotation fa = new FileAnnotation(insp);
-		df.annotate(changelogRevisionIndex, fa, HgIterateDirection.NewToOld);
+		df.annotate(0, changelogRevisionIndex, fa, HgIterateDirection.NewToOld);
 	}
 
 	// keeps <startSeq1, startSeq2, len> of equal blocks, origin to target, from some previous step
--- a/src/org/tmatesoft/hg/repo/HgBlameInspector.java	Wed May 22 15:52:31 2013 +0200
+++ b/src/org/tmatesoft/hg/repo/HgBlameInspector.java	Wed May 22 16:46:15 2013 +0200
@@ -16,173 +16,9 @@
  */
 package org.tmatesoft.hg.repo;
 
-import org.tmatesoft.hg.core.HgCallbackTargetException;
-import org.tmatesoft.hg.internal.Callback;
-import org.tmatesoft.hg.internal.Experimental;
-import org.tmatesoft.hg.util.Adaptable;
-
 /**
- * Client's sink for revision differences, diff/annotate functionality.
- * 
- * 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}.  
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- * @since 1.1
+ * @deprecated use {@link org.tmatesoft.hg.core.HgBlameInspector} instead
  */
-@Callback
-@Experimental(reason="Unstable API")
-public interface HgBlameInspector {
-
-	void same(EqualBlock block) throws HgCallbackTargetException;
-	void added(AddBlock block) throws HgCallbackTargetException;
-	void changed(ChangeBlock block) throws HgCallbackTargetException;
-	void deleted(DeleteBlock block) throws HgCallbackTargetException;
-	
-	/**
-	 * Represents content of a block, either as a sequence of bytes or a 
-	 * sequence of smaller blocks (lines), if appropriate (according to usage context).
-	 * 
-	 * This approach allows line-by-line access to content data along with complete byte sequence for the whole block, i.e.
-	 * <pre>
-	 *    BlockData bd = addBlock.addedLines()
-	 *    // bd describes data from the addition completely.
-	 *    // elements of the BlockData are lines
-	 *    bd.elementCount() == addBlock.totalAddedLines();
-	 *    // one cat obtain complete addition with
-	 *    byte[] everythingAdded = bd.asArray();
-	 *    // or iterate line by line
-	 *    for (int i = 0; i < bd.elementCount(); i++) {
-	 *    	 byte[] lineContent = bd.elementAt(i);
-	 *       String line = new String(lineContent, fileEncodingCharset);
-	 *    }
-	 *    where bd.elementAt(0) is the line at index addBlock.firstAddedLine() 
-	 * </pre> 
-	 * 
-	 * LineData or ChunkData? 
-	 */
-	public interface BlockData {
-		BlockData elementAt(int index);
-		int elementCount();
-		byte[] asArray();
-	}
-	
-	/**
-	 * {@link HgBlameInspector} 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 target file's revlog
-		 */
-		int fileRevisionIndex();
-
-		/**
-		 * @return file object under blame (target file)
-		 */
-		HgDataFile file();
-
-		/**
-		 * 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) throws HgCallbackTargetException;
-			/**
-			 * Comes after all change {@link Block blocks} were dispatched
-			 */
-			void done(RevisionDescriptor revisionDescription) throws HgCallbackTargetException;
-		}
-	}
-	
-	/**
-	 * Each change block comes from a single origin, blocks that are result of a merge
-	 * have {@link #originChangesetIndex()} equal to {@link RevisionDescriptor#mergeChangesetIndex()}.
-	 */
-	public interface Block {
-		int originChangesetIndex();
-		int targetChangesetIndex();
-	}
-	
-	public interface EqualBlock extends Block {
-		int originStart();
-		int targetStart();
-		int length();
-		BlockData content();
-	}
-	
-	public interface AddBlock extends Block {
-		/**
-		 * @return line index in the origin where this block is inserted
-		 */
-		int insertedAt();  
-		/**
-		 * @return line index of the first added line in the target revision
-		 */
-		int firstAddedLine();
-		/**
-		 * @return number of added lines in this block
-		 */
-		int totalAddedLines();
-		/**
-		 * @return content of added lines
-		 */
-		BlockData addedLines();
-	}
-	public interface DeleteBlock extends Block {
-		/**
-		 * @return line index in the target revision were this deleted block would be
-		 */
-		int removedAt();
-		/**
-		 * @return line index of the first removed line in the original revision
-		 */
-		int firstRemovedLine();
-		/**
-		 * @return number of deleted lines in this block
-		 */
-		int totalRemovedLines();
-		/**
-		 * @return content of deleted lines
-		 */
-		BlockData removedLines();
-	}
-	public interface ChangeBlock extends AddBlock, DeleteBlock {
-	}
+@Deprecated
+public interface HgBlameInspector extends org.tmatesoft.hg.core.HgBlameInspector {
 }
--- a/src/org/tmatesoft/hg/repo/HgDataFile.java	Wed May 22 15:52:31 2013 +0200
+++ b/src/org/tmatesoft/hg/repo/HgDataFile.java	Wed May 22 16:46:15 2013 +0200
@@ -27,14 +27,15 @@
 import java.nio.channels.FileChannel;
 import java.util.Arrays;
 
+import org.tmatesoft.hg.core.HgBlameInspector;
 import org.tmatesoft.hg.core.HgCallbackTargetException;
 import org.tmatesoft.hg.core.HgChangesetFileSneaker;
+import org.tmatesoft.hg.core.HgDiffCommand;
+import org.tmatesoft.hg.core.HgException;
 import org.tmatesoft.hg.core.HgIterateDirection;
+import org.tmatesoft.hg.core.HgLibraryFailureException;
 import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.internal.BlameHelper;
 import org.tmatesoft.hg.internal.DataAccess;
-import org.tmatesoft.hg.internal.FileHistory;
-import org.tmatesoft.hg.internal.FileRevisionHistoryChunk;
 import org.tmatesoft.hg.internal.FileUtils;
 import org.tmatesoft.hg.internal.FilterByteChannel;
 import org.tmatesoft.hg.internal.FilterDataAccess;
@@ -426,80 +427,53 @@
 		int changesetRevIndex = getChangesetRevisionIndex(fileRevisionIndex);
 		return getRepo().getManifest().getFileFlags(changesetRevIndex, getPath());
 	}
-	
+
 	/**
-	 * mimic 'hg diff -r clogRevIndex1 -r clogRevIndex2'
+	 * @deprecated use {@link HgDiffCommand} instead
 	 */
+	@Deprecated
 	public void diff(int clogRevIndex1, int clogRevIndex2, HgBlameInspector insp) throws HgRuntimeException, HgCallbackTargetException {
-		int fileRevIndex1 = fileRevIndex(this, clogRevIndex1);
-		int fileRevIndex2 = fileRevIndex(this, clogRevIndex2);
-		BlameHelper bh = new BlameHelper(insp);
-		bh.prepare(this, clogRevIndex1, clogRevIndex2);
-		bh.diff(fileRevIndex1, clogRevIndex1, fileRevIndex2, clogRevIndex2);
+		try {
+			new HgDiffCommand(getRepo()).file(this).range(clogRevIndex1, clogRevIndex2).executeDiff(insp);
+		} catch (HgLibraryFailureException ex) {
+			throw ex.getCause();
+		} catch (HgException ex) {
+			throw new HgInvalidStateException(ex.getMessage());
+		} catch (CancelledException ex) {
+			throw new HgInvalidStateException("Cancellatin is not expected");
+		}
 	}
 	
 	/**
-	 * Walk file history up/down to revision at given changeset and report changes for each revision
-	 */
-	public void annotate(int changelogRevisionIndex, HgBlameInspector insp, HgIterateDirection iterateOrder) throws HgRuntimeException, HgCallbackTargetException {
-		annotate(0, changelogRevisionIndex, insp, iterateOrder);
-	}
-
-	/**
-	 * Walk file history range and report changes for each revision
+	 * @deprecated use {@link HgDiffCommand} instead
 	 */
-	public void annotate(int changelogRevIndexStart, int changelogRevIndexEnd, HgBlameInspector insp, HgIterateDirection iterateOrder) throws HgRuntimeException, HgCallbackTargetException {
-		if (wrongRevisionIndex(changelogRevIndexStart) || wrongRevisionIndex(changelogRevIndexEnd)) {
-			throw new IllegalArgumentException();
-		}
-		// Note, changelogRevIndexEnd may be TIP, while the code below doesn't tolerate constants
-		//
-		int lastRevision = getRepo().getChangelog().getLastRevision();
-		if (changelogRevIndexEnd == TIP) {
-			changelogRevIndexEnd = lastRevision;
-		}
-		HgInternals.checkRevlogRange(changelogRevIndexStart, changelogRevIndexEnd, lastRevision);
-		if (!exists()) {
-			return;
-		}
-		BlameHelper bh = new BlameHelper(insp);
-		FileHistory fileHistory = bh.prepare(this, changelogRevIndexStart, changelogRevIndexEnd);
-
-		int[] fileClogParentRevs = new int[2];
-		int[] fileParentRevs = new int[2];
-		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);
-			}
+	@Deprecated
+	public void annotate(int clogRevIndex1, int clogRevIndex2, HgBlameInspector insp, HgIterateDirection iterateOrder) throws HgRuntimeException, HgCallbackTargetException {
+		try {
+			new HgDiffCommand(getRepo()).file(this).range(clogRevIndex1, clogRevIndex2).order(iterateOrder).executeAnnotate(insp);
+		} catch (HgLibraryFailureException ex) {
+			throw ex.getCause();
+		} catch (HgException ex) {
+			throw new HgInvalidStateException(ex.getMessage());
+		} catch (CancelledException ex) {
+			throw new HgInvalidStateException("Cancellatin is not expected");
 		}
 	}
-
+	
 	/**
-	 * Annotates changes of the file against its parent(s). 
-	 * Unlike {@link #annotate(HgDataFile, int, Inspector, HgIterateDirection)}, doesn't
-	 * walk file history, looks at the specified revision only. Handles both parents (if merge revision).
+	 * @deprecated use {@link HgDiffCommand} instead
 	 */
+	@Deprecated
 	public void annotateSingleRevision(int changelogRevisionIndex, HgBlameInspector insp) throws HgRuntimeException, HgCallbackTargetException {
-		// TODO detect if file is text/binary (e.g. looking for chars < ' ' and not \t\r\n\f
-		int fileRevIndex = fileRevIndex(this, changelogRevisionIndex);
-		int[] fileRevParents = new int[2];
-		parents(fileRevIndex, fileRevParents, null, null);
-		if (changelogRevisionIndex == TIP) {
-			changelogRevisionIndex = getChangesetRevisionIndex(fileRevIndex);
+		try {
+			new HgDiffCommand(getRepo()).file(this).changeset(changelogRevisionIndex).executeAnnotateSingleRevision(insp);
+		} catch (HgLibraryFailureException ex) {
+			throw ex.getCause();
+		} catch (HgException ex) {
+			throw new HgInvalidStateException(ex.getMessage());
+		} catch (CancelledException ex) {
+			throw new HgInvalidStateException("Cancellatin is not expected");
 		}
-		int[] fileClogParentRevs = new int[2];
-		fileClogParentRevs[0] = fileRevParents[0] == NO_REVISION ? NO_REVISION : getChangesetRevisionIndex(fileRevParents[0]);
-		fileClogParentRevs[1] = fileRevParents[1] == NO_REVISION ? NO_REVISION : getChangesetRevisionIndex(fileRevParents[1]);
-		BlameHelper bh = new BlameHelper(insp);
-		int clogIndexStart = fileClogParentRevs[0] == NO_REVISION ? (fileClogParentRevs[1] == NO_REVISION ? 0 : fileClogParentRevs[1]) : fileClogParentRevs[0];
-		bh.prepare(this, clogIndexStart, changelogRevisionIndex);
-		bh.annotateChange(fileRevIndex, changelogRevisionIndex, fileRevParents, fileClogParentRevs);
 	}
 
 	@Override
@@ -519,13 +493,6 @@
 		RevlogStream.Inspector insp = new MetadataInspector(metadata, null);
 		super.content.iterate(localRev, localRev, true, insp);
 	}
-	
-
-	private static int fileRevIndex(HgDataFile df, int csetRevIndex) throws HgRuntimeException {
-		Nodeid fileRev = df.getRepo().getManifest().getFileRevision(csetRevIndex, df.getPath());
-		return df.getRevisionIndex(fileRev);
-	}
-
 
 	private static class MetadataInspector extends ErrorHandlingInspector implements RevlogStream.Inspector {
 		private final Metadata metadata;
--- a/test/org/tmatesoft/hg/test/TestBlame.java	Wed May 22 15:52:31 2013 +0200
+++ b/test/org/tmatesoft/hg/test/TestBlame.java	Wed May 22 16:46:15 2013 +0200
@@ -51,7 +51,6 @@
 import org.tmatesoft.hg.internal.FileAnnotation.LineInspector;
 import org.tmatesoft.hg.internal.IntVector;
 import org.tmatesoft.hg.repo.HgBlameInspector;
-import org.tmatesoft.hg.repo.HgBlameInspector.BlockData;
 import org.tmatesoft.hg.repo.HgChangelog;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgLookup;
@@ -139,7 +138,7 @@
 		HgDataFile df = repo.getFileNode("file1");
 		ByteArrayOutputStream bos = new ByteArrayOutputStream();
 		DiffOutInspector dump = new DiffOutInspector(new PrintStream(bos));
-		df.annotate(TIP, dump, HgIterateDirection.OldToNew);
+		df.annotate(0, TIP, dump, HgIterateDirection.OldToNew);
 		LinkedList<String> apiResult = new LinkedList<String>(Arrays.asList(splitLines(bos.toString())));
 		
 		/*
@@ -336,7 +335,7 @@
 		HgDataFile df = repo.getFileNode("file.txt");
 		DiffOutInspector dump = new DiffOutInspector(System.out);
 		dump.needRevisions(true);
-		df.annotate(8, dump, HgIterateDirection.NewToOld);
+		df.annotate(0, 8, dump, HgIterateDirection.NewToOld);
 //		af.annotateSingleRevision(df, 113, dump);
 //		System.out.println();
 //		af.annotate(df, TIP, new LineDumpInspector(true), HgIterateDirection.NewToOld);
@@ -466,7 +465,7 @@
 		FileAnnotateInspector() {
 		}
 		
-		public void line(int lineNumber, int changesetRevIndex, BlockData lineContent, LineDescriptor ld) {
+		public void line(int lineNumber, int changesetRevIndex, HgBlameInspector.BlockData lineContent, LineDescriptor ld) {
 			if (lineRevisions == null) {
 				lineRevisions = new Integer[ld.totalLines()];
 				Arrays.fill(lineRevisions, NO_REVISION);