Merge "Show Repo and Branch in one line when small enough"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index fc4b00a..82cb3f9 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1171,15 +1171,6 @@
+
Default is `ALL`.
-[[change.api.excludeMergeableInChangeInfo]]change.api.excludeMergeableInChangeInfo::
-+
-If true, the mergeability bit in
-link:rest-api-changes.html#change-info[ChangeInfo] will never be set. It can
-be requested separately through the
-link:rest-api-changes.html#get-mergeable[get-mergeable] endpoint.
-+
-Default is false.
-
[[change.cacheAutomerge]]change.cacheAutomerge::
+
When reviewing diff commits, the left-hand side shows the output of the
@@ -1195,6 +1186,15 @@
+
Default is true.
+[[change.commentSizeLimit]]change.commentSizeLimit::
++
+Maximum allowed size in characters of a regular (non-robot) comment. Comments
+which exceed this size will be rejected. Size computation is approximate and may
+be off by roughly 1%. Common unit suffixes of 'k', 'm', or 'g' are supported.
+The value must be positive.
++
+The default limit is 16kB.
+
[[change.disablePrivateChanges]]change.disablePrivateChanges::
+
If set to true, users are not allowed to create private changes.
@@ -1212,6 +1212,13 @@
+
By default 500.
+[[change.maxComments]]change.maxComments::
++
+Maximum number of comments (regular plus robot) allowed per change. Additional
+comments are rejected.
++
+By default 5,000.
+
[[change.maxUpdates]]change.maxUpdates::
+
Maximum number of updates to a change. Counts only updates to the main NoteDb
@@ -1232,6 +1239,34 @@
By default 1000.
+[[change.mergeabilityComputationBehavior]]change.mergeabilityComputationBehavior::
++
+This setting determines when Gerrit computes if a change is mergeable or not.
+This computation is expensive, especially when the repository is large or when
+there are many open changes.
++
+This config can have the following states:
++
+* `API_REF_UPDATED_AND_CHANGE_REINDEX`: Gerrit indexes `mergeability` enabling
+ the `is:mergeable` predicate in change search and allowing fast retrieval of
+ this bit in query responses. Gerrit will always serve `mergeable` in
+ link:rest-api-changes.html#change-info[ChangeInfo] objects.
+ Gerrit will reindex all open changes when the target ref advances (expensive).
+* `REF_UPDATED_AND_CHANGE_REINDEX`: Gerrit indexes `mergeability` enabling the
+ `is:mergeable` predicate in change search and allowing fast retrieval of this
+ bit in query responses. Gerrit will never serve `mergeable` in
+ link:rest-api-changes.html#change-info[ChangeInfo]
+ objects. This state can be a final state for instances that only want to
+ optimize the read path, but not the write path for speed or serve as an
+ intermediary step for instances that want to optimize both and need to migrate
+ callers of their API.
+ Gerrit will reindex all open changes when the target ref advances (expensive).
+* `NEVER`: Gerrit does not index `mergeable`, so `is:mergeable` is disabled as
+ query operator. Gerrit does not serve `mergeable` in
+ link:rest-api-changes.html#change-info[ChangeInfo].
+
+Default is `REF_UPDATED_AND_CHANGE_REINDEX`.
+
[[change.move]]change.move::
+
Whether the link:rest-api-changes.html#move-change[Move Change] REST
@@ -1266,11 +1301,10 @@
[[change.robotCommentSizeLimit]]change.robotCommentSizeLimit::
+
-Maximum allowed size of a robot comment that will be accepted. Robot comments
-which exceed the indicated size will be rejected on addition. The specified
-value is interpreted as the maximum size in bytes of the JSON representation of
-the robot comment. Common unit suffixes of 'k', 'm', or 'g' are supported.
-Zero or negative values allow robot comments of unlimited size.
+Maximum allowed size in characters of a robot comment. Robot comments which
+exceed this size will be rejected on addition. Size computation is approximate
+and may be off by roughly 1%. Common unit suffixes of 'k', 'm', or 'g' are
+supported. Zero or negative values allow robot comments of unlimited size.
+
The default limit is 1024kB.
@@ -1400,9 +1434,10 @@
+
By default `true`, meaning mergeable changes are auto-abandoned.
+
-If link:#index.change.indexMergeable[`index.change.indexMergeable`]
-is disabled, setting this option to `false` has no effect and it
-behaves as though it were set to `true`.
+If
+link:#change.mergeabilityComputationBehavior[`change.mergeabilityComputationBehavior`]
+is set to `NEVER`, setting this option to `false` has no effect and it behaves
+as though it were set to `true`.
[[changeCleanup.cleanupAccountPatchReview]]changeCleanup.cleanupAccountPatchReview::
+
@@ -2776,22 +2811,6 @@
If not set or set to a zero, defaults to the number of logical CPUs as returned
by the JVM. If set to a negative value, defaults to a direct executor.
-[[index.change.indexMergeable]]index.change.indexMergeable::
-+
-Specifies if `mergeable` should be index or not. Indexing this field enables
-queries that contain the mergeability operator (`is:mergeable`). If enabled,
-Gerrit will check if the change is mergeable into the target branch when
-reindexing a change. This is an expensive operation.
-+
-If true, Gerrit will reindex all open changes when the target ref advances.
-Depending on the frequency of updates to the ref and the number of open changes,
-this can be very expensive.
-+
-When this setting is changed from `false` to `true`, all changes need to be
-reindexed.
-+
-Defaults to true.
-
[[index.onlineUpgrade]]index.onlineUpgrade::
+
Whether to upgrade to new index schema versions while the server is
@@ -3102,7 +3121,7 @@
events.
+
This can be set to the set of minimal options that consumers of Gerrit's
-events need. A minimal set would be (`SKIP_MERGEABLE`,`SKIP_DIFFSTAT`).
+events need. A minimal set would be (`SKIP_DIFFSTAT`).
+
Every option that gets added here will have a performance impact. The
general recommendation is therefore to set this to a minimal set of
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index 193a96f..a3b9d0b 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -344,6 +344,13 @@
Defaults to true.
+[[label_copyValue]]
+=== `label.Label-Name.copyValue`
+
+Value that should be copied forward when a new patch set is uploaded.
+This can be used to enable sticky votes. Can be specified multiple
+times. By default not set.
+
[[label_canOverride]]
=== `label.Label-Name.canOverride`
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 116ac76..037c859 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -22,12 +22,22 @@
* Python 2 or 3
* link:https://github.com/nodesource/distributions/blob/master/README.md[Node.js (including npm),role=external,window=_blank]
* Bower (`sudo npm install -g bower`)
-* link:https://docs.bazel.build/versions/master/install.html[Bazel,role=external,window=_blank] directly
-or through link:https://github.com/bazelbuild/bazelisk[Bazelisk,role=external,window=_blank]
+* link:https://docs.bazel.build/versions/master/install.html[Bazel,role=external,window=_blank] -launched with
+link:https://github.com/bazelbuild/bazelisk[Bazelisk,role=external,window=_blank]
* Maven
* zip, unzip
* gcc
+[[bazel]]
+=== Bazel
+
+link:https://github.com/bazelbuild/bazelisk[Bazelisk,role=external,window=_blank] includes a
+link:https://bazel.build/[Bazel,role=external,window=_blank] version check and downloads the correct
+`bazel` version for the git project/repository. Bazelisk is the recommended
+`bazel` launcher for Gerrit. Once Bazelisk is installed locally, a `bazel`
+symlink can be created towards it. This is so that every `bazel` command
+seamlessly uses Bazelisk, which then runs the proper `bazel` binary version.
+
[[java]]
=== Java
diff --git a/Documentation/dev-crafting-changes.txt b/Documentation/dev-crafting-changes.txt
index 2364fcc..ac8ca25 100644
--- a/Documentation/dev-crafting-changes.txt
+++ b/Documentation/dev-crafting-changes.txt
@@ -116,7 +116,10 @@
link:https://github.com/google/google-java-format[`google-java-format`,role=external,window=_blank]
tool (version 1.7), and to format Bazel BUILD, WORKSPACE and .bzl files the
link:https://github.com/bazelbuild/buildtools/tree/master/buildifier[`buildifier`,role=external,window=_blank]
-tool (version 0.29.0).
+tool (version 0.29.0). Unused dependencies are found and removed using the
+link:https://github.com/bazelbuild/buildtools/tree/master/unused_deps[`unused_deps`,role=external,window=_blank]
+build tool, a sibling of `buildifier`.
+
These tools automatically apply format according to the style guides; this
streamlines code review by reducing the need for time-consuming, tedious,
and contentious discussions about trivial issues like whitespace.
diff --git a/Documentation/dev-e2e-tests.txt b/Documentation/dev-e2e-tests.txt
index 5e39687..9dd4d0f 100644
--- a/Documentation/dev-e2e-tests.txt
+++ b/Documentation/dev-e2e-tests.txt
@@ -1,51 +1,82 @@
:linkattrs:
= Gerrit Code Review - End to end load tests
-This document provides a description of a Gerrit load test scenario implemented using the link:http://gatling.io[`Gatling`] framework.
+This document provides a description of a Gerrit load test scenario implemented using the
+link:https://gatling.io/[Gatling,role=external,window=_blank] framework.
-Similar scenarios have been successfully used to compare performance of different Gerrit versions or study the Gerrit response
-under different load profiles.
+Similar scenarios have been successfully used to compare performance of different Gerrit versions
+or study the Gerrit response under different load profiles.
== What is Gatling?
-Gatling is a load testing tool which provides out of the box support for the HTTP protocol. Documentation on how to write an
-HTTP load test can be found link:https://gatling.io/docs/current/http/http_protocol/[`here`,role=external,window=_blank].
+Gatling is a load testing tool which provides out of the box support for the HTTP protocol.
+Documentation on how to write an HTTP load test can be found
+link:https://gatling.io/docs/current/http/http_protocol/[here,role=external,window=_blank].
-However, in the scenario we are proposing, we are leveraging the link:https://github.com/GerritForge/gatling-git[`Gatling Git extension`,role=external,window=_blank]
+However, in the scenario we are proposing, we are leveraging the
+link:https://github.com/GerritForge/gatling-git[Gatling Git extension,role=external,window=_blank]
to run tests at Git protocol level.
-Gatling is written in Scala, but the abstraction provided by the Gatling DSL makes the scenarios implementation easy even without any Scala knowledge.
+Gatling is written in Scala, but the abstraction provided by the Gatling DSL makes the scenarios
+implementation easy even without any Scala knowledge.
Examples of scenarios can be found in the `e2e-tests` directory.
-=== How to run the load tests
+== How to build the tests
-==== Prerequisites
+An link:https://www.scala-sbt.org/download.html[sbt-based installation,role=external,window=_blank]
+of link:https://www.scala-lang.org/download/[Scala,role=external,window=_blank] is required.
-* link:https://www.scala-lang.org/download/[`Scala 2.12`]
-
-==== How to build
+The `scalaVersion` used by `sbt` once installed is defined in the `build.sbt` file. That specific
+version of Scala is automatically used by `sbt` while building:
----
sbt compile
----
-==== Setup
+The following warning, if present when executing `sbt` commands, can be removed by creating the
+link:https://www.scala-sbt.org/1.x/docs/Using-Sonatype.html#step+3%3A+Credentials[related credentials file,role=external,window=_blank]
+locally. Dummy values for `user` and `password` in that file can be used initially. Notice the
+plural form for the file name, expected by the warning (below), compared to the one from that linked
+example which is singular.
-If you are running SSH commands the private keys of the users used for testing need to go in `/tmp/ssh-keys`.
-The keys need to be generated this way (JSch won't validate them [otherwise,role=external,window=_blank](https://stackoverflow.com/questions/53134212/invalid-privatekey-when-using-jsch):
+----
+[warn] Credentials file ~/.sbt/sonatype_credentials does not exist
+----
+
+Every `sbt` command can include an optional log level
+link:https://www.scala-sbt.org/1.x/docs/Howto-Logging.html#Change+the+logging+level+globally[argument,role=external,window=_blank].
+Below, `[info]` logs are no longer shown:
+
+----
+sbt --warn compile
+----
+
+=== How to build using Docker
+
+----
+docker build . -t e2e-tests
+----
+
+== How to set-up
+
+=== SSH keys
+
+If you are running SSH commands, the private keys of the users used for testing need to go in
+`/tmp/ssh-keys`. The keys need to be generated this way (JSch won't validate them
+link:https://stackoverflow.com/questions/53134212/invalid-privatekey-when-using-jsch[otherwise,role=external,window=_blank]):
----
ssh-keygen -m PEM -t rsa -C "test@mail.com" -f /tmp/ssh-keys/id_rsa
----
-*NOTE*: Don't forget to add the public keys for the testing user(s) to your git server
+*NOTE*: Don't forget to add the public keys for the testing user(s) to your git server.
-==== Input file
+=== Input file
-The ReplayRecordsScenario is fed by the data coming from the [src/test/resources/data/requests.json](/src/test/resources/data/requests.json) file.
-Such file contains the commands and repo used during the load test.
-Below an example:
+The `ReplayRecordsFromFeederScenario` is fed with the data coming from the
+`src/test/resources/data/requests.json` file. Such a file contains the commands and repo used
+during the load test. Example below:
----
[
@@ -61,12 +92,13 @@
----
Valid commands are:
-* fetch
-* pull
-* push
-* clone
-==== How to use the framework
+* `fetch`
+* `pull`
+* `push`
+* `clone`
+
+== How to run tests
Run all tests:
----
@@ -83,6 +115,16 @@
sbt "gatling:lastReport"
----
+The `src/test/resources/logback.xml` file
+link:http://logback.qos.ch/manual/configuration.html[configures,role=external,window=_blank]
+Gatling's logging level.
+
+=== How to run using Docker
+
+----
+docker run -it e2e-tests -s com.google.gerrit.scenarios.ReplayRecordsFromFeederScenario
+----
+
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index 5f72f37..5974aca 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -2,7 +2,7 @@
= Gerrit Code Review: Developer Setup
To build a developer instance, you'll need link:https://bazel.build/[Bazel,role=external,window=_blank] to
-compile the code.
+compile the code, preferably launched with link:https://github.com/bazelbuild/bazelisk[Bazelisk,role=external,window=_blank].
== Git Setup
@@ -62,6 +62,12 @@
For instructions on running the acceptance tests with Bazel,
see <<dev-bazel#tests,Running Unit Tests with Bazel>>.
+[[e2e]]
+=== End-to-end tests
+
+<<dev-e2e-tests#,This document>> describes how load test scenarios are
+implemented using link:https://gatling.io/[`Gatling`].
+
== Local server
diff --git a/Documentation/metrics.txt b/Documentation/metrics.txt
index 236792b..00e5b97 100644
--- a/Documentation/metrics.txt
+++ b/Documentation/metrics.txt
@@ -152,6 +152,10 @@
* `notedb/external_id_update_count`: Total number of external ID updates.
* `notedb/read_all_external_ids_latency`: Latency for reading all
external ID's from NoteDb.
+* `notedb/read_single_account_config_latency`: Latency for reading a single
+account config from NoteDb.
+* `notedb/read_single_external_id_latency`: Latency for reading a single
+external ID from NoteDb.
=== Permissions
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index e3e9009..bb83d95 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -2428,8 +2428,8 @@
|Field Name |Description
|`change` |
link:rest-api-changes.html#change-info[ChangeInfo] entity describing the change
-on which one or more comments was deleted. Populated with only the
-link:rest-api-changes.html#skip_mergeable[SKIP_MERGEABLE] option.
+on which one or more comments was deleted. Populated with no change list
+options.
|`deleted` |
List of link:rest-api-changes.html#comment-info[CommentInfo] entities for each
comment that was deleted.
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 6da455d..1379de7 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -306,26 +306,6 @@
from the change owner, i.e. this change would show up in the results of
link:user-search.html#reviewedby[reviewedby:self].
--
-
-[[skip_mergeable]]
---
-* `SKIP_MERGEABLE`: skip the `mergeable` field in
-link:#change-info[ChangeInfo]. For fast moving projects, this field must
-be recomputed often, which is slow for projects with big trees.
-+
-When link:config-gerrit.html#change.api.excludeMergeableInChangeInfo[
-`change.api.excludeMergeableInChangeInfo`] is set in the `gerrit.config`,
-the `mergeable` field will always be omitted and `SKIP_MERGEABLE` has no
-effect.
-+
-When link:config-gerrit.html#index.change.indexMergeable[
-`index.change.indexMergeable`] is set to `false` in the `gerrit.config`,
-the `mergeable` field will always be omitted when querying changes and
-`SKIP_MERGEABLE` has no effect.
-+
-A change's mergeability can be requested separately by calling the
-link:#get-mergeable[get-mergeable] endpoint.
---
[[skip_diffstat]]
--
* `SKIP_DIFFSTAT`: skip the 'insertions' and 'deletions' field in link:#change-info[ChangeInfo].
@@ -2098,8 +2078,8 @@
"name": "Code Analyzer",
"email": "code.analyzer@example.com"
},
- "robotId": "importChecker",
- "robotRunId": "76b1375aa8626ea7149792831fe2ed85e80d9e04"
+ "robot_id": "importChecker",
+ "robot_run_id": "76b1375aa8626ea7149792831fe2ed85e80d9e04"
},
{
"id": "TveXwFiA",
@@ -2111,8 +2091,8 @@
"name": "Code Analyzer",
"email": "code.analyzer@example.com"
},
- "robotId": "styleChecker",
- "robotRunId": "5c606c425dd45184484f9d0a2ffd725a7607839b"
+ "robot_id": "styleChecker",
+ "robot_run_id": "5c606c425dd45184484f9d0a2ffd725a7607839b"
}
]
}
@@ -4900,8 +4880,8 @@
"name": "Code Analyzer",
"email": "code.analyzer@example.com"
},
- "robotId": "importChecker",
- "robotRunId": "76b1375aa8626ea7149792831fe2ed85e80d9e04"
+ "robot_id": "importChecker",
+ "robot_run_id": "76b1375aa8626ea7149792831fe2ed85e80d9e04"
},
{
"id": "TveXwFiA",
@@ -4913,8 +4893,8 @@
"name": "Code Analyzer",
"email": "code.analyzer@example.com"
},
- "robotId": "styleChecker",
- "robotRunId": "5c606c425dd45184484f9d0a2ffd725a7607839b"
+ "robot_id": "styleChecker",
+ "robot_run_id": "5c606c425dd45184484f9d0a2ffd725a7607839b"
}
]
}
@@ -4954,8 +4934,8 @@
"name": "Code Analyzer",
"email": "code.analyzer@example.com"
},
- "robotId": "importChecker",
- "robotRunId": "76b1375aa8626ea7149792831fe2ed85e80d9e04"
+ "robot_id": "importChecker",
+ "robot_run_id": "76b1375aa8626ea7149792831fe2ed85e80d9e04"
}
----
@@ -5522,8 +5502,8 @@
}
----
-As response a link:#cherry-pick-change-info[CherryPickChangeInfo]
-entity is returned that describes the resulting cherry-pick change.
+As response a link:#change-info[ChangeInfo] entity is returned that
+describes the resulting cherry-pick change.
.Response
----
@@ -5964,10 +5944,9 @@
Not set for merged changes.
|`mergeable` |optional|
Whether the change is mergeable. +
-Not set for merged changes, if the change has not yet been tested, or
-if the link:#skip_mergeable[skip_mergeable] option is set or when
-link:config-gerrit.html#change.api.excludeMergeableInChangeInfo[change.api.excludeMergeableInChangeInfo]
-is set.
+Only set for open changes if
+link:config-gerrit.html#change.mergeabilityComputationBehavior[change.mergeabilityComputationBehavior]
+is `API_REF_UPDATED_AND_CHANGE_REINDEX`.
|`submittable` |optional|
Whether the change has been approved by the project submit rules. +
Only set if link:#submittable[requested].
@@ -6063,6 +6042,21 @@
If the change that triggered the submission also has a topic, it will be
"<id>-<topic>" of the change that triggered the submission.
The callers must not rely on the format of the submission ID.
+|`cherry_pick_of_change` |optional|
+The numeric Change-Id of the change that this change was cherry-picked from.
+|`cherry_pick_of_patch_set`|optional|
+The patchset number of the change that this change was cherry-picked from.
+|`contains_git_conflicts` |optional, not set if `false`|
+Whether the change contains conflicts. +
+If `true`, some of the file contents of the change contain git conflict
+markers to indicate the conflicts. +
+Only set if this change info is returned in response to a request that
+creates a new change or patch set and conflicts are allowed. In
+particular this field is only populated if the change info is returned
+by one of the following REST endpoints: link:#create-change[Create
+Change], link:#create-merge-patch-set-for-change[Create Merge Patch Set
+For Change], link:#cherry-pick[Cherry Pick Revision],
+link:rest-api-project.html#cherry-pick-commit[Cherry Pick Commit]
|==================================
[[change-input]]
@@ -6141,23 +6135,6 @@
Which patchset (if any) generated this message.
|==================================
-[[cherry-pick-change-info]]
-=== CherryPickChangeInfo
-The `CherryPickChangeInfo` entity contains information about a
-cherry-pick change.
-
-`CherryPickChangeInfo` has the same fields as link:#change-info[
-ChangeInfo]. In addition `CherryPickChangeInfo` has the following
-fields:
-
-[options="header",cols="1,^1,5"]
-|======================================
-|Field Name ||Description
-|`contains_git_conflicts` |optional, not set if `false`|
-Whether any file in the change contains Git conflict markers.
-|======================================
-
-
[[cherrypick-input]]
=== CherryPickInput
The `CherryPickInput` entity contains information for cherry-picking a change to a new branch.
@@ -6189,9 +6166,8 @@
there are conflicts. If there are conflicts the file contents of the
created change contain git conflict markers to indicate the conflicts.
Callers can find out if there were conflicts by checking the
-`contains_git_conflicts` field in the link:#cherry-pick-change-info[
-CherryPickChangeInfo] that is returned by the cherry-pick REST
-endpoints. If there are conflicts the cherry-pick change is marked as
+`contains_git_conflicts` field in the link:#change-info[ChangeInfo]. If
+there are conflicts the cherry-pick change is marked as
work-in-progress.
|===========================
@@ -6801,16 +6777,16 @@
Submit type used for this change, can be `MERGE_IF_NECESSARY`,
`FAST_FORWARD_ONLY`, `REBASE_IF_NECESSARY`, `REBASE_ALWAYS`, `MERGE_ALWAYS` or
`CHERRY_PICK`.
-|`strategy` |optional|
+|`strategy` |optional|
The strategy of the merge, can be `recursive`, `resolve`,
`simple-two-way-in-core`, `ours` or `theirs`.
|`mergeable` ||
`true` if this change is cleanly mergeable, `false` otherwise
-|`commit_merged` |optional|
+|`commit_merged` |optional|
`true` if this change is already merged, `false` otherwise
-|`content_merged` |optional|
+|`content_merged`|optional|
`true` if the content of this change is already merged, `false` otherwise
-|`conflicts`|optional|
+|`conflicts` |optional|
A list of paths with conflicts
|`mergeable_into`|optional|
A list of other branch names where this change could merge cleanly
@@ -6821,9 +6797,9 @@
The `MergeInput` entity contains information about the merge
[options="header",cols="1,^1,5"]
-|============================
-|Field Name ||Description
-|`source` ||
+|==============================
+|Field Name ||Description
+|`source` ||
The source to merge from, e.g. a complete or abbreviated commit SHA-1,
a complete reference name, a short reference name under `refs/heads`, `refs/tags`,
or `refs/remotes` namespace, etc.
@@ -6832,10 +6808,20 @@
`source` is checked for visibility and reachability against only this
branch. This speeds up the operation, especially for large repos with
many branches.
-|`strategy` |optional|
+|`strategy` |optional|
The strategy of the merge, can be `recursive`, `resolve`,
`simple-two-way-in-core`, `ours` or `theirs`, default will use project settings.
-|============================
+|`allow_conflicts`|optional, defaults to false|
+If `true`, creating the merge succeeds also if there are conflicts. +
+If there are conflicts the file contents of the created change contain
+git conflict markers to indicate the conflicts. +
+Callers can find out whether there were conflicts by checking the
+`contains_git_conflicts` field in the link:#change-info[ChangeInfo]. +
+If there are conflicts the change is marked as work-in-progress. +
+This option is not supported for all merge strategies (e.g. it's
+supported for `recursive` and `resolve`, but not for
+`simple-two-way-in-core`).
+|==============================
[[merge-patch-set-input]]
=== MergePatchSetInput
@@ -6848,7 +6834,7 @@
|`subject` |optional|
The new subject for the change, if not specified, will reuse the current patch
set's subject
-|`inherit_parent` |optional, default to `false`|
+|`inherit_parent` |optional, default to `false`|
Use the current patch set's first parent as the merge tip when set to `true`.
|`base_change` |optional|
A link:#change-id[\{change-id\}] that identifies a change. When `inherit_parent`
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index e9e3b94..828674f 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1566,10 +1566,11 @@
the whole topic is submitted].
|`disable_private_changes` |not set if `false`|
Returns true if private changes are disabled.
-|`exclude_mergeable_in_change_info` |not set if `false`|
-Value of the link:config-gerrit.html#change.api.excludeMergeableInChangeInfo[
+|`mergeability_computation_behavior` ||
+Value of the link:config-gerrit.html#change.mergeabilityComputationBehavior[
configuration parameter] that controls whether the mergeability bit in
-link:rest-api-changes.html#change-info[ChangeInfo] will never be set.
+link:rest-api-changes.html#change-info[ChangeInfo] will never be set and if the
+bit is indexed.
|=============================
[[change-index-config-info]]
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 6fbb338..a4e27b3 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -2576,9 +2576,8 @@
}
----
-As response a link:rest-api-changes.html#cherry-pick-change-info[
-CherryPickChangeInfo] entity is returned that describes the resulting
-cherry-picked change.
+As response a link:rest-api-changes.html#change-info[ChangeInfo] entity
+is returned that describes the resulting cherry-picked change.
.Response
----
@@ -3917,6 +3916,8 @@
|`copy_all_scores_on_merge_first_parent_update`|`false` if not set|
Whether link:config-labels.html#label_copyAllScoresOnMergeFirstParentUpdate[
copyAllScoresOnMergeFirstParentUpdate] is set on the label.
+|`copy_values` |optional|
+List of values that should be copied forward when a new patch set is uploaded.
|`allow_post_submit`|`false` if not set|
Whether link:config-labels.html#label_allowPostSubmit[allowPostSubmit] is set
on the label.
@@ -3982,6 +3983,8 @@
|`copy_all_scores_on_merge_first_parent_update`|optional|
Whether link:config-labels.html#label_copyAllScoresOnMergeFirstParentUpdate[
copyAllScoresOnMergeFirstParentUpdate] is set on the label.
+|`copy_values` |optional|
+List of values that should be copied forward when a new patch set is uploaded.
|`allow_post_submit`|optional|
Whether link:config-labels.html#label_allowPostSubmit[allowPostSubmit] is set
on the label.
diff --git a/Jenkinsfile b/Jenkinsfile
index 7417bb3..edd4234 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -25,15 +25,6 @@
static final String gerritRepositoryNameSha1Suffix = "-a6a0e4682515f3521897c5f950d1394f4619d928"
}
-class Change {
- static String sha1 = ""
- static String number = ""
- static String branch = ""
- static String ref = ""
- static String patchNum = ""
- static String url = ""
-}
-
class Build {
String url
String result
@@ -83,36 +74,17 @@
gerritCheck(checks: [ "${check.uuid}" : "${check.getCheckResultFromBuild()}" ], url: "${check.consoleUrl}")
}
-def queryChangedFiles(url, changeNum, sha1) {
- def queryUrl = "${url}changes/${Change.number}/revisions/${Change.sha1}/files/"
+def queryChangedFiles(url) {
+ def queryUrl = "${url}changes/${env.GERRIT_CHANGE_NUMBER}/revisions/${env.GERRIT_PATCHSET_REVISION}/files/"
def response = httpRequest queryUrl
def files = response.getContent().substring(5)
def filesJson = new JsonSlurper().parseText(files)
return filesJson.keySet().findAll { it != "/COMMIT_MSG" }
}
-def queryChange(){
- def requestedChangeId = env.BRANCH_NAME.split('/')[1]
- def queryUrl = "${Globals.gerritUrl}changes/${requestedChangeId}/?pp=0&O=3"
- def response = httpRequest queryUrl
- def jsonSlurper = new JsonSlurper()
- return jsonSlurper.parseText(response.getContent().substring(5))
-}
-
-def getChangeMetaData(){
- def changeJson = queryChange()
- Change.sha1 = changeJson.current_revision
- Change.number = changeJson._number
- Change.branch = changeJson.branch
- def revision = changeJson.revisions.get(Change.sha1)
- Change.ref = revision.ref
- Change.patchNum = revision._number
- Change.url = Globals.gerritUrl + "#/c/" + Change.number + "/" + Change.patchNum
-}
-
def collectBuildModes() {
Builds.modes = ["notedb"]
- def changedFiles = queryChangedFiles(Globals.gerritUrl, Change.number, Change.sha1)
+ def changedFiles = queryChangedFiles(Globals.gerritUrl)
def polygerritFiles = changedFiles.findAll { it.startsWith("polygerrit-ui") ||
it.startsWith("lib/js") }
@@ -137,11 +109,11 @@
for (int i = 1; i <= retryTimes; i++) {
try {
slaveBuild = build job: "${buildName}", parameters: [
- string(name: 'REFSPEC', value: Change.ref),
- string(name: 'BRANCH', value: Change.sha1),
- string(name: 'CHANGE_URL', value: Change.url),
+ string(name: 'REFSPEC', value: "refs/changes/${env.BRANCH_NAME}"),
+ string(name: 'BRANCH', value: env.GERRIT_PATCHSET_REVISION),
+ string(name: 'CHANGE_URL', value: "${Globals.gerritUrl}c/${env.GERRIT_PROJECT}/+/${env.GERRIT_CHANGE_NUMBER}"),
string(name: 'MODE', value: mode),
- string(name: 'TARGET_BRANCH', value: Change.branch)
+ string(name: 'TARGET_BRANCH', value: env.GERRIT_BRANCH)
], propagate: false
} finally {
if (buildName == "Gerrit-codestyle"){
@@ -243,8 +215,6 @@
if (hasChangeNumber()) {
stage('Preparing'){
gerritReview labels: ['Verified': 0, 'Code-Style': 0]
-
- getChangeMetaData()
collectBuildModes()
}
}
diff --git a/WORKSPACE b/WORKSPACE
index 6e9fb38..1619c45 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -24,13 +24,6 @@
rbe_autoconfig(name = "rbe_default")
http_archive(
- name = "bazel_skylib",
- sha256 = "2ea8a5ed2b448baf4a6855d3ce049c4c452a6470b1efd1504fdb7c1c134d220a",
- strip_prefix = "bazel-skylib-0.8.0",
- urls = ["https://github.com/bazelbuild/bazel-skylib/archive/0.8.0.tar.gz"],
-)
-
-http_archive(
name = "io_bazel_rules_closure",
sha256 = "03c3b16f205085817fd89cfdcb2220a0138647ee7992be9cef291b069dd90301",
strip_prefix = "rules_closure-196a45f0ede2faec11dcc6c60fbc5e7471f4bd58",
@@ -47,15 +40,6 @@
urls = ["https://raw.githubusercontent.com/google/closure-compiler/35d2b3340ff23a69441f10fa3bc820691c2942f2/contrib/externs/polymer-1.0.js"],
)
-# Check Bazel version when invoked by Bazel directly
-load("//tools/bzl:bazelisk_version.bzl", "bazelisk_version")
-
-bazelisk_version(name = "bazelisk_version")
-
-load("@bazelisk_version//:check.bzl", "check_bazel_version")
-
-check_bazel_version()
-
load("@io_bazel_rules_closure//closure:repositories.bzl", "rules_closure_dependencies", "rules_closure_toolchains")
# Prevent redundant loading of dependencies.
@@ -64,7 +48,6 @@
# https://github.com/google/closure-templates/pull/155
rules_closure_dependencies(
omit_aopalliance = True,
- omit_bazel_skylib = True,
omit_javax_inject = True,
omit_rules_cc = True,
)
diff --git a/e2e-tests/load-tests/src/test/resources/logback.xml b/e2e-tests/load-tests/src/test/resources/logback.xml
new file mode 100644
index 0000000..a139e69
--- /dev/null
+++ b/e2e-tests/load-tests/src/test/resources/logback.xml
@@ -0,0 +1,12 @@
+<configuration>
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <!-- encoders are assigned the type
+ ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
+ <encoder>
+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+ <root level="warn">
+ <appender-ref ref="STDOUT"/>
+ </root>
+</configuration>
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index 135a80e..89d1f3d 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -1,5 +1,4 @@
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
-load("//tools/bzl:java.bzl", "java_library2")
load("//tools/bzl:javadoc.bzl", "java_doc")
FUNCTION_SRCS = [
@@ -11,13 +10,9 @@
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/gpg",
"//java/com/google/gerrit/git",
- "//java/com/google/gerrit/httpd/auth/openid",
"//java/com/google/gerrit/index:query_exception",
"//java/com/google/gerrit/launcher",
"//java/com/google/gerrit/lifecycle",
- "//java/com/google/gerrit/pgm",
- "//java/com/google/gerrit/pgm/http/jetty",
- "//java/com/google/gerrit/pgm/util",
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
"//java/com/google/gerrit/entities",
@@ -51,7 +46,6 @@
"//lib/guice",
"//lib/guice:guice-assistedinject",
"//lib/guice:guice-servlet",
- "//lib/jetty:servlet",
"//lib/mail",
"//lib/mina:sshd",
"//lib/log:impl-log4j",
@@ -63,6 +57,10 @@
]
TEST_DEPS = [
+ "//java/com/google/gerrit/httpd/auth/openid",
+ "//java/com/google/gerrit/pgm",
+ "//java/com/google/gerrit/pgm/http/jetty",
+ "//java/com/google/gerrit/pgm/util",
"//java/com/google/gerrit/truth",
"//java/com/google/gerrit/acceptance/config",
"//java/com/google/gerrit/acceptance/testsuite/project",
@@ -76,6 +74,13 @@
"//java/com/google/gerrit/git/testing",
]
+PGM_DEPLOY_ENV = [
+ "//lib:caffeine",
+ "//lib:caffeine-guava",
+ "//lib/jackson:jackson-core",
+ "//lib/prolog:cafeteria",
+]
+
java_library(
name = "lib",
testonly = True,
@@ -100,31 +105,33 @@
name = "framework-deploy-env",
testonly = True,
main_class = "Dummy",
- runtime_deps = DEPLOY_ENV,
+ runtime_deps = DEPLOY_ENV + PGM_DEPLOY_ENV,
)
-java_library2(
+exported_deps = [
+ ":function",
+ "//lib:jgit-junit",
+ "//lib:jimfs",
+ "//lib:servlet-api",
+ "//lib/httpcomponents:fluent-hc",
+ "//lib/httpcomponents:httpclient",
+ "//lib/httpcomponents:httpcore",
+ "//lib/mockito",
+ "//lib/truth",
+ "//lib/truth:truth-java8-extension",
+ "//lib/greenmail",
+] + TEST_DEPS
+
+java_library(
name = "framework-lib",
testonly = True,
srcs = glob(
["**/*.java"],
exclude = FUNCTION_SRCS,
),
- exported_deps = [
- ":function",
- "//lib:jgit-junit",
- "//lib:jimfs",
- "//lib:servlet-api",
- "//lib/httpcomponents:fluent-hc",
- "//lib/httpcomponents:httpclient",
- "//lib/httpcomponents:httpcore",
- "//lib/mockito",
- "//lib/truth",
- "//lib/truth:truth-java8-extension",
- "//lib/greenmail",
- ] + TEST_DEPS,
visibility = ["//visibility:public"],
- deps = DEPLOY_ENV,
+ exports = exported_deps,
+ deps = DEPLOY_ENV + exported_deps,
)
java_library(
diff --git a/java/com/google/gerrit/acceptance/ExtensionRegistry.java b/java/com/google/gerrit/acceptance/ExtensionRegistry.java
index f9116a1..2371bd0 100644
--- a/java/com/google/gerrit/acceptance/ExtensionRegistry.java
+++ b/java/com/google/gerrit/acceptance/ExtensionRegistry.java
@@ -23,6 +23,7 @@
import com.google.gerrit.extensions.events.GroupIndexedListener;
import com.google.gerrit.extensions.events.ProjectIndexedListener;
import com.google.gerrit.extensions.events.RevisionCreatedListener;
+import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.PrivateInternals_DynamicMapImpl;
@@ -69,6 +70,7 @@
private final DynamicSet<AccountActivationValidationListener>
accountActivationValidationListeners;
private final DynamicSet<OnSubmitValidationListener> onSubmitValidationListeners;
+ private final DynamicSet<WorkInProgressStateChangedListener> workInProgressStateChangedListeners;
@Inject
ExtensionRegistry(
@@ -93,7 +95,8 @@
DynamicSet<RevisionCreatedListener> revisionCreatedListeners,
DynamicSet<GroupBackend> groupBackends,
DynamicSet<AccountActivationValidationListener> accountActivationValidationListeners,
- DynamicSet<OnSubmitValidationListener> onSubmitValidationListeners) {
+ DynamicSet<OnSubmitValidationListener> onSubmitValidationListeners,
+ DynamicSet<WorkInProgressStateChangedListener> workInProgressStateChangedListeners) {
this.accountIndexedListeners = accountIndexedListeners;
this.changeIndexedListeners = changeIndexedListeners;
this.groupIndexedListeners = groupIndexedListeners;
@@ -116,6 +119,7 @@
this.groupBackends = groupBackends;
this.accountActivationValidationListeners = accountActivationValidationListeners;
this.onSubmitValidationListeners = onSubmitValidationListeners;
+ this.workInProgressStateChangedListeners = workInProgressStateChangedListeners;
}
public Registration newRegistration() {
@@ -219,6 +223,10 @@
return add(onSubmitValidationListeners, onSubmitValidationListener);
}
+ public Registration add(WorkInProgressStateChangedListener workInProgressStateChangedListener) {
+ return add(workInProgressStateChangedListeners, workInProgressStateChangedListener);
+ }
+
private <T> Registration add(DynamicSet<T> dynamicSet, T extension) {
return add(dynamicSet, extension, "gerrit");
}
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index f90df67..95afad7 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -571,8 +571,8 @@
cfg.setInt("sshd", null, "commandStartThreads", 1);
cfg.setInt("receive", null, "threadPoolSize", 1);
cfg.setInt("index", null, "threads", 1);
- if (cfg.getString("index", "change", "indexMergeable") == null) {
- cfg.setBoolean("index", "change", "indexMergeable", false);
+ if (cfg.getString("index", null, "mergeabilityComputationBehavior") == null) {
+ cfg.setString("index", null, "mergeabilityComputationBehavior", "NEVER");
}
}
diff --git a/java/com/google/gerrit/acceptance/InProcessProtocol.java b/java/com/google/gerrit/acceptance/InProcessProtocol.java
index 8a46d57..8fcc1e7 100644
--- a/java/com/google/gerrit/acceptance/InProcessProtocol.java
+++ b/java/com/google/gerrit/acceptance/InProcessProtocol.java
@@ -232,7 +232,7 @@
try {
perm.check(ProjectPermission.RUN_UPLOAD_PACK);
} catch (AuthException e) {
- throw new ServiceNotAuthorizedException();
+ throw new ServiceNotAuthorizedException(e.getMessage(), e);
} catch (PermissionBackendException e) {
throw new RuntimeException(e);
}
@@ -307,7 +307,7 @@
.project(req.project)
.check(ProjectPermission.RUN_RECEIVE_PACK);
} catch (AuthException e) {
- throw new ServiceNotAuthorizedException();
+ throw new ServiceNotAuthorizedException(e.getMessage(), e);
} catch (PermissionBackendException e) {
throw new RuntimeException(e);
}
diff --git a/java/com/google/gerrit/acceptance/StandaloneSiteTest.java b/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
index 1d6f51f..da93610 100644
--- a/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
+++ b/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
@@ -220,8 +220,11 @@
try {
status = p.waitFor();
} catch (InterruptedException e) {
- throw new InterruptedIOException(
- "interrupted waiting for: " + Joiner.on(' ').join(pb.command()));
+ InterruptedIOException iioe =
+ new InterruptedIOException(
+ "interrupted waiting for: " + Joiner.on(' ').join(pb.command()));
+ iioe.initCause(e);
+ throw iioe;
}
String result = new String(out, UTF_8);
diff --git a/java/com/google/gerrit/common/data/CommentDetail.java b/java/com/google/gerrit/common/data/CommentDetail.java
index d69f0bb..55e0143 100644
--- a/java/com/google/gerrit/common/data/CommentDetail.java
+++ b/java/com/google/gerrit/common/data/CommentDetail.java
@@ -18,10 +18,7 @@
import com.google.gerrit.entities.Comment;
import com.google.gerrit.entities.PatchSet;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
public class CommentDetail {
protected List<Comment> a;
@@ -29,8 +26,6 @@
private transient PatchSet.Id idA;
private transient PatchSet.Id idB;
- private transient Map<Integer, List<Comment>> forA;
- private transient Map<Integer, List<Comment>> forB;
public CommentDetail(PatchSet.Id idA, PatchSet.Id idB) {
this.a = new ArrayList<>();
@@ -67,88 +62,4 @@
public boolean isEmpty() {
return a.isEmpty() && b.isEmpty();
}
-
- public List<Comment> getForA(int lineNbr) {
- if (forA == null) {
- forA = index(a);
- }
- return get(forA, lineNbr);
- }
-
- public List<Comment> getForB(int lineNbr) {
- if (forB == null) {
- forB = index(b);
- }
- return get(forB, lineNbr);
- }
-
- private static List<Comment> get(Map<Integer, List<Comment>> m, int i) {
- List<Comment> r = m.get(i);
- return r != null ? orderComments(r) : Collections.emptyList();
- }
-
- /**
- * Order the comments based on their parent_uuid parent. It is possible to do this by iterating
- * over the list only once but it's probably overkill since the number of comments on a given line
- * will be small most of the time.
- *
- * @param comments The list of comments for a given line.
- * @return The comments sorted as they should appear in the UI
- */
- private static List<Comment> orderComments(List<Comment> comments) {
- // Map of comments keyed by their parent. The values are lists of comments since it is
- // possible for several comments to have the same parent (this can happen if two reviewers
- // click Reply on the same comment at the same time). Such comments will be displayed under
- // their correct parent in chronological order.
- Map<String, List<Comment>> parentMap = new HashMap<>();
-
- // It's possible to have more than one root comment if two reviewers create a comment on the
- // same line at the same time
- List<Comment> rootComments = new ArrayList<>();
-
- // Store all the comments in parentMap, keyed by their parent
- for (Comment c : comments) {
- String parentUuid = c.parentUuid;
- List<Comment> l = parentMap.get(parentUuid);
- if (l == null) {
- l = new ArrayList<>();
- parentMap.put(parentUuid, l);
- }
- l.add(c);
- if (parentUuid == null) {
- rootComments.add(c);
- }
- }
-
- // Add the comments in the list, starting with the head and then going through all the
- // comments that have it as a parent, and so on
- List<Comment> result = new ArrayList<>();
- addChildren(parentMap, rootComments, result);
-
- return result;
- }
-
- /** Add the comments to {@code outResult}, depth first */
- private static void addChildren(
- Map<String, List<Comment>> parentMap, List<Comment> children, List<Comment> outResult) {
- if (children != null) {
- for (Comment c : children) {
- outResult.add(c);
- addChildren(parentMap, parentMap.get(c.key.uuid), outResult);
- }
- }
- }
-
- private Map<Integer, List<Comment>> index(List<Comment> in) {
- HashMap<Integer, List<Comment>> r = new HashMap<>();
- for (Comment p : in) {
- List<Comment> l = r.get(p.lineNbr);
- if (l == null) {
- l = new ArrayList<>();
- r.put(p.lineNbr, l);
- }
- l.add(p);
- }
- return r;
- }
}
diff --git a/java/com/google/gerrit/common/data/LabelType.java b/java/com/google/gerrit/common/data/LabelType.java
index 14b8310..964cf67 100644
--- a/java/com/google/gerrit/common/data/LabelType.java
+++ b/java/com/google/gerrit/common/data/LabelType.java
@@ -14,14 +14,17 @@
package com.google.gerrit.common.data;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.LabelId;
import com.google.gerrit.entities.PatchSetApproval;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -37,6 +40,7 @@
public static final boolean DEF_COPY_ANY_SCORE = false;
public static final boolean DEF_COPY_MAX_SCORE = false;
public static final boolean DEF_COPY_MIN_SCORE = false;
+ public static final ImmutableList<Short> DEF_COPY_VALUES = ImmutableList.of();
public static final boolean DEF_IGNORE_SELF_APPROVAL = false;
public static LabelType withDefaultValues(String name) {
@@ -104,6 +108,7 @@
protected boolean copyAllScoresOnTrivialRebase;
protected boolean copyAllScoresIfNoCodeChange;
protected boolean copyAllScoresIfNoChange;
+ protected ImmutableList<Short> copyValues;
protected boolean allowPostSubmit;
protected boolean ignoreSelfApproval;
protected short defaultValue;
@@ -144,6 +149,7 @@
setCopyAnyScore(DEF_COPY_ANY_SCORE);
setCopyMaxScore(DEF_COPY_MAX_SCORE);
setCopyMinScore(DEF_COPY_MIN_SCORE);
+ setCopyValues(DEF_COPY_VALUES);
setAllowPostSubmit(DEF_ALLOW_POST_SUBMIT);
setIgnoreSelfApproval(DEF_IGNORE_SELF_APPROVAL);
@@ -298,6 +304,14 @@
this.copyAllScoresIfNoChange = copyAllScoresIfNoChange;
}
+ public ImmutableList<Short> getCopyValues() {
+ return copyValues;
+ }
+
+ public void setCopyValues(Collection<Short> copyValues) {
+ this.copyValues = copyValues.stream().sorted().collect(toImmutableList());
+ }
+
public boolean isMaxNegative(PatchSetApproval ca) {
return maxNegative == ca.value();
}
diff --git a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
index 084c2ec..f659bca 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -49,6 +49,7 @@
import com.google.gerrit.server.ReviewerByEmailSet;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.change.MergeabilityComputationBehavior;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.IndexUtils;
@@ -111,7 +112,7 @@
this.idField =
this.schema.useLegacyNumericFields() ? ChangeField.LEGACY_ID : ChangeField.LEGACY_ID_STR;
this.skipFields =
- gerritConfig.getBoolean("index", "change", "indexMergeable", true)
+ MergeabilityComputationBehavior.fromConfig(gerritConfig).includeInIndex()
? ImmutableSet.of()
: ImmutableSet.of(ChangeField.MERGEABLE.getName());
}
diff --git a/java/com/google/gerrit/entities/Comment.java b/java/com/google/gerrit/entities/Comment.java
index 55d739a..9c58fef 100644
--- a/java/com/google/gerrit/entities/Comment.java
+++ b/java/com/google/gerrit/entities/Comment.java
@@ -29,6 +29,8 @@
*
* <p>Changing fields in this class changes the storage format of inline comments in NoteDb and may
* require a corresponding data migration (adding new optional fields is generally okay).
+ *
+ * <p>Consider updating {@link #getApproximateSize()} when adding/changing fields.
*/
public class Comment {
public enum Status {
@@ -56,7 +58,7 @@
}
}
- public static class Key {
+ public static final class Key {
public String uuid;
public String filename;
public int patchSetId;
@@ -97,7 +99,7 @@
}
}
- public static class Identity {
+ public static final class Identity {
int id;
public Identity(Account.Id id) {
@@ -147,7 +149,7 @@
* </ul>
* </ul>
*/
- public static class Range implements Comparable<Range> {
+ public static final class Range implements Comparable<Range> {
private static final Comparator<Range> RANGE_COMPARATOR =
Comparator.<Range>comparingInt(range -> range.startLine)
.thenComparingInt(range -> range.startChar)
@@ -220,10 +222,12 @@
public Range range;
public String tag;
- // Hex commit SHA1 of the commit of the patchset to which this comment applies. Other classes call
- // this "commitId", but this class uses the old ReviewDb term "revId", and this field name is
- // serialized into JSON in NoteDb, so it can't easily be changed. Callers do not access this field
- // directly, and instead use the public getter/setter that wraps an ObjectId.
+ /**
+ * Hex commit SHA1 of the commit of the patchset to which this comment applies. Other classes call
+ * this "commitId", but this class uses the old ReviewDb term "revId", and this field name is
+ * serialized into JSON in NoteDb, so it can't easily be changed. Callers do not access this field
+ * directly, and instead use the public getter/setter that wraps an ObjectId.
+ */
private String revId;
public String serverId;
@@ -293,6 +297,23 @@
return realAuthor != null ? realAuthor : author;
}
+ /**
+ * Returns the comment's approximate size. This is used to enforce size limits and should
+ * therefore include all unbounded fields (e.g. String-s).
+ */
+ public int getApproximateSize() {
+ return nullableLength(message, parentUuid, tag, revId, serverId)
+ + (key != null ? nullableLength(key.filename, key.uuid) : 0);
+ }
+
+ static int nullableLength(String... strings) {
+ int length = 0;
+ for (String s : strings) {
+ length += s == null ? 0 : s.length();
+ }
+ return length;
+ }
+
@Override
public boolean equals(Object o) {
if (!(o instanceof Comment)) {
diff --git a/java/com/google/gerrit/entities/FixReplacement.java b/java/com/google/gerrit/entities/FixReplacement.java
index 046300e..fbbf746 100644
--- a/java/com/google/gerrit/entities/FixReplacement.java
+++ b/java/com/google/gerrit/entities/FixReplacement.java
@@ -14,10 +14,10 @@
package com.google.gerrit.entities;
-public class FixReplacement {
- public String path;
- public Comment.Range range;
- public String replacement;
+public final class FixReplacement {
+ public final String path;
+ public final Comment.Range range;
+ public final String replacement;
public FixReplacement(String path, Comment.Range range, String replacement) {
this.path = path;
@@ -38,4 +38,9 @@
+ '\''
+ '}';
}
+
+ /** Returns this instance's approximate size in bytes for the purpose of applying size limits. */
+ int getApproximateSize() {
+ return path.length() + replacement.length();
+ }
}
diff --git a/java/com/google/gerrit/entities/FixSuggestion.java b/java/com/google/gerrit/entities/FixSuggestion.java
index ac4e720..c88afe7 100644
--- a/java/com/google/gerrit/entities/FixSuggestion.java
+++ b/java/com/google/gerrit/entities/FixSuggestion.java
@@ -16,10 +16,10 @@
import java.util.List;
-public class FixSuggestion {
- public String fixId;
- public String description;
- public List<FixReplacement> replacements;
+public final class FixSuggestion {
+ public final String fixId;
+ public final String description;
+ public final List<FixReplacement> replacements;
public FixSuggestion(String fixId, String description, List<FixReplacement> replacements) {
this.fixId = fixId;
@@ -40,4 +40,11 @@
+ replacements
+ '}';
}
+
+ /** Returns this instance's approximate size in bytes for the purpose of applying size limits. */
+ int getApproximateSize() {
+ return fixId.length()
+ + description.length()
+ + replacements.stream().map(FixReplacement::getApproximateSize).reduce(0, Integer::sum);
+ }
}
diff --git a/java/com/google/gerrit/entities/KeyUtil.java b/java/com/google/gerrit/entities/KeyUtil.java
index d000b31..40fb757 100644
--- a/java/com/google/gerrit/entities/KeyUtil.java
+++ b/java/com/google/gerrit/entities/KeyUtil.java
@@ -97,7 +97,7 @@
}
}
} catch (ArrayIndexOutOfBoundsException err) {
- throw new IllegalArgumentException("Bad encoding: " + e);
+ throw new IllegalArgumentException("Bad encoding" + e, err);
}
try {
return new String(b, 0, bPtr, "UTF-8");
diff --git a/java/com/google/gerrit/entities/RobotComment.java b/java/com/google/gerrit/entities/RobotComment.java
index a7951ad..4a17566 100644
--- a/java/com/google/gerrit/entities/RobotComment.java
+++ b/java/com/google/gerrit/entities/RobotComment.java
@@ -19,7 +19,7 @@
import java.util.Map;
import java.util.Objects;
-public class RobotComment extends Comment {
+public final class RobotComment extends Comment {
public String robotId;
public String robotRunId;
public String url;
@@ -41,6 +41,22 @@
}
@Override
+ public int getApproximateSize() {
+ int approximateSize = super.getApproximateSize() + nullableLength(robotId, robotRunId, url);
+ approximateSize +=
+ properties != null
+ ? properties.entrySet().stream()
+ .map(entry -> nullableLength(entry.getKey(), entry.getValue()))
+ .reduce(0, Integer::sum)
+ : 0;
+ approximateSize +=
+ fixSuggestions != null
+ ? fixSuggestions.stream().map(FixSuggestion::getApproximateSize).reduce(0, Integer::sum)
+ : 0;
+ return approximateSize;
+ }
+
+ @Override
public String toString() {
return toStringHelper()
.add("robotId", robotId)
diff --git a/java/com/google/gerrit/exceptions/BUILD b/java/com/google/gerrit/exceptions/BUILD
index ef59be1..873b659 100644
--- a/java/com/google/gerrit/exceptions/BUILD
+++ b/java/com/google/gerrit/exceptions/BUILD
@@ -4,5 +4,8 @@
name = "exceptions",
srcs = glob(["*.java"]),
visibility = ["//visibility:public"],
- deps = ["//java/com/google/gerrit/entities"],
+ deps = [
+ "//java/com/google/gerrit/entities",
+ "//lib:jgit",
+ ],
)
diff --git a/java/com/google/gerrit/exceptions/InvalidSshKeyException.java b/java/com/google/gerrit/exceptions/InvalidSshKeyException.java
index 8baba20..dda80cf 100644
--- a/java/com/google/gerrit/exceptions/InvalidSshKeyException.java
+++ b/java/com/google/gerrit/exceptions/InvalidSshKeyException.java
@@ -23,4 +23,8 @@
public InvalidSshKeyException() {
super(MESSAGE);
}
+
+ public InvalidSshKeyException(Throwable cause) {
+ super(MESSAGE, cause);
+ }
}
diff --git a/java/com/google/gerrit/exceptions/MergeWithConflictsNotSupportedException.java b/java/com/google/gerrit/exceptions/MergeWithConflictsNotSupportedException.java
new file mode 100644
index 0000000..cab1d22
--- /dev/null
+++ b/java/com/google/gerrit/exceptions/MergeWithConflictsNotSupportedException.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 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.exceptions;
+
+import org.eclipse.jgit.merge.MergeStrategy;
+
+public class MergeWithConflictsNotSupportedException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public MergeWithConflictsNotSupportedException(MergeStrategy strategy) {
+ super("merge with conflicts is not supported with merge strategy: " + strategy.getName());
+ }
+}
diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index 119d941..96455a6 100644
--- a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -259,17 +259,12 @@
*
* <ul>
* <li>{@code CHECK} is omitted, to skip consistency checks.
- * <li>{@code SKIP_MERGEABLE} is omitted, so the {@code mergeable} bit <em>is</em> set.
* <li>{@code SKIP_DIFFSTAT} is omitted to ensure diffstat calculations.
* </ul>
*/
default ChangeInfo get() throws RestApiException {
return get(
- EnumSet.complementOf(
- EnumSet.of(
- ListChangesOption.CHECK,
- ListChangesOption.SKIP_MERGEABLE,
- ListChangesOption.SKIP_DIFFSTAT)));
+ EnumSet.complementOf(EnumSet.of(ListChangesOption.CHECK, ListChangesOption.SKIP_DIFFSTAT)));
}
/** {@link #get(ListChangesOption...)} with no options included. */
diff --git a/java/com/google/gerrit/extensions/api/changes/Changes.java b/java/com/google/gerrit/extensions/api/changes/Changes.java
index bcb49de1..8609207 100644
--- a/java/com/google/gerrit/extensions/api/changes/Changes.java
+++ b/java/com/google/gerrit/extensions/api/changes/Changes.java
@@ -69,6 +69,8 @@
ChangeApi create(ChangeInput in) throws RestApiException;
+ ChangeInfo createAsInfo(ChangeInput in) throws RestApiException;
+
QueryRequest query();
QueryRequest query(String query);
@@ -208,6 +210,11 @@
}
@Override
+ public ChangeInfo createAsInfo(ChangeInput in) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public QueryRequest query() {
throw new NotImplementedException();
}
diff --git a/java/com/google/gerrit/extensions/client/ListChangesOption.java b/java/com/google/gerrit/extensions/client/ListChangesOption.java
index 425265b..6071cc7 100644
--- a/java/com/google/gerrit/extensions/client/ListChangesOption.java
+++ b/java/com/google/gerrit/extensions/client/ListChangesOption.java
@@ -74,7 +74,11 @@
/** If tracking Ids are included, include detailed tracking Ids info. */
TRACKING_IDS(21),
- /** Skip mergeability data */
+ /**
+ * Use {@code gerrit.config} instead to turn this off for your instance. See {@code
+ * change.mergeabilityComputationBehavior}.
+ */
+ @Deprecated
SKIP_MERGEABLE(22),
/**
diff --git a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
index 67d0b0d9..66a2dd5 100644
--- a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
@@ -24,5 +24,5 @@
public String replyTooltip;
public int updateDelay;
public Boolean submitWholeTopic;
- public Boolean excludeMergeableInChangeInfo;
+ public String mergeabilityComputationBehavior;
}
diff --git a/java/com/google/gerrit/extensions/common/ChangeIndexConfigInfo.java b/java/com/google/gerrit/extensions/common/ChangeIndexConfigInfo.java
deleted file mode 100644
index 0cce106..0000000
--- a/java/com/google/gerrit/extensions/common/ChangeIndexConfigInfo.java
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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.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 3b3f2ad..d4a8477 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -56,6 +56,22 @@
public Integer cherryPickOfChange;
public Integer cherryPickOfPatchSet;
+ /**
+ * Whether the change contains conflicts.
+ *
+ * <p>If {@code true}, some of the file contents of the change contain git conflict markers to
+ * indicate the conflicts.
+ *
+ * <p>Only set if this change info is returned in response to a request that creates a new change
+ * or patch set and conflicts are allowed. In particular this field is only populated if the
+ * change info is returned by one of the following REST endpoints: {@link
+ * com.google.gerrit.server.restapi.change.CreateChange}, {@link
+ * com.google.gerrit.server.restapi.change.CreateMergePatchSet}, {@link
+ * com.google.gerrit.server.restapi.change.CherryPick}, {@link
+ * com.google.gerrit.server.restapi.change.CherryPickCommit}
+ */
+ public Boolean containsGitConflicts;
+
public int _number;
public AccountInfo owner;
diff --git a/java/com/google/gerrit/extensions/common/CherryPickChangeInfo.java b/java/com/google/gerrit/extensions/common/CherryPickChangeInfo.java
index 5e2b902..8b02e23 100644
--- a/java/com/google/gerrit/extensions/common/CherryPickChangeInfo.java
+++ b/java/com/google/gerrit/extensions/common/CherryPickChangeInfo.java
@@ -14,6 +14,11 @@
package com.google.gerrit.extensions.common;
-public class CherryPickChangeInfo extends ChangeInfo {
- public Boolean containsGitConflicts;
-}
+/**
+ * {@link ChangeInfo} that is returned when a change was cherry-picked.
+ *
+ * <p>This class used to define additional fields to {@link ChangeInfo}, but those were pulled up
+ * into {@link ChangeInfo}. Now this class is no longer needed, but we need to keep it for backwards
+ * compatibility of the Java API.
+ */
+public class CherryPickChangeInfo extends ChangeInfo {}
diff --git a/java/com/google/gerrit/extensions/common/IndexConfigInfo.java b/java/com/google/gerrit/extensions/common/IndexConfigInfo.java
deleted file mode 100644
index f3f510b..0000000
--- a/java/com/google/gerrit/extensions/common/IndexConfigInfo.java
+++ /dev/null
@@ -1,20 +0,0 @@
-// 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.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/LabelDefinitionInfo.java b/java/com/google/gerrit/extensions/common/LabelDefinitionInfo.java
index 64c3997..f552566 100644
--- a/java/com/google/gerrit/extensions/common/LabelDefinitionInfo.java
+++ b/java/com/google/gerrit/extensions/common/LabelDefinitionInfo.java
@@ -32,6 +32,7 @@
public Boolean copyAllScoresIfNoCodeChange;
public Boolean copyAllScoresOnTrivialRebase;
public Boolean copyAllScoresOnMergeFirstParentUpdate;
+ public List<Short> copyValues;
public Boolean allowPostSubmit;
public Boolean ignoreSelfApproval;
}
diff --git a/java/com/google/gerrit/extensions/common/LabelDefinitionInput.java b/java/com/google/gerrit/extensions/common/LabelDefinitionInput.java
index 0523f61..23d5df1 100644
--- a/java/com/google/gerrit/extensions/common/LabelDefinitionInput.java
+++ b/java/com/google/gerrit/extensions/common/LabelDefinitionInput.java
@@ -31,6 +31,7 @@
public Boolean copyAllScoresIfNoCodeChange;
public Boolean copyAllScoresOnTrivialRebase;
public Boolean copyAllScoresOnMergeFirstParentUpdate;
+ public List<Short> copyValues;
public Boolean allowPostSubmit;
public Boolean ignoreSelfApproval;
}
diff --git a/java/com/google/gerrit/extensions/common/MergeInput.java b/java/com/google/gerrit/extensions/common/MergeInput.java
index c3cfcee..7de5b38 100644
--- a/java/com/google/gerrit/extensions/common/MergeInput.java
+++ b/java/com/google/gerrit/extensions/common/MergeInput.java
@@ -35,4 +35,12 @@
* @see org.eclipse.jgit.merge.MergeStrategy
*/
public String strategy;
+
+ /**
+ * Whether the creation of the merge should succeed if there are conflicts.
+ *
+ * <p>If there are conflicts the file contents of the created change contain git conflict markers
+ * to indicate the conflicts.
+ */
+ public boolean allowConflicts;
}
diff --git a/java/com/google/gerrit/extensions/common/ServerInfo.java b/java/com/google/gerrit/extensions/common/ServerInfo.java
index 555c363..bc7fcfd 100644
--- a/java/com/google/gerrit/extensions/common/ServerInfo.java
+++ b/java/com/google/gerrit/extensions/common/ServerInfo.java
@@ -21,7 +21,6 @@
public ChangeConfigInfo change;
public DownloadInfo download;
public GerritInfo gerrit;
- public IndexConfigInfo index;
public Boolean noteDbEnabled;
public PluginConfigInfo plugin;
public SshdInfo sshd;
diff --git a/java/com/google/gerrit/extensions/restapi/MethodNotAllowedException.java b/java/com/google/gerrit/extensions/restapi/MethodNotAllowedException.java
index 2d59f27..8422b61 100644
--- a/java/com/google/gerrit/extensions/restapi/MethodNotAllowedException.java
+++ b/java/com/google/gerrit/extensions/restapi/MethodNotAllowedException.java
@@ -22,4 +22,12 @@
public MethodNotAllowedException(String msg) {
super(msg);
}
+
+ /**
+ * @param msg error text for client describing why the method is not allowed.
+ * @param cause reason for the method not being allowed.
+ */
+ public MethodNotAllowedException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
}
diff --git a/java/com/google/gerrit/extensions/restapi/NotImplementedException.java b/java/com/google/gerrit/extensions/restapi/NotImplementedException.java
index 566159d..d74986a7 100644
--- a/java/com/google/gerrit/extensions/restapi/NotImplementedException.java
+++ b/java/com/google/gerrit/extensions/restapi/NotImplementedException.java
@@ -25,4 +25,8 @@
public NotImplementedException(String message) {
super(message);
}
+
+ public NotImplementedException(String message, Throwable cause) {
+ super(message, cause);
+ }
}
diff --git a/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java b/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java
index 75cf713..270a040 100644
--- a/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java
+++ b/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java
@@ -34,6 +34,10 @@
super("Not found: " + id.get());
}
+ public ResourceNotFoundException(IdString id, Throwable cause) {
+ super("Not found: " + id.get(), cause);
+ }
+
public ResourceNotFoundException caching(CacheControl c) {
setCaching(c);
return this;
diff --git a/java/com/google/gerrit/extensions/validators/CommentForValidation.java b/java/com/google/gerrit/extensions/validators/CommentForValidation.java
index 51ae5ae..9b91dff 100644
--- a/java/com/google/gerrit/extensions/validators/CommentForValidation.java
+++ b/java/com/google/gerrit/extensions/validators/CommentForValidation.java
@@ -17,13 +17,21 @@
import com.google.auto.value.AutoValue;
/**
- * Holds a comment's text and {@link CommentType} in order to pass it to a validation plugin.
+ * Holds a comment's text and some metadata in order to pass it to a validation plugin.
*
* @see CommentValidator
*/
@AutoValue
public abstract class CommentForValidation {
+ /** The creator of the comment. */
+ public enum CommentSource {
+ /** A regular user comment. */
+ HUMAN,
+ /** A robot comment. */
+ ROBOT
+ }
+
/** The type of comment. */
public enum CommentType {
/** A regular (inline) comment. */
@@ -34,14 +42,27 @@
CHANGE_MESSAGE
}
- public static CommentForValidation create(CommentType type, String text) {
- return new AutoValue_CommentForValidation(type, text);
+ public static CommentForValidation create(
+ CommentSource source, CommentType type, String text, long size) {
+ return new AutoValue_CommentForValidation(source, type, text, size);
}
+ public abstract CommentSource getSource();
+
public abstract CommentType getType();
+ /**
+ * Returns the comment text. Note that especially for robot comments the total size may be
+ * significantly larger and should be determined by using {@link #getApproximateSize()}.
+ */
public abstract String getText();
+ /**
+ * Returns this instance's approximate size in bytes for the purpose of applying size limits. For
+ * robot comments this may be significantly larger than the size of the comment text.
+ */
+ public abstract long getApproximateSize();
+
public CommentValidationFailure failValidation(String message) {
return CommentValidationFailure.create(this, message);
}
diff --git a/java/com/google/gerrit/extensions/validators/CommentValidationContext.java b/java/com/google/gerrit/extensions/validators/CommentValidationContext.java
index 1cb00e3..db08058 100644
--- a/java/com/google/gerrit/extensions/validators/CommentValidationContext.java
+++ b/java/com/google/gerrit/extensions/validators/CommentValidationContext.java
@@ -34,16 +34,7 @@
/** Returns the project the comment is being added to. */
public abstract String getProject();
- public static Builder builder() {
- return new AutoValue_CommentValidationContext.Builder();
- }
-
- @AutoValue.Builder
- public abstract static class Builder {
- public abstract Builder changeId(int value);
-
- public abstract Builder project(String value);
-
- public abstract CommentValidationContext build();
+ public static CommentValidationContext create(int changeId, String project) {
+ return new AutoValue_CommentValidationContext(changeId, project);
}
}
diff --git a/java/com/google/gerrit/extensions/validators/CommentValidator.java b/java/com/google/gerrit/extensions/validators/CommentValidator.java
index ba73e46..2dfd6e2 100644
--- a/java/com/google/gerrit/extensions/validators/CommentValidator.java
+++ b/java/com/google/gerrit/extensions/validators/CommentValidator.java
@@ -25,7 +25,10 @@
public interface CommentValidator {
/**
- * Validate the specified comments.
+ * Validate the specified comments. This method will be called with all new comments that need to
+ * be validated. This allows validators to statelessly count the new comments. Note that the
+ * method may be called separately for texts that are not comments, but similar in nature and also
+ * subject to size limits, such as a change message.
*
* @return An empty list if all comments are valid, or else a list of validation failures.
*/
diff --git a/java/com/google/gerrit/httpd/HtmlDomUtil.java b/java/com/google/gerrit/httpd/HtmlDomUtil.java
index 25ae71c..57f2664 100644
--- a/java/com/google/gerrit/httpd/HtmlDomUtil.java
+++ b/java/com/google/gerrit/httpd/HtmlDomUtil.java
@@ -131,7 +131,7 @@
try {
d = newBuilder().newDocument();
} catch (ParserConfigurationException e) {
- throw new IOException("Cannot clone document");
+ throw new IOException("Cannot clone document", e);
}
Node n = d.importNode(doc.getDocumentElement(), true);
d.appendChild(n);
diff --git a/java/com/google/gerrit/httpd/ProjectOAuthFilter.java b/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
index 693232f..dab36c4 100644
--- a/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
+++ b/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
@@ -195,7 +195,7 @@
defaultAuthPlugin = loginProvider.getPluginName();
defaultAuthProvider = loginProvider.getExportName();
} catch (NoSuchElementException e) {
- throw new ServletException("No OAuth login provider installed");
+ throw new ServletException("No OAuth login provider installed", e);
} catch (IllegalArgumentException e) {
// multiple providers found => do not pick any
}
diff --git a/java/com/google/gerrit/httpd/plugins/PluginServletContext.java b/java/com/google/gerrit/httpd/plugins/PluginServletContext.java
index 6a8ef32..40083e4 100644
--- a/java/com/google/gerrit/httpd/plugins/PluginServletContext.java
+++ b/java/com/google/gerrit/httpd/plugins/PluginServletContext.java
@@ -61,9 +61,11 @@
try {
handler = API.class.getDeclaredMethod(method.getName(), method.getParameterTypes());
} catch (NoSuchMethodException e) {
- throw new NoSuchMethodError(
+ String msg =
String.format(
- "%s does not implement %s", PluginServletContext.class, method.toGenericString()));
+ "%s does not implement %s", PluginServletContext.class, method.toGenericString());
+ logger.atSevere().withCause(e).log(msg);
+ throw new NoSuchMethodError(msg);
}
return handler.invoke(this, args);
}
diff --git a/java/com/google/gerrit/httpd/raw/BazelBuild.java b/java/com/google/gerrit/httpd/raw/BazelBuild.java
index a13078d..7677e97 100644
--- a/java/com/google/gerrit/httpd/raw/BazelBuild.java
+++ b/java/com/google/gerrit/httpd/raw/BazelBuild.java
@@ -63,8 +63,9 @@
try {
status = rebuild.waitFor();
} catch (InterruptedException e) {
- throw new InterruptedIOException(
- "interrupted waiting for: " + Joiner.on(' ').join(proc.command()));
+ String msg = "interrupted waiting for: " + Joiner.on(' ').join(proc.command());
+ logger.atSevere().withCause(e).log(msg);
+ throw new InterruptedIOException(msg);
}
if (status != 0) {
logger.atWarning().log("build failed: %s", new String(out, UTF_8));
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 722955c..1fd0f82 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -697,8 +697,9 @@
if (status.isPresent()) {
responseBytes = reply(req, res, e, status.get(), getUserMessages(traceContext, e));
+ } else {
+ responseBytes = replyInternalServerError(req, res, e, getUserMessages(traceContext, e));
}
- responseBytes = replyInternalServerError(req, res, e, getUserMessages(traceContext, e));
}
} finally {
String metric = getViewName(viewData);
@@ -1182,7 +1183,7 @@
try {
first = json.peek();
} catch (EOFException e) {
- throw new BadRequestException("Expected JSON object");
+ throw new BadRequestException("Expected JSON object", e);
}
if (first == JsonToken.STRING) {
return parseString(json.nextString(), type);
diff --git a/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 3b277dd..375967b 100644
--- a/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -55,6 +55,7 @@
import com.google.gerrit.index.query.ResultSet;
import com.google.gerrit.proto.Protos;
import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.change.MergeabilityComputationBehavior;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.IndexExecutor;
@@ -182,7 +183,7 @@
this.changeDataFactory = changeDataFactory;
this.schema = schema;
this.skipFields =
- cfg.getBoolean("index", "change", "indexMergeable", true)
+ MergeabilityComputationBehavior.fromConfig(cfg).includeInIndex()
? ImmutableSet.of()
: ImmutableSet.of(ChangeField.MERGEABLE.getName());
diff --git a/java/com/google/gerrit/lucene/QueryBuilder.java b/java/com/google/gerrit/lucene/QueryBuilder.java
index e8ef95f..7d82bf5 100644
--- a/java/com/google/gerrit/lucene/QueryBuilder.java
+++ b/java/com/google/gerrit/lucene/QueryBuilder.java
@@ -208,7 +208,7 @@
// subclasses of OperatorPredicate.
value = Integer.parseInt(p.getValue());
} catch (NumberFormatException e) {
- throw new QueryParseException("not an integer: " + p.getValue());
+ throw new QueryParseException("not an integer: " + p.getValue(), e);
}
return intTermQuery.get(p.getField().getName(), value);
}
diff --git a/java/com/google/gerrit/pgm/BUILD b/java/com/google/gerrit/pgm/BUILD
index 03b7c3d..aa4df82 100644
--- a/java/com/google/gerrit/pgm/BUILD
+++ b/java/com/google/gerrit/pgm/BUILD
@@ -23,7 +23,7 @@
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/lucene",
"//java/com/google/gerrit/metrics/dropwizard",
- "//java/com/google/gerrit/pgm/http",
+ "//java/com/google/gerrit/pgm/http/jetty",
"//java/com/google/gerrit/pgm/init",
"//java/com/google/gerrit/pgm/init/api",
"//java/com/google/gerrit/pgm/util",
diff --git a/java/com/google/gerrit/pgm/DeleteZombieDrafts.java b/java/com/google/gerrit/pgm/DeleteZombieDrafts.java
new file mode 100644
index 0000000..90a60c1
--- /dev/null
+++ b/java/com/google/gerrit/pgm/DeleteZombieDrafts.java
@@ -0,0 +1,78 @@
+// Copyright (C) 2020 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.pgm;
+
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.util.SiteProgram;
+import com.google.gerrit.server.config.GerritServerConfigModule;
+import com.google.gerrit.server.config.SitePath;
+import com.google.gerrit.server.notedb.DeleteZombieCommentsRefs;
+import com.google.gerrit.server.notedb.DeleteZombieCommentsRefs.Factory;
+import com.google.gerrit.server.schema.SchemaModule;
+import com.google.gerrit.server.securestore.SecureStoreClassName;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+import com.google.inject.util.Providers;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.kohsuke.args4j.Option;
+
+/**
+ * A pgm which can be used to clean zombie draft comments refs More context in
+ * https://gerrit-review.googlesource.com/c/gerrit/+/246233
+ *
+ * <p>The implementation is in {@link DeleteZombieCommentsRefs}
+ */
+public class DeleteZombieDrafts extends SiteProgram {
+ @Option(
+ name = "--cleanup-percentage",
+ aliases = {"-c"},
+ usage = "Clean a % of zombie drafts (default is 100%)")
+ private Integer cleanupPercentage = 100;
+
+ @Override
+ public int run() throws IOException {
+ mustHaveValidSite();
+ Injector sysInjector = getSysInjector();
+ DeleteZombieCommentsRefs cleanup =
+ sysInjector.getInstance(Factory.class).create(cleanupPercentage);
+ cleanup.execute();
+ return 0;
+ }
+
+ private Injector getSysInjector() {
+ List<Module> modules = new ArrayList<>();
+ modules.add(
+ new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(Path.class).annotatedWith(SitePath.class).toInstance(getSitePath());
+ bind(ConsoleUI.class).toInstance(ConsoleUI.getInstance(false));
+ bind(String.class)
+ .annotatedWith(SecureStoreClassName.class)
+ .toProvider(Providers.of(getConfiguredSecureStoreClass()));
+ install(new FactoryModuleBuilder().build(DeleteZombieCommentsRefs.Factory.class));
+ }
+ });
+ modules.add(new GerritServerConfigModule());
+ modules.add(new SchemaModule());
+ return Guice.createInjector(modules);
+ }
+}
diff --git a/java/com/google/gerrit/pgm/http/BUILD b/java/com/google/gerrit/pgm/http/BUILD
deleted file mode 100644
index 34115ae..0000000
--- a/java/com/google/gerrit/pgm/http/BUILD
+++ /dev/null
@@ -1,7 +0,0 @@
-load("@rules_java//java:defs.bzl", "java_library")
-
-java_library(
- name = "http",
- visibility = ["//visibility:public"],
- exports = ["//java/com/google/gerrit/pgm/http/jetty"],
-)
diff --git a/java/com/google/gerrit/pgm/init/BaseInit.java b/java/com/google/gerrit/pgm/init/BaseInit.java
index 7fbf2d7..62ff66a 100644
--- a/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -332,7 +332,7 @@
IoUtil.loadJARs(secureStoreLib);
return new SecureStoreInitData(secureStoreLib, secureStores.get(0));
} catch (IOException e) {
- throw new InvalidSecureStoreException(String.format("%s is not a valid jar", secureStore));
+ throw new InvalidSecureStoreException(String.format("%s is not a valid jar", secureStore), e);
}
}
diff --git a/java/com/google/gerrit/pgm/init/InitHttpd.java b/java/com/google/gerrit/pgm/init/InitHttpd.java
index 027a3ac..b9fe874 100644
--- a/java/com/google/gerrit/pgm/init/InitHttpd.java
+++ b/java/com/google/gerrit/pgm/init/InitHttpd.java
@@ -169,7 +169,7 @@
uri = new URI(s + "://" + uri.getHost() + uri.getPath());
}
} catch (URISyntaxException e) {
- throw die("invalid httpd.listenUrl");
+ throw die("invalid httpd.listenUrl", e);
}
httpd.set("listenUrl", urlbuf.toString());
gerrit.string("Canonical URL", "canonicalWebUrl", uri.toString());
diff --git a/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
index 359321a..d9211a9 100644
--- a/java/com/google/gerrit/pgm/util/BatchProgramModule.java
+++ b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -57,6 +57,7 @@
import com.google.gerrit.server.extensions.events.EventUtil;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.extensions.events.RevisionCreated;
+import com.google.gerrit.server.extensions.events.WorkInProgressStateChanged;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.PureRevertCache;
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
@@ -177,6 +178,7 @@
bind(EventUtil.class).toProvider(Providers.of(null));
bind(GitReferenceUpdated.class).toInstance(GitReferenceUpdated.DISABLED);
bind(RevisionCreated.class).toInstance(RevisionCreated.DISABLED);
+ bind(WorkInProgressStateChanged.class).toInstance(WorkInProgressStateChanged.DISABLED);
bind(AccountVisibility.class).toProvider(AccountVisibilityProvider.class).in(SINGLETON);
}
}
diff --git a/java/com/google/gerrit/server/ApprovalInference.java b/java/com/google/gerrit/server/ApprovalInference.java
index 44b0529..a4c9f2a 100644
--- a/java/com/google/gerrit/server/ApprovalInference.java
+++ b/java/com/google/gerrit/server/ApprovalInference.java
@@ -101,6 +101,8 @@
return true;
} else if (type.isCopyAnyScore()) {
return true;
+ } else if (type.getCopyValues().contains(psa.value())) {
+ return true;
}
switch (kind) {
case MERGE_FIRST_PARENT_UPDATE:
diff --git a/java/com/google/gerrit/server/ApprovalsUtil.java b/java/com/google/gerrit/server/ApprovalsUtil.java
index 29a5748..58b601f 100644
--- a/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -331,7 +331,7 @@
forChange.check(new LabelPermission.WithValue(name, value));
} catch (AuthException e) {
throw new AuthException(
- String.format("applying label \"%s\": %d is restricted", name, value));
+ String.format("applying label \"%s\": %d is restricted", name, value), e);
}
}
}
diff --git a/java/com/google/gerrit/server/PublishCommentsOp.java b/java/com/google/gerrit/server/PublishCommentsOp.java
index 57d50fb..df57629 100644
--- a/java/com/google/gerrit/server/PublishCommentsOp.java
+++ b/java/com/google/gerrit/server/PublishCommentsOp.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server;
-import com.google.common.flogger.FluentLogger;
+import com.google.common.collect.ImmutableMap;
import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.entities.Comment;
import com.google.gerrit.entities.PatchSet;
@@ -45,8 +45,6 @@
* necessary event for this.
*/
public class PublishCommentsOp implements BatchUpdateOp {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
private final PatchSetUtil psUtil;
private final ChangeNotes.Factory changeNotesFactory;
private final ChangeMessagesUtil cmUtil;
@@ -118,18 +116,14 @@
if (notify.shouldNotify()) {
email.create(notify, changeNotes, ps, user, message, comments, null, labelDelta).sendAsync();
}
- try {
- commentAdded.fire(
- changeNotes.getChange(),
- ps,
- ctx.getAccount(),
- message.getMessage(),
- null,
- null,
- ctx.getWhen());
- } catch (Exception e) {
- logger.atWarning().withCause(e).log("comment-added event invocation failed");
- }
+ commentAdded.fire(
+ changeNotes.getChange(),
+ ps,
+ ctx.getAccount(),
+ message.getMessage(),
+ ImmutableMap.of(),
+ ImmutableMap.of(),
+ ctx.getWhen());
}
private boolean insertMessage(ChangeContext ctx, ChangeUpdate changeUpdate) {
diff --git a/java/com/google/gerrit/server/account/AccountManager.java b/java/com/google/gerrit/server/account/AccountManager.java
index 8137b92..6a16a53 100644
--- a/java/com/google/gerrit/server/account/AccountManager.java
+++ b/java/com/google/gerrit/server/account/AccountManager.java
@@ -391,7 +391,7 @@
try {
groupsUpdate.updateGroup(groupUuid, groupUpdate);
} catch (NoSuchGroupException e) {
- throw new AccountException(String.format("Group %s not found", groupUuid));
+ throw new AccountException(String.format("Group %s not found", groupUuid), e);
}
}
diff --git a/java/com/google/gerrit/server/account/Accounts.java b/java/com/google/gerrit/server/account/Accounts.java
index 8136631..7a6eb7a 100644
--- a/java/com/google/gerrit/server/account/Accounts.java
+++ b/java/com/google/gerrit/server/account/Accounts.java
@@ -21,6 +21,9 @@
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -45,12 +48,23 @@
private final GitRepositoryManager repoManager;
private final AllUsersName allUsersName;
private final ExternalIds externalIds;
+ private final Timer0 readSingleLatency;
@Inject
- Accounts(GitRepositoryManager repoManager, AllUsersName allUsersName, ExternalIds externalIds) {
+ Accounts(
+ GitRepositoryManager repoManager,
+ AllUsersName allUsersName,
+ ExternalIds externalIds,
+ MetricMaker metricMaker) {
this.repoManager = repoManager;
this.allUsersName = allUsersName;
this.externalIds = externalIds;
+ this.readSingleLatency =
+ metricMaker.newTimer(
+ "notedb/read_single_account_config_latency",
+ new Description("Latency for reading a single account config.")
+ .setCumulative()
+ .setUnit(Description.Units.MILLISECONDS));
}
public Optional<AccountState> get(Account.Id accountId)
@@ -133,8 +147,11 @@
private Optional<AccountState> read(Repository allUsersRepository, Account.Id accountId)
throws IOException, ConfigInvalidException {
- return AccountState.fromAccountConfig(
- externalIds, new AccountConfig(accountId, allUsersName, allUsersRepository).load());
+ AccountConfig cfg;
+ try (Timer0.Context ignored = readSingleLatency.start()) {
+ cfg = new AccountConfig(accountId, allUsersName, allUsersRepository).load();
+ }
+ return AccountState.fromAccountConfig(externalIds, cfg);
}
public static Stream<Account.Id> readUserRefs(Repository repo) throws IOException {
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalId.java b/java/com/google/gerrit/server/account/externalids/ExternalId.java
index b18b27b..2d501ad 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalId.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalId.java
@@ -24,6 +24,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
+import com.google.common.flogger.FluentLogger;
import com.google.common.hash.Hashing;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
@@ -43,6 +44,8 @@
@AutoValue
public abstract class ExternalId implements Serializable {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
// If these regular expressions are modified the same modifications should be done to the
// corresponding regular expressions in the
// com.google.gerrit.client.account.UsernameField class.
@@ -391,11 +394,12 @@
}
return accountId;
} catch (IllegalArgumentException e) {
- throw invalidConfig(
- noteId,
+ String msg =
String.format(
"Value %s for '%s.%s.%s' is invalid, expected account ID",
- accountIdStr, EXTERNAL_ID_SECTION, externalIdKeyStr, ACCOUNT_ID_KEY));
+ accountIdStr, EXTERNAL_ID_SECTION, externalIdKeyStr, ACCOUNT_ID_KEY);
+ logger.atSevere().withCause(e).log(msg);
+ throw invalidConfig(noteId, msg);
}
}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java b/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
index 6334265..c055313 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
@@ -68,6 +68,7 @@
private final AllUsersName allUsersName;
private boolean failOnLoad = false;
private final Timer0 readAllLatency;
+ private final Timer0 readSingleLatency;
@Inject
ExternalIdReader(
@@ -80,6 +81,12 @@
new Description("Latency for reading all external IDs from NoteDb.")
.setCumulative()
.setUnit(Units.MILLISECONDS));
+ this.readSingleLatency =
+ metricMaker.newTimer(
+ "notedb/read_single_external_id_latency",
+ new Description("Latency for reading a single external ID from NoteDb.")
+ .setCumulative()
+ .setUnit(Units.MILLISECONDS));
}
@VisibleForTesting
@@ -126,7 +133,8 @@
Optional<ExternalId> get(ExternalId.Key key) throws IOException, ConfigInvalidException {
checkReadEnabled();
- try (Repository repo = repoManager.openRepository(allUsersName)) {
+ try (Timer0.Context ctx = readSingleLatency.start();
+ Repository repo = repoManager.openRepository(allUsersName)) {
return ExternalIdNotes.loadReadOnly(allUsersName, repo).get(key);
}
}
@@ -136,7 +144,8 @@
throws IOException, ConfigInvalidException {
checkReadEnabled();
- try (Repository repo = repoManager.openRepository(allUsersName)) {
+ try (Timer0.Context ctx = readSingleLatency.start();
+ Repository repo = repoManager.openRepository(allUsersName)) {
return ExternalIdNotes.loadReadOnly(allUsersName, repo, rev).get(key);
}
}
diff --git a/java/com/google/gerrit/server/api/changes/ChangesImpl.java b/java/com/google/gerrit/server/api/changes/ChangesImpl.java
index b9635fb..d6ef61c 100644
--- a/java/com/google/gerrit/server/api/changes/ChangesImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangesImpl.java
@@ -99,6 +99,15 @@
}
@Override
+ public ChangeInfo createAsInfo(ChangeInput in) throws RestApiException {
+ try {
+ return createChange.apply(TopLevelResource.INSTANCE, in).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot create change", e);
+ }
+ }
+
+ @Override
public QueryRequest query() {
return new QueryRequest() {
@Override
diff --git a/java/com/google/gerrit/server/args4j/AccountIdHandler.java b/java/com/google/gerrit/server/args4j/AccountIdHandler.java
index 4d0af53..73a970b 100644
--- a/java/com/google/gerrit/server/args4j/AccountIdHandler.java
+++ b/java/com/google/gerrit/server/args4j/AccountIdHandler.java
@@ -16,6 +16,7 @@
import static com.google.gerrit.util.cli.Localizable.localizable;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.AuthType;
@@ -38,6 +39,8 @@
import org.kohsuke.args4j.spi.Setter;
public class AccountIdHandler extends OptionHandler<Account.Id> {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
private final AccountResolver accountResolver;
private final AccountManager accountManager;
private final AuthType authType;
@@ -78,11 +81,15 @@
case OPENID:
case OPENID_SSO:
default:
- throw new CmdLineException(owner, localizable("user \"%s\" not found"), token);
+ String msg = "user \"%s\" not found";
+ logger.atSevere().withCause(e).log(msg, token);
+ throw new CmdLineException(owner, localizable(msg), token);
}
}
} catch (StorageException e) {
- throw new CmdLineException(owner, localizable("database is down"));
+ String msg = "database is down";
+ logger.atSevere().withCause(e).log(msg);
+ throw new CmdLineException(owner, localizable(msg));
} catch (IOException e) {
throw new CmdLineException(owner, "Failed to load account", e);
} catch (ConfigInvalidException e) {
@@ -102,7 +109,9 @@
req.setSkipAuthentication(true);
return accountManager.authenticate(req).getAccountId();
} catch (AccountException e) {
- throw new CmdLineException(owner, localizable("user \"%s\" not found"), user);
+ String msg = "user \"%s\" not found";
+ logger.atSevere().withCause(e).log(msg, user);
+ throw new CmdLineException(owner, localizable(msg), user);
}
}
diff --git a/java/com/google/gerrit/server/args4j/ChangeIdHandler.java b/java/com/google/gerrit/server/args4j/ChangeIdHandler.java
index 9c14aca..ddcd4db 100644
--- a/java/com/google/gerrit/server/args4j/ChangeIdHandler.java
+++ b/java/com/google/gerrit/server/args4j/ChangeIdHandler.java
@@ -66,7 +66,7 @@
return 1;
}
} catch (IllegalArgumentException e) {
- throw new CmdLineException(owner, localizable("Change-Id is not valid"));
+ throw new CmdLineException(owner, localizable("Change-Id is not valid: %s"), e.getMessage());
} catch (StorageException e) {
throw new CmdLineException(owner, localizable("Database error: %s"), e.getMessage());
}
diff --git a/java/com/google/gerrit/server/args4j/PatchSetIdHandler.java b/java/com/google/gerrit/server/args4j/PatchSetIdHandler.java
index 5016363..8b7cbd6 100644
--- a/java/com/google/gerrit/server/args4j/PatchSetIdHandler.java
+++ b/java/com/google/gerrit/server/args4j/PatchSetIdHandler.java
@@ -43,7 +43,8 @@
try {
id = PatchSet.Id.parse(token);
} catch (IllegalArgumentException e) {
- throw new CmdLineException(owner, localizable("\"%s\" is not a valid patch set"), token);
+ throw new CmdLineException(
+ owner, localizable("\"%s\" is not a valid patch set: %s"), token, e.getMessage());
}
setter.addValue(id);
diff --git a/java/com/google/gerrit/server/args4j/ProjectHandler.java b/java/com/google/gerrit/server/args4j/ProjectHandler.java
index abb6a1f..61dbd2c 100644
--- a/java/com/google/gerrit/server/args4j/ProjectHandler.java
+++ b/java/com/google/gerrit/server/args4j/ProjectHandler.java
@@ -89,7 +89,7 @@
permissionBackend.currentUser().project(nameKey).check(permissionToCheck);
} catch (AuthException e) {
throw new CmdLineException(
- owner, localizable(new NoSuchProjectException(nameKey).getMessage()));
+ owner, localizable(new NoSuchProjectException(nameKey, e).getMessage()));
} catch (PermissionBackendException | IOException e) {
logger.atWarning().withCause(e).log("Cannot load project %s", nameWithoutSuffix);
throw new CmdLineException(
diff --git a/java/com/google/gerrit/server/cache/serialize/IntegerCacheSerializer.java b/java/com/google/gerrit/server/cache/serialize/IntegerCacheSerializer.java
index 4494454..aca9b05 100644
--- a/java/com/google/gerrit/server/cache/serialize/IntegerCacheSerializer.java
+++ b/java/com/google/gerrit/server/cache/serialize/IntegerCacheSerializer.java
@@ -37,7 +37,7 @@
cout.writeInt32NoTag(requireNonNull(object));
cout.flush();
} catch (IOException e) {
- throw new IllegalStateException("Failed to serialize int");
+ throw new IllegalStateException("Failed to serialize int", e);
}
int n = cout.getTotalBytesWritten();
return n == buf.length ? buf : Arrays.copyOfRange(buf, 0, n);
@@ -50,7 +50,7 @@
try {
ret = cin.readRawVarint32();
} catch (IOException e) {
- throw new IllegalArgumentException("Failed to deserialize int");
+ throw new IllegalArgumentException("Failed to deserialize int", e);
}
int n = cin.getTotalBytesRead();
if (n != in.length) {
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 974cb47..70e7967 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -29,7 +29,6 @@
import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWED;
import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWER_UPDATES;
import static com.google.gerrit.extensions.client.ListChangesOption.SKIP_DIFFSTAT;
-import static com.google.gerrit.extensions.client.ListChangesOption.SKIP_MERGEABLE;
import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE;
import static com.google.gerrit.extensions.client.ListChangesOption.TRACKING_IDS;
import static com.google.gerrit.server.ChangeMessagesUtil.createChangeMessageInfo;
@@ -219,7 +218,7 @@
private final Metrics metrics;
private final RevisionJson revisionJson;
private final Optional<PluginDefinedAttributesFactory> pluginDefinedAttributesFactory;
- private final boolean excludeMergeableInChangeInfo;
+ private final boolean includeMergeable;
private final boolean lazyLoad;
private AccountLoader accountLoader;
@@ -257,8 +256,7 @@
this.metrics = metrics;
this.revisionJson = revisionJsonFactory.create(options);
this.options = Sets.immutableEnumSet(options);
- this.excludeMergeableInChangeInfo =
- cfg.getBoolean("change", "api", "excludeMergeableInChangeInfo", false);
+ this.includeMergeable = MergeabilityComputationBehavior.fromConfig(cfg).includeInApi();
this.lazyLoad = containsAnyOf(this.options, REQUIRE_LAZY_LOAD);
this.pluginDefinedAttributesFactory = pluginDefinedAttributesFactory;
@@ -525,7 +523,7 @@
if (str.isOk()) {
out.submitType = str.type;
}
- if (!excludeMergeableInChangeInfo && !has(SKIP_MERGEABLE)) {
+ if (includeMergeable) {
out.mergeable = cd.isMergeable();
}
if (has(SUBMITTABLE)) {
diff --git a/java/com/google/gerrit/server/change/MergeabilityComputationBehavior.java b/java/com/google/gerrit/server/change/MergeabilityComputationBehavior.java
new file mode 100644
index 0000000..26e7a89
--- /dev/null
+++ b/java/com/google/gerrit/server/change/MergeabilityComputationBehavior.java
@@ -0,0 +1,58 @@
+// Copyright (C) 2020 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 org.eclipse.jgit.lib.Config;
+
+/**
+ * State that is used to decide if {@code mergeable} should be included in the REST API or the
+ * change index.
+ *
+ * <p>Computing mergeability of a change is an expensive operation - especially for Gerrit
+ * installations with a large number of open changes or large repositories. Therefore, we want to
+ * control when this computation is performed.
+ */
+public enum MergeabilityComputationBehavior {
+ NEVER(false, false),
+ REF_UPDATED_AND_CHANGE_REINDEX(true, false),
+ API_REF_UPDATED_AND_CHANGE_REINDEX(true, true);
+
+ private final boolean includeInIndex;
+ private final boolean includeInApi;
+
+ MergeabilityComputationBehavior(boolean includeInIndex, boolean includeInApi) {
+ this.includeInIndex = includeInIndex;
+ this.includeInApi = includeInApi;
+ }
+
+ /** Returns a {@link MergeabilityComputationBehavior} constructed from a Gerrit server config. */
+ public static MergeabilityComputationBehavior fromConfig(Config cfg) {
+ return cfg.getEnum(
+ "change",
+ null,
+ "mergeabilityComputationBehavior",
+ MergeabilityComputationBehavior.API_REF_UPDATED_AND_CHANGE_REINDEX);
+ }
+
+ /** Whether {@code mergeable} should be included in the change API. */
+ public boolean includeInApi() {
+ return includeInApi;
+ }
+
+ /** Whether {@code mergeable} should be included in the change index. */
+ public boolean includeInIndex() {
+ return includeInIndex;
+ }
+}
diff --git a/java/com/google/gerrit/server/change/PatchSetInserter.java b/java/com/google/gerrit/server/change/PatchSetInserter.java
index 71c54b1..d64854c 100644
--- a/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -34,6 +34,7 @@
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.extensions.events.RevisionCreated;
+import com.google.gerrit.server.extensions.events.WorkInProgressStateChanged;
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
@@ -74,6 +75,7 @@
private final ApprovalsUtil approvalsUtil;
private final ChangeMessagesUtil cmUtil;
private final PatchSetUtil psUtil;
+ private final WorkInProgressStateChanged wipStateChanged;
// Assisted-injected fields.
private final PatchSet.Id psId;
@@ -86,6 +88,7 @@
// Fields exposed as setters.
private String message;
private String description;
+ private Boolean workInProgress;
private boolean validate = true;
private boolean checkAddPatchSetPermission = true;
private List<String> groups = Collections.emptyList();
@@ -99,6 +102,7 @@
private PatchSetInfo patchSetInfo;
private ChangeMessage changeMessage;
private ReviewerSet oldReviewers;
+ private boolean oldWorkInProgressState;
@Inject
public PatchSetInserter(
@@ -111,6 +115,7 @@
PatchSetUtil psUtil,
RevisionCreated revisionCreated,
ProjectCache projectCache,
+ WorkInProgressStateChanged wipStateChanged,
@Assisted ChangeNotes notes,
@Assisted PatchSet.Id psId,
@Assisted ObjectId commitId) {
@@ -123,6 +128,7 @@
this.psUtil = psUtil;
this.revisionCreated = revisionCreated;
this.projectCache = projectCache;
+ this.wipStateChanged = wipStateChanged;
this.origNotes = notes;
this.psId = psId;
@@ -143,6 +149,11 @@
return this;
}
+ public PatchSetInserter setWorkInProgress(boolean workInProgress) {
+ this.workInProgress = workInProgress;
+ return this;
+ }
+
public PatchSetInserter setValidate(boolean validate) {
this.validate = validate;
return this;
@@ -230,6 +241,13 @@
changeMessage.setMessage(message);
}
+ oldWorkInProgressState = change.isWorkInProgress();
+ if (workInProgress != null) {
+ change.setWorkInProgress(workInProgress);
+ change.setReviewStarted(!workInProgress);
+ update.setWorkInProgress(workInProgress);
+ }
+
patchSetInfo =
patchSetInfoFactory.get(ctx.getRevWalk(), ctx.getRevWalk().parseCommit(commitId), psId);
if (!allowClosed) {
@@ -265,6 +283,10 @@
if (fireRevisionCreated) {
revisionCreated.fire(change, patchSet, ctx.getAccount(), ctx.getWhen(), notify);
}
+
+ if (workInProgress != null && oldWorkInProgressState != workInProgress) {
+ wipStateChanged.fire(change, patchSet, ctx.getAccount(), ctx.getWhen());
+ }
}
private void validate(RepoContext ctx)
diff --git a/java/com/google/gerrit/server/change/PureRevert.java b/java/com/google/gerrit/server/change/PureRevert.java
index f66d9bc..cb632dc 100644
--- a/java/com/google/gerrit/server/change/PureRevert.java
+++ b/java/com/google/gerrit/server/change/PureRevert.java
@@ -50,7 +50,7 @@
try {
claimedOriginalObjectId = ObjectId.fromString(claimedOriginal.get());
} catch (InvalidObjectIdException e) {
- throw new BadRequestException("invalid object ID");
+ throw new BadRequestException("invalid object ID", e);
}
return pureRevertCache.isPureRevert(
diff --git a/java/com/google/gerrit/server/config/ChangeCleanupConfig.java b/java/com/google/gerrit/server/config/ChangeCleanupConfig.java
index afd159c..bd51fc7 100644
--- a/java/com/google/gerrit/server/config/ChangeCleanupConfig.java
+++ b/java/com/google/gerrit/server/config/ChangeCleanupConfig.java
@@ -17,6 +17,7 @@
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.server.change.MergeabilityComputationBehavior;
import com.google.gerrit.server.config.ScheduleConfig.Schedule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -51,7 +52,7 @@
this.urlFormatter = urlFormatter;
schedule = ScheduleConfig.createSchedule(cfg, SECTION);
abandonAfter = readAbandonAfter(cfg);
- boolean indexMergeable = cfg.getBoolean("index", "change", "indexMergeable", true);
+ boolean indexMergeable = MergeabilityComputationBehavior.fromConfig(cfg).includeInIndex();
if (!indexMergeable) {
if (!readAbandonIfMergeable(cfg)) {
logger.atWarning().log(
diff --git a/java/com/google/gerrit/server/config/ConfigUtil.java b/java/com/google/gerrit/server/config/ConfigUtil.java
index f476adf..43c05e0 100644
--- a/java/com/google/gerrit/server/config/ConfigUtil.java
+++ b/java/com/google/gerrit/server/config/ConfigUtil.java
@@ -30,7 +30,6 @@
import org.eclipse.jgit.lib.Config;
public class ConfigUtil {
-
@SuppressWarnings("unchecked")
private static <T> T[] allValuesOf(T defaultValue) {
try {
@@ -182,7 +181,7 @@
try {
return getTimeUnit(s, defaultValue, wantUnit);
} catch (IllegalArgumentException notTime) {
- throw notTimeUnit(section, subsection, setting, valueString);
+ throw notTimeUnit(section, subsection, setting, valueString, notTime);
}
}
@@ -250,7 +249,7 @@
try {
return wantUnit.convert(Long.parseLong(digits) * inputMul, inputUnit);
} catch (NumberFormatException nfe) {
- throw notTimeUnit(valueString);
+ throw notTimeUnit(valueString, nfe);
}
}
@@ -421,13 +420,21 @@
}
private static IllegalArgumentException notTimeUnit(
- final String section,
- final String subsection,
- final String setting,
- final String valueString) {
- return new IllegalArgumentException(
- "Invalid time unit value: "
- + section
+ String section, String subsection, String setting, String valueString, Throwable why) {
+ return notTimeUnit(
+ section
+ + (subsection != null ? "." + subsection : "")
+ + "."
+ + setting
+ + " = "
+ + valueString,
+ why);
+ }
+
+ private static IllegalArgumentException notTimeUnit(
+ String section, String subsection, String setting, String valueString) {
+ return notTimeUnit(
+ section
+ (subsection != null ? "." + subsection : "")
+ "."
+ setting
@@ -439,5 +446,9 @@
return new IllegalArgumentException("Invalid time unit value: " + val);
}
+ private static IllegalArgumentException notTimeUnit(String val, Throwable why) {
+ return new IllegalArgumentException("Invalid time unit value: " + val, why);
+ }
+
private ConfigUtil() {}
}
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 7df3902..012e4cd 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -128,7 +128,8 @@
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.receive.ReceiveCommitsModule;
-import com.google.gerrit.server.git.validators.CommentLimitsValidator;
+import com.google.gerrit.server.git.validators.CommentCountValidator;
+import com.google.gerrit.server.git.validators.CommentSizeValidator;
import com.google.gerrit.server.git.validators.CommitValidationListener;
import com.google.gerrit.server.git.validators.MergeValidationListener;
import com.google.gerrit.server.git.validators.MergeValidators;
@@ -410,8 +411,11 @@
DynamicSet.setOf(binder(), UploadValidationListener.class);
bind(CommentValidator.class)
- .annotatedWith(Exports.named(CommentLimitsValidator.class.getSimpleName()))
- .to(CommentLimitsValidator.class);
+ .annotatedWith(Exports.named(CommentCountValidator.class.getSimpleName()))
+ .to(CommentCountValidator.class);
+ bind(CommentValidator.class)
+ .annotatedWith(Exports.named(CommentSizeValidator.class.getSimpleName()))
+ .to(CommentSizeValidator.class);
DynamicMap.mapOf(binder(), DynamicOptions.DynamicBean.class);
DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeOperatorFactory.class);
diff --git a/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java b/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java
index 359a3a8..06b244b 100644
--- a/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java
+++ b/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java
@@ -38,6 +38,12 @@
public class WorkInProgressStateChanged {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ public static final WorkInProgressStateChanged DISABLED =
+ new WorkInProgressStateChanged() {
+ @Override
+ public void fire(Change change, PatchSet patchSet, AccountState account, Timestamp when) {}
+ };
+
private final PluginSetContext<WorkInProgressStateChangedListener> listeners;
private final EventUtil util;
@@ -48,6 +54,11 @@
this.util = util;
}
+ private WorkInProgressStateChanged() {
+ this.listeners = null;
+ this.util = null;
+ }
+
public void fire(Change change, PatchSet patchSet, AccountState account, Timestamp when) {
if (listeners.isEmpty()) {
return;
diff --git a/java/com/google/gerrit/server/fixes/FixCalculator.java b/java/com/google/gerrit/server/fixes/FixCalculator.java
index 5d9d2c2..9ea628e 100644
--- a/java/com/google/gerrit/server/fixes/FixCalculator.java
+++ b/java/com/google/gerrit/server/fixes/FixCalculator.java
@@ -88,7 +88,68 @@
return String.format(
"(%s:%s - %s:%s)", range.startLine, range.startChar, range.endLine, range.endChar);
}
+ /*
+ Algorithm:
+ Input:
+ Original text (aka srcText)
+ Sorted list of replacements in ascending order, where each replacement has:
+ srcRange - part of the original text to be
+ replaced, inserted or deleted (see {@link Comment.Range} for details)
+ replacement - text to be set instead of srcRange
+ Replacement ranges must not intersect.
+ Output:
+ Final text (aka finalText)
+ List of Edit, where each Edit is an instance of {@link ReplaceEdit}
+ Each ReplaceEdit cover one or more lines in the original text
+ Each ReplaceEdit contains one or more Edit for intraline edits
+ See {@link ReplaceEdit} and {@link Edit} for details.
+ *
+ Note: The algorithm is implemented in this way to avoid string.replace operations.
+ It has complexity O(len(replacements) + max(len(originalText), len(finalText)) )
+
+ Main steps:
+ - set srcPos to start of the original text. It is like a cursor position in the original text.
+ - set dstPos to start of the final text. It is like a cursor position in the final text.
+ - the finalText initially empty
+
+ - for each replacement:
+ - append text between a previous and a current replacement to the finalText
+ (because replacements were sorted, this part of text can't be changed by
+ following replacements). I.e. append substring of srcText between srcPos
+ and replacement.srcRange.start to the finalText
+ Update srcPos and dstPos - set them at the end of appended text
+ (i.e. srcPos points to the position before replacement.srcRange.start,
+ dstPos points to the position where replacement.text should be inserted)
+ - set dstReplacementStart = dstPos
+ - append replacement.text to the finalText.
+ Update srcPos and dstPos accordingly (i.e. srcPos points to the position after
+ replacement.srcRange, dstPos points to the position in the finalText after
+ the appended replacement.text).
+ - set dstReplacementEnd = dstPos
+ - dstRange = (dstReplacementStart, dstReplacementEnd) - is the range in the finalText.
+ - srcRange = (replacement.Start, replacement.End) - is the range in the original text *
+
+ - If previously created ReplaceEdit ends on the same or previous line as srcRange.startLine,
+ then intraline edit is added to it (and ReplaceEdit endLine must be updated if needed);
+ srcRange and dstRange together is used to calculate intraline Edit
+ otherwise
+ create new ReplaceEdit and add intraline Edit to it
+ srcRange and dstRange together is used to calculate intraline Edit
+
+ - append text after the last replacements,
+ i.e. add part of srcText after srcPos to the finalText
+
+ - Return the finalText and all created ReplaceEdits
+
+ Implementation notes:
+ 1) The intraline Edits inside ReplaceEdit stores positions relative to ReplaceEdit start.
+ 2) srcPos and dstPos tracks current position as 3 numbers:
+ - line number
+ - column number
+ - textPos - absolute position from the start of the text. The textPos is used to calculate
+ relative positions of Edit inside ReplaceEdit
+ */
private static class ContentBuilder {
private static class FixRegion {
int startSrcLine;
diff --git a/java/com/google/gerrit/server/git/MergeUtil.java b/java/com/google/gerrit/server/git/MergeUtil.java
index 1dbf029..39fd103 100644
--- a/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/java/com/google/gerrit/server/git/MergeUtil.java
@@ -41,6 +41,7 @@
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.exceptions.InvalidMergeStrategyException;
+import com.google.gerrit.exceptions.MergeWithConflictsNotSupportedException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicSet;
@@ -424,15 +425,16 @@
return dc.writeTree(ins);
}
- public static RevCommit createMergeCommit(
+ public static CodeReviewCommit createMergeCommit(
ObjectInserter inserter,
Config repoConfig,
RevCommit mergeTip,
RevCommit originalCommit,
String mergeStrategy,
+ boolean allowConflicts,
PersonIdent committerIndent,
String commitMsg,
- RevWalk rw)
+ CodeReviewRevWalk rw)
throws IOException, MergeIdenticalTreeException, MergeConflictException,
InvalidMergeStrategyException {
@@ -443,26 +445,67 @@
}
Merger m = newMerger(inserter, repoConfig, mergeStrategy);
- if (m.merge(false, mergeTip, originalCommit)) {
- ObjectId tree = m.getResultTreeId();
- CommitBuilder mergeCommit = new CommitBuilder();
- mergeCommit.setTreeId(tree);
- mergeCommit.setParentIds(mergeTip, originalCommit);
- mergeCommit.setAuthor(committerIndent);
- mergeCommit.setCommitter(committerIndent);
- mergeCommit.setMessage(commitMsg);
- return rw.parseCommit(inserter.insert(mergeCommit));
+ DirCache dc = DirCache.newInCore();
+ if (allowConflicts && m instanceof ResolveMerger) {
+ // The DirCache must be set on ResolveMerger before calling
+ // ResolveMerger#merge(AnyObjectId...) otherwise the entries in DirCache don't get populated.
+ ((ResolveMerger) m).setDirCache(dc);
}
- List<String> conflicts = ImmutableList.of();
- if (m instanceof ResolveMerger) {
- conflicts = ((ResolveMerger) m).getUnmergedPaths();
+
+ ObjectId tree;
+ ImmutableSet<String> filesWithGitConflicts;
+ if (m.merge(false, mergeTip, originalCommit)) {
+ filesWithGitConflicts = null;
+ tree = m.getResultTreeId();
+ } else {
+ List<String> conflicts = ImmutableList.of();
+ if (m instanceof ResolveMerger) {
+ conflicts = ((ResolveMerger) m).getUnmergedPaths();
+ }
+
+ if (!allowConflicts) {
+ throw new MergeConflictException(createConflictMessage(conflicts));
+ }
+
+ // For merging with conflict markers we need a ResolveMerger, double-check that we have one.
+ if (!(m instanceof ResolveMerger)) {
+ throw new MergeWithConflictsNotSupportedException(MergeStrategy.get(mergeStrategy));
+ }
+ Map<String, MergeResult<? extends Sequence>> mergeResults =
+ ((ResolveMerger) m).getMergeResults();
+
+ filesWithGitConflicts =
+ mergeResults.entrySet().stream()
+ .filter(e -> e.getValue().containsConflicts())
+ .map(Map.Entry::getKey)
+ .collect(toImmutableSet());
+
+ tree =
+ mergeWithConflicts(
+ rw,
+ inserter,
+ dc,
+ "TARGET BRANCH",
+ mergeTip,
+ "SOURCE BRANCH",
+ originalCommit,
+ mergeResults);
}
- throw new MergeConflictException(createConflictMessage(conflicts));
+
+ CommitBuilder mergeCommit = new CommitBuilder();
+ mergeCommit.setTreeId(tree);
+ mergeCommit.setParentIds(mergeTip, originalCommit);
+ mergeCommit.setAuthor(committerIndent);
+ mergeCommit.setCommitter(committerIndent);
+ mergeCommit.setMessage(commitMsg);
+ CodeReviewCommit commit = rw.parseCommit(inserter.insert(mergeCommit));
+ commit.setFilesWithGitConflicts(filesWithGitConflicts);
+ return commit;
}
public static String createConflictMessage(List<String> conflicts) {
- StringBuilder sb = new StringBuilder("merge conflict(s)");
+ StringBuilder sb = new StringBuilder("merge conflict(s):");
for (String c : conflicts) {
sb.append('\n').append(c);
}
@@ -760,6 +803,7 @@
try {
failed(rw, mergeTip, n, getCommitMergeStatus(e.getReason()));
} catch (IOException e2) {
+ logger.atSevere().withCause(e2).log("Failed to set merge failure status for " + n.name());
throw new IntegrationException("Cannot merge " + n.name(), e);
}
} catch (IOException e) {
diff --git a/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java b/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
index 1bd8b45..b7dc2b3 100644
--- a/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
+++ b/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
@@ -106,7 +106,7 @@
try {
result = forProject.filter(refs, getDelegate(), RefFilterOptions.defaults());
} catch (PermissionBackendException e) {
- throw new IOException("");
+ throw new IOException("", e);
}
return buildPrefixRefMap(prefix, result);
}
diff --git a/java/com/google/gerrit/server/git/PureRevertCache.java b/java/com/google/gerrit/server/git/PureRevertCache.java
index b21cb5c..9f9530c 100644
--- a/java/com/google/gerrit/server/git/PureRevertCache.java
+++ b/java/com/google/gerrit/server/git/PureRevertCache.java
@@ -166,7 +166,7 @@
try {
claimedOriginalCommit = rw.parseCommit(original);
} catch (InvalidObjectIdException | MissingObjectException e) {
- throw new BadRequestException("invalid object ID");
+ throw new BadRequestException("invalid object ID", e);
}
if (claimedOriginalCommit.getParentCount() == 0) {
throw new BadRequestException("can't check against initial commit");
diff --git a/java/com/google/gerrit/server/git/meta/VersionedMetaData.java b/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
index 66482a9..c52bbbc 100644
--- a/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
+++ b/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
@@ -436,7 +436,7 @@
private String errorMsg(RefUpdate ru, File location) {
return String.format(
- "Cannot update %s in %s : %s (%s)",
+ "Cannot update %s in %s: %s (%s)",
ru.getName(), location, ru.getResult(), ru.getRefLogMessage());
}
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index d8aa054..4b6a4cd 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -92,6 +92,7 @@
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentForValidation.CommentSource;
import com.google.gerrit.extensions.validators.CommentForValidation.CommentType;
import com.google.gerrit.extensions.validators.CommentValidationContext;
import com.google.gerrit.extensions.validators.CommentValidationFailure;
@@ -126,6 +127,8 @@
import com.google.gerrit.server.git.ReceivePackInitializer;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.ValidationError;
+import com.google.gerrit.server.git.validators.CommentCountValidator;
+import com.google.gerrit.server.git.validators.CommentSizeValidator;
import com.google.gerrit.server.git.validators.CommitValidationMessage;
import com.google.gerrit.server.git.validators.RefOperationValidationException;
import com.google.gerrit.server.git.validators.RefOperationValidators;
@@ -1441,11 +1444,18 @@
final ReceiveCommand cmd;
final LabelTypes labelTypes;
/**
- * Result of running {@link CommentValidator}-s on drafts that are published with the commit
- * (which happens iff {@code --publish-comments} is set). Remains {@code true} if none are
- * installed.
+ * Draft comments are published with the commit iff {@code --publish-comments} is set. All
+ * drafts are withheld (overriding the option) if at least one of the following conditions are
+ * met:
+ *
+ * <ul>
+ * <li>Installed {@link CommentValidator} plugins reject one or more draft comments.
+ * <li>One or more comments exceed the maximum comment size (see {@link
+ * CommentSizeValidator}).
+ * <li>The maximum number of comments would be exceeded (see {@link CommentCountValidator}).
+ * </ul>
*/
- private boolean commentsValid = true;
+ private boolean withholdComments = false;
BranchNameKey dest;
PermissionBackend.ForRef perm;
@@ -1642,18 +1652,19 @@
.collect(toImmutableSet());
}
- void setCommentsValid(boolean commentsValid) {
- this.commentsValid = commentsValid;
+ void setWithholdComments(boolean withholdComments) {
+ this.withholdComments = withholdComments;
}
boolean shouldPublishComments() {
- if (!commentsValid) {
+ if (withholdComments) {
// Validation messages of type WARNING have already been added, now withhold the comments.
return false;
}
if (publishComments) {
return true;
- } else if (noPublishComments) {
+ }
+ if (noPublishComments) {
return false;
}
return defaultPublishComments;
@@ -2010,19 +2021,18 @@
.map(
comment ->
CommentForValidation.create(
+ CommentSource.HUMAN,
comment.lineNbr > 0
? CommentType.INLINE_COMMENT
: CommentType.FILE_COMMENT,
- comment.message))
+ comment.message,
+ comment.message.length()))
.collect(toImmutableList());
CommentValidationContext ctx =
- CommentValidationContext.builder()
- .changeId(change.getChangeId())
- .project(change.getProject().get())
- .build();
+ CommentValidationContext.create(change.getChangeId(), change.getProject().get());
ImmutableList<CommentValidationFailure> commentValidationFailures =
PublishCommentUtil.findInvalidComments(ctx, commentValidators, draftsForValidation);
- magicBranch.setCommentsValid(commentValidationFailures.isEmpty());
+ magicBranch.setWithholdComments(!commentValidationFailures.isEmpty());
commentValidationFailures.forEach(
failure ->
addMessage(
diff --git a/java/com/google/gerrit/server/git/validators/CommentCountValidator.java b/java/com/google/gerrit/server/git/validators/CommentCountValidator.java
new file mode 100644
index 0000000..2c939c8
--- /dev/null
+++ b/java/com/google/gerrit/server/git/validators/CommentCountValidator.java
@@ -0,0 +1,61 @@
+// Copyright (C) 2020 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.git.validators;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentValidationContext;
+import com.google.gerrit.extensions.validators.CommentValidationFailure;
+import com.google.gerrit.extensions.validators.CommentValidator;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.inject.Inject;
+import org.eclipse.jgit.lib.Config;
+
+/** Limits number of comments to prevent space/time complexity issues. */
+public class CommentCountValidator implements CommentValidator {
+ private final int maxComments;
+ private final ChangeNotes.Factory notesFactory;
+
+ @Inject
+ CommentCountValidator(@GerritServerConfig Config serverConfig, ChangeNotes.Factory notesFactory) {
+ this.notesFactory = notesFactory;
+ maxComments = serverConfig.getInt("change", "maxComments", 5_000);
+ }
+
+ @Override
+ public ImmutableList<CommentValidationFailure> validateComments(
+ CommentValidationContext ctx, ImmutableList<CommentForValidation> comments) {
+ ImmutableList.Builder<CommentValidationFailure> failures = ImmutableList.builder();
+ ChangeNotes notes =
+ notesFactory.createChecked(Project.nameKey(ctx.getProject()), Change.id(ctx.getChangeId()));
+ int numExistingComments = notes.getComments().size() + notes.getRobotComments().size();
+ if (!comments.isEmpty() && numExistingComments + comments.size() > maxComments) {
+ // This warning really applies to the set of all comments, but we need to pick one to attach
+ // the message to.
+ CommentForValidation commentForFailureMessage = Iterables.getLast(comments);
+
+ failures.add(
+ commentForFailureMessage.failValidation(
+ String.format(
+ "Exceeding maximum number of comments: %d (existing) + %d (new) > %d",
+ numExistingComments, comments.size(), maxComments)));
+ }
+ return failures.build();
+ }
+}
diff --git a/java/com/google/gerrit/server/git/validators/CommentLimitsValidator.java b/java/com/google/gerrit/server/git/validators/CommentLimitsValidator.java
deleted file mode 100644
index 3a8bcac..0000000
--- a/java/com/google/gerrit/server/git/validators/CommentLimitsValidator.java
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2020 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.git.validators;
-
-import com.google.common.collect.ImmutableList;
-import com.google.gerrit.extensions.validators.CommentForValidation;
-import com.google.gerrit.extensions.validators.CommentValidationContext;
-import com.google.gerrit.extensions.validators.CommentValidationFailure;
-import com.google.gerrit.extensions.validators.CommentValidator;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.inject.Inject;
-import org.eclipse.jgit.lib.Config;
-
-/** Limits the size of comments to prevent large comments from causing issues. */
-public class CommentLimitsValidator implements CommentValidator {
- private final int maxCommentLength;
-
- @Inject
- CommentLimitsValidator(@GerritServerConfig Config serverConfig) {
- maxCommentLength = serverConfig.getInt("change", null, "maxCommentLength", 16 << 10);
- }
-
- @Override
- public ImmutableList<CommentValidationFailure> validateComments(
- CommentValidationContext ctx, ImmutableList<CommentForValidation> comments) {
- return comments.stream()
- .filter(c -> c.getText().length() > maxCommentLength)
- .map(
- c ->
- c.failValidation(
- String.format(
- "Comment too large (%d > %d)", c.getText().length(), maxCommentLength)))
- .collect(ImmutableList.toImmutableList());
- }
-}
diff --git a/java/com/google/gerrit/server/git/validators/CommentSizeValidator.java b/java/com/google/gerrit/server/git/validators/CommentSizeValidator.java
new file mode 100644
index 0000000..58b0cb1
--- /dev/null
+++ b/java/com/google/gerrit/server/git/validators/CommentSizeValidator.java
@@ -0,0 +1,71 @@
+// Copyright (C) 2020 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.git.validators;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentValidationContext;
+import com.google.gerrit.extensions.validators.CommentValidationFailure;
+import com.google.gerrit.extensions.validators.CommentValidator;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import org.eclipse.jgit.lib.Config;
+
+/** Limits the size of comments to prevent space/time complexity issues. */
+public class CommentSizeValidator implements CommentValidator {
+ private final int commentSizeLimit;
+ private final int robotCommentSizeLimit;
+
+ @Inject
+ CommentSizeValidator(@GerritServerConfig Config serverConfig) {
+ commentSizeLimit = serverConfig.getInt("change", "commentSizeLimit", 16 << 10);
+ robotCommentSizeLimit = serverConfig.getInt("change", "robotCommentSizeLimit", 1 << 20);
+ }
+
+ @Override
+ public ImmutableList<CommentValidationFailure> validateComments(
+ CommentValidationContext ctx, ImmutableList<CommentForValidation> comments) {
+ return comments.stream()
+ .filter(this::exceedsSizeLimit)
+ .map(c -> c.failValidation(buildErrorMessage(c)))
+ .collect(ImmutableList.toImmutableList());
+ }
+
+ private boolean exceedsSizeLimit(CommentForValidation comment) {
+ switch (comment.getSource()) {
+ case HUMAN:
+ return comment.getApproximateSize() > commentSizeLimit;
+ case ROBOT:
+ return robotCommentSizeLimit > 0 && comment.getApproximateSize() > robotCommentSizeLimit;
+ }
+ throw new RuntimeException(
+ "Unknown comment source (should not have compiled): " + comment.getSource());
+ }
+
+ private String buildErrorMessage(CommentForValidation comment) {
+ switch (comment.getSource()) {
+ case HUMAN:
+ return String.format(
+ "Comment size exceeds limit (%d > %d)", comment.getApproximateSize(), commentSizeLimit);
+
+ case ROBOT:
+ return String.format(
+ "Size %d (bytes) of robot comment is greater than limit %d (bytes)",
+ comment.getApproximateSize(), robotCommentSizeLimit);
+ }
+ throw new RuntimeException(
+ "Unknown comment source (should not have compiled): " + comment.getSource());
+ }
+}
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidationException.java b/java/com/google/gerrit/server/git/validators/CommitValidationException.java
index 4dd2951..220fc12 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidationException.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidationException.java
@@ -32,6 +32,11 @@
this.messages = ImmutableList.of(message);
}
+ public CommitValidationException(String reason, CommitValidationMessage message, Throwable why) {
+ super(reason, why);
+ this.messages = ImmutableList.of(message);
+ }
+
public CommitValidationException(String reason, List<CommitValidationMessage> messages) {
super(reason);
this.messages = ImmutableList.copyOf(messages);
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidators.java b/java/com/google/gerrit/server/git/validators/CommitValidators.java
index 4159ebb..4d2dbef 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -151,6 +151,7 @@
new ProjectStateValidationListener(projectState),
new AmendedGerritMergeCommitValidationListener(perm, gerritIdent),
new AuthorUploaderValidator(user, perm, urlFormatter.get()),
+ new FileCountValidator(patchListCache, config),
new CommitterUploaderValidator(user, perm, urlFormatter.get()),
new SignedOffByValidator(user, perm, projectState),
new ChangeIdValidator(
@@ -414,7 +415,11 @@
diffSummary.getPaths().size(), maxFileCount));
}
} catch (PatchListNotAvailableException e) {
- logger.atWarning().withCause(e).log("Failed to validate file count");
+ // This happens e.g. for cherrypicks.
+ if (!receiveEvent.command.getRefName().startsWith(REFS_CHANGES)) {
+ logger.atWarning().withCause(e).log(
+ "Failed to validate file count for commit: %s", receiveEvent.commit.toString());
+ }
}
return Collections.emptyList();
}
@@ -500,7 +505,7 @@
perm.check(RefPermission.MERGE);
return Collections.emptyList();
} catch (AuthException e) {
- throw new CommitValidationException("you are not allowed to upload merges");
+ throw new CommitValidationException("you are not allowed to upload merges", e);
} catch (PermissionBackendException e) {
logger.atSevere().withCause(e).log("cannot check MERGE");
throw new CommitValidationException("internal auth error");
@@ -597,7 +602,7 @@
perm.check(RefPermission.FORGE_COMMITTER);
} catch (AuthException denied) {
throw new CommitValidationException(
- "not Signed-off-by author/committer/uploader in message footer");
+ "not Signed-off-by author/committer/uploader in message footer", denied);
} catch (PermissionBackendException e) {
logger.atSevere().withCause(e).log("cannot check FORGE_COMMITTER");
throw new CommitValidationException("internal auth error");
@@ -632,7 +637,7 @@
return Collections.emptyList();
} catch (AuthException e) {
throw new CommitValidationException(
- "invalid author", invalidEmail("author", author, user, urlFormatter));
+ "invalid author", invalidEmail("author", author, user, urlFormatter), e);
} catch (PermissionBackendException e) {
logger.atSevere().withCause(e).log("cannot check FORGE_AUTHOR");
throw new CommitValidationException("internal auth error");
@@ -665,7 +670,7 @@
return Collections.emptyList();
} catch (AuthException e) {
throw new CommitValidationException(
- "invalid committer", invalidEmail("committer", committer, user, urlFormatter));
+ "invalid committer", invalidEmail("committer", committer, user, urlFormatter), e);
} catch (PermissionBackendException e) {
logger.atSevere().withCause(e).log("cannot check FORGE_COMMITTER");
throw new CommitValidationException("internal auth error");
@@ -704,7 +709,8 @@
"pushing merge commit %s by %s requires '%s' permission",
receiveEvent.commit.getId(),
gerritIdent.getEmailAddress(),
- RefPermission.FORGE_SERVER.name()));
+ RefPermission.FORGE_SERVER.name()),
+ denied);
} catch (PermissionBackendException e) {
logger.atSevere().withCause(e).log("cannot check FORGE_SERVER");
throw new CommitValidationException("internal auth error");
diff --git a/java/com/google/gerrit/server/git/validators/MergeValidationException.java b/java/com/google/gerrit/server/git/validators/MergeValidationException.java
index 3624fe0..2b7803d 100644
--- a/java/com/google/gerrit/server/git/validators/MergeValidationException.java
+++ b/java/com/google/gerrit/server/git/validators/MergeValidationException.java
@@ -28,4 +28,8 @@
public MergeValidationException(String msg) {
super(msg);
}
+
+ public MergeValidationException(String msg, Throwable why) {
+ super(msg, why);
+ }
}
diff --git a/java/com/google/gerrit/server/git/validators/MergeValidators.java b/java/com/google/gerrit/server/git/validators/MergeValidators.java
index 8abe5c6..9c557a7 100644
--- a/java/com/google/gerrit/server/git/validators/MergeValidators.java
+++ b/java/com/google/gerrit/server/git/validators/MergeValidators.java
@@ -192,7 +192,7 @@
try {
permissionBackend.user(caller).check(GlobalPermission.ADMINISTRATE_SERVER);
} catch (AuthException e) {
- throw new MergeValidationException(SET_BY_ADMIN);
+ throw new MergeValidationException(SET_BY_ADMIN, e);
} catch (PermissionBackendException e) {
logger.atWarning().withCause(e).log("Cannot check ADMINISTRATE_SERVER");
throw new MergeValidationException("validation unavailable");
@@ -204,7 +204,7 @@
.project(destProject.getNameKey())
.check(ProjectPermission.WRITE_CONFIG);
} catch (AuthException e) {
- throw new MergeValidationException(SET_BY_OWNER);
+ throw new MergeValidationException(SET_BY_OWNER, e);
} catch (PermissionBackendException e) {
logger.atWarning().withCause(e).log("Cannot check WRITE_CONFIG");
throw new MergeValidationException("validation unavailable");
@@ -245,7 +245,7 @@
}
}
} catch (ConfigInvalidException | IOException e) {
- throw new MergeValidationException(INVALID_CONFIG);
+ throw new MergeValidationException(INVALID_CONFIG, e);
}
}
}
diff --git a/java/com/google/gerrit/server/git/validators/RefOperationValidators.java b/java/com/google/gerrit/server/git/validators/RefOperationValidators.java
index f3d8f4a..f3b6983 100644
--- a/java/com/google/gerrit/server/git/validators/RefOperationValidators.java
+++ b/java/com/google/gerrit/server/git/validators/RefOperationValidators.java
@@ -134,7 +134,7 @@
try {
perm.check(GlobalPermission.ACCESS_DATABASE);
} catch (AuthException | PermissionBackendException e) {
- throw new ValidationException("Not allowed to create user branch.");
+ throw new ValidationException("Not allowed to create user branch.", e);
}
if (Account.Id.fromRef(refEvent.command.getRefName()) == null) {
throw new ValidationException(
@@ -145,7 +145,7 @@
try {
perm.check(GlobalPermission.ACCESS_DATABASE);
} catch (AuthException | PermissionBackendException e) {
- throw new ValidationException("Not allowed to delete user branch.");
+ throw new ValidationException("Not allowed to delete user branch.", e);
}
}
}
diff --git a/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java b/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
index 4e27859..19bb423 100644
--- a/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
+++ b/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
@@ -28,6 +28,7 @@
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.change.MergeabilityComputationBehavior;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.QueueProvider.QueueType;
@@ -85,7 +86,7 @@
this.accountCache = accountCache;
this.indexer = indexer;
this.executor = executor;
- this.enabled = cfg.getBoolean("index", "change", "indexMergeable", true);
+ this.enabled = MergeabilityComputationBehavior.fromConfig(cfg).includeInIndex();
}
@Override
diff --git a/java/com/google/gerrit/server/mail/receive/MailProcessor.java b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
index e79696a..9c3dd02 100644
--- a/java/com/google/gerrit/server/mail/receive/MailProcessor.java
+++ b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
@@ -19,6 +19,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Change;
@@ -243,7 +244,7 @@
sendRejectionEmail(message, InboundEmailRejectionSender.Error.INTERNAL_EXCEPTION);
return;
}
- ChangeData cd = changeDataList.get(0);
+ ChangeData cd = Iterables.getOnlyElement(changeDataList);
if (existingMessageIds(cd).contains(message.id())) {
logger.atInfo().log("Message %s was already processed. Will delete message.", message.id());
return;
@@ -285,14 +286,14 @@
.map(
comment ->
CommentForValidation.create(
+ CommentForValidation.CommentSource.HUMAN,
MAIL_COMMENT_TYPE_TO_VALIDATION_TYPE.get(comment.getType()),
- comment.getMessage()))
+ comment.getMessage(),
+ comment.getMessage().length()))
.collect(ImmutableList.toImmutableList());
CommentValidationContext commentValidationCtx =
- CommentValidationContext.builder()
- .changeId(cd.change().getChangeId())
- .project(cd.change().getProject().get())
- .build();
+ CommentValidationContext.create(
+ cd.change().getChangeId(), cd.change().getProject().get());
ImmutableList<CommentValidationFailure> commentValidationFailures =
PublishCommentUtil.findInvalidComments(
commentValidationCtx, commentValidators, parsedCommentsForValidation);
diff --git a/java/com/google/gerrit/server/mail/send/ChangeEmail.java b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
index d7f09d2..9e53c65 100644
--- a/java/com/google/gerrit/server/mail/send/ChangeEmail.java
+++ b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
@@ -293,7 +293,7 @@
try {
ps = args.patchSetUtil.get(changeData.notes(), PatchSet.id(change.getId(), patchSetId));
} catch (StorageException e) {
- throw new PatchListNotAvailableException("Failed to get patchSet");
+ throw new PatchListNotAvailableException("Failed to get patchSet", e);
}
}
return args.patchListCache.get(change, ps);
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 5468e23..428df16 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -907,7 +907,9 @@
try {
adr = Address.parse(line);
} catch (IllegalArgumentException e) {
- throw invalidFooter(state.getByEmailFooterKey(), line);
+ ConfigInvalidException cie = invalidFooter(state.getByEmailFooterKey(), line);
+ cie.initCause(e);
+ throw cie;
}
if (!reviewersByEmail.containsRow(adr)) {
reviewersByEmail.put(adr, state, ts);
diff --git a/java/com/google/gerrit/server/notedb/DeleteZombieCommentsRefs.java b/java/com/google/gerrit/server/notedb/DeleteZombieCommentsRefs.java
new file mode 100644
index 0000000..9ec1d69
--- /dev/null
+++ b/java/com/google/gerrit/server/notedb/DeleteZombieCommentsRefs.java
@@ -0,0 +1,130 @@
+// Copyright (C) 2020 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;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.collect.Iterables;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.git.RefUpdateUtil;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/**
+ * This class can be used to clean zombie draft comments refs. More context in <a
+ * href="https://gerrit-review.googlesource.com/c/gerrit/+/246233">
+ * https://gerrit-review.googlesource.com/c/gerrit/+/246233 </a>
+ *
+ * <p>An earlier bug in the deletion of draft comments {@code
+ * refs/draft-comments/$change_id_short/$change_id/$user_id} caused some draft refs to remain in Git
+ * and not get deleted. These refs point to an empty tree.
+ */
+public class DeleteZombieCommentsRefs {
+ private final String EMPTY_TREE_ID = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
+ private final String DRAFT_REFS_PREFIX = "refs/draft-comments";
+ private final int CHUNK_SIZE = 100; // log progress after deleting every CHUNK_SIZE refs
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsers;
+ private final int cleanupPercentage;
+ private Repository allUsersRepo;
+
+ public interface Factory {
+ DeleteZombieCommentsRefs create(int cleanupPercentage);
+ }
+
+ @Inject
+ public DeleteZombieCommentsRefs(
+ AllUsersName allUsers,
+ GitRepositoryManager repoManager,
+ @Assisted Integer cleanupPercentage) {
+ this.allUsers = allUsers;
+ this.repoManager = repoManager;
+ this.cleanupPercentage = (cleanupPercentage == null) ? 100 : cleanupPercentage;
+ }
+
+ public void execute() throws IOException {
+ allUsersRepo = repoManager.openRepository(allUsers);
+
+ List<Ref> draftRefs = allUsersRepo.getRefDatabase().getRefsByPrefix(DRAFT_REFS_PREFIX);
+ List<Ref> zombieRefs = filterZombieRefs(draftRefs);
+
+ System.out.println(
+ String.format(
+ "Found a total of %d zombie draft refs in %s repo.",
+ zombieRefs.size(), allUsers.get()));
+
+ System.out.println(String.format("Cleanup percentage = %d", cleanupPercentage));
+ zombieRefs =
+ zombieRefs.stream()
+ .filter(ref -> Change.Id.fromAllUsersRef(ref.getName()).get() % 100 < cleanupPercentage)
+ .collect(toImmutableList());
+ System.out.println(
+ String.format("Number of zombie refs to be cleaned = %d", zombieRefs.size()));
+
+ long zombieRefsCnt = zombieRefs.size();
+ long deletedRefsCnt = 0;
+ long startTime = System.currentTimeMillis();
+
+ for (List<Ref> refsBatch : Iterables.partition(zombieRefs, CHUNK_SIZE)) {
+ deleteBatchZombieRefs(refsBatch);
+ long elapsed = (System.currentTimeMillis() - startTime) / 1000;
+ deletedRefsCnt += refsBatch.size();
+ logProgress(deletedRefsCnt, zombieRefsCnt, elapsed);
+ }
+ }
+
+ private void deleteBatchZombieRefs(List<Ref> refsBatch) throws IOException {
+ List<ReceiveCommand> deleteCommands =
+ refsBatch.stream()
+ .map(
+ zombieRef ->
+ new ReceiveCommand(
+ zombieRef.getObjectId(), ObjectId.zeroId(), zombieRef.getName()))
+ .collect(toImmutableList());
+ BatchRefUpdate bru = allUsersRepo.getRefDatabase().newBatchUpdate();
+ bru.setAtomic(true);
+ bru.addCommand(deleteCommands);
+ RefUpdateUtil.executeChecked(bru, allUsersRepo);
+ }
+
+ private List<Ref> filterZombieRefs(List<Ref> allDraftRefs) throws IOException {
+ List<Ref> zombieRefs = new ArrayList<>((int) (allDraftRefs.size() * 0.5));
+ for (Ref ref : allDraftRefs) {
+ if (isZombieRef(ref)) {
+ zombieRefs.add(ref);
+ }
+ }
+ return zombieRefs;
+ }
+
+ private boolean isZombieRef(Ref ref) throws IOException {
+ return allUsersRepo.parseCommit(ref.getObjectId()).getTree().getName().equals(EMPTY_TREE_ID);
+ }
+
+ private void logProgress(long deletedRefsCount, long allRefsCount, long elapsed) {
+ System.out.format(
+ "Deleted %d/%d zombie draft refs (%d seconds)\n", deletedRefsCount, allRefsCount, elapsed);
+ }
+}
diff --git a/java/com/google/gerrit/server/notedb/LimitExceededException.java b/java/com/google/gerrit/server/notedb/LimitExceededException.java
index 69f9241..0a57d96 100644
--- a/java/com/google/gerrit/server/notedb/LimitExceededException.java
+++ b/java/com/google/gerrit/server/notedb/LimitExceededException.java
@@ -23,12 +23,13 @@
* <ul>
* <li>The number of NoteDb updates per change.
* <li>The number of patch sets per change.
+ * <li>The number of files per change.
* </ul>
*/
public class LimitExceededException extends StorageException {
private static final long serialVersionUID = 1L;
- LimitExceededException(String message) {
+ public LimitExceededException(String message) {
super(message);
}
}
diff --git a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index 92df794..d88b626 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -28,6 +28,7 @@
import com.google.gerrit.entities.Patch.PatchType;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.server.fixes.FixCalculator;
import com.google.gerrit.server.mime.FileTypeRegistry;
import com.google.gerrit.server.patch.DiffContentCalculator.DiffCalculatorResult;
@@ -116,9 +117,12 @@
PatchScript toPatchScript(
Repository git, ObjectId baseId, String fileName, List<FixReplacement> fixReplacements)
- throws IOException, ResourceConflictException {
+ throws IOException, ResourceConflictException, ResourceNotFoundException {
SidesResolver sidesResolver = new SidesResolver(git, ComparisonType.againstOtherPatchSet());
PatchSide a = resolveSideA(git, sidesResolver, fileName, baseId);
+ if (a.mode == FileMode.MISSING) {
+ throw new ResourceNotFoundException(String.format("File %s not found", fileName));
+ }
FixCalculator.FixResult fixResult = FixCalculator.calculateFix(a.src, fixReplacements);
PatchSide b =
new PatchSide(
diff --git a/java/com/google/gerrit/server/patch/PatchScriptFactory.java b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index afc1ab1..b7b3d81 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptFactory.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -193,7 +193,7 @@
try {
permissionBackend.currentUser().change(notes).check(ChangePermission.READ);
} catch (AuthException e) {
- throw new NoSuchChangeException(changeId);
+ throw new NoSuchChangeException(changeId, e);
}
if (!projectCache.checkedGet(notes.getProjectName()).statePermitsRead()) {
diff --git a/java/com/google/gerrit/server/patch/PatchScriptFactoryForAutoFix.java b/java/com/google/gerrit/server/patch/PatchScriptFactoryForAutoFix.java
index cf07190..2f5458c 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptFactoryForAutoFix.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptFactoryForAutoFix.java
@@ -25,6 +25,7 @@
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.server.git.LargeObjectException;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.ChangePermission;
@@ -93,12 +94,12 @@
@Override
public PatchScript call()
throws LargeObjectException, AuthException, InvalidChangeOperationException, IOException,
- PermissionBackendException {
+ PermissionBackendException, ResourceNotFoundException {
try {
permissionBackend.currentUser().change(notes).check(ChangePermission.READ);
} catch (AuthException e) {
- throw new NoSuchChangeException(changeId);
+ throw new NoSuchChangeException(changeId, e);
}
if (!projectCache.checkedGet(notes.getProjectName()).statePermitsRead()) {
@@ -108,7 +109,7 @@
return createPatchScript();
}
- private PatchScript createPatchScript() throws LargeObjectException {
+ private PatchScript createPatchScript() throws LargeObjectException, ResourceNotFoundException {
checkState(patchSet.id().get() != 0, "edit not supported for left side");
PatchScriptBuilder b = newBuilder();
try {
diff --git a/java/com/google/gerrit/server/project/LabelDefinitionJson.java b/java/com/google/gerrit/server/project/LabelDefinitionJson.java
index 2ecd8c2..0452d0b 100644
--- a/java/com/google/gerrit/server/project/LabelDefinitionJson.java
+++ b/java/com/google/gerrit/server/project/LabelDefinitionJson.java
@@ -40,6 +40,7 @@
label.copyAllScoresOnTrivialRebase = toBoolean(labelType.isCopyAllScoresOnTrivialRebase());
label.copyAllScoresOnMergeFirstParentUpdate =
toBoolean(labelType.isCopyAllScoresOnMergeFirstParentUpdate());
+ label.copyValues = labelType.getCopyValues().isEmpty() ? null : labelType.getCopyValues();
label.allowPostSubmit = toBoolean(labelType.allowPostSubmit());
label.ignoreSelfApproval = toBoolean(labelType.ignoreSelfApproval());
return label;
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index fa877af..4ab583d 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -109,6 +109,7 @@
public static final String KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE = "copyAllScoresOnTrivialRebase";
public static final String KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE = "copyAllScoresIfNoCodeChange";
public static final String KEY_COPY_ALL_SCORES_IF_NO_CHANGE = "copyAllScoresIfNoChange";
+ public static final String KEY_COPY_VALUE = "copyValue";
public static final String KEY_VALUE = "value";
public static final String KEY_CAN_OVERRIDE = "canOverride";
public static final String KEY_BRANCH = "branch";
@@ -1014,6 +1015,27 @@
name,
KEY_COPY_ALL_SCORES_IF_NO_CHANGE,
LabelType.DEF_COPY_ALL_SCORES_IF_NO_CHANGE));
+ Set<Short> copyValues = new HashSet<>();
+ for (String value : rc.getStringList(LABEL, name, KEY_COPY_VALUE)) {
+ try {
+ short copyValue = Shorts.checkedCast(PermissionRule.parseInt(value));
+ if (!copyValues.add(copyValue)) {
+ error(
+ new ValidationError(
+ PROJECT_CONFIG,
+ String.format(
+ "Duplicate %s \"%s\" for label \"%s\"", KEY_COPY_VALUE, value, name)));
+ }
+ } catch (IllegalArgumentException notValue) {
+ error(
+ new ValidationError(
+ PROJECT_CONFIG,
+ String.format(
+ "Invalid %s \"%s\" for label \"%s\": %s",
+ KEY_COPY_VALUE, value, name, notValue.getMessage())));
+ }
+ }
+ label.setCopyValues(copyValues);
label.setCanOverride(
rc.getBoolean(LABEL, name, KEY_CAN_OVERRIDE, LabelType.DEF_CAN_OVERRIDE));
label.setRefPatterns(getStringListOrNull(rc, LABEL, name, KEY_BRANCH));
@@ -1492,6 +1514,11 @@
KEY_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE,
label.isCopyAllScoresOnMergeFirstParentUpdate(),
LabelType.DEF_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE);
+ rc.setStringList(
+ LABEL,
+ name,
+ KEY_COPY_VALUE,
+ label.getCopyValues().stream().map(LabelValue::formatValue).collect(toList()));
setBooleanConfigKey(
rc, LABEL, name, KEY_CAN_OVERRIDE, label.canOverride(), LabelType.DEF_CAN_OVERRIDE);
List<String> values = new ArrayList<>(label.getValues().size());
diff --git a/java/com/google/gerrit/server/project/ProjectCreator.java b/java/com/google/gerrit/server/project/ProjectCreator.java
index ba44142..c4c466e 100644
--- a/java/com/google/gerrit/server/project/ProjectCreator.java
+++ b/java/com/google/gerrit/server/project/ProjectCreator.java
@@ -132,9 +132,10 @@
+ nameKey.get()
+ " because the name is already occupied by another project."
+ " The other project has the same name, only spelled in a"
- + " different case.");
+ + " different case.",
+ e);
} catch (RepositoryNotFoundException badName) {
- throw new BadRequestException("invalid project name: " + nameKey);
+ throw new BadRequestException("invalid project name: " + nameKey, badName);
} catch (ConfigInvalidException e) {
String msg = "Cannot create " + nameKey;
logger.atSevere().withCause(e).log(msg);
diff --git a/java/com/google/gerrit/server/project/RefUtil.java b/java/com/google/gerrit/server/project/RefUtil.java
index 5d6379a..dc8cdc7 100644
--- a/java/com/google/gerrit/server/project/RefUtil.java
+++ b/java/com/google/gerrit/server/project/RefUtil.java
@@ -55,7 +55,7 @@
"Cannot resolve \"%s\" in project \"%s\"", baseRevision, projectName.get());
throw new InvalidRevisionException(baseRevision);
} catch (RevisionSyntaxException err) {
- throw new InvalidRevisionException(baseRevision);
+ throw new InvalidRevisionException(baseRevision, err);
}
}
@@ -66,7 +66,7 @@
try {
rw.markStart(rw.parseCommit(revid));
} catch (IncorrectObjectTypeException err) {
- throw new InvalidRevisionException(revid.name());
+ throw new InvalidRevisionException(revid.name(), err);
}
RefDatabase refDb = repo.getRefDatabase();
Iterable<Ref> refs =
@@ -86,7 +86,7 @@
rw.checkConnectivity();
return rw;
} catch (IncorrectObjectTypeException | MissingObjectException err) {
- throw new InvalidRevisionException(revid.name());
+ throw new InvalidRevisionException(revid.name(), err);
} catch (IOException err) {
logger.atSevere().withCause(err).log(
"Repository \"%s\" may be corrupt; suggest running git fsck", repo.getDirectory());
@@ -128,5 +128,9 @@
InvalidRevisionException(@Nullable String invalidRevision) {
super(MESSAGE + ": " + invalidRevision);
}
+
+ InvalidRevisionException(@Nullable String invalidRevision, Throwable why) {
+ super(MESSAGE + ": " + invalidRevision, why);
+ }
}
}
diff --git a/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java b/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
index 664df70..42a8310 100644
--- a/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
@@ -123,7 +123,9 @@
try {
args.permissionBackend.user(args.getUser()).change(changeNotes).check(ChangePermission.READ);
} catch (AuthException e) {
- throw error(String.format("change %s not found", change));
+ String msg = String.format("change %s not found", change);
+ logger.atSevere().withCause(e).log(msg);
+ throw error(msg);
}
return AccountPredicates.cansee(args, changeNotes);
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 7747998..327c21f 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -63,6 +63,7 @@
import com.google.gerrit.server.account.VersionedAccountDestinations;
import com.google.gerrit.server.account.VersionedAccountQueries;
import com.google.gerrit.server.change.ChangeTriplet;
+import com.google.gerrit.server.change.MergeabilityComputationBehavior;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -293,7 +294,7 @@
groupMembers,
anonymousUserProvider,
operatorAliasConfig,
- gerritConfig.getBoolean("index", "change", "indexMergeable", true));
+ MergeabilityComputationBehavior.fromConfig(gerritConfig).includeInIndex());
}
private Arguments(
@@ -1288,7 +1289,8 @@
throw new QueryParseException(
"'"
+ value
- + "' is not a valid input. It must be in the 'ChangeNumber[,PatchsetNumber]' format.");
+ + "' is not a valid input. It must be in the 'ChangeNumber[,PatchsetNumber]' format.",
+ e);
}
}
throw new QueryParseException(
diff --git a/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java b/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
index 2992869..d234546 100644
--- a/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
@@ -75,7 +75,7 @@
try {
parsedState = ProjectState.valueOf(state.replace('-', '_').toUpperCase());
} catch (IllegalArgumentException e) {
- throw error("state operator must be either 'active' or 'read-only'");
+ throw error("state operator must be either 'active' or 'read-only'", e);
}
if (parsedState == ProjectState.HIDDEN) {
throw error("state operator must be either 'active' or 'read-only'");
diff --git a/java/com/google/gerrit/server/restapi/account/Capabilities.java b/java/com/google/gerrit/server/restapi/account/Capabilities.java
index 07b1214..3d719ff9 100644
--- a/java/com/google/gerrit/server/restapi/account/Capabilities.java
+++ b/java/com/google/gerrit/server/restapi/account/Capabilities.java
@@ -75,7 +75,7 @@
permissionBackend.absentUser(target.getAccountId()).check(perm);
return new AccountResource.Capability(target, globalOrPluginPermissionName(perm));
} catch (AuthException e) {
- throw new ResourceNotFoundException(id);
+ throw new ResourceNotFoundException(id, e);
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/CreateAccount.java b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
index 8f525ef..907dd18 100644
--- a/java/com/google/gerrit/server/restapi/account/CreateAccount.java
+++ b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
@@ -163,7 +163,7 @@
try {
addGroupMember(groupUuid, accountId);
} catch (NoSuchGroupException e) {
- throw new UnprocessableEntityException(String.format("Group %s not found", groupUuid));
+ throw new UnprocessableEntityException(String.format("Group %s not found", groupUuid), e);
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java b/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java
index a6fbb10..4b505c6 100644
--- a/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java
+++ b/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java
@@ -27,7 +27,6 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.accounts.DeleteDraftCommentsInput;
import com.google.gerrit.extensions.api.accounts.DeletedDraftCommentInfo;
-import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -190,9 +189,7 @@
if (dirty) {
result = new DeletedDraftCommentInfo();
result.change =
- changeJsonFactory
- .create(ListChangesOption.SKIP_MERGEABLE)
- .format(changeDataFactory.create(ctx.getNotes()));
+ changeJsonFactory.noOptions().format(changeDataFactory.create(ctx.getNotes()));
result.deleted = comments.build();
}
return dirty;
diff --git a/java/com/google/gerrit/server/restapi/account/PutAgreement.java b/java/com/google/gerrit/server/restapi/account/PutAgreement.java
index 459ff26..42504a0 100644
--- a/java/com/google/gerrit/server/restapi/account/PutAgreement.java
+++ b/java/com/google/gerrit/server/restapi/account/PutAgreement.java
@@ -100,7 +100,7 @@
try {
addMembers.addMembers(uuid, ImmutableSet.of(accountState.account().id()));
} catch (NoSuchGroupException e) {
- throw new ResourceConflictException("autoverify group not found");
+ throw new ResourceConflictException("autoverify group not found", e);
}
agreementSignup.fire(accountState, agreementName);
diff --git a/java/com/google/gerrit/server/restapi/account/PutUsername.java b/java/com/google/gerrit/server/restapi/account/PutUsername.java
index dabfaa5..dc841b8 100644
--- a/java/com/google/gerrit/server/restapi/account/PutUsername.java
+++ b/java/com/google/gerrit/server/restapi/account/PutUsername.java
@@ -122,7 +122,7 @@
}
// Otherwise, someone else has this identity.
- throw new ResourceConflictException("username already used");
+ throw new ResourceConflictException("username already used", dupeErr);
}
sshKeyCache.evict(input.username);
diff --git a/java/com/google/gerrit/server/restapi/account/SshKeys.java b/java/com/google/gerrit/server/restapi/account/SshKeys.java
index 6e3f905..a6c4d80 100644
--- a/java/com/google/gerrit/server/restapi/account/SshKeys.java
+++ b/java/com/google/gerrit/server/restapi/account/SshKeys.java
@@ -70,7 +70,7 @@
permissionBackend.currentUser().check(GlobalPermission.MODIFY_ACCOUNT);
} catch (AuthException e) {
// If lacking MODIFY_ACCOUNT claim the resource does not exist.
- throw new ResourceNotFoundException();
+ throw new ResourceNotFoundException(id, e);
}
}
return parse(rsrc.getUser(), id);
@@ -86,7 +86,7 @@
}
return new AccountResource.SshKey(user, sshKey);
} catch (NumberFormatException e) {
- throw new ResourceNotFoundException(id);
+ throw new ResourceNotFoundException(id, e);
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/StarredChanges.java b/java/com/google/gerrit/server/restapi/account/StarredChanges.java
index 3c14ad3..c108dcb 100644
--- a/java/com/google/gerrit/server/restapi/account/StarredChanges.java
+++ b/java/com/google/gerrit/server/restapi/account/StarredChanges.java
@@ -123,10 +123,10 @@
try {
change = changes.parse(TopLevelResource.INSTANCE, id);
} catch (ResourceNotFoundException e) {
- throw new UnprocessableEntityException(String.format("change %s not found", id.get()));
+ throw new UnprocessableEntityException(String.format("change %s not found", id.get()), e);
} catch (StorageException | PermissionBackendException | IOException e) {
logger.atSevere().withCause(e).log("cannot resolve change");
- throw new UnprocessableEntityException("internal server error");
+ throw new UnprocessableEntityException("internal server error", e);
}
try {
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
index 3496491..ac81b45 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
@@ -422,7 +422,8 @@
try {
baseObjectId = ObjectId.fromString(base);
} catch (InvalidObjectIdException e) {
- throw new BadRequestException(String.format("Base %s doesn't represent a valid SHA-1", base));
+ throw new BadRequestException(
+ String.format("Base %s doesn't represent a valid SHA-1", base), e);
}
RevCommit baseCommit;
diff --git a/java/com/google/gerrit/server/restapi/change/CreateChange.java b/java/com/google/gerrit/server/restapi/change/CreateChange.java
index 537993a..0d3aaac 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateChange.java
@@ -29,6 +29,7 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.InvalidMergeStrategyException;
+import com.google.gerrit.exceptions.MergeWithConflictsNotSupportedException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.SubmitType;
@@ -56,6 +57,8 @@
import com.google.gerrit.server.change.NotifyResolver;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -186,9 +189,8 @@
checkRequiredPermissions(project, input.branch);
- Change newChange = createNewChange(input, me, projectState, updateFactory);
- ChangeJson json = jsonFactory.noOptions();
- return Response.created(json.format(newChange));
+ ChangeInfo newChange = createNewChange(input, me, projectState, updateFactory);
+ return Response.created(newChange);
}
/**
@@ -289,7 +291,7 @@
.check(RefPermission.CREATE_CHANGE);
}
- private Change createNewChange(
+ private ChangeInfo createNewChange(
ChangeInput input,
IdentifiedUser me,
ProjectState projectState,
@@ -304,7 +306,7 @@
try (Repository git = gitManager.openRepository(projectState.getNameKey());
ObjectInserter oi = git.newObjectInserter();
ObjectReader reader = oi.newReader();
- RevWalk rw = new RevWalk(reader)) {
+ CodeReviewRevWalk rw = CodeReviewCommit.newRevWalk(reader)) {
PatchSet basePatchSet = null;
List<String> groups = Collections.emptyList();
@@ -327,10 +329,15 @@
PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
String commitMessage = getCommitMessage(input.subject, me);
- RevCommit c;
+ CodeReviewCommit c;
if (input.merge != null) {
// create a merge commit
c = newMergeCommit(git, oi, rw, projectState, mergeTip, input.merge, author, commitMessage);
+ if (!c.getFilesWithGitConflicts().isEmpty()) {
+ logger.atFine().log(
+ "merge commit has conflicts in the following files: %s",
+ c.getFilesWithGitConflicts());
+ }
} else {
// create an empty commit
c = newCommit(oi, rw, author, mergeTip, commitMessage);
@@ -338,10 +345,10 @@
Change.Id changeId = Change.id(seq.nextChangeId());
ChangeInserter ins = changeInserterFactory.create(changeId, c, input.branch);
- ins.setMessage(String.format("Uploaded patch set %s.", ins.getPatchSetId().get()));
+ ins.setMessage(messageForNewChange(ins.getPatchSetId(), c));
ins.setTopic(input.topic);
ins.setPrivate(input.isPrivate);
- ins.setWorkInProgress(input.workInProgress);
+ ins.setWorkInProgress(input.workInProgress || !c.getFilesWithGitConflicts().isEmpty());
ins.setGroups(groups);
try (BatchUpdate bu = updateFactory.create(projectState.getNameKey(), me, now)) {
bu.setRepository(git, rw, oi);
@@ -351,8 +358,10 @@
bu.insertChange(ins);
bu.execute();
}
- return ins.getChange();
- } catch (InvalidMergeStrategyException e) {
+ ChangeInfo changeInfo = jsonFactory.noOptions().format(ins.getChange());
+ changeInfo.containsGitConflicts = !c.getFilesWithGitConflicts().isEmpty() ? true : null;
+ return changeInfo;
+ } catch (InvalidMergeStrategyException | MergeWithConflictsNotSupportedException e) {
throw new BadRequestException(e.getMessage());
}
}
@@ -469,9 +478,9 @@
return commitMessage;
}
- private static RevCommit newCommit(
+ private static CodeReviewCommit newCommit(
ObjectInserter oi,
- RevWalk rw,
+ CodeReviewRevWalk rw,
PersonIdent authorIdent,
RevCommit mergeTip,
String commitMessage)
@@ -490,10 +499,10 @@
return rw.parseCommit(insert(oi, commit));
}
- private RevCommit newMergeCommit(
+ private CodeReviewCommit newMergeCommit(
Repository repo,
ObjectInserter oi,
- RevWalk rw,
+ CodeReviewRevWalk rw,
ProjectState projectState,
RevCommit mergeTip,
MergeInput merge,
@@ -501,7 +510,8 @@
String commitMessage)
throws RestApiException, IOException {
logger.atFine().log(
- "Creating merge commit: source = %s, strategy = %s", merge.source, merge.strategy);
+ "Creating merge commit: source = %s, strategy = %s, allowConflicts = %s",
+ merge.source, merge.strategy, merge.allowConflicts);
if (Strings.isNullOrEmpty(merge.source)) {
throw new BadRequestException("merge.source must be non-empty");
@@ -531,6 +541,7 @@
mergeTip,
sourceCommit,
mergeStrategy,
+ merge.allowConflicts,
authorIdent,
commitMessage,
rw);
@@ -540,6 +551,20 @@
}
}
+ private static String messageForNewChange(PatchSet.Id patchSetId, CodeReviewCommit commit) {
+ StringBuilder stringBuilder =
+ new StringBuilder(String.format("Uploaded patch set %s.", patchSetId.get()));
+
+ if (!commit.getFilesWithGitConflicts().isEmpty()) {
+ stringBuilder.append("\n\nThe following files contain Git conflicts:\n");
+ commit.getFilesWithGitConflicts().stream()
+ .sorted()
+ .forEach(filePath -> stringBuilder.append("* ").append(filePath).append("\n"));
+ }
+
+ return stringBuilder.toString();
+ }
+
private static ObjectId insert(ObjectInserter inserter, CommitBuilder commit) throws IOException {
ObjectId id = inserter.insert(commit);
inserter.flush();
diff --git a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
index 8d50b86..cfe6bdf 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
@@ -21,6 +21,8 @@
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.exceptions.InvalidMergeStrategyException;
+import com.google.gerrit.exceptions.MergeWithConflictsNotSupportedException;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.MergeInput;
@@ -43,6 +45,8 @@
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.NotifyResolver;
import com.google.gerrit.server.change.PatchSetInserter;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -70,7 +74,6 @@
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.ChangeIdUtil;
@Singleton
@@ -140,7 +143,7 @@
try (Repository git = gitManager.openRepository(project);
ObjectInserter oi = git.newObjectInserter();
ObjectReader reader = oi.newReader();
- RevWalk rw = new RevWalk(reader)) {
+ CodeReviewRevWalk rw = CodeReviewCommit.newRevWalk(reader)) {
RevCommit sourceCommit = MergeUtil.resolveCommit(git, rw, merge.source);
if (!commits.canRead(projectState, git, sourceCommit)) {
@@ -161,7 +164,7 @@
Timestamp now = TimeUtil.nowTs();
IdentifiedUser me = user.get().asIdentifiedUser();
PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
- RevCommit newCommit =
+ CodeReviewCommit newCommit =
createMergeCommit(
in,
projectState,
@@ -181,7 +184,8 @@
bu.setRepository(git, rw, oi);
bu.setNotify(NotifyResolver.Result.none());
psInserter
- .setMessage("Uploaded patch set " + nextPsId.get() + ".")
+ .setMessage(messageForChange(nextPsId, newCommit))
+ .setWorkInProgress(!newCommit.getFilesWithGitConflicts().isEmpty())
.setCheckAddPatchSetPermission(false);
if (groups != null) {
psInserter.setGroups(groups);
@@ -191,7 +195,12 @@
}
ChangeJson json = jsonFactory.create(ListChangesOption.CURRENT_REVISION);
- return Response.ok(json.format(psInserter.getChange()));
+ ChangeInfo changeInfo = json.format(psInserter.getChange());
+ changeInfo.containsGitConflicts =
+ !newCommit.getFilesWithGitConflicts().isEmpty() ? true : null;
+ return Response.ok(changeInfo);
+ } catch (InvalidMergeStrategyException | MergeWithConflictsNotSupportedException e) {
+ throw new BadRequestException(e.getMessage());
}
}
@@ -205,18 +214,18 @@
try {
permissionBackend.currentUser().change(change).check(ChangePermission.READ);
} catch (AuthException e) {
- throw new UnprocessableEntityException("Read not permitted for " + baseChange);
+ throw new UnprocessableEntityException("Read not permitted for " + baseChange, e);
}
return psUtil.current(change);
}
- private RevCommit createMergeCommit(
+ private CodeReviewCommit createMergeCommit(
MergePatchSetInput in,
ProjectState projectState,
BranchNameKey dest,
Repository git,
ObjectInserter oi,
- RevWalk rw,
+ CodeReviewRevWalk rw,
RevCommit currentPsCommit,
RevCommit sourceCommit,
PersonIdent author,
@@ -255,6 +264,28 @@
mergeUtilFactory.create(projectState).mergeStrategyName());
return MergeUtil.createMergeCommit(
- oi, git.getConfig(), mergeTip, sourceCommit, mergeStrategy, author, commitMsg, rw);
+ oi,
+ git.getConfig(),
+ mergeTip,
+ sourceCommit,
+ mergeStrategy,
+ in.merge.allowConflicts,
+ author,
+ commitMsg,
+ rw);
+ }
+
+ private static String messageForChange(PatchSet.Id patchSetId, CodeReviewCommit commit) {
+ StringBuilder stringBuilder =
+ new StringBuilder(String.format("Uploaded patch set %s.", patchSetId.get()));
+
+ if (!commit.getFilesWithGitConflicts().isEmpty()) {
+ stringBuilder.append("\n\nThe following files contain Git conflicts:\n");
+ commit.getFilesWithGitConflicts().stream()
+ .sorted()
+ .forEach(filePath -> stringBuilder.append("* ").append(filePath).append("\n"));
+ }
+
+ return stringBuilder.toString();
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetDiff.java b/java/com/google/gerrit/server/restapi/change/GetDiff.java
index c1d286a..c3deb79 100644
--- a/java/com/google/gerrit/server/restapi/change/GetDiff.java
+++ b/java/com/google/gerrit/server/restapi/change/GetDiff.java
@@ -293,6 +293,7 @@
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
+ logger.atFine().withCause(e).log("invalid numeric value");
throw new CmdLineException(
owner,
localizable("\"%s\" is not a valid value for \"%s\""),
diff --git a/java/com/google/gerrit/server/restapi/change/GetFixPreview.java b/java/com/google/gerrit/server/restapi/change/GetFixPreview.java
index 0666756..b39424c 100644
--- a/java/com/google/gerrit/server/restapi/change/GetFixPreview.java
+++ b/java/com/google/gerrit/server/restapi/change/GetFixPreview.java
@@ -109,7 +109,7 @@
String fileName,
ImmutableList<FixReplacement> fixReplacements)
throws PermissionBackendException, AuthException, LargeObjectException,
- InvalidChangeOperationException, IOException {
+ InvalidChangeOperationException, IOException, ResourceNotFoundException {
PatchScriptFactoryForAutoFix psf =
patchScriptFactoryFactory.create(
git, notes, fileName, patchSet, fixReplacements, DiffPreferencesInfo.defaults());
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index 324069d..129df59 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReview.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReview.java
@@ -80,7 +80,6 @@
import com.google.gerrit.extensions.validators.CommentValidationContext;
import com.google.gerrit.extensions.validators.CommentValidationFailure;
import com.google.gerrit.extensions.validators.CommentValidator;
-import com.google.gerrit.json.OutputFormat;
import com.google.gerrit.mail.Address;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
@@ -125,11 +124,9 @@
import com.google.gerrit.server.update.UpdateException;
import com.google.gerrit.server.util.LabelVote;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gson.Gson;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
@@ -141,7 +138,6 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.OptionalInt;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -159,9 +155,6 @@
public static final String START_REVIEW_MESSAGE = "This change is ready for review.";
- private static final Gson GSON = OutputFormat.JSON_COMPACT.newGson();
- private static final int DEFAULT_ROBOT_COMMENT_SIZE_LIMIT_IN_BYTES = 1024 * 1024;
-
private final BatchUpdate.Factory updateFactory;
private final ChangeResource.Factory changeResourceFactory;
private final ChangeData.Factory changeDataFactory;
@@ -177,7 +170,6 @@
private final ReviewerAdder reviewerAdder;
private final AddReviewersEmail addReviewersEmail;
private final NotifyResolver notifyResolver;
- private final Config gerritConfig;
private final WorkInProgressOp.Factory workInProgressOpFactory;
private final ProjectCache projectCache;
private final PermissionBackend permissionBackend;
@@ -221,7 +213,6 @@
this.reviewerAdder = reviewerAdder;
this.addReviewersEmail = addReviewersEmail;
this.notifyResolver = notifyResolver;
- this.gerritConfig = gerritConfig;
this.workInProgressOpFactory = workInProgressOpFactory;
this.projectCache = projectCache;
this.permissionBackend = permissionBackend;
@@ -514,7 +505,8 @@
throw new AuthException(
String.format(
"not permitted to modify label \"%s\" on behalf of \"%s\"",
- type.getName(), in.onBehalfOf));
+ type.getName(), in.onBehalfOf),
+ e);
}
}
}
@@ -530,7 +522,7 @@
permissionBackend.user(reviewer).change(rev.getNotes()).check(ChangePermission.READ);
} catch (AuthException e) {
throw new UnprocessableEntityException(
- String.format("on_behalf_of account %s cannot see change", reviewer.getAccountId()));
+ String.format("on_behalf_of account %s cannot see change", reviewer.getAccountId()), e);
}
return new RevisionResource(
@@ -580,7 +572,7 @@
perm.check(new LabelPermission.WithValue(lt, val));
} catch (AuthException e) {
throw new AuthException(
- String.format("Applying label \"%s\": %d is restricted", lt.getName(), val));
+ String.format("Applying label \"%s\": %d is restricted", lt.getName(), val), e);
}
}
}
@@ -671,41 +663,15 @@
for (Map.Entry<String, List<RobotCommentInput>> e : in.entrySet()) {
String commentPath = e.getKey();
for (RobotCommentInput c : e.getValue()) {
- ensureSizeOfJsonInputIsWithinBounds(c);
ensureRobotIdIsSet(c.robotId, commentPath);
ensureRobotRunIdIsSet(c.robotRunId, commentPath);
ensureFixSuggestionsAreAddable(c.fixSuggestions, commentPath);
+ // Size is validated later, in CommentLimitsValidator.
}
}
checkComments(revision, in);
}
- private void ensureSizeOfJsonInputIsWithinBounds(RobotCommentInput robotCommentInput)
- throws BadRequestException {
- OptionalInt robotCommentSizeLimit = getRobotCommentSizeLimit();
- if (robotCommentSizeLimit.isPresent()) {
- int sizeLimit = robotCommentSizeLimit.getAsInt();
- byte[] robotCommentBytes = GSON.toJson(robotCommentInput).getBytes(StandardCharsets.UTF_8);
- int robotCommentSize = robotCommentBytes.length;
- if (robotCommentSize > sizeLimit) {
- throw new BadRequestException(
- String.format(
- "Size %d (bytes) of robot comment is greater than limit %d (bytes)",
- robotCommentSize, sizeLimit));
- }
- }
- }
-
- private OptionalInt getRobotCommentSizeLimit() {
- int robotCommentSizeLimit =
- gerritConfig.getInt(
- "change", "robotCommentSizeLimit", DEFAULT_ROBOT_COMMENT_SIZE_LIMIT_IN_BYTES);
- if (robotCommentSizeLimit <= 0) {
- return OptionalInt.empty();
- }
- return OptionalInt.of(robotCommentSizeLimit);
- }
-
private static void ensureRobotIdIsSet(String robotId, String commentPath)
throws BadRequestException {
if (robotId == null) {
@@ -909,8 +875,10 @@
user = ctx.getIdentifiedUser();
notes = ctx.getNotes();
ps = psUtil.get(ctx.getNotes(), psId);
- boolean dirty = insertComments(ctx);
- dirty |= insertRobotComments(ctx);
+ List<RobotComment> newRobotComments =
+ in.robotComments == null ? ImmutableList.of() : getNewRobotComments(ctx);
+ boolean dirty = insertComments(ctx, newRobotComments);
+ dirty |= insertRobotComments(ctx, newRobotComments);
dirty |= updateLabels(projectState, ctx);
dirty |= insertMessage(ctx);
return dirty;
@@ -937,7 +905,7 @@
ctx.getWhen());
}
- private boolean insertComments(ChangeContext ctx)
+ private boolean insertComments(ChangeContext ctx, List<RobotComment> newRobotComments)
throws UnprocessableEntityException, PatchListNotAvailableException,
CommentsRejectedException {
Map<String, List<CommentInput>> inputComments = in.comments;
@@ -1001,21 +969,21 @@
}
CommentValidationContext commentValidationCtx =
- CommentValidationContext.builder()
- .changeId(ctx.getChange().getChangeId())
- .project(ctx.getChange().getProject().get())
- .build();
+ CommentValidationContext.create(
+ ctx.getChange().getChangeId(), ctx.getChange().getProject().get());
switch (in.drafts) {
case PUBLISH:
case PUBLISH_ALL_REVISIONS:
validateComments(
- commentValidationCtx, Streams.concat(drafts.values().stream(), toPublish.stream()));
+ commentValidationCtx,
+ Streams.concat(
+ drafts.values().stream(), toPublish.stream(), newRobotComments.stream()));
publishCommentUtil.publish(ctx, ctx.getUpdate(psId), drafts.values(), in.tag);
comments.addAll(drafts.values());
break;
case KEEP:
- default:
- validateComments(commentValidationCtx, toPublish.stream());
+ validateComments(
+ commentValidationCtx, Stream.concat(toPublish.stream(), newRobotComments.stream()));
break;
}
ChangeUpdate changeUpdate = ctx.getUpdate(psId);
@@ -1031,10 +999,14 @@
.map(
comment ->
CommentForValidation.create(
+ comment instanceof RobotComment
+ ? CommentForValidation.CommentSource.ROBOT
+ : CommentForValidation.CommentSource.HUMAN,
comment.lineNbr > 0
? CommentForValidation.CommentType.INLINE_COMMENT
: CommentForValidation.CommentType.FILE_COMMENT,
- comment.message))
+ comment.message,
+ comment.getApproximateSize()))
.collect(toImmutableList());
ImmutableList<CommentValidationFailure> draftValidationFailures =
PublishCommentUtil.findInvalidComments(ctx, commentValidators, draftsForValidation);
@@ -1043,12 +1015,10 @@
}
}
- private boolean insertRobotComments(ChangeContext ctx) throws PatchListNotAvailableException {
+ private boolean insertRobotComments(ChangeContext ctx, List<RobotComment> newRobotComments) {
if (in.robotComments == null) {
return false;
}
-
- List<RobotComment> newRobotComments = getNewRobotComments(ctx);
commentsUtil.putRobotComments(ctx.getUpdate(psId), newRobotComments);
comments.addAll(newRobotComments);
return !newRobotComments.isEmpty();
@@ -1424,17 +1394,18 @@
}
if (!msg.isEmpty()) {
CommentValidationContext commentValidationCtx =
- CommentValidationContext.builder()
- .changeId(ctx.getChange().getChangeId())
- .project(ctx.getChange().getProject().get())
- .build();
+ CommentValidationContext.create(
+ ctx.getChange().getChangeId(), ctx.getChange().getProject().get());
ImmutableList<CommentValidationFailure> messageValidationFailure =
PublishCommentUtil.findInvalidComments(
commentValidationCtx,
commentValidators,
ImmutableList.of(
CommentForValidation.create(
- CommentForValidation.CommentType.CHANGE_MESSAGE, msg)));
+ CommentForValidation.CommentSource.HUMAN,
+ CommentForValidation.CommentType.CHANGE_MESSAGE,
+ msg,
+ msg.length())));
if (!messageValidationFailure.isEmpty()) {
throw new CommentsRejectedException(messageValidationFailure);
}
diff --git a/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java b/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java
index ee1f2e3..e6a60d5 100644
--- a/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java
+++ b/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java
@@ -174,7 +174,7 @@
archiveFormat.putEntry(aos, path, bos.toByteArray());
}
} catch (LimitExceededException e) {
- throw new NotImplementedException("The bundle is too big to generate at the server");
+ throw new NotImplementedException("The bundle is too big to generate at the server", e);
} catch (NoSuchProjectException e) {
throw new IOException(e);
}
diff --git a/java/com/google/gerrit/server/restapi/change/PutAssignee.java b/java/com/google/gerrit/server/restapi/change/PutAssignee.java
index dc1adfa..7b0d905 100644
--- a/java/com/google/gerrit/server/restapi/change/PutAssignee.java
+++ b/java/com/google/gerrit/server/restapi/change/PutAssignee.java
@@ -94,7 +94,7 @@
.change(rsrc.getNotes())
.check(ChangePermission.READ);
} catch (AuthException e) {
- throw new AuthException("read not permitted for " + input.assignee);
+ throw new AuthException("read not permitted for " + input.assignee, e);
}
try (BatchUpdate bu =
diff --git a/java/com/google/gerrit/server/restapi/change/QueryChanges.java b/java/com/google/gerrit/server/restapi/change/QueryChanges.java
index 544177f..878e714 100644
--- a/java/com/google/gerrit/server/restapi/change/QueryChanges.java
+++ b/java/com/google/gerrit/server/restapi/change/QueryChanges.java
@@ -129,7 +129,7 @@
try {
out = query();
} catch (QueryRequiresAuthException e) {
- throw new AuthException("Must be signed-in to use this operator");
+ throw new AuthException("Must be signed-in to use this operator", e);
} catch (QueryParseException e) {
logger.atFine().withCause(e).log("Reject change query with 400 Bad Request: %s", queries);
throw new BadRequestException(e.getMessage(), e);
diff --git a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
index 8fc59a2..d67fc6a 100644
--- a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
+++ b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.restapi.change;
import static com.google.common.base.MoreObjects.firstNonNull;
-import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
import static java.util.Objects.requireNonNull;
@@ -39,7 +38,6 @@
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
@@ -97,8 +95,7 @@
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
-public class RevertSubmission
- implements RestModifyView<ChangeResource, RevertInput>, UiAction<ChangeResource> {
+public class RevertSubmission implements RestModifyView<ChangeResource, RevertInput> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final Provider<InternalChangeQuery> queryProvider;
@@ -529,32 +526,6 @@
potentialCommitToReturn.getName(), changeNotes.getChange().getChangeId()));
}
- @Override
- public Description getDescription(ChangeResource rsrc) {
- Change change = rsrc.getChange();
- boolean projectStatePermitsWrite = false;
- try {
- projectStatePermitsWrite = projectCache.checkedGet(rsrc.getProject()).statePermitsWrite();
- } catch (IOException e) {
- logger.atSevere().withCause(e).log(
- "Failed to check if project state permits write: %s", rsrc.getProject());
- }
- return new UiAction.Description()
- .setLabel("Revert submission")
- .setTitle(
- "Revert this change and all changes that have been submitted together with this change")
- .setVisible(
- and(
- change.isMerged()
- && change.getSubmissionId() != null
- && isChangePartOfSubmission(change.getSubmissionId())
- && projectStatePermitsWrite,
- permissionBackend
- .user(rsrc.getUser())
- .ref(change.getDest())
- .testCond(CREATE_CHANGE)));
- }
-
/**
* @param submissionId the submission id of the change.
* @return True if the submission has more than one change, false otherwise.
diff --git a/java/com/google/gerrit/server/restapi/change/Submit.java b/java/com/google/gerrit/server/restapi/change/Submit.java
index 53e96f7..6fc3ece 100644
--- a/java/com/google/gerrit/server/restapi/change/Submit.java
+++ b/java/com/google/gerrit/server/restapi/change/Submit.java
@@ -438,7 +438,7 @@
permissionBackend.user(submitter).change(rsrc.getNotes()).check(ChangePermission.READ);
} catch (AuthException e) {
throw new UnprocessableEntityException(
- String.format("on_behalf_of account %s cannot see change", submitter.getAccountId()));
+ String.format("on_behalf_of account %s cannot see change", submitter.getAccountId()), e);
}
return submitter;
}
diff --git a/java/com/google/gerrit/server/restapi/config/ConfirmEmail.java b/java/com/google/gerrit/server/restapi/config/ConfirmEmail.java
index 03715c9..b55562e 100644
--- a/java/com/google/gerrit/server/restapi/config/ConfirmEmail.java
+++ b/java/com/google/gerrit/server/restapi/config/ConfirmEmail.java
@@ -88,7 +88,7 @@
}
throw new UnprocessableEntityException("invalid token");
} catch (EmailTokenVerifier.InvalidTokenException e) {
- throw new UnprocessableEntityException("invalid token");
+ throw new UnprocessableEntityException("invalid token", e);
} catch (AccountException e) {
throw new UnprocessableEntityException(e.getMessage());
}
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index 2d4bfe6..bbe38a3 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -23,11 +23,9 @@
import com.google.gerrit.extensions.common.AccountsInfo;
import com.google.gerrit.extensions.common.AuthInfo;
import com.google.gerrit.extensions.common.ChangeConfigInfo;
-import com.google.gerrit.extensions.common.ChangeIndexConfigInfo;
import com.google.gerrit.extensions.common.DownloadInfo;
import com.google.gerrit.extensions.common.DownloadSchemeInfo;
import com.google.gerrit.extensions.common.GerritInfo;
-import com.google.gerrit.extensions.common.IndexConfigInfo;
import com.google.gerrit.extensions.common.PluginConfigInfo;
import com.google.gerrit.extensions.common.ReceiveInfo;
import com.google.gerrit.extensions.common.ServerInfo;
@@ -45,6 +43,7 @@
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.avatar.AvatarProvider;
import com.google.gerrit.server.change.ArchiveFormat;
+import com.google.gerrit.server.change.MergeabilityComputationBehavior;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.AnonymousCowardName;
@@ -143,7 +142,6 @@
info.change = getChangeInfo();
info.download = getDownloadInfo();
info.gerrit = getGerritInfo();
- info.index = getIndexInfo();
info.noteDbEnabled = true;
info.plugin = getPluginInfo();
info.defaultTheme = getDefaultTheme();
@@ -230,10 +228,10 @@
info.updateDelay =
(int) ConfigUtil.getTimeUnit(config, "change", null, "updateDelay", 300, TimeUnit.SECONDS);
info.submitWholeTopic = toBoolean(MergeSuperSet.wholeTopicEnabled(config));
- info.excludeMergeableInChangeInfo =
- toBoolean(this.config.getBoolean("change", "api", "excludeMergeableInChangeInfo", false));
info.disablePrivateChanges =
toBoolean(this.config.getBoolean("change", null, "disablePrivateChanges", false));
+ info.mergeabilityComputationBehavior =
+ MergeabilityComputationBehavior.fromConfig(config).name();
return info;
}
@@ -299,14 +297,6 @@
return info;
}
- private IndexConfigInfo getIndexInfo() {
- ChangeIndexConfigInfo change = new ChangeIndexConfigInfo();
- change.indexMergeable = toBoolean(config.getBoolean("index", "change", "indexMergeable", true));
- IndexConfigInfo index = new IndexConfigInfo();
- index.change = change;
- return index;
- }
-
private String getDocUrl() {
String docUrl = config.getString("gerrit", null, "docUrl");
if (Strings.isNullOrEmpty(docUrl)) {
diff --git a/java/com/google/gerrit/server/restapi/config/TasksCollection.java b/java/com/google/gerrit/server/restapi/config/TasksCollection.java
index 289f76f..837d071 100644
--- a/java/com/google/gerrit/server/restapi/config/TasksCollection.java
+++ b/java/com/google/gerrit/server/restapi/config/TasksCollection.java
@@ -81,7 +81,7 @@
try {
taskId = (int) Long.parseLong(id.get(), 16);
} catch (NumberFormatException e) {
- throw new ResourceNotFoundException(id);
+ throw new ResourceNotFoundException(id, e);
}
Task<?> task = workQueue.getTask(taskId);
diff --git a/java/com/google/gerrit/server/restapi/group/AddMembers.java b/java/com/google/gerrit/server/restapi/group/AddMembers.java
index b60d78e..93d095d 100644
--- a/java/com/google/gerrit/server/restapi/group/AddMembers.java
+++ b/java/com/google/gerrit/server/restapi/group/AddMembers.java
@@ -138,7 +138,7 @@
try {
addMembers(groupUuid, newMemberIds);
} catch (NoSuchGroupException e) {
- throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid));
+ throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid), e);
}
return Response.ok(toAccountInfoList(newMemberIds));
}
@@ -234,7 +234,7 @@
}
throw new IllegalStateException();
} catch (UnprocessableEntityException e) {
- throw new ResourceNotFoundException(id);
+ throw new ResourceNotFoundException(id, e);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/AddSubgroups.java b/java/com/google/gerrit/server/restapi/group/AddSubgroups.java
index c969ace..3fd3f29 100644
--- a/java/com/google/gerrit/server/restapi/group/AddSubgroups.java
+++ b/java/com/google/gerrit/server/restapi/group/AddSubgroups.java
@@ -116,7 +116,7 @@
try {
addSubgroups(groupUuid, subgroupUuids);
} catch (NoSuchGroupException e) {
- throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid));
+ throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid), e);
}
return Response.ok(result);
}
@@ -153,7 +153,7 @@
}
throw new IllegalStateException();
} catch (UnprocessableEntityException e) {
- throw new ResourceNotFoundException(id);
+ throw new ResourceNotFoundException(id, e);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/CreateGroup.java b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
index b80b0e4..531d350 100644
--- a/java/com/google/gerrit/server/restapi/group/CreateGroup.java
+++ b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
@@ -220,7 +220,7 @@
return groupsUpdateProvider.get().createGroup(groupCreation, groupUpdateBuilder.build());
} catch (DuplicateKeyException e) {
throw new ResourceConflictException(
- "group '" + createGroupArgs.getGroupName() + "' already exists");
+ "group '" + createGroupArgs.getGroupName() + "' already exists", e);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/DeleteMembers.java b/java/com/google/gerrit/server/restapi/group/DeleteMembers.java
index fa1c5c6..a7b2e2d 100644
--- a/java/com/google/gerrit/server/restapi/group/DeleteMembers.java
+++ b/java/com/google/gerrit/server/restapi/group/DeleteMembers.java
@@ -78,7 +78,7 @@
try {
removeGroupMembers(groupUuid, membersToRemove);
} catch (NoSuchGroupException e) {
- throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid));
+ throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid), e);
}
return Response.none();
diff --git a/java/com/google/gerrit/server/restapi/group/DeleteSubgroups.java b/java/com/google/gerrit/server/restapi/group/DeleteSubgroups.java
index ef8612e..b9d6ca8 100644
--- a/java/com/google/gerrit/server/restapi/group/DeleteSubgroups.java
+++ b/java/com/google/gerrit/server/restapi/group/DeleteSubgroups.java
@@ -77,7 +77,7 @@
try {
removeSubgroups(groupUuid, subgroupsToRemove);
} catch (NoSuchGroupException e) {
- throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid));
+ throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid), e);
}
return Response.none();
diff --git a/java/com/google/gerrit/server/restapi/group/GetOwner.java b/java/com/google/gerrit/server/restapi/group/GetOwner.java
index 30d5c16..e8bdfaa 100644
--- a/java/com/google/gerrit/server/restapi/group/GetOwner.java
+++ b/java/com/google/gerrit/server/restapi/group/GetOwner.java
@@ -47,7 +47,7 @@
GroupControl c = controlFactory.validateFor(group.getOwnerGroupUUID());
return Response.ok(json.format(c.getGroup()));
} catch (NoSuchGroupException e) {
- throw new ResourceNotFoundException();
+ throw new ResourceNotFoundException(group.getOwnerGroupUUID().get(), e);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/PutDescription.java b/java/com/google/gerrit/server/restapi/group/PutDescription.java
index 98c773e..8fe4b20 100644
--- a/java/com/google/gerrit/server/restapi/group/PutDescription.java
+++ b/java/com/google/gerrit/server/restapi/group/PutDescription.java
@@ -66,7 +66,7 @@
try {
groupsUpdateProvider.get().updateGroup(groupUuid, groupUpdate);
} catch (NoSuchGroupException e) {
- throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid));
+ throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid), e);
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/PutName.java b/java/com/google/gerrit/server/restapi/group/PutName.java
index 802956a..9a3c87d 100644
--- a/java/com/google/gerrit/server/restapi/group/PutName.java
+++ b/java/com/google/gerrit/server/restapi/group/PutName.java
@@ -79,9 +79,9 @@
try {
groupsUpdateProvider.get().updateGroup(groupUuid, groupUpdate);
} catch (NoSuchGroupException e) {
- throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid));
+ throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid), e);
} catch (DuplicateKeyException e) {
- throw new ResourceConflictException("group with name " + newName + " already exists");
+ throw new ResourceConflictException("group with name " + newName + " already exists", e);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/PutOptions.java b/java/com/google/gerrit/server/restapi/group/PutOptions.java
index c96972f..53bf571 100644
--- a/java/com/google/gerrit/server/restapi/group/PutOptions.java
+++ b/java/com/google/gerrit/server/restapi/group/PutOptions.java
@@ -66,7 +66,7 @@
try {
groupsUpdateProvider.get().updateGroup(groupUuid, groupUpdate);
} catch (NoSuchGroupException e) {
- throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid));
+ throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid), e);
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/PutOwner.java b/java/com/google/gerrit/server/restapi/group/PutOwner.java
index 709f7e3..04129af 100644
--- a/java/com/google/gerrit/server/restapi/group/PutOwner.java
+++ b/java/com/google/gerrit/server/restapi/group/PutOwner.java
@@ -77,7 +77,7 @@
try {
groupsUpdateProvider.get().updateGroup(groupUuid, groupUpdate);
} catch (NoSuchGroupException e) {
- throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid));
+ throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid), e);
}
}
return Response.ok(json.format(owner));
diff --git a/java/com/google/gerrit/server/restapi/project/BranchesCollection.java b/java/com/google/gerrit/server/restapi/project/BranchesCollection.java
index fe2fb0f..2d78bb0 100644
--- a/java/com/google/gerrit/server/restapi/project/BranchesCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/BranchesCollection.java
@@ -82,9 +82,9 @@
.check(RefPermission.READ);
return new BranchResource(parent.getProjectState(), parent.getUser(), ref);
} catch (AuthException notAllowed) {
- throw new ResourceNotFoundException(id);
+ throw new ResourceNotFoundException(id, notAllowed);
} catch (RepositoryNotFoundException noRepo) {
- throw new ResourceNotFoundException();
+ throw new ResourceNotFoundException(id, noRepo);
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/CommitsCollection.java b/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
index 757b650..21d7f0b 100644
--- a/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
@@ -87,7 +87,7 @@
try {
objectId = ObjectId.fromString(id.get());
} catch (IllegalArgumentException e) {
- throw new ResourceNotFoundException(id);
+ throw new ResourceNotFoundException(id, e);
}
try (Repository repo = repoManager.openRepository(parent.getNameKey());
@@ -102,7 +102,7 @@
}
return new CommitResource(parent, commit);
} catch (MissingObjectException | IncorrectObjectTypeException e) {
- throw new ResourceNotFoundException(id);
+ throw new ResourceNotFoundException(id, e);
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java b/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
index 76f1105..fe48301 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
@@ -102,7 +102,7 @@
try {
forProject.ref(RefNames.REFS_CONFIG).check(RefPermission.CREATE_CHANGE);
} catch (AuthException denied) {
- throw new AuthException("cannot create change for " + RefNames.REFS_CONFIG);
+ throw new AuthException("cannot create change for " + RefNames.REFS_CONFIG, denied);
}
}
projectCache.checkedGet(rsrc.getNameKey()).checkStatePermitsWrite();
diff --git a/java/com/google/gerrit/server/restapi/project/CreateBranch.java b/java/com/google/gerrit/server/restapi/project/CreateBranch.java
index 67213c5..c15fdeb 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateBranch.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateBranch.java
@@ -127,7 +127,7 @@
try {
object = rw.parseCommit(object);
} catch (IncorrectObjectTypeException notCommit) {
- throw new BadRequestException("\"" + input.revision + "\" not a commit");
+ throw new BadRequestException("\"" + input.revision + "\" not a commit", notCommit);
}
}
@@ -197,7 +197,7 @@
throw err;
}
} catch (RefUtil.InvalidRevisionException e) {
- throw new BadRequestException("invalid revision \"" + input.revision + "\"");
+ throw new BadRequestException("invalid revision \"" + input.revision + "\"", e);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/CreateLabel.java b/java/com/google/gerrit/server/restapi/project/CreateLabel.java
index 5d51527..a85ad39 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateLabel.java
@@ -191,6 +191,10 @@
input.copyAllScoresOnMergeFirstParentUpdate);
}
+ if (input.copyValues != null) {
+ labelType.setCopyValues(input.copyValues);
+ }
+
if (input.allowPostSubmit != null) {
labelType.setAllowPostSubmit(input.allowPostSubmit);
}
diff --git a/java/com/google/gerrit/server/restapi/project/CreateTag.java b/java/com/google/gerrit/server/restapi/project/CreateTag.java
index 8fdf5e4..b087f15 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateTag.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateTag.java
@@ -149,7 +149,7 @@
}
}
} catch (InvalidRevisionException e) {
- throw new BadRequestException("Invalid base revision");
+ throw new BadRequestException("Invalid base revision", e);
} catch (GitAPIException e) {
logger.atSevere().withCause(e).log("Cannot create tag \"%s\"", ref);
throw new IOException(e);
diff --git a/java/com/google/gerrit/server/restapi/project/DashboardsCollection.java b/java/com/google/gerrit/server/restapi/project/DashboardsCollection.java
index 606f3bef..ca48109 100644
--- a/java/com/google/gerrit/server/restapi/project/DashboardsCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/DashboardsCollection.java
@@ -103,14 +103,14 @@
try {
info = newDashboardInfo(id.get());
} catch (InvalidDashboardId e) {
- throw new ResourceNotFoundException(id);
+ throw new ResourceNotFoundException(id, e);
}
for (ProjectState ps : parent.getProjectState().tree()) {
try {
return parse(ps, parent.getProjectState(), parent.getUser(), info);
} catch (AmbiguousObjectException | ConfigInvalidException | IncorrectObjectTypeException e) {
- throw new ResourceNotFoundException(id);
+ throw new ResourceNotFoundException(id, e);
} catch (ResourceNotFoundException e) {
continue;
}
@@ -135,7 +135,7 @@
permissionBackend.user(user).project(parent.getNameKey()).ref(ref).check(RefPermission.READ);
} catch (AuthException e) {
// Don't leak the project's existence
- throw new ResourceNotFoundException(info.id);
+ throw new ResourceNotFoundException(info.id, e);
}
if (!Repository.isValidRefName(ref)) {
throw new ResourceNotFoundException(info.id);
@@ -151,7 +151,7 @@
BlobBasedConfig cfg = new BlobBasedConfig(null, git, objId);
return new DashboardResource(current, user, ref, info.path, cfg, false);
} catch (RepositoryNotFoundException e) {
- throw new ResourceNotFoundException(info.id);
+ throw new ResourceNotFoundException(info.id, e);
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/GetAccess.java b/java/com/google/gerrit/server/restapi/project/GetAccess.java
index 550c4c4..a9a9403 100644
--- a/java/com/google/gerrit/server/restapi/project/GetAccess.java
+++ b/java/com/google/gerrit/server/restapi/project/GetAccess.java
@@ -165,7 +165,7 @@
} catch (ConfigInvalidException e) {
throw new ResourceConflictException(e.getMessage());
} catch (RepositoryNotFoundException e) {
- throw new ResourceNotFoundException(rsrc.getName());
+ throw new ResourceNotFoundException(rsrc.getName(), e);
}
// The following implementation must match the ProjectAccessFactory JSON RPC endpoint.
diff --git a/java/com/google/gerrit/server/restapi/project/GetHead.java b/java/com/google/gerrit/server/restapi/project/GetHead.java
index 928db97..4e0a144 100644
--- a/java/com/google/gerrit/server/restapi/project/GetHead.java
+++ b/java/com/google/gerrit/server/restapi/project/GetHead.java
@@ -82,13 +82,13 @@
.project(rsrc.getNameKey())
.check(ProjectPermission.WRITE_CONFIG);
} catch (AuthException ae) {
- throw new AuthException("not allowed to see HEAD");
+ throw new AuthException("not allowed to see HEAD", ae);
}
}
}
throw new ResourceNotFoundException(Constants.HEAD);
} catch (RepositoryNotFoundException e) {
- throw new ResourceNotFoundException(rsrc.getName());
+ throw new ResourceNotFoundException(rsrc.getName(), e);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/GetReflog.java b/java/com/google/gerrit/server/restapi/project/GetReflog.java
index a249f26..f9c6fd9 100644
--- a/java/com/google/gerrit/server/restapi/project/GetReflog.java
+++ b/java/com/google/gerrit/server/restapi/project/GetReflog.java
@@ -104,7 +104,7 @@
} catch (UnsupportedOperationException e) {
String msg = "reflog not supported on repo " + rsrc.getNameKey().get();
logger.atSevere().log(msg);
- throw new MethodNotAllowedException(msg);
+ throw new MethodNotAllowedException(msg, e);
}
if (r == null) {
throw new ResourceNotFoundException(rsrc.getRef());
diff --git a/java/com/google/gerrit/server/restapi/project/GetStatistics.java b/java/com/google/gerrit/server/restapi/project/GetStatistics.java
index d68e0af..db97855 100644
--- a/java/com/google/gerrit/server/restapi/project/GetStatistics.java
+++ b/java/com/google/gerrit/server/restapi/project/GetStatistics.java
@@ -51,7 +51,7 @@
} catch (GitAPIException | JGitInternalException e) {
throw new ResourceConflictException(e.getMessage());
} catch (IOException e) {
- throw new ResourceNotFoundException(rsrc.getName());
+ throw new ResourceNotFoundException(rsrc.getName(), e);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/ListBranches.java b/java/com/google/gerrit/server/restapi/project/ListBranches.java
index cd20820..fecdc8e 100644
--- a/java/com/google/gerrit/server/restapi/project/ListBranches.java
+++ b/java/com/google/gerrit/server/restapi/project/ListBranches.java
@@ -149,7 +149,7 @@
}
return toBranchInfo(rsrc, ImmutableList.of(r)).get(0);
} catch (RepositoryNotFoundException noRepo) {
- throw new ResourceNotFoundException();
+ throw new ResourceNotFoundException(rsrc.getNameKey().get(), noRepo);
}
}
@@ -165,7 +165,7 @@
.exactRef(Constants.HEAD, RefNames.REFS_CONFIG, RefNames.REFS_USERS_DEFAULT)
.values());
} catch (RepositoryNotFoundException noGitRepository) {
- throw new ResourceNotFoundException();
+ throw new ResourceNotFoundException(rsrc.getNameKey().get(), noGitRepository);
}
return toBranchInfo(rsrc, refs);
}
diff --git a/java/com/google/gerrit/server/restapi/project/ListDashboards.java b/java/com/google/gerrit/server/restapi/project/ListDashboards.java
index 058083c..4406719 100644
--- a/java/com/google/gerrit/server/restapi/project/ListDashboards.java
+++ b/java/com/google/gerrit/server/restapi/project/ListDashboards.java
@@ -120,7 +120,7 @@
}
return all;
} catch (RepositoryNotFoundException e) {
- throw new ResourceNotFoundException();
+ throw new ResourceNotFoundException(project, e);
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/ListTags.java b/java/com/google/gerrit/server/restapi/project/ListTags.java
index 8cea7f5..123c78a 100644
--- a/java/com/google/gerrit/server/restapi/project/ListTags.java
+++ b/java/com/google/gerrit/server/restapi/project/ListTags.java
@@ -219,7 +219,7 @@
try {
return repoManager.openRepository(project);
} catch (RepositoryNotFoundException noGitRepository) {
- throw new ResourceNotFoundException();
+ throw new ResourceNotFoundException(project.get(), noGitRepository);
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/PutConfig.java b/java/com/google/gerrit/server/restapi/project/PutConfig.java
index a0badd7..9f9433b 100644
--- a/java/com/google/gerrit/server/restapi/project/PutConfig.java
+++ b/java/com/google/gerrit/server/restapi/project/PutConfig.java
@@ -190,7 +190,7 @@
uiActions,
views);
} catch (RepositoryNotFoundException notFound) {
- throw new ResourceNotFoundException(projectName.get());
+ throw new ResourceNotFoundException(projectName.get(), notFound);
} catch (ConfigInvalidException err) {
throw new ResourceConflictException("Cannot read project " + projectName, err);
} catch (IOException err) {
diff --git a/java/com/google/gerrit/server/restapi/project/PutDescription.java b/java/com/google/gerrit/server/restapi/project/PutDescription.java
index 0281b8d..a0b9feb 100644
--- a/java/com/google/gerrit/server/restapi/project/PutDescription.java
+++ b/java/com/google/gerrit/server/restapi/project/PutDescription.java
@@ -92,7 +92,7 @@
? Response.none()
: Response.ok(project.getDescription());
} catch (RepositoryNotFoundException notFound) {
- throw new ResourceNotFoundException(resource.getName());
+ throw new ResourceNotFoundException(resource.getName(), notFound);
} catch (ConfigInvalidException e) {
throw new ResourceConflictException(
String.format("invalid project.config: %s", e.getMessage()));
diff --git a/java/com/google/gerrit/server/restapi/project/SetAccess.java b/java/com/google/gerrit/server/restapi/project/SetAccess.java
index f8c7234..02c1b54 100644
--- a/java/com/google/gerrit/server/restapi/project/SetAccess.java
+++ b/java/com/google/gerrit/server/restapi/project/SetAccess.java
@@ -130,7 +130,7 @@
} catch (InvalidNameException e) {
throw new BadRequestException(e.toString());
} catch (ConfigInvalidException e) {
- throw new ResourceConflictException(rsrc.getName());
+ throw new ResourceConflictException(rsrc.getName(), e);
}
return Response.ok(getAccess.apply(rsrc.getNameKey()));
diff --git a/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java b/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java
index 8879fae..9920be0 100644
--- a/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java
+++ b/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java
@@ -89,7 +89,7 @@
new ProjectResource(rsrc.getProjectState(), rsrc.getUser()),
IdString.fromUrl(input.id));
} catch (ResourceNotFoundException e) {
- throw new BadRequestException("dashboard " + input.id + " not found");
+ throw new BadRequestException("dashboard " + input.id + " not found", e);
} catch (ConfigInvalidException e) {
throw new ResourceConflictException(e.getMessage());
}
@@ -125,7 +125,7 @@
}
return Response.none();
} catch (RepositoryNotFoundException notFound) {
- throw new ResourceNotFoundException(rsrc.getProjectState().getProject().getName());
+ throw new ResourceNotFoundException(rsrc.getProjectState().getProject().getName(), notFound);
} catch (ConfigInvalidException e) {
throw new ResourceConflictException(
String.format("invalid project.config: %s", e.getMessage()));
diff --git a/java/com/google/gerrit/server/restapi/project/SetHead.java b/java/com/google/gerrit/server/restapi/project/SetHead.java
index 946695e..ae70e46 100644
--- a/java/com/google/gerrit/server/restapi/project/SetHead.java
+++ b/java/com/google/gerrit/server/restapi/project/SetHead.java
@@ -114,7 +114,7 @@
}
return Response.ok(ref);
} catch (RepositoryNotFoundException e) {
- throw new ResourceNotFoundException(rsrc.getName());
+ throw new ResourceNotFoundException(rsrc.getName(), e);
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/SetLabel.java b/java/com/google/gerrit/server/restapi/project/SetLabel.java
index 824b4ed..0a35865 100644
--- a/java/com/google/gerrit/server/restapi/project/SetLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/SetLabel.java
@@ -205,6 +205,11 @@
dirty = true;
}
+ if (input.copyValues != null) {
+ labelType.setCopyValues(input.copyValues);
+ dirty = true;
+ }
+
if (input.allowPostSubmit != null) {
labelType.setAllowPostSubmit(input.allowPostSubmit);
dirty = true;
diff --git a/java/com/google/gerrit/server/restapi/project/SetParent.java b/java/com/google/gerrit/server/restapi/project/SetParent.java
index 7241fcf..4cf0182 100644
--- a/java/com/google/gerrit/server/restapi/project/SetParent.java
+++ b/java/com/google/gerrit/server/restapi/project/SetParent.java
@@ -121,7 +121,7 @@
requireNonNull(parent);
return parent.get();
} catch (RepositoryNotFoundException notFound) {
- throw new ResourceNotFoundException(rsrc.getName());
+ throw new ResourceNotFoundException(rsrc.getName(), notFound);
} catch (ConfigInvalidException e) {
throw new ResourceConflictException(
String.format("invalid project.config: %s", e.getMessage()));
diff --git a/java/com/google/gerrit/server/rules/StoredValues.java b/java/com/google/gerrit/server/rules/StoredValues.java
index 085475e..1e08a24 100644
--- a/java/com/google/gerrit/server/rules/StoredValues.java
+++ b/java/com/google/gerrit/server/rules/StoredValues.java
@@ -57,7 +57,8 @@
try {
return cd.change();
} catch (StorageException e) {
- throw new SystemException("Cannot load change " + cd.getId());
+ throw new SystemException(
+ String.format("Cannot load change %s: %s", cd.getId(), e.getMessage()));
}
}
@@ -101,7 +102,7 @@
try {
patchList = plCache.get(plKey, project);
} catch (PatchListNotAvailableException e) {
- throw new SystemException("Cannot create " + plKey);
+ throw new SystemException(String.format("Cannot create %s: %s", plKey, e.getMessage()));
}
return patchList;
}
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java b/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java
index 5dec581..0e22af9 100644
--- a/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java
@@ -104,7 +104,7 @@
try {
schemaCreator.ensureCreated();
} catch (IOException | ConfigInvalidException e) {
- throw new StorageException("Cannot initialize Gerrit site");
+ throw new StorageException("Cannot initialize Gerrit site", e);
}
}
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java b/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java
index 02250f2..b450a4b 100644
--- a/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java
@@ -28,7 +28,7 @@
public class NoteDbSchemaVersions {
static final ImmutableSortedMap<Integer, Class<? extends NoteDbSchemaVersion>> ALL =
// List all supported NoteDb schema versions here.
- Stream.of(Schema_180.class, Schema_181.class)
+ Stream.of(Schema_180.class, Schema_181.class, Schema_182.class)
.collect(toImmutableSortedMap(naturalOrder(), v -> guessVersion(v).get(), v -> v));
public static final int FIRST = ALL.firstKey();
diff --git a/java/com/google/gerrit/server/schema/Schema_182.java b/java/com/google/gerrit/server/schema/Schema_182.java
new file mode 100644
index 0000000..3928b2b
--- /dev/null
+++ b/java/com/google/gerrit/server/schema/Schema_182.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.DeleteZombieCommentsRefs;
+
+/**
+ * Schema 182 for Gerrit metadata.
+ *
+ * <p>Upgrading to this schema version cleans the zombie draft comment refs in NoteDb
+ */
+public class Schema_182 implements NoteDbSchemaVersion {
+ @Override
+ public void upgrade(Arguments args, UpdateUI ui) throws Exception {
+ AllUsersName allUsers = args.allUsers;
+ GitRepositoryManager gitRepoManager = args.repoManager;
+ DeleteZombieCommentsRefs cleanup = new DeleteZombieCommentsRefs(allUsers, gitRepoManager, 100);
+ cleanup.execute();
+ }
+}
diff --git a/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java b/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
index a6e01af..65e18ad 100644
--- a/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
+++ b/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
@@ -161,7 +161,8 @@
} catch (MergeConflictException mce) {
// Unlike in Cherry-pick case, this should never happen.
toMerge.setStatusCode(CommitMergeStatus.REBASE_MERGE_CONFLICT);
- throw new IllegalStateException("MergeConflictException on message edit must not happen");
+ throw new IllegalStateException(
+ "MergeConflictException on message edit must not happen", mce);
} catch (MergeIdenticalTreeException mie) {
// this should not happen
toMerge.setStatusCode(SKIPPED_IDENTICAL_TREE);
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
index 6be4a91..cc40a30 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
@@ -550,7 +550,7 @@
return args.submoduleOp.composeGitlinksCommit(args.destBranch, commit);
} catch (SubmoduleException | IOException e) {
throw new IntegrationException(
- "cannot update gitlink for the commit at branch: " + args.destBranch);
+ "cannot update gitlink for the commit at branch: " + args.destBranch, e);
}
}
}
diff --git a/java/com/google/gerrit/server/util/SocketUtil.java b/java/com/google/gerrit/server/util/SocketUtil.java
index afa2aee..e617650 100644
--- a/java/com/google/gerrit/server/util/SocketUtil.java
+++ b/java/com/google/gerrit/server/util/SocketUtil.java
@@ -96,7 +96,7 @@
try {
port = Integer.parseInt(portStr);
} catch (NumberFormatException e) {
- throw new IllegalArgumentException("invalid port: " + desc);
+ throw new IllegalArgumentException("invalid port: " + desc, e);
}
} else {
port = defaultPort;
diff --git a/java/com/google/gerrit/sshd/BaseCommand.java b/java/com/google/gerrit/sshd/BaseCommand.java
index ac22dfc..5d7dba2 100644
--- a/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/java/com/google/gerrit/sshd/BaseCommand.java
@@ -387,6 +387,10 @@
return new UnloggedFailure(1, "fatal: " + msg);
}
+ protected UnloggedFailure die(String msg, Throwable why) {
+ return new UnloggedFailure(1, "fatal: " + msg, why);
+ }
+
protected UnloggedFailure die(Throwable why) {
return new UnloggedFailure(1, "fatal: " + why.getMessage(), why);
}
diff --git a/java/com/google/gerrit/sshd/ChangeArgumentParser.java b/java/com/google/gerrit/sshd/ChangeArgumentParser.java
index c273b4c..491bcb8 100644
--- a/java/com/google/gerrit/sshd/ChangeArgumentParser.java
+++ b/java/com/google/gerrit/sshd/ChangeArgumentParser.java
@@ -111,7 +111,7 @@
try {
changeResource = changesCollection.parse(cId);
} catch (RestApiException e) {
- throw new UnloggedFailure(1, "\"" + id + "\" no such change");
+ throw new UnloggedFailure(1, "\"" + id + "\" no such change", e);
}
changes.put(cId, changeResource);
}
diff --git a/java/com/google/gerrit/sshd/SshKeyCreatorImpl.java b/java/com/google/gerrit/sshd/SshKeyCreatorImpl.java
index e6c3a40..02a9e48 100644
--- a/java/com/google/gerrit/sshd/SshKeyCreatorImpl.java
+++ b/java/com/google/gerrit/sshd/SshKeyCreatorImpl.java
@@ -34,11 +34,10 @@
SshUtil.parse(key);
return key;
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
- throw new InvalidSshKeyException();
-
+ throw new InvalidSshKeyException(e);
} catch (NoSuchProviderException e) {
logger.atSevere().withCause(e).log("Cannot parse SSH key");
- throw new InvalidSshKeyException();
+ throw new InvalidSshKeyException(e);
}
}
}
diff --git a/java/com/google/gerrit/sshd/SuExec.java b/java/com/google/gerrit/sshd/SuExec.java
index f485bd7..ea163d5 100644
--- a/java/com/google/gerrit/sshd/SuExec.java
+++ b/java/com/google/gerrit/sshd/SuExec.java
@@ -127,9 +127,9 @@
try {
permissionBackend.user(caller).check(GlobalPermission.RUN_AS);
} catch (AuthException e) {
- throw die("suexec not permitted");
+ throw die("suexec not permitted", e);
} catch (PermissionBackendException e) {
- throw die("suexec not available: " + e);
+ throw die("suexec not available", e);
}
}
}
diff --git a/java/com/google/gerrit/sshd/commands/LsUserRefs.java b/java/com/google/gerrit/sshd/commands/LsUserRefs.java
index b4a2b42..80aee01 100644
--- a/java/com/google/gerrit/sshd/commands/LsUserRefs.java
+++ b/java/com/google/gerrit/sshd/commands/LsUserRefs.java
@@ -104,9 +104,9 @@
throw new Failure(1, "fatal: Error reading refs: '" + projectName, e);
}
} catch (RepositoryNotFoundException e) {
- throw die("'" + projectName + "': not a git archive");
+ throw die("'" + projectName + "': not a git archive", e);
} catch (IOException e) {
- throw die("Error opening: '" + projectName);
+ throw die("Error opening: '" + projectName, e);
}
}
}
diff --git a/java/com/google/gerrit/sshd/commands/PatchSetParser.java b/java/com/google/gerrit/sshd/commands/PatchSetParser.java
index e40ab87..f804c2d 100644
--- a/java/com/google/gerrit/sshd/commands/PatchSetParser.java
+++ b/java/com/google/gerrit/sshd/commands/PatchSetParser.java
@@ -99,7 +99,7 @@
try {
patchSetId = PatchSet.Id.parse(token);
} catch (IllegalArgumentException e) {
- throw error("\"" + token + "\" is not a valid patch set");
+ throw error("\"" + token + "\" is not a valid patch set", e);
}
ChangeNotes notes = getNotes(projectState, patchSetId.changeId());
PatchSet patchSet = psUtil.get(notes, patchSetId);
@@ -130,7 +130,7 @@
ChangeNotes notes = changeFinder.findOne(changeId);
return notesFactory.create(notes.getProjectName(), changeId);
} catch (NoSuchChangeException e) {
- throw error("\"" + changeId + "\" no such change");
+ throw error("\"" + changeId + "\" no such change", e);
}
}
@@ -153,4 +153,8 @@
public static UnloggedFailure error(String msg) {
return new UnloggedFailure(1, msg);
}
+
+ public static UnloggedFailure error(String msg, Throwable why) {
+ return new UnloggedFailure(1, msg, why);
+ }
}
diff --git a/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java b/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
index 8b045ec..cfb47f7 100644
--- a/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
+++ b/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
@@ -70,21 +70,21 @@
try {
data = Files.newInputStream(new File(source).toPath());
} catch (IOException e) {
- throw die("cannot read " + source);
+ throw die("cannot read " + source, e);
}
} else {
try {
data = new URL(source).openStream();
} catch (MalformedURLException e) {
- throw die("invalid url " + source);
+ throw die("invalid url " + source, e);
} catch (IOException e) {
- throw die("cannot read " + source);
+ throw die("cannot read " + source, e);
}
}
try {
loader.installPluginFromStream(name, data);
} catch (IOException e) {
- throw die("cannot install plugin");
+ throw die("cannot install plugin", e);
} catch (PluginInstallException e) {
e.printStackTrace(stderr);
String msg = String.format("Plugin failed to install. Cause: %s", e.getMessage());
diff --git a/java/com/google/gerrit/sshd/commands/Receive.java b/java/com/google/gerrit/sshd/commands/Receive.java
index e2fb633..6791b70 100644
--- a/java/com/google/gerrit/sshd/commands/Receive.java
+++ b/java/com/google/gerrit/sshd/commands/Receive.java
@@ -78,7 +78,7 @@
.project(project.getNameKey())
.check(ProjectPermission.RUN_RECEIVE_PACK);
} catch (AuthException e) {
- throw new Failure(1, "fatal: receive-pack not permitted on this server");
+ throw new Failure(1, "fatal: receive-pack not permitted on this server", e);
} catch (PermissionBackendException e) {
throw new Failure(1, "fatal: unable to check permissions " + e);
}
diff --git a/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 886f21e..3a1bf6c 100644
--- a/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -327,7 +327,7 @@
try {
allProjectsState = projectCache.checkedGet(allProjects);
} catch (IOException e) {
- throw die("missing " + allProjects.get());
+ throw die("missing " + allProjects.get(), e);
}
for (LabelType type : allProjectsState.getLabelTypes().getLabelTypes()) {
diff --git a/java/com/google/gerrit/sshd/commands/Upload.java b/java/com/google/gerrit/sshd/commands/Upload.java
index 87ae493..6c4e80a 100644
--- a/java/com/google/gerrit/sshd/commands/Upload.java
+++ b/java/com/google/gerrit/sshd/commands/Upload.java
@@ -61,9 +61,9 @@
try {
perm.check(ProjectPermission.RUN_UPLOAD_PACK);
} catch (AuthException e) {
- throw new Failure(1, "fatal: upload-pack not permitted on this server");
+ throw new Failure(1, "fatal: upload-pack not permitted on this server", e);
} catch (PermissionBackendException e) {
- throw new Failure(1, "fatal: unable to check permissions " + e);
+ throw new Failure(1, "fatal: unable to check permissions ", e);
}
Repository permissionAwareRepo = PermissionAwareRepositoryManager.wrap(repo, perm);
diff --git a/java/com/google/gerrit/testing/IndexConfig.java b/java/com/google/gerrit/testing/IndexConfig.java
index fb6c926..a8ed5be 100644
--- a/java/com/google/gerrit/testing/IndexConfig.java
+++ b/java/com/google/gerrit/testing/IndexConfig.java
@@ -14,6 +14,7 @@
package com.google.gerrit.testing;
+import com.google.gerrit.server.change.MergeabilityComputationBehavior;
import org.eclipse.jgit.lib.Config;
public class IndexConfig {
@@ -23,9 +24,10 @@
public static Config createFromExistingConfig(Config cfg) {
cfg.setInt("index", null, "maxPages", 10);
- // To avoid this flakiness indexMergeable is switched off for the tests as it incurs background
+ // To avoid this flakiness indexing mergeable is disabled for the tests as it incurs background
// reindex calls.
- cfg.setBoolean("index", "change", "indexMergeable", false);
+ cfg.setEnum(
+ "change", null, "mergeabilityComputationBehavior", MergeabilityComputationBehavior.NEVER);
cfg.setString("trackingid", "query-bug", "footer", "Bug:");
cfg.setString("trackingid", "query-bug", "match", "QUERY\\d{2,8}");
cfg.setString("trackingid", "query-bug", "system", "querytests");
diff --git a/javatests/com/google/gerrit/acceptance/api/change/AbandonIT.java b/javatests/com/google/gerrit/acceptance/api/change/AbandonIT.java
index 7ecbe69..d04eebd 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/AbandonIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/AbandonIT.java
@@ -143,7 +143,9 @@
@UseClockStep
@GerritConfig(name = "changeCleanup.abandonAfter", value = "1w")
@GerritConfig(name = "changeCleanup.abandonIfMergeable", value = "false")
- @GerritConfig(name = "index.change.indexMergeable", value = "true")
+ @GerritConfig(
+ name = "change.mergeabilityComputationBehavior",
+ value = "API_REF_UPDATED_AND_CHANGE_REINDEX")
public void notAbandonedIfMergeableWhenMergeableOperatorIsEnabled() throws Exception {
ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
@@ -180,14 +182,14 @@
}
/**
- * When indexMergeable is disabled then the abandonIfMergeable option is ineffective and the auto
- * abandon behaves as though it were set to its default value (true).
+ * When indexing mergeable is disabled then the abandonIfMergeable option is ineffective and the
+ * auto abandon behaves as though it were set to its default value (true).
*/
@Test
@UseClockStep
@GerritConfig(name = "changeCleanup.abandonAfter", value = "1w")
@GerritConfig(name = "changeCleanup.abandonIfMergeable", value = "false")
- @GerritConfig(name = "index.change.indexMergeable", value = "false")
+ @GerritConfig(name = "change.mergeabilityComputationBehavior", value = "NEVER")
public void abandonedIfMergeableWhenMergeableOperatorIsDisabled() throws Exception {
ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 74f753d..ae5066b 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -47,6 +47,7 @@
import static com.google.gerrit.extensions.client.ReviewerState.CC;
import static com.google.gerrit.extensions.client.ReviewerState.REMOVED;
import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
+import static com.google.gerrit.git.ObjectIds.abbreviateName;
import static com.google.gerrit.server.StarredChangesUtil.DEFAULT_LABEL;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.CHANGE_OWNER;
@@ -144,8 +145,10 @@
import com.google.gerrit.extensions.common.MergePatchSetInput;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.common.TrackingIdInfo;
+import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -180,6 +183,7 @@
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.name.Named;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
@@ -295,19 +299,7 @@
}
@Test
- public void skipMergeable() throws Exception {
- PushOneCommit.Result r = createChange();
- String triplet = project.get() + "~master~" + r.getChangeId();
- ChangeInfo c =
- gApi.changes().id(triplet).get(ImmutableList.of(ListChangesOption.SKIP_MERGEABLE));
- assertThat(c.mergeable).isNull();
-
- c = gApi.changes().id(triplet).get();
- assertThat(c.mergeable).isTrue();
- }
-
- @Test
- @GerritConfig(name = "change.api.excludeMergeableInChangeInfo", value = "true")
+ @GerritConfig(name = "change.mergeabilityComputationBehavior", value = "NEVER")
public void excludeMergeableInChangeInfo() throws Exception {
PushOneCommit.Result r = createChange();
ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
@@ -3196,20 +3188,19 @@
@Test
public void createMergePatchSet() throws Exception {
- PushOneCommit.Result start = pushTo("refs/heads/master");
- start.assertOkStatus();
- // create a change for master
- PushOneCommit.Result r = createChange();
- r.assertOkStatus();
- String changeId = r.getChangeId();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch("dev");
- testRepo.reset(start.getCommit());
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ testRepo.reset(initialHead);
PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
currentMaster.assertOkStatus();
String parent = currentMaster.getCommit().getName();
// push a commit into dev branch
- createBranch("dev");
+ testRepo.reset(initialHead);
PushOneCommit.Result changeA =
pushFactory
.create(user.newIdent(), testRepo, "change A", "A.txt", "A content")
@@ -3220,32 +3211,219 @@
MergePatchSetInput in = new MergePatchSetInput();
in.merge = mergeInput;
in.subject = "update change by merge ps2";
- gApi.changes().id(changeId).createMergePatchSet(in);
+
+ TestWorkInProgressStateChangedListener wipStateChangedListener =
+ new TestWorkInProgressStateChangedListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(wipStateChangedListener)) {
+ ChangeInfo changeInfo = gApi.changes().id(changeId).createMergePatchSet(in);
+ assertThat(changeInfo.subject).isEqualTo(in.subject);
+ assertThat(changeInfo.containsGitConflicts).isNull();
+ assertThat(changeInfo.workInProgress).isNull();
+ }
+ assertThat(wipStateChangedListener.invoked).isFalse();
+
+ // To get the revisions, we must retrieve the change with more change options.
ChangeInfo changeInfo =
gApi.changes().id(changeId).get(ALL_REVISIONS, CURRENT_COMMIT, CURRENT_REVISION);
assertThat(changeInfo.revisions).hasSize(2);
- assertThat(changeInfo.subject).isEqualTo(in.subject);
assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
.isEqualTo(parent);
+
+ // Verify the message that has been posted on the change.
+ List<ChangeMessageInfo> messages = gApi.changes().id(changeId).messages();
+ assertThat(messages).hasSize(2);
+ assertThat(Iterables.getLast(messages).message).isEqualTo("Uploaded patch set 2.");
+ }
+
+ @Test
+ public void createMergePatchSet_Conflict() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch("dev");
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ String fileName = "shared.txt";
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster =
+ pushFactory
+ .create(admin.newIdent(), testRepo, "change 1", fileName, "content 1")
+ .to("refs/heads/master");
+ currentMaster.assertOkStatus();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, "change 2", fileName, "content 2")
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = "update change by merge ps2";
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(changeId).createMergePatchSet(in));
+ assertThat(thrown).hasMessageThat().isEqualTo("merge conflict(s):\n" + fileName);
+ }
+
+ @Test
+ public void createMergePatchSet_ConflictAllowed() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch("dev");
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ String fileName = "shared.txt";
+ String sourceSubject = "source change";
+ String sourceContent = "source content";
+ String targetSubject = "target change";
+ String targetContent = "target content";
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster =
+ pushFactory
+ .create(admin.newIdent(), testRepo, targetSubject, fileName, targetContent)
+ .to("refs/heads/master");
+ currentMaster.assertOkStatus();
+ String parent = currentMaster.getCommit().getName();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, sourceSubject, fileName, sourceContent)
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ mergeInput.allowConflicts = true;
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = "update change by merge ps2";
+
+ TestWorkInProgressStateChangedListener wipStateChangedListener =
+ new TestWorkInProgressStateChangedListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(wipStateChangedListener)) {
+ ChangeInfo changeInfo = gApi.changes().id(changeId).createMergePatchSet(in);
+ assertThat(changeInfo.subject).isEqualTo(in.subject);
+ assertThat(changeInfo.containsGitConflicts).isTrue();
+ assertThat(changeInfo.workInProgress).isTrue();
+ }
+ assertThat(wipStateChangedListener.invoked).isTrue();
+ assertThat(wipStateChangedListener.wip).isTrue();
+
+ // To get the revisions, we must retrieve the change with more change options.
+ ChangeInfo changeInfo =
+ gApi.changes().id(changeId).get(ALL_REVISIONS, CURRENT_COMMIT, CURRENT_REVISION);
+ assertThat(changeInfo.revisions).hasSize(2);
+ assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
+ .isEqualTo(parent);
+
+ // Verify that the file content in the created patch set is correct.
+ // We expect that it has conflict markers to indicate the conflict.
+ BinaryResult bin = gApi.changes().id(changeId).current().file(fileName).content();
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ bin.writeTo(os);
+ String fileContent = new String(os.toByteArray(), UTF_8);
+ String sourceSha1 = abbreviateName(changeA.getCommit(), 6);
+ String targetSha1 = abbreviateName(currentMaster.getCommit(), 6);
+ assertThat(fileContent)
+ .isEqualTo(
+ "<<<<<<< TARGET BRANCH ("
+ + targetSha1
+ + " "
+ + targetSubject
+ + ")\n"
+ + targetContent
+ + "\n"
+ + "=======\n"
+ + sourceContent
+ + "\n"
+ + ">>>>>>> SOURCE BRANCH ("
+ + sourceSha1
+ + " "
+ + sourceSubject
+ + ")\n");
+
+ // Verify the message that has been posted on the change.
+ List<ChangeMessageInfo> messages = gApi.changes().id(changeId).messages();
+ assertThat(messages).hasSize(2);
+ assertThat(Iterables.getLast(messages).message)
+ .isEqualTo(
+ "Uploaded patch set 2.\n\n"
+ + "The following files contain Git conflicts:\n"
+ + "* "
+ + fileName
+ + "\n");
+ }
+
+ @Test
+ public void createMergePatchSet_ConflictAllowedNotSupportedByMergeStrategy() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch("dev");
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ String fileName = "shared.txt";
+ String sourceSubject = "source change";
+ String sourceContent = "source content";
+ String targetSubject = "target change";
+ String targetContent = "target content";
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster =
+ pushFactory
+ .create(admin.newIdent(), testRepo, targetSubject, fileName, targetContent)
+ .to("refs/heads/master");
+ currentMaster.assertOkStatus();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, sourceSubject, fileName, sourceContent)
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ mergeInput.allowConflicts = true;
+ mergeInput.strategy = "simple-two-way-in-core";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = "update change by merge ps2";
+
+ BadRequestException ex =
+ assertThrows(
+ BadRequestException.class, () -> gApi.changes().id(changeId).createMergePatchSet(in));
+ assertThat(ex)
+ .hasMessageThat()
+ .isEqualTo(
+ "merge with conflicts is not supported with merge strategy: " + mergeInput.strategy);
}
@Test
public void createMergePatchSetInheritParent() throws Exception {
- PushOneCommit.Result start = pushTo("refs/heads/master");
- start.assertOkStatus();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch("dev");
+
// create a change for master
PushOneCommit.Result r = createChange();
- r.assertOkStatus();
String changeId = r.getChangeId();
String parent = r.getCommit().getParent(0).getName();
// advance master branch
- testRepo.reset(start.getCommit());
+ testRepo.reset(initialHead);
PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
currentMaster.assertOkStatus();
// push a commit into dev branch
- createBranch("dev");
+ testRepo.reset(initialHead);
PushOneCommit.Result changeA =
pushFactory
.create(user.newIdent(), testRepo, "change A", "A.txt", "A content")
@@ -3327,6 +3505,46 @@
.isEqualTo(expectedParent);
}
+ @Test
+ public void createMergePatchSetWithUnupportedMergeStrategy() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch("dev");
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ String fileName = "shared.txt";
+ String sourceSubject = "source change";
+ String sourceContent = "source content";
+ String targetSubject = "target change";
+ String targetContent = "target content";
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster =
+ pushFactory
+ .create(admin.newIdent(), testRepo, targetSubject, fileName, targetContent)
+ .to("refs/heads/master");
+ currentMaster.assertOkStatus();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, sourceSubject, fileName, sourceContent)
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ mergeInput.strategy = "unsupported-strategy";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = "update change by merge ps2";
+
+ BadRequestException ex =
+ assertThrows(
+ BadRequestException.class, () -> gApi.changes().id(changeId).createMergePatchSet(in));
+ assertThat(ex).hasMessageThat().isEqualTo("invalid merge strategy: " + mergeInput.strategy);
+ }
+
private MergePatchSetInput createMergePatchSetInput(String baseChange) {
MergeInput mergeInput = new MergeInput();
mergeInput.source = "foo";
@@ -4425,7 +4643,6 @@
ListChangesOption.MESSAGES,
ListChangesOption.SUBMITTABLE,
ListChangesOption.WEB_LINKS,
- ListChangesOption.SKIP_MERGEABLE,
ListChangesOption.SKIP_DIFFSTAT);
PushOneCommit.Result change = createChange();
@@ -4438,14 +4655,16 @@
}
@Test
- @GerritConfig(name = "index.change.indexMergeable", value = "true")
+ @GerritConfig(
+ name = "change.mergeabilityComputationBehavior",
+ value = "API_REF_UPDATED_AND_CHANGE_REINDEX")
public void changeQueryReturnsMergeableWhenGerritIndexMergeable() throws Exception {
String changeId = createChange().getChangeId();
assertThat(gApi.changes().query(changeId).get().get(0).mergeable).isTrue();
}
@Test
- @GerritConfig(name = "index.change.indexMergeable", value = "false")
+ @GerritConfig(name = "change.mergeabilityComputationBehavior", value = "NEVER")
public void changeQueryDoesNotReturnMergeableWhenGerritDoesNotIndexMergeable() throws Exception {
String changeId = createChange().getChangeId();
assertThat(gApi.changes().query(changeId).get().get(0).mergeable).isNull();
@@ -4472,4 +4691,17 @@
private interface AddReviewerCaller {
void call(String changeId, String reviewer) throws RestApiException;
}
+
+ private static class TestWorkInProgressStateChangedListener
+ implements WorkInProgressStateChangedListener {
+ boolean invoked;
+ Boolean wip;
+
+ @Override
+ public void onWorkInProgressStateChanged(Event event) {
+ this.invoked = true;
+ this.wip =
+ event.getChange().workInProgress != null ? event.getChange().workInProgress : false;
+ }
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java b/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java
index 524a05e..707076c 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java
@@ -16,7 +16,10 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static java.util.stream.Collectors.toList;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -25,19 +28,23 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
+import com.google.common.truth.Correspondence;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
+import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
+import com.google.gerrit.extensions.common.RobotCommentInfo;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.validators.CommentForValidation;
-import com.google.gerrit.extensions.validators.CommentForValidation.CommentType;
import com.google.gerrit.extensions.validators.CommentValidationContext;
import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.server.restapi.change.PostReview;
@@ -46,9 +53,12 @@
import com.google.inject.Inject;
import com.google.inject.Module;
import java.sql.Timestamp;
+import java.util.Collection;
+import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
import org.mockito.Captor;
/** Tests for comment validation in {@link PostReview}. */
@@ -57,8 +67,43 @@
@Inject private TestCommentHelper testCommentHelper;
private static final String COMMENT_TEXT = "The comment text";
+ private static final CommentForValidation FILE_COMMENT_FOR_VALIDATION =
+ CommentForValidation.create(
+ CommentForValidation.CommentSource.HUMAN,
+ CommentForValidation.CommentType.FILE_COMMENT,
+ COMMENT_TEXT,
+ COMMENT_TEXT.length());
+ private static final CommentForValidation INLINE_COMMENT_FOR_VALIDATION =
+ CommentForValidation.create(
+ CommentForValidation.CommentSource.HUMAN,
+ CommentForValidation.CommentType.INLINE_COMMENT,
+ COMMENT_TEXT,
+ COMMENT_TEXT.length());
+ private static final CommentForValidation CHANGE_MESSAGE_FOR_VALIDATION =
+ CommentForValidation.create(
+ CommentForValidation.CommentSource.HUMAN,
+ CommentForValidation.CommentType.CHANGE_MESSAGE,
+ COMMENT_TEXT,
+ COMMENT_TEXT.length());
- @Captor private ArgumentCaptor<ImmutableList<CommentForValidation>> capture;
+ @Captor private ArgumentCaptor<ImmutableList<CommentForValidation>> captor;
+
+ private static class SingleCommentMatcher
+ implements ArgumentMatcher<ImmutableList<CommentForValidation>> {
+ final CommentForValidation left;
+
+ SingleCommentMatcher(CommentForValidation left) {
+ this.left = left;
+ }
+
+ @Override
+ public boolean matches(ImmutableList<CommentForValidation> rightList) {
+ CommentForValidation right = Iterables.getOnlyElement(rightList);
+ return left.getSource() == right.getSource()
+ && left.getType() == right.getType()
+ && left.getText().equals(right.getText());
+ }
+ }
@Override
public Module createModule() {
@@ -84,13 +129,7 @@
public void validateCommentsInInput_commentOK() throws Exception {
PushOneCommit.Result r = createChange();
when(mockCommentValidator.validateComments(
- CommentValidationContext.builder()
- .changeId(r.getChange().getId().get())
- .project(r.getChange().project().get())
- .build(),
- ImmutableList.of(
- CommentForValidation.create(
- CommentForValidation.CommentType.FILE_COMMENT, COMMENT_TEXT))))
+ eq(contextFor(r)), argThat(new SingleCommentMatcher(FILE_COMMENT_FOR_VALIDATION))))
.thenReturn(ImmutableList.of());
ReviewInput input = new ReviewInput();
@@ -107,15 +146,9 @@
@Test
public void validateCommentsInInput_commentRejected() throws Exception {
PushOneCommit.Result r = createChange();
- CommentForValidation commentForValidation =
- CommentForValidation.create(CommentType.FILE_COMMENT, COMMENT_TEXT);
when(mockCommentValidator.validateComments(
- CommentValidationContext.builder()
- .changeId(r.getChange().getId().get())
- .project(r.getChange().project().get())
- .build(),
- ImmutableList.of(CommentForValidation.create(CommentType.FILE_COMMENT, COMMENT_TEXT))))
- .thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
+ eq(contextFor(r)), argThat(new SingleCommentMatcher(FILE_COMMENT_FOR_VALIDATION))))
+ .thenReturn(ImmutableList.of(FILE_COMMENT_FOR_VALIDATION.failValidation("Oh no!")));
ReviewInput input = new ReviewInput();
CommentInput comment = newComment(r.getChange().currentFilePaths().get(0));
@@ -161,19 +194,13 @@
public void validateDrafts_draftOK() throws Exception {
PushOneCommit.Result r = createChange();
when(mockCommentValidator.validateComments(
- CommentValidationContext.builder()
- .changeId(r.getChange().getId().get())
- .project(r.getChange().project().get())
- .build(),
- ImmutableList.of(
- CommentForValidation.create(
- CommentForValidation.CommentType.INLINE_COMMENT, COMMENT_TEXT))))
+ eq(contextFor(r)), argThat(new SingleCommentMatcher((INLINE_COMMENT_FOR_VALIDATION)))))
.thenReturn(ImmutableList.of());
DraftInput draft =
testCommentHelper.newDraft(
r.getChange().currentFilePaths().get(0), Side.REVISION, 1, COMMENT_TEXT);
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().getName()).createDraft(draft).get();
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().getName()).createDraft(draft);
assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
ReviewInput input = new ReviewInput();
@@ -186,17 +213,9 @@
@Test
public void validateDrafts_draftRejected() throws Exception {
PushOneCommit.Result r = createChange();
- CommentForValidation commentForValidation =
- CommentForValidation.create(CommentType.INLINE_COMMENT, COMMENT_TEXT);
when(mockCommentValidator.validateComments(
- CommentValidationContext.builder()
- .changeId(r.getChange().getId().get())
- .project(r.getChange().project().get())
- .build(),
- ImmutableList.of(
- CommentForValidation.create(
- CommentForValidation.CommentType.INLINE_COMMENT, COMMENT_TEXT))))
- .thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
+ eq(contextFor(r)), argThat(new SingleCommentMatcher(INLINE_COMMENT_FOR_VALIDATION))))
+ .thenReturn(ImmutableList.of(INLINE_COMMENT_FOR_VALIDATION.failValidation("Oh no!")));
DraftInput draft =
testCommentHelper.newDraft(
@@ -233,7 +252,7 @@
testCommentHelper.addDraft(r.getChangeId(), r.getCommit().getName(), draftFile);
assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
- when(mockCommentValidator.validateComments(any(), capture.capture()))
+ when(mockCommentValidator.validateComments(any(), captor.capture()))
.thenReturn(ImmutableList.of());
ReviewInput input = new ReviewInput();
@@ -241,25 +260,33 @@
gApi.changes().id(r.getChangeId()).current().review(input);
assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).hasSize(2);
- assertThat(capture.getAllValues()).hasSize(1);
- assertThat(capture.getValue())
+ assertThat(captor.getAllValues()).hasSize(1);
+ assertThat(captor.getValue())
+ .comparingElementsUsing(
+ Correspondence.<CommentForValidation, CommentForValidation>from(
+ (a, b) ->
+ a.getSource() == b.getSource()
+ && a.getType() == b.getType()
+ && a.getText().equals(b.getText()),
+ "matches (ignoring size approximation)"))
.containsExactly(
CommentForValidation.create(
- CommentForValidation.CommentType.INLINE_COMMENT, draftInline.message),
+ CommentForValidation.CommentSource.HUMAN,
+ CommentForValidation.CommentType.INLINE_COMMENT,
+ draftInline.message,
+ draftInline.message.length()),
CommentForValidation.create(
- CommentForValidation.CommentType.FILE_COMMENT, draftFile.message));
+ CommentForValidation.CommentSource.HUMAN,
+ CommentForValidation.CommentType.FILE_COMMENT,
+ draftFile.message,
+ draftFile.message.length()));
}
@Test
public void validateCommentsInChangeMessage_messageOK() throws Exception {
PushOneCommit.Result r = createChange();
when(mockCommentValidator.validateComments(
- CommentValidationContext.builder()
- .changeId(r.getChange().getId().get())
- .project(r.getChange().project().get())
- .build(),
- ImmutableList.of(
- CommentForValidation.create(CommentType.CHANGE_MESSAGE, COMMENT_TEXT))))
+ contextFor(r), ImmutableList.of(CHANGE_MESSAGE_FOR_VALIDATION)))
.thenReturn(ImmutableList.of());
ReviewInput input = new ReviewInput().message(COMMENT_TEXT);
@@ -274,16 +301,9 @@
@Test
public void validateCommentsInChangeMessage_messageRejected() throws Exception {
PushOneCommit.Result r = createChange();
- CommentForValidation commentForValidation =
- CommentForValidation.create(CommentType.CHANGE_MESSAGE, COMMENT_TEXT);
when(mockCommentValidator.validateComments(
- CommentValidationContext.builder()
- .changeId(r.getChange().getId().get())
- .project(r.getChange().project().get())
- .build(),
- ImmutableList.of(
- CommentForValidation.create(CommentType.CHANGE_MESSAGE, COMMENT_TEXT))))
- .thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
+ contextFor(r), ImmutableList.of(CHANGE_MESSAGE_FOR_VALIDATION)))
+ .thenReturn(ImmutableList.of(CHANGE_MESSAGE_FOR_VALIDATION.failValidation("Oh no!")));
ReviewInput input = new ReviewInput().message(COMMENT_TEXT);
assertThat(gApi.changes().id(r.getChangeId()).get().messages)
@@ -308,7 +328,56 @@
assertThat(message.message).doesNotContain(COMMENT_TEXT);
}
+ @Test
+ @GerritConfig(name = "change.maxComments", value = "4")
+ public void restrictNumberOfComments() throws Exception {
+ when(mockCommentValidator.validateComments(any(), any())).thenReturn(ImmutableList.of());
+
+ PushOneCommit.Result r = createChange();
+ String filePath = r.getChange().currentFilePaths().get(0);
+ CommentInput commentInput = new CommentInput();
+ commentInput.line = 1;
+ commentInput.message = "foo";
+ commentInput.path = filePath;
+ RobotCommentInput robotCommentInput =
+ TestCommentHelper.createRobotCommentInputWithMandatoryFields(filePath);
+ ReviewInput reviewInput = new ReviewInput();
+ reviewInput.comments = ImmutableMap.of(filePath, ImmutableList.of(commentInput));
+ reviewInput.robotComments = ImmutableMap.of(filePath, ImmutableList.of(robotCommentInput));
+ gApi.changes().id(r.getChangeId()).current().review(reviewInput);
+
+ // reviewInput still has both a user and a robot comment (and deduplication is false). We also
+ // create a draft so that in total there would be 5 comments. The limit is set to 4, so this
+ // verifies that all three channels are considered.
+ DraftInput draftInline = testCommentHelper.newDraft(filePath, Side.REVISION, 1, "a draft");
+ testCommentHelper.addDraft(r.getChangeId(), r.getPatchSetId().getId(), draftInline);
+ reviewInput.drafts = DraftHandling.PUBLISH;
+ reviewInput.omitDuplicateComments = false;
+
+ BadRequestException exception =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.changes().id(r.getChangeId()).current().review(reviewInput));
+ assertThat(exception)
+ .hasMessageThat()
+ .contains("Exceeding maximum number of comments: 2 (existing) + 3 (new) > 4");
+
+ assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).hasSize(1);
+ assertThat(getRobotComments(r.getChangeId())).hasSize(1);
+ }
+
+ private List<RobotCommentInfo> getRobotComments(String changeId) throws RestApiException {
+ return gApi.changes().id(changeId).robotComments().values().stream()
+ .flatMap(Collection::stream)
+ .collect(toList());
+ }
+
private static CommentInput newComment(String path) {
return TestCommentHelper.populate(new CommentInput(), path, PostReviewIT.COMMENT_TEXT);
}
+
+ private static CommentValidationContext contextFor(PushOneCommit.Result result) {
+ return CommentValidationContext.create(
+ result.getChange().getId().get(), result.getChange().project().get());
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
index 7e69251..923b66f 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
@@ -185,6 +185,35 @@
}
@Test
+ public void stickyOnCopyValues() throws Exception {
+ TestAccount user2 = accountCreator.user2();
+
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ u.getConfig()
+ .getLabelSections()
+ .get("Code-Review")
+ .setCopyValues(ImmutableList.of((short) -1, (short) 1));
+ u.save();
+ }
+
+ for (ChangeKind changeKind :
+ EnumSet.of(REWORK, TRIVIAL_REBASE, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE, NO_CHANGE)) {
+ testRepo.reset(projectOperations.project(project).getHead("master"));
+
+ String changeId = createChange(changeKind);
+ vote(admin, changeId, -1, 1);
+ vote(user, changeId, -2, -1);
+ vote(user2, changeId, 1, -1);
+
+ updateChange(changeId, changeKind);
+ ChangeInfo c = detailedChange(changeId);
+ assertVotes(c, admin, -1, 0, changeKind);
+ assertVotes(c, user, 0, 0, changeKind);
+ assertVotes(c, user2, 1, 0, changeKind);
+ }
+ }
+
+ @Test
public void stickyOnTrivialRebase() throws Exception {
try (ProjectConfigUpdate u = updateProject(project)) {
u.getConfig().getLabelSections().get("Code-Review").setCopyAllScoresOnTrivialRebase(true);
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index e6b2190..36b7265 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -1102,7 +1102,9 @@
}
@Test
- @GerritConfig(name = "index.change.indexMergeable", value = "true")
+ @GerritConfig(
+ name = "change.mergeabilityComputationBehavior",
+ value = "API_REF_UPDATED_AND_CHANGE_REINDEX")
public void mergeable() throws Exception {
ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
index c35ded6..56d164b 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
@@ -28,6 +28,8 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.extensions.api.changes.PublishChangeEditInput;
import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
import com.google.gerrit.extensions.client.Comment;
import com.google.gerrit.extensions.common.ChangeInfo;
@@ -53,20 +55,22 @@
import java.util.Objects;
import java.util.Optional;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
public class RobotCommentsIT extends AbstractDaemonTest {
@Inject private TestCommentHelper testCommentHelper;
private static final String PLAIN_TEXT_CONTENT_TYPE = "text/plain";
+ private static final String GERRIT_COMMIT_MESSAGE_TYPE = "text/x-gerrit-commit-message";
private static final String FILE_NAME = "file_to_fix.txt";
private static final String FILE_NAME2 = "another_file_to_fix.txt";
+ private static final String FILE_NAME3 = "file_without_newline_at_end.txt";
private static final String FILE_CONTENT =
"First line\nSecond line\nThird line\nFourth line\nFifth line\nSixth line"
+ "\nSeventh line\nEighth line\nNinth line\nTenth line\n";
private static final String FILE_CONTENT2 = "1st line\n2nd line\n3rd line\n";
+ private static final String FILE_CONTENT3 = "1st line\n2nd line";
private String changeId;
private String commitId;
@@ -81,7 +85,8 @@
admin.newIdent(),
testRepo,
"Provide files which can be used for fixes",
- ImmutableMap.of(FILE_NAME, FILE_CONTENT, FILE_NAME2, FILE_CONTENT2));
+ ImmutableMap.of(
+ FILE_NAME, FILE_CONTENT, FILE_NAME2, FILE_CONTENT2, FILE_NAME3, FILE_CONTENT3));
PushOneCommit.Result changeResult = push.to("refs/for/master");
changeId = changeResult.getChangeId();
commitId = changeResult.getCommit().getName();
@@ -173,9 +178,8 @@
@Test
public void hugeRobotCommentIsRejected() {
- int defaultSizeLimit = 1024 * 1024;
- int sizeOfRest = 451;
- fixReplacementInfo.replacement = getStringFor(defaultSizeLimit - sizeOfRest + 1);
+ int defaultSizeLimit = 1 << 20;
+ fixReplacementInfo.replacement = getStringFor(defaultSizeLimit + 1);
BadRequestException thrown =
assertThrows(
@@ -186,9 +190,9 @@
@Test
public void reasonablyLargeRobotCommentIsAccepted() throws Exception {
- int defaultSizeLimit = 1024 * 1024;
- int sizeOfRest = 451;
- fixReplacementInfo.replacement = getStringFor(defaultSizeLimit - sizeOfRest);
+ int defaultSizeLimit = 1 << 20;
+ // Allow for a few hundred bytes in other fields.
+ fixReplacementInfo.replacement = getStringFor(defaultSizeLimit - 666);
testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
@@ -199,7 +203,7 @@
@Test
@GerritConfig(name = "change.robotCommentSizeLimit", value = "10k")
public void maximumAllowedSizeOfRobotCommentCanBeAdjusted() {
- int sizeLimit = 10 * 1024;
+ int sizeLimit = 10 << 20;
fixReplacementInfo.replacement = getStringFor(sizeLimit);
BadRequestException thrown =
@@ -212,8 +216,8 @@
@Test
@GerritConfig(name = "change.robotCommentSizeLimit", value = "0")
public void zeroForMaximumAllowedSizeOfRobotCommentRemovesRestriction() throws Exception {
- int defaultSizeLimit = 1024 * 1024;
- fixReplacementInfo.replacement = getStringFor(defaultSizeLimit);
+ int defaultSizeLimit = 1 << 20;
+ fixReplacementInfo.replacement = getStringFor(2 * defaultSizeLimit);
testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
@@ -225,8 +229,8 @@
@GerritConfig(name = "change.robotCommentSizeLimit", value = "-1")
public void negativeValueForMaximumAllowedSizeOfRobotCommentRemovesRestriction()
throws Exception {
- int defaultSizeLimit = 1024 * 1024;
- fixReplacementInfo.replacement = getStringFor(defaultSizeLimit);
+ int defaultSizeLimit = 1 << 20;
+ fixReplacementInfo.replacement = getStringFor(2 * defaultSizeLimit);
testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
@@ -995,20 +999,76 @@
}
@Test
- @Ignore
- public void getFixPreviewForNonExistingFile() throws Exception {
- // Not implemented yet.
- fixReplacementInfo.path = "a_non_existent_file.txt";
- fixReplacementInfo.range = createRange(1, 0, 2, 0);
- fixReplacementInfo.replacement = "Modified content\n";
+ public void getFixPreviewForCommitMsg() throws Exception {
+ updateCommitMessage(
+ changeId, "Commit title\n\nCommit message line 1\nLine 2\nLine 3\nLast line\n");
+ FixReplacementInfo commitMsgReplacement = new FixReplacementInfo();
+ commitMsgReplacement.path = Patch.COMMIT_MSG;
+ // The test assumes that the first 5 lines is a header.
+ // Line 10 has content "Line 2"
+ commitMsgReplacement.range = createRange(10, 0, 11, 0);
+ commitMsgReplacement.replacement = "New content\n";
- testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
+ FixSuggestionInfo commitMsgSuggestionInfo = createFixSuggestionInfo(commitMsgReplacement);
+ RobotCommentInput commitMsgRobotCommentInput =
+ TestCommentHelper.createRobotCommentInput(Patch.COMMIT_MSG, commitMsgSuggestionInfo);
+ testCommentHelper.addRobotComment(changeId, commitMsgRobotCommentInput);
+
+ List<RobotCommentInfo> robotCommentInfos = getRobotComments();
+
+ List<String> fixIds = getFixIds(robotCommentInfos);
+ String fixId = Iterables.getOnlyElement(fixIds);
+
+ Map<String, DiffInfo> fixPreview = gApi.changes().id(changeId).current().getFixPreview(fixId);
+ assertThat(fixPreview).hasSize(1);
+ assertThat(fixPreview).containsKey(Patch.COMMIT_MSG);
+
+ DiffInfo diff = fixPreview.get(Patch.COMMIT_MSG);
+ assertThat(diff).metaA().name().isEqualTo(Patch.COMMIT_MSG);
+ assertThat(diff).metaA().contentType().isEqualTo(GERRIT_COMMIT_MESSAGE_TYPE);
+ assertThat(diff).metaB().name().isEqualTo(Patch.COMMIT_MSG);
+ assertThat(diff).metaB().contentType().isEqualTo(GERRIT_COMMIT_MESSAGE_TYPE);
+
+ assertThat(diff).content().element(0).commonLines().hasSize(9);
+ // Header has a dynamic content, do not check it
+ assertThat(diff).content().element(0).commonLines().element(6).isEqualTo("Commit title");
+ assertThat(diff).content().element(0).commonLines().element(7).isEqualTo("");
+ assertThat(diff)
+ .content()
+ .element(0)
+ .commonLines()
+ .element(8)
+ .isEqualTo("Commit message line 1");
+ assertThat(diff).content().element(1).linesOfA().containsExactly("Line 2");
+ assertThat(diff).content().element(1).linesOfB().containsExactly("New content");
+ assertThat(diff).content().element(2).commonLines().containsExactly("Line 3", "Last line", "");
+ }
+
+ private void updateCommitMessage(String changeId, String newCommitMessage) throws Exception {
+ gApi.changes().id(changeId).edit().create();
+ gApi.changes().id(changeId).edit().modifyCommitMessage(newCommitMessage);
+ PublishChangeEditInput publishInput = new PublishChangeEditInput();
+ gApi.changes().id(changeId).edit().publish(publishInput);
+ }
+
+ @Test
+ public void getFixPreviewForNonExistingFile() throws Exception {
+ FixReplacementInfo replacement = new FixReplacementInfo();
+ replacement.path = "a_non_existent_file.txt";
+ replacement.range = createRange(1, 0, 2, 0);
+ replacement.replacement = "Modified content\n";
+
+ FixSuggestionInfo fixSuggestion = createFixSuggestionInfo(replacement);
+ RobotCommentInput commentInput =
+ TestCommentHelper.createRobotCommentInput(FILE_NAME2, fixSuggestion);
+ testCommentHelper.addRobotComment(changeId, commentInput);
+
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
String fixId = Iterables.getOnlyElement(fixIds);
assertThrows(
- BadRequestException.class,
+ ResourceNotFoundException.class,
() -> gApi.changes().id(changeId).current().getFixPreview(fixId));
}
@@ -1122,6 +1182,41 @@
assertThat(diff2).content().element(2).linesOfB().isNull();
}
+ @Test
+ public void getFixPreviewAddNewLineAtEnd() throws Exception {
+ FixReplacementInfo replacement = new FixReplacementInfo();
+ replacement.path = FILE_NAME3;
+ replacement.range = createRange(2, 8, 2, 8);
+ replacement.replacement = "\n";
+
+ FixSuggestionInfo fixSuggestion = createFixSuggestionInfo(replacement);
+ RobotCommentInput commentInput =
+ TestCommentHelper.createRobotCommentInput(FILE_NAME3, fixSuggestion);
+ testCommentHelper.addRobotComment(changeId, commentInput);
+
+ List<RobotCommentInfo> robotCommentInfos = getRobotComments();
+
+ List<String> fixIds = getFixIds(robotCommentInfos);
+ String fixId = Iterables.getOnlyElement(fixIds);
+
+ Map<String, DiffInfo> fixPreview = gApi.changes().id(changeId).current().getFixPreview(fixId);
+
+ assertThat(fixPreview).hasSize(1);
+ assertThat(fixPreview).containsKey(FILE_NAME3);
+
+ DiffInfo diff = fixPreview.get(FILE_NAME3);
+ assertThat(diff).metaA().totalLineCount().isEqualTo(2);
+ // Original file doesn't have EOL marker at the end of file.
+ // Due to the additional EOL mark diff has one additional line
+ // This behavior is in line with ordinary get diff API.
+ assertThat(diff).metaB().totalLineCount().isEqualTo(3);
+
+ assertThat(diff).content().hasSize(2);
+ assertThat(diff).content().element(0).commonLines().containsExactly("1st line");
+ assertThat(diff).content().element(1).linesOfA().containsExactly("2nd line");
+ assertThat(diff).content().element(1).linesOfB().containsExactly("2nd line", "");
+ }
+
private static FixSuggestionInfo createFixSuggestionInfo(
FixReplacementInfo... fixReplacementInfos) {
FixSuggestionInfo newFixSuggestionInfo = new FixSuggestionInfo();
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 0c0a8ed..cdffa3c 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -1205,7 +1205,9 @@
}
@Test
- @GerritConfig(name = "index.change.indexMergeable", value = "true")
+ @GerritConfig(
+ name = "change.mergeabilityComputationBehavior",
+ value = "API_REF_UPDATED_AND_CHANGE_REINDEX")
public void submitSchedulesOpenChangesOfSameBranchForReindexing() throws Throwable {
// Create a merged change.
PushOneCommit push =
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
index 911a04d..28b8cae 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
@@ -78,22 +78,6 @@
gApi.changes().id(changeId).current().submit();
Map<String, ActionInfo> actions = getChangeActions(changeId);
assertThat(actions).containsKey("revert");
- assertThat(actions).doesNotContainKey("revert_submission");
- }
-
- @Test
- public void changeActionTwoMergedChangesHaveReverts() throws Exception {
- String changeId1 = createChangeWithTopic().getChangeId();
- String changeId2 = createChangeWithTopic().getChangeId();
- gApi.changes().id(changeId1).current().review(ReviewInput.approve());
- gApi.changes().id(changeId2).current().review(ReviewInput.approve());
- gApi.changes().id(changeId2).current().submit();
- Map<String, ActionInfo> actions1 = getChangeActions(changeId1);
- assertThat(actions1).containsKey("revert");
- assertThat(actions1).containsKey("revert_submission");
- Map<String, ActionInfo> actions2 = getChangeActions(changeId2);
- assertThat(actions2).containsKey("revert");
- assertThat(actions2).containsKey("revert_submission");
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index 3f80dd1..0b6ecdc 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -18,12 +18,15 @@
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.common.data.Permission.READ;
import static com.google.gerrit.entities.RefNames.changeMetaRef;
+import static com.google.gerrit.git.ObjectIds.abbreviateName;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.SIGNED_OFF_BY_TAG;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.PushOneCommit.Result;
@@ -44,9 +47,11 @@
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
+import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.MergeInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -54,6 +59,7 @@
import com.google.gerrit.server.submit.ChangeAlreadyMergedException;
import com.google.gerrit.testing.FakeEmailSender.Message;
import com.google.inject.Inject;
+import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -141,6 +147,11 @@
ChangeInfo info = assertCreateSucceeds(newChangeInput(ChangeStatus.NEW));
assertThat(info.revisions.get(info.currentRevision).commit.message)
.contains("Change-Id: " + info.changeId);
+
+ // Verify the message that has been posted on the change.
+ List<ChangeMessageInfo> messages = gApi.changes().id(info._number).messages();
+ assertThat(messages).hasSize(1);
+ assertThat(Iterables.getOnlyElement(messages).message).isEqualTo("Uploaded patch set 1.");
}
@Test
@@ -364,7 +375,12 @@
public void createMergeChange() throws Exception {
changeInTwoBranches("branchA", "a.txt", "branchB", "b.txt");
ChangeInput in = newMergeChangeInput("branchA", "branchB", "");
- assertCreateSucceeds(in);
+ ChangeInfo change = assertCreateSucceeds(in);
+
+ // Verify the message that has been posted on the change.
+ List<ChangeMessageInfo> messages = gApi.changes().id(change._number).messages();
+ assertThat(messages).hasSize(1);
+ assertThat(Iterables.getOnlyElement(messages).message).isEqualTo("Uploaded patch set 1.");
}
@Test
@@ -382,6 +398,87 @@
}
@Test
+ public void createMergeChange_ConflictsAllowed() throws Exception {
+ String fileName = "shared.txt";
+ String sourceBranch = "sourceBranch";
+ String sourceSubject = "source change";
+ String sourceContent = "source content";
+ String targetBranch = "targetBranch";
+ String targetSubject = "target change";
+ String targetContent = "target content";
+ changeInTwoBranches(
+ sourceBranch,
+ sourceSubject,
+ fileName,
+ sourceContent,
+ targetBranch,
+ targetSubject,
+ fileName,
+ targetContent);
+ ChangeInput in = newMergeChangeInput(targetBranch, sourceBranch, "", true);
+ ChangeInfo change = assertCreateSucceedsWithConflicts(in);
+
+ // Verify that the file content in the created change is correct.
+ // We expect that it has conflict markers to indicate the conflict.
+ BinaryResult bin = gApi.changes().id(change._number).current().file(fileName).content();
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ bin.writeTo(os);
+ String fileContent = new String(os.toByteArray(), UTF_8);
+ String sourceSha1 = abbreviateName(projectOperations.project(project).getHead(sourceBranch), 6);
+ String targetSha1 = abbreviateName(projectOperations.project(project).getHead(targetBranch), 6);
+ assertThat(fileContent)
+ .isEqualTo(
+ "<<<<<<< TARGET BRANCH ("
+ + targetSha1
+ + " "
+ + targetSubject
+ + ")\n"
+ + targetContent
+ + "\n"
+ + "=======\n"
+ + sourceContent
+ + "\n"
+ + ">>>>>>> SOURCE BRANCH ("
+ + sourceSha1
+ + " "
+ + sourceSubject
+ + ")\n");
+
+ // Verify the message that has been posted on the change.
+ List<ChangeMessageInfo> messages = gApi.changes().id(change._number).messages();
+ assertThat(messages).hasSize(1);
+ assertThat(Iterables.getOnlyElement(messages).message)
+ .isEqualTo(
+ "Uploaded patch set 1.\n\n"
+ + "The following files contain Git conflicts:\n"
+ + "* "
+ + fileName
+ + "\n");
+ }
+
+ @Test
+ public void createMergeChange_ConflictAllowedNotSupportedByMergeStrategy() throws Exception {
+ String fileName = "shared.txt";
+ String sourceBranch = "sourceBranch";
+ String targetBranch = "targetBranch";
+ changeInTwoBranches(
+ sourceBranch,
+ "source change",
+ fileName,
+ "source content",
+ targetBranch,
+ "target change",
+ fileName,
+ "target content");
+ String mergeStrategy = "simple-two-way-in-core";
+ ChangeInput in = newMergeChangeInput(targetBranch, sourceBranch, mergeStrategy, true);
+ assertCreateFails(
+ in,
+ BadRequestException.class,
+ "merge with conflicts is not supported with merge strategy: " + mergeStrategy);
+ }
+
+ @Test
public void createMergeChangeFailsWithConflictIfThereAreTooManyCommonPredecessors()
throws Exception {
// Create an initial commit in master.
@@ -732,6 +829,26 @@
}
assertThat(out.revisions).hasSize(1);
assertThat(out.submitted).isNull();
+ assertThat(out.containsGitConflicts).isNull();
+ assertThat(in.status).isEqualTo(ChangeStatus.NEW);
+ return out;
+ }
+
+ private ChangeInfo assertCreateSucceedsWithConflicts(ChangeInput in) throws Exception {
+ ChangeInfo out = gApi.changes().createAsInfo(in);
+ assertThat(out.project).isEqualTo(in.project);
+ assertThat(RefNames.fullName(out.branch)).isEqualTo(RefNames.fullName(in.branch));
+ assertThat(out.subject).isEqualTo(in.subject.split("\n")[0]);
+ assertThat(out.topic).isEqualTo(in.topic);
+ assertThat(out.status).isEqualTo(in.status);
+ if (in.isPrivate) {
+ assertThat(out.isPrivate).isTrue();
+ } else {
+ assertThat(out.isPrivate).isNull();
+ }
+ assertThat(out.submitted).isNull();
+ assertThat(out.containsGitConflicts).isTrue();
+ assertThat(out.workInProgress).isTrue();
assertThat(in.status).isEqualTo(ChangeStatus.NEW);
return out;
}
@@ -764,6 +881,11 @@
}
private ChangeInput newMergeChangeInput(String targetBranch, String sourceRef, String strategy) {
+ return newMergeChangeInput(targetBranch, sourceRef, strategy, false);
+ }
+
+ private ChangeInput newMergeChangeInput(
+ String targetBranch, String sourceRef, String strategy, boolean allowConflicts) {
// create a merge change from branchA to master in gerrit
ChangeInput in = new ChangeInput();
in.project = project.get();
@@ -776,6 +898,7 @@
if (!Strings.isNullOrEmpty(strategy)) {
in.merge.strategy = strategy;
}
+ in.merge.allowConflicts = allowConflicts;
return in;
}
@@ -791,6 +914,34 @@
*/
private Map<String, Result> changeInTwoBranches(
String branchA, String fileA, String branchB, String fileB) throws Exception {
+ return changeInTwoBranches(
+ branchA, "change A", fileA, "A content", branchB, "change B", fileB, "B content");
+ }
+
+ /**
+ * Create an empty commit in master, two new branches with one commit each.
+ *
+ * @param branchA name of first branch to create
+ * @param subjectA commit message subject for the change on branchA
+ * @param fileA name of file to commit to branchA
+ * @param contentA file content to commit to branchA
+ * @param branchB name of second branch to create
+ * @param subjectB commit message subject for the change on branchB
+ * @param fileB name of file to commit to branchB
+ * @param contentB file content to commit to branchB
+ * @return A {@code Map} of branchName => commit result.
+ * @throws Exception
+ */
+ private Map<String, Result> changeInTwoBranches(
+ String branchA,
+ String subjectA,
+ String fileA,
+ String contentA,
+ String branchB,
+ String subjectB,
+ String fileB,
+ String contentB)
+ throws Exception {
// create a initial commit in master
Result initialCommit =
pushFactory
@@ -805,13 +956,13 @@
// create a commit in branchA
Result changeA =
pushFactory
- .create(user.newIdent(), testRepo, "change A", fileA, "A content")
+ .create(user.newIdent(), testRepo, subjectA, fileA, contentA)
.to("refs/heads/" + branchA);
changeA.assertOkStatus();
// create a commit in branchB
PushOneCommit commitB =
- pushFactory.create(user.newIdent(), testRepo, "change B", fileB, "B content");
+ pushFactory.create(user.newIdent(), testRepo, subjectB, fileB, contentB);
commitB.setParent(initialCommit.getCommit());
Result changeB = commitB.to("refs/heads/" + branchB);
changeB.assertOkStatus();
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
index ea3a6a0..61dc4d4 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
@@ -59,7 +59,9 @@
}
@Test
- @GerritConfig(name = "index.change.indexMergeable", value = "true")
+ @GerritConfig(
+ name = "change.mergeabilityComputationBehavior",
+ value = "API_REF_UPDATED_AND_CHANGE_REINDEX")
public void indexChangeAfterOwnerLosesVisibility() throws Exception {
// Create a test group with 2 users as members
TestAccount user2 = accountCreator.user2();
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
index 9573eb0..14a04cd 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -169,7 +169,8 @@
assertThat(i.change.updateDelay).isEqualTo(300);
assertThat(i.change.disablePrivateChanges).isNull();
assertThat(i.change.submitWholeTopic).isNull();
- assertThat(i.change.excludeMergeableInChangeInfo).isNull();
+ assertThat(i.change.mergeabilityComputationBehavior)
+ .isEqualTo("API_REF_UPDATED_AND_CHANGE_REINDEX");
// download
assertThat(i.download.archives).containsExactly("tar", "tbz2", "tgz", "txz");
@@ -180,9 +181,6 @@
assertThat(i.gerrit.allUsers).isEqualTo(AllUsersNameProvider.DEFAULT);
assertThat(i.gerrit.reportBugUrl).isNull();
- // index
- assertThat(i.index.change.indexMergeable).isNull(); // false in all tests
-
// plugin
assertThat(i.plugin.jsResourcePaths).isEmpty();
@@ -204,16 +202,9 @@
}
@Test
- @GerritConfig(name = "change.api.excludeMergeableInChangeInfo", value = "true")
- public void serverConfigWithExcludeMergeableInChangeInfo() throws Exception {
+ @GerritConfig(name = "change.mergeabilityComputationBehavior", value = "NEVER")
+ public void mergeabilityComputationBehavior_neverCompute() throws Exception {
ServerInfo i = gApi.config().server().getInfo();
- assertThat(i.change.excludeMergeableInChangeInfo).isTrue();
- }
-
- @Test
- @GerritConfig(name = "index.change.indexMergeable", value = "true")
- public void indexMergeableIsTrueWhenTrueInConfig() throws Exception {
- ServerInfo i = gApi.config().server().getInfo();
- assertThat(i.index.change.indexMergeable).isTrue();
+ assertThat(i.change.mergeabilityComputationBehavior).isEqualTo("NEVER");
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/CreateLabelIT.java b/javatests/com/google/gerrit/acceptance/rest/project/CreateLabelIT.java
index 57a1e56..e5587a9 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/CreateLabelIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/CreateLabelIT.java
@@ -244,6 +244,7 @@
assertThat(createdLabel.copyAllScoresIfNoCodeChange).isNull();
assertThat(createdLabel.copyAllScoresOnTrivialRebase).isNull();
assertThat(createdLabel.copyAllScoresOnMergeFirstParentUpdate).isNull();
+ assertThat(createdLabel.copyValues).isNull();
assertThat(createdLabel.allowPostSubmit).isTrue();
assertThat(createdLabel.ignoreSelfApproval).isNull();
}
@@ -537,6 +538,17 @@
}
@Test
+ public void createWithCopyValues() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyValues = ImmutableList.of((short) -1, (short) 1);
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyValues).containsExactly((short) -1, (short) 1).inOrder();
+ }
+
+ @Test
public void createWithAllowPostSubmit() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/GetLabelIT.java b/javatests/com/google/gerrit/acceptance/rest/project/GetLabelIT.java
index 9f98490..940fae5 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/GetLabelIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/GetLabelIT.java
@@ -117,6 +117,7 @@
assertThat(fooLabel.copyAllScoresIfNoCodeChange).isNull();
assertThat(fooLabel.copyAllScoresOnTrivialRebase).isNull();
assertThat(fooLabel.copyAllScoresOnMergeFirstParentUpdate).isNull();
+ assertThat(fooLabel.copyValues).isNull();
assertThat(fooLabel.allowPostSubmit).isNull();
assertThat(fooLabel.ignoreSelfApproval).isNull();
}
@@ -134,6 +135,7 @@
labelType.setCopyAllScoresIfNoCodeChange(true);
labelType.setCopyAllScoresOnTrivialRebase(true);
labelType.setCopyAllScoresOnMergeFirstParentUpdate(true);
+ labelType.setCopyValues(ImmutableList.of((short) -1, (short) 1));
labelType.setIgnoreSelfApproval(true);
u.getConfig().getLabelSections().put(labelType.getName(), labelType);
u.save();
@@ -148,6 +150,7 @@
assertThat(fooLabel.copyAllScoresIfNoCodeChange).isTrue();
assertThat(fooLabel.copyAllScoresOnTrivialRebase).isTrue();
assertThat(fooLabel.copyAllScoresOnMergeFirstParentUpdate).isTrue();
+ assertThat(fooLabel.copyValues).containsExactly((short) -1, (short) 1).inOrder();
assertThat(fooLabel.allowPostSubmit).isTrue();
assertThat(fooLabel.ignoreSelfApproval).isTrue();
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/LabelAssert.java b/javatests/com/google/gerrit/acceptance/rest/project/LabelAssert.java
index 7998ecb..65e352b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/LabelAssert.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/LabelAssert.java
@@ -47,6 +47,7 @@
assertThat(codeReviewLabel.copyAllScoresIfNoCodeChange).isNull();
assertThat(codeReviewLabel.copyAllScoresOnTrivialRebase).isTrue();
assertThat(codeReviewLabel.copyAllScoresOnMergeFirstParentUpdate).isNull();
+ assertThat(codeReviewLabel.copyValues).isNull();
assertThat(codeReviewLabel.allowPostSubmit).isTrue();
assertThat(codeReviewLabel.ignoreSelfApproval).isNull();
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ListLabelsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ListLabelsIT.java
index d2539e5..ef08079 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ListLabelsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ListLabelsIT.java
@@ -139,6 +139,7 @@
assertThat(fooLabel.copyAllScoresIfNoCodeChange).isNull();
assertThat(fooLabel.copyAllScoresOnTrivialRebase).isNull();
assertThat(fooLabel.copyAllScoresOnMergeFirstParentUpdate).isNull();
+ assertThat(fooLabel.copyValues).isNull();
assertThat(fooLabel.allowPostSubmit).isNull();
assertThat(fooLabel.ignoreSelfApproval).isNull();
}
@@ -156,6 +157,7 @@
labelType.setCopyAllScoresIfNoCodeChange(true);
labelType.setCopyAllScoresOnTrivialRebase(true);
labelType.setCopyAllScoresOnMergeFirstParentUpdate(true);
+ labelType.setCopyValues(ImmutableList.of((short) -1, (short) 1));
labelType.setIgnoreSelfApproval(true);
u.getConfig().getLabelSections().put(labelType.getName(), labelType);
u.save();
@@ -173,6 +175,7 @@
assertThat(fooLabel.copyAllScoresIfNoCodeChange).isTrue();
assertThat(fooLabel.copyAllScoresOnTrivialRebase).isTrue();
assertThat(fooLabel.copyAllScoresOnMergeFirstParentUpdate).isTrue();
+ assertThat(fooLabel.copyValues).containsExactly((short) -1, (short) 1).inOrder();
assertThat(fooLabel.allowPostSubmit).isTrue();
assertThat(fooLabel.ignoreSelfApproval).isTrue();
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java b/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java
index 97b795f..b08c72b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java
@@ -771,6 +771,44 @@
}
@Test
+ public void setCopyValues() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyValues).isNull();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyValues = ImmutableList.of((short) -1, (short) 1);
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyValues).containsExactly((short) -1, (short) 1).inOrder();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyValues)
+ .containsExactly((short) -1, (short) 1)
+ .inOrder();
+ }
+
+ @Test
+ public void unsetCopyValues() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setCopyValues(ImmutableList.of((short) -1, (short) 1));
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyValues).isNotEmpty();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyValues = ImmutableList.of();
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyValues).isNull();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyValues).isNull();
+ }
+
+ @Test
public void setAllowPostSubmit() throws Exception {
configLabel("foo", LabelFunction.NO_OP);
try (ProjectConfigUpdate u = updateProject(project)) {
diff --git a/javatests/com/google/gerrit/acceptance/server/event/EventPayloadIT.java b/javatests/com/google/gerrit/acceptance/server/event/EventPayloadIT.java
index d973934..3bc863f 100644
--- a/javatests/com/google/gerrit/acceptance/server/event/EventPayloadIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/event/EventPayloadIT.java
@@ -32,32 +32,26 @@
@Test
public void defaultOptions() throws Exception {
RevisionCreatedListener listener =
- new RevisionCreatedListener() {
- @Override
- public void onRevisionCreated(Event event) {
- assertThat(event.getChange().submittable).isNotNull();
- assertThat(event.getRevision().files).isNotEmpty();
- }
+ event -> {
+ assertThat(event.getChange().submittable).isNotNull();
+ assertThat(event.getRevision().files).isNotEmpty();
};
- try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
+ try (Registration ignored = extensionRegistry.newRegistration().add(listener)) {
createChange();
}
}
@Test
- @GerritConfig(name = "event.payload.listChangeOptions", value = "SKIP_MERGEABLE")
+ @GerritConfig(name = "event.payload.listChangeOptions", value = "SKIP_DIFFSTAT")
public void configuredOptions() throws Exception {
RevisionCreatedListener listener =
- new RevisionCreatedListener() {
- @Override
- public void onRevisionCreated(Event event) {
- assertThat(event.getChange().submittable).isNull();
- assertThat(event.getChange().mergeable).isNull();
- assertThat(event.getRevision().files).isNull();
- assertThat(event.getChange().subject).isNotEmpty();
- }
+ event -> {
+ assertThat(event.getChange().submittable).isNull();
+ assertThat(event.getChange().insertions).isNull();
+ assertThat(event.getRevision().files).isNull();
+ assertThat(event.getChange().subject).isNotEmpty();
};
- try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
+ try (Registration ignored = extensionRegistry.newRegistration().add(listener)) {
createChange();
}
}
diff --git a/javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsCommentValidationIT.java b/javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsCommentValidationIT.java
index ccfe783..eb1f907 100644
--- a/javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsCommentValidationIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsCommentValidationIT.java
@@ -31,7 +31,6 @@
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.extensions.validators.CommentForValidation;
-import com.google.gerrit.extensions.validators.CommentForValidation.CommentType;
import com.google.gerrit.extensions.validators.CommentValidationContext;
import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.testing.TestCommentHelper;
@@ -49,9 +48,15 @@
@Inject private CommentValidator mockCommentValidator;
@Inject private TestCommentHelper testCommentHelper;
- private static final int MAX_COMMENT_LENGTH = 666;
+ private static final int COMMENT_SIZE_LIMIT = 666;
private static final String COMMENT_TEXT = "The comment text";
+ private static final CommentForValidation COMMENT_FOR_VALIDATION =
+ CommentForValidation.create(
+ CommentForValidation.CommentSource.HUMAN,
+ CommentForValidation.CommentType.FILE_COMMENT,
+ COMMENT_TEXT,
+ COMMENT_TEXT.length());
@Captor private ArgumentCaptor<ImmutableList<CommentForValidation>> capture;
@Captor private ArgumentCaptor<CommentValidationContext> captureCtx;
@@ -82,13 +87,9 @@
String changeId = result.getChangeId();
String revId = result.getCommit().getName();
when(mockCommentValidator.validateComments(
- CommentValidationContext.builder()
- .changeId(result.getChange().getId().get())
- .project(result.getChange().project().get())
- .build(),
- ImmutableList.of(
- CommentForValidation.create(
- CommentForValidation.CommentType.FILE_COMMENT, COMMENT_TEXT))))
+ CommentValidationContext.create(
+ result.getChange().getId().get(), result.getChange().project().get()),
+ ImmutableList.of(COMMENT_FOR_VALIDATION)))
.thenReturn(ImmutableList.of());
DraftInput comment = testCommentHelper.newDraft(COMMENT_TEXT);
testCommentHelper.addDraft(changeId, revId, comment);
@@ -101,20 +102,14 @@
@Test
public void validateComments_commentRejected() throws Exception {
- CommentForValidation commentForValidation =
- CommentForValidation.create(CommentType.FILE_COMMENT, COMMENT_TEXT);
PushOneCommit.Result result = createChange();
String changeId = result.getChangeId();
String revId = result.getCommit().getName();
when(mockCommentValidator.validateComments(
- CommentValidationContext.builder()
- .changeId(result.getChange().getId().get())
- .project(result.getChange().project().get())
- .build(),
- ImmutableList.of(
- CommentForValidation.create(
- CommentForValidation.CommentType.FILE_COMMENT, COMMENT_TEXT))))
- .thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
+ CommentValidationContext.create(
+ result.getChange().getId().get(), result.getChange().project().get()),
+ ImmutableList.of(COMMENT_FOR_VALIDATION)))
+ .thenReturn(ImmutableList.of(COMMENT_FOR_VALIDATION.failValidation("Oh no!")));
DraftInput comment = testCommentHelper.newDraft(COMMENT_TEXT);
testCommentHelper.addDraft(changeId, revId, comment);
assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).isEmpty();
@@ -149,18 +144,24 @@
assertThat(capture.getAllValues().get(0))
.containsExactly(
CommentForValidation.create(
- CommentForValidation.CommentType.INLINE_COMMENT, draftInline.message),
+ CommentForValidation.CommentSource.HUMAN,
+ CommentForValidation.CommentType.INLINE_COMMENT,
+ draftInline.message,
+ draftInline.message.length()),
CommentForValidation.create(
- CommentForValidation.CommentType.FILE_COMMENT, draftFile.message));
+ CommentForValidation.CommentSource.HUMAN,
+ CommentForValidation.CommentType.FILE_COMMENT,
+ draftFile.message,
+ draftFile.message.length()));
}
@Test
- @GerritConfig(name = "change.maxCommentLength", value = "" + MAX_COMMENT_LENGTH)
+ @GerritConfig(name = "change.commentSizeLimit", value = "" + COMMENT_SIZE_LIMIT)
public void validateComments_enforceLimits_commentTooLarge() throws Exception {
when(mockCommentValidator.validateComments(any(), any())).thenReturn(ImmutableList.of());
PushOneCommit.Result result = createChange();
String changeId = result.getChangeId();
- int commentLength = MAX_COMMENT_LENGTH + 1;
+ int commentLength = COMMENT_SIZE_LIMIT + 1;
DraftInput comment =
testCommentHelper.newDraft(new String(new char[commentLength]).replace("\0", "x"));
testCommentHelper.addDraft(changeId, result.getCommit().getName(), comment);
@@ -169,7 +170,33 @@
Result amendResult = amendChange(changeId, "refs/for/master%publish-comments", admin, testRepo);
amendResult.assertOkStatus();
amendResult.assertMessage(
- String.format("Comment too large (%d > %d)", commentLength, MAX_COMMENT_LENGTH));
+ String.format("Comment size exceeds limit (%d > %d)", commentLength, COMMENT_SIZE_LIMIT));
assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).isEmpty();
}
+
+ @Test
+ @GerritConfig(name = "change.maxComments", value = "3")
+ public void countComments_limitNumberOfComments() throws Exception {
+ when(mockCommentValidator.validateComments(any(), any())).thenReturn(ImmutableList.of());
+ PushOneCommit.Result result = createChange();
+ String changeId = result.getChangeId();
+ String revId = result.getCommit().getName();
+ String filePath = result.getChange().currentFilePaths().get(0);
+ DraftInput draftInline = testCommentHelper.newDraft(filePath, Side.REVISION, 1, COMMENT_TEXT);
+ testCommentHelper.addDraft(changeId, revId, draftInline);
+ amendChange(changeId, "refs/for/master%publish-comments", admin, testRepo);
+ assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).hasSize(1);
+
+ for (int i = 1; i < 3; ++i) {
+ testCommentHelper.addRobotComment(
+ changeId,
+ TestCommentHelper.createRobotCommentInput(result.getChange().currentFilePaths().get(0)));
+ }
+
+ draftInline = testCommentHelper.newDraft(filePath, Side.REVISION, 1, COMMENT_TEXT);
+ testCommentHelper.addDraft(changeId, revId, draftInline);
+ Result amendResult = amendChange(changeId, "refs/for/master%publish-comments", admin, testRepo);
+ assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).hasSize(1);
+ amendResult.assertMessage("exceeding maximum number of comments");
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsLimitsIT.java b/javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsLimitsIT.java
new file mode 100644
index 0000000..c32827a
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsLimitsIT.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2020 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.acceptance.server.git.receive;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.cache.Cache;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.server.patch.DiffSummary;
+import com.google.gerrit.server.patch.DiffSummaryKey;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import org.junit.Test;
+
+/** Tests for applying limits to e.g. number of files per change. */
+public class ReceiveCommitsLimitsIT extends AbstractDaemonTest {
+
+ @Inject
+ private @Named("diff_summary") Cache<DiffSummaryKey, DiffSummary> diffSummaryCache;
+
+ @Test
+ @GerritConfig(name = "change.maxFiles", value = "1")
+ public void limitFileCount() throws Exception {
+ PushOneCommit.Result result =
+ pushFactory
+ .create(
+ admin.newIdent(),
+ testRepo,
+ "foo",
+ ImmutableMap.of("foo", "foo-1.0", "bar", "bar-1.0"))
+ .to("refs/for/master");
+ result.assertErrorStatus("Exceeding maximum number of files per change (2 > 1)");
+ }
+
+ @Test
+ public void cacheKeyMatches() throws Exception {
+ int cacheSizeBefore = diffSummaryCache.asMap().size();
+ PushOneCommit.Result result =
+ pushFactory
+ .create(
+ admin.newIdent(),
+ testRepo,
+ "foo",
+ ImmutableMap.of("foo", "foo-1.0", "bar", "bar-1.0"))
+ .to("refs/for/master");
+ result.assertOkStatus();
+
+ // 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);
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java b/javatests/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
index 768c269..3b0e27b 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
@@ -33,7 +33,9 @@
public class AbstractMailIT extends AbstractDaemonTest {
@Inject private RequestScopeOperations requestScopeOperations;
- protected MailMessage.Builder messageBuilderWithDefaultFields() {
+ static final String FILE_NAME = "gerrit-server/test.txt";
+
+ MailMessage.Builder messageBuilderWithDefaultFields() {
MailMessage.Builder b = MailMessage.builder();
b.id("some id");
b.from(user.getEmailAddress());
@@ -43,16 +45,15 @@
return b;
}
- protected String createChangeWithReview() throws Exception {
+ String createChangeWithReview() throws Exception {
return createChangeWithReview(admin);
}
- protected String createChangeWithReview(TestAccount reviewer) throws Exception {
+ String createChangeWithReview(TestAccount reviewer) throws Exception {
// Create change
- String file = "gerrit-server/test.txt";
String contents = "contents \nlorem \nipsum \nlorem";
PushOneCommit push =
- pushFactory.create(admin.newIdent(), testRepo, "first subject", file, contents);
+ pushFactory.create(admin.newIdent(), testRepo, "first subject", FILE_NAME, contents);
PushOneCommit.Result r = push.to("refs/for/master");
String changeId = r.getChangeId();
@@ -61,8 +62,8 @@
ReviewInput input = new ReviewInput();
input.message = "I have two comments";
input.comments = new HashMap<>();
- CommentInput c1 = newComment(file, Side.REVISION, 0, "comment on file");
- CommentInput c2 = newComment(file, Side.REVISION, 2, "inline comment");
+ CommentInput c1 = newComment(FILE_NAME, Side.REVISION, 0, "comment on file");
+ CommentInput c2 = newComment(FILE_NAME, Side.REVISION, 2, "inline comment");
input.comments.put(c1.path, ImmutableList.of(c1, c2));
revision(r).review(input);
return changeId;
@@ -94,7 +95,7 @@
* @param fc1 Comment in reply to a comment of file 1.
* @return A string with all inline comments and the original quoted email.
*/
- protected static String newPlaintextBody(
+ static String newPlaintextBody(
String changeURL, String changeMessage, String c1, String f1, String fc1) {
return (changeMessage == null ? "" : changeMessage + "\n")
+ "> Foo Bar has posted comments on this change. ( \n"
@@ -137,7 +138,7 @@
+ "> \n";
}
- protected static String textFooterForChange(int changeNumber, String timestamp) {
+ static String textFooterForChange(int changeNumber, String timestamp) {
return "Gerrit-Change-Number: "
+ changeNumber
+ "\n"
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java b/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
index 2409f52..502194a 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
@@ -21,9 +21,14 @@
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
+import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.CommentInfo;
@@ -341,17 +346,53 @@
assertThat(message.body()).contains("rejected one or more comments");
}
+ @Test
+ @GerritConfig(name = "change.maxComments", value = "4")
+ public void limitNumberOfComments() throws Exception {
+ String changeId = createChangeWithReview();
+ CommentInput commentInput = new CommentInput();
+ commentInput.line = 1;
+ commentInput.message = "foo";
+ commentInput.path = FILE_NAME;
+ RobotCommentInput robotCommentInput =
+ TestCommentHelper.createRobotCommentInputWithMandatoryFields(FILE_NAME);
+ ReviewInput reviewInput = new ReviewInput();
+ reviewInput.comments = ImmutableMap.of(FILE_NAME, ImmutableList.of(commentInput));
+ reviewInput.robotComments = ImmutableMap.of(FILE_NAME, ImmutableList.of(robotCommentInput));
+ gApi.changes().id(changeId).current().review(reviewInput);
+
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+ List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
+ String ts =
+ MailProcessingUtil.rfcDateformatter.format(
+ ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
+
+ MailMessage.Builder b = messageBuilderWithDefaultFields();
+ String txt = newPlaintextBody(getChangeUrl(changeInfo) + "/1", null, null, COMMENT_TEXT, null);
+ b.textContent(txt + textFooterForChange(changeInfo._number, ts));
+
+ Collection<CommentInfo> commentsBefore = testCommentHelper.getPublishedComments(changeId);
+ mailProcessor.process(b.build());
+ assertThat(testCommentHelper.getPublishedComments(changeId)).isEqualTo(commentsBefore);
+
+ assertNotifyTo(user);
+ Message message = sender.nextMessage();
+ assertThat(message.body()).contains("rejected one or more comments");
+ }
+
private String getChangeUrl(ChangeInfo changeInfo) {
return canonicalWebUrl.get() + "c/" + changeInfo.project + "/+/" + changeInfo._number;
}
private void setupFailValidation(
CommentForValidation.CommentType type, String failProject, int failChange) {
- CommentForValidation commentForValidation = CommentForValidation.create(type, COMMENT_TEXT);
+ CommentForValidation commentForValidation =
+ CommentForValidation.create(
+ CommentForValidation.CommentSource.HUMAN, type, COMMENT_TEXT, COMMENT_TEXT.length());
when(mockCommentValidator.validateComments(
- CommentValidationContext.builder().changeId(failChange).project(failProject).build(),
- ImmutableList.of(CommentForValidation.create(type, COMMENT_TEXT))))
+ CommentValidationContext.create(failChange, failProject),
+ ImmutableList.of(commentForValidation)))
.thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
}
}
diff --git a/javatests/com/google/gerrit/pgm/BUILD b/javatests/com/google/gerrit/pgm/BUILD
index 0cae937..b68aacc 100644
--- a/javatests/com/google/gerrit/pgm/BUILD
+++ b/javatests/com/google/gerrit/pgm/BUILD
@@ -5,7 +5,7 @@
name = "pgm_tests",
srcs = glob(["**/*.java"]),
deps = [
- "//java/com/google/gerrit/pgm/http",
+ "//java/com/google/gerrit/pgm/http/jetty",
"//java/com/google/gerrit/pgm/init/api",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/securestore/testing",
diff --git a/javatests/com/google/gerrit/server/git/DeleteZombieCommentsRefsTest.java b/javatests/com/google/gerrit/server/git/DeleteZombieCommentsRefsTest.java
new file mode 100644
index 0000000..bfcb9e4
--- /dev/null
+++ b/javatests/com/google/gerrit/server/git/DeleteZombieCommentsRefsTest.java
@@ -0,0 +1,247 @@
+// Copyright (C) 2020 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.git;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.git.RefUpdateUtil;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.notedb.DeleteZombieCommentsRefs;
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.TreeFormatter;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class DeleteZombieCommentsRefsTest {
+ private InMemoryRepositoryManager repoManager = new InMemoryRepositoryManager();
+ private Project.NameKey allUsersProject = Project.nameKey("All-Users");
+
+ @Test
+ public void cleanZombieDraftsSmall() throws Exception {
+ try (Repository usersRepo = repoManager.createRepository(allUsersProject)) {
+ Ref ref1 = createRefWithNonEmptyTreeCommit(usersRepo, 1, 1000001);
+ Ref ref2 = createRefWithEmptyTreeCommit(usersRepo, 1, 1000002);
+
+ DeleteZombieCommentsRefs clean =
+ new DeleteZombieCommentsRefs(new AllUsersName("All-Users"), repoManager, null);
+ clean.execute();
+
+ /* Check that ref1 still exists, and ref2 is deleted */
+ assertThat(usersRepo.exactRef(ref1.getName())).isNotNull();
+ assertThat(usersRepo.exactRef(ref2.getName())).isNull();
+ }
+ }
+
+ @Test
+ public void cleanZombieDraftsWithPercentage() throws Exception {
+ try (Repository usersRepo = repoManager.createRepository(allUsersProject)) {
+ Ref ref1 = createRefWithNonEmptyTreeCommit(usersRepo, 1005, 1000001);
+ Ref ref2 = createRefWithEmptyTreeCommit(usersRepo, 1006, 1000002);
+ Ref ref3 = createRefWithEmptyTreeCommit(usersRepo, 1060, 1000002);
+
+ assertThat(usersRepo.getRefDatabase().getRefs()).hasSize(3);
+
+ int cleanupPercentage = 50;
+ DeleteZombieCommentsRefs clean =
+ new DeleteZombieCommentsRefs(
+ new AllUsersName("All-Users"), repoManager, cleanupPercentage);
+ clean.execute();
+
+ /* ref1 not deleted, ref2 deleted, ref3 not deleted because of the clean percentage */
+ assertThat(usersRepo.getRefDatabase().getRefs()).hasSize(2);
+ assertThat(usersRepo.exactRef(ref1.getName())).isNotNull();
+ assertThat(usersRepo.exactRef(ref2.getName())).isNull();
+ assertThat(usersRepo.exactRef(ref3.getName())).isNotNull();
+
+ /* Re-execute the cleanup and make sure nothing's changed */
+ clean.execute();
+ assertThat(usersRepo.getRefDatabase().getRefs()).hasSize(2);
+ assertThat(usersRepo.exactRef(ref1.getName())).isNotNull();
+ assertThat(usersRepo.exactRef(ref2.getName())).isNull();
+ assertThat(usersRepo.exactRef(ref3.getName())).isNotNull();
+
+ /* Increase the cleanup percentage */
+ cleanupPercentage = 70;
+ clean =
+ new DeleteZombieCommentsRefs(
+ new AllUsersName("All-Users"), repoManager, cleanupPercentage);
+
+ clean.execute();
+
+ /* Now ref3 is deleted */
+ assertThat(usersRepo.getRefDatabase().getRefs()).hasSize(1);
+ assertThat(usersRepo.exactRef(ref1.getName())).isNotNull();
+ assertThat(usersRepo.exactRef(ref2.getName())).isNull();
+ assertThat(usersRepo.exactRef(ref3.getName())).isNull();
+ }
+ }
+
+ @Test
+ public void cleanZombieDraftsLarge() throws Exception {
+ try (Repository usersRepo = repoManager.createRepository(allUsersProject)) {
+ int goodRefsCnt = 5000;
+ int zombieRefsCnt = 5000;
+ int userIdGoodRefs = 1000001;
+ int userIdBadRefs = 1000002;
+
+ Ref nonEmptyBaseRef = createRefWithNonEmptyTreeCommit(usersRepo, 1, userIdGoodRefs);
+ Ref emptyBaseRef = createRefWithEmptyTreeCommit(usersRepo, 1, userIdBadRefs);
+
+ List<String> goodRefs =
+ createNRefsOnCommit(
+ usersRepo, nonEmptyBaseRef.getObjectId(), goodRefsCnt, userIdGoodRefs);
+ List<String> badRefs =
+ createNRefsOnCommit(usersRepo, emptyBaseRef.getObjectId(), zombieRefsCnt, userIdBadRefs);
+
+ goodRefs.add(0, nonEmptyBaseRef.getName());
+ badRefs.add(0, emptyBaseRef.getName());
+
+ assertThat(usersRepo.getRefDatabase().getRefs().size())
+ .isEqualTo(goodRefs.size() + badRefs.size());
+
+ DeleteZombieCommentsRefs clean =
+ new DeleteZombieCommentsRefs(new AllUsersName("All-Users"), repoManager, null);
+ clean.execute();
+
+ assertThat(
+ usersRepo.getRefDatabase().getRefs().stream()
+ .map(Ref::getName)
+ .collect(toImmutableList()))
+ .containsExactlyElementsIn(goodRefs);
+
+ assertThat(
+ usersRepo.getRefDatabase().getRefs().stream()
+ .map(Ref::getName)
+ .collect(toImmutableList()))
+ .containsNoneIn(badRefs);
+ }
+ }
+
+ private static List<String> createNRefsOnCommit(
+ Repository usersRepo, ObjectId commitId, int n, int uuid) throws IOException {
+ List<String> refNames = new ArrayList<>();
+ BatchRefUpdate bru = usersRepo.getRefDatabase().newBatchUpdate();
+ bru.setAtomic(true);
+ for (int i = 2; i <= n + 1; i++) {
+ String refName = getRefName(i, uuid);
+ bru.addCommand(
+ new ReceiveCommand(ObjectId.zeroId(), commitId, refName, ReceiveCommand.Type.CREATE));
+ refNames.add(refName);
+ }
+ RefUpdateUtil.executeChecked(bru, usersRepo);
+ return refNames;
+ }
+
+ private static String getRefName(int changeId, int userId) {
+ Change.Id cId = Change.id(changeId);
+ Account.Id aId = Account.id(userId);
+ return RefNames.refsDraftComments(cId, aId);
+ }
+
+ private static Ref createRefWithNonEmptyTreeCommit(Repository usersRepo, int changeId, int userId)
+ throws IOException {
+ RevWalk rw = new RevWalk(usersRepo);
+ ObjectId fileObj = createBlob(usersRepo, String.format("file %d content", changeId));
+ ObjectId treeObj =
+ createTree(usersRepo, rw.lookupBlob(fileObj), String.format("file%d.txt", changeId));
+ ObjectId commitObj = createCommit(usersRepo, treeObj, null);
+ Ref refObj = createRef(usersRepo, commitObj, getRefName(changeId, userId));
+ return refObj;
+ }
+
+ private static Ref createRefWithEmptyTreeCommit(Repository usersRepo, int changeId, int userId)
+ throws IOException {
+ ObjectId treeEmpty = createTree(usersRepo, null, "");
+ ObjectId commitObj = createCommit(usersRepo, treeEmpty, null);
+ Ref refObj = createRef(usersRepo, commitObj, getRefName(changeId, userId));
+ return refObj;
+ }
+
+ private static Ref createRef(Repository repo, ObjectId commitId, String refName)
+ throws IOException {
+ RefUpdate update = repo.updateRef(refName);
+ update.setNewObjectId(commitId);
+ update.setForceUpdate(true);
+ update.update();
+ return repo.exactRef(refName);
+ }
+
+ private static ObjectId createCommit(Repository repo, ObjectId treeId, ObjectId parentCommit)
+ throws IOException {
+ try (ObjectInserter oi = repo.newObjectInserter()) {
+ PersonIdent committer =
+ new PersonIdent(new PersonIdent("Foo Bar", "foo.bar@baz.com"), TimeUtil.nowTs());
+ CommitBuilder cb = new CommitBuilder();
+ cb.setTreeId(treeId);
+ cb.setCommitter(committer);
+ cb.setAuthor(committer);
+ cb.setMessage("Test commit");
+ if (parentCommit != null) {
+ cb.setParentIds(parentCommit);
+ }
+ ObjectId commitId = oi.insert(cb);
+ oi.flush();
+ oi.close();
+ return commitId;
+ }
+ }
+
+ private static ObjectId createTree(Repository repo, RevBlob blob, String blobName)
+ throws IOException {
+ try (ObjectInserter oi = repo.newObjectInserter()) {
+ TreeFormatter formatter = new TreeFormatter();
+ if (blob != null) {
+ formatter.append(blobName, blob);
+ }
+ ObjectId treeId = oi.insert(formatter);
+ oi.flush();
+ oi.close();
+ return treeId;
+ }
+ }
+
+ private static ObjectId createBlob(Repository repo, String content) throws IOException {
+ try (ObjectInserter oi = repo.newObjectInserter()) {
+ ObjectId blobId = oi.insert(Constants.OBJ_BLOB, content.getBytes(UTF_8));
+ oi.flush();
+ oi.close();
+ return blobId;
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 9c12052..38e4ca4 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -2025,7 +2025,9 @@
}
@Test
- @GerritConfig(name = "index.change.indexMergeable", value = "true")
+ @GerritConfig(
+ name = "change.mergeabilityComputationBehavior",
+ value = "API_REF_UPDATED_AND_CHANGE_REINDEX")
public void mergeable() throws Exception {
TestRepository<Repo> repo = createProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().add("file1", "contents1").create());
@@ -2043,7 +2045,7 @@
// If a change gets submitted, the remaining open changes get reindexed asynchronously to update
// their mergeability information. If the further assertions in this test are done before the
// asynchronous reindex completed they fail because the mergeability information in the index
- // was not updated yet. To avoid this flakiness indexMergeable is switched off for the
+ // was not updated yet. To avoid this flakiness indexing mergeable is switched off for the
// tests and we index change2 synchronously here.
gApi.changes().id(change2.getChangeId()).index();
@@ -3083,7 +3085,7 @@
}
@Test
- @GerritConfig(name = "index.change.indexMergeable", value = "false")
+ @GerritConfig(name = "change.mergeabilityComputationBehavior", value = "NEVER")
public void mergeableFailsWhenNotIndexed() throws Exception {
TestRepository<Repo> repo = createProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().add("file1", "contents1").create());
diff --git a/lib/BUILD b/lib/BUILD
index 199f4fb..f0c0aad 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -114,6 +114,7 @@
name = "caffeine",
data = ["//lib:LICENSE-Apache2.0"],
visibility = [
+ "//java/com/google/gerrit/acceptance:__pkg__",
"//java/com/google/gerrit/server/cache/mem:__pkg__",
],
exports = ["@caffeine//jar"],
@@ -128,6 +129,7 @@
name = "caffeine-guava",
data = ["//lib:LICENSE-Apache2.0"],
visibility = [
+ "//java/com/google/gerrit/acceptance:__pkg__",
"//java/com/google/gerrit/server/cache/mem:__pkg__",
],
exports = [":caffeine-guava-renamed"],
diff --git a/lib/jackson/BUILD b/lib/jackson/BUILD
index 3eed77a..d5253a0 100644
--- a/lib/jackson/BUILD
+++ b/lib/jackson/BUILD
@@ -4,6 +4,7 @@
name = "jackson-core",
data = ["//lib:LICENSE-Apache2.0"],
visibility = [
+ "//java/com/google/gerrit/acceptance:__pkg__",
"//java/com/google/gerrit/elasticsearch:__pkg__",
"//plugins:__pkg__",
],
diff --git a/plugins/BUILD b/plugins/BUILD
index ccb97e8..8d9682f 100644
--- a/plugins/BUILD
+++ b/plugins/BUILD
@@ -93,7 +93,18 @@
]
java_binary(
+ name = "bouncycastle-deploy-env",
+ main_class = "Dummy",
+ runtime_deps = [
+ "//lib/bouncycastle:bcpg",
+ "//lib/bouncycastle:bcpkix",
+ "//lib/bouncycastle:bcprov",
+ ],
+)
+
+java_binary(
name = "plugin-api",
+ deploy_env = ["bouncycastle-deploy-env"],
main_class = "Dummy",
visibility = ["//visibility:public"],
runtime_deps = [":plugin-lib"],
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index 283bd74..f4bd6a6 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -205,9 +205,6 @@
<gr-confirm-revert-dialog id="confirmRevertDialog"
class="confirmDialog"
on-confirm="_handleRevertDialogConfirm"
- commit-message="[[commitMessage]]"
- change="[[change]]"
- changes="[[_revertChanges]]"
on-cancel="_handleConfirmDialogCancel"
hidden></gr-confirm-revert-dialog>
<gr-confirm-revert-submission-dialog id="confirmRevertSubmissionDialog"
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index c0a603e..299c12a 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -923,9 +923,13 @@
showRevertDialog() {
const query = 'submissionid:' + this.change.submission_id;
+ /* A chromium plugin expects that the modifyRevertMsg hook will only
+ be called after the revert button is pressed, hence we populate the
+ revert dialog after revert button is pressed. */
this.$.restAPI.getChanges('', query)
.then(changes => {
- this._revertChanges = changes;
+ this.$.confirmRevertDialog.populate(this.change,
+ this.commitMessage, changes);
this._showActionDialog(this.$.confirmRevertDialog);
});
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 6495adb..088bebd 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -820,6 +820,11 @@
element.change = {
current_revision: 'abc1234',
};
+ sandbox.stub(element.$.restAPI, 'getChanges')
+ .returns(Promise.resolve([
+ {change_id: '12345678901234', topic: 'T', subject: 'random'},
+ {change_id: '23456', topic: 'T', subject: 'a'.repeat(100)},
+ ]));
sandbox.stub(element.$.confirmRevertDialog,
'_populateRevertSubmissionMessage', () => 'original msg');
flush(() => {
@@ -970,7 +975,7 @@
});
});
- test('confirm revert dialog shows one radio button', done => {
+ test('confirm revert dialog shows no radio button', done => {
const revertButton = element.shadowRoot
.querySelector('gr-button[data-action-key="revert"]');
MockInteractions.tap(revertButton);
@@ -978,7 +983,7 @@
const confirmRevertDialog = element.$.confirmRevertDialog;
const radioInputs = confirmRevertDialog.shadowRoot
.querySelectorAll('input[name="revertOptions"]');
- assert.equal(radioInputs.length, 1);
+ assert.equal(radioInputs.length, 0);
const msg = 'Revert "random commit message"\n\n'
+ 'This reverts commit 2000.\n\nReason '
+ 'for revert: <INSERT REASONING HERE>\n';
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 02b017b..0e86746 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -612,8 +612,10 @@
title$="[[_computeTotalCommentCounts(_change.unresolved_comment_count, _changeComments)]]">
<span>Comment Threads</span></gr-tooltip-content>
</paper-tab>
+ <paper-tab class="robotComments">Findings</paper-tab>
</paper-tabs>
- <template is="dom-if" if="[[_showMessagesView]]">
+ <template is="dom-if" if="[[_isSelectedView(_currentView,
+ _commentTabs.CHANGE_LOG)]]">
<gr-messages-list
class="hideOnMobileOverlay"
change-num="[[_changeNum]]"
@@ -626,12 +628,24 @@
on-message-anchor-tap="_handleMessageAnchorTap"
on-reply="_handleMessageReply"></gr-messages-list>
</template>
- <template is="dom-if" if="[[!_showMessagesView]]">
+ <template is="dom-if" if="[[_isSelectedView(_currentView,
+ _commentTabs.COMMENT_THREADS)]]">
<gr-thread-list
threads="[[_commentThreads]]"
change="[[_change]]"
change-num="[[_changeNum]]"
logged-in="[[_loggedIn]]"
+ only-show-robot-comments-with-human-reply
+ on-thread-list-modified="_handleReloadDiffComments"></gr-thread-list>
+ </template>
+ <template is="dom-if" if="[[_isSelectedView(_currentView,
+ _commentTabs.ROBOT_COMMENTS)]]">
+ <gr-thread-list
+ threads="[[_robotCommentThreads]]"
+ change="[[_change]]"
+ change-num="[[_changeNum]]"
+ logged-in="[[_loggedIn]]"
+ hide-toggle-buttons
on-thread-list-modified="_handleReloadDiffComments"></gr-thread-list>
</template>
</div>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 7536c2c..71f8d6f 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -59,6 +59,12 @@
UNIFIED: 'UNIFIED_DIFF',
};
+ const CommentTabs = {
+ CHANGE_LOG: 0,
+ COMMENT_THREADS: 1,
+ ROBOT_COMMENTS: 2,
+ };
+
const CHANGE_DATA_TIMING_LABEL = 'ChangeDataLoaded';
const CHANGE_RELOAD_TIMING_LABEL = 'ChangeReloaded';
const SEND_REPLY_TIMING_LABEL = 'SendReply';
@@ -132,6 +138,11 @@
computed: '_computeDiffPrefsDisabled(disableDiffPrefs, _loggedIn)',
},
_commentThreads: Array,
+ _robotCommentThreads: {
+ type: Array,
+ computed: '_computeRobotCommentThreads(_commentThreads,'
+ + ' _selectedRevision)',
+ },
/** @type {?} */
_serverConfig: {
type: Object,
@@ -193,6 +204,10 @@
type: String,
value: '',
},
+ _commentTabs: {
+ type: Object,
+ value: CommentTabs,
+ },
_lineHeight: Number,
_changeIdCommitMessageError: {
type: String,
@@ -280,9 +295,9 @@
type: Boolean,
value: undefined,
},
- _showMessagesView: {
- type: Boolean,
- value: true,
+ _currentView: {
+ type: Number,
+ value: CommentTabs.CHANGE_LOG,
},
_showFileTabContent: {
type: Boolean,
@@ -460,7 +475,11 @@
}
_handleCommentTabChange() {
- this._showMessagesView = this.$.commentTabs.selected === 0;
+ this._currentView = this.$.commentTabs.selected;
+ }
+
+ _isSelectedView(currentView, view) {
+ return currentView === view;
}
_handleFileTabChange(e) {
@@ -475,8 +494,7 @@
const tabName = this._selectedFilesTabPluginEndpoint || 'files';
const source = e && e.type ? e.type : '';
- this.$.reporting.reportInteraction('tab-changed',
- `tabname: ${tabName}, source: ${source}`);
+ this.$.reporting.reportInteraction('tab-changed', {tabName, source});
}
}
@@ -488,7 +506,7 @@
}
this.shadowRoot.querySelector('#primaryTabs').selected = idx + 1;
this.shadowRoot.querySelector('#primaryTabs').scrollIntoView();
- this.$.reporting.reportInteraction('show-tab', e.detail.tab);
+ this.$.reporting.reportInteraction('show-tab', {tabName: e.detail.tab});
}
_handleEditCommitMessage(e) {
@@ -562,6 +580,15 @@
return false;
}
+ _computeRobotCommentThreads(commentThreads, selectedRevision) {
+ if (!commentThreads || !selectedRevision) return [];
+ return commentThreads.filter(thread => {
+ const comments = thread.comments || [];
+ return comments.length && comments[0].robot_id && (comments[0].patch_set
+ === selectedRevision._number);
+ });
+ }
+
_handleReloadCommentThreads() {
// Get any new drafts that have been saved in the diff view and show
// in the comment thread view.
@@ -773,8 +800,7 @@
// get messed up if changed here, because it requires the tabs to be on
// the streen, and they are hidden shortly after this. The tab switching
// animation will happen in post render tasks.
- this._showMessagesView = true;
-
+ this._currentView = CommentTabs.CHANGE_LOG;
if (value.view !== Gerrit.Nav.View.CHANGE) {
this._initialLoadComplete = false;
return;
@@ -809,6 +835,7 @@
}
this._reloadPatchNumDependentResources().then(() => {
this._sendShowChangeEvent();
+ this._setPrimaryTab();
});
return;
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 1ef79cf..fe02876 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -63,6 +63,202 @@
let navigateToChangeStub;
const TEST_SCROLL_TOP_PX = 100;
+ const CommentTabs = {
+ CHANGE_LOG: 0,
+ COMMENT_THREADS: 1,
+ };
+
+ const THREADS = [
+ {
+ comments: [
+ {
+ __path: '/COMMIT_MSG',
+ author: {
+ _account_id: 1000000,
+ name: 'user',
+ username: 'user',
+ },
+ patch_set: 4,
+ id: 'ecf0b9fa_fe1a5f62',
+ line: 5,
+ updated: '2018-02-08 18:49:18.000000000',
+ message: 'test',
+ unresolved: true,
+ },
+ {
+ id: '503008e2_0ab203ee',
+ path: '/COMMIT_MSG',
+ line: 5,
+ in_reply_to: 'ecf0b9fa_fe1a5f62',
+ updated: '2018-02-13 22:48:48.018000000',
+ message: 'draft',
+ unresolved: false,
+ __draft: true,
+ __draftID: '0.m683trwff68',
+ __editing: false,
+ patch_set: '2',
+ },
+ ],
+ patchNum: 4,
+ path: '/COMMIT_MSG',
+ line: 5,
+ rootId: 'ecf0b9fa_fe1a5f62',
+ start_datetime: '2018-02-08 18:49:18.000000000',
+ },
+ {
+ comments: [
+ {
+ __path: 'test.txt',
+ author: {
+ _account_id: 1000000,
+ name: 'user',
+ username: 'user',
+ },
+ patch_set: 3,
+ id: '09a9fb0a_1484e6cf',
+ side: 'PARENT',
+ updated: '2018-02-13 22:47:19.000000000',
+ message: 'Some comment on another patchset.',
+ unresolved: false,
+ },
+ ],
+ patchNum: 3,
+ path: 'test.txt',
+ rootId: '09a9fb0a_1484e6cf',
+ start_datetime: '2018-02-13 22:47:19.000000000',
+ commentSide: 'PARENT',
+ },
+ {
+ comments: [
+ {
+ __path: '/COMMIT_MSG',
+ author: {
+ _account_id: 1000000,
+ name: 'user',
+ username: 'user',
+ },
+ patch_set: 2,
+ id: '8caddf38_44770ec1',
+ line: 4,
+ updated: '2018-02-13 22:48:40.000000000',
+ message: 'Another unresolved comment',
+ unresolved: true,
+ },
+ ],
+ patchNum: 2,
+ path: '/COMMIT_MSG',
+ line: 4,
+ rootId: '8caddf38_44770ec1',
+ start_datetime: '2018-02-13 22:48:40.000000000',
+ },
+ {
+ comments: [
+ {
+ __path: '/COMMIT_MSG',
+ author: {
+ _account_id: 1000000,
+ name: 'user',
+ username: 'user',
+ },
+ patch_set: 2,
+ id: 'scaddf38_44770ec1',
+ line: 4,
+ updated: '2018-02-14 22:48:40.000000000',
+ message: 'Yet another unresolved comment',
+ unresolved: true,
+ },
+ ],
+ patchNum: 2,
+ path: '/COMMIT_MSG',
+ line: 4,
+ rootId: 'scaddf38_44770ec1',
+ start_datetime: '2018-02-14 22:48:40.000000000',
+ },
+ {
+ comments: [
+ {
+ id: 'zcf0b9fa_fe1a5f62',
+ path: '/COMMIT_MSG',
+ line: 6,
+ updated: '2018-02-15 22:48:48.018000000',
+ message: 'resolved draft',
+ unresolved: false,
+ __draft: true,
+ __draftID: '0.m683trwff68',
+ __editing: false,
+ patch_set: '2',
+ },
+ ],
+ patchNum: 4,
+ path: '/COMMIT_MSG',
+ line: 6,
+ rootId: 'zcf0b9fa_fe1a5f62',
+ start_datetime: '2018-02-09 18:49:18.000000000',
+ },
+ {
+ comments: [
+ {
+ __path: '/COMMIT_MSG',
+ author: {
+ _account_id: 1000000,
+ name: 'user',
+ username: 'user',
+ },
+ patch_set: 4,
+ id: 'rc1',
+ line: 5,
+ updated: '2019-02-08 18:49:18.000000000',
+ message: 'test',
+ unresolved: true,
+ robot_id: 'rc1',
+ },
+ ],
+ patchNum: 4,
+ path: '/COMMIT_MSG',
+ line: 5,
+ rootId: 'rc1',
+ start_datetime: '2019-02-08 18:49:18.000000000',
+ },
+ {
+ comments: [
+ {
+ __path: '/COMMIT_MSG',
+ author: {
+ _account_id: 1000000,
+ name: 'user',
+ username: 'user',
+ },
+ patch_set: 4,
+ id: 'rc2',
+ line: 5,
+ updated: '2019-03-08 18:49:18.000000000',
+ message: 'test',
+ unresolved: true,
+ robot_id: 'rc2',
+ },
+ {
+ __path: '/COMMIT_MSG',
+ author: {
+ _account_id: 1000000,
+ name: 'user',
+ username: 'user',
+ },
+ patch_set: 4,
+ id: 'c2_1',
+ line: 5,
+ updated: '2019-03-08 18:49:18.000000000',
+ message: 'test',
+ unresolved: true,
+ },
+ ],
+ patchNum: 4,
+ path: '/COMMIT_MSG',
+ line: 5,
+ rootId: 'rc2',
+ start_datetime: '2019-03-08 18:49:18.000000000',
+ },
+ ];
+
setup(() => {
sandbox = sinon.sandbox.create();
stub('gr-endpoint-decorator', {
@@ -375,7 +571,7 @@
test('thread list modified', () => {
sandbox.spy(element, '_handleReloadDiffComments');
- element._showMessagesView = false;
+ element._currentView = CommentTabs.COMMENT_THREADS;
flushAsynchronousOperations();
return element._reloadComments().then(() => {
@@ -404,57 +600,90 @@
});
});
- test('thread list and change log tabs', done => {
- element._changeNum = '1';
- element._patchRange = {
- basePatchNum: 'PARENT',
- patchNum: 1,
- };
- element._change = {
- change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
- revisions: {
- rev2: {_number: 2, commit: {parents: []}},
- rev1: {_number: 1, commit: {parents: []}},
- rev13: {_number: 13, commit: {parents: []}},
- rev3: {_number: 3, commit: {parents: []}},
- },
- current_revision: 'rev3',
- status: 'NEW',
- labels: {
- test: {
- all: [],
- default_value: 0,
- values: [],
- approved: {},
+ suite('thread list and change log tabs', () => {
+ setup(() => {
+ element._changeNum = '1';
+ element._patchRange = {
+ basePatchNum: 'PARENT',
+ patchNum: 1,
+ };
+ element._change = {
+ change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
+ revisions: {
+ rev2: {_number: 2, commit: {parents: []}},
+ rev1: {_number: 1, commit: {parents: []}},
+ rev13: {_number: 13, commit: {parents: []}},
+ rev3: {_number: 3, commit: {parents: []}},
},
- },
- };
- sandbox.stub(element.$.relatedChanges, 'reload');
- sandbox.stub(element, '_reload').returns(Promise.resolve());
- sandbox.spy(element, '_paramsChanged');
- element.params = {view: 'change', changeNum: '1'};
+ current_revision: 'rev3',
+ status: 'NEW',
+ labels: {
+ test: {
+ all: [],
+ default_value: 0,
+ values: [],
+ approved: {},
+ },
+ },
+ };
+ sandbox.stub(element.$.relatedChanges, 'reload');
+ sandbox.stub(element, '_reload').returns(Promise.resolve());
+ sandbox.spy(element, '_paramsChanged');
+ element.params = {view: 'change', changeNum: '1'};
+ });
- // When the change is hard reloaded, paramsChanged will not set the tab.
- // It will be set in postLoadTasks, which requires the flush() to detect.
- assert.isTrue(element._paramsChanged.called);
- assert.isUndefined(element.$.commentTabs.selected);
+ test('tab switch works correctly', done => {
+ assert.isTrue(element._paramsChanged.called);
+ assert.equal(element.$.commentTabs.selected, CommentTabs.CHANGE_LOG);
- // Wait for tab to get selected
- flush(() => {
- assert.equal(element.$.commentTabs.selected, 0);
- assert.isTrue(element._showMessagesView);
- // Switch to comment thread tab
- MockInteractions.tap(element.$$('paper-tab.commentThreads'));
- assert.equal(element.$.commentTabs.selected, 1);
- assert.isFalse(element._showMessagesView);
+ // Wait for tab to get selected
+ flush(() => {
+ assert.equal(element.$.commentTabs.selected, CommentTabs.CHANGE_LOG);
+ assert.equal(element._currentView, CommentTabs.CHANGE_LOG);
+ // Switch to comment thread tab
+ MockInteractions.tap(element.$$('paper-tab.commentThreads'));
+ assert.equal(element.$.commentTabs.selected, 1);
+ assert.equal(element._currentView, CommentTabs.COMMENT_THREADS);
- // When the change is partially reloaded (ex: Shift+R), the content
- // is swapped out before the tab, so messages list will display even
- // though the tab for comment threads is still temporarily selected.
- element._paramsChanged(element.params);
- assert.equal(element.$.commentTabs.selected, 1);
- assert.isTrue(element._showMessagesView);
- done();
+ // When the change is partially reloaded (ex: Shift+R), the content
+ // is swapped out before the tab, so messages list will display even
+ // though the tab for comment threads is still temporarily selected.
+ element._paramsChanged(element.params);
+ assert.equal(element.$.commentTabs.selected,
+ CommentTabs.COMMENT_THREADS);
+ assert.equal(element._currentView, CommentTabs.CHANGE_LOG);
+ flush(() => {
+ // Correct tab is selected after the patchset is changed
+ assert.equal(element.$.commentTabs.selected,
+ CommentTabs.CHANGE_LOG);
+ assert.equal(element._currentView, CommentTabs.CHANGE_LOG);
+ done();
+ });
+ });
+ });
+ });
+
+ suite('Findings comment tab', () => {
+ setup(done => {
+ element._commentThreads = THREADS;
+ element._selectedRevision = {_number: 4};
+ flush(() => {
+ done();
+ });
+ });
+ test('only robot comments are rendered', () => {
+ assert.equal(element._robotCommentThreads.length, 2);
+ assert.equal(element._robotCommentThreads[0].comments[0].robot_id,
+ 'rc1');
+ assert.equal(element._robotCommentThreads[1].comments[0].robot_id,
+ 'rc2');
+ });
+ test('changing patchsets resets robot comments', done => {
+ element._selectedRevision = {_number: 3};
+ flush(() => {
+ assert.equal(element._robotCommentThreads.length, 0);
+ done();
+ });
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
index a7e65a3..52c0c89 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
@@ -36,18 +36,15 @@
word-wrap: break-word;
}
.file {
- border-top: 1px solid var(--border-color);
- font-weight: var(--font-weight-bold);
- margin: 10px 0 3px;
- padding: 10px 0 5px;
+ padding: var(--spacing-s) 0;
}
.container {
display: flex;
- margin: var(--spacing-m) 0;
+ padding: var(--spacing-s) 0;
}
.lineNum {
- margin-right: var(--spacing-m);
- min-width: 10em;
+ margin-right: var(--spacing-s);
+ min-width: 135px;
text-align: right;
}
.message {
@@ -57,16 +54,16 @@
@media screen and (max-width: 50em) {
.container {
flex-direction: column;
- margin: 0 0 var(--spacing-m) var(--spacing-m);
}
.lineNum {
+ margin-right: 0;
min-width: initial;
text-align: left;
}
}
</style>
<template is="dom-repeat" items="[[_computeFilesFromComments(comments)]]" as="file">
- <div class="file">[[computeDisplayPath(file)]]:</div>
+ <div class="file"><a class="fileLink" href="[[_computeDiffURL(file, changeNum, comments)]]">[[computeDisplayPath(file)]]</a></div>
<template is="dom-repeat"
items="[[_computeCommentsForFile(comments, file)]]" as="comment">
<div class="container">
@@ -74,7 +71,7 @@
href$="[[_computeDiffLineURL(file, changeNum, comment.patch_set, comment)]]">
<span hidden$="[[!comment.line]]">
<span>[[_computePatchDisplayName(comment)]]</span>
- Line <span>[[comment.line]]</span>:
+ Line <span>[[comment.line]]</span>
</span>
<span hidden$="[[comment.line]]">
File comment:
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
index 58bdbbc..16c55cd 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
@@ -52,18 +52,29 @@
return comment.side === 'PARENT';
}
- _computeDiffLineURL(file, changeNum, patchNum, comment) {
+ _computeDiffURL(filePath, changeNum, allComments) {
+ if ([filePath, changeNum, allComments].some(arg => arg === undefined)) {
+ return;
+ }
+ const fileComments = this._computeCommentsForFile(allComments, filePath);
+ // This can happen for files that don't exist anymore in the current ps.
+ if (fileComments.length === 0) return;
+ return Gerrit.Nav.getUrlForDiffById(changeNum, this.projectName,
+ filePath, fileComments[0].patch_set);
+ }
+
+ _computeDiffLineURL(filePath, changeNum, patchNum, comment) {
const basePatchNum = comment.hasOwnProperty('parent') ?
-comment.parent : null;
- return Gerrit.Nav.getUrlForDiffById(this.changeNum, this.projectName,
- file, patchNum, basePatchNum, comment.line,
+ return Gerrit.Nav.getUrlForDiffById(changeNum, this.projectName,
+ filePath, patchNum, basePatchNum, comment.line,
this._isOnParent(comment));
}
- _computeCommentsForFile(comments, file) {
+ _computeCommentsForFile(comments, filePath) {
// Changes are not picked up by the dom-repeat due to the array instance
// identity not changing even when it has elements added/removed from it.
- return (comments[file] || []).slice();
+ return (comments[filePath] || []).slice();
}
_computePatchDisplayName(comment) {
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 0ad3ed3..144cf20 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
@@ -67,56 +67,40 @@
<div class="error" hidden$="[[!_showErrorMessage]]">
<span> A reason is required </span>
</div>
- <div class="revertSubmissionLayout">
- <input
- name="revertOptions"
- type="radio"
- id="revertSingleChange"
- on-change="_handleRevertSingleChangeClicked"
- checked="[[_computeIfSingleRevert(_revertType)]]">
- <label for="revertSingleChange" class="label revertSingleChange">
- Revert single change
- </label>
- </div>
<template is="dom-if" if="[[_showRevertSubmission]]">
<div class="revertSubmissionLayout">
<input
name="revertOptions"
type="radio"
+ id="revertSingleChange"
+ on-change="_handleRevertSingleChangeClicked"
+ checked="[[_computeIfSingleRevert(_revertType)]]">
+ <label for="revertSingleChange" class="label revertSingleChange">
+ Revert single change
+ </label>
+ </div>
+ <div class="revertSubmissionLayout">
+ <input
+ name="revertOptions"
+ type="radio"
id="revertSubmission"
on-change="_handleRevertSubmissionClicked"
checked="[[_computeIfRevertSubmission(_revertType)]]">
<label for="revertSubmission" class="label revertSubmission">
- Revert entire submission ([[changes.length]] Changes)
+ Revert entire submission ([[_changesCount]] Changes)
</label>
</template>
<gr-endpoint-decorator name="confirm-revert-change">
- <!-- Duplicating the text-area as a plugin in the case of a single
- revert will override the entire textarea which should not happen
- for multiple revert -->
- <template is="dom-if" if="[[_computeIfSingleRevert(_revertType)]]">
- <label for="messageInput">
- Revert Commit Message
- </label>
- <iron-autogrow-textarea
- id="messageInput"
- class="message"
- autocomplete="on"
- max-rows="15"
- bind-value="{{_message}}"></iron-autogrow-textarea>
- </template>
- </gr-endpoint-decorator>
- <template is="dom-if" if="[[_computeIfRevertSubmission(_revertType)]]">
<label for="messageInput">
Revert Commit Message
</label>
<iron-autogrow-textarea
- id="messageInput"
- class="message"
- autocomplete="on"
- max-rows="15"
- bind-value="{{_message}}"></iron-autogrow-textarea>
- </template>
+ id="messageInput"
+ class="message"
+ autocomplete="on"
+ max-rows="15"
+ bind-value="{{_message}}"></iron-autogrow-textarea>
+ </gr-endpoint-decorator>
</div>
</gr-dialog>
<gr-js-api-interface id="jsAPI"></gr-js-api-interface>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
index b07738a..05660bf 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
@@ -62,22 +62,14 @@
type: Boolean,
value: false,
},
- /* List of changes with the same topic
- value is empty if only a single change is being reverted */
- changes: {
- type: Array,
- value() { return []; },
- },
- change: Object,
- /* commit message is _latestCommitMessage coming from gr-change-view
- read only and is not meant to be modified */
- commitMessage: String,
+ _changesCount: Number,
_showErrorMessage: {
type: Boolean,
value: false,
},
/* store the default revert messages per revert type so that we can
- check if user has edited the revert message or not */
+ check if user has edited the revert message or not
+ Set when populate() is called */
_originalRevertMessages: {
type: Array,
value() { return []; },
@@ -90,12 +82,6 @@
};
}
- static get observers() {
- return [
- 'onInputUpdate(change, commitMessage, changes)',
- ];
- }
-
_computeIfSingleRevert(revertType) {
return revertType === REVERT_TYPES.REVERT_SINGLE_CHANGE;
}
@@ -109,12 +95,12 @@
message, commitMessage);
}
- onInputUpdate(change, commitMessage, changes) {
- if (!change || !changes) return;
+ populate(change, commitMessage, changes) {
+ this._changesCount = changes.length;
// The option to revert a single change is always available
this._populateRevertSingleChangeMessage(
change, commitMessage, change.current_revision);
- this._populateRevertSubmissionMessage(change, changes);
+ this._populateRevertSubmissionMessage(change, changes, commitMessage);
}
_populateRevertSingleChangeMessage(change, commitMessage, commitHash) {
@@ -144,12 +130,12 @@
return subject.substring(0, CHANGE_SUBJECT_LIMIT) + '...';
}
- _modifyRevertSubmissionMsg(change, msg) {
+ _modifyRevertSubmissionMsg(change, msg, commitMessage) {
return this.$.jsAPI.modifyRevertSubmissionMsg(change, msg,
- this.commitMessage);
+ commitMessage);
}
- _populateRevertSubmissionMessage(change, changes) {
+ _populateRevertSubmissionMessage(change, changes, commitMessage) {
// Follow the same convention of the revert
const commitHash = change.current_revision;
if (!commitHash) {
@@ -166,7 +152,8 @@
this._message += change.change_id.substring(0, 10) + ':'
+ this._getTrimmedChangeSubject(change.subject) + '\n';
});
- this._message = this._modifyRevertSubmissionMsg(change, this._message);
+ this._message = this._modifyRevertSubmissionMsg(change, this._message,
+ commitMessage);
this._revertType = REVERT_TYPES.REVERT_SUBMISSION;
this._revertMessages[this._revertType] = this._message;
this._originalRevertMessages[this._revertType] = this._message;
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.html b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
index fbd5d68d..e77bf57 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
@@ -45,44 +45,19 @@
:host(.expanded) {
cursor: auto;
}
- :host > div {
- padding: 0 var(--spacing-l);
- }
- gr-avatar {
- position: absolute;
- left: var(--spacing-l);
- }
.collapsed .contentContainer {
- align-items: baseline;
+ align-items: center;
color: var(--deemphasized-text-color);
display: flex;
white-space: nowrap;
}
.contentContainer {
- margin-left: calc(var(--spacing-l) + 2.5em);
- padding: var(--spacing-m) 0;
+ padding: var(--spacing-m) var(--spacing-l);
}
- .showAvatar.collapsed .contentContainer {
- margin-left: calc(var(--spacing-l) + 1.75em);
- }
- .hideAvatar.collapsed .contentContainer,
- .hideAvatar.expanded .contentContainer {
- margin-left: 0;
- }
- .showAvatar.collapsed .contentContainer,
- .hideAvatar.collapsed .contentContainer,
- .hideAvatar.expanded .contentContainer {
- padding: var(--spacing-m) 0;
- }
- .collapsed gr-avatar {
- top: var(--spacing-m);
- height: var(--line-height-normal);
- width: var(--line-height-normal);
- }
- .expanded gr-avatar {
- top: var(--spacing-l);
- height: var(--line-height-h1);
- width: var(--line-height-h1);
+ .collapsed .contentContainer {
+ /* For expanded state we inherit the alternating background color
+ that is set in gr-messages-list. */
+ background-color: var(--background-color-primary);
}
.name {
font-weight: var(--font-weight-bold);
@@ -129,14 +104,29 @@
color: var(--primary-text-color);
margin-right: var(--spacing-s);
}
+ .authorLabel {
+ min-width: 160px;
+ display: inline-block;
+ }
.expanded .author {
cursor: pointer;
- margin-bottom: var(--spacing-s);
+ margin-bottom: var(--spacing-m);
+ }
+ .expanded .content {
+ padding-left: 40px;
}
.dateContainer {
position: absolute;
+ /* right and top values should match .contentContainer padding */
right: var(--spacing-l);
- top: 10px;
+ top: var(--spacing-m);
+ }
+ .dateContainer .patchset {
+ margin-right: var(--spacing-m);
+ color: var(--deemphasized-text-color);
+ }
+ .dateContainer .patchset:before {
+ content: 'Patchset ';
}
span.date {
color: var(--deemphasized-text-color);
@@ -148,16 +138,23 @@
cursor: pointer;
vertical-align: top;
}
- .replyContainer {
- padding: var(--spacing-m) 0 0 0;
- }
.score {
- border: 1px solid rgba(0,0,0,.12);
border-radius: var(--border-radius);
color: var(--primary-text-color);
display: inline-block;
- margin: -1px 0;
- padding: 0 var(--spacing-xxs);
+ padding: 0 var(--spacing-s);
+ text-align: center;
+ }
+ .score,
+ .commentsSummary {
+ margin-right: var(--spacing-s);
+ min-width: 115px;
+ }
+ .expanded .commentsSummary {
+ display: none;
+ }
+ .commentsIcon {
+ vertical-align: top;
}
.score.negative {
background-color: var(--vote-color-disliked);
@@ -176,35 +173,53 @@
font-weight: var(--font-weight-bold);
};
}
+ @media screen and (max-width: 50em) {
+ .expanded .content {
+ padding-left: 0;
+ }
+ .score,
+ .commentsSummary,
+ .authorLabel {
+ min-width: 0px;
+ }
+ .dateContainer .patchset:before {
+ content: 'PS ';
+ }
+ }
</style>
- <div class$="[[_computeClass(_expanded, showAvatar, message)]]">
- <gr-avatar account="[[author]]" image-size="100"></gr-avatar>
+ <div class$="[[_computeClass(_expanded)]]">
<div class="contentContainer">
<div class="author" on-click="_handleAuthorClick">
<span hidden$="[[!showOnBehalfOf]]">
<span class="name">[[message.real_author.name]]</span>
on behalf of
</span>
- <gr-account-label
- account="[[author]]"
- hide-avatar></gr-account-label>
+ <gr-account-label account="[[author]]" class="authorLabel"></gr-account-label>
<template is="dom-repeat" items="[[_getScores(message)]]" as="score">
<span class$="score [[_computeScoreClass(score, labelExtremes)]]">
[[score.label]] [[score.value]]
</span>
</template>
</div>
+ <template is="dom-if" if="[[_commentCountText]]">
+ <div class="commentsSummary">
+ <iron-icon icon="gr-icons:comment" class="commentsIcon"></iron-icon>
+ <span class="numberOfComments">[[_commentCountText]]</span>
+ </div>
+ </template>
<template is="dom-if" if="[[message.message]]">
<div class="content">
- <div class="message hideOnOpen">[[message.message]]</div>
+ <div class="message hideOnOpen">[[_messageContentCollapsed]]</div>
<gr-formatted-text
no-trailing-margin
class="message hideOnCollapsed"
- content="[[message.message]]"
+ content="[[_messageContentExpanded]]"
config="[[_projectConfig.commentlinks]]"></gr-formatted-text>
- <div class="replyContainer" hidden$="[[!showReplyButton]]" hidden>
- <gr-button link small on-click="_handleReplyTap">Reply</gr-button>
- </div>
+ <template is="dom-if" if="[[!_isMessageContentEmpty(message.message)]]">
+ <div class="replyContainer" hidden$="[[!showReplyButton]]" hidden>
+ <gr-button link small on-click="_handleReplyTap">Reply</gr-button>
+ </div>
+ </template>
<gr-comment-list
comments="[[comments]]"
change-num="[[changeNum]]"
@@ -228,6 +243,9 @@
</div>
</template>
<span class="dateContainer">
+ <template is="dom-if" if="[[message._revision_number]]">
+ <span class="patchset">[[message._revision_number]]</span>
+ </template>
<template is="dom-if" if="[[!message.id]]">
<span class="date">
<gr-date-formatter
@@ -248,7 +266,7 @@
id="expandToggle"
on-click="_toggleExpanded"
title="Toggle expanded state"
- icon="[[_computeExpandToggleIcon(_expanded)]]">
+ icon="[[_computeExpandToggleIcon(_expanded)]]"></iron-icon>
</span>
</div>
</div>
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.js b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
index 59f12f5..95109bb 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.js
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
@@ -69,10 +69,6 @@
type: Boolean,
computed: '_computeIsAutomated(message)',
},
- showAvatar: {
- type: Boolean,
- computed: '_computeShowAvatar(author, config)',
- },
showOnBehalfOf: {
type: Boolean,
computed: '_computeShowOnBehalfOf(message)',
@@ -101,6 +97,18 @@
type: Object,
computed: '_computeExpanded(message.expanded)',
},
+ _messageContentExpanded: {
+ type: String,
+ computed: '_computeMessageContentExpanded(message.message)',
+ },
+ _messageContentCollapsed: {
+ type: String,
+ computed: '_computeMessageContentCollapsed(message.message)',
+ },
+ _commentCountText: {
+ type: Number,
+ computed: '_computeCommentCountText(comments)',
+ },
_loggedIn: {
type: Boolean,
value: false,
@@ -140,12 +148,51 @@
}
}
- _computeAuthor(message) {
- return message.author || message.updated_by;
+ _computeCommentCountText(comments) {
+ if (!comments) return undefined;
+ let count = 0;
+ for (const file in comments) {
+ if (comments.hasOwnProperty(file)) {
+ const commentArray = comments[file] || [];
+ count += commentArray.length;
+ }
+ }
+ if (count === 0) {
+ return undefined;
+ } else if (count === 1) {
+ return '1 comment';
+ } else {
+ return `${count} comments`;
+ }
}
- _computeShowAvatar(author, config) {
- return !!(author && config && config.plugin && config.plugin.has_avatars);
+ _computeMessageContentExpanded(content) {
+ return this._computeMessageContent(content, true);
+ }
+
+ _computeMessageContentCollapsed(content) {
+ return this._computeMessageContent(content, false);
+ }
+
+ _computeMessageContent(content, isExpanded) {
+ if (!content) return '';
+ const lines = content.split('\n');
+ const filteredLines = lines.filter(line => {
+ if (!isExpanded && line.startsWith('>')) return false;
+ if (line.startsWith('Patch Set ')) return false;
+ if (line.startsWith('(') && line.endsWith(' comment)')) return false;
+ if (line.startsWith('(') && line.endsWith(' comments)')) return false;
+ return true;
+ });
+ return filteredLines.join('\n').trim();
+ }
+
+ _isMessageContentEmpty(content) {
+ return this._computeMessageContent(content).trim().length === 0;
+ }
+
+ _computeAuthor(message) {
+ return message.author || message.updated_by;
}
_computeShowOnBehalfOf(message) {
@@ -236,10 +283,9 @@
return classes.join(' ');
}
- _computeClass(expanded, showAvatar, message) {
+ _computeClass(expanded) {
const classes = [];
classes.push(expanded ? 'expanded' : 'collapsed');
- classes.push(showAvatar ? 'showAvatar' : 'hideAvatar');
return classes.join(' ');
}
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
index d71beb4..8d76510 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
@@ -64,6 +64,12 @@
align-items: center;
display: flex;
}
+ gr-message:nth-child(2n) {
+ background-color: var(--background-color-secondary);
+ }
+ gr-message:nth-child(2n+1) {
+ background-color: var(--background-color-tertiary);
+ }
</style>
<div class="header">
<span
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
index 2c47dc1..cd759d3 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
@@ -359,6 +359,16 @@
_processedMessagesChanged(messages) {
if (messages) {
this._visibleMessages = messages.slice(-MAX_INITIAL_SHOWN_MESSAGES);
+
+ if (messages.length === 0) return;
+ const tags = messages.map(message => message.tag || message.type ||
+ (message.comments ? 'comments' : 'none'));
+ const tagsCounted = tags.reduce((acc, val) => {
+ acc[val] = (acc[val] || 0) + 1;
+ return acc;
+ }, {all: messages.length});
+ this.$.reporting.reportInteraction('messages-count',
+ JSON.stringify(tagsCounted));
}
}
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index c177283..d54669f 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -86,19 +86,17 @@
}
.peopleList {
display: flex;
- padding-top: var(--spacing-xxs);
}
.peopleListLabel {
color: var(--deemphasized-text-color);
margin-top: var(--spacing-xs);
- min-width: 7em;
+ min-width: 6em;
padding-right: var(--spacing-m);
}
gr-account-list {
display: flex;
flex-wrap: wrap;
flex: 1;
- min-height: 1.8em;
}
#reviewerConfirmationOverlay {
padding: var(--spacing-l);
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html
index a5875ab1..132ce11 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html
@@ -32,7 +32,15 @@
opacity: .8;
pointer-events: none;
}
- .container > :not(:first-child) {
+ .container {
+ display: block;
+ /* This is a bit of a hack. We tried to use margin-top with
+ :not(:first-child) before, but :first-child does not understand
+ whether a child is visible or not. So adding a margin for every
+ child and then a negative one at the top does the trick. */
+ margin-top: calc(0px - var(--spacing-s));
+ }
+ .container > * {
margin-top: var(--spacing-s);
}
gr-button {
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html
index 4c82d2a..a096aec 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html
@@ -60,18 +60,20 @@
display: block
}
</style>
- <div class="header">
- <div class="toggleItem">
- <paper-toggle-button
- id="unresolvedToggle"
- checked="{{_unresolvedOnly}}"></paper-toggle-button>
- Only unresolved threads</div>
- <div class$="toggleItem draftToggle [[_computeShowDraftToggle(loggedIn)]]">
- <paper-toggle-button
- id="draftToggle"
- checked="{{_draftsOnly}}"></paper-toggle-button>
- Only threads with drafts</div>
- </div>
+ <template is="dom-if" if="[[!hideToggleButtons]]">
+ <div class="header">
+ <div class="toggleItem">
+ <paper-toggle-button
+ id="unresolvedToggle"
+ checked="{{_unresolvedOnly}}"></paper-toggle-button>
+ Only unresolved threads</div>
+ <div class$="toggleItem draftToggle [[_computeShowDraftToggle(loggedIn)]]">
+ <paper-toggle-button
+ id="draftToggle"
+ checked="{{_draftsOnly}}"></paper-toggle-button>
+ Only threads with drafts</div>
+ </div>
+ </template>
<div id="threads">
<template is="dom-if" if="[[!threads.length]]">
There are no inline comment threads on any diff for this change.
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
index 9e42de1..e449fad 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
@@ -41,7 +41,8 @@
_filteredThreads: {
type: Array,
computed: '_computeFilteredThreads(_sortedThreads, ' +
- '_unresolvedOnly, _draftsOnly)',
+ '_unresolvedOnly, _draftsOnly,' +
+ 'onlyShowRobotCommentsWithHumanReply)',
},
_unresolvedOnly: {
type: Boolean,
@@ -51,6 +52,16 @@
type: Boolean,
value: false,
},
+ /* Boolean properties used must default to false if passed as attribute
+ by the parent */
+ onlyShowRobotCommentsWithHumanReply: {
+ type: Boolean,
+ value: false,
+ },
+ hideToggleButtons: {
+ type: Boolean,
+ value: false,
+ },
};
}
@@ -97,12 +108,14 @@
});
}
- _computeFilteredThreads(sortedThreads, unresolvedOnly, draftsOnly) {
+ _computeFilteredThreads(sortedThreads, unresolvedOnly, draftsOnly,
+ onlyShowRobotCommentsWithHumanReply) {
// Polymer 2: check for undefined
if ([
sortedThreads,
unresolvedOnly,
draftsOnly,
+ onlyShowRobotCommentsWithHumanReply,
].some(arg => arg === undefined)) {
return undefined;
}
@@ -124,8 +137,8 @@
humanReplyToRobotComment = true;
}
});
- if (robotComment) {
- return humanReplyToRobotComment ? c : false;
+ if (robotComment && onlyShowRobotCommentsWithHumanReply) {
+ return humanReplyToRobotComment;
}
return c;
}
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
index b29246d..3bc41ce 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
@@ -43,6 +43,7 @@
setup(() => {
sandbox = sinon.sandbox.create();
element = fixture('basic');
+ element.onlyShowRobotCommentsWithHumanReply = true;
element.threads = [
{
comments: [
@@ -314,14 +315,15 @@
});
test('toggle unresolved only shows unresolved comments', () => {
- MockInteractions.tap(element.$.unresolvedToggle);
+ MockInteractions.tap(element.shadowRoot.querySelector(
+ '#unresolvedToggle'));
flushAsynchronousOperations();
assert.equal(Polymer.dom(element.root)
.querySelectorAll('gr-comment-thread').length, 5);
});
test('toggle drafts only shows threads with draft comments', () => {
- MockInteractions.tap(element.$.draftToggle);
+ MockInteractions.tap(element.shadowRoot.querySelector('#draftToggle'));
flushAsynchronousOperations();
assert.equal(Polymer.dom(element.root)
.querySelectorAll('gr-comment-thread').length, 2);
@@ -329,8 +331,9 @@
test('toggle drafts and unresolved only shows threads with drafts and ' +
'publicly unresolved ', () => {
- MockInteractions.tap(element.$.draftToggle);
- MockInteractions.tap(element.$.unresolvedToggle);
+ MockInteractions.tap(element.shadowRoot.querySelector('#draftToggle'));
+ MockInteractions.tap(element.shadowRoot.querySelector(
+ '#unresolvedToggle'));
flushAsynchronousOperations();
assert.equal(Polymer.dom(element.root)
.querySelectorAll('gr-comment-thread').length, 2);
@@ -348,5 +351,18 @@
'ecf0b9fa_fe1a5f62');
assert.equal(dispatchSpy.lastCall.args[0].detail.path, '/COMMIT_MSG');
});
+
+ suite('findings tab', () => {
+ setup(done => {
+ element.hideToggleButtons = true;
+ flush(() => {
+ done();
+ });
+ });
+ test('toggle buttons are hidden', () => {
+ assert.equal(element.shadowRoot.querySelector('.header').style.display,
+ 'none');
+ });
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
index 72f5d5b..4f9b103 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
@@ -196,7 +196,7 @@
let toast = toastSpy.lastCall.returnValue;
assert.isOk(toast);
assert.include(
- Polymer.dom(toast.root).textContent, 'Credentails expired.');
+ Polymer.dom(toast.root).textContent, 'Credentials expired.');
assert.include(
Polymer.dom(toast.root).textContent, 'Refresh credentials');
@@ -267,7 +267,7 @@
// toast
const toast = toastSpy.lastCall.returnValue;
assert.include(
- Polymer.dom(toast.root).textContent, 'Credentails expired.');
+ Polymer.dom(toast.root).textContent, 'Credentials expired.');
assert.include(
Polymer.dom(toast.root).textContent, 'Refresh credentials');
done();
@@ -313,7 +313,7 @@
flush(() => {
let toast = toastSpy.lastCall.returnValue;
assert.include(
- Polymer.dom(toast.root).textContent, 'Credentails expired.');
+ Polymer.dom(toast.root).textContent, 'Credentials expired.');
assert.include(
Polymer.dom(toast.root).textContent, 'Refresh credentials');
@@ -321,12 +321,13 @@
element.fire('show-alert', {
message: 'test-alert', action: 'reload',
});
- toast = toastSpy.lastCall.returnValue;
- assert.isOk(toast);
- assert.include(
- Polymer.dom(toast.root).textContent, 'Credentails expired.');
-
- done();
+ flush(() => {
+ toast = toastSpy.lastCall.returnValue;
+ assert.isOk(toast);
+ assert.include(
+ Polymer.dom(toast.root).textContent, 'Credentials expired.');
+ done();
+ });
});
});
});
@@ -462,4 +463,4 @@
});
});
});
-</script>
\ No newline at end of file
+</script>
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 889f1f3..2ed0206 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
@@ -96,11 +96,9 @@
const DRAFT_ACTION_TIMER = 'TimeBetweenDraftActions';
const DRAFT_ACTION_TIMER_MAX = 2 * 60 * 1000; // 2 minutes.
- const pending = [];
+ let pending = [];
// Variables that hold context info in global scope
- const loadedPlugins = [];
- const detectedExtensions = [];
let reportRepoName = undefined;
const onError = function(oldOnError, msg, url, line, column, error) {
@@ -152,7 +150,7 @@
if (task.duration > 200) {
GrReporting.prototype.reporter(TIMING.TYPE,
TIMING.CATEGORY_UI_LATENCY, `Task ${task.name}`,
- Math.round(task.duration), false);
+ Math.round(task.duration), {}, false);
}
}
});
@@ -160,6 +158,12 @@
}
}
+ document.addEventListener('visibilitychange', () => {
+ const eventName = `Visibility changed to ${document.visibilityState}`;
+ GrReporting.prototype.reporter(INTERACTION_TYPE, undefined, eventName,
+ undefined, {}, true);
+ });
+
// The Polymer pass of JSCompiler requires this to be reassignable
// eslint-disable-next-line prefer-const
let GrReporting = Polymer({
@@ -197,94 +201,75 @@
!this._baselines.hasOwnProperty(TIMER.METRICS_PLUGIN_LOADED);
},
- reporter(...args) {
- const report = (this._isMetricsPluginLoaded() && !pending.length) ?
- this.defaultReporter : this.cachingReporter;
- const contextInfo = {
- loadedPlugins,
- detectedExtensions,
- repoName: reportRepoName,
- isInBackgroundTab: document.visibilityState === 'hidden',
- startTimeMs: this.now(),
- };
- args.splice(4, 0, contextInfo);
- report.apply(this, args);
- },
-
/**
- * The default reporter reports events immediately.
+ * Reporter reports events. Events will be queued if metrics plugin is not
+ * yet installed.
*
* @param {string} type
* @param {string} category
* @param {string} eventName
* @param {string|number} eventValue
- * @param {Object} contextInfo
+ * @param {Object} eventDetails
* @param {boolean|undefined} opt_noLog If true, the event will not be
* logged to the JS console.
*/
- defaultReporter(type, category, eventName, eventValue, contextInfo,
- opt_noLog) {
- const detail = {
+ reporter(type, category, eventName, eventValue, eventDetails, opt_noLog) {
+ const eventInfo = this._createEventInfo(type, category,
+ eventName, eventValue, eventDetails);
+ if (type === ERROR.TYPE && category === ERROR.CATEGORY) {
+ console.error(eventValue && eventValue.error || eventName);
+ }
+
+ // We report events immediately when metrics plugin is loaded
+ if (this._isMetricsPluginLoaded() && !pending.length) {
+ this._reportEvent(eventInfo, opt_noLog);
+ } else {
+ // We cache until metrics plugin is loaded
+ pending.push([eventInfo, opt_noLog]);
+ if (this._isMetricsPluginLoaded()) {
+ pending.forEach(([eventInfo, opt_noLog]) => {
+ this._reportEvent(eventInfo, opt_noLog);
+ });
+ pending = [];
+ }
+ }
+ },
+
+ _reportEvent(eventInfo, opt_noLog) {
+ const {type, value, name} = eventInfo;
+ document.dispatchEvent(new CustomEvent(type, {detail: eventInfo}));
+ if (opt_noLog) { return; }
+ if (type !== ERROR.TYPE) {
+ if (value !== undefined) {
+ console.log(`Reporting: ${name}: ${value}`);
+ } else {
+ console.log(`Reporting: ${name}`);
+ }
+ }
+ },
+
+ _createEventInfo(type, category, name, value, eventDetails) {
+ const eventInfo = {
type,
category,
- name: eventName,
- value: eventValue,
+ name,
+ value,
+ eventStart: this.now(),
};
- if (category === TIMING.CATEGORY_UI_LATENCY && contextInfo) {
- detail.loadedPlugins = contextInfo.loadedPlugins;
- detail.detectedExtensions = contextInfo.detectedExtensions;
- }
- if (contextInfo && contextInfo.repoName) {
- detail.repoName = contextInfo.repoName;
- }
- if (contextInfo && contextInfo.isInBackgroundTab !== undefined) {
- detail.inBackgroundTab = contextInfo.isInBackgroundTab;
- }
- if (contextInfo && contextInfo.startTimeMs) {
- detail.eventStart = contextInfo.startTimeMs;
- }
- document.dispatchEvent(new CustomEvent(type, {detail}));
- if (opt_noLog) { return; }
- if (type === ERROR.TYPE && category === ERROR.CATEGORY) {
- console.error(eventValue && eventValue.error || eventName);
- } else {
- if (eventValue !== undefined) {
- console.log(`Reporting: ${eventName}: ${eventValue}`);
- } else {
- console.log(`Reporting: ${eventName}`);
- }
- }
- },
- /**
- * The caching reporter will queue reports until plugins have loaded, and
- * log events immediately if they're reported after plugins have loaded.
- *
- * @param {string} type
- * @param {string} category
- * @param {string} eventName
- * @param {string|number} eventValue
- * @param {Object} contextInfo
- * @param {boolean|undefined} opt_noLog If true, the event will not be
- * logged to the JS console.
- */
- cachingReporter(type, category, eventName, eventValue, contextInfo,
- opt_noLog) {
- if (type === ERROR.TYPE && category === ERROR.CATEGORY) {
- console.error(eventValue && eventValue.error || eventName);
+ if (typeof(eventDetails) === 'object' &&
+ Object.entries(eventDetails).length !== 0) {
+ eventInfo.eventDetails = JSON.stringify(eventDetails);
}
- if (this._isMetricsPluginLoaded()) {
- if (pending.length) {
- for (const args of pending.splice(0)) {
- this.defaultReporter(...args);
- }
- }
- this.defaultReporter(type, category, eventName, eventValue, contextInfo,
- opt_noLog);
- } else {
- pending.push([type, category, eventName, eventValue, contextInfo,
- opt_noLog]);
+ if (reportRepoName) {
+ eventInfo.repoName = reportRepoName;
}
+ const isInBackgroundTab = document.visibilityState === 'hidden';
+ if (isInBackgroundTab !== undefined) {
+ eventInfo.inBackgroundTab = isInBackgroundTab;
+ }
+
+ return eventInfo;
},
/**
@@ -310,14 +295,14 @@
}
},
- _reportPerformanceTiming(eventName) {
+ _reportPerformanceTiming(eventName, eventDetails) {
const eventTiming = this.performanceTiming[eventName];
if (eventTiming > 0) {
const elapsedTime = eventTiming -
this.performanceTiming.navigationStart;
// NavResTime - Navigation and resource timings.
this.reporter(TIMING.TYPE, TIMING.CATEGORY_UI_LATENCY,
- `NavResTime - ${eventName}`, elapsedTime, true);
+ `NavResTime - ${eventName}`, elapsedTime, eventDetails, true);
}
},
@@ -399,22 +384,19 @@
reportExtension(name) {
this.reporter(EXTENSION.TYPE, EXTENSION.DETECTED, name);
- if (!detectedExtensions.includes(name)) {
- detectedExtensions.push(name);
- }
},
pluginLoaded(name) {
if (name.startsWith('metrics-')) {
this.timeEnd(TIMER.METRICS_PLUGIN_LOADED);
}
- loadedPlugins.push(name);
},
pluginsLoaded(pluginsList) {
this.timeEnd(TIMER.PLUGINS_LOADED);
this.reporter(
- PLUGINS.TYPE, PLUGINS.INSTALLED, (pluginsList || []).join(','));
+ PLUGINS.TYPE, PLUGINS.INSTALLED, PLUGINS.INSTALLED, undefined,
+ {pluginsList: pluginsList || []}, true);
},
/**
@@ -470,9 +452,11 @@
*
* @param {string} name Timing name.
* @param {number} time The time to report as an integer of milliseconds.
+ * @param {Object} eventDetails non sensitive details
*/
- _reportTiming(name, time) {
- this.reporter(TIMING.TYPE, TIMING.CATEGORY_UI_LATENCY, name, time);
+ _reportTiming(name, time, eventDetails) {
+ this.reporter(TIMING.TYPE, TIMING.CATEGORY_UI_LATENCY, name, time,
+ eventDetails);
},
/**
@@ -532,11 +516,12 @@
*/
reportRpcTiming(anonymizedUrl, elapsed) {
this.reporter(TIMING.TYPE, TIMING.CATEGORY_RPC, 'RPC-' + anonymizedUrl,
- elapsed, true);
+ elapsed, {}, true);
},
- reportInteraction(eventName, opt_msg) {
- this.reporter(INTERACTION_TYPE, this.category, eventName, opt_msg);
+ reportInteraction(eventName, details) {
+ this.reporter(INTERACTION_TYPE, this.category, eventName, undefined,
+ details, true);
},
/**
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
index c7aa896..11fe132 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
@@ -67,7 +67,7 @@
sandbox.stub(element, 'now').returns(42);
element.appStarted();
assert.isTrue(
- element.reporter.calledWithExactly(
+ element.reporter.calledWithMatch(
'timing-report', 'UI Latency', 'App Started', 42
));
});
@@ -75,7 +75,7 @@
test('WebComponentsReady', () => {
sandbox.stub(element, 'now').returns(42);
element.timeEnd('WebComponentsReady');
- assert.isTrue(element.reporter.calledWithExactly(
+ assert.isTrue(element.reporter.calledWithMatch(
'timing-report', 'UI Latency', 'WebComponentsReady', 42
));
});
@@ -86,7 +86,7 @@
element.reporter.calledWithExactly(
'timing-report', 'UI Latency', 'NavResTime - loadEventEnd',
fakePerformance.loadEventEnd - fakePerformance.navigationStart,
- true)
+ undefined, true)
);
});
@@ -166,10 +166,10 @@
element.timeEnd('bar');
nowStub.returns(3);
element.timeEnd('foo');
- assert.isTrue(element.reporter.calledWithExactly(
+ assert.isTrue(element.reporter.calledWithMatch(
'timing-report', 'UI Latency', 'foo', 3
));
- assert.isTrue(element.reporter.calledWithExactly(
+ assert.isTrue(element.reporter.calledWithMatch(
'timing-report', 'UI Latency', 'bar', 1
));
});
@@ -179,7 +179,7 @@
const timer = element.getTimer('foo-bar');
nowStub.returns(150);
timer.end();
- assert.isTrue(element.reporter.calledWithExactly(
+ assert.isTrue(element.reporter.calledWithMatch(
'timing-report', 'UI Latency', 'foo-bar', 50));
});
@@ -236,9 +236,9 @@
nowStub.returns(1100);
element.timeEndWithAverage('foo', 'bar', 10);
assert.isTrue(element.reporter.calledTwice);
- assert.isTrue(element.reporter.calledWithExactly(
+ assert.isTrue(element.reporter.calledWithMatch(
'timing-report', 'UI Latency', 'foo', 100));
- assert.isTrue(element.reporter.calledWithExactly(
+ assert.isTrue(element.reporter.calledWithMatch(
'timing-report', 'UI Latency', 'bar', 10));
});
@@ -249,17 +249,36 @@
));
});
+ test('reportInteraction', () => {
+ element.reporter.restore();
+ sandbox.spy(element, '_reportEvent');
+ element.pluginsLoaded(); // so we don't cache
+ element.reportInteraction('button-click', {name: 'sendReply'});
+ assert.isTrue(element._reportEvent.getCall(2).calledWithMatch(
+ {
+ type: 'interaction',
+ name: 'button-click',
+ eventDetails: JSON.stringify({name: 'sendReply'}),
+ }
+ ));
+ });
+
test('report start time', () => {
element.reporter.restore();
sandbox.stub(element, 'now').returns(42);
- sandbox.spy(element, 'defaultReporter');
+ sandbox.spy(element, '_reportEvent');
const dispatchStub = sandbox.spy(document, 'dispatchEvent');
element.pluginsLoaded();
element.time('timeAction');
element.timeEnd('timeAction');
- assert.isTrue(element.defaultReporter.getCall(2).calledWithMatch(
- 'timing-report', 'UI Latency', 'timeAction', 0,
- {startTimeMs: 42}
+ assert.isTrue(element._reportEvent.getCall(2).calledWithMatch(
+ {
+ type: 'timing-report',
+ category: 'UI Latency',
+ name: 'timeAction',
+ value: 0,
+ eventStart: 42,
+ }
));
assert.equal(dispatchStub.getCall(2).args[0].detail.eventStart, 42);
});
@@ -267,50 +286,46 @@
suite('plugins', () => {
setup(() => {
element.reporter.restore();
- sandbox.stub(element, 'defaultReporter');
+ sandbox.stub(element, '_reportEvent');
});
test('pluginsLoaded reports time', () => {
sandbox.stub(element, 'now').returns(42);
element.pluginsLoaded();
- assert.isTrue(element.defaultReporter.calledWith(
- 'timing-report', 'UI Latency', 'PluginsLoaded', 42
+ assert.isTrue(element._reportEvent.calledWithMatch(
+ {
+ type: 'timing-report',
+ category: 'UI Latency',
+ name: 'PluginsLoaded',
+ value: 42,
+ }
));
});
test('pluginsLoaded reports plugins', () => {
element.pluginsLoaded(['foo', 'bar']);
- assert.isTrue(element.defaultReporter.calledWith(
- 'lifecycle', 'Plugins installed', 'foo,bar'
+ assert.isTrue(element._reportEvent.calledWithMatch(
+ {
+ type: 'lifecycle',
+ category: 'Plugins installed',
+ eventDetails: JSON.stringify({pluginsList: ['foo', 'bar']}),
+ }
));
});
test('caches reports if plugins are not loaded', () => {
element.timeEnd('foo');
- assert.isFalse(element.defaultReporter.called);
+ assert.isFalse(element._reportEvent.called);
});
test('reports if plugins are loaded', () => {
element.pluginsLoaded();
- assert.isTrue(element.defaultReporter.called);
- });
-
- test('reports plugins in timing events', () => {
- element.pluginsLoaded = [];
- sandbox.stub(element, 'now').returns(42);
- element.pluginLoaded('metrics-xyz1');
- // element.pluginLoaded('foo');
- element.time('timeAction');
- element.timeEnd('timeAction');
- assert.isTrue(element.defaultReporter.getCall(1).calledWithMatch(
- 'timing-report', 'UI Latency', 'timeAction', 0,
- {loadedPlugins: ['metrics-xyz1']}
- ));
+ assert.isTrue(element._reportEvent.called);
});
test('reports if metrics plugin xyz is loaded', () => {
element.pluginLoaded('metrics-xyz');
- assert.isTrue(element.defaultReporter.called);
+ assert.isTrue(element._reportEvent.called);
});
test('reports cached events preserving order', () => {
@@ -319,17 +334,18 @@
element.timeEnd('foo');
element.pluginsLoaded();
element.timeEnd('bar');
- assert.isTrue(element.defaultReporter.getCall(0).calledWith(
- 'timing-report', 'UI Latency', 'foo'
+ assert.isTrue(element._reportEvent.getCall(0).calledWithMatch(
+ {type: 'timing-report', category: 'UI Latency', name: 'foo'}
));
- assert.isTrue(element.defaultReporter.getCall(1).calledWith(
- 'timing-report', 'UI Latency', 'PluginsLoaded'
+ assert.isTrue(element._reportEvent.getCall(1).calledWithMatch(
+ {type: 'timing-report', category: 'UI Latency',
+ name: 'PluginsLoaded'}
));
- assert.isTrue(element.defaultReporter.getCall(2).calledWith(
- 'lifecycle', 'Plugins installed'
+ assert.isTrue(element._reportEvent.getCall(2).calledWithMatch(
+ {type: 'lifecycle', category: 'Plugins installed'}
));
- assert.isTrue(element.defaultReporter.getCall(3).calledWith(
- 'timing-report', 'UI Latency', 'bar'
+ assert.isTrue(element._reportEvent.getCall(3).calledWithMatch(
+ {type: 'timing-report', category: 'UI Latency', name: 'bar'}
));
});
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
index 05a7525..726ac10 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
@@ -237,6 +237,11 @@
this.moveToNextChunk(true);
}
+ moveToLastChunk() {
+ this.$.cursorManager.moveToEnd();
+ this.moveToPreviousChunk();
+ }
+
reInitCursor() {
this._updateStops();
if (this.initialLineNumber) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
index dff9e79..24ca3d1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
@@ -108,6 +108,18 @@
assert.equal(cursorElement.diffRow, firstDeltaRow);
});
+ test('moveToLastChunk', () => {
+ const chunks = Array.from(Polymer.dom(diffElement.root).querySelectorAll(
+ '.section.delta'));
+ assert.isAbove(chunks.length, 1);
+ assert.equal(chunks.indexOf(cursorElement.diffRow.parentElement), 0);
+
+ cursorElement.moveToLastChunk();
+
+ assert.equal(chunks.indexOf(cursorElement.diffRow.parentElement),
+ chunks.length - 1);
+ });
+
test('cursor scroll behavior', () => {
cursorElement._handleDiffRenderStart();
assert.equal(cursorElement._scrollBehavior, 'keep-visible');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
index cb23308..a15ca37 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
@@ -590,7 +590,7 @@
this.$.reporting.reportInteraction(EVENT_ZERO_REBASE);
} else {
this.$.reporting.reportInteraction(EVENT_NONZERO_REBASE,
- percentRebaseDelta);
+ {percentRebaseDelta});
}
}
@@ -1023,13 +1023,16 @@
_handleNormalizeRange(event) {
this.$.reporting.reportInteraction('normalize-range',
- `Modified invalid comment range on l. ${event.detail.lineNum}` +
- ` of the ${event.detail.side} side`);
+ {
+ side: event.detail.side,
+ lineNum: event.detail.lineNum,
+ });
}
_handleDiffContextExpanded(event) {
this.$.reporting.reportInteraction(
- 'diff-context-expanded', event.detail.numLines);
+ 'diff-context-expanded', {numLines: event.detail.numLines}
+ );
}
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
index 6d05876..d28f2a3 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
@@ -1069,8 +1069,10 @@
};
element._reportDiff(diff);
assert.isTrue(reportStub.calledOnce);
- assert.equal(reportStub.lastCall.args[0], 'rebase-percent-nonzero');
- assert.strictEqual(reportStub.lastCall.args[1], 50);
+ assert.isTrue(reportStub.calledWith(
+ 'rebase-percent-nonzero',
+ {percentRebaseDelta: 50}
+ ));
});
test('diff w/ all rebase delta', () => {
@@ -1081,8 +1083,10 @@
}]};
element._reportDiff(diff);
assert.isTrue(reportStub.calledOnce);
- assert.equal(reportStub.lastCall.args[0], 'rebase-percent-nonzero');
- assert.strictEqual(reportStub.lastCall.args[1], 100);
+ assert.isTrue(reportStub.calledWith(
+ 'rebase-percent-nonzero',
+ {percentRebaseDelta: 100}
+ ));
});
test('diff against parent event', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
index 7ed7962..4bf1f1b 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
@@ -33,10 +33,9 @@
content: var(--account-label-suffix);
}
gr-avatar {
- height: 1.3em;
- width: 1.3em;
- margin-right: var(--spacing-xs);
- vertical-align: -.25em;
+ height: var(--line-height-normal);
+ width: var(--line-height-normal);
+ vertical-align: top;
}
.text {
@apply --gr-account-label-text-style;
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.html b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.html
index 6103d6f..37591d8 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.html
@@ -32,6 +32,7 @@
display: flex;
flex: 1;
min-width: 10em;
+ margin: var(--spacing-xs) var(--spacing-xs) var(--spacing-xs) 0;
}
.group {
--account-label-suffix: ' (group)';
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
index 681717f..a078be7 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
@@ -102,7 +102,7 @@
}
}
this.$.reporting.reportInteraction('button-click',
- path.trim().toLowerCase());
+ {path: path.trim().toLowerCase()});
}
_disabledChanged(disabled) {
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
index 374204b..fd70fd9 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
@@ -163,6 +163,12 @@
}
}
+ moveToEnd() {
+ if (this.stops.length) {
+ this.setCursor(this.stops[this.stops.length - 1]);
+ }
+ }
+
setCursorAtIndex(index, opt_noScroll) {
this.setCursor(this.stops[index], opt_noScroll);
}
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
index ee9e7a0..f7349eb 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
@@ -59,16 +59,19 @@
cursor: pointer;
flex-direction: column;
font-size: inherit;
+ // This variable was introduced in Dec 2019. We keep both min-height
+ // rules around, because --paper-item-min-height is not yet upstreamed.
+ --paper-item-min-height: 0;
--paper-item: {
min-height: 0;
padding: 10px 16px;
- }
+ };
--paper-item-focused-before: {
background-color: var(--selection-background-color);
- }
+ };
--paper-item-focused: {
background-color: var(--selection-background-color);
- }
+ };
}
paper-item:hover {
background-color: var(--hover-background-color);
diff --git a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
index 5ecae00..75ab77a 100644
--- a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
+++ b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
@@ -54,6 +54,8 @@
<g id="info"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"></path></g>
<!-- This SVG is a copy from material.io https://material.io/icons/#ic_hourglass_full-->
<g id="hourglass"><path d="M6 2v6h.01L6 8.01 10 12l-4 4 .01.01H6V22h12v-5.99h-.01L18 16l-4-4 4-3.99-.01-.01H18V2H6z"/><path d="M0 0h24v24H0V0z" fill="none"/></g>
+ <!-- This SVG is a copy from material.io https://material.io/icons/#mode_comment-->
+ <g id="comment"><path d="M21.99 4c0-1.1-.89-2-1.99-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4-.01-18z"/><path d="M0 0h24v24H0z" fill="none"/></g>
<!-- This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js -->
<g id="error"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"></path></g>
<!-- This is a custom PolyGerrit SVG -->
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth.js
index 2978193..7648b7d 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth.js
@@ -264,7 +264,7 @@
ERROR: 3,
};
- Auth.CREDS_EXPIRED_MSG = 'Credentails expired.';
+ Auth.CREDS_EXPIRED_MSG = 'Credentials expired.';
// TODO(taoalpha): this whole thing should be moved to a service
window.Auth = Auth;
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 472a7ea..4aefe46 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -1001,7 +1001,6 @@
this.ListChangesOption.MESSAGES,
this.ListChangesOption.SUBMITTABLE,
this.ListChangesOption.WEB_LINKS,
- this.ListChangesOption.SKIP_MERGEABLE,
this.ListChangesOption.SKIP_DIFFSTAT,
];
return this.getConfig(false).then(config => {
@@ -1024,7 +1023,6 @@
const optionsHex = this.listChangesOptionsToHex(
this.ListChangesOption.ALL_COMMITS,
this.ListChangesOption.ALL_REVISIONS,
- this.ListChangesOption.SKIP_MERGEABLE,
this.ListChangesOption.SKIP_DIFFSTAT
);
return this._getChangeDetail(changeNum, optionsHex, opt_errFn,
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
index 1c62ca3..d599563 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
@@ -147,15 +147,16 @@
*/
GrReviewerUpdatesParser.prototype._getUpdateMessage = function(prev, state) {
if (prev === 'REMOVED' || !prev) {
- return 'added to ' + state + ': ';
+ return 'Added to ' + state.toLowerCase() + ': ';
} else if (state === 'REMOVED') {
if (prev) {
- return 'removed from ' + prev + ': ';
+ return 'Removed from ' + prev.toLowerCase() + ': ';
} else {
- return 'removed : ';
+ return 'Removed : ';
}
} else {
- return 'moved from ' + prev + ' to ' + state + ': ';
+ return 'Moved from ' + prev.toLowerCase() + ' to ' + state.toLowerCase() +
+ ': ';
}
};
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
index 818bfa9..593fcec 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
@@ -239,15 +239,15 @@
assert.equal(change.reviewer_updates[1].updates.length, 3);
let items = change.reviewer_updates[0].updates;
- assert.equal(items[0].message, 'added to CC: ');
+ assert.equal(items[0].message, 'Added to cc: ');
assert.deepEqual(items[0].reviewers, [reviewer1, reviewer2]);
items = change.reviewer_updates[1].updates;
- assert.equal(items[0].message, 'moved from CC to REVIEWER: ');
+ assert.equal(items[0].message, 'Moved from cc to reviewer: ');
assert.deepEqual(items[0].reviewers, [reviewer1]);
- assert.equal(items[1].message, 'removed from REVIEWER: ');
+ assert.equal(items[1].message, 'Removed from reviewer: ');
assert.deepEqual(items[1].reviewers, [reviewer1]);
- assert.equal(items[2].message, 'added to REVIEWER: ');
+ assert.equal(items[2].message, 'Added to reviewer: ');
assert.deepEqual(items[2].reviewers, [reviewer1, reviewer2]);
});
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
index a604bcf..07b664b2 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
@@ -201,7 +201,7 @@
this.text = this._getText(text);
this.$.textarea.selectionStart = colonIndex + 1;
this.$.textarea.selectionEnd = colonIndex + 1;
- this.$.reporting.reportInteraction('select-emoji');
+ this.$.reporting.reportInteraction('select-emoji', {type: text});
this._resetEmojiDropdown();
}
diff --git a/polygerrit-ui/app/styles/themes/app-theme.html b/polygerrit-ui/app/styles/themes/app-theme.html
index 8ad2576..db49822 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.html
+++ b/polygerrit-ui/app/styles/themes/app-theme.html
@@ -68,7 +68,7 @@
/* comment background colors */
--comment-background-color: #e8eaed;
--robot-comment-background-color: #e8f0fe;
- --unresolved-comment-background-color: #fef7f0;
+ --unresolved-comment-background-color: #fef7e0;
/* vote background colors */
--vote-color-approved: #9fcc6b;
--vote-color-disliked: #f7c4cb;
@@ -77,7 +77,7 @@
--vote-color-rejected: #f7a1ad;
/* misc colors */
- --border-color: #dadce0;
+ --border-color: #e8e8e8;
/* fonts */
--font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
diff --git a/resources/com/google/gerrit/server/mail/InboundEmailRejection.soy b/resources/com/google/gerrit/server/mail/InboundEmailRejection.soy
index e88c424..16c5c8d 100644
--- a/resources/com/google/gerrit/server/mail/InboundEmailRejection.soy
+++ b/resources/com/google/gerrit/server/mail/InboundEmailRejection.soy
@@ -64,6 +64,7 @@
{/template}
{template .InboundEmailRejection_COMMENT_REJECTED kind="text"}
- Gerrit Code Review rejected one or more comments because they did not pass validation.
+ Gerrit Code Review rejected one or more comments because they did not pass validation, or
+ because the maximum number of comments per change would be exceeded.
{call .InboundEmailRejectionFooter /}
{/template}
diff --git a/resources/com/google/gerrit/server/mail/InboundEmailRejectionHtml.soy b/resources/com/google/gerrit/server/mail/InboundEmailRejectionHtml.soy
index e17508d..8762e10 100644
--- a/resources/com/google/gerrit/server/mail/InboundEmailRejectionHtml.soy
+++ b/resources/com/google/gerrit/server/mail/InboundEmailRejectionHtml.soy
@@ -81,7 +81,8 @@
{template .InboundEmailRejectionHtml_COMMENT_REJECTED}
<p>
- Gerrit Code Review rejected one or more comments because they did not pass validation.
+ Gerrit Code Review rejected one or more comments because they did not pass validation, or
+ because the maximum number of comments per change would be exceeded.
</p>
{call .InboundEmailRejectionFooterHtml /}
{/template}
diff --git a/tools/BUILD b/tools/BUILD
index 32599aa..2d702fe 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -86,6 +86,7 @@
"-Xep:TypeParameterShadowing:ERROR",
"-Xep:TypeParameterUnusedInFormals:ERROR",
"-Xep:URLEqualsHashCode:ERROR",
+ "-Xep:UnusedException:ERROR",
"-Xep:UnsynchronizedOverridesSynchronized:ERROR",
"-Xep:WaitNotInLoop:ERROR",
"-Xep:WildcardImport:ERROR",
diff --git a/tools/bzl/bazelisk_version.bzl b/tools/bzl/bazelisk_version.bzl
deleted file mode 100644
index d8b3d10..0000000
--- a/tools/bzl/bazelisk_version.bzl
+++ /dev/null
@@ -1,16 +0,0 @@
-_template = """
-load("@bazel_skylib//lib:versions.bzl", "versions")
-
-def check_bazel_version():
- versions.check(minimum_bazel_version = "{version}")
-""".strip()
-
-def _impl(repository_ctx):
- repository_ctx.symlink(Label("@//:.bazelversion"), ".bazelversion")
- bazelversion = repository_ctx.read(".bazelversion").strip()
-
- repository_ctx.file("BUILD", executable = False)
-
- repository_ctx.file("check.bzl", executable = False, content = _template.format(version = bazelversion))
-
-bazelisk_version = repository_rule(implementation = _impl)
diff --git a/tools/bzl/java.bzl b/tools/bzl/java.bzl
deleted file mode 100644
index 8996b69..0000000
--- a/tools/bzl/java.bzl
+++ /dev/null
@@ -1,28 +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.
-
-# Syntactic sugar for native java_library() rule:
-# accept exported_deps attributes
-
-load("@rules_java//java:defs.bzl", "java_library")
-
-def java_library2(deps = [], exported_deps = [], exports = [], **kwargs):
- if exported_deps:
- deps = deps + exported_deps
- exports = exports + exported_deps
- java_library(
- deps = deps,
- exports = exports,
- **kwargs
- )
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
index ed64d1b..64a6e22 100644
--- a/tools/bzl/plugin.bzl
+++ b/tools/bzl/plugin.bzl
@@ -21,6 +21,7 @@
manifest_entries = [],
dir_name = None,
target_suffix = "",
+ deploy_env = [],
**kwargs):
java_library(
name = name + "__plugin",
@@ -43,6 +44,7 @@
runtime_deps = [
":%s__plugin" % name,
] + static_jars,
+ deploy_env = deploy_env,
visibility = ["//visibility:public"],
**kwargs
)
diff --git a/tools/coverage.sh b/tools/coverage.sh
index 11e50e6..c92d5cf 100755
--- a/tools/coverage.sh
+++ b/tools/coverage.sh
@@ -33,7 +33,7 @@
cp -r {java,javatests}/* ${destdir}/java
mkdir -p ${destdir}/plugins
-for plugin in `find plugins/ -type d` -maxdepth 1
+for plugin in `find plugins/ -maxdepth 1 -type d`
do
mkdir -p ${destdir}/${plugin}/java
cp -r plugins/*/{java,javatests}/* ${destdir}/${plugin}/java
diff --git a/tools/nongoogle.bzl b/tools/nongoogle.bzl
index acf01fa..1210504 100644
--- a/tools/nongoogle.bzl
+++ b/tools/nongoogle.bzl
@@ -126,18 +126,18 @@
sha1 = "dc13ae4faca6df981fc7aeb5a522d9db446d5d50",
)
- TESTCONTAINERS_VERSION = "1.12.4"
+ TESTCONTAINERS_VERSION = "1.12.5"
maven_jar(
name = "testcontainers",
artifact = "org.testcontainers:testcontainers:" + TESTCONTAINERS_VERSION,
- sha1 = "456b6facac12c4b67130d9056a43c011679e9f0c",
+ sha1 = "ca8a8dfbd3b194fb6c541e3bad858898865ce069",
)
maven_jar(
name = "testcontainers-elasticsearch",
artifact = "org.testcontainers:elasticsearch:" + TESTCONTAINERS_VERSION,
- sha1 = "9e210c277a35a95a76d03a79e2812575bd07391c",
+ sha1 = "d88f79c83e7057edd23d33621c36d6267b9c4e96",
)
maven_jar(
diff --git a/tools/remote-bazelrc b/tools/remote-bazelrc
index b26a8e8..826be1d 100644
--- a/tools/remote-bazelrc
+++ b/tools/remote-bazelrc
@@ -70,9 +70,6 @@
# Enable remote execution so actions are performed on the remote systems.
build:remote --remote_executor=remotebuildexecution.googleapis.com
-# Enable encryption.
-build:remote --tls_enabled=true
-
# Set a higher timeout value, just in case.
build:remote --remote_timeout=3600