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