RewriteGenerator.java

  1. /*
  2.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> 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.revwalk;

  11. import java.io.IOException;

  12. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  13. import org.eclipse.jgit.errors.MissingObjectException;

  14. /**
  15.  * Replaces a RevCommit's parents until not colored with REWRITE.
  16.  * <p>
  17.  * Before a RevCommit is returned to the caller its parents are updated to
  18.  * create a dense DAG. Instead of reporting the actual parents as recorded when
  19.  * the commit was created the returned commit will reflect the next closest
  20.  * commit that matched the revision walker's filters.
  21.  * <p>
  22.  * This generator is the second phase of a path limited revision walk and
  23.  * assumes it is receiving RevCommits from {@link TreeRevFilter},
  24.  * after they have been fully buffered by {@link AbstractRevQueue}. The full
  25.  * buffering is necessary to allow the simple loop used within our own
  26.  * {@link #rewrite(RevCommit)} to pull completely through a strand of
  27.  * {@link RevWalk#REWRITE} colored commits and come up with a simplification
  28.  * that makes the DAG dense. Not fully buffering the commits first would cause
  29.  * this loop to abort early, due to commits not being parsed and colored
  30.  * correctly.
  31.  *
  32.  * @see TreeRevFilter
  33.  */
  34. class RewriteGenerator extends Generator {
  35.     private static final int REWRITE = RevWalk.REWRITE;

  36.     /** For {@link #cleanup(RevCommit[])} to remove duplicate parents. */
  37.     private static final int DUPLICATE = RevWalk.TEMP_MARK;

  38.     private final Generator source;

  39.     RewriteGenerator(Generator s) {
  40.         super(s.firstParent);
  41.         source = s;
  42.     }

  43.     @Override
  44.     void shareFreeList(BlockRevQueue q) {
  45.         source.shareFreeList(q);
  46.     }

  47.     @Override
  48.     int outputType() {
  49.         return source.outputType() & ~NEEDS_REWRITE;
  50.     }

  51.     @Override
  52.     RevCommit next() throws MissingObjectException,
  53.             IncorrectObjectTypeException, IOException {
  54.         final RevCommit c = source.next();
  55.         if (c == null) {
  56.             return null;
  57.         }
  58.         boolean rewrote = false;
  59.         final RevCommit[] pList = c.parents;
  60.         final int nParents = pList.length;
  61.         for (int i = 0; i < nParents; i++) {
  62.             final RevCommit oldp = pList[i];
  63.             final RevCommit newp = rewrite(oldp);
  64.             if (firstParent) {
  65.                 if (newp == null) {
  66.                     c.parents = RevCommit.NO_PARENTS;
  67.                 } else {
  68.                     c.parents = new RevCommit[] { newp };
  69.                 }
  70.                 return c;
  71.             }
  72.             if (oldp != newp) {
  73.                 pList[i] = newp;
  74.                 rewrote = true;
  75.             }
  76.         }
  77.         if (rewrote) {
  78.             c.parents = cleanup(pList);
  79.         }
  80.         return c;
  81.     }

  82.     private RevCommit rewrite(RevCommit p) {
  83.         for (;;) {
  84.             final RevCommit[] pList = p.parents;
  85.             if (pList.length > 1) {
  86.                 // This parent is a merge, so keep it.
  87.                 //
  88.                 return p;
  89.             }

  90.             if ((p.flags & RevWalk.UNINTERESTING) != 0) {
  91.                 // Retain uninteresting parents. They show where the
  92.                 // DAG was cut off because it wasn't interesting.
  93.                 //
  94.                 return p;
  95.             }

  96.             if ((p.flags & REWRITE) == 0) {
  97.                 // This parent was not eligible for rewriting. We
  98.                 // need to keep it in the DAG.
  99.                 //
  100.                 return p;
  101.             }

  102.             if (pList.length == 0) {
  103.                 // We can't go back any further, other than to
  104.                 // just delete the parent entirely.
  105.                 //
  106.                 return null;
  107.             }

  108.             p = pList[0];
  109.         }
  110.     }

  111.     private RevCommit[] cleanup(RevCommit[] oldList) {
  112.         // Remove any duplicate parents caused due to rewrites (e.g. a merge
  113.         // with two sides that both simplified back into the merge base).
  114.         // We also may have deleted a parent by marking it null.
  115.         //
  116.         int newCnt = 0;
  117.         for (int o = 0; o < oldList.length; o++) {
  118.             final RevCommit p = oldList[o];
  119.             if (p == null)
  120.                 continue;
  121.             if ((p.flags & DUPLICATE) != 0) {
  122.                 oldList[o] = null;
  123.                 continue;
  124.             }
  125.             p.flags |= DUPLICATE;
  126.             newCnt++;
  127.         }

  128.         if (newCnt == oldList.length) {
  129.             for (RevCommit p : oldList)
  130.                 p.flags &= ~DUPLICATE;
  131.             return oldList;
  132.         }

  133.         final RevCommit[] newList = new RevCommit[newCnt];
  134.         newCnt = 0;
  135.         for (RevCommit p : oldList) {
  136.             if (p != null) {
  137.                 newList[newCnt++] = p;
  138.                 p.flags &= ~DUPLICATE;
  139.             }
  140.         }

  141.         return newList;
  142.     }
  143. }