diff options
author | Shawn O. Pearce <sop@google.com> | 2009-01-27 19:34:53 -0800 |
---|---|---|
committer | Shawn O. Pearce <sop@google.com> | 2009-01-27 19:34:53 -0800 |
commit | 20f6e9bdfd81cbd92ed04d7628bc1688da17f43e (patch) | |
tree | 4ddf5debd59318e1ea863a8ef13b384aa68f33ff | |
parent | b4f01838f33d857a1a350a5ceaae8ea42f53423a (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>
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); + } } |