Merge changes If84461dd,I602a7abf,I090c5e21,Ia0ae8115
* changes:
Document ApprovalInfo class
Document ActionInfo class
Document AccountExternalIdInfo class
Document fields in AccountInfo / AccountDetailInfo
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
index 6f55398..9dd58b8 100644
--- a/Documentation/intro-user.txt
+++ b/Documentation/intro-user.txt
@@ -453,6 +453,15 @@
Abandoned changes can be link:user-review-ui.html#restore[restored] if
later they are needed again.
+[[cherrypickof]]
+== Cherry-Pick changes of a Change
+
+When a change is created/updated using the 'cherry-pick' functionalty,
+the original change and patchset details are recorded in the Change's
+cherrypick field. This field cannot be set or updated by the user in
+any way. It is set automatically after the cherry-pick operation completes
+successfully.
+
[[topics]]
== Using Topics
diff --git a/Documentation/metrics.txt b/Documentation/metrics.txt
index b373129..236792b 100644
--- a/Documentation/metrics.txt
+++ b/Documentation/metrics.txt
@@ -141,10 +141,10 @@
=== NoteDb
-* `notedb/update_latency`: NoteDb update latency by table.
-* `notedb/stage_update_latency`: Latency for staging updates to NoteDb by table.
-* `notedb/read_latency`: NoteDb read latency by table.
-* `notedb/parse_latency`: NoteDb parse latency by table.
+* `notedb/update_latency`: NoteDb update latency for changes.
+* `notedb/stage_update_latency`: Latency for staging change updates to NoteDb.
+* `notedb/read_latency`: NoteDb read latency for changes.
+* `notedb/parse_latency`: NoteDb parse latency for changes.
* `notedb/external_id_cache_load_count`: Total number of times the external ID
cache loader was called.
* `notedb/external_id_partial_read_latency`: Latency for generating a new external ID
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 6cb111b..73ff085 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -73,7 +73,9 @@
Queries changes visible to the caller. The
link:user-search.html#_search_operators[query string] must be provided
by the `q` parameter. The `n` parameter can be used to limit the
-returned results.
+returned results. The `no-limit` parameter can be used remove the default
+limit on queries and return all results. This might not be supported by
+all index backends.
As result a list of link:#change-info[ChangeInfo] entries is returned.
The change output is sorted by the last update time, most recently
@@ -369,11 +371,6 @@
as link:#tracking-id-info[TrackingIdInfo].
--
-[[no-limit]]
---
-* `NO-LIMIT`: Return all results
---
-
.Request
----
GET /changes/?q=97&o=CURRENT_REVISION&o=CURRENT_COMMIT&o=CURRENT_FILES&o=DOWNLOAD_COMMANDS HTTP/1.0
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index affe1ea..e9e3b94 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1477,8 +1477,8 @@
|`git_basic_auth_policy` |optional|
The link:config-gerrit.html#auth.gitBasicAuthPolicy[policy] to authenticate
Git over HTTP and REST API requests when
-link:config-gerrit.html#auth.type[authentication type] is `LDAP`.
-Can be `HTTP`, `LDAP` or `HTTP_LDAP`.
+link:config-gerrit.html#auth.type[authentication type] is `LDAP`,
+`LDAP_BIND` or `OAUTH`. Can be `HTTP`, `LDAP`, `HTTP_LDAP` or `OAUTH`.
|==========================================
[[cache-info]]
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index aa5edf0..359c32a 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -229,6 +229,16 @@
Changes whose link:intro-user.html#hashtags[hashtag] matches 'HASHTAG'.
The match is case-insensitive.
+[[cherrypickof]]
+cherrypickof:'CHANGE[,PATCHSET]'::
++
+Changes which were created using the 'cherry-pick' functionality and
+whose source change number matches 'CHANGE' and source patchset number
+matches 'PATCHSET'. Note that 'PATCHSET' is optional. For example, a
+`cherrypickof:12345` matches all changes which were cherry-picked from
+change 12345 and `cherrypickof:12345,2` matches all changes which were
+cherry-picked from the 2nd patchset of change 12345.
+
[[ref]]
ref:'REF'::
+
diff --git a/e2e-tests/load-tests/Dockerfile b/e2e-tests/load-tests/Dockerfile
new file mode 100644
index 0000000..ceae672
--- /dev/null
+++ b/e2e-tests/load-tests/Dockerfile
@@ -0,0 +1,32 @@
+FROM denvazh/gatling:3.2.1
+
+ARG gatling_git_version=1.0.9
+RUN apk add --no-cache maven
+RUN mvn dependency:get \
+ -DgroupId=com.gerritforge \
+ -DartifactId=gatling-git_2.12 \
+ -Dversion=$gatling_git_version \
+ -Dtype=pom
+RUN mvn dependency:copy-dependencies \
+ -f /root/.m2/repository/com/gerritforge/gatling-git_2.12/$gatling_git_version/gatling-git_2.12-$gatling_git_version.pom \
+ -DoutputDirectory=/opt/gatling/lib/
+RUN mvn dependency:get \
+ -Dartifact=com.gerritforge:gatling-git_2.12:$gatling_git_version:jar \
+ -Ddest=/opt/gatling/lib/gatling-git.jar
+
+ARG gatling_home=/home/gatling
+RUN addgroup -g 1000 -S appgroup && \
+ adduser -u 1000 -S gatling -G appgroup -h $gatling_home
+RUN cp -R /opt/gatling/* $gatling_home && \
+ chown -R gatling:appgroup $gatling_home
+
+WORKDIR $gatling_home
+USER gatling
+
+COPY ./src/test/scala/com/google/gerrit/scenarios $gatling_home/user-files/simulations
+COPY ./src/test/resources/application.conf $gatling_home/conf
+COPY ./src/test/resources/data $gatling_home/user-files/resources/data
+
+ENV GATLING_HOME=$gatling_home
+
+ENTRYPOINT ["/home/gatling/bin/gatling.sh"]
diff --git a/e2e-tests/load-tests/README.md b/e2e-tests/load-tests/README.md
new file mode 100644
index 0000000..534fde5
--- /dev/null
+++ b/e2e-tests/load-tests/README.md
@@ -0,0 +1,11 @@
+# How to build the Docker image
+
+```$shell
+docker build . -t e2e-tests
+```
+
+# How to run a test
+
+```$shell
+docker run -it e2e-tests -s com.google.gerrit.scenarios.ReplayRecordsFromFeederScenario
+```
diff --git a/java/com/google/gerrit/acceptance/UseLocalDisk.java b/java/com/google/gerrit/acceptance/UseLocalDisk.java
index e177bb4..192caa0 100644
--- a/java/com/google/gerrit/acceptance/UseLocalDisk.java
+++ b/java/com/google/gerrit/acceptance/UseLocalDisk.java
@@ -21,6 +21,15 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
+/**
+ * Annotation to mark tests that require a local disk for the execution.
+ *
+ * <p>Tests that do not have this annotation are executed in memory.
+ *
+ * <p>Using this annotation makes the execution of the test more expensive/slower. This is why it
+ * should only be used if the test requires a local disk (e.g. if the test triggers the Git garbage
+ * collection functionality which only works with a local disk).
+ */
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface UseLocalDisk {}
diff --git a/java/com/google/gerrit/acceptance/UseSsh.java b/java/com/google/gerrit/acceptance/UseSsh.java
index 5509140..12a9977 100644
--- a/java/com/google/gerrit/acceptance/UseSsh.java
+++ b/java/com/google/gerrit/acceptance/UseSsh.java
@@ -21,6 +21,14 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
+/**
+ * Annotation to mark SSH tests.
+ *
+ * <p>When running tests the SSH functionality is disabled unless the {@link UseSsh} annotation is
+ * used.
+ *
+ * <p>SSH tests can be skipped when executing tests (see {@link com.google.gerrit.testing.SshMode}).
+ */
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface UseSsh {}
diff --git a/java/com/google/gerrit/common/data/GroupDetail.java b/java/com/google/gerrit/common/data/GroupDetail.java
deleted file mode 100644
index 991d5df..0000000
--- a/java/com/google/gerrit/common/data/GroupDetail.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (C) 2008 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.common.data;
-
-import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.AccountGroup;
-import java.util.Set;
-
-public class GroupDetail {
- private Set<Account.Id> members;
- private Set<AccountGroup.UUID> includes;
-
- public GroupDetail(Set<Account.Id> members, Set<AccountGroup.UUID> includes) {
- this.members = members;
- this.includes = includes;
- }
-
- public Set<Account.Id> getMembers() {
- return members;
- }
-
- public Set<AccountGroup.UUID> getIncludes() {
- return includes;
- }
-}
diff --git a/java/com/google/gerrit/common/data/GroupInfo.java b/java/com/google/gerrit/common/data/GroupInfo.java
deleted file mode 100644
index 5c36104..0000000
--- a/java/com/google/gerrit/common/data/GroupInfo.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (C) 2011 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.common.data;
-
-import com.google.gerrit.entities.AccountGroup;
-
-/** Summary information about an {@link AccountGroup}, for simple tabular displays. */
-public class GroupInfo {
- protected AccountGroup.UUID uuid;
- protected String name;
- protected String description;
- protected String url;
-
- protected GroupInfo() {}
-
- /**
- * Create an anonymous group info, when only the id is known.
- *
- * <p>This constructor should only be a last-ditch effort, when the usual group lookup has failed
- * and a stale group id has been discovered in the data store.
- */
- public GroupInfo(AccountGroup.UUID uuid) {
- this.uuid = uuid;
- }
-
- /**
- * Create a group description from a real data store record.
- *
- * @param a the data store record holding the specific group details.
- */
- public GroupInfo(GroupDescription.Basic a) {
- uuid = a.getGroupUUID();
- name = a.getName();
- url = a.getUrl();
-
- if (a instanceof GroupDescription.Internal) {
- description = ((GroupDescription.Internal) a).getDescription();
- }
- }
-
- /** @return the unique local id of the group */
- public AccountGroup.UUID getId() {
- return uuid;
- }
-
- /** @return the name of the group; null if not supplied */
- public String getName() {
- return name;
- }
-
- /** @return the description of the group; null if not supplied */
- public String getDescription() {
- return description;
- }
-
- public String getUrl() {
- return url;
- }
-}
diff --git a/java/com/google/gerrit/entities/Change.java b/java/com/google/gerrit/entities/Change.java
index 04e97dc..c768094 100644
--- a/java/com/google/gerrit/entities/Change.java
+++ b/java/com/google/gerrit/entities/Change.java
@@ -531,6 +531,9 @@
/** References a change that this change reverts. */
@Nullable protected Id revertOf;
+ /** References the source change and patchset that this change was cherry-picked from. */
+ @Nullable protected PatchSet.Id cherryPickOf;
+
protected Change() {}
public Change(
@@ -567,6 +570,7 @@
workInProgress = other.workInProgress;
reviewStarted = other.reviewStarted;
revertOf = other.revertOf;
+ cherryPickOf = other.cherryPickOf;
}
/** Legacy 32 bit integer identity for a change. */
@@ -760,6 +764,14 @@
return this.revertOf;
}
+ public PatchSet.Id getCherryPickOf() {
+ return cherryPickOf;
+ }
+
+ public void setCherryPickOf(@Nullable PatchSet.Id cherryPickOf) {
+ this.cherryPickOf = cherryPickOf;
+ }
+
@Override
public String toString() {
return new StringBuilder(getClass().getSimpleName())
diff --git a/java/com/google/gerrit/entities/PatchSet.java b/java/com/google/gerrit/entities/PatchSet.java
index 8b93dbc..4a33bd7 100644
--- a/java/com/google/gerrit/entities/PatchSet.java
+++ b/java/com/google/gerrit/entities/PatchSet.java
@@ -124,13 +124,17 @@
return id();
}
+ public String getCommaSeparatedChangeAndPatchSetId() {
+ return changeId().toString() + ',' + id();
+ }
+
public String toRefName() {
return changeId().refPrefixBuilder().append(id()).toString();
}
@Override
public final String toString() {
- return changeId().toString() + ',' + id();
+ return getCommaSeparatedChangeAndPatchSetId();
}
}
diff --git a/java/com/google/gerrit/entities/converter/ChangeProtoConverter.java b/java/com/google/gerrit/entities/converter/ChangeProtoConverter.java
index 5b066ea..25e68f9 100644
--- a/java/com/google/gerrit/entities/converter/ChangeProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/ChangeProtoConverter.java
@@ -29,6 +29,8 @@
private final ProtoConverter<Entities.Change_Id, Change.Id> changeIdConverter =
ChangeIdProtoConverter.INSTANCE;
+ private final ProtoConverter<Entities.PatchSet_Id, PatchSet.Id> patchSetIdConverter =
+ PatchSetIdProtoConverter.INSTANCE;
private final ProtoConverter<Entities.Change_Key, Change.Key> changeKeyConverter =
ChangeKeyProtoConverter.INSTANCE;
private final ProtoConverter<Entities.Account_Id, Account.Id> accountIdConverter =
@@ -78,6 +80,10 @@
if (revertOf != null) {
builder.setRevertOf(changeIdConverter.toProto(revertOf));
}
+ PatchSet.Id cherryPickOf = change.getCherryPickOf();
+ if (cherryPickOf != null) {
+ builder.setCherryPickOf(patchSetIdConverter.toProto(cherryPickOf));
+ }
return builder.build();
}
@@ -118,6 +124,9 @@
if (proto.hasRevertOf()) {
change.setRevertOf(changeIdConverter.fromProto(proto.getRevertOf()));
}
+ if (proto.hasCherryPickOf()) {
+ change.setCherryPickOf(patchSetIdConverter.fromProto(proto.getCherryPickOf()));
+ }
return change;
}
diff --git a/java/com/google/gerrit/extensions/common/AuthInfo.java b/java/com/google/gerrit/extensions/common/AuthInfo.java
index 79c2250..e2977ec 100644
--- a/java/com/google/gerrit/extensions/common/AuthInfo.java
+++ b/java/com/google/gerrit/extensions/common/AuthInfo.java
@@ -19,6 +19,7 @@
import com.google.gerrit.extensions.client.GitBasicAuthPolicy;
import java.util.List;
+/** API response containing values from the {@code auth} section of {@code gerrit.config}. */
public class AuthInfo {
public AuthType authType;
public Boolean useContributorAgreements;
diff --git a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
index e8aeb40..67d0b0d9 100644
--- a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
@@ -14,6 +14,7 @@
package com.google.gerrit.extensions.common;
+/** API response containing values from the {@code change} section of {@code gerrit.config}. */
public class ChangeConfigInfo {
public Boolean allowBlame;
public Boolean showAssigneeInChangesTable;
diff --git a/java/com/google/gerrit/extensions/common/ChangeIndexConfigInfo.java b/java/com/google/gerrit/extensions/common/ChangeIndexConfigInfo.java
index 7bca79e..0cce106 100644
--- a/java/com/google/gerrit/extensions/common/ChangeIndexConfigInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeIndexConfigInfo.java
@@ -14,6 +14,9 @@
package com.google.gerrit.extensions.common;
+/**
+ * API response containing values from the {@code index.change} section of {@code gerrit.config}.
+ */
public class ChangeIndexConfigInfo {
public Boolean indexMergeable;
}
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfo.java b/java/com/google/gerrit/extensions/common/ChangeInfo.java
index 0d7c5c7..3b3f2ad 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -53,6 +53,8 @@
public Boolean hasReviewStarted;
public Integer revertOf;
public String submissionId;
+ public Integer cherryPickOfChange;
+ public Integer cherryPickOfPatchSet;
public int _number;
diff --git a/java/com/google/gerrit/extensions/common/DownloadInfo.java b/java/com/google/gerrit/extensions/common/DownloadInfo.java
index 5ea5992..ea94e4d 100644
--- a/java/com/google/gerrit/extensions/common/DownloadInfo.java
+++ b/java/com/google/gerrit/extensions/common/DownloadInfo.java
@@ -17,6 +17,7 @@
import java.util.List;
import java.util.Map;
+/** API response containing values from the {@code download} section of {@code gerrit.config}. */
public class DownloadInfo {
public Map<String, DownloadSchemeInfo> schemes;
public List<String> archives;
diff --git a/java/com/google/gerrit/extensions/common/GerritInfo.java b/java/com/google/gerrit/extensions/common/GerritInfo.java
index 5c462d9..2ae6703 100644
--- a/java/com/google/gerrit/extensions/common/GerritInfo.java
+++ b/java/com/google/gerrit/extensions/common/GerritInfo.java
@@ -14,6 +14,7 @@
package com.google.gerrit.extensions.common;
+/** API response containing values from the {@code gerrit} section of {@code gerrit.config}. */
public class GerritInfo {
public String allProjects;
public String allUsers;
diff --git a/java/com/google/gerrit/extensions/common/IndexConfigInfo.java b/java/com/google/gerrit/extensions/common/IndexConfigInfo.java
index 084c53a..f3f510b 100644
--- a/java/com/google/gerrit/extensions/common/IndexConfigInfo.java
+++ b/java/com/google/gerrit/extensions/common/IndexConfigInfo.java
@@ -14,6 +14,7 @@
package com.google.gerrit.extensions.common;
+/** API response containing values from the {@code index} section of {@code gerrit.config}. */
public class IndexConfigInfo {
public ChangeIndexConfigInfo change;
}
diff --git a/java/com/google/gerrit/extensions/common/ReceiveInfo.java b/java/com/google/gerrit/extensions/common/ReceiveInfo.java
index 9fcd92b..23e45ae 100644
--- a/java/com/google/gerrit/extensions/common/ReceiveInfo.java
+++ b/java/com/google/gerrit/extensions/common/ReceiveInfo.java
@@ -14,6 +14,7 @@
package com.google.gerrit.extensions.common;
+/** API response containing values from the {@code receive} section of {@code gerrit.config}. */
public class ReceiveInfo {
public Boolean enableSignedPush;
}
diff --git a/java/com/google/gerrit/extensions/common/ServerInfo.java b/java/com/google/gerrit/extensions/common/ServerInfo.java
index 9cf1ec1..555c363 100644
--- a/java/com/google/gerrit/extensions/common/ServerInfo.java
+++ b/java/com/google/gerrit/extensions/common/ServerInfo.java
@@ -14,6 +14,7 @@
package com.google.gerrit.extensions.common;
+/** API response containing values from {@code gerrit.config} as nested objects. */
public class ServerInfo {
public AccountsInfo accounts;
public AuthInfo auth;
diff --git a/java/com/google/gerrit/extensions/common/SshdInfo.java b/java/com/google/gerrit/extensions/common/SshdInfo.java
index fb9cb16..7242f98 100644
--- a/java/com/google/gerrit/extensions/common/SshdInfo.java
+++ b/java/com/google/gerrit/extensions/common/SshdInfo.java
@@ -14,4 +14,10 @@
package com.google.gerrit.extensions.common;
+/**
+ * API response containing values from the {@code sshd} section of {@code gerrit.config}.
+ *
+ * <p>This is currently empty, but is a class (rather than boolean) for consistency. If we ever
+ * export SSH configuration, more fields will be added here.
+ */
public class SshdInfo {}
diff --git a/java/com/google/gerrit/extensions/common/SuggestInfo.java b/java/com/google/gerrit/extensions/common/SuggestInfo.java
index 91ca547..8c8c42f 100644
--- a/java/com/google/gerrit/extensions/common/SuggestInfo.java
+++ b/java/com/google/gerrit/extensions/common/SuggestInfo.java
@@ -14,6 +14,9 @@
package com.google.gerrit.extensions.common;
+/** API response containing values from the {@code suggest} section of {@code gerrit.config}. */
public class SuggestInfo {
+
+ /** Number of characters after which we provide suggestions for group member completion. */
public int from;
}
diff --git a/java/com/google/gerrit/extensions/common/UserConfigInfo.java b/java/com/google/gerrit/extensions/common/UserConfigInfo.java
index ec03dd0..3d8e851 100644
--- a/java/com/google/gerrit/extensions/common/UserConfigInfo.java
+++ b/java/com/google/gerrit/extensions/common/UserConfigInfo.java
@@ -14,6 +14,7 @@
package com.google.gerrit.extensions.common;
+/** API response containing values from the {@code user} section of {@code gerrit.config}. */
public class UserConfigInfo {
public String anonymousCowardName;
}
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index de39472..51a6191 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -98,7 +98,6 @@
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.httpd.restapi.ParameterParser.QueryParams;
import com.google.gerrit.json.OutputFormat;
@@ -106,6 +105,7 @@
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.ExceptionHook;
+import com.google.gerrit.server.ExceptionHookImpl;
import com.google.gerrit.server.OptionUtil;
import com.google.gerrit.server.RequestInfo;
import com.google.gerrit.server.RequestListener;
@@ -132,7 +132,6 @@
import com.google.gerrit.server.update.RetryableAction;
import com.google.gerrit.server.update.RetryableAction.Action;
import com.google.gerrit.server.update.RetryableAction.ActionType;
-import com.google.gerrit.server.update.UpdateException;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.util.http.CacheHeaders;
import com.google.gerrit.util.http.RequestUtil;
@@ -662,16 +661,6 @@
logger.atSevere().withCause(e).log("Error in %s %s", req.getMethod(), uriForLogging(req));
responseBytes =
replyError(req, res, status = SC_NOT_IMPLEMENTED, messageOr(e, "Not Implemented"), e);
- } catch (UpdateException e) {
- cause = Optional.of(e);
- Throwable t = e.getCause();
- if (t instanceof LockFailureException) {
- logger.atSevere().withCause(t).log("Error in %s %s", req.getMethod(), uriForLogging(req));
- responseBytes = replyError(req, res, status = SC_SERVICE_UNAVAILABLE, "Lock failure", e);
- } else {
- status = SC_INTERNAL_SERVER_ERROR;
- responseBytes = handleException(traceContext, e, req, res);
- }
} catch (QuotaException e) {
cause = Optional.of(e);
responseBytes =
@@ -684,6 +673,13 @@
e);
} catch (Exception e) {
cause = Optional.of(e);
+
+ if (ExceptionHookImpl.isLockFailure(e)) {
+ logger.atSevere().withCause(e.getCause()).log(
+ "Error in %s %s", req.getMethod(), uriForLogging(req));
+ responseBytes = replyError(req, res, status = SC_SERVICE_UNAVAILABLE, "Lock failure", e);
+ }
+
status = SC_INTERNAL_SERVER_ERROR;
responseBytes = handleException(traceContext, e, req, res);
} finally {
diff --git a/java/com/google/gerrit/index/project/IndexedProjectQuery.java b/java/com/google/gerrit/index/project/IndexedProjectQuery.java
index cdfeabd..5fc74ca 100644
--- a/java/com/google/gerrit/index/project/IndexedProjectQuery.java
+++ b/java/com/google/gerrit/index/project/IndexedProjectQuery.java
@@ -22,6 +22,10 @@
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
+/**
+ * Wrapper around {@link Predicate}s that are returned by the {@link
+ * com.google.gerrit.index.IndexRewriter}. See {@link IndexedQuery}.
+ */
public class IndexedProjectQuery extends IndexedQuery<Project.NameKey, ProjectData>
implements DataSource<ProjectData> {
diff --git a/java/com/google/gerrit/index/project/ProjectData.java b/java/com/google/gerrit/index/project/ProjectData.java
index 2bf9a4b5..c70b823 100644
--- a/java/com/google/gerrit/index/project/ProjectData.java
+++ b/java/com/google/gerrit/index/project/ProjectData.java
@@ -23,6 +23,11 @@
import java.util.List;
import java.util.Optional;
+/**
+ * Representation of a Gerrit project in the project index.
+ *
+ * <p>Includes information about all parent projects.
+ */
public class ProjectData {
private final Project project;
private final Optional<ProjectData> parent;
diff --git a/java/com/google/gerrit/index/project/ProjectIndex.java b/java/com/google/gerrit/index/project/ProjectIndex.java
index 3e99d55..b2ddaff 100644
--- a/java/com/google/gerrit/index/project/ProjectIndex.java
+++ b/java/com/google/gerrit/index/project/ProjectIndex.java
@@ -19,6 +19,10 @@
import com.google.gerrit.index.IndexDefinition;
import com.google.gerrit.index.query.Predicate;
+/**
+ * Index for Gerrit projects (repositories). This class is mainly used for typing the generic parent
+ * class that contains actual implementations.
+ */
public interface ProjectIndex extends Index<Project.NameKey, ProjectData> {
public interface Factory
diff --git a/java/com/google/gerrit/index/project/ProjectIndexCollection.java b/java/com/google/gerrit/index/project/ProjectIndexCollection.java
index 30227a3..7ff23c5 100644
--- a/java/com/google/gerrit/index/project/ProjectIndexCollection.java
+++ b/java/com/google/gerrit/index/project/ProjectIndexCollection.java
@@ -19,6 +19,7 @@
import com.google.gerrit.index.IndexCollection;
import com.google.inject.Singleton;
+/** Collection of active project indices. See {@link IndexCollection} for details on collections. */
@Singleton
public class ProjectIndexCollection
extends IndexCollection<Project.NameKey, ProjectData, ProjectIndex> {
diff --git a/java/com/google/gerrit/index/project/ProjectIndexRewriter.java b/java/com/google/gerrit/index/project/ProjectIndexRewriter.java
index 9e2bbdc..230161b 100644
--- a/java/com/google/gerrit/index/project/ProjectIndexRewriter.java
+++ b/java/com/google/gerrit/index/project/ProjectIndexRewriter.java
@@ -23,6 +23,7 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
+/** Rewriter for the project index. See {@link IndexRewriter} for details. */
@Singleton
public class ProjectIndexRewriter implements IndexRewriter<ProjectData> {
private final ProjectIndexCollection indexes;
diff --git a/java/com/google/gerrit/index/project/ProjectIndexer.java b/java/com/google/gerrit/index/project/ProjectIndexer.java
index bd5efeb2..34e8075 100644
--- a/java/com/google/gerrit/index/project/ProjectIndexer.java
+++ b/java/com/google/gerrit/index/project/ProjectIndexer.java
@@ -16,6 +16,7 @@
import com.google.gerrit.entities.Project;
+/** Interface for indexing a Gerrit project. */
public interface ProjectIndexer {
/**
diff --git a/java/com/google/gerrit/index/project/ProjectPredicate.java b/java/com/google/gerrit/index/project/ProjectPredicate.java
index 4926eef..11875ef 100644
--- a/java/com/google/gerrit/index/project/ProjectPredicate.java
+++ b/java/com/google/gerrit/index/project/ProjectPredicate.java
@@ -17,6 +17,7 @@
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.query.IndexPredicate;
+/** Predicate that is mapped to a field in the project index. */
public class ProjectPredicate extends IndexPredicate<ProjectData> {
public ProjectPredicate(FieldDef<ProjectData, ?> def, String value) {
super(def, value);
diff --git a/java/com/google/gerrit/index/query/IndexPredicate.java b/java/com/google/gerrit/index/query/IndexPredicate.java
index 7811a32..aac6682 100644
--- a/java/com/google/gerrit/index/query/IndexPredicate.java
+++ b/java/com/google/gerrit/index/query/IndexPredicate.java
@@ -17,7 +17,7 @@
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.FieldType;
-/** Index-aware predicate that includes a field type annotation. */
+/** Predicate that is mapped to a field in the index. */
public abstract class IndexPredicate<I> extends OperatorPredicate<I> {
private final FieldDef<I, ?> def;
diff --git a/java/com/google/gerrit/server/ExceptionHookImpl.java b/java/com/google/gerrit/server/ExceptionHookImpl.java
index eabb914..0cc2545 100644
--- a/java/com/google/gerrit/server/ExceptionHookImpl.java
+++ b/java/com/google/gerrit/server/ExceptionHookImpl.java
@@ -51,7 +51,7 @@
return Optional.empty();
}
- private static boolean isLockFailure(Throwable throwable) {
+ public static boolean isLockFailure(Throwable throwable) {
return isMatching(throwable, t -> t instanceof LockFailureException);
}
diff --git a/java/com/google/gerrit/server/account/GroupControl.java b/java/com/google/gerrit/server/account/GroupControl.java
index 36e68c4..64fd7c6 100644
--- a/java/com/google/gerrit/server/account/GroupControl.java
+++ b/java/com/google/gerrit/server/account/GroupControl.java
@@ -160,7 +160,7 @@
return isOwner;
}
- // Keep this logic in sync with VisibleRefFilter#isOwner(...).
+ // Keep this logic in sync with DefaultRefFilter#isGroupOwner(...).
if (group instanceof GroupDescription.Internal) {
AccountGroup.UUID ownerUUID = ((GroupDescription.Internal) group).getOwnerGroupUUID();
if (getUser().getEffectiveGroups().contains(ownerUUID)) {
diff --git a/java/com/google/gerrit/server/change/ActionJson.java b/java/com/google/gerrit/server/change/ActionJson.java
index 031c1f2..e87cf70 100644
--- a/java/com/google/gerrit/server/change/ActionJson.java
+++ b/java/com/google/gerrit/server/change/ActionJson.java
@@ -144,6 +144,8 @@
copy.unresolvedCommentCount = changeInfo.unresolvedCommentCount;
copy.workInProgress = changeInfo.workInProgress;
copy.id = changeInfo.id;
+ copy.cherryPickOfChange = changeInfo.cherryPickOfChange;
+ copy.cherryPickOfPatchSet = changeInfo.cherryPickOfPatchSet;
return copy;
}
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index d1c27d5..770f36c 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -111,6 +111,7 @@
private final String refName;
// Fields exposed as setters.
+ private PatchSet.Id cherryPickOf;
private Change.Status status;
private String topic;
private String message;
@@ -189,6 +190,7 @@
ctx.getWhen());
change.setStatus(MoreObjects.firstNonNull(status, Change.Status.NEW));
change.setTopic(topic);
+ change.setCherryPickOf(cherryPickOf);
change.setPrivate(isPrivate);
change.setWorkInProgress(workInProgress);
change.setReviewStarted(!workInProgress);
@@ -227,6 +229,11 @@
return this;
}
+ public ChangeInserter setCherryPickOf(PatchSet.Id cherryPickOf) {
+ this.cherryPickOf = cherryPickOf;
+ return this;
+ }
+
public ChangeInserter setMessage(String message) {
this.message = message;
return this;
@@ -378,6 +385,9 @@
if (revertOf != null) {
update.setRevertOf(revertOf.get());
}
+ if (cherryPickOf != null) {
+ update.setCherryPickOf(cherryPickOf.getCommaSeparatedChangeAndPatchSetId());
+ }
List<String> newGroups = groups;
if (newGroups.isEmpty()) {
@@ -556,10 +566,16 @@
reviewerInputs.stream(),
Streams.stream(
newAddReviewerInputFromCommitIdentity(
- change, patchSetInfo.getAuthor().getAccount(), NotifyHandling.NONE)),
+ change,
+ patchSetInfo.getCommitId(),
+ patchSetInfo.getAuthor().getAccount(),
+ NotifyHandling.NONE)),
Streams.stream(
newAddReviewerInputFromCommitIdentity(
- change, patchSetInfo.getCommitter().getAccount(), NotifyHandling.NONE)))
+ change,
+ patchSetInfo.getCommitId(),
+ patchSetInfo.getCommitter().getAccount(),
+ NotifyHandling.NONE)))
.collect(toImmutableList());
}
}
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 21ee28a..974cb47 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -588,6 +588,12 @@
}
out.revertOf = cd.change().getRevertOf() != null ? cd.change().getRevertOf().get() : null;
out.submissionId = cd.change().getSubmissionId();
+ out.cherryPickOfChange =
+ cd.change().getCherryPickOf() != null
+ ? cd.change().getCherryPickOf().changeId().get()
+ : null;
+ out.cherryPickOfPatchSet =
+ cd.change().getCherryPickOf() != null ? cd.change().getCherryPickOf().get() : null;
if (has(REVIEWER_UPDATES)) {
out.reviewerUpdates = reviewerUpdates(cd);
diff --git a/java/com/google/gerrit/server/change/ReviewerAdder.java b/java/com/google/gerrit/server/change/ReviewerAdder.java
index ba6ba21..4f66492 100644
--- a/java/com/google/gerrit/server/change/ReviewerAdder.java
+++ b/java/com/google/gerrit/server/change/ReviewerAdder.java
@@ -81,6 +81,7 @@
import java.util.function.Function;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
public class ReviewerAdder {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -125,12 +126,16 @@
}
public static Optional<InternalAddReviewerInput> newAddReviewerInputFromCommitIdentity(
- Change change, @Nullable Account.Id accountId, NotifyHandling notify) {
+ Change change, ObjectId commitId, @Nullable Account.Id accountId, NotifyHandling notify) {
if (accountId == null || accountId.equals(change.getOwner())) {
// If git ident couldn't be resolved to a user, or if it's not forged, do nothing.
return Optional.empty();
}
+ logger.atFine().log(
+ "Adding account %d from author/committer identity of commit %s as reviewer to change %d",
+ accountId.get(), commitId.name(), change.getChangeId());
+
InternalAddReviewerInput in = new InternalAddReviewerInput();
in.reviewer = accountId.toString();
in.state = REVIEWER;
diff --git a/java/com/google/gerrit/server/change/SetCherryPickOp.java b/java/com/google/gerrit/server/change/SetCherryPickOp.java
new file mode 100644
index 0000000..d271923
--- /dev/null
+++ b/java/com/google/gerrit/server/change/SetCherryPickOp.java
@@ -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;
+ }
+}
diff --git a/java/com/google/gerrit/server/data/ChangeAttribute.java b/java/com/google/gerrit/server/data/ChangeAttribute.java
index a6da2b9..bc51e2a 100644
--- a/java/com/google/gerrit/server/data/ChangeAttribute.java
+++ b/java/com/google/gerrit/server/data/ChangeAttribute.java
@@ -31,6 +31,8 @@
public String url;
public String commitMessage;
public List<String> hashtags;
+ public Integer cherryPickOfChange;
+ public Integer cherryPickOfPatchSet;
public Long createdOn;
public Long lastUpdated;
diff --git a/java/com/google/gerrit/server/events/EventFactory.java b/java/com/google/gerrit/server/events/EventFactory.java
index 3f22d7f..9422c18 100644
--- a/java/com/google/gerrit/server/events/EventFactory.java
+++ b/java/com/google/gerrit/server/events/EventFactory.java
@@ -144,6 +144,10 @@
a.createdOn = change.getCreatedOn().getTime() / 1000L;
a.wip = change.isWorkInProgress() ? true : null;
a.isPrivate = change.isPrivate() ? true : null;
+ a.cherryPickOfChange =
+ change.getCherryPickOf() != null ? change.getCherryPickOf().changeId().get() : null;
+ a.cherryPickOfPatchSet =
+ change.getCherryPickOf() != null ? change.getCherryPickOf().get() : null;
return a;
}
diff --git a/java/com/google/gerrit/server/git/receive/ReplaceOp.java b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
index 24154d60..62114b0 100644
--- a/java/com/google/gerrit/server/git/receive/ReplaceOp.java
+++ b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
@@ -368,10 +368,16 @@
Streams.concat(
Streams.stream(
newAddReviewerInputFromCommitIdentity(
- change, psInfo.getAuthor().getAccount(), NotifyHandling.NONE)),
+ change,
+ psInfo.getCommitId(),
+ psInfo.getAuthor().getAccount(),
+ NotifyHandling.NONE)),
Streams.stream(
newAddReviewerInputFromCommitIdentity(
- change, psInfo.getCommitter().getAccount(), NotifyHandling.NONE)));
+ change,
+ psInfo.getCommitId(),
+ psInfo.getCommitter().getAccount(),
+ NotifyHandling.NONE)));
if (magicBranch != null) {
inputs =
Streams.concat(
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidators.java b/java/com/google/gerrit/server/git/validators/CommitValidators.java
index e8f6096..90d6b66 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -44,6 +44,11 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.git.validators.ValidationMessage.Type;
+import com.google.gerrit.server.patch.DiffSummary;
+import com.google.gerrit.server.patch.DiffSummaryKey;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.patch.PatchListKey;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
@@ -76,7 +81,8 @@
import org.eclipse.jgit.util.SystemReader;
/**
- * Represents a list of CommitValidationListeners to run for a push to one branch of one project.
+ * Represents a list of {@link CommitValidationListener}s to run for a push to one branch of one
+ * project.
*/
public class CommitValidators {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -94,15 +100,16 @@
private final AllProjectsName allProjects;
private final ExternalIdsConsistencyChecker externalIdsConsistencyChecker;
private final AccountValidator accountValidator;
- private final String installCommitMsgHookCommand;
private final ProjectCache projectCache;
private final ProjectConfig.Factory projectConfigFactory;
+ private final PatchListCache patchListCache;
+ private final Config config;
@Inject
Factory(
@GerritPersonIdent PersonIdent gerritIdent,
DynamicItem<UrlFormatter> urlFormatter,
- @GerritServerConfig Config cfg,
+ @GerritServerConfig Config config,
PluginSetContext<CommitValidationListener> pluginValidators,
GitRepositoryManager repoManager,
AllUsersName allUsers,
@@ -110,19 +117,20 @@
ExternalIdsConsistencyChecker externalIdsConsistencyChecker,
AccountValidator accountValidator,
ProjectCache projectCache,
- ProjectConfig.Factory projectConfigFactory) {
+ ProjectConfig.Factory projectConfigFactory,
+ PatchListCache patchListCache) {
this.gerritIdent = gerritIdent;
this.urlFormatter = urlFormatter;
+ this.config = config;
this.pluginValidators = pluginValidators;
this.repoManager = repoManager;
this.allUsers = allUsers;
this.allProjects = allProjects;
this.externalIdsConsistencyChecker = externalIdsConsistencyChecker;
this.accountValidator = accountValidator;
- this.installCommitMsgHookCommand =
- cfg != null ? cfg.getString("gerrit", null, "installCommitMsgHookCommand") : null;
this.projectCache = projectCache;
this.projectConfigFactory = projectConfigFactory;
+ this.patchListCache = patchListCache;
}
public CommitValidators forReceiveCommits(
@@ -146,12 +154,7 @@
new CommitterUploaderValidator(user, perm, urlFormatter.get()),
new SignedOffByValidator(user, perm, projectState),
new ChangeIdValidator(
- projectState,
- user,
- urlFormatter.get(),
- installCommitMsgHookCommand,
- sshInfo,
- change),
+ projectState, user, urlFormatter.get(), config, sshInfo, change),
new ConfigValidator(projectConfigFactory, branch, user, rw, allUsers, allProjects),
new BannedCommitsValidator(rejectCommits),
new PluginCommitValidationListener(pluginValidators, skipValidation),
@@ -176,14 +179,10 @@
new ProjectStateValidationListener(projectState),
new AmendedGerritMergeCommitValidationListener(perm, gerritIdent),
new AuthorUploaderValidator(user, perm, urlFormatter.get()),
+ new FileCountValidator(patchListCache, config),
new SignedOffByValidator(user, perm, projectCache.checkedGet(branch.project())),
new ChangeIdValidator(
- projectState,
- user,
- urlFormatter.get(),
- installCommitMsgHookCommand,
- sshInfo,
- change),
+ projectState, user, urlFormatter.get(), config, sshInfo, change),
new ConfigValidator(projectConfigFactory, branch, user, rw, allUsers, allProjects),
new PluginCommitValidationListener(pluginValidators),
new ExternalIdUpdateListener(allUsers, externalIdsConsistencyChecker),
@@ -268,14 +267,14 @@
ProjectState projectState,
IdentifiedUser user,
UrlFormatter urlFormatter,
- String installCommitMsgHookCommand,
+ Config config,
SshInfo sshInfo,
Change change) {
this.projectState = projectState;
- this.urlFormatter = urlFormatter;
- this.installCommitMsgHookCommand = installCommitMsgHookCommand;
- this.sshInfo = sshInfo;
this.user = user;
+ this.urlFormatter = urlFormatter;
+ installCommitMsgHookCommand = config.getString("gerrit", null, "installCommitMsgHookCommand");
+ this.sshInfo = sshInfo;
this.change = change;
}
@@ -387,6 +386,40 @@
}
}
+ /** Limits the number of files per change. */
+ private static class FileCountValidator implements CommitValidationListener {
+
+ private final PatchListCache patchListCache;
+ private final int maxFileCount;
+
+ FileCountValidator(PatchListCache patchListCache, Config config) {
+ this.patchListCache = patchListCache;
+ maxFileCount = config.getInt("change", null, "maxFiles", 50_000);
+ }
+
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
+ throws CommitValidationException {
+ PatchListKey patchListKey =
+ PatchListKey.againstBase(
+ receiveEvent.commit.getId(), receiveEvent.commit.getParentCount());
+ DiffSummaryKey diffSummaryKey = DiffSummaryKey.fromPatchListKey(patchListKey);
+ try {
+ DiffSummary diffSummary =
+ patchListCache.getDiffSummary(diffSummaryKey, receiveEvent.project.getNameKey());
+ if (diffSummary.getPaths().size() > maxFileCount) {
+ throw new CommitValidationException(
+ String.format(
+ "Exceeding maximum number of files per change (%d > %d)",
+ diffSummary.getPaths().size(), maxFileCount));
+ }
+ } catch (PatchListNotAvailableException e) {
+ logger.atWarning().withCause(e).log("Failed to validate file count");
+ }
+ return Collections.emptyList();
+ }
+ }
+
/** If this is the special project configuration branch, validate the config. */
public static class ConfigValidator implements CommitValidationListener {
private final ProjectConfig.Factory projectConfigFactory;
diff --git a/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java b/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
index 9e6db44..d8ecbd9 100644
--- a/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
+++ b/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
@@ -27,7 +27,7 @@
import com.google.gerrit.server.account.AccountState;
/**
- * Wrapper around {@link Predicate}s that is returned by the {@link
+ * Wrapper around {@link Predicate}s that are returned by the {@link
* com.google.gerrit.index.IndexRewriter}. See {@link IndexedQuery}.
*/
public class IndexedAccountQuery extends IndexedQuery<Account.Id, AccountState>
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 07e9b9e4..d3a0065 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -269,6 +269,24 @@
public static final FieldDef<ChangeData, Integer> OWNER =
integer(ChangeQueryBuilder.FIELD_OWNER).build(changeGetter(c -> c.getOwner().get()));
+ /** References the source change number that this change was cherry-picked from. */
+ public static final FieldDef<ChangeData, Integer> CHERRY_PICK_OF_CHANGE =
+ integer(ChangeQueryBuilder.FIELD_CHERRY_PICK_OF_CHANGE)
+ .build(
+ cd ->
+ cd.change().getCherryPickOf() != null
+ ? cd.change().getCherryPickOf().changeId().get()
+ : null);
+
+ /** References the source change patch-set that this change was cherry-picked from. */
+ public static final FieldDef<ChangeData, Integer> CHERRY_PICK_OF_PATCHSET =
+ integer(ChangeQueryBuilder.FIELD_CHERRY_PICK_OF_PATCHSET)
+ .build(
+ cd ->
+ cd.change().getCherryPickOf() != null
+ ? cd.change().getCherryPickOf().get()
+ : null);
+
/** The user assigned to the change. */
public static final FieldDef<ChangeData, Integer> ASSIGNEE =
integer(ChangeQueryBuilder.FIELD_ASSIGNEE)
diff --git a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 55fe11d..6d6d4b8 100644
--- a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -91,6 +91,7 @@
// New numeric types: use dimensional points using the k-d tree geo-spatial data structure
// to offer fast single- and multi-dimensional numeric range. As the consequense, integer
// document id type is replaced with string document id type.
+ @Deprecated
static final Schema<ChangeData> V57 =
new Schema.Builder<ChangeData>()
.add(V56)
@@ -99,6 +100,14 @@
.legacyNumericFields(false)
.build();
+ // Add new field CHERRY_PICK_OF
+ static final Schema<ChangeData> V58 =
+ new Schema.Builder<ChangeData>()
+ .add(V57)
+ .add(ChangeField.CHERRY_PICK_OF_CHANGE)
+ .add(ChangeField.CHERRY_PICK_OF_PATCHSET)
+ .build();
+
/**
* Name of the change index to be used when contacting index backends or loading configurations.
*/
diff --git a/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java b/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
index 680db12..319b834 100644
--- a/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
+++ b/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
@@ -27,7 +27,7 @@
import java.util.Set;
/**
- * Wrapper around {@link Predicate}s that is returned by the {@link
+ * Wrapper around {@link Predicate}s that are returned by the {@link
* com.google.gerrit.index.IndexRewriter}. See {@link IndexedQuery}.
*/
public class IndexedGroupQuery extends IndexedQuery<AccountGroup.UUID, InternalGroup>
diff --git a/java/com/google/gerrit/server/logging/Metadata.java b/java/com/google/gerrit/server/logging/Metadata.java
index d312530..3bb4770 100644
--- a/java/com/google/gerrit/server/logging/Metadata.java
+++ b/java/com/google/gerrit/server/logging/Metadata.java
@@ -116,9 +116,6 @@
// Type of a sequence in NoteDb (ACCOUNTS, CHANGES, GROUPS).
public abstract Optional<String> noteDbSequenceType();
- // Name of a "table" in NoteDb (if set, always CHANGES).
- public abstract Optional<String> noteDbTable();
-
// The ID of a patch set.
public abstract Optional<Integer> patchSetId();
@@ -164,10 +161,10 @@
* httpStatus=Optional.empty, indexName=Optional.empty, indexVersion=Optional[0],
* methodName=Optional.empty, multiple=Optional.empty, operationName=Optional.empty,
* partial=Optional.empty, noteDbFilePath=Optional.empty, noteDbRefName=Optional.empty,
- * noteDbSequenceType=Optional.empty, noteDbTable=Optional.empty, patchSetId=Optional.empty,
- * pluginMetadata=[], pluginName=Optional.empty, projectName=Optional.empty,
- * pushType=Optional.empty, resourceCount=Optional.empty, restViewName=Optional.empty,
- * revision=Optional.empty, username=Optional.empty}
+ * noteDbSequenceType=Optional.empty, patchSetId=Optional.empty, pluginMetadata=[],
+ * pluginName=Optional.empty, projectName=Optional.empty, pushType=Optional.empty,
+ * resourceCount=Optional.empty, restViewName=Optional.empty, revision=Optional.empty,
+ * username=Optional.empty}
* </pre>
*
* <p>That's hard to read in logs. This is why this method
@@ -314,8 +311,6 @@
public abstract Builder noteDbSequenceType(@Nullable String noteDbSequenceType);
- public abstract Builder noteDbTable(@Nullable String noteDbTable);
-
public abstract Builder patchSetId(int patchSetId);
abstract ImmutableList.Builder<PluginMetadata> pluginMetadataBuilder();
diff --git a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index 9a1ba35..9b5b4d4 100644
--- a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.notedb;
-import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
@@ -23,7 +22,7 @@
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.metrics.Timer1;
+import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerId;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -141,7 +140,7 @@
if (args.failOnLoadForTest.get()) {
throw new StorageException("Reading from NoteDb is disabled");
}
- try (Timer1.Context<NoteDbTable> timer = args.metrics.readLatency.start(CHANGES);
+ try (Timer0.Context timer = args.metrics.readLatency.start();
Repository repo = args.repoManager.openRepository(getProjectName());
// Call openHandle even if reading is disabled, to trigger
// auto-rebuilding before this object may get passed to a ChangeUpdate.
diff --git a/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
index b221ef5..cebb67d 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
@@ -48,6 +48,7 @@
public static final FooterKey FOOTER_TAG = new FooterKey("Tag");
public static final FooterKey FOOTER_WORK_IN_PROGRESS = new FooterKey("Work-in-progress");
public static final FooterKey FOOTER_REVERT_OF = new FooterKey("Revert-of");
+ public static final FooterKey FOOTER_CHERRY_PICK_OF = new FooterKey("Cherry-pick-of");
static final String AUTHOR = "Author";
static final String BASE_PATCH_SET = "Base-for-patch-set";
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 3322b68..5468e23 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -18,6 +18,7 @@
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHANGE_ID;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHERRY_PICK_OF;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_COMMIT;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CURRENT;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_GROUPS;
@@ -36,7 +37,6 @@
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TOPIC;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_WORK_IN_PROGRESS;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.parseCommitMessageRange;
-import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.joining;
@@ -65,7 +65,7 @@
import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.metrics.Timer1;
+import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.server.AssigneeStatusUpdate;
import com.google.gerrit.server.ReviewerByEmailSet;
import com.google.gerrit.server.ReviewerSet;
@@ -148,6 +148,7 @@
private ReviewerByEmailSet pendingReviewersByEmail;
private Change.Id revertOf;
private int updateCount;
+ private PatchSet.Id cherryPickOf;
ChangeNotesParser(
Change.Id changeId,
@@ -185,7 +186,7 @@
walk.reset();
walk.markStart(walk.parseCommit(tip));
- try (Timer1.Context<NoteDbTable> timer = metrics.parseLatency.start(CHANGES)) {
+ try (Timer0.Context timer = metrics.parseLatency.start()) {
ChangeNotesCommit commit;
while ((commit = walk.next()) != null) {
parse(commit);
@@ -246,6 +247,7 @@
firstNonNull(workInProgress, false),
firstNonNull(hasReviewStarted, true),
revertOf,
+ cherryPickOf,
updateCount);
}
@@ -417,6 +419,10 @@
revertOf = parseRevertOf(commit);
}
+ if (cherryPickOf == null) {
+ cherryPickOf = parseCherryPickOf(commit);
+ }
+
previousWorkInProgressFooter = null;
parseWorkInProgress(commit);
}
@@ -966,6 +972,18 @@
return Change.id(revertOf);
}
+ private PatchSet.Id parseCherryPickOf(ChangeNotesCommit commit) throws ConfigInvalidException {
+ String cherryPickOf = parseOneFooter(commit, FOOTER_CHERRY_PICK_OF);
+ if (cherryPickOf == null) {
+ return null;
+ }
+ try {
+ return PatchSet.Id.parse(cherryPickOf);
+ } catch (IllegalArgumentException e) {
+ throw new ConfigInvalidException("\"" + cherryPickOf + "\" is not a valid patchset", e);
+ }
+ }
+
private void pruneReviewers() {
Iterator<Table.Cell<Account.Id, ReviewerStateInternal, Timestamp>> rit =
reviewers.cellSet().iterator();
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesState.java b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
index 896cca3..064e43b 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesState.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
@@ -123,6 +123,7 @@
boolean workInProgress,
boolean reviewStarted,
@Nullable Change.Id revertOf,
+ @Nullable PatchSet.Id cherryPickOf,
int updateCount) {
requireNonNull(
metaId,
@@ -152,6 +153,7 @@
.workInProgress(workInProgress)
.reviewStarted(reviewStarted)
.revertOf(revertOf)
+ .cherryPickOf(cherryPickOf)
.build())
.hashtags(hashtags)
.serverId(serverId)
@@ -220,6 +222,9 @@
@Nullable
abstract Change.Id revertOf();
+ @Nullable
+ abstract PatchSet.Id cherryPickOf();
+
abstract Builder toBuilder();
@AutoValue.Builder
@@ -254,6 +259,8 @@
abstract Builder revertOf(@Nullable Change.Id revertOf);
+ abstract Builder cherryPickOf(@Nullable PatchSet.Id cherryPickOf);
+
abstract ChangeColumns build();
}
}
@@ -341,6 +348,7 @@
change.setWorkInProgress(c.workInProgress());
change.setReviewStarted(c.reviewStarted());
change.setRevertOf(c.revertOf());
+ change.setCherryPickOf(c.cherryPickOf());
if (!patchSets().isEmpty()) {
change.setCurrentPatchSet(c.currentPatchSetId(), c.subject(), c.originalSubject());
@@ -514,6 +522,10 @@
if (cols.revertOf() != null) {
b.setRevertOf(cols.revertOf().get()).setHasRevertOf(true);
}
+ if (cols.cherryPickOf() != null) {
+ b.setCherryPickOf(cols.cherryPickOf().getCommaSeparatedChangeAndPatchSetId())
+ .setHasCherryPickOf(true);
+ }
return b.build();
}
@@ -637,6 +649,9 @@
if (proto.getHasRevertOf()) {
b.revertOf(Change.id(proto.getRevertOf()));
}
+ if (proto.getHasCherryPickOf()) {
+ b.cherryPickOf(PatchSet.Id.parse(proto.getCherryPickOf()));
+ }
return b.build();
}
diff --git a/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 6a900c0..2822f61 100644
--- a/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -21,6 +21,7 @@
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHANGE_ID;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHERRY_PICK_OF;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_COMMIT;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CURRENT;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_GROUPS;
@@ -137,6 +138,7 @@
private Boolean isPrivate;
private Boolean workInProgress;
private Integer revertOf;
+ private String cherryPickOf;
private ChangeDraftUpdate draftUpdate;
private RobotCommentUpdate robotCommentUpdate;
@@ -417,6 +419,10 @@
rootOnly = true;
}
+ public void setCherryPickOf(String cherryPickOf) {
+ this.cherryPickOf = cherryPickOf;
+ }
+
/** @return the tree id for the updated tree */
private ObjectId storeRevisionNotes(RevWalk rw, ObjectInserter inserter, ObjectId curr)
throws ConfigInvalidException, IOException {
@@ -665,6 +671,10 @@
addFooter(msg, FOOTER_REVERT_OF, revertOf);
}
+ if (cherryPickOf != null) {
+ addFooter(msg, FOOTER_CHERRY_PICK_OF, cherryPickOf);
+ }
+
cb.setMessage(msg.toString());
try {
ObjectId treeId = storeRevisionNotes(rw, ins, curr);
@@ -714,7 +724,8 @@
&& !currentPatchSet
&& isPrivate == null
&& workInProgress == null
- && revertOf == null;
+ && revertOf == null
+ && cherryPickOf == null;
}
ChangeDraftUpdate getDraftUpdate() {
diff --git a/java/com/google/gerrit/server/notedb/NoteDbMetrics.java b/java/com/google/gerrit/server/notedb/NoteDbMetrics.java
index 18ffd17..246e7e1 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbMetrics.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbMetrics.java
@@ -16,69 +16,61 @@
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
-import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.metrics.Timer1;
-import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.metrics.Timer0;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+/** Metrics for accessing and updating changes in NoteDb. */
@Singleton
class NoteDbMetrics {
/** End-to-end latency for writing a collection of updates. */
- final Timer1<NoteDbTable> updateLatency;
+ final Timer0 updateLatency;
/**
* The portion of {@link #updateLatency} due to preparing the sequence of updates.
*
* <p>May include some I/O (e.g. reading old refs), but excludes writes.
*/
- final Timer1<NoteDbTable> stageUpdateLatency;
+ final Timer0 stageUpdateLatency;
/** End-to-end latency for reading changes from NoteDb, including reading ref(s) and parsing. */
- final Timer1<NoteDbTable> readLatency;
+ final Timer0 readLatency;
/**
* The portion of {@link #readLatency} due to parsing commits, but excluding I/O (to a best
* effort).
*/
- final Timer1<NoteDbTable> parseLatency;
+ final Timer0 parseLatency;
@Inject
NoteDbMetrics(MetricMaker metrics) {
- Field<NoteDbTable> tableField =
- Field.ofEnum(NoteDbTable.class, "table", Metadata.Builder::noteDbTable).build();
-
updateLatency =
metrics.newTimer(
"notedb/update_latency",
- new Description("NoteDb update latency by table")
+ new Description("NoteDb update latency for changes")
.setCumulative()
- .setUnit(Units.MILLISECONDS),
- tableField);
+ .setUnit(Units.MILLISECONDS));
stageUpdateLatency =
metrics.newTimer(
"notedb/stage_update_latency",
- new Description("Latency for staging updates to NoteDb by table")
+ new Description("Latency for staging change updates to NoteDb")
.setCumulative()
- .setUnit(Units.MICROSECONDS),
- tableField);
+ .setUnit(Units.MICROSECONDS));
readLatency =
metrics.newTimer(
"notedb/read_latency",
- new Description("NoteDb read latency by table")
+ new Description("NoteDb read latency for changes")
.setCumulative()
- .setUnit(Units.MILLISECONDS),
- tableField);
+ .setUnit(Units.MILLISECONDS));
parseLatency =
metrics.newTimer(
"notedb/parse_latency",
- new Description("NoteDb parse latency by table")
+ new Description("NoteDb parse latency for changes")
.setCumulative()
- .setUnit(Units.MICROSECONDS),
- tableField);
+ .setUnit(Units.MICROSECONDS));
}
}
diff --git a/java/com/google/gerrit/server/notedb/NoteDbTable.java b/java/com/google/gerrit/server/notedb/NoteDbTable.java
deleted file mode 100644
index e299fdf..0000000
--- a/java/com/google/gerrit/server/notedb/NoteDbTable.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (C) 2016 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.notedb;
-
-public enum NoteDbTable {
- ACCOUNTS,
- GROUPS,
- CHANGES;
-
- public String key() {
- return name().toLowerCase();
- }
-
- @Override
- public String toString() {
- return key();
- }
-}
diff --git a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
index 1b92c0e..2d1a04a 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -17,7 +17,6 @@
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
@@ -27,7 +26,7 @@
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.metrics.Timer1;
+import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -273,7 +272,7 @@
* @throws IOException if a storage layer error occurs.
*/
private void stage() throws IOException {
- try (Timer1.Context<NoteDbTable> timer = metrics.stageUpdateLatency.start(CHANGES)) {
+ try (Timer0.Context timer = metrics.stageUpdateLatency.start()) {
if (isEmpty()) {
return;
}
@@ -298,7 +297,7 @@
executed = true;
return null;
}
- try (Timer1.Context<NoteDbTable> timer = metrics.updateLatency.start(CHANGES)) {
+ try (Timer0.Context timer = metrics.updateLatency.start()) {
stage();
// ChangeUpdates must execute before ChangeDraftUpdates.
//
diff --git a/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index 15fa0f4..b663b9d 100644
--- a/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -138,11 +138,10 @@
throws PatchListNotAvailableException {
Project.NameKey project = change.getProject();
ObjectId b = patchSet.commitId();
- Whitespace ws = Whitespace.IGNORE_NONE;
if (parentNum != null) {
- return get(PatchListKey.againstParentNum(parentNum, b, ws), project);
+ return get(PatchListKey.againstParentNum(parentNum, b, Whitespace.IGNORE_NONE), project);
}
- return get(PatchListKey.againstDefaultBase(b, ws), project);
+ return get(PatchListKey.againstDefaultBase(b, Whitespace.IGNORE_NONE), project);
}
@Override
diff --git a/java/com/google/gerrit/server/patch/PatchListKey.java b/java/com/google/gerrit/server/patch/PatchListKey.java
index bf38029..386c50d 100644
--- a/java/com/google/gerrit/server/patch/PatchListKey.java
+++ b/java/com/google/gerrit/server/patch/PatchListKey.java
@@ -59,6 +59,12 @@
return new PatchListKey(otherCommitId, newId, whitespace);
}
+ public static PatchListKey againstBase(ObjectId id, int parentCount) {
+ return parentCount > 1
+ ? PatchListKey.againstParentNum(1, id, Whitespace.IGNORE_NONE)
+ : PatchListKey.againstDefaultBase(id, Whitespace.IGNORE_NONE);
+ }
+
/**
* Old patch-set ID
*
diff --git a/java/com/google/gerrit/server/query/account/AccountPredicates.java b/java/com/google/gerrit/server/query/account/AccountPredicates.java
index 1eed7ea..e4da946 100644
--- a/java/com/google/gerrit/server/query/account/AccountPredicates.java
+++ b/java/com/google/gerrit/server/query/account/AccountPredicates.java
@@ -29,6 +29,7 @@
import com.google.gerrit.server.notedb.ChangeNotes;
import java.util.List;
+/** Utility class to create predicates for account index queries. */
public class AccountPredicates {
public static boolean hasActive(Predicate<AccountState> p) {
return QueryBuilder.find(p, AccountPredicate.class, AccountField.ACTIVE.getName()) != null;
@@ -130,6 +131,7 @@
return new CanSeeChangePredicate(args.permissionBackend, changeNotes);
}
+ /** Predicate that is mapped to a field in the account index. */
static class AccountPredicate extends IndexPredicate<AccountState>
implements Matchable<AccountState> {
AccountPredicate(FieldDef<AccountState, ?> def, String value) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index 78ca0fc..8879388 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -45,7 +45,6 @@
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.server.ApprovalsUtil;
@@ -301,6 +300,7 @@
private Integer unresolvedCommentCount;
private Integer totalCommentCount;
private LabelTypes labelTypes;
+ private Optional<PatchSet.Id> cherryPickOf;
private ImmutableList<byte[]> refStates;
private ImmutableList<byte[]> refStatePatterns;
@@ -396,12 +396,7 @@
return Optional.empty();
}
- ObjectId id = ps.commitId();
- Whitespace ws = Whitespace.IGNORE_NONE;
- PatchListKey pk =
- parentCount > 1
- ? PatchListKey.againstParentNum(1, id, ws)
- : PatchListKey.againstDefaultBase(id, ws);
+ PatchListKey pk = PatchListKey.againstBase(ps.commitId(), parentCount);
DiffSummaryKey key = DiffSummaryKey.fromPatchListKey(pk);
try {
diffSummary = Optional.of(patchListCache.getDiffSummary(key, c.getProject()));
diff --git a/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java b/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
index 7428e3a..a176a58 100644
--- a/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
@@ -19,6 +19,7 @@
import com.google.gerrit.index.query.Matchable;
import com.google.gerrit.index.query.Predicate;
+/** Predicate that is mapped to a field in the change index. */
public abstract class ChangeIndexPredicate extends IndexPredicate<ChangeData>
implements Matchable<ChangeData> {
/**
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 5f076b1..7747998 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -34,6 +34,7 @@
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.NotSignedInException;
import com.google.gerrit.exceptions.StorageException;
@@ -189,6 +190,9 @@
public static final String FIELD_WATCHEDBY = "watchedby";
public static final String FIELD_WIP = "wip";
public static final String FIELD_REVERTOF = "revertof";
+ public static final String FIELD_CHERRY_PICK_OF = "cherrypickof";
+ public static final String FIELD_CHERRY_PICK_OF_CHANGE = "cherrypickofchange";
+ public static final String FIELD_CHERRY_PICK_OF_PATCHSET = "cherrypickofpatchset";
public static final String ARG_ID_USER = "user";
public static final String ARG_ID_GROUP = "group";
@@ -1268,6 +1272,29 @@
"'submissionid' operator is not supported by change index version");
}
+ @Operator
+ public Predicate<ChangeData> cherryPickOf(String value) throws QueryParseException {
+ if (args.getSchema().hasField(ChangeField.CHERRY_PICK_OF_CHANGE)
+ && args.getSchema().hasField(ChangeField.CHERRY_PICK_OF_PATCHSET)) {
+ if (Ints.tryParse(value) != null) {
+ return new CherryPickOfChangePredicate(value);
+ }
+ try {
+ PatchSet.Id patchSetId = PatchSet.Id.parse(value);
+ return Predicate.and(
+ new CherryPickOfChangePredicate(patchSetId.changeId().toString()),
+ new CherryPickOfPatchSetPredicate(patchSetId.getId()));
+ } catch (IllegalArgumentException e) {
+ throw new QueryParseException(
+ "'"
+ + value
+ + "' is not a valid input. It must be in the 'ChangeNumber[,PatchsetNumber]' format.");
+ }
+ }
+ throw new QueryParseException(
+ "'cherrypickof' operator is not supported by change index version");
+ }
+
@Override
protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
if (query.startsWith("refs/")) {
diff --git a/java/com/google/gerrit/server/query/change/CherryPickOfChangePredicate.java b/java/com/google/gerrit/server/query/change/CherryPickOfChangePredicate.java
new file mode 100644
index 0000000..d452017
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/CherryPickOfChangePredicate.java
@@ -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;
+ }
+}
diff --git a/java/com/google/gerrit/server/query/change/CherryPickOfPatchSetPredicate.java b/java/com/google/gerrit/server/query/change/CherryPickOfPatchSetPredicate.java
new file mode 100644
index 0000000..888f45d
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/CherryPickOfPatchSetPredicate.java
@@ -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;
+ }
+}
diff --git a/java/com/google/gerrit/server/query/group/GroupPredicates.java b/java/com/google/gerrit/server/query/group/GroupPredicates.java
index 17a7000..5231c5a 100644
--- a/java/com/google/gerrit/server/query/group/GroupPredicates.java
+++ b/java/com/google/gerrit/server/query/group/GroupPredicates.java
@@ -23,6 +23,7 @@
import com.google.gerrit.server.index.group.GroupField;
import java.util.Locale;
+/** Utility class to create predicates for group index queries. */
public class GroupPredicates {
public static Predicate<InternalGroup> id(AccountGroup.Id groupId) {
return new GroupPredicate(GroupField.ID, groupId.toString());
@@ -63,6 +64,7 @@
return new GroupPredicate(GroupField.SUBGROUP, subgroupUuid.get());
}
+ /** Predicate that is mapped to a field in the group index. */
static class GroupPredicate extends IndexPredicate<InternalGroup> {
GroupPredicate(FieldDef<InternalGroup, ?> def, String value) {
super(def, value);
diff --git a/java/com/google/gerrit/server/query/project/ProjectPredicates.java b/java/com/google/gerrit/server/query/project/ProjectPredicates.java
index 4e56fac..8b4048f 100644
--- a/java/com/google/gerrit/server/query/project/ProjectPredicates.java
+++ b/java/com/google/gerrit/server/query/project/ProjectPredicates.java
@@ -22,6 +22,7 @@
import com.google.gerrit.index.query.Predicate;
import java.util.Locale;
+/** Utility class to create predicates for project index queries. */
public class ProjectPredicates {
public static Predicate<ProjectData> name(Project.NameKey nameKey) {
return new ProjectPredicate(ProjectField.NAME, nameKey.get());
diff --git a/java/com/google/gerrit/server/restapi/access/AccessCollection.java b/java/com/google/gerrit/server/restapi/access/AccessCollection.java
index 8ae2ce7..d8832a2 100644
--- a/java/com/google/gerrit/server/restapi/access/AccessCollection.java
+++ b/java/com/google/gerrit/server/restapi/access/AccessCollection.java
@@ -24,6 +24,7 @@
import com.google.inject.Provider;
import com.google.inject.Singleton;
+/** REST collection that serves requests to {@code /access/}. */
@Singleton
public class AccessCollection implements RestCollection<TopLevelResource, AccessResource> {
private final Provider<ListAccess> list;
diff --git a/java/com/google/gerrit/server/restapi/access/AccessResource.java b/java/com/google/gerrit/server/restapi/access/AccessResource.java
index 915165b..4847da4 100644
--- a/java/com/google/gerrit/server/restapi/access/AccessResource.java
+++ b/java/com/google/gerrit/server/restapi/access/AccessResource.java
@@ -18,6 +18,13 @@
import com.google.gerrit.extensions.restapi.RestView;
import com.google.inject.TypeLiteral;
+/**
+ * REST resource that represents members in {@link AccessCollection}.
+ *
+ * <p>{@link AccessCollection} doesn't support accessing single members, hence this class only
+ * defines the {@link TypeLiteral} for the resource kind that is needed for binding the REST
+ * collection.
+ */
public class AccessResource implements RestResource {
public static final TypeLiteral<RestView<AccessResource>> ACCESS_KIND =
new TypeLiteral<RestView<AccessResource>>() {};
diff --git a/java/com/google/gerrit/server/restapi/access/ListAccess.java b/java/com/google/gerrit/server/restapi/access/ListAccess.java
index 2520821..1e1bade 100644
--- a/java/com/google/gerrit/server/restapi/access/ListAccess.java
+++ b/java/com/google/gerrit/server/restapi/access/ListAccess.java
@@ -27,6 +27,11 @@
import java.util.TreeMap;
import org.kohsuke.args4j.Option;
+/**
+ * REST endpoint to list members of the {@link AccessCollection}.
+ *
+ * <p>This REST endpoint handles {@code GET /access/} requests.
+ */
public class ListAccess implements RestReadView<TopLevelResource> {
@Option(
diff --git a/java/com/google/gerrit/server/restapi/access/Module.java b/java/com/google/gerrit/server/restapi/access/Module.java
index 7da2e26b..3a4955d 100644
--- a/java/com/google/gerrit/server/restapi/access/Module.java
+++ b/java/com/google/gerrit/server/restapi/access/Module.java
@@ -19,6 +19,7 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
+/** Guice module that binds all REST endpoints for {@code /access/}. */
public class Module extends RestApiModule {
@Override
protected void configure() {
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
index e220f35..aa36b73 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
@@ -30,7 +30,6 @@
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.RawInput;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
@@ -120,7 +119,8 @@
@Override
public Response<?> apply(ChangeResource resource, IdString id, FileContentInput input)
- throws AuthException, ResourceConflictException, IOException, PermissionBackendException {
+ throws AuthException, BadRequestException, ResourceConflictException, IOException,
+ PermissionBackendException {
putEdit.apply(resource, id.get(), input);
return Response.none();
}
@@ -280,19 +280,24 @@
@Override
public Response<?> apply(ChangeEditResource rsrc, FileContentInput input)
- throws AuthException, ResourceConflictException, IOException, PermissionBackendException {
+ throws AuthException, BadRequestException, ResourceConflictException, IOException,
+ PermissionBackendException {
return apply(rsrc.getChangeResource(), rsrc.getPath(), input);
}
public Response<?> apply(ChangeResource rsrc, String path, FileContentInput input)
- throws ResourceConflictException, AuthException, IOException, PermissionBackendException {
+ throws AuthException, BadRequestException, ResourceConflictException, IOException,
+ PermissionBackendException {
if (Strings.isNullOrEmpty(path) || path.charAt(0) == '/') {
throw new ResourceConflictException("Invalid path: " + path);
}
- RawInput newContent = input.content;
+ if (input.content == null) {
+ throw new BadRequestException("new content required");
+ }
+
try (Repository repository = repositoryManager.openRepository(rsrc.getProject())) {
- editModifier.modifyFile(repository, rsrc.getNotes(), path, newContent);
+ editModifier.modifyFile(repository, rsrc.getNotes(), path, input.content);
} catch (InvalidChangeOperationException e) {
throw new ResourceConflictException(e.getMessage());
}
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
index 6b671c9..3496491 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
@@ -42,6 +42,7 @@
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.NotifyResolver;
import com.google.gerrit.server.change.PatchSetInserter;
+import com.google.gerrit.server.change.SetCherryPickOp;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -102,6 +103,7 @@
private final Provider<IdentifiedUser> user;
private final ChangeInserter.Factory changeInserterFactory;
private final PatchSetInserter.Factory patchSetInserterFactory;
+ private final SetCherryPickOp.Factory setCherryPickOfFactory;
private final MergeUtil.Factory mergeUtilFactory;
private final ChangeNotes.Factory changeNotesFactory;
private final ProjectCache projectCache;
@@ -118,6 +120,7 @@
Provider<IdentifiedUser> user,
ChangeInserter.Factory changeInserterFactory,
PatchSetInserter.Factory patchSetInserterFactory,
+ SetCherryPickOp.Factory setCherryPickOfFactory,
MergeUtil.Factory mergeUtilFactory,
ChangeNotes.Factory changeNotesFactory,
ProjectCache projectCache,
@@ -131,6 +134,7 @@
this.user = user;
this.changeInserterFactory = changeInserterFactory;
this.patchSetInserterFactory = patchSetInserterFactory;
+ this.setCherryPickOfFactory = setCherryPickOfFactory;
this.mergeUtilFactory = mergeUtilFactory;
this.changeNotesFactory = changeNotesFactory;
this.projectCache = projectCache;
@@ -368,7 +372,13 @@
dest.project(),
destChanges.get(0).getId().get()));
}
- changeId = insertPatchSet(bu, git, destChanges.get(0).notes(), cherryPickCommit);
+ changeId =
+ insertPatchSet(
+ bu,
+ git,
+ destChanges.get(0).notes(),
+ cherryPickCommit,
+ sourceChange.currentPatchSetId());
} else {
// Change key not found on destination branch. We can create a new
// change.
@@ -451,13 +461,22 @@
}
private Change.Id insertPatchSet(
- BatchUpdate bu, Repository git, ChangeNotes destNotes, CodeReviewCommit cherryPickCommit)
+ BatchUpdate bu,
+ Repository git,
+ ChangeNotes destNotes,
+ CodeReviewCommit cherryPickCommit,
+ PatchSet.Id sourcePatchSetId)
throws IOException {
Change destChange = destNotes.getChange();
PatchSet.Id psId = ChangeUtil.nextPatchSetId(git, destChange.currentPatchSetId());
PatchSetInserter inserter = patchSetInserterFactory.create(destNotes, psId, cherryPickCommit);
inserter.setMessage("Uploaded patch set " + inserter.getPatchSetId().get() + ".");
bu.addOp(destChange.getId(), inserter);
+ if (destChange.getCherryPickOf() == null
+ || !destChange.getCherryPickOf().equals(sourcePatchSetId)) {
+ SetCherryPickOp cherryPickOfUpdater = setCherryPickOfFactory.create(sourcePatchSetId);
+ bu.addOp(destChange.getId(), cherryPickOfUpdater);
+ }
return destChange.getId();
}
@@ -477,6 +496,7 @@
ChangeInserter ins = changeInserterFactory.create(changeId, cherryPickCommit, refName);
ins.setRevertOf(revertOf);
BranchNameKey sourceBranch = sourceChange == null ? null : sourceChange.getDest();
+ PatchSet.Id sourcePatchSetId = sourceChange == null ? null : sourceChange.currentPatchSetId();
ins.setMessage(
revertOf == null
? messageForDestinationChange(
@@ -484,6 +504,7 @@
: "Uploaded patch set 1.") // For revert commits, the message should not include
// cherry-pick information.
.setTopic(topic)
+ .setCherryPickOf(sourcePatchSetId)
.setWorkInProgress(
(sourceChange != null && sourceChange.isWorkInProgress())
|| !cherryPickCommit.getFilesWithGitConflicts().isEmpty());
diff --git a/java/com/google/gerrit/server/restapi/change/CreateChange.java b/java/com/google/gerrit/server/restapi/change/CreateChange.java
index 310f71b..c0b28d2 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateChange.java
@@ -20,6 +20,7 @@
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.entities.BranchNameKey;
@@ -86,7 +87,6 @@
import org.eclipse.jgit.errors.InvalidObjectIdException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.NoMergeBaseException;
-import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
@@ -104,6 +104,8 @@
@Singleton
public class CreateChange
implements RestCollectionModifyView<TopLevelResource, ChangeResource, ChangeInput> {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
private final BatchUpdate.Factory updateFactory;
private final String anonymousCowardName;
private final GitRepositoryManager gitManager;
@@ -294,20 +296,30 @@
BatchUpdate.Factory updateFactory)
throws RestApiException, PermissionBackendException, IOException, ConfigInvalidException,
UpdateException {
+ logger.atFine().log(
+ "Creating new change for target branch %s in project %s"
+ + " (new branch = %s, base change = %s, base commit = %s)",
+ input.branch, projectState.getName(), input.newBranch, input.baseChange, input.baseCommit);
+
try (Repository git = gitManager.openRepository(projectState.getNameKey());
ObjectInserter oi = git.newObjectInserter();
ObjectReader reader = oi.newReader();
RevWalk rw = new RevWalk(reader)) {
PatchSet basePatchSet = null;
List<String> groups = Collections.emptyList();
+
if (input.baseChange != null) {
ChangeNotes baseChange = getBaseChange(input.baseChange);
basePatchSet = psUtil.current(baseChange);
groups = basePatchSet.groups();
+ logger.atFine().log("base patch set = %s (groups = %s)", basePatchSet.id(), groups);
}
+
ObjectId parentCommit =
getParentCommit(
git, rw, input.branch, input.newBranch, basePatchSet, input.baseCommit, input.merge);
+ logger.atFine().log(
+ "parent commit = %s", parentCommit != null ? parentCommit.name() : "NULL");
RevCommit mergeTip = parentCommit == null ? null : rw.parseCommit(parentCommit);
@@ -464,6 +476,7 @@
RevCommit mergeTip,
String commitMessage)
throws IOException {
+ logger.atFine().log("Creating empty commit");
CommitBuilder commit = new CommitBuilder();
if (mergeTip == null) {
commit.setTreeId(emptyTreeId(oi));
@@ -487,6 +500,9 @@
PersonIdent authorIdent,
String commitMessage)
throws RestApiException, IOException {
+ logger.atFine().log(
+ "Creating merge commit: source = %s, strategy = %s", merge.source, merge.strategy);
+
if (Strings.isNullOrEmpty(merge.source)) {
throw new BadRequestException("merge.source must be non-empty");
}
@@ -500,6 +516,7 @@
// default merge strategy from project settings
String mergeStrategy =
firstNonNull(Strings.emptyToNull(merge.strategy), mergeUtil.mergeStrategyName());
+ logger.atFine().log("merge strategy = %s", mergeStrategy);
try {
return MergeUtil.createMergeCommit(
@@ -512,12 +529,8 @@
commitMessage,
rw);
} catch (NoMergeBaseException e) {
- if (MergeBaseFailureReason.TOO_MANY_MERGE_BASES == e.getReason()
- || MergeBaseFailureReason.CONFLICTS_DURING_MERGE_BASE_CALCULATION == e.getReason()) {
- throw new ResourceConflictException(
- String.format("Cannot create merge commit: %s", e.getMessage()), e);
- }
- throw e;
+ throw new ResourceConflictException(
+ String.format("Cannot create merge commit: %s", e.getMessage()), e);
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetDiff.java b/java/com/google/gerrit/server/restapi/change/GetDiff.java
index 82bdc34..c1d286a 100644
--- a/java/com/google/gerrit/server/restapi/change/GetDiff.java
+++ b/java/com/google/gerrit/server/restapi/change/GetDiff.java
@@ -29,6 +29,7 @@
import com.google.gerrit.extensions.common.DiffWebLinkInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.CacheControl;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -102,8 +103,8 @@
@Override
public Response<DiffInfo> apply(FileResource resource)
- throws ResourceConflictException, ResourceNotFoundException, AuthException,
- InvalidChangeOperationException, IOException, PermissionBackendException {
+ throws BadRequestException, ResourceConflictException, ResourceNotFoundException,
+ AuthException, InvalidChangeOperationException, IOException, PermissionBackendException {
DiffPreferencesInfo prefs = new DiffPreferencesInfo();
if (whitespace != null) {
prefs.ignoreWhitespace = whitespace;
@@ -130,6 +131,9 @@
RevisionResource baseResource =
revisions.parse(resource.getRevision().getChangeResource(), IdString.fromDecoded(base));
basePatchSet = baseResource.getPatchSet();
+ if (basePatchSet.id().get() == 0) {
+ throw new BadRequestException("edit not allowed as base");
+ }
psf = patchScriptFactoryFactory.create(notes, fileName, basePatchSet.id(), pId, prefs);
} else if (parentNum > 0) {
psf = patchScriptFactoryFactory.create(notes, fileName, parentNum - 1, pId, prefs);
diff --git a/java/com/google/gerrit/server/restapi/change/Module.java b/java/com/google/gerrit/server/restapi/change/Module.java
index 4409c6a..e339c67 100644
--- a/java/com/google/gerrit/server/restapi/change/Module.java
+++ b/java/com/google/gerrit/server/restapi/change/Module.java
@@ -40,6 +40,7 @@
import com.google.gerrit.server.change.RebaseChangeOp;
import com.google.gerrit.server.change.ReviewerResource;
import com.google.gerrit.server.change.SetAssigneeOp;
+import com.google.gerrit.server.change.SetCherryPickOp;
import com.google.gerrit.server.change.SetHashtagsOp;
import com.google.gerrit.server.change.SetPrivateOp;
import com.google.gerrit.server.change.WorkInProgressOp;
@@ -201,6 +202,7 @@
factory(RebaseChangeOp.Factory.class);
factory(ReviewerResource.Factory.class);
factory(SetAssigneeOp.Factory.class);
+ factory(SetCherryPickOp.Factory.class);
factory(SetHashtagsOp.Factory.class);
factory(SetPrivateOp.Factory.class);
factory(WorkInProgressOp.Factory.class);
diff --git a/java/com/google/gerrit/server/schema/Schema_180.java b/java/com/google/gerrit/server/schema/Schema_180.java
index 6912b3e..7e13d0d 100644
--- a/java/com/google/gerrit/server/schema/Schema_180.java
+++ b/java/com/google/gerrit/server/schema/Schema_180.java
@@ -14,6 +14,15 @@
package com.google.gerrit.server.schema;
+/**
+ * Schema 180 for Gerrit metadata.
+ *
+ * <p>180 is the first schema version that is supported by NoteDb. All previous schema versions were
+ * for ReviewDb. Since ReviewDb no longer exists those schema versions have been deleted.
+ *
+ * <p>Upgrading to this schema version creates the {@code refs/meta/version} ref in NoteDb that
+ * stores the number of the current schema version.
+ */
public class Schema_180 implements NoteDbSchemaVersion {
@Override
public void upgrade(Arguments args, UpdateUI ui) {
diff --git a/java/com/google/gerrit/server/schema/Schema_181.java b/java/com/google/gerrit/server/schema/Schema_181.java
index 3054ad3..d357ae7 100644
--- a/java/com/google/gerrit/server/schema/Schema_181.java
+++ b/java/com/google/gerrit/server/schema/Schema_181.java
@@ -17,10 +17,16 @@
import com.google.gerrit.gpg.PublicKeyStore;
import org.eclipse.jgit.lib.Repository;
+/**
+ * Schema 181 for Gerrit metadata.
+ *
+ * <p>Upgrading to this schema version populates the GPG subkey to master key map (see {@link
+ * PublicKeyStore}.
+ */
public class Schema_181 implements NoteDbSchemaVersion {
@Override
public void upgrade(Arguments args, UpdateUI ui) throws Exception {
- ui.message("Rebuild GPGP note map to build subkey to master key map");
+ ui.message("Rebuild GPG note map to build subkey to master key map");
try (Repository repo = args.repoManager.openRepository(args.allUsers);
PublicKeyStore store = new PublicKeyStore(repo)) {
store.rebuildSubkeyMasterKeyMap();
diff --git a/java/com/google/gerrit/server/submit/MergeSuperSet.java b/java/com/google/gerrit/server/submit/MergeSuperSet.java
index b36eb7d..f8540fb 100644
--- a/java/com/google/gerrit/server/submit/MergeSuperSet.java
+++ b/java/com/google/gerrit/server/submit/MergeSuperSet.java
@@ -18,7 +18,6 @@
import static java.util.Objects.requireNonNull;
import com.google.common.base.Strings;
-import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.registration.DynamicItem;
@@ -58,6 +57,7 @@
public class MergeSuperSet {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private final ChangeData.Factory changeDataFactory;
private final Provider<InternalChangeQuery> queryProvider;
private final Provider<MergeOpRepoManager> repoManagerProvider;
private final DynamicItem<MergeSuperSetComputation> mergeSuperSetComputation;
@@ -72,6 +72,7 @@
@Inject
MergeSuperSet(
@GerritServerConfig Config cfg,
+ ChangeData.Factory changeDataFactory,
Provider<InternalChangeQuery> queryProvider,
Provider<MergeOpRepoManager> repoManagerProvider,
DynamicItem<MergeSuperSetComputation> mergeSuperSetComputation,
@@ -79,6 +80,7 @@
ProjectCache projectCache,
ChangeNotes.Factory notesFactory) {
this.cfg = cfg;
+ this.changeDataFactory = changeDataFactory;
this.queryProvider = queryProvider;
this.repoManagerProvider = repoManagerProvider;
this.mergeSuperSetComputation = mergeSuperSetComputation;
@@ -105,14 +107,7 @@
orm = repoManagerProvider.get();
closeOrm = true;
}
- List<ChangeData> cds = queryProvider.get().byLegacyChangeId(change.getId());
- checkState(
- cds.size() == 1,
- "Expected exactly one ChangeData for change ID %s, got %s",
- change.getId(),
- cds.size());
- ChangeData cd = Iterables.getFirst(cds, null);
-
+ ChangeData cd = changeDataFactory.create(change.getProject(), change.getId());
boolean visible = false;
if (cd != null) {
ProjectState projectState = projectCache.checkedGet(cd.project());
diff --git a/java/gerrit/AbstractCommitUserIdentityPredicate.java b/java/gerrit/AbstractCommitUserIdentityPredicate.java
index bd8cf1a..51c4a3b 100644
--- a/java/gerrit/AbstractCommitUserIdentityPredicate.java
+++ b/java/gerrit/AbstractCommitUserIdentityPredicate.java
@@ -30,6 +30,20 @@
import java.io.IOException;
import org.eclipse.jgit.lib.PersonIdent;
+/**
+ * Abstract Prolog predicate for a Git person identity of a change.
+ *
+ * <p>Checks that the terms that are provided as input to this Prolog predicate match a Git person
+ * identity of the change (either author or committer).
+ *
+ * <p>The terms that are provided as input to this Prolog predicate are:
+ *
+ * <ul>
+ * <li>a user ID term that matches the account ID of the Git person identity
+ * <li>a string atom that matches the full name of the Git person identity
+ * <li>a string atom that matches the email of the Git person identity
+ * </ul>
+ */
abstract class AbstractCommitUserIdentityPredicate extends Predicate.P3 {
private static final SymbolTerm user = SymbolTerm.intern("user", 1);
private static final SymbolTerm anonymous = SymbolTerm.intern("anonymous");
diff --git a/java/gerrit/PRED_change_branch_1.java b/java/gerrit/PRED_change_branch_1.java
index 4501169..62744f7 100644
--- a/java/gerrit/PRED_change_branch_1.java
+++ b/java/gerrit/PRED_change_branch_1.java
@@ -23,6 +23,16 @@
import com.googlecode.prolog_cafe.lang.SymbolTerm;
import com.googlecode.prolog_cafe.lang.Term;
+/**
+ * Prolog predicate for the destination branch of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is a string atom that
+ * matches the destination branch of the change.
+ *
+ * <pre>
+ * 'change_branch'(-Branch)
+ * </pre>
+ */
public class PRED_change_branch_1 extends Predicate.P1 {
public PRED_change_branch_1(Term a1, Operation n) {
arg1 = a1;
diff --git a/java/gerrit/PRED_change_owner_1.java b/java/gerrit/PRED_change_owner_1.java
index d42c0e1..f6fbb80 100644
--- a/java/gerrit/PRED_change_owner_1.java
+++ b/java/gerrit/PRED_change_owner_1.java
@@ -25,6 +25,16 @@
import com.googlecode.prolog_cafe.lang.SymbolTerm;
import com.googlecode.prolog_cafe.lang.Term;
+/**
+ * Prolog predicate for the owner of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is a user ID term that
+ * matches the account ID of the change owner.
+ *
+ * <pre>
+ * 'change_owner'(user(-ID))
+ * </pre>
+ */
public class PRED_change_owner_1 extends Predicate.P1 {
private static final SymbolTerm user = SymbolTerm.intern("user", 1);
diff --git a/java/gerrit/PRED_change_project_1.java b/java/gerrit/PRED_change_project_1.java
index a973e1c..b2ef109 100644
--- a/java/gerrit/PRED_change_project_1.java
+++ b/java/gerrit/PRED_change_project_1.java
@@ -23,6 +23,16 @@
import com.googlecode.prolog_cafe.lang.SymbolTerm;
import com.googlecode.prolog_cafe.lang.Term;
+/**
+ * Prolog predicate for the project of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is a string atom that
+ * matches the project of the change.
+ *
+ * <pre>
+ * 'change_project'(-Project)
+ * </pre>
+ */
public class PRED_change_project_1 extends Predicate.P1 {
public PRED_change_project_1(Term a1, Operation n) {
arg1 = a1;
diff --git a/java/gerrit/PRED_change_topic_1.java b/java/gerrit/PRED_change_topic_1.java
index 11d737a..f0175ef 100644
--- a/java/gerrit/PRED_change_topic_1.java
+++ b/java/gerrit/PRED_change_topic_1.java
@@ -23,6 +23,16 @@
import com.googlecode.prolog_cafe.lang.SymbolTerm;
import com.googlecode.prolog_cafe.lang.Term;
+/**
+ * Prolog predicate for the topic of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is a string atom that
+ * matches the topic of the change.
+ *
+ * <pre>
+ * 'change_topic'(-Topic)
+ * </pre>
+ */
public class PRED_change_topic_1 extends Predicate.P1 {
public PRED_change_topic_1(Term a1, Operation n) {
arg1 = a1;
diff --git a/java/gerrit/PRED_commit_author_3.java b/java/gerrit/PRED_commit_author_3.java
index 998b30e..3381344 100644
--- a/java/gerrit/PRED_commit_author_3.java
+++ b/java/gerrit/PRED_commit_author_3.java
@@ -21,6 +21,27 @@
import com.googlecode.prolog_cafe.lang.Term;
import org.eclipse.jgit.revwalk.RevCommit;
+/**
+ * Prolog predicate for the Git author of the current patch set of a change.
+ *
+ * <p>Checks that the terms that are provided as input to this Prolog predicate match the Git author
+ * of the current patch set of the change.
+ *
+ * <p>The terms that are provided as input to this Prolog predicate are:
+ *
+ * <ul>
+ * <li>a user ID term that matches the account ID of the Git author of the current patch set of
+ * the change
+ * <li>a string atom that matches the full name of the Git author of the current patch set of the
+ * change
+ * <li>a string atom that matches the email of the Git author of the current patch set of the
+ * change
+ * </ul>
+ *
+ * <pre>
+ * 'commit_author'(user(-ID), -FullName, -Email)
+ * </pre>
+ */
public class PRED_commit_author_3 extends AbstractCommitUserIdentityPredicate {
public PRED_commit_author_3(Term a1, Term a2, Term a3, Operation n) {
super(a1, a2, a3, n);
diff --git a/java/gerrit/PRED_commit_committer_3.java b/java/gerrit/PRED_commit_committer_3.java
index 293d8ce..1757336 100644
--- a/java/gerrit/PRED_commit_committer_3.java
+++ b/java/gerrit/PRED_commit_committer_3.java
@@ -21,6 +21,27 @@
import com.googlecode.prolog_cafe.lang.Term;
import org.eclipse.jgit.revwalk.RevCommit;
+/**
+ * Prolog predicate for the Git committer of the current patch set of a change.
+ *
+ * <p>Checks that the terms that are provided as input to this Prolog predicate match the Git
+ * committer of the current patch set of the change.
+ *
+ * <p>The terms that are provided as input to this Prolog predicate are:
+ *
+ * <ul>
+ * <li>a user ID term that matches the account ID of the Git committer of the current patch set of
+ * the change
+ * <li>a string atom that matches the full name of the Git committer of the current patch set of
+ * the change
+ * <li>a string atom that matches the email of the Git committer of the current patch set of the
+ * change
+ * </ul>
+ *
+ * <pre>
+ * 'commit_committer'(user(-ID), -FullName, -Email)
+ * </pre>
+ */
public class PRED_commit_committer_3 extends AbstractCommitUserIdentityPredicate {
public PRED_commit_committer_3(Term a1, Term a2, Term a3, Operation n) {
super(a1, a2, a3, n);
diff --git a/java/gerrit/PRED_commit_message_1.java b/java/gerrit/PRED_commit_message_1.java
index eb996d6..3485af6 100644
--- a/java/gerrit/PRED_commit_message_1.java
+++ b/java/gerrit/PRED_commit_message_1.java
@@ -24,7 +24,10 @@
import org.eclipse.jgit.revwalk.RevCommit;
/**
- * Returns the commit message as a symbol
+ * Prolog predicate for the commit message of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is a string atom that
+ * matches the commit message of the change.
*
* <pre>
* 'commit_message'(-Msg)
diff --git a/java/gerrit/PRED_project_default_submit_type_1.java b/java/gerrit/PRED_project_default_submit_type_1.java
index d70a9e4..77a0261 100644
--- a/java/gerrit/PRED_project_default_submit_type_1.java
+++ b/java/gerrit/PRED_project_default_submit_type_1.java
@@ -24,6 +24,16 @@
import com.googlecode.prolog_cafe.lang.SymbolTerm;
import com.googlecode.prolog_cafe.lang.Term;
+/**
+ * Prolog predicate for the default submit type of the project of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is a string atom that
+ * matches the default submit type of the change's project.
+ *
+ * <pre>
+ * 'project_default_submit_type'(-SubmitType)
+ * </pre>
+ */
public class PRED_project_default_submit_type_1 extends Predicate.P1 {
private static final SymbolTerm[] term;
diff --git a/java/gerrit/PRED_pure_revert_1.java b/java/gerrit/PRED_pure_revert_1.java
index 6300a668..19e7b68 100644
--- a/java/gerrit/PRED_pure_revert_1.java
+++ b/java/gerrit/PRED_pure_revert_1.java
@@ -22,7 +22,17 @@
import com.googlecode.prolog_cafe.lang.Prolog;
import com.googlecode.prolog_cafe.lang.Term;
-/** Checks if change is a pure revert of the change it references in 'revertOf'. */
+/**
+ * Prolog Predicate that checks if the change is a pure revert of the change it references in
+ * 'revertOf'.
+ *
+ * <p>The input is an integer atom where '1' represents a pure revert and '0' represents a non-pure
+ * revert.
+ *
+ * <pre>
+ * 'pure_revert'(-PureRevert)
+ * </pre>
+ */
public class PRED_pure_revert_1 extends Predicate.P1 {
public PRED_pure_revert_1(Term a1, Operation n) {
arg1 = a1;
diff --git a/java/gerrit/PRED_unresolved_comments_count_1.java b/java/gerrit/PRED_unresolved_comments_count_1.java
index d4abcc54..9a1fcca 100644
--- a/java/gerrit/PRED_unresolved_comments_count_1.java
+++ b/java/gerrit/PRED_unresolved_comments_count_1.java
@@ -22,6 +22,16 @@
import com.googlecode.prolog_cafe.lang.Prolog;
import com.googlecode.prolog_cafe.lang.Term;
+/**
+ * Prolog predicate for the number of unresolved comments of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is an integer atom
+ * that matches the number of unresolved comments of the change.
+ *
+ * <pre>
+ * 'unresolved_comments_count'(-NumberOfUnresolvedComments)
+ * </pre>
+ */
public class PRED_unresolved_comments_count_1 extends Predicate.P1 {
public PRED_unresolved_comments_count_1(Term a1, Operation n) {
arg1 = a1;
diff --git a/java/gerrit/PRED_uploader_1.java b/java/gerrit/PRED_uploader_1.java
index 681d86c..89e367e 100644
--- a/java/gerrit/PRED_uploader_1.java
+++ b/java/gerrit/PRED_uploader_1.java
@@ -27,6 +27,16 @@
import com.googlecode.prolog_cafe.lang.SymbolTerm;
import com.googlecode.prolog_cafe.lang.Term;
+/**
+ * Prolog predicate for the uploader of the current patch set of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is a user ID term that
+ * matches the account ID of the uploader of the current patch set.
+ *
+ * <pre>
+ * 'uploader'(user(-ID))
+ * </pre>
+ */
public class PRED_uploader_1 extends Predicate.P1 {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
index d4a4c45..717d3cc 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
@@ -21,6 +21,7 @@
import static com.google.gerrit.extensions.common.testing.DiffInfoSubject.assertThat;
import static com.google.gerrit.extensions.common.testing.FileInfoSubject.assertThat;
import static com.google.gerrit.git.ObjectIds.abbreviateName;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toMap;
@@ -41,6 +42,7 @@
import com.google.gerrit.extensions.common.ChangeType;
import com.google.gerrit.extensions.common.DiffInfo;
import com.google.gerrit.extensions.common.FileInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.testing.ConfigSuite;
@@ -2718,6 +2720,23 @@
assertThat(diffInfo).content().element(0).numberOfSkippedLines().isGreaterThan(0);
}
+ @Test
+ public void editNotAllowedAsBase() throws Exception {
+ gApi.changes().id(changeId).edit().create();
+
+ BadRequestException e =
+ assertThrows(
+ BadRequestException.class,
+ () -> getDiffRequest(changeId, CURRENT, FILE_NAME).withBase("edit").get());
+ assertThat(e).hasMessageThat().isEqualTo("edit not allowed as base");
+
+ e =
+ assertThrows(
+ BadRequestException.class,
+ () -> getDiffRequest(changeId, CURRENT, FILE_NAME).withBase("0").get());
+ assertThat(e).hasMessageThat().isEqualTo("edit not allowed as base");
+ }
+
private static CommentInput createCommentInput(
int startLine, int startCharacter, int endLine, int endCharacter, String message) {
CommentInput comment = new CommentInput();
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 61d0fd5..c4dec31 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -337,6 +337,9 @@
assertThat(cherry.get().subject).contains(in.message);
assertThat(cherry.get().topic).isEqualTo("someTopic-foo");
+ assertThat(cherry.get().cherryPickOfChange).isEqualTo(orig.get()._number);
+ assertThat(cherry.get().cherryPickOfPatchSet).isEqualTo(1);
+
cherry.current().review(ReviewInput.approve());
cherry.current().submit();
}
@@ -416,23 +419,23 @@
ChangeApi orig = gApi.changes().id(project.get() + "~master~" + r.getChangeId());
ChangeApi cherry = orig.revision(r.getCommit().name()).cherryPick(in);
+ assertThat(cherry.get().cherryPickOfChange).isEqualTo(orig.get()._number);
+ assertThat(cherry.get().cherryPickOfPatchSet).isEqualTo(1);
assertThat(cherry.get().workInProgress).isTrue();
}
@Test
public void cherryPickToSameBranch() throws Exception {
PushOneCommit.Result r = createChange();
+ ChangeApi change = gApi.changes().id(project.get() + "~master~" + r.getChangeId());
CherryPickInput in = new CherryPickInput();
in.destination = "master";
in.message = "it generates a new patch set\n\nChange-Id: " + r.getChangeId();
- ChangeInfo cherryInfo =
- gApi.changes()
- .id(project.get() + "~master~" + r.getChangeId())
- .revision(r.getCommit().name())
- .cherryPick(in)
- .get();
+ ChangeInfo cherryInfo = change.revision(r.getCommit().name()).cherryPick(in).get();
assertThat(cherryInfo.messages).hasSize(2);
Iterator<ChangeMessageInfo> cherryIt = cherryInfo.messages.iterator();
+ assertThat(cherryInfo.cherryPickOfChange).isEqualTo(change.get()._number);
+ assertThat(cherryInfo.cherryPickOfPatchSet).isEqualTo(1);
assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 1.");
assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 2.");
}
@@ -625,6 +628,42 @@
}
@Test
+ public void cherryPickToExistingChangeUpdatesCherryPickOf() throws Exception {
+ PushOneCommit.Result r1 =
+ pushFactory
+ .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "a")
+ .to("refs/for/master");
+ String t1 = project.get() + "~master~" + r1.getChangeId();
+ ChangeApi orig = gApi.changes().id(project.get() + "~master~" + r1.getChangeId());
+
+ BranchInput bin = new BranchInput();
+ bin.revision = r1.getCommit().getParent(0).name();
+ gApi.projects().name(project.get()).branch("foo").create(bin);
+
+ PushOneCommit.Result r2 =
+ pushFactory
+ .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "b", r1.getChangeId())
+ .to("refs/for/foo");
+ String t2 = project.get() + "~foo~" + r2.getChangeId();
+
+ CherryPickInput in = new CherryPickInput();
+ in.destination = "foo";
+ in.message = r1.getCommit().getFullMessage();
+ ChangeApi cherry = gApi.changes().id(t1).current().cherryPick(in);
+ assertThat(get(t2, ALL_REVISIONS).revisions).hasSize(2);
+ assertThat(cherry.get().cherryPickOfChange).isEqualTo(orig.get()._number);
+ assertThat(cherry.get().cherryPickOfPatchSet).isEqualTo(1);
+
+ PushOneCommit.Result r3 = amendChange(r1.getChangeId(), SUBJECT, "b.txt", "b");
+ in = new CherryPickInput();
+ in.destination = "foo";
+ in.message = r3.getCommit().getFullMessage();
+ cherry = gApi.changes().id(t1).current().cherryPick(in);
+ assertThat(cherry.get().cherryPickOfChange).isEqualTo(orig.get()._number);
+ assertThat(cherry.get().cherryPickOfPatchSet).isEqualTo(2);
+ }
+
+ @Test
public void cherryPickToExistingChange() throws Exception {
PushOneCommit.Result r1 =
pushFactory
diff --git a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index bf93d58..b0f183e 100644
--- a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -520,6 +520,14 @@
}
@Test
+ public void changeEditNoContentProvidedRest() throws Exception {
+ createEmptyEditFor(changeId);
+ adminRestSession
+ .put(urlEditFile(changeId, FILE_NAME), new FileContentInput())
+ .assertBadRequest();
+ }
+
+ @Test
public void emptyPutRequest() throws Exception {
createEmptyEditFor(changeId);
adminRestSession.put(urlEditFile(changeId, FILE_NAME)).assertNoContent();
diff --git a/javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java
index 72e4a7a..bc669cc 100644
--- a/javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java
@@ -335,6 +335,7 @@
.put("workInProgress", boolean.class)
.put("reviewStarted", boolean.class)
.put("revertOf", Change.Id.class)
+ .put("cherryPickOf", PatchSet.Id.class)
.build());
}
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
index 6ece894..16981dc 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
@@ -776,6 +776,7 @@
.put("workInProgress", boolean.class)
.put("reviewStarted", boolean.class)
.put("revertOf", Change.Id.class)
+ .put("cherryPickOf", PatchSet.Id.class)
.put("toBuilder", ChangeNotesState.ChangeColumns.Builder.class)
.build());
}
diff --git a/javatests/com/google/gerrit/server/update/BatchUpdateTest.java b/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
index c7ca887..083493d 100644
--- a/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
+++ b/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.SubmitRecord;
@@ -35,10 +36,13 @@
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.Sequences;
+import com.google.gerrit.server.patch.DiffSummary;
+import com.google.gerrit.server.patch.DiffSummaryKey;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.testing.InMemoryTestEnvironment;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.name.Named;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
@@ -57,8 +61,9 @@
new InMemoryTestEnvironment(
() -> {
Config cfg = new Config();
- cfg.setInt("change", null, "maxUpdates", MAX_UPDATES);
+ cfg.setInt("change", null, "maxFiles", 2);
cfg.setInt("change", null, "maxPatchSets", MAX_PATCH_SETS);
+ cfg.setInt("change", null, "maxUpdates", MAX_UPDATES);
return cfg;
});
@@ -70,6 +75,9 @@
@Inject private Provider<CurrentUser> user;
@Inject private Sequences sequences;
+ @Inject
+ private @Named("diff_summary") Cache<DiffSummaryKey, DiffSummary> diffSummaryCache;
+
private Project.NameKey project;
private TestRepository<Repository> repo;
@@ -243,6 +251,59 @@
assertThat(getMetaId(changeId)).isEqualTo(oldMetaId);
}
+ @Test
+ public void limitFileCount_exceed() throws Exception {
+ Change.Id changeId = createChangeWithUpdates(1);
+ ChangeNotes notes = changeNotesFactory.create(project, changeId);
+
+ try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+ ObjectId commitId =
+ repo.amend(notes.getCurrentPatchSet().commitId())
+ .add("bar.txt", "bar")
+ .add("baz.txt", "baz")
+ .add("boom.txt", "boom")
+ .message("blah")
+ .create();
+ bu.addOp(
+ changeId,
+ patchSetInserterFactory
+ .create(notes, PatchSet.id(changeId, 2), commitId)
+ .setMessage("blah"));
+ ResourceConflictException thrown = assertThrows(ResourceConflictException.class, bu::execute);
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Exceeding maximum number of files per change (3 > 2)");
+ }
+ }
+
+ @Test
+ public void limitFileCount_cacheKeyMatches() throws Exception {
+ Change.Id changeId = createChangeWithUpdates(1);
+ ChangeNotes notes = changeNotesFactory.create(project, changeId);
+
+ int cacheSizeBefore = diffSummaryCache.asMap().size();
+
+ // We don't want to depend on the test helper used above so we perform an explicit commit here.
+ try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+ ObjectId commitId =
+ repo.amend(notes.getCurrentPatchSet().commitId())
+ .add("bar.txt", "bar")
+ .add("baz.txt", "baz")
+ .message("blah")
+ .create();
+ bu.addOp(
+ changeId,
+ patchSetInserterFactory
+ .create(notes, PatchSet.id(changeId, 3), commitId)
+ .setMessage("blah"));
+ bu.execute();
+ }
+
+ // Assert that we only performed the diff computation once. This would e.g. catch
+ // bugs/deviations in the computation of the cache key.
+ assertThat(diffSummaryCache.asMap()).hasSize(cacheSizeBefore + 1);
+ }
+
private Change.Id createChangeWithUpdates(int totalUpdates) throws Exception {
checkArgument(totalUpdates > 0);
checkArgument(totalUpdates <= MAX_UPDATES);
diff --git a/plugins/replication b/plugins/replication
index b3ed3c8..90ca234 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit b3ed3c8f9a8bab51beed794de5be8fd6da44231a
+Subproject commit 90ca234c456767b79608ad2254f3e80f02f308ca
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index 539b1b6..9073342 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -270,6 +270,19 @@
</template>
</span>
</section>
+ <template is="dom-if" if="[[_showCherryPickOf(change.*)]]">
+ <section>
+ <span class="title">Cherry pick of</span>
+ <span class="value">
+ <a href$="[[_computeCherryPickOfURL(change.cherry_pick_of_change, change.cherry_pick_of_patch_set, change.project)]]">
+ <gr-limited-text
+ text="[[change.cherry_pick_of_change]],[[change.cherry_pick_of_patch_set]]"
+ limit="40">
+ </gr-limited-text>
+ </a>
+ </span>
+ </section>
+ </template>
<section class="strategy" hidden$="[[_computeHideStrategy(change)]]" hidden>
<span class="title">Strategy</span>
<span class="value">[[_computeStrategy(change)]]</span>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index 94519b0..2eab284 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -227,6 +227,13 @@
return hasTopic && !settingTopic;
}
+ _showCherryPickOf(changeRecord) {
+ const hasCherryPickOf = !!changeRecord &&
+ !!changeRecord.base && !!changeRecord.base.cherry_pick_of_change &&
+ !!changeRecord.base.cherry_pick_of_patch_set;
+ return hasCherryPickOf;
+ }
+
_handleHashtagChanged(e) {
const lastHashtag = this.change.hashtag;
if (!this._newHashtag.length) { return; }
@@ -353,6 +360,10 @@
this.change.status.toLowerCase());
}
+ _computeCherryPickOfURL(change, patchset, project) {
+ return Gerrit.Nav.getUrlForChangeById(change, project, patchset);
+ }
+
_computeTopicURL(topic) {
return Gerrit.Nav.getUrlForTopic(topic);
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index 148d917..09cb4cc 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -464,6 +464,22 @@
assert.isTrue(element._showTopicChip({base: {topic: 'foo'}}, false));
});
+ test('_showCherryPickOf', () => {
+ assert.isFalse(element._showCherryPickOf(null));
+ assert.isFalse(element._showCherryPickOf({
+ base: {
+ cherry_pick_of_change: null,
+ cherry_pick_of_patch_set: null,
+ },
+ }));
+ assert.isTrue(element._showCherryPickOf({
+ base: {
+ cherry_pick_of_change: 123,
+ cherry_pick_of_patch_set: 1,
+ },
+ }));
+ });
+
suite('Topic removal', () => {
let change;
setup(() => {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.html
index 05a2bb2..9e7857c4 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.html
@@ -45,14 +45,7 @@
font-family: var(--monospace-font-family);
font-size: var(--font-size-mono);
line-height: var(--line-height-mono);
- padding: 0;
width: 73ch; /* Add a char to account for the border. */
-
- --iron-autogrow-textarea {
- border: 1px solid var(--border-color);
- box-sizing: border-box;
- font-family: var(--monospace-font-family);
- }
}
</style>
<gr-dialog
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html
index 8ddcd83..cab9fd6 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html
@@ -47,18 +47,11 @@
display: block;
width: 100%;
}
- .main .message {
- width: 100%;
- }
iron-autogrow-textarea {
font-family: var(--monospace-font-family);
font-size: var(--font-size-mono);
line-height: var(--line-height-mono);
- padding: 0;
- --iron-autogrow-textarea: {
- font-family: var(--monospace-font-family);
- width: 72ch;
- };
+ width: 73ch; /* Add a char to account for the border. */
}
</style>
<gr-dialog
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.html
index 24c1132..f65ec03 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.html
@@ -37,9 +37,6 @@
label {
cursor: pointer;
}
- iron-autogrow-textarea {
- padding: 0;
- }
.main {
display: flex;
flex-direction: column;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
index 2e1e6ae..fc3a8c1 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
@@ -41,14 +41,7 @@
font-family: var(--monospace-font-family);
font-size: var(--font-size-mono);
line-height: var(--line-height-mono);
- padding: 0;
width: 73ch; /* Add a char to account for the border. */
-
- --iron-autogrow-textarea {
- border: 1px solid var(--border-color);
- box-sizing: border-box;
- font-family: var(--monospace-font-family);
- }
}
</style>
<gr-dialog
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html
index 0107c33..f2cfef8 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html
@@ -40,14 +40,9 @@
}
iron-autogrow-textarea {
font-family: var(--monospace-font-family);
- padding: 0;
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
width: 73ch; /* Add a char to account for the border. */
-
- --iron-autogrow-textarea {
- border: 1px solid var(--border-color);
- box-sizing: border-box;
- font-family: var(--monospace-font-family);
- }
}
</style>
<gr-dialog
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
index 5c98281..889f1f3 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
@@ -436,8 +436,13 @@
// Finalize the interval. Either from a registered start mark or
// the navigation start time (if baseTime is 0).
- const startMark = baseTime === 0 ? undefined : `${name}-start`;
- window.performance.measure(name, startMark);
+ if (baseTime !== 0) {
+ window.performance.measure(name, `${name}-start`);
+ } else {
+ // Microsft Edge does not handle the 2nd param correctly
+ // (if undefined).
+ window.performance.measure(name);
+ }
},
/**
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
index 48e8662..ee44e06 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
@@ -29,6 +29,7 @@
'cc:',
'cc:self',
'change:',
+ 'cherrypickof:',
'comment:',
'commentby:',
'commit:',
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
index a96cf44..afd0e59 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
@@ -43,10 +43,6 @@
display: block;
font-family: var(--font-family);
padding: var(--spacing-m);
- --iron-autogrow-textarea: {
- box-sizing: border-box;
- padding: 2px;
- };
}
:host([disabled]) {
pointer-events: none;
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.html b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.html
index 62ab307..e986ad0 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.html
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.html
@@ -45,14 +45,7 @@
font-family: var(--monospace-font-family);
font-size: var(--font-size-mono);
line-height: var(--line-height-mono);
- padding: 0;
width: 73ch; /* Add a char to account for the border. */
-
- --iron-autogrow-textarea {
- border: 1px solid var(--border-color);
- box-sizing: border-box;
- font-family: var(--monospace-font-family);
- }
}
</style>
<gr-dialog
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
index 45ddfc8..950be2a 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
@@ -41,9 +41,11 @@
background-color: var(--view-background-color);
width: 100%;
+ /* You have to also repeat everything from shared-styles here, because
+ you can only *replace* --iron-autogrow-textarea vars as a whole. */
--iron-autogrow-textarea: {
- padding: var(--spacing-m);
box-sizing: border-box;
+ padding: var(--spacing-m);
overflow-y: hidden;
white-space: pre;
};
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
index 6803eb9..42a4f3b 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
@@ -59,11 +59,7 @@
visibility: visible;
white-space: normal;
}
- /*This is needed to not add a scroll bar on the side of gr-textarea
- since there is 2px of padding in iron-autogrow-textarea for the
- native textarea*/
iron-autogrow-textarea {
- padding: 2px;
position: relative;
/** This is needed for firefox */
diff --git a/polygerrit-ui/app/styles/gr-form-styles.html b/polygerrit-ui/app/styles/gr-form-styles.html
index 7c9ae0d..5133051 100644
--- a/polygerrit-ui/app/styles/gr-form-styles.html
+++ b/polygerrit-ui/app/styles/gr-form-styles.html
@@ -93,15 +93,8 @@
width: 100%;
}
.gr-form-styles iron-autogrow-textarea {
- border: none;
height: auto;
min-height: 4em;
- --iron-autogrow-textarea: {
- border: 1px solid var(--border-color);
- border-radius: var(--border-radius);
- box-sizing: border-box;
- padding: var(--spacing-s);
- }
}
.gr-form-styles gr-autocomplete {
width: 14em;
diff --git a/polygerrit-ui/app/styles/shared-styles.html b/polygerrit-ui/app/styles/shared-styles.html
index 51b92e1..ce6bead 100644
--- a/polygerrit-ui/app/styles/shared-styles.html
+++ b/polygerrit-ui/app/styles/shared-styles.html
@@ -53,13 +53,15 @@
color: var(--primary-text-color);
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
+ padding: 0;
box-sizing: border-box;
/* iron-autogrow-textarea has a "-webkit-appearance: textarea" :host
css rule, which prevents overriding the border color. Clear that. */
-webkit-appearance: none;
--iron-autogrow-textarea: {
- padding: 4px;
+ box-sizing: border-box;
+ padding: var(--spacing-s);
};
}
a {
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.html b/polygerrit-ui/app/styles/themes/dark-theme.html
index cfc1f62..80243dc 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.html
+++ b/polygerrit-ui/app/styles/themes/dark-theme.html
@@ -98,8 +98,8 @@
--light-rebased-add-highlight-color: #487165;
--light-remove-add-highlight-color: #2f3f2f;
--light-remove-highlight-color: #320404;
- --coverage-covered: #e0f2f1;
- --coverage-not-covered: #ffd1a4;
+ --coverage-covered: #112826;
+ --coverage-not-covered: #6b3600;
/* syntax colors */
--syntax-attr-color: #80cbbf;
diff --git a/proto/cache.proto b/proto/cache.proto
index 10e0216..8f73388 100644
--- a/proto/cache.proto
+++ b/proto/cache.proto
@@ -84,7 +84,7 @@
int32 change_id = 2;
- // Next ID: 24
+ // Next ID: 26
message ChangeColumnsProto {
string change_key = 1;
@@ -124,6 +124,9 @@
int32 revert_of = 22;
bool has_revert_of = 23;
+
+ string cherry_pick_of = 24;
+ bool has_cherry_pick_of = 25;
}
// Effectively required, even though the corresponding ChangeNotesState field
// is optional, since the field is only absent when NoteDb is disabled, in
diff --git a/proto/entities.proto b/proto/entities.proto
index 374b47c..84c7fbd 100644
--- a/proto/entities.proto
+++ b/proto/entities.proto
@@ -31,7 +31,7 @@
}
// Serialized form of com.google.gerrit.entities.Change.
-// Next ID: 24
+// Next ID: 25
message Change {
required Change_Id change_id = 1;
optional Change_Key change_key = 2;
@@ -51,6 +51,7 @@
optional bool work_in_progress = 21;
optional bool review_started = 22;
optional Change_Id revert_of = 23;
+ optional PatchSet_Id cherry_pick_of = 24;
// Deleted fields, should not be reused:
reserved 6; // sortkey