Candidate.java

  1. /*
  2.  * Copyright (C) 2011, 2019 Google Inc. and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.blame;

  11. import java.io.IOException;
  12. import java.util.List;

  13. import org.eclipse.jgit.blame.ReverseWalk.ReverseCommit;
  14. import org.eclipse.jgit.diff.Edit;
  15. import org.eclipse.jgit.diff.EditList;
  16. import org.eclipse.jgit.diff.RawText;
  17. import org.eclipse.jgit.errors.MissingObjectException;
  18. import org.eclipse.jgit.internal.JGitText;
  19. import org.eclipse.jgit.lib.Constants;
  20. import org.eclipse.jgit.lib.ObjectId;
  21. import org.eclipse.jgit.lib.ObjectLoader;
  22. import org.eclipse.jgit.lib.ObjectReader;
  23. import org.eclipse.jgit.lib.PersonIdent;
  24. import org.eclipse.jgit.lib.Repository;
  25. import org.eclipse.jgit.revwalk.RevCommit;
  26. import org.eclipse.jgit.revwalk.RevFlag;
  27. import org.eclipse.jgit.revwalk.RevWalk;
  28. import org.eclipse.jgit.treewalk.filter.PathFilter;
  29. import org.eclipse.jgit.util.LfsFactory;

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

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

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

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

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

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

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

  72.     /** repository used for LFS blob handling */
  73.     private Repository sourceRepository;

  74.     Candidate(Repository repo, RevCommit commit, PathFilter path) {
  75.         sourceRepository = repo;
  76.         sourceCommit = commit;
  77.         sourcePath = path;
  78.     }

  79.     void beginResult(RevWalk rw) throws MissingObjectException, IOException {
  80.         rw.parseBody(sourceCommit);
  81.     }

  82.     int getParentCount() {
  83.         return sourceCommit.getParentCount();
  84.     }

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

  88.     Candidate getNextCandidate(@SuppressWarnings("unused") int idx) {
  89.         return null;
  90.     }

  91.     boolean has(RevFlag flag) {
  92.         return sourceCommit.has(flag);
  93.     }

  94.     void add(RevFlag flag) {
  95.         sourceCommit.add(flag);
  96.     }

  97.     void remove(RevFlag flag) {
  98.         sourceCommit.remove(flag);
  99.     }

  100.     int getTime() {
  101.         return sourceCommit.getCommitTime();
  102.     }

  103.     PersonIdent getAuthor() {
  104.         return sourceCommit.getAuthorIdent();
  105.     }

  106.     Candidate create(Repository repo, RevCommit commit, PathFilter path) {
  107.         return new Candidate(repo, commit, path);
  108.     }

  109.     Candidate copy(RevCommit commit) {
  110.         Candidate r = create(sourceRepository, commit, sourcePath);
  111.         r.sourceBlob = sourceBlob;
  112.         r.sourceText = sourceText;
  113.         r.regionList = regionList;
  114.         r.renameScore = renameScore;
  115.         return r;
  116.     }

  117.     void loadText(ObjectReader reader) throws IOException {
  118.         ObjectLoader ldr = LfsFactory.getInstance().applySmudgeFilter(sourceRepository,
  119.                 reader.open(sourceBlob, Constants.OBJ_BLOB),
  120.                 LfsFactory.getAttributesForPath(sourceRepository,
  121.                         sourcePath.getPath(), sourceCommit)
  122.                         .get(Constants.ATTR_DIFF));
  123.         sourceText = new RawText(ldr.getCachedBytes(Integer.MAX_VALUE));
  124.     }

  125.     void takeBlame(EditList editList, Candidate child) {
  126.         blame(editList, this, child);
  127.     }

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

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

  138.             Edit e = editList.get(eIdx);

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

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

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

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

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

  167.             // If the region ends before the edit, blame on B.
  168.             int rEnd = r.sourceStart + r.length;
  169.             if (rEnd <= e.getEndB()) {
  170.                 Region next = r.next;
  171.                 bTail = add(bTail, b, r);
  172.                 r = next;
  173.                 if (rEnd == e.getEndB())
  174.                     eIdx++;
  175.                 continue;
  176.             }

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

  184.         if (r == null)
  185.             return;

  186.         // For any remaining region, pass the blame onto A after shifting
  187.         // the source start to account for the difference between the two.
  188.         Edit e = editList.get(editList.size() - 1);
  189.         int endB = e.getEndB();
  190.         int d = endB - e.getEndA();
  191.         if (aTail == null)
  192.             a.regionList = r;
  193.         else
  194.             aTail.next = r;
  195.         do {
  196.             if (endB <= r.sourceStart)
  197.                 r.sourceStart -= d;
  198.             r = r.next;
  199.         } while (r != null);
  200.     }

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

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

  219.         // Append the region onto the end of the list.
  220.         aTail.next = n;
  221.         n.next = null;
  222.         return n;
  223.     }

  224.     private Region clearRegionList() {
  225.         Region r = regionList;
  226.         regionList = null;
  227.         return r;
  228.     }

  229.     boolean canMergeRegions(Candidate other) {
  230.         return sourceCommit == other.sourceCommit
  231.                 && sourcePath.getPath().equals(other.sourcePath.getPath());
  232.     }

  233.     void mergeRegions(Candidate other) {
  234.         // regionList is always sorted by resultStart. Merge join two
  235.         // linked lists, preserving the ordering. Combine neighboring
  236.         // regions to reduce the number of results seen by callers.
  237.         Region a = clearRegionList();
  238.         Region b = other.clearRegionList();
  239.         Region t = null;

  240.         while (a != null && b != null) {
  241.             if (a.resultStart < b.resultStart) {
  242.                 Region n = a.next;
  243.                 t = add(t, this, a);
  244.                 a = n;
  245.             } else {
  246.                 Region n = b.next;
  247.                 t = add(t, this, b);
  248.                 b = n;
  249.             }
  250.         }

  251.         if (a != null) {
  252.             Region n = a.next;
  253.             t = add(t, this, a);
  254.             t.next = n;
  255.         } else /* b != null */{
  256.             Region n = b.next;
  257.             t = add(t, this, b);
  258.             t.next = n;
  259.         }
  260.     }

  261.     /** {@inheritDoc} */
  262.     @SuppressWarnings("nls")
  263.     @Override
  264.     public String toString() {
  265.         StringBuilder r = new StringBuilder();
  266.         r.append("Candidate[");
  267.         r.append(sourcePath.getPath());
  268.         if (sourceCommit != null)
  269.             r.append(" @ ").append(sourceCommit.abbreviate(6).name());
  270.         if (regionList != null)
  271.             r.append(" regions:").append(regionList);
  272.         r.append("]");
  273.         return r.toString();
  274.     }

  275.     /**
  276.      * Special candidate type used for reverse blame.
  277.      * <p>
  278.      * Reverse blame inverts the commit history graph to follow from a commit to
  279.      * its descendant children, rather than the normal history direction of
  280.      * child to parent. These types require a {@link ReverseCommit} which keeps
  281.      * children pointers, allowing reverse navigation of history.
  282.      */
  283.     static final class ReverseCandidate extends Candidate {
  284.         ReverseCandidate(Repository repo, ReverseCommit commit,
  285.                 PathFilter path) {
  286.             super(repo, commit, path);
  287.         }

  288.         @Override
  289.         int getParentCount() {
  290.             return ((ReverseCommit) sourceCommit).getChildCount();
  291.         }

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

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

  301.         @Override
  302.         Candidate create(Repository repo, RevCommit commit, PathFilter path) {
  303.             return new ReverseCandidate(repo, (ReverseCommit) commit, path);
  304.         }

  305.         @Override
  306.         public String toString() {
  307.             return "Reverse" + super.toString(); //$NON-NLS-1$
  308.         }
  309.     }

  310.     /**
  311.      * A {@link Candidate} to blame a working tree file in conflict state.
  312.      * <p>
  313.      * Contrary to {@link BlobCandidate}, it expects to be given the parent
  314.      * commits (typically HEAD and the MERGE_HEADs) and behaves like a merge
  315.      * commit during blame. It does <em>not</em> consider a previously pushed
  316.      * Candidate as its parent.
  317.      * </p>
  318.      */
  319.     static final class HeadCandidate extends Candidate {

  320.         private List<RevCommit> parents;

  321.         HeadCandidate(Repository repo, PathFilter path,
  322.                 List<RevCommit> parents) {
  323.             super(repo, null, path);
  324.             this.parents = parents;
  325.         }

  326.         @Override
  327.         void beginResult(RevWalk rw) {
  328.             // Blob candidates have nothing to prepare.
  329.         }

  330.         @Override
  331.         int getParentCount() {
  332.             return parents.size();
  333.         }

  334.         @Override
  335.         RevCommit getParent(int idx) {
  336.             return parents.get(idx);
  337.         }

  338.         @Override
  339.         boolean has(RevFlag flag) {
  340.             return true; // Pretend flag was added; sourceCommit is null.
  341.         }

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

  346.         @Override
  347.         void remove(RevFlag flag) {
  348.             // Do nothing, sourceCommit is null.
  349.         }

  350.         @Override
  351.         int getTime() {
  352.             return Integer.MAX_VALUE;
  353.         }

  354.         @Override
  355.         PersonIdent getAuthor() {
  356.             return new PersonIdent(JGitText.get().blameNotCommittedYet, ""); //$NON-NLS-1$
  357.         }
  358.     }

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

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

  377.         BlobCandidate(Repository repo, String name, PathFilter path) {
  378.             super(repo, null, path);
  379.             description = name;
  380.         }

  381.         @Override
  382.         void beginResult(RevWalk rw) {
  383.             // Blob candidates have nothing to prepare.
  384.         }

  385.         @Override
  386.         int getParentCount() {
  387.             return parent != null ? 1 : 0;
  388.         }

  389.         @Override
  390.         RevCommit getParent(int idx) {
  391.             return null;
  392.         }

  393.         @Override
  394.         Candidate getNextCandidate(int idx) {
  395.             return parent;
  396.         }

  397.         @Override
  398.         boolean has(RevFlag flag) {
  399.             return true; // Pretend flag was added; sourceCommit is null.
  400.         }

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

  405.         @Override

  406.         void remove(RevFlag flag) {
  407.             // Do nothing, sourceCommit is null.
  408.         }

  409.         @Override
  410.         int getTime() {
  411.             return Integer.MAX_VALUE;
  412.         }

  413.         @Override
  414.         PersonIdent getAuthor() {
  415.             return new PersonIdent(description, ""); //$NON-NLS-1$
  416.         }
  417.     }
  418. }