Add options to refs/for/ magic branch syntax
Git doesn't want to modify the network protocol to support passing data from the git push client to the server. Work around this by embedding option data into a new style of reference specification: refs/for/master%r=alice,cc=bob,cc=charlie,topic=options is now parsed by the server as: - set topic to "options" - CC charlie and bob - add reviewer alice - for branch refs/heads/master If % is used the "extra information" after the branch name is parsed as options with args4j. Each option is delimited by ",". Selecting publish vs. draft should be done with options draft or publish, appearing anywhere in the refspec after the % marker: refs/for/master%draft refs/for/master%draft,r=alice refs/for/master%r=alice,draft refs/for/master%r=alice,publish Change-Id: I895bd1218c2099b5b45cac943039bbd12565370c
This commit is contained in:
@@ -21,7 +21,6 @@ import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.RequestCleanup;
|
||||
import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
|
||||
import com.google.gerrit.server.changedetail.PublishDraft;
|
||||
import com.google.gerrit.server.git.AsyncReceiveCommits;
|
||||
import com.google.gerrit.server.git.BanCommit;
|
||||
import com.google.gerrit.server.git.MergeOp;
|
||||
import com.google.gerrit.server.git.MetaDataUpdate;
|
||||
@@ -50,7 +49,6 @@ public class GerritRequestModule extends FactoryModule {
|
||||
|
||||
factory(SubmoduleOp.Factory.class);
|
||||
factory(MergeOp.Factory.class);
|
||||
install(new AsyncReceiveCommits.Module());
|
||||
|
||||
// Not really per-request, but dammit, I don't know where else to
|
||||
// easily park this stuff.
|
||||
|
||||
@@ -27,6 +27,7 @@ import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_RE
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.LinkedListMultimap;
|
||||
@@ -81,6 +82,7 @@ import com.google.gerrit.server.project.RefControl;
|
||||
import com.google.gerrit.server.ssh.SshInfo;
|
||||
import com.google.gerrit.server.util.MagicBranch;
|
||||
import com.google.gerrit.server.util.RequestScopePropagator;
|
||||
import com.google.gerrit.util.cli.CmdLineParser;
|
||||
import com.google.gwtorm.server.AtomicUpdate;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.gwtorm.server.ResultSet;
|
||||
@@ -113,10 +115,13 @@ import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand.Result;
|
||||
import org.eclipse.jgit.transport.ReceivePack;
|
||||
import org.eclipse.jgit.transport.UploadPack;
|
||||
import org.kohsuke.args4j.CmdLineException;
|
||||
import org.kohsuke.args4j.Option;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -234,6 +239,7 @@ public class ReceiveCommits {
|
||||
private final ReviewDb db;
|
||||
private final SchemaFactory<ReviewDb> schemaFactory;
|
||||
private final AccountResolver accountResolver;
|
||||
private final CmdLineParser.Factory optionParserFactory;
|
||||
private final CreateChangeSender.Factory createChangeSenderFactory;
|
||||
private final MergedSender.Factory mergedSenderFactory;
|
||||
private final ReplacePatchSetSender.Factory replacePatchSetFactory;
|
||||
@@ -284,6 +290,7 @@ public class ReceiveCommits {
|
||||
ReceiveCommits(final ReviewDb db,
|
||||
final SchemaFactory<ReviewDb> schemaFactory,
|
||||
final AccountResolver accountResolver,
|
||||
final CmdLineParser.Factory optionParserFactory,
|
||||
final CreateChangeSender.Factory createChangeSenderFactory,
|
||||
final MergedSender.Factory mergedSenderFactory,
|
||||
final ReplacePatchSetSender.Factory replacePatchSetFactory,
|
||||
@@ -312,6 +319,7 @@ public class ReceiveCommits {
|
||||
this.db = db;
|
||||
this.schemaFactory = schemaFactory;
|
||||
this.accountResolver = accountResolver;
|
||||
this.optionParserFactory = optionParserFactory;
|
||||
this.createChangeSenderFactory = createChangeSenderFactory;
|
||||
this.mergedSenderFactory = mergedSenderFactory;
|
||||
this.replacePatchSetFactory = replacePatchSetFactory;
|
||||
@@ -971,15 +979,38 @@ public class ReceiveCommits {
|
||||
}
|
||||
|
||||
private static class MagicBranchInput {
|
||||
private static final Splitter COMMAS = Splitter.on(',').omitEmptyStrings();
|
||||
|
||||
final ReceiveCommand cmd;
|
||||
Branch.NameKey dest;
|
||||
RefControl ctl;
|
||||
String topic;
|
||||
Set<Account.Id> reviewer = Sets.newLinkedHashSet();
|
||||
Set<Account.Id> cc = Sets.newLinkedHashSet();
|
||||
|
||||
@Option(name = "--topic", metaVar = "NAME", usage = "attach topic to changes")
|
||||
String topic;
|
||||
|
||||
@Option(name = "--draft", usage = "mark new/update changes as draft")
|
||||
boolean draft;
|
||||
|
||||
@Option(name = "-r", metaVar = "EMAIL", usage = "add reviewer to changes")
|
||||
void reviewer(Account.Id id) {
|
||||
reviewer.add(id);
|
||||
}
|
||||
|
||||
@Option(name = "--cc", metaVar = "EMAIL", usage = "notify user by CC")
|
||||
void cc(Account.Id id) {
|
||||
cc.add(id);
|
||||
}
|
||||
|
||||
@Option(name = "--publish", usage = "publish new/updated changes")
|
||||
void publish(boolean publish) {
|
||||
draft = !publish;
|
||||
}
|
||||
|
||||
MagicBranchInput(ReceiveCommand cmd) {
|
||||
this.cmd = cmd;
|
||||
this.draft = cmd.getRefName().startsWith(MagicBranch.NEW_DRAFT_CHANGE);
|
||||
}
|
||||
|
||||
boolean isRef(Ref ref) {
|
||||
@@ -987,66 +1018,59 @@ public class ReceiveCommits {
|
||||
}
|
||||
|
||||
boolean isDraft() {
|
||||
return cmd.getRefName().startsWith(MagicBranch.NEW_DRAFT_CHANGE);
|
||||
return draft;
|
||||
}
|
||||
|
||||
MailRecipients getMailRecipients() {
|
||||
return new MailRecipients(reviewer, cc);
|
||||
}
|
||||
|
||||
String parse(Repository repo, Map<String, Ref> advertisedRefs) {
|
||||
String destBranchName = MagicBranch.getDestBranchName(cmd.getRefName());
|
||||
if (!destBranchName.startsWith(Constants.R_REFS)) {
|
||||
destBranchName = Constants.R_HEADS + destBranchName;
|
||||
String parse(CmdLineParser clp, Repository repo, Set<String> refs)
|
||||
throws CmdLineException {
|
||||
String ref = MagicBranch.getDestBranchName(cmd.getRefName());
|
||||
if (!ref.startsWith(Constants.R_REFS)) {
|
||||
ref = Constants.R_HEADS + ref;
|
||||
}
|
||||
|
||||
String head;
|
||||
try {
|
||||
head = repo.getFullBranch();
|
||||
} catch (IOException e) {
|
||||
log.error("Cannot read HEAD symref", e);
|
||||
cmd.setResult(REJECTED_OTHER_REASON, "internal error");
|
||||
return null;
|
||||
int optionStart = ref.indexOf('%');
|
||||
if (0 < optionStart) {
|
||||
ListMultimap<String, String> options = LinkedListMultimap.create();
|
||||
for (String s : COMMAS.split(ref.substring(optionStart + 1))) {
|
||||
int e = s.indexOf('=');
|
||||
if (0 < e) {
|
||||
options.put(s.substring(0, e), s.substring(e + 1));
|
||||
} else {
|
||||
options.put(s, "");
|
||||
}
|
||||
}
|
||||
clp.parseOptionMap(options);
|
||||
ref = ref.substring(0, optionStart);
|
||||
}
|
||||
|
||||
// Split the destination branch by branch and topic. The topic
|
||||
// Split the destination branch by branch and topic. The topic
|
||||
// suffix is entirely optional, so it might not even exist.
|
||||
int split = destBranchName.length();
|
||||
String head = readHEAD(repo);
|
||||
int split = ref.length();
|
||||
for (;;) {
|
||||
String name = destBranchName.substring(0, split);
|
||||
|
||||
if (advertisedRefs.containsKey(name)) {
|
||||
// We advertised the branch to the client so we know
|
||||
// the branch exists. Target this branch for the upload.
|
||||
break;
|
||||
} else if (head.equals(name)) {
|
||||
// We didn't advertise the branch, because it doesn't exist yet.
|
||||
// Allow it anyway as HEAD is a symbolic reference to the name.
|
||||
String name = ref.substring(0, split);
|
||||
if (refs.contains(name) || name.equals(head)) {
|
||||
break;
|
||||
}
|
||||
|
||||
split = name.lastIndexOf('/', split - 1);
|
||||
if (split <= Constants.R_REFS.length()) {
|
||||
String n = destBranchName;
|
||||
if (n.startsWith(Constants.R_HEADS)) {
|
||||
n = n.substring(Constants.R_HEADS.length());
|
||||
}
|
||||
cmd.setResult(REJECTED_OTHER_REASON, "branch " + n + " not found");
|
||||
return null;
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
|
||||
if (split < destBranchName.length()) {
|
||||
String t = destBranchName.substring(split + 1);
|
||||
topic = Strings.emptyToNull(t);
|
||||
if (split < ref.length()) {
|
||||
topic = Strings.emptyToNull(ref.substring(split + 1));
|
||||
}
|
||||
return destBranchName.substring(0, split);
|
||||
return ref.substring(0, split);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseMagicBranch(final ReceiveCommand cmd) {
|
||||
// Permit exactly one new change request per push.
|
||||
//
|
||||
if (magicBranch != null) {
|
||||
reject(cmd, "duplicate request");
|
||||
return;
|
||||
@@ -1056,10 +1080,32 @@ public class ReceiveCommits {
|
||||
magicBranch.reviewer.addAll(reviewersFromCommandLine);
|
||||
magicBranch.cc.addAll(ccFromCommandLine);
|
||||
|
||||
String ref = magicBranch.parse(repo, rp.getAdvertisedRefs());
|
||||
if (ref == null) {
|
||||
// Command was already rejected, but progress needs to update.
|
||||
commandProgress.update(1);
|
||||
String ref;
|
||||
CmdLineParser clp = optionParserFactory.create(magicBranch);
|
||||
try {
|
||||
ref = magicBranch.parse(clp, repo, rp.getAdvertisedRefs().keySet());
|
||||
} catch (CmdLineException e) {
|
||||
if (!clp.wasHelpRequestedByOption()) {
|
||||
reject(cmd, e.getMessage());
|
||||
return;
|
||||
}
|
||||
ref = null; // never happen
|
||||
}
|
||||
if (clp.wasHelpRequestedByOption()) {
|
||||
StringWriter w = new StringWriter();
|
||||
w.write("\nHelp for refs/for/branch:\n\n");
|
||||
clp.printUsage(w, null);
|
||||
addMessage(w.toString());
|
||||
reject(cmd, "see help");
|
||||
return;
|
||||
}
|
||||
if (!rp.getAdvertisedRefs().containsKey(ref) && !ref.equals(readHEAD(repo))) {
|
||||
if (ref.startsWith(Constants.R_HEADS)) {
|
||||
String n = ref.substring(Constants.R_HEADS.length());
|
||||
reject(cmd, "branch " + n + " not found");
|
||||
} else {
|
||||
reject(cmd, ref + " not found");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1108,6 +1154,15 @@ public class ReceiveCommits {
|
||||
}
|
||||
}
|
||||
|
||||
private static String readHEAD(Repository repo) {
|
||||
try {
|
||||
return repo.getFullBranch();
|
||||
} catch (IOException e) {
|
||||
log.error("Cannot read HEAD symref", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a list of commits to reject from {@code refs/meta/reject-commits}.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user