summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.test
diff options
context:
space:
mode:
authorDave Borowitz <dborowitz@google.com>2015-06-29 18:05:11 -0700
committerDave Borowitz <dborowitz@google.com>2015-07-10 13:16:37 -0700
commitd5a71e9ca3d95330acdd858306c4f75ae0b01e58 (patch)
treeac0290a03ee6fa398644ffa08236df5db1dfa3fe /org.eclipse.jgit.test
parent217b2a7cc5366491be5317d20f3f3c1b6e3475bf (diff)
downloadjgit-d5a71e9ca3d95330acdd858306c4f75ae0b01e58.tar.gz
jgit-d5a71e9ca3d95330acdd858306c4f75ae0b01e58.zip
Store push certificates in refs/meta/push-certs
Inspired by a proposal from gitolite[1], where we store a file in a tree for each ref name, and the contents of the file is the latest push cert to affect that ref. The main modification from that proposal (other than lacking the out-of-git batching) is to append "@{cert}" to filenames, which allows storing certificates for both refs/foo and refs/foo/bar. Those refnames cannot coexist at the same time in a repository, but we do not want to discard the push certificate responsible for deleting the ref, which we would have to do if refs/foo in the push cert tree changed from a tree to a blob. The "@{cert}" syntax is at least somewhat consistent with gitrevisions(7) wherein @{...} describe operators on ref names. As we cannot (currently) atomically update the push cert ref with the refs that were updated, this operation is inherently racy. Kick the can down the road by pushing this burden on callers. [1] https://github.com/sitaramc/gitolite/blob/cf062b8bb6b21a52f7c5002d33fbc950762c1aa7/contrib/hooks/repo-specific/save-push-signatures Change-Id: Id3eb32416f969fba4b5e4d9c4b47053c564b0ccd
Diffstat (limited to 'org.eclipse.jgit.test')
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java304
1 files changed, 304 insertions, 0 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java
new file mode 100644
index 0000000000..d2faaa7742
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2015, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import static org.eclipse.jgit.lib.ObjectId.zeroId;
+import static org.eclipse.jgit.lib.RefUpdate.Result.FAST_FORWARD;
+import static org.eclipse.jgit.lib.RefUpdate.Result.LOCK_FAILURE;
+import static org.eclipse.jgit.lib.RefUpdate.Result.NEW;
+import static org.eclipse.jgit.lib.RefUpdate.Result.NO_CHANGE;
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PushCertificateStoreTest {
+ private static final ObjectId ID1 =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+
+ private static final ObjectId ID2 =
+ ObjectId.fromString("badc0ffebadc0ffebadc0ffebadc0ffebadc0ffe");
+
+ private static PushCertificate newCert(String... updateLines) {
+ StringBuilder cert = new StringBuilder(
+ "certificate version 0.1\n"
+ + "pusher Dave Borowitz <dborowitz@google.com> 1433954361 -0700\n"
+ + "pushee git://localhost/repo.git\n"
+ + "nonce 1433954361-bde756572d665bba81d8\n"
+ + "\n");
+ for (String updateLine : updateLines) {
+ cert.append(updateLine).append('\n');
+ }
+ cert.append(
+ "-----BEGIN PGP SIGNATURE-----\n"
+ + "DUMMY/SIGNATURE\n"
+ + "-----END PGP SIGNATURE-----\n");
+ try {
+ return PushCertificateParser.fromReader(new InputStreamReader(
+ new ByteArrayInputStream(Constants.encode(cert.toString()))));
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ private static String command(ObjectId oldId, ObjectId newId, String ref) {
+ return oldId.name() + " " + newId.name() + " " + ref;
+ }
+
+ private AtomicInteger ts = new AtomicInteger(1433954361);
+ private InMemoryRepository repo;
+ private PushCertificateStore store;
+
+ @Before
+ public void setUp() throws Exception {
+ repo = new InMemoryRepository(new DfsRepositoryDescription("repo"));
+ store = newStore();
+ }
+
+ @Test
+ public void missingRef() throws Exception {
+ assertCerts("refs/heads/master");
+ }
+
+ @Test
+ public void saveNoChange() throws Exception {
+ assertEquals(NO_CHANGE, store.save());
+ }
+
+ @Test
+ public void saveOneCertOnOneRef() throws Exception {
+ PersonIdent ident = newIdent();
+ PushCertificate addMaster = newCert(
+ command(zeroId(), ID1, "refs/heads/master"));
+ store.put(addMaster, ident);
+ assertEquals(NEW, store.save());
+ assertCerts("refs/heads/master", addMaster);
+ assertCerts("refs/heads/branch");
+
+ try (RevWalk rw = new RevWalk(repo)) {
+ RevCommit c = rw.parseCommit(repo.resolve(PushCertificateStore.REF_NAME));
+ rw.parseBody(c);
+ assertEquals("Store push certificate for refs/heads/master\n",
+ c.getFullMessage());
+ assertEquals(ident, c.getAuthorIdent());
+ assertEquals(ident, c.getCommitterIdent());
+ }
+ }
+
+ @Test
+ public void saveTwoCertsOnSameRefInTwoUpdates() throws Exception {
+ PushCertificate addMaster = newCert(
+ command(zeroId(), ID1, "refs/heads/master"));
+ store.put(addMaster, newIdent());
+ assertEquals(NEW, store.save());
+ PushCertificate updateMaster = newCert(
+ command(ID1, ID2, "refs/heads/master"));
+ store.put(updateMaster, newIdent());
+ assertEquals(FAST_FORWARD, store.save());
+ assertCerts("refs/heads/master", updateMaster, addMaster);
+ }
+
+ @Test
+ public void saveTwoCertsOnSameRefInOneUpdate() throws Exception {
+ PersonIdent ident1 = newIdent();
+ PersonIdent ident2 = newIdent();
+ PushCertificate updateMaster = newCert(
+ command(ID1, ID2, "refs/heads/master"));
+ store.put(updateMaster, ident2);
+ PushCertificate addMaster = newCert(
+ command(zeroId(), ID1, "refs/heads/master"));
+ store.put(addMaster, ident1);
+ assertEquals(NEW, store.save());
+ assertCerts("refs/heads/master", updateMaster, addMaster);
+ }
+
+ @Test
+ public void saveTwoCertsOnDifferentRefsInOneUpdate() throws Exception {
+ PersonIdent ident1 = newIdent();
+ PersonIdent ident3 = newIdent();
+ PushCertificate addBranch = newCert(
+ command(zeroId(), ID1, "refs/heads/branch"));
+ store.put(addBranch, ident3);
+ PushCertificate addMaster = newCert(
+ command(zeroId(), ID1, "refs/heads/master"));
+ store.put(addMaster, ident1);
+ assertEquals(NEW, store.save());
+ assertCerts("refs/heads/master", addMaster);
+ assertCerts("refs/heads/branch", addBranch);
+ }
+
+ @Test
+ public void saveTwoCertsOnDifferentRefsInTwoUpdates() throws Exception {
+ PushCertificate addMaster = newCert(
+ command(zeroId(), ID1, "refs/heads/master"));
+ store.put(addMaster, newIdent());
+ assertEquals(NEW, store.save());
+ PushCertificate addBranch = newCert(
+ command(zeroId(), ID1, "refs/heads/branch"));
+ store.put(addBranch, newIdent());
+ assertEquals(FAST_FORWARD, store.save());
+ assertCerts("refs/heads/master", addMaster);
+ assertCerts("refs/heads/branch", addBranch);
+ }
+
+ @Test
+ public void saveOneCertOnMultipleRefs() throws Exception {
+ PersonIdent ident = newIdent();
+ PushCertificate addMasterAndBranch = newCert(
+ command(zeroId(), ID1, "refs/heads/branch"),
+ command(zeroId(), ID2, "refs/heads/master"));
+ store.put(addMasterAndBranch, ident);
+ assertEquals(NEW, store.save());
+ assertCerts("refs/heads/master", addMasterAndBranch);
+ assertCerts("refs/heads/branch", addMasterAndBranch);
+
+ try (RevWalk rw = new RevWalk(repo)) {
+ RevCommit c = rw.parseCommit(repo.resolve(PushCertificateStore.REF_NAME));
+ rw.parseBody(c);
+ assertEquals("Store push certificate for 2 refs\n", c.getFullMessage());
+ assertEquals(ident, c.getAuthorIdent());
+ assertEquals(ident, c.getCommitterIdent());
+ }
+ }
+
+ @Test
+ public void changeRefFileToDirectory() throws Exception {
+ PushCertificate deleteRefsHeads = newCert(
+ command(ID1, zeroId(), "refs/heads"));
+ store.put(deleteRefsHeads, newIdent());
+ PushCertificate addMaster = newCert(
+ command(zeroId(), ID1, "refs/heads/master"));
+ store.put(addMaster, newIdent());
+ assertEquals(NEW, store.save());
+ assertCerts("refs/heads", deleteRefsHeads);
+ assertCerts("refs/heads/master", addMaster);
+ }
+
+ @Test
+ public void getBeforeSaveDoesNotIncludePending() throws Exception {
+ PushCertificate addMaster = newCert(
+ command(zeroId(), ID1, "refs/heads/master"));
+ store.put(addMaster, newIdent());
+ assertEquals(NEW, store.save());
+
+ PushCertificate updateMaster = newCert(
+ command(ID1, ID2, "refs/heads/master"));
+ store.put(updateMaster, newIdent());
+
+ assertCerts("refs/heads/master", addMaster);
+ assertEquals(FAST_FORWARD, store.save());
+ assertCerts("refs/heads/master", updateMaster, addMaster);
+ }
+
+ @Test
+ public void lockFailure() throws Exception {
+ PushCertificateStore store1 = store;
+ PushCertificateStore store2 = newStore();
+ store2.get("refs/heads/master");
+
+ PushCertificate addMaster = newCert(
+ command(zeroId(), ID1, "refs/heads/master"));
+ store1.put(addMaster, newIdent());
+ assertEquals(NEW, store1.save());
+
+ PushCertificate addBranch = newCert(
+ command(zeroId(), ID2, "refs/heads/branch"));
+ store2.put(addBranch, newIdent());
+
+ assertEquals(LOCK_FAILURE, store2.save());
+ // Reread ref after lock failure.
+ assertCerts(store2, "refs/heads/master", addMaster);
+ assertCerts(store2, "refs/heads/branch");
+
+ assertEquals(FAST_FORWARD, store2.save());
+ assertCerts(store2, "refs/heads/master", addMaster);
+ assertCerts(store2, "refs/heads/branch", addBranch);
+ }
+
+ private PersonIdent newIdent() {
+ return new PersonIdent(
+ "A U. Thor", "author@example.com", ts.getAndIncrement(), 0);
+ }
+
+ private PushCertificateStore newStore() {
+ return new PushCertificateStore(repo);
+ }
+
+ private void assertCerts(String refName, PushCertificate... expected)
+ throws Exception {
+ assertCerts(store, refName, expected);
+ assertCerts(newStore(), refName, expected);
+ }
+
+ private static void assertCerts(PushCertificateStore store, String refName,
+ PushCertificate... expected) throws Exception {
+ List<PushCertificate> ex = Arrays.asList(expected);
+ PushCertificate first = !ex.isEmpty() ? ex.get(0) : null;
+ assertEquals(first, store.get(refName));
+ assertEquals(ex, toList(store.getAll(refName)));
+ }
+
+ private static <T> List<T> toList(Iterable<T> it) {
+ List<T> list = new ArrayList<>();
+ for (T t : it) {
+ list.add(t);
+ }
+ return list;
+ }
+}