1
2
3
4
5
6
7
8
9
10
11
12
13 package org.eclipse.jgit.transport;
14
15 import static java.nio.charset.StandardCharsets.UTF_8;
16 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
17 import static org.junit.Assert.assertEquals;
18 import static org.junit.Assert.assertNotNull;
19 import static org.junit.Assert.assertNull;
20 import static org.junit.Assert.assertThrows;
21 import static org.junit.Assert.assertTrue;
22 import static org.junit.Assert.fail;
23
24 import java.io.ByteArrayInputStream;
25 import java.io.ByteArrayOutputStream;
26 import java.io.FileNotFoundException;
27 import java.io.IOException;
28 import java.net.URISyntaxException;
29 import java.util.Collections;
30 import java.util.Set;
31
32 import org.eclipse.jgit.errors.MissingBundlePrerequisiteException;
33 import org.eclipse.jgit.errors.MissingObjectException;
34 import org.eclipse.jgit.errors.NotSupportedException;
35 import org.eclipse.jgit.errors.TransportException;
36 import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
37 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
38 import org.eclipse.jgit.lib.Constants;
39 import org.eclipse.jgit.lib.NullProgressMonitor;
40 import org.eclipse.jgit.lib.ObjectId;
41 import org.eclipse.jgit.lib.ObjectInserter;
42 import org.eclipse.jgit.lib.ObjectReader;
43 import org.eclipse.jgit.lib.Ref;
44 import org.eclipse.jgit.lib.Repository;
45 import org.eclipse.jgit.revwalk.RevCommit;
46 import org.eclipse.jgit.revwalk.RevWalk;
47 import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
48 import org.junit.Test;
49
50 public class BundleWriterTest extends SampleDataRepositoryTestCase {
51
52 @Test
53 public void testEmptyBundleFails() throws Exception {
54 Repository newRepo = createBareRepository();
55 assertThrows(TransportException.class,
56 () -> fetchFromBundle(newRepo, new byte[0]));
57 }
58
59 @Test
60 public void testNonBundleFails() throws Exception {
61 Repository newRepo = createBareRepository();
62 assertThrows(TransportException.class, () -> fetchFromBundle(newRepo,
63 "Not a bundle file".getBytes(UTF_8)));
64 }
65
66 @Test
67 public void testGarbageBundleFails() throws Exception {
68 Repository newRepo = createBareRepository();
69 assertThrows(TransportException.class, () -> fetchFromBundle(newRepo,
70 (TransportBundle.V2_BUNDLE_SIGNATURE + '\n' + "Garbage")
71 .getBytes(UTF_8)));
72 }
73
74 @Test
75 public void testWriteSingleRef() throws Exception {
76
77 final byte[] bundle = makeBundle("refs/heads/firstcommit",
78 "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", null);
79
80
81
82 Repository newRepo = createBareRepository();
83 FetchResult fetchResult = fetchFromBundle(newRepo, bundle);
84 Ref advertisedRef = fetchResult
85 .getAdvertisedRef("refs/heads/firstcommit");
86
87
88 assertEquals("42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", advertisedRef
89 .getObjectId().name());
90
91 assertEquals("42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", newRepo
92 .resolve("refs/heads/firstcommit").name());
93 }
94
95 @Test
96 public void testWriteHEAD() throws Exception {
97 byte[] bundle = makeBundle("HEAD",
98 "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", null);
99
100 Repository newRepo = createBareRepository();
101 FetchResult fetchResult = fetchFromBundle(newRepo, bundle);
102 Ref advertisedRef = fetchResult.getAdvertisedRef("HEAD");
103
104 assertEquals("42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", advertisedRef
105 .getObjectId().name());
106 }
107
108 @Test
109 public void testIncrementalBundle() throws Exception {
110 byte[] bundle;
111
112
113 bundle = makeBundle("refs/heads/aa", db.resolve("a").name(), null);
114
115
116
117
118 Repository newRepo = createBareRepository();
119 FetchResult fetchResult = fetchFromBundle(newRepo, bundle);
120 Ref advertisedRef = fetchResult.getAdvertisedRef("refs/heads/aa");
121
122 assertEquals(db.resolve("a").name(), advertisedRef.getObjectId().name());
123 assertEquals(db.resolve("a").name(), newRepo.resolve("refs/heads/aa")
124 .name());
125 assertNull(newRepo.resolve("refs/heads/a"));
126
127
128 try (RevWalk rw = new RevWalk(db)) {
129 bundle = makeBundle("refs/heads/cc", db.resolve("c").name(),
130 rw.parseCommit(db.resolve("a").toObjectId()));
131 fetchResult = fetchFromBundle(newRepo, bundle);
132 advertisedRef = fetchResult.getAdvertisedRef("refs/heads/cc");
133 assertEquals(db.resolve("c").name(), advertisedRef.getObjectId().name());
134 assertEquals(db.resolve("c").name(), newRepo.resolve("refs/heads/cc")
135 .name());
136 assertNull(newRepo.resolve("refs/heads/c"));
137 assertNull(newRepo.resolve("refs/heads/a"));
138
139 try {
140
141 Repository newRepo2 = createBareRepository();
142 fetchResult = fetchFromBundle(newRepo2, bundle);
143 fail("We should not be able to fetch from bundle with prerequisites that are not fulfilled");
144 } catch (MissingBundlePrerequisiteException e) {
145 assertTrue(e.getMessage()
146 .indexOf(db.resolve("refs/heads/a").name()) >= 0);
147 }
148 }
149 }
150
151 @Test
152 public void testAbortWrite() throws Exception {
153 boolean caught = false;
154 try {
155 makeBundleWithCallback(
156 "refs/heads/aa", db.resolve("a").name(), null, false);
157 } catch (WriteAbortedException e) {
158 caught = true;
159 }
160 assertTrue(caught);
161 }
162
163 @Test
164 public void testCustomObjectReader() throws Exception {
165 String refName = "refs/heads/blob";
166 String data = "unflushed data";
167 ObjectId id;
168 ByteArrayOutputStream out = new ByteArrayOutputStream();
169 try (Repository repo = new InMemoryRepository(
170 new DfsRepositoryDescription("repo"));
171 ObjectInserter ins = repo.newObjectInserter();
172 ObjectReader or = ins.newReader()) {
173 id = ins.insert(OBJ_BLOB, Constants.encode(data));
174 BundleWriter bw = new BundleWriter(or);
175 bw.include(refName, id);
176 bw.writeBundle(NullProgressMonitor.INSTANCE, out);
177 assertNull(repo.exactRef(refName));
178 try {
179 repo.open(id, OBJ_BLOB);
180 fail("We should not be able to open the unflushed blob");
181 } catch (MissingObjectException e) {
182
183 }
184 }
185
186 try (Repository repo = new InMemoryRepository(
187 new DfsRepositoryDescription("copy"))) {
188 fetchFromBundle(repo, out.toByteArray());
189 Ref ref = repo.exactRef(refName);
190 assertNotNull(ref);
191 assertEquals(id, ref.getObjectId());
192 assertEquals(data,
193 new String(repo.open(id, OBJ_BLOB).getBytes(), UTF_8));
194 }
195 }
196
197 private static FetchResult fetchFromBundle(final Repository newRepo,
198 final byte[] bundle) throws URISyntaxException,
199 NotSupportedException, TransportException {
200 final URIish uri = new URIish("in-memory://");
201 final ByteArrayInputStream in = new ByteArrayInputStream(bundle);
202 final RefSpec rs = new RefSpec("refs/heads/*:refs/heads/*");
203 final Set<RefSpec> refs = Collections.singleton(rs);
204 try (TransportBundleStream transport = new TransportBundleStream(
205 newRepo, uri, in)) {
206 return transport.fetch(NullProgressMonitor.INSTANCE, refs);
207 }
208 }
209
210 private byte[] makeBundle(final String name,
211 final String anObjectToInclude, final RevCommit assume)
212 throws FileNotFoundException, IOException {
213 return makeBundleWithCallback(name, anObjectToInclude, assume, true);
214 }
215
216 private byte[] makeBundleWithCallback(final String name,
217 final String anObjectToInclude, final RevCommit assume,
218 boolean value)
219 throws FileNotFoundException, IOException {
220 final BundleWriter bw;
221
222 bw = new BundleWriter(db);
223 bw.setObjectCountCallback(new NaiveObjectCountCallback(value));
224 bw.include(name, ObjectId.fromString(anObjectToInclude));
225 if (assume != null)
226 bw.assume(assume);
227 final ByteArrayOutputStream out = new ByteArrayOutputStream();
228 bw.writeBundle(NullProgressMonitor.INSTANCE, out);
229 return out.toByteArray();
230 }
231
232 private static class NaiveObjectCountCallback
233 implements ObjectCountCallback {
234 private final boolean value;
235
236 NaiveObjectCountCallback(boolean value) {
237 this.value = value;
238 }
239
240 @Override
241 public void setObjectCount(long unused) throws WriteAbortedException {
242 if (!value)
243 throw new WriteAbortedException();
244 }
245 }
246
247 }