changeset 677:1c49c0cee540

Report line number at the first appearance, like 'hg annotate -l' does
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 18 Jul 2013 18:47:45 +0200
parents 3219cfadda49
children 8625cba0a5a8
files cmdline/org/tmatesoft/hg/console/Annotate.java src/org/tmatesoft/hg/core/HgAnnotateCommand.java src/org/tmatesoft/hg/internal/ForwardAnnotateInspector.java src/org/tmatesoft/hg/internal/IntTuple.java src/org/tmatesoft/hg/internal/LineImpl.java src/org/tmatesoft/hg/internal/ReverseAnnotateInspector.java test/org/tmatesoft/hg/test/TestBlame.java
diffstat 7 files changed, 98 insertions(+), 38 deletions(-) [+]
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/Annotate.java	Thu Jul 18 18:03:51 2013 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Annotate.java	Thu Jul 18 18:47:45 2013 +0200
@@ -60,7 +60,7 @@
 
 		public void next(LineInfo lineInfo) {
 			if (lineNumbers) {
-				System.out.printf("%3d:%3d: %s", lineInfo.getChangesetIndex(), lineInfo.getLineNumber(), new String(lineInfo.getContent()));
+				System.out.printf("%3d:%3d: %s", lineInfo.getChangesetIndex(), lineInfo.getOriginLineNumber(), new String(lineInfo.getContent()));
 			} else {
 				System.out.printf("%3d: %s", lineInfo.getChangesetIndex(), new String(lineInfo.getContent()));
 			}
--- a/src/org/tmatesoft/hg/core/HgAnnotateCommand.java	Thu Jul 18 18:03:51 2013 +0200
+++ b/src/org/tmatesoft/hg/core/HgAnnotateCommand.java	Thu Jul 18 18:47:45 2013 +0200
@@ -143,8 +143,23 @@
 	 * Clients shall not implement this interface
 	 */
 	public interface LineInfo {
+		/**
+		 * @return 1-based index of the line in the annotated revision
+		 */
 		int getLineNumber();
+
+		/**
+		 * @return 1-based line number at the first appearance, at changeset {@link #getChangesetIndex()} 
+		 */
+		int getOriginLineNumber();
+		/**
+		 * @return changeset revision this line was introduced at
+		 */
 		int getChangesetIndex();
+
+		/**
+		 * @return line content
+		 */
 		byte[] getContent();
 	}
 }
