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 addRepoToClose(newRepo);
84 FetchResult fetchResult = fetchFromBundle(newRepo, bundle);
85 Ref advertisedRef = fetchResult
86 .getAdvertisedRef("refs/heads/firstcommit");
87
88
89 assertEquals("42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", advertisedRef
90 .getObjectId().name());
91
92 assertEquals("42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", newRepo
93 .resolve("refs/heads/firstcommit").name());
94 }
95
96 @Test
97 public void testWriteHEAD() throws Exception {
98 byte[] bundle = makeBundle("HEAD",
99 "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", null);
100
101 Repository newRepo = createBareRepository();
102 FetchResult fetchResult = fetchFromBundle(newRepo, bundle);
103 Ref advertisedRef = fetchResult.getAdvertisedRef("HEAD");
104
105 assertEquals("42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", advertisedRef
106 .getObjectId().name());
107 }
108
109 @Test
110 public void testIncrementalBundle() throws Exception {
111 byte[] bundle;
112
113
114 bundle = makeBundle("refs/heads/aa", db.resolve("a").name(), null);
115
116
117
118
119 Repository newRepo = createBareRepository();
120 addRepoToClose(newRepo);
121 FetchResult fetchResult = fetchFromBundle(newRepo, bundle);
122 Ref advertisedRef = fetchResult.getAdvertisedRef("refs/heads/aa");
123
124 assertEquals(db.resolve("a").name(), advertisedRef.getObjectId().name());
125 assertEquals(db.resolve("a").name(), newRepo.resolve("refs/heads/aa")
126 .name());
127 assertNull(newRepo.resolve("refs/heads/a"));
128
129
130 try (RevWalk rw = new RevWalk(db)) {
131 bundle = makeBundle("refs/heads/cc", db.resolve("c").name(),
132 rw.parseCommit(db.resolve("a").toObjectId()));
133 fetchResult = fetchFromBundle(newRepo, bundle);
134 advertisedRef = fetchResult.getAdvertisedRef("refs/heads/cc");
135 assertEquals(db.resolve("c").name(), advertisedRef.getObjectId().name());
136 assertEquals(db.resolve("c").name(), newRepo.resolve("refs/heads/cc")
137 .name());
138 assertNull(newRepo.resolve("refs/heads/c"));
139 assertNull(newRepo.resolve("refs/heads/a"));
140
141 try {
142
143 Repository newRepo2 = createBareRepository();
144 fetchResult = fetchFromBundle(newRepo2, bundle);
145 fail("We should not be able to fetch from bundle with prerequisites that are not fulfilled");
146 } catch (MissingBundlePrerequisiteException e) {
147 assertTrue(e.getMessage()
148 .indexOf(db.resolve("refs/heads/a").name()) >= 0);
149 }
150 }
151 }
152
153 @Test
154 public void testAbortWrite() throws Exception {
155 boolean caught = false;
156 try {
157 makeBundleWithCallback(
158 "refs/heads/aa", db.resolve("a").name(), null, false);
159 } catch (WriteAbortedException e) {
160 caught = true;
161 }
162 assertTrue(caught);
163 }
164
165 @Test
166 public void testCustomObjectReader() throws Exception {
167 String refName = "refs/heads/blob";
168 String data = "unflushed data";
169 ObjectId id;
170 ByteArrayOutputStream out = new ByteArrayOutputStream();
171 try (Repository repo = new InMemoryRepository(
172 new DfsRepositoryDescription("repo"));
173 ObjectInserter ins = repo.newObjectInserter();
174 ObjectReader or = ins.newReader()) {
175 id = ins.insert(OBJ_BLOB, Constants.encode(data));
176 BundleWriter bw = new BundleWriter(or);
177 bw.include(refName, id);
178 bw.writeBundle(NullProgressMonitor.INSTANCE, out);
179 assertNull(repo.exactRef(refName));
180 try {
181 repo.open(id, OBJ_BLOB);
182 fail("We should not be able to open the unflushed blob");
183 } catch (MissingObjectException e) {
184
185 }
186 }
187
188 try (Repository repo = new InMemoryRepository(
189 new DfsRepositoryDescription("copy"))) {
190 fetchFromBundle(repo, out.toByteArray());
191 Ref ref = repo.exactRef(refName);
192 assertNotNull(ref);
193 assertEquals(id, ref.getObjectId());
194 assertEquals(data,
195 new String(repo.open(id, OBJ_BLOB).getBytes(), UTF_8));
196 }
197 }
198
199 private static FetchResult fetchFromBundle(final Repository newRepo,
200 final byte[] bundle) throws URISyntaxException,
201 NotSupportedException, TransportException {
202 final URIish uri = new URIish("in-memory://");
203 final ByteArrayInputStream in = new ByteArrayInputStream(bundle);
204 final RefSpec rs = new RefSpec("refs/heads/*:refs/heads/*");
205 final Set<RefSpec> refs = Collections.singleton(rs);
206 try (TransportBundleStream transport = new TransportBundleStream(
207 newRepo, uri, in)) {
208 return transport.fetch(NullProgressMonitor.INSTANCE, refs);
209 }
210 }
211
212 private byte[] makeBundle(final String name,
213 final String anObjectToInclude, final RevCommit assume)
214 throws FileNotFoundException, IOException {
215 return makeBundleWithCallback(name, anObjectToInclude, assume, true);
216 }
217
218 private byte[] makeBundleWithCallback(final String name,
219 final String anObjectToInclude, final RevCommit assume,
220 boolean value)
221 throws FileNotFoundException, IOException {
222 final BundleWriter bw;
223
224 bw = new BundleWriter(db);
225 bw.setObjectCountCallback(new NaiveObjectCountCallback(value));
226 bw.include(name, ObjectId.fromString(anObjectToInclude));
227 if (assume != null)
228 bw.assume(assume);
229 final ByteArrayOutputStream out = new ByteArrayOutputStream();
230 bw.writeBundle(NullProgressMonitor.INSTANCE, out);
231 return out.toByteArray();
232 }
233
234 private static class NaiveObjectCountCallback
235 implements ObjectCountCallback {
236 private final boolean value;
237
238 NaiveObjectCountCallback(boolean value) {
239 this.value = value;
240 }
241
242 @Override
243 public void setObjectCount(long unused) throws WriteAbortedException {
244 if (!value)
245 throw new WriteAbortedException();
246 }
247 }
248
249 }