BundleWriter.java

  1. /*
  2.  * Copyright (C) 2008-2010, 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.transport;

  11. import static java.nio.charset.StandardCharsets.UTF_8;

  12. import java.io.IOException;
  13. import java.io.OutputStream;
  14. import java.io.OutputStreamWriter;
  15. import java.io.Writer;
  16. import java.text.MessageFormat;
  17. import java.util.ArrayList;
  18. import java.util.Collection;
  19. import java.util.HashSet;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.Set;
  23. import java.util.TreeMap;

  24. import org.eclipse.jgit.internal.JGitText;
  25. import org.eclipse.jgit.internal.storage.pack.CachedPack;
  26. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  27. import org.eclipse.jgit.lib.AnyObjectId;
  28. import org.eclipse.jgit.lib.Constants;
  29. import org.eclipse.jgit.lib.ObjectId;
  30. import org.eclipse.jgit.lib.ObjectReader;
  31. import org.eclipse.jgit.lib.ProgressMonitor;
  32. import org.eclipse.jgit.lib.Ref;
  33. import org.eclipse.jgit.lib.Repository;
  34. import org.eclipse.jgit.revwalk.RevCommit;
  35. import org.eclipse.jgit.storage.pack.PackConfig;

  36. /**
  37.  * Creates a Git bundle file, for sneaker-net transport to another system.
  38.  * <p>
  39.  * Bundles generated by this class can be later read in from a file URI using
  40.  * the bundle transport, or from an application controlled buffer by the more
  41.  * generic {@link org.eclipse.jgit.transport.TransportBundleStream}.
  42.  * <p>
  43.  * Applications creating bundles need to call one or more <code>include</code>
  44.  * calls to reflect which objects should be available as refs in the bundle for
  45.  * the other side to fetch. At least one include is required to create a valid
  46.  * bundle file, and duplicate names are not permitted.
  47.  * <p>
  48.  * Optional <code>assume</code> calls can be made to declare commits which the
  49.  * recipient must have in order to fetch from the bundle file. Objects reachable
  50.  * from these assumed commits can be used as delta bases in order to reduce the
  51.  * overall bundle size.
  52.  */
  53. public class BundleWriter {
  54.     private final Repository db;

  55.     private final ObjectReader reader;

  56.     private final Map<String, ObjectId> include;

  57.     private final Set<RevCommit> assume;

  58.     private final Set<ObjectId> tagTargets;

  59.     private final List<CachedPack> cachedPacks = new ArrayList<>();

  60.     private PackConfig packConfig;

  61.     private ObjectCountCallback callback;

  62.     /**
  63.      * Create a writer for a bundle.
  64.      *
  65.      * @param repo
  66.      *            repository where objects are stored.
  67.      */
  68.     public BundleWriter(Repository repo) {
  69.         db = repo;
  70.         reader = null;
  71.         include = new TreeMap<>();
  72.         assume = new HashSet<>();
  73.         tagTargets = new HashSet<>();
  74.     }

  75.     /**
  76.      * Create a writer for a bundle.
  77.      *
  78.      * @param or
  79.      *            reader for reading objects. Will be closed at the end of {@link
  80.      *            #writeBundle(ProgressMonitor, OutputStream)}, but readers may be
  81.      *            reused after closing.
  82.      * @since 4.8
  83.      */
  84.     public BundleWriter(ObjectReader or) {
  85.         db = null;
  86.         reader = or;
  87.         include = new TreeMap<>();
  88.         assume = new HashSet<>();
  89.         tagTargets = new HashSet<>();
  90.     }

  91.     /**
  92.      * Set the configuration used by the pack generator.
  93.      *
  94.      * @param pc
  95.      *            configuration controlling packing parameters. If null the
  96.      *            source repository's settings will be used, or the default
  97.      *            settings if constructed without a repo.
  98.      */
  99.     public void setPackConfig(PackConfig pc) {
  100.         this.packConfig = pc;
  101.     }

  102.     /**
  103.      * Include an object (and everything reachable from it) in the bundle.
  104.      *
  105.      * @param name
  106.      *            name the recipient can discover this object as from the
  107.      *            bundle's list of advertised refs . The name must be a valid
  108.      *            ref format and must not have already been included in this
  109.      *            bundle writer.
  110.      * @param id
  111.      *            object to pack. Multiple refs may point to the same object.
  112.      */
  113.     public void include(String name, AnyObjectId id) {
  114.         boolean validRefName = Repository.isValidRefName(name) || Constants.HEAD.equals(name);
  115.         if (!validRefName)
  116.             throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidRefName, name));
  117.         if (include.containsKey(name))
  118.             throw new IllegalStateException(JGitText.get().duplicateRef + name);
  119.         include.put(name, id.toObjectId());
  120.     }

  121.     /**
  122.      * Include a single ref (a name/object pair) in the bundle.
  123.      * <p>
  124.      * This is a utility function for:
  125.      * <code>include(r.getName(), r.getObjectId())</code>.
  126.      *
  127.      * @param r
  128.      *            the ref to include.
  129.      */
  130.     public void include(Ref r) {
  131.         include(r.getName(), r.getObjectId());

  132.         if (r.getPeeledObjectId() != null)
  133.             tagTargets.add(r.getPeeledObjectId());

  134.         else if (r.getObjectId() != null
  135.                 && r.getName().startsWith(Constants.R_HEADS))
  136.             tagTargets.add(r.getObjectId());
  137.     }

  138.     /**
  139.      * Add objects to the bundle file.
  140.      *
  141.      * <p>
  142.      * When this method is used, object traversal is disabled and specified pack
  143.      * files are directly saved to the Git bundle file.
  144.      *
  145.      * <p>
  146.      * Unlike {@link #include}, this doesn't affect the refs. Even if the
  147.      * objects are not reachable from any ref, they will be included in the
  148.      * bundle file.
  149.      *
  150.      * @param c
  151.      *            pack to include
  152.      * @since 5.9
  153.      */
  154.     public void addObjectsAsIs(Collection<? extends CachedPack> c) {
  155.         cachedPacks.addAll(c);
  156.     }

  157.     /**
  158.      * Assume a commit is available on the recipient's side.
  159.      * <p>
  160.      * In order to fetch from a bundle the recipient must have any assumed
  161.      * commit. Each assumed commit is explicitly recorded in the bundle header
  162.      * to permit the recipient to validate it has these objects.
  163.      *
  164.      * @param c
  165.      *            the commit to assume being available. This commit should be
  166.      *            parsed and not disposed in order to maximize the amount of
  167.      *            debugging information available in the bundle stream.
  168.      */
  169.     public void assume(RevCommit c) {
  170.         if (c != null)
  171.             assume.add(c);
  172.     }

  173.     /**
  174.      * Generate and write the bundle to the output stream.
  175.      * <p>
  176.      * This method can only be called once per BundleWriter instance.
  177.      *
  178.      * @param monitor
  179.      *            progress monitor to report bundle writing status to.
  180.      * @param os
  181.      *            the stream the bundle is written to. The stream should be
  182.      *            buffered by the caller. The caller is responsible for closing
  183.      *            the stream.
  184.      * @throws java.io.IOException
  185.      *             an error occurred reading a local object's data to include in
  186.      *             the bundle, or writing compressed object data to the output
  187.      *             stream.
  188.      */
  189.     public void writeBundle(ProgressMonitor monitor, OutputStream os)
  190.             throws IOException {
  191.         try (PackWriter packWriter = newPackWriter()) {
  192.             packWriter.setObjectCountCallback(callback);

  193.             packWriter.setIndexDisabled(true);
  194.             packWriter.setDeltaBaseAsOffset(true);
  195.             packWriter.setReuseValidatingObjects(false);
  196.             if (cachedPacks.isEmpty()) {
  197.                 HashSet<ObjectId> inc = new HashSet<>();
  198.                 HashSet<ObjectId> exc = new HashSet<>();
  199.                 inc.addAll(include.values());
  200.                 for (RevCommit r : assume) {
  201.                     exc.add(r.getId());
  202.                 }
  203.                 if (exc.isEmpty()) {
  204.                     packWriter.setTagTargets(tagTargets);
  205.                 }
  206.                 packWriter.setThin(!exc.isEmpty());
  207.                 packWriter.preparePack(monitor, inc, exc);
  208.             } else {
  209.                 packWriter.preparePack(cachedPacks);
  210.             }

  211.             final Writer w = new OutputStreamWriter(os, UTF_8);
  212.             w.write(TransportBundle.V2_BUNDLE_SIGNATURE);
  213.             w.write('\n');

  214.             final char[] tmp = new char[Constants.OBJECT_ID_STRING_LENGTH];
  215.             for (RevCommit a : assume) {
  216.                 w.write('-');
  217.                 a.copyTo(tmp, w);
  218.                 if (a.getRawBuffer() != null) {
  219.                     w.write(' ');
  220.                     w.write(a.getShortMessage());
  221.                 }
  222.                 w.write('\n');
  223.             }
  224.             for (Map.Entry<String, ObjectId> e : include.entrySet()) {
  225.                 e.getValue().copyTo(tmp, w);
  226.                 w.write(' ');
  227.                 w.write(e.getKey());
  228.                 w.write('\n');
  229.             }

  230.             w.write('\n');
  231.             w.flush();
  232.             packWriter.writePack(monitor, monitor, os);
  233.         }
  234.     }

  235.     private PackWriter newPackWriter() {
  236.         PackConfig pc = packConfig;
  237.         if (pc == null) {
  238.             pc = db != null ? new PackConfig(db) : new PackConfig();
  239.         }
  240.         return new PackWriter(pc, reader != null ? reader : db.newObjectReader());
  241.     }

  242.     /**
  243.      * Set the {@link org.eclipse.jgit.transport.ObjectCountCallback}.
  244.      * <p>
  245.      * It should be set before calling
  246.      * {@link #writeBundle(ProgressMonitor, OutputStream)}.
  247.      * <p>
  248.      * This callback will be passed on to
  249.      * {@link org.eclipse.jgit.internal.storage.pack.PackWriter#setObjectCountCallback}.
  250.      *
  251.      * @param callback
  252.      *            the callback to set
  253.      * @return this object for chaining.
  254.      * @since 4.1
  255.      */
  256.     public BundleWriter setObjectCountCallback(ObjectCountCallback callback) {
  257.         this.callback = callback;
  258.         return this;
  259.     }
  260. }