Allow saving push certs on a subset of refs
Consider a BatchRefUpdate produced by Gerrit Code Review, where the original command pushed over the wire might refer to "refs/for/master", but that command is ignored and replaced with some additional commands like creating "refs/changes/34/1234/1". We do not want to store the cert in "refs/for/master@{cert}", since that may lead someone looking to the ref to the incorrect conclusion that that ref exists. Add a separate put method that takes a collection of commands, and only stores certs on those refs that have a matching command in the cert. Change-Id: I4661bfe2ead28a2883b33a4e3dfe579b3157d68a
This commit is contained in:
parent
16ba9919db
commit
5706c8e38e
|
@ -55,6 +55,7 @@
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@ -284,15 +285,36 @@ public void saveInBatch() throws Exception {
|
||||||
List<ReceiveCommand> commands = batch.getCommands();
|
List<ReceiveCommand> commands = batch.getCommands();
|
||||||
assertEquals(1, commands.size());
|
assertEquals(1, commands.size());
|
||||||
ReceiveCommand cmd = commands.get(0);
|
ReceiveCommand cmd = commands.get(0);
|
||||||
|
|
||||||
try (RevWalk rw = new RevWalk(repo)) {
|
|
||||||
assertEquals("refs/meta/push-certs", cmd.getRefName());
|
assertEquals("refs/meta/push-certs", cmd.getRefName());
|
||||||
assertEquals(ReceiveCommand.Result.NOT_ATTEMPTED, cmd.getResult());
|
assertEquals(ReceiveCommand.Result.NOT_ATTEMPTED, cmd.getResult());
|
||||||
|
|
||||||
|
try (RevWalk rw = new RevWalk(repo)) {
|
||||||
batch.execute(rw, NullProgressMonitor.INSTANCE);
|
batch.execute(rw, NullProgressMonitor.INSTANCE);
|
||||||
assertEquals(ReceiveCommand.Result.OK, cmd.getResult());
|
assertEquals(ReceiveCommand.Result.OK, cmd.getResult());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putMatchingWithNoMatchingRefs() throws Exception {
|
||||||
|
PushCertificate addMaster = newCert(
|
||||||
|
command(zeroId(), ID1, "refs/heads/master"),
|
||||||
|
command(zeroId(), ID2, "refs/heads/branch"));
|
||||||
|
store.put(addMaster, newIdent(), Collections.<ReceiveCommand> emptyList());
|
||||||
|
assertEquals(NO_CHANGE, store.save());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putMatchingWithSomeMatchingRefs() throws Exception {
|
||||||
|
PushCertificate addMasterAndBranch = newCert(
|
||||||
|
command(zeroId(), ID1, "refs/heads/master"),
|
||||||
|
command(zeroId(), ID2, "refs/heads/branch"));
|
||||||
|
store.put(addMasterAndBranch, newIdent(),
|
||||||
|
Collections.singleton(addMasterAndBranch.getCommands().get(0)));
|
||||||
|
assertEquals(NEW, store.save());
|
||||||
|
assertCerts("refs/heads/master", addMasterAndBranch);
|
||||||
|
assertCerts("refs/heads/branch");
|
||||||
|
}
|
||||||
|
|
||||||
private PersonIdent newIdent() {
|
private PersonIdent newIdent() {
|
||||||
return new PersonIdent(
|
return new PersonIdent(
|
||||||
"A U. Thor", "author@example.com", ts.getAndIncrement(), 0);
|
"A U. Thor", "author@example.com", ts.getAndIncrement(), 0);
|
||||||
|
|
|
@ -55,10 +55,13 @@
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
import org.eclipse.jgit.dircache.DirCache;
|
import org.eclipse.jgit.dircache.DirCache;
|
||||||
|
@ -106,10 +109,13 @@ public class PushCertificateStore implements AutoCloseable {
|
||||||
private static class PendingCert {
|
private static class PendingCert {
|
||||||
private PushCertificate cert;
|
private PushCertificate cert;
|
||||||
private PersonIdent ident;
|
private PersonIdent ident;
|
||||||
|
private Collection<ReceiveCommand> matching;
|
||||||
|
|
||||||
private PendingCert(PushCertificate cert, PersonIdent ident) {
|
private PendingCert(PushCertificate cert, PersonIdent ident,
|
||||||
|
Collection<ReceiveCommand> matching) {
|
||||||
this.cert = cert;
|
this.cert = cert;
|
||||||
this.ident = ident;
|
this.ident = ident;
|
||||||
|
this.matching = matching;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,7 +314,34 @@ private static PushCertificate read(TreeWalk tw) throws IOException {
|
||||||
* #save()}.
|
* #save()}.
|
||||||
*/
|
*/
|
||||||
public void put(PushCertificate cert, PersonIdent ident) {
|
public void put(PushCertificate cert, PersonIdent ident) {
|
||||||
pending.add(new PendingCert(cert, ident));
|
put(cert, ident, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put a certificate to be saved to the store, matching a set of commands.
|
||||||
|
* <p>
|
||||||
|
* Like {@link #put(PushCertificate, PersonIdent)}, except a value is only
|
||||||
|
* stored for a push certificate if there is a corresponding command in the
|
||||||
|
* list that exactly matches the old/new values mentioned in the push
|
||||||
|
* certificate.
|
||||||
|
* <p>
|
||||||
|
* Pending certificates added to this method are not returned by {@link
|
||||||
|
* #get(String)} and {@link #getAll(String)} until after calling {@link
|
||||||
|
* #save()}.
|
||||||
|
*
|
||||||
|
* @param cert
|
||||||
|
* certificate to store.
|
||||||
|
* @param ident
|
||||||
|
* identity for the commit that stores this certificate. Pending
|
||||||
|
* certificates are sorted by identity timestamp during {@link
|
||||||
|
* #save()}.
|
||||||
|
* @param matching
|
||||||
|
* only store certs for the refs listed in this list whose values
|
||||||
|
* match the commands in the cert.
|
||||||
|
*/
|
||||||
|
public void put(PushCertificate cert, PersonIdent ident,
|
||||||
|
Collection<ReceiveCommand> matching) {
|
||||||
|
pending.add(new PendingCert(cert, ident, matching));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -355,8 +388,9 @@ public RefUpdate.Result save() throws IOException {
|
||||||
* #put(PushCertificate, PersonIdent)}, in order of identity timestamps, all
|
* #put(PushCertificate, PersonIdent)}, in order of identity timestamps, all
|
||||||
* commits are flushed, and a single command is added to the batch.
|
* commits are flushed, and a single command is added to the batch.
|
||||||
* <p>
|
* <p>
|
||||||
* The pending list is <em>not</em> cleared. If the ref update succeeds, the
|
* The cached ref value and pending list are <em>not</em> cleared. If the ref
|
||||||
* caller is responsible for calling {@link #clear()}.
|
* update succeeds, the caller is responsible for calling {@link #close()}
|
||||||
|
* and/or {@link #clear()}.
|
||||||
*
|
*
|
||||||
* @param batch
|
* @param batch
|
||||||
* update to save to.
|
* update to save to.
|
||||||
|
@ -423,10 +457,27 @@ private DirCache newDirCache() throws IOException {
|
||||||
|
|
||||||
private ObjectId saveCert(ObjectInserter inserter, DirCache dc,
|
private ObjectId saveCert(ObjectInserter inserter, DirCache dc,
|
||||||
PendingCert pc, ObjectId curr) throws IOException {
|
PendingCert pc, ObjectId curr) throws IOException {
|
||||||
|
Map<String, ReceiveCommand> byRef;
|
||||||
|
if (pc.matching != null) {
|
||||||
|
byRef = new HashMap<>();
|
||||||
|
for (ReceiveCommand cmd : pc.matching) {
|
||||||
|
if (byRef.put(cmd.getRefName(), cmd) != null) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
byRef = null;
|
||||||
|
}
|
||||||
|
|
||||||
DirCacheEditor editor = dc.editor();
|
DirCacheEditor editor = dc.editor();
|
||||||
String certText = pc.cert.toText() + pc.cert.getSignature();
|
String certText = pc.cert.toText() + pc.cert.getSignature();
|
||||||
final ObjectId certId = inserter.insert(OBJ_BLOB, certText.getBytes(UTF_8));
|
final ObjectId certId = inserter.insert(OBJ_BLOB, certText.getBytes(UTF_8));
|
||||||
|
boolean any = false;
|
||||||
for (ReceiveCommand cmd : pc.cert.getCommands()) {
|
for (ReceiveCommand cmd : pc.cert.getCommands()) {
|
||||||
|
if (byRef != null && !commandsEqual(cmd, byRef.get(cmd.getRefName()))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
any = true;
|
||||||
editor.add(new PathEdit(pathName(cmd.getRefName())) {
|
editor.add(new PathEdit(pathName(cmd.getRefName())) {
|
||||||
@Override
|
@Override
|
||||||
public void apply(DirCacheEntry ent) {
|
public void apply(DirCacheEntry ent) {
|
||||||
|
@ -435,6 +486,9 @@ public void apply(DirCacheEntry ent) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (!any) {
|
||||||
|
return curr;
|
||||||
|
}
|
||||||
editor.finish();
|
editor.finish();
|
||||||
CommitBuilder cb = new CommitBuilder();
|
CommitBuilder cb = new CommitBuilder();
|
||||||
cb.setAuthor(pc.ident);
|
cb.setAuthor(pc.ident);
|
||||||
|
@ -449,6 +503,15 @@ public void apply(DirCacheEntry ent) {
|
||||||
return inserter.insert(OBJ_COMMIT, cb.build());
|
return inserter.insert(OBJ_COMMIT, cb.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean commandsEqual(ReceiveCommand c1, ReceiveCommand c2) {
|
||||||
|
if (c1 == null || c2 == null) {
|
||||||
|
return c1 == c2;
|
||||||
|
}
|
||||||
|
return c1.getRefName().equals(c2.getRefName())
|
||||||
|
&& c1.getOldId().equals(c2.getOldId())
|
||||||
|
&& c1.getNewId().equals(c2.getNewId());
|
||||||
|
}
|
||||||
|
|
||||||
private RefUpdate.Result updateRef(ObjectId newId) throws IOException {
|
private RefUpdate.Result updateRef(ObjectId newId) throws IOException {
|
||||||
RefUpdate ru = db.updateRef(REF_NAME);
|
RefUpdate ru = db.updateRef(REF_NAME);
|
||||||
ru.setExpectedOldObjectId(commit != null ? commit : ObjectId.zeroId());
|
ru.setExpectedOldObjectId(commit != null ? commit : ObjectId.zeroId());
|
||||||
|
|
Loading…
Reference in New Issue