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