Merge changes from topic 'plugin-api'
* changes:
ServerInfoIT: Use API to install plugin
Add implementation of PluginApi
PluginLoader: Ensure that $site/plugins folder exists
Plugins#ListRequest: Remove parameter from all() method
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 8b4e529..b307efa 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -5729,6 +5729,8 @@
When present, change is marked as Work In Progress.
|`has_review_started` |optional, not set if `false`|
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]]
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 74ae568..bbca364 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -129,6 +129,11 @@
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.
+[[revertof]]
+revertof:'ID'::
++
+Changes that revert the change specified by the numeric 'ID'.
+
[[reviewerin]]
reviewerin:'GROUP'::
+
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 4dc8e51..6d0ba19 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -617,6 +617,7 @@
assertThat(revertChange.messages).hasSize(1);
assertThat(revertChange.messages.iterator().next().message).isEqualTo("Uploaded patch set 1.");
+ assertThat(revertChange.revertOf).isEqualTo(gApi.changes().id(r.getChangeId()).get()._number);
}
@Test
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
index f6d9f4c..a4f85e5 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -48,6 +48,7 @@
public Boolean isPrivate;
public Boolean workInProgress;
public Boolean hasReviewStarted;
+ public Integer revertOf;
public int _number;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
index 753d421..a5fc4f3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -154,6 +154,8 @@
suggestions.add("unresolved:");
+ suggestions.add("revertof:");
+
if (Gerrit.isNoteDbEnabled()) {
suggestions.add("cc:");
suggestions.add("hashtag:");
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
index 1a08d17..4252c72 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
@@ -524,6 +524,10 @@
@Column(id = 22)
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 */
@Column(id = 101, notNull = false, length = Integer.MAX_VALUE)
protected String noteDbState;
@@ -564,6 +568,7 @@
workInProgress = other.workInProgress;
reviewStarted = other.reviewStarted;
noteDbState = other.noteDbState;
+ revertOf = other.revertOf;
}
/** Legacy 32 bit integer identity for a change. */
@@ -733,6 +738,14 @@
this.reviewStarted = reviewStarted;
}
+ public void setRevertOf(Id revertOf) {
+ this.revertOf = revertOf;
+ }
+
+ public Id getRevertOf() {
+ return this.revertOf;
+ }
+
public String getNoteDbState() {
return noteDbState;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
index bf417d0..3e8a146 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -129,6 +129,7 @@
private boolean fireRevisionCreated;
private boolean sendMail;
private boolean updateRef;
+ private Change.Id revertOf;
// Fields set during the insertion process.
private ReceiveCommand cmd;
@@ -198,6 +199,7 @@
change.setPrivate(isPrivate);
change.setWorkInProgress(workInProgress);
change.setReviewStarted(!workInProgress);
+ change.setRevertOf(revertOf);
return change;
}
@@ -319,6 +321,11 @@
return this;
}
+ public ChangeInserter setRevertOf(Change.Id revertOf) {
+ this.revertOf = revertOf;
+ return this;
+ }
+
public void setPushCertificate(String cert) {
pushCert = cert;
}
@@ -390,6 +397,9 @@
update.setPsDescription(patchSetDescription);
update.setPrivate(isPrivate);
update.setWorkInProgress(workInProgress);
+ if (revertOf != null) {
+ update.setRevertOf(revertOf.get());
+ }
boolean draft = status == Change.Status.DRAFT;
List<String> newGroups = groups;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 33a1565..80bf1c3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -542,6 +542,7 @@
out.submitted = getSubmittedOn(cd);
out.plugins =
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 limited to specific patch sets but not the current patch set, don't
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
index af06054..53e09d4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
@@ -218,6 +218,7 @@
Set<Account.Id> ccs = new HashSet<>(reviewerSet.byState(ReviewerStateInternal.CC));
ccs.remove(user.getAccountId());
ins.setExtraCC(ccs);
+ ins.setRevertOf(changeIdToRevert);
try (BatchUpdate bu = updateFactory.create(db.get(), project, user, now)) {
bu.setRepository(git, revWalk, oi);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
index 7a8cc72..3fe2d13 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -207,6 +207,11 @@
.stored()
.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
static List<String> getReviewerFieldValues(ReviewerSet reviewers) {
List<String> r = new ArrayList<>(reviewers.asTable().size() * 2);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index bb0118b..2f03779 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -78,6 +78,7 @@
static final Schema<ChangeData> V43 =
schema(V42, ChangeField.EXACT_AUTHOR, ChangeField.EXACT_COMMITTER);
+ @Deprecated
static final Schema<ChangeData> V44 =
schema(
V43,
@@ -85,6 +86,8 @@
ChangeField.PENDING_REVIEWER,
ChangeField.PENDING_REVIEWER_BY_EMAIL);
+ static final Schema<ChangeData> V45 = schema(V44, ChangeField.REVERT_OF);
+
public static final String NAME = "changes";
public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
index 472eda1..6dfe7a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
@@ -57,6 +57,7 @@
protected PatchSet.Id psId;
private ObjectId result;
+ protected boolean rootOnly;
protected AbstractChangeUpdate(
Config cfg,
@@ -190,6 +191,11 @@
/** Whether no updates have been done. */
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
* the same as the change's project.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
index d56baed..526f3c8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
@@ -233,7 +233,8 @@
// last time this file was updated.
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.class, 1, 2, 3, 4, 5, 6, 7);
checkColumns(PatchSet.Id.class, 1, 2);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
index ce3b664..b23c1de 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
@@ -83,6 +83,7 @@
public static final FooterKey FOOTER_TOPIC = new FooterKey("Topic");
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");
private static final String AUTHOR = "Author";
private static final String BASE_PATCH_SET = "Base-for-patch-set";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
index 85e49ce..b01a8b3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -623,6 +623,10 @@
return state.isWorkInProgress();
}
+ public Change.Id getRevertOf() {
+ return state.revertOf();
+ }
+
public boolean hasReviewStarted() {
return state.hasReviewStarted();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index eb366bb..4b239ea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -27,6 +27,7 @@
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_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_SUBJECT;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMISSION_ID;
@@ -169,6 +170,7 @@
private Boolean hasReviewStarted;
private ReviewerSet pendingReviewers;
private ReviewerByEmailSet pendingReviewersByEmail;
+ private Change.Id revertOf;
ChangeNotesParser(
Change.Id changeId,
@@ -267,7 +269,8 @@
readOnlyUntil,
isPrivate,
workInProgress,
- hasReviewStarted);
+ hasReviewStarted,
+ revertOf);
}
private PatchSet.Id buildCurrentPatchSetId() {
@@ -415,6 +418,10 @@
parseIsPrivate(commit);
}
+ if (revertOf == null) {
+ revertOf = parseRevertOf(commit);
+ }
+
previousWorkInProgressFooter = null;
parseWorkInProgress(commit);
@@ -1022,6 +1029,18 @@
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() {
Iterator<Table.Cell<Account.Id, ReviewerStateInternal, Timestamp>> rit =
reviewers.cellSet().iterator();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java
index f9899e5..1dd944d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java
@@ -78,7 +78,8 @@
null,
null,
null,
- true);
+ true,
+ null);
}
static ChangeNotesState create(
@@ -113,7 +114,8 @@
@Nullable Timestamp readOnlyUntil,
@Nullable Boolean isPrivate,
@Nullable Boolean workInProgress,
- boolean hasReviewStarted) {
+ boolean hasReviewStarted,
+ @Nullable Change.Id revertOf) {
if (hashtags == null) {
hashtags = ImmutableSet.of();
}
@@ -135,7 +137,8 @@
status,
isPrivate,
workInProgress,
- hasReviewStarted),
+ hasReviewStarted,
+ revertOf),
ImmutableSet.copyOf(pastAssignees),
ImmutableSet.copyOf(hashtags),
ImmutableList.copyOf(patchSets.entrySet()),
@@ -153,7 +156,8 @@
readOnlyUntil,
isPrivate,
workInProgress,
- hasReviewStarted);
+ hasReviewStarted,
+ revertOf);
}
/**
@@ -205,6 +209,9 @@
@Nullable
abstract Boolean hasReviewStarted();
+
+ @Nullable
+ abstract Change.Id revertOf();
}
// Only null if NoteDb is disabled.
@@ -258,6 +265,9 @@
@Nullable
abstract Boolean hasReviewStarted();
+ @Nullable
+ abstract Change.Id revertOf();
+
Change newChange(Project.NameKey project) {
ChangeColumns c = checkNotNull(columns(), "columns are required");
Change change =
@@ -318,6 +328,7 @@
change.setPrivate(c.isPrivate() == null ? false : c.isPrivate());
change.setWorkInProgress(c.isWorkInProgress() == null ? false : c.isWorkInProgress());
change.setReviewStarted(c.hasReviewStarted() == null ? false : c.hasReviewStarted());
+ change.setRevertOf(c.revertOf());
if (!patchSets().isEmpty()) {
change.setCurrentPatchSet(c.currentPatchSetId(), c.subject(), c.originalSubject());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index fcde617..d692dff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -32,6 +32,7 @@
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_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_SUBJECT;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMISSION_ID;
@@ -156,6 +157,7 @@
private Timestamp readOnlyUntil;
private Boolean isPrivate;
private Boolean workInProgress;
+ private Integer revertOf;
private ChangeDraftUpdate draftUpdate;
private RobotCommentUpdate robotCommentUpdate;
@@ -512,6 +514,13 @@
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 */
private ObjectId storeRevisionNotes(RevWalk rw, ObjectInserter inserter, ObjectId curr)
throws ConfigInvalidException, OrmException, IOException {
@@ -755,6 +764,10 @@
addFooter(msg, FOOTER_WORK_IN_PROGRESS, workInProgress);
}
+ if (revertOf != null) {
+ addFooter(msg, FOOTER_REVERT_OF, revertOf);
+ }
+
cb.setMessage(msg.toString());
try {
ObjectId treeId = storeRevisionNotes(rw, ins, curr);
@@ -804,7 +817,8 @@
&& !currentPatchSet
&& readOnlyUntil == null
&& isPrivate == null
- && workInProgress == null;
+ && workInProgress == null
+ && revertOf == null;
}
ChangeDraftUpdate getDraftUpdate() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
index 45cf244..eef16fb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -688,6 +688,9 @@
ObjectId curr = old;
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);
if (next == null) {
continue;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
index 9c7f3bd..7aff213 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
@@ -618,6 +618,9 @@
update.setChangeId(change.getKey().get());
update.setBranch(change.getDest().get());
update.setSubject(change.getOriginalSubject());
+ if (change.getRevertOf() != null) {
+ update.setRevertOf(change.getRevertOf().get());
+ }
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 96f6b5d..9a939bb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -365,6 +365,7 @@
private PersonIdent author;
private PersonIdent committer;
private Integer unresolvedCommentCount;
+ private Change.Id revertOf;
private ImmutableList<byte[]> refStates;
private ImmutableList<byte[]> refStatePatterns;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 161233e..aecfc42 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -179,6 +179,7 @@
public static final String FIELD_VISIBLETO = "visibleto";
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 ARG_ID_USER = "user";
public static final String ARG_ID_GROUP = "group";
@@ -1179,6 +1180,14 @@
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
protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
if (query.startsWith("refs/")) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevertOfPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevertOfPredicate.java
new file mode 100644
index 0000000..7f4ade0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevertOfPredicate.java
@@ -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;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index 647a205..2a74aee 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -35,7 +35,7 @@
/** A version of the database schema. */
public abstract class SchemaVersion {
/** 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() {
return guessVersion(C);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_156.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_156.java
new file mode 100644
index 0000000..fd8fc00
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_156.java
@@ -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);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
index 3d12680..db8ec25 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -3494,6 +3494,43 @@
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() {
return noteUtil.getWriteJson();
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index d5e4aa7..044dbbe 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -32,6 +32,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import com.google.common.collect.Streams;
import com.google.common.truth.ThrowableSubject;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
@@ -51,6 +52,7 @@
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.common.AccountInfo;
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.CommentInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -2207,6 +2209,31 @@
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 {
return newChange(repo, null, null, null, null, false);
}
@@ -2341,36 +2368,47 @@
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 {
+ 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();
- Iterable<Integer> ids = ids(result);
+ Iterable<Change.Id> ids = ids(result);
assertThat(ids)
.named(format(query, ids, changes))
- .containsExactlyElementsIn(ids(changes))
+ .containsExactlyElementsIn(Arrays.asList(changes))
.inOrder();
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 {
StringBuilder b = new StringBuilder();
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(format(actualIds));
return b.toString();
}
- private String format(Iterable<Integer> changeIds) throws RestApiException {
+ private String format(Iterable<Change.Id> changeIds) throws RestApiException {
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();
b.append("[");
while (changeIds.hasNext()) {
- int id = changeIds.next();
- ChangeInfo c = gApi.changes().id(id).get();
+ Change.Id id = changeIds.next();
+ ChangeInfo c = gApi.changes().id(id.get()).get();
b.append("{")
.append(id)
.append(" (")
@@ -2393,12 +2431,12 @@
return b.toString();
}
- protected static Iterable<Integer> ids(Change... changes) {
- return FluentIterable.from(Arrays.asList(changes)).transform(in -> in.getId().get());
+ protected static Iterable<Change.Id> ids(Change... changes) {
+ return Arrays.stream(changes).map(c -> c.getId()).collect(toList());
}
- protected static Iterable<Integer> ids(Iterable<ChangeInfo> changes) {
- return FluentIterable.from(changes).transform(in -> in._number);
+ protected static Iterable<Change.Id> ids(Iterable<ChangeInfo> changes) {
+ return Streams.stream(changes).map(c -> new Change.Id(c._number)).collect(toList());
}
protected static long lastUpdatedMs(Change c) {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
index 120dbd5..91282f0 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
@@ -118,6 +118,7 @@
this._changesPerPage = prefs.changes_per_page;
return this._getChanges();
}).then(changes => {
+ changes = changes || [];
if (this._query && changes.length === 1) {
for (const query in LookupQueryPatterns) {
if (LookupQueryPatterns.hasOwnProperty(query) &&
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 75f6749..bf55fc6 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -166,7 +166,8 @@
],
keyBindings: {
- esc: '_handleEscKey',
+ 'esc': '_handleEscKey',
+ 'ctrl+enter meta+enter': '_handleEnterKey',
},
observers: [
@@ -229,6 +230,10 @@
this.cancel();
},
+ _handleEnterKey(e) {
+ this._submit();
+ },
+
_ccsChanged(splices) {
if (splices && splices.indexSplices) {
this._processReviewerChange(splices.indexSplices, ReviewerTypes.CC);
@@ -622,6 +627,10 @@
_sendTapHandler(e) {
e.preventDefault();
+ this._submit();
+ },
+
+ _submit() {
if (this._ccsEnabled && !this.$$('#ccs').submitEntryText()) {
// Do not proceed with the send if there is an invalid email entry in
// the text field of the CC entry.
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index 97fa033..5d9a2db 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -701,6 +701,18 @@
assert.isTrue(cancelHandler.called);
});
+ test('should not send on enter key', () => {
+ element.addEventListener('send', () => assert.fail('wrongly called'));
+ MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
+ flushAsynchronousOperations();
+ });
+
+ test('emit send on ctrl+enter key', done => {
+ element.addEventListener('send', () => done());
+ MockInteractions.pressAndReleaseKeyOn(element, 13, 'ctrl', 'enter');
+ flushAsynchronousOperations();
+ });
+
test('_computeMessagePlaceholder', () => {
assert.equal(
element._computeMessagePlaceholder(false),