You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BundleWriter.java 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  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. }