Package org.eclipse.jgit.blame

Source Code of org.eclipse.jgit.blame.Candidate$ReverseCandidate

/*
* 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.blame.ReverseWalk.ReverseCommit;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.diff.EditList;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.treewalk.filter.PathFilter;

/**
* A source that may have supplied some (or all) of the result file.
* <p>
* Candidates are kept in a queue by BlameGenerator, allowing the generator to
* perform a parallel search down the parents of any merges that are discovered
* during the history traversal. Each candidate retains a {@link #regionList}
* describing sections of the result file the candidate has taken responsibility
* for either directly or indirectly through its history. Actual blame from this
* region list will be assigned to the candidate when its ancestor commit(s) are
* themselves converted into Candidate objects and the ancestor's candidate uses
* {@link #takeBlame(EditList, Candidate)} to accept responsibility for sections
* of the result.
*/
class Candidate {
  /** Next candidate in the candidate queue. */
  Candidate queueNext;

  /** Commit being considered (or blamed, depending on state). */
  RevCommit sourceCommit;

  /** Path of the candidate file in {@link #sourceCommit}. */
  PathFilter sourcePath;

  /** Unique name of the candidate blob in {@link #sourceCommit}. */
  ObjectId sourceBlob;

  /** Complete contents of the file in {@link #sourceCommit}. */
  RawText sourceText;

  /**
   * Chain of regions this candidate may be blamed for.
   * <p>
   * This list is always kept sorted by resultStart order, making it simple to
   * merge-join with the sorted EditList during blame assignment.
   */
  Region regionList;

  /**
   * Score assigned to the rename to this candidate.
   * <p>
   * Consider the history "A<-B<-C". If the result file S in C was renamed to
   * R in B, the rename score for this rename will be held in this field by
   * the candidate object for B. By storing the score with B, the application
   * can see what the rename score was as it makes the transition from C/S to
   * B/R. This may seem backwards since it was C that performed the rename,
   * but the application doesn't learn about path R until B.
   */
  int renameScore;

  Candidate(RevCommit commit, PathFilter path) {
    sourceCommit = commit;
    sourcePath = path;
  }

  int getParentCount() {
    return sourceCommit.getParentCount();
  }

  RevCommit getParent(int idx) {
    return sourceCommit.getParent(idx);
  }

  Candidate getNextCandidate(@SuppressWarnings("unused") int idx) {
    return null;
  }

  void add(RevFlag flag) {
    sourceCommit.add(flag);
  }

  int getTime() {
    return sourceCommit.getCommitTime();
  }

  PersonIdent getAuthor() {
    return sourceCommit.getAuthorIdent();
  }

  Candidate create(RevCommit commit, PathFilter path) {
    return new Candidate(commit, path);
  }

  Candidate copy(RevCommit commit) {
    Candidate r = create(commit, sourcePath);
    r.sourceBlob = sourceBlob;
    r.sourceText = sourceText;
    r.regionList = regionList;
    r.renameScore = renameScore;
    return r;
  }

  void loadText(ObjectReader reader) throws IOException {
    ObjectLoader ldr = reader.open(sourceBlob, Constants.OBJ_BLOB);
    sourceText = new RawText(ldr.getCachedBytes(Integer.MAX_VALUE));
  }

  void takeBlame(EditList editList, Candidate child) {
    blame(editList, this, child);
  }

  private static void blame(EditList editList, Candidate a, Candidate b) {
    Region r = b.clearRegionList();
    Region aTail = null;
    Region bTail = null;

    for (int eIdx = 0; eIdx < editList.size();) {
      // If there are no more regions left, neither side has any
      // more responsibility for the result. Remaining edits can
      // be safely ignored.
      if (r == null)
        return;

      Edit e = editList.get(eIdx);

      // Edit ends before the next candidate region. Skip the edit.
      if (e.getEndB() <= r.sourceStart) {
        eIdx++;
        continue;
      }

      // Next candidate region starts before the edit. Assign some
      // of the blame onto A, but possibly split and also on B.
      if (r.sourceStart < e.getBeginB()) {
        int d = e.getBeginB() - r.sourceStart;
        if (r.length <= d) {
          // Pass the blame for this region onto A.
          Region next = r.next;
          r.sourceStart = e.getBeginA() - d;
          aTail = add(aTail, a, r);
          r = next;
          continue;
        }

        // Split the region and assign some to A, some to B.
        aTail = add(aTail, a, r.splitFirst(e.getBeginA() - d, d));
        r.slideAndShrink(d);
      }

      // At this point e.getBeginB() <= r.sourceStart.

      // An empty edit on the B side isn't relevant to this split,
      // as it does not overlap any candidate region.
      if (e.getLengthB() == 0) {
        eIdx++;
        continue;
      }

      // If the region ends before the edit, blame on B.
      int rEnd = r.sourceStart + r.length;
      if (rEnd <= e.getEndB()) {
        Region next = r.next;
        bTail = add(bTail, b, r);
        r = next;
        if (rEnd == e.getEndB())
          eIdx++;
        continue;
      }

      // This region extends beyond the edit. Blame the first
      // half of the region on B, and process the rest after.
      int len = e.getEndB() - r.sourceStart;
      bTail = add(bTail, b, r.splitFirst(r.sourceStart, len));
      r.slideAndShrink(len);
      eIdx++;
    }

    if (r == null)
      return;

    // For any remaining region, pass the blame onto A after shifting
    // the source start to account for the difference between the two.
    Edit e = editList.get(editList.size() - 1);
    int endB = e.getEndB();
    int d = endB - e.getEndA();
    if (aTail == null)
      a.regionList = r;
    else
      aTail.next = r;
    do {
      if (endB <= r.sourceStart)
        r.sourceStart -= d;
      r = r.next;
    } while (r != null);
  }

