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 package org.eclipse.jgit.transport;
45
46 import static java.nio.charset.StandardCharsets.UTF_8;
47 import static org.eclipse.jgit.lib.ObjectId.zeroId;
48 import static org.eclipse.jgit.lib.RefUpdate.Result.FAST_FORWARD;
49 import static org.eclipse.jgit.lib.RefUpdate.Result.LOCK_FAILURE;
50 import static org.eclipse.jgit.lib.RefUpdate.Result.NEW;
51 import static org.eclipse.jgit.lib.RefUpdate.Result.NO_CHANGE;
52 import static org.junit.Assert.assertEquals;
53 import static org.junit.Assert.assertFalse;
54 import static org.junit.Assert.assertTrue;
55
56 import java.io.ByteArrayInputStream;
57 import java.io.IOException;
58 import java.io.InputStreamReader;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.Collections;
62 import java.util.List;
63 import java.util.concurrent.atomic.AtomicInteger;
64
65 import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
66 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
67 import org.eclipse.jgit.lib.BatchRefUpdate;
68 import org.eclipse.jgit.lib.Constants;
69 import org.eclipse.jgit.lib.NullProgressMonitor;
70 import org.eclipse.jgit.lib.ObjectId;
71 import org.eclipse.jgit.lib.PersonIdent;
72 import org.eclipse.jgit.revwalk.RevCommit;
73 import org.eclipse.jgit.revwalk.RevWalk;
74 import org.junit.Before;
75 import org.junit.Test;
76
77 public class PushCertificateStoreTest {
78 private static final ObjectId ID1 =
79 ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
80
81 private static final ObjectId ID2 =
82 ObjectId.fromString("badc0ffebadc0ffebadc0ffebadc0ffebadc0ffe");
83
84 private static PushCertificate newCert(String... updateLines) {
85 StringBuilder cert = new StringBuilder(
86 "certificate version 0.1\n"
87 + "pusher Dave Borowitz <dborowitz@google.com> 1433954361 -0700\n"
88 + "pushee git://localhost/repo.git\n"
89 + "nonce 1433954361-bde756572d665bba81d8\n"
90 + "\n");
91 for (String updateLine : updateLines) {
92 cert.append(updateLine).append('\n');
93 }
94 cert.append(
95 "-----BEGIN PGP SIGNATURE-----\n"
96 + "DUMMY/SIGNATURE\n"
97 + "-----END PGP SIGNATURE-----\n");
98 try {
99 return PushCertificateParser.fromReader(new InputStreamReader(
100 new ByteArrayInputStream(
101 Constants.encode(cert.toString())),
102 UTF_8));
103 } catch (IOException e) {
104 throw new IllegalArgumentException(e);
105 }
106 }
107
108 private static String command(ObjectId oldId, ObjectId newId, String ref) {
109 return oldId.name() + " " + newId.name() + " " + ref;
110 }
111
112 private AtomicInteger ts = new AtomicInteger(1433954361);
113 private InMemoryRepository repo;
114 private PushCertificateStore store;
115
116 @Before
117 public void setUp() throws Exception {
118 repo = new InMemoryRepository(new DfsRepositoryDescription("repo"));
119 store = newStore();
120 }
121
122 @Test
123 public void missingRef() throws Exception {
124 assertCerts("refs/heads/master");
125 }
126
127 @Test
128 public void saveNoChange() throws Exception {
129 assertEquals(NO_CHANGE, store.save());
130 }
131
132 @Test
133 public void saveOneCertOnOneRef() throws Exception {
134 PersonIdent ident = newIdent();
135 PushCertificate addMaster = newCert(
136 command(zeroId(), ID1, "refs/heads/master"));
137 store.put(addMaster, ident);
138 assertEquals(NEW, store.save());
139 assertCerts("refs/heads/master", addMaster);
140 assertCerts("refs/heads/branch");
141
142 try (RevWalk rw = new RevWalk(repo)) {
143 RevCommit c = rw.parseCommit(repo.resolve(PushCertificateStore.REF_NAME));
144 rw.parseBody(c);
145 assertEquals("Store push certificate for refs/heads/master\n",
146 c.getFullMessage());
147 assertEquals(ident, c.getAuthorIdent());
148 assertEquals(ident, c.getCommitterIdent());
149 }
150 }
151
152 @Test
153 public void saveTwoCertsOnSameRefInTwoUpdates() throws Exception {
154 PushCertificate addMaster = newCert(
155 command(zeroId(), ID1, "refs/heads/master"));
156 store.put(addMaster, newIdent());
157 assertEquals(NEW, store.save());
158 PushCertificate updateMaster = newCert(
159 command(ID1, ID2, "refs/heads/master"));
160 store.put(updateMaster, newIdent());
161 assertEquals(FAST_FORWARD, store.save());
162 assertCerts("refs/heads/master", updateMaster, addMaster);
163 }
164
165 @Test
166 public void saveTwoCertsOnSameRefInOneUpdate() throws Exception {
167 PersonIdent ident1 = newIdent();
168 PersonIdent ident2 = newIdent();
169 PushCertificate updateMaster = newCert(
170 command(ID1, ID2, "refs/heads/master"));
171 store.put(updateMaster, ident2);
172 PushCertificate addMaster = newCert(
173 command(zeroId(), ID1, "refs/heads/master"));
174 store.put(addMaster, ident1);
175 assertEquals(NEW, store.save());
176 assertCerts("refs/heads/master", updateMaster, addMaster);
177 }
178
179 @Test
180 public void saveTwoCertsOnDifferentRefsInOneUpdate() throws Exception {
181 PersonIdent ident1 = newIdent();
182 PersonIdent ident3 = newIdent();
183 PushCertificate addBranch = newCert(
184 command(zeroId(), ID1, "refs/heads/branch"));
185 store.put(addBranch, ident3);
186 PushCertificate addMaster = newCert(
187 command(zeroId(), ID1, "refs/heads/master"));
188 store.put(addMaster, ident1);
189 assertEquals(NEW, store.save());
190 assertCerts("refs/heads/master", addMaster);
191 assertCerts("refs/heads/branch", addBranch);
192 }
193
194 @Test
195 public void saveTwoCertsOnDifferentRefsInTwoUpdates() throws Exception {
196 PushCertificate addMaster = newCert(
197 command(zeroId(), ID1, "refs/heads/master"));
198 store.put(addMaster, newIdent());
199 assertEquals(NEW, store.save());
200 PushCertificate addBranch = newCert(
201 command(zeroId(), ID1, "refs/heads/branch"));
202 store.put(addBranch, newIdent());
203 assertEquals(FAST_FORWARD, store.save());
204 assertCerts("refs/heads/master", addMaster);
205 assertCerts("refs/heads/branch", addBranch);
206 }
207
208 @Test
209 public void saveOneCertOnMultipleRefs() throws Exception {
210 PersonIdent ident = newIdent();
211 PushCertificate addMasterAndBranch = newCert(
212 command(zeroId(), ID1, "refs/heads/branch"),
213 command(zeroId(), ID2, "refs/heads/master"));
214 store.put(addMasterAndBranch, ident);
215 assertEquals(NEW, store.save());
216 assertCerts("refs/heads/master", addMasterAndBranch);
217 assertCerts("refs/heads/branch", addMasterAndBranch);
218
219 try (RevWalk rw = new RevWalk(repo)) {
220 RevCommit c = rw.parseCommit(repo.resolve(PushCertificateStore.REF_NAME));
221 rw.parseBody(c);
222 assertEquals("Store push certificate for 2 refs\n", c.getFullMessage());
223 assertEquals(ident, c.getAuthorIdent());
224 assertEquals(ident, c.getCommitterIdent());
225 }
226 }
227
228 @Test
229 public void changeRefFileToDirectory() throws Exception {
230 PushCertificate deleteRefsHeads = newCert(
231 command(ID1, zeroId(), "refs/heads"));
232 store.put(deleteRefsHeads, newIdent());
233 PushCertificate addMaster = newCert(
234 command(zeroId(), ID1, "refs/heads/master"));
235 store.put(addMaster, newIdent());
236 assertEquals(NEW, store.save());
237 assertCerts("refs/heads", deleteRefsHeads);
238 assertCerts("refs/heads/master", addMaster);
239 }
240
241 @Test
242 public void getBeforeSaveDoesNotIncludePending() throws Exception {
243 PushCertificate addMaster = newCert(
244 command(zeroId(), ID1, "refs/heads/master"));
245 store.put(addMaster, newIdent());
246 assertEquals(NEW, store.save());
247
248 PushCertificate updateMaster = newCert(
249 command(ID1, ID2, "refs/heads/master"));
250 store.put(updateMaster, newIdent());
251
252 assertCerts("refs/heads/master", addMaster);
253 assertEquals(FAST_FORWARD, store.save());
254 assertCerts("refs/heads/master", updateMaster, addMaster);
255 }
256
257 @Test
258 public void lockFailure() throws Exception {
259 PushCertificateStore store1 = store;
260 PushCertificateStore store2 = newStore();
261 store2.get("refs/heads/master");
262
263 PushCertificate addMaster = newCert(
264 command(zeroId(), ID1, "refs/heads/master"));
265 store1.put(addMaster, newIdent());
266 assertEquals(NEW, store1.save());
267
268 PushCertificate addBranch = newCert(
269 command(zeroId(), ID2, "refs/heads/branch"));
270 store2.put(addBranch, newIdent());
271
272 assertEquals(LOCK_FAILURE, store2.save());
273
274 assertCerts(store2, "refs/heads/master", addMaster);
275 assertCerts(store2, "refs/heads/branch");
276
277 assertEquals(FAST_FORWARD, store2.save());
278 assertCerts(store2, "refs/heads/master", addMaster);
279 assertCerts(store2, "refs/heads/branch", addBranch);
280 }
281
282 @Test
283 public void saveInBatch() throws Exception {
284 BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate();
285 assertFalse(store.save(batch));
286 assertEquals(0, batch.getCommands().size());
287 PushCertificate addMaster = newCert(
288 command(zeroId(), ID1, "refs/heads/master"));
289 store.put(addMaster, newIdent());
290 assertTrue(store.save(batch));
291
292 List<ReceiveCommand> commands = batch.getCommands();
293 assertEquals(1, commands.size());
294 ReceiveCommand cmd = commands.get(0);
295 assertEquals("refs/meta/push-certs", cmd.getRefName());
296 assertEquals(ReceiveCommand.Result.NOT_ATTEMPTED, cmd.getResult());
297
298 try (RevWalk rw = new RevWalk(repo)) {
299 batch.execute(rw, NullProgressMonitor.INSTANCE);
300 assertEquals(ReceiveCommand.Result.OK, cmd.getResult());
301 }
302 }
303
304 @Test
305 public void putMatchingWithNoMatchingRefs() throws Exception {
306 PushCertificate addMaster = newCert(
307 command(zeroId(), ID1, "refs/heads/master"),
308 command(zeroId(), ID2, "refs/heads/branch"));
309 store.put(addMaster, newIdent(), Collections.<ReceiveCommand> emptyList());
310 assertEquals(NO_CHANGE, store.save());
311 }
312
313 @Test
314 public void putMatchingWithNoMatchingRefsInBatchOnEmptyRef()
315 throws Exception {
316 PushCertificate addMaster = newCert(
317 command(zeroId(), ID1, "refs/heads/master"),
318 command(zeroId(), ID2, "refs/heads/branch"));
319 store.put(addMaster, newIdent(), Collections.<ReceiveCommand> emptyList());
320 BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate();
321 assertFalse(store.save(batch));
322 assertEquals(0, batch.getCommands().size());
323 }
324
325 @Test
326 public void putMatchingWithNoMatchingRefsInBatchOnNonEmptyRef()
327 throws Exception {
328 PushCertificate addMaster = newCert(
329 command(zeroId(), ID1, "refs/heads/master"));
330 store.put(addMaster, newIdent());
331 assertEquals(NEW, store.save());
332
333 PushCertificate addBranch = newCert(
334 command(zeroId(), ID2, "refs/heads/branch"));
335 store.put(addBranch, newIdent(), Collections.<ReceiveCommand> emptyList());
336 BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate();
337 assertFalse(store.save(batch));
338 assertEquals(0, batch.getCommands().size());
339 }
340
341 @Test
342 public void putMatchingWithSomeMatchingRefs() throws Exception {
343 PushCertificate addMasterAndBranch = newCert(
344 command(zeroId(), ID1, "refs/heads/master"),
345 command(zeroId(), ID2, "refs/heads/branch"));
346 store.put(addMasterAndBranch, newIdent(),
347 Collections.singleton(addMasterAndBranch.getCommands().get(0)));
348 assertEquals(NEW, store.save());
349 assertCerts("refs/heads/master", addMasterAndBranch);
350 assertCerts("refs/heads/branch");
351 }
352
353 private PersonIdent newIdent() {
354 return new PersonIdent(
355 "A U. Thor", "author@example.com", ts.getAndIncrement(), 0);
356 }
357
358 private PushCertificateStore newStore() {
359 return new PushCertificateStore(repo);
360 }
361
362 private void assertCerts(String refName, PushCertificate... expected)
363 throws Exception {
364 assertCerts(store, refName, expected);
365 assertCerts(newStore(), refName, expected);
366 }
367
368 private static void assertCerts(PushCertificateStore store, String refName,
369 PushCertificate... expected) throws Exception {
370 List<PushCertificate> ex = Arrays.asList(expected);
371 PushCertificate first = !ex.isEmpty() ? ex.get(0) : null;
372 assertEquals(first, store.get(refName));
373 assertEquals(ex, toList(store.getAll(refName)));
374 }
375
376 private static <T> List<T> toList(Iterable<T> it) {
377 List<T> list = new ArrayList<>();
378 for (T t : it) {
379 list.add(t);
380 }
381 return list;
382 }
383 }