changeset 546:cd78e8b9d7bc

File annotate test. Refactored FileAnnotation as standalone class, introduced LineInspector to make line offset calc code shared
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Mon, 18 Feb 2013 19:19:48 +0100
parents 15b406c7cd9d
children 66fc86e8c0dd
files build.xml src/org/tmatesoft/hg/internal/AnnotateFacility.java test/org/tmatesoft/hg/test/FileAnnotation.java test/org/tmatesoft/hg/test/TestBlame.java
diffstat 4 files changed, 255 insertions(+), 166 deletions(-) [+]
line wrap: on
line diff
--- a/build.xml	Fri Feb 15 22:15:13 2013 +0100
+++ b/build.xml	Mon Feb 18 19:19:48 2013 +0100
@@ -105,6 +105,7 @@
 			<test name="org.tmatesoft.hg.test.TestCheckout" />
 			<test name="org.tmatesoft.hg.test.TestAddRemove" />
 			<test name="org.tmatesoft.hg.test.TestCommit" />
+			<test name="org.tmatesoft.hg.test.TestBlame" />
 		</junit>
 	</target>
 
--- a/src/org/tmatesoft/hg/internal/AnnotateFacility.java	Fri Feb 15 22:15:13 2013 +0100
+++ b/src/org/tmatesoft/hg/internal/AnnotateFacility.java	Mon Feb 18 19:19:48 2013 +0100
@@ -35,7 +35,7 @@
 	/**
 	 * Annotates changes of the file against its parent(s)
 	 */
