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