Support topic branch tags on changes

The topic tag can be attached to a change during upload by suffixing
the 'refs/for/branch_name' with the topic label, treating the branch
as though it were a directory.

For example, to tag 'exp/nosql' onto a change headed for the
'sandbox/future-stuff' branch, git push can be used as:

  git push URL HEAD:refs/for/sandbox/future-stuff/exp-nosql

If the topic is supplied, it is displayed in the branch column of
a change, in parens.  If no topic was set, only the branch is shown.

Bug: issue 51
Change-Id: I07d6c137fc9aefa8c1ee1652bf1e7bcde9d33674
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2010-07-15 12:24:11 -07:00
parent 72fc9bb5a8
commit d50c94e759
11 changed files with 129 additions and 40 deletions

View File

@@ -55,7 +55,6 @@ import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@@ -159,6 +158,8 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
private Map<ObjectId, Ref> refsById;
private String destTopicName;
@Inject
ReceiveCommits(final ReviewDb db, final ApprovalTypes approvalTypes,
final AccountResolver accountResolver,
@@ -579,38 +580,55 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
destBranchName = Constants.R_HEADS + destBranchName;
}
if (rp.getAdvertisedRefs().containsKey(destBranchName)) {
// We advertised the branch to the client so we know
// the branch exists. Target this branch for the upload.
//
destBranch = new Branch.NameKey(project.getNameKey(), destBranchName);
} else {
// We didn't advertise the branch, because it doesn't exist yet.
// Allow it anyway if HEAD is a symbolic reference to the name.
//
final String head;
try {
head = repo.getFullBranch();
} catch (IOException e) {
log.error("Cannot read HEAD symref", e);
reject(cmd, "internal error");
return;
}
if (head.equals(destBranchName)) {
destBranch = new Branch.NameKey(project.getNameKey(), destBranchName);
}
}
if (destBranch == null) {
String n = destBranchName;
if (n.startsWith(Constants.R_HEADS))
n = n.substring(Constants.R_HEADS.length());
reject(cmd, "branch " + n + " not found");
final String head;
try {
head = repo.getFullBranch();
} catch (IOException e) {
log.error("Cannot read HEAD symref", e);
reject(cmd, "internal error");
return;
}
// Split the destination branch by branch and topic. The topic
// suffix is entirely optional, so it might not even exist.
//
int split = destBranchName.length();
for (;;) {
String name = destBranchName.substring(0, split);
if (rp.getAdvertisedRefs().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.
//
break;
}
split = name.lastIndexOf('/', split - 1);
if (split <= Constants.R_HEADS.length()) {
String n = destBranchName;
if (n.startsWith(Constants.R_HEADS))
n = n.substring(Constants.R_HEADS.length());
reject(cmd, "branch " + n + " not found");
return;
}
}
if (split < destBranchName.length()) {
destTopicName = destBranchName.substring(split + 1);
} else {
// We use empty string here to denote the topic wasn't
// supplied, but the caller used the syntax that allows
// for a topic to be given.
//
destTopicName = "";
}
destBranch = new Branch.NameKey(project.getNameKey(), //
destBranchName.substring(0, split));
destBranchCtl = projectControl.controlForRef(destBranch);
if (!destBranchCtl.canUpload()) {
reject(cmd);
@@ -858,6 +876,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
final Change change =
new Change(changeKey, new Change.Id(db.nextChangeId()), me, destBranch);
change.setTopic(destTopicName.isEmpty() ? null : destTopicName);
change.nextPatchSetId();
final PatchSet ps = new PatchSet(change.currPatchSetId());
@@ -1158,6 +1177,11 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
@Override
public Change update(Change change) {
if (change.getStatus().isOpen()) {
if (destTopicName != null) {
change.setTopic(destTopicName.isEmpty() //
? null //
: destTopicName);
}
change.setStatus(Change.Status.NEW);
change.setCurrentPatchSet(result.info);
ChangeUtil.updated(change);

View File

@@ -32,7 +32,7 @@ import java.util.List;
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
private static final Class<? extends SchemaVersion> C = Schema_38.class;
private static final Class<? extends SchemaVersion> C = Schema_39.class;
public static class Module extends AbstractModule {
@Override

View File

@@ -0,0 +1,25 @@
// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.schema;
import com.google.inject.Inject;
import com.google.inject.Provider;
public class Schema_39 extends SchemaVersion {
@Inject
Schema_39(Provider<Schema_38> prior) {
super(prior);
}
}