--- a/src/org/tmatesoft/hg/internal/ForwardAnnotateInspector.java	Thu Jul 18 18:03:51 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/ForwardAnnotateInspector.java	Thu Jul 18 18:47:45 2013 +0200
@@ -67,7 +67,7 @@
 			for (int i = 0, x = t.at(0); i < x; i++) {
 				final int lineInRev = t.at(2) + i;
 				final byte[] lc = revLines.get(lineInRev);
-				li.init(line++, t.at(1), lc);
+				li.init(line++, lineInRev+1, t.at(1), lc);
 				insp.next(li);
 				progress.worked(1);
 				cancel.checkCancelled();
@@ -112,7 +112,7 @@
 	public void deleted(DeleteBlock block) throws HgCallbackTargetException {
 	}
 	
-	private void copyBlock(int originChangesetIndex, int originStart, int length) {
+	private void copyBlock(int originChangesetIndex, int blockStart, int length) {
 		IntSliceSeq origin = all.get(originChangesetIndex);
 		assert origin != null; // shall visit parents before came to this child
 		int originPos = 0;
@@ -120,17 +120,20 @@
 		for (IntTuple t : origin) {
 			int originBlockLen = t.at(0);
 			int originBlockEnd = originPos + originBlockLen;
-			if (originBlockEnd > originStart) {
-				int originBlockOverlap = Math.min(originBlockLen, originBlockEnd - originStart);
+			if (originBlockEnd > blockStart) {
+				// part of origin block from blockStart up to originBlockEnd, but not more
+				// than size of the block (when blockStart is out of block start, i.e. < originPos)
+				int originBlockOverlap = Math.min(originBlockLen, originBlockEnd - blockStart);
 				assert originBlockOverlap > 0;
-				originBlockOverlap = Math.min(originBlockOverlap, targetBlockLen);
+				// eat as much as there's left in the block being copied
+				int originBlockConsumed = Math.min(originBlockOverlap, targetBlockLen);
 				int originBlockLine = t.at(2);
-				if (originPos < originStart) {
+				if (originPos < blockStart) {
 					originBlockLine += originBlockLen-originBlockOverlap;
 				}
 				// copy fragment of original block;
-				current.add(originBlockOverlap, t.at(1), originBlockLine);
-				targetBlockLen -= originBlockOverlap;
+				current.add(originBlockConsumed, t.at(1), originBlockLine);
+				targetBlockLen -= originBlockConsumed;
 				if (targetBlockLen == 0) {
 					break;
 				}
@@ -144,19 +147,17 @@
 		HgRepository repo = new HgLookup().detect("/home/artem/hg/junit-test-repos/test-annotate/");
 		HgDiffCommand cmd = new HgDiffCommand(repo);
 		cmd.file(repo.getFileNode("file1")).order(HgIterateDirection.OldToNew);
-		cmd.range(0, 8);
+		final int cset = 8;
+		cmd.range(0, cset);
 		final ForwardAnnotateInspector c2 = new ForwardAnnotateInspector();
 		cmd.executeAnnotate(c2);
-		for (IntTuple t : c2.all.get(8)) {
-			System.out.printf("Block %d lines from revision %d (starts with line %d in the origin)\n", t.at(0), t.at(1), t.at(2));
-		}
-		for (IntTuple t : c2.all.get(8)) {
+		for (IntTuple t : c2.all.get(cset)) {
 			System.out.printf("Block %d lines from revision %d (starts with line %d in the origin)\n", t.at(0), t.at(1), 1+t.at(2));
 		}
-		c2.report(8, new Inspector() {
+		c2.report(cset, new Inspector() {
 			
 			public void next(LineInfo lineInfo) throws HgCallbackTargetException {
-				System.out.printf("%3d:%3d: %s", lineInfo.getChangesetIndex(), lineInfo.getLineNumber(), new String(lineInfo.getContent()));
+				System.out.printf("%3d:%3d: %s", lineInfo.getChangesetIndex(), lineInfo.getOriginLineNumber(), new String(lineInfo.getContent()));
 			}
 		}, ProgressSupport.Factory.get(null), CancelSupport.Factory.get(null));
 	}
--- a/src/org/tmatesoft/hg/internal/IntTuple.java	Thu Jul 18 18:03:51 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/IntTuple.java	Thu Jul 18 18:47:45 2013 +0200
@@ -53,4 +53,17 @@
 			throw new Error(ex);
 		}
 	}
+	
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append('(');
+		for (int i = 0; i < size; i++) {
+			sb.append(at(i));
+			sb.append(", ");
+		}
+		sb.setLength(sb.length() - 2);
+		sb.append(')');
+		return sb.toString();
+	}
 }
\ No newline at end of file
--- a/src/org/tmatesoft/hg/internal/LineImpl.java	Thu Jul 18 18:03:51 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/LineImpl.java	Thu Jul 18 18:47:45 2013 +0200
@@ -24,11 +24,13 @@
  */
 final class LineImpl implements LineInfo {
 	private int ln;
+	private int origLine;
 	private int rev;
 	private byte[] content;
 
-	void init(int line, int csetRev, byte[] cnt) {
+	void init(int line, int firstAppearance, int csetRev, byte[] cnt) {
 		ln = line;
+		origLine = firstAppearance;
 		rev = csetRev;
 		content = cnt;
 	}
@@ -37,6 +39,11 @@
 		return ln;
 	}
 
+
+	public int getOriginLineNumber() {
+		return origLine;
+	}
+
 	public int getChangesetIndex() {
 		return rev;
 	}
--- a/src/org/tmatesoft/hg/internal/ReverseAnnotateInspector.java	Thu Jul 18 18:03:51 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/ReverseAnnotateInspector.java	Thu Jul 18 18:47:45 2013 +0200
@@ -59,6 +59,7 @@
 	private boolean activeEqualsComesFromMerge = false;
 
 	private int[] lineRevisions;
+	private int[] lineNumbers;
 
 	/**
 	 * @return desired order of iteration for diff
@@ -72,7 +73,7 @@
 		progress.start(lineRevisions.length);
 		for (int i = 0; i < lineRevisions.length; i++) {
 			byte[] c = lineContent.elementAt(i).asArray();
-			li.init(i+1, lineRevisions[i], c);
+			li.init(i+1, lineNumbers[i] + 1, lineRevisions[i], c);
 			insp.next(li);
 			progress.worked(1);
 			cancel.checkCancelled();
@@ -85,8 +86,9 @@
 		if (knownLines == null) {
 			lineContent = rd.target();
 			knownLines = new boolean[lineContent.elementCount()];
-			lineRevisions = new int [lineContent.elementCount()];
+			lineRevisions = new int [knownLines.length];
 			Arrays.fill(lineRevisions, NO_REVISION);
+			lineNumbers = new int[knownLines.length];
 			activeEquals = new RangePairSeq();
 			activeEquals.add(0, 0, knownLines.length);
 			equalRanges.put(rd.targetChangesetIndex(), activeEquals);
@@ -144,7 +146,7 @@
 				if (rs != null) {
 					rs.add(block.insertedAt() + i, lnInFinal, 1);
 				} else {
-					line(lnInFinal, block.targetChangesetIndex());
+					line(lnInFinal, ln, block.targetChangesetIndex());
 				}
 				knownLines[lnInFinal] = true;
 			}
@@ -158,7 +160,8 @@
 	public void deleted(DeleteBlock block) {
 	}
 
-	private void line(int lineNumber, int changesetRevIndex) {
+	private void line(int lineNumber, int firstAppearance, int changesetRevIndex) {
 		lineRevisions[lineNumber] = changesetRevIndex;
+		lineNumbers[lineNumber] = firstAppearance;
 	}
 }
\ No newline at end of file
--- a/test/org/tmatesoft/hg/test/TestBlame.java	Thu Jul 18 18:03:51 2013 +0200
+++ b/test/org/tmatesoft/hg/test/TestBlame.java	Thu Jul 18 18:47:45 2013 +0200
@@ -31,7 +31,6 @@
 import java.util.Arrays;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.ListIterator;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -108,7 +107,7 @@
 			final ReverseAnnotateInspector insp = new ReverseAnnotateInspector();
 			diffCmd.executeAnnotate(insp);
 			AnnotateInspector fa = new AnnotateInspector().fill(cs, insp);
-			doAnnotateLineCheck(cs, ar.getLines(), fa.changesets, fa.lines);
+			doAnnotateLineCheck(cs, ar, fa);
 		}
 	}
 	
@@ -125,7 +124,7 @@
 			final ReverseAnnotateInspector insp = new ReverseAnnotateInspector();
 			diffCmd.executeAnnotate(insp);
 			AnnotateInspector fa = new AnnotateInspector().fill(cs, insp);
-			doAnnotateLineCheck(cs, ar.getLines(), fa.changesets, fa.lines);
+			doAnnotateLineCheck(cs, ar, fa);
 		}
 		/*`hg annotate -r 8` and HgBlameFacility give different result
 		 * for "r0, line 5" line, which was deleted in rev2 and restored back in
@@ -253,28 +252,30 @@
 		cmd.execute(ai);
 		AnnotateRunner ar = new AnnotateRunner(fname, repo.getWorkingDir());
 		ar.run(changeset, true);
-		doAnnotateLineCheck(changeset, ar.getLines(), ai.changesets, ai.lines);
+		doAnnotateLineCheck(changeset, ar, ai);
 		
 		// no follow
 		cmd.file(fname, false);
 		ai = new AnnotateInspector();
 		cmd.execute(ai);
 		ar.run(changeset, false);
-		doAnnotateLineCheck(changeset, ar.getLines(), ai.changesets, ai.lines);
+		doAnnotateLineCheck(changeset, ar, ai);
 	}
 	
-	// FIXME add originLineNumber to HgAnnotateCommand#LineInfo, pass it from FileAnnotate, test
-
-	private void doAnnotateLineCheck(int cs, String[] hgAnnotateLines, List<Integer> cmdChangesets, List<String> cmdLines) {
+	private void doAnnotateLineCheck(int cs, AnnotateRunner ar, AnnotateInspector hg4jResult) {
+		String[] hgAnnotateLines = ar.getLines();
 		assertTrue("[sanity]", hgAnnotateLines.length > 0);
-		assertEquals("Number of lines reported by native annotate and our impl", hgAnnotateLines.length, cmdLines.size());
+		assertEquals("Number of lines reported by native annotate and our impl", hgAnnotateLines.length, hg4jResult.getLineCount());
 
-		for (int i = 0; i < cmdChangesets.size(); i++) {
-			int hgAnnotateRevIndex = Integer.parseInt(hgAnnotateLines[i].substring(0, hgAnnotateLines[i].indexOf(':')).trim());
-			errorCollector.assertEquals(String.format("Revision mismatch for line %d (annotating rev: %d)", i+1, cs), hgAnnotateRevIndex, cmdChangesets.get(i));
-			String hgAnnotateLine = hgAnnotateLines[i].substring(hgAnnotateLines[i].indexOf(':') + 1);
-			String apiLine = cmdLines.get(i).trim();
-			errorCollector.assertEquals(hgAnnotateLine.trim(), apiLine);
+		for (int i = 0; i < hgAnnotateLines.length; i++) {
+			String[] hgLine = hgAnnotateLines[i].split(":");
+			assertTrue(hgAnnotateLines[i], hgLine.length >= 3);
+			int hgAnnotateRevIndex = Integer.parseInt(hgLine[0].trim());
+			int hgFirstAppLine = Integer.parseInt(hgLine[1].trim());
+			String hgLineText = hgAnnotateLines[i].substring(hgLine[0].length() + hgLine[1].length() + 2).trim(); 
+			errorCollector.assertEquals(String.format("Revision mismatch for line %d (annotating rev: %d)", i+1, cs), hgAnnotateRevIndex, hg4jResult.getChangeset(i));
+			errorCollector.assertEquals(hgLineText, hg4jResult.getLine(i).trim());
+			errorCollector.assertEquals(hgFirstAppLine, hg4jResult.getOriginLine(i));
 		}
 	}
 	
@@ -525,10 +526,15 @@
 		}
 	}
 	
+	/**
+	 * Note, this class expects lines coming in natural sequence (not the order they are detected - possible with {@link ReverseAnnotateInspector})
+	 * Once async lines are done, shall change implementation here 
+	 */
 	static class AnnotateInspector implements HgAnnotateCommand.Inspector {
 		private int lineNumber = 1;
-		public final ArrayList<String> lines = new ArrayList<String>();
-		public final ArrayList<Integer> changesets = new ArrayList<Integer>();
+		private final ArrayList<String> lines = new ArrayList<String>();
+		private final IntVector changesets = new IntVector();
+		private final IntVector firstAppLines = new IntVector();
 
 		AnnotateInspector fill(int rev, ReverseAnnotateInspector ai) throws HgCallbackTargetException, CancelledException {
 			ai.report(rev, this, ProgressSupport.Factory.get(null), CancelSupport.Factory.get(null));
@@ -544,6 +550,20 @@
 			lineNumber++;
 			lines.add(new String(lineInfo.getContent()));
 			changesets.add(lineInfo.getChangesetIndex());
+			firstAppLines.add(lineInfo.getOriginLineNumber());
+		}
+		
+		int getLineCount() {
+			return changesets.size();
+		}
+		int getChangeset(int line) {
+			return changesets.get(line);
+		}
+		String getLine(int line) {
+			return lines.get(line);
+		}
+		int getOriginLine(int line) {
+			return firstAppLines.get(line);
 		}
 	}
 	
@@ -563,6 +583,7 @@
 			ArrayList<String> args = new ArrayList<String>();
 			args.add("hg");
 			args.add("annotate");
+			args.add("--line-number");
 			args.add("-r");
 			args.add(cset == TIP ? "tip" : String.valueOf(cset));
 			if (!follow) {