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@545: import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; tikhomirov@548: import static org.tmatesoft.hg.repo.HgRepository.TIP; tikhomirov@545: tikhomirov@543: import java.io.ByteArrayOutputStream; tikhomirov@543: import java.io.PrintStream; tikhomirov@543: import java.util.Arrays; 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@549: import org.tmatesoft.hg.console.Bundle.Dump; tikhomirov@542: import org.tmatesoft.hg.internal.AnnotateFacility; tikhomirov@542: import org.tmatesoft.hg.internal.AnnotateFacility.AddBlock; tikhomirov@549: import org.tmatesoft.hg.internal.AnnotateFacility.Block; tikhomirov@542: import org.tmatesoft.hg.internal.AnnotateFacility.ChangeBlock; tikhomirov@542: import org.tmatesoft.hg.internal.AnnotateFacility.DeleteBlock; tikhomirov@545: import org.tmatesoft.hg.internal.AnnotateFacility.EqualBlock; tikhomirov@546: import org.tmatesoft.hg.internal.AnnotateFacility.LineDescriptor; tikhomirov@542: import org.tmatesoft.hg.internal.IntMap; tikhomirov@542: import org.tmatesoft.hg.repo.HgDataFile; tikhomirov@542: import org.tmatesoft.hg.repo.HgLookup; tikhomirov@542: import org.tmatesoft.hg.repo.HgRepository; 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@543: final int checkChangeset = 539; tikhomirov@543: HgDataFile df = repo.getFileNode(fname); tikhomirov@543: ByteArrayOutputStream bos = new ByteArrayOutputStream(); tikhomirov@544: new AnnotateFacility().annotateChange(df, checkChangeset, 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@545: public void testFileAnnotate() 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@546: OutputParser.Stub op = new OutputParser.Stub(); tikhomirov@546: ExecHelper eh = new ExecHelper(op, null); tikhomirov@546: tikhomirov@549: for (int startChangeset : new int[] { TIP, /*539, 541/ *, TIP */}) { tikhomirov@548: FileAnnotateInspector fa = new FileAnnotateInspector(); tikhomirov@548: new AnnotateFacility().annotate(df, startChangeset, fa); tikhomirov@548: tikhomirov@548: tikhomirov@548: op.reset(); tikhomirov@548: eh.run("hg", "annotate", "-r", startChangeset == TIP ? "tip" : String.valueOf(startChangeset), fname); tikhomirov@548: tikhomirov@548: String[] hgAnnotateLines = splitLines(op.result()); tikhomirov@548: assertTrue("[sanity]", hgAnnotateLines.length > 0); tikhomirov@548: assertEquals("Number of lines reported by native annotate and our impl", hgAnnotateLines.length, fa.lineRevisions.length); tikhomirov@548: tikhomirov@548: for (int i = 0; i < fa.lineRevisions.length; i++) { tikhomirov@548: int hgAnnotateRevIndex = Integer.parseInt(hgAnnotateLines[i].substring(0, hgAnnotateLines[i].indexOf(':'))); tikhomirov@549: errorCollector.assertEquals(String.format("Revision mismatch for line %d", i+1), hgAnnotateRevIndex, fa.lineRevisions[i]); tikhomirov@548: } tikhomirov@545: } tikhomirov@545: } tikhomirov@545: 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@549: private void aaa() throws Exception { tikhomirov@546: HgRepository repo = new HgLookup().detectFromWorkingDir(); tikhomirov@546: final String fname = "src/org/tmatesoft/hg/internal/PatchGenerator.java"; tikhomirov@546: final int checkChangeset = 539; tikhomirov@546: HgDataFile df = repo.getFileNode(fname); tikhomirov@546: AnnotateFacility af = new AnnotateFacility(); tikhomirov@549: DiffOutInspector dump = new DiffOutInspector(System.out); tikhomirov@549: System.out.println("541 -> 543"); tikhomirov@549: af.annotateChange(df, 543, dump); tikhomirov@549: System.out.println("539 -> 541"); tikhomirov@549: af.annotateChange(df, 541, dump); tikhomirov@546: System.out.println("536 -> 539"); tikhomirov@549: af.annotateChange(df, checkChangeset, dump); tikhomirov@546: System.out.println("531 -> 536"); tikhomirov@549: af.annotateChange(df, 536, dump); tikhomirov@546: System.out.println(" -1 -> 531"); tikhomirov@549: af.annotateChange(df, 531, dump); tikhomirov@549: tikhomirov@549: FileAnnotateInspector fai = new FileAnnotateInspector(); tikhomirov@549: af.annotate(df, TIP, fai); tikhomirov@549: for (int i = 0; i < fai.lineRevisions.length; i++) { tikhomirov@549: System.out.printf("%3d: LINE %d\n", fai.lineRevisions[i], i+1); tikhomirov@549: } tikhomirov@549: } tikhomirov@546: tikhomirov@549: private void bbb() throws Exception { tikhomirov@549: HgRepository repo = new HgLookup().detectFromWorkingDir(); tikhomirov@549: final String fname = "src/org/tmatesoft/hg/repo/HgManifest.java"; tikhomirov@549: final int checkChangeset = 415; tikhomirov@549: HgDataFile df = repo.getFileNode(fname); tikhomirov@549: AnnotateFacility af = new AnnotateFacility(); tikhomirov@549: DiffOutInspector dump = new DiffOutInspector(System.out); tikhomirov@549: System.out.println("413 -> 415"); tikhomirov@549: af.diff(df, 413, 415, dump); tikhomirov@549: System.out.println("408 -> 415"); tikhomirov@549: af.diff(df, 408, 415, dump); tikhomirov@549: System.out.println("Combined (with merge):"); tikhomirov@549: dump.needRevisions(true); tikhomirov@549: af.annotateChange(df, checkChangeset, dump); tikhomirov@549: } tikhomirov@549: tikhomirov@549: private void leftovers() throws Exception { tikhomirov@543: IntMap linesOld = new IntMap(100), linesNew = new IntMap(100); tikhomirov@542: System.out.println("Changes to old revision:"); tikhomirov@542: for (int i = linesOld.firstKey(), x = linesOld.lastKey(); i < x; i++) { tikhomirov@542: if (linesOld.containsKey(i)) { tikhomirov@542: System.out.println(linesOld.get(i)); tikhomirov@542: } tikhomirov@542: } tikhomirov@542: tikhomirov@542: System.out.println("Changes in the new revision:"); tikhomirov@542: for (int i = linesNew.firstKey(), x = linesNew.lastKey(); i < x; i++) { tikhomirov@542: if (linesNew.containsKey(i)) { tikhomirov@542: System.out.println(linesNew.get(i)); tikhomirov@542: } tikhomirov@542: } tikhomirov@542: } tikhomirov@542: tikhomirov@542: public static void main(String[] args) throws Exception { tikhomirov@545: // System.out.println(Arrays.equals(new String[0], splitLines(""))); tikhomirov@545: // System.out.println(Arrays.equals(new String[] { "abc" }, splitLines("abc"))); tikhomirov@545: // System.out.println(Arrays.equals(new String[] { "a", "bc" }, splitLines("a\nbc"))); tikhomirov@545: // System.out.println(Arrays.equals(new String[] { "a", "bc" }, splitLines("a\nbc\n"))); tikhomirov@549: new TestBlame().bbb(); tikhomirov@542: } tikhomirov@543: tikhomirov@546: static class DiffOutInspector implements AnnotateFacility.BlockInspector { tikhomirov@543: private final PrintStream out; tikhomirov@549: private boolean dumpRevs; tikhomirov@543: tikhomirov@543: DiffOutInspector(PrintStream ps) { tikhomirov@543: out = ps; tikhomirov@543: } tikhomirov@543: tikhomirov@549: public void needRevisions(boolean dumpRevs) { tikhomirov@549: // Note, true makes output incompatible with 'hg diff' 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@549: } tikhomirov@549: 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: // String[] lines = block.removedLines(); tikhomirov@543: // assert lines.length == block.totalRemovedLines(); tikhomirov@543: // for (int i = 0, ln = block.firstRemovedLine(); i < lines.length; i++, ln++) { tikhomirov@543: // linesOld.put(ln, String.format("%3d:---:%s", ln, lines[i])); tikhomirov@543: // } tikhomirov@543: } tikhomirov@543: tikhomirov@543: public void changed(ChangeBlock block) { tikhomirov@543: // deleted(block); tikhomirov@543: // added(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: // String[] addedLines = block.addedLines(); tikhomirov@543: // assert addedLines.length == block.totalAddedLines(); tikhomirov@543: // for (int i = 0, ln = block.firstAddedLine(), x = addedLines.length; i < x; i++, ln++) { tikhomirov@543: // linesNew.put(ln, String.format("%3d:+++:%s", ln, addedLines[i])); tikhomirov@543: // } 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@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@546: private static class FileAnnotateInspector implements AnnotateFacility.LineInspector { tikhomirov@545: private int[] lineRevisions; tikhomirov@545: tikhomirov@546: FileAnnotateInspector() { tikhomirov@545: } tikhomirov@545: tikhomirov@546: public void line(int lineNumber, int changesetRevIndex, LineDescriptor ld) { tikhomirov@545: if (lineRevisions == null) { tikhomirov@546: lineRevisions = new int [ld.totalLines()]; tikhomirov@545: Arrays.fill(lineRevisions, NO_REVISION); tikhomirov@545: } tikhomirov@546: lineRevisions[lineNumber] = changesetRevIndex; tikhomirov@545: } tikhomirov@545: } tikhomirov@546: tikhomirov@542: }