Merge "Be more explicit about using Bazelisk in dev docs"
diff --git a/.bazelrc b/.bazelrc
index cf5403d..94df44e 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -20,12 +20,14 @@
build:remote --java_runtime_version=remotejdk_11
build:remote --tool_java_language_version=11
build:remote --tool_java_runtime_version=remotejdk_11
+build:remote --config=remote_shared
# Builds and executes on RBE using remotejdk_17
build:remote17 --java_language_version=17
build:remote17 --java_runtime_version=remotejdk_17
build:remote17 --tool_java_language_version=17
build:remote17 --tool_java_runtime_version=remotejdk_17
+build:remote17 --config=remote_shared
# Enable strict_action_env flag to. For more information on this feature see
# https://groups.google.com/forum/#!topic/bazel-discuss/_VmRfMyyHBk.
diff --git a/.gitmodules b/.gitmodules
index 6217b4d..7579477 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,7 @@
+[submodule "modules/java-prettify"]
+ path = modules/java-prettify
+ url = ../java-prettify
+
[submodule "modules/jgit"]
path = modules/jgit
url = ../jgit
diff --git a/Documentation/backend_licenses.txt b/Documentation/backend_licenses.txt
index 45da2ba..9ea01cf 100755
--- a/Documentation/backend_licenses.txt
+++ b/Documentation/backend_licenses.txt
@@ -1113,28 +1113,7 @@
[[flexmark]]
flexmark
-* flexmark
-* flexmark-ext-abbreviation
-* flexmark-ext-anchorlink
-* flexmark-ext-autolink
-* flexmark-ext-definition
-* flexmark-ext-emoji
-* flexmark-ext-escaped-character
-* flexmark-ext-footnotes
-* flexmark-ext-gfm-issues
-* flexmark-ext-gfm-strikethrough
-* flexmark-ext-gfm-tasklist
-* flexmark-ext-gfm-users
-* flexmark-ext-ins
-* flexmark-ext-jekyll-front-matter
-* flexmark-ext-superscript
-* flexmark-ext-tables
-* flexmark-ext-toc
-* flexmark-ext-typographic
-* flexmark-ext-wikilink
-* flexmark-ext-yaml-front-matter
-* flexmark-profile-pegdown
-* flexmark-util
+* flexmark-all-lib
[[flexmark_license]]
----
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 6b1dea5..7f25509 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -828,8 +828,7 @@
[[cache.name.maxAge]]cache.<name>.maxAge::
+
-Maximum age to keep an entry in the cache. Entries are removed from
-the cache and refreshed from source data every maxAge interval.
+Maximum age to keep an entry in the cache.
Values should use common unit suffixes to express their setting:
+
* s, sec, second, seconds
@@ -924,20 +923,6 @@
+
If 0 or negative, disk storage for the cache is disabled.
-[[cache.name.expireAfterWrite]]cache.<name>.expireAfterWrite::
-+
-Duration after which a cached value will be evicted and not
-read anymore.
-+
-Values should use common unit suffixes to express their setting:
-+
-* ms, milliseconds
-* s, sec, second, seconds
-* m, min, minute, minutes
-* h, hr, hour, hours
-+
-Disabled by default.
-
[[cache.name.refreshAfterWrite]]cache.<name>.refreshAfterWrite::
+
Duration after which we asynchronously refresh the cached value.
@@ -1006,6 +991,13 @@
The cache should be flushed whenever NoteDb change metadata in a repository is
modified outside of Gerrit.
+cache `"changes_by_project"`::
++
+Ideally, the memorylimit of this cache is large enough to cover all projects.
+This should significantly speed up change ref advertisements and git pushes,
+especially for projects with lots of changes, and particularly on replicas
+where there is no index.
+
cache `"git_modified_files"`::
+
Each item caches the list of git modified files between two git trees
@@ -2618,6 +2610,10 @@
same `serverId` (i.e.: a Gerrit cluster).
Unlike `instanceName` this value is not available in the email templates.
+The instance ID can also be configured by setting the Java system property
+`gerrit.instanceId` on startup. This will override the configuration in the
+gerrit.config.
+
[[gerrit.instanceName]]gerrit.instanceName::
+
Short identifier for this Gerrit instance.
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index a76282e..f4238d1 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -82,9 +82,10 @@
link:dev-build-plugins.html#_bundle_custom_plugin_in_release_war[bundling in release.war]
and run `tools/eclipse/project.py`.
-If a plugin requires additional test dependencies (not available in the Gerrit), then in order to
-execute tests directly from Eclipse, that plugin must be also added to `CUSTOM_PLUGINS_TEST_DEPS`
-list in `tools/bzl/plugins.bzl` (note that `tools/eclipse/project.py` has to be run again).
+If a plugin requires additional test dependencies (not available in the Gerrit),
+then in order to execute tests directly from Eclipse, that plugin must be also
+added to `CUSTOM_PLUGINS_TEST_DEPS` list in `tools/bzl/plugins.bzl` and Eclipse
+project configuration needs to be updated by running `tools/eclipse/project.py`.
== Java Versions
diff --git a/Documentation/images/user-review-ui-side-by-side-diff-normal-edits.png b/Documentation/images/user-review-ui-side-by-side-diff-normal-edits.png
new file mode 100644
index 0000000..f89a905
--- /dev/null
+++ b/Documentation/images/user-review-ui-side-by-side-diff-normal-edits.png
Binary files differ
diff --git a/Documentation/images/user-review-ui-side-by-side-diff-rebase-edits.png b/Documentation/images/user-review-ui-side-by-side-diff-rebase-edits.png
new file mode 100644
index 0000000..7edd688
--- /dev/null
+++ b/Documentation/images/user-review-ui-side-by-side-diff-rebase-edits.png
Binary files differ
diff --git a/Documentation/images/user-review-ui-side-by-side-diff-rebase.png b/Documentation/images/user-review-ui-side-by-side-diff-rebase.png
new file mode 100644
index 0000000..3a47f8c
--- /dev/null
+++ b/Documentation/images/user-review-ui-side-by-side-diff-rebase.png
Binary files differ
diff --git a/Documentation/images/user-review-ui-side-by-side-diff-squash.png b/Documentation/images/user-review-ui-side-by-side-diff-squash.png
new file mode 100644
index 0000000..6119742
--- /dev/null
+++ b/Documentation/images/user-review-ui-side-by-side-diff-squash.png
Binary files differ
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
index 4642247..7825e050 100644
--- a/Documentation/intro-user.txt
+++ b/Documentation/intro-user.txt
@@ -282,7 +282,7 @@
a dependency between the changes in Gerrit and each change can only be
applied if all its predecessor are applied as well. Dependencies
between changes can be seen from the
-link:user-review-ui.html#related-changes-tab[Related Changes] tab on
+link:user-review-ui.html#related-changes[Related Changes] tab on
the change screen.
[[watch]]
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index 5e6a540..6c3c459 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -63,6 +63,7 @@
* guice:guice-assistedinject
* guice:guice-library
* guice:guice-servlet
+* guice:jakarta-inject
* guice:javax_inject
* httpcomponents:httpclient
* httpcomponents:httpcore
@@ -1118,28 +1119,7 @@
[[flexmark]]
flexmark
-* flexmark
-* flexmark-ext-abbreviation
-* flexmark-ext-anchorlink
-* flexmark-ext-autolink
-* flexmark-ext-definition
-* flexmark-ext-emoji
-* flexmark-ext-escaped-character
-* flexmark-ext-footnotes
-* flexmark-ext-gfm-issues
-* flexmark-ext-gfm-strikethrough
-* flexmark-ext-gfm-tasklist
-* flexmark-ext-gfm-users
-* flexmark-ext-ins
-* flexmark-ext-jekyll-front-matter
-* flexmark-ext-superscript
-* flexmark-ext-tables
-* flexmark-ext-toc
-* flexmark-ext-typographic
-* flexmark-ext-wikilink
-* flexmark-ext-yaml-front-matter
-* flexmark-profile-pegdown
-* flexmark-util
+* flexmark-all-lib
[[flexmark_license]]
----
diff --git a/Documentation/metrics.txt b/Documentation/metrics.txt
index 0d72447..a560b84 100644
--- a/Documentation/metrics.txt
+++ b/Documentation/metrics.txt
@@ -279,6 +279,9 @@
view implementation class
* `http/server/rest_api/change_json/to_change_info_latency`: Latency for
toChangeInfo invocations in ChangeJson.
+* `http/server/rest_api/change_json/to_change_info_latency/parent_data_computation`:
+ Latency for computing parent data information in toRevisionInfo invocations
+ in RevisionJson. See link:rest-api-changes.html#parent-info[ParentInfo].
* `http/server/rest_api/change_json/to_change_infos_latency`: Latency for
toChangeInfos invocations in ChangeJson.
* `http/server/rest_api/change_json/format_query_results_latency`: Latency for
diff --git a/Documentation/pg-plugin-endpoints.txt b/Documentation/pg-plugin-endpoints.txt
index dd82f27..967946d 100644
--- a/Documentation/pg-plugin-endpoints.txt
+++ b/Documentation/pg-plugin-endpoints.txt
@@ -43,6 +43,11 @@
The following endpoints are available to plugins.
+=== auth-link
+The `auth-link` extension point is located in the top right corner of anonymous
+pages. The purpose is to improve user experience for custom OAuth providers by
+providing custom components and/or visual feedback of authentication progress.
+
=== banner
The `banner` extension point is located at the top of all pages. The purpose
is to allow plugins to show outage information and important announcements to
diff --git a/Documentation/pgm-reindex.txt b/Documentation/pgm-reindex.txt
index b74829d..183c132 100644
--- a/Documentation/pgm-reindex.txt
+++ b/Documentation/pgm-reindex.txt
@@ -39,6 +39,12 @@
--show-cache-stats::
Show cache statistics at the end of program.
+--build-bloom-filter::
+ Whether to build bloom filters for H2 disk caches. When using fully
+ populated disk caches on large Gerrit sites, it is recommended that
+ bloom filters are disabled to improve performance.
+
+
== CONTEXT
The secondary index must be enabled. See
link:config-gerrit.html#index.type[index.type].
diff --git a/Documentation/repository-maintenance.txt b/Documentation/repository-maintenance.txt
index 4bf84b5..61fb6a4 100644
--- a/Documentation/repository-maintenance.txt
+++ b/Documentation/repository-maintenance.txt
@@ -106,7 +106,8 @@
existing pack files from the `objects/pack` directory into the
`preserved` directory right before calling the real Git command. This
approach will then behave similarly to `jgit gc` with respect to
-preserving pack files.
+preserving pack files. An implementation is available
+link:https://gerrit.googlesource.com/gerrit/+/refs/heads/master/contrib/git-gc-preserve[here].
GERRIT
------
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 64bdce0..9ecef3f 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -1314,6 +1314,7 @@
"publish_comments_on_push": true,
"work_in_progress_by_default": true,
"allow_browser_notifications": true,
+ "diff_page_sidebar": "plugin-foo",
"default_base_for_merges": "FIRST_PARENT",
"my": [
{
@@ -1367,6 +1368,7 @@
"disable_keyboard_shortcuts": true,
"disable_token_highlighting": true,
"allow_browser_notifications": false,
+ "diff_page_sidebar": "NONE",
"diff_view": "SIDE_BY_SIDE",
"mute_common_path_prefixes": true,
"my": [
@@ -2714,6 +2716,10 @@
inline edit feature.
|`allow_browser_notifications` |not set if `false`|
Whether to prompt user to enable browser notification in browser.
+|`diff_page_sidebar` |optional|
+String indicating which sidebar should be open on the diff page. Set to "NONE"
+if no sidebars should be open. Plugin-supplied sidebars will be prefixed with
+"plugin-".
|`my` ||
The menu items of the `MY` top menu as a list of
link:rest-api-config.html#top-menu-item-info[TopMenuItemInfo] entities.
@@ -2785,6 +2791,10 @@
inline edit feature.
|`allow_browser_notifications` |not set if `false`|
Whether to prompt user to enable browser notification in browser.
+|`diff_page_sidebar` |optional|
+String indicating which sidebar should be open on the diff page. Set to "NONE"
+if no sidebars should be open. Plugin-supplied sidebars will be prefixed with
+"plugin-".
|`my` |optional|
The menu items of the `MY` top menu as a list of
link:rest-api-config.html#top-menu-item-info[TopMenuItemInfo] entities.
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index cf2ae81..df5566f 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -161,6 +161,15 @@
are filtered out. REST requests with the skip-visibility option are rejected when the current
user doesn't have the ADMINISTRATE_SERVER capability.
+The `allow-incomplete-results` query parameter can be used. This is a boolean
+parameter that can optionally be set to true. If set, the server can tolerate
+handling faulty records when parsed from the change index, for example if a
+field contained a value of a wrong format. For faulty records, the server
+will return a canonical empty record where only the fields {project, branch,
+change_id, _number, owner} are set and the subject will be set to
+"\*\**ERROR***". All other fields will be empty.
+Note that the handling of this parameter is up to the index implementation.
+
Clients are allowed to specify more than one query by setting the `q`
parameter multiple times. In this case the result is an array of
arrays, one per query in the same order the queries were given in.
@@ -1463,6 +1472,9 @@
Optionally, the parent revision (of the oldest ancestor to be rebased) can be changed to another
change, revision or branch through the link:#rebase-input[RebaseInput] entity.
+Providing a `committer_email` through the link:#rebase-input[RebaseInput] entity is not supported
+when rebasing a chain.
+
If the chain is outdated, i.e., there's a change that depends on an old revision of its parent, the
result is the same as individually rebasing all outdated changes on top of their parent's latest
revision before running the rebase chain action.
@@ -6693,9 +6705,9 @@
may result in both false-positives and false-negatives.
Furthermore the additional lookup mean that they come with a performance penalty.
-* an ID of the change in the format "<project>~<branch>~<Change-Id>",
+* an ID of the change in the format "<project>\~<branch>~<Change-Id>",
where for the branch the `refs/heads/` prefix can be omitted
- ("myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940")
+ ("myProject\~master~I8473b95934b5732ac55d26311a706c9c2bde9940")
* a Change-Id if it uniquely identifies one change
("I8473b95934b5732ac55d26311a706c9c2bde9940")
* a change number if it uniquely identifies one change ("4247")
@@ -7017,9 +7029,7 @@
|==================================
|Field Name ||Description
|`id` ||
-The ID of the change. Subject to a 'GerritBackendFeature__return_new_change_info_id'
- experiment, the format is either "'<project>\~<_number>'" (new format),
-or `triplet_id`. The experiment is needed to allow callers to migrate.
+The ID of the change. The format is "'<project>\~<_number>'".
'project' and '_number' are URL encoded. The callers must not rely on the format.
|`triplet_id` ||
The ID of the change in the format "'<project>\~<branch>~<Change-Id>'",
@@ -8336,6 +8346,10 @@
In addition, rebasing on behalf of the uploader is only supported for the
current patch set of a change. +
If the caller is the uploader this flag is ignored and a normal rebase is done.
+|`committer_email`|optional|
+Rebase is committed using this email address. Only the registered emails
+of the calling user or uploader (when `on_behalf_of_uploader` is `true`) are
+considered valid. This option is not supported when rebasing a chain.
|`validation_options` |optional|
Map with key-value pairs that are forwarded as options to the commit validation
listeners (e.g. can be used to skip certain validations). Which validation
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 5920e42..fff9d0b 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -273,7 +273,20 @@
Tree(t)::
Get projects inheritance in a tree-like format. This option does
not work together with the branch option.
-+
+
+[NOTE]
+If the calling user does not meet any of the following criteria:
+
+* The state of the parent project is either "ACTIVE" or "READ ONLY",
+and the calling user has READ permission to at least one ref.
+* The state of the parent project is "HIDDEN" and the calling user
+has READ permission for 'refs/meta/config'.
+
+Then the 'parent' field will be labeled as '?-N', where N represents the
+nesting level within the project's tree structure. In the provided example,
+'All-Projects' corresponds to level 1, 'parent-project' to level 2, and
+'child-project' to level 3.
+
Get all the projects with tree option:
+
.Request
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index 39929e1..899c7a7 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -280,9 +280,14 @@
** [[cherry-pick]]`Cherry-Pick`:
+
-Allows to cherry-pick the change to another branch. The destination
-branch can be selected from a dialog. Cherry-picking a change creates a
-new open change on the selected destination branch.
+Allows to cherry-pick the change to another branch. The destination branch
+can be selected from a dialog. Cherry-picking a change creates a new open
+change on the selected destination branch. 'Cherry-pick committer email'
+drop-down is visible for single change cherry-picks when user has more than
+one email registered to their account. It is possible to select any of the
+registered emails to be used as the cherry-pick committer email. It defaults
+to source commit's committer email if it is a registered email of the calling
+user, else defaults to calling user's preferred email.
+
It is also possible to cherry-pick a change to the same branch. This is
effectively the same as rebasing it to the current tip of the
@@ -655,6 +660,44 @@
image::images/user-review-ui-side-by-side-diff-screen-rename.png[width=400, link="images/user-review-ui-side-by-side-diff-screen-rename.png"]
+[[normal-and-rebase-edits]]
+=== Normal and Rebase Edits
+
+In the diff view, Gerrit shows added and removed contents with green and red
+colors respectively.
+
+image::images/user-review-ui-side-by-side-diff-normal-edits.png[width=800, link="images/user-review-ui-side-by-side-diff-normal-edits.png"]
+
+When comparing two patch-sets against each other, and if both patch-sets have
+different bases (parents), Gerrit also identifies parts of the diff that were
+modified due to rebase. Those are called “rebase edits” and are highlighted with
+different colors.
+
+image::images/user-review-ui-side-by-side-diff-rebase-edits.png[width=800, link="images/user-review-ui-side-by-side-diff-rebase-edits.png"]
+
+Gerrit identifies rebase edits by also inspecting the diff between parents, and
+if it detects an edit between parents that’s also an edit between the patch-sets
+(after mapping/transforming the edit), then it marks it as a rebase edit. This
+first diffs both patch-sets to identify all edits, then potentially excludes
+some of them if they were identified as rebase edits.
+
+image::images/user-review-ui-side-by-side-diff-rebase.png[width=400, link="images/user-review-ui-side-by-side-diff-rebase.png"]
+
+If all edits in a file were due to rebase, the file is skipped and is not shown
+among the list of files in the ‘files tab’.
+
+[[hazardous-rebases]]
+=== Hazardous Rebases
+
+A rebase might be hazardous in some cases. One such example is when users have a
+stack of changes (e.g. two changes as in the below figure) then squash both
+changes and upload the resulting commit as patch-set 2. In this case, PS1 and
+PS2 are identical and Gerrit shows an empty diff, which is a correct diff
+but it's hiding the fact that new content got implicitly merged into this change
+from the parent change.
+
+image::images/user-review-ui-side-by-side-diff-squash.png[width=400, link="images/user-review-ui-side-by-side-diff-squash.png"]
+
[[inline-comments]]
=== Inline Comments
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 9ed4792..cc020ad 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -345,7 +345,7 @@
of the argument.
[[message]]
-message:'MESSAGE'::
+message:'MESSAGE'::, description:'MESSAGE'::, d:'MESSAGE'::
+
Changes that match 'MESSAGE' arbitrary string in the commit message body.
By default full text matching is used, but regular expressions can be
diff --git a/WORKSPACE b/WORKSPACE
index d5e5200..8ce21d5 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -103,6 +103,12 @@
register_toolchains("//tools:error_prone_warnings_toolchain_java17_definition")
+# Java-Prettify external repository consumed from git submodule
+local_repository(
+ name = "java-prettify",
+ path = "modules/java-prettify",
+)
+
# JGit external repository consumed from git submodule
local_repository(
name = "jgit",
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index a6ba8df..80582a4 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -51,6 +51,8 @@
import com.google.common.testing.FakeTicker;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
import com.google.gerrit.acceptance.PushOneCommit.Result;
+import com.google.gerrit.acceptance.config.ConfigAnnotationParser;
+import com.google.gerrit.acceptance.config.GerritSystemProperty;
import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
@@ -283,7 +285,9 @@
@Inject protected TestTicker testTicker;
protected EventRecorder eventRecorder;
+
protected GerritServer server;
+
protected Project.NameKey project;
protected RestSession adminRestSession;
protected RestSession userRestSession;
@@ -293,7 +297,6 @@
protected TestAccount admin;
protected TestAccount user;
protected TestRepository<InMemoryRepository> testRepo;
- protected String testMethodName;
protected String resourcePrefix;
protected Description description;
protected GerritServer.Description testMethodDescription;
@@ -316,6 +319,101 @@
private String systemTimeZone;
private SystemReader oldSystemReader;
+ /**
+ * The Getters and Setters below are needed for tests that run on custom {@link GerritServer}
+ * (that can be set up via {@link #initServer} and {@link #setUpDatabase} methods. Because tests
+ * inherit directly from {@link AbstractDaemonTest}, the set up has to be delegated to some other
+ * class that can share the set up logic across different test classes.
+ *
+ * <p>E.g, we need to be able to do something like:
+ *
+ * <pre>{@code
+ * public class AccountIT extends AbstractDaemonTest {...}
+ *
+ * public class AbstractDaemonTestAdapter {
+ *
+ * protected void initServer() {...}
+ *
+ * ...
+ *
+ * }
+ *
+ * public class CustomAccountIT extends AccountIT {
+ *
+ * AbstractDaemonTestAdapter testAdapter;
+ *
+ * {@literal @Override}
+ * protected void initServer() {
+ * testAdapter.initServer();
+ * }
+ * ...
+ * }
+ *
+ * public class CustomChangeIT extends ChangeIT {
+ *
+ * AbstractDaemonTestAdapter testAdapter;
+ *
+ * {@literal @Override}
+ * protected void initServer() {
+ * testAdapter.initServer();
+ * }
+ * ...
+ * }
+ *
+ * }</pre>
+ */
+ public String getResourcePrefix() {
+ return resourcePrefix;
+ }
+
+ public void setResourcePrefix(String resourcePrefix) {
+ this.resourcePrefix = resourcePrefix;
+ }
+
+ public Description getDescription() {
+ return description;
+ }
+
+ public TestRepository<InMemoryRepository> getTestRepo() {
+ return testRepo;
+ }
+
+ public void setTestRepo(TestRepository<InMemoryRepository> testRepo) {
+ this.testRepo = testRepo;
+ }
+
+ public TestAccount getUser() {
+ return user;
+ }
+
+ public void setUser(TestAccount user) {
+ this.user = user;
+ }
+
+ public TestAccount getAdmin() {
+ return admin;
+ }
+
+ public void setAdmin(TestAccount admin) {
+ this.admin = admin;
+ }
+
+ public Project.NameKey getProject() {
+ return project;
+ }
+
+ public void setProject(Project.NameKey project) {
+ this.project = project;
+ }
+
+ public GerritServer getServer() {
+ return server;
+ }
+
+ public void setServer(GerritServer server) {
+ this.server = server;
+ }
+
@Before
public void clearSender() {
if (sender != null) {
@@ -407,7 +505,7 @@
initSsh();
}
- protected void reindexAccount(Account.Id accountId) {
+ public void reindexAccount(Account.Id accountId) {
accountIndexer.index(accountId);
}
@@ -437,6 +535,14 @@
GerritServer.Description.forTestMethod(description, configName);
testMethodDescription = methodDesc;
+ if (methodDesc.systemProperties() != null) {
+ ConfigAnnotationParser.parse(methodDesc.systemProperties());
+ }
+
+ if (methodDesc.systemProperty() != null) {
+ ConfigAnnotationParser.parse(methodDesc.systemProperty());
+ }
+
testRequiresSsh = classDesc.useSshAnnotation() || methodDesc.useSshAnnotation();
if (!testRequiresSsh) {
baseConfig.setString("sshd", null, "listenAddress", "off");
@@ -476,14 +582,13 @@
initSsh();
- testMethodName = description.getMethodName();
+ String testMethodName = description.getMethodName();
resourcePrefix =
UNSAFE_PROJECT_NAME
.matcher(description.getClassName() + "_" + testMethodName + "_")
.replaceAll("");
- Context ctx = newRequestContext(admin);
- atrScope.set(ctx);
+ setRequestScope(admin);
ProjectInput in = projectInput(description);
gApi.projects().create(in);
project = Project.nameKey(in.name);
@@ -596,7 +701,7 @@
}
/** Generate default project properties based on test description */
- protected ProjectInput projectInput(Description description) {
+ public ProjectInput projectInput(Description description) {
ProjectInput in = new ProjectInput();
TestProjectInput ann = description.getAnnotation(TestProjectInput.class);
in.name = name("project");
@@ -629,7 +734,7 @@
// Default implementation does nothing.
}
- protected static final Pattern UNSAFE_PROJECT_NAME = Pattern.compile("[^a-zA-Z0-9._/-]+");
+ public static final Pattern UNSAFE_PROJECT_NAME = Pattern.compile("[^a-zA-Z0-9._/-]+");
protected Git git() {
return testRepo.git();
@@ -701,6 +806,19 @@
server.close();
server = null;
}
+
+ GerritServer.Description methodDesc =
+ GerritServer.Description.forTestMethod(description, configName);
+ if (methodDesc.systemProperties() != null) {
+ for (GerritSystemProperty sysProp : methodDesc.systemProperties().value()) {
+ System.clearProperty(sysProp.name());
+ }
+ }
+
+ if (methodDesc.systemProperty() != null) {
+ System.clearProperty(methodDesc.systemProperty().name());
+ }
+
SystemReader.setInstance(oldSystemReader);
oldSystemReader = null;
// Set useDefaultTicker in afterTest, so the next beforeTest will use the default ticker
@@ -1040,6 +1158,12 @@
return gApi.changes().query(q).get();
}
+ /** Sets up {@code account} as a caller in tests. */
+ public void setRequestScope(TestAccount account) {
+ Context ctx = newRequestContext(account);
+ atrScope.set(ctx);
+ }
+
protected Context newRequestContext(TestAccount account) {
requestScopeOperations.setApiUser(account.id());
return atrScope.get();
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index 7c6df1e..73631e9 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -31,6 +31,8 @@
import com.google.gerrit.acceptance.config.ConfigAnnotationParser;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.config.GerritConfigs;
+import com.google.gerrit.acceptance.config.GerritSystemProperties;
+import com.google.gerrit.acceptance.config.GerritSystemProperty;
import com.google.gerrit.acceptance.config.GlobalPluginConfig;
import com.google.gerrit.acceptance.config.GlobalPluginConfigs;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
@@ -137,6 +139,8 @@
false, // @UseSystemTime is only valid on methods.
get(UseClockStep.class, testDesc.getTestClass()),
get(UseTimezone.class, testDesc.getTestClass()),
+ null, // @GerritSystemProperty is only valid on methods.
+ null, // @GerritSystemProperties is only valid on methods.
null, // @GerritConfig is only valid on methods.
null, // @GerritConfigs is only valid on methods.
null, // @GlobalPluginConfig is only valid on methods.
@@ -179,6 +183,8 @@
testDesc.getAnnotation(UseTimezone.class) != null
? testDesc.getAnnotation(UseTimezone.class)
: get(UseTimezone.class, testDesc.getTestClass()),
+ testDesc.getAnnotation(GerritSystemProperty.class),
+ testDesc.getAnnotation(GerritSystemProperties.class),
testDesc.getAnnotation(GerritConfig.class),
testDesc.getAnnotation(GerritConfigs.class),
testDesc.getAnnotation(GlobalPluginConfig.class),
@@ -234,6 +240,12 @@
abstract UseTimezone useTimezone();
@Nullable
+ abstract GerritSystemProperty systemProperty();
+
+ @Nullable
+ abstract GerritSystemProperties systemProperties();
+
+ @Nullable
abstract GerritConfig config();
@Nullable
@@ -249,6 +261,10 @@
if (useClockStep() != null && useSystemTime()) {
throw new IllegalStateException("Use either @UseClockStep or @UseSystemTime, not both");
}
+ if (systemProperties() != null && systemProperty() != null) {
+ throw new IllegalStateException(
+ "Use either @GerritSystemProperties or @GerritSystemProperty, not both");
+ }
if (configs() != null && config() != null) {
throw new IllegalStateException("Use either @GerritConfigs or @GerritConfig, not both");
}
diff --git a/java/com/google/gerrit/acceptance/config/ConfigAnnotationParser.java b/java/com/google/gerrit/acceptance/config/ConfigAnnotationParser.java
index 27ce857..fc6be03 100644
--- a/java/com/google/gerrit/acceptance/config/ConfigAnnotationParser.java
+++ b/java/com/google/gerrit/acceptance/config/ConfigAnnotationParser.java
@@ -47,6 +47,16 @@
return cfg;
}
+ public static void parse(GerritSystemProperties annotation) {
+ for (GerritSystemProperty prop : annotation.value()) {
+ parse(prop);
+ }
+ }
+
+ public static void parse(GerritSystemProperty annotation) {
+ System.setProperty(annotation.name(), annotation.value());
+ }
+
@Nullable
public static Map<String, Config> parse(GlobalPluginConfigs annotation) {
if (annotation == null || annotation.value().length < 1) {
diff --git a/java/com/google/gerrit/acceptance/config/GerritSystemProperties.java b/java/com/google/gerrit/acceptance/config/GerritSystemProperties.java
new file mode 100644
index 0000000..cc6389c
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/config/GerritSystemProperties.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2023 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.config;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Target({METHOD})
+@Retention(RUNTIME)
+public @interface GerritSystemProperties {
+ GerritSystemProperty[] value();
+}
diff --git a/java/com/google/gerrit/acceptance/config/GerritSystemProperty.java b/java/com/google/gerrit/acceptance/config/GerritSystemProperty.java
new file mode 100644
index 0000000..a2bf735
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/config/GerritSystemProperty.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2023 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.config;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Target({METHOD})
+@Retention(RUNTIME)
+@Repeatable(GerritSystemProperties.class)
+public @interface GerritSystemProperty {
+ /** System property name. */
+ String name();
+
+ /** Value of the system property. */
+ String value() default "";
+}
diff --git a/java/com/google/gerrit/extensions/api/changes/Changes.java b/java/com/google/gerrit/extensions/api/changes/Changes.java
index d8741f5..ec392d8 100644
--- a/java/com/google/gerrit/extensions/api/changes/Changes.java
+++ b/java/com/google/gerrit/extensions/api/changes/Changes.java
@@ -16,6 +16,7 @@
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
+import com.google.gerrit.common.UsedAt;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
@@ -81,6 +82,7 @@
private int limit;
private int start;
private boolean isNoLimit;
+ private boolean allowIncompleteResults;
private Set<ListChangesOption> options = EnumSet.noneOf(ListChangesOption.class);
private ListMultimap<String, String> pluginOptions = ArrayListMultimap.create();
@@ -106,6 +108,12 @@
return this;
}
+ @UsedAt(UsedAt.Project.GOOGLE)
+ public QueryRequest withAllowIncompleteResults(boolean allow) {
+ this.allowIncompleteResults = allow;
+ return this;
+ }
+
/** Set an option on the request, appending to existing options. */
public QueryRequest withOption(ListChangesOption options) {
this.options.add(options);
@@ -152,6 +160,11 @@
return start;
}
+ @UsedAt(UsedAt.Project.GOOGLE)
+ public boolean getAllowIncompleteResults() {
+ return allowIncompleteResults;
+ }
+
public Set<ListChangesOption> getOptions() {
return options;
}
diff --git a/java/com/google/gerrit/extensions/api/changes/RebaseInput.java b/java/com/google/gerrit/extensions/api/changes/RebaseInput.java
index 07e65d0..42dea8d 100644
--- a/java/com/google/gerrit/extensions/api/changes/RebaseInput.java
+++ b/java/com/google/gerrit/extensions/api/changes/RebaseInput.java
@@ -51,4 +51,12 @@
public boolean onBehalfOfUploader;
public Map<String, String> validationOptions;
+
+ /**
+ * Rebase will be committed using this email address. Only the registered emails of the calling
+ * user or uploader (when onBehalfOfUploader is true) are considered valid.
+ *
+ * <p>This option is not supported when rebasing a chain.
+ */
+ public String committerEmail;
}
diff --git a/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java b/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
index 5b33aca..5c48aaf 100644
--- a/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
+++ b/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
@@ -142,6 +142,13 @@
public List<MenuItem> my;
public List<String> changeTable;
public Boolean allowBrowserNotifications;
+ /**
+ * The sidebar section that the user prefers to have open on the diff page, or "NONE" if all
+ * sidebars should be closed.
+ *
+ * <p>Sidebars supplied by plugins are prefixed with "plugin-".
+ */
+ public String diffPageSidebar;
public DateFormat getDateFormat() {
if (dateFormat == null) {
@@ -205,7 +212,8 @@
&& Objects.equals(this.workInProgressByDefault, other.workInProgressByDefault)
&& Objects.equals(this.my, other.my)
&& Objects.equals(this.changeTable, other.changeTable)
- && Objects.equals(this.allowBrowserNotifications, other.allowBrowserNotifications);
+ && Objects.equals(this.allowBrowserNotifications, other.allowBrowserNotifications)
+ && Objects.equals(this.diffPageSidebar, other.diffPageSidebar);
}
@Override
@@ -232,7 +240,8 @@
workInProgressByDefault,
my,
changeTable,
- allowBrowserNotifications);
+ allowBrowserNotifications,
+ diffPageSidebar);
}
@Override
@@ -260,6 +269,7 @@
.add("my", my)
.add("changeTable", changeTable)
.add("allowBrowserNotifications", allowBrowserNotifications)
+ .add("diffPageSidebar", diffPageSidebar)
.toString();
}
@@ -285,6 +295,7 @@
p.disableTokenHighlighting = false;
p.workInProgressByDefault = false;
p.allowBrowserNotifications = true;
+ p.diffPageSidebar = "NONE";
return p;
}
}
diff --git a/java/com/google/gerrit/httpd/UrlModule.java b/java/com/google/gerrit/httpd/UrlModule.java
index 69adf82..5a898a1 100644
--- a/java/com/google/gerrit/httpd/UrlModule.java
+++ b/java/com/google/gerrit/httpd/UrlModule.java
@@ -72,7 +72,7 @@
serveRegex("^/settings/?$").with(screen(PageLinks.SETTINGS));
serveRegex("^/register$").with(registerScreen(false));
serveRegex("^/register/(.+)$").with(registerScreen(true));
- serveRegex("^/([1-9][0-9]*)/?$").with(NumericChangeIdRedirectServlet.class);
+ serveRegex("^(?:/c)?/([1-9][0-9]*)/?$").with(NumericChangeIdRedirectServlet.class);
serveRegex("^/p/(.*)$").with(queryProjectNew());
serveRegex("^/r/(.+)/?$").with(DirectChangeByCommit.class);
diff --git a/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java b/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
index be833ea..f0a8b89 100644
--- a/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
+++ b/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
@@ -147,8 +147,10 @@
@Nullable
String getRemoteDisplayname(HttpServletRequest req) {
if (displaynameHeader != null) {
- String raw = req.getHeader(displaynameHeader);
- return emptyToNull(new String(raw.getBytes(ISO_8859_1), UTF_8));
+ String raw = emptyToNull(req.getHeader(displaynameHeader));
+ if (raw != null) {
+ return new String(raw.getBytes(ISO_8859_1), UTF_8);
+ }
}
return null;
}
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index 3556593..e1abcb1 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -78,9 +78,9 @@
import com.google.gerrit.server.config.SysExecutorModule;
import com.google.gerrit.server.events.EventBroker.EventBrokerModule;
import com.google.gerrit.server.events.StreamEventsApiListener.StreamEventsApiListenerModule;
+import com.google.gerrit.server.git.ChangesByProjectCache;
import com.google.gerrit.server.git.GarbageCollectionModule;
import com.google.gerrit.server.git.GitRepositoryManagerModule;
-import com.google.gerrit.server.git.SearchingChangeCacheImpl.SearchingChangeCacheImplModule;
import com.google.gerrit.server.git.SystemReaderInstaller;
import com.google.gerrit.server.git.WorkQueue.WorkQueueModule;
import com.google.gerrit.server.index.IndexModule;
@@ -317,7 +317,7 @@
modules.add(new ProjectQueryBuilderModule());
modules.add(new DefaultRefLogIdentityProvider.Module());
modules.add(new PluginApiModule());
- modules.add(new SearchingChangeCacheImplModule());
+ modules.add(new ChangesByProjectCache.Module(ChangesByProjectCache.UseIndex.TRUE, config));
modules.add(new InternalAccountDirectoryModule());
modules.add(new DefaultPermissionBackendModule());
modules.add(new DefaultMemoryCacheModule());
diff --git a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
index 1d9a0af..fb28d30 100644
--- a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
+++ b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
@@ -27,6 +27,7 @@
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.accounts.AccountApi;
import com.google.gerrit.extensions.api.config.Server;
+import com.google.gerrit.extensions.client.ListOption;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.json.OutputFormat;
@@ -94,6 +95,8 @@
case CHANGE:
case DIFF:
data.put(
+ "defaultChangeDetailHex", ListOption.toHex(IndexPreloadingUtil.CHANGE_DETAIL_OPTIONS));
+ data.put(
"changeRequestsPath",
IndexPreloadingUtil.computeChangeRequestsPath(requestedPath, page).get());
data.put("changeNum", IndexPreloadingUtil.computeChangeNum(requestedPath, page).get());
@@ -118,6 +121,7 @@
serializeObject(GSON, accountApi.getEditPreferences()));
data.put("userIsAuthenticated", true);
if (page == RequestedPage.DASHBOARD) {
+ data.put("defaultDashboardHex", ListOption.toHex(IndexPreloadingUtil.DASHBOARD_OPTIONS));
data.put("dashboardQuery", IndexPreloadingUtil.computeDashboardQueryList());
}
} catch (AuthException e) {
diff --git a/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java b/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java
index 3ada18d..4c42e79 100644
--- a/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java
+++ b/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java
@@ -17,10 +17,12 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.common.UsedAt.Project;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.restapi.Url;
import java.net.URI;
import java.net.URISyntaxException;
@@ -81,6 +83,27 @@
NEW_USER)
.map(query -> query.replaceAll("\\$\\{user}", "self"))
.collect(toImmutableList());
+ public static final ImmutableSet<ListChangesOption> DASHBOARD_OPTIONS =
+ ImmutableSet.of(
+ ListChangesOption.LABELS,
+ ListChangesOption.DETAILED_ACCOUNTS,
+ ListChangesOption.SUBMIT_REQUIREMENTS,
+ ListChangesOption.STAR);
+
+ public static final ImmutableSet<ListChangesOption> CHANGE_DETAIL_OPTIONS =
+ ImmutableSet.of(
+ ListChangesOption.ALL_COMMITS,
+ ListChangesOption.ALL_REVISIONS,
+ ListChangesOption.CHANGE_ACTIONS,
+ ListChangesOption.DETAILED_ACCOUNTS,
+ ListChangesOption.DETAILED_LABELS,
+ ListChangesOption.DOWNLOAD_COMMANDS,
+ ListChangesOption.MESSAGES,
+ ListChangesOption.SUBMITTABLE,
+ ListChangesOption.WEB_LINKS,
+ ListChangesOption.SKIP_DIFFSTAT,
+ ListChangesOption.SUBMIT_REQUIREMENTS,
+ ListChangesOption.PARENTS);
@Nullable
public static String getPath(@Nullable String requestedURL) throws URISyntaxException {
diff --git a/java/com/google/gerrit/httpd/raw/StaticModule.java b/java/com/google/gerrit/httpd/raw/StaticModule.java
index 3c8287f..587d82a 100644
--- a/java/com/google/gerrit/httpd/raw/StaticModule.java
+++ b/java/com/google/gerrit/httpd/raw/StaticModule.java
@@ -46,6 +46,7 @@
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Path;
+import java.util.regex.Pattern;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
@@ -61,6 +62,8 @@
public class StaticModule extends ServletModule {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ public static final String CHANGE_NUMBER_URI_REGEX = "^(?:/c)?/([1-9][0-9]*)/?$";
+ private static final Pattern CHANGE_NUMBER_URI_PATTERN = Pattern.compile(CHANGE_NUMBER_URI_REGEX);
/**
* Paths that should be treated as static assets when serving PolyGerrit.
@@ -403,7 +406,11 @@
}
private static boolean isPolyGerritIndex(String path) {
- return matchPath(POLYGERRIT_INDEX_PATHS, path);
+ return !isChangeNumberUri(path) && matchPath(POLYGERRIT_INDEX_PATHS, path);
+ }
+
+ private static boolean isChangeNumberUri(String path) {
+ return CHANGE_NUMBER_URI_PATTERN.matcher(path).matches();
}
private static boolean matchPath(Iterable<String> paths, String path) {
diff --git a/java/com/google/gerrit/index/BUILD b/java/com/google/gerrit/index/BUILD
index ba1c8bd..8b48fc0 100644
--- a/java/com/google/gerrit/index/BUILD
+++ b/java/com/google/gerrit/index/BUILD
@@ -36,6 +36,7 @@
"//lib/antlr:java-runtime",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
+ "//lib/errorprone:annotations",
"//lib/flogger:api",
],
)
diff --git a/java/com/google/gerrit/index/QueryOptions.java b/java/com/google/gerrit/index/QueryOptions.java
index 29ab6d0..bee8fa1 100644
--- a/java/com/google/gerrit/index/QueryOptions.java
+++ b/java/com/google/gerrit/index/QueryOptions.java
@@ -52,6 +52,38 @@
int pageSizeMultiplier,
int limit,
Set<String> fields) {
+ return create(
+ config,
+ start,
+ searchAfter,
+ pageSize,
+ pageSizeMultiplier,
+ limit,
+ /* allowIncompleteResults= */ false,
+ fields);
+ }
+
+ public static QueryOptions create(
+ IndexConfig config,
+ int start,
+ int pageSize,
+ int pageSizeMultiplier,
+ int limit,
+ boolean allowIncompleteResults,
+ Set<String> fields) {
+ return create(
+ config, start, null, pageSize, pageSizeMultiplier, limit, allowIncompleteResults, fields);
+ }
+
+ public static QueryOptions create(
+ IndexConfig config,
+ int start,
+ Object searchAfter,
+ int pageSize,
+ int pageSizeMultiplier,
+ int limit,
+ boolean allowIncompleteResults,
+ Set<String> fields) {
checkArgument(start >= 0, "start must be nonnegative: %s", start);
checkArgument(limit > 0, "limit must be positive: %s", limit);
if (searchAfter != null) {
@@ -64,6 +96,7 @@
pageSize,
pageSizeMultiplier,
limit,
+ allowIncompleteResults,
ImmutableSet.copyOf(fields));
}
@@ -77,7 +110,15 @@
Math.min(
Math.min(Ints.saturatedCast((long) pageSize() + start()), config().maxPageSize()),
backendLimit);
- return create(config(), 0, null, pageSize, pageSizeMultiplier(), limit, fields());
+ return create(
+ config(),
+ 0,
+ null,
+ pageSize,
+ pageSizeMultiplier(),
+ limit,
+ allowIncompleteResults(),
+ fields());
}
public abstract IndexConfig config();
@@ -93,28 +134,62 @@
public abstract int limit();
+ /**
+ * When set to true, entities that fail to get parsed from the index are replaced with a canonical
+ * erroneous record. If false, parsing would throw an exception.
+ */
+ public abstract boolean allowIncompleteResults();
+
public abstract ImmutableSet<String> fields();
public QueryOptions withPageSize(int pageSize) {
return create(
- config(), start(), searchAfter(), pageSize, pageSizeMultiplier(), limit(), fields());
+ config(),
+ start(),
+ searchAfter(),
+ pageSize,
+ pageSizeMultiplier(),
+ limit(),
+ allowIncompleteResults(),
+ fields());
}
public QueryOptions withLimit(int newLimit) {
return create(
- config(), start(), searchAfter(), pageSize(), pageSizeMultiplier(), newLimit, fields());
+ config(),
+ start(),
+ searchAfter(),
+ pageSize(),
+ pageSizeMultiplier(),
+ newLimit,
+ allowIncompleteResults(),
+ fields());
}
public QueryOptions withStart(int newStart) {
return create(
- config(), newStart, searchAfter(), pageSize(), pageSizeMultiplier(), limit(), fields());
+ config(),
+ newStart,
+ searchAfter(),
+ pageSize(),
+ pageSizeMultiplier(),
+ limit(),
+ allowIncompleteResults(),
+ fields());
}
public QueryOptions withSearchAfter(Object newSearchAfter) {
// Index search-after APIs don't use 'start', so set it to 0 to be safe. ElasticSearch for
// example, expects it to be 0 when using search-after APIs.
return create(
- config(), start(), newSearchAfter, pageSize(), pageSizeMultiplier(), limit(), fields())
+ config(),
+ start(),
+ newSearchAfter,
+ pageSize(),
+ pageSizeMultiplier(),
+ limit(),
+ allowIncompleteResults(),
+ fields())
.withStart(0);
}
@@ -126,6 +201,7 @@
pageSize(),
pageSizeMultiplier(),
limit(),
+ allowIncompleteResults(),
filter.apply(this));
}
}
diff --git a/java/com/google/gerrit/index/query/QueryProcessor.java b/java/com/google/gerrit/index/query/QueryProcessor.java
index dcc6af6..d847a06 100644
--- a/java/com/google/gerrit/index/query/QueryProcessor.java
+++ b/java/com/google/gerrit/index/query/QueryProcessor.java
@@ -26,6 +26,7 @@
import com.google.common.collect.Ordering;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.Index;
@@ -61,7 +62,7 @@
protected static class Metrics {
final Timer1<String> executionTime;
- Metrics(MetricMaker metricMaker) {
+ protected Metrics(MetricMaker metricMaker) {
executionTime =
metricMaker.newTimer(
"query/query_latency",
@@ -92,17 +93,18 @@
private boolean enforceVisibility = true;
private int userProvidedLimit;
private boolean isNoLimit;
+ private boolean allowIncompleteResults;
private Set<String> requestedFields;
protected QueryProcessor(
- MetricMaker metricMaker,
+ Metrics metrics,
SchemaDefinitions<T> schemaDef,
IndexConfig indexConfig,
IndexCollection<?, T, ? extends Index<?, T>> indexes,
IndexRewriter<T> rewriter,
String limitField,
IntSupplier userQueryLimit) {
- this.metrics = new Metrics(metricMaker);
+ this.metrics = metrics;
this.schemaDef = schemaDef;
this.indexConfig = indexConfig;
this.indexes = indexes;
@@ -163,6 +165,12 @@
return this;
}
+ @CanIgnoreReturnValue
+ public QueryProcessor<T> setAllowIncompleteResults(boolean allowIncompleteResults) {
+ this.allowIncompleteResults = allowIncompleteResults;
+ return this;
+ }
+
public QueryProcessor<T> setRequestedFields(Set<String> fields) {
requestedFields = fields;
return this;
@@ -271,6 +279,7 @@
// ask for one more result from the query.
// NOTE: This is consistent to the behaviour before the introduction of pagination.`
limit == getBackendSupportedLimit() ? limit : Ints.saturatedCast((long) limit + 1),
+ allowIncompleteResults,
getRequestedFields());
logger.atFine().log("Query options: %s", opts);
// Apply index-specific rewrite first
@@ -358,9 +367,16 @@
int pageSize,
int pageSizeMultiplier,
int limit,
+ boolean allowIncompleteResults,
Set<String> requestedFields) {
return QueryOptions.create(
- indexConfig, start, pageSize, pageSizeMultiplier, limit, requestedFields);
+ indexConfig,
+ start,
+ pageSize,
+ pageSizeMultiplier,
+ limit,
+ allowIncompleteResults,
+ requestedFields);
}
/**
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index ba518b7..d56955f 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -89,8 +89,8 @@
import com.google.gerrit.server.config.SysExecutorModule;
import com.google.gerrit.server.events.EventBroker.EventBrokerModule;
import com.google.gerrit.server.events.StreamEventsApiListener.StreamEventsApiListenerModule;
+import com.google.gerrit.server.git.ChangesByProjectCache;
import com.google.gerrit.server.git.GarbageCollectionModule;
-import com.google.gerrit.server.git.SearchingChangeCacheImpl.SearchingChangeCacheImplModule;
import com.google.gerrit.server.git.WorkQueue.WorkQueueModule;
import com.google.gerrit.server.group.PeriodicGroupIndexer.PeriodicGroupIndexerModule;
import com.google.gerrit.server.index.AbstractIndexModule;
@@ -476,7 +476,10 @@
modules.add(new DefaultRefLogIdentityProvider.Module());
modules.add(new PluginApiModule());
- modules.add(new SearchingChangeCacheImplModule(replica));
+ modules.add(
+ new ChangesByProjectCache.Module(
+ replica ? ChangesByProjectCache.UseIndex.FALSE : ChangesByProjectCache.UseIndex.TRUE,
+ config));
modules.add(new InternalAccountDirectoryModule());
modules.add(new DefaultPermissionBackendModule());
modules.add(new DefaultMemoryCacheModule());
diff --git a/java/com/google/gerrit/pgm/Reindex.java b/java/com/google/gerrit/pgm/Reindex.java
index f22ceb5..bbba28c 100644
--- a/java/com/google/gerrit/pgm/Reindex.java
+++ b/java/com/google/gerrit/pgm/Reindex.java
@@ -42,6 +42,7 @@
import com.google.gerrit.server.index.IndexModule;
import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.index.options.AutoFlush;
+import com.google.gerrit.server.index.options.BuildBloomFilter;
import com.google.gerrit.server.index.options.IsFirstInsertForEntry;
import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
import com.google.gerrit.server.util.ReplicaUtil;
@@ -92,6 +93,9 @@
@Option(name = "--show-cache-stats", usage = "Show cache statistics at the end.")
private boolean showCacheStats;
+ @Option(name = "--build-bloom-filter", usage = "Build bloom filter for H2 disk caches.")
+ private boolean buildBloomFilter;
+
private Injector dbInjector;
private Injector sysInjector;
private Injector cfgInjector;
@@ -209,6 +213,9 @@
OptionalBinder.newOptionalBinder(binder(), IsFirstInsertForEntry.class)
.setBinding()
.toInstance(IsFirstInsertForEntry.YES);
+ OptionalBinder.newOptionalBinder(binder(), BuildBloomFilter.class)
+ .setBinding()
+ .toInstance(buildBloomFilter ? BuildBloomFilter.TRUE : BuildBloomFilter.FALSE);
}
});
modules.add(new BatchProgramModule(dbInjector));
diff --git a/java/com/google/gerrit/pgm/init/InitModule.java b/java/com/google/gerrit/pgm/init/InitModule.java
index 473e3aa..a40a704 100644
--- a/java/com/google/gerrit/pgm/init/InitModule.java
+++ b/java/com/google/gerrit/pgm/init/InitModule.java
@@ -14,12 +14,19 @@
package com.google.gerrit.pgm.init;
+import static com.google.gerrit.server.Sequence.LightweightAccounts;
+import static com.google.inject.Scopes.SINGLETON;
+
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.pgm.init.api.InitStep;
import com.google.gerrit.pgm.init.api.Section;
+import com.google.gerrit.server.Sequence;
import com.google.gerrit.server.account.externalids.ExternalIdFactory;
import com.google.gerrit.server.account.externalids.storage.notedb.ExternalIdFactoryNoteDbImpl;
+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.notedb.RepoSequence.DisabledGitRefUpdatedRepoAccountsSequenceProvider;
import com.google.inject.Singleton;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.internal.UniqueAnnotations;
@@ -37,6 +44,10 @@
@Override
protected void configure() {
bind(SitePaths.class);
+ bind(AllUsersName.class).toProvider(AllUsersNameProvider.class).in(SINGLETON);
+ bind(Sequence.class)
+ .annotatedWith(LightweightAccounts.class)
+ .toProvider(DisabledGitRefUpdatedRepoAccountsSequenceProvider.class);
factory(Section.Factory.class);
factory(VersionedAuthorizedKeysOnInit.Factory.class);
diff --git a/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java b/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
index c11230c..f6e2b9c 100644
--- a/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
+++ b/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
@@ -14,34 +14,22 @@
package com.google.gerrit.pgm.init.api;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.notedb.RepoSequence;
-import com.google.gerrit.server.notedb.Sequences;
+import static com.google.gerrit.server.Sequence.LightweightAccounts;
+
+import com.google.gerrit.server.Sequence;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
public class SequencesOnInit {
- private final GitRepositoryManager repoManager;
- private final AllUsersNameOnInitProvider allUsersName;
+ private final Sequence accountsSequence;
@Inject
- SequencesOnInit(GitRepositoryManagerOnInit repoManager, AllUsersNameOnInitProvider allUsersName) {
- this.repoManager = repoManager;
- this.allUsersName = allUsersName;
+ SequencesOnInit(@LightweightAccounts Sequence accountsSequence) {
+ this.accountsSequence = accountsSequence;
}
public int nextAccountId() {
- RepoSequence accountSeq =
- new RepoSequence(
- repoManager,
- GitReferenceUpdated.DISABLED,
- Project.nameKey(allUsersName.get()),
- Sequences.NAME_ACCOUNTS,
- () -> Sequences.FIRST_ACCOUNT_ID,
- 1);
- return accountSeq.next();
+ return accountsSequence.next();
}
}
diff --git a/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
index f42de84..718fa69 100644
--- a/java/com/google/gerrit/pgm/util/BatchProgramModule.java
+++ b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -69,11 +69,12 @@
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.extensions.events.RevisionCreated;
import com.google.gerrit.server.extensions.events.WorkInProgressStateChanged;
+import com.google.gerrit.server.git.ChangesByProjectCache;
import com.google.gerrit.server.git.PureRevertCache;
-import com.google.gerrit.server.git.SearchingChangeCacheImpl;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.notedb.ChangeDraftNotesUpdate;
import com.google.gerrit.server.notedb.NoteDbModule;
+import com.google.gerrit.server.notedb.RepoSequence.RepoSequenceModule;
import com.google.gerrit.server.patch.DiffExecutorModule;
import com.google.gerrit.server.patch.DiffOperationsImpl;
import com.google.gerrit.server.patch.PatchListCacheImpl;
@@ -164,10 +165,6 @@
factory(PatchSetInserter.Factory.class);
factory(RebaseChangeOp.Factory.class);
- // As Reindex is a batch program, don't assume the index is available for
- // the change cache.
- bind(SearchingChangeCacheImpl.class).toProvider(Providers.of(null));
-
bind(new TypeLiteral<ImmutableSet<GroupReference>>() {})
.annotatedWith(AdministrateServerGroups.class)
.toInstance(ImmutableSet.of());
@@ -179,6 +176,8 @@
.toInstance(Collections.emptySet());
modules.add(new BatchGitModule());
+ modules.add(
+ new ChangesByProjectCache.Module(ChangesByProjectCache.UseIndex.FALSE, getConfig()));
modules.add(new DefaultPermissionBackendModule());
modules.add(new DefaultMemoryCacheModule());
modules.add(new H2CacheModule());
@@ -198,6 +197,7 @@
modules.add(TagCache.module());
modules.add(PureRevertCache.module());
modules.add(new ApprovalModule());
+ modules.add(new RepoSequenceModule());
modules.add(SubmitRequirementsEvaluatorImpl.module());
factory(CapabilityCollection.Factory.class);
factory(ChangeData.AssistedFactory.class);
diff --git a/java/com/google/gerrit/proto/testing/BUILD b/java/com/google/gerrit/proto/testing/BUILD
index 0e5f887..069bb46 100644
--- a/java/com/google/gerrit/proto/testing/BUILD
+++ b/java/com/google/gerrit/proto/testing/BUILD
@@ -9,6 +9,7 @@
deps = [
"//lib:guava",
"//lib/commons:lang3",
+ "//lib/guice",
"//lib/truth",
],
)
diff --git a/java/com/google/gerrit/proto/testing/SerializedClassSubject.java b/java/com/google/gerrit/proto/testing/SerializedClassSubject.java
index 79affc6..1264478 100644
--- a/java/com/google/gerrit/proto/testing/SerializedClassSubject.java
+++ b/java/com/google/gerrit/proto/testing/SerializedClassSubject.java
@@ -20,6 +20,7 @@
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
+import com.google.inject.TypeLiteral;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@@ -61,10 +62,12 @@
}
private final Class<?> clazz;
+ private final TypeLiteral<?> clazzTl;
private SerializedClassSubject(FailureMetadata metadata, Class<?> clazz) {
super(metadata, clazz);
this.clazz = clazz;
+ this.clazzTl = TypeLiteral.get(clazz);
}
public void isAbstract() {
@@ -87,7 +90,7 @@
.that(
FieldUtils.getAllFieldsList(clazz).stream()
.filter(f -> !Modifier.isStatic(f.getModifiers()))
- .collect(toImmutableMap(Field::getName, Field::getGenericType)))
+ .collect(toImmutableMap(Field::getName, f -> clazzTl.getFieldType(f).getType())))
.containsExactlyEntriesIn(expectedFields);
}
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index 3587342..000f095 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -71,28 +71,7 @@
"//lib:autolink",
"//lib:automaton",
"//lib:blame-cache",
- "//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-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-profile-pegdown",
- "//lib:flexmark-util",
+ "//lib:flexmark-all-lib",
"//lib:gson",
"//lib:guava",
"//lib:guava-retrying",
diff --git a/java/com/google/gerrit/server/Sequence.java b/java/com/google/gerrit/server/Sequence.java
new file mode 100644
index 0000000..844b583
--- /dev/null
+++ b/java/com/google/gerrit/server/Sequence.java
@@ -0,0 +1,82 @@
+// Copyright (C) 2023 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;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * An incrementing sequence that's used to assign new unique numbers for change, account and group
+ * IDs.
+ */
+public interface Sequence {
+ String NAME_ACCOUNTS = "accounts";
+ String NAME_GROUPS = "groups";
+ String NAME_CHANGES = "changes";
+
+ /**
+ * Some callers cannot get the normal {@link #NAME_ACCOUNTS} sequence injected because some
+ * injected fields are not available at injection time. Allow for providing a light-weight
+ * injected instance.
+ */
+ @BindingAnnotation
+ @Target({FIELD, PARAMETER, METHOD})
+ @Retention(RUNTIME)
+ @interface LightweightAccounts {}
+
+ /**
+ * Some callers cannot get the normal {@link #NAME_GROUPS} sequence injected because some injected
+ * fields are not available at injection time. Allow for providing a light-weight injected
+ * instance.
+ */
+ @BindingAnnotation
+ @Target({FIELD, PARAMETER, METHOD})
+ @Retention(RUNTIME)
+ @interface LightweightGroups {}
+
+ enum SequenceType {
+ ACCOUNTS,
+ CHANGES,
+ GROUPS;
+ }
+
+ /** Returns the next available sequence value and increments the sequence for the next call. */
+ int next();
+
+ /** Similar to {@link #next()} but returns a {@code count} of next available values. */
+ ImmutableList<Integer> next(int count);
+
+ /** Returns the next available sequence value. */
+ int current();
+
+ /** Returns the last sequence number that was assigned. */
+ int last();
+
+ /**
+ * Stores a new {@code value} to be returned on the next calls for {@link #next()} or {@link
+ * #current()}.
+ */
+ void storeNew(int value);
+
+ /** Returns the batch size that was used to initialize the sequence. */
+ int getBatchSize();
+}
diff --git a/java/com/google/gerrit/server/account/AccountResolver.java b/java/com/google/gerrit/server/account/AccountResolver.java
index c267822..65e9d9d 100644
--- a/java/com/google/gerrit/server/account/AccountResolver.java
+++ b/java/com/google/gerrit/server/account/AccountResolver.java
@@ -26,7 +26,6 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Streams;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.common.UsedAt.Project;
import com.google.gerrit.entities.Account;
@@ -301,7 +300,7 @@
private abstract class AccountIdSearcher implements Searcher<Account.Id> {
@Override
public final Stream<AccountState> search(Account.Id input) {
- return Streams.stream(accountCache.get(input));
+ return accountCache.get(input).stream();
}
}
@@ -375,7 +374,7 @@
@Override
public Stream<AccountState> search(String input) {
- return Streams.stream(accountCache.getByUsername(input));
+ return accountCache.getByUsername(input).stream();
}
@Override
diff --git a/java/com/google/gerrit/server/api/changes/ChangesImpl.java b/java/com/google/gerrit/server/api/changes/ChangesImpl.java
index 6b107f1..1666820 100644
--- a/java/com/google/gerrit/server/api/changes/ChangesImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangesImpl.java
@@ -146,6 +146,7 @@
qc.setLimit(q.getLimit());
qc.setStart(q.getStart());
qc.setNoLimit(q.getNoLimit());
+ qc.setAllowIncompleteResults(q.getAllowIncompleteResults());
for (ListChangesOption option : q.getOptions()) {
qc.addOption(option);
}
diff --git a/java/com/google/gerrit/server/approval/ApprovalsUtil.java b/java/com/google/gerrit/server/approval/ApprovalsUtil.java
index 9b40a57..e64c273 100644
--- a/java/com/google/gerrit/server/approval/ApprovalsUtil.java
+++ b/java/com/google/gerrit/server/approval/ApprovalsUtil.java
@@ -377,6 +377,11 @@
return notes.load().getApprovals().onlyNonCopied();
}
+ public ListMultimap<PatchSet.Id, PatchSetApproval> byChangeIncludingCopiedApprovals(
+ ChangeNotes notes) {
+ return notes.load().getApprovals().all();
+ }
+
/**
* Copies approvals to a new patch set.
*
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
index 445d8a0..fdd55ac 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -14,11 +14,15 @@
package com.google.gerrit.server.cache.h2;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.server.cache.MemoryCacheFactory;
@@ -26,13 +30,17 @@
import com.google.gerrit.server.cache.PersistentCacheDef;
import com.google.gerrit.server.cache.h2.H2CacheImpl.SqlStore;
import com.google.gerrit.server.cache.h2.H2CacheImpl.ValueHolder;
+import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.options.BuildBloomFilter;
+import com.google.gerrit.server.index.options.IsFirstInsertForEntry;
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;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -57,32 +65,43 @@
private final ScheduledExecutorService cleanup;
private final long h2CacheSize;
private final boolean h2AutoServer;
+ private final boolean isOfflineReindex;
+ private final boolean buildBloomFilter;
@Inject
H2CacheFactory(
MemoryCacheFactory memCacheFactory,
@GerritServerConfig Config cfg,
SitePaths site,
- DynamicMap<Cache<?, ?>> cacheMap) {
+ DynamicMap<Cache<?, ?>> cacheMap,
+ @Nullable IsFirstInsertForEntry isFirstInsertForEntry,
+ @Nullable BuildBloomFilter buildBloomFilter) {
super(memCacheFactory, cfg, site);
h2CacheSize = cfg.getLong("cache", null, "h2CacheSize", -1);
h2AutoServer = cfg.getBoolean("cache", null, "h2AutoServer", false);
caches = new ArrayList<>();
this.cacheMap = cacheMap;
+ this.isOfflineReindex =
+ isFirstInsertForEntry != null && isFirstInsertForEntry.equals(IsFirstInsertForEntry.YES);
+ this.buildBloomFilter =
+ !(buildBloomFilter != null && buildBloomFilter.equals(BuildBloomFilter.FALSE));
if (diskEnabled) {
executor =
new LoggingContextAwareExecutorService(
Executors.newFixedThreadPool(
1, new ThreadFactoryBuilder().setNameFormat("DiskCache-Store-%d").build()));
+
cleanup =
- new LoggingContextAwareScheduledExecutorService(
- Executors.newScheduledThreadPool(
- 1,
- new ThreadFactoryBuilder()
- .setNameFormat("DiskCache-Prune-%d")
- .setDaemon(true)
- .build()));
+ isOfflineReindex
+ ? null
+ : new LoggingContextAwareScheduledExecutorService(
+ Executors.newScheduledThreadPool(
+ 1,
+ new ThreadFactoryBuilder()
+ .setNameFormat("DiskCache-Prune-%d")
+ .setDaemon(true)
+ .build()));
} else {
executor = null;
cleanup = null;
@@ -94,9 +113,11 @@
if (executor != null) {
for (H2CacheImpl<?, ?> cache : caches) {
executor.execute(cache::start);
- @SuppressWarnings("unused")
- Future<?> possiblyIgnoredError =
- cleanup.schedule(() -> cache.prune(cleanup), 30, TimeUnit.SECONDS);
+ if (cleanup != null) {
+ @SuppressWarnings("unused")
+ Future<?> possiblyIgnoredError =
+ cleanup.schedule(() -> cache.prune(cleanup), 30, TimeUnit.SECONDS);
+ }
}
}
}
@@ -105,7 +126,9 @@
public void stop() {
if (executor != null) {
try {
- cleanup.shutdownNow();
+ if (cleanup != null) {
+ cleanup.shutdownNow();
+ }
List<Runnable> pending = executor.shutdownNow();
if (executor.awaitTermination(15, TimeUnit.MINUTES)) {
@@ -183,6 +206,22 @@
if (h2AutoServer) {
url.append(";AUTO_SERVER=TRUE");
}
+ Duration refreshAfterWrite = def.refreshAfterWrite();
+ if (has(def.configKey(), "refreshAfterWrite")) {
+ long refreshAfterWriteInSec =
+ ConfigUtil.getTimeUnit(config, "cache", def.configKey(), "refreshAfterWrite", 0, SECONDS);
+ if (refreshAfterWriteInSec != 0) {
+ refreshAfterWrite = Duration.ofSeconds(refreshAfterWriteInSec);
+ }
+ }
+ Duration expireAfterWrite = def.expireAfterWrite();
+ if (has(def.configKey(), "maxAge")) {
+ long expireAfterWriteInsec =
+ ConfigUtil.getTimeUnit(config, "cache", def.configKey(), "maxAge", 0, SECONDS);
+ if (expireAfterWriteInsec != 0) {
+ expireAfterWrite = Duration.ofSeconds(expireAfterWriteInsec);
+ }
+ }
return new SqlStore<>(
url.toString(),
def.keyType(),
@@ -190,7 +229,12 @@
def.valueSerializer(),
def.version(),
maxSize,
- def.expireAfterWrite(),
- def.expireFromMemoryAfterAccess());
+ expireAfterWrite,
+ refreshAfterWrite,
+ buildBloomFilter);
+ }
+
+ private boolean has(String name, String var) {
+ return !Strings.isNullOrEmpty(config.getString("cache", name, var));
}
}
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
index 8327b88..29bf0e6 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -365,6 +365,7 @@
private final AtomicLong missCount = new AtomicLong();
private volatile BloomFilter<K> bloomFilter;
private int estimatedSize;
+ private boolean buildBloomFilter;
SqlStore(
String jdbcUrl,
@@ -374,7 +375,8 @@
int version,
long maxSize,
@Nullable Duration expireAfterWrite,
- @Nullable Duration refreshAfterWrite) {
+ @Nullable Duration refreshAfterWrite,
+ boolean buildBloomFilter) {
this.url = jdbcUrl;
this.keyType = createKeyType(keyType, keySerializer);
this.valueSerializer = valueSerializer;
@@ -382,6 +384,7 @@
this.maxSize = maxSize;
this.expireAfterWrite = expireAfterWrite;
this.refreshAfterWrite = refreshAfterWrite;
+ this.buildBloomFilter = buildBloomFilter;
int cores = Runtime.getRuntime().availableProcessors();
int keep = Math.min(cores, 16);
@@ -398,7 +401,7 @@
}
synchronized void open() {
- if (bloomFilter == null) {
+ if (buildBloomFilter && bloomFilter == null) {
bloomFilter = buildBloomFilter();
}
}
@@ -412,7 +415,7 @@
boolean mightContain(K key) {
BloomFilter<K> b = bloomFilter;
- if (b == null) {
+ if (buildBloomFilter && b == null) {
synchronized (this) {
b = bloomFilter;
if (b == null) {
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index 716295f..f32b2eb 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -693,20 +693,20 @@
}
return Streams.concat(
reviewerInputs.stream(),
- Streams.stream(
- newReviewerInputFromCommitIdentity(
- change,
- patchSetInfo.getCommitId(),
- patchSetInfo.getAuthor().getAccount(),
- NotifyHandling.NONE,
- change.getOwner())),
- Streams.stream(
- newReviewerInputFromCommitIdentity(
- change,
- patchSetInfo.getCommitId(),
- patchSetInfo.getCommitter().getAccount(),
- NotifyHandling.NONE,
- change.getOwner())))
+ newReviewerInputFromCommitIdentity(
+ change,
+ patchSetInfo.getCommitId(),
+ patchSetInfo.getAuthor().getAccount(),
+ NotifyHandling.NONE,
+ change.getOwner())
+ .stream(),
+ newReviewerInputFromCommitIdentity(
+ change,
+ patchSetInfo.getCommitId(),
+ patchSetInfo.getCommitter().getAccount(),
+ NotifyHandling.NONE,
+ change.getOwner())
+ .stream())
.collect(toImmutableList());
}
}
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 0b3b986..4ab9d5a 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -104,8 +104,6 @@
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.TrackingFooters;
-import com.google.gerrit.server.experiments.ExperimentFeatures;
-import com.google.gerrit.server.experiments.ExperimentFeaturesConstants;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -250,13 +248,11 @@
private AccountLoader accountLoader;
private FixInput fix;
- private ExperimentFeatures experimentFeatures;
@Inject
ChangeJson(
GitRepositoryManager repoManager,
AllUsersName allUsers,
- ExperimentFeatures experimentFeatures,
Provider<CurrentUser> user,
PermissionBackend permissionBackend,
ChangeData.Factory cdf,
@@ -276,7 +272,6 @@
@Assisted Optional<PluginDefinedInfosFactory> pluginDefinedInfosFactory) {
this.repoManager = repoManager;
this.allUsers = allUsers;
- this.experimentFeatures = experimentFeatures;
this.userProvider = user;
this.changeDataFactory = cdf;
this.permissionBackend = permissionBackend;
@@ -441,18 +436,12 @@
return info;
}
- private static void finish(ChangeInfo info, ExperimentFeatures experimentFeatures) {
+ private static void finish(ChangeInfo info) {
info.tripletId =
Joiner.on('~')
.join(Url.encode(info.project), Url.encode(info.branch), Url.encode(info.changeId));
- if (experimentFeatures.isFeatureEnabled(
- ExperimentFeaturesConstants.GERRIT_BACKEND_FEATURE_RETURN_NEW_CHANGE_INFO_ID,
- Project.nameKey(info.project))) {
- info.id =
- Joiner.on('~').join(Url.encode(info.project), Url.encode(String.valueOf(info._number)));
- } else {
- info.id = info.tripletId;
- }
+ info.id =
+ Joiner.on('~').join(Url.encode(info.project), Url.encode(String.valueOf(info._number)));
}
private static boolean containsAnyOf(
@@ -529,6 +518,13 @@
// (2) Reusing
boolean isCacheable = cacheQueryResultsByChangeNum && (i != changes.size() - 1);
ChangeData cd = changes.get(i);
+ if (cd.hasFailedParsingFromIndex()) {
+ Optional<ChangeInfo> faultyChangeInfo = createFaultyChangeInfo(cd);
+ if (faultyChangeInfo.isPresent()) {
+ changeInfos.add(faultyChangeInfo.get());
+ }
+ continue;
+ }
ChangeInfo info = cache.get(cd.getId());
if (info != null && isCacheable) {
changeInfos.add(info);
@@ -593,7 +589,7 @@
info.isPrivate = c.isPrivate() ? true : null;
info.workInProgress = c.isWorkInProgress() ? true : null;
info.hasReviewStarted = c.hasReviewStarted();
- finish(info, experimentFeatures);
+ finish(info);
} else {
info._number = result.id().get();
info.problems = result.problems();
@@ -758,20 +754,28 @@
if (needMessages) {
out.messages = messages(cd);
}
- finish(out, experimentFeatures);
+ finish(out);
// This block must come after the ChangeInfo is mostly populated, since
// it will be passed to ActionVisitors as-is.
if (needRevisions) {
out.revisions = revisionJson.getRevisions(accountLoader, cd, src, limitToPsId, out);
- if (out.revisions != null) {
- for (Map.Entry<String, RevisionInfo> entry : out.revisions.entrySet()) {
- if (entry.getValue().isCurrent) {
- out.currentRevision = entry.getKey();
- break;
- }
+ for (Map.Entry<String, RevisionInfo> entry : out.revisions.entrySet()) {
+ if (entry.getValue().isCurrent) {
+ out.currentRevision = entry.getKey();
+ break;
}
}
+ if (out.currentRevision == null) {
+ logger.atSevere().log(
+ "current revision for change %s not found"
+ + "(current patch set ID = %s, patch sets = %s, meta revision = %s)",
+ cd.getId(),
+ cd.change().currentPatchSetId(),
+ src.entrySet().stream()
+ .collect(toImmutableMap(Map.Entry::getKey, e -> e.getValue().commitId().name())),
+ getMetaRevisionIfAvailable(cd));
+ }
}
if (has(CURRENT_ACTIONS) || has(CHANGE_ACTIONS)) {
@@ -789,6 +793,14 @@
return out;
}
+ private Optional<ObjectId> getMetaRevisionIfAvailable(ChangeData cd) {
+ try {
+ return Optional.of(cd.metaRevisionOrThrow());
+ } catch (Exception e) {
+ return Optional.empty();
+ }
+ }
+
private Map<ReviewerState, Collection<AccountInfo>> reviewerMap(
ReviewerSet reviewers, ReviewerByEmailSet reviewersByEmail, boolean includeRemoved) {
Map<ReviewerState, Collection<AccountInfo>> reviewerMap = new HashMap<>();
@@ -1001,4 +1013,25 @@
}
return ImmutableListMultimap.of();
}
+
+ /**
+ * Create an empty {@link ChangeInfo} designating a faulty record if {@link
+ * ChangeData#hasFailedParsingFromIndex()} is true.
+ *
+ * <p>Few fields are populated: project, branch, changeId, _number, subject, owner.
+ */
+ private static Optional<ChangeInfo> createFaultyChangeInfo(ChangeData cd) {
+ ChangeInfo info = new ChangeInfo();
+ Change c = cd.change();
+ if (c == null) {
+ return Optional.empty();
+ }
+ info.project = c.getProject().get();
+ info.branch = c.getDest().shortName();
+ info.changeId = c.getKey().get();
+ info._number = c.getId().get();
+ info.subject = "***ERROR***";
+ info.owner = new AccountInfo(c.getOwner().get());
+ return Optional.of(info);
+ }
}
diff --git a/java/com/google/gerrit/server/change/RebaseChangeOp.java b/java/com/google/gerrit/server/change/RebaseChangeOp.java
index f46196f..de3b7d5 100644
--- a/java/com/google/gerrit/server/change/RebaseChangeOp.java
+++ b/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -400,6 +400,10 @@
return rebasedCommit;
}
+ public PatchSet getOriginalPatchSet() {
+ return originalPatchSet;
+ }
+
public PatchSet.Id getPatchSetId() {
checkState(rebasedPatchSetId != null, "getPatchSetId() only valid after updateRepo");
return rebasedPatchSetId;
diff --git a/java/com/google/gerrit/server/change/RebaseUtil.java b/java/com/google/gerrit/server/change/RebaseUtil.java
index 48b052f..47a1e11 100644
--- a/java/com/google/gerrit/server/change/RebaseUtil.java
+++ b/java/com/google/gerrit/server/change/RebaseUtil.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.change;
import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.Nullable;
@@ -38,6 +39,7 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.ChangePermission;
+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.RefPermission;
@@ -45,6 +47,7 @@
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.update.BatchUpdate;
+import com.google.gerrit.server.util.time.TimeUtil;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.IOException;
@@ -62,39 +65,38 @@
private final Provider<PersonIdent> serverIdent;
private final IdentifiedUser.GenericFactory userFactory;
private final PermissionBackend permissionBackend;
- private final ChangeResource.Factory changeResourceFactory;
private final GitRepositoryManager repoManager;
private final Provider<InternalChangeQuery> queryProvider;
private final ChangeNotes.Factory notesFactory;
private final PatchSetUtil psUtil;
private final RebaseChangeOp.Factory rebaseFactory;
+ private final Provider<CurrentUser> self;
@Inject
RebaseUtil(
@GerritPersonIdent Provider<PersonIdent> serverIdent,
IdentifiedUser.GenericFactory userFactory,
PermissionBackend permissionBackend,
- ChangeResource.Factory changeResourceFactory,
GitRepositoryManager repoManager,
Provider<InternalChangeQuery> queryProvider,
ChangeNotes.Factory notesFactory,
PatchSetUtil psUtil,
- RebaseChangeOp.Factory rebaseFactory) {
+ RebaseChangeOp.Factory rebaseFactory,
+ Provider<CurrentUser> self) {
this.serverIdent = serverIdent;
this.userFactory = userFactory;
this.permissionBackend = permissionBackend;
- this.changeResourceFactory = changeResourceFactory;
this.repoManager = repoManager;
this.queryProvider = queryProvider;
this.notesFactory = notesFactory;
this.psUtil = psUtil;
this.rebaseFactory = rebaseFactory;
+ this.self = self;
}
/**
- * Checks that the uploader has permissions to create a new patch set and creates a new {@link
- * RevisionResource} that contains the uploader (aka the impersonated user) as the current user
- * which can be used for {@link BatchUpdate} to do the rebase on behalf of the uploader.
+ * Checks that the uploader has permissions to create a new patch set as the current user which
+ * can be used for {@link BatchUpdate} to do the rebase on behalf of the uploader.
*
* <p>The following permissions are required for the uploader:
*
@@ -137,10 +139,8 @@
*
* @param rsrc the revision resource that should be rebased
* @param rebaseInput the request input containing options for the rebase
- * @return revision resource that contains the uploader (aka the impersonated user) as the current
- * user which can be used for {@link BatchUpdate} to do the rebase on behalf of the uploader
*/
- public RevisionResource onBehalfOf(RevisionResource rsrc, RebaseInput rebaseInput)
+ public void checkCanRebaseOnBehalfOf(RevisionResource rsrc, RebaseInput rebaseInput)
throws IOException, PermissionBackendException, BadRequestException,
ResourceConflictException {
if (rebaseInput.allowConflicts) {
@@ -208,9 +208,6 @@
}
}
}
-
- return new RevisionResource(
- changeResourceFactory.create(rsrc.getNotes(), uploader), rsrc.getPatchSet());
}
private void checkPermissionForUploader(
@@ -538,23 +535,77 @@
return baseId;
}
- public RebaseChangeOp getRebaseOp(RevisionResource revRsrc, RebaseInput input, ObjectId baseRev) {
+ public RebaseChangeOp getRebaseOp(
+ RevWalk rw,
+ RevisionResource revRsrc,
+ RebaseInput input,
+ ObjectId baseRev,
+ IdentifiedUser rebaseAsUser)
+ throws ResourceConflictException, PermissionBackendException, IOException {
return applyRebaseInputToOp(
- rebaseFactory.create(revRsrc.getNotes(), revRsrc.getPatchSet(), baseRev), input);
+ rw,
+ rebaseFactory.create(revRsrc.getNotes(), revRsrc.getPatchSet(), baseRev),
+ input,
+ rebaseAsUser);
}
public RebaseChangeOp getRebaseOp(
- RevisionResource revRsrc, RebaseInput input, Change.Id baseChange) {
+ RevWalk rw,
+ RevisionResource revRsrc,
+ RebaseInput input,
+ Change.Id baseChange,
+ IdentifiedUser rebaseAsUser)
+ throws ResourceConflictException, PermissionBackendException, IOException {
return applyRebaseInputToOp(
- rebaseFactory.create(revRsrc.getNotes(), revRsrc.getPatchSet(), baseChange), input);
+ rw,
+ rebaseFactory.create(revRsrc.getNotes(), revRsrc.getPatchSet(), baseChange),
+ input,
+ rebaseAsUser);
}
- private RebaseChangeOp applyRebaseInputToOp(RebaseChangeOp op, RebaseInput input) {
- return op.setForceContentMerge(true)
- .setAllowConflicts(input.allowConflicts)
- .setMergeStrategy(input.strategy)
- .setValidationOptions(
- ValidationOptionsUtil.getValidateOptionsAsMultimap(input.validationOptions))
- .setFireRevisionCreated(true);
+ private RebaseChangeOp applyRebaseInputToOp(
+ RevWalk rw, RebaseChangeOp op, RebaseInput input, IdentifiedUser rebaseAsUser)
+ throws ResourceConflictException, PermissionBackendException, IOException {
+ RebaseChangeOp rebaseChangeOp =
+ op.setForceContentMerge(true)
+ .setAllowConflicts(input.allowConflicts)
+ .setMergeStrategy(input.strategy)
+ .setValidationOptions(
+ ValidationOptionsUtil.getValidateOptionsAsMultimap(input.validationOptions))
+ .setFireRevisionCreated(true);
+
+ String originalPatchSetCommitterEmail =
+ rw.parseCommit(rebaseChangeOp.getOriginalPatchSet().commitId())
+ .getCommitterIdent()
+ .getEmailAddress();
+
+ if (input.committerEmail != null) {
+ if (!self.get().hasSameAccountId(rebaseAsUser)
+ && !input.committerEmail.equals(rebaseAsUser.getAccount().preferredEmail())
+ && !input.committerEmail.equals(originalPatchSetCommitterEmail)
+ && !permissionBackend.currentUser().test(GlobalPermission.VIEW_SECONDARY_EMAILS)) {
+ throw new ResourceConflictException(
+ String.format(
+ "Cannot rebase using committer email '%s'. It can only be done using the "
+ + "preferred email or the committer email of the uploader",
+ input.committerEmail));
+ }
+
+ ImmutableSet<String> emails = rebaseAsUser.getEmailAddresses();
+ if (!emails.contains(input.committerEmail)) {
+ throw new ResourceConflictException(
+ String.format(
+ "Cannot rebase using committer email '%s' as it is not a registered "
+ + "email of the user on whose behalf the rebase operation is performed",
+ input.committerEmail));
+ }
+ rebaseChangeOp.setCommitterIdent(
+ new PersonIdent(
+ rebaseAsUser.getName(),
+ input.committerEmail,
+ TimeUtil.now(),
+ serverIdent.get().getZoneId()));
+ }
+ return rebaseChangeOp;
}
}
diff --git a/java/com/google/gerrit/server/change/RevisionJson.java b/java/com/google/gerrit/server/change/RevisionJson.java
index 7dcec60..2ab7e15 100644
--- a/java/com/google/gerrit/server/change/RevisionJson.java
+++ b/java/com/google/gerrit/server/change/RevisionJson.java
@@ -52,6 +52,10 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.Extension;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Description.Units;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GpgException;
@@ -70,6 +74,7 @@
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.ArrayList;
@@ -91,6 +96,23 @@
RevisionJson create(Iterable<ListChangesOption> options);
}
+ @Singleton
+ private static class Metrics {
+ private final Timer0 parentDataLatency;
+
+ @Inject
+ Metrics(MetricMaker metricMaker) {
+ parentDataLatency =
+ metricMaker.newTimer(
+ "http/server/rest_api/change_json/to_change_info_latency/parent_data_computation",
+ new Description(
+ "Latency for computing parent data information in toRevisionInfo"
+ + " invocations in RevisionJson")
+ .setCumulative()
+ .setUnit(Units.MILLISECONDS));
+ }
+ }
+
private final MergeUtilFactory mergeUtilFactory;
private final IdentifiedUser.GenericFactory userFactory;
private final FileInfoJson fileInfoJson;
@@ -109,6 +131,7 @@
private final GitRepositoryManager repoManager;
private final PermissionBackend permissionBackend;
private final ParentDataProvider parentDataProvider;
+ private final Metrics metrics;
@Inject
RevisionJson(
@@ -129,6 +152,7 @@
GitRepositoryManager repoManager,
PermissionBackend permissionBackend,
ParentDataProvider parentDataProvider,
+ Metrics metrics,
@Assisted Iterable<ListChangesOption> options) {
this.userProvider = userProvider;
this.anonymous = anonymous;
@@ -147,6 +171,7 @@
this.permissionBackend = permissionBackend;
this.repoManager = repoManager;
this.parentDataProvider = parentDataProvider;
+ this.metrics = metrics;
this.options = ImmutableSet.copyOf(options);
}
@@ -339,14 +364,17 @@
c.getId().get());
}
if (has(PARENTS)) {
- String targetBranch =
- in.branch().isPresent() ? in.branch().get() : cd.change().getDest().branch();
- List<ParentCommitData> parentData = new ArrayList<>();
- for (RevCommit parent : commit.getParents()) {
- ParentCommitData p = parentDataProvider.get(project, repo, parent.getId(), targetBranch);
- parentData.add(p);
+ try (Timer0.Context ignored = metrics.parentDataLatency.start()) {
+ String targetBranch =
+ in.branch().isPresent() ? in.branch().get() : cd.change().getDest().branch();
+ List<ParentCommitData> parentData = new ArrayList<>();
+ for (RevCommit parent : commit.getParents()) {
+ ParentCommitData p =
+ parentDataProvider.get(project, repo, parent.getId(), targetBranch);
+ parentData.add(p);
+ }
+ out.parentsData = getParentInfo(parentData);
}
- out.parentsData = getParentInfo(parentData);
}
if (addFooters) {
Ref ref = repo.exactRef(branchName);
diff --git a/java/com/google/gerrit/server/config/ConfigUtil.java b/java/com/google/gerrit/server/config/ConfigUtil.java
index 5d94255..22c3d99 100644
--- a/java/com/google/gerrit/server/config/ConfigUtil.java
+++ b/java/com/google/gerrit/server/config/ConfigUtil.java
@@ -17,6 +17,9 @@
import static java.util.Objects.requireNonNull;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.UsedAt;
+import com.google.gerrit.common.UsedAt.Project;
+import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
@@ -391,6 +394,41 @@
return s;
}
+ /**
+ * Update user config by applying the specified delta
+ *
+ * <p>As opposed to {@link com.google.gerrit.server.config.ConfigUtil#storeSection}, this method
+ * does not unset a variable that are set to default, because it is expected that the input {@code
+ * original} is the raw user config value (does not include the defaults)
+ *
+ * <p>To use this method with the proto config (see {@link
+ * CachedPreferences#asUserPreferencesProto()}), the caller can first convert the proto to a java
+ * class usign one of the {@link UserPreferencesConverter} classes.
+ *
+ * <p>Fields marked with final or transient modifiers are skipped.
+ *
+ * @param original the original current user config
+ * @param updateDelta instance of class with config values that need to be uplied to the original
+ * config
+ */
+ @UsedAt(Project.GOOGLE)
+ public static <T> void updatePreferences(T original, T updateDelta) throws IOException {
+ try {
+ for (Field f : updateDelta.getClass().getDeclaredFields()) {
+ if (skipField(f)) {
+ continue;
+ }
+ f.setAccessible(true);
+ Object c = f.get(updateDelta);
+ if (c != null) {
+ f.set(original, c);
+ }
+ }
+ } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
+ throw new IOException("cannot apply delta the original config", e);
+ }
+ }
+
public static boolean skipField(Field field) {
int modifiers = field.getModifiers();
return Modifier.isFinal(modifiers) || Modifier.isTransient(modifiers);
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 81d48ad..b5048b4 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -174,6 +174,7 @@
import com.google.gerrit.server.notedb.ChangeDraftNotesUpdate;
import com.google.gerrit.server.notedb.DeleteZombieCommentsRefs;
import com.google.gerrit.server.notedb.NoteDbModule;
+import com.google.gerrit.server.notedb.RepoSequence.RepoSequenceModule;
import com.google.gerrit.server.notedb.StoreSubmitRequirementsOp;
import com.google.gerrit.server.patch.DiffFileSizeValidator;
import com.google.gerrit.server.patch.DiffOperationsImpl;
@@ -255,6 +256,7 @@
bind(IdGenerator.class);
bind(BlameCache.class).to(BlameCacheImpl.class);
+ install(new RepoSequenceModule());
install(BatchUpdate.module());
install(ChangeKindCacheImpl.module());
install(ChangeFinder.module());
diff --git a/java/com/google/gerrit/server/config/GerritInstanceIdProvider.java b/java/com/google/gerrit/server/config/GerritInstanceIdProvider.java
index 891ca76..6523f18 100644
--- a/java/com/google/gerrit/server/config/GerritInstanceIdProvider.java
+++ b/java/com/google/gerrit/server/config/GerritInstanceIdProvider.java
@@ -22,11 +22,14 @@
/** Provides {@link GerritInstanceId} from {@code gerrit.instanceId}. */
@Singleton
public class GerritInstanceIdProvider implements Provider<String> {
+ public static final String INSTANCE_ID_SYSTEM_PROPERTY = "gerrit.instanceId";
private final String instanceId;
@Inject
public GerritInstanceIdProvider(@GerritServerConfig Config cfg) {
- instanceId = cfg.getString("gerrit", null, "instanceId");
+ instanceId =
+ System.getProperty(
+ INSTANCE_ID_SYSTEM_PROPERTY, cfg.getString("gerrit", null, "instanceId"));
}
@Override
diff --git a/java/com/google/gerrit/server/config/UserPreferencesConverter.java b/java/com/google/gerrit/server/config/UserPreferencesConverter.java
index bb611ad..4a052d7 100644
--- a/java/com/google/gerrit/server/config/UserPreferencesConverter.java
+++ b/java/com/google/gerrit/server/config/UserPreferencesConverter.java
@@ -31,8 +31,8 @@
* <p>Upstream, we use java representations of the preference classes. Internally, we store proto
* equivalents in Spanner.
*/
-final class UserPreferencesConverter {
- static final class GeneralPreferencesInfoConverter {
+public final class UserPreferencesConverter {
+ public static final class GeneralPreferencesInfoConverter {
public static UserPreferences.GeneralPreferencesInfo toProto(GeneralPreferencesInfo info) {
UserPreferences.GeneralPreferencesInfo.Builder builder =
UserPreferences.GeneralPreferencesInfo.newBuilder();
@@ -111,6 +111,7 @@
builder =
setIfNotNull(
builder, builder::setAllowBrowserNotifications, info.allowBrowserNotifications);
+ builder = setIfNotNull(builder, builder::setDiffPageSidebar, info.diffPageSidebar);
return builder.build();
}
@@ -171,6 +172,7 @@
res.changeTable = proto.getChangeTableCount() != 0 ? proto.getChangeTableList() : null;
res.allowBrowserNotifications =
proto.hasAllowBrowserNotifications() ? proto.getAllowBrowserNotifications() : null;
+ res.diffPageSidebar = proto.hasDiffPageSidebar() ? proto.getDiffPageSidebar() : null;
return res;
}
@@ -197,7 +199,7 @@
private GeneralPreferencesInfoConverter() {}
}
- static final class DiffPreferencesInfoConverter {
+ public static final class DiffPreferencesInfoConverter {
public static UserPreferences.DiffPreferencesInfo toProto(DiffPreferencesInfo info) {
UserPreferences.DiffPreferencesInfo.Builder builder =
UserPreferences.DiffPreferencesInfo.newBuilder();
@@ -272,7 +274,7 @@
private DiffPreferencesInfoConverter() {}
}
- static final class EditPreferencesInfoConverter {
+ public static final class EditPreferencesInfoConverter {
public static UserPreferences.EditPreferencesInfo toProto(EditPreferencesInfo info) {
UserPreferences.EditPreferencesInfo.Builder builder =
UserPreferences.EditPreferencesInfo.newBuilder();
diff --git a/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java b/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java
index bda9f185..32ec401 100644
--- a/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java
+++ b/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java
@@ -25,11 +25,4 @@
/** Features, enabled by default in the current release. */
public static final ImmutableSet<String> DEFAULT_ENABLED_FEATURES = ImmutableSet.of();
-
- /**
- * Sets ChangeInfo.id to "'<project>~<_number>'", instead of "'<project>~<branch>~<Change-Id>'",
- * spearing an index lookup if the id is used in the follow-up API calls.
- */
- public static String GERRIT_BACKEND_FEATURE_RETURN_NEW_CHANGE_INFO_ID =
- "GerritBackendFeature__return_new_change_info_id";
}
diff --git a/java/com/google/gerrit/server/git/ChangesByProjectCache.java b/java/com/google/gerrit/server/git/ChangesByProjectCache.java
new file mode 100644
index 0000000..df91891
--- /dev/null
+++ b/java/com/google/gerrit/server/git/ChangesByProjectCache.java
@@ -0,0 +1,63 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.inject.AbstractModule;
+import java.io.IOException;
+import java.util.stream.Stream;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+
+public interface ChangesByProjectCache {
+ public enum UseIndex {
+ TRUE,
+ FALSE;
+ }
+
+ public static class Module extends AbstractModule {
+ private UseIndex useIndex;
+ private @GerritServerConfig Config config;
+
+ public Module(UseIndex useIndex, @GerritServerConfig Config config) {
+ this.useIndex = useIndex;
+ this.config = config;
+ }
+
+ @Override
+ protected void configure() {
+ boolean searchingCacheEnabled =
+ config.getLong("cache", SearchingChangeCacheImpl.ID_CACHE, "memoryLimit", 0) > 0;
+ if (searchingCacheEnabled && UseIndex.TRUE.equals(useIndex)) {
+ install(new SearchingChangeCacheImpl.SearchingChangeCacheImplModule());
+ } else {
+ bind(UseIndex.class).toInstance(useIndex);
+ install(new ChangesByProjectCacheImpl.Module());
+ }
+ }
+ }
+
+ /**
+ * Stream changeDatas for the project
+ *
+ * @param project project to read.
+ * @param repository repository for the project to read.
+ * @return Stream of known changes; empty if no changes.
+ */
+ Stream<ChangeData> streamChangeDatas(Project.NameKey project, Repository repository)
+ throws IOException;
+}
diff --git a/java/com/google/gerrit/server/git/ChangesByProjectCacheImpl.java b/java/com/google/gerrit/server/git/ChangesByProjectCacheImpl.java
new file mode 100644
index 0000000..094287b
--- /dev/null
+++ b/java/com/google/gerrit/server/git/ChangesByProjectCacheImpl.java
@@ -0,0 +1,360 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.cache.Cache;
+import com.google.common.cache.Weigher;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.ReviewerSet;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.git.ChangesByProjectCache.UseIndex;
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gerrit.server.logging.Metadata;
+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.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Stream;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Lightweight cache of changes in each project.
+ *
+ * <p>This cache is intended to be used when filtering references and stores only the minimal fields
+ * required for a read permission check.
+ */
+@Singleton
+public class ChangesByProjectCacheImpl implements ChangesByProjectCache {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private static final String CACHE_NAME = "changes_by_project";
+
+ public static class Module extends CacheModule {
+ @Override
+ protected void configure() {
+ cache(CACHE_NAME, Project.NameKey.class, CachedProjectChanges.class)
+ .weigher(ChangesByProjetCacheWeigher.class);
+ bind(ChangesByProjectCache.class).to(ChangesByProjectCacheImpl.class);
+ }
+ }
+
+ private final Cache<Project.NameKey, CachedProjectChanges> cache;
+ private final ChangeData.Factory cdFactory;
+ private final UseIndex useIndex;
+ private final Provider<InternalChangeQuery> queryProvider;
+
+ @Inject
+ ChangesByProjectCacheImpl(
+ @Named(CACHE_NAME) Cache<Project.NameKey, CachedProjectChanges> cache,
+ ChangeData.Factory cdFactory,
+ UseIndex useIndex,
+ Provider<InternalChangeQuery> queryProvider) {
+ this.cache = cache;
+ this.cdFactory = cdFactory;
+ this.useIndex = useIndex;
+ this.queryProvider = queryProvider;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Stream<ChangeData> streamChangeDatas(Project.NameKey project, Repository repo)
+ throws IOException {
+ CachedProjectChanges projectChanges = cache.getIfPresent(project);
+ if (projectChanges != null) {
+ return projectChanges
+ .getUpdatedChangeDatas(
+ project, repo, cdFactory, ChangeNotes.Factory.scanChangeIds(repo), "Updating")
+ .stream();
+ }
+ if (UseIndex.TRUE.equals(useIndex)) {
+ return queryChangeDatasAndLoad(project).stream();
+ }
+ return scanChangeDatasAndLoad(project, repo).stream();
+ }
+
+ private Collection<ChangeData> scanChangeDatasAndLoad(Project.NameKey project, Repository repo)
+ throws IOException {
+ CachedProjectChanges ours = new CachedProjectChanges();
+ CachedProjectChanges projectChanges = ours;
+ try {
+ projectChanges = cache.get(project, () -> ours);
+ } catch (ExecutionException e) {
+ logger.atWarning().withCause(e).log("Cannot load %s for %s", CACHE_NAME, project.get());
+ }
+ return projectChanges.getUpdatedChangeDatas(
+ project,
+ repo,
+ cdFactory,
+ ChangeNotes.Factory.scanChangeIds(repo),
+ ours == projectChanges ? "Scanning" : "Updating");
+ }
+
+ private Collection<ChangeData> queryChangeDatasAndLoad(Project.NameKey project) {
+ Collection<ChangeData> cds = queryChangeDatas(project);
+ cache.put(project, new CachedProjectChanges(cds));
+ return cds;
+ }
+
+ private Collection<ChangeData> queryChangeDatas(Project.NameKey project) {
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ "Querying changes of project", Metadata.builder().projectName(project.get()).build())) {
+ return queryProvider
+ .get()
+ .setRequestedFields(
+ ChangeField.CHANGE_SPEC, ChangeField.REVIEWER_SPEC, ChangeField.REF_STATE_SPEC)
+ .byProject(project);
+ }
+ }
+
+ private static class CachedProjectChanges {
+ Map<String, Map<Change.Id, ObjectId>> metaObjectIdByNonPrivateChangeByBranch =
+ new ConcurrentHashMap<>(); // BranchNameKey "normalized" to a String to dedup project
+ Map<Change.Id, PrivateChange> privateChangeById = new ConcurrentHashMap<>();
+
+ public CachedProjectChanges() {}
+
+ public CachedProjectChanges(Collection<ChangeData> cds) {
+ cds.stream().forEach(cd -> insert(cd));
+ }
+
+ public Collection<ChangeData> getUpdatedChangeDatas(
+ Project.NameKey project,
+ Repository repo,
+ ChangeData.Factory cdFactory,
+ Map<Change.Id, ObjectId> metaObjectIdByChange,
+ String operation) {
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ operation + " changes of project",
+ Metadata.builder().projectName(project.get()).build())) {
+ Map<Change.Id, ChangeData> cachedCdByChange = getChangeDataByChange(project, cdFactory);
+ List<ChangeData> cds = new ArrayList<>();
+ for (Map.Entry<Change.Id, ObjectId> e : metaObjectIdByChange.entrySet()) {
+ Change.Id id = e.getKey();
+ ChangeData cached = cachedCdByChange.get(id);
+ ChangeData cd = cached;
+ try {
+ if (cd == null || !cached.metaRevisionOrThrow().equals(e.getValue())) {
+ cd = cdFactory.create(project, id);
+ update(cached, cd);
+ }
+ } catch (Exception ex) {
+ // Do not let a bad change prevent other changes from being available.
+ logger.atFinest().withCause(ex).log("Can't load changeData for %s", id);
+ }
+ cds.add(cd);
+ }
+ return cds;
+ }
+ }
+
+ public CachedProjectChanges update(ChangeData old, ChangeData updated) {
+ if (old != null) {
+ if (old.isPrivateOrThrow()) {
+ privateChangeById.remove(old.getId());
+ } else {
+ Map<Change.Id, ObjectId> metaObjectIdByNonPrivateChange =
+ metaObjectIdByNonPrivateChangeByBranch.get(old.branchOrThrow().branch());
+ if (metaObjectIdByNonPrivateChange != null) {
+ metaObjectIdByNonPrivateChange.remove(old.getId());
+ }
+ }
+ }
+ return insert(updated);
+ }
+
+ public CachedProjectChanges insert(ChangeData cd) {
+ if (cd.isPrivateOrThrow()) {
+ privateChangeById.put(
+ cd.getId(),
+ new AutoValue_ChangesByProjectCacheImpl_PrivateChange(
+ cd.change(), cd.reviewers(), cd.metaRevisionOrThrow()));
+ } else {
+ metaObjectIdByNonPrivateChangeByBranch
+ .computeIfAbsent(cd.branchOrThrow().branch(), b -> new ConcurrentHashMap<>())
+ .put(cd.getId(), cd.metaRevisionOrThrow());
+ }
+ return this;
+ }
+
+ public Map<Change.Id, ChangeData> getChangeDataByChange(
+ Project.NameKey project, ChangeData.Factory cdFactory) {
+ Map<Change.Id, ChangeData> cdByChange = new HashMap<>(privateChangeById.size());
+ for (PrivateChange pc : privateChangeById.values()) {
+ try {
+ ChangeData cd = cdFactory.create(pc.change());
+ cd.setReviewers(pc.reviewers());
+ cd.setMetaRevision(pc.metaRevision());
+ cdByChange.put(cd.getId(), cd);
+ } catch (Exception ex) {
+ // Do not let a bad change prevent other changes from being available.
+ logger.atFinest().withCause(ex).log("Can't load changeData for %s", pc.change().getId());
+ }
+ }
+
+ for (Map.Entry<String, Map<Change.Id, ObjectId>> e :
+ metaObjectIdByNonPrivateChangeByBranch.entrySet()) {
+ BranchNameKey branch = BranchNameKey.create(project, e.getKey());
+ for (Map.Entry<Change.Id, ObjectId> e2 : e.getValue().entrySet()) {
+ Change.Id id = e2.getKey();
+ try {
+ cdByChange.put(id, cdFactory.createNonPrivate(branch, id, e2.getValue()));
+ } catch (Exception ex) {
+ // Do not let a bad change prevent other changes from being available.
+ logger.atFinest().withCause(ex).log("Can't load changeData for %s", id);
+ }
+ }
+ }
+ return cdByChange;
+ }
+
+ public int weigh() {
+ int size = 0;
+ size += 24 * 2; // guess at basic ConcurrentHashMap overhead * 2
+ for (Map.Entry<String, Map<Change.Id, ObjectId>> e :
+ metaObjectIdByNonPrivateChangeByBranch.entrySet()) {
+ size += JavaWeights.REFERENCE + e.getKey().length();
+ size +=
+ e.getValue().size()
+ * (JavaWeights.REFERENCE
+ + JavaWeights.OBJECT // Map.Entry
+ + JavaWeights.REFERENCE
+ + GerritWeights.CHANGE_NUM
+ + JavaWeights.REFERENCE
+ + GerritWeights.OBJECTID);
+ }
+ for (Map.Entry<Change.Id, PrivateChange> e : privateChangeById.entrySet()) {
+ size += JavaWeights.REFERENCE + GerritWeights.CHANGE_NUM;
+ size += JavaWeights.REFERENCE + e.getValue().weigh();
+ }
+ return size;
+ }
+ }
+
+ @AutoValue
+ abstract static class PrivateChange {
+ // Fields needed to serve permission checks on private Changes
+ abstract Change change();
+
+ @Nullable
+ abstract ReviewerSet reviewers();
+
+ abstract ObjectId metaRevision(); // Needed to confirm whether up-to-date
+
+ public int weigh() {
+ int size = 0;
+ size += JavaWeights.OBJECT; // this
+ size += JavaWeights.REFERENCE + weigh(change());
+ size += JavaWeights.REFERENCE + weigh(reviewers());
+ size += JavaWeights.REFERENCE + GerritWeights.OBJECTID; // metaRevision
+ return size;
+ }
+
+ private static int weigh(Change c) {
+ int size = 0;
+ size += JavaWeights.OBJECT; // change
+ size += JavaWeights.REFERENCE + GerritWeights.KEY_INT; // changeId
+ size += JavaWeights.REFERENCE + JavaWeights.OBJECT + 40; // changeKey;
+ size += JavaWeights.REFERENCE + GerritWeights.TIMESTAMP; // createdOn;
+ size += JavaWeights.REFERENCE + GerritWeights.TIMESTAMP; // lastUpdatedOn;
+ size += JavaWeights.REFERENCE + GerritWeights.ACCOUNT_ID; // owner;
+ size +=
+ JavaWeights.REFERENCE
+ + c.getDest().project().get().length()
+ + c.getDest().branch().length();
+ size += JavaWeights.CHAR; // status;
+ size += JavaWeights.INT; // currentPatchSetId;
+ size += JavaWeights.REFERENCE + c.getSubject().length();
+ size += JavaWeights.REFERENCE + (c.getTopic() == null ? 0 : c.getTopic().length());
+ size +=
+ JavaWeights.REFERENCE
+ + (c.getOriginalSubject().equals(c.getSubject()) ? 0 : c.getSubject().length());
+ size +=
+ JavaWeights.REFERENCE + (c.getSubmissionId() == null ? 0 : c.getSubmissionId().length());
+ size += JavaWeights.REFERENCE + GerritWeights.ACCOUNT_ID; // assignee;
+ size += JavaWeights.REFERENCE + JavaWeights.BOOLEAN; // isPrivate;
+ size += JavaWeights.REFERENCE + JavaWeights.BOOLEAN; // workInProgress;
+ size += JavaWeights.REFERENCE + JavaWeights.BOOLEAN; // reviewStarted;
+ size += JavaWeights.REFERENCE + GerritWeights.CHANGE_NUM; // revertOf;
+ size += JavaWeights.REFERENCE + GerritWeights.PACTCH_SET_ID; // cherryPickOf;
+ return size;
+ }
+
+ private static int weigh(ReviewerSet rs) {
+ int size = 0;
+ size += JavaWeights.OBJECT; // ReviewerSet
+ size += JavaWeights.REFERENCE; // table
+ size +=
+ rs.asTable().cellSet().size()
+ * (JavaWeights.OBJECT // cell (at least one object)
+ + JavaWeights.REFERENCE // ReviewerStateInternal
+ + (JavaWeights.REFERENCE + GerritWeights.ACCOUNT_ID)
+ + (JavaWeights.REFERENCE + GerritWeights.TIMESTAMP));
+ size += JavaWeights.REFERENCE; // accounts
+ return size;
+ }
+ }
+
+ private static class ChangesByProjetCacheWeigher
+ implements Weigher<Project.NameKey, CachedProjectChanges> {
+ @Override
+ public int weigh(Project.NameKey project, CachedProjectChanges changes) {
+ int size = 0;
+ size += project.get().length();
+ size += changes.weigh();
+ return size;
+ }
+ }
+
+ private static class GerritWeights {
+ public static final int KEY_INT = JavaWeights.OBJECT + JavaWeights.INT; // IntKey
+ public static final int CHANGE_NUM = KEY_INT;
+ public static final int ACCOUNT_ID = KEY_INT;
+ public static final int PACTCH_SET_ID =
+ JavaWeights.OBJECT
+ + (JavaWeights.REFERENCE + GerritWeights.CHANGE_NUM) // PatchSet.Id.changeId
+ + JavaWeights.INT; // PatchSet.Id patch_num;
+ public static final int TIMESTAMP = JavaWeights.OBJECT + 8; // Timestamp
+ public static final int OBJECTID = JavaWeights.OBJECT + (5 * JavaWeights.INT); // (w1-w5)
+ }
+
+ private static class JavaWeights {
+ public static final int BOOLEAN = 1;
+ public static final int CHAR = 1;
+ public static final int INT = 4;
+ public static final int OBJECT = 16;
+ public static final int REFERENCE = 8;
+ }
+}
diff --git a/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java b/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
index cfeec70..83024e3 100644
--- a/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
+++ b/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
@@ -40,12 +40,12 @@
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
-import com.google.inject.util.Providers;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
+import org.eclipse.jgit.lib.Repository;
/**
* Cache based on an index query of the most recent changes. The number of cached items depends on
@@ -55,35 +55,22 @@
* fraction of all changes. These are the changes that were modified last.
*/
@Singleton
-public class SearchingChangeCacheImpl implements GitReferenceUpdatedListener {
+public class SearchingChangeCacheImpl
+ implements ChangesByProjectCache, GitReferenceUpdatedListener {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
static final String ID_CACHE = "changes";
public static class SearchingChangeCacheImplModule extends CacheModule {
- private final boolean slave;
-
- public SearchingChangeCacheImplModule() {
- this(false);
- }
-
- public SearchingChangeCacheImplModule(boolean slave) {
- this.slave = slave;
- }
-
@Override
protected void configure() {
- if (slave) {
- bind(SearchingChangeCacheImpl.class).toProvider(Providers.of(null));
- } else {
- cache(ID_CACHE, Project.NameKey.class, new TypeLiteral<List<CachedChange>>() {})
- .maximumWeight(0)
- .loader(Loader.class);
+ cache(ID_CACHE, Project.NameKey.class, new TypeLiteral<List<CachedChange>>() {})
+ .maximumWeight(0)
+ .loader(Loader.class);
- bind(SearchingChangeCacheImpl.class);
- DynamicSet.bind(binder(), GitReferenceUpdatedListener.class)
- .to(SearchingChangeCacheImpl.class);
- }
+ bind(ChangesByProjectCache.class).to(SearchingChangeCacheImpl.class);
+ DynamicSet.bind(binder(), GitReferenceUpdatedListener.class)
+ .to(SearchingChangeCacheImpl.class);
}
}
@@ -117,9 +104,11 @@
* Additional stored fields are not loaded from the index.
*
* @param project project to read.
+ * @param unusedrepo repository for the project to read.
* @return stream of known changes; empty if no changes.
*/
- public Stream<ChangeData> getChangeData(Project.NameKey project) {
+ @Override
+ public Stream<ChangeData> streamChangeDatas(Project.NameKey project, Repository unusedrepo) {
List<CachedChange> cached;
try {
cached = cache.get(project);
diff --git a/java/com/google/gerrit/server/git/receive/ReplaceOp.java b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
index 7cc843b..254e57b 100644
--- a/java/com/google/gerrit/server/git/receive/ReplaceOp.java
+++ b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
@@ -373,20 +373,20 @@
// bulk new change email.
Stream<ReviewerInput> inputs =
Streams.concat(
- Streams.stream(
- newReviewerInputFromCommitIdentity(
- change,
- psInfo.getCommitId(),
- psInfo.getAuthor().getAccount(),
- NotifyHandling.NONE,
- newPatchSet.uploader())),
- Streams.stream(
- newReviewerInputFromCommitIdentity(
- change,
- psInfo.getCommitId(),
- psInfo.getCommitter().getAccount(),
- NotifyHandling.NONE,
- newPatchSet.uploader())));
+ newReviewerInputFromCommitIdentity(
+ change,
+ psInfo.getCommitId(),
+ psInfo.getAuthor().getAccount(),
+ NotifyHandling.NONE,
+ newPatchSet.uploader())
+ .stream(),
+ newReviewerInputFromCommitIdentity(
+ change,
+ psInfo.getCommitId(),
+ psInfo.getCommitter().getAccount(),
+ NotifyHandling.NONE,
+ newPatchSet.uploader())
+ .stream());
if (magicBranch != null) {
inputs =
Streams.concat(
diff --git a/java/com/google/gerrit/server/group/db/GroupConfigCommitMessage.java b/java/com/google/gerrit/server/group/db/GroupConfigCommitMessage.java
index 77c284a..6e2f02f 100644
--- a/java/com/google/gerrit/server/group/db/GroupConfigCommitMessage.java
+++ b/java/com/google/gerrit/server/group/db/GroupConfigCommitMessage.java
@@ -57,7 +57,7 @@
StringJoiner footerJoiner = new StringJoiner("\n", "\n\n", "");
footerJoiner.setEmptyValue("");
Streams.concat(
- Streams.stream(getFooterForRename()),
+ getFooterForRename().stream(),
getFootersForMemberModifications(),
getFootersForSubgroupModifications())
.sorted()
diff --git a/java/com/google/gerrit/server/index/IndexModule.java b/java/com/google/gerrit/server/index/IndexModule.java
index fbe18ca..c0bd62f 100644
--- a/java/com/google/gerrit/server/index/IndexModule.java
+++ b/java/com/google/gerrit/server/index/IndexModule.java
@@ -53,6 +53,7 @@
import com.google.gerrit.server.index.group.GroupIndexer;
import com.google.gerrit.server.index.group.GroupIndexerImpl;
import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
+import com.google.gerrit.server.index.options.BuildBloomFilter;
import com.google.gerrit.server.index.options.IsFirstInsertForEntry;
import com.google.gerrit.server.index.project.ProjectIndexDefinition;
import com.google.gerrit.server.index.project.ProjectIndexerImpl;
@@ -155,6 +156,9 @@
OptionalBinder.newOptionalBinder(binder(), IsFirstInsertForEntry.class)
.setDefault()
.toInstance(IsFirstInsertForEntry.NO);
+ OptionalBinder.newOptionalBinder(binder(), BuildBloomFilter.class)
+ .setDefault()
+ .toInstance(BuildBloomFilter.TRUE);
}
@Provides
diff --git a/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java b/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
index c5fa45a..00642a9 100644
--- a/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
+++ b/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
@@ -64,6 +64,24 @@
int pageSizeMultiplier,
int limit,
Set<String> fields) {
+ return createOptions(
+ config,
+ start,
+ pageSize,
+ pageSizeMultiplier,
+ limit,
+ /* allowIncompleteResults= */ false,
+ fields);
+ }
+
+ public static QueryOptions createOptions(
+ IndexConfig config,
+ int start,
+ int pageSize,
+ int pageSizeMultiplier,
+ int limit,
+ boolean allowIncompleteResults,
+ Set<String> fields) {
// Always include project and change id since both are needed to load the change from NoteDb.
if (!fields.contains(CHANGE_SPEC.getName())
&& !(fields.contains(PROJECT_SPEC.getName())
@@ -72,7 +90,8 @@
fields.add(PROJECT_SPEC.getName());
fields.add(NUMERIC_ID_STR_SPEC.getName());
}
- return QueryOptions.create(config, start, pageSize, pageSizeMultiplier, limit, fields);
+ return QueryOptions.create(
+ config, start, pageSize, pageSizeMultiplier, limit, allowIncompleteResults, fields);
}
@VisibleForTesting
@@ -84,6 +103,7 @@
opts.pageSize(),
opts.pageSizeMultiplier(),
opts.limit(),
+ opts.allowIncompleteResults(),
opts.fields());
}
diff --git a/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java b/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
index b52b2d1..f6a4ce1 100644
--- a/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
+++ b/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
@@ -89,18 +89,19 @@
public void onGitBatchRefUpdate(GitBatchRefUpdateListener.Event event) {
if (allUsersName.get().equals(event.getProjectName())) {
for (UpdatedRef ref : event.getUpdatedRefs()) {
- if (!RefNames.REFS_CONFIG.equals(ref.getRefName())) {
- if (ref.getRefName().startsWith(RefNames.REFS_STARRED_CHANGES)) {
- break;
- }
+ if (RefNames.isRefsUsers(ref.getRefName()) && !RefNames.isRefsEdit(ref.getRefName())) {
Account.Id accountId = Account.Id.fromRef(ref.getRefName());
if (accountId != null) {
indexer.get().index(accountId);
}
}
}
- // The update is in All-Users and not on refs/meta/config. So it's not a change. Return early.
- return;
+ if (event.getUpdatedRefs().stream()
+ .noneMatch(ru -> ru.getRefName().equals(RefNames.REFS_CONFIG))) {
+ // The update is in All-Users and not on refs/meta/config. So it's not a change. Return
+ // early.
+ return;
+ }
}
for (UpdatedRef ref : event.getUpdatedRefs()) {
diff --git a/java/com/google/gerrit/server/index/options/BuildBloomFilter.java b/java/com/google/gerrit/server/index/options/BuildBloomFilter.java
new file mode 100644
index 0000000..021f0fe
--- /dev/null
+++ b/java/com/google/gerrit/server/index/options/BuildBloomFilter.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2023 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.index.options;
+
+/** This enum can be used to decide if bloom filters for H2 disk caches should be built. */
+public enum BuildBloomFilter {
+ TRUE,
+ FALSE
+}
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java
index f11e043..0d7c841 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -166,6 +166,12 @@
return new ChangeNotes(args, newChange(project, changeId), true, null).load();
}
+ public ChangeNotes create(
+ Project.NameKey project, Change.Id changeId, @Nullable ObjectId metaRevId) {
+ checkArgument(project != null, "project is required");
+ return new ChangeNotes(args, newChange(project, changeId), true, null, metaRevId).load();
+ }
+
public ChangeNotes create(Repository repository, Project.NameKey project, Change.Id changeId) {
checkArgument(project != null, "project is required");
return new ChangeNotes(args, newChange(project, changeId), true, null).load(repository);
diff --git a/java/com/google/gerrit/server/notedb/RepoSequence.java b/java/com/google/gerrit/server/notedb/RepoSequence.java
index 9aaac19..38ce3a0 100644
--- a/java/com/google/gerrit/server/notedb/RepoSequence.java
+++ b/java/com/google/gerrit/server/notedb/RepoSequence.java
@@ -18,9 +18,7 @@
import static com.google.gerrit.entities.RefNames.REFS;
import static com.google.gerrit.entities.RefNames.REFS_SEQUENCES;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.REPO_SEQ;
-import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
-import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.github.rholder.retry.RetryException;
import com.github.rholder.retry.Retryer;
@@ -36,11 +34,20 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.git.RefUpdateUtil;
+import com.google.gerrit.server.Sequence;
+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.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.update.context.RefUpdateContext;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Provides;
+import com.google.inject.name.Named;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -49,12 +56,11 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand;
/**
* Class for managing an incrementing sequence backed by a git repository.
@@ -66,7 +72,7 @@
* memory until they run out. This means concurrent processes will hand out somewhat non-monotonic
* numbers.
*/
-public class RepoSequence {
+public class RepoSequence implements Sequence {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@FunctionalInterface
@@ -74,6 +80,134 @@
int get();
}
+ public static class RepoSequenceModule extends FactoryModule {
+ private static final String SECTION_NOTE_DB = "noteDb";
+ private static final String KEY_SEQUENCE_BATCH_SIZE = "sequenceBatchSize";
+ private static final int DEFAULT_ACCOUNTS_SEQUENCE_BATCH_SIZE = 1;
+ private static final int DEFAULT_GROUPS_SEQUENCE_BATCH_SIZE = 1;
+ private static final int DEFAULT_CHANGES_SEQUENCE_BATCH_SIZE = 20;
+
+ @Provides
+ @Named(NAME_ACCOUNTS)
+ Sequence getAccountSequence(
+ @GerritServerConfig Config cfg,
+ GitRepositoryManager repoManager,
+ AllUsersName allUsers,
+ GitReferenceUpdated gitReferenceUpdated) {
+ int accountBatchSize =
+ cfg.getInt(
+ SECTION_NOTE_DB,
+ NAME_ACCOUNTS,
+ KEY_SEQUENCE_BATCH_SIZE,
+ DEFAULT_ACCOUNTS_SEQUENCE_BATCH_SIZE);
+ return new RepoSequence(
+ repoManager,
+ gitReferenceUpdated,
+ allUsers,
+ NAME_ACCOUNTS,
+ () -> Sequences.FIRST_ACCOUNT_ID,
+ accountBatchSize);
+ }
+
+ @Provides
+ @Named(NAME_GROUPS)
+ Sequence getGroupSequence(
+ GitRepositoryManager repoManager,
+ AllUsersName allUsers,
+ GitReferenceUpdated gitReferenceUpdated) {
+ return new RepoSequence(
+ repoManager,
+ gitReferenceUpdated,
+ allUsers,
+ NAME_GROUPS,
+ () -> Sequences.FIRST_GROUP_ID,
+ DEFAULT_GROUPS_SEQUENCE_BATCH_SIZE);
+ }
+
+ @Provides
+ @Named(NAME_CHANGES)
+ Sequence getChangesSequence(
+ @GerritServerConfig Config cfg,
+ GitRepositoryManager repoManager,
+ AllProjectsName allProjects,
+ GitReferenceUpdated gitReferenceUpdated) {
+ int changeBatchSize =
+ cfg.getInt(
+ SECTION_NOTE_DB,
+ NAME_CHANGES,
+ KEY_SEQUENCE_BATCH_SIZE,
+ DEFAULT_CHANGES_SEQUENCE_BATCH_SIZE);
+ return new RepoSequence(
+ repoManager,
+ gitReferenceUpdated,
+ allProjects,
+ NAME_CHANGES,
+ () -> Sequences.FIRST_CHANGE_ID,
+ changeBatchSize);
+ }
+ }
+
+ /** A groups sequence provider that does not fire git reference updates. */
+ public static class DisabledGitRefUpdatedRepoGroupsSequenceProvider
+ implements Provider<Sequence> {
+ private static final int DEFAULT_GROUPS_SEQUENCE_BATCH_SIZE = 1;
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsers;
+
+ @Inject
+ DisabledGitRefUpdatedRepoGroupsSequenceProvider(
+ GitRepositoryManager repoManager, AllUsersName allUsersName) {
+ this.repoManager = repoManager;
+ this.allUsers = allUsersName;
+ }
+
+ @Override
+ public Sequence get() {
+ return new RepoSequence(
+ repoManager,
+ GitReferenceUpdated.DISABLED,
+ allUsers,
+ NAME_GROUPS,
+ () -> Sequences.FIRST_GROUP_ID,
+ DEFAULT_GROUPS_SEQUENCE_BATCH_SIZE);
+ }
+ }
+
+ /** A accounts sequence provider that does not fire git reference updates. */
+ public static class DisabledGitRefUpdatedRepoAccountsSequenceProvider
+ implements Provider<Sequence> {
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsers;
+ private final Config cfg;
+
+ @Inject
+ DisabledGitRefUpdatedRepoAccountsSequenceProvider(
+ @GerritServerConfig Config cfg,
+ GitRepositoryManager repoManager,
+ AllUsersName allUsersName) {
+ this.repoManager = repoManager;
+ this.allUsers = allUsersName;
+ this.cfg = cfg;
+ }
+
+ @Override
+ public Sequence get() {
+ int accountBatchSize =
+ cfg.getInt(
+ RepoSequenceModule.SECTION_NOTE_DB,
+ NAME_ACCOUNTS,
+ RepoSequenceModule.KEY_SEQUENCE_BATCH_SIZE,
+ RepoSequenceModule.DEFAULT_ACCOUNTS_SEQUENCE_BATCH_SIZE);
+ return new RepoSequence(
+ repoManager,
+ GitReferenceUpdated.DISABLED,
+ allUsers,
+ NAME_ACCOUNTS,
+ () -> Sequences.FIRST_ACCOUNT_ID,
+ accountBatchSize);
+ }
+ }
+
@VisibleForTesting
static RetryerBuilder<ImmutableList<Integer>> retryerBuilder() {
return RetryerBuilder.<ImmutableList<Integer>>newBuilder()
@@ -127,26 +261,6 @@
0);
}
- public RepoSequence(
- GitRepositoryManager repoManager,
- GitReferenceUpdated gitRefUpdated,
- Project.NameKey projectName,
- String name,
- Seed seed,
- int batchSize,
- int floor) {
- this(
- repoManager,
- gitRefUpdated,
- projectName,
- name,
- seed,
- batchSize,
- Runnables.doNothing(),
- RETRYER,
- floor);
- }
-
@VisibleForTesting
RepoSequence(
GitRepositoryManager repoManager,
@@ -160,7 +274,7 @@
this(repoManager, gitRefUpdated, projectName, name, seed, batchSize, afterReadRef, retryer, 0);
}
- RepoSequence(
+ private RepoSequence(
GitRepositoryManager repoManager,
GitReferenceUpdated gitRefUpdated,
Project.NameKey projectName,
@@ -201,6 +315,7 @@
*
* @return the next available sequence number
*/
+ @Override
public int next() {
return Iterables.getOnlyElement(next(1));
}
@@ -213,6 +328,7 @@
* @param count the number of sequence numbers which should be returned
* @return the next N available sequence numbers
*/
+ @Override
public ImmutableList<Integer> next(int count) {
if (count == 0) {
return ImmutableList.of();
@@ -295,12 +411,7 @@
}
}
- public static ReceiveCommand storeNew(ObjectInserter ins, String name, int val)
- throws IOException {
- ObjectId newId = ins.insert(OBJ_BLOB, Integer.toString(val).getBytes(UTF_8));
- return new ReceiveCommand(ObjectId.zeroId(), newId, RefNames.REFS_SEQUENCES + name);
- }
-
+ @Override
public void storeNew(int value) {
counterLock.lock();
try (Repository repo = repoManager.openRepository(projectName);
@@ -326,6 +437,12 @@
}
}
+ @Override
+ public int getBatchSize() {
+ return batchSize;
+ }
+
+ @Override
public int current() {
counterLock.lock();
try (Repository repo = repoManager.openRepository(projectName);
@@ -350,6 +467,7 @@
*
* <p>Explicitly calls {@link #next()} if this instance didn't return sequence number until now.
*/
+ @Override
public int last() {
if (counter == 0) {
next();
diff --git a/java/com/google/gerrit/server/notedb/Sequences.java b/java/com/google/gerrit/server/notedb/Sequences.java
index b42253e..780998b 100644
--- a/java/com/google/gerrit/server/notedb/Sequences.java
+++ b/java/com/google/gerrit/server/notedb/Sequences.java
@@ -14,98 +14,43 @@
package com.google.gerrit.server.notedb;
+import static com.google.gerrit.server.Sequence.NAME_ACCOUNTS;
+import static com.google.gerrit.server.Sequence.NAME_CHANGES;
+import static com.google.gerrit.server.Sequence.NAME_GROUPS;
+
import com.google.common.collect.ImmutableList;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer2;
-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.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.Sequence;
+import com.google.gerrit.server.Sequence.SequenceType;
import com.google.gerrit.server.logging.Metadata;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-import org.eclipse.jgit.lib.Config;
+import com.google.inject.name.Named;
@Singleton
public class Sequences {
- private static final String SECTION_NOTEDB = "noteDb";
- private static final String KEY_SEQUENCE_BATCH_SIZE = "sequenceBatchSize";
- private static final int DEFAULT_ACCOUNTS_SEQUENCE_BATCH_SIZE = 1;
- private static final int DEFAULT_CHANGES_SEQUENCE_BATCH_SIZE = 20;
-
- public static final String NAME_ACCOUNTS = "accounts";
- public static final String NAME_GROUPS = "groups";
- public static final String NAME_CHANGES = "changes";
-
- public static final int FIRST_ACCOUNT_ID = 1000000;
- public static final int FIRST_GROUP_ID = 1;
public static final int FIRST_CHANGE_ID = 1;
+ public static final int FIRST_GROUP_ID = 1;
+ public static final int FIRST_ACCOUNT_ID = 1000000;
- private enum SequenceType {
- ACCOUNTS,
- CHANGES,
- GROUPS;
- }
-
- private final RepoSequence accountSeq;
- private final RepoSequence changeSeq;
- private final RepoSequence groupSeq;
+ private final Sequence accountSeq;
+ private final Sequence changeSeq;
+ private final Sequence groupSeq;
private final Timer2<SequenceType, Boolean> nextIdLatency;
- private final int accountBatchSize;
- private final int changeBatchSize;
- private final int groupBatchSize = 1;
@Inject
public Sequences(
- @GerritServerConfig Config cfg,
- GitRepositoryManager repoManager,
- GitReferenceUpdated gitRefUpdated,
- AllProjectsName allProjects,
- AllUsersName allUsers,
- MetricMaker metrics) {
-
- accountBatchSize =
- cfg.getInt(
- SECTION_NOTEDB,
- NAME_ACCOUNTS,
- KEY_SEQUENCE_BATCH_SIZE,
- DEFAULT_ACCOUNTS_SEQUENCE_BATCH_SIZE);
- accountSeq =
- new RepoSequence(
- repoManager,
- gitRefUpdated,
- allUsers,
- NAME_ACCOUNTS,
- () -> FIRST_ACCOUNT_ID,
- accountBatchSize);
-
- changeBatchSize =
- cfg.getInt(
- SECTION_NOTEDB,
- NAME_CHANGES,
- KEY_SEQUENCE_BATCH_SIZE,
- DEFAULT_CHANGES_SEQUENCE_BATCH_SIZE);
- changeSeq =
- new RepoSequence(
- repoManager,
- gitRefUpdated,
- allProjects,
- NAME_CHANGES,
- () -> FIRST_CHANGE_ID,
- changeBatchSize);
-
- groupSeq =
- new RepoSequence(
- repoManager,
- gitRefUpdated,
- allUsers,
- NAME_GROUPS,
- () -> FIRST_GROUP_ID,
- groupBatchSize);
+ MetricMaker metrics,
+ @Named(NAME_ACCOUNTS) Sequence accountsSeq,
+ @Named(NAME_GROUPS) Sequence groupsSeq,
+ @Named(NAME_CHANGES) Sequence changesSeq) {
+ this.accountSeq = accountsSeq;
+ this.groupSeq = groupsSeq;
+ this.changeSeq = changesSeq;
nextIdLatency =
metrics.newTimer(
@@ -123,42 +68,42 @@
public int nextAccountId() {
try (Timer2.Context<SequenceType, Boolean> timer =
- nextIdLatency.start(SequenceType.ACCOUNTS, false)) {
+ nextIdLatency.start(Sequence.SequenceType.ACCOUNTS, false)) {
return accountSeq.next();
}
}
public int nextChangeId() {
try (Timer2.Context<SequenceType, Boolean> timer =
- nextIdLatency.start(SequenceType.CHANGES, false)) {
+ nextIdLatency.start(Sequence.SequenceType.CHANGES, false)) {
return changeSeq.next();
}
}
public ImmutableList<Integer> nextChangeIds(int count) {
try (Timer2.Context<SequenceType, Boolean> timer =
- nextIdLatency.start(SequenceType.CHANGES, count > 1)) {
+ nextIdLatency.start(Sequence.SequenceType.CHANGES, count > 1)) {
return changeSeq.next(count);
}
}
public int nextGroupId() {
try (Timer2.Context<SequenceType, Boolean> timer =
- nextIdLatency.start(SequenceType.GROUPS, false)) {
+ nextIdLatency.start(Sequence.SequenceType.GROUPS, false)) {
return groupSeq.next();
}
}
public int changeBatchSize() {
- return changeBatchSize;
+ return changeSeq.getBatchSize();
}
public int groupBatchSize() {
- return groupBatchSize;
+ return groupSeq.getBatchSize();
}
public int accountBatchSize() {
- return accountBatchSize;
+ return accountSeq.getBatchSize();
}
public int currentChangeId() {
diff --git a/java/com/google/gerrit/server/patch/GitPositionTransformer.java b/java/com/google/gerrit/server/patch/GitPositionTransformer.java
index f33d302..b2c02e7 100644
--- a/java/com/google/gerrit/server/patch/GitPositionTransformer.java
+++ b/java/com/google/gerrit/server/patch/GitPositionTransformer.java
@@ -139,8 +139,8 @@
Set<String> newFiles = newFilesPerOldFile.get(oldFilePath);
if (newFiles.isEmpty()) {
// File was deleted.
- return Streams.stream(
- positionConflictStrategy.getOnFileConflict(entity.position()).map(entity::withPosition));
+ return positionConflictStrategy.getOnFileConflict(entity.position()).map(entity::withPosition)
+ .stream();
}
return newFiles.stream().map(entity::withFilePath);
}
diff --git a/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java b/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
index 7562b49..f6faa4e 100644
--- a/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
+++ b/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
@@ -110,20 +110,22 @@
InvalidChangeOperationException {
PatchSet currentPatchset = notes.getCurrentPatchSet();
- PatchSet.Id latestApprovedPatchsetId = getLatestApprovedPatchsetId(notes);
- if (latestApprovedPatchsetId.get() == currentPatchset.id().get()) {
+ Optional<PatchSet.Id> latestApprovedPatchsetId = getLatestApprovedPatchsetId(notes);
+ if (latestApprovedPatchsetId.isEmpty()
+ || latestApprovedPatchsetId.get().get() == currentPatchset.id().get()) {
// If the latest approved patchset is the current patchset, no need to return anything.
return "";
}
StringBuilder diff =
new StringBuilder(
String.format(
- "\n\n%d is the latest approved patch-set.\n", latestApprovedPatchsetId.get()));
+ "\n\n%d is the latest approved patch-set.\n",
+ latestApprovedPatchsetId.get().get()));
Map<String, FileDiffOutput> modifiedFiles =
listModifiedFiles(
notes.getProjectName(),
currentPatchset,
- notes.getPatchSets().get(latestApprovedPatchsetId));
+ notes.getPatchSets().get(latestApprovedPatchsetId.get()));
// To make the message a bit more concise, we skip the magic files.
List<FileDiffOutput> modifiedFilesList =
@@ -173,7 +175,7 @@
getDiffForFile(
notes,
currentPatchset.id(),
- latestApprovedPatchsetId,
+ latestApprovedPatchsetId.get(),
fileDiff,
currentUser,
formatterResult,
@@ -290,10 +292,10 @@
return diffPreferencesInfo;
}
- private PatchSet.Id getLatestApprovedPatchsetId(ChangeNotes notes) {
+ private Optional<PatchSet.Id> getLatestApprovedPatchsetId(ChangeNotes notes) {
ProjectState projectState =
projectCache.get(notes.getProjectName()).orElseThrow(illegalState(notes.getProjectName()));
- PatchSet.Id maxPatchSetId = PatchSet.id(notes.getChangeId(), 1);
+ Optional<PatchSet.Id> maxPatchSetId = Optional.empty();
for (PatchSetApproval patchSetApproval : notes.getApprovals().onlyNonCopied().values()) {
if (!patchSetApproval.label().equals(LabelId.CODE_REVIEW)) {
continue;
@@ -303,8 +305,9 @@
if (!lt.isPresent() || !lt.get().isMaxPositive(patchSetApproval)) {
continue;
}
- if (patchSetApproval.patchSetId().get() > maxPatchSetId.get()) {
- maxPatchSetId = patchSetApproval.patchSetId();
+ if (maxPatchSetId.isEmpty()
+ || patchSetApproval.patchSetId().get() > maxPatchSetId.get().get()) {
+ maxPatchSetId = Optional.of(patchSetApproval.patchSetId());
}
}
return maxPatchSetId;
diff --git a/java/com/google/gerrit/server/permissions/ChangeControl.java b/java/com/google/gerrit/server/permissions/ChangeControl.java
index 0e37049..664ffa2 100644
--- a/java/com/google/gerrit/server/permissions/ChangeControl.java
+++ b/java/com/google/gerrit/server/permissions/ChangeControl.java
@@ -66,7 +66,7 @@
/** Can this user see this change? */
boolean isVisible() {
- if (getChange().isPrivate() && !isPrivateVisible(changeData)) {
+ if (changeData.isPrivateOrThrow() && !isPrivateVisible(changeData)) {
return false;
}
// Does the user have READ permission on the destination?
@@ -156,7 +156,7 @@
Permission.EDIT_TOPIC_NAME) // user can edit topic on a specific ref
|| getProjectControl().isAdmin();
}
- return refControl.canForceEditTopicName();
+ return refControl.canForceEditTopicName(isOwner());
}
/** Can this user toggle WorkInProgress state? */
diff --git a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
index eebaa8f..f179045 100644
--- a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
+++ b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
@@ -27,7 +27,6 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.RefNames;
@@ -36,16 +35,16 @@
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.SearchingChangeCacheImpl;
+import com.google.gerrit.server.git.ChangesByProjectCache;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.TagMatcher;
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.permissions.PermissionBackend.RefFilterOptions;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
+import com.google.inject.Singleton;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.ArrayList;
@@ -65,6 +64,27 @@
DefaultRefFilter create(ProjectControl projectControl);
}
+ @Singleton
+ private static class Metrics {
+ final Counter0 fullFilterCount;
+ final Counter0 skipFilterCount;
+
+ @Inject
+ Metrics(MetricMaker metricMaker) {
+ fullFilterCount =
+ metricMaker.newCounter(
+ "permissions/ref_filter/full_filter_count",
+ new Description("Rate of full ref filter operations").setRate());
+ skipFilterCount =
+ metricMaker.newCounter(
+ "permissions/ref_filter/skip_filter_count",
+ new Description(
+ "Rate of ref filter operations where we skip full evaluation"
+ + " because the user can read all refs")
+ .setRate());
+ }
+ }
+
private final TagCache tagCache;
private final PermissionBackend permissionBackend;
private final RefVisibilityControl refVisibilityControl;
@@ -72,11 +92,9 @@
private final CurrentUser user;
private final ProjectState projectState;
private final PermissionBackend.ForProject permissionBackendForProject;
- private final @Nullable SearchingChangeCacheImpl searchingChangeDataProvider;
+ private final ChangesByProjectCache changesByProjectCache;
private final ChangeData.Factory changeDataFactory;
- private final ChangeNotes.Factory changeNotesFactory;
- private final Counter0 fullFilterCount;
- private final Counter0 skipFilterCount;
+ private final Metrics metrics;
private final boolean skipFullRefEvaluationIfAllRefsAreVisible;
@Inject
@@ -85,17 +103,15 @@
PermissionBackend permissionBackend,
RefVisibilityControl refVisibilityControl,
@GerritServerConfig Config config,
- MetricMaker metricMaker,
- @Nullable SearchingChangeCacheImpl searchingChangeDataProvider,
+ Metrics metrics,
+ ChangesByProjectCache changesByProjectCache,
ChangeData.Factory changeDataFactory,
- ChangeNotes.Factory changeNotesFactory,
@Assisted ProjectControl projectControl) {
this.tagCache = tagCache;
this.permissionBackend = permissionBackend;
this.refVisibilityControl = refVisibilityControl;
- this.searchingChangeDataProvider = searchingChangeDataProvider;
+ this.changesByProjectCache = changesByProjectCache;
this.changeDataFactory = changeDataFactory;
- this.changeNotesFactory = changeNotesFactory;
this.skipFullRefEvaluationIfAllRefsAreVisible =
config.getBoolean("auth", "skipFullRefEvaluationIfAllRefsAreVisible", true);
this.projectControl = projectControl;
@@ -104,17 +120,7 @@
this.projectState = projectControl.getProjectState();
this.permissionBackendForProject =
permissionBackend.user(user).project(projectState.getNameKey());
- this.fullFilterCount =
- metricMaker.newCounter(
- "permissions/ref_filter/full_filter_count",
- new Description("Rate of full ref filter operations").setRate());
- this.skipFilterCount =
- metricMaker.newCounter(
- "permissions/ref_filter/skip_filter_count",
- new Description(
- "Rate of ref filter operations where we skip full evaluation"
- + " because the user can read all refs")
- .setRate());
+ this.metrics = metrics;
}
/** Filters given refs and tags by visibility. */
@@ -139,8 +145,7 @@
Suppliers.memoize(
() ->
GitVisibleChangeFilter.getVisibleChanges(
- searchingChangeDataProvider,
- changeNotesFactory,
+ changesByProjectCache,
changeDataFactory,
projectState.getNameKey(),
permissionBackendForProject,
@@ -202,13 +207,13 @@
logger.atFinest().log("User has READ on refs/* = %s", hasReadOnRefsStar);
if (skipFullRefEvaluationIfAllRefsAreVisible && !projectState.isAllUsers()) {
if (hasReadOnRefsStar) {
- skipFilterCount.increment();
+ metrics.skipFilterCount.increment();
logger.atFinest().log(
"Fast path, all refs are visible because user has READ on refs/*: %s", refs);
return new AutoValue_DefaultRefFilter_Result(
ImmutableList.copyOf(refs), ImmutableList.of());
} else if (projectControl.allRefsAreVisible(ImmutableSet.of(RefNames.REFS_CONFIG))) {
- skipFilterCount.increment();
+ metrics.skipFilterCount.increment();
refs = fastHideRefsMetaConfig(refs);
logger.atFinest().log(
"Fast path, all refs except %s are visible: %s", RefNames.REFS_CONFIG, refs);
@@ -217,7 +222,7 @@
}
}
logger.atFinest().log("Doing full ref filtering");
- fullFilterCount.increment();
+ metrics.fullFilterCount.increment();
boolean hasAccessDatabase =
permissionBackend
diff --git a/java/com/google/gerrit/server/permissions/GitVisibleChangeFilter.java b/java/com/google/gerrit/server/permissions/GitVisibleChangeFilter.java
index 0e5ff48..640ea9a 100644
--- a/java/com/google/gerrit/server/permissions/GitVisibleChangeFilter.java
+++ b/java/com/google/gerrit/server/permissions/GitVisibleChangeFilter.java
@@ -17,12 +17,9 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
-import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.server.git.SearchingChangeCacheImpl;
-import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.git.ChangesByProjectCache;
import com.google.gerrit.server.query.change.ChangeData;
import java.io.IOException;
import java.util.HashMap;
@@ -40,11 +37,7 @@
*
* <ul>
* <li>For a low number of expected checks, we check visibility one-by-one.
- * <li>For a high number of expected checks and settings where the change index is available, we
- * load the N most recent changes from the index and filter them by visibility. This is fast,
- * but comes with the caveat that older changes are pretended to be invisible.
- * <li>For a high number of expected checks and settings where the change index is unavailable, we
- * scan the repo and determine visibility one-by-one. This is *very* expensive.
+ * <li>For a high number of expected checks we use the ChangesByProjectCache.
* </ul>
*
* <p>Changes that fail to load are pretended to be invisible. This is important on the Git paths as
@@ -61,24 +54,23 @@
/** Returns a map of all visible changes. Might pretend old changes are invisible. */
static ImmutableMap<Change.Id, ChangeData> getVisibleChanges(
- @Nullable SearchingChangeCacheImpl searchingChangeCache,
- ChangeNotes.Factory changeNotesFactory,
+ ChangesByProjectCache changesByProjectCache,
ChangeData.Factory changeDataFactory,
Project.NameKey projectName,
PermissionBackend.ForProject forProject,
Repository repository,
ImmutableSet<Change.Id> changes) {
- Stream<ChangeData> changeDatas;
+ Stream<ChangeData> changeDatas = Stream.empty();
if (changes.size() < CHANGE_LIMIT_FOR_DIRECT_FILTERING) {
logger.atFine().log("Loading changes one by one for project %s", projectName);
changeDatas = loadChangeDatasOneByOne(changes, changeDataFactory, projectName);
- } else if (searchingChangeCache != null) {
- logger.atFine().log("Loading changes from SearchingChangeCache for project %s", projectName);
- changeDatas = searchingChangeCache.getChangeData(projectName);
} else {
- logger.atFine().log("Loading changes from all refs for project %s", projectName);
- changeDatas =
- scanRepoForChangeDatas(changeNotesFactory, changeDataFactory, repository, projectName);
+ logger.atFine().log("Loading changes from ChangesByProjectCache for project %s", projectName);
+ try {
+ changeDatas = changesByProjectCache.streamChangeDatas(projectName, repository);
+ } catch (IOException e) {
+ logger.atWarning().withCause(e).log("Unable to streamChangeDatas for %s", projectName);
+ }
}
HashMap<Change.Id, ChangeData> result = new HashMap<>();
changeDatas
@@ -88,7 +80,11 @@
try {
return forProject.change(cd).test(ChangePermission.READ);
} catch (PermissionBackendException e) {
- throw new StorageException(e);
+ // This is almost the same as the message .testOrFalse() would log, but with the
+ // added context of the change and coming from this class
+ logger.atWarning().withCause(e).log(
+ "Cannot test read permission for %s; assuming not visible", cd);
+ return false;
}
})
.forEach(
@@ -124,31 +120,4 @@
})
.filter(Objects::nonNull);
}
-
- /** Get a stream of all changes by scanning the repo. This is extremely slow. */
- private static Stream<ChangeData> scanRepoForChangeDatas(
- ChangeNotes.Factory changeNotesFactory,
- ChangeData.Factory changeDataFactory,
- Repository repository,
- Project.NameKey projectName) {
- Stream<ChangeData> cds;
- try {
- cds =
- changeNotesFactory
- .scan(repository, projectName)
- .map(
- notesResult -> {
- if (!notesResult.error().isPresent()) {
- return changeDataFactory.create(notesResult.notes());
- }
- logger.atWarning().withCause(notesResult.error().get()).log(
- "Unable to load ChangeNotes for %s", notesResult.id());
- return null;
- })
- .filter(Objects::nonNull);
- } catch (IOException e) {
- throw new StorageException(e);
- }
- return cds;
- }
}
diff --git a/java/com/google/gerrit/server/permissions/PermissionBackend.java b/java/com/google/gerrit/server/permissions/PermissionBackend.java
index eb5e053..ac9ac98 100644
--- a/java/com/google/gerrit/server/permissions/PermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/PermissionBackend.java
@@ -274,7 +274,7 @@
/** Returns an instance scoped for the change, and its destination ref and project. */
public ForChange change(ChangeData cd) {
try {
- return ref(cd.change().getDest().branch()).change(cd);
+ return ref(cd.branchOrThrow().branch()).change(cd);
} catch (StorageException e) {
return FailedPermissionBackend.change("unavailable", e);
}
diff --git a/java/com/google/gerrit/server/permissions/ProjectControl.java b/java/com/google/gerrit/server/permissions/ProjectControl.java
index c235012..fab894e 100644
--- a/java/com/google/gerrit/server/permissions/ProjectControl.java
+++ b/java/com/google/gerrit/server/permissions/ProjectControl.java
@@ -115,7 +115,7 @@
}
ChangeControl controlFor(ChangeData cd) {
- return new ChangeControl(controlForRef(cd.change().getDest()), cd);
+ return new ChangeControl(controlForRef(cd.branchOrThrow()), cd);
}
RefControl controlForRef(BranchNameKey ref) {
@@ -366,7 +366,7 @@
@Override
public ForChange change(ChangeData cd) {
try {
- checkProject(cd.change());
+ checkProject(cd);
return super.change(cd);
} catch (StorageException e) {
return FailedPermissionBackend.change("unavailable", e);
@@ -379,13 +379,21 @@
return super.change(notes);
}
+ private void checkProject(ChangeData cd) {
+ checkProject(cd.project());
+ }
+
private void checkProject(Change change) {
+ checkProject(change.getProject());
+ }
+
+ private void checkProject(Project.NameKey changeProject) {
Project.NameKey project = getProject().getNameKey();
checkArgument(
- project.equals(change.getProject()),
+ project.equals(changeProject),
"expected change in project %s, not %s",
project,
- change.getProject());
+ changeProject);
}
@Override
diff --git a/java/com/google/gerrit/server/permissions/RefControl.java b/java/com/google/gerrit/server/permissions/RefControl.java
index a3a041b..aba9522 100644
--- a/java/com/google/gerrit/server/permissions/RefControl.java
+++ b/java/com/google/gerrit/server/permissions/RefControl.java
@@ -162,8 +162,8 @@
}
/** Returns true if this user can force edit topic names. */
- boolean canForceEditTopicName() {
- return canPerform(Permission.EDIT_TOPIC_NAME, false, true);
+ boolean canForceEditTopicName(boolean isChangeOwner) {
+ return canPerform(Permission.EDIT_TOPIC_NAME, isChangeOwner, true);
}
/** Returns true if this user can delete changes. */
diff --git a/java/com/google/gerrit/server/plugins/JarPluginProvider.java b/java/com/google/gerrit/server/plugins/JarPluginProvider.java
index c00a69d..760631d 100644
--- a/java/com/google/gerrit/server/plugins/JarPluginProvider.java
+++ b/java/com/google/gerrit/server/plugins/JarPluginProvider.java
@@ -20,7 +20,9 @@
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.plugins.Plugin.ApiType;
import com.google.inject.Inject;
+import com.google.inject.Singleton;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
@@ -38,6 +40,7 @@
import java.util.jar.Manifest;
import org.eclipse.jgit.internal.storage.file.FileSnapshot;
+@Singleton
public class JarPluginProvider implements ServerPluginProvider {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -47,6 +50,8 @@
private final Path tmpDir;
private final PluginConfigFactory configFactory;
+ private ClassLoader pluginApiClassLoader = PluginUtil.parentFor(ApiType.PLUGIN);
+
@Inject
JarPluginProvider(SitePaths sitePaths, PluginConfigFactory configFactory) {
this.tmpDir = sitePaths.tmp_dir;
@@ -134,9 +139,14 @@
}
urls.add(tmp.toUri().toURL());
- ClassLoader pluginLoader =
- URLClassLoader.newInstance(
- urls.toArray(new URL[urls.size()]), PluginUtil.parentFor(type));
+ ClassLoader parentClassLoader = parentFor(type);
+
+ URLClassLoader pluginLoader =
+ URLClassLoader.newInstance(urls.toArray(new URL[urls.size()]), parentClassLoader);
+
+ if (manifest.getMainAttributes().getValue(ServerPlugin.API_MODULE) != null) {
+ pluginApiClassLoader = pluginLoader;
+ }
JarScanner jarScanner = createJarScanner(tmp);
PluginConfig pluginConfig = configFactory.getFromGerritConfig(name);
@@ -163,6 +173,17 @@
}
}
+ private ClassLoader parentFor(ApiType type) {
+ switch (type) {
+ case PLUGIN:
+ return pluginApiClassLoader;
+
+ // $CASES-OMITTED$
+ default:
+ return PluginUtil.parentFor(type);
+ }
+ }
+
private JarScanner createJarScanner(Path srcJar) throws InvalidPluginException {
try {
return new JarScanner(srcJar);
diff --git a/java/com/google/gerrit/server/plugins/JsPlugin.java b/java/com/google/gerrit/server/plugins/JsPlugin.java
index c120cdd..2c4edec 100644
--- a/java/com/google/gerrit/server/plugins/JsPlugin.java
+++ b/java/com/google/gerrit/server/plugins/JsPlugin.java
@@ -106,4 +106,9 @@
public PluginContentScanner getContentScanner() {
return PluginContentScanner.EMPTY;
}
+
+ @Override
+ public Injector getApiInjector() {
+ return null;
+ }
}
diff --git a/java/com/google/gerrit/server/plugins/Plugin.java b/java/com/google/gerrit/server/plugins/Plugin.java
index 238066b..b5ff041 100644
--- a/java/com/google/gerrit/server/plugins/Plugin.java
+++ b/java/com/google/gerrit/server/plugins/Plugin.java
@@ -21,10 +21,12 @@
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.server.PluginUser;
import com.google.inject.Injector;
+import com.google.inject.Module;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import org.eclipse.jgit.internal.storage.file.FileSnapshot;
@@ -143,6 +145,9 @@
@Nullable
public abstract Injector getHttpInjector();
+ @Nullable
+ public abstract Injector getApiInjector();
+
public void add(RegistrationHandle handle) {
if (manager != null) {
if (handle instanceof ReloadableRegistrationHandle) {
@@ -172,4 +177,8 @@
boolean isModified(Path jar) {
return snapshot.isModified(jar.toFile());
}
+
+ public Optional<Module> getApiModule() {
+ return Optional.empty();
+ }
}
diff --git a/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java b/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
index 87e1ca9..ae02e9d 100644
--- a/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
+++ b/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
@@ -19,6 +19,7 @@
import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.dynamicSetsOf;
import static java.util.Objects.requireNonNull;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
@@ -53,6 +54,7 @@
import com.google.inject.internal.UniqueAnnotations;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -60,6 +62,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.servlet.http.HttpServletRequest;
@@ -87,6 +90,8 @@
private Module sysModule;
private Module sshModule;
private Module httpModule;
+ private final List<Module> apiModules;
+ private Injector apiInjector;
private Provider<ModuleGenerator> sshGen;
private Provider<ModuleGenerator> httpGen;
@@ -94,14 +99,17 @@
private Map<TypeLiteral<?>, DynamicItem<?>> sysItems;
private Map<TypeLiteral<?>, DynamicItem<?>> sshItems;
private Map<TypeLiteral<?>, DynamicItem<?>> httpItems;
+ private Map<TypeLiteral<?>, DynamicItem<?>> apiItems;
private Map<TypeLiteral<?>, DynamicSet<?>> sysSets;
private Map<TypeLiteral<?>, DynamicSet<?>> sshSets;
private Map<TypeLiteral<?>, DynamicSet<?>> httpSets;
+ private Map<TypeLiteral<?>, DynamicSet<?>> apiSets;
private Map<TypeLiteral<?>, DynamicMap<?>> sysMaps;
private Map<TypeLiteral<?>, DynamicMap<?>> sshMaps;
private Map<TypeLiteral<?>, DynamicMap<?>> httpMaps;
+ private Map<TypeLiteral<?>, DynamicMap<?>> apiMaps;
@Inject
PluginGuiceEnvironment(
@@ -129,6 +137,8 @@
sysItems = dynamicItemsOf(sysInjector);
sysSets = dynamicSetsOf(sysInjector);
sysMaps = dynamicMapsOf(sysInjector);
+
+ apiModules = new ArrayList<>();
}
ServerInformation getServerInformation() {
@@ -258,6 +268,24 @@
attachMap(sysMaps, plugin.getSysInjector(), plugin);
attachMap(sshMaps, plugin.getSshInjector(), plugin);
attachMap(httpMaps, plugin.getHttpInjector(), plugin);
+
+ apiInjector = Optional.ofNullable(plugin.getApiInjector()).orElse(apiInjector);
+ plugin.getApiModule().ifPresent(apiModules::add);
+
+ if (apiInjector != null) {
+ apiItems = dynamicItemsOf(apiInjector);
+ apiSets = dynamicSetsOf(apiInjector);
+ apiMaps = dynamicMapsOf(apiInjector);
+
+ List<Injector> allPluginInjectors =
+ listOfInjectors(
+ plugin.getSysInjector(), plugin.getSshInjector(), plugin.getHttpInjector());
+ allPluginInjectors.forEach(i -> attachItem(apiItems, i, plugin));
+ allPluginInjectors.forEach(i -> attachSet(apiSets, i, plugin));
+ allPluginInjectors.forEach(i -> attachMap(apiMaps, i, plugin));
+ }
+
+ plugin.getApiModule().ifPresent(apiModules::add);
} finally {
exit(oldContext);
}
@@ -267,6 +295,18 @@
}
}
+ private List<Injector> listOfInjectors(Injector... injectors) {
+ ImmutableList.Builder<Injector> injectorsListBuilder = ImmutableList.builder();
+
+ for (Injector injector : injectors) {
+ if (injector != null) {
+ injectorsListBuilder.add(injector);
+ }
+ }
+
+ return injectorsListBuilder.build();
+ }
+
public void onStopPlugin(Plugin plugin) {
for (StopPluginListener l : onStop) {
l.onStopPlugin(plugin);
@@ -308,14 +348,20 @@
RequestContext oldContext = enter(newPlugin);
try {
+ Optional.ofNullable(newPlugin.getApiInjector())
+ .ifPresent(i -> reattachMap(old, apiMaps, i, newPlugin));
reattachMap(old, sysMaps, newPlugin.getSysInjector(), newPlugin);
reattachMap(old, sshMaps, newPlugin.getSshInjector(), newPlugin);
reattachMap(old, httpMaps, newPlugin.getHttpInjector(), newPlugin);
+ Optional.ofNullable(newPlugin.getApiInjector())
+ .ifPresent(i -> reattachSet(old, apiSets, i, newPlugin));
reattachSet(old, sysSets, newPlugin.getSysInjector(), newPlugin);
reattachSet(old, sshSets, newPlugin.getSshInjector(), newPlugin);
reattachSet(old, httpSets, newPlugin.getHttpInjector(), newPlugin);
+ Optional.ofNullable(newPlugin.getApiInjector())
+ .ifPresent(i -> reattachItem(old, apiItems, i, newPlugin));
reattachItem(old, sysItems, newPlugin.getSysInjector(), newPlugin);
reattachItem(old, sshItems, newPlugin.getSshInjector(), newPlugin);
reattachItem(old, httpItems, newPlugin.getHttpInjector(), newPlugin);
@@ -648,4 +694,13 @@
}
return false;
}
+
+ @Nullable
+ public Injector getApiInjector() {
+ return apiInjector;
+ }
+
+ public List<Module> getApiModules() {
+ return ImmutableList.copyOf(apiModules);
+ }
}
diff --git a/java/com/google/gerrit/server/plugins/PluginLoader.java b/java/com/google/gerrit/server/plugins/PluginLoader.java
index 38b2dda..3574dc3 100644
--- a/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -62,6 +62,7 @@
import java.util.TreeSet;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
+import java.util.jar.JarFile;
import org.eclipse.jgit.internal.storage.file.FileSnapshot;
import org.eclipse.jgit.lib.Config;
@@ -193,7 +194,9 @@
try {
Plugin plugin = runPlugin(name, dst, active);
if (active == null) {
- logger.atInfo().log("Installed plugin %s", plugin.getName());
+ logger.atInfo().log(
+ "Installed plugin %s%s",
+ plugin.getName(), plugin.getApiModule().isPresent() ? " (w/ ApiModule)" : "");
}
} catch (PluginInstallException e) {
Files.deleteIfExists(dst);
@@ -378,7 +381,10 @@
logger.atInfo().log("Reloading plugin %s", name);
Plugin newPlugin = runPlugin(name, active.getSrcFile(), active);
logger.atInfo().log(
- "Reloaded plugin %s, version %s", newPlugin.getName(), newPlugin.getVersion());
+ "Reloaded plugin %s%s, version %s",
+ newPlugin.getName(),
+ newPlugin.getApiModule().isPresent() ? " (w/ ApiModule)" : "",
+ newPlugin.getVersion());
} catch (PluginInstallException e) {
logger.atWarning().withCause(e.getCause()).log("Cannot reload plugin %s", name);
throw e;
@@ -397,7 +403,7 @@
syncDisabledPlugins(pluginsFiles);
Map<String, Path> activePlugins = filterDisabled(pluginsFiles);
- for (Map.Entry<String, Path> entry : jarsFirstSortedPluginsSet(activePlugins)) {
+ for (Map.Entry<String, Path> entry : jarsApiFirstSortedPluginsSet(activePlugins)) {
String name = entry.getKey();
Path path = entry.getValue();
String fileName = path.getFileName().toString();
@@ -427,9 +433,10 @@
if (!loadedPlugin.isDisabled()) {
loadedPlugins.add(name);
logger.atInfo().log(
- "%s plugin %s, version %s",
+ "%s plugin %s%s, version %s",
active == null ? "Loaded" : "Reloaded",
loadedPlugin.getName(),
+ loadedPlugin.getApiModule().isPresent() ? " (w/ ApiModule)" : "",
loadedPlugin.getVersion());
}
} catch (PluginInstallException e) {
@@ -454,7 +461,7 @@
}
}
- private TreeSet<Map.Entry<String, Path>> jarsFirstSortedPluginsSet(
+ private TreeSet<Map.Entry<String, Path>> jarsApiFirstSortedPluginsSet(
Map<String, Path> activePlugins) {
TreeSet<Map.Entry<String, Path>> sortedPlugins =
Sets.newTreeSet(
@@ -463,14 +470,34 @@
public int compare(Map.Entry<String, Path> e1, Map.Entry<String, Path> e2) {
Path n1 = e1.getValue().getFileName();
Path n2 = e2.getValue().getFileName();
- return ComparisonChain.start()
- .compareTrueFirst(isJar(n1), isJar(n2))
- .compare(n1, n2)
- .result();
+
+ try {
+ boolean e1IsApi = isApi(e1.getValue());
+ boolean e2IsApi = isApi(e2.getValue());
+ return ComparisonChain.start()
+ .compareTrueFirst(e1IsApi, e2IsApi)
+ .compareTrueFirst(isJar(n1), isJar(n2))
+ .compare(n1, n2)
+ .result();
+ } catch (IOException ioe) {
+ logger.atSevere().withCause(ioe).log("Unable to compare %s and %s", n1, n2);
+ return 0;
+ }
}
- private boolean isJar(Path n1) {
- return n1.toString().endsWith(".jar");
+ private boolean isJar(Path pluginPath) {
+ return pluginPath.toString().endsWith(".jar");
+ }
+
+ private boolean isApi(Path pluginPath) throws IOException {
+ return isJar(pluginPath) && hasApiModuleEntryInManifest(pluginPath);
+ }
+
+ private boolean hasApiModuleEntryInManifest(Path pluginPath) throws IOException {
+ try (JarFile jarFile = new JarFile(pluginPath.toFile())) {
+ return !Strings.isNullOrEmpty(
+ jarFile.getManifest().getMainAttributes().getValue(ServerPlugin.API_MODULE));
+ }
}
});
diff --git a/java/com/google/gerrit/server/plugins/PluginUtil.java b/java/com/google/gerrit/server/plugins/PluginUtil.java
index 4f00cd0..4abf864 100644
--- a/java/com/google/gerrit/server/plugins/PluginUtil.java
+++ b/java/com/google/gerrit/server/plugins/PluginUtil.java
@@ -81,7 +81,7 @@
return 0 < ext ? name.substring(0, ext) : name;
}
- static ClassLoader parentFor(Plugin.ApiType type) throws InvalidPluginException {
+ static ClassLoader parentFor(Plugin.ApiType type) {
switch (type) {
case EXTENSION:
return PluginName.class.getClassLoader();
@@ -90,7 +90,7 @@
case JS:
return JavaScriptPlugin.class.getClassLoader();
default:
- throw new InvalidPluginException("Unsupported ApiType " + type);
+ throw new IllegalArgumentException("Unsupported ApiType " + type);
}
}
}
diff --git a/java/com/google/gerrit/server/plugins/ServerPlugin.java b/java/com/google/gerrit/server/plugins/ServerPlugin.java
index af948b0..fe550e4 100644
--- a/java/com/google/gerrit/server/plugins/ServerPlugin.java
+++ b/java/com/google/gerrit/server/plugins/ServerPlugin.java
@@ -31,12 +31,15 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
+import java.util.stream.Collectors;
import org.eclipse.jgit.internal.storage.file.FileSnapshot;
public class ServerPlugin extends Plugin {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ public static final String API_MODULE = "Gerrit-ApiModule";
private final Manifest manifest;
private final PluginContentScanner scanner;
@@ -49,13 +52,17 @@
protected Class<? extends Module> batchModule;
protected Class<? extends Module> sshModule;
protected Class<? extends Module> httpModule;
+ private Class<? extends Module> apiModuleClass;
+ private Injector apiInjector;
private Injector sysInjector;
private Injector sshInjector;
private Injector httpInjector;
private LifecycleManager serverManager;
private List<ReloadableRegistrationHandle<?>> reloadableHandles;
+ private Optional<Module> apiModule = Optional.empty();
+
public ServerPlugin(
String name,
String pluginCanonicalWebUrl,
@@ -93,6 +100,7 @@
String sshName = main.getValue("Gerrit-SshModule");
String httpName = main.getValue("Gerrit-HttpModule");
String batchName = main.getValue("Gerrit-BatchModule");
+ String apiName = main.getValue(API_MODULE);
if (!Strings.isNullOrEmpty(sshName) && getApiType() != Plugin.ApiType.PLUGIN) {
throw new InvalidPluginException(
@@ -105,6 +113,7 @@
this.sysModule = load(sysName, classLoader);
this.sshModule = load(sshName, classLoader);
this.httpModule = load(httpName, classLoader);
+ this.apiModuleClass = load(apiName, classLoader);
} catch (ClassNotFoundException e) {
throw new InvalidPluginException("Unable to load plugin Guice Modules", e);
}
@@ -187,11 +196,10 @@
}
private void startPlugin(PluginGuiceEnvironment env) throws Exception {
- Injector root = newRootInjector(env);
serverManager = new LifecycleManager();
- serverManager.add(root);
if (gerritRuntime == GerritRuntime.BATCH) {
+ Injector root = newRootInjector(env);
if (batchModule != null) {
sysInjector = root.createChildInjector(root.getInstance(batchModule));
serverManager.add(sysInjector);
@@ -204,19 +212,27 @@
}
AutoRegisterModules auto = null;
- if (sysModule == null && sshModule == null && httpModule == null) {
+ if (sysModule == null && sshModule == null && httpModule == null && apiModuleClass == null) {
auto = new AutoRegisterModules(getName(), env, scanner, classLoader);
auto.discover();
}
+ Injector baseInjector;
+ if (apiModuleClass == null) {
+ baseInjector = newRootInjector(env);
+ } else {
+ baseInjector = newRootInjectorWithApiModule(env, apiModuleClass);
+ }
+ serverManager.add(baseInjector);
+
if (sysModule != null) {
- sysInjector = root.createChildInjector(root.getInstance(sysModule));
+ sysInjector = baseInjector.createChildInjector(baseInjector.getInstance(sysModule));
serverManager.add(sysInjector);
} else if (auto != null && auto.sysModule != null) {
- sysInjector = root.createChildInjector(auto.sysModule);
+ sysInjector = baseInjector.createChildInjector(auto.sysModule);
serverManager.add(sysInjector);
} else {
- sysInjector = root;
+ sysInjector = baseInjector;
}
if (env.hasSshModule()) {
@@ -255,12 +271,37 @@
}
private Injector newRootInjector(PluginGuiceEnvironment env) {
+ Optional<Injector> apiInjector = Optional.ofNullable(env.getApiInjector());
+
List<Module> modules = Lists.newArrayListWithCapacity(2);
if (getApiType() == ApiType.PLUGIN) {
- modules.add(env.getSysModule());
+ if (!apiInjector.isPresent()) {
+ modules.add(env.getSysModule());
+ }
}
modules.add(new ServerPluginInfoModule(this, env.getServerMetrics()));
- return Guice.createInjector(modules);
+ return apiInjector
+ .map(injector -> injector.createChildInjector(modules))
+ .orElse(Guice.createInjector(modules));
+ }
+
+ private Injector newRootInjectorWithApiModule(
+ PluginGuiceEnvironment env, Class<? extends Module> apiModuleClass) {
+ Injector baseInjector = Guice.createInjector(env.getSysModule());
+ apiModule = Optional.of(baseInjector.getInstance(apiModuleClass));
+
+ List<Module> modules = Lists.newArrayListWithCapacity(2);
+ List<Module> apiModulesNoDuplicates =
+ env.getApiModules().stream()
+ .filter(module -> !module.getClass().getName().equals(apiModuleClass.getName()))
+ .collect(Collectors.toList());
+ modules.addAll(apiModulesNoDuplicates);
+ apiModule.ifPresent(modules::add);
+
+ apiInjector = baseInjector.createChildInjector(modules);
+
+ return apiInjector.createChildInjector(
+ new ServerPluginInfoModule(this, env.getServerMetrics()));
}
@Override
@@ -285,6 +326,16 @@
}
@Override
+ public Injector getApiInjector() {
+ return apiInjector;
+ }
+
+ @Override
+ public Optional<Module> getApiModule() {
+ return apiModule;
+ }
+
+ @Override
@Nullable
public Injector getSshInjector() {
return sshInjector;
diff --git a/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java b/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java
index df2e1cf..47d43b9 100644
--- a/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java
+++ b/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java
@@ -92,6 +92,11 @@
}
@Override
+ public String toString() {
+ return "Project List Cache Warmer";
+ }
+
+ @Override
public void run() {
logger.atFine().log("Loading project_list cache");
cache.refreshProjectList();
diff --git a/java/com/google/gerrit/server/project/RefPatternMatcher.java b/java/com/google/gerrit/server/project/RefPatternMatcher.java
index be840b5..798838e 100644
--- a/java/com/google/gerrit/server/project/RefPatternMatcher.java
+++ b/java/com/google/gerrit/server/project/RefPatternMatcher.java
@@ -151,7 +151,7 @@
}
private ImmutableSet<String> getUsernames(CurrentUser user) {
- Stream<String> usernames = Streams.stream(user.getUserName());
+ Stream<String> usernames = user.getUserName().stream();
if (user.isIdentifiedUser()) {
usernames = Streams.concat(usernames, user.asIdentifiedUser().getEmailAddresses().stream());
}
diff --git a/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java b/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
index 1fe8641..866ce14 100644
--- a/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
+++ b/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -18,7 +18,6 @@
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.SubmitRecord;
import com.google.gerrit.entities.SubmitTypeRecord;
@@ -37,6 +36,7 @@
import com.google.gerrit.server.rules.PrologSubmitRuleUtil;
import com.google.gerrit.server.rules.SubmitRule;
import com.google.inject.Inject;
+import com.google.inject.Singleton;
import com.google.inject.assistedinject.Assisted;
import java.util.List;
import java.util.Optional;
@@ -48,41 +48,51 @@
public class SubmitRuleEvaluator {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private final ProjectCache projectCache;
- private final PrologSubmitRuleUtil prologSubmitRuleUtil;
- private final PluginSetContext<SubmitRule> submitRules;
- private final Timer0 submitRuleEvaluationLatency;
- private final Timer0 submitTypeEvaluationLatency;
- private final SubmitRuleOptions opts;
- private final CallerFinder callerFinder;
-
public interface Factory {
/** Returns a new {@link SubmitRuleEvaluator} with the specified options */
SubmitRuleEvaluator create(SubmitRuleOptions options);
}
+ @Singleton
+ private static class Metrics {
+ final Timer0 submitRuleEvaluationLatency;
+ final Timer0 submitTypeEvaluationLatency;
+
+ @Inject
+ Metrics(MetricMaker metricMaker) {
+ submitRuleEvaluationLatency =
+ metricMaker.newTimer(
+ "change/submit_rule_evaluation",
+ new Description("Latency for evaluating submit rules on a change.")
+ .setCumulative()
+ .setUnit(Units.MILLISECONDS));
+ submitTypeEvaluationLatency =
+ metricMaker.newTimer(
+ "change/submit_type_evaluation",
+ new Description("Latency for evaluating the submit type on a change.")
+ .setCumulative()
+ .setUnit(Units.MILLISECONDS));
+ }
+ }
+
+ private final ProjectCache projectCache;
+ private final PrologSubmitRuleUtil prologSubmitRuleUtil;
+ private final PluginSetContext<SubmitRule> submitRules;
+ private final Metrics metrics;
+ private final SubmitRuleOptions opts;
+ private final CallerFinder callerFinder;
+
@Inject
private SubmitRuleEvaluator(
ProjectCache projectCache,
PrologSubmitRuleUtil prologSubmitRuleUtil,
PluginSetContext<SubmitRule> submitRules,
- MetricMaker metricMaker,
+ Metrics metrics,
@Assisted SubmitRuleOptions options) {
this.projectCache = projectCache;
this.prologSubmitRuleUtil = prologSubmitRuleUtil;
this.submitRules = submitRules;
- this.submitRuleEvaluationLatency =
- metricMaker.newTimer(
- "change/submit_rule_evaluation",
- new Description("Latency for evaluating submit rules on a change.")
- .setCumulative()
- .setUnit(Units.MILLISECONDS));
- this.submitTypeEvaluationLatency =
- metricMaker.newTimer(
- "change/submit_type_evaluation",
- new Description("Latency for evaluating the submit type on a change.")
- .setCumulative()
- .setUnit(Units.MILLISECONDS));
+ this.metrics = metrics;
this.opts = options;
@@ -106,26 +116,22 @@
logger.atFine().log(
"Evaluate submit rules for change %d (caller: %s)",
cd.change().getId().get(), callerFinder.findCallerLazy());
- try (Timer0.Context ignored = submitRuleEvaluationLatency.start()) {
- Change change;
- ProjectState projectState;
- try {
- change = cd.change();
- if (change == null) {
- throw new StorageException("Change not found");
- }
-
- Project.NameKey name = cd.project();
- Optional<ProjectState> projectStateOptional = projectCache.get(name);
- if (!projectStateOptional.isPresent()) {
- throw new NoSuchProjectException(name);
- }
- projectState = projectStateOptional.get();
- } catch (NoSuchProjectException e) {
- throw new IllegalStateException("Unable to find project while evaluating submit rule", e);
+ try (Timer0.Context ignored = metrics.submitRuleEvaluationLatency.start()) {
+ if (cd.change() == null) {
+ throw new StorageException("Change not found");
}
- if (change.isClosed() && (!opts.recomputeOnClosedChanges() || OnlineReindexMode.isActive())) {
+ ProjectState projectState =
+ projectCache
+ .get(cd.project())
+ .orElseThrow(
+ () ->
+ new IllegalStateException(
+ "Unable to find project while evaluating submit rule",
+ new NoSuchProjectException(cd.project())));
+
+ if (cd.change().isClosed()
+ && (!opts.recomputeOnClosedChanges() || OnlineReindexMode.isActive())) {
return cd.notes().getSubmitRecords().stream()
.map(
r -> {
@@ -173,7 +179,7 @@
* @return record from the evaluated rules.
*/
public SubmitTypeRecord getSubmitType(ChangeData cd) {
- try (Timer0.Context ignored = submitTypeEvaluationLatency.start()) {
+ try (Timer0.Context ignored = metrics.submitTypeEvaluationLatency.start()) {
try {
Project.NameKey name = cd.project();
Optional<ProjectState> project = projectCache.get(name);
diff --git a/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java b/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
index eef913e..d812eef 100644
--- a/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
+++ b/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
@@ -34,6 +34,7 @@
import com.google.gerrit.server.notedb.Sequences;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
/**
* Query processor for the account index.
@@ -46,6 +47,14 @@
private final Sequences sequences;
private final IndexConfig indexConfig;
+ @Singleton
+ protected static class AccountQueryMetrics extends QueryProcessor.Metrics {
+ @Inject
+ protected AccountQueryMetrics(MetricMaker metricMaker) {
+ super(metricMaker);
+ }
+ }
+
static {
// It is assumed that basic rewrites do not touch visibleto predicates.
checkState(
@@ -57,14 +66,14 @@
protected AccountQueryProcessor(
Provider<CurrentUser> userProvider,
AccountLimits.Factory limitsFactory,
- MetricMaker metricMaker,
+ AccountQueryMetrics accountQueryMetrics,
IndexConfig indexConfig,
AccountIndexCollection indexes,
AccountIndexRewriter rewriter,
AccountControl.Factory accountControlFactory,
Sequences sequences) {
super(
- metricMaker,
+ accountQueryMetrics,
AccountSchemaDefinitions.INSTANCE,
indexConfig,
indexes,
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index 81aad7f..ab9d690 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -37,8 +37,10 @@
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.UsedAt;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AttentionSetUpdate;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.entities.Comment;
@@ -241,6 +243,19 @@
return assistedFactory.create(project, id, null, null);
}
+ public ChangeData create(Project.NameKey project, Change.Id id, ObjectId metaRevision) {
+ ChangeData cd = assistedFactory.create(project, id, null, null);
+ cd.setMetaRevision(metaRevision);
+ return cd;
+ }
+
+ public ChangeData createNonPrivate(BranchNameKey branch, Change.Id id, ObjectId metaRevision) {
+ ChangeData cd = create(branch.project(), id, metaRevision);
+ cd.branch = branch.branch();
+ cd.isPrivate = false;
+ return cd;
+ }
+
public ChangeData create(Change change) {
return assistedFactory.create(change.getProject(), change.getId(), change, null);
}
@@ -379,6 +394,8 @@
private PatchSet currentPatchSet;
private Collection<PatchSet> patchSets;
private ListMultimap<PatchSet.Id, PatchSetApproval> allApprovals;
+
+ private ListMultimap<PatchSet.Id, PatchSetApproval> allApprovalsWithCopied;
private List<PatchSetApproval> currentApprovals;
private List<String> currentFiles;
private Optional<DiffSummary> diffSummary;
@@ -388,7 +405,10 @@
private List<ChangeMessage> messages;
private Optional<ChangedLines> changedLines;
private SubmitTypeRecord submitTypeRecord;
+ private String branch;
+ private Boolean isPrivate;
private Boolean mergeable;
+ private ObjectId metaRevision;
private Set<String> hashtags;
private ImmutableMap<String, String> customKeyedValues;
/**
@@ -425,6 +445,7 @@
private String gerritServerId;
private String changeServerId;
private ChangeNumberVirtualIdAlgorithm virtualIdFunc;
+ private Boolean failedParsingFromIndex = false;
@Inject
private ChangeData(
@@ -513,6 +534,15 @@
return allUsersName;
}
+ @UsedAt(UsedAt.Project.GOOGLE)
+ public void setFailedParsingFromIndex(Boolean val) {
+ this.failedParsingFromIndex = val;
+ }
+
+ public boolean hasFailedParsingFromIndex() {
+ return failedParsingFromIndex;
+ }
+
@VisibleForTesting
public void setCurrentFilePaths(List<String> filePaths) {
PatchSet ps = currentPatchSet();
@@ -617,6 +647,55 @@
return project;
}
+ public BranchNameKey branchOrThrow() {
+ if (change == null) {
+ if (branch != null) {
+ return BranchNameKey.create(project, branch);
+ }
+ throwIfNotLazyLoad("branch");
+ change();
+ }
+ return change.getDest();
+ }
+
+ public boolean isPrivateOrThrow() {
+ if (change == null) {
+ if (isPrivate != null) {
+ return isPrivate;
+ }
+ throwIfNotLazyLoad("isPrivate");
+ change();
+ }
+ return change.isPrivate();
+ }
+
+ public ChangeData setMetaRevision(ObjectId metaRevision) {
+ this.metaRevision = metaRevision;
+ return this;
+ }
+
+ public ObjectId metaRevisionOrThrow() {
+ if (notes == null) {
+ if (metaRevision != null) {
+ return metaRevision;
+ }
+ if (refStates != null) {
+ Set<RefState> refs = refStates.get(project);
+ if (refs != null) {
+ String metaRef = RefNames.changeMetaRef(getId());
+ for (RefState r : refs) {
+ if (r.ref().equals(metaRef)) {
+ return r.id();
+ }
+ }
+ }
+ }
+ throwIfNotLazyLoad("metaRevision");
+ notes();
+ }
+ return notes.getRevision();
+ }
+
boolean fastIsVisibleTo(CurrentUser user) {
return visibleTo == user;
}
@@ -627,7 +706,7 @@
public Change change() {
if (change == null && lazyload()) {
- reloadChange();
+ loadChange();
}
return change;
}
@@ -637,13 +716,19 @@
}
public Change reloadChange() {
+ metaRevision = null;
+ return loadChange();
+ }
+
+ private Change loadChange() {
try {
- notes = notesFactory.createChecked(project, legacyId);
+ notes = notesFactory.createChecked(project, legacyId, metaRevision);
} catch (NoSuchChangeException e) {
throw new StorageException("Unable to load change " + legacyId, e);
}
change = notes.getChange();
changeServerId = notes.getServerId();
+ metaRevision = null;
setPatchSets(null);
return change;
}
@@ -661,7 +746,8 @@
if (!lazyload()) {
throw new StorageException("ChangeNotes not available, lazyLoad = false");
}
- notes = notesFactory.create(project(), legacyId);
+ notes = notesFactory.create(project(), legacyId, metaRevision);
+ change = notes.getChange();
}
return notes;
}
@@ -868,6 +954,16 @@
return allApprovals;
}
+ public ListMultimap<PatchSet.Id, PatchSetApproval> conditionallyLoadApprovalsWithCopied() {
+ if (allApprovalsWithCopied == null) {
+ if (!lazyload()) {
+ return ImmutableListMultimap.of();
+ }
+ allApprovalsWithCopied = approvalsUtil.byChangeIncludingCopiedApprovals(notes());
+ }
+ return allApprovalsWithCopied;
+ }
+
/* @return legacy submit ('SUBM') approval label */
// TODO(mariasavtchouk): Deprecate legacy submit label,
// see com.google.gerrit.entities.LabelId.LEGACY_SUBMIT_NAME
@@ -877,11 +973,7 @@
public ReviewerSet reviewers() {
if (reviewers == null) {
- if (!lazyload()) {
- // We are not allowed to load values from NoteDb. Reviewers were not populated with values
- // from the index. However, we need these values for permission checks.
- throw new IllegalStateException("reviewers not populated");
- }
+ throwIfNotLazyLoad("reviewers");
reviewers = approvalsUtil.getReviewers(notes());
}
return reviewers;
@@ -1435,6 +1527,14 @@
this.refStatePatterns = ImmutableList.copyOf(refStatePatterns);
}
+ private void throwIfNotLazyLoad(String field) {
+ if (!lazyload()) {
+ // We are not allowed to load values from NoteDb. 'field' was not populated, however,
+ // we need this value for permission checks.
+ throw new IllegalStateException("'" + field + "' field not populated");
+ }
+ }
+
@AutoValue
abstract static class ReviewedByEvent {
private static ReviewedByEvent create(ChangeMessage msg) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index f8a4a99..51f43ce 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -1165,6 +1165,16 @@
}
@Operator
+ public Predicate<ChangeData> d(String text) throws QueryParseException {
+ return message(text);
+ }
+
+ @Operator
+ public Predicate<ChangeData> description(String text) throws QueryParseException {
+ return message(text);
+ }
+
+ @Operator
public Predicate<ChangeData> message(String text) throws QueryParseException {
if (text.startsWith("^")) {
checkFieldAvailable(
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java b/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
index b7dc127..2979170 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
@@ -43,6 +43,7 @@
import com.google.gerrit.server.notedb.Sequences;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -66,6 +67,14 @@
private final Sequences sequences;
private final IndexConfig indexConfig;
+ @Singleton
+ protected static class ChangeQueryMetrics extends QueryProcessor.Metrics {
+ @Inject
+ protected ChangeQueryMetrics(MetricMaker metricMaker) {
+ super(metricMaker);
+ }
+ }
+
static {
// It is assumed that basic rewrites do not touch visibleto predicates.
checkState(
@@ -77,7 +86,7 @@
ChangeQueryProcessor(
Provider<CurrentUser> userProvider,
AccountLimits.Factory limitsFactory,
- MetricMaker metricMaker,
+ ChangeQueryMetrics changeQueryMetrics,
IndexConfig indexConfig,
ChangeIndexCollection indexes,
ChangeIndexRewriter rewriter,
@@ -85,7 +94,7 @@
ChangeIsVisibleToPredicate.Factory changeIsVisibleToPredicateFactory,
DynamicSet<ChangePluginDefinedInfoFactory> changePluginDefinedInfoFactories) {
super(
- metricMaker,
+ changeQueryMetrics,
ChangeSchemaDefinitions.INSTANCE,
indexConfig,
indexes,
@@ -115,9 +124,16 @@
int pageSize,
int pageSizeMultiplier,
int limit,
+ boolean allowIncompleteResults,
Set<String> requestedFields) {
return IndexedChangeQuery.createOptions(
- indexConfig, start, pageSize, pageSizeMultiplier, limit, requestedFields);
+ indexConfig,
+ start,
+ pageSize,
+ pageSizeMultiplier,
+ limit,
+ allowIncompleteResults,
+ requestedFields);
}
@Override
diff --git a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index 961404a..d21f5b6 100644
--- a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -303,7 +303,7 @@
rw,
c,
d.patchSets(),
- includeApprovals ? d.approvals().asMap() : null,
+ includeApprovals ? d.conditionallyLoadApprovalsWithCopied().asMap() : null,
includeFiles,
d.change(),
labelTypes,
diff --git a/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java b/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
index 344a978..74c8d39 100644
--- a/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
+++ b/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
@@ -34,6 +34,7 @@
import com.google.gerrit.server.notedb.Sequences;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
/**
* Query processor for the group index.
@@ -47,6 +48,14 @@
private final Sequences sequences;
private final IndexConfig indexConfig;
+ @Singleton
+ protected static class GroupQueryMetrics extends QueryProcessor.Metrics {
+ @Inject
+ protected GroupQueryMetrics(MetricMaker metricMaker) {
+ super(metricMaker);
+ }
+ }
+
static {
// It is assumed that basic rewrites do not touch visibleto predicates.
checkState(
@@ -58,14 +67,14 @@
protected GroupQueryProcessor(
Provider<CurrentUser> userProvider,
AccountLimits.Factory limitsFactory,
- MetricMaker metricMaker,
+ GroupQueryMetrics groupQueryMetrics,
IndexConfig indexConfig,
GroupIndexCollection indexes,
GroupIndexRewriter rewriter,
GroupControl.GenericFactory groupControlFactory,
Sequences sequences) {
super(
- metricMaker,
+ groupQueryMetrics,
GroupSchemaDefinitions.INSTANCE,
indexConfig,
indexes,
diff --git a/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java b/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java
index 3877c25..ddc7ccc 100644
--- a/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java
+++ b/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java
@@ -34,6 +34,7 @@
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
/**
* Query processor for the project index.
@@ -49,6 +50,14 @@
private final ProjectCache projectCache;
private final IndexConfig indexConfig;
+ @Singleton
+ protected static class ProjectQueryMetrics extends QueryProcessor.Metrics {
+ @Inject
+ protected ProjectQueryMetrics(MetricMaker metricMaker) {
+ super(metricMaker);
+ }
+ }
+
static {
// It is assumed that basic rewrites do not touch visibleto predicates.
checkState(
@@ -60,14 +69,14 @@
protected ProjectQueryProcessor(
Provider<CurrentUser> userProvider,
AccountLimits.Factory limitsFactory,
- MetricMaker metricMaker,
+ ProjectQueryMetrics projectQueryMetrics,
IndexConfig indexConfig,
ProjectIndexCollection indexes,
ProjectIndexRewriter rewriter,
PermissionBackend permissionBackend,
ProjectCache projectCache) {
super(
- metricMaker,
+ projectQueryMetrics,
ProjectSchemaDefinitions.INSTANCE,
indexConfig,
indexes,
diff --git a/java/com/google/gerrit/server/restapi/change/QueryChanges.java b/java/com/google/gerrit/server/restapi/change/QueryChanges.java
index 6ce4b39..4d279b0 100644
--- a/java/com/google/gerrit/server/restapi/change/QueryChanges.java
+++ b/java/com/google/gerrit/server/restapi/change/QueryChanges.java
@@ -60,6 +60,7 @@
private Integer start;
private Boolean noLimit;
private Boolean skipVisibility;
+ private Boolean allowIncompleteResults;
@Option(
name = "--query",
@@ -112,6 +113,11 @@
skipVisibility = on;
}
+ @Option(name = "--allow-incomplete-results", usage = "Return partial results")
+ public void setAllowIncompleteResults(boolean allowIncompleteResults) {
+ this.allowIncompleteResults = allowIncompleteResults;
+ }
+
@Override
public void setDynamicBean(String plugin, DynamicOptions.DynamicBean dynamicBean) {
dynamicBeans.put(plugin, dynamicBean);
@@ -181,6 +187,9 @@
if (skipVisibility != null) {
queryProcessor.enforceVisibility(!skipVisibility);
}
+ if (allowIncompleteResults != null) {
+ queryProcessor.setAllowIncompleteResults(allowIncompleteResults);
+ }
dynamicBeans.forEach((p, b) -> queryProcessor.setDynamicBean(p, b));
if (queries == null || queries.isEmpty()) {
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index 167f784..98a3f83 100644
--- a/java/com/google/gerrit/server/restapi/change/Rebase.java
+++ b/java/com/google/gerrit/server/restapi/change/Rebase.java
@@ -29,6 +29,7 @@
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.ChangeResource;
@@ -68,6 +69,7 @@
private final ProjectCache projectCache;
private final PatchSetUtil patchSetUtil;
private final RebaseMetrics rebaseMetrics;
+ private final IdentifiedUser.GenericFactory userFactory;
@Inject
public Rebase(
@@ -78,7 +80,8 @@
PermissionBackend permissionBackend,
ProjectCache projectCache,
PatchSetUtil patchSetUtil,
- RebaseMetrics rebaseMetrics) {
+ RebaseMetrics rebaseMetrics,
+ IdentifiedUser.GenericFactory userFactory) {
this.updateFactory = updateFactory;
this.repoManager = repoManager;
this.rebaseUtil = rebaseUtil;
@@ -87,16 +90,20 @@
this.projectCache = projectCache;
this.patchSetUtil = patchSetUtil;
this.rebaseMetrics = rebaseMetrics;
+ this.userFactory = userFactory;
}
@Override
public Response<ChangeInfo> apply(RevisionResource rsrc, RebaseInput input)
throws UpdateException, RestApiException, IOException, PermissionBackendException {
-
+ IdentifiedUser rebaseAsUser;
if (input.onBehalfOfUploader && !rsrc.getPatchSet().uploader().equals(rsrc.getAccountId())) {
+ rebaseAsUser =
+ userFactory.runAs(/*remotePeer= */ null, rsrc.getPatchSet().uploader(), rsrc.getUser());
rsrc.permissions().check(ChangePermission.REBASE_ON_BEHALF_OF_UPLOADER);
- rsrc = rebaseUtil.onBehalfOf(rsrc, input);
+ rebaseUtil.checkCanRebaseOnBehalfOf(rsrc, input);
} else {
+ rebaseAsUser = rsrc.getUser().asIdentifiedUser();
input.onBehalfOfUploader = false;
rsrc.permissions().check(ChangePermission.REBASE);
}
@@ -113,14 +120,16 @@
ObjectReader reader = oi.newReader();
RevWalk rw = CodeReviewCommit.newRevWalk(reader);
BatchUpdate bu =
- updateFactory.create(change.getProject(), rsrc.getUser(), TimeUtil.now())) {
+ updateFactory.create(change.getProject(), rebaseAsUser, TimeUtil.now())) {
rebaseUtil.verifyRebasePreconditions(rw, rsrc.getNotes(), rsrc.getPatchSet());
RebaseChangeOp rebaseOp =
rebaseUtil.getRebaseOp(
+ rw,
rsrc,
input,
- rebaseUtil.parseOrFindBaseRevision(repo, rw, permissionBackend, rsrc, input, true));
+ rebaseUtil.parseOrFindBaseRevision(repo, rw, permissionBackend, rsrc, input, true),
+ rebaseAsUser);
// TODO(dborowitz): Why no notification? This seems wrong; dig up blame.
bu.setNotify(NotifyResolver.Result.none());
diff --git a/java/com/google/gerrit/server/restapi/change/RebaseChain.java b/java/com/google/gerrit/server/restapi/change/RebaseChain.java
index 343fb72..76c5253 100644
--- a/java/com/google/gerrit/server/restapi/change/RebaseChain.java
+++ b/java/com/google/gerrit/server/restapi/change/RebaseChain.java
@@ -36,6 +36,7 @@
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.ChangeResource;
@@ -89,6 +90,7 @@
private final PatchSetUtil patchSetUtil;
private final ChangeJson.Factory json;
private final RebaseMetrics rebaseMetrics;
+ private final IdentifiedUser.GenericFactory userFactory;
@Inject
RebaseChain(
@@ -103,7 +105,8 @@
ProjectCache projectCache,
PatchSetUtil patchSetUtil,
ChangeJson.Factory json,
- RebaseMetrics rebaseMetrics) {
+ RebaseMetrics rebaseMetrics,
+ IdentifiedUser.GenericFactory userFactory) {
this.repoManager = repoManager;
this.getRelatedChangesUtil = getRelatedChangesUtil;
this.changeDataFactory = changeDataFactory;
@@ -116,11 +119,18 @@
this.patchSetUtil = patchSetUtil;
this.json = json;
this.rebaseMetrics = rebaseMetrics;
+ this.userFactory = userFactory;
}
@Override
public Response<RebaseChainInfo> apply(ChangeResource tipRsrc, RebaseInput input)
throws IOException, PermissionBackendException, RestApiException, UpdateException {
+ IdentifiedUser rebaseAsUser;
+ if (input.committerEmail != null) {
+ // TODO: committer_email can be supported if all changes in the chain
+ // belong to the same uploader. It can be attempted in future as needed.
+ throw new BadRequestException("committer_email is not supported when rebasing a chain");
+ }
if (input.onBehalfOfUploader) {
tipRsrc.permissions().check(ChangePermission.REBASE_ON_BEHALF_OF_UPLOADER);
if (input.allowConflicts) {
@@ -160,10 +170,14 @@
new RevisionResource(changeResourceFactory.create(changeData, user), ps);
if (input.onBehalfOfUploader
&& !revRsrc.getPatchSet().uploader().equals(revRsrc.getAccountId())) {
- revRsrc = rebaseUtil.onBehalfOf(revRsrc, input);
+ rebaseAsUser =
+ userFactory.runAs(
+ /*remotePeer= */ null, revRsrc.getPatchSet().uploader(), revRsrc.getUser());
+ rebaseUtil.checkCanRebaseOnBehalfOf(revRsrc, input);
revRsrc.permissions().check(ChangePermission.REBASE_ON_BEHALF_OF_UPLOADER);
anyRebaseOnBehalfOfUploader = true;
} else {
+ rebaseAsUser = revRsrc.getUser().asIdentifiedUser();
revRsrc.permissions().check(ChangePermission.REBASE);
}
rebaseUtil.verifyRebasePreconditions(rw, changeData.notes(), ps);
@@ -177,7 +191,7 @@
if (currentBase(rw, ps).equals(desiredBase)) {
isUpToDate = true;
} else {
- rebaseOp = rebaseUtil.getRebaseOp(revRsrc, input, desiredBase);
+ rebaseOp = rebaseUtil.getRebaseOp(rw, revRsrc, input, desiredBase, rebaseAsUser);
}
} else {
if (ancestorsAreUpToDate) {
@@ -187,7 +201,8 @@
isUpToDate = currentBase(rw, ps).equals(latestCommittedBase);
}
if (!isUpToDate) {
- rebaseOp = rebaseUtil.getRebaseOp(revRsrc, input, chain.get(i - 1).id());
+ rebaseOp =
+ rebaseUtil.getRebaseOp(rw, revRsrc, input, chain.get(i - 1).id(), rebaseAsUser);
}
}
@@ -196,7 +211,7 @@
continue;
}
ancestorsAreUpToDate = false;
- bu.addOp(revRsrc.getChange().getId(), revRsrc.getUser(), rebaseOp);
+ bu.addOp(revRsrc.getChange().getId(), rebaseAsUser, rebaseOp);
rebaseOps.put(revRsrc.getChange().getId(), rebaseOp);
}
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index 39165d0..9e0eac7 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -52,6 +52,7 @@
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.GerritInstanceId;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.documentation.QueryDocumentationExecutor;
@@ -92,6 +93,7 @@
private final ProjectCache projectCache;
private final AgreementJson agreementJson;
private final SitePaths sitePaths;
+ private final @Nullable @GerritInstanceId String instanceId;
@Inject
public GetServerInfo(
@@ -113,7 +115,8 @@
QueryDocumentationExecutor docSearcher,
ProjectCache projectCache,
AgreementJson agreementJson,
- SitePaths sitePaths) {
+ SitePaths sitePaths,
+ @Nullable @GerritInstanceId String instanceId) {
this.config = config;
this.accountVisibilityProvider = accountVisibilityProvider;
this.accountDefaultDisplayName = accountDefaultDisplayName;
@@ -133,6 +136,7 @@
this.projectCache = projectCache;
this.agreementJson = agreementJson;
this.sitePaths = sitePaths;
+ this.instanceId = instanceId;
}
@Override
@@ -289,7 +293,7 @@
info.editGpgKeys =
toBoolean(enableSignedPush && config.getBoolean("gerrit", null, "editGpgKeys", true));
info.primaryWeblinkName = config.getString("gerrit", null, "primaryWeblinkName");
- info.instanceId = config.getString("gerrit", null, "instanceId");
+ info.instanceId = instanceId;
info.defaultBranch = config.getString("gerrit", null, "defaultBranch");
return info;
}
diff --git a/java/com/google/gerrit/server/restapi/project/CommitsCollection.java b/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
index 09951b2..e0c699a 100644
--- a/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
@@ -143,37 +143,41 @@
if (!changes.isEmpty()) {
return true;
}
+ if (commit.getParents() != null && commit.getParents().length > 0) {
+ // Maybe the commit was a merge commit of a change. Try to find promising candidates for
+ // branches to check, by seeing if its parents were associated to changes.
+ // Only request changes from the index if the commit has parents. If size(parents) == 0, then
+ // the query does not make sense (it would request all changes from the project).
+ ImmutableList<Predicate<ChangeData>> parentPredicates =
+ Arrays.stream(commit.getParents())
+ .map(parent -> ChangePredicates.commitPrefix(parent.getId().getName()))
+ .collect(toImmutableList());
+ Predicate<ChangeData> pred =
+ Predicate.and(ChangePredicates.project(project), Predicate.or(parentPredicates));
+ changes =
+ retryHelper
+ .changeIndexQuery(
+ "queryChangesByProjectCommit", q -> q.enforceVisibility(true).query(pred))
+ .call();
+ Set<Ref> branchesForCommitParents = new HashSet<>(changes.size());
+ for (ChangeData cd : changes) {
+ Ref ref = repo.exactRef(cd.change().getDest().branch());
+ if (ref != null) {
+ branchesForCommitParents.add(ref);
+ }
+ }
- // Maybe the commit was a merge commit of a change. Try to find promising candidates for
- // branches to check, by seeing if its parents were associated to changes.
- Predicate<ChangeData> pred =
- Predicate.and(
- ChangePredicates.project(project),
- Predicate.or(
- Arrays.stream(commit.getParents())
- .map(parent -> ChangePredicates.commitPrefix(parent.getId().getName()))
- .collect(toImmutableList())));
- changes =
- retryHelper
- .changeIndexQuery(
- "queryChangesByProjectCommit", q -> q.enforceVisibility(true).query(pred))
- .call();
-
- Set<Ref> branchesForCommitParents = new HashSet<>(changes.size());
- for (ChangeData cd : changes) {
- Ref ref = repo.exactRef(cd.change().getDest().branch());
- if (ref != null) {
- branchesForCommitParents.add(ref);
+ if (reachable.fromRefs(
+ project, repo, commit, branchesForCommitParents.stream().collect(Collectors.toList()))) {
+ return true;
}
}
+ // This check covers 2 situations:
+ // 1) The commit does not have any parents. Check if it is visible from any ref in the project.
+ // Exclude change refs, since it is confirmed the commit is not a patchset of any change.
- if (reachable.fromRefs(
- project, repo, commit, branchesForCommitParents.stream().collect(Collectors.toList()))) {
- return true;
- }
-
- // If we have already checked change refs using the change index, spare any further checks for
- // changes.
+ // 2) If we have already checked change refs using the change index, spare any further checks
+ // for changes.
List<Ref> refs =
repo.getRefDatabase()
.getRefsByPrefixWithExclusions(RefDatabase.ALL, ImmutableSet.of(RefNames.REFS_CHANGES));
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteRef.java b/java/com/google/gerrit/server/restapi/project/DeleteRef.java
index 4fc2b86..388946e 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteRef.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteRef.java
@@ -117,9 +117,12 @@
.check(RefPermission.DELETE);
try (Repository repository = repoManager.openRepository(projectState.getNameKey())) {
- RefUpdate.Result result;
+ Ref refObj = repository.exactRef(ref);
+ if (refObj == null) {
+ throw new ResourceConflictException(String.format("ref %s doesn't exist", ref));
+ }
RefUpdate u = repository.updateRef(ref);
- u.setExpectedOldObjectId(repository.exactRef(ref).getObjectId());
+ u.setExpectedOldObjectId(refObj.getObjectId());
u.setNewObjectId(ObjectId.zeroId());
u.setForceUpdate(true);
refDeletionValidator.validateRefOperation(
@@ -127,7 +130,7 @@
identifiedUser.get(),
u,
/* pushOptions */ ImmutableListMultimap.of());
- result = u.delete();
+ RefUpdate.Result result = u.delete();
switch (result) {
case NEW:
diff --git a/java/com/google/gerrit/server/schema/AllProjectsCreator.java b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
index dc83d4a..123a873 100644
--- a/java/com/google/gerrit/server/schema/AllProjectsCreator.java
+++ b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
@@ -22,6 +22,8 @@
import static com.google.gerrit.server.schema.AclUtil.grant;
import static com.google.gerrit.server.schema.AclUtil.rule;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.INIT_REPO;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.gerrit.common.Version;
import com.google.gerrit.common.data.GlobalCapability;
@@ -32,13 +34,12 @@
import com.google.gerrit.entities.PermissionRule.Action;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.Sequence;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.notedb.RepoSequence;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.update.context.RefUpdateContext;
import com.google.inject.Inject;
@@ -48,6 +49,7 @@
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
@@ -242,16 +244,22 @@
private void initSequences(Repository git, BatchRefUpdate bru, int firstChangeId)
throws IOException {
- if (git.exactRef(REFS_SEQUENCES + Sequences.NAME_CHANGES) == null) {
+ if (git.exactRef(REFS_SEQUENCES + Sequence.NAME_CHANGES) == null) {
// Can't easily reuse the inserter from MetaDataUpdate, but this shouldn't slow down site
// initialization unduly.
try (ObjectInserter ins = git.newObjectInserter()) {
- bru.addCommand(RepoSequence.storeNew(ins, Sequences.NAME_CHANGES, firstChangeId));
+ bru.addCommand(createNewChangeSequence(ins, firstChangeId));
ins.flush();
}
}
}
+ private ReceiveCommand createNewChangeSequence(ObjectInserter ins, int val) throws IOException {
+ ObjectId newId = ins.insert(OBJ_BLOB, Integer.toString(val).getBytes(UTF_8));
+ return new ReceiveCommand(
+ ObjectId.zeroId(), newId, RefNames.REFS_SEQUENCES + Sequence.NAME_CHANGES);
+ }
+
private void execute(Repository git, BatchRefUpdate bru) throws IOException {
try (RevWalk rw = new RevWalk(git)) {
bru.execute(rw, NullProgressMonitor.INSTANCE);
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java b/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java
index 57ec7ef..d60a10a 100644
--- a/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java
@@ -23,10 +23,10 @@
import com.google.common.collect.ImmutableSortedSet;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.server.Sequence;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.update.context.RefUpdateContext;
import com.google.inject.Inject;
import java.io.IOException;
@@ -151,7 +151,7 @@
// In this case the server literally will not start under 2.16. We assume the user will fix
// this and get 2.16 running rather than abandoning 2.16 and jumping to 3.0 at this point.
try (Repository allUsers = repoManager.openRepository(allUsersName)) {
- if (allUsers.exactRef(RefNames.REFS_SEQUENCES + Sequences.NAME_GROUPS) == null) {
+ if (allUsers.exactRef(RefNames.REFS_SEQUENCES + Sequence.NAME_GROUPS) == null) {
throw new StorageException(
"You appear to be upgrading to 3.x from a version prior to 2.16; you must upgrade to"
+ " 2.16.x first");
diff --git a/java/com/google/gerrit/server/schema/SchemaCreatorImpl.java b/java/com/google/gerrit/server/schema/SchemaCreatorImpl.java
index 38e45ab..56c6fa8 100644
--- a/java/com/google/gerrit/server/schema/SchemaCreatorImpl.java
+++ b/java/com/google/gerrit/server/schema/SchemaCreatorImpl.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.schema;
+import static com.google.gerrit.server.Sequence.LightweightGroups;
+
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.AccountGroup;
@@ -21,8 +23,8 @@
import com.google.gerrit.entities.InternalGroup;
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.Sequence;
import com.google.gerrit.server.account.GroupUuid;
import com.google.gerrit.server.account.ServiceUserClassifier;
import com.google.gerrit.server.config.AllProjectsName;
@@ -37,7 +39,6 @@
import com.google.gerrit.server.group.db.InternalGroupCreation;
import com.google.gerrit.server.index.group.GroupIndex;
import com.google.gerrit.server.index.group.GroupIndexCollection;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.update.context.RefUpdateContext;
import com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType;
import com.google.inject.Inject;
@@ -45,7 +46,6 @@
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
@@ -58,12 +58,10 @@
private final AllProjectsCreator allProjectsCreator;
private final AllUsersCreator allUsersCreator;
private final AllUsersName allUsersName;
+ private final Sequence groupsSequence;
private final PersonIdent serverUser;
private final GroupIndexCollection indexCollection;
private final String serverId;
-
- private final Config config;
- private final MetricMaker metricMaker;
private final AllProjectsName allProjectsName;
@Inject
@@ -72,23 +70,21 @@
AllProjectsCreator ap,
AllUsersCreator auc,
AllUsersName allUsersName,
+ @LightweightGroups Sequence groupsSequence,
@GerritPersonIdent PersonIdent au,
GroupIndexCollection ic,
String serverId,
- Config config,
- MetricMaker metricMaker,
AllProjectsName apName) {
this.repoManager = repoManager;
allProjectsCreator = ap;
allUsersCreator = auc;
this.allUsersName = allUsersName;
+ this.groupsSequence = groupsSequence;
serverUser = au;
indexCollection = ic;
this.serverId = serverId;
- this.config = config;
this.allProjectsName = apName;
- this.metricMaker = metricMaker;
}
@Override
@@ -106,19 +102,9 @@
// We have to create the All-Users repository before we can use it to store the groups in it.
allUsersCreator.setAdministrators(admins).create();
- // Don't rely on injection to construct Sequences, as the default GitReferenceUpdated has a
- // thick dependency stack which may not all be available at schema creation time.
- Sequences seqs =
- new Sequences(
- config,
- repoManager,
- GitReferenceUpdated.DISABLED,
- allProjectsName,
- allUsersName,
- metricMaker);
try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
- createAdminsGroup(seqs, allUsersRepo, admins);
- createBatchUsersGroup(seqs, allUsersRepo, serviceUsers, admins.getUUID());
+ createAdminsGroup(allUsersRepo, admins);
+ createBatchUsersGroup(allUsersRepo, serviceUsers, admins.getUUID());
}
}
}
@@ -132,10 +118,9 @@
}
}
- private void createAdminsGroup(
- Sequences seqs, Repository allUsersRepo, GroupReference groupReference)
+ private void createAdminsGroup(Repository allUsersRepo, GroupReference groupReference)
throws IOException, ConfigInvalidException {
- InternalGroupCreation groupCreation = getGroupCreation(seqs, groupReference);
+ InternalGroupCreation groupCreation = getGroupCreation(groupReference);
GroupDelta groupDelta =
GroupDelta.builder().setDescription("Gerrit Site Administrators").build();
@@ -143,12 +128,9 @@
}
private void createBatchUsersGroup(
- Sequences seqs,
- Repository allUsersRepo,
- GroupReference groupReference,
- AccountGroup.UUID adminsGroupUuid)
+ Repository allUsersRepo, GroupReference groupReference, AccountGroup.UUID adminsGroupUuid)
throws IOException, ConfigInvalidException {
- InternalGroupCreation groupCreation = getGroupCreation(seqs, groupReference);
+ InternalGroupCreation groupCreation = getGroupCreation(groupReference);
GroupDelta groupDelta =
GroupDelta.builder()
.setDescription("Users who perform batch actions on Gerrit")
@@ -223,8 +205,8 @@
return GroupReference.create(groupUuid, name);
}
- private InternalGroupCreation getGroupCreation(Sequences seqs, GroupReference groupReference) {
- int next = seqs.nextGroupId();
+ private InternalGroupCreation getGroupCreation(GroupReference groupReference) {
+ int next = groupsSequence.next();
return InternalGroupCreation.builder()
.setNameKey(AccountGroup.nameKey(groupReference.getName()))
.setId(AccountGroup.id(next))
diff --git a/java/com/google/gerrit/server/schema/SchemaModule.java b/java/com/google/gerrit/server/schema/SchemaModule.java
index 9593522..7afc1f5 100644
--- a/java/com/google/gerrit/server/schema/SchemaModule.java
+++ b/java/com/google/gerrit/server/schema/SchemaModule.java
@@ -14,12 +14,14 @@
package com.google.gerrit.server.schema;
+import static com.google.gerrit.server.Sequence.LightweightGroups;
import static com.google.inject.Scopes.SINGLETON;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.GerritPersonIdentProvider;
+import com.google.gerrit.server.Sequence;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.config.AllUsersName;
@@ -31,6 +33,7 @@
import com.google.gerrit.server.config.GerritServerId;
import com.google.gerrit.server.config.GerritServerIdProvider;
import com.google.gerrit.server.index.group.GroupIndexCollection;
+import com.google.gerrit.server.notedb.RepoSequence.DisabledGitRefUpdatedRepoGroupsSequenceProvider;
import com.google.inject.TypeLiteral;
import org.eclipse.jgit.lib.PersonIdent;
@@ -64,6 +67,9 @@
// SchemaCreatorImpl, so it's needed.
// TODO(dborowitz): Is there any way to untangle this?
bind(GroupIndexCollection.class);
+ bind(Sequence.class)
+ .annotatedWith(LightweightGroups.class)
+ .toProvider(DisabledGitRefUpdatedRepoGroupsSequenceProvider.class);
bind(SchemaCreator.class).to(SchemaCreatorImpl.class);
}
}
diff --git a/java/com/google/gerrit/server/submit/CommitMergeStatus.java b/java/com/google/gerrit/server/submit/CommitMergeStatus.java
index 4638bfa..f7af684 100644
--- a/java/com/google/gerrit/server/submit/CommitMergeStatus.java
+++ b/java/com/google/gerrit/server/submit/CommitMergeStatus.java
@@ -52,7 +52,7 @@
"Marking change merged without cherry-picking to branch, as the resulting commit would be"
+ " empty."),
- MISSING_DEPENDENCY("Depends on change that was not submitted."),
+ MISSING_DEPENDENCY("Depends on commit that cannot be merged."),
MANUAL_RECURSIVE_MERGE(
"The change requires a local merge to resolve.\n"
diff --git a/java/com/google/gerrit/sshd/InactiveAccountDisconnector.java b/java/com/google/gerrit/sshd/InactiveAccountDisconnector.java
index 1086626..2f96915 100644
--- a/java/com/google/gerrit/sshd/InactiveAccountDisconnector.java
+++ b/java/com/google/gerrit/sshd/InactiveAccountDisconnector.java
@@ -39,7 +39,9 @@
sshDaemon,
(sshId, sshSession, abstractSession, ioSession) -> {
CurrentUser sessionUser = sshSession.getUser();
- if (sessionUser.isIdentifiedUser() && sessionUser.getAccountId().get() == id) {
+ if (sessionUser != null
+ && sessionUser.isIdentifiedUser()
+ && sessionUser.getAccountId().get() == id) {
logger.atInfo().log(
"Disconnecting SSH session %s because user %s(%d) got deactivated",
abstractSession, sessionUser.getLoggableName(), id);
diff --git a/java/com/google/gerrit/sshd/SshPluginStarterCallback.java b/java/com/google/gerrit/sshd/SshPluginStarterCallback.java
index 8711fe6..f807b19 100644
--- a/java/com/google/gerrit/sshd/SshPluginStarterCallback.java
+++ b/java/com/google/gerrit/sshd/SshPluginStarterCallback.java
@@ -25,6 +25,10 @@
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.internal.MoreTypes;
+import java.util.ArrayList;
+import java.util.List;
import org.apache.sshd.server.command.Command;
@Singleton
@@ -65,9 +69,9 @@
try {
return plugin.getSshInjector().getProvider(key);
} catch (RuntimeException err) {
- if (!providesDynamicOptions(plugin)) {
+ if (!providesDynamicOptions(plugin) && !providesCommandInterceptor(plugin)) {
logger.atWarning().withCause(err).log(
- "Plugin %s did not define its top-level command nor any DynamicOptions",
+ "Plugin %s did not define its top-level command, any DynamicOptions, nor any Ssh*CommandInterceptors",
plugin.getName());
}
}
@@ -78,4 +82,16 @@
private boolean providesDynamicOptions(Plugin plugin) {
return dynamicBeans.plugins().contains(plugin.getName());
}
+
+ private boolean providesCommandInterceptor(Plugin plugin) {
+ List<TypeLiteral<?>> typeLiterals = new ArrayList<>(2);
+ typeLiterals.add(
+ MoreTypes.canonicalizeForKey(
+ (TypeLiteral<?>) TypeLiteral.get(SshExecuteCommandInterceptor.class)));
+ typeLiterals.add(
+ MoreTypes.canonicalizeForKey(
+ (TypeLiteral<?>) TypeLiteral.get(SshCreateCommandInterceptor.class)));
+ return plugin.getSshInjector().getAllBindings().keySet().stream()
+ .anyMatch(key -> typeLiterals.contains(key.getTypeLiteral()));
+ }
}
diff --git a/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java b/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
index 4f23d1d..f42eb5c 100644
--- a/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
+++ b/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
@@ -27,6 +27,8 @@
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.restapi.change.DeleteReviewer;
import com.google.gerrit.server.restapi.change.PostReviewers;
+import com.google.gerrit.server.update.RetryHelper;
+import com.google.gerrit.server.update.RetryableAction;
import com.google.gerrit.sshd.ChangeArgumentParser;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
@@ -89,6 +91,8 @@
@Inject private ChangeArgumentParser changeArgumentParser;
+ @Inject private RetryHelper retryHelper;
+
private Set<Account.Id> toRemove = new HashSet<>();
private Map<Change.Id, ChangeResource> changes = new LinkedHashMap<>();
@@ -121,7 +125,15 @@
ReviewerResource rsrc = reviewerFactory.create(changeRsrc, reviewer);
String error = null;
try {
- deleteReviewer.apply(rsrc, new DeleteReviewerInput());
+ retryHelper
+ .action(
+ RetryableAction.ActionType.CHANGE_UPDATE,
+ "removeReviewers",
+ () -> {
+ deleteReviewer.apply(rsrc, new DeleteReviewerInput());
+ return null;
+ })
+ .call();
} catch (ResourceNotFoundException e) {
error = String.format("could not remove %s: not found", reviewer);
} catch (Exception e) {
@@ -139,15 +151,26 @@
ReviewerInput input = new ReviewerInput();
input.reviewer = reviewer;
input.confirmed = true;
- String error;
+ var error =
+ new Object() {
+ String value;
+ };
try {
- error = postReviewers.apply(changeRsrc, input).value().error;
+ retryHelper
+ .action(
+ RetryableAction.ActionType.CHANGE_UPDATE,
+ "applyReview",
+ () -> {
+ error.value = postReviewers.apply(changeRsrc, input).value().error;
+ return null;
+ })
+ .call();
} catch (Exception e) {
- error = String.format("could not add %s: %s", reviewer, e.getMessage());
+ error.value = String.format("could not add %s: %s", reviewer, e.getMessage());
}
- if (error != null) {
+ if (error.value != null) {
ok = false;
- writeError("error", error);
+ writeError("error", error.value);
}
}
diff --git a/java/com/google/gerrit/sshd/commands/SetTopicCommand.java b/java/com/google/gerrit/sshd/commands/SetTopicCommand.java
index 244fdbe..b590740 100644
--- a/java/com/google/gerrit/sshd/commands/SetTopicCommand.java
+++ b/java/com/google/gerrit/sshd/commands/SetTopicCommand.java
@@ -16,13 +16,13 @@
import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.extensions.api.changes.TopicInput;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.change.SetTopicOp;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.update.BatchUpdate;
-import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.gerrit.server.restapi.change.PutTopic;
import com.google.gerrit.sshd.ChangeArgumentParser;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
@@ -34,9 +34,8 @@
@CommandMetaData(name = "set-topic", description = "Set the topic for one or more changes")
public class SetTopicCommand extends SshCommand {
- private final BatchUpdate.Factory updateFactory;
private final ChangeArgumentParser changeArgumentParser;
- private final SetTopicOp.Factory topicOpFactory;
+ private final PutTopic putTopic;
private Map<Change.Id, ChangeResource> changes = new LinkedHashMap<>();
@@ -62,17 +61,14 @@
private String topic;
@Inject
- SetTopicCommand(
- BatchUpdate.Factory updateFactory,
- ChangeArgumentParser changeArgumentParser,
- SetTopicOp.Factory topicOpFactory) {
- this.updateFactory = updateFactory;
+ SetTopicCommand(ChangeArgumentParser changeArgumentParser, PutTopic putTopic) {
this.changeArgumentParser = changeArgumentParser;
- this.topicOpFactory = topicOpFactory;
+ this.putTopic = putTopic;
}
@Override
public void run() throws Exception {
+ boolean ok = true;
if (topic != null) {
topic = topic.trim();
}
@@ -83,11 +79,28 @@
}
for (ChangeResource r : changes.values()) {
- SetTopicOp op = topicOpFactory.create(topic);
- try (BatchUpdate u = updateFactory.create(r.getChange().getProject(), user, TimeUtil.now())) {
- u.addOp(r.getId(), op);
- u.execute();
+ TopicInput input = new TopicInput();
+ input.topic = topic;
+ try {
+ putTopic.apply(r, input);
+ } catch (ResourceNotFoundException e) {
+ ok = false;
+ writeError(
+ "error",
+ String.format(
+ "could not add topic to change %d: not found", r.getChange().getChangeId()));
+ } catch (Exception e) {
+ ok = false;
+ writeError(
+ "error",
+ String.format(
+ "could not add topic to change %d: %s",
+ r.getChange().getChangeId(), e.getMessage()));
}
}
+
+ if (!ok) {
+ throw die("one or more updates failed");
+ }
}
}
diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java
index 56fcff5..0f57fac 100644
--- a/java/com/google/gerrit/testing/InMemoryModule.java
+++ b/java/com/google/gerrit/testing/InMemoryModule.java
@@ -16,6 +16,7 @@
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService;
+import static com.google.gerrit.server.Sequence.LightweightGroups;
import static com.google.inject.Scopes.SINGLETON;
import com.google.common.base.Strings;
@@ -44,6 +45,7 @@
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.LibModuleType;
import com.google.gerrit.server.PluginUser;
+import com.google.gerrit.server.Sequence;
import com.google.gerrit.server.account.AccountCacheImpl;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.storage.notedb.AccountNoteDbStorageModule;
@@ -84,10 +86,10 @@
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.config.TrackingFootersProvider;
import com.google.gerrit.server.experiments.ConfigExperimentFeatures.ConfigExperimentFeaturesModule;
+import com.google.gerrit.server.git.ChangesByProjectCache;
import com.google.gerrit.server.git.GarbageCollection;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.PerThreadRequestScope;
-import com.google.gerrit.server.git.SearchingChangeCacheImpl.SearchingChangeCacheImplModule;
import com.google.gerrit.server.git.WorkQueue.WorkQueueModule;
import com.google.gerrit.server.group.testing.TestGroupBackend;
import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
@@ -99,6 +101,7 @@
import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
import com.google.gerrit.server.mail.EmailModule;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier.SignedTokenEmailTokenVerifierModule;
+import com.google.gerrit.server.notedb.RepoSequence.DisabledGitRefUpdatedRepoGroupsSequenceProvider;
import com.google.gerrit.server.patch.DiffExecutor;
import com.google.gerrit.server.permissions.DefaultPermissionBackendModule;
import com.google.gerrit.server.plugins.ServerInformationImpl;
@@ -207,7 +210,7 @@
factory(PluginUser.Factory.class);
install(new PluginApiModule());
install(new DefaultPermissionBackendModule());
- install(new SearchingChangeCacheImplModule());
+ install(new ChangesByProjectCache.Module(ChangesByProjectCache.UseIndex.TRUE, cfg));
factory(GarbageCollection.Factory.class);
install(new AuditModule());
install(new SubscriptionGraphModule());
@@ -312,6 +315,9 @@
.toProvider(AnonymousCowardNameProvider.class);
bind(GroupIndexCollection.class);
+ bind(Sequence.class)
+ .annotatedWith(LightweightGroups.class)
+ .toProvider(DisabledGitRefUpdatedRepoGroupsSequenceProvider.class);
bind(SchemaCreator.class).to(SchemaCreatorImpl.class);
}
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 6e4594c..5af87e8 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -132,6 +132,7 @@
import com.google.gerrit.gpg.testing.TestKey;
import com.google.gerrit.httpd.CacheBasedWebSession;
import com.google.gerrit.server.ExceptionHook;
+import com.google.gerrit.server.Sequence;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountControl;
import com.google.gerrit.server.account.AccountProperties;
@@ -331,7 +332,7 @@
refUpdateCounter.assertRefUpdateFor(
RefUpdateCounter.projectRef(allUsers, RefNames.refsUsers(accountId)),
RefUpdateCounter.projectRef(allUsers, RefNames.REFS_EXTERNAL_IDS),
- RefUpdateCounter.projectRef(allUsers, RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS));
+ RefUpdateCounter.projectRef(allUsers, RefNames.REFS_SEQUENCES + Sequence.NAME_ACCOUNTS));
}
}
@@ -3173,7 +3174,7 @@
@Test
public void deleteAccount_deletesAccountIdentifiers() throws Exception {
- TestAccount deleted = accountCreator.createValid(testMethodName);
+ TestAccount deleted = accountCreator.createValid(name("deleted"));
String secondaryEmail = "secondary@email.com";
gApi.accounts().id(deleted.id().get()).addEmail(newEmailInput(secondaryEmail));
@@ -3218,7 +3219,7 @@
@Test
@UseSsh
public void deleteAccount_deletesSshKeys() throws Exception {
- TestAccount deleted = accountCreator.createValid(testMethodName);
+ TestAccount deleted = accountCreator.createValid(name("deleted"));
requestScopeOperations.setApiUser(deleted.id());
String newKey = TestSshKeys.publicKey(SshSessionFactory.genSshKey(), deleted.email());
gApi.accounts().self().addSshKey(newKey);
@@ -3236,7 +3237,7 @@
@Test
public void deleteAccount_deletesGpgKeys() throws Exception {
- TestAccount deleted = accountCreator.createValid(testMethodName);
+ TestAccount deleted = accountCreator.createValid(name("deleted"));
requestScopeOperations.setApiUser(deleted.id());
addExternalIdEmail(
@@ -3261,7 +3262,7 @@
@Test
public void deleteAccount_deletesStarredChanges() throws Exception {
- TestAccount deleted = accountCreator.createValid(testMethodName);
+ TestAccount deleted = accountCreator.createValid(name("deleted"));
PushOneCommit.Result r = createChange();
String triplet = project.get() + "~master~" + r.getChangeId();
@@ -3291,7 +3292,7 @@
@Test
public void deleteAccount_deletesChangeEdits() throws Exception {
- TestAccount deleted = accountCreator.createValid(testMethodName);
+ TestAccount deleted = accountCreator.createValid(name("deleted"));
PushOneCommit.Result r = createChange();
requestScopeOperations.setApiUser(deleted.id());
@@ -3320,7 +3321,7 @@
@Test
public void deleteAccount_deletesDraftComments() throws Exception {
- TestAccount deleted = accountCreator.createValid(testMethodName);
+ TestAccount deleted = accountCreator.createValid(name("deleted"));
PushOneCommit.Result r = createChange();
requestScopeOperations.setApiUser(deleted.id());
@@ -3351,7 +3352,7 @@
@SuppressWarnings("unused")
public void deleteAccount_deletesReviewedFlags() throws Exception {
PushOneCommit.Result r = createChange();
- TestAccount deleted = accountCreator.createValid(testMethodName);
+ TestAccount deleted = accountCreator.createValid(name("deleted"));
ReviewerInput in = new ReviewerInput();
in.reviewer = deleted.email();
gApi.changes().id(r.getChangeId()).addReviewer(in);
@@ -3373,7 +3374,7 @@
@Test
public void deleteAccount_appliesForSelfById() throws Exception {
- TestAccount deleted = accountCreator.createValid(testMethodName);
+ TestAccount deleted = accountCreator.createValid(name("deleted"));
requestScopeOperations.setApiUser(deleted.id());
gApi.accounts().id(deleted.id().get()).delete();
@@ -3383,7 +3384,7 @@
@Test
public void deleteAccount_throwsForOtherUsers() throws Exception {
- TestAccount deleted = accountCreator.createValid(testMethodName);
+ TestAccount deleted = accountCreator.createValid(name("deleted"));
requestScopeOperations.setApiUser(user.id());
AuthException thrown =
assertThrows(AuthException.class, () -> gApi.accounts().id(deleted.id().get()).delete());
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
index 59ba00b..eed9de8 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
@@ -43,7 +43,7 @@
public class GeneralPreferencesIT extends AbstractDaemonTest {
@Inject private ExtensionRegistry extensionRegistry;
- private TestAccount user42;
+ protected TestAccount user42;
@Before
public void setUp() throws Exception {
@@ -84,6 +84,7 @@
i.muteCommonPathPrefixes ^= true;
i.signedOffBy ^= true;
i.allowBrowserNotifications ^= false;
+ i.diffPageSidebar = "plugin-insight";
i.diffView = DiffView.UNIFIED_DIFF;
i.my = new ArrayList<>();
i.my.add(new MenuItem("name", "url"));
@@ -96,6 +97,7 @@
assertThat(o.changeTable).containsExactlyElementsIn(i.changeTable);
assertThat(o.theme).isEqualTo(i.theme);
assertThat(o.allowBrowserNotifications).isEqualTo(i.allowBrowserNotifications);
+ assertThat(o.diffPageSidebar).isEqualTo(i.diffPageSidebar);
assertThat(o.disableKeyboardShortcuts).isEqualTo(i.disableKeyboardShortcuts);
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 7f52d71..8bdd42f 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -160,6 +160,7 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.git.ObjectIds;
+import com.google.gerrit.httpd.raw.IndexPreloadingUtil;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.query.PostFilterPredicate;
import com.google.gerrit.server.ChangeMessagesUtil;
@@ -238,13 +239,6 @@
@Inject private AccountControl.Factory accountControlFactory;
@Inject private ChangeOperations changeOperations;
- public static final ImmutableSet<ListChangesOption> DASHBOARD_OPTIONS =
- ImmutableSet.of(
- ListChangesOption.LABELS,
- ListChangesOption.DETAILED_ACCOUNTS,
- ListChangesOption.SUBMIT_REQUIREMENTS,
- ListChangesOption.STAR);
-
@Inject
@Named("diff_intraline")
private Cache<IntraLineDiffKey, IntraLineDiff> intraCache;
@@ -3185,7 +3179,7 @@
gApi.changes()
.query()
.withQuery("project:{" + project.get() + "} (status:open OR status:closed)")
- .withOptions(DASHBOARD_OPTIONS)
+ .withOptions(IndexPreloadingUtil.DASHBOARD_OPTIONS)
.get())
.hasSize(2);
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RebaseChainOnBehalfOfUploaderIT.java b/javatests/com/google/gerrit/acceptance/api/change/RebaseChainOnBehalfOfUploaderIT.java
index 7d1ddfc..297579c 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RebaseChainOnBehalfOfUploaderIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RebaseChainOnBehalfOfUploaderIT.java
@@ -100,6 +100,22 @@
}
@Test
+ public void cannotRebaseOnBehalfOfUploaderWithCommitterEmail() throws Exception {
+ Account.Id uploader = accountOperations.newAccount().create();
+ Change.Id changeId = changeOperations.newChange().owner(uploader).create();
+ RebaseInput rebaseInput = new RebaseInput();
+ rebaseInput.onBehalfOfUploader = true;
+ rebaseInput.committerEmail = "admin@example.com";
+ BadRequestException exception =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.changes().id(changeId.get()).rebaseChain(rebaseInput));
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo("committer_email is not supported when rebasing a chain");
+ }
+
+ @Test
public void rebaseChangeOnBehalfOfUploader_withRebasePermission() throws Exception {
testRebaseChainOnBehalfOfUploader(Permission.REBASE);
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java b/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
index d9b079a..c637916 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
@@ -63,6 +63,7 @@
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
@@ -144,6 +145,82 @@
}
@Test
+ public void rebaseWithCommitterEmail() throws Exception {
+ // Create three changes with the same parent
+ PushOneCommit.Result r1 = createChange();
+ testRepo.reset("HEAD~1");
+ PushOneCommit.Result r2 = createChange();
+ testRepo.reset("HEAD~1");
+ PushOneCommit.Result r3 = createChange();
+
+ // Create new user with a secondary email and with permission to rebase
+ Account.Id userWithSecondaryEmail =
+ accountOperations
+ .newAccount()
+ .preferredEmail("preferred@domain.org")
+ .addSecondaryEmail("secondary@domain.org")
+ .create();
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.REBASE).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
+
+ // Approve and submit the r1
+ RevisionApi revision = gApi.changes().id(r1.getChangeId()).current();
+ revision.review(ReviewInput.approve());
+ revision.submit();
+
+ // Rebase r2 as the new user with its primary email
+ RebaseInput ri = new RebaseInput();
+ ri.committerEmail = "preferred@domain.org";
+ requestScopeOperations.setApiUser(userWithSecondaryEmail);
+ rebaseCallWithInput.call(r2.getChangeId(), ri);
+ assertThat(r2.getChange().getCommitter().getEmailAddress()).isEqualTo(ri.committerEmail);
+
+ // Approve and submit the r3
+ requestScopeOperations.setApiUser(admin.id());
+ revision = gApi.changes().id(r3.getChangeId()).current();
+ revision.review(ReviewInput.approve());
+ revision.submit();
+
+ // Rebase r2 as the new user with its secondary email
+ ri = new RebaseInput();
+ ri.committerEmail = "secondary@domain.org";
+ requestScopeOperations.setApiUser(userWithSecondaryEmail);
+ rebaseCallWithInput.call(r2.getChangeId(), ri);
+ assertThat(r2.getChange().getCommitter().getEmailAddress()).isEqualTo(ri.committerEmail);
+ }
+
+ @Test
+ public void cannotRebaseWithInvalidCommitterEmail() throws Exception {
+ // Create two changes both with the same parent
+ PushOneCommit.Result c1 = createChange();
+ testRepo.reset("HEAD~1");
+ PushOneCommit.Result c2 = createChange();
+
+ // Approve and submit the first change
+ RevisionApi revision = gApi.changes().id(c1.getChangeId()).current();
+ revision.review(ReviewInput.approve());
+ revision.submit();
+
+ // Rebase the second change with invalid committer email
+ RebaseInput ri = new RebaseInput();
+ ri.committerEmail = "invalid@example.com";
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> rebaseCallWithInput.call(c2.getChangeId(), ri));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(
+ String.format(
+ "Cannot rebase using committer email '%s' as it is not a registered email of "
+ + "the user on whose behalf the rebase operation is performed",
+ ri.committerEmail));
+ }
+
+ @Test
public void rebaseAbandonedChange() throws Exception {
PushOneCommit.Result r = createChange();
String changeId = r.getChangeId();
@@ -1096,6 +1173,72 @@
assertThat(thrown).hasMessageThat().contains("The whole chain is already up to date.");
}
+ @Override
+ @Test
+ public void rebaseWithCommitterEmail() throws Exception {
+ // Create changes with the following hierarchy:
+ // * HEAD
+ // * r1
+ // * r2
+
+ PushOneCommit.Result r1 = createChange();
+ testRepo.reset("HEAD~1");
+ PushOneCommit.Result r2 = createChange();
+
+ // Approve and submit the first change
+ RevisionApi revision = gApi.changes().id(r1.getChangeId()).current();
+ revision.review(ReviewInput.approve());
+ revision.submit();
+
+ // Create new user with a secondary email and with permission to rebase
+ Account.Id userWithSecondaryEmail =
+ accountOperations
+ .newAccount()
+ .preferredEmail("preferred@domain.org")
+ .addSecondaryEmail("secondary@domain.org")
+ .create();
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.REBASE).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
+
+ // Rebase the chain through r2 with the new user and with its secondary email.
+ RebaseInput ri = new RebaseInput();
+ ri.committerEmail = "secondary@domain.org";
+ requestScopeOperations.setApiUser(userWithSecondaryEmail);
+ BadRequestException exception =
+ assertThrows(
+ BadRequestException.class, () -> gApi.changes().id(r2.getChangeId()).rebaseChain(ri));
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo("committer_email is not supported when rebasing a chain");
+ }
+
+ @Override
+ @Test
+ public void cannotRebaseWithInvalidCommitterEmail() throws Exception {
+ // Create two changes both with the same parent
+ PushOneCommit.Result c1 = createChange();
+ testRepo.reset("HEAD~1");
+ PushOneCommit.Result c2 = createChange();
+
+ // Approve and submit the first change
+ RevisionApi revision = gApi.changes().id(c1.getChangeId()).current();
+ revision.review(ReviewInput.approve());
+ revision.submit();
+
+ // Rebase the second change with invalid committer email
+ RebaseInput ri = new RebaseInput();
+ ri.committerEmail = "invalid@example.com";
+ BadRequestException exception =
+ assertThrows(
+ BadRequestException.class, () -> gApi.changes().id(c2.getChangeId()).rebaseChain(ri));
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo("committer_email is not supported when rebasing a chain");
+ }
+
@Test
public void rebaseChain() throws Exception {
// Create changes with the following hierarchy:
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RebaseOnBehalfOfUploaderIT.java b/javatests/com/google/gerrit/acceptance/api/change/RebaseOnBehalfOfUploaderIT.java
index 968c1f7..319c0cd 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RebaseOnBehalfOfUploaderIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RebaseOnBehalfOfUploaderIT.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
@@ -36,6 +37,7 @@
import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.Change;
@@ -100,6 +102,103 @@
}
@Test
+ public void rebaseOnBehalfOfUploaderWithCommitterEmail() throws Exception {
+ allowPermissionToAllUsers(Permission.REBASE);
+
+ String uploaderPreferredEmail = "uploader.preferred@example.com";
+ String uploaderSecondaryEmail = "uploader.secondary@example.com";
+ Account.Id uploader =
+ accountOperations
+ .newAccount()
+ .preferredEmail(uploaderPreferredEmail)
+ .addSecondaryEmail(uploaderSecondaryEmail)
+ .create();
+ Account.Id approver = admin.id();
+ Account.Id rebaser = accountOperations.newAccount().create();
+
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.VIEW_SECONDARY_EMAILS).group(REGISTERED_USERS))
+ .update();
+
+ // Create two changes both with the same parent.
+ requestScopeOperations.setApiUser(uploader);
+ Change.Id changeToBeTheNewBase =
+ changeOperations.newChange().project(project).owner(uploader).create();
+ Change.Id changeToBeRebased =
+ changeOperations.newChange().project(project).owner(uploader).create();
+
+ // Approve and submit the change that will be the new base for the change that will be rebased.
+ requestScopeOperations.setApiUser(approver);
+ gApi.changes().id(changeToBeTheNewBase.get()).current().review(ReviewInput.approve());
+ gApi.changes().id(changeToBeTheNewBase.get()).current().submit();
+
+ // Rebase the second change on behalf of the uploader
+ requestScopeOperations.setApiUser(rebaser);
+ RebaseInput rebaseInput = new RebaseInput();
+ rebaseInput.onBehalfOfUploader = true;
+ rebaseInput.committerEmail = uploaderSecondaryEmail;
+ gApi.changes().id(changeToBeRebased.get()).rebase(rebaseInput);
+
+ assertThat(
+ gApi.changes()
+ .id(changeToBeRebased.get())
+ .get()
+ .getCurrentRevision()
+ .commit
+ .committer
+ .email)
+ .isEqualTo(uploaderSecondaryEmail);
+ }
+
+ @Test
+ public void cannotRebaseOnBehalfOfUploaderWithCommitterEmailWithoutViewSecondaryEmails()
+ throws Exception {
+ allowPermissionToAllUsers(Permission.REBASE);
+
+ String uploaderPreferredEmail = "uploader.preferred@example.com";
+ String uploaderSecondaryEmail = "uploader.secondary@example.com";
+ Account.Id uploader =
+ accountOperations
+ .newAccount()
+ .preferredEmail(uploaderPreferredEmail)
+ .addSecondaryEmail(uploaderSecondaryEmail)
+ .create();
+ Account.Id approver = admin.id();
+ Account.Id rebaser = accountOperations.newAccount().create();
+
+ // Create two changes both with the same parent.
+ requestScopeOperations.setApiUser(uploader);
+ Change.Id changeToBeTheNewBase =
+ changeOperations.newChange().project(project).owner(uploader).create();
+ Change.Id changeToBeRebased =
+ changeOperations.newChange().project(project).owner(uploader).create();
+
+ // Approve and submit the change that will be the new base for the change that will be rebased.
+ requestScopeOperations.setApiUser(approver);
+ gApi.changes().id(changeToBeTheNewBase.get()).current().review(ReviewInput.approve());
+ gApi.changes().id(changeToBeTheNewBase.get()).current().submit();
+
+ // Rebase the second change on behalf of the uploader
+ requestScopeOperations.setApiUser(rebaser);
+ RebaseInput rebaseInput = new RebaseInput();
+ rebaseInput.onBehalfOfUploader = true;
+ rebaseInput.committerEmail = uploaderSecondaryEmail;
+
+ ResourceConflictException exception =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(changeToBeRebased.get()).rebase(rebaseInput));
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo(
+ String.format(
+ "Cannot rebase using committer email '%s'. It can only be done using "
+ + "the preferred email or the committer email of the uploader",
+ uploaderSecondaryEmail));
+ }
+
+ @Test
public void cannotRebaseNonCurrentPatchSetOnBehalfOfUploader() throws Exception {
Account.Id uploader = accountOperations.newAccount().create();
Change.Id changeId = changeOperations.newChange().owner(uploader).create();
diff --git a/javatests/com/google/gerrit/acceptance/api/change/SubmitRequirementIT.java b/javatests/com/google/gerrit/acceptance/api/change/SubmitRequirementIT.java
index d1e6bcba..ab2f358 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/SubmitRequirementIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/SubmitRequirementIT.java
@@ -70,6 +70,7 @@
import com.google.gerrit.extensions.common.SubmitRequirementResultInfo.Status;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.httpd.raw.IndexPreloadingUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.testing.TestLabels;
@@ -102,13 +103,6 @@
@Inject private ExtensionRegistry extensionRegistry;
@Inject private IndexOperations.Change changeIndexOperations;
- private static final ImmutableSet<ListChangesOption> DASHBOARD_OPTIONS =
- ImmutableSet.of(
- ListChangesOption.LABELS,
- ListChangesOption.DETAILED_ACCOUNTS,
- ListChangesOption.SUBMIT_REQUIREMENTS,
- ListChangesOption.STAR);
-
@Test
public void submitRecords() throws Exception {
PushOneCommit.Result r = createChange();
@@ -2958,7 +2952,7 @@
.withQuery("project:{" + project.get() + "} (status:open OR status:closed)")
.withOptions(
new ImmutableSet.Builder<ListChangesOption>()
- .addAll(DASHBOARD_OPTIONS)
+ .addAll(IndexPreloadingUtil.DASHBOARD_OPTIONS)
.add(ListChangesOption.SUBMIT_REQUIREMENTS)
.build())
.get();
diff --git a/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java b/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java
index 651130e..b0d39d5 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java
@@ -16,7 +16,9 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.entities.LabelFunction.NO_BLOCK;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.server.project.testing.TestLabels.label;
import static com.google.gerrit.server.project.testing.TestLabels.labelBuilder;
import static com.google.gerrit.server.project.testing.TestLabels.value;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
@@ -589,6 +591,34 @@
}
@Test
+ public void overriddenSubmitRequirementMissingCodeReviewVote_submitsWithoutDiff()
+ throws Exception {
+ // Set Code-Review to optional
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ u.getConfig()
+ .upsertLabelType(
+ label(
+ "Code-Review",
+ value(1, "Positive"),
+ value(0, "No score"),
+ value(-1, "Negative"))
+ .toBuilder()
+ .setFunction(NO_BLOCK)
+ .build());
+ u.save();
+ }
+
+ Change.Id changeId = changeOperations.newChange().project(project).create();
+ changeOperations.change(changeId).newPatchset().create();
+
+ // Submitted without Code-Review approval
+ gApi.changes().id(changeId.get()).current().submit();
+
+ assertThat(Iterables.getLast(gApi.changes().id(changeId.get()).messages()).message)
+ .isEqualTo("Change has been successfully merged");
+ }
+
+ @Test
public void diffChangeMessageOnSubmitWithStickyVote_noChanges() throws Exception {
Change.Id changeId = changeOperations.newChange().project(project).create();
gApi.changes().id(changeId.get()).current().review(ReviewInput.approve());
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
index b570466..7eb33b6 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
@@ -24,10 +24,12 @@
import static com.google.gerrit.extensions.common.testing.FileInfoSubject.assertThat;
import static com.google.gerrit.git.ObjectIds.abbreviateName;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toMap;
import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -36,8 +38,10 @@
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.PushOneCommit.Result;
+import com.google.gerrit.acceptance.testsuite.change.ChangeOperations;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.RawInputUtil;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Patch;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.extensions.api.changes.FileApi;
@@ -47,6 +51,7 @@
import com.google.gerrit.extensions.common.DiffInfo;
import com.google.gerrit.extensions.common.FileInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
+import com.google.gerrit.extensions.common.testing.ContentEntrySubject;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.webui.EditWebLink;
@@ -91,6 +96,7 @@
@Inject private ExtensionRegistry extensionRegistry;
@Inject private DiffOperations diffOperations;
+ @Inject private ChangeOperations changeOperations;
@Inject private ProjectOperations projectOperations;
private boolean intraline;
@@ -3034,6 +3040,145 @@
assertThat(e).hasMessageThat().isEqualTo("edit not allowed as base");
}
+ @Test
+ public void diffForAddedBinaryFile() throws Exception {
+ String imageFileName = "an_image.png";
+ byte[] imageBytes = createRgbImage(255, 0, 0);
+ Change.Id changeId =
+ changeOperations
+ .newChange()
+ .file(imageFileName)
+ .content(new String(imageBytes, UTF_8))
+ .create();
+
+ DiffInfo diffInfo = gApi.changes().id(changeId.get()).current().file(imageFileName).diff();
+
+ assertThat(diffInfo).binary().isTrue();
+ assertThat(diffInfo).content().isEmpty();
+ assertThat(diffInfo).diffHeader().contains("Binary files differ");
+ assertThat(diffInfo).metaA().isNull();
+ assertThat(diffInfo).metaB().isNotNull();
+ assertThat(diffInfo).webLinks().isNull();
+ }
+
+ @Test
+ public void diffForModifiedBinaryFile() throws Exception {
+ String imageFileName = "an_image.png";
+ byte[] imageBytes = createRgbImage(255, 0, 0);
+ Change.Id changeId1 =
+ changeOperations
+ .newChange()
+ .file(imageFileName)
+ .content(new String(imageBytes, UTF_8))
+ .create();
+
+ byte[] newImageBytes = createRgbImage(0, 255, 0);
+ Change.Id changeId2 =
+ changeOperations
+ .newChange()
+ .childOf()
+ .change(changeId1)
+ .file(imageFileName)
+ .content(new String(newImageBytes, UTF_8))
+ .create();
+
+ DiffInfo diffInfo = gApi.changes().id(changeId2.get()).current().file(imageFileName).diff();
+
+ assertThat(diffInfo).binary().isTrue();
+
+ // All fields in the contentEntry are null, except the 'skip' field. It's probably a bug that
+ // this is set for binary files.
+ ContentEntrySubject contentEntry = assertThat(diffInfo).content().onlyElement();
+ contentEntry.linesOfA().isNull();
+ contentEntry.linesOfB().isNull();
+ contentEntry.commonLines().isNull();
+
+ assertThat(diffInfo).diffHeader().contains("Binary files differ");
+ assertThat(diffInfo).metaA().isNotNull();
+ assertThat(diffInfo).metaB().isNotNull();
+ assertThat(diffInfo).webLinks().isNull();
+ }
+
+ @Test
+ public void diffForDeletedBinaryFile() throws Exception {
+ String imageFileName = "an_image.png";
+ byte[] imageBytes = createRgbImage(255, 0, 0);
+ Change.Id changeId1 =
+ changeOperations
+ .newChange()
+ .file(imageFileName)
+ .content(new String(imageBytes, UTF_8))
+ .create();
+
+ Change.Id changeId2 =
+ changeOperations
+ .newChange()
+ .childOf()
+ .change(changeId1)
+ .file(imageFileName)
+ .delete()
+ .create();
+
+ DiffInfo diffInfo = gApi.changes().id(changeId2.get()).current().file(imageFileName).diff();
+
+ assertThat(diffInfo).binary().isTrue();
+
+ // All fields in the contentEntry are null, except the 'skip' field. It's probably a bug that
+ // this is set for binary files.
+ ContentEntrySubject contentEntry = assertThat(diffInfo).content().onlyElement();
+ contentEntry.linesOfA().isNull();
+ contentEntry.linesOfB().isNull();
+ contentEntry.commonLines().isNull();
+
+ assertThat(diffInfo).diffHeader().contains("Binary files differ");
+ assertThat(diffInfo).metaA().isNotNull();
+ assertThat(diffInfo).metaB().isNull();
+ assertThat(diffInfo).webLinks().isNull();
+ }
+
+ @Test
+ public void diffForBinaryFileThatIsNotTouchedInTheChange() throws Exception {
+ String imageFileName1 = "an_image.png";
+ byte[] imageBytes1 = createRgbImage(255, 0, 0);
+ String imageContent1 = new String(imageBytes1, UTF_8);
+ Change.Id changeId1 =
+ changeOperations.newChange().file(imageFileName1).content(imageContent1).create();
+
+ String imageFileName2 = "another_image.png";
+ byte[] imageBytes2 = createRgbImage(0, 255, 0);
+ Change.Id changeId2 =
+ changeOperations
+ .newChange()
+ .childOf()
+ .change(changeId1)
+ .file(imageFileName2)
+ .content(new String(imageBytes2, UTF_8))
+ .create();
+
+ // Since file imageFileName1 was not touched in the second change, trying to get the diff for it
+ // should probably fail with '404 Not Found'.
+ DiffInfo diffInfo = gApi.changes().id(changeId2.get()).current().file(imageFileName1).diff();
+
+ // This should be detected as a binary file, but it isn't.
+ assertThat(diffInfo).binary().isNull();
+
+ // For binary files linesOfA, linesOfB and commonLines are expected to be null, but the content
+ // of the binary file is returned as common lines.
+ ContentEntrySubject contentEntry = assertThat(diffInfo).content().onlyElement();
+ contentEntry.linesOfA().isNull();
+ contentEntry.linesOfB().isNull();
+ contentEntry
+ .commonLines()
+ .containsExactlyElementsIn(Splitter.on("\n").splitToList(imageContent1));
+
+ // For binary file the header list should contain "Binary files differ", but it doesn't.
+ assertThat(diffInfo).diffHeader().isNull();
+
+ assertThat(diffInfo).metaA().isNotNull();
+ assertThat(diffInfo).metaB().isNotNull();
+ assertThat(diffInfo).webLinks().isNull();
+ }
+
private Registration newEditWebLink() {
EditWebLink webLink =
new EditWebLink() {
diff --git a/javatests/com/google/gerrit/acceptance/config/GerritInstanceIdIT.java b/javatests/com/google/gerrit/acceptance/config/GerritInstanceIdIT.java
index 0dd6a83..d5e9351 100644
--- a/javatests/com/google/gerrit/acceptance/config/GerritInstanceIdIT.java
+++ b/javatests/com/google/gerrit/acceptance/config/GerritInstanceIdIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.config;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.config.GerritInstanceIdProvider.INSTANCE_ID_SYSTEM_PROPERTY;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import org.junit.Test;
@@ -28,6 +29,13 @@
}
@Test
+ @GerritConfig(name = "gerrit.instanceId", value = "testInstanceId")
+ @GerritSystemProperty(name = INSTANCE_ID_SYSTEM_PROPERTY, value = "sysPropInstanceId")
+ public void instanceIdSystemPropertyOverridesConfig() {
+ assertThat(instanceId).isEqualTo("sysPropInstanceId");
+ }
+
+ @Test
public void shouldReturnNullWhenNotDefined() {
assertThat(instanceId).isNull();
}
diff --git a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
index c0531e5..3bec694 100644
--- a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
@@ -49,11 +49,11 @@
import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.Sequence;
import com.google.gerrit.server.account.ServiceUserClassifier;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.receive.ReceiveCommitsAdvertiseRefsHookChain;
import com.google.gerrit.server.git.receive.testing.TestRefAdvertiser;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
import com.google.gerrit.server.query.change.ChangeData;
@@ -1413,8 +1413,8 @@
RefNames.REFS_GROUPNAMES,
RefNames.refsGroups(admins),
RefNames.refsGroups(nonInteractiveUsers),
- RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS,
- RefNames.REFS_SEQUENCES + Sequences.NAME_GROUPS,
+ RefNames.REFS_SEQUENCES + Sequence.NAME_ACCOUNTS,
+ RefNames.REFS_SEQUENCES + Sequence.NAME_GROUPS,
RefNames.REFS_CONFIG,
Constants.HEAD);
diff --git a/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java b/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java
index a0ae91b..1143e89 100644
--- a/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java
@@ -427,6 +427,22 @@
adminRestSession.get("/projects").assertOK();
}
+ @Test
+ public void testNumericChangeIdRedirectWithPrefix() throws Exception {
+ int changeNumber = createChange().getChange().getId().get();
+
+ String redirectUri = String.format("/c/%s/+/%d/", project.get(), changeNumber);
+ anonymousRestSession.get("/c/" + changeNumber).assertTemporaryRedirect(redirectUri);
+ }
+
+ @Test
+ public void testNumericChangeIdRedirectWithoutPrefix() throws Exception {
+ int changeNumber = createChange().getChange().getId().get();
+
+ String redirectUri = String.format("/c/%s/+/%d/", project.get(), changeNumber);
+ anonymousRestSession.get("/" + changeNumber).assertTemporaryRedirect(redirectUri);
+ }
+
private ObjectId getMetaRefSha1(Result change) {
return change.getChange().notes().getRevision();
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
index 9c496fa..9d98ecb 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
@@ -120,7 +120,7 @@
}
@Test
- public void dependencyOnOutdatedPatchSetPreventsMerge() throws Throwable {
+ public void dependencyOnOutdatedPatchSetOfUnsubmittedChangePreventsMerge() throws Throwable {
// Create a change
PushOneCommit change = pushFactory.create(user.newIdent(), testRepo, "fix", "a.txt", "foo");
PushOneCommit.Result changeResult = change.to("refs/for/master");
@@ -147,7 +147,7 @@
"Failed to submit 2 changes due to the following problems:\n"
+ "Change "
+ change2Result.getChange().getId()
- + ": Depends on change that was not submitted."
+ + ": Depends on commit that cannot be merged."
+ " Commit "
+ change2Result.getCommit().name()
+ " depends on commit "
@@ -163,4 +163,56 @@
assertRefUpdatedEvents();
assertChangeMergedEvents();
}
+
+ @Test
+ public void dependencyOnOutdatedPatchSetOfSubmittedChangePreventsMerge() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+
+ // Create a change
+ PushOneCommit change = pushFactory.create(user.newIdent(), testRepo, "fix", "a.txt", "foo");
+ PushOneCommit.Result changeResult = change.to("refs/for/master");
+ PatchSet.Id patchSetId = changeResult.getPatchSetId();
+
+ // Create a successor change.
+ PushOneCommit change2 =
+ pushFactory.create(user.newIdent(), testRepo, "feature", "b.txt", "bar");
+ PushOneCommit.Result change2Result = change2.to("refs/for/master");
+
+ // Create new patch set for first change.
+ testRepo.reset(changeResult.getCommit().name());
+ amendChange(changeResult.getChangeId());
+
+ // Approve and submit the first changes
+ approve(changeResult.getChangeId());
+ submit(changeResult.getChangeId());
+ RevCommit headAfterSubmit = projectOperations.project(project).getHead("master");
+
+ // Approve the second change
+ approve(change2Result.getChangeId());
+
+ // submit button is disabled.
+ assertSubmitDisabled(change2Result.getChangeId());
+
+ submitWithConflict(
+ change2Result.getChangeId(),
+ "Failed to submit 1 change due to the following problems:\n"
+ + "Change "
+ + change2Result.getChange().getId()
+ + ": Depends on commit that cannot be merged."
+ + " Commit "
+ + change2Result.getCommit().name()
+ + " depends on commit "
+ + changeResult.getCommit().name()
+ + ", which is outdated patch set "
+ + patchSetId.get()
+ + " of change "
+ + changeResult.getChange().getId()
+ + ". The latest patch set is "
+ + changeResult.getPatchSetId().get()
+ + ".");
+
+ // Only events for the first change are sent.
+ assertRefUpdatedEvents(initialHead, headAfterSubmit);
+ assertChangeMergedEvents(changeResult.getChangeId(), headAfterSubmit.name());
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
index aeebc10..a30b5c4 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
@@ -417,7 +417,7 @@
"Failed to submit 2 changes due to the following problems:\n"
+ "Change "
+ change2Result.getChange().getId()
- + ": Depends on change that was not submitted."
+ + ": Depends on commit that cannot be merged."
+ " Commit "
+ change2Result.getCommit().name()
+ " depends on commit "
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
index 3be49df..a60f757 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
@@ -234,7 +234,7 @@
"Failed to submit 2 changes due to the following problems:\n"
+ "Change "
+ change2Result.getChange().getId()
- + ": Depends on change that was not submitted."
+ + ": Depends on commit that cannot be merged."
+ " Commit "
+ change2Result.getCommit().name()
+ " depends on commit "
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
index c4f8f2c..ac3622f 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
@@ -381,8 +381,7 @@
+ " due to the following problems:\n"
+ "Change "
+ change3a.getChange().getId()
- + ": Depends on change that"
- + " was not submitted."
+ + ": Depends on commit that cannot be merged."
+ " Commit "
+ change3a.getCommit().name()
+ " depends on commit "
@@ -485,7 +484,7 @@
"Failed to submit 1 change due to the following problems:\n"
+ "Change "
+ change3.getPatchSetId().changeId().get()
- + ": Depends on change that was not submitted."
+ + ": Depends on commit that cannot be merged."
+ " Commit "
+ change3.getCommit().name()
+ " depends on commit "
@@ -518,7 +517,7 @@
"Failed to submit 1 change due to the following problems:\n"
+ "Change "
+ change2Result.getChange().getId()
- + ": Depends on change that was not submitted."
+ + ": Depends on commit that cannot be merged."
+ " Commit "
+ change2Result.getCommit().name()
+ " depends on commit "
@@ -584,7 +583,7 @@
"Failed to submit 1 change due to the following problems:\n"
+ "Change "
+ change2Result.getChange().getId()
- + ": Depends on change that was not submitted."
+ + ": Depends on commit that cannot be merged."
+ " Commit "
+ change2Result.getCommit().name()
+ " depends on commit "
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
index 364ce84..b8b63e6 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -15,12 +15,14 @@
package com.google.gerrit.acceptance.rest.config;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.config.GerritInstanceIdProvider.INSTANCE_ID_SYSTEM_PROPERTY;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.UseSsh;
import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.acceptance.config.GerritSystemProperty;
import com.google.gerrit.common.RawInputUtil;
import com.google.gerrit.extensions.api.plugins.InstallPluginInput;
import com.google.gerrit.extensions.client.AccountFieldName;
@@ -225,4 +227,11 @@
ServerInfo i = gApi.config().server().getInfo();
assertThat(i.download.schemes).isEmpty();
}
+
+ @Test
+ @GerritSystemProperty(name = INSTANCE_ID_SYSTEM_PROPERTY, value = "sysPropInstanceId")
+ public void instanceIdFromSystemProperty() throws Exception {
+ ServerInfo i = gApi.config().server().getInfo();
+ assertThat(i.gerrit.instanceId).isEqualTo("sysPropInstanceId");
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/GetBranchIT.java b/javatests/com/google/gerrit/acceptance/rest/project/GetBranchIT.java
index 13c20dd..cf3bf89 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/GetBranchIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/GetBranchIT.java
@@ -43,8 +43,8 @@
import com.google.gerrit.extensions.api.projects.TagInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.Sequence;
import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.testing.ConfigSuite;
import com.google.inject.Inject;
import org.eclipse.jgit.junit.TestRepository;
@@ -458,7 +458,7 @@
public void getAccountSequenceRef() throws Exception {
// a user without the 'Access Database' capability cannot see the refs/sequences/accounts ref
requestScopeOperations.setApiUser(user.id());
- String accountSequenceRef = RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS;
+ String accountSequenceRef = RefNames.REFS_SEQUENCES + Sequence.NAME_ACCOUNTS;
assertBranchNotFound(allUsers, accountSequenceRef);
// a user with the 'Access Database' capability can see the refs/sequences/accounts ref
@@ -469,7 +469,7 @@
public void getChangeSequenceRef() throws Exception {
// a user without the 'Access Database' capability cannot see the refs/sequences/changes ref
requestScopeOperations.setApiUser(user.id());
- String changeSequenceRef = RefNames.REFS_SEQUENCES + Sequences.NAME_CHANGES;
+ String changeSequenceRef = RefNames.REFS_SEQUENCES + Sequence.NAME_CHANGES;
assertBranchNotFound(allProjects, changeSequenceRef);
// a user with the 'Access Database' capability can see the refs/sequences/changes ref
@@ -480,7 +480,7 @@
public void getGroupSequenceRef() throws Exception {
// a user without the 'Access Database' capability cannot see the refs/sequences/groups ref
requestScopeOperations.setApiUser(user.id());
- String groupSequenceRef = RefNames.REFS_SEQUENCES + Sequences.NAME_GROUPS;
+ String groupSequenceRef = RefNames.REFS_SEQUENCES + Sequence.NAME_GROUPS;
assertBranchNotFound(allUsers, groupSequenceRef);
// a user with the 'Access Database' capability can see the refs/sequences/groups ref
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/GetProjectIT.java b/javatests/com/google/gerrit/acceptance/rest/project/GetProjectIT.java
index 71ee90c..4c58521 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/GetProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/GetProjectIT.java
@@ -29,7 +29,7 @@
public class GetProjectIT extends AbstractDaemonTest {
@Test
- public void getProject() throws Exception {
+ public void testGetProject() throws Exception {
String name = project.get();
ProjectInfo p = gApi.projects().name(name).get();
assertThat(p.name).isEqualTo(name);
diff --git a/javatests/com/google/gerrit/acceptance/ssh/QueryIT.java b/javatests/com/google/gerrit/acceptance/ssh/QueryIT.java
index 912c464..8f4458d 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/QueryIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/QueryIT.java
@@ -24,6 +24,8 @@
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.SshSession;
import com.google.gerrit.acceptance.UseSsh;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewerInput;
@@ -325,6 +327,33 @@
}
}
+ @Test
+ public void allApprovalsAllPatchSetsOptionsWithCopyConditionJSON() throws Exception {
+ // Copy min Code-Review votes
+ try (ProjectConfigUpdate u = updateProject(Project.NameKey.parse("All-Projects"))) {
+ u.getConfig().updateLabelType(LabelId.CODE_REVIEW, b -> b.setCopyCondition("is:MIN"));
+ u.save();
+ }
+
+ // Create a change and add Code-Review -2 on first patch-set
+ String changeId = createChange().getChangeId();
+ gApi.changes().id(changeId).current().review(ReviewInput.reject());
+
+ // Create second patch-set
+ amendChange(changeId);
+
+ // Assert that second patch-set has Code-Review -2 vote
+ List<ChangeAttribute> changes =
+ executeSuccessfulQuery("--all-approvals --patch-sets " + changeId);
+ assertThat(changes).hasSize(1);
+ assertThat(changes.get(0).patchSets).hasSize(2);
+ assertThat(changes.get(0).patchSets.get(1).approvals).isNotNull();
+ assertThat(changes.get(0).patchSets.get(1).approvals).hasSize(1);
+ assertThat(changes.get(0).patchSets.get(1).approvals.get(0).type)
+ .isEqualTo(LabelId.CODE_REVIEW);
+ assertThat(changes.get(0).patchSets.get(1).approvals.get(0).value).isEqualTo("-2");
+ }
+
protected static class SamplePluginModule extends AbstractModule {
@Override
public void configure() {
diff --git a/javatests/com/google/gerrit/httpd/auth/container/HttpAuthFilterTest.java b/javatests/com/google/gerrit/httpd/auth/container/HttpAuthFilterTest.java
new file mode 100644
index 0000000..a5f8349
--- /dev/null
+++ b/javatests/com/google/gerrit/httpd/auth/container/HttpAuthFilterTest.java
@@ -0,0 +1,78 @@
+// Copyright (C) 2023 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.auth.container;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.httpd.WebSession;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.util.http.testutil.FakeHttpServletRequest;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
+public class HttpAuthFilterTest {
+
+ private static String DISPLAYNAME_HEADER = "displaynameHeader";
+ private static String DISPLAYNAME = "displayname";
+
+ @Mock private DynamicItem<WebSession> webSession;
+ @Mock private ExternalIdKeyFactory externalIdKeyFactory;
+ @Mock private AuthConfig authConfig;
+
+ @Test
+ public void getRemoteDisplaynameShouldReturnDisplaynameHeaderWhenHeaderIsConfiguredAndSet()
+ throws IOException {
+ doReturn(DISPLAYNAME_HEADER).when(authConfig).getHttpDisplaynameHeader();
+ HttpAuthFilter httpAuthFilter =
+ new HttpAuthFilter(webSession, authConfig, externalIdKeyFactory);
+
+ FakeHttpServletRequest req = new FakeHttpServletRequest();
+ req.addHeader(DISPLAYNAME_HEADER, DISPLAYNAME);
+
+ assertThat(httpAuthFilter.getRemoteDisplayname(req)).isEqualTo(DISPLAYNAME);
+ }
+
+ @Test
+ public void getRemoteDisplaynameShouldReturnNullWhenDisplaynameHeaderIsConfiguredAndNotSet()
+ throws IOException {
+ doReturn(DISPLAYNAME_HEADER).when(authConfig).getHttpDisplaynameHeader();
+ HttpAuthFilter httpAuthFilter =
+ new HttpAuthFilter(webSession, authConfig, externalIdKeyFactory);
+
+ FakeHttpServletRequest req = new FakeHttpServletRequest();
+
+ assertThat(httpAuthFilter.getRemoteDisplayname(req)).isNull();
+ }
+
+ @Test
+ public void getRemoteDisplaynameShouldReturnNullWhenDisplaynameHeaderIsConfiguredAndEmpty()
+ throws IOException {
+ doReturn(DISPLAYNAME_HEADER).when(authConfig).getHttpDisplaynameHeader();
+ HttpAuthFilter httpAuthFilter =
+ new HttpAuthFilter(webSession, authConfig, externalIdKeyFactory);
+
+ FakeHttpServletRequest req = new FakeHttpServletRequest();
+ req.addHeader(DISPLAYNAME_HEADER, "");
+
+ assertThat(httpAuthFilter.getRemoteDisplayname(req)).isNull();
+ }
+}
diff --git a/javatests/com/google/gerrit/httpd/raw/IndexHtmlUtilTest.java b/javatests/com/google/gerrit/httpd/raw/IndexHtmlUtilTest.java
index 5cb7feb..c06d231 100644
--- a/javatests/com/google/gerrit/httpd/raw/IndexHtmlUtilTest.java
+++ b/javatests/com/google/gerrit/httpd/raw/IndexHtmlUtilTest.java
@@ -115,7 +115,9 @@
when(gerritApi.config()).thenReturn(configApi);
assertThat(dynamicTemplateData(gerritApi, "/c/project/+/123"))
- .containsAtLeast("changeRequestsPath", "changes/project~123");
+ .containsAtLeast(
+ "defaultChangeDetailHex", "9916394",
+ "changeRequestsPath", "changes/project~123");
}
private static SanitizedContent ordain(String s) {
diff --git a/javatests/com/google/gerrit/proto/BUILD b/javatests/com/google/gerrit/proto/BUILD
index 216ef94..10cb0b4 100644
--- a/javatests/com/google/gerrit/proto/BUILD
+++ b/javatests/com/google/gerrit/proto/BUILD
@@ -6,6 +6,7 @@
deps = [
"//java/com/google/gerrit/proto",
"//java/com/google/gerrit/testing:gerrit-junit",
+ "//lib:guava",
"//lib:protobuf",
"//lib/truth",
"//lib/truth:truth-proto-extension",
diff --git a/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java b/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java
index 16fd4ca..8c4eb08 100644
--- a/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java
+++ b/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java
@@ -71,7 +71,8 @@
version,
1 << 20,
expireAfterWrite,
- refreshAfterWrite);
+ refreshAfterWrite,
+ true);
}
@Test
diff --git a/javatests/com/google/gerrit/server/cache/mem/BUILD b/javatests/com/google/gerrit/server/cache/mem/BUILD
index baa6ff8..81b0b2e 100644
--- a/javatests/com/google/gerrit/server/cache/mem/BUILD
+++ b/javatests/com/google/gerrit/server/cache/mem/BUILD
@@ -9,6 +9,7 @@
"//java/com/google/gerrit/metrics",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/cache/mem",
+ "//lib:guava",
"//lib:jgit",
"//lib:junit",
"//lib/guice",
diff --git a/javatests/com/google/gerrit/server/config/UserPreferencesConverterTest.java b/javatests/com/google/gerrit/server/config/UserPreferencesConverterTest.java
index bbc3c0a..c6ca3e4 100644
--- a/javatests/com/google/gerrit/server/config/UserPreferencesConverterTest.java
+++ b/javatests/com/google/gerrit/server/config/UserPreferencesConverterTest.java
@@ -109,6 +109,7 @@
.build()))
.addAllChangeTable(ImmutableList.of("table1", "table2"))
.setAllowBrowserNotifications(true)
+ .setDiffPageSidebar("plugin-insight")
.build();
UserPreferences.GeneralPreferencesInfo resProto =
GeneralPreferencesInfoConverter.toProto(
diff --git a/javatests/com/google/gerrit/server/git/TagSetTest.java b/javatests/com/google/gerrit/server/git/TagSetTest.java
index 0863a2c..089896e 100644
--- a/javatests/com/google/gerrit/server/git/TagSetTest.java
+++ b/javatests/com/google/gerrit/server/git/TagSetTest.java
@@ -128,8 +128,7 @@
.extendsClass(new TypeLiteral<AtomicReference<ObjectId>>() {}.getType());
assertThatSerializedClass(CachedRef.class)
.hasFields(
- ImmutableMap.of(
- "flag", int.class, "value", AtomicReference.class.getTypeParameters()[0]));
+ ImmutableMap.of("flag", int.class, "value", new TypeLiteral<ObjectId>() {}.getType()));
}
@Test
diff --git a/javatests/com/google/gerrit/server/permissions/RefControlTest.java b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
index c5bef59..43b0eba 100644
--- a/javatests/com/google/gerrit/server/permissions/RefControlTest.java
+++ b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
@@ -981,6 +981,20 @@
}
@Test
+ public void changeOwnerEditTopicName() throws Exception {
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(EDIT_TOPIC_NAME).ref("refs/heads/*").group(CHANGE_OWNER).force(true))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
+ assertWithMessage("u can edit topic name")
+ .that(u.controlForRef("refs/heads/master").canForceEditTopicName(true))
+ .isTrue();
+ }
+
+ @Test
public void unblockForceEditTopicName() throws Exception {
projectOperations
.project(localKey)
@@ -991,7 +1005,7 @@
ProjectControl u = user(localKey, DEVS);
assertWithMessage("u can edit topic name")
- .that(u.controlForRef("refs/heads/master").canForceEditTopicName())
+ .that(u.controlForRef("refs/heads/master").canForceEditTopicName(false))
.isTrue();
}
@@ -1010,7 +1024,7 @@
ProjectControl u = user(localKey, REGISTERED_USERS);
assertWithMessage("u can't edit topic name")
- .that(u.controlForRef("refs/heads/master").canForceEditTopicName())
+ .that(u.controlForRef("refs/heads/master").canForceEditTopicName(false))
.isFalse();
}
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 2c012fa..c90f5d4 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -1107,40 +1107,70 @@
}
@Test
- public void byMessageExact() throws Exception {
- repo = createAndOpenProject("repo");
- RevCommit commit1 = repo.parseBody(repo.commit().message("one").create());
- Change change1 = insert("repo", newChangeForCommit(repo, commit1));
- RevCommit commit2 = repo.parseBody(repo.commit().message("two").create());
- Change change2 = insert("repo", newChangeForCommit(repo, commit2));
- RevCommit commit3 = repo.parseBody(repo.commit().message("A great \"fix\" to my bug").create());
- Change change3 = insert("repo", newChangeForCommit(repo, commit3));
-
- assertQuery("message:foo");
- assertQuery("message:one", change1);
- assertQuery("message:two", change2);
- assertQuery("message:\"great \\\"fix\\\" to\"", change3);
+ public void byMessageExact_byAlias_d() throws Exception {
+ byMessageExact("d:", "d_repo");
}
@Test
- public void byMessageRegEx() throws Exception {
+ public void byMessageExact_byAlias_description() throws Exception {
+ byMessageExact("description:", "description_repo");
+ }
+
+ @Test
+ public void byMessageExact_byMainOperator() throws Exception {
+ byMessageExact("message:", "message_repo");
+ }
+
+ private void byMessageExact(String searchOperator, String projectName) throws Exception {
+ repo = createAndOpenProject(projectName);
+ RevCommit commit1 = repo.parseBody(repo.commit().message("one").create());
+ Change change1 = insert(projectName, newChangeForCommit(repo, commit1));
+ RevCommit commit2 = repo.parseBody(repo.commit().message("two").create());
+ Change change2 = insert(projectName, newChangeForCommit(repo, commit2));
+ RevCommit commit3 = repo.parseBody(repo.commit().message("A great \"fix\" to my bug").create());
+ Change change3 = insert(projectName, newChangeForCommit(repo, commit3));
+
+ assertQuery(searchOperator + "foo");
+ assertQuery(searchOperator + "one", change1);
+ assertQuery(searchOperator + "two", change2);
+ assertQuery(searchOperator + "\"great \\\"fix\\\" to\"", change3);
+ }
+
+ @Test
+ public void byMessageRegEx_byAlias_d() throws Exception {
+ byMessageRegEx("d:", "d_repo");
+ }
+
+ @Test
+ public void byMessageRegEx_byAlias_description() throws Exception {
+ byMessageRegEx("description:", "description_repo");
+ }
+
+ @Test
+ public void byMessageRegEx_byMainOperator() throws Exception {
+ byMessageRegEx("message:", "message_repo");
+ }
+
+ private void byMessageRegEx(String searchOperator, String projectName) throws Exception {
assume().that(getSchema().hasField(ChangeField.COMMIT_MESSAGE_EXACT)).isTrue();
- repo = createAndOpenProject("repo");
+ repo = createAndOpenProject(projectName);
RevCommit commit1 = repo.parseBody(repo.commit().message("aaaabcc").create());
- Change change1 = insert("repo", newChangeForCommit(repo, commit1));
+ Change change1 = insert(projectName, newChangeForCommit(repo, commit1));
RevCommit commit2 = repo.parseBody(repo.commit().message("aaaacc").create());
- Change change2 = insert("repo", newChangeForCommit(repo, commit2));
+ Change change2 = insert(projectName, newChangeForCommit(repo, commit2));
RevCommit commit3 = repo.parseBody(repo.commit().message("Title\n\nHELLO WORLD").create());
- Change change3 = insert("repo", newChangeForCommit(repo, commit3));
+ Change change3 = insert(projectName, newChangeForCommit(repo, commit3));
RevCommit commit4 =
repo.parseBody(repo.commit().message("Title\n\nfoobar hello WORLD").create());
- Change change4 = insert("repo", newChangeForCommit(repo, commit4));
+ Change change4 = insert(projectName, newChangeForCommit(repo, commit4));
- assertQuery("message:\"^aaaa(b|c)*\"", change2, change1);
- assertQuery("message:\"^aaaa(c)*c.*\"", change2);
- assertQuery("message:\"^.*HELLO WORLD.*\"", change3);
+ assertQuery(searchOperator + "\"^aaaa(b|c)*\"", change2, change1);
+ assertQuery(searchOperator + "\"^aaaa(c)*c.*\"", change2);
+ assertQuery(searchOperator + "\"^.*HELLO WORLD.*\"", change3);
assertQuery(
- "message:\"^.*(H|h)(E|e)(L|l)(L|l)(O|o) (W|w)(O|o)(R|r)(L|l)(D|d).*\"", change4, change3);
+ searchOperator + "\"^.*(H|h)(E|e)(L|l)(L|l)(O|o) (W|w)(O|o)(R|r)(L|l)(D|d).*\"",
+ change4,
+ change3);
}
@Test
diff --git a/javatests/com/google/gerrit/server/rules/BUILD b/javatests/com/google/gerrit/server/rules/BUILD
index 3732bd4..b1ab27e 100644
--- a/javatests/com/google/gerrit/server/rules/BUILD
+++ b/javatests/com/google/gerrit/server/rules/BUILD
@@ -6,6 +6,7 @@
deps = [
"//java/com/google/gerrit/entities",
"//java/com/google/gerrit/server",
+ "//lib:guava",
"//lib/truth",
],
)
diff --git a/javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java b/javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java
index 767ac28..5d5ef4e 100644
--- a/javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java
+++ b/javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java
@@ -24,13 +24,13 @@
import com.google.common.collect.ImmutableSortedSet;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.server.Sequence;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.IntBlob;
import com.google.gerrit.server.notedb.RepoSequence;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import com.google.gerrit.testing.TestUpdateUI;
import java.io.IOException;
@@ -150,7 +150,7 @@
repoManager,
GitReferenceUpdated.DISABLED,
allUsersName,
- Sequences.NAME_GROUPS,
+ Sequence.NAME_GROUPS,
() -> 1,
1)
.next();
diff --git a/javatests/com/google/gerrit/server/submit/BUILD b/javatests/com/google/gerrit/server/submit/BUILD
index 01acb72..fcccf92 100644
--- a/javatests/com/google/gerrit/server/submit/BUILD
+++ b/javatests/com/google/gerrit/server/submit/BUILD
@@ -12,6 +12,7 @@
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//java/com/google/gerrit/testing:test-ref-update-context",
+ "//lib:guava",
"//lib:jgit",
"//lib/mockito",
"//lib/truth",
diff --git a/javatests/com/google/gerrit/server/update/context/BUILD b/javatests/com/google/gerrit/server/update/context/BUILD
index e580595..65e544b 100644
--- a/javatests/com/google/gerrit/server/update/context/BUILD
+++ b/javatests/com/google/gerrit/server/update/context/BUILD
@@ -8,6 +8,7 @@
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//java/com/google/gerrit/testing:test-ref-update-context",
+ "//lib:guava",
"//lib/truth",
"//lib/truth:truth-java8-extension",
],
diff --git a/lib/BUILD b/lib/BUILD
index 5bb3593..f9ece52 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -179,222 +179,10 @@
)
java_library(
- name = "flexmark",
+ name = "flexmark-all-lib",
data = ["//lib:LICENSE-flexmark"],
visibility = ["//visibility:public"],
- exports = ["@flexmark//jar"],
- runtime_deps = [
- ":flexmark-ext-abbreviation",
- ],
-)
-
-java_library(
- name = "flexmark-ext-abbreviation",
- data = ["//lib:LICENSE-flexmark"],
- visibility = ["//visibility:public"],
- 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"],
-)
-
-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"],
-)
-
-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-ast//jar",
- "@flexmark-util-builder//jar",
- "@flexmark-util-data//jar",
- "@flexmark-util-html//jar",
- "@flexmark-util-misc//jar",
- "@flexmark-util-sequence//jar",
- "@flexmark-util-visitor//jar",
- ],
+ exports = ["@flexmark-all-lib//jar"],
)
java_library(
@@ -486,6 +274,7 @@
":icu4j",
":jsr305",
":protobuf",
+ "//lib/flogger:api",
"//lib/guice",
"//lib/guice:guice-assistedinject",
"//lib/guice:javax_inject",
diff --git a/lib/flogger/BUILD b/lib/flogger/BUILD
index 35c3c62..a335586 100644
--- a/lib/flogger/BUILD
+++ b/lib/flogger/BUILD
@@ -5,6 +5,7 @@
data = ["//lib:LICENSE-Apache2.0"],
visibility = ["//visibility:public"],
exports = [
+ "@flogger-google-extensions//jar",
"@flogger-log4j-backend//jar",
"@flogger-system-backend//jar",
"@flogger//jar",
diff --git a/lib/gitiles/BUILD b/lib/gitiles/BUILD
index 6e03801..b91cc1f 100644
--- a/lib/gitiles/BUILD
+++ b/lib/gitiles/BUILD
@@ -52,7 +52,6 @@
java_library(
name = "prettify",
- data = ["//lib:LICENSE-Apache2.0"],
visibility = ["//visibility:public"],
- exports = ["@prettify//jar"],
+ exports = ["@java-prettify"],
)
diff --git a/lib/guice/BUILD b/lib/guice/BUILD
index f73984b..091dcad 100644
--- a/lib/guice/BUILD
+++ b/lib/guice/BUILD
@@ -6,6 +6,7 @@
visibility = ["//visibility:public"],
exports = [
":guice-library",
+ ":jakarta-inject",
":javax_inject",
],
)
@@ -41,6 +42,13 @@
)
java_library(
+ name = "jakarta-inject",
+ data = ["//lib:LICENSE-Apache2.0"],
+ visibility = ["//visibility:public"],
+ exports = ["@jakarta-inject-api//jar"],
+)
+
+java_library(
name = "javax_inject",
data = ["//lib:LICENSE-Apache2.0"],
visibility = ["//visibility:public"],
diff --git a/lib/nongoogle_test.sh b/lib/nongoogle_test.sh
index 51c50bf..78f4852 100755
--- a/lib/nongoogle_test.sh
+++ b/lib/nongoogle_test.sh
@@ -18,6 +18,7 @@
eddsa
error-prone-annotations
flogger
+flogger-google-extensions
flogger-log4j-backend
flogger-system-backend
guava
diff --git a/modules/java-prettify b/modules/java-prettify
new file mode 160000
index 0000000..32fa081
--- /dev/null
+++ b/modules/java-prettify
@@ -0,0 +1 @@
+Subproject commit 32fa081a797a97beaf77a4f2efca26c39168e72f
diff --git a/plugins/download-commands b/plugins/download-commands
index 42b608a..4eb9ae1 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit 42b608a64bdb1350656b2ca09643ed4173cd6e73
+Subproject commit 4eb9ae135b88549b127dbe3e02a233f08b7bab6d
diff --git a/plugins/package.json b/plugins/package.json
index 25920e5..9e92086 100644
--- a/plugins/package.json
+++ b/plugins/package.json
@@ -32,10 +32,10 @@
"@polymer/polymer": "^3.5.1",
"@web/dev-server-esbuild": "^0.3.6",
"@web/test-runner": "^0.15.3",
- "lit": "^2.8.0",
+ "lit": "^3.0.0",
"rxjs": "^6.6.7",
"sinon": "^13.0.2"
},
"license": "Apache-2.0",
"private": true
-}
+}
\ No newline at end of file
diff --git a/plugins/yarn.lock b/plugins/yarn.lock
index f3a4f98..21ec522 100644
--- a/plugins/yarn.lock
+++ b/plugins/yarn.lock
@@ -569,6 +569,11 @@
resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.1.tgz#64df34e2f12e68e78ac57e571d25ec07fa460ca9"
integrity sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ==
+"@lit-labs/ssr-dom-shim@^1.1.2-pre.0":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.2.tgz#d693d972974a354034454ec1317eb6afd0b00312"
+ integrity sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g==
+
"@lit/reactive-element@^1.0.0", "@lit/reactive-element@^1.3.0", "@lit/reactive-element@^1.6.0":
version "1.6.3"
resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.6.3.tgz#25b4eece2592132845d303e091bad9b04cdcfe03"
@@ -576,6 +581,13 @@
dependencies:
"@lit-labs/ssr-dom-shim" "^1.0.0"
+"@lit/reactive-element@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-2.0.0.tgz#da14a256ac5533873b935840f306d572bac4a2ab"
+ integrity sha512-wn+2+uDcs62ROBmVAwssO4x5xue/uKD3MGGZOXL2sMxReTRIT0JXKyMXeu7gh0aJ4IJNEIG/3aOnUaQvM7BMzQ==
+ dependencies:
+ "@lit-labs/ssr-dom-shim" "^1.1.2-pre.0"
+
"@mdn/browser-compat-data@^4.0.0":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-4.2.1.tgz#1fead437f3957ceebe2e8c3f46beccdb9bc575b8"
@@ -2233,6 +2245,15 @@
"@lit/reactive-element" "^1.3.0"
lit-html "^2.8.0"
+lit-element@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-4.0.0.tgz#8343891bc9159a5fcb7f534914b37f2c0161e036"
+ integrity sha512-N6+f7XgusURHl69DUZU6sTBGlIN+9Ixfs3ykkNDfgfTkDYGGOWwHAYBhDqVswnFGyWgQYR2KiSpu4J76Kccs/A==
+ dependencies:
+ "@lit-labs/ssr-dom-shim" "^1.1.2-pre.0"
+ "@lit/reactive-element" "^2.0.0"
+ lit-html "^3.0.0"
+
lit-html@^2.0.0, lit-html@^2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.8.0.tgz#96456a4bb4ee717b9a7d2f94562a16509d39bffa"
@@ -2240,7 +2261,14 @@
dependencies:
"@types/trusted-types" "^2.0.2"
-lit@^2.0.0, lit@^2.8.0:
+lit-html@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-3.0.0.tgz#77d6776ee488642c74c5575315ef81aa09d24ea9"
+ integrity sha512-DNJIE8dNY0dQF2Gs0sdMNUppMQT2/CvV4OVnSdg7BXAsGqkVwsE5bqQ04POfkYH5dBIuGnJYdFz5fYYyNnOxiA==
+ dependencies:
+ "@types/trusted-types" "^2.0.2"
+
+lit@^2.0.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/lit/-/lit-2.8.0.tgz#4d838ae03059bf9cafa06e5c61d8acc0081e974e"
integrity sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==
@@ -2249,6 +2277,15 @@
lit-element "^3.3.0"
lit-html "^2.8.0"
+lit@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lit/-/lit-3.0.0.tgz#204bd65935892a73670471e893ee8ca55d2f9a3b"
+ integrity sha512-nQ0teRzU1Kdj++VdmttS2WvIen8M79wChJ6guRKIIym2M3Ansg3Adj9O6yuQh2IpjxiUXlNuS81WKlQ4iL3BmA==
+ dependencies:
+ "@lit/reactive-element" "^2.0.0"
+ lit-element "^4.0.0"
+ lit-html "^3.0.0"
+
lodash.assignwith@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz#127a97f02adc41751a954d24b0de17e100e038eb"
diff --git a/polygerrit-ui/app/api/annotation.ts b/polygerrit-ui/app/api/annotation.ts
index df9af40..7cf200f 100644
--- a/polygerrit-ui/app/api/annotation.ts
+++ b/polygerrit-ui/app/api/annotation.ts
@@ -3,7 +3,12 @@
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {CoverageRange, FileRange, TokenHighlightEventDetails} from './diff';
+import {
+ CoverageRange,
+ FileRange,
+ GrDiff,
+ TokenHighlightEventDetails,
+} from './diff';
import {BasePatchSetNum, ChangeInfo, RevisionPatchSetNum} from './rest-api';
/**
@@ -26,6 +31,7 @@
fileRange: FileRange;
/** @deprecated rely on fileRange.path */
path: string;
+ diffElement: GrDiff;
}
export declare type TokenHoverListener = (
diff --git a/polygerrit-ui/app/constants/constants.ts b/polygerrit-ui/app/constants/constants.ts
index d28f33f..24fb5c0 100644
--- a/polygerrit-ui/app/constants/constants.ts
+++ b/polygerrit-ui/app/constants/constants.ts
@@ -263,6 +263,7 @@
email_strategy: EmailStrategy.ATTENTION_SET_ONLY,
default_base_for_merges: DefaultBase.AUTO_MERGE,
allow_browser_notifications: false,
+ diff_page_sidebar: 'NONE',
};
}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts
index bff56bdd..e099184 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts
@@ -11,7 +11,10 @@
import {tableStyles} from '../../../styles/gr-table-styles';
import {LitElement, css, html, PropertyValues} from 'lit';
import {customElement, property} from 'lit/decorators.js';
-import {createDashboardUrl} from '../../../models/views/dashboard';
+import {
+ DashboardType,
+ createDashboardUrl,
+} from '../../../models/views/dashboard';
interface DashboardRef {
section: string;
@@ -156,7 +159,7 @@
_getUrl(project?: RepoName, dashboard?: DashboardId) {
if (!project || !dashboard) return '';
- return createDashboardUrl({project, dashboard});
+ return createDashboardUrl({project, type: DashboardType.REPO, dashboard});
}
_computeLoadingClass(loading: boolean) {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
index 4feb35d..fc5760b 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
@@ -218,10 +218,6 @@
.container {
position: relative;
}
- .strikethrough {
- color: var(--deemphasized-text-color);
- text-decoration: line-through;
- }
.content {
overflow: hidden;
position: absolute;
@@ -285,11 +281,18 @@
cursor: pointer;
text-decoration: none;
}
- a:hover {
+ /* The subject cell needs a separate rule for these reasons:
+ 1. :hover does not propagate to absolutely positioned children.
+ 2. .strikethrough for abandoned changes must be respected.
+ 3. We don't want the "spacer" and the to be underlined.
+ */
+ .cell:not(.subject) a:hover,
+ .cell.subject a:hover .content:not(.strikethrough) {
text-decoration: underline;
}
- .subject:hover .content {
- text-decoration: underline;
+ .strikethrough {
+ color: var(--deemphasized-text-color);
+ text-decoration: line-through;
}
.comma,
.placeholder {
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
index f4c5215..00729fd 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
@@ -43,6 +43,7 @@
import {Shortcut} from '../../../services/shortcuts/shortcuts-config';
import {ShortcutController} from '../../lit/shortcut-controller';
import {
+ DashboardType,
dashboardViewModelToken,
DashboardViewState,
} from '../../../models/views/dashboard';
@@ -386,7 +387,7 @@
this.firstTimeLoad = false;
this.loading = true;
- const {project, dashboard, title, user, sections} = this.viewState;
+ const {project, type, dashboard, title, user, sections} = this.viewState;
const dashboardPromise: Promise<UserDashboard | undefined> = project
? this.getRepositoryDashboard(project, dashboard)
@@ -406,7 +407,10 @@
})
.then(() => {
this.maybeShowDraftsBanner();
- this.reporting.dashboardDisplayed();
+ // Only report the metric for the default personal dashboard.
+ if (type === DashboardType.USER && isLoggedInUserDashboard) {
+ this.reporting.dashboardDisplayed();
+ }
})
.catch(err => {
fireTitleChange(title || this.computeTitle(user));
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
index b05d970..58a66a6 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
@@ -36,6 +36,7 @@
import {SinonStubbedMember} from 'sinon';
import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
import {GrButton} from '../../shared/gr-button/gr-button';
+import {DashboardType} from '../../../models/views/dashboard';
suite('gr-dashboard-view tests', () => {
let element: GrDashboardView;
@@ -63,6 +64,7 @@
test('render', async () => {
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.CUSTOM,
user: 'self',
sections: [
{name: 'test1', query: 'test1', hideIfEmpty: true},
@@ -117,6 +119,7 @@
setup(async () => {
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.CUSTOM,
user: 'user',
sections: [
{name: 'test1', query: 'test1', hideIfEmpty: true},
@@ -155,6 +158,7 @@
setup(async () => {
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.CUSTOM,
user: 'self',
sections: [
{name: 'test1', query: 'test1', hideIfEmpty: true},
@@ -167,6 +171,7 @@
test('not dashboard/self', () => {
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.USER,
user: 'notself',
dashboard: '' as DashboardId,
};
@@ -178,6 +183,7 @@
element.results = [];
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.USER,
user: 'self',
dashboard: '' as DashboardId,
};
@@ -192,6 +198,7 @@
];
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.USER,
user: 'self',
dashboard: '' as DashboardId,
};
@@ -212,6 +219,7 @@
assert.isFalse(changeIsOpen(element.results[0].results[0]));
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.USER,
user: 'self',
dashboard: '' as DashboardId,
};
@@ -322,6 +330,7 @@
element.loggedInUser = undefined;
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.CUSTOM,
user: 'self',
dashboard: '' as DashboardId,
sections: [
@@ -337,6 +346,7 @@
element.loggedInUser = createAccountDetailWithId(1);
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.CUSTOM,
user: 'self',
dashboard: '' as DashboardId,
sections: [
@@ -353,6 +363,7 @@
test("viewing another user's dashboard omits selfOnly sections", async () => {
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.CUSTOM,
user: 'user',
dashboard: '' as DashboardId,
sections: [
@@ -368,6 +379,7 @@
test('suffixForDashboard is included in getChanges query', async () => {
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.CUSTOM,
dashboard: '' as DashboardId,
sections: [
{name: '', query: '1'},
@@ -508,6 +520,7 @@
test('showNewUserHelp', async () => {
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.USER,
};
element.loading = false;
element.showNewUserHelp = false;
@@ -542,6 +555,7 @@
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.USER,
dashboard: '' as DashboardId,
user: 'self',
};
@@ -551,6 +565,7 @@
element.loading = false;
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.USER,
dashboard: '' as DashboardId,
user: 'user',
};
@@ -559,6 +574,7 @@
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.REPO,
dashboard: '' as DashboardId,
project: 'p' as RepoName,
user: 'user',
@@ -584,6 +600,7 @@
});
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.REPO,
dashboard: 'dashboard' as DashboardId,
project: 'project' as RepoName,
user: '',
@@ -592,6 +609,18 @@
});
test('viewState change triggers dashboardDisplayed()', async () => {
+ getChangesStub.returns(Promise.resolve([[]]));
+ const dashboardDisplayedStub = stubReporting('dashboardDisplayed');
+ element.viewState = {
+ view: GerritView.DASHBOARD,
+ type: DashboardType.USER,
+ user: 'self',
+ };
+ await element.reload();
+ assert.isTrue(dashboardDisplayedStub.calledOnce);
+ });
+
+ test('viewState change does not trigger dashboardDisplayed() for repo', async () => {
stubRestApi('getDashboard').returns(
Promise.resolve({
id: '' as DashboardId,
@@ -609,11 +638,24 @@
const dashboardDisplayedStub = stubReporting('dashboardDisplayed');
element.viewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.REPO,
dashboard: 'dashboard' as DashboardId,
project: 'project' as RepoName,
user: '',
};
await element.reload();
- assert.isTrue(dashboardDisplayedStub.calledOnce);
+ assert.isFalse(dashboardDisplayedStub.calledOnce);
+ });
+
+ test('viewState change does not trigger dashboardDisplayed() for not-self', async () => {
+ getChangesStub.returns(Promise.resolve([]));
+ const dashboardDisplayedStub = stubReporting('dashboardDisplayed');
+ element.viewState = {
+ view: GerritView.DASHBOARD,
+ type: DashboardType.USER,
+ user: 'notself',
+ };
+ await element.reload();
+ assert.isFalse(dashboardDisplayedStub.calledOnce);
});
});
diff --git a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.ts b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.ts
index 3f99416..d1bba95 100644
--- a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.ts
@@ -15,7 +15,10 @@
import {fontStyles} from '../../../styles/gr-font-styles';
import {LitElement, css, html, PropertyValues} from 'lit';
import {customElement, property} from 'lit/decorators.js';
-import {createDashboardUrl} from '../../../models/views/dashboard';
+import {
+ DashboardType,
+ createDashboardUrl,
+} from '../../../models/views/dashboard';
@customElement('gr-user-header')
export class GrUserHeader extends LitElement {
@@ -145,10 +148,12 @@
if (!accountDetails) return '';
const id = accountDetails._account_id;
- if (id) return createDashboardUrl({user: String(id)});
+ if (id)
+ return createDashboardUrl({type: DashboardType.USER, user: String(id)});
const email = accountDetails.email;
- if (email) return createDashboardUrl({user: email});
+ if (email)
+ return createDashboardUrl({type: DashboardType.USER, user: email});
return '';
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index 0be80bb..31c2b660 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -1563,6 +1563,7 @@
base: e.detail.base,
allow_conflicts: e.detail.allowConflicts,
on_behalf_of_uploader: e.detail.onBehalfOfUploader,
+ committer_email: e.detail.committerEmail,
};
const rebaseChain = !!e.detail.rebaseChain;
this.fireAction(
@@ -1610,6 +1611,7 @@
base: el.baseCommit ? el.baseCommit : null,
message: el.message,
allow_conflicts: conflicts,
+ committer_email: el.committerEmail ? el.committerEmail : null,
}
);
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
index 631d946..b953eec 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
@@ -629,6 +629,7 @@
allowConflicts: false,
rebaseChain: false,
onBehalfOfUploader: true,
+ committerEmail: 'test@default.org',
},
})
);
@@ -636,7 +637,12 @@
'/rebase',
assertUIActionInfo(rebaseAction),
true,
- {base: '1234', allow_conflicts: false, on_behalf_of_uploader: true},
+ {
+ base: '1234',
+ allow_conflicts: false,
+ on_behalf_of_uploader: true,
+ committer_email: 'test@default.org',
+ },
{allow_conflicts: false, on_behalf_of_uploader: true},
]);
});
@@ -1000,6 +1006,7 @@
base: null,
message: 'foo message',
allow_conflicts: false,
+ committer_email: null,
},
]);
});
@@ -1048,6 +1055,7 @@
base: null,
message: 'foo message',
allow_conflicts: true,
+ committer_email: null,
},
]);
});
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index c6675b3..85d9000 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -88,7 +88,11 @@
import {isUnresolved} from '../../../utils/comment-util';
import {PaperTabsElement} from '@polymer/paper-tabs/paper-tabs';
import {GrFileList} from '../gr-file-list/gr-file-list';
-import {EditRevisionInfo, ParsedChangeInfo} from '../../../types/types';
+import {
+ EditRevisionInfo,
+ LoadingStatus,
+ ParsedChangeInfo,
+} from '../../../types/types';
import {
EditableContentSaveEvent,
FileActionTapEvent,
@@ -120,7 +124,6 @@
ShortcutSection,
shortcutsServiceToken,
} from '../../../services/shortcuts/shortcuts-service';
-import {LoadingStatus} from '../../../models/change/change-model';
import {commentsModelToken} from '../../../models/comments/comments-model';
import {resolve} from '../../../models/dependency';
import {checksModelToken} from '../../../models/checks/checks-model';
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
index 19483a2..fba0392 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
@@ -59,11 +59,10 @@
import {GrEditControls} from '../../edit/gr-edit-controls/gr-edit-controls';
import {SinonFakeTimers} from 'sinon';
import {GerritView} from '../../../services/router/router-model';
-import {ParsedChangeInfo} from '../../../types/types';
+import {LoadingStatus, ParsedChangeInfo} from '../../../types/types';
import {
ChangeModel,
changeModelToken,
- LoadingStatus,
} from '../../../models/change/change-model';
import {FocusTarget} from '../gr-reply-dialog/gr-reply-dialog';
import {GrChangeStar} from '../../shared/gr-change-star/gr-change-star';
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
index 73521a6..1285664 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
@@ -20,6 +20,7 @@
import {createSearchUrl} from '../../../models/views/search';
import {getBrowseCommitWeblink} from '../../../utils/weblink-util';
import {shorten} from '../../../utils/patch-set-util';
+import {when} from 'lit/directives/when.js';
declare global {
interface HTMLElementTagNameMap {
@@ -31,7 +32,10 @@
export class GrCommitInfo extends LitElement {
// TODO(TS): Maybe limit to StandaloneCommitInfo.
@property({type: Object})
- commitInfo?: CommitInfo;
+ commitInfo?: Partial<CommitInfo>;
+
+ @property({type: Boolean})
+ showCopyButton = true;
@state() serverConfig?: ServerInfo;
@@ -45,6 +49,9 @@
align-items: center;
display: flex;
}
+ gr-weblink {
+ margin-right: 0;
+ }
`,
];
}
@@ -63,13 +70,18 @@
if (!commit) return nothing;
return html` <div class="container">
<gr-weblink imageAndText .info=${this.getWeblink(commit)}></gr-weblink>
- <gr-copy-clipboard
- hastooltip
- .buttonTitle=${'Copy full SHA to clipboard'}
- hideinput
- .text=${commit}
- >
- </gr-copy-clipboard>
+ ${when(
+ this.showCopyButton,
+ () => html`
+ <gr-copy-clipboard
+ hastooltip
+ .buttonTitle=${'Copy full SHA to clipboard'}
+ hideinput
+ .text=${commit}
+ >
+ </gr-copy-clipboard>
+ `
+ )}
</div>`;
}
@@ -86,6 +98,6 @@
this.serverConfig
);
if (primaryLink) return {...primaryLink, name};
- return {name, url: createSearchUrl({query: name})};
+ return {name, url: createSearchUrl({query: commit})};
}
}
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts
index 7bc4e60..3fed767 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts
@@ -79,7 +79,11 @@
assert.shadowDom.equal(
weblink,
/* HTML */ `
- <a href="/q/sha4567" rel="noopener noreferrer" target="_blank">
+ <a
+ href="/q/sha45678901234567890"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
<gr-tooltip-content>
<span> sha4567 </span>
</gr-tooltip-content>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
index b66b2cf..5e4e255 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
@@ -18,6 +18,8 @@
ChangeInfoId,
TopicName,
ChangeActionDialog,
+ EmailInfo,
+ GitPersonInfo,
} from '../../../types/common';
import {customElement, property, query, state} from 'lit/decorators.js';
import {
@@ -30,6 +32,7 @@
ChangeStatus,
ProgressStatus,
} from '../../../constants/constants';
+import {subscribe} from '../../lit/subscription-controller';
import {fire, fireNoBubble} from '../../../utils/event-util';
import {css, html, LitElement, PropertyValues} from 'lit';
import {sharedStyles} from '../../../styles/shared-styles';
@@ -43,6 +46,7 @@
import {ParsedChangeInfo} from '../../../types/types';
import {formStyles} from '../../../styles/form-styles';
import {branchName} from '../../../utils/patch-set-util';
+import {changeModelToken} from '../../../models/change/change-model';
const SUGGESTIONS_LIMIT = 15;
const CHANGE_SUBJECT_LIMIT = 50;
@@ -127,13 +131,24 @@
@state()
private invalidBranch = false;
+ @state()
+ emails: EmailInfo[] = [];
+
@query('#branchInput')
branchInput!: GrTypedAutocomplete<BranchName>;
+ @state()
+ committerEmail?: string;
+
+ @state()
+ latestCommitter?: GitPersonInfo;
+
private selectedChangeIds = new Set<ChangeInfoId>();
private readonly restApiService = getAppContext().restApiService;
+ private readonly getChangeModel = resolve(this, changeModelToken);
+
private readonly reporting = getAppContext().reportingService;
private readonly getNavigation = resolve(this, navigationToken);
@@ -142,6 +157,16 @@
super();
this.statuses = {};
this.query = (text: string) => this.getProjectBranchesSuggestions(text);
+ subscribe(
+ this,
+ () => this.getChangeModel().latestCommitter$,
+ x => (this.latestCommitter = x)
+ );
+ }
+
+ override connectedCallback() {
+ super.connectedCallback();
+ this.loadEmails();
}
override willUpdate(changedProperties: PropertyValues) {
@@ -341,6 +366,17 @@
@bind-value-changed=${(e: BindValueChangeEvent) =>
(this.message = e.detail.value ?? '')}
></iron-autogrow-textarea>
+ ${when(
+ this.canShowEmailDropdown(),
+ () => html`<div id="cherryPickEmailDropdown">Cherry Pick Committer Email
+ <gr-dropdown-list
+ .items=${this.getEmailDropdownItems()}
+ .value=${this.committerEmail}
+ @value-change=${this.setCommitterEmail}
+ >
+ </gr-dropdown-list>
+ <span></div>`
+ )}
`;
}
@@ -654,4 +690,38 @@
return branches;
});
}
+
+ async loadEmails() {
+ const accountEmails: EmailInfo[] =
+ (await this.restApiService.getAccountEmails()) ?? [];
+ let selectedEmail: string | undefined;
+ accountEmails.forEach(e => {
+ if (e.preferred) {
+ selectedEmail = e.email;
+ }
+ });
+
+ if (accountEmails.some(e => e.email === this.latestCommitter?.email)) {
+ selectedEmail = this.latestCommitter?.email;
+ }
+ this.emails = accountEmails;
+ this.committerEmail = selectedEmail;
+ }
+
+ private canShowEmailDropdown() {
+ return this.emails.length > 1;
+ }
+
+ private getEmailDropdownItems() {
+ return this.emails.map(e => {
+ return {
+ text: e.email,
+ value: e.email,
+ };
+ });
+ }
+
+ private setCommitterEmail(e: CustomEvent<{value: string}>) {
+ this.committerEmail = e.detail.value;
+ }
}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
index dc8dba9..83e6f9e 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
@@ -5,7 +5,12 @@
*/
import '../../../test/common-test-setup';
import './gr-confirm-cherrypick-dialog';
-import {queryAll, queryAndAssert, stubRestApi} from '../../../test/test-utils';
+import {
+ query,
+ queryAll,
+ queryAndAssert,
+ stubRestApi,
+} from '../../../test/test-utils';
import {GrConfirmCherrypickDialog} from './gr-confirm-cherrypick-dialog';
import {
BranchName,
@@ -24,11 +29,53 @@
import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
import {ProgressStatus} from '../../../constants/constants';
import {fixture, html, assert} from '@open-wc/testing';
+import {GrDropdownList} from '../../shared/gr-dropdown-list/gr-dropdown-list';
const CHERRY_PICK_TYPES = {
SINGLE_CHANGE: 1,
TOPIC: 2,
};
+
+const changes: ChangeInfo[] = [
+ {
+ ...createChange(),
+ id: '1234' as ChangeInfoId,
+ change_id: '12345678901234' as ChangeId,
+ topic: 'T' as TopicName,
+ subject: 'random',
+ project: 'A' as RepoName,
+ _number: 1 as NumericChangeId,
+ revisions: {
+ a: createRevision(),
+ },
+ current_revision: 'a' as CommitId,
+ },
+ {
+ ...createChange(),
+ id: '5678' as ChangeInfoId,
+ change_id: '23456' as ChangeId,
+ topic: 'T' as TopicName,
+ subject: 'a'.repeat(100),
+ project: 'B' as RepoName,
+ _number: 2 as NumericChangeId,
+ revisions: {
+ a: createRevision(),
+ },
+ current_revision: 'a' as CommitId,
+ },
+];
+
+const emails = [
+ {
+ email: 'primary@email.com',
+ preferred: true,
+ },
+ {
+ email: 'secondary@email.com',
+ preferred: false,
+ },
+];
+
suite('gr-confirm-cherrypick-dialog tests', () => {
let element: GrConfirmCherrypickDialog;
@@ -149,34 +196,6 @@
});
suite('cherry pick topic', () => {
- const changes: ChangeInfo[] = [
- {
- ...createChange(),
- id: '1234' as ChangeInfoId,
- change_id: '12345678901234' as ChangeId,
- topic: 'T' as TopicName,
- subject: 'random',
- project: 'A' as RepoName,
- _number: 1 as NumericChangeId,
- revisions: {
- a: createRevision(),
- },
- current_revision: 'a' as CommitId,
- },
- {
- ...createChange(),
- id: '5678' as ChangeInfoId,
- change_id: '23456' as ChangeId,
- topic: 'T' as TopicName,
- subject: 'a'.repeat(100),
- project: 'B' as RepoName,
- _number: 2 as NumericChangeId,
- revisions: {
- a: createRevision(),
- },
- current_revision: 'a' as CommitId,
- },
- ];
setup(async () => {
element.updateChanges(changes);
element.cherryPickType = CHERRY_PICK_TYPES.TOPIC;
@@ -290,4 +309,36 @@
assert.equal(branches.length, 1);
assert.equal(branches[0].name, 'test-branch');
});
+
+ suite('cherry pick single change with committer email', () => {
+ test('hide email dropdown when user has one email', async () => {
+ element.emails = emails.slice(0, 1);
+ await element.updateComplete;
+ assert.notExists(query(element, '#cherryPickEmailDropdown'));
+ });
+
+ test('show email dropdown when user has more than one email', async () => {
+ element.emails = emails;
+ await element.updateComplete;
+ const cherryPickEmailDropdown = queryAndAssert(
+ element,
+ '#cherryPickEmailDropdown'
+ );
+ assert.dom.equal(
+ cherryPickEmailDropdown,
+ `<div id="cherryPickEmailDropdown">Cherry Pick Committer Email
+ <gr-dropdown-list></gr-dropdown-list>
+ <span></span>
+ </div>`
+ );
+ const emailDropdown = queryAndAssert<GrDropdownList>(
+ cherryPickEmailDropdown,
+ 'gr-dropdown-list'
+ );
+ assert.deepEqual(
+ emailDropdown.items?.map(e => e.value),
+ emails.map(e => e.email)
+ );
+ });
+ });
});
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
index 8946a83..7a4caa7 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
@@ -8,18 +8,20 @@
import {customElement, property, query, state} from 'lit/decorators.js';
import {when} from 'lit/directives/when.js';
import {
- NumericChangeId,
- BranchName,
- ChangeActionDialog,
AccountDetailInfo,
AccountInfo,
+ BranchName,
+ ChangeActionDialog,
+ EmailInfo,
+ NumericChangeId,
+ GitPersonInfo,
} from '../../../types/common';
import '../../shared/gr-dialog/gr-dialog';
import '../../shared/gr-autocomplete/gr-autocomplete';
import {
- GrAutocomplete,
AutocompleteQuery,
AutocompleteSuggestion,
+ GrAutocomplete,
} from '../../shared/gr-autocomplete/gr-autocomplete';
import {getAppContext} from '../../../services/app-context';
import {sharedStyles} from '../../../styles/shared-styles';
@@ -43,6 +45,7 @@
allowConflicts: boolean;
rebaseChain: boolean;
onBehalfOfUploader: boolean;
+ committerEmail: string | null;
}
@customElement('gr-confirm-rebase-dialog')
@@ -92,6 +95,18 @@
@state()
allowConflicts = false;
+ @state()
+ selectedEmailForRebase: string | null | undefined;
+
+ @state()
+ currentUserEmails: EmailInfo[] = [];
+
+ @state()
+ uploaderEmails: EmailInfo[] = [];
+
+ @state()
+ committerEmailDropdownItems: EmailInfo[] = [];
+
@query('#rebaseOnParentInput')
private rebaseOnParentInput?: HTMLInputElement;
@@ -116,6 +131,9 @@
@state()
uploader?: AccountInfo;
+ @state()
+ latestCommitter?: GitPersonInfo;
+
private readonly restApiService = getAppContext().restApiService;
private readonly getChangeModel = resolve(this, changeModelToken);
@@ -150,6 +168,16 @@
() => this.getRelatedChangesModel().hasParent$,
x => (this.hasParent = x)
);
+ subscribe(
+ this,
+ () => this.getChangeModel().latestCommitter$,
+ x => (this.latestCommitter = x)
+ );
+ }
+
+ override connectedCallback() {
+ super.connectedCallback();
+ this.loadCommitterEmailDropdownItems();
}
override willUpdate(changedProperties: PropertyValues): void {
@@ -194,6 +222,9 @@
.rebaseOnBehalfMsg {
margin-top: var(--spacing-m);
}
+ .rebaseWithCommitterEmail {
+ margin-top: var(--spacing-m);
+ }
`,
];
}
@@ -288,6 +319,7 @@
type="checkbox"
@change=${() => {
this.allowConflicts = !!this.rebaseAllowConflicts?.checked;
+ this.loadCommitterEmailDropdownItems();
}}
/>
<label for="rebaseAllowConflicts"
@@ -311,6 +343,9 @@
type="checkbox"
@change=${() => {
this.shouldRebaseChain = !!this.rebaseChain?.checked;
+ if (this.shouldRebaseChain) {
+ this.selectedEmailForRebase = undefined;
+ }
}}
/>
<label for="rebaseChain">Rebase all ancestors</label>
@@ -325,6 +360,18 @@
></gr-account-chip
><span></div>`
)}
+ ${when(
+ this.canShowCommitterEmailDropdown(),
+ () => html`<div class="rebaseWithCommitterEmail"
+ >Rebase with committer email
+ <gr-dropdown-list
+ .items=${this.getCommitterEmailDropdownItems()}
+ .value=${this.selectedEmailForRebase}
+ @value-change=${this.handleCommitterEmailDropdownItems}
+ >
+ </gr-dropdown-list>
+ <span></div>`
+ )}
</div>
</gr-dialog>
`;
@@ -377,6 +424,69 @@
);
}
+ private setPreferredAsSelectedEmailForRebase(emails: EmailInfo[]) {
+ emails.forEach(e => {
+ if (e.preferred) {
+ this.selectedEmailForRebase = e.email;
+ }
+ });
+ }
+
+ private canShowCommitterEmailDropdown() {
+ return (
+ this.committerEmailDropdownItems &&
+ this.committerEmailDropdownItems.length > 1 &&
+ !this.shouldRebaseChain
+ );
+ }
+
+ private getCommitterEmailDropdownItems() {
+ return this.committerEmailDropdownItems?.map(e => {
+ return {
+ text: e.email,
+ value: e.email,
+ };
+ });
+ }
+
+ private isLatestCommitterEmailInDropdownItems(): boolean {
+ return this.committerEmailDropdownItems?.some(
+ e => e.email === this.latestCommitter?.email.toString()
+ );
+ }
+
+ public setSelectedEmailForRebase() {
+ if (this.isLatestCommitterEmailInDropdownItems()) {
+ this.selectedEmailForRebase = this.latestCommitter?.email;
+ } else {
+ this.setPreferredAsSelectedEmailForRebase(
+ this.committerEmailDropdownItems
+ );
+ }
+ }
+
+ async loadCommitterEmailDropdownItems() {
+ if (this.isCurrentUserEqualToLatestUploader() || this.allowConflicts) {
+ const currentUserEmails = await this.restApiService.getAccountEmails();
+ this.committerEmailDropdownItems = currentUserEmails || [];
+ } else if (this.uploader && this.uploader.email) {
+ const currentUploaderEmails =
+ await this.restApiService.getAccountEmailsFor(
+ this.uploader.email.toString()
+ );
+ this.committerEmailDropdownItems = currentUploaderEmails || [];
+ } else {
+ this.committerEmailDropdownItems = [];
+ }
+ if (this.committerEmailDropdownItems) {
+ this.setSelectedEmailForRebase();
+ }
+ }
+
+ private handleCommitterEmailDropdownItems(e: CustomEvent<{value: string}>) {
+ this.selectedEmailForRebase = e.detail.value;
+ }
+
filterChanges(
input: string,
changes: RebaseChange[]
@@ -436,6 +546,7 @@
allowConflicts: !!this.rebaseAllowConflicts?.checked,
rebaseChain: !!this.rebaseChain?.checked,
onBehalfOfUploader: this.rebaseOnBehalfOfUploader(),
+ committerEmail: this.selectedEmailForRebase || null,
};
fireNoBubbleNoCompose(this, 'confirm-rebase', detail);
this.text = '';
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
index 24f8a34..e6326b3 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
@@ -8,11 +8,18 @@
import {GrConfirmRebaseDialog, RebaseChange} from './gr-confirm-rebase-dialog';
import {
pressKey,
+ query,
queryAndAssert,
stubRestApi,
waitUntil,
} from '../../../test/test-utils';
-import {NumericChangeId, BranchName, Timestamp} from '../../../types/common';
+import {
+ NumericChangeId,
+ BranchName,
+ Timestamp,
+ AccountId,
+ EmailAddress,
+} from '../../../types/common';
import {
createAccountWithEmail,
createChangeViewChange,
@@ -22,11 +29,10 @@
import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
import {testResolver} from '../../../test/common-test-setup';
import {userModelToken} from '../../../models/user/user-model';
-import {
- changeModelToken,
- LoadingStatus,
-} from '../../../models/change/change-model';
+import {changeModelToken} from '../../../models/change/change-model';
import {GrAccountChip} from '../../shared/gr-account-chip/gr-account-chip';
+import {LoadingStatus} from '../../../types/types';
+import {GrDropdownList} from '../../shared/gr-dropdown-list/gr-dropdown-list';
suite('gr-confirm-rebase-dialog tests', () => {
let element: GrConfirmRebaseDialog;
@@ -159,6 +165,131 @@
});
});
+ suite('rebase with committer email', () => {
+ setup(async () => {
+ element.branch = 'test' as BranchName;
+ await element.updateComplete;
+ });
+
+ test('hide rebaseWithCommitterEmail dialog when committer has single email', async () => {
+ element.committerEmailDropdownItems = [
+ {
+ email: 'test1@example.com',
+ preferred: true,
+ pending_confirmation: true,
+ },
+ ];
+ await element.updateComplete;
+ assert.isNotOk(query(element, '.rebaseWithCommitterEmail'));
+ });
+
+ test('show rebaseWithCommitterEmail dialog when committer has more than one email', async () => {
+ element.committerEmailDropdownItems = [
+ {
+ email: 'test1@example.com',
+ preferred: true,
+ pending_confirmation: true,
+ },
+ {
+ email: 'test2@example.com',
+ pending_confirmation: true,
+ },
+ ];
+ await element.updateComplete;
+ const committerEmail = queryAndAssert(
+ element,
+ '.rebaseWithCommitterEmail'
+ );
+ assert.dom.equal(
+ committerEmail,
+ /* HTML */ `<div class="rebaseWithCommitterEmail"
+ >Rebase with committer email
+ <gr-dropdown-list>
+ </gr-dropdown-list>
+ <span></div>`
+ );
+ const dropdownList: GrDropdownList = queryAndAssert(
+ committerEmail,
+ 'gr-dropdown-list'
+ );
+ assert.strictEqual(dropdownList.items!.length, 2);
+ });
+
+ test('hide rebaseWithCommitterEmail dialog when RebaseChain is set', async () => {
+ element.shouldRebaseChain = true;
+ await element.updateComplete;
+ assert.isNotOk(query(element, '.rebaseWithCommitterEmail'));
+ });
+
+ test('show current user emails in the dropdown list when rebase with conflicts is allowed', async () => {
+ element.allowConflicts = true;
+ element.latestCommitter = {
+ email: 'commit@example.com' as EmailAddress,
+ name: 'committer',
+ date: '2023-06-12 18:32:08.000000000' as Timestamp,
+ };
+ element.committerEmailDropdownItems = [
+ {
+ email: 'currentuser1@example.com',
+ preferred: true,
+ pending_confirmation: true,
+ },
+ {
+ email: 'currentuser2@example.com',
+ pending_confirmation: true,
+ },
+ ];
+ await element.updateComplete;
+ const committerEmail = queryAndAssert(
+ element,
+ '.rebaseWithCommitterEmail'
+ );
+ const dropdownList: GrDropdownList = queryAndAssert(
+ committerEmail,
+ 'gr-dropdown-list'
+ );
+ assert.deepStrictEqual(
+ dropdownList.items!.map(e => e.value),
+ element.committerEmailDropdownItems.map(e => e.email)
+ );
+ });
+
+ test('show uploader emails in the dropdown list when rebase with conflicts is not allowed', async () => {
+ element.allowConflicts = false;
+ element.uploader = {_account_id: 2 as AccountId, name: '2'};
+ element.latestCommitter = {
+ email: 'commit@example.com' as EmailAddress,
+ name: 'committer',
+ date: '2023-06-12 18:32:08.000000000' as Timestamp,
+ };
+ element.committerEmailDropdownItems = [
+ {
+ email: 'uploader1@example.com',
+ preferred: true,
+ pending_confirmation: true,
+ },
+ {
+ email: 'uploader2@example.com',
+ preferred: false,
+ pending_confirmation: true,
+ },
+ ];
+ await element.updateComplete;
+ const committerEmail = queryAndAssert(
+ element,
+ '.rebaseWithCommitterEmail'
+ );
+ const dropdownList: GrDropdownList = queryAndAssert(
+ committerEmail,
+ 'gr-dropdown-list'
+ );
+ assert.deepStrictEqual(
+ dropdownList.items!.map(e => e.value),
+ element.committerEmailDropdownItems.map(e => e.email)
+ );
+ });
+ });
+
test('disableActions property disables dialog confirm', async () => {
element.disableActions = false;
await element.updateComplete;
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index 1c9ba8c..fdacf8b 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -47,8 +47,7 @@
import {DiffPreferencesInfo} from '../../../types/diff';
import {GrDiffHost} from '../../diff/gr-diff-host/gr-diff-host';
import {GrDiffPreferencesDialog} from '../../diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog';
-import {GrDiffCursor as GrDiffCursorNew} from '../../../embed/diff/gr-diff-cursor/gr-diff-cursor';
-import {GrDiffCursor} from '../../../embed/diff-old/gr-diff-cursor/gr-diff-cursor';
+import {GrDiffCursor} from '../../../embed/diff/gr-diff-cursor/gr-diff-cursor';
import {GrCursorManager} from '../../shared/gr-cursor-manager/gr-cursor-manager';
import {ChangeComments} from '../../diff/gr-comment-api/gr-comment-api';
import {ParsedChangeInfo, PatchSetFile} from '../../../types/types';
@@ -87,7 +86,6 @@
import {userModelToken} from '../../../models/user/user-model';
import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
import {FileMode, fileModeToString} from '../../../utils/file-util';
-import {isNewDiff} from '../../../embed/diff/gr-diff/gr-diff-utils';
export const DEFAULT_NUM_FILES_SHOWN = 200;
@@ -312,8 +310,7 @@
fileCursor = new GrCursorManager();
// private but used in test
- // TODO(newdiff-cleanup): Replace once newdiff migration is completed.
- diffCursor?: GrDiffCursor | GrDiffCursorNew;
+ diffCursor?: GrDiffCursor;
static override get styles() {
return [
@@ -899,8 +896,7 @@
);
}
});
- // TODO(newdiff-cleanup): Remove once newdiff migration is completed.
- this.diffCursor = isNewDiff() ? new GrDiffCursorNew() : new GrDiffCursor();
+ this.diffCursor = new GrDiffCursor();
this.diffCursor.replaceDiffs(this.diffs);
}
@@ -2318,13 +2314,6 @@
* Private but used in tests.
*/
async expandedFilesChanged(oldFiles: Array<PatchSetFile>) {
- // Clear content for any diffs that are not open so if they get re-opened
- // the stale content does not flash before it is cleared and reloaded.
- const collapsedDiffs = this.diffs.filter(
- diff => this.expandedFiles.findIndex(f => f.path === diff.path) === -1
- );
- this.clearCollapsedDiffs(collapsedDiffs);
-
this.filesExpanded = this.computeExpandedFiles();
const newFiles = this.expandedFiles.filter(
@@ -2341,14 +2330,6 @@
this.diffCursor?.reInitAndUpdateStops();
}
- // private but used in test
- clearCollapsedDiffs(collapsedDiffs: GrDiffHost[]) {
- for (const diff of collapsedDiffs) {
- diff.cancel();
- diff.clearDiffContent();
- }
- }
-
/**
* Given an array of paths and a NodeList of diff elements, render the diff
* for each path in order, awaiting the previous render to complete before
@@ -2431,7 +2412,6 @@
if (this.cancelForEachDiff) {
this.cancelForEachDiff();
}
- this.forEachDiff(d => d.cancel());
}
/**
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
index b2cd430..0f9cf6a 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
@@ -1373,7 +1373,6 @@
await waitEventLoop();
const renderSpy = sinon.spy(element, 'renderInOrder');
- const collapseStub = sinon.stub(element, 'clearCollapsedDiffs');
assert.equal(
queryAndAssert<GrIcon>(element, 'gr-icon').icon,
@@ -1385,7 +1384,6 @@
// Wait for expandedFilesChanged to finish.
await waitEventLoop();
- assert.equal(collapseStub.lastCall.args[0].length, 0);
assert.equal(
queryAndAssert<GrIcon>(element, 'gr-icon').icon,
'expand_less'
@@ -1404,11 +1402,9 @@
);
assert.equal(renderSpy.callCount, 1);
assert.isFalse(element.expandedFiles.some(f => f.path === path));
- assert.equal(collapseStub.lastCall.args[0].length, 1);
});
test('expandAllDiffs and collapseAllDiffs', async () => {
- const collapseStub = sinon.stub(element, 'clearCollapsedDiffs');
assertIsDefined(element.diffCursor);
const reInitStub = sinon.stub(element.diffCursor, 'reInitAndUpdateStops');
@@ -1423,7 +1419,6 @@
await waitEventLoop();
assert.equal(element.filesExpanded, FilesExpandedState.ALL);
assert.isTrue(reInitStub.calledTwice);
- assert.equal(collapseStub.lastCall.args[0].length, 0);
element.collapseAllDiffs();
await element.updateComplete;
@@ -1431,7 +1426,6 @@
await waitEventLoop();
assert.equal(element.expandedFiles.length, 0);
assert.equal(element.filesExpanded, FilesExpandedState.NONE);
- assert.equal(collapseStub.lastCall.args[0].length, 1);
});
test('expandedFilesChanged', async () => {
@@ -1467,19 +1461,6 @@
await promise;
});
- test('clearCollapsedDiffs', () => {
- // Have to type as any because the type is 'GrDiffHost'
- // which would require stubbing so many different
- // methods / properties that it isn't worth it.
- const diff = {
- cancel: sinon.stub(),
- clearDiffContent: sinon.stub(),
- } as any;
- element.clearCollapsedDiffs([diff]);
- assert.isTrue(diff.cancel.calledOnce);
- assert.isTrue(diff.clearDiffContent.calledOnce);
- });
-
test('filesExpanded value updates to correct enum', async () => {
element.files = [normalize({}, 'foo.bar'), normalize({}, 'baz.bar')];
await element.updateComplete;
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index 7987354..b58f2fa 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -1452,7 +1452,9 @@
this.includeComments = true;
fireNoBubble(this, 'send', {});
fireIronAnnounce(this, 'Reply sent');
+ return;
})
+ .then(result => result)
.finally(() => {
this.getNavigation().releaseNavigation('sending review');
this.disabled = false;
diff --git a/polygerrit-ui/app/elements/change/gr-revision-parents/gr-revision-parents.ts b/polygerrit-ui/app/elements/change/gr-revision-parents/gr-revision-parents.ts
index 7cfebf1..06cabfd 100644
--- a/polygerrit-ui/app/elements/change/gr-revision-parents/gr-revision-parents.ts
+++ b/polygerrit-ui/app/elements/change/gr-revision-parents/gr-revision-parents.ts
@@ -4,21 +4,35 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {customElement, state} from 'lit/decorators.js';
-import {css, html, LitElement} from 'lit';
+import {css, html, HTMLTemplateResult, LitElement} from 'lit';
import {resolve} from '../../../models/dependency';
import {subscribe} from '../../lit/subscription-controller';
import {changeModelToken} from '../../../models/change/change-model';
-import {EDIT, RevisionInfo} from '../../../api/rest-api';
+import {
+ CommitId,
+ EDIT,
+ NumericChangeId,
+ ParentInfo,
+ PatchSetNumber,
+ RepoName,
+ RevisionInfo,
+} from '../../../api/rest-api';
import {fontStyles} from '../../../styles/gr-font-styles';
-import {branchName, shorten} from '../../../utils/patch-set-util';
+import {branchName} from '../../../utils/patch-set-util';
import {when} from 'lit/directives/when.js';
+import {createChangeUrl} from '../../../models/views/change';
+import {sharedStyles} from '../../../styles/shared-styles';
@customElement('gr-revision-parents')
export class GrRevisionParents extends LitElement {
+ @state() repo?: RepoName;
+
@state() revision?: RevisionInfo;
@state() baseRevision?: RevisionInfo;
+ @state() showDetails = false;
+
private readonly getChangeModel = resolve(this, changeModelToken);
constructor() {
@@ -33,6 +47,11 @@
);
subscribe(
this,
+ () => this.getChangeModel().repo$,
+ x => (this.repo = x)
+ );
+ subscribe(
+ this,
() => this.getChangeModel().baseRevision$,
x => (this.baseRevision = x)
);
@@ -41,6 +60,7 @@
static override get styles() {
return [
fontStyles,
+ sharedStyles,
css`
:host {
display: block;
@@ -48,9 +68,8 @@
div.container {
padding: var(--spacing-m) var(--spacing-l);
border-top: 1px solid var(--border-color);
- background-color: var(--yellow-50);
}
- .flex {
+ .sections {
display: flex;
}
.section {
@@ -63,53 +82,312 @@
.title {
font-weight: var(--font-weight-bold);
}
+ .messageContainer {
+ display: flex;
+ padding: var(--spacing-m) var(--spacing-l);
+ border-top: 1px solid var(--border-color);
+ }
+ .messageContainer.info {
+ background-color: var(--info-background);
+ }
+ .messageContainer.warning {
+ background-color: var(--warning-background);
+ }
+ .messageContainer gr-icon {
+ margin-right: var(--spacing-m);
+ }
+ .messageContainer.info gr-icon {
+ color: var(--info-foreground);
+ }
+ .messageContainer.warning gr-icon {
+ color: var(--warning-foreground);
+ }
+ .messageContainer .text {
+ max-width: 600px;
+ }
+ .messageContainer .text p {
+ margin: 0;
+ }
+ .messageContainer .text gr-button {
+ margin-left: -4px;
+ }
+ gr-commit-info {
+ display: inline-block;
+ }
`,
];
}
override render() {
- // TODO(revision-parents): Figure out what to do about multiple parents.
- const baseParent = this.baseRevision?.parents_data?.[0];
- const parent = this.revision?.parents_data?.[0];
- if (!parent || !baseParent) return;
- // TODO(revision-parents): Design something nicer for the various cases.
+ return html`${this.renderMessage()}${this.renderDetails()}`;
+ }
+
+ private renderMessage() {
+ if (!this.baseRevision || !this.revision) return;
+ // For merges we are only interested in the target branch parent, which is [0].
+ // And for non-merges there is no more than 1 parent, so [0] is the only choice.
+ const parentLeft = this.baseRevision?.parents_data?.[0];
+ const parentRight = this.revision?.parents_data?.[0];
+ // Note that is you diff a patchset against its base, then baseRevision will be
+ // `undefined`. Thus after this line we know that we are dealing with diffs
+ // of the type "patchset x vs patchset y".
+ if (!parentLeft || !parentRight) return;
+
+ const psLeft = this.baseRevision?._number;
+ const psRight = this.revision?._number;
+ const parentCommitLeft = parentLeft.commit_id;
+ const parentCommitRight = parentRight.commit_id;
+ const branchLeft = branchName(parentLeft.branch_name);
+ const branchRight = branchName(parentRight.branch_name);
+ const isMergedLeft = parentLeft.is_merged_in_target_branch;
+ const isMergedRight = parentRight.is_merged_in_target_branch;
+ const changeNumLeft = parentLeft.change_number;
+ const changeNumRight = parentRight.change_number;
+ const changePsLeft = parentLeft.patch_set_number;
+ const changePsRight = parentRight.patch_set_number;
+
+ if (parentCommitLeft === parentCommitRight) return;
+
+ // Subsequently: different commit
+
+ if (branchLeft !== branchRight) {
+ return html`
+ ${this.renderWarning(
+ 'warning',
+ html`
+ Patchset ${psLeft} and ${psRight} are targeting different branches.
+ `
+ )}
+ `;
+ }
+
+ // Subsequently: different commit, same target branch
+
+ // Such a situation is really rare and weird. You have to do something like committing to one
+ // branch and then uploading to another. This warning should actually also be shown, if
+ // you are not comparing PS X and PS Y, because it is generally a weird patchset state.
+ const isWeirdLeft = !isMergedLeft && !changeNumLeft;
+ const isWeirdRight = !isMergedRight && !changeNumRight;
+ if (isWeirdLeft || isWeirdRight) {
+ const weirdPs =
+ isWeirdLeft && isWeirdRight
+ ? `${psLeft} and ${psRight} are`
+ : isWeirdLeft
+ ? `${psLeft} is`
+ : `${psRight} is`;
+ return html`
+ ${this.renderWarning(
+ 'warning',
+ html`
+ Patchset ${weirdPs} based on a commit that neither exists in its
+ target branch, nor is it a commit of another active change.
+ `
+ )}
+ `;
+ }
+
+ if (
+ changeNumLeft &&
+ changeNumRight &&
+ changeNumLeft === changeNumRight &&
+ // This check is probably redundant, because "same change and ps" should mean "same commit".
+ psLeft !== psRight
+ ) {
+ return html`
+ ${this.renderWarning(
+ 'info',
+ html`
+ The change was rebased from patchset
+ ${this.renderPatchsetLink(changeNumLeft, changePsLeft)} onto
+ patchset ${this.renderPatchsetLink(changeNumLeft, changePsRight)} of
+ change ${this.renderChangeLink(changeNumLeft)}
+ ${when(isMergedRight, () => html` (MERGED)`)}.
+ `
+ )}
+ `;
+ }
+
+ // No additional info? Then "different commit" and "same branch" means "standard rebase".
+ if (isMergedLeft && isMergedRight) {
+ return html`
+ ${this.renderWarning(
+ 'info',
+ html`
+ The change was rebased from
+ ${this.renderCommitLink(parentCommitLeft, false)} onto
+ ${this.renderCommitLink(parentCommitRight, false)}.
+ `
+ )}
+ `;
+ }
+
+ // By now we know that we have different commit, same target branch, no weird parent,
+ // and not a standard rebase. So let's spell out what the left and right side are based on.
+ return this.renderWarning(
+ 'warning',
+ html`${this.renderInfo(this.baseRevision)}<br />${this.renderInfo(
+ this.revision
+ )}`
+ );
+ }
+
+ private renderInfo(rev: RevisionInfo) {
+ const parent = rev.parents_data?.[0];
+ if (!parent) return;
+ const ps = rev._number;
+ const isMerged = parent.is_merged_in_target_branch;
+ const changeNum = parent.change_number;
+
+ if (changeNum && !isMerged) {
+ return html`
+ Patchset ${ps} is based on patchset
+ ${this.renderPatchsetLink(changeNum, parent.patch_set_number)} of change
+ ${this.renderChangeLink(changeNum)}.
+ `;
+ } else {
+ return html`
+ Patchset ${ps} is based on commit
+ ${this.renderCommitLink(parent.commit_id, false)} in the target branch
+ (${branchName(parent.branch_name)}).
+ `;
+ }
+ }
+
+ private renderWarning(icon: string, message: HTMLTemplateResult) {
+ const isWarning = icon === 'warning';
+ return html`
+ <div class="messageContainer ${icon}">
+ <div class="icon">
+ <gr-icon icon=${icon}></gr-icon>
+ </div>
+ <div class="text">
+ <p>
+ ${message}${when(
+ isWarning,
+ () => html`
+ <br />
+ The diff below may not be meaningful and may even be hiding
+ relevant changes.
+ `
+ )}
+ </p>
+ ${when(
+ isWarning,
+ () => html`
+ <p>
+ <gr-button
+ link
+ @click=${() => (this.showDetails = !this.showDetails)}
+ >${this.showDetails ? 'Hide' : 'Show'} details</gr-button
+ >
+ </p>
+ `
+ )}
+ </div>
+ </div>
+ `;
+ }
+
+ private renderDetails() {
+ if (!this.showDetails) return;
+ if (!this.baseRevision || !this.revision) return;
+ const parentLeft = this.baseRevision.parents_data?.[0];
+ const parentRight = this.revision.parents_data?.[0];
+ if (!parentRight || !parentLeft) return;
+
return html`
<div class="container">
- <div class="flex">
- <div class="section">
- <h4 class="heading-4">Patchset ${this.baseRevision?._number}</h4>
- <div>Branch: ${branchName(parent.branch_name)}</div>
- <div>Commit: ${shorten(parent.commit_id)}</div>
- <div>Is Merged: ${parent.is_merged_in_target_branch}</div>
- ${when(
- !!parent.change_number,
- () => html` <div>
- Change ID: ${parent.change_id?.substring(0, 10)}
- </div>
- <div>Change Number: ${parent.change_number}</div>
- <div>Patchset Number: ${parent.patch_set_number}</div>
- <div>Change Status: ${parent.change_status}</div>`
- )}
- </div>
- <div class="section">
- <h4 class="heading-4">Patchset ${this.revision?._number}</h4>
- <div>Branch: ${branchName(baseParent.branch_name)}</div>
- <div>Commit: ${shorten(baseParent.commit_id)}</div>
- <div>Is Merged: ${baseParent.is_merged_in_target_branch}</div>
- ${when(
- !!baseParent.change_number,
- () => html`<div>
- Change ID: ${baseParent.change_id?.substring(0, 10)}
- </div>
- <div>Change Number: ${baseParent.change_number}</div>
- <div>Patchset Number: ${baseParent.patch_set_number}</div>
- <div>Change Status: ${baseParent.change_status}</div>`
- )}
- </div>
+ <div class="sections">
+ ${this.renderSection(
+ this.baseRevision,
+ parentLeft,
+ parentLeft.change_number === parentRight.change_number
+ )}
+ ${this.renderSection(
+ this.revision,
+ parentRight,
+ parentLeft.change_number === parentRight.change_number
+ )}
</div>
</div>
`;
}
+
+ private renderCommitLink(commit?: CommitId, showCopyButton = true) {
+ if (!commit) return;
+ return html`<gr-commit-info
+ .commitInfo=${{commit}}
+ .showCopyButton=${showCopyButton}
+ ></gr-commit-info>`;
+ }
+
+ private renderChangeLink(changeNum: NumericChangeId) {
+ return html`
+ <a href=${createChangeUrl({changeNum, repo: this.repo!})}>${changeNum}</a>
+ `;
+ }
+
+ private renderPatchsetLink(
+ changeNum: NumericChangeId,
+ patchNum?: PatchSetNumber
+ ) {
+ if (!patchNum) return;
+ return html`
+ <a
+ href=${createChangeUrl({
+ changeNum,
+ repo: this.repo!,
+ patchNum,
+ })}
+ >${patchNum}</a
+ >
+ `;
+ }
+
+ private renderSection(
+ revision: RevisionInfo,
+ parent: ParentInfo,
+ sameChange: boolean
+ ) {
+ const ps = revision._number;
+ const commit = parent.commit_id;
+ const branch = branchName(parent.branch_name);
+ const isMerged = parent.is_merged_in_target_branch;
+ const changeNum = parent.change_number as NumericChangeId;
+ const changePs = parent.patch_set_number;
+
+ createChangeUrl({changeNum, repo: this.repo!});
+
+ return html`
+ <div class="section">
+ <h4 class="heading-4">Patchset ${ps}</h4>
+ <div>Target branch: ${branch}</div>
+ <div>Base commit: ${this.renderCommitLink(commit)}</div>
+ ${when(
+ !changeNum && !isMerged,
+ () => html`
+ <div>
+ <gr-icon icon="warning"></gr-icon>
+ <span
+ >Warning: The base commit is not known (aka reachable) in the
+ target branch.</span
+ >
+ </div>
+ `
+ )}
+ ${when(
+ changeNum && (sameChange || !isMerged),
+ () => html`
+ <div>
+ Base change: ${this.renderChangeLink(changeNum)}, patchset
+ ${this.renderPatchsetLink(changeNum, changePs)}
+ ${when(isMerged, () => html`(MERGED)`)}
+ </div>
+ `
+ )}
+ </div>
+ `;
+ }
}
declare global {
diff --git a/polygerrit-ui/app/elements/change/gr-revision-parents/gr-revision-parents_test.ts b/polygerrit-ui/app/elements/change/gr-revision-parents/gr-revision-parents_test.ts
index a1b9fdd..7b97550 100644
--- a/polygerrit-ui/app/elements/change/gr-revision-parents/gr-revision-parents_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-revision-parents/gr-revision-parents_test.ts
@@ -13,12 +13,68 @@
ChangeStatus,
CommitId,
NumericChangeId,
+ ParentInfo,
PatchSetNumber,
} from '../../../api/rest-api';
+import {queryAll} from '../../../utils/common-util';
+
+const PARENT_DEFAULT: ParentInfo = {
+ branch_name: 'refs/heads/master',
+ commit_id: '78e52ce873b1c08396422f51ad6aacf77ed95541' as CommitId,
+ is_merged_in_target_branch: true,
+};
+
+const PARENT_REBASED: ParentInfo = {
+ ...PARENT_DEFAULT,
+ commit_id: '00002ce873b1c08396422f51ad6aacf77ed95541' as CommitId,
+};
+
+const PARENT_OTHER_BRANCH: ParentInfo = {
+ ...PARENT_DEFAULT,
+ branch_name: 'refs/heads/otherbranch',
+ commit_id: '11112ce873b1c08396422f51ad6aacf77ed95541' as CommitId,
+};
+
+const PARENT_WEIRD: ParentInfo = {
+ ...PARENT_DEFAULT,
+ commit_id: '22222ce873b1c08396422f51ad6aacf77ed95541' as CommitId,
+ is_merged_in_target_branch: false,
+};
+
+const PARENT_CHANGE_123_1: ParentInfo = {
+ ...PARENT_DEFAULT,
+ commit_id: '12312ce873b1c08396422f51ad6aacf77ed95541' as CommitId,
+ is_merged_in_target_branch: false,
+ change_id: 'Idc69e6d7bba0ce0a9a0bdcd22adb506c0b76e628' as ChangeId,
+ change_number: 123 as NumericChangeId,
+ patch_set_number: 1 as PatchSetNumber,
+ change_status: ChangeStatus.NEW,
+};
+
+const PARENT_CHANGE_123_2: ParentInfo = {
+ ...PARENT_CHANGE_123_1,
+ commit_id: '12322ce873b1c08396422f51ad6aacf77ed95541' as CommitId,
+ patch_set_number: 2 as PatchSetNumber,
+};
suite('gr-revision-parents tests', () => {
let element: GrRevisionParents;
+ const setParents = async (
+ parentLeft: ParentInfo,
+ parentRight: ParentInfo
+ ) => {
+ element.baseRevision = {
+ ...createRevision(1),
+ parents_data: [parentLeft],
+ };
+ element.revision = {
+ ...createRevision(2),
+ parents_data: [parentRight],
+ };
+ await element.updateComplete;
+ };
+
setup(async () => {
element = await fixture(html`<gr-revision-parents></gr-revision-parents>`);
await element.updateComplete;
@@ -28,65 +84,155 @@
assert.shadowDom.equal(element, '');
});
- test('render', async () => {
- element.baseRevision = {
- ...createRevision(1),
- parents_data: [
- {
- branch_name: 'refs/heads/master',
- commit_id: '78e52ce873b1c08396422f51ad6aacf77ed95541' as CommitId,
- is_merged_in_target_branch: false,
- change_id: 'Idc69e6d7bba0ce0a9a0bdcd22adb506c0b76e628' as ChangeId,
- change_number: 1500 as NumericChangeId,
- patch_set_number: 1 as PatchSetNumber,
- change_status: ChangeStatus.NEW,
- },
- ],
- };
- element.revision = {
- ...createRevision(2),
- parents_data: [
- {
- branch_name: 'refs/heads/master',
- commit_id: '78e52ce873b1c08396422f51ad6aacf77ed95541' as CommitId,
- is_merged_in_target_branch: false,
- change_id: 'Idc69e6d7bba0ce0a9a0bdcd22adb506c0b76e628' as ChangeId,
- change_number: 1500 as NumericChangeId,
- patch_set_number: 2 as PatchSetNumber,
- change_status: ChangeStatus.NEW,
- },
- ],
- };
- await element.updateComplete;
-
- assert.shadowDom.equal(
- element,
+ test('render details: PARENT_DEFAULT', async () => {
+ element.showDetails = true;
+ await setParents(PARENT_DEFAULT, PARENT_DEFAULT);
+ assert.dom.equal(
+ queryAll(element, '.section')[0],
/* HTML */ `
- <div class="container">
- <div class="flex">
- <div class="section">
- <h4 class="heading-4">Patchset 1</h4>
- <div>Branch: master</div>
- <div>Commit: 78e52ce</div>
- <div>Is Merged: false</div>
- <div>Change ID: Idc69e6d7b</div>
- <div>Change Number: 1500</div>
- <div>Patchset Number: 2</div>
- <div>Change Status: NEW</div>
- </div>
- <div class="section">
- <h4 class="heading-4">Patchset 2</h4>
- <div>Branch: master</div>
- <div>Commit: 78e52ce</div>
- <div>Is Merged: false</div>
- <div>Change ID: Idc69e6d7b</div>
- <div>Change Number: 1500</div>
- <div>Patchset Number: 1</div>
- <div>Change Status: NEW</div>
- </div>
+ <div class="section">
+ <h4 class="heading-4">Patchset 1</h4>
+ <div>Target branch: master</div>
+ <div>
+ Base commit:
+ <gr-commit-info> </gr-commit-info>
</div>
</div>
`
);
});
+
+ test('render details: PARENT_WEIRD', async () => {
+ element.showDetails = true;
+ await setParents(PARENT_WEIRD, PARENT_WEIRD);
+ assert.dom.equal(
+ queryAll(element, '.section')[0],
+ `
+ <div class="section">
+ <h4 class="heading-4">Patchset 1</h4>
+ <div>Target branch: master</div>
+ <div>
+ Base commit:
+ <gr-commit-info> </gr-commit-info>
+ </div>
+ <div>
+ <gr-icon icon="warning"> </gr-icon>
+ <span>
+ Warning: The base commit is not known (aka reachable) in the
+ target branch.
+ </span>
+ </div>
+ </div>
+ `
+ );
+ });
+
+ test('render details: PARENT_CHANGE_123_1', async () => {
+ element.showDetails = true;
+ await setParents(PARENT_CHANGE_123_1, PARENT_CHANGE_123_2);
+ assert.dom.equal(
+ queryAll(element, '.section')[0],
+ /* HTML */ `
+ <div class="section">
+ <h4 class="heading-4">Patchset 1</h4>
+ <div>Target branch: master</div>
+ <div>
+ Base commit:
+ <gr-commit-info> </gr-commit-info>
+ </div>
+ <div>
+ Base change:
+ <a href="/c/123"> 123 </a>
+ , patchset
+ <a href="/c/123/1"> 1 </a>
+ </div>
+ </div>
+ `
+ );
+ });
+
+ test('render message PARENT_DEFAULT vs PARENT_DEFAULT', async () => {
+ await setParents(PARENT_DEFAULT, PARENT_DEFAULT);
+ assert.shadowDom.equal(element, '');
+ });
+
+ test('render message PARENT_DEFAULT vs PARENT_OTHER_BRANCH', async () => {
+ await setParents(PARENT_DEFAULT, PARENT_OTHER_BRANCH);
+ assert.shadowDom.equal(
+ element,
+ `<div class="messageContainer warning">
+ <div class="icon"><gr-icon icon="warning"></gr-icon></div>
+ <div class="text"><p>
+ Patchset 1 and 2 are targeting different branches.<br/>
+ The diff below may not be meaningful and may even be hiding
+ relevant changes.
+ </p><p><gr-button link="">Show details</gr-button></p></div></div>`
+ );
+ });
+
+ test('render message PARENT_DEFAULT vs PARENT_WEIRD', async () => {
+ await setParents(PARENT_DEFAULT, PARENT_WEIRD);
+ assert.shadowDom.equal(
+ element,
+ `<div class="messageContainer warning">
+ <div class="icon"><gr-icon icon="warning"></gr-icon></div>
+ <div class="text"><p>
+ Patchset 2 is based on a commit that neither exists in its
+ target branch, nor is it a commit of another active change.<br/>
+ The diff below may not be meaningful and may even be hiding
+ relevant changes.
+ </p><p><gr-button link="">Show details</gr-button></p></div></div>`
+ );
+ });
+
+ test('render message PARENT_DEFAULT vs PARENT_REBASED', async () => {
+ await setParents(PARENT_DEFAULT, PARENT_REBASED);
+ assert.shadowDom.equal(
+ element,
+ `<div class="messageContainer info">
+ <div class="icon"><gr-icon icon="info"></gr-icon></div>
+ <div class="text"><p>
+ The change was rebased from <gr-commit-info></gr-commit-info>
+ onto <gr-commit-info></gr-commit-info>.
+ </p></div></div>`
+ );
+ });
+
+ test('render message PARENT_CHANGE_123_1 vs PARENT_CHANGE_123_2', async () => {
+ await setParents(PARENT_CHANGE_123_1, PARENT_CHANGE_123_2);
+ assert.shadowDom.equal(
+ element,
+ `<div class="messageContainer info">
+ <div class="icon"><gr-icon icon="info"></gr-icon></div>
+ <div class="text"><p>
+ The change was rebased from patchset
+ <a href="/c/123/1">1</a> onto
+ patchset
+ <a href="/c/123/2">2</a> of
+ change
+ <a href="/c/123">123</a>.
+ </p></div></div>`
+ );
+ });
+
+ test('render message PARENT_DEFAULT vs PARENT_CHANGE_123_1', async () => {
+ await setParents(PARENT_DEFAULT, PARENT_CHANGE_123_1);
+ assert.shadowDom.equal(
+ element,
+ `<div class="messageContainer warning">
+ <div class="icon"><gr-icon icon="warning"></gr-icon></div>
+ <div class="text"><p>
+ Patchset 1 is based on commit
+ <gr-commit-info></gr-commit-info>
+ in the target branch
+ (master).<br>
+ Patchset 2 is based on patchset
+ <a href="/c/123/1">1</a>
+ of change
+ <a href="/c/123">123</a>.<br>
+ The diff below may not be meaningful and may even be hiding
+ relevant changes.
+ </p><p><gr-button link="">Show details</gr-button></p></div></div>`
+ );
+ });
});
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
index be94050..a55173c 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
@@ -437,7 +437,9 @@
></gr-icon>
</div>
${this.renderRegister()}
- <a class="loginButton" href=${this.loginUrl}>${this.loginText}</a>
+ <gr-endpoint-decorator name="auth-link">
+ <a class="loginButton" href=${this.loginUrl}>${this.loginText}</a>
+ </gr-endpoint-decorator>
<a
class="settingsButton"
href="${getBaseUrl()}/settings/"
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
index d2b833f..4b9c313 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
@@ -82,7 +82,9 @@
>
</gr-icon>
</div>
- <a class="loginButton" href="/login"> Sign in </a>
+ <gr-endpoint-decorator name="auth-link">
+ <a class="loginButton" href="/login"> Sign in </a>
+ </gr-endpoint-decorator>
<a
aria-label="Settings"
class="settingsButton"
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
index bb67a40..b7cbc34 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -70,6 +70,7 @@
createDiffUrl,
} from '../../../models/views/change';
import {
+ DashboardType,
DashboardViewModel,
DashboardViewState,
PROJECT_DASHBOARD_ROUTE,
@@ -79,7 +80,6 @@
SettingsViewState,
} from '../../../models/views/settings';
import {define} from '../../../models/dependency';
-import {Finalizable} from '../../../services/registry';
import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
import {
@@ -98,13 +98,14 @@
} from '../../../utils/comment-util';
import {isFileUnchanged} from '../../../utils/diff-util';
import {Route, ViewState} from '../../../models/views/base';
-import {Model} from '../../../models/model';
+import {Model} from '../../../models/base/model';
import {
InteractivePromise,
interactivePromise,
noAwait,
timeoutPromise,
} from '../../../utils/async-util';
+import {Finalizable} from '../../../types/types';
// TODO: Move all patterns to view model files and use the `Route` interface,
// which will enforce using `RegExp` in its `urlPattern` property.
@@ -196,10 +197,6 @@
CHANGE_ID_QUERY: /^\/id\/(I[0-9a-f]{40})$/,
- // Matches /c/<changeNum>/[*][/].
- CHANGE_LEGACY: /^\/c\/(\d+)\/?(.*)$/,
- CHANGE_NUMBER_LEGACY: /^\/(\d+)\/?/,
-
// Matches
// /c/<project>/+/<changeNum>/[<basePatchNum|edit>..][<patchNum|edit>].
// TODO(kaspern): Migrate completely to project based URLs, with backwards
@@ -849,12 +846,6 @@
);
this.mapRoute(
- RoutePattern.CHANGE_NUMBER_LEGACY,
- 'handleChangeNumberLegacyRoute',
- ctx => this.handleChangeNumberLegacyRoute(ctx)
- );
-
- this.mapRoute(
RoutePattern.DIFF_EDIT,
'handleDiffEditRoute',
ctx => this.handleDiffEditRoute(ctx),
@@ -884,10 +875,6 @@
this.handleChangeRoute(ctx)
);
- this.mapRoute(RoutePattern.CHANGE_LEGACY, 'handleChangeLegacyRoute', ctx =>
- this.handleChangeLegacyRoute(ctx)
- );
-
this.mapRoute(
RoutePattern.AGREEMENTS,
'handleAgreementsRoute',
@@ -1020,6 +1007,7 @@
} else {
const state: DashboardViewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.USER,
user: ctx.params[0],
};
// Note that router model view must be updated before view models.
@@ -1055,6 +1043,7 @@
const state: DashboardViewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.CUSTOM,
user: 'self',
sections,
title,
@@ -1305,14 +1294,6 @@
this.redirect(ctx.path.replace(LEGACY_QUERY_SUFFIX_PATTERN, ''));
}
- handleChangeNumberLegacyRoute(ctx: PageContext) {
- this.redirect(
- '/c/' +
- ctx.params[0] +
- (ctx.querystring.length > 0 ? `?${ctx.querystring}` : '')
- );
- }
-
handleChangeRoute(ctx: PageContext) {
// Parameter order is based on the regex group number matched.
const changeNum = Number(ctx.params[1]) as NumericChangeId;
@@ -1464,26 +1445,6 @@
this.changeViewModel.setState(state);
}
- handleChangeLegacyRoute(ctx: PageContext) {
- const changeNum = Number(ctx.params[0]) as NumericChangeId;
- if (!changeNum) {
- this.show404();
- return;
- }
- this.restApiService.getFromProjectLookup(changeNum).then(project => {
- // Show a 404 and terminate if the lookup request failed. Attempting
- // to redirect after failing to get the project loops infinitely.
- if (!project) {
- this.show404();
- return;
- }
- this.redirect(
- `/c/${project}/+/${changeNum}/${ctx.params[1]}` +
- (ctx.querystring.length > 0 ? `?${ctx.querystring}` : '')
- );
- });
- }
-
handleLegacyLinenum(ctx: PageContext) {
this.redirect(ctx.path.replace(LEGACY_LINENUM_PATTERN, '#$1'));
}
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
index 234bf95..ae45326 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
@@ -51,6 +51,7 @@
} from '../../../test/test-data-generators';
import {ParsedChangeInfo} from '../../../types/types';
import {ViewState} from '../../../models/views/base';
+import {DashboardType} from '../../../models/views/dashboard';
suite('gr-router tests', () => {
let router: GrRouter;
@@ -168,13 +169,11 @@
const unauthenticatedHandlers = [
'handleBranchListRoute',
'handleChangeIdQueryRoute',
- 'handleChangeNumberLegacyRoute',
'handleChangeRoute',
'handleCommentRoute',
'handleCommentsRoute',
'handleDiffRoute',
'handleDefaultRoute',
- 'handleChangeLegacyRoute',
'handleDocumentationRedirectRoute',
'handleDocumentationSearchRoute',
'handleDocumentationSearchRedirectRoute',
@@ -591,6 +590,7 @@
// CUSTOM_DASHBOARD: /^\/dashboard\/?$/,
await checkUrlToState('/dashboard?title=Custom Dashboard&a=b&d=e', {
...createDashboardViewState(),
+ type: DashboardType.CUSTOM,
sections: [
{name: 'a', query: 'b'},
{name: 'd', query: 'e'},
@@ -599,6 +599,7 @@
});
await checkUrlToState('/dashboard?a=b&c&d=&=e&foreach=is:open', {
...createDashboardViewState(),
+ type: DashboardType.CUSTOM,
sections: [{name: 'a', query: 'is:open b'}],
title: 'Custom Dashboard',
});
@@ -853,21 +854,6 @@
});
suite('CHANGE* / DIFF*', () => {
- test('CHANGE_NUMBER_LEGACY', async () => {
- // CHANGE_NUMBER_LEGACY: /^\/(\d+)\/?/,
- await checkRedirect('/12345', '/c/12345');
- });
-
- test('CHANGE_LEGACY', async () => {
- // CHANGE_LEGACY: /^\/c\/(\d+)\/?(.*)$/,
- stubRestApi('getFromProjectLookup').resolves('project' as RepoName);
- await checkRedirect('/c/1234', '/c/project/+/1234/');
- await checkRedirect(
- '/c/1234/comment/6789',
- '/c/project/+/1234/comment/6789'
- );
- });
-
test('DIFF_LEGACY_LINENUM', async () => {
await checkRedirect(
'/c/1234/3..8/foo/bar@321',
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index 769b064..7e6e23b 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -7,7 +7,6 @@
import '../../shared/gr-dialog/gr-dialog';
import '../../shared/gr-icon/gr-icon';
import '../../../embed/diff/gr-diff/gr-diff';
-import '../../../embed/diff-old/gr-diff/gr-diff';
import {navigationToken} from '../../core/gr-navigation/gr-navigation';
import {
NumericChangeId,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
index d82c2d3..1f1ac5c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
@@ -6,7 +6,6 @@
import '../../shared/gr-comment-thread/gr-comment-thread';
import '../../checks/gr-diff-check-result';
import '../../../embed/diff/gr-diff/gr-diff';
-import '../../../embed/diff-old/gr-diff/gr-diff';
import {
anyLineTooLong,
getDiffLength,
@@ -46,8 +45,7 @@
IgnoreWhitespaceType,
WebLinkInfo,
} from '../../../types/diff';
-import {GrDiff as GrDiffNew} from '../../../embed/diff/gr-diff/gr-diff';
-import {GrDiff} from '../../../embed/diff-old/gr-diff/gr-diff';
+import {GrDiff} from '../../../embed/diff/gr-diff/gr-diff';
import {DiffViewMode, Side, CommentSide} from '../../../constants/constants';
import {FilesWebLinks} from '../gr-patch-range-select/gr-patch-range-select';
import {KnownExperimentId} from '../../../services/flags/flags';
@@ -139,9 +137,8 @@
*/
@customElement('gr-diff-host')
export class GrDiffHost extends LitElement {
- // TODO(newdiff-cleanup): Replace once newdiff migration is completed.
@query('#diff')
- diffElement?: GrDiff | GrDiffNew;
+ diffElement?: GrDiff;
@property({type: Number})
changeNum?: NumericChangeId;
@@ -560,8 +557,6 @@
// TODO: Find better names for these 3 clear/cancel methods. Ideally the
// <gr-diff-host> should not re-used at all for another diff rendering pass.
this.clear();
- this.cancel();
- this.clearDiffContent();
assertIsDefined(this.path, 'path');
assertIsDefined(this.changeNum, 'changeNum');
this.diff = undefined;
@@ -669,6 +664,7 @@
patchNum: this.patchRange!.patchNum,
fileRange: this.file!,
path: this.path!,
+ diffElement: this.diffElement!,
},
highlight
);
@@ -814,11 +810,6 @@
};
}
- /** Cancel any remaining diff builder rendering work. */
- cancel() {
- this.diffElement?.cancel();
- }
-
getCursorStops() {
assertIsDefined(this.diffElement);
return this.diffElement.getCursorStops();
@@ -863,10 +854,6 @@
this.blame = null;
}
- clearDiffContent() {
- this.diffElement?.clearDiffContent();
- }
-
toggleAllContext() {
assertIsDefined(this.diffElement);
this.diffElement.toggleAllContext();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.ts
index d56f63f..9dabe1d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.ts
@@ -59,7 +59,6 @@
CommentsModel,
commentsModelToken,
} from '../../../models/comments/comments-model';
-import {isNewDiff} from '../../../embed/diff/gr-diff/gr-diff-utils';
suite('gr-diff-host tests', () => {
let element: GrDiffHost;
@@ -154,25 +153,6 @@
);
});
- test('reload() cancels before network resolves', async () => {
- if (isNewDiff()) return;
- assertIsDefined(element.diffElement);
- const cancelStub = sinon.stub(element.diffElement, 'cancel');
-
- // Stub the network calls into requests that never resolve.
- sinon.stub(element, 'getDiff').callsFake(() => new Promise(() => {}));
- element.patchRange = createPatchRange();
- element.change = createChange();
- element.prefs = undefined;
-
- // Needs to be set to something first for it to cancel.
- element.diff = createDiff();
- await element.updateComplete;
-
- element.reload();
- assert.isTrue(cancelStub.called);
- });
-
test('prefetch getDiff', async () => {
getDiffRestApiStub.returns(Promise.resolve(createDiff()));
element.changeNum = 123 as NumericChangeId;
@@ -558,15 +538,6 @@
assert.isTrue(showAuthRequireSpy.called);
});
- test('delegates cancel()', () => {
- assertIsDefined(element.diffElement);
- const stub = sinon.stub(element.diffElement, 'cancel');
- element.patchRange = createPatchRange();
- element.cancel();
- assert.isTrue(stub.calledOnce);
- assert.equal(stub.lastCall.args.length, 0);
- });
-
test('delegates getCursorStops()', () => {
const returnValue = [document.createElement('b')];
assertIsDefined(element.diffElement);
@@ -666,14 +637,6 @@
});
});
- test('delegates clearDiffContent()', () => {
- assertIsDefined(element.diffElement);
- const stub = sinon.stub(element.diffElement, 'clearDiffContent');
- element.clearDiffContent();
- assert.isTrue(stub.calledOnce);
- assert.equal(stub.lastCall.args.length, 0);
- });
-
test('delegates toggleAllContext()', () => {
assertIsDefined(element.diffElement);
const stub = sinon.stub(element.diffElement, 'toggleAllContext');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 3a2efe8..246e7ed 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -56,8 +56,7 @@
FilesWebLinks,
PatchRangeChangeEvent,
} from '../gr-patch-range-select/gr-patch-range-select';
-import {GrDiffCursor as GrDiffCursorNew} from '../../../embed/diff/gr-diff-cursor/gr-diff-cursor';
-import {GrDiffCursor} from '../../../embed/diff-old/gr-diff-cursor/gr-diff-cursor';
+import {GrDiffCursor} from '../../../embed/diff/gr-diff-cursor/gr-diff-cursor';
import {CommentSide, DiffViewMode, Side} from '../../../constants/constants';
import {GrApplyFixDialog} from '../gr-apply-fix-dialog/gr-apply-fix-dialog';
import {OpenFixPreviewEvent, ValueChangedEvent} from '../../../types/events';
@@ -66,7 +65,7 @@
import {toggleClass, whenVisible} from '../../../utils/dom-util';
import {CursorMoveResult} from '../../../api/core';
import {throttleWrap} from '../../../utils/async-util';
-import {filter, take, switchMap} from 'rxjs/operators';
+import {filter, take, switchMap, map} from 'rxjs/operators';
import {combineLatest} from 'rxjs';
import {
Shortcut,
@@ -104,7 +103,6 @@
FileNameToNormalizedFileInfoMap,
filesModelToken,
} from '../../../models/change/files-model';
-import {isNewDiff} from '../../../embed/diff/gr-diff/gr-diff-utils';
import {isImageDiff} from '../../../utils/diff-util';
import {formStyles} from '../../../styles/form-styles';
@@ -258,9 +256,8 @@
private throttledToggleFileReviewed?: (e: KeyboardEvent) => void;
- // TODO(newdiff-cleanup): Replace once newdiff migration is completed.
@state()
- cursor?: GrDiffCursor | GrDiffCursorNew;
+ cursor?: GrDiffCursor;
private readonly shortcutsController = new ShortcutController(this);
@@ -697,11 +694,23 @@
this.handleToggleFileReviewed()
);
this.addEventListener('open-fix-preview', e => this.onOpenFixPreview(e));
- // TODO(newdiff-cleanup): Remove once newdiff migration is completed.
- this.cursor = isNewDiff() ? new GrDiffCursorNew() : new GrDiffCursor();
+ this.cursor = new GrDiffCursor();
if (this.diffHost) this.reInitCursor();
window.addEventListener('scroll', this.updateSidebarHeight);
window.addEventListener('resize', this.updateSidebarHeight);
+ this.getUserModel()
+ .preferences$.pipe(
+ map(p => p.diff_page_sidebar),
+ take(1)
+ )
+ .toPromise()
+ .then(initialSidebar => {
+ if (initialSidebar === 'NONE' || initialSidebar === undefined) {
+ this.shownSidebar = undefined;
+ } else {
+ this.shownSidebar = initialSidebar.substring('plugin-'.length);
+ }
+ });
}
override disconnectedCallback() {
@@ -914,9 +923,16 @@
<gr-endpoint-decorator name="sidebarTrigger">
<gr-endpoint-param
name="onTrigger"
- .value=${(pluginName: string) =>
- (this.shownSidebar =
- this.shownSidebar === pluginName ? undefined : pluginName)}
+ .value=${(pluginName: string) => {
+ this.shownSidebar =
+ this.shownSidebar === pluginName ? undefined : pluginName;
+ this.getUserModel().updatePreferences({
+ diff_page_sidebar:
+ this.shownSidebar === pluginName
+ ? 'NONE'
+ : `plugin-${pluginName}`,
+ });
+ }}
></gr-endpoint-param>
<!-- params cannot start falsy, so the value must be wrapped -->
<gr-endpoint-param
@@ -985,10 +1001,11 @@
// Only close the sidebar if that particular sidebar is
// still open. An async onClose callback should not close a
// different sidebar.
- this.shownSidebar =
- this.shownSidebar === pluginName
- ? undefined
- : this.shownSidebar;
+ if (this.shownSidebar !== pluginName) return;
+ this.shownSidebar = undefined;
+ this.getUserModel().updatePreferences({
+ diff_page_sidebar: 'NONE',
+ });
}}
>
</gr-endpoint-param>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
index 9c1c3ce..93424b1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
@@ -54,7 +54,6 @@
import {
changeModelToken,
ChangeModel,
- LoadingStatus,
} from '../../../models/change/change-model';
import {assertIsDefined} from '../../../utils/common-util';
import {GrDiffModeSelector} from '../gr-diff-mode-selector/gr-diff-mode-selector';
@@ -77,6 +76,7 @@
import {FileNameToNormalizedFileInfoMap} from '../../../models/change/files-model';
import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
import {GrDiffCursor} from '../../../embed/diff/gr-diff-cursor/gr-diff-cursor';
+import {LoadingStatus} from '../../../types/types';
function createComment(
id: string,
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
index 6a05d86..6bf1adc 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
@@ -18,6 +18,7 @@
convertToPatchSetNum,
getParentInfoString,
shorten,
+ getParentCommit,
} from '../../../utils/patch-set-util';
import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
import {
@@ -276,7 +277,8 @@
KnownExperimentId.REVISION_PARENTS_DATA
);
dropdownContent.push({
- text: isMerge ? 'Auto Merge' : 'Base',
+ triggerText: isMerge ? 'Auto Merge' : 'Base',
+ text: isMerge ? 'Auto Merge' : `Base | ${getParentCommit(rev, 0)}`,
bottomText:
showParentsData && !isMerge ? getParentInfoString(rev, 0) : undefined,
value: PARENT,
@@ -286,7 +288,7 @@
dropdownContent.push({
disabled: idx >= parentCount,
triggerText: `Parent ${idx + 1}`,
- text: `Parent ${idx + 1}`,
+ text: `Parent ${idx + 1} | ${getParentCommit(rev, idx)}`,
bottomText: showParentsData ? getParentInfoString(rev, idx) : undefined,
mobileText: `Parent ${idx + 1}`,
value: -(idx + 1),
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts
index 0f2dd1e..e7ed97b 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts
@@ -180,7 +180,8 @@
commentThreads: [],
} as DropdownItem,
{
- text: 'Base',
+ text: 'Base | ',
+ triggerText: 'Base',
value: PARENT,
bottomText: undefined,
} as DropdownItem,
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
index f37a3d9..dc3bf7b 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
@@ -479,6 +479,8 @@
this.showAlert(PUBLISHING_EDIT_MSG);
+ // restApiService has some quirks where it will still call .then() with
+ // undefined or Response status 429 when it hits an error.
this.restApiService
.executeChangeAction(
changeNum,
@@ -488,7 +490,14 @@
{notify: NotifyType.NONE},
handleError
)
- .then(() => {
+ .then(res => {
+ if (
+ res === undefined ||
+ (res instanceof Response && res.status === 429)
+ ) {
+ // In an error case we should not navigate and lose edits.
+ return;
+ }
assertIsDefined(this.change, 'change');
this.getChangeModel().navigateToChangeResetReload();
});
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index 26ecf30..6dfa972 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -66,7 +66,7 @@
import {subscribe} from './lit/subscription-controller';
import {createSearchUrl} from '../models/views/search';
import {createSettingsUrl} from '../models/views/settings';
-import {createDashboardUrl} from '../models/views/dashboard';
+import {DashboardType, createDashboardUrl} from '../models/views/dashboard';
import {userModelToken} from '../models/user/user-model';
import {modalStyles} from '../styles/gr-modal-styles';
import {AdminChildView, createAdminUrl} from '../models/views/admin';
@@ -174,7 +174,9 @@
this.showKeyboardShortcuts()
);
this.shortcuts.addAbstract(Shortcut.GO_TO_USER_DASHBOARD, () =>
- this.getNavigation().setUrl(createDashboardUrl({user: 'self'}))
+ this.getNavigation().setUrl(
+ createDashboardUrl({type: DashboardType.USER, user: 'self'})
+ )
);
this.shortcuts.addAbstract(Shortcut.GO_TO_OPENED_CHANGES, () =>
this.getNavigation().setUrl(createSearchUrl({statuses: ['open']}))
diff --git a/polygerrit-ui/app/elements/gr-app-global-var-init.ts b/polygerrit-ui/app/elements/gr-app-global-var-init.ts
index ec46d8d..45dc3b6 100644
--- a/polygerrit-ui/app/elements/gr-app-global-var-init.ts
+++ b/polygerrit-ui/app/elements/gr-app-global-var-init.ts
@@ -22,7 +22,7 @@
initClickReporter,
initInteractionReporter,
} from '../services/gr-reporting/gr-reporting_impl';
-import {Finalizable} from '../services/registry';
+import {Finalizable} from '../types/types';
export function initGlobalVariables(
appContext: AppContext & Finalizable,
diff --git a/polygerrit-ui/app/elements/gr-app.ts b/polygerrit-ui/app/elements/gr-app.ts
index 40869a9..2ea6983 100644
--- a/polygerrit-ui/app/elements/gr-app.ts
+++ b/polygerrit-ui/app/elements/gr-app.ts
@@ -24,7 +24,6 @@
import {initGerrit, initGlobalVariables} from './gr-app-global-var-init';
import './gr-app-element';
-import {Finalizable} from '../services/registry';
import {
DependencyError,
DependencyToken,
@@ -46,6 +45,7 @@
} from '../services/service-worker-installer';
import {pluginLoaderToken} from './shared/gr-js-api-interface/gr-plugin-loader';
import {getAppContext} from '../services/app-context';
+import {Finalizable} from '../types/types';
initGlobalVariables(createAppContext(), true);
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-model/gr-comment-model.ts b/polygerrit-ui/app/elements/shared/gr-comment-model/gr-comment-model.ts
index 46b43c9..9e112cb 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-model/gr-comment-model.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-model/gr-comment-model.ts
@@ -6,7 +6,7 @@
import {Observable} from 'rxjs';
import {filter} from 'rxjs/operators';
import {define} from '../../../models/dependency';
-import {Model} from '../../../models/model';
+import {Model} from '../../../models/base/model';
import {isDefined} from '../../../types/types';
import {select} from '../../../utils/observable-util';
import {Comment} from '../../../types/common';
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
index f04c233..0b03581 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
@@ -8,7 +8,6 @@
import '../gr-comment/gr-comment';
import '../gr-icon/gr-icon';
import '../../../embed/diff/gr-diff/gr-diff';
-import '../../../embed/diff-old/gr-diff/gr-diff';
import '../gr-copy-clipboard/gr-copy-clipboard';
import {css, html, nothing, LitElement, PropertyValues} from 'lit';
import {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index b16c761..1e91345 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -17,7 +17,6 @@
import '../gr-suggestion-diff-preview/gr-suggestion-diff-preview';
import {getAppContext} from '../../../services/app-context';
import {css, html, LitElement, nothing, PropertyValues} from 'lit';
-import {when} from 'lit/directives/when.js';
import {customElement, property, query, state} from 'lit/decorators.js';
import {provide, resolve} from '../../../models/dependency';
import {GrTextarea} from '../gr-textarea/gr-textarea';
@@ -76,6 +75,7 @@
} from '../gr-comment-model/gr-comment-model';
import {formStyles} from '../../../styles/form-styles';
import {Interaction} from '../../../constants/reporting';
+import {Suggestion} from '../../../api/suggestions';
// visible for testing
export const AUTO_SAVE_DEBOUNCE_DELAY_MS = 2000;
@@ -208,7 +208,7 @@
generateSuggestion = true;
@state()
- generatedReplacement?: string;
+ generatedSuggestion?: Suggestion;
@state()
generatedReplacementId?: string;
@@ -542,6 +542,14 @@
color: inherit;
margin-right: var(--spacing-s);
}
+ .info {
+ background-color: var(--info-background);
+ padding: var(--spacing-l) var(--spacing-xl);
+ }
+ .info gr-icon {
+ color: var(--selected-foreground);
+ margin-right: var(--spacing-xl);
+ }
`,
];
}
@@ -572,17 +580,7 @@
<gr-endpoint-slot name="above-actions"></gr-endpoint-slot>
${this.renderHumanActions()} ${this.renderRobotActions()}
</div>
- ${when(
- this.showGeneratedSuggestion() &&
- this.generateSuggestion &&
- this.generatedReplacement,
- () =>
- html`<gr-suggestion-diff-preview
- .showAddSuggestionButton=${true}
- .suggestion=${this.generatedReplacement}
- .uuid=${this.generatedReplacementId}
- ></gr-suggestion-diff-preview>`
- )}
+ ${this.renderGeneratedSuggestionPreview()}
</div>
</gr-endpoint-decorator>
${this.renderConfirmDialog()}
@@ -932,11 +930,34 @@
);
}
+ private renderGeneratedSuggestionPreview() {
+ if (
+ !this.showGeneratedSuggestion() ||
+ !this.generateSuggestion ||
+ !this.generatedSuggestion
+ )
+ return nothing;
+ // TODO(milutin): This is temporary warning, will be removed, once we are
+ // able to change range of a comment
+ if (this.generatedSuggestion.newRange) {
+ const range = this.generatedSuggestion.newRange;
+ return html`<div class="info">
+ <gr-icon icon="info" filled></gr-icon>
+ There is a suggestion in range (${range.start_line}, ${range.end_line})
+ </div>`;
+ }
+ return html`<gr-suggestion-diff-preview
+ .showAddSuggestionButton=${true}
+ .suggestion=${this.generatedSuggestion?.replacement}
+ .uuid=${this.generatedReplacementId}
+ ></gr-suggestion-diff-preview>`;
+ }
+
private renderGenerateSuggestEditButton() {
if (!this.showGeneratedSuggestion()) {
return nothing;
}
- const numberOfSuggestions = !this.generatedReplacement ? '' : ' (1)';
+ const numberOfSuggestions = !this.generatedSuggestion ? '' : ' (1)';
return html`
<div class="action">
<label>
@@ -947,7 +968,7 @@
@change=${() => {
this.generateSuggestion = !this.generateSuggestion;
if (!this.generateSuggestion) {
- this.generatedReplacement = undefined;
+ this.generatedSuggestion = undefined;
} else {
this.generateSuggestionTrigger$.next();
}
@@ -988,21 +1009,28 @@
this.reporting.reportInteraction(Interaction.GENERATE_SUGGESTION_REQUEST, {
uuid: this.generatedReplacementId,
});
- const suggestion = await suggestionsPlugins[0].provider.suggestCode({
- prompt: this.messageText,
- changeNumber: this.changeNum,
- patchsetNumber: this.comment?.patch_set,
- filePath: this.comment.path,
- range: this.comment.range,
- lineNumber: this.comment.line,
- });
+ const suggestionResponse = await suggestionsPlugins[0].provider.suggestCode(
+ {
+ prompt: this.messageText,
+ changeNumber: this.changeNum,
+ patchsetNumber: this.comment?.patch_set,
+ filePath: this.comment.path,
+ range: this.comment.range,
+ lineNumber: this.comment.line,
+ }
+ );
+ // TODO(milutin): The suggestionResponse can contain multiple suggestion
+ // options. We pick the first one for now. In future we shouldn't ignore
+ // other suggestions.
this.reporting.reportInteraction(Interaction.GENERATE_SUGGESTION_RESPONSE, {
uuid: this.generatedReplacementId,
- response: suggestion.responseCode,
+ response: suggestionResponse.responseCode,
+ numSuggestions: suggestionResponse.suggestions.length,
+ hasNewRange: suggestionResponse.suggestions?.[0]?.newRange !== undefined,
});
- const replacement = suggestion.suggestions?.[0]?.replacement;
- if (!replacement) return;
- this.generatedReplacement = replacement;
+ const suggestion = suggestionResponse.suggestions?.[0];
+ if (!suggestion) return;
+ this.generatedSuggestion = suggestion;
}
private renderRobotActions() {
@@ -1105,12 +1133,9 @@
changed.has('generatedReplacement')
) {
if (
- !this.flagsService.isEnabled(
- KnownExperimentId.DIFF_FOR_USER_SUGGESTED_EDIT
- ) ||
!this.changeNum ||
!this.comment ||
- (!hasUserSuggestion(this.comment) && !this.generatedReplacement)
+ (!hasUserSuggestion(this.comment) && !this.generatedSuggestion)
)
return;
(async () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts
index ac6fa57..c65a1fe 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts
@@ -42,7 +42,10 @@
import {resolve} from '../../../models/dependency';
import {configModelToken} from '../../../models/config/config-model';
import {createSearchUrl} from '../../../models/views/search';
-import {createDashboardUrl} from '../../../models/views/dashboard';
+import {
+ DashboardType,
+ createDashboardUrl,
+} from '../../../models/views/dashboard';
import {fire, fireReload} from '../../../utils/event-util';
import {userModelToken} from '../../../models/user/user-model';
@@ -105,6 +108,7 @@
.top,
.attention,
.status,
+ .displayName,
.voteable {
padding: var(--spacing-s) var(--spacing-l);
}
@@ -181,7 +185,8 @@
</div>
</div>
${this.renderAccountStatusPlugins()} ${this.renderAccountStatus()}
- ${this.renderLinks()} ${this.renderChangeRelatedInfoAndActions()}
+ ${this.renderDisplayName()} ${this.renderLinks()}
+ ${this.renderChangeRelatedInfoAndActions()}
`;
}
@@ -281,6 +286,16 @@
`;
}
+ private renderDisplayName() {
+ if (!this.account.display_name) return nothing;
+ return html`
+ <div class="displayName">
+ <span class="title">Display name:</span>
+ <span class="value">${this.account.display_name.trim()}</span>
+ </div>
+ `;
+ }
+
private renderNeedsAttention() {
if (!(this.isAttentionEnabled && this.hasUserAttention)) return nothing;
const lastUpdate = getLastUpdate(this.account, this.change);
@@ -372,9 +387,15 @@
computeOwnerDashboardLink() {
if (!this.account) return undefined;
if (this.account._account_id)
- return createDashboardUrl({user: `${this.account._account_id}`});
+ return createDashboardUrl({
+ type: DashboardType.USER,
+ user: `${this.account._account_id}`,
+ });
if (this.account.email)
- return createDashboardUrl({user: this.account.email});
+ return createDashboardUrl({
+ type: DashboardType.USER,
+ user: this.account.email,
+ });
return undefined;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents_test.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents_test.ts
index c3c48cb..281d295 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents_test.ts
@@ -35,6 +35,7 @@
const ACCOUNT: AccountDetailInfo = {
...createAccountDetailWithId(31),
email: 'kermit@gmail.com' as EmailAddress,
+ display_name: 'Just Kermit',
username: 'kermit',
name: 'Kermit The Frog',
status: ' I am a frog ',
@@ -76,6 +77,10 @@
<span class="title">About me:</span>
<span class="value">I am a frog</span>
</div>
+ <div class="displayName">
+ <span class="title">Display name:</span>
+ <span class="value">Just Kermit</span>
+ </div>
<div class="links">
<gr-icon icon="link" class="linkIcon"></gr-icon>
<a href="/q/owner:kermit@gmail.com">Changes</a>
@@ -111,6 +116,10 @@
<span class="title"> About me: </span>
<span class="value"> I am a frog </span>
</div>
+ <div class="displayName">
+ <span class="title">Display name:</span>
+ <span class="value">Just Kermit</span>
+ </div>
<div class="links">
<gr-icon class="linkIcon" icon="link"> </gr-icon>
<a href="/q/owner:kermit@gmail.com"> Changes </a>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts
index cd73727..6b9c684 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts
@@ -20,9 +20,8 @@
ShowRevisionActionsDetail,
} from './gr-js-api-types';
import {EventType, TargetElement} from '../../../api/plugin';
-import {ParsedChangeInfo} from '../../../types/types';
+import {Finalizable, ParsedChangeInfo} from '../../../types/types';
import {MenuLink} from '../../../api/admin';
-import {Finalizable} from '../../../services/registry';
import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
import {Provider} from '../../../models/dependency';
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts
index afa16b9..6c180d7 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts
@@ -11,9 +11,8 @@
ReviewInput,
RevisionInfo,
} from '../../../types/common';
-import {Finalizable} from '../../../services/registry';
import {EventType, TargetElement} from '../../../api/plugin';
-import {ParsedChangeInfo} from '../../../types/types';
+import {Finalizable, ParsedChangeInfo} from '../../../types/types';
import {MenuLink} from '../../../api/admin';
import {FileRange, PatchRange} from '../../../api/diff';
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts
index e5ead0b..b78af2a 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts
@@ -17,7 +17,6 @@
import {fireAlert} from '../../../utils/event-util';
import {JsApiService} from './gr-js-api-types';
import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
-import {Finalizable} from '../../../services/registry';
import {PluginsModel} from '../../../models/plugins/plugins-model';
import {Gerrit} from '../../../api/gerrit';
import {fontStyles} from '../../../styles/gr-font-styles';
@@ -30,6 +29,7 @@
import {GrJsApiInterface} from './gr-js-api-interface-element';
import {define} from '../../../models/dependency';
import {modalStyles} from '../../../styles/gr-modal-styles';
+import {Finalizable} from '../../../types/types';
enum PluginState {
/** State that indicates the plugin is pending to be loaded. */
diff --git a/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts b/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts
index 64be38b..69cdedd 100644
--- a/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts
+++ b/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts
@@ -25,7 +25,6 @@
import {FilePreview} from '../../diff/gr-apply-fix-dialog/gr-apply-fix-dialog';
import {userModelToken} from '../../../models/user/user-model';
import {createUserFixSuggestion} from '../../../utils/comment-util';
-import {KnownExperimentId} from '../../../services/flags/flags';
import {commentModelToken} from '../gr-comment-model/gr-comment-model';
import {fire} from '../../../utils/event-util';
import {Interaction, Timing} from '../../../constants/reporting';
@@ -91,8 +90,6 @@
private readonly getCommentModel = resolve(this, commentModelToken);
- private readonly flagsService = getAppContext().flagsService;
-
private readonly syntaxLayer = new GrSyntaxLayerWorker(
resolve(this, highlightServiceToken),
() => getAppContext().reportingService
@@ -155,14 +152,8 @@
override updated(changed: PropertyValues) {
if (changed.has('commentedText') || changed.has('comment')) {
- if (
- this.flagsService.isEnabled(
- KnownExperimentId.DIFF_FOR_USER_SUGGESTED_EDIT
- )
- ) {
- if (this.previewLoadedFor !== this.suggestion) {
- this.fetchFixPreview();
- }
+ if (this.previewLoadedFor !== this.suggestion) {
+ this.fetchFixPreview();
}
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview_test.ts b/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview_test.ts
index dd6d62d..86be868 100644
--- a/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview_test.ts
@@ -100,6 +100,15 @@
};
await element.updateComplete;
- assert.shadowDom.equal(element, /* HTML */ '<gr-diff></gr-diff>');
+ assert.shadowDom.equal(
+ element,
+ /* HTML */ `
+ <gr-diff
+ class="disable-context-control-buttons hide-line-length-indicator"
+ >
+ </gr-diff>
+ `,
+ {ignoreAttributes: ['style']}
+ );
});
});
diff --git a/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls-section.ts b/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls-section.ts
deleted file mode 100644
index e558295..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls-section.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../elements/shared/gr-button/gr-button';
-import {html, LitElement} from 'lit';
-import {property, state} from 'lit/decorators.js';
-import {DiffInfo, DiffViewMode, RenderPreferences} from '../../../api/diff';
-import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {diffClasses, isNewDiff} from '../../diff/gr-diff/gr-diff-utils';
-import {getShowConfig} from './gr-context-controls';
-import {ifDefined} from 'lit/directives/if-defined.js';
-import {when} from 'lit/directives/when.js';
-
-export class GrContextControlsSection extends LitElement {
- /** Should context controls be rendered for expanding above the section? */
- @property({type: Boolean}) showAbove = false;
-
- /** Should context controls be rendered for expanding below the section? */
- @property({type: Boolean}) showBelow = false;
-
- /** Must be of type GrDiffGroupType.CONTEXT_CONTROL. */
- @property({type: Object})
- group?: GrDiffGroup;
-
- @property({type: Object})
- diff?: DiffInfo;
-
- @property({type: Object})
- renderPrefs?: RenderPreferences;
-
- /**
- * Semantic DOM diff testing does not work with just table fragments, so when
- * running such tests the render() method has to wrap the DOM in a proper
- * <table> element.
- */
- @state()
- addTableWrapperForTesting = false;
-
- /**
- * The browser API for handling selection does not (yet) work for selection
- * across multiple shadow DOM elements. So we are rendering gr-diff components
- * into the light DOM instead of the shadow DOM by overriding this method,
- * which was the recommended workaround by the lit team.
- * See also https://github.com/WICG/webcomponents/issues/79.
- */
- override createRenderRoot() {
- return this;
- }
-
- private renderPaddingRow(whereClass: 'above' | 'below') {
- if (!this.showAbove && whereClass === 'above') return;
- if (!this.showBelow && whereClass === 'below') return;
- const modeClass = this.isSideBySide() ? 'side-by-side' : 'unified';
- const type = this.isSideBySide()
- ? GrDiffGroupType.CONTEXT_CONTROL
- : undefined;
- return html`
- <tr
- class=${diffClasses('contextBackground', modeClass, whereClass)}
- left-type=${ifDefined(type)}
- right-type=${ifDefined(type)}
- >
- <td class=${diffClasses('blame')} data-line-number="0"></td>
- <td class=${diffClasses('contextLineNum')}></td>
- ${when(
- this.isSideBySide(),
- () => html`
- <td class=${diffClasses('sign')}></td>
- <td class=${diffClasses()}></td>
- `
- )}
- <td class=${diffClasses('contextLineNum')}></td>
- ${when(
- this.isSideBySide(),
- () => html`<td class=${diffClasses('sign')}></td>`
- )}
- <td class=${diffClasses()}></td>
- </tr>
- `;
- }
-
- private isSideBySide() {
- return this.renderPrefs?.view_mode !== DiffViewMode.UNIFIED;
- }
-
- private createContextControlRow() {
- // Note that <td> table cells that have `display: none` don't count!
- const colspan = this.renderPrefs?.show_sign_col ? '5' : '3';
- const showConfig = getShowConfig(this.showAbove, this.showBelow);
- return html`
- <tr class=${diffClasses('dividerRow', `show-${showConfig}`)}>
- <td class=${diffClasses('blame')} data-line-number="0"></td>
- ${when(
- this.isSideBySide(),
- () => html`<td class=${diffClasses()}></td>`
- )}
- <td class=${diffClasses('dividerCell')} colspan=${colspan}>
- <gr-context-controls
- class=${diffClasses()}
- .diff=${this.diff}
- .renderPreferences=${this.renderPrefs}
- .group=${this.group}
- .showConfig=${showConfig}
- >
- </gr-context-controls>
- </td>
- </tr>
- `;
- }
-
- override render() {
- const rows = html`
- ${this.renderPaddingRow('above')} ${this.createContextControlRow()}
- ${this.renderPaddingRow('below')}
- `;
- if (this.addTableWrapperForTesting) {
- return html`<table>
- ${rows}
- </table>`;
- }
- return rows;
- }
-}
-
-if (!isNewDiff()) {
- customElements.define(
- 'gr-context-controls-section',
- GrContextControlsSection
- );
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-context-controls-section': LitElement;
- }
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls-section_test.ts b/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls-section_test.ts
deleted file mode 100644
index 6a557fc..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls-section_test.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup';
-import './gr-context-controls-section';
-import {GrContextControlsSection} from './gr-context-controls-section';
-import {fixture, html, assert} from '@open-wc/testing';
-
-suite('gr-context-controls-section test', () => {
- let element: GrContextControlsSection;
-
- setup(async () => {
- element = await fixture<GrContextControlsSection>(
- html`<gr-context-controls-section></gr-context-controls-section>`
- );
- element.addTableWrapperForTesting = true;
- await element.updateComplete;
- });
-
- test('render: normal with showAbove and showBelow', async () => {
- element.showAbove = true;
- element.showBelow = true;
- await element.updateComplete;
- assert.lightDom.equal(
- element,
- /* HTML */ `
- <table>
- <tbody>
- <tr
- class="above contextBackground gr-diff side-by-side"
- left-type="contextControl"
- right-type="contextControl"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="contextLineNum gr-diff"></td>
- <td class="gr-diff sign"></td>
- <td class="gr-diff"></td>
- <td class="contextLineNum gr-diff"></td>
- <td class="gr-diff sign"></td>
- <td class="gr-diff"></td>
- </tr>
- <tr class="dividerRow gr-diff show-both">
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="gr-diff"></td>
- <td class="dividerCell gr-diff" colspan="3">
- <gr-context-controls class="gr-diff" showconfig="both">
- </gr-context-controls>
- </td>
- </tr>
- <tr
- class="below contextBackground gr-diff side-by-side"
- left-type="contextControl"
- right-type="contextControl"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="contextLineNum gr-diff"></td>
- <td class="gr-diff sign"></td>
- <td class="gr-diff"></td>
- <td class="contextLineNum gr-diff"></td>
- <td class="gr-diff sign"></td>
- <td class="gr-diff"></td>
- </tr>
- </tbody>
- </table>
- `
- );
- });
-});
diff --git a/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls.ts b/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls.ts
deleted file mode 100644
index e4afd23..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls.ts
+++ /dev/null
@@ -1,537 +0,0 @@
-/**
- * @license
- * Copyright 2021 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '@polymer/paper-button/paper-button';
-import '@polymer/paper-card/paper-card';
-import '@polymer/paper-checkbox/paper-checkbox';
-import '@polymer/paper-dropdown-menu/paper-dropdown-menu';
-import '@polymer/paper-fab/paper-fab';
-import '@polymer/paper-icon-button/paper-icon-button';
-import '@polymer/paper-item/paper-item';
-import '@polymer/paper-listbox/paper-listbox';
-import '@polymer/paper-tooltip/paper-tooltip';
-import {of, EMPTY, Subject} from 'rxjs';
-import {switchMap, delay} from 'rxjs/operators';
-
-import '../../../elements/shared/gr-button/gr-button';
-import {pluralize} from '../../../utils/string-util';
-import {fire} from '../../../utils/event-util';
-import {DiffInfo} from '../../../types/diff';
-import {assertIsDefined} from '../../../utils/common-util';
-import {css, html, LitElement, TemplateResult} from 'lit';
-import {property} from 'lit/decorators.js';
-import {subscribe} from '../../../elements/lit/subscription-controller';
-
-import {
- ContextButtonType,
- DiffContextButtonHoveredDetail,
- RenderPreferences,
- SyntaxBlock,
-} from '../../../api/diff';
-
-import {GrDiffGroup, hideInContextControl} from '../gr-diff/gr-diff-group';
-import {isNewDiff} from '../../diff/gr-diff/gr-diff-utils';
-
-declare global {
- interface HTMLElementEventMap {
- 'diff-context-button-hovered': CustomEvent<DiffContextButtonHoveredDetail>;
- }
-}
-
-const PARTIAL_CONTEXT_AMOUNT = 10;
-
-/**
- * Traverses a hierarchical structure of syntax blocks and
- * finds the most local/nested block that can be associated line.
- * It finds the closest block that contains the whole line and
- * returns the whole path from the syntax layer (blocks) sent as parameter
- * to the most nested block - the complete path from the top to bottom layer of
- * a syntax tree. Example: [myNamespace, MyClass, myMethod1, aLocalFunctionInsideMethod1]
- *
- * @param lineNum line number for the targeted line.
- * @param blocks Blocks for a specific syntax level in the file (to allow recursive calls)
- */
-function findBlockTreePathForLine(
- lineNum: number,
- blocks?: SyntaxBlock[]
-): SyntaxBlock[] {
- const containingBlock = blocks?.find(
- ({range}) => range.start_line < lineNum && range.end_line > lineNum
- );
- if (!containingBlock) return [];
- const innerPathInChild = findBlockTreePathForLine(
- lineNum,
- containingBlock?.children
- );
- return [containingBlock].concat(innerPathInChild);
-}
-
-export type GrContextControlsShowConfig = 'above' | 'below' | 'both';
-
-export function getShowConfig(
- showAbove: boolean,
- showBelow: boolean
-): GrContextControlsShowConfig {
- if (showAbove && !showBelow) return 'above';
- if (!showAbove && showBelow) return 'below';
-
- // Note that !showAbove && !showBelow also intentionally returns 'both'.
- // This means the file is completely collapsed, which is unusual, but at least
- // happens in one test.
- return 'both';
-}
-
-export class GrContextControls extends LitElement {
- @property({type: Object}) renderPreferences?: RenderPreferences;
-
- @property({type: Object}) diff?: DiffInfo;
-
- @property({type: Object}) group?: GrDiffGroup;
-
- @property({type: String, reflect: true})
- showConfig: GrContextControlsShowConfig = 'both';
-
- private expandButtonsHover = new Subject<{
- eventType: 'enter' | 'leave';
- buttonType: ContextButtonType;
- linesToExpand: number;
- }>();
-
- static override get styles() {
- return [
- css`
- :host {
- display: flex;
- justify-content: center;
- flex-direction: column;
- position: relative;
- }
-
- :host([showConfig='above']) {
- justify-content: flex-end;
- margin-top: calc(-1px - var(--line-height-normal) - var(--spacing-s));
- margin-bottom: var(--gr-context-controls-margin-bottom);
- height: calc(var(--line-height-normal) + var(--spacing-s));
- .horizontalFlex {
- align-items: end;
- }
- }
-
- :host([showConfig='below']) {
- justify-content: flex-start;
- margin-top: 1px;
- margin-bottom: calc(
- 0px - var(--line-height-normal) - var(--spacing-s)
- );
- .horizontalFlex {
- align-items: start;
- }
- }
-
- :host([showConfig='both']) {
- margin-top: calc(0px - var(--line-height-normal) - var(--spacing-s));
- margin-bottom: calc(
- 0px - var(--line-height-normal) - var(--spacing-s)
- );
- height: calc(
- 2 * var(--line-height-normal) + 2 * var(--spacing-s) +
- var(--divider-height)
- );
- .horizontalFlex {
- align-items: center;
- }
- }
-
- .contextControlButton {
- background-color: var(--default-button-background-color);
- font: var(--context-control-button-font, inherit);
- }
-
- paper-button {
- text-transform: none;
- align-items: center;
- background-color: var(--background-color);
- font-family: inherit;
- margin: var(--margin, 0);
- min-width: var(--border, 0);
- color: var(--diff-context-control-color);
- border: solid var(--border-color);
- border-width: 1px;
- border-radius: var(--border-radius);
- padding: var(--spacing-s) var(--spacing-l);
- }
-
- paper-button:hover {
- /* same as defined in gr-button */
- background: rgba(0, 0, 0, 0.12);
- }
- paper-button:focus-visible {
- /* paper-button sets this to 0, thus preventing focus-based styling. */
- outline-width: 1px;
- }
-
- .aboveBelowButtons {
- display: flex;
- flex-direction: column;
- justify-content: center;
- margin-left: var(--spacing-m);
- position: relative;
- }
- .aboveBelowButtons:first-child {
- margin-left: 0;
- /* Places a default background layer behind the "all button" that can have opacity */
- background-color: var(--default-button-background-color);
- }
-
- .horizontalFlex {
- display: flex;
- justify-content: center;
- align-items: var(
- --gr-context-controls-horizontal-align-items,
- center
- );
- }
-
- .aboveButton {
- border-bottom-width: 0;
- border-bottom-right-radius: 0;
- border-bottom-left-radius: 0;
- padding: var(--spacing-xxs) var(--spacing-l);
- }
- .belowButton {
- border-top-width: 0;
- border-top-left-radius: 0;
- border-top-right-radius: 0;
- padding: var(--spacing-xxs) var(--spacing-l);
- margin-top: calc(var(--divider-height) + 2 * var(--spacing-xxs));
- }
- .belowButton:first-child {
- margin-top: 0;
- }
- .breadcrumbTooltip {
- white-space: nowrap;
- }
- `,
- ];
- }
-
- constructor() {
- super();
- this.setupButtonHoverHandler();
- }
-
- private showBoth() {
- return this.showConfig === 'both';
- }
-
- private showAbove() {
- return this.showBoth() || this.showConfig === 'above';
- }
-
- private showBelow() {
- return this.showBoth() || this.showConfig === 'below';
- }
-
- private setupButtonHoverHandler() {
- subscribe(
- this,
- () =>
- this.expandButtonsHover.pipe(
- switchMap(e => {
- if (e.eventType === 'leave') {
- // cancel any previous delay
- // for mouse enter
- return EMPTY;
- }
- return of(e).pipe(delay(500));
- })
- ),
- ({buttonType, linesToExpand}) => {
- fire(this, 'diff-context-button-hovered', {
- buttonType,
- linesToExpand,
- });
- }
- );
- }
-
- private numLines() {
- assertIsDefined(this.group);
- // In context groups, there is the same number of lines left and right
- const left = this.group.lineRange.left;
- // Both start and end inclusive, so we need to add 1.
- return left.end_line - left.start_line + 1;
- }
-
- private createExpandAllButtonContainer() {
- return html` <div class="gr-diff aboveBelowButtons fullExpansion">
- ${this.createContextButton(ContextButtonType.ALL, this.numLines())}
- </div>`;
- }
-
- /**
- * Creates a specific expansion button (e.g. +X common lines, +10, +Block).
- */
- private createContextButton(
- type: ContextButtonType,
- linesToExpand: number,
- tooltip?: TemplateResult
- ) {
- if (!this.group) return;
- let text = '';
- let groups: GrDiffGroup[] = []; // The groups that replace this one if tapped.
- let ariaLabel = '';
- let classes = 'contextControlButton showContext ';
-
- if (type === ContextButtonType.ALL) {
- text = `+${pluralize(linesToExpand, 'common line')}`;
- ariaLabel = `Show ${pluralize(linesToExpand, 'common line')}`;
- classes += this.showBoth()
- ? 'centeredButton'
- : this.showAbove()
- ? 'aboveButton'
- : 'belowButton';
- if (this.group?.hasSkipGroup()) {
- // Expanding content would require load of more data
- text += ' (too large)';
- }
- groups.push(...this.group.contextGroups);
- } else if (type === ContextButtonType.ABOVE) {
- groups = hideInContextControl(
- this.group.contextGroups,
- linesToExpand,
- this.numLines()
- );
- text = `+${linesToExpand}`;
- classes += 'aboveButton';
- ariaLabel = `Show ${pluralize(linesToExpand, 'line')} above`;
- } else if (type === ContextButtonType.BELOW) {
- groups = hideInContextControl(
- this.group.contextGroups,
- 0,
- this.numLines() - linesToExpand
- );
- text = `+${linesToExpand}`;
- classes += 'belowButton';
- ariaLabel = `Show ${pluralize(linesToExpand, 'line')} below`;
- } else if (type === ContextButtonType.BLOCK_ABOVE) {
- groups = hideInContextControl(
- this.group.contextGroups,
- linesToExpand,
- this.numLines()
- );
- text = '+Block';
- classes += 'aboveButton';
- ariaLabel = 'Show block above';
- } else if (type === ContextButtonType.BLOCK_BELOW) {
- groups = hideInContextControl(
- this.group.contextGroups,
- 0,
- this.numLines() - linesToExpand
- );
- text = '+Block';
- classes += 'belowButton';
- ariaLabel = 'Show block below';
- }
- const expandHandler = this.createExpansionHandler(
- linesToExpand,
- type,
- groups
- );
-
- const mouseHandler = (eventType: 'enter' | 'leave') => {
- this.expandButtonsHover.next({
- eventType,
- buttonType: type,
- linesToExpand,
- });
- };
-
- const button = html` <paper-button
- class=${classes}
- aria-label=${ariaLabel}
- @click=${expandHandler}
- @mouseenter=${() => mouseHandler('enter')}
- @mouseleave=${() => mouseHandler('leave')}
- >
- <span class="showContext">${text}</span>
- ${tooltip}
- </paper-button>`;
- return button;
- }
-
- private createExpansionHandler(
- linesToExpand: number,
- type: ContextButtonType,
- groups: GrDiffGroup[]
- ) {
- return (e: Event) => {
- assertIsDefined(this.group);
- e.stopPropagation();
- if (type === ContextButtonType.ALL && this.group?.hasSkipGroup()) {
- fire(this, 'content-load-needed', {
- lineRange: this.group.lineRange,
- });
- } else {
- fire(this, 'diff-context-expanded', {
- numLines: this.numLines(),
- buttonType: type,
- expandedLines: linesToExpand,
- });
- fire(this, 'diff-context-expanded-internal', {
- contextGroup: this.group,
- groups,
- numLines: this.numLines(),
- buttonType: type,
- expandedLines: linesToExpand,
- });
- }
- };
- }
-
- private showPartialLinks() {
- return this.numLines() > PARTIAL_CONTEXT_AMOUNT;
- }
-
- /**
- * Creates a container div with partial (+10) expansion buttons (above and/or below).
- */
- private createPartialExpansionButtons() {
- if (!this.showPartialLinks()) {
- return undefined;
- }
- let aboveButton;
- let belowButton;
- if (this.showAbove()) {
- aboveButton = this.createContextButton(
- ContextButtonType.ABOVE,
- PARTIAL_CONTEXT_AMOUNT
- );
- }
- if (this.showBelow()) {
- belowButton = this.createContextButton(
- ContextButtonType.BELOW,
- PARTIAL_CONTEXT_AMOUNT
- );
- }
- return aboveButton || belowButton
- ? html` <div class="aboveBelowButtons partialExpansion">
- ${aboveButton} ${belowButton}
- </div>`
- : undefined;
- }
-
- /**
- * Creates a container div with block expansion buttons (above and/or below).
- */
- private createBlockExpansionButtons() {
- assertIsDefined(this.group, 'group');
- if (
- !this.showPartialLinks() ||
- !this.renderPreferences?.use_block_expansion ||
- this.group?.hasSkipGroup()
- ) {
- return undefined;
- }
- let aboveBlockButton;
- let belowBlockButton;
- if (this.showAbove()) {
- aboveBlockButton = this.createBlockButton(
- ContextButtonType.BLOCK_ABOVE,
- this.numLines(),
- this.group.lineRange.right.start_line - 1
- );
- }
- if (this.showBelow()) {
- belowBlockButton = this.createBlockButton(
- ContextButtonType.BLOCK_BELOW,
- this.numLines(),
- this.group.lineRange.right.end_line + 1
- );
- }
- if (aboveBlockButton || belowBlockButton) {
- return html` <div class="aboveBelowButtons blockExpansion">
- ${aboveBlockButton} ${belowBlockButton}
- </div>`;
- }
- return undefined;
- }
-
- private createBlockButtonTooltip(
- buttonType: ContextButtonType,
- syntaxPath: SyntaxBlock[],
- linesToExpand: number
- ) {
- // Create breadcrumb string:
- // myNamespace > MyClass > myMethod1 > aLocalFunctionInsideMethod1 > (anonymous)
- const tooltipText = syntaxPath.length
- ? syntaxPath.map(b => b.name || '(anonymous)').join(' > ')
- : `${linesToExpand} common lines`;
-
- const position =
- buttonType === ContextButtonType.BLOCK_ABOVE ? 'top' : 'bottom';
- return html`<paper-tooltip offset="10" position=${position}
- ><div class="breadcrumbTooltip">${tooltipText}</div></paper-tooltip
- >`;
- }
-
- private createBlockButton(
- buttonType: ContextButtonType,
- numLines: number,
- referenceLine: number
- ) {
- if (!this.diff?.meta_b) return;
- const syntaxTree = this.diff.meta_b.syntax_tree;
- const outlineSyntaxPath = findBlockTreePathForLine(
- referenceLine,
- syntaxTree
- );
- let linesToExpand = numLines;
- if (outlineSyntaxPath.length) {
- const {range} = outlineSyntaxPath[outlineSyntaxPath.length - 1];
- const targetLine =
- buttonType === ContextButtonType.BLOCK_ABOVE
- ? range.end_line
- : range.start_line;
- const distanceToTargetLine = Math.abs(targetLine - referenceLine);
- if (distanceToTargetLine < numLines) {
- linesToExpand = distanceToTargetLine;
- }
- }
- const tooltip = this.createBlockButtonTooltip(
- buttonType,
- outlineSyntaxPath,
- linesToExpand
- );
- return this.createContextButton(buttonType, linesToExpand, tooltip);
- }
-
- private hasValidProperties() {
- return !!(this.diff && this.group?.contextGroups?.length);
- }
-
- override render() {
- if (!this.hasValidProperties()) {
- console.error('Invalid properties for gr-context-controls!');
- return html`<p>invalid properties</p>`;
- }
- return html`
- <div class="horizontalFlex">
- ${this.createExpandAllButtonContainer()}
- ${this.createPartialExpansionButtons()}
- ${this.createBlockExpansionButtons()}
- </div>
- `;
- }
-}
-if (!isNewDiff()) {
- customElements.define('gr-context-controls', GrContextControls);
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-context-controls': LitElement;
- }
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls_test.ts b/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls_test.ts
deleted file mode 100644
index 196afe5..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls_test.ts
+++ /dev/null
@@ -1,374 +0,0 @@
-/**
- * @license
- * Copyright 2021 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup';
-import '../gr-diff/gr-diff-group';
-import './gr-context-controls';
-import {GrContextControls} from './gr-context-controls';
-
-import {GrDiffLine} from '../gr-diff/gr-diff-line';
-import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {
- DiffFileMetaInfo,
- DiffInfo,
- GrDiffLineType,
- SyntaxBlock,
-} from '../../../api/diff';
-import {fixture, html, assert} from '@open-wc/testing';
-import {waitEventLoop} from '../../../test/test-utils';
-
-suite('gr-context-control tests', () => {
- let element: GrContextControls;
-
- setup(async () => {
- element = document.createElement(
- 'gr-context-controls'
- ) as GrContextControls;
- element.diff = {content: []} as any as DiffInfo;
- element.renderPreferences = {};
- const div = await fixture(html`<div></div>`);
- div.appendChild(element);
- await waitEventLoop();
- });
-
- function createContextGroup(options: {offset?: number; count?: number}) {
- const offset = options.offset || 0;
- const numLines = options.count || 10;
- const lines = [];
- for (let i = 0; i < numLines; i++) {
- const line = new GrDiffLine(GrDiffLineType.BOTH);
- line.beforeNumber = offset + i + 1;
- line.afterNumber = offset + i + 1;
- line.text = 'lorem upsum';
- lines.push(line);
- }
- return new GrDiffGroup({
- type: GrDiffGroupType.CONTEXT_CONTROL,
- contextGroups: [new GrDiffGroup({type: GrDiffGroupType.BOTH, lines})],
- });
- }
-
- test('no +10 buttons for 10 or less lines', async () => {
- element.group = createContextGroup({count: 10});
-
- await waitEventLoop();
-
- const buttons = element.shadowRoot!.querySelectorAll(
- 'paper-button.showContext'
- );
- assert.equal(buttons.length, 1);
- assert.equal(buttons[0].textContent!.trim(), '+10 common lines');
- });
-
- test('context control at the top', async () => {
- element.group = createContextGroup({offset: 0, count: 20});
- element.showConfig = 'below';
-
- await waitEventLoop();
-
- const buttons = element.shadowRoot!.querySelectorAll(
- 'paper-button.showContext'
- );
-
- assert.equal(buttons.length, 2);
- assert.equal(buttons[0].textContent!.trim(), '+20 common lines');
- assert.equal(buttons[1].textContent!.trim(), '+10');
-
- assert.include([...buttons[0].classList.values()], 'belowButton');
- assert.include([...buttons[1].classList.values()], 'belowButton');
- });
-
- test('context control in the middle', async () => {
- element.group = createContextGroup({offset: 10, count: 20});
- element.showConfig = 'both';
-
- await waitEventLoop();
-
- const buttons = element.shadowRoot!.querySelectorAll(
- 'paper-button.showContext'
- );
-
- assert.equal(buttons.length, 3);
- assert.equal(buttons[0].textContent!.trim(), '+20 common lines');
- assert.equal(buttons[1].textContent!.trim(), '+10');
- assert.equal(buttons[2].textContent!.trim(), '+10');
-
- assert.include([...buttons[0].classList.values()], 'centeredButton');
- assert.include([...buttons[1].classList.values()], 'aboveButton');
- assert.include([...buttons[2].classList.values()], 'belowButton');
- });
-
- test('context control at the bottom', async () => {
- element.group = createContextGroup({offset: 30, count: 20});
- element.showConfig = 'above';
-
- await waitEventLoop();
-
- const buttons = element.shadowRoot!.querySelectorAll(
- 'paper-button.showContext'
- );
-
- assert.equal(buttons.length, 2);
- assert.equal(buttons[0].textContent!.trim(), '+20 common lines');
- assert.equal(buttons[1].textContent!.trim(), '+10');
-
- assert.include([...buttons[0].classList.values()], 'aboveButton');
- assert.include([...buttons[1].classList.values()], 'aboveButton');
- });
-
- function prepareForBlockExpansion(syntaxTree: SyntaxBlock[]) {
- element.renderPreferences!.use_block_expansion = true;
- element.diff!.meta_b = {
- syntax_tree: syntaxTree,
- } as any as DiffFileMetaInfo;
- }
-
- test('context control with block expansion at the top', async () => {
- prepareForBlockExpansion([]);
- element.group = createContextGroup({offset: 0, count: 20});
- element.showConfig = 'below';
-
- await waitEventLoop();
-
- const fullExpansionButtons = element.shadowRoot!.querySelectorAll(
- '.fullExpansion paper-button'
- );
- const partialExpansionButtons = element.shadowRoot!.querySelectorAll(
- '.partialExpansion paper-button'
- );
- const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
- '.blockExpansion paper-button'
- );
- assert.equal(fullExpansionButtons.length, 1);
- assert.equal(partialExpansionButtons.length, 1);
- assert.equal(blockExpansionButtons.length, 1);
- assert.equal(
- blockExpansionButtons[0].querySelector('span')!.textContent!.trim(),
- '+Block'
- );
- assert.include(
- [...blockExpansionButtons[0].classList.values()],
- 'belowButton'
- );
- });
-
- test('context control with block expansion in the middle', async () => {
- prepareForBlockExpansion([]);
- element.group = createContextGroup({offset: 10, count: 20});
- element.showConfig = 'both';
-
- await waitEventLoop();
-
- const fullExpansionButtons = element.shadowRoot!.querySelectorAll(
- '.fullExpansion paper-button'
- );
- const partialExpansionButtons = element.shadowRoot!.querySelectorAll(
- '.partialExpansion paper-button'
- );
- const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
- '.blockExpansion paper-button'
- );
- assert.equal(fullExpansionButtons.length, 1);
- assert.equal(partialExpansionButtons.length, 2);
- assert.equal(blockExpansionButtons.length, 2);
- assert.equal(
- blockExpansionButtons[0].querySelector('span')!.textContent!.trim(),
- '+Block'
- );
- assert.equal(
- blockExpansionButtons[1].querySelector('span')!.textContent!.trim(),
- '+Block'
- );
- assert.include(
- [...blockExpansionButtons[0].classList.values()],
- 'aboveButton'
- );
- assert.include(
- [...blockExpansionButtons[1].classList.values()],
- 'belowButton'
- );
- });
-
- test('context control with block expansion at the bottom', async () => {
- prepareForBlockExpansion([]);
- element.group = createContextGroup({offset: 30, count: 20});
- element.showConfig = 'above';
-
- await waitEventLoop();
-
- const fullExpansionButtons = element.shadowRoot!.querySelectorAll(
- '.fullExpansion paper-button'
- );
- const partialExpansionButtons = element.shadowRoot!.querySelectorAll(
- '.partialExpansion paper-button'
- );
- const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
- '.blockExpansion paper-button'
- );
- assert.equal(fullExpansionButtons.length, 1);
- assert.equal(partialExpansionButtons.length, 1);
- assert.equal(blockExpansionButtons.length, 1);
- assert.equal(
- blockExpansionButtons[0].querySelector('span')!.textContent!.trim(),
- '+Block'
- );
- assert.include(
- [...blockExpansionButtons[0].classList.values()],
- 'aboveButton'
- );
- });
-
- test('+ Block tooltip tooltip shows syntax block containing the target lines above and below', async () => {
- prepareForBlockExpansion([
- {
- name: 'aSpecificFunction',
- range: {start_line: 1, start_column: 0, end_line: 25, end_column: 0},
- children: [],
- },
- {
- name: 'anotherFunction',
- range: {start_line: 26, start_column: 0, end_line: 50, end_column: 0},
- children: [],
- },
- ]);
- element.group = createContextGroup({offset: 10, count: 20});
- element.showConfig = 'both';
-
- await waitEventLoop();
-
- const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
- '.blockExpansion paper-button'
- );
- assert.equal(
- blockExpansionButtons[0]
- .querySelector('.breadcrumbTooltip')!
- .textContent?.trim(),
- 'aSpecificFunction'
- );
- assert.equal(
- blockExpansionButtons[1]
- .querySelector('.breadcrumbTooltip')!
- .textContent?.trim(),
- 'anotherFunction'
- );
- });
-
- test('+Block tooltip shows nested syntax blocks as breadcrumbs', async () => {
- prepareForBlockExpansion([
- {
- name: 'aSpecificNamespace',
- range: {start_line: 1, start_column: 0, end_line: 200, end_column: 0},
- children: [
- {
- name: 'MyClass',
- range: {
- start_line: 2,
- start_column: 0,
- end_line: 100,
- end_column: 0,
- },
- children: [
- {
- name: 'aMethod',
- range: {
- start_line: 5,
- start_column: 0,
- end_line: 80,
- end_column: 0,
- },
- children: [],
- },
- ],
- },
- ],
- },
- ]);
- element.group = createContextGroup({offset: 10, count: 20});
- element.showConfig = 'both';
-
- await waitEventLoop();
-
- const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
- '.blockExpansion paper-button'
- );
- assert.equal(
- blockExpansionButtons[0]
- .querySelector('.breadcrumbTooltip')!
- .textContent?.trim(),
- 'aSpecificNamespace > MyClass > aMethod'
- );
- });
-
- test('+Block tooltip shows (anonymous) for empty blocks', async () => {
- prepareForBlockExpansion([
- {
- name: 'aSpecificNamespace',
- range: {start_line: 1, start_column: 0, end_line: 200, end_column: 0},
- children: [
- {
- name: '',
- range: {
- start_line: 2,
- start_column: 0,
- end_line: 100,
- end_column: 0,
- },
- children: [
- {
- name: 'aMethod',
- range: {
- start_line: 5,
- start_column: 0,
- end_line: 80,
- end_column: 0,
- },
- children: [],
- },
- ],
- },
- ],
- },
- ]);
- element.group = createContextGroup({offset: 10, count: 20});
- element.showConfig = 'both';
- await waitEventLoop();
-
- const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
- '.blockExpansion paper-button'
- );
- assert.equal(
- blockExpansionButtons[0]
- .querySelector('.breadcrumbTooltip')!
- .textContent?.trim(),
- 'aSpecificNamespace > (anonymous) > aMethod'
- );
- });
-
- test('+Block tooltip shows "all common lines" for empty syntax tree', async () => {
- prepareForBlockExpansion([]);
-
- element.group = createContextGroup({offset: 10, count: 20});
- element.showConfig = 'both';
- await waitEventLoop();
-
- const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
- '.blockExpansion paper-button'
- );
- const tooltipAbove =
- blockExpansionButtons[0].querySelector('paper-tooltip')!;
- const tooltipBelow =
- blockExpansionButtons[1].querySelector('paper-tooltip')!;
- assert.equal(
- tooltipAbove.querySelector('.breadcrumbTooltip')!.textContent?.trim(),
- '20 common lines'
- );
- assert.equal(
- tooltipBelow.querySelector('.breadcrumbTooltip')!.textContent?.trim(),
- '20 common lines'
- );
- assert.equal(tooltipAbove.getAttribute('position'), 'top');
- assert.equal(tooltipBelow.getAttribute('position'), 'bottom');
- });
-});
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder-binary.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder-binary.ts
deleted file mode 100644
index 9467654..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder-binary.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * @license
- * Copyright 2017 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {GrDiffBuilder} from './gr-diff-builder';
-import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
-import {createElementDiff} from '../../diff/gr-diff/gr-diff-utils';
-import {GrDiffGroup} from '../gr-diff/gr-diff-group';
-import {html, render} from 'lit';
-import {FILE} from '../../../api/diff';
-
-export class GrDiffBuilderBinary extends GrDiffBuilder {
- constructor(
- diff: DiffInfo,
- prefs: DiffPreferencesInfo,
- outputEl: HTMLElement
- ) {
- super(diff, prefs, outputEl);
- }
-
- override buildSectionElement(group: GrDiffGroup): HTMLElement {
- const section = createElementDiff('tbody', 'binary-diff');
- // Do not create a diff row for LOST.
- if (group.lines[0].beforeNumber !== FILE) return section;
- return super.buildSectionElement(group);
- }
-
- public renderBinaryDiff() {
- render(
- html`
- <tbody class="gr-diff binary-diff">
- <tr class="gr-diff">
- <td colspan="5" class="gr-diff">
- <span>Difference in binary files</span>
- </td>
- </tr>
- </tbody>
- `,
- this.outputEl
- );
- }
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder-element.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder-element.ts
deleted file mode 100644
index 5a4a26d..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder-element.ts
+++ /dev/null
@@ -1,573 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../gr-diff-processor/gr-diff-processor';
-import '../../../elements/shared/gr-hovercard/gr-hovercard';
-import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
-import {
- GrDiffBuilder,
- DiffContextExpandedEventDetail,
- isImageDiffBuilder,
- isBinaryDiffBuilder,
-} from './gr-diff-builder';
-import {GrDiffBuilderImage} from './gr-diff-builder-image';
-import {GrDiffBuilderBinary} from './gr-diff-builder-binary';
-import {BlameInfo, ImageInfo} from '../../../types/common';
-import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
-import {CoverageRange, DiffLayer} from '../../../types/types';
-import {
- GrDiffProcessor,
- GroupConsumer,
- KeyLocations,
-} from '../gr-diff-processor/gr-diff-processor';
-import {
- CommentRangeLayer,
- GrRangedCommentLayer,
-} from '../../diff/gr-ranged-comment-layer/gr-ranged-comment-layer';
-import {GrCoverageLayer} from '../../diff/gr-coverage-layer/gr-coverage-layer';
-import {DiffViewMode, LineNumber, RenderPreferences} from '../../../api/diff';
-import {createDefaultDiffPrefs, Side} from '../../../constants/constants';
-import {GrDiffLine} from '../gr-diff/gr-diff-line';
-import {
- GrDiffGroup,
- GrDiffGroupType,
- hideInContextControl,
-} from '../gr-diff/gr-diff-group';
-import {getLineNumber, getSideByLineEl} from '../../diff/gr-diff/gr-diff-utils';
-import {fireAlert, fire} from '../../../utils/event-util';
-import {assertIsDefined} from '../../../utils/common-util';
-import {isImageDiff} from '../../../utils/diff-util';
-
-const TRAILING_WHITESPACE_PATTERN = /\s+$/;
-const COMMIT_MSG_PATH = '/COMMIT_MSG';
-const COMMIT_MSG_LINE_LENGTH = 72;
-
-declare global {
- interface HTMLElementEventMap {
- /**
- * Fired when the diff begins rendering - both for full renders and for
- * partial rerenders.
- */
- 'render-start': CustomEvent<{}>;
- /**
- * Fired when the diff finishes rendering text content - both for full
- * renders and for partial rerenders.
- */
- 'render-content': CustomEvent<{}>;
- }
-}
-
-export function getLineNumberCellWidth(prefs: DiffPreferencesInfo) {
- return prefs.font_size * 4;
-}
-
-function annotateSymbols(
- contentEl: HTMLElement,
- line: GrDiffLine,
- separator: string | RegExp,
- className: string
-) {
- const split = line.text.split(separator);
- if (!split || split.length < 2) {
- return;
- }
- for (let i = 0, pos = 0; i < split.length - 1; i++) {
- // Skip forward by the length of the content
- pos += split[i].length;
-
- GrAnnotation.annotateElement(contentEl, pos, 1, `gr-diff ${className}`);
-
- pos++;
- }
-}
-
-// TODO: Rename the class and the file and remove "element". This is not an
-// element anymore.
-export class GrDiffBuilderElement implements GroupConsumer {
- diff?: DiffInfo;
-
- diffElement?: HTMLTableElement;
-
- viewMode?: string;
-
- baseImage: ImageInfo | null = null;
-
- revisionImage: ImageInfo | null = null;
-
- path?: string;
-
- prefs: DiffPreferencesInfo = createDefaultDiffPrefs();
-
- renderPrefs?: RenderPreferences;
-
- useNewImageDiffUi = false;
-
- /**
- * Layers passed in from the outside.
- *
- * See `layersInternal` for where these layers will end up together with the
- * internal layers.
- */
- layers: DiffLayer[] = [];
-
- // visible for testing
- builder?: GrDiffBuilder;
-
- /**
- * All layers, both from the outside and the default ones. See `layers` for
- * the property that can be set from the outside.
- */
- // visible for testing
- layersInternal: DiffLayer[] = [];
-
- // visible for testing
- showTabs?: boolean;
-
- // visible for testing
- showTrailingWhitespace?: boolean;
-
- private coverageLayerLeft = new GrCoverageLayer(Side.LEFT);
-
- private coverageLayerRight = new GrCoverageLayer(Side.RIGHT);
-
- private rangeLayer?: GrRangedCommentLayer;
-
- // visible for testing
- processor?: GrDiffProcessor;
-
- /**
- * Groups are mostly just passed on to the diff builder (this.builder). But
- * we also keep track of them here for being able to fire a `render-content`
- * event when .element of each group has rendered.
- *
- * TODO: Refactor DiffBuilderElement and DiffBuilders with a cleaner
- * separation of responsibilities.
- */
- private groups: GrDiffGroup[] = [];
-
- updateCommentRanges(ranges: CommentRangeLayer[]) {
- this.rangeLayer?.updateRanges(ranges);
- }
-
- updateCoverageRanges(rs: CoverageRange[]) {
- this.coverageLayerLeft.setRanges(rs.filter(r => r?.side === Side.LEFT));
- this.coverageLayerRight.setRanges(rs.filter(r => r?.side === Side.RIGHT));
- }
-
- render(keyLocations: KeyLocations): Promise<void> {
- assertIsDefined(this.diff, 'diff');
- assertIsDefined(this.diffElement, 'diff table');
-
- // Setting up annotation layers must happen after plugins are
- // installed, and |render| satisfies the requirement, however,
- // |attached| doesn't because in the diff view page, the element is
- // attached before plugins are installed.
- this.setupAnnotationLayers();
-
- this.showTabs = this.prefs.show_tabs;
- this.showTrailingWhitespace = this.prefs.show_whitespace_errors;
-
- this.cleanup();
- this.builder = this.getDiffBuilder();
- this.init();
-
- // TODO: Just pass along the diff model here instead of setting many
- // individual properties.
- this.processor = new GrDiffProcessor();
- this.processor.consumer = this;
- this.processor.context = this.prefs.context;
- this.processor.keyLocations = keyLocations;
- if (this.renderPrefs?.num_lines_rendered_at_once) {
- this.processor.asyncThreshold =
- this.renderPrefs.num_lines_rendered_at_once;
- }
-
- this.clearDiffContent();
- this.builder.addColumns(
- this.diffElement,
- getLineNumberCellWidth(this.prefs)
- );
-
- const isBinary = !!(isImageDiff(this.diff) || this.diff.binary);
-
- fire(this.diffElement, 'render-start', {});
- return (
- this.processor
- .process(this.diff.content, isBinary)
- .then(async () => {
- if (isImageDiffBuilder(this.builder)) {
- this.builder.renderImageDiff();
- } else if (isBinaryDiffBuilder(this.builder)) {
- this.builder.renderBinaryDiff();
- }
- await this.untilGroupsRendered();
- fire(this.diffElement, 'render-content', {});
- })
- // Mocha testing does not like uncaught rejections, so we catch
- // the cancels which are expected and should not throw errors in
- // tests.
- .catch(e => {
- if (!e.isCanceled) return Promise.reject(e);
- return;
- })
- );
- }
-
- // visible for testing
- async untilGroupsRendered(groups: readonly GrDiffGroup[] = this.groups) {
- return Promise.all(groups.map(g => g.waitUntilRendered()));
- }
-
- private onDiffContextExpanded = (
- e: CustomEvent<DiffContextExpandedEventDetail>
- ) => {
- // Don't stop propagation. The host may listen for reporting or
- // resizing.
- this.replaceGroup(e.detail.contextGroup, e.detail.groups);
- };
-
- // visible for testing
- setupAnnotationLayers() {
- this.rangeLayer = new GrRangedCommentLayer();
-
- const layers: DiffLayer[] = [
- this.createTrailingWhitespaceLayer(),
- this.createIntralineLayer(),
- this.createTabIndicatorLayer(),
- this.createSpecialCharacterIndicatorLayer(),
- this.rangeLayer,
- this.coverageLayerLeft,
- this.coverageLayerRight,
- ];
-
- if (this.layers) {
- layers.push(...this.layers);
- }
- this.layersInternal = layers;
- }
-
- getContentTdByLine(lineNumber: LineNumber, side?: Side) {
- if (!this.builder) return undefined;
- return this.builder.getContentTdByLine(lineNumber, side);
- }
-
- getContentTdByLineEl(lineEl?: Element): Element | undefined {
- if (!lineEl) return undefined;
- const line = getLineNumber(lineEl);
- if (!line) return undefined;
- const side = getSideByLineEl(lineEl);
- return this.getContentTdByLine(line, side);
- }
-
- getLineElByNumber(lineNumber: LineNumber, side?: Side) {
- if (!this.builder) return undefined;
- return this.builder.getLineElByNumber(lineNumber, side);
- }
-
- getLineNumberRows() {
- if (!this.builder) return [];
- return this.builder.getLineNumberRows();
- }
-
- getLineNumEls(side: Side) {
- if (!this.builder) return [];
- return this.builder.getLineNumEls(side);
- }
-
- /**
- * When the line is hidden behind a context expander, expand it.
- *
- * @param lineNum A line number to expand. Using number here because other
- * special case line numbers are never hidden, so it does not make sense
- * to expand them.
- * @param side The side the line number refer to.
- */
- unhideLine(lineNum: number, side: Side) {
- if (!this.builder) return;
- const group = this.builder.findGroup(side, lineNum);
- // Cannot unhide a line that is not part of the diff.
- if (!group) return;
- // If it's already visible, great!
- if (group.type !== GrDiffGroupType.CONTEXT_CONTROL) return;
- const lineRange = group.lineRange[side];
- const lineOffset = lineNum - lineRange.start_line;
- const newGroups = [];
- const groups = hideInContextControl(
- group.contextGroups,
- 0,
- lineOffset - 1 - this.prefs.context
- );
- // If there is a context group, it will be the first group because we
- // start hiding from 0 offset
- if (groups[0].type === GrDiffGroupType.CONTEXT_CONTROL) {
- newGroups.push(groups.shift()!);
- }
- newGroups.push(
- ...hideInContextControl(
- groups,
- lineOffset + 1 + this.prefs.context,
- // Both ends inclusive, so difference is the offset of the last line.
- // But we need to pass the first line not to hide, which is the element
- // after.
- lineRange.end_line - lineRange.start_line + 1
- )
- );
- this.replaceGroup(group, newGroups);
- }
-
- /**
- * Replace the group of a context control section by rendering the provided
- * groups instead. This happens in response to expanding a context control
- * group.
- *
- * @param contextGroup The context control group to replace
- * @param newGroups The groups that are replacing the context control group
- */
- private replaceGroup(
- contextGroup: GrDiffGroup,
- newGroups: readonly GrDiffGroup[]
- ) {
- if (!this.builder) return;
- fire(this.diffElement, 'render-start', {});
- this.builder.replaceGroup(contextGroup, newGroups);
- this.groups = this.groups.filter(g => g !== contextGroup);
- this.groups.push(...newGroups);
- this.untilGroupsRendered(newGroups).then(() => {
- fire(this.diffElement, 'render-content', {});
- });
- }
-
- /**
- * This is meant to be called when the gr-diff component re-connects, or when
- * the diff is (re-)rendered.
- *
- * Make sure that this method is symmetric with cleanup(), which is called
- * when gr-diff disconnects.
- */
- init() {
- this.cleanup();
- this.diffElement?.addEventListener(
- 'diff-context-expanded-internal',
- this.onDiffContextExpanded
- );
- this.builder?.init();
- }
-
- /**
- * This is meant to be called when the gr-diff component disconnects, or when
- * the diff is (re-)rendered.
- *
- * Make sure that this method is symmetric with init(), which is called when
- * gr-diff re-connects.
- */
- cleanup() {
- this.processor?.cancel();
- this.builder?.cleanup();
- this.diffElement?.removeEventListener(
- 'diff-context-expanded-internal',
- this.onDiffContextExpanded
- );
- }
-
- // visible for testing
- handlePreferenceError(pref: string): never {
- const message =
- `The value of the '${pref}' user preference is ` +
- 'invalid. Fix in diff preferences';
- assertIsDefined(this.diffElement, 'diff table');
- fireAlert(this.diffElement, message);
- throw Error(`Invalid preference value: ${pref}`);
- }
-
- // visible for testing
- getDiffBuilder(): GrDiffBuilder {
- assertIsDefined(this.diff, 'diff');
- assertIsDefined(this.diffElement, 'diff table');
- if (isNaN(this.prefs.tab_size) || this.prefs.tab_size <= 0) {
- this.handlePreferenceError('tab size');
- }
-
- if (isNaN(this.prefs.line_length) || this.prefs.line_length <= 0) {
- this.handlePreferenceError('diff width');
- }
-
- const localPrefs = {...this.prefs};
- if (this.path === COMMIT_MSG_PATH) {
- // override line_length for commit msg the same way as
- // in gr-diff
- localPrefs.line_length = COMMIT_MSG_LINE_LENGTH;
- }
-
- let builder = null;
- if (isImageDiff(this.diff)) {
- builder = new GrDiffBuilderImage(
- this.diff,
- localPrefs,
- this.diffElement,
- this.baseImage,
- this.revisionImage,
- this.renderPrefs,
- this.useNewImageDiffUi
- );
- } else if (this.diff.binary) {
- return new GrDiffBuilderBinary(this.diff, localPrefs, this.diffElement);
- } else if (this.viewMode === DiffViewMode.SIDE_BY_SIDE) {
- this.renderPrefs = {
- ...this.renderPrefs,
- view_mode: DiffViewMode.SIDE_BY_SIDE,
- };
- builder = new GrDiffBuilder(
- this.diff,
- localPrefs,
- this.diffElement,
- this.layersInternal,
- this.renderPrefs
- );
- } else if (this.viewMode === DiffViewMode.UNIFIED) {
- this.renderPrefs = {
- ...this.renderPrefs,
- view_mode: DiffViewMode.UNIFIED,
- };
- builder = new GrDiffBuilder(
- this.diff,
- localPrefs,
- this.diffElement,
- this.layersInternal,
- this.renderPrefs
- );
- }
- if (!builder) {
- throw Error(`Unsupported diff view mode: ${this.viewMode}`);
- }
- return builder;
- }
-
- private clearDiffContent() {
- assertIsDefined(this.diffElement, 'diff table');
- this.diffElement.innerHTML = '';
- }
-
- /**
- * Called when the processor starts converting the diff information from the
- * server into chunks.
- */
- clearGroups() {
- if (!this.builder) return;
- this.groups = [];
- this.builder.clearGroups();
- }
-
- /**
- * Called when the processor is done converting a chunk of the diff.
- */
- addGroup(group: GrDiffGroup) {
- if (!this.builder) return;
- this.builder.addGroups([group]);
- this.groups.push(group);
- }
-
- // visible for testing
- createIntralineLayer(): DiffLayer {
- return {
- // Take a DIV.contentText element and a line object with intraline
- // differences to highlight and apply them to the element as
- // annotations.
- annotate(contentEl: HTMLElement, _: HTMLElement, line: GrDiffLine) {
- const HL_CLASS = 'gr-diff intraline';
- for (const highlight of line.highlights) {
- // The start and end indices could be the same if a highlight is
- // meant to start at the end of a line and continue onto the
- // next one. Ignore it.
- if (highlight.startIndex === highlight.endIndex) {
- continue;
- }
-
- // If endIndex isn't present, continue to the end of the line.
- const endIndex =
- highlight.endIndex === undefined
- ? GrAnnotation.getStringLength(line.text)
- : highlight.endIndex;
-
- GrAnnotation.annotateElement(
- contentEl,
- highlight.startIndex,
- endIndex - highlight.startIndex,
- HL_CLASS
- );
- }
- },
- };
- }
-
- // visible for testing
- createTabIndicatorLayer(): DiffLayer {
- const show = () => this.showTabs;
- return {
- annotate(contentEl: HTMLElement, _: HTMLElement, line: GrDiffLine) {
- // If visible tabs are disabled, do nothing.
- if (!show()) {
- return;
- }
-
- // Find and annotate the locations of tabs.
- annotateSymbols(contentEl, line, '\t', 'tab-indicator');
- },
- };
- }
-
- private createSpecialCharacterIndicatorLayer(): DiffLayer {
- return {
- annotate(contentEl: HTMLElement, _: HTMLElement, line: GrDiffLine) {
- // Find and annotate the locations of soft hyphen (\u00AD)
- annotateSymbols(contentEl, line, '\u00AD', 'special-char-indicator');
- // Find and annotate Stateful Unicode directional controls
- annotateSymbols(
- contentEl,
- line,
- /[\u202A-\u202E\u2066-\u2069]/,
- 'special-char-warning'
- );
- },
- };
- }
-
- // visible for testing
- createTrailingWhitespaceLayer(): DiffLayer {
- const show = () => this.showTrailingWhitespace;
-
- return {
- annotate(contentEl: HTMLElement, _: HTMLElement, line: GrDiffLine) {
- if (!show()) {
- return;
- }
-
- const match = line.text.match(TRAILING_WHITESPACE_PATTERN);
- if (match) {
- // Normalize string positions in case there is unicode before or
- // within the match.
- const index = GrAnnotation.getStringLength(
- line.text.substr(0, match.index)
- );
- const length = GrAnnotation.getStringLength(match[0]);
- GrAnnotation.annotateElement(
- contentEl,
- index,
- length,
- 'gr-diff trailing-whitespace'
- );
- }
- },
- };
- }
-
- setBlame(blame: BlameInfo[] | null) {
- if (!this.builder) return;
- this.builder.setBlame(blame ?? []);
- }
-
- updateRenderPrefs(renderPrefs: RenderPreferences) {
- this.builder?.updateRenderPrefs(renderPrefs);
- }
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder-element_test.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder-element_test.ts
deleted file mode 100644
index 2cee232..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder-element_test.ts
+++ /dev/null
@@ -1,630 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup';
-import {
- createConfig,
- createEmptyDiff,
-} from '../../../test/test-data-generators';
-import './gr-diff-builder-element';
-import {stubBaseUrl, waitUntil} from '../../../test/test-utils';
-import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
-import {GrDiffLine} from '../gr-diff/gr-diff-line';
-import {
- DiffContent,
- DiffLayer,
- DiffPreferencesInfo,
- DiffViewMode,
- GrDiffLineType,
- Side,
-} from '../../../api/diff';
-import {stubRestApi} from '../../../test/test-utils';
-import {waitForEventOnce} from '../../../utils/event-util';
-import {GrDiffBuilderElement} from './gr-diff-builder-element';
-import {createDefaultDiffPrefs} from '../../../constants/constants';
-import {KeyLocations} from '../gr-diff-processor/gr-diff-processor';
-import {fixture, html, assert} from '@open-wc/testing';
-import {GrDiffRow} from './gr-diff-row';
-import {GrDiffBuilder} from './gr-diff-builder';
-import {querySelectorAll} from '../../../utils/dom-util';
-
-const DEFAULT_PREFS = createDefaultDiffPrefs();
-
-suite('gr-diff-builder tests', () => {
- let element: GrDiffBuilderElement;
- let builder: GrDiffBuilder;
- let diffTable: HTMLTableElement;
-
- const setBuilderPrefs = (prefs: Partial<DiffPreferencesInfo>) => {
- builder = new GrDiffBuilder(
- createEmptyDiff(),
- {...createDefaultDiffPrefs(), ...prefs},
- diffTable
- );
- };
-
- const line = (text: string) => {
- const line = new GrDiffLine(GrDiffLineType.BOTH);
- line.text = text;
- return line;
- };
-
- setup(async () => {
- diffTable = await fixture(html`<table id="diffTable"></table>`);
- element = new GrDiffBuilderElement();
- element.diffElement = diffTable;
- stubRestApi('getLoggedIn').returns(Promise.resolve(false));
- stubRestApi('getProjectConfig').returns(Promise.resolve(createConfig()));
- stubBaseUrl('/r');
- setBuilderPrefs({});
- });
-
- [DiffViewMode.UNIFIED, DiffViewMode.SIDE_BY_SIDE].forEach(mode => {
- test(`line_length used for regular files under ${mode}`, () => {
- element.path = '/a.txt';
- element.viewMode = mode;
- element.diff = createEmptyDiff();
- element.prefs = {
- ...createDefaultDiffPrefs(),
- tab_size: 4,
- line_length: 50,
- };
- builder = element.getDiffBuilder();
- assert.equal(builder.prefs.line_length, 50);
- });
-
- test(`line_length ignored for commit msg under ${mode}`, () => {
- element.path = '/COMMIT_MSG';
- element.viewMode = mode;
- element.diff = createEmptyDiff();
- element.prefs = {
- ...createDefaultDiffPrefs(),
- tab_size: 4,
- line_length: 50,
- };
- builder = element.getDiffBuilder();
- assert.equal(builder.prefs.line_length, 72);
- });
- });
-
- test('_handlePreferenceError throws with invalid preference', () => {
- element.prefs = {...createDefaultDiffPrefs(), tab_size: 0};
- assert.throws(() => element.getDiffBuilder());
- });
-
- test('_handlePreferenceError triggers alert and javascript error', () => {
- const errorStub = sinon.stub();
- diffTable.addEventListener('show-alert', errorStub);
- assert.throws(() => element.handlePreferenceError('tab size'));
- assert.equal(
- errorStub.lastCall.args[0].detail.message,
- "The value of the 'tab size' user preference is invalid. " +
- 'Fix in diff preferences'
- );
- });
-
- suite('intraline differences', () => {
- let el: HTMLElement;
- let str: string;
- let annotateElementSpy: sinon.SinonSpy;
- let layer: DiffLayer;
- const lineNumberEl = document.createElement('td');
-
- function slice(str: string, start: number, end?: number) {
- return Array.from(str).slice(start, end).join('');
- }
-
- setup(async () => {
- el = await fixture(html`
- <div>Lorem ipsum dolor sit amet, suspendisse inceptos vehicula</div>
- `);
- str = el.textContent ?? '';
- annotateElementSpy = sinon.spy(GrAnnotation, 'annotateElement');
- layer = element.createIntralineLayer();
- });
-
- test('annotate no highlights', () => {
- layer.annotate(el, lineNumberEl, line(str), Side.LEFT);
-
- // The content is unchanged.
- assert.isFalse(annotateElementSpy.called);
- assert.equal(el.childNodes.length, 1);
- assert.instanceOf(el.childNodes[0], Text);
- assert.equal(str, el.childNodes[0].textContent);
- });
-
- test('annotate with highlights', () => {
- const l = line(str);
- l.highlights = [
- {contentIndex: 0, startIndex: 6, endIndex: 12},
- {contentIndex: 0, startIndex: 18, endIndex: 22},
- ];
- const str0 = slice(str, 0, 6);
- const str1 = slice(str, 6, 12);
- const str2 = slice(str, 12, 18);
- const str3 = slice(str, 18, 22);
- const str4 = slice(str, 22);
-
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
-
- assert.isTrue(annotateElementSpy.called);
- assert.equal(el.childNodes.length, 5);
-
- assert.instanceOf(el.childNodes[0], Text);
- assert.equal(el.childNodes[0].textContent, str0);
-
- assert.notInstanceOf(el.childNodes[1], Text);
- assert.equal(el.childNodes[1].textContent, str1);
-
- assert.instanceOf(el.childNodes[2], Text);
- assert.equal(el.childNodes[2].textContent, str2);
-
- assert.notInstanceOf(el.childNodes[3], Text);
- assert.equal(el.childNodes[3].textContent, str3);
-
- assert.instanceOf(el.childNodes[4], Text);
- assert.equal(el.childNodes[4].textContent, str4);
- });
-
- test('annotate without endIndex', () => {
- const l = line(str);
- l.highlights = [{contentIndex: 0, startIndex: 28}];
-
- const str0 = slice(str, 0, 28);
- const str1 = slice(str, 28);
-
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
-
- assert.isTrue(annotateElementSpy.called);
- assert.equal(el.childNodes.length, 2);
-
- assert.instanceOf(el.childNodes[0], Text);
- assert.equal(el.childNodes[0].textContent, str0);
-
- assert.notInstanceOf(el.childNodes[1], Text);
- assert.equal(el.childNodes[1].textContent, str1);
- });
-
- test('annotate ignores empty highlights', () => {
- const l = line(str);
- l.highlights = [{contentIndex: 0, startIndex: 28, endIndex: 28}];
-
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
-
- assert.isFalse(annotateElementSpy.called);
- assert.equal(el.childNodes.length, 1);
- });
-
- test('annotate handles unicode', () => {
- // Put some unicode into the string:
- str = str.replace(/\s/g, '💢');
- el.textContent = str;
- const l = line(str);
- l.highlights = [{contentIndex: 0, startIndex: 6, endIndex: 12}];
-
- const str0 = slice(str, 0, 6);
- const str1 = slice(str, 6, 12);
- const str2 = slice(str, 12);
-
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
-
- assert.isTrue(annotateElementSpy.called);
- assert.equal(el.childNodes.length, 3);
-
- assert.instanceOf(el.childNodes[0], Text);
- assert.equal(el.childNodes[0].textContent, str0);
-
- assert.notInstanceOf(el.childNodes[1], Text);
- assert.equal(el.childNodes[1].textContent, str1);
-
- assert.instanceOf(el.childNodes[2], Text);
- assert.equal(el.childNodes[2].textContent, str2);
- });
-
- test('annotate handles unicode w/o endIndex', () => {
- // Put some unicode into the string:
- str = str.replace(/\s/g, '💢');
- el.textContent = str;
-
- const l = line(str);
- l.highlights = [{contentIndex: 0, startIndex: 6}];
-
- const str0 = slice(str, 0, 6);
- const str1 = slice(str, 6);
- const numHighlightedChars = GrAnnotation.getStringLength(str1);
-
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
-
- assert.isTrue(annotateElementSpy.calledWith(el, 6, numHighlightedChars));
- assert.equal(el.childNodes.length, 2);
-
- assert.instanceOf(el.childNodes[0], Text);
- assert.equal(el.childNodes[0].textContent, str0);
-
- assert.notInstanceOf(el.childNodes[1], Text);
- assert.equal(el.childNodes[1].textContent, str1);
- });
- });
-
- suite('tab indicators', () => {
- let layer: DiffLayer;
- const lineNumberEl = document.createElement('td');
-
- setup(() => {
- element.showTabs = true;
- layer = element.createTabIndicatorLayer();
- });
-
- test('does nothing with empty line', () => {
- const l = line('');
- const el = document.createElement('div');
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
-
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
-
- assert.isFalse(annotateElementStub.called);
- });
-
- test('does nothing with no tabs', () => {
- const str = 'lorem ipsum no tabs';
- const l = line(str);
- const el = document.createElement('div');
- el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
-
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
-
- assert.isFalse(annotateElementStub.called);
- });
-
- test('annotates tab at beginning', () => {
- const str = '\tlorem upsum';
- const l = line(str);
- const el = document.createElement('div');
- el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
-
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
-
- assert.equal(annotateElementStub.callCount, 1);
- const args = annotateElementStub.getCalls()[0].args;
- assert.equal(args[0], el);
- assert.equal(args[1], 0, 'offset of tab indicator');
- assert.equal(args[2], 1, 'length of tab indicator');
- assert.include(args[3], 'tab-indicator');
- });
-
- test('does not annotate when disabled', () => {
- element.showTabs = false;
-
- const str = '\tlorem upsum';
- const l = line(str);
- const el = document.createElement('div');
- el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
-
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
-
- assert.isFalse(annotateElementStub.called);
- });
-
- test('annotates multiple in beginning', () => {
- const str = '\t\tlorem upsum';
- const l = line(str);
- const el = document.createElement('div');
- el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
-
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
-
- assert.equal(annotateElementStub.callCount, 2);
-
- let args = annotateElementStub.getCalls()[0].args;
- assert.equal(args[0], el);
- assert.equal(args[1], 0, 'offset of tab indicator');
- assert.equal(args[2], 1, 'length of tab indicator');
- assert.include(args[3], 'tab-indicator');
-
- args = annotateElementStub.getCalls()[1].args;
- assert.equal(args[0], el);
- assert.equal(args[1], 1, 'offset of tab indicator');
- assert.equal(args[2], 1, 'length of tab indicator');
- assert.include(args[3], 'tab-indicator');
- });
-
- test('annotates intermediate tabs', () => {
- const str = 'lorem\tupsum';
- const l = line(str);
- const el = document.createElement('div');
- el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
-
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
-
- assert.equal(annotateElementStub.callCount, 1);
- const args = annotateElementStub.getCalls()[0].args;
- assert.equal(args[0], el);
- assert.equal(args[1], 5, 'offset of tab indicator');
- assert.equal(args[2], 1, 'length of tab indicator');
- assert.include(args[3], 'tab-indicator');
- });
- });
-
- suite('layers', () => {
- let initialLayersCount = 0;
- let withLayerCount = 0;
- setup(() => {
- const layers: DiffLayer[] = [];
- element.layers = layers;
- element.showTrailingWhitespace = true;
- element.setupAnnotationLayers();
- initialLayersCount = element.layersInternal.length;
- });
-
- test('no layers', () => {
- element.setupAnnotationLayers();
- assert.equal(element.layersInternal.length, initialLayersCount);
- });
-
- suite('with layers', () => {
- const layers: DiffLayer[] = [{annotate: () => {}}, {annotate: () => {}}];
- setup(() => {
- element.layers = layers;
- element.showTrailingWhitespace = true;
- element.setupAnnotationLayers();
- withLayerCount = element.layersInternal.length;
- });
- test('with layers', () => {
- element.setupAnnotationLayers();
- assert.equal(element.layersInternal.length, withLayerCount);
- assert.equal(initialLayersCount + layers.length, withLayerCount);
- });
- });
- });
-
- suite('trailing whitespace', () => {
- let layer: DiffLayer;
- const lineNumberEl = document.createElement('td');
-
- setup(() => {
- element.showTrailingWhitespace = true;
- layer = element.createTrailingWhitespaceLayer();
- });
-
- test('does nothing with empty line', () => {
- const l = line('');
- const el = document.createElement('div');
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
- assert.isFalse(annotateElementStub.called);
- });
-
- test('does nothing with no trailing whitespace', () => {
- const str = 'lorem ipsum blah blah';
- const l = line(str);
- const el = document.createElement('div');
- el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
- assert.isFalse(annotateElementStub.called);
- });
-
- test('annotates trailing spaces', () => {
- const str = 'lorem ipsum ';
- const l = line(str);
- const el = document.createElement('div');
- el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
- assert.isTrue(annotateElementStub.called);
- assert.equal(annotateElementStub.lastCall.args[1], 11);
- assert.equal(annotateElementStub.lastCall.args[2], 3);
- });
-
- test('annotates trailing tabs', () => {
- const str = 'lorem ipsum\t\t\t';
- const l = line(str);
- const el = document.createElement('div');
- el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
- assert.isTrue(annotateElementStub.called);
- assert.equal(annotateElementStub.lastCall.args[1], 11);
- assert.equal(annotateElementStub.lastCall.args[2], 3);
- });
-
- test('annotates mixed trailing whitespace', () => {
- const str = 'lorem ipsum\t \t';
- const l = line(str);
- const el = document.createElement('div');
- el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
- assert.isTrue(annotateElementStub.called);
- assert.equal(annotateElementStub.lastCall.args[1], 11);
- assert.equal(annotateElementStub.lastCall.args[2], 3);
- });
-
- test('unicode preceding trailing whitespace', () => {
- const str = '💢\t';
- const l = line(str);
- const el = document.createElement('div');
- el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
- assert.isTrue(annotateElementStub.called);
- assert.equal(annotateElementStub.lastCall.args[1], 1);
- assert.equal(annotateElementStub.lastCall.args[2], 1);
- });
-
- test('does not annotate when disabled', () => {
- element.showTrailingWhitespace = false;
- const str = 'lorem upsum\t \t ';
- const l = line(str);
- const el = document.createElement('div');
- el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
- layer.annotate(el, lineNumberEl, l, Side.LEFT);
- assert.isFalse(annotateElementStub.called);
- });
- });
-
- suite('rendering text, images and binary files', () => {
- let keyLocations: KeyLocations;
- let content: DiffContent[] = [];
-
- setup(() => {
- element.viewMode = 'SIDE_BY_SIDE';
- keyLocations = {left: {}, right: {}};
- element.prefs = {
- ...DEFAULT_PREFS,
- context: -1,
- syntax_highlighting: true,
- };
- content = [
- {
- a: ['all work and no play make andybons a dull boy'],
- b: ['elgoog elgoog elgoog'],
- },
- {
- ab: [
- 'Non eram nescius, Brute, cum, quae summis ingeniis ',
- 'exquisitaque doctrina philosophi Graeco sermone tractavissent',
- ],
- },
- ];
- });
-
- test('text', async () => {
- element.diff = {...createEmptyDiff(), content};
- element.render(keyLocations);
- await waitForEventOnce(diffTable, 'render-content');
- assert.equal(querySelectorAll(diffTable, 'tbody')?.length, 4);
- });
-
- test('image', async () => {
- const diff = {...createEmptyDiff(), content, binary: true};
- diff.meta_a!.content_type = 'image/png';
- diff.meta_b!.content_type = 'image/png';
- element.diff = diff;
- element.render(keyLocations);
- await waitForEventOnce(diffTable, 'render-content');
- assert.equal(querySelectorAll(diffTable, 'tbody')?.length, 4);
- });
-
- test('binary', async () => {
- element.diff = {...createEmptyDiff(), content, binary: true};
- element.render(keyLocations);
- await waitForEventOnce(diffTable, 'render-content');
- assert.equal(querySelectorAll(diffTable, 'tbody')?.length, 3);
- });
- });
-
- suite('context hiding and expanding', () => {
- let dispatchStub: sinon.SinonStub;
-
- setup(async () => {
- dispatchStub = sinon.stub(diffTable, 'dispatchEvent');
- element.diff = {
- ...createEmptyDiff(),
- content: [
- {ab: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(i => `unchanged ${i}`)},
- {a: ['before'], b: ['after']},
- {ab: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(i => `unchanged ${10 + i}`)},
- ],
- };
- element.viewMode = DiffViewMode.SIDE_BY_SIDE;
-
- const keyLocations: KeyLocations = {left: {}, right: {}};
- element.prefs = {
- ...DEFAULT_PREFS,
- context: 1,
- };
- element.render(keyLocations);
- // Make sure all listeners are installed.
- await element.untilGroupsRendered();
- });
-
- test('hides lines behind two context controls', () => {
- const contextControls = diffTable.querySelectorAll('gr-context-controls');
- assert.equal(contextControls.length, 2);
-
- const diffRows = diffTable.querySelectorAll('.diff-row');
- // The first two are LOST and FILE line
- assert.equal(diffRows.length, 2 + 1 + 1 + 1);
- assert.include(diffRows[2].textContent, 'unchanged 10');
- assert.include(diffRows[3].textContent, 'before');
- assert.include(diffRows[3].textContent, 'after');
- assert.include(diffRows[4].textContent, 'unchanged 11');
- });
-
- test('clicking +x common lines expands those lines', async () => {
- const contextControls = diffTable.querySelectorAll('gr-context-controls');
- const topExpandCommonButton =
- contextControls[0].shadowRoot?.querySelectorAll<HTMLElement>(
- '.showContext'
- )[0];
- assert.isOk(topExpandCommonButton);
- assert.include(topExpandCommonButton!.textContent, '+9 common lines');
- let diffRows = diffTable.querySelectorAll('.diff-row');
- // 5 lines:
- // FILE, LOST, the changed line plus one line of context in each direction
- assert.equal(diffRows.length, 5);
-
- topExpandCommonButton!.click();
-
- await waitUntil(() => {
- diffRows = diffTable.querySelectorAll<GrDiffRow>('.diff-row');
- return diffRows.length === 14;
- });
- // 14 lines: The 5 above plus the 9 unchanged lines that were expanded
- assert.equal(diffRows.length, 14);
- assert.include(diffRows[2].textContent, 'unchanged 1');
- assert.include(diffRows[3].textContent, 'unchanged 2');
- assert.include(diffRows[4].textContent, 'unchanged 3');
- assert.include(diffRows[5].textContent, 'unchanged 4');
- assert.include(diffRows[6].textContent, 'unchanged 5');
- assert.include(diffRows[7].textContent, 'unchanged 6');
- assert.include(diffRows[8].textContent, 'unchanged 7');
- assert.include(diffRows[9].textContent, 'unchanged 8');
- assert.include(diffRows[10].textContent, 'unchanged 9');
- assert.include(diffRows[11].textContent, 'unchanged 10');
- assert.include(diffRows[12].textContent, 'before');
- assert.include(diffRows[12].textContent, 'after');
- assert.include(diffRows[13].textContent, 'unchanged 11');
- });
-
- test('unhideLine shows the line with context', async () => {
- dispatchStub.reset();
- element.unhideLine(4, Side.LEFT);
-
- await waitUntil(() => {
- const rows = diffTable.querySelectorAll<GrDiffRow>('.diff-row');
- return rows.length === 2 + 5 + 1 + 1 + 1;
- });
-
- const diffRows = diffTable.querySelectorAll('.diff-row');
- // The first two are LOST and FILE line
- // Lines 3-5 (Line 4 plus 1 context in each direction) will be expanded
- // Because context expanders do not hide <3 lines, lines 1-2 will also
- // be shown.
- // Lines 6-9 continue to be hidden
- assert.equal(diffRows.length, 2 + 5 + 1 + 1 + 1);
- assert.include(diffRows[2].textContent, 'unchanged 1');
- assert.include(diffRows[3].textContent, 'unchanged 2');
- assert.include(diffRows[4].textContent, 'unchanged 3');
- assert.include(diffRows[5].textContent, 'unchanged 4');
- assert.include(diffRows[6].textContent, 'unchanged 5');
- assert.include(diffRows[7].textContent, 'unchanged 10');
- assert.include(diffRows[8].textContent, 'before');
- assert.include(diffRows[8].textContent, 'after');
- assert.include(diffRows[9].textContent, 'unchanged 11');
-
- await element.untilGroupsRendered();
- const firedEventTypes = dispatchStub.getCalls().map(c => c.args[0].type);
- assert.include(firedEventTypes, 'render-content');
- });
- });
-});
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder-image.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder-image.ts
deleted file mode 100644
index d71d52a..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder-image.ts
+++ /dev/null
@@ -1,279 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {ImageInfo} from '../../../types/common';
-import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
-import {FILE, RenderPreferences, Side} from '../../../api/diff';
-import '../../diff/gr-diff-image-viewer/gr-image-viewer';
-import {html, LitElement, nothing} from 'lit';
-import {property, query, state} from 'lit/decorators.js';
-import {GrDiffBuilder} from './gr-diff-builder';
-import {createElementDiff, isNewDiff} from '../../diff/gr-diff/gr-diff-utils';
-import {GrDiffGroup} from '../gr-diff/gr-diff-group';
-
-// MIME types for images we allow showing. Do not include SVG, it can contain
-// arbitrary JavaScript.
-const IMAGE_MIME_PATTERN = /^image\/(bmp|gif|x-icon|jpeg|jpg|png|tiff|webp)$/;
-
-export class GrDiffBuilderImage extends GrDiffBuilder {
- constructor(
- diff: DiffInfo,
- prefs: DiffPreferencesInfo,
- outputEl: HTMLElement,
- private readonly baseImage: ImageInfo | null,
- private readonly revisionImage: ImageInfo | null,
- renderPrefs?: RenderPreferences,
- private readonly useNewImageDiffUi: boolean = false
- ) {
- super(diff, prefs, outputEl, [], renderPrefs);
- }
-
- override buildSectionElement(group: GrDiffGroup): HTMLElement {
- const section = createElementDiff('tbody');
- // Do not create a diff row for LOST.
- if (group.lines[0].beforeNumber !== FILE) return section;
- return super.buildSectionElement(group);
- }
-
- public renderImageDiff() {
- const imageDiff = this.useNewImageDiffUi
- ? this.createImageDiffNew()
- : this.createImageDiffOld();
- this.outputEl.appendChild(imageDiff);
- }
-
- private createImageDiffNew() {
- const imageDiff = document.createElement(
- 'gr-diff-image-new'
- ) as GrDiffImageNew;
- imageDiff.automaticBlink = this.autoBlink();
- imageDiff.baseImage = this.baseImage ?? undefined;
- imageDiff.revisionImage = this.revisionImage ?? undefined;
- return imageDiff;
- }
-
- private createImageDiffOld() {
- const imageDiff = document.createElement(
- 'gr-diff-image-old'
- ) as GrDiffImageOld;
- imageDiff.baseImage = this.baseImage ?? undefined;
- imageDiff.revisionImage = this.revisionImage ?? undefined;
- return imageDiff;
- }
-
- private autoBlink(): boolean {
- return !!this.renderPrefs?.image_diff_prefs?.automatic_blink;
- }
-
- override updateRenderPrefs(renderPrefs: RenderPreferences) {
- this.renderPrefs = renderPrefs;
-
- // We have to update `imageDiff.automaticBlink` manually, because `this` is
- // not a LitElement.
- const imageDiff = this.outputEl.querySelector(
- 'gr-diff-image-new'
- ) as GrDiffImageNew;
- if (imageDiff) imageDiff.automaticBlink = this.autoBlink();
- }
-}
-
-class GrDiffImageNew extends LitElement {
- @property() baseImage?: ImageInfo;
-
- @property() revisionImage?: ImageInfo;
-
- @property() automaticBlink = false;
-
- /**
- * The browser API for handling selection does not (yet) work for selection
- * across multiple shadow DOM elements. So we are rendering gr-diff components
- * into the light DOM instead of the shadow DOM by overriding this method,
- * which was the recommended workaround by the lit team.
- * See also https://github.com/WICG/webcomponents/issues/79.
- */
- override createRenderRoot() {
- return this;
- }
-
- override render() {
- return html`
- <tbody class="gr-diff image-diff">
- <tr class="gr-diff">
- <td class="gr-diff" colspan="4">
- <gr-image-viewer
- class="gr-diff"
- .baseUrl=${imageSrc(this.baseImage)}
- .revisionUrl=${imageSrc(this.revisionImage)}
- .automaticBlink=${this.automaticBlink}
- >
- </gr-image-viewer>
- </td>
- </tr>
- </tbody>
- `;
- }
-}
-
-class GrDiffImageOld extends LitElement {
- @property() baseImage?: ImageInfo;
-
- @property() revisionImage?: ImageInfo;
-
- @query('img.left') baseImageEl?: HTMLImageElement;
-
- @query('img.right') revisionImageEl?: HTMLImageElement;
-
- @state() baseError?: string;
-
- @state() revisionError?: string;
-
- /**
- * The browser API for handling selection does not (yet) work for selection
- * across multiple shadow DOM elements. So we are rendering gr-diff components
- * into the light DOM instead of the shadow DOM by overriding this method,
- * which was the recommended workaround by the lit team.
- * See also https://github.com/WICG/webcomponents/issues/79.
- */
- override createRenderRoot() {
- return this;
- }
-
- override render() {
- return html`
- <tbody class="gr-diff image-diff">
- ${this.renderImagePairRow()} ${this.renderImageLabelRow()}
- </tbody>
- ${this.renderEndpoint()}
- `;
- }
-
- private renderEndpoint() {
- return html`
- <tbody class="gr-diff endpoint">
- <tr class="gr-diff">
- <td class="gr-diff" colspan="4">
- <gr-endpoint-decorator class="gr-diff" name="image-diff">
- ${this.renderEndpointParam('baseImage', this.baseImage)}
- ${this.renderEndpointParam('revisionImage', this.revisionImage)}
- </gr-endpoint-decorator>
- </td>
- </tr>
- </tbody>
- `;
- }
-
- private renderEndpointParam(name: string, value: unknown) {
- if (!value) return nothing;
- return html`
- <gr-endpoint-param class="gr-diff" name=${name} .value=${value}>
- </gr-endpoint-param>
- `;
- }
-
- private renderImagePairRow() {
- return html`
- <tr class="gr-diff">
- <td class="gr-diff left lineNum blank"></td>
- <td class="gr-diff left">${this.renderImage(Side.LEFT)}</td>
- <td class="gr-diff right lineNum blank"></td>
- <td class="gr-diff right">${this.renderImage(Side.RIGHT)}</td>
- </tr>
- `;
- }
-
- private renderImage(side: Side) {
- const image = side === Side.LEFT ? this.baseImage : this.revisionImage;
- if (!image) return nothing;
- const error = side === Side.LEFT ? this.baseError : this.revisionError;
- if (error) return error;
- const src = imageSrc(image);
- if (!src) return nothing;
-
- return html`
- <img
- class="gr-diff ${side}"
- src=${src}
- @load=${this.handleLoad}
- @error=${(e: Event) => this.handleError(e, side)}
- >
- </img>
- `;
- }
-
- private handleLoad() {
- this.requestUpdate();
- }
-
- private handleError(e: Event, side: Side) {
- const msg = `[Image failed to load] ${e.type}`;
- if (side === Side.LEFT) this.baseError = msg;
- if (side === Side.RIGHT) this.revisionError = msg;
- }
-
- private renderImageLabelRow() {
- return html`
- <tr class="gr-diff">
- <td class="gr-diff left lineNum blank"></td>
- <td class="gr-diff left">
- <label class="gr-diff">
- ${this.renderName(this.baseImage?._name ?? '')}
- <span class="gr-diff label">${this.imageLabel(Side.LEFT)}</span>
- </label>
- </td>
- <td class="gr-diff right lineNum blank"></td>
- <td class="gr-diff right">
- <label class="gr-diff">
- ${this.renderName(this.revisionImage?._name ?? '')}
- <span class="gr-diff label"> ${this.imageLabel(Side.RIGHT)} </span>
- </label>
- </td>
- </tr>
- `;
- }
-
- private renderName(name?: string) {
- const addNamesInLabel =
- this.baseImage &&
- this.revisionImage &&
- this.baseImage._name !== this.revisionImage._name;
- if (!addNamesInLabel) return nothing;
- return html`
- <span class="gr-diff name">${name}</span><br class="gr-diff" />
- `;
- }
-
- private imageLabel(side: Side) {
- const image = side === Side.LEFT ? this.baseImage : this.revisionImage;
- const imageEl =
- side === Side.LEFT ? this.baseImageEl : this.revisionImageEl;
- if (image) {
- const type = image.type ?? image._expectedType;
- if (imageEl?.naturalWidth && imageEl.naturalHeight) {
- return `${imageEl?.naturalWidth}×${imageEl.naturalHeight} ${type}`;
- } else {
- return type;
- }
- }
- return 'No image';
- }
-}
-
-function imageSrc(image?: ImageInfo): string {
- return image && IMAGE_MIME_PATTERN.test(image.type)
- ? `data:${image.type};base64,${image.body}`
- : '';
-}
-
-if (!isNewDiff()) {
- customElements.define('gr-diff-image-new', GrDiffImageNew);
- customElements.define('gr-diff-image-old', GrDiffImageOld);
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-diff-image-new': LitElement;
- 'gr-diff-image-old': LitElement;
- }
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder.ts
deleted file mode 100644
index 951466f..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-builder.ts
+++ /dev/null
@@ -1,352 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import './gr-diff-section';
-import '../gr-context-controls/gr-context-controls';
-import {
- ContentLoadNeededEventDetail,
- DiffContextExpandedExternalDetail,
- DiffViewMode,
- LineNumber,
- RenderPreferences,
-} from '../../../api/diff';
-import {GrDiffGroup} from '../gr-diff/gr-diff-group';
-import {BlameInfo} from '../../../types/common';
-import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
-import {Side} from '../../../constants/constants';
-import {DiffLayer, isDefined} from '../../../types/types';
-import {GrDiffRow} from './gr-diff-row';
-import {GrDiffSection} from './gr-diff-section';
-import {html, render} from 'lit';
-import {diffClasses} from '../../diff/gr-diff/gr-diff-utils';
-import {when} from 'lit/directives/when.js';
-import {GrDiffBuilderImage} from './gr-diff-builder-image';
-import {GrDiffBuilderBinary} from './gr-diff-builder-binary';
-
-export interface DiffContextExpandedEventDetail
- extends DiffContextExpandedExternalDetail {
- /** The context control group that should be replaced by `groups`. */
- contextGroup: GrDiffGroup;
- groups: GrDiffGroup[];
-}
-
-declare global {
- interface HTMLElementEventMap {
- 'diff-context-expanded-internal': CustomEvent<DiffContextExpandedEventDetail>;
- 'diff-context-expanded': CustomEvent<DiffContextExpandedExternalDetail>;
- 'content-load-needed': CustomEvent<ContentLoadNeededEventDetail>;
- }
-}
-
-export function isImageDiffBuilder<T extends GrDiffBuilder>(
- x: T | GrDiffBuilderImage | undefined
-): x is GrDiffBuilderImage {
- return !!x && !!(x as GrDiffBuilderImage).renderImageDiff;
-}
-
-export function isBinaryDiffBuilder<T extends GrDiffBuilder>(
- x: T | GrDiffBuilderBinary | undefined
-): x is GrDiffBuilderBinary {
- return !!x && !!(x as GrDiffBuilderBinary).renderBinaryDiff;
-}
-
-/**
- * The builder takes GrDiffGroups, and builds the corresponding DOM elements,
- * called sections. Only the builder should add or remove sections from the
- * DOM. Callers can use the ...group() methods to modify groups and thus cause
- * rendering changes.
- */
-export class GrDiffBuilder {
- private readonly diff: DiffInfo;
-
- readonly prefs: DiffPreferencesInfo;
-
- renderPrefs?: RenderPreferences;
-
- readonly outputEl: HTMLElement;
-
- private groups: GrDiffGroup[];
-
- private readonly layerUpdateListener: (
- start: LineNumber,
- end: LineNumber,
- side: Side
- ) => void;
-
- constructor(
- diff: DiffInfo,
- prefs: DiffPreferencesInfo,
- outputEl: HTMLElement,
- readonly layers: DiffLayer[] = [],
- renderPrefs?: RenderPreferences
- ) {
- this.diff = diff;
- this.prefs = prefs;
- this.renderPrefs = renderPrefs;
- this.outputEl = outputEl;
- this.groups = [];
-
- if (isNaN(prefs.tab_size) || prefs.tab_size <= 0) {
- throw Error('Invalid tab size from preferences.');
- }
-
- if (isNaN(prefs.line_length) || prefs.line_length <= 0) {
- throw Error('Invalid line length from preferences.');
- }
-
- this.layerUpdateListener = (
- start: LineNumber,
- end: LineNumber,
- side: Side
- ) => this.renderContentByRange(start, end, side);
- this.init();
- }
-
- getContentTdByLine(
- lineNumber: LineNumber,
- side?: Side
- ): HTMLTableCellElement | undefined {
- if (!side) return undefined;
- const row = this.findRow(lineNumber, side);
- return row?.getContentCell(side);
- }
-
- getLineElByNumber(
- lineNumber: LineNumber,
- side?: Side
- ): HTMLTableCellElement | undefined {
- if (!side) return undefined;
- const row = this.findRow(lineNumber, side);
- return row?.getLineNumberCell(side);
- }
-
- private findRow(lineNumber?: LineNumber, side?: Side): GrDiffRow | undefined {
- if (!side || !lineNumber) return undefined;
- const group = this.findGroup(side, lineNumber);
- if (!group) return undefined;
- const section = this.findSection(group);
- if (!section) return undefined;
- return section.findRow(side, lineNumber);
- }
-
- private getDiffRows() {
- const sections = [
- ...this.outputEl.querySelectorAll<GrDiffSection>('gr-diff-section'),
- ];
- return sections.map(s => s.getDiffRows()).flat();
- }
-
- getLineNumberRows(): HTMLTableRowElement[] {
- const rows = this.getDiffRows();
- return rows.map(r => r.getTableRow()).filter(isDefined);
- }
-
- getLineNumEls(side: Side): HTMLTableCellElement[] {
- const rows = this.getDiffRows();
- return rows.map(r => r.getLineNumberCell(side)).filter(isDefined);
- }
-
- /** This is used when layers initiate an update. */
- renderContentByRange(start: LineNumber, end: LineNumber, side: Side) {
- const groups = this.getGroupsByLineRange(start, end, side);
- for (const group of groups) {
- const section = this.findSection(group);
- for (const row of section?.getDiffRows() ?? []) {
- row.requestUpdate();
- }
- }
- }
-
- private findSection(group: GrDiffGroup): GrDiffSection | undefined {
- const leftClass = `left-${group.startLine(Side.LEFT)}`;
- const rightClass = `right-${group.startLine(Side.RIGHT)}`;
- return (
- this.outputEl.querySelector<GrDiffSection>(
- `gr-diff-section.${leftClass}.${rightClass}`
- ) ?? undefined
- );
- }
-
- buildSectionElement(group: GrDiffGroup): HTMLElement {
- const leftCl = `left-${group.startLine(Side.LEFT)}`;
- const rightCl = `right-${group.startLine(Side.RIGHT)}`;
- const section = html`
- <gr-diff-section
- class="${leftCl} ${rightCl}"
- .group=${group}
- .diff=${this.diff}
- .layers=${this.layers}
- .diffPrefs=${this.prefs}
- .renderPrefs=${this.renderPrefs}
- ></gr-diff-section>
- `;
- // When using Lit's `render()` method it wants to be in full control of the
- // element that it renders into, so we let it render into a temp element.
- // Rendering into the diff table directly would interfere with
- // `clearDiffContent()`for example.
- // TODO: Convert <gr-diff> to be fully lit controlled and incorporate this
- // method into Lit's `render()` cycle.
- const tempEl = document.createElement('div');
- render(section, tempEl);
- const sectionEl = tempEl.firstElementChild as GrDiffSection;
- return sectionEl;
- }
-
- addColumns(outputEl: HTMLElement, lineNumberWidth: number): void {
- const colgroup = html`
- <colgroup>
- <col class=${diffClasses('blame')}></col>
- ${when(
- this.renderPrefs?.view_mode === DiffViewMode.UNIFIED,
- () => html` ${this.renderUnifiedColumns(lineNumberWidth)} `,
- () => html`
- ${this.renderSideBySideColumns(Side.LEFT, lineNumberWidth)}
- ${this.renderSideBySideColumns(Side.RIGHT, lineNumberWidth)}
- `
- )}
- </colgroup>
- `;
- // When using Lit's `render()` method it wants to be in full control of the
- // element that it renders into, so we let it render into a temp element.
- // Rendering into the diff table directly would interfere with
- // `clearDiffContent()`for example.
- // TODO: Convert <gr-diff> to be fully lit controlled and incorporate this
- // method into Lit's `render()` cycle.
- const tempEl = document.createElement('div');
- render(colgroup, tempEl);
- const colgroupEl = tempEl.firstElementChild as HTMLElement;
- outputEl.appendChild(colgroupEl);
- }
-
- private renderUnifiedColumns(lineNumberWidth: number) {
- return html`
- <col class=${diffClasses()} width=${lineNumberWidth}></col>
- <col class=${diffClasses()} width=${lineNumberWidth}></col>
- <col class=${diffClasses()}></col>
- `;
- }
-
- private renderSideBySideColumns(side: Side, lineNumberWidth: number) {
- return html`
- <col class=${diffClasses(side)} width=${lineNumberWidth}></col>
- <col class=${diffClasses(side, 'sign')}></col>
- <col class=${diffClasses(side)}></col>
- `;
- }
-
- /**
- * This is meant to be called when the gr-diff component re-connects, or when
- * the diff is (re-)rendered.
- *
- * Make sure that this method is symmetric with cleanup(), which is called
- * when gr-diff disconnects.
- */
- init() {
- this.cleanup();
- for (const layer of this.layers) {
- if (layer.addListener) {
- layer.addListener(this.layerUpdateListener);
- }
- }
- }
-
- /**
- * This is meant to be called when the gr-diff component disconnects, or when
- * the diff is (re-)rendered.
- *
- * Make sure that this method is symmetric with init(), which is called when
- * gr-diff re-connects.
- */
- cleanup() {
- for (const layer of this.layers) {
- if (layer.removeListener) {
- layer.removeListener(this.layerUpdateListener);
- }
- }
- }
-
- addGroups(groups: readonly GrDiffGroup[]) {
- for (const group of groups) {
- this.groups.push(group);
- this.emitGroup(group);
- }
- }
-
- clearGroups() {
- for (const deletedGroup of this.groups) {
- deletedGroup.element?.remove();
- }
- this.groups = [];
- }
-
- replaceGroup(contextControl: GrDiffGroup, groups: readonly GrDiffGroup[]) {
- const i = this.groups.indexOf(contextControl);
- if (i === -1) throw new Error('cannot find context control group');
-
- const contextControlSection = this.groups[i].element;
- if (!contextControlSection) throw new Error('diff group element not set');
-
- this.groups.splice(i, 1, ...groups);
- for (const group of groups) {
- this.emitGroup(group, contextControlSection);
- }
- if (contextControlSection) contextControlSection.remove();
- }
-
- findGroup(side: Side, line: LineNumber) {
- return this.groups.find(group => group.containsLine(side, line));
- }
-
- private emitGroup(group: GrDiffGroup, beforeSection?: HTMLElement) {
- const element = this.buildSectionElement(group);
- this.outputEl.insertBefore(element, beforeSection ?? null);
- group.element = element;
- }
-
- // visible for testing
- getGroupsByLineRange(
- startLine: LineNumber,
- endLine: LineNumber,
- side: Side
- ): GrDiffGroup[] {
- const startIndex = this.groups.findIndex(group =>
- group.containsLine(side, startLine)
- );
- if (startIndex === -1) return [];
- let endIndex = this.groups.findIndex(group =>
- group.containsLine(side, endLine)
- );
- // Not all groups may have been processed yet (i.e. this.groups is still
- // incomplete). In that case let's just return *all* groups until the end
- // of the array.
- if (endIndex === -1) endIndex = this.groups.length - 1;
- // The filter preserves the legacy behavior to only return non-context
- // groups
- return this.groups
- .slice(startIndex, endIndex + 1)
- .filter(group => group.lines.length > 0);
- }
-
- /**
- * Set the blame information for the diff. For any already-rendered line,
- * re-render its blame cell content.
- */
- setBlame(blame: BlameInfo[]) {
- for (const blameInfo of blame) {
- for (const range of blameInfo.ranges) {
- for (let line = range.start; line <= range.end; line++) {
- const row = this.findRow(line, Side.LEFT);
- if (row) row.blameInfo = blameInfo;
- }
- }
- }
- }
-
- /**
- * Only special builders need to implement this. The default is to
- * just ignore it.
- */
- updateRenderPrefs(_: RenderPreferences) {}
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-row.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-row.ts
deleted file mode 100644
index 1486154..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-row.ts
+++ /dev/null
@@ -1,487 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {html, LitElement, nothing, TemplateResult} from 'lit';
-import {property, state} from 'lit/decorators.js';
-import {ifDefined} from 'lit/directives/if-defined.js';
-import {createRef, Ref, ref} from 'lit/directives/ref.js';
-import {
- DiffResponsiveMode,
- Side,
- LineNumber,
- DiffLayer,
- GrDiffLineType,
- LOST,
- FILE,
-} from '../../../api/diff';
-import {BlameInfo} from '../../../types/common';
-import {assertIsDefined} from '../../../utils/common-util';
-import {fire} from '../../../utils/event-util';
-import {getBaseUrl} from '../../../utils/url-util';
-import './gr-diff-text';
-import {GrDiffLine} from '../gr-diff/gr-diff-line';
-import {
- diffClasses,
- isNewDiff,
- isResponsive,
-} from '../../diff/gr-diff/gr-diff-utils';
-
-export class GrDiffRow extends LitElement {
- contentLeftRef: Ref<LitElement> = createRef();
-
- contentRightRef: Ref<LitElement> = createRef();
-
- contentCellLeftRef: Ref<HTMLTableCellElement> = createRef();
-
- contentCellRightRef: Ref<HTMLTableCellElement> = createRef();
-
- lineNumberLeftRef: Ref<HTMLTableCellElement> = createRef();
-
- lineNumberRightRef: Ref<HTMLTableCellElement> = createRef();
-
- blameCellRef: Ref<HTMLTableCellElement> = createRef();
-
- tableRowRef: Ref<HTMLTableRowElement> = createRef();
-
- @property({type: Object})
- left?: GrDiffLine;
-
- @property({type: Object})
- right?: GrDiffLine;
-
- @property({type: Object})
- blameInfo?: BlameInfo;
-
- @property({type: Object})
- responsiveMode?: DiffResponsiveMode;
-
- /**
- * true: side-by-side diff
- * false: unified diff
- */
- @property({type: Boolean})
- unifiedDiff = false;
-
- @property({type: Number})
- tabSize = 2;
-
- @property({type: Number})
- lineLength = 80;
-
- @property({type: Boolean})
- hideFileCommentButton = false;
-
- @property({type: Object})
- layers: DiffLayer[] = [];
-
- /**
- * Semantic DOM diff testing does not work with just table fragments, so when
- * running such tests the render() method has to wrap the DOM in a proper
- * <table> element.
- */
- @state()
- addTableWrapperForTesting = false;
-
- /**
- * Keeps track of whether diff layers have already been applied to the diff
- * row. That happens after the DOM has been created in the `updated()`
- * lifecycle callback.
- *
- * Once layers are applied, the diff row requires two rendering passes for an
- * update: 1. Remove all <gr-diff-text> elements and their layer manipulated
- * DOMs. 2. Add fresh <gr-diff-text> elements and let layers re-apply in
- * `updated()`.
- */
- private layersApplied = false;
-
- /**
- * The browser API for handling selection does not (yet) work for selection
- * across multiple shadow DOM elements. So we are rendering gr-diff components
- * into the light DOM instead of the shadow DOM by overriding this method,
- * which was the recommended workaround by the lit team.
- * See also https://github.com/WICG/webcomponents/issues/79.
- */
- override createRenderRoot() {
- return this;
- }
-
- override updated() {
- if (this.layersApplied) {
- // <gr-diff-text> elements have been removed during rendering. Let's start
- // another rendering cycle with freshly created <gr-diff-text> elements.
- this.updateComplete.then(() => {
- this.layersApplied = false;
- this.requestUpdate();
- });
- } else {
- this.updateLayers(Side.LEFT);
- this.updateLayers(Side.RIGHT);
- }
- }
-
- /**
- * The diff layers API is designed to let layers manipulate the DOM. So we
- * have to apply them after the rendering cycle is done (`updated()`). But
- * when re-rendering a row that already has layers applied, then we have to
- * first wipe away <gr-diff-text>. This is achieved by
- * `this.layersApplied = true`.
- */
- private async updateLayers(side: Side) {
- const line = this.line(side);
- const contentEl = this.contentRef(side).value;
- const lineNumberEl = this.lineNumberRef(side).value;
- if (!line || !contentEl || !lineNumberEl) return;
-
- // We have to wait for the <gr-diff-text> child component to finish
- // rendering before we can apply layers, which will re-write the HTML.
- await contentEl?.updateComplete;
- for (const layer of this.layers) {
- if (typeof layer.annotate === 'function') {
- layer.annotate(contentEl, lineNumberEl, line, side);
- }
- }
- // At this point we consider layers applied. So as soon as <gr-diff-row>
- // enters a new rendering cycle <gr-diff-text> elements will be removed.
- this.layersApplied = true;
- }
-
- override render() {
- if (!this.left || !this.right) return;
- const classes = this.unifiedDiff ? ['unified'] : ['side-by-side'];
- const unifiedType = this.unifiedType();
- if (this.unifiedDiff && unifiedType) classes.push(unifiedType);
- const row = html`
- <tr
- ${ref(this.tableRowRef)}
- class=${diffClasses('diff-row', ...classes)}
- left-type=${ifDefined(this.getType(Side.LEFT))}
- right-type=${ifDefined(this.getType(Side.RIGHT))}
- tabindex="-1"
- aria-labelledby=${this.ariaLabelIds()}
- >
- ${this.renderBlameCell()} ${this.renderLineNumberCell(Side.LEFT)}
- ${this.renderSignCell(Side.LEFT)} ${this.renderContentCell(Side.LEFT)}
- ${this.renderLineNumberCell(Side.RIGHT)}
- ${this.renderSignCell(Side.RIGHT)} ${this.renderContentCell(Side.RIGHT)}
- </tr>
- ${this.renderPostLineSlot(Side.LEFT)}
- ${this.renderPostLineSlot(Side.RIGHT)}
- `;
- if (this.addTableWrapperForTesting) {
- return html`<table>
- ${row}
- </table>`;
- }
- return row;
- }
-
- private ariaLabelIds() {
- const ids: string[] = [];
- ids.push(this.lineNumberId(Side.LEFT));
- if (!this.unifiedDiff) ids.push(this.contentId(Side.LEFT));
- ids.push(this.lineNumberId(Side.RIGHT));
- if (!this.unifiedDiff) ids.push(this.contentId(Side.RIGHT));
- if (this.unifiedDiff) ids.push(this.contentId(this.unifiedSide()));
- return ids.filter(id => !!id).join(' ');
- }
-
- private lineNumberId(side: Side): string {
- const lineNumber = this.lineNumber(side);
- if (!lineNumber) return '';
- return `${side}-button-${lineNumber}`;
- }
-
- private unifiedSide() {
- const isLeft = this.line(Side.RIGHT)?.type === GrDiffLineType.BLANK;
- return isLeft ? Side.LEFT : Side.RIGHT;
- }
-
- private contentId(side: Side): string {
- const lineNumber = this.lineNumber(side);
- if (!lineNumber) return '';
- return `${side}-content-${lineNumber}`;
- }
-
- getTableRow(): HTMLTableRowElement | undefined {
- return this.tableRowRef.value;
- }
-
- getLineNumberCell(side: Side): HTMLTableCellElement | undefined {
- return this.lineNumberRef(side).value;
- }
-
- getContentCell(side: Side) {
- return this.contentCellRef(side)?.value;
- }
-
- getBlameCell() {
- return this.blameCellRef.value;
- }
-
- private renderBlameCell() {
- // td.blame has `white-space: pre`, so prettier must not add spaces.
- // prettier-ignore
- return html`
- <td
- ${ref(this.blameCellRef)}
- class=${diffClasses('blame')}
- data-line-number=${this.left?.beforeNumber ?? 0}
- >${this.renderBlameElement()}</td>
- `;
- }
-
- private renderBlameElement() {
- const lineNum = this.left?.beforeNumber;
- const commit = this.blameInfo;
- if (!lineNum || !commit) return;
-
- const isStartOfRange = commit.ranges.some(r => r.start === lineNum);
- const extras: string[] = [];
- if (isStartOfRange) extras.push('startOfRange');
- const date = new Date(commit.time * 1000).toLocaleDateString();
- const shortName = commit.author.split(' ')[0];
- const url = `${getBaseUrl()}/q/${commit.id}`;
-
- // td.blame has `white-space: pre`, so prettier must not add spaces.
- // prettier-ignore
- return html`<span class=${diffClasses(...extras)}
- ><a href=${url} class=${diffClasses('blameDate')}>${date}</a
- ><span class=${diffClasses('blameAuthor')}> ${shortName}</span
- ><gr-hovercard class=${diffClasses()}>
- <span class=${diffClasses('blameHoverCard')}>
- Commit ${commit.id}<br />
- Author: ${commit.author}<br />
- Date: ${date}<br />
- <br />
- ${commit.commit_msg}
- </span>
- </gr-hovercard
- ></span>`;
- }
-
- private renderLineNumberCell(side: Side): TemplateResult {
- const line = this.line(side);
- const lineNumber = this.lineNumber(side);
- const isBlank = line?.type === GrDiffLineType.BLANK;
- if (!line || !lineNumber || isBlank || this.layersApplied) {
- const blankClass = isBlank && !this.unifiedDiff ? 'blankLineNum' : '';
- return html`<td
- ${ref(this.lineNumberRef(side))}
- class=${diffClasses(side, blankClass)}
- ></td>`;
- }
-
- return html`<td
- ${ref(this.lineNumberRef(side))}
- class=${diffClasses(side, 'lineNum')}
- data-value=${lineNumber}
- >
- ${this.renderLineNumberButton(line, lineNumber, side)}
- </td>`;
- }
-
- private renderLineNumberButton(
- line: GrDiffLine,
- lineNumber: LineNumber,
- side: Side
- ) {
- if (this.hideFileCommentButton && lineNumber === FILE) return;
- if (lineNumber === LOST) return;
- // .lineNumButton has `white-space: pre`, so prettier must not add spaces.
- // prettier-ignore
- return html`
- <button
- id=${this.lineNumberId(side)}
- class=${diffClasses('lineNumButton', side)}
- tabindex="-1"
- data-value=${lineNumber}
- aria-label=${ifDefined(
- this.computeLineNumberAriaLabel(line, lineNumber)
- )}
- @mouseenter=${() =>
- fire(this, 'line-mouse-enter', {lineNum: lineNumber, side})}
- @mouseleave=${() =>
- fire(this, 'line-mouse-leave', {lineNum: lineNumber, side})}
- >${lineNumber === FILE ? 'File' : lineNumber.toString()}</button>
- `;
- }
-
- private computeLineNumberAriaLabel(line: GrDiffLine, lineNumber: LineNumber) {
- if (lineNumber === FILE) return 'Add file comment';
-
- // Add aria-labels for valid line numbers.
- // For unified diff, this method will be called with number set to 0 for
- // the empty line number column for added/removed lines. This should not
- // be announced to the screenreader.
- if (
- lineNumber === LOST ||
- (typeof lineNumber === 'number' && lineNumber <= 0)
- )
- return undefined;
- switch (line.type) {
- case GrDiffLineType.REMOVE:
- return `${lineNumber} removed`;
- case GrDiffLineType.ADD:
- return `${lineNumber} added`;
- case GrDiffLineType.BOTH:
- case GrDiffLineType.BLANK:
- return `${lineNumber} unmodified`;
- }
- }
-
- private renderContentCell(side: Side) {
- let line = this.line(side);
- if (this.unifiedDiff) {
- if (side === Side.LEFT) return nothing;
- if (line?.type === GrDiffLineType.BLANK) {
- side = Side.LEFT;
- line = this.line(Side.LEFT);
- }
- }
- const lineNumber = this.lineNumber(side);
- assertIsDefined(line, 'line');
- const extras: string[] = [line.type, side];
- if (line.type !== GrDiffLineType.BLANK) extras.push('content');
- if (!line.hasIntralineInfo) extras.push('no-intraline-info');
- if (line.beforeNumber === FILE) extras.push('file');
- if (line.beforeNumber === LOST) extras.push('lost');
-
- // .content has `white-space: pre`, so prettier must not add spaces.
- // prettier-ignore
- return html`
- <td
- ${ref(this.contentCellRef(side))}
- class=${diffClasses(...extras)}
- @mouseenter=${() => {
- if (lineNumber)
- fire(this, 'line-mouse-enter', {lineNum: lineNumber, side});
- }}
- @mouseleave=${() => {
- if (lineNumber)
- fire(this, 'line-mouse-leave', {lineNum: lineNumber, side});
- }}
- >${this.renderText(side)}${this.renderThreadGroup(side)}</td>
- `;
- }
-
- private renderSignCell(side: Side) {
- if (this.unifiedDiff) return nothing;
- const line = this.line(side);
- assertIsDefined(line, 'line');
- const isBlank = line.type === GrDiffLineType.BLANK;
- const isAdd = line.type === GrDiffLineType.ADD && side === Side.RIGHT;
- const isRemove = line.type === GrDiffLineType.REMOVE && side === Side.LEFT;
- const extras: string[] = ['sign', side];
- if (isBlank) extras.push('blank');
- if (isAdd) extras.push('add');
- if (isRemove) extras.push('remove');
- if (!line.hasIntralineInfo) extras.push('no-intraline-info');
-
- const sign = isAdd ? '+' : isRemove ? '-' : '';
- return html`<td class=${diffClasses(...extras)}>${sign}</td>`;
- }
-
- private renderThreadGroup(side: Side) {
- const lineNumber = this.lineNumber(side);
- if (!lineNumber) return nothing;
- return html`<div class="thread-group" data-side=${side}>
- <slot name="${side}-${lineNumber}"></slot>
- ${this.renderSecondSlot()}
- </div>`;
- }
-
- private renderSecondSlot() {
- if (!this.unifiedDiff) return nothing;
- if (this.line(Side.LEFT)?.type !== GrDiffLineType.BOTH) return nothing;
- return html`<slot
- name="${Side.LEFT}-${this.lineNumber(Side.LEFT)}"
- ></slot>`;
- }
-
- private contentRef(side: Side) {
- return side === Side.LEFT ? this.contentLeftRef : this.contentRightRef;
- }
-
- private contentCellRef(side: Side) {
- return side === Side.LEFT
- ? this.contentCellLeftRef
- : this.contentCellRightRef;
- }
-
- private lineNumberRef(side: Side) {
- return side === Side.LEFT
- ? this.lineNumberLeftRef
- : this.lineNumberRightRef;
- }
-
- private lineNumber(side: Side) {
- return this.line(side)?.lineNumber(side);
- }
-
- private line(side: Side) {
- return side === Side.LEFT ? this.left : this.right;
- }
-
- private getType(side?: Side): string | undefined {
- if (this.unifiedDiff) return undefined;
- if (side === Side.LEFT) return this.left?.type;
- if (side === Side.RIGHT) return this.right?.type;
- return undefined;
- }
-
- private unifiedType() {
- return this.left?.type === GrDiffLineType.BLANK
- ? this.right?.type
- : this.left?.type;
- }
-
- /**
- * Returns a 'div' element containing the supplied |text| as its innerText,
- * with '\t' characters expanded to a width determined by |tabSize|, and the
- * text wrapped at column |lineLimit|, which may be Infinity if no wrapping is
- * desired.
- */
- private renderText(side: Side) {
- const line = this.line(side);
- const lineNumber = this.lineNumber(side);
- if (typeof lineNumber !== 'number') return;
-
- // Note that `this.layersApplied` will wipe away the <gr-diff-text>, and
- // another rendering cycle will be initiated in `updated()`.
- // prettier-ignore
- const textElement = line?.text && !this.layersApplied
- ? html`<gr-diff-text
- ${ref(this.contentRef(side))}
- .text=${line?.text}
- .tabSize=${this.tabSize}
- .lineLimit=${this.lineLength}
- .isResponsive=${isResponsive(this.responsiveMode)}
- ></gr-diff-text>` : '';
- // .content has `white-space: pre`, so prettier must not add spaces.
- // prettier-ignore
- return html`<div
- class=${diffClasses('contentText')}
- data-side=${ifDefined(side)}
- id=${this.contentId(side)}
- >${textElement}</div>`;
- }
-
- private renderPostLineSlot(side: Side) {
- const lineNumber = this.lineNumber(side);
- return lineNumber && Number.isInteger(lineNumber)
- ? html`<slot name="post-${side}-line-${lineNumber}"></slot>`
- : nothing;
- }
-}
-
-if (!isNewDiff()) {
- customElements.define('gr-diff-row', GrDiffRow);
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-diff-row': LitElement;
- }
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-row_test.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-row_test.ts
deleted file mode 100644
index 42d30aa..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-row_test.ts
+++ /dev/null
@@ -1,271 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup';
-import './gr-diff-row';
-import {GrDiffRow} from './gr-diff-row';
-import {fixture, html, assert} from '@open-wc/testing';
-import {GrDiffLine} from '../gr-diff/gr-diff-line';
-import {GrDiffLineType} from '../../../api/diff';
-
-suite('gr-diff-row test', () => {
- let element: GrDiffRow;
-
- setup(async () => {
- element = await fixture<GrDiffRow>(html`<gr-diff-row></gr-diff-row>`);
- element.addTableWrapperForTesting = true;
- await element.updateComplete;
- });
-
- test('both', async () => {
- const line = new GrDiffLine(GrDiffLineType.BOTH, 1, 1);
- line.text = 'lorem ipsum';
- element.left = line;
- element.right = line;
- await element.updateComplete;
- assert.lightDom.equal(
- element,
- /* HTML */ `
- <table>
- <tbody>
- <tr
- aria-labelledby="left-button-1 left-content-1 right-button-1 right-content-1"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="1"></td>
- <td class="gr-diff left lineNum" data-value="1">
- <button
- aria-label="1 unmodified"
- class="gr-diff left lineNumButton"
- data-value="1"
- id="left-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-1"
- >
- <gr-diff-text> lorem ipsum </gr-diff-text>
- </div>
- <div class="thread-group" data-side="left">
- <slot name="left-1"> </slot>
- </div>
- </td>
- <td class="gr-diff lineNum right" data-value="1">
- <button
- aria-label="1 unmodified"
- class="gr-diff lineNumButton right"
- data-value="1"
- id="right-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-1"
- >
- <gr-diff-text> lorem ipsum </gr-diff-text>
- </div>
- <div class="thread-group" data-side="right">
- <slot name="right-1"> </slot>
- </div>
- </td>
- </tr>
- <slot name="post-left-line-1"></slot>
- <slot name="post-right-line-1"></slot>
- </tbody>
- </table>
- `
- );
- });
-
- test('both unified', async () => {
- const line = new GrDiffLine(GrDiffLineType.BOTH, 1, 1);
- line.text = 'lorem ipsum';
- element.left = line;
- element.right = line;
- element.unifiedDiff = true;
- await element.updateComplete;
- assert.lightDom.equal(
- element,
- /* HTML */ `
- <table>
- <tbody>
- <tr
- aria-labelledby="left-button-1 right-button-1 right-content-1"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="1"></td>
- <td class="gr-diff left lineNum" data-value="1">
- <button
- aria-label="1 unmodified"
- class="gr-diff left lineNumButton"
- data-value="1"
- id="left-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="1">
- <button
- aria-label="1 unmodified"
- class="gr-diff lineNumButton right"
- data-value="1"
- id="right-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-1"
- >
- <gr-diff-text> lorem ipsum </gr-diff-text>
- </div>
- <div class="thread-group" data-side="right">
- <slot name="right-1"> </slot>
- <slot name="left-1"> </slot>
- </div>
- </td>
- </tr>
- <slot name="post-left-line-1"></slot>
- <slot name="post-right-line-1"></slot>
- </tbody>
- </table>
- `
- );
- });
-
- test('add', async () => {
- const line = new GrDiffLine(GrDiffLineType.ADD, 0, 1);
- line.text = 'lorem ipsum';
- element.left = new GrDiffLine(GrDiffLineType.BLANK);
- element.right = line;
- await element.updateComplete;
- assert.lightDom.equal(
- element,
- /* HTML */ `
- <table>
- <tbody>
- <tr
- aria-labelledby="right-button-1 right-content-1"
- class="diff-row gr-diff side-by-side"
- left-type="blank"
- right-type="add"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="blankLineNum gr-diff left"></td>
- <td class="blank gr-diff left no-intraline-info sign"></td>
- <td class="blank gr-diff left no-intraline-info">
- <div class="contentText gr-diff" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="1">
- <button
- aria-label="1 added"
- class="gr-diff lineNumButton right"
- data-value="1"
- id="right-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="add gr-diff no-intraline-info right sign">+</td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-1"
- >
- <gr-diff-text> lorem ipsum </gr-diff-text>
- </div>
- <div class="thread-group" data-side="right">
- <slot name="right-1"> </slot>
- </div>
- </td>
- <slot name="post-right-line-1"></slot>
- </tr>
- </tbody>
- </table>
- `
- );
- });
-
- test('remove', async () => {
- const line = new GrDiffLine(GrDiffLineType.REMOVE, 1, 0);
- line.text = 'lorem ipsum';
- element.left = line;
- element.right = new GrDiffLine(GrDiffLineType.BLANK);
- await element.updateComplete;
- assert.lightDom.equal(
- element,
- /* HTML */ `
- <table>
- <tbody>
- <tr
- aria-labelledby="left-button-1 left-content-1"
- class="diff-row gr-diff side-by-side"
- left-type="remove"
- right-type="blank"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="1"></td>
- <td class="gr-diff left lineNum" data-value="1">
- <button
- aria-label="1 removed"
- class="gr-diff left lineNumButton"
- data-value="1"
- id="left-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="gr-diff left no-intraline-info remove sign">-</td>
- <td class="content gr-diff left no-intraline-info remove">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-1"
- >
- <gr-diff-text> lorem ipsum </gr-diff-text>
- </div>
- <div class="thread-group" data-side="left">
- <slot name="left-1"> </slot>
- </div>
- </td>
- <td class="blankLineNum gr-diff right"></td>
- <td class="blank gr-diff no-intraline-info right sign"></td>
- <td class="blank gr-diff no-intraline-info right">
- <div class="contentText gr-diff" data-side="right"></div>
- </td>
- </tr>
- <slot name="post-left-line-1"></slot>
- </tbody>
- </table>
- `
- );
- });
-});
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-section.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-section.ts
deleted file mode 100644
index 28919e8..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-section.ts
+++ /dev/null
@@ -1,254 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {html, LitElement} from 'lit';
-import {property, state} from 'lit/decorators.js';
-import {
- DiffInfo,
- DiffLayer,
- DiffViewMode,
- RenderPreferences,
- Side,
- LineNumber,
- DiffPreferencesInfo,
-} from '../../../api/diff';
-import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {
- diffClasses,
- getResponsiveMode,
- isNewDiff,
-} from '../../diff/gr-diff/gr-diff-utils';
-import {GrDiffRow} from './gr-diff-row';
-import '../gr-context-controls/gr-context-controls-section';
-import '../gr-context-controls/gr-context-controls';
-import '../../diff/gr-range-header/gr-range-header';
-import './gr-diff-row';
-import {when} from 'lit/directives/when.js';
-import {fire} from '../../../utils/event-util';
-import {countLines} from '../../../utils/diff-util';
-
-export class GrDiffSection extends LitElement {
- @property({type: Object})
- group?: GrDiffGroup;
-
- @property({type: Object})
- diff?: DiffInfo;
-
- @property({type: Object})
- renderPrefs?: RenderPreferences;
-
- @property({type: Object})
- diffPrefs?: DiffPreferencesInfo;
-
- @property({type: Object})
- layers: DiffLayer[] = [];
-
- /**
- * Semantic DOM diff testing does not work with just table fragments, so when
- * running such tests the render() method has to wrap the DOM in a proper
- * <table> element.
- */
- @state()
- addTableWrapperForTesting = false;
-
- /**
- * The browser API for handling selection does not (yet) work for selection
- * across multiple shadow DOM elements. So we are rendering gr-diff components
- * into the light DOM instead of the shadow DOM by overriding this method,
- * which was the recommended workaround by the lit team.
- * See also https://github.com/WICG/webcomponents/issues/79.
- */
- override createRenderRoot() {
- return this;
- }
-
- override render() {
- if (!this.group) return;
- const extras: string[] = [];
- extras.push('section');
- extras.push(this.group.type);
- if (this.group.isTotal()) extras.push('total');
- if (this.group.dueToRebase) extras.push('dueToRebase');
- if (this.group.moveDetails) extras.push('dueToMove');
- if (this.group.moveDetails?.changed) extras.push('changed');
- if (this.group.ignoredWhitespaceOnly) extras.push('ignoredWhitespaceOnly');
-
- const pairs = this.getLinePairs();
- const responsiveMode = getResponsiveMode(this.diffPrefs, this.renderPrefs);
- const hideFileCommentButton =
- this.diffPrefs?.show_file_comment_button === false ||
- this.renderPrefs?.show_file_comment_button === false;
- const body = html`
- <tbody class=${diffClasses(...extras)}>
- ${this.renderContextControls()} ${this.renderMoveControls()}
- ${pairs.map(pair => {
- const leftCl = `left-${pair.left.lineNumber(Side.LEFT)}`;
- const rightCl = `right-${pair.right.lineNumber(Side.RIGHT)}`;
- return html`
- <gr-diff-row
- class="${leftCl} ${rightCl}"
- .left=${pair.left}
- .right=${pair.right}
- .layers=${this.layers}
- .lineLength=${this.diffPrefs?.line_length ?? 80}
- .tabSize=${this.diffPrefs?.tab_size ?? 2}
- .unifiedDiff=${this.isUnifiedDiff()}
- .responsiveMode=${responsiveMode}
- .hideFileCommentButton=${hideFileCommentButton}
- >
- </gr-diff-row>
- `;
- })}
- </tbody>
- `;
- if (this.addTableWrapperForTesting) {
- return html`<table>
- ${body}
- </table>`;
- }
- return body;
- }
-
- private isUnifiedDiff() {
- return this.renderPrefs?.view_mode === DiffViewMode.UNIFIED;
- }
-
- getLinePairs() {
- if (!this.group) return [];
- const isControl = this.group.type === GrDiffGroupType.CONTEXT_CONTROL;
- if (isControl) return [];
- return this.isUnifiedDiff()
- ? this.group.getUnifiedPairs()
- : this.group.getSideBySidePairs();
- }
-
- getDiffRows(): GrDiffRow[] {
- return [...this.querySelectorAll<GrDiffRow>('gr-diff-row')];
- }
-
- private renderContextControls() {
- if (this.group?.type !== GrDiffGroupType.CONTEXT_CONTROL) return;
-
- const leftStart = this.group.lineRange.left.start_line;
- const leftEnd = this.group.lineRange.left.end_line;
- const firstGroupIsSkipped = !!this.group.contextGroups[0].skip;
- const lastGroupIsSkipped =
- !!this.group.contextGroups[this.group.contextGroups.length - 1].skip;
- const lineCountLeft = countLines(this.diff, Side.LEFT);
- const containsWholeFile = lineCountLeft === leftEnd - leftStart + 1;
- const showAbove =
- (leftStart > 1 && !firstGroupIsSkipped) || containsWholeFile;
- const showBelow = leftEnd < lineCountLeft && !lastGroupIsSkipped;
-
- return html`
- <gr-context-controls-section
- .showAbove=${showAbove}
- .showBelow=${showBelow}
- .group=${this.group}
- .diff=${this.diff}
- .renderPrefs=${this.renderPrefs}
- >
- </gr-context-controls-section>
- `;
- }
-
- findRow(side: Side, lineNumber: LineNumber): GrDiffRow | undefined {
- return (
- this.querySelector<GrDiffRow>(`gr-diff-row.${side}-${lineNumber}`) ??
- undefined
- );
- }
-
- private renderMoveControls() {
- if (!this.group?.moveDetails) return;
- const movedIn = this.group.adds.length > 0;
- const plainCell = html`<td class=${diffClasses()}></td>`;
- const signCell = html`<td class=${diffClasses('sign')}></td>`;
- const lineNumberCell = html`
- <td class=${diffClasses('moveControlsLineNumCol')}></td>
- `;
- const moveCell = html`
- <td class=${diffClasses('moveHeader')}>
- <gr-range-header class=${diffClasses()} icon="move_item">
- ${this.renderMoveDescription(movedIn)}
- </gr-range-header>
- </td>
- `;
- return html`
- <tr
- class=${diffClasses('moveControls', movedIn ? 'movedIn' : 'movedOut')}
- >
- ${when(
- this.isUnifiedDiff(),
- () => html`${lineNumberCell} ${lineNumberCell} ${moveCell}`,
- () => html`${lineNumberCell} ${signCell}
- ${movedIn ? plainCell : moveCell} ${lineNumberCell} ${signCell}
- ${movedIn ? moveCell : plainCell}`
- )}
- </tr>
- `;
- }
-
- private renderMoveDescription(movedIn: boolean) {
- if (this.group?.moveDetails?.range) {
- const {changed, range} = this.group.moveDetails;
- const otherSide = movedIn ? Side.LEFT : Side.RIGHT;
- const andChangedLabel = changed ? 'and changed ' : '';
- const direction = movedIn ? 'from' : 'to';
- const textLabel = `Moved ${andChangedLabel}${direction} lines `;
- return html`
- <div class=${diffClasses()}>
- <span class=${diffClasses()}>${textLabel}</span>
- ${this.renderMovedLineAnchor(range.start, otherSide)}
- <span class=${diffClasses()}> - </span>
- ${this.renderMovedLineAnchor(range.end, otherSide)}
- </div>
- `;
- }
-
- return html`
- <div class=${diffClasses()}>
- <span class=${diffClasses()}
- >${movedIn ? 'Moved in' : 'Moved out'}</span
- >
- </div>
- `;
- }
-
- private renderMovedLineAnchor(line: number, side: Side) {
- const listener = (e: MouseEvent) => {
- e.preventDefault();
- this.handleMovedLineAnchorClick(e.target, side, line);
- };
- // `href` is not actually used but important for Screen Readers
- return html`
- <a class=${diffClasses()} href=${`#${line}`} @click=${listener}
- >${line}</a
- >
- `;
- }
-
- private handleMovedLineAnchorClick(
- anchor: EventTarget | null,
- side: Side,
- line: number
- ) {
- if (!anchor) return;
- fire(anchor, 'moved-link-clicked', {
- lineNum: line,
- side,
- });
- }
-}
-
-if (!isNewDiff()) {
- customElements.define('gr-diff-section', GrDiffSection);
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-diff-section': LitElement;
- }
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-section_test.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-section_test.ts
deleted file mode 100644
index 381f9b2..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-section_test.ts
+++ /dev/null
@@ -1,315 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup';
-import './gr-diff-section';
-import {GrDiffSection} from './gr-diff-section';
-import {fixture, html, assert} from '@open-wc/testing';
-import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {GrDiffLine} from '../gr-diff/gr-diff-line';
-import {DiffViewMode, GrDiffLineType} from '../../../api/diff';
-import {waitQueryAndAssert} from '../../../test/test-utils';
-
-suite('gr-diff-section test', () => {
- let element: GrDiffSection;
-
- setup(async () => {
- element = await fixture<GrDiffSection>(
- html`<gr-diff-section></gr-diff-section>`
- );
- element.addTableWrapperForTesting = true;
- await element.updateComplete;
- });
-
- suite('move controls', async () => {
- setup(async () => {
- const lines = [new GrDiffLine(GrDiffLineType.BOTH, 1, 1)];
- lines[0].text = 'asdf';
- const group = new GrDiffGroup({
- type: GrDiffGroupType.BOTH,
- lines,
- moveDetails: {changed: false, range: {start: 1, end: 2}},
- });
- element.group = group;
- await element.updateComplete;
- });
-
- test('side-by-side', async () => {
- const row = await waitQueryAndAssert(element, 'tr.moveControls');
- // Semantic dom diff has a problem with just comparing table rows or
- // cells directly. So as a workaround put the row into an empty test
- // table.
- const testTable = document.createElement('table');
- testTable.appendChild(row);
- assert.dom.equal(
- testTable,
- /* HTML */ `
- <table>
- <tbody>
- <tr class="gr-diff moveControls movedOut">
- <td class="gr-diff moveControlsLineNumCol"></td>
- <td class="gr-diff sign"></td>
- <td class="gr-diff moveHeader">
- <gr-range-header class="gr-diff" icon="move_item">
- <div class="gr-diff">
- <span class="gr-diff"> Moved to lines </span>
- <a class="gr-diff" href="#1"> 1 </a>
- <span class="gr-diff"> - </span>
- <a class="gr-diff" href="#2"> 2 </a>
- </div>
- </gr-range-header>
- </td>
- <td class="gr-diff moveControlsLineNumCol"></td>
- <td class="gr-diff sign"></td>
- <td class="gr-diff"></td>
- </tr>
- </tbody>
- </table>
- `,
- {}
- );
- });
-
- test('unified', async () => {
- element.renderPrefs = {
- ...element.renderPrefs,
- view_mode: DiffViewMode.UNIFIED,
- };
- const row = await waitQueryAndAssert(element, 'tr.moveControls');
- // Semantic dom diff has a problem with just comparing table rows or
- // cells directly. So as a workaround put the row into an empty test
- // table.
- const testTable = document.createElement('table');
- testTable.appendChild(row);
- assert.dom.equal(
- testTable,
- /* HTML */ `
- <table>
- <tbody>
- <tr class="gr-diff moveControls movedOut">
- <td class="gr-diff moveControlsLineNumCol"></td>
- <td class="gr-diff moveControlsLineNumCol"></td>
- <td class="gr-diff moveHeader">
- <gr-range-header class="gr-diff" icon="move_item">
- <div class="gr-diff">
- <span class="gr-diff"> Moved to lines </span>
- <a class="gr-diff" href="#1"> 1 </a>
- <span class="gr-diff"> - </span>
- <a class="gr-diff" href="#2"> 2 </a>
- </div>
- </gr-range-header>
- </td>
- </tr>
- </tbody>
- </table>
- `,
- {}
- );
- });
- });
-
- test('3 normal unchanged rows', async () => {
- const lines = [
- new GrDiffLine(GrDiffLineType.BOTH, 1, 1),
- new GrDiffLine(GrDiffLineType.BOTH, 1, 1),
- new GrDiffLine(GrDiffLineType.BOTH, 1, 1),
- ];
- lines[0].text = 'asdf';
- lines[1].text = 'qwer';
- lines[2].text = 'zxcv';
- const group = new GrDiffGroup({type: GrDiffGroupType.BOTH, lines});
- element.group = group;
- await element.updateComplete;
- assert.lightDom.equal(
- element,
- /* HTML */ `
- <gr-diff-row class="left-1 right-1"> </gr-diff-row>
- <slot name="post-left-line-1"></slot>
- <slot name="post-right-line-1"></slot>
- <gr-diff-row class="left-1 right-1"> </gr-diff-row>
- <slot name="post-left-line-1"></slot>
- <slot name="post-right-line-1"></slot>
- <gr-diff-row class="left-1 right-1"> </gr-diff-row>
- <slot name="post-left-line-1"></slot>
- <slot name="post-right-line-1"></slot>
- <table>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-1 left-content-1 right-button-1 right-content-1"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="1"></td>
- <td class="gr-diff left lineNum" data-value="1">
- <button
- aria-label="1 unmodified"
- class="gr-diff left lineNumButton"
- data-value="1"
- id="left-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-1"
- >
- <gr-diff-text> </gr-diff-text>
- </div>
- <div class="thread-group" data-side="left">
- <slot name="left-1"> </slot>
- </div>
- </td>
- <td class="gr-diff lineNum right" data-value="1">
- <button
- aria-label="1 unmodified"
- class="gr-diff lineNumButton right"
- data-value="1"
- id="right-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-1"
- >
- <gr-diff-text> </gr-diff-text>
- </div>
- <div class="thread-group" data-side="right">
- <slot name="right-1"> </slot>
- </div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-1 left-content-1 right-button-1 right-content-1"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="1"></td>
- <td class="gr-diff left lineNum" data-value="1">
- <button
- aria-label="1 unmodified"
- class="gr-diff left lineNumButton"
- data-value="1"
- id="left-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-1"
- >
- <gr-diff-text> </gr-diff-text>
- </div>
- <div class="thread-group" data-side="left">
- <slot name="left-1"> </slot>
- </div>
- </td>
- <td class="gr-diff lineNum right" data-value="1">
- <button
- aria-label="1 unmodified"
- class="gr-diff lineNumButton right"
- data-value="1"
- id="right-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-1"
- >
- <gr-diff-text> </gr-diff-text>
- </div>
- <div class="thread-group" data-side="right">
- <slot name="right-1"> </slot>
- </div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-1 left-content-1 right-button-1 right-content-1"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="1"></td>
- <td class="gr-diff left lineNum" data-value="1">
- <button
- aria-label="1 unmodified"
- class="gr-diff left lineNumButton"
- data-value="1"
- id="left-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-1"
- >
- <gr-diff-text> </gr-diff-text>
- </div>
- <div class="thread-group" data-side="left">
- <slot name="left-1"> </slot>
- </div>
- </td>
- <td class="gr-diff lineNum right" data-value="1">
- <button
- aria-label="1 unmodified"
- class="gr-diff lineNumButton right"
- data-value="1"
- id="right-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-1"
- >
- <gr-diff-text> </gr-diff-text>
- </div>
- <div class="thread-group" data-side="right">
- <slot name="right-1"> </slot>
- </div>
- </td>
- </tr>
- </tbody>
- </table>
- `
- );
- });
-});
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-text.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-text.ts
deleted file mode 100644
index 3878402..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-text.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {LitElement, html, TemplateResult} from 'lit';
-import {property} from 'lit/decorators.js';
-import {styleMap} from 'lit/directives/style-map.js';
-import {diffClasses, isNewDiff} from '../../diff/gr-diff/gr-diff-utils';
-
-const SURROGATE_PAIR = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
-
-const TAB = '\t';
-
-/**
- * Renders one line of code on one side of the diff. It takes care of:
- * - Tabs, see `tabSize` property.
- * - Line Breaks, see `lineLimit` property.
- * - Surrogate Character Pairs.
- *
- * Note that other modifications to the code in a gr-diff is done via diff
- * layers, which manipulate the DOM directly. So `gr-diff-text` is thrown
- * away and re-rendered every time something changes by its parent
- * `gr-diff-row`. So don't bother to optimize this component for re-rendering
- * performance. And be aware that building longer lived local state is not
- * useful here.
- */
-export class GrDiffText extends LitElement {
- /**
- * The browser API for handling selection does not (yet) work for selection
- * across multiple shadow DOM elements. So we are rendering gr-diff components
- * into the light DOM instead of the shadow DOM by overriding this method,
- * which was the recommended workaround by the lit team.
- * See also https://github.com/WICG/webcomponents/issues/79.
- */
- override createRenderRoot() {
- return this;
- }
-
- @property({type: String})
- text = '';
-
- @property({type: Boolean})
- isResponsive = false;
-
- @property({type: Number})
- tabSize = 2;
-
- @property({type: Number})
- lineLimit = 80;
-
- /** Temporary state while rendering. */
- private textOffset = 0;
-
- /** Temporary state while rendering. */
- private columnPos = 0;
-
- /** Temporary state while rendering. */
- private pieces: (string | TemplateResult)[] = [];
-
- /** Split up the string into tabs, surrogate pairs and regular segments. */
- override render() {
- this.textOffset = 0;
- this.columnPos = 0;
- this.pieces = [];
- const splitByTab = this.text.split('\t');
- for (let i = 0; i < splitByTab.length; i++) {
- const splitBySurrogate = splitByTab[i].split(SURROGATE_PAIR);
- for (let j = 0; j < splitBySurrogate.length; j++) {
- this.renderSegment(splitBySurrogate[j]);
- if (j < splitBySurrogate.length - 1) {
- this.renderSurrogatePair();
- }
- }
- if (i < splitByTab.length - 1) {
- this.renderTab();
- }
- }
- if (this.textOffset !== this.text.length) throw new Error('unfinished');
- return this.pieces;
- }
-
- /** Render regular characters, but insert line breaks appropriately. */
- private renderSegment(segment: string) {
- let segmentOffset = 0;
- while (segmentOffset < segment.length) {
- const newOffset = Math.min(
- segment.length,
- segmentOffset + this.lineLimit - this.columnPos
- );
- this.renderString(segment.substring(segmentOffset, newOffset));
- segmentOffset = newOffset;
- if (segmentOffset < segment.length && this.columnPos === this.lineLimit) {
- this.renderLineBreak();
- }
- }
- }
-
- /** Render regular characters. */
- private renderString(s: string) {
- if (s.length === 0) return;
- this.pieces.push(s);
- this.textOffset += s.length;
- this.columnPos += s.length;
- if (this.columnPos > this.lineLimit) throw new Error('over line limit');
- }
-
- /** Render a tab character. */
- private renderTab() {
- let tabSize = this.tabSize - (this.columnPos % this.tabSize);
- if (this.columnPos + tabSize > this.lineLimit) {
- this.renderLineBreak();
- tabSize = this.tabSize;
- }
- const piece = html`<span
- class=${diffClasses('tab')}
- style=${styleMap({'tab-size': `${tabSize}`})}
- >${TAB}</span
- >`;
- this.pieces.push(piece);
- this.textOffset += 1;
- this.columnPos += tabSize;
- }
-
- /** Render a surrogate pair: string length is 2, but is just 1 char. */
- private renderSurrogatePair() {
- if (this.columnPos === this.lineLimit) {
- this.renderLineBreak();
- }
- this.pieces.push(this.text.substring(this.textOffset, this.textOffset + 2));
- this.textOffset += 2;
- this.columnPos += 1;
- }
-
- /** Render a line break, don't advance text offset, reset col position. */
- private renderLineBreak() {
- if (this.isResponsive) {
- this.pieces.push(html`<wbr class=${diffClasses()}></wbr>`);
- } else {
- this.pieces.push(html`<span class=${diffClasses('br')}></span>`);
- }
- // this.textOffset += 0;
- this.columnPos = 0;
- }
-}
-
-if (!isNewDiff()) {
- customElements.define('gr-diff-text', GrDiffText);
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-diff-text': LitElement;
- }
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-text_test.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-text_test.ts
deleted file mode 100644
index 3858bed..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-builder/gr-diff-text_test.ts
+++ /dev/null
@@ -1,166 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup';
-import './gr-diff-text';
-import {GrDiffText} from './gr-diff-text';
-import {fixture, html, assert} from '@open-wc/testing';
-
-const LINE_BREAK = '<span class="gr-diff br"></span>';
-
-const LINE_BREAK_WBR = '<wbr class="gr-diff"></wbr>';
-
-const TAB = '<span class="" style=""></span>';
-
-const TAB_IGNORE = ['class', 'style'];
-
-suite('gr-diff-text test', () => {
- let element: GrDiffText;
-
- setup(async () => {
- element = await fixture<GrDiffText>(
- html`<gr-diff-text tabsize="4" linelimit="10"></gr-diff-text>`
- );
- });
-
- const check = async (
- text: string,
- html: string,
- ignoreAttributes: string[] = []
- ) => {
- element.text = text;
- await element.updateComplete;
- assert.lightDom.equal(element, html, {ignoreAttributes});
- };
-
- suite('lit rendering', () => {
- test('renderText newlines 1', async () => {
- await check('abcdef', 'abcdef');
- await check('a'.repeat(20), `aaaaaaaaaa${LINE_BREAK}aaaaaaaaaa`);
- });
-
- test('renderText newlines 1 responsive', async () => {
- element.isResponsive = true;
- await check('abcdef', 'abcdef');
- await check('a'.repeat(20), `aaaaaaaaaa${LINE_BREAK_WBR}aaaaaaaaaa`);
- });
-
- test('renderText newlines 2', async () => {
- await check(
- '<span class="thumbsup">👍</span>',
- '<span clas' +
- LINE_BREAK +
- 's="thumbsu' +
- LINE_BREAK +
- 'p">👍</span' +
- LINE_BREAK +
- '>'
- );
- });
-
- test('renderText newlines 3', async () => {
- await check(
- '01234\t56789',
- '01234' + TAB + '56' + LINE_BREAK + '789',
- TAB_IGNORE
- );
- });
-
- test('renderText newlines 4', async () => {
- element.lineLimit = 20;
- await element.updateComplete;
- await check(
- '👍'.repeat(58),
- '👍'.repeat(20) +
- LINE_BREAK +
- '👍'.repeat(20) +
- LINE_BREAK +
- '👍'.repeat(18)
- );
- });
-
- test('tab wrapper style', async () => {
- element.lineLimit = 100;
- element.tabSize = 4;
- await check(
- '\t',
- /* HTML */ '<span class="gr-diff tab" style="tab-size:4;"></span>'
- );
- await check(
- 'abc\t',
- /* HTML */ 'abc<span class="gr-diff tab" style="tab-size:1;"></span>'
- );
-
- element.tabSize = 8;
- await check(
- '\t',
- /* HTML */ '<span class="gr-diff tab" style="tab-size:8;"></span>'
- );
- await check(
- 'abc\t',
- /* HTML */ 'abc<span class="gr-diff tab" style="tab-size:5;"></span>'
- );
- });
-
- test('tab wrapper insertion', async () => {
- await check('abc\tdef', 'abc' + TAB + 'def', TAB_IGNORE);
- });
-
- test('escaping HTML', async () => {
- element.lineLimit = 100;
- await element.updateComplete;
- await check(
- '<script>alert("XSS");<' + '/script>',
- '<script>alert("XSS");</script>'
- );
- await check('& < > " \' / `', '& < > " \' / `');
- });
-
- test('text length with tabs and unicode', async () => {
- async function expectTextLength(
- text: string,
- tabSize: number,
- expected: number
- ) {
- element.text = text;
- element.tabSize = tabSize;
- element.lineLimit = expected;
- await element.updateComplete;
- const result = element.innerHTML;
-
- // Must not contain a line break.
- assert.isNotOk(element.querySelector('span.br'));
-
- // Increasing the line limit by 1 should not change anything.
- element.lineLimit = expected + 1;
- await element.updateComplete;
- const resultPlusOne = element.innerHTML;
- assert.equal(resultPlusOne, result);
-
- // Increasing the line limit to infinity should not change anything.
- element.lineLimit = Infinity;
- await element.updateComplete;
- const resultInf = element.innerHTML;
- assert.equal(resultInf, result);
-
- // Decreasing the line limit by 1 should introduce a line break.
- element.lineLimit = expected + 1;
- await element.updateComplete;
- assert.isNotOk(element.querySelector('span.br'));
- }
- expectTextLength('12345', 4, 5);
- expectTextLength('\t\t12', 4, 10);
- expectTextLength('abc💢123', 4, 7);
- expectTextLength('abc\t', 8, 8);
- expectTextLength('abc\t\t', 10, 20);
- expectTextLength('', 10, 0);
- // 17 Thai combining chars.
- expectTextLength('ก้้้้้้้้้้้้้้้้', 4, 17);
- expectTextLength('abc\tde', 10, 12);
- expectTextLength('abc\tde\t', 10, 20);
- expectTextLength('\t\t\t\t\t', 20, 100);
- });
- });
-});
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-cursor/gr-diff-cursor.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-cursor/gr-diff-cursor.ts
deleted file mode 100644
index 3d0e507..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-cursor/gr-diff-cursor.ts
+++ /dev/null
@@ -1,597 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {Subscription} from 'rxjs';
-import {AbortStop, CursorMoveResult, Stop} from '../../../api/core';
-import {
- DiffViewMode,
- GrDiffCursor as GrDiffCursorApi,
- GrDiffLineType,
- LineNumber,
- LineSelectedEventDetail,
-} from '../../../api/diff';
-import {ScrollMode, Side} from '../../../constants/constants';
-import {toggleClass} from '../../../utils/dom-util';
-import {
- GrCursorManager,
- isTargetable,
-} from '../../../elements/shared/gr-cursor-manager/gr-cursor-manager';
-import {GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {GrDiff} from '../gr-diff/gr-diff';
-import {fire} from '../../../utils/event-util';
-
-type GrDiffRowType = GrDiffLineType | GrDiffGroupType;
-
-const LEFT_SIDE_CLASS = 'target-side-left';
-const RIGHT_SIDE_CLASS = 'target-side-right';
-
-interface Address {
- leftSide: boolean;
- number: number;
-}
-
-/**
- * From <tr> diff row go up to <tbody> diff chunk.
- *
- * In Lit based diff there is a <gr-diff-row> element in between the two.
- */
-export function fromRowToChunk(
- rowEl: HTMLElement
-): HTMLTableSectionElement | undefined {
- const parent = rowEl.parentElement;
- if (!parent) return undefined;
- if (parent.tagName === 'TBODY') {
- return parent as HTMLTableSectionElement;
- }
-
- const grandParent = parent.parentElement;
- if (!grandParent) return undefined;
- if (grandParent.tagName === 'TBODY') {
- return grandParent as HTMLTableSectionElement;
- }
-
- return undefined;
-}
-
-/** A subset of the GrDiff API that the cursor is using. */
-export interface GrDiffCursorable extends HTMLElement {
- isRangeSelected(): boolean;
- createRangeComment(): void;
- getCursorStops(): Stop[];
- path?: string;
-}
-
-export class GrDiffCursor implements GrDiffCursorApi {
- private preventAutoScrollOnManualScroll = false;
-
- set side(side: Side) {
- if (this.sideInternal === side) {
- return;
- }
- if (this.sideInternal && this.diffRow) {
- this.fireCursorMoved(
- 'line-cursor-moved-out',
- this.diffRow,
- this.sideInternal
- );
- }
- this.sideInternal = side;
- this.updateSideClass();
- if (this.diffRow) {
- this.fireCursorMoved('line-cursor-moved-in', this.diffRow, this.side);
- }
- }
-
- get side(): Side {
- return this.sideInternal;
- }
-
- private sideInternal = Side.RIGHT;
-
- set diffRow(diffRow: HTMLElement | undefined) {
- if (this.diffRowInternal) {
- this.diffRowInternal.classList.remove(LEFT_SIDE_CLASS, RIGHT_SIDE_CLASS);
- this.fireCursorMoved(
- 'line-cursor-moved-out',
- this.diffRowInternal,
- this.side
- );
- }
- this.diffRowInternal = diffRow;
-
- this.updateSideClass();
- if (this.diffRow) {
- this.fireCursorMoved('line-cursor-moved-in', this.diffRow, this.side);
- }
- }
-
- get diffRow(): HTMLElement | undefined {
- return this.diffRowInternal;
- }
-
- private diffRowInternal?: HTMLElement;
-
- private diffs: GrDiffCursorable[] = [];
-
- /**
- * If set, the cursor will attempt to move to the line number (instead of
- * the first chunk) the next time the diff renders. It is set back to null
- * when used. It should be only used if you want the line to be focused
- * after initialization of the component and page should scroll
- * to that position. This parameter should be set at most for one gr-diff
- * element in the page.
- */
- initialLineNumber: number | null = null;
-
- // visible for testing
- cursorManager = new GrCursorManager();
-
- private targetSubscription?: Subscription;
-
- constructor() {
- this.cursorManager.cursorTargetClass = 'target-row';
- this.cursorManager.scrollMode = ScrollMode.KEEP_VISIBLE;
- this.cursorManager.focusOnMove = true;
-
- window.addEventListener('scroll', this._boundHandleWindowScroll);
- this.targetSubscription = this.cursorManager.target$.subscribe(target => {
- this.diffRow = target || undefined;
- });
- }
-
- dispose() {
- this.cursorManager.unsetCursor();
- if (this.targetSubscription) this.targetSubscription.unsubscribe();
- window.removeEventListener('scroll', this._boundHandleWindowScroll);
- }
-
- // Don't remove - used by clients embedding gr-diff outside of Gerrit.
- isAtStart() {
- return this.cursorManager.isAtStart();
- }
-
- // Don't remove - used by clients embedding gr-diff outside of Gerrit.
- isAtEnd() {
- return this.cursorManager.isAtEnd();
- }
-
- moveLeft() {
- this.side = Side.LEFT;
- if (this._isTargetBlank()) {
- this.moveUp();
- }
- }
-
- moveRight() {
- this.side = Side.RIGHT;
- if (this._isTargetBlank()) {
- this.moveUp();
- }
- }
-
- moveDown() {
- if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) {
- return this.cursorManager.next({
- filter: (row: Element) => this._rowHasSide(row),
- });
- } else {
- return this.cursorManager.next();
- }
- }
-
- moveUp() {
- if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) {
- return this.cursorManager.previous({
- filter: (row: Element) => this._rowHasSide(row),
- });
- } else {
- return this.cursorManager.previous();
- }
- }
-
- moveToVisibleArea() {
- if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) {
- this.cursorManager.moveToVisibleArea((row: Element) =>
- this._rowHasSide(row)
- );
- } else {
- this.cursorManager.moveToVisibleArea();
- }
- }
-
- moveToNextChunk(clipToTop?: boolean): CursorMoveResult {
- const result = this.cursorManager.next({
- filter: (row: HTMLElement) => this._isFirstRowOfChunk(row),
- getTargetHeight: target => fromRowToChunk(target)?.scrollHeight || 0,
- clipToTop,
- });
- this._fixSide();
- return result;
- }
-
- moveToPreviousChunk(): CursorMoveResult {
- const result = this.cursorManager.previous({
- filter: (row: HTMLElement) => this._isFirstRowOfChunk(row),
- });
- this._fixSide();
- return result;
- }
-
- moveToNextCommentThread(): CursorMoveResult {
- if (this.isAtEnd()) {
- return CursorMoveResult.CLIPPED;
- }
- const result = this.cursorManager.next({
- filter: (row: HTMLElement) => this._rowHasThread(row),
- });
- this._fixSide();
- return result;
- }
-
- moveToPreviousCommentThread(): CursorMoveResult {
- const result = this.cursorManager.previous({
- filter: (row: HTMLElement) => this._rowHasThread(row),
- });
- this._fixSide();
- return result;
- }
-
- moveToLineNumber(
- number: LineNumber,
- side: Side,
- path?: string,
- intentionalMove?: boolean
- ) {
- const row = this._findRowByNumberAndFile(number, side, path);
- if (row) {
- this.side = side;
- this.cursorManager.setCursor(row, undefined, intentionalMove);
- }
- }
-
- /**
- * Get the line number element targeted by the cursor row and side.
- */
- getTargetLineElement(): HTMLElement | null {
- let lineElSelector = '.lineNum';
-
- if (!this.diffRow) {
- return null;
- }
-
- if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) {
- lineElSelector += this.side === Side.LEFT ? '.left' : '.right';
- }
-
- return this.diffRow.querySelector(lineElSelector);
- }
-
- getTargetDiffElement(): GrDiff | null {
- if (!this.diffRow) return null;
-
- const hostOwner = this.diffRow.getRootNode() as ShadowRoot;
- if (hostOwner?.host?.tagName === 'GR-DIFF') {
- return hostOwner.host as GrDiff;
- }
- return null;
- }
-
- moveToFirstChunk() {
- this.cursorManager.moveToStart();
- if (this.diffRow && !this._isFirstRowOfChunk(this.diffRow)) {
- this.moveToNextChunk(true);
- } else {
- this._fixSide();
- }
- }
-
- moveToLastChunk() {
- this.cursorManager.moveToEnd();
- if (this.diffRow && !this._isFirstRowOfChunk(this.diffRow)) {
- this.moveToPreviousChunk();
- } else {
- this._fixSide();
- }
- }
-
- /**
- * Move the cursor either to initialLineNumber or the first chunk and
- * reset scroll behavior.
- *
- * This may grab the focus from the app.
- *
- * If you do not want to move the cursor or grab focus, and just want to
- * reset the scroll behavior, use reInitAndUpdateStops() instead.
- */
- reInitCursor() {
- this._updateStops();
- if (!this.diffRow) {
- // does not scroll during init unless requested
- this.cursorManager.scrollMode = this.initialLineNumber
- ? ScrollMode.KEEP_VISIBLE
- : ScrollMode.NEVER;
- if (this.initialLineNumber) {
- this.moveToLineNumber(this.initialLineNumber, this.side);
- this.initialLineNumber = null;
- } else {
- this.moveToFirstChunk();
- }
- }
- this.resetScrollMode();
- }
-
- resetScrollMode() {
- this.cursorManager.scrollMode = ScrollMode.KEEP_VISIBLE;
- }
-
- private _boundHandleWindowScroll = () => {
- if (this.preventAutoScrollOnManualScroll) {
- this.cursorManager.scrollMode = ScrollMode.NEVER;
- this.cursorManager.focusOnMove = false;
- this.preventAutoScrollOnManualScroll = false;
- }
- };
-
- reInitAndUpdateStops() {
- this.resetScrollMode();
- this._updateStops();
- }
-
- private boundHandleDiffLoadingChanged = () => {
- this._updateStops();
- };
-
- private _boundHandleDiffRenderStart = () => {
- this.preventAutoScrollOnManualScroll = true;
- };
-
- private _boundHandleDiffRenderContent = () => {
- this._updateStops();
- // When done rendering, turn focus on move and automatic scrolling back on
- this.cursorManager.focusOnMove = true;
- this.preventAutoScrollOnManualScroll = false;
- };
-
- private _boundHandleDiffLineSelected = (
- e: CustomEvent<LineSelectedEventDetail>
- ) => {
- this.moveToLineNumber(e.detail.number, e.detail.side, e.detail.path);
- };
-
- createCommentInPlace() {
- const diffWithRangeSelected = this.diffs.find(diff =>
- diff.isRangeSelected()
- );
- if (diffWithRangeSelected) {
- diffWithRangeSelected.createRangeComment();
- } else {
- const line = this.getTargetLineElement();
- const diff = this.getTargetDiffElement();
- if (diff && line) {
- diff.addDraftAtLine(line);
- }
- }
- }
-
- getTargetLineNumber(): LineNumber | undefined {
- return this.getAddress()?.number;
- }
-
- /**
- * Get an object describing the location of the cursor. Such as
- * {leftSide: false, number: 123} for line 123 of the revision, or
- * {leftSide: true, number: 321} for line 321 of the base patch.
- * Returns null if an address is not available.
- */
- getAddress(): Address | null {
- if (!this.diffRow) {
- return null;
- }
- // Get the line-number cell targeted by the cursor. If the mode is unified
- // then prefer the revision cell if available.
- return this.getAddressFor(this.diffRow, this.side);
- }
-
- private getAddressFor(diffRow: HTMLElement, side: Side): Address | null {
- let cell;
- if (this._getViewMode() === DiffViewMode.UNIFIED) {
- cell = diffRow.querySelector('.lineNum.right');
- if (!cell) {
- cell = diffRow.querySelector('.lineNum.left');
- }
- } else {
- cell = diffRow.querySelector('.lineNum.' + side);
- }
- if (!cell) {
- return null;
- }
-
- const number = cell.getAttribute('data-value');
- if (!number || number === 'FILE') {
- return null;
- }
-
- return {
- leftSide: cell.matches('.left'),
- number: Number(number),
- };
- }
-
- _getViewMode() {
- if (!this.diffRow) {
- return null;
- }
-
- if (this.diffRow.classList.contains('side-by-side')) {
- return DiffViewMode.SIDE_BY_SIDE;
- } else {
- return DiffViewMode.UNIFIED;
- }
- }
-
- _rowHasSide(row: Element) {
- const selector =
- (this.side === Side.LEFT ? '.left' : '.right') + ' + .content';
- return !!row.querySelector(selector);
- }
-
- _isFirstRowOfChunk(row: HTMLElement) {
- const chunk = fromRowToChunk(row);
- if (!chunk) return false;
-
- const isInDeltaChunk = chunk.classList.contains('delta');
- if (!isInDeltaChunk) return false;
-
- const firstRow = chunk.querySelector('tr:not(.moveControls)');
- return firstRow === row;
- }
-
- _rowHasThread(row: HTMLElement): boolean {
- const slots = [
- ...row.querySelectorAll<HTMLSlotElement>('.thread-group > slot'),
- ];
- return slots.some(slot => slot.assignedElements().length > 0);
- }
-
- /**
- * If we jumped to a row where there is no content on the current side then
- * switch to the alternate side.
- */
- _fixSide() {
- if (
- this._getViewMode() === DiffViewMode.SIDE_BY_SIDE &&
- this._isTargetBlank()
- ) {
- this.side = this.side === Side.LEFT ? Side.RIGHT : Side.LEFT;
- }
- }
-
- _isTargetBlank() {
- if (!this.diffRow) {
- return false;
- }
-
- const actions = this._getActionsForRow();
- return (
- (this.side === Side.LEFT && !actions.left) ||
- (this.side === Side.RIGHT && !actions.right)
- );
- }
-
- private fireCursorMoved(
- event: 'line-cursor-moved-out' | 'line-cursor-moved-in',
- row: HTMLElement,
- side: Side
- ) {
- const address = this.getAddressFor(row, side);
- if (address) {
- const {leftSide, number} = address;
- fire(row, event, {
- lineNum: number,
- side: leftSide ? Side.LEFT : Side.RIGHT,
- });
- }
- }
-
- private updateSideClass() {
- if (!this.diffRow) {
- return;
- }
- toggleClass(this.diffRow, LEFT_SIDE_CLASS, this.side === Side.LEFT);
- toggleClass(this.diffRow, RIGHT_SIDE_CLASS, this.side === Side.RIGHT);
- }
-
- _isActionType(type: GrDiffRowType) {
- return (
- type !== GrDiffLineType.BLANK && type !== GrDiffGroupType.CONTEXT_CONTROL
- );
- }
-
- _getActionsForRow() {
- const actions = {left: false, right: false};
- if (this.diffRow) {
- actions.left = this._isActionType(
- this.diffRow.getAttribute('left-type') as GrDiffRowType
- );
- actions.right = this._isActionType(
- this.diffRow.getAttribute('right-type') as GrDiffRowType
- );
- }
- return actions;
- }
-
- _updateStops() {
- this.cursorManager.stops = this.diffs.reduce(
- (stops: Stop[], diff) => stops.concat(diff.getCursorStops()),
- []
- );
- }
-
- replaceDiffs(diffs: GrDiffCursorable[]) {
- for (const diff of this.diffs) {
- this.removeEventListeners(diff);
- }
- this.diffs = [];
- for (const diff of diffs) {
- this.addEventListeners(diff);
- }
- this.diffs.push(...diffs);
- this._updateStops();
- }
-
- unregisterDiff(diff: GrDiffCursorable) {
- // This can happen during destruction - just don't unregister then.
- if (!this.diffs) return;
- const i = this.diffs.indexOf(diff);
- if (i !== -1) {
- this.diffs.splice(i, 1);
- }
- }
-
- private removeEventListeners(diff: GrDiffCursorable) {
- diff.removeEventListener(
- 'loading-changed',
- this.boundHandleDiffLoadingChanged
- );
- diff.removeEventListener('render-start', this._boundHandleDiffRenderStart);
- diff.removeEventListener(
- 'render-content',
- this._boundHandleDiffRenderContent
- );
- diff.removeEventListener(
- 'line-selected',
- this._boundHandleDiffLineSelected
- );
- }
-
- private addEventListeners(diff: GrDiffCursorable) {
- diff.addEventListener(
- 'loading-changed',
- this.boundHandleDiffLoadingChanged
- );
- diff.addEventListener('render-start', this._boundHandleDiffRenderStart);
- diff.addEventListener('render-content', this._boundHandleDiffRenderContent);
- diff.addEventListener('line-selected', this._boundHandleDiffLineSelected);
- }
-
- _findRowByNumberAndFile(
- targetNumber: LineNumber,
- side: Side,
- path?: string
- ): HTMLElement | undefined {
- let stops: Array<HTMLElement | AbortStop>;
- if (path) {
- const diff = this.diffs.filter(diff => diff.path === path)[0];
- stops = diff.getCursorStops();
- } else {
- stops = this.cursorManager.stops;
- }
- // Sadly needed for type narrowing to understand that the result is always
- // targetable.
- const targetableStops: HTMLElement[] = stops.filter(isTargetable);
- const selector = `.lineNum.${side}[data-value="${targetNumber}"]`;
- return targetableStops.find(stop => stop.querySelector(selector));
- }
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-cursor/gr-diff-cursor_test.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-cursor/gr-diff-cursor_test.ts
deleted file mode 100644
index 61f8551..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-cursor/gr-diff-cursor_test.ts
+++ /dev/null
@@ -1,694 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup';
-import '../gr-diff/gr-diff';
-import './gr-diff-cursor';
-import {fixture, html, assert} from '@open-wc/testing';
-import {
- mockPromise,
- queryAll,
- queryAndAssert,
- waitUntil,
-} from '../../../test/test-utils';
-import {createDiff} from '../../../test/test-data-generators';
-import {createDefaultDiffPrefs} from '../../../constants/constants';
-import {GrDiffCursor} from './gr-diff-cursor';
-import {waitForEventOnce} from '../../../utils/event-util';
-import {DiffInfo, DiffViewMode, Side} from '../../../api/diff';
-import {GrDiff} from '../gr-diff/gr-diff';
-import {assertIsDefined} from '../../../utils/common-util';
-
-suite('gr-diff-cursor tests', () => {
- let cursor: GrDiffCursor;
- let diffElement: GrDiff;
- let diff: DiffInfo;
-
- setup(async () => {
- diffElement = await fixture(html`<gr-diff></gr-diff>`);
- cursor = new GrDiffCursor();
-
- // Register the diff with the cursor.
- cursor.replaceDiffs([diffElement]);
-
- diffElement.loggedIn = false;
- diffElement.path = 'some/path.ts';
- const promise = mockPromise();
- const setupDone = () => {
- cursor._updateStops();
- cursor.moveToFirstChunk();
- diffElement.removeEventListener('render', setupDone);
- promise.resolve();
- };
- diffElement.addEventListener('render', setupDone);
-
- diff = createDiff();
- diffElement.prefs = createDefaultDiffPrefs();
- diffElement.diff = diff;
- await promise;
- });
-
- test('diff cursor functionality (side-by-side)', () => {
- assert.isOk(cursor.diffRow);
-
- const deltaRows = queryAll<HTMLTableRowElement>(
- diffElement,
- '.section.delta tr.diff-row'
- );
- assert.equal(cursor.diffRow, deltaRows[0]);
-
- cursor.moveDown();
-
- assert.notEqual(cursor.diffRow, deltaRows[0]);
- assert.equal(cursor.diffRow, deltaRows[1]);
-
- cursor.moveUp();
-
- assert.notEqual(cursor.diffRow, deltaRows[1]);
- assert.equal(cursor.diffRow, deltaRows[0]);
- });
-
- test('moveToFirstChunk', async () => {
- const diff: DiffInfo = {
- meta_a: {
- name: 'lorem-ipsum.txt',
- content_type: 'text/plain',
- lines: 3,
- },
- meta_b: {
- name: 'lorem-ipsum.txt',
- content_type: 'text/plain',
- lines: 3,
- },
- intraline_status: 'OK',
- change_type: 'MODIFIED',
- diff_header: [
- 'diff --git a/lorem-ipsum.txt b/lorem-ipsum.txt',
- 'index b2adcf4..554ae49 100644',
- '--- a/lorem-ipsum.txt',
- '+++ b/lorem-ipsum.txt',
- ],
- content: [
- {b: ['new line 1']},
- {ab: ['unchanged line']},
- {a: ['old line 2']},
- {ab: ['more unchanged lines']},
- ],
- };
-
- diffElement.diff = diff;
- // The file comment button, if present, is a cursor stop. Ensure
- // moveToFirstChunk() works correctly even if the button is not shown.
- diffElement.prefs!.show_file_comment_button = false;
- await waitForEventOnce(diffElement, 'render');
-
- cursor._updateStops();
-
- const chunks = [
- ...queryAll(diffElement, '.section.delta'),
- ] as HTMLElement[];
- assert.equal(chunks.length, 2);
-
- const rows = [
- ...queryAll(diffElement, '.section.delta tr.diff-row'),
- ] as HTMLTableRowElement[];
- assert.equal(rows.length, 2);
-
- // Verify it works on fresh diff.
- cursor.moveToFirstChunk();
- assert.ok(cursor.diffRow);
- assert.equal(cursor.diffRow, rows[0]);
- assert.equal(cursor.side, Side.RIGHT);
-
- // Verify it works from other cursor positions.
- cursor.moveToNextChunk();
- assert.ok(cursor.diffRow);
- assert.equal(cursor.diffRow, rows[1]);
- assert.equal(cursor.side, Side.LEFT);
-
- cursor.moveToFirstChunk();
- assert.ok(cursor.diffRow);
- assert.equal(cursor.diffRow, rows[0]);
- assert.equal(cursor.side, Side.RIGHT);
- });
-
- test('moveToLastChunk', async () => {
- const diff: DiffInfo = {
- meta_a: {
- name: 'lorem-ipsum.txt',
- content_type: 'text/plain',
- lines: 3,
- },
- meta_b: {
- name: 'lorem-ipsum.txt',
- content_type: 'text/plain',
- lines: 3,
- },
- intraline_status: 'OK',
- change_type: 'MODIFIED',
- diff_header: [
- 'diff --git a/lorem-ipsum.txt b/lorem-ipsum.txt',
- 'index b2adcf4..554ae49 100644',
- '--- a/lorem-ipsum.txt',
- '+++ b/lorem-ipsum.txt',
- ],
- content: [
- {ab: ['unchanged line']},
- {a: ['old line 2']},
- {ab: ['more unchanged lines']},
- {b: ['new line 3']},
- ],
- };
-
- diffElement.diff = diff;
- await waitForEventOnce(diffElement, 'render');
- cursor._updateStops();
-
- const chunks = [
- ...queryAll(diffElement, '.section.delta'),
- ] as HTMLElement[];
- assert.equal(chunks.length, 2);
-
- const rows = [
- ...queryAll(diffElement, '.section.delta tr.diff-row'),
- ] as HTMLTableRowElement[];
- assert.equal(rows.length, 2);
-
- // Verify it works on fresh diff.
- cursor.moveToLastChunk();
- assert.ok(cursor.diffRow);
- assert.equal(cursor.diffRow, rows[1]);
- assert.equal(cursor.side, Side.RIGHT);
-
- // Verify it works from other cursor positions.
- cursor.moveToPreviousChunk();
- assert.ok(cursor.diffRow);
- assert.equal(cursor.diffRow, rows[0]);
- assert.equal(cursor.side, Side.LEFT);
-
- cursor.moveToLastChunk();
- assert.ok(cursor.diffRow);
- assert.equal(cursor.diffRow, rows[1]);
- assert.equal(cursor.side, Side.RIGHT);
- });
-
- test('cursor scroll behavior', () => {
- assert.equal(cursor.cursorManager.scrollMode, 'keep-visible');
-
- diffElement.dispatchEvent(new Event('render-start'));
- assert.isTrue(cursor.cursorManager.focusOnMove);
-
- window.dispatchEvent(new Event('scroll'));
- assert.equal(cursor.cursorManager.scrollMode, 'never');
- assert.isFalse(cursor.cursorManager.focusOnMove);
-
- diffElement.dispatchEvent(new Event('render-content'));
- assert.isTrue(cursor.cursorManager.focusOnMove);
-
- cursor.reInitCursor();
- assert.equal(cursor.cursorManager.scrollMode, 'keep-visible');
- });
-
- test('moves to selected line', () => {
- const moveToNumStub = sinon.stub(cursor, 'moveToLineNumber');
-
- diffElement.dispatchEvent(
- new CustomEvent('line-selected', {
- detail: {number: '123', side: Side.RIGHT, path: 'some/file'},
- })
- );
-
- assert.isTrue(moveToNumStub.called);
- assert.equal(moveToNumStub.lastCall.args[0], 123);
- assert.equal(moveToNumStub.lastCall.args[1], Side.RIGHT);
- assert.equal(moveToNumStub.lastCall.args[2], 'some/file');
- });
-
- suite('unified diff', () => {
- setup(async () => {
- diffElement.viewMode = DiffViewMode.UNIFIED;
- await waitForEventOnce(diffElement, 'render');
- cursor.reInitCursor();
- });
-
- test('diff cursor functionality (unified)', () => {
- assert.isOk(cursor.diffRow);
-
- const rows = [
- ...queryAll(diffElement, '.section.delta tr.diff-row'),
- ] as HTMLTableRowElement[];
- assert.equal(cursor.diffRow, rows[0]);
-
- cursor.moveDown();
-
- assert.notEqual(cursor.diffRow, rows[0]);
- assert.equal(cursor.diffRow, rows[1]);
-
- cursor.moveUp();
-
- assert.notEqual(cursor.diffRow, rows[1]);
- assert.equal(cursor.diffRow, rows[0]);
- });
- });
-
- test('cursor side functionality', () => {
- // The side only applies to side-by-side mode, which should be the default
- // mode.
- assert.equal(diffElement.viewMode, 'SIDE_BY_SIDE');
-
- const rows = [
- ...queryAll(diffElement, '.section tr.diff-row'),
- ] as HTMLTableRowElement[];
- assert.equal(rows.length, 50);
- const deltaRows = [
- ...queryAll(diffElement, '.section.delta tr.diff-row'),
- ] as HTMLTableRowElement[];
- assert.equal(deltaRows.length, 14);
- const indexFirstDelta = rows.indexOf(deltaRows[0]);
- const rowBeforeFirstDelta = rows[indexFirstDelta - 1];
-
- // Because the first delta in this diff is on the right, it should be set
- // to the right side.
- assert.equal(cursor.side, Side.RIGHT);
- assert.equal(cursor.diffRow, deltaRows[0]);
- const firstIndex = cursor.cursorManager.index;
-
- // Move the side to the left. Because this delta only has a right side, we
- // should be moved up to the previous line where there is content on the
- // right. The previous row is part of the previous section.
- cursor.moveLeft();
-
- assert.equal(cursor.side, Side.LEFT);
- assert.notEqual(cursor.diffRow, rows[0]);
- assert.equal(cursor.diffRow, rowBeforeFirstDelta);
- assert.equal(cursor.cursorManager.index, firstIndex - 1);
-
- // If we move down, we should skip everything in the first delta because
- // we are on the left side and the first delta has no content on the left.
- cursor.moveDown();
-
- assert.equal(cursor.side, Side.LEFT);
- assert.notEqual(cursor.diffRow, rowBeforeFirstDelta);
- assert.notEqual(cursor.diffRow, rows[0]);
- assert.isTrue(cursor.cursorManager.index > firstIndex);
- });
-
- test('chunk skip functionality', () => {
- const deltaChunks = [...queryAll(diffElement, 'tbody.section.delta')];
-
- // We should be initialized to the first chunk. Since this chunk only has
- // content on the right side, our side should be right.
- assert.equal(cursor.diffRow, deltaChunks[0].querySelector('tr'));
- assert.equal(cursor.side, Side.RIGHT);
-
- // Move to the next chunk.
- cursor.moveToNextChunk();
-
- // Since this chunk only has content on the left side. we should have been
- // automatically moved over.
- assert.equal(cursor.diffRow, deltaChunks[1].querySelector('tr'));
- assert.equal(cursor.side, Side.LEFT);
- });
-
- suite('moved chunks without line range)', () => {
- setup(async () => {
- const promise = mockPromise();
- const renderHandler = function () {
- diffElement.removeEventListener('render', renderHandler);
- cursor.reInitCursor();
- promise.resolve();
- };
- diffElement.addEventListener('render', renderHandler);
- diffElement.diff = {
- ...diff,
- content: [
- {
- ab: ['Lorem ipsum dolor sit amet, suspendisse inceptos vehicula, '],
- },
- {
- b: [
- 'Nullam neque, ligula ac, id blandit.',
- 'Sagittis tincidunt torquent, tempor nunc amet.',
- 'At rhoncus id.',
- ],
- move_details: {changed: false},
- },
- {
- ab: ['Sem nascetur, erat ut, non in.'],
- },
- {
- a: [
- 'Nullam neque, ligula ac, id blandit.',
- 'Sagittis tincidunt torquent, tempor nunc amet.',
- 'At rhoncus id.',
- ],
- move_details: {changed: false},
- },
- {
- ab: ['Arcu eget, rhoncus amet cursus, ipsum elementum.'],
- },
- ],
- };
- await promise;
- });
-
- test('renders moveControls with simple descriptions', () => {
- const [movedIn, movedOut] = [
- ...queryAll<HTMLElement>(diffElement, '.dueToMove tr.moveControls'),
- ];
- assert.include(movedIn.innerText, 'Moved in');
- assert.include(movedOut.innerText, 'Moved out');
- });
- });
-
- suite('moved chunks (moveDetails)', () => {
- setup(async () => {
- const promise = mockPromise();
- const renderHandler = function () {
- diffElement.removeEventListener('render', renderHandler);
- cursor.reInitCursor();
- promise.resolve();
- };
- diffElement.addEventListener('render', renderHandler);
- diffElement.diff = {
- ...diff,
- content: [
- {
- ab: ['Lorem ipsum dolor sit amet, suspendisse inceptos vehicula, '],
- },
- {
- b: [
- 'Nullam neque, ligula ac, id blandit.',
- 'Sagittis tincidunt torquent, tempor nunc amet.',
- 'At rhoncus id.',
- ],
- move_details: {changed: false, range: {start: 4, end: 6}},
- },
- {
- ab: ['Sem nascetur, erat ut, non in.'],
- },
- {
- a: [
- 'Nullam neque, ligula ac, id blandit.',
- 'Sagittis tincidunt torquent, tempor nunc amet.',
- 'At rhoncus id.',
- ],
- move_details: {changed: false, range: {start: 2, end: 4}},
- },
- {
- ab: ['Arcu eget, rhoncus amet cursus, ipsum elementum.'],
- },
- ],
- };
- await promise;
- });
-
- test('renders moveControls with simple descriptions', () => {
- const [movedIn, movedOut] = [
- ...queryAll<HTMLElement>(diffElement, '.dueToMove tr.moveControls'),
- ];
- assert.include(movedIn.innerText, 'Moved from lines 4 - 6');
- assert.include(movedOut.innerText, 'Moved to lines 2 - 4');
- });
-
- test('startLineAnchor of movedIn chunk fires events', async () => {
- const [movedIn] = [...queryAll(diffElement, '.dueToMove .moveControls')];
- const [startLineAnchor] = movedIn.querySelectorAll('a');
-
- const promise = mockPromise();
- const onMovedLinkClicked = (e: CustomEvent) => {
- assert.deepEqual(e.detail, {lineNum: 4, side: Side.LEFT});
- promise.resolve();
- };
- assert.equal(startLineAnchor.textContent, '4');
- startLineAnchor.addEventListener(
- 'moved-link-clicked',
- onMovedLinkClicked
- );
- startLineAnchor.click();
- await promise;
- });
-
- test('endLineAnchor of movedOut fires events', async () => {
- const [, movedOut] = [
- ...queryAll(diffElement, '.dueToMove .moveControls'),
- ];
- const [, endLineAnchor] = movedOut.querySelectorAll('a');
-
- const promise = mockPromise();
- const onMovedLinkClicked = (e: CustomEvent) => {
- assert.deepEqual(e.detail, {lineNum: 4, side: Side.RIGHT});
- promise.resolve();
- };
- assert.equal(endLineAnchor.textContent, '4');
- endLineAnchor.addEventListener('moved-link-clicked', onMovedLinkClicked);
- endLineAnchor.click();
- await promise;
- });
- });
-
- test('initialLineNumber not provided', async () => {
- let scrollBehaviorDuringMove;
- const moveToNumStub = sinon.stub(cursor, 'moveToLineNumber');
- const moveToChunkStub = sinon
- .stub(cursor, 'moveToFirstChunk')
- .callsFake(() => {
- scrollBehaviorDuringMove = cursor.cursorManager.scrollMode;
- });
- diffElement.diff = createDiff();
- await diffElement.updateComplete;
- await waitForEventOnce(diffElement, 'render');
- cursor.reInitCursor();
- assert.isFalse(moveToNumStub.called);
- assert.isTrue(moveToChunkStub.called);
- assert.equal(scrollBehaviorDuringMove, 'never');
- assert.equal(cursor.cursorManager.scrollMode, 'keep-visible');
- });
-
- test('initialLineNumber provided', async () => {
- let scrollBehaviorDuringMove;
- const moveToNumStub = sinon
- .stub(cursor, 'moveToLineNumber')
- .callsFake(() => {
- scrollBehaviorDuringMove = cursor.cursorManager.scrollMode;
- });
- const moveToChunkStub = sinon.stub(cursor, 'moveToFirstChunk');
- cursor.initialLineNumber = 10;
- cursor.side = Side.RIGHT;
-
- diffElement.diff = createDiff();
- await diffElement.updateComplete;
- await waitForEventOnce(diffElement, 'render');
- cursor.reInitCursor();
- assert.isFalse(moveToChunkStub.called);
- assert.isTrue(moveToNumStub.called);
- assert.equal(moveToNumStub.lastCall.args[0], 10);
- assert.equal(moveToNumStub.lastCall.args[1], Side.RIGHT);
- assert.equal(scrollBehaviorDuringMove, 'keep-visible');
- assert.equal(cursor.cursorManager.scrollMode, 'keep-visible');
- });
-
- test('getTargetDiffElement', () => {
- cursor.initialLineNumber = 1;
- assert.isTrue(!!cursor.diffRow);
- assert.equal(cursor.getTargetDiffElement(), diffElement);
- });
-
- suite('createCommentInPlace', () => {
- setup(() => {
- diffElement.loggedIn = true;
- });
-
- test('adds new draft for selected line on the left', async () => {
- cursor.moveToLineNumber(2, Side.LEFT);
- const promise = mockPromise();
- diffElement.addEventListener('create-comment', e => {
- const {lineNum, range, side} = e.detail;
- assert.equal(lineNum, 2);
- assert.equal(range, undefined);
- assert.equal(side, Side.LEFT);
- promise.resolve();
- });
- cursor.createCommentInPlace();
- await promise;
- });
-
- test('adds draft for selected line on the right', async () => {
- cursor.moveToLineNumber(4, Side.RIGHT);
- const promise = mockPromise();
- diffElement.addEventListener('create-comment', e => {
- const {lineNum, range, side} = e.detail;
- assert.equal(lineNum, 4);
- assert.equal(range, undefined);
- assert.equal(side, Side.RIGHT);
- promise.resolve();
- });
- cursor.createCommentInPlace();
- await promise;
- });
-
- test('creates comment for range if selected', async () => {
- const someRange = {
- start_line: 2,
- start_character: 3,
- end_line: 6,
- end_character: 1,
- };
- diffElement.highlights.selectedRange = {
- side: Side.RIGHT,
- range: someRange,
- };
- const promise = mockPromise();
- diffElement.addEventListener('create-comment', e => {
- const {lineNum, range, side} = e.detail;
- assert.equal(lineNum, 6);
- assert.equal(range, someRange);
- assert.equal(side, Side.RIGHT);
- promise.resolve();
- });
- cursor.createCommentInPlace();
- await promise;
- });
-
- test('ignores call if nothing is selected', () => {
- const createRangeCommentStub = sinon.stub(
- diffElement,
- 'createRangeComment'
- );
- const addDraftAtLineStub = sinon.stub(diffElement, 'addDraftAtLine');
- cursor.diffRow = undefined;
- cursor.createCommentInPlace();
- assert.isFalse(createRangeCommentStub.called);
- assert.isFalse(addDraftAtLineStub.called);
- });
- });
-
- test('getAddress', () => {
- // It should initialize to the first chunk: line 5 of the revision.
- assert.deepEqual(cursor.getAddress(), {leftSide: false, number: 5});
-
- // Revision line 4 is up.
- cursor.moveUp();
- assert.deepEqual(cursor.getAddress(), {leftSide: false, number: 4});
-
- // Base line 4 is left.
- cursor.moveLeft();
- assert.deepEqual(cursor.getAddress(), {leftSide: true, number: 4});
-
- // Moving to the next chunk takes it back to the start.
- cursor.moveToNextChunk();
- assert.deepEqual(cursor.getAddress(), {leftSide: false, number: 5});
-
- // The following chunk is a removal starting on line 10 of the base.
- cursor.moveToNextChunk();
- assert.deepEqual(cursor.getAddress(), {leftSide: true, number: 10});
-
- // Should be null if there is no selection.
- cursor.cursorManager.unsetCursor();
- assert.isNotOk(cursor.getAddress());
- });
-
- test('_findRowByNumberAndFile', () => {
- // Get the first ab row after the first chunk.
- const rows = [...queryAll<HTMLTableRowElement>(diffElement, 'tr')];
- const row = rows[9];
- assert.ok(row);
-
- // It should be line 8 on the right, but line 5 on the left.
- assert.equal(cursor._findRowByNumberAndFile(8, Side.RIGHT), row);
- assert.equal(cursor._findRowByNumberAndFile(5, Side.LEFT), row);
- });
-
- test('expand context updates stops', async () => {
- const spy = sinon.spy(cursor, '_updateStops');
- const controls = queryAndAssert(diffElement, 'gr-context-controls');
- const showContext = queryAndAssert<HTMLElement>(controls, '.showContext');
- showContext.click();
- await waitForEventOnce(diffElement, 'render');
- await waitUntil(() => spy.called);
- assert.isTrue(spy.called);
- });
-
- test('updates stops when loading changes', () => {
- const spy = sinon.spy(cursor, '_updateStops');
- diffElement.dispatchEvent(new Event('loading-changed'));
- assert.isTrue(spy.called);
- });
-
- suite('multi diff', () => {
- let diffElements: GrDiff[];
-
- setup(async () => {
- diffElements = [
- await fixture(html`<gr-diff></gr-diff>`),
- await fixture(html`<gr-diff></gr-diff>`),
- await fixture(html`<gr-diff></gr-diff>`),
- ];
- cursor = new GrDiffCursor();
-
- // Register the diff with the cursor.
- cursor.replaceDiffs(diffElements);
-
- for (const el of diffElements) {
- el.prefs = createDefaultDiffPrefs();
- }
- });
-
- function getTargetDiffIndex() {
- // Mocha has a bug where when `assert.equals` fails, it will try to
- // JSON.stringify the operands, which fails when they are cyclic structures
- // like GrDiffElement. The failure is difficult to attribute to a specific
- // assertion because of the async nature assertion errors are handled and
- // can cause the test simply timing out, causing a lot of debugging headache.
- // Working with indices circumvents the problem.
- const target = cursor.getTargetDiffElement();
- assertIsDefined(target);
- return diffElements.indexOf(target);
- }
-
- test('do not skip loading diffs', async () => {
- diffElements[0].diff = createDiff();
- diffElements[2].diff = createDiff();
- await waitForEventOnce(diffElements[0], 'render');
- await waitForEventOnce(diffElements[2], 'render');
-
- const lastLine = diffElements[0].diff.meta_b?.lines;
- assertIsDefined(lastLine);
-
- // Goto second last line of the first diff
- cursor.moveToLineNumber(lastLine - 1, Side.RIGHT);
- assert.equal(
- cursor.getTargetLineElement()!.textContent?.trim(),
- `${lastLine - 1}`
- );
-
- // Can move down until we reach the loading file
- cursor.moveDown();
- assert.equal(getTargetDiffIndex(), 0);
- assert.equal(
- cursor.getTargetLineElement()!.textContent?.trim(),
- lastLine.toString()
- );
-
- // Cannot move down while still loading the diff we would switch to
- cursor.moveDown();
- assert.equal(getTargetDiffIndex(), 0);
- assert.equal(
- cursor.getTargetLineElement()!.textContent?.trim(),
- lastLine.toString()
- );
-
- // Diff 1 finishing to load
- diffElements[1].diff = createDiff();
- await waitForEventOnce(diffElements[1], 'render');
-
- // Now we can go down
- cursor.moveDown(); // LOST
- cursor.moveDown(); // FILE
- assert.equal(getTargetDiffIndex(), 1);
- assert.equal(cursor.getTargetLineElement()!.textContent?.trim(), 'File');
- });
- });
-});
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-annotation.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-annotation.ts
deleted file mode 100644
index 5669bcf..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-annotation.ts
+++ /dev/null
@@ -1,285 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {getSanitizeDOMValue} from '@polymer/polymer/lib/utils/settings';
-
-// TODO(wyatta): refactor this to be <MARK> rather than <HL>.
-const ANNOTATION_TAG = 'HL';
-
-// Astral code point as per https://mathiasbynens.be/notes/javascript-unicode
-const REGEX_ASTRAL_SYMBOL = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
-
-export const GrAnnotation = {
- /**
- * The DOM API textContent.length calculation is broken when the text
- * contains Unicode. See https://mathiasbynens.be/notes/javascript-unicode .
- *
- */
- getLength(node: Node) {
- if (node instanceof Comment) return 0;
- return GrAnnotation.getStringLength(node.textContent || '');
- },
-
- /**
- * Returns the number of Unicode code points in the given string
- *
- * This is not necessarily the same as the number of visible symbols.
- * See https://mathiasbynens.be/notes/javascript-unicode for more details.
- */
- getStringLength(str: string) {
- return [...str].length;
- },
-
- /**
- * Annotates the [offset, offset+length) text segment in the parent with the
- * element definition provided as arguments.
- *
- * @param parent the node whose contents will be annotated.
- * If parent is Text then parent.parentNode must not be null
- * @param offset the 0-based offset from which the annotation will
- * start.
- * @param length of the annotated text.
- * @param elementSpec the spec to create the
- * annotating element.
- */
- annotateWithElement(
- parent: Node,
- offset: number,
- length: number,
- elSpec: ElementSpec
- ) {
- const tagName = elSpec.tagName;
- const attributes = elSpec.attributes || {};
- let childNodes: Node[];
-
- if (parent instanceof Element) {
- childNodes = Array.from(parent.childNodes);
- } else if (parent instanceof Text) {
- childNodes = [parent];
- parent = parent.parentNode!;
- } else {
- return;
- }
-
- const nestedNodes: Node[] = [];
- for (let node of childNodes) {
- const initialNodeLength = GrAnnotation.getLength(node);
- // If the current node is completely before the offset.
- if (offset > 0 && initialNodeLength <= offset) {
- offset -= initialNodeLength;
- continue;
- }
-
- if (offset > 0) {
- node = GrAnnotation.splitNode(node, offset);
- offset = 0;
- }
- if (GrAnnotation.getLength(node) > length) {
- GrAnnotation.splitNode(node, length);
- }
- nestedNodes.push(node);
-
- length -= GrAnnotation.getLength(node);
- if (!length) break;
- }
-
- const wrapper = document.createElement(tagName);
- const sanitizer = getSanitizeDOMValue();
- for (let [name, value] of Object.entries(attributes)) {
- if (!value) continue;
- if (sanitizer) {
- value = sanitizer(value, name, 'attribute', wrapper) as string;
- }
- wrapper.setAttribute(name, value);
- }
- for (const inner of nestedNodes) {
- parent.replaceChild(wrapper, inner);
- wrapper.appendChild(inner);
- }
- },
-
- /**
- * Surrounds the element's text at specified range in an ANNOTATION_TAG
- * element. If the element has child elements, the range is split and
- * applied as deeply as possible.
- */
- annotateElement(
- parent: HTMLElement,
- offset: number,
- length: number,
- cssClass: string
- ) {
- const nodes: Array<HTMLElement | Text> = [].slice.apply(parent.childNodes);
- let nodeLength;
- let subLength;
-
- for (const node of nodes) {
- nodeLength = GrAnnotation.getLength(node);
-
- // If the current node is completely before the offset.
- if (nodeLength <= offset) {
- offset -= nodeLength;
- continue;
- }
-
- // Sublength is the annotation length for the current node.
- subLength = Math.min(length, nodeLength - offset);
-
- if (node instanceof Text) {
- GrAnnotation._annotateText(node, offset, subLength, cssClass);
- } else if (node instanceof Element) {
- GrAnnotation.annotateElement(node, offset, subLength, cssClass);
- }
-
- // If there is still more to annotate, then shift the indices, otherwise
- // work is done, so break the loop.
- if (subLength < length) {
- length -= subLength;
- offset = 0;
- } else {
- break;
- }
- }
- },
-
- /**
- * Wraps node in annotation tag with cssClass, replacing the node in DOM.
- */
- wrapInHighlight(node: Element | Text, cssClass: string) {
- let hl;
- if (!(node instanceof Text) && node.tagName === ANNOTATION_TAG) {
- hl = node;
- hl.classList.add(cssClass);
- } else {
- hl = document.createElement(ANNOTATION_TAG);
- hl.className = cssClass;
- if (node.parentElement) node.parentElement.replaceChild(hl, node);
- hl.appendChild(node);
- }
- return hl;
- },
-
- /**
- * Splits Text Node and wraps it in hl with cssClass.
- * Wraps trailing part after split, tailing one if firstPart is true.
- */
- splitAndWrapInHighlight(
- node: Text,
- offset: number,
- cssClass: string,
- firstPart?: boolean
- ) {
- if (
- (GrAnnotation.getLength(node) === offset && firstPart) ||
- (offset === 0 && !firstPart)
- ) {
- return GrAnnotation.wrapInHighlight(node, cssClass);
- }
- if (firstPart) {
- GrAnnotation.splitNode(node, offset);
- // Node points to first part of the Text, second one is sibling.
- } else {
- // if node is Text then splitNode will return a Text
- node = GrAnnotation.splitNode(node, offset) as Text;
- }
- return GrAnnotation.wrapInHighlight(node, cssClass);
- },
-
- /**
- * Splits Node at offset.
- * If Node is Element, it's cloned and the node at offset is split too.
- */
- splitNode(element: Node, offset: number) {
- if (element instanceof Text) {
- return GrAnnotation.splitTextNode(element, offset);
- }
- const tail = element.cloneNode(false);
-
- if (element.parentElement)
- element.parentElement.insertBefore(tail, element.nextSibling);
- // Skip nodes before offset.
- let node = element.firstChild;
- while (
- node &&
- (GrAnnotation.getLength(node) <= offset ||
- GrAnnotation.getLength(node) === 0)
- ) {
- offset -= GrAnnotation.getLength(node);
- node = node.nextSibling;
- }
- if (node && GrAnnotation.getLength(node) > offset) {
- tail.appendChild(GrAnnotation.splitNode(node, offset));
- }
- while (node && node.nextSibling) {
- tail.appendChild(node.nextSibling);
- }
- return tail;
- },
-
- /**
- * Node.prototype.splitText Unicode-valid alternative.
- *
- * DOM Api for splitText() is broken for Unicode:
- * https://mathiasbynens.be/notes/javascript-unicode
- *
- * @return Trailing Text Node.
- */
- splitTextNode(node: Text, offset: number) {
- if (node.textContent?.match(REGEX_ASTRAL_SYMBOL)) {
- const head = Array.from(node.textContent);
- const tail = head.splice(offset);
- const parent = node.parentNode;
-
- // Split the content of the original node.
- node.textContent = head.join('');
-
- const tailNode = document.createTextNode(tail.join(''));
- if (parent) {
- parent.insertBefore(tailNode, node.nextSibling);
- }
- return tailNode;
- } else {
- return node.splitText(offset);
- }
- },
-
- _annotateText(node: Text, offset: number, length: number, cssClass: string) {
- const nodeLength = GrAnnotation.getLength(node);
-
- // There are four cases:
- // 1) Entire node is highlighted.
- // 2) Highlight is at the start.
- // 3) Highlight is at the end.
- // 4) Highlight is in the middle.
-
- if (offset === 0 && nodeLength === length) {
- // Case 1.
- GrAnnotation.wrapInHighlight(node, cssClass);
- } else if (offset === 0) {
- // Case 2.
- GrAnnotation.splitAndWrapInHighlight(node, length, cssClass, true);
- } else if (offset + length === nodeLength) {
- // Case 3
- GrAnnotation.splitAndWrapInHighlight(node, offset, cssClass, false);
- } else {
- // Case 4
- GrAnnotation.splitAndWrapInHighlight(
- GrAnnotation.splitTextNode(node, offset),
- length,
- cssClass,
- true
- );
- }
- },
-};
-
-/**
- * Data used to construct an element.
- *
- */
-export interface ElementSpec {
- tagName: string;
- attributes?: {[attributeName: string]: string | undefined};
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-annotation_test.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-annotation_test.ts
deleted file mode 100644
index 4543c10..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-annotation_test.ts
+++ /dev/null
@@ -1,308 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import '../../../test/common-test-setup';
-import {GrAnnotation} from './gr-annotation';
-import {
- getSanitizeDOMValue,
- setSanitizeDOMValue,
-} from '@polymer/polymer/lib/utils/settings';
-import {assert, fixture, html} from '@open-wc/testing';
-
-suite('annotation', () => {
- let str: string;
- let parent: HTMLDivElement;
- let textNode: Text;
-
- setup(async () => {
- parent = await fixture(
- html`
- <div>Lorem ipsum dolor sit amet, suspendisse inceptos vehicula</div>
- `
- );
- textNode = parent.childNodes[0] as Text;
- str = textNode.textContent!;
- });
-
- test('_annotateText length:0 offset:0', () => {
- GrAnnotation._annotateText(textNode, 0, 0, 'foobar');
-
- assert.equal(parent.textContent, str);
- assert.equal(
- parent.innerHTML,
- '<hl class="foobar"></hl>Lorem ipsum dolor sit amet, suspendisse inceptos vehicula'
- );
- });
-
- test('_annotateText length:0 offset:1', () => {
- GrAnnotation._annotateText(textNode, 1, 0, 'foobar');
-
- assert.equal(parent.textContent, str);
- assert.equal(
- parent.innerHTML,
- 'L<hl class="foobar"></hl>orem ipsum dolor sit amet, suspendisse inceptos vehicula'
- );
- });
-
- test('_annotateText length:0 offset:str.length', () => {
- GrAnnotation._annotateText(textNode, str.length, 0, 'foobar');
-
- assert.equal(parent.textContent, str);
- assert.equal(
- parent.innerHTML,
- 'Lorem ipsum dolor sit amet, suspendisse inceptos vehicula<hl class="foobar"></hl>'
- );
- });
-
- test('_annotateText Case 1', () => {
- GrAnnotation._annotateText(textNode, 0, str.length, 'foobar');
-
- assert.equal(parent.textContent, str);
- assert.equal(
- parent.innerHTML,
- '<hl class="foobar">Lorem ipsum dolor sit amet, suspendisse inceptos vehicula</hl>'
- );
- });
-
- test('_annotateText Case 2', () => {
- GrAnnotation._annotateText(textNode, 0, 12, 'foobar');
-
- assert.equal(parent.textContent, str);
- assert.equal(
- parent.innerHTML,
- '<hl class="foobar">Lorem ipsum </hl>dolor sit amet, suspendisse inceptos vehicula'
- );
- });
-
- test('_annotateText Case 3', () => {
- GrAnnotation._annotateText(textNode, 12, str.length - 12, 'foobar');
-
- assert.equal(parent.textContent, str);
- assert.equal(
- parent.innerHTML,
- 'Lorem ipsum <hl class="foobar">dolor sit amet, suspendisse inceptos vehicula</hl>'
- );
- });
-
- test('_annotateText Case 4', () => {
- const index = str.indexOf('dolor');
- const length = 'dolor '.length;
-
- GrAnnotation._annotateText(textNode, index, length, 'foobar');
-
- assert.equal(parent.textContent, str);
- assert.equal(
- parent.innerHTML,
- 'Lorem ipsum <hl class="foobar">dolor </hl>sit amet, suspendisse inceptos vehicula'
- );
- });
-
- test('_annotateElement design doc example', () => {
- const layers = ['amet, ', 'inceptos ', 'amet, ', 'et, suspendisse ince'];
-
- // Apply the layers successively.
- layers.forEach((layer, i) => {
- GrAnnotation.annotateElement(
- parent,
- str.indexOf(layer),
- layer.length,
- `layer-${i + 1}`
- );
- });
-
- assert.equal(parent.textContent, str);
- assert.equal(
- parent.innerHTML,
- 'Lorem ipsum dolor sit <hl class="layer-1"><hl class="layer-3">am<hl class="layer-4">et, </hl></hl></hl><hl class="layer-4">suspendisse </hl><hl class="layer-2"><hl class="layer-4">ince</hl>ptos </hl>vehicula'
- );
- });
-
- test('splitTextNode', () => {
- const helloString = 'hello';
- const asciiString = 'ASCII';
- const unicodeString = 'Unic💢de';
-
- let node;
- let tail;
-
- // Non-unicode path:
- node = document.createTextNode(helloString + asciiString);
- tail = GrAnnotation.splitTextNode(node, helloString.length);
- assert(node.textContent, helloString);
- assert(tail.textContent, asciiString);
-
- // Unicdoe path:
- node = document.createTextNode(helloString + unicodeString);
- tail = GrAnnotation.splitTextNode(node, helloString.length);
- assert(node.textContent, helloString);
- assert(tail.textContent, unicodeString);
- });
-
- suite('annotateWithElement', () => {
- const fullText = '01234567890123456789';
- let mockSanitize: sinon.SinonSpy;
- let originalSanitizeDOMValue: (
- value: unknown,
- name: string,
- type: 'property' | 'attribute',
- node: Node | null | undefined
- ) => unknown;
-
- setup(() => {
- setSanitizeDOMValue(p0 => p0);
- originalSanitizeDOMValue = getSanitizeDOMValue()!;
- assert.isDefined(originalSanitizeDOMValue);
- mockSanitize = sinon.spy(originalSanitizeDOMValue);
- setSanitizeDOMValue(mockSanitize);
- });
-
- teardown(() => {
- setSanitizeDOMValue(originalSanitizeDOMValue);
- });
-
- test('annotates when fully contained', () => {
- const length = 10;
- const container = document.createElement('div');
- container.textContent = fullText;
- GrAnnotation.annotateWithElement(container, 1, length, {
- tagName: 'test-wrapper',
- });
-
- assert.equal(
- container.innerHTML,
- '0<test-wrapper>1234567890</test-wrapper>123456789'
- );
- });
-
- test('annotates when spanning multiple nodes', () => {
- const length = 10;
- const container = document.createElement('div');
- container.textContent = fullText;
- GrAnnotation.annotateElement(container, 5, length, 'testclass');
- GrAnnotation.annotateWithElement(container, 1, length, {
- tagName: 'test-wrapper',
- });
-
- assert.equal(
- container.innerHTML,
- '0' +
- '<test-wrapper>' +
- '1234' +
- '<hl class="testclass">567890</hl>' +
- '</test-wrapper>' +
- '<hl class="testclass">1234</hl>' +
- '56789'
- );
- });
-
- test('annotates text node', () => {
- const length = 10;
- const container = document.createElement('div');
- container.textContent = fullText;
- GrAnnotation.annotateWithElement(container.childNodes[0], 1, length, {
- tagName: 'test-wrapper',
- });
-
- assert.equal(
- container.innerHTML,
- '0<test-wrapper>1234567890</test-wrapper>123456789'
- );
- });
-
- test('handles zero-length nodes', () => {
- const container = document.createElement('div');
- container.appendChild(document.createTextNode('0123456789'));
- container.appendChild(document.createElement('span'));
- container.appendChild(document.createTextNode('0123456789'));
- GrAnnotation.annotateWithElement(container, 1, 10, {
- tagName: 'test-wrapper',
- });
-
- assert.equal(
- container.innerHTML,
- '0<test-wrapper>123456789<span></span>0</test-wrapper>123456789'
- );
- });
-
- test('handles comment nodes', () => {
- const container = document.createElement('div');
- container.appendChild(document.createComment('comment1'));
- container.appendChild(document.createTextNode('0123456789'));
- container.appendChild(document.createComment('comment2'));
- container.appendChild(document.createElement('span'));
- container.appendChild(document.createTextNode('0123456789'));
- GrAnnotation.annotateWithElement(container, 1, 10, {
- tagName: 'test-wrapper',
- });
-
- assert.equal(
- container.innerHTML,
- '<!--comment1-->' +
- '0<test-wrapper>123456789' +
- '<!--comment2-->' +
- '<span></span>0</test-wrapper>123456789'
- );
- });
-
- test('sets sanitized attributes', () => {
- const container = document.createElement('div');
- container.textContent = fullText;
- const attributes = {
- href: 'foo',
- 'data-foo': 'bar',
- class: 'hello world',
- };
- GrAnnotation.annotateWithElement(container, 1, length, {
- tagName: 'test-wrapper',
- attributes,
- });
- assert(
- mockSanitize.calledWith(
- 'foo',
- 'href',
- 'attribute',
- sinon.match.instanceOf(Element)
- )
- );
- assert(
- mockSanitize.calledWith(
- 'bar',
- 'data-foo',
- 'attribute',
- sinon.match.instanceOf(Element)
- )
- );
- assert(
- mockSanitize.calledWith(
- 'hello world',
- 'class',
- 'attribute',
- sinon.match.instanceOf(Element)
- )
- );
- const el = container.querySelector('test-wrapper')!;
- assert.equal(el.getAttribute('href'), 'foo');
- assert.equal(el.getAttribute('data-foo'), 'bar');
- assert.equal(el.getAttribute('class'), 'hello world');
- });
- });
-
- suite('getStringLength', () => {
- test('ASCII characters are counted correctly', () => {
- assert.equal(GrAnnotation.getStringLength('ASCII'), 5);
- });
-
- test('Unicode surrogate pairs count as one symbol', () => {
- assert.equal(GrAnnotation.getStringLength('Unic💢de'), 7);
- assert.equal(GrAnnotation.getStringLength('💢💢'), 2);
- });
-
- test('Grapheme clusters count as multiple symbols', () => {
- assert.equal(GrAnnotation.getStringLength('man\u0303ana'), 7); // mañana
- assert.equal(GrAnnotation.getStringLength('q\u0307\u0323'), 3); // q̣̇
- });
- });
-});
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-diff-highlight.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-diff-highlight.ts
deleted file mode 100644
index 2c0663d..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-diff-highlight.ts
+++ /dev/null
@@ -1,525 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../styles/shared-styles';
-import '../../diff/gr-selection-action-box/gr-selection-action-box';
-import {GrAnnotation} from './gr-annotation';
-import {normalize} from './gr-range-normalizer';
-import {strToClassName} from '../../../utils/dom-util';
-import {Side} from '../../../constants/constants';
-import {CommentRange} from '../../../types/common';
-import {GrSelectionActionBox} from '../../diff/gr-selection-action-box/gr-selection-action-box';
-import {
- getLineElByChild,
- getLineNumberByChild,
- getSideByLineEl,
- GrDiffThreadElement,
-} from '../../diff/gr-diff/gr-diff-utils';
-import {debounce, DelayedTask} from '../../../utils/async-util';
-import {assertIsDefined, queryAndAssert} from '../../../utils/common-util';
-import {fire} from '../../../utils/event-util';
-
-interface SidedRange {
- side: Side;
- range: CommentRange;
-}
-
-interface NormalizedPosition {
- node: Node | null;
- side: Side;
- line: number;
- column: number;
-}
-
-interface NormalizedRange {
- start: NormalizedPosition | null;
- end: NormalizedPosition | null;
-}
-
-/**
- * The methods that we actually want to call on the builder. We don't want a
- * fully blown dependency on GrDiffBuilderElement.
- */
-export interface DiffBuilderInterface {
- getContentTdByLineEl(lineEl?: Element): Element | undefined;
-}
-
-/**
- * Handles showing, positioning and interacting with <gr-selection-action-box>.
- *
- * Toggles a css class for highlighting comment ranges when the mouse leaves or
- * enters a comment thread element.
- */
-export class GrDiffHighlight {
- selectedRange?: SidedRange;
-
- private diffBuilder?: DiffBuilderInterface;
-
- private diffTable?: HTMLElement;
-
- private selectionChangeTask?: DelayedTask;
-
- init(diffTable: HTMLElement, diffBuilder: DiffBuilderInterface) {
- this.cleanup();
-
- this.diffTable = diffTable;
- this.diffBuilder = diffBuilder;
-
- diffTable.addEventListener(
- 'comment-thread-mouseleave',
- this.handleCommentThreadMouseleave
- );
- diffTable.addEventListener(
- 'comment-thread-mouseenter',
- this.handleCommentThreadMouseenter
- );
- diffTable.addEventListener(
- 'create-comment-requested',
- this.handleRangeCommentRequest
- );
- }
-
- cleanup() {
- this.selectionChangeTask?.cancel();
- if (this.diffTable) {
- this.diffTable.removeEventListener(
- 'comment-thread-mouseleave',
- this.handleCommentThreadMouseleave
- );
- this.diffTable.removeEventListener(
- 'comment-thread-mouseenter',
- this.handleCommentThreadMouseenter
- );
- this.diffTable.removeEventListener(
- 'create-comment-requested',
- this.handleRangeCommentRequest
- );
- }
- }
-
- /**
- * Determines side/line/range for a DOM selection and shows a tooltip.
- *
- * With native shadow DOM, gr-diff-highlight cannot access a selection that
- * references the DOM elements making up the diff because they are in the
- * shadow DOM the gr-diff element. For this reason, we listen to the
- * selectionchange event and retrieve the selection in gr-diff, and then
- * call this method to process the Selection.
- *
- * @param selection A DOM Selection living in the shadow DOM of
- * the diff element.
- * @param isMouseUp If true, this is called due to a mouseup
- * event, in which case we might want to immediately create a comment,
- * because isMouseUp === true combined with an existing selection must
- * mean that this is the end of a double-click.
- */
- handleSelectionChange(
- selection: Selection | Range | null,
- isMouseUp: boolean
- ) {
- if (selection === null) return;
- // Debounce is not just nice for waiting until the selection has settled,
- // it is also vital for being able to click on the action box before it is
- // removed.
- // If you wait longer than 50 ms, then you don't properly catch a very
- // quick 'c' press after the selection change. If you wait less than 10
- // ms, then you will have about 50 handleSelection() calls when doing a
- // simple drag for select.
- this.selectionChangeTask = debounce(
- this.selectionChangeTask,
- () => this.handleSelection(selection, isMouseUp),
- 10
- );
- }
-
- private getThreadEl(e: Event): GrDiffThreadElement | null {
- for (const pathEl of e.composedPath()) {
- if (
- pathEl instanceof HTMLElement &&
- pathEl.classList.contains('comment-thread')
- ) {
- return pathEl as GrDiffThreadElement;
- }
- }
- return null;
- }
-
- private toggleRangeElHighlight(
- threadEl: GrDiffThreadElement | null,
- highlightRange = false
- ) {
- const rootId = threadEl?.rootId;
- if (!rootId) return;
- if (!this.diffTable) return;
- if (highlightRange) {
- const selector = `.range.${strToClassName(rootId)}`;
- const rangeNodes = this.diffTable.querySelectorAll(selector);
- rangeNodes.forEach(rangeNode => {
- rangeNode.classList.add('rangeHoverHighlight');
- });
- const hintNode = this.diffTable.querySelector(
- `gr-ranged-comment-hint[threadElRootId="${rootId}"]`
- );
- hintNode?.shadowRoot
- ?.querySelectorAll('.rangeHighlight')
- .forEach(highlightNode =>
- highlightNode.classList.add('rangeHoverHighlight')
- );
- } else {
- const selector = `.rangeHoverHighlight.${strToClassName(rootId)}`;
- const rangeNodes = this.diffTable.querySelectorAll(selector);
- rangeNodes.forEach(rangeNode => {
- rangeNode.classList.remove('rangeHoverHighlight');
- });
- const hintNode = this.diffTable.querySelector(
- `gr-ranged-comment-hint[threadElRootId="${rootId}"]`
- );
- hintNode?.shadowRoot
- ?.querySelectorAll('.rangeHoverHighlight')
- .forEach(highlightNode =>
- highlightNode.classList.remove('rangeHoverHighlight')
- );
- }
- }
-
- private handleCommentThreadMouseenter = (e: Event) => {
- const threadEl = this.getThreadEl(e);
- this.toggleRangeElHighlight(threadEl, /* highlightRange= */ true);
- };
-
- private handleCommentThreadMouseleave = (e: Event) => {
- const threadEl = this.getThreadEl(e);
- this.toggleRangeElHighlight(threadEl, /* highlightRange= */ false);
- };
-
- /**
- * Get current normalized selection.
- * Merges multiple ranges, accounts for triple click, accounts for
- * syntax highligh, convert native DOM Range objects to Gerrit concepts
- * (line, side, etc).
- */
- private getNormalizedRange(selection: Selection | Range) {
- /* On Safari the ShadowRoot.getSelection() isn't there and the only thing
- we can get is a single Range */
- if (selection instanceof Range) {
- return this.normalizeRange(selection);
- }
- const rangeCount = selection.rangeCount;
- if (rangeCount === 0) {
- return null;
- } else if (rangeCount === 1) {
- return this.normalizeRange(selection.getRangeAt(0));
- } else {
- const startRange = this.normalizeRange(selection.getRangeAt(0));
- const endRange = this.normalizeRange(
- selection.getRangeAt(rangeCount - 1)
- );
- return {
- start: startRange.start,
- end: endRange.end,
- };
- }
- }
-
- /**
- * Normalize a specific DOM Range.
- *
- * @return fixed normalized range
- */
- private normalizeRange(domRange: Range): NormalizedRange {
- const range = normalize(domRange);
- return this.fixTripleClickSelection(
- {
- start: this.normalizeSelectionSide(
- range.startContainer,
- range.startOffset
- ),
- end: this.normalizeSelectionSide(range.endContainer, range.endOffset),
- },
- domRange
- );
- }
-
- /**
- * Adjust triple click selection for the whole line.
- * A triple click always results in:
- * - start.column == end.column == 0
- * - end.line == start.line + 1
- *
- * @param range Normalized range, ie column/line numbers
- * @param domRange DOM Range object
- * @return fixed normalized range
- */
- private fixTripleClickSelection(range: NormalizedRange, domRange: Range) {
- if (!range.start) {
- // Selection outside of current diff.
- return range;
- }
- const start = range.start;
- const end = range.end;
- // Happens when triple click in side-by-side mode with other side empty.
- const endsAtOtherEmptySide =
- !end &&
- domRange.endOffset === 0 &&
- domRange.endContainer instanceof HTMLElement &&
- domRange.endContainer.nodeName === 'TD' &&
- (domRange.endContainer.classList.contains('left') ||
- domRange.endContainer.classList.contains('right'));
- const endsAtBeginningOfNextLine =
- end &&
- start.column === 0 &&
- end.column === 0 &&
- end.line === start.line + 1;
- const content = domRange.cloneContents().querySelector('.contentText');
- const lineLength = (content && this.getLength(content)) || 0;
- if (lineLength && (endsAtBeginningOfNextLine || endsAtOtherEmptySide)) {
- // Move the selection to the end of the previous line.
- range.end = {
- node: start.node,
- column: lineLength,
- side: start.side,
- line: start.line,
- };
- }
- return range;
- }
-
- /**
- * Convert DOM Range selection to concrete numbers (line, column, side).
- * Moves range end if it's not inside td.content.
- * Returns null if selection end is not valid (outside of diff).
- *
- * @param node td.content child
- * @param offset offset within node
- */
- private normalizeSelectionSide(
- node: Node | null,
- offset: number
- ): NormalizedPosition | null {
- let column;
- if (!this.diffTable) return null;
- if (!this.diffBuilder) return null;
- if (!node || !this.diffTable.contains(node)) return null;
- const lineEl = getLineElByChild(node);
- if (!lineEl) return null;
- const side = getSideByLineEl(lineEl);
- if (!side) return null;
- const line = getLineNumberByChild(lineEl);
- if (typeof line !== 'number') return null;
- const contentTd = this.diffBuilder.getContentTdByLineEl(lineEl);
- if (!contentTd) return null;
- const contentText = contentTd.querySelector('.contentText');
- if (!contentTd.contains(node)) {
- node = contentText;
- column = 0;
- } else {
- const thread = contentTd.querySelector('.comment-thread');
- if (thread?.contains(node)) {
- column = this.getLength(contentText);
- node = contentText;
- } else {
- column = this.convertOffsetToColumn(node, offset);
- }
- }
-
- return {
- node,
- side,
- line,
- column,
- };
- }
-
- /**
- * The only line in which add a comment tooltip is cut off is the first
- * line. Even if there is a collapsed section, The first visible line is
- * in the position where the second line would have been, if not for the
- * collapsed section, so don't need to worry about this case for
- * positioning the tooltip.
- */
- // visible for testing
- positionActionBox(
- actionBox: GrSelectionActionBox,
- startLine: number,
- range: Text | Element | Range
- ) {
- if (startLine > 1) {
- actionBox.positionBelow = false;
- actionBox.placeAbove(range);
- return;
- }
- actionBox.positionBelow = true;
- actionBox.placeBelow(range);
- }
-
- private isRangeValid(range: NormalizedRange | null) {
- if (!range || !range.start || !range.start.node || !range.end) {
- return false;
- }
- const start = range.start;
- const end = range.end;
- return !(
- start.side !== end.side ||
- end.line < start.line ||
- (start.line === end.line && start.column === end.column)
- );
- }
-
- // visible for testing
- handleSelection(selection: Selection | Range, isMouseUp: boolean) {
- /* On Safari, the selection events may return a null range that should
- be ignored */
- if (!selection) return;
- if (!this.diffTable) return;
-
- const normalizedRange = this.getNormalizedRange(selection);
- if (!this.isRangeValid(normalizedRange)) {
- this.removeActionBox();
- return;
- }
- /* On Safari the ShadowRoot.getSelection() isn't there and the only thing
- we can get is a single Range */
- const domRange =
- selection instanceof Range ? selection : selection.getRangeAt(0);
- const start = normalizedRange!.start!;
- const end = normalizedRange!.end!;
-
- // TODO (viktard): Drop empty first and last lines from selection.
-
- // If the selection is from the end of one line to the start of the next
- // line, then this must have been a double-click, or you have started
- // dragging. Showing the action box is bad in the former case and not very
- // useful in the latter, so never do that.
- // If this was a mouse-up event, we create a comment immediately if
- // the selection is from the end of a line to the start of the next line.
- // In a perfect world we would only do this for double-click, but it is
- // extremely rare that a user would drag from the end of one line to the
- // start of the next and release the mouse, so we don't bother.
- // TODO(brohlfs): This does not work, if the double-click is before a new
- // diff chunk (start will be equal to end), and neither before an "expand
- // the diff context" block (end line will match the first line of the new
- // section and thus be greater than start line + 1).
- if (start.line === end.line - 1 && end.column === 0) {
- // Rather than trying to find the line contents (for comparing
- // start.column with the content length), we just check if the selection
- // is empty to see that it's at the end of a line.
- const content = domRange.cloneContents().querySelector('.contentText');
- if (isMouseUp && this.getLength(content) === 0) {
- this.fireCreateRangeComment(start.side, {
- start_line: start.line,
- start_character: 0,
- end_line: start.line,
- end_character: start.column,
- });
- }
- return;
- }
-
- let actionBox = this.diffTable.querySelector('gr-selection-action-box');
- if (!actionBox) {
- actionBox = document.createElement('gr-selection-action-box');
- this.diffTable.appendChild(actionBox);
- }
- this.selectedRange = {
- range: {
- start_line: start.line,
- start_character: start.column,
- end_line: end.line,
- end_character: end.column,
- },
- side: start.side,
- };
- if (start.line === end.line) {
- this.positionActionBox(actionBox, start.line, domRange);
- } else if (start.node instanceof Text) {
- if (start.column) {
- this.positionActionBox(
- actionBox,
- start.line,
- start.node.splitText(start.column)
- );
- }
- start.node.parentElement!.normalize(); // Undo splitText from above.
- } else if (
- start.node instanceof HTMLElement &&
- start.node.classList.contains('content') &&
- (start.node.firstChild instanceof Element ||
- start.node.firstChild instanceof Text)
- ) {
- this.positionActionBox(actionBox, start.line, start.node.firstChild);
- } else if (start.node instanceof Element || start.node instanceof Text) {
- this.positionActionBox(actionBox, start.line, start.node);
- } else {
- console.warn('Failed to position comment action box.');
- this.removeActionBox();
- }
- }
-
- private fireCreateRangeComment(side: Side, range: CommentRange) {
- if (this.diffTable) {
- fire(this.diffTable, 'create-range-comment', {side, range});
- }
- this.removeActionBox();
- }
-
- private handleRangeCommentRequest = (e: Event) => {
- e.stopPropagation();
- assertIsDefined(this.selectedRange, 'selectedRange');
- const {side, range} = this.selectedRange;
- this.fireCreateRangeComment(side, range);
- };
-
- // visible for testing
- removeActionBox() {
- this.selectedRange = undefined;
- const actionBox = this.diffTable?.querySelector('gr-selection-action-box');
- if (actionBox) actionBox.remove();
- }
-
- private convertOffsetToColumn(el: Node, offset: number) {
- if (el instanceof Element && el.classList.contains('content')) {
- return offset;
- }
- while (
- el.previousSibling ||
- !el.parentElement?.classList.contains('content')
- ) {
- if (el.previousSibling) {
- el = el.previousSibling;
- offset += this.getLength(el);
- } else {
- el = el.parentElement!;
- }
- }
- return offset;
- }
-
- /**
- * Get length of a node. If the node is a content node, then only give the
- * length of its .contentText child.
- *
- * @param node this is sometimes passed as null.
- */
- // visible for testing
- getLength(node: Node | null): number {
- if (node === null) return 0;
- if (node instanceof Element && node.classList.contains('content')) {
- return this.getLength(queryAndAssert(node, '.contentText'));
- } else {
- return GrAnnotation.getLength(node);
- }
- }
-}
-
-export interface CreateRangeCommentEventDetail {
- side: Side;
- range: CommentRange;
-}
-
-declare global {
- interface HTMLElementEventMap {
- 'create-range-comment': CustomEvent<CreateRangeCommentEventDetail>;
- }
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-diff-highlight_test.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-diff-highlight_test.ts
deleted file mode 100644
index e491e63..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-diff-highlight_test.ts
+++ /dev/null
@@ -1,717 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup';
-import './gr-diff-highlight';
-import {getTextOffset} from './gr-range-normalizer';
-import {fixture, fixtureCleanup, html, assert} from '@open-wc/testing';
-import {
- GrDiffHighlight,
- DiffBuilderInterface,
- CreateRangeCommentEventDetail,
-} from './gr-diff-highlight';
-import {Side} from '../../../api/diff';
-import {SinonStubbedMember} from 'sinon';
-import {queryAndAssert} from '../../../utils/common-util';
-import {GrDiffThreadElement} from '../../diff/gr-diff/gr-diff-utils';
-import {
- stubElement,
- waitQueryAndAssert,
- waitUntil,
-} from '../../../test/test-utils';
-import {GrSelectionActionBox} from '../../diff/gr-selection-action-box/gr-selection-action-box';
-
-// Splitting long lines in html into shorter rows breaks tests:
-// zero-length text nodes and new lines are not expected in some places
-/* eslint-disable max-len, lit/prefer-static-styles */
-/* prettier-ignore */
-const diffTable = html`
- <table id="diffTable">
- <tbody class="section both">
- <tr class="diff-row side-by-side" left-type="both" right-type="both">
- <td class="left lineNum" data-value="1"></td>
- <td class="content both"><div class="contentText">[1] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></td>
- <td class="right lineNum" data-value="1"></td>
- <td class="content both"><div class="contentText">[1] Nam cum ad me in Cumanum salutandi causa uterque</div></td>
- </tr>
- </tbody>
-
- <tbody class="section delta">
- <tr class="diff-row side-by-side" left-type="remove" right-type="add">
- <td class="left lineNum" data-value="2"></td>
- <!-- Next tag is formatted to eliminate zero-length text nodes. -->
- <td class="content remove"><div class="contentText">na💢ti <hl class="foo range generated_id314">te, inquit</hl>, sumus<hl class="bar">aliquando</hl> otiosum, <hl>certe</hl> a<hl><span class="tab-indicator" style="tab-size:8;"> </span></hl>udiam, <hl>quid</hl> sit,<span class="tab-indicator" style="tab-size:8;"> </span>quod<hl>Epicurum</hl></div></td>
- <td class="right lineNum" data-value="2"></td>
- <!-- Next tag is formatted to eliminate zero-length text nodes. -->
- <td class="content add"><div class="contentText">nacti , <hl>,</hl> sumus<hl><span class="tab-indicator" style="tab-size:8;"> </span></hl>otiosum,<span class="tab-indicator" style="tab-size:8;"> </span> audiam,sit, quod</div></td>
- </tr>
- </tbody>
-
- <tbody class="section both">
- <tr class="diff-row side-by-side" left-type="both" right-type="both">
- <td class="left lineNum" data-value="138"></td>
- <td class="content both"><div class="contentText">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></td>
- <td class="right lineNum" data-value="119"></td>
- <td class="content both"><div class="contentText">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></td>
- </tr>
- </tbody>
-
- <tbody class="section delta">
- <tr class="diff-row side-by-side" left-type="remove" right-type="add">
- <td class="left lineNum" data-value="140"></td>
- <!-- Next tag is formatted to eliminate zero-length text nodes. -->
- <td class="content remove"><div class="contentText"><!-- a comment node -->na💢ti <hl class="foo">te, inquit</hl>, sumus <hl class="bar">aliquando</hl> otiosum, <hl>certe</hl> a <hl><span class="tab-indicator" style="tab-size:8;">\u0009</span></hl>udiam, <hl>quid</hl> sit, <span class="tab-indicator" style="tab-size:8;">\u0009</span>quod <hl>Epicurum</hl></div><div class="comment-thread">
- [Yet another random diff thread content here]
- </div></td>
- <td class="right lineNum" data-value="120"></td>
- <!-- Next tag is formatted to eliminate zero-length text nodes. -->
- <td class="content add"><div class="contentText">nacti , <hl>,</hl> sumus <hl><span class="tab-indicator" style="tab-size:8;">\u0009</span></hl> otiosum, <span class="tab-indicator" style="tab-size:8;">\u0009</span> audiam, sit, quod</div></td>
- </tr>
- </tbody>
-
- <tbody class="section both">
- <tr class="diff-row side-by-side" left-type="both" right-type="both">
- <td class="left lineNum" data-value="141"></td>
- <td class="content both"><div class="contentText">nam et<hl><span class="tab-indicator" style="tab-size:8;"> </span></hl>complectitur<span class="tab-indicator" style="tab-size:8;"></span>verbis, quod vult, et dicit plane, quod intellegam;</div></td>
- <td class="right lineNum" data-value="130"></td>
- <td class="content both"><div class="contentText">nam et complectitur verbis, quod vult, et dicit plane, quodintellegam;</div></td>
- </tr>
- </tbody>
-
- <tbody class="section contextControl">
- <tr
- class="diff-row side-by-side"
- left-type="contextControl"
- right-type="contextControl"
- >
- <td class="left contextLineNum"></td>
- <td>
- <gr-button>+10↑</gr-button>
- -
- <gr-button>Show 21 common lines</gr-button>
- -
- <gr-button>+10↓</gr-button>
- </td>
- <td class="right contextLineNum"></td>
- <td>
- <gr-button>+10↑</gr-button>
- -
- <gr-button>Show 21 common lines</gr-button>
- -
- <gr-button>+10↓</gr-button>
- </td>
- </tr>
- </tbody>
-
- <tbody class="section delta total">
- <tr class="diff-row side-by-side" left-type="blank" right-type="add">
- <td class="left"></td>
- <td class="blank"></td>
- <td class="right lineNum" data-value="146"></td>
- <td class="content add"><div class="contentText">[17] Quid igitur est? inquit; audire enim cupio, quid non probes. Principio, inquam,</div></td>
- </tr>
- </tbody>
-
- <tbody class="section both">
- <tr class="diff-row side-by-side" left-type="both" right-type="both">
- <td class="left lineNum" data-value="165"></td>
- <td class="content both"><div class="contentText"></div></td>
- <td class="right lineNum" data-value="147"></td>
- <td class="content both"><div class="contentText">in physicis, <hl><span class="tab-indicator" style="tab-size:8;"> </span></hl>quibus maxime gloriatur, primum totus est alienus. Democritea dicit</div></td>
- </tr>
- </tbody>
- </table>
-`;
-/* eslint-enable max-len */
-
-suite('gr-diff-highlight', () => {
- suite('comment events', () => {
- let threadEl: GrDiffThreadElement;
- let hlRange: HTMLElement;
- let element: GrDiffHighlight;
- let diff: HTMLElement;
- let builder: {
- getContentTdByLineEl: SinonStubbedMember<
- DiffBuilderInterface['getContentTdByLineEl']
- >;
- };
-
- setup(async () => {
- diff = await fixture<HTMLTableElement>(diffTable);
- builder = {
- getContentTdByLineEl: sinon.stub(),
- };
- element = new GrDiffHighlight();
- element.init(diff, builder);
- hlRange = queryAndAssert(diff, 'hl.range.generated_id314');
-
- threadEl = document.createElement(
- 'div'
- ) as unknown as GrDiffThreadElement;
- threadEl.className = 'comment-thread';
- threadEl.rootId = 'id314';
- diff.appendChild(threadEl);
- });
-
- teardown(() => {
- element.cleanup();
- threadEl.remove();
- });
-
- test('comment-thread-mouseenter toggles rangeHoverHighlight class', async () => {
- assert.isFalse(hlRange.classList.contains('rangeHoverHighlight'));
- threadEl.dispatchEvent(
- new CustomEvent('comment-thread-mouseenter', {
- bubbles: true,
- composed: true,
- })
- );
- await waitUntil(() => hlRange.classList.contains('rangeHoverHighlight'));
- assert.isTrue(hlRange.classList.contains('rangeHoverHighlight'));
- });
-
- test('comment-thread-mouseleave toggles rangeHoverHighlight class', async () => {
- hlRange.classList.add('rangeHoverHighlight');
- threadEl.dispatchEvent(
- new CustomEvent('comment-thread-mouseleave', {
- bubbles: true,
- composed: true,
- })
- );
- await waitUntil(() => !hlRange.classList.contains('rangeHoverHighlight'));
- assert.isFalse(hlRange.classList.contains('rangeHoverHighlight'));
- });
-
- test(`create-range-comment for range when create-comment-requested
- is fired`, () => {
- const removeActionBoxStub = sinon.stub(element, 'removeActionBox');
- element.selectedRange = {
- side: Side.LEFT,
- range: {
- start_line: 7,
- start_character: 11,
- end_line: 24,
- end_character: 42,
- },
- };
- const requestEvent = new CustomEvent('create-comment-requested');
- let createRangeEvent: CustomEvent<CreateRangeCommentEventDetail>;
- diff.addEventListener('create-range-comment', e => {
- createRangeEvent = e;
- });
- diff.dispatchEvent(requestEvent);
- if (!createRangeEvent!) assert.fail('event not set');
- assert.deepEqual(element.selectedRange, createRangeEvent.detail);
- assert.isTrue(removeActionBoxStub.called);
- });
- });
-
- suite('selection', () => {
- let element: GrDiffHighlight;
- let diff: HTMLElement;
- let builder: {
- getContentTdByLineEl: SinonStubbedMember<
- DiffBuilderInterface['getContentTdByLineEl']
- >;
- };
- let contentStubs;
-
- setup(async () => {
- diff = await fixture<HTMLTableElement>(diffTable);
- builder = {
- getContentTdByLineEl: sinon.stub(),
- };
- element = new GrDiffHighlight();
- element.init(diff, builder);
- contentStubs = [];
- stubElement('gr-selection-action-box', 'placeAbove');
- stubElement('gr-selection-action-box', 'placeBelow');
- });
-
- teardown(() => {
- fixtureCleanup();
- element.cleanup();
- contentStubs = null;
- document.getSelection()!.removeAllRanges();
- });
-
- const stubContent = (line: number, side: Side) => {
- const contentTd = diff.querySelector(
- `.${side}.lineNum[data-value="${line}"] ~ .content`
- );
- if (!contentTd) assert.fail('content td not found');
- const contentText = contentTd.querySelector('.contentText');
- const lineEl =
- diff.querySelector(`.${side}.lineNum[data-value="${line}"]`) ??
- undefined;
- contentStubs.push({
- lineEl,
- contentTd,
- contentText,
- });
- builder.getContentTdByLineEl.withArgs(lineEl).returns(contentTd);
- return contentText;
- };
-
- const emulateSelection = (
- startNode: Node,
- startOffset: number,
- endNode: Node,
- endOffset: number
- ) => {
- const selection = document.getSelection();
- if (!selection) assert.fail('no selection');
- selection.removeAllRanges();
- const range = document.createRange();
- range.setStart(startNode, startOffset);
- range.setEnd(endNode, endOffset);
- selection.addRange(range);
- element.handleSelection(selection, false);
- };
-
- test('single first line', () => {
- const content = stubContent(1, Side.RIGHT);
- sinon.spy(element, 'positionActionBox');
- if (!content?.firstChild) assert.fail('content first child not found');
- emulateSelection(content.firstChild, 5, content.firstChild, 12);
- const actionBox = diff.querySelector('gr-selection-action-box');
- if (!actionBox) assert.fail('action box not found');
- assert.isTrue(actionBox.positionBelow);
- });
-
- test('multiline starting on first line', () => {
- const startContent = stubContent(1, Side.RIGHT);
- const endContent = stubContent(2, Side.RIGHT);
- sinon.spy(element, 'positionActionBox');
- if (!startContent?.firstChild) {
- assert.fail('first child of start content not found');
- }
- if (!endContent?.lastChild) {
- assert.fail('last child of end content not found');
- }
- emulateSelection(startContent.firstChild, 10, endContent.lastChild, 7);
- const actionBox = diff.querySelector('gr-selection-action-box');
- if (!actionBox) assert.fail('action box not found');
- assert.isTrue(actionBox.positionBelow);
- });
-
- test('single line', async () => {
- const content = stubContent(138, Side.LEFT);
- sinon.spy(element, 'positionActionBox');
- if (!content?.firstChild) assert.fail('content first child not found');
- emulateSelection(content.firstChild, 5, content.firstChild, 12);
- const actionBox = await waitQueryAndAssert<GrSelectionActionBox>(
- diff,
- 'gr-selection-action-box'
- );
- if (!element.selectedRange) assert.fail('no range selected');
- const {range, side} = element.selectedRange;
- assert.deepEqual(range, {
- start_line: 138,
- start_character: 5,
- end_line: 138,
- end_character: 12,
- });
- assert.equal(side, Side.LEFT);
- assert.notOk(actionBox.positionBelow);
- });
-
- test('multiline', () => {
- const startContent = stubContent(119, Side.RIGHT);
- const endContent = stubContent(120, Side.RIGHT);
- sinon.spy(element, 'positionActionBox');
- if (!startContent?.firstChild) {
- assert.fail('first child of start content not found');
- }
- if (!endContent?.lastChild) {
- assert.fail('last child of end content');
- }
- emulateSelection(startContent.firstChild, 10, endContent.lastChild, 7);
- const actionBox = diff.querySelector('gr-selection-action-box');
- if (!actionBox) assert.fail('action box not found');
- if (!element.selectedRange) assert.fail('no range selected');
- const {range, side} = element.selectedRange;
- assert.deepEqual(range, {
- start_line: 119,
- start_character: 10,
- end_line: 120,
- end_character: 36,
- });
- assert.equal(side, Side.RIGHT);
- assert.notOk(actionBox.positionBelow);
- });
-
- test('multiple ranges aka firefox implementation', () => {
- const startContent = stubContent(119, Side.RIGHT);
- const endContent = stubContent(120, Side.RIGHT);
- if (!startContent?.firstChild) {
- assert.fail('first child of start content not found');
- }
- if (!endContent?.lastChild) {
- assert.fail('last child of end content');
- }
-
- const startRange = document.createRange();
- startRange.setStart(startContent.firstChild, 10);
- startRange.setEnd(startContent.firstChild, 11);
-
- const endRange = document.createRange();
- endRange.setStart(endContent.lastChild, 6);
- endRange.setEnd(endContent.lastChild, 7);
-
- const getRangeAtStub = sinon.stub();
- getRangeAtStub
- .onFirstCall()
- .returns(startRange)
- .onSecondCall()
- .returns(endRange);
- const selection = {
- rangeCount: 2,
- getRangeAt: getRangeAtStub,
- removeAllRanges: sinon.stub(),
- } as unknown as Selection;
- element.handleSelection(selection, false);
- if (!element.selectedRange) assert.fail('no range selected');
- const {range} = element.selectedRange;
- assert.deepEqual(range, {
- start_line: 119,
- start_character: 10,
- end_line: 120,
- end_character: 36,
- });
- });
-
- test('multiline grow end highlight over tabs', () => {
- const startContent = stubContent(119, Side.RIGHT);
- const endContent = stubContent(120, Side.RIGHT);
- if (!startContent?.firstChild) {
- assert.fail('first child of start content not found');
- }
- if (!endContent?.firstChild) {
- assert.fail('first child of end content not found');
- }
- emulateSelection(startContent.firstChild, 10, endContent.firstChild, 2);
- if (!element.selectedRange) assert.fail('no range selected');
- const {range, side} = element.selectedRange;
- assert.deepEqual(range, {
- start_line: 119,
- start_character: 10,
- end_line: 120,
- end_character: 2,
- });
- assert.equal(side, Side.RIGHT);
- });
-
- test('collapsed', () => {
- const content = stubContent(138, Side.LEFT);
- if (!content?.firstChild) {
- assert.fail('first child of content not found');
- }
- emulateSelection(content.firstChild, 5, content.firstChild, 5);
- const sel = document.getSelection();
- if (!sel) assert.fail('no selection');
- assert.isOk(sel.getRangeAt(0).startContainer);
- assert.isFalse(!!element.selectedRange);
- });
-
- test('starts inside hl', () => {
- const content = stubContent(140, Side.LEFT);
- if (!content) {
- assert.fail('content not found');
- }
- const hl = content.querySelector('.foo');
- if (!hl?.firstChild) {
- assert.fail('first child of hl element not found');
- }
- if (!hl?.nextSibling) {
- assert.fail('next sibling of hl element not found');
- }
- emulateSelection(hl.firstChild, 2, hl.nextSibling, 7);
- if (!element.selectedRange) assert.fail('no range selected');
- const {range, side} = element.selectedRange;
- assert.deepEqual(range, {
- start_line: 140,
- start_character: 8,
- end_line: 140,
- end_character: 23,
- });
- assert.equal(side, Side.LEFT);
- });
-
- test('ends inside hl', () => {
- const content = stubContent(140, Side.LEFT);
- if (!content) assert.fail('content not found');
- const hl = content.querySelector('.bar');
- if (!hl) assert.fail('hl inside content not found');
- if (!hl.previousSibling) assert.fail('previous sibling not found');
- if (!hl.firstChild) assert.fail('first child not found');
- emulateSelection(hl.previousSibling, 2, hl.firstChild, 3);
- if (!element.selectedRange) assert.fail('no range selected');
- const {range} = element.selectedRange;
- assert.deepEqual(range, {
- start_line: 140,
- start_character: 18,
- end_line: 140,
- end_character: 27,
- });
- });
-
- test('multiple hl', () => {
- const content = stubContent(140, Side.LEFT);
- if (!content) assert.fail('content not found');
- if (!content.firstChild) assert.fail('first child not found');
- const hl = content.querySelectorAll('hl')[4];
- if (!hl) assert.fail('hl not found');
- if (!hl.firstChild) assert.fail('first child of hl not found');
- emulateSelection(content.firstChild, 2, hl.firstChild, 2);
- if (!element.selectedRange) assert.fail('no range selected');
- const {range, side} = element.selectedRange;
- assert.deepEqual(range, {
- start_line: 140,
- start_character: 2,
- end_line: 140,
- end_character: 61,
- });
- assert.equal(side, Side.LEFT);
- });
-
- test('starts outside of diff', () => {
- const contentText = stubContent(140, Side.LEFT);
- if (!contentText) assert.fail('content not found');
- if (!contentText.firstChild) assert.fail('child not found');
- const contentTd = contentText.parentElement;
- if (!contentTd) assert.fail('content td not found');
- if (!contentTd.parentElement) assert.fail('parent of td not found');
-
- emulateSelection(contentTd.parentElement, 0, contentText.firstChild, 2);
- assert.isFalse(!!element.selectedRange);
- });
-
- test('ends outside of diff', () => {
- const content = stubContent(140, Side.LEFT);
- if (!content) assert.fail('content not found');
- if (!content.firstChild) assert.fail('child not found');
- if (!content.nextElementSibling) assert.fail('sibling not found');
- if (!content.nextElementSibling.firstChild) {
- assert.fail('sibling child not found');
- }
- emulateSelection(
- content.nextElementSibling.firstChild,
- 2,
- content.firstChild,
- 2
- );
- assert.isFalse(!!element.selectedRange);
- });
-
- test('starts and ends on different sides', () => {
- const startContent = stubContent(140, Side.LEFT);
- const endContent = stubContent(130, Side.RIGHT);
- if (!startContent?.firstChild) {
- assert.fail('first child of start content not found');
- }
- if (!endContent?.firstChild) {
- assert.fail('first child of end content not found');
- }
- emulateSelection(startContent.firstChild, 2, endContent.firstChild, 2);
- assert.isFalse(!!element.selectedRange);
- });
-
- test('starts in comment thread element', () => {
- const startContent = stubContent(140, Side.LEFT);
- if (!startContent?.parentElement) {
- assert.fail('parent el of start content not found');
- }
- const comment =
- startContent.parentElement.querySelector('.comment-thread');
- if (!comment?.firstChild) {
- assert.fail('first child of comment not found');
- }
- const endContent = stubContent(141, Side.LEFT);
- if (!endContent?.firstChild) {
- assert.fail('first child of end content not found');
- }
- emulateSelection(comment.firstChild, 2, endContent.firstChild, 4);
- if (!element.selectedRange) assert.fail('no range selected');
- const {range, side} = element.selectedRange;
- assert.deepEqual(range, {
- start_line: 140,
- start_character: 83,
- end_line: 141,
- end_character: 4,
- });
- assert.equal(side, Side.LEFT);
- });
-
- test('ends in comment thread element', () => {
- const content = stubContent(140, Side.LEFT);
- if (!content?.firstChild) {
- assert.fail('first child of content not found');
- }
- if (!content?.parentElement) {
- assert.fail('parent element of content not found');
- }
- const comment = content.parentElement.querySelector('.comment-thread');
- if (!comment?.firstChild) {
- assert.fail('first child of comment element not found');
- }
- emulateSelection(content.firstChild, 4, comment.firstChild, 1);
- if (!element.selectedRange) assert.fail('no range selected');
- const {range, side} = element.selectedRange;
- assert.deepEqual(range, {
- start_line: 140,
- start_character: 4,
- end_line: 140,
- end_character: 83,
- });
- assert.equal(side, Side.LEFT);
- });
-
- test('starts in context element', () => {
- const contextControl = diff
- .querySelector('.contextControl')!
- .querySelector('gr-button');
- if (!contextControl) assert.fail('context control not found');
- const content = stubContent(146, Side.RIGHT);
- if (!content) assert.fail('content not found');
- if (!content.firstChild) assert.fail('content child not found');
- emulateSelection(contextControl, 0, content.firstChild, 7);
- // TODO (viktard): Select nearest line.
- assert.isFalse(!!element.selectedRange);
- });
-
- test('ends in context element', () => {
- const contextControl = diff
- .querySelector('.contextControl')!
- .querySelector('gr-button');
- if (!contextControl) {
- assert.fail('context control element not found');
- }
- const content = stubContent(141, Side.LEFT);
- if (!content?.firstChild) {
- assert.fail('first child of content element not found');
- }
- emulateSelection(content.firstChild, 2, contextControl, 1);
- // TODO (viktard): Select nearest line.
- assert.isFalse(!!element.selectedRange);
- });
-
- test('selection containing context element', () => {
- const startContent = stubContent(130, Side.RIGHT);
- const endContent = stubContent(146, Side.RIGHT);
- if (!startContent?.firstChild) {
- assert.fail('first child of start content not found');
- }
- if (!endContent?.firstChild) {
- assert.fail('first child of end content not found');
- }
- emulateSelection(startContent.firstChild, 3, endContent.firstChild, 14);
- if (!element.selectedRange) assert.fail('no range selected');
- const {range, side} = element.selectedRange;
- assert.deepEqual(range, {
- start_line: 130,
- start_character: 3,
- end_line: 146,
- end_character: 14,
- });
- assert.equal(side, Side.RIGHT);
- });
-
- test('ends at a tab', () => {
- const content = stubContent(140, Side.LEFT);
- if (!content?.firstChild) {
- assert.fail('first child of content element not found');
- }
- const span = content.querySelector('span');
- if (!span) assert.fail('span element not found');
- emulateSelection(content.firstChild, 1, span, 0);
- if (!element.selectedRange) assert.fail('no range selected');
- const {range, side} = element.selectedRange;
- assert.deepEqual(range, {
- start_line: 140,
- start_character: 1,
- end_line: 140,
- end_character: 51,
- });
- assert.equal(side, Side.LEFT);
- });
-
- test('starts at a tab', () => {
- const content = stubContent(140, Side.LEFT);
- if (!content) assert.fail('content element not found');
- emulateSelection(
- content.querySelectorAll('hl')[3],
- 0,
- content.querySelectorAll('span')[1].nextSibling!,
- 1
- );
- if (!element.selectedRange) assert.fail('no range selected');
- const {range, side} = element.selectedRange;
- assert.deepEqual(range, {
- start_line: 140,
- start_character: 51,
- end_line: 140,
- end_character: 71,
- });
- assert.equal(side, Side.LEFT);
- });
-
- test('properly accounts for syntax highlighting', () => {
- const content = stubContent(140, Side.LEFT);
- if (!content) assert.fail('content element not found');
- emulateSelection(
- content.querySelectorAll('hl')[3],
- 0,
- content.querySelectorAll('span')[1],
- 0
- );
- if (!element.selectedRange) assert.fail('no range selected');
- const {range, side} = element.selectedRange;
- assert.deepEqual(range, {
- start_line: 140,
- start_character: 51,
- end_line: 140,
- end_character: 69,
- });
- assert.equal(side, Side.LEFT);
- });
-
- test('GrRangeNormalizer.getTextOffset computes text offset', () => {
- let content = stubContent(140, Side.LEFT);
- if (!content) assert.fail('content element not found');
- if (!content.lastChild) assert.fail('last child of content not found');
- let child = content.lastChild.lastChild;
- if (!child) assert.fail('last child of last child of content not found');
- let result = getTextOffset(content, child);
- assert.equal(result, 75);
- content = stubContent(146, Side.RIGHT);
- if (!content) assert.fail('content element not found');
- child = content.lastChild;
- if (!child) assert.fail('child element not found');
- result = getTextOffset(content, child);
- assert.equal(result, 0);
- });
-
- test('fixTripleClickSelection', () => {
- const startContent = stubContent(119, Side.RIGHT);
- const endContent = stubContent(120, Side.RIGHT);
- if (!startContent?.firstChild) {
- assert.fail('first child of start content not found');
- }
- if (!endContent) assert.fail('end content not found');
- if (!endContent.firstChild) assert.fail('first child not found');
- emulateSelection(startContent.firstChild, 0, endContent.firstChild, 0);
- if (!element.selectedRange) assert.fail('no range selected');
- const {range, side} = element.selectedRange;
- assert.deepEqual(range, {
- start_line: 119,
- start_character: 0,
- end_line: 119,
- end_character: element.getLength(startContent),
- });
- assert.equal(side, Side.RIGHT);
- });
- });
-});
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-range-normalizer.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-range-normalizer.ts
deleted file mode 100644
index b177e14..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-range-normalizer.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-// Astral code point as per https://mathiasbynens.be/notes/javascript-unicode
-const REGEX_ASTRAL_SYMBOL = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
-
-export interface NormalizedRange {
- endContainer: Node;
- endOffset: number;
- startContainer: Node;
- startOffset: number;
-}
-
-/**
- * Remap DOM range to whole lines of a diff if necessary. If the start or
- * end containers are DOM elements that are singular pieces of syntax
- * highlighting, the containers are remapped to the .contentText divs that
- * contain the entire line of code.
- *
- * @param range - the standard DOM selector range.
- * @return A modified version of the range that correctly accounts
- * for syntax highlighting.
- */
-export function normalize(range: Range): NormalizedRange {
- const startContainer = getContentTextParent(range.startContainer);
- const startOffset =
- range.startOffset + getTextOffset(startContainer, range.startContainer);
- const endContainer = getContentTextParent(range.endContainer);
- const endOffset =
- range.endOffset + getTextOffset(endContainer, range.endContainer);
- return {
- startContainer,
- startOffset,
- endContainer,
- endOffset,
- };
-}
-
-function getContentTextParent(target: Node): Node {
- if (!target.parentElement) return target;
-
- let element: Element | null;
- if (target instanceof Element) {
- element = target;
- } else {
- element = target.parentElement;
- }
-
- while (element && !element.classList.contains('contentText')) {
- if (element.parentElement === null) {
- return target;
- }
- element = element.parentElement;
- }
- return element ? element : target;
-}
-
-/**
- * Gets the character offset of the child within the parent.
- * Performs a synchronous in-order traversal from top to bottom of the node
- * element, counting the length of the syntax until child is found.
- *
- * @param node The root DOM element to be searched through.
- * @param child The child element being searched for.
- */
-// TODO(TS): Only export for test.
-export function getTextOffset(node: Node | null, child: Node): number {
- let count = 0;
- let stack = [node];
- while (stack.length) {
- const n = stack.pop();
- if (n === child) {
- break;
- }
- if (n?.childNodes && n.childNodes.length !== 0) {
- const arr = [];
- for (const childNode of n.childNodes) {
- arr.push(childNode);
- }
- arr.reverse();
- stack = stack.concat(arr);
- } else {
- count += getLength(n);
- }
- }
- return count;
-}
-
-/**
- * The DOM API textContent.length calculation is broken when the text
- * contains Unicode. See https://mathiasbynens.be/notes/javascript-unicode .
- *
- * @param node A text node.
- * @return The length of the text.
- */
-function getLength(node?: Node | null) {
- return node && node.textContent && node.nodeType !== Node.COMMENT_NODE
- ? node.textContent.replace(REGEX_ASTRAL_SYMBOL, '_').length
- : 0;
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-model/gr-diff-model.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-model/gr-diff-model.ts
deleted file mode 100644
index 8fbda14..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-model/gr-diff-model.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * @license
- * Copyright 2023 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {Observable} from 'rxjs';
-import {filter} from 'rxjs/operators';
-import {
- DiffInfo,
- DiffPreferencesInfo,
- RenderPreferences,
-} from '../../../api/diff';
-import {define} from '../../../models/dependency';
-import {Model} from '../../../models/model';
-import {isDefined} from '../../../types/types';
-import {select} from '../../../utils/observable-util';
-
-export interface DiffState {
- diff: DiffInfo;
- path?: string;
- renderPrefs: RenderPreferences;
- diffPrefs: DiffPreferencesInfo;
-}
-
-export const diffModelToken = define<DiffModel>('diff-model');
-
-export class DiffModel extends Model<DiffState | undefined> {
- readonly diff$: Observable<DiffInfo> = select(
- this.state$.pipe(filter(isDefined)),
- diffState => diffState.diff
- );
-
- readonly path$: Observable<string | undefined> = select(
- this.state$.pipe(filter(isDefined)),
- diffState => diffState.path
- );
-
- readonly renderPrefs$: Observable<RenderPreferences> = select(
- this.state$.pipe(filter(isDefined)),
- diffState => diffState.renderPrefs
- );
-
- readonly diffPrefs$: Observable<DiffPreferencesInfo> = select(
- this.state$.pipe(filter(isDefined)),
- diffState => diffState.diffPrefs
- );
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-processor/gr-diff-processor.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-processor/gr-diff-processor.ts
deleted file mode 100644
index 256dc11..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-processor/gr-diff-processor.ts
+++ /dev/null
@@ -1,714 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {GrDiffLine, Highlights} from '../gr-diff/gr-diff-line';
-import {
- GrDiffGroup,
- GrDiffGroupType,
- hideInContextControl,
-} from '../gr-diff/gr-diff-group';
-import {DiffContent} from '../../../types/diff';
-import {Side} from '../../../constants/constants';
-import {debounce, DelayedTask} from '../../../utils/async-util';
-import {assert, assertIsDefined} from '../../../utils/common-util';
-import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
-import {FILE, GrDiffLineType, LOST, LineNumber} from '../../../api/diff';
-
-const WHOLE_FILE = -1;
-
-// visible for testing
-export interface State {
- lineNums: {
- left: number;
- right: number;
- };
- chunkIndex: number;
-}
-
-interface ChunkEnd {
- offset: number;
- keyLocation: boolean;
-}
-
-export interface KeyLocations {
- left: {[key: string]: boolean};
- right: {[key: string]: boolean};
-}
-
-/**
- * The maximum size for an addition or removal chunk before it is broken down
- * into a series of chunks that are this size at most.
- *
- * Note: The value of 120 is chosen so that it is larger than the default
- * asyncThreshold of 64, but feel free to tune this constant to your
- * performance needs.
- */
-function calcMaxGroupSize(asyncThreshold?: number): number {
- if (!asyncThreshold) return 120;
- return asyncThreshold * 2;
-}
-
-/** Interface for listening to the output of the processor. */
-export interface GroupConsumer {
- addGroup(group: GrDiffGroup): void;
- clearGroups(): void;
-}
-
-/**
- * Converts the API's `DiffContent`s to `GrDiffGroup`s for rendering.
- *
- * Glossary:
- * - "chunk": A single `DiffContent` as returned by the API.
- * - "group": A single `GrDiffGroup` as used for rendering.
- * - "common" chunk/group: A chunk/group that should be considered unchanged
- * for diffing purposes. This can mean its either actually unchanged, or it
- * has only whitespace changes.
- * - "key location": A line number and side of the diff that should not be
- * collapsed e.g. because a comment is attached to it, or because it was
- * provided in the URL and thus should be visible
- * - "uncollapsible" chunk/group: A chunk/group that is either not "common",
- * or cannot be collapsed because it contains a key location
- *
- * Here a a number of tasks this processor performs:
- * - splitting large chunks to allow more granular async rendering
- * - adding a group for the "File" pseudo line that file-level comments can
- * be attached to
- * - replacing common parts of the diff that are outside the user's
- * context setting and do not have comments with a group representing the
- * "expand context" widget. This may require splitting a chunk/group so
- * that the part that is within the context or has comments is shown, while
- * the rest is not.
- */
-export class GrDiffProcessor {
- context = 3;
-
- consumer?: GroupConsumer;
-
- keyLocations: KeyLocations = {left: {}, right: {}};
-
- asyncThreshold = 64;
-
- // visible for testing
- isScrolling?: boolean;
-
- /** Just for making sure that process() is only called once. */
- private isStarted = false;
-
- /** Indicates that processing should be stopped. */
- private isCancelled = false;
-
- private resetIsScrollingTask?: DelayedTask;
-
- private readonly handleWindowScroll = () => {
- this.isScrolling = true;
- this.resetIsScrollingTask = debounce(
- this.resetIsScrollingTask,
- () => (this.isScrolling = false),
- 50
- );
- };
-
- /**
- * Asynchronously process the diff chunks into groups. As it processes, it
- * will splice groups into the `groups` property of the component.
- *
- * @return A promise that resolves with an
- * array of GrDiffGroups when the diff is completely processed.
- */
- process(chunks: DiffContent[], isBinary: boolean) {
- assert(this.isStarted === false, 'diff processor cannot be started twice');
- this.isStarted = true;
-
- window.addEventListener('scroll', this.handleWindowScroll);
-
- assertIsDefined(this.consumer, 'consumer');
- this.consumer.clearGroups();
- this.consumer.addGroup(this.makeGroup(LOST));
- this.consumer.addGroup(this.makeGroup(FILE));
-
- if (isBinary) return Promise.resolve();
-
- return new Promise<void>(resolve => {
- const state = {
- lineNums: {left: 0, right: 0},
- chunkIndex: 0,
- };
-
- chunks = this.splitLargeChunks(chunks);
- chunks = this.splitCommonChunksWithKeyLocations(chunks);
-
- let currentBatch = 0;
- const nextStep = () => {
- if (this.isCancelled || state.chunkIndex >= chunks.length) {
- resolve();
- return;
- }
- if (this.isScrolling) {
- window.setTimeout(nextStep, 100);
- return;
- }
-
- const stateUpdate = this.processNext(state, chunks);
- for (const group of stateUpdate.groups) {
- this.consumer?.addGroup(group);
- currentBatch += group.lines.length;
- }
- state.lineNums.left += stateUpdate.lineDelta.left;
- state.lineNums.right += stateUpdate.lineDelta.right;
-
- state.chunkIndex = stateUpdate.newChunkIndex;
- if (currentBatch >= this.asyncThreshold) {
- currentBatch = 0;
- window.setTimeout(nextStep, 1);
- } else {
- nextStep.call(this);
- }
- };
-
- nextStep.call(this);
- }).finally(() => {
- this.finish();
- });
- }
-
- finish() {
- this.consumer = undefined;
- window.removeEventListener('scroll', this.handleWindowScroll);
- }
-
- cancel() {
- this.isCancelled = true;
- this.finish();
- }
-
- /**
- * Process the next uncollapsible chunk, or the next collapsible chunks.
- */
- // visible for testing
- processNext(state: State, chunks: DiffContent[]) {
- const firstUncollapsibleChunkIndex = this.firstUncollapsibleChunkIndex(
- chunks,
- state.chunkIndex
- );
- if (firstUncollapsibleChunkIndex === state.chunkIndex) {
- const chunk = chunks[state.chunkIndex];
- return {
- lineDelta: {
- left: this.linesLeft(chunk).length,
- right: this.linesRight(chunk).length,
- },
- groups: [
- this.chunkToGroup(
- chunk,
- state.lineNums.left + 1,
- state.lineNums.right + 1
- ),
- ],
- newChunkIndex: state.chunkIndex + 1,
- };
- }
-
- return this.processCollapsibleChunks(
- state,
- chunks,
- firstUncollapsibleChunkIndex
- );
- }
-
- private linesLeft(chunk: DiffContent) {
- return chunk.ab || chunk.a || [];
- }
-
- private linesRight(chunk: DiffContent) {
- return chunk.ab || chunk.b || [];
- }
-
- private firstUncollapsibleChunkIndex(chunks: DiffContent[], offset: number) {
- let chunkIndex = offset;
- while (
- chunkIndex < chunks.length &&
- this.isCollapsibleChunk(chunks[chunkIndex])
- ) {
- chunkIndex++;
- }
- return chunkIndex;
- }
-
- private isCollapsibleChunk(chunk: DiffContent) {
- return (chunk.ab || chunk.common || chunk.skip) && !chunk.keyLocation;
- }
-
- /**
- * Process a stretch of collapsible chunks.
- *
- * Outputs up to three groups:
- * 1) Visible context before the hidden common code, unless it's the
- * very beginning of the file.
- * 2) Context hidden behind a context bar, unless empty.
- * 3) Visible context after the hidden common code, unless it's the very
- * end of the file.
- */
- private processCollapsibleChunks(
- state: State,
- chunks: DiffContent[],
- firstUncollapsibleChunkIndex: number
- ) {
- const collapsibleChunks = chunks.slice(
- state.chunkIndex,
- firstUncollapsibleChunkIndex
- );
- const lineCount = collapsibleChunks.reduce(
- (sum, chunk) => sum + this.commonChunkLength(chunk),
- 0
- );
-
- let groups = this.chunksToGroups(
- collapsibleChunks,
- state.lineNums.left + 1,
- state.lineNums.right + 1
- );
-
- const hasSkippedGroup = !!groups.find(g => g.skip);
- if (this.context !== WHOLE_FILE || hasSkippedGroup) {
- const contextNumLines = this.context > 0 ? this.context : 0;
- const hiddenStart = state.chunkIndex === 0 ? 0 : contextNumLines;
- const hiddenEnd =
- lineCount -
- (firstUncollapsibleChunkIndex === chunks.length ? 0 : this.context);
- groups = hideInContextControl(groups, hiddenStart, hiddenEnd);
- }
-
- return {
- lineDelta: {
- left: lineCount,
- right: lineCount,
- },
- groups,
- newChunkIndex: firstUncollapsibleChunkIndex,
- };
- }
-
- private commonChunkLength(chunk: DiffContent) {
- if (chunk.skip) {
- return chunk.skip;
- }
- console.assert(!!chunk.ab || !!chunk.common);
-
- console.assert(
- !chunk.a || (!!chunk.b && chunk.a.length === chunk.b.length),
- 'common chunk needs same number of a and b lines: ',
- chunk
- );
- return this.linesLeft(chunk).length;
- }
-
- private chunksToGroups(
- chunks: DiffContent[],
- offsetLeft: number,
- offsetRight: number
- ): GrDiffGroup[] {
- return chunks.map(chunk => {
- const group = this.chunkToGroup(chunk, offsetLeft, offsetRight);
- const chunkLength = this.commonChunkLength(chunk);
- offsetLeft += chunkLength;
- offsetRight += chunkLength;
- return group;
- });
- }
-
- private chunkToGroup(
- chunk: DiffContent,
- offsetLeft: number,
- offsetRight: number
- ): GrDiffGroup {
- const type =
- chunk.ab || chunk.skip ? GrDiffGroupType.BOTH : GrDiffGroupType.DELTA;
- const lines = this.linesFromChunk(chunk, offsetLeft, offsetRight);
- const options = {
- moveDetails: chunk.move_details,
- dueToRebase: !!chunk.due_to_rebase,
- ignoredWhitespaceOnly: !!chunk.common,
- keyLocation: !!chunk.keyLocation,
- };
- if (chunk.skip !== undefined) {
- return new GrDiffGroup({
- type,
- skip: chunk.skip,
- offsetLeft,
- offsetRight,
- ...options,
- });
- } else {
- return new GrDiffGroup({
- type,
- lines,
- ...options,
- });
- }
- }
-
- private linesFromChunk(
- chunk: DiffContent,
- offsetLeft: number,
- offsetRight: number
- ) {
- if (chunk.ab) {
- return chunk.ab.map((row, i) =>
- this.lineFromRow(GrDiffLineType.BOTH, offsetLeft, offsetRight, row, i)
- );
- }
- let lines: GrDiffLine[] = [];
- if (chunk.a) {
- // Avoiding a.push(...b) because that causes callstack overflows for
- // large b, which can occur when large files are added removed.
- lines = lines.concat(
- this.linesFromRows(
- GrDiffLineType.REMOVE,
- chunk.a,
- offsetLeft,
- chunk.edit_a
- )
- );
- }
- if (chunk.b) {
- // Avoiding a.push(...b) because that causes callstack overflows for
- // large b, which can occur when large files are added removed.
- lines = lines.concat(
- this.linesFromRows(
- GrDiffLineType.ADD,
- chunk.b,
- offsetRight,
- chunk.edit_b
- )
- );
- }
- return lines;
- }
-
- // visible for testing
- linesFromRows(
- lineType: GrDiffLineType,
- rows: string[],
- offset: number,
- intralineInfos?: number[][]
- ): GrDiffLine[] {
- const grDiffHighlights = intralineInfos
- ? this.convertIntralineInfos(rows, intralineInfos)
- : undefined;
- return rows.map((row, i) =>
- this.lineFromRow(lineType, offset, offset, row, i, grDiffHighlights)
- );
- }
-
- private lineFromRow(
- type: GrDiffLineType,
- offsetLeft: number,
- offsetRight: number,
- row: string,
- i: number,
- highlights?: Highlights[]
- ): GrDiffLine {
- const line = new GrDiffLine(type);
- line.text = row;
- if (type !== GrDiffLineType.ADD) line.beforeNumber = offsetLeft + i;
- if (type !== GrDiffLineType.REMOVE) line.afterNumber = offsetRight + i;
- if (highlights) {
- line.hasIntralineInfo = true;
- line.highlights = highlights.filter(hl => hl.contentIndex === i);
- } else {
- line.hasIntralineInfo = false;
- }
- return line;
- }
-
- private makeGroup(number: LineNumber) {
- const line = new GrDiffLine(GrDiffLineType.BOTH);
- line.beforeNumber = number;
- line.afterNumber = number;
- return new GrDiffGroup({type: GrDiffGroupType.BOTH, lines: [line]});
- }
-
- /**
- * Split chunks into smaller chunks of the same kind.
- *
- * This is done to prevent doing too much work on the main thread in one
- * uninterrupted rendering step, which would make the browser unresponsive.
- *
- * Note that in the case of unmodified chunks, we only split chunks if the
- * context is set to file (because otherwise they are split up further down
- * the processing into the visible and hidden context), and only split it
- * into 2 chunks, one max sized one and the rest (for reasons that are
- * unclear to me).
- *
- * @param chunks Chunks as returned from the server
- * @return Finer grained chunks.
- */
- // visible for testing
- splitLargeChunks(chunks: DiffContent[]): DiffContent[] {
- const newChunks = [];
-
- for (const chunk of chunks) {
- if (!chunk.ab) {
- for (const subChunk of this.breakdownChunk(chunk)) {
- newChunks.push(subChunk);
- }
- continue;
- }
-
- // If the context is set to "whole file", then break down the shared
- // chunks so they can be rendered incrementally. Note: this is not
- // enabled for any other context preference because manipulating the
- // chunks in this way violates assumptions by the context grouper logic.
- const MAX_GROUP_SIZE = calcMaxGroupSize(this.asyncThreshold);
- if (this.context === -1 && chunk.ab.length > MAX_GROUP_SIZE * 2) {
- // Split large shared chunks in two, where the first is the maximum
- // group size.
- newChunks.push({ab: chunk.ab.slice(0, MAX_GROUP_SIZE)});
- newChunks.push({ab: chunk.ab.slice(MAX_GROUP_SIZE)});
- } else {
- newChunks.push(chunk);
- }
- }
- return newChunks;
- }
-
- /**
- * In order to show key locations, such as comments, out of the bounds of
- * the selected context, treat them as separate chunks within the model so
- * that the content (and context surrounding it) renders correctly.
- *
- * @param chunks DiffContents as returned from server.
- * @return Finer grained DiffContents.
- */
- // visible for testing
- splitCommonChunksWithKeyLocations(chunks: DiffContent[]): DiffContent[] {
- const result = [];
- let leftLineNum = 1;
- let rightLineNum = 1;
-
- for (const chunk of chunks) {
- // If it isn't a common chunk, append it as-is and update line numbers.
- if (!chunk.ab && !chunk.skip && !chunk.common) {
- if (chunk.a) {
- leftLineNum += chunk.a.length;
- }
- if (chunk.b) {
- rightLineNum += chunk.b.length;
- }
- result.push(chunk);
- continue;
- }
-
- if (chunk.common && chunk.a!.length !== chunk.b!.length) {
- throw new Error(
- 'DiffContent with common=true must always have equal length'
- );
- }
- const numLines = this.commonChunkLength(chunk);
- const chunkEnds = this.findChunkEndsAtKeyLocations(
- numLines,
- leftLineNum,
- rightLineNum
- );
- leftLineNum += numLines;
- rightLineNum += numLines;
-
- if (chunk.skip) {
- result.push({
- ...chunk,
- skip: chunk.skip,
- keyLocation: false,
- });
- } else if (chunk.ab) {
- result.push(
- ...this.splitAtChunkEnds(chunk.ab, chunkEnds).map(
- ({lines, keyLocation}) => {
- return {
- ...chunk,
- ab: lines,
- keyLocation,
- };
- }
- )
- );
- } else if (chunk.common) {
- const aChunks = this.splitAtChunkEnds(chunk.a!, chunkEnds);
- const bChunks = this.splitAtChunkEnds(chunk.b!, chunkEnds);
- result.push(
- ...aChunks.map(({lines, keyLocation}, i) => {
- return {
- ...chunk,
- a: lines,
- b: bChunks[i].lines,
- keyLocation,
- };
- })
- );
- }
- }
-
- return result;
- }
-
- /**
- * @return Offsets of the new chunk ends, including whether it's a key
- * location.
- */
- private findChunkEndsAtKeyLocations(
- numLines: number,
- leftOffset: number,
- rightOffset: number
- ): ChunkEnd[] {
- const result = [];
- let lastChunkEnd = 0;
- for (let i = 0; i < numLines; i++) {
- // If this line should not be collapsed.
- if (
- this.keyLocations[Side.LEFT][leftOffset + i] ||
- this.keyLocations[Side.RIGHT][rightOffset + i]
- ) {
- // If any lines have been accumulated into the chunk leading up to
- // this non-collapse line, then add them as a chunk and start a new
- // one.
- if (i > lastChunkEnd) {
- result.push({offset: i, keyLocation: false});
- lastChunkEnd = i;
- }
-
- // Add the non-collapse line as its own chunk.
- result.push({offset: i + 1, keyLocation: true});
- }
- }
-
- if (numLines > lastChunkEnd) {
- result.push({offset: numLines, keyLocation: false});
- }
-
- return result;
- }
-
- private splitAtChunkEnds(lines: string[], chunkEnds: ChunkEnd[]) {
- const result = [];
- let lastChunkEndOffset = 0;
- for (const {offset, keyLocation} of chunkEnds) {
- if (lastChunkEndOffset === offset) continue;
- result.push({
- lines: lines.slice(lastChunkEndOffset, offset),
- keyLocation,
- });
- lastChunkEndOffset = offset;
- }
- return result;
- }
-
- /**
- * Converts `IntralineInfo`s return by the API to `GrLineHighlights` used
- * for rendering.
- */
- // visible for testing
- convertIntralineInfos(
- rows: string[],
- intralineInfos: number[][]
- ): Highlights[] {
- // +1 to account for the \n that is not part of the rows passed here
- const lineLengths = rows.map(r => GrAnnotation.getStringLength(r) + 1);
-
- let rowIndex = 0;
- let idx = 0;
- const normalized = [];
- for (const [skipLength, markLength] of intralineInfos) {
- let lineLength = lineLengths[rowIndex];
- let j = 0;
- while (j < skipLength) {
- if (idx === lineLength) {
- idx = 0;
- lineLength = lineLengths[++rowIndex];
- continue;
- }
- idx++;
- j++;
- }
- let lineHighlight: Highlights = {
- contentIndex: rowIndex,
- startIndex: idx,
- };
-
- j = 0;
- while (lineLength && j < markLength) {
- if (idx === lineLength) {
- idx = 0;
- lineLength = lineLengths[++rowIndex];
- normalized.push(lineHighlight);
- lineHighlight = {
- contentIndex: rowIndex,
- startIndex: idx,
- };
- continue;
- }
- idx++;
- j++;
- }
- lineHighlight.endIndex = idx;
- normalized.push(lineHighlight);
- }
- return normalized;
- }
-
- /**
- * If a group is an addition or a removal, break it down into smaller groups
- * of that type using the MAX_GROUP_SIZE. If the group is a shared chunk
- * or a delta it is returned as the single element of the result array.
- */
- // visible for testing
- breakdownChunk(chunk: DiffContent): DiffContent[] {
- let key: 'a' | 'b' | 'ab' | null = null;
- const {a, b, ab, move_details} = chunk;
- if (a?.length && !b?.length) {
- key = 'a';
- } else if (b?.length && !a?.length) {
- key = 'b';
- } else if (ab?.length) {
- key = 'ab';
- }
-
- // Move chunks should not be divided because of move label
- // positioned in the top of the chunk
- if (!key || move_details) {
- return [chunk];
- }
-
- const MAX_GROUP_SIZE = calcMaxGroupSize(this.asyncThreshold);
- return this.breakdown(chunk[key]!, MAX_GROUP_SIZE).map(subChunkLines => {
- const subChunk: DiffContent = {};
- subChunk[key!] = subChunkLines;
- if (chunk.due_to_rebase) {
- subChunk.due_to_rebase = true;
- }
- if (chunk.move_details) {
- subChunk.move_details = chunk.move_details;
- }
- return subChunk;
- });
- }
-
- /**
- * Given an array and a size, return an array of arrays where no inner array
- * is larger than that size, preserving the original order.
- */
- // visible for testing
- breakdown<T>(array: T[], size: number): T[][] {
- if (!array.length) {
- return [];
- }
- if (array.length < size) {
- return [array];
- }
-
- const head = array.slice(0, array.length - size);
- const tail = array.slice(array.length - size);
-
- return this.breakdown(head, size).concat([tail]);
- }
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-processor/gr-diff-processor_test.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-processor/gr-diff-processor_test.ts
deleted file mode 100644
index 335f0d0..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-processor/gr-diff-processor_test.ts
+++ /dev/null
@@ -1,1136 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup';
-import './gr-diff-processor';
-import {GrDiffLine} from '../gr-diff/gr-diff-line';
-import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {GrDiffProcessor, State} from './gr-diff-processor';
-import {DiffContent} from '../../../types/diff';
-import {assert} from '@open-wc/testing';
-import {FILE, GrDiffLineType} from '../../../api/diff';
-
-suite('gr-diff-processor tests', () => {
- const WHOLE_FILE = -1;
- const loremIpsum =
- 'Lorem ipsum dolor sit amet, ei nonumes vituperata ius. ' +
- 'Duo animal omnesque fabellas et. Id has phaedrum dignissim ' +
- 'deterruisset, pro ei petentium comprehensam, ut vis solum dicta. ' +
- 'Eos cu aliquam labores qualisque, usu postea inermis te, et solum ' +
- 'fugit assum per.';
-
- let element: GrDiffProcessor;
- let groups: GrDiffGroup[];
-
- setup(() => {});
-
- suite('not logged in', () => {
- setup(() => {
- groups = [];
- element = new GrDiffProcessor();
- element.consumer = {
- addGroup(group: GrDiffGroup) {
- groups.push(group);
- },
- clearGroups() {
- groups = [];
- },
- };
- element.context = 4;
- });
-
- test('process loaded content', () => {
- const content: DiffContent[] = [
- {
- ab: ['<!DOCTYPE html>', '<meta charset="utf-8">'],
- },
- {
- a: [' Welcome ', ' to the wooorld of tomorrow!'],
- b: [' Hello, world!'],
- },
- {
- ab: [
- 'Leela: This is the only place the ship can’t hear us, so ',
- 'everyone pretend to shower.',
- 'Fry: Same as every day. Got it.',
- ],
- },
- ];
-
- return element.process(content, false).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
- assert.equal(groups.length, 4);
-
- let group = groups[0];
- assert.equal(group.type, GrDiffGroupType.BOTH);
- assert.equal(group.lines.length, 1);
- assert.equal(group.lines[0].text, '');
- assert.equal(group.lines[0].beforeNumber, FILE);
- assert.equal(group.lines[0].afterNumber, FILE);
-
- group = groups[1];
- assert.equal(group.type, GrDiffGroupType.BOTH);
- assert.equal(group.lines.length, 2);
-
- function beforeNumberFn(l: GrDiffLine) {
- return l.beforeNumber;
- }
- function afterNumberFn(l: GrDiffLine) {
- return l.afterNumber;
- }
- function textFn(l: GrDiffLine) {
- return l.text;
- }
-
- assert.deepEqual(group.lines.map(beforeNumberFn), [1, 2]);
- assert.deepEqual(group.lines.map(afterNumberFn), [1, 2]);
- assert.deepEqual(group.lines.map(textFn), [
- '<!DOCTYPE html>',
- '<meta charset="utf-8">',
- ]);
-
- group = groups[2];
- assert.equal(group.type, GrDiffGroupType.DELTA);
- assert.equal(group.lines.length, 3);
- assert.equal(group.adds.length, 1);
- assert.equal(group.removes.length, 2);
- assert.deepEqual(group.removes.map(beforeNumberFn), [3, 4]);
- assert.deepEqual(group.adds.map(afterNumberFn), [3]);
- assert.deepEqual(group.removes.map(textFn), [
- ' Welcome ',
- ' to the wooorld of tomorrow!',
- ]);
- assert.deepEqual(group.adds.map(textFn), [' Hello, world!']);
-
- group = groups[3];
- assert.equal(group.type, GrDiffGroupType.BOTH);
- assert.equal(group.lines.length, 3);
- assert.deepEqual(group.lines.map(beforeNumberFn), [5, 6, 7]);
- assert.deepEqual(group.lines.map(afterNumberFn), [4, 5, 6]);
- assert.deepEqual(group.lines.map(textFn), [
- 'Leela: This is the only place the ship can’t hear us, so ',
- 'everyone pretend to shower.',
- 'Fry: Same as every day. Got it.',
- ]);
- });
- });
-
- test('first group is for file', () => {
- const content = [{b: ['foo']}];
-
- return element.process(content, false).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
-
- assert.equal(groups[0].type, GrDiffGroupType.BOTH);
- assert.equal(groups[0].lines.length, 1);
- assert.equal(groups[0].lines[0].text, '');
- assert.equal(groups[0].lines[0].beforeNumber, FILE);
- assert.equal(groups[0].lines[0].afterNumber, FILE);
- });
- });
-
- suite('context groups', () => {
- test('at the beginning, larger than context', () => {
- element.context = 10;
- const content = [
- {
- ab: Array.from<string>({length: 100}).fill(
- 'all work and no play make jack a dull boy'
- ),
- },
- {a: ['all work and no play make andybons a dull boy']},
- ];
-
- return element.process(content, false).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
-
- // group[0] is the file group
-
- assert.equal(groups[1].type, GrDiffGroupType.CONTEXT_CONTROL);
- assert.instanceOf(groups[1].contextGroups[0], GrDiffGroup);
- assert.equal(groups[1].contextGroups[0].lines.length, 90);
- for (const l of groups[1].contextGroups[0].lines) {
- assert.equal(l.text, 'all work and no play make jack a dull boy');
- }
-
- assert.equal(groups[2].type, GrDiffGroupType.BOTH);
- assert.equal(groups[2].lines.length, 10);
- for (const l of groups[2].lines) {
- assert.equal(l.text, 'all work and no play make jack a dull boy');
- }
- });
- });
-
- test('at the beginning with skip chunks', async () => {
- element.context = 10;
- const content = [
- {
- ab: Array.from<string>({length: 20}).fill(
- 'all work and no play make jack a dull boy'
- ),
- },
- {skip: 43900},
- {ab: Array.from<string>({length: 30}).fill('some other content')},
- {a: ['some other content']},
- ];
-
- await element.process(content, false);
-
- groups.shift(); // remove portedThreadsWithoutRangeGroup
-
- // group[0] is the file group
-
- const commonGroup = groups[1];
-
- // Hidden context before
- assert.equal(commonGroup.type, GrDiffGroupType.CONTEXT_CONTROL);
- assert.instanceOf(commonGroup.contextGroups[0], GrDiffGroup);
- assert.equal(commonGroup.contextGroups[0].lines.length, 20);
- for (const l of commonGroup.contextGroups[0].lines) {
- assert.equal(l.text, 'all work and no play make jack a dull boy');
- }
-
- // Skipped group
- const skipGroup = commonGroup.contextGroups[1];
- assert.equal(skipGroup.skip, 43900);
- const expectedRange = {
- left: {start_line: 21, end_line: 43920},
- right: {start_line: 21, end_line: 43920},
- };
- assert.deepEqual(skipGroup.lineRange, expectedRange);
-
- // Hidden context after
- assert.equal(commonGroup.contextGroups[2].lines.length, 20);
- for (const l of commonGroup.contextGroups[2].lines) {
- assert.equal(l.text, 'some other content');
- }
-
- // Displayed lines
- assert.equal(groups[2].type, GrDiffGroupType.BOTH);
- assert.equal(groups[2].lines.length, 10);
- for (const l of groups[2].lines) {
- assert.equal(l.text, 'some other content');
- }
- });
-
- test('at the beginning, smaller than context', () => {
- element.context = 10;
- const content = [
- {
- ab: Array.from<string>({length: 5}).fill(
- 'all work and no play make jack a dull boy'
- ),
- },
- {a: ['all work and no play make andybons a dull boy']},
- ];
-
- return element.process(content, false).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
-
- // group[0] is the file group
-
- assert.equal(groups[1].type, GrDiffGroupType.BOTH);
- assert.equal(groups[1].lines.length, 5);
- for (const l of groups[1].lines) {
- assert.equal(l.text, 'all work and no play make jack a dull boy');
- }
- });
- });
-
- test('at the end, larger than context', () => {
- element.context = 10;
- const content = [
- {a: ['all work and no play make andybons a dull boy']},
- {
- ab: Array.from<string>({length: 100}).fill(
- 'all work and no play make jill a dull girl'
- ),
- },
- ];
-
- return element.process(content, false).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
-
- // group[0] is the file group
- // group[1] is the "a" group
-
- assert.equal(groups[2].type, GrDiffGroupType.BOTH);
- assert.equal(groups[2].lines.length, 10);
- for (const l of groups[2].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
-
- assert.equal(groups[3].type, GrDiffGroupType.CONTEXT_CONTROL);
- assert.instanceOf(groups[3].contextGroups[0], GrDiffGroup);
- assert.equal(groups[3].contextGroups[0].lines.length, 90);
- for (const l of groups[3].contextGroups[0].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- });
- });
-
- test('at the end, smaller than context', () => {
- element.context = 10;
- const content = [
- {a: ['all work and no play make andybons a dull boy']},
- {
- ab: Array.from<string>({length: 5}).fill(
- 'all work and no play make jill a dull girl'
- ),
- },
- ];
-
- return element.process(content, false).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
-
- // group[0] is the file group
- // group[1] is the "a" group
-
- assert.equal(groups[2].type, GrDiffGroupType.BOTH);
- assert.equal(groups[2].lines.length, 5);
- for (const l of groups[2].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- });
- });
-
- test('for interleaved ab and common: true chunks', () => {
- element.context = 10;
- const content = [
- {a: ['all work and no play make andybons a dull boy']},
- {
- ab: Array.from<string>({length: 3}).fill(
- 'all work and no play make jill a dull girl'
- ),
- },
- {
- a: Array.from<string>({length: 3}).fill(
- 'all work and no play make jill a dull girl'
- ),
- b: Array.from<string>({length: 3}).fill(
- ' all work and no play make jill a dull girl'
- ),
- common: true,
- },
- {
- ab: Array.from<string>({length: 3}).fill(
- 'all work and no play make jill a dull girl'
- ),
- },
- {
- a: Array.from<string>({length: 3}).fill(
- 'all work and no play make jill a dull girl'
- ),
- b: Array.from<string>({length: 3}).fill(
- ' all work and no play make jill a dull girl'
- ),
- common: true,
- },
- {
- ab: Array.from<string>({length: 3}).fill(
- 'all work and no play make jill a dull girl'
- ),
- },
- ];
-
- return element.process(content, false).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
-
- // group[0] is the file group
- // group[1] is the "a" group
-
- // The first three interleaved chunks are completely shown because
- // they are part of the context (3 * 3 <= 10)
-
- assert.equal(groups[2].type, GrDiffGroupType.BOTH);
- assert.equal(groups[2].lines.length, 3);
- for (const l of groups[2].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
-
- assert.equal(groups[3].type, GrDiffGroupType.DELTA);
- assert.equal(groups[3].lines.length, 6);
- assert.equal(groups[3].adds.length, 3);
- assert.equal(groups[3].removes.length, 3);
- for (const l of groups[3].removes) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- for (const l of groups[3].adds) {
- assert.equal(
- l.text,
- ' all work and no play make jill a dull girl'
- );
- }
-
- assert.equal(groups[4].type, GrDiffGroupType.BOTH);
- assert.equal(groups[4].lines.length, 3);
- for (const l of groups[4].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
-
- // The next chunk is partially shown, so it results in two groups
-
- assert.equal(groups[5].type, GrDiffGroupType.DELTA);
- assert.equal(groups[5].lines.length, 2);
- assert.equal(groups[5].adds.length, 1);
- assert.equal(groups[5].removes.length, 1);
- for (const l of groups[5].removes) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- for (const l of groups[5].adds) {
- assert.equal(
- l.text,
- ' all work and no play make jill a dull girl'
- );
- }
-
- assert.equal(groups[6].type, GrDiffGroupType.CONTEXT_CONTROL);
- assert.equal(groups[6].contextGroups.length, 2);
-
- assert.equal(groups[6].contextGroups[0].lines.length, 4);
- assert.equal(groups[6].contextGroups[0].removes.length, 2);
- assert.equal(groups[6].contextGroups[0].adds.length, 2);
- for (const l of groups[6].contextGroups[0].removes) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- for (const l of groups[6].contextGroups[0].adds) {
- assert.equal(
- l.text,
- ' all work and no play make jill a dull girl'
- );
- }
-
- // The final chunk is completely hidden
- assert.equal(groups[6].contextGroups[1].type, GrDiffGroupType.BOTH);
- assert.equal(groups[6].contextGroups[1].lines.length, 3);
- for (const l of groups[6].contextGroups[1].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- });
- });
-
- test('in the middle, larger than context', () => {
- element.context = 10;
- const content = [
- {a: ['all work and no play make andybons a dull boy']},
- {
- ab: Array.from<string>({length: 100}).fill(
- 'all work and no play make jill a dull girl'
- ),
- },
- {a: ['all work and no play make andybons a dull boy']},
- ];
-
- return element.process(content, false).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
-
- // group[0] is the file group
- // group[1] is the "a" group
-
- assert.equal(groups[2].type, GrDiffGroupType.BOTH);
- assert.equal(groups[2].lines.length, 10);
- for (const l of groups[2].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
-
- assert.equal(groups[3].type, GrDiffGroupType.CONTEXT_CONTROL);
- assert.instanceOf(groups[3].contextGroups[0], GrDiffGroup);
- assert.equal(groups[3].contextGroups[0].lines.length, 80);
- for (const l of groups[3].contextGroups[0].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
-
- assert.equal(groups[4].type, GrDiffGroupType.BOTH);
- assert.equal(groups[4].lines.length, 10);
- for (const l of groups[4].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- });
- });
-
- test('in the middle, smaller than context', () => {
- element.context = 10;
- const content = [
- {a: ['all work and no play make andybons a dull boy']},
- {
- ab: Array.from<string>({length: 5}).fill(
- 'all work and no play make jill a dull girl'
- ),
- },
- {a: ['all work and no play make andybons a dull boy']},
- ];
-
- return element.process(content, false).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
-
- // group[0] is the file group
- // group[1] is the "a" group
-
- assert.equal(groups[2].type, GrDiffGroupType.BOTH);
- assert.equal(groups[2].lines.length, 5);
- for (const l of groups[2].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- });
- });
- });
-
- test('in the middle with skip chunks', async () => {
- element.context = 10;
- const content = [
- {a: ['all work and no play make andybons a dull boy']},
- {
- ab: Array.from<string>({length: 20}).fill(
- 'all work and no play make jill a dull girl'
- ),
- },
- {skip: 60},
- {
- ab: Array.from<string>({length: 20}).fill(
- 'all work and no play make jill a dull girl'
- ),
- },
- {a: ['all work and no play make andybons a dull boy']},
- ];
-
- await element.process(content, false);
-
- groups.shift(); // remove portedThreadsWithoutRangeGroup
-
- // group[0] is the file group
- // group[1] is the chunk with a
- // group[2] is the displayed part of ab before
-
- const commonGroup = groups[3];
-
- // Hidden context before
- assert.equal(commonGroup.type, GrDiffGroupType.CONTEXT_CONTROL);
- assert.instanceOf(commonGroup.contextGroups[0], GrDiffGroup);
- assert.equal(commonGroup.contextGroups[0].lines.length, 10);
- for (const l of commonGroup.contextGroups[0].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
-
- // Skipped group
- const skipGroup = commonGroup.contextGroups[1];
- assert.equal(skipGroup.skip, 60);
- const expectedRange = {
- left: {start_line: 22, end_line: 81},
- right: {start_line: 21, end_line: 80},
- };
- assert.deepEqual(skipGroup.lineRange, expectedRange);
-
- // Hidden context after
- assert.equal(commonGroup.contextGroups[2].lines.length, 10);
- for (const l of commonGroup.contextGroups[2].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- // group[4] is the displayed part of the second ab
- });
-
- test('works with skip === 0', async () => {
- element.context = 3;
- const content = [
- {
- skip: 0,
- },
- {
- b: [
- '/**',
- ' * @license',
- ' * Copyright 2015 Google LLC',
- ' * SPDX-License-Identifier: Apache-2.0',
- ' */',
- "import '../../../test/common-test-setup';",
- ],
- },
- ];
- await element.process(content, false);
- });
-
- test('break up common diff chunks', () => {
- element.keyLocations = {
- left: {1: true},
- right: {10: true},
- };
-
- const content = [
- {
- ab: [
- 'copy',
- '',
- 'asdf',
- 'qwer',
- 'zxcv',
- '',
- 'http',
- '',
- 'vbnm',
- 'dfgh',
- 'yuio',
- 'sdfg',
- '1234',
- ],
- },
- ];
- const result = element.splitCommonChunksWithKeyLocations(content);
- assert.deepEqual(result, [
- {
- ab: ['copy'],
- keyLocation: true,
- },
- {
- ab: ['', 'asdf', 'qwer', 'zxcv', '', 'http', '', 'vbnm'],
- keyLocation: false,
- },
- {
- ab: ['dfgh'],
- keyLocation: true,
- },
- {
- ab: ['yuio', 'sdfg', '1234'],
- keyLocation: false,
- },
- ]);
- });
-
- test('breaks down shared chunks w/ whole-file', () => {
- const maxGroupSize = 128;
- const size = maxGroupSize * 2 + 5;
- const ab = Array(size)
- .fill(0)
- .map(() => `${Math.random()}`);
- const content = [{ab}];
- element.context = -1;
- const result = element.splitLargeChunks(content);
- assert.equal(result.length, 2);
- assert.deepEqual(result[0].ab, content[0].ab.slice(0, maxGroupSize));
- assert.deepEqual(result[1].ab, content[0].ab.slice(maxGroupSize));
- });
-
- test('breaks down added chunks', () => {
- const maxGroupSize = 128;
- const size = maxGroupSize * 2 + 5;
- const content = Array(size)
- .fill(0)
- .map(() => `${Math.random()}`);
- element.context = 5;
- const splitContent = element
- .splitLargeChunks([{a: [], b: content}])
- .map(r => r.b);
- assert.equal(splitContent.length, 3);
- assert.deepEqual(splitContent[0], content.slice(0, 5));
- assert.deepEqual(splitContent[1], content.slice(5, maxGroupSize + 5));
- assert.deepEqual(splitContent[2], content.slice(maxGroupSize + 5));
- });
-
- test('breaks down removed chunks', () => {
- const maxGroupSize = 128;
- const size = maxGroupSize * 2 + 5;
- const content = Array(size)
- .fill(0)
- .map(() => `${Math.random()}`);
- element.context = 5;
- const splitContent = element
- .splitLargeChunks([{a: content, b: []}])
- .map(r => r.a);
- assert.equal(splitContent.length, 3);
- assert.deepEqual(splitContent[0], content.slice(0, 5));
- assert.deepEqual(splitContent[1], content.slice(5, maxGroupSize + 5));
- assert.deepEqual(splitContent[2], content.slice(maxGroupSize + 5));
- });
-
- test('does not break down moved chunks', () => {
- const size = 120 * 2 + 5;
- const content = Array(size)
- .fill(0)
- .map(() => `${Math.random()}`);
- element.context = 5;
- const splitContent = element
- .splitLargeChunks([
- {
- a: content,
- b: [],
- move_details: {changed: false, range: {start: 1, end: 1}},
- },
- ])
- .map(r => r.a);
- assert.equal(splitContent.length, 1);
- assert.deepEqual(splitContent[0], content);
- });
-
- test('does not break-down common chunks w/ context', () => {
- const ab = Array(75)
- .fill(0)
- .map(() => `${Math.random()}`);
- const content = [{ab}];
- element.context = 4;
- const result = element.splitCommonChunksWithKeyLocations(content);
- assert.equal(result.length, 1);
- assert.deepEqual(result[0].ab, content[0].ab);
- assert.isFalse(result[0].keyLocation);
- });
-
- test('intraline normalization', () => {
- // The content and highlights are in the format returned by the Gerrit
- // REST API.
- let content = [
- ' <section class="summary">',
- ' <gr-formatted-text content="' +
- '[[_computeCurrentRevisionMessage(change)]]"></gr-formatted-text>',
- ' </section>',
- ];
- let highlights = [
- [31, 34],
- [42, 26],
- ];
-
- let results = element.convertIntralineInfos(content, highlights);
- assert.deepEqual(results, [
- {
- contentIndex: 0,
- startIndex: 31,
- },
- {
- contentIndex: 1,
- startIndex: 0,
- endIndex: 33,
- },
- {
- contentIndex: 1,
- endIndex: 101,
- startIndex: 75,
- },
- ]);
- const lines = element.linesFromRows(
- GrDiffLineType.BOTH,
- content,
- 0,
- highlights
- );
- assert.equal(lines.length, 3);
- assert.isTrue(lines[0].hasIntralineInfo);
- assert.equal(lines[0].highlights.length, 1);
- assert.isTrue(lines[1].hasIntralineInfo);
- assert.equal(lines[1].highlights.length, 2);
- assert.isTrue(lines[2].hasIntralineInfo);
- assert.equal(lines[2].highlights.length, 0);
-
- content = [
- ' this._path = value.path;',
- '',
- ' // When navigating away from the page, there is a ' +
- 'possibility that the',
- ' // patch number is no longer a part of the URL ' +
- '(say when navigating to',
- ' // the top-level change info view) and therefore ' +
- 'undefined in `params`.',
- ' if (!this._patchRange.patchNum) {',
- ];
- highlights = [
- [14, 17],
- [11, 70],
- [12, 67],
- [12, 67],
- [14, 29],
- ];
- results = element.convertIntralineInfos(content, highlights);
- assert.deepEqual(results, [
- {
- contentIndex: 0,
- startIndex: 14,
- endIndex: 31,
- },
- {
- contentIndex: 2,
- startIndex: 8,
- endIndex: 78,
- },
- {
- contentIndex: 3,
- startIndex: 11,
- endIndex: 78,
- },
- {
- contentIndex: 4,
- startIndex: 11,
- endIndex: 78,
- },
- {
- contentIndex: 5,
- startIndex: 12,
- endIndex: 41,
- },
- ]);
-
- content = ['🙈 a', '🙉 b', '🙊 c'];
- highlights = [[2, 7]];
- results = element.convertIntralineInfos(content, highlights);
- assert.deepEqual(results, [
- {
- contentIndex: 0,
- startIndex: 2,
- },
- {
- contentIndex: 1,
- startIndex: 0,
- },
- {
- contentIndex: 2,
- startIndex: 0,
- endIndex: 1,
- },
- ]);
- });
-
- test('isScrolling paused', () => {
- const content = Array(200).fill({ab: ['', '']});
- element.isScrolling = true;
- element.process(content, false);
- // Just the FILE and LOST groups.
- assert.equal(groups.length, 2);
- });
-
- test('isScrolling unpaused', () => {
- const content = Array(200).fill({ab: ['', '']});
- element.isScrolling = false;
- element.process(content, false);
- // More groups have been processed. How many does not matter here.
- assert.isAtLeast(groups.length, 3);
- });
-
- test('image diffs', () => {
- const content = Array(200).fill({ab: ['', '']});
- element.process(content, true);
- assert.equal(groups.length, 2);
-
- // Image diffs don't process content, just the 'FILE' line.
- assert.equal(groups[0].lines.length, 1);
- });
-
- suite('processNext', () => {
- let rows: string[];
-
- setup(() => {
- rows = loremIpsum.split(' ');
- });
-
- test('WHOLE_FILE', () => {
- element.context = WHOLE_FILE;
- const state: State = {
- lineNums: {left: 10, right: 100},
- chunkIndex: 1,
- };
- const chunks = [{a: ['foo']}, {ab: rows}, {a: ['bar']}];
- const result = element.processNext(state, chunks);
-
- // Results in one, uncollapsed group with all rows.
- assert.equal(result.groups.length, 1);
- assert.equal(result.groups[0].type, GrDiffGroupType.BOTH);
- assert.equal(result.groups[0].lines.length, rows.length);
-
- // Line numbers are set correctly.
- assert.equal(
- result.groups[0].lines[0].beforeNumber,
- state.lineNums.left + 1
- );
- assert.equal(
- result.groups[0].lines[0].afterNumber,
- state.lineNums.right + 1
- );
-
- assert.equal(
- result.groups[0].lines[rows.length - 1].beforeNumber,
- state.lineNums.left + rows.length
- );
- assert.equal(
- result.groups[0].lines[rows.length - 1].afterNumber,
- state.lineNums.right + rows.length
- );
- });
-
- test('WHOLE_FILE with skip chunks still get collapsed', () => {
- element.context = WHOLE_FILE;
- const lineNums = {left: 10, right: 100};
- const state = {
- lineNums,
- chunkIndex: 1,
- };
- const skip = 10000;
- const chunks = [{a: ['foo']}, {skip}, {ab: rows}, {a: ['bar']}];
- const result = element.processNext(state, chunks);
- // Results in one, uncollapsed group with all rows.
- assert.equal(result.groups.length, 1);
- assert.equal(result.groups[0].type, GrDiffGroupType.CONTEXT_CONTROL);
-
- // Skip and ab group are hidden in the same context control
- assert.equal(result.groups[0].contextGroups.length, 2);
- const [skippedGroup, abGroup] = result.groups[0].contextGroups;
-
- // Line numbers are set correctly.
- assert.deepEqual(skippedGroup.lineRange, {
- left: {
- start_line: lineNums.left + 1,
- end_line: lineNums.left + skip,
- },
- right: {
- start_line: lineNums.right + 1,
- end_line: lineNums.right + skip,
- },
- });
-
- assert.deepEqual(abGroup.lineRange, {
- left: {
- start_line: lineNums.left + skip + 1,
- end_line: lineNums.left + skip + rows.length,
- },
- right: {
- start_line: lineNums.right + skip + 1,
- end_line: lineNums.right + skip + rows.length,
- },
- });
- });
-
- test('with context', () => {
- element.context = 10;
- const state = {
- lineNums: {left: 10, right: 100},
- chunkIndex: 1,
- };
- const chunks = [{a: ['foo']}, {ab: rows}, {a: ['bar']}];
- const result = element.processNext(state, chunks);
- const expectedCollapseSize = rows.length - 2 * element.context;
-
- assert.equal(result.groups.length, 3, 'Results in three groups');
-
- // The first and last are uncollapsed context, whereas the middle has
- // a single context-control line.
- assert.equal(result.groups[0].lines.length, element.context);
- assert.equal(result.groups[2].lines.length, element.context);
-
- // The collapsed group has the hidden lines as its context group.
- assert.equal(
- result.groups[1].contextGroups[0].lines.length,
- expectedCollapseSize
- );
- });
-
- test('first', () => {
- element.context = 10;
- const state = {
- lineNums: {left: 10, right: 100},
- chunkIndex: 0,
- };
- const chunks = [{ab: rows}, {a: ['foo']}, {a: ['bar']}];
- const result = element.processNext(state, chunks);
- const expectedCollapseSize = rows.length - element.context;
-
- assert.equal(result.groups.length, 2, 'Results in two groups');
-
- // Only the first group is collapsed.
- assert.equal(result.groups[1].lines.length, element.context);
-
- // The collapsed group has the hidden lines as its context group.
- assert.equal(
- result.groups[0].contextGroups[0].lines.length,
- expectedCollapseSize
- );
- });
-
- test('few-rows', () => {
- // Only ten rows.
- rows = rows.slice(0, 10);
- element.context = 10;
- const state = {
- lineNums: {left: 10, right: 100},
- chunkIndex: 0,
- };
- const chunks = [{ab: rows}, {a: ['foo']}, {a: ['bar']}];
- const result = element.processNext(state, chunks);
-
- // Results in one uncollapsed group with all rows.
- assert.equal(result.groups.length, 1, 'Results in one group');
- assert.equal(result.groups[0].lines.length, rows.length);
- });
-
- test('no single line collapse', () => {
- rows = rows.slice(0, 7);
- element.context = 3;
- const state = {
- lineNums: {left: 10, right: 100},
- chunkIndex: 1,
- };
- const chunks = [{a: ['foo']}, {ab: rows}, {a: ['bar']}];
- const result = element.processNext(state, chunks);
-
- // Results in one uncollapsed group with all rows.
- assert.equal(result.groups.length, 1, 'Results in one group');
- assert.equal(result.groups[0].lines.length, rows.length);
- });
-
- suite('with key location', () => {
- let state: State;
- let chunks: DiffContent[];
-
- setup(() => {
- state = {
- lineNums: {left: 10, right: 100},
- chunkIndex: 0,
- };
- element.context = 10;
- chunks = [{ab: rows}, {ab: ['foo'], keyLocation: true}, {ab: rows}];
- });
-
- test('context before', () => {
- state.chunkIndex = 0;
- const result = element.processNext(state, chunks);
-
- // The first chunk is split into two groups:
- // 1) A context-control, hiding everything but the context before
- // the key location.
- // 2) The context before the key location.
- // The key location is not processed in this call to processNext
- assert.equal(result.groups.length, 2);
- // The collapsed group has the hidden lines as its context group.
- assert.equal(
- result.groups[0].contextGroups[0].lines.length,
- rows.length - element.context
- );
- assert.equal(result.groups[1].lines.length, element.context);
- });
-
- test('key location itself', () => {
- state.chunkIndex = 1;
- const result = element.processNext(state, chunks);
-
- // The second chunk results in a single group, that is just the
- // line with the key location
- assert.equal(result.groups.length, 1);
- assert.equal(result.groups[0].lines.length, 1);
- assert.equal(result.lineDelta.left, 1);
- assert.equal(result.lineDelta.right, 1);
- });
-
- test('context after', () => {
- state.chunkIndex = 2;
- const result = element.processNext(state, chunks);
-
- // The last chunk is split into two groups:
- // 1) The context after the key location.
- // 1) A context-control, hiding everything but the context after the
- // key location.
- assert.equal(result.groups.length, 2);
- assert.equal(result.groups[0].lines.length, element.context);
- // The collapsed group has the hidden lines as its context group.
- assert.equal(
- result.groups[1].contextGroups[0].lines.length,
- rows.length - element.context
- );
- });
- });
- });
-
- suite('gr-diff-processor helpers', () => {
- let rows: string[];
-
- setup(() => {
- rows = loremIpsum.split(' ');
- });
-
- test('linesFromRows', () => {
- const startLineNum = 10;
- let result = element.linesFromRows(
- GrDiffLineType.ADD,
- rows,
- startLineNum + 1
- );
-
- assert.equal(result.length, rows.length);
- assert.equal(result[0].type, GrDiffLineType.ADD);
- assert.notOk(result[0].hasIntralineInfo);
- assert.equal(result[0].afterNumber, startLineNum + 1);
- assert.notOk(result[0].beforeNumber);
- assert.equal(
- result[result.length - 1].afterNumber,
- startLineNum + rows.length
- );
- assert.notOk(result[result.length - 1].beforeNumber);
-
- result = element.linesFromRows(
- GrDiffLineType.REMOVE,
- rows,
- startLineNum + 1
- );
-
- assert.equal(result.length, rows.length);
- assert.equal(result[0].type, GrDiffLineType.REMOVE);
- assert.notOk(result[0].hasIntralineInfo);
- assert.equal(result[0].beforeNumber, startLineNum + 1);
- assert.notOk(result[0].afterNumber);
- assert.equal(
- result[result.length - 1].beforeNumber,
- startLineNum + rows.length
- );
- assert.notOk(result[result.length - 1].afterNumber);
- });
- });
-
- suite('breakdown*', () => {
- test('breakdownChunk breaks down additions', () => {
- const breakdownSpy = sinon.spy(element, 'breakdown');
- const chunk = {b: ['blah', 'blah', 'blah']};
- const result = element.breakdownChunk(chunk);
- assert.deepEqual(result, [chunk]);
- assert.isTrue(breakdownSpy.called);
- });
-
- test('breakdownChunk keeps due_to_rebase for broken down additions', () => {
- sinon.spy(element, 'breakdown');
- const chunk = {b: ['blah', 'blah', 'blah'], due_to_rebase: true};
- const result = element.breakdownChunk(chunk);
- for (const subResult of result) {
- assert.isTrue(subResult.due_to_rebase);
- }
- });
-
- test('breakdown common case', () => {
- const array = 'Lorem ipsum dolor sit amet, suspendisse inceptos'.split(
- ' '
- );
- const size = 3;
-
- const result = element.breakdown(array, size);
-
- for (const subResult of result) {
- assert.isAtMost(subResult.length, size);
- }
- const flattened = result.reduce((a, b) => a.concat(b), []);
- assert.deepEqual(flattened, array);
- });
-
- test('breakdown smaller than size', () => {
- const array = 'Lorem ipsum dolor sit amet, suspendisse inceptos'.split(
- ' '
- );
- const size = 10;
- const expected = [array];
-
- const result = element.breakdown(array, size);
-
- assert.deepEqual(result, expected);
- });
-
- test('breakdown empty', () => {
- const array: string[] = [];
- const size = 10;
- const expected: string[][] = [];
-
- const result = element.breakdown(array, size);
-
- assert.deepEqual(result, expected);
- });
- });
- });
-});
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-selection/gr-diff-selection.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-selection/gr-diff-selection.ts
deleted file mode 100644
index 407b403..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-selection/gr-diff-selection.ts
+++ /dev/null
@@ -1,189 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../styles/shared-styles';
-import {normalize} from '../gr-diff-highlight/gr-range-normalizer';
-import {
- descendedFromClass,
- parentWithClass,
- querySelectorAll,
-} from '../../../utils/dom-util';
-import {DiffInfo} from '../../../types/diff';
-import {Side} from '../../../constants/constants';
-import {
- getLineElByChild,
- getSide,
- getSideByLineEl,
- isThreadEl,
-} from '../../diff/gr-diff/gr-diff-utils';
-import {getContentFromDiff} from '../../../utils/diff-util';
-
-/**
- * Possible CSS classes indicating the state of selection. Dynamically added/
- * removed based on where the user clicks within the diff.
- */
-const SelectionClass = {
- COMMENT: 'selected-comment',
- LEFT: 'selected-left',
- RIGHT: 'selected-right',
- BLAME: 'selected-blame',
-};
-
-function selectionClassForSide(side?: Side) {
- return side === Side.LEFT ? SelectionClass.LEFT : SelectionClass.RIGHT;
-}
-
-export class GrDiffSelection {
- // visible for testing
- diff?: DiffInfo;
-
- // visible for testing
- diffTable?: HTMLElement;
-
- init(diff: DiffInfo, diffTable: HTMLElement) {
- this.cleanup();
- this.diff = diff;
- this.diffTable = diffTable;
- this.diffTable.classList.add(SelectionClass.RIGHT);
- this.diffTable.addEventListener('copy', this.handleCopy);
- this.diffTable.addEventListener('mousedown', this.handleDown);
- }
-
- cleanup() {
- if (!this.diffTable) return;
- this.diffTable.removeEventListener('copy', this.handleCopy);
- this.diffTable.removeEventListener('mousedown', this.handleDown);
- }
-
- handleDown = (e: Event) => {
- const target = e.target;
- if (!(target instanceof Element)) return;
-
- const commentEl = parentWithClass(target, 'comment-thread', this.diffTable);
- if (commentEl && isThreadEl(commentEl)) {
- this.setClasses([
- SelectionClass.COMMENT,
- selectionClassForSide(getSide(commentEl)),
- ]);
- return;
- }
-
- const blameSelected = descendedFromClass(target, 'blame', this.diffTable);
- if (blameSelected) {
- this.setClasses([SelectionClass.BLAME]);
- return;
- }
-
- // This works for both, the content and the line number cells.
- const lineEl = getLineElByChild(target);
- if (lineEl) {
- this.setClasses([selectionClassForSide(getSideByLineEl(lineEl))]);
- return;
- }
- };
-
- /**
- * Set the provided list of classes on the element, to the exclusion of all
- * other SelectionClass values.
- */
- setClasses(targetClasses: string[]) {
- if (!this.diffTable) return;
- // Remove any selection classes that do not belong.
- for (const className of Object.values(SelectionClass)) {
- if (!targetClasses.includes(className)) {
- this.diffTable.classList.remove(className);
- }
- }
- // Add new selection classes iff they are not already present.
- for (const targetClass of targetClasses) {
- if (!this.diffTable.classList.contains(targetClass)) {
- this.diffTable.classList.add(targetClass);
- }
- }
- }
-
- handleCopy = (e: ClipboardEvent) => {
- const target = e.composedPath()[0];
- if (!(target instanceof Element)) return;
- if (target instanceof HTMLTextAreaElement) return;
- if (!descendedFromClass(target, 'diff-row', this.diffTable)) return;
- if (!this.diffTable) return;
- if (this.diffTable.classList.contains(SelectionClass.COMMENT)) return;
-
- const lineEl = getLineElByChild(target);
- if (!lineEl) return;
- const side = getSideByLineEl(lineEl);
- const text = this.getSelectedText(side);
- if (text && e.clipboardData) {
- e.clipboardData.setData('Text', text);
- e.preventDefault();
- }
- };
-
- getSelection() {
- const diffHosts = querySelectorAll(document.body, 'gr-diff');
- if (!diffHosts.length) return document.getSelection();
-
- const curDiffHost = diffHosts.find(diffHost => {
- if (!diffHost?.shadowRoot?.getSelection) return false;
- const selection = diffHost.shadowRoot.getSelection();
- // Pick the one with valid selection:
- // https://developer.mozilla.org/en-US/docs/Web/API/Selection/type
- return selection && selection.type !== 'None';
- });
-
- return curDiffHost?.shadowRoot?.getSelection
- ? curDiffHost.shadowRoot.getSelection()
- : document.getSelection();
- }
-
- /**
- * Get the text of the current selection. If commentSelected is
- * true, it returns only the text of comments within the selection.
- * Otherwise it returns the text of the selected diff region.
- *
- * @param side The side that is selected.
- * @param commentSelected Whether or not a comment is selected.
- * @return The selected text.
- */
- getSelectedText(side: Side) {
- if (!this.diff) return '';
- const sel = this.getSelection();
- if (!sel || sel.rangeCount !== 1) {
- return ''; // No multi-select support yet.
- }
- const range = normalize(sel.getRangeAt(0));
- const startLineEl = getLineElByChild(range.startContainer);
- if (!startLineEl) return;
- const endLineEl = getLineElByChild(range.endContainer);
- // Happens when triple click in side-by-side mode with other side empty.
- const endsAtOtherEmptySide =
- !endLineEl &&
- range.endOffset === 0 &&
- range.endContainer.nodeName === 'TD' &&
- range.endContainer instanceof HTMLTableCellElement &&
- (range.endContainer.classList.contains('left') ||
- range.endContainer.classList.contains('right'));
- const startLineDataValue = startLineEl.getAttribute('data-value');
- if (!startLineDataValue) return;
- const startLineNum = Number(startLineDataValue);
- let endLineNum;
- if (endsAtOtherEmptySide) {
- endLineNum = startLineNum + 1;
- } else if (endLineEl) {
- const endLineDataValue = endLineEl.getAttribute('data-value');
- if (endLineDataValue) endLineNum = Number(endLineDataValue);
- }
-
- return getContentFromDiff(
- this.diff,
- startLineNum,
- range.startOffset,
- endLineNum,
- range.endOffset,
- side
- );
- }
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-selection/gr-diff-selection_test.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-selection/gr-diff-selection_test.ts
deleted file mode 100644
index f216e04..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-selection/gr-diff-selection_test.ts
+++ /dev/null
@@ -1,219 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup';
-import './gr-diff-selection';
-import '../gr-diff/gr-diff';
-import '../../../elements/shared/gr-comment-thread/gr-comment-thread';
-import {GrDiffSelection} from './gr-diff-selection';
-import {createDiff} from '../../../test/test-data-generators';
-import {DiffInfo, Side} from '../../../api/diff';
-import {fixture, html, assert} from '@open-wc/testing';
-import {mouseDown} from '../../../test/test-utils';
-import {GrDiff} from '../gr-diff/gr-diff';
-import {waitForEventOnce} from '../../../utils/event-util';
-import {createDefaultDiffPrefs} from '../../../constants/constants';
-
-function firstTextNode(el: HTMLElement) {
- return [...el.childNodes].filter(node => node.nodeType === Node.TEXT_NODE)[0];
-}
-
-suite('gr-diff-selection', () => {
- let element: GrDiffSelection;
- let diffTable: HTMLElement;
- let grDiff: GrDiff;
-
- const emulateCopyOn = function (target: HTMLElement | null) {
- const fakeEvent = {
- target,
- preventDefault: sinon.stub(),
- composedPath() {
- return [target];
- },
- clipboardData: {
- setData: sinon.stub(),
- },
- };
- element.handleCopy(fakeEvent as unknown as ClipboardEvent);
- return fakeEvent;
- };
-
- setup(async () => {
- grDiff = await fixture<GrDiff>(html`<gr-diff></gr-diff>`);
- element = grDiff.diffSelection;
-
- const diff: DiffInfo = {
- ...createDiff(),
- content: [
- {
- a: ['ba ba'],
- b: ['some other text'],
- },
- {
- a: ['zin'],
- b: ['more more more'],
- },
- {
- a: ['ga ga'],
- b: ['some other text'],
- },
- ],
- };
- grDiff.prefs = createDefaultDiffPrefs();
- grDiff.diff = diff;
- await waitForEventOnce(grDiff, 'render');
- assert.isOk(element.diffTable);
- diffTable = element.diffTable!;
- });
-
- test('applies selected-left on left side click', () => {
- diffTable.classList.add('selected-right');
- const lineNumberEl = diffTable.querySelector<HTMLElement>('.lineNum.left');
- if (!lineNumberEl) assert.fail('line number element missing');
- mouseDown(lineNumberEl);
- assert.isTrue(
- diffTable.classList.contains('selected-left'),
- 'adds selected-left'
- );
- assert.isFalse(
- diffTable.classList.contains('selected-right'),
- 'removes selected-right'
- );
- });
-
- test('applies selected-right on right side click', () => {
- diffTable.classList.add('selected-left');
- const lineNumberEl = diffTable.querySelector<HTMLElement>('.lineNum.right');
- if (!lineNumberEl) assert.fail('line number element missing');
- mouseDown(lineNumberEl);
- assert.isTrue(
- diffTable.classList.contains('selected-right'),
- 'adds selected-right'
- );
- assert.isFalse(
- diffTable.classList.contains('selected-left'),
- 'removes selected-left'
- );
- });
-
- test('applies selected-blame on blame click', () => {
- diffTable.classList.add('selected-left');
- const blameDiv = document.createElement('div');
- blameDiv.classList.add('blame');
- diffTable.appendChild(blameDiv);
- mouseDown(blameDiv);
- assert.isTrue(
- diffTable.classList.contains('selected-blame'),
- 'adds selected-right'
- );
- assert.isFalse(
- diffTable.classList.contains('selected-left'),
- 'removes selected-left'
- );
- });
-
- test('ignores copy for non-content Element', () => {
- const getSelectedTextStub = sinon.stub(element, 'getSelectedText');
- emulateCopyOn(diffTable.querySelector('.not-diff-row'));
- assert.isFalse(getSelectedTextStub.called);
- });
-
- test('asks for text for left side Elements', () => {
- const getSelectedTextStub = sinon.stub(element, 'getSelectedText');
- emulateCopyOn(diffTable.querySelector('div.contentText'));
- assert.deepEqual([Side.LEFT], getSelectedTextStub.lastCall.args);
- });
-
- test('reacts to copy for content Elements', () => {
- const getSelectedTextStub = sinon.stub(element, 'getSelectedText');
- emulateCopyOn(diffTable.querySelector('div.contentText'));
- assert.isTrue(getSelectedTextStub.called);
- });
-
- test('copy event is prevented for content Elements', () => {
- const getSelectedTextStub = sinon.stub(element, 'getSelectedText');
- getSelectedTextStub.returns('test');
- const event = emulateCopyOn(diffTable.querySelector('div.contentText'));
- assert.isTrue(event.preventDefault.called);
- });
-
- test('inserts text into clipboard on copy', () => {
- sinon.stub(element, 'getSelectedText').returns('the text');
- const event = emulateCopyOn(diffTable.querySelector('div.contentText'));
- assert.deepEqual(
- ['Text', 'the text'],
- event.clipboardData.setData.lastCall.args
- );
- });
-
- test('setClasses adds given SelectionClass values, removes others', () => {
- diffTable.classList.add('selected-right');
- element.setClasses(['selected-comment', 'selected-left']);
- assert.isTrue(diffTable.classList.contains('selected-comment'));
- assert.isTrue(diffTable.classList.contains('selected-left'));
- assert.isFalse(diffTable.classList.contains('selected-right'));
- assert.isFalse(diffTable.classList.contains('selected-blame'));
-
- element.setClasses(['selected-blame']);
- assert.isFalse(diffTable.classList.contains('selected-comment'));
- assert.isFalse(diffTable.classList.contains('selected-left'));
- assert.isFalse(diffTable.classList.contains('selected-right'));
- assert.isTrue(diffTable.classList.contains('selected-blame'));
- });
-
- test('setClasses removes before it ads', () => {
- diffTable.classList.add('selected-right');
- const addStub = sinon.stub(diffTable.classList, 'add');
- const removeStub = sinon
- .stub(diffTable.classList, 'remove')
- .callsFake(() => {
- assert.isFalse(addStub.called);
- });
- element.setClasses(['selected-comment', 'selected-left']);
- assert.isTrue(addStub.called);
- assert.isTrue(removeStub.called);
- });
-
- test('copies content correctly', () => {
- diffTable.classList.add('selected-left');
- diffTable.classList.remove('selected-right');
-
- const selection = document.getSelection();
- if (selection === null) assert.fail('no selection');
- selection.removeAllRanges();
- const range = document.createRange();
- const texts = diffTable.querySelectorAll<HTMLElement>('gr-diff-text');
- range.setStart(firstTextNode(texts[0]), 3);
- range.setEnd(firstTextNode(texts[4]), 2);
- selection.addRange(range);
-
- assert.equal(element.getSelectedText(Side.LEFT), 'ba\nzin\nga');
- });
-
- test('defers to default behavior for textarea', () => {
- diffTable.classList.add('selected-left');
- diffTable.classList.remove('selected-right');
- const selectedTextSpy = sinon.spy(element, 'getSelectedText');
- emulateCopyOn(diffTable.querySelector('textarea'));
-
- assert.isFalse(selectedTextSpy.called);
- });
-
- test('regression test for 4794', () => {
- diffTable.classList.add('selected-right');
- diffTable.classList.remove('selected-left');
-
- const selection = document.getSelection();
- if (!selection) assert.fail('no selection');
- selection.removeAllRanges();
- const range = document.createRange();
- const texts = diffTable.querySelectorAll<HTMLElement>('gr-diff-text');
- range.setStart(firstTextNode(texts[1]), 4);
- range.setEnd(firstTextNode(texts[1]), 10);
- selection.addRange(range);
-
- assert.equal(element.getSelectedText(Side.RIGHT), ' other');
- });
-});
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff-group.ts b/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff-group.ts
deleted file mode 100644
index 771e298..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff-group.ts
+++ /dev/null
@@ -1,520 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {BLANK_LINE, GrDiffLine} from './gr-diff-line';
-import {GrDiffLineType, LineNumber, LineRange, Side} from '../../../api/diff';
-import {assertIsDefined, assert} from '../../../utils/common-util';
-import {untilRendered} from '../../../utils/dom-util';
-import {isDefined} from '../../../types/types';
-import {LitElement} from 'lit';
-
-export enum GrDiffGroupType {
- /** Unchanged context. */
- BOTH = 'both',
-
- /** A widget used to show more context. */
- CONTEXT_CONTROL = 'contextControl',
-
- /** Added, removed or modified chunk. */
- DELTA = 'delta',
-}
-
-export interface GrDiffLinePair {
- left: GrDiffLine;
- right: GrDiffLine;
-}
-
-/**
- * Hides lines in the given range behind a context control group.
- *
- * Groups that would be partially visible are split into their visible and
- * hidden parts, respectively.
- * The groups need to be "common groups", meaning they have to have either
- * originated from an `ab` chunk, or from an `a`+`b` chunk with
- * `common: true`.
- *
- * If the hidden range is 3 lines or less, nothing is hidden and no context
- * control group is created.
- *
- * @param groups Common groups, ordered by their line ranges.
- * @param hiddenStart The first element to be hidden, as a
- * non-negative line number offset relative to the first group's start
- * line, left and right respectively.
- * @param hiddenEnd The first visible element after the hidden range,
- * as a non-negative line number offset relative to the first group's
- * start line, left and right respectively.
- */
-export function hideInContextControl(
- groups: readonly GrDiffGroup[],
- hiddenStart: number,
- hiddenEnd: number
-): GrDiffGroup[] {
- if (groups.length === 0) return [];
- // Clamp hiddenStart and hiddenEnd - inspired by e.g. substring
- hiddenStart = Math.max(hiddenStart, 0);
- hiddenEnd = Math.max(hiddenEnd, hiddenStart);
-
- let before: GrDiffGroup[] = [];
- let hidden = groups;
- let after: readonly GrDiffGroup[] = [];
-
- const numHidden = hiddenEnd - hiddenStart;
-
- // Showing a context control row for less than 4 lines does not make much,
- // because then that row would consume as much space as the collapsed code.
- if (numHidden > 3) {
- if (hiddenStart) {
- [before, hidden] = splitCommonGroups(hidden, hiddenStart);
- }
- if (hiddenEnd) {
- let beforeLength = 0;
- if (before.length > 0) {
- const beforeStart = before[0].lineRange.left.start_line;
- const beforeEnd = before[before.length - 1].lineRange.left.end_line;
- beforeLength = beforeEnd - beforeStart + 1;
- }
- [hidden, after] = splitCommonGroups(hidden, hiddenEnd - beforeLength);
- }
- } else {
- [hidden, after] = [[], hidden];
- }
-
- const result = [...before];
- if (hidden.length) {
- result.push(
- new GrDiffGroup({
- type: GrDiffGroupType.CONTEXT_CONTROL,
- contextGroups: [...hidden],
- })
- );
- }
- result.push(...after);
- return result;
-}
-
-/**
- * Splits a group in two, defined by leftSplit and rightSplit. Primarily to be
- * used in function splitCommonGroups
- * Groups with some lines before and some lines after the split will be split
- * into two groups, which will be put into the first and second list.
- *
- * @param group The group to be split in two
- * @param leftSplit The line number relative to the split on the left side
- * @param rightSplit The line number relative to the split on the right side
- * @return two new groups, one before the split and another after it
- */
-function splitGroupInTwo(
- group: GrDiffGroup,
- leftSplit: number,
- rightSplit: number
-) {
- let beforeSplit: GrDiffGroup | undefined;
- let afterSplit: GrDiffGroup | undefined;
- // split line is in the middle of a group, we need to break the group
- // in lines before and after the split.
- if (group.skip) {
- // Currently we assume skip chunks "refuse" to be split. Expanding this
- // group will in the future mean load more data - and therefore we want to
- // fire an event when user wants to do it.
- const closerToStartThanEnd =
- leftSplit - group.lineRange.left.start_line <
- group.lineRange.right.end_line - leftSplit;
- if (closerToStartThanEnd) {
- afterSplit = group;
- } else {
- beforeSplit = group;
- }
- } else {
- const before = [];
- const after = [];
- for (const line of group.lines) {
- if (
- (line.beforeNumber &&
- typeof line.beforeNumber === 'number' &&
- line.beforeNumber < leftSplit) ||
- (line.afterNumber &&
- typeof line.afterNumber === 'number' &&
- line.afterNumber < rightSplit)
- ) {
- before.push(line);
- } else {
- after.push(line);
- }
- }
- if (before.length) {
- beforeSplit =
- before.length === group.lines.length
- ? group
- : group.cloneWithLines(before);
- }
- if (after.length) {
- afterSplit =
- after.length === group.lines.length
- ? group
- : group.cloneWithLines(after);
- }
- }
- return {beforeSplit, afterSplit};
-}
-
-/**
- * Splits a list of common groups into two lists of groups.
- *
- * Groups where all lines are before or all lines are after the split will be
- * retained as is and put into the first or second list respectively. Groups
- * with some lines before and some lines after the split will be split into
- * two groups, which will be put into the first and second list.
- *
- * @param split A line number offset relative to the first group's
- * start line at which the groups should be split.
- * @return The outer array has 2 elements, the
- * list of groups before and the list of groups after the split.
- */
-function splitCommonGroups(
- groups: readonly GrDiffGroup[],
- split: number
-): GrDiffGroup[][] {
- if (groups.length === 0) return [[], []];
- const leftSplit = groups[0].lineRange.left.start_line + split;
- const rightSplit = groups[0].lineRange.right.start_line + split;
-
- const beforeGroups = [];
- const afterGroups = [];
- for (const group of groups) {
- const isCompletelyBefore =
- group.lineRange.left.end_line < leftSplit ||
- group.lineRange.right.end_line < rightSplit;
- const isCompletelyAfter =
- leftSplit <= group.lineRange.left.start_line ||
- rightSplit <= group.lineRange.right.start_line;
- if (isCompletelyBefore) {
- beforeGroups.push(group);
- } else if (isCompletelyAfter) {
- afterGroups.push(group);
- } else {
- const {beforeSplit, afterSplit} = splitGroupInTwo(
- group,
- leftSplit,
- rightSplit
- );
- if (beforeSplit) {
- beforeGroups.push(beforeSplit);
- }
- if (afterSplit) {
- afterGroups.push(afterSplit);
- }
- }
- }
- return [beforeGroups, afterGroups];
-}
-
-export interface GrMoveDetails {
- changed: boolean;
- range?: {
- start: number;
- end: number;
- };
-}
-
-/** A chunk of the diff that should be rendered together. */
-export class GrDiffGroup {
- constructor(
- options:
- | {
- type: GrDiffGroupType.BOTH | GrDiffGroupType.DELTA;
- lines?: GrDiffLine[];
- skip?: undefined;
- moveDetails?: GrMoveDetails;
- dueToRebase?: boolean;
- ignoredWhitespaceOnly?: boolean;
- keyLocation?: boolean;
- }
- | {
- type: GrDiffGroupType.BOTH | GrDiffGroupType.DELTA;
- lines?: undefined;
- skip: number;
- offsetLeft: number;
- offsetRight: number;
- moveDetails?: GrMoveDetails;
- dueToRebase?: boolean;
- ignoredWhitespaceOnly?: boolean;
- keyLocation?: boolean;
- }
- | {
- type: GrDiffGroupType.CONTEXT_CONTROL;
- contextGroups: GrDiffGroup[];
- }
- ) {
- this.type = options.type;
- switch (options.type) {
- case GrDiffGroupType.BOTH:
- case GrDiffGroupType.DELTA: {
- this.moveDetails = options.moveDetails;
- this.dueToRebase = options.dueToRebase ?? false;
- this.ignoredWhitespaceOnly = options.ignoredWhitespaceOnly ?? false;
- this.keyLocation = options.keyLocation ?? false;
- if (options.skip && options.lines) {
- throw new Error('Cannot set skip and lines');
- }
- this.skip = options.skip;
- if (options.skip !== undefined) {
- this.lineRange = {
- left: {
- start_line: options.offsetLeft,
- end_line: options.offsetLeft + options.skip - 1,
- },
- right: {
- start_line: options.offsetRight,
- end_line: options.offsetRight + options.skip - 1,
- },
- };
- } else {
- assertIsDefined(options.lines);
- assert(options.lines.length > 0, 'diff group must have lines');
- for (const line of options.lines) {
- this.addLine(line);
- }
- }
- break;
- }
- case GrDiffGroupType.CONTEXT_CONTROL: {
- this.contextGroups = options.contextGroups;
- if (this.contextGroups.length > 0) {
- const firstGroup = this.contextGroups[0];
- const lastGroup = this.contextGroups[this.contextGroups.length - 1];
- this.lineRange = {
- left: {
- start_line: firstGroup.lineRange.left.start_line,
- end_line: lastGroup.lineRange.left.end_line,
- },
- right: {
- start_line: firstGroup.lineRange.right.start_line,
- end_line: lastGroup.lineRange.right.end_line,
- },
- };
- }
- break;
- }
- default:
- throw new Error(`Unknown group type: ${this.type}`);
- }
- }
-
- readonly type: GrDiffGroupType;
-
- readonly dueToRebase: boolean = false;
-
- /**
- * True means all changes in this line are whitespace changes that should
- * not be highlighted as changed as per the user settings.
- */
- readonly ignoredWhitespaceOnly: boolean = false;
-
- /**
- * True means it should not be collapsed (because it was in the URL, or
- * there is a comment on that line)
- */
- readonly keyLocation: boolean = false;
-
- /**
- * Once rendered the diff builder sets this to the diff section element.
- */
- element?: HTMLElement;
-
- readonly lines: GrDiffLine[] = [];
-
- readonly adds: GrDiffLine[] = [];
-
- readonly removes: GrDiffLine[] = [];
-
- readonly contextGroups: GrDiffGroup[] = [];
-
- readonly skip?: number;
-
- /** Both start and end line are inclusive. */
- readonly lineRange: {[side in Side]: LineRange} = {
- [Side.LEFT]: {start_line: 0, end_line: 0},
- [Side.RIGHT]: {start_line: 0, end_line: 0},
- };
-
- readonly moveDetails?: GrMoveDetails;
-
- /**
- * Creates a new group with the same properties but different lines.
- *
- * The element property is not copied, because the original element is still a
- * rendering of the old lines, so that would not make sense.
- */
- cloneWithLines(lines: GrDiffLine[]): GrDiffGroup {
- if (
- this.type !== GrDiffGroupType.BOTH &&
- this.type !== GrDiffGroupType.DELTA
- ) {
- throw new Error('Cannot clone context group with lines');
- }
- const group = new GrDiffGroup({
- type: this.type,
- lines,
- dueToRebase: this.dueToRebase,
- ignoredWhitespaceOnly: this.ignoredWhitespaceOnly,
- });
- return group;
- }
-
- private addLine(line: GrDiffLine) {
- this.lines.push(line);
-
- const notDelta =
- this.type === GrDiffGroupType.BOTH ||
- this.type === GrDiffGroupType.CONTEXT_CONTROL;
- if (
- notDelta &&
- (line.type === GrDiffLineType.ADD || line.type === GrDiffLineType.REMOVE)
- ) {
- throw Error('Cannot add delta line to a non-delta group.');
- }
-
- if (line.type === GrDiffLineType.ADD) {
- this.adds.push(line);
- } else if (line.type === GrDiffLineType.REMOVE) {
- this.removes.push(line);
- }
- this._updateRangeWithNewLine(line);
- }
-
- getSideBySidePairs(): GrDiffLinePair[] {
- if (
- this.type === GrDiffGroupType.BOTH ||
- this.type === GrDiffGroupType.CONTEXT_CONTROL
- ) {
- return this.lines.map(line => {
- return {left: line, right: line};
- });
- }
-
- const pairs: GrDiffLinePair[] = [];
- let i = 0;
- let j = 0;
- while (i < this.removes.length || j < this.adds.length) {
- pairs.push({
- left: this.removes[i] || BLANK_LINE,
- right: this.adds[j] || BLANK_LINE,
- });
- i++;
- j++;
- }
- return pairs;
- }
-
- getUnifiedPairs(): GrDiffLinePair[] {
- return this.lines
- .map(line => {
- if (line.type === GrDiffLineType.ADD) {
- return {left: BLANK_LINE, right: line};
- }
- if (line.type === GrDiffLineType.REMOVE) {
- if (this.ignoredWhitespaceOnly) return undefined;
- return {left: line, right: BLANK_LINE};
- }
- return {left: line, right: line};
- })
- .filter(isDefined);
- }
-
- /** Returns true if it is, or contains, a skip group. */
- hasSkipGroup() {
- return (
- this.skip !== undefined ||
- this.contextGroups?.some(g => g.skip !== undefined)
- );
- }
-
- containsLine(side: Side, line: LineNumber) {
- if (typeof line !== 'number') {
- // For FILE and LOST, beforeNumber and afterNumber are the same
- return this.lines[0]?.beforeNumber === line;
- }
- const lineRange = this.lineRange[side];
- return lineRange.start_line <= line && line <= lineRange.end_line;
- }
-
- startLine(side: Side): LineNumber {
- // For both CONTEXT_CONTROL groups and SKIP groups the `lines` array will
- // be empty. So we have to use `lineRange` instead of looking at the first
- // line.
- if (
- this.type === GrDiffGroupType.CONTEXT_CONTROL ||
- this.skip !== undefined
- ) {
- return side === Side.LEFT
- ? this.lineRange.left.start_line
- : this.lineRange.right.start_line;
- }
- // For "normal" groups we could also use the `lineRange`, but for FILE or
- // LOST lines we want to return FILE or LOST. The `lineRange` contains
- // numbers only.
- return this.lines[0].lineNumber(side);
- }
-
- private _updateRangeWithNewLine(line: GrDiffLine) {
- if (typeof line.beforeNumber !== 'number') return;
- if (typeof line.afterNumber !== 'number') return;
-
- if (line.type === GrDiffLineType.ADD || line.type === GrDiffLineType.BOTH) {
- if (
- this.lineRange.right.start_line === 0 ||
- line.afterNumber < this.lineRange.right.start_line
- ) {
- this.lineRange.right.start_line = line.afterNumber;
- }
- if (line.afterNumber > this.lineRange.right.end_line) {
- this.lineRange.right.end_line = line.afterNumber;
- }
- }
-
- if (
- line.type === GrDiffLineType.REMOVE ||
- line.type === GrDiffLineType.BOTH
- ) {
- if (
- this.lineRange.left.start_line === 0 ||
- line.beforeNumber < this.lineRange.left.start_line
- ) {
- this.lineRange.left.start_line = line.beforeNumber;
- }
- if (line.beforeNumber > this.lineRange.left.end_line) {
- this.lineRange.left.end_line = line.beforeNumber;
- }
- }
- }
-
- async waitUntilRendered() {
- const lineNumber = this.lines[0]?.beforeNumber;
- // The LOST or FILE lines may be hidden and thus never resolve an
- // untilRendered() promise.
- if (
- this.skip !== undefined ||
- typeof lineNumber !== 'number' ||
- this.type === GrDiffGroupType.CONTEXT_CONTROL
- ) {
- return Promise.resolve();
- }
- assertIsDefined(this.element);
- await (this.element as LitElement).updateComplete;
- await untilRendered(this.element.firstElementChild as HTMLElement);
- }
-
- /**
- * Determines whether the group is either totally an addition or totally
- * a removal.
- */
- isTotal(): boolean {
- return (
- this.type === GrDiffGroupType.DELTA &&
- (!this.adds.length || !this.removes.length) &&
- !(!this.adds.length && !this.removes.length)
- );
- }
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff-group_test.ts b/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff-group_test.ts
deleted file mode 100644
index bbbb4ad..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff-group_test.ts
+++ /dev/null
@@ -1,314 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup';
-import {GrDiffLine, BLANK_LINE} from './gr-diff-line';
-import {
- GrDiffGroup,
- GrDiffGroupType,
- hideInContextControl,
-} from './gr-diff-group';
-import {assert} from '@open-wc/testing';
-import {FILE, GrDiffLineType, LOST, Side} from '../../../api/diff';
-
-suite('gr-diff-group tests', () => {
- test('delta line pairs', () => {
- const l1 = new GrDiffLine(GrDiffLineType.ADD, 0, 128);
- const l2 = new GrDiffLine(GrDiffLineType.ADD, 0, 129);
- const l3 = new GrDiffLine(GrDiffLineType.REMOVE, 64, 0);
- let group = new GrDiffGroup({
- type: GrDiffGroupType.DELTA,
- lines: [l1, l2, l3],
- });
- assert.deepEqual(group.lines, [l1, l2, l3]);
- assert.deepEqual(group.adds, [l1, l2]);
- assert.deepEqual(group.removes, [l3]);
- assert.deepEqual(group.lineRange, {
- left: {start_line: 64, end_line: 64},
- right: {start_line: 128, end_line: 129},
- });
-
- let pairs = group.getSideBySidePairs();
- assert.deepEqual(pairs, [
- {left: l3, right: l1},
- {left: BLANK_LINE, right: l2},
- ]);
-
- group = new GrDiffGroup({type: GrDiffGroupType.DELTA, lines: [l1, l2, l3]});
- assert.deepEqual(group.lines, [l1, l2, l3]);
- assert.deepEqual(group.adds, [l1, l2]);
- assert.deepEqual(group.removes, [l3]);
-
- pairs = group.getSideBySidePairs();
- assert.deepEqual(pairs, [
- {left: l3, right: l1},
- {left: BLANK_LINE, right: l2},
- ]);
- });
-
- test('group must have lines', () => {
- try {
- new GrDiffGroup({type: GrDiffGroupType.BOTH});
- } catch (e) {
- // expected
- return;
- }
- assert.fail('a standard diff group cannot be empty');
- });
-
- test('group/header line pairs', () => {
- const l1 = new GrDiffLine(GrDiffLineType.BOTH, 64, 128);
- const l2 = new GrDiffLine(GrDiffLineType.BOTH, 65, 129);
- const l3 = new GrDiffLine(GrDiffLineType.BOTH, 66, 130);
-
- const group = new GrDiffGroup({
- type: GrDiffGroupType.BOTH,
- lines: [l1, l2, l3],
- });
-
- assert.deepEqual(group.lines, [l1, l2, l3]);
- assert.deepEqual(group.adds, []);
- assert.deepEqual(group.removes, []);
-
- assert.deepEqual(group.lineRange, {
- left: {start_line: 64, end_line: 66},
- right: {start_line: 128, end_line: 130},
- });
-
- const pairs = group.getSideBySidePairs();
- assert.deepEqual(pairs, [
- {left: l1, right: l1},
- {left: l2, right: l2},
- {left: l3, right: l3},
- ]);
- });
-
- test('adding delta lines to non-delta group', () => {
- const l1 = new GrDiffLine(GrDiffLineType.ADD);
- const l2 = new GrDiffLine(GrDiffLineType.REMOVE);
- const l3 = new GrDiffLine(GrDiffLineType.BOTH);
-
- assert.throws(
- () => new GrDiffGroup({type: GrDiffGroupType.BOTH, lines: [l1, l2, l3]})
- );
- });
-
- suite('hideInContextControl', () => {
- let groups: GrDiffGroup[];
- setup(() => {
- groups = [
- new GrDiffGroup({
- type: GrDiffGroupType.BOTH,
- lines: [
- new GrDiffLine(GrDiffLineType.BOTH, 5, 7),
- new GrDiffLine(GrDiffLineType.BOTH, 6, 8),
- new GrDiffLine(GrDiffLineType.BOTH, 7, 9),
- ],
- }),
- new GrDiffGroup({
- type: GrDiffGroupType.DELTA,
- lines: [
- new GrDiffLine(GrDiffLineType.REMOVE, 8),
- new GrDiffLine(GrDiffLineType.ADD, 0, 10),
- new GrDiffLine(GrDiffLineType.REMOVE, 9),
- new GrDiffLine(GrDiffLineType.ADD, 0, 11),
- new GrDiffLine(GrDiffLineType.REMOVE, 10),
- new GrDiffLine(GrDiffLineType.ADD, 0, 12),
- new GrDiffLine(GrDiffLineType.REMOVE, 11),
- new GrDiffLine(GrDiffLineType.ADD, 0, 13),
- ],
- }),
- new GrDiffGroup({
- type: GrDiffGroupType.BOTH,
- lines: [
- new GrDiffLine(GrDiffLineType.BOTH, 12, 14),
- new GrDiffLine(GrDiffLineType.BOTH, 13, 15),
- new GrDiffLine(GrDiffLineType.BOTH, 14, 16),
- ],
- }),
- ];
- });
-
- test('hides hidden groups in context control', () => {
- const collapsedGroups = hideInContextControl(groups, 3, 7);
- assert.equal(collapsedGroups.length, 3);
-
- assert.equal(collapsedGroups[0], groups[0]);
-
- assert.equal(collapsedGroups[1].type, GrDiffGroupType.CONTEXT_CONTROL);
- assert.equal(collapsedGroups[1].contextGroups.length, 1);
- assert.equal(collapsedGroups[1].contextGroups[0], groups[1]);
-
- assert.equal(collapsedGroups[2], groups[2]);
- });
-
- test('splits partially hidden groups', () => {
- const collapsedGroups = hideInContextControl(groups, 4, 8);
- assert.equal(collapsedGroups.length, 4);
- assert.equal(collapsedGroups[0], groups[0]);
-
- assert.equal(collapsedGroups[1].type, GrDiffGroupType.DELTA);
- assert.deepEqual(collapsedGroups[1].adds, [groups[1].adds[0]]);
- assert.deepEqual(collapsedGroups[1].removes, [groups[1].removes[0]]);
-
- assert.equal(collapsedGroups[2].type, GrDiffGroupType.CONTEXT_CONTROL);
- assert.equal(collapsedGroups[2].contextGroups.length, 2);
-
- assert.equal(
- collapsedGroups[2].contextGroups[0].type,
- GrDiffGroupType.DELTA
- );
- assert.deepEqual(
- collapsedGroups[2].contextGroups[0].adds,
- groups[1].adds.slice(1)
- );
- assert.deepEqual(
- collapsedGroups[2].contextGroups[0].removes,
- groups[1].removes.slice(1)
- );
-
- assert.equal(
- collapsedGroups[2].contextGroups[1].type,
- GrDiffGroupType.BOTH
- );
- assert.deepEqual(collapsedGroups[2].contextGroups[1].lines, [
- groups[2].lines[0],
- ]);
-
- assert.equal(collapsedGroups[3].type, GrDiffGroupType.BOTH);
- assert.deepEqual(collapsedGroups[3].lines, groups[2].lines.slice(1));
- });
-
- suite('with skip chunks', () => {
- setup(() => {
- const skipGroup = new GrDiffGroup({
- type: GrDiffGroupType.BOTH,
- skip: 60,
- offsetLeft: 8,
- offsetRight: 10,
- });
- groups = [
- new GrDiffGroup({
- type: GrDiffGroupType.BOTH,
- lines: [
- new GrDiffLine(GrDiffLineType.BOTH, 5, 7),
- new GrDiffLine(GrDiffLineType.BOTH, 6, 8),
- new GrDiffLine(GrDiffLineType.BOTH, 7, 9),
- ],
- }),
- skipGroup,
- new GrDiffGroup({
- type: GrDiffGroupType.BOTH,
- lines: [
- new GrDiffLine(GrDiffLineType.BOTH, 68, 70),
- new GrDiffLine(GrDiffLineType.BOTH, 69, 71),
- new GrDiffLine(GrDiffLineType.BOTH, 70, 72),
- ],
- }),
- ];
- });
-
- test('refuses to split skip group when closer to before', () => {
- const collapsedGroups = hideInContextControl(groups, 4, 10);
- assert.deepEqual(groups, collapsedGroups);
- });
- });
-
- test('groups unchanged if the hidden range is empty', () => {
- assert.deepEqual(hideInContextControl(groups, 0, 0), groups);
- });
-
- test('groups unchanged if there is only 1 line to hide', () => {
- assert.deepEqual(hideInContextControl(groups, 3, 4), groups);
- });
- });
-
- suite('isTotal', () => {
- test('is total for add', () => {
- const lines = [];
- for (let idx = 0; idx < 10; idx++) {
- lines.push(new GrDiffLine(GrDiffLineType.ADD));
- }
- const group = new GrDiffGroup({type: GrDiffGroupType.DELTA, lines});
- assert.isTrue(group.isTotal());
- });
-
- test('is total for remove', () => {
- const lines = [];
- for (let idx = 0; idx < 10; idx++) {
- lines.push(new GrDiffLine(GrDiffLineType.REMOVE));
- }
- const group = new GrDiffGroup({type: GrDiffGroupType.DELTA, lines});
- assert.isTrue(group.isTotal());
- });
-
- test('not total for non-delta', () => {
- const lines = [];
- for (let idx = 0; idx < 10; idx++) {
- lines.push(new GrDiffLine(GrDiffLineType.BOTH));
- }
- const group = new GrDiffGroup({type: GrDiffGroupType.DELTA, lines});
- assert.isFalse(group.isTotal());
- });
- });
-
- suite('startLine', () => {
- test('DELTA', () => {
- const lines: GrDiffLine[] = [];
- lines.push(new GrDiffLine(GrDiffLineType.BOTH, 3, 4));
- const group = new GrDiffGroup({type: GrDiffGroupType.DELTA, lines});
- assert.equal(group.startLine(Side.LEFT), 3);
- assert.equal(group.startLine(Side.RIGHT), 4);
- });
-
- test('CONTEXT CONTROL', () => {
- const lines: GrDiffLine[] = [];
- lines.push(new GrDiffLine(GrDiffLineType.BOTH, 3, 4));
- const delta = new GrDiffGroup({type: GrDiffGroupType.DELTA, lines});
- const group = new GrDiffGroup({
- type: GrDiffGroupType.CONTEXT_CONTROL,
- contextGroups: [delta],
- });
- assert.equal(group.startLine(Side.LEFT), 3);
- assert.equal(group.startLine(Side.RIGHT), 4);
- });
-
- test('SKIP', () => {
- const group = new GrDiffGroup({
- type: GrDiffGroupType.BOTH,
- skip: 10,
- offsetLeft: 3,
- offsetRight: 6,
- });
- assert.equal(group.startLine(Side.LEFT), 3);
- assert.equal(group.startLine(Side.RIGHT), 6);
-
- const group2 = new GrDiffGroup({
- type: GrDiffGroupType.BOTH,
- skip: 0,
- offsetLeft: 3,
- offsetRight: 6,
- });
- assert.equal(group2.startLine(Side.LEFT), 3);
- assert.equal(group2.startLine(Side.RIGHT), 6);
- });
-
- test('FILE', () => {
- const lines: GrDiffLine[] = [];
- lines.push(new GrDiffLine(GrDiffLineType.BOTH, FILE, FILE));
- const group = new GrDiffGroup({type: GrDiffGroupType.DELTA, lines});
- assert.equal(group.startLine(Side.LEFT), FILE);
- assert.equal(group.startLine(Side.RIGHT), FILE);
- });
-
- test('LOST', () => {
- const lines: GrDiffLine[] = [];
- lines.push(new GrDiffLine(GrDiffLineType.BOTH, LOST, LOST));
- const group = new GrDiffGroup({type: GrDiffGroupType.DELTA, lines});
- assert.equal(group.startLine(Side.LEFT), LOST);
- assert.equal(group.startLine(Side.RIGHT), LOST);
- });
- });
-});
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff-line.ts b/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff-line.ts
deleted file mode 100644
index 1a89207..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff-line.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {
- FILE,
- GrDiffLine as GrDiffLineApi,
- GrDiffLineType,
- LineNumber,
- Side,
-} from '../../../api/diff';
-
-export class GrDiffLine implements GrDiffLineApi {
- constructor(
- readonly type: GrDiffLineType,
- public beforeNumber: LineNumber = 0,
- public afterNumber: LineNumber = 0
- ) {}
-
- hasIntralineInfo = false;
-
- highlights: Highlights[] = [];
-
- text = '';
-
- lineNumber(side: Side) {
- return side === Side.LEFT ? this.beforeNumber : this.afterNumber;
- }
-
- // TODO(TS): remove this properties
- static readonly Type = GrDiffLineType;
-
- static readonly File = FILE;
-}
-
-/**
- * A line highlight object consists of three fields:
- * - contentIndex: The index of the chunk `content` field (the line
- * being referred to).
- * - startIndex: Index of the character where the highlight should begin.
- * - endIndex: (optional) Index of the character where the highlight should
- * end. If omitted, the highlight is meant to be a continuation onto the
- * next line.
- */
-export interface Highlights {
- contentIndex: number;
- startIndex: number;
- endIndex?: number;
-}
-
-export const BLANK_LINE = new GrDiffLine(GrDiffLineType.BLANK);
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff-styles.ts b/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff-styles.ts
deleted file mode 100644
index a6dddd64..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff-styles.ts
+++ /dev/null
@@ -1,671 +0,0 @@
-/**
- * @license
- * Copyright 2023 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {css} from 'lit';
-
-export const grDiffStyles = css`
- /* This is used to hide all left side of the diff (e.g. diffs besides
- comments in the change log). Since we want to remove the first 4
- cells consistently in all rows except context buttons (.dividerRow). */
- :host(.no-left) .sideBySide colgroup col:nth-child(-n + 4),
- :host(.no-left) .sideBySide tr:not(.dividerRow) td:nth-child(-n + 4) {
- display: none;
- }
- :host(.disable-context-control-buttons) {
- --context-control-display: none;
- }
- :host(.disable-context-control-buttons) .section {
- border-right: none;
- }
- :host(.hide-line-length-indicator) .full-width td.content .contentText {
- background-image: none;
- }
-
- :host {
- font-family: var(--monospace-font-family, ''), 'Roboto Mono';
- font-size: var(--font-size, var(--font-size-code, 12px));
- /* usually 16px = 12px + 4px */
- line-height: calc(
- var(--font-size, var(--font-size-code, 12px)) + var(--spacing-s, 4px)
- );
- }
-
- .thread-group {
- display: block;
- max-width: var(--content-width, 80ch);
- white-space: normal;
- background-color: var(--diff-blank-background-color);
- }
- .diffContainer {
- max-width: var(--diff-max-width, none);
- font-family: var(--monospace-font-family);
- }
- table {
- border-collapse: collapse;
- table-layout: fixed;
- }
- td.lineNum {
- /* Enforces background whenever lines wrap */
- background-color: var(--diff-blank-background-color);
- }
-
- /* Provides the option to add side borders (left and right) to the line
- number column. */
- td.lineNum,
- td.blankLineNum,
- td.moveControlsLineNumCol,
- td.contextLineNum {
- box-shadow: var(--line-number-box-shadow, unset);
- }
-
- /* Context controls break up the table visually, so we set the right
- border on individual sections to leave a gap for the divider.
-
- Also taken into account for max-width calculations in SHRINK_ONLY mode
- (check GrDiff.updatePreferenceStyles). */
- .section {
- border-right: 1px solid var(--border-color);
- }
- .section.contextControl {
- /* Divider inside this section must not have border; we set borders on
- the padding rows below. */
- border-right-width: 0;
- }
- /* Padding rows behind context controls. The diff is styled to be cut
- into two halves by the negative space of the divider on which the
- context control buttons are anchored. */
- .contextBackground {
- border-right: 1px solid var(--border-color);
- }
- .contextBackground.above {
- border-bottom: 1px solid var(--border-color);
- }
- .contextBackground.below {
- border-top: 1px solid var(--border-color);
- }
-
- .lineNumButton {
- display: block;
- width: 100%;
- height: 100%;
- background-color: var(--diff-blank-background-color);
- box-shadow: var(--line-number-box-shadow, unset);
- }
- td.lineNum {
- vertical-align: top;
- }
-
- /* The only way to focus this (clicking) will apply our own focus
- styling, so this default styling is not needed and distracting. */
- .lineNumButton:focus {
- outline: none;
- }
- gr-image-viewer {
- width: 100%;
- height: 100%;
- max-width: var(--image-viewer-max-width, 95vw);
- max-height: var(--image-viewer-max-height, 90vh);
- /* Defined by paper-styles default-theme and used in various
- components. background-color-secondary is a compromise between
- fairly light in light theme (where we ideally would want
- background-color-primary) yet slightly offset against the app
- background in dark mode, where drop shadows e.g. around paper-card
- are almost invisible. */
- --primary-background-color: var(--background-color-secondary);
- }
- .image-diff .gr-diff {
- text-align: center;
- }
- .image-diff img {
- box-shadow: var(--elevation-level-1);
- max-width: 50em;
- }
- .image-diff .right.lineNumButton {
- border-left: 1px solid var(--border-color);
- }
- .image-diff label {
- font-family: var(--font-family);
- font-style: italic;
- }
- tbody.binary-diff td {
- font-family: var(--font-family);
- font-style: italic;
- text-align: center;
- padding: var(--spacing-s) 0;
- }
- .diff-row {
- outline: none;
- user-select: none;
- }
- .diff-row.target-row.target-side-left .lineNumButton.left,
- .diff-row.target-row.target-side-right .lineNumButton.right,
- .diff-row.target-row.unified .lineNumButton {
- color: var(--primary-text-color);
- }
-
- /* Preparing selected line cells with position relative so it allows a
- positioned overlay with 'position: absolute'. */
- .target-row td {
- position: relative;
- }
-
- /* Defines an overlay to the selected line for drawing an outline without
- blocking user interaction (e.g. text selection). */
- .target-row td::before {
- border-width: 0;
- border-style: solid;
- border-color: var(--focused-line-outline-color);
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- pointer-events: none;
- user-select: none;
- content: ' ';
- }
-
- /* The outline for the selected content cell should be the same in all
- cases. */
- .target-row.target-side-left td.left.content::before,
- .target-row.target-side-right td.right.content::before,
- .unified.target-row td.content::before {
- border-width: 1px 1px 1px 0;
- }
-
- /* The outline for the sign cell should be always be contiguous
- top/bottom. */
- .target-row.target-side-left td.left.sign::before,
- .target-row.target-side-right td.right.sign::before {
- border-width: 1px 0;
- }
-
- /* For side-by-side we need to select the correct line number to
- "visually close" the outline. */
- .side-by-side.target-row.target-side-left td.left.lineNum::before,
- .side-by-side.target-row.target-side-right td.right.lineNum::before {
- border-width: 1px 0 1px 1px;
- }
-
- /* For unified diff we always start the overlay from the left cell. */
- .unified.target-row td.left:not(.content)::before {
- border-width: 1px 0 1px 1px;
- }
-
- /* For unified diff we should continue the top/bottom border in right
- line number column. */
- .unified.target-row td.right:not(.content)::before {
- border-width: 1px 0;
- }
-
- .content {
- background-color: var(--diff-blank-background-color);
- }
-
- /* Describes two states of semantic tokens: whenever a token has a
- definition that can be navigated to (navigable) and whenever
- the token is actually clickable to perform this navigation. */
- .semantic-token.navigable {
- text-decoration-style: dotted;
- text-decoration-line: underline;
- }
- .semantic-token.navigable.clickable {
- text-decoration-style: solid;
- cursor: pointer;
- }
-
- /* The file line, which has no contentText, add some margin before the
- first comment. We cannot add padding the container because we only
- want it if there is at least one comment thread, and the slotting
- makes :empty not work as expected. */
- .content.file slot:first-child::slotted(.comment-thread) {
- display: block;
- margin-top: var(--spacing-xs);
- }
- .contentText {
- background-color: var(--view-background-color);
- }
- .blank {
- background-color: var(--diff-blank-background-color);
- }
- .image-diff .content {
- background-color: var(--diff-blank-background-color);
- }
- .responsive {
- width: 100%;
- }
- .responsive .contentText {
- white-space: break-spaces;
- word-break: break-all;
- }
- .lineNumButton,
- .content {
- vertical-align: top;
- white-space: pre;
- }
- .contextLineNum,
- .lineNumButton {
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
-
- color: var(--deemphasized-text-color);
- padding: 0 var(--spacing-m);
- text-align: right;
- }
- .canComment .lineNumButton {
- cursor: pointer;
- }
- .sign {
- min-width: 1ch;
- width: 1ch;
- background-color: var(--view-background-color);
- }
- .sign.blank {
- background-color: var(--diff-blank-background-color);
- }
- .content {
- /* Set min width since setting width on table cells still allows them
- to shrink. Do not set max width because CJK
- (Chinese-Japanese-Korean) glyphs have variable width. */
- min-width: var(--content-width, 80ch);
- width: var(--content-width, 80ch);
- }
- /* If there are no intraline info, consider everything changed */
- .content.add .contentText .intraline,
- .content.add.no-intraline-info .contentText,
- .sign.add.no-intraline-info,
- .delta.total .content.add .contentText {
- background-color: var(--dark-add-highlight-color);
- }
- .content.add .contentText,
- .sign.add {
- background-color: var(--light-add-highlight-color);
- }
- /* If there are no intraline info, consider everything changed */
- .content.remove .contentText .intraline,
- .content.remove.no-intraline-info .contentText,
- .delta.total .content.remove .contentText,
- .sign.remove.no-intraline-info {
- background-color: var(--dark-remove-highlight-color);
- }
- .content.remove .contentText,
- .sign.remove {
- background-color: var(--light-remove-highlight-color);
- }
-
- .ignoredWhitespaceOnly .sign.no-intraline-info {
- background-color: var(--view-background-color);
- }
-
- /* dueToRebase */
- .dueToRebase .content.add .contentText .intraline,
- .delta.total.dueToRebase .content.add .contentText {
- background-color: var(--dark-rebased-add-highlight-color);
- }
- .dueToRebase .content.add .contentText {
- background-color: var(--light-rebased-add-highlight-color);
- }
- .dueToRebase .content.remove .contentText .intraline,
- .delta.total.dueToRebase .content.remove .contentText {
- background-color: var(--dark-rebased-remove-highlight-color);
- }
- .dueToRebase .content.remove .contentText {
- background-color: var(--light-rebased-remove-highlight-color);
- }
-
- /* dueToMove */
- .dueToMove .sign.add,
- .dueToMove .content.add .contentText,
- .dueToMove .moveControls.movedIn .sign.right,
- .dueToMove .moveControls.movedIn .moveHeader,
- .delta.total.dueToMove .content.add .contentText {
- background-color: var(--diff-moved-in-background);
- }
-
- .dueToMove.changed .sign.add,
- .dueToMove.changed .content.add .contentText,
- .dueToMove.changed .moveControls.movedIn .sign.right,
- .dueToMove.changed .moveControls.movedIn .moveHeader,
- .delta.total.dueToMove.changed .content.add .contentText {
- background-color: var(--diff-moved-in-changed-background);
- }
-
- .dueToMove .sign.remove,
- .dueToMove .content.remove .contentText,
- .dueToMove .moveControls.movedOut .moveHeader,
- .dueToMove .moveControls.movedOut .sign.left,
- .delta.total.dueToMove .content.remove .contentText {
- background-color: var(--diff-moved-out-background);
- }
-
- .delta.dueToMove .movedIn .moveHeader {
- --gr-range-header-color: var(--diff-moved-in-label-color);
- }
- .delta.dueToMove.changed .movedIn .moveHeader {
- --gr-range-header-color: var(--diff-moved-in-changed-label-color);
- }
- .delta.dueToMove .movedOut .moveHeader {
- --gr-range-header-color: var(--diff-moved-out-label-color);
- }
-
- .moveHeader a {
- color: inherit;
- }
-
- /* ignoredWhitespaceOnly */
- .ignoredWhitespaceOnly .content.add .contentText .intraline,
- .delta.total.ignoredWhitespaceOnly .content.add .contentText,
- .ignoredWhitespaceOnly .content.add .contentText,
- .ignoredWhitespaceOnly .content.remove .contentText .intraline,
- .delta.total.ignoredWhitespaceOnly .content.remove .contentText,
- .ignoredWhitespaceOnly .content.remove .contentText {
- background-color: var(--view-background-color);
- }
-
- .content .contentText gr-diff-text:empty:after,
- .content .contentText gr-legacy-text:empty:after,
- .content .contentText:empty:after {
- /* Newline, to ensure empty lines are one line-height tall. */
- content: '\\A';
- }
-
- /* Context controls */
- .contextControl {
- display: var(--context-control-display, table-row-group);
- background-color: transparent;
- border: none;
- --divider-height: var(--spacing-s);
- --divider-border: 1px;
- }
- /* TODO: Is this still used? */
- .contextControl gr-button gr-icon {
- /* should match line-height of gr-button */
- font-size: var(--line-height-mono, 18px);
- }
- .contextControl td:not(.lineNumButton) {
- text-align: center;
- }
-
- /* Padding rows behind context controls. Styled as a continuation of the
- line gutters and code area. */
- .contextBackground > .contextLineNum {
- background-color: var(--diff-blank-background-color);
- }
- .contextBackground > td:not(.contextLineNum) {
- background-color: var(--view-background-color);
- }
- .contextBackground {
- /* One line of background behind the context expanders which they can
- render on top of, plus some padding. */
- height: calc(var(--line-height-normal) + var(--spacing-s));
- }
-
- .dividerCell {
- vertical-align: top;
- }
- .dividerRow.show-both .dividerCell {
- height: var(--divider-height);
- }
- .dividerRow.show-above .dividerCell,
- .dividerRow.show-above .dividerCell {
- height: 0;
- }
-
- .br:after {
- /* Line feed */
- content: '\\A';
- }
- .tab {
- display: inline-block;
- }
- .tab-indicator:before {
- color: var(--diff-tab-indicator-color);
- /* >> character */
- content: '\\00BB';
- position: absolute;
- }
- .special-char-indicator {
- /* spacing so elements don't collide */
- padding-right: var(--spacing-m);
- }
- .special-char-indicator:before {
- color: var(--diff-tab-indicator-color);
- content: '•';
- position: absolute;
- }
- .special-char-warning {
- /* spacing so elements don't collide */
- padding-right: var(--spacing-m);
- }
- .special-char-warning:before {
- color: var(--warning-foreground);
- content: '!';
- position: absolute;
- }
- /* Is defined after other background-colors, such that this
- rule wins in case of same specificity. */
- .trailing-whitespace,
- .content .contentText .trailing-whitespace,
- .trailing-whitespace .intraline,
- .content .contentText .trailing-whitespace .intraline {
- border-radius: var(--border-radius, 4px);
- background-color: var(--diff-trailing-whitespace-indicator);
- }
- #diffHeader {
- background-color: var(--table-header-background-color);
- border-bottom: 1px solid var(--border-color);
- color: var(--link-color);
- padding: var(--spacing-m) 0 var(--spacing-m) 48px;
- }
- #diffTable {
- /* for gr-selection-action-box positioning */
- position: relative;
- }
- #diffTable:focus {
- outline: none;
- }
- #loadingError,
- #sizeWarning {
- display: block;
- margin: var(--spacing-l) auto;
- max-width: 60em;
- text-align: center;
- }
- #loadingError {
- color: var(--error-text-color);
- }
- #sizeWarning gr-button {
- margin: var(--spacing-l);
- }
- .target-row td.blame {
- background: var(--diff-selection-background-color);
- }
- td.lost div {
- background-color: var(--info-background);
- }
- td.lost div.lost-message {
- font-family: var(--font-family, 'Roboto');
- font-size: var(--font-size-normal, 14px);
- line-height: var(--line-height-normal);
- padding: var(--spacing-s) 0;
- }
- td.lost div.lost-message gr-icon {
- padding: 0 var(--spacing-s) 0 var(--spacing-m);
- color: var(--blue-700);
- }
-
- col.sign,
- td.sign {
- display: none;
- }
-
- /* Sign column should only be shown in high-contrast mode. */
- :host(.with-sign-col) col.sign {
- display: table-column;
- }
- :host(.with-sign-col) td.sign {
- display: table-cell;
- }
- col.blame {
- display: none;
- }
- td.blame {
- display: none;
- padding: 0 var(--spacing-m);
- white-space: pre;
- }
- :host(.showBlame) col.blame {
- display: table-column;
- }
- :host(.showBlame) td.blame {
- display: table-cell;
- }
- td.blame > span {
- opacity: 0.6;
- }
- td.blame > span.startOfRange {
- opacity: 1;
- }
- td.blame .blameDate {
- font-family: var(--monospace-font-family);
- color: var(--link-color);
- text-decoration: none;
- }
- .responsive td.blame {
- overflow: hidden;
- width: 200px;
- }
- /** Support the line length indicator **/
- .responsive td.content .contentText {
- /* Same strategy as in
- https://stackoverflow.com/questions/1179928/how-can-i-put-a-vertical-line-down-the-center-of-a-div
- */
- background-image: linear-gradient(
- var(--line-length-indicator-color),
- var(--line-length-indicator-color)
- );
- background-size: 1px 100%;
- background-position: var(--line-limit-marker) 0;
- background-repeat: no-repeat;
- }
- .newlineWarning {
- color: var(--deemphasized-text-color);
- text-align: center;
- }
- .newlineWarning.hidden {
- display: none;
- }
- .lineNum.COVERED .lineNumButton {
- color: var(
- --coverage-covered-line-num-color,
- var(--deemphasized-text-color)
- );
- background-color: var(--coverage-covered, #e0f2f1);
- }
- .lineNum.NOT_COVERED .lineNumButton {
- color: var(
- --coverage-covered-line-num-color,
- var(--deemphasized-text-color)
- );
- background-color: var(--coverage-not-covered, #ffd1a4);
- }
- .lineNum.PARTIALLY_COVERED .lineNumButton {
- color: var(
- --coverage-covered-line-num-color,
- var(--deemphasized-text-color)
- );
- background: linear-gradient(
- to right bottom,
- var(--coverage-not-covered, #ffd1a4) 0%,
- var(--coverage-not-covered, #ffd1a4) 50%,
- var(--coverage-covered, #e0f2f1) 50%,
- var(--coverage-covered, #e0f2f1) 100%
- );
- }
-
- // TODO: Investigate whether this CSS is still necessary.
- /* BEGIN: Select and copy for Polymer 2 */
- /* Below was copied and modified from the original css in gr-diff-selection.html. */
- .content,
- .contextControl,
- .blame {
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- }
-
- .selected-left:not(.selected-comment)
- .side-by-side
- .left
- + .content
- .contentText,
- .selected-right:not(.selected-comment)
- .side-by-side
- .right
- + .content
- .contentText,
- .selected-left:not(.selected-comment)
- .unified
- .left.lineNum
- ~ .content:not(.both)
- .contentText,
- .selected-right:not(.selected-comment)
- .unified
- .right.lineNum
- ~ .content
- .contentText,
- .selected-left.selected-comment .side-by-side .left + .content .message,
- .selected-right.selected-comment
- .side-by-side
- .right
- + .content
- .message
- :not(.collapsedContent),
- .selected-comment .unified .message :not(.collapsedContent),
- .selected-blame .blame {
- -webkit-user-select: text;
- -moz-user-select: text;
- -ms-user-select: text;
- user-select: text;
- }
-
- /* Make comments and check results selectable when selected */
- .selected-left.selected-comment ::slotted(.comment-thread[diff-side='left']),
- .selected-right.selected-comment
- ::slotted(.comment-thread[diff-side='right']) {
- -webkit-user-select: text;
- -moz-user-select: text;
- -ms-user-select: text;
- user-select: text;
- }
- /* END: Select and copy for Polymer 2 */
-
- .whitespace-change-only-message {
- background-color: var(--diff-context-control-background-color);
- border: 1px solid var(--diff-context-control-border-color);
- text-align: center;
- }
-
- .token-highlight {
- background-color: var(--token-highlighting-color, #fffd54);
- }
-
- gr-selection-action-box {
- /* Needs z-index to appear above wrapped content, since it's inserted
- into DOM before it. */
- z-index: 120;
- }
-
- gr-diff-image-new,
- gr-diff-image-old,
- gr-diff-section,
- gr-context-controls-section,
- gr-diff-row {
- display: contents;
- }
-`;
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff.ts b/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff.ts
deleted file mode 100644
index 47ed7cf..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff.ts
+++ /dev/null
@@ -1,1127 +0,0 @@
-/**
- * @license
- * Copyright 2015 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../styles/shared-styles';
-import '../../../elements/shared/gr-button/gr-button';
-import '../../../elements/shared/gr-icon/gr-icon';
-import '../gr-diff-builder/gr-diff-builder-element';
-import '../gr-diff-highlight/gr-diff-highlight';
-import '../gr-diff-selection/gr-diff-selection';
-import '../../diff/gr-syntax-themes/gr-syntax-theme';
-import '../../diff/gr-ranged-comment-themes/gr-ranged-comment-theme';
-import '../../diff/gr-ranged-comment-hint/gr-ranged-comment-hint';
-import {
- getLine,
- getLineElByChild,
- getLineNumber,
- getRange,
- getSide,
- GrDiffThreadElement,
- isLongCommentRange,
- isThreadEl,
- rangesEqual,
- getResponsiveMode,
- isResponsive,
- isNewDiff,
- getDataFromCommentThreadEl,
- GrDiffCommentThread,
-} from '../../diff/gr-diff/gr-diff-utils';
-import {BlameInfo, CommentRange, ImageInfo} from '../../../types/common';
-import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
-import {
- CreateRangeCommentEventDetail,
- GrDiffHighlight,
-} from '../gr-diff-highlight/gr-diff-highlight';
-import {
- GrDiffBuilderElement,
- getLineNumberCellWidth,
-} from '../gr-diff-builder/gr-diff-builder-element';
-import {CoverageRange, DiffLayer} from '../../../types/types';
-import {CommentRangeLayer} from '../../diff/gr-ranged-comment-layer/gr-ranged-comment-layer';
-import {
- createDefaultDiffPrefs,
- DiffViewMode,
- Side,
-} from '../../../constants/constants';
-import {KeyLocations} from '../gr-diff-processor/gr-diff-processor';
-import {fire, fireAlert} from '../../../utils/event-util';
-import {MovedLinkClickedEvent, ValueChangedEvent} from '../../../types/events';
-import {getContentEditableRange} from '../../../utils/safari-selection-util';
-import {AbortStop} from '../../../api/core';
-import {
- RenderPreferences,
- GrDiff as GrDiffApi,
- DisplayLine,
- LineNumber,
- LOST,
-} from '../../../api/diff';
-import {isSafari, toggleClass} from '../../../utils/dom-util';
-import {assertIsDefined} from '../../../utils/common-util';
-import {
- debounceP,
- DelayedPromise,
- DELAYED_CANCELLATION,
-} from '../../../utils/async-util';
-import {GrDiffSelection} from '../gr-diff-selection/gr-diff-selection';
-import {property, query, state} from 'lit/decorators.js';
-import {sharedStyles} from '../../../styles/shared-styles';
-import {html, LitElement, nothing, PropertyValues} from 'lit';
-import {when} from 'lit/directives/when.js';
-import {grSyntaxTheme} from '../../diff/gr-syntax-themes/gr-syntax-theme';
-import {grRangedCommentTheme} from '../../diff/gr-ranged-comment-themes/gr-ranged-comment-theme';
-import {classMap} from 'lit/directives/class-map.js';
-import {iconStyles} from '../../../styles/gr-icon-styles';
-import {expandFileMode} from '../../../utils/file-util';
-import {DiffModel, diffModelToken} from '../gr-diff-model/gr-diff-model';
-import {provide} from '../../../models/dependency';
-import {grDiffStyles} from './gr-diff-styles';
-import {getDiffLength} from '../../../utils/diff-util';
-
-const NO_NEWLINE_LEFT = 'No newline at end of left file.';
-const NO_NEWLINE_RIGHT = 'No newline at end of right file.';
-
-const LARGE_DIFF_THRESHOLD_LINES = 10000;
-const FULL_CONTEXT = -1;
-
-const COMMIT_MSG_PATH = '/COMMIT_MSG';
-/**
- * 72 is the unofficial length standard for git commit messages.
- * Derived from the fact that git log/show appends 4 ws in the beginning of
- * each line when displaying commit messages. To center the commit message
- * in an 80 char terminal a 4 ws border is added to the rightmost side:
- * 4 + 72 + 4
- */
-const COMMIT_MSG_LINE_LENGTH = 72;
-
-export class GrDiff extends LitElement implements GrDiffApi {
- /**
- * 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 created
- *
- * @event create-comment
- */
-
- /**
- * Fired when rendering, including syntax highlighting, is done. Also fired
- * when no rendering can be done because required preferences are not set.
- *
- * @event render
- */
-
- /**
- * Fired for interaction reporting when a diff context is expanded.
- * Contains an event.detail with numLines about the number of lines that
- * were expanded.
- *
- * @event diff-context-expanded
- */
-
- @query('#diffTable')
- diffTable?: HTMLTableElement;
-
- @property({type: Boolean})
- noAutoRender = false;
-
- @property({type: String})
- path?: string;
-
- @property({type: Object})
- prefs?: DiffPreferencesInfo;
-
- @property({type: Object})
- renderPrefs: RenderPreferences = {};
-
- @property({type: Boolean, reflect: true})
- override hidden = false;
-
- @property({type: Boolean})
- noRenderOnPrefsChange?: boolean;
-
- // Private but used in tests.
- @state()
- commentRanges: CommentRangeLayer[] = [];
-
- // explicitly highlight a range if it is not associated with any comment
- @property({type: Object})
- highlightRange?: CommentRange;
-
- @property({type: Array})
- coverageRanges: CoverageRange[] = [];
-
- @property({type: Boolean})
- lineWrapping = false;
-
- @property({type: String})
- viewMode = DiffViewMode.SIDE_BY_SIDE;
-
- @property({type: Object})
- lineOfInterest?: DisplayLine;
-
- /**
- * True when diff is changed, until the content is done rendering.
- * Use getter/setter loading instead of this.
- */
- private _loading = true;
-
- get loading() {
- return this._loading;
- }
-
- set loading(loading: boolean) {
- if (this._loading === loading) return;
- const oldLoading = this._loading;
- this._loading = loading;
- fire(this, 'loading-changed', {value: this._loading});
- this.requestUpdate('loading', oldLoading);
- }
-
- @property({type: Boolean})
- loggedIn = false;
-
- @property({type: Object})
- diff?: DiffInfo;
-
- @state()
- private diffTableClass = '';
-
- @property({type: Object})
- baseImage?: ImageInfo;
-
- @property({type: Object})
- revisionImage?: ImageInfo;
-
- /**
- * In order to allow multi-select in Safari browsers, a workaround is required
- * to trigger 'beforeinput' events to get a list of static ranges. This is
- * obtained by making the content of the diff table "contentEditable".
- */
- @property({type: Boolean})
- override isContentEditable = isSafari();
-
- /**
- * Whether the safety check for large diffs when whole-file is set has
- * been bypassed. If the value is null, then the safety has not been
- * bypassed. If the value is a number, then that number represents the
- * context preference to use when rendering the bypassed diff.
- *
- * Private but used in tests.
- */
- @state()
- safetyBypass: number | null = null;
-
- // Private but used in tests.
- @state()
- showWarning?: boolean;
-
- @property({type: String})
- errorMessage: string | null = null;
-
- @property({type: Array})
- blame: BlameInfo[] | null = null;
-
- @property({type: Boolean})
- showNewlineWarningLeft = false;
-
- @property({type: Boolean})
- showNewlineWarningRight = false;
-
- @property({type: Boolean})
- useNewImageDiffUi = false;
-
- // Private but used in tests.
- @state()
- diffLength?: number;
-
- /**
- * Observes comment nodes added or removed at any point.
- * Can be used to unregister upon detachment.
- */
- private nodeObserver?: MutationObserver;
-
- @property({type: Array})
- layers?: DiffLayer[];
-
- // Private but used in tests.
- renderDiffTableTask?: DelayedPromise<void>;
-
- // Private but used in tests.
- diffSelection = new GrDiffSelection();
-
- // Private but used in tests.
- highlights = new GrDiffHighlight();
-
- // Private but used in tests.
- diffBuilder = new GrDiffBuilderElement();
-
- private diffModel = new DiffModel(undefined);
-
- static override get styles() {
- return [
- iconStyles,
- sharedStyles,
- grSyntaxTheme,
- grRangedCommentTheme,
- grDiffStyles,
- ];
- }
-
- constructor() {
- super();
- provide(this, diffModelToken, () => this.diffModel);
- this.addEventListener(
- 'create-range-comment',
- (e: CustomEvent<CreateRangeCommentEventDetail>) =>
- this.handleCreateRangeComment(e)
- );
- this.addEventListener('render-content', () => this.handleRenderContent());
- this.addEventListener('moved-link-clicked', (e: MovedLinkClickedEvent) => {
- this.dispatchSelectedLine(e.detail.lineNum, e.detail.side);
- });
- }
-
- override connectedCallback() {
- super.connectedCallback();
- if (this.loggedIn) {
- this.addSelectionListeners();
- }
- if (this.diff && this.diffTable) {
- this.diffSelection.init(this.diff, this.diffTable);
- }
- if (this.diffTable && this.diffBuilder) {
- this.highlights.init(this.diffTable, this.diffBuilder);
- }
- this.diffBuilder.init();
- }
-
- override disconnectedCallback() {
- this.removeSelectionListeners();
- this.renderDiffTableTask?.cancel();
- this.diffSelection.cleanup();
- this.highlights.cleanup();
- this.diffBuilder.cleanup();
- super.disconnectedCallback();
- }
-
- protected override willUpdate(changedProperties: PropertyValues<this>): void {
- if (
- changedProperties.has('path') ||
- changedProperties.has('lineWrapping') ||
- changedProperties.has('viewMode') ||
- changedProperties.has('useNewImageDiffUi') ||
- changedProperties.has('prefs')
- ) {
- this.prefsChanged();
- }
- if (changedProperties.has('blame')) {
- this.blameChanged();
- }
- if (changedProperties.has('renderPrefs')) {
- this.renderPrefsChanged();
- }
- if (changedProperties.has('loggedIn')) {
- if (this.loggedIn && this.isConnected) {
- this.addSelectionListeners();
- } else {
- this.removeSelectionListeners();
- }
- }
- if (changedProperties.has('coverageRanges')) {
- this.diffBuilder.updateCoverageRanges(this.coverageRanges);
- }
- if (changedProperties.has('lineOfInterest')) {
- this.lineOfInterestChanged();
- }
- }
-
- protected override updated(changedProperties: PropertyValues<this>): void {
- if (changedProperties.has('diff')) {
- // diffChanged relies on diffTable ahving been rendered.
- this.diffChanged();
- }
- }
-
- override render() {
- return html`
- ${this.renderHeader()} ${this.renderContainer()}
- ${this.renderNewlineWarning()} ${this.renderLoadingError()}
- ${this.renderSizeWarning()}
- `;
- }
-
- private renderHeader() {
- const diffheaderItems = this.computeDiffHeaderItems();
- if (diffheaderItems.length === 0) return nothing;
- return html`
- <div id="diffHeader">
- ${diffheaderItems.map(item => html`<div>${item}</div>`)}
- </div>
- `;
- }
-
- private renderContainer() {
- const cssClasses = {
- oldDiff: true,
- diffContainer: true,
- unified: this.viewMode === DiffViewMode.UNIFIED,
- sideBySide: this.viewMode === DiffViewMode.SIDE_BY_SIDE,
- canComment: this.loggedIn,
- };
- return html`
- <div class=${classMap(cssClasses)} @click=${this.handleTap}>
- <table
- id="diffTable"
- class=${this.diffTableClass}
- ?contenteditable=${this.isContentEditable}
- ></table>
- ${when(
- this.showNoChangeMessage(),
- () => html`
- <div class="whitespace-change-only-message">
- This file only contains whitespace changes. Modify the whitespace
- setting to see the changes.
- </div>
- `
- )}
- </div>
- `;
- }
-
- private renderNewlineWarning() {
- const newlineWarning = this.computeNewlineWarning();
- if (!newlineWarning) return nothing;
- return html`<div class="newlineWarning">${newlineWarning}</div>`;
- }
-
- private renderLoadingError() {
- if (!this.errorMessage) return nothing;
- return html`<div id="loadingError">${this.errorMessage}</div>`;
- }
-
- private renderSizeWarning() {
- if (!this.showWarning) return nothing;
- // TODO: Update comment about 'Whole file' as it's not in settings.
- return html`
- <div id="sizeWarning">
- <p>
- Prevented render because "Whole file" is enabled and this diff is very
- large (about ${this.diffLength} lines).
- </p>
- <gr-button @click=${this.collapseContext}>
- Render with limited context
- </gr-button>
- <gr-button @click=${this.handleFullBypass}>
- Render anyway (may be slow)
- </gr-button>
- </div>
- `;
- }
-
- private addSelectionListeners() {
- document.addEventListener('selectionchange', this.handleSelectionChange);
- document.addEventListener('mouseup', this.handleMouseUp);
- }
-
- private removeSelectionListeners() {
- document.removeEventListener('selectionchange', this.handleSelectionChange);
- document.removeEventListener('mouseup', this.handleMouseUp);
- }
-
- getLineNumEls(side: Side): HTMLElement[] {
- return this.diffBuilder.getLineNumEls(side);
- }
-
- // Private but used in tests.
- showNoChangeMessage() {
- return (
- !this.loading &&
- this.diff &&
- !this.diff.binary &&
- this.prefs &&
- this.prefs.ignore_whitespace !== 'IGNORE_NONE' &&
- this.diffLength === 0
- );
- }
-
- private readonly handleSelectionChange = () => {
- // Because of shadow DOM selections, we handle the selectionchange here,
- // and pass the shadow DOM selection into gr-diff-highlight, where the
- // corresponding range is determined and normalized.
- const selection = this.getShadowOrDocumentSelection();
- this.highlights.handleSelectionChange(selection, false);
- };
-
- private readonly handleMouseUp = () => {
- // To handle double-click outside of text creating comments, we check on
- // mouse-up if there's a selection that just covers a line change. We
- // can't do that on selection change since the user may still be dragging.
- const selection = this.getShadowOrDocumentSelection();
- this.highlights.handleSelectionChange(selection, true);
- };
-
- /** Gets the current selection, preferring the shadow DOM selection. */
- private getShadowOrDocumentSelection() {
- // When using native shadow DOM, the selection returned by
- // document.getSelection() cannot reference the actual DOM elements making
- // up the diff in Safari because they are in the shadow DOM of the gr-diff
- // element. This takes the shadow DOM selection if one exists.
- return this.shadowRoot?.getSelection
- ? this.shadowRoot.getSelection()
- : isSafari()
- ? getContentEditableRange()
- : document.getSelection();
- }
-
- private updateRanges(
- addedThreadEls: GrDiffThreadElement[],
- removedThreadEls: GrDiffThreadElement[]
- ) {
- function commentRangeFromThreadEl(
- threadEl: GrDiffThreadElement
- ): CommentRangeLayer | undefined {
- const side = getSide(threadEl);
- if (!side) return undefined;
- const range = getRange(threadEl);
- if (!range) return undefined;
-
- return {side, range, id: threadEl.rootId};
- }
-
- // TODO(brohlfs): Rewrite `.map().filter() as ...` with `.reduce()` instead.
- const addedCommentRanges = addedThreadEls
- .map(commentRangeFromThreadEl)
- .filter(range => !!range) as CommentRangeLayer[];
- const removedCommentRanges = removedThreadEls
- .map(commentRangeFromThreadEl)
- .filter(range => !!range) as CommentRangeLayer[];
- for (const removedCommentRange of removedCommentRanges) {
- const i = this.commentRanges.findIndex(
- cr =>
- cr.side === removedCommentRange.side &&
- rangesEqual(cr.range, removedCommentRange.range)
- );
- this.commentRanges.splice(i, 1);
- }
-
- if (addedCommentRanges?.length) {
- this.commentRanges.push(...addedCommentRanges);
- }
- if (this.highlightRange) {
- this.commentRanges.push({
- side: Side.RIGHT,
- range: this.highlightRange,
- id: 'highlightRange',
- });
- }
-
- this.diffBuilder.updateCommentRanges(this.commentRanges);
- }
-
- /**
- * The key locations based on the comments and line of interests,
- * where lines should not be collapsed.
- *
- */
- private computeKeyLocations() {
- const keyLocations: KeyLocations = {left: {}, right: {}};
- if (this.lineOfInterest) {
- const side = this.lineOfInterest.side;
- keyLocations[side][this.lineOfInterest.lineNum] = true;
- }
- const threadEls = [...this.childNodes].filter(isThreadEl);
-
- for (const threadEl of threadEls) {
- const side = getSide(threadEl);
- if (!side) continue;
- const lineNum = getLine(threadEl);
- const commentRange = getRange(threadEl);
- keyLocations[side][lineNum] = true;
- // Add start_line as well if exists,
- // the being and end of the range should not be collapsed.
- if (commentRange?.start_line) {
- keyLocations[side][commentRange.start_line] = true;
- }
- }
- return keyLocations;
- }
-
- // Dispatch events that are handled by the gr-diff-highlight.
- private redispatchHoverEvents(
- hoverEl: HTMLElement,
- threadEl: GrDiffThreadElement
- ) {
- hoverEl.addEventListener('mouseenter', () => {
- const data = getDataFromCommentThreadEl(threadEl);
- if (data) fire(threadEl, 'comment-thread-mouseenter', data);
- });
- hoverEl.addEventListener('mouseleave', () => {
- const data = getDataFromCommentThreadEl(threadEl);
- if (data) fire(threadEl, 'comment-thread-mouseleave', data);
- });
- }
-
- /** Cancel any remaining diff builder rendering work. */
- cancel() {
- this.diffBuilder.cleanup();
- this.renderDiffTableTask?.cancel();
- }
-
- getCursorStops(): Array<HTMLElement | AbortStop> {
- if (this.hidden && this.noAutoRender) return [];
-
- // Get rendered stops.
- const stops: Array<HTMLElement | AbortStop> =
- this.diffBuilder.getLineNumberRows();
-
- // If we are still loading this diff, abort after the rendered stops to
- // avoid skipping over to e.g. the next file.
- if (this.loading) {
- stops.push(new AbortStop());
- }
- return stops;
- }
-
- isRangeSelected() {
- return !!this.highlights.selectedRange;
- }
-
- toggleLeftDiff() {
- toggleClass(this, 'no-left');
- }
-
- private blameChanged() {
- this.diffBuilder.setBlame(this.blame);
- if (this.blame) {
- this.classList.add('showBlame');
- } else {
- this.classList.remove('showBlame');
- }
- }
-
- // Private but used in tests.
- handleTap(e: Event) {
- const el = e.target as Element;
-
- if (
- el.getAttribute('data-value') !== LOST &&
- (el.classList.contains('lineNum') ||
- el.classList.contains('lineNumButton'))
- ) {
- this.addDraftAtLine(el);
- } else if (
- el.tagName === 'HL' ||
- el.classList.contains('content') ||
- el.classList.contains('contentText')
- ) {
- const target = getLineElByChild(el);
- if (target) {
- this.selectLine(target);
- }
- }
- }
-
- // Private but used in tests.
- selectLine(el: Element) {
- const lineNumber = Number(el.getAttribute('data-value'));
- const side = el.classList.contains('left') ? Side.LEFT : Side.RIGHT;
- this.dispatchSelectedLine(lineNumber, side);
- }
-
- private dispatchSelectedLine(number: LineNumber, side: Side) {
- fire(this, 'line-selected', {
- number,
- side,
- path: this.path,
- });
- }
-
- addDraftAtLine(el: Element) {
- this.selectLine(el);
-
- const lineNum = getLineNumber(el);
- if (lineNum === null) {
- fireAlert(this, 'Invalid line number');
- return;
- }
-
- this.createComment(el, lineNum);
- }
-
- createRangeComment() {
- if (!this.isRangeSelected()) {
- throw Error('Selection is needed for new range comment');
- }
- const selectedRange = this.highlights.selectedRange;
- if (!selectedRange) throw Error('selected range not set');
- const {side, range} = selectedRange;
- this.createCommentForSelection(side, range);
- }
-
- createCommentForSelection(side: Side, range: CommentRange) {
- const lineNum = range.end_line;
- const lineEl = this.diffBuilder.getLineElByNumber(lineNum, side);
- if (lineEl) {
- this.createComment(lineEl, lineNum, side, range);
- }
- }
-
- private handleCreateRangeComment(
- e: CustomEvent<CreateRangeCommentEventDetail>
- ) {
- const range = e.detail.range;
- const side = e.detail.side;
- this.createCommentForSelection(side, range);
- }
-
- // Private but used in tests.
- createComment(
- lineEl: Element,
- lineNum: LineNumber,
- side?: Side,
- range?: CommentRange
- ) {
- const contentEl = this.diffBuilder.getContentTdByLineEl(lineEl);
- if (!contentEl) throw new Error('content el not found for line el');
- side = side ?? this.getCommentSideByLineAndContent(lineEl, contentEl);
- fire(this, 'create-comment', {
- side,
- lineNum,
- range,
- });
- }
-
- private getCommentSideByLineAndContent(
- lineEl: Element,
- contentEl: Element
- ): Side {
- return lineEl.classList.contains(Side.LEFT) ||
- contentEl.classList.contains('remove')
- ? Side.LEFT
- : Side.RIGHT;
- }
-
- private lineOfInterestChanged() {
- if (this.loading) return;
- if (!this.lineOfInterest) return;
- const lineNum = this.lineOfInterest.lineNum;
- if (typeof lineNum !== 'number') return;
- this.diffBuilder.unhideLine(lineNum, this.lineOfInterest.side);
- }
-
- private cleanup() {
- this.cancel();
- this.blame = null;
- this.safetyBypass = null;
- this.showWarning = false;
- this.clearDiffContent();
- }
-
- private prefsChanged() {
- if (!this.prefs) return;
- this.diffModel.updateState({diffPrefs: this.prefs});
-
- this.blame = null;
- this.updatePreferenceStyles();
-
- if (this.diff && !this.noRenderOnPrefsChange) {
- this.debounceRenderDiffTable();
- }
- }
-
- private updatePreferenceStyles() {
- assertIsDefined(this.prefs, 'prefs');
- const lineLength =
- this.path === COMMIT_MSG_PATH
- ? COMMIT_MSG_LINE_LENGTH
- : this.prefs.line_length;
- const sideBySide = this.viewMode === 'SIDE_BY_SIDE';
-
- const responsiveMode = getResponsiveMode(this.prefs, this.renderPrefs);
- const responsive = isResponsive(responsiveMode);
- this.diffTableClass = responsive ? 'responsive' : '';
- const lineLimit = `${lineLength}ch`;
- this.style.setProperty(
- '--line-limit-marker',
- responsiveMode === 'FULL_RESPONSIVE' ? lineLimit : '-1px'
- );
- this.style.setProperty('--content-width', responsive ? 'none' : lineLimit);
- if (responsiveMode === 'SHRINK_ONLY') {
- // Calculating ideal (initial) width for the whole table including
- // width of each table column (content and line number columns) and
- // border. We also add a 1px correction as some values are calculated
- // in 'ch'.
-
- // We might have 1 to 2 columns for content depending if side-by-side
- // or unified mode
- const contentWidth = `${sideBySide ? 2 : 1} * ${lineLimit}`;
-
- // We always have 2 columns for line number
- const lineNumberWidth = `2 * ${getLineNumberCellWidth(this.prefs)}px`;
-
- // border-right in ".section" css definition (in gr-diff_html.ts)
- const sectionRightBorder = '1px';
-
- // each sign col has 1ch width.
- const signColsWidth =
- sideBySide && this.renderPrefs?.show_sign_col ? '2ch' : '0ch';
-
- // As some of these calculations are done using 'ch' we end up having <1px
- // difference between ideal and calculated size for each side leading to
- // lines using the max columns (e.g. 80) to wrap (decided exclusively by
- // the browser).This happens even in monospace fonts. Empirically adding
- // 2px as correction to be sure wrapping won't happen in these cases so it
- // doesn't block further experimentation with the SHRINK_MODE. This was
- // previously set to 1px but due to to a more aggressive text wrapping
- // (via word-break: break-all; - check .contextText) we need to be even
- // more lenient in some cases. If we find another way to avoid this
- // correction we will change it.
- const dontWrapCorrection = '2px';
- this.style.setProperty(
- '--diff-max-width',
- `calc(${contentWidth} + ${lineNumberWidth} + ${signColsWidth} + ${sectionRightBorder} + ${dontWrapCorrection})`
- );
- } else {
- this.style.setProperty('--diff-max-width', 'none');
- }
- if (this.prefs.font_size) {
- this.style.setProperty('--font-size', `${this.prefs.font_size}px`);
- }
- }
-
- private renderPrefsChanged() {
- this.diffModel.updateState({renderPrefs: this.renderPrefs});
- if (this.renderPrefs.hide_left_side) {
- this.classList.add('no-left');
- }
- if (this.renderPrefs.disable_context_control_buttons) {
- this.classList.add('disable-context-control-buttons');
- }
- if (this.renderPrefs.hide_line_length_indicator) {
- this.classList.add('hide-line-length-indicator');
- }
- if (this.renderPrefs.show_sign_col) {
- this.classList.add('with-sign-col');
- }
- if (this.prefs) {
- this.updatePreferenceStyles();
- }
- this.diffBuilder.updateRenderPrefs(this.renderPrefs);
- }
-
- private diffChanged() {
- this.loading = true;
- this.cleanup();
- if (this.diff) {
- this.diffLength = this.getDiffLength(this.diff);
- this.debounceRenderDiffTable();
- assertIsDefined(this.diffTable, 'diffTable');
- this.diffSelection.init(this.diff, this.diffTable);
- this.highlights.init(this.diffTable, this.diffBuilder);
- }
- }
-
- // Implemented so the test can stub it.
- getDiffLength(diff?: DiffInfo) {
- return getDiffLength(diff);
- }
-
- /**
- * When called multiple times from the same task, will call
- * _renderDiffTable only once, in the next task (scheduled via `setTimeout`).
- *
- * This should be used instead of calling _renderDiffTable directly to
- * render the diff in response to an input change, because there may be
- * multiple inputs changing in the same microtask, but we only want to
- * render once.
- */
- private debounceRenderDiffTable() {
- // at this point gr-diff might be considered as rendered from the outside
- // (client), although it was not actually rendered. Clients need to know
- // when it is safe to perform operations like cursor moves, for example,
- // and if changing an input actually requires a reload of the diff table.
- // Since `fire` is synchronous it allows clients to be aware when an
- // async render is needed and that they can wait for a further `render`
- // event to actually take further action.
- fire(this, 'render-required', {});
- this.renderDiffTableTask = debounceP(
- this.renderDiffTableTask,
- async () => await this.renderDiffTable()
- );
- this.renderDiffTableTask.catch((e: unknown) => {
- if (e === DELAYED_CANCELLATION) return;
- throw e;
- });
- }
-
- // Private but used in tests.
- async renderDiffTable() {
- this.unobserveNodes();
- if (!this.diff || !this.prefs) {
- fire(this, 'render', {});
- return;
- }
- if (
- this.prefs.context === -1 &&
- this.diffLength &&
- this.diffLength >= LARGE_DIFF_THRESHOLD_LINES &&
- this.safetyBypass === null
- ) {
- this.showWarning = true;
- fire(this, 'render', {});
- return;
- }
-
- this.showWarning = false;
-
- const keyLocations = this.computeKeyLocations();
-
- this.diffModel.setState({
- diff: this.diff,
- path: this.path,
- renderPrefs: this.renderPrefs,
- diffPrefs: this.prefs,
- });
-
- // TODO: Setting tons of public properties like this is obviously a code
- // smell. We are introducing a diff model for managing all this
- // data. Then diff builder will only need access to that model.
- this.diffBuilder.prefs = this.getBypassPrefs();
- this.diffBuilder.renderPrefs = this.renderPrefs;
- this.diffBuilder.diff = this.diff;
- this.diffBuilder.path = this.path;
- this.diffBuilder.viewMode = this.viewMode;
- this.diffBuilder.layers = this.layers ?? [];
- this.diffBuilder.baseImage = this.baseImage ?? null;
- this.diffBuilder.revisionImage = this.revisionImage ?? null;
- this.diffBuilder.useNewImageDiffUi = this.useNewImageDiffUi;
- this.diffBuilder.diffElement = this.diffTable;
- // `this.commentRanges` are probably empty here, because they will only be
- // populated by the node observer, which starts observing *after* rendering.
- this.diffBuilder.updateCommentRanges(this.commentRanges);
- this.diffBuilder.updateCoverageRanges(this.coverageRanges);
- await this.diffBuilder.render(keyLocations);
- }
-
- private handleRenderContent() {
- this.querySelectorAll('gr-ranged-comment-hint').forEach(element =>
- element.remove()
- );
- this.loading = false;
- this.observeNodes();
- // We are just converting 'render-content' into 'render' here. Maybe we
- // should retire the 'render' event in favor of 'render-content'?
- fire(this, 'render', {});
- }
-
- private observeNodes() {
- // First stop observing old nodes.
- this.unobserveNodes();
- // Then introduce a Mutation observer that watches for children being added
- // to gr-diff. If those children are `isThreadEl`, namely then they are
- // processed.
- this.nodeObserver = new MutationObserver(mutations => {
- const addedThreadEls = extractAddedNodes(mutations).filter(isThreadEl);
- const removedThreadEls =
- extractRemovedNodes(mutations).filter(isThreadEl);
- this.processNodes(addedThreadEls, removedThreadEls);
- });
- this.nodeObserver.observe(this, {childList: true});
- // Make sure to process existing gr-comment-threads that already exist.
- this.processNodes([...this.childNodes].filter(isThreadEl), []);
- }
-
- private processNodes(
- addedThreadEls: GrDiffThreadElement[],
- removedThreadEls: GrDiffThreadElement[]
- ) {
- this.updateRanges(addedThreadEls, removedThreadEls);
- addedThreadEls.forEach(threadEl =>
- this.redispatchHoverEvents(threadEl, threadEl)
- );
- // Removed nodes do not need to be handled because all this code does is
- // adding a slot for the added thread elements, and the extra slots do
- // not hurt. It's probably a bigger performance cost to remove them than
- // to keep them around. Medium term we can even consider to add one slot
- // for each line from the start.
- for (const threadEl of addedThreadEls) {
- const lineNum = getLine(threadEl);
- const commentSide = getSide(threadEl);
- const range = getRange(threadEl);
- if (!commentSide) continue;
- const lineEl = this.diffBuilder.getLineElByNumber(lineNum, commentSide);
- // When the line the comment refers to does not exist, log an error
- // but don't crash. This can happen e.g. if the API does not fully
- // validate e.g. (robot) comments
- if (!lineEl) {
- console.error(
- 'thread attached to line ',
- commentSide,
- lineNum,
- ' which does not exist.'
- );
- continue;
- }
- const contentEl = this.diffBuilder.getContentTdByLineEl(lineEl);
- if (!contentEl) continue;
- if (lineNum === LOST) {
- this.insertPortedCommentsWithoutRangeMessage(contentEl);
- }
-
- const slotAtt = threadEl.getAttribute('slot');
- if (range && isLongCommentRange(range) && slotAtt) {
- const longRangeCommentHint = document.createElement(
- 'gr-ranged-comment-hint'
- );
- longRangeCommentHint.range = range;
- longRangeCommentHint.setAttribute('threadElRootId', threadEl.rootId);
- longRangeCommentHint.setAttribute('slot', slotAtt);
- this.insertBefore(longRangeCommentHint, threadEl);
- this.redispatchHoverEvents(longRangeCommentHint, threadEl);
- }
- }
-
- for (const threadEl of removedThreadEls) {
- this.querySelector(
- `gr-ranged-comment-hint[threadElRootId="${threadEl.rootId}"]`
- )?.remove();
- }
- }
-
- private unobserveNodes() {
- if (this.nodeObserver) {
- this.nodeObserver.disconnect();
- this.nodeObserver = undefined;
- }
- // You only stop observing for comment thread elements when the diff is
- // completely rendered from scratch. And then comment thread elements
- // will be (re-)added *after* rendering is done. That is also when we
- // re-start observing. So it is appropriate to thoroughly clean up
- // everything that the observer is managing.
- this.commentRanges = [];
- }
-
- private insertPortedCommentsWithoutRangeMessage(lostCell: Element) {
- const existingMessage = lostCell.querySelector('div.lost-message');
- if (existingMessage) return;
-
- const div = document.createElement('div');
- div.className = 'lost-message';
- const icon = document.createElement('gr-icon');
- icon.setAttribute('icon', 'info');
- div.appendChild(icon);
- const span = document.createElement('span');
- span.innerText = 'Original comment position not found in this patchset';
- div.appendChild(span);
- lostCell.insertBefore(div, lostCell.firstChild);
- }
-
- /**
- * Get the preferences object including the safety bypass context (if any).
- */
- private getBypassPrefs() {
- assertIsDefined(this.prefs, 'prefs');
- if (this.safetyBypass !== null) {
- return {...this.prefs, context: this.safetyBypass};
- }
- return this.prefs;
- }
-
- clearDiffContent() {
- this.unobserveNodes();
- if (!this.diffTable) return;
- while (this.diffTable.hasChildNodes()) {
- this.diffTable.removeChild(this.diffTable.lastChild!);
- }
- }
-
- // Private but used in tests.
- computeDiffHeaderItems() {
- return (this.diff?.diff_header ?? [])
- .filter(
- item =>
- !(
- item.startsWith('diff --git ') ||
- item.startsWith('index ') ||
- item.startsWith('+++ ') ||
- item.startsWith('--- ') ||
- item === 'Binary files differ'
- )
- )
- .map(expandFileMode);
- }
-
- private handleFullBypass() {
- this.safetyBypass = FULL_CONTEXT;
- this.debounceRenderDiffTable();
- }
-
- private collapseContext() {
- // Uses the default context amount if the preference is for the entire file.
- this.safetyBypass =
- this.prefs?.context && this.prefs.context >= 0
- ? null
- : createDefaultDiffPrefs().context;
- this.debounceRenderDiffTable();
- }
-
- toggleAllContext() {
- if (!this.prefs) {
- return;
- }
- if (this.getBypassPrefs().context < 0) {
- this.collapseContext();
- } else {
- this.handleFullBypass();
- }
- }
-
- private computeNewlineWarning(): string | undefined {
- const messages = [];
- if (this.showNewlineWarningLeft) {
- messages.push(NO_NEWLINE_LEFT);
- }
- if (this.showNewlineWarningRight) {
- messages.push(NO_NEWLINE_RIGHT);
- }
- if (!messages.length) {
- return undefined;
- }
- return messages.join(' \u2014 '); // \u2014 - '—'
- }
-}
-
-function extractAddedNodes(mutations: MutationRecord[]) {
- return mutations.flatMap(mutation => [...mutation.addedNodes]);
-}
-
-function extractRemovedNodes(mutations: MutationRecord[]) {
- return mutations.flatMap(mutation => [...mutation.removedNodes]);
-}
-
-if (!isNewDiff()) {
- customElements.define('gr-diff', GrDiff);
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-diff': LitElement;
- }
- interface HTMLElementEventMap {
- 'comment-thread-mouseenter': CustomEvent<GrDiffCommentThread>;
- 'comment-thread-mouseleave': CustomEvent<GrDiffCommentThread>;
- 'loading-changed': ValueChangedEvent<boolean>;
- 'render-required': CustomEvent<{}>;
- }
-}
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff_test.ts b/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff_test.ts
deleted file mode 100644
index ac444b7..0000000
--- a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff_test.ts
+++ /dev/null
@@ -1,4183 +0,0 @@
-/**
- * @license
- * Copyright 2015 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup';
-import {createDiff} from '../../../test/test-data-generators';
-import './gr-diff';
-import {getComputedStyleValue} from '../../../utils/dom-util';
-import '@polymer/paper-button/paper-button';
-import {
- DiffContent,
- DiffInfo,
- DiffPreferencesInfo,
- DiffViewMode,
- IgnoreWhitespaceType,
- Side,
-} from '../../../api/diff';
-import {
- mockPromise,
- mouseDown,
- query,
- queryAll,
- queryAndAssert,
- waitEventLoop,
- waitQueryAndAssert,
- waitUntil,
-} from '../../../test/test-utils';
-import {AbortStop} from '../../../api/core';
-import {waitForEventOnce} from '../../../utils/event-util';
-import {GrDiff} from './gr-diff';
-import {ImageInfo} from '../../../types/common';
-import {GrRangedCommentHint} from '../../diff/gr-ranged-comment-hint/gr-ranged-comment-hint';
-import {assertIsDefined} from '../../../utils/common-util';
-import {fixture, html, assert} from '@open-wc/testing';
-
-suite('gr-diff a11y test', () => {
- test('audit', async () => {
- assert.isAccessible(await fixture(html`<gr-diff></gr-diff>`));
- });
-});
-
-suite('gr-diff tests', () => {
- let element: GrDiff;
-
- const MINIMAL_PREFS: DiffPreferencesInfo = {
- tab_size: 2,
- line_length: 80,
- font_size: 12,
- context: 3,
- ignore_whitespace: 'IGNORE_NONE',
- };
-
- setup(async () => {
- element = await fixture<GrDiff>(html`<gr-diff></gr-diff>`);
- });
-
- suite('rendering', () => {
- test('empty diff', async () => {
- await element.updateComplete;
- assert.shadowDom.equal(
- element,
- /* HTML */ `
- <div class="diffContainer oldDiff sideBySide">
- <table id="diffTable"></table>
- </div>
- `
- );
- });
-
- test('a unified diff lit', async () => {
- element.viewMode = DiffViewMode.UNIFIED;
- element.prefs = {...MINIMAL_PREFS};
- element.diff = createDiff();
- await element.updateComplete;
- await waitForEventOnce(element, 'render');
- assert.shadowDom.equal(
- element,
- /* HTML */ `
- <div class="diffContainer oldDiff unified">
- <table class="selected-right" id="diffTable">
- <colgroup>
- <col class="blame gr-diff" />
- <col class="gr-diff" width="48" />
- <col class="gr-diff" width="48" />
- <col class="gr-diff" />
- </colgroup>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-LOST right-button-LOST right-content-LOST"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="LOST"></td>
- <td class="gr-diff left lineNum" data-value="LOST"></td>
- <td class="gr-diff lineNum right" data-value="LOST"></td>
- <td class="both content gr-diff lost no-intraline-info right">
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-FILE right-button-FILE right-content-FILE"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="FILE"></td>
- <td class="gr-diff left lineNum" data-value="FILE">
- <button
- aria-label="Add file comment"
- class="gr-diff left lineNumButton"
- data-value="FILE"
- id="left-button-FILE"
- tabindex="-1"
- >
- File
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="FILE">
- <button
- aria-label="Add file comment"
- class="gr-diff lineNumButton right"
- data-value="FILE"
- id="right-button-FILE"
- tabindex="-1"
- >
- File
- </button>
- </td>
- <td class="both content file gr-diff no-intraline-info right">
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-1 right-button-1 right-content-1"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="1"></td>
- <td class="gr-diff left lineNum" data-value="1">
- <button
- aria-label="1 unmodified"
- class="gr-diff left lineNumButton"
- data-value="1"
- id="left-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="1">
- <button
- aria-label="1 unmodified"
- class="gr-diff lineNumButton right"
- data-value="1"
- id="right-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-1"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-2 right-button-2 right-content-2"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="2"></td>
- <td class="gr-diff left lineNum" data-value="2">
- <button
- aria-label="2 unmodified"
- class="gr-diff left lineNumButton"
- data-value="2"
- id="left-button-2"
- tabindex="-1"
- >
- 2
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="2">
- <button
- aria-label="2 unmodified"
- class="gr-diff lineNumButton right"
- data-value="2"
- id="right-button-2"
- tabindex="-1"
- >
- 2
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-2"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-3 right-button-3 right-content-3"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="3"></td>
- <td class="gr-diff left lineNum" data-value="3">
- <button
- aria-label="3 unmodified"
- class="gr-diff left lineNumButton"
- data-value="3"
- id="left-button-3"
- tabindex="-1"
- >
- 3
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="3">
- <button
- aria-label="3 unmodified"
- class="gr-diff lineNumButton right"
- data-value="3"
- id="right-button-3"
- tabindex="-1"
- >
- 3
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-3"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-4 right-button-4 right-content-4"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="4"></td>
- <td class="gr-diff left lineNum" data-value="4">
- <button
- aria-label="4 unmodified"
- class="gr-diff left lineNumButton"
- data-value="4"
- id="left-button-4"
- tabindex="-1"
- >
- 4
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="4">
- <button
- aria-label="4 unmodified"
- class="gr-diff lineNumButton right"
- data-value="4"
- id="right-button-4"
- tabindex="-1"
- >
- 4
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-4"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="delta gr-diff section total">
- <tr
- aria-labelledby="right-button-5 right-content-5"
- class="add diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="gr-diff left"></td>
- <td class="gr-diff lineNum right" data-value="5">
- <button
- aria-label="5 added"
- class="gr-diff lineNumButton right"
- data-value="5"
- id="right-button-5"
- tabindex="-1"
- >
- 5
- </button>
- </td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-5"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="right-button-6 right-content-6"
- class="add diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="gr-diff left"></td>
- <td class="gr-diff lineNum right" data-value="6">
- <button
- aria-label="6 added"
- class="gr-diff lineNumButton right"
- data-value="6"
- id="right-button-6"
- tabindex="-1"
- >
- 6
- </button>
- </td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-6"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="right-button-7 right-content-7"
- class="add diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="gr-diff left"></td>
- <td class="gr-diff lineNum right" data-value="7">
- <button
- aria-label="7 added"
- class="gr-diff lineNumButton right"
- data-value="7"
- id="right-button-7"
- tabindex="-1"
- >
- 7
- </button>
- </td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-7"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-5 right-button-8 right-content-8"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="5"></td>
- <td class="gr-diff left lineNum" data-value="5">
- <button
- aria-label="5 unmodified"
- class="gr-diff left lineNumButton"
- data-value="5"
- id="left-button-5"
- tabindex="-1"
- >
- 5
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="8">
- <button
- aria-label="8 unmodified"
- class="gr-diff lineNumButton right"
- data-value="8"
- id="right-button-8"
- tabindex="-1"
- >
- 8
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-8"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-6 right-button-9 right-content-9"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="6"></td>
- <td class="gr-diff left lineNum" data-value="6">
- <button
- aria-label="6 unmodified"
- class="gr-diff left lineNumButton"
- data-value="6"
- id="left-button-6"
- tabindex="-1"
- >
- 6
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="9">
- <button
- aria-label="9 unmodified"
- class="gr-diff lineNumButton right"
- data-value="9"
- id="right-button-9"
- tabindex="-1"
- >
- 9
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-9"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-7 right-button-10 right-content-10"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="7"></td>
- <td class="gr-diff left lineNum" data-value="7">
- <button
- aria-label="7 unmodified"
- class="gr-diff left lineNumButton"
- data-value="7"
- id="left-button-7"
- tabindex="-1"
- >
- 7
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="10">
- <button
- aria-label="10 unmodified"
- class="gr-diff lineNumButton right"
- data-value="10"
- id="right-button-10"
- tabindex="-1"
- >
- 10
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-10"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-8 right-button-11 right-content-11"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="8"></td>
- <td class="gr-diff left lineNum" data-value="8">
- <button
- aria-label="8 unmodified"
- class="gr-diff left lineNumButton"
- data-value="8"
- id="left-button-8"
- tabindex="-1"
- >
- 8
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="11">
- <button
- aria-label="11 unmodified"
- class="gr-diff lineNumButton right"
- data-value="11"
- id="right-button-11"
- tabindex="-1"
- >
- 11
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-11"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-9 right-button-12 right-content-12"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="9"></td>
- <td class="gr-diff left lineNum" data-value="9">
- <button
- aria-label="9 unmodified"
- class="gr-diff left lineNumButton"
- data-value="9"
- id="left-button-9"
- tabindex="-1"
- >
- 9
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="12">
- <button
- aria-label="12 unmodified"
- class="gr-diff lineNumButton right"
- data-value="12"
- id="right-button-12"
- tabindex="-1"
- >
- 12
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-12"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="delta gr-diff section total">
- <tr
- aria-labelledby="left-button-10 left-content-10"
- class="diff-row gr-diff remove unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="10"></td>
- <td class="gr-diff left lineNum" data-value="10">
- <button
- aria-label="10 removed"
- class="gr-diff left lineNumButton"
- data-value="10"
- id="left-button-10"
- tabindex="-1"
- >
- 10
- </button>
- </td>
- <td class="gr-diff right"></td>
- <td class="content gr-diff left no-intraline-info remove">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-10"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-11 left-content-11"
- class="diff-row gr-diff remove unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="11"></td>
- <td class="gr-diff left lineNum" data-value="11">
- <button
- aria-label="11 removed"
- class="gr-diff left lineNumButton"
- data-value="11"
- id="left-button-11"
- tabindex="-1"
- >
- 11
- </button>
- </td>
- <td class="gr-diff right"></td>
- <td class="content gr-diff left no-intraline-info remove">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-11"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-12 left-content-12"
- class="diff-row gr-diff remove unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="12"></td>
- <td class="gr-diff left lineNum" data-value="12">
- <button
- aria-label="12 removed"
- class="gr-diff left lineNumButton"
- data-value="12"
- id="left-button-12"
- tabindex="-1"
- >
- 12
- </button>
- </td>
- <td class="gr-diff right"></td>
- <td class="content gr-diff left no-intraline-info remove">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-12"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-13 left-content-13"
- class="diff-row gr-diff remove unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="13"></td>
- <td class="gr-diff left lineNum" data-value="13">
- <button
- aria-label="13 removed"
- class="gr-diff left lineNumButton"
- data-value="13"
- id="left-button-13"
- tabindex="-1"
- >
- 13
- </button>
- </td>
- <td class="gr-diff right"></td>
- <td class="content gr-diff left no-intraline-info remove">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-13"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="delta gr-diff ignoredWhitespaceOnly section">
- <tr
- aria-labelledby="right-button-13 right-content-13"
- class="add diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="gr-diff left"></td>
- <td class="gr-diff lineNum right" data-value="13">
- <button
- aria-label="13 added"
- class="gr-diff lineNumButton right"
- data-value="13"
- id="right-button-13"
- tabindex="-1"
- >
- 13
- </button>
- </td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-13"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="right-button-14 right-content-14"
- class="add diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="gr-diff left"></td>
- <td class="gr-diff lineNum right" data-value="14">
- <button
- aria-label="14 added"
- class="gr-diff lineNumButton right"
- data-value="14"
- id="right-button-14"
- tabindex="-1"
- >
- 14
- </button>
- </td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-14"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="delta gr-diff section">
- <tr
- aria-labelledby="left-button-16 left-content-16"
- class="diff-row gr-diff remove unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="16"></td>
- <td class="gr-diff left lineNum" data-value="16">
- <button
- aria-label="16 removed"
- class="gr-diff left lineNumButton"
- data-value="16"
- id="left-button-16"
- tabindex="-1"
- >
- 16
- </button>
- </td>
- <td class="gr-diff right"></td>
- <td class="content gr-diff left remove">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-16"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="right-button-15 right-content-15"
- class="add diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="gr-diff left"></td>
- <td class="gr-diff lineNum right" data-value="15">
- <button
- aria-label="15 added"
- class="gr-diff lineNumButton right"
- data-value="15"
- id="right-button-15"
- tabindex="-1"
- >
- 15
- </button>
- </td>
- <td class="add content gr-diff right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-15"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-17 right-button-16 right-content-16"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="17"></td>
- <td class="gr-diff left lineNum" data-value="17">
- <button
- aria-label="17 unmodified"
- class="gr-diff left lineNumButton"
- data-value="17"
- id="left-button-17"
- tabindex="-1"
- >
- 17
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="16">
- <button
- aria-label="16 unmodified"
- class="gr-diff lineNumButton right"
- data-value="16"
- id="right-button-16"
- tabindex="-1"
- >
- 16
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-16"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-18 right-button-17 right-content-17"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="18"></td>
- <td class="gr-diff left lineNum" data-value="18">
- <button
- aria-label="18 unmodified"
- class="gr-diff left lineNumButton"
- data-value="18"
- id="left-button-18"
- tabindex="-1"
- >
- 18
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="17">
- <button
- aria-label="17 unmodified"
- class="gr-diff lineNumButton right"
- data-value="17"
- id="right-button-17"
- tabindex="-1"
- >
- 17
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-17"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-19 right-button-18 right-content-18"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="19"></td>
- <td class="gr-diff left lineNum" data-value="19">
- <button
- aria-label="19 unmodified"
- class="gr-diff left lineNumButton"
- data-value="19"
- id="left-button-19"
- tabindex="-1"
- >
- 19
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="18">
- <button
- aria-label="18 unmodified"
- class="gr-diff lineNumButton right"
- data-value="18"
- id="right-button-18"
- tabindex="-1"
- >
- 18
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-18"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="contextControl gr-diff section">
- <tr class="above contextBackground gr-diff unified">
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="contextLineNum gr-diff"></td>
- <td class="contextLineNum gr-diff"></td>
- <td class="gr-diff"></td>
- </tr>
- <tr class="dividerRow gr-diff show-both">
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="dividerCell gr-diff" colspan="3">
- <gr-context-controls class="gr-diff" showconfig="both">
- </gr-context-controls>
- </td>
- </tr>
- <tr class="below contextBackground gr-diff unified">
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="contextLineNum gr-diff"></td>
- <td class="contextLineNum gr-diff"></td>
- <td class="gr-diff"></td>
- </tr>
- </tbody>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-38 right-button-37 right-content-37"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="38"></td>
- <td class="gr-diff left lineNum" data-value="38">
- <button
- aria-label="38 unmodified"
- class="gr-diff left lineNumButton"
- data-value="38"
- id="left-button-38"
- tabindex="-1"
- >
- 38
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="37">
- <button
- aria-label="37 unmodified"
- class="gr-diff lineNumButton right"
- data-value="37"
- id="right-button-37"
- tabindex="-1"
- >
- 37
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-37"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-39 right-button-38 right-content-38"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="39"></td>
- <td class="gr-diff left lineNum" data-value="39">
- <button
- aria-label="39 unmodified"
- class="gr-diff left lineNumButton"
- data-value="39"
- id="left-button-39"
- tabindex="-1"
- >
- 39
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="38">
- <button
- aria-label="38 unmodified"
- class="gr-diff lineNumButton right"
- data-value="38"
- id="right-button-38"
- tabindex="-1"
- >
- 38
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-38"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-40 right-button-39 right-content-39"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="40"></td>
- <td class="gr-diff left lineNum" data-value="40">
- <button
- aria-label="40 unmodified"
- class="gr-diff left lineNumButton"
- data-value="40"
- id="left-button-40"
- tabindex="-1"
- >
- 40
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="39">
- <button
- aria-label="39 unmodified"
- class="gr-diff lineNumButton right"
- data-value="39"
- id="right-button-39"
- tabindex="-1"
- >
- 39
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-39"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="delta gr-diff section total">
- <tr
- aria-labelledby="right-button-40 right-content-40"
- class="add diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="gr-diff left"></td>
- <td class="gr-diff lineNum right" data-value="40">
- <button
- aria-label="40 added"
- class="gr-diff lineNumButton right"
- data-value="40"
- id="right-button-40"
- tabindex="-1"
- >
- 40
- </button>
- </td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-40"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="right-button-41 right-content-41"
- class="add diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="gr-diff left"></td>
- <td class="gr-diff lineNum right" data-value="41">
- <button
- aria-label="41 added"
- class="gr-diff lineNumButton right"
- data-value="41"
- id="right-button-41"
- tabindex="-1"
- >
- 41
- </button>
- </td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-41"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="right-button-42 right-content-42"
- class="add diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="gr-diff left"></td>
- <td class="gr-diff lineNum right" data-value="42">
- <button
- aria-label="42 added"
- class="gr-diff lineNumButton right"
- data-value="42"
- id="right-button-42"
- tabindex="-1"
- >
- 42
- </button>
- </td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-42"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="right-button-43 right-content-43"
- class="add diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="gr-diff left"></td>
- <td class="gr-diff lineNum right" data-value="43">
- <button
- aria-label="43 added"
- class="gr-diff lineNumButton right"
- data-value="43"
- id="right-button-43"
- tabindex="-1"
- >
- 43
- </button>
- </td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-43"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-41 right-button-44 right-content-44"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="41"></td>
- <td class="gr-diff left lineNum" data-value="41">
- <button
- aria-label="41 unmodified"
- class="gr-diff left lineNumButton"
- data-value="41"
- id="left-button-41"
- tabindex="-1"
- >
- 41
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="44">
- <button
- aria-label="44 unmodified"
- class="gr-diff lineNumButton right"
- data-value="44"
- id="right-button-44"
- tabindex="-1"
- >
- 44
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-44"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-42 right-button-45 right-content-45"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="42"></td>
- <td class="gr-diff left lineNum" data-value="42">
- <button
- aria-label="42 unmodified"
- class="gr-diff left lineNumButton"
- data-value="42"
- id="left-button-42"
- tabindex="-1"
- >
- 42
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="45">
- <button
- aria-label="45 unmodified"
- class="gr-diff lineNumButton right"
- data-value="45"
- id="right-button-45"
- tabindex="-1"
- >
- 45
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-45"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-43 right-button-46 right-content-46"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="43"></td>
- <td class="gr-diff left lineNum" data-value="43">
- <button
- aria-label="43 unmodified"
- class="gr-diff left lineNumButton"
- data-value="43"
- id="left-button-43"
- tabindex="-1"
- >
- 43
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="46">
- <button
- aria-label="46 unmodified"
- class="gr-diff lineNumButton right"
- data-value="46"
- id="right-button-46"
- tabindex="-1"
- >
- 46
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-46"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-44 right-button-47 right-content-47"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="44"></td>
- <td class="gr-diff left lineNum" data-value="44">
- <button
- aria-label="44 unmodified"
- class="gr-diff left lineNumButton"
- data-value="44"
- id="left-button-44"
- tabindex="-1"
- >
- 44
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="47">
- <button
- aria-label="47 unmodified"
- class="gr-diff lineNumButton right"
- data-value="47"
- id="right-button-47"
- tabindex="-1"
- >
- 47
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-47"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-45 right-button-48 right-content-48"
- class="both diff-row gr-diff unified"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="45"></td>
- <td class="gr-diff left lineNum" data-value="45">
- <button
- aria-label="45 unmodified"
- class="gr-diff left lineNumButton"
- data-value="45"
- id="left-button-45"
- tabindex="-1"
- >
- 45
- </button>
- </td>
- <td class="gr-diff lineNum right" data-value="48">
- <button
- aria-label="48 unmodified"
- class="gr-diff lineNumButton right"
- data-value="48"
- id="right-button-48"
- tabindex="-1"
- >
- 48
- </button>
- </td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-48"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- </table>
- </div>
- `,
- {
- ignoreTags: [
- 'gr-context-controls-section',
- 'gr-diff-section',
- 'gr-diff-row',
- 'gr-diff-text',
- 'gr-legacy-text',
- 'slot',
- ],
- }
- );
- });
-
- test('a normal diff lit', async () => {
- element.prefs = {...MINIMAL_PREFS};
- element.diff = createDiff();
- await element.updateComplete;
- await waitForEventOnce(element, 'render');
- assert.shadowDom.equal(
- element,
- /* HTML */ `
- <div class="diffContainer oldDiff sideBySide">
- <table class="selected-right" id="diffTable">
- <colgroup>
- <col class="blame gr-diff" />
- <col class="gr-diff left" width="48" />
- <col class="gr-diff left sign" />
- <col class="gr-diff left" />
- <col class="gr-diff right" width="48" />
- <col class="gr-diff right sign" />
- <col class="gr-diff right" />
- </colgroup>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-LOST left-content-LOST right-button-LOST right-content-LOST"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="LOST"></td>
- <td class="gr-diff left lineNum" data-value="LOST"></td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left lost no-intraline-info">
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="LOST"></td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff lost no-intraline-info right">
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-FILE left-content-FILE right-button-FILE right-content-FILE"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="FILE"></td>
- <td class="gr-diff left lineNum" data-value="FILE">
- <button
- aria-label="Add file comment"
- class="gr-diff left lineNumButton"
- data-value="FILE"
- id="left-button-FILE"
- tabindex="-1"
- >
- File
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content file gr-diff left no-intraline-info">
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="FILE">
- <button
- aria-label="Add file comment"
- class="gr-diff lineNumButton right"
- data-value="FILE"
- id="right-button-FILE"
- tabindex="-1"
- >
- File
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content file gr-diff no-intraline-info right">
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-1 left-content-1 right-button-1 right-content-1"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="1"></td>
- <td class="gr-diff left lineNum" data-value="1">
- <button
- aria-label="1 unmodified"
- class="gr-diff left lineNumButton"
- data-value="1"
- id="left-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-1"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="1">
- <button
- aria-label="1 unmodified"
- class="gr-diff lineNumButton right"
- data-value="1"
- id="right-button-1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-1"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-2 left-content-2 right-button-2 right-content-2"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="2"></td>
- <td class="gr-diff left lineNum" data-value="2">
- <button
- aria-label="2 unmodified"
- class="gr-diff left lineNumButton"
- data-value="2"
- id="left-button-2"
- tabindex="-1"
- >
- 2
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-2"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="2">
- <button
- aria-label="2 unmodified"
- class="gr-diff lineNumButton right"
- data-value="2"
- id="right-button-2"
- tabindex="-1"
- >
- 2
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-2"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-3 left-content-3 right-button-3 right-content-3"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="3"></td>
- <td class="gr-diff left lineNum" data-value="3">
- <button
- aria-label="3 unmodified"
- class="gr-diff left lineNumButton"
- data-value="3"
- id="left-button-3"
- tabindex="-1"
- >
- 3
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-3"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="3">
- <button
- aria-label="3 unmodified"
- class="gr-diff lineNumButton right"
- data-value="3"
- id="right-button-3"
- tabindex="-1"
- >
- 3
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-3"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-4 left-content-4 right-button-4 right-content-4"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="4"></td>
- <td class="gr-diff left lineNum" data-value="4">
- <button
- aria-label="4 unmodified"
- class="gr-diff left lineNumButton"
- data-value="4"
- id="left-button-4"
- tabindex="-1"
- >
- 4
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-4"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="4">
- <button
- aria-label="4 unmodified"
- class="gr-diff lineNumButton right"
- data-value="4"
- id="right-button-4"
- tabindex="-1"
- >
- 4
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-4"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="delta gr-diff section total">
- <tr
- aria-labelledby="right-button-5 right-content-5"
- class="diff-row gr-diff side-by-side"
- left-type="blank"
- right-type="add"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="blankLineNum gr-diff left"></td>
- <td class="blank gr-diff left no-intraline-info sign"></td>
- <td class="blank gr-diff left no-intraline-info">
- <div class="contentText gr-diff" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="5">
- <button
- aria-label="5 added"
- class="gr-diff lineNumButton right"
- data-value="5"
- id="right-button-5"
- tabindex="-1"
- >
- 5
- </button>
- </td>
- <td class="add gr-diff no-intraline-info right sign">+</td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-5"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="right-button-6 right-content-6"
- class="diff-row gr-diff side-by-side"
- left-type="blank"
- right-type="add"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="blankLineNum gr-diff left"></td>
- <td class="blank gr-diff left no-intraline-info sign"></td>
- <td class="blank gr-diff left no-intraline-info">
- <div class="contentText gr-diff" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="6">
- <button
- aria-label="6 added"
- class="gr-diff lineNumButton right"
- data-value="6"
- id="right-button-6"
- tabindex="-1"
- >
- 6
- </button>
- </td>
- <td class="add gr-diff no-intraline-info right sign">+</td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-6"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="right-button-7 right-content-7"
- class="diff-row gr-diff side-by-side"
- left-type="blank"
- right-type="add"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="blankLineNum gr-diff left"></td>
- <td class="blank gr-diff left no-intraline-info sign"></td>
- <td class="blank gr-diff left no-intraline-info">
- <div class="contentText gr-diff" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="7">
- <button
- aria-label="7 added"
- class="gr-diff lineNumButton right"
- data-value="7"
- id="right-button-7"
- tabindex="-1"
- >
- 7
- </button>
- </td>
- <td class="add gr-diff no-intraline-info right sign">+</td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-7"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-5 left-content-5 right-button-8 right-content-8"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="5"></td>
- <td class="gr-diff left lineNum" data-value="5">
- <button
- aria-label="5 unmodified"
- class="gr-diff left lineNumButton"
- data-value="5"
- id="left-button-5"
- tabindex="-1"
- >
- 5
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-5"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="8">
- <button
- aria-label="8 unmodified"
- class="gr-diff lineNumButton right"
- data-value="8"
- id="right-button-8"
- tabindex="-1"
- >
- 8
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-8"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-6 left-content-6 right-button-9 right-content-9"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="6"></td>
- <td class="gr-diff left lineNum" data-value="6">
- <button
- aria-label="6 unmodified"
- class="gr-diff left lineNumButton"
- data-value="6"
- id="left-button-6"
- tabindex="-1"
- >
- 6
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-6"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="9">
- <button
- aria-label="9 unmodified"
- class="gr-diff lineNumButton right"
- data-value="9"
- id="right-button-9"
- tabindex="-1"
- >
- 9
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-9"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-7 left-content-7 right-button-10 right-content-10"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="7"></td>
- <td class="gr-diff left lineNum" data-value="7">
- <button
- aria-label="7 unmodified"
- class="gr-diff left lineNumButton"
- data-value="7"
- id="left-button-7"
- tabindex="-1"
- >
- 7
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-7"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="10">
- <button
- aria-label="10 unmodified"
- class="gr-diff lineNumButton right"
- data-value="10"
- id="right-button-10"
- tabindex="-1"
- >
- 10
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-10"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-8 left-content-8 right-button-11 right-content-11"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="8"></td>
- <td class="gr-diff left lineNum" data-value="8">
- <button
- aria-label="8 unmodified"
- class="gr-diff left lineNumButton"
- data-value="8"
- id="left-button-8"
- tabindex="-1"
- >
- 8
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-8"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="11">
- <button
- aria-label="11 unmodified"
- class="gr-diff lineNumButton right"
- data-value="11"
- id="right-button-11"
- tabindex="-1"
- >
- 11
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-11"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-9 left-content-9 right-button-12 right-content-12"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="9"></td>
- <td class="gr-diff left lineNum" data-value="9">
- <button
- aria-label="9 unmodified"
- class="gr-diff left lineNumButton"
- data-value="9"
- id="left-button-9"
- tabindex="-1"
- >
- 9
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-9"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="12">
- <button
- aria-label="12 unmodified"
- class="gr-diff lineNumButton right"
- data-value="12"
- id="right-button-12"
- tabindex="-1"
- >
- 12
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-12"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="delta gr-diff section total">
- <tr
- aria-labelledby="left-button-10 left-content-10"
- class="diff-row gr-diff side-by-side"
- left-type="remove"
- right-type="blank"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="10"></td>
- <td class="gr-diff left lineNum" data-value="10">
- <button
- aria-label="10 removed"
- class="gr-diff left lineNumButton"
- data-value="10"
- id="left-button-10"
- tabindex="-1"
- >
- 10
- </button>
- </td>
- <td class="gr-diff left no-intraline-info remove sign">-</td>
- <td class="content gr-diff left no-intraline-info remove">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-10"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="blankLineNum gr-diff right"></td>
- <td class="blank gr-diff no-intraline-info right sign"></td>
- <td class="blank gr-diff no-intraline-info right">
- <div class="contentText gr-diff" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-11 left-content-11"
- class="diff-row gr-diff side-by-side"
- left-type="remove"
- right-type="blank"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="11"></td>
- <td class="gr-diff left lineNum" data-value="11">
- <button
- aria-label="11 removed"
- class="gr-diff left lineNumButton"
- data-value="11"
- id="left-button-11"
- tabindex="-1"
- >
- 11
- </button>
- </td>
- <td class="gr-diff left no-intraline-info remove sign">-</td>
- <td class="content gr-diff left no-intraline-info remove">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-11"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="blankLineNum gr-diff right"></td>
- <td class="blank gr-diff no-intraline-info right sign"></td>
- <td class="blank gr-diff no-intraline-info right">
- <div class="contentText gr-diff" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-12 left-content-12"
- class="diff-row gr-diff side-by-side"
- left-type="remove"
- right-type="blank"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="12"></td>
- <td class="gr-diff left lineNum" data-value="12">
- <button
- aria-label="12 removed"
- class="gr-diff left lineNumButton"
- data-value="12"
- id="left-button-12"
- tabindex="-1"
- >
- 12
- </button>
- </td>
- <td class="gr-diff left no-intraline-info remove sign">-</td>
- <td class="content gr-diff left no-intraline-info remove">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-12"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="blankLineNum gr-diff right"></td>
- <td class="blank gr-diff no-intraline-info right sign"></td>
- <td class="blank gr-diff no-intraline-info right">
- <div class="contentText gr-diff" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-13 left-content-13"
- class="diff-row gr-diff side-by-side"
- left-type="remove"
- right-type="blank"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="13"></td>
- <td class="gr-diff left lineNum" data-value="13">
- <button
- aria-label="13 removed"
- class="gr-diff left lineNumButton"
- data-value="13"
- id="left-button-13"
- tabindex="-1"
- >
- 13
- </button>
- </td>
- <td class="gr-diff left no-intraline-info remove sign">-</td>
- <td class="content gr-diff left no-intraline-info remove">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-13"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="blankLineNum gr-diff right"></td>
- <td class="blank gr-diff no-intraline-info right sign"></td>
- <td class="blank gr-diff no-intraline-info right">
- <div class="contentText gr-diff" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="delta gr-diff ignoredWhitespaceOnly section">
- <tr
- aria-labelledby="left-button-14 left-content-14 right-button-13 right-content-13"
- class="diff-row gr-diff side-by-side"
- left-type="remove"
- right-type="add"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="14"></td>
- <td class="gr-diff left lineNum" data-value="14">
- <button
- aria-label="14 removed"
- class="gr-diff left lineNumButton"
- data-value="14"
- id="left-button-14"
- tabindex="-1"
- >
- 14
- </button>
- </td>
- <td class="gr-diff left no-intraline-info remove sign">-</td>
- <td class="content gr-diff left no-intraline-info remove">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-14"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="13">
- <button
- aria-label="13 added"
- class="gr-diff lineNumButton right"
- data-value="13"
- id="right-button-13"
- tabindex="-1"
- >
- 13
- </button>
- </td>
- <td class="add gr-diff no-intraline-info right sign">+</td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-13"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-15 left-content-15 right-button-14 right-content-14"
- class="diff-row gr-diff side-by-side"
- left-type="remove"
- right-type="add"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="15"></td>
- <td class="gr-diff left lineNum" data-value="15">
- <button
- aria-label="15 removed"
- class="gr-diff left lineNumButton"
- data-value="15"
- id="left-button-15"
- tabindex="-1"
- >
- 15
- </button>
- </td>
- <td class="gr-diff left no-intraline-info remove sign">-</td>
- <td class="content gr-diff left no-intraline-info remove">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-15"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="14">
- <button
- aria-label="14 added"
- class="gr-diff lineNumButton right"
- data-value="14"
- id="right-button-14"
- tabindex="-1"
- >
- 14
- </button>
- </td>
- <td class="add gr-diff no-intraline-info right sign">+</td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-14"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="delta gr-diff section">
- <tr
- aria-labelledby="left-button-16 left-content-16 right-button-15 right-content-15"
- class="diff-row gr-diff side-by-side"
- left-type="remove"
- right-type="add"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="16"></td>
- <td class="gr-diff left lineNum" data-value="16">
- <button
- aria-label="16 removed"
- class="gr-diff left lineNumButton"
- data-value="16"
- id="left-button-16"
- tabindex="-1"
- >
- 16
- </button>
- </td>
- <td class="gr-diff left remove sign">-</td>
- <td class="content gr-diff left remove">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-16"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="15">
- <button
- aria-label="15 added"
- class="gr-diff lineNumButton right"
- data-value="15"
- id="right-button-15"
- tabindex="-1"
- >
- 15
- </button>
- </td>
- <td class="add gr-diff right sign">+</td>
- <td class="add content gr-diff right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-15"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-17 left-content-17 right-button-16 right-content-16"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="17"></td>
- <td class="gr-diff left lineNum" data-value="17">
- <button
- aria-label="17 unmodified"
- class="gr-diff left lineNumButton"
- data-value="17"
- id="left-button-17"
- tabindex="-1"
- >
- 17
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-17"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="16">
- <button
- aria-label="16 unmodified"
- class="gr-diff lineNumButton right"
- data-value="16"
- id="right-button-16"
- tabindex="-1"
- >
- 16
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-16"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-18 left-content-18 right-button-17 right-content-17"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="18"></td>
- <td class="gr-diff left lineNum" data-value="18">
- <button
- aria-label="18 unmodified"
- class="gr-diff left lineNumButton"
- data-value="18"
- id="left-button-18"
- tabindex="-1"
- >
- 18
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-18"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="17">
- <button
- aria-label="17 unmodified"
- class="gr-diff lineNumButton right"
- data-value="17"
- id="right-button-17"
- tabindex="-1"
- >
- 17
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-17"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-19 left-content-19 right-button-18 right-content-18"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="19"></td>
- <td class="gr-diff left lineNum" data-value="19">
- <button
- aria-label="19 unmodified"
- class="gr-diff left lineNumButton"
- data-value="19"
- id="left-button-19"
- tabindex="-1"
- >
- 19
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-19"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="18">
- <button
- aria-label="18 unmodified"
- class="gr-diff lineNumButton right"
- data-value="18"
- id="right-button-18"
- tabindex="-1"
- >
- 18
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-18"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="contextControl gr-diff section">
- <tr
- class="above contextBackground gr-diff side-by-side"
- left-type="contextControl"
- right-type="contextControl"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="contextLineNum gr-diff"></td>
- <td class="gr-diff sign"></td>
- <td class="gr-diff"></td>
- <td class="contextLineNum gr-diff"></td>
- <td class="gr-diff sign"></td>
- <td class="gr-diff"></td>
- </tr>
- <tr class="dividerRow gr-diff show-both">
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="gr-diff"></td>
- <td class="dividerCell gr-diff" colspan="3">
- <gr-context-controls
- class="gr-diff"
- showconfig="both"
- ></gr-context-controls>
- </td>
- </tr>
- <tr
- class="below contextBackground gr-diff side-by-side"
- left-type="contextControl"
- right-type="contextControl"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="contextLineNum gr-diff"></td>
- <td class="gr-diff sign"></td>
- <td class="gr-diff"></td>
- <td class="contextLineNum gr-diff"></td>
- <td class="gr-diff sign"></td>
- <td class="gr-diff"></td>
- </tr>
- </tbody>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-38 left-content-38 right-button-37 right-content-37"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="38"></td>
- <td class="gr-diff left lineNum" data-value="38">
- <button
- aria-label="38 unmodified"
- class="gr-diff left lineNumButton"
- data-value="38"
- id="left-button-38"
- tabindex="-1"
- >
- 38
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-38"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="37">
- <button
- aria-label="37 unmodified"
- class="gr-diff lineNumButton right"
- data-value="37"
- id="right-button-37"
- tabindex="-1"
- >
- 37
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-37"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-39 left-content-39 right-button-38 right-content-38"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="39"></td>
- <td class="gr-diff left lineNum" data-value="39">
- <button
- aria-label="39 unmodified"
- class="gr-diff left lineNumButton"
- data-value="39"
- id="left-button-39"
- tabindex="-1"
- >
- 39
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-39"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="38">
- <button
- aria-label="38 unmodified"
- class="gr-diff lineNumButton right"
- data-value="38"
- id="right-button-38"
- tabindex="-1"
- >
- 38
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-38"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-40 left-content-40 right-button-39 right-content-39"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="40"></td>
- <td class="gr-diff left lineNum" data-value="40">
- <button
- aria-label="40 unmodified"
- class="gr-diff left lineNumButton"
- data-value="40"
- id="left-button-40"
- tabindex="-1"
- >
- 40
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-40"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="39">
- <button
- aria-label="39 unmodified"
- class="gr-diff lineNumButton right"
- data-value="39"
- id="right-button-39"
- tabindex="-1"
- >
- 39
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-39"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="delta gr-diff section total">
- <tr
- aria-labelledby="right-button-40 right-content-40"
- class="diff-row gr-diff side-by-side"
- left-type="blank"
- right-type="add"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="blankLineNum gr-diff left"></td>
- <td class="blank gr-diff left no-intraline-info sign"></td>
- <td class="blank gr-diff left no-intraline-info">
- <div class="contentText gr-diff" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="40">
- <button
- aria-label="40 added"
- class="gr-diff lineNumButton right"
- data-value="40"
- id="right-button-40"
- tabindex="-1"
- >
- 40
- </button>
- </td>
- <td class="add gr-diff no-intraline-info right sign">+</td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-40"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="right-button-41 right-content-41"
- class="diff-row gr-diff side-by-side"
- left-type="blank"
- right-type="add"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="blankLineNum gr-diff left"></td>
- <td class="blank gr-diff left no-intraline-info sign"></td>
- <td class="blank gr-diff left no-intraline-info">
- <div class="contentText gr-diff" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="41">
- <button
- aria-label="41 added"
- class="gr-diff lineNumButton right"
- data-value="41"
- id="right-button-41"
- tabindex="-1"
- >
- 41
- </button>
- </td>
- <td class="add gr-diff no-intraline-info right sign">+</td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-41"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="right-button-42 right-content-42"
- class="diff-row gr-diff side-by-side"
- left-type="blank"
- right-type="add"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="blankLineNum gr-diff left"></td>
- <td class="blank gr-diff left no-intraline-info sign"></td>
- <td class="blank gr-diff left no-intraline-info">
- <div class="contentText gr-diff" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="42">
- <button
- aria-label="42 added"
- class="gr-diff lineNumButton right"
- data-value="42"
- id="right-button-42"
- tabindex="-1"
- >
- 42
- </button>
- </td>
- <td class="add gr-diff no-intraline-info right sign">+</td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-42"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="right-button-43 right-content-43"
- class="diff-row gr-diff side-by-side"
- left-type="blank"
- right-type="add"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="0"></td>
- <td class="blankLineNum gr-diff left"></td>
- <td class="blank gr-diff left no-intraline-info sign"></td>
- <td class="blank gr-diff left no-intraline-info">
- <div class="contentText gr-diff" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="43">
- <button
- aria-label="43 added"
- class="gr-diff lineNumButton right"
- data-value="43"
- id="right-button-43"
- tabindex="-1"
- >
- 43
- </button>
- </td>
- <td class="add gr-diff no-intraline-info right sign">+</td>
- <td class="add content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-43"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-41 left-content-41 right-button-44 right-content-44"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="41"></td>
- <td class="gr-diff left lineNum" data-value="41">
- <button
- aria-label="41 unmodified"
- class="gr-diff left lineNumButton"
- data-value="41"
- id="left-button-41"
- tabindex="-1"
- >
- 41
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-41"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="44">
- <button
- aria-label="44 unmodified"
- class="gr-diff lineNumButton right"
- data-value="44"
- id="right-button-44"
- tabindex="-1"
- >
- 44
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-44"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-42 left-content-42 right-button-45 right-content-45"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="42"></td>
- <td class="gr-diff left lineNum" data-value="42">
- <button
- aria-label="42 unmodified"
- class="gr-diff left lineNumButton"
- data-value="42"
- id="left-button-42"
- tabindex="-1"
- >
- 42
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-42"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="45">
- <button
- aria-label="45 unmodified"
- class="gr-diff lineNumButton right"
- data-value="45"
- id="right-button-45"
- tabindex="-1"
- >
- 45
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-45"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-43 left-content-43 right-button-46 right-content-46"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="43"></td>
- <td class="gr-diff left lineNum" data-value="43">
- <button
- aria-label="43 unmodified"
- class="gr-diff left lineNumButton"
- data-value="43"
- id="left-button-43"
- tabindex="-1"
- >
- 43
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-43"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="46">
- <button
- aria-label="46 unmodified"
- class="gr-diff lineNumButton right"
- data-value="46"
- id="right-button-46"
- tabindex="-1"
- >
- 46
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-46"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-44 left-content-44 right-button-47 right-content-47"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="44"></td>
- <td class="gr-diff left lineNum" data-value="44">
- <button
- aria-label="44 unmodified"
- class="gr-diff left lineNumButton"
- data-value="44"
- id="left-button-44"
- tabindex="-1"
- >
- 44
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-44"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="47">
- <button
- aria-label="47 unmodified"
- class="gr-diff lineNumButton right"
- data-value="47"
- id="right-button-47"
- tabindex="-1"
- >
- 47
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-47"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- <tr
- aria-labelledby="left-button-45 left-content-45 right-button-48 right-content-48"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="45"></td>
- <td class="gr-diff left lineNum" data-value="45">
- <button
- aria-label="45 unmodified"
- class="gr-diff left lineNumButton"
- data-value="45"
- id="left-button-45"
- tabindex="-1"
- >
- 45
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td class="both content gr-diff left no-intraline-info">
- <div
- class="contentText gr-diff"
- data-side="left"
- id="left-content-45"
- ></div>
- <div class="thread-group" data-side="left"></div>
- </td>
- <td class="gr-diff lineNum right" data-value="48">
- <button
- aria-label="48 unmodified"
- class="gr-diff lineNumButton right"
- data-value="48"
- id="right-button-48"
- tabindex="-1"
- >
- 48
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td class="both content gr-diff no-intraline-info right">
- <div
- class="contentText gr-diff"
- data-side="right"
- id="right-content-48"
- ></div>
- <div class="thread-group" data-side="right"></div>
- </td>
- </tr>
- </tbody>
- </table>
- </div>
- `,
- {
- ignoreTags: [
- 'gr-context-controls-section',
- 'gr-diff-section',
- 'gr-diff-row',
- 'gr-diff-text',
- 'gr-legacy-text',
- 'slot',
- ],
- }
- );
- });
- });
-
- suite('selectionchange event handling', () => {
- let handleSelectionChangeStub: sinon.SinonSpy;
-
- const emulateSelection = function () {
- document.dispatchEvent(new CustomEvent('selectionchange'));
- };
-
- setup(async () => {
- handleSelectionChangeStub = sinon.spy(
- element.highlights,
- 'handleSelectionChange'
- );
- });
-
- test('enabled if logged in', async () => {
- element.loggedIn = true;
- await element.updateComplete;
- emulateSelection();
- assert.isTrue(handleSelectionChangeStub.called);
- });
-
- test('ignored if logged out', async () => {
- element.loggedIn = false;
- await element.updateComplete;
- emulateSelection();
- assert.isFalse(handleSelectionChangeStub.called);
- });
- });
-
- test('cancel', () => {
- const cleanupStub = sinon.stub(element.diffBuilder, 'cleanup');
- element.cancel();
- assert.isTrue(cleanupStub.calledOnce);
- });
-
- test('line limit with line_wrapping', async () => {
- element.prefs = {...MINIMAL_PREFS, line_wrapping: true};
- await element.updateComplete;
- assert.equal(getComputedStyleValue('--line-limit-marker', element), '80ch');
- });
-
- test('line limit without line_wrapping', async () => {
- element.prefs = {...MINIMAL_PREFS, line_wrapping: false};
- await element.updateComplete;
- assert.equal(getComputedStyleValue('--line-limit-marker', element), '-1px');
- });
-
- suite('FULL_RESPONSIVE mode', () => {
- setup(async () => {
- element.prefs = {...MINIMAL_PREFS};
- element.renderPrefs = {responsive_mode: 'FULL_RESPONSIVE'};
- await element.updateComplete;
- });
-
- test('line limit is based on line_length', async () => {
- element.prefs = {...element.prefs!, line_length: 100};
- await element.updateComplete;
- assert.equal(
- getComputedStyleValue('--line-limit-marker', element),
- '100ch'
- );
- });
-
- test('content-width should not be defined', () => {
- assert.equal(getComputedStyleValue('--content-width', element), 'none');
- });
- });
-
- suite('SHRINK_ONLY mode', () => {
- setup(async () => {
- element.prefs = {...MINIMAL_PREFS};
- element.renderPrefs = {responsive_mode: 'SHRINK_ONLY'};
- await element.updateComplete;
- });
-
- test('content-width should not be defined', () => {
- assert.equal(getComputedStyleValue('--content-width', element), 'none');
- });
-
- test('max-width considers two content columns in side-by-side', async () => {
- element.viewMode = DiffViewMode.SIDE_BY_SIDE;
- await element.updateComplete;
- assert.equal(
- getComputedStyleValue('--diff-max-width', element),
- 'calc(2 * 80ch + 2 * 48px + 0ch + 1px + 2px)'
- );
- });
-
- test('max-width considers one content column in unified', async () => {
- element.viewMode = DiffViewMode.UNIFIED;
- await element.updateComplete;
- assert.equal(
- getComputedStyleValue('--diff-max-width', element),
- 'calc(1 * 80ch + 2 * 48px + 0ch + 1px + 2px)'
- );
- });
-
- test('max-width considers font-size', async () => {
- element.prefs = {...element.prefs!, font_size: 13};
- await element.updateComplete;
- // Each line number column: 4 * 13 = 52px
- assert.equal(
- getComputedStyleValue('--diff-max-width', element),
- 'calc(2 * 80ch + 2 * 52px + 0ch + 1px + 2px)'
- );
- });
-
- test('sign cols are considered if show_sign_col is true', async () => {
- element.renderPrefs = {...element.renderPrefs, show_sign_col: true};
- await element.updateComplete;
- assert.equal(
- getComputedStyleValue('--diff-max-width', element),
- 'calc(2 * 80ch + 2 * 48px + 2ch + 1px + 2px)'
- );
- });
- });
-
- suite('not logged in', () => {
- setup(async () => {
- element.loggedIn = false;
- await element.updateComplete;
- });
-
- test('toggleLeftDiff', () => {
- element.toggleLeftDiff();
- assert.isTrue(element.classList.contains('no-left'));
- element.toggleLeftDiff();
- assert.isFalse(element.classList.contains('no-left'));
- });
-
- suite('binary diffs', () => {
- test('render binary diff', async () => {
- element.prefs = {
- ...MINIMAL_PREFS,
- };
- element.diff = {
- meta_a: {name: 'carrot.exe', content_type: 'binary', lines: 0},
- meta_b: {name: 'carrot.exe', content_type: 'binary', lines: 0},
- change_type: 'MODIFIED',
- intraline_status: 'OK',
- diff_header: [],
- content: [],
- binary: true,
- };
- await waitForEventOnce(element, 'render');
-
- assert.shadowDom.equal(
- element,
- /* HTML */ `
- <div class="diffContainer oldDiff sideBySide">
- <gr-diff-section class="left-FILE right-FILE"> </gr-diff-section>
- <gr-diff-row class="left-FILE right-FILE"> </gr-diff-row>
- <table class="selected-right" id="diffTable">
- <colgroup>
- <col class="blame gr-diff" />
- <col class="gr-diff left" width="48" />
- <col class="gr-diff left sign" />
- <col class="gr-diff left" />
- <col class="gr-diff right" width="48" />
- <col class="gr-diff right sign" />
- <col class="gr-diff right" />
- </colgroup>
- <tbody class="binary-diff gr-diff"></tbody>
- <tbody class="both gr-diff section">
- <tr
- aria-labelledby="left-button-FILE left-content-FILE right-button-FILE right-content-FILE"
- class="diff-row gr-diff side-by-side"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff" data-line-number="FILE"></td>
- <td class="gr-diff left lineNum" data-value="FILE">
- <button
- aria-label="Add file comment"
- class="gr-diff left lineNumButton"
- data-value="FILE"
- id="left-button-FILE"
- tabindex="-1"
- >
- File
- </button>
- </td>
- <td class="gr-diff left no-intraline-info sign"></td>
- <td
- class="both content file gr-diff left no-intraline-info"
- >
- <div class="thread-group" data-side="left">
- <slot name="left-FILE"> </slot>
- </div>
- </td>
- <td class="gr-diff lineNum right" data-value="FILE">
- <button
- aria-label="Add file comment"
- class="gr-diff lineNumButton right"
- data-value="FILE"
- id="right-button-FILE"
- tabindex="-1"
- >
- File
- </button>
- </td>
- <td class="gr-diff no-intraline-info right sign"></td>
- <td
- class="both content file gr-diff no-intraline-info right"
- >
- <div class="thread-group" data-side="right">
- <slot name="right-FILE"> </slot>
- </div>
- </td>
- </tr>
- </tbody>
- <tbody class="binary-diff gr-diff">
- <tr class="gr-diff">
- <td class="gr-diff" colspan="5">
- <span> Difference in binary files </span>
- </td>
- </tr>
- </tbody>
- </table>
- </div>
- `
- );
- });
- });
-
- suite('image diffs', () => {
- let mockFile1: ImageInfo;
- let mockFile2: ImageInfo;
- setup(() => {
- mockFile1 = {
- body:
- 'Qk06AAAAAAAAADYAAAAoAAAAAQAAAP////8BACAAAAAAAAAAAAATCwAAE' +
- 'wsAAAAAAAAAAAAAAAAA/w==',
- type: 'image/bmp',
- };
- mockFile2 = {
- body:
- 'Qk06AAAAAAAAADYAAAAoAAAAAQAAAP////8BACAAAAAAAAAAAAATCwAAE' +
- 'wsAAAAAAAAAAAAA/////w==',
- type: 'image/bmp',
- };
-
- element.prefs = {
- context: 10,
- cursor_blink_rate: 0,
- font_size: 12,
- ignore_whitespace: 'IGNORE_NONE',
- line_length: 100,
- line_wrapping: false,
- show_line_endings: true,
- show_tabs: true,
- show_whitespace_errors: true,
- syntax_highlighting: true,
- tab_size: 8,
- };
- });
-
- test('render image diff', async () => {
- 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,
- };
-
- await waitForEventOnce(element, 'render');
- const imageDiffSection = queryAndAssert(element, 'tbody.image-diff');
- assert.lightDom.equal(
- imageDiffSection,
- /* HTML */ `
- <tbody class="gr-diff image-diff">
- <tr class="gr-diff">
- <td class="blank gr-diff left lineNum"></td>
- <td class="gr-diff left">
- <img
- class="gr-diff left"
- src="data:image/bmp;base64,${mockFile1.body}"
- />
- </td>
- <td class="blank gr-diff lineNum right"></td>
- <td class="gr-diff right">
- <img
- class="gr-diff right"
- src="data:image/bmp;base64,${mockFile2.body}"
- />
- </td>
- </tr>
- <tr class="gr-diff">
- <td class="blank gr-diff left lineNum"></td>
- <td class="gr-diff left">
- <label class="gr-diff">
- <span class="gr-diff label"> image/bmp </span>
- </label>
- </td>
- <td class="blank gr-diff lineNum right"></td>
- <td class="gr-diff right">
- <label class="gr-diff">
- <span class="gr-diff label"> image/bmp </span>
- </label>
- </td>
- </tr>
- </tbody>
- `
- );
- const endpoint = queryAndAssert(element, 'tbody.endpoint');
- assert.dom.equal(
- endpoint,
- /* HTML */ `
- <tbody class="gr-diff endpoint">
- <tr class="gr-diff">
- <gr-endpoint-decorator class="gr-diff" name="image-diff">
- <gr-endpoint-param class="gr-diff" name="baseImage">
- </gr-endpoint-param>
- <gr-endpoint-param class="gr-diff" name="revisionImage">
- </gr-endpoint-param>
- </gr-endpoint-decorator>
- </tr>
- </tbody>
- `
- );
- });
-
- test('renders image diffs with a different file name', async () => {
- const mockDiff: DiffInfo = {
- 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,
- };
-
- element.baseImage = mockFile1;
- element.baseImage._name = mockDiff.meta_a!.name;
- element.revisionImage = mockFile2;
- element.revisionImage._name = mockDiff.meta_b!.name;
- element.diff = mockDiff;
-
- await waitForEventOnce(element, 'render');
- const imageDiffSection = queryAndAssert(element, 'tbody.image-diff');
- const leftLabel = queryAndAssert(imageDiffSection, 'td.left label');
- const rightLabel = queryAndAssert(imageDiffSection, 'td.right label');
- assert.dom.equal(
- leftLabel,
- /* HTML */ `
- <label class="gr-diff">
- <span class="gr-diff name"> carrot.jpg </span>
- <br class="gr-diff" />
- <span class="gr-diff label"> image/bmp </span>
- </label>
- `
- );
- assert.dom.equal(
- rightLabel,
- /* HTML */ `
- <label class="gr-diff">
- <span class="gr-diff name"> carrot2.jpg </span>
- <br class="gr-diff" />
- <span class="gr-diff label"> image/bmp </span>
- </label>
- `
- );
- });
-
- test('renders added image', async () => {
- const mockDiff: DiffInfo = {
- 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,
- };
- element.revisionImage = mockFile2;
- element.diff = mockDiff;
-
- await waitForEventOnce(element, 'render');
- const imageDiffSection = queryAndAssert(element, 'tbody.image-diff');
- const leftImage = query(imageDiffSection, 'td.left img');
- const rightImage = queryAndAssert(imageDiffSection, 'td.right img');
- assert.isNotOk(leftImage);
- assert.dom.equal(
- rightImage,
- /* HTML */ `
- <img
- class="gr-diff right"
- src="data:image/bmp;base64,${mockFile2.body}"
- />
- `
- );
- });
-
- test('renders removed image', async () => {
- const mockDiff: DiffInfo = {
- 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,
- };
- element.baseImage = mockFile1;
- element.diff = mockDiff;
-
- await waitForEventOnce(element, 'render');
- const imageDiffSection = queryAndAssert(element, 'tbody.image-diff');
- const leftImage = queryAndAssert(imageDiffSection, 'td.left img');
- const rightImage = query(imageDiffSection, 'td.right img');
- assert.isNotOk(rightImage);
- assert.dom.equal(
- leftImage,
- /* HTML */ `
- <img
- class="gr-diff left"
- src="data:image/bmp;base64,${mockFile1.body}"
- />
- `
- );
- });
-
- test('does not render disallowed image type', async () => {
- const mockDiff: DiffInfo = {
- 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';
- element.baseImage = mockFile1;
- element.diff = mockDiff;
-
- await waitForEventOnce(element, 'render');
- const imageDiffSection = queryAndAssert(element, 'tbody.image-diff');
- const leftImage = query(imageDiffSection, 'td.left img');
- assert.isNotOk(leftImage);
- });
- });
-
- test('handleTap lineNum', async () => {
- const addDraftStub = sinon.stub(element, 'addDraftAtLine');
- const el = document.createElement('div');
- el.className = 'lineNum';
- const promise = mockPromise();
- el.addEventListener('click', e => {
- element.handleTap(e);
- assert.isTrue(addDraftStub.called);
- assert.equal(addDraftStub.lastCall.args[0], el);
- promise.resolve();
- });
- el.click();
- await promise;
- });
-
- test('handleTap content', async () => {
- const content = document.createElement('div');
- const lineEl = document.createElement('div');
- lineEl.className = 'lineNum';
- const row = document.createElement('div');
- row.appendChild(lineEl);
- row.appendChild(content);
-
- const selectStub = sinon.stub(element, 'selectLine');
-
- content.className = 'content';
- const promise = mockPromise();
- content.addEventListener('click', e => {
- element.handleTap(e);
- assert.isTrue(selectStub.called);
- assert.equal(selectStub.lastCall.args[0], lineEl);
- promise.resolve();
- });
- content.click();
- await promise;
- });
-
- suite('getCursorStops', () => {
- async function setupDiff() {
- element.diff = createDiff();
- element.prefs = {
- context: 10,
- tab_size: 8,
- font_size: 12,
- line_length: 100,
- cursor_blink_rate: 0,
- line_wrapping: false,
-
- show_line_endings: true,
- show_tabs: true,
- show_whitespace_errors: true,
- syntax_highlighting: true,
- ignore_whitespace: 'IGNORE_NONE',
- };
- await element.updateComplete;
- element.renderDiffTable();
- }
-
- test('returns [] when hidden and noAutoRender', async () => {
- element.noAutoRender = true;
- await setupDiff();
- element.loading = false;
- await element.updateComplete;
- element.hidden = true;
- await element.updateComplete;
- assert.equal(element.getCursorStops().length, 0);
- });
-
- test('returns one stop per line and one for the file row', async () => {
- await setupDiff();
- element.loading = false;
- await element.updateComplete;
- const ROWS = 48;
- const FILE_ROW = 1;
- const LOST_ROW = 1;
- assert.equal(
- element.getCursorStops().length,
- ROWS + FILE_ROW + LOST_ROW
- );
- });
-
- test('returns an additional AbortStop when still loading', async () => {
- await setupDiff();
- element.loading = true;
- await element.updateComplete;
- const ROWS = 48;
- const FILE_ROW = 1;
- const LOST_ROW = 1;
- const actual = element.getCursorStops();
- assert.equal(actual.length, ROWS + FILE_ROW + LOST_ROW + 1);
- assert.isTrue(actual[actual.length - 1] instanceof AbortStop);
- });
- });
- });
-
- suite('logged in', async () => {
- let fakeLineEl: HTMLElement;
- setup(async () => {
- element.loggedIn = true;
-
- fakeLineEl = {
- getAttribute: sinon.stub().returns(42),
- classList: {
- contains: sinon.stub().returns(true),
- },
- } as unknown as HTMLElement;
- await element.updateComplete;
- });
-
- test('addDraftAtLine', () => {
- sinon.stub(element, 'selectLine');
- const createCommentStub = sinon.stub(element, 'createComment');
- element.addDraftAtLine(fakeLineEl);
- assert.isTrue(createCommentStub.calledWithExactly(fakeLineEl, 42));
- });
-
- test('adds long range comment hint', async () => {
- const range = {
- start_line: 1,
- end_line: 12,
- start_character: 0,
- end_character: 0,
- };
- const threadEl = document.createElement('div');
- threadEl.className = 'comment-thread';
- threadEl.setAttribute('diff-side', 'right');
- threadEl.setAttribute('line-num', '1');
- threadEl.setAttribute('range', JSON.stringify(range));
- threadEl.setAttribute('slot', 'right-1');
- const content = [
- {
- a: ['asdf'],
- },
- {
- ab: Array(13).fill('text'),
- },
- ];
- await setupSampleDiff({content});
-
- element.appendChild(threadEl);
-
- const hint = await waitQueryAndAssert<GrRangedCommentHint>(
- element,
- 'gr-ranged-comment-hint'
- );
- assert.deepEqual(hint.range, range);
- });
-
- test('no duplicate range hint for same thread', async () => {
- const range = {
- start_line: 1,
- end_line: 12,
- start_character: 0,
- end_character: 0,
- };
- const threadEl = document.createElement('div');
- threadEl.className = 'comment-thread';
- threadEl.setAttribute('diff-side', 'right');
- threadEl.setAttribute('line-num', '1');
- threadEl.setAttribute('range', JSON.stringify(range));
- threadEl.setAttribute('slot', 'right-1');
- const firstHint = document.createElement('gr-ranged-comment-hint');
- firstHint.range = range;
- firstHint.setAttribute('slot', 'right-1');
- const content = [
- {
- a: ['asdf'],
- },
- {
- ab: Array(13).fill('text'),
- },
- ];
- await setupSampleDiff({content});
-
- element.appendChild(firstHint);
- element.appendChild(threadEl);
-
- assert.equal(
- element.querySelectorAll('gr-ranged-comment-hint').length,
- 1
- );
- });
-
- test('removes long range comment hint when comment is discarded', async () => {
- const range = {
- start_line: 1,
- end_line: 7,
- start_character: 0,
- end_character: 0,
- };
- const threadEl = document.createElement('div');
- threadEl.className = 'comment-thread';
- threadEl.setAttribute('diff-side', 'right');
- threadEl.setAttribute('line-num', '1');
- threadEl.setAttribute('range', JSON.stringify(range));
- threadEl.setAttribute('slot', 'right-1');
- const content = [
- {
- ab: Array(8).fill('text'),
- },
- ];
- await setupSampleDiff({content});
-
- element.appendChild(threadEl);
- await waitUntil(() => element.commentRanges.length === 1);
-
- threadEl.remove();
- await waitUntil(() => element.commentRanges.length === 0);
-
- assert.isEmpty(element.querySelectorAll('gr-ranged-comment-hint'));
- });
-
- suite('change in preferences', () => {
- setup(async () => {
- element.diff = {
- meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
- meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 560},
- diff_header: [],
- intraline_status: 'OK',
- change_type: 'MODIFIED',
- content: [{skip: 66}],
- };
- await element.updateComplete;
- await element.renderDiffTableTask?.flush();
- });
-
- test('change in preferences re-renders diff', async () => {
- const stub = sinon.stub(element, 'renderDiffTable');
- element.prefs = {
- ...MINIMAL_PREFS,
- };
- await element.updateComplete;
- await element.renderDiffTableTask?.flush();
- assert.isTrue(stub.called);
- });
-
- test('adding/removing property in preferences re-renders diff', async () => {
- const stub = sinon.stub(element, 'renderDiffTable');
- const newPrefs1: DiffPreferencesInfo = {
- ...MINIMAL_PREFS,
- line_wrapping: true,
- };
- element.prefs = newPrefs1;
- await element.updateComplete;
- await element.renderDiffTableTask?.flush();
- assert.isTrue(stub.called);
- stub.reset();
-
- const newPrefs2 = {...newPrefs1};
- delete newPrefs2.line_wrapping;
- element.prefs = newPrefs2;
- await element.updateComplete;
- await element.renderDiffTableTask?.flush();
- assert.isTrue(stub.called);
- });
-
- test(
- 'change in preferences does not re-renders diff with ' +
- 'noRenderOnPrefsChange',
- async () => {
- const stub = sinon.stub(element, 'renderDiffTable');
- element.noRenderOnPrefsChange = true;
- element.prefs = {
- ...MINIMAL_PREFS,
- context: 12,
- };
- await element.updateComplete;
- await element.renderDiffTableTask?.flush();
- assert.isFalse(stub.called);
- }
- );
- });
- });
-
- suite('diff header', () => {
- setup(async () => {
- element.diff = {
- meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
- meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 560},
- diff_header: [],
- intraline_status: 'OK',
- change_type: 'MODIFIED',
- content: [{skip: 66}],
- };
- await element.updateComplete;
- });
-
- test('hidden', async () => {
- assert.equal(element.computeDiffHeaderItems().length, 0);
- element.diff?.diff_header?.push('diff --git a/test.jpg b/test.jpg');
- assert.equal(element.computeDiffHeaderItems().length, 0);
- element.diff?.diff_header?.push('index 2adc47d..f9c2f2c 100644');
- assert.equal(element.computeDiffHeaderItems().length, 0);
- element.diff?.diff_header?.push('--- a/test.jpg');
- assert.equal(element.computeDiffHeaderItems().length, 0);
- element.diff?.diff_header?.push('+++ b/test.jpg');
- assert.equal(element.computeDiffHeaderItems().length, 0);
- element.diff?.diff_header?.push('test');
- assert.equal(element.computeDiffHeaderItems().length, 1);
- element.requestUpdate('diff');
- await element.updateComplete;
-
- const header = queryAndAssert(element, '#diffHeader');
- assert.equal(header.textContent?.trim(), 'test');
- });
-
- test('binary files', () => {
- element.diff!.binary = true;
- assert.equal(element.computeDiffHeaderItems().length, 0);
- element.diff?.diff_header?.push('diff --git a/test.jpg b/test.jpg');
- assert.equal(element.computeDiffHeaderItems().length, 0);
- element.diff?.diff_header?.push('test');
- assert.equal(element.computeDiffHeaderItems().length, 1);
- element.diff?.diff_header?.push('Binary files differ');
- assert.equal(element.computeDiffHeaderItems().length, 1);
- });
- });
-
- suite('safety and bypass', () => {
- let renderStub: sinon.SinonStub;
-
- setup(async () => {
- renderStub = sinon.stub(element.diffBuilder, 'render').callsFake(() => {
- assertIsDefined(element.diffTable);
- const diffTable = element.diffTable;
- diffTable.dispatchEvent(
- new CustomEvent('render', {bubbles: true, composed: true})
- );
- return Promise.resolve();
- });
- sinon.stub(element, 'getDiffLength').returns(10000);
- element.diff = createDiff();
- element.noRenderOnPrefsChange = true;
- await element.updateComplete;
- });
-
- test('large render w/ context = 10', async () => {
- element.prefs = {...MINIMAL_PREFS, context: 10};
- element.renderDiffTable();
- await waitForEventOnce(element, 'render');
-
- assert.isTrue(renderStub.called);
- assert.isFalse(element.showWarning);
- });
-
- test('large render w/ whole file and bypass', async () => {
- element.prefs = {...MINIMAL_PREFS, context: -1};
- element.safetyBypass = 10;
- element.renderDiffTable();
- await waitForEventOnce(element, 'render');
-
- assert.isTrue(renderStub.called);
- assert.isFalse(element.showWarning);
- });
-
- test('large render w/ whole file and no bypass', async () => {
- element.prefs = {...MINIMAL_PREFS, context: -1};
- element.renderDiffTable();
- await waitForEventOnce(element, 'render');
-
- assert.isFalse(renderStub.called);
- assert.isTrue(element.showWarning);
- });
-
- test('toggles expand context using bypass', async () => {
- element.prefs = {...MINIMAL_PREFS, context: 3};
-
- element.toggleAllContext();
- element.renderDiffTable();
- await element.updateComplete;
-
- assert.equal(element.prefs.context, 3);
- assert.equal(element.safetyBypass, -1);
- assert.equal(element.diffBuilder.prefs.context, -1);
- });
-
- test('toggles collapse context from bypass', async () => {
- element.prefs = {...MINIMAL_PREFS, context: 3};
- element.safetyBypass = -1;
-
- element.toggleAllContext();
- element.renderDiffTable();
- await element.updateComplete;
-
- assert.equal(element.prefs.context, 3);
- assert.isNull(element.safetyBypass);
- assert.equal(element.diffBuilder.prefs.context, 3);
- });
-
- test('toggles collapse context from pref using default', async () => {
- element.prefs = {...MINIMAL_PREFS, context: -1};
-
- element.toggleAllContext();
- element.renderDiffTable();
- await element.updateComplete;
-
- assert.equal(element.prefs.context, -1);
- assert.equal(element.safetyBypass, 10);
- assert.equal(element.diffBuilder.prefs.context, 10);
- });
- });
-
- suite('blame', () => {
- test('unsetting', async () => {
- element.blame = [];
- const setBlameSpy = sinon.spy(element.diffBuilder, 'setBlame');
- element.classList.add('showBlame');
- element.blame = null;
- await element.updateComplete;
- assert.isTrue(setBlameSpy.calledWithExactly(null));
- assert.isFalse(element.classList.contains('showBlame'));
- });
-
- test('setting', async () => {
- element.blame = [
- {
- author: 'test-author',
- time: 12345,
- commit_msg: '',
- id: 'commit id',
- ranges: [{start: 1, end: 2}],
- },
- ];
- await element.updateComplete;
- assert.isTrue(element.classList.contains('showBlame'));
- });
- });
-
- suite('trailing newline warnings', () => {
- const NO_NEWLINE_LEFT = 'No newline at end of left file.';
- const NO_NEWLINE_RIGHT = 'No newline at end of right file.';
-
- const getWarning = (element: GrDiff) => {
- const warningElement = query(element, '.newlineWarning');
- return warningElement?.textContent ?? '';
- };
-
- setup(async () => {
- element.showNewlineWarningLeft = false;
- element.showNewlineWarningRight = false;
- await element.updateComplete;
- });
-
- test('shows combined warning if both sides set to warn', async () => {
- element.showNewlineWarningLeft = true;
- element.showNewlineWarningRight = true;
- await element.updateComplete;
- assert.include(
- getWarning(element),
- NO_NEWLINE_LEFT + ' \u2014 ' + NO_NEWLINE_RIGHT
- ); // \u2014 - '—'
- });
-
- suite('showNewlineWarningLeft', () => {
- test('show warning if true', async () => {
- element.showNewlineWarningLeft = true;
- await element.updateComplete;
- assert.include(getWarning(element), NO_NEWLINE_LEFT);
- });
-
- test('hide warning if false', async () => {
- element.showNewlineWarningLeft = false;
- await element.updateComplete;
- assert.notInclude(getWarning(element), NO_NEWLINE_LEFT);
- });
- });
-
- suite('showNewlineWarningRight', () => {
- test('show warning if true', async () => {
- element.showNewlineWarningRight = true;
- await element.updateComplete;
- assert.include(getWarning(element), NO_NEWLINE_RIGHT);
- });
-
- test('hide warning if false', async () => {
- element.showNewlineWarningRight = false;
- await element.updateComplete;
- assert.notInclude(getWarning(element), NO_NEWLINE_RIGHT);
- });
- });
- });
-
- suite('key locations', () => {
- let renderStub: sinon.SinonStub;
-
- setup(async () => {
- element.prefs = {...MINIMAL_PREFS};
- element.diff = createDiff();
- renderStub = sinon.stub(element.diffBuilder, 'render');
- await element.updateComplete;
- });
-
- test('lineOfInterest is a key location', () => {
- element.lineOfInterest = {lineNum: 789, side: Side.LEFT};
- element.renderDiffTable();
- assert.isTrue(renderStub.called);
- assert.deepEqual(renderStub.lastCall.args[0], {
- left: {789: true},
- right: {},
- });
- });
-
- test('line comments are key locations', async () => {
- const threadEl = document.createElement('div');
- threadEl.className = 'comment-thread';
- threadEl.setAttribute('diff-side', 'right');
- threadEl.setAttribute('line-num', '3');
- element.appendChild(threadEl);
- await element.updateComplete;
-
- element.renderDiffTable();
- assert.isTrue(renderStub.called);
- assert.deepEqual(renderStub.lastCall.args[0], {
- left: {},
- right: {3: true},
- });
- });
-
- test('file comments are key locations', async () => {
- const threadEl = document.createElement('div');
- threadEl.className = 'comment-thread';
- threadEl.setAttribute('diff-side', 'left');
- element.appendChild(threadEl);
- await element.updateComplete;
-
- element.renderDiffTable();
- assert.isTrue(renderStub.called);
- assert.deepEqual(renderStub.lastCall.args[0], {
- left: {FILE: true},
- right: {},
- });
- });
- });
- const setupSampleDiff = async function (params: {
- content: DiffContent[];
- ignore_whitespace?: IgnoreWhitespaceType;
- binary?: boolean;
- }) {
- const {ignore_whitespace, content} = params;
- // binary can't be undefined, use false if not set
- const binary = params.binary || false;
- element.prefs = {
- ignore_whitespace: ignore_whitespace || 'IGNORE_ALL',
- context: 10,
- cursor_blink_rate: 0,
- font_size: 12,
-
- line_length: 100,
- line_wrapping: false,
- show_line_endings: true,
- show_tabs: true,
- show_whitespace_errors: true,
- syntax_highlighting: true,
- tab_size: 8,
- };
- element.diff = {
- intraline_status: 'OK',
- change_type: 'MODIFIED',
- diff_header: [
- 'diff --git a/carrot.js b/carrot.js',
- 'index 2adc47d..f9c2f2c 100644',
- '--- a/carrot.js',
- '+++ b/carrot.jjs',
- 'file differ',
- ],
- content,
- binary,
- };
- await element.updateComplete;
- await element.renderDiffTableTask;
- };
-
- test('clear diff table content as soon as diff changes', async () => {
- const content = [
- {
- a: ['all work and no play make andybons a dull boy'],
- },
- {
- b: ['Non eram nescius, Brute, cum, quae summis ingeniis '],
- },
- ];
- function diffTableHasContent() {
- assertIsDefined(element.diffTable);
- const diffTable = element.diffTable;
- return diffTable.innerText.includes(content[0].a?.[0] ?? '');
- }
- await setupSampleDiff({content});
- await waitUntil(diffTableHasContent);
- element.diff = {...element.diff!};
- await element.updateComplete;
- // immediately cleaned up
- assertIsDefined(element.diffTable);
- const diffTable = element.diffTable;
- assert.equal(diffTable.innerHTML, '');
- element.renderDiffTable();
- await element.updateComplete;
- // rendered again
- await waitUntil(diffTableHasContent);
- });
-
- suite('selection test', () => {
- test('user-select set correctly on side-by-side view', async () => {
- const content = [
- {
- a: ['all work and no play make andybons a dull boy'],
- b: ['elgoog elgoog elgoog'],
- },
- {
- ab: [
- 'Non eram nescius, Brute, cum, quae summis ingeniis ',
- 'exquisitaque doctrina philosophi Graeco sermone tractavissent',
- ],
- },
- ];
- await setupSampleDiff({content});
- await waitEventLoop();
-
- const diffLine = queryAll<HTMLElement>(element, '.contentText')[2];
- assert.equal(getComputedStyle(diffLine).userSelect, 'none');
- mouseDown(diffLine);
- assert.equal(getComputedStyle(diffLine).userSelect, 'text');
- });
-
- test('user-select set correctly on unified view', async () => {
- const content = [
- {
- a: ['all work and no play make andybons a dull boy'],
- b: ['elgoog elgoog elgoog'],
- },
- {
- ab: [
- 'Non eram nescius, Brute, cum, quae summis ingeniis ',
- 'exquisitaque doctrina philosophi Graeco sermone tractavissent',
- ],
- },
- ];
- await setupSampleDiff({content});
- element.viewMode = DiffViewMode.UNIFIED;
- await element.updateComplete;
- const diffLine = queryAll<HTMLElement>(element, '.contentText')[2];
- assert.equal(getComputedStyle(diffLine).userSelect, 'none');
- mouseDown(diffLine);
- assert.equal(getComputedStyle(diffLine).userSelect, 'text');
- });
- });
-
- suite('whitespace changes only message', () => {
- test('show the message if ignore_whitespace is criteria matches', async () => {
- await setupSampleDiff({content: [{skip: 100}]});
- element.loading = false;
- assert.isTrue(element.showNoChangeMessage());
- });
-
- test('do not show the message for binary files', async () => {
- await setupSampleDiff({content: [{skip: 100}], binary: true});
- element.loading = false;
- assert.isFalse(element.showNoChangeMessage());
- });
-
- test('do not show the message if still loading', async () => {
- await setupSampleDiff({content: [{skip: 100}]});
- element.loading = true;
- assert.isFalse(element.showNoChangeMessage());
- });
-
- test('do not show the message if contains valid changes', async () => {
- const content = [
- {
- a: ['all work and no play make andybons a dull boy'],
- b: ['elgoog elgoog elgoog'],
- },
- {
- ab: [
- 'Non eram nescius, Brute, cum, quae summis ingeniis ',
- 'exquisitaque doctrina philosophi Graeco sermone tractavissent',
- ],
- },
- ];
- await setupSampleDiff({content});
- element.loading = false;
- assert.equal(element.diffLength, 3);
- assert.isFalse(element.showNoChangeMessage());
- });
-
- test('do not show message if ignore whitespace is disabled', async () => {
- const content = [
- {
- a: ['all work and no play make andybons a dull boy'],
- b: ['elgoog elgoog elgoog'],
- },
- {
- ab: [
- 'Non eram nescius, Brute, cum, quae summis ingeniis ',
- 'exquisitaque doctrina philosophi Graeco sermone tractavissent',
- ],
- },
- ];
- await setupSampleDiff({ignore_whitespace: 'IGNORE_NONE', content});
- element.loading = false;
- assert.isFalse(element.showNoChangeMessage());
- });
- });
-
- test('getDiffLength', () => {
- const diff = createDiff();
- assert.equal(element.getDiffLength(diff), 52);
- });
-});
diff --git a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section.ts
index e7ae8a4..394d09b 100644
--- a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section.ts
+++ b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section.ts
@@ -8,7 +8,7 @@
import {property, state} from 'lit/decorators.js';
import {DiffInfo, DiffViewMode, RenderPreferences} from '../../../api/diff';
import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {diffClasses, isNewDiff} from '../gr-diff/gr-diff-utils';
+import {diffClasses} from '../gr-diff/gr-diff-utils';
import {getShowConfig} from './gr-context-controls';
import {ifDefined} from 'lit/directives/if-defined.js';
import {when} from 'lit/directives/when.js';
@@ -140,17 +140,10 @@
}
}
-// TODO(newdiff-cleanup): Remove once newdiff migration is completed.
-if (isNewDiff()) {
- customElements.define(
- 'gr-context-controls-section',
- GrContextControlsSection
- );
-}
+customElements.define('gr-context-controls-section', GrContextControlsSection);
declare global {
interface HTMLElementTagNameMap {
- // TODO(newdiff-cleanup): Replace once newdiff migration is completed.
- 'gr-context-controls-section': LitElement;
+ 'gr-context-controls-section': GrContextControlsSection;
}
}
diff --git a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
index b2c0fcb..e889b90 100644
--- a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
+++ b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
@@ -32,7 +32,6 @@
} from '../../../api/diff';
import {GrDiffGroup, hideInContextControl} from '../gr-diff/gr-diff-group';
-import {isNewDiff} from '../gr-diff/gr-diff-utils';
declare global {
interface HTMLElementEventMap {
@@ -527,14 +526,10 @@
}
}
-// TODO(newdiff-cleanup): Remove once newdiff migration is completed.
-if (isNewDiff()) {
- customElements.define('gr-context-controls', GrContextControls);
-}
+customElements.define('gr-context-controls', GrContextControls);
declare global {
interface HTMLElementTagNameMap {
- // TODO(newdiff-cleanup): Replace once newdiff migration is completed.
- 'gr-context-controls': LitElement;
+ 'gr-context-controls': GrContextControls;
}
}
diff --git a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls_test.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls_test.ts
index 7f5827c..215dc88 100644
--- a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls_test.ts
@@ -23,10 +23,7 @@
let element: GrContextControls;
setup(async () => {
- // TODO(newdiff-cleanup): Remove cast when newdiff migration is complete.
- element = document.createElement(
- 'gr-context-controls'
- ) as GrContextControls;
+ element = document.createElement('gr-context-controls');
element.diff = {content: []} as any as DiffInfo;
element.renderPreferences = {};
const div = await fixture(html`<div></div>`);
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-image.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-image.ts
index fd3b975..9eb1e14 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-image.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-image.ts
@@ -8,7 +8,6 @@
import '../gr-diff-image-viewer/gr-image-viewer';
import {html, LitElement, nothing} from 'lit';
import {property, query, state} from 'lit/decorators.js';
-import {isNewDiff} from '../gr-diff/gr-diff-utils';
// MIME types for images we allow showing. Do not include SVG, it can contain
// arbitrary JavaScript.
@@ -201,16 +200,12 @@
: '';
}
-// TODO(newdiff-cleanup): Remove once newdiff migration is completed.
-if (isNewDiff()) {
- customElements.define('gr-diff-image-new', GrDiffImageNew);
- customElements.define('gr-diff-image-old', GrDiffImageOld);
-}
+customElements.define('gr-diff-image-new', GrDiffImageNew);
+customElements.define('gr-diff-image-old', GrDiffImageOld);
declare global {
interface HTMLElementTagNameMap {
- // TODO(newdiff-cleanup): Replace once newdiff migration is completed.
- 'gr-diff-image-new': LitElement;
- 'gr-diff-image-old': LitElement;
+ 'gr-diff-image-new': GrDiffImageNew;
+ 'gr-diff-image-old': GrDiffImageOld;
}
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row.ts
index da84b34..8f71936 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row.ts
@@ -26,7 +26,6 @@
diffClasses,
GrDiffCommentThread,
isLongCommentRange,
- isNewDiff,
isResponsive,
} from '../gr-diff/gr-diff-utils';
import {resolve} from '../../../models/dependency';
@@ -579,14 +578,10 @@
}
}
-// TODO(newdiff-cleanup): Remove once newdiff migration is completed.
-if (isNewDiff()) {
- customElements.define('gr-diff-row', GrDiffRow);
-}
+customElements.define('gr-diff-row', GrDiffRow);
declare global {
interface HTMLElementTagNameMap {
- // TODO(newdiff-cleanup): Replace once newdiff migration is completed.
- 'gr-diff-row': LitElement;
+ 'gr-diff-row': GrDiffRow;
}
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
index 325f902..4dd580f 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
@@ -15,11 +15,7 @@
DiffPreferencesInfo,
} from '../../../api/diff';
import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {
- isNewDiff,
- diffClasses,
- getResponsiveMode,
-} from '../gr-diff/gr-diff-utils';
+import {diffClasses, getResponsiveMode} from '../gr-diff/gr-diff-utils';
import {GrDiffRow} from './gr-diff-row';
import '../gr-context-controls/gr-context-controls-section';
import '../gr-context-controls/gr-context-controls';
@@ -297,14 +293,10 @@
}
}
-// TODO(newdiff-cleanup): Remove once newdiff migration is completed.
-if (isNewDiff()) {
- customElements.define('gr-diff-section', GrDiffSection);
-}
+customElements.define('gr-diff-section', GrDiffSection);
declare global {
interface HTMLElementTagNameMap {
- // TODO(newdiff-cleanup): Replace once newdiff migration is completed.
- 'gr-diff-section': LitElement;
+ 'gr-diff-section': GrDiffSection;
}
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts
index 2acedc8..5161b18 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts
@@ -6,7 +6,7 @@
import {LitElement, html, TemplateResult} from 'lit';
import {property} from 'lit/decorators.js';
import {styleMap} from 'lit/directives/style-map.js';
-import {isNewDiff, diffClasses} from '../gr-diff/gr-diff-utils';
+import {diffClasses} from '../gr-diff/gr-diff-utils';
const SURROGATE_PAIR = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
@@ -144,14 +144,10 @@
}
}
-// TODO(newdiff-cleanup): Remove once newdiff migration is completed.
-if (isNewDiff()) {
- customElements.define('gr-diff-text', GrDiffText);
-}
+customElements.define('gr-diff-text', GrDiffText);
declare global {
interface HTMLElementTagNameMap {
- // TODO(newdiff-cleanup): Replace once newdiff migration is completed.
- 'gr-diff-text': LitElement;
+ 'gr-diff-text': GrDiffText;
}
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
index 4f05cca..e0febdc 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
@@ -457,7 +457,14 @@
<paper-button class=${classMap(leftClasses)} @click=${this.selectBase}>
Base
</paper-button>
- <paper-fab mini icon="gr-icons:swapHoriz" @click=${this.manualBlink}>
+ <paper-fab
+ mini
+ icon="gr-icons:swapHoriz"
+ title=${this.baseSelected
+ ? 'switch to Revision version'
+ : 'switch to Base version'}
+ @click=${this.manualBlink}
+ >
</paper-fab>
<paper-button
class=${classMap(rightClasses)}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts b/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts
index ffc2c5e..7778228 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts
@@ -19,7 +19,7 @@
Side,
} from '../../../api/diff';
import {define} from '../../../models/dependency';
-import {Model} from '../../../models/model';
+import {Model} from '../../../models/base/model';
import {select} from '../../../utils/observable-util';
import {
FullContext,
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-element.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-element.ts
index 352d5db..3ea3ada 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-element.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-element.ts
@@ -16,7 +16,6 @@
import '../gr-diff-builder/gr-diff-row';
import {
isResponsive,
- isNewDiff,
FullContext,
diffClasses,
FULL_CONTEXT,
@@ -186,7 +185,6 @@
private renderContainer() {
const cssClasses = {
- newDiff: true,
diffContainer: true,
unified: this.viewMode === DiffViewMode.UNIFIED,
sideBySide: this.viewMode === DiffViewMode.SIDE_BY_SIDE,
@@ -411,10 +409,7 @@
return prefs.font_size * 4;
}
-// TODO(newdiff-cleanup): Remove once newdiff migration is completed.
-if (isNewDiff()) {
- customElements.define('gr-diff-element', GrDiffElement);
-}
+customElements.define('gr-diff-element', GrDiffElement);
declare global {
interface HTMLElementTagNameMap {
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-element_test.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-element_test.ts
index 8cc5324..c6194c5 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-element_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-element_test.ts
@@ -57,7 +57,7 @@
assert.lightDom.equal(
element,
/* HTML */ `
- <div class="diffContainer newDiff sideBySide">
+ <div class="diffContainer sideBySide">
<table id="diffTable">
<colgroup>
<col class="blame gr-diff" />
@@ -85,7 +85,7 @@
assert.lightDom.equal(
element,
/* HTML */ `
- <div class="diffContainer newDiff unified">
+ <div class="diffContainer unified">
<table id="diffTable">
<colgroup>
<col class="blame gr-diff" />
@@ -1319,7 +1319,7 @@
assert.lightDom.equal(
element,
/* HTML */ `
- <div class="diffContainer newDiff sideBySide">
+ <div class="diffContainer sideBySide">
<table id="diffTable">
<colgroup>
<col class="blame gr-diff" />
@@ -2935,7 +2935,7 @@
assert.lightDom.equal(
element,
/* HTML */ `
- <div class="diffContainer newDiff sideBySide">
+ <div class="diffContainer sideBySide">
<gr-diff-section class="left-FILE right-FILE"> </gr-diff-section>
<gr-diff-row class="left-FILE right-FILE"> </gr-diff-row>
<table id="diffTable">
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-styles.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-styles.ts
index e5a5e65..f2f85af 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-styles.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-styles.ts
@@ -462,10 +462,7 @@
color: var(--link-color);
padding: var(--spacing-m) 0 var(--spacing-m) 48px;
}
- /* for new diff */
- gr-diff-element,
- /* for old diff, TODO: remove */
- #diffTable {
+ gr-diff-element {
/* for gr-selection-action-box positioning */
position: relative;
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
index 7e30581..0a4d5b9 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
@@ -40,12 +40,6 @@
*/
export const REGEX_TAB_OR_SURROGATE_PAIR = /\t|[\uD800-\uDBFF][\uDC00-\uDFFF]/;
-// TODO(newdiff-cleanup): Remove once newdiff migration is completed.
-export function isNewDiff() {
- const flags = new Set(window.ENABLED_EXPERIMENTS ?? []);
- return flags.has('UiFeature__new_diff');
-}
-
export function getResponsiveMode(
prefs?: DiffPreferencesInfo,
renderPrefs?: RenderPreferences
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
index 3696f99..5738cb7 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
@@ -20,7 +20,6 @@
isThreadEl,
getResponsiveMode,
isResponsive,
- isNewDiff,
getSideByLineEl,
compareComments,
getDataFromCommentThreadEl,
@@ -47,6 +46,7 @@
DisplayLine,
LineNumber,
ContentLoadNeededEventDetail,
+ DiffContextExpandedExternalDetail,
} from '../../../api/diff';
import {isSafari, toggleClass} from '../../../utils/dom-util';
import {assertIsDefined} from '../../../utils/common-util';
@@ -431,7 +431,6 @@
}
override render() {
- fire(this, 'render-start', {});
return html`<gr-diff-element></gr-diff-element>`;
}
@@ -491,9 +490,6 @@
this.commentThreadRedispatcher(e.target, 'comment-thread-mouseleave');
};
- /** TODO: Can be removed when diff-old is gone. */
- cancel() {}
-
getCursorStops(): Array<HTMLElement | AbortStop> {
if (this.hidden && this.noAutoRender) return [];
@@ -698,9 +694,6 @@
this.rangeLayer.updateRanges(ranges);
}
- /** TODO: Can be removed when diff-old is gone. */
- clearDiffContent() {}
-
// TODO: Migrate callers to just update prefs.context.
toggleAllContext() {
const current = this.diffModel.getState().showFullContext;
@@ -1018,15 +1011,11 @@
}
}
-// TODO(newdiff-cleanup): Remove once newdiff migration is completed.
-if (isNewDiff()) {
- customElements.define('gr-diff', GrDiff);
-}
+customElements.define('gr-diff', GrDiff);
declare global {
interface HTMLElementTagNameMap {
- // TODO(newdiff-cleanup): Replace once newdiff migration is completed.
- 'gr-diff': LitElement;
+ 'gr-diff': GrDiff;
}
interface HTMLElementEventMap {
'comment-thread-mouseenter': CustomEvent<GrDiffCommentThread>;
@@ -1043,6 +1032,7 @@
* renders and for partial rerenders.
*/
'render-content': CustomEvent<{}>;
+ 'diff-context-expanded': CustomEvent<DiffContextExpandedExternalDetail>;
'diff-context-expanded-internal-new': CustomEvent<DiffContextExpandedEventDetail>;
'content-load-needed': CustomEvent<ContentLoadNeededEventDetail>;
}
diff --git a/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts b/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts
index b95a450..cd10f4d 100644
--- a/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts
+++ b/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts
@@ -48,7 +48,7 @@
cursor: pointer;
font-family: var(--font-family);
position: absolute;
- width: 100%;
+ width: 20ch;
}
gr-tooltip[invisible] {
visibility: hidden;
diff --git a/polygerrit-ui/app/embed/gr-diff-app-context-init.ts b/polygerrit-ui/app/embed/gr-diff-app-context-init.ts
index 36ebb9f..bad23cf 100644
--- a/polygerrit-ui/app/embed/gr-diff-app-context-init.ts
+++ b/polygerrit-ui/app/embed/gr-diff-app-context-init.ts
@@ -3,11 +3,12 @@
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {create, Registry, Finalizable} from '../services/registry';
+import {create, Registry} from '../services/registry';
import {AppContext} from '../services/app-context';
import {AuthService} from '../services/gr-auth/gr-auth';
import {FlagsService} from '../services/flags/flags';
import {grReportingMock} from '../services/gr-reporting/gr-reporting_mock';
+import {Finalizable} from '../types/types';
class MockFlagsService implements FlagsService {
isEnabled() {
diff --git a/polygerrit-ui/app/embed/gr-diff.ts b/polygerrit-ui/app/embed/gr-diff.ts
index 02a153d..6de43ed 100644
--- a/polygerrit-ui/app/embed/gr-diff.ts
+++ b/polygerrit-ui/app/embed/gr-diff.ts
@@ -12,25 +12,20 @@
// exposed by shared gr-diff component.
import '../api/embed';
import '../scripts/bundled-polymer';
-import './diff-old/gr-diff/gr-diff';
-import './diff-old/gr-diff-cursor/gr-diff-cursor';
import './diff/gr-diff/gr-diff';
import './diff/gr-diff-cursor/gr-diff-cursor';
import {TokenHighlightLayer} from './diff/gr-diff-builder/token-highlight-layer';
-import {GrDiffCursor as GrDiffCursorOld} from './diff-old/gr-diff-cursor/gr-diff-cursor';
-import {GrDiffCursor as GrDiffCursorNew} from './diff/gr-diff-cursor/gr-diff-cursor';
-import {GrAnnotation as GrAnnotationOld} from './diff-old/gr-diff-highlight/gr-annotation';
-import {GrAnnotationImpl as GrAnnotationNew} from './diff/gr-diff-highlight/gr-annotation';
+import {GrDiffCursor} from './diff/gr-diff-cursor/gr-diff-cursor';
+import {GrAnnotationImpl as GrAnnotation} from './diff/gr-diff-highlight/gr-annotation';
import {createDiffAppContext} from './gr-diff-app-context-init';
import {injectAppContext} from '../services/app-context';
-import {isNewDiff} from './diff/gr-diff/gr-diff-utils';
// Setup appContext for diff.
// TODO (dmfilippov): find a better solution
injectAppContext(createDiffAppContext());
// Setup global variables for existing usages of this component
window.grdiff = {
- GrAnnotation: isNewDiff() ? GrAnnotationNew : GrAnnotationOld,
- GrDiffCursor: isNewDiff() ? GrDiffCursorNew : GrDiffCursorOld,
+ GrAnnotation,
+ GrDiffCursor,
TokenHighlightLayer,
};
diff --git a/polygerrit-ui/app/models/accounts-model/accounts-model.ts b/polygerrit-ui/app/models/accounts-model/accounts-model.ts
index 1c67857..0802f06 100644
--- a/polygerrit-ui/app/models/accounts-model/accounts-model.ts
+++ b/polygerrit-ui/app/models/accounts-model/accounts-model.ts
@@ -10,7 +10,7 @@
import {getUserId, isDetailedAccount} from '../../utils/account-util';
import {hasOwnProperty} from '../../utils/common-util';
import {define} from '../dependency';
-import {Model} from '../model';
+import {Model} from '../base/model';
export interface AccountsState {
accounts: {
diff --git a/polygerrit-ui/app/models/model.ts b/polygerrit-ui/app/models/base/model.ts
similarity index 95%
rename from polygerrit-ui/app/models/model.ts
rename to polygerrit-ui/app/models/base/model.ts
index 19b52fc..4574b31f 100644
--- a/polygerrit-ui/app/models/model.ts
+++ b/polygerrit-ui/app/models/base/model.ts
@@ -4,8 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {BehaviorSubject, Observable, Subscription} from 'rxjs';
-import {Finalizable} from '../services/registry';
-import {deepEqual} from '../utils/deep-util';
+import {deepEqual} from '../../utils/deep-util';
+import {Finalizable} from '../../types/types';
/**
* A Model stores a value <T> and controls changes to that value via `subject$`
diff --git a/polygerrit-ui/app/models/model_test.ts b/polygerrit-ui/app/models/base/model_test.ts
similarity index 97%
rename from polygerrit-ui/app/models/model_test.ts
rename to polygerrit-ui/app/models/base/model_test.ts
index 3fa88e7..073c2fc 100644
--- a/polygerrit-ui/app/models/model_test.ts
+++ b/polygerrit-ui/app/models/base/model_test.ts
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {assert, waitUntil} from '@open-wc/testing';
-import '../test/common-test-setup';
+import '../../test/common-test-setup';
import {Model} from './model';
interface TestModelState {
diff --git a/polygerrit-ui/app/models/browser/browser-model.ts b/polygerrit-ui/app/models/browser/browser-model.ts
index 50b6325..86c8d62 100644
--- a/polygerrit-ui/app/models/browser/browser-model.ts
+++ b/polygerrit-ui/app/models/browser/browser-model.ts
@@ -7,7 +7,7 @@
import {define} from '../dependency';
import {DiffViewMode} from '../../api/diff';
import {UserModel} from '../user/user-model';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {select} from '../../utils/observable-util';
// This value is somewhat arbitrary and not based on research or calculations.
diff --git a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
index e82c262..26003e1 100644
--- a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
+++ b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
@@ -13,7 +13,7 @@
GroupInfo,
Hashtag,
} from '../../api/rest-api';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {RestApiService} from '../../services/gr-rest-api/gr-rest-api';
import {define} from '../dependency';
import {select} from '../../utils/observable-util';
diff --git a/polygerrit-ui/app/models/change/change-model.ts b/polygerrit-ui/app/models/change/change-model.ts
index e6f8512..2414107 100644
--- a/polygerrit-ui/app/models/change/change-model.ts
+++ b/polygerrit-ui/app/models/change/change-model.ts
@@ -27,12 +27,12 @@
findEdit,
sortRevisions,
} from '../../utils/patch-set-util';
-import {isDefined, ParsedChangeInfo} from '../../types/types';
+import {isDefined, LoadingStatus, ParsedChangeInfo} from '../../types/types';
import {fireAlert, fireTitleChange} from '../../utils/event-util';
import {RestApiService} from '../../services/gr-rest-api/gr-rest-api';
import {select} from '../../utils/observable-util';
import {assertIsDefined} from '../../utils/common-util';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {UserModel} from '../user/user-model';
import {define} from '../dependency';
import {isOwner} from '../../utils/change-util';
@@ -50,13 +50,6 @@
import {ReportingService} from '../../services/gr-reporting/gr-reporting';
import {Timing} from '../../constants/reporting';
-export enum LoadingStatus {
- NOT_LOADED = 'NOT_LOADED',
- LOADING = 'LOADING',
- RELOADING = 'RELOADING',
- LOADED = 'LOADED',
-}
-
const ERR_REVIEW_STATUS = 'Couldn’t change file review status.';
export interface ChangeState {
@@ -255,6 +248,11 @@
change => change?.revisions[change.current_revision]?.uploader
);
+ public readonly latestCommitter$ = select(
+ this.change$,
+ change => change?.revisions[change.current_revision]?.commit?.committer
+ );
+
/**
* Emits the current patchset number. If the route does not define the current
* patchset num, then this selector waits for the change to be defined and
diff --git a/polygerrit-ui/app/models/change/change-model_test.ts b/polygerrit-ui/app/models/change/change-model_test.ts
index 00a03fe..f074ac3 100644
--- a/polygerrit-ui/app/models/change/change-model_test.ts
+++ b/polygerrit-ui/app/models/change/change-model_test.ts
@@ -30,11 +30,14 @@
PatchSetNum,
PatchSetNumber,
} from '../../types/common';
-import {EditRevisionInfo, ParsedChangeInfo} from '../../types/types';
+import {
+ EditRevisionInfo,
+ LoadingStatus,
+ ParsedChangeInfo,
+} from '../../types/types';
import {getAppContext} from '../../services/app-context';
import {
ChangeState,
- LoadingStatus,
updateChangeWithEdit,
updateRevisionsWithCommitShas,
} from './change-model';
diff --git a/polygerrit-ui/app/models/change/files-model.ts b/polygerrit-ui/app/models/change/files-model.ts
index c01b718..192520d 100644
--- a/polygerrit-ui/app/models/change/files-model.ts
+++ b/polygerrit-ui/app/models/change/files-model.ts
@@ -18,7 +18,7 @@
import {select} from '../../utils/observable-util';
import {FileInfoStatus, SpecialFilePath} from '../../constants/constants';
import {specialFilePathCompare} from '../../utils/path-list-util';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {define} from '../dependency';
import {ChangeModel} from './change-model';
import {CommentsModel} from '../comments/comments-model';
diff --git a/polygerrit-ui/app/models/change/related-changes-model.ts b/polygerrit-ui/app/models/change/related-changes-model.ts
index 6972ec8..9c0d60f 100644
--- a/polygerrit-ui/app/models/change/related-changes-model.ts
+++ b/polygerrit-ui/app/models/change/related-changes-model.ts
@@ -10,7 +10,7 @@
} from '../../types/common';
import {RestApiService} from '../../services/gr-rest-api/gr-rest-api';
import {select} from '../../utils/observable-util';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {define} from '../dependency';
import {ChangeModel} from './change-model';
import {combineLatest, forkJoin, from, of} from 'rxjs';
diff --git a/polygerrit-ui/app/models/checks/checks-model.ts b/polygerrit-ui/app/models/checks/checks-model.ts
index 6718df9..2979912 100644
--- a/polygerrit-ui/app/models/checks/checks-model.ts
+++ b/polygerrit-ui/app/models/checks/checks-model.ts
@@ -53,7 +53,7 @@
import {ReportingService} from '../../services/gr-reporting/gr-reporting';
import {Execution, Interaction, Timing} from '../../constants/reporting';
import {fireAlert, fire} from '../../utils/event-util';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {define} from '../dependency';
import {
ChecksPlugin,
diff --git a/polygerrit-ui/app/models/comments/comments-model.ts b/polygerrit-ui/app/models/comments/comments-model.ts
index 7bf2785..e61b88b 100644
--- a/polygerrit-ui/app/models/comments/comments-model.ts
+++ b/polygerrit-ui/app/models/comments/comments-model.ts
@@ -43,7 +43,7 @@
import {assert, assertIsDefined} from '../../utils/common-util';
import {debounce, DelayedTask} from '../../utils/async-util';
import {ReportingService} from '../../services/gr-reporting/gr-reporting';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {Deduping} from '../../api/reporting';
import {extractMentionedUsers, getUserId} from '../../utils/account-util';
import {SpecialFilePath} from '../../constants/constants';
diff --git a/polygerrit-ui/app/models/config/config-model.ts b/polygerrit-ui/app/models/config/config-model.ts
index cc10025..66ee2e8 100644
--- a/polygerrit-ui/app/models/config/config-model.ts
+++ b/polygerrit-ui/app/models/config/config-model.ts
@@ -9,7 +9,7 @@
import {RestApiService} from '../../services/gr-rest-api/gr-rest-api';
import {ChangeModel} from '../change/change-model';
import {select} from '../../utils/observable-util';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {define} from '../dependency';
import {loginUrl} from '../../utils/url-util';
diff --git a/polygerrit-ui/app/models/plugins/plugins-model.ts b/polygerrit-ui/app/models/plugins/plugins-model.ts
index 1d38c17..33ec35a 100644
--- a/polygerrit-ui/app/models/plugins/plugins-model.ts
+++ b/polygerrit-ui/app/models/plugins/plugins-model.ts
@@ -10,7 +10,7 @@
ChecksApiConfig,
ChecksProvider,
} from '../../api/checks';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {select} from '../../utils/observable-util';
import {CoverageProvider, TokenHoverListener} from '../../api/annotation';
import {SuggestionsProvider} from '../../api/suggestions';
diff --git a/polygerrit-ui/app/models/user/user-model.ts b/polygerrit-ui/app/models/user/user-model.ts
index 8c1bb96..55d387b 100644
--- a/polygerrit-ui/app/models/user/user-model.ts
+++ b/polygerrit-ui/app/models/user/user-model.ts
@@ -27,7 +27,7 @@
import {DiffPreferencesInfo} from '../../types/diff';
import {select} from '../../utils/observable-util';
import {define} from '../dependency';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {isDefined} from '../../types/types';
export function changeTablePrefs(prefs: Partial<PreferencesInfo>) {
diff --git a/polygerrit-ui/app/models/views/admin.ts b/polygerrit-ui/app/models/views/admin.ts
index 017470e..02b5796 100644
--- a/polygerrit-ui/app/models/views/admin.ts
+++ b/polygerrit-ui/app/models/views/admin.ts
@@ -6,7 +6,7 @@
import {GerritView} from '../../services/router/router-model';
import {getBaseUrl} from '../../utils/url-util';
import {define} from '../dependency';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {Route, ViewState} from './base';
import {
RepoName,
diff --git a/polygerrit-ui/app/models/views/agreement.ts b/polygerrit-ui/app/models/views/agreement.ts
index 839699c..ad587eb 100644
--- a/polygerrit-ui/app/models/views/agreement.ts
+++ b/polygerrit-ui/app/models/views/agreement.ts
@@ -5,7 +5,7 @@
*/
import {GerritView} from '../../services/router/router-model';
import {define} from '../dependency';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {ViewState} from './base';
export interface AgreementViewState extends ViewState {
diff --git a/polygerrit-ui/app/models/views/change.ts b/polygerrit-ui/app/models/views/change.ts
index 6cd7a0b..06d981a 100644
--- a/polygerrit-ui/app/models/views/change.ts
+++ b/polygerrit-ui/app/models/views/change.ts
@@ -24,7 +24,7 @@
} from '../../utils/url-util';
import {AttemptChoice} from '../checks/checks-util';
import {define} from '../dependency';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {ViewState} from './base';
export enum ChangeChildView {
diff --git a/polygerrit-ui/app/models/views/dashboard.ts b/polygerrit-ui/app/models/views/dashboard.ts
index d2e7995..f5fc8b4 100644
--- a/polygerrit-ui/app/models/views/dashboard.ts
+++ b/polygerrit-ui/app/models/views/dashboard.ts
@@ -9,7 +9,7 @@
import {DashboardSection} from '../../utils/dashboard-util';
import {encodeURL, getBaseUrl} from '../../utils/url-util';
import {define} from '../dependency';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {Route, ViewState} from './base';
export const PROJECT_DASHBOARD_ROUTE: Route<DashboardViewState> = {
@@ -19,6 +19,7 @@
const dashboard = (ctx.params[1] ?? '') as DashboardId;
const state: DashboardViewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.REPO,
project,
dashboard,
};
@@ -26,8 +27,15 @@
},
};
+export enum DashboardType {
+ USER,
+ REPO,
+ CUSTOM,
+}
+
export interface DashboardViewState extends ViewState {
view: GerritView.DASHBOARD;
+ type: DashboardType;
project?: RepoName;
dashboard?: DashboardId;
user?: string;
diff --git a/polygerrit-ui/app/models/views/dashboard_test.ts b/polygerrit-ui/app/models/views/dashboard_test.ts
index 9509977..47f4868a 100644
--- a/polygerrit-ui/app/models/views/dashboard_test.ts
+++ b/polygerrit-ui/app/models/views/dashboard_test.ts
@@ -11,6 +11,7 @@
import {DashboardId} from '../../types/common';
import {
createDashboardUrl,
+ DashboardType,
DashboardViewState,
PROJECT_DASHBOARD_ROUTE,
} from './dashboard';
@@ -23,6 +24,7 @@
const state: DashboardViewState = {
view: GerritView.DASHBOARD,
+ type: DashboardType.REPO,
project: 'asdf' as RepoName,
dashboard: 'qwer' as DashboardId,
};
@@ -37,21 +39,31 @@
suite('createDashboardUrl()', () => {
test('self dashboard', () => {
- assert.equal(createDashboardUrl({}), '/dashboard/self');
+ assert.equal(
+ createDashboardUrl({type: DashboardType.USER}),
+ '/dashboard/self'
+ );
});
test('baseUrl', () => {
window.CANONICAL_PATH = '/base';
- assert.equal(createDashboardUrl({}).substring(0, 5), '/base');
+ assert.equal(
+ createDashboardUrl({type: DashboardType.USER}).substring(0, 5),
+ '/base'
+ );
window.CANONICAL_PATH = undefined;
});
test('user dashboard', () => {
- assert.equal(createDashboardUrl({user: 'user'}), '/dashboard/user');
+ assert.equal(
+ createDashboardUrl({type: DashboardType.USER, user: 'user'}),
+ '/dashboard/user'
+ );
});
test('custom self dashboard, no title', () => {
const state = {
+ type: DashboardType.CUSTOM,
sections: [
{name: 'section 1', query: 'query 1'},
{name: 'section 2', query: 'query 2'},
@@ -65,6 +77,7 @@
test('custom repo dashboard', () => {
const state = {
+ type: DashboardType.CUSTOM,
sections: [
{name: 'section 1', query: 'query 1 ${project}'},
{name: 'section 2', query: 'query 2 ${repo}'},
@@ -80,6 +93,7 @@
test('custom user dashboard, with title', () => {
const state = {
+ type: DashboardType.CUSTOM,
user: 'user',
sections: [{name: 'name', query: 'query'}],
title: 'custom dashboard',
@@ -92,6 +106,7 @@
test('repo dashboard', () => {
const state = {
+ type: DashboardType.REPO,
project: 'gerrit/repo' as RepoName,
dashboard: 'default:main' as DashboardId,
};
@@ -103,6 +118,7 @@
test('project dashboard (legacy)', () => {
const state = {
+ type: DashboardType.REPO,
project: 'gerrit/project' as RepoName,
dashboard: 'default:main' as DashboardId,
};
diff --git a/polygerrit-ui/app/models/views/documentation.ts b/polygerrit-ui/app/models/views/documentation.ts
index abb0f03..ac844fc 100644
--- a/polygerrit-ui/app/models/views/documentation.ts
+++ b/polygerrit-ui/app/models/views/documentation.ts
@@ -6,7 +6,7 @@
import {GerritView} from '../../services/router/router-model';
import {getBaseUrl} from '../../utils/url-util';
import {define} from '../dependency';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {ViewState} from './base';
export interface DocumentationViewState extends ViewState {
diff --git a/polygerrit-ui/app/models/views/group.ts b/polygerrit-ui/app/models/views/group.ts
index f4a7c78..2297bc4 100644
--- a/polygerrit-ui/app/models/views/group.ts
+++ b/polygerrit-ui/app/models/views/group.ts
@@ -7,7 +7,7 @@
import {GroupId} from '../../types/common';
import {encodeURL, getBaseUrl} from '../../utils/url-util';
import {define} from '../dependency';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {ViewState} from './base';
export enum GroupDetailView {
diff --git a/polygerrit-ui/app/models/views/plugin.ts b/polygerrit-ui/app/models/views/plugin.ts
index ca70fb4..e3fc271 100644
--- a/polygerrit-ui/app/models/views/plugin.ts
+++ b/polygerrit-ui/app/models/views/plugin.ts
@@ -6,7 +6,7 @@
import {GerritView} from '../../services/router/router-model';
import {select} from '../../utils/observable-util';
import {define} from '../dependency';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {ViewState} from './base';
/**
diff --git a/polygerrit-ui/app/models/views/repo.ts b/polygerrit-ui/app/models/views/repo.ts
index 66bf5bf..1629449 100644
--- a/polygerrit-ui/app/models/views/repo.ts
+++ b/polygerrit-ui/app/models/views/repo.ts
@@ -7,7 +7,7 @@
import {BranchName, RepoName} from '../../types/common';
import {encodeURL, getBaseUrl} from '../../utils/url-util';
import {define} from '../dependency';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {ViewState} from './base';
export enum RepoDetailView {
diff --git a/polygerrit-ui/app/models/views/search.ts b/polygerrit-ui/app/models/views/search.ts
index 2edc540..bc17a58 100644
--- a/polygerrit-ui/app/models/views/search.ts
+++ b/polygerrit-ui/app/models/views/search.ts
@@ -21,7 +21,7 @@
import {escapeAndWrapSearchOperatorValue} from '../../utils/string-util';
import {encodeURL, getBaseUrl} from '../../utils/url-util';
import {define, Provider} from '../dependency';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {UserModel} from '../user/user-model';
import {ViewState} from './base';
import {createChangeUrl} from './change';
diff --git a/polygerrit-ui/app/models/views/settings.ts b/polygerrit-ui/app/models/views/settings.ts
index c1a8c08..0a6285f 100644
--- a/polygerrit-ui/app/models/views/settings.ts
+++ b/polygerrit-ui/app/models/views/settings.ts
@@ -7,7 +7,7 @@
import {select} from '../../utils/observable-util';
import {getBaseUrl} from '../../utils/url-util';
import {define} from '../dependency';
-import {Model} from '../model';
+import {Model} from '../base/model';
import {ViewState} from './base';
export interface SettingsViewState extends ViewState {
diff --git a/polygerrit-ui/app/package.json b/polygerrit-ui/app/package.json
index 7910931..a5dc421 100644
--- a/polygerrit-ui/app/package.json
+++ b/polygerrit-ui/app/package.json
@@ -38,7 +38,7 @@
"highlightjs-closure-templates": "https://github.com/highlightjs/highlightjs-closure-templates",
"highlightjs-structured-text": "https://github.com/highlightjs/highlightjs-structured-text",
"immer": "^9.0.21",
- "lit": "^2.8.0",
+ "lit": "^3.0.0",
"polymer-bridges": "file:../../polymer-bridges",
"polymer-resin": "^2.0.1",
"resemblejs": "^5.0.0",
@@ -55,4 +55,4 @@
},
"license": "Apache-2.0",
"private": true
-}
+}
\ No newline at end of file
diff --git a/polygerrit-ui/app/services/app-context-init.ts b/polygerrit-ui/app/services/app-context-init.ts
index 8589ae3..40517ac 100644
--- a/polygerrit-ui/app/services/app-context-init.ts
+++ b/polygerrit-ui/app/services/app-context-init.ts
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {AppContext} from './app-context';
-import {create, Finalizable, Registry} from './registry';
+import {create, Registry} from './registry';
import {DependencyToken} from '../models/dependency';
import {FlagsServiceImplementation} from './flags/flags_impl';
import {GrReporting} from './gr-reporting/gr-reporting_impl';
@@ -72,6 +72,7 @@
RelatedChangesModel,
relatedChangesModelToken,
} from '../models/change/related-changes-model';
+import {Finalizable} from '../types/types';
/**
* The AppContext lazy initializator for all services
@@ -87,7 +88,8 @@
authService: (_ctx: Partial<AppContext>) => new Auth(),
restApiService: (ctx: Partial<AppContext>) => {
assertIsDefined(ctx.authService, 'authService');
- return new GrRestApiServiceImpl(ctx.authService);
+ assertIsDefined(ctx.flagsService, 'flagsService');
+ return new GrRestApiServiceImpl(ctx.authService, ctx.flagsService);
},
};
return create<AppContext>(appRegistry);
diff --git a/polygerrit-ui/app/services/app-context-init_test.ts b/polygerrit-ui/app/services/app-context-init_test.ts
index 7834e53..3572189 100644
--- a/polygerrit-ui/app/services/app-context-init_test.ts
+++ b/polygerrit-ui/app/services/app-context-init_test.ts
@@ -5,9 +5,9 @@
*/
import '../test/common-test-setup';
import {AppContext} from './app-context';
-import {Finalizable} from './registry';
import {createTestAppContext} from '../test/test-app-context-init';
import {assert} from '@open-wc/testing';
+import {Finalizable} from '../types/types';
suite('app context initializer tests', () => {
let appContext: AppContext & Finalizable;
diff --git a/polygerrit-ui/app/services/app-context.ts b/polygerrit-ui/app/services/app-context.ts
index aa2c032..eeddc8e 100644
--- a/polygerrit-ui/app/services/app-context.ts
+++ b/polygerrit-ui/app/services/app-context.ts
@@ -3,11 +3,11 @@
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {Finalizable} from './registry';
import {FlagsService} from './flags/flags';
import {ReportingService} from './gr-reporting/gr-reporting';
import {AuthService} from './gr-auth/gr-auth';
import {RestApiService} from './gr-rest-api/gr-rest-api';
+import {Finalizable} from '../types/types';
export interface AppContext {
flagsService: FlagsService;
diff --git a/polygerrit-ui/app/services/flags/flags.ts b/polygerrit-ui/app/services/flags/flags.ts
index a59f3da..b7d36f4 100644
--- a/polygerrit-ui/app/services/flags/flags.ts
+++ b/polygerrit-ui/app/services/flags/flags.ts
@@ -3,7 +3,8 @@
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {Finalizable} from '../registry';
+
+import {Finalizable} from '../../types/types';
export interface FlagsService extends Finalizable {
isEnabled(experimentId: string): boolean;
@@ -19,6 +20,5 @@
PUSH_NOTIFICATIONS_DEVELOPER = 'UiFeature__push_notifications_developer',
PUSH_NOTIFICATIONS = 'UiFeature__push_notifications',
ML_SUGGESTED_EDIT = 'UiFeature__ml_suggested_edit',
- DIFF_FOR_USER_SUGGESTED_EDIT = 'UiFeature__diff_for_user_suggested_edit',
REVISION_PARENTS_DATA = 'UiFeature__revision_parents_data',
}
diff --git a/polygerrit-ui/app/services/flags/flags_impl.ts b/polygerrit-ui/app/services/flags/flags_impl.ts
index 4ef55a2..ca9aa60 100644
--- a/polygerrit-ui/app/services/flags/flags_impl.ts
+++ b/polygerrit-ui/app/services/flags/flags_impl.ts
@@ -3,8 +3,8 @@
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
+import {Finalizable} from '../../types/types';
import {FlagsService} from './flags';
-import {Finalizable} from '../registry';
declare global {
interface Window {
diff --git a/polygerrit-ui/app/services/gr-auth/gr-auth.ts b/polygerrit-ui/app/services/gr-auth/gr-auth.ts
index 945d6f9..a6b4fdd 100644
--- a/polygerrit-ui/app/services/gr-auth/gr-auth.ts
+++ b/polygerrit-ui/app/services/gr-auth/gr-auth.ts
@@ -4,8 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {define} from '../../models/dependency';
-import {AuthRequestInit} from '../../types/types';
-import {Finalizable} from '../registry';
+import {AuthRequestInit, Finalizable} from '../../types/types';
export enum AuthType {
XSRF_TOKEN = 'xsrf_token',
ACCESS_TOKEN = 'access_token',
diff --git a/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts b/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts
index 2312fc9..fb53d5e 100644
--- a/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts
+++ b/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts
@@ -3,10 +3,9 @@
* Copyright 2017 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {AuthRequestInit} from '../../types/types';
+import {AuthRequestInit, Finalizable} from '../../types/types';
import {fire} from '../../utils/event-util';
import {getBaseUrl} from '../../utils/url-util';
-import {Finalizable} from '../registry';
import {
AuthService,
AuthStatus,
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
index 9b08a6e..6df2c67 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
@@ -3,7 +3,6 @@
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {Finalizable} from '../registry';
import {NumericChangeId} from '../../types/common';
import {EventDetails, ReportingOptions} from '../../api/reporting';
import {PluginApi} from '../../api/plugin';
@@ -13,6 +12,7 @@
LifeCycle,
Timing,
} from '../../constants/reporting';
+import {Finalizable} from '../../types/types';
export type EventValue = string | number | {error?: Error};
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
index 61534a4..84b9481 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
@@ -9,7 +9,6 @@
import {NumericChangeId} from '../../types/common';
import {Deduping, EventDetails, ReportingOptions} from '../../api/reporting';
import {PluginApi} from '../../api/plugin';
-import {Finalizable} from '../registry';
import {
Execution,
Interaction,
@@ -18,6 +17,7 @@
} from '../../constants/reporting';
import {onCLS, onFID, onLCP, Metric, onINP} from 'web-vitals';
import {getEventPath, isElementTarget} from '../../utils/dom-util';
+import {Finalizable} from '../../types/types';
// Latency reporting constants.
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
index fd1b88c..fb1f0c3 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
@@ -7,7 +7,7 @@
import {EventDetails} from '../../api/reporting';
import {PluginApi} from '../../api/plugin';
import {Execution, Interaction} from '../../constants/reporting';
-import {Finalizable} from '../registry';
+import {Finalizable} from '../../types/types';
export class MockTimer implements Timer {
end(): this {
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
index 5339e38..acc7cdd 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
@@ -20,7 +20,6 @@
import {GrReviewerUpdatesParser} from '../../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser';
import {parseDate} from '../../utils/date-util';
import {getBaseUrl} from '../../utils/url-util';
-import {Finalizable} from '../registry';
import {getParentIndex, isMergeParent} from '../../utils/patch-set-util';
import {listChangesOptionsToHex} from '../../utils/change-util';
import {assertNever, hasOwnProperty} from '../../utils/common-util';
@@ -139,12 +138,17 @@
ReviewerState,
} from '../../constants/constants';
import {firePageError, fireServerError} from '../../utils/event-util';
-import {AuthRequestInit, ParsedChangeInfo} from '../../types/types';
+import {
+ AuthRequestInit,
+ Finalizable,
+ ParsedChangeInfo,
+} from '../../types/types';
import {ErrorCallback} from '../../api/rest';
import {addDraftProp} from '../../utils/comment-util';
import {BaseScheduler, Scheduler} from '../scheduler/scheduler';
import {MaxInFlightScheduler} from '../scheduler/max-in-flight-scheduler';
import {escapeAndWrapSearchOperatorValue} from '../../utils/string-util';
+import {FlagsService, KnownExperimentId} from '../flags/flags';
const MAX_PROJECT_RESULTS = 25;
export const PROBE_PATH = '/Documentation/index.html';
@@ -298,7 +302,10 @@
// Used to serialize requests for certain RPCs
readonly _serialScheduler: Scheduler<Response>;
- constructor(private readonly authService: AuthService) {
+ constructor(
+ private readonly authService: AuthService,
+ private readonly flagService: FlagsService
+ ) {
this._restApiHelper = new GrRestApiHelper(
this._cache,
this.authService,
@@ -790,11 +797,34 @@
}) as Promise<AccountDetailInfo | undefined>;
}
- getAccountEmails() {
- return this._fetchSharedCacheURL({
- url: '/accounts/self/emails',
- reportUrlAsIs: true,
- }) as Promise<EmailInfo[] | undefined>;
+ async getAccountEmails() {
+ const isloggedIn = await this.getLoggedIn();
+ if (isloggedIn) {
+ return this._fetchSharedCacheURL({
+ url: '/accounts/self/emails',
+ reportUrlAsIs: true,
+ }) as Promise<EmailInfo[] | undefined>;
+ } else return;
+ }
+
+ getAccountEmailsFor(email: string) {
+ return this.getLoggedIn()
+ .then(isLoggedIn => {
+ if (isLoggedIn) {
+ return this.getAccountCapabilities();
+ } else {
+ return undefined;
+ }
+ })
+ .then((capabilities: AccountCapabilityInfo | undefined) => {
+ if (capabilities && capabilities.viewSecondaryEmails) {
+ return this._fetchSharedCacheURL({
+ url: '/accounts/' + email + '/emails',
+ reportUrlAsIs: true,
+ }) as Promise<EmailInfo[] | undefined>;
+ }
+ return undefined;
+ });
}
addAccountEmail(email: string): Promise<Response> {
@@ -828,9 +858,9 @@
if (cachedEmails) {
const emails = cachedEmails.map(entry => {
if (entry.email === email) {
- return {email, preferred: true};
+ return {email: entry.email, preferred: true};
} else {
- return {email};
+ return {email: entry.email, preferred: false};
}
});
this._cache.set('/accounts/self/emails', emails);
@@ -1191,9 +1221,7 @@
cancelCondition?: CancelConditionCallback
): Promise<ParsedChangeInfo | undefined> {
if (!changeNum) return;
- const optionsHex = listChangesOptionsToHex(
- ...(await this.getChangeOptions())
- );
+ const optionsHex = await this.getChangeOptionsHex();
return this._getChangeDetail(
changeNum,
@@ -1207,11 +1235,13 @@
);
}
- /**
- * Returns the options to use for querying multiple changes (e.g. dashboard or search).
- * @return The options hex to use when fetching multiple changes.
- */
private getListChangesOptionsHex() {
+ if (
+ window.DEFAULT_DETAIL_HEXES &&
+ window.DEFAULT_DETAIL_HEXES.dashboardPage
+ ) {
+ return window.DEFAULT_DETAIL_HEXES.dashboardPage;
+ }
const options = [
ListChangesOption.LABELS,
ListChangesOption.DETAILED_ACCOUNTS,
@@ -1222,11 +1252,18 @@
return listChangesOptionsToHex(...options);
}
+ async getChangeOptionsHex(): Promise<string> {
+ if (window.DEFAULT_DETAIL_HEXES && window.DEFAULT_DETAIL_HEXES.changePage) {
+ return window.DEFAULT_DETAIL_HEXES.changePage;
+ }
+ return listChangesOptionsToHex(...(await this.getChangeOptions()));
+ }
+
async getChangeOptions(): Promise<number[]> {
const config = await this.getConfig(false);
// This list MUST be kept in sync with
- // ChangeIT#changeDetailsDoesNotRequireIndex
+ // ChangeIT#changeDetailsDoesNotRequireIndex and IndexPreloadingUtil#CHANGE_DETAIL_OPTIONS
// This list MUST be kept in sync with getResponseFormatOptions
const options = [
ListChangesOption.ALL_COMMITS,
@@ -1235,14 +1272,15 @@
ListChangesOption.DETAILED_ACCOUNTS,
ListChangesOption.DETAILED_LABELS,
ListChangesOption.DOWNLOAD_COMMANDS,
- ListChangesOption.LABELS,
ListChangesOption.MESSAGES,
ListChangesOption.SUBMITTABLE,
ListChangesOption.WEB_LINKS,
ListChangesOption.SKIP_DIFFSTAT,
ListChangesOption.SUBMIT_REQUIREMENTS,
- ListChangesOption.PARENTS,
];
+ if (this.flagService.isEnabled(KnownExperimentId.REVISION_PARENTS_DATA)) {
+ options.push(ListChangesOption.PARENTS);
+ }
if (config?.receive?.enable_signed_push) {
options.push(ListChangesOption.PUSH_CERTIFICATES);
}
@@ -1253,7 +1291,7 @@
const config = await this.getConfig(false);
// This list MUST be kept in sync with
- // ChangeIT#changeDetailsDoesNotRequireIndex
+ // ChangeIT#changeDetailsDoesNotRequireIndex and IndexPreloadingUtil#CHANGE_DETAIL_OPTIONS
// This list MUST be kept in sync with getChangeOptions
const options = [
'ALL_COMMITS',
@@ -1262,14 +1300,15 @@
'DETAILED_LABELS',
'DETAILED_ACCOUNTS',
'DOWNLOAD_COMMANDS',
- 'LABELS',
'MESSAGES',
'SUBMITTABLE',
'WEB_LINKS',
'SKIP_DIFFSTAT',
'SUBMIT_REQUIREMENTS',
- 'PARENTS',
];
+ if (this.flagService.isEnabled(KnownExperimentId.REVISION_PARENTS_DATA)) {
+ options.push('PARENTS');
+ }
if (config?.receive?.enable_signed_push) {
options.push('PUSH_CERTIFICATES');
}
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts
index 2fd7ba1..624bcb5 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts
@@ -62,6 +62,7 @@
import {AuthService} from '../gr-auth/gr-auth';
import {GrAuthMock} from '../gr-auth/gr-auth_mock';
import {getBaseUrl} from '../../utils/url-util';
+import {FlagsServiceImplementation} from '../flags/flags_impl';
const EXPECTED_QUERY_OPTIONS = listChangesOptionsToHex(
ListChangesOption.CHANGE_ACTIONS,
@@ -91,7 +92,10 @@
// fake auth
authService = new GrAuthMock();
sinon.stub(authService, 'authCheck').resolves(true);
- element = new GrRestApiServiceImpl(authService);
+ element = new GrRestApiServiceImpl(
+ authService,
+ new FlagsServiceImplementation()
+ );
element._projectLookup = {};
});
@@ -563,6 +567,29 @@
assert.deepEqual(sendStub.lastCall.args[0].body, {token: 'foo'});
});
+ test('setPreferredAccountEmail', async () => {
+ const email1 = 'email1@example.com';
+ const email2 = 'email2@example.com';
+ const encodedEmail = encodeURIComponent(email2);
+ const sendStub = sinon.stub(element._restApiHelper, 'send').resolves();
+ element._cache.set('/accounts/self/emails', [
+ {email: email1, preferred: true},
+ {email: email2, preferred: false},
+ ]);
+
+ await element.setPreferredAccountEmail(email2);
+ assert.isTrue(sendStub.calledOnce);
+ assert.equal(sendStub.lastCall.args[0].method, HttpMethod.PUT);
+ assert.equal(
+ sendStub.lastCall.args[0].url,
+ `/accounts/self/emails/${encodedEmail}/preferred`
+ );
+ assert.deepEqual(element._cache.get('/accounts/self/emails'), [
+ {email: email1, preferred: false},
+ {email: email2, preferred: true},
+ ]);
+ });
+
test('setAccountStatus', async () => {
const sendStub = sinon
.stub(element._restApiHelper, 'send')
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
index 3916094..c70f780 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
@@ -4,7 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {HttpMethod} from '../../constants/constants';
-import {Finalizable} from '../registry';
import {
AccountCapabilityInfo,
AccountDetailInfo,
@@ -98,7 +97,7 @@
DiffPreferencesInfo,
IgnoreWhitespaceType,
} from '../../types/diff';
-import {ParsedChangeInfo} from '../../types/types';
+import {Finalizable, ParsedChangeInfo} from '../../types/types';
import {ErrorCallback} from '../../api/rest';
export type CancelConditionCallback = () => boolean;
@@ -230,6 +229,7 @@
saveEditPreferences(prefs: EditPreferencesInfo): Promise<Response>;
getAccountEmails(): Promise<EmailInfo[] | undefined>;
+ getAccountEmailsFor(email: string): Promise<EmailInfo[] | undefined>;
deleteAccountEmail(email: string): Promise<Response>;
setPreferredAccountEmail(email: string): Promise<void>;
diff --git a/polygerrit-ui/app/services/highlight/highlight-service.ts b/polygerrit-ui/app/services/highlight/highlight-service.ts
index d10d875..d59431b 100644
--- a/polygerrit-ui/app/services/highlight/highlight-service.ts
+++ b/polygerrit-ui/app/services/highlight/highlight-service.ts
@@ -11,10 +11,10 @@
SyntaxWorkerMessageType,
SyntaxLayerLine,
} from '../../types/syntax-worker-api';
+import {Finalizable} from '../../types/types';
import {prependOrigin} from '../../utils/url-util';
import {createWorker} from '../../utils/worker-util';
import {ReportingService} from '../gr-reporting/gr-reporting';
-import {Finalizable} from '../registry';
const hljsLibUrl = `${
window.STATIC_RESOURCE_PATH ?? ''
diff --git a/polygerrit-ui/app/services/registry.ts b/polygerrit-ui/app/services/registry.ts
index 48a5241..a88aec3 100644
--- a/polygerrit-ui/app/services/registry.ts
+++ b/polygerrit-ui/app/services/registry.ts
@@ -4,11 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-// A finalizable object has a single method `finalize` that is called when
-// the object is no longer needed and should clean itself up.
-export interface Finalizable {
- finalize(): void;
-}
+import {Finalizable} from '../types/types';
// A factory can take a partially created TContext and generate a property
// for a given key on that TContext.
diff --git a/polygerrit-ui/app/services/registry_test.ts b/polygerrit-ui/app/services/registry_test.ts
index 639cd64..15d37d9 100644
--- a/polygerrit-ui/app/services/registry_test.ts
+++ b/polygerrit-ui/app/services/registry_test.ts
@@ -3,9 +3,10 @@
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {create, Finalizable, Registry} from './registry';
+import {create, Registry} from './registry';
import '../test/common-test-setup';
import {assert} from '@open-wc/testing';
+import {Finalizable} from '../types/types';
class Foo implements Finalizable {
constructor(private readonly final: string[]) {}
diff --git a/polygerrit-ui/app/services/router/router-model.ts b/polygerrit-ui/app/services/router/router-model.ts
index 5e2cc10..05927f3 100644
--- a/polygerrit-ui/app/services/router/router-model.ts
+++ b/polygerrit-ui/app/services/router/router-model.ts
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {Observable} from 'rxjs';
-import {Model} from '../../models/model';
+import {Model} from '../../models/base/model';
import {select} from '../../utils/observable-util';
import {define} from '../../models/dependency';
diff --git a/polygerrit-ui/app/services/service-worker-installer.ts b/polygerrit-ui/app/services/service-worker-installer.ts
index b83713c..8c606d4 100644
--- a/polygerrit-ui/app/services/service-worker-installer.ts
+++ b/polygerrit-ui/app/services/service-worker-installer.ts
@@ -15,7 +15,7 @@
import {LifeCycle} from '../constants/reporting';
import {ReportingService} from './gr-reporting/gr-reporting';
import {define} from '../models/dependency';
-import {Model} from '../models/model';
+import {Model} from '../models/base/model';
import {Observable} from 'rxjs';
import {select} from '../utils/observable-util';
diff --git a/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts b/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts
index 756c209..db9d8fe 100644
--- a/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts
+++ b/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts
@@ -22,10 +22,10 @@
ShortcutOptions,
} from '../../utils/dom-util';
import {ReportingService} from '../gr-reporting/gr-reporting';
-import {Finalizable} from '../registry';
import {UserModel} from '../../models/user/user-model';
import {define} from '../../models/dependency';
import {isCharacterLetter, isUpperCase} from '../../utils/string-util';
+import {Finalizable} from '../../types/types';
export {Shortcut, ShortcutSection};
diff --git a/polygerrit-ui/app/services/storage/gr-storage.ts b/polygerrit-ui/app/services/storage/gr-storage.ts
index d7eb09a..0b34614 100644
--- a/polygerrit-ui/app/services/storage/gr-storage.ts
+++ b/polygerrit-ui/app/services/storage/gr-storage.ts
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {NumericChangeId} from '../../types/common';
-import {Finalizable} from '../registry';
+import {Finalizable} from '../../types/types';
export interface StorageObject {
message?: string;
diff --git a/polygerrit-ui/app/services/storage/gr-storage_impl.ts b/polygerrit-ui/app/services/storage/gr-storage_impl.ts
index 7a47e0e..9ddba01 100644
--- a/polygerrit-ui/app/services/storage/gr-storage_impl.ts
+++ b/polygerrit-ui/app/services/storage/gr-storage_impl.ts
@@ -4,9 +4,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {StorageObject, StorageService} from './gr-storage';
-import {Finalizable} from '../registry';
import {NumericChangeId} from '../../types/common';
import {define} from '../../models/dependency';
+import {Finalizable} from '../../types/types';
export const DURATION_DAY = 24 * 60 * 60 * 1000;
diff --git a/polygerrit-ui/app/test/common-test-setup.ts b/polygerrit-ui/app/test/common-test-setup.ts
index ed472cc..82c1bb9 100644
--- a/polygerrit-ui/app/test/common-test-setup.ts
+++ b/polygerrit-ui/app/test/common-test-setup.ts
@@ -7,7 +7,6 @@
// https://github.com/Polymer/polymer-resin/issues/9 is resolved.
import '../scripts/bundled-polymer';
import {getAppContext} from '../services/app-context';
-import {Finalizable} from '../services/registry';
import {
createTestAppContext,
createTestDependencies,
@@ -40,6 +39,7 @@
import '../styles/themes/app-theme';
import {Creator} from '../services/app-context-init';
import {pluginLoaderToken} from '../elements/shared/gr-js-api-interface/gr-plugin-loader';
+import {Finalizable} from '../types/types';
declare global {
interface Window {
diff --git a/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts b/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
index b6fef81..e0a1682 100644
--- a/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
+++ b/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
@@ -191,6 +191,9 @@
getAccountEmails(): Promise<EmailInfo[] | undefined> {
return Promise.resolve([]);
},
+ getAccountEmailsFor(): Promise<EmailInfo[] | undefined> {
+ return Promise.resolve([]);
+ },
getAccountGPGKeys(): Promise<Record<string, GpgKeyInfo>> {
return Promise.resolve({});
},
diff --git a/polygerrit-ui/app/test/test-app-context-init.ts b/polygerrit-ui/app/test/test-app-context-init.ts
index da6a92d..6b9f507 100644
--- a/polygerrit-ui/app/test/test-app-context-init.ts
+++ b/polygerrit-ui/app/test/test-app-context-init.ts
@@ -5,7 +5,7 @@
*/
// Init app context before any other imports
-import {create, Registry, Finalizable} from '../services/registry';
+import {create, Registry} from '../services/registry';
import {AppContext} from '../services/app-context';
import {grReportingMock} from '../services/gr-reporting/gr-reporting_mock';
import {grRestApiMock} from './mocks/gr-rest-api_mock';
@@ -22,6 +22,7 @@
diffModelToken,
DiffModel,
} from '../embed/diff/gr-diff-model/gr-diff-model';
+import {Finalizable} from '../types/types';
export function createTestAppContext(): AppContext & Finalizable {
const appRegistry: Registry<AppContext> = {
diff --git a/polygerrit-ui/app/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts
index 38d50d5..d941c9f 100644
--- a/polygerrit-ui/app/test/test-data-generators.ts
+++ b/polygerrit-ui/app/test/test-data-generators.ts
@@ -112,7 +112,7 @@
import {GroupViewState} from '../models/views/group';
import {RepoDetailView, RepoViewState} from '../models/views/repo';
import {AdminChildView, AdminViewState} from '../models/views/admin';
-import {DashboardViewState} from '../models/views/dashboard';
+import {DashboardType, DashboardViewState} from '../models/views/dashboard';
const TEST_DEFAULT_EXPRESSION = 'label:Verified=MAX -label:Verified=MIN';
export const TEST_PROJECT_NAME: RepoName = 'test-project' as RepoName;
@@ -744,6 +744,7 @@
export function createDashboardViewState(): DashboardViewState {
return {
view: GerritView.DASHBOARD,
+ type: DashboardType.USER,
user: 'self',
};
}
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 7f197d6..9992f8b 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -253,6 +253,8 @@
export type UserId = AccountId | GroupId | EmailAddress;
+export type DiffPageSidebar = 'NONE' | `plugin-${string}`;
+
// Must be kept in sync with the ListChangesOption enum.
// See: java/com/google/gerrit/extensions/client/ListChangesOption.java
export const ListChangesOption = {
@@ -1292,6 +1294,7 @@
viewConnections?: boolean;
viewPlugins?: boolean;
viewQueue?: boolean;
+ viewSecondaryEmails?: boolean;
}
/**
@@ -1332,6 +1335,7 @@
// The email_format doesn't mentioned in doc, but exists in Java class GeneralPreferencesInfo
email_format?: EmailFormat;
allow_browser_notifications?: boolean;
+ diff_page_sidebar?: DiffPageSidebar;
}
/**
diff --git a/polygerrit-ui/app/types/globals.ts b/polygerrit-ui/app/types/globals.ts
index 7448a27..4a709b0 100644
--- a/polygerrit-ui/app/types/globals.ts
+++ b/polygerrit-ui/app/types/globals.ts
@@ -23,6 +23,12 @@
// it's defined because of limitations from typescript, which don't import .mjs
page?: unknown;
hljs?: HighlightJS;
+
+ DEFAULT_DETAIL_HEXES?: {
+ diffPage?: string;
+ changePage?: string;
+ dashboardPage?: string;
+ };
STATIC_RESOURCE_PATH?: string;
PRELOADED_QUERIES?: {
diff --git a/polygerrit-ui/app/types/types.ts b/polygerrit-ui/app/types/types.ts
index c9f3414..43e06a4 100644
--- a/polygerrit-ui/app/types/types.ts
+++ b/polygerrit-ui/app/types/types.ts
@@ -19,6 +19,12 @@
Timestamp,
} from './common';
+// A finalizable object has a single method `finalize` that is called when
+// the object is no longer needed and should clean itself up.
+export interface Finalizable {
+ finalize(): void;
+}
+
export function isDefined<T>(x: T): x is NonNullable<T> {
return x !== undefined && x !== null;
}
@@ -32,6 +38,13 @@
GENERIC = 'GENERIC',
}
+export enum LoadingStatus {
+ NOT_LOADED = 'NOT_LOADED',
+ LOADING = 'LOADING',
+ RELOADING = 'RELOADING',
+ LOADED = 'LOADED',
+}
+
export interface AuthRequestInit extends RequestInit {
// RequestInit define headers as HeadersInit, i.e.
// Headers | string[][] | Record<string, string>
diff --git a/polygerrit-ui/app/utils/patch-set-util.ts b/polygerrit-ui/app/utils/patch-set-util.ts
index dffc091..7a5cd45 100644
--- a/polygerrit-ui/app/utils/patch-set-util.ts
+++ b/polygerrit-ui/app/utils/patch-set-util.ts
@@ -327,24 +327,32 @@
return branch as BranchName;
}
-export function getParentInfoString(
+export function getParentCommit(
rev?: RevisionInfo | EditRevisionInfo,
index?: number
) {
const parents = rev?.parents_data ?? [];
const parent = parents[index ?? 0];
if (!parent) return '';
+ return shorten(parent.commit_id) ?? '';
+}
- let info = '';
- if (parent.change_number) {
- info = `${info}Change ${parent.change_number} at patchset ${parent.patch_set_number}\n`;
- }
+export function getParentInfoString(
+ rev?: RevisionInfo | EditRevisionInfo,
+ index?: number
+) {
+ const parents = rev?.parents_data ?? [];
+ const parent = parents[index ?? 0];
+ if (!parent || parent.is_merged_in_target_branch) return '';
- if (parent.branch_name) {
- const commit = shorten(parent.commit_id) ?? 'unknown';
- info = `${info}Branch ${branchName(
- parent.branch_name
- )} at commit ${commit} `;
+ if (index === 0) {
+ if (parent.change_number) {
+ return `Patchset ${parent.patch_set_number} of Change ${parent.change_number}`;
+ } else {
+ return 'Warning: The base commit is not known (aka reachable) in the target branch.';
+ }
+ } else {
+ // For merge changes the parents with index > 0 are expected to be from a different branch.
+ return 'Other branch';
}
- return info;
}
diff --git a/polygerrit-ui/app/workers/service-worker-class.ts b/polygerrit-ui/app/workers/service-worker-class.ts
index 03b6b902..260ee57 100644
--- a/polygerrit-ui/app/workers/service-worker-class.ts
+++ b/polygerrit-ui/app/workers/service-worker-class.ts
@@ -16,7 +16,7 @@
getServiceWorkerState,
putServiceWorkerState,
} from './service-worker-indexdb';
-import {createDashboardUrl} from '../models/views/dashboard';
+import {DashboardType, createDashboardUrl} from '../models/views/dashboard';
import {createChangeUrl} from '../models/views/change';
import {noAwait} from '../utils/async-util';
@@ -142,7 +142,7 @@
private showNotificationForDashboard(numOfChangesToNotifyAbout: number) {
const title = `You are in the attention set for ${numOfChangesToNotifyAbout} new changes.`;
- const dashboardUrl = createDashboardUrl({});
+ const dashboardUrl = createDashboardUrl({type: DashboardType.USER});
const data = {url: `${self.location.origin}${dashboardUrl}`};
const icon = `${self.location.origin}/favicon.ico`;
this.ctx.registration.showNotification(title, {data, icon});
diff --git a/polygerrit-ui/app/yarn.lock b/polygerrit-ui/app/yarn.lock
index a27631c..9cca523 100644
--- a/polygerrit-ui/app/yarn.lock
+++ b/polygerrit-ui/app/yarn.lock
@@ -2,17 +2,17 @@
# yarn lockfile v1
-"@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0":
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.1.tgz#64df34e2f12e68e78ac57e571d25ec07fa460ca9"
- integrity sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ==
+"@lit-labs/ssr-dom-shim@^1.1.2-pre.0":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.2.tgz#d693d972974a354034454ec1317eb6afd0b00312"
+ integrity sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g==
-"@lit/reactive-element@^1.3.0", "@lit/reactive-element@^1.6.0":
- version "1.6.3"
- resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.6.3.tgz#25b4eece2592132845d303e091bad9b04cdcfe03"
- integrity sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==
+"@lit/reactive-element@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-2.0.0.tgz#da14a256ac5533873b935840f306d572bac4a2ab"
+ integrity sha512-wn+2+uDcs62ROBmVAwssO4x5xue/uKD3MGGZOXL2sMxReTRIT0JXKyMXeu7gh0aJ4IJNEIG/3aOnUaQvM7BMzQ==
dependencies:
- "@lit-labs/ssr-dom-shim" "^1.0.0"
+ "@lit-labs/ssr-dom-shim" "^1.1.2-pre.0"
"@mapbox/node-pre-gyp@^1.0.0":
version "1.0.11"
@@ -46,7 +46,7 @@
resolved "https://registry.yarnpkg.com/@polymer/font-roboto/-/font-roboto-3.0.2.tgz#80cdaa7225db2359130dfb2c6d9a3be1820020c3"
integrity sha512-tx5TauYSmzsIvmSqepUPDYbs4/Ejz2XbZ1IkD7JEGqkdNUJlh+9KU85G56Tfdk/xjEZ8zorFfN09OSwiMrIQWA==
-"@polymer/iron-a11y-announcer@^3.0.0-pre.26", "@polymer/iron-a11y-announcer@^3.1.0":
+"@polymer/iron-a11y-announcer@^3.0.0-pre.26", "@polymer/iron-a11y-announcer@^3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@polymer/iron-a11y-announcer/-/iron-a11y-announcer-3.2.0.tgz#d04b1301413d473336cc5797dfd97b3b36dd0cd7"
integrity sha512-We+hyaFHcg7Ke8ovsoxUpYEXFIJLHxMCDaLehTB4dELS+C+K0zMnGSiqQvb/YzGS+nSYpAfkQIyg1msOCdHMtA==
@@ -423,7 +423,7 @@
"@polymer/paper-styles" "^3.0.0-pre.26"
"@polymer/polymer" "^3.0.0"
-"@polymer/polymer@^3.0.0", "@polymer/polymer@^3.0.2", "@polymer/polymer@^3.0.5", "@polymer/polymer@^3.3.1", "@polymer/polymer@^3.4.1":
+"@polymer/polymer@^3.0.0", "@polymer/polymer@^3.0.2", "@polymer/polymer@^3.0.5", "@polymer/polymer@^3.3.1", "@polymer/polymer@^3.5.1":
version "3.5.1"
resolved "https://registry.yarnpkg.com/@polymer/polymer/-/polymer-3.5.1.tgz#4b5234e43b8876441022bcb91313ab3c4a29f0c8"
integrity sha512-JlAHuy+1qIC6hL1ojEUfIVD58fzTpJAoCxFwV5yr0mYTXV1H8bz5zy0+rC963Cgr9iNXQ4T9ncSjC2fkF9BQfw==
@@ -435,17 +435,17 @@
resolved "https://registry.yarnpkg.com/@types/resemblejs/-/resemblejs-4.1.0.tgz#1c150e0de4117b29f9d5d5231489edc7cef8263e"
integrity sha512-+MIkKy/UngDfhTnvn2yK/KSzlbtLeB5BU73qqZrzIF24+e2r8enJ4cW3UbtkstByYSDV8pbheGAqg7zT8ZZ2pA==
-"@types/resize-observer-browser@^0.1.5":
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz#294aaadf24ac6580b8fbd1fe3ab7b59fe85f9ef3"
- integrity sha512-G9eN0Sn0ii9PWQ3Vl72jDPgeJwRWhv2Qk/nQkJuWmRmOB4HX3/BhD5SE1dZs/hzPZL/WKnvF0RHdTSG54QJFyg==
+"@types/resize-observer-browser@^0.1.7":
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.8.tgz#db41a93f9f37dad8e6f0c7bd2f5bbc042bf714d1"
+ integrity sha512-OpjAd26fD1G2OWlYzkrapJ12n+kyi0znYgE2AHfNccHY/am3kG+lfJ5brfcZ7+1CIybkPWGKrW+Wm97kbcOQaQ==
"@types/trusted-types@^2.0.2":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.4.tgz#2b38784cd16957d3782e8e2b31c03bc1d13b4d65"
integrity sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ==
-"@webcomponents/shadycss@^1.10.2", "@webcomponents/shadycss@^1.9.1":
+"@webcomponents/shadycss@^1.11.2", "@webcomponents/shadycss@^1.9.1":
version "1.11.2"
resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.11.2.tgz#7539b0ad29598aa2eafee8b341059e20ac9e1006"
integrity sha512-vRq+GniJAYSBmTRnhCYPAPq6THYqovJ/gzGThWbgEZUQaBccndGTi1hdiUP15HzEco0I6t4RCtXyX0rsSmwgPw==
@@ -658,30 +658,30 @@
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
-lit-element@^3.3.0:
- version "3.3.3"
- resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.3.3.tgz#10bc19702b96ef5416cf7a70177255bfb17b3209"
- integrity sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==
+lit-element@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-4.0.0.tgz#8343891bc9159a5fcb7f534914b37f2c0161e036"
+ integrity sha512-N6+f7XgusURHl69DUZU6sTBGlIN+9Ixfs3ykkNDfgfTkDYGGOWwHAYBhDqVswnFGyWgQYR2KiSpu4J76Kccs/A==
dependencies:
- "@lit-labs/ssr-dom-shim" "^1.1.0"
- "@lit/reactive-element" "^1.3.0"
- lit-html "^2.8.0"
+ "@lit-labs/ssr-dom-shim" "^1.1.2-pre.0"
+ "@lit/reactive-element" "^2.0.0"
+ lit-html "^3.0.0"
-lit-html@^2.8.0:
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.8.0.tgz#96456a4bb4ee717b9a7d2f94562a16509d39bffa"
- integrity sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==
+lit-html@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-3.0.0.tgz#77d6776ee488642c74c5575315ef81aa09d24ea9"
+ integrity sha512-DNJIE8dNY0dQF2Gs0sdMNUppMQT2/CvV4OVnSdg7BXAsGqkVwsE5bqQ04POfkYH5dBIuGnJYdFz5fYYyNnOxiA==
dependencies:
"@types/trusted-types" "^2.0.2"
-lit@^2.8.0:
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/lit/-/lit-2.8.0.tgz#4d838ae03059bf9cafa06e5c61d8acc0081e974e"
- integrity sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==
+lit@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lit/-/lit-3.0.0.tgz#204bd65935892a73670471e893ee8ca55d2f9a3b"
+ integrity sha512-nQ0teRzU1Kdj++VdmttS2WvIen8M79wChJ6guRKIIym2M3Ansg3Adj9O6yuQh2IpjxiUXlNuS81WKlQ4iL3BmA==
dependencies:
- "@lit/reactive-element" "^1.6.0"
- lit-element "^3.3.0"
- lit-html "^2.8.0"
+ "@lit/reactive-element" "^2.0.0"
+ lit-element "^4.0.0"
+ lit-html "^3.0.0"
lru-cache@^6.0.0:
version "6.0.0"
diff --git a/polygerrit-ui/web-test-runner.config.mjs b/polygerrit-ui/web-test-runner.config.mjs
index bd8d9ac..415571c 100644
--- a/polygerrit-ui/web-test-runner.config.mjs
+++ b/polygerrit-ui/web-test-runner.config.mjs
@@ -2,8 +2,7 @@
import { defaultReporter, summaryReporter } from "@web/test-runner";
import { visualRegressionPlugin } from "@web/test-runner-visual-regression/plugin";
-function testRunnerHtmlFactory(options) {
- const setNewDiffExp = `<script type="text/javascript">window.ENABLED_EXPERIMENTS = ['UiFeature__new_diff'];</script>`;
+function testRunnerHtmlFactory() {
return (testFramework) => `
<!DOCTYPE html>
<html>
@@ -15,7 +14,6 @@
href="polygerrit-ui/app/styles/material-icons.css">
</head>
<body>
- ${options.newDiff ? setNewDiffExp : ''}
<script type="module" src="${testFramework}"></script>
</body>
</html>
@@ -26,34 +24,11 @@
const config = {
files: [
"app/**/*_test.{ts,js}",
- "!app/embed/diff/gr-context-controls/**/*_test.{ts,js}",
- "!app/embed/diff/gr-diff/**/*_test.{ts,js}",
- "!app/embed/diff/gr-diff-builder/**/*_test.{ts,js}",
- "!app/embed/diff/gr-diff-cursor/**/*_test.{ts,js}",
- "!app/embed/diff/gr-diff-highlight/**/*_test.{ts,js}",
- "!app/embed/diff/gr-diff-model/**/*_test.{ts,js}",
- "!app/embed/diff/gr-diff-processor/**/*_test.{ts,js}",
- "!app/embed/diff/gr-diff-selection/**/*_test.{ts,js}",
"!**/node_modules/**/*",
...(process.argv.includes("--run-screenshots")
? []
: ["!app/**/*_screenshot_test.{ts,js}"]),
],
- // TODO(newdiff-cleanup): Remove once newdiff migration is completed.
- groups: [
- {
- name: "new-diff",
- files: [
- "app/embed/diff/**/*_test.{ts,js}",
- "app/elements/change/gr-file-list/gr-file-list_test.{ts,js}",
- "app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.{ts,js}",
- "app/elements/diff/gr-diff-host/gr-diff-host_test.{ts,js}",
- "app/elements/diff/gr-diff-view/gr-diff-view_test.{ts,js}",
- "app/elements/shared/gr-comment-thread/gr-comment-thread_test.{ts,js}",
- ],
- testRunnerHtml: testRunnerHtmlFactory({newDiff: true}),
- },
- ],
port: 9876,
nodeResolve: true,
testFramework: { config: { ui: "tdd", timeout: 5000 } },
@@ -85,6 +60,6 @@
await next();
},
],
- testRunnerHtml: testRunnerHtmlFactory({newDiff: false}),
+ testRunnerHtml: testRunnerHtmlFactory(),
};
export default config;
diff --git a/polygerrit-ui/yarn.lock b/polygerrit-ui/yarn.lock
index 751b807..6e8259c 100644
--- a/polygerrit-ui/yarn.lock
+++ b/polygerrit-ui/yarn.lock
@@ -1749,13 +1749,20 @@
"@types/chai" "*"
"@types/sinon" "*"
-"@types/sinon@*", "@types/sinon@^10.0.15":
+"@types/sinon@*":
version "10.0.16"
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.16.tgz#4bf10313bd9aa8eef1e50ec9f4decd3dd455b4d3"
integrity sha512-j2Du5SYpXZjJVJtXBokASpPRj+e2z+VUhCPHmM6WMfe3dpHu6iVKJMU6AiBcMp/XTAYnEj6Wc1trJUWwZ0QaAQ==
dependencies:
"@types/sinonjs__fake-timers" "*"
+"@types/sinon@^10.0.16":
+ version "10.0.20"
+ resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.20.tgz#f1585debf4c0d99f9938f4111e5479fb74865146"
+ integrity sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg==
+ dependencies:
+ "@types/sinonjs__fake-timers" "*"
+
"@types/sinonjs__fake-timers@*":
version "8.1.2"
resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz#bf2e02a3dbd4aecaf95942ecd99b7402e03fad5e"
diff --git a/proto/entities.proto b/proto/entities.proto
index 372426a..3412291 100644
--- a/proto/entities.proto
+++ b/proto/entities.proto
@@ -172,7 +172,7 @@
// Proto representation of the User preferences classes
// Next ID: 4
message UserPreferences {
- // Next ID: 23
+ // Next ID: 24
message GeneralPreferencesInfo {
// Number of changes to show in a screen.
optional int32 changes_per_page = 1 [default = 25];
@@ -251,6 +251,7 @@
repeated string change_table = 18;
optional bool allow_browser_notifications = 19 [default = true];
+ optional string diff_page_sidebar = 23 [default = "NONE"];
}
optional GeneralPreferencesInfo general_preferences_info = 1;
diff --git a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
index b589279..dbfef44 100644
--- a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
+++ b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
@@ -48,6 +48,14 @@
// Disable extra font load from paper-styles
window.polymerSkipLoadingFontRoboto = true;
window.CLOSURE_NO_DEPS = true;
+ window.DEFAULT_DETAIL_HEXES = {lb}
+ {if $defaultChangeDetailHex}
+ changePage: '{$defaultChangeDetailHex}',
+ {/if}
+ {if $defaultDashboardHex}
+ dashboardPage: '{$defaultDashboardHex}',
+ {/if}
+ {rb};
window.PRELOADED_QUERIES = {lb}
{if $userIsAuthenticated and $defaultDashboardHex and $dashboardQuery}
dashboardQuery: [{for $query in $dashboardQuery}{$query},{/for}],
diff --git a/tools/BUILD b/tools/BUILD
index c6149a5..01f1a8f 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -202,7 +202,7 @@
"-Xep:InjectOnConstructorOfAbstractClass:ERROR",
"-Xep:InheritDoc:ERROR",
"-Xep:InlineFormatString:ERROR",
- "-Xep:InlineMeInliner:ERROR",
+ "-Xep:InlineMeInliner:WARN",
"-Xep:InlineMeSuggester:ERROR",
"-Xep:InlineMeValidator:ERROR",
"-Xep:InputStreamSlowMultibyteRead:ERROR",
diff --git a/tools/deps.bzl b/tools/deps.bzl
index 4293d7c..f6ee1d8 100644
--- a/tools/deps.bzl
+++ b/tools/deps.bzl
@@ -3,7 +3,6 @@
CAFFEINE_VERS = "2.9.2"
ANTLR_VERS = "3.5.2"
COMMONMARK_VERSION = "0.21.0"
-FLEXMARK_VERS = "0.64.0"
GREENMAIL_VERS = "1.5.5"
MAIL_VERS = "1.6.0"
MIME4J_VERS = "0.8.1"
@@ -14,7 +13,7 @@
AUTO_VALUE_GSON_VERSION = "1.3.1"
PROLOG_VERS = "1.4.4"
PROLOG_REPO = GERRIT
-GITILES_VERS = "1.1.0"
+GITILES_VERS = "1.3.0"
GITILES_REPO = GERRIT
# When updating Bouncy Castle, also update it in bazlets.
@@ -68,6 +67,12 @@
sha1 = "3cd63d075497751784b2fa84be59432f4905bf7c",
)
+ maven_jar(
+ name = "jakarta-inject-api",
+ artifact = "jakarta.inject:jakarta.inject-api:2.0.1",
+ sha1 = "4c28afe1991a941d7702fe1362c365f0a8641d1e",
+ )
+
# JGit's transitive dependencies
maven_jar(
@@ -197,178 +202,10 @@
)
maven_jar(
- name = "flexmark",
- artifact = "com.vladsch.flexmark:flexmark:" + FLEXMARK_VERS,
- sha1 = "bb5fcdf1335a35c4c0285fee2683a32e6a70cd59",
- )
-
- maven_jar(
- name = "flexmark-ext-abbreviation",
- artifact = "com.vladsch.flexmark:flexmark-ext-abbreviation:" + FLEXMARK_VERS,
- sha1 = "48a64d5d1e5d1e0f8efde75f051b16800952eeb3",
- )
-
- maven_jar(
- name = "flexmark-ext-anchorlink",
- artifact = "com.vladsch.flexmark:flexmark-ext-anchorlink:" + FLEXMARK_VERS,
- sha1 = "e263a138896153bad6b69828d65c409fd99292a8",
- )
-
- maven_jar(
- name = "flexmark-ext-autolink",
- artifact = "com.vladsch.flexmark:flexmark-ext-autolink:" + FLEXMARK_VERS,
- sha1 = "e380ee48d5a779d08f071aaaf9a69e5985f24802",
- )
-
- maven_jar(
- name = "flexmark-ext-definition",
- artifact = "com.vladsch.flexmark:flexmark-ext-definition:" + FLEXMARK_VERS,
- sha1 = "892d83daafb1255489377a4331c6b04cd9fef288",
- )
-
- maven_jar(
- name = "flexmark-ext-emoji",
- artifact = "com.vladsch.flexmark:flexmark-ext-emoji:" + FLEXMARK_VERS,
- sha1 = "63777b6b82456fb08587537db949c67ac6ac5d66",
- )
-
- maven_jar(
- name = "flexmark-ext-escaped-character",
- artifact = "com.vladsch.flexmark:flexmark-ext-escaped-character:" + FLEXMARK_VERS,
- sha1 = "19b48848dd0d242c8dd527113cf91711847cf7f1",
- )
-
- maven_jar(
- name = "flexmark-ext-footnotes",
- artifact = "com.vladsch.flexmark:flexmark-ext-footnotes:" + FLEXMARK_VERS,
- sha1 = "a885e4c512f6f73b3ea02a26f71e0116e720b33e",
- )
-
- maven_jar(
- name = "flexmark-ext-gfm-issues",
- artifact = "com.vladsch.flexmark:flexmark-ext-gfm-issues:" + FLEXMARK_VERS,
- sha1 = "f1f2e840cd295bfaad006e7bddb715edfd747e44",
- )
-
- maven_jar(
- name = "flexmark-ext-gfm-strikethrough",
- artifact = "com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:" + FLEXMARK_VERS,
- sha1 = "9e4d01143752ebe98d45ac29c77e17fa8cc8b947",
- )
-
- maven_jar(
- name = "flexmark-ext-gfm-tasklist",
- artifact = "com.vladsch.flexmark:flexmark-ext-gfm-tasklist:" + FLEXMARK_VERS,
- sha1 = "ad5d3dda8a17100ee01606386aacb73d41e35764",
- )
-
- maven_jar(
- name = "flexmark-ext-gfm-users",
- artifact = "com.vladsch.flexmark:flexmark-ext-gfm-users:" + FLEXMARK_VERS,
- sha1 = "59846560518f6ef5282d333e1ba1a68dc5ea8c43",
- )
-
- maven_jar(
- name = "flexmark-ext-ins",
- artifact = "com.vladsch.flexmark:flexmark-ext-ins:" + FLEXMARK_VERS,
- sha1 = "b5a7e72e337d31e2956f662b37ec1f47289e7751",
- )
-
- maven_jar(
- name = "flexmark-ext-jekyll-front-matter",
- artifact = "com.vladsch.flexmark:flexmark-ext-jekyll-front-matter:" + FLEXMARK_VERS,
- sha1 = "5543c826f325444edacc6920992753568aa046f6",
- )
-
- maven_jar(
- name = "flexmark-ext-superscript",
- artifact = "com.vladsch.flexmark:flexmark-ext-superscript:" + FLEXMARK_VERS,
- sha1 = "bcee70d603fb7acf9ef490036b14a5c1cb3f533c",
- )
-
- maven_jar(
- name = "flexmark-ext-tables",
- artifact = "com.vladsch.flexmark:flexmark-ext-tables:" + FLEXMARK_VERS,
- sha1 = "52b829753bb928cd3f26c43d39639eb95b3f9f88",
- )
-
- maven_jar(
- name = "flexmark-ext-toc",
- artifact = "com.vladsch.flexmark:flexmark-ext-toc:" + FLEXMARK_VERS,
- sha1 = "69b96da85758688a564babea371fc9268a11d718",
- )
-
- maven_jar(
- name = "flexmark-ext-typographic",
- artifact = "com.vladsch.flexmark:flexmark-ext-typographic:" + FLEXMARK_VERS,
- sha1 = "5e2cb29da6c43eecffbd34a4f69976f61a383e97",
- )
-
- maven_jar(
- name = "flexmark-ext-wikilink",
- artifact = "com.vladsch.flexmark:flexmark-ext-wikilink:" + FLEXMARK_VERS,
- sha1 = "5ada81fecf0d68cdcd5c763da0f5f22aa885d559",
- )
-
- maven_jar(
- name = "flexmark-ext-yaml-front-matter",
- artifact = "com.vladsch.flexmark:flexmark-ext-yaml-front-matter:" + FLEXMARK_VERS,
- sha1 = "377b29278cad7603aa3ca705c31a9f48cb6f8fb9",
- )
-
- maven_jar(
- name = "flexmark-profile-pegdown",
- artifact = "com.vladsch.flexmark:flexmark-profile-pegdown:" + FLEXMARK_VERS,
- sha1 = "c67446ffd3653416ed26f924ba99f8125fb74791",
- )
-
- maven_jar(
- name = "flexmark-util-data",
- artifact = "com.vladsch.flexmark:flexmark-util-data:" + FLEXMARK_VERS,
+ name = "flexmark-all-lib",
+ artifact = "com.vladsch.flexmark:flexmark-all:0.64.0:lib",
attach_source = False,
- sha1 = "63162607faa7c98f6e50a9d1e826004c5475841d",
- )
-
- maven_jar(
- name = "flexmark-util-ast",
- artifact = "com.vladsch.flexmark:flexmark-util-ast:" + FLEXMARK_VERS,
- attach_source = False,
- sha1 = "10fff87e61b7c3bb12afddf7b418977cd02acdc8",
- )
-
- maven_jar(
- name = "flexmark-util-misc",
- artifact = "com.vladsch.flexmark:flexmark-util-misc:" + FLEXMARK_VERS,
- attach_source = False,
- sha1 = "d2129f4f2b55fbf645e3499c7b0cdddcfef81112",
- )
-
- maven_jar(
- name = "flexmark-util-visitor",
- artifact = "com.vladsch.flexmark:flexmark-util-visitor:" + FLEXMARK_VERS,
- attach_source = False,
- sha1 = "7eb0030ae2a2eec80d74790caeb4cdb6b4bf8e17",
- )
-
- maven_jar(
- name = "flexmark-util-builder",
- artifact = "com.vladsch.flexmark:flexmark-util-builder:" + FLEXMARK_VERS,
- attach_source = False,
- sha1 = "2d6adaf6053f72de86a050fe6967049f7c2d3500",
- )
-
- maven_jar(
- name = "flexmark-util-sequence",
- artifact = "com.vladsch.flexmark:flexmark-util-sequence:" + FLEXMARK_VERS,
- attach_source = False,
- sha1 = "299e3b4a4272ba9dcd6b9081b8d24d1a672a0921",
- )
-
- maven_jar(
- name = "flexmark-util-html",
- artifact = "com.vladsch.flexmark:flexmark-util-html:" + FLEXMARK_VERS,
- attach_source = False,
- sha1 = "fc52f860cf45f57468d8f14ee63c1e6469ee3f47",
+ sha1 = "de92cef20c1f61681a3c8f64dd5975fbd3125049",
)
# Transitive dependency of flexmark and gitiles
@@ -554,21 +391,14 @@
artifact = "com.google.gitiles:blame-cache:" + GITILES_VERS,
attach_source = False,
repository = GITILES_REPO,
- sha1 = "31c1a6e5d92b57bb2f9db24e1032145961c09a8d",
+ sha1 = "d0f5c98207648503b225501e84f529fa88651ebe",
)
maven_jar(
name = "gitiles-servlet",
artifact = "com.google.gitiles:gitiles-servlet:" + GITILES_VERS,
repository = GITILES_REPO,
- sha1 = "c6550362c5c22d8e07edd4e2151ee12594082e76",
- )
-
- # prettify must match the version used in Gitiles
- maven_jar(
- name = "prettify",
- artifact = "com.github.twalcari:java-prettify:1.2.2",
- sha1 = "b8ba1c1eb8b2e45cfd465d01218c6060e887572e",
+ sha1 = "b4ce5bc26e6a2674728d0d3c72c21e0b3443666d",
)
maven_jar(
diff --git a/tools/node_tools/package.json b/tools/node_tools/package.json
index afefbb2..bd4eb5c 100644
--- a/tools/node_tools/package.json
+++ b/tools/node_tools/package.json
@@ -9,15 +9,14 @@
"@types/node": "^10.17.12",
"@types/parse5": "^4.0.0",
"@types/parse5-html-rewriting-stream": "^5.1.2",
- "crisper": "^2.1.1",
"dom5": "^3.0.1",
"parse5-html-rewriting-stream": "^5.1.1",
- "polymer-bundler": "^4.0.10",
- "rollup": "^2.3.4",
+ "rollup": "^2.79.1",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-define": "^1.0.1",
"rollup-plugin-node-resolve": "^5.2.0",
- "rollup-plugin-terser": "^5.1.3",
+ "rollup-plugin-terser": "^7.0.2",
+ "terser": "~5.8.0",
"typescript": "^4.9.5"
},
"devDependencies": {},
@@ -28,4 +27,4 @@
"wct-local": "2.1.6",
"launchpad": "git+https://github.com/418sec/launchpad.git#de5aca11dc16a8e530195281c77614bdbb08e7be"
}
-}
+}
\ No newline at end of file
diff --git a/tools/node_tools/yarn.lock b/tools/node_tools/yarn.lock
index 92afd0d..9f3cada 100644
--- a/tools/node_tools/yarn.lock
+++ b/tools/node_tools/yarn.lock
@@ -2,101 +2,28 @@
# yarn lockfile v1
-"@babel/code-frame@^7.16.7", "@babel/code-frame@^7.5.5":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
- integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==
+"@babel/code-frame@^7.10.4":
+ version "7.22.13"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
+ integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
dependencies:
- "@babel/highlight" "^7.16.7"
+ "@babel/highlight" "^7.22.13"
+ chalk "^2.4.2"
-"@babel/generator@^7.0.0-beta.42", "@babel/generator@^7.18.2":
- version "7.18.2"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d"
- integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==
+"@babel/helper-validator-identifier@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
+ integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
+
+"@babel/highlight@^7.22.13":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
+ integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
dependencies:
- "@babel/types" "^7.18.2"
- "@jridgewell/gen-mapping" "^0.3.0"
- jsesc "^2.5.1"
-
-"@babel/helper-environment-visitor@^7.18.2":
- version "7.18.2"
- resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd"
- integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==
-
-"@babel/helper-function-name@^7.17.9":
- version "7.17.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12"
- integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==
- dependencies:
- "@babel/template" "^7.16.7"
- "@babel/types" "^7.17.0"
-
-"@babel/helper-hoist-variables@^7.16.7":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246"
- integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==
- dependencies:
- "@babel/types" "^7.16.7"
-
-"@babel/helper-split-export-declaration@^7.16.7":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b"
- integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==
- dependencies:
- "@babel/types" "^7.16.7"
-
-"@babel/helper-validator-identifier@^7.16.7":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad"
- integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==
-
-"@babel/highlight@^7.16.7":
- version "7.17.12"
- resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.12.tgz#257de56ee5afbd20451ac0a75686b6b404257351"
- integrity sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==
- dependencies:
- "@babel/helper-validator-identifier" "^7.16.7"
- chalk "^2.0.0"
+ "@babel/helper-validator-identifier" "^7.22.20"
+ chalk "^2.4.2"
js-tokens "^4.0.0"
-"@babel/parser@^7.16.7", "@babel/parser@^7.18.0":
- version "7.18.4"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.4.tgz#6774231779dd700e0af29f6ad8d479582d7ce5ef"
- integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==
-
-"@babel/template@^7.16.7":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
- integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==
- dependencies:
- "@babel/code-frame" "^7.16.7"
- "@babel/parser" "^7.16.7"
- "@babel/types" "^7.16.7"
-
-"@babel/traverse@^7.0.0-beta.42":
- version "7.18.2"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.2.tgz#b77a52604b5cc836a9e1e08dca01cba67a12d2e8"
- integrity sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA==
- dependencies:
- "@babel/code-frame" "^7.16.7"
- "@babel/generator" "^7.18.2"
- "@babel/helper-environment-visitor" "^7.18.2"
- "@babel/helper-function-name" "^7.17.9"
- "@babel/helper-hoist-variables" "^7.16.7"
- "@babel/helper-split-export-declaration" "^7.16.7"
- "@babel/parser" "^7.18.0"
- "@babel/types" "^7.18.2"
- debug "^4.1.0"
- globals "^11.1.0"
-
-"@babel/types@^7.0.0-beta.42", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.2":
- version "7.18.4"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354"
- integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==
- dependencies:
- "@babel/helper-validator-identifier" "^7.16.7"
- to-fast-properties "^2.0.0"
-
"@bazel/concatjs@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@bazel/concatjs/-/concatjs-5.5.0.tgz#e6104ed70595cae59463ae6b0b5389252566221e"
@@ -132,36 +59,44 @@
google-protobuf "^3.6.1"
"@jridgewell/gen-mapping@^0.3.0":
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz#cf92a983c83466b8c0ce9124fadeaf09f7c66ea9"
- integrity sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
+ integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
dependencies:
- "@jridgewell/set-array" "^1.0.0"
+ "@jridgewell/set-array" "^1.0.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
-"@jridgewell/resolve-uri@^3.0.3":
- version "3.0.7"
- resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe"
- integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==
+"@jridgewell/resolve-uri@^3.1.0":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
+ integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
-"@jridgewell/set-array@^1.0.0":
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea"
- integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==
+"@jridgewell/set-array@^1.0.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
+ integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
-"@jridgewell/sourcemap-codec@^1.4.10":
- version "1.4.13"
- resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c"
- integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==
+"@jridgewell/source-map@^0.3.3":
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91"
+ integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
+ version "1.4.15"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+ integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@jridgewell/trace-mapping@^0.3.9":
- version "0.3.13"
- resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea"
- integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==
+ version "0.3.20"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f"
+ integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==
dependencies:
- "@jridgewell/resolve-uri" "^3.0.3"
- "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2"
@@ -236,37 +171,6 @@
dependencies:
defer-to-connect "^2.0.0"
-"@types/babel-generator@^6.25.1":
- version "6.25.5"
- resolved "https://registry.yarnpkg.com/@types/babel-generator/-/babel-generator-6.25.5.tgz#b02723fd589349b05524376e5530228d3675d878"
- integrity sha512-lhbwMlAy5rfWG+R6l8aPtJdEFX/kcv6LMFIuvUb0i89ehqgD24je9YcB+0fRspQhgJGlEsUImxpw4pQeKS/+8Q==
- dependencies:
- "@types/babel-types" "*"
-
-"@types/babel-traverse@^6.25.2", "@types/babel-traverse@^6.25.3":
- version "6.25.7"
- resolved "https://registry.yarnpkg.com/@types/babel-traverse/-/babel-traverse-6.25.7.tgz#bc75fce23d8394534562a36a32dec94a54d11835"
- integrity sha512-BeQiEGLnVzypzBdsexEpZAHUx+WucOMXW6srEWDkl4SegBlaCy+iBvRO+4vz6EZ+BNQg22G4MCdDdvZxf+jW5A==
- dependencies:
- "@types/babel-types" "*"
-
-"@types/babel-types@*":
- version "7.0.11"
- resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.11.tgz#263b113fa396fac4373188d73225297fb86f19a9"
- integrity sha512-pkPtJUUY+Vwv6B1inAz55rQvivClHJxc9aVEPPmaq2cbyeMLCiDpbKpcKyX4LAwpNGi+SHBv0tHv6+0gXv0P2A==
-
-"@types/babel-types@^6.25.1":
- version "6.25.2"
- resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-6.25.2.tgz#5c57f45973e4f13742dbc5273dd84cffe7373a9e"
- integrity sha512-+3bMuktcY4a70a0KZc8aPJlEOArPuAKQYHU5ErjkOqGJdx8xuEEVK6nWogqigBOJ8nKPxRpyCUDTCPmZ3bUxGA==
-
-"@types/babylon@^6.16.2":
- version "6.16.6"
- resolved "https://registry.yarnpkg.com/@types/babylon/-/babylon-6.16.6.tgz#a1e7e01567b26a5ebad321a74d10299189d8d932"
- integrity sha512-G4yqdVlhr6YhzLXFKy5F7HtRBU8Y23+iWy7UKthMq/OSQnL1hbsoeXESQ2LY8zEDlknipDG3nRGhUC9tkwvy/w==
- dependencies:
- "@types/babel-types" "*"
-
"@types/body-parser@*":
version "1.19.2"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0"
@@ -285,28 +189,6 @@
"@types/node" "*"
"@types/responselike" "*"
-"@types/chai-subset@^1.3.0":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94"
- integrity sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==
- dependencies:
- "@types/chai" "*"
-
-"@types/chai@*":
- version "4.3.1"
- resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.1.tgz#e2c6e73e0bdeb2521d00756d099218e9f5d90a04"
- integrity sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==
-
-"@types/chalk@^0.4.30":
- version "0.4.31"
- resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-0.4.31.tgz#a31d74241a6b1edbb973cf36d97a2896834a51f9"
- integrity sha512-nF0fisEPYMIyfrFgabFimsz9Lnuu9MwkNrrlATm2E4E46afKDyeelT+8bXfw1VSc7sLBxMxRgT7PxTC2JcqN4Q==
-
-"@types/clone@^0.1.29", "@types/clone@^0.1.30":
- version "0.1.30"
- resolved "https://registry.yarnpkg.com/@types/clone/-/clone-0.1.30.tgz#e7365648c1b42136a59c7d5040637b3b5c83b614"
- integrity sha512-vcxBr+ybljeSiasmdke1cQ9ICxoEwaBgM1OQ/P5h4MPj/kRyLcDl5L8PrftlbyV1kBbJIs3M3x1A1+rcWd4mEA==
-
"@types/connect@*":
version "3.4.35"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
@@ -314,16 +196,6 @@
dependencies:
"@types/node" "*"
-"@types/cssbeautify@^0.3.1":
- version "0.3.2"
- resolved "https://registry.yarnpkg.com/@types/cssbeautify/-/cssbeautify-0.3.2.tgz#8a76207cd980d3e7b29b4b6dea1f4ed861285615"
- integrity sha512-b3PXlFAcS4gvGr2pDz0NoZEBo3MMQe8Ozy6+Mvm3XIEcHS4oQstvCnnCofBZD/0tQgxSzkYbW+cD3yD4yaKTxQ==
-
-"@types/doctrine@^0.0.1":
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/@types/doctrine/-/doctrine-0.0.1.tgz#b999f2d9f7b43cabe0a1a2f39bc203bc7dcada9d"
- integrity sha512-iN9ewNbXmuWLOAB3wk/YpCqIBWK3wBNE1D/4u+jA/GyrqsE4r3ozbpS5F0fr0tIYmmnqhbVvT9OOXzt+vw+LDg==
-
"@types/estree@*":
version "0.0.51"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
@@ -358,11 +230,6 @@
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812"
integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==
-"@types/is-windows@^0.2.0":
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/@types/is-windows/-/is-windows-0.2.0.tgz#6f24ee48731d31168ea510610d6dd15e5fc9c6ff"
- integrity sha512-xuK4kuYgV6/auME6nVp78i9B22jBUYZUCTl64fpJ3O7qWRxK5uRya5yrkBAlSU17k3EVf0DwT7NUjCo5wZD8OA==
-
"@types/json-buffer@~3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/json-buffer/-/json-buffer-3.0.0.tgz#85c1ff0f0948fc159810d4b5be35bf8c20875f64"
@@ -390,31 +257,16 @@
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
-"@types/minimatch@^3.0.1":
- version "3.0.5"
- resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
- integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==
-
"@types/node@*":
version "17.0.38"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.38.tgz#f8bb07c371ccb1903f3752872c89f44006132947"
integrity sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g==
-"@types/node@6.0.*":
- version "6.0.118"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.118.tgz#8014a9b1dee0b72b4d7cd142563f1af21241c3a2"
- integrity sha512-N33cKXGSqhOYaPiT4xUGsYlPPDwFtQM/6QxJxuMXA/7BcySW+lkn2yigWP7vfs4daiL/7NJNU6DMCqg5N4B+xQ==
-
"@types/node@^10.1.0", "@types/node@^10.17.12":
version "10.17.60"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
-"@types/node@^4.0.30":
- version "4.9.5"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-4.9.5.tgz#a3785db96b07a4b56466cc99fd624838746f2e25"
- integrity sha512-+8fpgbXsbATKRF2ayAlYhPl2E9MPdLjrnK/79ZEpyPJ+k7dZwJm9YM8FK+l4rqL//xHk7PgQhGwz6aA2ckxbCQ==
-
"@types/parse5-html-rewriting-stream@^5.1.2":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@types/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-5.1.2.tgz#919d5bbf69ef61e11d873e7195891c3811491a03"
@@ -435,13 +287,6 @@
resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb"
integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==
-"@types/parse5@^0.0.31":
- version "0.0.31"
- resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-0.0.31.tgz#e827a493a443b156e1b582a2e4c3bdc0040f2ee7"
- integrity sha512-W9yKi+ZkSypS/6SXd0ebArnPxg5mwSAdmLqlJX+boeu845j3WVaYSJjqIg0i8Rh5btq7KytgIcta2KJB1aS4Mw==
- dependencies:
- "@types/node" "6.0.*"
-
"@types/parse5@^2.2.34":
version "2.2.34"
resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-2.2.34.tgz#e3870a10e82735a720f62d71dcd183ba78ef3a9d"
@@ -456,11 +301,6 @@
dependencies:
"@types/node" "*"
-"@types/path-is-inside@^1.0.0":
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/@types/path-is-inside/-/path-is-inside-1.0.0.tgz#02d6ff38975d684bdec96204494baf9f29f0e17f"
- integrity sha512-hfnXRGugz+McgX2jxyy5qz9sB21LRzlGn24zlwN2KEgoPtEvjzNRrLtUkOOebPDPZl3Rq7ywKxYvylVcEZDnEw==
-
"@types/qs@*":
version "6.9.7"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
@@ -471,13 +311,6 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
-"@types/resolve@0.0.6":
- version "0.0.6"
- resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.6.tgz#0bd2f236c2e1cebb98b79885df57edd71a8d770e"
- integrity sha512-g+Rg8uMWY76oYTyaL+m7ZcblqF/oj7pE6uEUyACluJx4zcop1Lk14qQiocdEkEVMDFm6DmKpxJhsER+ZuTwG3g==
- dependencies:
- "@types/node" "*"
-
"@types/resolve@0.0.8":
version "0.0.8"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194"
@@ -500,39 +333,15 @@
"@types/mime" "^1"
"@types/node" "*"
-"@types/whatwg-url@^6.4.0":
- version "6.4.0"
- resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-6.4.0.tgz#1e59b8c64bc0dbdf66d037cf8449d1c3d5270237"
- integrity sha512-tonhlcbQ2eho09am6RHnHOgvtDfDYINd5rgxD+2YSkKENooVCFsWizJz139MQW/PV8FfClyKrNe9ZbdHrSCxGg==
- dependencies:
- "@types/node" "*"
-
"@types/which@^1.3.1":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@types/which/-/which-1.3.2.tgz#9c246fc0c93ded311c8512df2891fb41f6227fdf"
integrity sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==
-acorn-jsx@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
- integrity sha512-AU7pnZkguthwBjKgCg6998ByQNIMjbuDQZ8bb78QAFZwPfmKia8AIzgY/gWgqCjnht8JLdXmB4YxA0KaV60ncQ==
- dependencies:
- acorn "^3.0.4"
-
-acorn@^3.0.4:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
- integrity sha512-OLUyIIZ7mF5oaAUT1w0TFqQS81q3saT46x8t7ukpPjMNk+nbs4ZHhs7ToV8EWnLYLepjETXd4XaCE4uxkMeqUw==
-
-acorn@^5.5.0:
- version "5.7.4"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
- integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
-
-acorn@^7.1.0:
- version "7.4.1"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
- integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
+acorn@^8.8.2:
+ version "8.10.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
+ integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
agent-base@^4.3.0:
version "4.3.0"
@@ -541,23 +350,6 @@
dependencies:
es6-promisify "^5.0.0"
-ansi-escape-sequences@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/ansi-escape-sequences/-/ansi-escape-sequences-3.0.0.tgz#1c18394b6af9b76ff9a63509fa497669fd2ce53e"
- integrity sha512-nOj2mwGB2lJzx9YDqaiI77vYh4SWcOCTday6kdtx6ojUk1s1HqSiK604UIq8jlBVC0UBsX7Bph3SfOf9QsJerA==
- dependencies:
- array-back "^1.0.3"
-
-ansi-regex@^2.0.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
- integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==
-
-ansi-styles@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
- integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==
-
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@@ -565,25 +357,6 @@
dependencies:
color-convert "^1.9.0"
-array-back@^1.0.3, array-back@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/array-back/-/array-back-1.0.4.tgz#644ba7f095f7ffcf7c43b5f0dc39d3c1f03c063b"
- integrity sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==
- dependencies:
- typical "^2.6.0"
-
-array-back@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/array-back/-/array-back-2.0.0.tgz#6877471d51ecc9c9bfa6136fb6c7d5fe69748022"
- integrity sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==
- dependencies:
- typical "^2.6.1"
-
-array-back@^3.0.1, array-back@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0"
- integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==
-
ast-matcher@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ast-matcher/-/ast-matcher-1.1.1.tgz#95a6dc72318319507024fff438b7839e4e280813"
@@ -596,79 +369,6 @@
dependencies:
lodash "^4.17.14"
-babel-code-frame@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
- integrity sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==
- dependencies:
- chalk "^1.1.3"
- esutils "^2.0.2"
- js-tokens "^3.0.2"
-
-babel-generator@^6.26.1:
- version "6.26.1"
- resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
- integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==
- dependencies:
- babel-messages "^6.23.0"
- babel-runtime "^6.26.0"
- babel-types "^6.26.0"
- detect-indent "^4.0.0"
- jsesc "^1.3.0"
- lodash "^4.17.4"
- source-map "^0.5.7"
- trim-right "^1.0.1"
-
-babel-messages@^6.23.0:
- version "6.23.0"
- resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
- integrity sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==
- dependencies:
- babel-runtime "^6.22.0"
-
-babel-runtime@^6.22.0, babel-runtime@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
- integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==
- dependencies:
- core-js "^2.4.0"
- regenerator-runtime "^0.11.0"
-
-babel-traverse@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
- integrity sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==
- dependencies:
- babel-code-frame "^6.26.0"
- babel-messages "^6.23.0"
- babel-runtime "^6.26.0"
- babel-types "^6.26.0"
- babylon "^6.18.0"
- debug "^2.6.8"
- globals "^9.18.0"
- invariant "^2.2.2"
- lodash "^4.17.4"
-
-babel-types@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
- integrity sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==
- dependencies:
- babel-runtime "^6.26.0"
- esutils "^2.0.2"
- lodash "^4.17.4"
- to-fast-properties "^1.0.3"
-
-babylon@^6.18.0:
- version "6.18.0"
- resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
- integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
-
-babylon@^7.0.0-beta.42:
- version "7.0.0-beta.47"
- resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.47.tgz#6d1fa44f0abec41ab7c780481e62fd9aafbdea80"
- integrity sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==
-
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@@ -749,25 +449,7 @@
normalize-url "^6.0.1"
responselike "^2.0.0"
-cancel-token@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/cancel-token/-/cancel-token-0.1.1.tgz#c18197674bb1c84c1d6933ebf15d8d5a5ce79b4f"
- integrity sha512-22DV8aB/ovjL6l9S+QLwFzyP5+azENgfNywoJffIE8ZNx2Nnz7UlJ0mEULTtaeuf+tmnvaUdN6WKtV1LTBlbuA==
- dependencies:
- "@types/node" "^4.0.30"
-
-chalk@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
- integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==
- dependencies:
- ansi-styles "^2.2.1"
- escape-string-regexp "^1.0.2"
- has-ansi "^2.0.0"
- strip-ansi "^3.0.0"
- supports-color "^2.0.0"
-
-chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1:
+chalk@^2.3.0, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -788,12 +470,7 @@
dependencies:
mimic-response "^1.0.0"
-clone@^1.0.2:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
- integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
-
-clone@^2.0.0, clone@^2.1.0:
+clone@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
@@ -810,47 +487,6 @@
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
-command-line-args@^3.0.1:
- version "3.0.5"
- resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-3.0.5.tgz#5bd4ad45e7983e5c1344918e40280ee2693c5ac0"
- integrity sha512-M29kjOI24VF4HqatnqVyDqyeq3SYYZbq6LWv/AdVZ5LvrcqVNSN2XeYPrBxcO19T8YkGmyCqTUqYR07DFjVhyg==
- dependencies:
- array-back "^1.0.4"
- feature-detect-es6 "^1.3.1"
- find-replace "^1.0.2"
- typical "^2.6.0"
-
-command-line-args@^5.0.2:
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e"
- integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==
- dependencies:
- array-back "^3.1.0"
- find-replace "^3.0.0"
- lodash.camelcase "^4.3.0"
- typical "^4.0.0"
-
-command-line-usage@^3.0.8:
- version "3.0.8"
- resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-3.0.8.tgz#b6a20978c1b383477f5c11a529428b880bfe0f4d"
- integrity sha512-KMWPF8wNWa+wzffE9247hlDB1c9DMMxhwIFzwRn7oNv5CU7auuJ3zKWv756F/9qqlEucC5jI8/3S8qdGKdVelw==
- dependencies:
- ansi-escape-sequences "^3.0.0"
- array-back "^1.0.3"
- feature-detect-es6 "^1.3.1"
- table-layout "^0.3.0"
- typical "^2.6.0"
-
-command-line-usage@^5.0.5:
- version "5.0.5"
- resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-5.0.5.tgz#5f25933ffe6dedd983c635d38a21d7e623fda357"
- integrity sha512-d8NrGylA5oCXSbGoKz05FkehDAzSmIm4K03S5VDh4d5lZAtTWfc3D1RuETtuQCn8129nYfJfDdF7P/lwcz1BlA==
- dependencies:
- array-back "^2.0.0"
- chalk "^2.4.1"
- table-layout "^0.4.3"
- typical "^2.6.1"
-
commander@^2.20.0, commander@^2.20.3:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@@ -869,20 +505,6 @@
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
-core-js@^2.4.0, core-js@^2.4.1:
- version "2.6.12"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
- integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
-
-crisper@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/crisper/-/crisper-2.1.1.tgz#4cc7321c3e90f3c5cbdc3503217f118fd7d5c51c"
- integrity sha512-yxfj9nTbFunDASztAxVF8hCPwaZBvTjayNzG3YL/VVQfQaKBXX2+TM3p1xB1Pxd8RYeDQJkJIQRwM3FQSIa+pw==
- dependencies:
- command-line-args "^3.0.1"
- command-line-usage "^3.0.8"
- dom5 "^1.0.1"
-
cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -892,12 +514,7 @@
shebang-command "^2.0.0"
which "^2.0.1"
-cssbeautify@^0.3.1:
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/cssbeautify/-/cssbeautify-0.3.1.tgz#12dd1f734035c2e6faca67dcbdcef74e42811397"
- integrity sha512-ljnSOCOiMbklF+dwPbpooyB78foId02vUrTDogWzu6ca2DCNB7Kc/BHEGBnYOlUYtwXvSW0mWTwaiO2pwFIoRg==
-
-debug@^2.2.0, debug@^2.6.8:
+debug@^2.2.0:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -911,7 +528,7 @@
dependencies:
ms "^2.1.1"
-debug@^4.1.0, debug@^4.3.1:
+debug@^4.3.1:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -925,47 +542,12 @@
dependencies:
mimic-response "^3.1.0"
-deep-extend@~0.4.1:
- version "0.4.2"
- resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
- integrity sha512-cQ0iXSEKi3JRNhjUsLWvQ+MVPxLVqpwCd0cFsWbJxlCim2TlCo1JvN5WaPdPvSpUdEnkJ/X+mPGcq5RJ68EK8g==
-
-deep-extend@~0.6.0:
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
- integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
-
defer-to-connect@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587"
integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==
-detect-indent@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
- integrity sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==
- dependencies:
- repeating "^2.0.0"
-
-doctrine@^2.0.2:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
- integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==
- dependencies:
- esutils "^2.0.2"
-
-dom5@^1.0.1:
- version "1.3.6"
- resolved "https://registry.yarnpkg.com/dom5/-/dom5-1.3.6.tgz#a7088a9fc5f3b08dc9f6eda4c7abaeb241945e0d"
- integrity sha512-mcW8C3hP6NR7PD2mpa6cLihu0ToVrsloG69a/4vZ8lbKrAApEVJi99O2vqd5G1gfnvmLHbGSo/LdHbWBwdF4Rw==
- dependencies:
- "@types/clone" "^0.1.29"
- "@types/node" "^4.0.30"
- "@types/parse5" "^0.0.31"
- clone "^1.0.2"
- parse5 "^1.4.1"
-
-dom5@^3.0.0, dom5@^3.0.1:
+dom5@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dom5/-/dom5-3.0.1.tgz#cdfc7331f376e284bf379e6ea054afc136702944"
integrity sha512-JPFiouQIr16VQ4dX6i0+Hpbg3H2bMKPmZ+WZgBOSSvOPx9QHwwY8sPzeM2baUtViESYto6wC2nuZOMC/6gulcA==
@@ -993,7 +575,7 @@
dependencies:
es6-promise "^4.0.3"
-escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
@@ -1003,14 +585,6 @@
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
-espree@^3.5.2:
- version "3.5.4"
- resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7"
- integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==
- dependencies:
- acorn "^5.5.0"
- acorn-jsx "^3.0.0"
-
estree-walker@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
@@ -1021,11 +595,6 @@
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
-esutils@^2.0.2:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
- integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
-
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
@@ -1033,28 +602,6 @@
dependencies:
pend "~1.2.0"
-feature-detect-es6@^1.3.1:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/feature-detect-es6/-/feature-detect-es6-1.5.0.tgz#a69bb7662c65f64f89f07eac5a461b649a1e0a00"
- integrity sha512-DzWPIGzTnfp3/KK1d/YPfmgLqeDju9F2DQYBL35VusgSApcA7XGqVtXfR4ETOOFEzdFJ3J7zh0Gkk011TiA4uQ==
- dependencies:
- array-back "^1.0.4"
-
-find-replace@^1.0.2:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-1.0.3.tgz#b88e7364d2d9c959559f388c66670d6130441fa0"
- integrity sha512-KrUnjzDCD9426YnCP56zGYy/eieTnhtK6Vn++j+JJzmlsWWwEkDnsyVF575spT6HJ6Ow9tlbT3TQTDsa+O4UWA==
- dependencies:
- array-back "^1.0.4"
- test-value "^2.1.0"
-
-find-replace@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38"
- integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==
- dependencies:
- array-back "^3.0.1"
-
freeport@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/freeport/-/freeport-1.0.5.tgz#255e8ab84170c33ba85d990e821ae5f4a1a9bc5d"
@@ -1099,16 +646,6 @@
once "^1.3.0"
path-is-absolute "^1.0.0"
-globals@^11.1.0:
- version "11.12.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
- integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
-
-globals@^9.18.0:
- version "9.18.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
- integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
-
google-protobuf@^3.6.1:
version "3.20.1"
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.20.1.tgz#1b255c2b59bcda7c399df46c65206aa3c7a0ce8b"
@@ -1131,18 +668,16 @@
p-cancelable "^2.0.0"
responselike "^2.0.0"
-has-ansi@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
- integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==
- dependencies:
- ansi-regex "^2.0.0"
-
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
@@ -1176,11 +711,6 @@
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
-indent@0.0.2:
- version "0.0.2"
- resolved "https://registry.yarnpkg.com/indent/-/indent-0.0.2.tgz#8c79f080190559b687034b84c7aefa97d5a911d9"
- integrity sha512-/F1w9/msSQCfXDTvEU8rKBObcv4cBN6m8hujC/zwVc8vOuf4b76AwBVGChbg+3o0M3kp1XDjoMDQR5Nh6SAHfA==
-
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -1194,13 +724,6 @@
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
-invariant@^2.2.2:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
- integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
- dependencies:
- loose-envify "^1.0.0"
-
is-core-module@^2.8.1:
version "2.9.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
@@ -1208,11 +731,6 @@
dependencies:
has "^1.0.3"
-is-finite@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3"
- integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==
-
is-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
@@ -1225,54 +743,30 @@
dependencies:
"@types/estree" "*"
-is-windows@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
- integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
-
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
-jest-worker@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5"
- integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==
+jest-worker@^26.2.1:
+ version "26.6.2"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
+ integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==
dependencies:
+ "@types/node" "*"
merge-stream "^2.0.0"
- supports-color "^6.1.0"
+ supports-color "^7.0.0"
-"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
+js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
-js-tokens@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
- integrity sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==
-
-jsesc@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
- integrity sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==
-
-jsesc@^2.5.1:
- version "2.5.2"
- resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
- integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
-
json-buffer@3.0.1, json-buffer@~3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
-jsonschema@^1.1.0:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab"
- integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==
-
keyv@^4.0.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.3.0.tgz#b4352e0e4fe7c94111947d6738a6d3fe7903027c"
@@ -1294,11 +788,6 @@
rimraf "^3.0.0"
underscore "^1.8.3"
-lodash.camelcase@^4.3.0:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
- integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==
-
lodash.mapvalues@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
@@ -1309,17 +798,7 @@
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
-lodash.padend@^4.6.1:
- version "4.6.1"
- resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e"
- integrity sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==
-
-lodash.sortby@^4.7.0:
- version "4.7.0"
- resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
- integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==
-
-lodash@4.17.21, lodash@^4.17.14, lodash@^4.17.4:
+lodash@4.17.21, lodash@^4.17.14:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -1329,25 +808,11 @@
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
-loose-envify@^1.0.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
- integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
- dependencies:
- js-tokens "^3.0.0 || ^4.0.0"
-
lowercase-keys@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
-magic-string@^0.22.4:
- version "0.22.5"
- resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e"
- integrity sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==
- dependencies:
- vlq "^0.2.2"
-
magic-string@^0.25.2, magic-string@^0.25.7:
version "0.25.9"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
@@ -1370,7 +835,7 @@
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
-minimatch@^3.0.4, minimatch@^3.1.1:
+minimatch@^3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
@@ -1441,11 +906,6 @@
dependencies:
parse5 "^5.1.1"
-parse5@^1.4.1:
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94"
- integrity sha512-w2jx/0tJzvgKwZa58sj2vAYq/S/K1QJfIB3cWYea/Iu1scFPDQQ3IQiVZTHWtRBwAjv2Yd7S/xeZf3XqLDb3bA==
-
parse5@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
@@ -1461,11 +921,6 @@
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
-path-is-inside@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
- integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==
-
path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
@@ -1495,72 +950,6 @@
xmlbuilder "8.2.2"
xmldom "0.1.x"
-polymer-analyzer@^3.2.2:
- version "3.2.4"
- resolved "https://registry.yarnpkg.com/polymer-analyzer/-/polymer-analyzer-3.2.4.tgz#7d76356620a2328e8bc9e30e47069f9729260ca1"
- integrity sha512-JmxUhMajTuC18tLXbTtu2+aN2x9bTX+4MvCD4IZKJ0rtAL8jWi1iRLfogpHJB4Ig9Dc8EEEuEYipLuzPFl3vqA==
- dependencies:
- "@babel/generator" "^7.0.0-beta.42"
- "@babel/traverse" "^7.0.0-beta.42"
- "@babel/types" "^7.0.0-beta.42"
- "@types/babel-generator" "^6.25.1"
- "@types/babel-traverse" "^6.25.2"
- "@types/babel-types" "^6.25.1"
- "@types/babylon" "^6.16.2"
- "@types/chai-subset" "^1.3.0"
- "@types/chalk" "^0.4.30"
- "@types/clone" "^0.1.30"
- "@types/cssbeautify" "^0.3.1"
- "@types/doctrine" "^0.0.1"
- "@types/is-windows" "^0.2.0"
- "@types/minimatch" "^3.0.1"
- "@types/parse5" "^2.2.34"
- "@types/path-is-inside" "^1.0.0"
- "@types/resolve" "0.0.6"
- "@types/whatwg-url" "^6.4.0"
- babylon "^7.0.0-beta.42"
- cancel-token "^0.1.1"
- chalk "^1.1.3"
- clone "^2.0.0"
- cssbeautify "^0.3.1"
- doctrine "^2.0.2"
- dom5 "^3.0.0"
- indent "0.0.2"
- is-windows "^1.0.2"
- jsonschema "^1.1.0"
- minimatch "^3.0.4"
- parse5 "^4.0.0"
- path-is-inside "^1.0.2"
- resolve "^1.5.0"
- shady-css-parser "^0.1.0"
- stable "^0.1.6"
- strip-indent "^2.0.0"
- vscode-uri "=1.0.6"
- whatwg-url "^6.4.0"
-
-polymer-bundler@^4.0.10:
- version "4.0.10"
- resolved "https://registry.yarnpkg.com/polymer-bundler/-/polymer-bundler-4.0.10.tgz#abc8d33977652f031068d034c8104841e80d4cbb"
- integrity sha512-nwlN3LQlQDqbZ2sUH3394C/dHZUDHq8tpdS5HARvPDb0Q9IXWD+znOR1cr7wSjF0EZN4LiUH5hWyUoV4QSjhpQ==
- dependencies:
- "@types/babel-generator" "^6.25.1"
- "@types/babel-traverse" "^6.25.3"
- babel-generator "^6.26.1"
- babel-traverse "^6.26.0"
- babel-types "^6.26.0"
- clone "^2.1.0"
- command-line-args "^5.0.2"
- command-line-usage "^5.0.5"
- dom5 "^3.0.0"
- espree "^3.5.2"
- magic-string "^0.22.4"
- mkdirp "^0.5.1"
- parse5 "^4.0.0"
- polymer-analyzer "^3.2.2"
- rollup "^1.3.0"
- source-map "^0.5.6"
- vscode-uri "=1.0.6"
-
progress@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
@@ -1593,11 +982,6 @@
end-of-stream "^1.1.0"
once "^1.3.1"
-punycode@^2.1.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
- integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
-
q@^1.4.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
@@ -1624,29 +1008,12 @@
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
-reduce-flatten@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-1.0.1.tgz#258c78efd153ddf93cb561237f61184f3696e327"
- integrity sha1-JYx479FT3fk8tWEjf2EYTzaW4yc=
-
-regenerator-runtime@^0.11.0:
- version "0.11.1"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
- integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-
-repeating@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
- integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=
- dependencies:
- is-finite "^1.0.0"
-
resolve-alpn@^1.0.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"
integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==
-resolve@^1.11.0, resolve@^1.11.1, resolve@^1.5.0:
+resolve@^1.11.0, resolve@^1.11.1:
version "1.22.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
@@ -1701,37 +1068,27 @@
resolve "^1.11.1"
rollup-pluginutils "^2.8.1"
-rollup-plugin-terser@^5.1.3:
- version "5.3.1"
- resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz#8c650062c22a8426c64268548957463bf981b413"
- integrity sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w==
+rollup-plugin-terser@^7.0.2:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
+ integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==
dependencies:
- "@babel/code-frame" "^7.5.5"
- jest-worker "^24.9.0"
- rollup-pluginutils "^2.8.2"
+ "@babel/code-frame" "^7.10.4"
+ jest-worker "^26.2.1"
serialize-javascript "^4.0.0"
- terser "^4.6.2"
+ terser "^5.0.0"
-rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2:
+rollup-pluginutils@^2.8.1:
version "2.8.2"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"
integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==
dependencies:
estree-walker "^0.6.1"
-rollup@^1.3.0:
- version "1.32.1"
- resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.32.1.tgz#4480e52d9d9e2ae4b46ba0d9ddeaf3163940f9c4"
- integrity sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==
- dependencies:
- "@types/estree" "*"
- "@types/node" "*"
- acorn "^7.1.0"
-
-rollup@^2.3.4:
- version "2.75.5"
- resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.75.5.tgz#7985c1962483235dd07966f09fdad5c5f89f16d0"
- integrity sha512-JzNlJZDison3o2mOxVmb44Oz7t74EfSd1SQrplQk0wSaXV7uLQXtVdHbxlcT3w+8tZ1TL4r/eLfc7nAbz38BBA==
+rollup@^2.79.1:
+ version "2.79.1"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
+ integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==
optionalDependencies:
fsevents "~2.3.2"
@@ -1770,11 +1127,6 @@
dependencies:
randombytes "^2.1.0"
-shady-css-parser@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/shady-css-parser/-/shady-css-parser-0.1.0.tgz#534dc79c8ca5884c5ed92a4e5a13d6d863bca428"
- integrity sha512-irfJUUkEuDlNHKZNAp2r7zOyMlmbfVJ+kWSfjlCYYUx/7dJnANLCyTzQZsuxy5NJkvtNwSxY5Gj8MOlqXUQPyA==
-
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
@@ -1795,7 +1147,7 @@
buffer-from "^1.0.0"
source-map "^0.6.0"
-source-map-support@~0.5.12:
+source-map-support@~0.5.20:
version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
@@ -1803,26 +1155,21 @@
buffer-from "^1.0.0"
source-map "^0.6.0"
-source-map@^0.5.6, source-map@^0.5.7:
- version "0.5.7"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
- integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
-
-source-map@^0.6.0, source-map@~0.6.1:
+source-map@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+source-map@~0.7.2:
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
+ integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
+
sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
-stable@^0.1.6:
- version "0.1.8"
- resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
- integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
-
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@@ -1830,23 +1177,6 @@
dependencies:
safe-buffer "~5.2.0"
-strip-ansi@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
- integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
- dependencies:
- ansi-regex "^2.0.0"
-
-strip-indent@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
- integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=
-
-supports-color@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
- integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
-
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -1854,41 +1184,18 @@
dependencies:
has-flag "^3.0.0"
-supports-color@^6.1.0:
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
- integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
+supports-color@^7.0.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
- has-flag "^3.0.0"
+ has-flag "^4.0.0"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
-table-layout@^0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-0.3.0.tgz#6ee20dc483db371b3e5c87f704ed2f7c799d2c9a"
- integrity sha1-buINxIPbNxs+XIf3BO0vfHmdLJo=
- dependencies:
- array-back "^1.0.3"
- core-js "^2.4.1"
- deep-extend "~0.4.1"
- feature-detect-es6 "^1.3.1"
- typical "^2.6.0"
- wordwrapjs "^2.0.0-0"
-
-table-layout@^0.4.3:
- version "0.4.5"
- resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-0.4.5.tgz#d906de6a25fa09c0c90d1d08ecd833ecedcb7378"
- integrity sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==
- dependencies:
- array-back "^2.0.0"
- deep-extend "~0.6.0"
- lodash.padend "^4.6.1"
- typical "^2.6.1"
- wordwrapjs "^3.0.0"
-
tar-stream@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
@@ -1900,44 +1207,24 @@
inherits "^2.0.3"
readable-stream "^3.1.1"
-terser@^4.6.2:
- version "4.8.0"
- resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17"
- integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==
+terser@^5.0.0:
+ version "5.22.0"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.22.0.tgz#4f18103f84c5c9437aafb7a14918273310a8a49d"
+ integrity sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw==
+ dependencies:
+ "@jridgewell/source-map" "^0.3.3"
+ acorn "^8.8.2"
+ commander "^2.20.0"
+ source-map-support "~0.5.20"
+
+terser@~5.8.0:
+ version "5.8.0"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.8.0.tgz#c6d352f91aed85cc6171ccb5e84655b77521d947"
+ integrity sha512-f0JH+6yMpneYcRJN314lZrSwu9eKkUFEHLN/kNy8ceh8gaRiLgFPJqrB9HsXjhEGdv4e/ekjTOFxIlL6xlma8A==
dependencies:
commander "^2.20.0"
- source-map "~0.6.1"
- source-map-support "~0.5.12"
-
-test-value@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/test-value/-/test-value-2.1.0.tgz#11da6ff670f3471a73b625ca4f3fdcf7bb748291"
- integrity sha1-Edpv9nDzRxpztiXKTz/c97t0gpE=
- dependencies:
- array-back "^1.0.3"
- typical "^2.6.0"
-
-to-fast-properties@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
- integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=
-
-to-fast-properties@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
- integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
-
-tr46@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
- integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=
- dependencies:
- punycode "^2.1.0"
-
-trim-right@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
- integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
+ source-map "~0.7.2"
+ source-map-support "~0.5.20"
tslib@^1.8.1:
version "1.14.1"
@@ -1956,16 +1243,6 @@
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
-typical@^2.6.0, typical@^2.6.1:
- version "2.6.1"
- resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d"
- integrity sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=
-
-typical@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4"
- integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==
-
underscore@^1.8.3:
version "1.13.4"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.4.tgz#7886b46bbdf07f768e0052f1828e1dcab40c0dee"
@@ -1976,16 +1253,6 @@
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
-vlq@^0.2.2:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"
- integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==
-
-vscode-uri@=1.0.6:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.6.tgz#6b8f141b0bbc44ad7b07e94f82f168ac7608ad4d"
- integrity sha512-sLI2L0uGov3wKVb9EB+vIQBl9tVP90nqRvxSoJ35vI3NjxE8jfsE5DSOhWgSunHSZmKS4OCi2jrtfxK7uyp2ww==
-
wct-local@2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/wct-local/-/wct-local-2.1.6.tgz#2d099c52996e77265d16e03a5d6d897b77ea9967"
@@ -2002,20 +1269,6 @@
selenium-standalone "^6.7.0"
which "^1.0.8"
-webidl-conversions@^4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
- integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
-
-whatwg-url@^6.4.0:
- version "6.5.0"
- resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"
- integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==
- dependencies:
- lodash.sortby "^4.7.0"
- tr46 "^1.0.1"
- webidl-conversions "^4.0.2"
-
which@^1.0.8:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
@@ -2030,24 +1283,6 @@
dependencies:
isexe "^2.0.0"
-wordwrapjs@^2.0.0-0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-2.0.0.tgz#ab55f695e6118da93858fdd70c053d1c5e01ac20"
- integrity sha1-q1X2leYRjak4WP3XDAU9HF4BrCA=
- dependencies:
- array-back "^1.0.3"
- feature-detect-es6 "^1.3.1"
- reduce-flatten "^1.0.1"
- typical "^2.6.0"
-
-wordwrapjs@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-3.0.0.tgz#c94c372894cadc6feb1a66bff64e1d9af92c5d1e"
- integrity sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==
- dependencies:
- reduce-flatten "^1.0.1"
- typical "^2.6.1"
-
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
diff --git a/tools/nongoogle.bzl b/tools/nongoogle.bzl
index a03ccf0..55b3185 100644
--- a/tools/nongoogle.bzl
+++ b/tools/nongoogle.bzl
@@ -1,10 +1,10 @@
load("//tools/bzl:maven_jar.bzl", "maven_jar")
-GUAVA_VERSION = "30.1-jre"
+GUAVA_VERSION = "32.1.2-jre"
-GUAVA_BIN_SHA1 = "00d0c3ce2311c9e36e73228da25a6e99b2ab826f"
+GUAVA_BIN_SHA1 = "5e64ec7e056456bef3a4bc4c6fdaef71e8ab6318"
-GUAVA_TESTLIB_BIN_SHA1 = "798c3827308605cd69697d8f1596a1735d3ef6e2"
+GUAVA_TESTLIB_BIN_SHA1 = "c7a8a2c91b6809ff46373b1bc06185241801f6b5"
GUAVA_DOC_URL = "https://google.github.io/guava/releases/" + GUAVA_VERSION + "/api/docs/"
@@ -67,18 +67,18 @@
sha1 = "cb2f351bf4463751201f43bb99865235d5ba07ca",
)
- SSHD_VERS = "2.9.2"
+ SSHD_VERS = "2.10.0"
maven_jar(
name = "sshd-osgi",
artifact = "org.apache.sshd:sshd-osgi:" + SSHD_VERS,
- sha1 = "bac0415734519b2fe433fea196017acf7ed32660",
+ sha1 = "03677ac1da780b7bdb682da50b762d79ea0d940d",
)
maven_jar(
name = "sshd-sftp",
artifact = "org.apache.sshd:sshd-sftp:" + SSHD_VERS,
- sha1 = "7f9089c87b3b44f19998252fd3b68637e3322920",
+ sha1 = "88707339ac0693d48df0ec1bafb84c78d792ed08",
)
maven_jar(
@@ -96,7 +96,7 @@
maven_jar(
name = "sshd-mina",
artifact = "org.apache.sshd:sshd-mina:" + SSHD_VERS,
- sha1 = "765dced3a2b4069bb0c550e18bda057bad8de26f",
+ sha1 = "b1f77377fbc517400e7665d0b2c83b58b41aa45d",
)
maven_jar(
@@ -135,8 +135,8 @@
maven_jar(
name = "error-prone-annotations",
- artifact = "com.google.errorprone:error_prone_annotations:2.15.0",
- sha1 = "38c8485a652f808c8c149150da4e5c2b0bd17f9a",
+ artifact = "com.google.errorprone:error_prone_annotations:2.22.0",
+ sha1 = "bfb9e4281a4cea34f0ec85b3acd47621cfab35b4",
)
FLOGGER_VERS = "0.7.4"
@@ -154,6 +154,12 @@
)
maven_jar(
+ name = "flogger-google-extensions",
+ artifact = "com.google.flogger:google-extensions:" + FLOGGER_VERS,
+ sha1 = "c49493bd815e3842b8406e21117119d560399977",
+ )
+
+ maven_jar(
name = "flogger-system-backend",
artifact = "com.google.flogger:flogger-system-backend:" + FLOGGER_VERS,
sha1 = "4bee7ebbd97c63ca7fb17529aeb49a57b670d061",
@@ -171,31 +177,31 @@
sha1 = GUAVA_TESTLIB_BIN_SHA1,
)
- GUICE_VERS = "5.1.0"
+ GUICE_VERS = "6.0.0"
maven_jar(
name = "guice-library",
artifact = "com.google.inject:guice:" + GUICE_VERS,
- sha1 = "da25056c694c54ba16e78e4fc35f17fc60f0d1b4",
+ sha1 = "9b422c69c4fa1ea95b2615444a94fede9b02fc40",
)
maven_jar(
name = "guice-assistedinject",
artifact = "com.google.inject.extensions:guice-assistedinject:" + GUICE_VERS,
- sha1 = "58a8956f00d6939978d7da735f393d7af7db5c02",
+ sha1 = "849d991e4adf998cb9877124fe74b063c88726cf",
)
maven_jar(
name = "guice-servlet",
artifact = "com.google.inject.extensions:guice-servlet:" + GUICE_VERS,
- sha1 = "cb89ddec4246a469698a3461e69de1f245016c5d",
+ sha1 = "1a505f5f1a269e01946790e863178a5055de4fa0",
)
# Keep this version of Soy synchronized with the version used in Gitiles.
maven_jar(
name = "soy",
- artifact = "com.google.template:soy:2021-02-01",
- sha1 = "8e833744832ba88059205a1e30e0898f925d8cb5",
+ artifact = "com.google.template:soy:2022-07-20",
+ sha1 = "f64eb90da6d91beddf11653865c90f26d26710cf",
)
# Test-only dependencies below.
diff --git a/tools/remote-bazelrc b/tools/remote-bazelrc
index c9a83e4..d634133 100644
--- a/tools/remote-bazelrc
+++ b/tools/remote-bazelrc
@@ -25,37 +25,37 @@
# this higher can make builds faster by allowing more jobs to run in parallel.
# Setting it too high can result in jobs that timeout, however, while waiting
# for a remote machine to execute them.
-build:remote --jobs=200
-build:remote --disk_cache=
+build:remote_shared --jobs=200
+build:remote_shared --disk_cache=
# Set several flags related to specifying the platform, toolchain and java
# properties.
-build:remote --crosstool_top=@rbe_jdk11//cc:toolchain
-build:remote --extra_toolchains=@rbe_jdk11//config:cc-toolchain
-build:remote --extra_execution_platforms=@rbe_jdk11//config:platform
-build:remote --host_platform=@rbe_jdk11//config:platform
-build:remote --platforms=@rbe_jdk11//config:platform
-build:remote --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
+build:remote_shared --crosstool_top=@rbe_jdk11//cc:toolchain
+build:remote_shared --extra_toolchains=@rbe_jdk11//config:cc-toolchain
+build:remote_shared --extra_execution_platforms=@rbe_jdk11//config:platform
+build:remote_shared --host_platform=@rbe_jdk11//config:platform
+build:remote_shared --platforms=@rbe_jdk11//config:platform
+build:remote_shared --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
# Set various strategies so that all actions execute remotely. Mixing remote
# and local execution will lead to errors unless the toolchain and remote
# machine exactly match the host machine.
-build:remote --define=EXECUTOR=remote
+build:remote_shared --define=EXECUTOR=remote
# Enable the remote cache so action results can be shared across machines,
# developers, and workspaces.
-build:remote --remote_cache=remotebuildexecution.googleapis.com
+build:remote_shared --remote_cache=remotebuildexecution.googleapis.com
# Enable remote execution so actions are performed on the remote systems.
-build:remote --remote_executor=remotebuildexecution.googleapis.com
+build:remote_shared --remote_executor=remotebuildexecution.googleapis.com
# Set a higher timeout value, just in case.
-build:remote --remote_timeout=3600
+build:remote_shared --remote_timeout=3600
# Enable authentication. This will pick up application default credentials by
# default. You can use --auth_credentials=some_file.json to use a service
# account credential instead.
-build:remote --google_default_credentials
+build:remote_shared --google_default_credentials
# The following flags enable the remote cache so action results can be shared
# across machines, developers, and workspaces.