MergedReftable.java

  1. /*
  2.  * Copyright (C) 2017, 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.internal.storage.reftable;

  11. import java.io.IOException;
  12. import java.util.ArrayList;
  13. import java.util.List;
  14. import java.util.PriorityQueue;

  15. import org.eclipse.jgit.lib.AnyObjectId;
  16. import org.eclipse.jgit.lib.Ref;
  17. import org.eclipse.jgit.lib.ReflogEntry;

  18. /**
  19.  * Merges multiple reference tables together.
  20.  * <p>
  21.  * A {@link org.eclipse.jgit.internal.storage.reftable.MergedReftable}
  22.  * merge-joins multiple
  23.  * {@link org.eclipse.jgit.internal.storage.reftable.ReftableReader} on the fly.
  24.  * Tables higher/later in the stack shadow lower/earlier tables, hiding
  25.  * references that been updated/replaced.
  26.  * <p>
  27.  * By default deleted references are skipped and not returned to the caller.
  28.  * {@link #setIncludeDeletes(boolean)} can be used to modify this behavior if
  29.  * the caller needs to preserve deletions during partial compaction.
  30.  * <p>
  31.  * A {@code MergedReftable} is not thread-safe.
  32.  */
  33. public class MergedReftable extends Reftable {
  34.     private final ReftableReader[] tables;

  35.     /**
  36.      * Initialize a merged table reader.
  37.      * <p>
  38.      *
  39.      * @param tableStack
  40.      *            stack of tables to read from. The base of the stack is at
  41.      *            index 0, the most recent should be at the top of the stack at
  42.      *            {@code tableStack.size() - 1}. The top of the stack (higher
  43.      *            index) shadows the base of the stack (lower index).
  44.      */
  45.     public MergedReftable(List<ReftableReader> tableStack) {
  46.         tables = tableStack.toArray(new ReftableReader[0]);

  47.         // Tables must expose deletes to this instance to correctly
  48.         // shadow references from lower tables.
  49.         for (ReftableReader t : tables) {
  50.             t.setIncludeDeletes(true);
  51.         }
  52.     }

  53.     /**
  54.      * {@inheritDoc}
  55.      */
  56.     @Override
  57.     public long maxUpdateIndex() throws IOException {
  58.         if (tables.length == 0) {
  59.             return 0;
  60.         }
  61.         long maxUpdateIndex = tables[tables.length - 1].maxUpdateIndex();
  62.         for (int i = tables.length - 2; i >= 0; i--) {
  63.             if (maxUpdateIndex < tables[i].maxUpdateIndex()) {
  64.                 maxUpdateIndex = tables[i].maxUpdateIndex();
  65.             }
  66.         }
  67.         return maxUpdateIndex;
  68.     }

  69.     /**
  70.      * {@inheritDoc}
  71.      */
  72.     @Override
  73.     public long minUpdateIndex() throws IOException {
  74.         if (tables.length == 0) {
  75.             return 0;
  76.         }
  77.         long minUpdateIndex = tables[0].minUpdateIndex();
  78.         for (int i = 1; i < tables.length; i++) {
  79.             if (tables[i].minUpdateIndex() < minUpdateIndex) {
  80.                 minUpdateIndex = tables[i].minUpdateIndex();
  81.             }
  82.         }
  83.         return minUpdateIndex;
  84.     }

  85.     /** {@inheritDoc} */
  86.     @Override
  87.     public boolean hasObjectMap() throws IOException {
  88.         boolean has = true;
  89.         for (int i = 0; has && i < tables.length; i++) {
  90.             has = has && tables[i].hasObjectMap();
  91.         }
  92.         return has;
  93.     }

  94.     /** {@inheritDoc} */
  95.     @Override
  96.     public RefCursor allRefs() throws IOException {
  97.         MergedRefCursor m = new MergedRefCursor();
  98.         for (int i = 0; i < tables.length; i++) {
  99.             m.add(new RefQueueEntry(tables[i].allRefs(), i));
  100.         }
  101.         return m;
  102.     }

  103.     /** {@inheritDoc} */
  104.     @Override
  105.     public RefCursor seekRef(String name) throws IOException {
  106.         MergedRefCursor m = new MergedRefCursor();
  107.         for (int i = 0; i < tables.length; i++) {
  108.             m.add(new RefQueueEntry(tables[i].seekRef(name), i));
  109.         }
  110.         return m;
  111.     }

  112.     /** {@inheritDoc} */
  113.     @Override
  114.     public RefCursor seekRefsWithPrefix(String prefix) throws IOException {
  115.         MergedRefCursor m = new MergedRefCursor();
  116.         for (int i = 0; i < tables.length; i++) {
  117.             m.add(new RefQueueEntry(tables[i].seekRefsWithPrefix(prefix), i));
  118.         }
  119.         return m;
  120.     }

  121.     /** {@inheritDoc} */
  122.     @Override
  123.     public RefCursor byObjectId(AnyObjectId name) throws IOException {
  124.         MergedRefCursor m = new FilteringMergedRefCursor(name);
  125.         for (int i = 0; i < tables.length; i++) {
  126.             m.add(new RefQueueEntry(tables[i].byObjectId(name), i));
  127.         }
  128.         return m;
  129.     }

  130.     /** {@inheritDoc} */
  131.     @Override
  132.     public LogCursor allLogs() throws IOException {
  133.         MergedLogCursor m = new MergedLogCursor();
  134.         for (int i = 0; i < tables.length; i++) {
  135.             m.add(new LogQueueEntry(tables[i].allLogs(), i));
  136.         }
  137.         return m;
  138.     }

  139.     /** {@inheritDoc} */
  140.     @Override
  141.     public LogCursor seekLog(String refName, long updateIdx)
  142.             throws IOException {
  143.         MergedLogCursor m = new MergedLogCursor();
  144.         for (int i = 0; i < tables.length; i++) {
  145.             m.add(new LogQueueEntry(tables[i].seekLog(refName, updateIdx), i));
  146.         }
  147.         return m;
  148.     }

  149.     int queueSize() {
  150.         return Math.max(1, tables.length);
  151.     }

  152.     private class MergedRefCursor extends RefCursor {
  153.         private final PriorityQueue<RefQueueEntry> queue;
  154.         private RefQueueEntry head;
  155.         private Ref ref;

  156.         MergedRefCursor() {
  157.             queue = new PriorityQueue<>(queueSize(), RefQueueEntry::compare);
  158.         }

  159.         void add(RefQueueEntry t) throws IOException {
  160.             // Common case is many iterations over the same RefQueueEntry
  161.             // for the bottom of the stack (scanning all refs). Its almost
  162.             // always less than the top of the queue. Avoid the queue's
  163.             // O(log N) insertion and removal costs for this common case.
  164.             if (!t.rc.next()) {
  165.                 t.rc.close();
  166.             } else if (head == null) {
  167.                 RefQueueEntry p = queue.peek();
  168.                 if (p == null || RefQueueEntry.compare(t, p) < 0) {
  169.                     head = t;
  170.                 } else {
  171.                     head = queue.poll();
  172.                     queue.add(t);
  173.                 }
  174.             } else if (RefQueueEntry.compare(t, head) > 0) {
  175.                 queue.add(t);
  176.             } else {
  177.                 queue.add(head);
  178.                 head = t;
  179.             }
  180.         }

  181.         @Override
  182.         public boolean next() throws IOException {
  183.             for (;;) {
  184.                 RefQueueEntry t = poll();
  185.                 if (t == null) {
  186.                     return false;
  187.                 }

  188.                 ref = t.rc.getRef();
  189.                 boolean include = includeDeletes || !t.rc.wasDeleted();
  190.                 add(t);
  191.                 skipShadowedRefs(ref.getName());
  192.                 if (include) {
  193.                     return true;
  194.                 }
  195.             }
  196.         }

  197.         @Override
  198.         public void seekPastPrefix(String prefixName) throws IOException {
  199.             List<RefQueueEntry> entriesToAdd = new ArrayList<>();
  200.             entriesToAdd.addAll(queue);
  201.             if (head != null) {
  202.                 entriesToAdd.add(head);
  203.             }

  204.             head = null;
  205.             queue.clear();

  206.             for(RefQueueEntry entry : entriesToAdd){
  207.                 entry.rc.seekPastPrefix(prefixName);
  208.                 add(entry);
  209.             }
  210.         }

  211.         private RefQueueEntry poll() {
  212.             RefQueueEntry e = head;
  213.             if (e != null) {
  214.                 head = null;
  215.                 return e;
  216.             }
  217.             return queue.poll();
  218.         }

  219.         private void skipShadowedRefs(String name) throws IOException {
  220.             for (;;) {
  221.                 RefQueueEntry t = head != null ? head : queue.peek();
  222.                 if (t != null && name.equals(t.name())) {
  223.                     add(poll());
  224.                 } else {
  225.                     break;
  226.                 }
  227.             }
  228.         }

  229.         @Override
  230.         public Ref getRef() {
  231.             return ref;
  232.         }

  233.         @Override
  234.         public void close() {
  235.             if (head != null) {
  236.                 head.rc.close();
  237.                 head = null;
  238.             }
  239.             while (!queue.isEmpty()) {
  240.                 queue.remove().rc.close();
  241.             }
  242.         }
  243.     }

  244.     private class FilteringMergedRefCursor extends MergedRefCursor {
  245.         final AnyObjectId filterId;
  246.         Ref filteredRef;

  247.         FilteringMergedRefCursor(AnyObjectId id) {
  248.             filterId = id;
  249.             filteredRef = null;
  250.         }

  251.         @Override
  252.         public Ref getRef() {
  253.             return filteredRef;
  254.         }

  255.         @Override
  256.         public boolean next() throws IOException {
  257.             for (;;) {
  258.                 boolean ok = super.next();
  259.                 if (!ok) {
  260.                     return false;
  261.                 }

  262.                 String name = super.getRef().getName();

  263.                 try (RefCursor c = seekRef(name)) {
  264.                     if (c.next()) {
  265.                         if (filterId.equals(c.getRef().getObjectId())) {
  266.                             filteredRef = c.getRef();
  267.                             return true;
  268.                         }
  269.                     }
  270.                 }
  271.             }
  272.         }
  273.     }

  274.     private static class RefQueueEntry {
  275.         static int compare(RefQueueEntry a, RefQueueEntry b) {
  276.             int cmp = a.name().compareTo(b.name());
  277.             if (cmp == 0) {
  278.                 // higher updateIndex shadows lower updateIndex.
  279.                 cmp = Long.signum(b.updateIndex() - a.updateIndex());
  280.             }
  281.             if (cmp == 0) {
  282.                 // higher index shadows lower index, so higher index first.
  283.                 cmp = b.stackIdx - a.stackIdx;
  284.             }
  285.             return cmp;
  286.         }

  287.         final RefCursor rc;
  288.         final int stackIdx;

  289.         RefQueueEntry(RefCursor rc, int stackIdx) {
  290.             this.rc = rc;
  291.             this.stackIdx = stackIdx;
  292.         }

  293.         String name() {
  294.             return rc.getRef().getName();
  295.         }

  296.         long updateIndex() {
  297.             return rc.getRef().getUpdateIndex();
  298.         }
  299.     }

  300.     private class MergedLogCursor extends LogCursor {
  301.         private final PriorityQueue<LogQueueEntry> queue;
  302.         private String refName;
  303.         private long updateIndex;
  304.         private ReflogEntry entry;

  305.         MergedLogCursor() {
  306.             queue = new PriorityQueue<>(queueSize(), LogQueueEntry::compare);
  307.         }

  308.         void add(LogQueueEntry t) throws IOException {
  309.             if (t.lc.next()) {
  310.                 queue.add(t);
  311.             } else {
  312.                 t.lc.close();
  313.             }
  314.         }

  315.         @Override
  316.         public boolean next() throws IOException {
  317.             for (;;) {
  318.                 LogQueueEntry t = queue.poll();
  319.                 if (t == null) {
  320.                     return false;
  321.                 }

  322.                 refName = t.lc.getRefName();
  323.                 updateIndex = t.lc.getUpdateIndex();
  324.                 entry = t.lc.getReflogEntry();
  325.                 boolean include = includeDeletes || entry != null;
  326.                 skipShadowed(refName, updateIndex);
  327.                 add(t);
  328.                 if (include) {
  329.                     return true;
  330.                 }
  331.             }
  332.         }

  333.         private void skipShadowed(String name, long index) throws IOException {
  334.             for (;;) {
  335.                 LogQueueEntry t = queue.peek();
  336.                 if (t != null && name.equals(t.name()) && index == t.index()) {
  337.                     add(queue.remove());
  338.                 } else {
  339.                     break;
  340.                 }
  341.             }
  342.         }

  343.         @Override
  344.         public String getRefName() {
  345.             return refName;
  346.         }

  347.         @Override
  348.         public long getUpdateIndex() {
  349.             return updateIndex;
  350.         }

  351.         @Override
  352.         public ReflogEntry getReflogEntry() {
  353.             return entry;
  354.         }

  355.         @Override
  356.         public void close() {
  357.             while (!queue.isEmpty()) {
  358.                 queue.remove().lc.close();
  359.             }
  360.         }
  361.     }

  362.     private static class LogQueueEntry {
  363.         static int compare(LogQueueEntry a, LogQueueEntry b) {
  364.             int cmp = a.name().compareTo(b.name());
  365.             if (cmp == 0) {
  366.                 // higher update index sorts first.
  367.                 cmp = Long.signum(b.index() - a.index());
  368.             }
  369.             if (cmp == 0) {
  370.                 // higher index comes first.
  371.                 cmp = b.stackIdx - a.stackIdx;
  372.             }
  373.             return cmp;
  374.         }

  375.         final LogCursor lc;
  376.         final int stackIdx;

  377.         LogQueueEntry(LogCursor lc, int stackIdx) {
  378.             this.lc = lc;
  379.             this.stackIdx = stackIdx;
  380.         }

  381.         String name() {
  382.             return lc.getRefName();
  383.         }

  384.         long index() {
  385.             return lc.getUpdateIndex();
  386.         }
  387.     }
  388. }