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.

BundleWriterTest.java 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. /*
  2. * Copyright (C) 2008-2009, Google Inc.
  3. * Copyright (C) 2008, Mike Ralphson <mike@abacus.co.uk>
  4. * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> and others
  5. *
  6. * This program and the accompanying materials are made available under the
  7. * terms of the Eclipse Distribution License v. 1.0 which is available at
  8. * https://www.eclipse.org/org/documents/edl-v10.php.
  9. *
  10. * SPDX-License-Identifier: BSD-3-Clause
  11. */
  12. package org.eclipse.jgit.transport;
  13. import static java.nio.charset.StandardCharsets.UTF_8;
  14. import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
  15. import static org.junit.Assert.assertEquals;
  16. import static org.junit.Assert.assertNotNull;
  17. import static org.junit.Assert.assertNull;
  18. import static org.junit.Assert.assertThrows;
  19. import static org.junit.Assert.assertTrue;
  20. import static org.junit.Assert.fail;
  21. import java.io.ByteArrayInputStream;
  22. import java.io.ByteArrayOutputStream;
  23. import java.io.FileNotFoundException;
  24. import java.io.IOException;
  25. import java.net.URISyntaxException;
  26. import java.util.Collections;
  27. import java.util.Set;
  28. import org.eclipse.jgit.errors.MissingBundlePrerequisiteException;
  29. import org.eclipse.jgit.errors.MissingObjectException;
  30. import org.eclipse.jgit.errors.NotSupportedException;
  31. import org.eclipse.jgit.errors.TransportException;
  32. import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
  33. import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
  34. import org.eclipse.jgit.lib.Constants;
  35. import org.eclipse.jgit.lib.NullProgressMonitor;
  36. import org.eclipse.jgit.lib.ObjectId;
  37. import org.eclipse.jgit.lib.ObjectInserter;
  38. import org.eclipse.jgit.lib.ObjectReader;
  39. import org.eclipse.jgit.lib.Ref;
  40. import org.eclipse.jgit.lib.Repository;
  41. import org.eclipse.jgit.revwalk.RevCommit;
  42. import org.eclipse.jgit.revwalk.RevWalk;
  43. import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
  44. import org.junit.Test;
  45. public class BundleWriterTest extends SampleDataRepositoryTestCase {
  46. @Test
  47. public void testEmptyBundleFails() throws Exception {
  48. Repository newRepo = createBareRepository();
  49. assertThrows(TransportException.class,
  50. () -> fetchFromBundle(newRepo, new byte[0]));
  51. }
  52. @Test
  53. public void testNonBundleFails() throws Exception {
  54. Repository newRepo = createBareRepository();
  55. assertThrows(TransportException.class, () -> fetchFromBundle(newRepo,
  56. "Not a bundle file".getBytes(UTF_8)));
  57. }
  58. @Test
  59. public void testGarbageBundleFails() throws Exception {
  60. Repository newRepo = createBareRepository();
  61. assertThrows(TransportException.class, () -> fetchFromBundle(newRepo,
  62. (TransportBundle.V2_BUNDLE_SIGNATURE + '\n' + "Garbage")
  63. .getBytes(UTF_8)));
  64. }
  65. @Test
  66. public void testWriteSingleRef() throws Exception {
  67. // Create a tiny bundle, (well one of) the first commits only
  68. final byte[] bundle = makeBundle("refs/heads/firstcommit",
  69. "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", null);
  70. // Then we clone a new repo from that bundle and do a simple test. This
  71. // makes sure we could read the bundle we created.
  72. Repository newRepo = createBareRepository();
  73. FetchResult fetchResult = fetchFromBundle(newRepo, bundle);
  74. Ref advertisedRef = fetchResult
  75. .getAdvertisedRef("refs/heads/firstcommit");
  76. // We expect first commit to appear by id
  77. assertEquals("42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", advertisedRef
  78. .getObjectId().name());
  79. // ..and by name as the bundle created a new ref
  80. assertEquals("42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", newRepo
  81. .resolve("refs/heads/firstcommit").name());
  82. }
  83. @Test
  84. public void testWriteHEAD() throws Exception {
  85. byte[] bundle = makeBundle("HEAD",
  86. "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", null);
  87. Repository newRepo = createBareRepository();
  88. FetchResult fetchResult = fetchFromBundle(newRepo, bundle);
  89. Ref advertisedRef = fetchResult.getAdvertisedRef("HEAD");
  90. assertEquals("42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", advertisedRef
  91. .getObjectId().name());
  92. }
  93. @Test
  94. public void testIncrementalBundle() throws Exception {
  95. byte[] bundle;
  96. // Create a small bundle, an early commit
  97. bundle = makeBundle("refs/heads/aa", db.resolve("a").name(), null);
  98. // Then we clone a new repo from that bundle and do a simple test. This
  99. // makes sure
  100. // we could read the bundle we created.
  101. Repository newRepo = createBareRepository();
  102. FetchResult fetchResult = fetchFromBundle(newRepo, bundle);
  103. Ref advertisedRef = fetchResult.getAdvertisedRef("refs/heads/aa");
  104. assertEquals(db.resolve("a").name(), advertisedRef.getObjectId().name());
  105. assertEquals(db.resolve("a").name(), newRepo.resolve("refs/heads/aa")
  106. .name());
  107. assertNull(newRepo.resolve("refs/heads/a"));
  108. // Next an incremental bundle
  109. try (RevWalk rw = new RevWalk(db)) {
  110. bundle = makeBundle("refs/heads/cc", db.resolve("c").name(),
  111. rw.parseCommit(db.resolve("a").toObjectId()));
  112. fetchResult = fetchFromBundle(newRepo, bundle);
  113. advertisedRef = fetchResult.getAdvertisedRef("refs/heads/cc");
  114. assertEquals(db.resolve("c").name(), advertisedRef.getObjectId().name());
  115. assertEquals(db.resolve("c").name(), newRepo.resolve("refs/heads/cc")
  116. .name());
  117. assertNull(newRepo.resolve("refs/heads/c"));
  118. assertNull(newRepo.resolve("refs/heads/a")); // still unknown
  119. try {
  120. // Check that we actually needed the first bundle
  121. Repository newRepo2 = createBareRepository();
  122. fetchResult = fetchFromBundle(newRepo2, bundle);
  123. fail("We should not be able to fetch from bundle with prerequisites that are not fulfilled");
  124. } catch (MissingBundlePrerequisiteException e) {
  125. assertTrue(e.getMessage()
  126. .indexOf(db.resolve("refs/heads/a").name()) >= 0);
  127. }
  128. }
  129. }
  130. @Test
  131. public void testAbortWrite() throws Exception {
  132. boolean caught = false;
  133. try {
  134. makeBundleWithCallback(
  135. "refs/heads/aa", db.resolve("a").name(), null, false);
  136. } catch (WriteAbortedException e) {
  137. caught = true;
  138. }
  139. assertTrue(caught);
  140. }
  141. @Test
  142. public void testCustomObjectReader() throws Exception {
  143. String refName = "refs/heads/blob";
  144. String data = "unflushed data";
  145. ObjectId id;
  146. ByteArrayOutputStream out = new ByteArrayOutputStream();
  147. try (Repository repo = new InMemoryRepository(
  148. new DfsRepositoryDescription("repo"));
  149. ObjectInserter ins = repo.newObjectInserter();
  150. ObjectReader or = ins.newReader()) {
  151. id = ins.insert(OBJ_BLOB, Constants.encode(data));
  152. BundleWriter bw = new BundleWriter(or);
  153. bw.include(refName, id);
  154. bw.writeBundle(NullProgressMonitor.INSTANCE, out);
  155. assertNull(repo.exactRef(refName));
  156. try {
  157. repo.open(id, OBJ_BLOB);
  158. fail("We should not be able to open the unflushed blob");
  159. } catch (MissingObjectException e) {
  160. // Expected.
  161. }
  162. }
  163. try (Repository repo = new InMemoryRepository(
  164. new DfsRepositoryDescription("copy"))) {
  165. fetchFromBundle(repo, out.toByteArray());
  166. Ref ref = repo.exactRef(refName);
  167. assertNotNull(ref);
  168. assertEquals(id, ref.getObjectId());
  169. assertEquals(data,
  170. new String(repo.open(id, OBJ_BLOB).getBytes(), UTF_8));
  171. }
  172. }
  173. private static FetchResult fetchFromBundle(final Repository newRepo,
  174. final byte[] bundle) throws URISyntaxException,
  175. NotSupportedException, TransportException {
  176. final URIish uri = new URIish("in-memory://");
  177. final ByteArrayInputStream in = new ByteArrayInputStream(bundle);
  178. final RefSpec rs = new RefSpec("refs/heads/*:refs/heads/*");
  179. final Set<RefSpec> refs = Collections.singleton(rs);
  180. try (TransportBundleStream transport = new TransportBundleStream(
  181. newRepo, uri, in)) {
  182. return transport.fetch(NullProgressMonitor.INSTANCE, refs);
  183. }
  184. }
  185. private byte[] makeBundle(final String name,
  186. final String anObjectToInclude, final RevCommit assume)
  187. throws FileNotFoundException, IOException {
  188. return makeBundleWithCallback(name, anObjectToInclude, assume, true);
  189. }
  190. private byte[] makeBundleWithCallback(final String name,
  191. final String anObjectToInclude, final RevCommit assume,
  192. boolean value)
  193. throws FileNotFoundException, IOException {
  194. final BundleWriter bw;
  195. bw = new BundleWriter(db);
  196. bw.setObjectCountCallback(new NaiveObjectCountCallback(value));
  197. bw.include(name, ObjectId.fromString(anObjectToInclude));
  198. if (assume != null)
  199. bw.assume(assume);
  200. final ByteArrayOutputStream out = new ByteArrayOutputStream();
  201. bw.writeBundle(NullProgressMonitor.INSTANCE, out);
  202. return out.toByteArray();
  203. }
  204. private static class NaiveObjectCountCallback
  205. implements ObjectCountCallback {
  206. private final boolean value;
  207. NaiveObjectCountCallback(boolean value) {
  208. this.value = value;
  209. }
  210. @Override
  211. public void setObjectCount(long unused) throws WriteAbortedException {
  212. if (!value)
  213. throw new WriteAbortedException();
  214. }
  215. }
  216. }