Merge "Fix gr-registration-dialog"
diff --git a/Documentation/cmd-convert-ref-storage.txt b/Documentation/cmd-convert-ref-storage.txt
new file mode 100644
index 0000000..aae385f
--- /dev/null
+++ b/Documentation/cmd-convert-ref-storage.txt
@@ -0,0 +1,58 @@
+= gerrit convert-ref-storage
+
+== NAME
+gerrit convert-ref-storage - Convert ref storage to reftable (experimental).
+
+A reftable file is a portable binary file format customized for reference storage.
+References are sorted, enabling linear scans, binary search lookup, and range scans.
+
+See also link:https://www.git-scm.com/docs/reftable for more details[reftable,role=external,window=_blank]
+
+== SYNOPSIS
+[verse]
+--
+_ssh_ -p <port> <host> _gerrit convert-ref-storage_
+ [--format <format>]
+ [--backup | -b]
+ [--reflogs | -r]
+ [--project <PROJECT> | -p <PROJECT>]
+--
+
+== DESCRIPTION
+Convert ref storage to reftable.
+
+== ACCESS
+Administrators
+
+== OPTIONS
+--project::
+-p::
+ Required; Name of the project for which the ref format should be changed.
+
+--format::
+ Format to convert to: `reftable` or `refdir`.
+ Default: reftable.
+
+--backup::
+-b::
+ Create backup of old ref storage format.
+ Default: true.
+
+--reflogs::
+-r::
+ Write reflogs to reftable.
+ Default: true.
+
+== EXAMPLES
+
+Convert ref format for project "core" to reftable:
+----
+$ ssh -p 29418 review.example.com gerrit convert-ref-format -p core
+----
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-community.txt b/Documentation/dev-community.txt
index 70fda87..7488f74 100644
--- a/Documentation/dev-community.txt
+++ b/Documentation/dev-community.txt
@@ -43,7 +43,7 @@
** link:dev-contributing.html#mentorship[Mentorship]
* link:dev-design-docs.html[Design Docs]
* link:dev-readme.html[Developer Setup]
-* link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui[Polymer Frontend Developer Setup]
+* link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui[TypeScript Frontend Developer Setup]
* link:dev-crafting-changes.html[Crafting Changes]
* link:dev-starter-projects.html[Starter Projects]
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index f2a3e12..a66d3b5 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -1450,7 +1450,7 @@
By implementing the `com.google.gerrit.server.restapi.change.OnPostReview`
interface plugins can extend the change message that is being posted when the
-[post review](rest-api-changes.html#set-review) REST endpoint is invoked.
+link:rest-api-changes.html#set-review[post review] REST endpoint is invoked.
This is useful if certain approvals have a special meaning (e.g. custom logic
that is implemented in Prolog submit rules, signal for triggering an action
@@ -1458,6 +1458,8 @@
in the change message. This makes the effect of a given approval more
transparent to the user.
+[[ui_extension]]
+== UI Extension
[[actions]]
=== Actions
diff --git a/Documentation/images/user-checks-overview.png b/Documentation/images/user-checks-overview.png
new file mode 100644
index 0000000..7a9864e
--- /dev/null
+++ b/Documentation/images/user-checks-overview.png
Binary files differ
diff --git a/Documentation/pg-plugin-checks-api.txt b/Documentation/pg-plugin-checks-api.txt
new file mode 100644
index 0000000..4e93da1
--- /dev/null
+++ b/Documentation/pg-plugin-checks-api.txt
@@ -0,0 +1,44 @@
+:linkattrs:
+= Gerrit Code Review - JavaScript Plugin Checks API
+
+This API is provided by link:pg-plugin-dev.html#plugin-checks[plugin.checks()].
+It allows plugins to contribute to the "Checks" tab and summary:
+
+image::images/user-checks-overview.png[width=800]
+
+Each plugin can link:#register[register] a checks provider that will be called
+when a change page is loaded. Such a call would return a list of `Runs` and each
+run can contain a list of `Results`.
+
+The details of the ChecksApi are documented in the
+link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/api/checks.ts[source code].
+Note that this link points to the `master` branch and might thus reflect a
+newer version of the API than your Gerrit installation.
+
+If no plugins are registered with the ChecksApi, then the Checks tab will be
+hidden.
+
+You can read about the motivation, the use cases and the original plans in the
+link:https://www.gerritcodereview.com/design-docs/ci-reboot.html[design doc].
+
+Here are some examples of open source plugins that make use of the Checks API:
+
+* link:https://gerrit.googlesource.com/plugins/checks/+/master/gr-checks/plugin.js[Gerrit Checks Plugin]
+* link:https://chromium.googlesource.com/infra/gerrit-plugins/buildbucket/+/master/src/main/resources/static/buildbucket.js[Chromium Buildbucket Plugin]
+* link:https://chromium.googlesource.com/infra/gerrit-plugins/code-coverage/+/master/src/main/resources/static/chromium-coverage.js[Chromium Coverage Plugin]
+
+[[register]]
+== register
+`checksApi.register(provider, config?)`
+
+.Params
+- *provider* Must implement a `fetch()` interface that returns a
+ `Promise<FetchResponse>` with runs and results. See also documentation in the
+ link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/api/checks.ts[source code].
+- *config* Optional configuration values for the checks provider.
+
+[[announceUpdate]]
+== announceUpdate
+`checksApi.announceUpdate()`
+
+Tells Gerrit to call `provider.fetch()`.
diff --git a/Documentation/pg-plugin-dev.txt b/Documentation/pg-plugin-dev.txt
index 9c565da..dc7986f 100644
--- a/Documentation/pg-plugin-dev.txt
+++ b/Documentation/pg-plugin-dev.txt
@@ -21,6 +21,7 @@
located in `gerrit-site/plugins` folder, where `pluginname` is an alphanumeric
plugin name.
+=== Examples
Here's a recommended starter `myplugin.js`:
``` js
@@ -29,6 +30,10 @@
});
```
+You can find more elaborate examples in the
+link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/samples/[polygerrit-ui/app/samples/]
+directory of the source tree.
+
[[low-level-api-concepts]]
== Low-level DOM API concepts
@@ -96,9 +101,9 @@
`plugin.registerStyleModule(endpointName, moduleName)`. A style must be defined
as a standalone `<dom-module>` defined in the same .js file.
-See `samples/theme-plugin.js` for examples.
-
-Note: TODO: Insert link to the full styling API.
+See
+link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/samples/theme-plugin.js[samples/theme-plugin.js]
+for an example.
``` js
const styleElement = document.createElement('dom-module');
@@ -141,8 +146,9 @@
binding,role=external,window=_blank] for plugins that don't use Polymer. Can be used to bind element
attribute changes to callbacks.
-See `samples/bind-parameters.js` for examples on both Polymer data bindings
-and `attibuteHelper` usage.
+See
+link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/samples/bind-parameters.js[samples/bind-parameters.js]
+for an example.
=== hook
`plugin.hook(endpointName, opt_options)`
@@ -369,6 +375,12 @@
Returns an instance of the
link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/api/change-reply.ts[ChangeReplyPluginApi].
+[[checks]]
+=== checks
+`plugin.checks()`
+
+Returns an instance of the link:pg-plugin-checks-api.html[ChecksApi].
+
=== getPluginName
`plugin.getPluginName()`
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index dab8117..d3635b3 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -7058,6 +7058,9 @@
|`web_links` |optional|
Links to the file in external sites as a list of
link:rest-api-changes.html#web-link-info[WebLinkInfo] entries.
+|`edit_web_links` |optional|
+Links to edit the file in external sites as a list of
+link:rest-api-changes.html#web-link-info[WebLinkInfo] entries.
|==========================
[[diff-info]]
diff --git a/Documentation/user-attention-set.txt b/Documentation/user-attention-set.txt
index 5053d10..1f67fc7 100644
--- a/Documentation/user-attention-set.txt
+++ b/Documentation/user-attention-set.txt
@@ -149,7 +149,7 @@
instead.
The "Assignee" feature can be turned on/off with the
-link:config-gerrit.html#change.enableAttentionSet[enableAssignee] config option.
+link:config-gerrit.html#change.enableAssignee[enableAssignee] config option.
=== Bold Changes / Mark Reviewed
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index b05050d..003df28 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -1284,6 +1284,7 @@
assertThat(diff.metaB.lines).isEqualTo(expectedLines.size());
assertThat(diff.metaB.name).isEqualTo(path);
assertThat(diff.metaB.webLinks).isNull();
+ assertThat(diff.metaB.editWebLinks).isNull();
assertThat(diff.content).hasSize(1);
DiffInfo.ContentEntry contentEntry = diff.content.get(0);
diff --git a/java/com/google/gerrit/acceptance/ExtensionRegistry.java b/java/com/google/gerrit/acceptance/ExtensionRegistry.java
index 35f8ce6..6c6bab0 100644
--- a/java/com/google/gerrit/acceptance/ExtensionRegistry.java
+++ b/java/com/google/gerrit/acceptance/ExtensionRegistry.java
@@ -32,6 +32,7 @@
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.PrivateInternals_DynamicMapImpl;
import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.extensions.webui.EditWebLink;
import com.google.gerrit.extensions.webui.FileHistoryWebLink;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
import com.google.gerrit.server.ExceptionHook;
@@ -75,6 +76,7 @@
private final DynamicSet<GitReferenceUpdatedListener> refUpdatedListeners;
private final DynamicSet<FileHistoryWebLink> fileHistoryWebLinks;
private final DynamicSet<PatchSetWebLink> patchSetWebLinks;
+ private final DynamicSet<EditWebLink> editWebLinks;
private final DynamicSet<RevisionCreatedListener> revisionCreatedListeners;
private final DynamicSet<GroupBackend> groupBackends;
private final DynamicSet<AccountActivationValidationListener>
@@ -109,6 +111,7 @@
DynamicSet<GitReferenceUpdatedListener> refUpdatedListeners,
DynamicSet<FileHistoryWebLink> fileHistoryWebLinks,
DynamicSet<PatchSetWebLink> patchSetWebLinks,
+ DynamicSet<EditWebLink> editWebLinks,
DynamicSet<RevisionCreatedListener> revisionCreatedListeners,
DynamicSet<GroupBackend> groupBackends,
DynamicSet<AccountActivationValidationListener> accountActivationValidationListeners,
@@ -139,6 +142,7 @@
this.refUpdatedListeners = refUpdatedListeners;
this.fileHistoryWebLinks = fileHistoryWebLinks;
this.patchSetWebLinks = patchSetWebLinks;
+ this.editWebLinks = editWebLinks;
this.revisionCreatedListeners = revisionCreatedListeners;
this.groupBackends = groupBackends;
this.accountActivationValidationListeners = accountActivationValidationListeners;
@@ -240,6 +244,10 @@
return add(patchSetWebLinks, patchSetWebLink);
}
+ public Registration add(EditWebLink editWebLink) {
+ return add(editWebLinks, editWebLink);
+ }
+
public Registration add(RevisionCreatedListener revisionCreatedListener) {
return add(revisionCreatedListeners, revisionCreatedListener);
}
diff --git a/java/com/google/gerrit/extensions/common/DiffInfo.java b/java/com/google/gerrit/extensions/common/DiffInfo.java
index 2511e96..5a59613 100644
--- a/java/com/google/gerrit/extensions/common/DiffInfo.java
+++ b/java/com/google/gerrit/extensions/common/DiffInfo.java
@@ -52,6 +52,8 @@
public Integer lines;
// Links to the file in external sites
public List<WebLinkInfo> webLinks;
+ // Links to edit the file in external sites
+ public List<WebLinkInfo> editWebLinks;
}
public static final class ContentEntry {
diff --git a/java/com/google/gerrit/extensions/common/testing/FileMetaSubject.java b/java/com/google/gerrit/extensions/common/testing/FileMetaSubject.java
index 0953bfe..d0212f3 100644
--- a/java/com/google/gerrit/extensions/common/testing/FileMetaSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/FileMetaSubject.java
@@ -64,4 +64,9 @@
isNotNull();
return check("webLinks").that(fileMeta.webLinks);
}
+
+ public IterableSubject editWebLinks() {
+ isNotNull();
+ return check("editWebLinks").that(fileMeta.editWebLinks);
+ }
}
diff --git a/java/com/google/gerrit/extensions/webui/EditWebLink.java b/java/com/google/gerrit/extensions/webui/EditWebLink.java
new file mode 100644
index 0000000..cd70feb
--- /dev/null
+++ b/java/com/google/gerrit/extensions/webui/EditWebLink.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.webui;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.extensions.common.WebLinkInfo;
+
+@ExtensionPoint
+public interface EditWebLink extends WebLink {
+
+ /**
+ * {@link com.google.gerrit.extensions.common.WebLinkInfo} describing a link from a file to an
+ * external service for editing.
+ *
+ * <p>In order for the web link to be visible {@link WebLinkInfo#url} and {@link WebLinkInfo#name}
+ * must be set.
+ *
+ * @param projectName name of the project
+ * @param revision name of the revision (e.g. branch or commit ID)
+ * @param fileName name of the file
+ * @return WebLinkInfo that links to project in external service, null if there should be no link.
+ */
+ WebLinkInfo getEditWebLink(String projectName, String revision, String fileName);
+}
diff --git a/java/com/google/gerrit/pgm/util/ErrorLogJsonLayout.java b/java/com/google/gerrit/pgm/util/ErrorLogJsonLayout.java
index 2ea1f82..7d4abfc 100644
--- a/java/com/google/gerrit/pgm/util/ErrorLogJsonLayout.java
+++ b/java/com/google/gerrit/pgm/util/ErrorLogJsonLayout.java
@@ -97,7 +97,7 @@
private String getSourceHost() {
try {
- return InetAddress.getLocalHost().getHostName();
+ return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
return "unknown-host";
}
diff --git a/java/com/google/gerrit/server/WebLinks.java b/java/com/google/gerrit/server/WebLinks.java
index e66e7f5..3b626ea 100644
--- a/java/com/google/gerrit/server/WebLinks.java
+++ b/java/com/google/gerrit/server/WebLinks.java
@@ -27,6 +27,7 @@
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.webui.BranchWebLink;
import com.google.gerrit.extensions.webui.DiffWebLink;
+import com.google.gerrit.extensions.webui.EditWebLink;
import com.google.gerrit.extensions.webui.FileHistoryWebLink;
import com.google.gerrit.extensions.webui.FileWebLink;
import com.google.gerrit.extensions.webui.ParentWebLink;
@@ -56,6 +57,7 @@
private final DynamicSet<PatchSetWebLink> patchSetLinks;
private final DynamicSet<ParentWebLink> parentLinks;
+ private final DynamicSet<EditWebLink> editLinks;
private final DynamicSet<FileWebLink> fileLinks;
private final DynamicSet<FileHistoryWebLink> fileHistoryLinks;
private final DynamicSet<DiffWebLink> diffLinks;
@@ -67,6 +69,7 @@
public WebLinks(
DynamicSet<PatchSetWebLink> patchSetLinks,
DynamicSet<ParentWebLink> parentLinks,
+ DynamicSet<EditWebLink> editLinks,
DynamicSet<FileWebLink> fileLinks,
DynamicSet<FileHistoryWebLink> fileLogLinks,
DynamicSet<DiffWebLink> diffLinks,
@@ -75,6 +78,7 @@
DynamicSet<TagWebLink> tagLinks) {
this.patchSetLinks = patchSetLinks;
this.parentLinks = parentLinks;
+ this.editLinks = editLinks;
this.fileLinks = fileLinks;
this.fileHistoryLinks = fileLogLinks;
this.diffLinks = diffLinks;
@@ -115,6 +119,18 @@
* @param project Project name.
* @param revision SHA1 of revision.
* @param file File name.
+ * @return Links for editing.
+ */
+ public ImmutableList<WebLinkInfo> getEditLinks(String project, String revision, String file) {
+ return Patch.isMagic(file)
+ ? ImmutableList.of()
+ : filterLinks(editLinks, webLink -> webLink.getEditWebLink(project, revision, file));
+ }
+
+ /**
+ * @param project Project name.
+ * @param revision SHA1 of revision.
+ * @param file File name.
* @return Links for files.
*/
public ImmutableList<WebLinkInfo> getFileLinks(String project, String revision, String file) {
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index bb851e2..339b350 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -66,6 +66,7 @@
import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.extensions.webui.BranchWebLink;
import com.google.gerrit.extensions.webui.DiffWebLink;
+import com.google.gerrit.extensions.webui.EditWebLink;
import com.google.gerrit.extensions.webui.FileHistoryWebLink;
import com.google.gerrit.extensions.webui.FileWebLink;
import com.google.gerrit.extensions.webui.ParentWebLink;
@@ -395,6 +396,7 @@
DynamicSet.setOf(binder(), FileWebLink.class);
DynamicSet.setOf(binder(), FileHistoryWebLink.class);
DynamicSet.setOf(binder(), DiffWebLink.class);
+ DynamicSet.setOf(binder(), EditWebLink.class);
DynamicSet.setOf(binder(), ProjectWebLink.class);
DynamicSet.setOf(binder(), BranchWebLink.class);
DynamicSet.setOf(binder(), TagWebLink.class);
diff --git a/java/com/google/gerrit/server/config/GitwebConfig.java b/java/com/google/gerrit/server/config/GitwebConfig.java
index 8214f03..97cc830 100644
--- a/java/com/google/gerrit/server/config/GitwebConfig.java
+++ b/java/com/google/gerrit/server/config/GitwebConfig.java
@@ -27,6 +27,7 @@
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.extensions.webui.BranchWebLink;
+import com.google.gerrit.extensions.webui.EditWebLink;
import com.google.gerrit.extensions.webui.FileHistoryWebLink;
import com.google.gerrit.extensions.webui.FileWebLink;
import com.google.gerrit.extensions.webui.ParentWebLink;
@@ -71,6 +72,7 @@
}
if (!isNullOrEmpty(type.getFile()) || !isNullOrEmpty(type.getRootTree())) {
+ DynamicSet.bind(binder(), EditWebLink.class).to(GitwebLinks.class);
DynamicSet.bind(binder(), FileWebLink.class).to(GitwebLinks.class);
}
@@ -253,6 +255,7 @@
@Singleton
static class GitwebLinks
implements BranchWebLink,
+ EditWebLink,
FileHistoryWebLink,
FileWebLink,
PatchSetWebLink,
@@ -327,6 +330,12 @@
}
@Override
+ public WebLinkInfo getEditWebLink(String projectName, String revision, String fileName) {
+ // For Gitweb treat edit links the same as file links
+ return getFileWebLink(projectName, revision, fileName);
+ }
+
+ @Override
public WebLinkInfo getPatchSetWebLink(
String projectName, String commit, String commitMessage, String branchName) {
if (revision != null) {
diff --git a/java/com/google/gerrit/server/diff/DiffInfoCreator.java b/java/com/google/gerrit/server/diff/DiffInfoCreator.java
index c29ffc8..53f0019 100644
--- a/java/com/google/gerrit/server/diff/DiffInfoCreator.java
+++ b/java/com/google/gerrit/server/diff/DiffInfoCreator.java
@@ -156,8 +156,10 @@
FileContentUtil.resolveContentType(
state, side.fileName(), fileInfo.mode, fileInfo.mimeType);
result.lines = fileInfo.content.getSize();
- ImmutableList<WebLinkInfo> links = webLinksProvider.getFileWebLinks(side.type());
- result.webLinks = links.isEmpty() ? null : links;
+ ImmutableList<WebLinkInfo> fileLinks = webLinksProvider.getFileWebLinks(side.type());
+ result.webLinks = fileLinks.isEmpty() ? null : fileLinks;
+ ImmutableList<WebLinkInfo> editLinks = webLinksProvider.getEditWebLinks(side.type());
+ result.editWebLinks = editLinks.isEmpty() ? null : editLinks;
result.commitId = fileInfo.commitId;
return Optional.of(result);
}
diff --git a/java/com/google/gerrit/server/diff/DiffWebLinksProvider.java b/java/com/google/gerrit/server/diff/DiffWebLinksProvider.java
index 0f71b17..d4c7f5b 100644
--- a/java/com/google/gerrit/server/diff/DiffWebLinksProvider.java
+++ b/java/com/google/gerrit/server/diff/DiffWebLinksProvider.java
@@ -24,6 +24,9 @@
/** Returns links associated with the diff view */
ImmutableList<DiffWebLinkInfo> getDiffLinks();
- /** Returns links associated with the diff side */
+ /** Returns file links associated with the diff side */
ImmutableList<WebLinkInfo> getFileWebLinks(DiffSide.Type fileInfoType);
+
+ /** Returns edit links associated with the diff side */
+ ImmutableList<WebLinkInfo> getEditWebLinks(DiffSide.Type fileInfoType);
}
diff --git a/java/com/google/gerrit/server/git/DelegateRepository.java b/java/com/google/gerrit/server/git/DelegateRepository.java
index 2816429..ddfc115 100644
--- a/java/com/google/gerrit/server/git/DelegateRepository.java
+++ b/java/com/google/gerrit/server/git/DelegateRepository.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.git;
+import static com.google.common.base.Preconditions.checkState;
+
import com.google.gerrit.common.UsedAt;
import java.io.File;
import java.io.IOException;
@@ -30,6 +32,7 @@
import org.eclipse.jgit.errors.RevisionSyntaxException;
import org.eclipse.jgit.events.ListenerList;
import org.eclipse.jgit.events.RepositoryEvent;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.BaseRepositoryBuilder;
import org.eclipse.jgit.lib.ObjectDatabase;
@@ -391,4 +394,19 @@
throws IOException {
delegate.writeRebaseTodoFile(path, steps, append);
}
+
+ /**
+ * Converts between ref storage formats.
+ *
+ * @param format the format to convert to, either "reftable" or "refdir"
+ * @param writeLogs whether to write reflogs
+ * @param backup whether to make a backup of the old data
+ * @throws IOException on I/O problems.
+ */
+ public void convertRefStorage(String format, boolean writeLogs, boolean backup)
+ throws IOException {
+ checkState(
+ delegate instanceof FileRepository, "Repository is not an instance of FileRepository!");
+ ((FileRepository) delegate).convertRefStorage(format, writeLogs, backup);
+ }
}
diff --git a/java/com/google/gerrit/server/git/validators/MergeValidators.java b/java/com/google/gerrit/server/git/validators/MergeValidators.java
index cbaa121..6b145ca 100644
--- a/java/com/google/gerrit/server/git/validators/MergeValidators.java
+++ b/java/com/google/gerrit/server/git/validators/MergeValidators.java
@@ -51,6 +51,7 @@
import java.util.Objects;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
/**
@@ -104,7 +105,8 @@
new PluginMergeValidationListener(mergeValidationListeners),
projectConfigValidatorFactory.create(),
accountValidatorFactory.create(),
- groupValidatorFactory.create());
+ groupValidatorFactory.create(),
+ new DestBranchRefValidator());
for (MergeValidationListener validator : validators) {
validator.onPreMerge(repo, revWalk, commit, destProject, destBranch, patchSetId, caller);
@@ -198,7 +200,7 @@
throw new MergeValidationException(SET_BY_ADMIN, e);
} catch (PermissionBackendException e) {
logger.atWarning().withCause(e).log("Cannot check ADMINISTRATE_SERVER");
- throw new MergeValidationException("validation unavailable");
+ throw new MergeValidationException("validation unavailable", e);
}
} else {
try {
@@ -210,7 +212,7 @@
throw new MergeValidationException(SET_BY_OWNER, e);
} catch (PermissionBackendException e) {
logger.atWarning().withCause(e).log("Cannot check WRITE_CONFIG");
- throw new MergeValidationException("validation unavailable");
+ throw new MergeValidationException("validation unavailable", e);
}
}
if (allUsersName.equals(destProject.getNameKey())
@@ -317,7 +319,7 @@
}
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Cannot validate account update");
- throw new MergeValidationException("account validation unavailable");
+ throw new MergeValidationException("account validation unavailable", e);
}
try {
@@ -329,7 +331,7 @@
}
} catch (IOException e) {
logger.atSevere().withCause(e).log("Cannot validate account update");
- throw new MergeValidationException("account validation unavailable");
+ throw new MergeValidationException("account validation unavailable", e);
}
}
}
@@ -366,4 +368,34 @@
throw new MergeValidationException("group update not allowed");
}
}
+
+ /**
+ * Validator to ensure that destBranch is not a symbolic reference (an attempt to merge into a
+ * symbolic ref branch leads to LOCK_FAILURE exception).
+ */
+ private static class DestBranchRefValidator implements MergeValidationListener {
+ @Override
+ public void onPreMerge(
+ Repository repo,
+ CodeReviewRevWalk revWalk,
+ CodeReviewCommit commit,
+ ProjectState destProject,
+ BranchNameKey destBranch,
+ PatchSet.Id patchSetId,
+ IdentifiedUser caller)
+ throws MergeValidationException {
+ try {
+ Ref ref = repo.exactRef(destBranch.branch());
+ // Usually the target branch exists, but there is an exception for some branches (see
+ // {@link com.google.gerrit.server.git.receive.ReceiveCommits} for details).
+ // Such non-existing branches should be ignored.
+ if (ref != null && ref.isSymbolic()) {
+ throw new MergeValidationException("the target branch is a symbolic ref");
+ }
+ } catch (IOException e) {
+ logger.atSevere().withCause(e).log("Cannot validate destination branch");
+ throw new MergeValidationException("symref validation unavailable", e);
+ }
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetDiff.java b/java/com/google/gerrit/server/restapi/change/GetDiff.java
index b8902b7..d48d76a 100644
--- a/java/com/google/gerrit/server/restapi/change/GetDiff.java
+++ b/java/com/google/gerrit/server/restapi/change/GetDiff.java
@@ -226,18 +226,26 @@
}
@Override
+ public ImmutableList<WebLinkInfo> getEditWebLinks(DiffSide.Type type) {
+ String rev = getSideRev(type);
+ DiffSide side = getDiffSide(type);
+ return webLinks.getEditLinks(projectName.get(), rev, side.fileName());
+ }
+
+ @Override
public ImmutableList<WebLinkInfo> getFileWebLinks(DiffSide.Type type) {
- String rev;
- DiffSide side;
- if (type == DiffSide.Type.SIDE_A) {
- rev = revA;
- side = sideA;
- } else {
- rev = revB;
- side = sideB;
- }
+ String rev = getSideRev(type);
+ DiffSide side = getDiffSide(type);
return webLinks.getFileLinks(projectName.get(), rev, side.fileName());
}
+
+ private String getSideRev(DiffSide.Type sideType) {
+ return DiffSide.Type.SIDE_A == sideType ? revA : revB;
+ }
+
+ private DiffSide getDiffSide(DiffSide.Type sideType) {
+ return DiffSide.Type.SIDE_A == sideType ? sideA : sideB;
+ }
}
public GetDiff setBase(String base) {
diff --git a/java/com/google/gerrit/server/restapi/change/GetFixPreview.java b/java/com/google/gerrit/server/restapi/change/GetFixPreview.java
index 6089778..5191fc8 100644
--- a/java/com/google/gerrit/server/restapi/change/GetFixPreview.java
+++ b/java/com/google/gerrit/server/restapi/change/GetFixPreview.java
@@ -140,5 +140,10 @@
public ImmutableList<WebLinkInfo> getFileWebLinks(Type fileInfoType) {
return ImmutableList.of();
}
+
+ @Override
+ public ImmutableList<WebLinkInfo> getEditWebLinks(Type fileInfoType) {
+ return ImmutableList.of();
+ }
}
}
diff --git a/java/com/google/gerrit/sshd/commands/ConvertRefStorage.java b/java/com/google/gerrit/sshd/commands/ConvertRefStorage.java
new file mode 100644
index 0000000..21d90ed
--- /dev/null
+++ b/java/com/google/gerrit/sshd/commands/ConvertRefStorage.java
@@ -0,0 +1,91 @@
+// Copyright (C) 2021 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.sshd.commands;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.server.git.DelegateRepository;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+import java.io.IOException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.lib.Repository;
+import org.kohsuke.args4j.Option;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@CommandMetaData(
+ name = "convert-ref-storage",
+ description = "Convert ref storage to reftable (experimental)",
+ runsAt = MASTER_OR_SLAVE)
+public class ConvertRefStorage extends SshCommand {
+ @Inject private GitRepositoryManager repoManager;
+
+ private enum StorageFormatOption {
+ reftable,
+ refdir,
+ }
+
+ @Option(
+ name = "--format",
+ usage = "storage format to convert to (reftable or refdir) (default: reftable)")
+ private StorageFormatOption storageFormat = StorageFormatOption.reftable;
+
+ @Option(
+ name = "--backup",
+ aliases = {"-b"},
+ usage = "create backup of old ref storage format (default: true)")
+ private boolean backup = true;
+
+ @Option(
+ name = "--reflogs",
+ aliases = {"-r"},
+ usage = "write reflogs to reftable (default: true)")
+ private boolean writeLogs = true;
+
+ @Option(
+ name = "--project",
+ aliases = {"-p"},
+ metaVar = "PROJECT",
+ required = true,
+ usage = "project for which the storage format should be changed")
+ private ProjectState projectState;
+
+ @Override
+ public void run() throws Exception {
+ enableGracefulStop();
+ Project.NameKey projectName = projectState.getNameKey();
+ try (Repository repo = repoManager.openRepository(projectName)) {
+ if (repo instanceof DelegateRepository) {
+ ((DelegateRepository) repo).convertRefStorage(storageFormat.name(), writeLogs, backup);
+ } else {
+ checkState(
+ repo instanceof FileRepository, "Repository is not an instance of FileRepository!");
+ ((FileRepository) repo).convertRefStorage(storageFormat.name(), writeLogs, backup);
+ }
+ } catch (RepositoryNotFoundException e) {
+ throw die("'" + projectName + "': not a git archive", e);
+ } catch (IOException e) {
+ throw die("Error converting: '" + projectName + "': " + e.getMessage(), e);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index cfd17f4..8ee6a0d 100644
--- a/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -47,6 +47,7 @@
command(gerrit, AproposCommand.class);
command(gerrit, BanCommitCommand.class);
command(gerrit, CloseConnection.class);
+ command(gerrit, ConvertRefStorage.class);
command(gerrit, FlushCaches.class);
command(gerrit, ListProjectsCommand.class);
command(gerrit, ListMembersCommand.class);
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index a8aff81..e17f854 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -202,6 +202,8 @@
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -3054,6 +3056,24 @@
}
@Test
+ public void submitToSymref() throws Exception {
+ // Create symref in the origin repository (testRepo references to a local repository)
+ try (Repository repo = repoManager.openRepository(project)) {
+ RefUpdate u = repo.updateRef("refs/heads/master_symref");
+ assertThat(u.link("refs/heads/master")).isEqualTo(Result.NEW);
+ }
+
+ PushOneCommit.Result r = createChange("refs/for/master_symref");
+ String id = r.getChangeId();
+
+ gApi.changes().id(id).current().review(ReviewInput.approve());
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> gApi.changes().id(id).current().submit());
+ assertThat(thrown).hasMessageThat().contains("the target branch is a symbolic ref");
+ }
+
+ @Test
public void check() throws Exception {
PushOneCommit.Result r = createChange();
assertThat(gApi.changes().id(r.getChangeId()).get().problems).isNull();
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
index 0b18503..37b4a1c 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
@@ -30,6 +30,8 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.PushOneCommit.Result;
@@ -40,8 +42,11 @@
import com.google.gerrit.extensions.common.ChangeType;
import com.google.gerrit.extensions.common.DiffInfo;
import com.google.gerrit.extensions.common.FileInfo;
+import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.webui.EditWebLink;
+import com.google.inject.Inject;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -75,6 +80,8 @@
.collect(joining());
private static final String FILE_CONTENT2 = "1st line\n2nd line\n3rd line\n";
+ @Inject private ExtensionRegistry extensionRegistry;
+
private boolean intraline;
private boolean useNewDiffCacheListFiles;
private boolean useNewDiffCacheGetDiff;
@@ -142,6 +149,24 @@
}
@Test
+ public void editWebLinkIncludedInDiff() throws Exception {
+ try (Registration registration = newEditWebLink()) {
+ String fileName = "a_new_file.txt";
+ String fileContent = "First line\nSecond line\n";
+ PushOneCommit.Result result = createChange("Add a file", fileName, fileContent);
+ DiffInfo info =
+ gApi.changes()
+ .id(result.getChangeId())
+ .revision(result.getCommit().name())
+ .file(fileName)
+ .diff();
+ assertThat(info.metaB.editWebLinks).hasSize(1);
+ assertThat(info.metaB.editWebLinks.get(0).url)
+ .isEqualTo("http://edit/" + project + "/" + fileName);
+ }
+ }
+
+ @Test
public void deletedFileIsIncludedInDiff() throws Exception {
gApi.changes().id(changeId).edit().deleteFile(FILE_NAME);
gApi.changes().id(changeId).edit().publish();
@@ -2875,6 +2900,18 @@
assertThat(e).hasMessageThat().isEqualTo("edit not allowed as base");
}
+ private Registration newEditWebLink() {
+ EditWebLink webLink =
+ new EditWebLink() {
+ @Override
+ public WebLinkInfo getEditWebLink(String projectName, String revision, String fileName) {
+ return new WebLinkInfo(
+ "name", "imageURL", "http://edit/" + projectName + "/" + fileName);
+ }
+ };
+ return extensionRegistry.newRegistration().add(webLink);
+ }
+
private String updatedCommitMessage() {
return "An unchanged patchset\n\nChange-Id: " + changeId;
}
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
index bbe7b81..2b37cfd 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
@@ -45,6 +45,7 @@
ImmutableList.of(
"apropos",
"close-connection",
+ "convert-ref-storage",
"flush-caches",
"gc",
"logging",
diff --git a/plugins/replication b/plugins/replication
index 75c44d0..0022a34 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 75c44d0b1dec203859112ad42074eb16839ea353
+Subproject commit 0022a34428cf8bfe4feb0935cdd20b0257bfc8a3
diff --git a/polygerrit-ui/app/api/checks.ts b/polygerrit-ui/app/api/checks.ts
index f026a9d..ee4ea9e 100644
--- a/polygerrit-ui/app/api/checks.ts
+++ b/polygerrit-ui/app/api/checks.ts
@@ -15,11 +15,6 @@
* limitations under the License.
*/
-// IMPORTANT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-// The entire API is currently in DRAFT state.
-// Changes to all type and interfaces are expected.
-// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
export interface ChecksPluginApi {
/**
* Must only be called once. You cannot register twice. You cannot unregister.
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts
index a10cb14..360ff5c 100644
--- a/polygerrit-ui/app/api/diff.ts
+++ b/polygerrit-ui/app/api/diff.ts
@@ -53,6 +53,35 @@
}
/**
+ * Represents a syntax block in a code (e.g. method, function, class, if-else).
+ */
+export interface SyntaxBlock {
+ /** Name of the block (e.g. name of the method/class)*/
+ name: string;
+ /** Where does this block syntatically starts and ends (line number and column).*/
+ range: {
+ /** first line of the block (1-based inclusive). */
+ start_line: number;
+ /**
+ * column of the range start inside the first line (e.g. "{" character ending a function/method)
+ * (1-based inclusive).
+ */
+ start_column: number;
+ /**
+ * last line of the block (1-based inclusive).
+ */
+ end_line: number;
+ /**
+ * column of the block end inside the end line (e.g. "}" character ending a function/method)
+ * (1-based inclusive).
+ */
+ end_column: number;
+ };
+ /** Sub-blocks of the current syntax block (e.g. methods of a class) */
+ children: SyntaxBlock[];
+}
+
+/**
* The DiffFileMetaInfo entity contains meta information about a file diff.
* https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#diff-file-meta-info
*/
@@ -65,6 +94,12 @@
lines: number;
// TODO: Not documented.
language?: string;
+ /**
+ * The first level of syntax blocks tree (outline) within the current file.
+ * It contains an hierarchical structure where each block contains its
+ * sub-blocks (children).
+ */
+ syntax_tree?: SyntaxBlock[];
}
export declare type ChangeType =
@@ -276,10 +311,11 @@
}
| {type: 'magnifier-clicked'}
| {type: 'magnifier-dragged'}
- | {type: 'version-switcher-clicked'; button: 'base' | 'revision'}
+ | {type: 'version-switcher-clicked'; button: 'base' | 'revision' | 'switch'}
| {type: 'zoom-level-changed'; scale: number | 'fit'}
| {type: 'follow-mouse-changed'; value: boolean}
- | {type: 'background-color-changed'; value: string};
+ | {type: 'background-color-changed'; value: string}
+ | {type: 'automatic-blink-changed'; value: boolean};
export enum GrDiffLineType {
ADD = 'add',
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 827cbf1..b4d25a6 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
@@ -122,6 +122,7 @@
DraftInfo,
isDraftThread,
isRobot,
+ isUnresolved,
} from '../../../utils/comment-util';
import {
PolymerDeepPropertyChange,
@@ -901,6 +902,13 @@
return false;
}
+ _computeShowUnresolved(threads?: CommentThread[]) {
+ // If all threads are resolved and the Comments Tab is opened then show
+ // all threads instead
+ if (!threads?.length) return true;
+ return threads.filter(thread => isUnresolved(thread)).length > 0;
+ }
+
_robotCommentCountPerPatchSet(threads: CommentThread[]) {
return threads.reduce((robotCommentCountMap, thread) => {
const comments = thread.comments;
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index ae4ad79..6025268 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -570,7 +570,7 @@
logged-in="[[_loggedIn]]"
comment-tab-state="[[_tabState.commentTab]]"
only-show-robot-comments-with-human-reply=""
- unresolved-only
+ unresolved-only="[[_computeShowUnresolved(_commentThreads)]]"
show-comment-context
></gr-thread-list>
</template>
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 bb3c975..ca16ec0 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
@@ -618,52 +618,20 @@
return changeComments.computeCommentsString(patchRange, file.__path, file);
}
- _computeDraftCount(
- changeComments?: ChangeComments,
- patchRange?: PatchRange,
- path?: string
- ) {
- if (
- changeComments === undefined ||
- patchRange === undefined ||
- path === undefined
- ) {
- return '';
- }
- return (
- changeComments.computeDraftCount({
- patchNum: patchRange.basePatchNum,
- path,
- }) +
- changeComments.computeDraftCount({
- patchNum: patchRange.patchNum,
- path,
- }) +
- changeComments.computePortedDraftCount(
- {
- patchNum: patchRange.patchNum,
- basePatchNum: patchRange.basePatchNum,
- },
- path
- )
- );
- }
-
/**
* Computes a string with the number of drafts.
*/
_computeDraftsString(
changeComments?: ChangeComments,
patchRange?: PatchRange,
- path?: string
+ file?: NormalizedFileInfo
) {
- const draftCount = this._computeDraftCount(
- changeComments,
+ const draftCount = changeComments?.computeDraftCountForFile(
patchRange,
- path
+ file
);
- if (draftCount === '') return draftCount;
- return pluralize(draftCount, 'draft');
+ if (draftCount === 0) return '';
+ return pluralize(Number(draftCount), 'draft');
}
/**
@@ -672,12 +640,11 @@
_computeDraftsStringMobile(
changeComments?: ChangeComments,
patchRange?: PatchRange,
- path?: string
+ file?: NormalizedFileInfo
) {
- const draftCount = this._computeDraftCount(
- changeComments,
+ const draftCount = changeComments?.computeDraftCountForFile(
patchRange,
- path
+ file
);
return draftCount === 0 ? '' : `${draftCount}d`;
}
@@ -688,23 +655,23 @@
_computeCommentsStringMobile(
changeComments?: ChangeComments,
patchRange?: PatchRange,
- path?: string
+ file?: NormalizedFileInfo
) {
if (
changeComments === undefined ||
patchRange === undefined ||
- path === undefined
+ file === undefined
) {
return '';
}
const commentThreadCount =
changeComments.computeCommentThreadCount({
patchNum: patchRange.basePatchNum,
- path,
+ path: file.__path,
}) +
changeComments.computeCommentThreadCount({
patchNum: patchRange.patchNum,
- path,
+ path: file.__path,
});
return commentThreadCount === 0 ? '' : `${commentThreadCount}c`;
}
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
index 59338df..40bd5bc 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
@@ -423,8 +423,7 @@
<span class="drafts"
><!-- This comments ensure that span is empty when the function
returns empty string.
- -->[[_computeDraftsString(changeComments, patchRange,
- file.__path)]]<!-- This comments ensure that span is empty when
+ -->[[_computeDraftsString(changeComments, patchRange, file)]]<!-- This comments ensure that span is empty when
the function returns empty string.
--></span
>
@@ -450,14 +449,14 @@
><!-- This comments ensure that span is empty when the function
returns empty string.
-->[[_computeDraftsStringMobile(changeComments, patchRange,
- file.__path)]]<!-- This comments ensure that span is empty when
+ file)]]<!-- This comments ensure that span is empty when
the function returns empty string.
--></span
>
<span
><!--
-->[[_computeCommentsStringMobile(changeComments, patchRange,
- file.__path)]]<!--
+ file)]]<!--
--></span
>
<span class="noCommentsScreenReaderText">
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
index b8ba86c..dcc2e46 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
@@ -360,103 +360,103 @@
assert.equal(
element._computeCommentsStringMobile(element.changeComments, parentTo1
- , '/COMMIT_MSG'), '2c');
+ , {__path: '/COMMIT_MSG'}), '2c');
assert.equal(
element._computeCommentsStringMobile(element.changeComments, _1To2
- , '/COMMIT_MSG'), '3c');
+ , {__path: '/COMMIT_MSG'}), '3c');
assert.equal(
element._computeDraftsString(element.changeComments, parentTo1,
- 'unresolved.file'), '1 draft');
+ {__path: 'unresolved.file'}), '1 draft');
assert.equal(
element._computeDraftsString(element.changeComments, _1To2,
- 'unresolved.file'), '1 draft');
+ {__path: 'unresolved.file'}), '1 draft');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, parentTo1,
- 'unresolved.file'), '1d');
+ {__path: 'unresolved.file'}), '1d');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, _1To2,
- 'unresolved.file'), '1d');
+ {__path: 'unresolved.file'}), '1d');
assert.equal(
element._computeCommentsStringMobile(
element.changeComments,
parentTo1,
- 'myfile.txt'
+ {__path: 'myfile.txt'}
), '1c');
assert.equal(
element._computeCommentsStringMobile(element.changeComments, _1To2,
- 'myfile.txt'), '3c');
+ {__path: 'myfile.txt'}), '3c');
assert.equal(
element._computeDraftsString(element.changeComments, parentTo1,
- 'myfile.txt'), '');
+ {__path: 'myfile.txt'}), '');
assert.equal(
element._computeDraftsString(element.changeComments, _1To2,
- 'myfile.txt'), '');
+ {__path: 'myfile.txt'}), '');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, parentTo1,
- 'myfile.txt'), '');
+ {__path: 'myfile.txt'}), '');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, _1To2,
- 'myfile.txt'), '');
+ {__path: 'myfile.txt'}), '');
assert.equal(
element._computeCommentsStringMobile(
element.changeComments,
parentTo1,
- 'file_added_in_rev2.txt'
+ {__path: 'file_added_in_rev2.txt'}
), '');
assert.equal(
element._computeCommentsStringMobile(element.changeComments, _1To2,
- 'file_added_in_rev2.txt'), '');
+ {__path: 'file_added_in_rev2.txt'}), '');
assert.equal(
element._computeDraftsString(element.changeComments, parentTo1,
- 'file_added_in_rev2.txt'), '');
+ {__path: 'file_added_in_rev2.txt'}), '');
assert.equal(
element._computeDraftsString(element.changeComments, _1To2,
- 'file_added_in_rev2.txt'), '');
+ {__path: 'file_added_in_rev2.txt'}), '');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, parentTo1,
- 'file_added_in_rev2.txt'), '');
+ {__path: 'file_added_in_rev2.txt'}), '');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, _1To2,
- 'file_added_in_rev2.txt'), '');
+ {__path: 'file_added_in_rev2.txt'}), '');
assert.equal(
element._computeCommentsStringMobile(
element.changeComments,
parentTo2,
- '/COMMIT_MSG'
+ {__path: '/COMMIT_MSG'}
), '1c');
assert.equal(
element._computeCommentsStringMobile(element.changeComments, _1To2,
- '/COMMIT_MSG'), '3c');
+ {__path: '/COMMIT_MSG'}), '3c');
assert.equal(
element._computeDraftsString(element.changeComments, parentTo1,
- '/COMMIT_MSG'), '2 drafts');
+ {__path: '/COMMIT_MSG'}), '2 drafts');
assert.equal(
element._computeDraftsString(element.changeComments, _1To2,
- '/COMMIT_MSG'), '2 drafts');
+ {__path: '/COMMIT_MSG'}), '2 drafts');
assert.equal(
element._computeDraftsStringMobile(
element.changeComments,
parentTo1,
- '/COMMIT_MSG'
+ {__path: '/COMMIT_MSG'}
), '2d');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, _1To2,
- '/COMMIT_MSG'), '2d');
+ {__path: '/COMMIT_MSG'}), '2d');
assert.equal(
element._computeCommentsStringMobile(
element.changeComments,
parentTo2,
- 'myfile.txt'
+ {__path: 'myfile.txt'}
), '2c');
assert.equal(
element._computeCommentsStringMobile(element.changeComments, _1To2,
- 'myfile.txt'), '3c');
+ {__path: 'myfile.txt'}), '3c');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, parentTo2,
- 'myfile.txt'), '');
+ {__path: 'myfile.txt'}), '');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, _1To2,
- 'myfile.txt'), '');
+ {__path: 'myfile.txt'}), '');
});
test('_reviewedTitle', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
index fed02a7..26af2a2 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -62,6 +62,7 @@
const LABEL_TITLE_SCORE_PATTERN = /^(-?)([A-Za-z0-9-]+?)([+-]\d+)?[.]?$/;
const UPLOADED_NEW_PATCHSET_PATTERN = /Uploaded patch set (\d+)./;
const MERGED_PATCHSET_PATTERN = /(\d+) is the latest approved patch-set/;
+const VOTE_RESET_TEXT = '0 (vote reset)';
declare global {
interface HTMLElementTagNameMap {
@@ -466,7 +467,7 @@
)
.map(ms => {
const label = ms?.[2];
- const value = ms?.[1] === '-' ? 'removed' : ms?.[3];
+ const value = ms?.[1] === '-' ? VOTE_RESET_TEXT : ms?.[3];
return {label, value};
});
}
@@ -479,7 +480,7 @@
if (!score.value) {
return '';
}
- if (score.value === 'removed') {
+ if (score.value.includes(VOTE_RESET_TEXT)) {
return 'removed';
}
const classes = [];
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts
index 8cc7a3f..73afde8 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts
@@ -51,6 +51,7 @@
import {appContext} from '../../../services/app-context';
import {CommentSide, Side} from '../../../constants/constants';
import {pluralize} from '../../../utils/string-util';
+import {NormalizedFileInfo} from '../../change/gr-file-list/gr-file-list';
export type CommentIdToCommentThreadMap = {
[urlEncodedCommentId: string]: CommentThread;
@@ -522,6 +523,33 @@
.length;
}
+ computeDraftCountForFile(patchRange?: PatchRange, file?: NormalizedFileInfo) {
+ if (patchRange === undefined || file === undefined) {
+ return 0;
+ }
+ const getCommentForPath = (path?: string) => {
+ if (!path) return 0;
+ return (
+ this.computeDraftCount({
+ patchNum: patchRange.basePatchNum,
+ path,
+ }) +
+ this.computeDraftCount({
+ patchNum: patchRange.patchNum,
+ path,
+ }) +
+ this.computePortedDraftCount(
+ {
+ patchNum: patchRange.patchNum,
+ basePatchNum: patchRange.basePatchNum,
+ },
+ path
+ )
+ );
+ };
+ return getCommentForPath(file.__path) + getCommentForPath(file.old_path);
+ }
+
/**
* @param includeUnmodified Included unmodified status of the file in the
* comment string or not. For files we opt of chip instead of a string.
@@ -537,6 +565,14 @@
if (!patchRange) return '';
const threads = this.getThreadsBySideForFile({path}, patchRange);
+ if (changeFileInfo?.old_path) {
+ threads.push(
+ ...this.getThreadsBySideForFile(
+ {path: changeFileInfo.old_path},
+ patchRange
+ )
+ );
+ }
const commentThreadCount = threads.filter(thread => !isDraftThread(thread))
.length;
const unresolvedCount = threads.reduce((cnt, thread) => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
index 7c01a95..5ba3606 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
@@ -97,8 +97,13 @@
return section;
}
+ let diffInfo;
+ let renderPrefs;
+
setup(() => {
- builder = new GrDiffBuilder({content: []}, prefs, null, []);
+ diffInfo = {content: []};
+ renderPrefs = {};
+ builder = new GrDiffBuilder(diffInfo, prefs, null, [], renderPrefs);
});
test('no +10 buttons for 10 or less lines', () => {
@@ -149,6 +154,70 @@
assert.include([...buttons[0].classList.values()], 'aboveButton');
assert.include([...buttons[1].classList.values()], 'aboveButton');
});
+
+ suite('with block expansion', () => {
+ setup(() => {
+ builder._numLinesLeft = 50;
+ renderPrefs.use_block_expansion = true;
+ diffInfo.meta_b = {
+ syntax_tree: [],
+ };
+ });
+
+ test('context control with block expansion at the top', () => {
+ const section = createContextSectionForGroups({offset: 0, count: 20});
+
+ const fullExpansionButtons = section
+ .querySelectorAll('.fullExpansion gr-button');
+ const partialExpansionButtons = section
+ .querySelectorAll('.partialExpansion gr-button');
+ const blockExpansionButtons = section
+ .querySelectorAll('.blockExpansion gr-button');
+ assert.equal(fullExpansionButtons.length, 1);
+ assert.equal(partialExpansionButtons.length, 1);
+ assert.equal(blockExpansionButtons.length, 1);
+ assert.equal(blockExpansionButtons[0].textContent, '+Block');
+ assert.include([...blockExpansionButtons[0].classList.values()],
+ 'belowButton');
+ });
+
+ test('context control in the middle', () => {
+ const section = createContextSectionForGroups({offset: 10, count: 20});
+
+ const fullExpansionButtons = section
+ .querySelectorAll('.fullExpansion gr-button');
+ const partialExpansionButtons = section
+ .querySelectorAll('.partialExpansion gr-button');
+ const blockExpansionButtons = section
+ .querySelectorAll('.blockExpansion gr-button');
+ assert.equal(fullExpansionButtons.length, 1);
+ assert.equal(partialExpansionButtons.length, 2);
+ assert.equal(blockExpansionButtons.length, 2);
+ assert.equal(blockExpansionButtons[0].textContent, '+Block');
+ assert.equal(blockExpansionButtons[1].textContent, '+Block');
+ assert.include([...blockExpansionButtons[0].classList.values()],
+ 'aboveButton');
+ assert.include([...blockExpansionButtons[1].classList.values()],
+ 'belowButton');
+ });
+
+ test('context control at the bottom', () => {
+ const section = createContextSectionForGroups({offset: 30, count: 20});
+
+ const fullExpansionButtons = section
+ .querySelectorAll('.fullExpansion gr-button');
+ const partialExpansionButtons = section
+ .querySelectorAll('.partialExpansion gr-button');
+ const blockExpansionButtons = section
+ .querySelectorAll('.blockExpansion gr-button');
+ assert.equal(fullExpansionButtons.length, 1);
+ assert.equal(partialExpansionButtons.length, 1);
+ assert.equal(blockExpansionButtons.length, 1);
+ assert.equal(blockExpansionButtons[0].textContent, '+Block');
+ assert.include([...blockExpansionButtons[0].classList.values()],
+ 'aboveButton');
+ });
+ });
});
test('newlines 1', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
index c8b69f0..d5e6ecd 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
@@ -20,6 +20,7 @@
DiffContextExpandedExternalDetail,
MovedLinkClickedEventDetail,
RenderPreferences,
+ SyntaxBlock,
} from '../../../api/diff';
import {getBaseUrl} from '../../../utils/url-util';
import {GrDiffLine, GrDiffLineType, LineNumber} from '../gr-diff/gr-diff-line';
@@ -73,6 +74,19 @@
}
}
+function findMostNestedContainingBlock(
+ lineNum: number,
+ blocks?: SyntaxBlock[]
+): SyntaxBlock | undefined {
+ const containingBlock = blocks?.find(
+ ({range}) => range.start_line < lineNum && range.end_line > lineNum
+ );
+ const containingChildBlock = containingBlock
+ ? findMostNestedContainingBlock(lineNum, containingBlock?.children)
+ : undefined;
+ return containingChildBlock || containingBlock;
+}
+
export abstract class GrDiffBuilder {
private readonly _diff: DiffInfo;
@@ -306,6 +320,7 @@
);
}
+ // TODO(renanoliveira): Move context controls to polymer component (or at least a separate class).
_createContextControls(
section: HTMLElement,
contextGroups: GrDiffGroup[],
@@ -314,9 +329,9 @@
const leftStart = contextGroups[0].lineRange.left.start_line;
const leftEnd =
contextGroups[contextGroups.length - 1].lineRange.left.end_line;
- const numLines = leftEnd - leftStart + 1;
-
- if (numLines === 0) console.error('context group without lines');
+ const rightStart = contextGroups[0].lineRange.right.start_line;
+ const rightEnd =
+ contextGroups[contextGroups.length - 1].lineRange.right.end_line;
const firstGroupIsSkipped = !!contextGroups[0].skip;
const lastGroupIsSkipped = !!contextGroups[contextGroups.length - 1].skip;
@@ -335,7 +350,8 @@
contextGroups,
showAbove,
showBelow,
- numLines
+ rightStart,
+ rightEnd
)
);
if (showBelow) {
@@ -354,8 +370,12 @@
contextGroups: GrDiffGroup[],
showAbove: boolean,
showBelow: boolean,
- numLines: number
+ rightStart: number,
+ rightEnd: number
): HTMLElement {
+ const numLines = rightEnd - rightStart + 1;
+ if (numLines === 0) console.error('context group without lines');
+
const row = this._createElement('tr', 'contextDivider');
if (!(showAbove && showBelow)) {
row.classList.add('collapsed');
@@ -364,13 +384,55 @@
const element = this._createElement('td', 'dividerCell');
row.appendChild(element);
- const showAllContainer = this._createElement('div', 'aboveBelowButtons');
+ const showAllContainer = this._createExpandAllButtonContainer(
+ section,
+ contextGroups,
+ showAbove,
+ showBelow,
+ numLines
+ );
element.appendChild(showAllContainer);
+ const showPartialLinks = numLines > PARTIAL_CONTEXT_AMOUNT;
+ if (showPartialLinks) {
+ const partialExpansionContainer = this._createPartialExpansionButtons(
+ section,
+ contextGroups,
+ showAbove,
+ showBelow,
+ numLines
+ );
+ if (partialExpansionContainer) {
+ element.appendChild(partialExpansionContainer);
+ }
+ const blockExpansionContainer = this._createBlockExpansionButtons(
+ section,
+ contextGroups,
+ showAbove,
+ showBelow,
+ rightStart,
+ rightEnd,
+ numLines
+ );
+ if (blockExpansionContainer) {
+ element.appendChild(blockExpansionContainer);
+ }
+ }
+ return row;
+ }
+
+ private _createExpandAllButtonContainer(
+ section: HTMLElement,
+ contextGroups: GrDiffGroup[],
+ showAbove: boolean,
+ showBelow: boolean,
+ numLines: number
+ ) {
const showAllButton = this._createContextButton(
ContextButtonType.ALL,
section,
contextGroups,
+ numLines,
numLines
);
showAllButton.classList.add(
@@ -380,61 +442,131 @@
? 'aboveButton'
: 'belowButton'
);
+ const showAllContainer = this._createElement(
+ 'div',
+ 'aboveBelowButtons fullExpansion'
+ );
showAllContainer.appendChild(showAllButton);
+ return showAllContainer;
+ }
- const showPartialLinks = numLines > PARTIAL_CONTEXT_AMOUNT;
- if (showPartialLinks) {
- const container = this._createElement('div', 'aboveBelowButtons');
- if (showAbove) {
- container.appendChild(
- this._createContextButton(
- ContextButtonType.ABOVE,
- section,
- contextGroups,
- numLines
- )
- );
- }
- if (showBelow) {
- container.appendChild(
- this._createContextButton(
- ContextButtonType.BELOW,
- section,
- contextGroups,
- numLines
- )
- );
- }
- element.appendChild(container);
- if (this._renderPrefs?.use_block_expansion) {
- const blockExpansionContainer = this._createElement(
- 'div',
- 'aboveBelowButtons'
- );
- if (showAbove) {
- blockExpansionContainer.appendChild(
- this._createContextButton(
- ContextButtonType.BLOCK_ABOVE,
- section,
- contextGroups,
- numLines
- )
- );
- }
- if (showBelow) {
- blockExpansionContainer.appendChild(
- this._createContextButton(
- ContextButtonType.BLOCK_BELOW,
- section,
- contextGroups,
- numLines
- )
- );
- }
- element.appendChild(blockExpansionContainer);
+ private _createPartialExpansionButtons(
+ section: HTMLElement,
+ contextGroups: GrDiffGroup[],
+ showAbove: boolean,
+ showBelow: boolean,
+ numLines: number
+ ) {
+ let aboveButton;
+ let belowButton;
+ if (showAbove) {
+ aboveButton = this._createContextButton(
+ ContextButtonType.ABOVE,
+ section,
+ contextGroups,
+ numLines,
+ PARTIAL_CONTEXT_AMOUNT
+ );
+ }
+ if (showBelow) {
+ belowButton = this._createContextButton(
+ ContextButtonType.BELOW,
+ section,
+ contextGroups,
+ numLines,
+ PARTIAL_CONTEXT_AMOUNT
+ );
+ }
+ if (aboveButton || belowButton) {
+ const partialExpansionContainer = this._createElement(
+ 'div',
+ 'aboveBelowButtons partialExpansion'
+ );
+ aboveButton && partialExpansionContainer.appendChild(aboveButton);
+ belowButton && partialExpansionContainer.appendChild(belowButton);
+ return partialExpansionContainer;
+ }
+ return undefined;
+ }
+
+ private _createBlockExpansionButtons(
+ section: HTMLElement,
+ contextGroups: GrDiffGroup[],
+ showAbove: boolean,
+ showBelow: boolean,
+ rightStart: number,
+ rightEnd: number,
+ numLines: number
+ ) {
+ if (!this._renderPrefs?.use_block_expansion) {
+ return undefined;
+ }
+ let aboveBlockButton;
+ let belowBlockButton;
+ const rightSyntaxTree = this._diff.meta_b.syntax_tree;
+ if (showAbove) {
+ aboveBlockButton = this._createBlockButton(
+ section,
+ contextGroups,
+ ContextButtonType.BLOCK_ABOVE,
+ numLines,
+ rightStart - 1,
+ rightSyntaxTree
+ );
+ }
+ if (showBelow) {
+ belowBlockButton = this._createBlockButton(
+ section,
+ contextGroups,
+ ContextButtonType.BLOCK_BELOW,
+ numLines,
+ rightEnd + 1,
+ rightSyntaxTree
+ );
+ }
+ if (aboveBlockButton || belowBlockButton) {
+ const blockExpansionContainer = this._createElement(
+ 'div',
+ 'blockExpansion aboveBelowButtons'
+ );
+ aboveBlockButton && blockExpansionContainer.appendChild(aboveBlockButton);
+ belowBlockButton && blockExpansionContainer.appendChild(belowBlockButton);
+ return blockExpansionContainer;
+ }
+ return undefined;
+ }
+
+ private _createBlockButton(
+ section: HTMLElement,
+ contextGroups: GrDiffGroup[],
+ buttonType: ContextButtonType,
+ numLines: number,
+ referenceLine: number,
+ syntaxTree?: SyntaxBlock[]
+ ) {
+ const containingBlock = findMostNestedContainingBlock(
+ referenceLine,
+ syntaxTree
+ );
+ let linesToExpand = numLines;
+ if (containingBlock) {
+ const {range} = containingBlock;
+ const targetLine =
+ buttonType === ContextButtonType.BLOCK_ABOVE
+ ? range.end_line
+ : range.start_line;
+ const distanceToTargetLine = Math.abs(targetLine - referenceLine);
+ if (distanceToTargetLine < numLines) {
+ linesToExpand = distanceToTargetLine;
}
}
- return row;
+ return this._createContextButton(
+ buttonType,
+ section,
+ contextGroups,
+ numLines,
+ linesToExpand
+ );
}
/**
@@ -469,10 +601,9 @@
type: ContextButtonType,
section: HTMLElement,
contextGroups: GrDiffGroup[],
- numLines: number
+ numLines: number,
+ linesToExpand: number
) {
- const linesToExpand =
- type === ContextButtonType.ALL ? numLines : PARTIAL_CONTEXT_AMOUNT;
const button = this._createElement('gr-button', 'showContext');
button.classList.add('contextControlButton');
button.setAttribute('link', 'true');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
index 1d6bc95..2a4b250 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
@@ -394,7 +394,7 @@
>
Base
</paper-button>
- <paper-fab mini icon="gr-icons:swapHoriz" @click="${this.toggleImage}">
+ <paper-fab mini icon="gr-icons:swapHoriz" @click="${this.manualBlink}">
</paper-fab>
<paper-button
class="right"
@@ -634,7 +634,14 @@
);
}
- toggleImage() {
+ manualBlink() {
+ this.toggleImage();
+ this.dispatchEvent(
+ createEvent({type: 'version-switcher-clicked', button: 'switch'})
+ );
+ }
+
+ private toggleImage() {
if (this.baseUrl && this.revisionUrl) {
this.baseSelected = !this.baseSelected;
}
@@ -651,6 +658,9 @@
this.automaticBlinkTimer = undefined;
}
}
+ this.dispatchEvent(
+ createEvent({type: 'automatic-blink-changed', value: this.automaticBlink})
+ );
}
private setBlinkInterval() {
diff --git a/polygerrit-ui/app/samples/add-from-favorite-reviewers.js b/polygerrit-ui/app/samples/add-from-favorite-reviewers.js
index 76b2787..1616ef3 100644
--- a/polygerrit-ui/app/samples/add-from-favorite-reviewers.js
+++ b/polygerrit-ui/app/samples/add-from-favorite-reviewers.js
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/**
* This plugin will a button to quickly add favorite reviewers to
* reviewers in reply dialog.
@@ -142,4 +143,4 @@
'reply-reviewers', ReviewerShortcut.is, {slot: 'right'});
plugin.registerCustomComponent(
'reply-reviewers', ReviewerShortcutContent.is, {slot: 'below'});
-});
\ No newline at end of file
+});
diff --git a/polygerrit-ui/app/samples/bind-parameters.js b/polygerrit-ui/app/samples/bind-parameters.js
index 30c7c3d..4527a80 100644
--- a/polygerrit-ui/app/samples/bind-parameters.js
+++ b/polygerrit-ui/app/samples/bind-parameters.js
@@ -14,15 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-// Element class exists in all browsers:
-// https://developer.mozilla.org/en-US/docs/Web/API/Element
-// Rename it to PolymerElement to avoid conflicts. Also,
-// typescript reports the following error:
-// error TS2451: Cannot redeclare block-scoped variable 'Element'.
-const {html, Element: PolymerElement} = Polymer;
-
-class MyBindSample extends PolymerElement {
+class MyBindSample extends Polymer.Element {
static get is() { return 'my-bind-sample'; }
static get properties() {
@@ -39,7 +31,7 @@
}
static get template() {
- return html`
+ return Polymer.html`
Template example: Patchset number [[revision._number]]. <br/>
Computed example: [[computedExample]].
`;
diff --git a/polygerrit-ui/app/samples/coverage-plugin.js b/polygerrit-ui/app/samples/coverage-plugin.js
deleted file mode 100644
index 8d321c7..0000000
--- a/polygerrit-ui/app/samples/coverage-plugin.js
+++ /dev/null
@@ -1,76 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-function populateWithDummyData(coverageData) {
- coverageData['/COMMIT_MSG'] = {
- linesMissingCoverage: [3, 4, 7, 14],
- totalLines: 14,
- changeNum: 94,
- patchNum: 2,
- };
-
- // more coverage info on other files
-}
-
-/**
- * This plugin will add a toggler on file diff page to
- * display fake coverage data.
- *
- * As the fake coverage data only provided for COMMIT_MSG file,
- * so it will only work for COMMIT_MSG file diff.
- */
-Gerrit.install(plugin => {
- const coverageData = {};
- let displayCoverage = false;
- const annotationApi = plugin.annotationApi();
- const styleApi = plugin.styles();
-
- const coverageStyle = styleApi.css('background-color: #EF9B9B !important');
- const emptyStyle = styleApi.css('');
-
- annotationApi.setLayer(context => {
- if (Object.keys(coverageData).length === 0) {
- // Coverage data is not ready yet.
- return;
- }
- const path = context.path;
- const line = context.line;
- // Highlight lines missing coverage with this background color if
- // coverage should be displayed, else do nothing.
- const annotationStyle = displayCoverage
- ? coverageStyle
- : emptyStyle;
-
- // ideally should check to make sure its the same patch for same change
- // for demo purpose, this is only checking to make sure we have fake data
- if (coverageData[path]) {
- const linesMissingCoverage = coverageData[path].linesMissingCoverage;
- if (linesMissingCoverage.includes(line.afterNumber)) {
- context.annotateRange(0, line.text.length, annotationStyle, 'right');
- context.annotateLineNumber(annotationStyle, 'right');
- }
- }
- }).enableToggleCheckbox('Display Coverage', checkbox => {
- populateWithDummyData(coverageData);
- checkbox.disabled = false;
- checkbox.onclick = e => {
- displayCoverage = e.target.checked;
- Object.keys(coverageData).forEach(file => {
- annotationApi.notify(file, 0, coverageData[file].totalLines, 'right');
- });
- };
- });
-});
diff --git a/polygerrit-ui/app/samples/extra-column-on-file-list.js b/polygerrit-ui/app/samples/extra-column-on-file-list.js
index 2e37c01..c64bcd4 100644
--- a/polygerrit-ui/app/samples/extra-column-on-file-list.js
+++ b/polygerrit-ui/app/samples/extra-column-on-file-list.js
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/**
* This plugin will an extra column to file list on change page to show
* the first character of the path.
@@ -74,4 +75,4 @@
'change-view-file-list-header-prepend', ColumnHeader.is);
plugin.registerDynamicCustomComponent(
'change-view-file-list-content-prepend', ColumnContent.is);
-});
\ No newline at end of file
+});
diff --git a/polygerrit-ui/app/samples/lgtm-plugin.js b/polygerrit-ui/app/samples/lgtm-plugin.js
index 9de1496..537b1fa 100644
--- a/polygerrit-ui/app/samples/lgtm-plugin.js
+++ b/polygerrit-ui/app/samples/lgtm-plugin.js
@@ -14,8 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/**
- * This plugin will +1 on Code-Review label if detect that you have
+ * This plugin will +1 on Code-Review label if it detects that you have
* LGTM as start of your reply.
*/
Gerrit.install(plugin => {
@@ -29,4 +30,4 @@
replyApi.setLabelValue(label, '+1');
}
});
-});
\ No newline at end of file
+});
diff --git a/polygerrit-ui/app/samples/repo-command.js b/polygerrit-ui/app/samples/repo-command.js
index 4f64059..acecd7d 100644
--- a/polygerrit-ui/app/samples/repo-command.js
+++ b/polygerrit-ui/app/samples/repo-command.js
@@ -15,14 +15,7 @@
* limitations under the License.
*/
-// Element class exists in all browsers:
-// https://developer.mozilla.org/en-US/docs/Web/API/Element
-// Rename it to PolymerElement to avoid conflicts. Also,
-// typescript reports the following error:
-// error TS2451: Cannot redeclare block-scoped variable 'Element'.
-const {html, Element: PolymerElement} = Polymer;
-
-class RepoCommandLow extends PolymerElement {
+class RepoCommandLow extends Polymer.Element {
static get is() { return 'repo-command-low'; }
static get properties() {
@@ -32,20 +25,16 @@
}
static get template() {
- return html`
- <style include="shared-styles">
- :host {
- display: block;
- margin-bottom: var(--spacing-xxl);
- }
- </style>
- <h3>Low-level bork</h3>
- <gr-button
- on-click="_handleCommandTap"
- >
- Low-level bork
- </gr-button>
- `;
+ return Polymer.html`
+ <style include="shared-styles">
+ :host {
+ display: block;
+ margin-bottom: var(--spacing-xxl);
+ }
+ </style>
+ <h3>Plugin Bork</h3>
+ <gr-button on-click="_handleCommandTap">Bork</gr-button>
+ `;
}
connectedCallback() {
@@ -56,32 +45,16 @@
}
_handleCommandTap() {
- alert('(softly) bork, bork.');
+ alert('bork');
}
}
-// register the custom component
customElements.define(RepoCommandLow.is, RepoCommandLow);
/**
- * This plugin will add two new commands in command page for
- * All-Projects.
- *
- * The added commands will simply alert you when click.
+ * This plugin adds a new command to the command page of the repo All-Projects.
*/
Gerrit.install(plugin => {
- // High-level API
- plugin.project()
- .createCommand('Bork', (repoName, projectConfig) => {
- if (repoName !== 'All-Projects') {
- return false;
- }
- })
- .onTap(() => {
- alert('Bork, bork!');
- });
-
- // Low-level API
plugin.registerCustomComponent(
'repo-command', 'repo-command-low');
});
diff --git a/polygerrit-ui/app/samples/some-screen.js b/polygerrit-ui/app/samples/some-screen.js
index c600fe4..8edaaa9 100644
--- a/polygerrit-ui/app/samples/some-screen.js
+++ b/polygerrit-ui/app/samples/some-screen.js
@@ -15,14 +15,7 @@
* limitations under the License.
*/
-// Element class exists in all browsers:
-// https://developer.mozilla.org/en-US/docs/Web/API/Element
-// Rename it to PolymerElement to avoid conflicts. Also,
-// typescript reports the following error:
-// error TS2451: Cannot redeclare block-scoped variable 'Element'.
-const {html, Element: PolymerElement} = Polymer;
-
-class SomeScreenMain extends PolymerElement {
+class SomeScreenMain extends Polymer.Element {
static get is() { return 'some-screen-main'; }
static get properties() {
@@ -32,7 +25,7 @@
}
static get template() {
- return html`
+ return Polymer.html`
This is the <b>main</b> plugin screen at [[token]]
<ul>
<li><a href$="[[rootUrl]]/bar">without component</a></li>
@@ -46,7 +39,6 @@
}
}
-// register the custom component
customElements.define(SomeScreenMain.is, SomeScreenMain);
/**
diff --git a/polygerrit-ui/app/samples/suggest-vote.js b/polygerrit-ui/app/samples/suggest-vote.js
index b3c3046..10d0d4a 100644
--- a/polygerrit-ui/app/samples/suggest-vote.js
+++ b/polygerrit-ui/app/samples/suggest-vote.js
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/**
* This plugin will upgrade your +1 on Code-Review label
* to +2 and show a message below the voting labels.
@@ -29,8 +30,7 @@
if (wasSuggested && name === CODE_REVIEW) {
replyApi.showMessage('');
wasSuggested = false;
- } else if (replyApi.getLabelValue(CODE_REVIEW) === '+1' &&
- !wasSuggested) {
+ } else if (replyApi.getLabelValue(CODE_REVIEW) === '+1' && !wasSuggested) {
replyApi.setLabelValue(CODE_REVIEW, '+2');
replyApi.showMessage(`Suggested ${CODE_REVIEW} upgrade: +2`);
wasSuggested = true;
diff --git a/polygerrit-ui/app/types/diff.ts b/polygerrit-ui/app/types/diff.ts
index c3b38c6..d30917a0 100644
--- a/polygerrit-ui/app/types/diff.ts
+++ b/polygerrit-ui/app/types/diff.ts
@@ -74,6 +74,11 @@
export interface DiffFileMetaInfo extends DiffFileMetaInfoApi {
/** Links to the file in external sites as a list of WebLinkInfo entries. */
web_links?: WebLinkInfo[];
+ /**
+ * Links to edit the file in external sites as a list of WebLinkInfo
+ * entries.
+ */
+ edit_web_links?: WebLinkInfo[];
}
/**
diff --git a/polygerrit-ui/app/utils/path-list-util.ts b/polygerrit-ui/app/utils/path-list-util.ts
index dda6031..fd922fc 100644
--- a/polygerrit-ui/app/utils/path-list-util.ts
+++ b/polygerrit-ui/app/utils/path-list-util.ts
@@ -64,6 +64,8 @@
return file === SpecialFilePath.PATCHSET_LEVEL_COMMENTS;
}
+// In case there are files with comments on them but they are unchanged, then
+// we explicitly displays the file to render the comments with Unchanged status
export function addUnmodifiedFiles(
files: {[filename: string]: FileInfo},
commentedPaths: {[fileName: string]: boolean}
@@ -73,6 +75,18 @@
if (hasOwnProperty(files, commentedPath) || shouldHideFile(commentedPath)) {
return;
}
+
+ // if file is Renamed but has comments, then do not show the entry for the
+ // old file path name
+ if (
+ Object.values(files).some(
+ file =>
+ file.status === FileInfoStatus.RENAMED &&
+ file.old_path === commentedPath
+ )
+ ) {
+ return;
+ }
// TODO(TS): either change FileInfo to mark delta and size optional
// or fill in 0 here
files[commentedPath] = {
diff --git a/tools/bzl/js.bzl b/tools/bzl/js.bzl
index 7c63cc3..03a29da 100644
--- a/tools/bzl/js.bzl
+++ b/tools/bzl/js.bzl
@@ -83,13 +83,13 @@
srcs = [plugin_name + ".js"],
)
-def gerrit_js_bundle(name, srcs, entry_point):
+def gerrit_js_bundle(name, entry_point, srcs = []):
"""Produces a Gerrit JavaScript bundle archive.
This rule bundles and minifies the javascript files of a frontend plugin and
produces a file archive.
Output of this rule is an archive with "${name}.jar" with specific layout for
- Gerrit frontentd plugins. That archive should be provided to gerrit_plugin
+ Gerrit frontend plugins. That archive should be provided to gerrit_plugin
rule as resource_jars attribute.
Args:
@@ -97,8 +97,13 @@
srcs: Plugin sources.
entry_point: Plugin entry_point.
"""
+
+ bundle = name + "-bundle"
+ minified = name + ".min"
+ main = name + ".js"
+
rollup_bundle(
- name = name + "-bundle",
+ name = bundle,
srcs = srcs,
entry_point = entry_point,
format = "iife",
@@ -110,22 +115,22 @@
)
terser_minified(
- name = name + ".min",
+ name = minified,
sourcemap = False,
- src = name + "-bundle.js",
+ src = bundle,
)
native.genrule(
name = name + "_rename_js",
- srcs = [name + ".min"],
- outs = [name + ".js"],
+ srcs = [minified],
+ outs = [main],
cmd = "cp $< $@",
output_to_bindir = True,
)
genrule2(
name = name,
- srcs = [name + ".js"],
+ srcs = [main],
outs = [name + ".jar"],
cmd = " && ".join([
"mkdir $$TMP/static",