  private static Region add(Region aTail, Candidate a, Region n) {
    // If there is no region on the list, use only this one.
    if (aTail == null) {
      a.regionList = n;
      n.next = null;
      return n;
    }

    // If the prior region ends exactly where the new region begins
    // in both the result and the source, combine these together into
    // one contiguous region. This occurs when intermediate commits
    // have inserted and deleted lines in the middle of a region. Try
    // to report this region as a single region to the application,
    // rather than in fragments.
    if (aTail.resultStart + aTail.length == n.resultStart
        && aTail.sourceStart + aTail.length == n.sourceStart) {
      aTail.length += n.length;
      return aTail;
    }

    // Append the region onto the end of the list.
    aTail.next = n;
    n.next = null;
    return n;
  }

  private Region clearRegionList() {
    Region r = regionList;
    regionList = null;
    return r;
  }

  @Override
  public String toString() {
    StringBuilder r = new StringBuilder();
    r.append("Candidate[");
    r.append(sourcePath.getPath());
    if (sourceCommit != null)
      r.append(" @ ").append(sourceCommit.abbreviate(6).name());
    if (regionList != null)
      r.append(" regions:").append(regionList);
    r.append("]");
    return r.toString();
  }

  /**
   * Special candidate type used for reverse blame.
   * <p>
   * Reverse blame inverts the commit history graph to follow from a commit to
   * its descendant children, rather than the normal history direction of
   * child to parent. These types require a {@link ReverseCommit} which keeps
   * children pointers, allowing reverse navigation of history.
   */
  static final class ReverseCandidate extends Candidate {
    ReverseCandidate(ReverseCommit commit, PathFilter path) {
      super(commit, path);
    }

    @Override
    int getParentCount() {
      return ((ReverseCommit) sourceCommit).getChildCount();
    }

    @Override
    RevCommit getParent(int idx) {
      return ((ReverseCommit) sourceCommit).getChild(idx);
    }

    @Override
    int getTime() {
      // Invert the timestamp so newer dates sort older.
      return -sourceCommit.getCommitTime();
    }

    @Override
    Candidate create(RevCommit commit, PathFilter path) {
      return new ReverseCandidate((ReverseCommit) commit, path);
    }

    @Override
    public String toString() {
      return "Reverse" + super.toString();
    }
  }

  /**
   * Candidate loaded from a file source, and not a commit.
   * <p>
   * The {@link Candidate#sourceCommit} field is always null on this type of
   * candidate. Instead history traversal follows the single {@link #parent}
   * field to discover the next Candidate. Often this is a normal Candidate
   * type that has a valid sourceCommit.
   */
  static final class BlobCandidate extends Candidate {
    /**
     * Next candidate to pass blame onto.
     * <p>
     * When computing the differences that this candidate introduced to the
     * file content, the parent's sourceText is used as the base.
     */
    Candidate parent;

    /** Author name to refer to this blob with. */
    String description;

    BlobCandidate(String name, PathFilter path) {
      super(null, path);
      description = name;
    }

    @Override
    int getParentCount() {
      return parent != null ? 1 : 0;
    }

    @Override
    RevCommit getParent(int idx) {
      return null;
    }

    @Override
    Candidate getNextCandidate(int idx) {
      return parent;
    }

    @Override
    void add(RevFlag flag) {
      // Do nothing, sourceCommit is null.
    }

    @Override
    int getTime() {
      return Integer.MAX_VALUE;
    }

    @Override
    PersonIdent getAuthor() {
      return new PersonIdent(description, null);
    }
  }
}
TOP

Related Classes of org.eclipse.jgit.blame.Candidate$ReverseCandidate

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.