Merge changes from topic 'revert-of'

* changes:
  Add revertOf to ChangeIndex
  Add revertOf to ChangeInfo and populate it when /revert is called
  Add revertOf to ReviewDb and NoteDb
This commit is contained in:
Edwin Kempin
2017-07-27 16:44:21 +00:00
committed by Gerrit Code Review
27 changed files with 274 additions and 20 deletions

View File

@@ -5729,6 +5729,8 @@ When present, change is marked as private.
When present, change is marked as Work In Progress. When present, change is marked as Work In Progress.
|`has_review_started` |optional, not set if `false`| |`has_review_started` |optional, not set if `false`|
When present, change has been marked Ready at some point in time. When present, change has been marked Ready at some point in time.
|`revert_of` |optional|
The numeric Change-Id of the change that this change reverts.
|================================== |==================================
[[change-input]] [[change-input]]

View File

@@ -129,6 +129,11 @@ cc:'USER'::
Changes that have the given user CC'ed on them. The special case of `cc:self` Changes that have the given user CC'ed on them. The special case of `cc:self`
will find changes where the caller has been CC'ed. will find changes where the caller has been CC'ed.
[[revertof]]
revertof:'ID'::
+
Changes that revert the change specified by the numeric 'ID'.
[[reviewerin]] [[reviewerin]]
reviewerin:'GROUP':: reviewerin:'GROUP'::
+ +

View File

@@ -617,6 +617,7 @@ public class ChangeIT extends AbstractDaemonTest {
assertThat(revertChange.messages).hasSize(1); assertThat(revertChange.messages).hasSize(1);
assertThat(revertChange.messages.iterator().next().message).isEqualTo("Uploaded patch set 1."); assertThat(revertChange.messages.iterator().next().message).isEqualTo("Uploaded patch set 1.");
assertThat(revertChange.revertOf).isEqualTo(gApi.changes().id(r.getChangeId()).get()._number);
} }
@Test @Test

View File