-	public void annotateChange(HgDataFile df, int changesetRevisionIndex, Inspector insp) {
+	public void annotateChange(HgDataFile df, int changesetRevisionIndex, BlockInspector insp) {
 		// TODO detect if file is text/binary (e.g. looking for chars < ' ' and not \t\r\n\f
 		Nodeid fileRev = df.getRepo().getManifest().getFileRevision(changesetRevisionIndex, df.getPath());
 		int fileRevIndex = df.getRevisionIndex(fileRev);
@@ -76,7 +76,7 @@
 	}
 
 	@Callback
-	public interface Inspector {
+	public interface BlockInspector {
 		void same(EqualBlock block);
 		void added(AddBlock block);
 		void changed(ChangeBlock block);
@@ -84,7 +84,7 @@
 	}
 	
 	@Callback
-	public interface InspectorEx extends Inspector { // XXX better name
+	public interface BlockInspectorEx extends BlockInspector { // XXX better name
 		// XXX perhaps, shall pass object instead of separate values for future extension?
 		void start(int originLineCount, int targetLineCount);
 		void done();
@@ -120,13 +120,24 @@
 	}
 	public interface ChangeBlock extends AddBlock, DeleteBlock {
 	}
+	
+	@Callback
+	public interface LineInspector {
+		void line(int lineNumber, int changesetRevIndex, LineDescriptor ld);
+	}
+	
+	public interface LineDescriptor {
+		int totalLines();
+	}
+	
+
 
 	static class BlameBlockInspector extends PatchGenerator.DeltaInspector<LineSequence> {
-		private final Inspector insp;
+		private final BlockInspector insp;
 		private final int csetP1;
 		private final int csetTarget;
 
-		public BlameBlockInspector(Inspector inspector, int parentCset1, int targetCset) {
+		public BlameBlockInspector(BlockInspector inspector, int parentCset1, int targetCset) {
 			assert inspector != null;
 			insp = inspector;
 			csetP1 = parentCset1;
@@ -136,16 +147,16 @@
 		@Override
 		public void begin(LineSequence s1, LineSequence s2) {
 			super.begin(s1, s2);
-			if (insp instanceof InspectorEx) {
-				((InspectorEx) insp).start(s1.chunkCount() - 1, s2.chunkCount() - 1);
+			if (insp instanceof BlockInspectorEx) {
+				((BlockInspectorEx) insp).start(s1.chunkCount() - 1, s2.chunkCount() - 1);
 			}
 		}
 		
 		@Override
 		public void end() {
 			super.end();
-			if(insp instanceof InspectorEx) {
-				((InspectorEx) insp).done();
+			if(insp instanceof BlockInspectorEx) {
+				((BlockInspectorEx) insp).done();
 			}
 		}
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/org/tmatesoft/hg/test/FileAnnotation.java	Mon Feb 18 19:19:48 2013 +0100
@@ -0,0 +1,196 @@
+/*
+ * 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.test;
+
+import java.util.LinkedList;
+
+import org.tmatesoft.hg.internal.AnnotateFacility;
+import org.tmatesoft.hg.internal.AnnotateFacility.LineInspector;
+import org.tmatesoft.hg.internal.IntVector;
+import org.tmatesoft.hg.internal.AnnotateFacility.AddBlock;
+import org.tmatesoft.hg.internal.AnnotateFacility.ChangeBlock;
+import org.tmatesoft.hg.internal.AnnotateFacility.DeleteBlock;
+import org.tmatesoft.hg.internal.AnnotateFacility.EqualBlock;
+
+/**
+ * 
+ * @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(int originLineCount, int targetLineCount) {
+			if (knownLines == null) {
+				knownLines = new boolean[targetLineCount];
+			}
+		}
+
+//		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() {
+			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
+						}
+						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);
+					}
+				}
+				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;
+				}
+			}
+		}
+		
+		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 LineDescriptor implements AnnotateFacility.LineDescriptor {
+			LineDescriptor() {
+			}
+			
+			public int totalLines() {
+				return FileAnnotation.this.knownLines.length;
+			}
+		}
+	}
\ No newline at end of file
--- a/test/org/tmatesoft/hg/test/TestBlame.java	Fri Feb 15 22:15:13 2013 +0100
+++ b/test/org/tmatesoft/hg/test/TestBlame.java	Mon Feb 18 19:19:48 2013 +0100
@@ -16,12 +16,13 @@
  */
 package org.tmatesoft.hg.test;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION;
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.util.Arrays;
-import java.util.LinkedList;
 import java.util.regex.Pattern;
 
 import org.junit.Assert;
@@ -31,8 +32,8 @@
 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.IntMap;
-import org.tmatesoft.hg.internal.IntVector;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgLookup;
 import org.tmatesoft.hg.repo.HgRepository;
@@ -66,22 +67,25 @@
 	public void testFileAnnotate() throws Exception {
 		HgRepository repo = new HgLookup().detectFromWorkingDir();
 		final String fname = "src/org/tmatesoft/hg/internal/PatchGenerator.java";
-		final int checkChangeset = 539;
+		final int[] checkChangesets = new int[] { 539 , 536, 531 };
 		HgDataFile df = repo.getFileNode(fname);
 		AnnotateFacility af = new AnnotateFacility();
-		System.out.println("536 -> 539");
-		af.annotateChange(df, checkChangeset, new DiffOutInspector(System.out));
-		System.out.println("531 -> 536");
-		af.annotateChange(df, 536, new DiffOutInspector(System.out));
-		System.out.println(" -1 -> 531");
-		af.annotateChange(df, 531, new DiffOutInspector(System.out));
+		FileAnnotateInspector fa = new FileAnnotateInspector();
+		for (int cs : checkChangesets) {
+			af.annotateChange(df, cs, new FileAnnotation(fa));
+		}
 		
-		FileAnnotation fa = new FileAnnotation();
-		af.annotateChange(df, checkChangeset, fa);
-		af.annotateChange(df, 536, fa);
-		af.annotateChange(df, 531, fa);
+		OutputParser.Stub op = new OutputParser.Stub();
+		ExecHelper eh = new ExecHelper(op, null);
+		eh.run("hg", "annotate", "-r", String.valueOf(checkChangesets[0]), fname);
+		
+		String[] hgAnnotateLines = splitLines(op.result());
+		assertTrue("[sanity]", hgAnnotateLines.length > 0);
+		assertEquals("Number of lines reported by native annotate and our impl", hgAnnotateLines.length, fa.lineRevisions.length);
+
 		for (int i = 0; i < fa.lineRevisions.length; i++) {
-			System.out.printf("%3d: %d\n", fa.lineRevisions[i], i+1);
+			int hgAnnotateRevIndex = Integer.parseInt(hgAnnotateLines[i].substring(0, hgAnnotateLines[i].indexOf(':')));
+			assertEquals(String.format("Revision mismatch for line %d", i+1), hgAnnotateRevIndex, fa.lineRevisions[i]);
 		}
 	}
 	
@@ -110,7 +114,19 @@
 		return rv;
 	}
 	
-	private void leftovers() {
+	private void leftovers() throws Exception {
+		HgRepository repo = new HgLookup().detectFromWorkingDir();
+		final String fname = "src/org/tmatesoft/hg/internal/PatchGenerator.java";
+		final int checkChangeset = 539;
+		HgDataFile df = repo.getFileNode(fname);
+		AnnotateFacility af = new AnnotateFacility();
+		System.out.println("536 -> 539");
+		af.annotateChange(df, checkChangeset, new DiffOutInspector(System.out));
+		System.out.println("531 -> 536");
+		af.annotateChange(df, 536, new DiffOutInspector(System.out));
+		System.out.println(" -1 -> 531");
+		af.annotateChange(df, 531, new DiffOutInspector(System.out));
+
 		IntMap<String> linesOld = new IntMap<String>(100), linesNew = new IntMap<String>(100);
 		System.out.println("Changes to old revision:");
 		for (int i = linesOld.firstKey(), x = linesOld.lastKey(); i < x; i++) {
@@ -135,7 +151,7 @@
 		new TestBlame().testFileAnnotate();
 	}
 
-	static class DiffOutInspector implements AnnotateFacility.Inspector {
+	static class DiffOutInspector implements AnnotateFacility.BlockInspector {
 		private final PrintStream out;
 		
 		DiffOutInspector(PrintStream ps) {
@@ -201,154 +217,19 @@
 		}
 	}
 
-	private static class FileAnnotation implements AnnotateFacility.InspectorEx {
+	private static class FileAnnotateInspector implements AnnotateFacility.LineInspector {
 		private int[] lineRevisions;
-		private LinkedList<DeleteBlock> deleted = new LinkedList<DeleteBlock>();
-		private LinkedList<DeleteBlock> newDeleted = new LinkedList<DeleteBlock>();
-		// keeps <startSeq1, startSeq2, len> of equal blocks
-		// 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);
-		private IntVector newIdentical = new IntVector(20*3, 2*3);
 		
-		public FileAnnotation() {
+		FileAnnotateInspector() {
 		}
 		
-		public void start(int originLineCount, int targetLineCount) {
+		public void line(int lineNumber, int changesetRevIndex, LineDescriptor ld) {
 			if (lineRevisions == null) {
-				lineRevisions = new int [targetLineCount];
+				lineRevisions = new int [ld.totalLines()];
 				Arrays.fill(lineRevisions, NO_REVISION);
 			}
-		}
-
-//		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() {
-			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
-						}
-						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);
-					}
-				}
-				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 && historyUnknown(lnInFinal)) {
-					lineRevisions[lnInFinal] = block.targetChangesetIndex();
-				}
-			}
-		}
-
-		public void changed(ChangeBlock block) {
-			deleted(block);
-			added(block);
-		}
-
-		public void deleted(DeleteBlock block) {
-			newDeleted.add(block);
-		}
-		
-		private boolean historyUnknown(int lineNumber) {
-			return lineRevisions[lineNumber] == NO_REVISION;
-		}
-
-		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;
+			lineRevisions[lineNumber] = changesetRevIndex;
 		}
 	}
+
 }