Merge "Add file status tooltip and add declare modified status"
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 2362401..496c205 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -18,12 +18,14 @@
For more details on how to determine the correct SSH port number,
see link:user-upload.html#test_ssh[Testing Your SSH Connection].
-=== [[client_commands]]Commands
+[[client_commands]]
+=== Commands
link:cmd-cherry-pick.html[gerrit-cherry-pick]::
Download and cherry-pick one or more changes (commits).
-=== [[client_hooks]]Hooks
+[[client_hooks]]
+=== Hooks
Client hooks can be installed into a local Git repository, improving
the developer experience when working with a Gerrit Code Review
@@ -47,7 +49,8 @@
For more details on how to determine the correct SSH port number,
see link:user-upload.html#test_ssh[Testing Your SSH Connection].
-=== [[user_commands]]User Commands
+[[user_commands]]
+=== User Commands
link:cmd-apropos.html[gerrit apropos]::
Search Gerrit documentation index.
@@ -85,6 +88,9 @@
link:cmd-set-project.html[gerrit set-project]::
Change a project's settings.
+link:cmd-set-project-parent.html[gerrit set-project-parent]::
+ Change the project permissions are inherited from.
+
link:cmd-set-reviewers.html[gerrit set-reviewers]::
Add or remove reviewers on a change.
@@ -103,8 +109,8 @@
git upload-pack::
Standard Git server side command for client side `git fetch`.
-[[admin_commands]]Administrator Commands
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[admin_commands]]
+=== Administrator Commands
link:cmd-close-connection.html[gerrit close-connection]::
Close the specified SSH connection.
@@ -178,9 +184,6 @@
link:cmd-set-members.html[gerrit set-members]::
Set group members.
-link:cmd-set-project-parent.html[gerrit set-project-parent]::
- Change the project permissions are inherited from.
-
link:cmd-show-caches.html[gerrit show-caches]::
Display current cache statistics.
@@ -205,6 +208,36 @@
link:cmd-suexec.html[suexec]::
Execute a command as any registered user account.
+[[trace]]
+=== Trace
+
+For executing SSH commands tracing can be enabled by setting the
+`--trace` and `--trace-id <trace-id>` options. It is recommended to use
+the ID of the issue that is being investigated as trace ID.
+
+----
+ $ ssh -p 29418 review.example.com gerrit create-project --trace --trace-id issue/123 foo/bar
+----
+
+It is also possible to omit the trace ID and get a unique trace ID
+generated.
+
+----
+ $ ssh -p 29418 review.example.com gerrit create-project --trace foo/bar
+----
+
+Enabling tracing results in additional logs with debug information that
+are written to the `error_log`. All logs that correspond to the traced
+request are associated with the trace ID. The trace ID is printed to
+the stderr command output:
+
+----
+ TRACE_ID: 1534174322774-7edf2a7b
+----
+
+Given the trace ID an administrator can find the corresponding logs and
+investigate issues more easily.
+
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/cmd-set-project-parent.txt b/Documentation/cmd-set-project-parent.txt
index 6e2328c..ec5a5c6 100644
--- a/Documentation/cmd-set-project-parent.txt
+++ b/Documentation/cmd-set-project-parent.txt
@@ -20,7 +20,11 @@
the project to inherit through another one.
== ACCESS
-Caller must be a member of the privileged 'Administrators' group.
+Caller must be a member of the privileged 'Administrators' group
+or, if
+link:config-gerrit.html#receive.allowProjectOwnersToChangeParent[receive.allowProjectOwnersToChangeParent]
+is enabled, be a project owner of the projects that is getting their
+parent updated.
== SCRIPTING
This command is intended to be used in scripts.
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 4c71124..0108a04 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -786,6 +786,7 @@
+
* `"change_notes"`: disk storage is disabled by default
* `"diff_summary"`: default is `1g` (1 GiB of disk space)
+* `"external_ids_map"`: disk storage is disabled by default
+
If 0 or negative, disk storage for the cache is disabled.
@@ -862,8 +863,10 @@
cache may temporarily contain 2 entries, but the second one is promptly
expired.
+
-It is not recommended to change the attributes of this cache away from
-the defaults.
+It is not recommended to change the in-memory attributes of this cache
+away from the defaults. The cache may be persisted by setting
+`diskLimit`, which is only recommended if cold start performance is
+problematic.
cache `"git_tags"`::
+
@@ -3667,6 +3670,17 @@
+
Default is true.
+[[receive.allowProjectOwnersToChangeParent]]receive.allowProjectOwnersToChangeParent::
++
+If true, Gerrit will allow project owners to change the parent of a project.
++
+By default only Gerrit administrators are allowed to change the parent
+of a project. By allowing project owners to change parents, it may
+allow the owner to circumvent certain enforced rules (like important
+BLOCK rules).
++
+Default is false.
+
[[receive.checkReferencedObjectsAreReachable]]receive.checkReferencedObjectsAreReachable::
+
If set to true, Gerrit will validate that all referenced objects that
@@ -3734,6 +3748,14 @@
+
Common unit suffixes of 'k', 'm', or 'g' are supported.
+[[receive.inheritProjectMaxObjectSizeLimit]]receive.inheritProjectMaxObjectSizeLimit::
++
+Controls whether the project-level link:config-project-config.html[`receive.maxObjectSizeLimit`]
+value is inherited from the parent project. When `true`, the value is
+inherited, otherwise it is not inherited.
++
+Default is false, the value is not inherited.
+
[[receive.maxTrustDepth]]receive.maxTrustDepth::
+
If signed push validation is link:#receive.enableSignedPush[enabled],
@@ -4626,7 +4648,7 @@
[[theme.backgroundColor]]theme.backgroundColor::
+
-Background color for the page, and major data tables like the all
+_(GWT UI only)_ Background color for the page, and major data tables like the all
open changes table or the account dashboard. The value must be a
valid HTML hex color code, or standard color name.
+
@@ -4634,7 +4656,7 @@
[[theme.topMenuColor]]theme.topMenuColor::
+
-This is the color of the main menu bar at the top of the page.
+_(GWT UI only)_ This is the color of the main menu bar at the top of the page.
The value must be a valid HTML hex color code, or standard color
name.
+
@@ -4642,53 +4664,52 @@
[[theme.textColor]]theme.textColor::
+
-Text color for the page, and major data tables like the all
-open changes table or the account dashboard. The value must be a
-valid HTML hex color code, or standard color name.
+_(GWT UI only)_ Text color for the page, and major data tables like the all open
+changes table or the account dashboard. The value must be a valid HTML hex color
+code, or standard color name.
+
By default dark grey, `353535`.
[[theme.trimColor]]theme.trimColor::
+
-Primary color used as a background color behind text. This is
-the color of the main menu bar at the top, of table headers,
-and of major UI areas that we want to offset from other portions
-of the page. The value must be a valid HTML hex color code, or
-standard color name.
+_(GWT UI only)_ Primary color used as a background color behind text. This is
+the color of the main menu bar at the top, of table headers, and of major UI
+areas that we want to offset from other portions of the page. The value must be
+a valid HTML hex color code, or standard color name.
+
By default a light grey, `EEEEEE`.
[[theme.selectionColor]]theme.selectionColor::
+
-Background color used within a trimColor area to denote the currently
-selected tab, or the background color used in a table to denote the
-currently selected row. The value must be a valid HTML hex color
-code, or standard color name.
+_(GWT UI only)_ Background color used within a trimColor area to denote the
+currently selected tab, or the background color used in a table to denote the
+currently selected row. The value must be a valid HTML hex color code, or
+standard color name.
+
By default a pale blue, `D8EDF9`.
[[theme.changeTableOutdatedColor]]theme.changeTableOutdatedColor::
+
-Background color used for patch outdated messages. The value must be
-a valid HTML hex color code, or standard color name.
+_(GWT UI only)_ Background color used for patch outdated messages. The value
+must be a valid HTML hex color code, or standard color name.
+
By default a shade of red, `F08080`.
[[theme.tableOddRowColor]]theme.tableOddRowColor::
+
-Background color for tables such as lists of open reviews for odd
-rows. This is so you can have a different color for odd and even
-rows of the table. The value must be a valid HTML hex color code,
-or standard color name.
+_(GWT UI only)_ Background color for tables such as lists of open reviews for
+odd rows. This is so you can have a different color for odd and even rows of
+the table. The value must be a valid HTML hex color code, or standard color
+name.
+
By default transparent.
[[theme.tableEvenRowColor]]theme.tableEvenRowColor::
+
-Background color for tables such as lists of open reviews for even
-rows. This is so you can have a different color for odd and even
-rows of the table. The value must be a valid HTML hex color code,
-or standard color name.
+_(GWT UI only)_ Background color for tables such as lists of open reviews for
+even rows. This is so you can have a different color for odd and even rows of
+the table. The value must be a valid HTML hex color code, or standard color
+name.
+
By default transparent.
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index 1aa6cd7c..ff43520 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -374,6 +374,15 @@
link:access-control.html#reference[here], but must not contain `${username}` or
`${shardeduserid}`.
+[[label_ignoreSelfApproval]]
+=== `label.Label-Name.ignoreSelfApproval`
+
+If true, the label may be voted on by the uploader of the latest patch set,
+but their approval does not make a change submittable. Instead, a
+non-uploader who has the right to vote has to approve the change.
+
+Defaults to false.
+
[[label_example]]
=== Example
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt
index ac303e9..cc5386f 100644
--- a/Documentation/config-project-config.txt
+++ b/Documentation/config-project-config.txt
@@ -144,9 +144,14 @@
`gerrit.config` globally (link:config-gerrit.html#receive.maxObjectSizeLimit[
receive.maxObjectSizeLimit]).
+
-The project specific setting in `project.config` is only honored when it
-further reduces the global limit. The setting is not inherited from the
-parent project; it must be explicitly set per project.
+The project specific setting in `project.config` may not set a value higher
+than the global limit (if configured). In other words, it is only honored when
+it further reduces the global limit.
++
+When link:config-gerrit.html#receive.inheritProjectMaxObjectSizeLimit[
+`receive.inheritProjectmaxObjectSizeLimit`] is enabled in the global config,
+the value is inherited from the parent project. Otherwise, it is not inherited
+and must be explicitly set per project.
+
Default is zero.
+
@@ -256,7 +261,8 @@
- 'rejectEmptyCommit': Defines whether empty commits should be rejected when a change is merged.
Changes might not seem empty at first but when attempting to merge, rebasing can lead to an empty
-commit. If this option is set to 'true' the merge would fail.
+commit. If this option is set to 'true' the merge would fail. An empty commit is still allowed as
+the initial commit on a branch.
Merge strategy
diff --git a/Documentation/database-setup.txt b/Documentation/database-setup.txt
index d35772e..2153751 100644
--- a/Documentation/database-setup.txt
+++ b/Documentation/database-setup.txt
@@ -16,7 +16,8 @@
with the database while Gerrit is offline, it's not easy to backup the data,
and it's not possible to set up H2 in a load balanced/hotswap configuration.
-If this option interests you, you might want to consider link:install-quick.html[the quick guide].
+If this option interests you, you might want to consider
+link:linux-quickstart.html[the quick guide].
[[createdb_derby]]
=== Apache Derby
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 979d021..6331581 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -148,6 +148,11 @@
Note that when building an individual plugin, the `core.zip` package
is not regenerated.
+To build with all Error Prone warnings activated, run:
+
+----
+ bazel build --java_toolchain //tools:error_prone_warnings_toolchain //...
+----
[[IDEs]]
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
index 8337c0b..bc9f782 100644
--- a/Documentation/dev-contributing.txt
+++ b/Documentation/dev-contributing.txt
@@ -164,7 +164,7 @@
To format Java source code, Gerrit uses the
link:https://github.com/google/google-java-format[`google-java-format`]
-tool (version 1.5), and to format Bazel BUILD, WORKSPACE and .bzl files the
+tool (version 1.6), and to format Bazel BUILD, WORKSPACE and .bzl files the
link:https://github.com/bazelbuild/buildtools/tree/master/buildifier[`buildifier`]
tool (version 0.15.0).
These tools automatically apply format according to the style guides; this
@@ -368,6 +368,36 @@
that they are vetted long enough before they go into a release and we can be sure
that the update doesn't introduce a regression.
+[[deprecating-features]]
+=== Deprecating features
+
+Gerrit should be as stable as possible and we aim to add only features that last.
+However, sometimes we are required to deprecate and remove features to be able
+to move forward with the project and keep the code-base clean. The following process
+should serve as a guideline on how to deprecate functionality in Gerrit. Its purpose
+is that we have a structured process for deprecation that users, administrators and
+developers can agree and rely on.
+
+General process:
+* Make sure that the feature (e.g. a field on the API) is not needed anymore or blocks
+ further development or improvement. If in doubt, consult the mailing list.
+* If you can provide a schema migration that moves users to a comparable feature, do
+ so and stop here.
+* Mark the feature as deprecated in the documentation and release notes.
+* If possible, mark the feature deprecated in any user-visible interface. For example,
+ if you are deprecating a Git push option, add a message to the Git response if
+ the user provided the option informing them about deprecation.
+* Annotate the code with `@Deprecated` and `@RemoveAfter(x.xx)` if applicable.
+ Alternatively, use `// DEPRECATED, remove after x.xx` (where x.xx is the version
+ number that has to be branched off before removing the feature)
+* Gate the feature behind a config that is off by default (forcing admins to turn
+ the deprecated feature on explicitly).
+* After the next release was branched off, remove any code that backed the feature.
+
+You can optionally consult the mailing list to ask if there are users of the feature you
+wish to deprecate. If there are no major users, you can remove the feature without
+following this process and without the grace period of one release.
+
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index 6e39502..0f23db5 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -3,7 +3,7 @@
This document is about configuring Gerrit Code Review into an
Eclipse workspace for development and debugging with GWT.
-Java 6 or later SDK is also required to run GWT's compiler and
+Java 8 or later SDK is also required to run GWT's compiler and
runtime debugging environment.
@@ -49,6 +49,19 @@
link:dev-build-plugins.html#_bundle_custom_plugin_in_release_war[bundling in release.war]
and run `tools/eclipse/project.py`.
+[[Newer Java versions]]
+
+Java 9 and later are supported, but some adjustments must be done, because
+Java 8 is still the default:
+
+* Add JRE, e.g.: directory: /usr/lib64/jvm/java-9-openjdk, name: java-9-openjdk-9
+* Change execution environemnt for gerrit project to: JavaSE-9 (java-9-openjdk-9)
+* Check that compiler compliance level in gerrit project is set to: 9
+* Add this parameter to VM argument for gerrit_daemin launcher:
+----
+ --add-modules java.activation \
+ --add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED
+----
[[Formatting]]
== Code Formatter Settings
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 5c008c7..6517262 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -2534,7 +2534,7 @@
attribute.
Documentation may be written in the Markdown flavor
-link:https://github.com/sirthias/pegdown[pegdown]
+link:https://github.com/vsch/flexmark-java[flexmark-java]
if the file name ends with `.md`. Gerrit will automatically convert
Markdown to HTML if accessed with extension `.html`.
diff --git a/Documentation/dev-polygerrit.txt b/Documentation/dev-polygerrit.txt
index 79049fc..5621d32 100644
--- a/Documentation/dev-polygerrit.txt
+++ b/Documentation/dev-polygerrit.txt
@@ -1,12 +1,15 @@
= PolyGerrit - GUI
-[IMPORTANT]
-PolyGerrit is still a beta feature. Some features may be missing.
-
== Configuring
By default both GWT and PolyGerrit UI are available to users.
+To make PolyGerrit the default UI but keep GWT as a secondary UI:
+----
+[gerrit]
+ ui = POLYGERRIT
+----
+
To disable GWT but not PolyGerrit:
----
[gerrit]
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 2afac94..0abd8a1 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -365,6 +365,17 @@
must reference the new version. Upload a change to bazlets repository with
api version upgrade.
+[[clean-up-on-master]]
+=== Clean up on master
+
+Once you are done with the release, check if there are any code changes in the
+master branch that were gated on the next release. Mostly, these are
+feature-deprecations that we were holding off on to have a stable release where
+the feature is still contained, but marked as deprecated.
+
+See link:dev-contributing.html#deprecating-features[Deprecating features] for
+details.
+
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/error-missing-changeid.txt b/Documentation/error-missing-changeid.txt
index 9cddd85..08f2c09 100644
--- a/Documentation/error-missing-changeid.txt
+++ b/Documentation/error-missing-changeid.txt
@@ -1,4 +1,4 @@
-= missing Change-Id in commit message footer
+= commit xxxxxxx: missing Change-Id in message footer
With this error message Gerrit rejects to push a commit to a project
which is configured to always require a Change-Id in the commit
diff --git a/Documentation/error-missing-subject.txt b/Documentation/error-missing-subject.txt
index 3703ade..6ef37a4 100644
--- a/Documentation/error-missing-subject.txt
+++ b/Documentation/error-missing-subject.txt
@@ -1,4 +1,4 @@
-= missing subject; Change-Id must be in commit message footer
+= commit xxxxxxx: missing subject; Change-Id must be in message footer
With this error message Gerrit rejects to push a commit to a project
which is configured to always require a Change-Id in the commit
diff --git a/Documentation/error-multiple-changeid-lines.txt b/Documentation/error-multiple-changeid-lines.txt
index 0729547..31567f4 100644
--- a/Documentation/error-multiple-changeid-lines.txt
+++ b/Documentation/error-multiple-changeid-lines.txt
@@ -1,4 +1,4 @@
-= multiple Change-Id lines in commit message footer
+= commit xxxxxxx: multiple Change-Id lines in message footer
With this error message Gerrit rejects to push a commit if the commit
message footer of the pushed commit contains several Change-Id lines.
diff --git a/Documentation/images/inline-edit-add-file-page.png b/Documentation/images/inline-edit-add-file-page.png
new file mode 100644
index 0000000..1a761b4
--- /dev/null
+++ b/Documentation/images/inline-edit-add-file-page.png
Binary files differ
diff --git a/Documentation/images/inline-edit-create-change-form.png b/Documentation/images/inline-edit-create-change-form.png
new file mode 100644
index 0000000..7a93460
--- /dev/null
+++ b/Documentation/images/inline-edit-create-change-form.png
Binary files differ
diff --git a/Documentation/images/inline-edit-create-change.png b/Documentation/images/inline-edit-create-change.png
new file mode 100644
index 0000000..1df0421
--- /dev/null
+++ b/Documentation/images/inline-edit-create-change.png
Binary files differ
diff --git a/Documentation/images/inline-edit-delete-file.png b/Documentation/images/inline-edit-delete-file.png
new file mode 100644
index 0000000..1634e0f
--- /dev/null
+++ b/Documentation/images/inline-edit-delete-file.png
Binary files differ
diff --git a/Documentation/images/inline-edit-diff-screen.png b/Documentation/images/inline-edit-diff-screen.png
new file mode 100644
index 0000000..228484a
--- /dev/null
+++ b/Documentation/images/inline-edit-diff-screen.png
Binary files differ
diff --git a/Documentation/images/inline-edit-home-page.png b/Documentation/images/inline-edit-home-page.png
new file mode 100644
index 0000000..a1b8eb4
--- /dev/null
+++ b/Documentation/images/inline-edit-home-page.png
Binary files differ
diff --git a/Documentation/images/inline-edit-new-change-page.png b/Documentation/images/inline-edit-new-change-page.png
new file mode 100644
index 0000000..8a33dd6
--- /dev/null
+++ b/Documentation/images/inline-edit-new-change-page.png
Binary files differ
diff --git a/Documentation/images/inline-edit-open-file.png b/Documentation/images/inline-edit-open-file.png
new file mode 100644
index 0000000..a5422f5
--- /dev/null
+++ b/Documentation/images/inline-edit-open-file.png
Binary files differ
diff --git a/Documentation/images/inline-edit-prefill-files.png b/Documentation/images/inline-edit-prefill-files.png
new file mode 100644
index 0000000..0b2b766
--- /dev/null
+++ b/Documentation/images/inline-edit-prefill-files.png
Binary files differ
diff --git a/Documentation/images/inline-edit-review-message.png b/Documentation/images/inline-edit-review-message.png
new file mode 100644
index 0000000..bd76fad
--- /dev/null
+++ b/Documentation/images/inline-edit-review-message.png
Binary files differ
diff --git a/Documentation/images/inline-edit-start-review-button.png b/Documentation/images/inline-edit-start-review-button.png
new file mode 100644
index 0000000..df6350b
--- /dev/null
+++ b/Documentation/images/inline-edit-start-review-button.png
Binary files differ
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 84925f4..6011158 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -67,6 +67,7 @@
. link:config-reverseproxy.html[Reverse Proxy]
. link:config-auto-site-initialization.html[Automatic Site Initialization on Startup]
. link:pgm-index.html[Server Side Administrative Tools]
+. link:user-request-tracing.html[Request Tracing]
. link:note-db.html[NoteDb]
. link:config-accounts.html[Accounts on NoteDb]
. link:config-groups.html[Groups on NoteDb]
diff --git a/Documentation/install-quick.txt b/Documentation/install-quick.txt
deleted file mode 100644
index 2503449..0000000
--- a/Documentation/install-quick.txt
+++ /dev/null
@@ -1,234 +0,0 @@
-= Gerrit Code Review - Quick get started guide
-
-****
-This guide was made with the impatient in mind, ready to try out Gerrit on their
-own server but not prepared to make the full installation procedure yet.
-
-Explanation is sparse and you should not use a server installed this way in a
-live setup, this is made with proof of concept activities in mind.
-
-It is presumed you install it on a Unix based server such as any of the Linux
-flavors or BSD.
-
-It's also presumed that you have access to an OpenID enabled email address.
-Examples of OpenID enable email providers are Gmail, Yahoo! Mail and Hotmail.
-It's also possible to register a custom email address with OpenID, but that is
-outside the scope of this quick installation guide. For testing purposes one of
-the above providers should be fine. Please note that network access to the
-OpenID provider you choose is necessary for both you and your Gerrit instance.
-****
-
-
-[[requirements]]
-== Requirements
-
-Most distributions come with Java today. Do you already have Java installed?
-
-----
- $ java -version
- openjdk version "1.8.0_72"
- OpenJDK Runtime Environment (build 1.8.0_72-b15)
- OpenJDK 64-Bit Server VM (build 25.72-b15, mixed mode)
-----
-
-If Java isn't installed, get it:
-
-* JRE, minimum version 1.8 http://www.oracle.com/technetwork/java/javase/downloads/index.html[Download]
-
-
-[[user]]
-== Create a user to host the Gerrit service
-
-We will run the service as a non-privileged user on your system.
-First create the user and then become the user:
-
-----
- $ sudo adduser gerrit
- $ sudo su gerrit
-----
-
-If you don't have root privileges you could skip this step and run Gerrit
-as your own user as well.
-
-
-[[download]]
-== Download Gerrit
-
-It's time to download the archive that contains the Gerrit web and ssh service.
-
-You can choose from different versions to download from here:
-
-* https://www.gerritcodereview.com/download/index.html[A list of releases available]
-
-This tutorial is based on version 2.2.2, and you can download that from this link
-
-* https://www.gerritcodereview.com/download/gerrit-2.2.2.war[Link to the 2.2.2 war archive]
-
-
-[[initialization]]
-== Initialize the Site
-
-It's time to run the initialization, and with the batch switch enabled, we don't have to answer any questions at all:
-
-----
- gerrit@host:~$ java -jar gerrit.war init --batch -d ~/gerrit_testsite
- Generating SSH host key ... rsa(simple)... done
- Initialized /home/gerrit/gerrit_testsite
- Executing /home/gerrit/gerrit_testsite/bin/gerrit.sh start
- Starting Gerrit Code Review: OK
- gerrit@host:~$
-----
-
-When the init is complete, you can review your settings in the
-file `'$site_path/etc/gerrit.config'`.
-
-Note that initialization also starts the server. If any settings changes are
-made, the server must be restarted before they will take effect.
-
-----
- gerrit@host:~$ ~/gerrit_testsite/bin/gerrit.sh restart
- Stopping Gerrit Code Review: OK
- Starting Gerrit Code Review: OK
- gerrit@host:~$
-----
-
-The server can be also stopped and started by passing the `stop` and `start`
-commands to gerrit.sh.
-
-----
- gerrit@host:~$ ~/gerrit_testsite/bin/gerrit.sh stop
- Stopping Gerrit Code Review: OK
- gerrit@host:~$
- gerrit@host:~$ ~/gerrit_testsite/bin/gerrit.sh start
- Starting Gerrit Code Review: OK
- gerrit@host:~$
-----
-
-include::config-login-register.txt[]
-
-== Project creation
-
-Your base Gerrit server is now running and you have a user that's ready
-to interact with it. You now have two options, either you create a new
-test project to work with or you already have a git with history that
-you would like to import into Gerrit and try out code review on.
-
-=== New project from scratch
-If you choose to create a new repository from scratch, it's easier for
-you to create a project with an initial commit in it. That way first
-time setup between client and server is easier.
-
-This is done via the SSH port:
-
-----
- user@host:~$ ssh -p 29418 user@localhost gerrit create-project demo-project --empty-commit
- user@host:~$
-----
-
-This will create a repository that you can clone to work with.
-
-=== Already existing project
-
-The other alternative is if you already have a git project that you
-want to try out Gerrit on.
-First you have to create the project. This is done via the SSH port:
-
-----
- user@host:~$ ssh -p 29418 user@localhost gerrit create-project demo-project
- user@host:~$
-----
-
-You need to make sure that at least initially your account is granted
-"Create Reference" privileges for the refs/heads/* reference.
-This is done via the web interface in the Admin/Projects/Access page
-that correspond to your project.
-
-After that it's time to upload the previous history to the server:
-
-----
- user@host:~/my-project$ git push ssh://user@localhost:29418/demo-project *:*
- Counting objects: 2011, done.
- Writing objects: 100% (2011/2011), 456293 bytes, done.
- Total 2011 (delta 0), reused 0 (delta 0)
- To ssh://user@localhost:29418/demo-project
- * [new branch] master -> master
- user@host:~/my-project$
-----
-
-This will create a repository that you can clone to work with.
-
-
-== My first change
-
-Download a local clone of the repository and move into it
-
-----
- user@host:~$ git clone ssh://user@localhost:29418/demo-project
- Cloning into demo-project...
- remote: Counting objects: 2, done
- remote: Finding sources: 100% (2/2)
- remote: Total 2 (delta 0), reused 0 (delta 0)
- user@host:~$ cd demo-project
- user@host:~/demo-project$
-----
-
-Install the link:user-changeid.html[Change-Id commitmsg hook]
-
-----
- scp -p -P 29418 user@localhost:hooks/commit-msg $(git rev-parse --git-dir)/hooks/
-----
-
-Then make a change to the repository and upload it as a reviewable change
-in Gerrit.
-
-----
- user@host:~/demo-project$ date > testfile.txt
- user@host:~/demo-project$ git add testfile.txt
- user@host:~/demo-project$ git commit -m "My pretty test commit"
- [master ff643a5] My pretty test commit
- 1 files changed, 1 insertions(+), 0 deletions(-)
- create mode 100644 testfile.txt
- user@host:~/demo-project$
-----
-
-Usually when you push to a remote git, you push to the reference
-`'/refs/heads/branch'`, but when working with Gerrit you have to push to a
-virtual branch representing "code review before submission to branch".
-This virtual name space is known as /refs/for/<branch>
-
-----
- user@host:~/demo-project$ git push origin HEAD:refs/for/master
- Counting objects: 4, done.
- Writing objects: 100% (3/3), 293 bytes, done.
- Total 3 (delta 0), reused 0 (delta 0)
- remote:
- remote: New Changes:
- remote: http://localhost:8080/1
- remote:
- To ssh://user@localhost:29418/demo-project
- * [new branch] HEAD -> refs/for/master
- user@host:~/demo-project$
-----
-
-You should now be able to access your change by browsing to the http URL
-suggested above, http://localhost:8080/1
-
-
-== Quick Installation Complete
-
-This covers the scope of getting Gerrit started and your first change uploaded.
-It doesn't give any clue as to how the review workflow works, please read
-link:http://source.android.com/source/life-of-a-patch[Default Workflow] to
-learn more about the workflow of Gerrit.
-
-To read more on the installation of Gerrit please see link:install.html[the detailed
-installation page].
-
-
-GERRIT
-------
-
-Part of link:index.html[Gerrit Code Review]
-
-SEARCHBOX
----------
diff --git a/Documentation/install.txt b/Documentation/install.txt
index cc19b3f..dbca368 100644
--- a/Documentation/install.txt
+++ b/Documentation/install.txt
@@ -5,7 +5,9 @@
To run the Gerrit service, the following requirement must be met on the host:
-* JRE, minimum version 1.8 http://www.oracle.com/technetwork/java/javase/downloads/index.html[Download]
+* JRE, version 1.8 http://www.oracle.com/technetwork/java/javase/downloads/index.html[Download]
++
+Gerrit is not yet compatible with Java 9 or newer at this time.
By default, Gerrit uses link:note-db.html[NoteDB] as the storage backend. (If
desired, you can _optionally_ use an external database such as MySQL or
diff --git a/Documentation/linux-quickstart.txt b/Documentation/linux-quickstart.txt
index 2464c3a..bfebc6a 100644
--- a/Documentation/linux-quickstart.txt
+++ b/Documentation/linux-quickstart.txt
@@ -18,7 +18,8 @@
. A Unix-based server, including any Linux flavor, MacOS, or Berkeley Software
Distribution (BSD).
-. Java SE Runtime Environment 1.8 (or higher).
+. Java SE Runtime Environment version 1.8. Gerrit is not compatible with Java
+ 9 or newer yet.
== Download Gerrit
diff --git a/Documentation/rest-api-access.txt b/Documentation/rest-api-access.txt
index 65a15ca..c2a7d21 100644
--- a/Documentation/rest-api-access.txt
+++ b/Documentation/rest-api-access.txt
@@ -263,6 +263,7 @@
],
"can_upload": true,
"can_add": true,
+ "can_add_tags": true,
"config_visible": true,
"groups": {
"53a4f647a89ea57992571187d8025f830625192a": {
@@ -313,6 +314,7 @@
],
"can_upload": true,
"can_add": true,
+ "can_add_tags": true,
"config_visible": true
}
}
@@ -399,6 +401,8 @@
Whether the calling user can upload to any ref.
|`can_add` |not set if `false`|
Whether the calling user can add any ref.
+|`can_add_tags` |not set if `false`|
+Whether the calling user can add any tag ref.
|`config_visible` |not set if `false`|
Whether the calling user can see the `refs/meta/config` branch of the
project.
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index edb642e..e28a9c4 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -418,7 +418,10 @@
.Response
----
HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+ )]}'
ok
----
@@ -1095,7 +1098,10 @@
.Response
----
HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+ )]}'
ok
----
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 4b8922a..3ec989e 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1927,7 +1927,7 @@
GerritInfo] entity.
|`note_db_enabled` |not set if `false`|
Whether the NoteDb storage backend is fully enabled.
-|`plugin ` ||
+|`plugin` ||
Information about Gerrit extensions by plugins as
link:#plugin-config-info[PluginConfigInfo] entity.
|`receive` |optional|
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 5fd8be4..b517d3c 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1141,6 +1141,7 @@
],
"can_upload": true,
"can_add": true,
+ "can_add_tags": true,
"config_visible": true,
"groups": {
"c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
@@ -1242,6 +1243,7 @@
],
"can_upload": true,
"can_add": true,
+ "can_add_tags": true,
"config_visible": true,
"groups": {
"global:Anonymous-Users": {
@@ -1410,6 +1412,95 @@
Content-Disposition: attachment
----
+[[check]]
+=== Check project consistency
+
+Performs consistency checks on the project.
+
+Which consistency checks should be performed is controlled by the
+link:#check-project-input[CheckProjectInput] entity in the request
+body.
+
+The following consistency checks are supported:
+
+[[auto-closeable-changes-check]]
+--
+* AutoCloseableChangesCheck: Searches for open changes that can be
+ auto-closed because a patch set of the change is already contained in
+ the destination branch or because the destination branch contains a
+ commit with the same Change-Id. Normally Gerrit auto-closes such
+ changes when the corresponding commits are pushed directly to the
+ repository. However if a branch is updated behind Gerrit's back or if
+ auto-closing changes fails (and the push is still successful) change
+ states can get inconsistent (changes that are already part of the
+ destination branch are still open). This consistency check is
+ intended to detect and repair this situation.
+--
+
+To fix any problems that can be fixed automatically set the `fix` field
+in the inputs for the consistency checks to `true`.
+
+This REST endpoint requires the
+link:access-control.html#capability_administrateServer[Administrate Server]
+global capability.
+
+.Request
+----
+ POST /projects/MyProject/check HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "auto_closeable_changes_check": {
+ "fix": true,
+ "branch": "refs/heads/master",
+ "max_commits": 100
+ }
+ }
+----
+
+As response a link:#check-project-result-info[CheckProjectResultInfo]
+entity is returned that results for the consistency checks.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "auto_closeable_changes_check_result": {
+ "auto_closeable_changes": {
+ "refs/heads/master": [
+ {
+ "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "project": "myProject",
+ "branch": "master",
+ "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "subject": "Implementing Feature X",
+ "status": "NEW",
+ "created": "2013-02-01 09:59:32.126000000",
+ "updated": "2013-02-21 11:16:36.775000000",
+ "insertions": 34,
+ "deletions": 101,
+ "_number": 3965,
+ "owner": {
+ "name": "John Doe"
+ },
+ "problems": [
+ {
+ "message": "Patch set 1 (2f15e416237ed9b561199f24184f5f5d2708c584) is merged into destination ref refs/heads/master (2f15e416237ed9b561199f24184f5f5d2708c584), but change status is NEW",
+ "status": "FIXED",
+ "outcome": "Marked change as merged"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+----
+
[[branch-endpoints]]
== Branch Endpoints
@@ -2850,6 +2941,52 @@
check. This defaults to `read`. If given, it `ref` must be given too.
|=========================================
+[[auto_closeable_changes_check_input]]
+=== AutoCloseableChangesCheckInput
+The `AutoCloseableChangesCheckInput` entity contains options for running
+the link:#auto-closeable-changes-check[AutoCloseableChangesCheck].
+
+[options="header",cols="1,^2,4"]
+|=============================
+|Field Name ||Description
+|`fix` |optional|
+Whether auto-closeable changes should be closed automatically.
+|`branch` ||
+The branch for which the link:#auto-closeable-changes-check[
+AutoCloseableChangesCheck] should be performed. The 'refs/heads/'
+prefix for the branch name can be omitted.
+|`skip_commits` |optional|
+Number of commits that should be skipped when walking the commits of
+the branch.
+|`max_commits` |optional|
+Maximum number of commits to walk. If not specified this defaults to
+10,000 commits. 10,000 is also the maximum that can be set.
+Auto-closing changes is an expensive operation and the more commits
+are walked the slower it gets. This is why you should avoid walking too
+many commits.
+|=============================
+
+[[auto_closeable_changes_check_result]]
+=== AutoCloseableChangesCheckResult
+The `AutoCloseableChangesCheckResult` entity contains the results of
+running the link:#auto-closeable-changes-check[AutoCloseableChangesCheck]
+on a project.
+
+[options="header",cols="1,6"]
+|====================================
+|Field Name |Description
+|`auto_closeable_changes`|
+Changes that can be auto-closed as list of
+link:rest-api-changes.html#change-info[ChangeInfo] entities. For each
+returned link:rest-api-changes.html#change-info[ChangeInfo] entity the
+`problems` field is populated that includes details about the detected
+issues. If `fix` in the link:#auto_closeable_changes_check_input[
+AutoCloseableChangesCheckInput] was set to `true`, `status` and
+`outcome` in link:rest-api-changes.html#problem-info[ProblemInfo] are
+populated. If the status says `FIXED` Gerrit was able to auto-close the
+change now.
+|====================================
+
[[ban-input]]
=== BanInput
The `BanInput` entity contains information for banning commits in a
@@ -2907,6 +3044,36 @@
If not set, `HEAD` will be used as base revision.
|=======================
+[[check-project-input]]
+=== CheckProjectInput
+The `CheckProjectInput` entity contains information about which
+consistency checks should be run on a project.
+
+[options="header",cols="1,^2,4"]
+|===========================================
+|Field Name ||Description
+|`auto_closeable_changes_check`|optional|
+Parameters for the link:#auto-closeable-changes-check[
+AutoCloseableChangesCheck] as
+link:rest-api-changes.html#auto_closeable_changes_check_input[
+AutoCloseableChangesCheckInput] entity.
+|===========================================
+
+[[check-project-result-info]]
+=== CheckProjectResultInfo
+The `CheckProjectResultInfo` entity contains results for consistency
+checks that have been run on a project.
+
+[options="header",cols="1,^2,4"]
+|==================================================
+|Field Name ||Description
+|`auto_closeable_changes_check_result`|optional|
+Results for the link:#auto-closeable-changes-check[
+AutoCloseableChangesCheck] as
+link:rest-api-changes.html#auto_closeable_changes_check_result[
+AutoCloseableChangesCheckResult] entity.
+|==================================================
+
[[config-info]]
=== ConfigInfo
The `ConfigInfo` entity contains information about the effective project
@@ -3270,16 +3437,17 @@
|===============================
|Field Name ||Description
|`value` |optional|
-The effective value of the max object size limit as a formatted string. +
+The effective value in bytes of the max object size limit. +
Not set if there is no limit for the object size.
|`configured_value`|optional|
The max object size limit that is configured on the project as a
formatted string. +
Not set if there is no limit for the object size configured on project
level.
-|`inherited_value` |optional|
-The max object size limit that is inherited as a formatted string. +
-Not set if there is no global limit for the object size.
+|`summary` |optional|
+A string describing whether the value was inherited or overridden from
+the parent project or global config. +
+Not set if not inherited or overridden.
|===============================
[[project-access-input]]
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index 0957d32..8f6a47b 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -191,6 +191,53 @@
"`422 Unprocessable Entity`" is returned if the ID of a resource that is
specified in the request body cannot be resolved.
+[[tracing]]
+=== Request Tracing
+For each REST endpoint tracing can be enabled by setting the
+`trace=<trace-id>` request parameter. It is recommended to use the ID
+of the issue that is being investigated as trace ID.
+
+.Example Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/suggest_reviewers?trace=issue/123&q=J
+----
+
+It is also possible to omit the trace ID and get a unique trace ID
+generated.
+
+.Example Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/suggest_reviewers?trace&q=J
+----
+
+Alternatively request tracing can also be enabled by setting the
+`X-Gerrit-Trace` header:
+
+.Example Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/suggest_reviewers?q=J
+ X-Gerrit-Trace: issue/123
+----
+
+Enabling tracing results in additional logs with debug information that
+are written to the `error_log`. All logs that correspond to the traced
+request are associated with the trace ID. The trace ID is returned with
+the REST response in the `X-Gerrit-Trace` header.
+
+.Example Response
+----
+HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+ X-Gerrit-Trace: 1533885943749-8257c498
+
+ )]}'
+ ... <json> ...
+----
+
+Given the trace ID an administrator can find the corresponding logs and
+investigate issues more easily.
+
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/user-inline-edit.txt b/Documentation/user-inline-edit.txt
index bce8183..ada2560 100644
--- a/Documentation/user-inline-edit.txt
+++ b/Documentation/user-inline-edit.txt
@@ -1,191 +1,236 @@
-= Inline Edit
+= Creating and Editing Changes in the Gerrit Web Interface
-This page explains the workflow for creating and amending changes in the
-browser.
+== Overview
+
+The following content explains how to use the Gerrit web interface to create
+and edit changes. Use the web interface to make minor changes to files. When
+you create a change in the Gerrit user interface, you don't clone a Gerrit
+repository or use the CLI to issue Git commands — you perform your work
+directly in the Gerrit web interface.
+
+To learn more, see the link:intro-user.html[Gerrit User's Guide].
[[create-change]]
-== Creating a New Change
+== Creating a Change
-A new change can be created directly in the browser, meaning it is not necessary
-to clone the whole repository to make trivial changes.
+To create a change in the Gerrit web interface:
-The new change is created as a public
-link:user-upload.html#wip[work-in-progress change].
+. From the link:http://gerrit-review.googlesource.com[Gerrit Code Review]
+ dashboard, select Browse > Repositories.
-There are two different ways to create a new change:
+. Under Repository Name, click the name of the repository you want to work
+ on. For example, Public-Projects. To find a specific repository, enter all
+ or part of its name next to Filter:
++
+image::images/inline-edit-home-page.png[width=600]
-By clicking on the 'Create Change' button in the project screen:
+. In the left navigation panel for the repository you selected, click
+ Commands:
++
+image::images/inline-edit-create-change.png[width=350]
-[[create-change-from-project-info-screen]]
+. Under Repository Commands, click Create Change.
-image::images/inline-edit-create-change-project-screen.png[width=800, link="images/inline-edit-create-change-project-screen.png"]
+. In the Create Change window, enter the following information:
-The user can select the branch on which the new change should be created:
+ * Select branch for new change: Specify the destination branch of the
+ change.
-image::images/inline-edit-create-change-project-screen-dialog.png[width=800, link="images/inline-edit-create-change-project-screen-dialog.png"]
+ * Provide base commit SHA1 for change: Leave this field blank.
-By clicking the 'Follow-Up' button on the change screen, to create a new change
-based on the selected change.
++
+IMPORTANT: Git uses a unique SHA1 value to identify each and every commit (in
+other words, each Git commit generates a new SHA1 hash). This value differs
+from a Gerrit Change-Id, which is used by Gerrit to uniquely identify a
+change. The Gerrit Change-Id remains static throughout the life of a Gerrit
+change.
-[[create-change-from-change-screen]]
+ - Description: Briefly describe the change. Be sure to use the
+ link:dev-contributing.html#commit-message[Commit Message] format.
+ The first line becomes the subject of the change and is included in
+ the Commit Message. Because the message also appears on its own in
+ dashboards and in the results of `git log --pretty=oneline output`,
+ make the message informative and brief.
-image::images/inline-edit-create-follow-up-change.png[width=800, link="images/inline-edit-create-follow-up-change.png"]
+ - Private change: Select this option to designate this change as private.
+ Only you (and any reviewers you add) can see your private changes.
+
+. On the Create Change window, click Create. Gerrit creates a public Work
+ In Progress (WIP) change. Until the change is sent for review, it remains a
+ WIP and appears in _your_ dashboard only. In addition, all email
+ notifications are turned off.
+
+. Add the files you want to be reviewed.
+
+
+[[add-files]]
+== Adding a File to a Change
+
+Files can only be added to changes that have not been merged into the code
+base.
+
+To add a file to the change:
+
+. In the top left corner of the change, click Edit.
+. Next to Files, click Open:
+
++
+image::images/inline-edit-open-file.png[width=600]
+
+. In the Open File window, do one of the following:
+
+* To add an existing file:
+
+ ** Enter all or part of the file name in the text box. Gerrit automatically
+ populates a list of possible matching files:
++
+image::images/inline-edit-prefill-files.png[width=500]
++
+ ** Select the file you want to add to the change.
+ ** Click Open.
++
+_or,_
+
+* To create a new file, enter the name of the new file you want to add to the
+change and then click Open.
+
[[editing-change]]
-== Editing Changes
+== Modifying a Change
-To switch to edit mode, press the 'Edit' button at the top of the file list:
+To work on a file you've added to a change:
-[[switch-to-edit-mode]]
-image::images/inline-edit-enter-edit-mode-from-file-list.png[width=800, link="images/inline-edit-enter-edit-mode-from-file-list.png"]
+. On the change page, click the file name. When you add a new file to a
+ change, a blank page is displayed. When you add an existing file to a
+ change, the entire file is displayed.
-While in edit mode, it is possible to add new files to the change by clicking
-the 'Add...' button at the top of the file list.
+. Update the file and then click Save. You _must_ click Save to add the
+ file to the change.
-File changes can be reverted or files can be removed from the change or
-deleted files can be restored, by clicking the icons to the left of the file
-name.
+. To close the text editor and display the change page, click Close.
++
+When you save your work and close the file, the file is added to the change
+and the file name is listed in the Files section. The letter displayed to the
+left of the file name denotes the action performed on the file. In this case,
+one file was modified:
-To switch from edit mode back to review mode, click the 'Done Editing' button.
+- M: Modified
+- A: Added
+- D: Deleted
++
+image::images/inline-edit-add-file-page.png[width=650]
-image::images/inline-edit-file-list-in-edit-mode.png[width=800, link="images/inline-edit-file-list-in-edit-mode.png"]
+. When you're done editing and adding files, click Stop Editing.
-[[open-full-screen-editor]]
-While in edit mode, clicking on a file name in the file list opens a full
-screen editor for that file.
+. Click Publish Edit. When you publish an edit, you promote it to a regular
+ patch set. The special ref that represents the change is deleted when the
+ change is published.
-To save edits, click the 'Save' button or press `CTRL-S`. To return to the
-change screen, click the 'Close' button.
+Not happy with your edits? Click Delete Edit.
-Note that when editing the commit message, trailing blank lines will be stripped.
-image::images/inline-edit-full-screen-editor.png[width=800, link="images/inline-edit-full-screen-editor.png"]
+[[submit-change]]
+== Starting the Review
-If there are unsaved edits when the 'Close' button is pressed, a dialog will
-pop up asking to confirm the edits.
+When you start a review, Gerrit removes the WIP designation and submits
+the change to code review. The change appears in other Gerrit dashboards and
+reviewers are notified when the change is updated.
-image::images/inline-edit-confirm-unsaved-edits.png[width=800, link="images/inline-edit-confirm-unsaved-edits.png"]
+To start a review:
-To discard the unsaved edits and return to the change screen, click the 'OK'
-button. To continue editing, click 'Cancel'.
+. Open the change and then click Start Review:
++
+image::images/inline-edit-start-review-button.png[width=400]
-[[switch-to-edit-mode-from-side-by-side]]
+. In the change notification form:
-While in review mode, it is possible to switch directly to edit mode and into an
-editor for a file under review by clicking on the edit icon in the patch set list
-on the side-by-side diff view.
+ ** Add the names of the reviewers and anyone else you want to copy.
+ ** Describe the change.
+ ** Click Start Review:
++
+image::images/inline-edit-review-message.png[width=550]
-image::images/inline-edit-enter-edit-mode-from-diff.png[width=800, link="images/inline-edit-enter-edit-mode-from-diff.png"]
+The change is now displayed in other Gerrit dashboards and reviewers are
+notified that the change is available for code review.
-[[reviewing-changes-made-in-change-edit]]
-== Reviewing Change Edits
-Change edits are reviewed in the same way as regular patch sets, using the
-side-by-side diff screen. Change edits are shown as 'edit' in the patch list
-on the diff screen:
+[[review-edits]]
+== Reviewing Changes
-image::images/inline-edit-edit-in-diff-screen-patch-list.png[width=800, link="images/inline-edit-edit-in-diff-screen-patch-list.png"]
+Use the side-by-side diff screen.
-and on the change screen:
+image::images/inline-edit-diff-screen.png[width=800]
-image::images/inline-edit-edit-in-patch-list.png[width=800, link="images/inline-edit-edit-in-patch-list.png"]
+It's possible that subsequent patch sets may exist. For example, this sequence
+means that the change was created on top of patch set 9 while a regular
+patchset was uploaded later:
-Note that patch sets may exist that were created after the change edit was created.
+1 2 3 4 5 6 7 8 9 edit 10
-For example this sequence:
-`1 2 3 4 5 6 7 8 9 edit 10`
+[[search-for-changes]]
+== Searching for Changes with Pending Edits
-means that the change edit was created on top of patch set number 9 and a regular
-patch set was uploaded later.
+To find changes with pending edits:
-[[change-edit-actions]]
-== Change Edit Actions
+* From the Gerrit dashboard, select Your > Changes. All your changes are
+listed, according to Work in progress, Outgoing reviews, Incoming reviews,
+CCed on, and Recently closed.
-Change edits can be deleted, published and rebased, and a patch set that
-represents a change edit can be downloaded like a regular patch set.
+For more information about Search operators, see
+link:user-search.html[Searching Changes]. For example, to find only
+those changes that contain edits, see link:user-search.html#has[has:edit].
-[[delete-change-edit]]
-There is a special ref for a change edit. When the change edit is deleted, this
-ref is deleted as well. To delete a change edit click on the "Delete Edit"
-button.
+[change-edit-actions]
+== Modifying Changes
-[[publish-change-edit]]
-
-When a change edit is based on the current patch set, it can be published. By
-publishing a change edit it is promoted to a regular patch set. The special ref
-that represents the change edit is deleted on publish. To publish a change edit
-click on the "Publish Edit" button. This button is only shown when the change
-edit is based on the current patch set. Otherwise the change edit must first be
-rebased onto the current patch set.
[[rebase-change-edit]]
+=== Rebasing a Change Edit
-Only change edits that are based on the current patch set can be published. If
-in the meantime a new patch set was uploaded, the change edit must be rebased on
-top of the current patch set before it can be published. Rebasing a change
-edit is done by clicking on the "Rebase Edit" button. If the rebase results in
-conflicts, these conflicts cannot be resolved in the browser. In this case the
-change edit must be downloaded (see below) and the conflicts must be resolved in
-the local environment. The commit that contains the conflict resolution can then
-be uploaded by setting `edit` as option on the target ref:
+Only when a change is based on the current patch set can the change be
+published. In the meantime, if a new patch set has been uploaded, the change
+must be rebased on top of the current patch set before the change can be
+published.
-----
- $ git push host HEAD:refs/for/master%edit
-----
+To rebase a change:
+
+- Open the change and then click Rebase Edit.
+
+If the rebase generates conflicts, the conflicts can't be resolved in the web
+interface. Instead, the change must be downloaded (see below) and the conflicts
+resolved in the local environment.
+
+When the conflicts are resolved in the local environment, the commit that
+contains the conflict resolution can be uploaded by setting `edit` as an
+option on the target ref. For example:
+
+....
+$ git push host HEAD:refs/for/master%edit
+....
+
[[download-change-edit-patch]]
+=== Downloading a Patch
-Like regular patch sets, change edits can be downloaded by the download
-commands (e.g. provided by the `download-commands` plugin). To download a
-change edit, select the desired scheme from the "Download" dropdown and copy the
-command to your terminal. Note: only change edit owners and users that were
-granted the link:access-control.html#capability_accessDatabase[accessDatabase]
-global capability are able to access change edit refs.
+As with regular patch sets, you can download changes. For example, as provided
+by the `download-commands` plugin. Only the owners of a change and those
+users granted the
+link:access-control.html#capability_accessDatabase[accessDatabase] global
+capability can access change refs.
-[[search-for-change-edits]]
+To download a change:
-To search change edits from the UI the link:user-search.html#has[has:edit]
-predicate can be used.
+. Open the change, click the More icon, and then select Download patch.
+. Copy the desired scheme from the Download drop-down.
+. Paste the command into a terminal window.
-Alternatively change edits can be accessed through "My => Edits" dashboard.
-
-[[not-implemented-features]]
-== Not Implemented Features
-
-* Support default configuration options for inline editor that an
-administrator has set in `refs/users/default:preferences.config` file.
-
-* Allow to rename files that are already contained in the change (from the file table).
-The same rename file dialog can be used with preselected and disabled original file
-name.
-
-* Changed files in change edit should be marked as changed in file table in edit mode.
-One option is to use dirty icon or "*" char in front of changed files, another option
-is to use different hyperlink color for changed files (red?), to avoid adding yet another
-column to the file table
-
-* Add navigation icons in header area of edit screen. When dozen files need to be changed
-in context of change edit, this is not the best workflow to open one file in edit screen,
-change it, save it, close edit screen and select next file from the file table to edit.
-"<-" | "->" icons in header of edit screen could be used to navigate to the next file to
-change from the file table. This would behave like the navigation icons in side by side
-with the following logic on click:
-
-** "save-when-file-was-changed" or
-** "close-when-no-changes"
-
-* Implement conflict resolution during rebase of change edit using inline edit
-feature by creating new edit on top of current patch set with auto merge content
-
-* Similarly, reuse inline edit feature for conflict resolution during rebase of regular
-patch sets
+image::images/inline-edit-actions-download.png[width=600]
GERRIT
-------
+
Part of link:index.html[Gerrit Code Review]
-SEARCHBOX
----------
+SEARCHBOX
\ No newline at end of file
diff --git a/Documentation/user-request-tracing.txt b/Documentation/user-request-tracing.txt
new file mode 100644
index 0000000..8430e97
--- /dev/null
+++ b/Documentation/user-request-tracing.txt
@@ -0,0 +1,72 @@
+= Request Tracing
+
+Gerrit supports on-demand tracing of single requests that results in
+additional logs with debug information that are written to the
+`error_log`. The logs that correspond to a traced request are
+associated with a unique trace ID. This trace ID is returned with the
+response and can be used by an administrator to find the matching log
+entries.
+
+How tracing is enabled and how the trace ID is returned depends on the
+request type:
+
+* REST API: For REST calls tracing can be enabled by setting the
+ `trace` request parameter or the `X-Gerrit-Trace` header, the trace
+ ID is returned as `X-Gerrit-Trace` header. More information about
+ this can be found in the link:rest-api.html#tracing[Request Tracing]
+ section of the link:rest-api.html[REST API documentation].
+* SSH API: For SSH calls tracing can be enabled by setting the
+ `--trace` option. More information about this can be found in
+ the link:cmd-index.html#trace[Trace] section of the
+ link:cmd-index.html[SSH command documentation].
+* Git: For Git pushes tracing can be enabled by setting the
+ `trace` push option, the trace ID is returned in the command output.
+ More information about this can be found in
+ the link:user-upload.html#trace[Trace] section of the
+ link:user-upload.html[upload documentation]. Tracing for Git requests
+ other than Git push is not supported.
+
+When request tracing is enabled it is possible to provide an ID that
+should be used as trace ID. If a trace ID is not provided a trace ID is
+automatically generated. The trace ID must be provided to the support
+team so that they can find the trace.
+
+When doing traces it is recommended to specify the ID of the issue
+that is being investigated as trace ID so that the traces of the issue
+can be found more easily. When the issue ID is used as trace ID there
+is no need to find the generated trace ID and report it in the issue.
+
+Since tracing consumes additional server resources tracing should only
+be enabled for single requests if there is a concrete need for
+debugging. In particular bots should never enable tracing for all their
+requests by default.
+
+== Find log entries for a trace ID
+
+If tracing is enabled all log messages that correspond to the traced
+request have a `TRACE_ID` tag set, e.g.:
+
+----
+[2018-08-13 15:28:08,913] [HTTP-76] TRACE com.google.gerrit.httpd.restapi.RestApiServlet : Received REST request: GET /a/accounts/self (parameters: [trace]) [CONTEXT forced=true TRACE_ID="1534166888910-3985dfba" ]
+[2018-08-13 15:28:08,914] [HTTP-76] TRACE com.google.gerrit.httpd.restapi.RestApiServlet : Calling user: admin [CONTEXT forced=true TRACE_ID="1534166888910-3985dfba" ]
+[2018-08-13 15:28:08,942] [HTTP-76] TRACE com.google.gerrit.httpd.restapi.RestApiServlet : REST call succeeded: 200 [CONTEXT forced=true TRACE_ID="1534166888910-3985dfba" ]
+----
+
+By doing a grep with the trace ID over the error log the log entries
+that correspond to the request can be found.
+
+== Which information is captured in a trace?
+
+* request details
+** REST API: request URL, request parameter names, calling user,
+ response code, response body on errors
+** SSH API: parameter names
+** Git API: push options, magic branch parameter names
+* cache misses, cache evictions
+* reads from NoteDb, writes to NoteDb
+* reads of meta data files, writes of meta data files
+* index queries (with parameters and matches)
+* reindex events
+* permission checks (e.g. which rule is responsible for a deny)
+* timer metrics
+* all other logs
diff --git a/Documentation/user-upload.txt b/Documentation/user-upload.txt
index baf388e..751e886 100644
--- a/Documentation/user-upload.txt
+++ b/Documentation/user-upload.txt
@@ -421,6 +421,36 @@
$ git push exp
----
+[[trace]]
+==== Trace
+
+When pushing to Gerrit tracing can be enabled by setting the
+`trace=<trace-id>` push option. It is recommended to use the ID of the
+issue that is being investigated as trace ID.
+
+----
+ git push -o trace=issue/123 ssh://john.doe@git.example.com:29418/kernel/common HEAD:refs/for/master
+----
+
+It is also possible to omit the trace ID and get a unique trace ID
+generated.
+
+.Example Request
+----
+ git push -o trace ssh://john.doe@git.example.com:29418/kernel/common HEAD:refs/for/master
+----
+
+Enabling tracing results in additional logs with debug information that
+are written to the `error_log`. All logs that correspond to the traced
+request are associated with the trace ID. This trace ID is returned in
+the command output:
+
+----
+ remote: TRACE_ID: 1534174322774-7edf2a7b
+----
+
+Given the trace ID an administrator can find the corresponding logs and
+investigate issues more easily.
[[push_replace]]
=== Replace Changes
diff --git a/WORKSPACE b/WORKSPACE
index c1d7213..dca68d3 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -16,7 +16,7 @@
name = "io_bazel_rules_closure",
sha256 = "4dd84dd2bdd6c9f56cb5a475d504ea31d199c34309e202e9379501d01c3067e5",
strip_prefix = "rules_closure-3103a773820b59b76345f94c231cb213e0d404e2",
- url = "https://github.com/bazelbuild/rules_closure/archive/3103a773820b59b76345f94c231cb213e0d404e2.tar.gz",
+ urls = ["https://github.com/bazelbuild/rules_closure/archive/3103a773820b59b76345f94c231cb213e0d404e2.tar.gz"],
)
# File is specific to Polymer and copied from the Closure Github -- should be
@@ -174,12 +174,12 @@
sha1 = "94ad16d728b374d65bd897625f3fbb3da223a2b6",
)
-FLOGGER_VERS = "0.2"
+FLOGGER_VERS = "0.3.1"
maven_jar(
name = "flogger",
artifact = "com.google.flogger:flogger:" + FLOGGER_VERS,
- sha1 = "a22d04ed3b84bae8ecf8aa6d4430ad000bcdf7b4",
+ sha1 = "585030fe1ec709760cbef997a459729fb965df0e",
)
maven_jar(
@@ -191,7 +191,7 @@
maven_jar(
name = "flogger-system-backend",
artifact = "com.google.flogger:flogger-system-backend:" + FLOGGER_VERS,
- sha1 = "b995c84b8443d6cfbd011a55719b63494b974c3a",
+ sha1 = "287b569d76abcd82f9de87fe41829fbc7ebd8ac9",
)
maven_jar(
@@ -203,8 +203,8 @@
maven_jar(
name = "gson",
- artifact = "com.google.code.gson:gson:2.8.4",
- sha1 = "d0de1ca9b69e69d1d497ee3c6009d015f64dad57",
+ artifact = "com.google.code.gson:gson:2.8.5",
+ sha1 = "f645ed69d595b24d4cf8b3fbb64cc505bede8829",
)
maven_jar(
@@ -351,22 +351,163 @@
sha1 = "959a0c62f9a5c2309e0ad0b0589c74d69e101241",
)
+FLEXMARK_VERS = "0.34.18"
+
maven_jar(
- name = "pegdown",
- artifact = "org.pegdown:pegdown:1.6.0",
- sha1 = "231ae49d913467deb2027d0b8a0b68b231deef4f",
+ name = "flexmark",
+ artifact = "com.vladsch.flexmark:flexmark:" + FLEXMARK_VERS,
+ sha1 = "65cc1489ef8902023140900a3a7fcce89fba678d",
)
maven_jar(
- name = "grappa",
- artifact = "com.github.parboiled1:grappa:1.0.4",
- sha1 = "ad4b44b9c305dad7aa1e680d4b5c8eec9c4fd6f5",
+ name = "flexmark-ext-abbreviation",
+ artifact = "com.vladsch.flexmark:flexmark-ext-abbreviation:" + FLEXMARK_VERS,
+ sha1 = "a0384932801e51f16499358dec69a730739aca3f",
)
maven_jar(
- name = "jitescript",
- artifact = "me.qmx.jitescript:jitescript:0.4.0",
- sha1 = "2e35862b0435c1b027a21f3d6eecbe50e6e08d54",
+ name = "flexmark-ext-anchorlink",
+ artifact = "com.vladsch.flexmark:flexmark-ext-anchorlink:" + FLEXMARK_VERS,
+ sha1 = "6df2e23b5c94a5e46b1956a29179eb783f84ea2f",
+)
+
+maven_jar(
+ name = "flexmark-ext-autolink",
+ artifact = "com.vladsch.flexmark:flexmark-ext-autolink:" + FLEXMARK_VERS,
+ sha1 = "069f8ff15e5b435cc96b23f31798ce64a7a3f6d3",
+)
+
+maven_jar(
+ name = "flexmark-ext-definition",
+ artifact = "com.vladsch.flexmark:flexmark-ext-definition:" + FLEXMARK_VERS,
+ sha1 = "ff177d8970810c05549171e3ce189e2c68b906c0",
+)
+
+maven_jar(
+ name = "flexmark-ext-emoji",
+ artifact = "com.vladsch.flexmark:flexmark-ext-emoji:" + FLEXMARK_VERS,
+ sha1 = "410bf7d8e5b8bc2c4a8cff644d1b2bc7b271a41e",
+)
+
+maven_jar(
+ name = "flexmark-ext-escaped-character",
+ artifact = "com.vladsch.flexmark:flexmark-ext-escaped-character:" + FLEXMARK_VERS,
+ sha1 = "6f4fb89311b54284a6175341d4a5e280f13b2179",
+)
+
+maven_jar(
+ name = "flexmark-ext-footnotes",
+ artifact = "com.vladsch.flexmark:flexmark-ext-footnotes:" + FLEXMARK_VERS,
+ sha1 = "35efe7d9aea97b6f36e09c65f748863d14e1cfe4",
+)
+
+maven_jar(
+ name = "flexmark-ext-gfm-issues",
+ artifact = "com.vladsch.flexmark:flexmark-ext-gfm-issues:" + FLEXMARK_VERS,
+ sha1 = "ec1d660102f6a1d0fbe5e57c13b7ff8bae6cff72",
+)
+
+maven_jar(
+ name = "flexmark-ext-gfm-strikethrough",
+ artifact = "com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:" + FLEXMARK_VERS,
+ sha1 = "6060442b742c9b6d4d83d7dd4f0fe477c4686dd2",
+)
+
+maven_jar(
+ name = "flexmark-ext-gfm-tables",
+ artifact = "com.vladsch.flexmark:flexmark-ext-gfm-tables:" + FLEXMARK_VERS,
+ sha1 = "2fe597849e46e02e0c1ea1d472848f74ff261282",
+)
+
+maven_jar(
+ name = "flexmark-ext-gfm-tasklist",
+ artifact = "com.vladsch.flexmark:flexmark-ext-gfm-tasklist:" + FLEXMARK_VERS,
+ sha1 = "b3af19ce4efdc980a066c1bf0f5a6cf8c24c487a",
+)
+
+maven_jar(
+ name = "flexmark-ext-gfm-users",
+ artifact = "com.vladsch.flexmark:flexmark-ext-gfm-users:" + FLEXMARK_VERS,
+ sha1 = "7456c5f7272c195ee953a02ebab4f58374fb23ee",
+)
+
+maven_jar(
+ name = "flexmark-ext-ins",
+ artifact = "com.vladsch.flexmark:flexmark-ext-ins:" + FLEXMARK_VERS,
+ sha1 = "13fe1a95a8f3be30b574451cfe8d3d5936fa3e94",
+)
+
+maven_jar(
+ name = "flexmark-ext-jekyll-front-matter",
+ artifact = "com.vladsch.flexmark:flexmark-ext-jekyll-front-matter:" + FLEXMARK_VERS,
+ sha1 = "e146e2bf3a740d6ef06a33a516c4d1f6d3761109",
+)
+
+maven_jar(
+ name = "flexmark-ext-superscript",
+ artifact = "com.vladsch.flexmark:flexmark-ext-superscript:" + FLEXMARK_VERS,
+ sha1 = "02541211e8e4a6c89ce0a68b07b656d8a19ac282",
+)
+
+maven_jar(
+ name = "flexmark-ext-tables",
+ artifact = "com.vladsch.flexmark:flexmark-ext-tables:" + FLEXMARK_VERS,
+ sha1 = "775d9587de71fd50573f32eee98ab039b4dcc219",
+)
+
+maven_jar(
+ name = "flexmark-ext-toc",
+ artifact = "com.vladsch.flexmark:flexmark-ext-toc:" + FLEXMARK_VERS,
+ sha1 = "85b75fe1ebe24c92b9d137bcbc51d232845b6077",
+)
+
+maven_jar(
+ name = "flexmark-ext-typographic",
+ artifact = "com.vladsch.flexmark:flexmark-ext-typographic:" + FLEXMARK_VERS,
+ sha1 = "c1bf0539de37d83aa05954b442f929e204cd89db",
+)
+
+maven_jar(
+ name = "flexmark-ext-wikilink",
+ artifact = "com.vladsch.flexmark:flexmark-ext-wikilink:" + FLEXMARK_VERS,
+ sha1 = "400b23b9a4e0c008af0d779f909ee357628be39d",
+)
+
+maven_jar(
+ name = "flexmark-ext-yaml-front-matter",
+ artifact = "com.vladsch.flexmark:flexmark-ext-yaml-front-matter:" + FLEXMARK_VERS,
+ sha1 = "491f815285a8e16db1e906f3789a94a8a9836fa6",
+)
+
+maven_jar(
+ name = "flexmark-formatter",
+ artifact = "com.vladsch.flexmark:flexmark-formatter:" + FLEXMARK_VERS,
+ sha1 = "d46308006800d243727100ca0f17e6837070fd48",
+)
+
+maven_jar(
+ name = "flexmark-html-parser",
+ artifact = "com.vladsch.flexmark:flexmark-html-parser:" + FLEXMARK_VERS,
+ sha1 = "fece2e646d11b6a77fc611b4bd3eb1fb8a635c87",
+)
+
+maven_jar(
+ name = "flexmark-profile-pegdown",
+ artifact = "com.vladsch.flexmark:flexmark-profile-pegdown:" + FLEXMARK_VERS,
+ sha1 = "297f723bb51286eaa7029558fac87d819643d577",
+)
+
+maven_jar(
+ name = "flexmark-util",
+ artifact = "com.vladsch.flexmark:flexmark-util:" + FLEXMARK_VERS,
+ sha1 = "31e2e1fbe8273d7c913506eafeb06b1a7badb062",
+)
+
+# Transitive dependency of flexmark
+maven_jar(
+ name = "autolink",
+ artifact = "org.nibor.autolink:autolink:0.7.0",
+ sha1 = "649f9f13422cf50c926febe6035662ae25dc89b2",
)
GREENMAIL_VERS = "1.5.5"
@@ -405,36 +546,36 @@
sha1 = "5e3bda828a80c7a21dfbe2308d1755759c2fd7b4",
)
-OW2_VERS = "6.0"
+OW2_VERS = "6.2.1"
maven_jar(
name = "ow2-asm",
artifact = "org.ow2.asm:asm:" + OW2_VERS,
- sha1 = "bc6fa6b19424bb9592fe43bbc20178f92d403105",
+ sha1 = "c01b6798f81b0fc2c5faa70cbe468c275d4b50c7",
)
maven_jar(
name = "ow2-asm-analysis",
artifact = "org.ow2.asm:asm-analysis:" + OW2_VERS,
- sha1 = "dd1cc1381a970800268160203aae2d3784da779b",
+ sha1 = "e8b876c5ccf226cae2f44ed2c436ad3407d0ec1d",
)
maven_jar(
name = "ow2-asm-commons",
artifact = "org.ow2.asm:asm-commons:" + OW2_VERS,
- sha1 = "f256fd215d8dd5a4fa2ab3201bf653de266ed4ec",
+ sha1 = "eaf31376d741a3e2017248a4c759209fe25c77d3",
)
maven_jar(
name = "ow2-asm-tree",
artifact = "org.ow2.asm:asm-tree:" + OW2_VERS,
- sha1 = "a624f1a6e4e428dcd680a01bab2d4c56b35b18f0",
+ sha1 = "332b022092ecec53cdb6272dc436884b2d940615",
)
maven_jar(
name = "ow2-asm-util",
artifact = "org.ow2.asm:asm-util:" + OW2_VERS,
- sha1 = "430b2fc839b5de1f3643b528853d5cf26096c1de",
+ sha1 = "400d664d7c92a659d988c00cb65150d1b30cf339",
)
AUTO_VALUE_VERSION = "1.6.2"
@@ -675,13 +816,6 @@
sha1 = "42a25dc3219429f0e5d060061f71acb49bf010a0",
)
-# Only needed when jgit is built from the development tree
-maven_jar(
- name = "hamcrest-library",
- artifact = "org.hamcrest:hamcrest-library:1.3",
- sha1 = "4785a3c21320980282f9f33d0d1264a69040538f",
-)
-
TRUTH_VERS = "0.42"
maven_jar(
@@ -723,8 +857,8 @@
maven_jar(
name = "cglib-3_2",
- artifact = "cglib:cglib-nodep:3.2.0",
- sha1 = "cf1ca207c15b04ace918270b6cb3f5601160cdfd",
+ artifact = "cglib:cglib-nodep:3.2.6",
+ sha1 = "92bf48723d277d6efd1150b2f7e9e1e92cb56caf",
)
maven_jar(
@@ -861,8 +995,8 @@
maven_jar(
name = "postgresql",
- artifact = "org.postgresql:postgresql:9.4.1211",
- sha1 = "721e3017fab68db9f0b08537ec91b8d757973ca8",
+ artifact = "org.postgresql:postgresql:42.2.4",
+ sha1 = "dff98730c28a4b3a3263f0cf4abb9a3392f815a7",
)
maven_jar(
@@ -904,8 +1038,8 @@
maven_jar(
name = "elasticsearch-rest-client",
- artifact = "org.elasticsearch.client:elasticsearch-rest-client:6.3.2",
- sha1 = "2077ea5f00fdd2d6af85223b730ba8047303297f",
+ artifact = "org.elasticsearch.client:elasticsearch-rest-client:6.4.0",
+ sha1 = "0eaa13decb9796eb671c5841d0770ae68b348da5",
)
JACKSON_VERSION = "2.8.9"
diff --git a/antlr3/BUILD b/antlr3/BUILD
index 6d7102a..fc96715 100644
--- a/antlr3/BUILD
+++ b/antlr3/BUILD
@@ -15,3 +15,17 @@
],
visibility = ["//visibility:public"],
)
+
+java_library(
+ name = "query_parser",
+ srcs = [":query"],
+ visibility = [
+ "//java/com/google/gerrit/index:__pkg__",
+ "//javatests/com/google/gerrit/index:__pkg__",
+ "//plugins:__pkg__",
+ ],
+ deps = [
+ "//java/com/google/gerrit/index:query_exception",
+ "//lib/antlr:java-runtime",
+ ],
+)
diff --git a/contrib/abandon_stale.py b/contrib/abandon_stale.py
index 3501b8b..2e01131 100755
--- a/contrib/abandon_stale.py
+++ b/contrib/abandon_stale.py
@@ -103,6 +103,9 @@
default=None,
action='store',
help='only abandon changes owned by the given user')
+ parser.add_option('--exclude-wip', dest='exclude_wip',
+ action='store_true',
+ help='Exclude changes that are Work-in-Progress')
parser.add_option('-v', '--verbose', dest='verbose',
action='store_true',
help='enable verbose (debug) logging')
@@ -148,7 +151,9 @@
if options.testmode:
query_terms = ["status:new", "owner:self", "topic:test-abandon"]
else:
- query_terms = ["status:new", "-is:wip", "age:%s" % options.age]
+ query_terms = ["status:new", "age:%s" % options.age]
+ if options.exclude_wip:
+ query_terms += ["-is:wip"]
if options.branches:
query_terms += ["branch:%s" % b for b in options.branches]
elif options.exclude_branches:
diff --git a/gerrit-acceptance-tests/tests.bzl b/gerrit-acceptance-tests/tests.bzl
deleted file mode 100644
index c1e34dd..0000000
--- a/gerrit-acceptance-tests/tests.bzl
+++ /dev/null
@@ -1,21 +0,0 @@
-load("//tools/bzl:junit.bzl", "junit_tests")
-
-def acceptance_tests(
- group,
- deps = [],
- labels = [],
- vm_args = ["-Xmx256m"],
- **kwargs):
- junit_tests(
- name = group,
- deps = deps + [
- "//gerrit-acceptance-tests:lib",
- ],
- tags = labels + [
- "acceptance",
- "slow",
- ],
- size = "large",
- jvm_flags = vm_args,
- **kwargs
- )
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java
index 866d74f..0f786a6 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java
@@ -14,6 +14,8 @@
package com.google.gerrit.client.info;
+import static java.util.Comparator.comparing;
+
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.Natives;
@@ -30,8 +32,6 @@
import com.google.gwtjsonrpc.client.impl.ser.JavaSqlTimestamp_JsonSerializer;
import java.sql.Timestamp;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -447,18 +447,8 @@
public static void sortRevisionInfoByNumber(JsArray<RevisionInfo> list) {
final int editParent = findEditParent(list);
- Collections.sort(
- Natives.asList(list),
- new Comparator<RevisionInfo>() {
- @Override
- public int compare(RevisionInfo a, RevisionInfo b) {
- return num(a) - num(b);
- }
-
- private int num(RevisionInfo r) {
- return !r.isEdit() ? 2 * (r._number() - 1) + 1 : 2 * editParent;
- }
- });
+ Natives.asList(list)
+ .sort(comparing(r -> !r.isEdit() ? 2 * (r._number() - 1) + 1 : 2 * editParent));
}
public static int findEditParent(JsArray<RevisionInfo> list) {
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java
index 345a260..fc3dbf1 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java
@@ -19,7 +19,6 @@
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
-import java.util.Collections;
import java.util.Comparator;
public class FileInfo extends JavaScriptObject {
@@ -55,8 +54,7 @@
public final native void _row(int r) /*-{ this._row = r }-*/;
public static void sortFileInfoByPath(JsArray<FileInfo> list) {
- Collections.sort(
- Natives.asList(list), Comparator.comparing(FileInfo::path, FilenameComparator.INSTANCE));
+ Natives.asList(list).sort(Comparator.comparing(FileInfo::path, FilenameComparator.INSTANCE));
}
public static String getFileName(String path) {
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/NativeMap.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/NativeMap.java
index 4b17068..41306ff 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/NativeMap.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/NativeMap.java
@@ -14,11 +14,12 @@
package com.google.gerrit.client.rpc;
+import static java.util.stream.Collectors.toCollection;
+
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.rpc.AsyncCallback;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -57,10 +58,7 @@
}
public final List<String> sortedKeys() {
- Set<String> keys = keySet();
- List<String> sorted = new ArrayList<>(keys);
- Collections.sort(sorted);
- return sorted;
+ return keySet().stream().sorted().collect(toCollection(ArrayList::new));
}
public final native JsArray<T> values() /*-{
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
index 74fcdc2..38e1b60 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
@@ -126,7 +126,25 @@
if (size == 0) {
return Resources.C.notAvailable();
}
- int p = Math.abs(Math.round(delta * 100 / size));
+ int p = Math.abs(saturatedCast(delta * 100 / size));
return p + "%";
}
+
+ /**
+ * Returns the {@code int} nearest in value to {@code value}.
+ *
+ * @param value any {@code long} value
+ * @return the same value cast to {@code int} if it is in the range of the {@code int} type,
+ * {@link Integer#MAX_VALUE} if it is too large, or {@link Integer#MIN_VALUE} if it is too
+ * small
+ */
+ private static int saturatedCast(long value) {
+ if (value > Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ }
+ if (value < Integer.MIN_VALUE) {
+ return Integer.MIN_VALUE;
+ }
+ return (int) value;
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/access/ProjectAccessInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/access/ProjectAccessInfo.java
index b115c7d..88635df 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/access/ProjectAccessInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/access/ProjectAccessInfo.java
@@ -19,6 +19,8 @@
public class ProjectAccessInfo extends JavaScriptObject {
public final native boolean canAddRefs() /*-{ return this.can_add ? true : false; }-*/;
+ public final native boolean canAddTagRefs() /*-{ return this.can_add_tags ? true : false; }-*/;
+
public final native boolean isOwner() /*-{ return this.is_owner ? true : false; }-*/;
public final native boolean configVisible() /*-{ return this.config_visible ? true : false; }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGpgKeysScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGpgKeysScreen.java
index 0dc1dab..1a0090a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGpgKeysScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGpgKeysScreen.java
@@ -14,6 +14,8 @@
package com.google.gerrit.client.account;
+import static java.util.Comparator.comparing;
+
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.info.GpgKeyInfo;
import com.google.gerrit.client.rpc.GerritCallback;
@@ -40,8 +42,6 @@
import com.google.gwtexpui.clippy.client.CopyableLabel;
import com.google.gwtexpui.globalkey.client.NpTextArea;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
public class MyGpgKeysScreen extends SettingsScreen {
@@ -118,14 +118,7 @@
List<GpgKeyInfo> list = Natives.asList(result.values());
// TODO(dborowitz): Sort on something more meaningful, like
// created date?
- Collections.sort(
- list,
- new Comparator<GpgKeyInfo>() {
- @Override
- public int compare(GpgKeyInfo a, GpgKeyInfo b) {
- return a.id().compareTo(b.id());
- }
- });
+ list.sort(comparing(GpgKeyInfo::id));
keys.clear();
keyText.setText("");
errorPanel.setVisible(false);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
index 5c6d40f..730d98e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
@@ -14,6 +14,8 @@
package com.google.gerrit.client.account;
+import static java.util.Comparator.naturalOrder;
+
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.rpc.GerritCallback;
@@ -29,7 +31,6 @@
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -169,7 +170,7 @@
void display(JsArray<ExternalIdInfo> results) {
List<ExternalIdInfo> idList = Natives.asList(results);
- Collections.sort(idList);
+ idList.sort(naturalOrder());
while (1 < table.getRowCount()) {
table.removeRow(table.getRowCount() - 1);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
index 80c6d1a..4fdd067 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
@@ -41,7 +41,7 @@
// corresponding regular expressions in the
// com.google.gerrit.server.account.externalids.ExternalId class.
private static final String USER_NAME_PATTERN_FIRST_REGEX = "[a-zA-Z0-9]";
- private static final String USER_NAME_PATTERN_REST_REGEX = "[a-zA-Z0-9._@-]";
+ private static final String USER_NAME_PATTERN_REST_REGEX = "[a-zA-Z0-9.!#$%&’*+=?^_`\\{|\\}~@-]";
private CopyableLabel userNameLbl;
private NpTextBox userNameTxt;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
index 1b946cd..7bd8b82 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
@@ -14,6 +14,8 @@
package com.google.gerrit.client.admin;
+import static java.util.stream.Collectors.toCollection;
+
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.LabelType;
@@ -45,7 +47,6 @@
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.ValueListBox;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
public class AccessSectionEditor extends Composite
@@ -205,9 +206,8 @@
}
private void sortPermissions(AccessSection accessSection) {
- List<Permission> permissionList = new ArrayList<>(accessSection.getPermissions());
- Collections.sort(permissionList);
- accessSection.setPermissions(permissionList);
+ accessSection.setPermissions(
+ accessSection.getPermissions().stream().sorted().collect(toCollection(ArrayList::new)));
}
void setEditing(boolean editing) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
index 2614224..6eaab5d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
@@ -14,6 +14,8 @@
package com.google.gerrit.client.admin;
+import static java.util.Comparator.comparing;
+
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.VoidResult;
@@ -29,7 +31,6 @@
import com.google.gerrit.client.ui.FancyFlexTable;
import com.google.gerrit.client.ui.Hyperlink;
import com.google.gerrit.client.ui.SmallHeading;
-import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
@@ -295,26 +296,9 @@
void insert(AccountInfo info) {
Comparator<AccountInfo> c =
- new Comparator<AccountInfo>() {
- @Override
- public int compare(AccountInfo a, AccountInfo b) {
- int cmp = nullToEmpty(a.name()).compareTo(nullToEmpty(b.name()));
- if (cmp != 0) {
- return cmp;
- }
-
- cmp = nullToEmpty(a.email()).compareTo(nullToEmpty(b.email()));
- if (cmp != 0) {
- return cmp;
- }
-
- return a._accountId() - b._accountId();
- }
-
- public String nullToEmpty(String str) {
- return str == null ? "" : str;
- }
- };
+ comparing((AccountInfo a) -> nullToEmpty(a.name()))
+ .thenComparing(a -> nullToEmpty(a.email()))
+ .thenComparing(AccountInfo::_accountId);
int insertPos = getInsertRow(c, info);
if (insertPos >= 0) {
table.insertRow(insertPos);
@@ -405,20 +389,7 @@
void insert(GroupInfo info) {
Comparator<GroupInfo> c =
- new Comparator<GroupInfo>() {
- @Override
- public int compare(GroupInfo a, GroupInfo b) {
- int cmp = nullToEmpty(a.name()).compareTo(nullToEmpty(b.name()));
- if (cmp != 0) {
- return cmp;
- }
- return a.getGroupUUID().compareTo(b.getGroupUUID());
- }
-
- private String nullToEmpty(@Nullable String str) {
- return (str == null) ? "" : str;
- }
- };
+ comparing((GroupInfo g) -> nullToEmpty(g.name())).thenComparing(GroupInfo::getGroupUUID);
int insertPos = getInsertRow(c, info);
if (insertPos >= 0) {
table.insertRow(insertPos);
@@ -457,4 +428,9 @@
setRowItem(row, i);
}
}
+
+ // Like Guava's Strings#nullToEmpty, which can't be used in GWT UI code.
+ private static String nullToEmpty(String str) {
+ return str == null ? "" : str;
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
index 0c2f6fa..7b18a39 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
@@ -36,7 +36,7 @@
String effectiveMaxObjectSizeLimit(String effectiveMaxObjectSizeLimit);
- String globalMaxObjectSizeLimit(String globalMaxObjectSizeLimit);
+ String noMaxObjectSizeLimit();
String pluginProjectOptionsTitle(String pluginName);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
index 6338920..c9aa987 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
@@ -5,8 +5,8 @@
deletedGroup = Deleted Group {0}
deletedReference = Reference {0} was deleted
deletedSection = Section {0} was deleted
-effectiveMaxObjectSizeLimit = effective: {0}
-globalMaxObjectSizeLimit = The global max object size limit is set to {0}. The limit cannot be increased on project level.
+effectiveMaxObjectSizeLimit = effective: {0} bytes
+noMaxObjectSizeLimit = No max object size limit is set.
pluginProjectOptionsTitle = {0} Plugin Options
pluginProjectOptionsTitle = {0} Plugin
pluginProjectInheritedValue = inherited: {0}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java
index 259847e..be0db41 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java
@@ -14,6 +14,8 @@
package com.google.gerrit.client.admin;
+import static java.util.Comparator.comparing;
+
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.groups.GroupList;
@@ -32,8 +34,6 @@
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
import com.google.gwt.user.client.ui.Image;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
public class GroupTable extends NavigationTable<GroupInfo> {
@@ -105,14 +105,7 @@
table.removeRow(table.getRowCount() - 1);
}
- Collections.sort(
- list,
- new Comparator<GroupInfo>() {
- @Override
- public int compare(GroupInfo a, GroupInfo b) {
- return a.name().compareTo(b.name());
- }
- });
+ list.sort(comparing(GroupInfo::name));
for (GroupInfo group : list.subList(fromIndex, toIndex)) {
final int row = table.getRowCount();
table.insertRow(row);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index 751e951..d10a031 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -439,14 +439,14 @@
setSubmitType(result.defaultSubmitType());
setState(result.state());
maxObjectSizeLimit.setText(result.maxObjectSizeLimit().configuredValue());
- if (result.maxObjectSizeLimit().inheritedValue() != null) {
- effectiveMaxObjectSizeLimit.setVisible(true);
+ if (result.maxObjectSizeLimit().value() != null) {
effectiveMaxObjectSizeLimit.setText(
AdminMessages.I.effectiveMaxObjectSizeLimit(result.maxObjectSizeLimit().value()));
- effectiveMaxObjectSizeLimit.setTitle(
- AdminMessages.I.globalMaxObjectSizeLimit(result.maxObjectSizeLimit().inheritedValue()));
+ if (result.maxObjectSizeLimit().summary() != null) {
+ effectiveMaxObjectSizeLimit.setTitle(result.maxObjectSizeLimit().summary());
+ }
} else {
- effectiveMaxObjectSizeLimit.setVisible(false);
+ effectiveMaxObjectSizeLimit.setText(AdminMessages.I.noMaxObjectSizeLimit());
}
saveProject.setEnabled(false);
@@ -512,6 +512,9 @@
textBox.setValue(param.value());
addWidget(g, textBox, param);
}
+ if (textBox.getValue().length() > textBox.getVisibleLength()) {
+ textBox.setVisibleLength(textBox.getValue().length());
+ }
saveEnabler.listenTo(textBox);
return textBox;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
index 18e4176..22c331d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
@@ -94,7 +94,7 @@
new GerritCallback<ProjectAccessInfo>() {
@Override
public void onSuccess(ProjectAccessInfo result) {
- addPanel.setVisible(result.canAddRefs());
+ addPanel.setVisible(result.canAddTagRefs());
}
});
query = new Query(match).start(start).run();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
index 1f4820f..801a927 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
@@ -14,6 +14,10 @@
package com.google.gerrit.client.change;
+import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toList;
+
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.Util;
@@ -135,9 +139,12 @@
}
void set(ChangeInfo info) {
- List<String> names = new ArrayList<>(info.labels());
+ List<String> names =
+ info.labels()
+ .stream()
+ .sorted()
+ .collect(collectingAndThen(toList(), Collections::unmodifiableList));
Set<Integer> removable = info.removableReviewerIds();
- Collections.sort(names);
resize(names.size(), 2);
@@ -197,8 +204,7 @@
}
private static List<Integer> sort(Set<Integer> keySet, int a, int b) {
- List<Integer> r = new ArrayList<>(keySet);
- Collections.sort(r);
+ List<Integer> r = keySet.stream().sorted().collect(toCollection(ArrayList::new));
if (keySet.contains(a)) {
r.remove(Integer.valueOf(a));
r.add(0, a);
@@ -238,31 +244,32 @@
Set<Integer> removable,
String label,
Map<Integer, VotableInfo> votable) {
- List<AccountInfo> users = new ArrayList<>(in);
- Collections.sort(
- users,
- new Comparator<AccountInfo>() {
- @Override
- public int compare(AccountInfo a, AccountInfo b) {
- String as = name(a);
- String bs = name(b);
- if (as.isEmpty()) {
- return 1;
- } else if (bs.isEmpty()) {
- return -1;
- }
- return as.compareTo(bs);
- }
+ List<AccountInfo> users =
+ in.stream()
+ .sorted(
+ new Comparator<AccountInfo>() {
+ @Override
+ public int compare(AccountInfo a, AccountInfo b) {
+ String as = name(a);
+ String bs = name(b);
+ if (as.isEmpty()) {
+ return 1;
+ } else if (bs.isEmpty()) {
+ return -1;
+ }
+ return as.compareTo(bs);
+ }
- private String name(AccountInfo a) {
- if (a.name() != null) {
- return a.name();
- } else if (a.email() != null) {
- return a.email();
- }
- return "";
- }
- });
+ private String name(AccountInfo a) {
+ if (a.name() != null) {
+ return a.name();
+ } else if (a.email() != null) {
+ return a.email();
+ }
+ return "";
+ }
+ })
+ .collect(collectingAndThen(toList(), Collections::unmodifiableList));
SafeHtmlBuilder html = new SafeHtmlBuilder();
Iterator<? extends AccountInfo> itr = users.iterator();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
index 0bbd614..8a1a2d5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
@@ -16,6 +16,8 @@
import static com.google.gwt.event.dom.client.KeyCodes.KEY_ENTER;
import static com.google.gwt.event.dom.client.KeyCodes.KEY_MAC_ENTER;
+import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.toList;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.ChangeApi;
@@ -123,11 +125,15 @@
this.lc = new LocalComments(project, psId.getParentKey());
initWidget(uiBinder.createAndBindUi(this));
- List<String> names = new ArrayList<>(permitted.keySet());
+ List<String> names =
+ permitted
+ .keySet()
+ .stream()
+ .sorted()
+ .collect(collectingAndThen(toList(), Collections::unmodifiableList));
if (names.isEmpty()) {
UIObject.setVisible(labelsParent, false);
} else {
- Collections.sort(names);
renderLabels(names, all, permitted);
}
@@ -439,8 +445,11 @@
clp, project, psId, Util.C.commitMessage(), copyPath(Patch.MERGE_LIST, l)));
}
- List<String> paths = new ArrayList<>(m.keySet());
- Collections.sort(paths);
+ List<String> paths =
+ m.keySet()
+ .stream()
+ .sorted()
+ .collect(collectingAndThen(toList(), Collections::unmodifiableList));
for (String path : paths) {
if (!Patch.isMagic(path)) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
index c6e4e2f..7ec1102 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
@@ -14,6 +14,8 @@
package com.google.gerrit.client.changes;
+import static java.util.Comparator.comparing;
+
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.NotFoundScreen;
import com.google.gerrit.client.info.ChangeInfo;
@@ -176,7 +178,7 @@
}
}
- Collections.sort(Natives.asList(out), outComparator());
+ Natives.asList(out).sort(outComparator());
table.updateColumnsForLabels(wip, out, in, done);
workInProgress.display(wip);
@@ -187,16 +189,7 @@
}
private Comparator<ChangeInfo> outComparator() {
- return new Comparator<ChangeInfo>() {
- @Override
- public int compare(ChangeInfo a, ChangeInfo b) {
- int cmp = a.created().compareTo(b.created());
- if (cmp != 0) {
- return cmp;
- }
- return a._number() - b._number();
- }
- };
+ return comparing(ChangeInfo::created).thenComparing(ChangeInfo::_number);
}
private boolean hasChanges(JsArray<ChangeList> result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
index caea87e..4fda78b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
@@ -16,11 +16,13 @@
import static com.google.gerrit.client.FormatUtil.relativeFormat;
import static com.google.gerrit.client.FormatUtil.shortFormat;
+import static java.util.stream.Collectors.toList;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.info.AccountInfo;
import com.google.gerrit.client.info.ChangeInfo;
import com.google.gerrit.client.info.ChangeInfo.LabelInfo;
+import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.AccountLinkPanel;
import com.google.gerrit.client.ui.BranchLink;
import com.google.gerrit.client.ui.ChangeLink;
@@ -45,6 +47,7 @@
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
@@ -185,17 +188,13 @@
}
public void updateColumnsForLabels(ChangeList... lists) {
- labelNames = new ArrayList<>();
- for (ChangeList list : lists) {
- for (int i = 0; i < list.length(); i++) {
- for (String name : list.get(i).labels()) {
- if (!labelNames.contains(name)) {
- labelNames.add(name);
- }
- }
- }
- }
- Collections.sort(labelNames);
+ labelNames =
+ Arrays.stream(lists)
+ .flatMap(l -> Natives.asList(l).stream())
+ .flatMap(c -> c.labels().stream())
+ .distinct()
+ .sorted()
+ .collect(toList());
int baseColumns = BASE_COLUMNS;
if (baseColumns + labelNames.size() < columns) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardsTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardsTable.java
index 0e4ef4e..dcb9c01 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardsTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardsTable.java
@@ -14,6 +14,8 @@
package com.google.gerrit.client.dashboards;
+import static java.util.Comparator.comparing;
+
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.NavigationTable;
@@ -25,8 +27,6 @@
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.Image;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -75,14 +75,7 @@
table.removeRow(table.getRowCount() - 1);
}
- Collections.sort(
- list,
- new Comparator<DashboardInfo>() {
- @Override
- public int compare(DashboardInfo a, DashboardInfo b) {
- return a.id().compareTo(b.id());
- }
- });
+ list.sort(comparing(DashboardInfo::id));
String ref = null;
for (DashboardInfo d : list) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java
index 533b745..2698584 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java
@@ -14,6 +14,8 @@
package com.google.gerrit.client.diff;
+import static java.util.Comparator.comparing;
+
import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.CommentApi;
@@ -27,8 +29,6 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.rpc.AsyncCallback;
-import java.util.Collections;
-import java.util.Comparator;
/** Collection of published and draft comments loaded from the server. */
class CommentsCollections {
@@ -158,14 +158,7 @@
for (CommentInfo c : Natives.asList(in)) {
c.path(path);
}
- Collections.sort(
- Natives.asList(in),
- new Comparator<CommentInfo>() {
- @Override
- public int compare(CommentInfo a, CommentInfo b) {
- return a.updated().compareTo(b.updated());
- }
- });
+ Natives.asList(in).sort(comparing(CommentInfo::updated));
}
return in;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UnifiedChunkManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UnifiedChunkManager.java
index 1a662e2..98ad023 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UnifiedChunkManager.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UnifiedChunkManager.java
@@ -14,6 +14,8 @@
package com.google.gerrit.client.diff;
+import static java.util.Comparator.comparing;
+
import com.google.gerrit.client.diff.DiffInfo.Region;
import com.google.gerrit.client.diff.DiffInfo.Span;
import com.google.gerrit.client.rpc.Natives;
@@ -227,12 +229,7 @@
/** Diff chunks are ordered by their starting lines in CodeMirror */
private Comparator<UnifiedDiffChunkInfo> getDiffChunkComparatorCmLine() {
- return new Comparator<UnifiedDiffChunkInfo>() {
- @Override
- public int compare(UnifiedDiffChunkInfo o1, UnifiedDiffChunkInfo o2) {
- return o1.getCmLine() - o2.getCmLine();
- }
- };
+ return comparing(UnifiedDiffChunkInfo::getCmLine);
}
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
index 4185ef3..684f8e6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
@@ -175,10 +175,10 @@
public static class MaxObjectSizeLimitInfo extends JavaScriptObject {
public final native String value() /*-{ return this.value; }-*/;
- public final native String inheritedValue() /*-{ return this.inherited_value; }-*/;
-
public final native String configuredValue() /*-{ return this.configured_value }-*/;
+ public final native String summary() /*-{ return this.summary; }-*/;
+
protected MaxObjectSizeLimitInfo() {}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java
index ac89180..3576b12 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java
@@ -14,14 +14,14 @@
package com.google.gerrit.client.ui;
+import static java.util.Comparator.comparing;
+
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.projects.ProjectInfo;
import com.google.gerrit.client.projects.ProjectMap;
import com.google.gerrit.client.rpc.Natives;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.Image;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
public class ProjectsTable extends NavigationTable<ProjectInfo> {
@@ -69,14 +69,7 @@
}
List<ProjectInfo> list = Natives.asList(projects.values());
- Collections.sort(
- list,
- new Comparator<ProjectInfo>() {
- @Override
- public int compare(ProjectInfo a, ProjectInfo b) {
- return a.name().compareTo(b.name());
- }
- });
+ list.sort(comparing(ProjectInfo::name));
for (ProjectInfo p : list.subList(fromIndex, toIndex)) {
insert(table.getRowCount(), p);
}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java b/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
index c6f113e..9df066d 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
@@ -14,6 +14,8 @@
package net.codemirror.mode;
+import static java.util.Comparator.comparing;
+
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.Natives;
import com.google.gwt.core.client.JavaScriptObject;
@@ -21,8 +23,6 @@
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.resources.client.DataResource;
import com.google.gwt.safehtml.shared.SafeUri;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
@@ -242,14 +242,7 @@
byMime.put(m.mode(), m);
}
}
- Collections.sort(
- Natives.asList(filtered),
- new Comparator<ModeInfo>() {
- @Override
- public int compare(ModeInfo a, ModeInfo b) {
- return a.name().toLowerCase().compareTo(b.name().toLowerCase());
- }
- });
+ Natives.asList(filtered).sort(comparing(m -> m.name().toLowerCase()));
setAll(filtered);
}
diff --git a/java/Main.java b/java/Main.java
index f26b6df..11d8234 100644
--- a/java/Main.java
+++ b/java/Main.java
@@ -14,6 +14,7 @@
public final class Main {
private static final String FLOGGER_BACKEND_PROPERTY = "flogger.backend_factory";
+ private static final String FLOGGER_LOGGING_CONTEXT = "flogger.logging_context";
// We don't do any real work here because we need to import
// the archive lookup code and we cannot import a class in
@@ -42,6 +43,9 @@
}
private static void configureFloggerBackend() {
+ System.setProperty(
+ FLOGGER_LOGGING_CONTEXT, "com.google.gerrit.server.logging.LoggingContext#getInstance");
+
if (System.getProperty(FLOGGER_BACKEND_PROPERTY) != null) {
// Flogger backend is already configured
return;
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 1d87880..69d603f 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -1487,12 +1487,7 @@
assertNotifyTo(expected.email, expected.fullName);
}
- protected void assertNotifyTo(
- com.google.gerrit.acceptance.testsuite.account.TestAccount expected) {
- assertNotifyTo(expected.preferredEmail().orElse(null), expected.fullname().orElse(null));
- }
-
- private void assertNotifyTo(String expectedEmail, String expectedFullname) {
+ protected void assertNotifyTo(String expectedEmail, String expectedFullname) {
Address expectedAddress = new Address(expectedFullname, expectedEmail);
assertThat(sender.getMessages()).hasSize(1);
Message m = sender.getMessages().get(0);
@@ -1506,11 +1501,6 @@
assertNotifyCc(expected.emailAddress);
}
- protected void assertNotifyCc(
- com.google.gerrit.acceptance.testsuite.account.TestAccount expected) {
- assertNotifyCc(expected.preferredEmail().orElse(null), expected.fullname().orElse(null));
- }
-
protected void assertNotifyCc(String expectedEmail, String expectedFullname) {
Address expectedAddress = new Address(expectedFullname, expectedEmail);
assertNotifyCc(expectedAddress);
@@ -1533,13 +1523,10 @@
assertThat(m.headers().get("Cc").isEmpty()).isTrue();
}
- protected void assertNotifyBcc(
- com.google.gerrit.acceptance.testsuite.account.TestAccount expected) {
+ protected void assertNotifyBcc(String expectedEmail, String expectedFullName) {
assertThat(sender.getMessages()).hasSize(1);
Message m = sender.getMessages().get(0);
- assertThat(m.rcpt())
- .containsExactly(
- new Address(expected.fullname().orElse(null), expected.preferredEmail().orElse(null)));
+ assertThat(m.rcpt()).containsExactly(new Address(expectedFullName, expectedEmail));
assertThat(m.headers().get("To").isEmpty()).isTrue();
assertThat(m.headers().get("Cc").isEmpty()).isTrue();
}
diff --git a/java/com/google/gerrit/acceptance/EventRecorder.java b/java/com/google/gerrit/acceptance/EventRecorder.java
index f9f95b5..5654c35 100644
--- a/java/com/google/gerrit/acceptance/EventRecorder.java
+++ b/java/com/google/gerrit/acceptance/EventRecorder.java
@@ -63,6 +63,7 @@
eventListenerRegistration =
eventListeners.add(
+ "gerrit",
new UserScopedEventListener() {
@Override
public void onEvent(Event e) {
@@ -137,6 +138,10 @@
return events;
}
+ public void assertNoRefUpdatedEvents(String project, String branch) throws Exception {
+ getRefUpdatedEvents(project, branch, 0);
+ }
+
public void assertRefUpdatedEvents(String project, String branch, String... expected)
throws Exception {
ImmutableList<RefUpdatedEvent> events =
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index 6e5424c..582c7cb 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -24,6 +24,8 @@
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
import com.google.gerrit.acceptance.testsuite.account.AccountOperationsImpl;
+import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
+import com.google.gerrit.acceptance.testsuite.group.GroupOperationsImpl;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.lucene.LuceneIndexModule;
@@ -441,6 +443,7 @@
bindConstant().annotatedWith(SshEnabled.class).to(daemon.getEnableSshd());
bind(AccountCreator.class);
bind(AccountOperations.class).to(AccountOperationsImpl.class);
+ bind(GroupOperations.class).to(GroupOperationsImpl.class);
factory(PushOneCommit.Factory.class);
install(InProcessProtocol.module());
install(new NoSshModule());
diff --git a/java/com/google/gerrit/acceptance/HttpResponse.java b/java/com/google/gerrit/acceptance/HttpResponse.java
index b62e932..3e98d71 100644
--- a/java/com/google/gerrit/acceptance/HttpResponse.java
+++ b/java/com/google/gerrit/acceptance/HttpResponse.java
@@ -14,11 +14,16 @@
package com.google.gerrit.acceptance;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.ByteBuffer;
+import java.util.Arrays;
import org.apache.http.Header;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
@@ -34,7 +39,7 @@
public Reader getReader() throws IllegalStateException, IOException {
if (reader == null && response.getEntity() != null) {
- reader = new InputStreamReader(response.getEntity().getContent());
+ reader = new InputStreamReader(response.getEntity().getContent(), UTF_8);
}
return reader;
}
@@ -59,6 +64,13 @@
return hdr != null ? hdr.getValue() : null;
}
+ public ImmutableList<String> getHeaders(String name) {
+ return Arrays.asList(response.getHeaders(name))
+ .stream()
+ .map(Header::getValue)
+ .collect(toImmutableList());
+ }
+
public boolean hasContent() {
Preconditions.checkNotNull(response, "Response is not initialized.");
return response.getEntity() != null;
diff --git a/java/com/google/gerrit/acceptance/SshSession.java b/java/com/google/gerrit/acceptance/SshSession.java
index 27dae3b..52d7f28 100644
--- a/java/com/google/gerrit/acceptance/SshSession.java
+++ b/java/com/google/gerrit/acceptance/SshSession.java
@@ -17,6 +17,7 @@
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
import com.jcraft.jsch.ChannelExec;
@@ -54,10 +55,10 @@
InputStream err = channel.getErrStream();
channel.connect();
- Scanner s = new Scanner(err).useDelimiter("\\A");
+ Scanner s = new Scanner(err, UTF_8.name()).useDelimiter("\\A");
error = s.hasNext() ? s.next() : null;
- s = new Scanner(in).useDelimiter("\\A");
+ s = new Scanner(in, UTF_8.name()).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
} finally {
channel.disconnect();
diff --git a/java/com/google/gerrit/acceptance/testsuite/ThrowingConsumer.java b/java/com/google/gerrit/acceptance/testsuite/ThrowingConsumer.java
new file mode 100644
index 0000000..5efdc81
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/testsuite/ThrowingConsumer.java
@@ -0,0 +1,20 @@
+// Copyright (C) 2018 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.testsuite;
+
+@FunctionalInterface
+public interface ThrowingConsumer<T> {
+ void accept(T t) throws Exception;
+}
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperations.java b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperations.java
index 58a00d0..61b7599 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperations.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperations.java
@@ -42,7 +42,7 @@
* <p>Example:
*
* <pre>
- * TestAccount createdAccount = accountOperations
+ * Account.Id createdAccountId = accountOperations
* .newAccount()
* .username("janedoe")
* .preferredEmail("janedoe@example.com")
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
index 3d741b0..ebbcfe4 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
@@ -59,12 +59,12 @@
return TestAccountCreation.builder(this::createAccount);
}
- private TestAccount createAccount(TestAccountCreation accountCreation) throws Exception {
+ private Account.Id createAccount(TestAccountCreation accountCreation) throws Exception {
AccountsUpdate.AccountUpdater accountUpdater =
(account, updateBuilder) ->
fillBuilder(updateBuilder, accountCreation, account.getAccount().getId());
AccountState createdAccount = createAccount(accountUpdater);
- return toTestAccount(createdAccount);
+ return createdAccount.getAccount().getId();
}
private AccountState createAccount(AccountsUpdate.AccountUpdater accountUpdater)
@@ -85,17 +85,6 @@
accountCreation.active().ifPresent(builder::setActive);
}
- private static TestAccount toTestAccount(AccountState accountState) {
- Account createdAccount = accountState.getAccount();
- return TestAccount.builder()
- .accountId(createdAccount.getId())
- .preferredEmail(Optional.ofNullable(createdAccount.getPreferredEmail()))
- .fullname(Optional.ofNullable(createdAccount.getFullName()))
- .username(accountState.getUserName())
- .active(accountState.getAccount().isActive())
- .build();
- }
-
private static InternalAccountUpdate.Builder setPreferredEmail(
InternalAccountUpdate.Builder builder, Account.Id accountId, String preferredEmail) {
return builder
@@ -133,18 +122,28 @@
return toTestAccount(account);
}
+ private TestAccount toTestAccount(AccountState accountState) {
+ Account account = accountState.getAccount();
+ return TestAccount.builder()
+ .accountId(account.getId())
+ .preferredEmail(Optional.ofNullable(account.getPreferredEmail()))
+ .fullname(Optional.ofNullable(account.getFullName()))
+ .username(accountState.getUserName())
+ .active(accountState.getAccount().isActive())
+ .build();
+ }
+
@Override
public TestAccountUpdate.Builder forUpdate() {
return TestAccountUpdate.builder(this::updateAccount);
}
- private TestAccount updateAccount(TestAccountUpdate accountUpdate)
+ private void updateAccount(TestAccountUpdate accountUpdate)
throws OrmException, IOException, ConfigInvalidException {
AccountsUpdate.AccountUpdater accountUpdater =
(account, updateBuilder) -> fillBuilder(updateBuilder, accountUpdate, accountId);
Optional<AccountState> updatedAccount = updateAccount(accountUpdater);
checkState(updatedAccount.isPresent(), "Tried to update non-existing test account");
- return toTestAccount(updatedAccount.get());
}
private Optional<AccountState> updateAccount(AccountsUpdate.AccountUpdater accountUpdater)
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java b/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java
index a82d180..ab32409 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java
@@ -16,6 +16,7 @@
import com.google.auto.value.AutoValue;
import com.google.gerrit.acceptance.testsuite.ThrowingFunction;
+import com.google.gerrit.reviewdb.client.Account;
import java.util.Optional;
@AutoValue
@@ -32,9 +33,9 @@
public abstract Optional<Boolean> active();
- abstract ThrowingFunction<TestAccountCreation, TestAccount> accountCreator();
+ abstract ThrowingFunction<TestAccountCreation, Account.Id> accountCreator();
- public static Builder builder(ThrowingFunction<TestAccountCreation, TestAccount> accountCreator) {
+ public static Builder builder(ThrowingFunction<TestAccountCreation, Account.Id> accountCreator) {
return new AutoValue_TestAccountCreation.Builder()
.accountCreator(accountCreator)
.httpPassword("http-pass");
@@ -83,11 +84,11 @@
}
abstract Builder accountCreator(
- ThrowingFunction<TestAccountCreation, TestAccount> accountCreator);
+ ThrowingFunction<TestAccountCreation, Account.Id> accountCreator);
abstract TestAccountCreation autoBuild();
- public TestAccount create() throws Exception {
+ public Account.Id create() throws Exception {
TestAccountCreation accountUpdate = autoBuild();
return accountUpdate.accountCreator().apply(accountUpdate);
}
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/TestAccountUpdate.java b/java/com/google/gerrit/acceptance/testsuite/account/TestAccountUpdate.java
index 517e4b5..251f452 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/TestAccountUpdate.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/TestAccountUpdate.java
@@ -15,7 +15,7 @@
package com.google.gerrit.acceptance.testsuite.account;
import com.google.auto.value.AutoValue;
-import com.google.gerrit.acceptance.testsuite.ThrowingFunction;
+import com.google.gerrit.acceptance.testsuite.ThrowingConsumer;
import java.util.Optional;
@AutoValue
@@ -32,9 +32,9 @@
public abstract Optional<Boolean> active();
- abstract ThrowingFunction<TestAccountUpdate, TestAccount> accountUpdater();
+ abstract ThrowingConsumer<TestAccountUpdate> accountUpdater();
- public static Builder builder(ThrowingFunction<TestAccountUpdate, TestAccount> accountUpdater) {
+ public static Builder builder(ThrowingConsumer<TestAccountUpdate> accountUpdater) {
return new AutoValue_TestAccountUpdate.Builder()
.accountUpdater(accountUpdater)
.httpPassword("http-pass");
@@ -82,14 +82,13 @@
return active(false);
}
- abstract Builder accountUpdater(
- ThrowingFunction<TestAccountUpdate, TestAccount> accountUpdater);
+ abstract Builder accountUpdater(ThrowingConsumer<TestAccountUpdate> accountUpdater);
abstract TestAccountUpdate autoBuild();
- public TestAccount update() throws Exception {
+ public void update() throws Exception {
TestAccountUpdate accountUpdate = autoBuild();
- return accountUpdate.accountUpdater().apply(accountUpdate);
+ accountUpdate.accountUpdater().accept(accountUpdate);
}
}
}
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/GroupOperations.java b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperations.java
new file mode 100644
index 0000000..f75ca2e
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperations.java
@@ -0,0 +1,97 @@
+// Copyright (C) 2018 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.testsuite.group;
+
+import com.google.gerrit.reviewdb.client.AccountGroup;
+
+/**
+ * An aggregation of operations on groups for test purposes.
+ *
+ * <p>To execute the operations, no Gerrit permissions are necessary.
+ *
+ * <p><strong>Note:</strong> This interface is not implemented using the REST or extension API.
+ * Hence, it cannot be used for testing those APIs.
+ */
+public interface GroupOperations {
+ /**
+ * Starts the fluent chain for querying or modifying a group. Please see the methods of {@link
+ * MoreGroupOperations} for details on possible operations.
+ *
+ * @return an aggregation of operations on a specific group
+ */
+ MoreGroupOperations group(AccountGroup.UUID groupUuid);
+
+ /**
+ * Starts the fluent chain to create a group. The returned builder can be used to specify the
+ * attributes of the new group. To create the group for real, {@link
+ * TestGroupCreation.Builder#create()} must be called.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * AccountGroup.UUID createdGroupUuid = groupOperations
+ * .newGroup()
+ * .name("verifiers")
+ * .description("All verifiers of this server")
+ * .create();
+ * </pre>
+ *
+ * <p><strong>Note:</strong> If another group with the provided name already exists, the creation
+ * of the group will fail.
+ *
+ * @return a builder to create the new group
+ */
+ TestGroupCreation.Builder newGroup();
+
+ /** An aggregation of methods on a specific group. */
+ interface MoreGroupOperations {
+
+ /**
+ * Checks whether the group exists.
+ *
+ * @return {@code true} if the group exists
+ */
+ boolean exists() throws Exception;
+
+ /**
+ * Retrieves the group.
+ *
+ * <p><strong>Note:</strong> This call will fail with an exception if the requested group
+ * doesn't exist. If you want to check for the existence of a group, use {@link #exists()}
+ * instead.
+ *
+ * @return the corresponding {@code TestGroup}
+ */
+ TestGroup get() throws Exception;
+
+ /**
+ * Starts the fluent chain to update a group. The returned builder can be used to specify how
+ * the attributes of the group should be modified. To update the group for real, {@link
+ * TestGroupUpdate.Builder#update()} must be called.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * groupOperations.forUpdate().description("Another description for this group").update();
+ * </pre>
+ *
+ * <p><strong>Note:</strong> The update will fail with an exception if the group to update
+ * doesn't exist. If you want to check for the existence of a group, use {@link #exists()}.
+ *
+ * @return a builder to update the group
+ */
+ TestGroupUpdate.Builder forUpdate();
+ }
+}
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java
new file mode 100644
index 0000000..f9769c5
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java
@@ -0,0 +1,159 @@
+// Copyright (C) 2018 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.testsuite.group;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.Sequences;
+import com.google.gerrit.server.ServerInitiated;
+import com.google.gerrit.server.account.GroupUUID;
+import com.google.gerrit.server.group.InternalGroup;
+import com.google.gerrit.server.group.db.Groups;
+import com.google.gerrit.server.group.db.GroupsUpdate;
+import com.google.gerrit.server.group.db.InternalGroupCreation;
+import com.google.gerrit.server.group.db.InternalGroupUpdate;
+import com.google.gwtorm.server.OrmDuplicateKeyException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.util.Optional;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.PersonIdent;
+
+/**
+ * The implementation of {@code GroupOperations}.
+ *
+ * <p>There is only one implementation of {@code GroupOperations}. Nevertheless, we keep the
+ * separation between interface and implementation to enhance clarity.
+ */
+public class GroupOperationsImpl implements GroupOperations {
+ private final Groups groups;
+ private final GroupsUpdate groupsUpdate;
+ private final Sequences seq;
+ private final PersonIdent serverIdent;
+
+ @Inject
+ public GroupOperationsImpl(
+ Groups groups,
+ @ServerInitiated GroupsUpdate groupsUpdate,
+ Sequences seq,
+ @GerritPersonIdent PersonIdent serverIdent) {
+ this.groups = groups;
+ this.groupsUpdate = groupsUpdate;
+ this.seq = seq;
+ this.serverIdent = serverIdent;
+ }
+
+ @Override
+ public MoreGroupOperations group(AccountGroup.UUID groupUuid) {
+ return new MoreGroupOperationsImpl(groupUuid);
+ }
+
+ @Override
+ public TestGroupCreation.Builder newGroup() {
+ return TestGroupCreation.builder(this::createNewGroup);
+ }
+
+ private AccountGroup.UUID createNewGroup(TestGroupCreation groupCreation)
+ throws ConfigInvalidException, IOException, OrmException {
+ InternalGroupCreation internalGroupCreation = toInternalGroupCreation(groupCreation);
+ InternalGroupUpdate internalGroupUpdate = toInternalGroupUpdate(groupCreation);
+ InternalGroup internalGroup =
+ groupsUpdate.createGroup(internalGroupCreation, internalGroupUpdate);
+ return internalGroup.getGroupUUID();
+ }
+
+ private InternalGroupCreation toInternalGroupCreation(TestGroupCreation groupCreation)
+ throws OrmException {
+ AccountGroup.Id groupId = new AccountGroup.Id(seq.nextGroupId());
+ String groupName = groupCreation.name().orElse("group-with-id-" + groupId.get());
+ AccountGroup.UUID groupUuid = GroupUUID.make(groupName, serverIdent);
+ AccountGroup.NameKey nameKey = new AccountGroup.NameKey(groupName);
+ return InternalGroupCreation.builder()
+ .setId(groupId)
+ .setGroupUUID(groupUuid)
+ .setNameKey(nameKey)
+ .build();
+ }
+
+ private static InternalGroupUpdate toInternalGroupUpdate(TestGroupCreation groupCreation) {
+ InternalGroupUpdate.Builder builder = InternalGroupUpdate.builder();
+ groupCreation.description().ifPresent(builder::setDescription);
+ groupCreation.ownerGroupUuid().ifPresent(builder::setOwnerGroupUUID);
+ groupCreation.visibleToAll().ifPresent(builder::setVisibleToAll);
+ builder.setMemberModification(originalMembers -> groupCreation.members());
+ builder.setSubgroupModification(originalSubgroups -> groupCreation.subgroups());
+ return builder.build();
+ }
+
+ private class MoreGroupOperationsImpl implements MoreGroupOperations {
+ private final AccountGroup.UUID groupUuid;
+
+ MoreGroupOperationsImpl(AccountGroup.UUID groupUuid) {
+ this.groupUuid = groupUuid;
+ }
+
+ @Override
+ public boolean exists() throws Exception {
+ return groups.getGroup(groupUuid).isPresent();
+ }
+
+ @Override
+ public TestGroup get() throws Exception {
+ Optional<InternalGroup> group = groups.getGroup(groupUuid);
+ checkState(group.isPresent(), "Tried to get non-existing test group");
+ return toTestGroup(group.get());
+ }
+
+ private TestGroup toTestGroup(InternalGroup internalGroup) {
+ return TestGroup.builder()
+ .groupUuid(internalGroup.getGroupUUID())
+ .groupId(internalGroup.getId())
+ .nameKey(internalGroup.getNameKey())
+ .description(Optional.ofNullable(internalGroup.getDescription()))
+ .ownerGroupUuid(internalGroup.getOwnerGroupUUID())
+ .visibleToAll(internalGroup.isVisibleToAll())
+ .createdOn(internalGroup.getCreatedOn())
+ .members(internalGroup.getMembers())
+ .subgroups(internalGroup.getSubgroups())
+ .build();
+ }
+
+ @Override
+ public TestGroupUpdate.Builder forUpdate() {
+ return TestGroupUpdate.builder(this::updateGroup);
+ }
+
+ private void updateGroup(TestGroupUpdate groupUpdate)
+ throws OrmDuplicateKeyException, NoSuchGroupException, ConfigInvalidException, IOException {
+ InternalGroupUpdate internalGroupUpdate = toInternalGroupUpdate(groupUpdate);
+ groupsUpdate.updateGroup(groupUuid, internalGroupUpdate);
+ }
+
+ private InternalGroupUpdate toInternalGroupUpdate(TestGroupUpdate groupUpdate) {
+ InternalGroupUpdate.Builder builder = InternalGroupUpdate.builder();
+ groupUpdate.name().map(AccountGroup.NameKey::new).ifPresent(builder::setName);
+ groupUpdate.description().ifPresent(builder::setDescription);
+ groupUpdate.ownerGroupUuid().ifPresent(builder::setOwnerGroupUUID);
+ groupUpdate.visibleToAll().ifPresent(builder::setVisibleToAll);
+ builder.setMemberModification(groupUpdate.memberModification()::apply);
+ builder.setSubgroupModification(groupUpdate.subgroupModification()::apply);
+ return builder.build();
+ }
+ }
+}
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/TestGroup.java b/java/com/google/gerrit/acceptance/testsuite/group/TestGroup.java
new file mode 100644
index 0000000..b450304
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/testsuite/group/TestGroup.java
@@ -0,0 +1,78 @@
+// Copyright (C) 2018 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.testsuite.group;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import java.sql.Timestamp;
+import java.util.Optional;
+
+@AutoValue
+public abstract class TestGroup {
+
+ public abstract AccountGroup.UUID groupUuid();
+
+ public abstract AccountGroup.Id groupId();
+
+ public String name() {
+ return nameKey().get();
+ }
+
+ public abstract AccountGroup.NameKey nameKey();
+
+ public abstract Optional<String> description();
+
+ public abstract AccountGroup.UUID ownerGroupUuid();
+
+ public abstract boolean visibleToAll();
+
+ public abstract Timestamp createdOn();
+
+ public abstract ImmutableSet<Account.Id> members();
+
+ public abstract ImmutableSet<AccountGroup.UUID> subgroups();
+
+ static Builder builder() {
+ return new AutoValue_TestGroup.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+
+ public abstract Builder groupUuid(AccountGroup.UUID groupUuid);
+
+ public abstract Builder groupId(AccountGroup.Id id);
+
+ public abstract Builder nameKey(AccountGroup.NameKey name);
+
+ public abstract Builder description(String description);
+
+ public abstract Builder description(Optional<String> description);
+
+ public abstract Builder ownerGroupUuid(AccountGroup.UUID ownerGroupUuid);
+
+ public abstract Builder visibleToAll(boolean visibleToAll);
+
+ public abstract Builder createdOn(Timestamp createdOn);
+
+ public abstract Builder members(ImmutableSet<Account.Id> members);
+
+ public abstract Builder subgroups(ImmutableSet<AccountGroup.UUID> subgroups);
+
+ abstract TestGroup build();
+ }
+}
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java
new file mode 100644
index 0000000..efed720
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2018 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.testsuite.group;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.gerrit.acceptance.testsuite.ThrowingFunction;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import java.util.Optional;
+import java.util.Set;
+
+@AutoValue
+public abstract class TestGroupCreation {
+
+ public abstract Optional<String> name();
+
+ public abstract Optional<String> description();
+
+ public abstract Optional<AccountGroup.UUID> ownerGroupUuid();
+
+ public abstract Optional<Boolean> visibleToAll();
+
+ public abstract ImmutableSet<Account.Id> members();
+
+ public abstract ImmutableSet<AccountGroup.UUID> subgroups();
+
+ abstract ThrowingFunction<TestGroupCreation, AccountGroup.UUID> groupCreator();
+
+ public static Builder builder(
+ ThrowingFunction<TestGroupCreation, AccountGroup.UUID> groupCreator) {
+ return new AutoValue_TestGroupCreation.Builder().groupCreator(groupCreator);
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract Builder name(String name);
+
+ public abstract Builder description(String description);
+
+ public Builder clearDescription() {
+ return description("");
+ }
+
+ public abstract Builder ownerGroupUuid(AccountGroup.UUID ownerGroupUuid);
+
+ public abstract Builder visibleToAll(boolean visibleToAll);
+
+ public Builder clearMembers() {
+ return members(ImmutableSet.of());
+ }
+
+ public Builder members(Account.Id member1, Account.Id... otherMembers) {
+ return members(Sets.union(ImmutableSet.of(member1), ImmutableSet.copyOf(otherMembers)));
+ }
+
+ public abstract Builder members(Set<Account.Id> members);
+
+ abstract ImmutableSet.Builder<Account.Id> membersBuilder();
+
+ public Builder addMember(Account.Id member) {
+ membersBuilder().add(member);
+ return this;
+ }
+
+ public Builder clearSubgroups() {
+ return subgroups(ImmutableSet.of());
+ }
+
+ public Builder subgroups(AccountGroup.UUID subgroup1, AccountGroup.UUID... otherSubgroups) {
+ return subgroups(Sets.union(ImmutableSet.of(subgroup1), ImmutableSet.copyOf(otherSubgroups)));
+ }
+
+ public abstract Builder subgroups(Set<AccountGroup.UUID> subgroups);
+
+ abstract ImmutableSet.Builder<AccountGroup.UUID> subgroupsBuilder();
+
+ public Builder addSubgroup(AccountGroup.UUID subgroup) {
+ subgroupsBuilder().add(subgroup);
+ return this;
+ }
+
+ abstract Builder groupCreator(
+ ThrowingFunction<TestGroupCreation, AccountGroup.UUID> groupCreator);
+
+ abstract TestGroupCreation autoBuild();
+
+ /**
+ * Executes the group creation as specified.
+ *
+ * @return the UUID of the created group
+ */
+ public AccountGroup.UUID create() throws Exception {
+ TestGroupCreation groupCreation = autoBuild();
+ return groupCreation.groupCreator().apply(groupCreation);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java
new file mode 100644
index 0000000..095a270
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java
@@ -0,0 +1,134 @@
+// Copyright (C) 2018 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.testsuite.group;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.gerrit.acceptance.testsuite.ThrowingConsumer;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+
+@AutoValue
+public abstract class TestGroupUpdate {
+
+ public abstract Optional<String> name();
+
+ public abstract Optional<String> description();
+
+ public abstract Optional<AccountGroup.UUID> ownerGroupUuid();
+
+ public abstract Optional<Boolean> visibleToAll();
+
+ public abstract Function<ImmutableSet<Account.Id>, Set<Account.Id>> memberModification();
+
+ public abstract Function<ImmutableSet<AccountGroup.UUID>, Set<AccountGroup.UUID>>
+ subgroupModification();
+
+ abstract ThrowingConsumer<TestGroupUpdate> groupUpdater();
+
+ public static Builder builder(ThrowingConsumer<TestGroupUpdate> groupUpdater) {
+ return new AutoValue_TestGroupUpdate.Builder()
+ .groupUpdater(groupUpdater)
+ .memberModification(in -> in)
+ .subgroupModification(in -> in);
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract Builder name(String name);
+
+ public abstract Builder description(String description);
+
+ public Builder clearDescription() {
+ return description("");
+ }
+
+ public abstract Builder ownerGroupUuid(AccountGroup.UUID ownerGroupUUID);
+
+ public abstract Builder visibleToAll(boolean visibleToAll);
+
+ abstract Builder memberModification(
+ Function<ImmutableSet<Account.Id>, Set<Account.Id>> memberModification);
+
+ abstract Function<ImmutableSet<Account.Id>, Set<Account.Id>> memberModification();
+
+ public Builder clearMembers() {
+ return memberModification(originalMembers -> ImmutableSet.of());
+ }
+
+ public Builder addMember(Account.Id member) {
+ Function<ImmutableSet<Account.Id>, Set<Account.Id>> previousModification =
+ memberModification();
+ memberModification(
+ originalMembers ->
+ Sets.union(previousModification.apply(originalMembers), ImmutableSet.of(member)));
+ return this;
+ }
+
+ public Builder removeMember(Account.Id member) {
+ Function<ImmutableSet<Account.Id>, Set<Account.Id>> previousModification =
+ memberModification();
+ memberModification(
+ originalMembers ->
+ Sets.difference(
+ previousModification.apply(originalMembers), ImmutableSet.of(member)));
+ return this;
+ }
+
+ abstract Builder subgroupModification(
+ Function<ImmutableSet<AccountGroup.UUID>, Set<AccountGroup.UUID>> subgroupModification);
+
+ abstract Function<ImmutableSet<AccountGroup.UUID>, Set<AccountGroup.UUID>>
+ subgroupModification();
+
+ public Builder clearSubgroups() {
+ return subgroupModification(originalMembers -> ImmutableSet.of());
+ }
+
+ public Builder addSubgroup(AccountGroup.UUID subgroup) {
+ Function<ImmutableSet<AccountGroup.UUID>, Set<AccountGroup.UUID>> previousModification =
+ subgroupModification();
+ subgroupModification(
+ originalSubgroups ->
+ Sets.union(previousModification.apply(originalSubgroups), ImmutableSet.of(subgroup)));
+ return this;
+ }
+
+ public Builder removeSubgroup(AccountGroup.UUID subgroup) {
+ Function<ImmutableSet<AccountGroup.UUID>, Set<AccountGroup.UUID>> previousModification =
+ subgroupModification();
+ subgroupModification(
+ originalSubgroups ->
+ Sets.difference(
+ previousModification.apply(originalSubgroups), ImmutableSet.of(subgroup)));
+ return this;
+ }
+
+ abstract Builder groupUpdater(ThrowingConsumer<TestGroupUpdate> groupUpdater);
+
+ abstract TestGroupUpdate autoBuild();
+
+ /** Executes the group update as specified. */
+ public void update() throws Exception {
+ TestGroupUpdate groupUpdater = autoBuild();
+ groupUpdater.groupUpdater().accept(groupUpdater);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/common/data/LabelType.java b/java/com/google/gerrit/common/data/LabelType.java
index 7bfd22e..ff7d25b 100644
--- a/java/com/google/gerrit/common/data/LabelType.java
+++ b/java/com/google/gerrit/common/data/LabelType.java
@@ -14,16 +14,18 @@
package com.google.gerrit.common.data;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.toList;
+
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.LabelId;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
public class LabelType {
public static final boolean DEF_ALLOW_POST_SUBMIT = true;
@@ -34,6 +36,7 @@
public static final boolean DEF_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE = false;
public static final boolean DEF_COPY_MAX_SCORE = false;
public static final boolean DEF_COPY_MIN_SCORE = false;
+ public static final boolean DEF_IGNORE_SELF_APPROVAL = false;
public static LabelType withDefaultValues(String name) {
checkName(name);
@@ -70,22 +73,13 @@
private static List<LabelValue> sortValues(List<LabelValue> values) {
values = new ArrayList<>(values);
- if (values.size() <= 1) {
- return Collections.unmodifiableList(values);
+ if (values.isEmpty()) {
+ return Collections.emptyList();
}
- Collections.sort(
- values,
- new Comparator<LabelValue>() {
- @Override
- public int compare(LabelValue o1, LabelValue o2) {
- return o1.getValue() - o2.getValue();
- }
- });
- short min = values.get(0).getValue();
- short max = values.get(values.size() - 1).getValue();
- short v = min;
+ values = values.stream().sorted(comparing(LabelValue::getValue)).collect(toList());
+ short v = values.get(0).getValue();
short i = 0;
- List<LabelValue> result = new ArrayList<>(max - min + 1);
+ ArrayList<LabelValue> result = new ArrayList<>();
// Fill in any missing values with empty text.
while (i < values.size()) {
while (v < values.get(i).getValue()) {
@@ -94,6 +88,7 @@
v++;
result.add(values.get(i++));
}
+ result.trimToSize();
return Collections.unmodifiableList(result);
}
@@ -109,6 +104,7 @@
protected boolean copyAllScoresIfNoCodeChange;
protected boolean copyAllScoresIfNoChange;
protected boolean allowPostSubmit;
+ protected boolean ignoreSelfApproval;
protected short defaultValue;
protected List<LabelValue> values;
@@ -117,7 +113,6 @@
private transient boolean canOverride;
private transient List<String> refPatterns;
- private transient List<Integer> intList;
private transient Map<Short, LabelValue> byValue;
protected LabelType() {}
@@ -148,6 +143,12 @@
setCopyMaxScore(DEF_COPY_MAX_SCORE);
setCopyMinScore(DEF_COPY_MIN_SCORE);
setAllowPostSubmit(DEF_ALLOW_POST_SUBMIT);
+ setIgnoreSelfApproval(DEF_IGNORE_SELF_APPROVAL);
+
+ byValue = new HashMap<>();
+ for (LabelValue v : values) {
+ byValue.put(v.getValue(), v);
+ }
}
public String getName() {
@@ -162,11 +163,8 @@
if (functionName == null) {
return null;
}
- Optional<LabelFunction> f = LabelFunction.parse(functionName);
- if (!f.isPresent()) {
- throw new IllegalStateException("Unsupported functionName: " + functionName);
- }
- return f.get();
+ return LabelFunction.parse(functionName)
+ .orElseThrow(() -> new IllegalStateException("Unsupported functionName: " + functionName));
}
public void setFunction(@Nullable LabelFunction function) {
@@ -193,8 +191,21 @@
this.allowPostSubmit = allowPostSubmit;
}
+ public boolean ignoreSelfApproval() {
+ return ignoreSelfApproval;
+ }
+
+ public void setIgnoreSelfApproval(boolean ignoreSelfApproval) {
+ this.ignoreSelfApproval = ignoreSelfApproval;
+ }
+
public void setRefPatterns(List<String> refPatterns) {
- this.refPatterns = refPatterns;
+ if (refPatterns != null) {
+ this.refPatterns =
+ refPatterns.stream().collect(collectingAndThen(toList(), Collections::unmodifiableList));
+ } else {
+ this.refPatterns = null;
+ }
}
public List<LabelValue> getValues() {
@@ -281,36 +292,13 @@
}
public LabelValue getValue(short value) {
- initByValue();
return byValue.get(value);
}
public LabelValue getValue(PatchSetApproval ca) {
- initByValue();
return byValue.get(ca.getValue());
}
- private void initByValue() {
- if (byValue == null) {
- byValue = new HashMap<>();
- for (LabelValue v : values) {
- byValue.put(v.getValue(), v);
- }
- }
- }
-
- public List<Integer> getValuesAsList() {
- if (intList == null) {
- intList = new ArrayList<>(values.size());
- for (LabelValue v : values) {
- intList.add(Integer.valueOf(v.getValue()));
- }
- Collections.sort(intList);
- Collections.reverse(intList);
- }
- return intList;
- }
-
public LabelId getLabelId() {
return new LabelId(name);
}
diff --git a/java/com/google/gerrit/common/data/LabelValue.java b/java/com/google/gerrit/common/data/LabelValue.java
index 811e751..c0ba781 100644
--- a/java/com/google/gerrit/common/data/LabelValue.java
+++ b/java/com/google/gerrit/common/data/LabelValue.java
@@ -14,6 +14,8 @@
package com.google.gerrit.common.data;
+import java.util.Objects;
+
public class LabelValue {
public static String formatValue(short value) {
if (value < 0) {
@@ -56,6 +58,20 @@
}
@Override
+ public boolean equals(Object o) {
+ if (!(o instanceof LabelValue)) {
+ return false;
+ }
+ LabelValue v = (LabelValue) o;
+ return value == v.value && Objects.equals(text, v.text);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value, text);
+ }
+
+ @Override
public String toString() {
return format();
}
diff --git a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
index f322c3d..2c1c93a 100644
--- a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
+++ b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -71,6 +71,7 @@
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.nio.entity.NStringEntity;
+import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
@@ -98,7 +99,7 @@
String content = "";
if (responseEntity != null) {
InputStream contentStream = responseEntity.getContent();
- try (Reader reader = new InputStreamReader(contentStream)) {
+ try (Reader reader = new InputStreamReader(contentStream, UTF_8)) {
content = CharStreams.toString(reader);
}
}
@@ -171,10 +172,10 @@
public void deleteAll() throws IOException {
// Delete the index, if it exists.
String endpoint = indexName + client.adapter().indicesExistParam();
- Response response = client.get().performRequest("HEAD", endpoint);
+ Response response = client.get().performRequest(new Request("HEAD", endpoint));
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
- response = client.get().performRequest("DELETE", indexName);
+ response = client.get().performRequest(new Request("DELETE", indexName));
statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
throw new IOException(
@@ -307,9 +308,13 @@
private Response performRequest(
String method, Object payload, String uri, Map<String, String> params) throws IOException {
+ Request request = new Request(method, uri);
String payloadStr = payload instanceof String ? (String) payload : payload.toString();
- HttpEntity entity = new NStringEntity(payloadStr, ContentType.APPLICATION_JSON);
- return client.get().performRequest(method, uri, params, entity);
+ request.setEntity(new NStringEntity(payloadStr, ContentType.APPLICATION_JSON));
+ for (Map.Entry<String, String> entry : params.entrySet()) {
+ request.addParameter(entry.getKey(), entry.getValue());
+ }
+ return client.get().performRequest(request);
}
protected class ElasticQuerySource implements DataSource<V> {
diff --git a/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
index e36ab2d..a777f47 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
@@ -24,7 +24,7 @@
import java.util.List;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
-import org.apache.http.client.methods.HttpGet;
+import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
@Singleton
@@ -40,10 +40,8 @@
List<String> discover(String prefix, String indexName) throws IOException {
String name = prefix + indexName + "_";
- Response response =
- client
- .get()
- .performRequest(HttpGet.METHOD_NAME, client.adapter().getVersionDiscoveryUrl(name));
+ Request request = new Request("GET", client.adapter().getVersionDiscoveryUrl(name));
+ Response response = client.get().performRequest(request);
StatusLine statusLine = response.getStatusLine();
if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
diff --git a/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java b/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
index 2beb528..8cb69e0 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
@@ -32,13 +32,14 @@
ElasticQueryAdapter(ElasticVersion version) {
this.ignoreUnmapped = version == ElasticVersion.V2_4;
- this.usePostV5Type = isV6(version);
- this.versionDiscoveryUrl = isV6(version) ? "%s*" : "%s*/_aliases";
+ this.usePostV5Type = version.isV6();
+ this.versionDiscoveryUrl = version.isV6() ? "%s*" : "%s*/_aliases";
switch (version) {
case V5_6:
case V6_2:
case V6_3:
+ case V6_4:
this.searchFilteringName = "_source";
this.indicesExistParam = "?allow_no_indices=false";
this.exactFieldType = "keyword";
@@ -58,10 +59,6 @@
}
}
- private boolean isV6(ElasticVersion version) {
- return version == ElasticVersion.V6_2 || version == ElasticVersion.V6_3;
- }
-
void setIgnoreUnmapped(JsonObject properties) {
if (ignoreUnmapped) {
properties.addProperty("ignore_unmapped", true);
diff --git a/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java b/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
index db59257..337f2ca 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
@@ -29,6 +29,7 @@
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
+import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
@@ -39,7 +40,7 @@
private final ElasticConfiguration cfg;
- private RestClient client;
+ private volatile RestClient client;
private ElasticQueryAdapter adapter;
@Inject
@@ -105,7 +106,7 @@
private ElasticVersion getVersion() throws ElasticException {
try {
- Response response = client.performRequest("GET", "");
+ Response response = client.performRequest(new Request("GET", ""));
StatusLine statusLine = response.getStatusLine();
if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
throw new FailedToGetVersion(statusLine);
diff --git a/java/com/google/gerrit/elasticsearch/ElasticVersion.java b/java/com/google/gerrit/elasticsearch/ElasticVersion.java
index 610a212..dfa5d21 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticVersion.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticVersion.java
@@ -21,7 +21,8 @@
V2_4("2.4.*"),
V5_6("5.6.*"),
V6_2("6.2.*"),
- V6_3("6.3.*");
+ V6_3("6.3.*"),
+ V6_4("6.4.*");
private final String version;
private final Pattern pattern;
@@ -31,29 +32,33 @@
this.pattern = Pattern.compile(version);
}
- public static class InvalidVersion extends ElasticException {
+ public static class UnsupportedVersion extends ElasticException {
private static final long serialVersionUID = 1L;
- InvalidVersion(String version) {
+ UnsupportedVersion(String version) {
super(
String.format(
- "Invalid version: [%s]. Supported versions: %s", version, supportedVersions()));
+ "Unsupported version: [%s]. Supported versions: %s", version, supportedVersions()));
}
}
- public static ElasticVersion forVersion(String version) throws InvalidVersion {
+ public static ElasticVersion forVersion(String version) throws UnsupportedVersion {
for (ElasticVersion value : ElasticVersion.values()) {
if (value.pattern.matcher(version).matches()) {
return value;
}
}
- throw new InvalidVersion(version);
+ throw new UnsupportedVersion(version);
}
public static String supportedVersions() {
return Joiner.on(", ").join(ElasticVersion.values());
}
+ public boolean isV6() {
+ return version.startsWith("6.");
+ }
+
@Override
public String toString() {
return version;
diff --git a/java/com/google/gerrit/extensions/annotations/RemoveAfter.java b/java/com/google/gerrit/extensions/annotations/RemoveAfter.java
new file mode 100644
index 0000000..aa31dd0
--- /dev/null
+++ b/java/com/google/gerrit/extensions/annotations/RemoveAfter.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2018 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.annotations;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for features that are deprecated, but still present to adhere to the one-release-grace
+ * period we promised to users.
+ */
+@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
+@Retention(SOURCE)
+@BindingAnnotation
+public @interface RemoveAfter {
+ /**
+ * Version after which the annotated functionality can be removed. Once the referenced version was
+ * branched off, the annotated code can be removed.
+ */
+ String value();
+}
diff --git a/java/com/google/gerrit/extensions/api/access/GerritPermission.java b/java/com/google/gerrit/extensions/api/access/GerritPermission.java
index 133de31..02afbdc 100644
--- a/java/com/google/gerrit/extensions/api/access/GerritPermission.java
+++ b/java/com/google/gerrit/extensions/api/access/GerritPermission.java
@@ -18,7 +18,13 @@
/** Gerrit permission for hosts, projects, refs, changes, labels and plugins. */
public interface GerritPermission {
- /** @return readable identifier of this permission for exception message. */
+ /**
+ * A description in the context of an exception message.
+ *
+ * <p>Should be grammatical when used in the construction "not permitted: [description] on
+ * [resource]", although individual {@code PermissionBackend} implementations may vary the
+ * wording.
+ */
String describeForException();
static String describeEnumValue(Enum<?> value) {
diff --git a/java/com/google/gerrit/extensions/api/access/ProjectAccessInfo.java b/java/com/google/gerrit/extensions/api/access/ProjectAccessInfo.java
index 5d8e950..8273d84 100644
--- a/java/com/google/gerrit/extensions/api/access/ProjectAccessInfo.java
+++ b/java/com/google/gerrit/extensions/api/access/ProjectAccessInfo.java
@@ -29,6 +29,7 @@
public Set<String> ownerOf;
public Boolean canUpload;
public Boolean canAdd;
+ public Boolean canAddTags;
public Boolean configVisible;
public Map<String, GroupInfo> groups;
public List<WebLinkInfo> configWebLinks;
diff --git a/java/com/google/gerrit/extensions/api/changes/IncludedInInfo.java b/java/com/google/gerrit/extensions/api/changes/IncludedInInfo.java
index d876034..8fe47bd 100644
--- a/java/com/google/gerrit/extensions/api/changes/IncludedInInfo.java
+++ b/java/com/google/gerrit/extensions/api/changes/IncludedInInfo.java
@@ -13,6 +13,7 @@
// limitations under the License.
package com.google.gerrit.extensions.api.changes;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -23,9 +24,11 @@
public Map<String, Collection<String>> external;
public IncludedInInfo(
- List<String> branches, List<String> tags, Map<String, Collection<String>> external) {
- this.branches = branches;
- this.tags = tags;
+ Collection<String> branches,
+ Collection<String> tags,
+ Map<String, Collection<String>> external) {
+ this.branches = new ArrayList<>(branches);
+ this.tags = new ArrayList<>(tags);
this.external = external;
}
}
diff --git a/java/com/google/gerrit/extensions/api/projects/CheckProjectInput.java b/java/com/google/gerrit/extensions/api/projects/CheckProjectInput.java
new file mode 100644
index 0000000..145b200
--- /dev/null
+++ b/java/com/google/gerrit/extensions/api/projects/CheckProjectInput.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2018 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.api.projects;
+
+public class CheckProjectInput {
+ public AutoCloseableChangesCheckInput autoCloseableChangesCheck;
+
+ public static class AutoCloseableChangesCheckInput {
+ /** Whether auto-closeable changes should be fixed by setting their status to MERGED. */
+ public Boolean fix;
+
+ /** Branch that should be checked for auto-closeable changes. */
+ public String branch;
+
+ /** Number of commits to skip. */
+ public Integer skipCommits;
+
+ /** Maximum number of commits to walk. */
+ public Integer maxCommits;
+ }
+}
diff --git a/java/com/google/gerrit/extensions/api/projects/CheckProjectResultInfo.java b/java/com/google/gerrit/extensions/api/projects/CheckProjectResultInfo.java
new file mode 100644
index 0000000..e685122
--- /dev/null
+++ b/java/com/google/gerrit/extensions/api/projects/CheckProjectResultInfo.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2018 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.api.projects;
+
+import com.google.gerrit.extensions.common.ChangeInfo;
+import java.util.List;
+
+public class CheckProjectResultInfo {
+ public AutoCloseableChangesCheckResult autoCloseableChangesCheckResult;
+
+ public static class AutoCloseableChangesCheckResult {
+ public List<ChangeInfo> autoCloseableChanges;
+ }
+}
diff --git a/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java b/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
index b3dd1f1..08ba486 100644
--- a/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
+++ b/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
@@ -14,6 +14,7 @@
package com.google.gerrit.extensions.api.projects;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.extensions.client.SubmitType;
@@ -58,9 +59,17 @@
}
public static class MaxObjectSizeLimitInfo {
- public String value;
- public String configuredValue;
- public String inheritedValue;
+ /** The effective value in bytes. Null if not set. */
+ @Nullable public String value;
+
+ /** The value configured explicitly on the project as a formatted string. Null if not set. */
+ @Nullable public String configuredValue;
+
+ /**
+ * Whether the value was inherited or overridden from the project's parent hierarchy or global
+ * config. Null if not inherited or overridden.
+ */
+ @Nullable public String summary;
}
public static class ConfigParameterInfo {
diff --git a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index 63d40f0..0139b52 100644
--- a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -43,6 +43,8 @@
AccessCheckInfo checkAccess(AccessCheckInput in) throws RestApiException;
+ CheckProjectResultInfo check(CheckProjectInput in) throws RestApiException;
+
ConfigInfo config() throws RestApiException;
ConfigInfo config(ConfigInput in) throws RestApiException;
@@ -243,6 +245,11 @@
}
@Override
+ public CheckProjectResultInfo check(CheckProjectInput in) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public ConfigInfo config() throws RestApiException {
throw new NotImplementedException();
}
diff --git a/java/com/google/gerrit/extensions/common/SubmitRequirementInfo.java b/java/com/google/gerrit/extensions/common/SubmitRequirementInfo.java
index a940403..53f0375 100644
--- a/java/com/google/gerrit/extensions/common/SubmitRequirementInfo.java
+++ b/java/com/google/gerrit/extensions/common/SubmitRequirementInfo.java
@@ -14,6 +14,7 @@
package com.google.gerrit.extensions.common;
+import com.google.common.base.MoreObjects;
import java.util.Map;
import java.util.Objects;
@@ -50,4 +51,14 @@
public int hashCode() {
return Objects.hash(status, fallbackText, type, data);
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("status", status)
+ .add("fallbackText", fallbackText)
+ .add("type", type)
+ .add("data", data)
+ .toString();
+ }
}
diff --git a/java/com/google/gerrit/extensions/common/testing/ContentEntrySubject.java b/java/com/google/gerrit/extensions/common/testing/ContentEntrySubject.java
index 9030a1c..5fc8ba6 100644
--- a/java/com/google/gerrit/extensions/common/testing/ContentEntrySubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/ContentEntrySubject.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertAbout;
import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.IntegerSubject;
import com.google.common.truth.IterableSubject;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
@@ -81,4 +82,10 @@
ContentEntry contentEntry = actual();
return Truth.assertThat(contentEntry.editB).named("intraline edits of 'b'");
}
+
+ public IntegerSubject numberOfSkippedLines() {
+ isNotNull();
+ ContentEntry contentEntry = actual();
+ return Truth.assertThat(contentEntry.skip).named("number of skipped lines");
+ }
}
diff --git a/java/com/google/gerrit/extensions/events/PrivateStateChangedListener.java b/java/com/google/gerrit/extensions/events/PrivateStateChangedListener.java
index e46ceb8..2da6ec9 100644
--- a/java/com/google/gerrit/extensions/events/PrivateStateChangedListener.java
+++ b/java/com/google/gerrit/extensions/events/PrivateStateChangedListener.java
@@ -15,7 +15,7 @@
package com.google.gerrit.extensions.events;
public interface PrivateStateChangedListener {
- interface Event extends ChangeEvent {}
+ interface Event extends RevisionEvent {}
void onPrivateStateChanged(Event event);
}
diff --git a/java/com/google/gerrit/extensions/events/WorkInProgressStateChangedListener.java b/java/com/google/gerrit/extensions/events/WorkInProgressStateChangedListener.java
index e957421..d0e2bc1 100644
--- a/java/com/google/gerrit/extensions/events/WorkInProgressStateChangedListener.java
+++ b/java/com/google/gerrit/extensions/events/WorkInProgressStateChangedListener.java
@@ -15,7 +15,7 @@
package com.google.gerrit.extensions.events;
public interface WorkInProgressStateChangedListener {
- interface Event extends ChangeEvent {}
+ interface Event extends RevisionEvent {}
void onWorkInProgressStateChanged(Event event);
}
diff --git a/java/com/google/gerrit/extensions/registration/DynamicItem.java b/java/com/google/gerrit/extensions/registration/DynamicItem.java
index 477b666..4f36ab4 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicItem.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicItem.java
@@ -14,6 +14,7 @@
package com.google.gerrit.extensions.registration;
+import com.google.gerrit.common.Nullable;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Provider;
@@ -34,17 +35,6 @@
* exception is thrown.
*/
public class DynamicItem<T> {
- /** Pair of provider implementation and plugin providing it. */
- static class NamedProvider<T> {
- final Provider<T> impl;
- final String pluginName;
-
- NamedProvider(Provider<T> provider, String pluginName) {
- this.impl = provider;
- this.pluginName = pluginName;
- }
- }
-
/**
* Declare a singleton {@code DynamicItem<T>} with a binder.
*
@@ -88,7 +78,8 @@
* @param item item to store.
*/
public static <T> DynamicItem<T> itemOf(Class<T> member, T item) {
- return new DynamicItem<>(keyFor(TypeLiteral.get(member)), Providers.of(item), "gerrit");
+ return new DynamicItem<>(
+ keyFor(TypeLiteral.get(member)), Providers.of(item), PluginName.GERRIT);
}
@SuppressWarnings("unchecked")
@@ -137,12 +128,26 @@
* @return the configured item instance; null if no implementation has been bound to the item.
* This is common if no plugin registered an implementation for the type.
*/
+ @Nullable
public T get() {
NamedProvider<T> item = ref.get();
return item != null ? item.impl.get() : null;
}
/**
+ * Get the name of the plugin that has bound the configured item, or null.
+ *
+ * @return the name of the plugin that has bound the configured item; null if no implementation
+ * has been bound to the item. This is common if no plugin registered an implementation for
+ * the type.
+ */
+ @Nullable
+ public String getPluginName() {
+ NamedProvider<T> item = ref.get();
+ return item != null ? item.pluginName : null;
+ }
+
+ /**
* Set the element to provide.
*
* @param item the item to use. Must not be null.
@@ -165,7 +170,7 @@
NamedProvider<T> old = null;
while (!ref.compareAndSet(old, item)) {
old = ref.get();
- if (old != null && !"gerrit".equals(old.pluginName)) {
+ if (old != null && !PluginName.GERRIT.equals(old.pluginName)) {
throw new ProvisionException(
String.format(
"%s already provided by %s, ignoring plugin %s",
@@ -197,7 +202,9 @@
NamedProvider<T> old = null;
while (!ref.compareAndSet(old, item)) {
old = ref.get();
- if (old != null && !"gerrit".equals(old.pluginName) && !pluginName.equals(old.pluginName)) {
+ if (old != null
+ && !PluginName.GERRIT.equals(old.pluginName)
+ && !pluginName.equals(old.pluginName)) {
// We allow to replace:
// 1. Gerrit core items, e.g. websession cache
// can be replaced by plugin implementation
@@ -233,6 +240,7 @@
}
@Override
+ @Nullable
public ReloadableHandle replace(Key<T> newKey, Provider<T> newItem) {
NamedProvider<T> n = new NamedProvider<>(newItem, item.pluginName);
if (ref.compareAndSet(item, n)) {
diff --git a/java/com/google/gerrit/extensions/registration/DynamicItemProvider.java b/java/com/google/gerrit/extensions/registration/DynamicItemProvider.java
index 5b76741..d8dd1f9 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicItemProvider.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicItemProvider.java
@@ -36,7 +36,7 @@
@Override
public DynamicItem<T> get() {
- return new DynamicItem<>(key, find(injector, type), "gerrit");
+ return new DynamicItem<>(key, find(injector, type), PluginName.GERRIT);
}
private static <T> Provider<T> find(Injector src, TypeLiteral<T> type) {
diff --git a/java/com/google/gerrit/extensions/registration/DynamicMap.java b/java/com/google/gerrit/extensions/registration/DynamicMap.java
index 7178a16..96d19b2 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicMap.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicMap.java
@@ -41,6 +41,28 @@
* singleton and non-singleton members.
*/
public abstract class DynamicMap<T> implements Iterable<DynamicMap.Entry<T>> {
+ public static class Entry<T> {
+ private final NamePair namePair;
+ private final Provider<T> provider;
+
+ private Entry(NamePair namePair, Provider<T> provider) {
+ this.namePair = namePair;
+ this.provider = provider;
+ }
+
+ public String getPluginName() {
+ return namePair.pluginName;
+ }
+
+ public String getExportName() {
+ return namePair.exportName;
+ }
+
+ public Provider<T> getProvider() {
+ return provider;
+ }
+ }
+
/**
* Declare a singleton {@code DynamicMap<T>} with a binder.
*
@@ -154,23 +176,8 @@
@Override
public Entry<T> next() {
- final Map.Entry<NamePair, Provider<T>> e = i.next();
- return new Entry<T>() {
- @Override
- public String getPluginName() {
- return e.getKey().pluginName;
- }
-
- @Override
- public String getExportName() {
- return e.getKey().exportName;
- }
-
- @Override
- public Provider<T> getProvider() {
- return e.getValue();
- }
- };
+ Map.Entry<NamePair, Provider<T>> e = i.next();
+ return new Entry<>(e.getKey(), e.getValue());
}
@Override
@@ -180,14 +187,6 @@
};
}
- public interface Entry<T> {
- String getPluginName();
-
- String getExportName();
-
- Provider<T> getProvider();
- }
-
static class NamePair {
private final String pluginName;
private final String exportName;
diff --git a/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java b/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java
index 420a356..9d96131 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java
@@ -37,7 +37,7 @@
if (bindings != null) {
for (Binding<T> b : bindings) {
if (b.getKey().getAnnotation() != null) {
- m.put("gerrit", b.getKey(), b.getProvider());
+ m.put(PluginName.GERRIT, b.getKey(), b.getProvider());
}
}
}
diff --git a/java/com/google/gerrit/extensions/registration/DynamicSet.java b/java/com/google/gerrit/extensions/registration/DynamicSet.java
index 7ffb86d..6b3a49b 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicSet.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicSet.java
@@ -14,6 +14,12 @@
package com.google.gerrit.extensions.registration;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
+import static java.util.Comparator.naturalOrder;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Provider;
@@ -39,6 +45,24 @@
* singleton and non-singleton members.
*/
public class DynamicSet<T> implements Iterable<T> {
+ public static class Entry<T> {
+ private final String pluginName;
+ private final Provider<T> provider;
+
+ private Entry(String pluginName, Provider<T> provider) {
+ this.pluginName = pluginName;
+ this.provider = provider;
+ }
+
+ public String getPluginName() {
+ return pluginName;
+ }
+
+ public Provider<T> getProvider() {
+ return provider;
+ }
+ }
+
/**
* Declare a singleton {@code DynamicSet<T>} with a binder.
*
@@ -129,12 +153,12 @@
}
public static <T> DynamicSet<T> emptySet() {
- return new DynamicSet<>(Collections.<AtomicReference<Provider<T>>>emptySet());
+ return new DynamicSet<>(Collections.<AtomicReference<NamedProvider<T>>>emptySet());
}
- private final CopyOnWriteArrayList<AtomicReference<Provider<T>>> items;
+ private final CopyOnWriteArrayList<AtomicReference<NamedProvider<T>>> items;
- DynamicSet(Collection<AtomicReference<Provider<T>>> base) {
+ DynamicSet(Collection<AtomicReference<NamedProvider<T>>> base) {
items = new CopyOnWriteArrayList<>(base);
}
@@ -144,38 +168,59 @@
@Override
public Iterator<T> iterator() {
- final Iterator<AtomicReference<Provider<T>>> itr = items.iterator();
+ Iterator<Entry<T>> entryIterator = entries().iterator();
return new Iterator<T>() {
- private T next;
-
@Override
public boolean hasNext() {
- while (next == null && itr.hasNext()) {
- Provider<T> p = itr.next().get();
- if (p != null) {
- try {
- next = p.get();
- } catch (RuntimeException e) {
- // TODO Log failed member of DynamicSet.
- }
- }
- }
- return next != null;
+ return entryIterator.hasNext();
}
@Override
public T next() {
- if (hasNext()) {
- T result = next;
- next = null;
- return result;
- }
- throw new NoSuchElementException();
+ Entry<T> next = entryIterator.next();
+ return next != null ? next.getProvider().get() : null;
}
+ };
+ }
+ public Iterable<Entry<T>> entries() {
+ final Iterator<AtomicReference<NamedProvider<T>>> itr = items.iterator();
+ return new Iterable<Entry<T>>() {
@Override
- public void remove() {
- throw new UnsupportedOperationException();
+ public Iterator<Entry<T>> iterator() {
+ return new Iterator<Entry<T>>() {
+ private Entry<T> next;
+
+ @Override
+ public boolean hasNext() {
+ while (next == null && itr.hasNext()) {
+ NamedProvider<T> p = itr.next().get();
+ if (p != null) {
+ try {
+ next = new Entry<>(p.pluginName, p.impl);
+ } catch (RuntimeException e) {
+ // TODO Log failed member of DynamicSet.
+ }
+ }
+ }
+ return next != null;
+ }
+
+ @Override
+ public Entry<T> next() {
+ if (hasNext()) {
+ Entry<T> result = next;
+ next = null;
+ return result;
+ }
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
}
};
}
@@ -198,13 +243,29 @@
}
/**
- * Add one new element to the set.
+ * Get the names of all running plugins supplying this type.
*
- * @param item the item to add to the collection. Must not be null.
- * @return handle to remove the item at a later point in time.
+ * @return sorted set of active plugins that supply at least one item.
*/
- public RegistrationHandle add(T item) {
- return add(Providers.of(item));
+ public ImmutableSortedSet<String> plugins() {
+ return items
+ .stream()
+ .map(i -> i.get().pluginName)
+ .collect(toImmutableSortedSet(naturalOrder()));
+ }
+
+ /**
+ * Get the items exported by a single plugin.
+ *
+ * @param pluginName name of the plugin.
+ * @return items exported by a plugin.
+ */
+ public ImmutableSet<Provider<T>> byPlugin(String pluginName) {
+ return items
+ .stream()
+ .filter(i -> i.get().pluginName.equals(pluginName))
+ .map(i -> i.get().impl)
+ .collect(toImmutableSet());
}
/**
@@ -213,13 +274,24 @@
* @param item the item to add to the collection. Must not be null.
* @return handle to remove the item at a later point in time.
*/
- public RegistrationHandle add(Provider<T> item) {
- final AtomicReference<Provider<T>> ref = new AtomicReference<>(item);
+ public RegistrationHandle add(String pluginName, T item) {
+ return add(pluginName, Providers.of(item));
+ }
+
+ /**
+ * Add one new element to the set.
+ *
+ * @param item the item to add to the collection. Must not be null.
+ * @return handle to remove the item at a later point in time.
+ */
+ public RegistrationHandle add(String pluginName, Provider<T> item) {
+ final AtomicReference<NamedProvider<T>> ref =
+ new AtomicReference<>(new NamedProvider<>(item, pluginName));
items.add(ref);
return new RegistrationHandle() {
@Override
public void remove() {
- if (ref.compareAndSet(item, null)) {
+ if (ref.compareAndSet(ref.get(), null)) {
items.remove(ref);
}
}
@@ -229,6 +301,7 @@
/**
* Add one new element that may be hot-replaceable in the future.
*
+ * @param pluginName unique name of the plugin providing the item.
* @param key unique description from the item's Guice binding. This can be later obtained from
* the registration handle to facilitate matching with the new equivalent instance during a
* hot reload.
@@ -236,18 +309,19 @@
* @return a handle that can remove this item later, or hot-swap the item without it ever leaving
* the collection.
*/
- public ReloadableRegistrationHandle<T> add(Key<T> key, Provider<T> item) {
- AtomicReference<Provider<T>> ref = new AtomicReference<>(item);
+ public ReloadableRegistrationHandle<T> add(String pluginName, Key<T> key, Provider<T> item) {
+ AtomicReference<NamedProvider<T>> ref =
+ new AtomicReference<>(new NamedProvider<>(item, pluginName));
items.add(ref);
- return new ReloadableHandle(ref, key, item);
+ return new ReloadableHandle(ref, key, ref.get());
}
private class ReloadableHandle implements ReloadableRegistrationHandle<T> {
- private final AtomicReference<Provider<T>> ref;
+ private final AtomicReference<NamedProvider<T>> ref;
private final Key<T> key;
- private final Provider<T> item;
+ private final NamedProvider<T> item;
- ReloadableHandle(AtomicReference<Provider<T>> ref, Key<T> key, Provider<T> item) {
+ ReloadableHandle(AtomicReference<NamedProvider<T>> ref, Key<T> key, NamedProvider<T> item) {
this.ref = ref;
this.key = key;
this.item = item;
@@ -267,8 +341,9 @@
@Override
public ReloadableHandle replace(Key<T> newKey, Provider<T> newItem) {
- if (ref.compareAndSet(item, newItem)) {
- return new ReloadableHandle(ref, newKey, newItem);
+ NamedProvider<T> n = new NamedProvider<>(newItem, item.pluginName);
+ if (ref.compareAndSet(item, n)) {
+ return new ReloadableHandle(ref, newKey, n);
}
return null;
}
diff --git a/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java b/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java
index 707c76a..6d36f54 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java
@@ -38,16 +38,17 @@
return new DynamicSet<>(find(injector, type));
}
- private static <T> List<AtomicReference<Provider<T>>> find(Injector src, TypeLiteral<T> type) {
+ private static <T> List<AtomicReference<NamedProvider<T>>> find(
+ Injector src, TypeLiteral<T> type) {
List<Binding<T>> bindings = src.findBindingsByType(type);
int cnt = bindings != null ? bindings.size() : 0;
if (cnt == 0) {
return Collections.emptyList();
}
- List<AtomicReference<Provider<T>>> r = new ArrayList<>(cnt);
+ List<AtomicReference<NamedProvider<T>>> r = new ArrayList<>(cnt);
for (Binding<T> b : bindings) {
if (b.getKey().getAnnotation() != null) {
- r.add(new AtomicReference<>(b.getProvider()));
+ r.add(new AtomicReference<>(new NamedProvider<>(b.getProvider(), PluginName.GERRIT)));
}
}
return r;
diff --git a/java/com/google/gerrit/extensions/registration/NamedProvider.java b/java/com/google/gerrit/extensions/registration/NamedProvider.java
new file mode 100644
index 0000000..aca651b
--- /dev/null
+++ b/java/com/google/gerrit/extensions/registration/NamedProvider.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2018 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.registration;
+
+import com.google.inject.Provider;
+
+/** Pair of provider implementation and plugin providing it. */
+class NamedProvider<T> {
+ final Provider<T> impl;
+ final String pluginName;
+
+ NamedProvider(Provider<T> provider, String pluginName) {
+ this.impl = provider;
+ this.pluginName = pluginName;
+ }
+}
diff --git a/java/com/google/gerrit/extensions/registration/PluginName.java b/java/com/google/gerrit/extensions/registration/PluginName.java
new file mode 100644
index 0000000..c110d45
--- /dev/null
+++ b/java/com/google/gerrit/extensions/registration/PluginName.java
@@ -0,0 +1,22 @@
+// Copyright (C) 2018 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.registration;
+
+public class PluginName {
+ /** Name that is used as plugin name if Gerrit core implements a plugin extension point. */
+ public static final String GERRIT = "gerrit";
+
+ private PluginName() {}
+}
diff --git a/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicTypes.java b/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicTypes.java
index e606079..fd31fcd 100644
--- a/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicTypes.java
+++ b/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicTypes.java
@@ -81,7 +81,7 @@
}
public static List<RegistrationHandle> attachItems(
- Injector src, Map<TypeLiteral<?>, DynamicItem<?>> items, String pluginName) {
+ Injector src, String pluginName, Map<TypeLiteral<?>, DynamicItem<?>> items) {
if (src == null || items == null || items.isEmpty()) {
return Collections.emptyList();
}
@@ -107,7 +107,7 @@
}
public static List<RegistrationHandle> attachSets(
- Injector src, Map<TypeLiteral<?>, DynamicSet<?>> sets) {
+ Injector src, String pluginName, Map<TypeLiteral<?>, DynamicSet<?>> sets) {
if (src == null || sets == null || sets.isEmpty()) {
return Collections.emptyList();
}
@@ -123,7 +123,7 @@
for (Binding<Object> b : bindings(src, type)) {
if (b.getKey().getAnnotation() != null) {
- handles.add(set.add(b.getKey(), b.getProvider()));
+ handles.add(set.add(pluginName, b.getKey(), b.getProvider()));
}
}
}
@@ -135,7 +135,7 @@
}
public static List<RegistrationHandle> attachMaps(
- Injector src, String groupName, Map<TypeLiteral<?>, DynamicMap<?>> maps) {
+ Injector src, String pluginName, Map<TypeLiteral<?>, DynamicMap<?>> maps) {
if (src == null || maps == null || maps.isEmpty()) {
return Collections.emptyList();
}
@@ -147,12 +147,12 @@
TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey();
@SuppressWarnings("unchecked")
- PrivateInternals_DynamicMapImpl<Object> set =
+ PrivateInternals_DynamicMapImpl<Object> map =
(PrivateInternals_DynamicMapImpl<Object>) e.getValue();
for (Binding<Object> b : bindings(src, type)) {
if (b.getKey().getAnnotation() != null) {
- handles.add(set.put(groupName, b.getKey(), b.getProvider()));
+ handles.add(map.put(pluginName, b.getKey(), b.getProvider()));
}
}
}
@@ -174,8 +174,8 @@
handles = new ArrayList<>(4);
Injector parent = self.getParent();
while (parent != null) {
- handles.addAll(attachSets(self, dynamicSetsOf(parent)));
- handles.addAll(attachMaps(self, "gerrit", dynamicMapsOf(parent)));
+ handles.addAll(attachSets(self, PluginName.GERRIT, dynamicSetsOf(parent)));
+ handles.addAll(attachMaps(self, PluginName.GERRIT, dynamicMapsOf(parent)));
parent = parent.getParent();
}
if (handles.isEmpty()) {
diff --git a/java/com/google/gerrit/extensions/restapi/AuthException.java b/java/com/google/gerrit/extensions/restapi/AuthException.java
index 0b4f459..fe1744b 100644
--- a/java/com/google/gerrit/extensions/restapi/AuthException.java
+++ b/java/com/google/gerrit/extensions/restapi/AuthException.java
@@ -14,10 +14,17 @@
package com.google.gerrit.extensions.restapi;
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Strings;
+import java.util.Optional;
+
/** Caller cannot perform the request operation (HTTP 403 Forbidden). */
public class AuthException extends RestApiException {
private static final long serialVersionUID = 1L;
+ private Optional<String> advice = Optional.empty();
+
/** @param msg message to return to the client. */
public AuthException(String msg) {
super(msg);
@@ -30,4 +37,19 @@
public AuthException(String msg, Throwable cause) {
super(msg, cause);
}
+
+ public void setAdvice(String advice) {
+ checkArgument(!Strings.isNullOrEmpty(advice));
+ this.advice = Optional.of(advice);
+ }
+
+ /**
+ * Advice that the user can follow to acquire authorization to perform the action.
+ *
+ * <p>This may be long-form text with newlines, and may be printed to a terminal, for example in
+ * the message stream in response to a push.
+ */
+ public Optional<String> getAdvice() {
+ return advice;
+ }
}
diff --git a/java/com/google/gerrit/git/testing/PushResultSubject.java b/java/com/google/gerrit/git/testing/PushResultSubject.java
index c5163d1..929e182 100644
--- a/java/com/google/gerrit/git/testing/PushResultSubject.java
+++ b/java/com/google/gerrit/git/testing/PushResultSubject.java
@@ -54,6 +54,13 @@
Truth.assertThat(trimMessages()).isEqualTo(Arrays.stream(expectedLines).collect(joining("\n")));
}
+ public void containsMessages(String... expectedLines) {
+ checkArgument(expectedLines.length > 0, "use hasNoMessages()");
+ isNotNull();
+ Iterable<String> got = Splitter.on("\n").split(trimMessages());
+ Truth.assertThat(got).containsAllIn(expectedLines).inOrder();
+ }
+
private String trimMessages() {
return trimMessages(actual().getMessages());
}
diff --git a/java/com/google/gerrit/httpd/AllRequestFilter.java b/java/com/google/gerrit/httpd/AllRequestFilter.java
index b8b0bc8..9d171d5 100644
--- a/java/com/google/gerrit/httpd/AllRequestFilter.java
+++ b/java/com/google/gerrit/httpd/AllRequestFilter.java
@@ -76,7 +76,7 @@
// synchronized.
if (!initializedFilters.contains(filter)) {
filter.init(filterConfig);
- initializedFilters.add(filter);
+ initializedFilters.add("gerrit", filter);
}
} else {
ret = false;
@@ -89,7 +89,7 @@
initializedFilters = new DynamicSet<>();
for (AllRequestFilter filter : filtersToCleanUp) {
if (filters.contains(filter)) {
- initializedFilters.add(filter);
+ initializedFilters.add("gerrit", filter);
} else {
filter.destroy();
}
diff --git a/java/com/google/gerrit/httpd/BUILD b/java/com/google/gerrit/httpd/BUILD
index 3e71098..fae7c6a 100644
--- a/java/com/google/gerrit/httpd/BUILD
+++ b/java/com/google/gerrit/httpd/BUILD
@@ -16,6 +16,7 @@
"//java/com/google/gerrit/server/audit",
"//java/com/google/gerrit/server/git/receive",
"//java/com/google/gerrit/server/ioutil",
+ "//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/server/restapi",
"//java/com/google/gerrit/util/cli",
"//java/com/google/gerrit/util/http",
diff --git a/java/com/google/gerrit/httpd/GerritAuthModule.java b/java/com/google/gerrit/httpd/GerritAuthModule.java
new file mode 100644
index 0000000..c0ef207
--- /dev/null
+++ b/java/com/google/gerrit/httpd/GerritAuthModule.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2018 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.httpd;
+
+import static com.google.gerrit.extensions.api.lfs.LfsDefinitions.LFS_URL_WO_AUTH_REGEX;
+
+import com.google.gerrit.extensions.client.GitBasicAuthPolicy;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.inject.Inject;
+import com.google.inject.servlet.ServletModule;
+import javax.servlet.Filter;
+
+/** Configures filter for authenticating REST requests. */
+public class GerritAuthModule extends ServletModule {
+ private static final String NOT_AUTHORIZED_LFS_URL_REGEX = "^(?:(?!/a/))" + LFS_URL_WO_AUTH_REGEX;
+ private final AuthConfig authConfig;
+
+ @Inject
+ GerritAuthModule(AuthConfig authConfig) {
+ this.authConfig = authConfig;
+ }
+
+ @Override
+ protected void configureServlets() {
+ Class<? extends Filter> authFilter = retreiveAuthFilterFromConfig(authConfig);
+
+ filterRegex(NOT_AUTHORIZED_LFS_URL_REGEX).through(authFilter);
+ filter("/a/*").through(authFilter);
+ }
+
+ static Class<? extends Filter> retreiveAuthFilterFromConfig(AuthConfig authConfig) {
+ Class<? extends Filter> authFilter;
+ if (authConfig.isTrustContainerAuth()) {
+ authFilter = ContainerAuthFilter.class;
+ } else {
+ authFilter =
+ authConfig.getGitBasicAuthPolicy() == GitBasicAuthPolicy.OAUTH
+ ? ProjectOAuthFilter.class
+ : ProjectBasicAuthFilter.class;
+ }
+ return authFilter;
+ }
+}
diff --git a/java/com/google/gerrit/httpd/GitOverHttpModule.java b/java/com/google/gerrit/httpd/GitOverHttpModule.java
index 3f3737d..8400d60 100644
--- a/java/com/google/gerrit/httpd/GitOverHttpModule.java
+++ b/java/com/google/gerrit/httpd/GitOverHttpModule.java
@@ -14,20 +14,16 @@
package com.google.gerrit.httpd;
-import static com.google.gerrit.extensions.api.lfs.LfsDefinitions.LFS_URL_WO_AUTH_REGEX;
+import static com.google.gerrit.httpd.GitOverHttpServlet.URL_REGEX;
-import com.google.gerrit.extensions.client.GitBasicAuthPolicy;
import com.google.gerrit.reviewdb.client.CoreDownloadSchemes;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.DownloadConfig;
import com.google.inject.Inject;
import com.google.inject.servlet.ServletModule;
-import javax.servlet.Filter;
/** Configures Git access over HTTP with authentication. */
public class GitOverHttpModule extends ServletModule {
- private static final String NOT_AUTHORIZED_LFS_URL_REGEX = "^(?:(?!/a/))" + LFS_URL_WO_AUTH_REGEX;
-
private final AuthConfig authConfig;
private final DownloadConfig downloadConfig;
@@ -39,28 +35,10 @@
@Override
protected void configureServlets() {
- Class<? extends Filter> authFilter;
- if (authConfig.isTrustContainerAuth()) {
- authFilter = ContainerAuthFilter.class;
- } else {
- authFilter =
- authConfig.getGitBasicAuthPolicy() == GitBasicAuthPolicy.OAUTH
- ? ProjectOAuthFilter.class
- : ProjectBasicAuthFilter.class;
+ if (downloadConfig.getDownloadSchemes().contains(CoreDownloadSchemes.ANON_HTTP)
+ || downloadConfig.getDownloadSchemes().contains(CoreDownloadSchemes.HTTP)) {
+ filterRegex(URL_REGEX).through(GerritAuthModule.retreiveAuthFilterFromConfig(authConfig));
+ serveRegex(URL_REGEX).with(GitOverHttpServlet.class);
}
-
- if (isHttpEnabled()) {
- String git = GitOverHttpServlet.URL_REGEX;
- filterRegex(git).through(authFilter);
- serveRegex(git).with(GitOverHttpServlet.class);
- }
-
- filterRegex(NOT_AUTHORIZED_LFS_URL_REGEX).through(authFilter);
- filter("/a/*").through(authFilter);
- }
-
- private boolean isHttpEnabled() {
- return downloadConfig.getDownloadSchemes().contains(CoreDownloadSchemes.ANON_HTTP)
- || downloadConfig.getDownloadSchemes().contains(CoreDownloadSchemes.HTTP);
}
}
diff --git a/java/com/google/gerrit/httpd/QueryDocumentationFilter.java b/java/com/google/gerrit/httpd/QueryDocumentationFilter.java
index 8b82c00..c41a7b9 100644
--- a/java/com/google/gerrit/httpd/QueryDocumentationFilter.java
+++ b/java/com/google/gerrit/httpd/QueryDocumentationFilter.java
@@ -59,7 +59,7 @@
HttpServletResponse rsp = (HttpServletResponse) response;
try {
List<DocResult> result = searcher.doQuery(request.getParameter("q"));
- RestApiServlet.replyJson(req, rsp, ImmutableListMultimap.of(), result);
+ RestApiServlet.replyJson(req, rsp, false, ImmutableListMultimap.of(), result);
} catch (DocQueryException e) {
logger.atSevere().withCause(e).log("Doc search failed");
rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index 8e380f5..cb476af 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -23,6 +23,7 @@
import com.google.gerrit.extensions.client.AuthType;
import com.google.gerrit.gpg.GpgModule;
import com.google.gerrit.httpd.AllRequestFilter;
+import com.google.gerrit.httpd.GerritAuthModule;
import com.google.gerrit.httpd.GetUserFilter;
import com.google.gerrit.httpd.GitOverHttpModule;
import com.google.gerrit.httpd.H2CacheBasedWebSession;
@@ -422,9 +423,10 @@
private Injector createWebInjector() {
final List<Module> modules = new ArrayList<>();
modules.add(RequestContextFilter.module());
- modules.add(AllRequestFilter.module());
modules.add(RequestMetricsFilter.module());
+ modules.add(sysInjector.getInstance(GerritAuthModule.class));
modules.add(sysInjector.getInstance(GitOverHttpModule.class));
+ modules.add(AllRequestFilter.module());
modules.add(sysInjector.getInstance(WebModule.class));
modules.add(sysInjector.getInstance(RequireSslFilter.Module.class));
if (sshInjector != null) {
diff --git a/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index 9a24e47..74cadd3 100644
--- a/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -456,8 +456,8 @@
}
}
- Collections.sort(cmds, PluginEntry.COMPARATOR_BY_NAME);
- Collections.sort(docs, PluginEntry.COMPARATOR_BY_NAME);
+ cmds.sort(PluginEntry.COMPARATOR_BY_NAME);
+ docs.sort(PluginEntry.COMPARATOR_BY_NAME);
StringBuilder md = new StringBuilder();
md.append(String.format("# Plugin %s #\n", pluginName));
diff --git a/java/com/google/gerrit/httpd/raw/BazelBuild.java b/java/com/google/gerrit/httpd/raw/BazelBuild.java
index 940a51b..2b390a9 100644
--- a/java/com/google/gerrit/httpd/raw/BazelBuild.java
+++ b/java/com/google/gerrit/httpd/raw/BazelBuild.java
@@ -23,14 +23,15 @@
import com.google.common.html.HtmlEscapers;
import com.google.common.io.ByteStreams;
import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.launcher.GerritLauncher;
import com.google.gerrit.util.http.CacheHeaders;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.PrintWriter;
-import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Properties;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.util.RawParseUtils;
@@ -122,20 +123,19 @@
}
}
- private Properties loadBuildProperties(Path propPath) throws IOException {
- Properties properties = new Properties();
- try (InputStream in = Files.newInputStream(propPath)) {
- properties.load(in);
- } catch (NoSuchFileException e) {
- // Ignore; will be run from PATH, with a descriptive error if it fails.
- }
- return properties;
- }
-
private ProcessBuilder newBuildProcess(Label label) throws IOException {
- Properties properties = loadBuildProperties(sourceRoot.resolve(".bazel_path"));
+ Properties properties = GerritLauncher.loadBuildProperties(sourceRoot.resolve(".bazel_path"));
String bazel = firstNonNull(properties.getProperty("bazel"), "bazel");
- ProcessBuilder proc = new ProcessBuilder(bazel, "build", label.fullName());
+ List<String> cmd = new ArrayList<>();
+ cmd.add(bazel);
+ cmd.add("build");
+ if (GerritLauncher.isJdk9OrLater()) {
+ String v = GerritLauncher.getJdkVersionPostJdk8();
+ cmd.add("--host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java" + v);
+ cmd.add("--java_toolchain=@bazel_tools//tools/jdk:toolchain_java" + v);
+ }
+ cmd.add(label.fullName());
+ ProcessBuilder proc = new ProcessBuilder(cmd);
if (properties.containsKey("PATH")) {
proc.environment().put("PATH", properties.getProperty("PATH"));
}
diff --git a/java/com/google/gerrit/httpd/restapi/ParameterParser.java b/java/com/google/gerrit/httpd/restapi/ParameterParser.java
index 2870cd0..172321d 100644
--- a/java/com/google/gerrit/httpd/restapi/ParameterParser.java
+++ b/java/com/google/gerrit/httpd/restapi/ParameterParser.java
@@ -56,8 +56,10 @@
import org.kohsuke.args4j.CmdLineException;
public class ParameterParser {
+ public static final String TRACE_PARAMETER = "trace";
+
private static final ImmutableSet<String> RESERVED_KEYS =
- ImmutableSet.of("pp", "prettyPrint", "strict", "callback", "alt", "fields");
+ ImmutableSet.of("pp", "prettyPrint", "strict", "callback", "alt", "fields", TRACE_PARAMETER);
@AutoValue
public abstract static class QueryParams {
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiMetrics.java b/java/com/google/gerrit/httpd/restapi/RestApiMetrics.java
index 4af03a3..562687b 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiMetrics.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiMetrics.java
@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.restapi;
import com.google.common.base.Strings;
+import com.google.gerrit.extensions.registration.PluginName;
import com.google.gerrit.httpd.restapi.RestApiServlet.ViewData;
import com.google.gerrit.metrics.Counter1;
import com.google.gerrit.metrics.Counter2;
@@ -79,7 +80,8 @@
break;
}
}
- if (!Strings.isNullOrEmpty(viewData.pluginName) && !"gerrit".equals(viewData.pluginName)) {
+ if (!Strings.isNullOrEmpty(viewData.pluginName)
+ && !PluginName.GERRIT.equals(viewData.pluginName)) {
impl = viewData.pluginName + '-' + impl;
}
return impl;
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index a5c5a53..e0559f1 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -18,6 +18,7 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.flogger.LazyArgs.lazy;
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS;
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS;
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS;
@@ -48,6 +49,7 @@
import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
@@ -68,6 +70,7 @@
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.PluginName;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
@@ -107,6 +110,8 @@
import com.google.gerrit.server.cache.PerThreadCache;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.LockFailureException;
+import com.google.gerrit.server.logging.RequestId;
+import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -131,6 +136,7 @@
import com.google.inject.util.Providers;
import java.io.BufferedReader;
import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.FilterOutputStream;
import java.io.IOException;
@@ -177,6 +183,8 @@
private static final String FORM_TYPE = "application/x-www-form-urlencoded";
+ @VisibleForTesting public static final String X_GERRIT_TRACE = "X-Gerrit-Trace";
+
// HTTP 422 Unprocessable Entity.
// TODO: Remove when HttpServletResponse.SC_UNPROCESSABLE_ENTITY is available
private static final int SC_UNPROCESSABLE_ENTITY = 422;
@@ -280,332 +288,347 @@
RestResource rsrc = TopLevelResource.INSTANCE;
ViewData viewData = null;
- try (PerThreadCache ignored = PerThreadCache.create()) {
- if (isCorsPreflight(req)) {
- doCorsPreflight(req, res);
- return;
- }
+ try (TraceContext traceContext = enableTracing(req, res)) {
+ try (PerThreadCache ignored = PerThreadCache.create()) {
+ logger.atFinest().log(
+ "Received REST request: %s %s (parameters: %s)",
+ req.getMethod(), req.getRequestURI(), getParameterNames(req));
+ logger.atFinest().log("Calling user: %s", globals.currentUser.get().getLoggableName());
- qp = ParameterParser.getQueryParams(req);
- checkCors(req, res, qp.hasXdOverride());
- if (qp.hasXdOverride()) {
- req = applyXdOverrides(req, qp);
- }
- checkUserSession(req);
-
- List<IdString> path = splitPath(req);
- RestCollection<RestResource, RestResource> rc = members.get();
- globals
- .permissionBackend
- .currentUser()
- .checkAny(GlobalPermission.fromAnnotation(rc.getClass()));
-
- viewData = new ViewData(null, null);
-
- if (path.isEmpty()) {
- if (rc instanceof NeedsParams) {
- ((NeedsParams) rc).setParams(qp.params());
+ if (isCorsPreflight(req)) {
+ doCorsPreflight(req, res);
+ return;
}
- if (isRead(req)) {
- viewData = new ViewData(null, rc.list());
- } else if (isPost(req)) {
- RestView<RestResource> restCollectionView =
- rc.views().get("gerrit", "POST_ON_COLLECTION./");
- if (restCollectionView != null) {
- viewData = new ViewData(null, restCollectionView);
- } else {
- throw methodNotAllowed(req);
- }
- } else {
- // DELETE on root collections is not supported
- throw methodNotAllowed(req);
+ qp = ParameterParser.getQueryParams(req);
+ checkCors(req, res, qp.hasXdOverride());
+ if (qp.hasXdOverride()) {
+ req = applyXdOverrides(req, qp);
}
- } else {
- IdString id = path.remove(0);
- try {
- rsrc = rc.parse(rsrc, id);
- if (path.isEmpty()) {
- checkPreconditions(req);
- }
- } catch (ResourceNotFoundException e) {
- if (!path.isEmpty()) {
- throw e;
- }
+ checkUserSession(req);
- if (isPost(req) || isPut(req)) {
- RestView<RestResource> createView = rc.views().get("gerrit", "CREATE./");
- if (createView != null) {
- viewData = new ViewData(null, createView);
- status = SC_CREATED;
- path.add(id);
- } else {
- throw e;
- }
- } else if (isDelete(req)) {
- RestView<RestResource> deleteView = rc.views().get("gerrit", "DELETE_MISSING./");
- if (deleteView != null) {
- viewData = new ViewData(null, deleteView);
- status = SC_NO_CONTENT;
- path.add(id);
- } else {
- throw e;
- }
- } else {
- throw e;
- }
- }
- if (viewData.view == null) {
- viewData = view(rc, req.getMethod(), path);
- }
- }
- checkRequiresCapability(viewData);
+ List<IdString> path = splitPath(req);
+ RestCollection<RestResource, RestResource> rc = members.get();
+ globals
+ .permissionBackend
+ .currentUser()
+ .checkAny(GlobalPermission.fromAnnotation(rc.getClass()));
- while (viewData.view instanceof RestCollection<?, ?>) {
- @SuppressWarnings("unchecked")
- RestCollection<RestResource, RestResource> c =
- (RestCollection<RestResource, RestResource>) viewData.view;
+ viewData = new ViewData(null, null);
if (path.isEmpty()) {
+ if (rc instanceof NeedsParams) {
+ ((NeedsParams) rc).setParams(qp.params());
+ }
+
if (isRead(req)) {
- viewData = new ViewData(null, c.list());
+ viewData = new ViewData(null, rc.list());
} else if (isPost(req)) {
RestView<RestResource> restCollectionView =
- c.views().get(viewData.pluginName, "POST_ON_COLLECTION./");
- if (restCollectionView != null) {
- viewData = new ViewData(null, restCollectionView);
- } else {
- throw methodNotAllowed(req);
- }
- } else if (isDelete(req)) {
- RestView<RestResource> restCollectionView =
- c.views().get(viewData.pluginName, "DELETE_ON_COLLECTION./");
+ rc.views().get(PluginName.GERRIT, "POST_ON_COLLECTION./");
if (restCollectionView != null) {
viewData = new ViewData(null, restCollectionView);
} else {
throw methodNotAllowed(req);
}
} else {
+ // DELETE on root collections is not supported
throw methodNotAllowed(req);
}
- break;
- }
- IdString id = path.remove(0);
- try {
- rsrc = c.parse(rsrc, id);
- checkPreconditions(req);
- viewData = new ViewData(null, null);
- } catch (ResourceNotFoundException e) {
- if (!path.isEmpty()) {
- throw e;
- }
+ } else {
+ IdString id = path.remove(0);
+ try {
+ rsrc = rc.parse(rsrc, id);
+ if (path.isEmpty()) {
+ checkPreconditions(req);
+ }
+ } catch (ResourceNotFoundException e) {
+ if (!path.isEmpty()) {
+ throw e;
+ }
- if (isPost(req) || isPut(req)) {
- RestView<RestResource> createView = c.views().get("gerrit", "CREATE./");
- if (createView != null) {
- viewData = new ViewData(null, createView);
- status = SC_CREATED;
- path.add(id);
+ if (isPost(req) || isPut(req)) {
+ RestView<RestResource> createView = rc.views().get(PluginName.GERRIT, "CREATE./");
+ if (createView != null) {
+ viewData = new ViewData(null, createView);
+ status = SC_CREATED;
+ path.add(id);
+ } else {
+ throw e;
+ }
+ } else if (isDelete(req)) {
+ RestView<RestResource> deleteView =
+ rc.views().get(PluginName.GERRIT, "DELETE_MISSING./");
+ if (deleteView != null) {
+ viewData = new ViewData(null, deleteView);
+ status = SC_NO_CONTENT;
+ path.add(id);
+ } else {
+ throw e;
+ }
} else {
throw e;
}
- } else if (isDelete(req)) {
- RestView<RestResource> deleteView = c.views().get("gerrit", "DELETE_MISSING./");
- if (deleteView != null) {
- viewData = new ViewData(null, deleteView);
- status = SC_NO_CONTENT;
- path.add(id);
- } else {
- throw e;
- }
- } else {
- throw e;
}
- }
- if (viewData.view == null) {
- viewData = view(c, req.getMethod(), path);
+ if (viewData.view == null) {
+ viewData = view(rc, req.getMethod(), path);
+ }
}
checkRequiresCapability(viewData);
- }
- if (notModified(req, rsrc, viewData.view)) {
- res.sendError(SC_NOT_MODIFIED);
- return;
- }
+ while (viewData.view instanceof RestCollection<?, ?>) {
+ @SuppressWarnings("unchecked")
+ RestCollection<RestResource, RestResource> c =
+ (RestCollection<RestResource, RestResource>) viewData.view;
- if (!globals.paramParser.get().parse(viewData.view, qp.params(), req, res)) {
- return;
- }
-
- if (viewData.view instanceof RestReadView<?> && isRead(req)) {
- result = ((RestReadView<RestResource>) viewData.view).apply(rsrc);
- } else if (viewData.view instanceof RestModifyView<?, ?>) {
- @SuppressWarnings("unchecked")
- RestModifyView<RestResource, Object> m =
- (RestModifyView<RestResource, Object>) viewData.view;
-
- Type type = inputType(m);
- inputRequestBody = parseRequest(req, type);
- result = m.apply(rsrc, inputRequestBody);
- if (inputRequestBody instanceof RawInput) {
- try (InputStream is = req.getInputStream()) {
- ServletUtils.consumeRequestBody(is);
+ if (path.isEmpty()) {
+ if (isRead(req)) {
+ viewData = new ViewData(null, c.list());
+ } else if (isPost(req)) {
+ RestView<RestResource> restCollectionView =
+ c.views().get(viewData.pluginName, "POST_ON_COLLECTION./");
+ if (restCollectionView != null) {
+ viewData = new ViewData(null, restCollectionView);
+ } else {
+ throw methodNotAllowed(req);
+ }
+ } else if (isDelete(req)) {
+ RestView<RestResource> restCollectionView =
+ c.views().get(viewData.pluginName, "DELETE_ON_COLLECTION./");
+ if (restCollectionView != null) {
+ viewData = new ViewData(null, restCollectionView);
+ } else {
+ throw methodNotAllowed(req);
+ }
+ } else {
+ throw methodNotAllowed(req);
+ }
+ break;
}
- }
- } else if (viewData.view instanceof RestCollectionCreateView<?, ?, ?>) {
- @SuppressWarnings("unchecked")
- RestCollectionCreateView<RestResource, RestResource, Object> m =
- (RestCollectionCreateView<RestResource, RestResource, Object>) viewData.view;
+ IdString id = path.remove(0);
+ try {
+ rsrc = c.parse(rsrc, id);
+ checkPreconditions(req);
+ viewData = new ViewData(null, null);
+ } catch (ResourceNotFoundException e) {
+ if (!path.isEmpty()) {
+ throw e;
+ }
- Type type = inputType(m);
- inputRequestBody = parseRequest(req, type);
- result = m.apply(rsrc, path.get(0), inputRequestBody);
- if (inputRequestBody instanceof RawInput) {
- try (InputStream is = req.getInputStream()) {
- ServletUtils.consumeRequestBody(is);
+ if (isPost(req) || isPut(req)) {
+ RestView<RestResource> createView = c.views().get(PluginName.GERRIT, "CREATE./");
+ if (createView != null) {
+ viewData = new ViewData(null, createView);
+ status = SC_CREATED;
+ path.add(id);
+ } else {
+ throw e;
+ }
+ } else if (isDelete(req)) {
+ RestView<RestResource> deleteView =
+ c.views().get(PluginName.GERRIT, "DELETE_MISSING./");
+ if (deleteView != null) {
+ viewData = new ViewData(null, deleteView);
+ status = SC_NO_CONTENT;
+ path.add(id);
+ } else {
+ throw e;
+ }
+ } else {
+ throw e;
+ }
}
- }
- } else if (viewData.view instanceof RestCollectionDeleteMissingView<?, ?, ?>) {
- @SuppressWarnings("unchecked")
- RestCollectionDeleteMissingView<RestResource, RestResource, Object> m =
- (RestCollectionDeleteMissingView<RestResource, RestResource, Object>) viewData.view;
-
- Type type = inputType(m);
- inputRequestBody = parseRequest(req, type);
- result = m.apply(rsrc, path.get(0), inputRequestBody);
- if (inputRequestBody instanceof RawInput) {
- try (InputStream is = req.getInputStream()) {
- ServletUtils.consumeRequestBody(is);
+ if (viewData.view == null) {
+ viewData = view(c, req.getMethod(), path);
}
+ checkRequiresCapability(viewData);
}
- } else if (viewData.view instanceof RestCollectionModifyView<?, ?, ?>) {
- @SuppressWarnings("unchecked")
- RestCollectionModifyView<RestResource, RestResource, Object> m =
- (RestCollectionModifyView<RestResource, RestResource, Object>) viewData.view;
- Type type = inputType(m);
- inputRequestBody = parseRequest(req, type);
- result = m.apply(rsrc, inputRequestBody);
- if (inputRequestBody instanceof RawInput) {
- try (InputStream is = req.getInputStream()) {
- ServletUtils.consumeRequestBody(is);
+ if (notModified(req, rsrc, viewData.view)) {
+ res.sendError(SC_NOT_MODIFIED);
+ return;
+ }
+
+ if (!globals.paramParser.get().parse(viewData.view, qp.params(), req, res)) {
+ return;
+ }
+
+ if (viewData.view instanceof RestReadView<?> && isRead(req)) {
+ result = ((RestReadView<RestResource>) viewData.view).apply(rsrc);
+ } else if (viewData.view instanceof RestModifyView<?, ?>) {
+ @SuppressWarnings("unchecked")
+ RestModifyView<RestResource, Object> m =
+ (RestModifyView<RestResource, Object>) viewData.view;
+
+ Type type = inputType(m);
+ inputRequestBody = parseRequest(req, type);
+ result = m.apply(rsrc, inputRequestBody);
+ if (inputRequestBody instanceof RawInput) {
+ try (InputStream is = req.getInputStream()) {
+ ServletUtils.consumeRequestBody(is);
+ }
}
- }
- } else {
- throw new ResourceNotFoundException();
- }
+ } else if (viewData.view instanceof RestCollectionCreateView<?, ?, ?>) {
+ @SuppressWarnings("unchecked")
+ RestCollectionCreateView<RestResource, RestResource, Object> m =
+ (RestCollectionCreateView<RestResource, RestResource, Object>) viewData.view;
- if (result instanceof Response) {
- @SuppressWarnings("rawtypes")
- Response<?> r = (Response) result;
- status = r.statusCode();
- configureCaching(req, res, rsrc, viewData.view, r.caching());
- } else if (result instanceof Response.Redirect) {
- CacheHeaders.setNotCacheable(res);
- res.sendRedirect(((Response.Redirect) result).location());
- return;
- } else if (result instanceof Response.Accepted) {
- CacheHeaders.setNotCacheable(res);
- res.setStatus(SC_ACCEPTED);
- res.setHeader(HttpHeaders.LOCATION, ((Response.Accepted) result).location());
- return;
- } else {
- CacheHeaders.setNotCacheable(res);
- }
- res.setStatus(status);
+ Type type = inputType(m);
+ inputRequestBody = parseRequest(req, type);
+ result = m.apply(rsrc, path.get(0), inputRequestBody);
+ if (inputRequestBody instanceof RawInput) {
+ try (InputStream is = req.getInputStream()) {
+ ServletUtils.consumeRequestBody(is);
+ }
+ }
+ } else if (viewData.view instanceof RestCollectionDeleteMissingView<?, ?, ?>) {
+ @SuppressWarnings("unchecked")
+ RestCollectionDeleteMissingView<RestResource, RestResource, Object> m =
+ (RestCollectionDeleteMissingView<RestResource, RestResource, Object>) viewData.view;
- if (result != Response.none()) {
- result = Response.unwrap(result);
- if (result instanceof BinaryResult) {
- responseBytes = replyBinaryResult(req, res, (BinaryResult) result);
+ Type type = inputType(m);
+ inputRequestBody = parseRequest(req, type);
+ result = m.apply(rsrc, path.get(0), inputRequestBody);
+ if (inputRequestBody instanceof RawInput) {
+ try (InputStream is = req.getInputStream()) {
+ ServletUtils.consumeRequestBody(is);
+ }
+ }
+ } else if (viewData.view instanceof RestCollectionModifyView<?, ?, ?>) {
+ @SuppressWarnings("unchecked")
+ RestCollectionModifyView<RestResource, RestResource, Object> m =
+ (RestCollectionModifyView<RestResource, RestResource, Object>) viewData.view;
+
+ Type type = inputType(m);
+ inputRequestBody = parseRequest(req, type);
+ result = m.apply(rsrc, inputRequestBody);
+ if (inputRequestBody instanceof RawInput) {
+ try (InputStream is = req.getInputStream()) {
+ ServletUtils.consumeRequestBody(is);
+ }
+ }
} else {
- responseBytes = replyJson(req, res, qp.config(), result);
+ throw new ResourceNotFoundException();
}
- }
- } catch (MalformedJsonException | JsonParseException e) {
- responseBytes =
- replyError(req, res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request", e);
- } catch (BadRequestException e) {
- responseBytes =
- replyError(
- req, res, status = SC_BAD_REQUEST, messageOr(e, "Bad Request"), e.caching(), e);
- } catch (AuthException e) {
- responseBytes =
- replyError(req, res, status = SC_FORBIDDEN, messageOr(e, "Forbidden"), e.caching(), e);
- } catch (AmbiguousViewException e) {
- responseBytes = replyError(req, res, status = SC_NOT_FOUND, messageOr(e, "Ambiguous"), e);
- } catch (ResourceNotFoundException e) {
- responseBytes =
- replyError(req, res, status = SC_NOT_FOUND, messageOr(e, "Not Found"), e.caching(), e);
- } catch (MethodNotAllowedException e) {
- responseBytes =
- replyError(
- req,
- res,
- status = SC_METHOD_NOT_ALLOWED,
- messageOr(e, "Method Not Allowed"),
- e.caching(),
- e);
- } catch (ResourceConflictException e) {
- responseBytes =
- replyError(req, res, status = SC_CONFLICT, messageOr(e, "Conflict"), e.caching(), e);
- } catch (PreconditionFailedException e) {
- responseBytes =
- replyError(
- req,
- res,
- status = SC_PRECONDITION_FAILED,
- messageOr(e, "Precondition Failed"),
- e.caching(),
- e);
- } catch (UnprocessableEntityException e) {
- responseBytes =
- replyError(
- req,
- res,
- status = SC_UNPROCESSABLE_ENTITY,
- messageOr(e, "Unprocessable Entity"),
- e.caching(),
- e);
- } catch (NotImplementedException e) {
- responseBytes =
- replyError(req, res, status = SC_NOT_IMPLEMENTED, messageOr(e, "Not Implemented"), e);
- } catch (UpdateException e) {
- Throwable t = e.getCause();
- if (t instanceof LockFailureException) {
+
+ if (result instanceof Response) {
+ @SuppressWarnings("rawtypes")
+ Response<?> r = (Response) result;
+ status = r.statusCode();
+ configureCaching(req, res, rsrc, viewData.view, r.caching());
+ } else if (result instanceof Response.Redirect) {
+ CacheHeaders.setNotCacheable(res);
+ String location = ((Response.Redirect) result).location();
+ res.sendRedirect(location);
+ logger.atFinest().log("REST call redirected to: %s", location);
+ return;
+ } else if (result instanceof Response.Accepted) {
+ CacheHeaders.setNotCacheable(res);
+ res.setStatus(SC_ACCEPTED);
+ res.setHeader(HttpHeaders.LOCATION, ((Response.Accepted) result).location());
+ logger.atFinest().log("REST call succeeded: %d", SC_ACCEPTED);
+ return;
+ } else {
+ CacheHeaders.setNotCacheable(res);
+ }
+ res.setStatus(status);
+ logger.atFinest().log("REST call succeeded: %d", status);
+
+ if (result != Response.none()) {
+ result = Response.unwrap(result);
+ if (result instanceof BinaryResult) {
+ responseBytes = replyBinaryResult(req, res, (BinaryResult) result);
+ } else {
+ responseBytes = replyJson(req, res, false, qp.config(), result);
+ }
+ }
+ } catch (MalformedJsonException | JsonParseException e) {
responseBytes =
- replyError(req, res, status = SC_SERVICE_UNAVAILABLE, messageOr(t, "Lock failure"), e);
- } else {
+ replyError(
+ req, res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request", e);
+ } catch (BadRequestException e) {
+ responseBytes =
+ replyError(
+ req, res, status = SC_BAD_REQUEST, messageOr(e, "Bad Request"), e.caching(), e);
+ } catch (AuthException e) {
+ responseBytes =
+ replyError(req, res, status = SC_FORBIDDEN, messageOr(e, "Forbidden"), e.caching(), e);
+ } catch (AmbiguousViewException e) {
+ responseBytes = replyError(req, res, status = SC_NOT_FOUND, messageOr(e, "Ambiguous"), e);
+ } catch (ResourceNotFoundException e) {
+ responseBytes =
+ replyError(req, res, status = SC_NOT_FOUND, messageOr(e, "Not Found"), e.caching(), e);
+ } catch (MethodNotAllowedException e) {
+ responseBytes =
+ replyError(
+ req,
+ res,
+ status = SC_METHOD_NOT_ALLOWED,
+ messageOr(e, "Method Not Allowed"),
+ e.caching(),
+ e);
+ } catch (ResourceConflictException e) {
+ responseBytes =
+ replyError(req, res, status = SC_CONFLICT, messageOr(e, "Conflict"), e.caching(), e);
+ } catch (PreconditionFailedException e) {
+ responseBytes =
+ replyError(
+ req,
+ res,
+ status = SC_PRECONDITION_FAILED,
+ messageOr(e, "Precondition Failed"),
+ e.caching(),
+ e);
+ } catch (UnprocessableEntityException e) {
+ responseBytes =
+ replyError(
+ req,
+ res,
+ status = SC_UNPROCESSABLE_ENTITY,
+ messageOr(e, "Unprocessable Entity"),
+ e.caching(),
+ e);
+ } catch (NotImplementedException e) {
+ responseBytes =
+ replyError(req, res, status = SC_NOT_IMPLEMENTED, messageOr(e, "Not Implemented"), e);
+ } catch (UpdateException e) {
+ Throwable t = e.getCause();
+ if (t instanceof LockFailureException) {
+ responseBytes =
+ replyError(
+ req, res, status = SC_SERVICE_UNAVAILABLE, messageOr(t, "Lock failure"), e);
+ } else {
+ status = SC_INTERNAL_SERVER_ERROR;
+ responseBytes = handleException(e, req, res);
+ }
+ } catch (Exception e) {
status = SC_INTERNAL_SERVER_ERROR;
responseBytes = handleException(e, req, res);
+ } finally {
+ String metric =
+ viewData != null && viewData.view != null ? globals.metrics.view(viewData) : "_unknown";
+ globals.metrics.count.increment(metric);
+ if (status >= SC_BAD_REQUEST) {
+ globals.metrics.errorCount.increment(metric, status);
+ }
+ if (responseBytes != -1) {
+ globals.metrics.responseBytes.record(metric, responseBytes);
+ }
+ globals.metrics.serverLatency.record(
+ metric, System.nanoTime() - startNanos, TimeUnit.NANOSECONDS);
+ globals.auditService.dispatch(
+ new ExtendedHttpAuditEvent(
+ globals.webSession.get().getSessionId(),
+ globals.currentUser.get(),
+ req,
+ auditStartTs,
+ qp != null ? qp.params() : ImmutableListMultimap.of(),
+ inputRequestBody,
+ status,
+ result,
+ rsrc,
+ viewData == null ? null : viewData.view));
}
- } catch (Exception e) {
- status = SC_INTERNAL_SERVER_ERROR;
- responseBytes = handleException(e, req, res);
- } finally {
- String metric =
- viewData != null && viewData.view != null ? globals.metrics.view(viewData) : "_unknown";
- globals.metrics.count.increment(metric);
- if (status >= SC_BAD_REQUEST) {
- globals.metrics.errorCount.increment(metric, status);
- }
- if (responseBytes != -1) {
- globals.metrics.responseBytes.record(metric, responseBytes);
- }
- globals.metrics.serverLatency.record(
- metric, System.nanoTime() - startNanos, TimeUnit.NANOSECONDS);
- globals.auditService.dispatch(
- new ExtendedHttpAuditEvent(
- globals.webSession.get().getSessionId(),
- globals.currentUser.get(),
- req,
- auditStartTs,
- qp != null ? qp.params() : ImmutableListMultimap.of(),
- inputRequestBody,
- status,
- result,
- rsrc,
- viewData == null ? null : viewData.view));
}
}
@@ -968,9 +991,22 @@
throw new InstantiationException("Cannot make " + type);
}
+ /**
+ * Sets a JSON reply on the given HTTP servlet response.
+ *
+ * @param req the HTTP servlet request
+ * @param res the HTTP servlet response on which the reply should be set
+ * @param allowTracing whether it is allowed to log the reply if tracing is enabled, must not be
+ * set to {@code true} if the reply may contain sensitive data
+ * @param config config parameters for the JSON formatting
+ * @param result the object that should be formatted as JSON
+ * @return the length of the response
+ * @throws IOException
+ */
public static long replyJson(
@Nullable HttpServletRequest req,
HttpServletResponse res,
+ boolean allowTracing,
ListMultimap<String, String> config,
Object result)
throws IOException {
@@ -985,6 +1021,21 @@
}
w.write('\n');
w.flush();
+
+ if (allowTracing) {
+ logger.atFinest().log(
+ "JSON response body:\n%s",
+ lazy(
+ () -> {
+ try {
+ ByteArrayOutputStream debugOut = new ByteArrayOutputStream();
+ buf.writeTo(debugOut, null);
+ return debugOut.toString(UTF_8.name());
+ } catch (IOException e) {
+ return "<JSON formatting failed>";
+ }
+ }));
+ }
return replyBinaryResult(
req, res, asBinaryResult(buf).setContentType(JSON_TYPE).setCharacterEncoding(UTF_8));
}
@@ -1197,14 +1248,14 @@
}
String name = method + "." + p.get(0);
- RestView<RestResource> core = views.get("gerrit", name);
+ RestView<RestResource> core = views.get(PluginName.GERRIT, name);
if (core != null) {
- return new ViewData("gerrit", core);
+ return new ViewData(PluginName.GERRIT, core);
}
- core = views.get("gerrit", "GET." + p.get(0));
+ core = views.get(PluginName.GERRIT, "GET." + p.get(0));
if (core != null) {
- return new ViewData("gerrit", core);
+ return new ViewData(PluginName.GERRIT, core);
}
Map<String, RestView<RestResource>> r = new TreeMap<>();
@@ -1265,6 +1316,51 @@
}
}
+ private List<String> getParameterNames(HttpServletRequest req) {
+ List<String> parameterNames = new ArrayList<>(req.getParameterMap().keySet());
+ Collections.sort(parameterNames);
+ return parameterNames;
+ }
+
+ private TraceContext enableTracing(HttpServletRequest req, HttpServletResponse res) {
+ // There are 2 ways to enable tracing for REST calls:
+ // 1. by using the 'trace' or 'trace=<trace-id>' request parameter
+ // 2. by setting the 'X-Gerrit-Trace:' or 'X-Gerrit-Trace:<trace-id>' header
+ String traceValueFromHeader = req.getHeader(X_GERRIT_TRACE);
+ String traceValueFromRequestParam = req.getParameter(ParameterParser.TRACE_PARAMETER);
+ boolean doTrace = traceValueFromHeader != null || traceValueFromRequestParam != null;
+
+ // Check whether no trace ID, one trace ID or 2 different trace IDs have been specified.
+ String traceId1;
+ String traceId2;
+ if (!Strings.isNullOrEmpty(traceValueFromHeader)) {
+ traceId1 = traceValueFromHeader;
+ if (!Strings.isNullOrEmpty(traceValueFromRequestParam)
+ && !traceValueFromHeader.equals(traceValueFromRequestParam)) {
+ traceId2 = traceValueFromRequestParam;
+ } else {
+ traceId2 = null;
+ }
+ } else {
+ traceId1 = Strings.emptyToNull(traceValueFromRequestParam);
+ traceId2 = null;
+ }
+
+ // Use the first trace ID to start tracing. If this trace ID is null, a trace ID will be
+ // generated.
+ TraceContext traceContext =
+ TraceContext.newTrace(
+ doTrace,
+ traceId1,
+ (tagName, traceId) -> res.setHeader(X_GERRIT_TRACE, traceId.toString()));
+ // If a second trace ID was specified, add a tag for it as well.
+ if (traceId2 != null) {
+ traceContext.addTag(RequestId.Type.TRACE_ID, traceId2);
+ res.addHeader(X_GERRIT_TRACE, traceId2);
+ }
+ return traceContext;
+ }
+
private boolean isDelete(HttpServletRequest req) {
return "DELETE".equals(req.getMethod());
}
@@ -1296,10 +1392,15 @@
private void checkRequiresCapability(ViewData d)
throws AuthException, PermissionBackendException {
- globals
- .permissionBackend
- .currentUser()
- .checkAny(GlobalPermission.fromAnnotation(d.pluginName, d.view.getClass()));
+ try {
+ globals.permissionBackend.currentUser().check(GlobalPermission.ADMINISTRATE_SERVER);
+ } catch (AuthException e) {
+ // Skiping
+ globals
+ .permissionBackend
+ .currentUser()
+ .checkAny(GlobalPermission.fromAnnotation(d.pluginName, d.view.getClass()));
+ }
}
private static long handleException(
@@ -1341,17 +1442,34 @@
configureCaching(req, res, null, null, c);
checkArgument(statusCode >= 400, "non-error status: %s", statusCode);
res.setStatus(statusCode);
- return replyText(req, res, msg);
+ logger.atFinest().log("REST call failed: %d", statusCode);
+ return replyText(req, res, true, msg);
}
- static long replyText(@Nullable HttpServletRequest req, HttpServletResponse res, String text)
+ /**
+ * Sets a text reply on the given HTTP servlet response.
+ *
+ * @param req the HTTP servlet request
+ * @param res the HTTP servlet response on which the reply should be set
+ * @param allowTracing whether it is allowed to log the reply if tracing is enabled, must not be
+ * set to {@code true} if the reply may contain sensitive data
+ * @param text the text reply
+ * @return the length of the response
+ * @throws IOException
+ */
+ static long replyText(
+ @Nullable HttpServletRequest req, HttpServletResponse res, boolean allowTracing, String text)
throws IOException {
if ((req == null || isRead(req)) && isMaybeHTML(text)) {
- return replyJson(req, res, ImmutableListMultimap.of("pp", "0"), new JsonPrimitive(text));
+ return replyJson(
+ req, res, allowTracing, ImmutableListMultimap.of("pp", "0"), new JsonPrimitive(text));
}
if (!text.endsWith("\n")) {
text += "\n";
}
+ if (allowTracing) {
+ logger.atFinest().log("Text response body:\n%s", text);
+ }
return replyBinaryResult(req, res, BinaryResult.create(text).setContentType(PLAIN_TEXT));
}
diff --git a/java/com/google/gerrit/index/BUILD b/java/com/google/gerrit/index/BUILD
index 6604ca1..d5517e1 100644
--- a/java/com/google/gerrit/index/BUILD
+++ b/java/com/google/gerrit/index/BUILD
@@ -12,19 +12,6 @@
)
java_library(
- name = "query_parser",
- srcs = ["//antlr3:query"],
- visibility = [
- "//javatests/com/google/gerrit/index:__pkg__",
- "//plugins:__pkg__",
- ],
- deps = [
- ":query_exception",
- "//lib/antlr:java-runtime",
- ],
-)
-
-java_library(
name = "index",
srcs = glob(
["**/*.java"],
@@ -33,7 +20,7 @@
visibility = ["//visibility:public"],
deps = [
":query_exception",
- ":query_parser",
+ "//antlr3:query_parser",
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/metrics",
diff --git a/java/com/google/gerrit/index/SiteIndexer.java b/java/com/google/gerrit/index/SiteIndexer.java
index 24b7a69..9c56396 100644
--- a/java/com/google/gerrit/index/SiteIndexer.java
+++ b/java/com/google/gerrit/index/SiteIndexer.java
@@ -15,12 +15,14 @@
package com.google.gerrit.index;
import static com.google.common.base.Preconditions.checkNotNull;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Stopwatch;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.io.OutputStream;
+import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
@@ -64,7 +66,7 @@
protected int totalWork = -1;
protected OutputStream progressOut = NullOutputStream.INSTANCE;
- protected PrintWriter verboseWriter = new PrintWriter(NullOutputStream.INSTANCE);
+ protected PrintWriter verboseWriter = newPrintWriter(NullOutputStream.INSTANCE);
public void setTotalWork(int num) {
totalWork = num;
@@ -75,7 +77,7 @@
}
public void setVerboseOut(OutputStream out) {
- verboseWriter = new PrintWriter(checkNotNull(out));
+ verboseWriter = newPrintWriter(checkNotNull(out));
}
public abstract Result indexAll(I index);
@@ -86,6 +88,10 @@
new ErrorListener(future, desc, progress, ok), MoreExecutors.directExecutor());
}
+ protected PrintWriter newPrintWriter(OutputStream out) {
+ return new PrintWriter(new OutputStreamWriter(out, UTF_8));
+ }
+
private static class ErrorListener implements Runnable {
private final ListenableFuture<?> future;
private final String desc;
diff --git a/java/com/google/gerrit/index/project/ProjectData.java b/java/com/google/gerrit/index/project/ProjectData.java
index 7365660..fb029ac 100644
--- a/java/com/google/gerrit/index/project/ProjectData.java
+++ b/java/com/google/gerrit/index/project/ProjectData.java
@@ -16,6 +16,7 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
+import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.reviewdb.client.Project;
import java.util.ArrayList;
@@ -53,4 +54,11 @@
public ImmutableList<String> getParentNames() {
return tree().stream().skip(1).map(p -> p.getProject().getName()).collect(toImmutableList());
}
+
+ @Override
+ public String toString() {
+ MoreObjects.ToStringHelper h = MoreObjects.toStringHelper(this);
+ h.addValue(project.getName());
+ return h.toString();
+ }
}
diff --git a/java/com/google/gerrit/index/query/AndSource.java b/java/com/google/gerrit/index/query/AndSource.java
index e2605f4..d1e1c30 100644
--- a/java/com/google/gerrit/index/query/AndSource.java
+++ b/java/com/google/gerrit/index/query/AndSource.java
@@ -15,6 +15,7 @@
package com.google.gerrit.index.query;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.base.Throwables;
import com.google.common.collect.FluentIterable;
@@ -26,7 +27,6 @@
import com.google.gwtorm.server.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -175,10 +175,8 @@
return cardinality;
}
- private List<Predicate<T>> sort(Collection<? extends Predicate<T>> that) {
- List<Predicate<T>> r = new ArrayList<>(that);
- Collections.sort(r, this);
- return r;
+ private ImmutableList<Predicate<T>> sort(Collection<? extends Predicate<T>> that) {
+ return that.stream().sorted(this).collect(toImmutableList());
}
@Override
diff --git a/java/com/google/gerrit/index/query/Predicate.java b/java/com/google/gerrit/index/query/Predicate.java
index ca74a52..53c92c9 100644
--- a/java/com/google/gerrit/index/query/Predicate.java
+++ b/java/com/google/gerrit/index/query/Predicate.java
@@ -105,6 +105,18 @@
return getChildren().get(i);
}
+ /** Get the number of leaf terms in this predicate. */
+ public int getLeafCount() {
+ int leafCount = 0;
+ for (Predicate<?> childPredicate : getChildren()) {
+ if (childPredicate instanceof IndexPredicate) {
+ leafCount++;
+ }
+ leafCount += childPredicate.getLeafCount();
+ }
+ return leafCount;
+ }
+
/** Create a copy of this predicate, with new children. */
public abstract Predicate<T> copy(Collection<? extends Predicate<T>> children);
diff --git a/java/com/google/gerrit/index/query/QueryProcessor.java b/java/com/google/gerrit/index/query/QueryProcessor.java
index 27ed72f..1a42ebd 100644
--- a/java/com/google/gerrit/index/query/QueryProcessor.java
+++ b/java/com/google/gerrit/index/query/QueryProcessor.java
@@ -22,11 +22,13 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.index.Index;
import com.google.gerrit.index.IndexCollection;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.IndexRewriter;
+import com.google.gerrit.index.IndexedQuery;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.SchemaDefinitions;
import com.google.gerrit.metrics.Description;
@@ -52,6 +54,8 @@
* holding on to a single instance.
*/
public abstract class QueryProcessor<T> {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
protected static class Metrics {
final Timer1<String> executionTime;
@@ -206,6 +210,7 @@
List<Integer> limits = new ArrayList<>(cnt);
List<Predicate<T>> predicates = new ArrayList<>(cnt);
List<DataSource<T>> sources = new ArrayList<>(cnt);
+ int queryCount = 0;
for (Predicate<T> q : queries) {
int limit = getEffectiveLimit(q);
limits.add(limit);
@@ -224,11 +229,17 @@
// max for this user. The only way to see if there are more entities is to
// ask for one more result from the query.
QueryOptions opts = createOptions(indexConfig, start, limit + 1, getRequestedFields());
+ logger.atFine().log("Query options: " + opts);
Predicate<T> pred = rewriter.rewrite(q, opts);
if (enforceVisibility) {
pred = enforceVisibility(pred);
}
predicates.add(pred);
+ logger.atFine().log(
+ "%s index query[%d]:\n%s",
+ schemaDef.getName(),
+ queryCount++,
+ pred instanceof IndexedQuery ? pred.getChild(0) : pred);
@SuppressWarnings("unchecked")
DataSource<T> s = (DataSource<T>) pred;
@@ -243,12 +254,14 @@
out = new ArrayList<>(cnt);
for (int i = 0; i < cnt; i++) {
+ List<T> matchesList = matches.get(i).toList();
+ logger.atFine().log("Matches[%d]:\n%s", i, matchesList);
out.add(
QueryResult.create(
queryStrings != null ? queryStrings.get(i) : null,
predicates.get(i),
limits.get(i),
- matches.get(i).toList()));
+ matchesList));
}
// Only measure successful queries that actually touched the index.
diff --git a/java/com/google/gerrit/launcher/GerritLauncher.java b/java/com/google/gerrit/launcher/GerritLauncher.java
index 13dad0e..0d26fe7 100644
--- a/java/com/google/gerrit/launcher/GerritLauncher.java
+++ b/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -34,6 +34,7 @@
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.CodeSource;
@@ -44,6 +45,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
import java.util.Scanner;
import java.util.SortedMap;
import java.util.TreeMap;
@@ -644,6 +646,25 @@
return resolveInSourceRoot("eclipse-out");
}
+ public static boolean isJdk9OrLater() {
+ return Double.parseDouble(System.getProperty("java.class.version")) >= 53.0;
+ }
+
+ public static String getJdkVersionPostJdk8() {
+ // 9.0.4 => 9
+ return System.getProperty("java.version").substring(0, 1);
+ }
+
+ public static Properties loadBuildProperties(Path propPath) throws IOException {
+ Properties properties = new Properties();
+ try (InputStream in = Files.newInputStream(propPath)) {
+ properties.load(in);
+ } catch (NoSuchFileException e) {
+ // Ignore; will be run from PATH, with a descriptive error if it fails.
+ }
+ return properties;
+ }
+
static final String SOURCE_ROOT_RESOURCE = "/com/google/gerrit/launcher/workspace-root.txt";
/**
@@ -708,14 +729,36 @@
return ret;
}
- private static ClassLoader useDevClasspath() throws MalformedURLException, FileNotFoundException {
+ private static ClassLoader useDevClasspath() throws IOException {
Path out = getDeveloperEclipseOut();
List<URL> dirs = new ArrayList<>();
dirs.add(out.resolve("classes").toUri().toURL());
ClassLoader cl = GerritLauncher.class.getClassLoader();
- for (URL u : ((URLClassLoader) cl).getURLs()) {
- if (includeJar(u)) {
- dirs.add(u);
+
+ if (isJdk9OrLater()) {
+ Path rootPath = resolveInSourceRoot(".").normalize();
+
+ Properties properties = loadBuildProperties(rootPath.resolve(".bazel_path"));
+ Path outputBase = Paths.get(properties.getProperty("output_base"));
+
+ Path runtimeClasspath =
+ rootPath.resolve("bazel-bin/tools/eclipse/main_classpath_collect.runtime_classpath");
+ for (String f : Files.readAllLines(runtimeClasspath, UTF_8)) {
+ URL url;
+ if (f.startsWith("external")) {
+ url = outputBase.resolve(f).toUri().toURL();
+ } else {
+ url = rootPath.resolve(f).toUri().toURL();
+ }
+ if (includeJar(url)) {
+ dirs.add(url);
+ }
+ }
+ } else {
+ for (URL u : ((URLClassLoader) cl).getURLs()) {
+ if (includeJar(u)) {
+ dirs.add(u);
+ }
}
}
return URLClassLoader.newInstance(
@@ -724,7 +767,9 @@
private static boolean includeJar(URL u) {
String path = u.getPath();
- return path.endsWith(".jar") && !path.endsWith("-src.jar");
+ return path.endsWith(".jar")
+ && !path.endsWith("-src.jar")
+ && !path.contains("/com/google/gerrit");
}
private GerritLauncher() {}
diff --git a/java/com/google/gerrit/lucene/AbstractLuceneIndex.java b/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
index 3871ced..dc293cd 100644
--- a/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
+++ b/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
@@ -39,6 +39,8 @@
import com.google.gerrit.index.query.FieldBundle;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.IndexUtils;
+import com.google.gerrit.server.logging.LoggingContextAwareExecutorService;
+import com.google.gerrit.server.logging.LoggingContextAwareScheduledExecutorService;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import java.io.IOException;
@@ -54,6 +56,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -99,7 +102,7 @@
private final ReferenceManager<IndexSearcher> searcherManager;
private final ControlledRealTimeReopenThread<IndexSearcher> reopenThread;
private final Set<NrtFuture> notDoneNrtFutures;
- private ScheduledThreadPoolExecutor autoCommitExecutor;
+ private ScheduledExecutorService autoCommitExecutor;
AbstractLuceneIndex(
Schema<V> schema,
@@ -128,12 +131,13 @@
delegateWriter = autoCommitWriter;
autoCommitExecutor =
- new ScheduledThreadPoolExecutor(
- 1,
- new ThreadFactoryBuilder()
- .setNameFormat(index + " Commit-%d")
- .setDaemon(true)
- .build());
+ new LoggingContextAwareScheduledExecutorService(
+ new ScheduledThreadPoolExecutor(
+ 1,
+ new ThreadFactoryBuilder()
+ .setNameFormat(index + " Commit-%d")
+ .setDaemon(true)
+ .build()));
@SuppressWarnings("unused") // Error handling within Runnable.
Future<?> possiblyIgnoredError =
autoCommitExecutor.scheduleAtFixedRate(
@@ -168,12 +172,13 @@
writerThread =
MoreExecutors.listeningDecorator(
- Executors.newFixedThreadPool(
- 1,
- new ThreadFactoryBuilder()
- .setNameFormat(index + " Write-%d")
- .setDaemon(true)
- .build()));
+ new LoggingContextAwareExecutorService(
+ Executors.newFixedThreadPool(
+ 1,
+ new ThreadFactoryBuilder()
+ .setNameFormat(index + " Write-%d")
+ .setDaemon(true)
+ .build())));
reopenThread =
new ControlledRealTimeReopenThread<>(
diff --git a/java/com/google/gerrit/lucene/BUILD b/java/com/google/gerrit/lucene/BUILD
index 6cb7751..9c6ba74 100644
--- a/java/com/google/gerrit/lucene/BUILD
+++ b/java/com/google/gerrit/lucene/BUILD
@@ -33,6 +33,7 @@
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/logging",
"//lib:guava",
"//lib:gwtorm",
"//lib/flogger:api",
diff --git a/java/com/google/gerrit/metrics/DisabledMetricMaker.java b/java/com/google/gerrit/metrics/DisabledMetricMaker.java
index ea408e2..d3c030d 100644
--- a/java/com/google/gerrit/metrics/DisabledMetricMaker.java
+++ b/java/com/google/gerrit/metrics/DisabledMetricMaker.java
@@ -68,7 +68,7 @@
@Override
public Timer0 newTimer(String name, Description desc) {
- return new Timer0() {
+ return new Timer0(name) {
@Override
public void record(long value, TimeUnit unit) {}
@@ -79,7 +79,7 @@
@Override
public <F1> Timer1<F1> newTimer(String name, Description desc, Field<F1> field1) {
- return new Timer1<F1>() {
+ return new Timer1<F1>(name) {
@Override
public void record(F1 field1, long value, TimeUnit unit) {}
@@ -91,7 +91,7 @@
@Override
public <F1, F2> Timer2<F1, F2> newTimer(
String name, Description desc, Field<F1> field1, Field<F2> field2) {
- return new Timer2<F1, F2>() {
+ return new Timer2<F1, F2>(name) {
@Override
public void record(F1 field1, F2 field2, long value, TimeUnit unit) {}
@@ -103,7 +103,7 @@
@Override
public <F1, F2, F3> Timer3<F1, F2, F3> newTimer(
String name, Description desc, Field<F1> field1, Field<F2> field2, Field<F3> field3) {
- return new Timer3<F1, F2, F3>() {
+ return new Timer3<F1, F2, F3>(name) {
@Override
public void record(F1 field1, F2 field2, F3 field3, long value, TimeUnit unit) {}
diff --git a/java/com/google/gerrit/metrics/Timer0.java b/java/com/google/gerrit/metrics/Timer0.java
index 55d1ddf..225b76f 100644
--- a/java/com/google/gerrit/metrics/Timer0.java
+++ b/java/com/google/gerrit/metrics/Timer0.java
@@ -16,6 +16,7 @@
import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import java.util.concurrent.TimeUnit;
@@ -31,6 +32,8 @@
*/
public abstract class Timer0 implements RegistrationHandle {
public static class Context extends TimerContext {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
private final Timer0 timer;
Context(Timer0 timer) {
@@ -39,10 +42,17 @@
@Override
public void record(long elapsed) {
+ logger.atFinest().log("%s took %dms", timer.name, TimeUnit.NANOSECONDS.toMillis(elapsed));
timer.record(elapsed, NANOSECONDS);
}
}
+ protected final String name;
+
+ public Timer0(String name) {
+ this.name = name;
+ }
+
/**
* Begin a timer for the current block, value will be recorded when closed.
*
diff --git a/java/com/google/gerrit/metrics/Timer1.java b/java/com/google/gerrit/metrics/Timer1.java
index f623841..0db0353 100644
--- a/java/com/google/gerrit/metrics/Timer1.java
+++ b/java/com/google/gerrit/metrics/Timer1.java
@@ -16,6 +16,7 @@
import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import java.util.concurrent.TimeUnit;
@@ -33,6 +34,8 @@
*/
public abstract class Timer1<F1> implements RegistrationHandle {
public static class Context extends TimerContext {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
private final Timer1<Object> timer;
private final Object field1;
@@ -44,10 +47,18 @@
@Override
public void record(long elapsed) {
+ logger.atFinest().log(
+ "%s (%s) took %dms", timer.name, field1, TimeUnit.NANOSECONDS.toMillis(elapsed));
timer.record(field1, elapsed, NANOSECONDS);
}
}
+ protected final String name;
+
+ public Timer1(String name) {
+ this.name = name;
+ }
+
/**
* Begin a timer for the current block, value will be recorded when closed.
*
diff --git a/java/com/google/gerrit/metrics/Timer2.java b/java/com/google/gerrit/metrics/Timer2.java
index b03ff83..cfdfb7a 100644
--- a/java/com/google/gerrit/metrics/Timer2.java
+++ b/java/com/google/gerrit/metrics/Timer2.java
@@ -16,6 +16,7 @@
import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import java.util.concurrent.TimeUnit;
@@ -34,6 +35,8 @@
*/
public abstract class Timer2<F1, F2> implements RegistrationHandle {
public static class Context extends TimerContext {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
private final Timer2<Object, Object> timer;
private final Object field1;
private final Object field2;
@@ -47,10 +50,19 @@
@Override
public void record(long elapsed) {
+ logger.atFinest().log(
+ "%s (%s, %s) took %dms",
+ timer.name, field1, field2, TimeUnit.NANOSECONDS.toMillis(elapsed));
timer.record(field1, field2, elapsed, NANOSECONDS);
}
}
+ protected final String name;
+
+ public Timer2(String name) {
+ this.name = name;
+ }
+
/**
* Begin a timer for the current block, value will be recorded when closed.
*
diff --git a/java/com/google/gerrit/metrics/Timer3.java b/java/com/google/gerrit/metrics/Timer3.java
index 91af42c..1711445 100644
--- a/java/com/google/gerrit/metrics/Timer3.java
+++ b/java/com/google/gerrit/metrics/Timer3.java
@@ -16,6 +16,7 @@
import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import java.util.concurrent.TimeUnit;
@@ -35,6 +36,8 @@
*/
public abstract class Timer3<F1, F2, F3> implements RegistrationHandle {
public static class Context extends TimerContext {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
private final Timer3<Object, Object, Object> timer;
private final Object field1;
private final Object field2;
@@ -50,10 +53,19 @@
@Override
public void record(long elapsed) {
+ logger.atFinest().log(
+ "%s (%s, %s, %s) took %dms",
+ timer.name, field1, field2, field3, TimeUnit.NANOSECONDS.toMillis(elapsed));
timer.record(field1, field2, field3, elapsed, NANOSECONDS);
}
}
+ protected final String name;
+
+ public Timer3(String name) {
+ this.name = name;
+ }
+
/**
* Begin a timer for the current block, value will be recorded when closed.
*
diff --git a/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java b/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java
index 3b19a62..a7ffe07 100644
--- a/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java
+++ b/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java
@@ -26,7 +26,7 @@
/** Abstract timer broken down into buckets by {@link Field} values. */
abstract class BucketedTimer implements BucketedMetric {
private final DropWizardMetricMaker metrics;
- private final String name;
+ protected final String name;
private final Description.FieldOrdering ordering;
protected final Field<?>[] fields;
protected final TimerImpl total;
diff --git a/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java b/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java
index fc53ee7..ead718f 100644
--- a/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java
+++ b/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java
@@ -393,11 +393,10 @@
}
class TimerImpl extends Timer0 {
- private final String name;
final com.codahale.metrics.Timer metric;
private TimerImpl(String name, com.codahale.metrics.Timer metric) {
- this.name = name;
+ super(name);
this.metric = metric;
}
diff --git a/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java b/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java
index fe6f70e..fc4ba3f 100644
--- a/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java
+++ b/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java
@@ -27,7 +27,7 @@
}
Timer1<F1> timer() {
- return new Timer1<F1>() {
+ return new Timer1<F1>(name) {
@Override
public void record(F1 field1, long value, TimeUnit unit) {
total.record(value, unit);
diff --git a/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java b/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java
index 43cc290..d04a65e 100644
--- a/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java
+++ b/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java
@@ -30,7 +30,7 @@
}
<F1, F2> Timer2<F1, F2> timer2() {
- return new Timer2<F1, F2>() {
+ return new Timer2<F1, F2>(name) {
@Override
public void record(F1 field1, F2 field2, long value, TimeUnit unit) {
total.record(value, unit);
@@ -45,7 +45,7 @@
}
<F1, F2, F3> Timer3<F1, F2, F3> timer3() {
- return new Timer3<F1, F2, F3>() {
+ return new Timer3<F1, F2, F3>(name) {
@Override
public void record(F1 field1, F2 field2, F3 field3, long value, TimeUnit unit) {
total.record(value, unit);
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 517787c..1a9af55 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -26,6 +26,7 @@
import com.google.gerrit.extensions.client.AuthType;
import com.google.gerrit.gpg.GpgModule;
import com.google.gerrit.httpd.AllRequestFilter;
+import com.google.gerrit.httpd.GerritAuthModule;
import com.google.gerrit.httpd.GetUserFilter;
import com.google.gerrit.httpd.GitOverHttpModule;
import com.google.gerrit.httpd.H2CacheBasedWebSession;
@@ -575,10 +576,11 @@
modules.add(new ProjectQoSFilter.Module());
}
modules.add(RequestContextFilter.module());
- modules.add(AllRequestFilter.module());
modules.add(RequestMetricsFilter.module());
modules.add(H2CacheBasedWebSession.module());
+ modules.add(sysInjector.getInstance(GerritAuthModule.class));
modules.add(sysInjector.getInstance(GitOverHttpModule.class));
+ modules.add(AllRequestFilter.module());
modules.add(sysInjector.getInstance(WebModule.class));
modules.add(sysInjector.getInstance(RequireSslFilter.Module.class));
modules.add(new HttpPluginModule());
diff --git a/java/com/google/gerrit/pgm/MigrateToNoteDb.java b/java/com/google/gerrit/pgm/MigrateToNoteDb.java
index 07da3f7..61d7ed9 100644
--- a/java/com/google/gerrit/pgm/MigrateToNoteDb.java
+++ b/java/com/google/gerrit/pgm/MigrateToNoteDb.java
@@ -16,6 +16,7 @@
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
@@ -39,6 +40,7 @@
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
+import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -141,7 +143,7 @@
migrator.migrate();
}
}
- try (PrintWriter w = new PrintWriter(System.out, true)) {
+ try (PrintWriter w = new PrintWriter(new OutputStreamWriter(System.out, UTF_8), true)) {
gcAllUsers.run(w);
}
} finally {
diff --git a/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java b/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
index 707802e..6336c93 100644
--- a/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
+++ b/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
@@ -16,10 +16,10 @@
import com.google.gerrit.pgm.init.api.AllUsersNameOnInitProvider;
import com.google.gerrit.pgm.init.api.InitFlags;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIdNotes;
+import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
@@ -39,13 +39,13 @@
public class ExternalIdsOnInit {
private final InitFlags flags;
private final SitePaths site;
- private final String allUsers;
+ private final AllUsersName allUsers;
@Inject
public ExternalIdsOnInit(InitFlags flags, SitePaths site, AllUsersNameOnInitProvider allUsers) {
this.flags = flags;
this.site = site;
- this.allUsers = allUsers.get();
+ this.allUsers = new AllUsersName(allUsers.get());
}
public synchronized void insert(String commitMessage, Collection<ExternalId> extIds)
@@ -53,11 +53,10 @@
File path = getPath();
if (path != null) {
try (Repository allUsersRepo = new FileRepository(path)) {
- ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsersRepo);
+ ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsers, allUsersRepo);
extIdNotes.insert(extIds);
try (MetaDataUpdate metaDataUpdate =
- new MetaDataUpdate(
- GitReferenceUpdated.DISABLED, new Project.NameKey(allUsers), allUsersRepo)) {
+ new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsers, allUsersRepo)) {
PersonIdent serverIdent = new GerritPersonIdentProvider(flags.cfg).get();
metaDataUpdate.getCommitBuilder().setAuthor(serverIdent);
metaDataUpdate.getCommitBuilder().setCommitter(serverIdent);
@@ -73,6 +72,6 @@
if (basePath == null) {
throw new IllegalStateException("gerrit.basePath must be configured");
}
- return FileKey.resolve(basePath.resolve(allUsers).toFile(), FS.DETECTED);
+ return FileKey.resolve(basePath.resolve(allUsers.get()).toFile(), FS.DETECTED);
}
}
diff --git a/java/com/google/gerrit/pgm/init/GroupsOnInit.java b/java/com/google/gerrit/pgm/init/GroupsOnInit.java
index 8fc9119..8e06aa1 100644
--- a/java/com/google/gerrit/pgm/init/GroupsOnInit.java
+++ b/java/com/google/gerrit/pgm/init/GroupsOnInit.java
@@ -25,9 +25,9 @@
import com.google.gerrit.pgm.init.api.InitFlags;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdentProvider;
+import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerIdProvider;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
@@ -64,13 +64,13 @@
private final InitFlags flags;
private final SitePaths site;
- private final String allUsers;
+ private final AllUsersName allUsers;
@Inject
public GroupsOnInit(InitFlags flags, SitePaths site, AllUsersNameOnInitProvider allUsers) {
this.flags = flags;
this.site = site;
- this.allUsers = allUsers.get();
+ this.allUsers = new AllUsersName(allUsers.get());
}
/**
@@ -90,7 +90,7 @@
if (allUsersRepoPath != null) {
try (Repository allUsersRepo = new FileRepository(allUsersRepoPath)) {
AccountGroup.UUID groupUuid = groupReference.getUUID();
- GroupConfig groupConfig = GroupConfig.loadForGroup(allUsersRepo, groupUuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(allUsers, allUsersRepo, groupUuid);
return groupConfig
.getLoadedGroup()
.orElseThrow(() -> new NoSuchGroupException(groupReference.getUUID()));
@@ -145,7 +145,7 @@
private void addGroupMemberInNoteDb(
Repository repository, AccountGroup.UUID groupUuid, Account account)
throws IOException, ConfigInvalidException, NoSuchGroupException {
- GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(allUsers, repository, groupUuid);
InternalGroup group =
groupConfig.getLoadedGroup().orElseThrow(() -> new NoSuchGroupException(groupUuid));
@@ -160,7 +160,7 @@
private File getPathToAllUsersRepository() {
Path basePath = site.resolve(flags.cfg.getString("gerrit", null, "basePath"));
checkArgument(basePath != null, "gerrit.basePath must be configured");
- return RepositoryCache.FileKey.resolve(basePath.resolve(allUsers).toFile(), FS.DETECTED);
+ return RepositoryCache.FileKey.resolve(basePath.resolve(allUsers.get()).toFile(), FS.DETECTED);
}
private static InternalGroupUpdate getMemberAdditionUpdate(Account account) {
@@ -186,7 +186,7 @@
private MetaDataUpdate createMetaDataUpdate(Repository repository, PersonIdent personIdent) {
MetaDataUpdate metaDataUpdate =
- new MetaDataUpdate(GitReferenceUpdated.DISABLED, new Project.NameKey(allUsers), repository);
+ new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsers, repository);
metaDataUpdate.getCommitBuilder().setAuthor(personIdent);
metaDataUpdate.getCommitBuilder().setCommitter(personIdent);
return metaDataUpdate;
diff --git a/java/com/google/gerrit/pgm/init/InitLogging.java b/java/com/google/gerrit/pgm/init/InitLogging.java
new file mode 100644
index 0000000..52d0d2f
--- /dev/null
+++ b/java/com/google/gerrit/pgm/init/InitLogging.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2018 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.init;
+
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
+import com.google.inject.Inject;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class InitLogging implements InitStep {
+ private static final String CONTAINER = "container";
+ private static final String JAVA_OPTIONS = "javaOptions";
+ private static final String FLOGGER_BACKEND_PROPERTY = "flogger.backend_factory";
+ private static final String FLOGGER_LOGGING_CONTEXT = "flogger.logging_context";
+
+ private final Section container;
+
+ @Inject
+ public InitLogging(Section.Factory sections) {
+ this.container = sections.get(CONTAINER, null);
+ }
+
+ @Override
+ public void run() throws Exception {
+ List<String> javaOptions = new ArrayList<>(Arrays.asList(container.getList(JAVA_OPTIONS)));
+ if (!isSet(javaOptions, FLOGGER_BACKEND_PROPERTY)) {
+ javaOptions.add(
+ getJavaOption(
+ FLOGGER_BACKEND_PROPERTY,
+ "com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance"));
+ }
+ if (!isSet(javaOptions, FLOGGER_LOGGING_CONTEXT)) {
+ javaOptions.add(
+ getJavaOption(
+ FLOGGER_LOGGING_CONTEXT,
+ "com.google.gerrit.server.logging.LoggingContext#getInstance"));
+ }
+ container.setList(JAVA_OPTIONS, javaOptions);
+ }
+
+ private static boolean isSet(List<String> javaOptions, String javaOptionName) {
+ return javaOptions
+ .stream()
+ .anyMatch(
+ o ->
+ o.startsWith("-D" + javaOptionName + "=")
+ || o.startsWith("\"-D" + javaOptionName + "="));
+ }
+
+ private static String getJavaOption(String javaOptionName, String value) {
+ return String.format("-D%s=%s", javaOptionName, value);
+ }
+}
diff --git a/java/com/google/gerrit/pgm/init/InitModule.java b/java/com/google/gerrit/pgm/init/InitModule.java
index 65cf355..f677ceb 100644
--- a/java/com/google/gerrit/pgm/init/InitModule.java
+++ b/java/com/google/gerrit/pgm/init/InitModule.java
@@ -49,6 +49,7 @@
if (initDb) {
step().to(InitDatabase.class);
}
+ step().to(InitLogging.class);
step().to(InitIndex.class);
step().to(InitAuth.class);
step().to(InitAdminUser.class);
diff --git a/java/com/google/gerrit/pgm/init/InitPlugins.java b/java/com/google/gerrit/pgm/init/InitPlugins.java
index 385d20c..e43114c 100644
--- a/java/com/google/gerrit/pgm/init/InitPlugins.java
+++ b/java/com/google/gerrit/pgm/init/InitPlugins.java
@@ -14,7 +14,8 @@
package com.google.gerrit.pgm.init;
-import com.google.common.collect.FluentIterable;
+import static java.util.Comparator.comparing;
+
import com.google.gerrit.common.PluginData;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitFlags;
@@ -25,12 +26,10 @@
import com.google.inject.Injector;
import com.google.inject.Singleton;
import java.io.IOException;
-import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Comparator;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
@@ -58,25 +57,16 @@
throws IOException {
final List<PluginData> result = new ArrayList<>();
pluginsDistribution.foreach(
- new PluginsDistribution.Processor() {
- @Override
- public void process(String pluginName, InputStream in) throws IOException {
- Path tmpPlugin = JarPluginProvider.storeInTemp(pluginName, in, site);
- String pluginVersion = getVersion(tmpPlugin);
- if (deleteTempPluginFile) {
- Files.delete(tmpPlugin);
- }
- result.add(new PluginData(pluginName, pluginVersion, tmpPlugin));
+ (pluginName, in) -> {
+ Path tmpPlugin = JarPluginProvider.storeInTemp(pluginName, in, site);
+ String pluginVersion = getVersion(tmpPlugin);
+ if (deleteTempPluginFile) {
+ Files.delete(tmpPlugin);
}
+ result.add(new PluginData(pluginName, pluginVersion, tmpPlugin));
});
- return FluentIterable.from(result)
- .toSortedList(
- new Comparator<PluginData>() {
- @Override
- public int compare(PluginData a, PluginData b) {
- return a.name.compareTo(b.name);
- }
- });
+ result.sort(comparing(p -> p.name));
+ return result;
}
private final ConsoleUI ui;
diff --git a/java/com/google/gerrit/pgm/init/InitSshd.java b/java/com/google/gerrit/pgm/init/InitSshd.java
index 0cc30f8..68bdefc 100644
--- a/java/com/google/gerrit/pgm/init/InitSshd.java
+++ b/java/com/google/gerrit/pgm/init/InitSshd.java
@@ -103,7 +103,7 @@
"-q" /* quiet */,
"-t",
"rsa",
- "-P",
+ "-N",
emptyPassphraseArg,
"-C",
comment,
diff --git a/java/com/google/gerrit/pgm/init/api/Section.java b/java/com/google/gerrit/pgm/init/api/Section.java
index 009e989..baf37b6 100644
--- a/java/com/google/gerrit/pgm/init/api/Section.java
+++ b/java/com/google/gerrit/pgm/init/api/Section.java
@@ -23,6 +23,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -59,6 +60,10 @@
return flags.cfg.getString(section, subsection, name);
}
+ public String[] getList(String name) {
+ return flags.cfg.getStringList(section, subsection, name);
+ }
+
public void set(String name, String value) {
final ArrayList<String> all = new ArrayList<>();
all.addAll(Arrays.asList(flags.cfg.getStringList(section, subsection, name)));
@@ -79,6 +84,10 @@
}
}
+ public void setList(String name, List<String> values) {
+ flags.cfg.setStringList(section, subsection, name, values);
+ }
+
public <T extends Enum<?>> void set(String name, T value) {
if (value != null) {
set(name, value.name());
diff --git a/java/com/google/gerrit/pgm/init/api/VersionedMetaDataOnInit.java b/java/com/google/gerrit/pgm/init/api/VersionedMetaDataOnInit.java
index e3b95ee..738cafd 100644
--- a/java/com/google/gerrit/pgm/init/api/VersionedMetaDataOnInit.java
+++ b/java/com/google/gerrit/pgm/init/api/VersionedMetaDataOnInit.java
@@ -14,6 +14,7 @@
package com.google.gerrit.pgm.init.api;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.meta.VersionedMetaData;
@@ -57,7 +58,7 @@
File path = getPath();
if (path != null) {
try (Repository repo = new FileRepository(path)) {
- load(repo);
+ load(new Project.NameKey(project), repo);
}
}
return this;
diff --git a/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
index 8d77ed8..683a205 100644
--- a/java/com/google/gerrit/pgm/util/BatchProgramModule.java
+++ b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -75,6 +75,7 @@
import com.google.gerrit.server.query.change.ChangeQueryProcessor;
import com.google.gerrit.server.restapi.group.GroupModule;
import com.google.gerrit.server.rules.DefaultSubmitRule;
+import com.google.gerrit.server.rules.IgnoreSelfApprovalRule;
import com.google.gerrit.server.rules.PrologModule;
import com.google.gerrit.server.rules.SubmitRule;
import com.google.gerrit.server.update.BatchUpdate;
@@ -188,6 +189,7 @@
factory(SubmitRuleEvaluator.Factory.class);
install(new PrologModule());
install(new DefaultSubmitRule.Module());
+ install(new IgnoreSelfApprovalRule.Module());
bind(ChangeJson.Factory.class).toProvider(Providers.<ChangeJson.Factory>of(null));
bind(EventUtil.class).toProvider(Providers.<EventUtil>of(null));
diff --git a/java/com/google/gerrit/pgm/util/SiteProgram.java b/java/com/google/gerrit/pgm/util/SiteProgram.java
index 057496f..1338efb 100644
--- a/java/com/google/gerrit/pgm/util/SiteProgram.java
+++ b/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -64,6 +64,8 @@
import org.kohsuke.args4j.Option;
public abstract class SiteProgram extends AbstractProgram {
+ private static final String CONNECTION_ERROR = "Cannot connect to SQL database";
+
@Option(
name = "--site-path",
aliases = {"-d"},
@@ -106,14 +108,13 @@
/** @return provides database connectivity and site path. */
protected Injector createDbInjector(boolean enableMetrics, DataSourceProvider.Context context) {
- Path sitePath = getSitePath();
List<Module> modules = new ArrayList<>();
Module sitePathModule =
new AbstractModule() {
@Override
protected void configure() {
- bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
+ bind(Path.class).annotatedWith(SitePath.class).toInstance(getSitePath());
bind(String.class)
.annotatedWith(SecureStoreClassName.class)
.toProvider(Providers.of(getConfiguredSecureStoreClass()));
@@ -198,16 +199,16 @@
Throwable why = first.getCause();
if (why instanceof SQLException) {
- throw die("Cannot connect to SQL database", why);
+ throw die(CONNECTION_ERROR, why);
}
if (why instanceof OrmException
&& why.getCause() != null
&& "Unable to determine driver URL".equals(why.getMessage())) {
why = why.getCause();
if (isCannotCreatePoolException(why)) {
- throw die("Cannot connect to SQL database", why.getCause());
+ throw die(CONNECTION_ERROR, why.getCause());
}
- throw die("Cannot connect to SQL database", why);
+ throw die(CONNECTION_ERROR, why);
}
StringBuilder buf = new StringBuilder();
@@ -259,8 +260,9 @@
for (Binding<DataSourceType> binding : dsTypeBindings) {
Annotation annotation = binding.getKey().getAnnotation();
if (annotation instanceof Named) {
- if (((Named) annotation).value().toLowerCase().contains(dbProductName)) {
- return ((Named) annotation).value();
+ Named named = (Named) annotation;
+ if (named.value().toLowerCase().contains(dbProductName)) {
+ return named.value();
}
}
}
diff --git a/java/com/google/gerrit/proto/ProtoGen.java b/java/com/google/gerrit/proto/ProtoGen.java
index 1c55a05..4a1598b 100644
--- a/java/com/google/gerrit/proto/ProtoGen.java
+++ b/java/com/google/gerrit/proto/ProtoGen.java
@@ -34,12 +34,11 @@
public class ProtoGen {
@Option(
- name = "--output",
- aliases = {"-o"},
- required = true,
- metaVar = "FILE",
- usage = "File to write .proto into"
- )
+ name = "--output",
+ aliases = {"-o"},
+ required = true,
+ metaVar = "FILE",
+ usage = "File to write .proto into")
private File file;
public static void main(String[] argv) throws Exception {
diff --git a/java/com/google/gerrit/reviewdb/client/Change.java b/java/com/google/gerrit/reviewdb/client/Change.java
index 201315e..8d4de05 100644
--- a/java/com/google/gerrit/reviewdb/client/Change.java
+++ b/java/com/google/gerrit/reviewdb/client/Change.java
@@ -253,7 +253,10 @@
}
}
- /** Globally unique identification of this change. */
+ /**
+ * Globally unique identification of this change. This generally takes the form of a string
+ * "Ixxxxxx...", and is stored in the Change-Id footer of a commit.
+ */
public static class Key extends StringKey<com.google.gwtorm.client.Key<?>> {
private static final long serialVersionUID = 1L;
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index e99c686..e5bc480 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -40,16 +40,43 @@
"//java/com/google/gerrit/metrics",
"//java/com/google/gerrit/prettify:server",
"//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/server/cache/serialize",
"//java/com/google/gerrit/server/ioutil",
+ "//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/server/util/git",
"//java/com/google/gerrit/util/cli",
"//java/com/google/gerrit/util/ssl",
"//java/org/apache/commons/net",
"//java/org/eclipse/jgit:server",
"//lib:args4j",
+ "//lib:autolink",
"//lib:automaton",
"//lib:blame-cache",
- "//lib:grappa",
+ "//lib:flexmark",
+ "//lib:flexmark-ext-abbreviation",
+ "//lib:flexmark-ext-anchorlink",
+ "//lib:flexmark-ext-autolink",
+ "//lib:flexmark-ext-definition",
+ "//lib:flexmark-ext-emoji",
+ "//lib:flexmark-ext-escaped-character",
+ "//lib:flexmark-ext-footnotes",
+ "//lib:flexmark-ext-gfm-issues",
+ "//lib:flexmark-ext-gfm-strikethrough",
+ "//lib:flexmark-ext-gfm-tables",
+ "//lib:flexmark-ext-gfm-tasklist",
+ "//lib:flexmark-ext-gfm-users",
+ "//lib:flexmark-ext-ins",
+ "//lib:flexmark-ext-jekyll-front-matter",
+ "//lib:flexmark-ext-superscript",
+ "//lib:flexmark-ext-tables",
+ "//lib:flexmark-ext-toc",
+ "//lib:flexmark-ext-typographic",
+ "//lib:flexmark-ext-wikilink",
+ "//lib:flexmark-ext-yaml-front-matter",
+ "//lib:flexmark-formatter",
+ "//lib:flexmark-html-parser",
+ "//lib:flexmark-profile-pegdown",
+ "//lib:flexmark-util",
"//lib:gson",
"//lib:guava",
"//lib:guava-retrying",
@@ -58,7 +85,6 @@
"//lib:jsch",
"//lib:juniversalchardet",
"//lib:mime-util",
- "//lib:pegdown",
"//lib:protobuf",
"//lib:servlet-api-3_1",
"//lib:soy",
diff --git a/java/com/google/gerrit/server/ChangeUtil.java b/java/com/google/gerrit/server/ChangeUtil.java
index d90f5d0..571f322 100644
--- a/java/com/google/gerrit/server/ChangeUtil.java
+++ b/java/com/google/gerrit/server/ChangeUtil.java
@@ -37,17 +37,9 @@
private static final Random UUID_RANDOM = new SecureRandom();
private static final BaseEncoding UUID_ENCODING = BaseEncoding.base16().lowerCase();
- private static final int SUBJECT_MAX_LENGTH = 80;
- private static final String SUBJECT_CROP_APPENDIX = "...";
- private static final int SUBJECT_CROP_RANGE = 10;
-
public static final Ordering<PatchSet> PS_ID_ORDER =
Ordering.from(comparingInt(PatchSet::getPatchSetId));
- public static String formatChangeUrl(String canonicalWebUrl, Change change) {
- return canonicalWebUrl + "c/" + change.getProject().get() + "/+/" + change.getChangeId();
- }
-
/** @return a new unique identifier for change message entities. */
public static String messageUuid() {
byte[] buf = new byte[8];
@@ -123,21 +115,6 @@
id);
}
- public static String cropSubject(String subject) {
- if (subject.length() > SUBJECT_MAX_LENGTH) {
- int maxLength = SUBJECT_MAX_LENGTH - SUBJECT_CROP_APPENDIX.length();
- for (int cropPosition = maxLength;
- cropPosition > maxLength - SUBJECT_CROP_RANGE;
- cropPosition--) {
- if (Character.isWhitespace(subject.charAt(cropPosition - 1))) {
- return subject.substring(0, cropPosition) + SUBJECT_CROP_APPENDIX;
- }
- }
- return subject.substring(0, maxLength) + SUBJECT_CROP_APPENDIX;
- }
- return subject;
- }
-
public static String status(Change c) {
return c != null ? c.getStatus().name().toLowerCase() : "deleted";
}
diff --git a/java/com/google/gerrit/server/CommentsUtil.java b/java/com/google/gerrit/server/CommentsUtil.java
index 18d9b3d..99dfbbb 100644
--- a/java/com/google/gerrit/server/CommentsUtil.java
+++ b/java/com/google/gerrit/server/CommentsUtil.java
@@ -502,7 +502,7 @@
}
private static <T extends Comment> List<T> sort(List<T> comments) {
- Collections.sort(comments, COMMENT_ORDER);
+ comments.sort(COMMENT_ORDER);
return comments;
}
diff --git a/java/com/google/gerrit/server/IdentifiedUser.java b/java/com/google/gerrit/server/IdentifiedUser.java
index 16546f9..d9a4cae 100644
--- a/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/java/com/google/gerrit/server/IdentifiedUser.java
@@ -15,10 +15,12 @@
package com.google.gerrit.server;
import static com.google.common.base.MoreObjects.firstNonNull;
+import static com.google.common.flogger.LazyArgs.lazy;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountCache;
@@ -54,6 +56,8 @@
/** An authenticated user. */
public class IdentifiedUser extends CurrentUser {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
/** Create an IdentifiedUser, ignoring any per-request state. */
@Singleton
public static class GenericFactory {
@@ -375,8 +379,13 @@
if (effectiveGroups == null) {
if (authConfig.isIdentityTrustable(state().getExternalIds())) {
effectiveGroups = groupBackend.membershipsOf(this);
+ logger.atFinest().log(
+ "Known groups of %s: %s", getLoggableName(), lazy(effectiveGroups::getKnownGroups));
} else {
effectiveGroups = registeredGroups;
+ logger.atFinest().log(
+ "%s has a non-trusted identity, falling back to %s as known groups",
+ getLoggableName(), lazy(registeredGroups::getKnownGroups));
}
}
return effectiveGroups;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/server/StarredChangesUtil.java
index ec8620d..fa6cd6c 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/server/StarredChangesUtil.java
@@ -376,6 +376,8 @@
}
public static StarRef readLabels(Repository repo, String refName) throws IOException {
+ logger.atFine().log("Read star labels from %s", refName);
+
Ref ref = repo.exactRef(refName);
if (ref == null) {
return StarRef.MISSING;
@@ -448,6 +450,7 @@
private void updateLabels(
Repository repo, String refName, ObjectId oldObjectId, Collection<String> labels)
throws IOException, OrmException, InvalidLabelsException {
+ logger.atFine().log("Update star labels in %s (labels=%s)", refName, labels);
try (RevWalk rw = new RevWalk(repo)) {
RefUpdate u = repo.updateRef(refName);
u.setExpectedOldObjectId(oldObjectId);
@@ -485,6 +488,7 @@
return;
}
+ logger.atFine().log("Delete star labels in %s", refName);
RefUpdate u = repo.updateRef(refName);
u.setForceUpdate(true);
u.setExpectedOldObjectId(oldObjectId);
diff --git a/java/com/google/gerrit/server/account/AccountCacheImpl.java b/java/com/google/gerrit/server/account/AccountCacheImpl.java
index 76bfcfd..2cde94c 100644
--- a/java/com/google/gerrit/server/account/AccountCacheImpl.java
+++ b/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -154,12 +154,14 @@
@Override
public void evict(@Nullable Account.Id accountId) {
if (accountId != null) {
+ logger.atFine().log("Evict account %d", accountId.get());
byId.invalidate(accountId);
}
}
@Override
public void evictAll() {
+ logger.atFine().log("Evict all accounts");
byId.invalidateAll();
}
@@ -179,6 +181,7 @@
@Override
public Optional<AccountState> load(Account.Id who) throws Exception {
+ logger.atFine().log("Loading account %s", who);
return accounts.get(who);
}
}
diff --git a/java/com/google/gerrit/server/account/AccountConfig.java b/java/com/google/gerrit/server/account/AccountConfig.java
index 6aeb691..3bdc71a 100644
--- a/java/com/google/gerrit/server/account/AccountConfig.java
+++ b/java/com/google/gerrit/server/account/AccountConfig.java
@@ -30,6 +30,7 @@
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.gerrit.server.account.ProjectWatches.ProjectWatchKey;
import com.google.gerrit.server.account.externalids.ExternalIds;
+import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.git.meta.VersionedMetaData;
@@ -77,6 +78,7 @@
*/
public class AccountConfig extends VersionedMetaData implements ValidationError.Sink {
private final Account.Id accountId;
+ private final AllUsersName allUsersName;
private final Repository repo;
private final String ref;
@@ -87,8 +89,9 @@
private Optional<InternalAccountUpdate> accountUpdate = Optional.empty();
private List<ValidationError> validationErrors;
- public AccountConfig(Account.Id accountId, Repository allUsersRepo) {
+ public AccountConfig(Account.Id accountId, AllUsersName allUsersName, Repository allUsersRepo) {
this.accountId = checkNotNull(accountId, "accountId");
+ this.allUsersName = checkNotNull(allUsersName, "allUsersName");
this.repo = checkNotNull(allUsersRepo, "allUsersRepo");
this.ref = RefNames.refsUsers(accountId);
}
@@ -99,7 +102,7 @@
}
public AccountConfig load() throws IOException, ConfigInvalidException {
- load(repo);
+ load(allUsersName, repo);
return this;
}
@@ -242,7 +245,7 @@
new Preferences(
accountId,
readConfig(Preferences.PREFERENCES_CONFIG),
- Preferences.readDefaultConfig(repo),
+ Preferences.readDefaultConfig(allUsersName, repo),
this);
projectWatches.parse();
@@ -253,7 +256,8 @@
projectWatches = new ProjectWatches(accountId, new Config(), this);
preferences =
- new Preferences(accountId, new Config(), Preferences.readDefaultConfig(repo), this);
+ new Preferences(
+ accountId, new Config(), Preferences.readDefaultConfig(allUsersName, repo), this);
}
Ref externalIdsRef = repo.exactRef(RefNames.REFS_EXTERNAL_IDS);
diff --git a/java/com/google/gerrit/server/account/AccountState.java b/java/com/google/gerrit/server/account/AccountState.java
index e56ad72..1854dc1 100644
--- a/java/com/google/gerrit/server/account/AccountState.java
+++ b/java/com/google/gerrit/server/account/AccountState.java
@@ -18,6 +18,7 @@
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
import com.google.common.base.Function;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
@@ -315,4 +316,11 @@
}
return properties;
}
+
+ @Override
+ public String toString() {
+ MoreObjects.ToStringHelper h = MoreObjects.toStringHelper(this);
+ h.addValue(getAccount().getId());
+ return h.toString();
+ }
}
diff --git a/java/com/google/gerrit/server/account/Accounts.java b/java/com/google/gerrit/server/account/Accounts.java
index b74365f..1a61c02 100644
--- a/java/com/google/gerrit/server/account/Accounts.java
+++ b/java/com/google/gerrit/server/account/Accounts.java
@@ -134,7 +134,9 @@
private Optional<AccountState> read(Repository allUsersRepository, Account.Id accountId)
throws IOException, ConfigInvalidException {
return AccountState.fromAccountConfig(
- allUsersName, externalIds, new AccountConfig(accountId, allUsersRepository).load());
+ allUsersName,
+ externalIds,
+ new AccountConfig(accountId, allUsersName, allUsersRepository).load());
}
public static Stream<Account.Id> readUserRefs(Repository repo) throws IOException {
diff --git a/java/com/google/gerrit/server/account/AccountsUpdate.java b/java/com/google/gerrit/server/account/AccountsUpdate.java
index 5876b07..fe944ae 100644
--- a/java/com/google/gerrit/server/account/AccountsUpdate.java
+++ b/java/com/google/gerrit/server/account/AccountsUpdate.java
@@ -368,7 +368,7 @@
private AccountConfig read(Repository allUsersRepo, Account.Id accountId)
throws IOException, ConfigInvalidException {
- AccountConfig accountConfig = new AccountConfig(accountId, allUsersRepo).load();
+ AccountConfig accountConfig = new AccountConfig(accountId, allUsersName, allUsersRepo).load();
afterReadRevision.run();
return accountConfig;
}
diff --git a/java/com/google/gerrit/server/account/GroupBackends.java b/java/com/google/gerrit/server/account/GroupBackends.java
index 803d491..1b15512 100644
--- a/java/com/google/gerrit/server/account/GroupBackends.java
+++ b/java/com/google/gerrit/server/account/GroupBackends.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.account;
+import static java.util.Comparator.comparing;
+
import com.google.common.collect.Iterables;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupReference;
@@ -25,12 +27,7 @@
public class GroupBackends {
public static final Comparator<GroupReference> GROUP_REF_NAME_COMPARATOR =
- new Comparator<GroupReference>() {
- @Override
- public int compare(GroupReference a, GroupReference b) {
- return a.getName().compareTo(b.getName());
- }
- };
+ comparing(GroupReference::getName);
/**
* Runs {@link GroupBackend#suggest(String, ProjectState)} and filters the result to return the
diff --git a/java/com/google/gerrit/server/account/GroupCacheImpl.java b/java/com/google/gerrit/server/account/GroupCacheImpl.java
index e7aae15..1f8cb88 100644
--- a/java/com/google/gerrit/server/account/GroupCacheImpl.java
+++ b/java/com/google/gerrit/server/account/GroupCacheImpl.java
@@ -116,6 +116,7 @@
@Override
public void evict(AccountGroup.Id groupId) {
if (groupId != null) {
+ logger.atFine().log("Evict group %s by ID", groupId.get());
byId.invalidate(groupId);
}
}
@@ -123,6 +124,7 @@
@Override
public void evict(AccountGroup.NameKey groupName) {
if (groupName != null) {
+ logger.atFine().log("Evict group '%s' by name", groupName.get());
byName.invalidate(groupName.get());
}
}
@@ -130,6 +132,7 @@
@Override
public void evict(AccountGroup.UUID groupUuid) {
if (groupUuid != null) {
+ logger.atFine().log("Evict group %s by UUID", groupUuid.get());
byUUID.invalidate(groupUuid.get());
}
}
@@ -144,6 +147,7 @@
@Override
public Optional<InternalGroup> load(AccountGroup.Id key) throws Exception {
+ logger.atFine().log("Loading group %s by ID", key);
return groupQueryProvider.get().byId(key);
}
}
@@ -158,6 +162,7 @@
@Override
public Optional<InternalGroup> load(String name) throws Exception {
+ logger.atFine().log("Loading group '%s' by name", name);
return groupQueryProvider.get().byName(new AccountGroup.NameKey(name));
}
}
@@ -172,6 +177,7 @@
@Override
public Optional<InternalGroup> load(String uuid) throws Exception {
+ logger.atFine().log("Loading group %s by UUID", uuid);
return groups.getGroup(new AccountGroup.UUID(uuid));
}
}
diff --git a/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java b/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
index f262a79..5fb4ca1 100644
--- a/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
+++ b/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
@@ -112,6 +112,7 @@
@Override
public void evictGroupsWithMember(Account.Id memberId) {
if (memberId != null) {
+ logger.atFine().log("Evict groups with member %d", memberId.get());
groupsWithMember.invalidate(memberId);
}
}
@@ -119,9 +120,11 @@
@Override
public void evictParentGroupsOf(AccountGroup.UUID groupId) {
if (groupId != null) {
+ logger.atFine().log("Evict parent groups of %s", groupId.get());
parentGroups.invalidate(groupId);
if (!AccountGroup.isInternalGroup(groupId)) {
+ logger.atFine().log("Evict external group %s", groupId.get());
external.invalidate(EXTERNAL_NAME);
}
}
@@ -148,6 +151,7 @@
@Override
public ImmutableSet<AccountGroup.UUID> load(Account.Id memberId) throws OrmException {
+ logger.atFine().log("Loading groups with member %s", memberId);
return groupQueryProvider
.get()
.byMember(memberId)
@@ -168,6 +172,7 @@
@Override
public ImmutableList<AccountGroup.UUID> load(AccountGroup.UUID key) throws OrmException {
+ logger.atFine().log("Loading parent groups of %s", key);
return groupQueryProvider
.get()
.bySubgroup(key)
@@ -187,6 +192,7 @@
@Override
public ImmutableList<AccountGroup.UUID> load(String key) throws Exception {
+ logger.atFine().log("Loading all external groups");
return groups.getExternalGroups().collect(toImmutableList());
}
}
diff --git a/java/com/google/gerrit/server/account/Preferences.java b/java/com/google/gerrit/server/account/Preferences.java
index aa09675..5bf6e26 100644
--- a/java/com/google/gerrit/server/account/Preferences.java
+++ b/java/com/google/gerrit/server/account/Preferences.java
@@ -39,6 +39,7 @@
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.UserConfigSections;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
@@ -414,25 +415,28 @@
return urlAliases;
}
- public static GeneralPreferencesInfo readDefaultGeneralPreferences(Repository allUsersRepo)
+ public static GeneralPreferencesInfo readDefaultGeneralPreferences(
+ AllUsersName allUsersName, Repository allUsersRepo)
throws IOException, ConfigInvalidException {
- return parseGeneralPreferences(readDefaultConfig(allUsersRepo), null, null);
+ return parseGeneralPreferences(readDefaultConfig(allUsersName, allUsersRepo), null, null);
}
- public static DiffPreferencesInfo readDefaultDiffPreferences(Repository allUsersRepo)
+ public static DiffPreferencesInfo readDefaultDiffPreferences(
+ AllUsersName allUsersName, Repository allUsersRepo)
throws IOException, ConfigInvalidException {
- return parseDiffPreferences(readDefaultConfig(allUsersRepo), null, null);
+ return parseDiffPreferences(readDefaultConfig(allUsersName, allUsersRepo), null, null);
}
- public static EditPreferencesInfo readDefaultEditPreferences(Repository allUsersRepo)
+ public static EditPreferencesInfo readDefaultEditPreferences(
+ AllUsersName allUsersName, Repository allUsersRepo)
throws IOException, ConfigInvalidException {
- return parseEditPreferences(readDefaultConfig(allUsersRepo), null, null);
+ return parseEditPreferences(readDefaultConfig(allUsersName, allUsersRepo), null, null);
}
- static Config readDefaultConfig(Repository allUsersRepo)
+ static Config readDefaultConfig(AllUsersName allUsersName, Repository allUsersRepo)
throws IOException, ConfigInvalidException {
VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
- defaultPrefs.load(allUsersRepo);
+ defaultPrefs.load(allUsersName, allUsersRepo);
return defaultPrefs.getConfig();
}
diff --git a/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java b/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
index 04a3a95..965f1ba 100644
--- a/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
+++ b/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
@@ -121,7 +121,7 @@
throws IOException, ConfigInvalidException {
try (Repository git = repoManager.openRepository(allUsersName)) {
VersionedAuthorizedKeys authorizedKeys = authorizedKeysFactory.create(accountId);
- authorizedKeys.load(git);
+ authorizedKeys.load(allUsersName, git);
return authorizedKeys;
}
}
diff --git a/java/com/google/gerrit/server/account/externalids/AllExternalIds.java b/java/com/google/gerrit/server/account/externalids/AllExternalIds.java
index 15f7a0ad4..bb1ade7 100644
--- a/java/com/google/gerrit/server/account/externalids/AllExternalIds.java
+++ b/java/com/google/gerrit/server/account/externalids/AllExternalIds.java
@@ -14,37 +14,99 @@
package com.google.gerrit.server.account.externalids;
+import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
+import static java.util.stream.Collectors.toList;
+
import com.google.auto.value.AutoValue;
import com.google.common.base.Strings;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.Multimaps;
+import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.SetMultimap;
-import com.google.gerrit.reviewdb.client.Account.Id;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.cache.proto.Cache.AllExternalIdsProto;
+import com.google.gerrit.server.cache.proto.Cache.AllExternalIdsProto.ExternalIdProto;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers.ObjectIdConverter;
+import java.util.Collection;
-/**
- * Cache value containing all external IDs.
- *
- * <p>All returned fields are unmodifiable.
- */
+/** Cache value containing all external IDs. */
@AutoValue
public abstract class AllExternalIds {
- static AllExternalIds create(Multimap<Id, ExternalId> byAccount) {
- SetMultimap<String, ExternalId> byEmailCopy =
- MultimapBuilder.hashKeys(byAccount.size()).hashSetValues(1).build();
- byAccount
- .values()
- .stream()
- .filter(e -> !Strings.isNullOrEmpty(e.email()))
- .forEach(e -> byEmailCopy.put(e.email(), e));
-
+ static AllExternalIds create(SetMultimap<Account.Id, ExternalId> byAccount) {
return new AutoValue_AllExternalIds(
- Multimaps.unmodifiableSetMultimap(
- MultimapBuilder.hashKeys(byAccount.size()).hashSetValues(5).build(byAccount)),
- byEmailCopy);
+ ImmutableSetMultimap.copyOf(byAccount), byEmailCopy(byAccount.values()));
}
- public abstract SetMultimap<Id, ExternalId> byAccount();
+ static AllExternalIds create(Collection<ExternalId> externalIds) {
+ return new AutoValue_AllExternalIds(
+ externalIds.stream().collect(toImmutableSetMultimap(e -> e.accountId(), e -> e)),
+ byEmailCopy(externalIds));
+ }
- public abstract SetMultimap<String, ExternalId> byEmail();
+ private static ImmutableSetMultimap<String, ExternalId> byEmailCopy(
+ Collection<ExternalId> externalIds) {
+ return externalIds
+ .stream()
+ .filter(e -> !Strings.isNullOrEmpty(e.email()))
+ .collect(toImmutableSetMultimap(e -> e.email(), e -> e));
+ }
+
+ public abstract ImmutableSetMultimap<Account.Id, ExternalId> byAccount();
+
+ public abstract ImmutableSetMultimap<String, ExternalId> byEmail();
+
+ enum Serializer implements CacheSerializer<AllExternalIds> {
+ INSTANCE;
+
+ @Override
+ public byte[] serialize(AllExternalIds object) {
+ ObjectIdConverter idConverter = ObjectIdConverter.create();
+ AllExternalIdsProto.Builder allBuilder = AllExternalIdsProto.newBuilder();
+ object
+ .byAccount()
+ .values()
+ .stream()
+ .map(extId -> toProto(idConverter, extId))
+ .forEach(allBuilder::addExternalId);
+ return ProtoCacheSerializers.toByteArray(allBuilder.build());
+ }
+
+ private static ExternalIdProto toProto(ObjectIdConverter idConverter, ExternalId externalId) {
+ ExternalIdProto.Builder b =
+ ExternalIdProto.newBuilder()
+ .setKey(externalId.key().get())
+ .setAccountId(externalId.accountId().get());
+ if (externalId.email() != null) {
+ b.setEmail(externalId.email());
+ }
+ if (externalId.password() != null) {
+ b.setPassword(externalId.password());
+ }
+ if (externalId.blobId() != null) {
+ b.setBlobId(idConverter.toByteString(externalId.blobId()));
+ }
+ return b.build();
+ }
+
+ @Override
+ public AllExternalIds deserialize(byte[] in) {
+ ObjectIdConverter idConverter = ObjectIdConverter.create();
+ return create(
+ ProtoCacheSerializers.parseUnchecked(AllExternalIdsProto.parser(), in)
+ .getExternalIdList()
+ .stream()
+ .map(proto -> toExternalId(idConverter, proto))
+ .collect(toList()));
+ }
+
+ private static ExternalId toExternalId(ObjectIdConverter idConverter, ExternalIdProto proto) {
+ return ExternalId.create(
+ ExternalId.Key.parse(proto.getKey()),
+ new Account.Id(proto.getAccountId()),
+ // ExternalId treats null and empty strings the same, so no need to distinguish here.
+ proto.getEmail(),
+ proto.getPassword(),
+ !proto.getBlobId().isEmpty() ? idConverter.fromByteString(proto.getBlobId()) : null);
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalId.java b/java/com/google/gerrit/server/account/externalids/ExternalId.java
index db8ea41..96ea0cc 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalId.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalId.java
@@ -47,14 +47,12 @@
// corresponding regular expressions in the
// com.google.gerrit.client.account.UsernameField class.
private static final String USER_NAME_PATTERN_FIRST_REGEX = "[a-zA-Z0-9]";
- private static final String USER_NAME_PATTERN_REST_REGEX = "[a-zA-Z0-9._@-]";
+ private static final String USER_NAME_PATTERN_REST_REGEX = "[a-zA-Z0-9.!#$%&’*+=?^_`\\{|\\}~@-]";
private static final String USER_NAME_PATTERN_LAST_REGEX = "[a-zA-Z0-9]";
/** Regular expression that a username must match. */
private static final String USER_NAME_PATTERN_REGEX =
- "^"
- + //
- "("
+ "^("
+ //
USER_NAME_PATTERN_FIRST_REGEX
+ //
@@ -67,9 +65,7 @@
+ //
USER_NAME_PATTERN_FIRST_REGEX
+ //
- ")"
- + //
- "$";
+ ")$";
private static final Pattern USER_NAME_PATTERN = Pattern.compile(USER_NAME_PATTERN_REGEX);
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdCache.java b/java/com/google/gerrit/server/account/externalids/ExternalIdCache.java
index a8844cd..1ac737e 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdCache.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdCache.java
@@ -34,8 +34,7 @@
ObjectId oldNotesRev,
ObjectId newNotesRev,
Collection<ExternalId> toRemove,
- Collection<ExternalId> toAdd)
- throws IOException;
+ Collection<ExternalId> toAdd);
Set<ExternalId> byAccount(Account.Id accountId) throws IOException;
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java
index 533b1c0..5f64568 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java
@@ -16,9 +16,8 @@
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.SetMultimap;
import com.google.common.flogger.FluentLogger;
@@ -60,8 +59,7 @@
ObjectId oldNotesRev,
ObjectId newNotesRev,
Collection<ExternalId> toRemove,
- Collection<ExternalId> toAdd)
- throws IOException {
+ Collection<ExternalId> toAdd) {
updateCache(
oldNotesRev,
newNotesRev,
@@ -121,17 +119,17 @@
private void updateCache(
ObjectId oldNotesRev,
ObjectId newNotesRev,
- Consumer<Multimap<Account.Id, ExternalId>> update) {
+ Consumer<SetMultimap<Account.Id, ExternalId>> update) {
lock.lock();
try {
- ListMultimap<Account.Id, ExternalId> m;
+ SetMultimap<Account.Id, ExternalId> m;
if (!ObjectId.zeroId().equals(oldNotesRev)) {
m =
MultimapBuilder.hashKeys()
- .arrayListValues()
+ .hashSetValues()
.build(extIdsByAccount.get(oldNotesRev).byAccount());
} else {
- m = MultimapBuilder.hashKeys().arrayListValues().build();
+ m = MultimapBuilder.hashKeys().hashSetValues().build();
}
update.accept(m);
extIdsByAccount.put(newNotesRev, AllExternalIds.create(m));
@@ -152,13 +150,10 @@
@Override
public AllExternalIds load(ObjectId notesRev) throws Exception {
- Multimap<Account.Id, ExternalId> extIdsByAccount =
- MultimapBuilder.hashKeys().arrayListValues().build();
- for (ExternalId extId : externalIdReader.all(notesRev)) {
- extId.checkThatBlobIdIsSet();
- extIdsByAccount.put(extId.accountId(), extId);
- }
- return AllExternalIds.create(extIdsByAccount);
+ logger.atFine().log("Loading external IDs (revision=%s)", notesRev);
+ ImmutableSet<ExternalId> externalIds = externalIdReader.all(notesRev);
+ externalIds.forEach(ExternalId::checkThatBlobIdIsSet);
+ return AllExternalIds.create(externalIds);
}
}
}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdModule.java b/java/com/google/gerrit/server/account/externalids/ExternalIdModule.java
index 228b1e6..fc311e7 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdModule.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdModule.java
@@ -16,6 +16,7 @@
import com.google.gerrit.server.account.externalids.ExternalIdCacheImpl.Loader;
import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.serialize.ObjectIdCacheSerializer;
import com.google.inject.TypeLiteral;
import java.time.Duration;
import org.eclipse.jgit.lib.ObjectId;
@@ -23,7 +24,7 @@
public class ExternalIdModule extends CacheModule {
@Override
protected void configure() {
- cache(ExternalIdCacheImpl.CACHE_NAME, ObjectId.class, new TypeLiteral<AllExternalIds>() {})
+ persist(ExternalIdCacheImpl.CACHE_NAME, ObjectId.class, new TypeLiteral<AllExternalIds>() {})
// The cached data is potentially pretty large and we are always only interested
// in the latest value. However, due to a race condition, it is possible for different
// threads to observe different values of the meta ref, and hence request different keys
@@ -32,7 +33,11 @@
// memory.
.maximumWeight(2)
.expireFromMemoryAfterAccess(Duration.ofMinutes(1))
- .loader(Loader.class);
+ .loader(Loader.class)
+ .diskLimit(-1)
+ .version(1)
+ .keySerializer(ObjectIdCacheSerializer.INSTANCE)
+ .valueSerializer(AllExternalIds.Serializer.INSTANCE);
bind(ExternalIdCacheImpl.class);
bind(ExternalIdCache.class).to(ExternalIdCacheImpl.class);
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
index 7cd1db0..b117888 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
@@ -35,6 +35,7 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.git.meta.VersionedMetaData;
import com.google.gerrit.server.index.account.AccountIndexer;
@@ -117,24 +118,32 @@
private final AccountCache accountCache;
private final Provider<AccountIndexer> accountIndexer;
private final MetricMaker metricMaker;
+ private final AllUsersName allUsersName;
@Inject
Factory(
ExternalIdCache externalIdCache,
AccountCache accountCache,
Provider<AccountIndexer> accountIndexer,
- MetricMaker metricMaker) {
+ MetricMaker metricMaker,
+ AllUsersName allUsersName) {
this.externalIdCache = externalIdCache;
this.accountCache = accountCache;
this.accountIndexer = accountIndexer;
this.metricMaker = metricMaker;
+ this.allUsersName = allUsersName;
}
@Override
public ExternalIdNotes load(Repository allUsersRepo)
throws IOException, ConfigInvalidException {
return new ExternalIdNotes(
- externalIdCache, accountCache, accountIndexer, metricMaker, allUsersRepo)
+ externalIdCache,
+ accountCache,
+ accountIndexer,
+ metricMaker,
+ allUsersName,
+ allUsersRepo)
.load();
}
@@ -142,7 +151,12 @@
public ExternalIdNotes load(Repository allUsersRepo, @Nullable ObjectId rev)
throws IOException, ConfigInvalidException {
return new ExternalIdNotes(
- externalIdCache, accountCache, accountIndexer, metricMaker, allUsersRepo)
+ externalIdCache,
+ accountCache,
+ accountIndexer,
+ metricMaker,
+ allUsersName,
+ allUsersRepo)
.load(rev);
}
}
@@ -151,23 +165,30 @@
public static class FactoryNoReindex implements ExternalIdNotesLoader {
private final ExternalIdCache externalIdCache;
private final MetricMaker metricMaker;
+ private final AllUsersName allUsersName;
@Inject
- FactoryNoReindex(ExternalIdCache externalIdCache, MetricMaker metricMaker) {
+ FactoryNoReindex(
+ ExternalIdCache externalIdCache, MetricMaker metricMaker, AllUsersName allUsersName) {
this.externalIdCache = externalIdCache;
this.metricMaker = metricMaker;
+ this.allUsersName = allUsersName;
}
@Override
public ExternalIdNotes load(Repository allUsersRepo)
throws IOException, ConfigInvalidException {
- return new ExternalIdNotes(externalIdCache, null, null, metricMaker, allUsersRepo).load();
+ return new ExternalIdNotes(
+ externalIdCache, null, null, metricMaker, allUsersName, allUsersRepo)
+ .load();
}
@Override
public ExternalIdNotes load(Repository allUsersRepo, @Nullable ObjectId rev)
throws IOException, ConfigInvalidException {
- return new ExternalIdNotes(externalIdCache, null, null, metricMaker, allUsersRepo).load(rev);
+ return new ExternalIdNotes(
+ externalIdCache, null, null, metricMaker, allUsersName, allUsersRepo)
+ .load(rev);
}
}
@@ -177,10 +198,15 @@
*
* @return read-only {@link ExternalIdNotes} instance
*/
- public static ExternalIdNotes loadReadOnly(Repository allUsersRepo)
+ public static ExternalIdNotes loadReadOnly(AllUsersName allUsersName, Repository allUsersRepo)
throws IOException, ConfigInvalidException {
return new ExternalIdNotes(
- new DisabledExternalIdCache(), null, null, new DisabledMetricMaker(), allUsersRepo)
+ new DisabledExternalIdCache(),
+ null,
+ null,
+ new DisabledMetricMaker(),
+ allUsersName,
+ allUsersRepo)
.setReadOnly()
.load();
}
@@ -195,10 +221,16 @@
* external IDs will be empty
* @return read-only {@link ExternalIdNotes} instance
*/
- public static ExternalIdNotes loadReadOnly(Repository allUsersRepo, @Nullable ObjectId rev)
+ public static ExternalIdNotes loadReadOnly(
+ AllUsersName allUsersName, Repository allUsersRepo, @Nullable ObjectId rev)
throws IOException, ConfigInvalidException {
return new ExternalIdNotes(
- new DisabledExternalIdCache(), null, null, new DisabledMetricMaker(), allUsersRepo)
+ new DisabledExternalIdCache(),
+ null,
+ null,
+ new DisabledMetricMaker(),
+ allUsersName,
+ allUsersRepo)
.setReadOnly()
.load(rev);
}
@@ -213,16 +245,23 @@
*
* @return {@link ExternalIdNotes} instance that doesn't updates caches on save
*/
- public static ExternalIdNotes loadNoCacheUpdate(Repository allUsersRepo)
+ public static ExternalIdNotes loadNoCacheUpdate(
+ AllUsersName allUsersName, Repository allUsersRepo)
throws IOException, ConfigInvalidException {
return new ExternalIdNotes(
- new DisabledExternalIdCache(), null, null, new DisabledMetricMaker(), allUsersRepo)
+ new DisabledExternalIdCache(),
+ null,
+ null,
+ new DisabledMetricMaker(),
+ allUsersName,
+ allUsersRepo)
.load();
}
private final ExternalIdCache externalIdCache;
@Nullable private final AccountCache accountCache;
@Nullable private final Provider<AccountIndexer> accountIndexer;
+ private final AllUsersName allUsersName;
private final Counter0 updateCount;
private final Repository repo;
@@ -243,6 +282,7 @@
@Nullable AccountCache accountCache,
@Nullable Provider<AccountIndexer> accountIndexer,
MetricMaker metricMaker,
+ AllUsersName allUsersName,
Repository allUsersRepo) {
this.externalIdCache = checkNotNull(externalIdCache, "externalIdCache");
this.accountCache = accountCache;
@@ -251,6 +291,7 @@
metricMaker.newCounter(
"notedb/external_id_update_count",
new Description("Total number of external ID updates.").setRate().setUnit("updates"));
+ this.allUsersName = checkNotNull(allUsersName, "allUsersRepo");
this.repo = checkNotNull(allUsersRepo, "allUsersRepo");
}
@@ -279,7 +320,7 @@
* @return {@link ExternalIdNotes} instance for chaining
*/
private ExternalIdNotes load() throws IOException, ConfigInvalidException {
- load(repo);
+ load(allUsersName, repo);
return this;
}
@@ -298,10 +339,10 @@
return load();
}
if (ObjectId.zeroId().equals(rev)) {
- load(repo, null);
+ load(allUsersName, repo, null);
return this;
}
- load(repo, rev);
+ load(allUsersName, repo, rev);
return this;
}
@@ -615,6 +656,8 @@
@Override
protected void onLoad() throws IOException, ConfigInvalidException {
+ logger.atFine().log("Reading external IDs");
+
noteMap = revision != null ? NoteMap.read(reader, revision) : NoteMap.newEmptyMap();
if (afterReadRevision != null) {
@@ -636,7 +679,7 @@
*
* <p>Must only be called after committing changes.
*
- * <p>No-op if this instance was created by {@link #loadNoCacheUpdate(Repository)}.
+ * <p>No-op if this instance was created by {@link #loadNoCacheUpdate(AllUsersName, Repository)}.
*
* <p>No eviction from account cache and no reindex if this instance was created by {@link
* FactoryNoReindex}.
@@ -651,7 +694,7 @@
*
* <p>Must only be called after committing changes.
*
- * <p>No-op if this instance was created by {@link #loadNoCacheUpdate(Repository)}.
+ * <p>No-op if this instance was created by {@link #loadNoCacheUpdate(AllUsersName, Repository)}.
*
* <p>No eviction from account cache if this instance was created by {@link FactoryNoReindex}.
*
@@ -701,6 +744,8 @@
return false;
}
+ logger.atFine().log("Updating external IDs");
+
if (Strings.isNullOrEmpty(commit.getMessage())) {
commit.setMessage("Update external IDs\n");
}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java b/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
index ee6d5cd..cf5500e 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
@@ -99,7 +99,7 @@
try (Timer0.Context ctx = readAllLatency.start();
Repository repo = repoManager.openRepository(allUsersName)) {
- return ExternalIdNotes.loadReadOnly(repo).all();
+ return ExternalIdNotes.loadReadOnly(allUsersName, repo).all();
}
}
@@ -118,7 +118,7 @@
try (Timer0.Context ctx = readAllLatency.start();
Repository repo = repoManager.openRepository(allUsersName)) {
- return ExternalIdNotes.loadReadOnly(repo, rev).all();
+ return ExternalIdNotes.loadReadOnly(allUsersName, repo, rev).all();
}
}
@@ -127,7 +127,7 @@
checkReadEnabled();
try (Repository repo = repoManager.openRepository(allUsersName)) {
- return ExternalIdNotes.loadReadOnly(repo).get(key);
+ return ExternalIdNotes.loadReadOnly(allUsersName, repo).get(key);
}
}
@@ -137,7 +137,7 @@
checkReadEnabled();
try (Repository repo = repoManager.openRepository(allUsersName)) {
- return ExternalIdNotes.loadReadOnly(repo, rev).get(key);
+ return ExternalIdNotes.loadReadOnly(allUsersName, repo, rev).get(key);
}
}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java b/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java
index 0756a72..14ead2f 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java
@@ -59,14 +59,14 @@
public List<ConsistencyProblemInfo> check() throws IOException, ConfigInvalidException {
try (Repository repo = repoManager.openRepository(allUsers)) {
- return check(ExternalIdNotes.loadReadOnly(repo));
+ return check(ExternalIdNotes.loadReadOnly(allUsers, repo));
}
}
public List<ConsistencyProblemInfo> check(ObjectId rev)
throws IOException, ConfigInvalidException {
try (Repository repo = repoManager.openRepository(allUsers)) {
- return check(ExternalIdNotes.loadReadOnly(repo, rev));
+ return check(ExternalIdNotes.loadReadOnly(allUsers, repo, rev));
}
}
diff --git a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index a49f8c4..463c23e 100644
--- a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -24,6 +24,8 @@
import com.google.gerrit.extensions.api.config.AccessCheckInput;
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.api.projects.BranchInfo;
+import com.google.gerrit.extensions.api.projects.CheckProjectInput;
+import com.google.gerrit.extensions.api.projects.CheckProjectResultInfo;
import com.google.gerrit.extensions.api.projects.ChildProjectApi;
import com.google.gerrit.extensions.api.projects.CommitApi;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
@@ -53,6 +55,7 @@
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.project.ProjectJson;
import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.restapi.project.Check;
import com.google.gerrit.server.restapi.project.CheckAccess;
import com.google.gerrit.server.restapi.project.ChildProjectsCollection;
import com.google.gerrit.server.restapi.project.CommitsCollection;
@@ -115,6 +118,7 @@
private final CommitApiImpl.Factory commitApi;
private final DashboardApiImpl.Factory dashboardApi;
private final CheckAccess checkAccess;
+ private final Check check;
private final Provider<ListDashboards> listDashboards;
private final GetHead getHead;
private final SetHead setHead;
@@ -148,6 +152,7 @@
CommitApiImpl.Factory commitApi,
DashboardApiImpl.Factory dashboardApi,
CheckAccess checkAccess,
+ Check check,
Provider<ListDashboards> listDashboards,
GetHead getHead,
SetHead setHead,
@@ -181,6 +186,7 @@
commitApi,
dashboardApi,
checkAccess,
+ check,
listDashboards,
getHead,
setHead,
@@ -216,6 +222,7 @@
CommitApiImpl.Factory commitApi,
DashboardApiImpl.Factory dashboardApi,
CheckAccess checkAccess,
+ Check check,
Provider<ListDashboards> listDashboards,
GetHead getHead,
SetHead setHead,
@@ -249,6 +256,7 @@
commitApi,
dashboardApi,
checkAccess,
+ check,
listDashboards,
getHead,
setHead,
@@ -284,6 +292,7 @@
CommitApiImpl.Factory commitApi,
DashboardApiImpl.Factory dashboardApi,
CheckAccess checkAccess,
+ Check check,
Provider<ListDashboards> listDashboards,
GetHead getHead,
SetHead setHead,
@@ -316,6 +325,7 @@
this.createAccessChange = createAccessChange;
this.dashboardApi = dashboardApi;
this.checkAccess = checkAccess;
+ this.check = check;
this.listDashboards = listDashboards;
this.getHead = getHead;
this.setHead = setHead;
@@ -372,15 +382,6 @@
}
@Override
- public AccessCheckInfo checkAccess(AccessCheckInput in) throws RestApiException {
- try {
- return checkAccess.apply(checkExists(), in);
- } catch (Exception e) {
- throw asRestApiException("Cannot check access rights", e);
- }
- }
-
- @Override
public ProjectAccessInfo access(ProjectAccessInput p) throws RestApiException {
try {
return setAccess.apply(checkExists(), p);
@@ -399,6 +400,24 @@
}
@Override
+ public AccessCheckInfo checkAccess(AccessCheckInput in) throws RestApiException {
+ try {
+ return checkAccess.apply(checkExists(), in);
+ } catch (Exception e) {
+ throw asRestApiException("Cannot check access rights", e);
+ }
+ }
+
+ @Override
+ public CheckProjectResultInfo check(CheckProjectInput in) throws RestApiException {
+ try {
+ return check.apply(checkExists(), in);
+ } catch (Exception e) {
+ throw asRestApiException("Cannot check project", e);
+ }
+ }
+
+ @Override
public void description(DescriptionInput in) throws RestApiException {
try {
putDescription.apply(checkExists(), in);
diff --git a/java/com/google/gerrit/server/audit/BUILD b/java/com/google/gerrit/server/audit/BUILD
index 5efdc5a..d85668e 100644
--- a/java/com/google/gerrit/server/audit/BUILD
+++ b/java/com/google/gerrit/server/audit/BUILD
@@ -24,9 +24,34 @@
"//java/org/apache/commons/net",
"//java/org/eclipse/jgit:server",
"//lib:args4j",
+ "//lib:autolink",
"//lib:automaton",
"//lib:blame-cache",
- "//lib:grappa",
+ "//lib:flexmark",
+ "//lib:flexmark-ext-abbreviation",
+ "//lib:flexmark-ext-anchorlink",
+ "//lib:flexmark-ext-autolink",
+ "//lib:flexmark-ext-definition",
+ "//lib:flexmark-ext-emoji",
+ "//lib:flexmark-ext-escaped-character",
+ "//lib:flexmark-ext-footnotes",
+ "//lib:flexmark-ext-gfm-issues",
+ "//lib:flexmark-ext-gfm-strikethrough",
+ "//lib:flexmark-ext-gfm-tables",
+ "//lib:flexmark-ext-gfm-tasklist",
+ "//lib:flexmark-ext-gfm-users",
+ "//lib:flexmark-ext-ins",
+ "//lib:flexmark-ext-jekyll-front-matter",
+ "//lib:flexmark-ext-superscript",
+ "//lib:flexmark-ext-tables",
+ "//lib:flexmark-ext-toc",
+ "//lib:flexmark-ext-typographic",
+ "//lib:flexmark-ext-wikilink",
+ "//lib:flexmark-ext-yaml-front-matter",
+ "//lib:flexmark-formatter",
+ "//lib:flexmark-html-parser",
+ "//lib:flexmark-profile-pegdown",
+ "//lib:flexmark-util",
"//lib:gson",
"//lib:guava",
"//lib:guava-retrying",
@@ -35,7 +60,6 @@
"//lib:jsch",
"//lib:juniversalchardet",
"//lib:mime-util",
- "//lib:pegdown",
"//lib:protobuf",
"//lib:servlet-api-3_1",
"//lib:soy",
diff --git a/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
index 8d12d32..ede8050 100644
--- a/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
+++ b/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
@@ -351,6 +351,7 @@
@Override
public Optional<Account.Id> load(String username) throws Exception {
+ logger.atFine().log("Loading account for username %s", username);
return externalIds
.get(ExternalId.Key.create(SCHEME_GERRIT, username))
.map(ExternalId::accountId);
@@ -367,6 +368,7 @@
@Override
public Set<AccountGroup.UUID> load(String username) throws Exception {
+ logger.atFine().log("Loading group for member with username %s", username);
final DirContext ctx = helper.open();
try {
return helper.queryForGroups(ctx, username, null);
@@ -386,6 +388,7 @@
@Override
public Boolean load(String groupDn) throws Exception {
+ logger.atFine().log("Loading groupDn %s", groupDn);
final DirContext ctx = helper.open();
try {
Name compositeGroupName = new CompositeName().add(groupDn);
diff --git a/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java b/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java
index 13a09a1..4c364c5 100644
--- a/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java
+++ b/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java
@@ -24,10 +24,10 @@
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.CacheSerializer;
-import com.google.gerrit.server.cache.IntKeyCacheSerializer;
-import com.google.gerrit.server.cache.ProtoCacheSerializers;
import com.google.gerrit.server.cache.proto.Cache.OAuthTokenProto;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.IntKeyCacheSerializer;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/cache/CacheMetrics.java b/java/com/google/gerrit/server/cache/CacheMetrics.java
index 11f2034..3435652 100644
--- a/java/com/google/gerrit/server/cache/CacheMetrics.java
+++ b/java/com/google/gerrit/server/cache/CacheMetrics.java
@@ -18,6 +18,7 @@
import com.google.common.cache.CacheStats;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.PluginName;
import com.google.gerrit.metrics.CallbackMetric;
import com.google.gerrit.metrics.CallbackMetric1;
import com.google.gerrit.metrics.Description;
@@ -95,7 +96,7 @@
}
private static String metricNameOf(DynamicMap.Entry<Cache<?, ?>> e) {
- if ("gerrit".equals(e.getPluginName())) {
+ if (PluginName.GERRIT.equals(e.getPluginName())) {
return e.getExportName();
}
return String.format("plugin/%s/%s", e.getPluginName(), e.getExportName());
diff --git a/java/com/google/gerrit/server/cache/CacheModule.java b/java/com/google/gerrit/server/cache/CacheModule.java
index ca399e7..2878624 100644
--- a/java/com/google/gerrit/server/cache/CacheModule.java
+++ b/java/com/google/gerrit/server/cache/CacheModule.java
@@ -20,6 +20,7 @@
import com.google.common.cache.Weigher;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.server.cache.serialize.JavaCacheSerializer;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Scopes;
diff --git a/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java b/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java
index be06601..a7fdbbd 100644
--- a/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java
+++ b/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java
@@ -18,6 +18,7 @@
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.registration.PluginName;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -36,7 +37,7 @@
private final DynamicSet<CacheRemovalListener> listeners;
private final String cacheName;
- private String pluginName = "gerrit";
+ private String pluginName = PluginName.GERRIT;
@Inject
ForwardingRemovalListener(
diff --git a/java/com/google/gerrit/server/cache/PersistentCacheBinding.java b/java/com/google/gerrit/server/cache/PersistentCacheBinding.java
index 0239ea2..5635f44 100644
--- a/java/com/google/gerrit/server/cache/PersistentCacheBinding.java
+++ b/java/com/google/gerrit/server/cache/PersistentCacheBinding.java
@@ -16,6 +16,7 @@
import com.google.common.cache.CacheLoader;
import com.google.common.cache.Weigher;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
import java.time.Duration;
/** Configure a persistent cache declared within a {@link CacheModule} instance. */
@@ -30,6 +31,9 @@
PersistentCacheBinding<K, V> loader(Class<? extends CacheLoader<K, V>> clazz);
@Override
+ PersistentCacheBinding<K, V> expireFromMemoryAfterAccess(Duration duration);
+
+ @Override
PersistentCacheBinding<K, V> weigher(Class<? extends Weigher<K, V>> clazz);
PersistentCacheBinding<K, V> version(int version);
diff --git a/java/com/google/gerrit/server/cache/PersistentCacheDef.java b/java/com/google/gerrit/server/cache/PersistentCacheDef.java
index 9bd120f..8de685c 100644
--- a/java/com/google/gerrit/server/cache/PersistentCacheDef.java
+++ b/java/com/google/gerrit/server/cache/PersistentCacheDef.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.cache;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
+
public interface PersistentCacheDef<K, V> extends CacheDef<K, V> {
long diskLimit();
diff --git a/java/com/google/gerrit/server/cache/PersistentCacheProvider.java b/java/com/google/gerrit/server/cache/PersistentCacheProvider.java
index 2db9e56..59d66e3 100644
--- a/java/com/google/gerrit/server/cache/PersistentCacheProvider.java
+++ b/java/com/google/gerrit/server/cache/PersistentCacheProvider.java
@@ -20,6 +20,8 @@
import com.google.common.cache.CacheLoader;
import com.google.common.cache.Weigher;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.JavaCacheSerializer;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
@@ -63,6 +65,11 @@
}
@Override
+ public PersistentCacheBinding<K, V> expireFromMemoryAfterAccess(Duration duration) {
+ return (PersistentCacheBinding<K, V>) super.expireFromMemoryAfterAccess(duration);
+ }
+
+ @Override
public PersistentCacheBinding<K, V> weigher(Class<? extends Weigher<K, V>> clazz) {
return (PersistentCacheBinding<K, V>) super.weigher(clazz);
}
diff --git a/java/com/google/gerrit/server/cache/h2/BUILD b/java/com/google/gerrit/server/cache/h2/BUILD
index fc57a11..f6418e3 100644
--- a/java/com/google/gerrit/server/cache/h2/BUILD
+++ b/java/com/google/gerrit/server/cache/h2/BUILD
@@ -8,6 +8,8 @@
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/cache/serialize",
+ "//java/com/google/gerrit/server/logging",
"//lib:guava",
"//lib:h2",
"//lib/flogger:api",
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheDefProxy.java b/java/com/google/gerrit/server/cache/h2/H2CacheDefProxy.java
index 78de67dd..48c0a5b 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheDefProxy.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheDefProxy.java
@@ -17,9 +17,9 @@
import com.google.common.cache.CacheLoader;
import com.google.common.cache.Weigher;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.server.cache.CacheSerializer;
import com.google.gerrit.server.cache.PersistentCacheDef;
import com.google.gerrit.server.cache.h2.H2CacheImpl.ValueHolder;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
import com.google.inject.TypeLiteral;
import java.time.Duration;
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
index 9abccbc..af1228d 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -28,6 +28,8 @@
import com.google.gerrit.server.cache.h2.H2CacheImpl.ValueHolder;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.logging.LoggingContextAwareExecutorService;
+import com.google.gerrit.server.logging.LoggingContextAwareScheduledExecutorService;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -74,15 +76,17 @@
if (cacheDir != null) {
executor =
- Executors.newFixedThreadPool(
- 1, new ThreadFactoryBuilder().setNameFormat("DiskCache-Store-%d").build());
+ new LoggingContextAwareExecutorService(
+ Executors.newFixedThreadPool(
+ 1, new ThreadFactoryBuilder().setNameFormat("DiskCache-Store-%d").build()));
cleanup =
- Executors.newScheduledThreadPool(
- 1,
- new ThreadFactoryBuilder()
- .setNameFormat("DiskCache-Prune-%d")
- .setDaemon(true)
- .build());
+ new LoggingContextAwareScheduledExecutorService(
+ Executors.newScheduledThreadPool(
+ 1,
+ new ThreadFactoryBuilder()
+ .setNameFormat("DiskCache-Prune-%d")
+ .setDaemon(true)
+ .build()));
} else {
executor = null;
cleanup = null;
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
index 6878e46..606fdf0 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -25,8 +25,8 @@
import com.google.common.hash.BloomFilter;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
-import com.google.gerrit.server.cache.CacheSerializer;
import com.google.gerrit.server.cache.PersistentCache;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
import com.google.inject.TypeLiteral;
import java.io.IOException;
import java.io.InvalidClassException;
@@ -235,6 +235,8 @@
@Override
public ValueHolder<V> load(K key) throws Exception {
+ logger.atFine().log("Loading value for %s from cache", key);
+
if (store.mightContain(key)) {
ValueHolder<V> h = store.getIfPresent(key);
if (h != null) {
diff --git a/java/com/google/gerrit/server/cache/h2/ObjectKeyTypeImpl.java b/java/com/google/gerrit/server/cache/h2/ObjectKeyTypeImpl.java
index 44e2bb2..591883e 100644
--- a/java/com/google/gerrit/server/cache/h2/ObjectKeyTypeImpl.java
+++ b/java/com/google/gerrit/server/cache/h2/ObjectKeyTypeImpl.java
@@ -17,7 +17,7 @@
import com.google.common.hash.Funnel;
import com.google.common.hash.Funnels;
import com.google.common.hash.PrimitiveSink;
-import com.google.gerrit.server.cache.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
diff --git a/java/com/google/gerrit/server/cache/serialize/BUILD b/java/com/google/gerrit/server/cache/serialize/BUILD
new file mode 100644
index 0000000..957a153
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/serialize/BUILD
@@ -0,0 +1,12 @@
+java_library(
+ name = "serialize",
+ srcs = glob(["*.java"]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//java/com/google/gerrit/common:annotations",
+ "//lib:guava",
+ "//lib:gwtorm",
+ "//lib:protobuf",
+ "//lib/jgit/org.eclipse.jgit:jgit",
+ ],
+)
diff --git a/java/com/google/gerrit/server/cache/BooleanCacheSerializer.java b/java/com/google/gerrit/server/cache/serialize/BooleanCacheSerializer.java
similarity index 96%
rename from java/com/google/gerrit/server/cache/BooleanCacheSerializer.java
rename to java/com/google/gerrit/server/cache/serialize/BooleanCacheSerializer.java
index 59fc946..28cd6eb 100644
--- a/java/com/google/gerrit/server/cache/BooleanCacheSerializer.java
+++ b/java/com/google/gerrit/server/cache/serialize/BooleanCacheSerializer.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
diff --git a/java/com/google/gerrit/server/cache/CacheSerializer.java b/java/com/google/gerrit/server/cache/serialize/CacheSerializer.java
similarity index 96%
rename from java/com/google/gerrit/server/cache/CacheSerializer.java
rename to java/com/google/gerrit/server/cache/serialize/CacheSerializer.java
index 08deecd..2d41f2c 100644
--- a/java/com/google/gerrit/server/cache/CacheSerializer.java
+++ b/java/com/google/gerrit/server/cache/serialize/CacheSerializer.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
/**
* Interface for serializing/deserializing a type to/from a persistent cache.
diff --git a/java/com/google/gerrit/server/cache/EnumCacheSerializer.java b/java/com/google/gerrit/server/cache/serialize/EnumCacheSerializer.java
similarity index 96%
rename from java/com/google/gerrit/server/cache/EnumCacheSerializer.java
rename to java/com/google/gerrit/server/cache/serialize/EnumCacheSerializer.java
index c5be783..7856e55 100644
--- a/java/com/google/gerrit/server/cache/EnumCacheSerializer.java
+++ b/java/com/google/gerrit/server/cache/serialize/EnumCacheSerializer.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
diff --git a/java/com/google/gerrit/server/cache/IntKeyCacheSerializer.java b/java/com/google/gerrit/server/cache/serialize/IntKeyCacheSerializer.java
similarity index 95%
rename from java/com/google/gerrit/server/cache/IntKeyCacheSerializer.java
rename to java/com/google/gerrit/server/cache/serialize/IntKeyCacheSerializer.java
index a07c004..cff8682 100644
--- a/java/com/google/gerrit/server/cache/IntKeyCacheSerializer.java
+++ b/java/com/google/gerrit/server/cache/serialize/IntKeyCacheSerializer.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
import static com.google.common.base.Preconditions.checkNotNull;
diff --git a/java/com/google/gerrit/server/cache/IntegerCacheSerializer.java b/java/com/google/gerrit/server/cache/serialize/IntegerCacheSerializer.java
similarity index 97%
rename from java/com/google/gerrit/server/cache/IntegerCacheSerializer.java
rename to java/com/google/gerrit/server/cache/serialize/IntegerCacheSerializer.java
index 5eddb71..3195941 100644
--- a/java/com/google/gerrit/server/cache/IntegerCacheSerializer.java
+++ b/java/com/google/gerrit/server/cache/serialize/IntegerCacheSerializer.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
import static com.google.common.base.Preconditions.checkNotNull;
diff --git a/java/com/google/gerrit/server/cache/JavaCacheSerializer.java b/java/com/google/gerrit/server/cache/serialize/JavaCacheSerializer.java
similarity index 97%
rename from java/com/google/gerrit/server/cache/JavaCacheSerializer.java
rename to java/com/google/gerrit/server/cache/serialize/JavaCacheSerializer.java
index 55358bc..ee71846 100644
--- a/java/com/google/gerrit/server/cache/JavaCacheSerializer.java
+++ b/java/com/google/gerrit/server/cache/serialize/JavaCacheSerializer.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
import com.google.gerrit.common.Nullable;
import java.io.ByteArrayInputStream;
diff --git a/java/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializer.java b/java/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializer.java
new file mode 100644
index 0000000..500875d
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializer.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2018 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.cache.serialize;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+
+public enum ObjectIdCacheSerializer implements CacheSerializer<ObjectId> {
+ INSTANCE;
+
+ @Override
+ public byte[] serialize(ObjectId object) {
+ byte[] buf = new byte[Constants.OBJECT_ID_LENGTH];
+ object.copyRawTo(buf, 0);
+ return buf;
+ }
+
+ @Override
+ public ObjectId deserialize(byte[] in) {
+ if (in == null || in.length != Constants.OBJECT_ID_LENGTH) {
+ throw new IllegalArgumentException("Failed to deserialize ObjectId");
+ }
+ return ObjectId.fromRaw(in);
+ }
+}
diff --git a/java/com/google/gerrit/server/cache/ProtoCacheSerializers.java b/java/com/google/gerrit/server/cache/serialize/ProtoCacheSerializers.java
similarity index 98%
rename from java/com/google/gerrit/server/cache/ProtoCacheSerializers.java
rename to java/com/google/gerrit/server/cache/serialize/ProtoCacheSerializers.java
index c6fc0b9..4e0b106 100644
--- a/java/com/google/gerrit/server/cache/ProtoCacheSerializers.java
+++ b/java/com/google/gerrit/server/cache/serialize/ProtoCacheSerializers.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
import static com.google.common.base.Preconditions.checkArgument;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
diff --git a/java/com/google/gerrit/server/cache/StringSerializer.java b/java/com/google/gerrit/server/cache/serialize/StringCacheSerializer.java
similarity index 94%
rename from java/com/google/gerrit/server/cache/StringSerializer.java
rename to java/com/google/gerrit/server/cache/serialize/StringCacheSerializer.java
index 1e456c7..525b75b 100644
--- a/java/com/google/gerrit/server/cache/StringSerializer.java
+++ b/java/com/google/gerrit/server/cache/serialize/StringCacheSerializer.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -23,7 +23,7 @@
import java.nio.charset.Charset;
import java.nio.charset.CodingErrorAction;
-public enum StringSerializer implements CacheSerializer<String> {
+public enum StringCacheSerializer implements CacheSerializer<String> {
INSTANCE;
@Override
diff --git a/java/com/google/gerrit/server/cache/testing/BUILD b/java/com/google/gerrit/server/cache/testing/BUILD
index ed412af..9a9f1ef 100644
--- a/java/com/google/gerrit/server/cache/testing/BUILD
+++ b/java/com/google/gerrit/server/cache/testing/BUILD
@@ -5,6 +5,7 @@
srcs = glob(["*.java"]),
visibility = ["//visibility:public"],
deps = [
+ "//java/com/google/gerrit/server/cache/serialize",
"//lib:guava",
"//lib:protobuf",
"//lib/commons:lang3",
diff --git a/java/com/google/gerrit/server/cache/testing/CacheSerializerTestUtil.java b/java/com/google/gerrit/server/cache/testing/CacheSerializerTestUtil.java
index 5d41490..b339e24 100644
--- a/java/com/google/gerrit/server/cache/testing/CacheSerializerTestUtil.java
+++ b/java/com/google/gerrit/server/cache/testing/CacheSerializerTestUtil.java
@@ -18,12 +18,16 @@
/** Static utilities for testing cache serializers. */
public class CacheSerializerTestUtil {
- public static ByteString bytes(int... ints) {
+ public static ByteString byteString(int... ints) {
+ return ByteString.copyFrom(byteArray(ints));
+ }
+
+ public static byte[] byteArray(int... ints) {
byte[] bytes = new byte[ints.length];
for (int i = 0; i < ints.length; i++) {
bytes[i] = (byte) ints[i];
}
- return ByteString.copyFrom(bytes);
+ return bytes;
}
private CacheSerializerTestUtil() {}
diff --git a/java/com/google/gerrit/server/cache/testing/SerializedClassSubject.java b/java/com/google/gerrit/server/cache/testing/SerializedClassSubject.java
index 19c5b67..b902c1c 100644
--- a/java/com/google/gerrit/server/cache/testing/SerializedClassSubject.java
+++ b/java/com/google/gerrit/server/cache/testing/SerializedClassSubject.java
@@ -32,7 +32,7 @@
/**
* Subject about classes that are serialized into persistent caches.
*
- * <p>Hand-written {@link com.google.gerrit.server.cache.CacheSerializer CacheSerializer}
+ * <p>Hand-written {@link com.google.gerrit.server.cache.serialize.CacheSerializer CacheSerializer}
* implementations depend on the exact representation of the data stored in a class, so it is
* important to verify any assumptions about the structure of the serialized classes. This class
* contains assertions about serialized classes, and should be used for every class that has a
@@ -100,4 +100,11 @@
.named("no-argument abstract methods on %s", actual().getName())
.isEqualTo(expectedMethods);
}
+
+ public void extendsClass(Type superclassType) {
+ isNotNull();
+ assertThat(actual().getGenericSuperclass())
+ .named("superclass of %s", actual().getName())
+ .isEqualTo(superclassType);
+ }
}
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index e8c55e8..38c97f7 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -552,8 +552,6 @@
return;
}
- PermissionBackend.ForRef perm =
- permissionBackend.user(ctx.getUser()).project(ctx.getProject()).ref(refName);
try {
try (CommitReceivedEvent event =
new CommitReceivedEvent(
@@ -565,7 +563,7 @@
ctx.getIdentifiedUser())) {
commitValidatorsFactory
.forGerritCommits(
- perm,
+ permissionBackend.user(ctx.getUser()).project(ctx.getProject()),
new Branch.NameKey(ctx.getProject(), refName),
ctx.getIdentifiedUser(),
new NoSshInfo(),
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 04b649b..173d1da 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -1207,6 +1207,12 @@
Collection<LabelInfo> labels = out.labels.values();
Set<Account.Id> fixed = Sets.newHashSetWithExpectedSize(labels.size());
Set<Account.Id> removable = Sets.newHashSetWithExpectedSize(labels.size());
+
+ // Check if the user has the permission to remove a reviewer. This means we can bypass the
+ // testRemoveReviewer check for a specific reviewer in the loop saving potentially many
+ // permission checks.
+ boolean canRemoveAnyReviewer =
+ permissionBackendForChange(userProvider.get(), cd).test(ChangePermission.REMOVE_REVIEWER);
for (LabelInfo label : labels) {
if (label.all == null) {
continue;
@@ -1214,8 +1220,9 @@
for (ApprovalInfo ai : label.all) {
Account.Id id = new Account.Id(ai._accountId);
- if (removeReviewerControl.testRemoveReviewer(
- cd, userProvider.get(), id, MoreObjects.firstNonNull(ai.value, 0))) {
+ if (canRemoveAnyReviewer
+ || removeReviewerControl.testRemoveReviewer(
+ cd, userProvider.get(), id, MoreObjects.firstNonNull(ai.value, 0))) {
removable.add(id);
} else {
fixed.add(id);
@@ -1232,7 +1239,8 @@
for (AccountInfo ai : ccs) {
if (ai._accountId != null) {
Account.Id id = new Account.Id(ai._accountId);
- if (removeReviewerControl.testRemoveReviewer(cd, userProvider.get(), id, 0)) {
+ if (canRemoveAnyReviewer
+ || removeReviewerControl.testRemoveReviewer(cd, userProvider.get(), id, 0)) {
removable.add(id);
}
}
@@ -1397,8 +1405,7 @@
out.commitWithFooters =
mergeUtilFactory
.create(projectCache.get(project))
- .createCommitMessageOnSubmit(
- commit, mergeTip, cd.notes(), userProvider.get(), in.getId());
+ .createCommitMessageOnSubmit(commit, mergeTip, cd.notes(), in.getId());
}
}
diff --git a/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java b/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
index 24685af..a6786d8 100644
--- a/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
+++ b/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
@@ -29,11 +29,11 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.CacheSerializer;
-import com.google.gerrit.server.cache.EnumCacheSerializer;
-import com.google.gerrit.server.cache.ProtoCacheSerializers;
-import com.google.gerrit.server.cache.ProtoCacheSerializers.ObjectIdConverter;
import com.google.gerrit.server.cache.proto.Cache.ChangeKindKeyProto;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.EnumCacheSerializer;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers.ObjectIdConverter;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.InMemoryInserter;
diff --git a/java/com/google/gerrit/server/change/IncludedIn.java b/java/com/google/gerrit/server/change/IncludedIn.java
index 8f8925a..d5d54ec 100644
--- a/java/com/google/gerrit/server/change/IncludedIn.java
+++ b/java/com/google/gerrit/server/change/IncludedIn.java
@@ -63,13 +63,13 @@
ListMultimap<String, String> external = MultimapBuilder.hashKeys().arrayListValues().build();
for (ExternalIncludedIn ext : externalIncludedIn) {
ListMultimap<String, String> extIncludedIns =
- ext.getIncludedIn(project.get(), rev.name(), d.getTags(), d.getBranches());
+ ext.getIncludedIn(project.get(), rev.name(), d.tags(), d.branches());
if (extIncludedIns != null) {
external.putAll(extIncludedIns);
}
}
return new IncludedInInfo(
- d.getBranches(), d.getTags(), (!external.isEmpty() ? external.asMap() : null));
+ d.branches(), d.tags(), (!external.isEmpty() ? external.asMap() : null));
}
}
}
diff --git a/java/com/google/gerrit/server/change/IncludedInResolver.java b/java/com/google/gerrit/server/change/IncludedInResolver.java
index d1bc0a2..62e9454 100644
--- a/java/com/google/gerrit/server/change/IncludedInResolver.java
+++ b/java/com/google/gerrit/server/change/IncludedInResolver.java
@@ -14,6 +14,13 @@
package com.google.gerrit.server.change;
+import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
+import static java.util.Comparator.comparing;
+import static java.util.Comparator.naturalOrder;
+import static java.util.stream.Collectors.toList;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
@@ -22,7 +29,6 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -93,11 +99,9 @@
parseCommits(allTagsAndBranches);
Set<String> allMatchingTagsAndBranches = includedIn(tipsByCommitTime, 0);
- Result detail = new Result();
- detail.setBranches(getMatchingRefNames(allMatchingTagsAndBranches, branches));
- detail.setTags(getMatchingRefNames(allMatchingTagsAndBranches, tags));
-
- return detail;
+ return new AutoValue_IncludedInResolver_Result(
+ getMatchingRefNames(allMatchingTagsAndBranches, branches),
+ getMatchingRefNames(allMatchingTagsAndBranches, tags));
}
private boolean includedInOne(Collection<Ref> refs) throws IOException {
@@ -151,15 +155,7 @@
*/
private void partition(List<RevCommit> before, List<RevCommit> after) {
int insertionPoint =
- Collections.binarySearch(
- tipsByCommitTime,
- target,
- new Comparator<RevCommit>() {
- @Override
- public int compare(RevCommit c1, RevCommit c2) {
- return c1.getCommitTime() - c2.getCommitTime();
- }
- });
+ Collections.binarySearch(tipsByCommitTime, target, comparing(RevCommit::getCommitTime));
if (insertionPoint < 0) {
insertionPoint = -(insertionPoint + 1);
}
@@ -175,15 +171,14 @@
* Returns the short names of refs which are as well in the matchingRefs list as well as in the
* allRef list.
*/
- private static List<String> getMatchingRefNames(
+ private static ImmutableSortedSet<String> getMatchingRefNames(
Set<String> matchingRefs, Collection<Ref> allRefs) {
- List<String> refNames = Lists.newArrayListWithCapacity(matchingRefs.size());
- for (Ref r : allRefs) {
- if (matchingRefs.contains(r.getName())) {
- refNames.add(Repository.shortenRefName(r.getName()));
- }
- }
- return refNames;
+ return allRefs
+ .stream()
+ .map(Ref::getName)
+ .filter(matchingRefs::contains)
+ .map(Repository::shortenRefName)
+ .collect(toImmutableSortedSet(naturalOrder()));
}
/** Parse commit of ref and store the relation between ref and commit. */
@@ -211,43 +206,14 @@
}
commitToRef.put(commit, ref.getName());
}
- tipsByCommitTime = Lists.newArrayList(commitToRef.keySet());
- sortOlderFirst(tipsByCommitTime);
+ tipsByCommitTime =
+ commitToRef.keySet().stream().sorted(comparing(RevCommit::getCommitTime)).collect(toList());
}
- private void sortOlderFirst(List<RevCommit> tips) {
- Collections.sort(
- tips,
- new Comparator<RevCommit>() {
- @Override
- public int compare(RevCommit c1, RevCommit c2) {
- return c1.getCommitTime() - c2.getCommitTime();
- }
- });
- }
+ @AutoValue
+ public abstract static class Result {
+ public abstract ImmutableSortedSet<String> branches();
- public static class Result {
- private List<String> branches;
- private List<String> tags;
-
- public Result() {}
-
- public void setBranches(List<String> b) {
- Collections.sort(b);
- branches = b;
- }
-
- public List<String> getBranches() {
- return branches;
- }
-
- public void setTags(List<String> t) {
- Collections.sort(t);
- tags = t;
- }
-
- public List<String> getTags() {
- return tags;
- }
+ public abstract ImmutableSortedSet<String> tags();
}
}
diff --git a/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java b/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
index ba54361..2d00886 100644
--- a/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
+++ b/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
@@ -26,12 +26,12 @@
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.server.cache.BooleanCacheSerializer;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.CacheSerializer;
-import com.google.gerrit.server.cache.ProtoCacheSerializers;
-import com.google.gerrit.server.cache.ProtoCacheSerializers.ObjectIdConverter;
import com.google.gerrit.server.cache.proto.Cache.MergeabilityKeyProto;
+import com.google.gerrit.server.cache.serialize.BooleanCacheSerializer;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers.ObjectIdConverter;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.submit.SubmitDryRun;
diff --git a/java/com/google/gerrit/server/change/PatchSetInserter.java b/java/com/google/gerrit/server/change/PatchSetInserter.java
index d71a93d..8bd6c17 100644
--- a/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -323,9 +323,6 @@
return;
}
- PermissionBackend.ForRef perm =
- permissionBackend.user(ctx.getUser()).ref(origNotes.getChange().getDest());
-
String refName = getPatchSetId().toRefName();
try (CommitReceivedEvent event =
new CommitReceivedEvent(
@@ -340,7 +337,7 @@
ctx.getIdentifiedUser())) {
commitValidatorsFactory
.forGerritCommits(
- perm,
+ permissionBackend.user(ctx.getUser()).project(ctx.getProject()),
origNotes.getChange().getDest(),
ctx.getIdentifiedUser(),
new NoSshInfo(),
diff --git a/java/com/google/gerrit/server/change/RebaseChangeOp.java b/java/com/google/gerrit/server/change/RebaseChangeOp.java
index 909ea3a..1f216f0 100644
--- a/java/com/google/gerrit/server/change/RebaseChangeOp.java
+++ b/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -165,8 +165,7 @@
rw.parseBody(baseCommit);
newCommitMessage =
newMergeUtil()
- .createCommitMessageOnSubmit(
- original, baseCommit, notes, changeOwner, originalPatchSet.getId());
+ .createCommitMessageOnSubmit(original, baseCommit, notes, originalPatchSet.getId());
} else {
newCommitMessage = original.getFullMessage();
}
diff --git a/java/com/google/gerrit/server/change/WalkSorter.java b/java/com/google/gerrit/server/change/WalkSorter.java
index cff1ac7..916a62b 100644
--- a/java/com/google/gerrit/server/change/WalkSorter.java
+++ b/java/com/google/gerrit/server/change/WalkSorter.java
@@ -34,7 +34,6 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
@@ -110,7 +109,7 @@
for (Map.Entry<Project.NameKey, Collection<ChangeData>> e : byProject.asMap().entrySet()) {
sortedByProject.add(sortProject(e.getKey(), e.getValue()));
}
- Collections.sort(sortedByProject, PROJECT_LIST_SORTER);
+ sortedByProject.sort(PROJECT_LIST_SORTER);
return Iterables.concat(sortedByProject);
}
diff --git a/java/com/google/gerrit/server/change/WorkInProgressOp.java b/java/com/google/gerrit/server/change/WorkInProgressOp.java
index 75a9323..49def5f 100644
--- a/java/com/google/gerrit/server/change/WorkInProgressOp.java
+++ b/java/com/google/gerrit/server/change/WorkInProgressOp.java
@@ -125,7 +125,7 @@
@Override
public void postUpdate(Context ctx) {
- stateChanged.fire(change, ctx.getAccount(), ctx.getWhen());
+ stateChanged.fire(change, ps, ctx.getAccount(), ctx.getWhen());
if (workInProgress || notify.ordinal() < NotifyHandling.OWNER_REVIEWERS.ordinal()) {
return;
}
diff --git a/java/com/google/gerrit/server/config/CacheResource.java b/java/com/google/gerrit/server/config/CacheResource.java
index 16c7508..ffa7b5a 100644
--- a/java/com/google/gerrit/server/config/CacheResource.java
+++ b/java/com/google/gerrit/server/config/CacheResource.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.config;
import com.google.common.cache.Cache;
+import com.google.gerrit.extensions.registration.PluginName;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
@@ -52,7 +53,7 @@
}
public static String cacheNameOf(String plugin, String name) {
- if ("gerrit".equals(plugin)) {
+ if (PluginName.GERRIT.equals(plugin)) {
return name;
}
return plugin + "-" + name;
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index b6a257b..0761d2e 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -170,6 +170,7 @@
import com.google.gerrit.server.restapi.change.SuggestReviewers;
import com.google.gerrit.server.restapi.group.GroupModule;
import com.google.gerrit.server.rules.DefaultSubmitRule;
+import com.google.gerrit.server.rules.IgnoreSelfApprovalRule;
import com.google.gerrit.server.rules.PrologModule;
import com.google.gerrit.server.rules.RulesCache;
import com.google.gerrit.server.rules.SubmitRule;
@@ -244,6 +245,7 @@
install(new NoteDbModule(cfg));
install(new PrologModule());
install(new DefaultSubmitRule.Module());
+ install(new IgnoreSelfApprovalRule.Module());
install(new ReceiveCommitsModule());
install(new SshAddressesModule());
install(ThreadLocalRequestContext.module());
diff --git a/java/com/google/gerrit/server/config/SysExecutorModule.java b/java/com/google/gerrit/server/config/SysExecutorModule.java
index 2e97c06..f552434 100644
--- a/java/com/google/gerrit/server/config/SysExecutorModule.java
+++ b/java/com/google/gerrit/server/config/SysExecutorModule.java
@@ -19,6 +19,7 @@
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gerrit.server.FanOutExecutor;
import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.logging.LoggingContextAwareExecutorService;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
@@ -82,14 +83,18 @@
return MoreExecutors.newDirectExecutorService();
}
return MoreExecutors.listeningDecorator(
- MoreExecutors.getExitingExecutorService(
- new ThreadPoolExecutor(
- 1,
- poolSize,
- 10,
- TimeUnit.MINUTES,
- new ArrayBlockingQueue<Runnable>(poolSize),
- new ThreadFactoryBuilder().setNameFormat("ChangeUpdate-%d").setDaemon(true).build(),
- new ThreadPoolExecutor.CallerRunsPolicy())));
+ new LoggingContextAwareExecutorService(
+ MoreExecutors.getExitingExecutorService(
+ new ThreadPoolExecutor(
+ 1,
+ poolSize,
+ 10,
+ TimeUnit.MINUTES,
+ new ArrayBlockingQueue<Runnable>(poolSize),
+ new ThreadFactoryBuilder()
+ .setNameFormat("ChangeUpdate-%d")
+ .setDaemon(true)
+ .build(),
+ new ThreadPoolExecutor.CallerRunsPolicy()))));
}
}
diff --git a/java/com/google/gerrit/server/documentation/MarkdownFormatter.java b/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
index a7f9a05..2eb46f1 100644
--- a/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
+++ b/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
@@ -14,30 +14,33 @@
package com.google.gerrit.server.documentation;
+import static com.vladsch.flexmark.profiles.pegdown.Extensions.ALL;
+import static com.vladsch.flexmark.profiles.pegdown.Extensions.HARDWRAPS;
+import static com.vladsch.flexmark.profiles.pegdown.Extensions.SUPPRESS_ALL_HTML;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.pegdown.Extensions.ALL;
-import static org.pegdown.Extensions.HARDWRAPS;
-import static org.pegdown.Extensions.SUPPRESS_ALL_HTML;
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
+import com.vladsch.flexmark.Extension;
+import com.vladsch.flexmark.ast.Block;
+import com.vladsch.flexmark.ast.Heading;
+import com.vladsch.flexmark.ast.Node;
+import com.vladsch.flexmark.ast.util.TextCollectingVisitor;
+import com.vladsch.flexmark.html.HtmlRenderer;
+import com.vladsch.flexmark.parser.Parser;
+import com.vladsch.flexmark.profiles.pegdown.PegdownOptionsAdapter;
+import com.vladsch.flexmark.util.options.MutableDataHolder;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.nio.charset.Charset;
+import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang.StringEscapeUtils;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.TemporaryBuffer;
-import org.pegdown.LinkRenderer;
-import org.pegdown.PegDownProcessor;
-import org.pegdown.ToHtmlSerializer;
-import org.pegdown.ast.HeaderNode;
-import org.pegdown.ast.Node;
-import org.pegdown.ast.RootNode;
-import org.pegdown.ast.TextNode;
public class MarkdownFormatter {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -48,9 +51,9 @@
AtomicBoolean file = new AtomicBoolean();
String src;
try {
- src = readPegdownCss(file);
+ src = readFlexMarkJavaCss(file);
} catch (IOException err) {
- logger.atWarning().withCause(err).log("Cannot load pegdown.css");
+ logger.atWarning().withCause(err).log("Cannot load flexmark-java.css");
src = "";
}
defaultCss = file.get() ? null : src;
@@ -61,9 +64,9 @@
return defaultCss;
}
try {
- return readPegdownCss(new AtomicBoolean());
+ return readFlexMarkJavaCss(new AtomicBoolean());
} catch (IOException err) {
- logger.atWarning().withCause(err).log("Cannot load pegdown.css");
+ logger.atWarning().withCause(err).log("Cannot load flexmark-java.css");
return "";
}
}
@@ -81,8 +84,28 @@
return this;
}
+ private MutableDataHolder markDownOptions() {
+ int options = ALL & ~(HARDWRAPS);
+ if (suppressHtml) {
+ options |= SUPPRESS_ALL_HTML;
+ }
+
+ MutableDataHolder optionsExt =
+ PegdownOptionsAdapter.flexmarkOptions(
+ options, MarkdownFormatterHeader.HeadingExtension.create())
+ .toMutable();
+
+ ArrayList<Extension> extensions = new ArrayList<>();
+ for (Extension extension : optionsExt.get(com.vladsch.flexmark.parser.Parser.EXTENSIONS)) {
+ extensions.add(extension);
+ }
+
+ return optionsExt;
+ }
+
public byte[] markdownToDocHtml(String md, String charEnc) throws UnsupportedEncodingException {
- RootNode root = parseMarkdown(md);
+ Node root = parseMarkdown(md);
+ HtmlRenderer renderer = HtmlRenderer.builder(markDownOptions()).build();
String title = findTitle(root);
StringBuilder html = new StringBuilder();
@@ -100,7 +123,7 @@
html.append("\n</style>");
html.append("</head>");
html.append("<body>\n");
- html.append(new ToHtmlSerializer(new LinkRenderer()).toHtml(root));
+ html.append(renderer.render(root));
html.append("\n</body></html>");
return html.toString().getBytes(charEnc);
}
@@ -111,38 +134,36 @@
}
private String findTitle(Node root) {
- if (root instanceof HeaderNode) {
- HeaderNode h = (HeaderNode) root;
- if (h.getLevel() == 1 && h.getChildren() != null && !h.getChildren().isEmpty()) {
- StringBuilder b = new StringBuilder();
- for (Node n : root.getChildren()) {
- if (n instanceof TextNode) {
- b.append(((TextNode) n).getText());
- }
- }
- return b.toString();
+ if (root instanceof Heading) {
+ Heading h = (Heading) root;
+ if (h.getLevel() == 1 && h.hasChildren()) {
+ TextCollectingVisitor collectingVisitor = new TextCollectingVisitor();
+ return collectingVisitor.collectAndGetText(h);
}
}
- for (Node n : root.getChildren()) {
- String title = findTitle(n);
- if (title != null) {
- return title;
+ if (root instanceof Block && root.hasChildren()) {
+ Node child = root.getFirstChild();
+ while (child != null) {
+ String title = findTitle(child);
+ if (title != null) {
+ return title;
+ }
+ child = child.getNext();
}
}
+
return null;
}
- private RootNode parseMarkdown(String md) {
- int options = ALL & ~(HARDWRAPS);
- if (suppressHtml) {
- options |= SUPPRESS_ALL_HTML;
- }
- return new PegDownProcessor(options).parseMarkdown(md.toCharArray());
+ private Node parseMarkdown(String md) {
+ Parser parser = Parser.builder(markDownOptions()).build();
+ Node document = parser.parse(md);
+ return document;
}
- private static String readPegdownCss(AtomicBoolean file) throws IOException {
- String name = "pegdown.css";
+ private static String readFlexMarkJavaCss(AtomicBoolean file) throws IOException {
+ String name = "flexmark-java.css";
URL url = MarkdownFormatter.class.getResource(name);
if (url == null) {
throw new FileNotFoundException("Resource " + name);
diff --git a/java/com/google/gerrit/server/documentation/MarkdownFormatterHeader.java b/java/com/google/gerrit/server/documentation/MarkdownFormatterHeader.java
new file mode 100644
index 0000000..00471fd
--- /dev/null
+++ b/java/com/google/gerrit/server/documentation/MarkdownFormatterHeader.java
@@ -0,0 +1,158 @@
+// Copyright (C) 2018 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.documentation;
+
+import com.vladsch.flexmark.ast.Heading;
+import com.vladsch.flexmark.ast.Node;
+import com.vladsch.flexmark.ext.anchorlink.AnchorLink;
+import com.vladsch.flexmark.ext.anchorlink.internal.AnchorLinkNodeRenderer;
+import com.vladsch.flexmark.html.CustomNodeRenderer;
+import com.vladsch.flexmark.html.HtmlRenderer;
+import com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension;
+import com.vladsch.flexmark.html.HtmlWriter;
+import com.vladsch.flexmark.html.renderer.DelegatingNodeRendererFactory;
+import com.vladsch.flexmark.html.renderer.NodeRenderer;
+import com.vladsch.flexmark.html.renderer.NodeRendererContext;
+import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
+import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
+import com.vladsch.flexmark.profiles.pegdown.Extensions;
+import com.vladsch.flexmark.profiles.pegdown.PegdownOptionsAdapter;
+import com.vladsch.flexmark.util.options.DataHolder;
+import com.vladsch.flexmark.util.options.MutableDataHolder;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class MarkdownFormatterHeader {
+ static class HeadingExtension implements HtmlRendererExtension {
+ @Override
+ public void rendererOptions(final MutableDataHolder options) {
+ // add any configuration settings to options you want to apply to everything, here
+ }
+
+ @Override
+ public void extend(final HtmlRenderer.Builder rendererBuilder, final String rendererType) {
+ rendererBuilder.nodeRendererFactory(new HeadingNodeRenderer.Factory());
+ }
+
+ static HeadingExtension create() {
+ return new HeadingExtension();
+ }
+ }
+
+ static class HeadingNodeRenderer implements NodeRenderer {
+ public HeadingNodeRenderer() {}
+
+ @Override
+ public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
+ return new HashSet<NodeRenderingHandler<? extends Node>>(
+ Arrays.asList(
+ new NodeRenderingHandler<>(
+ AnchorLink.class,
+ new CustomNodeRenderer<AnchorLink>() {
+ @Override
+ public void render(
+ AnchorLink node, NodeRendererContext context, HtmlWriter html) {
+ HeadingNodeRenderer.this.render(node, context);
+ }
+ }),
+ new NodeRenderingHandler<>(
+ Heading.class,
+ new CustomNodeRenderer<Heading>() {
+ @Override
+ public void render(Heading node, NodeRendererContext context, HtmlWriter html) {
+ HeadingNodeRenderer.this.render(node, context, html);
+ }
+ })));
+ }
+
+ void render(final AnchorLink node, final NodeRendererContext context) {
+ Node parent = node.getParent();
+
+ if (parent instanceof Heading && ((Heading) parent).getLevel() == 1) {
+ // render without anchor link
+ context.renderChildren(node);
+ } else {
+ context.delegateRender();
+ }
+ }
+
+ static boolean haveExtension(int extensions, int flags) {
+ return (extensions & flags) != 0;
+ }
+
+ static boolean haveAllExtensions(int extensions, int flags) {
+ return (extensions & flags) == flags;
+ }
+
+ void render(final Heading node, final NodeRendererContext context, final HtmlWriter html) {
+ if (node.getLevel() == 1) {
+ // render without anchor link
+ final int extensions = context.getOptions().get(PegdownOptionsAdapter.PEGDOWN_EXTENSIONS);
+ if (context.getHtmlOptions().renderHeaderId
+ || haveExtension(extensions, Extensions.ANCHORLINKS)
+ || haveAllExtensions(
+ extensions, Extensions.EXTANCHORLINKS | Extensions.EXTANCHORLINKS_WRAP)) {
+ String id = context.getNodeId(node);
+ if (id != null) {
+ html.attr("id", id);
+ }
+ }
+
+ if (context.getHtmlOptions().sourcePositionParagraphLines) {
+ html.srcPos(node.getChars())
+ .withAttr()
+ .tagLine(
+ "h" + node.getLevel(),
+ new Runnable() {
+ @Override
+ public void run() {
+ html.srcPos(node.getText()).withAttr().tag("span");
+ context.renderChildren(node);
+ html.tag("/span");
+ }
+ });
+ } else {
+ html.srcPos(node.getText())
+ .withAttr()
+ .tagLine(
+ "h" + node.getLevel(),
+ new Runnable() {
+ @Override
+ public void run() {
+ context.renderChildren(node);
+ }
+ });
+ }
+ } else {
+ context.delegateRender();
+ }
+ }
+
+ public static class Factory implements DelegatingNodeRendererFactory {
+ @Override
+ public NodeRenderer create(final DataHolder options) {
+ return new HeadingNodeRenderer();
+ }
+
+ @Override
+ public Set<Class<? extends NodeRendererFactory>> getDelegates() {
+ Set<Class<? extends NodeRendererFactory>> delegates = new HashSet<>();
+ delegates.add(AnchorLinkNodeRenderer.Factory.class);
+ return delegates;
+ }
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/events/EventFactory.java b/java/com/google/gerrit/server/events/EventFactory.java
index 2fbc1c7..fbce4b2 100644
--- a/java/com/google/gerrit/server/events/EventFactory.java
+++ b/java/com/google/gerrit/server/events/EventFactory.java
@@ -73,7 +73,6 @@
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -329,10 +328,9 @@
}
}
// Sort by original parent order.
- Collections.sort(
- ca.dependsOn,
+ ca.dependsOn.sort(
comparing(
- (DependencyAttribute d) -> {
+ d -> {
for (int i = 0; i < parentNames.size(); i++) {
if (parentNames.get(i).equals(d.revision)) {
return i;
diff --git a/java/com/google/gerrit/server/events/PrivateStateChangedEvent.java b/java/com/google/gerrit/server/events/PrivateStateChangedEvent.java
index af42b08..d03eda4 100644
--- a/java/com/google/gerrit/server/events/PrivateStateChangedEvent.java
+++ b/java/com/google/gerrit/server/events/PrivateStateChangedEvent.java
@@ -18,7 +18,7 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.data.AccountAttribute;
-public class PrivateStateChangedEvent extends ChangeEvent {
+public class PrivateStateChangedEvent extends PatchSetEvent {
static final String TYPE = "private-state-changed";
public Supplier<AccountAttribute> changer;
diff --git a/java/com/google/gerrit/server/events/StreamEventsApiListener.java b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
index 41cc701..367a38b 100644
--- a/java/com/google/gerrit/server/events/StreamEventsApiListener.java
+++ b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
@@ -472,10 +472,12 @@
try {
ChangeNotes notes = getNotes(ev.getChange());
Change change = notes.getChange();
+ PatchSet patchSet = getPatchSet(notes, ev.getRevision());
WorkInProgressStateChangedEvent event = new WorkInProgressStateChangedEvent(change);
event.change = changeAttributeSupplier(change, notes);
event.changer = accountAttributeSupplier(ev.getWho());
+ event.patchSet = patchSetAttributeSupplier(change, patchSet);
dispatcher.get().postEvent(change, event);
} catch (OrmException | PermissionBackendException e) {
@@ -488,10 +490,12 @@
try {
ChangeNotes notes = getNotes(ev.getChange());
Change change = notes.getChange();
+ PatchSet patchSet = getPatchSet(notes, ev.getRevision());
PrivateStateChangedEvent event = new PrivateStateChangedEvent(change);
event.change = changeAttributeSupplier(change, notes);
event.changer = accountAttributeSupplier(ev.getWho());
+ event.patchSet = patchSetAttributeSupplier(change, patchSet);
dispatcher.get().postEvent(change, event);
} catch (OrmException | PermissionBackendException e) {
diff --git a/java/com/google/gerrit/server/events/WorkInProgressStateChangedEvent.java b/java/com/google/gerrit/server/events/WorkInProgressStateChangedEvent.java
index ad32672..5e52c7b 100644
--- a/java/com/google/gerrit/server/events/WorkInProgressStateChangedEvent.java
+++ b/java/com/google/gerrit/server/events/WorkInProgressStateChangedEvent.java
@@ -18,7 +18,7 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.data.AccountAttribute;
-public class WorkInProgressStateChangedEvent extends ChangeEvent {
+public class WorkInProgressStateChangedEvent extends PatchSetEvent {
static final String TYPE = "wip-state-changed";
public Supplier<AccountAttribute> changer;
diff --git a/java/com/google/gerrit/server/extensions/events/EventUtil.java b/java/com/google/gerrit/server/extensions/events/EventUtil.java
index 74fba9a..5116708 100644
--- a/java/com/google/gerrit/server/extensions/events/EventUtil.java
+++ b/java/com/google/gerrit/server/extensions/events/EventUtil.java
@@ -121,8 +121,11 @@
public void logEventListenerError(Object event, Object listener, Exception error) {
logger.atWarning().log(
- "Error in event listener %s for event %s: %s",
- listener.getClass().getName(), event.getClass().getName(), error.getMessage());
+ "Error in event listener %s for event %s: %s - %s",
+ listener.getClass().getName(),
+ event.getClass().getName(),
+ error.getClass().getName(),
+ error.getMessage());
logger.atFine().withCause(error).log(
"Cause of error in event listener %s:", listener.getClass().getName());
}
diff --git a/java/com/google/gerrit/server/extensions/events/PrivateStateChanged.java b/java/com/google/gerrit/server/extensions/events/PrivateStateChanged.java
index acd275d..2df56aa 100644
--- a/java/com/google/gerrit/server/extensions/events/PrivateStateChanged.java
+++ b/java/com/google/gerrit/server/extensions/events/PrivateStateChanged.java
@@ -18,13 +18,19 @@
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.PrivateStateChangedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import java.io.IOException;
import java.sql.Timestamp;
@Singleton
@@ -40,12 +46,17 @@
this.util = util;
}
- public void fire(Change change, AccountState account, Timestamp when) {
+ public void fire(Change change, PatchSet patchSet, AccountState account, Timestamp when) {
if (!listeners.iterator().hasNext()) {
return;
}
try {
- Event event = new Event(util.changeInfo(change), util.accountInfo(account), when);
+ Event event =
+ new Event(
+ util.changeInfo(change),
+ util.revisionInfo(change.getProject(), patchSet),
+ util.accountInfo(account),
+ when);
for (PrivateStateChangedListener l : listeners) {
try {
l.onPrivateStateChanged(event);
@@ -53,16 +64,20 @@
util.logEventListenerError(event, l, e);
}
}
- } catch (OrmException e) {
+ } catch (OrmException
+ | PatchListNotAvailableException
+ | GpgException
+ | IOException
+ | PermissionBackendException e) {
logger.atSevere().withCause(e).log("Couldn't fire event");
}
}
- private static class Event extends AbstractChangeEvent
+ private static class Event extends AbstractRevisionEvent
implements PrivateStateChangedListener.Event {
- protected Event(ChangeInfo change, AccountInfo who, Timestamp when) {
- super(change, who, when, NotifyHandling.ALL);
+ protected Event(ChangeInfo change, RevisionInfo revision, AccountInfo who, Timestamp when) {
+ super(change, revision, who, when, NotifyHandling.ALL);
}
}
}
diff --git a/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java b/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java
index 3f9f35b..1c22561 100644
--- a/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java
+++ b/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java
@@ -18,13 +18,19 @@
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import java.io.IOException;
import java.sql.Timestamp;
@Singleton
@@ -41,12 +47,17 @@
this.util = util;
}
- public void fire(Change change, AccountState account, Timestamp when) {
+ public void fire(Change change, PatchSet patchSet, AccountState account, Timestamp when) {
if (!listeners.iterator().hasNext()) {
return;
}
try {
- Event event = new Event(util.changeInfo(change), util.accountInfo(account), when);
+ Event event =
+ new Event(
+ util.changeInfo(change),
+ util.revisionInfo(change.getProject(), patchSet),
+ util.accountInfo(account),
+ when);
for (WorkInProgressStateChangedListener l : listeners) {
try {
l.onWorkInProgressStateChanged(event);
@@ -54,16 +65,20 @@
util.logEventListenerError(event, l, e);
}
}
- } catch (OrmException e) {
+ } catch (OrmException
+ | PatchListNotAvailableException
+ | GpgException
+ | IOException
+ | PermissionBackendException e) {
logger.atSevere().withCause(e).log("Couldn't fire event");
}
}
- private static class Event extends AbstractChangeEvent
+ private static class Event extends AbstractRevisionEvent
implements WorkInProgressStateChangedListener.Event {
- protected Event(ChangeInfo change, AccountInfo who, Timestamp when) {
- super(change, who, when, NotifyHandling.ALL);
+ protected Event(ChangeInfo change, RevisionInfo revision, AccountInfo who, Timestamp when) {
+ super(change, revision, who, when, NotifyHandling.ALL);
}
}
}
diff --git a/java/com/google/gerrit/server/extensions/webui/UiActions.java b/java/com/google/gerrit/server/extensions/webui/UiActions.java
index f8cb4ce..af28bed3 100644
--- a/java/com/google/gerrit/server/extensions/webui/UiActions.java
+++ b/java/com/google/gerrit/server/extensions/webui/UiActions.java
@@ -26,6 +26,7 @@
import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
import com.google.gerrit.extensions.conditions.BooleanCondition;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.PluginName;
import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
@@ -169,7 +170,7 @@
PrivateInternals_UiActionDescription.setMethod(dsc, e.getExportName().substring(0, d));
PrivateInternals_UiActionDescription.setId(
- dsc, "gerrit".equals(e.getPluginName()) ? name : e.getPluginName() + '~' + name);
+ dsc, PluginName.GERRIT.equals(e.getPluginName()) ? name : e.getPluginName() + '~' + name);
return dsc;
}
}
diff --git a/java/com/google/gerrit/server/git/DefaultChangeReportFormatter.java b/java/com/google/gerrit/server/git/DefaultChangeReportFormatter.java
index 1c87a63..513d909 100644
--- a/java/com/google/gerrit/server/git/DefaultChangeReportFormatter.java
+++ b/java/com/google/gerrit/server/git/DefaultChangeReportFormatter.java
@@ -14,12 +14,16 @@
package com.google.gerrit.server.git;
-import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.inject.Inject;
/** Print a change description for use in git command-line progress. */
public class DefaultChangeReportFormatter implements ChangeReportFormatter {
+ private static final int SUBJECT_MAX_LENGTH = 80;
+ private static final String SUBJECT_CROP_APPENDIX = "...";
+ private static final int SUBJECT_CROP_RANGE = 10;
+
private final String canonicalWebUrl;
@Inject
@@ -37,19 +41,37 @@
return formatChangeUrl(canonicalWebUrl, input);
}
- @Override
- public String changeClosed(ChangeReportFormatter.Input input) {
- return String.format(
- "change %s closed", ChangeUtil.formatChangeUrl(canonicalWebUrl, input.change()));
+ public static String formatChangeUrl(String canonicalWebUrl, Change change) {
+ return canonicalWebUrl + "c/" + change.getProject().get() + "/+/" + change.getChangeId();
}
- private String formatChangeUrl(String url, Input input) {
+ @Override
+ public String changeClosed(ChangeReportFormatter.Input input) {
+ return String.format("change %s closed", formatChangeUrl(canonicalWebUrl, input.change()));
+ }
+
+ protected String cropSubject(String subject) {
+ if (subject.length() > SUBJECT_MAX_LENGTH) {
+ int maxLength = SUBJECT_MAX_LENGTH - SUBJECT_CROP_APPENDIX.length();
+ for (int cropPosition = maxLength;
+ cropPosition > maxLength - SUBJECT_CROP_RANGE;
+ cropPosition--) {
+ if (Character.isWhitespace(subject.charAt(cropPosition - 1))) {
+ return subject.substring(0, cropPosition) + SUBJECT_CROP_APPENDIX;
+ }
+ }
+ return subject.substring(0, maxLength) + SUBJECT_CROP_APPENDIX;
+ }
+ return subject;
+ }
+
+ protected String formatChangeUrl(String url, Input input) {
StringBuilder m =
new StringBuilder()
.append(" ")
- .append(ChangeUtil.formatChangeUrl(url, input.change()))
+ .append(formatChangeUrl(url, input.change()))
.append(" ")
- .append(ChangeUtil.cropSubject(input.subject()));
+ .append(cropSubject(input.subject()));
if (input.isEdit()) {
m.append(" [EDIT]");
}
diff --git a/java/com/google/gerrit/server/git/MergeUtil.java b/java/com/google/gerrit/server/git/MergeUtil.java
index 0231378..c035269 100644
--- a/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/java/com/google/gerrit/server/git/MergeUtil.java
@@ -41,7 +41,6 @@
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -220,7 +219,7 @@
} catch (IOException e) {
throw new IntegrationException("Branch head sorting failed", e);
}
- Collections.sort(result, CodeReviewCommit.ORDER);
+ result.sort(CodeReviewCommit.ORDER);
return result;
}
@@ -315,12 +314,10 @@
*
* @param n
* @param notes
- * @param user
* @param psId
* @return new message
*/
- private String createDetailedCommitMessage(
- RevCommit n, ChangeNotes notes, CurrentUser user, PatchSet.Id psId) {
+ private String createDetailedCommitMessage(RevCommit n, ChangeNotes notes, PatchSet.Id psId) {
Change c = notes.getChange();
final List<FooterLine> footers = n.getFooterLines();
final StringBuilder msgbuf = new StringBuilder();
@@ -424,12 +421,7 @@
}
public String createCommitMessageOnSubmit(CodeReviewCommit n, RevCommit mergeTip) {
- return createCommitMessageOnSubmit(
- n,
- mergeTip,
- n.notes(),
- identifiedUserFactory.create(n.notes().getChange().getOwner()),
- n.getPatchsetId());
+ return createCommitMessageOnSubmit(n, mergeTip, n.notes(), n.getPatchsetId());
}
/**
@@ -442,14 +434,13 @@
* @param n
* @param mergeTip
* @param notes
- * @param user
* @param id
* @return new message
*/
public String createCommitMessageOnSubmit(
- RevCommit n, RevCommit mergeTip, ChangeNotes notes, CurrentUser user, Id id) {
+ RevCommit n, RevCommit mergeTip, ChangeNotes notes, Id id) {
return commitMessageGenerator.generate(
- n, mergeTip, notes.getChange().getDest(), createDetailedCommitMessage(n, notes, user, id));
+ n, mergeTip, notes.getChange().getDest(), createDetailedCommitMessage(n, notes, id));
}
private static boolean isCodeReview(LabelId id) {
diff --git a/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java b/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
index 1b83097..5f1d8c6 100644
--- a/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
+++ b/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
@@ -144,6 +144,7 @@
@Override
public List<CachedChange> load(Project.NameKey key) throws Exception {
+ logger.atFine().log("Loading changes of project %s", key);
try (ManualRequestContext ctx = requestContext.open()) {
List<ChangeData> cds =
queryProvider
diff --git a/java/com/google/gerrit/server/git/TagCache.java b/java/com/google/gerrit/server/git/TagCache.java
index 4d0e056..535644d 100644
--- a/java/com/google/gerrit/server/git/TagCache.java
+++ b/java/com/google/gerrit/server/git/TagCache.java
@@ -17,7 +17,7 @@
import com.google.common.cache.Cache;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.StringSerializer;
+import com.google.gerrit.server.cache.serialize.StringCacheSerializer;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
@@ -35,7 +35,7 @@
protected void configure() {
persist(CACHE_NAME, String.class, TagSetHolder.class)
.version(1)
- .keySerializer(StringSerializer.INSTANCE)
+ .keySerializer(StringCacheSerializer.INSTANCE)
.valueSerializer(TagSetHolder.Serializer.INSTANCE);
bind(TagCache.class);
}
diff --git a/java/com/google/gerrit/server/git/TagSet.java b/java/com/google/gerrit/server/git/TagSet.java
index 916a64a..ce8814f 100644
--- a/java/com/google/gerrit/server/git/TagSet.java
+++ b/java/com/google/gerrit/server/git/TagSet.java
@@ -21,10 +21,10 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.cache.ProtoCacheSerializers.ObjectIdConverter;
import com.google.gerrit.server.cache.proto.Cache.TagSetHolderProto.TagSetProto;
import com.google.gerrit.server.cache.proto.Cache.TagSetHolderProto.TagSetProto.CachedRefProto;
import com.google.gerrit.server.cache.proto.Cache.TagSetHolderProto.TagSetProto.TagProto;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers.ObjectIdConverter;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.util.BitSet;
diff --git a/java/com/google/gerrit/server/git/TagSetHolder.java b/java/com/google/gerrit/server/git/TagSetHolder.java
index 0790a36..4c0c035 100644
--- a/java/com/google/gerrit/server/git/TagSetHolder.java
+++ b/java/com/google/gerrit/server/git/TagSetHolder.java
@@ -18,9 +18,9 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.cache.CacheSerializer;
-import com.google.gerrit.server.cache.ProtoCacheSerializers;
import com.google.gerrit.server.cache.proto.Cache.TagSetHolderProto;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers;
import java.util.Collection;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
diff --git a/java/com/google/gerrit/server/git/TransferConfig.java b/java/com/google/gerrit/server/git/TransferConfig.java
index 204a0d5..8c93833 100644
--- a/java/com/google/gerrit/server/git/TransferConfig.java
+++ b/java/com/google/gerrit/server/git/TransferConfig.java
@@ -16,7 +16,6 @@
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.concurrent.TimeUnit;
@@ -29,6 +28,7 @@
private final PackConfig packConfig;
private final long maxObjectSizeLimit;
private final String maxObjectSizeLimitFormatted;
+ private final boolean inheritProjectMaxObjectSizeLimit;
@Inject
TransferConfig(@GerritServerConfig Config cfg) {
@@ -43,6 +43,8 @@
TimeUnit.SECONDS);
maxObjectSizeLimit = cfg.getLong("receive", "maxObjectSizeLimit", 0);
maxObjectSizeLimitFormatted = cfg.getString("receive", null, "maxObjectSizeLimit");
+ inheritProjectMaxObjectSizeLimit =
+ cfg.getBoolean("receive", "inheritProjectMaxObjectSizeLimit", false);
packConfig = new PackConfig();
packConfig.setDeltaCompress(false);
@@ -67,13 +69,7 @@
return maxObjectSizeLimitFormatted;
}
- public long getEffectiveMaxObjectSizeLimit(ProjectState p) {
- long global = getMaxObjectSizeLimit();
- long local = p.getMaxObjectSizeLimit();
- if (global > 0 && local > 0) {
- return Math.min(global, local);
- }
- // zero means "no limit", in this case the max is more limiting
- return Math.max(global, local);
+ public boolean getInheritProjectMaxObjectSizeLimit() {
+ return inheritProjectMaxObjectSizeLimit;
}
}
diff --git a/java/com/google/gerrit/server/git/WorkQueue.java b/java/com/google/gerrit/server/git/WorkQueue.java
index 98a1823..a7336f0 100644
--- a/java/com/google/gerrit/server/git/WorkQueue.java
+++ b/java/com/google/gerrit/server/git/WorkQueue.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.git;
+import static java.util.stream.Collectors.toList;
+
import com.google.common.base.CaseFormat;
import com.google.common.base.Supplier;
import com.google.common.flogger.FluentLogger;
@@ -24,6 +26,8 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.ScheduleConfig.Schedule;
+import com.google.gerrit.server.logging.LoggingContext;
+import com.google.gerrit.server.logging.LoggingContextAwareRunnable;
import com.google.gerrit.server.util.IdGenerator;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -42,6 +46,7 @@
import java.util.concurrent.Future;
import java.util.concurrent.RunnableScheduledFuture;
import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
@@ -274,6 +279,75 @@
}
@Override
+ public void execute(Runnable command) {
+ super.execute(LoggingContext.copy(command));
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ return super.submit(LoggingContext.copy(task));
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ return super.submit(LoggingContext.copy(task), result);
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ return super.submit(LoggingContext.copy(task));
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException {
+ return super.invokeAll(tasks.stream().map(LoggingContext::copy).collect(toList()));
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(
+ Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return super.invokeAll(
+ tasks.stream().map(LoggingContext::copy).collect(toList()), timeout, unit);
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException, ExecutionException {
+ return super.invokeAny(tasks.stream().map(LoggingContext::copy).collect(toList()));
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return super.invokeAny(
+ tasks.stream().map(LoggingContext::copy).collect(toList()), timeout, unit);
+ }
+
+ @Override
+ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+ return super.schedule(LoggingContext.copy(command), delay, unit);
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+ return super.schedule(LoggingContext.copy(callable), delay, unit);
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(
+ Runnable command, long initialDelay, long period, TimeUnit unit) {
+ return super.scheduleAtFixedRate(LoggingContext.copy(command), initialDelay, period, unit);
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(
+ Runnable command, long initialDelay, long delay, TimeUnit unit) {
+ return super.scheduleWithFixedDelay(LoggingContext.copy(command), initialDelay, delay, unit);
+ }
+
+ @Override
protected void terminated() {
super.terminated();
queues.remove(this);
@@ -367,6 +441,10 @@
Task<V> task;
+ if (runnable instanceof LoggingContextAwareRunnable) {
+ runnable = ((LoggingContextAwareRunnable) runnable).unwrap();
+ }
+
if (runnable instanceof ProjectRunnable) {
task = new ProjectTask<>((ProjectRunnable) runnable, r, this, id);
} else {
diff --git a/java/com/google/gerrit/server/git/meta/TabFile.java b/java/com/google/gerrit/server/git/meta/TabFile.java
index ef25cd8..4c0378a 100644
--- a/java/com/google/gerrit/server/git/meta/TabFile.java
+++ b/java/com/google/gerrit/server/git/meta/TabFile.java
@@ -14,13 +14,15 @@
package com.google.gerrit.server.git.meta;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.server.git.ValidationError;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -122,10 +124,8 @@
return buf.toString();
}
- protected static <T extends Comparable<? super T>> List<T> sort(Collection<T> m) {
- ArrayList<T> r = new ArrayList<>(m);
- Collections.sort(r);
- return r;
+ protected static <T extends Comparable<? super T>> ImmutableList<T> sort(Collection<T> m) {
+ return m.stream().sorted().collect(toImmutableList());
}
protected static String pad(int len, String src) {
diff --git a/java/com/google/gerrit/server/git/meta/VersionedMetaData.java b/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
index da1f1ac..8b14177 100644
--- a/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
+++ b/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
@@ -17,7 +17,9 @@
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.MoreObjects;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.LockFailureException;
import java.io.BufferedReader;
import java.io.IOException;
@@ -62,6 +64,8 @@
* read from the repository, or format an update that can later be written back to the repository.
*/
public abstract class VersionedMetaData {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
/**
* Path information that does not hold references to any repository data structures, allowing the
* application to retain this object for long periods of time.
@@ -81,6 +85,7 @@
/** The revision at which the data was loaded. Is null for data yet to be created. */
@Nullable protected RevCommit revision;
+ protected Project.NameKey projectName;
protected RevWalk rw;
protected ObjectReader reader;
protected ObjectInserter inserter;
@@ -114,13 +119,15 @@
* <p>The repository is not held after the call completes, allowing the application to retain this
* object for long periods of time.
*
+ * @param projectName the name of the project
* @param db repository to access.
* @throws IOException
* @throws ConfigInvalidException
*/
- public void load(Repository db) throws IOException, ConfigInvalidException {
+ public void load(Project.NameKey projectName, Repository db)
+ throws IOException, ConfigInvalidException {
Ref ref = db.getRefDatabase().exactRef(getRefName());
- load(db, ref != null ? ref.getObjectId() : null);
+ load(projectName, db, ref != null ? ref.getObjectId() : null);
}
/**
@@ -133,15 +140,16 @@
* <p>The repository is not held after the call completes, allowing the application to retain this
* object for long periods of time.
*
+ * @param projectName the name of the project
* @param db repository to access.
* @param id revision to load.
* @throws IOException
* @throws ConfigInvalidException
*/
- public void load(Repository db, @Nullable ObjectId id)
+ public void load(Project.NameKey projectName, Repository db, @Nullable ObjectId id)
throws IOException, ConfigInvalidException {
try (RevWalk walk = new RevWalk(db)) {
- load(walk, id);
+ load(projectName, walk, id);
}
}
@@ -156,12 +164,15 @@
* instance does not hold a reference to the walk or the repository after the call completes,
* allowing the application to retain this object for long periods of time.
*
+ * @param projectName the name of the project
* @param walk open walk to access to access.
* @param id revision to load.
* @throws IOException
* @throws ConfigInvalidException
*/
- public void load(RevWalk walk, ObjectId id) throws IOException, ConfigInvalidException {
+ public void load(Project.NameKey projectName, RevWalk walk, ObjectId id)
+ throws IOException, ConfigInvalidException {
+ this.projectName = projectName;
this.rw = walk;
this.reader = walk.getObjectReader();
try {
@@ -174,11 +185,11 @@
}
public void load(MetaDataUpdate update) throws IOException, ConfigInvalidException {
- load(update.getRepository());
+ load(update.getProjectName(), update.getRepository());
}
public void load(MetaDataUpdate update, ObjectId id) throws IOException, ConfigInvalidException {
- load(update.getRepository(), id);
+ load(update.getProjectName(), update.getRepository(), id);
}
/**
@@ -481,6 +492,9 @@
return new byte[] {};
}
+ logger.atFine().log(
+ "Read file '%s' from ref '%s' of project '%s' from revision '%s'",
+ fileName, getRefName(), projectName, revision.name());
try (TreeWalk tw = TreeWalk.forPath(reader, fileName, revision.getTree())) {
if (tw != null) {
ObjectLoader obj = reader.open(tw.getObjectId(0), Constants.OBJ_BLOB);
@@ -553,6 +567,8 @@
}
protected void saveFile(String fileName, byte[] raw) throws IOException {
+ logger.atFine().log(
+ "Save file '%s' in ref '%s' of project '%s'", fileName, getRefName(), projectName);
DirCacheEditor editor = newTree.editor();
if (raw != null && 0 < raw.length) {
final ObjectId blobId = inserter.insert(Constants.OBJ_BLOB, raw);
diff --git a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
index f0cc558..eb62d54 100644
--- a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
@@ -94,6 +94,7 @@
// Don't expose the binding for ReceiveCommits.Factory. All callers should
// be using AsyncReceiveCommits.Factory instead.
install(new FactoryModuleBuilder().build(ReceiveCommits.Factory.class));
+ install(new FactoryModuleBuilder().build(BranchCommitValidator.Factory.class));
}
@Provides
@@ -224,7 +225,7 @@
receivePack.setAllowNonFastForwards(true);
receivePack.setRefLogIdent(user.newRefLogIdent());
receivePack.setTimeout(transferConfig.getTimeout());
- receivePack.setMaxObjectSizeLimit(transferConfig.getEffectiveMaxObjectSizeLimit(projectState));
+ receivePack.setMaxObjectSizeLimit(projectState.getEffectiveMaxObjectSizeLimit().value);
receivePack.setCheckReceivedObjects(projectState.getConfig().getCheckReceivedObjects());
receivePack.setRefFilter(new ReceiveRefFilter());
receivePack.setAllowPushOptions(true);
diff --git a/java/com/google/gerrit/server/git/receive/BUILD b/java/com/google/gerrit/server/git/receive/BUILD
index fddb9d6..f1c604b 100644
--- a/java/com/google/gerrit/server/git/receive/BUILD
+++ b/java/com/google/gerrit/server/git/receive/BUILD
@@ -8,6 +8,7 @@
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/util/cli",
"//lib:args4j",
"//lib:guava",
diff --git a/java/com/google/gerrit/server/git/receive/BranchCommitValidator.java b/java/com/google/gerrit/server/git/receive/BranchCommitValidator.java
new file mode 100644
index 0000000..24b6ab1
--- /dev/null
+++ b/java/com/google/gerrit/server/git/receive/BranchCommitValidator.java
@@ -0,0 +1,132 @@
+// Copyright (C) 2018 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.receive;
+
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.git.validators.CommitValidationException;
+import com.google.gerrit.server.git.validators.CommitValidationMessage;
+import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.git.validators.ValidationMessage;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.ssh.SshInfo;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.io.IOException;
+import java.util.List;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/** Validates single commits for a branch. */
+public class BranchCommitValidator {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final CommitValidators.Factory commitValidatorsFactory;
+ private final IdentifiedUser user;
+ private final PermissionBackend.ForProject permissions;
+ private final Project project;
+ private final Branch.NameKey branch;
+ private final SshInfo sshInfo;
+
+ interface Factory {
+ BranchCommitValidator create(
+ ProjectState projectState, Branch.NameKey branch, IdentifiedUser user);
+ }
+
+ @Inject
+ BranchCommitValidator(
+ CommitValidators.Factory commitValidatorsFactory,
+ PermissionBackend permissionBackend,
+ SshInfo sshInfo,
+ @Assisted ProjectState projectState,
+ @Assisted Branch.NameKey branch,
+ @Assisted IdentifiedUser user) {
+ this.sshInfo = sshInfo;
+ this.user = user;
+ this.branch = branch;
+ this.commitValidatorsFactory = commitValidatorsFactory;
+ project = projectState.getProject();
+ permissions = permissionBackend.user(user).project(project.getNameKey());
+ }
+
+ /**
+ * Validates a single commit. If the commit does not validate, the command is rejected.
+ *
+ * @param objectReader the object reader to use.
+ * @param cmd the ReceiveCommand executing the push.
+ * @param commit the commit being validated.
+ * @param isMerged whether this is a merge commit created by magicBranch --merge option
+ * @param change the change for which this is a new patchset.
+ */
+ public boolean validCommit(
+ ObjectReader objectReader,
+ ReceiveCommand cmd,
+ RevCommit commit,
+ boolean isMerged,
+ List<ValidationMessage> messages,
+ NoteMap rejectCommits,
+ @Nullable Change change)
+ throws IOException {
+ try (CommitReceivedEvent receiveEvent =
+ new CommitReceivedEvent(cmd, project, branch.get(), objectReader, commit, user)) {
+ CommitValidators validators;
+ if (isMerged) {
+ validators =
+ commitValidatorsFactory.forMergedCommits(permissions, branch, user.asIdentifiedUser());
+ } else {
+ validators =
+ commitValidatorsFactory.forReceiveCommits(
+ permissions,
+ branch,
+ user.asIdentifiedUser(),
+ sshInfo,
+ rejectCommits,
+ receiveEvent.revWalk,
+ change);
+ }
+
+ for (CommitValidationMessage m : validators.validate(receiveEvent)) {
+ messages.add(
+ new CommitValidationMessage(messageForCommit(commit, m.getMessage()), m.isError()));
+ }
+ } catch (CommitValidationException e) {
+ logger.atFine().log("Commit validation failed on %s", commit.name());
+ for (CommitValidationMessage m : e.getMessages()) {
+ // The non-error messages may contain background explanation for the
+ // fatal error, so have to preserve all messages.
+ messages.add(
+ new CommitValidationMessage(messageForCommit(commit, m.getMessage()), m.isError()));
+ }
+ cmd.setResult(REJECTED_OTHER_REASON, messageForCommit(commit, e.getMessage()));
+ return false;
+ }
+ return true;
+ }
+
+ private String messageForCommit(RevCommit c, String msg) {
+ return String.format("commit %s: %s", c.abbreviate(RevId.ABBREV_LEN).name(), msg);
+ }
+}
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 26795cb..4b475f9 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.git.receive;
import static com.google.common.base.MoreObjects.firstNonNull;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.common.FooterConstants.CHANGE_ID;
@@ -38,8 +39,8 @@
import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_MISSING_OBJECT;
import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
-import com.google.auto.value.AutoValue;
import com.google.common.base.Function;
+import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
@@ -57,11 +58,11 @@
import com.google.common.collect.Sets;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
-import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.extensions.api.changes.HashtagsInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.RecipientType;
@@ -81,7 +82,6 @@
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
@@ -93,20 +93,15 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.Sequences;
-import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountResolver;
-import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.SetHashtagsOp;
import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.ProjectConfigEntry;
import com.google.gerrit.server.edit.ChangeEdit;
import com.google.gerrit.server.edit.ChangeEditUtil;
-import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.git.BanCommit;
import com.google.gerrit.server.git.ChangeReportFormatter;
import com.google.gerrit.server.git.GroupCollector;
@@ -116,13 +111,13 @@
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.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidationMessage;
-import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.git.validators.RefOperationValidationException;
import com.google.gerrit.server.git.validators.RefOperationValidators;
import com.google.gerrit.server.git.validators.ValidationMessage;
import com.google.gerrit.server.index.change.ChangeIndexer;
+import com.google.gerrit.server.logging.RequestId;
+import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.mail.MailUtil.MailRecipients;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.NotesMigration;
@@ -132,6 +127,7 @@
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.PermissionDeniedException;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.CreateRefControl;
@@ -142,7 +138,6 @@
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
-import com.google.gerrit.server.ssh.SshInfo;
import com.google.gerrit.server.submit.MergeOp;
import com.google.gerrit.server.submit.MergeOpRepoManager;
import com.google.gerrit.server.submit.SubmoduleException;
@@ -159,7 +154,6 @@
import com.google.gerrit.server.update.UpdateException;
import com.google.gerrit.server.util.LabelVote;
import com.google.gerrit.server.util.MagicBranch;
-import com.google.gerrit.server.util.RequestId;
import com.google.gerrit.server.util.RequestScopePropagator;
import com.google.gerrit.util.cli.CmdLineParser;
import com.google.gwtorm.server.OrmException;
@@ -216,37 +210,19 @@
*
* <p>Conceptually, most use of Gerrit is a push of some commits to refs/for/BRANCH. However, the
* receive-pack protocol that this is based on allows multiple ref updates to be processed at once.
+ * So we have to be prepared to also handle normal pushes (refs/heads/BRANCH), and legacy pushes
+ * (refs/changes/CHANGE). It is hard to split this class up further, because normal pushes can also
+ * result in updates to reviews, through the autoclose mechanism.
*/
class ReceiveCommits {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private enum ReceiveError {
- CONFIG_UPDATE(
- "You are not allowed to perform this operation.\n"
- + "Configuration changes can only be pushed by project owners\n"
- + "who also have 'Push' rights on "
- + RefNames.REFS_CONFIG),
- UPDATE(
- "You are not allowed to perform this operation.\n"
- + "To push into this reference you need 'Push' rights."),
- DELETE(
- "You need 'Delete Reference' rights or 'Push' rights with the \n"
- + "'Force Push' flag set to delete references."),
- DELETE_CHANGES("Cannot delete from '" + REFS_CHANGES + "'"),
- CODE_REVIEW(
- "You need 'Push' rights to upload code review requests.\n"
- + "Verify that you are pushing to the right branch.");
-
- private final String value;
-
- ReceiveError(String value) {
- this.value = value;
- }
-
- String get() {
- return value;
- }
- }
+ private static final String CODE_REVIEW_ERROR =
+ "You need 'Push' rights to upload code review requests.\n"
+ + "Verify that you are pushing to the right branch.";
+ private static final String CANNOT_DELETE_CHANGES = "Cannot delete from '" + REFS_CHANGES + "'";
+ private static final String CANNOT_DELETE_CONFIG =
+ "Cannot delete project configuration from '" + RefNames.REFS_CONFIG + "'";
interface Factory {
ReceiveCommits create(
@@ -313,7 +289,6 @@
// Injected fields.
private final AccountResolver accountResolver;
- private final Provider<AccountsUpdate> accountsUpdateProvider;
private final AllProjectsName allProjectsName;
private final BatchUpdate.Factory batchUpdateFactory;
private final ChangeEditUtil editUtil;
@@ -322,7 +297,7 @@
private final ChangeNotes.Factory notesFactory;
private final ChangeReportFormatter changeFormatter;
private final CmdLineParser.Factory optionParserFactory;
- private final CommitValidators.Factory commitValidatorsFactory;
+ private final BranchCommitValidator.Factory commitValidatorFactory;
private final CreateGroupPermissionSyncer createGroupPermissionSyncer;
private final CreateRefControl createRefControl;
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
@@ -344,10 +319,8 @@
private final ReviewDb db;
private final Sequences seq;
private final SetHashtagsOp.Factory hashtagsFactory;
- private final SshInfo sshInfo;
private final SubmoduleOp.Factory subOpFactory;
private final TagCache tagCache;
- private final String canonicalWebUrl;
// Assisted injected fields.
private final AllRefsWatcher allRefsWatcher;
@@ -357,39 +330,23 @@
private final ReceivePack receivePack;
// Immutable fields derived from constructor arguments.
+ private final boolean allowProjectOwnersToChangeParent;
private final boolean allowPushToRefsChanges;
private final LabelTypes labelTypes;
private final NoteMap rejectCommits;
private final PermissionBackend.ForProject permissions;
private final Project project;
private final Repository repo;
- private final RequestId receiveId;
// Collections populated during processing.
private final List<UpdateGroupsRequest> updateGroups;
private final List<ValidationMessage> messages;
- private final ListMultimap<ReceiveError, String> errors;
+ /** Multimap of error text to refnames that produced that error. */
+ private final ListMultimap<String, String> errors;
+
private final ListMultimap<String, String> pushOptions;
private final Map<Change.Id, ReplaceRequest> replaceByChange;
- @AutoValue
- protected abstract static class ValidCommitKey {
- abstract ObjectId getObjectId();
-
- abstract Branch.NameKey getBranch();
- }
-
- private final Set<ValidCommitKey> validCommits;
-
- /**
- * Actual commands to be executed, as opposed to the mix of actual and magic commands that were
- * provided over the wire.
- *
- * <p>Excludes commands executed implicitly as part of other {@link BatchUpdateOp}s, such as
- * creating patch set refs.
- */
- private final List<ReceiveCommand> actualCommands;
-
// Collections lazily populated during processing.
private ListMultimap<Change.Id, Ref> refsByChange;
private ListMultimap<ObjectId, Ref> refsById;
@@ -397,21 +354,15 @@
// Other settings populated during processing.
private MagicBranchInput magicBranch;
private boolean newChangeForAllNotInTarget;
- private String setFullNameTo;
private boolean setChangeAsPrivate;
private Optional<NoteDbPushOption> noteDbPushOption;
+ private Optional<String> tracePushOption;
- // Handles for outputting back over the wire to the end user.
- private Task newProgress;
- private Task replaceProgress;
- private Task closeProgress;
- private Task commandProgress;
private MessageSender messageSender;
@Inject
ReceiveCommits(
AccountResolver accountResolver,
- @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider,
AllProjectsName allProjectsName,
BatchUpdate.Factory batchUpdateFactory,
@GerritServerConfig Config cfg,
@@ -421,7 +372,7 @@
ChangeNotes.Factory notesFactory,
DynamicItem<ChangeReportFormatter> changeFormatterProvider,
CmdLineParser.Factory optionParserFactory,
- CommitValidators.Factory commitValidatorsFactory,
+ BranchCommitValidator.Factory commitValidatorFactory,
CreateGroupPermissionSyncer createGroupPermissionSyncer,
CreateRefControl createRefControl,
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
@@ -443,10 +394,8 @@
ReviewDb db,
Sequences seq,
SetHashtagsOp.Factory hashtagsFactory,
- SshInfo sshInfo,
SubmoduleOp.Factory subOpFactory,
TagCache tagCache,
- @CanonicalWebUrl @Nullable String canonicalWebUrl,
@Assisted ProjectState projectState,
@Assisted IdentifiedUser user,
@Assisted ReceivePack rp,
@@ -456,12 +405,11 @@
throws IOException {
// Injected fields.
this.accountResolver = accountResolver;
- this.accountsUpdateProvider = accountsUpdateProvider;
this.allProjectsName = allProjectsName;
this.batchUpdateFactory = batchUpdateFactory;
this.changeFormatter = changeFormatterProvider.get();
this.changeInserterFactory = changeInserterFactory;
- this.commitValidatorsFactory = commitValidatorsFactory;
+ this.commitValidatorFactory = commitValidatorFactory;
this.createRefControl = createRefControl;
this.createGroupPermissionSyncer = createGroupPermissionSyncer;
this.db = db;
@@ -487,7 +435,6 @@
this.retryHelper = retryHelper;
this.requestScopePropagator = requestScopePropagator;
this.seq = seq;
- this.sshInfo = sshInfo;
this.subOpFactory = subOpFactory;
this.tagCache = tagCache;
@@ -504,18 +451,17 @@
project = projectState.getProject();
labelTypes = projectState.getLabelTypes();
permissions = permissionBackend.user(user).project(project.getNameKey());
- receiveId = RequestId.forProject(project.getNameKey());
rejectCommits = BanCommit.loadRejectCommitsMap(rp.getRepository(), rp.getRevWalk());
- this.canonicalWebUrl = canonicalWebUrl;
// Collections populated during processing.
- actualCommands = new ArrayList<>();
errors = MultimapBuilder.linkedHashKeys().arrayListValues().build();
messages = new ArrayList<>();
pushOptions = LinkedListMultimap.create();
replaceByChange = new LinkedHashMap<>();
updateGroups = new ArrayList<>();
- validCommits = new HashSet<>();
+
+ this.allowProjectOwnersToChangeParent =
+ cfg.getBoolean("receive", "allowProjectOwnersToChangeParent", false);
// Other settings populated during processing.
newChangeForAllNotInTarget =
@@ -558,53 +504,176 @@
}
void processCommands(Collection<ReceiveCommand> commands, MultiProgressMonitor progress) {
- newProgress = progress.beginSubTask("new", UNKNOWN);
- replaceProgress = progress.beginSubTask("updated", UNKNOWN);
- closeProgress = progress.beginSubTask("closed", UNKNOWN);
- commandProgress = progress.beginSubTask("refs", UNKNOWN);
+ Task commandProgress = progress.beginSubTask("refs", UNKNOWN);
+ commands = commands.stream().map(c -> wrapReceiveCommand(c, commandProgress)).collect(toList());
+ processCommandsUnsafe(commands, progress);
+ for (ReceiveCommand cmd : commands) {
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ cmd.setResult(REJECTED_OTHER_REASON, "internal server error");
+ }
+ }
- try {
- parsePushOptions();
- logDebug("Parsing %d commands", commands.size());
- for (ReceiveCommand cmd : commands) {
- if (!projectState.getProject().getState().permitsWrite()) {
+ // This sends error messages before the 'done' string of the progress monitor is sent.
+ // Currently, the test framework relies on this ordering to understand if pushes completed
+ // successfully.
+ sendErrorMessages();
+
+ commandProgress.end();
+ progress.end();
+ }
+
+ // Process as many commands as possible, but may leave some commands in state NOT_ATTEMPTED.
+ private void processCommandsUnsafe(
+ Collection<ReceiveCommand> commands, MultiProgressMonitor progress) {
+ parsePushOptions();
+ try (TraceContext traceContext =
+ TraceContext.newTrace(
+ tracePushOption.isPresent(),
+ tracePushOption.orElse(null),
+ (tagName, traceId) -> addMessage(tagName + ": " + traceId))) {
+ traceContext.addTag(RequestId.Type.RECEIVE_ID, new RequestId(project.getNameKey().get()));
+
+ // Log the push options here, rather than in parsePushOptions(), so that they are included
+ // into the trace if tracing is enabled.
+ logger.atFine().log("push options: %s", receivePack.getPushOptions());
+
+ if (!projectState.getProject().getState().permitsWrite()) {
+ for (ReceiveCommand cmd : commands) {
reject(cmd, "prohibited by Gerrit: project state does not permit write");
- break;
}
- parseCommand(cmd);
+ return;
}
- } catch (PermissionBackendException | NoSuchProjectException | IOException err) {
- for (ReceiveCommand cmd : actualCommands) {
- if (cmd.getResult() == NOT_ATTEMPTED) {
- cmd.setResult(REJECTED_OTHER_REASON, "internal server error");
+
+ logger.atFine().log("Parsing %d commands", commands.size());
+
+ List<ReceiveCommand> magicCommands = new ArrayList<>();
+ List<ReceiveCommand> directPatchSetPushCommands = new ArrayList<>();
+ List<ReceiveCommand> regularCommands = new ArrayList<>();
+
+ for (ReceiveCommand cmd : commands) {
+ if (MagicBranch.isMagicBranch(cmd.getRefName())) {
+ magicCommands.add(cmd);
+ } else if (isDirectChangesPush(cmd.getRefName())) {
+ directPatchSetPushCommands.add(cmd);
+ } else {
+ regularCommands.add(cmd);
}
}
- logError(String.format("Failed to process refs in %s", project.getName()), err);
- }
- List<CreateRequest> newChanges = Collections.emptyList();
- if (magicBranch != null && magicBranch.cmd.getResult() == NOT_ATTEMPTED) {
- newChanges = selectNewAndReplacedChangesFromMagicBranch();
- }
- preparePatchSetsForReplace(newChanges);
- insertChangesAndPatchSets(newChanges);
- newProgress.end();
- replaceProgress.end();
+ int commandTypes =
+ (magicCommands.isEmpty() ? 0 : 1)
+ + (directPatchSetPushCommands.isEmpty() ? 0 : 1)
+ + (regularCommands.isEmpty() ? 0 : 1);
+ if (commandTypes > 1) {
+ for (ReceiveCommand cmd : commands) {
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ cmd.setResult(REJECTED_OTHER_REASON, "cannot combine normal pushes and magic pushes");
+ }
+ }
+ return;
+ }
+
+ try {
+ if (!regularCommands.isEmpty()) {
+ handleRegularCommands(regularCommands, progress);
+ return;
+ }
+
+ for (ReceiveCommand cmd : directPatchSetPushCommands) {
+ parseDirectChangesPush(cmd);
+ }
+
+ boolean first = true;
+ for (ReceiveCommand cmd : magicCommands) {
+ if (first) {
+ parseMagicBranch(cmd);
+ first = false;
+ } else {
+ reject(cmd, "duplicate request");
+ }
+ }
+ } catch (PermissionBackendException | NoSuchProjectException | IOException err) {
+ logger.atSevere().withCause(err).log("Failed to process refs in %s", project.getName());
+ return;
+ }
+
+ Task newProgress = progress.beginSubTask("new", UNKNOWN);
+ Task replaceProgress = progress.beginSubTask("updated", UNKNOWN);
+
+ List<CreateRequest> newChanges = Collections.emptyList();
+ if (magicBranch != null && magicBranch.cmd.getResult() == NOT_ATTEMPTED) {
+ newChanges = selectNewAndReplacedChangesFromMagicBranch(newProgress);
+ }
+
+ // Commit validation has already happened, so any changes without Change-Id are for the
+ // deprecated feature.
+ warnAboutMissingChangeId(newChanges);
+ preparePatchSetsForReplace(newChanges);
+ insertChangesAndPatchSets(newChanges, replaceProgress);
+ newProgress.end();
+ replaceProgress.end();
+ queueSuccessMessages(newChanges);
+ refsPublishDeprecationWarning();
+ }
+ }
+
+ private void refsPublishDeprecationWarning() {
+ // TODO(xchangcheng): remove after migrating tools which are using this magic branch.
+ if (magicBranch != null && magicBranch.publish) {
+ addMessage("Pushing to refs/publish/* is deprecated, use refs/for/* instead.");
+ }
+ }
+
+ private void sendErrorMessages() {
if (!errors.isEmpty()) {
- logDebug("Handling error conditions: %s", errors.keySet());
- for (ReceiveError error : errors.keySet()) {
- receivePack.sendMessage(buildError(error, errors.get(error)));
+ logger.atFine().log("Handling error conditions: %s", errors.keySet());
+ for (String error : errors.keySet()) {
+ receivePack.sendMessage("error: " + buildError(error, errors.get(error)));
}
receivePack.sendMessage(String.format("User: %s", user.getLoggableName()));
receivePack.sendMessage(COMMAND_REJECTION_MESSAGE_FOOTER);
}
+ }
+
+ private void handleRegularCommands(List<ReceiveCommand> cmds, MultiProgressMonitor progress)
+ throws PermissionBackendException, IOException, NoSuchProjectException {
+ for (ReceiveCommand cmd : cmds) {
+ parseRegularCommand(cmd);
+ }
+
+ try (BatchUpdate bu =
+ batchUpdateFactory.create(
+ db, project.getNameKey(), user.materializedCopy(), TimeUtil.nowTs());
+ ObjectInserter ins = repo.newObjectInserter();
+ ObjectReader reader = ins.newReader();
+ RevWalk rw = new RevWalk(reader)) {
+ bu.setRepository(repo, rw, ins).updateChangesInParallel();
+ bu.setRefLogMessage("push");
+
+ int added = 0;
+ for (ReceiveCommand cmd : cmds) {
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ bu.addRepoOnlyOp(new UpdateOneRefOp(cmd));
+ added++;
+ }
+ }
+ logger.atFine().log("Added %d additional ref updates", added);
+ bu.execute();
+ } catch (UpdateException | RestApiException e) {
+ for (ReceiveCommand cmd : cmds) {
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ cmd.setResult(REJECTED_OTHER_REASON, "internal server error");
+ }
+ }
+ logger.atFine().withCause(e).log("update failed:");
+ }
Set<Branch.NameKey> branches = new HashSet<>();
- for (ReceiveCommand c : actualCommands) {
+ for (ReceiveCommand c : cmds) {
// Most post-update steps should happen in UpdateOneRefOp#postUpdate. The only steps that
- // should happen in this loop are things that can't happen within one BatchUpdate because they
- // involve kicking off an additional BatchUpdate.
+ // should happen in this loop are things that can't happen within one BatchUpdate because
+ // they involve kicking off an additional BatchUpdate.
if (c.getResult() != OK) {
continue;
}
@@ -613,7 +682,9 @@
case CREATE:
case UPDATE:
case UPDATE_NONFASTFORWARD:
- autoCloseChanges(c);
+ Task closeProgress = progress.beginSubTask("closed", UNKNOWN);
+ autoCloseChanges(c, closeProgress);
+ closeProgress.end();
branches.add(new Branch.NameKey(project.getNameKey(), c.getRefName()));
break;
@@ -626,26 +697,34 @@
// Update superproject gitlinks if required.
if (!branches.isEmpty()) {
try (MergeOpRepoManager orm = ormProvider.get()) {
- orm.setContext(db, TimeUtil.nowTs(), user, receiveId);
+ orm.setContext(db, TimeUtil.nowTs(), user);
SubmoduleOp op = subOpFactory.create(branches, orm);
op.updateSuperProjects();
} catch (SubmoduleException e) {
- logError("Can't update the superprojects", e);
+ logger.atSevere().withCause(e).log("Can't update the superprojects");
}
}
-
- // Update account info with details discovered during commit walking.
- updateAccountInfo();
-
- closeProgress.end();
- commandProgress.end();
- progress.end();
- reportMessages(newChanges);
}
- private void reportMessages(List<CreateRequest> newChanges) {
+ /** Appends messages for successful change creation/updates. */
+ private void queueSuccessMessages(List<CreateRequest> newChanges) {
List<CreateRequest> created =
newChanges.stream().filter(r -> r.change != null).collect(toList());
+ List<ReplaceRequest> updated =
+ replaceByChange
+ .values()
+ .stream()
+ .filter(r -> r.inputCommand.getResult() == OK)
+ .sorted(comparingInt(r -> r.notes.getChangeId().get()))
+ .collect(toList());
+
+ if (created.isEmpty() && updated.isEmpty()) {
+ return;
+ }
+
+ addMessage("");
+ addMessage("SUCCESS");
+
if (!created.isEmpty()) {
addMessage("");
addMessage("New Changes:");
@@ -654,16 +733,8 @@
changeFormatter.newChange(
ChangeReportFormatter.Input.builder().setChange(c.change).build()));
}
- addMessage("");
}
- List<ReplaceRequest> updated =
- replaceByChange
- .values()
- .stream()
- .filter(r -> r.inputCommand.getResult() == OK)
- .sorted(comparingInt(r -> r.notes.getChangeId().get()))
- .collect(toList());
if (!updated.isEmpty()) {
addMessage("");
addMessage("Updated Changes:");
@@ -689,7 +760,7 @@
subject = receivePack.getRevWalk().parseCommit(u.newCommitId).getShortMessage();
} catch (IOException e) {
// Log and fall back to original change subject
- logWarn("failed to get subject for edit patch set", e);
+ logger.atWarning().withCause(e).log("failed to get subject for edit patch set");
subject = u.notes.getChange().getSubject();
}
} else {
@@ -715,22 +786,16 @@
}
addMessage("");
}
-
- // TODO(xchangcheng): remove after migrating tools which are using this magic branch.
- if (magicBranch != null && magicBranch.publish) {
- addMessage("Pushing to refs/publish/* is deprecated, use refs/for/* instead.");
- }
}
- private void insertChangesAndPatchSets(List<CreateRequest> newChanges) {
+ private void insertChangesAndPatchSets(List<CreateRequest> newChanges, Task replaceProgress) {
ReceiveCommand magicBranchCmd = magicBranch != null ? magicBranch.cmd : null;
if (magicBranchCmd != null && magicBranchCmd.getResult() != NOT_ATTEMPTED) {
- logWarn(
- String.format(
- "Skipping change updates on %s because ref update failed: %s %s",
- project.getName(),
- magicBranchCmd.getResult(),
- Strings.nullToEmpty(magicBranchCmd.getMessage())));
+ logger.atWarning().log(
+ "Skipping change updates on %s because ref update failed: %s %s",
+ project.getName(),
+ magicBranchCmd.getResult(),
+ Strings.nullToEmpty(magicBranchCmd.getMessage()));
return;
}
@@ -741,26 +806,22 @@
ObjectReader reader = ins.newReader();
RevWalk rw = new RevWalk(reader)) {
bu.setRepository(repo, rw, ins).updateChangesInParallel();
- bu.setRequestId(receiveId);
bu.setRefLogMessage("push");
- logDebug("Adding %d replace requests", newChanges.size());
+ logger.atFine().log("Adding %d replace requests", newChanges.size());
for (ReplaceRequest replace : replaceByChange.values()) {
replace.addOps(bu, replaceProgress);
}
- logDebug("Adding %d create requests", newChanges.size());
+ logger.atFine().log("Adding %d create requests", newChanges.size());
for (CreateRequest create : newChanges) {
create.addOps(bu);
}
- logDebug("Adding %d group update requests", newChanges.size());
+ logger.atFine().log("Adding %d group update requests", newChanges.size());
updateGroups.forEach(r -> r.addOps(bu));
- logDebug("Adding %d additional ref updates", actualCommands.size());
- actualCommands.forEach(c -> bu.addRepoOnlyOp(new UpdateOneRefOp(c)));
-
- logDebug("Executing batch");
+ logger.atFine().log("Executing batch");
try {
bu.execute();
} catch (UpdateException e) {
@@ -777,16 +838,17 @@
replace.inputCommand.setResult(OK);
}
} else {
- logDebug("Rejecting due to message from ReplaceOp");
+ logger.atFine().log("Rejecting due to message from ReplaceOp");
reject(replace.inputCommand, rejectMessage);
}
}
} catch (ResourceConflictException e) {
- addMessage(e.getMessage());
+ addError(e.getMessage());
reject(magicBranchCmd, "conflict");
} catch (RestApiException | IOException err) {
- logError("Can't insert change/patch set for " + project.getName(), err);
+ logger.atSevere().withCause(err).log(
+ "Can't insert change/patch set for %s", project.getName());
reject(magicBranchCmd, "internal server error: " + err.getMessage());
}
@@ -794,7 +856,7 @@
try {
submit(newChanges, replaceByChange.values());
} catch (ResourceConflictException e) {
- addMessage(e.getMessage());
+ addError(e.getMessage());
reject(magicBranchCmd, "conflict");
} catch (RestApiException
| OrmException
@@ -802,26 +864,21 @@
| IOException
| ConfigInvalidException
| PermissionBackendException e) {
- logError("Error submitting changes to " + project.getName(), e);
+ logger.atSevere().withCause(e).log("Error submitting changes to %s", project.getName());
reject(magicBranchCmd, "error during submit");
}
}
}
- private String buildError(ReceiveError error, List<String> branches) {
+ private String buildError(String error, List<String> branches) {
StringBuilder sb = new StringBuilder();
if (branches.size() == 1) {
- sb.append("Branch ").append(branches.get(0)).append(":\n");
- sb.append(error.get());
+ sb.append("branch ").append(branches.get(0)).append(":\n");
+ sb.append(error);
return sb.toString();
}
- sb.append("Branches");
- String delim = " ";
- for (String branch : branches) {
- sb.append(delim).append(branch);
- delim = ", ";
- }
- return sb.append(":\n").append(error.get()).toString();
+ sb.append("branches ").append(Joiner.on(", ").join(branches));
+ return sb.append(":\n").append(error).toString();
}
/** Parses push options specified as "git push -o OPTION" */
@@ -841,7 +898,7 @@
List<String> noteDbValues = pushOptions.get("notedb");
if (!noteDbValues.isEmpty()) {
// These semantics for duplicates/errors are somewhat arbitrary and may not match e.g. the
- // CommandLineParser behavior used by MagicBranchInput.
+ // CmdLineParser behavior used by MagicBranchInput.
String value = noteDbValues.get(noteDbValues.size() - 1);
noteDbPushOption = NoteDbPushOption.parse(value);
if (!noteDbPushOption.isPresent()) {
@@ -850,13 +907,70 @@
} else {
noteDbPushOption = Optional.of(NoteDbPushOption.DISALLOW);
}
+
+ List<String> traceValues = pushOptions.get("trace");
+ if (!traceValues.isEmpty()) {
+ String value = traceValues.get(traceValues.size() - 1);
+ tracePushOption = Optional.of(value);
+ } else {
+ tracePushOption = Optional.empty();
+ }
}
- private void parseCommand(ReceiveCommand cmd)
+ private static boolean isDirectChangesPush(String refname) {
+ Matcher m = NEW_PATCHSET_PATTERN.matcher(refname);
+ return m.matches();
+ }
+
+ private void parseDirectChangesPush(ReceiveCommand cmd) {
+ Matcher m = NEW_PATCHSET_PATTERN.matcher(cmd.getRefName());
+ checkArgument(m.matches());
+
+ if (allowPushToRefsChanges) {
+ // The referenced change must exist and must still be open.
+ Change.Id changeId = Change.Id.parse(m.group(1));
+ parseReplaceCommand(cmd, changeId);
+ messages.add(new ValidationMessage("warning: pushes to refs/changes are deprecated", false));
+ } else {
+ reject(cmd, "upload to refs/changes not allowed");
+ }
+ }
+
+ // Wrap ReceiveCommand so the progress counter works automatically.
+ private ReceiveCommand wrapReceiveCommand(ReceiveCommand cmd, Task progress) {
+ String refname = cmd.getRefName();
+
+ if (projectState.isAllUsers() && RefNames.REFS_USERS_SELF.equals(cmd.getRefName())) {
+ refname = RefNames.refsUsers(user.getAccountId());
+ logger.atFine().log("Swapping out command for %s to %s", RefNames.REFS_USERS_SELF, refname);
+ }
+
+ // We must also update the original, because callers may inspect it afterwards to decide if
+ // the command went through or not.
+ return new ReceiveCommand(cmd.getOldId(), cmd.getNewId(), refname, cmd.getType()) {
+ @Override
+ public void setResult(Result s, String m) {
+ if (getResult() == NOT_ATTEMPTED) { // Only report the progress update once.
+ progress.update(1);
+ }
+ // Counter intuitively, we don't check that results == NOT_ATTEMPTED here.
+ // This is so submit-on-push can still reject the update if the change is created
+ // successfully
+ // (status OK) but the submit failed (merge failed: REJECTED_OTHER_REASON).
+ super.setResult(s, m);
+ cmd.setResult(s, m);
+ }
+ };
+ }
+
+ /*
+ * Interpret a normal push.
+ */
+ private void parseRegularCommand(ReceiveCommand cmd)
throws PermissionBackendException, NoSuchProjectException, IOException {
if (cmd.getResult() != NOT_ATTEMPTED) {
// Already rejected by the core receive process.
- logDebug("Already processed by core: %s %s", cmd.getResult(), cmd);
+ logger.atFine().log("Already processed by core: %s %s", cmd.getResult(), cmd);
return;
}
@@ -864,45 +978,12 @@
reject(cmd, "not valid ref");
return;
}
-
- if (MagicBranch.isMagicBranch(cmd.getRefName())) {
- parseMagicBranch(cmd);
- return;
- }
-
- if (projectState.isAllUsers() && RefNames.REFS_USERS_SELF.equals(cmd.getRefName())) {
- String newName = RefNames.refsUsers(user.getAccountId());
- logDebug("Swapping out command for %s to %s", RefNames.REFS_USERS_SELF, newName);
- final ReceiveCommand orgCmd = cmd;
- cmd =
- new ReceiveCommand(cmd.getOldId(), cmd.getNewId(), newName, cmd.getType()) {
- @Override
- public void setResult(Result s, String m) {
- super.setResult(s, m);
- orgCmd.setResult(s, m);
- }
- };
- }
-
- Matcher m = NEW_PATCHSET_PATTERN.matcher(cmd.getRefName());
- if (m.matches()) {
- if (allowPushToRefsChanges) {
- // The referenced change must exist and must still be open.
- //
- Change.Id changeId = Change.Id.parse(m.group(1));
- parseReplaceCommand(cmd, changeId);
- } else {
- reject(cmd, "upload to refs/changes not allowed");
- }
- return;
- }
-
if (RefNames.isNoteDbMetaRef(cmd.getRefName())) {
// Reject pushes to NoteDb refs without a special option and permission. Note that this
// prohibition doesn't depend on NoteDb being enabled in any way, since all sites will
// migrate to NoteDb eventually, and we don't want garbage data waiting there when the
// migration finishes.
- logDebug(
+ logger.atFine().log(
"%s NoteDb ref %s with %s=%s",
cmd.getType(), cmd.getRefName(), NoteDbPushOption.OPTION_NAME, noteDbPushOption);
if (!Optional.of(NoteDbPushOption.ALLOW).equals(noteDbPushOption)) {
@@ -953,50 +1034,63 @@
}
if (isConfig(cmd)) {
- logDebug("Processing %s command", cmd.getRefName());
- try {
- permissions.check(ProjectPermission.WRITE_CONFIG);
- } catch (AuthException e) {
- reject(
- cmd,
- String.format(
- "must be either project owner or have %s permission",
- ProjectPermission.WRITE_CONFIG.describeForException()));
- return;
- }
+ validateConfigPush(cmd);
+ }
+ }
- switch (cmd.getType()) {
- case CREATE:
- case UPDATE:
- case UPDATE_NONFASTFORWARD:
- try {
- ProjectConfig cfg = new ProjectConfig(project.getNameKey());
- cfg.load(receivePack.getRevWalk(), cmd.getNewId());
- if (!cfg.getValidationErrors().isEmpty()) {
- addError("Invalid project configuration:");
- for (ValidationError err : cfg.getValidationErrors()) {
- addError(" " + err.getMessage());
- }
- reject(cmd, "invalid project configuration");
- logError(
- "User "
- + user.getLoggableName()
- + " tried to push invalid project configuration "
- + cmd.getNewId().name()
- + " for "
- + project.getName());
+ /** Validates a push to refs/meta/config, and reject the command if it fails. */
+ private void validateConfigPush(ReceiveCommand cmd) throws PermissionBackendException {
+ logger.atFine().log("Processing %s command", cmd.getRefName());
+ try {
+ permissions.check(ProjectPermission.WRITE_CONFIG);
+ } catch (AuthException e) {
+ reject(
+ cmd,
+ String.format(
+ "must be either project owner or have %s permission",
+ ProjectPermission.WRITE_CONFIG.describeForException()));
+ return;
+ }
+
+ switch (cmd.getType()) {
+ case CREATE:
+ case UPDATE:
+ case UPDATE_NONFASTFORWARD:
+ try {
+ ProjectConfig cfg = new ProjectConfig(project.getNameKey());
+ cfg.load(project.getNameKey(), receivePack.getRevWalk(), cmd.getNewId());
+ if (!cfg.getValidationErrors().isEmpty()) {
+ addError("Invalid project configuration:");
+ for (ValidationError err : cfg.getValidationErrors()) {
+ addError(" " + err.getMessage());
+ }
+ reject(cmd, "invalid project configuration");
+ logger.atSevere().log(
+ "User %s tried to push invalid project configuration %s for %s",
+ user.getLoggableName(), cmd.getNewId().name(), project.getName());
+ return;
+ }
+ Project.NameKey newParent = cfg.getProject().getParent(allProjectsName);
+ Project.NameKey oldParent = project.getParent(allProjectsName);
+ if (oldParent == null) {
+ // update of the 'All-Projects' project
+ if (newParent != null) {
+ reject(cmd, "invalid project configuration: root project cannot have parent");
return;
}
- Project.NameKey newParent = cfg.getProject().getParent(allProjectsName);
- Project.NameKey oldParent = project.getParent(allProjectsName);
- if (oldParent == null) {
- // update of the 'All-Projects' project
- if (newParent != null) {
- reject(cmd, "invalid project configuration: root project cannot have parent");
- return;
- }
- } else {
- if (!oldParent.equals(newParent)) {
+ } else {
+ if (!oldParent.equals(newParent)) {
+ if (allowProjectOwnersToChangeParent) {
+ try {
+ permissionBackend
+ .user(user)
+ .project(project.getNameKey())
+ .check(ProjectPermission.WRITE_CONFIG);
+ } catch (AuthException e) {
+ reject(cmd, "invalid project configuration: only project owners can set parent");
+ return;
+ }
+ } else {
try {
permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
} catch (AuthException e) {
@@ -1004,77 +1098,75 @@
return;
}
}
-
- if (projectCache.get(newParent) == null) {
- reject(cmd, "invalid project configuration: parent does not exist");
- return;
- }
}
- for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
- PluginConfig pluginCfg = cfg.getPluginConfig(e.getPluginName());
- ProjectConfigEntry configEntry = e.getProvider().get();
- String value = pluginCfg.getString(e.getExportName());
- String oldValue =
- projectState
- .getConfig()
- .getPluginConfig(e.getPluginName())
- .getString(e.getExportName());
- if (configEntry.getType() == ProjectConfigEntryType.ARRAY) {
- oldValue =
- Arrays.stream(
- projectState
- .getConfig()
- .getPluginConfig(e.getPluginName())
- .getStringList(e.getExportName()))
- .collect(joining("\n"));
- }
-
- if ((value == null ? oldValue != null : !value.equals(oldValue))
- && !configEntry.isEditable(projectState)) {
- reject(
- cmd,
- String.format(
- "invalid project configuration: Not allowed to set parameter"
- + " '%s' of plugin '%s' on project '%s'.",
- e.getExportName(), e.getPluginName(), project.getName()));
- continue;
- }
-
- if (ProjectConfigEntryType.LIST.equals(configEntry.getType())
- && value != null
- && !configEntry.getPermittedValues().contains(value)) {
- reject(
- cmd,
- String.format(
- "invalid project configuration: The value '%s' is "
- + "not permitted for parameter '%s' of plugin '%s'.",
- value, e.getExportName(), e.getPluginName()));
- }
+ if (projectCache.get(newParent) == null) {
+ reject(cmd, "invalid project configuration: parent does not exist");
+ return;
}
- } catch (Exception e) {
- reject(cmd, "invalid project configuration");
- logError(
- "User "
- + user.getLoggableName()
- + " tried to push invalid project configuration "
- + cmd.getNewId().name()
- + " for "
- + project.getName(),
- e);
- return;
}
- break;
-
- case DELETE:
- break;
-
- default:
- reject(
- cmd,
- "prohibited by Gerrit: don't know how to handle config update of type "
- + cmd.getType());
+ validatePluginConfig(cmd, cfg);
+ } catch (Exception e) {
+ reject(cmd, "invalid project configuration");
+ logger.atSevere().withCause(e).log(
+ "User %s tried to push invalid project configuration %s for %s",
+ user.getLoggableName(), cmd.getNewId().name(), project.getName());
return;
+ }
+ break;
+
+ case DELETE:
+ break;
+
+ default:
+ reject(
+ cmd,
+ "prohibited by Gerrit: don't know how to handle config update of type "
+ + cmd.getType());
+ }
+ }
+
+ /**
+ * validates a push to refs/meta/config for plugin configuration, and rejects the push if it
+ * fails.
+ */
+ private void validatePluginConfig(ReceiveCommand cmd, ProjectConfig cfg) {
+ for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
+ PluginConfig pluginCfg = cfg.getPluginConfig(e.getPluginName());
+ ProjectConfigEntry configEntry = e.getProvider().get();
+ String value = pluginCfg.getString(e.getExportName());
+ String oldValue =
+ projectState.getConfig().getPluginConfig(e.getPluginName()).getString(e.getExportName());
+ if (configEntry.getType() == ProjectConfigEntryType.ARRAY) {
+ oldValue =
+ Arrays.stream(
+ projectState
+ .getConfig()
+ .getPluginConfig(e.getPluginName())
+ .getStringList(e.getExportName()))
+ .collect(joining("\n"));
+ }
+
+ if ((value == null ? oldValue != null : !value.equals(oldValue))
+ && !configEntry.isEditable(projectState)) {
+ reject(
+ cmd,
+ String.format(
+ "invalid project configuration: Not allowed to set parameter"
+ + " '%s' of plugin '%s' on project '%s'.",
+ e.getExportName(), e.getPluginName(), project.getName()));
+ continue;
+ }
+
+ if (ProjectConfigEntryType.LIST.equals(configEntry.getType())
+ && value != null
+ && !configEntry.getPermittedValues().contains(value)) {
+ reject(
+ cmd,
+ String.format(
+ "invalid project configuration: The value '%s' is "
+ + "not permitted for parameter '%s' of plugin '%s'.",
+ value, e.getExportName(), e.getPluginName()));
}
}
}
@@ -1085,13 +1177,12 @@
try {
obj = receivePack.getRevWalk().parseAny(cmd.getNewId());
} catch (IOException err) {
- logError(
- "Invalid object " + cmd.getNewId().name() + " for " + cmd.getRefName() + " creation",
- err);
+ logger.atSevere().withCause(err).log(
+ "Invalid object %s for %s creation", cmd.getNewId().name(), cmd.getRefName());
reject(cmd, "invalid object");
return;
}
- logDebug("Creating %s", cmd);
+ logger.atFine().log("Creating %s", cmd);
if (isHead(cmd) && !isCommit(cmd)) {
return;
@@ -1102,45 +1193,32 @@
// Must pass explicit user instead of injecting a provider into CreateRefControl, since
// Provider<CurrentUser> within ReceiveCommits will always return anonymous.
createRefControl.checkCreateRef(Providers.of(user), receivePack.getRepository(), branch, obj);
- } catch (AuthException | ResourceConflictException denied) {
+ } catch (AuthException denied) {
+ rejectProhibited(cmd, denied);
+ return;
+ } catch (ResourceConflictException denied) {
reject(cmd, "prohibited by Gerrit: " + denied.getMessage());
return;
}
- if (!validRefOperation(cmd)) {
- // validRefOperation sets messages, so no need to provide more feedback.
- return;
+ if (validRefOperation(cmd)) {
+ validateRegularPushCommits(new Branch.NameKey(project.getNameKey(), cmd.getRefName()), cmd);
}
-
- validateNewCommits(new Branch.NameKey(project.getNameKey(), cmd.getRefName()), cmd);
- actualCommands.add(cmd);
}
private void parseUpdate(ReceiveCommand cmd) throws PermissionBackendException {
- logDebug("Updating %s", cmd);
- boolean ok;
- try {
- permissions.ref(cmd.getRefName()).check(RefPermission.UPDATE);
- ok = true;
- } catch (AuthException err) {
- ok = false;
- }
- if (ok) {
+ logger.atFine().log("Updating %s", cmd);
+ Optional<AuthException> err = checkRefPermission(cmd, RefPermission.UPDATE);
+ if (!err.isPresent()) {
if (isHead(cmd) && !isCommit(cmd)) {
+ reject(cmd, "head must point to commit");
return;
}
- if (!validRefOperation(cmd)) {
- return;
+ if (validRefOperation(cmd)) {
+ validateRegularPushCommits(new Branch.NameKey(project.getNameKey(), cmd.getRefName()), cmd);
}
- validateNewCommits(new Branch.NameKey(project.getNameKey(), cmd.getRefName()), cmd);
- actualCommands.add(cmd);
} else {
- if (RefNames.REFS_CONFIG.equals(cmd.getRefName())) {
- errors.put(ReceiveError.CONFIG_UPDATE, RefNames.REFS_CONFIG);
- } else {
- errors.put(ReceiveError.UPDATE, cmd.getRefName());
- }
- reject(cmd, "prohibited by Gerrit: ref update access denied");
+ rejectProhibited(cmd, err.get());
}
}
@@ -1149,7 +1227,8 @@
try {
obj = receivePack.getRevWalk().parseAny(cmd.getNewId());
} catch (IOException err) {
- logError("Invalid object " + cmd.getNewId().name() + " for " + cmd.getRefName(), err);
+ logger.atSevere().withCause(err).log(
+ "Invalid object %s for %s", cmd.getNewId().name(), cmd.getRefName());
reject(cmd, "invalid object");
return false;
}
@@ -1162,34 +1241,21 @@
}
private void parseDelete(ReceiveCommand cmd) throws PermissionBackendException {
- logDebug("Deleting %s", cmd);
+ logger.atFine().log("Deleting %s", cmd);
if (cmd.getRefName().startsWith(REFS_CHANGES)) {
- errors.put(ReceiveError.DELETE_CHANGES, cmd.getRefName());
+ errors.put(CANNOT_DELETE_CHANGES, cmd.getRefName());
reject(cmd, "cannot delete changes");
- } else if (canDelete(cmd)) {
- if (!validRefOperation(cmd)) {
- return;
- }
- actualCommands.add(cmd);
- } else if (RefNames.REFS_CONFIG.equals(cmd.getRefName())) {
+ } else if (isConfigRef(cmd.getRefName())) {
+ errors.put(CANNOT_DELETE_CONFIG, cmd.getRefName());
reject(cmd, "cannot delete project configuration");
+ }
+
+ Optional<AuthException> err = checkRefPermission(cmd, RefPermission.DELETE);
+ if (!err.isPresent()) {
+ validRefOperation(cmd);
+
} else {
- errors.put(ReceiveError.DELETE, cmd.getRefName());
- reject(cmd, "cannot delete references");
- }
- }
-
- private boolean canDelete(ReceiveCommand cmd) throws PermissionBackendException {
- if (isConfigRef(cmd.getRefName())) {
- // Never allow to delete the meta config branch.
- return false;
- }
-
- try {
- permissions.ref(cmd.getRefName()).check(RefPermission.DELETE);
- return projectState.statePermitsWrite();
- } catch (AuthException e) {
- return false;
+ rejectProhibited(cmd, err.get());
}
}
@@ -1200,38 +1266,62 @@
} catch (IncorrectObjectTypeException notCommit) {
newObject = null;
} catch (IOException err) {
- logError(
- "Invalid object " + cmd.getNewId().name() + " for " + cmd.getRefName() + " forced update",
- err);
+ logger.atSevere().withCause(err).log(
+ "Invalid object %s for %s forced update", cmd.getNewId().name(), cmd.getRefName());
reject(cmd, "invalid object");
return;
}
- logDebug("Rewinding %s", cmd);
+ logger.atFine().log("Rewinding %s", cmd);
if (newObject != null) {
- validateNewCommits(new Branch.NameKey(project.getNameKey(), cmd.getRefName()), cmd);
+ validateRegularPushCommits(new Branch.NameKey(project.getNameKey(), cmd.getRefName()), cmd);
if (cmd.getResult() != NOT_ATTEMPTED) {
return;
}
}
- boolean ok;
- try {
- permissions.ref(cmd.getRefName()).check(RefPermission.FORCE_UPDATE);
- ok = true;
- } catch (AuthException err) {
- ok = false;
- }
- if (ok) {
- if (!validRefOperation(cmd)) {
- return;
- }
- actualCommands.add(cmd);
+ Optional<AuthException> err = checkRefPermission(cmd, RefPermission.FORCE_UPDATE);
+ if (!err.isPresent()) {
+ validRefOperation(cmd);
} else {
- cmd.setResult(REJECTED_OTHER_REASON, "need '" + PermissionRule.FORCE_PUSH + "' privilege.");
+ rejectProhibited(cmd, err.get());
}
}
+ private Optional<AuthException> checkRefPermission(ReceiveCommand cmd, RefPermission perm)
+ throws PermissionBackendException {
+ return checkRefPermission(permissions.ref(cmd.getRefName()), perm);
+ }
+
+ private Optional<AuthException> checkRefPermission(
+ PermissionBackend.ForRef forRef, RefPermission perm) throws PermissionBackendException {
+ try {
+ forRef.check(perm);
+ return Optional.empty();
+ } catch (AuthException e) {
+ return Optional.of(e);
+ }
+ }
+
+ private void rejectProhibited(ReceiveCommand cmd, AuthException err) {
+ err.getAdvice().ifPresent(a -> errors.put(a, cmd.getRefName()));
+ reject(cmd, prohibited(err, cmd.getRefName()));
+ }
+
+ private static String prohibited(AuthException e, String alreadyDisplayedResource) {
+ String msg = e.getMessage();
+ if (e instanceof PermissionDeniedException) {
+ PermissionDeniedException pde = (PermissionDeniedException) e;
+ if (pde.getResource().isPresent()
+ && pde.getResource().get().equals(alreadyDisplayedResource)) {
+ // Avoid repeating resource name if exactly the given name was already displayed by the
+ // generic git push machinery.
+ msg = PermissionDeniedException.MESSAGE_PREFIX + pde.describePermission();
+ }
+ }
+ return "prohibited by Gerrit: " + msg;
+ }
+
static class MagicBranchInput {
private static final Splitter COMMAS = Splitter.on(',').omitEmptyStrings();
@@ -1249,6 +1339,9 @@
CmdLineParser cmdLineParser;
Set<String> hashtags = new HashSet<>();
+ @Option(name = "--trace", metaVar = "NAME", usage = "enable tracing")
+ String trace;
+
@Option(name = "--base", metaVar = "BASE", usage = "merge base of changes")
List<ObjectId> base;
@@ -1501,14 +1594,8 @@
* <p>Assumes we are handling a magic branch here.
*/
private void parseMagicBranch(ReceiveCommand cmd) throws PermissionBackendException {
- // Permit exactly one new change request per push.
- if (magicBranch != null) {
- reject(cmd, "duplicate request");
- return;
- }
-
- logDebug("Found magic branch %s", cmd.getRefName());
- magicBranch = new MagicBranchInput(user, cmd, labelTypes, notesMigration);
+ logger.atFine().log("Found magic branch %s", cmd.getRefName());
+ MagicBranchInput magicBranch = new MagicBranchInput(user, cmd, labelTypes, notesMigration);
magicBranch.reviewer.addAll(extraReviewers.get(ReviewerStateInternal.REVIEWER));
magicBranch.cc.addAll(extraReviewers.get(ReviewerStateInternal.CC));
@@ -1519,7 +1606,7 @@
ref = magicBranch.parse(repo, receivePack.getAdvertisedRefs().keySet(), pushOptions);
} catch (CmdLineException e) {
if (!magicBranch.cmdLineParser.wasHelpRequestedByOption()) {
- logDebug("Invalid branch syntax");
+ logger.atFine().log("Invalid branch syntax");
reject(cmd, e.getMessage());
return;
}
@@ -1540,13 +1627,13 @@
return;
}
if (projectState.isAllUsers() && RefNames.REFS_USERS_SELF.equals(ref)) {
- logDebug("Handling %s", RefNames.REFS_USERS_SELF);
+ logger.atFine().log("Handling %s", RefNames.REFS_USERS_SELF);
ref = RefNames.refsUsers(user.getAccountId());
}
if (!receivePack.getAdvertisedRefs().containsKey(ref)
&& !ref.equals(readHEAD(repo))
&& !ref.equals(RefNames.REFS_CONFIG)) {
- logDebug("Ref %s not found", ref);
+ logger.atFine().log("Ref %s not found", ref);
if (ref.startsWith(Constants.R_HEADS)) {
String n = ref.substring(Constants.R_HEADS.length());
reject(cmd, "branch " + n + " not found");
@@ -1559,22 +1646,16 @@
magicBranch.dest = new Branch.NameKey(project.getNameKey(), ref);
magicBranch.perm = permissions.ref(ref);
- try {
- magicBranch.perm.check(RefPermission.CREATE_CHANGE);
- } catch (AuthException denied) {
- errors.put(ReceiveError.CODE_REVIEW, ref);
- reject(cmd, denied.getMessage());
- return;
- }
- if (!projectState.statePermitsWrite()) {
- reject(cmd, "project state does not permit write");
+ Optional<AuthException> err = checkRefPermission(magicBranch.perm, RefPermission.CREATE_CHANGE);
+ if (err.isPresent()) {
+ rejectProhibited(cmd, err.get());
return;
}
// TODO(davido): Remove legacy support for drafts magic branch option
// after repo-tool supports private and work-in-progress changes.
if (magicBranch.draft && !receiveConfig.allowDrafts) {
- errors.put(ReceiveError.CODE_REVIEW, ref);
+ errors.put(CODE_REVIEW_ERROR, ref);
reject(cmd, "draft workflow is disabled");
return;
}
@@ -1607,10 +1688,9 @@
}
if (magicBranch.submit) {
- try {
- permissions.ref(ref).check(RefPermission.UPDATE_BY_SUBMIT);
- } catch (AuthException e) {
- reject(cmd, e.getMessage());
+ err = checkRefPermission(magicBranch.perm, RefPermission.UPDATE_BY_SUBMIT);
+ if (err.isPresent()) {
+ rejectProhibited(cmd, err.get());
return;
}
}
@@ -1619,10 +1699,10 @@
RevCommit tip;
try {
tip = walk.parseCommit(magicBranch.cmd.getNewId());
- logDebug("Tip of push: %s", tip.name());
+ logger.atFine().log("Tip of push: %s", tip.name());
} catch (IOException ex) {
magicBranch.cmd.setResult(REJECTED_MISSING_OBJECT);
- logError("Invalid pack upload; one or more objects weren't sent", ex);
+ logger.atSevere().withCause(ex).log("Invalid pack upload; one or more objects weren't sent");
return;
}
@@ -1649,12 +1729,12 @@
|| magicBranch.base != null
|| magicBranch.merged
|| tip.getParentCount() == 0) {
- logDebug("Forcing newChangeForAllNotInTarget = false");
+ logger.atFine().log("Forcing newChangeForAllNotInTarget = false");
newChangeForAllNotInTarget = false;
}
if (magicBranch.base != null) {
- logDebug("Handling %base: %s", magicBranch.base);
+ logger.atFine().log("Handling %%base: %s", magicBranch.base);
magicBranch.baseCommit = Lists.newArrayListWithCapacity(magicBranch.base.size());
for (ObjectId id : magicBranch.base) {
try {
@@ -1666,7 +1746,8 @@
reject(cmd, "base not found");
return;
} catch (IOException e) {
- logWarn(String.format("Project %s cannot read %s", project.getName(), id.name()), e);
+ logger.atWarning().withCause(e).log(
+ "Project %s cannot read %s", project.getName(), id.name());
reject(cmd, "internal server error");
return;
}
@@ -1677,31 +1758,40 @@
return; // readBranchTip already rejected cmd.
}
magicBranch.baseCommit = Collections.singletonList(branchTip);
- logDebug("Set baseCommit = %s", magicBranch.baseCommit.get(0).name());
+ logger.atFine().log("Set baseCommit = %s", magicBranch.baseCommit.get(0).name());
}
} catch (IOException ex) {
- logWarn(
- String.format("Error walking to %s in project %s", destBranch, project.getName()), ex);
+ logger.atWarning().withCause(ex).log(
+ "Error walking to %s in project %s", destBranch, project.getName());
reject(cmd, "internal server error");
return;
}
- // Validate that the new commits are connected with the target
- // branch. If they aren't, we want to abort. We do this check by
- // looking to see if we can compute a merge base between the new
- // commits and the target branch head.
- //
+ if (validateConnected(magicBranch.cmd, magicBranch.dest, tip)) {
+ this.magicBranch = magicBranch;
+ }
+ }
+
+ // Validate that the new commits are connected with the target
+ // branch. If they aren't, we want to abort. We do this check by
+ // looking to see if we can compute a merge base between the new
+ // commits and the target branch head.
+ private boolean validateConnected(ReceiveCommand cmd, Branch.NameKey dest, RevCommit tip) {
+ RevWalk walk = receivePack.getRevWalk();
try {
- Ref targetRef = receivePack.getAdvertisedRefs().get(magicBranch.dest.get());
+ Ref targetRef = receivePack.getAdvertisedRefs().get(dest.get());
if (targetRef == null || targetRef.getObjectId() == null) {
// The destination branch does not yet exist. Assume the
// history being sent for review will start it and thus
// is "connected" to the branch.
- logDebug("Branch is unborn");
- return;
+ logger.atFine().log("Branch is unborn");
+
+ // This is not an error condition.
+ return true;
}
+
RevCommit h = walk.parseCommit(targetRef.getObjectId());
- logDebug("Current branch tip: %s", h.name());
+ logger.atFine().log("Current branch tip: %s", h.name());
RevFilter oldRevFilter = walk.getRevFilter();
try {
walk.reset();
@@ -1709,16 +1799,19 @@
walk.markStart(tip);
walk.markStart(h);
if (walk.next() == null) {
- reject(magicBranch.cmd, "no common ancestry");
+ reject(cmd, "no common ancestry");
+ return false;
}
} finally {
walk.reset();
walk.setRevFilter(oldRevFilter);
}
} catch (IOException e) {
- magicBranch.cmd.setResult(REJECTED_MISSING_OBJECT);
- logError("Invalid pack upload; one or more objects weren't sent", e);
+ cmd.setResult(REJECTED_MISSING_OBJECT);
+ logger.atSevere().withCause(e).log("Invalid pack upload; one or more objects weren't sent");
+ return false;
}
+ return true;
}
private static String readHEAD(Repository repo) {
@@ -1741,7 +1834,7 @@
// Handle an upload to refs/changes/XX/CHANGED-NUMBER.
private void parseReplaceCommand(ReceiveCommand cmd, Change.Id changeId) {
- logDebug("Parsing replace command");
+ logger.atFine().log("Parsing replace command");
if (cmd.getType() != ReceiveCommand.Type.CREATE) {
reject(cmd, "invalid usage");
return;
@@ -1750,9 +1843,9 @@
RevCommit newCommit;
try {
newCommit = receivePack.getRevWalk().parseCommit(cmd.getNewId());
- logDebug("Replacing with %s", newCommit);
+ logger.atFine().log("Replacing with %s", newCommit);
} catch (IOException e) {
- logError("Cannot parse " + cmd.getNewId().name() + " as commit", e);
+ logger.atSevere().withCause(e).log("Cannot parse %s as commit", cmd.getNewId().name());
reject(cmd, "invalid commit");
return;
}
@@ -1761,11 +1854,11 @@
try {
changeEnt = notesFactory.createChecked(db, project.getNameKey(), changeId).getChange();
} catch (NoSuchChangeException e) {
- logError("Change not found " + changeId, e);
+ logger.atSevere().withCause(e).log("Change not found %s", changeId);
reject(cmd, "change " + changeId + " not found");
return;
} catch (OrmException e) {
- logError("Cannot lookup existing change " + changeId, e);
+ logger.atSevere().withCause(e).log("Cannot lookup existing change %s", changeId);
reject(cmd, "database error");
return;
}
@@ -1774,10 +1867,29 @@
return;
}
- logDebug("Replacing change %s", changeEnt.getId());
- requestReplace(cmd, true, changeEnt, newCommit);
+ BranchCommitValidator validator =
+ commitValidatorFactory.create(projectState, changeEnt.getDest(), user);
+ try {
+ if (validator.validCommit(
+ receivePack.getRevWalk().getObjectReader(),
+ cmd,
+ newCommit,
+ false,
+ messages,
+ rejectCommits,
+ changeEnt)) {
+ logger.atFine().log("Replacing change %s", changeEnt.getId());
+ requestReplace(cmd, true, changeEnt, newCommit);
+ }
+ } catch (IOException e) {
+ reject(cmd, "I/O exception validating commit");
+ }
}
+ /**
+ * Add an update for an existing change. Returns true if it succeeded; rejects the command if it
+ * failed.
+ */
private boolean requestReplace(
ReceiveCommand cmd, boolean checkMergedInto, Change change, RevCommit newCommit) {
if (change.getStatus().isClosed()) {
@@ -1797,14 +1909,34 @@
return true;
}
- private List<CreateRequest> selectNewAndReplacedChangesFromMagicBranch() {
- logDebug("Finding new and replaced changes");
+ private void warnAboutMissingChangeId(List<CreateRequest> newChanges) {
+ for (CreateRequest create : newChanges) {
+ try {
+ receivePack.getRevWalk().parseBody(create.commit);
+ } catch (IOException e) {
+ continue;
+ }
+ List<String> idList = create.commit.getFooterLines(FooterConstants.CHANGE_ID);
+
+ if (idList.isEmpty()) {
+ messages.add(
+ new ValidationMessage("warning: pushing without Change-Id is deprecated", false));
+ break;
+ }
+ }
+ }
+
+ private List<CreateRequest> selectNewAndReplacedChangesFromMagicBranch(Task newProgress) {
+ logger.atFine().log("Finding new and replaced changes");
List<CreateRequest> newChanges = new ArrayList<>();
ListMultimap<ObjectId, Ref> existing = changeRefsById();
GroupCollector groupCollector =
GroupCollector.create(changeRefsById(), db, psUtil, notesFactory, project.getNameKey());
+ BranchCommitValidator validator =
+ commitValidatorFactory.create(projectState, magicBranch.dest, user);
+
try {
RevCommit start = setUpWalkForSelectingChanges();
if (start == null) {
@@ -1874,13 +2006,15 @@
List<String> idList = c.getFooterLines(CHANGE_ID);
if (!idList.isEmpty()) {
- pending.put(c, new ChangeLookup(c, new Change.Key(idList.get(idList.size() - 1).trim())));
+ pending.put(
+ c, lookupByChangeKey(c, new Change.Key(idList.get(idList.size() - 1).trim())));
} else {
- pending.put(c, new ChangeLookup(c));
+ pending.put(c, lookupByCommit(c));
}
+
int n = pending.size() + newChanges.size();
if (maxBatchChanges != 0 && n > maxBatchChanges) {
- logDebug("%d changes exceeds limit of %d", n, maxBatchChanges);
+ logger.atFine().log("%d changes exceeds limit of %d", n, maxBatchChanges);
reject(
magicBranch.cmd,
"the number of pushed changes in a batch exceeds the max limit " + maxBatchChanges);
@@ -1899,12 +2033,19 @@
continue;
}
- logDebug("Creating new change for %s even though it is already tracked", name);
+ logger.atFine().log("Creating new change for %s even though it is already tracked", name);
}
- if (!validCommit(receivePack.getRevWalk(), magicBranch.dest, magicBranch.cmd, c, null)) {
+ if (!validator.validCommit(
+ receivePack.getRevWalk().getObjectReader(),
+ magicBranch.cmd,
+ c,
+ magicBranch.merged,
+ messages,
+ rejectCommits,
+ null)) {
// Not a change the user can propose? Abort as early as possible.
- logDebug("Aborting early due to invalid commit");
+ logger.atFine().log("Aborting early due to invalid commit");
return Collections.emptyList();
}
@@ -1914,16 +2055,16 @@
magicBranch.cmd,
"Pushing merges in commit chains with 'all not in target' is not allowed,\n"
+ "to override please set the base manually");
- logDebug("Rejecting merge commit %s with newChangeForAllNotInTarget", name);
+ logger.atFine().log("Rejecting merge commit %s with newChangeForAllNotInTarget", name);
// TODO(dborowitz): Should we early return here?
}
if (idList.isEmpty()) {
- newChanges.add(new CreateRequest(c, magicBranch.dest.get()));
+ newChanges.add(new CreateRequest(c, magicBranch.dest.get(), newProgress));
continue;
}
}
- logDebug(
+ logger.atFine().log(
"Finished initial RevWalk with %d commits total: %d already"
+ " tracked, %d new changes with no Change-Id, and %d deferred"
+ " lookups",
@@ -1940,14 +2081,14 @@
}
if (newChangeIds.contains(p.changeKey)) {
- logDebug("Multiple commits with Change-Id %s", p.changeKey);
+ logger.atFine().log("Multiple commits with Change-Id %s", p.changeKey);
reject(magicBranch.cmd, SAME_CHANGE_ID_IN_MULTIPLE_CHANGES);
return Collections.emptyList();
}
List<ChangeData> changes = p.destChanges;
if (changes.size() > 1) {
- logDebug(
+ logger.atFine().log(
"Multiple changes in branch %s with Change-Id %s: %s",
magicBranch.dest,
p.changeKey,
@@ -2003,9 +2144,9 @@
}
newChangeIds.add(p.changeKey);
}
- newChanges.add(new CreateRequest(p.commit, magicBranch.dest.get()));
+ newChanges.add(new CreateRequest(p.commit, magicBranch.dest.get(), newProgress));
}
- logDebug(
+ logger.atFine().log(
"Finished deferred lookups with %d updates and %d new changes",
replaceByChange.size(), newChanges.size());
} catch (IOException e) {
@@ -2013,10 +2154,10 @@
// identified the missing object earlier before we got control.
//
magicBranch.cmd.setResult(REJECTED_MISSING_OBJECT);
- logError("Invalid pack upload; one or more objects weren't sent", e);
+ logger.atSevere().withCause(e).log("Invalid pack upload; one or more objects weren't sent");
return Collections.emptyList();
} catch (OrmException e) {
- logError("Cannot query database to locate prior changes", e);
+ logger.atSevere().withCause(e).log("Cannot query database to locate prior changes");
reject(magicBranch.cmd, "database error");
return Collections.emptyList();
}
@@ -2044,9 +2185,9 @@
for (UpdateGroupsRequest update : updateGroups) {
update.groups = ImmutableList.copyOf((groups.get(update.commit)));
}
- logDebug("Finished updating groups from GroupCollector");
+ logger.atFine().log("Finished updating groups from GroupCollector");
} catch (OrmException e) {
- logError("Error collecting groups for changes", e);
+ logger.atSevere().withCause(e).log("Error collecting groups for changes");
reject(magicBranch.cmd, "internal server error");
}
return newChanges;
@@ -2058,7 +2199,7 @@
notesFactory.create(db, project.getNameKey(), Change.Id.fromRef(ref.getName()));
Change change = notes.getChange();
if (change.getDest().equals(magicBranch.dest)) {
- logDebug("Found change %s from existing refs.", change.getKey());
+ logger.atFine().log("Found change %s from existing refs.", change.getKey());
// Reindex the change asynchronously, ignoring errors.
@SuppressWarnings("unused")
Future<?> possiblyIgnoredError = indexer.indexAsync(project.getNameKey(), change.getId());
@@ -2079,7 +2220,7 @@
if (magicBranch.baseCommit != null) {
markExplicitBasesUninteresting();
} else if (magicBranch.merged) {
- logDebug("Marking parents of merged commit %s uninteresting", start.name());
+ logger.atFine().log("Marking parents of merged commit %s uninteresting", start.name());
for (RevCommit c : start.getParents()) {
rw.markUninteresting(c);
}
@@ -2090,13 +2231,13 @@
}
private void markExplicitBasesUninteresting() throws IOException {
- logDebug("Marking %d base commits uninteresting", magicBranch.baseCommit.size());
+ logger.atFine().log("Marking %d base commits uninteresting", magicBranch.baseCommit.size());
for (RevCommit c : magicBranch.baseCommit) {
receivePack.getRevWalk().markUninteresting(c);
}
Ref targetRef = allRefs().get(magicBranch.dest.get());
if (targetRef != null) {
- logDebug(
+ logger.atFine().log(
"Marking target ref %s (%s) uninteresting",
magicBranch.dest.get(), targetRef.getObjectId().name());
receivePack
@@ -2150,38 +2291,45 @@
rw.markUninteresting(rw.parseCommit(ref.getObjectId()));
i++;
} catch (IOException e) {
- logWarn(String.format("Invalid ref %s in %s", ref.getName(), project.getName()), e);
+ logger.atWarning().withCause(e).log(
+ "Invalid ref %s in %s", ref.getName(), project.getName());
}
}
}
- logDebug("Marked %d heads as uninteresting", i);
+ logger.atFine().log("Marked %d heads as uninteresting", i);
}
private static boolean isValidChangeId(String idStr) {
return idStr.matches("^I[0-9a-fA-F]{40}$") && !idStr.matches("^I00*$");
}
- private class ChangeLookup {
+ private static class ChangeLookup {
final RevCommit commit;
- final Change.Key changeKey;
+
+ @Nullable final Change.Key changeKey;
final List<ChangeData> destChanges;
- ChangeLookup(RevCommit c, Change.Key key) throws OrmException {
- commit = c;
- changeKey = key;
- destChanges = queryProvider.get().byBranchKey(magicBranch.dest, key);
- }
-
- ChangeLookup(RevCommit c) throws OrmException {
- commit = c;
- destChanges = queryProvider.get().byBranchCommit(magicBranch.dest, c.getName());
- changeKey = null;
+ ChangeLookup(RevCommit c, @Nullable Change.Key key, final List<ChangeData> destChanges) {
+ this.commit = c;
+ this.changeKey = key;
+ this.destChanges = destChanges;
}
}
+ ChangeLookup lookupByChangeKey(RevCommit c, Change.Key key) throws OrmException {
+ return new ChangeLookup(c, key, queryProvider.get().byBranchKey(magicBranch.dest, key));
+ }
+
+ ChangeLookup lookupByCommit(RevCommit c) throws OrmException {
+ return new ChangeLookup(
+ c, null, queryProvider.get().byBranchCommit(magicBranch.dest, c.getName()));
+ }
+
+ /** Represents a commit for which a Change should be created. */
private class CreateRequest {
final RevCommit commit;
- private final String refName;
+ final Task progress;
+ final String refName;
Change.Id changeId;
ReceiveCommand cmd;
@@ -2190,9 +2338,10 @@
Change change;
- CreateRequest(RevCommit commit, String refName) {
+ CreateRequest(RevCommit commit, String refName, Task progress) {
this.commit = commit;
this.refName = refName;
+ this.progress = progress;
}
private void setChangeId(int id) {
@@ -2236,16 +2385,15 @@
Account.Id me = user.getAccountId();
List<FooterLine> footerLines = commit.getFooterLines();
MailRecipients recipients = new MailRecipients();
- Map<String, Short> approvals = new HashMap<>();
checkNotNull(magicBranch);
recipients.add(magicBranch.getMailRecipients());
- approvals = magicBranch.labels;
+ Map<String, Short> approvals = magicBranch.labels;
recipients.add(getRecipientsFromFooters(accountResolver, footerLines));
recipients.remove(me);
StringBuilder msg =
new StringBuilder(
ApprovalsUtil.renderMessageWithApprovals(
- psId.get(), approvals, Collections.<String, PatchSetApproval>emptyMap()));
+ psId.get(), approvals, Collections.emptyMap()));
msg.append('.');
if (!Strings.isNullOrEmpty(magicBranch.message)) {
msg.append("\n").append(magicBranch.message);
@@ -2287,7 +2435,7 @@
return false;
}
});
- bu.addOp(changeId, new ChangeProgressOp(newProgress));
+ bu.addOp(changeId, new ChangeProgressOp(progress));
} catch (Exception e) {
throw INSERT_EXCEPTION.apply(e);
}
@@ -2308,7 +2456,7 @@
Change tipChange = bySha.get(magicBranch.cmd.getNewId());
checkNotNull(
tipChange, "tip of push does not correspond to a change; found these changes: %s", bySha);
- logDebug(
+ logger.atFine().log(
"Processing submit with tip change %s (%s)", tipChange.getId(), magicBranch.cmd.getNewId());
try (MergeOp op = mergeOpProvider.get()) {
op.merge(db, tipChange, user, false, new SubmitInput(), false);
@@ -2321,31 +2469,27 @@
for (Iterator<ReplaceRequest> itr = replaceByChange.values().iterator(); itr.hasNext(); ) {
ReplaceRequest req = itr.next();
if (req.inputCommand.getResult() == NOT_ATTEMPTED) {
- req.validate(false);
+ req.validateNewPatchSet();
}
}
} catch (OrmException err) {
- logError(
- String.format(
- "Cannot read database before replacement for project %s", project.getName()),
- err);
+ logger.atSevere().withCause(err).log(
+ "Cannot read database before replacement for project %s", project.getName());
for (ReplaceRequest req : replaceByChange.values()) {
if (req.inputCommand.getResult() == NOT_ATTEMPTED) {
req.inputCommand.setResult(REJECTED_OTHER_REASON, "internal server error");
}
}
} catch (IOException | PermissionBackendException err) {
- logError(
- String.format(
- "Cannot read repository before replacement for project %s", project.getName()),
- err);
+ logger.atSevere().withCause(err).log(
+ "Cannot read repository before replacement for project %s", project.getName());
for (ReplaceRequest req : replaceByChange.values()) {
if (req.inputCommand.getResult() == NOT_ATTEMPTED) {
req.inputCommand.setResult(REJECTED_OTHER_REASON, "internal server error");
}
}
}
- logDebug("Read %d changes to replace", replaceByChange.size());
+ logger.atFine().log("Read %d changes to replace", replaceByChange.size());
if (magicBranch != null && magicBranch.cmd.getResult() != NOT_ATTEMPTED) {
// Cancel creations tied to refs/for/ or refs/drafts/ command.
@@ -2369,6 +2513,7 @@
}
}
+ /** Represents a commit that should be stored in a new patchset of an existing change. */
private class ReplaceRequest {
final Change.Id ontoChange;
final ObjectId newCommitId;
@@ -2380,9 +2525,9 @@
ReceiveCommand prev;
ReceiveCommand cmd;
PatchSetInfo info;
- private PatchSet.Id priorPatchSet;
+ PatchSet.Id priorPatchSet;
List<String> groups = ImmutableList.of();
- private ReplaceOp replaceOp;
+ ReplaceOp replaceOp;
ReplaceRequest(
Change.Id toChange, RevCommit newCommit, ReceiveCommand cmd, boolean checkMergedInto) {
@@ -2398,10 +2543,8 @@
receivePack.getRevWalk().parseCommit(ref.getObjectId()),
PatchSet.Id.fromRef(ref.getName()));
} catch (IOException err) {
- logWarn(
- String.format(
- "Project %s contains invalid change ref %s", project.getName(), ref.getName()),
- err);
+ logger.atWarning().withCause(err).log(
+ "Project %s contains invalid change ref %s", project.getName(), ref.getName());
}
}
}
@@ -2417,18 +2560,46 @@
* <li>May reset {@code receivePack.getRevWalk()}; do not call in the middle of a walk.
* </ul>
*
- * @param autoClose whether the caller intends to auto-close the change after adding a new patch
- * set.
* @return whether the new commit is valid
* @throws IOException
* @throws OrmException
* @throws PermissionBackendException
*/
- boolean validate(boolean autoClose)
- throws IOException, OrmException, PermissionBackendException {
- if (!autoClose && inputCommand.getResult() != NOT_ATTEMPTED) {
+ boolean validateNewPatchSet() throws IOException, OrmException, PermissionBackendException {
+ if (!validateNewPatchSetNoteDb()) {
return false;
- } else if (notes == null) {
+ }
+ sameTreeWarning();
+
+ if (magicBranch != null) {
+ validateMagicBranchWipStatusChange();
+ if (inputCommand.getResult() != NOT_ATTEMPTED) {
+ return false;
+ }
+
+ if (magicBranch.edit || magicBranch.draft) {
+ return newEdit();
+ }
+ }
+
+ newPatchSet();
+ return true;
+ }
+
+ boolean validateNewPatchSetForAutoClose()
+ throws IOException, OrmException, PermissionBackendException {
+ if (!validateNewPatchSetNoteDb()) {
+ return false;
+ }
+
+ newPatchSet();
+ return true;
+ }
+
+ /** Validates the new PS against permissions and notedb status. */
+ private boolean validateNewPatchSetNoteDb()
+ throws IOException, OrmException, PermissionBackendException {
+ if (notes == null) {
reject(inputCommand, "change " + ontoChange + " not found");
return false;
}
@@ -2441,7 +2612,6 @@
}
RevCommit newCommit = receivePack.getRevWalk().parseCommit(newCommitId);
- RevCommit priorCommit = revisions.inverse().get(priorPatchSet);
// Not allowed to create a new patch set if the current patch set is locked.
if (psUtil.isPatchSetLocked(notes)) {
@@ -2456,10 +2626,6 @@
return false;
}
- if (!projectState.statePermitsWrite()) {
- reject(inputCommand, "cannot add patch set to " + ontoChange + ".");
- return false;
- }
if (change.getStatus().isClosed()) {
reject(inputCommand, "change " + ontoChange + " closed");
return false;
@@ -2485,48 +2651,13 @@
}
}
- if (!validCommit(
- receivePack.getRevWalk(), change.getDest(), inputCommand, newCommit, change)) {
- return false;
- }
- receivePack.getRevWalk().parseBody(priorCommit);
+ return true;
+ }
- // Don't allow the same tree if the commit message is unmodified
- // or no parents were updated (rebase), else warn that only part
- // of the commit was modified.
- if (newCommit.getTree().equals(priorCommit.getTree())) {
- boolean messageEq =
- Objects.equals(newCommit.getFullMessage(), priorCommit.getFullMessage());
- boolean parentsEq = parentsEqual(newCommit, priorCommit);
- boolean authorEq = authorEqual(newCommit, priorCommit);
- ObjectReader reader = receivePack.getRevWalk().getObjectReader();
-
- if (messageEq && parentsEq && authorEq && !autoClose) {
- addMessage(
- String.format(
- "(W) No changes between prior commit %s and new commit %s",
- reader.abbreviate(priorCommit).name(), reader.abbreviate(newCommit).name()));
- } else {
- StringBuilder msg = new StringBuilder();
- msg.append("(I) ");
- msg.append(reader.abbreviate(newCommit).name());
- msg.append(":");
- msg.append(" no files changed");
- if (!authorEq) {
- msg.append(", author changed");
- }
- if (!messageEq) {
- msg.append(", message updated");
- }
- if (!parentsEq) {
- msg.append(", was rebased");
- }
- addMessage(msg.toString());
- }
- }
-
- if (magicBranch != null
- && (magicBranch.workInProgress || magicBranch.ready)
+ /** Validates whether the WIP change is allowed. Rejects inputCommand if not. */
+ private void validateMagicBranchWipStatusChange() throws PermissionBackendException {
+ Change change = notes.getChange();
+ if ((magicBranch.workInProgress || magicBranch.ready)
&& magicBranch.workInProgress != change.isWorkInProgress()
&& !user.getAccountId().equals(change.getOwner())) {
boolean hasWriteConfigPermission = false;
@@ -2542,27 +2673,59 @@
permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
} catch (AuthException e1) {
reject(inputCommand, ONLY_CHANGE_OWNER_OR_PROJECT_OWNER_CAN_MODIFY_WIP);
- return false;
}
}
}
-
- if (magicBranch != null && (magicBranch.edit || magicBranch.draft)) {
- return newEdit();
- }
-
- newPatchSet();
- return true;
}
+ /** prints a warning if the new PS has the same tree as the previous commit. */
+ private void sameTreeWarning() throws IOException {
+ RevCommit newCommit = receivePack.getRevWalk().parseCommit(newCommitId);
+ RevCommit priorCommit = revisions.inverse().get(priorPatchSet);
+
+ if (newCommit.getTree().equals(priorCommit.getTree())) {
+ boolean messageEq =
+ Objects.equals(newCommit.getFullMessage(), priorCommit.getFullMessage());
+ boolean parentsEq = parentsEqual(newCommit, priorCommit);
+ boolean authorEq = authorEqual(newCommit, priorCommit);
+ ObjectReader reader = receivePack.getRevWalk().getObjectReader();
+
+ if (messageEq && parentsEq && authorEq) {
+ addMessage(
+ String.format(
+ "warning: no changes between prior commit %s and new commit %s",
+ reader.abbreviate(priorCommit).name(), reader.abbreviate(newCommit).name()));
+ } else {
+ StringBuilder msg = new StringBuilder();
+ msg.append("warning: ").append(reader.abbreviate(newCommit).name());
+ msg.append(":");
+ msg.append(" no files changed");
+ if (!authorEq) {
+ msg.append(", author changed");
+ }
+ if (!messageEq) {
+ msg.append(", message updated");
+ }
+ if (!parentsEq) {
+ msg.append(", was rebased");
+ }
+ addMessage(msg.toString());
+ }
+ }
+ }
+
+ /**
+ * Sets cmd and prev to the ReceiveCommands for change edits. Returns false if there was a
+ * failure.
+ */
private boolean newEdit() {
psId = notes.getChange().currentPatchSetId();
- Optional<ChangeEdit> edit = null;
+ Optional<ChangeEdit> edit;
try {
edit = editUtil.byChange(notes, user);
} catch (AuthException | IOException e) {
- logError("Cannot retrieve edit", e);
+ logger.atSevere().withCause(e).log("Cannot retrieve edit");
return false;
}
@@ -2585,8 +2748,8 @@
return true;
}
+ /** Creates a ReceiveCommand for a new edit. */
private void createEditCommand() {
- // create new edit
cmd =
new ReceiveCommand(
ObjectId.zeroId(),
@@ -2594,6 +2757,7 @@
RefNames.refsEdit(user.getAccountId(), notes.getChangeId(), psId));
}
+ /** Updates 'this' to add a new patchset. */
private void newPatchSet() throws IOException, OrmException {
RevCommit newCommit = receivePack.getRevWalk().parseCommit(newCommitId);
psId =
@@ -2644,8 +2808,8 @@
}
private class UpdateGroupsRequest {
- private final PatchSet.Id psId;
- private final RevCommit commit;
+ final PatchSet.Id psId;
+ final RevCommit commit;
List<String> groups = ImmutableList.of();
UpdateGroupsRequest(Ref ref, RevCommit commit) {
@@ -2680,7 +2844,7 @@
}
private class UpdateOneRefOp implements RepoOnlyOp {
- private final ReceiveCommand cmd;
+ final ReceiveCommand cmd;
private UpdateOneRefOp(ReceiveCommand cmd) {
this.cmd = checkNotNull(cmd);
@@ -2695,11 +2859,11 @@
public void postUpdate(Context ctx) {
String refName = cmd.getRefName();
if (cmd.getType() == ReceiveCommand.Type.UPDATE) { // aka fast-forward
- logDebug("Updating tag cache on fast-forward of %s", cmd.getRefName());
+ logger.atFine().log("Updating tag cache on fast-forward of %s", cmd.getRefName());
tagCache.updateFastForward(project.getNameKey(), refName, cmd.getOldId(), cmd.getNewId());
}
if (isConfig(cmd)) {
- logDebug("Reloading project in cache");
+ logger.atFine().log("Reloading project in cache");
try {
projectCache.evict(project);
} catch (IOException e) {
@@ -2708,7 +2872,7 @@
}
ProjectState ps = projectCache.get(project.getNameKey());
try {
- logDebug("Updating project description");
+ logger.atFine().log("Updating project description");
repo.setGitwebDescription(ps.getProject().getDescription());
} catch (IOException e) {
logger.atWarning().withCause(e).log("cannot update description of %s", project.getName());
@@ -2793,6 +2957,8 @@
&& Objects.equals(aAuthor.getEmailAddress(), bAuthor.getEmailAddress());
}
+ // Run RefValidators on the command. If any validator fails, the command status is set to
+ // REJECTED, and the return value is 'false'
private boolean validRefOperation(ReceiveCommand cmd) {
RefOperationValidators refValidators = refValidatorsFactory.create(getProject(), user, cmd);
@@ -2807,31 +2973,36 @@
return true;
}
- private void validateNewCommits(Branch.NameKey branch, ReceiveCommand cmd)
+ /**
+ * Validates the commits that a regular push brings in.
+ *
+ * <p>On validation failure, the command is rejected.
+ */
+ private void validateRegularPushCommits(Branch.NameKey branch, ReceiveCommand cmd)
throws PermissionBackendException {
- PermissionBackend.ForRef perm = permissions.ref(branch.get());
if (!RefNames.REFS_CONFIG.equals(cmd.getRefName())
&& !(MagicBranch.isMagicBranch(cmd.getRefName())
|| NEW_PATCHSET_PATTERN.matcher(cmd.getRefName()).matches())
&& pushOptions.containsKey(PUSH_OPTION_SKIP_VALIDATION)) {
- try {
- if (projectState.is(BooleanProjectConfig.USE_SIGNED_OFF_BY)) {
- throw new AuthException(
- "requireSignedOffBy prevents option " + PUSH_OPTION_SKIP_VALIDATION);
- }
-
- perm.check(RefPermission.SKIP_VALIDATION);
- if (!Iterables.isEmpty(rejectCommits)) {
- throw new AuthException("reject-commits prevents " + PUSH_OPTION_SKIP_VALIDATION);
- }
- logDebug("Short-circuiting new commit validation");
- } catch (AuthException denied) {
- reject(cmd, denied.getMessage());
+ if (projectState.is(BooleanProjectConfig.USE_SIGNED_OFF_BY)) {
+ reject(cmd, "requireSignedOffBy prevents option " + PUSH_OPTION_SKIP_VALIDATION);
+ return;
}
+
+ Optional<AuthException> err =
+ checkRefPermission(permissions.ref(branch.get()), RefPermission.SKIP_VALIDATION);
+ if (err.isPresent()) {
+ rejectProhibited(cmd, err.get());
+ return;
+ }
+ if (!Iterables.isEmpty(rejectCommits)) {
+ reject(cmd, "reject-commits prevents " + PUSH_OPTION_SKIP_VALIDATION);
+ }
+ logger.atFine().log("Short-circuiting new commit validation");
return;
}
- boolean missingFullName = Strings.isNullOrEmpty(user.getAccount().getFullName());
+ BranchCommitValidator validator = commitValidatorFactory.create(projectState, branch, user);
RevWalk walk = receivePack.getRevWalk();
walk.reset();
walk.sort(RevSort.NONE);
@@ -2847,79 +3018,35 @@
int n = 0;
for (RevCommit c; (c = walk.next()) != null; ) {
if (++n > limit) {
- logDebug("Number of new commits exceeds limit of %d", limit);
- addMessage(
+ logger.atFine().log("Number of new commits exceeds limit of %d", limit);
+ reject(
+ cmd,
String.format(
- "Cannot push more than %d commits to %s without %s option "
- + "(see %sDocumentation/user-upload.html#skip_validation for details)",
- limit, branch.get(), PUSH_OPTION_SKIP_VALIDATION, canonicalWebUrl));
- reject(cmd, "too many commits");
+ "more than %d commits, and %s not set", limit, PUSH_OPTION_SKIP_VALIDATION));
return;
}
if (existing.keySet().contains(c)) {
continue;
- } else if (!validCommit(walk, branch, cmd, c, null)) {
+ }
+
+ if (!validator.validCommit(
+ walk.getObjectReader(), cmd, c, false, messages, rejectCommits, null)) {
break;
}
-
- if (missingFullName && user.hasEmailAddress(c.getCommitterIdent().getEmailAddress())) {
- logDebug("Will update full name of caller");
- setFullNameTo = c.getCommitterIdent().getName();
- missingFullName = false;
- }
}
- logDebug("Validated %d new commits", n);
+ logger.atFine().log("Validated %d new commits", n);
} catch (IOException err) {
cmd.setResult(REJECTED_MISSING_OBJECT);
- logError("Invalid pack upload; one or more objects weren't sent", err);
+ logger.atSevere().withCause(err).log("Invalid pack upload; one or more objects weren't sent");
}
}
- private boolean validCommit(
- RevWalk rw, Branch.NameKey branch, ReceiveCommand cmd, ObjectId id, @Nullable Change change)
- throws IOException {
- PermissionBackend.ForRef perm = permissions.ref(branch.get());
-
- ValidCommitKey key = new AutoValue_ReceiveCommits_ValidCommitKey(id.copy(), branch);
- if (validCommits.contains(key)) {
- return true;
- }
-
- RevCommit c = rw.parseCommit(id);
- rw.parseBody(c);
-
- try (CommitReceivedEvent receiveEvent =
- new CommitReceivedEvent(cmd, project, branch.get(), rw.getObjectReader(), c, user)) {
- boolean isMerged =
- magicBranch != null
- && cmd.getRefName().equals(magicBranch.cmd.getRefName())
- && magicBranch.merged;
- CommitValidators validators =
- isMerged
- ? commitValidatorsFactory.forMergedCommits(
- project.getNameKey(), perm, user.asIdentifiedUser())
- : commitValidatorsFactory.forReceiveCommits(
- perm, branch, user.asIdentifiedUser(), sshInfo, repo, rw, change);
- messages.addAll(validators.validate(receiveEvent));
- } catch (CommitValidationException e) {
- logDebug("Commit validation failed on %s", c.name());
- messages.addAll(e.getMessages());
- reject(cmd, e.getMessage());
- return false;
- }
- validCommits.add(key);
- return true;
- }
-
- private void autoCloseChanges(ReceiveCommand cmd) {
- logDebug("Starting auto-closing of changes");
+ private void autoCloseChanges(ReceiveCommand cmd, Task progress) {
+ logger.atFine().log("Starting auto-closing of changes");
String refName = cmd.getRefName();
- checkState(
- !MagicBranch.isMagicBranch(refName),
- "shouldn't be auto-closing changes on magic branch %s",
- refName);
+
// TODO(dborowitz): Combine this BatchUpdate with the main one in
- // insertChangesAndPatchSets.
+ // handleRegularCommands
try {
retryHelper.execute(
updateFactory -> {
@@ -2929,7 +3056,6 @@
ObjectReader reader = ins.newReader();
RevWalk rw = new RevWalk(reader)) {
bu.setRepository(repo, rw, ins).updateChangesInParallel();
- bu.setRequestId(receiveId);
// TODO(dborowitz): Teach BatchUpdate to ignore missing changes.
RevCommit newTip = rw.parseCommit(cmd.getNewId());
@@ -2983,8 +3109,8 @@
for (ReplaceRequest req : replaceAndClose) {
Change.Id id = req.notes.getChangeId();
- if (!req.validate(true)) {
- logDebug("Not closing %s because validation failed", id);
+ if (!req.validateNewPatchSetForAutoClose()) {
+ logger.atFine().log("Not closing %s because validation failed", id);
continue;
}
req.addOps(bu, null);
@@ -2993,15 +3119,15 @@
mergedByPushOpFactory
.create(requestScopePropagator, req.psId, refName)
.setPatchSetProvider(req.replaceOp::getPatchSet));
- bu.addOp(id, new ChangeProgressOp(closeProgress));
+ bu.addOp(id, new ChangeProgressOp(progress));
}
- logDebug(
+ logger.atFine().log(
"Auto-closing %s changes with existing patch sets and %s with new patch sets",
existingPatchSets, newPatchSets);
bu.execute();
} catch (IOException | OrmException | PermissionBackendException e) {
- logError("Failed to auto-close changes", e);
+ logger.atSevere().withCause(e).log("Failed to auto-close changes");
}
return null;
},
@@ -3011,9 +3137,9 @@
.timeout(retryHelper.getDefaultTimeout(ActionType.CHANGE_UPDATE).multipliedBy(5))
.build());
} catch (RestApiException e) {
- logError("Can't insert patchset", e);
+ logger.atSevere().withCause(e).log("Can't insert patchset");
} catch (UpdateException e) {
- logError("Failed to auto-close changes", e);
+ logger.atSevere().withCause(e).log("Failed to auto-close changes");
}
}
@@ -3035,31 +3161,6 @@
}
}
- private void updateAccountInfo() {
- if (setFullNameTo == null) {
- return;
- }
- logDebug("Updating full name of caller");
- try {
- Optional<AccountState> accountState =
- accountsUpdateProvider
- .get()
- .update(
- "Set Full Name on Receive Commits",
- user.getAccountId(),
- (a, u) -> {
- if (Strings.isNullOrEmpty(a.getAccount().getFullName())) {
- u.setFullName(setFullNameTo);
- }
- });
- accountState
- .map(AccountState::getAccount)
- .ifPresent(a -> user.getAccount().setFullName(a.getFullName()));
- } catch (OrmException | IOException | ConfigInvalidException e) {
- logWarn("Failed to update full name of caller", e);
- }
- }
-
private Map<Change.Key, ChangeNotes> openChangesByKeyByBranch(Branch.NameKey branch)
throws OrmException {
Map<Change.Key, ChangeNotes> r = new HashMap<>();
@@ -3080,11 +3181,8 @@
return allRefsWatcher.getAllRefs();
}
- private void reject(@Nullable ReceiveCommand cmd, String why) {
- if (cmd != null) {
- cmd.setResult(REJECTED_OTHER_REASON, why);
- commandProgress.update(1);
- }
+ private static void reject(ReceiveCommand cmd, String why) {
+ cmd.setResult(REJECTED_OTHER_REASON, why);
}
private static boolean isHead(ReceiveCommand cmd) {
@@ -3094,46 +3192,4 @@
private static boolean isConfig(ReceiveCommand cmd) {
return cmd.getRefName().equals(RefNames.REFS_CONFIG);
}
-
- private void logDebug(String msg) {
- logger.atFine().log(receiveId + msg);
- }
-
- private void logDebug(String msg, @Nullable Object arg) {
- logger.atFine().log(receiveId + msg, arg);
- }
-
- private void logDebug(String msg, @Nullable Object arg1, @Nullable Object arg2) {
- logger.atFine().log(receiveId + msg, arg1, arg2);
- }
-
- private void logDebug(
- String msg, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3) {
- logger.atFine().log(receiveId + msg, arg1, arg2, arg3);
- }
-
- private void logDebug(
- String msg,
- @Nullable Object arg1,
- @Nullable Object arg2,
- @Nullable Object arg3,
- @Nullable Object arg4) {
- logger.atFine().log(receiveId + msg, arg1, arg2, arg3, arg4);
- }
-
- private void logWarn(String msg, Throwable t) {
- logger.atWarning().withCause(t).log("%s%s", receiveId, msg);
- }
-
- private void logWarn(String msg) {
- logWarn(msg, null);
- }
-
- private void logError(String msg, Throwable t) {
- logger.atSevere().withCause(t).log("%s%s", receiveId, msg);
- }
-
- private void logError(String msg) {
- logError(msg, null);
- }
}
diff --git a/java/com/google/gerrit/server/git/validators/AccountValidator.java b/java/com/google/gerrit/server/git/validators/AccountValidator.java
index 5462631..e9fe562 100644
--- a/java/com/google/gerrit/server/git/validators/AccountValidator.java
+++ b/java/com/google/gerrit/server/git/validators/AccountValidator.java
@@ -22,6 +22,7 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountConfig;
import com.google.gerrit.server.account.AccountProperties;
+import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
import com.google.inject.Inject;
@@ -38,21 +39,30 @@
public class AccountValidator {
private final Provider<IdentifiedUser> self;
+ private final AllUsersName allUsersName;
private final OutgoingEmailValidator emailValidator;
@Inject
- public AccountValidator(Provider<IdentifiedUser> self, OutgoingEmailValidator emailValidator) {
+ public AccountValidator(
+ Provider<IdentifiedUser> self,
+ AllUsersName allUsersName,
+ OutgoingEmailValidator emailValidator) {
this.self = self;
+ this.allUsersName = allUsersName;
this.emailValidator = emailValidator;
}
public List<String> validate(
- Account.Id accountId, Repository repo, RevWalk rw, @Nullable ObjectId oldId, ObjectId newId)
+ Account.Id accountId,
+ Repository allUsersRepo,
+ RevWalk rw,
+ @Nullable ObjectId oldId,
+ ObjectId newId)
throws IOException {
Optional<Account> oldAccount = Optional.empty();
if (oldId != null && !ObjectId.zeroId().equals(oldId)) {
try {
- oldAccount = loadAccount(accountId, repo, rw, oldId, null);
+ oldAccount = loadAccount(accountId, allUsersRepo, rw, oldId, null);
} catch (ConfigInvalidException e) {
// ignore, maybe the new commit is repairing it now
}
@@ -61,7 +71,7 @@
List<String> messages = new ArrayList<>();
Optional<Account> newAccount;
try {
- newAccount = loadAccount(accountId, repo, rw, newId, messages);
+ newAccount = loadAccount(accountId, allUsersRepo, rw, newId, messages);
} catch (ConfigInvalidException e) {
return ImmutableList.of(
String.format(
@@ -94,14 +104,14 @@
private Optional<Account> loadAccount(
Account.Id accountId,
- Repository repo,
+ Repository allUsersRepo,
RevWalk rw,
ObjectId commit,
@Nullable List<String> messages)
throws IOException, ConfigInvalidException {
rw.reset();
- AccountConfig accountConfig = new AccountConfig(accountId, repo);
- accountConfig.load(rw, commit);
+ AccountConfig accountConfig = new AccountConfig(accountId, allUsersName, allUsersRepo);
+ accountConfig.load(allUsersName, rw, commit);
if (messages != null) {
messages.addAll(
accountConfig
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidators.java b/java/com/google/gerrit/server/git/validators/CommitValidators.java
index 1cf71c0..7930fe8 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -35,9 +35,7 @@
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Branch.NameKey;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.externalids.ExternalIdsConsistencyChecker;
@@ -46,11 +44,9 @@
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.events.CommitReceivedEvent;
-import com.google.gerrit.server.git.BanCommit;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.permissions.PermissionBackend.ForRef;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.ProjectCache;
@@ -79,6 +75,9 @@
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.SystemReader;
+/**
+ * Represents a list of CommitValidationListeners to run for a push to one branch of one project.
+ */
public class CommitValidators {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -124,15 +123,15 @@
}
public CommitValidators forReceiveCommits(
- PermissionBackend.ForRef perm,
+ PermissionBackend.ForProject forProject,
Branch.NameKey branch,
IdentifiedUser user,
SshInfo sshInfo,
- Repository repo,
+ NoteMap rejectCommits,
RevWalk rw,
@Nullable Change change)
throws IOException {
- NoteMap rejectCommits = BanCommit.loadRejectCommitsMap(repo, rw);
+ PermissionBackend.ForRef perm = forProject.ref(branch.get());
ProjectState projectState = projectCache.checkedGet(branch.getParentKey());
return new CommitValidators(
ImmutableList.of(
@@ -158,13 +157,14 @@
}
public CommitValidators forGerritCommits(
- ForRef perm,
+ PermissionBackend.ForProject forProject,
NameKey branch,
IdentifiedUser user,
SshInfo sshInfo,
RevWalk rw,
@Nullable Change change)
throws IOException {
+ PermissionBackend.ForRef perm = forProject.ref(branch.get());
ProjectState projectState = projectCache.checkedGet(branch.getParentKey());
return new CommitValidators(
ImmutableList.of(
@@ -188,7 +188,7 @@
}
public CommitValidators forMergedCommits(
- Project.NameKey project, PermissionBackend.ForRef perm, IdentifiedUser user)
+ PermissionBackend.ForProject forProject, Branch.NameKey branch, IdentifiedUser user)
throws IOException {
// Generally only include validators that are based on permissions of the
// user creating a change for a merged commit; generally exclude
@@ -203,10 +203,11 @@
// discuss what to do about it.
// - Plugin validators may do things like require certain commit message
// formats, so we play it safe and exclude them.
+ PermissionBackend.ForRef perm = forProject.ref(branch.get());
return new CommitValidators(
ImmutableList.of(
new UploadMergesPermissionValidator(perm),
- new ProjectStateValidationListener(projectCache.checkedGet(project)),
+ new ProjectStateValidationListener(projectCache.checkedGet(branch.getParentKey())),
new AuthorUploaderValidator(user, perm, canonicalWebUrl),
new CommitterUploaderValidator(user, perm, canonicalWebUrl)));
}
@@ -237,18 +238,17 @@
public static class ChangeIdValidator implements CommitValidationListener {
private static final String CHANGE_ID_PREFIX = FooterConstants.CHANGE_ID.getName() + ":";
- private static final String MISSING_CHANGE_ID_MSG =
- "[%s] missing Change-Id in commit message footer";
+ private static final String MISSING_CHANGE_ID_MSG = "missing Change-Id in message footer";
private static final String MISSING_SUBJECT_MSG =
- "[%s] missing subject; Change-Id must be in commit message footer";
+ "missing subject; Change-Id must be in message footer";
private static final String MULTIPLE_CHANGE_ID_MSG =
- "[%s] multiple Change-Id lines in commit message footer";
+ "multiple Change-Id lines in message footer";
private static final String INVALID_CHANGE_ID_MSG =
- "[%s] invalid Change-Id line format in commit message footer";
+ "invalid Change-Id line format in message footer";
@VisibleForTesting
public static final String CHANGE_ID_MISMATCH_MSG =
- "[%s] Change-Id in commit message footer does not match Change-Id of target change";
+ "Change-Id in message footer does not match Change-Id of target change";
private static final Pattern CHANGE_ID = Pattern.compile(CHANGE_ID_PATTERN);
@@ -283,35 +283,29 @@
RevCommit commit = receiveEvent.commit;
List<CommitValidationMessage> messages = new ArrayList<>();
List<String> idList = commit.getFooterLines(FooterConstants.CHANGE_ID);
- String sha1 = commit.abbreviate(RevId.ABBREV_LEN).name();
if (idList.isEmpty()) {
String shortMsg = commit.getShortMessage();
if (shortMsg.startsWith(CHANGE_ID_PREFIX)
&& CHANGE_ID.matcher(shortMsg.substring(CHANGE_ID_PREFIX.length()).trim()).matches()) {
- String errMsg = String.format(MISSING_SUBJECT_MSG, sha1);
- throw new CommitValidationException(errMsg);
+ throw new CommitValidationException(MISSING_SUBJECT_MSG);
}
if (projectState.is(BooleanProjectConfig.REQUIRE_CHANGE_ID)) {
- String errMsg = String.format(MISSING_CHANGE_ID_MSG, sha1);
- messages.add(getMissingChangeIdErrorMsg(errMsg, commit));
- throw new CommitValidationException(errMsg, messages);
+ messages.add(getMissingChangeIdErrorMsg(MISSING_CHANGE_ID_MSG, commit));
+ throw new CommitValidationException(MISSING_CHANGE_ID_MSG, messages);
}
} else if (idList.size() > 1) {
- String errMsg = String.format(MULTIPLE_CHANGE_ID_MSG, sha1);
- throw new CommitValidationException(errMsg, messages);
+ throw new CommitValidationException(MULTIPLE_CHANGE_ID_MSG, messages);
} else {
String v = idList.get(idList.size() - 1).trim();
// Reject Change-Ids with wrong format and invalid placeholder ID from
// Egit (I0000000000000000000000000000000000000000).
if (!CHANGE_ID.matcher(v).matches() || v.matches("^I00*$")) {
- String errMsg = String.format(INVALID_CHANGE_ID_MSG, sha1);
- messages.add(getMissingChangeIdErrorMsg(errMsg, receiveEvent.commit));
- throw new CommitValidationException(errMsg, messages);
+ messages.add(getMissingChangeIdErrorMsg(INVALID_CHANGE_ID_MSG, receiveEvent.commit));
+ throw new CommitValidationException(INVALID_CHANGE_ID_MSG, messages);
}
if (change != null && !v.equals(change.getKey().get())) {
- String errMsg = String.format(CHANGE_ID_MISMATCH_MSG, sha1);
- throw new CommitValidationException(errMsg);
+ throw new CommitValidationException(CHANGE_ID_MISMATCH_MSG);
}
}
@@ -539,7 +533,7 @@
perm.check(RefPermission.FORGE_COMMITTER);
} catch (AuthException denied) {
throw new CommitValidationException(
- "not Signed-off-by author/committer/uploader in commit message footer");
+ "not Signed-off-by author/committer/uploader in message footer");
} catch (PermissionBackendException e) {
logger.atSevere().withCause(e).log("cannot check FORGE_COMMITTER");
throw new CommitValidationException("internal auth error");
@@ -574,8 +568,7 @@
return Collections.emptyList();
} catch (AuthException e) {
throw new CommitValidationException(
- "invalid author",
- invalidEmail(receiveEvent.commit, "author", author, user, canonicalWebUrl));
+ "invalid author", invalidEmail("author", author, user, canonicalWebUrl));
} catch (PermissionBackendException e) {
logger.atSevere().withCause(e).log("cannot check FORGE_AUTHOR");
throw new CommitValidationException("internal auth error");
@@ -608,8 +601,7 @@
return Collections.emptyList();
} catch (AuthException e) {
throw new CommitValidationException(
- "invalid committer",
- invalidEmail(receiveEvent.commit, "committer", committer, user, canonicalWebUrl));
+ "invalid committer", invalidEmail("committer", committer, user, canonicalWebUrl));
} catch (PermissionBackendException e) {
logger.atSevere().withCause(e).log("cannot check FORGE_COMMITTER");
throw new CommitValidationException("internal auth error");
@@ -829,42 +821,30 @@
}
private static CommitValidationMessage invalidEmail(
- RevCommit c,
- String type,
- PersonIdent who,
- IdentifiedUser currentUser,
- String canonicalWebUrl) {
+ String type, PersonIdent who, IdentifiedUser currentUser, String canonicalWebUrl) {
StringBuilder sb = new StringBuilder();
- sb.append("\n");
- sb.append("ERROR: In commit ").append(c.name()).append("\n");
- sb.append("ERROR: ")
- .append(type)
- .append(" email address ")
+
+ sb.append("email address ")
.append(who.getEmailAddress())
- .append("\n");
- sb.append("ERROR: does not match your user account and you have no 'forge ")
+ .append(" is not registered in your account, and you lack 'forge ")
.append(type)
.append("' permission.\n");
- sb.append("ERROR:\n");
+
if (currentUser.getEmailAddresses().isEmpty()) {
- sb.append("ERROR: You have not registered any email addresses.\n");
+ sb.append("You have not registered any email addresses.\n");
} else {
- sb.append("ERROR: The following addresses are currently registered:\n");
+ sb.append("The following addresses are currently registered:\n");
for (String address : currentUser.getEmailAddresses()) {
- sb.append("ERROR: ").append(address).append("\n");
+ sb.append(" ").append(address).append("\n");
}
}
- sb.append("ERROR:\n");
+
if (canonicalWebUrl != null) {
- sb.append("ERROR: To register an email address, please visit:\n");
- sb.append("ERROR: ")
- .append(canonicalWebUrl)
- .append("#")
- .append(PageLinks.SETTINGS_CONTACT)
- .append("\n");
+ sb.append("To register an email address, visit:\n");
+ sb.append(canonicalWebUrl).append("#").append(PageLinks.SETTINGS_CONTACT).append("\n");
}
sb.append("\n");
- return new CommitValidationMessage(sb.toString(), false);
+ return new CommitValidationMessage(sb.toString(), true);
}
/**
diff --git a/java/com/google/gerrit/server/git/validators/MergeValidators.java b/java/com/google/gerrit/server/git/validators/MergeValidators.java
index 94d9996..c4df4dd 100644
--- a/java/com/google/gerrit/server/git/validators/MergeValidators.java
+++ b/java/com/google/gerrit/server/git/validators/MergeValidators.java
@@ -32,12 +32,14 @@
import com.google.gerrit.server.account.AccountProperties;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.ProjectConfigEntry;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectState;
@@ -48,6 +50,7 @@
import java.io.IOException;
import java.util.List;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -114,12 +117,17 @@
"Change contains a project configuration that changes the parent"
+ " project.\n"
+ "The change must be submitted by a Gerrit administrator.";
+ private static final String SET_BY_OWNER =
+ "Change contains a project configuration that changes the parent"
+ + " project.\n"
+ + "The change must be submitted by a Gerrit administrator or the project owner.";
private final AllProjectsName allProjectsName;
private final AllUsersName allUsersName;
private final ProjectCache projectCache;
private final PermissionBackend permissionBackend;
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
+ private final boolean allowProjectOwnersToChangeParent;
public interface Factory {
ProjectConfigValidator create();
@@ -131,12 +139,15 @@
AllUsersName allUsersName,
ProjectCache projectCache,
PermissionBackend permissionBackend,
- DynamicMap<ProjectConfigEntry> pluginConfigEntries) {
+ DynamicMap<ProjectConfigEntry> pluginConfigEntries,
+ @GerritServerConfig Config config) {
this.allProjectsName = allProjectsName;
this.allUsersName = allUsersName;
this.projectCache = projectCache;
this.permissionBackend = permissionBackend;
this.pluginConfigEntries = pluginConfigEntries;
+ this.allowProjectOwnersToChangeParent =
+ config.getBoolean("receive", "allowProjectOwnersToChangeParent", false);
}
@Override
@@ -152,7 +163,7 @@
final Project.NameKey newParent;
try {
ProjectConfig cfg = new ProjectConfig(destProject.getNameKey());
- cfg.load(repo, commit);
+ cfg.load(destProject.getNameKey(), repo, commit);
newParent = cfg.getProject().getParent(allProjectsName);
final Project.NameKey oldParent = destProject.getProject().getParent(allProjectsName);
if (oldParent == null) {
@@ -162,13 +173,27 @@
}
} else {
if (!oldParent.equals(newParent)) {
- try {
- permissionBackend.user(caller).check(GlobalPermission.ADMINISTRATE_SERVER);
- } catch (AuthException e) {
- throw new MergeValidationException(SET_BY_ADMIN);
- } catch (PermissionBackendException e) {
- logger.atWarning().withCause(e).log("Cannot check ADMINISTRATE_SERVER");
- throw new MergeValidationException("validation unavailable");
+ if (!allowProjectOwnersToChangeParent) {
+ try {
+ permissionBackend.user(caller).check(GlobalPermission.ADMINISTRATE_SERVER);
+ } catch (AuthException e) {
+ throw new MergeValidationException(SET_BY_ADMIN);
+ } catch (PermissionBackendException e) {
+ logger.atWarning().withCause(e).log("Cannot check ADMINISTRATE_SERVER");
+ throw new MergeValidationException("validation unavailable");
+ }
+ } else {
+ try {
+ permissionBackend
+ .user(caller)
+ .project(destProject.getNameKey())
+ .check(ProjectPermission.WRITE_CONFIG);
+ } catch (AuthException e) {
+ throw new MergeValidationException(SET_BY_OWNER);
+ } catch (PermissionBackendException e) {
+ logger.atWarning().withCause(e).log("Cannot check WRITE_CONFIG");
+ throw new MergeValidationException("validation unavailable");
+ }
}
if (allUsersName.equals(destProject.getNameKey())
&& !allProjectsName.equals(newParent)) {
diff --git a/java/com/google/gerrit/server/group/db/AuditLogReader.java b/java/com/google/gerrit/server/group/db/AuditLogReader.java
index c6d1a6f..61fdb60 100644
--- a/java/com/google/gerrit/server/group/db/AuditLogReader.java
+++ b/java/com/google/gerrit/server/group/db/AuditLogReader.java
@@ -24,6 +24,7 @@
import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerId;
import com.google.gerrit.server.notedb.NoteDbUtil;
import com.google.inject.Inject;
@@ -49,10 +50,12 @@
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final String serverId;
+ private final AllUsersName allUsersName;
@Inject
- public AuditLogReader(@GerritServerId String serverId) {
+ public AuditLogReader(@GerritServerId String serverId, AllUsersName allUsersName) {
this.serverId = serverId;
+ this.allUsersName = allUsersName;
}
// Having separate methods for reading the two types of audit records mirrors the split in
@@ -60,8 +63,8 @@
// revisit this, e.g. to do only a single walk, or even change the record types.
public ImmutableList<AccountGroupMemberAudit> getMembersAudit(
- Repository repo, AccountGroup.UUID uuid) throws IOException, ConfigInvalidException {
- return getMembersAudit(getGroupId(repo, uuid), parseCommits(repo, uuid));
+ Repository allUsersRepo, AccountGroup.UUID uuid) throws IOException, ConfigInvalidException {
+ return getMembersAudit(getGroupId(allUsersRepo, uuid), parseCommits(allUsersRepo, uuid));
}
private ImmutableList<AccountGroupMemberAudit> getMembersAudit(
@@ -211,10 +214,13 @@
}
}
- private AccountGroup.Id getGroupId(Repository repo, AccountGroup.UUID uuid)
+ private AccountGroup.Id getGroupId(Repository allUsersRepo, AccountGroup.UUID uuid)
throws ConfigInvalidException, IOException {
// TODO(dborowitz): This re-walks all commits just to find createdOn, which we don't need.
- return GroupConfig.loadForGroup(repo, uuid).getLoadedGroup().get().getId();
+ return GroupConfig.loadForGroup(allUsersName, allUsersRepo, uuid)
+ .getLoadedGroup()
+ .get()
+ .getId();
}
@AutoValue
diff --git a/java/com/google/gerrit/server/group/db/GroupConfig.java b/java/com/google/gerrit/server/group/db/GroupConfig.java
index 62f87c6..2fae4cc 100644
--- a/java/com/google/gerrit/server/group/db/GroupConfig.java
+++ b/java/com/google/gerrit/server/group/db/GroupConfig.java
@@ -27,6 +27,7 @@
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.git.meta.VersionedMetaData;
@@ -51,9 +52,10 @@
* A representation of a group in NoteDb.
*
* <p>Groups in NoteDb can be created by following the descriptions of {@link
- * #createForNewGroup(Repository, InternalGroupCreation)}. For reading groups from NoteDb or
- * updating them, refer to {@link #loadForGroup(Repository, AccountGroup.UUID)} or {@link
- * #loadForGroupSnapshot(Repository, AccountGroup.UUID, ObjectId)}.
+ * #createForNewGroup(Project.NameKey, Repository, InternalGroupCreation)}. For reading groups from
+ * NoteDb or updating them, refer to {@link #loadForGroup(Project.NameKey, Repository,
+ * AccountGroup.UUID)} or {@link #loadForGroupSnapshot(Project.NameKey, Repository,
+ * AccountGroup.UUID, ObjectId)}.
*
* <p><strong>Note: </strong>Any modification (group creation or update) only becomes permanent (and
* hence written to NoteDb) if {@link #commit(MetaDataUpdate)} is called.
@@ -100,6 +102,7 @@
* <p><strong>Note: </strong>The returned {@code GroupConfig} has to be committed via {@link
* #commit(MetaDataUpdate)} in order to create the group for real.
*
+ * @param projectName the name of the project which holds the NoteDb commits for groups
* @param repository the repository which holds the NoteDb commits for groups
* @param groupCreation an {@code InternalGroupCreation} specifying all properties which are
* required for a new group
@@ -110,10 +113,10 @@
* @throws OrmDuplicateKeyException if a group with the same UUID already exists
*/
public static GroupConfig createForNewGroup(
- Repository repository, InternalGroupCreation groupCreation)
+ Project.NameKey projectName, Repository repository, InternalGroupCreation groupCreation)
throws IOException, ConfigInvalidException, OrmDuplicateKeyException {
GroupConfig groupConfig = new GroupConfig(groupCreation.getGroupUUID());
- groupConfig.load(repository);
+ groupConfig.load(projectName, repository);
groupConfig.setGroupCreation(groupCreation);
return groupConfig;
}
@@ -131,27 +134,30 @@
* {@code InternalGroupUpdate} via {@link #setGroupUpdate(InternalGroupUpdate, AuditLogFormatter)}
* and committing the {@code GroupConfig} via {@link #commit(MetaDataUpdate)}.
*
+ * @param projectName the name of the project which holds the NoteDb commits for groups
* @param repository the repository which holds the NoteDb commits for groups
* @param groupUuid the UUID of the group
* @return a {@code GroupConfig} for the group with the specified UUID
* @throws IOException if the repository can't be accessed for some reason
* @throws ConfigInvalidException if the group exists but can't be read due to an invalid format
*/
- public static GroupConfig loadForGroup(Repository repository, AccountGroup.UUID groupUuid)
+ public static GroupConfig loadForGroup(
+ Project.NameKey projectName, Repository repository, AccountGroup.UUID groupUuid)
throws IOException, ConfigInvalidException {
GroupConfig groupConfig = new GroupConfig(groupUuid);
- groupConfig.load(repository);
+ groupConfig.load(projectName, repository);
return groupConfig;
}
/**
* Creates a {@code GroupConfig} for an existing group at a specific revision of the repository.
*
- * <p>This method behaves nearly the same as {@link #loadForGroup(Repository, AccountGroup.UUID)}.
- * The only difference is that {@link #loadForGroup(Repository, AccountGroup.UUID)} loads the
- * group from the current state of the repository whereas this method loads the group at a
- * specific (maybe past) revision.
+ * <p>This method behaves nearly the same as {@link #loadForGroup(Project.NameKey, Repository,
+ * AccountGroup.UUID)}. The only difference is that {@link #loadForGroup(Project.NameKey,
+ * Repository, AccountGroup.UUID)} loads the group from the current state of the repository
+ * whereas this method loads the group at a specific (maybe past) revision.
*
+ * @param projectName the name of the project which holds the NoteDb commits for groups
* @param repository the repository which holds the NoteDb commits for groups
* @param groupUuid the UUID of the group
* @param commitId the revision of the repository at which the group should be loaded
@@ -160,10 +166,13 @@
* @throws ConfigInvalidException if the group exists but can't be read due to an invalid format
*/
public static GroupConfig loadForGroupSnapshot(
- Repository repository, AccountGroup.UUID groupUuid, ObjectId commitId)
+ Project.NameKey projectName,
+ Repository repository,
+ AccountGroup.UUID groupUuid,
+ ObjectId commitId)
throws IOException, ConfigInvalidException {
GroupConfig groupConfig = new GroupConfig(groupUuid);
- groupConfig.load(repository, commitId);
+ groupConfig.load(projectName, repository, commitId);
return groupConfig;
}
diff --git a/java/com/google/gerrit/server/group/db/GroupNameNotes.java b/java/com/google/gerrit/server/group/db/GroupNameNotes.java
index 1b74241..440bc42 100644
--- a/java/com/google/gerrit/server/group/db/GroupNameNotes.java
+++ b/java/com/google/gerrit/server/group/db/GroupNameNotes.java
@@ -25,10 +25,12 @@
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multiset;
+import com.google.common.flogger.FluentLogger;
import com.google.common.hash.Hashing;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.git.meta.VersionedMetaData;
import com.google.gwtorm.server.OrmDuplicateKeyException;
@@ -62,12 +64,12 @@
* map of name/UUID pairs and manage it with this class.
*
* <p>To claim the name for a new group, create an instance of {@code GroupNameNotes} via {@link
- * #forNewGroup(Repository, AccountGroup.UUID, AccountGroup.NameKey)} and call {@link
- * #commit(com.google.gerrit.server.git.meta.MetaDataUpdate) commit(MetaDataUpdate)} on it. For
- * renaming, call {@link #forRename(Repository, AccountGroup.UUID, AccountGroup.NameKey,
- * AccountGroup.NameKey)} and also commit the returned {@code GroupNameNotes}. Both times, the
- * creation of the {@code GroupNameNotes} will fail if the (new) name is already used. Committing
- * the {@code GroupNameNotes} is necessary to make the adjustments for real.
+ * #forNewGroup(Project.NameKey, Repository, AccountGroup.UUID, AccountGroup.NameKey)} and call
+ * {@link #commit(com.google.gerrit.server.git.meta.MetaDataUpdate) commit(MetaDataUpdate)} on it.
+ * For renaming, call {@link #forRename(Project.NameKey, Repository, AccountGroup.UUID,
+ * AccountGroup.NameKey, AccountGroup.NameKey)} and also commit the returned {@code GroupNameNotes}.
+ * Both times, the creation of the {@code GroupNameNotes} will fail if the (new) name is already
+ * used. Committing the {@code GroupNameNotes} is necessary to make the adjustments for real.
*
* <p>The map has an additional benefit: We can quickly iterate over all group name/UUID pairs
* without having to load all groups completely (which is costly).
@@ -87,6 +89,8 @@
* </ul>
*/
public class GroupNameNotes extends VersionedMetaData {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
private static final String SECTION_NAME = "group";
private static final String UUID_PARAM = "uuid";
private static final String NAME_PARAM = "name";
@@ -101,6 +105,7 @@
* via {@link #commit(com.google.gerrit.server.git.meta.MetaDataUpdate) commit(MetaDataUpdate)} in
* order to claim the new name and free up the old one.
*
+ * @param projectName the name of the project which holds the commits of the notes
* @param repository the repository which holds the commits of the notes
* @param groupUuid the UUID of the group which is renamed
* @param oldName the current name of the group
@@ -112,6 +117,7 @@
* @throws OrmDuplicateKeyException if a group with the new name already exists
*/
public static GroupNameNotes forRename(
+ Project.NameKey projectName,
Repository repository,
AccountGroup.UUID groupUuid,
AccountGroup.NameKey oldName,
@@ -121,7 +127,7 @@
checkNotNull(newName);
GroupNameNotes groupNameNotes = new GroupNameNotes(groupUuid, oldName, newName);
- groupNameNotes.load(repository);
+ groupNameNotes.load(projectName, repository);
groupNameNotes.ensureNewNameIsNotUsed();
return groupNameNotes;
}
@@ -133,6 +139,7 @@
* via {@link #commit(com.google.gerrit.server.git.meta.MetaDataUpdate) commit(MetaDataUpdate)} in
* order to claim the new name.
*
+ * @param projectName the name of the project which holds the commits of the notes
* @param repository the repository which holds the commits of the notes
* @param groupUuid the UUID of the new group
* @param groupName the name of the new group
@@ -142,12 +149,15 @@
* @throws OrmDuplicateKeyException if a group with the new name already exists
*/
public static GroupNameNotes forNewGroup(
- Repository repository, AccountGroup.UUID groupUuid, AccountGroup.NameKey groupName)
+ Project.NameKey projectName,
+ Repository repository,
+ AccountGroup.UUID groupUuid,
+ AccountGroup.NameKey groupName)
throws IOException, ConfigInvalidException, OrmDuplicateKeyException {
checkNotNull(groupName);
GroupNameNotes groupNameNotes = new GroupNameNotes(groupUuid, null, groupName);
- groupNameNotes.load(repository);
+ groupNameNotes.load(projectName, repository);
groupNameNotes.ensureNewNameIsNotUsed();
return groupNameNotes;
}
@@ -323,6 +333,8 @@
protected void onLoad() throws IOException, ConfigInvalidException {
nameConflicting = false;
+ logger.atFine().log("Reading group notes");
+
if (revision != null) {
NoteMap noteMap = NoteMap.read(reader, revision);
if (newGroupName.isPresent()) {
@@ -365,6 +377,8 @@
return false;
}
+ logger.atFine().log("Updating group notes");
+
NoteMap noteMap = revision == null ? NoteMap.newEmptyMap() : NoteMap.read(reader, revision);
if (oldGroupName.isPresent()) {
removeNote(noteMap, oldGroupName.get(), inserter);
diff --git a/java/com/google/gerrit/server/group/db/Groups.java b/java/com/google/gerrit/server/group/db/Groups.java
index 46fa998..f2289d4 100644
--- a/java/com/google/gerrit/server/group/db/Groups.java
+++ b/java/com/google/gerrit/server/group/db/Groups.java
@@ -70,14 +70,14 @@
public Optional<InternalGroup> getGroup(AccountGroup.UUID groupUuid)
throws IOException, ConfigInvalidException {
try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
- return getGroupFromNoteDb(allUsersRepo, groupUuid);
+ return getGroupFromNoteDb(allUsersName, allUsersRepo, groupUuid);
}
}
private static Optional<InternalGroup> getGroupFromNoteDb(
- Repository allUsersRepository, AccountGroup.UUID groupUuid)
+ AllUsersName allUsersName, Repository allUsersRepository, AccountGroup.UUID groupUuid)
throws IOException, ConfigInvalidException {
- GroupConfig groupConfig = GroupConfig.loadForGroup(allUsersRepository, groupUuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(allUsersName, allUsersRepository, groupUuid);
Optional<InternalGroup> loadedGroup = groupConfig.getLoadedGroup();
if (loadedGroup.isPresent()) {
// Check consistency with group name notes.
@@ -110,16 +110,18 @@
*/
public Stream<AccountGroup.UUID> getExternalGroups() throws IOException, ConfigInvalidException {
try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
- return getExternalGroupsFromNoteDb(allUsersRepo);
+ return getExternalGroupsFromNoteDb(allUsersName, allUsersRepo);
}
}
- private static Stream<AccountGroup.UUID> getExternalGroupsFromNoteDb(Repository allUsersRepo)
+ private static Stream<AccountGroup.UUID> getExternalGroupsFromNoteDb(
+ AllUsersName allUsersName, Repository allUsersRepo)
throws IOException, ConfigInvalidException {
ImmutableList<GroupReference> allInternalGroups = GroupNameNotes.loadAllGroups(allUsersRepo);
ImmutableSet.Builder<AccountGroup.UUID> allSubgroups = ImmutableSet.builder();
for (GroupReference internalGroup : allInternalGroups) {
- Optional<InternalGroup> group = getGroupFromNoteDb(allUsersRepo, internalGroup.getUUID());
+ Optional<InternalGroup> group =
+ getGroupFromNoteDb(allUsersName, allUsersRepo, internalGroup.getUUID());
group.map(InternalGroup::getSubgroups).ifPresent(allSubgroups::addAll);
}
return allSubgroups
@@ -131,15 +133,16 @@
/**
* Returns the membership audit records for a given group.
*
- * @param repo All-Users repository.
+ * @param allUsersRepo All-Users repository.
* @param groupUuid the UUID of the group
* @return the audit records, in arbitrary order; empty if the group does not exist
* @throws IOException if an error occurs while reading from NoteDb
* @throws ConfigInvalidException if the group couldn't be retrieved from NoteDb
*/
- public List<AccountGroupMemberAudit> getMembersAudit(Repository repo, AccountGroup.UUID groupUuid)
+ public List<AccountGroupMemberAudit> getMembersAudit(
+ Repository allUsersRepo, AccountGroup.UUID groupUuid)
throws IOException, ConfigInvalidException {
- return auditLogReader.getMembersAudit(repo, groupUuid);
+ return auditLogReader.getMembersAudit(allUsersRepo, groupUuid);
}
/**
diff --git a/java/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyChecker.java b/java/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyChecker.java
index b5324f1..3182028 100644
--- a/java/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyChecker.java
+++ b/java/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyChecker.java
@@ -28,7 +28,9 @@
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.group.InternalGroup;
+import com.google.inject.Inject;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -53,6 +55,13 @@
public class GroupsNoteDbConsistencyChecker {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private final AllUsersName allUsersName;
+
+ @Inject
+ GroupsNoteDbConsistencyChecker(AllUsersName allUsersName) {
+ this.allUsersName = allUsersName;
+ }
+
/**
* The result of a consistency check. The UUID map is only non-null if no problems were detected.
*/
@@ -63,15 +72,15 @@
}
/** Checks for problems with the given All-Users repo. */
- public Result check(Repository repo) throws IOException {
- Result r = doCheck(repo);
+ public Result check(Repository allUsersRepo) throws IOException {
+ Result r = doCheck(allUsersRepo);
if (!r.problems.isEmpty()) {
r.uuidToGroupMap = null;
}
return r;
}
- private Result doCheck(Repository repo) throws IOException {
+ private Result doCheck(Repository allUsersRepo) throws IOException {
Result result = new Result();
result.problems = new ArrayList<>();
result.uuidToGroupMap = new HashMap<>();
@@ -79,9 +88,9 @@
BiMap<AccountGroup.UUID, String> uuidNameBiMap = HashBiMap.create();
// Get all refs in an attempt to avoid seeing half committed group updates.
- Map<String, Ref> refs = repo.getAllRefs();
- readGroups(repo, refs, result);
- readGroupNames(repo, refs, result, uuidNameBiMap);
+ Map<String, Ref> refs = allUsersRepo.getAllRefs();
+ readGroups(allUsersRepo, refs, result);
+ readGroupNames(allUsersRepo, refs, result, uuidNameBiMap);
// The sequential IDs are not keys in NoteDb, so no need to check them.
if (!result.problems.isEmpty()) {
@@ -94,7 +103,7 @@
return result;
}
- private void readGroups(Repository repo, Map<String, Ref> refs, Result result)
+ private void readGroups(Repository allUsersRepo, Map<String, Ref> refs, Result result)
throws IOException {
for (Map.Entry<String, Ref> entry : refs.entrySet()) {
if (!entry.getKey().startsWith(RefNames.REFS_GROUPS)) {
@@ -108,7 +117,8 @@
}
try {
GroupConfig cfg =
- GroupConfig.loadForGroupSnapshot(repo, uuid, entry.getValue().getObjectId());
+ GroupConfig.loadForGroupSnapshot(
+ allUsersName, allUsersRepo, uuid, entry.getValue().getObjectId());
result.uuidToGroupMap.put(uuid, cfg.getLoadedGroup().get());
} catch (ConfigInvalidException e) {
result.problems.add(error("group %s does not parse: %s", uuid, e.getMessage()));
diff --git a/java/com/google/gerrit/server/group/db/GroupsUpdate.java b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
index cd38c6a..314825b 100644
--- a/java/com/google/gerrit/server/group/db/GroupsUpdate.java
+++ b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
@@ -232,9 +232,11 @@
try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
AccountGroup.NameKey groupName = groupUpdate.getName().orElseGet(groupCreation::getNameKey);
GroupNameNotes groupNameNotes =
- GroupNameNotes.forNewGroup(allUsersRepo, groupCreation.getGroupUUID(), groupName);
+ GroupNameNotes.forNewGroup(
+ allUsersName, allUsersRepo, groupCreation.getGroupUUID(), groupName);
- GroupConfig groupConfig = GroupConfig.createForNewGroup(allUsersRepo, groupCreation);
+ GroupConfig groupConfig =
+ GroupConfig.createForNewGroup(allUsersName, allUsersRepo, groupCreation);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
commit(allUsersRepo, groupConfig, groupNameNotes);
@@ -269,7 +271,7 @@
AccountGroup.UUID groupUuid, InternalGroupUpdate groupUpdate)
throws IOException, ConfigInvalidException, OrmDuplicateKeyException, NoSuchGroupException {
try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
- GroupConfig groupConfig = GroupConfig.loadForGroup(allUsersRepo, groupUuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(allUsersName, allUsersRepo, groupUuid);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
if (!groupConfig.getLoadedGroup().isPresent()) {
throw new NoSuchGroupException(groupUuid);
@@ -280,7 +282,8 @@
if (groupUpdate.getName().isPresent()) {
AccountGroup.NameKey oldName = originalGroup.getNameKey();
AccountGroup.NameKey newName = groupUpdate.getName().get();
- groupNameNotes = GroupNameNotes.forRename(allUsersRepo, groupUuid, oldName, newName);
+ groupNameNotes =
+ GroupNameNotes.forRename(allUsersName, allUsersRepo, groupUuid, oldName, newName);
}
commit(allUsersRepo, groupConfig, groupNameNotes);
diff --git a/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java b/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
index b7bb0dd..f8c17d1 100644
--- a/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
+++ b/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.index.account;
import com.google.common.collect.ImmutableSet;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.events.AccountIndexedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
@@ -22,6 +23,8 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.io.IOException;
@@ -30,6 +33,8 @@
import java.util.Optional;
public class AccountIndexerImpl implements AccountIndexer {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
public interface Factory {
AccountIndexerImpl create(AccountIndexCollection indexes);
@@ -70,14 +75,29 @@
@Override
public void index(Account.Id id) throws IOException {
+ byIdCache.evict(id);
+ Optional<AccountState> accountState = byIdCache.get(id);
+
+ if (accountState.isPresent()) {
+ logger.atFine().log("Replace account %d in index", id.get());
+ } else {
+ logger.atFine().log("Delete account %d from index", id.get());
+ }
+
for (Index<Account.Id, AccountState> i : getWriteIndexes()) {
// Evict the cache to get an up-to-date value for sure.
- byIdCache.evict(id);
- Optional<AccountState> accountState = byIdCache.get(id);
if (accountState.isPresent()) {
- i.replace(accountState.get());
+ try (TraceTimer traceTimer =
+ TraceContext.newTimer(
+ "Replacing account %d in index version %d", id.get(), i.getSchema().getVersion())) {
+ i.replace(accountState.get());
+ }
} else {
- i.delete(id);
+ try (TraceTimer traceTimer =
+ TraceContext.newTimer(
+ "Deleteing account %d in index version %d", id.get(), i.getSchema().getVersion())) {
+ i.delete(id);
+ }
}
}
fireAccountIndexedEvent(id.get());
diff --git a/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java b/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
index 0015268..acb7236 100644
--- a/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
+++ b/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
@@ -30,7 +30,6 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
-import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -60,7 +59,7 @@
@Override
public SiteIndexer.Result indexAll(AccountIndex index) {
- ProgressMonitor progress = new TextProgressMonitor(new PrintWriter(progressOut));
+ ProgressMonitor progress = new TextProgressMonitor(newPrintWriter(progressOut));
progress.start(2);
Stopwatch sw = Stopwatch.createStarted();
List<Account.Id> ids;
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndexer.java b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
index e947e60..d0a9749 100644
--- a/java/com/google/gerrit/server/index/change/ChangeIndexer.java
+++ b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
@@ -33,6 +33,8 @@
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.index.IndexExecutor;
import com.google.gerrit.server.index.IndexUtils;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.project.NoSuchChangeException;
@@ -212,8 +214,14 @@
}
private void indexImpl(ChangeData cd) throws IOException {
+ logger.atFine().log("Replace change %d in index.", cd.getId().get());
for (Index<?, ChangeData> i : getWriteIndexes()) {
- i.replace(cd);
+ try (TraceTimer traceTimer =
+ TraceContext.newTimer(
+ "Replacing change %d in index version %d",
+ cd.getId().get(), i.getSchema().getVersion())) {
+ i.replace(cd);
+ }
}
fireChangeIndexedEvent(cd.project().get(), cd.getId().get());
}
@@ -411,13 +419,17 @@
@Override
public Void call() throws IOException {
+ logger.atFine().log("Delete change %d from index.", id.get());
// Don't bother setting a RequestContext to provide the DB.
// Implementations should not need to access the DB in order to delete a
// change ID.
for (ChangeIndex i : getWriteIndexes()) {
- i.delete(id);
+ try (TraceTimer traceTimer =
+ TraceContext.newTimer(
+ "Deleteing change %d in index version %d", id.get(), i.getSchema().getVersion())) {
+ i.delete(id);
+ }
}
- logger.atInfo().log("Deleted change %s from index.", id.get());
fireChangeDeletedFromIndexEvent(id.get());
return null;
}
diff --git a/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java b/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
index 2823c2e..3474934 100644
--- a/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
+++ b/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
@@ -33,7 +33,6 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
-import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -64,7 +63,7 @@
@Override
public SiteIndexer.Result indexAll(GroupIndex index) {
- ProgressMonitor progress = new TextProgressMonitor(new PrintWriter(progressOut));
+ ProgressMonitor progress = new TextProgressMonitor(newPrintWriter(progressOut));
progress.start(2);
Stopwatch sw = Stopwatch.createStarted();
List<AccountGroup.UUID> uuids;
diff --git a/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java b/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
index fcbdc57..d6ba253 100644
--- a/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
+++ b/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.index.group;
import com.google.common.collect.ImmutableSet;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.events.GroupIndexedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
@@ -22,6 +23,8 @@
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.group.InternalGroup;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.io.IOException;
@@ -30,6 +33,8 @@
import java.util.Optional;
public class GroupIndexerImpl implements GroupIndexer {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
public interface Factory {
GroupIndexerImpl create(GroupIndexCollection indexes);
@@ -70,14 +75,29 @@
@Override
public void index(AccountGroup.UUID uuid) throws IOException {
+ // Evict the cache to get an up-to-date value for sure.
+ groupCache.evict(uuid);
+ Optional<InternalGroup> internalGroup = groupCache.get(uuid);
+
+ if (internalGroup.isPresent()) {
+ logger.atFine().log("Replace group %s in index", uuid.get());
+ } else {
+ logger.atFine().log("Delete group %s from index", uuid.get());
+ }
+
for (Index<AccountGroup.UUID, InternalGroup> i : getWriteIndexes()) {
- // Evict the cache to get an up-to-date value for sure.
- groupCache.evict(uuid);
- Optional<InternalGroup> internalGroup = groupCache.get(uuid);
if (internalGroup.isPresent()) {
- i.replace(internalGroup.get());
+ try (TraceTimer traceTimer =
+ TraceContext.newTimer(
+ "Replacing group %s in index version %d", uuid.get(), i.getSchema().getVersion())) {
+ i.replace(internalGroup.get());
+ }
} else {
- i.delete(uuid);
+ try (TraceTimer traceTimer =
+ TraceContext.newTimer(
+ "Deleting group %s in index version %d", uuid.get(), i.getSchema().getVersion())) {
+ i.delete(uuid);
+ }
}
}
fireGroupIndexedEvent(uuid.get());
diff --git a/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java b/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java
index a79bb7a..c2a28af 100644
--- a/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java
+++ b/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.index.project;
import com.google.common.collect.ImmutableSet;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.events.ProjectIndexedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
@@ -23,6 +24,8 @@
import com.google.gerrit.index.project.ProjectIndexCollection;
import com.google.gerrit.index.project.ProjectIndexer;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.assistedinject.Assisted;
@@ -32,6 +35,8 @@
import java.util.Collections;
public class ProjectIndexerImpl implements ProjectIndexer {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
public interface Factory {
ProjectIndexerImpl create(ProjectIndexCollection indexes);
@@ -69,14 +74,26 @@
public void index(Project.NameKey nameKey) throws IOException {
ProjectState projectState = projectCache.get(nameKey);
if (projectState != null) {
+ logger.atFine().log("Replace project %s in index", nameKey.get());
ProjectData projectData = projectState.toProjectData();
for (ProjectIndex i : getWriteIndexes()) {
- i.replace(projectData);
+ try (TraceTimer traceTimer =
+ TraceContext.newTimer(
+ "Replacing project %s in index version %d",
+ nameKey.get(), i.getSchema().getVersion())) {
+ i.replace(projectData);
+ }
}
fireProjectIndexedEvent(nameKey.get());
} else {
+ logger.atFine().log("Delete project %s from index", nameKey.get());
for (ProjectIndex i : getWriteIndexes()) {
- i.delete(nameKey);
+ try (TraceTimer traceTimer =
+ TraceContext.newTimer(
+ "Deleting project %s in index version %d",
+ nameKey.get(), i.getSchema().getVersion())) {
+ i.delete(nameKey);
+ }
}
}
}
diff --git a/java/com/google/gerrit/server/logging/BUILD b/java/com/google/gerrit/server/logging/BUILD
new file mode 100644
index 0000000..d3211f0
--- /dev/null
+++ b/java/com/google/gerrit/server/logging/BUILD
@@ -0,0 +1,13 @@
+java_library(
+ name = "logging",
+ srcs = glob(
+ ["**/*.java"],
+ ),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//java/com/google/gerrit/common:annotations",
+ "//java/com/google/gerrit/common:server",
+ "//lib:guava",
+ "//lib/flogger:api",
+ ],
+)
diff --git a/java/com/google/gerrit/server/logging/LoggingContext.java b/java/com/google/gerrit/server/logging/LoggingContext.java
new file mode 100644
index 0000000..1e81c29
--- /dev/null
+++ b/java/com/google/gerrit/server/logging/LoggingContext.java
@@ -0,0 +1,123 @@
+// Copyright (C) 2018 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.logging;
+
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.flogger.backend.Tags;
+import java.util.concurrent.Callable;
+import java.util.logging.Level;
+
+/**
+ * Logging context for Flogger.
+ *
+ * <p>To configure this logging context for Flogger set the following system property (also see
+ * {@link com.google.common.flogger.backend.system.DefaultPlatform}):
+ *
+ * <ul>
+ * <li>{@code
+ * flogger.logging_context=com.google.gerrit.server.logging.LoggingContext#getInstance}.
+ * </ul>
+ */
+public class LoggingContext extends com.google.common.flogger.backend.system.LoggingContext {
+ private static final LoggingContext INSTANCE = new LoggingContext();
+
+ private static final ThreadLocal<MutableTags> tags = new ThreadLocal<>();
+ private static final ThreadLocal<Boolean> forceLogging = new ThreadLocal<>();
+
+ private LoggingContext() {}
+
+ /** This method is expected to be called via reflection (and might otherwise be unused). */
+ public static LoggingContext getInstance() {
+ return INSTANCE;
+ }
+
+ public static Runnable copy(Runnable runnable) {
+ if (runnable instanceof LoggingContextAwareRunnable) {
+ return runnable;
+ }
+ return new LoggingContextAwareRunnable(runnable);
+ }
+
+ public static <T> Callable<T> copy(Callable<T> callable) {
+ if (callable instanceof LoggingContextAwareCallable) {
+ return callable;
+ }
+ return new LoggingContextAwareCallable<>(callable);
+ }
+
+ @Override
+ public boolean shouldForceLogging(String loggerName, Level level, boolean isEnabled) {
+ return isLoggingForced();
+ }
+
+ @Override
+ public Tags getTags() {
+ MutableTags mutableTags = tags.get();
+ return mutableTags != null ? mutableTags.getTags() : Tags.empty();
+ }
+
+ public ImmutableSetMultimap<String, String> getTagsAsMap() {
+ MutableTags mutableTags = tags.get();
+ return mutableTags != null ? mutableTags.asMap() : ImmutableSetMultimap.of();
+ }
+
+ boolean addTag(String name, String value) {
+ return getMutableTags().add(name, value);
+ }
+
+ void removeTag(String name, String value) {
+ MutableTags mutableTags = getMutableTags();
+ mutableTags.remove(name, value);
+ if (mutableTags.isEmpty()) {
+ tags.remove();
+ }
+ }
+
+ void setTags(ImmutableSetMultimap<String, String> newTags) {
+ if (newTags.isEmpty()) {
+ tags.remove();
+ return;
+ }
+ getMutableTags().set(newTags);
+ }
+
+ void clearTags() {
+ tags.remove();
+ }
+
+ private MutableTags getMutableTags() {
+ MutableTags mutableTags = tags.get();
+ if (mutableTags == null) {
+ mutableTags = new MutableTags();
+ tags.set(mutableTags);
+ }
+ return mutableTags;
+ }
+
+ boolean isLoggingForced() {
+ Boolean force = forceLogging.get();
+ return force != null ? force : false;
+ }
+
+ boolean forceLogging(boolean force) {
+ Boolean oldValue = forceLogging.get();
+ if (force) {
+ forceLogging.set(true);
+ } else {
+ forceLogging.remove();
+ }
+ return oldValue != null ? oldValue : false;
+ }
+}
diff --git a/java/com/google/gerrit/server/logging/LoggingContextAwareCallable.java b/java/com/google/gerrit/server/logging/LoggingContextAwareCallable.java
new file mode 100644
index 0000000..6aff5c4
--- /dev/null
+++ b/java/com/google/gerrit/server/logging/LoggingContextAwareCallable.java
@@ -0,0 +1,66 @@
+// Copyright (C) 2018 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.logging;
+
+import com.google.common.collect.ImmutableSetMultimap;
+import java.util.concurrent.Callable;
+
+/**
+ * Wrapper for a {@link Callable} that copies the {@link LoggingContext} from the current thread to
+ * the thread that executes the callable.
+ *
+ * <p>The state of the logging context that is copied to the thread that executes the callable is
+ * fixed at the creation time of this wrapper. If the callable is submitted to an executor and is
+ * executed later this means that changes that are done to the logging context in between creating
+ * and executing the callable do not apply.
+ *
+ * <p>See {@link LoggingContextAwareRunnable} for an example.
+ *
+ * @see LoggingContextAwareRunnable
+ */
+class LoggingContextAwareCallable<T> implements Callable<T> {
+ private final Callable<T> callable;
+ private final Thread callingThread;
+ private final ImmutableSetMultimap<String, String> tags;
+ private final boolean forceLogging;
+
+ LoggingContextAwareCallable(Callable<T> callable) {
+ this.callable = callable;
+ this.callingThread = Thread.currentThread();
+ this.tags = LoggingContext.getInstance().getTagsAsMap();
+ this.forceLogging = LoggingContext.getInstance().isLoggingForced();
+ }
+
+ @Override
+ public T call() throws Exception {
+ if (callingThread.equals(Thread.currentThread())) {
+ // propagation of logging context is not needed
+ return callable.call();
+ }
+
+ // propagate logging context
+ LoggingContext loggingCtx = LoggingContext.getInstance();
+ ImmutableSetMultimap<String, String> oldTags = loggingCtx.getTagsAsMap();
+ boolean oldForceLogging = loggingCtx.isLoggingForced();
+ loggingCtx.setTags(tags);
+ loggingCtx.forceLogging(forceLogging);
+ try {
+ return callable.call();
+ } finally {
+ loggingCtx.setTags(oldTags);
+ loggingCtx.forceLogging(oldForceLogging);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/logging/LoggingContextAwareExecutorService.java b/java/com/google/gerrit/server/logging/LoggingContextAwareExecutorService.java
new file mode 100644
index 0000000..17e152e
--- /dev/null
+++ b/java/com/google/gerrit/server/logging/LoggingContextAwareExecutorService.java
@@ -0,0 +1,110 @@
+// Copyright (C) 2018 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.logging;
+
+import static java.util.stream.Collectors.toList;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * An {@link ExecutorService} that copies the {@link LoggingContext} on executing a {@link Runnable}
+ * to the executing thread.
+ */
+public class LoggingContextAwareExecutorService implements ExecutorService {
+ private final ExecutorService executorService;
+
+ public LoggingContextAwareExecutorService(ExecutorService executorService) {
+ this.executorService = executorService;
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ executorService.execute(LoggingContext.copy(command));
+ }
+
+ @Override
+ public void shutdown() {
+ executorService.shutdown();
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ return executorService.shutdownNow();
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return executorService.isShutdown();
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return executorService.isTerminated();
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ return executorService.awaitTermination(timeout, unit);
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ return executorService.submit(LoggingContext.copy(task));
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ return executorService.submit(LoggingContext.copy(task), result);
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ return executorService.submit(LoggingContext.copy(task));
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException {
+ return executorService.invokeAll(tasks.stream().map(LoggingContext::copy).collect(toList()));
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(
+ Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return executorService.invokeAll(
+ tasks.stream().map(LoggingContext::copy).collect(toList()), timeout, unit);
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException, ExecutionException {
+ return executorService.invokeAny(tasks.stream().map(LoggingContext::copy).collect(toList()));
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return executorService.invokeAny(
+ tasks.stream().map(LoggingContext::copy).collect(toList()), timeout, unit);
+ }
+}
diff --git a/java/com/google/gerrit/server/logging/LoggingContextAwareRunnable.java b/java/com/google/gerrit/server/logging/LoggingContextAwareRunnable.java
new file mode 100644
index 0000000..0bd7d00
--- /dev/null
+++ b/java/com/google/gerrit/server/logging/LoggingContextAwareRunnable.java
@@ -0,0 +1,89 @@
+// Copyright (C) 2018 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.logging;
+
+import com.google.common.collect.ImmutableSetMultimap;
+
+/**
+ * Wrapper for a {@link Runnable} that copies the {@link LoggingContext} from the current thread to
+ * the thread that executes the runnable.
+ *
+ * <p>The state of the logging context that is copied to the thread that executes the runnable is
+ * fixed at the creation time of this wrapper. If the runnable is submitted to an executor and is
+ * executed later this means that changes that are done to the logging context in between creating
+ * and executing the runnable do not apply.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * try (TraceContext traceContext = TraceContext.newTrace(true, ...)) {
+ * executor
+ * .submit(new LoggingContextAwareRunnable(
+ * () -> {
+ * // Tracing is enabled since the runnable is created within the TraceContext.
+ * // Tracing is even enabled if the executor runs the runnable only after the
+ * // TraceContext was closed.
+ *
+ * // The tag "foo=bar" is not set, since it was added to the logging context only
+ * // after this runnable was created.
+ *
+ * // do stuff
+ * }))
+ * .get();
+ * traceContext.addTag("foo", "bar");
+ * }
+ * </pre>
+ *
+ * @see LoggingContextAwareCallable
+ */
+public class LoggingContextAwareRunnable implements Runnable {
+ private final Runnable runnable;
+ private final Thread callingThread;
+ private final ImmutableSetMultimap<String, String> tags;
+ private final boolean forceLogging;
+
+ LoggingContextAwareRunnable(Runnable runnable) {
+ this.runnable = runnable;
+ this.callingThread = Thread.currentThread();
+ this.tags = LoggingContext.getInstance().getTagsAsMap();
+ this.forceLogging = LoggingContext.getInstance().isLoggingForced();
+ }
+
+ public Runnable unwrap() {
+ return runnable;
+ }
+
+ @Override
+ public void run() {
+ if (callingThread.equals(Thread.currentThread())) {
+ // propagation of logging context is not needed
+ runnable.run();
+ return;
+ }
+
+ // propagate logging context
+ LoggingContext loggingCtx = LoggingContext.getInstance();
+ ImmutableSetMultimap<String, String> oldTags = loggingCtx.getTagsAsMap();
+ boolean oldForceLogging = loggingCtx.isLoggingForced();
+ loggingCtx.setTags(tags);
+ loggingCtx.forceLogging(forceLogging);
+ try {
+ runnable.run();
+ } finally {
+ loggingCtx.setTags(oldTags);
+ loggingCtx.forceLogging(oldForceLogging);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/logging/LoggingContextAwareScheduledExecutorService.java b/java/com/google/gerrit/server/logging/LoggingContextAwareScheduledExecutorService.java
new file mode 100644
index 0000000..e17a91e
--- /dev/null
+++ b/java/com/google/gerrit/server/logging/LoggingContextAwareScheduledExecutorService.java
@@ -0,0 +1,59 @@
+// Copyright (C) 2018 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.logging;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link ScheduledExecutorService} that copies the {@link LoggingContext} on executing a {@link
+ * Runnable} to the executing thread.
+ */
+public class LoggingContextAwareScheduledExecutorService extends LoggingContextAwareExecutorService
+ implements ScheduledExecutorService {
+ private final ScheduledExecutorService scheduledExecutorService;
+
+ public LoggingContextAwareScheduledExecutorService(
+ ScheduledExecutorService scheduledExecutorService) {
+ super(scheduledExecutorService);
+ this.scheduledExecutorService = scheduledExecutorService;
+ }
+
+ @Override
+ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+ return scheduledExecutorService.schedule(LoggingContext.copy(command), delay, unit);
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+ return scheduledExecutorService.schedule(LoggingContext.copy(callable), delay, unit);
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(
+ Runnable command, long initialDelay, long period, TimeUnit unit) {
+ return scheduledExecutorService.scheduleAtFixedRate(
+ LoggingContext.copy(command), initialDelay, period, unit);
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(
+ Runnable command, long initialDelay, long delay, TimeUnit unit) {
+ return scheduledExecutorService.scheduleWithFixedDelay(
+ LoggingContext.copy(command), initialDelay, delay, unit);
+ }
+}
diff --git a/java/com/google/gerrit/server/logging/MutableTags.java b/java/com/google/gerrit/server/logging/MutableTags.java
new file mode 100644
index 0000000..a936a43
--- /dev/null
+++ b/java/com/google/gerrit/server/logging/MutableTags.java
@@ -0,0 +1,113 @@
+// Copyright (C) 2018 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.logging;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.MultimapBuilder;
+import com.google.common.collect.SetMultimap;
+import com.google.common.flogger.backend.Tags;
+
+public class MutableTags {
+ private final SetMultimap<String, String> tagMap =
+ MultimapBuilder.hashKeys().hashSetValues().build();
+ private Tags tags = Tags.empty();
+
+ public Tags getTags() {
+ return tags;
+ }
+
+ /**
+ * Adds a tag if a tag with the same name and value doesn't exist yet.
+ *
+ * @param name the name of the tag
+ * @param value the value of the tag
+ * @return {@code true} if the tag was added, {@code false} if the tag was not added because it
+ * already exists
+ */
+ public boolean add(String name, String value) {
+ checkNotNull(name, "tag name is required");
+ checkNotNull(value, "tag value is required");
+ boolean ret = tagMap.put(name, value);
+ if (ret) {
+ buildTags();
+ }
+ return ret;
+ }
+
+ /**
+ * Removes the tag with the given name and value.
+ *
+ * @param name the name of the tag
+ * @param value the value of the tag
+ */
+ public void remove(String name, String value) {
+ checkNotNull(name, "tag name is required");
+ checkNotNull(value, "tag value is required");
+ if (tagMap.remove(name, value)) {
+ buildTags();
+ }
+ }
+
+ /**
+ * Checks if the contained tag map is empty.
+ *
+ * @return {@code true} if there are no tags, otherwise {@code false}
+ */
+ public boolean isEmpty() {
+ return tagMap.isEmpty();
+ }
+
+ /** Clears all tags. */
+ public void clear() {
+ tagMap.clear();
+ tags = Tags.empty();
+ }
+
+ /**
+ * Returns the tags as Multimap.
+ *
+ * @return the tags as Multimap
+ */
+ public ImmutableSetMultimap<String, String> asMap() {
+ return ImmutableSetMultimap.copyOf(tagMap);
+ }
+
+ /**
+ * Replaces the existing tags with the provided tags.
+ *
+ * @param tags the tags that should be set.
+ */
+ void set(ImmutableSetMultimap<String, String> tags) {
+ tagMap.clear();
+ tags.forEach(tagMap::put);
+ buildTags();
+ }
+
+ private void buildTags() {
+ if (tagMap.isEmpty()) {
+ if (tags.isEmpty()) {
+ return;
+ }
+ tags = Tags.empty();
+ return;
+ }
+
+ Tags.Builder tagsBuilder = Tags.builder();
+ tagMap.forEach(tagsBuilder::addTag);
+ tags = tagsBuilder.build();
+ }
+}
diff --git a/java/com/google/gerrit/server/util/RequestId.java b/java/com/google/gerrit/server/logging/RequestId.java
similarity index 70%
rename from java/com/google/gerrit/server/util/RequestId.java
rename to java/com/google/gerrit/server/logging/RequestId.java
index 8e8db12..b0a8ad9 100644
--- a/java/com/google/gerrit/server/util/RequestId.java
+++ b/java/com/google/gerrit/server/logging/RequestId.java
@@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.util;
+package com.google.gerrit.server.logging;
+import com.google.common.base.Enums;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -36,27 +36,34 @@
MACHINE_ID = id;
}
- public static RequestId forChange(Change c) {
- return new RequestId(c.getId().toString());
+ public enum Type {
+ RECEIVE_ID,
+ SUBMISSION_ID,
+ TRACE_ID;
+
+ static boolean isId(String id) {
+ return id != null && Enums.getIfPresent(Type.class, id).isPresent();
+ }
}
- public static RequestId forProject(Project.NameKey p) {
- return new RequestId(p.toString());
+ public static boolean isSet() {
+ return LoggingContext.getInstance().getTagsAsMap().keySet().stream().anyMatch(Type::isId);
}
private final String str;
- private RequestId(String resourceId) {
+ public RequestId() {
+ this(null);
+ }
+
+ public RequestId(@Nullable String resourceId) {
Hasher h = Hashing.murmur3_128().newHasher();
h.putLong(Thread.currentThread().getId()).putUnencodedChars(MACHINE_ID);
str =
- "["
- + resourceId
- + "-"
+ (resourceId != null ? resourceId + "-" : "")
+ TimeUtil.nowTs().getTime()
+ "-"
- + h.hash().toString().substring(0, 8)
- + "]";
+ + h.hash().toString().substring(0, 8);
}
@Override
diff --git a/java/com/google/gerrit/server/logging/TraceContext.java b/java/com/google/gerrit/server/logging/TraceContext.java
new file mode 100644
index 0000000..977baa5
--- /dev/null
+++ b/java/com/google/gerrit/server/logging/TraceContext.java
@@ -0,0 +1,263 @@
+// Copyright (C) 2018 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.logging;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Strings;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * TraceContext that allows to set logging tags and enforce logging.
+ *
+ * <p>The logging tags are attached to all log entries that are triggered while the trace context is
+ * open. If force logging is enabled all logs that are triggered while the trace context is open are
+ * written to the log file regardless of the configured log level.
+ *
+ * <pre>
+ * try (TraceContext traceContext = TraceContext.open()
+ * .addTag("tag-name", "tag-value")
+ * .forceLogging()) {
+ * // This gets logged as: A log [CONTEXT forced=true tag-name="tag-value" ]
+ * // Since force logging is enabled this gets logged independently of the configured log
+ * // level.
+ * logger.atFinest().log("A log");
+ *
+ * // do stuff
+ * }
+ * </pre>
+ *
+ * <p>The logging tags and the force logging flag are stored in the {@link LoggingContext}. {@link
+ * LoggingContextAwareExecutorService}, {@link LoggingContextAwareScheduledExecutorService} and the
+ * executor in {@link com.google.gerrit.server.git.WorkQueue} ensure that the logging context is
+ * automatically copied to background threads.
+ *
+ * <p>On close of the trace context newly set tags are unset. Force logging is disabled on close if
+ * it got enabled while the trace context was open.
+ *
+ * <p>Trace contexts can be nested:
+ *
+ * <pre>
+ * // Initially there are no tags
+ * logger.atSevere().log("log without tag");
+ *
+ * // a tag can be set by opening a trace context
+ * try (TraceContext ctx = TraceContext.open().addTag("tag1", "value1")) {
+ * logger.atSevere().log("log with tag1=value1");
+ *
+ * // while a trace context is open further tags can be added.
+ * ctx.addTag("tag2", "value2")
+ * logger.atSevere().log("log with tag1=value1 and tag2=value2");
+ *
+ * // also by opening another trace context a another tag can be added
+ * try (TraceContext ctx2 = TraceContext.open().addTag("tag3", "value3")) {
+ * logger.atSevere().log("log with tag1=value1, tag2=value2 and tag3=value3");
+ *
+ * // it's possible to have the same tag name with multiple values
+ * ctx2.addTag("tag3", "value3a")
+ * logger.atSevere().log("log with tag1=value1, tag2=value2, tag3=value3 and tag3=value3a");
+ *
+ * // adding a tag with the same name and value as an existing tag has no effect
+ * try (TraceContext ctx3 = TraceContext.open().addTag("tag3", "value3a")) {
+ * logger.atSevere().log("log with tag1=value1, tag2=value2, tag3=value3 and tag3=value3a");
+ * }
+ *
+ * // closing ctx3 didn't remove tag3=value3a since it was already set before opening ctx3
+ * logger.atSevere().log("log with tag1=value1, tag2=value2, tag3=value3 and tag3=value3a");
+ * }
+ *
+ * // closing ctx2 removed tag3=value3 and tag3-value3a
+ * logger.atSevere().log("with tag1=value1 and tag2=value2");
+ * }
+ *
+ * // closing ctx1 removed tag1=value1 and tag2=value2
+ * logger.atSevere().log("log without tag");
+ * </pre>
+ */
+public class TraceContext implements AutoCloseable {
+ public static TraceContext open() {
+ return new TraceContext();
+ }
+
+ /**
+ * Opens a new trace context for request tracing.
+ *
+ * <ul>
+ * <li>sets a tag with a trace ID
+ * <li>enables force logging
+ * </ul>
+ *
+ * <p>if no trace ID is provided a new trace ID is only generated if request tracing was not
+ * started yet. If request tracing was already started the given {@code traceIdConsumer} is
+ * invoked with the existing trace ID and no new logging tag is set.
+ *
+ * <p>No-op if {@code trace} is {@code false}.
+ *
+ * @param trace whether tracing should be started
+ * @param traceId trace ID that should be used for tracing, if {@code null} a trace ID is
+ * generated
+ * @param traceIdConsumer consumer for the trace ID, should be used to return the generated trace
+ * ID to the client, not invoked if {@code trace} is {@code false}
+ * @return the trace context
+ */
+ public static TraceContext newTrace(
+ boolean trace, @Nullable String traceId, TraceIdConsumer traceIdConsumer) {
+ if (!trace) {
+ // Create an empty trace context.
+ return open();
+ }
+
+ if (!Strings.isNullOrEmpty(traceId)) {
+ traceIdConsumer.accept(RequestId.Type.TRACE_ID.name(), traceId);
+ return open().addTag(RequestId.Type.TRACE_ID, traceId).forceLogging();
+ }
+
+ Optional<String> existingTraceId =
+ LoggingContext.getInstance()
+ .getTagsAsMap()
+ .get(RequestId.Type.TRACE_ID.name())
+ .stream()
+ .findAny();
+ if (existingTraceId.isPresent()) {
+ // request tracing was already started, no need to generate a new trace ID
+ traceIdConsumer.accept(RequestId.Type.TRACE_ID.name(), existingTraceId.get());
+ return open();
+ }
+
+ RequestId newTraceId = new RequestId();
+ traceIdConsumer.accept(RequestId.Type.TRACE_ID.name(), newTraceId.toString());
+ return open().addTag(RequestId.Type.TRACE_ID, newTraceId).forceLogging();
+ }
+
+ @FunctionalInterface
+ public interface TraceIdConsumer {
+ void accept(String tagName, String traceId);
+ }
+
+ /**
+ * Opens a new timer that logs the time for an operation if request tracing is enabled.
+ *
+ * <p>If request tracing is not enabled this is a no-op.
+ *
+ * @param message the message
+ * @return the trace timer
+ */
+ public static TraceTimer newTimer(String message) {
+ return new TraceTimer(message);
+ }
+
+ /**
+ * Opens a new timer that logs the time for an operation if request tracing is enabled.
+ *
+ * <p>If request tracing is not enabled this is a no-op.
+ *
+ * @param format the message format string
+ * @param arg argument for the message
+ * @return the trace timer
+ */
+ public static TraceTimer newTimer(String format, Object arg) {
+ return new TraceTimer(format, arg);
+ }
+
+ /**
+ * Opens a new timer that logs the time for an operation if request tracing is enabled.
+ *
+ * <p>If request tracing is not enabled this is a no-op.
+ *
+ * @param format the message format string
+ * @param arg1 first argument for the message
+ * @param arg2 second argument for the message
+ * @return the trace timer
+ */
+ public static TraceTimer newTimer(String format, Object arg1, Object arg2) {
+ return new TraceTimer(format, arg1, arg2);
+ }
+
+ public static class TraceTimer implements AutoCloseable {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final Consumer<Long> logFn;
+ private final Stopwatch stopwatch;
+
+ private TraceTimer(String message) {
+ this(elapsedMs -> logger.atFine().log(message + " (%d ms)", elapsedMs));
+ }
+
+ private TraceTimer(String format, @Nullable Object arg) {
+ this(elapsedMs -> logger.atFine().log(format + " (%d ms)", arg, elapsedMs));
+ }
+
+ private TraceTimer(String format, @Nullable Object arg1, @Nullable Object arg2) {
+ this(elapsedMs -> logger.atFine().log(format + " (%d ms)", arg1, arg2, elapsedMs));
+ }
+
+ private TraceTimer(Consumer<Long> logFn) {
+ this.logFn = logFn;
+ this.stopwatch = Stopwatch.createStarted();
+ }
+
+ @Override
+ public void close() {
+ stopwatch.stop();
+ logFn.accept(stopwatch.elapsed(TimeUnit.MILLISECONDS));
+ }
+ }
+
+ // Table<TAG_NAME, TAG_VALUE, REMOVE_ON_CLOSE>
+ private final Table<String, String, Boolean> tags = HashBasedTable.create();
+
+ private boolean stopForceLoggingOnClose;
+
+ private TraceContext() {}
+
+ public TraceContext addTag(RequestId.Type requestId, Object tagValue) {
+ return addTag(checkNotNull(requestId, "request ID is required").name(), tagValue);
+ }
+
+ public TraceContext addTag(String tagName, Object tagValue) {
+ String name = checkNotNull(tagName, "tag name is required");
+ String value = checkNotNull(tagValue, "tag value is required").toString();
+ tags.put(name, value, LoggingContext.getInstance().addTag(name, value));
+ return this;
+ }
+
+ public TraceContext forceLogging() {
+ if (stopForceLoggingOnClose) {
+ return this;
+ }
+
+ stopForceLoggingOnClose = !LoggingContext.getInstance().forceLogging(true);
+ return this;
+ }
+
+ @Override
+ public void close() {
+ for (Table.Cell<String, String, Boolean> cell : tags.cellSet()) {
+ if (cell.getValue()) {
+ LoggingContext.getInstance().removeTag(cell.getRowKey(), cell.getColumnKey());
+ }
+ }
+ if (stopForceLoggingOnClose) {
+ LoggingContext.getInstance().forceLogging(false);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/mail/ListMailFilter.java b/java/com/google/gerrit/server/mail/ListMailFilter.java
index eee8c60..1549f8d 100644
--- a/java/com/google/gerrit/server/mail/ListMailFilter.java
+++ b/java/com/google/gerrit/server/mail/ListMailFilter.java
@@ -53,7 +53,8 @@
}
boolean match = mailPattern.matcher(message.from().getEmail()).find();
- if (mode == ListFilterMode.WHITELIST && !match || mode == ListFilterMode.BLACKLIST && match) {
+ if ((mode == ListFilterMode.WHITELIST && !match)
+ || (mode == ListFilterMode.BLACKLIST && match)) {
logger.atInfo().log("Mail message from %s rejected by list filter", message.from());
return false;
}
diff --git a/java/com/google/gerrit/server/mail/send/CommentSender.java b/java/com/google/gerrit/server/mail/send/CommentSender.java
index 54176e2..0baaa11c 100644
--- a/java/com/google/gerrit/server/mail/send/CommentSender.java
+++ b/java/com/google/gerrit/server/mail/send/CommentSender.java
@@ -239,7 +239,7 @@
}
}
- Collections.sort(groups, Comparator.comparing(g -> g.filename, FilenameComparator.INSTANCE));
+ groups.sort(Comparator.comparing(g -> g.filename, FilenameComparator.INSTANCE));
return groups;
}
diff --git a/java/com/google/gerrit/server/mime/MimeUtilFileTypeRegistry.java b/java/com/google/gerrit/server/mime/MimeUtilFileTypeRegistry.java
index eecf935..0e9a2b7 100644
--- a/java/com/google/gerrit/server/mime/MimeUtilFileTypeRegistry.java
+++ b/java/com/google/gerrit/server/mime/MimeUtilFileTypeRegistry.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.mime;
+import static java.util.Comparator.comparing;
+
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
@@ -22,12 +24,9 @@
import eu.medsea.mimeutil.MimeType;
import eu.medsea.mimeutil.MimeUtil2;
import java.io.InputStream;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Comparator;
import java.util.HashSet;
-import java.util.List;
import java.util.Set;
import org.eclipse.jgit.lib.Config;
@@ -115,16 +114,7 @@
return MimeUtil2.UNKNOWN_MIME_TYPE;
}
- final List<MimeType> types = new ArrayList<>(mimeTypes);
- Collections.sort(
- types,
- new Comparator<MimeType>() {
- @Override
- public int compare(MimeType a, MimeType b) {
- return getCorrectedMimeSpecificity(b) - getCorrectedMimeSpecificity(a);
- }
- });
- return types.get(0);
+ return Collections.max(mimeTypes, comparing(this::getCorrectedMimeSpecificity));
}
@Override
diff --git a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index a083a71..632f6fc 100644
--- a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -145,6 +145,7 @@
if (loaded) {
return self();
}
+
boolean read = args.migration.readChanges();
if (!read && primaryStorage == PrimaryStorage.NOTE_DB) {
throw new OrmException("NoteDb is required to read change " + changeId);
diff --git a/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java b/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
index 010c5c0..e0cc771 100644
--- a/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
@@ -17,6 +17,7 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
@@ -41,6 +42,8 @@
/** A single delta related to a specific patch-set of a change. */
public abstract class AbstractChangeUpdate {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
protected final NotesMigration migration;
protected final ChangeNoteUtil noteUtil;
protected final Account.Id accountId;
@@ -218,6 +221,11 @@
checkArgument(rw.getObjectReader().getCreatedFromInserter() == ins);
checkNotReadOnly();
+
+ logger.atFinest().log(
+ "%s for change %s of project %s in %s (NoteDb)",
+ getClass().getSimpleName(), getId(), getProjectName(), getRefName());
+
ObjectId z = ObjectId.zeroId();
CommitBuilder cb = applyImpl(rw, ins, curr);
if (cb == null) {
diff --git a/java/com/google/gerrit/server/notedb/ChangeBundle.java b/java/com/google/gerrit/server/notedb/ChangeBundle.java
index 1d3c752..0ebee1a 100644
--- a/java/com/google/gerrit/server/notedb/ChangeBundle.java
+++ b/java/com/google/gerrit/server/notedb/ChangeBundle.java
@@ -17,11 +17,16 @@
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
import static com.google.gerrit.common.TimeUtil.truncateToSecond;
import static com.google.gerrit.reviewdb.server.ReviewDbUtil.checkColumns;
import static com.google.gerrit.reviewdb.server.ReviewDbUtil.intKeyOrdering;
import static com.google.gerrit.server.notedb.ChangeBundle.Source.NOTE_DB;
import static com.google.gerrit.server.notedb.ChangeBundle.Source.REVIEW_DB;
+import static java.util.Comparator.comparing;
+import static java.util.Comparator.naturalOrder;
+import static java.util.Comparator.nullsFirst;
import static java.util.stream.Collectors.toList;
import com.google.auto.value.AutoValue;
@@ -31,7 +36,6 @@
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
-import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -43,13 +47,14 @@
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSet.Id;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CommentsUtil;
@@ -70,7 +75,6 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import java.util.TreeMap;
/**
* A bundle of all entities rooted at a single {@link Change} entity.
@@ -105,110 +109,65 @@
Source.NOTE_DB);
}
- private static Map<ChangeMessage.Key, ChangeMessage> changeMessageMap(
- Iterable<ChangeMessage> in) {
- Map<ChangeMessage.Key, ChangeMessage> out =
- new TreeMap<>(
- new Comparator<ChangeMessage.Key>() {
- @Override
- public int compare(ChangeMessage.Key a, ChangeMessage.Key b) {
- return ComparisonChain.start()
- .compare(a.getParentKey().get(), b.getParentKey().get())
- .compare(a.get(), b.get())
- .result();
- }
- });
- for (ChangeMessage cm : in) {
- out.put(cm.getKey(), cm);
- }
- return out;
+ private static ImmutableSortedMap<ChangeMessage.Key, ChangeMessage> changeMessageMap(
+ Collection<ChangeMessage> in) {
+ return in.stream()
+ .collect(
+ toImmutableSortedMap(
+ comparing((ChangeMessage.Key k) -> k.getParentKey().get())
+ .thenComparing(k -> k.get()),
+ cm -> cm.getKey(),
+ cm -> cm));
}
// Unlike the *Map comparators, which are intended to make key lists diffable,
// this comparator sorts first on timestamp, then on every other field.
- private static final Ordering<ChangeMessage> CHANGE_MESSAGE_ORDER =
- new Ordering<ChangeMessage>() {
- final Ordering<Comparable<?>> nullsFirst = Ordering.natural().nullsFirst();
-
- @Override
- public int compare(ChangeMessage a, ChangeMessage b) {
- return ComparisonChain.start()
- .compare(a.getWrittenOn(), b.getWrittenOn())
- .compare(a.getKey().getParentKey().get(), b.getKey().getParentKey().get())
- .compare(psId(a), psId(b), nullsFirst)
- .compare(a.getAuthor(), b.getAuthor(), intKeyOrdering())
- .compare(a.getMessage(), b.getMessage(), nullsFirst)
- .result();
- }
-
- private Integer psId(ChangeMessage m) {
- return m.getPatchSetId() != null ? m.getPatchSetId().get() : null;
- }
- };
+ private static final Comparator<ChangeMessage> CHANGE_MESSAGE_COMPARATOR =
+ comparing(ChangeMessage::getWrittenOn)
+ .thenComparing(m -> m.getKey().getParentKey().get())
+ .thenComparing(
+ m -> m.getPatchSetId() != null ? m.getPatchSetId().get() : null,
+ nullsFirst(naturalOrder()))
+ .thenComparing(ChangeMessage::getAuthor, intKeyOrdering())
+ .thenComparing(ChangeMessage::getMessage, nullsFirst(naturalOrder()));
private static ImmutableList<ChangeMessage> changeMessageList(Iterable<ChangeMessage> in) {
- return CHANGE_MESSAGE_ORDER.immutableSortedCopy(in);
+ return Streams.stream(in).sorted(CHANGE_MESSAGE_COMPARATOR).collect(toImmutableList());
}
- private static TreeMap<PatchSet.Id, PatchSet> patchSetMap(Iterable<PatchSet> in) {
- TreeMap<PatchSet.Id, PatchSet> out =
- new TreeMap<>(
- new Comparator<PatchSet.Id>() {
- @Override
- public int compare(PatchSet.Id a, PatchSet.Id b) {
- return patchSetIdChain(a, b).result();
- }
- });
- for (PatchSet ps : in) {
- out.put(ps.getId(), ps);
- }
- return out;
+ private static ImmutableSortedMap<Id, PatchSet> patchSetMap(Iterable<PatchSet> in) {
+ return Streams.stream(in)
+ .collect(toImmutableSortedMap(patchSetIdComparator(), PatchSet::getId, ps -> ps));
}
- private static Map<PatchSetApproval.Key, PatchSetApproval> patchSetApprovalMap(
+ private static ImmutableSortedMap<PatchSetApproval.Key, PatchSetApproval> patchSetApprovalMap(
Iterable<PatchSetApproval> in) {
- Map<PatchSetApproval.Key, PatchSetApproval> out =
- new TreeMap<>(
- new Comparator<PatchSetApproval.Key>() {
- @Override
- public int compare(PatchSetApproval.Key a, PatchSetApproval.Key b) {
- return patchSetIdChain(a.getParentKey(), b.getParentKey())
- .compare(a.getAccountId().get(), b.getAccountId().get())
- .compare(a.getLabelId(), b.getLabelId())
- .result();
- }
- });
- for (PatchSetApproval psa : in) {
- out.put(psa.getKey(), psa);
- }
- return out;
+ return Streams.stream(in)
+ .collect(
+ toImmutableSortedMap(
+ comparing(PatchSetApproval.Key::getParentKey, patchSetIdComparator())
+ .thenComparing(PatchSetApproval.Key::getAccountId, intKeyOrdering())
+ .thenComparing(PatchSetApproval.Key::getLabelId),
+ PatchSetApproval::getKey,
+ a -> a));
}
- private static Map<PatchLineComment.Key, PatchLineComment> patchLineCommentMap(
+ private static ImmutableSortedMap<PatchLineComment.Key, PatchLineComment> patchLineCommentMap(
Iterable<PatchLineComment> in) {
- Map<PatchLineComment.Key, PatchLineComment> out =
- new TreeMap<>(
- new Comparator<PatchLineComment.Key>() {
- @Override
- public int compare(PatchLineComment.Key a, PatchLineComment.Key b) {
- Patch.Key pka = a.getParentKey();
- Patch.Key pkb = b.getParentKey();
- return patchSetIdChain(pka.getParentKey(), pkb.getParentKey())
- .compare(pka.get(), pkb.get())
- .compare(a.get(), b.get())
- .result();
- }
- });
- for (PatchLineComment plc : in) {
- out.put(plc.getKey(), plc);
- }
- return out;
+ return Streams.stream(in)
+ .collect(
+ toImmutableSortedMap(
+ comparing(
+ (PatchLineComment.Key k) -> k.getParentKey().getParentKey(),
+ patchSetIdComparator())
+ .thenComparing(PatchLineComment.Key::getParentKey)
+ .thenComparing(PatchLineComment.Key::get),
+ PatchLineComment::getKey,
+ c -> c));
}
- private static ComparisonChain patchSetIdChain(PatchSet.Id a, PatchSet.Id b) {
- return ComparisonChain.start()
- .compare(a.getParentKey().get(), b.getParentKey().get())
- .compare(a.get(), b.get());
+ private static Comparator<PatchSet.Id> patchSetIdComparator() {
+ return comparing((PatchSet.Id id) -> id.getParentKey().get()).thenComparing(id -> id.get());
}
static {
@@ -436,7 +395,7 @@
excludeOrigSubj = true;
String aTopic = trimOrNull(a.getTopic());
excludeTopic =
- Objects.equals(aTopic, b.getTopic()) || "".equals(aTopic) && b.getTopic() == null;
+ Objects.equals(aTopic, b.getTopic()) || ("".equals(aTopic) && b.getTopic() == null);
aUpdated = bundleA.getLatestTimestamp();
} else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
boolean createdOnMatchesFirstPs =
@@ -454,7 +413,7 @@
excludeOrigSubj = true;
String bTopic = trimOrNull(b.getTopic());
excludeTopic =
- Objects.equals(bTopic, a.getTopic()) || a.getTopic() == null && "".equals(bTopic);
+ Objects.equals(bTopic, a.getTopic()) || (a.getTopic() == null && "".equals(bTopic));
bUpdated = bundleB.getLatestTimestamp();
}
@@ -598,9 +557,10 @@
}
if (!bs.isEmpty()) {
sb.append("Only in B:");
- for (ChangeMessage cm : CHANGE_MESSAGE_ORDER.sortedCopy(bs.values())) {
- sb.append("\n ").append(cm);
- }
+ bs.values()
+ .stream()
+ .sorted(CHANGE_MESSAGE_COMPARATOR)
+ .forEach(cm -> sb.append("\n ").append(cm));
}
diffs.add(sb.toString());
}
@@ -758,7 +718,8 @@
excludePostSubmit = a.getValue() == 0 && b.isPostSubmit();
} else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
excludeGranted =
- tb.before(psb.getCreatedOn()) && ta.equals(psa.getCreatedOn()) || tb.compareTo(ta) < 0;
+ (tb.before(psb.getCreatedOn()) && ta.equals(psa.getCreatedOn()))
+ || (tb.compareTo(ta) < 0);
excludePostSubmit = b.getValue() == 0 && a.isPostSubmit();
}
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesCache.java b/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
index f0187ed..cc316e5 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
@@ -18,6 +18,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.Cache;
import com.google.common.collect.Table;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
@@ -25,10 +26,10 @@
import com.google.gerrit.server.ReviewerByEmailSet;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.CacheSerializer;
-import com.google.gerrit.server.cache.ProtoCacheSerializers;
-import com.google.gerrit.server.cache.ProtoCacheSerializers.ObjectIdConverter;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesKeyProto;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers.ObjectIdConverter;
import com.google.gerrit.server.notedb.AbstractChangeNotes.Args;
import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
import com.google.inject.Inject;
@@ -46,6 +47,8 @@
@Singleton
public class ChangeNotesCache {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
@VisibleForTesting static final String CACHE_NAME = "change_notes";
public static Module module() {
@@ -345,6 +348,8 @@
@Override
public ChangeNotesState call() throws ConfigInvalidException, IOException {
+ logger.atFine().log(
+ "Load change notes for change %s of project %s", key.changeId(), key.project());
ChangeNotesParser parser =
new ChangeNotesParser(
key.changeId(),
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 4eeab81..cbb7020 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -79,7 +79,6 @@
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -297,9 +296,7 @@
}
result.put(a.getPatchSetId(), a);
}
- for (Collection<PatchSetApproval> v : result.asMap().values()) {
- Collections.sort((List<PatchSetApproval>) v, ChangeNotes.PSA_BY_TIME);
- }
+ result.keySet().forEach(k -> result.get(k).sort(ChangeNotes.PSA_BY_TIME));
return result;
}
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesState.java b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
index 11d6880..c51aec3 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesState.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
@@ -23,7 +23,7 @@
import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.APPROVAL_CODEC;
import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.MESSAGE_CODEC;
import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.PATCH_SET_CODEC;
-import static com.google.gerrit.server.cache.ProtoCacheSerializers.toByteString;
+import static com.google.gerrit.server.cache.serialize.ProtoCacheSerializers.toByteString;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
@@ -53,14 +53,14 @@
import com.google.gerrit.server.ReviewerByEmailSet;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.ReviewerStatusUpdate;
-import com.google.gerrit.server.cache.CacheSerializer;
-import com.google.gerrit.server.cache.ProtoCacheSerializers;
-import com.google.gerrit.server.cache.ProtoCacheSerializers.ObjectIdConverter;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ChangeColumnsProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerByEmailSetEntryProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerSetEntryProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerStatusUpdateProto;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers.ObjectIdConverter;
import com.google.gerrit.server.index.change.ChangeField.StoredSubmitRecord;
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
import com.google.gson.Gson;
diff --git a/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index bd7f8fc..b60a332 100644
--- a/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -63,9 +63,9 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.util.LabelVote;
-import com.google.gerrit.server.util.RequestId;
import com.google.gwtorm.client.IntKey;
import com.google.gwtorm.server.OrmException;
import com.google.inject.assistedinject.Assisted;
diff --git a/java/com/google/gerrit/server/notedb/DraftCommentNotes.java b/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
index 79da7e1..81d32d9 100644
--- a/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
+++ b/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
@@ -150,6 +150,8 @@
return;
}
+ logger.atFine().log(
+ "Load draft comment notes for change %s of project %s", getChangeId(), getProjectName());
RevCommit tipCommit = handle.walk().parseCommit(rev);
ObjectReader reader = handle.walk().getObjectReader();
revisionNoteMap =
diff --git a/java/com/google/gerrit/server/notedb/LegacyChangeNoteWrite.java b/java/com/google/gerrit/server/notedb/LegacyChangeNoteWrite.java
index 7931d88..c9711b5 100644
--- a/java/com/google/gerrit/server/notedb/LegacyChangeNoteWrite.java
+++ b/java/com/google/gerrit/server/notedb/LegacyChangeNoteWrite.java
@@ -15,10 +15,12 @@
package com.google.gerrit.server.notedb;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.server.CommentsUtil.COMMENT_ORDER;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Comment;
@@ -30,8 +32,6 @@
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.eclipse.jgit.lib.PersonIdent;
@@ -87,8 +87,7 @@
return;
}
- List<Integer> psIds = new ArrayList<>(comments.keySet());
- Collections.sort(psIds);
+ ImmutableList<Integer> psIds = comments.keySet().stream().sorted().collect(toImmutableList());
OutputStreamWriter streamWriter = new OutputStreamWriter(out, UTF_8);
try (PrintWriter writer = new PrintWriter(streamWriter)) {
diff --git a/java/com/google/gerrit/server/notedb/RobotCommentNotes.java b/java/com/google/gerrit/server/notedb/RobotCommentNotes.java
index 7eb3a54..a05e6a1 100644
--- a/java/com/google/gerrit/server/notedb/RobotCommentNotes.java
+++ b/java/com/google/gerrit/server/notedb/RobotCommentNotes.java
@@ -17,6 +17,7 @@
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
@@ -34,6 +35,8 @@
import org.eclipse.jgit.revwalk.RevCommit;
public class RobotCommentNotes extends AbstractChangeNotes<RobotCommentNotes> {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
public interface Factory {
RobotCommentNotes create(Change change);
}
@@ -86,6 +89,8 @@
}
metaId = metaId.copy();
+ logger.atFine().log(
+ "Load robot comment notes for change %s of project %s", getChangeId(), getProjectName());
RevCommit tipCommit = handle.walk().parseCommit(metaId);
ObjectReader reader = handle.walk().getObjectReader();
revisionNoteMap =
diff --git a/java/com/google/gerrit/server/patch/DiffExecutorModule.java b/java/com/google/gerrit/server/patch/DiffExecutorModule.java
index 5359479..eb6a280 100644
--- a/java/com/google/gerrit/server/patch/DiffExecutorModule.java
+++ b/java/com/google/gerrit/server/patch/DiffExecutorModule.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.patch;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.gerrit.server.logging.LoggingContextAwareExecutorService;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
@@ -31,7 +32,8 @@
@Singleton
@DiffExecutor
public ExecutorService createDiffExecutor() {
- return Executors.newCachedThreadPool(
- new ThreadFactoryBuilder().setNameFormat("Diff-%d").setDaemon(true).build());
+ return new LoggingContextAwareExecutorService(
+ Executors.newCachedThreadPool(
+ new ThreadFactoryBuilder().setNameFormat("Diff-%d").setDaemon(true).build()));
}
}
diff --git a/java/com/google/gerrit/server/patch/DiffSummaryLoader.java b/java/com/google/gerrit/server/patch/DiffSummaryLoader.java
index 8bca19f..9153638 100644
--- a/java/com/google/gerrit/server/patch/DiffSummaryLoader.java
+++ b/java/com/google/gerrit/server/patch/DiffSummaryLoader.java
@@ -19,7 +19,6 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
@@ -66,8 +65,9 @@
break;
}
}
- Collections.sort(r);
return new DiffSummary(
- r.toArray(new String[r.size()]), patchList.getInsertions(), patchList.getDeletions());
+ r.stream().sorted().toArray(String[]::new),
+ patchList.getInsertions(),
+ patchList.getDeletions());
}
}
diff --git a/java/com/google/gerrit/server/patch/PatchList.java b/java/com/google/gerrit/server/patch/PatchList.java
index cf5df4a..dd717ba 100644
--- a/java/com/google/gerrit/server/patch/PatchList.java
+++ b/java/com/google/gerrit/server/patch/PatchList.java
@@ -47,12 +47,7 @@
private static final long serialVersionUID = PatchListKey.serialVersionUID;
private static final Comparator<PatchListEntry> PATCH_CMP =
- new Comparator<PatchListEntry>() {
- @Override
- public int compare(PatchListEntry a, PatchListEntry b) {
- return comparePaths(a.getNewName(), b.getNewName());
- }
- };
+ Comparator.comparing(PatchListEntry::getNewName, PatchList::comparePaths);
@VisibleForTesting
static int comparePaths(String a, String b) {
diff --git a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index b4f7251..61f0180 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.patch;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Comparator.comparing;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.data.CommentDetail;
@@ -34,7 +35,6 @@
import eu.medsea.mimeutil.MimeUtil2;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
@@ -55,13 +55,7 @@
static final int MAX_CONTEXT = 5000000;
static final int BIG_FILE = 9000;
- private static final Comparator<Edit> EDIT_SORT =
- new Comparator<Edit>() {
- @Override
- public int compare(Edit o1, Edit o2) {
- return o1.getBeginA() - o2.getBeginA();
- }
- };
+ private static final Comparator<Edit> EDIT_SORT = comparing(Edit::getBeginA);
private Repository db;
private Project.NameKey projectKey;
@@ -285,6 +279,13 @@
int aSize = a.src.size();
int bSize = b.src.size();
+ if (edits.isEmpty() && (aSize == 0 || bSize == 0)) {
+ // The diff was requested for a file which was either added or deleted but which JGit doesn't
+ // consider a file addition/deletion (e.g. requesting a diff for the old file name of a
+ // renamed file looks like a deletion).
+ return;
+ }
+
Optional<Edit> lastEdit = getLast(edits);
if (isNewlineAtEndDeleted()) {
Optional<Edit> lastLineEdit = lastEdit.filter(edit -> edit.getEndA() == aSize);
@@ -362,7 +363,7 @@
// them correctly later.
//
edits.addAll(empty);
- Collections.sort(edits, EDIT_SORT);
+ edits.sort(EDIT_SORT);
}
private void safeAdd(List<Edit> empty, Edit toAdd) {
diff --git a/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java b/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
index 136b4ae..51a0f95 100644
--- a/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
@@ -19,6 +19,7 @@
import static java.util.stream.Collectors.toSet;
import com.google.common.collect.Sets;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.PermissionRule.Action;
import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
@@ -46,6 +47,8 @@
@Singleton
public class DefaultPermissionBackend extends PermissionBackend {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
private static final CurrentUser.PropertyKey<Boolean> IS_ADMIN = CurrentUser.PropertyKey.create();
private final Provider<CurrentUser> currentUser;
@@ -186,6 +189,13 @@
private boolean isAdmin() {
if (admin == null) {
admin = computeAdmin();
+ if (admin) {
+ logger.atFinest().log(
+ "user %s is an administrator of the server", user.getLoggableName());
+ } else {
+ logger.atFinest().log(
+ "user %s is not an administrator of the server", user.getLoggableName());
+ }
}
return admin;
}
@@ -210,11 +220,32 @@
private boolean canEmailReviewers() {
List<PermissionRule> email = capabilities().emailReviewers;
- return allow(email) || notDenied(email);
+ if (allow(email)) {
+ logger.atFinest().log(
+ "user %s can email reviewers (allowed by %s)", user.getLoggableName(), email);
+ return true;
+ }
+
+ if (notDenied(email)) {
+ logger.atFinest().log(
+ "user %s can email reviewers (not denied by %s)", user.getLoggableName(), email);
+ return true;
+ }
+
+ logger.atFinest().log("user %s cannot email reviewers", user.getLoggableName());
+ return false;
}
private boolean has(String permissionName) {
- return allow(capabilities().getPermission(checkNotNull(permissionName)));
+ boolean has = allow(capabilities().getPermission(checkNotNull(permissionName)));
+ if (has) {
+ logger.atFinest().log(
+ "user %s has global capability %s", user.getLoggableName(), permissionName);
+ } else {
+ logger.atFinest().log(
+ "user %s doesn't have global capability %s", user.getLoggableName(), permissionName);
+ }
+ return has;
}
private boolean allow(Collection<PermissionRule> rules) {
diff --git a/java/com/google/gerrit/server/permissions/GlobalPermission.java b/java/com/google/gerrit/server/permissions/GlobalPermission.java
index 71718fb..01ef725 100644
--- a/java/com/google/gerrit/server/permissions/GlobalPermission.java
+++ b/java/com/google/gerrit/server/permissions/GlobalPermission.java
@@ -24,6 +24,7 @@
import com.google.gerrit.extensions.api.access.GerritPermission;
import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
import com.google.gerrit.extensions.api.access.PluginPermission;
+import com.google.gerrit.extensions.registration.PluginName;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.LinkedHashSet;
@@ -116,7 +117,7 @@
Class<?> annotationClass)
throws PermissionBackendException {
if (pluginName != null
- && !"gerrit".equals(pluginName)
+ && !PluginName.GERRIT.equals(pluginName)
&& (scope == CapabilityScope.PLUGIN || scope == CapabilityScope.CONTEXT)) {
return new PluginPermission(pluginName, capability, fallBackToAdmin);
}
diff --git a/java/com/google/gerrit/server/permissions/PermissionDeniedException.java b/java/com/google/gerrit/server/permissions/PermissionDeniedException.java
new file mode 100644
index 0000000..6018263
--- /dev/null
+++ b/java/com/google/gerrit/server/permissions/PermissionDeniedException.java
@@ -0,0 +1,58 @@
+// Copyright (C) 2018 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.permissions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.gerrit.extensions.api.access.GerritPermission;
+import com.google.gerrit.extensions.restapi.AuthException;
+import java.util.Optional;
+
+/**
+ * This signals that some permission check failed. The message is short so it can print on a
+ * single-line in the Git output.
+ */
+public class PermissionDeniedException extends AuthException {
+ private static final long serialVersionUID = 1L;
+
+ public static final String MESSAGE_PREFIX = "not permitted: ";
+
+ private final GerritPermission permission;
+ private final Optional<String> resource;
+
+ public PermissionDeniedException(GerritPermission permission) {
+ super(MESSAGE_PREFIX + checkNotNull(permission).describeForException());
+ this.permission = permission;
+ this.resource = Optional.empty();
+ }
+
+ public PermissionDeniedException(GerritPermission permission, String resource) {
+ super(
+ MESSAGE_PREFIX
+ + checkNotNull(permission).describeForException()
+ + " on "
+ + checkNotNull(resource));
+ this.permission = permission;
+ this.resource = Optional.of(resource);
+ }
+
+ public String describePermission() {
+ return permission.describeForException();
+ }
+
+ public Optional<String> getResource() {
+ return resource;
+ }
+}
diff --git a/java/com/google/gerrit/server/permissions/ProjectControl.java b/java/com/google/gerrit/server/permissions/ProjectControl.java
index 67662c7..4a4ea37 100644
--- a/java/com/google/gerrit/server/permissions/ProjectControl.java
+++ b/java/com/google/gerrit/server/permissions/ProjectControl.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.permissions;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_TAGS;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.Permission;
@@ -212,6 +213,10 @@
return (canPerformOnAnyRef(Permission.CREATE) || isAdmin());
}
+ private boolean canAddTagRefs() {
+ return (canPerformOnTagRef(Permission.CREATE) || isAdmin());
+ }
+
private boolean canCreateChanges() {
for (SectionMatcher matcher : access()) {
AccessSection section = matcher.getSection();
@@ -233,6 +238,26 @@
return declaredOwner;
}
+ private boolean canPerformOnTagRef(String permissionName) {
+ for (SectionMatcher matcher : access()) {
+ AccessSection section = matcher.getSection();
+
+ if (section.getName().startsWith(REFS_TAGS)) {
+ Permission permission = section.getPermission(permissionName);
+ if (permission == null) {
+ continue;
+ }
+
+ Boolean can = canPerform(permissionName, section, permission);
+ if (can != null) {
+ return can;
+ }
+ }
+ }
+
+ return false;
+ }
+
private boolean canPerformOnAnyRef(String permissionName) {
for (SectionMatcher matcher : access()) {
AccessSection section = matcher.getSection();
@@ -241,25 +266,33 @@
continue;
}
- for (PermissionRule rule : permission.getRules()) {
- if (rule.isBlock() || rule.isDeny() || !match(rule)) {
- continue;
- }
-
- // Being in a group that was granted this permission is only an
- // approximation. There might be overrides and doNotInherit
- // that would render this to be false.
- //
- if (controlForRef(section.getName()).canPerform(permissionName)) {
- return true;
- }
- break;
+ Boolean can = canPerform(permissionName, section, permission);
+ if (can != null) {
+ return can;
}
}
return false;
}
+ private Boolean canPerform(String permissionName, AccessSection section, Permission permission) {
+ for (PermissionRule rule : permission.getRules()) {
+ if (rule.isBlock() || rule.isDeny() || !match(rule)) {
+ continue;
+ }
+
+ // Being in a group that was granted this permission is only an
+ // approximation. There might be overrides and doNotInherit
+ // that would render this to be false.
+ //
+ if (controlForRef(section.getName()).canPerform(permissionName)) {
+ return true;
+ }
+ break;
+ }
+ return null;
+ }
+
private boolean canPerformOnAllRefs(String permission, Set<String> ignore) {
boolean canPerform = false;
Set<String> patterns = allRefPatterns(permission);
@@ -403,6 +436,8 @@
case CREATE_REF:
return canAddRefs();
+ case CREATE_TAG_REF:
+ return canAddTagRefs();
case CREATE_CHANGE:
return canCreateChanges();
diff --git a/java/com/google/gerrit/server/permissions/ProjectPermission.java b/java/com/google/gerrit/server/permissions/ProjectPermission.java
index 3fee6cf..7c58ccb 100644
--- a/java/com/google/gerrit/server/permissions/ProjectPermission.java
+++ b/java/com/google/gerrit/server/permissions/ProjectPermission.java
@@ -51,6 +51,21 @@
CREATE_REF,
/**
+ * Can create at least one tag reference in the project.
+ *
+ * <p>This project level permission only validates the user may create some tag reference within
+ * the project. The exact reference name must be checked at creation:
+ *
+ * <pre>permissionBackend
+ * .user(user)
+ * .project(proj)
+ * .ref(ref)
+ * .check(RefPermission.CREATE);
+ * </pre>
+ */
+ CREATE_TAG_REF,
+
+ /**
* Can create at least one change in the project.
*
* <p>This project level permission only validates the user may create a change for some branch
diff --git a/java/com/google/gerrit/server/permissions/RefControl.java b/java/com/google/gerrit/server/permissions/RefControl.java
index 3bd2817..762dfbe 100644
--- a/java/com/google/gerrit/server/permissions/RefControl.java
+++ b/java/com/google/gerrit/server/permissions/RefControl.java
@@ -16,6 +16,7 @@
import static com.google.common.base.Preconditions.checkArgument;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.PermissionRule;
@@ -41,6 +42,8 @@
/** Manages access control for Git references (aka branches, tags). */
class RefControl {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
private final IdentifiedUser.GenericFactory identifiedUserFactory;
private final ProjectControl projectControl;
private final String refName;
@@ -392,15 +395,37 @@
/** True if the user has this permission. */
private boolean canPerform(String permissionName, boolean isChangeOwner, boolean withForce) {
if (isBlocked(permissionName, isChangeOwner, withForce)) {
+ logger.atFine().log(
+ "'%s' cannot perform '%s' with force=%s on project '%s' for ref '%s'"
+ + " because this permission is blocked",
+ getUser().getLoggableName(),
+ permissionName,
+ withForce,
+ projectControl.getProject().getName(),
+ refName);
return false;
}
for (PermissionRule pr : relevant.getAllowRules(permissionName)) {
if (isAllow(pr, withForce) && projectControl.match(pr, isChangeOwner)) {
+ logger.atFine().log(
+ "'%s' can perform '%s' with force=%s on project '%s' for ref '%s'",
+ getUser().getLoggableName(),
+ permissionName,
+ withForce,
+ projectControl.getProject().getName(),
+ refName);
return true;
}
}
+ logger.atFine().log(
+ "'%s' cannot perform '%s' with force=%s on project '%s' for ref '%s'",
+ getUser().getLoggableName(),
+ permissionName,
+ withForce,
+ projectControl.getProject().getName(),
+ refName);
return false;
}
@@ -449,7 +474,88 @@
@Override
public void check(RefPermission perm) throws AuthException, PermissionBackendException {
if (!can(perm)) {
- throw new AuthException(perm.describeForException() + " not permitted for " + refName);
+ PermissionDeniedException pde = new PermissionDeniedException(perm, refName);
+ switch (perm) {
+ case UPDATE:
+ if (refName.equals(RefNames.REFS_CONFIG)) {
+ pde.setAdvice(
+ "Configuration changes can only be pushed by project owners\n"
+ + "who also have 'Push' rights on "
+ + RefNames.REFS_CONFIG);
+ } else {
+ pde.setAdvice("To push into this reference you need 'Push' rights.");
+ }
+ break;
+ case DELETE:
+ pde.setAdvice(
+ "You need 'Delete Reference' rights or 'Push' rights with the \n"
+ + "'Force Push' flag set to delete references.");
+ break;
+ case CREATE_CHANGE:
+ // This is misleading in the default permission backend, since "create change" on a
+ // branch is encoded as "push" on refs/for/DESTINATION.
+ pde.setAdvice(
+ "You need 'Create Change' rights to upload code review requests.\n"
+ + "Verify that you are pushing to the right branch.");
+ break;
+ case CREATE:
+ pde.setAdvice("You need 'Create' rights to create new references.");
+ break;
+ case CREATE_SIGNED_TAG:
+ pde.setAdvice("You need 'Create Signed Tag' rights to push a signed tag.");
+ break;
+ case CREATE_TAG:
+ pde.setAdvice("You need 'Create Tag' rights to push a normal tag.");
+ break;
+ case FORCE_UPDATE:
+ pde.setAdvice(
+ "You need 'Push' rights with 'Force' flag set to do a non-fastforward push.");
+ break;
+ case FORGE_AUTHOR:
+ pde.setAdvice(
+ "You need 'Forge Author' rights to push commits with another user as author.");
+ break;
+ case FORGE_COMMITTER:
+ pde.setAdvice(
+ "You need 'Forge Committer' rights to push commits with another user as committer.");
+ break;
+ case FORGE_SERVER:
+ pde.setAdvice(
+ "You need 'Forge Server' rights to push merge commits authored by the server.");
+ break;
+ case MERGE:
+ pde.setAdvice(
+ "You need 'Push Merge' in addition to 'Push' rights to push merge commits.");
+ break;
+
+ case READ:
+ pde.setAdvice("You need 'Read' rights to fetch or clone this ref.");
+ break;
+
+ case READ_CONFIG:
+ pde.setAdvice("You need 'Read' rights on refs/meta/config to see the configuration.");
+ break;
+ case READ_PRIVATE_CHANGES:
+ pde.setAdvice("You need 'Read Private Changes' to see private changes.");
+ break;
+ case SET_HEAD:
+ pde.setAdvice("You need 'Set HEAD' rights to set the default branch.");
+ break;
+ case SKIP_VALIDATION:
+ pde.setAdvice(
+ "You need 'Forge Author', 'Forge Server', 'Forge Committer'\n"
+ + "and 'Push Merge' rights to skip validation.");
+ break;
+ case UPDATE_BY_SUBMIT:
+ pde.setAdvice(
+ "You need 'Submit' rights on refs/for/ to submit changes during change upload.");
+ break;
+
+ case WRITE_CONFIG:
+ pde.setAdvice("You need 'Write' rights on refs/meta/config.");
+ break;
+ }
+ throw pde;
}
}
diff --git a/java/com/google/gerrit/server/permissions/SectionSortCache.java b/java/com/google/gerrit/server/permissions/SectionSortCache.java
index 48c8bff..e5392b0 100644
--- a/java/com/google/gerrit/server/permissions/SectionSortCache.java
+++ b/java/com/google/gerrit/server/permissions/SectionSortCache.java
@@ -26,7 +26,6 @@
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
@@ -88,7 +87,7 @@
poison |= srcMap.put(sections.get(i), i) != null;
}
- Collections.sort(sections, new MostSpecificComparator(ref));
+ sections.sort(new MostSpecificComparator(ref));
int[] srcIdx;
if (isIdentityTransform(sections, srcMap)) {
diff --git a/java/com/google/gerrit/server/plugins/InstallPlugin.java b/java/com/google/gerrit/server/plugins/InstallPlugin.java
index 6404304..a79a5a6 100644
--- a/java/com/google/gerrit/server/plugins/InstallPlugin.java
+++ b/java/com/google/gerrit/server/plugins/InstallPlugin.java
@@ -27,6 +27,7 @@
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
@@ -94,6 +95,7 @@
}
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+ @Singleton
static class Create
implements RestCollectionCreateView<TopLevelResource, PluginResource, InstallPluginInput> {
private final PluginLoader loader;
@@ -114,6 +116,7 @@
}
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+ @Singleton
static class Overwrite implements RestModifyView<PluginResource, InstallPluginInput> {
private final Provider<InstallPlugin> install;
diff --git a/java/com/google/gerrit/server/plugins/JarPluginProvider.java b/java/com/google/gerrit/server/plugins/JarPluginProvider.java
index 229f394..5b80059 100644
--- a/java/com/google/gerrit/server/plugins/JarPluginProvider.java
+++ b/java/com/google/gerrit/server/plugins/JarPluginProvider.java
@@ -16,6 +16,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.registration.PluginName;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.SitePaths;
@@ -90,7 +91,7 @@
@Override
public String getProviderPluginName() {
- return "gerrit";
+ return PluginName.GERRIT;
}
private static String getExtension(Path path) {
diff --git a/java/com/google/gerrit/server/plugins/PluginEntry.java b/java/com/google/gerrit/server/plugins/PluginEntry.java
index f7b1e82..3a6c7b2 100644
--- a/java/com/google/gerrit/server/plugins/PluginEntry.java
+++ b/java/com/google/gerrit/server/plugins/PluginEntry.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.plugins;
+import static java.util.Comparator.comparing;
+
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
@@ -28,13 +30,7 @@
public class PluginEntry {
public static final String ATTR_CHARACTER_ENCODING = "Character-Encoding";
public static final String ATTR_CONTENT_TYPE = "Content-Type";
- public static final Comparator<PluginEntry> COMPARATOR_BY_NAME =
- new Comparator<PluginEntry>() {
- @Override
- public int compare(PluginEntry a, PluginEntry b) {
- return a.getName().compareTo(b.getName());
- }
- };
+ public static final Comparator<PluginEntry> COMPARATOR_BY_NAME = comparing(PluginEntry::getName);
private static final Map<Object, String> EMPTY_ATTRS = Collections.emptyMap();
private static final Optional<Long> NO_SIZE = Optional.empty();
diff --git a/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java b/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
index effd51a..8bc04a3 100644
--- a/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
+++ b/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
@@ -274,14 +274,15 @@
private void attachItem(
Map<TypeLiteral<?>, DynamicItem<?>> items, @Nullable Injector src, Plugin plugin) {
for (RegistrationHandle h :
- PrivateInternals_DynamicTypes.attachItems(src, items, plugin.getName())) {
+ PrivateInternals_DynamicTypes.attachItems(src, plugin.getName(), items)) {
plugin.add(h);
}
}
private void attachSet(
Map<TypeLiteral<?>, DynamicSet<?>> sets, @Nullable Injector src, Plugin plugin) {
- for (RegistrationHandle h : PrivateInternals_DynamicTypes.attachSets(src, sets)) {
+ for (RegistrationHandle h :
+ PrivateInternals_DynamicTypes.attachSets(src, plugin.getName(), sets)) {
plugin.add(h);
}
}
@@ -434,7 +435,7 @@
oi.remove();
replace(newPlugin, h2, b);
} else {
- newPlugin.add(set.add(b.getKey(), b.getProvider()));
+ newPlugin.add(set.add(newPlugin.getName(), b.getKey(), b.getProvider()));
}
}
}
diff --git a/java/com/google/gerrit/server/plugins/UniversalServerPluginProvider.java b/java/com/google/gerrit/server/plugins/UniversalServerPluginProvider.java
index 0bef1e5..4d89482 100644
--- a/java/com/google/gerrit/server/plugins/UniversalServerPluginProvider.java
+++ b/java/com/google/gerrit/server/plugins/UniversalServerPluginProvider.java
@@ -16,6 +16,7 @@
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.registration.PluginName;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.nio.file.Path;
@@ -60,7 +61,7 @@
@Override
public String getProviderPluginName() {
- return "gerrit";
+ return PluginName.GERRIT;
}
private ServerPluginProvider providerOf(Path srcPath) {
diff --git a/java/com/google/gerrit/server/project/ProjectCacheClock.java b/java/com/google/gerrit/server/project/ProjectCacheClock.java
index 5d208f3..eb451fd 100644
--- a/java/com/google/gerrit/server/project/ProjectCacheClock.java
+++ b/java/com/google/gerrit/server/project/ProjectCacheClock.java
@@ -18,6 +18,7 @@
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.logging.LoggingContextAwareScheduledExecutorService;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.concurrent.Executors;
@@ -53,13 +54,14 @@
// Start with generation 1 (to avoid magic 0 below).
generation.set(1);
executor =
- Executors.newScheduledThreadPool(
- 1,
- new ThreadFactoryBuilder()
- .setNameFormat("ProjectCacheClock-%d")
- .setDaemon(true)
- .setPriority(Thread.MIN_PRIORITY)
- .build());
+ new LoggingContextAwareScheduledExecutorService(
+ Executors.newScheduledThreadPool(
+ 1,
+ new ThreadFactoryBuilder()
+ .setNameFormat("ProjectCacheClock-%d")
+ .setDaemon(true)
+ .setPriority(Thread.MIN_PRIORITY)
+ .build()));
@SuppressWarnings("unused") // Runnable already handles errors
Future<?> possiblyIgnoredError =
executor.scheduleAtFixedRate(
diff --git a/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index df80e35..dd6ce56 100644
--- a/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -178,6 +178,7 @@
@Override
public void evict(Project.NameKey p) throws IOException {
if (p != null) {
+ logger.atFine().log("Evict project '%s'", p.get());
byName.invalidate(p.get());
}
indexer.get().index(p);
@@ -269,11 +270,12 @@
@Override
public ProjectState load(String projectName) throws Exception {
+ logger.atFine().log("Loading project %s", projectName);
long now = clock.read();
Project.NameKey key = new Project.NameKey(projectName);
try (Repository git = mgr.openRepository(key)) {
ProjectConfig cfg = new ProjectConfig(key);
- cfg.load(git);
+ cfg.load(key, git);
ProjectState state = projectStateFactory.create(cfg);
state.initLastCheck(now);
@@ -298,6 +300,7 @@
@Override
public ImmutableSortedSet<Project.NameKey> load(ListKey key) throws Exception {
+ logger.atFine().log("Loading project list");
return ImmutableSortedSet.copyOf(mgr.list());
}
}
diff --git a/java/com/google/gerrit/server/project/ProjectCacheWarmer.java b/java/com/google/gerrit/server/project/ProjectCacheWarmer.java
index 7ebbc51..10cf2de 100644
--- a/java/com/google/gerrit/server/project/ProjectCacheWarmer.java
+++ b/java/com/google/gerrit/server/project/ProjectCacheWarmer.java
@@ -19,10 +19,11 @@
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.logging.LoggingContextAwareExecutorService;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Config;
@@ -43,10 +44,11 @@
public void start() {
int cpus = Runtime.getRuntime().availableProcessors();
if (config.getBoolean("cache", "projects", "loadOnStartup", false)) {
- ThreadPoolExecutor pool =
- new ScheduledThreadPoolExecutor(
- config.getInt("cache", "projects", "loadThreads", cpus),
- new ThreadFactoryBuilder().setNameFormat("ProjectCacheLoader-%d").build());
+ ExecutorService pool =
+ new LoggingContextAwareExecutorService(
+ new ScheduledThreadPoolExecutor(
+ config.getInt("cache", "projects", "loadThreads", cpus),
+ new ThreadFactoryBuilder().setNameFormat("ProjectCacheLoader-%d").build()));
Thread scheduler =
new Thread(
() -> {
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index e3638b6..bccc415 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -15,8 +15,10 @@
package com.google.gerrit.server.project;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.common.data.Permission.isPermission;
import static com.google.gerrit.reviewdb.client.Project.DEFAULT_SUBMIT_TYPE;
+import static java.util.stream.Collectors.toList;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
@@ -25,6 +27,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.primitives.Shorts;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.common.data.GlobalCapability;
@@ -69,6 +72,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
@@ -77,6 +81,8 @@
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RefSpec;
public class ProjectConfig extends VersionedMetaData implements ValidationError.Sink {
@@ -86,6 +92,7 @@
public static final String KEY_DEFAULT_VALUE = "defaultValue";
public static final String KEY_COPY_MIN_SCORE = "copyMinScore";
public static final String KEY_ALLOW_POST_SUBMIT = "allowPostSubmit";
+ public static final String KEY_IGNORE_SELF_APPROVAL = "ignoreSelfApproval";
public static final String KEY_COPY_MAX_SCORE = "copyMaxScore";
public static final String KEY_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE =
"copyAllScoresOnMergeFirstParentUpdate";
@@ -158,7 +165,6 @@
private static final Pattern EXCLUSIVE_PERMISSIONS_SPLIT_PATTERN = Pattern.compile("[, \t]{1,}");
- private Project.NameKey projectName;
private Project project;
private AccountsSection accountsSection;
private GroupList groupList;
@@ -239,6 +245,20 @@
this.projectName = projectName;
}
+ public void load(Repository repo) throws IOException, ConfigInvalidException {
+ super.load(projectName, repo);
+ }
+
+ public void load(Repository repo, @Nullable ObjectId revision)
+ throws IOException, ConfigInvalidException {
+ super.load(projectName, repo, revision);
+ }
+
+ public void load(RevWalk rw, @Nullable ObjectId revision)
+ throws IOException, ConfigInvalidException {
+ super.load(projectName, rw, revision);
+ }
+
public Project.NameKey getName() {
return projectName;
}
@@ -437,10 +457,7 @@
return rulesId;
}
- /**
- * @return the maxObjectSizeLimit for this project, if set. Zero if this project doesn't define
- * own maxObjectSizeLimit.
- */
+ /** @return the maxObjectSizeLimit configured on this project, or zero if not configured. */
public long getMaxObjectSizeLimit() {
return maxObjectSizeLimit;
}
@@ -867,6 +884,8 @@
}
label.setAllowPostSubmit(
rc.getBoolean(LABEL, name, KEY_ALLOW_POST_SUBMIT, LabelType.DEF_ALLOW_POST_SUBMIT));
+ label.setIgnoreSelfApproval(
+ rc.getBoolean(LABEL, name, KEY_IGNORE_SELF_APPROVAL, LabelType.DEF_IGNORE_SELF_APPROVAL));
label.setCopyMinScore(
rc.getBoolean(LABEL, name, KEY_COPY_MIN_SCORE, LabelType.DEF_COPY_MIN_SCORE));
label.setCopyMaxScore(
@@ -1149,21 +1168,20 @@
private void saveNotifySections(Config rc, Set<AccountGroup.UUID> keepGroups) {
for (NotifyConfig nc : sort(notifySections.values())) {
- List<String> email = new ArrayList<>();
- for (GroupReference gr : nc.getGroups()) {
- if (gr.getUUID() != null) {
- keepGroups.add(gr.getUUID());
- }
- email.add(new PermissionRule(gr).asString(false));
- }
- Collections.sort(email);
+ nc.getGroups()
+ .stream()
+ .map(gr -> gr.getUUID())
+ .filter(Objects::nonNull)
+ .forEach(keepGroups::add);
+ List<String> email =
+ nc.getGroups()
+ .stream()
+ .map(gr -> new PermissionRule(gr).asString(false))
+ .sorted()
+ .collect(toList());
- List<String> addrs = new ArrayList<>();
- for (Address addr : nc.getAddresses()) {
- addrs.add(addr.toString());
- }
- Collections.sort(addrs);
- email.addAll(addrs);
+ // Separate stream operation so that emails list contains 2 sorted sub-lists.
+ nc.getAddresses().stream().map(Address::toString).sorted().forEach(email::add);
set(rc, NOTIFY, nc.getName(), KEY_HEADER, nc.getHeader(), NotifyConfig.Header.BCC);
if (email.isEmpty()) {
@@ -1307,6 +1325,13 @@
rc,
LABEL,
name,
+ KEY_IGNORE_SELF_APPROVAL,
+ label.ignoreSelfApproval(),
+ LabelType.DEF_IGNORE_SELF_APPROVAL);
+ setBooleanConfigKey(
+ rc,
+ LABEL,
+ name,
KEY_COPY_MIN_SCORE,
label.isCopyMinScore(),
LabelType.DEF_COPY_MIN_SCORE);
@@ -1437,10 +1462,8 @@
validationErrors.add(error);
}
- private static <T extends Comparable<? super T>> List<T> sort(Collection<T> m) {
- ArrayList<T> r = new ArrayList<>(m);
- Collections.sort(r);
- return r;
+ private static <T extends Comparable<? super T>> ImmutableList<T> sort(Collection<T> m) {
+ return m.stream().sorted().collect(toImmutableList());
}
public boolean hasLegacyPermissions() {
diff --git a/java/com/google/gerrit/server/project/ProjectState.java b/java/com/google/gerrit/server/project/ProjectState.java
index 726e513..a9b19d9 100644
--- a/java/com/google/gerrit/server/project/ProjectState.java
+++ b/java/com/google/gerrit/server/project/ProjectState.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.common.data.PermissionRule.Action.ALLOW;
import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@@ -46,6 +47,7 @@
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.BranchOrderSection;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -87,6 +89,8 @@
private final ProjectConfig config;
private final Map<String, ProjectLevelConfig> configs;
private final Set<AccountGroup.UUID> localOwners;
+ private final long globalMaxObjectSizeLimit;
+ private final boolean inheritProjectMaxObjectSizeLimit;
/** Last system time the configuration's revision was examined. */
private volatile long lastCheckGeneration;
@@ -107,14 +111,15 @@
@Inject
public ProjectState(
- final SitePaths sitePaths,
- final ProjectCache projectCache,
- final AllProjectsName allProjectsName,
- final AllUsersName allUsersName,
- final GitRepositoryManager gitMgr,
- final List<CommentLinkInfo> commentLinks,
- final CapabilityCollection.Factory limitsFactory,
- @Assisted final ProjectConfig config) {
+ SitePaths sitePaths,
+ ProjectCache projectCache,
+ AllProjectsName allProjectsName,
+ AllUsersName allUsersName,
+ GitRepositoryManager gitMgr,
+ List<CommentLinkInfo> commentLinks,
+ CapabilityCollection.Factory limitsFactory,
+ TransferConfig transferConfig,
+ @Assisted ProjectConfig config) {
this.sitePaths = sitePaths;
this.projectCache = projectCache;
this.isAllProjects = config.getProject().getNameKey().equals(allProjectsName);
@@ -128,6 +133,8 @@
isAllProjects
? limitsFactory.create(config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES))
: null;
+ this.globalMaxObjectSizeLimit = transferConfig.getMaxObjectSizeLimit();
+ this.inheritProjectMaxObjectSizeLimit = transferConfig.getInheritProjectMaxObjectSizeLimit();
if (isAllProjects && !Permission.canBeOnAllProjects(AccessSection.ALL, Permission.OWNER)) {
localOwners = Collections.emptySet();
@@ -225,7 +232,7 @@
ProjectLevelConfig cfg = new ProjectLevelConfig(fileName, this);
try (Repository git = gitMgr.openRepository(getNameKey())) {
- cfg.load(git, config.getRevision());
+ cfg.load(getNameKey(), git, config.getRevision());
} catch (IOException | ConfigInvalidException e) {
logger.atWarning().withCause(e).log("Failed to load %s for %s", fileName, getName());
}
@@ -260,6 +267,60 @@
}
}
+ public static class EffectiveMaxObjectSizeLimit {
+ public long value;
+ public String summary;
+ }
+
+ private static final String MAY_NOT_SET = "This project may not set a higher limit.";
+
+ @VisibleForTesting
+ public static final String INHERITED_FROM_PARENT = "Inherited from parent project '%s'.";
+
+ @VisibleForTesting
+ public static final String OVERRIDDEN_BY_PARENT =
+ "Overridden by parent project '%s'. " + MAY_NOT_SET;
+
+ @VisibleForTesting
+ public static final String INHERITED_FROM_GLOBAL = "Inherited from the global config.";
+
+ @VisibleForTesting
+ public static final String OVERRIDDEN_BY_GLOBAL =
+ "Overridden by the global config. " + MAY_NOT_SET;
+
+ public EffectiveMaxObjectSizeLimit getEffectiveMaxObjectSizeLimit() {
+ EffectiveMaxObjectSizeLimit result = new EffectiveMaxObjectSizeLimit();
+
+ result.value = config.getMaxObjectSizeLimit();
+
+ if (inheritProjectMaxObjectSizeLimit) {
+ for (ProjectState parent : parents()) {
+ long parentValue = parent.config.getMaxObjectSizeLimit();
+ if (parentValue > 0 && result.value > 0) {
+ if (parentValue < result.value) {
+ result.value = parentValue;
+ result.summary = String.format(OVERRIDDEN_BY_PARENT, parent.config.getName());
+ }
+ } else if (parentValue > 0) {
+ result.value = parentValue;
+ result.summary = String.format(INHERITED_FROM_PARENT, parent.config.getName());
+ }
+ }
+ }
+
+ if (globalMaxObjectSizeLimit > 0 && result.value > 0) {
+ if (globalMaxObjectSizeLimit < result.value) {
+ result.value = globalMaxObjectSizeLimit;
+ result.summary = OVERRIDDEN_BY_GLOBAL;
+ }
+ } else if (globalMaxObjectSizeLimit > result.value) {
+ // zero means "no limit", in this case the max is more limiting
+ result.value = globalMaxObjectSizeLimit;
+ result.summary = INHERITED_FROM_GLOBAL;
+ }
+ return result;
+ }
+
/** Get the sections that pertain only to this project. */
List<SectionMatcher> getLocalAccessSections() {
List<SectionMatcher> sm = localAccessSections;
diff --git a/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java b/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java
new file mode 100644
index 0000000..e61c5df
--- /dev/null
+++ b/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java
@@ -0,0 +1,333 @@
+// Copyright (C) 2018 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.project;
+
+import static com.google.gerrit.common.FooterConstants.CHANGE_ID;
+import static com.google.gerrit.index.query.Predicate.and;
+import static com.google.gerrit.index.query.Predicate.or;
+import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open;
+import static java.util.stream.Collectors.toSet;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.extensions.api.changes.FixInput;
+import com.google.gerrit.extensions.api.projects.CheckProjectInput;
+import com.google.gerrit.extensions.api.projects.CheckProjectInput.AutoCloseableChangesCheckInput;
+import com.google.gerrit.extensions.api.projects.CheckProjectResultInfo;
+import com.google.gerrit.extensions.api.projects.CheckProjectResultInfo.AutoCloseableChangesCheckResult;
+import com.google.gerrit.extensions.client.ListChangesOption;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.change.ChangeJson;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeIdPredicate;
+import com.google.gerrit.server.query.change.CommitPredicate;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gerrit.server.query.change.ProjectPredicate;
+import com.google.gerrit.server.query.change.RefPredicate;
+import com.google.gerrit.server.update.RetryHelper;
+import com.google.gerrit.server.update.RetryHelper.ActionType;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevSort;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+@Singleton
+public class ProjectsConsistencyChecker {
+ @VisibleForTesting public static final int AUTO_CLOSE_MAX_COMMITS_LIMIT = 10000;
+
+ private final GitRepositoryManager repoManager;
+ private final RetryHelper retryHelper;
+ private final Provider<InternalChangeQuery> changeQueryProvider;
+ private final ChangeJson.Factory changeJsonFactory;
+ private final IndexConfig indexConfig;
+
+ @Inject
+ ProjectsConsistencyChecker(
+ GitRepositoryManager repoManager,
+ RetryHelper retryHelper,
+ Provider<InternalChangeQuery> changeQueryProvider,
+ ChangeJson.Factory changeJsonFactory,
+ IndexConfig indexConfig) {
+ this.repoManager = repoManager;
+ this.retryHelper = retryHelper;
+ this.changeQueryProvider = changeQueryProvider;
+ this.changeJsonFactory = changeJsonFactory;
+ this.indexConfig = indexConfig;
+ }
+
+ public CheckProjectResultInfo check(Project.NameKey projectName, CheckProjectInput input)
+ throws IOException, OrmException, RestApiException {
+ CheckProjectResultInfo r = new CheckProjectResultInfo();
+ if (input.autoCloseableChangesCheck != null) {
+ r.autoCloseableChangesCheckResult =
+ checkForAutoCloseableChanges(projectName, input.autoCloseableChangesCheck);
+ }
+ return r;
+ }
+
+ private AutoCloseableChangesCheckResult checkForAutoCloseableChanges(
+ Project.NameKey projectName, AutoCloseableChangesCheckInput input)
+ throws IOException, OrmException, RestApiException {
+ AutoCloseableChangesCheckResult r = new AutoCloseableChangesCheckResult();
+ if (Strings.isNullOrEmpty(input.branch)) {
+ throw new BadRequestException("branch is required");
+ }
+
+ boolean fix = input.fix != null ? input.fix : false;
+
+ if (input.maxCommits != null && input.maxCommits > AUTO_CLOSE_MAX_COMMITS_LIMIT) {
+ throw new BadRequestException(
+ "max commits can at most be set to " + AUTO_CLOSE_MAX_COMMITS_LIMIT);
+ }
+ int maxCommits = input.maxCommits != null ? input.maxCommits : AUTO_CLOSE_MAX_COMMITS_LIMIT;
+
+ // Result that we want to return to the client.
+ List<ChangeInfo> autoCloseableChanges = new ArrayList<>();
+
+ // Remember the change IDs of all changes that we already included into the result, so that we
+ // can avoid including the same change twice.
+ Set<Change.Id> seenChanges = new HashSet<>();
+
+ try (Repository repo = repoManager.openRepository(projectName);
+ RevWalk rw = new RevWalk(repo)) {
+ String branch = RefNames.fullName(input.branch);
+ Ref ref = repo.exactRef(branch);
+ if (ref == null) {
+ throw new UnprocessableEntityException(
+ String.format("branch '%s' not found", input.branch));
+ }
+
+ rw.reset();
+ rw.markStart(rw.parseCommit(ref.getObjectId()));
+ rw.sort(RevSort.TOPO);
+ rw.sort(RevSort.REVERSE);
+
+ // Cache the SHA1's of all merged commits. We need this for knowing which commit merged the
+ // change when auto-closing changes by commit.
+ List<ObjectId> mergedSha1s = new ArrayList<>();
+
+ // Cache the Change-Id to commit SHA1 mapping for all Change-Id's that we find in merged
+ // commits. We need this for knowing which commit merged the change when auto-closing
+ // changes by Change-Id.
+ Map<Change.Key, ObjectId> changeIdToMergedSha1 = new HashMap<>();
+
+ // Base predicate which is fixed for every change query.
+ Predicate<ChangeData> basePredicate =
+ and(new ProjectPredicate(projectName.get()), new RefPredicate(branch), open());
+
+ int maxLeafPredicates = indexConfig.maxTerms() - basePredicate.getLeafCount();
+
+ // List of predicates by which we want to find open changes for the branch. These predicates
+ // will be combined with the 'or' operator.
+ List<Predicate<ChangeData>> predicates = new ArrayList<>(maxLeafPredicates);
+
+ RevCommit commit;
+ int skippedCommits = 0;
+ int walkedCommits = 0;
+ while ((commit = rw.next()) != null) {
+ if (input.skipCommits != null && skippedCommits < input.skipCommits) {
+ skippedCommits++;
+ continue;
+ }
+
+ if (walkedCommits >= maxCommits) {
+ break;
+ }
+ walkedCommits++;
+
+ ObjectId commitId = commit.copy();
+ mergedSha1s.add(commitId);
+
+ // Consider all Change-Id lines since this is what ReceiveCommits#autoCloseChanges does.
+ List<String> changeIds = commit.getFooterLines(CHANGE_ID);
+
+ // Number of predicates that we need to add for this commit, 1 per Change-Id plus one for
+ // the commit.
+ int newPredicatesCount = changeIds.size() + 1;
+
+ // We accumulated the max number of query terms that can be used in one query, execute
+ // the query and start a new one.
+ if (predicates.size() + newPredicatesCount > maxLeafPredicates) {
+ autoCloseableChanges.addAll(
+ executeQueryAndAutoCloseChanges(
+ basePredicate, seenChanges, predicates, fix, changeIdToMergedSha1, mergedSha1s));
+ mergedSha1s.clear();
+ changeIdToMergedSha1.clear();
+ predicates.clear();
+
+ if (newPredicatesCount > maxLeafPredicates) {
+ // Whee, a single commit generates more than maxLeafPredicates predicates. Give up.
+ throw new ResourceConflictException(
+ String.format(
+ "commit %s contains more Change-Ids than we can handle", commit.name()));
+ }
+ }
+
+ changeIds.forEach(
+ changeId -> {
+ // It can happen that there are multiple merged commits with the same Change-Id
+ // footer (e.g. if a change was cherry-picked to a stable branch stable branch which
+ // then got merged back into master, or just by directly pushing several commits
+ // with the same Change-Id). In this case it is hard to say which of the commits
+ // should be used to auto-close an open change with the same Change-Id (and branch).
+ // Possible approaches are:
+ // 1. use the oldest commit with that Change-Id to auto-close the change
+ // 2. use the newest commit with that Change-Id to auto-close the change
+ // Possibility 1. has the disadvantage that the commit may have been merged before
+ // the change was created in which case it is strange how it could auto-close the
+ // change. Also this strategy would require to walk all commits since otherwise we
+ // cannot be sure that we have seen the oldest commit with that Change-Id.
+ // Possibility 2 has the disadvantage that it doesn't produce the same result as if
+ // auto-closing on push would have worked, since on direct push the first commit with
+ // a Change-Id of an open change would have closed that change. Also for this we
+ // would need to consider all commits that are skipped.
+ // Since both possibilities are not perfect and require extra effort we choose the
+ // easiest approach, which is use the newest commit with that Change-Id that we have
+ // seen (this means we ignore skipped commits). This should be okay since the
+ // important thing for callers is that auto-closable changes are closed. Which of the
+ // commits is used to auto-close a change if there are several candidates is of minor
+ // importance and hence can be non-deterministic.
+ Change.Key changeKey = new Change.Key(changeId);
+ if (!changeIdToMergedSha1.containsKey(changeKey)) {
+ changeIdToMergedSha1.put(changeKey, commitId);
+ }
+
+ // Find changes that have a matching Change-Id.
+ predicates.add(new ChangeIdPredicate(changeId));
+ });
+
+ // Find changes that have a matching commit.
+ predicates.add(new CommitPredicate(commit.name()));
+ }
+
+ if (predicates.size() > 0) {
+ // Execute the query with the remaining predicates that were collected.
+ autoCloseableChanges.addAll(
+ executeQueryAndAutoCloseChanges(
+ basePredicate, seenChanges, predicates, fix, changeIdToMergedSha1, mergedSha1s));
+ }
+ }
+
+ r.autoCloseableChanges = autoCloseableChanges;
+ return r;
+ }
+
+ private List<ChangeInfo> executeQueryAndAutoCloseChanges(
+ Predicate<ChangeData> basePredicate,
+ Set<Change.Id> seenChanges,
+ List<Predicate<ChangeData>> predicates,
+ boolean fix,
+ Map<Change.Key, ObjectId> changeIdToMergedSha1,
+ List<ObjectId> mergedSha1s)
+ throws OrmException {
+ if (predicates.isEmpty()) {
+ return ImmutableList.of();
+ }
+
+ try {
+ List<ChangeData> queryResult =
+ retryHelper.execute(
+ ActionType.INDEX_QUERY,
+ () -> {
+ // Execute the query.
+ return changeQueryProvider
+ .get()
+ .setRequestedFields(ChangeField.CHANGE, ChangeField.PATCH_SET)
+ .query(and(basePredicate, or(predicates)));
+ },
+ OrmException.class::isInstance);
+
+ // Result for this query that we want to return to the client.
+ List<ChangeInfo> autoCloseableChangesByBranch = new ArrayList<>();
+
+ for (ChangeData autoCloseableChange : queryResult) {
+ // Skip changes that we have already processed, either by this query or by
+ // earlier queries.
+ if (seenChanges.add(autoCloseableChange.getId())) {
+ retryHelper.execute(
+ ActionType.CHANGE_UPDATE,
+ () -> {
+ // Auto-close by change
+ if (changeIdToMergedSha1.containsKey(autoCloseableChange.change().getKey())) {
+ autoCloseableChangesByBranch.add(
+ changeJson(
+ fix, changeIdToMergedSha1.get(autoCloseableChange.change().getKey()))
+ .format(autoCloseableChange));
+ return null;
+ }
+
+ // Auto-close by commit
+ for (ObjectId patchSetSha1 :
+ autoCloseableChange
+ .patchSets()
+ .stream()
+ .map(ps -> ObjectId.fromString(ps.getRevision().get()))
+ .collect(toSet())) {
+ if (mergedSha1s.contains(patchSetSha1)) {
+ autoCloseableChangesByBranch.add(
+ changeJson(fix, patchSetSha1).format(autoCloseableChange));
+ break;
+ }
+ }
+ return null;
+ },
+ OrmException.class::isInstance);
+ }
+ }
+
+ return autoCloseableChangesByBranch;
+ } catch (Exception e) {
+ Throwables.throwIfUnchecked(e);
+ Throwables.throwIfInstanceOf(e, OrmException.class);
+ throw new OrmException(e);
+ }
+ }
+
+ private ChangeJson changeJson(Boolean fix, ObjectId mergedAs) {
+ ChangeJson changeJson = changeJsonFactory.create(ListChangesOption.CHECK);
+ if (fix != null && fix.booleanValue()) {
+ FixInput fixInput = new FixInput();
+ fixInput.expectMergedAs = mergedAs.name();
+ changeJson.fix(fixInput);
+ }
+ return changeJson;
+ }
+}
diff --git a/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java b/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java
index cc9fc0d..bd7b7fe 100644
--- a/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java
+++ b/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.query.account;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.index.query.IsVisibleToPredicate;
import com.google.gerrit.server.account.AccountControl;
import com.google.gerrit.server.account.AccountState;
@@ -21,6 +22,8 @@
import com.google.gwtorm.server.OrmException;
public class AccountIsVisibleToPredicate extends IsVisibleToPredicate<AccountState> {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
protected final AccountControl accountControl;
public AccountIsVisibleToPredicate(AccountControl accountControl) {
@@ -30,7 +33,11 @@
@Override
public boolean match(AccountState accountState) throws OrmException {
- return accountControl.canSee(accountState);
+ boolean canSee = accountControl.canSee(accountState);
+ if (!canSee) {
+ logger.atFine().log("Filter out non-visisble account: %s", accountState);
+ }
+ return canSee;
}
@Override
diff --git a/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java b/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
index 27baef1..f81ea15 100644
--- a/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
@@ -74,10 +74,11 @@
try {
ProjectState projectState = projectCache.checkedGet(cd.project());
if (projectState == null) {
- logger.atInfo().log("No such project: %s", cd.project());
+ logger.atFine().log("Filter out change %s of non-existing project %s", cd, cd.project());
return false;
}
if (!projectState.statePermitsRead()) {
+ logger.atFine().log("Filter out change %s of non-reabable project %s", cd, cd.project());
return false;
}
} catch (IOException e) {
@@ -94,11 +95,13 @@
Throwable cause = e.getCause();
if (cause instanceof RepositoryNotFoundException) {
logger.atWarning().withCause(e).log(
- "Skipping change %s because the corresponding repository was not found", cd.getId());
+ "Filter out change %s because the corresponding repository %s was not found",
+ cd, cd.project());
return false;
}
throw new OrmException("unable to check permissions on change " + cd.getId(), e);
} catch (AuthException e) {
+ logger.atFine().log("Filter out non-visisble change: %s", cd);
return false;
}
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index f8f892b..5667869 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -1107,7 +1107,7 @@
public Predicate<ChangeData> query(String name) throws QueryParseException {
try (Repository git = args.repoManager.openRepository(args.allUsersName)) {
VersionedAccountQueries q = VersionedAccountQueries.forUser(self());
- q.load(git);
+ q.load(args.allUsersName, git);
String query = q.getQueryList().getQuery(name);
if (query != null) {
return parse(query);
@@ -1131,7 +1131,7 @@
public Predicate<ChangeData> destination(String name) throws QueryParseException {
try (Repository git = args.repoManager.openRepository(args.allUsersName)) {
VersionedAccountDestinations d = VersionedAccountDestinations.forUser(self());
- d.load(git);
+ d.load(args.allUsersName, git);
Set<Branch.NameKey> destinations = d.getDestinationList().getDestinations(name);
if (destinations != null && !destinations.isEmpty()) {
return new DestinationPredicate(destinations, name);
diff --git a/java/com/google/gerrit/server/query/change/ConflictKey.java b/java/com/google/gerrit/server/query/change/ConflictKey.java
index 52904f7..42f5b13 100644
--- a/java/com/google/gerrit/server/query/change/ConflictKey.java
+++ b/java/com/google/gerrit/server/query/change/ConflictKey.java
@@ -20,10 +20,10 @@
import com.google.common.base.Enums;
import com.google.common.collect.Ordering;
import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.server.cache.CacheSerializer;
-import com.google.gerrit.server.cache.ProtoCacheSerializers;
-import com.google.gerrit.server.cache.ProtoCacheSerializers.ObjectIdConverter;
import com.google.gerrit.server.cache.proto.Cache.ConflictKeyProto;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers.ObjectIdConverter;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
diff --git a/java/com/google/gerrit/server/query/change/ConflictsCacheImpl.java b/java/com/google/gerrit/server/query/change/ConflictsCacheImpl.java
index 0b8c5ee..426c5d6 100644
--- a/java/com/google/gerrit/server/query/change/ConflictsCacheImpl.java
+++ b/java/com/google/gerrit/server/query/change/ConflictsCacheImpl.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.query.change;
import com.google.common.cache.Cache;
-import com.google.gerrit.server.cache.BooleanCacheSerializer;
import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.serialize.BooleanCacheSerializer;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index 3c62848..495d27c 100644
--- a/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -138,7 +138,21 @@
}
public List<ChangeData> byBranchKey(Branch.NameKey branch, Change.Key key) throws OrmException {
- return query(and(ref(branch), project(branch.getParentKey()), change(key)));
+ return query(byBranchKeyPred(branch, key));
+ }
+
+ public List<ChangeData> byBranchKeyOpen(Project.NameKey project, String branch, Change.Key key)
+ throws OrmException {
+ return query(and(byBranchKeyPred(new Branch.NameKey(project, branch), key), open()));
+ }
+
+ public static Predicate<ChangeData> byBranchKeyOpenPred(
+ Project.NameKey project, String branch, Change.Key key) {
+ return and(byBranchKeyPred(new Branch.NameKey(project, branch), key), open());
+ }
+
+ private static Predicate<ChangeData> byBranchKeyPred(Branch.NameKey branch, Change.Key key) {
+ return and(ref(branch), project(branch.getParentKey()), change(key));
}
public List<ChangeData> byProject(Project.NameKey project) throws OrmException {
@@ -264,13 +278,28 @@
public List<ChangeData> byBranchCommit(String project, String branch, String hash)
throws OrmException {
- return query(and(new ProjectPredicate(project), new RefPredicate(branch), commit(hash)));
+ return query(byBranchCommitPred(project, branch, hash));
}
public List<ChangeData> byBranchCommit(Branch.NameKey branch, String hash) throws OrmException {
return byBranchCommit(branch.getParentKey().get(), branch.get(), hash);
}
+ public List<ChangeData> byBranchCommitOpen(String project, String branch, String hash)
+ throws OrmException {
+ return query(and(byBranchCommitPred(project, branch, hash), open()));
+ }
+
+ public static Predicate<ChangeData> byBranchCommitOpenPred(
+ Project.NameKey project, String branch, String hash) {
+ return and(byBranchCommitPred(project.get(), branch, hash), open());
+ }
+
+ private static Predicate<ChangeData> byBranchCommitPred(
+ String project, String branch, String hash) {
+ return and(new ProjectPredicate(project), new RefPredicate(branch), commit(hash));
+ }
+
public List<ChangeData> bySubmissionId(String cs) throws OrmException {
if (Strings.isNullOrEmpty(cs)) {
return Collections.emptyList();
diff --git a/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java b/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java
index ffa59c2..144a81c 100644
--- a/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java
+++ b/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.query.group;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.index.query.IsVisibleToPredicate;
import com.google.gerrit.server.CurrentUser;
@@ -24,6 +25,8 @@
import com.google.gwtorm.server.OrmException;
public class GroupIsVisibleToPredicate extends IsVisibleToPredicate<InternalGroup> {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
protected final GroupControl.GenericFactory groupControlFactory;
protected final CurrentUser user;
@@ -37,7 +40,11 @@
@Override
public boolean match(InternalGroup group) throws OrmException {
try {
- return groupControlFactory.controlFor(user, group.getGroupUUID()).isVisible();
+ boolean canSee = groupControlFactory.controlFor(user, group.getGroupUUID()).isVisible();
+ if (!canSee) {
+ logger.atFine().log("Filter out non-visisble group: %s", group.getGroupUUID());
+ }
+ return canSee;
} catch (NoSuchGroupException e) {
// Ignored
return false;
diff --git a/java/com/google/gerrit/server/query/project/ProjectIsVisibleToPredicate.java b/java/com/google/gerrit/server/query/project/ProjectIsVisibleToPredicate.java
index dc567ea..b1c5af0 100644
--- a/java/com/google/gerrit/server/query/project/ProjectIsVisibleToPredicate.java
+++ b/java/com/google/gerrit/server/query/project/ProjectIsVisibleToPredicate.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.query.project;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.index.project.ProjectData;
import com.google.gerrit.index.query.IsVisibleToPredicate;
import com.google.gerrit.server.CurrentUser;
@@ -24,6 +25,8 @@
import com.google.gwtorm.server.OrmException;
public class ProjectIsVisibleToPredicate extends IsVisibleToPredicate<ProjectData> {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
protected final PermissionBackend permissionBackend;
protected final CurrentUser user;
@@ -36,13 +39,19 @@
@Override
public boolean match(ProjectData pd) throws OrmException {
if (!pd.getProject().getState().permitsRead()) {
+ logger.atFine().log("Filter out non-readable project: %s", pd);
return false;
}
- return permissionBackend
- .user(user)
- .project(pd.getProject().getNameKey())
- .testOrFalse(ProjectPermission.ACCESS);
+ boolean canSee =
+ permissionBackend
+ .user(user)
+ .project(pd.getProject().getNameKey())
+ .testOrFalse(ProjectPermission.ACCESS);
+ if (!canSee) {
+ logger.atFine().log("Filter out non-visible project: %s", pd);
+ }
+ return canSee;
}
@Override
diff --git a/java/com/google/gerrit/server/restapi/account/CreateAccount.java b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
index dc447de..0e8eb70 100644
--- a/java/com/google/gerrit/server/restapi/account/CreateAccount.java
+++ b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
@@ -55,6 +55,7 @@
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
@@ -63,6 +64,7 @@
import org.eclipse.jgit.errors.ConfigInvalidException;
@RequiresCapability(GlobalCapability.CREATE_ACCOUNT)
+@Singleton
public class CreateAccount
implements RestCollectionCreateView<TopLevelResource, AccountResource, AccountInput> {
private final Sequences seq;
@@ -113,8 +115,7 @@
throw new BadRequestException("username must match URL");
}
if (!ExternalId.isValidUsername(username)) {
- throw new BadRequestException(
- "Username '" + username + "' must contain only letters, numbers, _, - or .");
+ throw new BadRequestException("Invalid username '" + username + "'");
}
Set<AccountGroup.UUID> groups = parseGroups(input.groups);
diff --git a/java/com/google/gerrit/server/restapi/account/CreateEmail.java b/java/com/google/gerrit/server/restapi/account/CreateEmail.java
index cdfb1fa..e4e8525 100644
--- a/java/com/google/gerrit/server/restapi/account/CreateEmail.java
+++ b/java/com/google/gerrit/server/restapi/account/CreateEmail.java
@@ -44,9 +44,11 @@
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
+@Singleton
public class CreateEmail
implements RestCollectionCreateView<AccountResource, AccountResource.Email, EmailInput> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
diff --git a/java/com/google/gerrit/server/restapi/account/GetCapabilities.java b/java/com/google/gerrit/server/restapi/account/GetCapabilities.java
index 7889f6e..5c466bf 100644
--- a/java/com/google/gerrit/server/restapi/account/GetCapabilities.java
+++ b/java/com/google/gerrit/server/restapi/account/GetCapabilities.java
@@ -26,8 +26,8 @@
import com.google.gerrit.extensions.api.access.PluginPermission;
import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.CurrentUser;
@@ -172,9 +172,9 @@
}
@Override
- public BinaryResult apply(Capability resource) throws ResourceNotFoundException {
+ public Response<String> apply(Capability resource) throws ResourceNotFoundException {
permissionBackend.checkUsesDefaultCapabilities();
- return BinaryResult.create("ok\n");
+ return Response.ok("ok");
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetEmails.java b/java/com/google/gerrit/server/restapi/account/GetEmails.java
index 85262ee..ed3347f 100644
--- a/java/com/google/gerrit/server/restapi/account/GetEmails.java
+++ b/java/com/google/gerrit/server/restapi/account/GetEmails.java
@@ -14,6 +14,9 @@
package com.google.gerrit.server.restapi.account;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
+
import com.google.gerrit.extensions.common.EmailInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestReadView;
@@ -25,10 +28,8 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
@Singleton
public class GetEmails implements RestReadView<AccountResource> {
@@ -47,24 +48,19 @@
if (!self.get().hasSameAccountId(rsrc.getUser())) {
permissionBackend.currentUser().check(GlobalPermission.MODIFY_ACCOUNT);
}
+ return rsrc.getUser()
+ .getEmailAddresses()
+ .stream()
+ .filter(Objects::nonNull)
+ .map(e -> toEmailInfo(rsrc, e))
+ .sorted(comparing((EmailInfo e) -> e.email))
+ .collect(toList());
+ }
- List<EmailInfo> emails = new ArrayList<>();
- for (String email : rsrc.getUser().getEmailAddresses()) {
- if (email != null) {
- EmailInfo e = new EmailInfo();
- e.email = email;
- e.preferred(rsrc.getUser().getAccount().getPreferredEmail());
- emails.add(e);
- }
- }
- Collections.sort(
- emails,
- new Comparator<EmailInfo>() {
- @Override
- public int compare(EmailInfo a, EmailInfo b) {
- return a.email.compareTo(b.email);
- }
- });
- return emails;
+ private static EmailInfo toEmailInfo(AccountResource rsrc, String email) {
+ EmailInfo e = new EmailInfo();
+ e.email = email;
+ e.preferred(rsrc.getUser().getAccount().getPreferredEmail());
+ return e;
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java b/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java
index 112bb24..61021be 100644
--- a/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java
+++ b/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java
@@ -14,8 +14,10 @@
package com.google.gerrit.server.restapi.account;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
+
import com.google.common.base.Strings;
-import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -36,11 +38,7 @@
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
-import java.util.Map;
import org.eclipse.jgit.errors.ConfigInvalidException;
@Singleton
@@ -67,31 +65,28 @@
Account.Id accountId = rsrc.getUser().getAccountId();
AccountState account = accounts.get(accountId).orElseThrow(ResourceNotFoundException::new);
- List<ProjectWatchInfo> projectWatchInfos = new ArrayList<>();
- for (Map.Entry<ProjectWatchKey, ImmutableSet<NotifyType>> e :
- account.getProjectWatches().entrySet()) {
- ProjectWatchInfo pwi = new ProjectWatchInfo();
- pwi.filter = e.getKey().filter();
- pwi.project = e.getKey().project().get();
- pwi.notifyAbandonedChanges = toBoolean(e.getValue().contains(NotifyType.ABANDONED_CHANGES));
- pwi.notifyNewChanges = toBoolean(e.getValue().contains(NotifyType.NEW_CHANGES));
- pwi.notifyNewPatchSets = toBoolean(e.getValue().contains(NotifyType.NEW_PATCHSETS));
- pwi.notifySubmittedChanges = toBoolean(e.getValue().contains(NotifyType.SUBMITTED_CHANGES));
- pwi.notifyAllComments = toBoolean(e.getValue().contains(NotifyType.ALL_COMMENTS));
- projectWatchInfos.add(pwi);
- }
- Collections.sort(
- projectWatchInfos,
- new Comparator<ProjectWatchInfo>() {
- @Override
- public int compare(ProjectWatchInfo pwi1, ProjectWatchInfo pwi2) {
- return ComparisonChain.start()
- .compare(pwi1.project, pwi2.project)
- .compare(Strings.nullToEmpty(pwi1.filter), Strings.nullToEmpty(pwi2.filter))
- .result();
- }
- });
- return projectWatchInfos;
+ return account
+ .getProjectWatches()
+ .entrySet()
+ .stream()
+ .map(e -> toProjectWatchInfo(e.getKey(), e.getValue()))
+ .sorted(
+ comparing((ProjectWatchInfo pwi) -> pwi.project)
+ .thenComparing(pwi -> Strings.nullToEmpty(pwi.filter)))
+ .collect(toList());
+ }
+
+ private static ProjectWatchInfo toProjectWatchInfo(
+ ProjectWatchKey key, ImmutableSet<NotifyType> watchTypes) {
+ ProjectWatchInfo pwi = new ProjectWatchInfo();
+ pwi.filter = key.filter();
+ pwi.project = key.project().get();
+ pwi.notifyAbandonedChanges = toBoolean(watchTypes.contains(NotifyType.ABANDONED_CHANGES));
+ pwi.notifyNewChanges = toBoolean(watchTypes.contains(NotifyType.NEW_CHANGES));
+ pwi.notifyNewPatchSets = toBoolean(watchTypes.contains(NotifyType.NEW_PATCHSETS));
+ pwi.notifySubmittedChanges = toBoolean(watchTypes.contains(NotifyType.SUBMITTED_CHANGES));
+ pwi.notifyAllComments = toBoolean(watchTypes.contains(NotifyType.ALL_COMMENTS));
+ return pwi;
}
private static Boolean toBoolean(boolean value) {
diff --git a/java/com/google/gerrit/server/restapi/change/CommentJson.java b/java/com/google/gerrit/server/restapi/change/CommentJson.java
index e92abe1..a562592 100644
--- a/java/com/google/gerrit/server/restapi/change/CommentJson.java
+++ b/java/com/google/gerrit/server/restapi/change/CommentJson.java
@@ -14,11 +14,13 @@
package com.google.gerrit.server.restapi.change;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.server.CommentsUtil.COMMENT_INFO_ORDER;
import static java.util.stream.Collectors.toList;
import com.google.common.base.Strings;
-import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Streams;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.Comment.Range;
import com.google.gerrit.extensions.client.Side;
@@ -35,7 +37,6 @@
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -96,9 +97,7 @@
list.add(o);
}
- for (List<T> list : out.values()) {
- Collections.sort(list, COMMENT_INFO_ORDER);
- }
+ out.values().forEach(l -> l.sort(COMMENT_INFO_ORDER));
if (loader != null) {
loader.fill();
@@ -106,13 +105,14 @@
return out;
}
- public List<T> formatAsList(Iterable<F> comments) throws PermissionBackendException {
+ public ImmutableList<T> formatAsList(Iterable<F> comments) throws PermissionBackendException {
AccountLoader loader = fillAccounts ? accountLoaderFactory.create(true) : null;
- List<T> out =
- FluentIterable.from(comments)
- .transform(c -> toInfo(c, loader))
- .toSortedList(COMMENT_INFO_ORDER);
+ ImmutableList<T> out =
+ Streams.stream(comments)
+ .map(c -> toInfo(c, loader))
+ .sorted(COMMENT_INFO_ORDER)
+ .collect(toImmutableList());
if (loader != null) {
loader.fill();
diff --git a/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java b/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java
index db8ef0c..dbd0ccf 100644
--- a/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java
+++ b/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.restapi.change;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Comment;
@@ -61,7 +62,7 @@
.format(listComments(rsrc));
}
- public List<CommentInfo> getComments(RevisionResource rsrc)
+ public ImmutableList<CommentInfo> getComments(RevisionResource rsrc)
throws OrmException, PermissionBackendException {
return commentJson
.get()
diff --git a/java/com/google/gerrit/server/restapi/change/ListRobotComments.java b/java/com/google/gerrit/server/restapi/change/ListRobotComments.java
index 66138ab..99366aa 100644
--- a/java/com/google/gerrit/server/restapi/change/ListRobotComments.java
+++ b/java/com/google/gerrit/server/restapi/change/ListRobotComments.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.restapi.change;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.extensions.common.RobotCommentInfo;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.RobotComment;
@@ -52,7 +53,7 @@
.format(listComments(rsrc));
}
- public List<RobotCommentInfo> getComments(RevisionResource rsrc)
+ public ImmutableList<RobotCommentInfo> getComments(RevisionResource rsrc)
throws OrmException, PermissionBackendException {
return commentJson
.get()
diff --git a/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java b/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
index 47c6970..6b7a708 100644
--- a/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
+++ b/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
@@ -107,8 +107,13 @@
ProjectState projectState,
List<Account.Id> candidateList)
throws OrmException, IOException, ConfigInvalidException {
+ logger.atFine().log("Candidates %s", candidateList);
+
String query = suggestReviewers.getQuery();
+ logger.atFine().log("query: %s", query);
+
double baseWeight = config.getInt("addReviewer", "baseWeight", 1);
+ logger.atFine().log("base weight: %s", baseWeight);
Map<Account.Id, MutableDouble> reviewerScores;
if (Strings.isNullOrEmpty(query)) {
@@ -116,6 +121,7 @@
} else {
reviewerScores = baseRankingForCandidateList(candidateList, projectState, baseWeight);
}
+ logger.atFine().log("Base reviewer scores: %s", reviewerScores);
// Send the query along with a candidate list to all plugins and merge the
// results. Plugins don't necessarily need to use the candidates list, they
@@ -163,6 +169,7 @@
}
}
}
+ logger.atFine().log("Reviewer scores: %s", reviewerScores);
} catch (ExecutionException | InterruptedException e) {
logger.atSevere().withCause(e).log("Exception while suggesting reviewers");
return ImmutableList.of();
@@ -170,12 +177,20 @@
if (changeNotes != null) {
// Remove change owner
- reviewerScores.remove(changeNotes.getChange().getOwner());
+ if (reviewerScores.remove(changeNotes.getChange().getOwner()) != null) {
+ logger.atFine().log("Remove change owner %s", changeNotes.getChange().getOwner());
+ }
// Remove existing reviewers
- reviewerScores
- .keySet()
- .removeAll(approvalsUtil.getReviewers(dbProvider.get(), changeNotes).byState(REVIEWER));
+ approvalsUtil
+ .getReviewers(dbProvider.get(), changeNotes)
+ .byState(REVIEWER)
+ .forEach(
+ r -> {
+ if (reviewerScores.remove(r) != null) {
+ logger.atFine().log("Remove existing reviewer %s", r);
+ }
+ });
}
// Sort results
@@ -185,6 +200,7 @@
.stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
List<Account.Id> sortedSuggestions = sorted.map(Map.Entry::getKey).collect(toList());
+ logger.atFine().log("Sorted suggestions: %s", sortedSuggestions);
return sortedSuggestions;
}
diff --git a/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java b/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
index 57ff0a3..3becf24 100644
--- a/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
+++ b/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
@@ -44,6 +44,7 @@
import com.google.gerrit.server.account.AccountControl;
import com.google.gerrit.server.account.AccountDirectory.FillOptions;
import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupMembers;
import com.google.gerrit.server.index.account.AccountField;
@@ -228,24 +229,30 @@
// For performance reasons we don't use AccountQueryProvider as it would always load the
// complete account from the cache (or worse, from NoteDb) even though we only need the ID
// which we can directly get from the returned results.
+ Predicate<AccountState> pred =
+ Predicate.and(
+ AccountPredicates.isActive(),
+ accountQueryBuilder.defaultQuery(suggestReviewers.getQuery()));
+ logger.atFine().log("accounts index query: %s", pred);
ResultSet<FieldBundle> result =
accountIndexes
.getSearchIndex()
.getSource(
- Predicate.and(
- AccountPredicates.isActive(),
- accountQueryBuilder.defaultQuery(suggestReviewers.getQuery())),
+ pred,
QueryOptions.create(
indexConfig,
0,
suggestReviewers.getLimit() * CANDIDATE_LIST_MULTIPLIER,
ImmutableSet.of(AccountField.ID.getName())))
.readRaw();
- return result
- .toList()
- .stream()
- .map(f -> new Account.Id(f.getValue(AccountField.ID).intValue()))
- .collect(toList());
+ List<Account.Id> matches =
+ result
+ .toList()
+ .stream()
+ .map(f -> new Account.Id(f.getValue(AccountField.ID).intValue()))
+ .collect(toList());
+ logger.atFine().log("Matches: %s", matches);
+ return matches;
} catch (QueryParseException e) {
return ImmutableList.of();
}
@@ -374,8 +381,10 @@
GroupAsReviewer result = new GroupAsReviewer();
int maxAllowed = suggestReviewers.getMaxAllowed();
int maxAllowedWithoutConfirmation = suggestReviewers.getMaxAllowedWithoutConfirmation();
+ logger.atFine().log("maxAllowedWithoutConfirmation: " + maxAllowedWithoutConfirmation);
if (!PostReviewers.isLegalReviewerGroup(group.getUUID())) {
+ logger.atFine().log("Ignore group %s that is not legal as reviewer", group.getUUID());
return result;
}
@@ -383,6 +392,7 @@
Set<Account> members = groupMembers.listAccounts(group.getUUID(), project.getNameKey());
if (members.isEmpty()) {
+ logger.atFine().log("Ignore group %s since it has no members", group.getUUID());
return result;
}
@@ -392,6 +402,11 @@
}
boolean needsConfirmation = result.size > maxAllowedWithoutConfirmation;
+ if (needsConfirmation) {
+ logger.atFine().log(
+ "group %s needs confirmation to be added as reviewer, it has %d members",
+ group.getUUID(), result.size);
+ }
// require that at least one member in the group can see the change
for (Account account : members) {
@@ -401,9 +416,12 @@
} else {
result.allowed = true;
}
+ logger.atFine().log("Suggest group %s", group.getUUID());
return result;
}
}
+ logger.atFine().log(
+ "Ignore group %s since none of its members can see the change", group.getUUID());
} catch (NoSuchProjectException e) {
return result;
}
diff --git a/java/com/google/gerrit/server/restapi/change/SetPrivateOp.java b/java/com/google/gerrit/server/restapi/change/SetPrivateOp.java
index 1b50834..8aac92c 100644
--- a/java/com/google/gerrit/server/restapi/change/SetPrivateOp.java
+++ b/java/com/google/gerrit/server/restapi/change/SetPrivateOp.java
@@ -18,8 +18,11 @@
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.ChangeMessagesUtil;
+import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.extensions.events.PrivateStateChanged;
+import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
@@ -44,19 +47,23 @@
}
private final ChangeMessagesUtil cmUtil;
+ private final PatchSetUtil psUtil;
private final boolean isPrivate;
private final Input input;
private final PrivateStateChanged privateStateChanged;
private Change change;
+ private PatchSet ps;
@Inject
SetPrivateOp(
PrivateStateChanged privateStateChanged,
+ PatchSetUtil psUtil,
@Assisted ChangeMessagesUtil cmUtil,
@Assisted boolean isPrivate,
@Assisted Input input) {
this.cmUtil = cmUtil;
+ this.psUtil = psUtil;
this.isPrivate = isPrivate;
this.input = input;
this.privateStateChanged = privateStateChanged;
@@ -65,6 +72,8 @@
@Override
public boolean updateChange(ChangeContext ctx) throws ResourceConflictException, OrmException {
change = ctx.getChange();
+ ChangeNotes notes = ctx.getNotes();
+ ps = psUtil.get(ctx.getDb(), notes, change.currentPatchSetId());
ChangeUpdate update = ctx.getUpdate(change.currentPatchSetId());
change.setPrivate(isPrivate);
change.setLastUpdatedOn(ctx.getWhen());
@@ -75,7 +84,7 @@
@Override
public void postUpdate(Context ctx) {
- privateStateChanged.fire(change, ctx.getAccount(), ctx.getWhen());
+ privateStateChanged.fire(change, ps, ctx.getAccount(), ctx.getWhen());
}
private void addMessage(ChangeContext ctx, ChangeUpdate update) throws OrmException {
diff --git a/java/com/google/gerrit/server/restapi/change/SuggestReviewers.java b/java/com/google/gerrit/server/restapi/change/SuggestReviewers.java
index 8b39e8e..b1d49f2 100644
--- a/java/com/google/gerrit/server/restapi/change/SuggestReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/SuggestReviewers.java
@@ -16,6 +16,7 @@
import static com.google.gerrit.server.config.GerritConfigListenerHelper.acceptIfChanged;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.common.AccountVisibility;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.ConfigKey;
@@ -27,6 +28,8 @@
import org.kohsuke.args4j.Option;
public class SuggestReviewers {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
private static final int DEFAULT_MAX_SUGGESTED = 10;
protected final Provider<ReviewDb> dbProvider;
@@ -101,6 +104,8 @@
"addreviewer",
"maxWithoutConfirmation",
PostReviewers.DEFAULT_MAX_REVIEWERS_WITHOUT_CHECK);
+
+ logger.atFine().log("AccountVisibility: %s", av.name());
}
public static GerritConfigListener configListener() {
diff --git a/java/com/google/gerrit/server/restapi/config/CachesCollection.java b/java/com/google/gerrit/server/restapi/config/CachesCollection.java
index 152fef9..a4b8802 100644
--- a/java/com/google/gerrit/server/restapi/config/CachesCollection.java
+++ b/java/com/google/gerrit/server/restapi/config/CachesCollection.java
@@ -20,6 +20,7 @@
import com.google.common.cache.Cache;
import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.PluginName;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
@@ -66,7 +67,7 @@
permissionBackend.currentUser().check(GlobalPermission.VIEW_CACHES);
String cacheName = id.get();
- String pluginName = "gerrit";
+ String pluginName = PluginName.GERRIT;
int i = cacheName.lastIndexOf('-');
if (i != -1) {
pluginName = cacheName.substring(0, i);
diff --git a/java/com/google/gerrit/server/restapi/config/GetDiffPreferences.java b/java/com/google/gerrit/server/restapi/config/GetDiffPreferences.java
index 4a89dfc..13c2818 100644
--- a/java/com/google/gerrit/server/restapi/config/GetDiffPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/GetDiffPreferences.java
@@ -44,7 +44,7 @@
public DiffPreferencesInfo apply(ConfigResource configResource)
throws BadRequestException, ResourceConflictException, IOException, ConfigInvalidException {
try (Repository git = gitManager.openRepository(allUsersName)) {
- return Preferences.readDefaultDiffPreferences(git);
+ return Preferences.readDefaultDiffPreferences(allUsersName, git);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/GetEditPreferences.java b/java/com/google/gerrit/server/restapi/config/GetEditPreferences.java
index 44466d3..2ec547b 100644
--- a/java/com/google/gerrit/server/restapi/config/GetEditPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/GetEditPreferences.java
@@ -43,7 +43,7 @@
public EditPreferencesInfo apply(ConfigResource configResource)
throws BadRequestException, ResourceConflictException, IOException, ConfigInvalidException {
try (Repository git = gitManager.openRepository(allUsersName)) {
- return Preferences.readDefaultEditPreferences(git);
+ return Preferences.readDefaultEditPreferences(allUsersName, git);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/GetPreferences.java b/java/com/google/gerrit/server/restapi/config/GetPreferences.java
index e0c54d4..4dbbc8c 100644
--- a/java/com/google/gerrit/server/restapi/config/GetPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/GetPreferences.java
@@ -41,7 +41,7 @@
public GeneralPreferencesInfo apply(ConfigResource rsrc)
throws IOException, ConfigInvalidException {
try (Repository git = gitMgr.openRepository(allUsersName)) {
- return Preferences.readDefaultGeneralPreferences(git);
+ return Preferences.readDefaultGeneralPreferences(allUsersName, git);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index b7931bd..66e9f90 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -64,7 +64,6 @@
import com.google.gerrit.server.restapi.change.AllowedFormats;
import com.google.gerrit.server.submit.MergeSuperSet;
import com.google.inject.Inject;
-import java.net.MalformedURLException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
@@ -149,44 +148,42 @@
}
@Override
- public ServerInfo apply(ConfigResource rsrc)
- throws MalformedURLException, PermissionBackendException {
+ public ServerInfo apply(ConfigResource rsrc) throws PermissionBackendException {
ServerInfo info = new ServerInfo();
- info.accounts = getAccountsInfo(accountVisibilityProvider);
- info.auth = getAuthInfo(authConfig, realm);
- info.change = getChangeInfo(config);
- info.download =
- getDownloadInfo(downloadSchemes, downloadCommands, cloneCommands, archiveFormats);
- info.gerrit = getGerritInfo(config, allProjectsName, allUsersName);
+ info.accounts = getAccountsInfo();
+ info.auth = getAuthInfo();
+ info.change = getChangeInfo();
+ info.download = getDownloadInfo();
+ info.gerrit = getGerritInfo();
info.noteDbEnabled = toBoolean(isNoteDbEnabled());
info.plugin = getPluginInfo();
if (Files.exists(sitePaths.site_theme)) {
info.defaultTheme = "/static/" + SitePaths.THEME_FILENAME;
}
- info.sshd = getSshdInfo(config);
- info.suggest = getSuggestInfo(config);
+ info.sshd = getSshdInfo();
+ info.suggest = getSuggestInfo();
- Map<String, String> urlAliases = getUrlAliasesInfo(config);
+ Map<String, String> urlAliases = getUrlAliasesInfo();
info.urlAliases = !urlAliases.isEmpty() ? urlAliases : null;
- info.user = getUserInfo(anonymousCowardName);
+ info.user = getUserInfo();
info.receive = getReceiveInfo();
return info;
}
- private AccountsInfo getAccountsInfo(AccountVisibilityProvider accountVisibilityProvider) {
+ private AccountsInfo getAccountsInfo() {
AccountsInfo info = new AccountsInfo();
info.visibility = accountVisibilityProvider.get();
return info;
}
- private AuthInfo getAuthInfo(AuthConfig cfg, Realm realm) throws PermissionBackendException {
+ private AuthInfo getAuthInfo() throws PermissionBackendException {
AuthInfo info = new AuthInfo();
- info.authType = cfg.getAuthType();
- info.useContributorAgreements = toBoolean(cfg.isUseContributorAgreements());
+ info.authType = authConfig.getAuthType();
+ info.useContributorAgreements = toBoolean(authConfig.isUseContributorAgreements());
info.editableAccountFields = new ArrayList<>(realm.getEditableFields());
- info.switchAccountUrl = cfg.getSwitchAccountUrl();
- info.gitBasicAuthPolicy = cfg.getGitBasicAuthPolicy();
+ info.switchAccountUrl = authConfig.getSwitchAccountUrl();
+ info.gitBasicAuthPolicy = authConfig.getGitBasicAuthPolicy();
if (info.useContributorAgreements != null) {
Collection<ContributorAgreement> agreements =
@@ -202,22 +199,22 @@
switch (info.authType) {
case LDAP:
case LDAP_BIND:
- info.registerUrl = cfg.getRegisterUrl();
- info.registerText = cfg.getRegisterText();
- info.editFullNameUrl = cfg.getEditFullNameUrl();
+ info.registerUrl = authConfig.getRegisterUrl();
+ info.registerText = authConfig.getRegisterText();
+ info.editFullNameUrl = authConfig.getEditFullNameUrl();
break;
case CUSTOM_EXTENSION:
- info.registerUrl = cfg.getRegisterUrl();
- info.registerText = cfg.getRegisterText();
- info.editFullNameUrl = cfg.getEditFullNameUrl();
- info.httpPasswordUrl = cfg.getHttpPasswordUrl();
+ info.registerUrl = authConfig.getRegisterUrl();
+ info.registerText = authConfig.getRegisterText();
+ info.editFullNameUrl = authConfig.getEditFullNameUrl();
+ info.httpPasswordUrl = authConfig.getHttpPasswordUrl();
break;
case HTTP:
case HTTP_LDAP:
- info.loginUrl = cfg.getLoginUrl();
- info.loginText = cfg.getLoginText();
+ info.loginUrl = authConfig.getLoginUrl();
+ info.loginText = authConfig.getLoginText();
break;
case CLIENT_SSL_CERT_LDAP:
@@ -230,40 +227,37 @@
return info;
}
- private ChangeConfigInfo getChangeInfo(Config cfg) {
+ private ChangeConfigInfo getChangeInfo() {
ChangeConfigInfo info = new ChangeConfigInfo();
- info.allowBlame = toBoolean(cfg.getBoolean("change", "allowBlame", true));
+ info.allowBlame = toBoolean(config.getBoolean("change", "allowBlame", true));
boolean hasAssigneeInIndex =
indexes.getSearchIndex().getSchema().hasField(ChangeField.ASSIGNEE);
info.showAssigneeInChangesTable =
toBoolean(
- cfg.getBoolean("change", "showAssigneeInChangesTable", false) && hasAssigneeInIndex);
- info.largeChange = cfg.getInt("change", "largeChange", 500);
+ config.getBoolean("change", "showAssigneeInChangesTable", false) && hasAssigneeInIndex);
+ info.largeChange = config.getInt("change", "largeChange", 500);
info.replyTooltip =
- Optional.ofNullable(cfg.getString("change", null, "replyTooltip")).orElse("Reply and score")
+ Optional.ofNullable(config.getString("change", null, "replyTooltip"))
+ .orElse("Reply and score")
+ " (Shortcut: a)";
info.replyLabel =
- Optional.ofNullable(cfg.getString("change", null, "replyLabel")).orElse("Reply") + "\u2026";
+ Optional.ofNullable(config.getString("change", null, "replyLabel")).orElse("Reply")
+ + "\u2026";
info.updateDelay =
- (int) ConfigUtil.getTimeUnit(cfg, "change", null, "updateDelay", 300, TimeUnit.SECONDS);
- info.submitWholeTopic = MergeSuperSet.wholeTopicEnabled(cfg);
+ (int) ConfigUtil.getTimeUnit(config, "change", null, "updateDelay", 300, TimeUnit.SECONDS);
+ info.submitWholeTopic = MergeSuperSet.wholeTopicEnabled(config);
info.disablePrivateChanges =
- toBoolean(config.getBoolean("change", null, "disablePrivateChanges", false));
+ toBoolean(this.config.getBoolean("change", null, "disablePrivateChanges", false));
return info;
}
- private DownloadInfo getDownloadInfo(
- DynamicMap<DownloadScheme> downloadSchemes,
- DynamicMap<DownloadCommand> downloadCommands,
- DynamicMap<CloneCommand> cloneCommands,
- AllowedFormats archiveFormats) {
+ private DownloadInfo getDownloadInfo() {
DownloadInfo info = new DownloadInfo();
info.schemes = new HashMap<>();
for (DynamicMap.Entry<DownloadScheme> e : downloadSchemes) {
DownloadScheme scheme = e.getProvider().get();
if (scheme.isEnabled() && scheme.getUrl("${project}") != null) {
- info.schemes.put(
- e.getExportName(), getDownloadSchemeInfo(scheme, downloadCommands, cloneCommands));
+ info.schemes.put(e.getExportName(), getDownloadSchemeInfo(scheme));
}
}
info.archives =
@@ -271,10 +265,7 @@
return info;
}
- private DownloadSchemeInfo getDownloadSchemeInfo(
- DownloadScheme scheme,
- DynamicMap<DownloadCommand> downloadCommands,
- DynamicMap<CloneCommand> cloneCommands) {
+ private DownloadSchemeInfo getDownloadSchemeInfo(DownloadScheme scheme) {
DownloadSchemeInfo info = new DownloadSchemeInfo();
info.url = scheme.getUrl("${project}");
info.isAuthRequired = toBoolean(scheme.isAuthRequired());
@@ -304,17 +295,16 @@
return info;
}
- private GerritInfo getGerritInfo(
- Config cfg, AllProjectsName allProjectsName, AllUsersName allUsersName) {
+ private GerritInfo getGerritInfo() {
GerritInfo info = new GerritInfo();
info.allProjects = allProjectsName.get();
info.allUsers = allUsersName.get();
- info.reportBugUrl = cfg.getString("gerrit", null, "reportBugUrl");
- info.reportBugText = cfg.getString("gerrit", null, "reportBugText");
- info.docUrl = getDocUrl(cfg);
+ info.reportBugUrl = config.getString("gerrit", null, "reportBugUrl");
+ info.reportBugText = config.getString("gerrit", null, "reportBugText");
+ info.docUrl = getDocUrl();
info.docSearch = docSearcher.isAvailable();
info.editGpgKeys =
- toBoolean(enableSignedPush && cfg.getBoolean("gerrit", null, "editGpgKeys", true));
+ toBoolean(enableSignedPush && config.getBoolean("gerrit", null, "editGpgKeys", true));
info.webUis = EnumSet.noneOf(UiType.class);
info.webUis.add(UiType.POLYGERRIT);
if (gerritOptions.enableGwtUi()) {
@@ -323,8 +313,8 @@
return info;
}
- private String getDocUrl(Config cfg) {
- String docUrl = cfg.getString("gerrit", null, "docUrl");
+ private String getDocUrl() {
+ String docUrl = config.getString("gerrit", null, "docUrl");
if (Strings.isNullOrEmpty(docUrl)) {
return null;
}
@@ -352,18 +342,18 @@
return info;
}
- private Map<String, String> getUrlAliasesInfo(Config cfg) {
+ private Map<String, String> getUrlAliasesInfo() {
Map<String, String> urlAliases = new HashMap<>();
- for (String subsection : cfg.getSubsections(URL_ALIAS)) {
+ for (String subsection : config.getSubsections(URL_ALIAS)) {
urlAliases.put(
- cfg.getString(URL_ALIAS, subsection, KEY_MATCH),
- cfg.getString(URL_ALIAS, subsection, KEY_TOKEN));
+ config.getString(URL_ALIAS, subsection, KEY_MATCH),
+ config.getString(URL_ALIAS, subsection, KEY_TOKEN));
}
return urlAliases;
}
- private SshdInfo getSshdInfo(Config cfg) {
- String[] addr = cfg.getStringList("sshd", null, "listenAddress");
+ private SshdInfo getSshdInfo() {
+ String[] addr = config.getStringList("sshd", null, "listenAddress");
if (addr.length == 1 && isOff(addr[0])) {
return null;
}
@@ -376,13 +366,13 @@
|| "no".equalsIgnoreCase(listenHostname);
}
- private SuggestInfo getSuggestInfo(Config cfg) {
+ private SuggestInfo getSuggestInfo() {
SuggestInfo info = new SuggestInfo();
- info.from = cfg.getInt("suggest", "from", 0);
+ info.from = config.getInt("suggest", "from", 0);
return info;
}
- private UserConfigInfo getUserInfo(String anonymousCowardName) {
+ private UserConfigInfo getUserInfo() {
UserConfigInfo info = new UserConfigInfo();
info.anonymousCowardName = anonymousCowardName;
return info;
diff --git a/java/com/google/gerrit/server/restapi/config/ListCaches.java b/java/com/google/gerrit/server/restapi/config/ListCaches.java
index c0a9d71..38664fb 100644
--- a/java/com/google/gerrit/server/restapi/config/ListCaches.java
+++ b/java/com/google/gerrit/server/restapi/config/ListCaches.java
@@ -14,14 +14,16 @@
package com.google.gerrit.server.restapi.config;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
import static com.google.gerrit.common.data.GlobalCapability.VIEW_CACHES;
import static com.google.gerrit.server.config.CacheResource.cacheNameOf;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.joining;
-import com.google.common.base.Joiner;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheStats;
+import com.google.common.collect.Streams;
import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.BinaryResult;
@@ -29,11 +31,9 @@
import com.google.gerrit.server.cache.PersistentCache;
import com.google.gerrit.server.config.ConfigResource;
import com.google.inject.Inject;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
import java.util.Map;
import java.util.TreeMap;
+import java.util.stream.Stream;
import org.kohsuke.args4j.Option;
@RequiresAnyCapability({VIEW_CACHES, MAINTAIN_SERVER})
@@ -72,19 +72,17 @@
if (format == null) {
return getCacheInfos();
}
- List<String> cacheNames = new ArrayList<>();
- for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
- cacheNames.add(cacheNameOf(e.getPluginName(), e.getExportName()));
- }
- Collections.sort(cacheNames);
-
+ Stream<String> cacheNames =
+ Streams.stream(cacheMap)
+ .map(e -> cacheNameOf(e.getPluginName(), e.getExportName()))
+ .sorted();
if (OutputFormat.TEXT_LIST.equals(format)) {
- return BinaryResult.create(Joiner.on('\n').join(cacheNames))
+ return BinaryResult.create(cacheNames.collect(joining("\n")))
.base64()
.setContentType("text/plain")
.setCharacterEncoding(UTF_8);
}
- return cacheNames;
+ return cacheNames.collect(toImmutableList());
}
public enum CacheType {
diff --git a/java/com/google/gerrit/server/restapi/config/ListTasks.java b/java/com/google/gerrit/server/restapi/config/ListTasks.java
index 7b69831..f77cda4 100644
--- a/java/com/google/gerrit/server/restapi/config/ListTasks.java
+++ b/java/com/google/gerrit/server/restapi/config/ListTasks.java
@@ -14,7 +14,9 @@
package com.google.gerrit.server.restapi.config;
-import com.google.common.collect.ComparisonChain;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
+
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Project;
@@ -35,8 +37,6 @@
import com.google.inject.Singleton;
import java.sql.Timestamp;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -106,20 +106,14 @@
}
private List<TaskInfo> getTasks() {
- List<TaskInfo> taskInfos = workQueue.getTaskInfos(TaskInfo::new);
- Collections.sort(
- taskInfos,
- new Comparator<TaskInfo>() {
- @Override
- public int compare(TaskInfo a, TaskInfo b) {
- return ComparisonChain.start()
- .compare(a.state.ordinal(), b.state.ordinal())
- .compare(a.delay, b.delay)
- .compare(a.command, b.command)
- .result();
- }
- });
- return taskInfos;
+ return workQueue
+ .getTaskInfos(TaskInfo::new)
+ .stream()
+ .sorted(
+ comparing((TaskInfo t) -> t.state.ordinal())
+ .thenComparing(t -> t.delay)
+ .thenComparing(t -> t.command))
+ .collect(toList());
}
public static class TaskInfo {
diff --git a/java/com/google/gerrit/server/restapi/config/PostCaches.java b/java/com/google/gerrit/server/restapi/config/PostCaches.java
index 7f9b756..57ba097 100644
--- a/java/com/google/gerrit/server/restapi/config/PostCaches.java
+++ b/java/com/google/gerrit/server/restapi/config/PostCaches.java
@@ -20,6 +20,7 @@
import com.google.common.cache.Cache;
import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.PluginName;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.Response;
@@ -110,7 +111,7 @@
List<CacheResource> cacheResources = new ArrayList<>(cacheNames.size());
for (String n : cacheNames) {
- String pluginName = "gerrit";
+ String pluginName = PluginName.GERRIT;
String cacheName = n;
int i = cacheName.lastIndexOf('-');
if (i != -1) {
diff --git a/java/com/google/gerrit/server/restapi/group/AddMembers.java b/java/com/google/gerrit/server/restapi/group/AddMembers.java
index 648873d..a897e1a 100644
--- a/java/com/google/gerrit/server/restapi/group/AddMembers.java
+++ b/java/com/google/gerrit/server/restapi/group/AddMembers.java
@@ -216,6 +216,7 @@
return result;
}
+ @Singleton
public static class CreateMember
implements RestCollectionCreateView<GroupResource, MemberResource, Input> {
private final AddMembers put;
diff --git a/java/com/google/gerrit/server/restapi/group/AddSubgroups.java b/java/com/google/gerrit/server/restapi/group/AddSubgroups.java
index 948cb5b..ca77ebf 100644
--- a/java/com/google/gerrit/server/restapi/group/AddSubgroups.java
+++ b/java/com/google/gerrit/server/restapi/group/AddSubgroups.java
@@ -131,6 +131,7 @@
groupsUpdateProvider.get().updateGroup(parentGroupUuid, groupUpdate);
}
+ @Singleton
public static class CreateSubgroup
implements RestCollectionCreateView<GroupResource, SubgroupResource, Input> {
private final AddSubgroups addSubgroups;
diff --git a/java/com/google/gerrit/server/restapi/group/CreateGroup.java b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
index 2d062e9..64d515c 100644
--- a/java/com/google/gerrit/server/restapi/group/CreateGroup.java
+++ b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
@@ -57,6 +57,7 @@
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -69,6 +70,7 @@
import org.eclipse.jgit.lib.PersonIdent;
@RequiresCapability(GlobalCapability.CREATE_GROUP)
+@Singleton
public class CreateGroup
implements RestCollectionCreateView<TopLevelResource, GroupResource, GroupInput> {
private final Provider<IdentifiedUser> self;
diff --git a/java/com/google/gerrit/server/restapi/group/GetAuditLog.java b/java/com/google/gerrit/server/restapi/group/GetAuditLog.java
index 7af4284..dcdd8a8 100644
--- a/java/com/google/gerrit/server/restapi/group/GetAuditLog.java
+++ b/java/com/google/gerrit/server/restapi/group/GetAuditLog.java
@@ -41,7 +41,6 @@
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -138,7 +137,7 @@
accountLoader.fill();
// sort by date and then reverse so that the newest audit event comes first
- Collections.sort(auditEvents, comparing((GroupAuditEventInfo a) -> a.date).reversed());
+ auditEvents.sort(comparing((GroupAuditEventInfo a) -> a.date).reversed());
return auditEvents;
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/ListSubgroups.java b/java/com/google/gerrit/server/restapi/group/ListSubgroups.java
index 97a260e..864b01b 100644
--- a/java/com/google/gerrit/server/restapi/group/ListSubgroups.java
+++ b/java/com/google/gerrit/server/restapi/group/ListSubgroups.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.group;
import static com.google.common.base.Strings.nullToEmpty;
+import static java.util.Comparator.comparing;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.GroupDescription;
@@ -29,8 +30,6 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
@Singleton
@@ -72,18 +71,8 @@
continue;
}
}
- Collections.sort(
- included,
- new Comparator<GroupInfo>() {
- @Override
- public int compare(GroupInfo a, GroupInfo b) {
- int cmp = nullToEmpty(a.name).compareTo(nullToEmpty(b.name));
- if (cmp != 0) {
- return cmp;
- }
- return nullToEmpty(a.id).compareTo(nullToEmpty(b.id));
- }
- });
+ included.sort(
+ comparing((GroupInfo g) -> nullToEmpty(g.name)).thenComparing(g -> nullToEmpty(g.id)));
return included;
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/Check.java b/java/com/google/gerrit/server/restapi/project/Check.java
new file mode 100644
index 0000000..a6fd764
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/project/Check.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2018 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.restapi.project;
+
+import com.google.gerrit.extensions.api.projects.CheckProjectInput;
+import com.google.gerrit.extensions.api.projects.CheckProjectResultInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.project.ProjectsConsistencyChecker;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class Check implements RestModifyView<ProjectResource, CheckProjectInput> {
+ private final PermissionBackend permissionBackend;
+ private final ProjectsConsistencyChecker projectsConsistencyChecker;
+
+ @Inject
+ Check(
+ PermissionBackend permissionBackend, ProjectsConsistencyChecker projectsConsistencyChecker) {
+ this.permissionBackend = permissionBackend;
+ this.projectsConsistencyChecker = projectsConsistencyChecker;
+ }
+
+ @Override
+ public CheckProjectResultInfo apply(ProjectResource rsrc, CheckProjectInput input)
+ throws AuthException, BadRequestException, ResourceConflictException, Exception {
+ permissionBackend.user(rsrc.getUser()).check(GlobalPermission.ADMINISTRATE_SERVER);
+ return projectsConsistencyChecker.check(rsrc.getNameKey(), input);
+ }
+}
diff --git a/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java b/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
index 0d52090..60b5dee 100644
--- a/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
+++ b/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
@@ -33,10 +33,10 @@
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.ProjectConfigEntry;
import com.google.gerrit.server.extensions.webui.UiActions;
-import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.project.BooleanProjectConfigTransformations;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectState.EffectiveMaxObjectSizeLimit;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -48,7 +48,6 @@
boolean serverEnableSignedPush,
ProjectState projectState,
CurrentUser user,
- TransferConfig config,
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory,
AllProjectsName allProjects,
@@ -72,14 +71,7 @@
this.requireSignedPush = null;
}
- MaxObjectSizeLimitInfo maxObjectSizeLimit = new MaxObjectSizeLimitInfo();
- maxObjectSizeLimit.value =
- config.getEffectiveMaxObjectSizeLimit(projectState) == config.getMaxObjectSizeLimit()
- ? config.getFormattedMaxObjectSizeLimit()
- : p.getMaxObjectSizeLimit();
- maxObjectSizeLimit.configuredValue = p.getMaxObjectSizeLimit();
- maxObjectSizeLimit.inheritedValue = config.getFormattedMaxObjectSizeLimit();
- this.maxObjectSizeLimit = maxObjectSizeLimit;
+ this.maxObjectSizeLimit = getMaxObjectSizeLimit(projectState, p);
this.defaultSubmitType = new SubmitTypeInfo();
this.defaultSubmitType.value = projectState.getSubmitType();
@@ -114,6 +106,16 @@
this.extensionPanelNames = projectState.getConfig().getExtensionPanelSections();
}
+ private MaxObjectSizeLimitInfo getMaxObjectSizeLimit(ProjectState projectState, Project p) {
+ MaxObjectSizeLimitInfo info = new MaxObjectSizeLimitInfo();
+ EffectiveMaxObjectSizeLimit limit = projectState.getEffectiveMaxObjectSizeLimit();
+ long value = limit.value;
+ info.value = value == 0 ? null : String.valueOf(value);
+ info.configuredValue = p.getMaxObjectSizeLimit();
+ info.summary = limit.summary;
+ return info;
+ }
+
private Map<String, Map<String, ConfigParameterInfo>> getPluginConfig(
ProjectState project,
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
diff --git a/java/com/google/gerrit/server/restapi/project/CreateBranch.java b/java/com/google/gerrit/server/restapi/project/CreateBranch.java
index 8973508..62106e8 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateBranch.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateBranch.java
@@ -41,6 +41,7 @@
import com.google.gerrit.server.util.MagicBranch;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import java.io.IOException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.lib.Constants;
@@ -51,6 +52,7 @@
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
+@Singleton
public class CreateBranch
implements RestCollectionCreateView<ProjectResource, BranchResource, BranchInput> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
diff --git a/java/com/google/gerrit/server/restapi/project/CreateProject.java b/java/com/google/gerrit/server/restapi/project/CreateProject.java
index 43a4f44..271848b 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateProject.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateProject.java
@@ -72,6 +72,7 @@
import com.google.gerrit.server.validators.ValidationException;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@@ -90,6 +91,7 @@
import org.eclipse.jgit.transport.ReceiveCommand;
@RequiresCapability(GlobalCapability.CREATE_PROJECT)
+@Singleton
public class CreateProject
implements RestCollectionCreateView<TopLevelResource, ProjectResource, ProjectInput> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
diff --git a/java/com/google/gerrit/server/restapi/project/CreateTag.java b/java/com/google/gerrit/server/restapi/project/CreateTag.java
index bc34f05..dd73b0b 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateTag.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateTag.java
@@ -41,6 +41,7 @@
import com.google.gerrit.server.project.RefUtil.InvalidRevisionException;
import com.google.gerrit.server.project.TagResource;
import com.google.inject.Inject;
+import com.google.inject.Singleton;
import java.io.IOException;
import java.util.TimeZone;
import org.eclipse.jgit.api.Git;
@@ -53,6 +54,7 @@
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
+@Singleton
public class CreateTag implements RestCollectionCreateView<ProjectResource, TagResource, TagInput> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final PermissionBackend permissionBackend;
diff --git a/java/com/google/gerrit/server/restapi/project/GetAccess.java b/java/com/google/gerrit/server/restapi/project/GetAccess.java
index d545f92..a6b9404 100644
--- a/java/com/google/gerrit/server/restapi/project/GetAccess.java
+++ b/java/com/google/gerrit/server/restapi/project/GetAccess.java
@@ -16,6 +16,7 @@
import static com.google.gerrit.server.permissions.GlobalPermission.ADMINISTRATE_SERVER;
import static com.google.gerrit.server.permissions.ProjectPermission.CREATE_REF;
+import static com.google.gerrit.server.permissions.ProjectPermission.CREATE_TAG_REF;
import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
import static com.google.gerrit.server.permissions.RefPermission.READ;
import static com.google.gerrit.server.permissions.RefPermission.WRITE_CONFIG;
@@ -270,6 +271,7 @@
|| (canReadConfig
&& perm.ref(RefNames.REFS_CONFIG).testOrFalse(CREATE_CHANGE))));
info.canAdd = toBoolean(perm.testOrFalse(CREATE_REF));
+ info.canAddTags = toBoolean(perm.testOrFalse(CREATE_TAG_REF));
info.configVisible = canReadConfig || canWriteConfig;
info.groups =
diff --git a/java/com/google/gerrit/server/restapi/project/GetConfig.java b/java/com/google/gerrit/server/restapi/project/GetConfig.java
index aafff9e..b3ad962 100644
--- a/java/com/google/gerrit/server/restapi/project/GetConfig.java
+++ b/java/com/google/gerrit/server/restapi/project/GetConfig.java
@@ -23,7 +23,6 @@
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.ProjectConfigEntry;
import com.google.gerrit.server.extensions.webui.UiActions;
-import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -31,7 +30,6 @@
@Singleton
public class GetConfig implements RestReadView<ProjectResource> {
private final boolean serverEnableSignedPush;
- private final TransferConfig config;
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
private final PluginConfigFactory cfgFactory;
private final AllProjectsName allProjects;
@@ -41,14 +39,12 @@
@Inject
public GetConfig(
@EnableSignedPush boolean serverEnableSignedPush,
- TransferConfig config,
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory,
AllProjectsName allProjects,
UiActions uiActions,
DynamicMap<RestView<ProjectResource>> views) {
this.serverEnableSignedPush = serverEnableSignedPush;
- this.config = config;
this.pluginConfigEntries = pluginConfigEntries;
this.allProjects = allProjects;
this.cfgFactory = cfgFactory;
@@ -62,7 +58,6 @@
serverEnableSignedPush,
resource.getProjectState(),
resource.getUser(),
- config,
pluginConfigEntries,
cfgFactory,
allProjects,
diff --git a/java/com/google/gerrit/server/restapi/project/ListBranches.java b/java/com/google/gerrit/server/restapi/project/ListBranches.java
index bf4a547..a0d2528 100644
--- a/java/com/google/gerrit/server/restapi/project/ListBranches.java
+++ b/java/com/google/gerrit/server/restapi/project/ListBranches.java
@@ -46,7 +46,6 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
@@ -226,7 +225,7 @@
// Do nothing.
}
}
- Collections.sort(branches, new BranchComparator());
+ branches.sort(new BranchComparator());
return branches;
}
diff --git a/java/com/google/gerrit/server/restapi/project/ListTags.java b/java/com/google/gerrit/server/restapi/project/ListTags.java
index e79fdca..f59e984 100644
--- a/java/com/google/gerrit/server/restapi/project/ListTags.java
+++ b/java/com/google/gerrit/server/restapi/project/ListTags.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.project;
import static com.google.gerrit.reviewdb.client.RefNames.isConfigRef;
+import static java.util.Comparator.comparing;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.extensions.api.projects.ProjectApi.ListRefsRequest;
@@ -39,8 +40,6 @@
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
import java.util.Map;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -135,14 +134,7 @@
}
}
- Collections.sort(
- tags,
- new Comparator<TagInfo>() {
- @Override
- public int compare(TagInfo a, TagInfo b) {
- return a.ref.compareTo(b.ref);
- }
- });
+ tags.sort(comparing(t -> t.ref));
return new RefFilter<TagInfo>(Constants.R_TAGS)
.start(start)
diff --git a/java/com/google/gerrit/server/restapi/project/Module.java b/java/com/google/gerrit/server/restapi/project/Module.java
index a57438e..0497787 100644
--- a/java/com/google/gerrit/server/restapi/project/Module.java
+++ b/java/com/google/gerrit/server/restapi/project/Module.java
@@ -54,6 +54,8 @@
post(PROJECT_KIND, "check.access").to(CheckAccess.class);
get(PROJECT_KIND, "check.access").to(CheckAccessReadView.class);
+ post(PROJECT_KIND, "check").to(Check.class);
+
get(PROJECT_KIND, "parent").to(GetParent.class);
put(PROJECT_KIND, "parent").to(SetParent.class);
diff --git a/java/com/google/gerrit/server/restapi/project/PutConfig.java b/java/com/google/gerrit/server/restapi/project/PutConfig.java
index db596e6..76ea0c9 100644
--- a/java/com/google/gerrit/server/restapi/project/PutConfig.java
+++ b/java/com/google/gerrit/server/restapi/project/PutConfig.java
@@ -38,7 +38,6 @@
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.ProjectConfigEntry;
import com.google.gerrit.server.extensions.webui.UiActions;
-import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -71,7 +70,6 @@
private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
private final ProjectCache projectCache;
private final ProjectState.Factory projectStateFactory;
- private final TransferConfig config;
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
private final PluginConfigFactory cfgFactory;
private final AllProjectsName allProjects;
@@ -86,7 +84,6 @@
Provider<MetaDataUpdate.User> metaDataUpdateFactory,
ProjectCache projectCache,
ProjectState.Factory projectStateFactory,
- TransferConfig config,
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory,
AllProjectsName allProjects,
@@ -98,7 +95,6 @@
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.projectCache = projectCache;
this.projectStateFactory = projectStateFactory;
- this.config = config;
this.pluginConfigEntries = pluginConfigEntries;
this.cfgFactory = cfgFactory;
this.allProjects = allProjects;
@@ -168,12 +164,11 @@
throw new ResourceConflictException("Cannot update " + projectName);
}
- ProjectState state = projectStateFactory.create(projectConfig);
+ ProjectState state = projectStateFactory.create(ProjectConfig.read(md));
return new ConfigInfoImpl(
serverEnableSignedPush,
state,
user.get(),
- config,
pluginConfigEntries,
cfgFactory,
allProjects,
diff --git a/java/com/google/gerrit/server/restapi/project/SetParent.java b/java/com/google/gerrit/server/restapi/project/SetParent.java
index 6710f6c..b38619b 100644
--- a/java/com/google/gerrit/server/restapi/project/SetParent.java
+++ b/java/com/google/gerrit/server/restapi/project/SetParent.java
@@ -30,10 +30,12 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectResource;
@@ -43,6 +45,7 @@
import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
@Singleton
public class SetParent implements RestModifyView<ProjectResource, ParentInput> {
@@ -51,6 +54,7 @@
private final MetaDataUpdate.Server updateFactory;
private final AllProjectsName allProjects;
private final AllUsersName allUsers;
+ private final boolean allowProjectOwnersToChangeParent;
@Inject
SetParent(
@@ -58,12 +62,15 @@
PermissionBackend permissionBackend,
MetaDataUpdate.Server updateFactory,
AllProjectsName allProjects,
- AllUsersName allUsers) {
+ AllUsersName allUsers,
+ @GerritServerConfig Config config) {
this.cache = cache;
this.permissionBackend = permissionBackend;
this.updateFactory = updateFactory;
this.allProjects = allProjects;
this.allUsers = allUsers;
+ this.allowProjectOwnersToChangeParent =
+ config.getBoolean("receive", "allowProjectOwnersToChangeParent", false);
}
@Override
@@ -114,7 +121,11 @@
throws AuthException, ResourceConflictException, UnprocessableEntityException,
PermissionBackendException, BadRequestException {
if (checkIfAdmin) {
- permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
+ if (allowProjectOwnersToChangeParent) {
+ permissionBackend.user(user).project(project).check(ProjectPermission.WRITE_CONFIG);
+ } else {
+ permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
+ }
}
if (project.equals(allUsers) && !allProjects.get().equals(newParent)) {
diff --git a/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java b/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java
new file mode 100644
index 0000000..b9ddbc6
--- /dev/null
+++ b/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java
@@ -0,0 +1,171 @@
+// Copyright (C) 2018 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.rules;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.data.LabelFunction;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.common.data.SubmitRequirement;
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.server.project.SubmitRuleOptions;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Rule to require an approval from a user that did not upload the current patch set or block
+ * submission.
+ */
+@Singleton
+public class IgnoreSelfApprovalRule implements SubmitRule {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private static final String E_UNABLE_TO_FETCH_UPLOADER = "Unable to fetch uploader";
+ private static final String E_UNABLE_TO_FETCH_LABELS =
+ "Unable to fetch labels and approvals for the change";
+
+ public static class Module extends FactoryModule {
+ @Override
+ public void configure() {
+ bind(SubmitRule.class)
+ .annotatedWith(Exports.named("IgnoreSelfApprovalRule"))
+ .to(IgnoreSelfApprovalRule.class);
+ }
+ }
+
+ @Inject
+ IgnoreSelfApprovalRule() {}
+
+ @Override
+ public Collection<SubmitRecord> evaluate(ChangeData cd, SubmitRuleOptions options) {
+ List<LabelType> labelTypes;
+ List<PatchSetApproval> approvals;
+ try {
+ labelTypes = cd.getLabelTypes().getLabelTypes();
+ approvals = cd.currentApprovals();
+ } catch (OrmException e) {
+ logger.atWarning().withCause(e).log(E_UNABLE_TO_FETCH_LABELS);
+ return singletonRuleError(E_UNABLE_TO_FETCH_LABELS);
+ }
+
+ boolean shouldIgnoreSelfApproval = labelTypes.stream().anyMatch(l -> l.ignoreSelfApproval());
+ if (!shouldIgnoreSelfApproval) {
+ // Shortcut to avoid further processing if no label should ignore uploader approvals
+ return ImmutableList.of();
+ }
+
+ Account.Id uploader;
+ try {
+ uploader = cd.currentPatchSet().getUploader();
+ } catch (OrmException e) {
+ logger.atWarning().withCause(e).log(E_UNABLE_TO_FETCH_UPLOADER);
+ return singletonRuleError(E_UNABLE_TO_FETCH_UPLOADER);
+ }
+
+ SubmitRecord submitRecord = new SubmitRecord();
+ submitRecord.status = SubmitRecord.Status.OK;
+ submitRecord.labels = new ArrayList<>(labelTypes.size());
+ submitRecord.requirements = new ArrayList<>();
+
+ for (LabelType t : labelTypes) {
+ if (!t.ignoreSelfApproval()) {
+ // The default rules are enough in this case.
+ continue;
+ }
+
+ LabelFunction labelFunction = t.getFunction();
+ if (labelFunction == null) {
+ continue;
+ }
+
+ Collection<PatchSetApproval> allApprovalsForLabel = filterApprovalsByLabel(approvals, t);
+ SubmitRecord.Label allApprovalsCheckResult = labelFunction.check(t, allApprovalsForLabel);
+ SubmitRecord.Label ignoreSelfApprovalCheckResult =
+ labelFunction.check(t, filterOutPositiveApprovalsOfUser(allApprovalsForLabel, uploader));
+
+ if (labelCheckPassed(allApprovalsCheckResult)
+ && !labelCheckPassed(ignoreSelfApprovalCheckResult)) {
+ // The label has a valid approval from the uploader and no other valid approval. Set the
+ // label
+ // to NOT_READY and indicate the need for non-uploader approval as requirement.
+ submitRecord.labels.add(ignoreSelfApprovalCheckResult);
+ submitRecord.status = SubmitRecord.Status.NOT_READY;
+ // Add an additional requirement to be more descriptive on why the label counts as not
+ // approved.
+ submitRecord.requirements.add(
+ SubmitRequirement.builder()
+ .setFallbackText("Approval from non-uploader required")
+ .setType("non_uploader_approval")
+ .build());
+ }
+ }
+
+ if (submitRecord.labels.isEmpty()) {
+ return ImmutableList.of();
+ }
+
+ return ImmutableList.of(submitRecord);
+ }
+
+ private static boolean labelCheckPassed(SubmitRecord.Label label) {
+ switch (label.status) {
+ case OK:
+ case MAY:
+ return true;
+
+ case NEED:
+ case REJECT:
+ case IMPOSSIBLE:
+ return false;
+ }
+ return false;
+ }
+
+ private static Collection<SubmitRecord> singletonRuleError(String reason) {
+ SubmitRecord submitRecord = new SubmitRecord();
+ submitRecord.errorMessage = reason;
+ submitRecord.status = SubmitRecord.Status.RULE_ERROR;
+ return ImmutableList.of(submitRecord);
+ }
+
+ @VisibleForTesting
+ static Collection<PatchSetApproval> filterOutPositiveApprovalsOfUser(
+ Collection<PatchSetApproval> approvals, Account.Id user) {
+ return approvals
+ .stream()
+ .filter(input -> input.getValue() < 0 || !input.getAccountId().equals(user))
+ .collect(toImmutableList());
+ }
+
+ @VisibleForTesting
+ static Collection<PatchSetApproval> filterApprovalsByLabel(
+ Collection<PatchSetApproval> approvals, LabelType t) {
+ return approvals
+ .stream()
+ .filter(input -> input.getLabelId().get().equals(t.getLabelId().get()))
+ .collect(toImmutableList());
+ }
+}
diff --git a/java/com/google/gerrit/server/schema/AllProjectsCreator.java b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
index a91d5e6..348f88c 100644
--- a/java/com/google/gerrit/server/schema/AllProjectsCreator.java
+++ b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
@@ -42,6 +42,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.Sequences;
+import com.google.gerrit.server.UsedAt;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -52,6 +53,8 @@
import com.google.gerrit.server.project.ProjectConfig;
import com.google.inject.Inject;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.BatchRefUpdate;
@@ -66,28 +69,29 @@
/** Creates the {@code All-Projects} repository and initial ACLs. */
public class AllProjectsCreator {
- private final GitRepositoryManager mgr;
+ private final GitRepositoryManager repositoryManager;
private final AllProjectsName allProjectsName;
private final PersonIdent serverUser;
private final NotesMigration notesMigration;
- private String message;
- private int firstChangeId = ReviewDb.FIRST_CHANGE_ID;
-
- @Nullable private GroupReference admin;
-
- @Nullable private GroupReference batch;
private final GroupReference anonymous;
private final GroupReference registered;
private final GroupReference owners;
+ @Nullable private GroupReference admin;
+ @Nullable private GroupReference batch;
+ private String message;
+ private int firstChangeId = ReviewDb.FIRST_CHANGE_ID;
+ private LabelType codeReviewLabel;
+ private List<LabelType> additionalLabelType;
+
@Inject
AllProjectsCreator(
- GitRepositoryManager mgr,
+ GitRepositoryManager repositoryManager,
AllProjectsName allProjectsName,
- SystemGroupBackend systemGroupBackend,
@GerritPersonIdent PersonIdent serverUser,
- NotesMigration notesMigration) {
- this.mgr = mgr;
+ NotesMigration notesMigration,
+ SystemGroupBackend systemGroupBackend) {
+ this.repositoryManager = repositoryManager;
this.allProjectsName = allProjectsName;
this.serverUser = serverUser;
this.notesMigration = notesMigration;
@@ -95,6 +99,8 @@
this.anonymous = systemGroupBackend.getGroup(ANONYMOUS_USERS);
this.registered = systemGroupBackend.getGroup(REGISTERED_USERS);
this.owners = systemGroupBackend.getGroup(PROJECT_OWNERS);
+ this.codeReviewLabel = getDefaultCodeReviewLabel();
+ this.additionalLabelType = new ArrayList<>();
}
/** If called, grant default permissions to this admin group */
@@ -114,19 +120,35 @@
return this;
}
+ @UsedAt(UsedAt.Project.GOOGLE)
public AllProjectsCreator setFirstChangeIdForNoteDb(int id) {
checkArgument(id > 0, "id must be positive: %s", id);
firstChangeId = id;
return this;
}
+ /** If called, the provided "Code-Review" label will be used rather than the default. */
+ @UsedAt(UsedAt.Project.GOOGLE)
+ public AllProjectsCreator setCodeReviewLabel(LabelType labelType) {
+ checkArgument(
+ labelType.getName().equals("Code-Review"), "label should have 'Code-Review' as its name");
+ this.codeReviewLabel = labelType;
+ return this;
+ }
+
+ @UsedAt(UsedAt.Project.GOOGLE)
+ public AllProjectsCreator addAdditionalLabel(LabelType labelType) {
+ additionalLabelType.add(labelType);
+ return this;
+ }
+
public void create() throws IOException, ConfigInvalidException {
- try (Repository git = mgr.openRepository(allProjectsName)) {
+ try (Repository git = repositoryManager.openRepository(allProjectsName)) {
initAllProjects(git);
} catch (RepositoryNotFoundException notFound) {
// A repository may be missing if this project existed only to store
// inheritable permissions. For example 'All-Projects'.
- try (Repository git = mgr.createRepository(allProjectsName)) {
+ try (Repository git = repositoryManager.createRepository(allProjectsName)) {
initAllProjects(git);
RefUpdate u = git.updateRef(Constants.HEAD);
u.link(RefNames.REFS_CONFIG);
@@ -179,10 +201,10 @@
stream.add(rule(config, batch));
}
- LabelType cr = initCodeReviewLabel(config);
- grant(config, heads, cr, -1, 1, registered);
+ initLabels(config);
+ grant(config, heads, codeReviewLabel, -1, 1, registered);
- grant(config, heads, cr, -2, 2, admin, owners);
+ grant(config, heads, codeReviewLabel, -2, 2, admin, owners);
grant(config, heads, Permission.CREATE, admin, owners);
grant(config, heads, Permission.PUSH, admin, owners);
grant(config, heads, Permission.SUBMIT, admin, owners);
@@ -199,7 +221,7 @@
meta.getPermission(Permission.READ, true).setExclusiveGroup(true);
grant(config, meta, Permission.READ, admin, owners);
- grant(config, meta, cr, -2, 2, admin, owners);
+ grant(config, meta, codeReviewLabel, -2, 2, admin, owners);
grant(config, meta, Permission.CREATE, admin, owners);
grant(config, meta, Permission.PUSH, admin, owners);
grant(config, meta, Permission.SUBMIT, admin, owners);
@@ -210,7 +232,8 @@
}
}
- public static LabelType initCodeReviewLabel(ProjectConfig c) {
+ @UsedAt(UsedAt.Project.GOOGLE)
+ public static LabelType getDefaultCodeReviewLabel() {
LabelType type =
new LabelType(
"Code-Review",
@@ -222,10 +245,14 @@
new LabelValue((short) -2, "This shall not be merged")));
type.setCopyMinScore(true);
type.setCopyAllScoresOnTrivialRebase(true);
- c.getLabelSections().put(type.getName(), type);
return type;
}
+ private void initLabels(ProjectConfig projectConfig) {
+ projectConfig.getLabelSections().put(codeReviewLabel.getName(), codeReviewLabel);
+ additionalLabelType.forEach(t -> projectConfig.getLabelSections().put(t.getName(), t));
+ }
+
private void initSequences(Repository git, BatchRefUpdate bru) throws IOException {
if (notesMigration.readChangeSequence()
&& git.exactRef(REFS_SEQUENCES + Sequences.NAME_CHANGES) == null) {
diff --git a/java/com/google/gerrit/server/schema/AllUsersCreator.java b/java/com/google/gerrit/server/schema/AllUsersCreator.java
index 90002f6..3779d0d 100644
--- a/java/com/google/gerrit/server/schema/AllUsersCreator.java
+++ b/java/com/google/gerrit/server/schema/AllUsersCreator.java
@@ -14,8 +14,10 @@
package com.google.gerrit.server.schema;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.server.schema.AclUtil.grant;
+import static com.google.gerrit.server.schema.AllProjectsCreator.getDefaultCodeReviewLabel;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.Version;
@@ -26,6 +28,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.UsedAt;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -50,6 +53,7 @@
private final GroupReference registered;
@Nullable private GroupReference admin;
+ private LabelType codeReviewLabel;
@Inject
AllUsersCreator(
@@ -61,6 +65,7 @@
this.allUsersName = allUsersName;
this.serverUser = serverUser;
this.registered = systemGroupBackend.getGroup(REGISTERED_USERS);
+ this.codeReviewLabel = getDefaultCodeReviewLabel();
}
/**
@@ -72,6 +77,15 @@
return this;
}
+ /** If called, the provided "Code-Review" label will be used rather than the default. */
+ @UsedAt(UsedAt.Project.GOOGLE)
+ public AllUsersCreator setCodeReviewLabel(LabelType labelType) {
+ checkArgument(
+ labelType.getName().equals("Code-Review"), "label should have 'Code-Review' as its name");
+ this.codeReviewLabel = labelType;
+ return this;
+ }
+
public void create() throws IOException, ConfigInvalidException {
try (Repository git = mgr.openRepository(allUsersName)) {
initAllUsers(git);
@@ -100,11 +114,14 @@
AccessSection users =
config.getAccessSection(
RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}", true);
- LabelType cr = AllProjectsCreator.initCodeReviewLabel(config);
+
+ // Initialize "Code-Review" label.
+ config.getLabelSections().put(codeReviewLabel.getName(), codeReviewLabel);
+
grant(config, users, Permission.READ, false, true, registered);
grant(config, users, Permission.PUSH, false, true, registered);
grant(config, users, Permission.SUBMIT, false, true, registered);
- grant(config, users, cr, -2, 2, true, registered);
+ grant(config, users, codeReviewLabel, -2, 2, true, registered);
if (admin != null) {
AccessSection defaults = config.getAccessSection(RefNames.REFS_USERS_DEFAULT, true);
diff --git a/java/com/google/gerrit/server/schema/DataSourceProvider.java b/java/com/google/gerrit/server/schema/DataSourceProvider.java
index 8021a54..d4cfaa6 100644
--- a/java/com/google/gerrit/server/schema/DataSourceProvider.java
+++ b/java/com/google/gerrit/server/schema/DataSourceProvider.java
@@ -44,6 +44,8 @@
/** Provides access to the DataSource. */
@Singleton
public class DataSourceProvider implements Provider<DataSource>, LifecycleListener {
+ private static final String DATABASE_KEY = "database";
+
private final Config cfg;
private final MetricMaker metrics;
private final Context ctx;
@@ -93,7 +95,7 @@
}
private DataSource open(Config cfg, Context context, DataSourceType dst) {
- ConfigSection dbs = new ConfigSection(cfg, "database");
+ ConfigSection dbs = new ConfigSection(cfg, DATABASE_KEY);
String driver = dbs.optional("driver");
if (Strings.isNullOrEmpty(driver)) {
driver = dst.getDriver();
@@ -112,41 +114,41 @@
if (context == Context.SINGLE_USER) {
usePool = false;
} else {
- usePool = cfg.getBoolean("database", "connectionpool", dst.usePool());
+ usePool = cfg.getBoolean(DATABASE_KEY, "connectionpool", dst.usePool());
}
if (usePool) {
- final BasicDataSource ds = new BasicDataSource();
- ds.setDriverClassName(driver);
- ds.setUrl(url);
+ final BasicDataSource lds = new BasicDataSource();
+ lds.setDriverClassName(driver);
+ lds.setUrl(url);
if (username != null && !username.isEmpty()) {
- ds.setUsername(username);
+ lds.setUsername(username);
}
if (password != null && !password.isEmpty()) {
- ds.setPassword(password);
+ lds.setPassword(password);
}
int poolLimit = threadSettingsConfig.getDatabasePoolLimit();
- ds.setMaxActive(poolLimit);
- ds.setMinIdle(cfg.getInt("database", "poolminidle", 4));
- ds.setMaxIdle(cfg.getInt("database", "poolmaxidle", Math.min(poolLimit, 16)));
- ds.setMaxWait(
+ lds.setMaxActive(poolLimit);
+ lds.setMinIdle(cfg.getInt(DATABASE_KEY, "poolminidle", 4));
+ lds.setMaxIdle(cfg.getInt(DATABASE_KEY, "poolmaxidle", Math.min(poolLimit, 16)));
+ lds.setMaxWait(
ConfigUtil.getTimeUnit(
cfg,
- "database",
+ DATABASE_KEY,
null,
"poolmaxwait",
MILLISECONDS.convert(30, SECONDS),
MILLISECONDS));
- ds.setInitialSize(ds.getMinIdle());
+ lds.setInitialSize(lds.getMinIdle());
long evictIdleTimeMs = 1000L * 60;
- ds.setMinEvictableIdleTimeMillis(evictIdleTimeMs);
- ds.setTimeBetweenEvictionRunsMillis(evictIdleTimeMs / 2);
- ds.setTestOnBorrow(true);
- ds.setTestOnReturn(true);
- ds.setValidationQuery(dst.getValidationQuery());
- ds.setValidationQueryTimeout(5);
- exportPoolMetrics(ds);
- return intercept(interceptor, ds);
+ lds.setMinEvictableIdleTimeMillis(evictIdleTimeMs);
+ lds.setTimeBetweenEvictionRunsMillis(evictIdleTimeMs / 2);
+ lds.setTestOnBorrow(true);
+ lds.setTestOnReturn(true);
+ lds.setValidationQuery(dst.getValidationQuery());
+ lds.setValidationQueryTimeout(5);
+ exportPoolMetrics(lds);
+ return intercept(interceptor, lds);
}
// Don't use the connection pool.
//
diff --git a/java/com/google/gerrit/server/schema/GroupBundle.java b/java/com/google/gerrit/server/schema/GroupBundle.java
index 58d3435..a15cedc 100644
--- a/java/com/google/gerrit/server/schema/GroupBundle.java
+++ b/java/com/google/gerrit/server/schema/GroupBundle.java
@@ -38,6 +38,7 @@
import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
import com.google.gerrit.server.group.InternalGroup;
@@ -118,9 +119,10 @@
this.auditLogReader = auditLogReader;
}
- public GroupBundle fromNoteDb(Repository repo, AccountGroup.UUID uuid)
+ public GroupBundle fromNoteDb(
+ Project.NameKey projectName, Repository repo, AccountGroup.UUID uuid)
throws ConfigInvalidException, IOException {
- GroupConfig groupConfig = GroupConfig.loadForGroup(repo, uuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repo, uuid);
InternalGroup internalGroup = groupConfig.getLoadedGroup().get();
AccountGroup.Id groupId = internalGroup.getId();
diff --git a/java/com/google/gerrit/server/schema/GroupRebuilder.java b/java/com/google/gerrit/server/schema/GroupRebuilder.java
index f98c948..be8dcff 100644
--- a/java/com/google/gerrit/server/schema/GroupRebuilder.java
+++ b/java/com/google/gerrit/server/schema/GroupRebuilder.java
@@ -81,7 +81,7 @@
.setNameKey(group.getNameKey())
.setGroupUUID(group.getGroupUUID())
.build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(allUsersRepo, groupCreation);
+ GroupConfig groupConfig = GroupConfig.createForNewGroup(allUsers, allUsersRepo, groupCreation);
groupConfig.setAllowSaveEmptyName();
InternalGroupUpdate.Builder updateBuilder =
diff --git a/java/com/google/gerrit/server/schema/SchemaCreator.java b/java/com/google/gerrit/server/schema/SchemaCreator.java
index d650dc7..743019d 100644
--- a/java/com/google/gerrit/server/schema/SchemaCreator.java
+++ b/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -214,12 +214,14 @@
AuditLogFormatter auditLogFormatter =
AuditLogFormatter.createBackedBy(ImmutableSet.of(), ImmutableSet.of(), serverId);
- GroupConfig groupConfig = GroupConfig.createForNewGroup(allUsersRepo, groupCreation);
+ GroupConfig groupConfig =
+ GroupConfig.createForNewGroup(allUsersName, allUsersRepo, groupCreation);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
AccountGroup.NameKey groupName = groupUpdate.getName().orElseGet(groupCreation::getNameKey);
GroupNameNotes groupNameNotes =
- GroupNameNotes.forNewGroup(allUsersRepo, groupCreation.getGroupUUID(), groupName);
+ GroupNameNotes.forNewGroup(
+ allUsersName, allUsersRepo, groupCreation.getGroupUUID(), groupName);
commit(allUsersRepo, groupConfig, groupNameNotes);
diff --git a/java/com/google/gerrit/server/schema/Schema_139.java b/java/com/google/gerrit/server/schema/Schema_139.java
index 1d90305..f362a7d 100644
--- a/java/com/google/gerrit/server/schema/Schema_139.java
+++ b/java/com/google/gerrit/server/schema/Schema_139.java
@@ -148,7 +148,7 @@
md.getCommitBuilder().setCommitter(serverUser);
md.setMessage(MSG);
- AccountConfig accountConfig = new AccountConfig(e.getKey(), git);
+ AccountConfig accountConfig = new AccountConfig(e.getKey(), allUsersName, git);
accountConfig.load(md);
accountConfig.setAccountUpdate(
InternalAccountUpdate.builder()
diff --git a/java/com/google/gerrit/server/schema/Schema_144.java b/java/com/google/gerrit/server/schema/Schema_144.java
index f1c9745..bb0cbca 100644
--- a/java/com/google/gerrit/server/schema/Schema_144.java
+++ b/java/com/google/gerrit/server/schema/Schema_144.java
@@ -80,7 +80,7 @@
try {
try (Repository repo = repoManager.openRepository(allUsersName)) {
- ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(repo);
+ ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsersName, repo);
extIdNotes.upsert(toAdd);
try (MetaDataUpdate metaDataUpdate =
new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, repo)) {
diff --git a/java/com/google/gerrit/server/schema/Schema_148.java b/java/com/google/gerrit/server/schema/Schema_148.java
index 98d4909..9433da8 100644
--- a/java/com/google/gerrit/server/schema/Schema_148.java
+++ b/java/com/google/gerrit/server/schema/Schema_148.java
@@ -55,7 +55,7 @@
@Override
protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
try (Repository repo = repoManager.openRepository(allUsersName)) {
- ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(repo);
+ ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsersName, repo);
for (ExternalId extId : extIdNotes.all()) {
if (needsUpdate(extId)) {
extIdNotes.upsert(extId);
diff --git a/java/com/google/gerrit/server/schema/Schema_154.java b/java/com/google/gerrit/server/schema/Schema_154.java
index 8dfd356..fab1693 100644
--- a/java/com/google/gerrit/server/schema/Schema_154.java
+++ b/java/com/google/gerrit/server/schema/Schema_154.java
@@ -139,7 +139,10 @@
PersonIdent ident = serverIdent.get();
md.getCommitBuilder().setAuthor(ident);
md.getCommitBuilder().setCommitter(ident);
- new AccountConfig(account.getId(), allUsersRepo).load().setAccount(account).commit(md);
+ new AccountConfig(account.getId(), allUsersName, allUsersRepo)
+ .load()
+ .setAccount(account)
+ .commit(md);
}
@FunctionalInterface
diff --git a/java/com/google/gerrit/server/schema/Schema_160.java b/java/com/google/gerrit/server/schema/Schema_160.java
index 99ca465..eb8b70f 100644
--- a/java/com/google/gerrit/server/schema/Schema_160.java
+++ b/java/com/google/gerrit/server/schema/Schema_160.java
@@ -110,7 +110,7 @@
md.getCommitBuilder().setAuthor(ident);
md.getCommitBuilder().setCommitter(ident);
Prefs prefs = new Prefs(ref);
- prefs.load(repo);
+ prefs.load(allUsersName, repo);
prefs.removeMyDrafts();
prefs.commit(md);
if (prefs.dirty()) {
diff --git a/java/com/google/gerrit/server/schema/Schema_167.java b/java/com/google/gerrit/server/schema/Schema_167.java
index ba93751..a5066cc 100644
--- a/java/com/google/gerrit/server/schema/Schema_167.java
+++ b/java/com/google/gerrit/server/schema/Schema_167.java
@@ -150,7 +150,8 @@
ReviewDb db, Repository allUsersRepo, Config gerritConfig, SitePaths sitePaths)
throws IOException, ConfigInvalidException {
String serverId = new GerritServerIdProvider(gerritConfig, sitePaths).get();
- SimpleInMemoryAccountCache accountCache = new SimpleInMemoryAccountCache(allUsersRepo);
+ SimpleInMemoryAccountCache accountCache =
+ new SimpleInMemoryAccountCache(allUsersName, allUsersRepo);
SimpleInMemoryGroupCache groupCache = new SimpleInMemoryGroupCache(db);
return AuditLogFormatter.create(
accountCache::get,
@@ -178,10 +179,12 @@
// The regular account cache isn't available during init. -> Use a simple replacement which tries
// to load every account only once from disk.
private static class SimpleInMemoryAccountCache {
+ private final AllUsersName allUsersName;
private final Repository allUsersRepo;
private Map<Account.Id, Optional<Account>> accounts = new HashMap<>();
- public SimpleInMemoryAccountCache(Repository allUsersRepo) {
+ public SimpleInMemoryAccountCache(AllUsersName allUsersName, Repository allUsersRepo) {
+ this.allUsersName = allUsersName;
this.allUsersRepo = allUsersRepo;
}
@@ -192,7 +195,8 @@
private Optional<Account> load(Account.Id accountId) {
try {
- AccountConfig accountConfig = new AccountConfig(accountId, allUsersRepo).load();
+ AccountConfig accountConfig =
+ new AccountConfig(accountId, allUsersName, allUsersRepo).load();
return accountConfig.getLoadedAccount();
} catch (IOException | ConfigInvalidException ignored) {
logger.atWarning().withCause(ignored).log(
diff --git a/java/com/google/gerrit/server/submit/FastForwardOp.java b/java/com/google/gerrit/server/submit/FastForwardOp.java
index f1749f4..08f5abb 100644
--- a/java/com/google/gerrit/server/submit/FastForwardOp.java
+++ b/java/com/google/gerrit/server/submit/FastForwardOp.java
@@ -28,6 +28,7 @@
@Override
protected void updateRepoImpl(RepoContext ctx) throws IntegrationException {
if (args.project.is(BooleanProjectConfig.REJECT_EMPTY_COMMIT)
+ && toMerge.getParentCount() > 0
&& toMerge.getTree().equals(toMerge.getParent(0).getTree())) {
toMerge.setStatusCode(EMPTY_COMMIT);
return;
diff --git a/java/com/google/gerrit/server/submit/GitModules.java b/java/com/google/gerrit/server/submit/GitModules.java
index fd9a6fa..1fccbdd 100644
--- a/java/com/google/gerrit/server/submit/GitModules.java
+++ b/java/com/google/gerrit/server/submit/GitModules.java
@@ -22,7 +22,6 @@
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.submit.MergeOpRepoManager.OpenRepo;
-import com.google.gerrit.server.util.RequestId;
import com.google.gerrit.server.util.git.SubmoduleSectionParser;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -51,7 +50,6 @@
private static final String GIT_MODULES = ".gitmodules";
- private final RequestId submissionId;
private final Branch.NameKey branch;
Set<SubmoduleSubscription> subscriptions;
@@ -62,9 +60,8 @@
@Assisted MergeOpRepoManager orm)
throws IOException {
this.branch = branch;
- this.submissionId = orm.getSubmissionId();
Project.NameKey project = branch.getParentKey();
- logDebug("Loading .gitmodules of %s for project %s", branch, project);
+ logger.atFine().log("Loading .gitmodules of %s for project %s", branch, project);
try {
OpenRepo or = orm.getRepo(project);
ObjectId id = or.repo.resolve(branch.get());
@@ -76,7 +73,7 @@
try (TreeWalk tw = TreeWalk.forPath(or.repo, GIT_MODULES, commit.getTree())) {
if (tw == null || (tw.getRawMode(0) & FileMode.TYPE_MASK) != FileMode.TYPE_FILE) {
subscriptions = Collections.emptySet();
- logDebug("The .gitmodules file doesn't exist in %s", branch);
+ logger.atFine().log("The .gitmodules file doesn't exist in %s", branch);
return;
}
}
@@ -94,22 +91,14 @@
}
public Collection<SubmoduleSubscription> subscribedTo(Branch.NameKey src) {
- logDebug("Checking for a subscription of %s for %s", src, branch);
+ logger.atFine().log("Checking for a subscription of %s for %s", src, branch);
Collection<SubmoduleSubscription> ret = new ArrayList<>();
for (SubmoduleSubscription s : subscriptions) {
if (s.getSubmodule().equals(src)) {
- logDebug("Found %s", s);
+ logger.atFine().log("Found %s", s);
ret.add(s);
}
}
return ret;
}
-
- private void logDebug(String msg, @Nullable Object arg) {
- logger.atFine().log(submissionId + msg, arg);
- }
-
- private void logDebug(String msg, @Nullable Object arg1, @Nullable Object arg2) {
- logger.atFine().log(submissionId + msg, arg1, arg2);
- }
}
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index 9cfa272..43d5f75 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -63,6 +63,8 @@
import com.google.gerrit.server.git.MergeTip;
import com.google.gerrit.server.git.validators.MergeValidationException;
import com.google.gerrit.server.git.validators.MergeValidators;
+import com.google.gerrit.server.logging.RequestId;
+import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.NoSuchProjectException;
@@ -77,7 +79,6 @@
import com.google.gerrit.server.update.RetryHelper;
import com.google.gerrit.server.update.RetryHelper.ActionType;
import com.google.gerrit.server.update.UpdateException;
-import com.google.gerrit.server.util.RequestId;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -455,78 +456,83 @@
this.dryrun = dryrun;
this.caller = caller;
this.ts = TimeUtil.nowTs();
- submissionId = RequestId.forChange(change);
this.db = db;
- openRepoManager();
+ this.submissionId = new RequestId(change.getId().toString());
- logDebug("Beginning integration of %s", change);
- try {
- ChangeSet indexBackedChangeSet =
- mergeSuperSet.setMergeOpRepoManager(orm).completeChangeSet(db, change, caller);
- checkState(
- indexBackedChangeSet.ids().contains(change.getId()),
- "change %s missing from %s",
- change.getId(),
- indexBackedChangeSet);
- if (indexBackedChangeSet.furtherHiddenChanges()) {
- throw new AuthException(
- "A change to be submitted with " + change.getId() + " is not visible");
+ try (TraceContext traceContext =
+ TraceContext.open().addTag(RequestId.Type.SUBMISSION_ID, submissionId)) {
+ openRepoManager();
+
+ logger.atFine().log("Beginning integration of %s", change);
+ try {
+ ChangeSet indexBackedChangeSet =
+ mergeSuperSet.setMergeOpRepoManager(orm).completeChangeSet(db, change, caller);
+ checkState(
+ indexBackedChangeSet.ids().contains(change.getId()),
+ "change %s missing from %s",
+ change.getId(),
+ indexBackedChangeSet);
+ if (indexBackedChangeSet.furtherHiddenChanges()) {
+ throw new AuthException(
+ "A change to be submitted with " + change.getId() + " is not visible");
+ }
+ logger.atFine().log("Calculated to merge %s", indexBackedChangeSet);
+
+ // Reload ChangeSet so that we don't rely on (potentially) stale index data for merging
+ ChangeSet cs = reloadChanges(indexBackedChangeSet);
+
+ // Count cross-project submissions outside of the retry loop. The chance of a single project
+ // failing increases with the number of projects, so the failure count would be inflated if
+ // this metric were incremented inside of integrateIntoHistory.
+ int projects = cs.projects().size();
+ if (projects > 1) {
+ topicMetrics.topicSubmissions.increment();
+ }
+
+ RetryTracker retryTracker = new RetryTracker();
+ retryHelper.execute(
+ updateFactory -> {
+ long attempt = retryTracker.lastAttemptNumber + 1;
+ boolean isRetry = attempt > 1;
+ if (isRetry) {
+ logger.atFine().log("Retrying, attempt #%d; skipping merged changes", attempt);
+ this.ts = TimeUtil.nowTs();
+ openRepoManager();
+ }
+ this.commitStatus = new CommitStatus(cs, isRetry);
+ if (checkSubmitRules) {
+ logger.atFine().log("Checking submit rules and state");
+ checkSubmitRulesAndState(cs, isRetry);
+ } else {
+ logger.atFine().log("Bypassing submit rules");
+ bypassSubmitRules(cs, isRetry);
+ }
+ try {
+ integrateIntoHistory(cs);
+ } catch (IntegrationException e) {
+ logger.atSevere().withCause(e).log("Error from integrateIntoHistory");
+ throw new ResourceConflictException(e.getMessage(), e);
+ }
+ return null;
+ },
+ RetryHelper.options()
+ .listener(retryTracker)
+ // Up to the entire submit operation is retried, including possibly many projects.
+ // Multiply the timeout by the number of projects we're actually attempting to
+ // submit.
+ .timeout(
+ retryHelper
+ .getDefaultTimeout(ActionType.CHANGE_UPDATE)
+ .multipliedBy(cs.projects().size()))
+ .build());
+
+ if (projects > 1) {
+ topicMetrics.topicSubmissionsCompleted.increment();
+ }
+ } catch (IOException e) {
+ // Anything before the merge attempt is an error
+ throw new OrmException(e);
}
- logDebug("Calculated to merge %s", indexBackedChangeSet);
-
- // Reload ChangeSet so that we don't rely on (potentially) stale index data for merging
- ChangeSet cs = reloadChanges(indexBackedChangeSet);
-
- // Count cross-project submissions outside of the retry loop. The chance of a single project
- // failing increases with the number of projects, so the failure count would be inflated if
- // this metric were incremented inside of integrateIntoHistory.
- int projects = cs.projects().size();
- if (projects > 1) {
- topicMetrics.topicSubmissions.increment();
- }
-
- RetryTracker retryTracker = new RetryTracker();
- retryHelper.execute(
- updateFactory -> {
- long attempt = retryTracker.lastAttemptNumber + 1;
- boolean isRetry = attempt > 1;
- if (isRetry) {
- logDebug("Retrying, attempt #%d; skipping merged changes", attempt);
- this.ts = TimeUtil.nowTs();
- openRepoManager();
- }
- this.commitStatus = new CommitStatus(cs, isRetry);
- if (checkSubmitRules) {
- logDebug("Checking submit rules and state");
- checkSubmitRulesAndState(cs, isRetry);
- } else {
- logDebug("Bypassing submit rules");
- bypassSubmitRules(cs, isRetry);
- }
- try {
- integrateIntoHistory(cs);
- } catch (IntegrationException e) {
- logError("Error from integrateIntoHistory", e);
- throw new ResourceConflictException(e.getMessage(), e);
- }
- return null;
- },
- RetryHelper.options()
- .listener(retryTracker)
- // Up to the entire submit operation is retried, including possibly many projects.
- // Multiply the timeout by the number of projects we're actually attempting to submit.
- .timeout(
- retryHelper
- .getDefaultTimeout(ActionType.CHANGE_UPDATE)
- .multipliedBy(cs.projects().size()))
- .build());
-
- if (projects > 1) {
- topicMetrics.topicSubmissionsCompleted.increment();
- }
- } catch (IOException e) {
- // Anything before the merge attempt is an error
- throw new OrmException(e);
}
}
@@ -535,7 +541,7 @@
orm.close();
}
orm = ormProvider.get();
- orm.setContext(db, ts, caller, submissionId);
+ orm.setContext(db, ts, caller);
}
private ChangeSet reloadChanges(ChangeSet changeSet) {
@@ -581,7 +587,7 @@
private void integrateIntoHistory(ChangeSet cs)
throws IntegrationException, RestApiException, UpdateException {
checkArgument(!cs.furtherHiddenChanges(), "cannot integrate hidden changes into history");
- logDebug("Beginning merge attempt on %s", cs);
+ logger.atFine().log("Beginning merge attempt on %s", cs);
Map<Branch.NameKey, BranchBatch> toSubmit = new HashMap<>();
ListMultimap<Branch.NameKey, ChangeData> cbb;
@@ -609,7 +615,6 @@
batchUpdateFactory.execute(
orm.batchUpdates(allProjects),
new SubmitStrategyListener(submitInput, strategies, commitStatus),
- submissionId,
dryrun);
} catch (NoSuchProjectException e) {
throw new ResourceNotFoundException(e.getMessage());
@@ -723,7 +728,7 @@
throw new IntegrationException("Failed to determine already accepted commits.", e);
}
- logDebug("Found %d existing heads", alreadyAccepted.size());
+ logger.atFine().log("Found %d existing heads", alreadyAccepted.size());
return alreadyAccepted;
}
@@ -737,7 +742,7 @@
private BranchBatch validateChangeList(OpenRepo or, Collection<ChangeData> submitted)
throws IntegrationException {
- logDebug("Validating %d changes", submitted.size());
+ logger.atFine().log("Validating %d changes", submitted.size());
Set<CodeReviewCommit> toSubmit = new LinkedHashSet<>(submitted.size());
SetMultimap<ObjectId, PatchSet.Id> revisions = getRevisions(or, submitted);
@@ -775,7 +780,7 @@
}
if (chg.currentPatchSetId() == null) {
String msg = "Missing current patch set on change";
- logError(msg + " " + changeId);
+ logger.atSevere().log("%s %s", msg, changeId);
commitStatus.problem(changeId, msg);
continue;
}
@@ -856,7 +861,7 @@
commit.add(or.canMergeFlag);
toSubmit.add(commit);
}
- logDebug("Submitting on this run: %s", toSubmit);
+ logger.atFine().log("Submitting on this run: %s", toSubmit);
return new AutoValue_MergeOp_BranchBatch(submitType, toSubmit);
}
@@ -894,7 +899,7 @@
try {
return orm.getRepo(project);
} catch (NoSuchProjectException e) {
- logWarn("Project " + project + " no longer exists, abandoning open changes.");
+ logger.atWarning().log("Project %s no longer exists, abandoning open changes.", project);
abandonAllOpenChangeForDeletedProject(project);
} catch (IOException e) {
throw new IntegrationException("Error opening project " + project, e);
@@ -907,7 +912,6 @@
for (ChangeData cd : queryProvider.get().byProjectOpen(destProject)) {
try (BatchUpdate bu =
batchUpdateFactory.create(db, destProject, internalUserFactory.create(), ts)) {
- bu.setRequestId(submissionId);
bu.addOp(
cd.getId(),
new BatchUpdateOp() {
@@ -936,12 +940,14 @@
try {
bu.execute();
} catch (UpdateException | RestApiException e) {
- logWarn("Cannot abandon changes for deleted project " + destProject, e);
+ logger.atWarning().withCause(e).log(
+ "Cannot abandon changes for deleted project %s", destProject);
}
}
}
} catch (OrmException e) {
- logWarn("Cannot abandon changes for deleted project " + destProject, e);
+ logger.atWarning().withCause(e).log(
+ "Cannot abandon changes for deleted project %s", destProject);
}
}
@@ -965,28 +971,4 @@
+ " projects involved; some projects may have submitted successfully, but others may have"
+ " failed";
}
-
- private void logDebug(String msg) {
- logger.atFine().log(submissionId + msg);
- }
-
- private void logDebug(String msg, @Nullable Object arg) {
- logger.atFine().log(submissionId + msg, arg);
- }
-
- private void logWarn(String msg, Throwable t) {
- logger.atWarning().withCause(t).log("%s%s", submissionId, msg);
- }
-
- private void logWarn(String msg) {
- logger.atWarning().log("%s%s", submissionId, msg);
- }
-
- private void logError(String msg, Throwable t) {
- logger.atSevere().withCause(t).log("%s%s", submissionId, msg);
- }
-
- private void logError(String msg) {
- logError(msg, null);
- }
}
diff --git a/java/com/google/gerrit/server/submit/MergeOpRepoManager.java b/java/com/google/gerrit/server/submit/MergeOpRepoManager.java
index 67059e6..3f07ed7 100644
--- a/java/com/google/gerrit/server/submit/MergeOpRepoManager.java
+++ b/java/com/google/gerrit/server/submit/MergeOpRepoManager.java
@@ -31,7 +31,6 @@
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.update.BatchUpdate;
-import com.google.gerrit.server.util.RequestId;
import com.google.inject.Inject;
import java.io.IOException;
import java.sql.Timestamp;
@@ -112,7 +111,6 @@
batchUpdateFactory
.create(db, getProjectName(), caller, ts)
.setRepository(repo, rw, ins)
- .setRequestId(submissionId)
.setOnSubmitValidators(onSubmitValidatorsFactory.create());
}
return update;
@@ -162,7 +160,6 @@
private ReviewDb db;
private Timestamp ts;
private IdentifiedUser caller;
- private RequestId submissionId;
@Inject
MergeOpRepoManager(
@@ -178,15 +175,10 @@
openRepos = new HashMap<>();
}
- public void setContext(ReviewDb db, Timestamp ts, IdentifiedUser caller, RequestId submissionId) {
+ public void setContext(ReviewDb db, Timestamp ts, IdentifiedUser caller) {
this.db = db;
this.ts = ts;
this.caller = caller;
- this.submissionId = submissionId;
- }
-
- public RequestId getSubmissionId() {
- return submissionId;
}
public OpenRepo getRepo(Project.NameKey project) throws NoSuchProjectException, IOException {
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategy.java b/java/com/google/gerrit/server/submit/SubmitStrategy.java
index 7b7ae48..6e3e0b8 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategy.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategy.java
@@ -43,13 +43,13 @@
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.validators.OnSubmitValidators;
+import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.submit.MergeOp.CommitStatus;
import com.google.gerrit.server.update.BatchUpdate;
-import com.google.gerrit.server.util.RequestId;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java b/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java
index 2cb0744..7f70f37 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java
@@ -26,8 +26,8 @@
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.MergeTip;
+import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.server.submit.MergeOp.CommitStatus;
-import com.google.gerrit.server.util.RequestId;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.Set;
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
index abe3632..51dad5b 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
@@ -22,7 +22,6 @@
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch;
@@ -54,7 +53,6 @@
import com.google.gwtorm.server.OrmException;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -103,7 +101,8 @@
@Override
public final void updateRepo(RepoContext ctx) throws Exception {
- logDebug("%s#updateRepo for change %s", getClass().getSimpleName(), toMerge.change().getId());
+ logger.atFine().log(
+ "%s#updateRepo for change %s", getClass().getSimpleName(), toMerge.change().getId());
checkState(
ctx.getRevWalk() == args.rw,
"SubmitStrategyOp requires callers to call BatchUpdate#setRepository with exactly the same"
@@ -117,18 +116,18 @@
if (alreadyMergedCommit == null) {
updateRepoImpl(ctx);
} else {
- logDebug("Already merged as %s", alreadyMergedCommit.name());
+ logger.atFine().log("Already merged as %s", alreadyMergedCommit.name());
}
CodeReviewCommit tipAfter = args.mergeTip.getCurrentTip();
if (Objects.equals(tipBefore, tipAfter)) {
- logDebug("Did not move tip", getClass().getSimpleName());
+ logger.atFine().log("Did not move tip");
return;
} else if (tipAfter == null) {
- logDebug("No merge tip, no update to perform");
+ logger.atFine().log("No merge tip, no update to perform");
return;
}
- logDebug("Moved tip from %s to %s", tipBefore, tipAfter);
+ logger.atFine().log("Moved tip from %s to %s", tipBefore, tipAfter);
checkProjectConfig(ctx, tipAfter);
@@ -144,7 +143,7 @@
throws IntegrationException {
String refName = getDest().get();
if (RefNames.REFS_CONFIG.equals(refName)) {
- logDebug("Loading new configuration from %s", RefNames.REFS_CONFIG);
+ logger.atFine().log("Loading new configuration from %s", RefNames.REFS_CONFIG);
try {
ProjectConfig cfg = new ProjectConfig(getProject());
cfg.load(ctx.getRevWalk(), commit);
@@ -184,8 +183,7 @@
continue; // Bogus ref, can't be merged into tip so we don't care.
}
}
- Collections.sort(
- commits,
+ commits.sort(
ReviewDbUtil.intKeyOrdering().reverse().onResultOf(CodeReviewCommit::getPatchsetId));
CodeReviewCommit result = MergeUtil.findAnyMergedInto(rw, commits, tip);
if (result == null) {
@@ -216,7 +214,8 @@
@Override
public final boolean updateChange(ChangeContext ctx) throws Exception {
- logDebug("%s#updateChange for change %s", getClass().getSimpleName(), toMerge.change().getId());
+ logger.atFine().log(
+ "%s#updateChange for change %s", getClass().getSimpleName(), toMerge.change().getId());
toMerge.setNotes(ctx.getNotes()); // Update change and notes from ctx.
PatchSet.Id oldPsId = checkNotNull(toMerge.getPatchsetId());
PatchSet.Id newPsId;
@@ -225,12 +224,12 @@
// Either another thread won a race, or we are retrying a whole topic submission after one
// repo failed with lock failure.
if (alreadyMergedCommit == null) {
- logDebug(
+ logger.atFine().log(
"Change is already merged according to its status, but we were unable to find it"
+ " merged into the current tip (%s)",
args.mergeTip.getCurrentTip().name());
} else {
- logDebug("Change is already merged");
+ logger.atFine().log("Change is already merged");
}
changeAlreadyMerged = true;
return false;
@@ -276,7 +275,7 @@
checkNotNull(commit, "missing commit for change " + id);
CommitMergeStatus s = commit.getStatusCode();
checkNotNull(s, "status not set for change " + id + " expected to previously fail fast");
- logDebug("Status of change %s (%s) on %s: %s", id, commit.name(), c.getDest(), s);
+ logger.atFine().log("Status of change %s (%s) on %s: %s", id, commit.name(), c.getDest(), s);
setApproval(ctx, args.caller);
mergeResultRev =
@@ -302,7 +301,7 @@
private PatchSet getOrCreateAlreadyMergedPatchSet(ChangeContext ctx)
throws IOException, OrmException {
PatchSet.Id psId = alreadyMergedCommit.getPatchsetId();
- logDebug("Fixing up already-merged patch set %s", psId);
+ logger.atFine().log("Fixing up already-merged patch set %s", psId);
PatchSet prevPs = args.psUtil.current(ctx.getDb(), ctx.getNotes());
ctx.getRevWalk().parseBody(alreadyMergedCommit);
ctx.getChange()
@@ -310,7 +309,7 @@
psId, alreadyMergedCommit.getShortMessage(), ctx.getChange().getOriginalSubject());
PatchSet existing = args.psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
if (existing != null) {
- logDebug("Patch set row exists, only updating change");
+ logger.atFine().log("Patch set row exists, only updating change");
return existing;
}
// No patch set for the already merged commit, although we know it came form
@@ -336,7 +335,7 @@
PatchSet.Id oldPsId = toMerge.getPatchsetId();
PatchSet.Id newPsId = ctx.getChange().currentPatchSetId();
- logDebug("Add approval for %s", id);
+ logger.atFine().log("Add approval for %s", id);
ChangeUpdate origPsUpdate = ctx.getUpdate(oldPsId);
origPsUpdate.putReviewer(user.getAccountId(), REVIEWER);
LabelNormalizer.Result normalized = approve(ctx, origPsUpdate);
@@ -397,7 +396,7 @@
// change happened.
for (PatchSetApproval psa : normalized.unchanged()) {
if (includeUnchanged || psa.isLegacySubmit()) {
- logDebug("Adding submit label %s", psa);
+ logger.atFine().log("Adding submit label %s", psa);
update.putApprovalFor(psa.getAccountId(), psa.getLabel(), psa.getValue());
}
}
@@ -488,7 +487,7 @@
private void setMerged(ChangeContext ctx, ChangeMessage msg) throws OrmException {
Change c = ctx.getChange();
ReviewDb db = ctx.getDb();
- logDebug("Setting change %s merged", c.getId());
+ logger.atFine().log("Setting change %s merged", c.getId());
c.setStatus(Change.Status.MERGED);
c.setSubmissionId(args.submissionId.toStringForStorage());
@@ -511,7 +510,7 @@
// If we naively execute postUpdate even if the change is already merged when updateChange
// being, then we are subject to a race where postUpdate steps are run twice if two submit
// processes run at the same time.
- logDebug("Skipping post-update steps for change %s", getId());
+ logger.atFine().log("Skipping post-update steps for change %s", getId());
return;
}
postUpdateImpl(ctx);
@@ -595,37 +594,4 @@
"cannot update gitlink for the commit at branch: " + args.destBranch);
}
}
-
- protected final void logDebug(String msg) {
- logger.atFine().log(this.args.submissionId + msg);
- }
-
- protected final void logDebug(String msg, @Nullable Object arg) {
- logger.atFine().log(this.args.submissionId + msg, arg);
- }
-
- protected final void logDebug(String msg, @Nullable Object arg1, @Nullable Object arg2) {
- logger.atFine().log(this.args.submissionId + msg, arg1, arg2);
- }
-
- protected final void logDebug(
- String msg,
- @Nullable Object arg1,
- @Nullable Object arg2,
- @Nullable Object arg3,
- @Nullable Object arg4) {
- logger.atFine().log(this.args.submissionId + msg, arg1, arg2, arg3, arg4);
- }
-
- protected final void logWarn(String msg, Throwable t) {
- logger.atWarning().withCause(t).log("%s%s", args.submissionId, msg);
- }
-
- protected void logError(String msg, Throwable t) {
- logger.atSevere().withCause(t).log("%s%s", args.submissionId, msg);
- }
-
- protected void logError(String msg) {
- logError(msg, null);
- }
}
diff --git a/java/com/google/gerrit/server/submit/SubmoduleOp.java b/java/com/google/gerrit/server/submit/SubmoduleOp.java
index 319e2e1..50be62a 100644
--- a/java/com/google/gerrit/server/submit/SubmoduleOp.java
+++ b/java/com/google/gerrit/server/submit/SubmoduleOp.java
@@ -15,12 +15,12 @@
package com.google.gerrit.server.submit;
import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.SetMultimap;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.SubscribeSection;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Branch;
@@ -28,6 +28,7 @@
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.UsedAt;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.VerboseSuperprojectUpdate;
import com.google.gerrit.server.git.CodeReviewCommit;
@@ -140,17 +141,31 @@
private final MergeOpRepoManager orm;
private final Map<Branch.NameKey, GitModules> branchGitModules;
- // always update-to-current branch tips during submit process
+ /** Branches updated as part of the enclosing submit or push batch. */
+ private final ImmutableSet<Branch.NameKey> updatedBranches;
+
+ /**
+ * Current branch tips, taking into account commits created during the submit process as well as
+ * submodule updates produced by this class.
+ */
private final Map<Branch.NameKey, CodeReviewCommit> branchTips;
- // branches for all the submitting changes
- private final Set<Branch.NameKey> updatedBranches;
- // branches which in either a submodule or a superproject
+
+ /**
+ * Branches in a superproject that contain submodule subscriptions, plus branches in submodules
+ * which are subscribed to by some superproject.
+ */
private final Set<Branch.NameKey> affectedBranches;
- // sorted version of affectedBranches
+
+ /** Copy of {@link #affectedBranches}, sorted by submodule traversal order. */
private final ImmutableSet<Branch.NameKey> sortedBranches;
- // map of superproject branch and its submodule subscriptions
+
+ /** Multimap of superproject branch to submodule subscriptions contained in that branch. */
private final SetMultimap<Branch.NameKey, SubmoduleSubscription> targets;
- // map of superproject and its branches which has submodule subscriptions
+
+ /**
+ * Multimap of superproject name to all branch names within that superproject which have submodule
+ * subscriptions.
+ */
private final SetMultimap<Project.NameKey, Branch.NameKey> branchesByProject;
private SubmoduleOp(
@@ -174,29 +189,57 @@
cfg.getLong("submodule", "maxCombinedCommitMessageSize", 256 << 10);
this.maxCommitMessages = cfg.getLong("submodule", "maxCommitMessages", 1000);
this.orm = orm;
- this.updatedBranches = updatedBranches;
+ this.updatedBranches = ImmutableSet.copyOf(updatedBranches);
this.targets = MultimapBuilder.hashKeys().hashSetValues().build();
this.affectedBranches = new HashSet<>();
this.branchTips = new HashMap<>();
this.branchGitModules = new HashMap<>();
this.branchesByProject = MultimapBuilder.hashKeys().hashSetValues().build();
- this.sortedBranches = calculateSubscriptionMap();
+ this.sortedBranches = calculateSubscriptionMaps();
}
- private ImmutableSet<Branch.NameKey> calculateSubscriptionMap() throws SubmoduleException {
+ /**
+ * Calculate the internal maps used by the operation.
+ *
+ * <p>In addition to the return value, the following fields are populated as a side effect:
+ *
+ * <ul>
+ * <li>{@link #affectedBranches}
+ * <li>{@link #targets}
+ * <li>{@link #branchesByProject}
+ * </ul>
+ *
+ * @return the ordered set to be stored in {@link #sortedBranches}.
+ * @throws SubmoduleException if an error occurred walking projects.
+ */
+ // TODO(dborowitz): This setup process is hard to follow, in large part due to the accumulation of
+ // mutable maps, which makes this whole class difficult to understand.
+ //
+ // A cleaner architecture for this process might be:
+ // 1. Separate out the code to parse submodule subscriptions and build up an in-memory data
+ // structure representing the subscription graph, using a separate class with a properly-
+ // documented interface.
+ // 2. Walk the graph to produce a work plan. This would be a list of items indicating: create a
+ // commit in project X reading branch tips for submodules S1..Sn and updating gitlinks in X.
+ // 3. Execute the work plan, i.e. convert the items into BatchUpdate.Ops and add them to the
+ // relevant updates.
+ //
+ // In addition to improving readability, this approach has the advantage of making (1) and (2)
+ // testable using small tests.
+ private ImmutableSet<Branch.NameKey> calculateSubscriptionMaps() throws SubmoduleException {
if (!enableSuperProjectSubscriptions) {
- logDebug("Updating superprojects disabled");
+ logger.atFine().log("Updating superprojects disabled");
return null;
}
- logDebug("Calculating superprojects - submodules map");
+ logger.atFine().log("Calculating superprojects - submodules map");
LinkedHashSet<Branch.NameKey> allVisited = new LinkedHashSet<>();
for (Branch.NameKey updatedBranch : updatedBranches) {
if (allVisited.contains(updatedBranch)) {
continue;
}
- searchForSuperprojects(updatedBranch, new LinkedHashSet<Branch.NameKey>(), allVisited);
+ searchForSuperprojects(updatedBranch, new LinkedHashSet<>(), allVisited);
}
// Since the searchForSuperprojects will add all branches (related or
@@ -213,7 +256,7 @@
LinkedHashSet<Branch.NameKey> currentVisited,
LinkedHashSet<Branch.NameKey> allVisited)
throws SubmoduleException {
- logDebug("Now processing %s", current);
+ logger.atFine().log("Now processing %s", current);
if (currentVisited.contains(current)) {
throw new SubmoduleException(
@@ -275,9 +318,9 @@
private Collection<Branch.NameKey> getDestinationBranches(Branch.NameKey src, SubscribeSection s)
throws IOException {
Collection<Branch.NameKey> ret = new HashSet<>();
- logDebug("Inspecting SubscribeSection %s", s);
+ logger.atFine().log("Inspecting SubscribeSection %s", s);
for (RefSpec r : s.getMatchingRefSpecs()) {
- logDebug("Inspecting [matching] ref %s", r);
+ logger.atFine().log("Inspecting [matching] ref %s", r);
if (!r.matchSource(src.get())) {
continue;
}
@@ -295,7 +338,7 @@
}
for (RefSpec r : s.getMultiMatchRefSpecs()) {
- logDebug("Inspecting [all] ref %s", r);
+ logger.atFine().log("Inspecting [all] ref %s", r);
if (!r.matchSource(src.get())) {
continue;
}
@@ -319,17 +362,18 @@
}
}
}
- logDebug("Returning possible branches: %s for project %s", ret, s.getProject());
+ logger.atFine().log("Returning possible branches: %s for project %s", ret, s.getProject());
return ret;
}
+ @UsedAt(UsedAt.Project.PLUGIN_DELETE_PROJECT)
public Collection<SubmoduleSubscription> superProjectSubscriptionsForSubmoduleBranch(
Branch.NameKey srcBranch) throws IOException {
- logDebug("Calculating possible superprojects for %s", srcBranch);
+ logger.atFine().log("Calculating possible superprojects for %s", srcBranch);
Collection<SubmoduleSubscription> ret = new ArrayList<>();
Project.NameKey srcProject = srcBranch.getParentKey();
for (SubscribeSection s : projectCache.get(srcProject).getSubscribeSections(srcBranch)) {
- logDebug("Checking subscribe section %s", s);
+ logger.atFine().log("Checking subscribe section %s", s);
Collection<Branch.NameKey> branches = getDestinationBranches(srcBranch, s);
for (Branch.NameKey targetBranch : branches) {
Project.NameKey targetProject = targetBranch.getParentKey();
@@ -337,11 +381,11 @@
OpenRepo or = orm.getRepo(targetProject);
ObjectId id = or.repo.resolve(targetBranch.get());
if (id == null) {
- logDebug("The branch %s doesn't exist.", targetBranch);
+ logger.atFine().log("The branch %s doesn't exist.", targetBranch);
continue;
}
} catch (NoSuchProjectException e) {
- logDebug("The project %s doesn't exist", targetProject);
+ logger.atFine().log("The project %s doesn't exist", targetProject);
continue;
}
@@ -353,7 +397,7 @@
ret.addAll(m.subscribedTo(srcBranch));
}
}
- logDebug("Calculated superprojects for %s are %s", srcBranch, ret);
+ logger.atFine().log("Calculated superprojects for %s are %s", srcBranch, ret);
return ret;
}
@@ -376,15 +420,14 @@
}
}
}
- batchUpdateFactory.execute(
- orm.batchUpdates(superProjects), BatchUpdateListener.NONE, orm.getSubmissionId(), false);
+ batchUpdateFactory.execute(orm.batchUpdates(superProjects), BatchUpdateListener.NONE, false);
} catch (RestApiException | UpdateException | IOException | NoSuchProjectException e) {
throw new SubmoduleException("Cannot update gitlinks", e);
}
}
/** Create a separate gitlink commit */
- public CodeReviewCommit composeGitlinksCommit(Branch.NameKey subscriber)
+ private CodeReviewCommit composeGitlinksCommit(Branch.NameKey subscriber)
throws IOException, SubmoduleException {
OpenRepo or;
try {
@@ -406,14 +449,18 @@
addBranchTip(subscriber, currentCommit);
}
- StringBuilder msgbuf = new StringBuilder("");
+ StringBuilder msgbuf = new StringBuilder();
PersonIdent author = null;
DirCache dc = readTree(or.rw, currentCommit);
DirCacheEditor ed = dc.editor();
int count = 0;
- List<SubmoduleSubscription> subscriptions = new ArrayList<>(targets.get(subscriber));
- Collections.sort(subscriptions, comparing(SubmoduleSubscription::getPath));
+ List<SubmoduleSubscription> subscriptions =
+ targets
+ .get(subscriber)
+ .stream()
+ .sorted(comparing(SubmoduleSubscription::getPath))
+ .collect(toList());
for (SubmoduleSubscription s : subscriptions) {
if (count > 0) {
msgbuf.append("\n\n");
@@ -452,8 +499,7 @@
}
/** Amend an existing commit with gitlink updates */
- public CodeReviewCommit composeGitlinksCommit(
- Branch.NameKey subscriber, CodeReviewCommit currentCommit)
+ CodeReviewCommit composeGitlinksCommit(Branch.NameKey subscriber, CodeReviewCommit currentCommit)
throws IOException, SubmoduleException {
OpenRepo or;
try {
@@ -462,7 +508,7 @@
throw new SubmoduleException("Cannot access superproject", e);
}
- StringBuilder msgbuf = new StringBuilder("");
+ StringBuilder msgbuf = new StringBuilder();
DirCache dc = readTree(or.rw, currentCommit);
DirCacheEditor ed = dc.editor();
for (SubmoduleSubscription s : targets.get(subscriber)) {
@@ -496,6 +542,7 @@
private RevCommit updateSubmodule(
DirCache dc, DirCacheEditor ed, StringBuilder msgbuf, SubmoduleSubscription s)
throws SubmoduleException, IOException {
+ logger.atFine().log("Updating gitlink for %s", s);
OpenRepo subOr;
try {
subOr = orm.getRepo(s.getSubmodule().getParentKey());
@@ -523,7 +570,14 @@
// check that the old gitlink is a commit that actually exists. If not, then there is an
// inconsistency between the superproject and subproject state, and we don't want to risk
// making things worse by updating the gitlink to something else.
- oldCommit = subOr.rw.parseCommit(dce.getObjectId());
+ try {
+ oldCommit = subOr.rw.parseCommit(dce.getObjectId());
+ } catch (IOException e) {
+ // Broken gitlink; sanity check failed. Warn and continue so the submit operation can
+ // proceed, it will just skip this gitlink update.
+ logger.atSevere().withCause(e).log("Failed to read commit %s", dce.getObjectId().name());
+ return null;
+ }
}
final CodeReviewCommit newCommit;
@@ -577,7 +631,8 @@
msgbuf.append(" from branch '");
msgbuf.append(s.getSubmodule().getShortName());
msgbuf.append("'");
- msgbuf.append("\n to " + newCommit.getName());
+ msgbuf.append("\n to ");
+ msgbuf.append(newCommit.getName());
// newly created submodule gitlink, do not append whole history
if (oldCommit == null) {
@@ -628,7 +683,7 @@
return dc;
}
- public ImmutableSet<Project.NameKey> getProjectsInOrder() throws SubmoduleException {
+ ImmutableSet<Project.NameKey> getProjectsInOrder() throws SubmoduleException {
LinkedHashSet<Project.NameKey> projects = new LinkedHashSet<>();
for (Project.NameKey project : branchesByProject.keySet()) {
addAllSubmoduleProjects(project, new LinkedHashSet<>(), projects);
@@ -671,7 +726,7 @@
projects.add(project);
}
- public ImmutableSet<Branch.NameKey> getBranchesInOrder() {
+ ImmutableSet<Branch.NameKey> getBranchesInOrder() {
LinkedHashSet<Branch.NameKey> branches = new LinkedHashSet<>();
if (sortedBranches != null) {
branches.addAll(sortedBranches);
@@ -680,27 +735,15 @@
return ImmutableSet.copyOf(branches);
}
- public boolean hasSubscription(Branch.NameKey branch) {
+ boolean hasSubscription(Branch.NameKey branch) {
return targets.containsKey(branch);
}
- public void addBranchTip(Branch.NameKey branch, CodeReviewCommit tip) {
+ void addBranchTip(Branch.NameKey branch, CodeReviewCommit tip) {
branchTips.put(branch, tip);
}
- public void addOp(BatchUpdate bu, Branch.NameKey branch) {
+ void addOp(BatchUpdate bu, Branch.NameKey branch) {
bu.addRepoOnlyOp(new GitlinkOp(branch));
}
-
- private void logDebug(String msg) {
- logger.atFine().log(orm.getSubmissionId() + " " + msg);
- }
-
- private void logDebug(String msg, @Nullable Object arg) {
- logger.atFine().log(orm.getSubmissionId() + " " + msg, arg);
- }
-
- private void logDebug(String msg, @Nullable Object arg1, @Nullable Object arg2) {
- logger.atFine().log(orm.getSubmissionId() + " " + msg, arg1, arg2);
- }
}
diff --git a/java/com/google/gerrit/server/submit/TestHelperOp.java b/java/com/google/gerrit/server/submit/TestHelperOp.java
index 2f0a3f6..bbb198a 100644
--- a/java/com/google/gerrit/server/submit/TestHelperOp.java
+++ b/java/com/google/gerrit/server/submit/TestHelperOp.java
@@ -15,12 +15,10 @@
package com.google.gerrit.server.submit;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.change.TestSubmitInput;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.RepoContext;
-import com.google.gerrit.server.util.RequestId;
import java.io.IOException;
import java.util.Queue;
import org.eclipse.jgit.lib.ObjectId;
@@ -30,27 +28,22 @@
private final Change.Id changeId;
private final TestSubmitInput input;
- private final RequestId submissionId;
TestHelperOp(Change.Id changeId, SubmitStrategy.Arguments args) {
this.changeId = changeId;
this.input = (TestSubmitInput) args.submitInput;
- this.submissionId = args.submissionId;
}
@Override
public void updateRepo(RepoContext ctx) throws IOException {
Queue<Boolean> q = input.generateLockFailures;
if (q != null && !q.isEmpty() && q.remove()) {
- logDebug("Adding bogus ref update to trigger lock failure, via change %s", changeId);
+ logger.atFine().log(
+ "Adding bogus ref update to trigger lock failure, via change %s", changeId);
ctx.addRefUpdate(
ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"),
ObjectId.zeroId(),
"refs/test/" + getClass().getSimpleName());
}
}
-
- private void logDebug(String msg, @Nullable Object arg) {
- logger.atFine().log(submissionId + msg, arg);
- }
}
diff --git a/java/com/google/gerrit/server/update/BatchUpdate.java b/java/com/google/gerrit/server/update/BatchUpdate.java
index dd3cc73..60cacee 100644
--- a/java/com/google/gerrit/server/update/BatchUpdate.java
+++ b/java/com/google/gerrit/server/update/BatchUpdate.java
@@ -38,12 +38,12 @@
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.validators.OnSubmitValidators;
+import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.NoSuchRefException;
-import com.google.gerrit.server.util.RequestId;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
@@ -126,10 +126,7 @@
@SuppressWarnings({"rawtypes", "unchecked"})
public void execute(
- Collection<BatchUpdate> updates,
- BatchUpdateListener listener,
- @Nullable RequestId requestId,
- boolean dryRun)
+ Collection<BatchUpdate> updates, BatchUpdateListener listener, boolean dryRun)
throws UpdateException, RestApiException {
checkNotNull(listener);
checkDifferentProject(updates);
@@ -141,11 +138,11 @@
if (migration.disableChangeReviewDb()) {
ImmutableList<NoteDbBatchUpdate> noteDbUpdates =
(ImmutableList) ImmutableList.copyOf(updates);
- NoteDbBatchUpdate.execute(noteDbUpdates, listener, requestId, dryRun);
+ NoteDbBatchUpdate.execute(noteDbUpdates, listener, dryRun);
} else {
ImmutableList<ReviewDbBatchUpdate> reviewDbUpdates =
(ImmutableList) ImmutableList.copyOf(updates);
- ReviewDbBatchUpdate.execute(reviewDbUpdates, listener, requestId, dryRun);
+ ReviewDbBatchUpdate.execute(reviewDbUpdates, listener, dryRun);
}
}
@@ -159,20 +156,6 @@
}
}
- static void setRequestIds(
- Collection<? extends BatchUpdate> updates, @Nullable RequestId requestId) {
- if (requestId != null) {
- for (BatchUpdate u : updates) {
- checkArgument(
- u.requestId == null || u.requestId == requestId,
- "refusing to overwrite RequestId %s in update with %s",
- u.requestId,
- requestId);
- u.setRequestId(requestId);
- }
- }
- }
-
static Order getOrder(Collection<? extends BatchUpdate> updates, BatchUpdateListener listener) {
Order o = null;
for (BatchUpdate u : updates) {
@@ -248,7 +231,6 @@
protected BatchRefUpdate batchRefUpdate;
protected Order order;
protected OnSubmitValidators onSubmitValidators;
- protected RequestId requestId;
protected PushCertificate pushCert;
protected String refLogMessage;
@@ -284,11 +266,6 @@
protected abstract Context newContext();
- public BatchUpdate setRequestId(RequestId requestId) {
- this.requestId = requestId;
- return this;
- }
-
public BatchUpdate setRepository(Repository repo, RevWalk revWalk, ObjectInserter inserter) {
checkState(this.repoView == null, "repo already set");
repoView = new RepoView(repo, revWalk, inserter);
@@ -384,39 +361,39 @@
return this;
}
- protected void logDebug(String msg, Throwable t) {
+ protected static void logDebug(String msg, Throwable t) {
// Only log if there is a requestId assigned, since those are the
// expensive/complicated requests like MergeOp. Doing it every time would be
// noisy.
- if (requestId != null) {
- logger.atFine().withCause(t).log(requestId + "%s", msg);
+ if (RequestId.isSet()) {
+ logger.atFine().withCause(t).log("%s", msg);
}
}
- protected void logDebug(String msg) {
+ protected static void logDebug(String msg) {
// Only log if there is a requestId assigned, since those are the
// expensive/complicated requests like MergeOp. Doing it every time would be
// noisy.
- if (requestId != null) {
- logger.atFine().log(requestId + msg);
+ if (RequestId.isSet()) {
+ logger.atFine().log(msg);
}
}
- protected void logDebug(String msg, @Nullable Object arg) {
+ protected static void logDebug(String msg, @Nullable Object arg) {
// Only log if there is a requestId assigned, since those are the
// expensive/complicated requests like MergeOp. Doing it every time would be
// noisy.
- if (requestId != null) {
- logger.atFine().log(requestId + msg, arg);
+ if (RequestId.isSet()) {
+ logger.atFine().log(msg, arg);
}
}
- protected void logDebug(String msg, @Nullable Object arg1, @Nullable Object arg2) {
+ protected static void logDebug(String msg, @Nullable Object arg1, @Nullable Object arg2) {
// Only log if there is a requestId assigned, since those are the
// expensive/complicated requests like MergeOp. Doing it every time would be
// noisy.
- if (requestId != null) {
- logger.atFine().log(requestId + msg, arg1, arg2);
+ if (RequestId.isSet()) {
+ logger.atFine().log(msg, arg1, arg2);
}
}
}
diff --git a/java/com/google/gerrit/server/update/NoteDbBatchUpdate.java b/java/com/google/gerrit/server/update/NoteDbBatchUpdate.java
index 8612fac..abe865c 100644
--- a/java/com/google/gerrit/server/update/NoteDbBatchUpdate.java
+++ b/java/com/google/gerrit/server/update/NoteDbBatchUpdate.java
@@ -21,7 +21,6 @@
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -35,7 +34,6 @@
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
-import com.google.gerrit.server.util.RequestId;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -68,15 +66,11 @@
}
static void execute(
- ImmutableList<NoteDbBatchUpdate> updates,
- BatchUpdateListener listener,
- @Nullable RequestId requestId,
- boolean dryrun)
+ ImmutableList<NoteDbBatchUpdate> updates, BatchUpdateListener listener, boolean dryrun)
throws UpdateException, RestApiException {
if (updates.isEmpty()) {
return;
}
- setRequestIds(updates, requestId);
try {
@SuppressWarnings("deprecation")
@@ -293,7 +287,7 @@
@Override
public void execute(BatchUpdateListener listener) throws UpdateException, RestApiException {
- execute(ImmutableList.of(this), listener, requestId, false);
+ execute(ImmutableList.of(this), listener, false);
}
@Override
diff --git a/java/com/google/gerrit/server/update/RetryHelper.java b/java/com/google/gerrit/server/update/RetryHelper.java
index 132c04b..10e3455 100644
--- a/java/com/google/gerrit/server/update/RetryHelper.java
+++ b/java/com/google/gerrit/server/update/RetryHelper.java
@@ -31,6 +31,7 @@
import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.metrics.Counter1;
@@ -52,6 +53,8 @@
@Singleton
public class RetryHelper {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
@FunctionalInterface
public interface ChangeAction<T> {
T call(BatchUpdate.Factory batchUpdateFactory) throws Exception;
@@ -277,6 +280,9 @@
retryerBuilder.withRetryListener(listener);
return executeWithTimeoutCount(actionType, action, retryerBuilder.build());
} finally {
+ if (listener.getAttemptCount() > 1) {
+ logger.atFine().log("%s was attempted %d times", actionType, listener.getAttemptCount());
+ }
metrics.attemptCounts.record(actionType, listener.getAttemptCount());
}
}
diff --git a/java/com/google/gerrit/server/update/ReviewDbBatchUpdate.java b/java/com/google/gerrit/server/update/ReviewDbBatchUpdate.java
index 9bf4bb2..df50bd5 100644
--- a/java/com/google/gerrit/server/update/ReviewDbBatchUpdate.java
+++ b/java/com/google/gerrit/server/update/ReviewDbBatchUpdate.java
@@ -28,7 +28,6 @@
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
-import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.metrics.Description;
@@ -51,6 +50,7 @@
import com.google.gerrit.server.git.InsertedObject;
import com.google.gerrit.server.git.LockFailureException;
import com.google.gerrit.server.index.change.ChangeIndexer;
+import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.NoteDbChangeState;
@@ -58,7 +58,6 @@
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
import com.google.gerrit.server.notedb.NoteDbUpdateManager.MismatchedStateException;
import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gerrit.server.util.RequestId;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
@@ -240,15 +239,11 @@
}
static void execute(
- ImmutableList<ReviewDbBatchUpdate> updates,
- BatchUpdateListener listener,
- @Nullable RequestId requestId,
- boolean dryrun)
+ ImmutableList<ReviewDbBatchUpdate> updates, BatchUpdateListener listener, boolean dryrun)
throws UpdateException, RestApiException {
if (updates.isEmpty()) {
return;
}
- setRequestIds(updates, requestId);
try {
Order order = getOrder(updates, listener);
boolean updateChangesInParallel = getUpdateChangesInParallel(updates);
@@ -358,7 +353,7 @@
@Override
public void execute(BatchUpdateListener listener) throws UpdateException, RestApiException {
- execute(ImmutableList.of(this), listener, requestId, false);
+ execute(ImmutableList.of(this), listener, false);
}
@Override
@@ -616,7 +611,6 @@
NoteDbUpdateManager.StagedResult noteDbResult;
boolean dirty;
boolean deleted;
- private String taskId;
private ChangeTask(
Change.Id id, Collection<BatchUpdateOp> changeOps, Thread mainThread, boolean dryrun) {
@@ -628,27 +622,30 @@
@Override
public Void call() throws Exception {
- taskId = id.toString() + "-" + Thread.currentThread().getId();
- if (Thread.currentThread() == mainThread) {
- initRepository();
- Repository repo = repoView.getRepository();
- try (RevWalk rw = new RevWalk(repo)) {
- call(ReviewDbBatchUpdate.this.db, repo, rw);
+ try (TraceContext traceContext =
+ TraceContext.open()
+ .addTag("TASK_ID", id.toString() + "-" + Thread.currentThread().getId())) {
+ if (Thread.currentThread() == mainThread) {
+ initRepository();
+ Repository repo = repoView.getRepository();
+ try (RevWalk rw = new RevWalk(repo)) {
+ call(ReviewDbBatchUpdate.this.db, repo, rw);
+ }
+ } else {
+ // Possible optimization: allow Ops to declare whether they need to
+ // access the repo from updateChange, and don't open in this thread
+ // unless we need it. However, as of this writing the only operations
+ // that are executed in parallel are during ReceiveCommits, and they
+ // all need the repo open anyway. (The non-parallel case above does not
+ // reopen the repo.)
+ try (ReviewDb threadLocalDb = schemaFactory.open();
+ Repository repo = repoManager.openRepository(project);
+ RevWalk rw = new RevWalk(repo)) {
+ call(threadLocalDb, repo, rw);
+ }
}
- } else {
- // Possible optimization: allow Ops to declare whether they need to
- // access the repo from updateChange, and don't open in this thread
- // unless we need it. However, as of this writing the only operations
- // that are executed in parallel are during ReceiveCommits, and they
- // all need the repo open anyway. (The non-parallel case above does not
- // reopen the repo.)
- try (ReviewDb threadLocalDb = schemaFactory.open();
- Repository repo = repoManager.openRepository(project);
- RevWalk rw = new RevWalk(repo)) {
- call(threadLocalDb, repo, rw);
- }
+ return null;
}
- return null;
}
private void call(ReviewDb db, Repository repo, RevWalk rw) throws Exception {
@@ -822,18 +819,6 @@
private boolean isNewChange(Change.Id id) {
return newChanges.containsKey(id);
}
-
- private void logDebug(String msg, Throwable t) {
- ReviewDbBatchUpdate.this.logDebug("[" + taskId + "] " + msg, t);
- }
-
- private void logDebug(String msg) {
- ReviewDbBatchUpdate.this.logDebug("[" + taskId + "] " + msg);
- }
-
- private void logDebug(String msg, @Nullable Object arg) {
- ReviewDbBatchUpdate.this.logDebug("[" + taskId + "] " + msg, arg);
- }
}
private static Iterable<Change> changesToUpdate(ChangeContextImpl ctx) {
diff --git a/java/com/google/gerrit/sshd/BUILD b/java/com/google/gerrit/sshd/BUILD
index 4743b35..47b1d89 100644
--- a/java/com/google/gerrit/sshd/BUILD
+++ b/java/com/google/gerrit/sshd/BUILD
@@ -15,6 +15,7 @@
"//java/com/google/gerrit/server/cache/h2",
"//java/com/google/gerrit/server/git/receive",
"//java/com/google/gerrit/server/ioutil",
+ "//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/server/restapi",
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/util/cli",
diff --git a/java/com/google/gerrit/sshd/CommandFactoryProvider.java b/java/com/google/gerrit/sshd/CommandFactoryProvider.java
index 3eef4d6..1fdf7d8 100644
--- a/java/com/google/gerrit/sshd/CommandFactoryProvider.java
+++ b/java/com/google/gerrit/sshd/CommandFactoryProvider.java
@@ -22,6 +22,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.logging.LoggingContextAwareExecutorService;
import com.google.gerrit.sshd.SshScope.Context;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
@@ -77,11 +78,12 @@
int threads = cfg.getInt("sshd", "commandStartThreads", 2);
startExecutor = workQueue.createQueue(threads, "SshCommandStart", true);
destroyExecutor =
- Executors.newSingleThreadExecutor(
- new ThreadFactoryBuilder()
- .setNameFormat("SshCommandDestroy-%s")
- .setDaemon(true)
- .build());
+ new LoggingContextAwareExecutorService(
+ Executors.newSingleThreadExecutor(
+ new ThreadFactoryBuilder()
+ .setNameFormat("SshCommandDestroy-%s")
+ .setDaemon(true)
+ .build()));
}
@Override
diff --git a/java/com/google/gerrit/sshd/SshCommand.java b/java/com/google/gerrit/sshd/SshCommand.java
index 3e42ebe..99c8724 100644
--- a/java/com/google/gerrit/sshd/SshCommand.java
+++ b/java/com/google/gerrit/sshd/SshCommand.java
@@ -14,11 +14,19 @@
package com.google.gerrit.sshd;
+import com.google.gerrit.server.logging.TraceContext;
import java.io.IOException;
import java.io.PrintWriter;
import org.apache.sshd.server.Environment;
+import org.kohsuke.args4j.Option;
public abstract class SshCommand extends BaseCommand {
+ @Option(name = "--trace", usage = "enable request tracing")
+ private boolean trace;
+
+ @Option(name = "--trace-id", usage = "trace ID (can only be set if --trace was set too)")
+ private String traceId;
+
protected PrintWriter stdout;
protected PrintWriter stderr;
@@ -31,7 +39,7 @@
parseCommandLine();
stdout = toPrintWriter(out);
stderr = toPrintWriter(err);
- try {
+ try (TraceContext traceContext = enableTracing()) {
SshCommand.this.run();
} finally {
stdout.flush();
@@ -42,4 +50,14 @@
}
protected abstract void run() throws UnloggedFailure, Failure, Exception;
+
+ private TraceContext enableTracing() throws UnloggedFailure {
+ if (!trace && traceId != null) {
+ throw die("A trace ID can only be set if --trace was specified.");
+ }
+ return TraceContext.newTrace(
+ trace,
+ traceId,
+ (tagName, traceId) -> stderr.println(String.format("%s: %s", tagName, traceId)));
+ }
}
diff --git a/java/com/google/gerrit/sshd/SshKeyCacheImpl.java b/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
index b573062..81ce91d 100644
--- a/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
+++ b/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
@@ -86,6 +86,7 @@
@Override
public void evict(String username) {
if (username != null) {
+ logger.atFine().log("Evict SSH key for username %s", username);
cache.invalidate(username);
}
}
@@ -102,6 +103,8 @@
@Override
public Iterable<SshKeyCacheEntry> load(String username) throws Exception {
+ logger.atFine().log("Loading SSH keys for account with username %s", username);
+
Optional<ExternalId> user = externalIds.get(ExternalId.Key.create(SCHEME_USERNAME, username));
if (!user.isPresent()) {
return NO_SUCH_USER;
diff --git a/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index 0e36d53..1c857e4 100644
--- a/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -114,7 +114,7 @@
command(gerrit, SetMembersCommand.class);
command(gerrit, CreateBranchCommand.class);
command(gerrit, SetAccountCommand.class);
- command(gerrit, AdminSetParent.class);
+ command(gerrit, SetParentCommand.class);
command(testSubmit, TestSubmitRuleCommand.class);
command(testSubmit, TestSubmitTypeCommand.class);
diff --git a/java/com/google/gerrit/sshd/commands/AdminSetParent.java b/java/com/google/gerrit/sshd/commands/SetParentCommand.java
similarity index 66%
rename from java/com/google/gerrit/sshd/commands/AdminSetParent.java
rename to java/com/google/gerrit/sshd/commands/SetParentCommand.java
index 67ed098..56ee371 100644
--- a/java/com/google/gerrit/sshd/commands/AdminSetParent.java
+++ b/java/com/google/gerrit/sshd/commands/SetParentCommand.java
@@ -16,41 +16,36 @@
import static java.util.stream.Collectors.toList;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.api.projects.ParentInput;
import com.google.gerrit.extensions.common.ProjectInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.restapi.project.ListChildProjects;
+import com.google.gerrit.server.restapi.project.SetParent;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@CommandMetaData(
name = "set-project-parent",
description = "Change the project permissions are inherited from")
-final class AdminSetParent extends SshCommand {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
+final class SetParentCommand extends SshCommand {
@Option(
name = "--parent",
aliases = {"-p"},
@@ -80,14 +75,18 @@
@Inject private ProjectCache projectCache;
- @Inject private MetaDataUpdate.User metaDataUpdateFactory;
-
- @Inject private AllProjectsName allProjectsName;
-
@Inject private ListChildProjects listChildProjects;
+ @Inject private SetParent setParent;
+
private Project.NameKey newParentKey;
+ private static ParentInput parentInput(String parent) {
+ ParentInput input = new ParentInput();
+ input.parent = parent;
+ return input;
+ }
+
@Override
protected void run() throws Failure {
if (oldParent == null && children.isEmpty()) {
@@ -100,25 +99,9 @@
}
final StringBuilder err = new StringBuilder();
- final Set<Project.NameKey> grandParents = new HashSet<>();
-
- grandParents.add(allProjectsName);
if (newParent != null) {
newParentKey = newParent.getProject().getNameKey();
-
- // Catalog all grandparents of the "parent", we want to
- // catch a cycle in the parent pointers before it occurs.
- //
- Project.NameKey gp = newParent.getProject().getParent();
- while (gp != null && grandParents.add(gp)) {
- final ProjectState s = projectCache.get(gp);
- if (s != null) {
- gp = s.getProject().getParent();
- } else {
- break;
- }
- }
}
final List<Project.NameKey> childProjects =
@@ -135,47 +118,19 @@
for (Project.NameKey nameKey : childProjects) {
final String name = nameKey.get();
-
- if (allProjectsName.equals(nameKey)) {
- // Don't allow the wild card project to have a parent.
- //
- err.append("error: Cannot set parent of '").append(name).append("'\n");
- continue;
- }
-
- if (grandParents.contains(nameKey) || nameKey.equals(newParentKey)) {
- // Try to avoid creating a cycle in the parent pointers.
- //
- err.append("error: Cycle exists between '")
- .append(name)
- .append("' and '")
- .append(newParentKey != null ? newParentKey.get() : allProjectsName.get())
- .append("'\n");
- continue;
- }
-
- try (MetaDataUpdate md = metaDataUpdateFactory.create(nameKey)) {
- ProjectConfig config = ProjectConfig.read(md);
- config.getProject().setParentName(newParentKey);
- md.setMessage(
- "Inherit access from "
- + (newParentKey != null ? newParentKey.get() : allProjectsName.get())
- + "\n");
- config.commit(md);
- } catch (RepositoryNotFoundException notFound) {
- err.append("error: Project ").append(name).append(" not found\n");
- } catch (IOException | ConfigInvalidException e) {
- final String msg = "Cannot update project " + name;
- logger.atSevere().withCause(e).log(msg);
- err.append("error: ").append(msg).append("\n");
- }
-
+ ProjectState project = projectCache.get(nameKey);
try {
- projectCache.evict(nameKey);
- } catch (IOException e) {
- final String msg = "Cannot reindex project: " + name;
- logger.atSevere().withCause(e).log(msg);
- err.append("error: ").append(msg).append("\n");
+ setParent.apply(new ProjectResource(project, user), parentInput(newParentKey.get()));
+ } catch (AuthException e) {
+ err.append("error: insuffient access rights to change parent of '")
+ .append(name)
+ .append("'\n");
+ } catch (ResourceConflictException | ResourceNotFoundException | BadRequestException e) {
+ err.append("error: ").append(e.getMessage()).append("'\n");
+ } catch (UnprocessableEntityException | IOException e) {
+ throw new Failure(1, "failure in request", e);
+ } catch (PermissionBackendException e) {
+ throw new Failure(1, "permissions unavailable", e);
}
}
diff --git a/java/com/google/gerrit/sshd/commands/ShowConnections.java b/java/com/google/gerrit/sshd/commands/ShowConnections.java
index 9b517c6..baadf02 100644
--- a/java/com/google/gerrit/sshd/commands/ShowConnections.java
+++ b/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -15,8 +15,10 @@
package com.google.gerrit.sshd.commands;
import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -33,11 +35,7 @@
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.Date;
-import java.util.List;
import java.util.Optional;
import org.apache.sshd.common.io.IoAcceptor;
import org.apache.sshd.common.io.IoSession;
@@ -92,25 +90,27 @@
throw new Failure(1, "fatal: sshd no longer running");
}
- final List<IoSession> list = new ArrayList<>(acceptor.getManagedSessions().values());
- Collections.sort(
- list,
- new Comparator<IoSession>() {
- @Override
- public int compare(IoSession arg0, IoSession arg1) {
- if (arg0 instanceof MinaSession) {
- MinaSession mArg0 = (MinaSession) arg0;
- MinaSession mArg1 = (MinaSession) arg1;
- if (mArg0.getSession().getCreationTime() < mArg1.getSession().getCreationTime()) {
- return -1;
- } else if (mArg0.getSession().getCreationTime()
- > mArg1.getSession().getCreationTime()) {
- return 1;
- }
- }
- return (int) (arg0.getId() - arg1.getId());
- }
- });
+ final ImmutableList<IoSession> list =
+ acceptor
+ .getManagedSessions()
+ .values()
+ .stream()
+ .sorted(
+ (arg0, arg1) -> {
+ if (arg0 instanceof MinaSession) {
+ MinaSession mArg0 = (MinaSession) arg0;
+ MinaSession mArg1 = (MinaSession) arg1;
+ if (mArg0.getSession().getCreationTime()
+ < mArg1.getSession().getCreationTime()) {
+ return -1;
+ } else if (mArg0.getSession().getCreationTime()
+ > mArg1.getSession().getCreationTime()) {
+ return 1;
+ }
+ }
+ return (int) (arg0.getId() - arg1.getId());
+ })
+ .collect(toImmutableList());
hostNameWidth = wide ? Integer.MAX_VALUE : columns - 9 - 9 - 10 - 32;
diff --git a/java/com/google/gerrit/sshd/commands/StreamEvents.java b/java/com/google/gerrit/sshd/commands/StreamEvents.java
index c97372c..ffd98d5 100644
--- a/java/com/google/gerrit/sshd/commands/StreamEvents.java
+++ b/java/com/google/gerrit/sshd/commands/StreamEvents.java
@@ -151,6 +151,7 @@
stdout = toPrintWriter(out);
eventListenerRegistration =
eventListeners.add(
+ "gerrit",
new UserScopedEventListener() {
@Override
public void onEvent(Event event) {
diff --git a/java/com/google/gerrit/testing/BUILD b/java/com/google/gerrit/testing/BUILD
index cf65908..15ceb77 100644
--- a/java/com/google/gerrit/testing/BUILD
+++ b/java/com/google/gerrit/testing/BUILD
@@ -29,6 +29,7 @@
"//java/com/google/gerrit/server/audit",
"//java/com/google/gerrit/server/cache/h2",
"//java/com/google/gerrit/server/cache/mem",
+ "//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/server/restapi",
"//java/com/google/gerrit/server/schema",
"//lib:guava",
diff --git a/java/com/google/gerrit/testing/ConfigSuite.java b/java/com/google/gerrit/testing/ConfigSuite.java
index b0229c3..9e45b7c 100644
--- a/java/com/google/gerrit/testing/ConfigSuite.java
+++ b/java/com/google/gerrit/testing/ConfigSuite.java
@@ -24,6 +24,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import com.google.gerrit.server.logging.LoggingContext;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -105,11 +106,13 @@
*/
public class ConfigSuite extends Suite {
private static final String FLOGGER_BACKEND_PROPERTY = "flogger.backend_factory";
+ private static final String FLOGGER_LOGGING_CONTEXT = "flogger.logging_context";
static {
System.setProperty(
FLOGGER_BACKEND_PROPERTY,
"com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance");
+ System.setProperty(FLOGGER_LOGGING_CONTEXT, LoggingContext.class.getName() + "#getInstance");
}
public static final String DEFAULT = "default";
@@ -156,7 +159,7 @@
@Override
public Object createTest() throws Exception {
- Object test = getTestClass().getJavaClass().newInstance();
+ Object test = getTestClass().getJavaClass().getDeclaredConstructor().newInstance();
parameterField.set(test, new org.eclipse.jgit.lib.Config(cfg));
if (nameField != null) {
nameField.set(test, name);
diff --git a/java/com/google/gerrit/util/cli/BUILD b/java/com/google/gerrit/util/cli/BUILD
index 91c14f7..c94fc1d 100644
--- a/java/com/google/gerrit/util/cli/BUILD
+++ b/java/com/google/gerrit/util/cli/BUILD
@@ -7,6 +7,7 @@
"//java/com/google/gerrit/common:server",
"//lib:args4j",
"//lib:guava",
+ "//lib/flogger:api",
"//lib/guice",
"//lib/guice:guice-assistedinject",
],
diff --git a/java/com/google/gerrit/util/cli/CmdLineParser.java b/java/com/google/gerrit/util/cli/CmdLineParser.java
index 8639a06..231b335 100644
--- a/java/com/google/gerrit/util/cli/CmdLineParser.java
+++ b/java/com/google/gerrit/util/cli/CmdLineParser.java
@@ -40,6 +40,7 @@
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.MultimapBuilder;
+import com.google.common.flogger.FluentLogger;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.StringWriter;
@@ -77,6 +78,8 @@
* from the GNU style format to the args4j style format prior to invoking args4j for parsing.
*/
public class CmdLineParser {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
public interface Factory {
CmdLineParser create(Object bean);
}
@@ -235,6 +238,7 @@
}
public void parseOptionMap(ListMultimap<String, String> params) throws CmdLineException {
+ logger.atFinest().log("Command-line parameters: %s", params.keySet());
List<String> tmp = Lists.newArrayListWithCapacity(2 * params.size());
for (String key : params.keySet()) {
String name = makeOption(key);
@@ -326,8 +330,8 @@
}
private static class PrefixedOption implements Option {
- String prefix;
- Option o;
+ private final String prefix;
+ private final Option o;
PrefixedOption(String prefix, Option o) {
this.prefix = prefix;
diff --git a/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java b/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
index 1318125..20d093e 100644
--- a/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
+++ b/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
@@ -14,6 +14,9 @@
package com.google.gwtexpui.globalkey.client;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
+
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
@@ -31,10 +34,7 @@
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@@ -228,15 +228,6 @@
}
private List<KeyCommand> sort(KeyCommandSet set) {
- final List<KeyCommand> keys = new ArrayList<>(set.getKeys());
- Collections.sort(
- keys,
- new Comparator<KeyCommand>() {
- @Override
- public int compare(KeyCommand arg0, KeyCommand arg1) {
- return arg0.getHelpText().compareTo(arg1.getHelpText());
- }
- });
- return keys;
+ return set.getKeys().stream().sorted(comparing(KeyCommand::getHelpText)).collect(toList());
}
}
diff --git a/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java b/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java
index ef80cdb..758521f 100644
--- a/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java
+++ b/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java
@@ -14,11 +14,12 @@
package com.google.gwtexpui.safehtml.client;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
+
import com.google.gwt.user.client.ui.SuggestOracle;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
/**
@@ -115,15 +116,8 @@
* terms.
*/
private static List<String> splitQuery(String query) {
- List<String> queryTerms = Arrays.asList(query.split("\\s+"));
- Collections.sort(
- queryTerms,
- new Comparator<String>() {
- @Override
- public int compare(String s1, String s2) {
- return Integer.compare(s2.length(), s1.length());
- }
- });
+ List<String> queryTerms =
+ Arrays.stream(query.split("\\s+")).sorted(comparing(String::length)).collect(toList());
List<String> result = new ArrayList<>();
for (String s : queryTerms) {
diff --git a/javatests/com/google/gerrit/acceptance/TestGroupBackendTest.java b/javatests/com/google/gerrit/acceptance/TestGroupBackendTest.java
index 3c7b966..bf387fd 100644
--- a/javatests/com/google/gerrit/acceptance/TestGroupBackendTest.java
+++ b/javatests/com/google/gerrit/acceptance/TestGroupBackendTest.java
@@ -39,7 +39,7 @@
@Test
public void universalGroupBackendHandlesTestGroup() throws Exception {
- RegistrationHandle registrationHandle = groupBackends.add(testGroupBackend);
+ RegistrationHandle registrationHandle = groupBackends.add("gerrit", testGroupBackend);
try {
assertThat(universalGroupBackend.handles(testUUID)).isTrue();
} finally {
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index c619e8c..de66b87 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -230,7 +230,7 @@
@Before
public void addAccountIndexEventCounter() {
accountIndexedCounter = new AccountIndexedCounter();
- accountIndexEventCounterHandle = accountIndexedListeners.add(accountIndexedCounter);
+ accountIndexEventCounterHandle = accountIndexedListeners.add("gerrit", accountIndexedCounter);
}
@After
@@ -243,7 +243,7 @@
@Before
public void addRefUpdateCounter() {
refUpdateCounter = new RefUpdateCounter();
- refUpdateCounterHandle = refUpdateListeners.add(refUpdateCounter);
+ refUpdateCounterHandle = refUpdateListeners.add("gerrit", refUpdateCounter);
}
@After
@@ -526,12 +526,13 @@
@Test
public void validateAccountActivation() throws Exception {
- com.google.gerrit.acceptance.testsuite.account.TestAccount activatableAccount =
+ Account.Id activatableAccountId =
accountOperations.newAccount().inactive().preferredEmail("foo@activatable.com").create();
- com.google.gerrit.acceptance.testsuite.account.TestAccount deactivatableAccount =
+ Account.Id deactivatableAccountId =
accountOperations.newAccount().preferredEmail("foo@deactivatable.com").create();
RegistrationHandle registrationHandle =
accountActivationValidationListeners.add(
+ "gerrit",
new AccountActivationValidationListener() {
@Override
public void validateActivation(AccountState account) throws ValidationException {
@@ -553,61 +554,56 @@
/* Test account that can be activated, but not deactivated */
// Deactivate account that is already inactive
try {
- gApi.accounts().id(activatableAccount.accountId().get()).setActive(false);
+ gApi.accounts().id(activatableAccountId.get()).setActive(false);
fail("Expected exception");
} catch (ResourceConflictException e) {
assertThat(e.getMessage()).isEqualTo("account not active");
}
- assertThat(accountOperations.account(activatableAccount.accountId()).get().active())
- .isFalse();
+ assertThat(accountOperations.account(activatableAccountId).get().active()).isFalse();
// Activate account that can be activated
- gApi.accounts().id(activatableAccount.accountId().get()).setActive(true);
- assertThat(accountOperations.account(activatableAccount.accountId()).get().active()).isTrue();
+ gApi.accounts().id(activatableAccountId.get()).setActive(true);
+ assertThat(accountOperations.account(activatableAccountId).get().active()).isTrue();
// Activate account that is already active
- gApi.accounts().id(activatableAccount.accountId().get()).setActive(true);
- assertThat(accountOperations.account(activatableAccount.accountId()).get().active()).isTrue();
+ gApi.accounts().id(activatableAccountId.get()).setActive(true);
+ assertThat(accountOperations.account(activatableAccountId).get().active()).isTrue();
// Try deactivating account that cannot be deactivated
try {
- gApi.accounts().id(activatableAccount.accountId().get()).setActive(false);
+ gApi.accounts().id(activatableAccountId.get()).setActive(false);
fail("Expected exception");
} catch (ResourceConflictException e) {
assertThat(e.getMessage()).isEqualTo("not allowed to deactive account");
}
- assertThat(accountOperations.account(activatableAccount.accountId()).get().active()).isTrue();
+ assertThat(accountOperations.account(activatableAccountId).get().active()).isTrue();
/* Test account that can be deactivated, but not activated */
// Activate account that is already inactive
- gApi.accounts().id(deactivatableAccount.accountId().get()).setActive(true);
- assertThat(accountOperations.account(deactivatableAccount.accountId()).get().active())
- .isTrue();
+ gApi.accounts().id(deactivatableAccountId.get()).setActive(true);
+ assertThat(accountOperations.account(deactivatableAccountId).get().active()).isTrue();
// Deactivate account that can be deactivated
- gApi.accounts().id(deactivatableAccount.accountId().get()).setActive(false);
- assertThat(accountOperations.account(deactivatableAccount.accountId()).get().active())
- .isFalse();
+ gApi.accounts().id(deactivatableAccountId.get()).setActive(false);
+ assertThat(accountOperations.account(deactivatableAccountId).get().active()).isFalse();
// Deactivate account that is already inactive
try {
- gApi.accounts().id(deactivatableAccount.accountId().get()).setActive(false);
+ gApi.accounts().id(deactivatableAccountId.get()).setActive(false);
fail("Expected exception");
} catch (ResourceConflictException e) {
assertThat(e.getMessage()).isEqualTo("account not active");
}
- assertThat(accountOperations.account(deactivatableAccount.accountId()).get().active())
- .isFalse();
+ assertThat(accountOperations.account(deactivatableAccountId).get().active()).isFalse();
// Try activating account that cannot be activated
try {
- gApi.accounts().id(deactivatableAccount.accountId().get()).setActive(true);
+ gApi.accounts().id(deactivatableAccountId.get()).setActive(true);
fail("Expected exception");
} catch (ResourceConflictException e) {
assertThat(e.getMessage()).isEqualTo("not allowed to active account");
}
- assertThat(accountOperations.account(deactivatableAccount.accountId()).get().active())
- .isFalse();
+ assertThat(accountOperations.account(deactivatableAccountId).get().active()).isFalse();
} finally {
registrationHandle.remove();
}
@@ -2168,6 +2164,38 @@
}
@Test
+ public void createUserWithValidUsername() throws Exception {
+ ImmutableList<String> names =
+ ImmutableList.of(
+ "user@domain",
+ "user-name",
+ "user_name",
+ "1234",
+ "user1234",
+ "1234@domain",
+ "user!+alias{*}#$%&’^=~|@domain");
+ for (String name : names) {
+ gApi.accounts().create(name);
+ }
+ }
+
+ @Test
+ public void createUserWithInvalidUsername() throws Exception {
+ ImmutableList<String> invalidNames =
+ ImmutableList.of(
+ "@", "@foo", "-", "-foo", "_", "_foo", "!", "+", "{", "}", "*", "%", "#", "$", "&", "’",
+ "^", "=", "~");
+ for (String name : invalidNames) {
+ try {
+ gApi.accounts().create(name);
+ fail(String.format("Expected BadRequestException for username [%s]", name));
+ } catch (BadRequestException e) {
+ assertThat(e).hasMessageThat().isEqualTo(String.format("Invalid username '%s'", name));
+ }
+ }
+ }
+
+ @Test
public void allGroupsForAUserAccountCanBeRetrieved() throws Exception {
String username = name("user1");
accountOperations.newAccount().username(username).create();
@@ -2508,7 +2536,7 @@
// Manually inserting/updating/deleting an external ID of the user makes the index document
// stale.
try (Repository repo = repoManager.openRepository(allUsers)) {
- ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(repo);
+ ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsers, repo);
ExternalId.Key key = ExternalId.Key.create("foo", "foo");
extIdNotes.insert(ExternalId.create(key, accountId));
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIndexerIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIndexerIT.java
index ef8451d..60a61d1 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIndexerIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIndexerIT.java
@@ -162,7 +162,7 @@
md.getCommitBuilder().setAuthor(ident);
md.getCommitBuilder().setCommitter(ident);
- AccountConfig accountConfig = new AccountConfig(accountId, allUsersRepo).load();
+ AccountConfig accountConfig = new AccountConfig(accountId, allUsersName, allUsersRepo).load();
accountConfig.setAccountUpdate(accountUpdate);
accountConfig.commit(md);
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 0a42b1e..b3a5e2d 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -68,7 +68,6 @@
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
-import com.google.gerrit.acceptance.testsuite.account.TestAccount;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.LabelFunction;
@@ -203,7 +202,7 @@
@Before
public void addChangeIndexedCounter() {
changeIndexedCounter = new ChangeIndexedCounter();
- changeIndexedCounterHandle = changeIndexedListeners.add(changeIndexedCounter);
+ changeIndexedCounterHandle = changeIndexedListeners.add("gerrit", changeIndexedCounter);
}
@After
@@ -214,16 +213,6 @@
}
@Test
- public void reflog() throws Exception {
- // Tests are using DfsRepository which does not implement getReflogReader,
- // so this will always fail.
- // TODO: change this if/when DfsRepository#getReflogReader is implemented.
- exception.expect(MethodNotAllowedException.class);
- exception.expectMessage("reflog not supported");
- gApi.projects().name(project.get()).branch("master").reflog();
- }
-
- @Test
public void get() throws Exception {
PushOneCommit.Result r = createChange();
String triplet = project.get() + "~master~" + r.getChangeId();
@@ -1698,7 +1687,7 @@
// create a group named "ab" with one user: testUser
String email = "abcd@test.com";
String fullname = "abcd";
- TestAccount testUser =
+ Account.Id accountIdOfTestUser =
accountOperations
.newAccount()
.username("abcd")
@@ -1731,7 +1720,7 @@
Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER);
assertThat(reviewers).isNotNull();
assertThat(reviewers).hasSize(1);
- assertThat(reviewers.iterator().next()._accountId).isEqualTo(testUser.accountId().get());
+ assertThat(reviewers.iterator().next()._accountId).isEqualTo(accountIdOfTestUser.get());
// Ensure ETag and lastUpdatedOn are updated.
rsrc = parseResource(r);
@@ -1758,7 +1747,7 @@
String myGroupUserEmail = "lee@test.com";
String myGroupUserFullname = "lee";
- TestAccount myGroupUser =
+ Account.Id accountIdOfGroupUser =
accountOperations
.newAccount()
.username("lee")
@@ -1795,7 +1784,7 @@
Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER);
assertThat(reviewers).isNotNull();
assertThat(reviewers).hasSize(1);
- assertThat(reviewers.iterator().next()._accountId).isEqualTo(myGroupUser.accountId().get());
+ assertThat(reviewers.iterator().next()._accountId).isEqualTo(accountIdOfGroupUser.get());
// Ensure ETag and lastUpdatedOn are updated.
rsrc = parseResource(r);
@@ -2225,13 +2214,12 @@
// notify unrelated account as TO
String email = "user2@example.com";
- TestAccount user2 =
- accountOperations
- .newAccount()
- .username("user2")
- .preferredEmail(email)
- .fullname("User2")
- .create();
+ accountOperations
+ .newAccount()
+ .username("user2")
+ .preferredEmail(email)
+ .fullname("User2")
+ .create();
setApiUser(user);
recommend(r.getChangeId());
setApiUser(admin);
@@ -2239,7 +2227,7 @@
in.notifyDetails = new HashMap<>();
in.notifyDetails.put(RecipientType.TO, new NotifyInfo(ImmutableList.of(email)));
gApi.changes().id(r.getChangeId()).reviewer(user.getId().toString()).deleteVote(in);
- assertNotifyTo(user2);
+ assertNotifyTo(email, "User2");
// notify unrelated account as CC
setApiUser(user);
@@ -2249,7 +2237,7 @@
in.notifyDetails = new HashMap<>();
in.notifyDetails.put(RecipientType.CC, new NotifyInfo(ImmutableList.of(email)));
gApi.changes().id(r.getChangeId()).reviewer(user.getId().toString()).deleteVote(in);
- assertNotifyCc(user2);
+ assertNotifyCc(email, "User2");
// notify unrelated account as BCC
setApiUser(user);
@@ -2259,7 +2247,7 @@
in.notifyDetails = new HashMap<>();
in.notifyDetails.put(RecipientType.BCC, new NotifyInfo(ImmutableList.of(email)));
gApi.changes().id(r.getChangeId()).reviewer(user.getId().toString()).deleteVote(in);
- assertNotifyBcc(user2);
+ assertNotifyBcc(email, "User2");
}
@Test
@@ -2609,6 +2597,7 @@
PushOneCommit.Result change = createChange();
RegistrationHandle handle =
changeMessageModifiers.add(
+ "gerrit",
new ChangeMessageModifier() {
@Override
public String onSubmit(
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
index 4e3f048..d6be960 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -183,24 +183,23 @@
@Test
public void cachedGroupsForMemberAreUpdatedOnMemberAdditionAndRemoval() throws Exception {
String username = name("user");
- com.google.gerrit.acceptance.testsuite.account.TestAccount account =
- accountOperations.newAccount().username(username).create();
+ Account.Id accountId = accountOperations.newAccount().username(username).create();
// Fill the cache for the observed account.
- groupIncludeCache.getGroupsWithMember(account.accountId());
+ groupIncludeCache.getGroupsWithMember(accountId);
String groupName = createGroup("users");
AccountGroup.UUID groupUuid = new AccountGroup.UUID(gApi.groups().id(groupName).get().id);
gApi.groups().id(groupName).addMembers(username);
Collection<AccountGroup.UUID> groupsWithMemberAfterAddition =
- groupIncludeCache.getGroupsWithMember(account.accountId());
+ groupIncludeCache.getGroupsWithMember(accountId);
assertThat(groupsWithMemberAfterAddition).contains(groupUuid);
gApi.groups().id(groupName).removeMembers(username);
Collection<AccountGroup.UUID> groupsWithMemberAfterRemoval =
- groupIncludeCache.getGroupsWithMember(account.accountId());
+ groupIncludeCache.getGroupsWithMember(accountId);
assertThat(groupsWithMemberAfterRemoval).doesNotContain(groupUuid);
}
@@ -411,19 +410,17 @@
@Test
public void cachedGroupsForMemberAreUpdatedOnGroupCreation() throws Exception {
- com.google.gerrit.acceptance.testsuite.account.TestAccount account =
- accountOperations.newAccount().create();
+ Account.Id accountId = accountOperations.newAccount().create();
// Fill the cache for the observed account.
- groupIncludeCache.getGroupsWithMember(account.accountId());
+ groupIncludeCache.getGroupsWithMember(accountId);
GroupInput groupInput = new GroupInput();
groupInput.name = name("Users");
- groupInput.members = ImmutableList.of(String.valueOf(account.accountId().get()));
+ groupInput.members = ImmutableList.of(String.valueOf(accountId.get()));
GroupInfo group = gApi.groups().create(groupInput).get();
- Collection<AccountGroup.UUID> groups =
- groupIncludeCache.getGroupsWithMember(account.accountId());
+ Collection<AccountGroup.UUID> groups = groupIncludeCache.getGroupsWithMember(accountId);
assertThat(groups).containsExactly(new AccountGroup.UUID(group.id));
}
@@ -1298,7 +1295,7 @@
GroupIndexedCounter groupIndexedCounter = new GroupIndexedCounter();
RegistrationHandle groupIndexEventCounterHandle =
- groupIndexedListeners.add(groupIndexedCounter);
+ groupIndexedListeners.add("gerrit", groupIndexedCounter);
try {
// Running the reindexer right after startup should not need to reindex any group since
// reindexing was already done on startup.
@@ -1355,7 +1352,7 @@
GroupIndexedCounter groupIndexedCounter = new GroupIndexedCounter();
RegistrationHandle groupIndexEventCounterHandle =
- groupIndexedListeners.add(groupIndexedCounter);
+ groupIndexedListeners.add("gerrit", groupIndexedCounter);
try {
// No group indexing happened on startup. All groups should be reindexed now.
slaveGroupIndexer.run();
diff --git a/javatests/com/google/gerrit/acceptance/api/project/CheckAccessIT.java b/javatests/com/google/gerrit/acceptance/api/project/CheckAccessIT.java
index 2b1416a..e4194a3 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/CheckAccessIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/CheckAccessIT.java
@@ -20,16 +20,18 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.config.AccessCheckInfo;
import com.google.gerrit.extensions.api.config.AccessCheckInput;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.inject.Inject;
import java.util.List;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
@@ -39,35 +41,29 @@
public class CheckAccessIT extends AbstractDaemonTest {
+ @Inject private GroupOperations groupOperations;
+
private Project.NameKey normalProject;
private Project.NameKey secretProject;
private Project.NameKey secretRefProject;
private TestAccount privilegedUser;
- private InternalGroup privilegedGroup;
@Before
public void setUp() throws Exception {
normalProject = createProject("normal");
secretProject = createProject("secret");
secretRefProject = createProject("secretRef");
- privilegedGroup = group(createGroup("privilegedGroup"));
+ AccountGroup.UUID privilegedGroupUuid =
+ groupOperations.newGroup().name(name("privilegedGroup")).create();
privilegedUser = accountCreator.create("privilegedUser", "snowden@nsa.gov", "Ed Snowden");
- gApi.groups().id(privilegedGroup.getGroupUUID().get()).addMembers(privilegedUser.username);
+ groupOperations.group(privilegedGroupUuid).forUpdate().addMember(privilegedUser.id).update();
- assertThat(gApi.groups().id(privilegedGroup.getGroupUUID().get()).members().get(0).email)
- .contains("snowden");
-
- grant(secretProject, "refs/*", Permission.READ, false, privilegedGroup.getGroupUUID());
+ grant(secretProject, "refs/*", Permission.READ, false, privilegedGroupUuid);
block(secretProject, "refs/*", Permission.READ, SystemGroupBackend.REGISTERED_USERS);
deny(secretRefProject, "refs/*", Permission.READ, SystemGroupBackend.ANONYMOUS_USERS);
- grant(
- secretRefProject,
- "refs/heads/secret/*",
- Permission.READ,
- false,
- privilegedGroup.getGroupUUID());
+ grant(secretRefProject, "refs/heads/secret/*", Permission.READ, false, privilegedGroupUuid);
block(
secretRefProject,
"refs/heads/secret/*",
@@ -81,13 +77,8 @@
SystemGroupBackend.REGISTERED_USERS);
// Ref permission
- grant(
- normalProject,
- "refs/*",
- Permission.VIEW_PRIVATE_CHANGES,
- false,
- privilegedGroup.getGroupUUID());
- grant(normalProject, "refs/*", Permission.FORGE_SERVER, false, privilegedGroup.getGroupUUID());
+ grant(normalProject, "refs/*", Permission.VIEW_PRIVATE_CHANGES, false, privilegedGroupUuid);
+ grant(normalProject, "refs/*", Permission.FORGE_SERVER, false, privilegedGroupUuid);
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/api/project/CheckProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/CheckProjectIT.java
new file mode 100644
index 0000000..6c6ad3d
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/api/project/CheckProjectIT.java
@@ -0,0 +1,314 @@
+// Copyright (C) 2018 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.api.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+
+import com.google.common.collect.Iterables;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.extensions.api.projects.CheckProjectInput;
+import com.google.gerrit.extensions.api.projects.CheckProjectInput.AutoCloseableChangesCheckInput;
+import com.google.gerrit.extensions.api.projects.CheckProjectResultInfo;
+import com.google.gerrit.extensions.client.ChangeStatus;
+import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.server.project.ProjectsConsistencyChecker;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CheckProjectIT extends AbstractDaemonTest {
+ private TestRepository<InMemoryRepository> serverSideTestRepo;
+
+ @Before
+ public void setUp() throws Exception {
+ serverSideTestRepo =
+ new TestRepository<>((InMemoryRepository) repoManager.openRepository(project));
+ }
+
+ @Test
+ public void noProblem() throws Exception {
+ PushOneCommit.Result r = createChange("refs/for/master");
+ String branch = r.getChange().change().getDest().get();
+
+ ChangeInfo info = gApi.changes().id(r.getChange().getId().get()).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+
+ CheckProjectResultInfo checkResult =
+ gApi.projects().name(project.get()).check(checkProjectInputForAutoCloseableCheck(branch));
+ assertThat(checkResult.autoCloseableChangesCheckResult.autoCloseableChanges).isEmpty();
+
+ info = gApi.changes().id(r.getChange().getId().get()).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+ }
+
+ @Test
+ public void detectAutoCloseableChangeByCommit() throws Exception {
+ RevCommit commit = pushCommitWithoutChangeIdForReview();
+ ChangeInfo change =
+ Iterables.getOnlyElement(gApi.changes().query("commit:" + commit.name()).get());
+
+ String branch = "refs/heads/master";
+ serverSideTestRepo.branch(branch).update(testRepo.getRevWalk().parseCommit(commit));
+
+ ChangeInfo info = gApi.changes().id(change._number).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+
+ CheckProjectResultInfo checkResult =
+ gApi.projects().name(project.get()).check(checkProjectInputForAutoCloseableCheck(branch));
+ assertThat(
+ checkResult
+ .autoCloseableChangesCheckResult
+ .autoCloseableChanges
+ .stream()
+ .map(i -> i._number)
+ .collect(toList()))
+ .containsExactly(change._number);
+
+ info = gApi.changes().id(change._number).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+ }
+
+ @Test
+ public void fixAutoCloseableChangeByCommit() throws Exception {
+ RevCommit commit = pushCommitWithoutChangeIdForReview();
+ ChangeInfo change =
+ Iterables.getOnlyElement(gApi.changes().query("commit:" + commit.name()).get());
+
+ String branch = "refs/heads/master";
+ serverSideTestRepo.branch(branch).update(commit);
+
+ ChangeInfo info = gApi.changes().id(change._number).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+
+ CheckProjectInput input = checkProjectInputForAutoCloseableCheck(branch);
+ input.autoCloseableChangesCheck.fix = true;
+ CheckProjectResultInfo checkResult = gApi.projects().name(project.get()).check(input);
+ assertThat(
+ checkResult
+ .autoCloseableChangesCheckResult
+ .autoCloseableChanges
+ .stream()
+ .map(i -> i._number)
+ .collect(toSet()))
+ .containsExactly(change._number);
+
+ info = gApi.changes().id(change._number).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
+ }
+
+ @Test
+ public void detectAutoCloseableChangeByChangeId() throws Exception {
+ PushOneCommit.Result r = createChange("refs/for/master");
+ String branch = r.getChange().change().getDest().get();
+
+ RevCommit amendedCommit = serverSideTestRepo.amend(r.getCommit()).create();
+ serverSideTestRepo.branch(branch).update(amendedCommit);
+
+ ChangeInfo info = gApi.changes().id(r.getChange().getId().get()).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+
+ CheckProjectResultInfo checkResult =
+ gApi.projects().name(project.get()).check(checkProjectInputForAutoCloseableCheck(branch));
+ assertThat(
+ checkResult
+ .autoCloseableChangesCheckResult
+ .autoCloseableChanges
+ .stream()
+ .map(i -> i._number)
+ .collect(toSet()))
+ .containsExactly(r.getChange().getId().get());
+
+ info = gApi.changes().id(r.getChange().getId().get()).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+ }
+
+ @Test
+ public void fixAutoCloseableChangeByChangeId() throws Exception {
+ PushOneCommit.Result r = createChange("refs/for/master");
+ String branch = r.getChange().change().getDest().get();
+
+ RevCommit amendedCommit = serverSideTestRepo.amend(r.getCommit()).create();
+ serverSideTestRepo.branch(branch).update(amendedCommit);
+
+ ChangeInfo info = gApi.changes().id(r.getChange().getId().get()).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+
+ CheckProjectInput input = checkProjectInputForAutoCloseableCheck(branch);
+ input.autoCloseableChangesCheck.fix = true;
+ CheckProjectResultInfo checkResult = gApi.projects().name(project.get()).check(input);
+ assertThat(
+ checkResult
+ .autoCloseableChangesCheckResult
+ .autoCloseableChanges
+ .stream()
+ .map(i -> i._number)
+ .collect(toSet()))
+ .containsExactly(r.getChange().getId().get());
+
+ info = gApi.changes().id(r.getChange().getId().get()).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
+ }
+
+ @Test
+ public void maxCommits() throws Exception {
+ PushOneCommit.Result r = createChange("refs/for/master");
+ String branch = r.getChange().change().getDest().get();
+
+ RevCommit amendedCommit = serverSideTestRepo.amend(r.getCommit()).create();
+ serverSideTestRepo.branch(branch).update(amendedCommit);
+
+ serverSideTestRepo.commit(amendedCommit);
+
+ ChangeInfo info = gApi.changes().id(r.getChange().getId().get()).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+
+ CheckProjectInput input = checkProjectInputForAutoCloseableCheck(branch);
+ input.autoCloseableChangesCheck.fix = true;
+ input.autoCloseableChangesCheck.maxCommits = 1;
+ CheckProjectResultInfo checkResult = gApi.projects().name(project.get()).check(input);
+ assertThat(checkResult.autoCloseableChangesCheckResult.autoCloseableChanges).isEmpty();
+
+ info = gApi.changes().id(r.getChange().getId().get()).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+
+ input.autoCloseableChangesCheck.maxCommits = 2;
+ checkResult = gApi.projects().name(project.get()).check(input);
+ assertThat(
+ checkResult
+ .autoCloseableChangesCheckResult
+ .autoCloseableChanges
+ .stream()
+ .map(i -> i._number)
+ .collect(toSet()))
+ .containsExactly(r.getChange().getId().get());
+
+ info = gApi.changes().id(r.getChange().getId().get()).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
+ }
+
+ @Test
+ public void skipCommits() throws Exception {
+ PushOneCommit.Result r = createChange("refs/for/master");
+ String branch = r.getChange().change().getDest().get();
+
+ RevCommit amendedCommit = serverSideTestRepo.amend(r.getCommit()).create();
+ serverSideTestRepo.branch(branch).update(amendedCommit);
+
+ serverSideTestRepo.commit(amendedCommit);
+
+ ChangeInfo info = gApi.changes().id(r.getChange().getId().get()).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+
+ CheckProjectInput input = checkProjectInputForAutoCloseableCheck(branch);
+ input.autoCloseableChangesCheck.fix = true;
+ input.autoCloseableChangesCheck.maxCommits = 1;
+ CheckProjectResultInfo checkResult = gApi.projects().name(project.get()).check(input);
+ assertThat(checkResult.autoCloseableChangesCheckResult.autoCloseableChanges).isEmpty();
+
+ info = gApi.changes().id(r.getChange().getId().get()).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+
+ input.autoCloseableChangesCheck.skipCommits = 1;
+ checkResult = gApi.projects().name(project.get()).check(input);
+ assertThat(
+ checkResult
+ .autoCloseableChangesCheckResult
+ .autoCloseableChanges
+ .stream()
+ .map(i -> i._number)
+ .collect(toSet()))
+ .containsExactly(r.getChange().getId().get());
+
+ info = gApi.changes().id(r.getChange().getId().get()).info();
+ assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
+ }
+
+ @Test
+ public void noBranch() throws Exception {
+ CheckProjectInput input = new CheckProjectInput();
+ input.autoCloseableChangesCheck = new AutoCloseableChangesCheckInput();
+
+ exception.expect(BadRequestException.class);
+ exception.expectMessage("branch is required");
+ gApi.projects().name(project.get()).check(input);
+ }
+
+ @Test
+ public void nonExistingBranch() throws Exception {
+ CheckProjectInput input = checkProjectInputForAutoCloseableCheck("non-existing");
+
+ exception.expect(UnprocessableEntityException.class);
+ exception.expectMessage("branch 'non-existing' not found");
+ gApi.projects().name(project.get()).check(input);
+ }
+
+ @Test
+ public void branchPrefixCanBeOmitted() throws Exception {
+ CheckProjectInput input = checkProjectInputForAutoCloseableCheck("master");
+ gApi.projects().name(project.get()).check(input);
+ }
+
+ @Test
+ public void setLimitForMaxCommits() throws Exception {
+ CheckProjectInput input = checkProjectInputForAutoCloseableCheck("refs/heads/master");
+ input.autoCloseableChangesCheck.maxCommits =
+ ProjectsConsistencyChecker.AUTO_CLOSE_MAX_COMMITS_LIMIT;
+ gApi.projects().name(project.get()).check(input);
+ }
+
+ @Test
+ public void tooLargeMaxCommits() throws Exception {
+ CheckProjectInput input = checkProjectInputForAutoCloseableCheck("refs/heads/master");
+ input.autoCloseableChangesCheck.maxCommits =
+ ProjectsConsistencyChecker.AUTO_CLOSE_MAX_COMMITS_LIMIT + 1;
+
+ exception.expect(BadRequestException.class);
+ exception.expectMessage(
+ "max commits can at most be set to "
+ + ProjectsConsistencyChecker.AUTO_CLOSE_MAX_COMMITS_LIMIT);
+ gApi.projects().name(project.get()).check(input);
+ }
+
+ private RevCommit pushCommitWithoutChangeIdForReview() throws Exception {
+ setRequireChangeId(InheritableBoolean.FALSE);
+ RevCommit commit =
+ testRepo
+ .branch("HEAD")
+ .commit()
+ .message("A change")
+ .author(admin.getIdent())
+ .committer(new PersonIdent(admin.getIdent(), testRepo.getDate()))
+ .create();
+ pushHead(testRepo, "refs/for/master");
+ return commit;
+ }
+
+ private static CheckProjectInput checkProjectInputForAutoCloseableCheck(String branch) {
+ CheckProjectInput input = new CheckProjectInput();
+ input.autoCloseableChangesCheck = new AutoCloseableChangesCheckInput();
+ input.autoCloseableChangesCheck.branch = branch;
+ return input;
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
index 8479dd1..18888ea 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -16,6 +16,10 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
+import static com.google.gerrit.server.project.ProjectState.INHERITED_FROM_GLOBAL;
+import static com.google.gerrit.server.project.ProjectState.INHERITED_FROM_PARENT;
+import static com.google.gerrit.server.project.ProjectState.OVERRIDDEN_BY_GLOBAL;
+import static com.google.gerrit.server.project.ProjectState.OVERRIDDEN_BY_PARENT;
import static java.util.stream.Collectors.toSet;
import com.google.common.collect.ImmutableList;
@@ -23,6 +27,7 @@
import com.google.common.util.concurrent.AtomicLongMap;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
@@ -67,7 +72,7 @@
@Before
public void addProjectIndexedCounter() {
projectIndexedCounter = new ProjectIndexedCounter();
- projectIndexedCounterHandle = projectIndexedListeners.add(projectIndexedCounter);
+ projectIndexedCounterHandle = projectIndexedListeners.add("gerrit", projectIndexedCounter);
}
@After
@@ -368,7 +373,7 @@
gApi.projects().name(project.get()).branch("test").create(new BranchInput());
setApiUser(user);
exception.expect(AuthException.class);
- exception.expectMessage("set HEAD not permitted for refs/heads/test");
+ exception.expectMessage("not permitted: set HEAD on refs/heads/test");
gApi.projects().name(project.get()).head("test");
}
@@ -410,6 +415,185 @@
ImmutableMap.of(project.get(), 1L, middle.get(), 1L, leave.get(), 1L));
}
+ @Test
+ public void maxObjectSizeIsNotSetByDefault() throws Exception {
+ ConfigInfo info = getConfig();
+ assertThat(info.maxObjectSizeLimit.value).isNull();
+ assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
+ assertThat(info.maxObjectSizeLimit.summary).isNull();
+ }
+
+ @Test
+ public void maxObjectSizeCanBeSetAndCleared() throws Exception {
+ // Set a value
+ ConfigInfo info = setMaxObjectSize("100k");
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
+ assertThat(info.maxObjectSizeLimit.summary).isNull();
+
+ // Clear the value
+ info = setMaxObjectSize("0");
+ assertThat(info.maxObjectSizeLimit.value).isNull();
+ assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
+ assertThat(info.maxObjectSizeLimit.summary).isNull();
+ }
+
+ @Test
+ @GerritConfig(name = "receive.inheritProjectMaxObjectSizeLimit", value = "true")
+ public void maxObjectSizeIsInheritedFromParentProject() throws Exception {
+ Project.NameKey child = createProject(name("child"), project);
+
+ ConfigInfo info = setMaxObjectSize("100k");
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
+ assertThat(info.maxObjectSizeLimit.summary).isNull();
+
+ info = getConfig(child);
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
+ assertThat(info.maxObjectSizeLimit.summary)
+ .isEqualTo(String.format(INHERITED_FROM_PARENT, project));
+ }
+
+ @Test
+ public void maxObjectSizeIsNotInheritedFromParentProject() throws Exception {
+ Project.NameKey child = createProject(name("child"), project);
+
+ ConfigInfo info = setMaxObjectSize("100k");
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
+ assertThat(info.maxObjectSizeLimit.summary).isNull();
+
+ info = getConfig(child);
+ assertThat(info.maxObjectSizeLimit.value).isNull();
+ assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
+ assertThat(info.maxObjectSizeLimit.summary).isNull();
+ }
+
+ @Test
+ public void maxObjectSizeOverridesParentProjectWhenNotSetOnParent() throws Exception {
+ Project.NameKey child = createProject(name("child"), project);
+
+ ConfigInfo info = setMaxObjectSize("0");
+ assertThat(info.maxObjectSizeLimit.value).isNull();
+ assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
+ assertThat(info.maxObjectSizeLimit.summary).isNull();
+
+ info = setMaxObjectSize(child, "100k");
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
+ assertThat(info.maxObjectSizeLimit.summary).isNull();
+ }
+
+ @Test
+ public void maxObjectSizeOverridesParentProjectWhenLower() throws Exception {
+ Project.NameKey child = createProject(name("child"), project);
+
+ ConfigInfo info = setMaxObjectSize("200k");
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("200k");
+ assertThat(info.maxObjectSizeLimit.summary).isNull();
+
+ info = setMaxObjectSize(child, "100k");
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
+ assertThat(info.maxObjectSizeLimit.summary).isNull();
+ }
+
+ @Test
+ @GerritConfig(name = "receive.inheritProjectMaxObjectSizeLimit", value = "true")
+ public void maxObjectSizeDoesNotOverrideParentProjectWhenHigher() throws Exception {
+ Project.NameKey child = createProject(name("child"), project);
+
+ ConfigInfo info = setMaxObjectSize("100k");
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
+ assertThat(info.maxObjectSizeLimit.summary).isNull();
+
+ info = setMaxObjectSize(child, "200k");
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("200k");
+ assertThat(info.maxObjectSizeLimit.summary)
+ .isEqualTo(String.format(OVERRIDDEN_BY_PARENT, project));
+ }
+
+ @Test
+ @GerritConfig(name = "receive.maxObjectSizeLimit", value = "200k")
+ public void maxObjectSizeIsInheritedFromGlobalConfig() throws Exception {
+ Project.NameKey child = createProject(name("child"), project);
+
+ ConfigInfo info = getConfig();
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
+ assertThat(info.maxObjectSizeLimit.summary).isEqualTo(INHERITED_FROM_GLOBAL);
+
+ info = getConfig(child);
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
+ assertThat(info.maxObjectSizeLimit.summary).isEqualTo(INHERITED_FROM_GLOBAL);
+ }
+
+ @Test
+ @GerritConfig(name = "receive.maxObjectSizeLimit", value = "200k")
+ public void maxObjectSizeOverridesGlobalConfigWhenLower() throws Exception {
+ ConfigInfo info = setMaxObjectSize("100k");
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
+ assertThat(info.maxObjectSizeLimit.summary).isNull();
+ }
+
+ @Test
+ @GerritConfig(name = "receive.maxObjectSizeLimit", value = "300k")
+ public void inheritedMaxObjectSizeOverridesGlobalConfigWhenLower() throws Exception {
+ Project.NameKey child = createProject(name("child"), project);
+
+ ConfigInfo info = setMaxObjectSize("200k");
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("200k");
+ assertThat(info.maxObjectSizeLimit.summary).isNull();
+
+ info = setMaxObjectSize(child, "100k");
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
+ assertThat(info.maxObjectSizeLimit.summary).isNull();
+ }
+
+ @Test
+ @GerritConfig(name = "receive.maxObjectSizeLimit", value = "200k")
+ @GerritConfig(name = "receive.inheritProjectMaxObjectSizeLimit", value = "true")
+ public void maxObjectSizeDoesNotOverrideGlobalConfigWhenHigher() throws Exception {
+ Project.NameKey child = createProject(name("child"), project);
+
+ ConfigInfo info = setMaxObjectSize("300k");
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("300k");
+ assertThat(info.maxObjectSizeLimit.summary).isEqualTo(OVERRIDDEN_BY_GLOBAL);
+
+ info = getConfig(child);
+ assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
+ assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
+ assertThat(info.maxObjectSizeLimit.summary).isEqualTo(OVERRIDDEN_BY_GLOBAL);
+ }
+
+ @Test
+ public void invalidMaxObjectSizeIsRejected() throws Exception {
+ exception.expect(ResourceConflictException.class);
+ exception.expectMessage("100 foo");
+ setMaxObjectSize("100 foo");
+ }
+
+ private ConfigInfo setConfig(Project.NameKey name, ConfigInput input) throws Exception {
+ return gApi.projects().name(name.get()).config(input);
+ }
+
+ private ConfigInfo getConfig(Project.NameKey name) throws Exception {
+ return gApi.projects().name(name.get()).config();
+ }
+
+ private ConfigInfo getConfig() throws Exception {
+ return getConfig(project);
+ }
+
private ConfigInput createTestConfigInput() {
ConfigInput input = new ConfigInput();
input.description = "some description";
@@ -427,6 +611,16 @@
return input;
}
+ private ConfigInfo setMaxObjectSize(String value) throws Exception {
+ return setMaxObjectSize(project, value);
+ }
+
+ private ConfigInfo setMaxObjectSize(Project.NameKey name, String value) throws Exception {
+ ConfigInput input = new ConfigInput();
+ input.maxObjectSizeLimit = value;
+ return setConfig(name, input);
+ }
+
private static class ProjectIndexedCounter implements ProjectIndexedListener {
private final AtomicLongMap<String> countsByProject = AtomicLongMap.create();
diff --git a/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java b/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java
index ec4f327..3295f1a 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java
@@ -17,13 +17,16 @@
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.group.SystemGroupBackend;
import org.junit.Test;
@NoHttpd
@@ -38,6 +41,40 @@
}
@Test
+ @GerritConfig(name = "receive.allowProjectOwnersToChangeParent", value = "true")
+ public void setParentNotAllowedForNonOwners() throws Exception {
+ String parent = createProject("parent", null, true).get();
+ setApiUser(user);
+ exception.expect(AuthException.class);
+ gApi.projects().name(project.get()).parent(parent);
+ }
+
+ @Test
+ @GerritConfig(name = "receive.allowProjectOwnersToChangeParent", value = "true")
+ public void setParentAllowedByAdminWhenAllowProjectOwnersEnabled() throws Exception {
+ String parent = createProject("parent", null, true).get();
+
+ gApi.projects().name(project.get()).parent(parent);
+ assertThat(gApi.projects().name(project.get()).parent()).isEqualTo(parent);
+
+ // When the parent name is not explicitly set, it should be
+ // set to "All-Projects".
+ gApi.projects().name(project.get()).parent(null);
+ assertThat(gApi.projects().name(project.get()).parent())
+ .isEqualTo(AllProjectsNameProvider.DEFAULT);
+ }
+
+ @Test
+ @GerritConfig(name = "receive.allowProjectOwnersToChangeParent", value = "true")
+ public void setParentAllowedForOwners() throws Exception {
+ String parent = createProject("parent", null, true).get();
+ setApiUser(user);
+ grant(project, "refs/*", Permission.OWNER, false, SystemGroupBackend.REGISTERED_USERS);
+ gApi.projects().name(project.get()).parent(parent);
+ assertThat(gApi.projects().name(project.get()).parent()).isEqualTo(parent);
+ }
+
+ @Test
public void setParent() throws Exception {
String parent = createProject("parent", null, true).get();
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
index 53cc5ad..057f837 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
@@ -32,6 +32,9 @@
import com.google.gerrit.common.RawInputUtil;
import com.google.gerrit.extensions.api.changes.FileApi;
import com.google.gerrit.extensions.api.changes.RebaseInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
+import com.google.gerrit.extensions.client.Comment;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.common.ChangeType;
import com.google.gerrit.extensions.common.DiffInfo;
@@ -2343,6 +2346,126 @@
assertThat(changedFiles.get(FILE_NAME)).linesDeleted().isEqualTo(2);
}
+ @Test
+ public void diffOfUnmodifiedFileWithWholeFileContextReturnsFileContents() throws Exception {
+ addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
+ String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
+ addModifiedPatchSet(
+ changeId, FILE_NAME2, content -> content.replace("2nd line\n", "Second line\n"));
+
+ DiffInfo diffInfo =
+ getDiffRequest(changeId, CURRENT, FILE_NAME)
+ .withBase(previousPatchSetId)
+ .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
+ .get();
+ // We don't list the full file contents here as that is not the focus of this test.
+ assertThat(diffInfo)
+ .content()
+ .element(0)
+ .commonLines()
+ .containsAllOf("Line 1", "Line two", "Line 3", "Line 4", "Line 5")
+ .inOrder();
+ }
+
+ @Test
+ public void diffOfUnmodifiedFileWithCommentAndWholeFileContextReturnsFileContents()
+ throws Exception {
+ addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
+ String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
+ CommentInput comment = createCommentInput(2, 0, 3, 0, "Should be 'Line 2'.");
+ ReviewInput reviewInput = new ReviewInput();
+ reviewInput.comments = ImmutableMap.of(FILE_NAME, ImmutableList.of(comment));
+ gApi.changes().id(changeId).revision(previousPatchSetId).review(reviewInput);
+ addModifiedPatchSet(
+ changeId, FILE_NAME2, content -> content.replace("2nd line\n", "Second line\n"));
+
+ DiffInfo diffInfo =
+ getDiffRequest(changeId, CURRENT, FILE_NAME)
+ .withBase(previousPatchSetId)
+ .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
+ .get();
+ // We don't list the full file contents here as that is not the focus of this test.
+ assertThat(diffInfo)
+ .content()
+ .element(0)
+ .commonLines()
+ .containsAllOf("Line 1", "Line two", "Line 3", "Line 4", "Line 5")
+ .inOrder();
+ }
+
+ @Test
+ public void diffOfNonExistentFileIsAnEmptyDiffResult() throws Exception {
+ addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
+
+ DiffInfo diffInfo =
+ getDiffRequest(changeId, CURRENT, "a_non-existent_file.txt")
+ .withBase(initialPatchSetId)
+ .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
+ .get();
+ assertThat(diffInfo).content().isEmpty();
+ }
+
+ @Test
+ public void requestingDiffForOldFileNameOfRenamedFileYieldsReasonableResult() throws Exception {
+ addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
+ String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
+ String newFilePath = "a_new_file.txt";
+ gApi.changes().id(changeId).edit().renameFile(FILE_NAME, newFilePath);
+ gApi.changes().id(changeId).edit().publish();
+
+ DiffInfo diffInfo =
+ getDiffRequest(changeId, CURRENT, FILE_NAME)
+ .withBase(previousPatchSetId)
+ .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
+ .get();
+ // This behavior has been present in Gerrit for quite some time. It differs from the results
+ // returned for other cases (e.g. requesting the diff with whole file context for an unmodified
+ // file; requesting the diff with whole file context for a non-existent file). However, it's not
+ // completely clear what should be returned. The closest would be the result of a file deletion
+ // but that might also be misleading for users as actually a file rename occurred. In fact,
+ // requesting the diff result for the old file name of a renamed file is not a reasonable use
+ // case at all. We at least guarantee that we don't run into an internal error.
+ assertThat(diffInfo).content().element(0).commonLines().isNull();
+ assertThat(diffInfo).content().element(0).numberOfSkippedLines().isGreaterThan(0);
+ }
+
+ @Test
+ public void requestingDiffForOldFileNameOfRenamedFileWithCommentOnOldFileYieldsReasonableResult()
+ throws Exception {
+ addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
+ String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
+ CommentInput comment = createCommentInput(2, 0, 3, 0, "Should be 'Line 2'.");
+ ReviewInput reviewInput = new ReviewInput();
+ reviewInput.comments = ImmutableMap.of(FILE_NAME, ImmutableList.of(comment));
+ gApi.changes().id(changeId).revision(previousPatchSetId).review(reviewInput);
+ String newFilePath = "a_new_file.txt";
+ gApi.changes().id(changeId).edit().renameFile(FILE_NAME, newFilePath);
+ gApi.changes().id(changeId).edit().publish();
+
+ DiffInfo diffInfo =
+ getDiffRequest(changeId, CURRENT, FILE_NAME)
+ .withBase(previousPatchSetId)
+ .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
+ .get();
+ // See comment for requestingDiffForOldFileNameOfRenamedFileYieldsReasonableResult().
+ // This test should additionally ensure that we also don't run into an internal error when
+ // a comment is present.
+ assertThat(diffInfo).content().element(0).commonLines().isNull();
+ assertThat(diffInfo).content().element(0).numberOfSkippedLines().isGreaterThan(0);
+ }
+
+ private static CommentInput createCommentInput(
+ int startLine, int startCharacter, int endLine, int endCharacter, String message) {
+ CommentInput comment = new CommentInput();
+ comment.range = new Comment.Range();
+ comment.range.startLine = startLine;
+ comment.range.startCharacter = startCharacter;
+ comment.range.endLine = endLine;
+ comment.range.endCharacter = endCharacter;
+ comment.message = message;
+ return comment;
+ }
+
private void assertDiffForNewFile(
PushOneCommit.Result pushResult, String path, String expectedContentSideB) throws Exception {
DiffInfo diff =
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 8a3d0f3..ca4304e 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -920,6 +920,7 @@
CountDownLatch reindexed = new CountDownLatch(1);
RegistrationHandle handle =
changeIndexedListeners.add(
+ "gerrit",
new ChangeIndexedListener() {
@Override
public void onChangeIndexed(String projectName, int id) {
@@ -1086,6 +1087,7 @@
WebLinkInfo expectedWebLinkInfo = new WebLinkInfo("foo", "imageUrl", "url");
RegistrationHandle handle =
patchSetLinks.add(
+ "gerrit",
new PatchSetWebLink() {
@Override
public WebLinkInfo getPatchSetWebLink(String projectName, String commit) {
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index b4ae8a2..f1e67c1 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -103,6 +103,7 @@
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
@@ -212,6 +213,24 @@
}
@Test
+ @TestProjectInput(createEmptyCommit = false)
+ public void validateConnected() throws Exception {
+ RevCommit c = testRepo.commit().message("Initial commit").insertChangeId().create();
+ testRepo.reset(c);
+
+ String r = "refs/heads/master";
+ PushResult pr = pushHead(testRepo, r, false);
+ assertPushOk(pr, r);
+
+ RevCommit amended =
+ testRepo.amend(c).message("different initial commit").insertChangeId().create();
+ testRepo.reset(amended);
+ r = "refs/for/master";
+ pr = pushHead(testRepo, r, false);
+ assertPushRejected(pr, r, "no common ancestry");
+ }
+
+ @Test
public void pushInitialCommitForRefsMetaConfigBranch() throws Exception {
// delete refs/meta/config
try (Repository repo = repoManager.openRepository(project);
@@ -301,13 +320,14 @@
r2.assertOkStatus();
r2.assertChange(Change.Status.NEW, null);
r2.assertMessage(
- "New changes:\n"
+ "success\n"
+ + "\n"
+ + "New changes:\n"
+ " "
+ url
+ id2
+ " another commit\n"
+ "\n"
- + "\n"
+ "Updated changes:\n"
+ " "
+ url
@@ -339,6 +359,20 @@
}
@Test
+ public void pushWithoutChangeIdDeprecated() throws Exception {
+ setRequireChangeId(InheritableBoolean.FALSE);
+ testRepo
+ .branch("HEAD")
+ .commit()
+ .message("A change")
+ .author(admin.getIdent())
+ .committer(new PersonIdent(admin.getIdent(), testRepo.getDate()))
+ .create();
+ PushResult result = pushHead(testRepo, "refs/for/master");
+ assertThat(result.getMessages()).contains("warning: pushing without Change-Id is deprecated");
+ }
+
+ @Test
public void autocloseByChangeId() throws Exception {
// Create a change
PushOneCommit.Result r = pushTo("refs/for/master");
@@ -1155,7 +1189,7 @@
pushFactory.create(
db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT, "b.txt", "anotherContent");
r = push.to("refs/for/master");
- r.assertErrorStatus("not Signed-off-by author/committer/uploader in commit message footer");
+ r.assertErrorStatus("not Signed-off-by author/committer/uploader in message footer");
}
@Test
@@ -1228,7 +1262,7 @@
// BatchUpdate implementations differ in how they hook into progress monitors. We mostly just
// care that there is a new change.
- assertThat(pr.getMessages()).containsMatch("changes: new: 1,( refs: 1)? done");
+ assertThat(pr.getMessages()).containsMatch("changes: .*new: 1.*done");
assertTwoChangesWithSameRevision(r);
}
@@ -1359,7 +1393,7 @@
private void testPushWithoutChangeId() throws Exception {
RevCommit c = createCommit(testRepo, "Message without Change-Id");
assertThat(GitUtil.getChangeId(testRepo, c)).isEmpty();
- pushForReviewRejected(testRepo, "missing Change-Id in commit message footer");
+ pushForReviewRejected(testRepo, "missing Change-Id in message footer");
setRequireChangeId(InheritableBoolean.FALSE);
pushForReviewOk(testRepo);
@@ -1384,8 +1418,9 @@
r = push.to("refs/changes/" + r.getChange().change().getId().get());
r.assertErrorStatus(
String.format(
- ChangeIdValidator.CHANGE_ID_MISMATCH_MSG,
- r.getCommit().abbreviate(RevId.ABBREV_LEN).name()));
+ "commit %s: %s",
+ r.getCommit().abbreviate(RevId.ABBREV_LEN).name(),
+ ChangeIdValidator.CHANGE_ID_MISMATCH_MSG));
}
@Test
@@ -1406,10 +1441,10 @@
+ "\n"
+ "Change-Id: I10f98c2ef76e52e23aa23be5afeb71e40b350e86\n"
+ "Change-Id: Ie9a132e107def33bdd513b7854b50de911edba0a\n");
- pushForReviewRejected(testRepo, "multiple Change-Id lines in commit message footer");
+ pushForReviewRejected(testRepo, "multiple Change-Id lines in message footer");
setRequireChangeId(InheritableBoolean.FALSE);
- pushForReviewRejected(testRepo, "multiple Change-Id lines in commit message footer");
+ pushForReviewRejected(testRepo, "multiple Change-Id lines in message footer");
}
@Test
@@ -1425,10 +1460,10 @@
private void testpushWithInvalidChangeId() throws Exception {
createCommit(testRepo, "Message with invalid Change-Id\n\nChange-Id: X\n");
- pushForReviewRejected(testRepo, "invalid Change-Id line format in commit message footer");
+ pushForReviewRejected(testRepo, "invalid Change-Id line format in message footer");
setRequireChangeId(InheritableBoolean.FALSE);
- pushForReviewRejected(testRepo, "invalid Change-Id line format in commit message footer");
+ pushForReviewRejected(testRepo, "invalid Change-Id line format in message footer");
}
@Test
@@ -1449,19 +1484,19 @@
"Message with invalid Change-Id\n"
+ "\n"
+ "Change-Id: I0000000000000000000000000000000000000000\n");
- pushForReviewRejected(testRepo, "invalid Change-Id line format in commit message footer");
+ pushForReviewRejected(testRepo, "invalid Change-Id line format in message footer");
setRequireChangeId(InheritableBoolean.FALSE);
- pushForReviewRejected(testRepo, "invalid Change-Id line format in commit message footer");
+ pushForReviewRejected(testRepo, "invalid Change-Id line format in message footer");
}
@Test
public void pushWithChangeIdInSubjectLine() throws Exception {
createCommit(testRepo, "Change-Id: I1234000000000000000000000000000000000000");
- pushForReviewRejected(testRepo, "missing subject; Change-Id must be in commit message footer");
+ pushForReviewRejected(testRepo, "missing subject; Change-Id must be in message footer");
setRequireChangeId(InheritableBoolean.FALSE);
- pushForReviewRejected(testRepo, "missing subject; Change-Id must be in commit message footer");
+ pushForReviewRejected(testRepo, "missing subject; Change-Id must be in message footer");
}
@Test
@@ -2037,7 +2072,8 @@
assertPushOk(pushHead(testRepo, master), master);
commits.addAll(initChanges(3));
- assertPushRejected(pushHead(testRepo, master), master, "too many commits");
+ assertPushRejected(
+ pushHead(testRepo, master), master, "more than 2 commits, and skip-validation not set");
grantSkipValidation(project, master, SystemGroupBackend.REGISTERED_USERS);
PushResult r =
@@ -2079,7 +2115,7 @@
allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
pr = pushOne(testRepo, c.name(), ref, false, false, opts);
- assertPushRejected(pr, ref, "prohibited by Gerrit: create not permitted for " + ref);
+ assertPushRejected(pr, ref, "prohibited by Gerrit: not permitted: create");
grant(project, "refs/changes/*", Permission.CREATE);
grant(project, "refs/changes/*", Permission.PUSH);
@@ -2101,11 +2137,11 @@
.push()
.setRefSpecs(
new RefSpec(noteDbCommit.name() + ":" + ref),
- new RefSpec(changeCommit.name() + ":refs/for/master"))
+ new RefSpec(changeCommit.name() + ":refs/heads/permitted"))
.call());
assertPushRejected(pr, ref, "NoteDb update requires -o notedb=allow");
- assertPushOk(pr, "refs/for/master");
+ assertPushOk(pr, "refs/heads/permitted");
}
private DraftInput newDraft(String path, int line, String message) {
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java b/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
index 252ec88..943b052 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
@@ -29,11 +29,22 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.StreamSupport;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEditor;
+import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
+import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.FileMode;
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.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTree;
@@ -451,4 +462,47 @@
RevCommit c = rw.parseCommit(commitId);
return c.getAuthorIdent();
}
+
+ protected void directUpdateSubmodule(String project, String refName, String path, AnyObjectId id)
+ throws Exception {
+ path = name(path);
+ try (Repository serverRepo = repoManager.openRepository(new Project.NameKey(name(project)));
+ ObjectInserter ins = serverRepo.newObjectInserter();
+ RevWalk rw = new RevWalk(serverRepo)) {
+ Ref ref = serverRepo.exactRef(refName);
+ assertThat(ref).named(refName).isNotNull();
+ ObjectId oldCommitId = ref.getObjectId();
+
+ DirCache dc = DirCache.newInCore();
+ DirCacheBuilder b = dc.builder();
+ b.addTree(
+ new byte[0], DirCacheEntry.STAGE_0, rw.getObjectReader(), rw.parseTree(oldCommitId));
+ b.finish();
+ DirCacheEditor e = dc.editor();
+ e.add(
+ new PathEdit(path) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ ent.setFileMode(FileMode.GITLINK);
+ ent.setObjectId(id);
+ }
+ });
+ e.finish();
+
+ CommitBuilder cb = new CommitBuilder();
+ cb.addParentId(oldCommitId);
+ cb.setTreeId(dc.writeTree(ins));
+ PersonIdent ident = serverIdent.get();
+ cb.setAuthor(ident);
+ cb.setCommitter(ident);
+ cb.setMessage("Direct update submodule " + path);
+ ObjectId newCommitId = ins.insert(cb);
+ ins.flush();
+
+ RefUpdate ru = serverRepo.updateRef(refName);
+ ru.setExpectedOldObjectId(oldCommitId);
+ ru.setNewObjectId(newCommitId);
+ assertThat(ru.update()).isEqualTo(RefUpdate.Result.FAST_FORWARD);
+ }
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/git/ForcePushIT.java b/javatests/com/google/gerrit/acceptance/git/ForcePushIT.java
index 87ac022..d80faa8 100644
--- a/javatests/com/google/gerrit/acceptance/git/ForcePushIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/ForcePushIT.java
@@ -51,7 +51,7 @@
pushFactory.create(db, admin.getIdent(), testRepo, "change2", "b.txt", "content");
push2.setForce(true);
PushOneCommit.Result r2 = push2.to("refs/heads/master");
- r2.assertErrorStatus("need 'Force Push' privilege.");
+ r2.assertErrorStatus("not permitted: force update");
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java b/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
index af74657..907ad7f 100644
--- a/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
@@ -46,6 +46,7 @@
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import org.eclipse.jgit.transport.TrackingRefUpdate;
import org.junit.Before;
import org.junit.Test;
@@ -78,16 +79,37 @@
}
@Test
+ public void mixingMagicAndRegularPush() throws Exception {
+ testRepo.branch("HEAD").commit().create();
+ PushResult r = push("HEAD:refs/heads/master", "HEAD:refs/for/master");
+
+ String msg = "cannot combine normal pushes and magic pushes";
+ assertThat(r.getRemoteUpdate("refs/heads/master")).isNotEqualTo(Status.OK);
+ assertThat(r.getRemoteUpdate("refs/for/master")).isNotEqualTo(Status.OK);
+ assertThat(r.getRemoteUpdate("refs/for/master").getMessage()).isEqualTo(msg);
+ }
+
+ @Test
+ public void mixingDirectChangesAndRegularPush() throws Exception {
+ testRepo.branch("HEAD").commit().create();
+ PushResult r = push("HEAD:refs/heads/master", "HEAD:refs/changes/01/101");
+
+ String msg = "cannot combine normal pushes and magic pushes";
+ assertThat(r.getRemoteUpdate("refs/heads/master")).isNotEqualTo(Status.OK);
+ assertThat(r.getRemoteUpdate("refs/changes/01/101")).isNotEqualTo(Status.OK);
+ assertThat(r.getRemoteUpdate("refs/heads/master").getMessage()).isEqualTo(msg);
+ }
+
+ @Test
public void fastForwardUpdateDenied() throws Exception {
testRepo.branch("HEAD").commit().create();
PushResult r = push("HEAD:refs/heads/master");
assertThat(r)
.onlyRef("refs/heads/master")
- .isRejected("prohibited by Gerrit: ref update access denied");
+ .isRejected("prohibited by Gerrit: not permitted: update");
assertThat(r)
.hasMessages(
- "Branch refs/heads/master:",
- "You are not allowed to perform this operation.",
+ "error: branch refs/heads/master:",
"To push into this reference you need 'Push' rights.",
"User: admin",
"Contact an administrator to fix the permissions");
@@ -98,19 +120,21 @@
public void nonFastForwardUpdateDenied() throws Exception {
ObjectId commit = testRepo.commit().create();
PushResult r = push("+" + commit.name() + ":refs/heads/master");
- assertThat(r).onlyRef("refs/heads/master").isRejected("need 'Force Push' privilege.");
- assertThat(r).hasNoMessages();
- // TODO(dborowitz): Why does this not mention refs?
- assertThat(r).hasProcessed(ImmutableMap.of());
+ assertThat(r)
+ .onlyRef("refs/heads/master")
+ .isRejected("prohibited by Gerrit: not permitted: force update");
+ assertThat(r).hasProcessed(ImmutableMap.of("refs", 1));
}
@Test
public void deleteDenied() throws Exception {
PushResult r = push(":refs/heads/master");
- assertThat(r).onlyRef("refs/heads/master").isRejected("cannot delete references");
+ assertThat(r)
+ .onlyRef("refs/heads/master")
+ .isRejected("prohibited by Gerrit: not permitted: delete");
assertThat(r)
.hasMessages(
- "Branch refs/heads/master:",
+ "error: branch refs/heads/master:",
"You need 'Delete Reference' rights or 'Push' rights with the ",
"'Force Push' flag set to delete references.",
"User: admin",
@@ -124,8 +148,8 @@
PushResult r = push("HEAD:refs/heads/newbranch");
assertThat(r)
.onlyRef("refs/heads/newbranch")
- .isRejected("prohibited by Gerrit: create not permitted for refs/heads/newbranch");
- assertThat(r).hasNoMessages();
+ .isRejected("prohibited by Gerrit: not permitted: create");
+ assertThat(r).containsMessages("You need 'Create' rights to create new references.");
assertThat(r).hasProcessed(ImmutableMap.of("refs", 1));
}
@@ -139,18 +163,17 @@
testRepo.branch("HEAD").commit().create();
PushResult r = push(":refs/heads/foo", ":refs/heads/bar", "HEAD:refs/heads/master");
- assertThat(r).ref("refs/heads/foo").isRejected("cannot delete references");
- assertThat(r).ref("refs/heads/bar").isRejected("cannot delete references");
+ assertThat(r).ref("refs/heads/foo").isRejected("prohibited by Gerrit: not permitted: delete");
+ assertThat(r).ref("refs/heads/bar").isRejected("prohibited by Gerrit: not permitted: delete");
assertThat(r)
.ref("refs/heads/master")
- .isRejected("prohibited by Gerrit: ref update access denied");
+ .isRejected("prohibited by Gerrit: not permitted: update");
assertThat(r)
.hasMessages(
- "Branches refs/heads/foo, refs/heads/bar:",
+ "error: branches refs/heads/foo, refs/heads/bar:",
"You need 'Delete Reference' rights or 'Push' rights with the ",
"'Force Push' flag set to delete references.",
- "Branch refs/heads/master:",
- "You are not allowed to perform this operation.",
+ "error: branch refs/heads/master:",
"To push into this reference you need 'Push' rights.",
"User: admin",
"Contact an administrator to fix the permissions");
@@ -185,11 +208,10 @@
// ReceiveCommits theoretically has a different message when a WRITE_CONFIG check fails, but
// it never gets there, since DefaultPermissionBackend special-cases refs/meta/config and
// denies UPDATE if the user is not a project owner.
- .isRejected("prohibited by Gerrit: ref update access denied");
+ .isRejected("prohibited by Gerrit: not permitted: update");
assertThat(r)
.hasMessages(
- "Branch refs/meta/config:",
- "You are not allowed to perform this operation.",
+ "error: branch refs/meta/config:",
"Configuration changes can only be pushed by project owners",
"who also have 'Push' rights on refs/meta/config",
"User: admin",
@@ -212,14 +234,12 @@
PushResult r = push("HEAD:refs/for/master");
assertThat(r)
.onlyRef("refs/for/master")
- .isRejected("create change not permitted for refs/heads/master");
+ .isRejected("prohibited by Gerrit: not permitted: create change on refs/heads/master");
assertThat(r)
- .hasMessages(
- "Branch refs/heads/master:",
- "You need 'Push' rights to upload code review requests.",
- "Verify that you are pushing to the right branch.",
- "User: admin",
- "Contact an administrator to fix the permissions");
+ .containsMessages(
+ "error: branch refs/for/master:",
+ "You need 'Create Change' rights to upload code review requests.",
+ "Verify that you are pushing to the right branch.");
assertThat(r).hasProcessed(ImmutableMap.of("refs", 1));
}
@@ -234,8 +254,10 @@
PushResult r = push("HEAD:refs/for/master%submit");
assertThat(r)
.onlyRef("refs/for/master%submit")
- .isRejected("update by submit not permitted for refs/heads/master");
- assertThat(r).hasNoMessages();
+ .isRejected("prohibited by Gerrit: not permitted: update by submit on refs/heads/master");
+ assertThat(r)
+ .containsMessages(
+ "You need 'Submit' rights on refs/for/ to submit changes during change upload.");
assertThat(r).hasProcessed(ImmutableMap.of("refs", 1));
}
@@ -269,8 +291,11 @@
push(c -> c.setPushOptions(ImmutableList.of("skip-validation")), "HEAD:refs/heads/master");
assertThat(r)
.onlyRef("refs/heads/master")
- .isRejected("skip validation not permitted for refs/heads/master");
- assertThat(r).hasNoMessages();
+ .isRejected("prohibited by Gerrit: not permitted: skip validation");
+ assertThat(r)
+ .containsMessages(
+ "You need 'Forge Author', 'Forge Server', 'Forge Committer'",
+ "and 'Push Merge' rights to skip validation.");
assertThat(r).hasProcessed(ImmutableMap.of("refs", 1));
}
diff --git a/javatests/com/google/gerrit/acceptance/git/SubmitOnPushIT.java b/javatests/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
index 0705dca..700b18b 100644
--- a/javatests/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
@@ -37,11 +37,8 @@
import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevObject;
-import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteRefUpdate;
@@ -62,38 +59,6 @@
}
@Test
- public void submitOnPushWithTag() throws Exception {
- grant(project, "refs/for/refs/heads/master", Permission.SUBMIT);
- grant(project, "refs/tags/*", Permission.CREATE);
- grant(project, "refs/tags/*", Permission.PUSH);
- PushOneCommit.Tag tag = new PushOneCommit.Tag("v1.0");
- PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
- push.setTag(tag);
- PushOneCommit.Result r = push.to("refs/for/master%submit");
- r.assertOkStatus();
- r.assertChange(Change.Status.MERGED, null, admin);
- assertSubmitApproval(r.getPatchSetId());
- assertCommit(project, "refs/heads/master");
- assertTag(project, "refs/heads/master", tag);
- }
-
- @Test
- public void submitOnPushWithAnnotatedTag() throws Exception {
- grant(project, "refs/for/refs/heads/master", Permission.SUBMIT);
- grant(project, "refs/tags/*", Permission.PUSH);
- PushOneCommit.AnnotatedTag tag =
- new PushOneCommit.AnnotatedTag("v1.0", "annotation", admin.getIdent());
- PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
- push.setTag(tag);
- PushOneCommit.Result r = push.to("refs/for/master%submit");
- r.assertOkStatus();
- r.assertChange(Change.Status.MERGED, null, admin);
- assertSubmitApproval(r.getPatchSetId());
- assertCommit(project, "refs/heads/master");
- assertTag(project, "refs/heads/master", tag);
- }
-
- @Test
public void submitOnPushToRefsMetaConfig() throws Exception {
grant(project, "refs/for/refs/meta/config", Permission.SUBMIT);
@@ -158,7 +123,7 @@
@Test
public void submitOnPushNotAllowed_Error() throws Exception {
PushOneCommit.Result r = pushTo("refs/for/master%submit");
- r.assertErrorStatus("update by submit not permitted");
+ r.assertErrorStatus("not permitted: update by submit");
}
@Test
@@ -170,7 +135,7 @@
push(
"refs/for/master%submit",
PushOneCommit.SUBJECT, "a.txt", "other content", r.getChangeId());
- r.assertErrorStatus("update by submit not permitted");
+ r.assertErrorStatus("not permitted: update by submit ");
}
@Test
@@ -385,31 +350,6 @@
}
}
- private void assertTag(Project.NameKey project, String branch, PushOneCommit.Tag tag)
- throws Exception {
- try (Repository repo = repoManager.openRepository(project)) {
- Ref tagRef = repo.findRef(tag.name);
- assertThat(tagRef).isNotNull();
- ObjectId taggedCommit = null;
- if (tag instanceof PushOneCommit.AnnotatedTag) {
- PushOneCommit.AnnotatedTag annotatedTag = (PushOneCommit.AnnotatedTag) tag;
- try (RevWalk rw = new RevWalk(repo)) {
- RevObject object = rw.parseAny(tagRef.getObjectId());
- assertThat(object).isInstanceOf(RevTag.class);
- RevTag tagObject = (RevTag) object;
- assertThat(tagObject.getFullMessage()).isEqualTo(annotatedTag.message);
- assertThat(tagObject.getTaggerIdent()).isEqualTo(annotatedTag.tagger);
- taggedCommit = tagObject.getObject();
- }
- } else {
- taggedCommit = tagRef.getObjectId();
- }
- ObjectId headCommit = repo.exactRef(branch).getObjectId();
- assertThat(taggedCommit).isNotNull();
- assertThat(taggedCommit).isEqualTo(headCommit);
- }
- }
-
private PushOneCommit.Result push(String ref, String subject, String fileName, String content)
throws Exception {
PushOneCommit push =
diff --git a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
index 847004f..98e3cae 100644
--- a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
@@ -29,6 +29,7 @@
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.PushResult;
@@ -655,6 +656,65 @@
}
}
+ @Test
+ public void updateOnlyRelevantSubmodules() throws Exception {
+ TestRepository<?> superRepo = createProjectWithPush("super-project");
+ TestRepository<?> subRepo1 = createProjectWithPush("subscribed-to-project-1");
+ TestRepository<?> subRepo2 = createProjectWithPush("subscribed-to-project-2");
+
+ allowMatchingSubmoduleSubscription(
+ "subscribed-to-project-1", "refs/heads/master", "super-project", "refs/heads/master");
+ allowMatchingSubmoduleSubscription(
+ "subscribed-to-project-2", "refs/heads/master", "super-project", "refs/heads/master");
+
+ Config config = new Config();
+ prepareSubmoduleConfigEntry(config, "subscribed-to-project-1", "master");
+ prepareSubmoduleConfigEntry(config, "subscribed-to-project-2", "master");
+ pushSubmoduleConfig(superRepo, "master", config);
+
+ // Push once to initialize submodules.
+ ObjectId subTip2 = pushChangeTo(subRepo2, "master");
+ ObjectId subTip1 = pushChangeTo(subRepo1, "master");
+
+ expectToHaveSubmoduleState(superRepo, "master", "subscribed-to-project-1", subTip1);
+ expectToHaveSubmoduleState(superRepo, "master", "subscribed-to-project-2", subTip2);
+
+ directUpdateRef("subscribed-to-project-2", "refs/heads/master");
+ subTip1 = pushChangeTo(subRepo1, "master");
+
+ expectToHaveSubmoduleState(superRepo, "master", "subscribed-to-project-1", subTip1);
+ expectToHaveSubmoduleState(superRepo, "master", "subscribed-to-project-2", subTip2);
+ }
+
+ @Test
+ public void skipUpdatingBrokenGitlinkPointer() throws Exception {
+ TestRepository<?> superRepo = createProjectWithPush("super-project");
+ TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
+
+ allowMatchingSubmoduleSubscription(
+ "subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master");
+ createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+
+ // Push once to initialize submodule.
+ ObjectId subTip = pushChangeTo(subRepo, "master");
+ expectToHaveSubmoduleState(superRepo, "master", "subscribed-to-project", subTip);
+
+ // Write an invalid SHA-1 directly to the gitlink.
+ ObjectId badId = ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ directUpdateSubmodule("super-project", "refs/heads/master", "subscribed-to-project", badId);
+ expectToHaveSubmoduleState(superRepo, "master", "subscribed-to-project", badId);
+
+ // Push succeeds, but gitlink update is skipped.
+ pushChangeTo(subRepo, "master");
+ expectToHaveSubmoduleState(superRepo, "master", "subscribed-to-project", badId);
+ }
+
+ private ObjectId directUpdateRef(String project, String ref) throws Exception {
+ try (Repository serverRepo = repoManager.openRepository(new Project.NameKey(name(project)))) {
+ return new TestRepository<>(serverRepo).branch(ref).commit().create().copy();
+ }
+ }
+
private void testSubmoduleSubjectCommitMessageAndExpectTruncation() throws Exception {
TestRepository<?> superRepo = createProjectWithPush("super-project");
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
diff --git a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
index 2812c86..eef3295 100644
--- a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
@@ -856,4 +856,44 @@
.that(superRepo.getRepository().resolve("origin/master^"))
.isEqualTo(superPreviousId);
}
+
+ @Test
+ public void skipUpdatingBrokenGitlinkPointer() throws Exception {
+ TestRepository<?> superRepo = createProjectWithPush("super-project");
+ TestRepository<?> sub1 = createProjectWithPush("sub1");
+ TestRepository<?> sub2 = createProjectWithPush("sub2");
+
+ allowMatchingSubmoduleSubscription(
+ "sub1", "refs/heads/master", "super-project", "refs/heads/master");
+ allowMatchingSubmoduleSubscription(
+ "sub2", "refs/heads/master", "super-project", "refs/heads/master");
+
+ Config config = new Config();
+ prepareSubmoduleConfigEntry(config, "sub1", "master");
+ prepareSubmoduleConfigEntry(config, "sub2", "master");
+ pushSubmoduleConfig(superRepo, "master", config);
+
+ // Write an invalid SHA-1 directly to one of the gitlinks.
+ ObjectId badId = ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ directUpdateSubmodule("super-project", "refs/heads/master", "sub1", badId);
+ expectToHaveSubmoduleState(superRepo, "master", "sub1", badId);
+
+ String topic = "same-topic";
+ ObjectId sub1Id = pushChangeTo(sub1, "refs/for/master", "some message", topic);
+ ObjectId sub2Id = pushChangeTo(sub2, "refs/for/master", "some message", topic);
+
+ String changeId1 = getChangeId(sub1, sub1Id).get();
+ String changeId2 = getChangeId(sub2, sub2Id).get();
+ approve(changeId1);
+ approve(changeId2);
+
+ gApi.changes().id(changeId1).current().submit();
+
+ assertThat(info(changeId1).status).isEqualTo(ChangeStatus.MERGED);
+ assertThat(info(changeId2).status).isEqualTo(ChangeStatus.MERGED);
+
+ // sub1 was skipped but sub2 succeeded.
+ expectToHaveSubmoduleState(superRepo, "master", "sub1", badId);
+ expectToHaveSubmoduleState(superRepo, "master", "sub2", sub2, "master");
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
index c90b3d3..0d5d2cd 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
+++ b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
@@ -47,13 +47,8 @@
}
@ConfigSuite.Config
- public static Config elasticsearchV6_2() {
- return getConfig(ElasticVersion.V6_2);
- }
-
- @ConfigSuite.Config
- public static Config elasticsearchV6_3() {
- return getConfig(ElasticVersion.V6_3);
+ public static Config elasticsearchV6() {
+ return getConfig(ElasticVersion.V6_4);
}
@Override
diff --git a/javatests/com/google/gerrit/acceptance/rest/BUILD b/javatests/com/google/gerrit/acceptance/rest/BUILD
index b4940bc..b94a98d 100644
--- a/javatests/com/google/gerrit/acceptance/rest/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/BUILD
@@ -4,7 +4,10 @@
srcs = glob(["*IT.java"]),
group = "rest_bindings",
labels = ["rest"],
- deps = [":util"],
+ deps = [
+ ":util",
+ "//java/com/google/gerrit/server/logging",
+ ],
)
java_library(
diff --git a/javatests/com/google/gerrit/acceptance/rest/TraceIT.java b/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
new file mode 100644
index 0000000..137dc21
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
@@ -0,0 +1,299 @@
+// Copyright (C) 2018 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.rest;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.apache.http.HttpStatus.SC_CREATED;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.truth.Expect;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.httpd.restapi.ParameterParser;
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.git.validators.CommitValidationException;
+import com.google.gerrit.server.git.validators.CommitValidationListener;
+import com.google.gerrit.server.git.validators.CommitValidationMessage;
+import com.google.gerrit.server.logging.LoggingContext;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.project.CreateProjectArgs;
+import com.google.gerrit.server.validators.ProjectCreationValidationListener;
+import com.google.gerrit.server.validators.ValidationException;
+import com.google.inject.Inject;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import org.apache.http.message.BasicHeader;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class TraceIT extends AbstractDaemonTest {
+ @Rule public final Expect expect = Expect.create();
+
+ @Inject private DynamicSet<ProjectCreationValidationListener> projectCreationValidationListeners;
+ @Inject private DynamicSet<CommitValidationListener> commitValidationListeners;
+ @Inject private WorkQueue workQueue;
+
+ private TraceValidatingProjectCreationValidationListener projectCreationListener;
+ private RegistrationHandle projectCreationListenerRegistrationHandle;
+ private TraceValidatingCommitValidationListener commitValidationListener;
+ private RegistrationHandle commitValidationRegistrationHandle;
+
+ @Before
+ public void setup() {
+ projectCreationListener = new TraceValidatingProjectCreationValidationListener();
+ projectCreationListenerRegistrationHandle =
+ projectCreationValidationListeners.add("gerrit", projectCreationListener);
+ commitValidationListener = new TraceValidatingCommitValidationListener();
+ commitValidationRegistrationHandle =
+ commitValidationListeners.add("gerrit", commitValidationListener);
+ }
+
+ @After
+ public void cleanup() {
+ projectCreationListenerRegistrationHandle.remove();
+ commitValidationRegistrationHandle.remove();
+ }
+
+ @Test
+ public void restCallWithoutTrace() throws Exception {
+ RestResponse response = adminRestSession.put("/projects/new1");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+ }
+
+ @Test
+ public void restCallWithTraceRequestParam() throws Exception {
+ RestResponse response =
+ adminRestSession.put("/projects/new2?" + ParameterParser.TRACE_PARAMETER);
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNotNull();
+ assertThat(projectCreationListener.traceId).isNotNull();
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ }
+
+ @Test
+ public void restCallWithTraceRequestParamAndProvidedTraceId() throws Exception {
+ RestResponse response =
+ adminRestSession.put("/projects/new3?" + ParameterParser.TRACE_PARAMETER + "=issue/123");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
+ assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ }
+
+ @Test
+ public void restCallWithTraceHeader() throws Exception {
+ RestResponse response =
+ adminRestSession.putWithHeader(
+ "/projects/new4", new BasicHeader(RestApiServlet.X_GERRIT_TRACE, null));
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNotNull();
+ assertThat(projectCreationListener.traceId).isNotNull();
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ }
+
+ @Test
+ public void restCallWithTraceHeaderAndProvidedTraceId() throws Exception {
+ RestResponse response =
+ adminRestSession.putWithHeader(
+ "/projects/new5", new BasicHeader(RestApiServlet.X_GERRIT_TRACE, "issue/123"));
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
+ assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ }
+
+ @Test
+ public void restCallWithTraceRequestParamAndTraceHeader() throws Exception {
+ // trace ID only specified by trace header
+ RestResponse response =
+ adminRestSession.putWithHeader(
+ "/projects/new6?trace", new BasicHeader(RestApiServlet.X_GERRIT_TRACE, "issue/123"));
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
+ assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+
+ // trace ID only specified by trace request parameter
+ response =
+ adminRestSession.putWithHeader(
+ "/projects/new7?trace=issue/123", new BasicHeader(RestApiServlet.X_GERRIT_TRACE, null));
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
+ assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+
+ // same trace ID specified by trace header and trace request parameter
+ response =
+ adminRestSession.putWithHeader(
+ "/projects/new8?trace=issue/123",
+ new BasicHeader(RestApiServlet.X_GERRIT_TRACE, "issue/123"));
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
+ assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+
+ // different trace IDs specified by trace header and trace request parameter
+ response =
+ adminRestSession.putWithHeader(
+ "/projects/new9?trace=issue/123",
+ new BasicHeader(RestApiServlet.X_GERRIT_TRACE, "issue/456"));
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeaders(RestApiServlet.X_GERRIT_TRACE))
+ .containsExactly("issue/123", "issue/456");
+ assertThat(projectCreationListener.traceIds).containsExactly("issue/123", "issue/456");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ }
+
+ @Test
+ public void pushWithoutTrace() throws Exception {
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
+ PushOneCommit.Result r = push.to("refs/heads/master");
+ r.assertOkStatus();
+ assertThat(commitValidationListener.traceId).isNull();
+ assertThat(commitValidationListener.isLoggingForced).isFalse();
+ }
+
+ @Test
+ public void pushWithTrace() throws Exception {
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
+ push.setPushOptions(ImmutableList.of("trace"));
+ PushOneCommit.Result r = push.to("refs/heads/master");
+ r.assertOkStatus();
+ assertThat(commitValidationListener.traceId).isNotNull();
+ assertThat(commitValidationListener.isLoggingForced).isTrue();
+ }
+
+ @Test
+ public void pushWithTraceAndProvidedTraceId() throws Exception {
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
+ push.setPushOptions(ImmutableList.of("trace=issue/123"));
+ PushOneCommit.Result r = push.to("refs/heads/master");
+ r.assertOkStatus();
+ assertThat(commitValidationListener.traceId).isEqualTo("issue/123");
+ assertThat(commitValidationListener.isLoggingForced).isTrue();
+ }
+
+ @Test
+ public void pushForReviewWithoutTrace() throws Exception {
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
+ PushOneCommit.Result r = push.to("refs/for/master");
+ r.assertOkStatus();
+ assertThat(commitValidationListener.traceId).isNull();
+ assertThat(commitValidationListener.isLoggingForced).isFalse();
+ }
+
+ @Test
+ public void pushForReviewWithTrace() throws Exception {
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
+ push.setPushOptions(ImmutableList.of("trace"));
+ PushOneCommit.Result r = push.to("refs/for/master");
+ r.assertOkStatus();
+ assertThat(commitValidationListener.traceId).isNotNull();
+ assertThat(commitValidationListener.isLoggingForced).isTrue();
+ }
+
+ @Test
+ public void pushForReviewWithTraceAndProvidedTraceId() throws Exception {
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
+ push.setPushOptions(ImmutableList.of("trace=issue/123"));
+ PushOneCommit.Result r = push.to("refs/for/master");
+ r.assertOkStatus();
+ assertThat(commitValidationListener.traceId).isEqualTo("issue/123");
+ assertThat(commitValidationListener.isLoggingForced).isTrue();
+ }
+
+ @Test
+ public void workQueueCopyLoggingContext() throws Exception {
+ assertThat(LoggingContext.getInstance().getTags().isEmpty()).isTrue();
+ assertForceLogging(false);
+ try (TraceContext traceContext = TraceContext.open().forceLogging().addTag("foo", "bar")) {
+ SortedMap<String, SortedSet<Object>> tagMap = LoggingContext.getInstance().getTags().asMap();
+ assertThat(tagMap.keySet()).containsExactly("foo");
+ assertThat(tagMap.get("foo")).containsExactly("bar");
+ assertForceLogging(true);
+
+ workQueue
+ .createQueue(1, "test-queue")
+ .submit(
+ () -> {
+ // Verify that the tags and force logging flag have been propagated to the new
+ // thread.
+ SortedMap<String, SortedSet<Object>> threadTagMap =
+ LoggingContext.getInstance().getTags().asMap();
+ expect.that(threadTagMap.keySet()).containsExactly("foo");
+ expect.that(threadTagMap.get("foo")).containsExactly("bar");
+ expect
+ .that(LoggingContext.getInstance().shouldForceLogging(null, null, false))
+ .isTrue();
+ })
+ .get();
+
+ // Verify that tags and force logging flag in the outer thread are still set.
+ tagMap = LoggingContext.getInstance().getTags().asMap();
+ assertThat(tagMap.keySet()).containsExactly("foo");
+ assertThat(tagMap.get("foo")).containsExactly("bar");
+ assertForceLogging(true);
+ }
+ assertThat(LoggingContext.getInstance().getTags().isEmpty()).isTrue();
+ assertForceLogging(false);
+ }
+
+ private void assertForceLogging(boolean expected) {
+ assertThat(LoggingContext.getInstance().shouldForceLogging(null, null, false))
+ .isEqualTo(expected);
+ }
+
+ private static class TraceValidatingProjectCreationValidationListener
+ implements ProjectCreationValidationListener {
+ String traceId;
+ ImmutableSet<String> traceIds;
+ Boolean isLoggingForced;
+
+ @Override
+ public void validateNewProject(CreateProjectArgs args) throws ValidationException {
+ this.traceId =
+ Iterables.getFirst(LoggingContext.getInstance().getTagsAsMap().get("TRACE_ID"), null);
+ this.traceIds = LoggingContext.getInstance().getTagsAsMap().get("TRACE_ID");
+ this.isLoggingForced = LoggingContext.getInstance().shouldForceLogging(null, null, false);
+ }
+ }
+
+ private static class TraceValidatingCommitValidationListener implements CommitValidationListener {
+ String traceId;
+ Boolean isLoggingForced;
+
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
+ throws CommitValidationException {
+ this.traceId =
+ Iterables.getFirst(LoggingContext.getInstance().getTagsAsMap().get("TRACE_ID"), null);
+ this.isLoggingForced = LoggingContext.getInstance().shouldForceLogging(null, null, false);
+ return ImmutableList.of();
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
index f031729..b74a0d7 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
@@ -60,7 +60,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -105,8 +104,6 @@
.fromJson(
response.getReader(), new TypeToken<List<AccountExternalIdInfo>>() {}.getType());
- Collections.sort(expectedIdInfos);
- Collections.sort(results);
assertThat(results).containsExactlyElementsIn(expectedIdInfos);
}
@@ -133,8 +130,6 @@
.fromJson(
response.getReader(), new TypeToken<List<AccountExternalIdInfo>>() {}.getType());
- Collections.sort(expectedIdInfos);
- Collections.sort(results);
assertThat(results).containsExactlyElementsIn(expectedIdInfos);
}
@@ -927,7 +922,7 @@
private void insertExtIdBehindGerritsBack(ExternalId extId) throws Exception {
try (Repository repo = repoManager.openRepository(allUsers)) {
// Inserting an external ID "behind Gerrit's back" means that the caches are not updated.
- ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(repo);
+ ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsers, repo);
extIdNotes.insert(extId);
try (MetaDataUpdate metaDataUpdate =
new MetaDataUpdate(GitReferenceUpdated.DISABLED, null, repo)) {
@@ -980,7 +975,7 @@
private void assertRefUpdateFailure(RemoteRefUpdate update, String msg) {
assertThat(update.getStatus()).isEqualTo(Status.REJECTED_OTHER_REASON);
- assertThat(update.getMessage()).isEqualTo(msg);
+ assertThat(update.getMessage()).contains(msg);
}
private AutoCloseable createFailOnLoadContext() {
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 61a2d84..af11149 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -27,6 +27,7 @@
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -154,11 +155,12 @@
@Test
@TestProjectInput(createEmptyCommit = false)
public void submitToEmptyRepo() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ assertThat(getRemoteHead()).isNull();
PushOneCommit.Result change = createChange();
+ assertThat(change.getCommit().getParents()).isEmpty();
Map<Branch.NameKey, ObjectId> actual = fetchFromSubmitPreview(change.getChangeId());
RevCommit headAfterSubmitPreview = getRemoteHead();
- assertThat(headAfterSubmitPreview).isEqualTo(initialHead);
+ assertThat(headAfterSubmitPreview).isNull();
assertThat(actual).hasSize(1);
submit(change.getChangeId());
@@ -1070,6 +1072,45 @@
change.current().submit();
}
+ @Test
+ @TestProjectInput(createEmptyCommit = false, rejectEmptyCommit = InheritableBoolean.TRUE)
+ public void submitNonemptyCommitToEmptyRepoWithRejectEmptyCommit_allowed() throws Exception {
+ assertThat(getRemoteHead()).isNull();
+ PushOneCommit.Result change = createChange();
+ assertThat(change.getCommit().getParents()).isEmpty();
+ Map<Branch.NameKey, ObjectId> actual = fetchFromSubmitPreview(change.getChangeId());
+ RevCommit headAfterSubmitPreview = getRemoteHead();
+ assertThat(headAfterSubmitPreview).isNull();
+ assertThat(actual).hasSize(1);
+
+ submit(change.getChangeId());
+ assertThat(getRemoteHead().getId()).isEqualTo(change.getCommit());
+ assertTrees(project, actual);
+ }
+
+ @Test
+ @TestProjectInput(createEmptyCommit = false, rejectEmptyCommit = InheritableBoolean.TRUE)
+ public void submitEmptyCommitToEmptyRepoWithRejectEmptyCommit_allowed() throws Exception {
+ assertThat(getRemoteHead()).isNull();
+ PushOneCommit.Result change =
+ pushFactory
+ .create(db, admin.getIdent(), testRepo, "Change 1", ImmutableMap.of())
+ .to("refs/for/master");
+ change.assertOkStatus();
+ // TODO(dborowitz): Use EMPTY_TREE_ID after upgrading to https://git.eclipse.org/r/127473
+ assertThat(change.getCommit().getTree())
+ .isEqualTo(ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"));
+
+ Map<Branch.NameKey, ObjectId> actual = fetchFromSubmitPreview(change.getChangeId());
+ RevCommit headAfterSubmitPreview = getRemoteHead();
+ assertThat(headAfterSubmitPreview).isNull();
+ assertThat(actual).hasSize(1);
+
+ submit(change.getChangeId());
+ assertThat(getRemoteHead().getId()).isEqualTo(change.getCommit());
+ assertTrees(project, actual);
+ }
+
private void setChangeStatusToNew(PushOneCommit.Result... changes) throws Exception {
for (PushOneCommit.Result change : changes) {
try (BatchUpdate bu =
@@ -1283,7 +1324,7 @@
protected void addOnSubmitValidationListener(OnSubmitValidationListener listener) {
assertThat(onSubmitValidatorHandle).isNull();
- onSubmitValidatorHandle = onSubmitValidationListeners.add(listener);
+ onSubmitValidatorHandle = onSubmitValidationListeners.add("gerrit", listener);
}
private String getLatestDiff(Repository repo) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
index 171babd..f45f9dc 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
@@ -331,7 +331,7 @@
assertThat(origActions.get("abandon").label).isEqualTo("Abandon");
Visitor v = new Visitor();
- visitorHandle = actionVisitors.add(v);
+ visitorHandle = actionVisitors.add("gerrit", v);
Map<String, ActionInfo> newActions =
gApi.changes().id(id).get(EnumSet.of(ListChangesOption.CHANGE_ACTIONS)).actions;
@@ -380,7 +380,7 @@
assertThat(origActions.get("rebase").label).isEqualTo("Rebase");
Visitor v = new Visitor();
- visitorHandle = actionVisitors.add(v);
+ visitorHandle = actionVisitors.add("gerrit", v);
// Test different codepaths within ActionJson...
// ...via revision API.
@@ -443,7 +443,7 @@
assertThat(origActions.get("description").label).isEqualTo("Edit Description");
Visitor v = new Visitor();
- visitorHandle = actionVisitors.add(v);
+ visitorHandle = actionVisitors.add("gerrit", v);
// Unlike for the current revision, actions for old revisions are only available via the
// revision API.
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index 09c7e0b..9218336 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -101,9 +101,7 @@
ChangeInput ci = newChangeInput(ChangeStatus.NEW);
ci.subject = "Subject\n\nChange-Id: I0000000000000000000000000000000000000000";
assertCreateFails(
- ci,
- ResourceConflictException.class,
- "invalid Change-Id line format in commit message footer");
+ ci, ResourceConflictException.class, "invalid Change-Id line format in message footer");
}
@Test
@@ -113,7 +111,7 @@
assertCreateFails(
ci,
ResourceConflictException.class,
- "missing subject; Change-Id must be in commit message footer");
+ "missing subject; Change-Id must be in message footer");
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
index 8cd1770..2182b2f 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
@@ -97,6 +97,7 @@
PushOneCommit.Result change = createChange();
RegistrationHandle handle =
changeMessageModifiers.add(
+ "gerrit",
new ChangeMessageModifier() {
@Override
public String onSubmit(
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java
index e8b8fe8..3d8d06e 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java
@@ -87,6 +87,7 @@
RegistrationHandle handle =
changeMessageModifiers.add(
+ "gerrit",
new ChangeMessageModifier() {
@Override
public String onSubmit(
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
index 3534959..a64305c 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
@@ -89,6 +89,7 @@
public void webLink() throws Exception {
RegistrationHandle handle =
fileHistoryWebLinkDynamicSet.add(
+ "gerrit",
new FileHistoryWebLink() {
@Override
public WebLinkInfo getFileHistoryWebLink(
@@ -111,6 +112,7 @@
public void webLinkNoRefsMetaConfig() throws Exception {
RegistrationHandle handle =
fileHistoryWebLinkDynamicSet.add(
+ "gerrit",
new FileHistoryWebLink() {
@Override
public WebLinkInfo getFileHistoryWebLink(
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/BanCommitIT.java b/javatests/com/google/gerrit/acceptance/rest/project/BanCommitIT.java
index 19f6295..1eea84b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/BanCommitIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/BanCommitIT.java
@@ -46,7 +46,7 @@
pushHead(testRepo, "refs/heads/master", false).getRemoteUpdate("refs/heads/master");
assertThat(u).isNotNull();
assertThat(u.getStatus()).isEqualTo(REJECTED_OTHER_REASON);
- assertThat(u.getMessage()).startsWith("contains banned commit");
+ assertThat(u.getMessage()).contains("contains banned commit");
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java b/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
index 0017e08..df89686 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
@@ -61,7 +61,7 @@
@Test
public void createBranch_Forbidden() throws Exception {
setApiUser(user);
- assertCreateFails(testBranch, AuthException.class, "create not permitted for refs/heads/test");
+ assertCreateFails(testBranch, AuthException.class, "not permitted: create on refs/heads/test");
}
@Test
@@ -85,7 +85,7 @@
@Test
public void createBranchByAdminCreateReferenceBlocked_Forbidden() throws Exception {
blockCreateReference();
- assertCreateFails(testBranch, AuthException.class, "create not permitted for refs/heads/test");
+ assertCreateFails(testBranch, AuthException.class, "not permitted: create on refs/heads/test");
}
@Test
@@ -93,7 +93,7 @@
grantOwner();
blockCreateReference();
setApiUser(user);
- assertCreateFails(testBranch, AuthException.class, "create not permitted for refs/heads/test");
+ assertCreateFails(testBranch, AuthException.class, "not permitted: create on refs/heads/test");
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java b/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
index 5e1b0bf..b426a37 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
@@ -193,7 +193,7 @@
private void assertDeleteForbidden(Branch.NameKey branch) throws Exception {
assertThat(branch(branch).get().canDelete).isNull();
exception.expect(AuthException.class);
- exception.expectMessage("delete not permitted");
+ exception.expectMessage("not permitted: delete");
branch(branch).delete();
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java b/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java
index 330f2b8..c1bd8f1 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java
@@ -75,7 +75,7 @@
project().deleteBranches(input);
fail("Expected AuthException");
} catch (AuthException e) {
- assertThat(e).hasMessageThat().isEqualTo("delete not permitted for refs/heads/test-1");
+ assertThat(e).hasMessageThat().isEqualTo("not permitted: delete on refs/heads/test-1");
}
setApiUser(admin);
assertBranches(BRANCHES);
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/DeleteTagIT.java b/javatests/com/google/gerrit/acceptance/rest/project/DeleteTagIT.java
index 0cbbe44..3ae0b44 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/DeleteTagIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/DeleteTagIT.java
@@ -125,7 +125,7 @@
private void assertDeleteForbidden() throws Exception {
assertThat(tag().get().canDelete).isNull();
exception.expect(AuthException.class);
- exception.expectMessage("delete not permitted");
+ exception.expectMessage("not permitted: delete");
tag().delete();
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/TagsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/TagsIT.java
index 714751d..d4edc0d 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/TagsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/TagsIT.java
@@ -254,7 +254,7 @@
TagInput input = new TagInput();
input.ref = "test";
exception.expect(AuthException.class);
- exception.expectMessage("create not permitted");
+ exception.expectMessage("not permitted: create");
tag(input.ref).create(input);
}
diff --git a/javatests/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java b/javatests/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java
index 14b3858..f4a833f 100644
--- a/javatests/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java
@@ -76,6 +76,7 @@
eventListenerRegistration =
source.add(
+ "gerrit",
new CommentAddedListener() {
@Override
public void onCommentAdded(Event event) {
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
index a5d78c6..87c5ace 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
@@ -610,7 +610,7 @@
}
private void addListener(NotesMigrationStateListener listener) {
- addedListeners.add(listeners.add(listener));
+ addedListeners.add(listeners.add("gerrit", listener));
}
private ImmutableSortedSet<String> getObjectFiles(Project.NameKey project) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/ReflogIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/ReflogIT.java
deleted file mode 100644
index bcae987..0000000
--- a/javatests/com/google/gerrit/acceptance/server/notedb/ReflogIT.java
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.acceptance.server.notedb;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
-import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
-
-import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.acceptance.UseLocalDisk;
-import com.google.gerrit.reviewdb.client.Change;
-import java.io.File;
-import org.eclipse.jgit.lib.ReflogEntry;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.Test;
-
-@UseLocalDisk
-public class ReflogIT extends AbstractDaemonTest {
- @Test
- public void guessRestApiInReflog() throws Exception {
- assume().that(notesMigration.disableChangeReviewDb()).isTrue();
- PushOneCommit.Result r = createChange();
- Change.Id id = r.getChange().getId();
-
- try (Repository repo = repoManager.openRepository(r.getChange().project())) {
- File log = new File(repo.getDirectory(), "logs/" + changeMetaRef(id));
- if (!log.exists()) {
- log.getParentFile().mkdirs();
- assertThat(log.createNewFile()).isTrue();
- }
-
- gApi.changes().id(id.get()).topic("foo");
- ReflogEntry last = repo.getReflogReader(changeMetaRef(id)).getLastEntry();
- assertThat(last).named("last RefLogEntry").isNotNull();
- assertThat(last.getComment()).isEqualTo("restapi.change.PutTopic");
- }
- }
-}
diff --git a/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java b/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
index 45b7767..8b0c56b 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
@@ -82,6 +82,7 @@
eventListenerRegistration =
source.add(
+ "gerrit",
new CommentAddedListener() {
@Override
public void onCommentAdded(Event event) {
diff --git a/javatests/com/google/gerrit/acceptance/server/project/ReflogIT.java b/javatests/com/google/gerrit/acceptance/server/project/ReflogIT.java
new file mode 100644
index 0000000..8abb59d
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/project/ReflogIT.java
@@ -0,0 +1,110 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.server.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.groups.GroupApi;
+import com.google.gerrit.extensions.api.projects.BranchApi;
+import com.google.gerrit.extensions.api.projects.ReflogEntryInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.project.testing.Util;
+import java.io.File;
+import java.util.List;
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Test;
+
+@UseLocalDisk
+public class ReflogIT extends AbstractDaemonTest {
+ @Test
+ public void guessRestApiInReflog() throws Exception {
+ assume().that(notesMigration.disableChangeReviewDb()).isTrue();
+ PushOneCommit.Result r = createChange();
+ Change.Id id = r.getChange().getId();
+
+ try (Repository repo = repoManager.openRepository(r.getChange().project())) {
+ File log = new File(repo.getDirectory(), "logs/" + changeMetaRef(id));
+ if (!log.exists()) {
+ log.getParentFile().mkdirs();
+ assertThat(log.createNewFile()).isTrue();
+ }
+
+ gApi.changes().id(id.get()).topic("foo");
+ ReflogEntry last = repo.getReflogReader(changeMetaRef(id)).getLastEntry();
+ assertThat(last).named("last RefLogEntry").isNotNull();
+ assertThat(last.getComment()).isEqualTo("restapi.change.PutTopic");
+ }
+ }
+
+ @Test
+ public void reflogUpdatedBySubmittingChange() throws Exception {
+ BranchApi branchApi = gApi.projects().name(project.get()).branch("master");
+ List<ReflogEntryInfo> reflog = branchApi.reflog();
+ assertThat(reflog).isNotEmpty();
+
+ // Current number of entries in the reflog
+ int refLogLen = reflog.size();
+
+ // Create and submit a change
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String revision = r.getCommit().name();
+ ReviewInput in = ReviewInput.approve();
+ gApi.changes().id(changeId).revision(revision).review(in);
+ gApi.changes().id(changeId).revision(revision).submit();
+
+ // Submitting the change causes a new entry in the reflog
+ reflog = branchApi.reflog();
+ assertThat(reflog).hasSize(refLogLen + 1);
+ }
+
+ @Test
+ public void regularUserIsNotAllowedToGetReflog() throws Exception {
+ setApiUser(user);
+ exception.expect(AuthException.class);
+ gApi.projects().name(project.get()).branch("master").reflog();
+ }
+
+ @Test
+ public void ownerUserIsAllowedToGetReflog() throws Exception {
+ GroupApi groupApi = gApi.groups().create(name("get-reflog"));
+ groupApi.addMembers("user");
+
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ Util.allow(
+ u.getConfig(), Permission.OWNER, new AccountGroup.UUID(groupApi.get().id), "refs/*");
+ u.save();
+ }
+
+ setApiUser(user);
+ gApi.projects().name(project.get()).branch("master").reflog();
+ }
+
+ @Test
+ public void adminUserIsAllowedToGetReflog() throws Exception {
+ setApiUser(admin);
+ gApi.projects().name(project.get()).branch("master").reflog();
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java b/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java
new file mode 100644
index 0000000..d1b05e3
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java
@@ -0,0 +1,99 @@
+// Copyright (C) 2018 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.rules;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.common.data.SubmitRequirement;
+import com.google.gerrit.server.project.SubmitRuleOptions;
+import com.google.gerrit.server.rules.IgnoreSelfApprovalRule;
+import com.google.inject.Inject;
+import java.util.Collection;
+import java.util.Map;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.junit.Test;
+
+@NoHttpd
+public class IgnoreSelfApprovalRuleIT extends AbstractDaemonTest {
+ @Inject private IgnoreSelfApprovalRule rule;
+
+ @Test
+ public void blocksWhenUploaderIsOnlyApprover() throws Exception {
+ enableRule("Code-Review", true);
+
+ PushOneCommit.Result r = createChange();
+ approve(r.getChangeId());
+
+ Collection<SubmitRecord> submitRecords =
+ rule.evaluate(r.getChange(), SubmitRuleOptions.defaults());
+
+ assertThat(submitRecords).hasSize(1);
+ SubmitRecord result = submitRecords.iterator().next();
+ assertThat(result.status).isEqualTo(SubmitRecord.Status.NOT_READY);
+ assertThat(result.labels).isNotEmpty();
+ assertThat(result.requirements)
+ .containsExactly(
+ SubmitRequirement.builder()
+ .setFallbackText("Approval from non-uploader required")
+ .setType("non_uploader_approval")
+ .build());
+ }
+
+ @Test
+ public void allowsSubmissionWhenChangeHasNonUploaderApproval() throws Exception {
+ enableRule("Code-Review", true);
+
+ // Create change as user
+ TestRepository<InMemoryRepository> userTestRepo = cloneProject(project, user);
+ PushOneCommit push = pushFactory.create(db, user.getIdent(), userTestRepo);
+ PushOneCommit.Result r = push.to("refs/for/master");
+
+ // Approve as admin
+ approve(r.getChangeId());
+
+ Collection<SubmitRecord> submitRecords =
+ rule.evaluate(r.getChange(), SubmitRuleOptions.defaults());
+ assertThat(submitRecords).isEmpty();
+ }
+
+ @Test
+ public void doesNothingByDefault() throws Exception {
+ enableRule("Code-Review", false);
+
+ PushOneCommit.Result r = createChange();
+ approve(r.getChangeId());
+
+ Collection<SubmitRecord> submitRecords =
+ rule.evaluate(r.getChange(), SubmitRuleOptions.defaults());
+ assertThat(submitRecords).isEmpty();
+ }
+
+ private void enableRule(String labelName, boolean newState) throws Exception {
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ Map<String, LabelType> localLabelSections = u.getConfig().getLabelSections();
+ if (localLabelSections.isEmpty()) {
+ localLabelSections.putAll(projectCache.getAllProjects().getConfig().getLabelSections());
+ }
+ localLabelSections.get(labelName).setIgnoreSelfApproval(newState);
+ u.save();
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java b/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
index 25bb7a6..ed3cdbc 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
@@ -50,7 +50,7 @@
@Before
public void addChangeIndexedCounter() {
changeIndexedCounter = new ChangeIndexedCounter();
- changeIndexedCounterHandle = changeIndexedListeners.add(changeIndexedCounter);
+ changeIndexedCounterHandle = changeIndexedListeners.add("gerrit", changeIndexedCounter);
}
@After
diff --git a/javatests/com/google/gerrit/acceptance/ssh/BUILD b/javatests/com/google/gerrit/acceptance/ssh/BUILD
index b195ecc..a01cd3e 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/BUILD
+++ b/javatests/com/google/gerrit/acceptance/ssh/BUILD
@@ -17,6 +17,7 @@
vm_args = ["-Xmx512m"],
deps = [
":util",
+ "//java/com/google/gerrit/server/logging",
"//lib/commons:compress",
],
)
diff --git a/javatests/com/google/gerrit/acceptance/ssh/BanCommitIT.java b/javatests/com/google/gerrit/acceptance/ssh/BanCommitIT.java
index 7a80f2e..2b00718 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/BanCommitIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/BanCommitIT.java
@@ -42,6 +42,6 @@
pushHead(testRepo, "refs/heads/master", false).getRemoteUpdate("refs/heads/master");
assertThat(u).isNotNull();
assertThat(u.getStatus()).isEqualTo(REJECTED_OTHER_REASON);
- assertThat(u.getMessage()).startsWith("contains banned commit");
+ assertThat(u.getMessage()).contains("contains banned commit");
}
}
diff --git a/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java b/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
index 95da5a6..9d69955 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
@@ -46,13 +46,8 @@
}
@ConfigSuite.Config
- public static Config elasticsearchV6_2() {
- return getConfig(ElasticVersion.V6_2);
- }
-
- @ConfigSuite.Config
- public static Config elasticsearchV6_3() {
- return getConfig(ElasticVersion.V6_3);
+ public static Config elasticsearchV6() {
+ return getConfig(ElasticVersion.V6_4);
}
@Override
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
index cc86c0b..e603413 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
@@ -14,12 +14,14 @@
package com.google.gerrit.acceptance.ssh;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
@@ -28,7 +30,6 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.sshd.Commands;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Test;
@@ -39,7 +40,7 @@
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
// TODO: It would be better to dynamically generate these lists
- private static final List<String> COMMON_ROOT_COMMANDS =
+ private static final ImmutableList<String> COMMON_ROOT_COMMANDS =
ImmutableList.of(
"apropos",
"close-connection",
@@ -57,7 +58,7 @@
"show-queue",
"version");
- private static final List<String> MASTER_ONLY_ROOT_COMMANDS =
+ private static final ImmutableList<String> MASTER_ONLY_ROOT_COMMANDS =
ImmutableList.of(
"ban-commit",
"create-account",
@@ -79,19 +80,12 @@
"stream-events",
"test-submit");
- private static final Map<String, List<String>> MASTER_COMMANDS =
+ private static final ImmutableMap<String, List<String>> MASTER_COMMANDS =
ImmutableMap.of(
Commands.ROOT,
- ImmutableList.copyOf(
- new ArrayList<String>() {
- private static final long serialVersionUID = 1L;
-
- {
- addAll(COMMON_ROOT_COMMANDS);
- addAll(MASTER_ONLY_ROOT_COMMANDS);
- Collections.sort(this);
- }
- }),
+ Streams.concat(COMMON_ROOT_COMMANDS.stream(), MASTER_ONLY_ROOT_COMMANDS.stream())
+ .sorted()
+ .collect(toImmutableList()),
"index",
ImmutableList.of(
"changes", "changes-in-project"), // "activate" and "start" are not included
@@ -102,7 +96,7 @@
"test-submit",
ImmutableList.of("rule", "type"));
- private static final Map<String, List<String>> SLAVE_COMMANDS =
+ private static final ImmutableMap<String, List<String>> SLAVE_COMMANDS =
ImmutableMap.of(
Commands.ROOT,
COMMON_ROOT_COMMANDS,
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SshTraceIT.java b/javatests/com/google/gerrit/acceptance/ssh/SshTraceIT.java
new file mode 100644
index 0000000..899b0cf
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/ssh/SshTraceIT.java
@@ -0,0 +1,92 @@
+package com.google.gerrit.acceptance.ssh;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Iterables;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.UseSsh;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.server.logging.LoggingContext;
+import com.google.gerrit.server.logging.RequestId;
+import com.google.gerrit.server.project.CreateProjectArgs;
+import com.google.gerrit.server.validators.ProjectCreationValidationListener;
+import com.google.gerrit.server.validators.ValidationException;
+import com.google.inject.Inject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+@UseSsh
+public class SshTraceIT extends AbstractDaemonTest {
+ @Inject private DynamicSet<ProjectCreationValidationListener> projectCreationValidationListeners;
+
+ private TraceValidatingProjectCreationValidationListener projectCreationListener;
+ private RegistrationHandle projectCreationListenerRegistrationHandle;
+
+ @Before
+ public void setup() {
+ projectCreationListener = new TraceValidatingProjectCreationValidationListener();
+ projectCreationListenerRegistrationHandle =
+ projectCreationValidationListeners.add("gerrit", projectCreationListener);
+ }
+
+ @After
+ public void cleanup() {
+ projectCreationListenerRegistrationHandle.remove();
+ }
+
+ @Test
+ public void sshCallWithoutTrace() throws Exception {
+ adminSshSession.exec("gerrit create-project new1");
+ adminSshSession.assertSuccess();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.foundTraceId).isFalse();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+ }
+
+ @Test
+ public void sshCallWithTrace() throws Exception {
+ adminSshSession.exec("gerrit create-project --trace new2");
+
+ // The trace ID is written to stderr.
+ adminSshSession.assertFailure(RequestId.Type.TRACE_ID.name());
+
+ assertThat(projectCreationListener.traceId).isNotNull();
+ assertThat(projectCreationListener.foundTraceId).isTrue();
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ }
+
+ @Test
+ public void sshCallWithTraceAndProvidedTraceId() throws Exception {
+ adminSshSession.exec("gerrit create-project --trace --trace-id issue/123 new3");
+
+ // The trace ID is written to stderr.
+ adminSshSession.assertFailure(RequestId.Type.TRACE_ID.name());
+
+ assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
+ assertThat(projectCreationListener.foundTraceId).isTrue();
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ }
+
+ @Test
+ public void sshCallWithTraceIdAndWithoutTraceFails() throws Exception {
+ adminSshSession.exec("gerrit create-project --trace-id issue/123 new3");
+ adminSshSession.assertFailure("A trace ID can only be set if --trace was specified.");
+ }
+
+ private static class TraceValidatingProjectCreationValidationListener
+ implements ProjectCreationValidationListener {
+ String traceId;
+ Boolean foundTraceId;
+ Boolean isLoggingForced;
+
+ @Override
+ public void validateNewProject(CreateProjectArgs args) throws ValidationException {
+ this.traceId =
+ Iterables.getFirst(LoggingContext.getInstance().getTagsAsMap().get("TRACE_ID"), null);
+ this.foundTraceId = traceId != null;
+ this.isLoggingForced = LoggingContext.getInstance().shouldForceLogging(null, null, false);
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImplTest.java b/javatests/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImplTest.java
new file mode 100644
index 0000000..954b0e6
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImplTest.java
@@ -0,0 +1,656 @@
+// Copyright (C) 2018 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.testsuite.group;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.truth.Correspondence;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
+import com.google.gerrit.extensions.api.groups.GroupInput;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.inject.Inject;
+import java.sql.Timestamp;
+import java.util.Objects;
+import java.util.Optional;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class GroupOperationsImplTest extends AbstractDaemonTest {
+
+ @Rule public ExpectedException expectedException = ExpectedException.none();
+
+ @Inject private AccountOperations accountOperations;
+
+ @Inject private GroupOperationsImpl groupOperations;
+
+ private int uniqueGroupNameIndex;
+
+ @Test
+ public void groupCanBeCreatedWithoutSpecifyingAnyParameters() throws Exception {
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().create();
+
+ GroupInfo foundGroup = getGroupFromServer(groupUuid);
+ assertThat(foundGroup.id).isEqualTo(groupUuid.get());
+ assertThat(foundGroup.name).isNotEmpty();
+ }
+
+ @Test
+ public void twoGroupsWithoutAnyParametersDoNotClash() throws Exception {
+ AccountGroup.UUID groupUuid1 = groupOperations.newGroup().create();
+ AccountGroup.UUID groupUuid2 = groupOperations.newGroup().create();
+
+ TestGroup group1 = groupOperations.group(groupUuid1).get();
+ TestGroup group2 = groupOperations.group(groupUuid2).get();
+ assertThat(group1.groupUuid()).isNotEqualTo(group2.groupUuid());
+ }
+
+ @Test
+ public void groupCreatedByTestApiCanBeRetrievedViaOfficialApi() throws Exception {
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().name("unique group created via test API").create();
+
+ GroupInfo foundGroup = getGroupFromServer(groupUuid);
+ assertThat(foundGroup.id).isEqualTo(groupUuid.get());
+ assertThat(foundGroup.name).isEqualTo("unique group created via test API");
+ }
+
+ @Test
+ public void specifiedNameIsRespectedForGroupCreation() throws Exception {
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().name("XYZ-123-this-name-must-be-unique").create();
+
+ GroupInfo group = getGroupFromServer(groupUuid);
+ assertThat(group.name).isEqualTo("XYZ-123-this-name-must-be-unique");
+ }
+
+ @Test
+ public void specifiedDescriptionIsRespectedForGroupCreation() throws Exception {
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().description("All authenticated users").create();
+
+ GroupInfo group = getGroupFromServer(groupUuid);
+ assertThat(group.description).isEqualTo("All authenticated users");
+ }
+
+ @Test
+ public void requestingNoDescriptionIsPossibleForGroupCreation() throws Exception {
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().clearDescription().create();
+
+ GroupInfo group = getGroupFromServer(groupUuid);
+ assertThat(group.description).isNull();
+ }
+
+ @Test
+ public void specifiedOwnerIsRespectedForGroupCreation() throws Exception {
+ AccountGroup.UUID ownerGroupUuid = groupOperations.newGroup().create();
+
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().ownerGroupUuid(ownerGroupUuid).create();
+
+ GroupInfo foundGroup = getGroupFromServer(groupUuid);
+ assertThat(foundGroup.ownerId).isEqualTo(ownerGroupUuid.get());
+ }
+
+ @Test
+ public void specifiedVisibilityIsRespectedForGroupCreation() throws Exception {
+ AccountGroup.UUID group1Uuid = groupOperations.newGroup().visibleToAll(true).create();
+ AccountGroup.UUID group2Uuid = groupOperations.newGroup().visibleToAll(false).create();
+
+ GroupInfo foundGroup1 = getGroupFromServer(group1Uuid);
+ GroupInfo foundGroup2 = getGroupFromServer(group2Uuid);
+ assertThat(foundGroup1.options.visibleToAll).isTrue();
+ // False == null
+ assertThat(foundGroup2.options.visibleToAll).isNull();
+ }
+
+ @Test
+ public void specifiedMembersAreRespectedForGroupCreation() throws Exception {
+ Account.Id account1Id = accountOperations.newAccount().create();
+ Account.Id account2Id = accountOperations.newAccount().create();
+ Account.Id account3Id = accountOperations.newAccount().create();
+ Account.Id account4Id = accountOperations.newAccount().create();
+
+ AccountGroup.UUID groupUuid =
+ groupOperations
+ .newGroup()
+ .members(account1Id, account2Id)
+ .addMember(account3Id)
+ .addMember(account4Id)
+ .create();
+
+ GroupInfo foundGroup = getGroupFromServer(groupUuid);
+ assertThat(foundGroup.members)
+ .comparingElementsUsing(getAccountToIdCorrespondence())
+ .containsExactly(account1Id, account2Id, account3Id, account4Id);
+ }
+
+ @Test
+ public void directlyAddingMembersIsPossibleForGroupCreation() throws Exception {
+ Account.Id account1Id = accountOperations.newAccount().create();
+ Account.Id account2Id = accountOperations.newAccount().create();
+
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().addMember(account1Id).addMember(account2Id).create();
+
+ GroupInfo foundGroup = getGroupFromServer(groupUuid);
+ assertThat(foundGroup.members)
+ .comparingElementsUsing(getAccountToIdCorrespondence())
+ .containsExactly(account1Id, account2Id);
+ }
+
+ @Test
+ public void requestingNoMembersIsPossibleForGroupCreation() throws Exception {
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().clearMembers().create();
+
+ GroupInfo foundGroup = getGroupFromServer(groupUuid);
+ assertThat(foundGroup.members).isEmpty();
+ }
+
+ @Test
+ public void specifiedSubgroupsAreRespectedForGroupCreation() throws Exception {
+ AccountGroup.UUID group1Uuid = groupOperations.newGroup().create();
+ AccountGroup.UUID group2Uuid = groupOperations.newGroup().create();
+ AccountGroup.UUID group3Uuid = groupOperations.newGroup().create();
+ AccountGroup.UUID group4Uuid = groupOperations.newGroup().create();
+
+ AccountGroup.UUID groupUuid =
+ groupOperations
+ .newGroup()
+ .subgroups(group1Uuid, group2Uuid)
+ .addSubgroup(group3Uuid)
+ .addSubgroup(group4Uuid)
+ .create();
+
+ GroupInfo foundGroup = getGroupFromServer(groupUuid);
+ assertThat(foundGroup.includes)
+ .comparingElementsUsing(getGroupToUuidCorrespondence())
+ .containsExactly(group1Uuid, group2Uuid, group3Uuid, group4Uuid);
+ }
+
+ @Test
+ public void directlyAddingSubgroupsIsPossibleForGroupCreation() throws Exception {
+ AccountGroup.UUID group1Uuid = groupOperations.newGroup().create();
+ AccountGroup.UUID group2Uuid = groupOperations.newGroup().create();
+
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().addSubgroup(group1Uuid).addSubgroup(group2Uuid).create();
+
+ GroupInfo foundGroup = getGroupFromServer(groupUuid);
+ assertThat(foundGroup.includes)
+ .comparingElementsUsing(getGroupToUuidCorrespondence())
+ .containsExactly(group1Uuid, group2Uuid);
+ }
+
+ @Test
+ public void requestingNoSubgroupsIsPossibleForGroupCreation() throws Exception {
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().clearSubgroups().create();
+
+ GroupInfo foundGroup = getGroupFromServer(groupUuid);
+ assertThat(foundGroup.includes).isEmpty();
+ }
+
+ @Test
+ public void existingGroupCanBeCheckedForExistence() throws Exception {
+ AccountGroup.UUID groupUuid = createGroupInServer(createArbitraryGroupInput());
+
+ boolean exists = groupOperations.group(groupUuid).exists();
+
+ assertThat(exists).isTrue();
+ }
+
+ @Test
+ public void notExistingGroupCanBeCheckedForExistence() throws Exception {
+ AccountGroup.UUID notExistingGroupUuid = new AccountGroup.UUID("not-existing-group");
+
+ boolean exists = groupOperations.group(notExistingGroupUuid).exists();
+
+ assertThat(exists).isFalse();
+ }
+
+ @Test
+ public void retrievingNotExistingGroupFails() throws Exception {
+ AccountGroup.UUID notExistingGroupUuid = new AccountGroup.UUID("not-existing-group");
+
+ expectedException.expect(IllegalStateException.class);
+ groupOperations.group(notExistingGroupUuid).get();
+ }
+
+ @Test
+ public void groupNotCreatedByTestApiCanBeRetrieved() throws Exception {
+ GroupInput input = createArbitraryGroupInput();
+ input.name = "unique group not created via test API";
+ AccountGroup.UUID groupUuid = createGroupInServer(input);
+
+ TestGroup foundGroup = groupOperations.group(groupUuid).get();
+
+ assertThat(foundGroup.groupUuid()).isEqualTo(groupUuid);
+ assertThat(foundGroup.name()).isEqualTo("unique group not created via test API");
+ }
+
+ @Test
+ public void uuidOfExistingGroupCanBeRetrieved() throws Exception {
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().create();
+
+ AccountGroup.UUID foundGroupUuid = groupOperations.group(groupUuid).get().groupUuid();
+
+ assertThat(foundGroupUuid).isEqualTo(groupUuid);
+ }
+
+ @Test
+ public void nameOfExistingGroupCanBeRetrieved() throws Exception {
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().name("ABC-789-this-name-must-be-unique").create();
+
+ String groupName = groupOperations.group(groupUuid).get().name();
+
+ assertThat(groupName).isEqualTo("ABC-789-this-name-must-be-unique");
+ }
+
+ @Test
+ public void nameKeyOfExistingGroupCanBeRetrieved() throws Exception {
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().name("ABC-789-this-name-must-be-unique").create();
+
+ AccountGroup.NameKey groupName = groupOperations.group(groupUuid).get().nameKey();
+
+ assertThat(groupName).isEqualTo(new AccountGroup.NameKey("ABC-789-this-name-must-be-unique"));
+ }
+
+ @Test
+ public void descriptionOfExistingGroupCanBeRetrieved() throws Exception {
+ AccountGroup.UUID groupUuid =
+ groupOperations
+ .newGroup()
+ .description("This is a very detailed description of this group.")
+ .create();
+
+ Optional<String> description = groupOperations.group(groupUuid).get().description();
+
+ assertThat(description).hasValue("This is a very detailed description of this group.");
+ }
+
+ @Test
+ public void emptyDescriptionOfExistingGroupCanBeRetrieved() throws Exception {
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().clearDescription().create();
+
+ Optional<String> description = groupOperations.group(groupUuid).get().description();
+
+ assertThat(description).isEmpty();
+ }
+
+ @Test
+ public void ownerGroupUuidOfExistingGroupCanBeRetrieved() throws Exception {
+ AccountGroup.UUID originalOwnerGroupUuid = new AccountGroup.UUID("owner group");
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().ownerGroupUuid(originalOwnerGroupUuid).create();
+
+ AccountGroup.UUID ownerGroupUuid = groupOperations.group(groupUuid).get().ownerGroupUuid();
+
+ assertThat(ownerGroupUuid).isEqualTo(originalOwnerGroupUuid);
+ }
+
+ @Test
+ public void visibilityOfExistingGroupCanBeRetrieved() throws Exception {
+ AccountGroup.UUID visibleGroupUuid = groupOperations.newGroup().visibleToAll(true).create();
+ AccountGroup.UUID invisibleGroupUuid = groupOperations.newGroup().visibleToAll(false).create();
+
+ TestGroup visibleGroup = groupOperations.group(visibleGroupUuid).get();
+ TestGroup invisibleGroup = groupOperations.group(invisibleGroupUuid).get();
+
+ assertThat(visibleGroup.visibleToAll()).named("visibility of visible group").isTrue();
+ assertThat(invisibleGroup.visibleToAll()).named("visibility of invisible group").isFalse();
+ }
+
+ @Test
+ public void createdOnOfExistingGroupCanBeRetrieved() throws Exception {
+ GroupInfo group = gApi.groups().create(createArbitraryGroupInput()).detail();
+ AccountGroup.UUID groupUuid = new AccountGroup.UUID(group.id);
+
+ Timestamp createdOn = groupOperations.group(groupUuid).get().createdOn();
+
+ assertThat(createdOn).isEqualTo(group.createdOn);
+ }
+
+ @Test
+ public void membersOfExistingGroupCanBeRetrieved() throws Exception {
+ Account.Id memberId1 = new Account.Id(1000);
+ Account.Id memberId2 = new Account.Id(2000);
+ Account.Id memberId3 = new Account.Id(3000);
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().members(memberId1, memberId2, memberId3).create();
+
+ ImmutableSet<Account.Id> members = groupOperations.group(groupUuid).get().members();
+
+ assertThat(members).containsExactly(memberId1, memberId2, memberId3);
+ }
+
+ @Test
+ public void emptyMembersOfExistingGroupCanBeRetrieved() throws Exception {
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().clearMembers().create();
+
+ ImmutableSet<Account.Id> members = groupOperations.group(groupUuid).get().members();
+
+ assertThat(members).isEmpty();
+ }
+
+ @Test
+ public void subgroupsOfExistingGroupCanBeRetrieved() throws Exception {
+ AccountGroup.UUID subgroupUuid1 = new AccountGroup.UUID("subgroup 1");
+ AccountGroup.UUID subgroupUuid2 = new AccountGroup.UUID("subgroup 2");
+ AccountGroup.UUID subgroupUuid3 = new AccountGroup.UUID("subgroup 3");
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().subgroups(subgroupUuid1, subgroupUuid2, subgroupUuid3).create();
+
+ ImmutableSet<AccountGroup.UUID> subgroups = groupOperations.group(groupUuid).get().subgroups();
+
+ assertThat(subgroups).containsExactly(subgroupUuid1, subgroupUuid2, subgroupUuid3);
+ }
+
+ @Test
+ public void emptySubgroupsOfExistingGroupCanBeRetrieved() throws Exception {
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().clearSubgroups().create();
+
+ ImmutableSet<AccountGroup.UUID> subgroups = groupOperations.group(groupUuid).get().subgroups();
+
+ assertThat(subgroups).isEmpty();
+ }
+
+ @Test
+ public void updateWithoutAnyParametersIsANoop() throws Exception {
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().create();
+ TestGroup originalGroup = groupOperations.group(groupUuid).get();
+
+ groupOperations.group(groupUuid).forUpdate().update();
+
+ TestGroup updatedGroup = groupOperations.group(groupUuid).get();
+ assertThat(updatedGroup).isEqualTo(originalGroup);
+ }
+
+ @Test
+ public void updateWritesToInternalGroupSystem() throws Exception {
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().description("original description").create();
+
+ groupOperations.group(groupUuid).forUpdate().description("updated description").update();
+
+ String currentDescription = getGroupFromServer(groupUuid).description;
+ assertThat(currentDescription).isEqualTo("updated description");
+ }
+
+ @Test
+ public void nameCanBeUpdated() throws Exception {
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().name("original name").create();
+
+ groupOperations.group(groupUuid).forUpdate().name("updated name").update();
+
+ String currentName = groupOperations.group(groupUuid).get().name();
+ assertThat(currentName).isEqualTo("updated name");
+ }
+
+ @Test
+ public void descriptionCanBeUpdated() throws Exception {
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().description("original description").create();
+
+ groupOperations.group(groupUuid).forUpdate().description("updated description").update();
+
+ Optional<String> currentDescription = groupOperations.group(groupUuid).get().description();
+ assertThat(currentDescription).hasValue("updated description");
+ }
+
+ @Test
+ public void descriptionCanBeCleared() throws Exception {
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().description("original description").create();
+
+ groupOperations.group(groupUuid).forUpdate().clearDescription().update();
+
+ Optional<String> currentDescription = groupOperations.group(groupUuid).get().description();
+ assertThat(currentDescription).isEmpty();
+ }
+
+ @Test
+ public void ownerGroupUuidCanBeUpdated() throws Exception {
+ AccountGroup.UUID originalOwnerGroupUuid = new AccountGroup.UUID("original owner");
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().ownerGroupUuid(originalOwnerGroupUuid).create();
+
+ AccountGroup.UUID updatedOwnerGroupUuid = new AccountGroup.UUID("updated owner");
+ groupOperations.group(groupUuid).forUpdate().ownerGroupUuid(updatedOwnerGroupUuid).update();
+
+ AccountGroup.UUID currentOwnerGroupUuid =
+ groupOperations.group(groupUuid).get().ownerGroupUuid();
+ assertThat(currentOwnerGroupUuid).isEqualTo(updatedOwnerGroupUuid);
+ }
+
+ @Test
+ public void visibilityCanBeUpdated() throws Exception {
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().visibleToAll(true).create();
+
+ groupOperations.group(groupUuid).forUpdate().visibleToAll(false).update();
+
+ boolean visibleToAll = groupOperations.group(groupUuid).get().visibleToAll();
+ assertThat(visibleToAll).isFalse();
+ }
+
+ @Test
+ public void membersCanBeAdded() throws Exception {
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().clearMembers().create();
+
+ Account.Id memberId1 = new Account.Id(1000);
+ Account.Id memberId2 = new Account.Id(2000);
+ groupOperations.group(groupUuid).forUpdate().addMember(memberId1).addMember(memberId2).update();
+
+ ImmutableSet<Account.Id> members = groupOperations.group(groupUuid).get().members();
+ assertThat(members).containsExactly(memberId1, memberId2);
+ }
+
+ @Test
+ public void membersCanBeRemoved() throws Exception {
+ Account.Id memberId1 = new Account.Id(1000);
+ Account.Id memberId2 = new Account.Id(2000);
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().members(memberId1, memberId2).create();
+
+ groupOperations.group(groupUuid).forUpdate().removeMember(memberId2).update();
+
+ ImmutableSet<Account.Id> members = groupOperations.group(groupUuid).get().members();
+ assertThat(members).containsExactly(memberId1);
+ }
+
+ @Test
+ public void memberAdditionAndRemovalCanBeMixed() throws Exception {
+ Account.Id memberId1 = new Account.Id(1000);
+ Account.Id memberId2 = new Account.Id(2000);
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().members(memberId1, memberId2).create();
+
+ Account.Id memberId3 = new Account.Id(3000);
+ groupOperations
+ .group(groupUuid)
+ .forUpdate()
+ .removeMember(memberId1)
+ .addMember(memberId3)
+ .update();
+
+ ImmutableSet<Account.Id> members = groupOperations.group(groupUuid).get().members();
+ assertThat(members).containsExactly(memberId2, memberId3);
+ }
+
+ @Test
+ public void membersCanBeCleared() throws Exception {
+ Account.Id memberId1 = new Account.Id(1000);
+ Account.Id memberId2 = new Account.Id(2000);
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().members(memberId1, memberId2).create();
+
+ groupOperations.group(groupUuid).forUpdate().clearMembers().update();
+
+ ImmutableSet<Account.Id> members = groupOperations.group(groupUuid).get().members();
+ assertThat(members).isEmpty();
+ }
+
+ @Test
+ public void furtherMembersCanBeAddedAfterClearingAll() throws Exception {
+ Account.Id memberId1 = new Account.Id(1000);
+ Account.Id memberId2 = new Account.Id(2000);
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().members(memberId1, memberId2).create();
+
+ Account.Id memberId3 = new Account.Id(3000);
+ groupOperations.group(groupUuid).forUpdate().clearMembers().addMember(memberId3).update();
+
+ ImmutableSet<Account.Id> members = groupOperations.group(groupUuid).get().members();
+ assertThat(members).containsExactly(memberId3);
+ }
+
+ @Test
+ public void subgroupsCanBeAdded() throws Exception {
+ AccountGroup.UUID groupUuid = groupOperations.newGroup().clearSubgroups().create();
+
+ AccountGroup.UUID subgroupUuid1 = new AccountGroup.UUID("subgroup 1");
+ AccountGroup.UUID subgroupUuid2 = new AccountGroup.UUID("subgroup 2");
+ groupOperations
+ .group(groupUuid)
+ .forUpdate()
+ .addSubgroup(subgroupUuid1)
+ .addSubgroup(subgroupUuid2)
+ .update();
+
+ ImmutableSet<AccountGroup.UUID> subgroups = groupOperations.group(groupUuid).get().subgroups();
+ assertThat(subgroups).containsExactly(subgroupUuid1, subgroupUuid2);
+ }
+
+ @Test
+ public void subgroupsCanBeRemoved() throws Exception {
+ AccountGroup.UUID subgroupUuid1 = new AccountGroup.UUID("subgroup 1");
+ AccountGroup.UUID subgroupUuid2 = new AccountGroup.UUID("subgroup 2");
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().subgroups(subgroupUuid1, subgroupUuid2).create();
+
+ groupOperations.group(groupUuid).forUpdate().removeSubgroup(subgroupUuid2).update();
+
+ ImmutableSet<AccountGroup.UUID> subgroups = groupOperations.group(groupUuid).get().subgroups();
+ assertThat(subgroups).containsExactly(subgroupUuid1);
+ }
+
+ @Test
+ public void subgroupAdditionAndRemovalCanBeMixed() throws Exception {
+ AccountGroup.UUID subgroupUuid1 = new AccountGroup.UUID("subgroup 1");
+ AccountGroup.UUID subgroupUuid2 = new AccountGroup.UUID("subgroup 2");
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().subgroups(subgroupUuid1, subgroupUuid2).create();
+
+ AccountGroup.UUID subgroupUuid3 = new AccountGroup.UUID("subgroup 3");
+ groupOperations
+ .group(groupUuid)
+ .forUpdate()
+ .removeSubgroup(subgroupUuid1)
+ .addSubgroup(subgroupUuid3)
+ .update();
+
+ ImmutableSet<AccountGroup.UUID> subgroups = groupOperations.group(groupUuid).get().subgroups();
+ assertThat(subgroups).containsExactly(subgroupUuid2, subgroupUuid3);
+ }
+
+ @Test
+ public void subgroupsCanBeCleared() throws Exception {
+ AccountGroup.UUID subgroupUuid1 = new AccountGroup.UUID("subgroup 1");
+ AccountGroup.UUID subgroupUuid2 = new AccountGroup.UUID("subgroup 2");
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().subgroups(subgroupUuid1, subgroupUuid2).create();
+
+ groupOperations.group(groupUuid).forUpdate().clearSubgroups().update();
+
+ ImmutableSet<AccountGroup.UUID> subgroups = groupOperations.group(groupUuid).get().subgroups();
+ assertThat(subgroups).isEmpty();
+ }
+
+ @Test
+ public void furtherSubgroupsCanBeAddedAfterClearingAll() throws Exception {
+ AccountGroup.UUID subgroupUuid1 = new AccountGroup.UUID("subgroup 1");
+ AccountGroup.UUID subgroupUuid2 = new AccountGroup.UUID("subgroup 2");
+ AccountGroup.UUID groupUuid =
+ groupOperations.newGroup().subgroups(subgroupUuid1, subgroupUuid2).create();
+
+ AccountGroup.UUID subgroupUuid3 = new AccountGroup.UUID("subgroup 3");
+ groupOperations
+ .group(groupUuid)
+ .forUpdate()
+ .clearSubgroups()
+ .addSubgroup(subgroupUuid3)
+ .update();
+
+ ImmutableSet<AccountGroup.UUID> subgroups = groupOperations.group(groupUuid).get().subgroups();
+ assertThat(subgroups).containsExactly(subgroupUuid3);
+ }
+
+ private GroupInput createArbitraryGroupInput() {
+ GroupInput groupInput = new GroupInput();
+ groupInput.name = name("verifiers-" + uniqueGroupNameIndex++);
+ return groupInput;
+ }
+
+ private GroupInfo getGroupFromServer(AccountGroup.UUID groupUuid) throws RestApiException {
+ return gApi.groups().id(groupUuid.get()).detail();
+ }
+
+ private AccountGroup.UUID createGroupInServer(GroupInput input) throws RestApiException {
+ GroupInfo group = gApi.groups().create(input).detail();
+ return new AccountGroup.UUID(group.id);
+ }
+
+ private static Correspondence<AccountInfo, Account.Id> getAccountToIdCorrespondence() {
+ return new Correspondence<AccountInfo, Account.Id>() {
+ @Override
+ public boolean compare(AccountInfo actualAccount, Account.Id expectedId) {
+ Account.Id accountId =
+ Optional.ofNullable(actualAccount)
+ .map(account -> account._accountId)
+ .map(Account.Id::new)
+ .orElse(null);
+ return Objects.equals(accountId, expectedId);
+ }
+
+ @Override
+ public String toString() {
+ return "has ID";
+ }
+ };
+ }
+
+ private static Correspondence<GroupInfo, AccountGroup.UUID> getGroupToUuidCorrespondence() {
+ return new Correspondence<GroupInfo, AccountGroup.UUID>() {
+ @Override
+ public boolean compare(GroupInfo actualGroup, AccountGroup.UUID expectedUuid) {
+ AccountGroup.UUID groupUuid =
+ Optional.ofNullable(actualGroup)
+ .map(group -> group.id)
+ .map(AccountGroup.UUID::new)
+ .orElse(null);
+ return Objects.equals(groupUuid, expectedUuid);
+ }
+
+ @Override
+ public String toString() {
+ return "has UUID";
+ }
+ };
+ }
+}
diff --git a/javatests/com/google/gerrit/common/data/LabelTypeTest.java b/javatests/com/google/gerrit/common/data/LabelTypeTest.java
new file mode 100644
index 0000000..6c3befb
--- /dev/null
+++ b/javatests/com/google/gerrit/common/data/LabelTypeTest.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.common.data;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class LabelTypeTest {
+ @Test
+ public void sortLabelValues() {
+ LabelValue v0 = new LabelValue((short) 0, "Zero");
+ LabelValue v1 = new LabelValue((short) 1, "One");
+ LabelValue v2 = new LabelValue((short) 2, "Two");
+ LabelType types = new LabelType("Label", ImmutableList.of(v2, v0, v1));
+ assertThat(types.getValues()).containsExactly(v0, v1, v2).inOrder();
+ }
+
+ @Test
+ public void insertMissingLabelValues() {
+ LabelValue v0 = new LabelValue((short) 0, "Zero");
+ LabelValue v2 = new LabelValue((short) 2, "Two");
+ LabelValue v5 = new LabelValue((short) 5, "Five");
+ LabelType types = new LabelType("Label", ImmutableList.of(v2, v5, v0));
+ assertThat(types.getValues())
+ .containsExactly(
+ v0,
+ new LabelValue((short) 1, ""),
+ v2,
+ new LabelValue((short) 3, ""),
+ new LabelValue((short) 4, ""),
+ v5)
+ .inOrder();
+ }
+}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
index f5614b4..8b3c08f 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -45,11 +45,13 @@
case V2_4:
return "elasticsearch:2.4.6-alpine";
case V5_6:
- return "docker.elastic.co/elasticsearch/elasticsearch:5.6.10";
+ return "docker.elastic.co/elasticsearch/elasticsearch:5.6.11";
case V6_2:
return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.2.4";
case V6_3:
return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.3.2";
+ case V6_4:
+ return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.0";
}
throw new IllegalStateException("No tests for version: " + version.name());
}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
index 1d17b5b..b8154ce 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
@@ -41,7 +41,7 @@
return;
}
- container = ElasticContainer.createAndStart(ElasticVersion.V6_3);
+ container = ElasticContainer.createAndStart(ElasticVersion.V6_4);
nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
index 7c5d2e2..3445b36 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
@@ -41,7 +41,7 @@
return;
}
- container = ElasticContainer.createAndStart(ElasticVersion.V6_3);
+ container = ElasticContainer.createAndStart(ElasticVersion.V6_4);
nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
index 15b58e0..851b27d 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
@@ -41,7 +41,7 @@
return;
}
- container = ElasticContainer.createAndStart(ElasticVersion.V6_3);
+ container = ElasticContainer.createAndStart(ElasticVersion.V6_4);
nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
index 860dca6..b598a0a 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
@@ -29,21 +29,31 @@
assertThat(ElasticVersion.forVersion("2.4.6")).isEqualTo(ElasticVersion.V2_4);
assertThat(ElasticVersion.forVersion("5.6.0")).isEqualTo(ElasticVersion.V5_6);
- assertThat(ElasticVersion.forVersion("5.6.9")).isEqualTo(ElasticVersion.V5_6);
- assertThat(ElasticVersion.forVersion("5.6.10")).isEqualTo(ElasticVersion.V5_6);
+ assertThat(ElasticVersion.forVersion("5.6.11")).isEqualTo(ElasticVersion.V5_6);
assertThat(ElasticVersion.forVersion("6.2.0")).isEqualTo(ElasticVersion.V6_2);
assertThat(ElasticVersion.forVersion("6.2.4")).isEqualTo(ElasticVersion.V6_2);
assertThat(ElasticVersion.forVersion("6.3.0")).isEqualTo(ElasticVersion.V6_3);
- assertThat(ElasticVersion.forVersion("6.3.1")).isEqualTo(ElasticVersion.V6_3);
+ assertThat(ElasticVersion.forVersion("6.3.2")).isEqualTo(ElasticVersion.V6_3);
+
+ assertThat(ElasticVersion.forVersion("6.4.0")).isEqualTo(ElasticVersion.V6_4);
+ assertThat(ElasticVersion.forVersion("6.4.1")).isEqualTo(ElasticVersion.V6_4);
}
@Test
public void unsupportedVersion() throws Exception {
- exception.expect(ElasticVersion.InvalidVersion.class);
+ exception.expect(ElasticVersion.UnsupportedVersion.class);
exception.expectMessage(
- "Invalid version: [4.0.0]. Supported versions: " + ElasticVersion.supportedVersions());
+ "Unsupported version: [4.0.0]. Supported versions: " + ElasticVersion.supportedVersions());
ElasticVersion.forVersion("4.0.0");
}
+
+ @Test
+ public void version6() throws Exception {
+ assertThat(ElasticVersion.V6_2.isV6()).isTrue();
+ assertThat(ElasticVersion.V6_3.isV6()).isTrue();
+ assertThat(ElasticVersion.V6_4.isV6()).isTrue();
+ assertThat(ElasticVersion.V5_6.isV6()).isFalse();
+ }
}
diff --git a/javatests/com/google/gerrit/extensions/registration/DynamicSetTest.java b/javatests/com/google/gerrit/extensions/registration/DynamicSetTest.java
index 117e474..c86160f 100644
--- a/javatests/com/google/gerrit/extensions/registration/DynamicSetTest.java
+++ b/javatests/com/google/gerrit/extensions/registration/DynamicSetTest.java
@@ -15,9 +15,12 @@
package com.google.gerrit.extensions.registration;
import static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.toSet;
import com.google.inject.Key;
+import com.google.inject.Provider;
import com.google.inject.util.Providers;
+import java.util.Iterator;
import org.junit.Test;
public class DynamicSetTest {
@@ -40,7 +43,7 @@
@Test
public void containsTrueWithSingleElement() throws Exception {
DynamicSet<Integer> ds = new DynamicSet<>();
- ds.add(2);
+ ds.add("gerrit", 2);
assertThat(ds.contains(2)).isTrue(); // See above comment about ds.contains
}
@@ -48,7 +51,7 @@
@Test
public void containsFalseWithSingleElement() throws Exception {
DynamicSet<Integer> ds = new DynamicSet<>();
- ds.add(2);
+ ds.add("gerrit", 2);
assertThat(ds.contains(3)).isFalse(); // See above comment about ds.contains
}
@@ -56,8 +59,8 @@
@Test
public void containsTrueWithTwoElements() throws Exception {
DynamicSet<Integer> ds = new DynamicSet<>();
- ds.add(2);
- ds.add(4);
+ ds.add("gerrit", 2);
+ ds.add("gerrit", 4);
assertThat(ds.contains(4)).isTrue(); // See above comment about ds.contains
}
@@ -65,8 +68,8 @@
@Test
public void containsFalseWithTwoElements() throws Exception {
DynamicSet<Integer> ds = new DynamicSet<>();
- ds.add(2);
- ds.add(4);
+ ds.add("gerrit", 2);
+ ds.add("gerrit", 4);
assertThat(ds.contains(3)).isFalse(); // See above comment about ds.contains
}
@@ -74,12 +77,12 @@
@Test
public void containsDynamic() throws Exception {
DynamicSet<Integer> ds = new DynamicSet<>();
- ds.add(2);
+ ds.add("gerrit", 2);
Key<Integer> key = Key.get(Integer.class);
- ReloadableRegistrationHandle<Integer> handle = ds.add(key, Providers.of(4));
+ ReloadableRegistrationHandle<Integer> handle = ds.add("gerrit", key, Providers.of(4));
- ds.add(6);
+ ds.add("gerrit", 6);
// At first, 4 is contained.
assertThat(ds.contains(4)).isTrue(); // See above comment about ds.contains
@@ -90,4 +93,49 @@
// And now 4 should no longer be contained.
assertThat(ds.contains(4)).isFalse(); // See above comment about ds.contains
}
+
+ @Test
+ public void plugins() {
+ DynamicSet<Integer> ds = new DynamicSet<>();
+ ds.add("foo", 1);
+ ds.add("bar", 2);
+ ds.add("bar", 3);
+
+ assertThat(ds.plugins()).containsExactly("bar", "foo").inOrder();
+ }
+
+ @Test
+ public void byPlugin() {
+ DynamicSet<Integer> ds = new DynamicSet<>();
+ ds.add("foo", 1);
+ ds.add("bar", 2);
+ ds.add("bar", 3);
+
+ assertThat(ds.byPlugin("foo").stream().map(Provider::get).collect(toSet())).containsExactly(1);
+ assertThat(ds.byPlugin("bar").stream().map(Provider::get).collect(toSet()))
+ .containsExactly(2, 3);
+ }
+
+ @Test
+ public void entryIterator() {
+ DynamicSet<Integer> ds = new DynamicSet<>();
+ ds.add("foo", 1);
+ ds.add("bar", 2);
+ ds.add("bar", 3);
+
+ Iterator<DynamicSet.Entry<Integer>> entryIterator = ds.entries().iterator();
+ DynamicSet.Entry<Integer> next = entryIterator.next();
+ assertThat(next.getPluginName()).isEqualTo("foo");
+ assertThat(next.getProvider().get()).isEqualTo(1);
+
+ next = entryIterator.next();
+ assertThat(next.getPluginName()).isEqualTo("bar");
+ assertThat(next.getProvider().get()).isEqualTo(2);
+
+ next = entryIterator.next();
+ assertThat(next.getPluginName()).isEqualTo("bar");
+ assertThat(next.getProvider().get()).isEqualTo(3);
+
+ assertThat(entryIterator.hasNext()).isFalse();
+ }
}
diff --git a/javatests/com/google/gerrit/gpg/PushCertificateCheckerTest.java b/javatests/com/google/gerrit/gpg/PushCertificateCheckerTest.java
index ad8f4311..266f868 100644
--- a/javatests/com/google/gerrit/gpg/PushCertificateCheckerTest.java
+++ b/javatests/com/google/gerrit/gpg/PushCertificateCheckerTest.java
@@ -184,7 +184,7 @@
}
String cert = payload + new String(bout.toByteArray(), UTF_8);
- Reader reader = new InputStreamReader(new ByteArrayInputStream(cert.getBytes(UTF_8)));
+ Reader reader = new InputStreamReader(new ByteArrayInputStream(cert.getBytes(UTF_8)), UTF_8);
PushCertificateParser parser = new PushCertificateParser(repo, signedPushConfig);
return parser.parse(reader);
}
diff --git a/javatests/com/google/gerrit/httpd/AllRequestFilterFilterProxyTest.java b/javatests/com/google/gerrit/httpd/AllRequestFilterFilterProxyTest.java
index 086dcc2..1c6559b0 100644
--- a/javatests/com/google/gerrit/httpd/AllRequestFilterFilterProxyTest.java
+++ b/javatests/com/google/gerrit/httpd/AllRequestFilterFilterProxyTest.java
@@ -76,7 +76,7 @@
*/
private ReloadableRegistrationHandle<AllRequestFilter> addFilter(AllRequestFilter filter) {
Key<AllRequestFilter> key = Key.get(AllRequestFilter.class);
- return filters.add(key, Providers.of(filter));
+ return filters.add("gerrit", key, Providers.of(filter));
}
@Test
diff --git a/javatests/com/google/gerrit/httpd/raw/IndexServletTest.java b/javatests/com/google/gerrit/httpd/raw/IndexServletTest.java
index edafeb3..307a23e 100644
--- a/javatests/com/google/gerrit/httpd/raw/IndexServletTest.java
+++ b/javatests/com/google/gerrit/httpd/raw/IndexServletTest.java
@@ -15,13 +15,14 @@
package com.google.gerrit.httpd.raw;
import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.template.soy.data.SoyMapData;
import java.net.URISyntaxException;
import org.junit.Test;
public class IndexServletTest {
- class TestIndexServlet extends IndexServlet {
+ static class TestIndexServlet extends IndexServlet {
private static final long serialVersionUID = 1L;
TestIndexServlet(String canonicalURL, String cdnPath, String faviconPath)
@@ -30,7 +31,7 @@
}
String getIndexSource() {
- return new String(indexSource);
+ return new String(indexSource, UTF_8);
}
}
diff --git a/javatests/com/google/gerrit/index/BUILD b/javatests/com/google/gerrit/index/BUILD
index 14a7048..5597ed1 100644
--- a/javatests/com/google/gerrit/index/BUILD
+++ b/javatests/com/google/gerrit/index/BUILD
@@ -6,9 +6,9 @@
srcs = glob(["**/*.java"]),
visibility = ["//visibility:public"],
deps = [
+ "//antlr3:query_parser",
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/index:query_exception",
- "//java/com/google/gerrit/index:query_parser",
"//lib:guava",
"//lib:junit",
"//lib/antlr:java-runtime",
diff --git a/javatests/com/google/gerrit/mail/BUILD b/javatests/com/google/gerrit/mail/BUILD
index 488bbcc..2fd8f24 100644
--- a/javatests/com/google/gerrit/mail/BUILD
+++ b/javatests/com/google/gerrit/mail/BUILD
@@ -22,7 +22,6 @@
"//java/com/google/gerrit/server/project/testing:project-test-util",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//java/org/eclipse/jgit:server",
- "//lib:grappa",
"//lib:gson",
"//lib:guava-retrying",
"//lib:gwtorm",
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index 18d9c71..7be1827 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -45,16 +45,17 @@
"//java/com/google/gerrit/metrics",
"//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/cache/serialize",
"//java/com/google/gerrit/server/cache/testing",
"//java/com/google/gerrit/server/group/testing",
"//java/com/google/gerrit/server/ioutil",
+ "//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/server/project/testing:project-test-util",
"//java/com/google/gerrit/server/restapi",
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//java/com/google/gerrit/truth",
"//java/org/eclipse/jgit:server",
- "//lib:grappa",
"//lib:gson",
"//lib:guava",
"//lib:guava-retrying",
@@ -63,6 +64,7 @@
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
"//lib/commons:codec",
+ "//lib/flogger:api",
"//lib/guice",
"//lib/jgit/org.eclipse.jgit:jgit",
"//lib/jgit/org.eclipse.jgit.junit:junit",
diff --git a/javatests/com/google/gerrit/server/account/UniversalGroupBackendTest.java b/javatests/com/google/gerrit/server/account/UniversalGroupBackendTest.java
index 91cc2b7..6dd0f3e 100644
--- a/javatests/com/google/gerrit/server/account/UniversalGroupBackendTest.java
+++ b/javatests/com/google/gerrit/server/account/UniversalGroupBackendTest.java
@@ -55,7 +55,7 @@
user = createNiceMock(IdentifiedUser.class);
replay(user);
backends = new DynamicSet<>();
- backends.add(new SystemGroupBackend(new Config()));
+ backends.add("gerrit", new SystemGroupBackend(new Config()));
backend = new UniversalGroupBackend(backends);
}
@@ -123,7 +123,7 @@
replay(member, notMember, backend);
backends = new DynamicSet<>();
- backends.add(backend);
+ backends.add("gerrit", backend);
backend = new UniversalGroupBackend(backends);
GroupMembership checker = backend.membershipsOf(member);
diff --git a/javatests/com/google/gerrit/server/account/externalids/AllExternalIdsTest.java b/javatests/com/google/gerrit/server/account/externalids/AllExternalIdsTest.java
new file mode 100644
index 0000000..f29ff1f
--- /dev/null
+++ b/javatests/com/google/gerrit/server/account/externalids/AllExternalIdsTest.java
@@ -0,0 +1,143 @@
+// Copyright (C) 2018 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.account.externalids;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
+import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.byteString;
+import static com.google.gerrit.server.cache.testing.SerializedClassSubject.assertThatSerializedClass;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.account.externalids.AllExternalIds.Serializer;
+import com.google.gerrit.server.cache.proto.Cache.AllExternalIdsProto;
+import com.google.gerrit.server.cache.proto.Cache.AllExternalIdsProto.ExternalIdProto;
+import com.google.inject.TypeLiteral;
+import java.util.Arrays;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Test;
+
+public class AllExternalIdsTest {
+ @Test
+ public void serializeEmptyExternalIds() throws Exception {
+ assertRoundTrip(allExternalIds(), AllExternalIdsProto.getDefaultInstance());
+ }
+
+ @Test
+ public void serializeMultipleExternalIds() throws Exception {
+ Account.Id accountId1 = new Account.Id(1001);
+ Account.Id accountId2 = new Account.Id(1002);
+ assertRoundTrip(
+ allExternalIds(
+ ExternalId.create("scheme1", "id1", accountId1),
+ ExternalId.create("scheme2", "id2", accountId1),
+ ExternalId.create("scheme2", "id3", accountId2),
+ ExternalId.create("scheme3", "id4", accountId2)),
+ AllExternalIdsProto.newBuilder()
+ .addExternalId(
+ ExternalIdProto.newBuilder().setKey("scheme1:id1").setAccountId(1001).build())
+ .addExternalId(
+ ExternalIdProto.newBuilder().setKey("scheme2:id2").setAccountId(1001).build())
+ .addExternalId(
+ ExternalIdProto.newBuilder().setKey("scheme2:id3").setAccountId(1002).build())
+ .addExternalId(
+ ExternalIdProto.newBuilder().setKey("scheme3:id4").setAccountId(1002).build())
+ .build());
+ }
+
+ @Test
+ public void serializeExternalIdWithEmail() throws Exception {
+ assertRoundTrip(
+ allExternalIds(ExternalId.createEmail(new Account.Id(1001), "foo@example.com")),
+ AllExternalIdsProto.newBuilder()
+ .addExternalId(
+ ExternalIdProto.newBuilder()
+ .setKey("mailto:foo@example.com")
+ .setAccountId(1001)
+ .setEmail("foo@example.com"))
+ .build());
+ }
+
+ @Test
+ public void serializeExternalIdWithPassword() throws Exception {
+ assertRoundTrip(
+ allExternalIds(
+ ExternalId.create("scheme", "id", new Account.Id(1001), null, "hashed password")),
+ AllExternalIdsProto.newBuilder()
+ .addExternalId(
+ ExternalIdProto.newBuilder()
+ .setKey("scheme:id")
+ .setAccountId(1001)
+ .setPassword("hashed password"))
+ .build());
+ }
+
+ @Test
+ public void serializeExternalIdWithBlobId() throws Exception {
+ assertRoundTrip(
+ allExternalIds(
+ ExternalId.create(
+ ExternalId.create("scheme", "id", new Account.Id(1001)),
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"))),
+ AllExternalIdsProto.newBuilder()
+ .addExternalId(
+ ExternalIdProto.newBuilder()
+ .setKey("scheme:id")
+ .setAccountId(1001)
+ .setBlobId(
+ byteString(
+ 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef)))
+ .build());
+ }
+
+ @Test
+ public void allExternalIdsMethods() {
+ assertThatSerializedClass(AllExternalIds.class)
+ .hasAutoValueMethods(
+ ImmutableMap.of(
+ "byAccount",
+ new TypeLiteral<ImmutableSetMultimap<Account.Id, ExternalId>>() {}.getType(),
+ "byEmail",
+ new TypeLiteral<ImmutableSetMultimap<String, ExternalId>>() {}.getType()));
+ }
+
+ @Test
+ public void externalIdMethods() {
+ assertThatSerializedClass(ExternalId.class)
+ .hasAutoValueMethods(
+ ImmutableMap.of(
+ "key", ExternalId.Key.class,
+ "accountId", Account.Id.class,
+ "email", String.class,
+ "password", String.class,
+ "blobId", ObjectId.class));
+ }
+
+ private static AllExternalIds allExternalIds(ExternalId... externalIds) {
+ return AllExternalIds.create(Arrays.asList(externalIds));
+ }
+
+ private static void assertRoundTrip(
+ AllExternalIds allExternalIds, AllExternalIdsProto expectedProto) throws Exception {
+ AllExternalIdsProto actualProto =
+ AllExternalIdsProto.parseFrom(Serializer.INSTANCE.serialize(allExternalIds));
+ assertThat(actualProto).ignoringRepeatedFieldOrder().isEqualTo(expectedProto);
+ AllExternalIds actual =
+ Serializer.INSTANCE.deserialize(Serializer.INSTANCE.serialize(allExternalIds));
+ assertThat(actual).isEqualTo(allExternalIds);
+ }
+}
diff --git a/javatests/com/google/gerrit/server/auth/oauth/OAuthTokenCacheTest.java b/javatests/com/google/gerrit/server/auth/oauth/OAuthTokenCacheTest.java
index 5e93a09..81fd6d7 100644
--- a/javatests/com/google/gerrit/server/auth/oauth/OAuthTokenCacheTest.java
+++ b/javatests/com/google/gerrit/server/auth/oauth/OAuthTokenCacheTest.java
@@ -6,8 +6,8 @@
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.extensions.auth.oauth.OAuthToken;
-import com.google.gerrit.server.cache.CacheSerializer;
import com.google.gerrit.server.cache.proto.Cache.OAuthTokenProto;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
import java.lang.reflect.Type;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/javatests/com/google/gerrit/server/cache/BUILD b/javatests/com/google/gerrit/server/cache/BUILD
index ab88169..4950266 100644
--- a/javatests/com/google/gerrit/server/cache/BUILD
+++ b/javatests/com/google/gerrit/server/cache/BUILD
@@ -5,16 +5,7 @@
srcs = glob(["*.java"]),
deps = [
"//java/com/google/gerrit/server",
- "//java/com/google/gerrit/server/cache/testing",
- "//lib:guava",
- "//lib:gwtorm",
"//lib:junit",
- "//lib:protobuf",
- "//lib/auto:auto-value",
- "//lib/auto:auto-value-annotations",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/truth",
- "//lib/truth:truth-proto-extension",
- "//proto:cache_java_proto",
],
)
diff --git a/javatests/com/google/gerrit/server/cache/h2/BUILD b/javatests/com/google/gerrit/server/cache/h2/BUILD
index 63ae94b..2ee8e48 100644
--- a/javatests/com/google/gerrit/server/cache/h2/BUILD
+++ b/javatests/com/google/gerrit/server/cache/h2/BUILD
@@ -6,6 +6,7 @@
deps = [
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/cache/h2",
+ "//java/com/google/gerrit/server/cache/serialize",
"//lib:guava",
"//lib:h2",
"//lib:junit",
diff --git a/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java b/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java
index 9bba996..147aeeb 100644
--- a/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java
+++ b/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java
@@ -19,9 +19,9 @@
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.MoreExecutors;
-import com.google.gerrit.server.cache.StringSerializer;
import com.google.gerrit.server.cache.h2.H2CacheImpl.SqlStore;
import com.google.gerrit.server.cache.h2.H2CacheImpl.ValueHolder;
+import com.google.gerrit.server.cache.serialize.StringCacheSerializer;
import com.google.inject.TypeLiteral;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -42,8 +42,8 @@
new SqlStore<>(
"jdbc:h2:mem:Test_" + id,
KEY_TYPE,
- StringSerializer.INSTANCE,
- StringSerializer.INSTANCE,
+ StringCacheSerializer.INSTANCE,
+ StringCacheSerializer.INSTANCE,
version,
1 << 20,
null);
@@ -87,9 +87,9 @@
@Test
public void stringSerializer() {
String input = "foo";
- byte[] serialized = StringSerializer.INSTANCE.serialize(input);
+ byte[] serialized = StringCacheSerializer.INSTANCE.serialize(input);
assertThat(serialized).isEqualTo(new byte[] {'f', 'o', 'o'});
- assertThat(StringSerializer.INSTANCE.deserialize(serialized)).isEqualTo(input);
+ assertThat(StringCacheSerializer.INSTANCE.deserialize(serialized)).isEqualTo(input);
}
@Test
diff --git a/javatests/com/google/gerrit/server/cache/serialize/BUILD b/javatests/com/google/gerrit/server/cache/serialize/BUILD
new file mode 100644
index 0000000..35d8527
--- /dev/null
+++ b/javatests/com/google/gerrit/server/cache/serialize/BUILD
@@ -0,0 +1,20 @@
+load("//tools/bzl:junit.bzl", "junit_tests")
+
+junit_tests(
+ name = "tests",
+ srcs = glob(["*.java"]),
+ deps = [
+ "//java/com/google/gerrit/server/cache/serialize",
+ "//java/com/google/gerrit/server/cache/testing",
+ "//lib:guava",
+ "//lib:gwtorm",
+ "//lib:junit",
+ "//lib:protobuf",
+ "//lib/auto:auto-value",
+ "//lib/auto:auto-value-annotations",
+ "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib/truth",
+ "//lib/truth:truth-proto-extension",
+ "//proto:cache_java_proto",
+ ],
+)
diff --git a/javatests/com/google/gerrit/server/cache/BooleanCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/BooleanCacheSerializerTest.java
similarity index 97%
rename from javatests/com/google/gerrit/server/cache/BooleanCacheSerializerTest.java
rename to javatests/com/google/gerrit/server/cache/serialize/BooleanCacheSerializerTest.java
index 3186620..7504850 100644
--- a/javatests/com/google/gerrit/server/cache/BooleanCacheSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/BooleanCacheSerializerTest.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assert_;
diff --git a/javatests/com/google/gerrit/server/cache/EnumCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/EnumCacheSerializerTest.java
similarity index 97%
rename from javatests/com/google/gerrit/server/cache/EnumCacheSerializerTest.java
rename to javatests/com/google/gerrit/server/cache/serialize/EnumCacheSerializerTest.java
index 60bbb16..0b80fc7 100644
--- a/javatests/com/google/gerrit/server/cache/EnumCacheSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/EnumCacheSerializerTest.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assert_;
diff --git a/javatests/com/google/gerrit/server/cache/IntKeyCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/IntKeyCacheSerializerTest.java
similarity index 97%
rename from javatests/com/google/gerrit/server/cache/IntKeyCacheSerializerTest.java
rename to javatests/com/google/gerrit/server/cache/serialize/IntKeyCacheSerializerTest.java
index 7a7c27c..987a62a 100644
--- a/javatests/com/google/gerrit/server/cache/IntKeyCacheSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/IntKeyCacheSerializerTest.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assert_;
diff --git a/javatests/com/google/gerrit/server/cache/IntegerCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/IntegerCacheSerializerTest.java
similarity index 97%
rename from javatests/com/google/gerrit/server/cache/IntegerCacheSerializerTest.java
rename to javatests/com/google/gerrit/server/cache/serialize/IntegerCacheSerializerTest.java
index 962b797..c2db808 100644
--- a/javatests/com/google/gerrit/server/cache/IntegerCacheSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/IntegerCacheSerializerTest.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assert_;
diff --git a/javatests/com/google/gerrit/server/cache/JavaCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/JavaCacheSerializerTest.java
similarity index 96%
rename from javatests/com/google/gerrit/server/cache/JavaCacheSerializerTest.java
rename to javatests/com/google/gerrit/server/cache/serialize/JavaCacheSerializerTest.java
index 41d07b9..6596730 100644
--- a/javatests/com/google/gerrit/server/cache/JavaCacheSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/JavaCacheSerializerTest.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
import static com.google.common.truth.Truth.assertThat;
diff --git a/javatests/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializerTest.java
new file mode 100644
index 0000000..c56f8f8
--- /dev/null
+++ b/javatests/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializerTest.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2018 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.cache.serialize;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.byteArray;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Test;
+
+public class ObjectIdCacheSerializerTest {
+ @Test
+ public void serialize() {
+ ObjectId id = ObjectId.fromString("aabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
+ byte[] serialized = ObjectIdCacheSerializer.INSTANCE.serialize(id);
+ assertThat(serialized)
+ .isEqualTo(
+ byteArray(
+ 0xaa, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
+ 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb));
+ assertThat(ObjectIdCacheSerializer.INSTANCE.deserialize(serialized)).isEqualTo(id);
+ }
+
+ @Test
+ public void deserializeInvalid() {
+ assertDeserializeFails(null);
+ assertDeserializeFails(byteArray());
+ assertDeserializeFails(byteArray(0xaa));
+ assertDeserializeFails(
+ byteArray(
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa));
+ }
+
+ private void assertDeserializeFails(byte[] bytes) {
+ try {
+ ObjectIdCacheSerializer.INSTANCE.deserialize(bytes);
+ assert_().fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/server/cache/ProtoCacheSerializersTest.java b/javatests/com/google/gerrit/server/cache/serialize/ProtoCacheSerializersTest.java
similarity index 93%
rename from javatests/com/google/gerrit/server/cache/ProtoCacheSerializersTest.java
rename to javatests/com/google/gerrit/server/cache/serialize/ProtoCacheSerializersTest.java
index 8bf9762..69694fe 100644
--- a/javatests/com/google/gerrit/server/cache/ProtoCacheSerializersTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/ProtoCacheSerializersTest.java
@@ -12,16 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assert_;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
-import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.bytes;
+import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.byteString;
-import com.google.gerrit.server.cache.ProtoCacheSerializers.ObjectIdConverter;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesKeyProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers.ObjectIdConverter;
import com.google.protobuf.ByteString;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
@@ -32,13 +32,13 @@
ObjectIdConverter idConverter = ObjectIdConverter.create();
assertThat(
idConverter.fromByteString(
- bytes(
+ byteString(
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa)))
.isEqualTo(ObjectId.fromString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
assertThat(
idConverter.fromByteString(
- bytes(
+ byteString(
0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb)))
.isEqualTo(ObjectId.fromString("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"));
@@ -61,14 +61,14 @@
idConverter.toByteString(
ObjectId.fromString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")))
.isEqualTo(
- bytes(
+ byteString(
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa));
assertThat(
idConverter.toByteString(
ObjectId.fromString("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")))
.isEqualTo(
- bytes(
+ byteString(
0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb));
}
diff --git a/javatests/com/google/gerrit/server/cache/StringSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/StringCacheSerializerTest.java
similarity index 69%
rename from javatests/com/google/gerrit/server/cache/StringSerializerTest.java
rename to javatests/com/google/gerrit/server/cache/serialize/StringCacheSerializerTest.java
index 3035338..fa3b7d7 100644
--- a/javatests/com/google/gerrit/server/cache/StringSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/StringCacheSerializerTest.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.server.cache.serialize;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assert_;
@@ -21,12 +21,13 @@
import java.nio.charset.StandardCharsets;
import org.junit.Test;
-public class StringSerializerTest {
+public class StringCacheSerializerTest {
@Test
public void serialize() {
- assertThat(StringSerializer.INSTANCE.serialize("")).isEmpty();
- assertThat(StringSerializer.INSTANCE.serialize("abc")).isEqualTo(new byte[] {'a', 'b', 'c'});
- assertThat(StringSerializer.INSTANCE.serialize("a\u1234c"))
+ assertThat(StringCacheSerializer.INSTANCE.serialize("")).isEmpty();
+ assertThat(StringCacheSerializer.INSTANCE.serialize("abc"))
+ .isEqualTo(new byte[] {'a', 'b', 'c'});
+ assertThat(StringCacheSerializer.INSTANCE.serialize("a\u1234c"))
.isEqualTo(new byte[] {'a', (byte) 0xe1, (byte) 0x88, (byte) 0xb4, 'c'});
}
@@ -34,7 +35,7 @@
public void serializeInvalidChar() {
// Can't use UTF-8 for the test, since it can encode all Unicode code points.
try {
- StringSerializer.serialize(StandardCharsets.US_ASCII, "\u1234");
+ StringCacheSerializer.serialize(StandardCharsets.US_ASCII, "\u1234");
assert_().fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
assertThat(expected).hasCauseThat().isInstanceOf(CharacterCodingException.class);
@@ -43,10 +44,11 @@
@Test
public void deserialize() {
- assertThat(StringSerializer.INSTANCE.deserialize(new byte[0])).isEmpty();
- assertThat(StringSerializer.INSTANCE.deserialize(new byte[] {'a', 'b', 'c'})).isEqualTo("abc");
+ assertThat(StringCacheSerializer.INSTANCE.deserialize(new byte[0])).isEmpty();
+ assertThat(StringCacheSerializer.INSTANCE.deserialize(new byte[] {'a', 'b', 'c'}))
+ .isEqualTo("abc");
assertThat(
- StringSerializer.INSTANCE.deserialize(
+ StringCacheSerializer.INSTANCE.deserialize(
new byte[] {'a', (byte) 0xe1, (byte) 0x88, (byte) 0xb4, 'c'}))
.isEqualTo("a\u1234c");
}
@@ -54,7 +56,7 @@
@Test
public void deserializeInvalidChar() {
try {
- StringSerializer.INSTANCE.deserialize(new byte[] {(byte) 0xff});
+ StringCacheSerializer.INSTANCE.deserialize(new byte[] {(byte) 0xff});
assert_().fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
assertThat(expected).hasCauseThat().isInstanceOf(CharacterCodingException.class);
diff --git a/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java b/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java
index 03e0d4e..b847ed7 100644
--- a/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java
+++ b/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java
@@ -16,12 +16,12 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
-import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.bytes;
+import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.byteString;
import static com.google.gerrit.server.cache.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.server.cache.CacheSerializer;
import com.google.gerrit.server.cache.proto.Cache.ChangeKindKeyProto;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
import com.google.gerrit.server.change.ChangeKindCacheImpl.Key;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
@@ -39,9 +39,9 @@
assertThat(ChangeKindKeyProto.parseFrom(serialized))
.isEqualTo(
ChangeKindKeyProto.newBuilder()
- .setPrior(bytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
+ .setPrior(byteString(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
.setNext(
- bytes(
+ byteString(
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef))
.setStrategyName("aStrategy")
diff --git a/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java b/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java
index e91c3b4..dca2dcb 100644
--- a/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java
+++ b/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java
@@ -14,10 +14,9 @@
package com.google.gerrit.server.change;
+import static com.google.common.truth.Truth.assertThat;
+
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
import org.eclipse.jgit.junit.RepositoryTestCase;
@@ -27,7 +26,6 @@
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.After;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -55,9 +53,6 @@
private RevCommit commit_v1_3;
private RevCommit commit_v2_5;
- private List<String> expTags = new ArrayList<>();
- private List<String> expBranches = new ArrayList<>();
-
private RevWalk revWalk;
@Override
@@ -140,12 +135,8 @@
IncludedInResolver.Result detail = resolve(commit_v2_5);
// Check that only tags and branches which refer the tip are returned
- expTags.add(TAG_2_5);
- expTags.add(TAG_2_5_ANNOTATED);
- expTags.add(TAG_2_5_ANNOTATED_TWICE);
- assertEquals(expTags, detail.getTags());
- expBranches.add(BRANCH_2_5);
- assertEquals(expBranches, detail.getBranches());
+ assertThat(detail.tags()).containsExactly(TAG_2_5, TAG_2_5_ANNOTATED, TAG_2_5_ANNOTATED_TWICE);
+ assertThat(detail.branches()).containsExactly(BRANCH_2_5);
}
@Test
@@ -154,22 +145,18 @@
IncludedInResolver.Result detail = resolve(commit_initial);
// Check whether all tags and branches are returned
- expTags.add(TAG_1_0);
- expTags.add(TAG_1_0_1);
- expTags.add(TAG_1_3);
- expTags.add(TAG_2_0);
- expTags.add(TAG_2_0_1);
- expTags.add(TAG_2_5);
- expTags.add(TAG_2_5_ANNOTATED);
- expTags.add(TAG_2_5_ANNOTATED_TWICE);
- assertEquals(expTags, detail.getTags());
-
- expBranches.add(BRANCH_MASTER);
- expBranches.add(BRANCH_1_0);
- expBranches.add(BRANCH_1_3);
- expBranches.add(BRANCH_2_0);
- expBranches.add(BRANCH_2_5);
- assertEquals(expBranches, detail.getBranches());
+ assertThat(detail.tags())
+ .containsExactly(
+ TAG_1_0,
+ TAG_1_0_1,
+ TAG_1_3,
+ TAG_2_0,
+ TAG_2_0_1,
+ TAG_2_5,
+ TAG_2_5_ANNOTATED,
+ TAG_2_5_ANNOTATED_TWICE);
+ assertThat(detail.branches())
+ .containsExactly(BRANCH_MASTER, BRANCH_1_0, BRANCH_1_3, BRANCH_2_0, BRANCH_2_5);
}
@Test
@@ -178,27 +165,15 @@
IncludedInResolver.Result detail = resolve(commit_v1_3);
// Check whether all succeeding tags and branches are returned
- expTags.add(TAG_1_3);
- expTags.add(TAG_2_5);
- expTags.add(TAG_2_5_ANNOTATED);
- expTags.add(TAG_2_5_ANNOTATED_TWICE);
- assertEquals(expTags, detail.getTags());
-
- expBranches.add(BRANCH_1_3);
- expBranches.add(BRANCH_2_5);
- assertEquals(expBranches, detail.getBranches());
+ assertThat(detail.tags())
+ .containsExactly(TAG_1_3, TAG_2_5, TAG_2_5_ANNOTATED, TAG_2_5_ANNOTATED_TWICE);
+ assertThat(detail.branches()).containsExactly(BRANCH_1_3, BRANCH_2_5);
}
private IncludedInResolver.Result resolve(RevCommit commit) throws Exception {
return IncludedInResolver.resolve(db, revWalk, commit);
}
- private void assertEquals(List<String> list1, List<String> list2) {
- Collections.sort(list1);
- Collections.sort(list2);
- Assert.assertEquals(list1, list2);
- }
-
private void createAndCheckoutBranch(ObjectId objectId, String branchName) throws IOException {
String fullBranchName = "refs/heads/" + branchName;
super.createBranch(objectId, fullBranchName);
diff --git a/javatests/com/google/gerrit/server/change/MergeabilityCacheImplTest.java b/javatests/com/google/gerrit/server/change/MergeabilityCacheImplTest.java
index c8e6f2b..e10a236 100644
--- a/javatests/com/google/gerrit/server/change/MergeabilityCacheImplTest.java
+++ b/javatests/com/google/gerrit/server/change/MergeabilityCacheImplTest.java
@@ -16,7 +16,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
-import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.bytes;
+import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.byteString;
import static com.google.gerrit.server.cache.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
@@ -39,11 +39,11 @@
.isEqualTo(
MergeabilityKeyProto.newBuilder()
.setCommit(
- bytes(
+ byteString(
0xba, 0xdc, 0x0f, 0xee, 0xba, 0xdc, 0x0f, 0xee, 0xba, 0xdc, 0x0f, 0xee,
0xba, 0xdc, 0x0f, 0xee, 0xba, 0xdc, 0x0f, 0xee))
.setInto(
- bytes(
+ byteString(
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef))
.setSubmitType("MERGE_IF_NECESSARY")
diff --git a/javatests/com/google/gerrit/server/git/TagSetHolderTest.java b/javatests/com/google/gerrit/server/git/TagSetHolderTest.java
index b9bac50..3d3e734 100644
--- a/javatests/com/google/gerrit/server/git/TagSetHolderTest.java
+++ b/javatests/com/google/gerrit/server/git/TagSetHolderTest.java
@@ -16,7 +16,9 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
+import static com.google.gerrit.server.cache.testing.SerializedClassSubject.assertThatSerializedClass;
+import com.google.common.collect.ImmutableMap;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.cache.proto.Cache.TagSetHolderProto;
import org.junit.Test;
@@ -54,4 +56,14 @@
assertThat(deserialized.getProjectName()).isEqualTo(holder.getProjectName());
TagSetTest.assertEqual(holder.getTagSet(), deserialized.getTagSet());
}
+
+ @Test
+ public void fields() {
+ assertThatSerializedClass(TagSetHolder.class)
+ .hasFields(
+ ImmutableMap.of(
+ "buildLock", Object.class,
+ "projectName", Project.NameKey.class,
+ "tags", TagSet.class));
+ }
}
diff --git a/javatests/com/google/gerrit/server/git/TagSetTest.java b/javatests/com/google/gerrit/server/git/TagSetTest.java
index 2591c3d..1eebe75 100644
--- a/javatests/com/google/gerrit/server/git/TagSetTest.java
+++ b/javatests/com/google/gerrit/server/git/TagSetTest.java
@@ -17,8 +17,10 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
-import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.bytes;
+import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.byteString;
+import static com.google.gerrit.server.cache.testing.SerializedClassSubject.assertThatSerializedClass;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Streams;
import com.google.gerrit.common.Nullable;
@@ -28,11 +30,14 @@
import com.google.gerrit.server.cache.proto.Cache.TagSetHolderProto.TagSetProto.TagProto;
import com.google.gerrit.server.git.TagSet.CachedRef;
import com.google.gerrit.server.git.TagSet.Tag;
+import com.google.inject.TypeLiteral;
+import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdOwnerMap;
import org.junit.Test;
@@ -66,7 +71,7 @@
"refs/heads/master",
CachedRefProto.newBuilder()
.setId(
- bytes(
+ byteString(
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa))
.setFlag(1)
@@ -75,7 +80,7 @@
"refs/heads/branch",
CachedRefProto.newBuilder()
.setId(
- bytes(
+ byteString(
0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb))
.setFlag(2)
@@ -83,24 +88,60 @@
.addTag(
TagProto.newBuilder()
.setId(
- bytes(
+ byteString(
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc))
- .setFlags(bytes(0x2a))
+ .setFlags(byteString(0x2a))
.build())
.addTag(
TagProto.newBuilder()
.setId(
- bytes(
+ byteString(
0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd))
- .setFlags(bytes(0x54))
+ .setFlags(byteString(0x54))
.build())
.build());
assertEqual(tagSet, TagSet.fromProto(proto));
}
+ @Test
+ public void tagSetFields() {
+ assertThatSerializedClass(TagSet.class)
+ .hasFields(
+ ImmutableMap.of(
+ "projectName", Project.NameKey.class,
+ "refs", new TypeLiteral<Map<String, CachedRef>>() {}.getType(),
+ "tags", new TypeLiteral<ObjectIdOwnerMap<Tag>>() {}.getType()));
+ }
+
+ @Test
+ public void cachedRefFields() {
+ assertThatSerializedClass(CachedRef.class)
+ .extendsClass(new TypeLiteral<AtomicReference<ObjectId>>() {}.getType());
+ assertThatSerializedClass(CachedRef.class)
+ .hasFields(
+ ImmutableMap.of(
+ "flag", int.class, "value", AtomicReference.class.getTypeParameters()[0]));
+ }
+
+ @Test
+ public void tagFields() {
+ assertThatSerializedClass(Tag.class).extendsClass(ObjectIdOwnerMap.Entry.class);
+ assertThatSerializedClass(Tag.class)
+ .hasFields(
+ ImmutableMap.<String, Type>builder()
+ .put("refFlags", BitSet.class)
+ .put("next", ObjectIdOwnerMap.Entry.class)
+ .put("w1", int.class)
+ .put("w2", int.class)
+ .put("w3", int.class)
+ .put("w4", int.class)
+ .put("w5", int.class)
+ .build());
+ }
+
// TODO(dborowitz): Find some more common place to put this method, which requires access to
// package-private TagSet details.
static void assertEqual(@Nullable TagSet a, @Nullable TagSet b) {
diff --git a/javatests/com/google/gerrit/server/git/meta/VersionedMetaDataTest.java b/javatests/com/google/gerrit/server/git/meta/VersionedMetaDataTest.java
index 6090a78..56e53b9 100644
--- a/javatests/com/google/gerrit/server/git/meta/VersionedMetaDataTest.java
+++ b/javatests/com/google/gerrit/server/git/meta/VersionedMetaDataTest.java
@@ -203,7 +203,7 @@
private MyMetaData load(String ref, int expectedValue) throws Exception {
MyMetaData d = new MyMetaData(ref);
- d.load(repo);
+ d.load(project, repo);
assertThat(d.getValue()).isEqualTo(expectedValue);
return d;
}
diff --git a/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java b/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
index 2aa6035..943f784 100644
--- a/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
+++ b/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
@@ -140,7 +140,7 @@
@Override
public String getName() {
try {
- return GroupConfig.loadForGroup(allUsersRepo, uuid)
+ return GroupConfig.loadForGroup(allUsersName, allUsersRepo, uuid)
.getLoadedGroup()
.map(InternalGroup::getName)
.orElse("Group " + uuid);
diff --git a/javatests/com/google/gerrit/server/group/db/AuditLogReaderTest.java b/javatests/com/google/gerrit/server/group/db/AuditLogReaderTest.java
index 4e93aee..309d710 100644
--- a/javatests/com/google/gerrit/server/group/db/AuditLogReaderTest.java
+++ b/javatests/com/google/gerrit/server/group/db/AuditLogReaderTest.java
@@ -37,7 +37,7 @@
@Before
public void setUp() throws Exception {
- auditLogReader = new AuditLogReader(SERVER_ID);
+ auditLogReader = new AuditLogReader(SERVER_ID, allUsersName);
}
@Test
@@ -250,7 +250,8 @@
.setMemberModification(members -> ImmutableSet.of(authorId))
.build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(allUsersRepo, groupCreation);
+ GroupConfig groupConfig =
+ GroupConfig.createForNewGroup(allUsersName, allUsersRepo, groupCreation);
groupConfig.setGroupUpdate(groupUpdate, getAuditLogFormatter());
groupConfig.commit(createMetaDataUpdate(authorIdent));
@@ -261,7 +262,7 @@
private void updateGroup(AccountGroup.UUID uuid, InternalGroupUpdate groupUpdate)
throws Exception {
- GroupConfig groupConfig = GroupConfig.loadForGroup(allUsersRepo, uuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(allUsersName, allUsersRepo, uuid);
groupConfig.setGroupUpdate(groupUpdate, getAuditLogFormatter());
groupConfig.commit(createMetaDataUpdate(userIdent));
}
diff --git a/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java b/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java
index d03a38b..dec1d63 100644
--- a/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java
+++ b/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java
@@ -65,6 +65,7 @@
@Rule public ExpectedException expectedException = ExpectedException.none();
+ private Project.NameKey projectName;
private Repository repository;
private TestRepository<?> testRepository;
private final AccountGroup.UUID groupUuid = new AccountGroup.UUID("users-XYZ");
@@ -76,6 +77,7 @@
@Before
public void setUp() throws Exception {
+ projectName = new Project.NameKey("Test Repository");
repository = new InMemoryRepository(new DfsRepositoryDescription("Test Repository"));
testRepository = new TestRepository<>(repository);
}
@@ -117,7 +119,7 @@
public void nameOfNewGroupMustNotBeEmpty() throws Exception {
InternalGroupCreation groupCreation =
getPrefilledGroupCreationBuilder().setNameKey(new AccountGroup.NameKey("")).build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation);
+ GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
expectedException.expectCause(instanceOf(ConfigInvalidException.class));
@@ -130,7 +132,7 @@
public void nameOfNewGroupMustNotBeNull() throws Exception {
InternalGroupCreation groupCreation =
getPrefilledGroupCreationBuilder().setNameKey(new AccountGroup.NameKey(null)).build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation);
+ GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
expectedException.expectCause(instanceOf(ConfigInvalidException.class));
@@ -152,7 +154,7 @@
public void idOfNewGroupMustNotBeNegative() throws Exception {
InternalGroupCreation groupCreation =
getPrefilledGroupCreationBuilder().setId(new AccountGroup.Id(-2)).build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation);
+ GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
expectedException.expectCause(instanceOf(ConfigInvalidException.class));
@@ -230,7 +232,7 @@
InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build();
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder().setOwnerGroupUUID(new AccountGroup.UUID(null)).build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation);
+ GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
@@ -245,7 +247,7 @@
InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build();
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder().setOwnerGroupUUID(new AccountGroup.UUID("")).build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation);
+ GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
@@ -362,7 +364,7 @@
expectedException.expect(ConfigInvalidException.class);
expectedException.expectMessage("ID of the group " + groupUuid);
- GroupConfig.loadForGroup(repository, groupUuid);
+ GroupConfig.loadForGroup(projectName, repository, groupUuid);
}
@Test
@@ -372,7 +374,7 @@
expectedException.expect(ConfigInvalidException.class);
expectedException.expectMessage("ID of the group " + groupUuid);
- GroupConfig.loadForGroup(repository, groupUuid);
+ GroupConfig.loadForGroup(projectName, repository, groupUuid);
}
@Test
@@ -398,7 +400,7 @@
expectedException.expect(ConfigInvalidException.class);
expectedException.expectMessage("Owner UUID of the group " + groupUuid);
- GroupConfig.loadForGroup(repository, groupUuid);
+ GroupConfig.loadForGroup(projectName, repository, groupUuid);
}
@Test
@@ -546,7 +548,7 @@
public void nameCannotBeUpdatedToNull() throws Exception {
createArbitraryGroup(groupUuid);
- GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder().setName(new AccountGroup.NameKey(null)).build();
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
@@ -562,7 +564,7 @@
public void nameCannotBeUpdatedToEmptyString() throws Exception {
createArbitraryGroup(groupUuid);
- GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("")).build();
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
@@ -579,7 +581,7 @@
createArbitraryGroup(groupUuid);
AccountGroup.NameKey emptyName = new AccountGroup.NameKey("");
- GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
groupConfig.setAllowSaveEmptyName();
InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().setName(emptyName).build();
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
@@ -629,7 +631,7 @@
public void ownerGroupUuidCannotBeUpdatedToNull() throws Exception {
createArbitraryGroup(groupUuid);
- GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder().setOwnerGroupUUID(new AccountGroup.UUID(null)).build();
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
@@ -645,7 +647,7 @@
public void ownerGroupUuidCannotBeUpdatedToEmptyString() throws Exception {
createArbitraryGroup(groupUuid);
- GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder().setOwnerGroupUUID(new AccountGroup.UUID("")).build();
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
@@ -874,7 +876,7 @@
public void groupConfigMayBeReusedForFurtherUpdates() throws Exception {
InternalGroupCreation groupCreation =
getPrefilledGroupCreationBuilder().setGroupUUID(groupUuid).setId(groupId).build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation);
+ GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
commit(groupConfig);
AccountGroup.NameKey name = new AccountGroup.NameKey("Robots");
@@ -1054,7 +1056,7 @@
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Another name")).build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation);
+ GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
commit(groupConfig);
@@ -1072,7 +1074,7 @@
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder().setDescription("A test group").build();
- GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
commit(groupConfig);
@@ -1138,7 +1140,7 @@
.setName(new AccountGroup.NameKey("Another name"))
.setUpdatedOn(createdOn)
.build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation);
+ GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
PersonIdent committerIdent =
@@ -1171,7 +1173,7 @@
.setName(new AccountGroup.NameKey("Another name"))
.setUpdatedOn(createdOn)
.build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation);
+ GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
PersonIdent authorIdent =
@@ -1230,7 +1232,7 @@
.setName(new AccountGroup.NameKey("Another name"))
.setUpdatedOn(updatedOn)
.build();
- GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
PersonIdent committerIdent =
@@ -1258,7 +1260,7 @@
.setName(new AccountGroup.NameKey("Another name"))
.setUpdatedOn(updatedOn)
.build();
- GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
PersonIdent authorIdent =
@@ -1299,7 +1301,8 @@
updateGroup(groupUuid, groupUpdate2);
GroupConfig groupConfig =
- GroupConfig.loadForGroupSnapshot(repository, groupUuid, commitAfterUpdate1.copy());
+ GroupConfig.loadForGroupSnapshot(
+ projectName, repository, groupUuid, commitAfterUpdate1.copy());
Optional<InternalGroup> group = groupConfig.getLoadedGroup();
assertThatGroup(group).value().nameKey().isEqualTo(firstName);
assertThatGroup(group).value().refState().isEqualTo(commitAfterUpdate1.copy());
@@ -1344,7 +1347,7 @@
.setMemberModification(members -> ImmutableSet.of(account13.getId(), account7.getId()))
.build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation);
+ GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
commit(groupConfig);
@@ -1369,7 +1372,7 @@
.setSubgroupModification(
subgroups -> ImmutableSet.of(group1.getGroupUUID(), group2.getGroupUUID()))
.build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation);
+ GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
commit(groupConfig);
@@ -1584,14 +1587,14 @@
private Optional<InternalGroup> createGroup(InternalGroupCreation groupCreation)
throws Exception {
- GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation);
+ GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
commit(groupConfig);
return groupConfig.getLoadedGroup();
}
private Optional<InternalGroup> createGroup(
InternalGroupCreation groupCreation, InternalGroupUpdate groupUpdate) throws Exception {
- GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation);
+ GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
commit(groupConfig);
return groupConfig.getLoadedGroup();
@@ -1605,14 +1608,14 @@
private Optional<InternalGroup> updateGroup(
AccountGroup.UUID uuid, InternalGroupUpdate groupUpdate, AuditLogFormatter auditLogFormatter)
throws Exception {
- GroupConfig groupConfig = GroupConfig.loadForGroup(repository, uuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, uuid);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
commit(groupConfig);
return groupConfig.getLoadedGroup();
}
private Optional<InternalGroup> loadGroup(AccountGroup.UUID uuid) throws Exception {
- GroupConfig groupConfig = GroupConfig.loadForGroup(repository, uuid);
+ GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, uuid);
return groupConfig.getLoadedGroup();
}
diff --git a/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java b/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java
index 3616e0e..a70df6b 100644
--- a/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java
+++ b/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java
@@ -31,6 +31,7 @@
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
@@ -84,12 +85,14 @@
private final AccountGroup.NameKey groupName = new AccountGroup.NameKey("users");
private AtomicInteger idCounter;
+ private AllUsersName allUsersName;
private Repository repo;
@Before
public void setUp() {
TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
idCounter = new AtomicInteger();
+ allUsersName = new AllUsersName(AllUsersNameProvider.DEFAULT);
repo = new InMemoryRepository(new DfsRepositoryDescription(AllUsersNameProvider.DEFAULT));
}
@@ -110,13 +113,13 @@
@Test
public void uuidOfNewGroupMustNotBeNull() throws Exception {
expectedException.expect(NullPointerException.class);
- GroupNameNotes.forNewGroup(repo, null, groupName);
+ GroupNameNotes.forNewGroup(allUsersName, repo, null, groupName);
}
@Test
public void nameOfNewGroupMustNotBeNull() throws Exception {
expectedException.expect(NullPointerException.class);
- GroupNameNotes.forNewGroup(repo, groupUuid, null);
+ GroupNameNotes.forNewGroup(allUsersName, repo, groupUuid, null);
}
@Test
@@ -135,7 +138,7 @@
AccountGroup.UUID anotherGroupUuid = new AccountGroup.UUID("AnotherGroup");
expectedException.expect(OrmDuplicateKeyException.class);
expectedException.expectMessage(groupName.get());
- GroupNameNotes.forNewGroup(repo, anotherGroupUuid, groupName);
+ GroupNameNotes.forNewGroup(allUsersName, repo, anotherGroupUuid, groupName);
}
@Test
@@ -179,7 +182,7 @@
createGroup(groupUuid, groupName);
expectedException.expect(NullPointerException.class);
- GroupNameNotes.forRename(repo, groupUuid, groupName, null);
+ GroupNameNotes.forRename(allUsersName, repo, groupUuid, groupName, null);
}
@Test
@@ -188,7 +191,7 @@
AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
expectedException.expect(NullPointerException.class);
- GroupNameNotes.forRename(repo, groupUuid, null, anotherName);
+ GroupNameNotes.forRename(allUsersName, repo, groupUuid, null, anotherName);
}
@Test
@@ -199,7 +202,7 @@
AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
expectedException.expect(ConfigInvalidException.class);
expectedException.expectMessage(anotherOldName.get());
- GroupNameNotes.forRename(repo, groupUuid, anotherOldName, anotherName);
+ GroupNameNotes.forRename(allUsersName, repo, groupUuid, anotherOldName, anotherName);
}
@Test
@@ -211,7 +214,7 @@
expectedException.expect(OrmDuplicateKeyException.class);
expectedException.expectMessage(anotherGroupName.get());
- GroupNameNotes.forRename(repo, groupUuid, groupName, anotherGroupName);
+ GroupNameNotes.forRename(allUsersName, repo, groupUuid, groupName, anotherGroupName);
}
@Test
@@ -220,7 +223,7 @@
AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
expectedException.expect(NullPointerException.class);
- GroupNameNotes.forRename(repo, null, groupName, anotherName);
+ GroupNameNotes.forRename(allUsersName, repo, null, groupName, anotherName);
}
@Test
@@ -231,7 +234,7 @@
AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
expectedException.expect(ConfigInvalidException.class);
expectedException.expectMessage(groupUuid.get());
- GroupNameNotes.forRename(repo, anotherGroupUuid, groupName, anotherName);
+ GroupNameNotes.forRename(allUsersName, repo, anotherGroupUuid, groupName, anotherName);
}
@Test
@@ -287,7 +290,8 @@
@Test
public void newCommitIsNotCreatedWhenCommittingGroupCreationTwice() throws Exception {
- GroupNameNotes groupNameNotes = GroupNameNotes.forNewGroup(repo, groupUuid, groupName);
+ GroupNameNotes groupNameNotes =
+ GroupNameNotes.forNewGroup(allUsersName, repo, groupUuid, groupName);
commit(groupNameNotes);
ImmutableList<CommitInfo> commitsAfterFirstCommit = log();
@@ -303,7 +307,7 @@
AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
GroupNameNotes groupNameNotes =
- GroupNameNotes.forRename(repo, groupUuid, groupName, anotherName);
+ GroupNameNotes.forRename(allUsersName, repo, groupUuid, groupName, anotherName);
commit(groupNameNotes);
ImmutableList<CommitInfo> commitsAfterFirstCommit = log();
@@ -504,14 +508,16 @@
private void createGroup(AccountGroup.UUID groupUuid, AccountGroup.NameKey groupName)
throws Exception {
- GroupNameNotes groupNameNotes = GroupNameNotes.forNewGroup(repo, groupUuid, groupName);
+ GroupNameNotes groupNameNotes =
+ GroupNameNotes.forNewGroup(allUsersName, repo, groupUuid, groupName);
commit(groupNameNotes);
}
private void renameGroup(
AccountGroup.UUID groupUuid, AccountGroup.NameKey oldName, AccountGroup.NameKey newName)
throws Exception {
- GroupNameNotes groupNameNotes = GroupNameNotes.forRename(repo, groupUuid, oldName, newName);
+ GroupNameNotes groupNameNotes =
+ GroupNameNotes.forRename(allUsersName, repo, groupUuid, oldName, newName);
commit(groupNameNotes);
}
diff --git a/javatests/com/google/gerrit/server/logging/LoggingContextAwareExecutorServiceTest.java b/javatests/com/google/gerrit/server/logging/LoggingContextAwareExecutorServiceTest.java
new file mode 100644
index 0000000..5117c01
--- /dev/null
+++ b/javatests/com/google/gerrit/server/logging/LoggingContextAwareExecutorServiceTest.java
@@ -0,0 +1,71 @@
+// Copyright (C) 2018 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.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.truth.Expect;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class LoggingContextAwareExecutorServiceTest {
+ @Rule public final Expect expect = Expect.create();
+
+ @Test
+ public void loggingContextPropagationToBackgroundThread() throws Exception {
+ assertThat(LoggingContext.getInstance().getTags().isEmpty()).isTrue();
+ assertForceLogging(false);
+ try (TraceContext traceContext = TraceContext.open().forceLogging().addTag("foo", "bar")) {
+ SortedMap<String, SortedSet<Object>> tagMap = LoggingContext.getInstance().getTags().asMap();
+ assertThat(tagMap.keySet()).containsExactly("foo");
+ assertThat(tagMap.get("foo")).containsExactly("bar");
+ assertForceLogging(true);
+
+ ExecutorService executor =
+ new LoggingContextAwareExecutorService(Executors.newFixedThreadPool(1));
+ executor
+ .submit(
+ () -> {
+ // Verify that the tags and force logging flag have been propagated to the new
+ // thread.
+ SortedMap<String, SortedSet<Object>> threadTagMap =
+ LoggingContext.getInstance().getTags().asMap();
+ expect.that(threadTagMap.keySet()).containsExactly("foo");
+ expect.that(threadTagMap.get("foo")).containsExactly("bar");
+ expect
+ .that(LoggingContext.getInstance().shouldForceLogging(null, null, false))
+ .isTrue();
+ })
+ .get();
+
+ // Verify that tags and force logging flag in the outer thread are still set.
+ tagMap = LoggingContext.getInstance().getTags().asMap();
+ assertThat(tagMap.keySet()).containsExactly("foo");
+ assertThat(tagMap.get("foo")).containsExactly("bar");
+ assertForceLogging(true);
+ }
+ assertThat(LoggingContext.getInstance().getTags().isEmpty()).isTrue();
+ assertForceLogging(false);
+ }
+
+ private void assertForceLogging(boolean expected) {
+ assertThat(LoggingContext.getInstance().shouldForceLogging(null, null, false))
+ .isEqualTo(expected);
+ }
+}
diff --git a/javatests/com/google/gerrit/server/logging/MutableTagsTest.java b/javatests/com/google/gerrit/server/logging/MutableTagsTest.java
new file mode 100644
index 0000000..4fadbb4
--- /dev/null
+++ b/javatests/com/google/gerrit/server/logging/MutableTagsTest.java
@@ -0,0 +1,176 @@
+// Copyright (C) 2018 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.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MutableTagsTest {
+ private MutableTags tags;
+
+ @Before
+ public void setup() {
+ tags = new MutableTags();
+ }
+
+ @Test
+ public void addTag() {
+ assertThat(tags.add("name", "value")).isTrue();
+ assertTags(ImmutableMap.of("name", ImmutableSet.of("value")));
+ }
+
+ @Test
+ public void addTagsWithDifferentName() {
+ assertThat(tags.add("name1", "value1")).isTrue();
+ assertThat(tags.add("name2", "value2")).isTrue();
+ assertTags(
+ ImmutableMap.of("name1", ImmutableSet.of("value1"), "name2", ImmutableSet.of("value2")));
+ }
+
+ @Test
+ public void addTagsWithSameNameButDifferentValues() {
+ assertThat(tags.add("name", "value1")).isTrue();
+ assertThat(tags.add("name", "value2")).isTrue();
+ assertTags(ImmutableMap.of("name", ImmutableSet.of("value1", "value2")));
+ }
+
+ @Test
+ public void addTagsWithSameNameAndSameValue() {
+ assertThat(tags.add("name", "value")).isTrue();
+ assertThat(tags.add("name", "value")).isFalse();
+ assertTags(ImmutableMap.of("name", ImmutableSet.of("value")));
+ }
+
+ @Test
+ public void getEmptyTags() {
+ assertThat(tags.getTags().isEmpty()).isTrue();
+ assertTags(ImmutableMap.of());
+ }
+
+ @Test
+ public void isEmpty() {
+ assertThat(tags.isEmpty()).isTrue();
+
+ tags.add("foo", "bar");
+ assertThat(tags.isEmpty()).isFalse();
+
+ tags.remove("foo", "bar");
+ assertThat(tags.isEmpty()).isTrue();
+ }
+
+ @Test
+ public void removeTags() {
+ tags.add("name1", "value1");
+ tags.add("name1", "value2");
+ tags.add("name2", "value");
+ assertTags(
+ ImmutableMap.of(
+ "name1", ImmutableSet.of("value1", "value2"), "name2", ImmutableSet.of("value")));
+
+ tags.remove("name2", "value");
+ assertTags(ImmutableMap.of("name1", ImmutableSet.of("value1", "value2")));
+
+ tags.remove("name1", "value1");
+ assertTags(ImmutableMap.of("name1", ImmutableSet.of("value2")));
+
+ tags.remove("name1", "value2");
+ assertTags(ImmutableMap.of());
+ }
+
+ @Test
+ public void removeNonExistingTag() {
+ tags.add("name", "value");
+ assertTags(ImmutableMap.of("name", ImmutableSet.of("value")));
+
+ tags.remove("foo", "bar");
+ assertTags(ImmutableMap.of("name", ImmutableSet.of("value")));
+
+ tags.remove("name", "foo");
+ assertTags(ImmutableMap.of("name", ImmutableSet.of("value")));
+ }
+
+ @Test
+ public void setTags() {
+ tags.add("name", "value");
+ assertTags(ImmutableMap.of("name", ImmutableSet.of("value")));
+
+ tags.set(ImmutableSetMultimap.of("foo", "bar", "foo", "baz", "bar", "baz"));
+ assertTags(
+ ImmutableMap.of("foo", ImmutableSet.of("bar", "baz"), "bar", ImmutableSet.of("baz")));
+ }
+
+ @Test
+ public void asMap() {
+ tags.add("name", "value");
+ assertThat(tags.asMap()).containsExactlyEntriesIn(ImmutableSetMultimap.of("name", "value"));
+
+ tags.set(ImmutableSetMultimap.of("foo", "bar", "foo", "baz", "bar", "baz"));
+ assertThat(tags.asMap())
+ .containsExactlyEntriesIn(
+ ImmutableSetMultimap.of("foo", "bar", "foo", "baz", "bar", "baz"));
+ }
+
+ @Test
+ public void clearTags() {
+ tags.add("name1", "value1");
+ tags.add("name1", "value2");
+ tags.add("name2", "value");
+ assertTags(
+ ImmutableMap.of(
+ "name1", ImmutableSet.of("value1", "value2"), "name2", ImmutableSet.of("value")));
+
+ tags.clear();
+ assertTags(ImmutableMap.of());
+ }
+
+ @Test
+ public void addInvalidTag() {
+ assertNullPointerException("tag name is required", () -> tags.add(null, "foo"));
+ assertNullPointerException("tag value is required", () -> tags.add("foo", null));
+ }
+
+ @Test
+ public void removeInvalidTag() {
+ assertNullPointerException("tag name is required", () -> tags.remove(null, "foo"));
+ assertNullPointerException("tag value is required", () -> tags.remove("foo", null));
+ }
+
+ private void assertTags(ImmutableMap<String, ImmutableSet<String>> expectedTagMap) {
+ SortedMap<String, SortedSet<Object>> actualTagMap = tags.getTags().asMap();
+ assertThat(actualTagMap.keySet()).containsExactlyElementsIn(expectedTagMap.keySet());
+ for (Map.Entry<String, ImmutableSet<String>> expectedEntry : expectedTagMap.entrySet()) {
+ assertThat(actualTagMap.get(expectedEntry.getKey()))
+ .containsExactlyElementsIn(expectedEntry.getValue());
+ }
+ }
+
+ private void assertNullPointerException(String expectedMessage, Runnable r) {
+ try {
+ r.run();
+ assert_().fail("expected NullPointerException");
+ } catch (NullPointerException e) {
+ assertThat(e.getMessage()).isEqualTo(expectedMessage);
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/server/logging/TraceContextTest.java b/javatests/com/google/gerrit/server/logging/TraceContextTest.java
new file mode 100644
index 0000000..044d237
--- /dev/null
+++ b/javatests/com/google/gerrit/server/logging/TraceContextTest.java
@@ -0,0 +1,264 @@
+// Copyright (C) 2018 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.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.server.logging.TraceContext.TraceIdConsumer;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import org.junit.After;
+import org.junit.Test;
+
+public class TraceContextTest {
+ @After
+ public void cleanup() {
+ LoggingContext.getInstance().clearTags();
+ LoggingContext.getInstance().forceLogging(false);
+ }
+
+ @Test
+ public void openContext() {
+ assertTags(ImmutableMap.of());
+ try (TraceContext traceContext = TraceContext.open().addTag("foo", "bar")) {
+ assertTags(ImmutableMap.of("foo", ImmutableSet.of("bar")));
+ }
+ assertTags(ImmutableMap.of());
+ }
+
+ @Test
+ public void openNestedContexts() {
+ assertTags(ImmutableMap.of());
+ try (TraceContext traceContext = TraceContext.open().addTag("foo", "bar")) {
+ assertTags(ImmutableMap.of("foo", ImmutableSet.of("bar")));
+
+ try (TraceContext traceContext2 = TraceContext.open().addTag("abc", "xyz")) {
+ assertTags(ImmutableMap.of("abc", ImmutableSet.of("xyz"), "foo", ImmutableSet.of("bar")));
+ }
+
+ assertTags(ImmutableMap.of("foo", ImmutableSet.of("bar")));
+ }
+ assertTags(ImmutableMap.of());
+ }
+
+ @Test
+ public void openNestedContextsWithSameTagName() {
+ assertTags(ImmutableMap.of());
+ try (TraceContext traceContext = TraceContext.open().addTag("foo", "bar")) {
+ assertTags(ImmutableMap.of("foo", ImmutableSet.of("bar")));
+
+ try (TraceContext traceContext2 = TraceContext.open().addTag("foo", "baz")) {
+ assertTags(ImmutableMap.of("foo", ImmutableSet.of("bar", "baz")));
+ }
+
+ assertTags(ImmutableMap.of("foo", ImmutableSet.of("bar")));
+ }
+ assertTags(ImmutableMap.of());
+ }
+
+ @Test
+ public void openNestedContextsWithSameTagNameAndValue() {
+ assertTags(ImmutableMap.of());
+ try (TraceContext traceContext = TraceContext.open().addTag("foo", "bar")) {
+ assertTags(ImmutableMap.of("foo", ImmutableSet.of("bar")));
+
+ try (TraceContext traceContext2 = TraceContext.open().addTag("foo", "bar")) {
+ assertTags(ImmutableMap.of("foo", ImmutableSet.of("bar")));
+ }
+
+ assertTags(ImmutableMap.of("foo", ImmutableSet.of("bar")));
+ }
+ assertTags(ImmutableMap.of());
+ }
+
+ @Test
+ public void openContextWithRequestId() {
+ assertTags(ImmutableMap.of());
+ try (TraceContext traceContext = TraceContext.open().addTag(RequestId.Type.RECEIVE_ID, "foo")) {
+ assertTags(ImmutableMap.of("RECEIVE_ID", ImmutableSet.of("foo")));
+ }
+ assertTags(ImmutableMap.of());
+ }
+
+ @Test
+ public void addTag() {
+ assertTags(ImmutableMap.of());
+ try (TraceContext traceContext = TraceContext.open().addTag("foo", "bar")) {
+ assertTags(ImmutableMap.of("foo", ImmutableSet.of("bar")));
+
+ traceContext.addTag("foo", "baz");
+ traceContext.addTag("bar", "baz");
+ assertTags(
+ ImmutableMap.of("foo", ImmutableSet.of("bar", "baz"), "bar", ImmutableSet.of("baz")));
+ }
+ assertTags(ImmutableMap.of());
+ }
+
+ @Test
+ public void openContextWithForceLogging() {
+ assertForceLogging(false);
+ try (TraceContext traceContext = TraceContext.open().forceLogging()) {
+ assertForceLogging(true);
+ }
+ assertForceLogging(false);
+ }
+
+ @Test
+ public void openNestedContextsWithForceLogging() {
+ assertForceLogging(false);
+ try (TraceContext traceContext = TraceContext.open().forceLogging()) {
+ assertForceLogging(true);
+
+ try (TraceContext traceContext2 = TraceContext.open()) {
+ // force logging is still enabled since outer trace context forced logging
+ assertForceLogging(true);
+
+ try (TraceContext traceContext3 = TraceContext.open().forceLogging()) {
+ assertForceLogging(true);
+ }
+
+ assertForceLogging(true);
+ }
+
+ assertForceLogging(true);
+ }
+ assertForceLogging(false);
+ }
+
+ @Test
+ public void forceLogging() {
+ assertForceLogging(false);
+ try (TraceContext traceContext = TraceContext.open()) {
+ assertForceLogging(false);
+
+ traceContext.forceLogging();
+ assertForceLogging(true);
+
+ traceContext.forceLogging();
+ assertForceLogging(true);
+ }
+ assertForceLogging(false);
+ }
+
+ @Test
+ public void newTrace() {
+ TestTraceIdConsumer traceIdConsumer = new TestTraceIdConsumer();
+ try (TraceContext traceContext = TraceContext.newTrace(true, null, traceIdConsumer)) {
+ assertForceLogging(true);
+ assertThat(LoggingContext.getInstance().getTagsAsMap().keySet())
+ .containsExactly(RequestId.Type.TRACE_ID.name());
+ }
+ assertThat(traceIdConsumer.tagName).isEqualTo(RequestId.Type.TRACE_ID.name());
+ assertThat(traceIdConsumer.traceId).isNotNull();
+ }
+
+ @Test
+ public void newTraceWithProvidedTraceId() {
+ TestTraceIdConsumer traceIdConsumer = new TestTraceIdConsumer();
+ String traceId = "foo";
+ try (TraceContext traceContext = TraceContext.newTrace(true, traceId, traceIdConsumer)) {
+ assertForceLogging(true);
+ assertTags(ImmutableMap.of(RequestId.Type.TRACE_ID.name(), ImmutableSet.of(traceId)));
+ }
+ assertThat(traceIdConsumer.tagName).isEqualTo(RequestId.Type.TRACE_ID.name());
+ assertThat(traceIdConsumer.traceId).isEqualTo(traceId);
+ }
+
+ @Test
+ public void newTraceDisabled() {
+ TestTraceIdConsumer traceIdConsumer = new TestTraceIdConsumer();
+ try (TraceContext traceContext = TraceContext.newTrace(false, null, traceIdConsumer)) {
+ assertForceLogging(false);
+ assertTags(ImmutableMap.of());
+ }
+ assertThat(traceIdConsumer.tagName).isNull();
+ assertThat(traceIdConsumer.traceId).isNull();
+ }
+
+ @Test
+ public void newTraceDisabledWithProvidedTraceId() {
+ TestTraceIdConsumer traceIdConsumer = new TestTraceIdConsumer();
+ try (TraceContext traceContext = TraceContext.newTrace(false, "foo", traceIdConsumer)) {
+ assertForceLogging(false);
+ assertTags(ImmutableMap.of());
+ }
+ assertThat(traceIdConsumer.tagName).isNull();
+ assertThat(traceIdConsumer.traceId).isNull();
+ }
+
+ @Test
+ public void onlyOneTraceId() {
+ TestTraceIdConsumer traceIdConsumer1 = new TestTraceIdConsumer();
+ try (TraceContext traceContext1 = TraceContext.newTrace(true, null, traceIdConsumer1)) {
+ String expectedTraceId = traceIdConsumer1.traceId;
+ assertThat(expectedTraceId).isNotNull();
+
+ TestTraceIdConsumer traceIdConsumer2 = new TestTraceIdConsumer();
+ try (TraceContext traceContext2 = TraceContext.newTrace(true, null, traceIdConsumer2)) {
+ assertForceLogging(true);
+ assertTags(
+ ImmutableMap.of(RequestId.Type.TRACE_ID.name(), ImmutableSet.of(expectedTraceId)));
+ }
+ assertThat(traceIdConsumer2.tagName).isEqualTo(RequestId.Type.TRACE_ID.name());
+ assertThat(traceIdConsumer2.traceId).isEqualTo(expectedTraceId);
+ }
+ }
+
+ @Test
+ public void multipleTraceIdsIfTraceIdProvided() {
+ String traceId1 = "foo";
+ try (TraceContext traceContext1 =
+ TraceContext.newTrace(true, traceId1, (tagName, traceId) -> {})) {
+ TestTraceIdConsumer traceIdConsumer = new TestTraceIdConsumer();
+ String traceId2 = "bar";
+ try (TraceContext traceContext2 = TraceContext.newTrace(true, traceId2, traceIdConsumer)) {
+ assertForceLogging(true);
+ assertTags(
+ ImmutableMap.of(RequestId.Type.TRACE_ID.name(), ImmutableSet.of(traceId1, traceId2)));
+ }
+ assertThat(traceIdConsumer.tagName).isEqualTo(RequestId.Type.TRACE_ID.name());
+ assertThat(traceIdConsumer.traceId).isEqualTo(traceId2);
+ }
+ }
+
+ private void assertTags(ImmutableMap<String, ImmutableSet<String>> expectedTagMap) {
+ SortedMap<String, SortedSet<Object>> actualTagMap =
+ LoggingContext.getInstance().getTags().asMap();
+ assertThat(actualTagMap.keySet()).containsExactlyElementsIn(expectedTagMap.keySet());
+ for (Map.Entry<String, ImmutableSet<String>> expectedEntry : expectedTagMap.entrySet()) {
+ assertThat(actualTagMap.get(expectedEntry.getKey()))
+ .containsExactlyElementsIn(expectedEntry.getValue());
+ }
+ }
+
+ private void assertForceLogging(boolean expected) {
+ assertThat(LoggingContext.getInstance().shouldForceLogging(null, null, false))
+ .isEqualTo(expected);
+ }
+
+ private static class TestTraceIdConsumer implements TraceIdConsumer {
+ String tagName;
+ String traceId;
+
+ @Override
+ public void accept(String tagName, String traceId) {
+ this.tagName = tagName;
+ this.traceId = traceId;
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesCacheTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesCacheTest.java
index 5a7d812..7b140b7 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesCacheTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesCacheTest.java
@@ -16,7 +16,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
-import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.bytes;
+import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.byteString;
import static com.google.gerrit.server.cache.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
@@ -41,7 +41,7 @@
.setProject("project")
.setChangeId(1234)
.setId(
- bytes(
+ byteString(
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef))
.build());
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
index 574f6ac..7b41ba3 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
@@ -19,7 +19,7 @@
import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.APPROVAL_CODEC;
import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.MESSAGE_CODEC;
import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.PATCH_SET_CODEC;
-import static com.google.gerrit.server.cache.ProtoCacheSerializers.toByteString;
+import static com.google.gerrit.server.cache.serialize.ProtoCacheSerializers.toByteString;
import static com.google.gerrit.server.cache.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableList;
@@ -42,12 +42,12 @@
import com.google.gerrit.server.ReviewerByEmailSet;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.ReviewerStatusUpdate;
-import com.google.gerrit.server.cache.ProtoCacheSerializers.ObjectIdConverter;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ChangeColumnsProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerByEmailSetEntryProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerSetEntryProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerStatusUpdateProto;
+import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers.ObjectIdConverter;
import com.google.gerrit.server.notedb.ChangeNotesState.ChangeColumns;
import com.google.gerrit.server.notedb.ChangeNotesState.Serializer;
import com.google.gwtorm.client.KeyUtil;
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
index 852c8bc..8cd2753 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -51,8 +51,8 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.config.GerritServerId;
+import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
-import com.google.gerrit.server.util.RequestId;
import com.google.gerrit.testing.TestChanges;
import com.google.gerrit.testing.TestTimeUtil;
import com.google.gwtorm.server.OrmException;
@@ -426,7 +426,7 @@
@Test
public void approvalsPostSubmit() throws Exception {
Change c = newChange();
- RequestId submissionId = RequestId.forChange(c);
+ RequestId submissionId = submissionId(c);
ChangeUpdate update = newUpdate(c, changeOwner);
update.putApproval("Code-Review", (short) 1);
update.putApproval("Verified", (short) 1);
@@ -461,7 +461,7 @@
@Test
public void approvalsDuringSubmit() throws Exception {
Change c = newChange();
- RequestId submissionId = RequestId.forChange(c);
+ RequestId submissionId = submissionId(c);
ChangeUpdate update = newUpdate(c, changeOwner);
update.putApproval("Code-Review", (short) 1);
update.putApproval("Verified", (short) 1);
@@ -598,7 +598,7 @@
@Test
public void submitRecords() throws Exception {
Change c = newChange();
- RequestId submissionId = RequestId.forChange(c);
+ RequestId submissionId = submissionId(c);
ChangeUpdate update = newUpdate(c, changeOwner);
update.setSubjectForCommit("Submit patch set 1");
@@ -640,7 +640,7 @@
@Test
public void latestSubmitRecordsOnly() throws Exception {
Change c = newChange();
- RequestId submissionId = RequestId.forChange(c);
+ RequestId submissionId = submissionId(c);
ChangeUpdate update = newUpdate(c, changeOwner);
update.setSubjectForCommit("Submit patch set 1");
update.merge(
@@ -941,7 +941,7 @@
// Finish off by merging the change.
update = newUpdate(c, changeOwner);
update.merge(
- RequestId.forChange(c),
+ submissionId(c),
ImmutableList.of(
submitRecord(
"NOT_READY",
@@ -3141,4 +3141,8 @@
update.commit();
return tr.parseBody(commit);
}
+
+ private RequestId submissionId(Change c) {
+ return new RequestId(c.getId().toString());
+ }
}
diff --git a/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java b/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
index 963546f..8daf67f 100644
--- a/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
+++ b/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
@@ -24,7 +24,7 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.util.RequestId;
+import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.testing.ConfigSuite;
import com.google.gerrit.testing.TestChanges;
import java.util.Date;
@@ -151,7 +151,7 @@
ChangeUpdate update = newUpdate(c, changeOwner);
update.setSubjectForCommit("Submit patch set 1");
- RequestId submissionId = RequestId.forChange(c);
+ RequestId submissionId = submissionId(c);
update.merge(
submissionId,
ImmutableList.of(
@@ -220,7 +220,7 @@
ChangeUpdate update = newUpdate(c, changeOwner);
update.setSubjectForCommit("Submit patch set 1");
- RequestId submissionId = RequestId.forChange(c);
+ RequestId submissionId = submissionId(c);
update.merge(
submissionId, ImmutableList.of(submitRecord("RULE_ERROR", "Problem with patch set:\n1")));
update.commit();
@@ -424,4 +424,8 @@
RevCommit commit = parseCommit(commitId);
assertThat(commit.getFullMessage()).isEqualTo(expected);
}
+
+ private RequestId submissionId(Change c) {
+ return new RequestId(c.getId().toString());
+ }
}
diff --git a/javatests/com/google/gerrit/server/permissions/RefControlTest.java b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
index 7890de8..39896ed 100644
--- a/javatests/com/google/gerrit/server/permissions/RefControlTest.java
+++ b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
@@ -56,6 +56,7 @@
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.index.SingleVersionModule.SingleVersionListener;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
@@ -204,6 +205,7 @@
@Inject private ThreadLocalRequestContext requestContext;
@Inject private DefaultRefFilter.Factory refFilterFactory;
@Inject private IdentifiedUser.GenericFactory identifiedUserFactory;
+ @Inject private TransferConfig transferConfig;
@Before
public void setUp() throws Exception {
@@ -971,6 +973,7 @@
repoManager,
commentLinks,
capabilityCollectionFactory,
+ transferConfig,
pc));
return repo;
}
@@ -998,7 +1001,7 @@
return all.get(local.getProject().getNameKey());
}
- private class MockUser extends CurrentUser {
+ private static class MockUser extends CurrentUser {
@Nullable private final String username;
private final GroupMembership groups;
diff --git a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
index 3d9d661..9d01405 100644
--- a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
+++ b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
@@ -579,7 +579,7 @@
PersonIdent ident = serverIdent.get();
md.getCommitBuilder().setAuthor(ident);
md.getCommitBuilder().setCommitter(ident);
- new AccountConfig(accountId, repo)
+ new AccountConfig(accountId, allUsers, repo)
.load()
.setAccountUpdate(InternalAccountUpdate.builder().setFullName(newName).build())
.commit(md);
diff --git a/javatests/com/google/gerrit/server/query/change/ConflictKeyTest.java b/javatests/com/google/gerrit/server/query/change/ConflictKeyTest.java
index b87bbf7..de2acf0 100644
--- a/javatests/com/google/gerrit/server/query/change/ConflictKeyTest.java
+++ b/javatests/com/google/gerrit/server/query/change/ConflictKeyTest.java
@@ -18,7 +18,7 @@
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.extensions.client.SubmitType.FAST_FORWARD_ONLY;
import static com.google.gerrit.extensions.client.SubmitType.MERGE_IF_NECESSARY;
-import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.bytes;
+import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.byteString;
import static com.google.gerrit.server.cache.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
@@ -68,11 +68,11 @@
.isEqualTo(
ConflictKeyProto.newBuilder()
.setCommit(
- bytes(
+ byteString(
0xba, 0xdc, 0x0f, 0xee, 0xba, 0xdc, 0x0f, 0xee, 0xba, 0xdc, 0x0f, 0xee,
0xba, 0xdc, 0x0f, 0xee, 0xba, 0xdc, 0x0f, 0xee))
.setOtherCommit(
- bytes(
+ byteString(
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef))
.setSubmitType("MERGE_IF_NECESSARY")
diff --git a/javatests/com/google/gerrit/server/rules/BUILD b/javatests/com/google/gerrit/server/rules/BUILD
index 42452df..8f4c90d 100644
--- a/javatests/com/google/gerrit/server/rules/BUILD
+++ b/javatests/com/google/gerrit/server/rules/BUILD
@@ -7,9 +7,11 @@
resources = ["//prologtests:gerrit_common_test"],
deps = [
"//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/project/testing:project-test-util",
"//java/com/google/gerrit/testing:gerrit-test-util",
+ "//lib:guava",
"//lib/guice",
"//lib/jgit/org.eclipse.jgit:jgit",
"//lib/prolog:runtime",
diff --git a/javatests/com/google/gerrit/server/rules/IgnoreSelfApprovalRuleTest.java b/javatests/com/google/gerrit/server/rules/IgnoreSelfApprovalRuleTest.java
new file mode 100644
index 0000000..27f4423
--- /dev/null
+++ b/javatests/com/google/gerrit/server/rules/IgnoreSelfApprovalRuleTest.java
@@ -0,0 +1,96 @@
+// Copyright (C) 2018 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.rules;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.LabelValue;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.LabelId;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import org.junit.Test;
+
+public class IgnoreSelfApprovalRuleTest {
+ private static final Change.Id CHANGE_ID = new Change.Id(100);
+ private static final PatchSet.Id PS_ID = new PatchSet.Id(CHANGE_ID, 1);
+ private static final LabelType VERIFIED = makeLabel("Verified");
+ private static final Account.Id USER1 = makeAccount(100001);
+
+ @Test
+ public void filtersByLabel() {
+ LabelType codeReview = makeLabel("Code-Review");
+ PatchSetApproval approvalVerified = makeApproval(VERIFIED.getLabelId(), USER1, 2);
+ PatchSetApproval approvalCr = makeApproval(codeReview.getLabelId(), USER1, 2);
+
+ Collection<PatchSetApproval> filteredApprovals =
+ IgnoreSelfApprovalRule.filterApprovalsByLabel(
+ ImmutableList.of(approvalVerified, approvalCr), VERIFIED);
+
+ assertThat(filteredApprovals).containsExactly(approvalVerified);
+ }
+
+ @Test
+ public void filtersVotesFromUser() {
+ PatchSetApproval approvalM2 = makeApproval(VERIFIED.getLabelId(), USER1, -2);
+ PatchSetApproval approvalM1 = makeApproval(VERIFIED.getLabelId(), USER1, -1);
+
+ ImmutableList<PatchSetApproval> approvals =
+ ImmutableList.of(
+ approvalM2,
+ approvalM1,
+ makeApproval(VERIFIED.getLabelId(), USER1, 0),
+ makeApproval(VERIFIED.getLabelId(), USER1, +1),
+ makeApproval(VERIFIED.getLabelId(), USER1, +2));
+
+ Collection<PatchSetApproval> filteredApprovals =
+ IgnoreSelfApprovalRule.filterOutPositiveApprovalsOfUser(approvals, USER1);
+
+ assertThat(filteredApprovals).containsExactly(approvalM1, approvalM2);
+ }
+
+ private static LabelType makeLabel(String labelName) {
+ List<LabelValue> values = new ArrayList<>();
+ // The label text is irrelevant here, only the numerical value is used
+ values.add(new LabelValue((short) -2, "-2"));
+ values.add(new LabelValue((short) -1, "-1"));
+ values.add(new LabelValue((short) 0, "No vote."));
+ values.add(new LabelValue((short) 1, "+1"));
+ values.add(new LabelValue((short) 2, "+2"));
+ return new LabelType(labelName, values);
+ }
+
+ private static PatchSetApproval makeApproval(LabelId labelId, Account.Id accountId, int value) {
+ PatchSetApproval.Key key = makeKey(PS_ID, accountId, labelId);
+ return new PatchSetApproval(key, (short) value, Date.from(Instant.now()));
+ }
+
+ private static PatchSetApproval.Key makeKey(
+ PatchSet.Id psId, Account.Id accountId, LabelId labelId) {
+ return new PatchSetApproval.Key(psId, accountId, labelId);
+ }
+
+ private static Account.Id makeAccount(int account) {
+ return new Account.Id(account);
+ }
+}
diff --git a/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java b/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java
index ead824f..c76a246 100644
--- a/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java
+++ b/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java
@@ -66,6 +66,7 @@
private static final String SERVER_EMAIL = "noreply@gerritcodereview.com";
private AtomicInteger idCounter;
+ private AllUsersName allUsersName;
private Repository repo;
private GroupRebuilder rebuilder;
private GroupBundle.Factory bundleFactory;
@@ -74,7 +75,7 @@
public void setUp() throws Exception {
TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
idCounter = new AtomicInteger();
- AllUsersName allUsersName = new AllUsersName(AllUsersNameProvider.DEFAULT);
+ allUsersName = new AllUsersName(AllUsersNameProvider.DEFAULT);
repo = new InMemoryRepositoryManager().createRepository(allUsersName);
rebuilder =
new GroupRebuilder(
@@ -83,7 +84,7 @@
// Note that the expected name/email values in tests are not necessarily realistic,
// since they use these trivial name/email functions.
getAuditLogFormatter());
- bundleFactory = new GroupBundle.Factory(new AuditLogReader(SERVER_ID));
+ bundleFactory = new GroupBundle.Factory(new AuditLogReader(SERVER_ID, allUsersName));
}
@After
@@ -606,7 +607,7 @@
}
private GroupBundle reload(AccountGroup g) throws Exception {
- return bundleFactory.fromNoteDb(repo, g.getGroupUUID());
+ return bundleFactory.fromNoteDb(allUsersName, repo, g.getGroupUUID());
}
private void assertMigratedCleanly(GroupBundle noteDbBundle, GroupBundle expectedReviewDbBundle) {
diff --git a/javatests/com/google/gerrit/server/schema/SchemaCreatorTest.java b/javatests/com/google/gerrit/server/schema/SchemaCreatorTest.java
index 7f8b6f3..d3f69982 100644
--- a/javatests/com/google/gerrit/server/schema/SchemaCreatorTest.java
+++ b/javatests/com/google/gerrit/server/schema/SchemaCreatorTest.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.schema;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
-import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
@@ -36,6 +36,7 @@
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import org.eclipse.jgit.lib.Repository;
import org.junit.After;
@@ -108,15 +109,22 @@
assertThat(codeReview.getDefaultValue()).isEqualTo(0);
assertThat(codeReview.getFunction()).isEqualTo(LabelFunction.MAX_WITH_BLOCK);
assertThat(codeReview.isCopyMinScore()).isTrue();
- assertValueRange(codeReview, 2, 1, 0, -1, -2);
+ assertValueRange(codeReview, -2, -1, 0, 1, 2);
}
private void assertValueRange(LabelType label, Integer... range) {
- assertThat(label.getValuesAsList()).containsExactlyElementsIn(Arrays.asList(range)).inOrder();
- assertThat(label.getMax().getValue()).isEqualTo(range[0]);
- assertThat(label.getMin().getValue()).isEqualTo(range[range.length - 1]);
+ List<Integer> rangeList = Arrays.asList(range);
+ assertThat(rangeList).isNotEmpty();
+ assertThat(rangeList).isStrictlyOrdered();
+
+ assertThat(label.getValues().stream().map(v -> (int) v.getValue()))
+ .containsExactlyElementsIn(rangeList)
+ .inOrder();
+ assertThat(label.getMax().getValue()).isEqualTo(Collections.max(rangeList));
+ assertThat(label.getMin().getValue()).isEqualTo(Collections.min(rangeList));
for (LabelValue v : label.getValues()) {
- assertThat(Strings.isNullOrEmpty(v.getText())).isFalse();
+ assertThat(v.getText()).isNotNull();
+ assertThat(v.getText()).isNotEmpty();
}
}
}
diff --git a/javatests/com/google/gerrit/server/schema/Schema_159_to_160_Test.java b/javatests/com/google/gerrit/server/schema/Schema_159_to_160_Test.java
index a01d611..0080f3f 100644
--- a/javatests/com/google/gerrit/server/schema/Schema_159_to_160_Test.java
+++ b/javatests/com/google/gerrit/server/schema/Schema_159_to_160_Test.java
@@ -189,7 +189,7 @@
Supplier<VersionedAccountPreferences> prefsSupplier) throws Exception {
try (Repository repo = repoManager.openRepository(allUsersName)) {
VersionedAccountPreferences prefs = prefsSupplier.get();
- prefs.load(repo);
+ prefs.load(allUsersName, repo);
Config cfg = prefs.getConfig();
return cfg.getSubsections(MY)
.stream()
diff --git a/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInReviewDbTest.java b/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInReviewDbTest.java
index 6020325..75f9307 100644
--- a/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInReviewDbTest.java
+++ b/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInReviewDbTest.java
@@ -586,7 +586,7 @@
AccountGroup group = createInReviewDb("group");
TestGroupBackend testGroupBackend = new TestGroupBackend();
- backends.add(testGroupBackend);
+ backends.add("gerrit", testGroupBackend);
AccountGroup.UUID subgroupUuid = testGroupBackend.create("test").getGroupUUID();
assertThat(groupBackend.handles(subgroupUuid)).isTrue();
addSubgroupsInReviewDb(group.getId(), subgroupUuid);
@@ -916,7 +916,7 @@
private GroupBundle readGroupBundleFromNoteDb(AccountGroup.UUID groupUuid) throws Exception {
try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
- return groupBundleFactory.fromNoteDb(allUsersRepo, groupUuid);
+ return groupBundleFactory.fromNoteDb(allUsersName, allUsersRepo, groupUuid);
}
}
@@ -985,7 +985,7 @@
private Optional<InternalGroup> getGroupFromNoteDb(AccountGroup.UUID groupUuid) throws Exception {
try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
- return GroupConfig.loadForGroup(allUsersRepo, groupUuid).getLoadedGroup();
+ return GroupConfig.loadForGroup(allUsersName, allUsersRepo, groupUuid).getLoadedGroup();
}
}
diff --git a/javatests/com/google/gerrit/server/util/ParboiledTest.java b/javatests/com/google/gerrit/server/util/ParboiledTest.java
deleted file mode 100644
index 3bcfb56..0000000
--- a/javatests/com/google/gerrit/server/util/ParboiledTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (C) 2014 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.util;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.parboiled.BaseParser;
-import org.parboiled.Parboiled;
-import org.parboiled.Rule;
-import org.parboiled.annotations.BuildParseTree;
-import org.parboiled.parserunners.ReportingParseRunner;
-import org.parboiled.support.ParseTreeUtils;
-import org.parboiled.support.ParsingResult;
-
-public class ParboiledTest {
-
- private static final String EXPECTED =
- "[Expression] '42'\n"
- + " [Term] '42'\n"
- + " [Factor] '42'\n"
- + " [Number] '42'\n"
- + " [0..9] '4'\n"
- + " [0..9] '2'\n"
- + " [zeroOrMore]\n"
- + " [zeroOrMore]\n";
-
- private CalculatorParser parser;
-
- @Before
- public void setUp() {
- parser = Parboiled.createParser(CalculatorParser.class);
- }
-
- @Test
- public void test() {
- ParsingResult<String> result = new ReportingParseRunner<String>(parser.Expression()).run("42");
- assertThat(result.isSuccess()).isTrue();
- // next test is optional; we could stop here.
- assertThat(ParseTreeUtils.printNodeTree(result)).isEqualTo(EXPECTED);
- }
-
- @BuildParseTree
- static class CalculatorParser extends BaseParser<Object> {
- Rule Expression() {
- return sequence(Term(), zeroOrMore(anyOf("+-"), Term()));
- }
-
- Rule Term() {
- return sequence(Factor(), zeroOrMore(anyOf("*/"), Factor()));
- }
-
- Rule Factor() {
- return firstOf(Number(), sequence('(', Expression(), ')'));
- }
-
- Rule Number() {
- return oneOrMore(charRange('0', '9'));
- }
- }
-}
diff --git a/javatests/com/google/gerrit/server/util/git/BUILD b/javatests/com/google/gerrit/server/util/git/BUILD
index 928705c..61a776fa 100644
--- a/javatests/com/google/gerrit/server/util/git/BUILD
+++ b/javatests/com/google/gerrit/server/util/git/BUILD
@@ -12,7 +12,6 @@
"//java/com/google/gerrit/server/util/git",
"//java/com/google/gerrit/truth",
"//java/org/eclipse/jgit:server",
- "//lib:grappa",
"//lib:gson",
"//lib:guava",
"//lib:guava-retrying",
diff --git a/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletResponse.java b/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletResponse.java
index 2b1a07e..f6b3e30 100644
--- a/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletResponse.java
+++ b/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletResponse.java
@@ -25,6 +25,7 @@
import com.google.common.net.HttpHeaders;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.Collection;
@@ -106,7 +107,7 @@
public synchronized PrintWriter getWriter() {
checkState(outputStream == null, "getOutputStream() already called");
if (writer == null) {
- writer = new PrintWriter(actualBody);
+ writer = new PrintWriter(new OutputStreamWriter(actualBody, UTF_8));
}
return writer;
}
diff --git a/lib/BUILD b/lib/BUILD
index 38b0b80..e5034c9 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -123,32 +123,257 @@
)
java_library(
- name = "pegdown",
- data = ["//lib:LICENSE-Apache2.0"],
+ name = "flexmark",
+ data = ["//lib:LICENSE-flexmark"],
visibility = ["//visibility:public"],
- exports = ["@pegdown//jar"],
- runtime_deps = [":grappa"],
-)
-
-java_library(
- name = "grappa",
- data = ["//lib:LICENSE-Apache2.0"],
- visibility = ["//visibility:public"],
- exports = ["@grappa//jar"],
+ exports = ["@flexmark//jar"],
runtime_deps = [
- ":jitescript",
- "//lib/ow2:ow2-asm",
- "//lib/ow2:ow2-asm-analysis",
- "//lib/ow2:ow2-asm-tree",
- "//lib/ow2:ow2-asm-util",
+ ":flexmark-ext-abbreviation",
],
)
java_library(
- name = "jitescript",
- data = ["//lib:LICENSE-Apache2.0"],
+ name = "flexmark-ext-abbreviation",
+ data = ["//lib:LICENSE-flexmark"],
visibility = ["//visibility:public"],
- exports = ["@jitescript//jar"],
+ exports = ["@flexmark-ext-abbreviation//jar"],
+ runtime_deps = [
+ ":flexmark-ext-anchorlink",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-anchorlink",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-anchorlink//jar"],
+ runtime_deps = [
+ ":flexmark-ext-autolink",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-autolink",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-autolink//jar"],
+ runtime_deps = [
+ ":flexmark-ext-definition",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-definition",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-definition//jar"],
+ runtime_deps = [
+ ":flexmark-ext-emoji",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-emoji",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-emoji//jar"],
+ runtime_deps = [
+ ":flexmark-ext-escaped-character",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-escaped-character",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-escaped-character//jar"],
+ runtime_deps = [
+ ":flexmark-ext-footnotes",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-footnotes",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-footnotes//jar"],
+ runtime_deps = [
+ ":flexmark-ext-gfm-issues",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-gfm-issues",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-gfm-issues//jar"],
+ runtime_deps = [
+ ":flexmark-ext-gfm-strikethrough",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-gfm-strikethrough",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-gfm-strikethrough//jar"],
+ runtime_deps = [
+ ":flexmark-ext-gfm-tables",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-gfm-tables",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-gfm-tables//jar"],
+ runtime_deps = [
+ ":flexmark-ext-gfm-tasklist",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-gfm-tasklist",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-gfm-tasklist//jar"],
+ runtime_deps = [
+ ":flexmark-ext-gfm-users",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-gfm-users",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-gfm-users//jar"],
+ runtime_deps = [
+ ":flexmark-ext-ins",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-ins",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-ins//jar"],
+ runtime_deps = [
+ ":flexmark-ext-jekyll-front-matter",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-jekyll-front-matter",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-jekyll-front-matter//jar"],
+ runtime_deps = [
+ ":flexmark-ext-superscript",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-superscript",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-superscript//jar"],
+ runtime_deps = [
+ ":flexmark-ext-tables",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-tables",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-tables//jar"],
+ runtime_deps = [
+ ":flexmark-ext-toc",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-toc",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-toc//jar"],
+ runtime_deps = [
+ ":flexmark-ext-typographic",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-typographic",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-typographic//jar"],
+ runtime_deps = [
+ ":flexmark-ext-wikilink",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-wikilink",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-wikilink//jar"],
+ runtime_deps = [
+ ":flexmark-ext-yaml-front-matter",
+ ],
+)
+
+java_library(
+ name = "flexmark-ext-yaml-front-matter",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-ext-yaml-front-matter//jar"],
+ runtime_deps = [
+ ":flexmark-formatter",
+ ],
+)
+
+java_library(
+ name = "flexmark-formatter",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-formatter//jar"],
+ runtime_deps = [
+ ":flexmark-html-parser",
+ ],
+)
+
+java_library(
+ name = "flexmark-html-parser",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-html-parser//jar"],
+ runtime_deps = [
+ ":flexmark-profile-pegdown",
+ ],
+)
+
+java_library(
+ name = "flexmark-profile-pegdown",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-profile-pegdown//jar"],
+ runtime_deps = [
+ ":flexmark-util",
+ ],
+)
+
+java_library(
+ name = "flexmark-util",
+ data = ["//lib:LICENSE-flexmark"],
+ visibility = ["//visibility:public"],
+ exports = ["@flexmark-util//jar"],
+)
+
+java_library(
+ name = "autolink",
+ data = ["//lib:LICENSE-autolink"],
+ visibility = ["//visibility:public"],
+ exports = ["@autolink//jar"],
)
java_library(
diff --git a/lib/LICENSE-Apache1.1 b/lib/LICENSE-Apache1.1
deleted file mode 100644
index 8eda4fc..0000000
--- a/lib/LICENSE-Apache1.1
+++ /dev/null
@@ -1,51 +0,0 @@
-The Apache Software License, Version 1.1
-
-Copyright (c) 2000-2002 The Apache Software Foundation. All rights
-reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
-2. Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in
- the documentation and/or other materials provided with the
- distribution.
-
-3. The end-user documentation included with the redistribution,
- if any, must include the following acknowledgment:
- "This product includes software developed by the
- Apache Software Foundation (http://www.apache.org/)."
- Alternately, this acknowledgment may appear in the software itself,
- if and wherever such third-party acknowledgments normally appear.
-
-4. The names "Apache" and "Apache Software Foundation", "Jakarta-Oro"
- must not be used to endorse or promote products derived from this
- software without prior written permission. For written
- permission, please contact apache@apache.org.
-
-5. Products derived from this software may not be called "Apache"
- or "Jakarta-Oro", nor may "Apache" or "Jakarta-Oro" appear in their
- name, without prior written permission of the Apache Software Foundation.
-
-THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
-ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGE.
-====================================================================
-
-This software consists of voluntary contributions made by many
-individuals on behalf of the Apache Software Foundation. For more
-information on the Apache Software Foundation, please see
-<http://www.apache.org/>.
diff --git a/lib/LICENSE-autolink b/lib/LICENSE-autolink
new file mode 100644
index 0000000..565820a
--- /dev/null
+++ b/lib/LICENSE-autolink
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Robin Stocker
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/lib/LICENSE-flexmark b/lib/LICENSE-flexmark
new file mode 100644
index 0000000..c5e6ce0
--- /dev/null
+++ b/lib/LICENSE-flexmark
@@ -0,0 +1,26 @@
+Copyright (c) 2015-2016, Atlassian Pty Ltd
+All rights reserved.
+
+Copyright (c) 2016, Vladimir Schneider,
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/jgit/jgit.bzl b/lib/jgit/jgit.bzl
index ba34750..7191901 100644
--- a/lib/jgit/jgit.bzl
+++ b/lib/jgit/jgit.bzl
@@ -1,6 +1,6 @@
load("//tools/bzl:maven_jar.bzl", "GERRIT", "MAVEN_CENTRAL", "MAVEN_LOCAL", "maven_jar")
-_JGIT_VERS = "5.0.2.201807311906-r"
+_JGIT_VERS = "5.0.3.201809091024-r"
_DOC_VERS = _JGIT_VERS # Set to _JGIT_VERS unless using a snapshot
@@ -18,36 +18,50 @@
name = "jgit",
path = LOCAL_JGIT_REPO,
)
+ jgit_maven_repos_dev()
else:
jgit_maven_repos()
+def jgit_maven_repos_dev():
+ # Transitive dependencies from JGit's WORKSPACE.
+ maven_jar(
+ name = "hamcrest-library",
+ artifact = "org.hamcrest:hamcrest-library:1.3",
+ sha1 = "4785a3c21320980282f9f33d0d1264a69040538f",
+ )
+ maven_jar(
+ name = "jzlib",
+ artifact = "com.jcraft:jzlib:1.1.1",
+ sha1 = "a1551373315ffc2f96130a0e5704f74e151777ba",
+ )
+
def jgit_maven_repos():
maven_jar(
name = "jgit-lib",
artifact = "org.eclipse.jgit:org.eclipse.jgit:" + _JGIT_VERS,
repository = _JGIT_REPO,
- sha1 = "a81d7c8d153a8a744b6be1d9c6d698270beec1c0",
- src_sha1 = "c89f8f38cebaf75d13f9b2f7a1da71206d8c38f7",
+ sha1 = "0afec2df3ff8835bc4d5c279d14fad0daae6dd93",
+ src_sha1 = "e2c978064e2a46b260bbda0d8c393ed741046420",
unsign = True,
)
maven_jar(
name = "jgit-servlet",
artifact = "org.eclipse.jgit:org.eclipse.jgit.http.server:" + _JGIT_VERS,
repository = _JGIT_REPO,
- sha1 = "ab3d0c85bc2008da513c1127ab4acf3df8ef414e",
+ sha1 = "8fb0f9b6c38ac6fce60f2ead740e03dd79c3c288",
unsign = True,
)
maven_jar(
name = "jgit-archive",
artifact = "org.eclipse.jgit:org.eclipse.jgit.archive:" + _JGIT_VERS,
repository = _JGIT_REPO,
- sha1 = "ba6e0aaf3f733f2f460e227145526e1737ca160f",
+ sha1 = "72a157ce261f3eb938d9e0ee83d7c9700aa7d736",
)
maven_jar(
name = "jgit-junit",
artifact = "org.eclipse.jgit:org.eclipse.jgit.junit:" + _JGIT_VERS,
repository = _JGIT_REPO,
- sha1 = "fe28963520e19c918eb26747e678ec9772ba800f",
+ sha1 = "eb430358d96dedd923e4075cd54a7db4cab51ca2",
unsign = True,
)
diff --git a/plugins/BUILD b/plugins/BUILD
index ef18e01..a7622b2 100644
--- a/plugins/BUILD
+++ b/plugins/BUILD
@@ -27,17 +27,19 @@
]
EXPORTS = [
+ "//antlr3:query_parser",
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/index:query_exception",
- "//java/com/google/gerrit/index:query_parser",
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/metrics",
"//java/com/google/gerrit/metrics/dropwizard",
"//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server/audit",
+ "//java/com/google/gerrit/server/logging",
+ "//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/util/http",
"//lib/commons:compress",
"//lib/commons:dbcp",
@@ -100,13 +102,13 @@
main_class = "Dummy",
visibility = ["//visibility:public"],
runtime_deps = [
+ "//antlr3:libquery_parser-src.jar",
"//java/com/google/gerrit/common:libannotations-src.jar",
"//java/com/google/gerrit/common:libserver-src.jar",
"//java/com/google/gerrit/extensions:libapi-src.jar",
"//java/com/google/gerrit/httpd:libhttpd-src.jar",
"//java/com/google/gerrit/index:libindex-src.jar",
"//java/com/google/gerrit/index:libquery_exception-src.jar",
- "//java/com/google/gerrit/index:libquery_parser-src.jar",
"//java/com/google/gerrit/pgm/init/api:libapi-src.jar",
"//java/com/google/gerrit/reviewdb:libserver-src.jar",
"//java/com/google/gerrit/server:libserver-src.jar",
@@ -121,9 +123,9 @@
java_doc(
name = "plugin-api-javadoc",
libs = PLUGIN_API + [
+ "//antlr3:query_parser",
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/index:query_exception",
- "//java/com/google/gerrit/index:query_parser",
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
"//java/com/google/gerrit/extensions:api",
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index 4ebf98c..22342a6 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit 4ebf98c77086477a5fa63e339a539b47d4e8d202
+Subproject commit 22342a6da26c75b14bc629331c339d1b820b4d39
diff --git a/plugins/commit-message-length-validator b/plugins/commit-message-length-validator
index 315a115..4f6b685 160000
--- a/plugins/commit-message-length-validator
+++ b/plugins/commit-message-length-validator
@@ -1 +1 @@
-Subproject commit 315a11558913fa8f9c6d3b1723e45583b25afa1c
+Subproject commit 4f6b685e12e34a4f583cf84ba1c58ccc2b75e8b0
diff --git a/plugins/hooks b/plugins/hooks
index 07672f3..cc74144 160000
--- a/plugins/hooks
+++ b/plugins/hooks
@@ -1 +1 @@
-Subproject commit 07672f31880ba80300b38492df9d0acfcd6ee00a
+Subproject commit cc74144db755a18c5a63764a336b93ab3d1be1fe
diff --git a/plugins/replication b/plugins/replication
index 1086fac..d557ccc 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 1086faccd0cf2aa53977854767fdc77f048b0253
+Subproject commit d557ccc642c59a55750f560ce0d98870e1550d65
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
index e4024e9..cc636d7 160000
--- a/plugins/singleusergroup
+++ b/plugins/singleusergroup
@@ -1 +1 @@
-Subproject commit e4024e9d8d8139fc4c658c3af1a5e11e19b2d476
+Subproject commit cc636d7e36afb62455a9f045b125d246fd84afd0
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index 660f54d..c119562 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -90,7 +90,7 @@
1. [Build Gerrit](https://gerrit-review.googlesource.com/Documentation/dev-bazel.html#_gerrit_development_war_file)
2. Set up a local test site. Docs
- [here](https://gerrit-review.googlesource.com/Documentation/install-quick.html) and
+ [here](https://gerrit-review.googlesource.com/Documentation/linux-quickstart.html) and
[here](https://gerrit-review.googlesource.com/Documentation/dev-readme.html#init).
When your project is set up and works using the classic UI, run a test server
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group.html b/polygerrit-ui/app/elements/admin/gr-group/gr-group.html
index 1e19107..349cadc 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group.html
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group.html
@@ -23,7 +23,6 @@
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
<link rel="import" href="../../shared/gr-copy-clipboard/gr-copy-clipboard.html">
-<link rel="import" href="../../shared/gr-download-commands/gr-download-commands.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-select/gr-select.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
index ce0392d..799b831 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
@@ -213,7 +213,7 @@
for (const key in response) {
if (!response.hasOwnProperty(key)) { continue; }
projects.push({
- name: key,
+ name: response[key].name,
value: response[key].id,
});
}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.html b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.html
index f42652d..7db4e4c 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.html
@@ -27,7 +27,12 @@
}
</style>
<h3>[[title]]</h3>
- <gr-button on-tap="_onCommandTap">[[title]]</gr-button>
+ <gr-button
+ title$="[[tooltip]]"
+ disabled$="[[disabled]]"
+ on-tap="_onCommandTap">
+ [[title]]
+ </gr-button>
</template>
<script src="gr-repo-command.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.js b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.js
index e49c4de..bcdb7f6 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.js
@@ -22,6 +22,8 @@
properties: {
title: String,
+ disabled: Boolean,
+ tooltip: String,
},
/**
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.html b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.html
index 935c967..dba01aa 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.html
@@ -50,7 +50,8 @@
on-command-tap="_handleEditRepoConfig">
</gr-repo-command>
<gr-repo-command
- title="Run GC"
+ title="[[_repoConfig.actions.gc.label]]"
+ tooltip="[[_repoConfig.actions.gc.title]]"
hidden$="[[!_repoConfig.actions.gc.enabled]]"
on-command-tap="_handleRunningGC">
</gr-repo-command>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js
index 5560972..5a8b5b1 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js
@@ -120,12 +120,7 @@
.then(repos => {
// Late response.
if (filter !== this._filter || !repos) { return; }
- this._repos = Object.keys(repos)
- .map(key => {
- const repo = repos[key];
- repo.name = key;
- return repo;
- });
+ this._repos = repos;
this._loading = false;
});
},
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
index 5977714..d84dec5 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
@@ -25,11 +25,20 @@
// gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
const DEFAULT_SECTIONS = [
{
+ // Changes with unpublished draft comments. This section is omitted when
+ // viewing other users, so we don't need to filter anything out.
+ name: 'Has unpublished drafts',
+ query: 'has:draft',
+ selfOnly: true,
+ hideIfEmpty: true,
+ },
+ {
// WIP open changes owned by viewing user. This section is omitted when
// viewing other users, so we don't need to filter anything out.
name: 'Work in progress',
query: 'is:open owner:${user} is:wip',
selfOnly: true,
+ hideIfEmpty: true,
},
{
// Non-WIP open changes owned by viewed user. Filter out changes ignored
@@ -58,7 +67,8 @@
// changes not owned by the viewing user (the one instance of
// 'owner:self' is intentional and implements this logic).
query: 'is:closed -is:ignored (-is:wip OR owner:self) ' +
- '(owner:${user} OR reviewer:${user} OR assignee:${user})',
+ '(owner:${user} OR reviewer:${user} OR assignee:${user} ' +
+ 'OR cc:${user})',
suffixForDashboard: '-age:4w limit:10',
},
];
@@ -160,16 +170,10 @@
_getUserDashboard(user, sections, title) {
sections = sections
.filter(section => (user === 'self' || !section.selfOnly))
- .map(section => {
- const dashboardSection = {
- name: section.name,
- query: section.query.replace(USER_PLACEHOLDER_PATTERN, user),
- };
- if (section.suffixForDashboard) {
- dashboardSection.suffixForDashboard = section.suffixForDashboard;
- }
- return dashboardSection;
- });
+ .map(section => Object.assign({}, section, {
+ name: section.name,
+ query: section.query.replace(USER_PLACEHOLDER_PATTERN, user),
+ }));
return Promise.resolve({title, sections});
},
@@ -197,45 +201,57 @@
// in an async so that attachment to the DOM can take place first.
const title = params.title || this._computeTitle(user);
this.async(() => this.fire('title-change', {title}));
+ return this._reload();
+ },
+ /**
+ * Reloads the element.
+ *
+ * @return {Promise<!Object>}
+ */
+ _reload() {
this._loading = true;
-
- const dashboardPromise = params.project ?
- this._getProjectDashboard(params.project, params.dashboard) :
+ const {project, dashboard, title, user, sections} = this.params;
+ const dashboardPromise = project ?
+ this._getProjectDashboard(project, dashboard) :
this._getUserDashboard(
- params.user || 'self',
- params.sections || DEFAULT_SECTIONS,
- params.title || this._computeTitle(params.user));
+ user || 'self',
+ sections || DEFAULT_SECTIONS,
+ title || this._computeTitle(user));
- return dashboardPromise.then(dashboard => {
- if (!dashboard) {
- this._loading = false;
- return;
+ return dashboardPromise.then(this._fetchDashboardChanges.bind(this))
+ .then(() => {
+ this.$.reporting.dashboardDisplayed();
+ }).catch(err => {
+ console.warn(err);
+ }).then(() => { this._loading = false; });
+ },
+
+ /**
+ * Fetches the changes for each dashboard section and sets this._results
+ * with the response.
+ *
+ * @param {!Object} res
+ * @return {Promise}
+ */
+ _fetchDashboardChanges(res) {
+ if (!res) { return Promise.resolve(); }
+ const queries = res.sections.map(section => {
+ if (section.suffixForDashboard) {
+ return section.query + ' ' + section.suffixForDashboard;
}
- const queries = dashboard.sections.map(section => {
- if (section.suffixForDashboard) {
- return section.query + ' ' + section.suffixForDashboard;
- }
- return section.query;
- });
- const req =
- this.$.restAPI.getChanges(null, queries, null, this.options);
- return req.then(response => {
- this._loading = false;
- this._results = response.map((results, i) => {
- return {
- sectionName: dashboard.sections[i].name,
- query: dashboard.sections[i].query,
- results,
- };
- });
- });
- }).then(() => {
- this.$.reporting.dashboardDisplayed();
- }).catch(err => {
- this._loading = false;
- console.warn(err);
+ return section.query;
});
+
+ return this.$.restAPI.getChanges(null, queries, null, this.options)
+ .then(changes => {
+ this._results = changes.map((results, i) => ({
+ sectionName: res.sections[i].name,
+ query: res.sections[i].query,
+ results,
+ })).filter((section, i) => !res.sections[i].hideIfEmpty ||
+ section.results.length);
+ });
},
_computeUserHeaderClass(userParam) {
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
index a1da018..cac2627 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
@@ -207,8 +207,11 @@
sections: [
{name: 'section 1', query: 'query 1'},
{name: 'section 2', query: 'query 2 for self'},
- {name: 'section 3', query: 'self only query'},
{
+ name: 'section 3',
+ query: 'self only query',
+ selfOnly: true,
+ }, {
name: 'section 4',
query: 'query 4',
suffixForDashboard: 'suffix',
@@ -239,6 +242,21 @@
});
});
+ test('hideIfEmpty sections', () => {
+ const sections = [
+ {name: 'test1', query: 'test1', hideIfEmpty: true},
+ {name: 'test2', query: 'test2', hideIfEmpty: true},
+ ];
+ getChangesStub.restore();
+ sandbox.stub(element.$.restAPI, 'getChanges')
+ .returns(Promise.resolve([[], ['nonempty']]));
+
+ return element._fetchDashboardChanges({sections}).then(() => {
+ assert.equal(element._results.length, 1);
+ assert.equal(element._results[0].sectionName, 'test2');
+ });
+ });
+
test('_computeUserHeaderClass', () => {
assert.equal(element._computeUserHeaderClass(undefined), '');
assert.equal(element._computeUserHeaderClass(''), '');
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 b12b906..6af94a1 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
@@ -212,6 +212,12 @@
* @event show-alert
*/
+ /**
+ * Fires when a change action fails.
+ *
+ * @event show-error
+ */
+
properties: {
/**
* @type {{
@@ -738,7 +744,7 @@
} else if (!values.includes(a)) {
return;
}
- actions[a].label = this._getActionLabel(actions[a], type);
+ actions[a].label = this._getActionLabel(actions[a]);
// Triggers a re-render by ensuring object inequality.
result.push(Object.assign({}, actions[a]));
@@ -768,15 +774,15 @@
* Given a change action, return a display label that uses the appropriate
* casing or includes explanatory details.
*/
- _getActionLabel(action, type) {
- if (action.label === 'Delete' && type === ActionType.CHANGE) {
+ _getActionLabel(action) {
+ if (action.label === 'Delete') {
// This label is common within change and revision actions. Make it more
// explicit to the user.
return 'Delete change';
- } else if (action.label === 'WIP' && type === ActionType.CHANGE) {
+ } else if (action.label === 'WIP') {
return 'Mark as work in progress';
}
- // Otherwise, just map the anme to sentence case.
+ // Otherwise, just map the name to sentence case.
return this._toSentenceCase(action.label);
},
@@ -1154,7 +1160,7 @@
_handleResponseError(response) {
return response.text().then(errText => {
- this.fire('show-alert',
+ this.fire('show-error',
{message: `Could not perform action: ${errText}`});
if (!errText.startsWith('Change is already up to date')) {
throw Error(errText);
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 cfd2c45..91b39de 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
@@ -1370,6 +1370,7 @@
suite('_send', () => {
let cleanup;
let payload;
+ let onShowError;
let onShowAlert;
setup(() => {
@@ -1378,6 +1379,8 @@
element.latestPatchNum = 12;
payload = {foo: 'bar'};
+ onShowError = sinon.stub();
+ element.addEventListener('show-error', onShowError);
onShowAlert = sinon.stub();
element.addEventListener('show-alert', onShowAlert);
});
@@ -1395,7 +1398,7 @@
test('change action', () => {
return element._send('DELETE', payload, '/endpoint', false, cleanup)
.then(() => {
- assert.isFalse(onShowAlert.called);
+ assert.isFalse(onShowError.called);
assert.isTrue(cleanup.calledOnce);
assert.isTrue(sendStub.calledWith(42, 'DELETE', '/endpoint',
null, payload));
@@ -1405,7 +1408,7 @@
test('revision action', () => {
return element._send('DELETE', payload, '/endpoint', true, cleanup)
.then(() => {
- assert.isFalse(onShowAlert.called);
+ assert.isFalse(onShowError.called);
assert.isTrue(cleanup.calledOnce);
assert.isTrue(sendStub.calledWith(42, 'DELETE', '/endpoint',
12, payload));
@@ -1423,6 +1426,7 @@
return element._send('DELETE', payload, '/endpoint', true, cleanup)
.then(() => {
assert.isTrue(onShowAlert.calledOnce);
+ assert.isFalse(onShowError.called);
assert.isTrue(cleanup.calledOnce);
assert.isFalse(sendStub.called);
});
@@ -1441,7 +1445,7 @@
return element._send('DELETE', payload, '/endpoint', true, cleanup)
.then(() => {
- assert.isFalse(onShowAlert.called);
+ assert.isFalse(onShowError.called);
assert.isTrue(cleanup.called);
assert.isTrue(sendStub.calledOnce);
assert.isTrue(handleErrorStub.called);
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html
index 939ac67..1431887 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html
@@ -56,7 +56,6 @@
float: right;
}
.title {
- font-weight: bold;
min-width: 10em;
padding: .75em .5em 0 var(--requirements-horizontal-padding);
vertical-align: top;
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 76d7f1e..461bfc4 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
@@ -606,6 +606,7 @@
</gr-overlay>
<gr-overlay id="uploadHelpOverlay" with-backdrop>
<gr-upload-help-dialog
+ target-branch="[[_change.branch]]"
on-close="_handleCloseUploadHelpDialog"></gr-upload-help-dialog>
</gr-overlay>
<gr-overlay id="includedInOverlay" with-backdrop>
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 e905e038..ebd72ad 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
@@ -122,7 +122,7 @@
_changeComments: Object,
_canStartReview: {
type: Boolean,
- computed: '_computeCanStartReview(_loggedIn, _change, _account)',
+ computed: '_computeCanStartReview(_change)',
},
_comments: Object,
/** @type {?} */
@@ -1341,9 +1341,9 @@
});
},
- _computeCanStartReview(loggedIn, change, account) {
- return !!(loggedIn && change.work_in_progress &&
- change.owner._account_id === account._account_id);
+ _computeCanStartReview(change) {
+ return !!(change.actions && change.actions.ready &&
+ change.actions.ready.enabled);
},
_computeReplyDisabled() { return false; },
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 b5b8cd9..4e6cb6e 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
@@ -1411,18 +1411,24 @@
});
test('canStartReview computation', () => {
- const account1 = {_account_id: 1};
- const account2 = {_account_id: 2};
- const change = {
- owner: {_account_id: 1},
+ const change1 = {};
+ const change2 = {
+ actions: {
+ ready: {
+ enabled: true,
+ },
+ },
};
- assert.isFalse(element._computeCanStartReview(true, change, account1));
- change.work_in_progress = false;
- assert.isFalse(element._computeCanStartReview(true, change, account1));
- change.work_in_progress = true;
- assert.isTrue(element._computeCanStartReview(true, change, account1));
- assert.isFalse(element._computeCanStartReview(false, change, account1));
- assert.isFalse(element._computeCanStartReview(true, change, account2));
+ const change3 = {
+ actions: {
+ ready: {
+ label: 'Ready for Review',
+ },
+ },
+ };
+ assert.isFalse(element._computeCanStartReview(change1));
+ assert.isTrue(element._computeCanStartReview(change2));
+ assert.isFalse(element._computeCanStartReview(change3));
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
index 954507e..9996abc 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
@@ -40,6 +40,7 @@
setup(() => {
element = fixture('basic');
sandbox = sinon.sandbox.create();
+ sandbox.stub(Gerrit.Nav, 'mapCommentlinks', x => x);
});
teardown(() => { sandbox.restore(); });
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html
index 96a5454..b6c8fcc 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html
@@ -39,7 +39,6 @@
has-tooltip
button-title="Copy full SHA to clipboard"
hide-input
- hide-label
text="[[commitInfo.commit]]">
</gr-copy-clipboard>
</div>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index ef423fb..fa984c9 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -23,7 +23,7 @@
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
-<link rel="import" href="../../diff/gr-diff/gr-diff.html">
+<link rel="import" href="../../diff/gr-diff-host/gr-diff-host.html">
<link rel="import" href="../../diff/gr-diff-cursor/gr-diff-cursor.html">
<link rel="import" href="../../edit/gr-edit-file-controls/gr-edit-file-controls.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -389,8 +389,9 @@
</div>
<template is="dom-if"
if="[[_isFileExpanded(file.__path, _expandedFilePaths.*)]]">
- <gr-diff
+ <gr-diff-host
no-auto-render
+ show-load-failure
display-line="[[_displayLine]]"
inline-index=[[index]]
hidden="[[!_isFileExpanded(file.__path, _expandedFilePaths.*)]]"
@@ -402,7 +403,7 @@
project-config="[[projectConfig]]"
on-line-selected="_onLineSelected"
no-render-on-prefs-change
- view-mode="[[diffViewMode]]"></gr-diff>
+ view-mode="[[diffViewMode]]"></gr-diff-host>
</template>
</div>
</template>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index d3b4043..f54e058 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -283,7 +283,7 @@
},
get diffs() {
- return Polymer.dom(this.root).querySelectorAll('gr-diff');
+ return Polymer.dom(this.root).querySelectorAll('gr-diff-host');
},
openDiffPrefs() {
@@ -843,10 +843,9 @@
},
_updateDiffCursor() {
- const diffElements = Polymer.dom(this.root).querySelectorAll('gr-diff');
// Overwrite the cursor's list of diffs:
this.$.diffCursor.splice(
- ...['diffs', 0, this.$.diffCursor.diffs.length].concat(diffElements));
+ ...['diffs', 0, this.$.diffCursor.diffs.length].concat(this.diffs));
},
_filesChanged() {
@@ -976,7 +975,7 @@
* for each path in order, awaiting the previous render to complete before
* continung.
* @param {!Array<string>} paths
- * @param {!NodeList<!Object>} diffElements (GrDiffElement)
+ * @param {!NodeList<!Object>} diffElements (GrDiffHostElement)
* @param {number} initialCount The total number of paths in the pass. This
* is used to generate log messages.
* @return {!Promise}
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index c1a6c05..88b5f66 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -69,7 +69,7 @@
stub('gr-date-formatter', {
_loadTimeFormat() { return Promise.resolve(''); },
});
- stub('gr-diff', {
+ stub('gr-diff-host', {
reload() { return Promise.resolve(); },
});
@@ -830,7 +830,7 @@
// Click inside the diff. This should result in no additional calls to
// _togglePathExpanded or _reviewFile.
- Polymer.dom(element.root).querySelector('gr-diff').click();
+ Polymer.dom(element.root).querySelector('gr-diff-host').click();
assert.isTrue(tapSpy.calledTwice);
assert.isTrue(toggleExpandSpy.calledOnce);
assert.isFalse(reviewStub.called);
@@ -1024,6 +1024,8 @@
done();
},
cancel() {},
+ getCursorStops() { return []; },
+ addEventListener(eventName, callback) { callback(new Event(eventName)); },
}];
sinon.stub(element, 'diffs', {
get() { return diffs; },
@@ -1354,7 +1356,6 @@
const setupDiff = function(diff) {
const mock = document.createElement('mock-diff-response');
- diff._diff = mock.diffResponse;
diff.comments = {
left: diff.path === '/COMMIT_MSG' ? commitMsgComments : [],
right: [],
@@ -1382,12 +1383,12 @@
theme: 'DEFAULT',
ignore_whitespace: 'IGNORE_NONE',
};
- diff._renderDiffTable();
+ diff._diff = mock.diffResponse;
};
const renderAndGetNewDiffs = function(index) {
const diffs =
- Polymer.dom(element.root).querySelectorAll('gr-diff');
+ Polymer.dom(element.root).querySelectorAll('gr-diff-host');
for (let i = index; i < diffs.length; i++) {
setupDiff(diffs[i]);
@@ -1410,7 +1411,7 @@
stub('gr-date-formatter', {
_loadTimeFormat() { return Promise.resolve(''); },
});
- stub('gr-diff', {
+ stub('gr-diff-host', {
reload() { return Promise.resolve(); },
});
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
index 9a96120..a7abf85 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
@@ -211,15 +211,37 @@
},
/**
- * Do the given objects describe the same change?
- * (Compares the change objects by their change numbers.)
+ * Do the given objects describe the same change? Compares the changes by
+ * their numbers.
* @see /Documentation/rest-api-changes.html#change-info
- * @param {!Object} a
- * @param {!Object} b
+ * @see /Documentation/rest-api-changes.html#related-change-and-commit-info
+ * @param {!Object} a Either ChangeInfo or RelatedChangeAndCommitInfo
+ * @param {!Object} b Either ChangeInfo or RelatedChangeAndCommitInfo
* @return {boolean}
*/
_changesEqual(a, b) {
- return a._number === b._number;
+ const aNum = this._getChangeNumber(a);
+ const bNum = this._getChangeNumber(b);
+ return aNum === bNum;
+ },
+
+ /**
+ * Get the change number from either a ChangeInfo (such as those included in
+ * SubmittedTogetherInfo responses) or get the change number from a
+ * RelatedChangeAndCommitInfo (such as those included in a
+ * RelatedChangesInfo response).
+ * @see /Documentation/rest-api-changes.html#change-info
+ * @see /Documentation/rest-api-changes.html#related-change-and-commit-info
+ *
+ * @param {!Object} change Either a ChangeInfo or a
+ * RelatedChangeAndCommitInfo object.
+ * @return {number}
+ */
+ _getChangeNumber(change) {
+ if (change.hasOwnProperty('_change_number')) {
+ return change._change_number;
+ }
+ return change._number;
},
_computeLinkClass(change) {
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
index c208f01..ef4af16 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
@@ -224,7 +224,7 @@
test('_computeChangeContainerClass', () => {
const change1 = {change_id: 123, _number: 0};
- const change2 = {change_id: 456, _number: 1};
+ const change2 = {change_id: 456, _change_number: 1};
const change3 = {change_id: 123, _number: 2};
assert.notEqual(element._computeChangeContainerClass(
@@ -239,10 +239,19 @@
const change1 = {change_id: 123, _number: 0};
const change2 = {change_id: 456, _number: 1};
const change3 = {change_id: 123, _number: 2};
+ const change4 = {change_id: 123, _change_number: 1};
assert.isTrue(element._changesEqual(change1, change1));
assert.isFalse(element._changesEqual(change1, change2));
assert.isFalse(element._changesEqual(change1, change3));
+ assert.isTrue(element._changesEqual(change2, change4));
+ });
+
+ test('_getChangeNumber', () => {
+ const change1 = {change_id: 123, _number: 0};
+ const change2 = {change_id: 456, _change_number: 1};
+ assert.equal(element._getChangeNumber(change1), 0);
+ assert.equal(element._getChangeNumber(change2), 1);
});
test('event for section loaded fires for each section ', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.html b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.html
index 072e7113..a9843a3 100644
--- a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.html
@@ -17,6 +17,7 @@
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
+<link rel="import" href="../../shared/gr-shell-command/gr-shell-command.html">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-upload-help-dialog">
@@ -33,32 +34,9 @@
margin-left: 1em;
list-style: decimal;
}
- p,
- .commandContainer {
+ p {
margin-bottom: .75em;
}
- .commandContainer {
- background: #f5f5f5;
- padding: .5em .5em .5em 2.5em;
- position: relative;
- width: 100%;
- }
- .commandContainer:before {
- background: #ebebeb;
- bottom: 0;
- content: '$';
- display: block;
- left: 0;
- padding: .8em;
- position: absolute;
- top: 0;
- width: 2em;
- }
- .commandContainer gr-copy-clipboard {
- --text-container-style: {
- border: none;
- }
- }
</style>
<gr-dialog
confirm-label="Done"
@@ -78,18 +56,14 @@
Update the local commit with your modifications using the following
command.
</p>
- <div class="commandContainer">
- <gr-copy-clipboard text="[[_commitCommand]]"></gr-copy-clipboard>
- </div>
+ <gr-shell-command command="[[_commitCommand]]"></gr-shell-command>
<p>
Leave the "Change-Id:" line of the commit message as is.
</p>
</li>
<li>
<p>Push the updated commit to Gerrit.</p>
- <div class="commandContainer">
- <gr-copy-clipboard text="[[_pushCommand]]"></gr-copy-clipboard>
- </div>
+ <gr-shell-command command="[[_pushCommand]]"></gr-shell-command>
</li>
<li>
<p>Refresh this page to view the the update.</p>
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js
index d796999..548116c 100644
--- a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js
@@ -18,7 +18,7 @@
'use strict';
const COMMIT_COMMAND = 'git add . && git commit --amend --no-edit';
- const PUSH_COMMAND = 'git push origin HEAD:refs/for/master';
+ const PUSH_COMMAND_PREFIX = 'git push origin HEAD:refs/for/';
Polymer({
is: 'gr-upload-help-dialog',
@@ -30,6 +30,7 @@
*/
properties: {
+ targetBranch: String,
_commitCommand: {
type: String,
value: COMMIT_COMMAND,
@@ -37,8 +38,7 @@
},
_pushCommand: {
type: String,
- value: PUSH_COMMAND,
- readOnly: true,
+ computed: '_computePushCommand(targetBranch)',
},
},
@@ -46,5 +46,9 @@
e.preventDefault();
this.fire('close', null, {bubbles: false});
},
+
+ _computePushCommand(targetBranch) {
+ return PUSH_COMMAND_PREFIX + targetBranch;
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
new file mode 100644
index 0000000..60fe3e6
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2018 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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-upload-help-dialog</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-upload-help-dialog.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-upload-help-dialog></gr-upload-help-dialog>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-upload-help-dialog tests', () => {
+ let element;
+
+ setup(() => {
+ element = fixture('basic');
+ });
+
+ test('constructs push command from branch', () => {
+ element.targetBranch = 'foo';
+ assert.equal(element._pushCommand, 'git push origin HEAD:refs/for/foo');
+
+ element.targetBranch = 'master';
+ assert.equal(element._pushCommand,
+ 'git push origin HEAD:refs/for/master');
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.html b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.html
new file mode 100644
index 0000000..f8bf33c
--- /dev/null
+++ b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.html
@@ -0,0 +1,49 @@
+<!--
+@license
+Copyright (C) 2018 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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+
+<dom-module id="gr-error-dialog">
+ <template>
+ <style include="shared-styles">
+ .main {
+ max-height: 40em;
+ max-width: 60em;
+ overflow-y: auto;
+ white-space: pre-wrap;
+ }
+ @media screen and (max-width: 50em) {
+ .main {
+ max-height: none;
+ max-width: 50em;
+ }
+ }
+ </style>
+ <gr-dialog
+ id="dialog"
+ cancel-label=""
+ on-confirm="_handleConfirm"
+ confirm-label="Dismiss"
+ confirm-on-enter>
+ <div class="header" slot="header">An error occurred</div>
+ <div class="main" slot="main">[[text]]</div>
+ </gr-dialog>
+ </template>
+ <script src="gr-error-dialog.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.js b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.js
new file mode 100644
index 0000000..8d3b58e
--- /dev/null
+++ b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.js
@@ -0,0 +1,37 @@
+/**
+ * @license
+ * Copyright (C) 2018 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.
+ */
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-error-dialog',
+
+ /**
+ * Fired when the dismiss button is pressed.
+ *
+ * @event dismiss
+ */
+
+ properties: {
+ text: String,
+ },
+
+ _handleConfirm() {
+ this.dispatchEvent(new CustomEvent('dismiss'));
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html
new file mode 100644
index 0000000..e2c314b
--- /dev/null
+++ b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2018 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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-error-dialog</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-error-dialog.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-error-dialog></gr-error-dialog>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-error-dialog tests', () => {
+ let element;
+
+ setup(() => {
+ element = fixture('basic');
+ });
+
+ test('dismiss tap fires event', done => {
+ element.addEventListener('dismiss', () => { done(); });
+ MockInteractions.tap(element.$.dialog.$.confirm);
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.html b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.html
index 95c5403..3ec4bb5 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.html
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.html
@@ -17,11 +17,20 @@
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../core/gr-error-dialog/gr-error-dialog.html">
<link rel="import" href="../../shared/gr-alert/gr-alert.html">
+<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-error-manager">
<template>
+ <gr-overlay with-backdrop id="errorOverlay">
+ <gr-error-dialog
+ id="errorDialog"
+ on-dismiss="_handleDismissErrorDialog"
+ confirm-label="Dismiss"
+ confirm-on-enter></gr-error-dialog>
+ </gr-overlay>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-error-manager.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
index 758148e..e3596e7 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
@@ -62,6 +62,7 @@
this.listen(document, 'network-error', '_handleNetworkError');
this.listen(document, 'auth-error', '_handleAuthError');
this.listen(document, 'show-alert', '_handleShowAlert');
+ this.listen(document, 'show-error', '_handleShowErrorDialog');
this.listen(document, 'visibilitychange', '_handleVisibilityChange');
this.listen(document, 'show-auth-required', '_handleAuthRequired');
},
@@ -73,6 +74,7 @@
this.unlisten(document, 'auth-error', '_handleAuthError');
this.unlisten(document, 'show-auth-required', '_handleAuthRequired');
this.unlisten(document, 'visibilitychange', '_handleVisibilityChange');
+ this.unlisten(document, 'show-error', '_handleShowErrorDialog');
},
_shouldSuppressError(msg) {
@@ -101,7 +103,7 @@
// This indicates the auth token is no longer valid.
this._handleAuthError();
} else if (!this._shouldSuppressError(text)) {
- this._showAlert('Server error: ' + text);
+ this._showErrorDialog('Server error: ' + text);
}
console.error(text);
});
@@ -257,5 +259,18 @@
_handleWindowFocus() {
this.flushDebouncer('checkLoggedIn');
},
+
+ _handleShowErrorDialog(e) {
+ this._showErrorDialog(e.detail.message);
+ },
+
+ _handleDismissErrorDialog() {
+ this.$.errorOverlay.close();
+ },
+
+ _showErrorDialog(message) {
+ this.$.errorDialog.text = message;
+ this.$.errorOverlay.open();
+ },
});
})();
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 c4ba8d2..e28b979 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
@@ -87,7 +87,7 @@
});
test('show normal server error', done => {
- const showAlertStub = sandbox.stub(element, '_showAlert');
+ const showErrorStub = sandbox.stub(element, '_showErrorDialog');
const textSpy = sandbox.spy(() => { return Promise.resolve('ZOMG'); });
element.fire('server-error', {response: {status: 500, text: textSpy}});
@@ -96,8 +96,8 @@
element.$.restAPI.getLoggedIn.lastCall.returnValue,
textSpy.lastCall.returnValue,
]).then(() => {
- assert.isTrue(showAlertStub.calledOnce);
- assert.isTrue(showAlertStub.lastCall.calledWithExactly(
+ assert.isTrue(showErrorStub.calledOnce);
+ assert.isTrue(showErrorStub.lastCall.calledWithExactly(
'Server error: ZOMG'));
done();
});
@@ -279,5 +279,21 @@
element._showAlert();
assert.isTrue(hideStub.calledOnce);
});
+
+ test('show-error', () => {
+ const openStub = sandbox.stub(element.$.errorOverlay, 'open');
+ const closeStub = sandbox.stub(element.$.errorOverlay, 'close');
+ const message = 'test message';
+ element.fire('show-error', {message});
+ flushAsynchronousOperations();
+
+ assert.isTrue(openStub.called);
+ assert.equal(element.$.errorDialog.text, message);
+
+ element.$.errorDialog.fire('dismiss');
+ flushAsynchronousOperations();
+
+ assert.isTrue(closeStub.called);
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
index a700ccd..c5e32cc 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
@@ -71,10 +71,15 @@
// - `detail`, optional, String: the name of the repo detail view.
// Takes any value from Gerrit.Nav.RepoDetailView.
//
+ // - Gerrit.Nav.View.DASHBOARD
+ // - `repo`, optional, String.
+ // - `sections`, optional, Array of objects with `title` and `query`
+ // strings.
+ // - `user`, optional, String.
+ //
// - Gerrit.Nav.View.ROOT:
// - no possible parameters.
-
window.Gerrit = window.Gerrit || {};
// Prevent redefinition.
@@ -132,6 +137,9 @@
/** @type {Function} */
_generateWeblinks: uninitialized,
+ /** @type {Function} */
+ mapCommentlinks: uninitialized,
+
/**
* @param {number=} patchNum
* @param {number|string=} basePatchNum
@@ -144,20 +152,38 @@
/**
* Setup router implementation.
- * @param {Function} navigate
- * @param {Function} generateUrl
- * @param {Function} generateWeblinks
+ * @param {function(!string)} navigate the router-abstracted equivalent of
+ * `window.location.href = ...`. Takes a string.
+ * @param {function(!Object): string} generateUrl generates a URL given
+ * navigation parameters, detailed in the file header.
+ * @param {function(!Object): string} generateWeblinks weblinks generator
+ * function takes single payload parameter with type property that
+ * determines which
+ * part of the UI is the consumer of the weblinks. type property can
+ * be one of file, change, or patchset.
+ * - For file type, payload will also contain string properties: repo,
+ * commit, file.
+ * - For patchset type, payload will also contain string properties:
+ * repo, commit.
+ * - For change type, payload will also contain string properties:
+ * repo, commit. If server provides weblinks, those will be passed
+ * as options.weblinks property on the main payload object.
+ * @param {function(!Object): Object} mapCommentlinks provides an escape
+ * hatch to modify the commentlinks object, e.g. if it contains any
+ * relative URLs.
*/
- setup(navigate, generateUrl, generateWeblinks) {
+ setup(navigate, generateUrl, generateWeblinks, mapCommentlinks) {
this._navigate = navigate;
this._generateUrl = generateUrl;
this._generateWeblinks = generateWeblinks;
+ this.mapCommentlinks = mapCommentlinks;
},
destroy() {
this._navigate = uninitialized;
this._generateUrl = uninitialized;
this._generateWeblinks = uninitialized;
+ this.mapCommentlinks = uninitialized;
},
/**
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index a72feb1..bdd0942 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -176,6 +176,8 @@
const LEGACY_QUERY_SUFFIX_PATTERN = /,n,z$/;
+ const REPO_TOKEN_PATTERN = /\$\{(project|repo)\}/g;
+
// Polymer makes `app` intrinsically defined on the window by virtue of the
// custom element having the id "app", but it is made explicit here.
const app = document.querySelector('#app');
@@ -394,7 +396,8 @@
suffix += ',edit';
}
if (params.project) {
- return `/c/${params.project}/+/${params.changeNum}${suffix}`;
+ const encodedProject = this.encodeURL(params.project, true);
+ return `/c/${encodedProject}/+/${params.changeNum}${suffix}`;
} else {
return `/c/${params.changeNum}${suffix}`;
}
@@ -405,20 +408,19 @@
* @return {string}
*/
_generateDashboardUrl(params) {
+ const repoName = params.repo || params.project || null;
if (params.sections) {
// Custom dashboard.
- const queryParams = params.sections.map(section => {
- return encodeURIComponent(section.name) + '=' +
- encodeURIComponent(section.query);
- });
+ const queryParams = this._sectionsToEncodedParams(params.sections,
+ repoName);
if (params.title) {
queryParams.push('title=' + encodeURIComponent(params.title));
}
const user = params.user ? params.user : '';
return `/dashboard/${user}?${queryParams.join('&')}`;
- } else if (params.project) {
+ } else if (repoName) {
// Project dashboard.
- return `/p/${params.project}/+/dashboard/${params.dashboard}`;
+ return `/p/${repoName}/+/dashboard/${params.dashboard}`;
} else {
// User dashboard.
return `/dashboard/${params.user || 'self'}`;
@@ -426,6 +428,23 @@
},
/**
+ * @param {!Array<!{name: string, query: string}>} sections
+ * @param {string=} opt_repoName
+ * @return {!Array<string>}
+ */
+ _sectionsToEncodedParams(sections, opt_repoName) {
+ return sections.map(section => {
+ // If there is a repo name provided, make sure to substitute it into the
+ // ${repo} (or legacy ${project}) query tokens.
+ const query = opt_repoName ?
+ section.query.replace(REPO_TOKEN_PATTERN, opt_repoName) :
+ section.query;
+ return encodeURIComponent(section.name) + '=' +
+ encodeURIComponent(query);
+ });
+ },
+
+ /**
* @param {!Object} params
* @return {string}
*/
@@ -444,7 +463,8 @@
}
if (params.project) {
- return `/c/${params.project}/+/${params.changeNum}${suffix}`;
+ const encodedProject = this.encodeURL(params.project, true);
+ return `/c/${encodedProject}/+/${params.changeNum}${suffix}`;
} else {
return `/c/${params.changeNum}${suffix}`;
}
@@ -660,7 +680,8 @@
Gerrit.Nav.setup(
url => { page.show(url); },
this._generateUrl.bind(this),
- params => this._generateWeblinks(params)
+ params => this._generateWeblinks(params),
+ x => x
);
page.exit('*', (ctx, next) => {
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index b68a5e9..53a7c07 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -283,6 +283,16 @@
'/c/test/+/1234/5..10?revert&foo=bar');
});
+ test('change with repo name encoding', () => {
+ const params = {
+ view: Gerrit.Nav.View.CHANGE,
+ changeNum: '1234',
+ project: 'x+/y+/z+/w',
+ };
+ assert.equal(element._generateUrl(params),
+ '/c/x%252B/y%252B/z%252B/w/+/1234');
+ });
+
test('diff', () => {
const params = {
view: Gerrit.Nav.View.DIFF,
@@ -317,6 +327,18 @@
'/c/test/+/42/2/file.cpp#b123');
});
+ test('diff with repo name encoding', () => {
+ const params = {
+ view: Gerrit.Nav.View.DIFF,
+ changeNum: '42',
+ path: 'x+y/path.cpp',
+ patchNum: 12,
+ project: 'x+/y',
+ };
+ assert.equal(element._generateUrl(params),
+ '/c/x%252B/y/+/42/12/x%252By/path.cpp');
+ });
+
test('edit', () => {
const params = {
view: Gerrit.Nav.View.EDIT,
@@ -375,6 +397,21 @@
'/dashboard/?section%201=query%201§ion%202=query%202');
});
+ test('custom repo dashboard', () => {
+ const params = {
+ view: Gerrit.Nav.View.DASHBOARD,
+ sections: [
+ {name: 'section 1', query: 'query 1 ${project}'},
+ {name: 'section 2', query: 'query 2 ${repo}'},
+ ],
+ repo: 'repo-name',
+ };
+ assert.equal(
+ element._generateUrl(params),
+ '/dashboard/?section%201=query%201%20repo-name&' +
+ 'section%202=query%202%20repo-name');
+ });
+
test('custom user dashboard, with title', () => {
const params = {
view: Gerrit.Nav.View.DASHBOARD,
@@ -387,7 +424,18 @@
'/dashboard/user?name=query&title=custom%20dashboard');
});
- test('project dashboard', () => {
+ test('repo dashboard', () => {
+ const params = {
+ view: Gerrit.Nav.View.DASHBOARD,
+ repo: 'gerrit/repo',
+ dashboard: 'default:main',
+ };
+ assert.equal(
+ element._generateUrl(params),
+ '/p/gerrit/repo/+/dashboard/default:main');
+ });
+
+ test('project dashboard (legacy)', () => {
const params = {
view: Gerrit.Nav.View.DASHBOARD,
project: 'gerrit/project',
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
index 657c8e0..3a48213 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
@@ -30,7 +30,7 @@
gr-autocomplete {
background-color: var(--view-background-color);
border: 1px solid var(--border-color);
- border-radius: 2px 0 0 2px;
+ border-radius: 2px;
flex: 1;
font: inherit;
outline: none;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js
index cb768ef..88ff79b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js
@@ -35,7 +35,6 @@
GrDiffBuilderImage.prototype.constructor = GrDiffBuilderImage;
GrDiffBuilderImage.prototype.renderDiff = function() {
- this._outputEl.classList.add('image-diff');
const section = this._createElement('tbody', 'image-diff');
this._emitImagePair(section);
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 8a89937..9668a54 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
@@ -35,6 +35,7 @@
<mock-diff-response></mock-diff-response>
<gr-diff></gr-diff>
<gr-diff-cursor></gr-diff-cursor>
+ <gr-rest-api-interface></gr-rest-api-interface>
</template>
</test-fixture>
@@ -48,27 +49,18 @@
setup(done => {
sandbox = sinon.sandbox.create();
- stub('gr-rest-api-interface', {
- getLoggedIn() { return Promise.resolve(false); },
- });
-
const fixtureElems = fixture('basic');
mockDiffResponse = fixtureElems[0];
diffElement = fixtureElems[1];
cursorElement = fixtureElems[2];
+ const restAPI = fixtureElems[3];
// Register the diff with the cursor.
cursorElement.push('diffs', diffElement);
+ diffElement.loggedIn = false;
+ diffElement.patchRange = {basePatchNum: 1, patchNum: 2};
diffElement.comments = {left: [], right: []};
- diffElement.$.restAPI.getDiffPreferences().then(prefs => {
- diffElement.prefs = prefs;
- });
-
- sandbox.stub(diffElement, '_getDiff', () => {
- return Promise.resolve(mockDiffResponse.diffResponse);
- });
-
const setupDone = () => {
cursorElement._updateStops();
cursorElement.moveToFirstChunk();
@@ -77,7 +69,10 @@
};
diffElement.addEventListener('render', setupDone);
- diffElement.reload();
+ restAPI.getDiffPreferences().then(prefs => {
+ diffElement.prefs = prefs;
+ diffElement.diff = mockDiffResponse.diffResponse;
+ });
});
teardown(() => sandbox.restore());
@@ -219,7 +214,7 @@
done();
}
diffElement.addEventListener('render', renderHandler);
- diffElement.reload();
+ diffElement._diffChanged(mockDiffResponse.diffResponse);
});
test('initialLineNumber enabled', done => {
@@ -239,7 +234,7 @@
cursorElement.initialLineNumber = 10;
cursorElement.side = 'right';
- diffElement.reload();
+ diffElement._diffChanged(mockDiffResponse.diffResponse);
});
test('getAddress', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
index a45f8ec..cee3cad 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
@@ -156,11 +156,10 @@
/**
* Adjust triple click selection for the whole line.
- * domRange.endContainer may be one of the following:
- * 1) 0 offset at right column's line number cell, or
- * 2) 0 offset at left column's line number at the next line.
- * Case 1 means left column was triple clicked.
- * Case 2 means right column or unified view triple clicked.
+ * A triple click always results in:
+ * - start.column == end.column == 0
+ * - end.line == start.line + 1
+ *
* @param {!Object} range Normalized range, ie column/line numbers
* @param {!Range} domRange DOM Range object
* @return {!Object} fixed normalized range
@@ -172,20 +171,13 @@
}
const start = range.start;
const end = range.end;
- const endsAtOtherSideLineNum =
- domRange.endOffset === 0 &&
- domRange.endContainer.nodeName === 'TD' &&
- (domRange.endContainer.classList.contains('left') ||
- domRange.endContainer.classList.contains('right'));
- const endsOnOtherSideStart = endsAtOtherSideLineNum ||
- end &&
+ const endsAtBeginningOfNextLine = end &&
+ start.column === 0 &&
end.column === 0 &&
- end.line === start.line &&
- end.side != start.side;
+ end.line === start.line + 1;
const content = domRange.cloneContents().querySelector('.contentText');
const lineLength = content && this._getLength(content) || 0;
- if (lineLength && endsOnOtherSideStart || endsAtOtherSideLineNum) {
- // Selection ends at the beginning of the next line.
+ if (lineLength && endsAtBeginningOfNextLine) {
// Move the selection to the end of the previous line.
range.end = {
node: start.node,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
index a82a11e..7b19338 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
@@ -576,54 +576,18 @@
assert.equal(result, 0);
});
- // TODO (viktard): Selection starts in line number.
- // TODO (viktard): Empty lines in selection start.
- // TODO (viktard): Empty lines in selection end.
- // TODO (viktard): Only empty lines selected.
- // TODO (viktard): Unified mode.
-
- suite('triple click', () => {
- test('_fixTripleClickSelection', () => {
- const fakeRange = {
- startContainer: '',
- startOffset: '',
- endContainer: '',
- endOffset: '',
- };
- const fixedRange = {};
- sandbox.stub(GrRangeNormalizer, 'normalize').returns(fakeRange);
- sandbox.stub(element, '_normalizeSelectionSide');
- sandbox.stub(element, '_fixTripleClickSelection').returns(fixedRange);
- assert.strictEqual(element._normalizeRange({}), fixedRange);
- assert.isTrue(element._fixTripleClickSelection.called);
+ test('_fixTripleClickSelection', () => {
+ const startContent = stubContent(119, 'right');
+ const endContent = stubContent(120, 'right');
+ emulateSelection(startContent.firstChild, 0, endContent.firstChild, 0);
+ assert.isTrue(element.isRangeSelected());
+ assert.deepEqual(getActionRange(), {
+ startLine: 119,
+ startChar: 0,
+ endLine: 119,
+ endChar: element._getLength(startContent),
});
-
- test('left pane', () => {
- const startNode = stubContent(138, 'left');
- const endNode =
- stubContent(119, 'right').parentElement.previousElementSibling;
- builder.getLineNumberByChild.withArgs(endNode).returns(119);
- emulateSelection(startNode, 0, endNode, 0);
- assert.deepEqual(getActionRange(), {
- startLine: 138,
- startChar: 0,
- endLine: 138,
- endChar: 63,
- });
- });
-
- test('right pane', () => {
- const startNode = stubContent(119, 'right');
- const endNode =
- stubContent(140, 'left').parentElement.previousElementSibling;
- emulateSelection(startNode, 0, endNode, 0);
- assert.deepEqual(getActionRange(), {
- startLine: 119,
- startChar: 0,
- endLine: 119,
- endChar: 63,
- });
- });
+ assert.equal(getActionSide(), 'right');
});
});
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.html b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.html
new file mode 100644
index 0000000..e3bf866
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.html
@@ -0,0 +1,55 @@
+<!--
+@license
+Copyright (C) 2018 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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+
+<link rel="import" href="../gr-diff/gr-diff.html">
+
+<dom-module id="gr-diff-host">
+ <template>
+ <gr-diff
+ id="diff"
+ change-num="[[changeNum]]"
+ no-auto-render=[[noAutoRender]]
+ patch-range="[[patchRange]]"
+ path="[[path]]"
+ prefs="[[prefs]]"
+ project-config="[[projectConfig]]"
+ project-name="[[projectName]]"
+ display-line="[[displayLine]]"
+ is-image-diff="[[isImageDiff]]"
+ commit-range="[[commitRange]]"
+ hidden$="[[hidden]]"
+ no-render-on-prefs-change="[[noRenderOnPrefsChange]]"
+ comments="[[comments]]"
+ line-wrapping="[[lineWrapping]]"
+ view-mode="[[viewMode]]"
+ line-of-interest="[[lineOfInterest]]"
+ logged-in="[[_loggedIn]]"
+ loading="[[_loading]]"
+ error-message="[[_errorMessage]]"
+ base-image="[[_baseImage]]"
+ revision-image=[[_revisionImage]]
+ blame="[[_blame]]"
+ diff="[[_diff]]"></gr-diff>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ <gr-reporting id="reporting" category="diff"></gr-reporting>
+ </template>
+ <script src="gr-diff-host.js"></script>
+</dom-module>
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
new file mode 100644
index 0000000..3e9e796
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
@@ -0,0 +1,434 @@
+/**
+ * @license
+ * Copyright (C) 2018 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.
+ */
+(function() {
+ 'use strict';
+
+ const MSG_EMPTY_BLAME = 'No blame information for this diff.';
+
+ const EVENT_AGAINST_PARENT = 'diff-against-parent';
+ const EVENT_ZERO_REBASE = 'rebase-percent-zero';
+ const EVENT_NONZERO_REBASE = 'rebase-percent-nonzero';
+
+ const DiffViewMode = {
+ SIDE_BY_SIDE: 'SIDE_BY_SIDE',
+ UNIFIED: 'UNIFIED_DIFF',
+ };
+
+ /**
+ * @param {Object} diff
+ * @return {boolean}
+ */
+ function isImageDiff(diff) {
+ if (!diff) { return false; }
+
+ const isA = diff.meta_a &&
+ diff.meta_a.content_type.startsWith('image/');
+ const isB = diff.meta_b &&
+ diff.meta_b.content_type.startsWith('image/');
+
+ return !!(diff.binary && (isA || isB));
+ }
+
+ /**
+ * Wrapper around gr-diff.
+ *
+ * Webcomponent fetching diffs and related data from restAPI and passing them
+ * to the presentational gr-diff for rendering.
+ */
+ // TODO(oler): Move all calls to restAPI from gr-diff here.
+ Polymer({
+ is: 'gr-diff-host',
+
+ /**
+ * Fired when the user selects a line.
+ * @event line-selected
+ */
+
+ /**
+ * Fired if being logged in is required.
+ *
+ * @event show-auth-required
+ */
+
+ /**
+ * Fired when a comment is saved or discarded
+ *
+ * @event diff-comments-modified
+ */
+
+ properties: {
+ changeNum: String,
+ noAutoRender: {
+ type: Boolean,
+ value: false,
+ },
+ /** @type {?} */
+ patchRange: Object,
+ path: String,
+ prefs: {
+ type: Object,
+ },
+ projectConfig: {
+ type: Object,
+ },
+ projectName: String,
+ displayLine: {
+ type: Boolean,
+ value: false,
+ },
+ isImageDiff: {
+ type: Boolean,
+ computed: '_computeIsImageDiff(_diff)',
+ notify: true,
+ },
+ commitRange: Object,
+ filesWeblinks: {
+ type: Object,
+ value() { return {}; },
+ notify: true,
+ },
+ hidden: {
+ type: Boolean,
+ reflectToAttribute: true,
+ },
+ noRenderOnPrefsChange: Boolean,
+ comments: Object,
+ lineWrapping: {
+ type: Boolean,
+ value: false,
+ },
+ viewMode: {
+ type: String,
+ value: DiffViewMode.SIDE_BY_SIDE,
+ },
+
+ /**
+ * Special line number which should not be collapsed into a shared region.
+ * @type {{
+ * number: number,
+ * leftSide: {boolean}
+ * }|null}
+ */
+ lineOfInterest: Object,
+
+ /**
+ * If the diff fails to load, show the failure message in the diff rather
+ * than bubbling the error up to the whole page. This is useful for when
+ * loading inline diffs because one diff failing need not mark the whole
+ * page with a failure.
+ */
+ showLoadFailure: Boolean,
+
+ isBlameLoaded: {
+ type: Boolean,
+ notify: true,
+ computed: '_computeIsBlameLoaded(_blame)',
+ },
+
+ _loggedIn: {
+ type: Boolean,
+ value: false,
+ },
+
+ _loading: {
+ type: Boolean,
+ value: false,
+ },
+
+ /** @type {?string} */
+ _errorMessage: {
+ type: String,
+ value: null,
+ },
+
+ /** @type {?Object} */
+ _baseImage: Object,
+ /** @type {?Object} */
+ _revisionImage: Object,
+
+ _diff: Object,
+
+ /** @type {?Object} */
+ _blame: {
+ type: Object,
+ value: null,
+ },
+ },
+
+ listeners: {
+ 'draft-interaction': '_handleDraftInteraction',
+ },
+
+ ready() {
+ if (this._canReload()) {
+ this.reload();
+ }
+ },
+
+ attached() {
+ this._getLoggedIn().then(loggedIn => {
+ this._loggedIn = loggedIn;
+ });
+ },
+
+ /** @return {!Promise} */
+ reload() {
+ this._loading = true;
+ this._errorMessage = null;
+
+ const diffRequest = this._getDiff()
+ .then(diff => {
+ this._reportDiff(diff);
+ return diff;
+ })
+ .catch(e => {
+ this._handleGetDiffError(e);
+ return null;
+ });
+
+ const assetRequest = diffRequest.then(diff => {
+ // If the diff is null, then it's failed to load.
+ if (!diff) { return null; }
+
+ return this._loadDiffAssets(diff);
+ });
+
+ return Promise.all([diffRequest, assetRequest])
+ .then(results => {
+ const diff = results[0];
+ if (!diff) {
+ return Promise.resolve();
+ }
+ this.filesWeblinks = this._getFilesWeblinks(diff);
+ return new Promise(resolve => {
+ const callback = () => {
+ resolve();
+ this.removeEventListener('render', callback);
+ };
+ this.addEventListener('render', callback);
+ this._diff = diff;
+ });
+ })
+ .catch(err => {
+ console.warn('Error encountered loading diff:', err);
+ })
+ .then(() => { this._loading = false; });
+ },
+
+ _getFilesWeblinks(diff) {
+ if (!this.commitRange) { return {}; }
+ return {
+ meta_a: Gerrit.Nav.getFileWebLinks(
+ this.projectName, this.commitRange.baseCommit, this.path,
+ {weblinks: diff && diff.meta_a && diff.meta_a.web_links}),
+ meta_b: Gerrit.Nav.getFileWebLinks(
+ this.projectName, this.commitRange.commit, this.path,
+ {weblinks: diff && diff.meta_b && diff.meta_b.web_links}),
+ };
+ },
+
+ /** Cancel any remaining diff builder rendering work. */
+ cancel() {
+ this.$.diff.cancel();
+ },
+
+ /** @return {!Array<!HTMLElement>} */
+ getCursorStops() {
+ return this.$.diff.getCursorStops();
+ },
+
+ /** @return {boolean} */
+ isRangeSelected() {
+ return this.$.diff.isRangeSelected();
+ },
+
+ toggleLeftDiff() {
+ this.$.diff.toggleLeftDiff();
+ },
+
+ /**
+ * Load and display blame information for the base of the diff.
+ * @return {Promise} A promise that resolves when blame finishes rendering.
+ */
+ loadBlame() {
+ return this.$.restAPI.getBlame(this.changeNum, this.patchRange.patchNum,
+ this.path, true)
+ .then(blame => {
+ if (!blame.length) {
+ this.fire('show-alert', {message: MSG_EMPTY_BLAME});
+ return Promise.reject(MSG_EMPTY_BLAME);
+ }
+
+ this._blame = blame;
+ });
+ },
+
+ /** Unload blame information for the diff. */
+ clearBlame() {
+ this._blame = null;
+ },
+
+ /** @return {!Array<!HTMLElement>} */
+ getThreadEls() {
+ return this.$.diff.getThreadEls();
+ },
+
+ /** @param {HTMLElement} el */
+ addDraftAtLine(el) {
+ this.$.diff.addDraftAtLine(el);
+ },
+
+ clearDiffContent() {
+ this.$.diff.clearDiffContent();
+ },
+
+ expandAllContext() {
+ this.$.diff.expandAllContext();
+ },
+
+ /** @return {!Promise} */
+ _getLoggedIn() {
+ return this.$.restAPI.getLoggedIn();
+ },
+
+ /** @return {boolean}} */
+ _canReload() {
+ return !!this.changeNum && !!this.patchRange && !!this.path &&
+ !this.noAutoRender;
+ },
+
+ /** @return {!Promise<!Object>} */
+ _getDiff() {
+ // Wrap the diff request in a new promise so that the error handler
+ // rejects the promise, allowing the error to be handled in the .catch.
+ return new Promise((resolve, reject) => {
+ this.$.restAPI.getDiff(
+ this.changeNum,
+ this.patchRange.basePatchNum,
+ this.patchRange.patchNum,
+ this.path,
+ reject)
+ .then(resolve);
+ });
+ },
+
+ _handleGetDiffError(response) {
+ // Loading the diff may respond with 409 if the file is too large. In this
+ // case, use a toast error..
+ if (response.status === 409) {
+ this.fire('server-error', {response});
+ return;
+ }
+
+ if (this.showLoadFailure) {
+ this._errorMessage = [
+ 'Encountered error when loading the diff:',
+ response.status,
+ response.statusText,
+ ].join(' ');
+ return;
+ }
+
+ this.fire('page-error', {response});
+ },
+
+ /**
+ * Report info about the diff response.
+ */
+ _reportDiff(diff) {
+ if (!diff || !diff.content) { return; }
+
+ // Count the delta lines stemming from normal deltas, and from
+ // due_to_rebase deltas.
+ let nonRebaseDelta = 0;
+ let rebaseDelta = 0;
+ diff.content.forEach(chunk => {
+ if (chunk.ab) { return; }
+ const deltaSize = Math.max(
+ chunk.a ? chunk.a.length : 0, chunk.b ? chunk.b.length : 0);
+ if (chunk.due_to_rebase) {
+ rebaseDelta += deltaSize;
+ } else {
+ nonRebaseDelta += deltaSize;
+ }
+ });
+
+ // Find the percent of the delta from due_to_rebase chunks rounded to two
+ // digits. Diffs with no delta are considered 0%.
+ const totalDelta = rebaseDelta + nonRebaseDelta;
+ const percentRebaseDelta = !totalDelta ? 0 :
+ Math.round(100 * rebaseDelta / totalDelta);
+
+ // Report the due_to_rebase percentage in the "diff" category when
+ // applicable.
+ if (this.patchRange.basePatchNum === 'PARENT') {
+ this.$.reporting.reportInteraction(EVENT_AGAINST_PARENT);
+ } else if (percentRebaseDelta === 0) {
+ this.$.reporting.reportInteraction(EVENT_ZERO_REBASE);
+ } else {
+ this.$.reporting.reportInteraction(EVENT_NONZERO_REBASE,
+ percentRebaseDelta);
+ }
+ },
+
+ /**
+ * @param {Object} diff
+ * @return {!Promise}
+ */
+ _loadDiffAssets(diff) {
+ if (isImageDiff(diff)) {
+ return this._getImages(diff).then(images => {
+ this._baseImage = images.baseImage;
+ this._revisionImage = images.revisionImage;
+ });
+ } else {
+ this._baseImage = null;
+ this._revisionImage = null;
+ return Promise.resolve();
+ }
+ },
+
+ /**
+ * @param {Object} diff
+ * @return {boolean}
+ */
+ _computeIsImageDiff(diff) {
+ return isImageDiff(diff);
+ },
+
+ /**
+ * @param {Object} blame
+ * @return {boolean}
+ */
+ _computeIsBlameLoaded(blame) {
+ return !!blame;
+ },
+
+ /**
+ * @param {Object} diff
+ * @return {!Promise}
+ */
+ _getImages(diff) {
+ return this.$.restAPI.getImagesForDiff(this.changeNum, diff,
+ this.patchRange);
+ },
+
+ _handleDraftInteraction() {
+ this.$.reporting.recordDraftInteraction();
+ },
+ });
+})();
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
new file mode 100644
index 0000000..a05d44f
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
@@ -0,0 +1,779 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2018 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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-diff</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+
+<link rel="import" href="gr-diff-host.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-diff-host></gr-diff-host>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-diff-host tests', () => {
+ let element;
+ let sandbox;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ element = fixture('basic');
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ test('reload() cancels before network resolves', () => {
+ const cancelStub = sandbox.stub(element.$.diff, 'cancel');
+
+ // Stub the network calls into requests that never resolve.
+ sandbox.stub(element, '_getDiff', () => new Promise(() => {}));
+
+ element.reload();
+ assert.isTrue(cancelStub.called);
+ });
+
+ suite('not logged in', () => {
+ setup(() => {
+ const getLoggedInPromise = Promise.resolve(false);
+ stub('gr-rest-api-interface', {
+ getLoggedIn() { return getLoggedInPromise; },
+ });
+ element = fixture('basic');
+ return getLoggedInPromise;
+ });
+
+ test('reload() loads files weblinks', () => {
+ const weblinksStub = sandbox.stub(Gerrit.Nav, '_generateWeblinks')
+ .returns({name: 'stubb', url: '#s'});
+ sandbox.stub(element.$.restAPI, 'getDiff').returns(Promise.resolve({
+ content: [],
+ }));
+ element.projectName = 'test-project';
+ element.path = 'test-path';
+ element.commitRange = {baseCommit: 'test-base', commit: 'test-commit'};
+ element.patchRange = {};
+ return element.reload().then(() => {
+ assert.isTrue(weblinksStub.calledTwice);
+ assert.isTrue(weblinksStub.firstCall.calledWith({
+ commit: 'test-base',
+ file: 'test-path',
+ options: {
+ weblinks: undefined,
+ },
+ repo: 'test-project',
+ type: Gerrit.Nav.WeblinkType.FILE}));
+ assert.isTrue(weblinksStub.secondCall.calledWith({
+ commit: 'test-commit',
+ file: 'test-path',
+ options: {
+ weblinks: undefined,
+ },
+ repo: 'test-project',
+ type: Gerrit.Nav.WeblinkType.FILE}));
+ assert.deepEqual(element.filesWeblinks, {
+ meta_a: [{name: 'stubb', url: '#s'}],
+ meta_b: [{name: 'stubb', url: '#s'}],
+ });
+ });
+ });
+
+ test('_getDiff handles null diff responses', done => {
+ stub('gr-rest-api-interface', {
+ getDiff() { return Promise.resolve(null); },
+ });
+ element.changeNum = 123;
+ element.patchRange = {basePatchNum: 1, patchNum: 2};
+ element.path = 'file.txt';
+ element._getDiff().then(done);
+ });
+
+ test('reload resolves on error', () => {
+ const onErrStub = sandbox.stub(element, '_handleGetDiffError');
+ const error = {ok: false, status: 500};
+ sandbox.stub(element.$.restAPI, 'getDiff',
+ (changeNum, basePatchNum, patchNum, path, onErr) => {
+ onErr(error);
+ });
+ return element.reload().then(() => {
+ assert.isTrue(onErrStub.calledOnce);
+ });
+ });
+
+ suite('_handleGetDiffError', () => {
+ let serverErrorStub;
+ let pageErrorStub;
+
+ setup(() => {
+ serverErrorStub = sinon.stub();
+ element.addEventListener('server-error', serverErrorStub);
+ pageErrorStub = sinon.stub();
+ element.addEventListener('page-error', pageErrorStub);
+ });
+
+ test('page error on HTTP-409', () => {
+ element._handleGetDiffError({status: 409});
+ assert.isTrue(serverErrorStub.calledOnce);
+ assert.isFalse(pageErrorStub.called);
+ assert.isNotOk(element._errorMessage);
+ });
+
+ test('server error on non-HTTP-409', () => {
+ element._handleGetDiffError({status: 500});
+ assert.isFalse(serverErrorStub.called);
+ assert.isTrue(pageErrorStub.calledOnce);
+ assert.isNotOk(element._errorMessage);
+ });
+
+ test('error message if showLoadFailure', () => {
+ element.showLoadFailure = true;
+ element._handleGetDiffError({status: 500, statusText: 'Failure!'});
+ assert.isFalse(serverErrorStub.called);
+ assert.isFalse(pageErrorStub.called);
+ assert.equal(element._errorMessage,
+ 'Encountered error when loading the diff: 500 Failure!');
+ });
+ });
+
+ suite('image diffs', () => {
+ let mockFile1;
+ let mockFile2;
+ setup(() => {
+ mockFile1 = {
+ body: 'Qk06AAAAAAAAADYAAAAoAAAAAQAAAP////8BACAAAAAAAAAAAAATCwAAE' +
+ 'wsAAAAAAAAAAAAAAAAA/w==',
+ type: 'image/bmp',
+ };
+ mockFile2 = {
+ body: 'Qk06AAAAAAAAADYAAAAoAAAAAQAAAP////8BACAAAAAAAAAAAAATCwAAE' +
+ 'wsAAAAAAAAAAAAA/////w==',
+ type: 'image/bmp',
+ };
+ sandbox.stub(element.$.restAPI,
+ 'getB64FileContents',
+ (changeId, patchNum, path, opt_parentIndex) => {
+ return Promise.resolve(opt_parentIndex === 1 ? mockFile1 :
+ mockFile2);
+ });
+
+ element.patchRange = {basePatchNum: 'PARENT', patchNum: 1};
+ element.comments = {left: [], right: []};
+ });
+
+ test('renders image diffs with same file name', done => {
+ const mockDiff = {
+ meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
+ meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
+ lines: 560},
+ intraline_status: 'OK',
+ change_type: 'MODIFIED',
+ diff_header: [
+ 'diff --git a/carrot.jpg b/carrot.jpg',
+ 'index 2adc47d..f9c2f2c 100644',
+ '--- a/carrot.jpg',
+ '+++ b/carrot.jpg',
+ 'Binary files differ',
+ ],
+ content: [{skip: 66}],
+ binary: true,
+ };
+ sandbox.stub(element.$.restAPI, 'getDiff')
+ .returns(Promise.resolve(mockDiff));
+
+ const rendered = () => {
+ // Recognizes that it should be an image diff.
+ assert.isTrue(element.isImageDiff);
+ assert.instanceOf(
+ element.$.diff.$.diffBuilder._builder, GrDiffBuilderImage);
+
+ // Left image rendered with the parent commit's version of the file.
+ const leftImage =
+ element.$.diff.$.diffTable.querySelector('td.left img');
+ const leftLabel =
+ element.$.diff.$.diffTable.querySelector('td.left label');
+ const leftLabelContent = leftLabel.querySelector('.label');
+ const leftLabelName = leftLabel.querySelector('.name');
+
+ const rightImage =
+ element.$.diff.$.diffTable.querySelector('td.right img');
+ const rightLabel = element.$.diff.$.diffTable.querySelector(
+ 'td.right label');
+ const rightLabelContent = rightLabel.querySelector('.label');
+ const rightLabelName = rightLabel.querySelector('.name');
+
+ assert.isNotOk(rightLabelName);
+ assert.isNotOk(leftLabelName);
+
+ let leftLoaded = false;
+ let rightLoaded = false;
+
+ leftImage.addEventListener('load', () => {
+ assert.isOk(leftImage);
+ assert.equal(leftImage.getAttribute('src'),
+ 'data:image/bmp;base64, ' + mockFile1.body);
+ assert.equal(leftLabelContent.textContent, '1×1 image/bmp');
+ leftLoaded = true;
+ if (rightLoaded) {
+ element.removeEventListener('render', rendered);
+ done();
+ }
+ });
+
+ rightImage.addEventListener('load', () => {
+ assert.isOk(rightImage);
+ assert.equal(rightImage.getAttribute('src'),
+ 'data:image/bmp;base64, ' + mockFile2.body);
+ assert.equal(rightLabelContent.textContent, '1×1 image/bmp');
+
+ rightLoaded = true;
+ if (leftLoaded) {
+ element.removeEventListener('render', rendered);
+ done();
+ }
+ });
+ };
+
+ element.addEventListener('render', rendered);
+
+ element.$.restAPI.getDiffPreferences().then(prefs => {
+ element.prefs = prefs;
+ element.reload();
+ });
+ });
+
+ test('renders image diffs with a different file name', done => {
+ const mockDiff = {
+ meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
+ meta_b: {name: 'carrot2.jpg', content_type: 'image/jpeg',
+ lines: 560},
+ intraline_status: 'OK',
+ change_type: 'MODIFIED',
+ diff_header: [
+ 'diff --git a/carrot.jpg b/carrot2.jpg',
+ 'index 2adc47d..f9c2f2c 100644',
+ '--- a/carrot.jpg',
+ '+++ b/carrot2.jpg',
+ 'Binary files differ',
+ ],
+ content: [{skip: 66}],
+ binary: true,
+ };
+ sandbox.stub(element.$.restAPI, 'getDiff')
+ .returns(Promise.resolve(mockDiff));
+
+ const rendered = () => {
+ // Recognizes that it should be an image diff.
+ assert.isTrue(element.isImageDiff);
+ assert.instanceOf(
+ element.$.diff.$.diffBuilder._builder, GrDiffBuilderImage);
+
+ // Left image rendered with the parent commit's version of the file.
+ const leftImage =
+ element.$.diff.$.diffTable.querySelector('td.left img');
+ const leftLabel =
+ element.$.diff.$.diffTable.querySelector('td.left label');
+ const leftLabelContent = leftLabel.querySelector('.label');
+ const leftLabelName = leftLabel.querySelector('.name');
+
+ const rightImage =
+ element.$.diff.$.diffTable.querySelector('td.right img');
+ const rightLabel = element.$.diff.$.diffTable.querySelector(
+ 'td.right label');
+ const rightLabelContent = rightLabel.querySelector('.label');
+ const rightLabelName = rightLabel.querySelector('.name');
+
+ assert.isOk(rightLabelName);
+ assert.isOk(leftLabelName);
+ assert.equal(leftLabelName.textContent, mockDiff.meta_a.name);
+ assert.equal(rightLabelName.textContent, mockDiff.meta_b.name);
+
+ let leftLoaded = false;
+ let rightLoaded = false;
+
+ leftImage.addEventListener('load', () => {
+ assert.isOk(leftImage);
+ assert.equal(leftImage.getAttribute('src'),
+ 'data:image/bmp;base64, ' + mockFile1.body);
+ assert.equal(leftLabelContent.textContent, '1×1 image/bmp');
+ leftLoaded = true;
+ if (rightLoaded) {
+ element.removeEventListener('render', rendered);
+ done();
+ }
+ });
+
+ rightImage.addEventListener('load', () => {
+ assert.isOk(rightImage);
+ assert.equal(rightImage.getAttribute('src'),
+ 'data:image/bmp;base64, ' + mockFile2.body);
+ assert.equal(rightLabelContent.textContent, '1×1 image/bmp');
+
+ rightLoaded = true;
+ if (leftLoaded) {
+ element.removeEventListener('render', rendered);
+ done();
+ }
+ });
+ };
+
+ element.addEventListener('render', rendered);
+
+ element.$.restAPI.getDiffPreferences().then(prefs => {
+ element.prefs = prefs;
+ element.reload();
+ });
+ });
+
+ test('renders added image', done => {
+ const mockDiff = {
+ meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
+ lines: 560},
+ intraline_status: 'OK',
+ change_type: 'ADDED',
+ diff_header: [
+ 'diff --git a/carrot.jpg b/carrot.jpg',
+ 'index 0000000..f9c2f2c 100644',
+ '--- /dev/null',
+ '+++ b/carrot.jpg',
+ 'Binary files differ',
+ ],
+ content: [{skip: 66}],
+ binary: true,
+ };
+ sandbox.stub(element.$.restAPI, 'getDiff')
+ .returns(Promise.resolve(mockDiff));
+
+ element.addEventListener('render', () => {
+ // Recognizes that it should be an image diff.
+ assert.isTrue(element.isImageDiff);
+ assert.instanceOf(
+ element.$.diff.$.diffBuilder._builder, GrDiffBuilderImage);
+
+ const leftImage =
+ element.$.diff.$.diffTable.querySelector('td.left img');
+ const rightImage =
+ element.$.diff.$.diffTable.querySelector('td.right img');
+
+ assert.isNotOk(leftImage);
+ assert.isOk(rightImage);
+ done();
+ });
+
+ element.$.restAPI.getDiffPreferences().then(prefs => {
+ element.prefs = prefs;
+ element.reload();
+ });
+ });
+
+ test('renders removed image', done => {
+ const mockDiff = {
+ meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg',
+ lines: 560},
+ intraline_status: 'OK',
+ change_type: 'DELETED',
+ diff_header: [
+ 'diff --git a/carrot.jpg b/carrot.jpg',
+ 'index f9c2f2c..0000000 100644',
+ '--- a/carrot.jpg',
+ '+++ /dev/null',
+ 'Binary files differ',
+ ],
+ content: [{skip: 66}],
+ binary: true,
+ };
+ sandbox.stub(element.$.restAPI, 'getDiff')
+ .returns(Promise.resolve(mockDiff));
+
+ element.addEventListener('render', () => {
+ // Recognizes that it should be an image diff.
+ assert.isTrue(element.isImageDiff);
+ assert.instanceOf(
+ element.$.diff.$.diffBuilder._builder, GrDiffBuilderImage);
+
+ const leftImage =
+ element.$.diff.$.diffTable.querySelector('td.left img');
+ const rightImage =
+ element.$.diff.$.diffTable.querySelector('td.right img');
+
+ assert.isOk(leftImage);
+ assert.isNotOk(rightImage);
+ done();
+ });
+
+ element.$.restAPI.getDiffPreferences().then(prefs => {
+ element.prefs = prefs;
+ element.reload();
+ });
+ });
+
+ test('does not render disallowed image type', done => {
+ const mockDiff = {
+ meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg-evil',
+ lines: 560},
+ intraline_status: 'OK',
+ change_type: 'DELETED',
+ diff_header: [
+ 'diff --git a/carrot.jpg b/carrot.jpg',
+ 'index f9c2f2c..0000000 100644',
+ '--- a/carrot.jpg',
+ '+++ /dev/null',
+ 'Binary files differ',
+ ],
+ content: [{skip: 66}],
+ binary: true,
+ };
+ mockFile1.type = 'image/jpeg-evil';
+
+ sandbox.stub(element.$.restAPI, 'getDiff')
+ .returns(Promise.resolve(mockDiff));
+
+ element.addEventListener('render', () => {
+ // Recognizes that it should be an image diff.
+ assert.isTrue(element.isImageDiff);
+ assert.instanceOf(
+ element.$.diff.$.diffBuilder._builder, GrDiffBuilderImage);
+ const leftImage =
+ element.$.diff.$.diffTable.querySelector('td.left img');
+ assert.isNotOk(leftImage);
+ done();
+ });
+
+ element.$.restAPI.getDiffPreferences().then(prefs => {
+ element.prefs = prefs;
+ element.reload();
+ });
+ });
+ });
+ });
+
+ test('delegates cancel()', () => {
+ const stub = sandbox.stub(element.$.diff, 'cancel');
+ element.reload();
+ assert.isTrue(stub.calledOnce);
+ assert.equal(stub.lastCall.args.length, 0);
+ });
+
+ test('delegates getCursorStops()', () => {
+ const returnValue = [document.createElement('b')];
+ const stub = sandbox.stub(element.$.diff, 'getCursorStops')
+ .returns(returnValue);
+ assert.equal(element.getCursorStops(), returnValue);
+ assert.isTrue(stub.calledOnce);
+ assert.equal(stub.lastCall.args.length, 0);
+ });
+
+ test('delegates isRangeSelected()', () => {
+ const returnValue = true;
+ const stub = sandbox.stub(element.$.diff, 'isRangeSelected')
+ .returns(returnValue);
+ assert.equal(element.isRangeSelected(), returnValue);
+ assert.isTrue(stub.calledOnce);
+ assert.equal(stub.lastCall.args.length, 0);
+ });
+
+ test('delegates toggleLeftDiff()', () => {
+ const stub = sandbox.stub(element.$.diff, 'toggleLeftDiff');
+ element.toggleLeftDiff();
+ assert.isTrue(stub.calledOnce);
+ assert.equal(stub.lastCall.args.length, 0);
+ });
+
+ suite('blame', () => {
+ setup(() => {
+ element = fixture('basic');
+ });
+
+ test('clearBlame', () => {
+ element._blame = [];
+ const setBlameSpy = sandbox.spy(element.$.diff.$.diffBuilder, 'setBlame');
+ element.clearBlame();
+ assert.isNull(element._blame);
+ assert.isTrue(setBlameSpy.calledWithExactly(null));
+ assert.equal(element.isBlameLoaded, false);
+ });
+
+ test('loadBlame', () => {
+ const mockBlame = [{id: 'commit id', ranges: [{start: 1, end: 2}]}];
+ const showAlertStub = sinon.stub();
+ element.addEventListener('show-alert', showAlertStub);
+ const getBlameStub = sandbox.stub(element.$.restAPI, 'getBlame')
+ .returns(Promise.resolve(mockBlame));
+ element.changeNum = 42;
+ element.patchRange = {patchNum: 5, basePatchNum: 4};
+ element.path = 'foo/bar.baz';
+ return element.loadBlame().then(() => {
+ assert.isTrue(getBlameStub.calledWithExactly(
+ 42, 5, 'foo/bar.baz', true));
+ assert.isFalse(showAlertStub.called);
+ assert.equal(element._blame, mockBlame);
+ assert.equal(element.isBlameLoaded, true);
+ });
+ });
+
+ test('loadBlame empty', () => {
+ const mockBlame = [];
+ const showAlertStub = sinon.stub();
+ element.addEventListener('show-alert', showAlertStub);
+ sandbox.stub(element.$.restAPI, 'getBlame')
+ .returns(Promise.resolve(mockBlame));
+ element.changeNum = 42;
+ element.patchRange = {patchNum: 5, basePatchNum: 4};
+ element.path = 'foo/bar.baz';
+ return element.loadBlame()
+ .then(() => {
+ assert.isTrue(false, 'Promise should not resolve');
+ })
+ .catch(() => {
+ assert.isTrue(showAlertStub.calledOnce);
+ assert.isNull(element._blame);
+ assert.equal(element.isBlameLoaded, false);
+ });
+ });
+ });
+
+ test('delegates getThreadEls()', () => {
+ const returnValue = [document.createElement('b')];
+ const stub = sandbox.stub(element.$.diff, 'getThreadEls')
+ .returns(returnValue);
+ assert.equal(element.getThreadEls(), returnValue);
+ assert.isTrue(stub.calledOnce);
+ assert.equal(stub.lastCall.args.length, 0);
+ });
+
+ test('delegates addDraftAtLine(el)', () => {
+ const param0 = document.createElement('b');
+ const stub = sandbox.stub(element.$.diff, 'addDraftAtLine');
+ element.addDraftAtLine(param0);
+ assert.isTrue(stub.calledOnce);
+ assert.equal(stub.lastCall.args.length, 1);
+ assert.equal(stub.lastCall.args[0], param0);
+ });
+
+ test('delegates clearDiffContent()', () => {
+ const stub = sandbox.stub(element.$.diff, 'clearDiffContent');
+ element.clearDiffContent();
+ assert.isTrue(stub.calledOnce);
+ assert.equal(stub.lastCall.args.length, 0);
+ });
+
+ test('delegates expandAllContext()', () => {
+ const stub = sandbox.stub(element.$.diff, 'expandAllContext');
+ element.expandAllContext();
+ assert.isTrue(stub.calledOnce);
+ assert.equal(stub.lastCall.args.length, 0);
+ });
+
+ test('passes in changeNum', () => {
+ const value = '12345';
+ element.changeNum = value;
+ assert.equal(element.$.diff.changeNum, value);
+ });
+
+ test('passes in noAutoRender', () => {
+ const value = true;
+ element.noAutoRender = value;
+ assert.equal(element.$.diff.noAutoRender, value);
+ });
+
+ test('passes in patchRange', () => {
+ const value = {patchNum: 'foo', basePatchNum: 'bar'};
+ element.patchRange = value;
+ assert.equal(element.$.diff.patchRange, value);
+ });
+
+ test('passes in path', () => {
+ const value = 'some/file/path';
+ element.path = value;
+ assert.equal(element.$.diff.path, value);
+ });
+
+ test('passes in prefs', () => {
+ const value = {};
+ element.prefs = value;
+ assert.equal(element.$.diff.prefs, value);
+ });
+
+ test('passes in projectConfig', () => {
+ const value = {};
+ element.projectConfig = value;
+ assert.equal(element.$.diff.projectConfig, value);
+ });
+
+ test('passes in changeNum', () => {
+ const value = '12345';
+ element.changeNum = value;
+ assert.equal(element.$.diff.changeNum, value);
+ });
+
+ test('passes in projectName', () => {
+ const value = 'Gerrit';
+ element.projectName = value;
+ assert.equal(element.$.diff.projectName, value);
+ });
+
+ test('passes in displayLine', () => {
+ const value = true;
+ element.displayLine = value;
+ assert.equal(element.$.diff.displayLine, value);
+ });
+
+ test('passes in commitRange', () => {
+ const value = {};
+ element.commitRange = value;
+ assert.equal(element.$.diff.commitRange, value);
+ });
+
+ test('passes in hidden', () => {
+ const value = true;
+ element.hidden = value;
+ assert.equal(element.$.diff.hidden, value);
+ assert.isNotNull(element.getAttribute('hidden'));
+ });
+
+ test('passes in noRenderOnPrefsChange', () => {
+ const value = true;
+ element.noRenderOnPrefsChange = value;
+ assert.equal(element.$.diff.noRenderOnPrefsChange, value);
+ });
+
+ test('passes in comments', () => {
+ const value = {left: [], right: []};
+ element.comments = value;
+ assert.equal(element.$.diff.comments, value);
+ });
+
+ test('passes in lineWrapping', () => {
+ const value = true;
+ element.lineWrapping = value;
+ assert.equal(element.$.diff.lineWrapping, value);
+ });
+
+ test('passes in viewMode', () => {
+ const value = 'SIDE_BY_SIDE';
+ element.viewMode = value;
+ assert.equal(element.$.diff.viewMode, value);
+ });
+
+ test('passes in lineOfInterest', () => {
+ const value = {number: 123, leftSide: true};
+ element.lineOfInterest = value;
+ assert.equal(element.$.diff.lineOfInterest, value);
+ });
+
+ suite('_reportDiff', () => {
+ let reportStub;
+
+ setup(() => {
+ element = fixture('basic');
+ element.patchRange = {basePatchNum: 1};
+ reportStub = sandbox.stub(element.$.reporting, 'reportInteraction');
+ });
+
+ test('null and content-less', () => {
+ element._reportDiff(null);
+ assert.isFalse(reportStub.called);
+
+ element._reportDiff({});
+ assert.isFalse(reportStub.called);
+ });
+
+ test('diff w/ no delta', () => {
+ const diff = {
+ content: [
+ {ab: ['foo', 'bar']},
+ {ab: ['baz', 'foo']},
+ ],
+ };
+ element._reportDiff(diff);
+ assert.isTrue(reportStub.calledOnce);
+ assert.equal(reportStub.lastCall.args[0], 'rebase-percent-zero');
+ assert.isUndefined(reportStub.lastCall.args[1]);
+ });
+
+ test('diff w/ no rebase delta', () => {
+ const diff = {
+ content: [
+ {ab: ['foo', 'bar']},
+ {a: ['baz', 'foo']},
+ {ab: ['foo', 'bar']},
+ {a: ['baz', 'foo'], b: ['bar', 'baz']},
+ {ab: ['foo', 'bar']},
+ {b: ['baz', 'foo']},
+ {ab: ['foo', 'bar']},
+ ],
+ };
+ element._reportDiff(diff);
+ assert.isTrue(reportStub.calledOnce);
+ assert.equal(reportStub.lastCall.args[0], 'rebase-percent-zero');
+ assert.isUndefined(reportStub.lastCall.args[1]);
+ });
+
+ test('diff w/ some rebase delta', () => {
+ const diff = {
+ content: [
+ {ab: ['foo', 'bar']},
+ {a: ['baz', 'foo'], due_to_rebase: true},
+ {ab: ['foo', 'bar']},
+ {a: ['baz', 'foo'], b: ['bar', 'baz']},
+ {ab: ['foo', 'bar']},
+ {b: ['baz', 'foo'], due_to_rebase: true},
+ {ab: ['foo', 'bar']},
+ {a: ['baz', 'foo']},
+ ],
+ };
+ element._reportDiff(diff);
+ assert.isTrue(reportStub.calledOnce);
+ assert.equal(reportStub.lastCall.args[0], 'rebase-percent-nonzero');
+ assert.strictEqual(reportStub.lastCall.args[1], 50);
+ });
+
+ test('diff w/ all rebase delta', () => {
+ const diff = {content: [{
+ a: ['foo', 'bar'],
+ b: ['baz', 'foo'],
+ due_to_rebase: true,
+ }]};
+ element._reportDiff(diff);
+ assert.isTrue(reportStub.calledOnce);
+ assert.equal(reportStub.lastCall.args[0], 'rebase-percent-nonzero');
+ assert.strictEqual(reportStub.lastCall.args[1], 100);
+ });
+
+ test('diff against parent event', () => {
+ element.patchRange.basePatchNum = 'PARENT';
+ const diff = {content: [{
+ a: ['foo', 'bar'],
+ b: ['baz', 'foo'],
+ }]};
+ element._reportDiff(diff);
+ assert.isTrue(reportStub.calledOnce);
+ assert.equal(reportStub.lastCall.args[0], 'diff-against-parent');
+ assert.isUndefined(reportStub.lastCall.args[1]);
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index 1b5203e..2e56871 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -36,7 +36,7 @@
<link rel="import" href="../gr-diff-cursor/gr-diff-cursor.html">
<link rel="import" href="../gr-diff-mode-selector/gr-diff-mode-selector.html">
<link rel="import" href="../gr-diff-preferences/gr-diff-preferences.html">
-<link rel="import" href="../gr-diff/gr-diff.html">
+<link rel="import" href="../gr-diff-host/gr-diff-host.html">
<link rel="import" href="../gr-patch-range-select/gr-patch-range-select.html">
<dom-module id="gr-diff-view">
@@ -320,8 +320,8 @@
</div>
</gr-fixed-panel>
<div class="loading" hidden$="[[!_loading]]">Loading...</div>
- <gr-diff
- id="diff"
+ <gr-diff-host
+ id="diffHost"
hidden
hidden$="[[_loading]]"
class$="[[_computeDiffClass(_panelFloatingDisabled)]]"
@@ -337,7 +337,7 @@
view-mode="[[_diffMode]]"
is-blame-loaded="{{_isBlameLoaded}}"
on-line-selected="_onLineSelected">
- </gr-diff>
+ </gr-diff-host>
<gr-diff-preferences
id="diffPreferences"
prefs="{{_prefs}}"
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 8798a8f..b0eb423 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -200,7 +200,7 @@
this._loggedIn = loggedIn;
});
- this.$.cursor.push('diffs', this.$.diff);
+ this.$.cursor.push('diffs', this.$.diffHost);
},
_getLoggedIn() {
@@ -276,7 +276,7 @@
this.modifierPressed(e)) { return; }
e.preventDefault();
- this.$.diff.displayLine = false;
+ this.$.diffHost.displayLine = false;
},
_handleShiftLeftKey(e) {
@@ -303,7 +303,7 @@
if (this.modifierPressed(e)) { return; }
e.preventDefault();
- this.$.diff.displayLine = true;
+ this.$.diffHost.displayLine = true;
this.$.cursor.moveUp();
},
@@ -317,7 +317,7 @@
if (this.modifierPressed(e)) { return; }
e.preventDefault();
- this.$.diff.displayLine = true;
+ this.$.diffHost.displayLine = true;
this.$.cursor.moveDown();
},
@@ -350,13 +350,13 @@
_handleCKey(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
- if (this.$.diff.isRangeSelected()) { return; }
+ if (this.$.diffHost.isRangeSelected()) { return; }
if (this.modifierPressed(e)) { return; }
e.preventDefault();
const line = this.$.cursor.getTargetLineElement();
if (line) {
- this.$.diff.addDraftAtLine(line);
+ this.$.diffHost.addDraftAtLine(line);
}
},
@@ -407,7 +407,7 @@
if (e.detail.keyboardEvent.shiftKey) { // Hide left diff.
e.preventDefault();
- this.$.diff.toggleLeftDiff();
+ this.$.diffHost.toggleLeftDiff();
return;
}
@@ -549,7 +549,7 @@
this.$.restAPI.setInProjectLookup(value.changeNum, value.project);
}
- this.$.diff.lineOfInterest = this._getLineOfInterest(this.params);
+ this.$.diffHost.lineOfInterest = this._getLineOfInterest(this.params);
this._initCursor(this.params);
this._changeNum = value.changeNum;
@@ -621,8 +621,8 @@
});
}
this._loading = false;
- this.$.diff.comments = this._commentsForDiff;
- return this.$.diff.reload();
+ this.$.diffHost.comments = this._commentsForDiff;
+ return this.$.diffHost.reload();
}).then(() => {
this.$.reporting.diffViewDisplayed();
});
@@ -949,13 +949,13 @@
*/
_toggleBlame() {
if (this._isBlameLoaded) {
- this.$.diff.clearBlame();
+ this.$.diffHost.clearBlame();
return;
}
this._isBlameLoading = true;
this.fire('show-alert', {message: MSG_LOADING_BLAME});
- this.$.diff.loadBlame()
+ this.$.diffHost.loadBlame()
.then(() => {
this._isBlameLoading = false;
this.fire('show-alert', {message: MSG_LOADED_BLAME});
@@ -991,7 +991,7 @@
_handleShiftXKey(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
- this.$.diff.expandAllContext();
+ this.$.diffHost.expandAllContext();
},
});
})();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index f947d18..00527e4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -73,7 +73,7 @@
test('params change triggers diffViewDisplayed()', () => {
sandbox.stub(element.$.reporting, 'diffViewDisplayed');
- sandbox.stub(element.$.diff, 'reload').returns(Promise.resolve());
+ sandbox.stub(element.$.diffHost, 'reload').returns(Promise.resolve());
sandbox.spy(element, '_paramsChanged');
element.params = {
view: Gerrit.Nav.View.DIFF,
@@ -89,7 +89,8 @@
});
test('toggle left diff with a hotkey', () => {
- const toggleLeftDiffStub = sandbox.stub(element.$.diff, 'toggleLeftDiff');
+ const toggleLeftDiffStub = sandbox.stub(
+ element.$.diffHost, 'toggleLeftDiff');
MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift', 'a');
assert.isTrue(toggleLeftDiffStub.calledOnce);
});
@@ -168,7 +169,7 @@
MockInteractions.pressAndReleaseKeyOn(element, 80, 'shift', 'p');
assert(scrollStub.calledOnce);
- const computeContainerClassStub = sandbox.stub(element.$.diff,
+ const computeContainerClassStub = sandbox.stub(element.$.diffHost.$.diff,
'_computeContainerClass');
MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
assert(computeContainerClassStub.lastCall.calledWithExactly(
@@ -189,7 +190,7 @@
});
test('shift+x shortcut expands all diff context', () => {
- const expandStub = sandbox.stub(element.$.diff, 'expandAllContext');
+ const expandStub = sandbox.stub(element.$.diffHost, 'expandAllContext');
MockInteractions.pressAndReleaseKeyOn(element, 88, 'shift', 'x');
flushAsynchronousOperations();
assert.isTrue(expandStub.called);
@@ -550,7 +551,7 @@
const getReviewedStub = sandbox.stub(element, '_getReviewedStatus',
() => Promise.resolve());
- sandbox.stub(element.$.diff, 'reload');
+ sandbox.stub(element.$.diffHost, 'reload');
element._loggedIn = true;
element.params = {
view: Gerrit.Nav.View.DIFF,
@@ -575,7 +576,7 @@
test('file review status', () => {
const saveReviewedStub = sandbox.stub(element, '_saveReviewedState',
() => Promise.resolve());
- sandbox.stub(element.$.diff, 'reload');
+ sandbox.stub(element.$.diffHost, 'reload');
element._loggedIn = true;
element.params = {
@@ -621,7 +622,7 @@
});
test('hash is determined from params', done => {
- sandbox.stub(element.$.diff, 'reload');
+ sandbox.stub(element.$.diffHost, 'reload');
sandbox.stub(element, '_initCursor');
element._loggedIn = true;
@@ -642,7 +643,7 @@
test('diff mode selector correctly toggles the diff', () => {
const select = element.$.modeSelect;
- const diffDisplay = element.$.diff;
+ const diffDisplay = element.$.diffHost;
element._userPrefs = {default_diff_view: 'SIDE_BY_SIDE'};
// The mode selected in the view state reflects the selected option.
@@ -687,7 +688,7 @@
suite('_commitRange', () => {
setup(() => {
- sandbox.stub(element.$.diff, 'reload');
+ sandbox.stub(element.$.diffHost, 'reload');
sandbox.stub(element, '_initCursor');
sandbox.stub(element, '_getChangeDetail').returns(Promise.resolve({
_number: 42,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 8e975f4..6add75c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -18,9 +18,7 @@
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
-<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
-<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-diff-builder/gr-diff-builder.html">
<link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html">
<link rel="import" href="../gr-diff-highlight/gr-diff-highlight.html">
@@ -55,7 +53,6 @@
background-color: var(--table-header-background-color);
}
.image-diff .gr-diff {
- background-color: var(--table-header-background-color);
text-align: center;
}
.image-diff img {
@@ -83,6 +80,9 @@
.content {
background-color: var(--view-background-color);
}
+ .image-diff .content {
+ background-color: var(--table-header-background-color);
+ }
.full-width {
width: 100%;
}
@@ -192,15 +192,20 @@
font-size: var(--font-size, var(--font-size-normal));
padding: 0.5em 0 0.5em 4em;
}
+ #loadingError,
#sizeWarning {
display: none;
margin: 1em auto;
max-width: 60em;
text-align: center;
}
+ #loadingError {
+ color: var(--error-text-color);
+ }
#sizeWarning gr-button {
margin: 1em;
}
+ #loadingError.showError,
#sizeWarning.warn {
display: block;
}
@@ -265,26 +270,26 @@
<div>[[item]]</div>
</template>
</div>
- <div class$="[[_computeContainerClass(_loggedIn, viewMode, displayLine)]]"
+ <div class$="[[_computeContainerClass(loggedIn, viewMode, displayLine)]]"
on-tap="_handleTap">
- <gr-diff-selection diff="[[_diff]]">
+ <gr-diff-selection diff="[[diff]]">
<gr-diff-highlight
id="highlights"
- logged-in="[[_loggedIn]]"
+ logged-in="[[loggedIn]]"
comments="{{comments}}">
<gr-diff-builder
id="diffBuilder"
comments="[[comments]]"
project-name="[[projectName]]"
- diff="[[_diff]]"
+ diff="[[diff]]"
diff-path="[[path]]"
change-num="[[changeNum]]"
patch-num="[[patchRange.patchNum]]"
view-mode="[[viewMode]]"
line-wrapping="[[lineWrapping]]"
is-image-diff="[[isImageDiff]]"
- base-image="[[_baseImage]]"
- revision-image="[[_revisionImage]]"
+ base-image="[[baseImage]]"
+ revision-image="[[revisionImage]]"
parent-index="[[_parentIndex]]"
create-comment-fn="[[_createThreadGroupFn]]"
line-of-interest="[[lineOfInterest]]">
@@ -296,13 +301,16 @@
</gr-diff-highlight>
</gr-diff-selection>
</div>
- <div class$="[[_computeNewlineWarningClass(_newlineWarning, _loading)]]">
+ <div class$="[[_computeNewlineWarningClass(_newlineWarning, loading)]]">
[[_newlineWarning]]
</div>
+ <div id="loadingError" class$="[[_computeErrorClass(errorMessage)]]">
+ [[errorMessage]]
+ </div>
<div id="sizeWarning" class$="[[_computeWarningClass(_showWarning)]]">
<p>
Prevented render because "Whole file" is enabled and this diff is very
- large (about [[_diffLength(_diff)]] lines).
+ large (about [[_diffLength(diff)]] lines).
</p>
<gr-button on-tap="_handleLimitedBypass">
Render with limited context
@@ -311,8 +319,6 @@
Render anyway (may be slow)
</gr-button>
</div>
- <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
- <gr-reporting id="reporting" category="diff"></gr-reporting>
</template>
<script src="gr-diff-line.js"></script>
<script src="gr-diff-group.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index 4b19e67..9f1b412 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -21,11 +21,6 @@
const ERR_COMMENT_ON_EDIT_BASE = 'You cannot comment on the base patch set ' +
'of an edit.';
const ERR_INVALID_LINE = 'Invalid line number: ';
- const MSG_EMPTY_BLAME = 'No blame information for this diff.';
-
- const EVENT_AGAINST_PARENT = 'diff-against-parent';
- const EVENT_ZERO_REBASE = 'rebase-percent-zero';
- const EVENT_NONZERO_REBASE = 'rebase-percent-nonzero';
const NO_NEWLINE_BASE = 'No newline at end of base file.';
const NO_NEWLINE_REVISION = 'No newline at end of revision file.';
@@ -64,6 +59,12 @@
* @event diff-comments-modified
*/
+ /**
+ * Fired when a draft is added or edited.
+ *
+ * @event draft-interaction
+ */
+
properties: {
changeNum: String,
noAutoRender: {
@@ -88,21 +89,17 @@
},
isImageDiff: {
type: Boolean,
- computed: '_computeIsImageDiff(_diff)',
- notify: true,
},
commitRange: Object,
- filesWeblinks: {
- type: Object,
- value() { return {}; },
- notify: true,
- },
hidden: {
type: Boolean,
reflectToAttribute: true,
},
noRenderOnPrefsChange: Boolean,
- comments: Object,
+ comments: {
+ type: Object,
+ value: {left: [], right: []},
+ },
lineWrapping: {
type: Boolean,
value: false,
@@ -123,29 +120,33 @@
*/
lineOfInterest: Object,
- _loading: {
+ loading: {
type: Boolean,
value: false,
+ observer: '_loadingChanged',
},
- _loggedIn: {
+ loggedIn: {
type: Boolean,
value: false,
},
- _diff: Object,
+ diff: {
+ type: Object,
+ observer: '_diffChanged',
+ },
_diffHeaderItems: {
type: Array,
value: [],
- computed: '_computeDiffHeaderItems(_diff.*)',
+ computed: '_computeDiffHeaderItems(diff.*)',
},
_diffTableClass: {
type: String,
value: '',
},
/** @type {?Object} */
- _baseImage: Object,
+ baseImage: Object,
/** @type {?Object} */
- _revisionImage: Object,
+ revisionImage: Object,
/**
* Whether the safety check for large diffs when whole-file is set has
@@ -162,15 +163,17 @@
_showWarning: Boolean,
- /** @type {?Object} */
- _blame: {
- type: Object,
+ /** @type {?string} */
+ errorMessage: {
+ type: String,
value: null,
},
- isBlameLoaded: {
- type: Boolean,
- notify: true,
- computed: '_computeIsBlameLoaded(_blame)',
+
+ /** @type {?Object} */
+ blame: {
+ type: Object,
+ value: null,
+ observer: '_blameChanged',
},
_parentIndex: {
@@ -180,7 +183,7 @@
_newlineWarning: {
type: String,
- computed: '_computeNewlineWarning(_diff)',
+ computed: '_computeNewlineWarning(diff)',
},
/**
@@ -205,44 +208,6 @@
'create-comment': '_handleCreateComment',
},
- attached() {
- this._getLoggedIn().then(loggedIn => {
- this._loggedIn = loggedIn;
- });
- },
-
- ready() {
- if (this._canRender()) {
- this.reload();
- }
- },
-
- /** @return {!Promise} */
- reload() {
- this._loading = true;
- this.cancel();
- this.clearBlame();
- this._safetyBypass = null;
- this._showWarning = false;
- this.clearDiffContent();
-
- const promises = [];
-
- promises.push(this._getDiff().then(diff => {
- this._diff = diff;
- return this._loadDiffAssets();
- }));
-
- return Promise.all(promises).then(() => {
- if (this.prefs) {
- return this._renderDiffTable();
- }
- return Promise.resolve();
- }).then(() => {
- this._loading = false;
- });
- },
-
/** Cancel any remaining diff builder rendering work. */
cancel() {
this.$.diffBuilder.cancel();
@@ -266,37 +231,13 @@
this.toggleClass('no-left');
},
- /**
- * Load and display blame information for the base of the diff.
- * @return {Promise} A promise that resolves when blame finishes rendering.
- */
- loadBlame() {
- return this.$.restAPI.getBlame(this.changeNum, this.patchRange.patchNum,
- this.path, true)
- .then(blame => {
- if (!blame.length) {
- this.fire('show-alert', {message: MSG_EMPTY_BLAME});
- return Promise.reject(MSG_EMPTY_BLAME);
- }
-
- this._blame = blame;
-
- this.$.diffBuilder.setBlame(blame);
- this.classList.add('showBlame');
- });
- },
-
- _computeIsBlameLoaded(blame) {
- return !!blame;
- },
-
- /**
- * Unload blame information for the diff.
- */
- clearBlame() {
- this._blame = null;
- this.$.diffBuilder.setBlame(null);
- this.classList.remove('showBlame');
+ _blameChanged(newValue) {
+ this.$.diffBuilder.setBlame(newValue);
+ if (newValue) {
+ this.classList.add('showBlame');
+ } else {
+ this.classList.remove('showBlame');
+ }
},
_handleCommentSaveOrDiscard() {
@@ -304,12 +245,6 @@
{bubbles: true}));
},
- /** @return {boolean}} */
- _canRender() {
- return !!this.changeNum && !!this.patchRange && !!this.path &&
- !this.noAutoRender;
- },
-
/** @return {!Array<!HTMLElement>} */
getThreadEls() {
let threads = [];
@@ -371,20 +306,18 @@
addDraftAtLine(el) {
this._selectLine(el);
- this._isValidElForComment(el).then(valid => {
- if (!valid) { return; }
+ if (!this._isValidElForComment(el)) { return; }
- const value = el.getAttribute('data-value');
- let lineNum;
- if (value !== GrDiffLine.FILE) {
- lineNum = parseInt(value, 10);
- if (isNaN(lineNum)) {
- this.fire('show-alert', {message: ERR_INVALID_LINE + value});
- return;
- }
+ const value = el.getAttribute('data-value');
+ let lineNum;
+ if (value !== GrDiffLine.FILE) {
+ lineNum = parseInt(value, 10);
+ if (isNaN(lineNum)) {
+ this.fire('show-alert', {message: ERR_INVALID_LINE + value});
+ return;
}
- this._createComment(el, lineNum);
- });
+ }
+ this._createComment(el, lineNum);
},
_handleCreateComment(e) {
@@ -392,36 +325,34 @@
const side = e.detail.side;
const lineNum = range.endLine;
const lineEl = this.$.diffBuilder.getLineElByNumber(lineNum, side);
- this._isValidElForComment(lineEl).then(valid => {
- if (!valid) { return; }
+ if (this._isValidElForComment(lineEl)) {
this._createComment(lineEl, lineNum, side, range);
- });
+ }
},
+ /** @return {boolean} */
_isValidElForComment(el) {
- return this._getLoggedIn().then(loggedIn => {
- if (!loggedIn) {
- this.fire('show-auth-required');
- return false;
- }
- const patchNum = el.classList.contains(DiffSide.LEFT) ?
- this.patchRange.basePatchNum :
- this.patchRange.patchNum;
+ if (!this.loggedIn) {
+ this.fire('show-auth-required');
+ return false;
+ }
+ const patchNum = el.classList.contains(DiffSide.LEFT) ?
+ this.patchRange.basePatchNum :
+ this.patchRange.patchNum;
- const isEdit = this.patchNumEquals(patchNum, this.EDIT_NAME);
- const isEditBase = this.patchNumEquals(patchNum, this.PARENT_NAME) &&
- this.patchNumEquals(this.patchRange.patchNum, this.EDIT_NAME);
+ const isEdit = this.patchNumEquals(patchNum, this.EDIT_NAME);
+ const isEditBase = this.patchNumEquals(patchNum, this.PARENT_NAME) &&
+ this.patchNumEquals(this.patchRange.patchNum, this.EDIT_NAME);
- if (isEdit) {
- this.fire('show-alert', {message: ERR_COMMENT_ON_EDIT});
- return false;
- } else if (isEditBase) {
- this.fire('show-alert', {message: ERR_COMMENT_ON_EDIT_BASE});
- return false;
- }
- return true;
- });
+ if (isEdit) {
+ this.fire('show-alert', {message: ERR_COMMENT_ON_EDIT});
+ return false;
+ } else if (isEditBase) {
+ this.fire('show-alert', {message: ERR_COMMENT_ON_EDIT_BASE});
+ return false;
+ }
+ return true;
},
/**
@@ -431,7 +362,7 @@
* @param {!Object=} opt_range
*/
_createComment(lineEl, opt_lineNum, opt_side, opt_range) {
- this.$.reporting.recordDraftInteraction();
+ this.dispatchEvent(new CustomEvent('draft-interaction', {bubbles: true}));
const contentText = this.$.diffBuilder.getContentByLineEl(lineEl);
const contentEl = contentText.parentElement;
const side = opt_side ||
@@ -462,20 +393,6 @@
},
/**
- * @param {string} commentSide
- * @param {!Object=} opt_range
- */
- _getRangeString(commentSide, opt_range) {
- return opt_range ?
- 'range-' +
- opt_range.startLine + '-' +
- opt_range.startChar + '-' +
- opt_range.endLine + '-' +
- opt_range.endChar + '-' +
- commentSide : 'line-' + commentSide;
- },
-
- /**
* Gets or creates a comment thread for a specific spot on a diff.
* May include a range, if the comment is a range comment.
*
@@ -661,6 +578,17 @@
this._prefsChanged(this.prefs);
},
+ /** @param {boolean} newValue */
+ _loadingChanged(newValue) {
+ if (newValue) {
+ this.cancel();
+ this._blame = null;
+ this._safetyBypass = null;
+ this._showWarning = false;
+ this.clearDiffContent();
+ }
+ },
+
_lineWrappingObserver() {
this._prefsChanged(this.prefs);
},
@@ -668,7 +596,7 @@
_prefsChanged(prefs) {
if (!prefs) { return; }
- this.clearBlame();
+ this._blame = null;
const stylesToUpdate = {};
@@ -689,21 +617,32 @@
this.updateStyles(stylesToUpdate);
- if (this._diff && this.comments && !this.noRenderOnPrefsChange) {
+ if (this.diff && this.comments && !this.noRenderOnPrefsChange) {
+ this._renderDiffTable();
+ }
+ },
+
+ _diffChanged(newValue) {
+ if (newValue) {
this._renderDiffTable();
}
},
_renderDiffTable() {
+ if (!this.prefs) {
+ this.dispatchEvent(new CustomEvent('render', {bubbles: true}));
+ return;
+ }
if (this.prefs.context === -1 &&
- this._diffLength(this._diff) >= LARGE_DIFF_THRESHOLD_LINES &&
+ this._diffLength(this.diff) >= LARGE_DIFF_THRESHOLD_LINES &&
this._safetyBypass === null) {
this._showWarning = true;
- return Promise.resolve();
+ this.dispatchEvent(new CustomEvent('render', {bubbles: true}));
+ return;
}
this._showWarning = false;
- return this.$.diffBuilder.render(this.comments, this._getBypassPrefs());
+ this.$.diffBuilder.render(this.comments, this._getBypassPrefs());
},
/**
@@ -720,117 +659,6 @@
this.$.diffTable.innerHTML = null;
},
- _handleGetDiffError(response) {
- // Loading the diff may respond with 409 if the file is too large. In this
- // case, use a toast error..
- if (response.status === 409) {
- this.fire('server-error', {response});
- return;
- }
- this.fire('page-error', {response});
- },
-
- /** @return {!Promise<!Object>} */
- _getDiff() {
- return this.$.restAPI.getDiff(
- this.changeNum,
- this.patchRange.basePatchNum,
- this.patchRange.patchNum,
- this.path,
- this._handleGetDiffError.bind(this)).then(diff => {
- this._reportDiff(diff);
- if (!this.commitRange) {
- this.filesWeblinks = {};
- return diff;
- }
- this.filesWeblinks = {
- meta_a: Gerrit.Nav.getFileWebLinks(
- this.projectName, this.commitRange.baseCommit, this.path,
- {weblinks: diff && diff.meta_a && diff.meta_a.web_links}),
- meta_b: Gerrit.Nav.getFileWebLinks(
- this.projectName, this.commitRange.commit, this.path,
- {weblinks: diff && diff.meta_b && diff.meta_b.web_links}),
- };
- return diff;
- });
- },
-
- /**
- * Report info about the diff response.
- */
- _reportDiff(diff) {
- if (!diff || !diff.content) { return; }
-
- // Count the delta lines stemming from normal deltas, and from
- // due_to_rebase deltas.
- let nonRebaseDelta = 0;
- let rebaseDelta = 0;
- diff.content.forEach(chunk => {
- if (chunk.ab) { return; }
- const deltaSize = Math.max(
- chunk.a ? chunk.a.length : 0, chunk.b ? chunk.b.length : 0);
- if (chunk.due_to_rebase) {
- rebaseDelta += deltaSize;
- } else {
- nonRebaseDelta += deltaSize;
- }
- });
-
- // Find the percent of the delta from due_to_rebase chunks rounded to two
- // digits. Diffs with no delta are considered 0%.
- const totalDelta = rebaseDelta + nonRebaseDelta;
- const percentRebaseDelta = !totalDelta ? 0 :
- Math.round(100 * rebaseDelta / totalDelta);
-
- // Report the due_to_rebase percentage in the "diff" category when
- // applicable.
- if (this.patchRange.basePatchNum === 'PARENT') {
- this.$.reporting.reportInteraction(EVENT_AGAINST_PARENT);
- } else if (percentRebaseDelta === 0) {
- this.$.reporting.reportInteraction(EVENT_ZERO_REBASE);
- } else {
- this.$.reporting.reportInteraction(EVENT_NONZERO_REBASE,
- percentRebaseDelta);
- }
- },
-
- /** @return {!Promise} */
- _getLoggedIn() {
- return this.$.restAPI.getLoggedIn();
- },
-
- /** @return {boolean} */
- _computeIsImageDiff() {
- if (!this._diff) { return false; }
-
- const isA = this._diff.meta_a &&
- this._diff.meta_a.content_type.startsWith('image/');
- const isB = this._diff.meta_b &&
- this._diff.meta_b.content_type.startsWith('image/');
-
- return !!(this._diff.binary && (isA || isB));
- },
-
- /** @return {!Promise} */
- _loadDiffAssets() {
- if (this.isImageDiff) {
- return this._getImages().then(images => {
- this._baseImage = images.baseImage;
- this._revisionImage = images.revisionImage;
- });
- } else {
- this._baseImage = null;
- this._revisionImage = null;
- return Promise.resolve();
- }
- },
-
- /** @return {!Promise} */
- _getImages() {
- return this.$.restAPI.getImagesForDiff(this.changeNum, this._diff,
- this.patchRange);
- },
-
_projectConfigChanged(projectConfig) {
const threadEls = this.getThreadEls();
for (let i = 0; i < threadEls.length; i++) {
@@ -891,6 +719,14 @@
},
/**
+ * @param {string} errorMessage
+ * @return {string}
+ */
+ _computeErrorClass(errorMessage) {
+ return errorMessage ? 'showError' : '';
+ },
+
+ /**
* @return {number|null}
*/
_computeParentIndex(patchRangeRecord) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index b98602f..0274fae 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -48,18 +48,8 @@
sandbox.restore();
});
- test('reload cancels before network resolves', () => {
- element = fixture('basic');
- const cancelStub = sandbox.stub(element, 'cancel');
-
- // Stub the network calls into requests that never resolve.
- sandbox.stub(element, '_getDiff', () => new Promise(() => {}));
-
- element.reload();
- assert.isTrue(cancelStub.called);
- });
-
test('cancel', () => {
+ element = fixture('basic');
const cancelStub = sandbox.stub(element.$.diffBuilder, 'cancel');
element.cancel();
assert.isTrue(cancelStub.calledOnce);
@@ -172,10 +162,12 @@
suite('not logged in', () => {
setup(() => {
+ const getLoggedInPromise = Promise.resolve(false);
stub('gr-rest-api-interface', {
- getLoggedIn() { return Promise.resolve(false); },
+ getLoggedIn() { return getLoggedInPromise; },
});
element = fixture('basic');
+ return getLoggedInPromise;
});
test('toggleLeftDiff', () => {
@@ -185,15 +177,12 @@
assert.isFalse(element.classList.contains('no-left'));
});
- test('addDraftAtLine', done => {
+ test('addDraftAtLine', () => {
sandbox.stub(element, '_selectLine');
const loggedInErrorSpy = sandbox.spy();
element.addEventListener('show-auth-required', loggedInErrorSpy);
element.addDraftAtLine();
- flush(() => {
- assert.isTrue(loggedInErrorSpy.called);
- done();
- });
+ assert.isTrue(loggedInErrorSpy.called);
});
test('view does not start with displayLine classList', () => {
@@ -209,39 +198,6 @@
element.$$('.diffContainer').classList.contains('displayLine'));
});
- test('loads files weblinks', () => {
- const weblinksStub = sandbox.stub(Gerrit.Nav, '_generateWeblinks')
- .returns({name: 'stubb', url: '#s'});
- sandbox.stub(element.$.restAPI, 'getDiff').returns(Promise.resolve({}));
- element.projectName = 'test-project';
- element.path = 'test-path';
- element.commitRange = {baseCommit: 'test-base', commit: 'test-commit'};
- element.patchRange = {};
- return element._getDiff().then(() => {
- assert.isTrue(weblinksStub.calledTwice);
- assert.isTrue(weblinksStub.firstCall.calledWith({
- commit: 'test-base',
- file: 'test-path',
- options: {
- weblinks: undefined,
- },
- repo: 'test-project',
- type: Gerrit.Nav.WeblinkType.FILE}));
- assert.isTrue(weblinksStub.secondCall.calledWith({
- commit: 'test-commit',
- file: 'test-path',
- options: {
- weblinks: undefined,
- },
- repo: 'test-project',
- type: Gerrit.Nav.WeblinkType.FILE}));
- assert.deepEqual(element.filesWeblinks, {
- meta_a: [{name: 'stubb', url: '#s'}],
- meta_b: [{name: 'stubb', url: '#s'}],
- });
- });
- });
-
test('remove comment', () => {
element.comments = {
meta: {
@@ -343,20 +299,6 @@
});
});
- test('_getRangeString', () => {
- const side = 'PARENT';
- const range = {
- startLine: 1,
- startChar: 1,
- endLine: 1,
- endChar: 2,
- };
- assert.equal(element._getRangeString(side, range),
- 'range-1-1-1-2-PARENT');
- assert.equal(element._getRangeString(side, null),
- 'line-PARENT');
- }),
-
test('thread groups', () => {
const contentEl = document.createElement('div');
const commentSide = 'left';
@@ -410,7 +352,6 @@
suite('image diffs', () => {
let mockFile1;
let mockFile2;
- const stubs = [];
setup(() => {
mockFile1 = {
body: 'Qk06AAAAAAAAADYAAAAoAAAAAQAAAP////8BACAAAAAAAAAAAAATCwAAE' +
@@ -422,66 +363,29 @@
'wsAAAAAAAAAAAAA/////w==',
type: 'image/bmp',
};
- const mockCommit = {
- commit: '9a1a1d10baece5efbba10bc4ccf808a67a50ac0a',
- parents: [{
- commit: '7338aa9adfe57909f1fdaf88975cdea467d3382f',
- subject: 'Added a carrot',
- }],
- author: {
- name: 'Wyatt Allen',
- email: 'wyatta@google.com',
- date: '2016-05-23 21:44:51.000000000',
- tz: -420,
- },
- committer: {
- name: 'Wyatt Allen',
- email: 'wyatta@google.com',
- date: '2016-05-25 00:25:41.000000000',
- tz: -420,
- },
- subject: 'Updated the carrot',
- message: 'Updated the carrot\n\nChange-Id: Iabcd123\n',
- };
- const mockComments = {baseComments: [], comments: []};
-
- stubs.push(sandbox.stub(element.$.restAPI, 'getCommitInfo',
- () => Promise.resolve(mockCommit)));
- stubs.push(sandbox.stub(element.$.restAPI,
- 'getB64FileContents',
- (changeId, patchNum, path, opt_parentIndex) => {
- return Promise.resolve(opt_parentIndex === 1 ? mockFile1 :
- mockFile2);
- }));
- stubs.push(sandbox.stub(element.$.restAPI, '_getDiffComments',
- () => Promise.resolve(mockComments)));
- stubs.push(sandbox.stub(element.$.restAPI, 'getDiffDrafts',
- () => Promise.resolve(mockComments)));
element.patchRange = {basePatchNum: 'PARENT', patchNum: 1};
element.comments = {left: [], right: []};
+ element.isImageDiff = true;
+ element.prefs = {
+ auto_hide_diff_table_header: true,
+ context: 10,
+ cursor_blink_rate: 0,
+ font_size: 12,
+ ignore_whitespace: 'IGNORE_NONE',
+ intraline_difference: true,
+ line_length: 100,
+ line_wrapping: false,
+ show_line_endings: true,
+ show_tabs: true,
+ show_whitespace_errors: true,
+ syntax_highlighting: true,
+ tab_size: 8,
+ theme: 'DEFAULT',
+ };
});
test('renders image diffs with same file name', done => {
- const mockDiff = {
- meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
- meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
- lines: 560},
- intraline_status: 'OK',
- change_type: 'MODIFIED',
- diff_header: [
- 'diff --git a/carrot.jpg b/carrot.jpg',
- 'index 2adc47d..f9c2f2c 100644',
- '--- a/carrot.jpg',
- '+++ b/carrot.jpg',
- 'Binary files differ',
- ],
- content: [{skip: 66}],
- binary: true,
- };
- stubs.push(sandbox.stub(element, '_getDiff',
- () => Promise.resolve(mockDiff)));
-
const rendered = () => {
// Recognizes that it should be an image diff.
assert.isTrue(element.isImageDiff);
@@ -536,10 +440,24 @@
element.addEventListener('render', rendered);
- element.$.restAPI.getDiffPreferences().then(prefs => {
- element.prefs = prefs;
- element.reload();
- });
+ element.baseImage = mockFile1;
+ element.revisionImage = mockFile2;
+ element.diff = {
+ meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
+ meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
+ lines: 560},
+ intraline_status: 'OK',
+ change_type: 'MODIFIED',
+ diff_header: [
+ 'diff --git a/carrot.jpg b/carrot.jpg',
+ 'index 2adc47d..f9c2f2c 100644',
+ '--- a/carrot.jpg',
+ '+++ b/carrot.jpg',
+ 'Binary files differ',
+ ],
+ content: [{skip: 66}],
+ binary: true,
+ };
});
test('renders image diffs with a different file name', done => {
@@ -559,8 +477,6 @@
content: [{skip: 66}],
binary: true,
};
- stubs.push(sandbox.stub(element, '_getDiff',
- () => Promise.resolve(mockDiff)));
const rendered = () => {
// Recognizes that it should be an image diff.
@@ -618,10 +534,11 @@
element.addEventListener('render', rendered);
- element.$.restAPI.getDiffPreferences().then(prefs => {
- element.prefs = prefs;
- element.reload();
- });
+ element.baseImage = mockFile1;
+ element.baseImage._name = mockDiff.meta_a.name;
+ element.revisionImage = mockFile2;
+ element.revisionImage._name = mockDiff.meta_b.name;
+ element.diff = mockDiff;
});
test('renders added image', done => {
@@ -640,8 +557,6 @@
content: [{skip: 66}],
binary: true,
};
- stubs.push(sandbox.stub(element, '_getDiff',
- () => Promise.resolve(mockDiff)));
element.addEventListener('render', () => {
// Recognizes that it should be an image diff.
@@ -657,10 +572,8 @@
done();
});
- element.$.restAPI.getDiffPreferences().then(prefs => {
- element.prefs = prefs;
- element.reload();
- });
+ element.revisionImage = mockFile2;
+ element.diff = mockDiff;
});
test('renders removed image', done => {
@@ -679,8 +592,6 @@
content: [{skip: 66}],
binary: true,
};
- stubs.push(sandbox.stub(element, '_getDiff',
- () => Promise.resolve(mockDiff)));
element.addEventListener('render', () => {
// Recognizes that it should be an image diff.
@@ -696,10 +607,8 @@
done();
});
- element.$.restAPI.getDiffPreferences().then(prefs => {
- element.prefs = prefs;
- element.reload();
- });
+ element.baseImage = mockFile1;
+ element.diff = mockDiff;
});
test('does not render disallowed image type', done => {
@@ -720,9 +629,6 @@
};
mockFile1.type = 'image/jpeg-evil';
- stubs.push(sandbox.stub(element, '_getDiff',
- () => Promise.resolve(mockDiff)));
-
element.addEventListener('render', () => {
// Recognizes that it should be an image diff.
assert.isTrue(element.isImageDiff);
@@ -733,10 +639,8 @@
done();
});
- element.$.restAPI.getDiffPreferences().then(prefs => {
- element.prefs = prefs;
- element.reload();
- });
+ element.baseImage = mockFile1;
+ element.diff = mockDiff;
});
});
@@ -783,20 +687,10 @@
content.click();
});
- test('_getDiff handles null diff responses', done => {
- stub('gr-rest-api-interface', {
- getDiff() { return Promise.resolve(null); },
- });
- element.changeNum = 123;
- element.patchRange = {basePatchNum: 1, patchNum: 2};
- element.path = 'file.txt';
- element._getDiff().then(done);
- });
-
suite('getCursorStops', () => {
const setupDiff = function() {
const mock = document.createElement('mock-diff-response');
- element._diff = mock.diffResponse;
+ element.diff = mock.diffResponse;
element.comments = {
left: [],
right: [],
@@ -845,14 +739,8 @@
suite('logged in', () => {
let fakeLineEl;
setup(() => {
- stub('gr-rest-api-interface', {
- getLoggedIn() { return Promise.resolve(true); },
- getPreferences() {
- return Promise.resolve({time_format: 'HHMM_12'});
- },
- getAccountCapabilities() { return Promise.resolve(); },
- });
element = fixture('basic');
+ element.loggedIn = true;
element.patchRange = {};
fakeLineEl = {
@@ -863,58 +751,40 @@
};
});
- test('addDraftAtLine', done => {
+ test('addDraftAtLine', () => {
sandbox.stub(element, '_selectLine');
sandbox.stub(element, '_createComment');
- const loggedInErrorSpy = sandbox.spy();
- element.addEventListener('show-auth-required', loggedInErrorSpy);
element.addDraftAtLine(fakeLineEl);
- flush(() => {
- assert.isFalse(loggedInErrorSpy.called);
- assert.isTrue(element._createComment
- .calledWithExactly(fakeLineEl, 42));
- done();
- });
+ assert.isTrue(element._createComment
+ .calledWithExactly(fakeLineEl, 42));
});
- test('addDraftAtLine on an edit', done => {
+ test('addDraftAtLine on an edit', () => {
element.patchRange.basePatchNum = element.EDIT_NAME;
sandbox.stub(element, '_selectLine');
sandbox.stub(element, '_createComment');
- const loggedInErrorSpy = sandbox.spy();
const alertSpy = sandbox.spy();
- element.addEventListener('show-auth-required', loggedInErrorSpy);
element.addEventListener('show-alert', alertSpy);
element.addDraftAtLine(fakeLineEl);
- flush(() => {
- assert.isFalse(loggedInErrorSpy.called);
- assert.isTrue(alertSpy.called);
- assert.isFalse(element._createComment.called);
- done();
- });
+ assert.isTrue(alertSpy.called);
+ assert.isFalse(element._createComment.called);
});
- test('addDraftAtLine on an edit base', done => {
+ test('addDraftAtLine on an edit base', () => {
element.patchRange.patchNum = element.EDIT_NAME;
element.patchRange.basePatchNum = element.PARENT_NAME;
sandbox.stub(element, '_selectLine');
sandbox.stub(element, '_createComment');
- const loggedInErrorSpy = sandbox.spy();
const alertSpy = sandbox.spy();
- element.addEventListener('show-auth-required', loggedInErrorSpy);
element.addEventListener('show-alert', alertSpy);
element.addDraftAtLine(fakeLineEl);
- flush(() => {
- assert.isFalse(loggedInErrorSpy.called);
- assert.isTrue(alertSpy.called);
- assert.isFalse(element._createComment.called);
- done();
- });
+ assert.isTrue(alertSpy.called);
+ assert.isFalse(element._createComment.called);
});
suite('change in preferences', () => {
setup(() => {
- element._diff = {
+ element.diff = {
meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
lines: 560},
@@ -1048,7 +918,8 @@
suite('diff header', () => {
setup(() => {
- element._diff = {
+ element = fixture('basic');
+ element.diff = {
meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
lines: 560},
@@ -1061,15 +932,15 @@
test('hidden', () => {
assert.equal(element._diffHeaderItems.length, 0);
- element.push('_diff.diff_header', 'diff --git a/test.jpg b/test.jpg');
+ element.push('diff.diff_header', 'diff --git a/test.jpg b/test.jpg');
assert.equal(element._diffHeaderItems.length, 0);
- element.push('_diff.diff_header', 'index 2adc47d..f9c2f2c 100644');
+ element.push('diff.diff_header', 'index 2adc47d..f9c2f2c 100644');
assert.equal(element._diffHeaderItems.length, 0);
- element.push('_diff.diff_header', '--- a/test.jpg');
+ element.push('diff.diff_header', '--- a/test.jpg');
assert.equal(element._diffHeaderItems.length, 0);
- element.push('_diff.diff_header', '+++ b/test.jpg');
+ element.push('diff.diff_header', '+++ b/test.jpg');
assert.equal(element._diffHeaderItems.length, 0);
- element.push('_diff.diff_header', 'test');
+ element.push('diff.diff_header', 'test');
assert.equal(element._diffHeaderItems.length, 1);
flushAsynchronousOperations();
@@ -1077,13 +948,13 @@
});
test('binary files', () => {
- element._diff.binary = true;
+ element.diff.binary = true;
assert.equal(element._diffHeaderItems.length, 0);
- element.push('_diff.diff_header', 'diff --git a/test.jpg b/test.jpg');
+ element.push('diff.diff_header', 'diff --git a/test.jpg b/test.jpg');
assert.equal(element._diffHeaderItems.length, 0);
- element.push('_diff.diff_header', 'test');
+ element.push('diff.diff_header', 'test');
assert.equal(element._diffHeaderItems.length, 1);
- element.push('_diff.diff_header', 'Binary files differ');
+ element.push('diff.diff_header', 'Binary files differ');
assert.equal(element._diffHeaderItems.length, 1);
});
});
@@ -1094,39 +965,49 @@
setup(() => {
element = fixture('basic');
renderStub = sandbox.stub(element.$.diffBuilder, 'render',
- () => Promise.resolve());
+ () => {
+ Promise.resolve();
+ element.$.diffBuilder.dispatchEvent(
+ new CustomEvent('render', {bubbles: true}));
+ });
const mock = document.createElement('mock-diff-response');
- element._diff = mock.diffResponse;
+ element.diff = mock.diffResponse;
element.comments = {left: [], right: []};
element.noRenderOnPrefsChange = true;
});
- test('lage render w/ context = 10', () => {
+ test('large render w/ context = 10', done => {
element.prefs = {context: 10};
sandbox.stub(element, '_diffLength', () => 10000);
- return element._renderDiffTable().then(() => {
+ element.addEventListener('render', () => {
assert.isTrue(renderStub.called);
assert.isFalse(element._showWarning);
+ done();
});
+ element._renderDiffTable();
});
- test('lage render w/ whole file and bypass', () => {
+ test('large render w/ whole file and bypass', done => {
element.prefs = {context: -1};
element._safetyBypass = 10;
sandbox.stub(element, '_diffLength', () => 10000);
- return element._renderDiffTable().then(() => {
+ element.addEventListener('render', () => {
assert.isTrue(renderStub.called);
assert.isFalse(element._showWarning);
+ done();
});
+ element._renderDiffTable();
});
- test('lage render w/ whole file and no bypass', () => {
+ test('large render w/ whole file and no bypass', done => {
element.prefs = {context: -1};
sandbox.stub(element, '_diffLength', () => 10000);
- return element._renderDiffTable().then(() => {
+ element.addEventListener('render', () => {
assert.isFalse(renderStub.called);
assert.isTrue(element._showWarning);
+ done();
});
+ element._renderDiffTable();
});
});
@@ -1135,144 +1016,19 @@
element = fixture('basic');
});
- test('clearBlame', () => {
- element._blame = [];
+ test('unsetting', () => {
+ element.blame = [];
const setBlameSpy = sandbox.spy(element.$.diffBuilder, 'setBlame');
element.classList.add('showBlame');
- element.clearBlame();
- assert.isNull(element._blame);
+ element.blame = null;
assert.isTrue(setBlameSpy.calledWithExactly(null));
assert.isFalse(element.classList.contains('showBlame'));
});
- test('loadBlame', () => {
+ test('setting', () => {
const mockBlame = [{id: 'commit id', ranges: [{start: 1, end: 2}]}];
- const showAlertStub = sinon.stub();
- element.addEventListener('show-alert', showAlertStub);
- const getBlameStub = sandbox.stub(element.$.restAPI, 'getBlame')
- .returns(Promise.resolve(mockBlame));
- element.changeNum = 42;
- element.patchRange = {patchNum: 5, basePatchNum: 4};
- element.path = 'foo/bar.baz';
- return element.loadBlame().then(() => {
- assert.isTrue(getBlameStub.calledWithExactly(
- 42, 5, 'foo/bar.baz', true));
- assert.isFalse(showAlertStub.called);
- assert.equal(element._blame, mockBlame);
- assert.isTrue(element.classList.contains('showBlame'));
- });
- });
-
- test('loadBlame empty', () => {
- const mockBlame = [];
- const showAlertStub = sinon.stub();
- element.addEventListener('show-alert', showAlertStub);
- sandbox.stub(element.$.restAPI, 'getBlame')
- .returns(Promise.resolve(mockBlame));
- element.changeNum = 42;
- element.patchRange = {patchNum: 5, basePatchNum: 4};
- element.path = 'foo/bar.baz';
- return element.loadBlame()
- .then(() => {
- assert.isTrue(false, 'Promise should not resolve');
- })
- .catch(() => {
- assert.isTrue(showAlertStub.calledOnce);
- assert.isNull(element._blame);
- assert.isFalse(element.classList.contains('showBlame'));
- });
- });
- });
-
- suite('_reportDiff', () => {
- let reportStub;
-
- setup(() => {
- element = fixture('basic');
- element.patchRange = {basePatchNum: 1};
- reportStub = sandbox.stub(element.$.reporting, 'reportInteraction');
- });
-
- test('null and content-less', () => {
- element._reportDiff(null);
- assert.isFalse(reportStub.called);
-
- element._reportDiff({});
- assert.isFalse(reportStub.called);
- });
-
- test('diff w/ no delta', () => {
- const diff = {
- content: [
- {ab: ['foo', 'bar']},
- {ab: ['baz', 'foo']},
- ],
- };
- element._reportDiff(diff);
- assert.isTrue(reportStub.calledOnce);
- assert.equal(reportStub.lastCall.args[0], 'rebase-percent-zero');
- assert.isUndefined(reportStub.lastCall.args[1]);
- });
-
- test('diff w/ no rebase delta', () => {
- const diff = {
- content: [
- {ab: ['foo', 'bar']},
- {a: ['baz', 'foo']},
- {ab: ['foo', 'bar']},
- {a: ['baz', 'foo'], b: ['bar', 'baz']},
- {ab: ['foo', 'bar']},
- {b: ['baz', 'foo']},
- {ab: ['foo', 'bar']},
- ],
- };
- element._reportDiff(diff);
- assert.isTrue(reportStub.calledOnce);
- assert.equal(reportStub.lastCall.args[0], 'rebase-percent-zero');
- assert.isUndefined(reportStub.lastCall.args[1]);
- });
-
- test('diff w/ some rebase delta', () => {
- const diff = {
- content: [
- {ab: ['foo', 'bar']},
- {a: ['baz', 'foo'], due_to_rebase: true},
- {ab: ['foo', 'bar']},
- {a: ['baz', 'foo'], b: ['bar', 'baz']},
- {ab: ['foo', 'bar']},
- {b: ['baz', 'foo'], due_to_rebase: true},
- {ab: ['foo', 'bar']},
- {a: ['baz', 'foo']},
- ],
- };
- element._reportDiff(diff);
- assert.isTrue(reportStub.calledOnce);
- assert.equal(reportStub.lastCall.args[0], 'rebase-percent-nonzero');
- assert.strictEqual(reportStub.lastCall.args[1], 50);
- });
-
- test('diff w/ all rebase delta', () => {
- const diff = {content: [{
- a: ['foo', 'bar'],
- b: ['baz', 'foo'],
- due_to_rebase: true,
- }]};
- element._reportDiff(diff);
- assert.isTrue(reportStub.calledOnce);
- assert.equal(reportStub.lastCall.args[0], 'rebase-percent-nonzero');
- assert.strictEqual(reportStub.lastCall.args[1], 100);
- });
-
- test('diff against parent event', () => {
- element.patchRange.basePatchNum = 'PARENT';
- const diff = {content: [{
- a: ['foo', 'bar'],
- b: ['baz', 'foo'],
- }]};
- element._reportDiff(diff);
- assert.isTrue(reportStub.calledOnce);
- assert.equal(reportStub.lastCall.args[0], 'diff-against-parent');
- assert.isUndefined(reportStub.lastCall.args[1]);
+ element.blame = mockBlame;
+ assert.isTrue(element.classList.contains('showBlame'));
});
});
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html
index c2d6cdf..f80e9f8 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html
@@ -51,7 +51,6 @@
}
header gr-editable-label {
font-size: var(--font-size-large);
- font-weight: bold;
--label-style: {
text-overflow: initial;
white-space: initial;
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index b4aec99..0508608 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -128,6 +128,10 @@
});
this.$.restAPI.getConfig().then(config => {
this._serverConfig = config;
+
+ if (config && config.gerrit && config.gerrit.report_bug_url) {
+ this._feedbackUrl = config.gerrit.report_bug_url;
+ }
});
this.$.restAPI.getVersion().then(version => {
this._version = version;
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
index b75ae44..7476637 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
@@ -40,7 +40,7 @@
this._handleMigrations(plugins.js_resource_paths || [], htmlPlugins)
.map(p => this._urlFor(p))
.filter(p => !Gerrit._isPluginPreloaded(p));
- const defaultTheme = config.default_theme;
+ const defaultTheme = this._urlFor(config.default_theme);
const pluginsPending =
[].concat(jsPlugins, htmlPlugins, defaultTheme || []);
Gerrit._setPluginsPending(pluginsPending);
@@ -98,6 +98,9 @@
},
_urlFor(pathOrUrl) {
+ if (!pathOrUrl) {
+ return pathOrUrl;
+ }
if (pathOrUrl.startsWith('preloaded:') ||
pathOrUrl.startsWith('http')) {
// Plugins are loaded from another domain or preloaded.
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
index 641a800..26958ee 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
@@ -188,6 +188,15 @@
assert.equal(Gerrit._pluginInstallError.callCount, 2);
});
+ test('default theme is loaded with html plugins', () => {
+ sandbox.stub(Gerrit, '_setPluginsPending');
+ element.config = {
+ default_theme: '/oof',
+ plugin: {},
+ };
+ assert.isTrue(Gerrit._setPluginsPending.calledWith([url + '/oof']));
+ });
+
test('skips preloaded plugins', () => {
sandbox.stub(Gerrit, '_isPluginPreloaded')
.withArgs(url + '/plugins/foo/bar').returns(true)
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.html b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.html
index fa188d7..4f69513 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.html
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.html
@@ -37,6 +37,9 @@
cursor: pointer;
text-align: center;
}
+ .checkboxContainer input {
+ cursor: pointer;
+ }
.checkboxContainer:hover {
outline: 1px solid var(--border-color);
}
@@ -52,12 +55,12 @@
<tbody>
<tr>
<td>Number</td>
- <td
- class="checkboxContainer"
- on-tap="_handleTargetTap">
+ <td class="checkboxContainer"
+ on-tap="_handleCheckboxContainerTap">
<input
type="checkbox"
name="number"
+ on-tap="_handleNumberCheckboxTap"
checked$="[[showNumber]]">
</td>
</tr>
@@ -65,10 +68,11 @@
<tr>
<td>[[item]]</td>
<td class="checkboxContainer"
- on-tap="_handleTargetTap">
+ on-tap="_handleCheckboxContainerTap">
<input
type="checkbox"
name="[[item]]"
+ on-tap="_handleTargetTap"
checked$="[[!isColumnHidden(item, displayedColumns)]]">
</td>
</tr>
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js
index 7b74096..7d109633 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js
@@ -35,40 +35,42 @@
Gerrit.ChangeTableBehavior,
],
- _getButtonText(isShown) {
- return isShown ? 'Hide' : 'Show';
- },
-
- _updateDisplayedColumns(displayedColumns, name, checked) {
- if (!checked) {
- return displayedColumns.filter(column => {
- return name.toLowerCase() !== column.toLowerCase();
- });
- } else {
- return displayedColumns.concat([name]);
- }
+ /**
+ * Get the list of enabled column names from whichever checkboxes are
+ * checked (excluding the number checkbox).
+ * @return {!Array<string>}
+ */
+ _getDisplayedColumns() {
+ return Polymer.dom(this.root)
+ .querySelectorAll('.checkboxContainer input:not([name=number])')
+ .filter(checkbox => checkbox.checked)
+ .map(checkbox => checkbox.name);
},
/**
- * Handles tap on either the checkbox itself or the surrounding table cell.
+ * Handle a tap on a checkbox container and relay the tap to the checkbox it
+ * contains.
+ */
+ _handleCheckboxContainerTap(e) {
+ const checkbox = Polymer.dom(e.target).querySelector('input');
+ if (!checkbox) { return; }
+ checkbox.click();
+ },
+
+ /**
+ * Handle a tap on the number checkbox and update the showNumber property
+ * accordingly.
+ */
+ _handleNumberCheckboxTap(e) {
+ this.showNumber = Polymer.dom(e).rootTarget.checked;
+ },
+
+ /**
+ * Handle a tap on a displayed column checkboxes (excluding number) and
+ * update the displayedColumns property accordingly.
*/
_handleTargetTap(e) {
- let checkbox = Polymer.dom(e.target).querySelector('input');
- if (checkbox) {
- checkbox.click();
- } else {
- // The target is the checkbox itself.
- checkbox = Polymer.dom(e).rootTarget;
- }
-
- if (checkbox.name === 'number') {
- this.showNumber = checkbox.checked;
- return;
- }
-
- this.set('displayedColumns',
- this._updateDisplayedColumns(
- this.displayedColumns, checkbox.name, checkbox.checked));
+ this.set('displayedColumns', this._getDisplayedColumns());
},
});
})();
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
index 587cc3b..32fab9d 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
@@ -53,6 +53,7 @@
];
element.set('displayedColumns', columns);
+ element.showNumber = false;
flushAsynchronousOperations();
});
@@ -108,66 +109,50 @@
displayedLength + 1);
});
- test('_handleTargetTap', () => {
- const checkbox = element.$$('table tr:nth-child(2) input');
- let originalDisplayedColumns = element.displayedColumns;
- const td = element.$$('table tr:nth-child(2) .checkboxContainer');
- const displayedColumnStub =
- sandbox.stub(element, '_updateDisplayedColumns');
-
- MockInteractions.tap(checkbox);
- assert.isTrue(displayedColumnStub.lastCall.calledWithExactly(
- originalDisplayedColumns,
- checkbox.name,
- checkbox.checked));
-
- originalDisplayedColumns = element.displayedColumns;
- MockInteractions.tap(td);
- assert.isTrue(displayedColumnStub.lastCall.calledWithExactly(
- originalDisplayedColumns,
- checkbox.name,
- checkbox.checked));
+ test('_getDisplayedColumns', () => {
+ assert.deepEqual(element._getDisplayedColumns(), columns);
+ MockInteractions.tap(
+ element.$$('.checkboxContainer input[name=Assignee]'));
+ assert.deepEqual(element._getDisplayedColumns(),
+ columns.filter(c => c !== 'Assignee'));
});
- test('_handleTargetTap on number', () => {
- element.showNumber = false;
- const checkbox = element.$$('table tr:nth-child(1) input');
- const displayedColumnStub =
- sandbox.stub(element, '_updateDisplayedColumns');
+ test('_handleCheckboxContainerTap relayes taps to checkboxes', () => {
+ sandbox.stub(element, '_handleNumberCheckboxTap');
+ sandbox.stub(element, '_handleTargetTap');
- MockInteractions.tap(checkbox);
- assert.isFalse(displayedColumnStub.called);
+ MockInteractions.tap(
+ element.$$('table tr:first-of-type .checkboxContainer'));
+ assert.isTrue(element._handleNumberCheckboxTap.calledOnce);
+ assert.isFalse(element._handleTargetTap.called);
+
+ MockInteractions.tap(
+ element.$$('table tr:last-of-type .checkboxContainer'));
+ assert.isTrue(element._handleNumberCheckboxTap.calledOnce);
+ assert.isTrue(element._handleTargetTap.calledOnce);
+ });
+
+ test('_handleNumberCheckboxTap', () => {
+ sandbox.spy(element, '_handleNumberCheckboxTap');
+
+ MockInteractions
+ .tap(element.$$('.checkboxContainer input[name=number]'));
+ assert.isTrue(element._handleNumberCheckboxTap.calledOnce);
assert.isTrue(element.showNumber);
- MockInteractions.tap(checkbox);
+ MockInteractions
+ .tap(element.$$('.checkboxContainer input[name=number]'));
+ assert.isTrue(element._handleNumberCheckboxTap.calledTwice);
assert.isFalse(element.showNumber);
});
- test('_updateDisplayedColumns', () => {
- let name = 'Subject';
- let checked = false;
- assert.deepEqual(element._updateDisplayedColumns(columns, name, checked),
- [
- 'Status',
- 'Owner',
- 'Assignee',
- 'Repo',
- 'Branch',
- 'Updated',
- ]);
- name = 'Size';
- checked = true;
- assert.deepEqual(element._updateDisplayedColumns(columns, name, checked),
- [
- 'Subject',
- 'Status',
- 'Owner',
- 'Assignee',
- 'Repo',
- 'Branch',
- 'Updated',
- 'Size',
- ]);
+ test('_handleTargetTap', () => {
+ sandbox.spy(element, '_handleTargetTap');
+ assert.include(element.displayedColumns, 'Assignee');
+ MockInteractions
+ .tap(element.$$('.checkboxContainer input[name=Assignee]'));
+ assert.isTrue(element._handleTargetTap.calledOnce);
+ assert.notInclude(element.displayedColumns, 'Assignee');
});
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.html b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.html
index 52785ff..32ca557 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.html
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.html
@@ -20,7 +20,6 @@
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-icons/gr-icons.html">
-<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-copy-clipboard">
<template>
@@ -30,15 +29,11 @@
display: flex;
flex-wrap: wrap;
}
- .text label {
- flex: 0 0 100%;
- }
.copyText {
flex-grow: 1;
margin-right: .3em;
}
- .hideInput,
- .hideLabel label {
+ .hideInput {
display: none;
}
input {
@@ -51,8 +46,7 @@
width: 1.2em;
}
</style>
- <div class$="text [[_computeLabelClass(hideLabel)]]">
- <label>[[title]]</label>
+ <div class="text">
<input id="input" is="iron-input"
class$="copyText [[_computeInputClass(hideInput)]]"
type="text"
@@ -67,8 +61,7 @@
on-tap="_copyToClipboard">
<iron-icon id="icon" icon="gr-icons:content-copy"></iron-icon>
</gr-button>
- </div>
- <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </div>
</template>
<script src="gr-copy-clipboard.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js
index cd8cb00..cabee36 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js
@@ -24,7 +24,6 @@
properties: {
text: String,
- title: String,
buttonTitle: String,
hasTooltip: {
type: Boolean,
@@ -34,10 +33,6 @@
type: Boolean,
value: false,
},
- hideLabel: {
- type: Boolean,
- value: false,
- },
},
focusOnCopy() {
@@ -48,10 +43,6 @@
return hideInput ? 'hideInput' : '';
},
- _computeLabelClass(hideLabel) {
- return hideLabel ? 'hideLabel' : '';
- },
-
_handleInputTap(e) {
e.preventDefault();
Polymer.dom(e).rootTarget.select();
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
index c865917..d6e9dca 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
@@ -38,12 +38,8 @@
let sandbox;
setup(() => {
- stub('gr-rest-api-interface', {
- saveChangeStarred() { return Promise.resolve({ok: true}); },
- });
sandbox = sinon.sandbox.create();
element = fixture('basic');
- element.title = 'Checkout';
element.text = `git fetch http://gerrit@localhost:8080/a/test-project
refs/changes/05/5/1 && git checkout FETCH_HEAD`;
flushAsynchronousOperations();
@@ -79,12 +75,5 @@
flushAsynchronousOperations();
assert.equal(getComputedStyle(element.$.input).display, 'none');
});
-
- test('hideLabel', () => {
- assert.notEqual(getComputedStyle(element.$$('label')).display, 'none');
- element.hideLabel = true;
- flushAsynchronousOperations();
- assert.equal(getComputedStyle(element.$$('label')).display, 'none');
- });
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.html b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.html
index 83e99be..bfa7885 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.html
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.html
@@ -20,7 +20,7 @@
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
<link rel="import" href="../../../bower_components/paper-tabs/paper-tabs.html">
-<link rel="import" href="../../shared/gr-copy-clipboard/gr-copy-clipboard.html">
+<link rel="import" href="../../shared/gr-shell-command/gr-shell-command.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -52,7 +52,7 @@
display: flex;
flex-direction: column;
}
- gr-copy-clipboard {
+ gr-shell-command {
width: 60em;
margin-bottom: .5em;
}
@@ -75,9 +75,9 @@
<template is="dom-repeat"
items="[[commands]]"
as="command">
- <gr-copy-clipboard
- title=[[command.title]]
- text=[[command.command]]></gr-copy-clipboard>
+ <gr-shell-command
+ label=[[command.title]]
+ command=[[command.command]]></gr-shell-command>
</template>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js
index 319cd04..ca77a30 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js
@@ -44,7 +44,7 @@
},
focusOnCopy() {
- this.$$('gr-copy-clipboard').focusOnCopy();
+ this.$$('gr-shell-command').focusOnCopy();
},
_getLoggedIn() {
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
index 47219a7..c59e56a 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
@@ -74,7 +74,7 @@
});
test('focusOnCopy', () => {
- const focusStub = sandbox.stub(element.$$('gr-copy-clipboard'),
+ const focusStub = sandbox.stub(element.$$('gr-shell-command'),
'focusOnCopy');
element.focusOnCopy();
assert.isTrue(focusStub.called);
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
index c1d0936..e0c7c37 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
@@ -78,6 +78,16 @@
assert.strictEqual(plugin, otherPlugin);
});
+ test('flushes preinstalls if provided', () => {
+ assert.doesNotThrow(() => {
+ Gerrit._flushPreinstalls();
+ });
+ window.Gerrit.flushPreinstalls = sandbox.stub();
+ Gerrit._flushPreinstalls();
+ assert.isTrue(window.Gerrit.flushPreinstalls.calledOnce);
+ delete window.Gerrit.flushPreinstalls;
+ });
+
test('url', () => {
assert.equal(plugin.url(), 'http://test.com/plugins/testplugin/');
assert.equal(plugin.url('/static/test.js'),
@@ -429,6 +439,16 @@
assert.strictEqual(pluginApi.getPluginName(), 'foo');
});
+ test('installing preloaded plugin', () => {
+ let plugin;
+ window.ASSETS_PATH = 'http://blips.com/chitz/';
+ Gerrit.install(p => { plugin = p; }, '0.1', 'preloaded:foo');
+ assert.strictEqual(plugin.getPluginName(), 'foo');
+ assert.strictEqual(plugin.url('/some/thing.html'),
+ 'http://blips.com/plugins/foo/some/thing.html');
+ delete window.ASSETS_PATH;
+ });
+
suite('test plugin with base url', () => {
setup(() => {
sandbox.stub(Gerrit.BaseUrlBehavior, 'getBaseUrl').returns('/r');
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.js
index 57cbc85..c18f753 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.js
@@ -43,10 +43,14 @@
* @param {string} method HTTP Method (GET, POST, etc)
* @param {string} url URL without base path or plugin prefix
* @param {Object=} payload Respected for POST and PUT only.
+ * @param {?function(?Response, string=)=} opt_errFn
+ * passed as null sometimes.
* @return {!Promise}
*/
- GrPluginRestApi.prototype.fetch = function(method, url, opt_payload) {
- return getRestApi().send(method, this.opt_prefix + url, opt_payload);
+ GrPluginRestApi.prototype.fetch = function(method, url, opt_payload,
+ opt_errFn) {
+ return getRestApi().send(method, this.opt_prefix + url, opt_payload,
+ opt_errFn);
};
/**
@@ -54,10 +58,13 @@
* @param {string} method HTTP Method (GET, POST, etc)
* @param {string} url URL without base path or plugin prefix
* @param {Object=} payload Respected for POST and PUT only.
+ * @param {?function(?Response, string=)=} opt_errFn
+ * passed as null sometimes.
* @return {!Promise} resolves on success, rejects on error.
*/
- GrPluginRestApi.prototype.send = function(method, url, opt_payload) {
- return this.fetch(method, url, opt_payload).then(response => {
+ GrPluginRestApi.prototype.send = function(method, url, opt_payload,
+ opt_errFn) {
+ return this.fetch(method, url, opt_payload, opt_errFn).then(response => {
if (response.status < 200 || response.status >= 300) {
return response.text().then(text => {
if (text) {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index 1efc176..36a428d 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
@@ -103,6 +103,12 @@
// http://www.gwtproject.org/doc/latest/DevGuideCodingBasicsJSNI.html
window.$wnd = window;
+ function flushPreinstalls() {
+ if (window.Gerrit.flushPreinstalls) {
+ window.Gerrit.flushPreinstalls();
+ }
+ }
+
function installPreloadedPlugins() {
if (!Gerrit._preloadedPlugins) { return; }
for (const name in Gerrit._preloadedPlugins) {
@@ -161,6 +167,13 @@
this._url = new URL(opt_url);
this._name = getPluginNameFromUrl(this._url);
+ if (this._url.protocol === PRELOADED_PROTOCOL) {
+ // Original plugin URL is used in plugin assets URLs calculation.
+ const assetsBaseUrl = window.ASSETS_PATH ||
+ (window.location.origin + Gerrit.BaseUrlBehavior.getBaseUrl());
+ this._url = new URL(assetsBaseUrl + '/plugins/' + this._name +
+ '/static/' + this._name + '.js');
+ }
}
Plugin._sharedAPIElement = document.createElement('gr-js-api-interface');
@@ -435,6 +448,8 @@
},
};
+ flushPreinstalls();
+
const Gerrit = window.Gerrit || {};
let _resolveAllPluginsLoaded = null;
@@ -447,6 +462,7 @@
if (!app) {
// No gr-app found (running tests)
Gerrit._installPreloadedPlugins = installPreloadedPlugins;
+ Gerrit._flushPreinstalls = flushPreinstalls;
Gerrit._resetPlugins = () => {
_allPluginsPromise = null;
_pluginsInstalled = [];
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.html b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.html
index 2aead04..ca5c49f 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.html
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.html
@@ -29,12 +29,6 @@
<template strip-whitespace>
<style include="gr-voting-styles"></style>
<style include="shared-styles">
- .title {
- font-weight: bold;
- max-width: 20em;
- padding-right: .5em;
- word-break: break-word;
- }
.placeholder {
color: var(--deemphasized-text-color);
padding-top: .2em;
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html
index ec589fe..c35768f 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html
@@ -16,6 +16,7 @@
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../../styles/shared-styles.html">
<script src="../../../bower_components/ba-linkify/ba-linkify.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
index 22e14e9..530da02 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
@@ -60,15 +60,16 @@
* commentLink patterns
*/
_contentOrConfigChanged(content, config) {
- var output = Polymer.dom(this.$.output);
+ config = Gerrit.Nav.mapCommentlinks(config);
+ const output = Polymer.dom(this.$.output);
output.textContent = '';
- var parser = new GrLinkTextParser(config,
+ const parser = new GrLinkTextParser(config,
this._handleParseResult.bind(this), this.removeZeroWidthSpace);
parser.parse(content);
// Ensure that links originating from HTML commentlink configs open in a
// new tab. @see Issue 5567
- output.querySelectorAll('a').forEach(function(anchor) {
+ output.querySelectorAll('a').forEach(anchor => {
anchor.setAttribute('target', '_blank');
anchor.setAttribute('rel', 'noopener');
});
@@ -87,9 +88,9 @@
* @param {DocumentFragment|undefined} fragment
*/
_handleParseResult(text, href, fragment) {
- var output = Polymer.dom(this.$.output);
+ const output = Polymer.dom(this.$.output);
if (href) {
- var a = document.createElement('a');
+ const a = document.createElement('a');
a.href = href;
a.textContent = text;
a.target = '_blank';
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
index baa025e..23c1442 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
@@ -37,29 +37,30 @@
</test-fixture>
<script>
- suite('gr-linked-text tests', function() {
- var element;
- var sandbox;
+ suite('gr-linked-text tests', () => {
+ let element;
+ let sandbox;
- setup(function() {
+ setup(() => {
element = fixture('basic');
sandbox = sinon.sandbox.create();
+ sandbox.stub(Gerrit.Nav, 'mapCommentlinks', x => x);
element.config = {
ph: {
match: '([Bb]ug|[Ii]ssue)\\s*#?(\\d+)',
- link: 'https://code.google.com/p/gerrit/issues/detail?id=$2'
+ link: 'https://code.google.com/p/gerrit/issues/detail?id=$2',
},
changeid: {
match: '(I[0-9a-f]{8,40})',
- link: '#/q/$1'
+ link: '#/q/$1',
},
changeid2: {
match: 'Change-Id: +(I[0-9a-f]{8,40})',
- link: '#/q/$1'
+ link: '#/q/$1',
},
googlesearch: {
match: 'google:(.+)',
- link: 'https://bing.com/search?q=$1', // html should supercede link.
+ link: 'https://bing.com/search?q=$1', // html should supercede link.
html: '<a href="https://google.com/search?q=$1">$1</a>',
},
hashedhtml: {
@@ -74,27 +75,27 @@
};
});
- teardown(function() {
+ teardown(() => {
sandbox.restore();
});
- test('URL pattern was parsed and linked.', function() {
- // Reguar inline link.
- var url = 'https://code.google.com/p/gerrit/issues/detail?id=3650';
+ test('URL pattern was parsed and linked.', () => {
+ // Regular inline link.
+ const url = 'https://code.google.com/p/gerrit/issues/detail?id=3650';
element.content = url;
- var linkEl = element.$.output.childNodes[0];
+ const linkEl = element.$.output.childNodes[0];
assert.equal(linkEl.target, '_blank');
assert.equal(linkEl.rel, 'noopener');
assert.equal(linkEl.href, url);
assert.equal(linkEl.textContent, url);
});
- test('Bug pattern was parsed and linked', function() {
+ test('Bug pattern was parsed and linked', () => {
// "Issue/Bug" pattern.
element.content = 'Issue 3650';
- var linkEl = element.$.output.childNodes[0];
- var url = 'https://code.google.com/p/gerrit/issues/detail?id=3650';
+ let linkEl = element.$.output.childNodes[0];
+ const url = 'https://code.google.com/p/gerrit/issues/detail?id=3650';
assert.equal(linkEl.target, '_blank');
assert.equal(linkEl.href, url);
assert.equal(linkEl.textContent, 'Issue 3650');
@@ -107,26 +108,26 @@
assert.equal(linkEl.textContent, 'Bug 3650');
});
- test('Change-Id pattern was parsed and linked', function() {
+ test('Change-Id pattern was parsed and linked', () => {
// "Change-Id:" pattern.
- var changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
- var prefix = 'Change-Id: ';
+ const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
+ const prefix = 'Change-Id: ';
element.content = prefix + changeID;
- var textNode = element.$.output.childNodes[0];
- var linkEl = element.$.output.childNodes[1];
+ const textNode = element.$.output.childNodes[0];
+ const linkEl = element.$.output.childNodes[1];
assert.equal(textNode.textContent, prefix);
- var url = '/q/' + changeID;
+ const url = '/q/' + changeID;
assert.equal(linkEl.target, '_blank');
// Since url is a path, the host is added automatically.
assert.isTrue(linkEl.href.endsWith(url));
assert.equal(linkEl.textContent, changeID);
});
- test('Multiple matches', function() {
+ test('Multiple matches', () => {
element.content = 'Issue 3650\nIssue 3450';
- var linkEl1 = element.$.output.childNodes[0];
- var linkEl2 = element.$.output.childNodes[2];
+ const linkEl1 = element.$.output.childNodes[0];
+ const linkEl2 = element.$.output.childNodes[2];
assert.equal(linkEl1.target, '_blank');
assert.equal(linkEl1.href,
@@ -139,22 +140,22 @@
assert.equal(linkEl2.textContent, 'Issue 3450');
});
- test('Change-Id pattern parsed before bug pattern', function() {
+ test('Change-Id pattern parsed before bug pattern', () => {
// "Change-Id:" pattern.
- var changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
- var prefix = 'Change-Id: ';
+ const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
+ const prefix = 'Change-Id: ';
// "Issue/Bug" pattern.
- var bug = 'Issue 3650';
+ const bug = 'Issue 3650';
- var changeUrl = '/q/' + changeID;
- var bugUrl = 'https://code.google.com/p/gerrit/issues/detail?id=3650';
+ const changeUrl = '/q/' + changeID;
+ const bugUrl = 'https://code.google.com/p/gerrit/issues/detail?id=3650';
element.content = prefix + changeID + bug;
- var textNode = element.$.output.childNodes[0];
- var changeLinkEl = element.$.output.childNodes[1];
- var bugLinkEl = element.$.output.childNodes[2];
+ const textNode = element.$.output.childNodes[0];
+ const changeLinkEl = element.$.output.childNodes[1];
+ const bugLinkEl = element.$.output.childNodes[2];
assert.equal(textNode.textContent, prefix);
@@ -167,41 +168,41 @@
assert.equal(bugLinkEl.textContent, 'Issue 3650');
});
- test('html field in link config', function() {
+ test('html field in link config', () => {
element.content = 'google:do a barrel roll';
- var linkEl = element.$.output.childNodes[0];
+ const linkEl = element.$.output.childNodes[0];
assert.equal(linkEl.getAttribute('href'),
'https://google.com/search?q=do a barrel roll');
assert.equal(linkEl.textContent, 'do a barrel roll');
});
- test('removing hash from links', function() {
+ test('removing hash from links', () => {
element.content = 'hash:foo';
- var linkEl = element.$.output.childNodes[0];
+ const linkEl = element.$.output.childNodes[0];
assert.isTrue(linkEl.href.endsWith('/awesomesauce'));
assert.equal(linkEl.textContent, 'foo');
});
- test('disabled config', function() {
+ test('disabled config', () => {
element.content = 'foo:baz';
assert.equal(element.$.output.innerHTML, 'foo:baz');
});
- test('R=email labels link correctly', function() {
+ test('R=email labels link correctly', () => {
element.removeZeroWidthSpace = true;
element.content = 'R=\u200Btest@google.com';
assert.equal(element.$.output.textContent, 'R=test@google.com');
assert.equal(element.$.output.innerHTML.match(/(R=<a)/g).length, 1);
});
- test('CC=email labels link correctly', function() {
+ test('CC=email labels link correctly', () => {
element.removeZeroWidthSpace = true;
element.content = 'CC=\u200Btest@google.com';
assert.equal(element.$.output.textContent, 'CC=test@google.com');
assert.equal(element.$.output.innerHTML.match(/(CC=<a)/g).length, 1);
});
- test('only {http,https,mailto} protocols are linkified', function() {
+ test('only {http,https,mailto} protocols are linkified', () => {
element.content = 'xx mailto:test@google.com yy';
let links = element.$.output.querySelectorAll('a');
assert.equal(links.length, 1);
@@ -226,7 +227,7 @@
assert.equal(links.length, 0);
});
- test('overlapping links', function() {
+ test('overlapping links', () => {
element.config = {
b1: {
match: '(B:\\s*)(\\d+)',
@@ -238,7 +239,7 @@
},
};
element.content = '- B: 123, 45';
- var links = Polymer.dom(element.root).querySelectorAll('a');
+ const links = Polymer.dom(element.root).querySelectorAll('a');
assert.equal(links.length, 2);
assert.equal(element.$$('span').textContent, '- B: 123, 45');
@@ -250,31 +251,31 @@
assert.equal(links[1].textContent, '45');
});
- test('_contentOrConfigChanged called with config', function() {
- var contentStub = sandbox.stub(element, '_contentChanged');
- var contentConfigStub = sandbox.stub(element, '_contentOrConfigChanged');
+ test('_contentOrConfigChanged called with config', () => {
+ const contentStub = sandbox.stub(element, '_contentChanged');
+ const contentConfigStub = sandbox.stub(element, '_contentOrConfigChanged');
element.content = 'some text';
assert.isTrue(contentStub.called);
assert.isTrue(contentConfigStub.called);
});
});
- suite('gr-linked-text with null config', function() {
- var element;
- var sandbox;
+ suite('gr-linked-text with null config', () => {
+ let element;
+ let sandbox;
- setup(function() {
+ setup(() => {
element = fixture('basic');
sandbox = sinon.sandbox.create();
});
- teardown(function() {
+ teardown(() => {
sandbox.restore();
});
- test('_contentOrConfigChanged not called without config', function() {
- var contentStub = sandbox.stub(element, '_contentChanged');
- var contentConfigStub = sandbox.stub(element, '_contentOrConfigChanged');
+ test('_contentOrConfigChanged not called without config', () => {
+ const contentStub = sandbox.stub(element, '_contentChanged');
+ const contentConfigStub = sandbox.stub(element, '_contentOrConfigChanged');
element.content = 'some text';
assert.isTrue(contentStub.called);
assert.isFalse(contentConfigStub.called);
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
index 8b49ca0..8526c3e 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
@@ -41,8 +41,8 @@
* @param {Object|null|undefined} linkConfig Comment links as specified by the
* commentlinks field on a project config.
* @param {Function} callback The callback to be fired when an intermediate
- * parse result is emitted. The callback is passed text and href strings if
- * a link is to be created, or a document fragment otherwise.
+ * parse result is emitted. The callback is passed text and href strings
+ * if a link is to be created, or a document fragment otherwise.
* @param {boolean|undefined} opt_removeZeroWidthSpace If true, zero-width
* spaces will be removed from R=<email> and CC=<email> expressions.
*/
@@ -73,14 +73,14 @@
*/
GrLinkTextParser.prototype.processLinks = function(text, outputArray) {
this.sortArrayReverse(outputArray);
- var fragment = document.createDocumentFragment();
- var cursor = text.length;
+ const fragment = document.createDocumentFragment();
+ let cursor = text.length;
// Start inserting linkified URLs from the end of the String. That way, the
// string positions of the items don't change as we iterate through.
- outputArray.forEach(function(item) {
- // Add any text between the current linkified item and the item added before
- // if it exists.
+ outputArray.forEach(item => {
+ // Add any text between the current linkified item and the item added
+ // before if it exists.
if (item.position + item.length !== cursor) {
fragment.insertBefore(
document.createTextNode(
@@ -130,32 +130,32 @@
*/
GrLinkTextParser.prototype.addItem =
function(text, href, html, position, length, outputArray) {
- var htmlOutput = '';
+ let htmlOutput = '';
- if (href) {
- var a = document.createElement('a');
- a.href = href;
- a.textContent = text;
- a.target = '_blank';
- a.rel = 'noopener';
- htmlOutput = a;
- } else if (html) {
- var fragment = document.createDocumentFragment();
+ if (href) {
+ const a = document.createElement('a');
+ a.href = href;
+ a.textContent = text;
+ a.target = '_blank';
+ a.rel = 'noopener';
+ htmlOutput = a;
+ } else if (html) {
+ const fragment = document.createDocumentFragment();
// Create temporary div to hold the nodes in.
- var div = document.createElement('div');
- div.innerHTML = html;
- while (div.firstChild) {
- fragment.appendChild(div.firstChild);
- }
- htmlOutput = fragment;
- }
+ const div = document.createElement('div');
+ div.innerHTML = html;
+ while (div.firstChild) {
+ fragment.appendChild(div.firstChild);
+ }
+ htmlOutput = fragment;
+ }
- outputArray.push({
- html: htmlOutput,
- position: position,
- length: length,
- });
- };
+ outputArray.push({
+ html: htmlOutput,
+ position,
+ length,
+ });
+ };
/**
* Create a CommentLinkItem for a link and append it to the given output
@@ -171,9 +171,9 @@
*/
GrLinkTextParser.prototype.addLink =
function(text, href, position, length, outputArray) {
- if (!text || this.hasOverlap(position, length, outputArray)) { return; }
- this.addItem(text, href, null, position, length, outputArray);
- };
+ if (!text || this.hasOverlap(position, length, outputArray)) { return; }
+ this.addItem(text, href, null, position, length, outputArray);
+ };
/**
* Create a CommentLinkItem specified by an HTMl string and append it to the
@@ -188,9 +188,9 @@
*/
GrLinkTextParser.prototype.addHTML =
function(html, position, length, outputArray) {
- if (this.hasOverlap(position, length, outputArray)) { return; }
- this.addItem(null, null, html, position, length, outputArray);
- };
+ if (this.hasOverlap(position, length, outputArray)) { return; }
+ this.addItem(null, null, html, position, length, outputArray);
+ };
/**
* Does the given range overlap with anything already in the item list.
@@ -200,18 +200,18 @@
*/
GrLinkTextParser.prototype.hasOverlap =
function(position, length, outputArray) {
- var endPosition = position + length;
- for (var i = 0; i < outputArray.length; i++) {
- var arrayItemStart = outputArray[i].position;
- var arrayItemEnd = outputArray[i].position + outputArray[i].length;
- if ((position >= arrayItemStart && position < arrayItemEnd) ||
+ const endPosition = position + length;
+ for (let i = 0; i < outputArray.length; i++) {
+ const arrayItemStart = outputArray[i].position;
+ const arrayItemEnd = outputArray[i].position + outputArray[i].length;
+ if ((position >= arrayItemStart && position < arrayItemEnd) ||
(endPosition > arrayItemStart && endPosition <= arrayItemEnd) ||
(position === arrayItemStart && position === arrayItemEnd)) {
return true;
- }
- }
- return false;
- };
+ }
+ }
+ return false;
+ };
/**
* Parse the given source text and emit callbacks for the items that are
@@ -241,9 +241,9 @@
text = text.replace(/^(CC|R)=\u200B/gm, '$1=');
}
- // If the href is provided then ba-linkify has recognized it as a URL. If the
- // source text does not include a protocol, the protocol will be added by
- // ba-linkify. Create the link if the href is provided and its protocol
+ // If the href is provided then ba-linkify has recognized it as a URL. If
+ // the source text does not include a protocol, the protocol will be added
+ // by ba-linkify. Create the link if the href is provided and its protocol
// matches the expected pattern.
if (href && URL_PROTOCOL_PATTERN.test(href)) {
this.addText(text, href);
@@ -262,9 +262,10 @@
* object.
*/
GrLinkTextParser.prototype.parseLinks = function(text, patterns) {
- // The outputArray is used to store all of the matches found for all patterns.
- var outputArray = [];
- for (var p in patterns) {
+ // The outputArray is used to store all of the matches found for all
+ // patterns.
+ const outputArray = [];
+ for (const p in patterns) {
if (patterns[p].enabled != null && patterns[p].enabled == false) {
continue;
}
@@ -279,38 +280,37 @@
}
}
- var pattern = new RegExp(patterns[p].match, 'g');
+ const pattern = new RegExp(patterns[p].match, 'g');
- var match;
- var textToCheck = text;
- var susbtrIndex = 0;
+ let match;
+ let textToCheck = text;
+ let susbtrIndex = 0;
while ((match = pattern.exec(textToCheck)) != null) {
textToCheck = textToCheck.substr(match.index + match[0].length);
- var result = match[0].replace(pattern,
+ let result = match[0].replace(pattern,
patterns[p].html || patterns[p].link);
+ let i;
// Skip portion of replacement string that is equal to original.
- for (var i = 0; i < result.length; i++) {
- if (result[i] !== match[0][i]) {
- break;
- }
+ for (i = 0; i < result.length; i++) {
+ if (result[i] !== match[0][i]) { break; }
}
result = result.slice(i);
if (patterns[p].html) {
this.addHTML(
- result,
- susbtrIndex + match.index + i,
- match[0].length - i,
- outputArray);
+ result,
+ susbtrIndex + match.index + i,
+ match[0].length - i,
+ outputArray);
} else if (patterns[p].link) {
this.addLink(
- match[0],
- result,
- susbtrIndex + match.index + i,
- match[0].length - i,
- outputArray);
+ match[0],
+ result,
+ susbtrIndex + match.index + i,
+ match[0].length - i,
+ outputArray);
} else {
throw Error('linkconfig entry ' + p +
' doesn’t contain a link or html attribute.');
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 d014758..af10b8371 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
@@ -61,7 +61,6 @@
* endpoint: string,
* patchNum: (string|number|null|undefined),
* errFn: (function(?Response, string=)|null|undefined),
- * cancelCondition: (function()|null|undefined),
* params: (Object|null|undefined),
* fetchOptions: (Object|null|undefined),
* anonymizedEndpoint: (string|undefined),
@@ -1498,13 +1497,37 @@
* @return {!Promise<?Object>}
*/
getRepos(filter, reposPerPage, opt_offset) {
+ const defaultFilter = 'state:active OR state:read-only';
+ const namePartDelimiters = /[@.\-\s\/_]/g;
const offset = opt_offset || 0;
+ if (filter && !filter.includes(':') && filter.match(namePartDelimiters)) {
+ // The query language specifies hyphens as operators. Split the string
+ // by hyphens and 'AND' the parts together as 'inname:' queries.
+ // If the filter includes a semicolon, the user is using a more complex
+ // query so we trust them and don't do any magic under the hood.
+ const originalFilter = filter;
+ filter = '';
+ originalFilter.split(namePartDelimiters).forEach(part => {
+ if (part) {
+ filter += (filter === '' ? 'inname:' : ' AND inname:') + part;
+ }
+ });
+ }
+ // Check if filter is now empty which could be either because the user did
+ // not provide it or because the user provided only a split character.
+ if (!filter) {
+ filter = defaultFilter;
+ }
+
+ filter = filter.trim();
+ const encodedFilter = encodeURIComponent(filter);
+
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
return this._fetchSharedCacheURL({
- url: `/projects/?d&n=${reposPerPage + 1}&S=${offset}` +
- this._computeFilter(filter),
+ url: `/projects/?n=${reposPerPage + 1}&S=${offset}` +
+ `&query=${encodedFilter}`,
anonymizedUrl: '/projects/?*',
});
},
@@ -2112,10 +2135,8 @@
* @param {number|string} patchNum
* @param {string} path
* @param {function(?Response, string=)=} opt_errFn
- * @param {function()=} opt_cancelCondition
*/
- getDiff(changeNum, basePatchNum, patchNum, path,
- opt_errFn, opt_cancelCondition) {
+ getDiff(changeNum, basePatchNum, patchNum, path, opt_errFn) {
const params = {
context: 'ALL',
intraline: null,
@@ -2133,7 +2154,6 @@
endpoint,
patchNum,
errFn: opt_errFn,
- cancelCondition: opt_cancelCondition,
params,
anonymizedEndpoint: '/files/*/diff',
});
@@ -2796,7 +2816,6 @@
return this._fetchJSON({
url: url + req.endpoint,
errFn: req.errFn,
- cancelCondition: req.cancelCondition,
params: req.params,
fetchOptions: req.fetchOptions,
anonymizedUrl: anonymizedEndpoint ?
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index 193d306..d9656e4 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -930,33 +930,68 @@
});
});
- test('getRepos', () => {
- sandbox.stub(element, '_fetchSharedCacheURL');
- element.getRepos('test', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
- '/projects/?d&n=26&S=0&m=test');
+ suite('getRepos', () => {
+ const defaultQuery = 'state%3Aactive%20OR%20state%3Aread-only';
- element.getRepos(null, 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
- '/projects/?d&n=26&S=0');
+ setup(() => {
+ sandbox.stub(element, '_fetchSharedCacheURL');
+ });
- element.getRepos('test', 25, 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
- '/projects/?d&n=26&S=25&m=test');
- });
+ test('normal use', () => {
+ element.getRepos('test', 25);
+ assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ '/projects/?n=26&S=0&query=test');
- test('getRepos filter', () => {
- sandbox.stub(element, '_fetchSharedCacheURL');
- element.getRepos('test/test/test', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
- '/projects/?d&n=26&S=0&m=test%2Ftest%2Ftest');
- });
+ element.getRepos(null, 25);
+ assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ `/projects/?n=26&S=0&query=${defaultQuery}`);
- test('getRepos filter regex', () => {
- sandbox.stub(element, '_fetchSharedCacheURL');
- element.getRepos('^test.*', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
- '/projects/?d&n=26&S=0&r=%5Etest.*');
+ element.getRepos('test', 25, 25);
+ assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ '/projects/?n=26&S=25&query=test');
+ });
+
+ test('with blank', () => {
+ element.getRepos('test/test', 25);
+ assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ '/projects/?n=26&S=0&query=inname%3Atest%20AND%20inname%3Atest');
+ });
+
+ test('with hyphen', () => {
+ element.getRepos('foo-bar', 25);
+ assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ '/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
+ });
+
+ test('with leading hyphen', () => {
+ element.getRepos('-bar', 25);
+ assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ '/projects/?n=26&S=0&query=inname%3Abar');
+ });
+
+ test('with trailing hyphen', () => {
+ element.getRepos('foo-bar-', 25);
+ assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ '/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
+ });
+
+ test('with underscore', () => {
+ element.getRepos('foo_bar', 25);
+ assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ '/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
+ });
+
+ test('with underscore', () => {
+ element.getRepos('foo_bar', 25);
+ assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ '/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
+ });
+
+ test('hyphen only', () => {
+ element.getRepos('-', 25);
+ assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ `/projects/?n=26&S=0&query=${defaultQuery}`);
+ });
});
test('getGroups filter regex', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.html b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.html
new file mode 100644
index 0000000..fe6ed88
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.html
@@ -0,0 +1,58 @@
+<!--
+@license
+Copyright (C) 2018 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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-copy-clipboard/gr-copy-clipboard.html">
+
+<dom-module id="gr-shell-command">
+ <template>
+ <style include="shared-styles">
+ .commandContainer {
+ margin-bottom: .75em;
+ }
+ .commandContainer {
+ background-color: var(--shell-command-background-color);
+ padding: .5em .5em .5em 2.5em;
+ position: relative;
+ width: 100%;
+ }
+ .commandContainer:before {
+ background: var(--shell-command-decoration-background-color);
+ bottom: 0;
+ box-sizing: border-box;
+ content: '$';
+ display: block;
+ left: 0;
+ padding: .8em;
+ position: absolute;
+ top: 0;
+ width: 2em;
+ }
+ .commandContainer gr-copy-clipboard {
+ --text-container-style: {
+ border: none;
+ }
+ }
+ </style>
+ <label>[[label]]</label>
+ <div class="commandContainer">
+ <gr-copy-clipboard text="[[command]]"></gr-copy-clipboard>
+ </div>
+ </template>
+ <script src="gr-shell-command.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.js b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.js
new file mode 100644
index 0000000..2c546cc
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.js
@@ -0,0 +1,32 @@
+/**
+ * @license
+ * Copyright (C) 2018 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.
+ */
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-shell-command',
+
+ properties: {
+ command: String,
+ label: String,
+ },
+
+ focusOnCopy() {
+ this.$$('gr-copy-clipboard').focusOnCopy();
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html
new file mode 100644
index 0000000..a49f76f
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2018 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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-shell-command</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-shell-command.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-shell-command></gr-shell-command>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-shell-command tests', () => {
+ let element;
+ let sandbox;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ element = fixture('basic');
+ element.text = `git fetch http://gerrit@localhost:8080/a/test-project
+ refs/changes/05/5/1 && git checkout FETCH_HEAD`;
+ flushAsynchronousOperations();
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ test('focusOnCopy', () => {
+ const focusStub = sandbox.stub(element.$$('gr-copy-clipboard'),
+ 'focusOnCopy');
+ element.focusOnCopy();
+ assert.isTrue(focusStub.called);
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/styles/dashboard-header-styles.html b/polygerrit-ui/app/styles/dashboard-header-styles.html
index a88f68c..b82bf3a 100644
--- a/polygerrit-ui/app/styles/dashboard-header-styles.html
+++ b/polygerrit-ui/app/styles/dashboard-header-styles.html
@@ -39,7 +39,7 @@
}
.info > div > span {
display: inline-block;
- font-weight: bold;
+ font-family: var(--font-family-bold);
text-align: right;
width: 4em;
}
diff --git a/polygerrit-ui/app/styles/themes/app-theme.html b/polygerrit-ui/app/styles/themes/app-theme.html
index 81c195a..ea81796 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.html
+++ b/polygerrit-ui/app/styles/themes/app-theme.html
@@ -96,6 +96,9 @@
--diff-highlight-range-color: rgba(255, 213, 0, 0.5);
--diff-highlight-range-hover-color: rgba(255, 255, 0, 0.5);
+ --shell-command-background-color: #f5f5f5;
+ --shell-command-decoration-background-color: #ebebeb;
+
--comment-text-color: #000;
--comment-background-color: #fcfad6;
--unresolved-comment-background-color: #fcfaa6;
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.html b/polygerrit-ui/app/styles/themes/dark-theme.html
index 1f473da..8ade9ba 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.html
+++ b/polygerrit-ui/app/styles/themes/dark-theme.html
@@ -29,6 +29,7 @@
--diff-selection-background-color: #3A71D8;
--light-remove-highlight-color: rgb(53, 27, 27);
--light-add-highlight-color: rgb(24, 45, 24);
+ --light-remove-add-highlight-color: #2f3f2f;
--light-rebased-remove-highlight-color: rgb(60, 37, 8);
--light-rebased-add-highlight-color: rgb(72, 113, 101);
--dark-remove-highlight-color: rgba(255, 0, 0, 0.15);
@@ -39,6 +40,8 @@
--diff-context-control-border-color: var(--border-color);
--diff-highlight-range-color: rgba(0, 100, 200, 0.5);
--diff-highlight-range-hover-color: rgba(0, 150, 255, 0.5);
+ --shell-command-background-color: #5f5f5f;
+ --shell-command-decoration-background-color: #999999;
--comment-text-color: var(--primary-text-color);
--comment-background-color: #0B162B;
--unresolved-comment-background-color: rgb(56, 90, 154);
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index d0bb4c1..d463d61 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -86,7 +86,9 @@
'change/gr-reply-dialog/gr-reply-dialog_test.html',
'change/gr-reviewer-list/gr-reviewer-list_test.html',
'change/gr-thread-list/gr-thread-list_test.html',
+ 'change/gr-upload-help-dialog/gr-upload-help-dialog_test.html',
'core/gr-account-dropdown/gr-account-dropdown_test.html',
+ 'core/gr-error-dialog/gr-error-dialog_test.html',
'core/gr-error-manager/gr-error-manager_test.html',
'core/gr-main-header/gr-main-header_test.html',
'core/gr-navigation/gr-navigation_test.html',
diff --git a/proto/cache.proto b/proto/cache.proto
index 33f9143..c2ac0d9 100644
--- a/proto/cache.proto
+++ b/proto/cache.proto
@@ -219,3 +219,18 @@
}
TagSetProto tags = 2;
}
+
+// Serialized form of
+// com.google.gerrit.server.account.externalids.AllExternalIds.
+// Next ID: 2
+message AllExternalIdsProto {
+ // Next ID: 6
+ message ExternalIdProto {
+ string key = 1;
+ int32 accountId = 2;
+ string email = 3;
+ string password = 4;
+ bytes blobId = 5;
+ }
+ repeated ExternalIdProto external_id = 1;
+}
diff --git a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
index 052de6b..f401735 100644
--- a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
+++ b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
@@ -19,7 +19,8 @@
/**
* @param canonicalPath
* @param staticResourcePath
- * @param? assetsUrl
+ * @param? assetsPath {string} URL to static assets root, if served from CDN.
+ * @param? assetsBundle {string} Assets bundle .html file, served from $assetsPath.
* @param? faviconPath
* @param? versionInfo
* @param? deprecateGwtUi
@@ -37,6 +38,7 @@
{if $deprecateGwtUi}window.DEPRECATE_GWT_UI = true;{/if}
{if $versionInfo}window.VERSION_INFO = '{$versionInfo}';{/if}
{if $staticResourcePath != ''}window.STATIC_RESOURCE_PATH = '{$staticResourcePath}';{/if}
+ {if $assetsPath}window.ASSETS_PATH = '{$assetsPath}';{/if}
</script>{\n}
{if $faviconPath}
@@ -62,8 +64,8 @@
// CC them on any changes that load content before gr-app.html.
//
// github.com/Polymer/polymer-resin/blob/master/getting-started.md#integrating
- {if $assetsUrl}
- <link rel="import" href="{$assetsUrl}">{\n}
+ {if $assetsPath and $assetsBundle}
+ <link rel="import" href="{$assetsPath + $assetsBundle}">{\n}
{/if}
<link rel="preload" href="{$staticResourcePath}/elements/gr-app.js" as="script" crossorigin="anonymous">{\n}
diff --git a/resources/com/google/gerrit/pgm/init/gerrit.sh b/resources/com/google/gerrit/pgm/init/gerrit.sh
index 5571e7c..d3f3666 100755
--- a/resources/com/google/gerrit/pgm/init/gerrit.sh
+++ b/resources/com/google/gerrit/pgm/init/gerrit.sh
@@ -443,6 +443,11 @@
echo -16 > "/proc/${PID}/oom_adj"
fi
fi
+ elif [ "$(uname -s)"=="Linux" ] && test -d "/proc/${PID}"; then
+ echo "WARNING: Could not adjust Gerrit's process for the kernel's out-of-memory killer."
+ echo " This may be caused by ${0} not being run as root."
+ echo " Consider changing the OOM score adjustment manually for Gerrit's PID=${PID} with e.g.:"
+ echo " echo '-1000' | sudo tee /proc/${PID}/oom_score_adj"
fi
TIMEOUT="$GERRIT_STARTUP_TIMEOUT"
diff --git a/resources/com/google/gerrit/server/documentation/pegdown.css b/resources/com/google/gerrit/server/documentation/flexmark-java.css
similarity index 100%
rename from resources/com/google/gerrit/server/documentation/pegdown.css
rename to resources/com/google/gerrit/server/documentation/flexmark-java.css
diff --git a/tools/BUILD b/tools/BUILD
index 060cbd8..73ecfb9 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -1,6 +1,128 @@
+load("@bazel_tools//tools/jdk:default_java_toolchain.bzl", "default_java_toolchain")
+
py_binary(
name = "merge_jars",
srcs = ["merge_jars.py"],
main = "merge_jars.py",
visibility = ["//visibility:public"],
)
+
+# TODO(davido): remove this when minimum suported Bazel version >= 0.17
+# Copied from tools/jdk/default_java_toolchain.bzl to make Bazel 0.16
+# and later Bazel released to work as expected. See this issue for context:
+# https://github.com/bazelbuild/bazel/issues/6009
+JDK9_JVM_OPTS = [
+ # Allow JavaBuilder to access internal javac APIs.
+ "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+ "--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
+
+ # override the javac in the JDK.
+ "--patch-module=java.compiler=$(location @bazel_tools//third_party/java/jdk/langtools:java_compiler_jar)",
+ "--patch-module=jdk.compiler=$(location @bazel_tools//third_party/java/jdk/langtools:jdk_compiler_jar)",
+
+ # quiet warnings from com.google.protobuf.UnsafeUtil,
+ # see: https://github.com/google/protobuf/issues/3781
+ "--add-opens=java.base/java.nio=ALL-UNNAMED",
+]
+
+# See https://github.com/bazelbuild/bazel/issues/3427 for more context
+default_java_toolchain(
+ name = "error_prone_warnings_toolchain_bazel_0.16",
+ bootclasspath = ["@bazel_tools//tools/jdk:platformclasspath.jar"],
+ jvm_opts = JDK9_JVM_OPTS,
+ package_configuration = [
+ ":error_prone",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+default_java_toolchain(
+ name = "error_prone_warnings_toolchain",
+ bootclasspath = ["@bazel_tools//tools/jdk:platformclasspath.jar"],
+ package_configuration = [
+ ":error_prone",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+# This EP warnings list is based on:
+# https://github.com/bazelbuild/BUILD_file_generator/blob/master/tools/bazel_defs/java.bzl
+java_package_configuration(
+ name = "error_prone",
+ javacopts = [
+ "-XepDisableWarningsInGeneratedCode",
+ "-Xep:MissingCasesInEnumSwitch:ERROR",
+ "-Xep:ReferenceEquality:WARN",
+ "-Xep:StringEquality:WARN",
+ "-Xep:WildcardImport:WARN",
+ "-Xep:AmbiguousMethodReference:WARN",
+ "-Xep:BadAnnotationImplementation:WARN",
+ "-Xep:BadComparable:WARN",
+ "-Xep:BoxedPrimitiveConstructor:ERROR",
+ "-Xep:CannotMockFinalClass:WARN",
+ "-Xep:ClassCanBeStatic:WARN",
+ "-Xep:ClassNewInstance:WARN",
+ "-Xep:DefaultCharset:ERROR",
+ "-Xep:DoubleCheckedLocking:WARN",
+ "-Xep:ElementsCountedInLoop:WARN",
+ "-Xep:EqualsHashCode:WARN",
+ "-Xep:EqualsIncompatibleType:WARN",
+ "-Xep:ExpectedExceptionChecker:ERROR",
+ "-Xep:Finally:WARN",
+ "-Xep:FloatingPointLiteralPrecision:WARN",
+ "-Xep:FragmentInjection:WARN",
+ "-Xep:FragmentNotInstantiable:WARN",
+ "-Xep:FunctionalInterfaceClash:WARN",
+ "-Xep:FutureReturnValueIgnored:WARN",
+ "-Xep:GetClassOnEnum:WARN",
+ "-Xep:ImmutableAnnotationChecker:WARN",
+ "-Xep:ImmutableEnumChecker:WARN",
+ "-Xep:IncompatibleModifiers:WARN",
+ "-Xep:InjectOnConstructorOfAbstractClass:WARN",
+ "-Xep:InputStreamSlowMultibyteRead:WARN",
+ "-Xep:IterableAndIterator:WARN",
+ "-Xep:JUnit3FloatingPointComparisonWithoutDelta:WARN",
+ "-Xep:JUnitAmbiguousTestClass:WARN",
+ "-Xep:LiteralClassName:WARN",
+ "-Xep:MissingFail:WARN",
+ "-Xep:MissingOverride:WARN",
+ "-Xep:MutableConstantField:WARN",
+ "-Xep:NarrowingCompoundAssignment:WARN",
+ "-Xep:NonAtomicVolatileUpdate:WARN",
+ "-Xep:NonOverridingEquals:WARN",
+ "-Xep:NullableConstructor:WARN",
+ "-Xep:NullablePrimitive:WARN",
+ "-Xep:NullableVoid:WARN",
+ "-Xep:OperatorPrecedence:WARN",
+ "-Xep:OverridesGuiceInjectableMethod:WARN",
+ "-Xep:PreconditionsInvalidPlaceholder:WARN",
+ "-Xep:ProtoFieldPreconditionsCheckNotNull:WARN",
+ "-Xep:ProtocolBufferOrdinal:WARN",
+ "-Xep:RequiredModifiers:WARN",
+ "-Xep:ShortCircuitBoolean:WARN",
+ "-Xep:SimpleDateFormatConstant:WARN",
+ "-Xep:StaticGuardedByInstance:WARN",
+ "-Xep:SynchronizeOnNonFinalField:WARN",
+ "-Xep:TruthConstantAsserts:WARN",
+ "-Xep:TypeParameterShadowing:WARN",
+ "-Xep:TypeParameterUnusedInFormals:WARN",
+ "-Xep:URLEqualsHashCode:WARN",
+ "-Xep:UnsynchronizedOverridesSynchronized:WARN",
+ "-Xep:WaitNotInLoop:WARN",
+ ],
+ packages = ["error_prone_packages"],
+)
+
+package_group(
+ name = "error_prone_packages",
+ packages = [
+ "//java/...",
+ "//javatests/...",
+ ],
+)
diff --git a/tools/bzl/gwt.bzl b/tools/bzl/gwt.bzl
index 2adb7dd..b185214 100644
--- a/tools/bzl/gwt.bzl
+++ b/tools/bzl/gwt.bzl
@@ -15,7 +15,7 @@
load("//tools/bzl:genrule2.bzl", "genrule2")
load("//tools/bzl:java.bzl", "java_library2")
-jar_filetype = FileType([".jar"])
+jar_filetype = [".jar"]
BROWSERS = [
"chrome",
@@ -225,7 +225,7 @@
default = Label("@bazel_tools//tools/zip:zipper"),
cfg = "host",
executable = True,
- single_file = True,
+ allow_single_file = True,
),
},
outputs = {
diff --git a/tools/bzl/javadoc.bzl b/tools/bzl/javadoc.bzl
index 34ae9d2..8f2316c 100644
--- a/tools/bzl/javadoc.bzl
+++ b/tools/bzl/javadoc.bzl
@@ -36,7 +36,7 @@
"rm -rf %s" % dir,
"mkdir %s" % dir,
" ".join([
- ctx.file._javadoc.path,
+ "%s/bin/javadoc" % ctx.attr._jdk[java_common.JavaRuntimeInfo].java_home,
"-Xdoclint:-missing",
"-protected",
"-encoding UTF-8",
@@ -67,14 +67,10 @@
"pkgs": attr.string_list(),
"title": attr.string(),
"external_docs": attr.string_list(),
- "_javadoc": attr.label(
- default = Label("@local_jdk//:bin/javadoc"),
- single_file = True,
- allow_files = True,
- ),
"_jdk": attr.label(
- default = Label("@local_jdk//:jdk-default"),
+ default = Label("@bazel_tools//tools/jdk:current_java_runtime"),
allow_files = True,
+ providers = [java_common.JavaRuntimeInfo],
),
},
outputs = {"zip": "%{name}.zip"},
diff --git a/tools/bzl/js.bzl b/tools/bzl/js.bzl
index d6d0c95..0997bcb 100644
--- a/tools/bzl/js.bzl
+++ b/tools/bzl/js.bzl
@@ -426,7 +426,7 @@
def bundle_assets(*args, **kwargs):
"""Combine html, js, css files and optionally split into js and html bundles."""
- _bundle_rule(*args, pkg = PACKAGE_NAME, **kwargs)
+ _bundle_rule(*args, pkg = native.package_name(), **kwargs)
def polygerrit_plugin(name, app, srcs = [], assets = None, **kwargs):
"""Bundles plugin dependencies for deployment.
@@ -447,7 +447,7 @@
name = name + "_combined",
app = app,
srcs = srcs if app in srcs else srcs + [app],
- pkg = PACKAGE_NAME,
+ pkg = native.package_name(),
**kwargs
)
diff --git a/tools/bzl/license-map.py b/tools/bzl/license-map.py
index 476ccb9..ebe57f2 100644
--- a/tools/bzl/license-map.py
+++ b/tools/bzl/license-map.py
@@ -35,7 +35,7 @@
continue
handled_rules.append(rule_name)
- for c in child.getchildren():
+ for c in list(child):
if c.tag != "rule-input":
continue
diff --git a/tools/bzl/license.bzl b/tools/bzl/license.bzl
index f011446..d059216 100644
--- a/tools/bzl/license.bzl
+++ b/tools/bzl/license.bzl
@@ -39,7 +39,7 @@
if target[0] not in ":/":
target = ":" + target
if target[0] != "/":
- target = "//" + PACKAGE_NAME + target
+ target = "//" + native.package_name() + target
forbidden = "//lib:LICENSE-DO_NOT_DISTRIBUTE"
native.genquery(
diff --git a/tools/bzl/pkg_war.bzl b/tools/bzl/pkg_war.bzl
index 1a376e9..40dd769 100644
--- a/tools/bzl/pkg_war.bzl
+++ b/tools/bzl/pkg_war.bzl
@@ -14,7 +14,7 @@
# War packaging.
-jar_filetype = FileType([".jar"])
+jar_filetype = [".jar"]
LIBS = [
"//java/com/google/gerrit/common:version",
diff --git a/tools/eclipse/BUILD b/tools/eclipse/BUILD
index d022c40..0c9d023 100644
--- a/tools/eclipse/BUILD
+++ b/tools/eclipse/BUILD
@@ -11,7 +11,6 @@
"//gerrit-gwtui:ui_tests",
"//javatests/com/google/gerrit/elasticsearch:elasticsearch_test_utils",
"//javatests/com/google/gerrit/server:server_tests",
- "//proto:reviewdb_java_proto",
]
DEPS = [
@@ -33,8 +32,7 @@
"//lib/gwt:w3c-css-sac",
"//lib/jetty:servlets",
"//lib/prolog:compiler-lib",
- # TODO(davido): I do not understand why it must be on the Eclipse classpath
- #'//Documentation:index',
+ "//proto:reviewdb_java_proto",
]
java_library(
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index b99c04e..64d837a 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -52,10 +52,12 @@
action='store', default='gerrit', dest='project_name')
opts.add_option('-b', '--batch', action='store_true',
dest='batch', help='Bazel batch option')
+opts.add_option('-j', '--java', action='store',
+ dest='java', help='Post Java 8 support (9|10|11|...)')
args, _ = opts.parse_args()
batch_option = '--batch' if args.batch else None
-
+custom_java = args.java
def _build_bazel_cmd(*args):
cmd = ['bazel']
@@ -63,6 +65,9 @@
cmd.append('--batch')
for arg in args:
cmd.append(arg)
+ if custom_java:
+ cmd.append('--host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java%s' % custom_java)
+ cmd.append('--java_toolchain=@bazel_tools//tools/jdk:toolchain_java%s' % custom_java)
return cmd
@@ -70,9 +75,10 @@
return check_output(_build_bazel_cmd('info', 'output_base')).strip()
-def gen_bazel_path():
+def gen_bazel_path(ext_location):
bazel = check_output(['which', 'bazel']).strip().decode('UTF-8')
with open(path.join(ROOT, ".bazel_path"), 'w') as fd:
+ fd.write("output_base=%s\n" % ext_location)
fd.write("bazel=%s\n" % bazel)
fd.write("PATH=%s\n" % environ["PATH"])
@@ -301,7 +307,7 @@
gen_project(args.project_name)
gen_classpath(ext_location)
gen_factorypath(ext_location)
- gen_bazel_path()
+ gen_bazel_path(ext_location)
# TODO(davido): Remove this when GWT gone
gwt_working_dir = ".gwt_work_dir"