| /* |
| * Copyright (C) 2011, Google Inc. |
| * and other copyright owners as documented in the project's IP log. |
| * |
| * This program and the accompanying materials are made available |
| * under the terms of the Eclipse Distribution License v1.0 which |
| * accompanies this distribution, is reproduced below, and is |
| * available at http://www.eclipse.org/org/documents/edl-v10.php |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * |
| * - Neither the name of the Eclipse Foundation, Inc. nor the |
| * names of its contributors may be used to endorse or promote |
| * products derived from this software without specific prior |
| * written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.eclipse.jgit.blame; |
| |
| import java.io.IOException; |
| |
| import org.eclipse.jgit.diff.RawText; |
| import org.eclipse.jgit.lib.PersonIdent; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| |
| /** |
| * Collects line annotations for inspection by applications. |
| * <p> |
| * A result is usually updated incrementally as the BlameGenerator digs back |
| * further through history. Applications that want to lay annotations down text |
| * to the original source file in a viewer may find the BlameResult structure an |
| * easy way to acquire the information, at the expense of keeping tables in |
| * memory tracking every line of the result file. |
| * <p> |
| * This class is not thread-safe. |
| * <p> |
| * During blame processing there are two files involved: |
| * <ul> |
| * <li>result - The file whose lines are being examined. This is the revision |
| * the user is trying to view blame/annotation information alongside of.</li> |
| * <li>source - The file that was blamed with supplying one or more lines of |
| * data into result. The source may be a different file path (due to copy or |
| * rename). Source line numbers may differ from result line numbers due to lines |
| * being added/removed in intermediate revisions.</li> |
| * </ul> |
| */ |
| public class BlameResult { |
| /** |
| * Construct a new BlameResult for a generator. |
| * |
| * @param gen |
| * the generator the result will consume records from. |
| * @return the new result object. null if the generator cannot find the path |
| * it starts from. |
| * @throws java.io.IOException |
| * the repository cannot be read. |
| */ |
| public static BlameResult create(BlameGenerator gen) throws IOException { |
| String path = gen.getResultPath(); |
| RawText contents = gen.getResultContents(); |
| if (contents == null) { |
| gen.close(); |
| return null; |
| } |
| return new BlameResult(gen, path, contents); |
| } |
| |
| private final String resultPath; |
| |
| private final RevCommit[] sourceCommits; |
| |
| private final PersonIdent[] sourceAuthors; |
| |
| private final PersonIdent[] sourceCommitters; |
| |
| private final String[] sourcePaths; |
| |
| /** Warning: these are actually 1-based. */ |
| private final int[] sourceLines; |
| |
| private RawText resultContents; |
| |
| private BlameGenerator generator; |
| |
| private int lastLength; |
| |
| BlameResult(BlameGenerator bg, String path, RawText text) { |
| generator = bg; |
| resultPath = path; |
| resultContents = text; |
| |
| int cnt = text.size(); |
| sourceCommits = new RevCommit[cnt]; |
| sourceAuthors = new PersonIdent[cnt]; |
| sourceCommitters = new PersonIdent[cnt]; |
| sourceLines = new int[cnt]; |
| sourcePaths = new String[cnt]; |
| } |
| |
| /** |
| * Get result path |
| * |
| * @return path of the file this result annotates |
| */ |
| public String getResultPath() { |
| return resultPath; |
| } |
| |
| /** |
| * Get result contents |
| * |
| * @return contents of the result file, available for display |
| */ |
| public RawText getResultContents() { |
| return resultContents; |
| } |
| |
| /** |
| * Throw away the {@link #getResultContents()}. |
| */ |
| public void discardResultContents() { |
| resultContents = null; |
| } |
| |
| /** |
| * Check if the given result line has been annotated yet. |
| * |
| * @param idx |
| * line to read data of, 0 based. |
| * @return true if the data has been annotated, false otherwise. |
| */ |
| public boolean hasSourceData(int idx) { |
| return sourceLines[idx] != 0; |
| } |
| |
| /** |
| * Check if the given result line has been annotated yet. |
| * |
| * @param start |
| * first index to examine. |
| * @param end |
| * last index to examine. |
| * @return true if the data has been annotated, false otherwise. |
| */ |
| public boolean hasSourceData(int start, int end) { |
| for (; start < end; start++) |
| if (sourceLines[start] == 0) |
| return false; |
| return true; |
| } |
| |
| /** |
| * Get the commit that provided the specified line of the result. |
| * <p> |
| * The source commit may be null if the line was blamed to an uncommitted |
| * revision, such as the working tree copy, or during a reverse blame if the |
| * line survives to the end revision (e.g. the branch tip). |
| * |
| * @param idx |
| * line to read data of, 0 based. |
| * @return commit that provided line {@code idx}. May be null. |
| */ |
| public RevCommit getSourceCommit(int idx) { |
| return sourceCommits[idx]; |
| } |
| |
| /** |
| * Get the author that provided the specified line of the result. |
| * |
| * @param idx |
| * line to read data of, 0 based. |
| * @return author that provided line {@code idx}. May be null. |
| */ |
| public PersonIdent getSourceAuthor(int idx) { |
| return sourceAuthors[idx]; |
| } |
| |
| /** |
| * Get the committer that provided the specified line of the result. |
| * |
| * @param idx |
| * line to read data of, 0 based. |
| * @return committer that provided line {@code idx}. May be null. |
| */ |
| public PersonIdent getSourceCommitter(int idx) { |
| return sourceCommitters[idx]; |
| } |
| |
| /** |
| * Get the file path that provided the specified line of the result. |
| * |
| * @param idx |
| * line to read data of, 0 based. |
| * @return source file path that provided line {@code idx}. |
| */ |
| public String getSourcePath(int idx) { |
| return sourcePaths[idx]; |
| } |
| |
| /** |
| * Get the corresponding line number in the source file. |
| * |
| * @param idx |
| * line to read data of, 0 based. |
| * @return matching line number in the source file. |
| */ |
| public int getSourceLine(int idx) { |
| return sourceLines[idx] - 1; |
| } |
| |
| /** |
| * Compute all pending information. |
| * |
| * @throws java.io.IOException |
| * the repository cannot be read. |
| */ |
| public void computeAll() throws IOException { |
| BlameGenerator gen = generator; |
| if (gen == null) |
| return; |
| |
| try { |
| while (gen.next()) |
| loadFrom(gen); |
| } finally { |
| gen.close(); |
| generator = null; |
| } |
| } |
| |
| /** |
| * Compute the next available segment and return the first index. |
| * <p> |
| * Computes one segment and returns to the caller the first index that is |
| * available. After return the caller can also inspect {@link #lastLength()} |
| * to determine how many lines of the result were computed. |
| * |
| * @return index that is now available. -1 if no more are available. |
| * @throws java.io.IOException |
| * the repository cannot be read. |
| */ |
| public int computeNext() throws IOException { |
| BlameGenerator gen = generator; |
| if (gen == null) |
| return -1; |
| |
| if (gen.next()) { |
| loadFrom(gen); |
| lastLength = gen.getRegionLength(); |
| return gen.getResultStart(); |
| } else { |
| gen.close(); |
| generator = null; |
| return -1; |
| } |
| } |
| |
| /** |
| * Get last length |
| * |
| * @return length of the last segment found by {@link #computeNext()} |
| */ |
| public int lastLength() { |
| return lastLength; |
| } |
| |
| /** |
| * Compute until the entire range has been populated. |
| * |
| * @param start |
| * first index to examine (inclusive). |
| * @param end |
| * end index (exclusive). |
| * @throws java.io.IOException |
| * the repository cannot be read. |
| */ |
| public void computeRange(int start, int end) throws IOException { |
| BlameGenerator gen = generator; |
| if (gen == null) |
| return; |
| if (start == 0 && end == resultContents.size()) { |
| computeAll(); |
| return; |
| } |
| |
| while (start < end) { |
| if (hasSourceData(start, end)) |
| return; |
| |
| if (!gen.next()) { |
| gen.close(); |
| generator = null; |
| return; |
| } |
| |
| loadFrom(gen); |
| |
| // If the result contains either end of our current range bounds, |
| // update the bounds to avoid scanning that section during the |
| // next loop iteration. |
| |
| int resLine = gen.getResultStart(); |
| int resEnd = gen.getResultEnd(); |
| |
| if (resLine <= start && start < resEnd) |
| start = resEnd; |
| |
| if (resLine <= end && end < resEnd) |
| end = resLine; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public String toString() { |
| StringBuilder r = new StringBuilder(); |
| r.append("BlameResult: "); //$NON-NLS-1$ |
| r.append(getResultPath()); |
| return r.toString(); |
| } |
| |
| private void loadFrom(BlameGenerator gen) { |
| RevCommit srcCommit = gen.getSourceCommit(); |
| PersonIdent srcAuthor = gen.getSourceAuthor(); |
| PersonIdent srcCommitter = gen.getSourceCommitter(); |
| String srcPath = gen.getSourcePath(); |
| int srcLine = gen.getSourceStart(); |
| int resLine = gen.getResultStart(); |
| int resEnd = gen.getResultEnd(); |
| |
| for (; resLine < resEnd; resLine++) { |
| // Reverse blame can generate multiple results for the same line. |
| // Favor the first one selected, as this is the oldest and most |
| // likely to be nearest to the inquiry made by the user. |
| if (sourceLines[resLine] != 0) |
| continue; |
| |
| sourceCommits[resLine] = srcCommit; |
| sourceAuthors[resLine] = srcAuthor; |
| sourceCommitters[resLine] = srcCommitter; |
| sourcePaths[resLine] = srcPath; |
| |
| // Since sourceLines is 1-based to permit hasSourceData to use 0 to |
| // mean the line has not been annotated yet, pre-increment instead |
| // of the traditional post-increment when making the assignment. |
| sourceLines[resLine] = ++srcLine; |
| } |
| } |
| } |