summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn O. Pearce <sop@google.com>2009-01-27 19:34:53 -0800
committerShawn O. Pearce <sop@google.com>2009-01-27 19:34:53 -0800
commit20f6e9bdfd81cbd92ed04d7628bc1688da17f43e (patch)
tree4ddf5debd59318e1ea863a8ef13b384aa68f33ff
parentb4f01838f33d857a1a350a5ceaae8ea42f53423a (diff)
Add new project rights to allow direct branch updates over SSH
Four new rights can be used to control access over the SSH server: * Push Annotated Tag (refs/tags/*) * Push Branch: (refs/heads/*) - Update Existing - Create New - Force Update (aka non-fast forward) ; Delete These can be granted on a per-group/per-project basis to permit users to remotely control branches over the Git SSH interface. Its mostly useful for groups that are used to having a standard Git server, where "anything goes" is permitted within the scope of the project. Signed-off-by: Shawn O. Pearce <sop@google.com>
-rw-r--r--appjar/src/main/java/com/google/gerrit/client/reviewdb/ApprovalCategory.java11
-rw-r--r--appjar/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java2
-rw-r--r--appjar/src/main/java/com/google/gerrit/server/GerritServer.java47
-rw-r--r--appjar/src/main/java/com/google/gerrit/server/ssh/Receive.java165
4 files changed, 222 insertions, 3 deletions
diff --git a/appjar/src/main/java/com/google/gerrit/client/reviewdb/ApprovalCategory.java b/appjar/src/main/java/com/google/gerrit/client/reviewdb/ApprovalCategory.java
index c04c6660ff..36942af0c4 100644
--- a/appjar/src/main/java/com/google/gerrit/client/reviewdb/ApprovalCategory.java
+++ b/appjar/src/main/java/com/google/gerrit/client/reviewdb/ApprovalCategory.java
@@ -30,6 +30,17 @@ public final class ApprovalCategory {
public static final ApprovalCategory.Id READ =
new ApprovalCategory.Id("READ");
+ /** Id of the special "Push Annotated Tag" action (and category). */
+ public static final ApprovalCategory.Id PUSH_TAG =
+ new ApprovalCategory.Id("pTAG");
+
+ /** Id of the special "Push Branch" action (and category). */
+ public static final ApprovalCategory.Id PUSH_HEAD =
+ new ApprovalCategory.Id("pHD");
+ public static final short PUSH_HEAD_UPDATE = 1;
+ public static final short PUSH_HEAD_CREATE = 2;
+ public static final short PUSH_HEAD_REPLACE = 3;
+
public static class Id extends StringKey<Key<?>> {
@Column(length = 4)
protected String id;
diff --git a/appjar/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java b/appjar/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java
index 4c7188bc72..a5e2232d47 100644
--- a/appjar/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java
+++ b/appjar/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java
@@ -21,7 +21,7 @@ import com.google.gwtorm.client.Sequence;
/** The review service database schema. */
public interface ReviewDb extends Schema {
- public static final int VERSION = 2;
+ public static final int VERSION = 3;
@Relation
SchemaVersionAccess schemaVersion();
diff --git a/appjar/src/main/java/com/google/gerrit/server/GerritServer.java b/appjar/src/main/java/com/google/gerrit/server/GerritServer.java
index d4928f2dd3..eca8e783cf 100644
--- a/appjar/src/main/java/com/google/gerrit/server/GerritServer.java
+++ b/appjar/src/main/java/com/google/gerrit/server/GerritServer.java
@@ -330,6 +330,40 @@ public class GerritServer {
txn.commit();
}
+ private void initPushTagCategory(final ReviewDb c) throws OrmException {
+ final Transaction txn = c.beginTransaction();
+ final ApprovalCategory cat;
+ final ArrayList<ApprovalCategoryValue> vals;
+
+ cat = new ApprovalCategory(ApprovalCategory.PUSH_TAG, "Push Annotated Tag");
+ cat.setPosition((short) -1);
+ cat.setFunctionName(NoOpFunction.NAME);
+ vals = new ArrayList<ApprovalCategoryValue>();
+ vals.add(value(cat, 1, "Create Tag"));
+ c.approvalCategories().insert(Collections.singleton(cat), txn);
+ c.approvalCategoryValues().insert(vals, txn);
+ txn.commit();
+ }
+
+ private void initPushUpdateBranchCategory(final ReviewDb c)
+ throws OrmException {
+ final Transaction txn = c.beginTransaction();
+ final ApprovalCategory cat;
+ final ArrayList<ApprovalCategoryValue> vals;
+
+ cat = new ApprovalCategory(ApprovalCategory.PUSH_HEAD, "Push Branch");
+ cat.setPosition((short) -1);
+ cat.setFunctionName(NoOpFunction.NAME);
+ vals = new ArrayList<ApprovalCategoryValue>();
+ vals.add(value(cat, ApprovalCategory.PUSH_HEAD_UPDATE, "Update Branch"));
+ vals.add(value(cat, ApprovalCategory.PUSH_HEAD_CREATE, "Create Branch"));
+ vals.add(value(cat, ApprovalCategory.PUSH_HEAD_REPLACE,
+ "Force Push Branch; Delete Branch"));
+ c.approvalCategories().insert(Collections.singleton(cat), txn);
+ c.approvalCategoryValues().insert(vals, txn);
+ txn.commit();
+ }
+
private static ApprovalCategoryValue value(final ApprovalCategory cat,
final int value, final String name) {
return new ApprovalCategoryValue(new ApprovalCategoryValue.Id(cat.getId(),
@@ -363,8 +397,19 @@ public class GerritServer {
initVerifiedCategory(c);
initCodeReviewCategory(c);
initSubmitCategory(c);
+ initPushTagCategory(c);
+ initPushUpdateBranchCategory(c);
+ }
+
+ if (sVer.versionNbr == 2) {
+ initPushTagCategory(c);
+ initPushUpdateBranchCategory(c);
+
+ sVer.versionNbr = 3;
+ c.schemaVersion().update(Collections.singleton(sVer));
+ }
- } else if (sVer.versionNbr == ReviewDb.VERSION) {
+ if (sVer.versionNbr == ReviewDb.VERSION) {
sConfig = c.systemConfig().get(new SystemConfig.Key());
} else {
diff --git a/appjar/src/main/java/com/google/gerrit/server/ssh/Receive.java b/appjar/src/main/java/com/google/gerrit/server/ssh/Receive.java
index 5ead398186..13a5dafba1 100644
--- a/appjar/src/main/java/com/google/gerrit/server/ssh/Receive.java
+++ b/appjar/src/main/java/com/google/gerrit/server/ssh/Receive.java
@@ -14,6 +14,12 @@
package com.google.gerrit.server.ssh;
+import static com.google.gerrit.client.reviewdb.ApprovalCategory.PUSH_HEAD;
+import static com.google.gerrit.client.reviewdb.ApprovalCategory.PUSH_HEAD_UPDATE;
+import static com.google.gerrit.client.reviewdb.ApprovalCategory.PUSH_HEAD_CREATE;
+import static com.google.gerrit.client.reviewdb.ApprovalCategory.PUSH_HEAD_REPLACE;
+import static com.google.gerrit.client.reviewdb.ApprovalCategory.PUSH_TAG;
+
import com.google.gerrit.client.Link;
import com.google.gerrit.client.data.ApprovalType;
import com.google.gerrit.client.reviewdb.Account;
@@ -49,12 +55,16 @@ import org.spearce.jgit.lib.PersonIdent;
import org.spearce.jgit.lib.Ref;
import org.spearce.jgit.lib.RefUpdate;
import org.spearce.jgit.revwalk.RevCommit;
+import org.spearce.jgit.revwalk.RevObject;
import org.spearce.jgit.revwalk.RevSort;
+import org.spearce.jgit.revwalk.RevTag;
import org.spearce.jgit.revwalk.RevWalk;
+import org.spearce.jgit.transport.PostReceiveHook;
import org.spearce.jgit.transport.PreReceiveHook;
import org.spearce.jgit.transport.ReceiveCommand;
import org.spearce.jgit.transport.ReceivePack;
import org.spearce.jgit.transport.ReceiveCommand.Result;
+import org.spearce.jgit.transport.ReceiveCommand.Type;
import java.io.IOException;
import java.io.OutputStreamWriter;
@@ -125,6 +135,34 @@ class Receive extends AbstractGitCommand {
appendPatchSets();
}
});
+ rp.setPostReceiveHook(new PostReceiveHook() {
+ public void onPostReceive(final ReceivePack arg0,
+ final Collection<ReceiveCommand> commands) {
+ for (final ReceiveCommand c : commands) {
+ if (c.getResult() == Result.OK) {
+ if (isHead(c)) {
+ // Make sure the branch table matches the repository
+ //
+ switch (c.getType()) {
+ case CREATE:
+ insertBranchEntity(c);
+ break;
+ case DELETE:
+ deleteBranchEntity(c);
+ break;
+ }
+ }
+
+ if (isHead(c) || isTag(c)) {
+ // We only schedule heads and tags for replication.
+ // Change refs are scheduled when they are created.
+ //
+ PushQueue.scheduleUpdate(proj.getNameKey(), c.getRefName());
+ }
+ }
+ }
+ }
+ });
rp.receive(in, out, err);
if (!allNewChanges.isEmpty() && server.getCanonicalURL() != null) {
@@ -343,12 +381,88 @@ class Receive extends AbstractGitCommand {
continue;
}
+ switch (cmd.getType()) {
+ case CREATE:
+ parseCreate(cmd);
+ continue;
+
+ case UPDATE:
+ parseUpdate(cmd);
+ continue;
+
+ case DELETE:
+ case UPDATE_NONFASTFORWARD:
+ parseRewindOrDelete(cmd);
+ continue;
+ }
+
// Everything else is bogus as far as we are concerned.
//
reject(cmd);
}
}
+ private void parseCreate(final ReceiveCommand cmd) {
+ if (isHead(cmd) && canPerform(PUSH_HEAD, PUSH_HEAD_CREATE)) {
+ // Let the core receive process handle it
+
+ } else if (isTag(cmd) && canPerform(PUSH_TAG, (short) 1)) {
+ parseCreateTag(cmd);
+
+ } else {
+ reject(cmd);
+ }
+ }
+
+ private void parseCreateTag(final ReceiveCommand cmd) {
+ try {
+ final RevObject obj = rp.getRevWalk().parseAny(cmd.getNewId());
+ if (!(obj instanceof RevTag)) {
+ reject(cmd, "not annotated tag");
+ return;
+ }
+
+ final RevTag tag = (RevTag) obj;
+ final PersonIdent tagger = tag.getTaggerIdent();
+ if (tagger == null) {
+ reject(cmd, "no tagger");
+ return;
+ }
+
+ final String email = tagger.getEmailAddress();
+ if (!myEmails.contains(email)) {
+ reject(cmd, "invalid tagger " + email);
+ return;
+ }
+
+ // Let the core receive process handle it
+ //
+ } catch (IOException e) {
+ log.error("Bad tag " + cmd.getRefName() + " " + cmd.getNewId().name(), e);
+ reject(cmd, "invalid object");
+ }
+ }
+
+ private void parseUpdate(final ReceiveCommand cmd) {
+ if (isHead(cmd) && canPerform(PUSH_HEAD, PUSH_HEAD_UPDATE)) {
+ // Let the core receive process handle it
+ } else {
+ reject(cmd);
+ }
+ }
+
+ private void parseRewindOrDelete(final ReceiveCommand cmd) {
+ if (isHead(cmd) && canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE)) {
+ // Let the core receive process handle it
+
+ } else if (isHead(cmd) && cmd.getType() == Type.UPDATE_NONFASTFORWARD) {
+ cmd.setResult(ReceiveCommand.Result.REJECTED_NONFASTFORWARD);
+
+ } else {
+ reject(cmd);
+ }
+ }
+
private void parseNewChangeCommand(final ReceiveCommand cmd) {
// Permit exactly one new change request per push.
//
@@ -715,7 +829,7 @@ class Receive extends AbstractGitCommand {
change.setCurrentPatchSet(imp.getPatchSetInfo());
ChangeUtil.updated(change);
db.changes().update(Collections.singleton(change), txn);
-
+
final ReplaceResult result = new ReplaceResult();
result.change = change;
result.patchSet = ps;
@@ -767,6 +881,47 @@ class Receive extends AbstractGitCommand {
}
}
+ private void insertBranchEntity(final ReceiveCommand c) {
+ try {
+ final Branch.NameKey nameKey =
+ new Branch.NameKey(proj.getNameKey(), c.getRefName());
+ final Branch.Id idKey = new Branch.Id(db.nextBranchId());
+ final Branch b = new Branch(nameKey, idKey);
+ db.branches().insert(Collections.singleton(b));
+ } catch (OrmException e) {
+ final String msg = "database failure creating " + c.getRefName();
+ log.error(msg, e);
+
+ try {
+ err.write(("remote error: " + msg + "\n").getBytes("UTF-8"));
+ err.flush();
+ } catch (IOException e2) {
+ // Ignore errors writing to the client
+ }
+ }
+ }
+
+ private void deleteBranchEntity(final ReceiveCommand c) {
+ try {
+ final Branch.NameKey nameKey =
+ new Branch.NameKey(proj.getNameKey(), c.getRefName());
+ final Branch b = db.branches().get(nameKey);
+ if (b != null) {
+ db.branches().delete(Collections.singleton(b));
+ }
+ } catch (OrmException e) {
+ final String msg = "database failure deleting " + c.getRefName();
+ log.error(msg, e);
+
+ try {
+ err.write(("remote error: " + msg + "\n").getBytes("UTF-8"));
+ err.flush();
+ } catch (IOException e2) {
+ // Ignore errors writing to the client
+ }
+ }
+ }
+
private static void reject(final ReceiveCommand cmd) {
reject(cmd, "prohibited by Gerrit");
}
@@ -774,4 +929,12 @@ class Receive extends AbstractGitCommand {
private static void reject(final ReceiveCommand cmd, final String why) {
cmd.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, why);
}
+
+ private static boolean isTag(final ReceiveCommand cmd) {
+ return cmd.getRefName().startsWith(Constants.R_TAGS);
+ }
+
+ private static boolean isHead(final ReceiveCommand cmd) {
+ return cmd.getRefName().startsWith(Constants.R_HEADS);
+ }
}