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.Constants.CHARSET;
47 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
48 import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
49 import static org.eclipse.jgit.lib.FileMode.TYPE_FILE;
50
51 import java.io.BufferedReader;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.io.InputStreamReader;
55 import java.io.Reader;
56 import java.text.MessageFormat;
57 import java.util.ArrayList;
58 import java.util.Collection;
59 import java.util.Collections;
60 import java.util.Comparator;
61 import java.util.HashMap;
62 import java.util.Iterator;
63 import java.util.List;
64 import java.util.Map;
65 import java.util.NoSuchElementException;
66
67 import org.eclipse.jgit.dircache.DirCache;
68 import org.eclipse.jgit.dircache.DirCacheEditor;
69 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
70 import org.eclipse.jgit.dircache.DirCacheEntry;
71 import org.eclipse.jgit.internal.JGitText;
72 import org.eclipse.jgit.lib.BatchRefUpdate;
73 import org.eclipse.jgit.lib.CommitBuilder;
74 import org.eclipse.jgit.lib.Constants;
75 import org.eclipse.jgit.lib.FileMode;
76 import org.eclipse.jgit.lib.ObjectId;
77 import org.eclipse.jgit.lib.ObjectInserter;
78 import org.eclipse.jgit.lib.ObjectLoader;
79 import org.eclipse.jgit.lib.ObjectReader;
80 import org.eclipse.jgit.lib.PersonIdent;
81 import org.eclipse.jgit.lib.Ref;
82 import org.eclipse.jgit.lib.RefUpdate;
83 import org.eclipse.jgit.lib.Repository;
84 import org.eclipse.jgit.revwalk.RevCommit;
85 import org.eclipse.jgit.revwalk.RevWalk;
86 import org.eclipse.jgit.treewalk.TreeWalk;
87 import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
88 import org.eclipse.jgit.treewalk.filter.PathFilter;
89 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
90 import org.eclipse.jgit.treewalk.filter.TreeFilter;
91
92
93
94
95
96
97
98
99
100
101
102
103 public class PushCertificateStore implements AutoCloseable {
104
105 static final String REF_NAME =
106 Constants.R_REFS + "meta/push-certs";
107
108 private static class PendingCert {
109 PushCertificate cert;
110 PersonIdent ident;
111 Collection<ReceiveCommand> matching;
112
113 PendingCert(PushCertificate cert, PersonIdent ident,
114 Collection<ReceiveCommand> matching) {
115 this.cert = cert;
116 this.ident = ident;
117 this.matching = matching;
118 }
119 }
120
121 private final Repository db;
122 private final List<PendingCert> pending;
123 ObjectReader reader;
124 RevCommit commit;
125
126
127
128
129
130
131
132 public PushCertificateStore(Repository db) {
133 this.db = db;
134 pending = new ArrayList<>();
135 }
136
137
138
139
140
141
142
143
144
145 @Override
146 public void close() {
147 if (reader != null) {
148 reader.close();
149 reader = null;
150 commit = null;
151 }
152 }
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168 public PushCertificate get(String refName) throws IOException {
169 if (reader == null) {
170 load();
171 }
172 try (TreeWalk tw = newTreeWalk(refName)) {
173 return read(tw);
174 }
175 }
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195 public Iterable<PushCertificate> getAll(String refName) {
196 return new Iterable<PushCertificate>() {
197 @Override
198 public Iterator<PushCertificate> iterator() {
199 return new Iterator<PushCertificate>() {
200 private final String path = pathName(refName);
201 private PushCertificate next;
202
203 private RevWalk rw;
204 {
205 try {
206 if (reader == null) {
207 load();
208 }
209 if (commit != null) {
210 rw = new RevWalk(reader);
211 rw.setTreeFilter(AndTreeFilter.create(
212 PathFilterGroup.create(
213 Collections.singleton(PathFilter.create(path))),
214 TreeFilter.ANY_DIFF));
215 rw.setRewriteParents(false);
216 rw.markStart(rw.parseCommit(commit));
217 } else {
218 rw = null;
219 }
220 } catch (IOException e) {
221 throw new RuntimeException(e);
222 }
223 }
224
225 @Override
226 public boolean hasNext() {
227 try {
228 if (next == null) {
229 if (rw == null) {
230 return false;
231 }
232 try {
233 RevCommit c = rw.next();
234 if (c != null) {
235 try (TreeWalk tw = TreeWalk.forPath(
236 rw.getObjectReader(), path, c.getTree())) {
237 next = read(tw);
238 }
239 } else {
240 next = null;
241 }
242 } catch (IOException e) {
243 throw new RuntimeException(e);
244 }
245 }
246 return next != null;
247 } finally {
248 if (next == null && rw != null) {
249 rw.close();
250 rw = null;
251 }
252 }
253 }
254
255 @Override
256 public PushCertificate next() {
257 hasNext();
258 PushCertificate n = next;
259 if (n == null) {
260 throw new NoSuchElementException();
261 }
262 next = null;
263 return n;
264 }
265
266 @Override
267 public void remove() {
268 throw new UnsupportedOperationException();
269 }
270 };
271 }
272 };
273 }
274
275 void load() throws IOException {
276 close();
277 reader = db.newObjectReader();
278 Ref ref = db.getRefDatabase().exactRef(REF_NAME);
279 if (ref == null) {
280
281 return;
282 }
283 try (RevWalk rw = new RevWalk(reader)) {
284 commit = rw.parseCommit(ref.getObjectId());
285 }
286 }
287
288 static PushCertificate read(TreeWalk tw) throws IOException {
289 if (tw == null || (tw.getRawMode(0) & TYPE_FILE) != TYPE_FILE) {
290 return null;
291 }
292 ObjectLoader loader =
293 tw.getObjectReader().open(tw.getObjectId(0), OBJ_BLOB);
294 try (InputStream in = loader.openStream();
295 Reader r = new BufferedReader(new InputStreamReader(in, CHARSET))) {
296 return PushCertificateParser.fromReader(r);
297 }
298 }
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318 public void put(PushCertificate cert, PersonIdent ident) {
319 put(cert, ident, null);
320 }
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344 public void put(PushCertificate cert, PersonIdent ident,
345 Collection<ReceiveCommand> matching) {
346 pending.add(new PendingCert(cert, ident, matching));
347 }
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364 public RefUpdate.Result save() throws IOException {
365 ObjectId newId = write();
366 if (newId == null) {
367 return RefUpdate.Result.NO_CHANGE;
368 }
369 try (ObjectInserter inserter = db.newObjectInserter()) {
370 RefUpdate.Result result = updateRef(newId);
371 switch (result) {
372 case FAST_FORWARD:
373 case NEW:
374 case NO_CHANGE:
375 pending.clear();
376 break;
377 default:
378 break;
379 }
380 return result;
381 } finally {
382 close();
383 }
384 }
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405 public boolean save(BatchRefUpdate batch) throws IOException {
406 ObjectId newId = write();
407 if (newId == null || newId.equals(commit)) {
408 return false;
409 }
410 batch.addCommand(new ReceiveCommand(
411 commit != null ? commit : ObjectId.zeroId(), newId, REF_NAME));
412 return true;
413 }
414
415
416
417
418
419 public void clear() {
420 pending.clear();
421 }
422
423 private ObjectId write() throws IOException {
424 if (pending.isEmpty()) {
425 return null;
426 }
427 if (reader == null) {
428 load();
429 }
430 sortPending(pending);
431
432 ObjectId curr = commit;
433 DirCache dc = newDirCache();
434 try (ObjectInserter inserter = db.newObjectInserter()) {
435 for (PendingCert pc : pending) {
436 curr = saveCert(inserter, dc, pc, curr);
437 }
438 inserter.flush();
439 return curr;
440 }
441 }
442
443 private static void sortPending(List<PendingCert> pending) {
444 Collections.sort(pending, new Comparator<PendingCert>() {
445 @Override
446 public int compare(PendingCert a, PendingCert b) {
447 return Long.signum(
448 a.ident.getWhen().getTime() - b.ident.getWhen().getTime());
449 }
450 });
451 }
452
453 private DirCache newDirCache() throws IOException {
454 if (commit != null) {
455 return DirCache.read(reader, commit.getTree());
456 }
457 return DirCache.newInCore();
458 }
459
460 private ObjectId saveCert(ObjectInserter inserter, DirCache dc,
461 PendingCert pc, ObjectId curr) throws IOException {
462 Map<String, ReceiveCommand> byRef;
463 if (pc.matching != null) {
464 byRef = new HashMap<>();
465 for (ReceiveCommand cmd : pc.matching) {
466 if (byRef.put(cmd.getRefName(), cmd) != null) {
467 throw new IllegalStateException();
468 }
469 }
470 } else {
471 byRef = null;
472 }
473
474 DirCacheEditor editor = dc.editor();
475 String certText = pc.cert.toText() + pc.cert.getSignature();
476 final ObjectId certId = inserter.insert(OBJ_BLOB, certText.getBytes(CHARSET));
477 boolean any = false;
478 for (ReceiveCommand cmd : pc.cert.getCommands()) {
479 if (byRef != null && !commandsEqual(cmd, byRef.get(cmd.getRefName()))) {
480 continue;
481 }
482 any = true;
483 editor.add(new PathEdit(pathName(cmd.getRefName())) {
484 @Override
485 public void apply(DirCacheEntry ent) {
486 ent.setFileMode(FileMode.REGULAR_FILE);
487 ent.setObjectId(certId);
488 }
489 });
490 }
491 if (!any) {
492 return curr;
493 }
494 editor.finish();
495 CommitBuilder cb = new CommitBuilder();
496 cb.setAuthor(pc.ident);
497 cb.setCommitter(pc.ident);
498 cb.setTreeId(dc.writeTree(inserter));
499 if (curr != null) {
500 cb.setParentId(curr);
501 } else {
502 cb.setParentIds(Collections.<ObjectId> emptyList());
503 }
504 cb.setMessage(buildMessage(pc.cert));
505 return inserter.insert(OBJ_COMMIT, cb.build());
506 }
507
508 private static boolean commandsEqual(ReceiveCommand c1, ReceiveCommand c2) {
509 if (c1 == null || c2 == null) {
510 return c1 == c2;
511 }
512 return c1.getRefName().equals(c2.getRefName())
513 && c1.getOldId().equals(c2.getOldId())
514 && c1.getNewId().equals(c2.getNewId());
515 }
516
517 private RefUpdate.Result updateRef(ObjectId newId) throws IOException {
518 RefUpdate ru = db.updateRef(REF_NAME);
519 ru.setExpectedOldObjectId(commit != null ? commit : ObjectId.zeroId());
520 ru.setNewObjectId(newId);
521 ru.setRefLogIdent(pending.get(pending.size() - 1).ident);
522 ru.setRefLogMessage(JGitText.get().storePushCertReflog, false);
523 try (RevWalk rw = new RevWalk(reader)) {
524 return ru.update(rw);
525 }
526 }
527
528 private TreeWalk newTreeWalk(String refName) throws IOException {
529 if (commit == null) {
530 return null;
531 }
532 return TreeWalk.forPath(reader, pathName(refName), commit.getTree());
533 }
534
535 static String pathName(String refName) {
536 return refName + "@{cert}";
537 }
538
539 private static String buildMessage(PushCertificate cert) {
540 StringBuilder sb = new StringBuilder();
541 if (cert.getCommands().size() == 1) {
542 sb.append(MessageFormat.format(
543 JGitText.get().storePushCertOneRef,
544 cert.getCommands().get(0).getRefName()));
545 } else {
546 sb.append(MessageFormat.format(
547 JGitText.get().storePushCertMultipleRefs,
548 Integer.valueOf(cert.getCommands().size())));
549 }
550 return sb.append('\n').toString();
551 }
552 }