tikhomirov@542: /* tikhomirov@542: * Copyright (c) 2013 TMate Software Ltd tikhomirov@542: * tikhomirov@542: * This program is free software; you can redistribute it and/or modify tikhomirov@542: * it under the terms of the GNU General Public License as published by tikhomirov@542: * the Free Software Foundation; version 2 of the License. tikhomirov@542: * tikhomirov@542: * This program is distributed in the hope that it will be useful, tikhomirov@542: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@542: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@542: * GNU General Public License for more details. tikhomirov@542: * tikhomirov@542: * For information on how to redistribute this software under tikhomirov@542: * the terms of a license other than GNU General Public License tikhomirov@542: * contact TMate Software at support@hg4j.com tikhomirov@542: */ tikhomirov@542: package org.tmatesoft.hg.test; tikhomirov@542: tikhomirov@546: import static org.junit.Assert.assertEquals; tikhomirov@546: import static org.junit.Assert.assertTrue; tikhomirov@573: import static org.tmatesoft.hg.core.HgIterateDirection.NewToOld; tikhomirov@573: import static org.tmatesoft.hg.core.HgIterateDirection.OldToNew; tikhomirov@548: import static org.tmatesoft.hg.repo.HgRepository.TIP; tikhomirov@545: tikhomirov@543: import java.io.ByteArrayOutputStream; tikhomirov@570: import java.io.File; tikhomirov@573: import java.io.IOException; tikhomirov@573: import java.io.OutputStream; tikhomirov@543: import java.io.PrintStream; tikhomirov@570: import java.util.ArrayList; tikhomirov@543: import java.util.Arrays; tikhomirov@553: import java.util.LinkedHashSet; tikhomirov@553: import java.util.LinkedList; tikhomirov@570: import java.util.List; tikhomirov@557: import java.util.ListIterator; tikhomirov@557: import java.util.regex.Matcher; tikhomirov@543: import java.util.regex.Pattern; tikhomirov@543: tikhomirov@543: import org.junit.Assert; tikhomirov@549: import org.junit.Rule; tikhomirov@542: import org.junit.Test; tikhomirov@570: import org.tmatesoft.hg.core.HgAnnotateCommand; tikhomirov@570: import org.tmatesoft.hg.core.HgAnnotateCommand.LineInfo; tikhomirov@630: import org.tmatesoft.hg.core.HgBlameInspector; tikhomirov@562: import org.tmatesoft.hg.core.HgCallbackTargetException; tikhomirov@630: import org.tmatesoft.hg.core.HgDiffCommand; tikhomirov@570: import org.tmatesoft.hg.core.HgRepoFacade; tikhomirov@625: import org.tmatesoft.hg.core.Nodeid; tikhomirov@676: import org.tmatesoft.hg.internal.ForwardAnnotateInspector; tikhomirov@555: import org.tmatesoft.hg.internal.IntVector; tikhomirov@676: import org.tmatesoft.hg.internal.ReverseAnnotateInspector; tikhomirov@625: import org.tmatesoft.hg.repo.HgChangelog; tikhomirov@557: import org.tmatesoft.hg.repo.HgDataFile; tikhomirov@557: import org.tmatesoft.hg.repo.HgLookup; tikhomirov@557: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@676: import org.tmatesoft.hg.util.CancelSupport; tikhomirov@676: import org.tmatesoft.hg.util.CancelledException; tikhomirov@570: import org.tmatesoft.hg.util.Path; tikhomirov@676: import org.tmatesoft.hg.util.ProgressSupport; tikhomirov@542: tikhomirov@542: /** tikhomirov@542: * tikhomirov@542: * @author Artem Tikhomirov tikhomirov@542: * @author TMate Software Ltd. tikhomirov@542: */ tikhomirov@542: public class TestBlame { tikhomirov@542: tikhomirov@549: @Rule tikhomirov@549: public ErrorCollectorExt errorCollector = new ErrorCollectorExt(); tikhomirov@549: tikhomirov@542: tikhomirov@542: @Test tikhomirov@542: public void testSingleParentBlame() throws Exception { tikhomirov@542: HgRepository repo = new HgLookup().detectFromWorkingDir(); tikhomirov@543: final String fname = "src/org/tmatesoft/hg/internal/PatchGenerator.java"; tikhomirov@625: final int checkChangeset = repo.getChangelog().getRevisionIndex(Nodeid.fromAscii("946b131962521f9199e1fedbdc2487d3aaef5e46")); // 539 tikhomirov@543: HgDataFile df = repo.getFileNode(fname); tikhomirov@543: ByteArrayOutputStream bos = new ByteArrayOutputStream(); tikhomirov@630: HgDiffCommand diffCmd = new HgDiffCommand(repo); tikhomirov@630: diffCmd.file(df).changeset(checkChangeset); tikhomirov@632: diffCmd.executeParentsAnnotate(new DiffOutInspector(new PrintStream(bos))); tikhomirov@543: LineGrepOutputParser gp = new LineGrepOutputParser("^@@.+"); tikhomirov@543: ExecHelper eh = new ExecHelper(gp, null); tikhomirov@543: eh.run("hg", "diff", "-c", String.valueOf(checkChangeset), "-U", "0", fname); tikhomirov@543: // tikhomirov@543: String[] apiResult = splitLines(bos.toString()); tikhomirov@543: String[] expected = splitLines(gp.result()); tikhomirov@543: Assert.assertArrayEquals(expected, apiResult); tikhomirov@543: } tikhomirov@543: tikhomirov@545: @Test tikhomirov@557: public void testFileLineAnnotate1() throws Exception { tikhomirov@545: HgRepository repo = new HgLookup().detectFromWorkingDir(); tikhomirov@545: final String fname = "src/org/tmatesoft/hg/internal/PatchGenerator.java"; tikhomirov@545: HgDataFile df = repo.getFileNode(fname); tikhomirov@570: AnnotateRunner ar = new AnnotateRunner(df.getPath(), null); tikhomirov@548: tikhomirov@630: final HgDiffCommand diffCmd = new HgDiffCommand(repo); tikhomirov@630: diffCmd.file(df).order(NewToOld); tikhomirov@625: final HgChangelog clog = repo.getChangelog(); tikhomirov@625: final int[] toTest = new int[] { tikhomirov@625: clog.getRevisionIndex(Nodeid.fromAscii("946b131962521f9199e1fedbdc2487d3aaef5e46")), // 539 tikhomirov@625: clog.getRevisionIndex(Nodeid.fromAscii("1e95f48d9886abe79b9711ab371bc877ca5e773e")), // 541 tikhomirov@625: /*, TIP */}; tikhomirov@625: for (int cs : toTest) { tikhomirov@570: ar.run(cs, false); tikhomirov@630: diffCmd.range(0, cs); tikhomirov@676: final ReverseAnnotateInspector insp = new ReverseAnnotateInspector(); tikhomirov@676: diffCmd.executeAnnotate(insp); tikhomirov@676: AnnotateInspector fa = new AnnotateInspector().fill(cs, insp); tikhomirov@676: doAnnotateLineCheck(cs, ar.getLines(), fa.changesets, fa.lines); tikhomirov@557: } tikhomirov@557: } tikhomirov@548: tikhomirov@557: @Test tikhomirov@557: public void testFileLineAnnotate2() throws Exception { tikhomirov@557: HgRepository repo = Configuration.get().find("test-annotate"); tikhomirov@557: HgDataFile df = repo.getFileNode("file1"); tikhomirov@570: AnnotateRunner ar = new AnnotateRunner(df.getPath(), repo.getWorkingDir()); tikhomirov@570: tikhomirov@630: final HgDiffCommand diffCmd = new HgDiffCommand(repo).file(df).order(NewToOld); tikhomirov@558: for (int cs : new int[] { 4, 6 /*, 8 see below*/, TIP}) { tikhomirov@570: ar.run(cs, false); tikhomirov@630: diffCmd.range(0, cs); tikhomirov@676: final ReverseAnnotateInspector insp = new ReverseAnnotateInspector(); tikhomirov@676: diffCmd.executeAnnotate(insp); tikhomirov@676: AnnotateInspector fa = new AnnotateInspector().fill(cs, insp); tikhomirov@676: doAnnotateLineCheck(cs, ar.getLines(), fa.changesets, fa.lines); tikhomirov@545: } tikhomirov@558: /*`hg annotate -r 8` and HgBlameFacility give different result tikhomirov@558: * for "r0, line 5" line, which was deleted in rev2 and restored back in tikhomirov@558: * rev4 (both in default branch), while branch with r3 and r6 kept the line intact. tikhomirov@558: * HgBlame reports rev4 for the line, `hg annotate` gives original, rev0. tikhomirov@558: * However `hg annotate -r 4` shows rev4 for the line, too. The aforementioned rev0 for tikhomirov@558: * the merge rev8 results from the iteration order and is implementation specific tikhomirov@558: * (i.e. one can't tell which one is right). Mercurial walks from parents to children, tikhomirov@676: * and traces equal lines, while HgBlameFacility walks from child to parents and records tikhomirov@558: * changes (additions). Seems it processes branch with rev3 and rev6 first tikhomirov@558: * (printout in context.py, annotate and annotate.pair reveals that), and the line 0_5 tikhomirov@558: * comes as unchanged through this branch, and later processing rev2 and rev4 doesn't tikhomirov@558: * change that. tikhomirov@558: */ tikhomirov@545: } tikhomirov@545: tikhomirov@553: @Test tikhomirov@553: public void testComplexHistoryAnnotate() throws Exception { tikhomirov@553: HgRepository repo = Configuration.get().find("test-annotate"); tikhomirov@553: HgDataFile df = repo.getFileNode("file1"); tikhomirov@553: ByteArrayOutputStream bos = new ByteArrayOutputStream(); tikhomirov@553: DiffOutInspector dump = new DiffOutInspector(new PrintStream(bos)); tikhomirov@630: HgDiffCommand diffCmd = new HgDiffCommand(repo); tikhomirov@630: diffCmd.file(df).range(0, TIP).order(OldToNew); tikhomirov@630: diffCmd.executeAnnotate(dump); tikhomirov@553: LinkedList apiResult = new LinkedList(Arrays.asList(splitLines(bos.toString()))); tikhomirov@553: tikhomirov@557: /* tikhomirov@557: * FIXME this is an ugly hack to deal with the way `hg diff -c ` describes the change tikhomirov@557: * and our merge handling approach. For merged revision m, and lines changed both in p1 and p2 tikhomirov@557: * we report lines from p2 as pure additions, regardless of intersecting p1 changes (which tikhomirov@557: * are reported as deletions, if no sufficient changed lines in m found) tikhomirov@557: * So, here we try to combine deletion that follows a change (based on identical insertionPoint) tikhomirov@557: * into a single change tikhomirov@557: * To fix, need to find better approach to find out reference info (i.e. `hg diff -c` is flawed in this case, tikhomirov@557: * as it uses first parent only). tikhomirov@557: */ tikhomirov@557: Pattern fix = Pattern.compile("@@ -(\\d+),(\\d+) \\+(\\d+),(\\d+) @@"); tikhomirov@557: int v1, v2, v3, v4; tikhomirov@557: v1 = v2 = v3 = v4 = -1; tikhomirov@557: for (ListIterator it = apiResult.listIterator(); it.hasNext();) { tikhomirov@557: String n = it.next(); tikhomirov@557: Matcher m = fix.matcher(n); tikhomirov@557: if (m.find()) { tikhomirov@557: int d1 = Integer.parseInt(m.group(1)); tikhomirov@557: int d2 = Integer.parseInt(m.group(2)); tikhomirov@557: int d3 = Integer.parseInt(m.group(3)); tikhomirov@557: int d4 = Integer.parseInt(m.group(4)); tikhomirov@557: if (v1 == d1 && d4 == 0) { tikhomirov@557: it.previous(); // shift to current element tikhomirov@557: it.previous(); // to real previous tikhomirov@557: it.remove(); tikhomirov@557: it.next(); tikhomirov@557: it.set(String.format("@@ -%d,%d +%d,%d @@", v1, v2+d2, v3, v4)); tikhomirov@557: } tikhomirov@557: v1 = d1; tikhomirov@557: v2 = d2; tikhomirov@557: v3 = d3; tikhomirov@557: v4 = d4; tikhomirov@557: } tikhomirov@557: } tikhomirov@557: tikhomirov@553: LineGrepOutputParser gp = new LineGrepOutputParser("^@@.+"); tikhomirov@553: ExecHelper eh = new ExecHelper(gp, repo.getWorkingDir()); tikhomirov@553: for (int cs : dump.getReportedTargetRevisions()) { tikhomirov@553: gp.reset(); tikhomirov@553: eh.run("hg", "diff", "-c", String.valueOf(cs), "-U", "0", df.getPath().toString()); tikhomirov@553: for (String expected : splitLines(gp.result())) { tikhomirov@553: if (!apiResult.remove(expected)) { tikhomirov@553: errorCollector.fail(String.format("Expected diff output '%s' for changes in revision %d", expected, cs)); tikhomirov@553: } tikhomirov@553: } tikhomirov@553: } tikhomirov@553: errorCollector.assertTrue(String.format("Annotate API reported excessive diff: %s ", apiResult.toString()), apiResult.isEmpty()); tikhomirov@553: } tikhomirov@570: tikhomirov@573: tikhomirov@573: @Test tikhomirov@573: public void testPartialHistoryFollow() throws Exception { tikhomirov@573: HgRepository repo = Configuration.get().find("test-annotate2"); tikhomirov@573: HgDataFile df = repo.getFileNode("file1b.txt"); tikhomirov@573: // rev3: file1 -> file1a, rev7: file1a -> file1b, tip: rev10 tikhomirov@573: DiffOutInspector insp = new DiffOutInspector(new PrintStream(new OutputStream() { tikhomirov@573: @Override tikhomirov@573: public void write(int b) throws IOException { tikhomirov@573: // NULL OutputStream tikhomirov@573: } tikhomirov@573: })); tikhomirov@573: // rev6 changes rev4, rev4 changes rev3. Plus, anything changed tikhomirov@573: // earlier than rev2 shall be reported as new from change3 tikhomirov@573: int[] change_2_8_new2old = new int[] {4, 6, 3, 4, -1, 3}; tikhomirov@573: int[] change_2_8_old2new = new int[] {-1, 3, 3, 4, 4, 6 }; tikhomirov@630: final HgDiffCommand cmd = new HgDiffCommand(repo); tikhomirov@630: cmd.file(df); tikhomirov@630: cmd.range(2, 8).order(NewToOld); tikhomirov@630: cmd.executeAnnotate(insp); tikhomirov@573: Assert.assertArrayEquals(change_2_8_new2old, insp.getReportedRevisionPairs()); tikhomirov@573: insp.reset(); tikhomirov@630: cmd.order(OldToNew).executeAnnotate(insp); tikhomirov@573: Assert.assertArrayEquals(change_2_8_old2new, insp.getReportedRevisionPairs()); tikhomirov@573: // same as 2 to 8, with addition of rev9 changes rev7 (rev6 to rev7 didn't change content, only name) tikhomirov@573: int[] change_3_9_new2old = new int[] {7, 9, 4, 6, 3, 4, -1, 3 }; tikhomirov@573: int[] change_3_9_old2new = new int[] {-1, 3, 3, 4, 4, 6, 7, 9 }; tikhomirov@573: insp.reset(); tikhomirov@630: cmd.range(3, 9).order(NewToOld).executeAnnotate(insp); tikhomirov@573: Assert.assertArrayEquals(change_3_9_new2old, insp.getReportedRevisionPairs()); tikhomirov@573: insp.reset(); tikhomirov@630: cmd.order(OldToNew).executeAnnotate(insp); tikhomirov@573: Assert.assertArrayEquals(change_3_9_old2new, insp.getReportedRevisionPairs()); tikhomirov@573: } tikhomirov@573: tikhomirov@570: @Test tikhomirov@570: public void testAnnotateCmdFollowNoFollow() throws Exception { tikhomirov@570: HgRepoFacade hgRepoFacade = new HgRepoFacade(); tikhomirov@570: HgRepository repo = Configuration.get().find("test-annotate2"); tikhomirov@570: hgRepoFacade.init(repo); tikhomirov@570: HgAnnotateCommand cmd = hgRepoFacade.createAnnotateCommand(); tikhomirov@570: final Path fname = Path.create("file1b.txt"); tikhomirov@570: final int changeset = TIP; tikhomirov@570: AnnotateInspector ai = new AnnotateInspector(); tikhomirov@570: tikhomirov@570: cmd.changeset(changeset); tikhomirov@570: // follow tikhomirov@570: cmd.file(fname); tikhomirov@570: cmd.execute(ai); tikhomirov@570: AnnotateRunner ar = new AnnotateRunner(fname, repo.getWorkingDir()); tikhomirov@570: ar.run(changeset, true); tikhomirov@570: doAnnotateLineCheck(changeset, ar.getLines(), ai.changesets, ai.lines); tikhomirov@570: tikhomirov@570: // no follow tikhomirov@570: cmd.file(fname, false); tikhomirov@570: ai = new AnnotateInspector(); tikhomirov@570: cmd.execute(ai); tikhomirov@570: ar.run(changeset, false); tikhomirov@570: doAnnotateLineCheck(changeset, ar.getLines(), ai.changesets, ai.lines); tikhomirov@570: } tikhomirov@676: tikhomirov@676: // FIXME add originLineNumber to HgAnnotateCommand#LineInfo, pass it from FileAnnotate, test tikhomirov@570: tikhomirov@570: private void doAnnotateLineCheck(int cs, String[] hgAnnotateLines, List cmdChangesets, List cmdLines) { tikhomirov@570: assertTrue("[sanity]", hgAnnotateLines.length > 0); tikhomirov@570: assertEquals("Number of lines reported by native annotate and our impl", hgAnnotateLines.length, cmdLines.size()); tikhomirov@570: tikhomirov@570: for (int i = 0; i < cmdChangesets.size(); i++) { tikhomirov@570: int hgAnnotateRevIndex = Integer.parseInt(hgAnnotateLines[i].substring(0, hgAnnotateLines[i].indexOf(':')).trim()); tikhomirov@570: errorCollector.assertEquals(String.format("Revision mismatch for line %d (annotating rev: %d)", i+1, cs), hgAnnotateRevIndex, cmdChangesets.get(i)); tikhomirov@570: String hgAnnotateLine = hgAnnotateLines[i].substring(hgAnnotateLines[i].indexOf(':') + 1); tikhomirov@570: String apiLine = cmdLines.get(i).trim(); tikhomirov@570: errorCollector.assertEquals(hgAnnotateLine.trim(), apiLine); tikhomirov@570: } tikhomirov@570: } tikhomirov@625: tikhomirov@625: tikhomirov@625: @Test tikhomirov@625: public void testDiffTwoRevisions() throws Exception { tikhomirov@625: HgRepository repo = Configuration.get().find("test-annotate"); tikhomirov@625: HgDataFile df = repo.getFileNode("file1"); tikhomirov@625: LineGrepOutputParser gp = new LineGrepOutputParser("^@@.+"); tikhomirov@625: ExecHelper eh = new ExecHelper(gp, repo.getWorkingDir()); tikhomirov@625: int[] toTest = { 3, 4, 5 }; // p1 ancestry line, p2 ancestry line, not in ancestry line tikhomirov@630: final HgDiffCommand diffCmd = new HgDiffCommand(repo).file(df); tikhomirov@625: for (int cs : toTest) { tikhomirov@625: ByteArrayOutputStream bos = new ByteArrayOutputStream(); tikhomirov@630: diffCmd.range(cs, 8).executeDiff(new DiffOutInspector(new PrintStream(bos))); tikhomirov@625: eh.run("hg", "diff", "-r", String.valueOf(cs), "-r", "8", "-U", "0", df.getPath().toString()); tikhomirov@625: // tikhomirov@625: String[] apiResult = splitLines(bos.toString()); tikhomirov@625: String[] expected = splitLines(gp.result()); tikhomirov@630: Assert.assertArrayEquals("diff -r " + cs + "-r 8", expected, apiResult); tikhomirov@625: gp.reset(); tikhomirov@625: } tikhomirov@625: } tikhomirov@625: tikhomirov@625: /** tikhomirov@625: * Make sure boundary values are ok (down to BlameHelper#prepare and FileHistory) tikhomirov@625: */ tikhomirov@625: @Test tikhomirov@625: public void testAnnotateFirstFileRev() throws Exception { tikhomirov@625: HgRepository repo = Configuration.get().find("test-annotate"); tikhomirov@625: HgDataFile df = repo.getFileNode("file1"); tikhomirov@625: LineGrepOutputParser gp = new LineGrepOutputParser("^@@.+"); tikhomirov@625: ExecHelper eh = new ExecHelper(gp, repo.getWorkingDir()); tikhomirov@625: eh.run("hg", "diff", "-c", "0", "-U", "0", df.getPath().toString()); tikhomirov@625: // tikhomirov@625: ByteArrayOutputStream bos = new ByteArrayOutputStream(); tikhomirov@630: HgDiffCommand diffCmd = new HgDiffCommand(repo).file(df); tikhomirov@632: diffCmd.changeset(0).executeParentsAnnotate(new DiffOutInspector(new PrintStream(bos))); tikhomirov@625: // tikhomirov@625: String[] apiResult = splitLines(bos.toString()); tikhomirov@625: String[] expected = splitLines(gp.result()); tikhomirov@625: Assert.assertArrayEquals(expected, apiResult); tikhomirov@625: } tikhomirov@570: tikhomirov@624: // TODO HgWorkingCopyStatusCollector (and HgStatusCollector), with their ancestors (rev 59/69) have examples tikhomirov@570: // of *incorrect* assignment of common lines (like "}") - our impl doesn't process common lines in any special way tikhomirov@570: // while original diff lib does. Would be nice to behave as close to original, as possible. tikhomirov@553: tikhomirov@543: private static String[] splitLines(CharSequence seq) { tikhomirov@543: int lineCount = 0; tikhomirov@543: for (int i = 0, x = seq.length(); i < x; i++) { tikhomirov@543: if (seq.charAt(i) == '\n') { tikhomirov@543: lineCount++; tikhomirov@542: } tikhomirov@543: } tikhomirov@543: if (seq.length() > 0 && seq.charAt(seq.length()-1) != '\n') { tikhomirov@543: lineCount++; tikhomirov@543: } tikhomirov@543: String[] rv = new String[lineCount]; tikhomirov@543: int lineStart = 0, lineEnd = 0, ix = 0; tikhomirov@543: do { tikhomirov@543: while (lineEnd < seq.length() && seq.charAt(lineEnd) != '\n') lineEnd++; tikhomirov@543: if (lineEnd == lineStart) { tikhomirov@543: continue; tikhomirov@542: } tikhomirov@543: CharSequence line = seq.subSequence(lineStart, lineEnd); tikhomirov@543: rv[ix++] = line.toString(); tikhomirov@543: lineStart = ++lineEnd; tikhomirov@543: } while (lineStart < seq.length()); tikhomirov@543: assert ix == lineCount; tikhomirov@543: return rv; tikhomirov@543: } tikhomirov@543: tikhomirov@549: tikhomirov@561: private void ccc() throws Throwable { tikhomirov@562: HgRepository repo = new HgLookup().detect("/home/artem/hg/hgtest-annotate-merge/"); tikhomirov@562: HgDataFile df = repo.getFileNode("file.txt"); tikhomirov@561: DiffOutInspector dump = new DiffOutInspector(System.out); tikhomirov@561: dump.needRevisions(true); tikhomirov@630: HgDiffCommand diffCmd = new HgDiffCommand(repo); tikhomirov@630: diffCmd.file(df); tikhomirov@630: diffCmd.range(0, 8).order(NewToOld); tikhomirov@630: diffCmd.executeAnnotate(dump); tikhomirov@561: // af.annotateSingleRevision(df, 113, dump); tikhomirov@557: // System.out.println(); tikhomirov@557: // af.annotate(df, TIP, new LineDumpInspector(true), HgIterateDirection.NewToOld); tikhomirov@557: // System.out.println(); tikhomirov@557: // af.annotate(df, TIP, new LineDumpInspector(false), HgIterateDirection.NewToOld); tikhomirov@557: // System.out.println(); tikhomirov@562: /* tikhomirov@561: OutputParser.Stub op = new OutputParser.Stub(); tikhomirov@561: eh = new ExecHelper(op, repo.getWorkingDir()); tikhomirov@561: for (int cs : new int[] { 24, 46, 49, 52, 59, 62, 64, TIP}) { tikhomirov@561: doLineAnnotateTest(df, cs, op); tikhomirov@561: } tikhomirov@561: errorCollector.verify(); tikhomirov@562: */ tikhomirov@676: ForwardAnnotateInspector insp = new ForwardAnnotateInspector(); tikhomirov@676: diffCmd.range(0, 8).order(insp.iterateDirection()); tikhomirov@676: diffCmd.executeAnnotate(insp); tikhomirov@676: AnnotateInspector fa = new AnnotateInspector().fill(8, insp); tikhomirov@676: for (int i = 0; i < fa.changesets.size(); i++) { tikhomirov@676: final String line = fa.lines.get(i); tikhomirov@676: System.out.printf("%d: %s", fa.changesets.get(i), line == null ? "null\n" : line); tikhomirov@542: } tikhomirov@542: } tikhomirov@553: tikhomirov@561: public static void main(String[] args) throws Throwable { tikhomirov@570: TestBlame tt = new TestBlame(); tikhomirov@570: tt.ccc(); tikhomirov@542: } tikhomirov@543: tikhomirov@603: private static class DiffOutInspector implements HgBlameInspector { tikhomirov@543: private final PrintStream out; tikhomirov@549: private boolean dumpRevs; tikhomirov@553: private IntVector reportedRevisionPairs = new IntVector(); tikhomirov@543: tikhomirov@543: DiffOutInspector(PrintStream ps) { tikhomirov@543: out = ps; tikhomirov@543: } tikhomirov@543: tikhomirov@554: // Note, true makes output incompatible with 'hg diff' tikhomirov@549: public void needRevisions(boolean dumpRevs) { tikhomirov@549: this.dumpRevs = dumpRevs; tikhomirov@549: } tikhomirov@549: tikhomirov@549: private void printRevs(Block b) { tikhomirov@549: if (dumpRevs) { tikhomirov@549: out.printf("[%3d -> %3d] ", b.originChangesetIndex(), b.targetChangesetIndex()); tikhomirov@549: } tikhomirov@553: reportedRevisionPairs.add(b.originChangesetIndex(), b.targetChangesetIndex()); tikhomirov@553: } tikhomirov@553: tikhomirov@553: int[] getReportedTargetRevisions() { tikhomirov@553: LinkedHashSet rv = new LinkedHashSet(); tikhomirov@553: for (int i = 1; i < reportedRevisionPairs.size(); i += 2) { tikhomirov@553: rv.add(reportedRevisionPairs.get(i)); tikhomirov@553: } tikhomirov@553: int[] x = new int[rv.size()]; tikhomirov@553: int i = 0; tikhomirov@553: for (int v : rv) { tikhomirov@553: x[i++] = v; tikhomirov@553: } tikhomirov@553: return x; tikhomirov@549: } tikhomirov@549: tikhomirov@573: int[] getReportedRevisionPairs() { tikhomirov@573: return reportedRevisionPairs.toArray(); tikhomirov@573: } tikhomirov@573: tikhomirov@573: void reset() { tikhomirov@573: reportedRevisionPairs.clear(); tikhomirov@573: } tikhomirov@573: tikhomirov@545: public void same(EqualBlock block) { tikhomirov@543: // nothing tikhomirov@543: } tikhomirov@543: tikhomirov@543: public void deleted(DeleteBlock block) { tikhomirov@549: printRevs(block); tikhomirov@543: out.printf("@@ -%d,%d +%d,0 @@\n", block.firstRemovedLine() + 1, block.totalRemovedLines(), block.removedAt()); tikhomirov@543: } tikhomirov@543: tikhomirov@543: public void changed(ChangeBlock block) { tikhomirov@549: printRevs(block); tikhomirov@543: out.printf("@@ -%d,%d +%d,%d @@\n", block.firstRemovedLine() + 1, block.totalRemovedLines(), block.firstAddedLine() + 1, block.totalAddedLines()); tikhomirov@543: } tikhomirov@543: tikhomirov@543: public void added(AddBlock block) { tikhomirov@549: printRevs(block); tikhomirov@543: out.printf("@@ -%d,0 +%d,%d @@\n", block.insertedAt(), block.firstAddedLine() + 1, block.totalAddedLines()); tikhomirov@543: } tikhomirov@543: } tikhomirov@543: tikhomirov@543: public static class LineGrepOutputParser implements OutputParser { tikhomirov@543: tikhomirov@543: private final Pattern pattern; tikhomirov@543: private final StringBuilder result = new StringBuilder(); tikhomirov@543: tikhomirov@543: public LineGrepOutputParser(String regexp) { tikhomirov@543: pattern = Pattern.compile(regexp); tikhomirov@543: } tikhomirov@543: tikhomirov@553: public void reset() { tikhomirov@553: result.setLength(0); tikhomirov@553: } tikhomirov@553: tikhomirov@543: public CharSequence result() { tikhomirov@543: return result; tikhomirov@543: } tikhomirov@543: tikhomirov@543: public void parse(CharSequence seq) { tikhomirov@543: int lineStart = 0, lineEnd = 0; tikhomirov@543: do { tikhomirov@543: while (lineEnd < seq.length() && seq.charAt(lineEnd) != '\n') lineEnd++; tikhomirov@543: if (lineEnd == lineStart) { tikhomirov@543: continue; tikhomirov@543: } tikhomirov@543: CharSequence line = seq.subSequence(lineStart, lineEnd); tikhomirov@543: if (pattern.matcher(line).matches()) { tikhomirov@543: result.append(line); tikhomirov@543: result.append('\n'); tikhomirov@543: } tikhomirov@543: lineStart = ++lineEnd; tikhomirov@543: } while (lineStart < seq.length()); tikhomirov@543: } tikhomirov@543: } tikhomirov@545: tikhomirov@603: @SuppressWarnings("unused") tikhomirov@603: private static class LineDumpInspector implements HgBlameInspector { tikhomirov@554: tikhomirov@554: private final boolean lineByLine; tikhomirov@554: tikhomirov@554: public LineDumpInspector(boolean lineByLine) { tikhomirov@554: this.lineByLine = lineByLine; tikhomirov@554: } tikhomirov@554: tikhomirov@554: public void same(EqualBlock block) { tikhomirov@554: } tikhomirov@554: tikhomirov@554: public void added(AddBlock block) { tikhomirov@554: BlockData lines = block.addedLines(); tikhomirov@554: printBlock(lines, block.targetChangesetIndex(), block.firstAddedLine(), block.totalAddedLines(), "+++"); tikhomirov@554: } tikhomirov@554: tikhomirov@554: public void changed(ChangeBlock block) { tikhomirov@554: deleted(block); tikhomirov@554: added(block); tikhomirov@554: } tikhomirov@554: tikhomirov@554: public void deleted(DeleteBlock block) { tikhomirov@554: BlockData lines = block.removedLines(); tikhomirov@554: assert lines.elementCount() == block.totalRemovedLines(); tikhomirov@554: printBlock(lines, block.originChangesetIndex(), block.firstRemovedLine(), block.totalRemovedLines(), "---"); tikhomirov@554: } tikhomirov@554: tikhomirov@554: private void printBlock(BlockData lines, int cset, int first, int length, String marker) { tikhomirov@554: assert lines.elementCount() == length; tikhomirov@554: if (lineByLine) { tikhomirov@554: for (int i = 0, ln = first; i < length; i++, ln++) { tikhomirov@554: String line = new String(lines.elementAt(i).asArray()); tikhomirov@554: System.out.printf("%3d:%3d:%s:%s", cset, ln, marker, line); tikhomirov@554: } tikhomirov@554: } else { tikhomirov@554: String content = new String(lines.asArray()); tikhomirov@554: System.out.printf("%3d:%s:[%d..%d):\n%s", cset, marker, first, first+length, content); tikhomirov@554: } tikhomirov@554: } tikhomirov@554: } tikhomirov@570: tikhomirov@570: static class AnnotateInspector implements HgAnnotateCommand.Inspector { tikhomirov@570: private int lineNumber = 1; tikhomirov@570: public final ArrayList lines = new ArrayList(); tikhomirov@570: public final ArrayList changesets = new ArrayList(); tikhomirov@676: tikhomirov@676: AnnotateInspector fill(int rev, ReverseAnnotateInspector ai) throws HgCallbackTargetException, CancelledException { tikhomirov@676: ai.report(rev, this, ProgressSupport.Factory.get(null), CancelSupport.Factory.get(null)); tikhomirov@676: return this; tikhomirov@676: } tikhomirov@676: AnnotateInspector fill(int rev, ForwardAnnotateInspector ai) throws HgCallbackTargetException, CancelledException { tikhomirov@676: ai.report(rev, this, ProgressSupport.Factory.get(null), CancelSupport.Factory.get(null)); tikhomirov@676: return this; tikhomirov@676: } tikhomirov@676: tikhomirov@570: public void next(LineInfo lineInfo) throws HgCallbackTargetException { tikhomirov@570: Assert.assertEquals(lineInfo.getLineNumber(), lineNumber); tikhomirov@570: lineNumber++; tikhomirov@570: lines.add(new String(lineInfo.getContent())); tikhomirov@570: changesets.add(lineInfo.getChangesetIndex()); tikhomirov@570: } tikhomirov@570: } tikhomirov@570: tikhomirov@570: private static class AnnotateRunner { tikhomirov@570: private final ExecHelper eh; tikhomirov@570: private final OutputParser.Stub op; tikhomirov@570: private final Path file; tikhomirov@570: tikhomirov@570: public AnnotateRunner(Path filePath, File repoDir) { tikhomirov@570: file = filePath; tikhomirov@570: op = new OutputParser.Stub(); tikhomirov@570: eh = new ExecHelper(op, repoDir); tikhomirov@570: } tikhomirov@570: tikhomirov@570: public void run(int cset, boolean follow) throws Exception { tikhomirov@570: op.reset(); tikhomirov@570: ArrayList args = new ArrayList(); tikhomirov@570: args.add("hg"); tikhomirov@570: args.add("annotate"); tikhomirov@570: args.add("-r"); tikhomirov@570: args.add(cset == TIP ? "tip" : String.valueOf(cset)); tikhomirov@570: if (!follow) { tikhomirov@570: args.add("--no-follow"); tikhomirov@570: } tikhomirov@570: args.add(file.toString()); tikhomirov@570: eh.run(args); tikhomirov@570: } tikhomirov@570: tikhomirov@570: public String[] getLines() { tikhomirov@570: return splitLines(op.result()); tikhomirov@570: } tikhomirov@570: } tikhomirov@542: }