是否有支持Annotate / Blame的java的Diff库?

时间:2010-12-14 19:56:04

标签: java diff annotate blame

我正在挖掘Google的免费(开源)Java差异库的结果,并且似乎有很多(其中一些甚至使用通用对象而不仅仅是字符串)。

在我挖掘大量搜索结果而没有找到我正在搜索的内容之前,我先问一下:

这些差异库是否支持cvs annotate或svn blame等功能。我想

  • 将当前String[]传递给函数
  • 继续将String[]的旧版本传递给一个函数,直到我用完所有版本,或者库告诉我没有原始行没有注释(最后一件事不是必须但是非常有用,因为检索旧版String[]是昂贵的,所以我想尽早停止)
  • 调用一个函数,它给我一个ìnt[],告诉我当前版本的每一行,它最后一次更改的版本或它是否完全没有更改(即最后一个版本中的最后一次更改) )。

支持非String s的对象很不错,但没有必要。如果API不完全那样,我想我可以忍受它。

如果没有,任何人都可以建议一个可扩展的差异库,其中可以轻松添加该功能,最好是希望将该功能作为贡献添加(并且在接受贡献之前不需要填充大量文书工作) ,像GNU项目)?那时我会自愿(至少尝试)将它添加到那里。

3 个答案:

答案 0 :(得分:2)

我决定自己为Dmitry Naumenko的java-diff-utils图书馆实施它:

/*
   Copyright 2010 Michael Schierl (schierlm@gmx.de)

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package difflib.annotate;

import java.util.*;

import difflib.*;

/**
 * Generates an annotated version of a revision based on a list of older
 * revisions, like <tt>cvs annotate</tt> or <tt>svn blame</tt>.
 * 
 * @author <a href="schierlm@gmx.de">Michael Schierl</a>
 * 
 * @param <R>
 *            Type of the revision metadata
 */
public class Annotate<R> {

    private final List<R> revisions;
    private final int[] lineNumbers;
    private R currentRevision;
    private final List<Object> currentLines;
    private final List<Integer> currentLineMap;

    /**
     * Creates a new annotation generator.
     * 
     * @param revision
     *            Revision metadata for the revision to be annotated
     * @param targetLines
     *            Lines of the revision to be annotated
     */
    public Annotate(R revision, List<?> targetLines) {
        revisions = new ArrayList<R>();
        lineNumbers = new int[targetLines.size()];
        currentRevision = revision;
        currentLines = new ArrayList<Object>(targetLines);
        currentLineMap = new ArrayList<Integer>();
        for (int i = 0; i < lineNumbers.length; i++) {
            lineNumbers[i] = -1;
            revisions.add(null);
            currentLineMap.add(i);
        }
    }

    /**
     * Check whether there are still lines that are unannotated. In that case,
     * more older revisions should be retrieved and passed to the function. Note
     * that as soon as you pass an empty revision, all lines will be annotated
     * (with a later revision), therefore if you do not have any more revisions,
     * pass an empty revision to annotate the rest of the lines.
     */
    public boolean areLinesUnannotated() {
        for (int i = 0; i < lineNumbers.length; i++) {
            if (lineNumbers[i] == -1 || revisions.get(i) == null)
                return true;
        }
        return false;
    }

    /**
     * Add the previous revision and update annotation info.
     * 
     * @param revision
     *            Revision metadata for this revision
     * @param lines
     *            Lines of this revision
     */
    public void addRevision(R revision, List<?> lines) {
        Patch patch = DiffUtils.diff(currentLines, lines);
        int lineOffset = 0; // remember number of already deleted/added lines
        for (Delta d : patch.getDeltas()) {
            Chunk original = d.getOriginal();
            Chunk revised = d.getRevised();
            int pos = original.getPosition() + lineOffset;
            // delete lines
            for (int i = 0; i < original.getSize(); i++) {
                int origLine = currentLineMap.remove(pos);
                currentLines.remove(pos);
                if (origLine != -1) {
                    lineNumbers[origLine] = original.getPosition() + i;
                    revisions.set(origLine, currentRevision);
                }
            }
            for (int i = 0; i < revised.getSize(); i++) {
                currentLines.add(pos + i, revised.getLines().get(i));
                currentLineMap.add(pos + i, -1);
            }
            lineOffset += revised.getSize() - original.getSize();
        }

        currentRevision = revision;
        if (!currentLines.equals(lines))
            throw new RuntimeException("Patch application failed");
    }

    /**
     * Return the result of the annotation. It will be a List of the same length
     * as the target revision, for which every entry states the revision where
     * the line appeared last.
     */
    public List<R> getAnnotatedRevisions() {
        return Collections.unmodifiableList(revisions);
    }

    /**
     * Return the result of the annotation. It will be a List of the same length
     * as the target revision, for which every entry states the line number in
     * the revision where the line appeared last.
     */
    public int[] getAnnotatedLineNumbers() {
        return (int[]) lineNumbers.clone();
    }
}

我还将它发送给Dmitry Naumenko(带有一些测试用例),以防他想加入它。

答案 1 :(得分:1)

我可能错了,但我认为annotate / blame需要一个版本控制系统才能工作,因为它需要访问文件的历史记录。通用的diff-library不能这样做。 因此,如果这是您的目标,请查看与这些VCS一起使用的库,例如svnkit。 如果没有,这样的库可能是关于注释/指责如何完成的一个很好的起点,通常这涉及区分文件的所有版本的链。

答案 2 :(得分:1)

你可以使用 xwiki-commons-blame-api。 它实际上使用code from this thread's accepted answerThanks to Michael Schierl for sharing this code on StackOverflow

您可以在it's unit tests.

中了解如何在Java中使用它

或者像Scala一样:

import java.util
import org.xwiki.blame.AnnotatedContent
import org.xwiki.blame.internal.DefaultBlameManager

case class Revision(id: Int,
                    parentId: Option[Int] = None,
                    content: Option[String] = None)

def createAnnotation(revisions: Seq[Revision]): Option[AnnotatedContent[Revision, String]] = {
    val blameManager = new DefaultBlameManager()

    val annotatedContent = revisions.foldLeft(null.asInstanceOf[AnnotatedContent[Revision, String]]){
      (annotation, revision) =>
        blameManager.blame(annotation, revision, splitByWords(revision.content))
    }
    Option(annotatedContent)
}

def splitByWords(content: Option[String]): util.List[String] = {
    val array = content.fold(Array[String]())(_.split("[^\\pL_\\pN]+"))
    util.Arrays.asList(array:_*)
}