I'm digging through Google's results for free (open source) Java diff libaries, and there seem quite a bunch of those (some of them even working with generic Objects and not only with Strings).
Before I'm digging through tons of search results and not finding what I'm searching, I'll ask here first:
Does any of those diff libraries support a feature like cvs annotate or svn blame. I want to
String[]
to a functionString[]
to a function, until either I have used up all of them, or the library tells me that no original line was left unannotated (the last thing is not really a must but very useful since retrieving older versions of the String[]
is expensive so I'd like to stop as early as possible)ìnt[]
that tells me for every line of the current version, in which version it was last changed or whether it was not changed at all (i. e. last changed in the very first version).Having support for objects that are not String
s is nice, but no must. And if the API is not exactly that way, I guess I could live with it.
If there is none, can anybody suggest an extensible diff library where that feature can be added easily, preferrably one that would like to receive that feature as a contribution (and does not require tons of paperwork to be filled before they accept contributions, like the GNU project)? I'd volunteer to (at least try to) add it there, then.
I decided to implement it myself for Dmitry Naumenko's java-diff-utils library:
/*
Copyright 2010 Michael Schierl ([email protected])
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="[email protected]">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();
}
}
I also sent it to Dmitry Naumenko (with a few test cases) in case he wants to include it.
I might be wrong but I think annotate/blame needs a version control system to work, since it needs to access the history of the file. A generic diff-library can't do that. So if this is your target check out the libraries that work with those VCS, like svnkit. If not, such a library may be a good starting point on how annotate/blame is done, very often this involves diff'ing the chain of all versions of a file.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With