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:
@@ -64,6 +64,11 @@ Send a review for a change on the master branch to charlie@example.com:
|
|||||||
git push --receive-pack='git receive-pack --reviewer charlie@example.com' ssh://review.example.com:29418/project HEAD:refs/for/master
|
git push --receive-pack='git receive-pack --reviewer charlie@example.com' ssh://review.example.com:29418/project HEAD:refs/for/master
|
||||||
=====
|
=====
|
||||||
|
|
||||||
|
Send reviews, but tagging them with the topic name 'bug42':
|
||||||
|
=====
|
||||||
|
git push --receive-pack='git receive-pack --reviewer charlie@example.com' ssh://review.example.com:29418/project HEAD:refs/for/master/bug42
|
||||||
|
=====
|
||||||
|
|
||||||
Also CC two other parties:
|
Also CC two other parties:
|
||||||
=====
|
=====
|
||||||
git push --receive-pack='git receive-pack --reviewer charlie@example.com --cc alice@example.com --cc bob@example.com' ssh://review.example.com:29418/project HEAD:refs/for/master
|
git push --receive-pack='git receive-pack --reviewer charlie@example.com --cc alice@example.com --cc bob@example.com' ssh://review.example.com:29418/project HEAD:refs/for/master
|
||||||
|
@@ -116,6 +116,16 @@ Other users (e.g. project owners) who have configured Gerrit to
|
|||||||
notify them of new changes will be automatically sent an email
|
notify them of new changes will be automatically sent an email
|
||||||
message when the push is completed.
|
message when the push is completed.
|
||||||
|
|
||||||
|
To include a short tag associated with all of the changes in the
|
||||||
|
same group, such as the local topic branch name, append it after
|
||||||
|
the destination branch name. In this example the short topic tag
|
||||||
|
'driver/i42' will be saved on each change this push creates or
|
||||||
|
updates:
|
||||||
|
|
||||||
|
====
|
||||||
|
git push ssh://john.doe@git.example.com:29418/kernel/common HEAD:refs/for/experimental/driver/i42
|
||||||
|
====
|
||||||
|
|
||||||
If you are frequently uploading changes to the same Gerrit server,
|
If you are frequently uploading changes to the same Gerrit server,
|
||||||
consider adding an SSH host block in `~/.ssh/config` to remember
|
consider adding an SSH host block in `~/.ssh/config` to remember
|
||||||
your username, hostname and port number. This permits the use of
|
your username, hostname and port number. This permits the use of
|
||||||
|
@@ -27,6 +27,7 @@ public class ChangeInfo {
|
|||||||
protected Change.Status status;
|
protected Change.Status status;
|
||||||
protected ProjectInfo project;
|
protected ProjectInfo project;
|
||||||
protected String branch;
|
protected String branch;
|
||||||
|
protected String topic;
|
||||||
protected boolean starred;
|
protected boolean starred;
|
||||||
protected Timestamp lastUpdatedOn;
|
protected Timestamp lastUpdatedOn;
|
||||||
protected String sortKey;
|
protected String sortKey;
|
||||||
@@ -42,6 +43,7 @@ public class ChangeInfo {
|
|||||||
status = c.getStatus();
|
status = c.getStatus();
|
||||||
project = new ProjectInfo(c.getProject());
|
project = new ProjectInfo(c.getProject());
|
||||||
branch = c.getDest().getShortName();
|
branch = c.getDest().getShortName();
|
||||||
|
topic = c.getTopic();
|
||||||
lastUpdatedOn = c.getLastUpdatedOn();
|
lastUpdatedOn = c.getLastUpdatedOn();
|
||||||
sortKey = c.getSortKey();
|
sortKey = c.getSortKey();
|
||||||
}
|
}
|
||||||
@@ -74,6 +76,10 @@ public class ChangeInfo {
|
|||||||
return branch;
|
return branch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTopic() {
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isStarred() {
|
public boolean isStarred() {
|
||||||
return starred;
|
return starred;
|
||||||
}
|
}
|
||||||
|
@@ -80,6 +80,7 @@ public interface ChangeConstants extends Constants {
|
|||||||
String changeInfoBlockOwner();
|
String changeInfoBlockOwner();
|
||||||
String changeInfoBlockProject();
|
String changeInfoBlockProject();
|
||||||
String changeInfoBlockBranch();
|
String changeInfoBlockBranch();
|
||||||
|
String changeInfoBlockTopic();
|
||||||
String changeInfoBlockUploaded();
|
String changeInfoBlockUploaded();
|
||||||
String changeInfoBlockUpdated();
|
String changeInfoBlockUpdated();
|
||||||
String changeInfoBlockStatus();
|
String changeInfoBlockStatus();
|
||||||
|
@@ -57,6 +57,7 @@ approvalTableAddReviewer = Add Reviewer
|
|||||||
changeInfoBlockOwner = Owner
|
changeInfoBlockOwner = Owner
|
||||||
changeInfoBlockProject = Project
|
changeInfoBlockProject = Project
|
||||||
changeInfoBlockBranch = Branch
|
changeInfoBlockBranch = Branch
|
||||||
|
changeInfoBlockTopic = Topic
|
||||||
changeInfoBlockUploaded = Uploaded
|
changeInfoBlockUploaded = Uploaded
|
||||||
changeInfoBlockUpdated = Updated
|
changeInfoBlockUpdated = Updated
|
||||||
changeInfoBlockStatus = Status
|
changeInfoBlockStatus = Status
|
||||||
|
@@ -23,12 +23,9 @@ import com.google.gerrit.client.ui.ProjectLink;
|
|||||||
import com.google.gerrit.common.data.AccountInfoCache;
|
import com.google.gerrit.common.data.AccountInfoCache;
|
||||||
import com.google.gerrit.reviewdb.Branch;
|
import com.google.gerrit.reviewdb.Branch;
|
||||||
import com.google.gerrit.reviewdb.Change;
|
import com.google.gerrit.reviewdb.Change;
|
||||||
import com.google.gwt.user.client.DOM;
|
|
||||||
import com.google.gwt.user.client.ui.ComplexPanel;
|
|
||||||
import com.google.gwt.user.client.ui.Composite;
|
import com.google.gwt.user.client.ui.Composite;
|
||||||
import com.google.gwt.user.client.ui.FlowPanel;
|
import com.google.gwt.user.client.ui.FlowPanel;
|
||||||
import com.google.gwt.user.client.ui.Grid;
|
import com.google.gwt.user.client.ui.Grid;
|
||||||
import com.google.gwt.user.client.ui.InlineLabel;
|
|
||||||
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
|
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
|
||||||
import com.google.gwtexpui.clippy.client.CopyableLabel;
|
import com.google.gwtexpui.clippy.client.CopyableLabel;
|
||||||
|
|
||||||
@@ -37,11 +34,12 @@ public class ChangeInfoBlock extends Composite {
|
|||||||
private static final int R_OWNER = 1;
|
private static final int R_OWNER = 1;
|
||||||
private static final int R_PROJECT = 2;
|
private static final int R_PROJECT = 2;
|
||||||
private static final int R_BRANCH = 3;
|
private static final int R_BRANCH = 3;
|
||||||
private static final int R_UPLOADED = 4;
|
private static final int R_TOPIC = 4;
|
||||||
private static final int R_UPDATED = 5;
|
private static final int R_UPLOADED = 5;
|
||||||
private static final int R_STATUS = 6;
|
private static final int R_UPDATED = 6;
|
||||||
private static final int R_PERMALINK = 7;
|
private static final int R_STATUS = 7;
|
||||||
private static final int R_CNT = 8;
|
private static final int R_PERMALINK = 8;
|
||||||
|
private static final int R_CNT = 9;
|
||||||
|
|
||||||
private final Grid table;
|
private final Grid table;
|
||||||
|
|
||||||
@@ -54,6 +52,7 @@ public class ChangeInfoBlock extends Composite {
|
|||||||
initRow(R_OWNER, Util.C.changeInfoBlockOwner());
|
initRow(R_OWNER, Util.C.changeInfoBlockOwner());
|
||||||
initRow(R_PROJECT, Util.C.changeInfoBlockProject());
|
initRow(R_PROJECT, Util.C.changeInfoBlockProject());
|
||||||
initRow(R_BRANCH, Util.C.changeInfoBlockBranch());
|
initRow(R_BRANCH, Util.C.changeInfoBlockBranch());
|
||||||
|
initRow(R_TOPIC, Util.C.changeInfoBlockTopic());
|
||||||
initRow(R_UPLOADED, Util.C.changeInfoBlockUploaded());
|
initRow(R_UPLOADED, Util.C.changeInfoBlockUploaded());
|
||||||
initRow(R_UPDATED, Util.C.changeInfoBlockUpdated());
|
initRow(R_UPDATED, Util.C.changeInfoBlockUpdated());
|
||||||
initRow(R_STATUS, Util.C.changeInfoBlockStatus());
|
initRow(R_STATUS, Util.C.changeInfoBlockStatus());
|
||||||
@@ -85,6 +84,7 @@ public class ChangeInfoBlock extends Composite {
|
|||||||
table.setWidget(R_OWNER, 1, AccountDashboardLink.link(acc, chg.getOwner()));
|
table.setWidget(R_OWNER, 1, AccountDashboardLink.link(acc, chg.getOwner()));
|
||||||
table.setWidget(R_PROJECT, 1, new ProjectLink(chg.getProject(), chg.getStatus()));
|
table.setWidget(R_PROJECT, 1, new ProjectLink(chg.getProject(), chg.getStatus()));
|
||||||
table.setText(R_BRANCH, 1, dst.getShortName());
|
table.setText(R_BRANCH, 1, dst.getShortName());
|
||||||
|
table.setText(R_TOPIC, 1, chg.getTopic());
|
||||||
table.setText(R_UPLOADED, 1, mediumFormat(chg.getCreatedOn()));
|
table.setText(R_UPLOADED, 1, mediumFormat(chg.getCreatedOn()));
|
||||||
table.setText(R_UPDATED, 1, mediumFormat(chg.getLastUpdatedOn()));
|
table.setText(R_UPDATED, 1, mediumFormat(chg.getLastUpdatedOn()));
|
||||||
table.setText(R_STATUS, 1, Util.toLongString(chg.getStatus()));
|
table.setText(R_STATUS, 1, Util.toLongString(chg.getStatus()));
|
||||||
|
@@ -226,7 +226,12 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
|
|||||||
table.setWidget(row, C_OWNER, link(c.getOwner()));
|
table.setWidget(row, C_OWNER, link(c.getOwner()));
|
||||||
table.setWidget(row, C_PROJECT, new ProjectLink(c.getProject().getKey(), c
|
table.setWidget(row, C_PROJECT, new ProjectLink(c.getProject().getKey(), c
|
||||||
.getStatus()));
|
.getStatus()));
|
||||||
table.setText(row, C_BRANCH, c.getBranch());
|
|
||||||
|
String branchText = c.getBranch();
|
||||||
|
if (c.getTopic() != null) {
|
||||||
|
branchText += " (" + c.getTopic() + ")";
|
||||||
|
}
|
||||||
|
table.setText(row, C_BRANCH, branchText);
|
||||||
table.setText(row, C_LAST_UPDATE, shortFormat(c.getLastUpdatedOn()));
|
table.setText(row, C_LAST_UPDATE, shortFormat(c.getLastUpdatedOn()));
|
||||||
setRowItem(row, c);
|
setRowItem(row, c);
|
||||||
}
|
}
|
||||||
|
@@ -351,6 +351,10 @@ public final class Change {
|
|||||||
@Column(id = 13)
|
@Column(id = 13)
|
||||||
protected String subject;
|
protected String subject;
|
||||||
|
|
||||||
|
/** Topic name assigned by the user, if any. */
|
||||||
|
@Column(id = 14, notNull = false)
|
||||||
|
protected String topic;
|
||||||
|
|
||||||
protected Change() {
|
protected Change() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,4 +460,12 @@ public final class Change {
|
|||||||
open = newStatus.isOpen();
|
open = newStatus.isOpen();
|
||||||
status = newStatus.getCode();
|
status = newStatus.getCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTopic() {
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTopic(String topic) {
|
||||||
|
this.topic = topic;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -55,7 +55,6 @@ import com.google.gwtorm.client.OrmException;
|
|||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.assistedinject.Assisted;
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
|
||||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
|
||||||
import org.eclipse.jgit.errors.MissingObjectException;
|
import org.eclipse.jgit.errors.MissingObjectException;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
@@ -159,6 +158,8 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
|
|
||||||
private Map<ObjectId, Ref> refsById;
|
private Map<ObjectId, Ref> refsById;
|
||||||
|
|
||||||
|
private String destTopicName;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ReceiveCommits(final ReviewDb db, final ApprovalTypes approvalTypes,
|
ReceiveCommits(final ReviewDb db, final ApprovalTypes approvalTypes,
|
||||||
final AccountResolver accountResolver,
|
final AccountResolver accountResolver,
|
||||||
@@ -579,16 +580,6 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
destBranchName = Constants.R_HEADS + destBranchName;
|
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;
|
final String head;
|
||||||
try {
|
try {
|
||||||
head = repo.getFullBranch();
|
head = repo.getFullBranch();
|
||||||
@@ -598,19 +589,46 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (head.equals(destBranchName)) {
|
// Split the destination branch by branch and topic. The topic
|
||||||
destBranch = new Branch.NameKey(project.getNameKey(), destBranchName);
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destBranch == null) {
|
split = name.lastIndexOf('/', split - 1);
|
||||||
|
if (split <= Constants.R_HEADS.length()) {
|
||||||
String n = destBranchName;
|
String n = destBranchName;
|
||||||
if (n.startsWith(Constants.R_HEADS))
|
if (n.startsWith(Constants.R_HEADS))
|
||||||
n = n.substring(Constants.R_HEADS.length());
|
n = n.substring(Constants.R_HEADS.length());
|
||||||
reject(cmd, "branch " + n + " not found");
|
reject(cmd, "branch " + n + " not found");
|
||||||
return;
|
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);
|
destBranchCtl = projectControl.controlForRef(destBranch);
|
||||||
if (!destBranchCtl.canUpload()) {
|
if (!destBranchCtl.canUpload()) {
|
||||||
reject(cmd);
|
reject(cmd);
|
||||||
@@ -858,6 +876,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
|
|
||||||
final Change change =
|
final Change change =
|
||||||
new Change(changeKey, new Change.Id(db.nextChangeId()), me, destBranch);
|
new Change(changeKey, new Change.Id(db.nextChangeId()), me, destBranch);
|
||||||
|
change.setTopic(destTopicName.isEmpty() ? null : destTopicName);
|
||||||
change.nextPatchSetId();
|
change.nextPatchSetId();
|
||||||
|
|
||||||
final PatchSet ps = new PatchSet(change.currPatchSetId());
|
final PatchSet ps = new PatchSet(change.currPatchSetId());
|
||||||
@@ -1158,6 +1177,11 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
@Override
|
@Override
|
||||||
public Change update(Change change) {
|
public Change update(Change change) {
|
||||||
if (change.getStatus().isOpen()) {
|
if (change.getStatus().isOpen()) {
|
||||||
|
if (destTopicName != null) {
|
||||||
|
change.setTopic(destTopicName.isEmpty() //
|
||||||
|
? null //
|
||||||
|
: destTopicName);
|
||||||
|
}
|
||||||
change.setStatus(Change.Status.NEW);
|
change.setStatus(Change.Status.NEW);
|
||||||
change.setCurrentPatchSet(result.info);
|
change.setCurrentPatchSet(result.info);
|
||||||
ChangeUtil.updated(change);
|
ChangeUtil.updated(change);
|
||||||
|
@@ -32,7 +32,7 @@ import java.util.List;
|
|||||||
/** A version of the database schema. */
|
/** A version of the database schema. */
|
||||||
public abstract class SchemaVersion {
|
public abstract class SchemaVersion {
|
||||||
/** The current schema version. */
|
/** 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 {
|
public static class Module extends AbstractModule {
|
||||||
@Override
|
@Override
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user