Merge "Add 'CherryPickOf' field for a change"
This commit is contained in:
@@ -453,6 +453,15 @@ in list of open changes anymore.
|
||||
Abandoned changes can be link:user-review-ui.html#restore[restored] if
|
||||
later they are needed again.
|
||||
|
||||
[[cherrypickof]]
|
||||
== Cherry-Pick changes of a Change
|
||||
|
||||
When a change is created/updated using the 'cherry-pick' functionalty,
|
||||
the original change and patchset details are recorded in the Change's
|
||||
cherrypick field. This field cannot be set or updated by the user in
|
||||
any way. It is set automatically after the cherry-pick operation completes
|
||||
successfully.
|
||||
|
||||
[[topics]]
|
||||
== Using Topics
|
||||
|
||||
|
||||
@@ -229,6 +229,16 @@ hashtag:'HASHTAG'::
|
||||
Changes whose link:intro-user.html#hashtags[hashtag] matches 'HASHTAG'.
|
||||
The match is case-insensitive.
|
||||
|
||||
[[cherrypickof]]
|
||||
cherrypickof:'CHANGE[,PATCHSET]'::
|
||||
+
|
||||
Changes which were created using the 'cherry-pick' functionality and
|
||||
whose source change number matches 'CHANGE' and source patchset number
|
||||
matches 'PATCHSET'. Note that 'PATCHSET' is optional. For example, a
|
||||
`cherrypickof:12345` matches all changes which were cherry-picked from
|
||||
change 12345 and `cherrypickof:12345,2` matches all changes which were
|
||||
cherry-picked from the 2nd patchset of change 12345.
|
||||
|
||||
[[ref]]
|
||||
ref:'REF'::
|
||||
+
|
||||
|
||||
@@ -531,6 +531,9 @@ public final class Change {
|
||||
/** References a change that this change reverts. */
|
||||
@Nullable protected Id revertOf;
|
||||
|
||||
/** References the source change and patchset that this change was cherry-picked from. */
|
||||
@Nullable protected PatchSet.Id cherryPickOf;
|
||||
|
||||
protected Change() {}
|
||||
|
||||
public Change(
|
||||
@@ -567,6 +570,7 @@ public final class Change {
|
||||
workInProgress = other.workInProgress;
|
||||
reviewStarted = other.reviewStarted;
|
||||
revertOf = other.revertOf;
|
||||
cherryPickOf = other.cherryPickOf;
|
||||
}
|
||||
|
||||
/** Legacy 32 bit integer identity for a change. */
|
||||
@@ -760,6 +764,14 @@ public final class Change {
|
||||
return this.revertOf;
|
||||
}
|
||||
|
||||
public PatchSet.Id getCherryPickOf() {
|
||||
return cherryPickOf;
|
||||
}
|
||||
|
||||
public void setCherryPickOf(@Nullable PatchSet.Id cherryPickOf) {
|
||||
this.cherryPickOf = cherryPickOf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder(getClass().getSimpleName())
|
||||
|
||||
@@ -124,13 +124,17 @@ public abstract class PatchSet {
|
||||
return id();
|
||||
}
|
||||
|
||||
public String getCommaSeparatedChangeAndPatchSetId() {
|
||||
return changeId().toString() + ',' + id();
|
||||
}
|
||||
|
||||
public String toRefName() {
|
||||
return changeId().refPrefixBuilder().append(id()).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return changeId().toString() + ',' + id();
|
||||
return getCommaSeparatedChangeAndPatchSetId();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ public enum ChangeProtoConverter implements ProtoConverter<Entities.Change, Chan
|
||||
|
||||
private final ProtoConverter<Entities.Change_Id, Change.Id> changeIdConverter =
|
||||
ChangeIdProtoConverter.INSTANCE;
|
||||
private final ProtoConverter<Entities.PatchSet_Id, PatchSet.Id> patchSetIdConverter =
|
||||
PatchSetIdProtoConverter.INSTANCE;
|
||||
private final ProtoConverter<Entities.Change_Key, Change.Key> changeKeyConverter =
|
||||
ChangeKeyProtoConverter.INSTANCE;
|
||||
private final ProtoConverter<Entities.Account_Id, Account.Id> accountIdConverter =
|
||||
@@ -78,6 +80,10 @@ public enum ChangeProtoConverter implements ProtoConverter<Entities.Change, Chan
|
||||
if (revertOf != null) {
|
||||
builder.setRevertOf(changeIdConverter.toProto(revertOf));
|
||||
}
|
||||
PatchSet.Id cherryPickOf = change.getCherryPickOf();
|
||||
if (cherryPickOf != null) {
|
||||
builder.setCherryPickOf(patchSetIdConverter.toProto(cherryPickOf));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@@ -118,6 +124,9 @@ public enum ChangeProtoConverter implements ProtoConverter<Entities.Change, Chan
|
||||
if (proto.hasRevertOf()) {
|
||||
change.setRevertOf(changeIdConverter.fromProto(proto.getRevertOf()));
|
||||
}
|
||||
if (proto.hasCherryPickOf()) {
|
||||
change.setCherryPickOf(patchSetIdConverter.fromProto(proto.getCherryPickOf()));
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,8 @@ public class ChangeInfo {
|
||||
public Boolean hasReviewStarted;
|
||||
public Integer revertOf;
|
||||
public String submissionId;
|
||||
public Integer cherryPickOfChange;
|
||||
public Integer cherryPickOfPatchSet;
|
||||
|
||||
public int _number;
|
||||
|
||||
|
||||
@@ -144,6 +144,8 @@ public class ActionJson {
|
||||
copy.unresolvedCommentCount = changeInfo.unresolvedCommentCount;
|
||||
copy.workInProgress = changeInfo.workInProgress;
|
||||
copy.id = changeInfo.id;
|
||||
copy.cherryPickOfChange = changeInfo.cherryPickOfChange;
|
||||
copy.cherryPickOfPatchSet = changeInfo.cherryPickOfPatchSet;
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ public class ChangeInserter implements InsertChangeOp {
|
||||
private final String refName;
|
||||
|
||||
// Fields exposed as setters.
|
||||
private PatchSet.Id cherryPickOf;
|
||||
private Change.Status status;
|
||||
private String topic;
|
||||
private String message;
|
||||
@@ -189,6 +190,7 @@ public class ChangeInserter implements InsertChangeOp {
|
||||
ctx.getWhen());
|
||||
change.setStatus(MoreObjects.firstNonNull(status, Change.Status.NEW));
|
||||
change.setTopic(topic);
|
||||
change.setCherryPickOf(cherryPickOf);
|
||||
change.setPrivate(isPrivate);
|
||||
change.setWorkInProgress(workInProgress);
|
||||
change.setReviewStarted(!workInProgress);
|
||||
@@ -227,6 +229,11 @@ public class ChangeInserter implements InsertChangeOp {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChangeInserter setCherryPickOf(PatchSet.Id cherryPickOf) {
|
||||
this.cherryPickOf = cherryPickOf;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChangeInserter setMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
@@ -378,6 +385,9 @@ public class ChangeInserter implements InsertChangeOp {
|
||||
if (revertOf != null) {
|
||||
update.setRevertOf(revertOf.get());
|
||||
}
|
||||
if (cherryPickOf != null) {
|
||||
update.setCherryPickOf(cherryPickOf.getCommaSeparatedChangeAndPatchSetId());
|
||||
}
|
||||
|
||||
List<String> newGroups = groups;
|
||||
if (newGroups.isEmpty()) {
|
||||
|
||||
@@ -588,6 +588,12 @@ public class ChangeJson {
|
||||
}
|
||||
out.revertOf = cd.change().getRevertOf() != null ? cd.change().getRevertOf().get() : null;
|
||||
out.submissionId = cd.change().getSubmissionId();
|
||||
out.cherryPickOfChange =
|
||||
cd.change().getCherryPickOf() != null
|
||||
? cd.change().getCherryPickOf().changeId().get()
|
||||
: null;
|
||||
out.cherryPickOfPatchSet =
|
||||
cd.change().getCherryPickOf() != null ? cd.change().getCherryPickOf().get() : null;
|
||||
|
||||
if (has(REVIEWER_UPDATES)) {
|
||||
out.reviewerUpdates = reviewerUpdates(cd);
|
||||
|
||||
49
java/com/google/gerrit/server/change/SetCherryPickOp.java
Normal file
49
java/com/google/gerrit/server/change/SetCherryPickOp.java
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright (C) 2019 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.change;
|
||||
|
||||
import com.google.gerrit.entities.Change;
|
||||
import com.google.gerrit.entities.PatchSet;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.server.notedb.ChangeUpdate;
|
||||
import com.google.gerrit.server.update.BatchUpdateOp;
|
||||
import com.google.gerrit.server.update.ChangeContext;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
public class SetCherryPickOp implements BatchUpdateOp {
|
||||
public interface Factory {
|
||||
SetCherryPickOp create(PatchSet.Id cherryPickOf);
|
||||
}
|
||||
|
||||
private final PatchSet.Id newCherryPickOf;
|
||||
|
||||
@Inject
|
||||
SetCherryPickOp(@Assisted PatchSet.Id newCherryPickOf) {
|
||||
this.newCherryPickOf = newCherryPickOf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateChange(ChangeContext ctx) throws RestApiException {
|
||||
Change change = ctx.getChange();
|
||||
if (newCherryPickOf.equals(change.getCherryPickOf())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ChangeUpdate update = ctx.getUpdate(change.currentPatchSetId());
|
||||
update.setCherryPickOf(newCherryPickOf.getCommaSeparatedChangeAndPatchSetId());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ public class ChangeAttribute {
|
||||
public String url;
|
||||
public String commitMessage;
|
||||
public List<String> hashtags;
|
||||
public Integer cherryPickOfChange;
|
||||
public Integer cherryPickOfPatchSet;
|
||||
|
||||
public Long createdOn;
|
||||
public Long lastUpdated;
|
||||
|
||||
@@ -144,6 +144,10 @@ public class EventFactory {
|
||||
a.createdOn = change.getCreatedOn().getTime() / 1000L;
|
||||
a.wip = change.isWorkInProgress() ? true : null;
|
||||
a.isPrivate = change.isPrivate() ? true : null;
|
||||
a.cherryPickOfChange =
|
||||
change.getCherryPickOf() != null ? change.getCherryPickOf().changeId().get() : null;
|
||||
a.cherryPickOfPatchSet =
|
||||
change.getCherryPickOf() != null ? change.getCherryPickOf().get() : null;
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
@@ -269,6 +269,24 @@ public class ChangeField {
|
||||
public static final FieldDef<ChangeData, Integer> OWNER =
|
||||
integer(ChangeQueryBuilder.FIELD_OWNER).build(changeGetter(c -> c.getOwner().get()));
|
||||
|
||||
/** References the source change number that this change was cherry-picked from. */
|
||||
public static final FieldDef<ChangeData, Integer> CHERRY_PICK_OF_CHANGE =
|
||||
integer(ChangeQueryBuilder.FIELD_CHERRY_PICK_OF_CHANGE)
|
||||
.build(
|
||||
cd ->
|
||||
cd.change().getCherryPickOf() != null
|
||||
? cd.change().getCherryPickOf().changeId().get()
|
||||
: null);
|
||||
|
||||
/** References the source change patch-set that this change was cherry-picked from. */
|
||||
public static final FieldDef<ChangeData, Integer> CHERRY_PICK_OF_PATCHSET =
|
||||
integer(ChangeQueryBuilder.FIELD_CHERRY_PICK_OF_PATCHSET)
|
||||
.build(
|
||||
cd ->
|
||||
cd.change().getCherryPickOf() != null
|
||||
? cd.change().getCherryPickOf().get()
|
||||
: null);
|
||||
|
||||
/** The user assigned to the change. */
|
||||
public static final FieldDef<ChangeData, Integer> ASSIGNEE =
|
||||
integer(ChangeQueryBuilder.FIELD_ASSIGNEE)
|
||||
|
||||
@@ -91,6 +91,7 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
|
||||
// New numeric types: use dimensional points using the k-d tree geo-spatial data structure
|
||||
// to offer fast single- and multi-dimensional numeric range. As the consequense, integer
|
||||
// document id type is replaced with string document id type.
|
||||
@Deprecated
|
||||
static final Schema<ChangeData> V57 =
|
||||
new Schema.Builder<ChangeData>()
|
||||
.add(V56)
|
||||
@@ -99,6 +100,14 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
|
||||
.legacyNumericFields(false)
|
||||
.build();
|
||||
|
||||
// Add new field CHERRY_PICK_OF
|
||||
static final Schema<ChangeData> V58 =
|
||||
new Schema.Builder<ChangeData>()
|
||||
.add(V57)
|
||||
.add(ChangeField.CHERRY_PICK_OF_CHANGE)
|
||||
.add(ChangeField.CHERRY_PICK_OF_PATCHSET)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Name of the change index to be used when contacting index backends or loading configurations.
|
||||
*/
|
||||
|
||||
@@ -48,6 +48,7 @@ public class ChangeNoteUtil {
|
||||
public static final FooterKey FOOTER_TAG = new FooterKey("Tag");
|
||||
public static final FooterKey FOOTER_WORK_IN_PROGRESS = new FooterKey("Work-in-progress");
|
||||
public static final FooterKey FOOTER_REVERT_OF = new FooterKey("Revert-of");
|
||||
public static final FooterKey FOOTER_CHERRY_PICK_OF = new FooterKey("Cherry-pick-of");
|
||||
|
||||
static final String AUTHOR = "Author";
|
||||
static final String BASE_PATCH_SET = "Base-for-patch-set";
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE;
|
||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH;
|
||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHANGE_ID;
|
||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHERRY_PICK_OF;
|
||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_COMMIT;
|
||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CURRENT;
|
||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_GROUPS;
|
||||
@@ -147,6 +148,7 @@ class ChangeNotesParser {
|
||||
private ReviewerByEmailSet pendingReviewersByEmail;
|
||||
private Change.Id revertOf;
|
||||
private int updateCount;
|
||||
private PatchSet.Id cherryPickOf;
|
||||
|
||||
ChangeNotesParser(
|
||||
Change.Id changeId,
|
||||
@@ -245,6 +247,7 @@ class ChangeNotesParser {
|
||||
firstNonNull(workInProgress, false),
|
||||
firstNonNull(hasReviewStarted, true),
|
||||
revertOf,
|
||||
cherryPickOf,
|
||||
updateCount);
|
||||
}
|
||||
|
||||
@@ -416,6 +419,10 @@ class ChangeNotesParser {
|
||||
revertOf = parseRevertOf(commit);
|
||||
}
|
||||
|
||||
if (cherryPickOf == null) {
|
||||
cherryPickOf = parseCherryPickOf(commit);
|
||||
}
|
||||
|
||||
previousWorkInProgressFooter = null;
|
||||
parseWorkInProgress(commit);
|
||||
}
|
||||
@@ -965,6 +972,18 @@ class ChangeNotesParser {
|
||||
return Change.id(revertOf);
|
||||
}
|
||||
|
||||
private PatchSet.Id parseCherryPickOf(ChangeNotesCommit commit) throws ConfigInvalidException {
|
||||
String cherryPickOf = parseOneFooter(commit, FOOTER_CHERRY_PICK_OF);
|
||||
if (cherryPickOf == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return PatchSet.Id.parse(cherryPickOf);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ConfigInvalidException("\"" + cherryPickOf + "\" is not a valid patchset", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void pruneReviewers() {
|
||||
Iterator<Table.Cell<Account.Id, ReviewerStateInternal, Timestamp>> rit =
|
||||
reviewers.cellSet().iterator();
|
||||
|
||||
@@ -123,6 +123,7 @@ public abstract class ChangeNotesState {
|
||||
boolean workInProgress,
|
||||
boolean reviewStarted,
|
||||
@Nullable Change.Id revertOf,
|
||||
@Nullable PatchSet.Id cherryPickOf,
|
||||
int updateCount) {
|
||||
requireNonNull(
|
||||
metaId,
|
||||
@@ -152,6 +153,7 @@ public abstract class ChangeNotesState {
|
||||
.workInProgress(workInProgress)
|
||||
.reviewStarted(reviewStarted)
|
||||
.revertOf(revertOf)
|
||||
.cherryPickOf(cherryPickOf)
|
||||
.build())
|
||||
.hashtags(hashtags)
|
||||
.serverId(serverId)
|
||||
@@ -220,6 +222,9 @@ public abstract class ChangeNotesState {
|
||||
@Nullable
|
||||
abstract Change.Id revertOf();
|
||||
|
||||
@Nullable
|
||||
abstract PatchSet.Id cherryPickOf();
|
||||
|
||||
abstract Builder toBuilder();
|
||||
|
||||
@AutoValue.Builder
|
||||
@@ -254,6 +259,8 @@ public abstract class ChangeNotesState {
|
||||
|
||||
abstract Builder revertOf(@Nullable Change.Id revertOf);
|
||||
|
||||
abstract Builder cherryPickOf(@Nullable PatchSet.Id cherryPickOf);
|
||||
|
||||
abstract ChangeColumns build();
|
||||
}
|
||||
}
|
||||
@@ -341,6 +348,7 @@ public abstract class ChangeNotesState {
|
||||
change.setWorkInProgress(c.workInProgress());
|
||||
change.setReviewStarted(c.reviewStarted());
|
||||
change.setRevertOf(c.revertOf());
|
||||
change.setCherryPickOf(c.cherryPickOf());
|
||||
|
||||
if (!patchSets().isEmpty()) {
|
||||
change.setCurrentPatchSet(c.currentPatchSetId(), c.subject(), c.originalSubject());
|
||||
@@ -514,6 +522,10 @@ public abstract class ChangeNotesState {
|
||||
if (cols.revertOf() != null) {
|
||||
b.setRevertOf(cols.revertOf().get()).setHasRevertOf(true);
|
||||
}
|
||||
if (cols.cherryPickOf() != null) {
|
||||
b.setCherryPickOf(cols.cherryPickOf().getCommaSeparatedChangeAndPatchSetId())
|
||||
.setHasCherryPickOf(true);
|
||||
}
|
||||
return b.build();
|
||||
}
|
||||
|
||||
@@ -637,6 +649,9 @@ public abstract class ChangeNotesState {
|
||||
if (proto.getHasRevertOf()) {
|
||||
b.revertOf(Change.id(proto.getRevertOf()));
|
||||
}
|
||||
if (proto.getHasCherryPickOf()) {
|
||||
b.cherryPickOf(PatchSet.Id.parse(proto.getCherryPickOf()));
|
||||
}
|
||||
return b.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import static com.google.gerrit.entities.RefNames.changeMetaRef;
|
||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE;
|
||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH;
|
||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHANGE_ID;
|
||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHERRY_PICK_OF;
|
||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_COMMIT;
|
||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CURRENT;
|
||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_GROUPS;
|
||||
@@ -137,6 +138,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
||||
private Boolean isPrivate;
|
||||
private Boolean workInProgress;
|
||||
private Integer revertOf;
|
||||
private String cherryPickOf;
|
||||
|
||||
private ChangeDraftUpdate draftUpdate;
|
||||
private RobotCommentUpdate robotCommentUpdate;
|
||||
@@ -417,6 +419,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
||||
rootOnly = true;
|
||||
}
|
||||
|
||||
public void setCherryPickOf(String cherryPickOf) {
|
||||
this.cherryPickOf = cherryPickOf;
|
||||
}
|
||||
|
||||
/** @return the tree id for the updated tree */
|
||||
private ObjectId storeRevisionNotes(RevWalk rw, ObjectInserter inserter, ObjectId curr)
|
||||
throws ConfigInvalidException, IOException {
|
||||
@@ -665,6 +671,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
||||
addFooter(msg, FOOTER_REVERT_OF, revertOf);
|
||||
}
|
||||
|
||||
if (cherryPickOf != null) {
|
||||
addFooter(msg, FOOTER_CHERRY_PICK_OF, cherryPickOf);
|
||||
}
|
||||
|
||||
cb.setMessage(msg.toString());
|
||||
try {
|
||||
ObjectId treeId = storeRevisionNotes(rw, ins, curr);
|
||||
@@ -714,7 +724,8 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
||||
&& !currentPatchSet
|
||||
&& isPrivate == null
|
||||
&& workInProgress == null
|
||||
&& revertOf == null;
|
||||
&& revertOf == null
|
||||
&& cherryPickOf == null;
|
||||
}
|
||||
|
||||
ChangeDraftUpdate getDraftUpdate() {
|
||||
|
||||
@@ -300,6 +300,7 @@ public class ChangeData {
|
||||
private Integer unresolvedCommentCount;
|
||||
private Integer totalCommentCount;
|
||||
private LabelTypes labelTypes;
|
||||
private Optional<PatchSet.Id> cherryPickOf;
|
||||
|
||||
private ImmutableList<byte[]> refStates;
|
||||
private ImmutableList<byte[]> refStatePatterns;
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.google.gerrit.entities.Account;
|
||||
import com.google.gerrit.entities.AccountGroup;
|
||||
import com.google.gerrit.entities.BranchNameKey;
|
||||
import com.google.gerrit.entities.Change;
|
||||
import com.google.gerrit.entities.PatchSet;
|
||||
import com.google.gerrit.entities.RefNames;
|
||||
import com.google.gerrit.exceptions.NotSignedInException;
|
||||
import com.google.gerrit.exceptions.StorageException;
|
||||
@@ -189,6 +190,9 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
|
||||
public static final String FIELD_WATCHEDBY = "watchedby";
|
||||
public static final String FIELD_WIP = "wip";
|
||||
public static final String FIELD_REVERTOF = "revertof";
|
||||
public static final String FIELD_CHERRY_PICK_OF = "cherrypickof";
|
||||
public static final String FIELD_CHERRY_PICK_OF_CHANGE = "cherrypickofchange";
|
||||
public static final String FIELD_CHERRY_PICK_OF_PATCHSET = "cherrypickofpatchset";
|
||||
|
||||
public static final String ARG_ID_USER = "user";
|
||||
public static final String ARG_ID_GROUP = "group";
|
||||
@@ -1268,6 +1272,29 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
|
||||
"'submissionid' operator is not supported by change index version");
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> cherryPickOf(String value) throws QueryParseException {
|
||||
if (args.getSchema().hasField(ChangeField.CHERRY_PICK_OF_CHANGE)
|
||||
&& args.getSchema().hasField(ChangeField.CHERRY_PICK_OF_PATCHSET)) {
|
||||
if (Ints.tryParse(value) != null) {
|
||||
return new CherryPickOfChangePredicate(value);
|
||||
}
|
||||
try {
|
||||
PatchSet.Id patchSetId = PatchSet.Id.parse(value);
|
||||
return Predicate.and(
|
||||
new CherryPickOfChangePredicate(patchSetId.changeId().toString()),
|
||||
new CherryPickOfPatchSetPredicate(patchSetId.getId()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new QueryParseException(
|
||||
"'"
|
||||
+ value
|
||||
+ "' is not a valid input. It must be in the 'ChangeNumber[,PatchsetNumber]' format.");
|
||||
}
|
||||
}
|
||||
throw new QueryParseException(
|
||||
"'cherrypickof' operator is not supported by change index version");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
|
||||
if (query.startsWith("refs/")) {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2019 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.query.change;
|
||||
|
||||
import com.google.gerrit.server.index.change.ChangeField;
|
||||
|
||||
public class CherryPickOfChangePredicate extends ChangeIndexPredicate {
|
||||
public CherryPickOfChangePredicate(String cherryPickOfChange) {
|
||||
super(ChangeField.CHERRY_PICK_OF_CHANGE, cherryPickOfChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(ChangeData cd) {
|
||||
if (cd.change().getCherryPickOf() == null) {
|
||||
return false;
|
||||
}
|
||||
return Integer.toString(cd.change().getCherryPickOf().changeId().get()).equals(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2019 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.query.change;
|
||||
|
||||
import com.google.gerrit.server.index.change.ChangeField;
|
||||
|
||||
public class CherryPickOfPatchSetPredicate extends ChangeIndexPredicate {
|
||||
public CherryPickOfPatchSetPredicate(String cherryPickOfPatchSet) {
|
||||
super(ChangeField.CHERRY_PICK_OF_PATCHSET, cherryPickOfPatchSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(ChangeData cd) {
|
||||
if (cd.change().getCherryPickOf() == null) {
|
||||
return false;
|
||||
}
|
||||
return cd.change().getCherryPickOf().getId().equals(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import com.google.gerrit.server.ReviewerSet;
|
||||
import com.google.gerrit.server.change.ChangeInserter;
|
||||
import com.google.gerrit.server.change.NotifyResolver;
|
||||
import com.google.gerrit.server.change.PatchSetInserter;
|
||||
import com.google.gerrit.server.change.SetCherryPickOp;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
@@ -102,6 +103,7 @@ public class CherryPickChange {
|
||||
private final Provider<IdentifiedUser> user;
|
||||
private final ChangeInserter.Factory changeInserterFactory;
|
||||
private final PatchSetInserter.Factory patchSetInserterFactory;
|
||||
private final SetCherryPickOp.Factory setCherryPickOfFactory;
|
||||
private final MergeUtil.Factory mergeUtilFactory;
|
||||
private final ChangeNotes.Factory changeNotesFactory;
|
||||
private final ProjectCache projectCache;
|
||||
@@ -118,6 +120,7 @@ public class CherryPickChange {
|
||||
Provider<IdentifiedUser> user,
|
||||
ChangeInserter.Factory changeInserterFactory,
|
||||
PatchSetInserter.Factory patchSetInserterFactory,
|
||||
SetCherryPickOp.Factory setCherryPickOfFactory,
|
||||
MergeUtil.Factory mergeUtilFactory,
|
||||
ChangeNotes.Factory changeNotesFactory,
|
||||
ProjectCache projectCache,
|
||||
@@ -131,6 +134,7 @@ public class CherryPickChange {
|
||||
this.user = user;
|
||||
this.changeInserterFactory = changeInserterFactory;
|
||||
this.patchSetInserterFactory = patchSetInserterFactory;
|
||||
this.setCherryPickOfFactory = setCherryPickOfFactory;
|
||||
this.mergeUtilFactory = mergeUtilFactory;
|
||||
this.changeNotesFactory = changeNotesFactory;
|
||||
this.projectCache = projectCache;
|
||||
@@ -368,7 +372,13 @@ public class CherryPickChange {
|
||||
dest.project(),
|
||||
destChanges.get(0).getId().get()));
|
||||
}
|
||||
changeId = insertPatchSet(bu, git, destChanges.get(0).notes(), cherryPickCommit);
|
||||
changeId =
|
||||
insertPatchSet(
|
||||
bu,
|
||||
git,
|
||||
destChanges.get(0).notes(),
|
||||
cherryPickCommit,
|
||||
sourceChange.currentPatchSetId());
|
||||
} else {
|
||||
// Change key not found on destination branch. We can create a new
|
||||
// change.
|
||||
@@ -451,13 +461,22 @@ public class CherryPickChange {
|
||||
}
|
||||
|
||||
private Change.Id insertPatchSet(
|
||||
BatchUpdate bu, Repository git, ChangeNotes destNotes, CodeReviewCommit cherryPickCommit)
|
||||
BatchUpdate bu,
|
||||
Repository git,
|
||||
ChangeNotes destNotes,
|
||||
CodeReviewCommit cherryPickCommit,
|
||||
PatchSet.Id sourcePatchSetId)
|
||||
throws IOException {
|
||||
Change destChange = destNotes.getChange();
|
||||
PatchSet.Id psId = ChangeUtil.nextPatchSetId(git, destChange.currentPatchSetId());
|
||||
PatchSetInserter inserter = patchSetInserterFactory.create(destNotes, psId, cherryPickCommit);
|
||||
inserter.setMessage("Uploaded patch set " + inserter.getPatchSetId().get() + ".");
|
||||
bu.addOp(destChange.getId(), inserter);
|
||||
if (destChange.getCherryPickOf() == null
|
||||
|| !destChange.getCherryPickOf().equals(sourcePatchSetId)) {
|
||||
SetCherryPickOp cherryPickOfUpdater = setCherryPickOfFactory.create(sourcePatchSetId);
|
||||
bu.addOp(destChange.getId(), cherryPickOfUpdater);
|
||||
}
|
||||
return destChange.getId();
|
||||
}
|
||||
|
||||
@@ -477,6 +496,7 @@ public class CherryPickChange {
|
||||
ChangeInserter ins = changeInserterFactory.create(changeId, cherryPickCommit, refName);
|
||||
ins.setRevertOf(revertOf);
|
||||
BranchNameKey sourceBranch = sourceChange == null ? null : sourceChange.getDest();
|
||||
PatchSet.Id sourcePatchSetId = sourceChange == null ? null : sourceChange.currentPatchSetId();
|
||||
ins.setMessage(
|
||||
revertOf == null
|
||||
? messageForDestinationChange(
|
||||
@@ -484,6 +504,7 @@ public class CherryPickChange {
|
||||
: "Uploaded patch set 1.") // For revert commits, the message should not include
|
||||
// cherry-pick information.
|
||||
.setTopic(topic)
|
||||
.setCherryPickOf(sourcePatchSetId)
|
||||
.setWorkInProgress(
|
||||
(sourceChange != null && sourceChange.isWorkInProgress())
|
||||
|| !cherryPickCommit.getFilesWithGitConflicts().isEmpty());
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.google.gerrit.server.change.PatchSetInserter;
|
||||
import com.google.gerrit.server.change.RebaseChangeOp;
|
||||
import com.google.gerrit.server.change.ReviewerResource;
|
||||
import com.google.gerrit.server.change.SetAssigneeOp;
|
||||
import com.google.gerrit.server.change.SetCherryPickOp;
|
||||
import com.google.gerrit.server.change.SetHashtagsOp;
|
||||
import com.google.gerrit.server.change.SetPrivateOp;
|
||||
import com.google.gerrit.server.change.WorkInProgressOp;
|
||||
@@ -201,6 +202,7 @@ public class Module extends RestApiModule {
|
||||
factory(RebaseChangeOp.Factory.class);
|
||||
factory(ReviewerResource.Factory.class);
|
||||
factory(SetAssigneeOp.Factory.class);
|
||||
factory(SetCherryPickOp.Factory.class);
|
||||
factory(SetHashtagsOp.Factory.class);
|
||||
factory(SetPrivateOp.Factory.class);
|
||||
factory(WorkInProgressOp.Factory.class);
|
||||
|
||||
@@ -337,6 +337,9 @@ public class RevisionIT extends AbstractDaemonTest {
|
||||
|
||||
assertThat(cherry.get().subject).contains(in.message);
|
||||
assertThat(cherry.get().topic).isEqualTo("someTopic-foo");
|
||||
assertThat(cherry.get().cherryPickOfChange).isEqualTo(orig.get()._number);
|
||||
assertThat(cherry.get().cherryPickOfPatchSet).isEqualTo(1);
|
||||
|
||||
cherry.current().review(ReviewInput.approve());
|
||||
cherry.current().submit();
|
||||
}
|
||||
@@ -416,23 +419,23 @@ public class RevisionIT extends AbstractDaemonTest {
|
||||
ChangeApi orig = gApi.changes().id(project.get() + "~master~" + r.getChangeId());
|
||||
|
||||
ChangeApi cherry = orig.revision(r.getCommit().name()).cherryPick(in);
|
||||
assertThat(cherry.get().cherryPickOfChange).isEqualTo(orig.get()._number);
|
||||
assertThat(cherry.get().cherryPickOfPatchSet).isEqualTo(1);
|
||||
assertThat(cherry.get().workInProgress).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cherryPickToSameBranch() throws Exception {
|
||||
PushOneCommit.Result r = createChange();
|
||||
ChangeApi change = gApi.changes().id(project.get() + "~master~" + r.getChangeId());
|
||||
CherryPickInput in = new CherryPickInput();
|
||||
in.destination = "master";
|
||||
in.message = "it generates a new patch set\n\nChange-Id: " + r.getChangeId();
|
||||
ChangeInfo cherryInfo =
|
||||
gApi.changes()
|
||||
.id(project.get() + "~master~" + r.getChangeId())
|
||||
.revision(r.getCommit().name())
|
||||
.cherryPick(in)
|
||||
.get();
|
||||
ChangeInfo cherryInfo = change.revision(r.getCommit().name()).cherryPick(in).get();
|
||||
assertThat(cherryInfo.messages).hasSize(2);
|
||||
Iterator<ChangeMessageInfo> cherryIt = cherryInfo.messages.iterator();
|
||||
assertThat(cherryInfo.cherryPickOfChange).isEqualTo(change.get()._number);
|
||||
assertThat(cherryInfo.cherryPickOfPatchSet).isEqualTo(1);
|
||||
assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 1.");
|
||||
assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 2.");
|
||||
}
|
||||
@@ -624,6 +627,42 @@ public class RevisionIT extends AbstractDaemonTest {
|
||||
+ PushOneCommit.FILE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cherryPickToExistingChangeUpdatesCherryPickOf() throws Exception {
|
||||
PushOneCommit.Result r1 =
|
||||
pushFactory
|
||||
.create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "a")
|
||||
.to("refs/for/master");
|
||||
String t1 = project.get() + "~master~" + r1.getChangeId();
|
||||
ChangeApi orig = gApi.changes().id(project.get() + "~master~" + r1.getChangeId());
|
||||
|
||||
BranchInput bin = new BranchInput();
|
||||
bin.revision = r1.getCommit().getParent(0).name();
|
||||
gApi.projects().name(project.get()).branch("foo").create(bin);
|
||||
|
||||
PushOneCommit.Result r2 =
|
||||
pushFactory
|
||||
.create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "b", r1.getChangeId())
|
||||
.to("refs/for/foo");
|
||||
String t2 = project.get() + "~foo~" + r2.getChangeId();
|
||||
|
||||
CherryPickInput in = new CherryPickInput();
|
||||
in.destination = "foo";
|
||||
in.message = r1.getCommit().getFullMessage();
|
||||
ChangeApi cherry = gApi.changes().id(t1).current().cherryPick(in);
|
||||
assertThat(get(t2, ALL_REVISIONS).revisions).hasSize(2);
|
||||
assertThat(cherry.get().cherryPickOfChange).isEqualTo(orig.get()._number);
|
||||
assertThat(cherry.get().cherryPickOfPatchSet).isEqualTo(1);
|
||||
|
||||
PushOneCommit.Result r3 = amendChange(r1.getChangeId(), SUBJECT, "b.txt", "b");
|
||||
in = new CherryPickInput();
|
||||
in.destination = "foo";
|
||||
in.message = r3.getCommit().getFullMessage();
|
||||
cherry = gApi.changes().id(t1).current().cherryPick(in);
|
||||
assertThat(cherry.get().cherryPickOfChange).isEqualTo(orig.get()._number);
|
||||
assertThat(cherry.get().cherryPickOfPatchSet).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cherryPickToExistingChange() throws Exception {
|
||||
PushOneCommit.Result r1 =
|
||||
|
||||
@@ -335,6 +335,7 @@ public class ChangeProtoConverterTest {
|
||||
.put("workInProgress", boolean.class)
|
||||
.put("reviewStarted", boolean.class)
|
||||
.put("revertOf", Change.Id.class)
|
||||
.put("cherryPickOf", PatchSet.Id.class)
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
@@ -776,6 +776,7 @@ public class ChangeNotesStateTest {
|
||||
.put("workInProgress", boolean.class)
|
||||
.put("reviewStarted", boolean.class)
|
||||
.put("revertOf", Change.Id.class)
|
||||
.put("cherryPickOf", PatchSet.Id.class)
|
||||
.put("toBuilder", ChangeNotesState.ChangeColumns.Builder.class)
|
||||
.build());
|
||||
}
|
||||
|
||||
@@ -270,6 +270,19 @@ limitations under the License.
|
||||
</template>
|
||||
</span>
|
||||
</section>
|
||||
<template is="dom-if" if="[[_showCherryPickOf(change.*)]]">
|
||||
<section>
|
||||
<span class="title">Cherry pick of</span>
|
||||
<span class="value">
|
||||
<a href$="[[_computeCherryPickOfURL(change.cherry_pick_of_change, change.cherry_pick_of_patch_set, change.project)]]">
|
||||
<gr-limited-text
|
||||
text="[[change.cherry_pick_of_change]],[[change.cherry_pick_of_patch_set]]"
|
||||
limit="40">
|
||||
</gr-limited-text>
|
||||
</a>
|
||||
</span>
|
||||
</section>
|
||||
</template>
|
||||
<section class="strategy" hidden$="[[_computeHideStrategy(change)]]" hidden>
|
||||
<span class="title">Strategy</span>
|
||||
<span class="value">[[_computeStrategy(change)]]</span>
|
||||
|
||||
@@ -227,6 +227,13 @@
|
||||
return hasTopic && !settingTopic;
|
||||
}
|
||||
|
||||
_showCherryPickOf(changeRecord) {
|
||||
const hasCherryPickOf = !!changeRecord &&
|
||||
!!changeRecord.base && !!changeRecord.base.cherry_pick_of_change &&
|
||||
!!changeRecord.base.cherry_pick_of_patch_set;
|
||||
return hasCherryPickOf;
|
||||
}
|
||||
|
||||
_handleHashtagChanged(e) {
|
||||
const lastHashtag = this.change.hashtag;
|
||||
if (!this._newHashtag.length) { return; }
|
||||
@@ -353,6 +360,10 @@
|
||||
this.change.status.toLowerCase());
|
||||
}
|
||||
|
||||
_computeCherryPickOfURL(change, patchset, project) {
|
||||
return Gerrit.Nav.getUrlForChangeById(change, project, patchset);
|
||||
}
|
||||
|
||||
_computeTopicURL(topic) {
|
||||
return Gerrit.Nav.getUrlForTopic(topic);
|
||||
}
|
||||
|
||||
@@ -464,6 +464,22 @@ limitations under the License.
|
||||
assert.isTrue(element._showTopicChip({base: {topic: 'foo'}}, false));
|
||||
});
|
||||
|
||||
test('_showCherryPickOf', () => {
|
||||
assert.isFalse(element._showCherryPickOf(null));
|
||||
assert.isFalse(element._showCherryPickOf({
|
||||
base: {
|
||||
cherry_pick_of_change: null,
|
||||
cherry_pick_of_patch_set: null,
|
||||
},
|
||||
}));
|
||||
assert.isTrue(element._showCherryPickOf({
|
||||
base: {
|
||||
cherry_pick_of_change: 123,
|
||||
cherry_pick_of_patch_set: 1,
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
suite('Topic removal', () => {
|
||||
let change;
|
||||
setup(() => {
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
'cc:',
|
||||
'cc:self',
|
||||
'change:',
|
||||
'cherrypickof:',
|
||||
'comment:',
|
||||
'commentby:',
|
||||
'commit:',
|
||||
|
||||
@@ -84,7 +84,7 @@ message ChangeNotesStateProto {
|
||||
|
||||
int32 change_id = 2;
|
||||
|
||||
// Next ID: 24
|
||||
// Next ID: 26
|
||||
message ChangeColumnsProto {
|
||||
string change_key = 1;
|
||||
|
||||
@@ -124,6 +124,9 @@ message ChangeNotesStateProto {
|
||||
|
||||
int32 revert_of = 22;
|
||||
bool has_revert_of = 23;
|
||||
|
||||
string cherry_pick_of = 24;
|
||||
bool has_cherry_pick_of = 25;
|
||||
}
|
||||
// Effectively required, even though the corresponding ChangeNotesState field
|
||||
// is optional, since the field is only absent when NoteDb is disabled, in
|
||||
|
||||
@@ -31,7 +31,7 @@ message Change_Key {
|
||||
}
|
||||
|
||||
// Serialized form of com.google.gerrit.entities.Change.
|
||||
// Next ID: 24
|
||||
// Next ID: 25
|
||||
message Change {
|
||||
required Change_Id change_id = 1;
|
||||
optional Change_Key change_key = 2;
|
||||
@@ -51,6 +51,7 @@ message Change {
|
||||
optional bool work_in_progress = 21;
|
||||
optional bool review_started = 22;
|
||||
optional Change_Id revert_of = 23;
|
||||
optional PatchSet_Id cherry_pick_of = 24;
|
||||
|
||||
// Deleted fields, should not be reused:
|
||||
reserved 6; // sortkey
|
||||
|
||||
Reference in New Issue
Block a user