MergeBaseGenerator.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 java.text.MessageFormat;
  13. import java.util.LinkedList;

  14. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  15. import org.eclipse.jgit.errors.MissingObjectException;
  16. import org.eclipse.jgit.internal.JGitText;

  17. /**
  18.  * Computes the merge base(s) of the starting commits.
  19.  * <p>
  20.  * This generator is selected if the RevFilter is only
  21.  * {@link org.eclipse.jgit.revwalk.filter.RevFilter#MERGE_BASE}.
  22.  * <p>
  23.  * To compute the merge base we assign a temporary flag to each of the starting
  24.  * commits. The maximum number of starting commits is bounded by the number of
  25.  * free flags available in the RevWalk when the generator is initialized. These
  26.  * flags will be automatically released on the next reset of the RevWalk, but
  27.  * not until then, as they are assigned to commits throughout the history.
  28.  * <p>
  29.  * Several internal flags are reused here for a different purpose, but this
  30.  * should not have any impact as this generator should be run alone, and without
  31.  * any other generators wrapped around it.
  32.  */
  33. class MergeBaseGenerator extends Generator {
  34.     private static final int PARSED = RevWalk.PARSED;
  35.     private static final int IN_PENDING = RevWalk.SEEN;
  36.     private static final int POPPED = RevWalk.TEMP_MARK;
  37.     private static final int MERGE_BASE = RevWalk.REWRITE;

  38.     private final RevWalk walker;
  39.     private final DateRevQueue pending;

  40.     private int branchMask;
  41.     private int recarryTest;
  42.     private int recarryMask;
  43.     private int mergeBaseAncestor = -1;
  44.     private LinkedList<RevCommit> ret = new LinkedList<>();

  45.     private CarryStack stack;

  46.     MergeBaseGenerator(RevWalk w) {
  47.         super(w.isFirstParent());
  48.         walker = w;
  49.         pending = new DateRevQueue(firstParent);
  50.     }

  51.     void init(AbstractRevQueue p) throws IOException {
  52.         try {
  53.             for (;;) {
  54.                 final RevCommit c = p.next();
  55.                 if (c == null)
  56.                     break;
  57.                 add(c);
  58.             }
  59.             // Setup the condition used by carryOntoOne to detect a late
  60.             // merge base and produce it on the next round.
  61.             //
  62.             recarryTest = branchMask | POPPED;
  63.             recarryMask = branchMask | POPPED | MERGE_BASE;
  64.             mergeBaseAncestor = walker.allocFlag();

  65.             for (;;) {
  66.                 RevCommit c = _next();
  67.                 if (c == null) {
  68.                     break;
  69.                 }
  70.                 ret.add(c);
  71.             }
  72.         } finally {
  73.             // Always free the flags immediately. This ensures the flags
  74.             // will be available for reuse when the walk resets.
  75.             //
  76.             walker.freeFlag(branchMask | mergeBaseAncestor);
  77.         }
  78.     }

  79.     private void add(RevCommit c) {
  80.         final int flag = walker.allocFlag();
  81.         branchMask |= flag;
  82.         if ((c.flags & branchMask) != 0) {
  83.             // This should never happen. RevWalk ensures we get a
  84.             // commit admitted to the initial queue only once. If
  85.             // we see this marks aren't correctly erased.
  86.             //
  87.             throw new IllegalStateException(MessageFormat.format(JGitText.get().staleRevFlagsOn, c.name()));
  88.         }
  89.         c.flags |= flag;
  90.         pending.add(c);
  91.     }

  92.     @Override
  93.     int outputType() {
  94.         return 0;
  95.     }

  96.     private RevCommit _next() throws MissingObjectException,
  97.             IncorrectObjectTypeException, IOException {
  98.         for (;;) {
  99.             final RevCommit c = pending.next();
  100.             if (c == null) {
  101.                 return null;
  102.             }

  103.             for (RevCommit p : c.getParents()) {
  104.                 if ((p.flags & IN_PENDING) != 0)
  105.                     continue;
  106.                 if ((p.flags & PARSED) == 0)
  107.                     p.parseHeaders(walker);
  108.                 p.flags |= IN_PENDING;
  109.                 pending.add(p);
  110.             }

  111.             int carry = c.flags & branchMask;
  112.             boolean mb = carry == branchMask;
  113.             if (mb) {
  114.                 // If we are a merge base make sure our ancestors are
  115.                 // also flagged as being popped, so that they do not
  116.                 // generate to the caller.
  117.                 //
  118.                 carry |= MERGE_BASE | mergeBaseAncestor;
  119.             }
  120.             carryOntoHistory(c, carry);

  121.             if ((c.flags & MERGE_BASE) != 0) {
  122.                 // This commit is an ancestor of a merge base we already
  123.                 // popped back to the caller. If everyone in pending is
  124.                 // that way we are done traversing; if not we just need
  125.                 // to move to the next available commit and try again.
  126.                 //
  127.                 if (pending.everbodyHasFlag(MERGE_BASE))
  128.                     return null;
  129.                 continue;
  130.             }
  131.             c.flags |= POPPED;

  132.             if (mb) {
  133.                 c.flags |= MERGE_BASE;
  134.                 return c;
  135.             }
  136.         }
  137.     }

  138.     @Override
  139.     RevCommit next() throws MissingObjectException,
  140.             IncorrectObjectTypeException, IOException {
  141.         while (!ret.isEmpty()) {
  142.             RevCommit commit = ret.remove();
  143.             if ((commit.flags & mergeBaseAncestor) == 0) {
  144.                 return commit;
  145.             }
  146.         }
  147.         return null;
  148.     }

  149.     private void carryOntoHistory(RevCommit c, int carry) {
  150.         stack = null;
  151.         for (;;) {
  152.             carryOntoHistoryInnerLoop(c, carry);
  153.             if (stack == null) {
  154.                 break;
  155.             }
  156.             c = stack.c;
  157.             carry = stack.carry;
  158.             stack = stack.prev;
  159.         }
  160.     }

  161.     private void carryOntoHistoryInnerLoop(RevCommit c, int carry) {
  162.         for (;;) {
  163.             RevCommit[] parents = c.getParents();
  164.             if (parents == null || parents.length == 0) {
  165.                 break;
  166.             }

  167.             int e = parents.length - 1;
  168.             for (int i = 0; i < e; i++) {
  169.                 RevCommit p = parents[i];
  170.                 if (carryOntoOne(p, carry) == CONTINUE) {
  171.                     // Walking p will be required, buffer p on stack.
  172.                     stack = new CarryStack(stack, p, carry);
  173.                 }
  174.                 // For other results from carryOntoOne:
  175.                 // HAVE_ALL: p has all bits, do nothing to skip that path.
  176.                 // CONTINUE_ON_STACK: callee pushed StackElement for p.
  177.             }

  178.             c = parents[e];
  179.             if (carryOntoOne(c, carry) != CONTINUE) {
  180.                 break;
  181.             }
  182.         }
  183.     }

  184.     private static final int CONTINUE = 0;
  185.     private static final int HAVE_ALL = 1;
  186.     private static final int CONTINUE_ON_STACK = 2;

  187.     private int carryOntoOne(RevCommit p, int carry) {
  188.         // If we already had all carried flags, our parents do too.
  189.         // Return HAVE_ALL to stop caller from running down this leg
  190.         // of the revision graph any further.
  191.         //
  192.         // Otherwise return CONTINUE to ask the caller to walk history.
  193.         int rc = (p.flags & carry) == carry ? HAVE_ALL : CONTINUE;
  194.         p.flags |= carry;

  195.         if ((p.flags & recarryMask) == recarryTest) {
  196.             // We were popped without being a merge base, but we just got
  197.             // voted to be one. Inject ourselves back at the front of the
  198.             // pending queue and tell all of our ancestors they are within
  199.             // the merge base now.
  200.             p.flags &= ~POPPED;
  201.             pending.add(p);
  202.             stack = new CarryStack(stack, p, branchMask | MERGE_BASE);
  203.             return CONTINUE_ON_STACK;
  204.         }
  205.         return rc;
  206.     }

  207.     private static class CarryStack {
  208.         final CarryStack prev;
  209.         final RevCommit c;
  210.         final int carry;

  211.         CarryStack(CarryStack prev, RevCommit c, int carry) {
  212.             this.prev = prev;
  213.             this.c = c;
  214.             this.carry = carry;
  215.         }
  216.     }
  217. }