@@ -48,6 +48,7 @@ public class ChangeInfo {
public Boolean isPrivate; public Boolean isPrivate;
public Boolean workInProgress; public Boolean workInProgress;
public Boolean hasReviewStarted; public Boolean hasReviewStarted;
public Integer revertOf;
public int _number; public int _number;

View File

@@ -154,6 +154,8 @@ public class SearchSuggestOracle extends HighlightSuggestOracle {
suggestions.add("unresolved:"); suggestions.add("unresolved:");
suggestions.add("revertof:");
if (Gerrit.isNoteDbEnabled()) { if (Gerrit.isNoteDbEnabled()) {
suggestions.add("cc:"); suggestions.add("cc:");
suggestions.add("hashtag:"); suggestions.add("hashtag:");

View File

@@ -524,6 +524,10 @@ public final class Change {
@Column(id = 22) @Column(id = 22)
protected boolean reviewStarted; protected boolean reviewStarted;
/** References a change that this change reverts. */
@Column(id = 23, notNull = false)
protected Id revertOf;
/** @see com.google.gerrit.server.notedb.NoteDbChangeState */ /** @see com.google.gerrit.server.notedb.NoteDbChangeState */
@Column(id = 101, notNull = false, length = Integer.MAX_VALUE) @Column(id = 101, notNull = false, length = Integer.MAX_VALUE)
protected String noteDbState; protected String noteDbState;
@@ -564,6 +568,7 @@ public final class Change {
workInProgress = other.workInProgress; workInProgress = other.workInProgress;
reviewStarted = other.reviewStarted; reviewStarted = other.reviewStarted;
noteDbState = other.noteDbState; noteDbState = other.noteDbState;
revertOf = other.revertOf;
} }
/** Legacy 32 bit integer identity for a change. */ /** Legacy 32 bit integer identity for a change. */
@@ -733,6 +738,14 @@ public final class Change {
this.reviewStarted = reviewStarted; this.reviewStarted = reviewStarted;
} }
public void setRevertOf(Id revertOf) {
this.revertOf = revertOf;
}
public Id getRevertOf() {
return this.revertOf;
}
public String getNoteDbState() { public String getNoteDbState() {
return noteDbState; return noteDbState;
} }

View File

@@ -129,6 +129,7 @@ public class ChangeInserter implements InsertChangeOp {
private boolean fireRevisionCreated; private boolean fireRevisionCreated;
private boolean sendMail; private boolean sendMail;
private boolean updateRef; private boolean updateRef;
private Change.Id revertOf;
// Fields set during the insertion process. // Fields set during the insertion process.
private ReceiveCommand cmd; private ReceiveCommand cmd;
@@ -198,6 +199,7 @@ public class ChangeInserter implements InsertChangeOp {
change.setPrivate(isPrivate); change.setPrivate(isPrivate);
change.setWorkInProgress(workInProgress); change.setWorkInProgress(workInProgress);
change.setReviewStarted(!workInProgress); change.setReviewStarted(!workInProgress);
change.setRevertOf(revertOf);
return change; return change;
} }
@@ -319,6 +321,11 @@ public class ChangeInserter implements InsertChangeOp {
return this; return this;
} }
public ChangeInserter setRevertOf(Change.Id revertOf) {
this.revertOf = revertOf;
return this;
}
public void setPushCertificate(String cert) { public void setPushCertificate(String cert) {
pushCert = cert; pushCert = cert;
} }
@@ -390,6 +397,9 @@ public class ChangeInserter implements InsertChangeOp {
update.setPsDescription(patchSetDescription); update.setPsDescription(patchSetDescription);
update.setPrivate(isPrivate); update.setPrivate(isPrivate);
update.setWorkInProgress(workInProgress); update.setWorkInProgress(workInProgress);
if (revertOf != null) {
update.setRevertOf(revertOf.get());
}
boolean draft = status == Change.Status.DRAFT; boolean draft = status == Change.Status.DRAFT;
List<String> newGroups = groups; List<String> newGroups = groups;

View File

@@ -542,6 +542,7 @@ public class ChangeJson {
out.submitted = getSubmittedOn(cd); out.submitted = getSubmittedOn(cd);
out.plugins = out.plugins =
pluginDefinedAttributesFactory != null ? pluginDefinedAttributesFactory.create(cd) : null; pluginDefinedAttributesFactory != null ? pluginDefinedAttributesFactory.create(cd) : null;
out.revertOf = cd.change().getRevertOf() != null ? cd.change().getRevertOf().get() : null;
if (out.labels != null && has(DETAILED_LABELS)) { if (out.labels != null && has(DETAILED_LABELS)) {
// If limited to specific patch sets but not the current patch set, don't // If limited to specific patch sets but not the current patch set, don't

View File

@@ -218,6 +218,7 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
Set<Account.Id> ccs = new HashSet<>(reviewerSet.byState(ReviewerStateInternal.CC)); Set<Account.Id> ccs = new HashSet<>(reviewerSet.byState(ReviewerStateInternal.CC));
ccs.remove(user.getAccountId()); ccs.remove(user.getAccountId());
ins.setExtraCC(ccs); ins.setExtraCC(ccs);
ins.setRevertOf(changeIdToRevert);
try (BatchUpdate bu = updateFactory.create(db.get(), project, user, now)) { try (BatchUpdate bu = updateFactory.create(db.get(), project, user, now)) {
bu.setRepository(git, revWalk, oi); bu.setRepository(git, revWalk, oi);

View File

@@ -207,6 +207,11 @@ public class ChangeField {
.stored() .stored()
.buildRepeatable(cd -> getReviewerByEmailFieldValues(cd.pendingReviewersByEmail())); .buildRepeatable(cd -> getReviewerByEmailFieldValues(cd.pendingReviewersByEmail()));
/** References a change that this change reverts. */
public static final FieldDef<ChangeData, Integer> REVERT_OF =
integer(ChangeQueryBuilder.FIELD_REVERTOF)
.build(cd -> cd.change().getRevertOf() != null ? cd.change().getRevertOf().get() : null);
@VisibleForTesting @VisibleForTesting
static List<String> getReviewerFieldValues(ReviewerSet reviewers) { static List<String> getReviewerFieldValues(ReviewerSet reviewers) {
List<String> r = new ArrayList<>(reviewers.asTable().size() * 2); List<String> r = new ArrayList<>(reviewers.asTable().size() * 2);

View File

@@ -78,6 +78,7 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
static final Schema<ChangeData> V43 = static final Schema<ChangeData> V43 =
schema(V42, ChangeField.EXACT_AUTHOR, ChangeField.EXACT_COMMITTER); schema(V42, ChangeField.EXACT_AUTHOR, ChangeField.EXACT_COMMITTER);
@Deprecated
static final Schema<ChangeData> V44 = static final Schema<ChangeData> V44 =
schema( schema(
V43, V43,
@@ -85,6 +86,8 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
ChangeField.PENDING_REVIEWER, ChangeField.PENDING_REVIEWER,
ChangeField.PENDING_REVIEWER_BY_EMAIL); ChangeField.PENDING_REVIEWER_BY_EMAIL);
static final Schema<ChangeData> V45 = schema(V44, ChangeField.REVERT_OF);
public static final String NAME = "changes"; public static final String NAME = "changes";
public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions(); public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();

View File

@@ -57,6 +57,7 @@ public abstract class AbstractChangeUpdate {
protected PatchSet.Id psId; protected PatchSet.Id psId;
private ObjectId result; private ObjectId result;
protected boolean rootOnly;
protected AbstractChangeUpdate( protected AbstractChangeUpdate(
Config cfg, Config cfg,
@@ -190,6 +191,11 @@ public abstract class AbstractChangeUpdate {
/** Whether no updates have been done. */ /** Whether no updates have been done. */
public abstract boolean isEmpty(); public abstract boolean isEmpty();
/** Wether this update can only be a root commit. */
public boolean isRootOnly() {
return rootOnly;
}
/** /**
* @return the NameKey for the project where the update will be stored, which is not necessarily * @return the NameKey for the project where the update will be stored, which is not necessarily
* the same as the change's project. * the same as the change's project.

View File

@@ -233,7 +233,8 @@ public class ChangeBundle {
// last time this file was updated. // last time this file was updated.
checkColumns(Change.Id.class, 1); checkColumns(Change.Id.class, 1);
checkColumns(Change.class, 1, 2, 3, 4, 5, 7, 8, 10, 12, 13, 14, 17, 18, 19, 20, 21, 22, 101); checkColumns(
Change.class, 1, 2, 3, 4, 5, 7, 8, 10, 12, 13, 14, 17, 18, 19, 20, 21, 22, 23, 101);
checkColumns(ChangeMessage.Key.class, 1, 2); checkColumns(ChangeMessage.Key.class, 1, 2);
checkColumns(ChangeMessage.class, 1, 2, 3, 4, 5, 6, 7); checkColumns(ChangeMessage.class, 1, 2, 3, 4, 5, 6, 7);
checkColumns(PatchSet.Id.class, 1, 2); checkColumns(PatchSet.Id.class, 1, 2);

View File

@@ -83,6 +83,7 @@ public class ChangeNoteUtil {
public static final FooterKey FOOTER_TOPIC = new FooterKey("Topic"); public static final FooterKey FOOTER_TOPIC = new FooterKey("Topic");
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");
private static final String AUTHOR = "Author"; private static final String AUTHOR = "Author";
private static final String BASE_PATCH_SET = "Base-for-patch-set"; private static final String BASE_PATCH_SET = "Base-for-patch-set";

View File

@@ -623,6 +623,10 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
return state.isWorkInProgress(); return state.isWorkInProgress();
} }
public Change.Id getRevertOf() {
return state.revertOf();
}
public boolean hasReviewStarted() { public boolean hasReviewStarted() {
return state.hasReviewStarted(); return state.hasReviewStarted();
} }

View File

@@ -27,6 +27,7 @@ import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET_DE
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PRIVATE; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PRIVATE;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_READ_ONLY_UNTIL; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_READ_ONLY_UNTIL;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REAL_USER; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REAL_USER;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REVERT_OF;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBJECT; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBJECT;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMISSION_ID; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMISSION_ID;
@@ -169,6 +170,7 @@ class ChangeNotesParser {
private Boolean hasReviewStarted; private Boolean hasReviewStarted;
private ReviewerSet pendingReviewers; private ReviewerSet pendingReviewers;
private ReviewerByEmailSet pendingReviewersByEmail; private ReviewerByEmailSet pendingReviewersByEmail;
private Change.Id revertOf;
ChangeNotesParser( ChangeNotesParser(
Change.Id changeId, Change.Id changeId,
@@ -267,7 +269,8 @@ class ChangeNotesParser {
readOnlyUntil, readOnlyUntil,
isPrivate, isPrivate,
workInProgress, workInProgress,
hasReviewStarted); hasReviewStarted,
revertOf);
} }
private PatchSet.Id buildCurrentPatchSetId() { private PatchSet.Id buildCurrentPatchSetId() {
@@ -415,6 +418,10 @@ class ChangeNotesParser {
parseIsPrivate(commit); parseIsPrivate(commit);
} }
if (revertOf == null) {
revertOf = parseRevertOf(commit);
}
previousWorkInProgressFooter = null; previousWorkInProgressFooter = null;
parseWorkInProgress(commit); parseWorkInProgress(commit);
@@ -1022,6 +1029,18 @@ class ChangeNotesParser {
throw invalidFooter(FOOTER_WORK_IN_PROGRESS, raw); throw invalidFooter(FOOTER_WORK_IN_PROGRESS, raw);
} }
private Change.Id parseRevertOf(ChangeNotesCommit commit) throws ConfigInvalidException {
String footer = parseOneFooter(commit, FOOTER_REVERT_OF);
if (footer == null) {
return null;
}
Integer revertOf = Ints.tryParse(footer);
if (revertOf == null) {
throw invalidFooter(FOOTER_REVERT_OF, footer);
}
return new Change.Id(revertOf);
}
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();

View File

@@ -78,7 +78,8 @@ public abstract class ChangeNotesState {
null, null,
null, null,
null, null,
true); true,
null);
} }
static ChangeNotesState create( static ChangeNotesState create(
@@ -113,7 +114,8 @@ public abstract class ChangeNotesState {
@Nullable Timestamp readOnlyUntil, @Nullable Timestamp readOnlyUntil,
@Nullable Boolean isPrivate, @Nullable Boolean isPrivate,
@Nullable Boolean workInProgress, @Nullable Boolean workInProgress,
boolean hasReviewStarted) { boolean hasReviewStarted,
@Nullable Change.Id revertOf) {
if (hashtags == null) { if (hashtags == null) {
hashtags = ImmutableSet.of(); hashtags = ImmutableSet.of();
} }
@@ -135,7 +137,8 @@ public abstract class ChangeNotesState {
status, status,
isPrivate, isPrivate,
workInProgress, workInProgress,
hasReviewStarted), hasReviewStarted,
revertOf),
ImmutableSet.copyOf(pastAssignees), ImmutableSet.copyOf(pastAssignees),
ImmutableSet.copyOf(hashtags), ImmutableSet.copyOf(hashtags),
ImmutableList.copyOf(patchSets.entrySet()), ImmutableList.copyOf(patchSets.entrySet()),
@@ -153,7 +156,8 @@ public abstract class ChangeNotesState {
readOnlyUntil, readOnlyUntil,
isPrivate, isPrivate,
workInProgress, workInProgress,
hasReviewStarted); hasReviewStarted,
revertOf);
} }
/** /**
@@ -205,6 +209,9 @@ public abstract class ChangeNotesState {
@Nullable @Nullable
abstract Boolean hasReviewStarted(); abstract Boolean hasReviewStarted();
@Nullable
abstract Change.Id revertOf();
} }
// Only null if NoteDb is disabled. // Only null if NoteDb is disabled.
@@ -258,6 +265,9 @@ public abstract class ChangeNotesState {
@Nullable @Nullable
abstract Boolean hasReviewStarted(); abstract Boolean hasReviewStarted();
@Nullable
abstract Change.Id revertOf();
Change newChange(Project.NameKey project) { Change newChange(Project.NameKey project) {
ChangeColumns c = checkNotNull(columns(), "columns are required"); ChangeColumns c = checkNotNull(columns(), "columns are required");
Change change = Change change =
@@ -318,6 +328,7 @@ public abstract class ChangeNotesState {
change.setPrivate(c.isPrivate() == null ? false : c.isPrivate()); change.setPrivate(c.isPrivate() == null ? false : c.isPrivate());
change.setWorkInProgress(c.isWorkInProgress() == null ? false : c.isWorkInProgress()); change.setWorkInProgress(c.isWorkInProgress() == null ? false : c.isWorkInProgress());
change.setReviewStarted(c.hasReviewStarted() == null ? false : c.hasReviewStarted()); change.setReviewStarted(c.hasReviewStarted() == null ? false : c.hasReviewStarted());
change.setRevertOf(c.revertOf());
if (!patchSets().isEmpty()) { if (!patchSets().isEmpty()) {
change.setCurrentPatchSet(c.currentPatchSetId(), c.subject(), c.originalSubject()); change.setCurrentPatchSet(c.currentPatchSetId(), c.subject(), c.originalSubject());

View File

@@ -32,6 +32,7 @@ import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET_DE
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PRIVATE; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PRIVATE;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_READ_ONLY_UNTIL; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_READ_ONLY_UNTIL;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REAL_USER; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REAL_USER;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REVERT_OF;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBJECT; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBJECT;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMISSION_ID; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMISSION_ID;
@@ -156,6 +157,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
private Timestamp readOnlyUntil; private Timestamp readOnlyUntil;
private Boolean isPrivate; private Boolean isPrivate;
private Boolean workInProgress; private Boolean workInProgress;
private Integer revertOf;
private ChangeDraftUpdate draftUpdate; private ChangeDraftUpdate draftUpdate;
private RobotCommentUpdate robotCommentUpdate; private RobotCommentUpdate robotCommentUpdate;
@@ -512,6 +514,13 @@ public class ChangeUpdate extends AbstractChangeUpdate {
this.groups = groups; this.groups = groups;
} }
public void setRevertOf(int revertOf) {
int ownId = getChange().getId().get();
checkArgument(ownId != revertOf, "A change cannot revert itself");
this.revertOf = revertOf;
rootOnly = true;
}
/** @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, OrmException, IOException { throws ConfigInvalidException, OrmException, IOException {
@@ -755,6 +764,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
addFooter(msg, FOOTER_WORK_IN_PROGRESS, workInProgress); addFooter(msg, FOOTER_WORK_IN_PROGRESS, workInProgress);
} }
if (revertOf != null) {
addFooter(msg, FOOTER_REVERT_OF, revertOf);
}
cb.setMessage(msg.toString()); cb.setMessage(msg.toString());
try { try {
ObjectId treeId = storeRevisionNotes(rw, ins, curr); ObjectId treeId = storeRevisionNotes(rw, ins, curr);
@@ -804,7 +817,8 @@ public class ChangeUpdate extends AbstractChangeUpdate {
&& !currentPatchSet && !currentPatchSet
&& readOnlyUntil == null && readOnlyUntil == null
&& isPrivate == null && isPrivate == null
&& workInProgress == null; && workInProgress == null
&& revertOf == null;
} }
ChangeDraftUpdate getDraftUpdate() { ChangeDraftUpdate getDraftUpdate() {

View File

@@ -688,6 +688,9 @@ public class NoteDbUpdateManager implements AutoCloseable {
ObjectId curr = old; ObjectId curr = old;
for (U u : updates) { for (U u : updates) {
if (u.isRootOnly() && !old.equals(ObjectId.zeroId())) {
throw new OrmException("Given ChangeUpdate is only allowed on initial commit");
}
ObjectId next = u.apply(or.rw, or.tempIns, curr); ObjectId next = u.apply(or.rw, or.tempIns, curr);
if (next == null) { if (next == null) {
continue; continue;

View File

@@ -618,6 +618,9 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
update.setChangeId(change.getKey().get()); update.setChangeId(change.getKey().get());
update.setBranch(change.getDest().get()); update.setBranch(change.getDest().get());
update.setSubject(change.getOriginalSubject()); update.setSubject(change.getOriginalSubject());
if (change.getRevertOf() != null) {
update.setRevertOf(change.getRevertOf().get());
}
} }
@Override @Override

View File

@@ -365,6 +365,7 @@ public class ChangeData {
private PersonIdent author; private PersonIdent author;
private PersonIdent committer; private PersonIdent committer;
private Integer unresolvedCommentCount; private Integer unresolvedCommentCount;
private Change.Id revertOf;
private ImmutableList<byte[]> refStates; private ImmutableList<byte[]> refStates;
private ImmutableList<byte[]> refStatePatterns; private ImmutableList<byte[]> refStatePatterns;

View File

@@ -179,6 +179,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
public static final String FIELD_VISIBLETO = "visibleto"; public static final String FIELD_VISIBLETO = "visibleto";
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 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";
@@ -1179,6 +1180,14 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
return new IsUnresolvedPredicate(value); return new IsUnresolvedPredicate(value);
} }
@Operator
public Predicate<ChangeData> revertof(String value) throws QueryParseException {
if (args.getSchema().hasField(ChangeField.REVERT_OF)) {
return new RevertOfPredicate(value);
}
throw new QueryParseException("'revertof' 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/")) {

View File

@@ -0,0 +1,37 @@
// Copyright (C) 2017 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;
import com.google.gwtorm.server.OrmException;
public class RevertOfPredicate extends ChangeIndexPredicate {
public RevertOfPredicate(String revertOf) {
super(ChangeField.REVERT_OF, revertOf);
}
@Override
public boolean match(ChangeData cd) throws OrmException {
if (cd.change().getRevertOf() == null) {
return false;
}
return cd.change().getRevertOf().toString().equals(value);
}
@Override
public int getCost() {
return 1;
}
}

View File

@@ -35,7 +35,7 @@ import java.util.concurrent.TimeUnit;
/** A version of the database schema. */ /** A version of the database schema. */
public abstract class SchemaVersion { public abstract class SchemaVersion {
/** The current schema version. */ /** The current schema version. */
public static final Class<Schema_155> C = Schema_155.class; public static final Class<Schema_156> C = Schema_156.class;
public static int getBinaryVersion() { public static int getBinaryVersion() {
return guessVersion(C); return guessVersion(C);

View File

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

View File

@@ -3494,6 +3494,43 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
assertThat(notes.getPendingReviewersByEmail().asTable()).isEmpty(); assertThat(notes.getPendingReviewersByEmail().asTable()).isEmpty();
} }
@Test
public void revertOfIsNullByDefault() throws Exception {
Change c = newChange();
ChangeNotes notes = newNotes(c);
assertThat(notes.getRevertOf()).isNull();
}
@Test
public void setRevertOfPersistsValue() throws Exception {
Change changeToRevert = newChange();
Change c = TestChanges.newChange(project, changeOwner.getAccountId());
ChangeUpdate update = newUpdate(c, changeOwner);
update.setChangeId(c.getKey().get());
update.setRevertOf(changeToRevert.getId().get());
update.commit();
assertThat(newNotes(c).getRevertOf()).isEqualTo(changeToRevert.getId());
}
@Test
public void setRevertOfToCurrentChangeFails() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
exception.expect(IllegalArgumentException.class);
exception.expectMessage("A change cannot revert itself");
update.setRevertOf(c.getId().get());
}
@Test
public void setRevertOfOnChildCommitFails() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
exception.expect(OrmException.class);
exception.expectMessage("Given ChangeUpdate is only allowed on initial commit");
update.setRevertOf(newChange().getId().get());
update.commit();
}
private boolean testJson() { private boolean testJson() {
return noteUtil.getWriteJson(); return noteUtil.getWriteJson();
} }

View File

@@ -32,6 +32,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Streams;
import com.google.common.truth.ThrowableSubject; import com.google.common.truth.ThrowableSubject;
import com.google.gerrit.common.Nullable; import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil; import com.google.gerrit.common.TimeUtil;
@@ -51,6 +52,7 @@ import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.ReviewerState; import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.ChangeMessageInfo; import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.CommentInfo; import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -2207,6 +2209,31 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
assertQuery("us"); assertQuery("us");
} }
@Test
public void revertOf() throws Exception {
if (getSchemaVersion() < 45) {
assertMissingField(ChangeField.REVERT_OF);
assertFailingQuery(
"revertof:1", "'revertof' operator is not supported by change index version");
return;
}
TestRepository<Repo> repo = createProject("repo");
// Create two commits and revert second commit (initial commit can't be reverted)
Change initial = insert(repo, newChange(repo));
gApi.changes().id(initial.getChangeId()).current().review(ReviewInput.approve());
gApi.changes().id(initial.getChangeId()).current().submit();
ChangeInfo changeToRevert =
gApi.changes().create(new ChangeInput("repo", "master", "commit to revert")).get();
gApi.changes().id(changeToRevert.id).current().review(ReviewInput.approve());
gApi.changes().id(changeToRevert.id).current().submit();
ChangeInfo changeThatReverts = gApi.changes().id(changeToRevert.id).revert().get();
assertQueryByIds(
"revertof:" + changeToRevert._number, new Change.Id(changeThatReverts._number));
}
protected ChangeInserter newChange(TestRepository<Repo> repo) throws Exception { protected ChangeInserter newChange(TestRepository<Repo> repo) throws Exception {
return newChange(repo, null, null, null, null, false); return newChange(repo, null, null, null, null, false);
} }
@@ -2341,36 +2368,47 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
return assertQuery(newQuery(query), changes); return assertQuery(newQuery(query), changes);
} }
protected List<ChangeInfo> assertQueryByIds(Object query, Change.Id... changes) throws Exception {
return assertQueryByIds(newQuery(query), changes);
}
protected List<ChangeInfo> assertQuery(QueryRequest query, Change... changes) throws Exception { protected List<ChangeInfo> assertQuery(QueryRequest query, Change... changes) throws Exception {
return assertQueryByIds(
query, Arrays.stream(changes).map(Change::getId).toArray(Change.Id[]::new));
}
protected List<ChangeInfo> assertQueryByIds(QueryRequest query, Change.Id... changes)
throws Exception {
List<ChangeInfo> result = query.get(); List<ChangeInfo> result = query.get();
Iterable<Integer> ids = ids(result); Iterable<Change.Id> ids = ids(result);
assertThat(ids) assertThat(ids)
.named(format(query, ids, changes)) .named(format(query, ids, changes))
.containsExactlyElementsIn(ids(changes)) .containsExactlyElementsIn(Arrays.asList(changes))
.inOrder(); .inOrder();
return result; return result;
} }
private String format(QueryRequest query, Iterable<Integer> actualIds, Change... expectedChanges) private String format(
QueryRequest query, Iterable<Change.Id> actualIds, Change.Id... expectedChanges)
throws RestApiException { throws RestApiException {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append("query '").append(query.getQuery()).append("' with expected changes "); b.append("query '").append(query.getQuery()).append("' with expected changes ");
b.append(format(Arrays.stream(expectedChanges).map(Change::getChangeId).iterator())); b.append(format(Arrays.asList(expectedChanges)));
b.append(" and result "); b.append(" and result ");
b.append(format(actualIds)); b.append(format(actualIds));
return b.toString(); return b.toString();
} }
private String format(Iterable<Integer> changeIds) throws RestApiException { private String format(Iterable<Change.Id> changeIds) throws RestApiException {
return format(changeIds.iterator()); return format(changeIds.iterator());
} }
private String format(Iterator<Integer> changeIds) throws RestApiException { private String format(Iterator<Change.Id> changeIds) throws RestApiException {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append("["); b.append("[");
while (changeIds.hasNext()) { while (changeIds.hasNext()) {
int id = changeIds.next(); Change.Id id = changeIds.next();
ChangeInfo c = gApi.changes().id(id).get(); ChangeInfo c = gApi.changes().id(id.get()).get();
b.append("{") b.append("{")
.append(id) .append(id)
.append(" (") .append(" (")
@@ -2393,12 +2431,12 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
return b.toString(); return b.toString();
} }
protected static Iterable<Integer> ids(Change... changes) { protected static Iterable<Change.Id> ids(Change... changes) {
return FluentIterable.from(Arrays.asList(changes)).transform(in -> in.getId().get()); return Arrays.stream(changes).map(c -> c.getId()).collect(toList());
} }
protected static Iterable<Integer> ids(Iterable<ChangeInfo> changes) { protected static Iterable<Change.Id> ids(Iterable<ChangeInfo> changes) {
return FluentIterable.from(changes).transform(in -> in._number); return Streams.stream(changes).map(c -> new Change.Id(c._number)).collect(toList());
} }
protected static long lastUpdatedMs(Change c) { protected static long lastUpdatedMs(Change c) {