Add 'CherryPickOf' field for a change
After a change is created or updated using the 'cherry-pick' functionality, this field will contain the source change number and the patchset. Having this field helps us identify changes where actual dev time was spent on by filtering out propagated changes. This is especially useful for organizations wanting to generate cost metrics. Change-Id: I782a56aa52c52670ec74fabb713fe47ecba24de1
This commit is contained in:
parent
8b457aeb17
commit
4a711eda84
@ -453,6 +453,15 @@ in list of open changes anymore.
|
|||||||
Abandoned changes can be link:user-review-ui.html#restore[restored] if
|
Abandoned changes can be link:user-review-ui.html#restore[restored] if
|
||||||
later they are needed again.
|
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]]
|
[[topics]]
|
||||||
== Using Topics
|
== Using Topics
|
||||||
|
|
||||||
|
@ -229,6 +229,16 @@ hashtag:'HASHTAG'::
|
|||||||
Changes whose link:intro-user.html#hashtags[hashtag] matches 'HASHTAG'.
|
Changes whose link:intro-user.html#hashtags[hashtag] matches 'HASHTAG'.
|
||||||
The match is case-insensitive.
|
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:'REF'::
|
ref:'REF'::
|
||||||
+
|
+
|
||||||
|
@ -531,6 +531,9 @@ public final class Change {
|
|||||||
/** References a change that this change reverts. */
|
/** References a change that this change reverts. */
|
||||||
@Nullable protected Id revertOf;
|
@Nullable protected Id revertOf;
|
||||||
|
|
||||||
|
/** References the source change and patchset that this change was cherry-picked from. */
|
||||||
|
@Nullable protected PatchSet.Id cherryPickOf;
|
||||||
|
|
||||||
protected Change() {}
|
protected Change() {}
|
||||||
|
|
||||||
public Change(
|
public Change(
|
||||||
@ -567,6 +570,7 @@ public final class Change {
|
|||||||
workInProgress = other.workInProgress;
|
workInProgress = other.workInProgress;
|
||||||
reviewStarted = other.reviewStarted;
|
reviewStarted = other.reviewStarted;
|
||||||
revertOf = other.revertOf;
|
revertOf = other.revertOf;
|
||||||
|
cherryPickOf = other.cherryPickOf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Legacy 32 bit integer identity for a change. */
|
/** Legacy 32 bit integer identity for a change. */
|
||||||
@ -760,6 +764,14 @@ public final class Change {
|
|||||||
return this.revertOf;
|
return this.revertOf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PatchSet.Id getCherryPickOf() {
|
||||||
|
return cherryPickOf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCherryPickOf(@Nullable PatchSet.Id cherryPickOf) {
|
||||||
|
this.cherryPickOf = cherryPickOf;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new StringBuilder(getClass().getSimpleName())
|
return new StringBuilder(getClass().getSimpleName())
|
||||||
|
@ -124,13 +124,17 @@ public abstract class PatchSet {
|
|||||||
return id();
|
return id();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCommaSeparatedChangeAndPatchSetId() {
|
||||||
|
return changeId().toString() + ',' + id();
|
||||||
|
}
|
||||||
|
|
||||||
public String toRefName() {
|
public String toRefName() {
|
||||||
return changeId().refPrefixBuilder().append(id()).toString();
|
return changeId().refPrefixBuilder().append(id()).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final String toString() {
|
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 =
|
private final ProtoConverter<Entities.Change_Id, Change.Id> changeIdConverter =
|
||||||
ChangeIdProtoConverter.INSTANCE;
|
ChangeIdProtoConverter.INSTANCE;
|
||||||
|
private final ProtoConverter<Entities.PatchSet_Id, PatchSet.Id> patchSetIdConverter =
|
||||||
|
PatchSetIdProtoConverter.INSTANCE;
|
||||||
private final ProtoConverter<Entities.Change_Key, Change.Key> changeKeyConverter =
|
private final ProtoConverter<Entities.Change_Key, Change.Key> changeKeyConverter =
|
||||||
ChangeKeyProtoConverter.INSTANCE;
|
ChangeKeyProtoConverter.INSTANCE;
|
||||||
private final ProtoConverter<Entities.Account_Id, Account.Id> accountIdConverter =
|
private final ProtoConverter<Entities.Account_Id, Account.Id> accountIdConverter =
|
||||||
@ -78,6 +80,10 @@ public enum ChangeProtoConverter implements ProtoConverter<Entities.Change, Chan
|
|||||||
if (revertOf != null) {
|
if (revertOf != null) {
|
||||||
builder.setRevertOf(changeIdConverter.toProto(revertOf));
|
builder.setRevertOf(changeIdConverter.toProto(revertOf));
|
||||||
}
|
}
|
||||||
|
PatchSet.Id cherryPickOf = change.getCherryPickOf();
|
||||||
|
if (cherryPickOf != null) {
|
||||||
|
builder.setCherryPickOf(patchSetIdConverter.toProto(cherryPickOf));
|
||||||
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,6 +124,9 @@ public enum ChangeProtoConverter implements ProtoConverter<Entities.Change, Chan
|
|||||||
if (proto.hasRevertOf()) {
|
if (proto.hasRevertOf()) {
|
||||||
change.setRevertOf(changeIdConverter.fromProto(proto.getRevertOf()));
|
change.setRevertOf(changeIdConverter.fromProto(proto.getRevertOf()));
|
||||||
}
|
}
|
||||||
|
if (proto.hasCherryPickOf()) {
|
||||||
|
change.setCherryPickOf(patchSetIdConverter.fromProto(proto.getCherryPickOf()));
|
||||||
|
}
|
||||||
return change;
|
return change;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,8 @@ public class ChangeInfo {
|
|||||||
public Boolean hasReviewStarted;
|
public Boolean hasReviewStarted;
|
||||||
public Integer revertOf;
|
public Integer revertOf;
|
||||||
public String submissionId;
|
public String submissionId;
|
||||||
|
public Integer cherryPickOfChange;
|
||||||
|
public Integer cherryPickOfPatchSet;
|
||||||
|
|
||||||
public int _number;
|
public int _number;
|
||||||
|
|
||||||
|
@ -144,6 +144,8 @@ public class ActionJson {
|
|||||||
copy.unresolvedCommentCount = changeInfo.unresolvedCommentCount;
|
copy.unresolvedCommentCount = changeInfo.unresolvedCommentCount;
|
||||||
copy.workInProgress = changeInfo.workInProgress;
|
copy.workInProgress = changeInfo.workInProgress;
|
||||||
copy.id = changeInfo.id;
|
copy.id = changeInfo.id;
|
||||||
|
copy.cherryPickOfChange = changeInfo.cherryPickOfChange;
|
||||||
|
copy.cherryPickOfPatchSet = changeInfo.cherryPickOfPatchSet;
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,6 +111,7 @@ public class ChangeInserter implements InsertChangeOp {
|
|||||||
private final String refName;
|
private final String refName;
|
||||||
|
|
||||||
// Fields exposed as setters.
|
// Fields exposed as setters.
|
||||||
|
private PatchSet.Id cherryPickOf;
|
||||||
private Change.Status status;
|
private Change.Status status;
|
||||||
private String topic;
|
private String topic;
|
||||||
private String message;
|
private String message;
|
||||||
@ -189,6 +190,7 @@ public class ChangeInserter implements InsertChangeOp {
|
|||||||
ctx.getWhen());
|
ctx.getWhen());
|
||||||
change.setStatus(MoreObjects.firstNonNull(status, Change.Status.NEW));
|
change.setStatus(MoreObjects.firstNonNull(status, Change.Status.NEW));
|
||||||
change.setTopic(topic);
|
change.setTopic(topic);
|
||||||
|
change.setCherryPickOf(cherryPickOf);
|
||||||
change.setPrivate(isPrivate);
|
change.setPrivate(isPrivate);
|
||||||
change.setWorkInProgress(workInProgress);
|
change.setWorkInProgress(workInProgress);
|
||||||
change.setReviewStarted(!workInProgress);
|
change.setReviewStarted(!workInProgress);
|
||||||
@ -227,6 +229,11 @@ public class ChangeInserter implements InsertChangeOp {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChangeInserter setCherryPickOf(PatchSet.Id cherryPickOf) {
|
||||||
|
this.cherryPickOf = cherryPickOf;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public ChangeInserter setMessage(String message) {
|
public ChangeInserter setMessage(String message) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
return this;
|
return this;
|
||||||
@ -378,6 +385,9 @@ public class ChangeInserter implements InsertChangeOp {
|
|||||||
if (revertOf != null) {
|
if (revertOf != null) {
|
||||||
update.setRevertOf(revertOf.get());
|
update.setRevertOf(revertOf.get());
|
||||||
}
|
}
|
||||||
|
if (cherryPickOf != null) {
|
||||||
|
update.setCherryPickOf(cherryPickOf.getCommaSeparatedChangeAndPatchSetId());
|
||||||
|
}
|
||||||
|
|
||||||
List<String> newGroups = groups;
|
List<String> newGroups = groups;
|
||||||
if (newGroups.isEmpty()) {
|
if (newGroups.isEmpty()) {
|
||||||
|
@ -588,6 +588,12 @@ public class ChangeJson {
|
|||||||
}
|
}
|
||||||
out.revertOf = cd.change().getRevertOf() != null ? cd.change().getRevertOf().get() : null;
|
out.revertOf = cd.change().getRevertOf() != null ? cd.change().getRevertOf().get() : null;
|
||||||
out.submissionId = cd.change().getSubmissionId();
|
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)) {
|
if (has(REVIEWER_UPDATES)) {
|
||||||
out.reviewerUpdates = reviewerUpdates(cd);
|
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 url;
|
||||||
public String commitMessage;
|
public String commitMessage;
|
||||||
public List<String> hashtags;
|
public List<String> hashtags;
|
||||||
|
public Integer cherryPickOfChange;
|
||||||
|
public Integer cherryPickOfPatchSet;
|
||||||
|
|
||||||
public Long createdOn;
|
public Long createdOn;
|
||||||
public Long lastUpdated;
|
public Long lastUpdated;
|
||||||
|
@ -144,6 +144,10 @@ public class EventFactory {
|
|||||||
a.createdOn = change.getCreatedOn().getTime() / 1000L;
|
a.createdOn = change.getCreatedOn().getTime() / 1000L;
|
||||||
a.wip = change.isWorkInProgress() ? true : null;
|
a.wip = change.isWorkInProgress() ? true : null;
|
||||||
a.isPrivate = change.isPrivate() ? 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;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,6 +269,24 @@ public class ChangeField {
|
|||||||
public static final FieldDef<ChangeData, Integer> OWNER =
|
public static final FieldDef<ChangeData, Integer> OWNER =
|
||||||
integer(ChangeQueryBuilder.FIELD_OWNER).build(changeGetter(c -> c.getOwner().get()));
|
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. */
|
/** The user assigned to the change. */
|
||||||
public static final FieldDef<ChangeData, Integer> ASSIGNEE =
|
public static final FieldDef<ChangeData, Integer> ASSIGNEE =
|
||||||
integer(ChangeQueryBuilder.FIELD_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
|
// 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
|
// to offer fast single- and multi-dimensional numeric range. As the consequense, integer
|
||||||
// document id type is replaced with string document id type.
|
// document id type is replaced with string document id type.
|
||||||
|
@Deprecated
|
||||||
static final Schema<ChangeData> V57 =
|
static final Schema<ChangeData> V57 =
|
||||||
new Schema.Builder<ChangeData>()
|
new Schema.Builder<ChangeData>()
|
||||||
.add(V56)
|
.add(V56)
|
||||||
@ -99,6 +100,14 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
|
|||||||
.legacyNumericFields(false)
|
.legacyNumericFields(false)
|
||||||
.build();
|
.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.
|
* 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_TAG = new FooterKey("Tag");
|
||||||
public static final FooterKey FOOTER_WORK_IN_PROGRESS = new FooterKey("Work-in-progress");
|
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_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 AUTHOR = "Author";
|
||||||
static final String BASE_PATCH_SET = "Base-for-patch-set";
|
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_ASSIGNEE;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH;
|
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_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_COMMIT;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CURRENT;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CURRENT;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_GROUPS;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_GROUPS;
|
||||||
@ -148,6 +149,7 @@ class ChangeNotesParser {
|
|||||||
private ReviewerByEmailSet pendingReviewersByEmail;
|
private ReviewerByEmailSet pendingReviewersByEmail;
|
||||||
private Change.Id revertOf;
|
private Change.Id revertOf;
|
||||||
private int updateCount;
|
private int updateCount;
|
||||||
|
private PatchSet.Id cherryPickOf;
|
||||||
|
|
||||||
ChangeNotesParser(
|
ChangeNotesParser(
|
||||||
Change.Id changeId,
|
Change.Id changeId,
|
||||||
@ -246,6 +248,7 @@ class ChangeNotesParser {
|
|||||||
firstNonNull(workInProgress, false),
|
firstNonNull(workInProgress, false),
|
||||||
firstNonNull(hasReviewStarted, true),
|
firstNonNull(hasReviewStarted, true),
|
||||||
revertOf,
|
revertOf,
|
||||||
|
cherryPickOf,
|
||||||
updateCount);
|
updateCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,6 +420,10 @@ class ChangeNotesParser {
|
|||||||
revertOf = parseRevertOf(commit);
|
revertOf = parseRevertOf(commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cherryPickOf == null) {
|
||||||
|
cherryPickOf = parseCherryPickOf(commit);
|
||||||
|
}
|
||||||
|
|
||||||
previousWorkInProgressFooter = null;
|
previousWorkInProgressFooter = null;
|
||||||
parseWorkInProgress(commit);
|
parseWorkInProgress(commit);
|
||||||
}
|
}
|
||||||
@ -966,6 +973,18 @@ class ChangeNotesParser {
|
|||||||
return Change.id(revertOf);
|
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() {
|
private void pruneReviewers() {
|
||||||
Iterator<Table.Cell<Account.Id, ReviewerStateInternal, Timestamp>> rit =
|
Iterator<Table.Cell<Account.Id, ReviewerStateInternal, Timestamp>> rit =
|
||||||
reviewers.cellSet().iterator();
|
reviewers.cellSet().iterator();
|
||||||
|
@ -123,6 +123,7 @@ public abstract class ChangeNotesState {
|
|||||||
boolean workInProgress,
|
boolean workInProgress,
|
||||||
boolean reviewStarted,
|
boolean reviewStarted,
|
||||||
@Nullable Change.Id revertOf,
|
@Nullable Change.Id revertOf,
|
||||||
|
@Nullable PatchSet.Id cherryPickOf,
|
||||||
int updateCount) {
|
int updateCount) {
|
||||||
requireNonNull(
|
requireNonNull(
|
||||||
metaId,
|
metaId,
|
||||||
@ -152,6 +153,7 @@ public abstract class ChangeNotesState {
|
|||||||
.workInProgress(workInProgress)
|
.workInProgress(workInProgress)
|
||||||
.reviewStarted(reviewStarted)
|
.reviewStarted(reviewStarted)
|
||||||
.revertOf(revertOf)
|
.revertOf(revertOf)
|
||||||
|
.cherryPickOf(cherryPickOf)
|
||||||
.build())
|
.build())
|
||||||
.hashtags(hashtags)
|
.hashtags(hashtags)
|
||||||
.serverId(serverId)
|
.serverId(serverId)
|
||||||
@ -220,6 +222,9 @@ public abstract class ChangeNotesState {
|
|||||||
@Nullable
|
@Nullable
|
||||||
abstract Change.Id revertOf();
|
abstract Change.Id revertOf();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
abstract PatchSet.Id cherryPickOf();
|
||||||
|
|
||||||
abstract Builder toBuilder();
|
abstract Builder toBuilder();
|
||||||
|
|
||||||
@AutoValue.Builder
|
@AutoValue.Builder
|
||||||
@ -254,6 +259,8 @@ public abstract class ChangeNotesState {
|
|||||||
|
|
||||||
abstract Builder revertOf(@Nullable Change.Id revertOf);
|
abstract Builder revertOf(@Nullable Change.Id revertOf);
|
||||||
|
|
||||||
|
abstract Builder cherryPickOf(@Nullable PatchSet.Id cherryPickOf);
|
||||||
|
|
||||||
abstract ChangeColumns build();
|
abstract ChangeColumns build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -341,6 +348,7 @@ public abstract class ChangeNotesState {
|
|||||||
change.setWorkInProgress(c.workInProgress());
|
change.setWorkInProgress(c.workInProgress());
|
||||||
change.setReviewStarted(c.reviewStarted());
|
change.setReviewStarted(c.reviewStarted());
|
||||||
change.setRevertOf(c.revertOf());
|
change.setRevertOf(c.revertOf());
|
||||||
|
change.setCherryPickOf(c.cherryPickOf());
|
||||||
|
|
||||||
if (!patchSets().isEmpty()) {
|
if (!patchSets().isEmpty()) {
|
||||||
change.setCurrentPatchSet(c.currentPatchSetId(), c.subject(), c.originalSubject());
|
change.setCurrentPatchSet(c.currentPatchSetId(), c.subject(), c.originalSubject());
|
||||||
@ -514,6 +522,10 @@ public abstract class ChangeNotesState {
|
|||||||
if (cols.revertOf() != null) {
|
if (cols.revertOf() != null) {
|
||||||
b.setRevertOf(cols.revertOf().get()).setHasRevertOf(true);
|
b.setRevertOf(cols.revertOf().get()).setHasRevertOf(true);
|
||||||
}
|
}
|
||||||
|
if (cols.cherryPickOf() != null) {
|
||||||
|
b.setCherryPickOf(cols.cherryPickOf().getCommaSeparatedChangeAndPatchSetId())
|
||||||
|
.setHasCherryPickOf(true);
|
||||||
|
}
|
||||||
return b.build();
|
return b.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -637,6 +649,9 @@ public abstract class ChangeNotesState {
|
|||||||
if (proto.getHasRevertOf()) {
|
if (proto.getHasRevertOf()) {
|
||||||
b.revertOf(Change.id(proto.getRevertOf()));
|
b.revertOf(Change.id(proto.getRevertOf()));
|
||||||
}
|
}
|
||||||
|
if (proto.getHasCherryPickOf()) {
|
||||||
|
b.cherryPickOf(PatchSet.Id.parse(proto.getCherryPickOf()));
|
||||||
|
}
|
||||||
return b.build();
|
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_ASSIGNEE;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH;
|
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_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_COMMIT;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CURRENT;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CURRENT;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_GROUPS;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_GROUPS;
|
||||||
@ -137,6 +138,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
private Boolean isPrivate;
|
private Boolean isPrivate;
|
||||||
private Boolean workInProgress;
|
private Boolean workInProgress;
|
||||||
private Integer revertOf;
|
private Integer revertOf;
|
||||||
|
private String cherryPickOf;
|
||||||
|
|
||||||
private ChangeDraftUpdate draftUpdate;
|
private ChangeDraftUpdate draftUpdate;
|
||||||
private RobotCommentUpdate robotCommentUpdate;
|
private RobotCommentUpdate robotCommentUpdate;
|
||||||
@ -417,6 +419,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
rootOnly = true;
|
rootOnly = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCherryPickOf(String cherryPickOf) {
|
||||||
|
this.cherryPickOf = cherryPickOf;
|
||||||
|
}
|
||||||
|
|
||||||
/** @return the tree id for the updated tree */
|
/** @return the tree id for the updated tree */
|
||||||
private ObjectId storeRevisionNotes(RevWalk rw, ObjectInserter inserter, ObjectId curr)
|
private ObjectId storeRevisionNotes(RevWalk rw, ObjectInserter inserter, ObjectId curr)
|
||||||
throws ConfigInvalidException, IOException {
|
throws ConfigInvalidException, IOException {
|
||||||
@ -665,6 +671,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
addFooter(msg, FOOTER_REVERT_OF, revertOf);
|
addFooter(msg, FOOTER_REVERT_OF, revertOf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cherryPickOf != null) {
|
||||||
|
addFooter(msg, FOOTER_CHERRY_PICK_OF, cherryPickOf);
|
||||||
|
}
|
||||||
|
|
||||||
cb.setMessage(msg.toString());
|
cb.setMessage(msg.toString());
|
||||||
try {
|
try {
|
||||||
ObjectId treeId = storeRevisionNotes(rw, ins, curr);
|
ObjectId treeId = storeRevisionNotes(rw, ins, curr);
|
||||||
@ -714,7 +724,8 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
&& !currentPatchSet
|
&& !currentPatchSet
|
||||||
&& isPrivate == null
|
&& isPrivate == null
|
||||||
&& workInProgress == null
|
&& workInProgress == null
|
||||||
&& revertOf == null;
|
&& revertOf == null
|
||||||
|
&& cherryPickOf == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangeDraftUpdate getDraftUpdate() {
|
ChangeDraftUpdate getDraftUpdate() {
|
||||||
|
@ -301,6 +301,7 @@ public class ChangeData {
|
|||||||
private Integer unresolvedCommentCount;
|
private Integer unresolvedCommentCount;
|
||||||
private Integer totalCommentCount;
|
private Integer totalCommentCount;
|
||||||
private LabelTypes labelTypes;
|
private LabelTypes labelTypes;
|
||||||
|
private Optional<PatchSet.Id> cherryPickOf;
|
||||||
|
|
||||||
private ImmutableList<byte[]> refStates;
|
private ImmutableList<byte[]> refStates;
|
||||||
private ImmutableList<byte[]> refStatePatterns;
|
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.AccountGroup;
|
||||||
import com.google.gerrit.entities.BranchNameKey;
|
import com.google.gerrit.entities.BranchNameKey;
|
||||||
import com.google.gerrit.entities.Change;
|
import com.google.gerrit.entities.Change;
|
||||||
|
import com.google.gerrit.entities.PatchSet;
|
||||||
import com.google.gerrit.entities.RefNames;
|
import com.google.gerrit.entities.RefNames;
|
||||||
import com.google.gerrit.exceptions.NotSignedInException;
|
import com.google.gerrit.exceptions.NotSignedInException;
|
||||||
import com.google.gerrit.exceptions.StorageException;
|
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_WATCHEDBY = "watchedby";
|
||||||
public static final String FIELD_WIP = "wip";
|
public static final String FIELD_WIP = "wip";
|
||||||
public static final String FIELD_REVERTOF = "revertof";
|
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_USER = "user";
|
||||||
public static final String ARG_ID_GROUP = "group";
|
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");
|
"'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
|
@Override
|
||||||
protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
|
protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
|
||||||
if (query.startsWith("refs/")) {
|
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.ChangeInserter;
|
||||||
import com.google.gerrit.server.change.NotifyResolver;
|
import com.google.gerrit.server.change.NotifyResolver;
|
||||||
import com.google.gerrit.server.change.PatchSetInserter;
|
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;
|
||||||
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
@ -102,6 +103,7 @@ public class CherryPickChange {
|
|||||||
private final Provider<IdentifiedUser> user;
|
private final Provider<IdentifiedUser> user;
|
||||||
private final ChangeInserter.Factory changeInserterFactory;
|
private final ChangeInserter.Factory changeInserterFactory;
|
||||||
private final PatchSetInserter.Factory patchSetInserterFactory;
|
private final PatchSetInserter.Factory patchSetInserterFactory;
|
||||||
|
private final SetCherryPickOp.Factory setCherryPickOfFactory;
|
||||||
private final MergeUtil.Factory mergeUtilFactory;
|
private final MergeUtil.Factory mergeUtilFactory;
|
||||||
private final ChangeNotes.Factory changeNotesFactory;
|
private final ChangeNotes.Factory changeNotesFactory;
|
||||||
private final ProjectCache projectCache;
|
private final ProjectCache projectCache;
|
||||||
@ -117,6 +119,7 @@ public class CherryPickChange {
|
|||||||
Provider<IdentifiedUser> user,
|
Provider<IdentifiedUser> user,
|
||||||
ChangeInserter.Factory changeInserterFactory,
|
ChangeInserter.Factory changeInserterFactory,
|
||||||
PatchSetInserter.Factory patchSetInserterFactory,
|
PatchSetInserter.Factory patchSetInserterFactory,
|
||||||
|
SetCherryPickOp.Factory setCherryPickOfFactory,
|
||||||
MergeUtil.Factory mergeUtilFactory,
|
MergeUtil.Factory mergeUtilFactory,
|
||||||
ChangeNotes.Factory changeNotesFactory,
|
ChangeNotes.Factory changeNotesFactory,
|
||||||
ProjectCache projectCache,
|
ProjectCache projectCache,
|
||||||
@ -129,6 +132,7 @@ public class CherryPickChange {
|
|||||||
this.user = user;
|
this.user = user;
|
||||||
this.changeInserterFactory = changeInserterFactory;
|
this.changeInserterFactory = changeInserterFactory;
|
||||||
this.patchSetInserterFactory = patchSetInserterFactory;
|
this.patchSetInserterFactory = patchSetInserterFactory;
|
||||||
|
this.setCherryPickOfFactory = setCherryPickOfFactory;
|
||||||
this.mergeUtilFactory = mergeUtilFactory;
|
this.mergeUtilFactory = mergeUtilFactory;
|
||||||
this.changeNotesFactory = changeNotesFactory;
|
this.changeNotesFactory = changeNotesFactory;
|
||||||
this.projectCache = projectCache;
|
this.projectCache = projectCache;
|
||||||
@ -374,7 +378,13 @@ public class CherryPickChange {
|
|||||||
dest.project(),
|
dest.project(),
|
||||||
destChanges.get(0).getId().get()));
|
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 {
|
} else {
|
||||||
// Change key not found on destination branch. We can create a new
|
// Change key not found on destination branch. We can create a new
|
||||||
// change.
|
// change.
|
||||||
@ -457,13 +467,22 @@ public class CherryPickChange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Change.Id insertPatchSet(
|
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 {
|
throws IOException {
|
||||||
Change destChange = destNotes.getChange();
|
Change destChange = destNotes.getChange();
|
||||||
PatchSet.Id psId = ChangeUtil.nextPatchSetId(git, destChange.currentPatchSetId());
|
PatchSet.Id psId = ChangeUtil.nextPatchSetId(git, destChange.currentPatchSetId());
|
||||||
PatchSetInserter inserter = patchSetInserterFactory.create(destNotes, psId, cherryPickCommit);
|
PatchSetInserter inserter = patchSetInserterFactory.create(destNotes, psId, cherryPickCommit);
|
||||||
inserter.setMessage("Uploaded patch set " + inserter.getPatchSetId().get() + ".");
|
inserter.setMessage("Uploaded patch set " + inserter.getPatchSetId().get() + ".");
|
||||||
bu.addOp(destChange.getId(), inserter);
|
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();
|
return destChange.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,6 +502,7 @@ public class CherryPickChange {
|
|||||||
ChangeInserter ins = changeInserterFactory.create(changeId, cherryPickCommit, refName);
|
ChangeInserter ins = changeInserterFactory.create(changeId, cherryPickCommit, refName);
|
||||||
ins.setRevertOf(revertOf);
|
ins.setRevertOf(revertOf);
|
||||||
BranchNameKey sourceBranch = sourceChange == null ? null : sourceChange.getDest();
|
BranchNameKey sourceBranch = sourceChange == null ? null : sourceChange.getDest();
|
||||||
|
PatchSet.Id sourcePatchSetId = sourceChange == null ? null : sourceChange.currentPatchSetId();
|
||||||
ins.setMessage(
|
ins.setMessage(
|
||||||
revertOf == null
|
revertOf == null
|
||||||
? messageForDestinationChange(
|
? messageForDestinationChange(
|
||||||
@ -490,6 +510,7 @@ public class CherryPickChange {
|
|||||||
: "Uploaded patch set 1.") // For revert commits, the message should not include
|
: "Uploaded patch set 1.") // For revert commits, the message should not include
|
||||||
// cherry-pick information.
|
// cherry-pick information.
|
||||||
.setTopic(topic)
|
.setTopic(topic)
|
||||||
|
.setCherryPickOf(sourcePatchSetId)
|
||||||
.setWorkInProgress(
|
.setWorkInProgress(
|
||||||
(sourceChange != null && sourceChange.isWorkInProgress())
|
(sourceChange != null && sourceChange.isWorkInProgress())
|
||||||
|| !cherryPickCommit.getFilesWithGitConflicts().isEmpty());
|
|| !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.RebaseChangeOp;
|
||||||
import com.google.gerrit.server.change.ReviewerResource;
|
import com.google.gerrit.server.change.ReviewerResource;
|
||||||
import com.google.gerrit.server.change.SetAssigneeOp;
|
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.SetHashtagsOp;
|
||||||
import com.google.gerrit.server.change.SetPrivateOp;
|
import com.google.gerrit.server.change.SetPrivateOp;
|
||||||
import com.google.gerrit.server.change.WorkInProgressOp;
|
import com.google.gerrit.server.change.WorkInProgressOp;
|
||||||
@ -201,6 +202,7 @@ public class Module extends RestApiModule {
|
|||||||
factory(RebaseChangeOp.Factory.class);
|
factory(RebaseChangeOp.Factory.class);
|
||||||
factory(ReviewerResource.Factory.class);
|
factory(ReviewerResource.Factory.class);
|
||||||
factory(SetAssigneeOp.Factory.class);
|
factory(SetAssigneeOp.Factory.class);
|
||||||
|
factory(SetCherryPickOp.Factory.class);
|
||||||
factory(SetHashtagsOp.Factory.class);
|
factory(SetHashtagsOp.Factory.class);
|
||||||
factory(SetPrivateOp.Factory.class);
|
factory(SetPrivateOp.Factory.class);
|
||||||
factory(WorkInProgressOp.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().subject).contains(in.message);
|
||||||
assertThat(cherry.get().topic).isEqualTo("someTopic-foo");
|
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().review(ReviewInput.approve());
|
||||||
cherry.current().submit();
|
cherry.current().submit();
|
||||||
}
|
}
|
||||||
@ -416,23 +419,23 @@ public class RevisionIT extends AbstractDaemonTest {
|
|||||||
ChangeApi orig = gApi.changes().id(project.get() + "~master~" + r.getChangeId());
|
ChangeApi orig = gApi.changes().id(project.get() + "~master~" + r.getChangeId());
|
||||||
|
|
||||||
ChangeApi cherry = orig.revision(r.getCommit().name()).cherryPick(in);
|
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();
|
assertThat(cherry.get().workInProgress).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void cherryPickToSameBranch() throws Exception {
|
public void cherryPickToSameBranch() throws Exception {
|
||||||
PushOneCommit.Result r = createChange();
|
PushOneCommit.Result r = createChange();
|
||||||
|
ChangeApi change = gApi.changes().id(project.get() + "~master~" + r.getChangeId());
|
||||||
CherryPickInput in = new CherryPickInput();
|
CherryPickInput in = new CherryPickInput();
|
||||||
in.destination = "master";
|
in.destination = "master";
|
||||||
in.message = "it generates a new patch set\n\nChange-Id: " + r.getChangeId();
|
in.message = "it generates a new patch set\n\nChange-Id: " + r.getChangeId();
|
||||||
ChangeInfo cherryInfo =
|
ChangeInfo cherryInfo = change.revision(r.getCommit().name()).cherryPick(in).get();
|
||||||
gApi.changes()
|
|
||||||
.id(project.get() + "~master~" + r.getChangeId())
|
|
||||||
.revision(r.getCommit().name())
|
|
||||||
.cherryPick(in)
|
|
||||||
.get();
|
|
||||||
assertThat(cherryInfo.messages).hasSize(2);
|
assertThat(cherryInfo.messages).hasSize(2);
|
||||||
Iterator<ChangeMessageInfo> cherryIt = cherryInfo.messages.iterator();
|
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 1.");
|
||||||
assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 2.");
|
assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 2.");
|
||||||
}
|
}
|
||||||
@ -624,6 +627,42 @@ public class RevisionIT extends AbstractDaemonTest {
|
|||||||
+ PushOneCommit.FILE_NAME);
|
+ 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
|
@Test
|
||||||
public void cherryPickToExistingChange() throws Exception {
|
public void cherryPickToExistingChange() throws Exception {
|
||||||
PushOneCommit.Result r1 =
|
PushOneCommit.Result r1 =
|
||||||
|
@ -335,6 +335,7 @@ public class ChangeProtoConverterTest {
|
|||||||
.put("workInProgress", boolean.class)
|
.put("workInProgress", boolean.class)
|
||||||
.put("reviewStarted", boolean.class)
|
.put("reviewStarted", boolean.class)
|
||||||
.put("revertOf", Change.Id.class)
|
.put("revertOf", Change.Id.class)
|
||||||
|
.put("cherryPickOf", PatchSet.Id.class)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -776,6 +776,7 @@ public class ChangeNotesStateTest {
|
|||||||
.put("workInProgress", boolean.class)
|
.put("workInProgress", boolean.class)
|
||||||
.put("reviewStarted", boolean.class)
|
.put("reviewStarted", boolean.class)
|
||||||
.put("revertOf", Change.Id.class)
|
.put("revertOf", Change.Id.class)
|
||||||
|
.put("cherryPickOf", PatchSet.Id.class)
|
||||||
.put("toBuilder", ChangeNotesState.ChangeColumns.Builder.class)
|
.put("toBuilder", ChangeNotesState.ChangeColumns.Builder.class)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
@ -270,6 +270,19 @@ limitations under the License.
|
|||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</section>
|
</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>
|
<section class="strategy" hidden$="[[_computeHideStrategy(change)]]" hidden>
|
||||||
<span class="title">Strategy</span>
|
<span class="title">Strategy</span>
|
||||||
<span class="value">[[_computeStrategy(change)]]</span>
|
<span class="value">[[_computeStrategy(change)]]</span>
|
||||||
|
@ -227,6 +227,13 @@
|
|||||||
return hasTopic && !settingTopic;
|
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) {
|
_handleHashtagChanged(e) {
|
||||||
const lastHashtag = this.change.hashtag;
|
const lastHashtag = this.change.hashtag;
|
||||||
if (!this._newHashtag.length) { return; }
|
if (!this._newHashtag.length) { return; }
|
||||||
@ -353,6 +360,10 @@
|
|||||||
this.change.status.toLowerCase());
|
this.change.status.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_computeCherryPickOfURL(change, patchset, project) {
|
||||||
|
return Gerrit.Nav.getUrlForChangeById(change, project, patchset);
|
||||||
|
}
|
||||||
|
|
||||||
_computeTopicURL(topic) {
|
_computeTopicURL(topic) {
|
||||||
return Gerrit.Nav.getUrlForTopic(topic);
|
return Gerrit.Nav.getUrlForTopic(topic);
|
||||||
}
|
}
|
||||||
|
@ -464,6 +464,22 @@ limitations under the License.
|
|||||||
assert.isTrue(element._showTopicChip({base: {topic: 'foo'}}, false));
|
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', () => {
|
suite('Topic removal', () => {
|
||||||
let change;
|
let change;
|
||||||
setup(() => {
|
setup(() => {
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
'cc:',
|
'cc:',
|
||||||
'cc:self',
|
'cc:self',
|
||||||
'change:',
|
'change:',
|
||||||
|
'cherrypickof:',
|
||||||
'comment:',
|
'comment:',
|
||||||
'commentby:',
|
'commentby:',
|
||||||
'commit:',
|
'commit:',
|
||||||
|
@ -84,7 +84,7 @@ message ChangeNotesStateProto {
|
|||||||
|
|
||||||
int32 change_id = 2;
|
int32 change_id = 2;
|
||||||
|
|
||||||
// Next ID: 24
|
// Next ID: 26
|
||||||
message ChangeColumnsProto {
|
message ChangeColumnsProto {
|
||||||
string change_key = 1;
|
string change_key = 1;
|
||||||
|
|
||||||
@ -124,6 +124,9 @@ message ChangeNotesStateProto {
|
|||||||
|
|
||||||
int32 revert_of = 22;
|
int32 revert_of = 22;
|
||||||
bool has_revert_of = 23;
|
bool has_revert_of = 23;
|
||||||
|
|
||||||
|
string cherry_pick_of = 24;
|
||||||
|
bool has_cherry_pick_of = 25;
|
||||||
}
|
}
|
||||||
// Effectively required, even though the corresponding ChangeNotesState field
|
// Effectively required, even though the corresponding ChangeNotesState field
|
||||||
// is optional, since the field is only absent when NoteDb is disabled, in
|
// 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.
|
// Serialized form of com.google.gerrit.entities.Change.
|
||||||
// Next ID: 24
|
// Next ID: 25
|
||||||
message Change {
|
message Change {
|
||||||
required Change_Id change_id = 1;
|
required Change_Id change_id = 1;
|
||||||
optional Change_Key change_key = 2;
|
optional Change_Key change_key = 2;
|
||||||
@ -51,6 +51,7 @@ message Change {
|
|||||||
optional bool work_in_progress = 21;
|
optional bool work_in_progress = 21;
|
||||||
optional bool review_started = 22;
|
optional bool review_started = 22;
|
||||||
optional Change_Id revert_of = 23;
|
optional Change_Id revert_of = 23;
|
||||||
|
optional PatchSet_Id cherry_pick_of = 24;
|
||||||
|
|
||||||
// Deleted fields, should not be reused:
|
// Deleted fields, should not be reused:
|
||||||
reserved 6; // sortkey
|
reserved 6; // sortkey
|
||||||
|
Loading…
Reference in New Issue
Block a user