DfsGarbageCollector.java

  1. /*
  2.  * Copyright (C) 2011, 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.dfs;

  11. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.COMPACT;
  12. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC;
  13. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_REST;
  14. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.INSERT;
  15. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.RECEIVE;
  16. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
  17. import static org.eclipse.jgit.internal.storage.dfs.DfsPackCompactor.configureReftable;
  18. import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
  19. import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
  20. import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
  21. import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
  22. import static org.eclipse.jgit.internal.storage.pack.PackWriter.NONE;

  23. import java.io.IOException;
  24. import java.util.ArrayList;
  25. import java.util.Arrays;
  26. import java.util.Calendar;
  27. import java.util.Collection;
  28. import java.util.EnumSet;
  29. import java.util.GregorianCalendar;
  30. import java.util.HashSet;
  31. import java.util.List;
  32. import java.util.Set;
  33. import java.util.concurrent.TimeUnit;

  34. import org.eclipse.jgit.internal.JGitText;
  35. import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
  36. import org.eclipse.jgit.internal.storage.file.PackIndex;
  37. import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
  38. import org.eclipse.jgit.internal.storage.pack.PackExt;
  39. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  40. import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor;
  41. import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
  42. import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
  43. import org.eclipse.jgit.lib.AnyObjectId;
  44. import org.eclipse.jgit.lib.Constants;
  45. import org.eclipse.jgit.lib.NullProgressMonitor;
  46. import org.eclipse.jgit.lib.ObjectId;
  47. import org.eclipse.jgit.lib.ObjectIdSet;
  48. import org.eclipse.jgit.lib.ProgressMonitor;
  49. import org.eclipse.jgit.lib.Ref;
  50. import org.eclipse.jgit.lib.RefDatabase;
  51. import org.eclipse.jgit.revwalk.RevWalk;
  52. import org.eclipse.jgit.storage.pack.PackConfig;
  53. import org.eclipse.jgit.storage.pack.PackStatistics;
  54. import org.eclipse.jgit.util.SystemReader;
  55. import org.eclipse.jgit.util.io.CountingOutputStream;

  56. /**
  57.  * Repack and garbage collect a repository.
  58.  */
  59. public class DfsGarbageCollector {
  60.     private final DfsRepository repo;
  61.     private final RefDatabase refdb;
  62.     private final DfsObjDatabase objdb;

  63.     private final List<DfsPackDescription> newPackDesc;
  64.     private final List<PackStatistics> newPackStats;
  65.     private final List<ObjectIdSet> newPackObj;

  66.     private DfsReader ctx;

  67.     private PackConfig packConfig;
  68.     private ReftableConfig reftableConfig;
  69.     private boolean convertToReftable = true;
  70.     private boolean includeDeletes;
  71.     private long reftableInitialMinUpdateIndex = 1;
  72.     private long reftableInitialMaxUpdateIndex = 1;

  73.     // See packIsCoalesceableGarbage(), below, for how these two variables
  74.     // interact.
  75.     private long coalesceGarbageLimit = 50 << 20;
  76.     private long garbageTtlMillis = TimeUnit.DAYS.toMillis(1);

  77.     private long startTimeMillis;
  78.     private List<DfsPackFile> packsBefore;
  79.     private List<DfsReftable> reftablesBefore;
  80.     private List<DfsPackFile> expiredGarbagePacks;

  81.     private Collection<Ref> refsBefore;
  82.     private Set<ObjectId> allHeadsAndTags;
  83.     private Set<ObjectId> allTags;
  84.     private Set<ObjectId> nonHeads;
  85.     private Set<ObjectId> tagTargets;

  86.     /**
  87.      * Initialize a garbage collector.
  88.      *
  89.      * @param repository
  90.      *            repository objects to be packed will be read from.
  91.      */
  92.     public DfsGarbageCollector(DfsRepository repository) {
  93.         repo = repository;
  94.         refdb = repo.getRefDatabase();
  95.         objdb = repo.getObjectDatabase();
  96.         newPackDesc = new ArrayList<>(4);
  97.         newPackStats = new ArrayList<>(4);
  98.         newPackObj = new ArrayList<>(4);

  99.         packConfig = new PackConfig(repo);
  100.         packConfig.setIndexVersion(2);
  101.     }

  102.     /**
  103.      * Get configuration used to generate the new pack file.
  104.      *
  105.      * @return configuration used to generate the new pack file.
  106.      */
  107.     public PackConfig getPackConfig() {
  108.         return packConfig;
  109.     }

  110.     /**
  111.      * Set the new configuration to use when creating the pack file.
  112.      *
  113.      * @param newConfig
  114.      *            the new configuration to use when creating the pack file.
  115.      * @return {@code this}
  116.      */
  117.     public DfsGarbageCollector setPackConfig(PackConfig newConfig) {
  118.         packConfig = newConfig;
  119.         return this;
  120.     }

  121.     /**
  122.      * Set configuration to write a reftable.
  123.      *
  124.      * @param cfg
  125.      *            configuration to write a reftable. Reftable writing is
  126.      *            disabled (default) when {@code cfg} is {@code null}.
  127.      * @return {@code this}
  128.      */
  129.     public DfsGarbageCollector setReftableConfig(ReftableConfig cfg) {
  130.         reftableConfig = cfg;
  131.         return this;
  132.     }

  133.     /**
  134.      * Whether the garbage collector should convert references to reftable.
  135.      *
  136.      * @param convert
  137.      *            if {@code true}, {@link #setReftableConfig(ReftableConfig)}
  138.      *            has been set non-null, and a GC reftable doesn't yet exist,
  139.      *            the garbage collector will make one by scanning the existing
  140.      *            references, and writing a new reftable. Default is
  141.      *            {@code true}.
  142.      * @return {@code this}
  143.      */
  144.     public DfsGarbageCollector setConvertToReftable(boolean convert) {
  145.         convertToReftable = convert;
  146.         return this;
  147.     }

  148.     /**
  149.      * Whether the garbage collector will include tombstones for deleted
  150.      * references in the reftable.
  151.      *
  152.      * @param include
  153.      *            if {@code true}, the garbage collector will include tombstones
  154.      *            for deleted references in the reftable. Default is
  155.      *            {@code false}.
  156.      * @return {@code this}
  157.      */
  158.     public DfsGarbageCollector setIncludeDeletes(boolean include) {
  159.         includeDeletes = include;
  160.         return this;
  161.     }

  162.     /**
  163.      * Set minUpdateIndex for the initial reftable created during conversion.
  164.      *
  165.      * @param u
  166.      *            minUpdateIndex for the initial reftable created by scanning
  167.      *            {@link org.eclipse.jgit.internal.storage.dfs.DfsRefDatabase#getRefs(String)}.
  168.      *            Ignored unless caller has also set
  169.      *            {@link #setReftableConfig(ReftableConfig)}. Defaults to
  170.      *            {@code 1}. Must be {@code u >= 0}.
  171.      * @return {@code this}
  172.      */
  173.     public DfsGarbageCollector setReftableInitialMinUpdateIndex(long u) {
  174.         reftableInitialMinUpdateIndex = Math.max(u, 0);
  175.         return this;
  176.     }

  177.     /**
  178.      * Set maxUpdateIndex for the initial reftable created during conversion.
  179.      *
  180.      * @param u
  181.      *            maxUpdateIndex for the initial reftable created by scanning
  182.      *            {@link org.eclipse.jgit.internal.storage.dfs.DfsRefDatabase#getRefs(String)}.
  183.      *            Ignored unless caller has also set
  184.      *            {@link #setReftableConfig(ReftableConfig)}. Defaults to
  185.      *            {@code 1}. Must be {@code u >= 0}.
  186.      * @return {@code this}
  187.      */
  188.     public DfsGarbageCollector setReftableInitialMaxUpdateIndex(long u) {
  189.         reftableInitialMaxUpdateIndex = Math.max(0, u);
  190.         return this;
  191.     }

  192.     /**
  193.      * Get coalesce garbage limit
  194.      *
  195.      * @return coalesce garbage limit, packs smaller than this size will be
  196.      *         repacked.
  197.      */
  198.     public long getCoalesceGarbageLimit() {
  199.         return coalesceGarbageLimit;
  200.     }

  201.     /**
  202.      * Set the byte size limit for garbage packs to be repacked.
  203.      * <p>
  204.      * Any UNREACHABLE_GARBAGE pack smaller than this limit will be repacked at
  205.      * the end of the run. This allows the garbage collector to coalesce
  206.      * unreachable objects into a single file.
  207.      * <p>
  208.      * If an UNREACHABLE_GARBAGE pack is already larger than this limit it will
  209.      * be left alone by the garbage collector. This avoids unnecessary disk IO
  210.      * reading and copying the objects.
  211.      * <p>
  212.      * If limit is set to 0 the UNREACHABLE_GARBAGE coalesce is disabled.<br>
  213.      * If limit is set to {@link java.lang.Long#MAX_VALUE}, everything is
  214.      * coalesced.
  215.      * <p>
  216.      * Keeping unreachable garbage prevents race conditions with repository
  217.      * changes that may suddenly need an object whose only copy was stored in
  218.      * the UNREACHABLE_GARBAGE pack.
  219.      *
  220.      * @param limit
  221.      *            size in bytes.
  222.      * @return {@code this}
  223.      */
  224.     public DfsGarbageCollector setCoalesceGarbageLimit(long limit) {
  225.         coalesceGarbageLimit = limit;
  226.         return this;
  227.     }

  228.     /**
  229.      * Get time to live for garbage packs.
  230.      *
  231.      * @return garbage packs older than this limit (in milliseconds) will be
  232.      *         pruned as part of the garbage collection process if the value is
  233.      *         &gt; 0, otherwise garbage packs are retained.
  234.      */
  235.     public long getGarbageTtlMillis() {
  236.         return garbageTtlMillis;
  237.     }

  238.     /**
  239.      * Set the time to live for garbage objects.
  240.      * <p>
  241.      * Any UNREACHABLE_GARBAGE older than this limit will be pruned at the end
  242.      * of the run.
  243.      * <p>
  244.      * If timeToLiveMillis is set to 0, UNREACHABLE_GARBAGE purging is disabled.
  245.      *
  246.      * @param ttl
  247.      *            Time to live whatever unit is specified.
  248.      * @param unit
  249.      *            The specified time unit.
  250.      * @return {@code this}
  251.      */
  252.     public DfsGarbageCollector setGarbageTtl(long ttl, TimeUnit unit) {
  253.         garbageTtlMillis = unit.toMillis(ttl);
  254.         return this;
  255.     }

  256.     /**
  257.      * Create a single new pack file containing all of the live objects.
  258.      * <p>
  259.      * This method safely decides which packs can be expired after the new pack
  260.      * is created by validating the references have not been modified in an
  261.      * incompatible way.
  262.      *
  263.      * @param pm
  264.      *            progress monitor to receive updates on as packing may take a
  265.      *            while, depending on the size of the repository.
  266.      * @return true if the repack was successful without race conditions. False
  267.      *         if a race condition was detected and the repack should be run
  268.      *         again later.
  269.      * @throws java.io.IOException
  270.      *             a new pack cannot be created.
  271.      */
  272.     public boolean pack(ProgressMonitor pm) throws IOException {
  273.         if (pm == null)
  274.             pm = NullProgressMonitor.INSTANCE;
  275.         if (packConfig.getIndexVersion() != 2)
  276.             throw new IllegalStateException(
  277.                     JGitText.get().supportOnlyPackIndexVersion2);

  278.         startTimeMillis = SystemReader.getInstance().getCurrentTime();
  279.         ctx = objdb.newReader();
  280.         try {
  281.             refdb.refresh();
  282.             objdb.clearCache();

  283.             refsBefore = getAllRefs();
  284.             readPacksBefore();
  285.             readReftablesBefore();

  286.             Set<ObjectId> allHeads = new HashSet<>();
  287.             allHeadsAndTags = new HashSet<>();
  288.             allTags = new HashSet<>();
  289.             nonHeads = new HashSet<>();
  290.             tagTargets = new HashSet<>();
  291.             for (Ref ref : refsBefore) {
  292.                 if (ref.isSymbolic() || ref.getObjectId() == null) {
  293.                     continue;
  294.                 }
  295.                 if (isHead(ref)) {
  296.                     allHeads.add(ref.getObjectId());
  297.                 } else if (isTag(ref)) {
  298.                     allTags.add(ref.getObjectId());
  299.                 } else {
  300.                     nonHeads.add(ref.getObjectId());
  301.                 }
  302.                 if (ref.getPeeledObjectId() != null) {
  303.                     tagTargets.add(ref.getPeeledObjectId());
  304.                 }
  305.             }
  306.             // Don't exclude tags that are also branch tips.
  307.             allTags.removeAll(allHeads);
  308.             allHeadsAndTags.addAll(allHeads);
  309.             allHeadsAndTags.addAll(allTags);

  310.             // Hoist all branch tips and tags earlier in the pack file
  311.             tagTargets.addAll(allHeadsAndTags);

  312.             // Combine the GC_REST objects into the GC pack if requested
  313.             if (packConfig.getSinglePack()) {
  314.                 allHeadsAndTags.addAll(nonHeads);
  315.                 nonHeads.clear();
  316.             }

  317.             boolean rollback = true;
  318.             try {
  319.                 packHeads(pm);
  320.                 packRest(pm);
  321.                 packGarbage(pm);
  322.                 objdb.commitPack(newPackDesc, toPrune());
  323.                 rollback = false;
  324.                 return true;
  325.             } finally {
  326.                 if (rollback)
  327.                     objdb.rollbackPack(newPackDesc);
  328.             }
  329.         } finally {
  330.             ctx.close();
  331.         }
  332.     }

  333.     private Collection<Ref> getAllRefs() throws IOException {
  334.         Collection<Ref> refs = refdb.getRefs();
  335.         List<Ref> addl = refdb.getAdditionalRefs();
  336.         if (!addl.isEmpty()) {
  337.             List<Ref> all = new ArrayList<>(refs.size() + addl.size());
  338.             all.addAll(refs);
  339.             // add additional refs which start with refs/
  340.             for (Ref r : addl) {
  341.                 if (r.getName().startsWith(Constants.R_REFS)) {
  342.                     all.add(r);
  343.                 }
  344.             }
  345.             return all;
  346.         }
  347.         return refs;
  348.     }

  349.     private void readPacksBefore() throws IOException {
  350.         DfsPackFile[] packs = objdb.getPacks();
  351.         packsBefore = new ArrayList<>(packs.length);
  352.         expiredGarbagePacks = new ArrayList<>(packs.length);

  353.         long now = SystemReader.getInstance().getCurrentTime();
  354.         for (DfsPackFile p : packs) {
  355.             DfsPackDescription d = p.getPackDescription();
  356.             if (d.getPackSource() != UNREACHABLE_GARBAGE) {
  357.                 packsBefore.add(p);
  358.             } else if (packIsExpiredGarbage(d, now)) {
  359.                 expiredGarbagePacks.add(p);
  360.             } else if (packIsCoalesceableGarbage(d, now)) {
  361.                 packsBefore.add(p);
  362.             }
  363.         }
  364.     }

  365.     private void readReftablesBefore() throws IOException {
  366.         DfsReftable[] tables = objdb.getReftables();
  367.         reftablesBefore = new ArrayList<>(Arrays.asList(tables));
  368.     }

  369.     private boolean packIsExpiredGarbage(DfsPackDescription d, long now) {
  370.         // Consider the garbage pack as expired when it's older than
  371.         // garbagePackTtl. This check gives concurrent inserter threads
  372.         // sufficient time to identify an object is not in the graph and should
  373.         // have a new copy written, rather than relying on something from an
  374.         // UNREACHABLE_GARBAGE pack.
  375.         return d.getPackSource() == UNREACHABLE_GARBAGE
  376.                 && garbageTtlMillis > 0
  377.                 && now - d.getLastModified() >= garbageTtlMillis;
  378.     }

  379.     private boolean packIsCoalesceableGarbage(DfsPackDescription d, long now) {
  380.         // An UNREACHABLE_GARBAGE pack can be coalesced if its size is less than
  381.         // the coalesceGarbageLimit and either garbageTtl is zero or if the pack
  382.         // is created in a close time interval (on a single calendar day when
  383.         // the garbageTtl is more than one day or one third of the garbageTtl).
  384.         //
  385.         // When the garbageTtl is more than 24 hours, garbage packs that are
  386.         // created within a single calendar day are coalesced together. This
  387.         // would make the effective ttl of the garbage pack as garbageTtl+23:59
  388.         // and limit the number of garbage to a maximum number of
  389.         // garbageTtl_in_days + 1 (assuming all of them are less than the size
  390.         // of coalesceGarbageLimit).
  391.         //
  392.         // When the garbageTtl is less than or equal to 24 hours, garbage packs
  393.         // that are created within a one third of garbageTtl are coalesced
  394.         // together. This would make the effective ttl of the garbage packs as
  395.         // garbageTtl + (garbageTtl / 3) and would limit the number of garbage
  396.         // packs to a maximum number of 4 (assuming all of them are less than
  397.         // the size of coalesceGarbageLimit).

  398.         if (d.getPackSource() != UNREACHABLE_GARBAGE
  399.                 || d.getFileSize(PackExt.PACK) >= coalesceGarbageLimit) {
  400.             return false;
  401.         }

  402.         if (garbageTtlMillis == 0) {
  403.             return true;
  404.         }

  405.         long lastModified = d.getLastModified();
  406.         long dayStartLastModified = dayStartInMillis(lastModified);
  407.         long dayStartToday = dayStartInMillis(now);

  408.         if (dayStartLastModified != dayStartToday) {
  409.             return false; // this pack is not created today.
  410.         }

  411.         if (garbageTtlMillis > TimeUnit.DAYS.toMillis(1)) {
  412.             return true; // ttl is more than one day and pack is created today.
  413.         }

  414.         long timeInterval = garbageTtlMillis / 3;
  415.         if (timeInterval == 0) {
  416.             return false; // ttl is too small, don't try to coalesce.
  417.         }

  418.         long modifiedTimeSlot = (lastModified - dayStartLastModified) / timeInterval;
  419.         long presentTimeSlot = (now - dayStartToday) / timeInterval;
  420.         return modifiedTimeSlot == presentTimeSlot;
  421.     }

  422.     private static long dayStartInMillis(long timeInMillis) {
  423.         Calendar cal = new GregorianCalendar(
  424.                 SystemReader.getInstance().getTimeZone());
  425.         cal.setTimeInMillis(timeInMillis);
  426.         cal.set(Calendar.HOUR_OF_DAY, 0);
  427.         cal.set(Calendar.MINUTE, 0);
  428.         cal.set(Calendar.SECOND, 0);
  429.         cal.set(Calendar.MILLISECOND, 0);
  430.         return cal.getTimeInMillis();
  431.     }

  432.     /**
  433.      * Get all of the source packs that fed into this compaction.
  434.      *
  435.      * @return all of the source packs that fed into this compaction.
  436.      */
  437.     public Set<DfsPackDescription> getSourcePacks() {
  438.         return toPrune();
  439.     }

  440.     /**
  441.      * Get new packs created by this compaction.
  442.      *
  443.      * @return new packs created by this compaction.
  444.      */
  445.     public List<DfsPackDescription> getNewPacks() {
  446.         return newPackDesc;
  447.     }

  448.     /**
  449.      * Get statistics corresponding to the {@link #getNewPacks()}.
  450.      * <p>
  451.      * The elements can be null if the stat is not available for the pack file.
  452.      *
  453.      * @return statistics corresponding to the {@link #getNewPacks()}.
  454.      */
  455.     public List<PackStatistics> getNewPackStatistics() {
  456.         return newPackStats;
  457.     }

  458.     private Set<DfsPackDescription> toPrune() {
  459.         Set<DfsPackDescription> toPrune = new HashSet<>();
  460.         for (DfsPackFile pack : packsBefore) {
  461.             toPrune.add(pack.getPackDescription());
  462.         }
  463.         if (reftableConfig != null) {
  464.             for (DfsReftable table : reftablesBefore) {
  465.                 toPrune.add(table.getPackDescription());
  466.             }
  467.         }
  468.         for (DfsPackFile pack : expiredGarbagePacks) {
  469.             toPrune.add(pack.getPackDescription());
  470.         }
  471.         return toPrune;
  472.     }

  473.     private void packHeads(ProgressMonitor pm) throws IOException {
  474.         if (allHeadsAndTags.isEmpty()) {
  475.             writeReftable();
  476.             return;
  477.         }

  478.         try (PackWriter pw = newPackWriter()) {
  479.             pw.setTagTargets(tagTargets);
  480.             pw.preparePack(pm, allHeadsAndTags, NONE, NONE, allTags);
  481.             if (0 < pw.getObjectCount()) {
  482.                 long estSize = estimateGcPackSize(INSERT, RECEIVE, COMPACT, GC);
  483.                 writePack(GC, pw, pm, estSize);
  484.             } else {
  485.                 writeReftable();
  486.             }
  487.         }
  488.     }

  489.     private void packRest(ProgressMonitor pm) throws IOException {
  490.         if (nonHeads.isEmpty())
  491.             return;

  492.         try (PackWriter pw = newPackWriter()) {
  493.             for (ObjectIdSet packedObjs : newPackObj)
  494.                 pw.excludeObjects(packedObjs);
  495.             pw.preparePack(pm, nonHeads, allHeadsAndTags);
  496.             if (0 < pw.getObjectCount())
  497.                 writePack(GC_REST, pw, pm,
  498.                         estimateGcPackSize(INSERT, RECEIVE, COMPACT, GC_REST));
  499.         }
  500.     }

  501.     private void packGarbage(ProgressMonitor pm) throws IOException {
  502.         PackConfig cfg = new PackConfig(packConfig);
  503.         cfg.setReuseDeltas(true);
  504.         cfg.setReuseObjects(true);
  505.         cfg.setDeltaCompress(false);
  506.         cfg.setBuildBitmaps(false);

  507.         try (PackWriter pw = new PackWriter(cfg, ctx);
  508.                 RevWalk pool = new RevWalk(ctx)) {
  509.             pw.setDeltaBaseAsOffset(true);
  510.             pw.setReuseDeltaCommits(true);
  511.             pm.beginTask(JGitText.get().findingGarbage, objectsBefore());
  512.             long estimatedPackSize = 12 + 20; // header and trailer sizes.
  513.             for (DfsPackFile oldPack : packsBefore) {
  514.                 PackIndex oldIdx = oldPack.getPackIndex(ctx);
  515.                 PackReverseIndex oldRevIdx = oldPack.getReverseIdx(ctx);
  516.                 long maxOffset = oldPack.getPackDescription().getFileSize(PACK)
  517.                         - 20; // pack size - trailer size.
  518.                 for (PackIndex.MutableEntry ent : oldIdx) {
  519.                     pm.update(1);
  520.                     ObjectId id = ent.toObjectId();
  521.                     if (pool.lookupOrNull(id) != null || anyPackHas(id))
  522.                         continue;

  523.                     long offset = ent.getOffset();
  524.                     int type = oldPack.getObjectType(ctx, offset);
  525.                     pw.addObject(pool.lookupAny(id, type));
  526.                     long objSize = oldRevIdx.findNextOffset(offset, maxOffset)
  527.                             - offset;
  528.                     estimatedPackSize += objSize;
  529.                 }
  530.             }
  531.             pm.endTask();
  532.             if (0 < pw.getObjectCount())
  533.                 writePack(UNREACHABLE_GARBAGE, pw, pm, estimatedPackSize);
  534.         }
  535.     }

  536.     private boolean anyPackHas(AnyObjectId id) {
  537.         for (ObjectIdSet packedObjs : newPackObj)
  538.             if (packedObjs.contains(id))
  539.                 return true;
  540.         return false;
  541.     }

  542.     private static boolean isHead(Ref ref) {
  543.         return ref.getName().startsWith(Constants.R_HEADS);
  544.     }

  545.     private static boolean isTag(Ref ref) {
  546.         return ref.getName().startsWith(Constants.R_TAGS);
  547.     }

  548.     private int objectsBefore() {
  549.         int cnt = 0;
  550.         for (DfsPackFile p : packsBefore)
  551.             cnt += (int) p.getPackDescription().getObjectCount();
  552.         return cnt;
  553.     }

  554.     private PackWriter newPackWriter() {
  555.         PackWriter pw = new PackWriter(packConfig, ctx);
  556.         pw.setDeltaBaseAsOffset(true);
  557.         pw.setReuseDeltaCommits(false);
  558.         return pw;
  559.     }

  560.     private long estimateGcPackSize(PackSource first, PackSource... rest) {
  561.         EnumSet<PackSource> sourceSet = EnumSet.of(first, rest);
  562.         // Every pack file contains 12 bytes of header and 20 bytes of trailer.
  563.         // Include the final pack file header and trailer size here and ignore
  564.         // the same from individual pack files.
  565.         long size = 32;
  566.         for (DfsPackDescription pack : getSourcePacks()) {
  567.             if (sourceSet.contains(pack.getPackSource())) {
  568.                 size += pack.getFileSize(PACK) - 32;
  569.             }
  570.         }
  571.         return size;
  572.     }

  573.     private DfsPackDescription writePack(PackSource source, PackWriter pw,
  574.             ProgressMonitor pm, long estimatedPackSize) throws IOException {
  575.         DfsPackDescription pack = repo.getObjectDatabase().newPack(source,
  576.                 estimatedPackSize);

  577.         if (source == GC && reftableConfig != null) {
  578.             writeReftable(pack);
  579.         }

  580.         try (DfsOutputStream out = objdb.writeFile(pack, PACK)) {
  581.             pw.writePack(pm, pm, out);
  582.             pack.addFileExt(PACK);
  583.             pack.setBlockSize(PACK, out.blockSize());
  584.         }

  585.         try (DfsOutputStream out = objdb.writeFile(pack, INDEX)) {
  586.             CountingOutputStream cnt = new CountingOutputStream(out);
  587.             pw.writeIndex(cnt);
  588.             pack.addFileExt(INDEX);
  589.             pack.setFileSize(INDEX, cnt.getCount());
  590.             pack.setBlockSize(INDEX, out.blockSize());
  591.             pack.setIndexVersion(pw.getIndexVersion());
  592.         }

  593.         if (pw.prepareBitmapIndex(pm)) {
  594.             try (DfsOutputStream out = objdb.writeFile(pack, BITMAP_INDEX)) {
  595.                 CountingOutputStream cnt = new CountingOutputStream(out);
  596.                 pw.writeBitmapIndex(cnt);
  597.                 pack.addFileExt(BITMAP_INDEX);
  598.                 pack.setFileSize(BITMAP_INDEX, cnt.getCount());
  599.                 pack.setBlockSize(BITMAP_INDEX, out.blockSize());
  600.             }
  601.         }

  602.         PackStatistics stats = pw.getStatistics();
  603.         pack.setPackStats(stats);
  604.         pack.setLastModified(startTimeMillis);
  605.         newPackDesc.add(pack);
  606.         newPackStats.add(stats);
  607.         newPackObj.add(pw.getObjectSet());
  608.         return pack;
  609.     }

  610.     private void writeReftable() throws IOException {
  611.         if (reftableConfig != null) {
  612.             DfsPackDescription pack = objdb.newPack(GC);
  613.             newPackDesc.add(pack);
  614.             newPackStats.add(null);
  615.             writeReftable(pack);
  616.         }
  617.     }

  618.     private void writeReftable(DfsPackDescription pack) throws IOException {
  619.         if (convertToReftable && !hasGcReftable()) {
  620.             writeReftable(pack, refsBefore);
  621.             return;
  622.         }

  623.         try (DfsReftableStack stack = DfsReftableStack.open(ctx, reftablesBefore);
  624.              DfsOutputStream out = objdb.writeFile(pack, REFTABLE)) {
  625.             ReftableCompactor compact = new ReftableCompactor(out);
  626.             compact.addAll(stack.readers());
  627.             compact.setIncludeDeletes(includeDeletes);
  628.             compact.setConfig(configureReftable(reftableConfig, out));
  629.             compact.compact();
  630.             pack.addFileExt(REFTABLE);
  631.             pack.setReftableStats(compact.getStats());
  632.         }
  633.     }

  634.     private boolean hasGcReftable() {
  635.         for (DfsReftable table : reftablesBefore) {
  636.             if (table.getPackDescription().getPackSource() == GC) {
  637.                 return true;
  638.             }
  639.         }
  640.         return false;
  641.     }

  642.     private void writeReftable(DfsPackDescription pack, Collection<Ref> refs)
  643.             throws IOException {
  644.         try (DfsOutputStream out = objdb.writeFile(pack, REFTABLE)) {
  645.             ReftableConfig cfg = configureReftable(reftableConfig, out);
  646.             ReftableWriter writer = new ReftableWriter(cfg, out)
  647.                     .setMinUpdateIndex(reftableInitialMinUpdateIndex)
  648.                     .setMaxUpdateIndex(reftableInitialMaxUpdateIndex).begin()
  649.                     .sortAndWriteRefs(refs).finish();
  650.             pack.addFileExt(REFTABLE);
  651.             pack.setReftableStats(writer.getStats());
  652.         }
  653.     }
  654. }