Merge "Add Gerrit.Change to types.js"
diff --git a/Documentation/dev-e2e-tests.txt b/Documentation/dev-e2e-tests.txt
index 8887875..30da8c5 100644
--- a/Documentation/dev-e2e-tests.txt
+++ b/Documentation/dev-e2e-tests.txt
@@ -2,7 +2,7 @@
= Gerrit Code Review - End to end tests
This document provides descriptions of Gerrit end-to-end (`e2e`) test scenarios implemented using
-link:https://gatling.io/[Gatling,role=external,window=_blank] framework.
+the link:https://gatling.io/[Gatling,role=external,window=_blank] framework.
Similar scenarios have been successfully used to compare performance of different Gerrit versions
or study the Gerrit response under different load profiles. Although mostly for load, scenarios can
@@ -56,6 +56,14 @@
[warn] Credentials file ~/.sbt/sonatype_credentials does not exist
----
+The other warning below can be safely ignored, so far. Running the proposed `sbt evicted` command
+should only list `scala-java8-compat_2.12` as `[warn]`. The other dependency conflicts should show
+as `[info]`. All of the listed conflicts get usually resolved seamlessly or so.
+
+----
+[warn] There may be incompatibilities among your library dependencies; run 'evicted' to see detailed eviction warnings.
+----
+
Every `sbt` command can include an optional log level
link:https://www.scala-sbt.org/1.x/docs/Howto-Logging.html#Change+the+logging+level+globally[argument,role=external,window=_blank].
Below, `[info]` logs are no longer shown:
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 6f9367d..3fc4ea8d 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -6177,6 +6177,12 @@
`contains_git_conflicts` field in the link:#change-info[ChangeInfo]. If
there are conflicts the cherry-pick change is marked as
work-in-progress.
+|`topic` |optional|
+The topic of the created cherry-picked change. If not set, the default depends
+on the source. If the source is a change with a topic, the resulting topic
+of the cherry-picked change will be {source_change_topic}-{destination_branch}.
+Otherwise, if the source change has no topic, or the source is a commit,
+the created change will have no topic.
|===========================
[[comment-info]]
diff --git a/WORKSPACE b/WORKSPACE
index 7f7f8e0..6a822d7 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -46,11 +46,13 @@
# otherwise refer to RBE docs.
rbe_autoconfig(name = "rbe_default")
+# TODO(davido): Switch to upstream again, when this PR is merged:
+# https://github.com/bazelbuild/rules_closure/pull/478
http_archive(
name = "io_bazel_rules_closure",
- sha256 = "03c3b16f205085817fd89cfdcb2220a0138647ee7992be9cef291b069dd90301",
- strip_prefix = "rules_closure-196a45f0ede2faec11dcc6c60fbc5e7471f4bd58",
- urls = ["https://github.com/bazelbuild/rules_closure/archive/196a45f0ede2faec11dcc6c60fbc5e7471f4bd58.tar.gz"],
+ sha256 = "b9c2bc6ba377aa497eb7c31681d34404febf9d4e3c9c7d98ce0d78238a0af20f",
+ strip_prefix = "rules_closure-0.31",
+ urls = ["https://github.com/davido/rules_closure/archive/V0.31.tar.gz"],
)
http_archive(
diff --git a/e2e-tests/Dockerfile b/e2e-tests/Dockerfile
index ceae672..17c001b 100644
--- a/e2e-tests/Dockerfile
+++ b/e2e-tests/Dockerfile
@@ -1,6 +1,6 @@
FROM denvazh/gatling:3.2.1
-ARG gatling_git_version=1.0.9
+ARG gatling_git_version=1.0.10
RUN apk add --no-cache maven
RUN mvn dependency:get \
-DgroupId=com.gerritforge \
diff --git a/e2e-tests/README.md b/e2e-tests/README.md
deleted file mode 100644
index 534fde5..0000000
--- a/e2e-tests/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# How to build the Docker image
-
-```$shell
-docker build . -t e2e-tests
-```
-
-# How to run a test
-
-```$shell
-docker run -it e2e-tests -s com.google.gerrit.scenarios.ReplayRecordsFromFeederScenario
-```
diff --git a/e2e-tests/build.sbt b/e2e-tests/build.sbt
index 685db37..a322970 100644
--- a/e2e-tests/build.sbt
+++ b/e2e-tests/build.sbt
@@ -13,6 +13,7 @@
name := "gerrit",
libraryDependencies ++=
gatling ++
- Seq("io.gatling" % "gatling-core" % "3.1.1") ++
- Seq("io.gatling" % "gatling-app" % "3.1.1")
+ Seq("io.gatling" % "gatling-core" % GatlingVersion) ++
+ Seq("io.gatling" % "gatling-app" % GatlingVersion),
+ scalacOptions += "-language:postfixOps"
) dependsOn gatlingGitExtension
diff --git a/e2e-tests/project/Dependencies.scala b/e2e-tests/project/Dependencies.scala
index 72d2ac2..63328f9 100644
--- a/e2e-tests/project/Dependencies.scala
+++ b/e2e-tests/project/Dependencies.scala
@@ -1,8 +1,10 @@
import sbt._
object Dependencies {
+ val GatlingVersion = "3.2.0"
+
lazy val gatling = Seq(
"io.gatling.highcharts" % "gatling-charts-highcharts",
"io.gatling" % "gatling-test-framework",
- ).map(_ % "3.1.1" % Test)
+ ).map(_ % GatlingVersion % Test)
}
diff --git a/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java b/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java
index 5ac67e7..4ec6f01 100644
--- a/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java
+++ b/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java
@@ -29,4 +29,5 @@
public boolean keepReviewers;
public boolean allowConflicts;
+ public String topic;
}
diff --git a/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
index 6c6389e5..7ae570f 100644
--- a/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -16,6 +16,7 @@
import com.google.common.collect.ListMultimap;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.client.ArchiveFormat;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.common.ApprovalInfo;
@@ -158,6 +159,15 @@
/** Returns votes on the revision. */
ListMultimap<String, ApprovalInfo> votes() throws RestApiException;
+ /**
+ * Retrieves the revision as an archive.
+ *
+ * @param format the format of the archive
+ * @return the archive as {@link BinaryResult}
+ * @throws RestApiException
+ */
+ BinaryResult getArchive(ArchiveFormat format) throws RestApiException;
+
abstract class MergeListRequest {
private boolean addLinks;
private int uninterestingParent = 1;
@@ -392,5 +402,10 @@
public String etag() throws RestApiException {
throw new NotImplementedException();
}
+
+ @Override
+ public BinaryResult getArchive(ArchiveFormat format) throws RestApiException {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/java/com/google/gerrit/extensions/client/ArchiveFormat.java b/java/com/google/gerrit/extensions/client/ArchiveFormat.java
new file mode 100644
index 0000000..4ec59cb
--- /dev/null
+++ b/java/com/google/gerrit/extensions/client/ArchiveFormat.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.client;
+
+/**
+ * The {@link com.google.gerrit.server.restapi.change.GetArchive} REST endpoint allows to download
+ * revisions as archive. This enum defines the supported archive formats.
+ */
+public enum ArchiveFormat {
+ TGZ,
+ TAR,
+ TBZ2,
+ TXZ,
+ ZIP;
+}
diff --git a/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index 48a8689..b515dfe 100644
--- a/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -37,6 +37,7 @@
import com.google.gerrit.extensions.api.changes.RevisionReviewerApi;
import com.google.gerrit.extensions.api.changes.RobotCommentApi;
import com.google.gerrit.extensions.api.changes.SubmitInput;
+import com.google.gerrit.extensions.client.ArchiveFormat;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.common.ApprovalInfo;
@@ -70,6 +71,7 @@
import com.google.gerrit.server.restapi.change.DraftComments;
import com.google.gerrit.server.restapi.change.Files;
import com.google.gerrit.server.restapi.change.Fixes;
+import com.google.gerrit.server.restapi.change.GetArchive;
import com.google.gerrit.server.restapi.change.GetCommit;
import com.google.gerrit.server.restapi.change.GetDescription;
import com.google.gerrit.server.restapi.change.GetFixPreview;
@@ -96,6 +98,7 @@
import com.google.inject.assistedinject.Assisted;
import java.util.EnumSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.lib.Repository;
@@ -146,6 +149,7 @@
private final GetRelated getRelated;
private final PutDescription putDescription;
private final GetDescription getDescription;
+ private final Provider<GetArchive> getArchiveProvider;
private final ApprovalsUtil approvalsUtil;
private final AccountLoader.Factory accountLoaderFactory;
@@ -190,6 +194,7 @@
GetRelated getRelated,
PutDescription putDescription,
GetDescription getDescription,
+ Provider<GetArchive> getArchiveProvider,
ApprovalsUtil approvalsUtil,
AccountLoader.Factory accountLoaderFactory,
@Assisted RevisionResource r) {
@@ -232,6 +237,7 @@
this.getRelated = getRelated;
this.putDescription = putDescription;
this.getDescription = getDescription;
+ this.getArchiveProvider = getArchiveProvider;
this.approvalsUtil = approvalsUtil;
this.accountLoaderFactory = accountLoaderFactory;
this.revision = r;
@@ -649,4 +655,15 @@
public String etag() throws RestApiException {
return revisionActions.getETag(revision);
}
+
+ @Override
+ public BinaryResult getArchive(ArchiveFormat format) throws RestApiException {
+ GetArchive getArchive = getArchiveProvider.get();
+ getArchive.setFormat(format != null ? format.name().toLowerCase(Locale.US) : null);
+ try {
+ return getArchive.apply(revision).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get archive", e);
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/change/ArchiveFormat.java b/java/com/google/gerrit/server/change/ArchiveFormatInternal.java
similarity index 95%
rename from java/com/google/gerrit/server/change/ArchiveFormat.java
rename to java/com/google/gerrit/server/change/ArchiveFormatInternal.java
index d895a66..f6e9ff9 100644
--- a/java/com/google/gerrit/server/change/ArchiveFormat.java
+++ b/java/com/google/gerrit/server/change/ArchiveFormatInternal.java
@@ -28,7 +28,7 @@
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectLoader;
-public enum ArchiveFormat {
+public enum ArchiveFormatInternal {
TGZ("application/x-gzip", new TgzFormat()),
TAR("application/x-tar", new TarFormat()),
TBZ2("application/x-bzip2", new Tbz2Format()),
@@ -40,7 +40,7 @@
private final String mimeType;
- ArchiveFormat(String mimeType, ArchiveCommand.Format<?> format) {
+ ArchiveFormatInternal(String mimeType, ArchiveCommand.Format<?> format) {
this.format = format;
this.mimeType = mimeType;
ArchiveCommand.registerFormat(name(), format);
diff --git a/java/com/google/gerrit/server/config/DownloadConfig.java b/java/com/google/gerrit/server/config/DownloadConfig.java
index 6dea07d..58ce098 100644
--- a/java/com/google/gerrit/server/config/DownloadConfig.java
+++ b/java/com/google/gerrit/server/config/DownloadConfig.java
@@ -17,7 +17,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.entities.CoreDownloadSchemes;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DownloadCommand;
-import com.google.gerrit.server.change.ArchiveFormat;
+import com.google.gerrit.server.change.ArchiveFormatInternal;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.lang.reflect.Field;
@@ -37,7 +37,7 @@
public class DownloadConfig {
private final ImmutableSet<String> downloadSchemes;
private final ImmutableSet<DownloadCommand> downloadCommands;
- private final ImmutableSet<ArchiveFormat> archiveFormats;
+ private final ImmutableSet<ArchiveFormatInternal> archiveFormats;
@Inject
DownloadConfig(@GerritServerConfig Config cfg) {
@@ -69,13 +69,13 @@
String v = cfg.getString("download", null, "archive");
if (v == null) {
- archiveFormats = ImmutableSet.copyOf(EnumSet.allOf(ArchiveFormat.class));
+ archiveFormats = ImmutableSet.copyOf(EnumSet.allOf(ArchiveFormatInternal.class));
} else if (v.isEmpty() || "off".equalsIgnoreCase(v)) {
archiveFormats = ImmutableSet.of();
} else {
archiveFormats =
ImmutableSet.copyOf(
- ConfigUtil.getEnumList(cfg, "download", null, "archive", ArchiveFormat.TGZ));
+ ConfigUtil.getEnumList(cfg, "download", null, "archive", ArchiveFormatInternal.TGZ));
}
}
@@ -110,7 +110,7 @@
}
/** Archive formats for downloading. */
- public ImmutableSet<ArchiveFormat> getArchiveFormats() {
+ public ImmutableSet<ArchiveFormatInternal> getArchiveFormats() {
return archiveFormats;
}
}
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidators.java b/java/com/google/gerrit/server/git/validators/CommitValidators.java
index 6c4aacc..1e97a44 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -403,6 +403,17 @@
@Override
public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
throws CommitValidationException {
+ // TODO(zieren): Refactor interface to signal the intent of the event instead of hard-coding
+ // it here. Due to interface limitations, this method is called from both receive commits
+ // and from main Gerrit (e.g. when publishing a change edit). This is why we need to gate the
+ // early return on REFS_CHANGES (though pushes to refs/changes are not possible).
+ String refName = receiveEvent.command.getRefName();
+ if (!refName.startsWith("refs/for/") && !refName.startsWith(RefNames.REFS_CHANGES)) {
+ // This is a direct push bypassing review. We don't need to enforce any file-count limits
+ // here.
+ return Collections.emptyList();
+ }
+
PatchListKey patchListKey =
PatchListKey.againstBase(
receiveEvent.commit.getId(), receiveEvent.commit.getParentCount());
diff --git a/java/com/google/gerrit/server/restapi/change/AllowedFormats.java b/java/com/google/gerrit/server/restapi/change/AllowedFormats.java
index 2e313a1..ebec3295 100644
--- a/java/com/google/gerrit/server/restapi/change/AllowedFormats.java
+++ b/java/com/google/gerrit/server/restapi/change/AllowedFormats.java
@@ -18,7 +18,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
-import com.google.gerrit.server.change.ArchiveFormat;
+import com.google.gerrit.server.change.ArchiveFormatInternal;
import com.google.gerrit.server.config.DownloadConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -28,13 +28,13 @@
@Singleton
public class AllowedFormats {
- final ImmutableMap<String, ArchiveFormat> extensions;
- final ImmutableSet<ArchiveFormat> allowed;
+ final ImmutableMap<String, ArchiveFormatInternal> extensions;
+ final ImmutableSet<ArchiveFormatInternal> allowed;
@Inject
AllowedFormats(DownloadConfig cfg) {
- Map<String, ArchiveFormat> exts = new HashMap<>();
- for (ArchiveFormat format : cfg.getArchiveFormats()) {
+ Map<String, ArchiveFormatInternal> exts = new HashMap<>();
+ for (ArchiveFormatInternal format : cfg.getArchiveFormats()) {
for (String ext : format.getSuffixes()) {
exts.put(ext, format);
}
@@ -46,14 +46,14 @@
// valid JAR file, whose code would have access to cookies on the domain.
allowed =
Sets.immutableEnumSet(
- Iterables.filter(cfg.getArchiveFormats(), f -> f != ArchiveFormat.ZIP));
+ Iterables.filter(cfg.getArchiveFormats(), f -> f != ArchiveFormatInternal.ZIP));
}
- public Set<ArchiveFormat> getAllowed() {
+ public Set<ArchiveFormatInternal> getAllowed() {
return allowed;
}
- public ImmutableMap<String, ArchiveFormat> getExtensions() {
+ public ImmutableMap<String, ArchiveFormatInternal> getExtensions() {
return extensions;
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
index 82b138d..5960117 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
@@ -175,7 +175,6 @@
null,
null,
null,
- null,
null);
}
@@ -217,7 +216,6 @@
null,
null,
null,
- null,
null);
}
@@ -235,7 +233,6 @@
* @param ignoreIdenticalTree When false, we throw an error when trying to cherry-pick creates an
* empty commit. When true, we allow creation of an empty commit.
* @param timestamp the current timestamp.
- * @param topic Topic name for the change created.
* @param revertedChange The id of the change that is reverted. This is used for the "revertOf"
* field to mark the created cherry pick change as "revertOf" the original change that was
* reverted.
@@ -263,7 +260,6 @@
BranchNameKey dest,
boolean ignoreIdenticalTree,
Timestamp timestamp,
- @Nullable String topic,
@Nullable Change.Id revertedChange,
@Nullable ObjectId changeIdForNewChange,
@Nullable Change.Id idForNewChange,
@@ -381,8 +377,11 @@
} else {
// Change key not found on destination branch. We can create a new
// change.
- String newTopic = topic;
- if (topic == null
+ String newTopic = null;
+ if (input.topic != null) {
+ newTopic = Strings.emptyToNull(input.topic.trim());
+ }
+ if (newTopic == null
&& sourceChange != null
&& !Strings.isNullOrEmpty(sourceChange.getTopic())) {
newTopic = sourceChange.getTopic() + "-" + newDest.shortName();
diff --git a/java/com/google/gerrit/server/restapi/change/GetArchive.java b/java/com/google/gerrit/server/restapi/change/GetArchive.java
index 4ebcbdd..7ab1432 100644
--- a/java/com/google/gerrit/server/restapi/change/GetArchive.java
+++ b/java/com/google/gerrit/server/restapi/change/GetArchive.java
@@ -17,12 +17,13 @@
import static com.google.gerrit.git.ObjectIds.abbreviateName;
import com.google.common.base.Strings;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.change.ArchiveFormat;
+import com.google.gerrit.server.change.ArchiveFormatInternal;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
@@ -38,9 +39,12 @@
public class GetArchive implements RestReadView<RevisionResource> {
private final GitRepositoryManager repoManager;
private final AllowedFormats allowedFormats;
+ @Nullable private String format;
@Option(name = "--format")
- private String format;
+ public void setFormat(String format) {
+ this.format = format;
+ }
@Inject
GetArchive(GitRepositoryManager repoManager, AllowedFormats allowedFormats) {
@@ -54,17 +58,17 @@
if (Strings.isNullOrEmpty(format)) {
throw new BadRequestException("format is not specified");
}
- final ArchiveFormat f = allowedFormats.extensions.get("." + format);
+ ArchiveFormatInternal f = allowedFormats.extensions.get("." + format);
if (f == null) {
throw new BadRequestException("unknown archive format");
}
- if (f == ArchiveFormat.ZIP) {
+ if (f == ArchiveFormatInternal.ZIP) {
throw new MethodNotAllowedException("zip format is disabled");
}
boolean close = true;
- final Repository repo = repoManager.openRepository(rsrc.getProject());
+ Repository repo = repoManager.openRepository(rsrc.getProject());
try {
- final RevCommit commit;
+ RevCommit commit;
String name;
try (RevWalk rw = new RevWalk(repo)) {
commit = rw.parseCommit(rsrc.getPatchSet().commitId());
@@ -103,7 +107,7 @@
}
}
- private static String name(ArchiveFormat format, RevWalk rw, RevCommit commit)
+ private static String name(ArchiveFormatInternal format, RevWalk rw, RevCommit commit)
throws IOException {
return String.format(
"%s%s", abbreviateName(commit, rw.getObjectReader()), format.getDefaultSuffix());
diff --git a/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java b/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java
index e6a60d5..ed6c0a5 100644
--- a/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java
+++ b/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java
@@ -29,7 +29,7 @@
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.ArchiveFormat;
+import com.google.gerrit.server.change.ArchiveFormatInternal;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.ioutil.LimitedByteArrayOutputStream;
@@ -87,12 +87,12 @@
if (Strings.isNullOrEmpty(format)) {
throw new BadRequestException("format is not specified");
}
- ArchiveFormat f = allowedFormats.extensions.get("." + format);
+ ArchiveFormatInternal f = allowedFormats.extensions.get("." + format);
if (f == null && format.equals("tgz")) {
// Always allow tgz, even when the allowedFormats doesn't contain it.
// Then we allow at least one format even if the list of allowed
// formats is empty.
- f = ArchiveFormat.TGZ;
+ f = ArchiveFormatInternal.TGZ;
}
if (f == null) {
throw new BadRequestException("unknown archive format");
@@ -109,7 +109,7 @@
return Response.ok(getBundles(rsrc, f));
}
- private BinaryResult getBundles(RevisionResource rsrc, ArchiveFormat f)
+ private BinaryResult getBundles(RevisionResource rsrc, ArchiveFormatInternal f)
throws RestApiException, UpdateException, IOException, ConfigInvalidException,
PermissionBackendException {
IdentifiedUser caller = rsrc.getUser().asIdentifiedUser();
@@ -138,10 +138,11 @@
private static class SubmitPreviewResult extends BinaryResult {
private final MergeOp mergeOp;
- private final ArchiveFormat archiveFormat;
+ private final ArchiveFormatInternal archiveFormat;
private final int maxBundleSize;
- private SubmitPreviewResult(MergeOp mergeOp, ArchiveFormat archiveFormat, int maxBundleSize) {
+ private SubmitPreviewResult(
+ MergeOp mergeOp, ArchiveFormatInternal archiveFormat, int maxBundleSize) {
this.mergeOp = mergeOp;
this.archiveFormat = archiveFormat;
this.maxBundleSize = maxBundleSize;
diff --git a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
index 52cc46f..e7da89a 100644
--- a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
+++ b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
@@ -20,6 +20,7 @@
import static com.google.gerrit.server.project.ProjectCache.illegalState;
import static java.util.Objects.requireNonNull;
+import com.google.common.base.Strings;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.flogger.FluentLogger;
@@ -195,6 +196,9 @@
}
private String createTopic(String topic, String submissionId) {
+ if (topic != null) {
+ topic = Strings.emptyToNull(topic.trim());
+ }
if (topic == null) {
return String.format(
"revert-%s-%s", submissionId, RandomStringUtils.randomAlphabetic(10).toUpperCase());
@@ -321,12 +325,7 @@
bu.addOp(
changeNotes.getChange().getId(),
new CreateCherryPickOp(
- revCommitId,
- revertInput.topic,
- generatedChangeId,
- cherryPickRevertChangeId,
- groupName,
- timestamp));
+ revCommitId, generatedChangeId, cherryPickRevertChangeId, groupName, timestamp));
bu.addOp(changeNotes.getChange().getId(), new PostRevertedMessageOp(generatedChangeId));
bu.addOp(
cherryPickRevertChangeId,
@@ -356,6 +355,7 @@
cherryPickInput.notifyDetails = revertInput.notifyDetails;
cherryPickInput.parent = 1;
cherryPickInput.keepReviewers = true;
+ cherryPickInput.topic = revertInput.topic;
return cherryPickInput;
}
@@ -570,7 +570,6 @@
private class CreateCherryPickOp implements BatchUpdateOp {
private final ObjectId revCommitId;
- private final String topic;
private final ObjectId computedChangeId;
private final Change.Id cherryPickRevertChangeId;
private final String groupName;
@@ -578,13 +577,11 @@
CreateCherryPickOp(
ObjectId revCommitId,
- String topic,
ObjectId computedChangeId,
Change.Id cherryPickRevertChangeId,
String groupName,
Timestamp timestamp) {
this.revCommitId = revCommitId;
- this.topic = topic;
this.computedChangeId = computedChangeId;
this.cherryPickRevertChangeId = cherryPickRevertChangeId;
this.groupName = groupName;
@@ -604,7 +601,6 @@
change.getProject(), RefNames.fullName(cherryPickInput.destination)),
true,
timestamp,
- topic,
change.getId(),
computedChangeId,
cherryPickRevertChangeId,
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index 4ddc3e8..c83bf42 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -43,7 +43,7 @@
import com.google.gerrit.server.account.AccountVisibilityProvider;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.avatar.AvatarProvider;
-import com.google.gerrit.server.change.ArchiveFormat;
+import com.google.gerrit.server.change.ArchiveFormatInternal;
import com.google.gerrit.server.change.MergeabilityComputationBehavior;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
@@ -254,7 +254,9 @@
}
});
info.archives =
- archiveFormats.getAllowed().stream().map(ArchiveFormat::getShortName).collect(toList());
+ archiveFormats.getAllowed().stream()
+ .map(ArchiveFormatInternal::getShortName)
+ .collect(toList());
return info;
}
diff --git a/java/com/google/gerrit/server/restapi/project/CreateBranch.java b/java/com/google/gerrit/server/restapi/project/CreateBranch.java
index c15fdeb..b901057 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateBranch.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateBranch.java
@@ -17,7 +17,6 @@
import static com.google.gerrit.entities.RefNames.isConfigRef;
import com.google.common.base.Strings;
-import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.BranchInfo;
@@ -46,7 +45,6 @@
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
@@ -58,8 +56,6 @@
@Singleton
public class CreateBranch
implements RestCollectionCreateView<ProjectResource, BranchResource, BranchInput> {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
private final Provider<IdentifiedUser> identifiedUser;
private final PermissionBackend permissionBackend;
private final GitRepositoryManager repoManager;
@@ -114,7 +110,7 @@
+ "\"");
}
- final BranchNameKey name = BranchNameKey.create(rsrc.getNameKey(), ref);
+ BranchNameKey name = BranchNameKey.create(rsrc.getNameKey(), ref);
try (Repository repo = repoManager.openRepository(rsrc.getNameKey())) {
ObjectId revid = RefUtil.parseBaseRevision(repo, rsrc.getNameKey(), input.revision);
RevWalk rw = RefUtil.verifyConnected(repo, revid);
@@ -122,80 +118,71 @@
if (ref.startsWith(Constants.R_HEADS)) {
// Ensure that what we start the branch from is a commit. If we
- // were given a tag, deference to the commit instead.
+ // were given a tag, dereference to the commit instead.
//
- try {
- object = rw.parseCommit(object);
- } catch (IncorrectObjectTypeException notCommit) {
- throw new BadRequestException("\"" + input.revision + "\" not a commit", notCommit);
- }
+ object = rw.parseCommit(object);
}
createRefControl.checkCreateRef(identifiedUser, repo, name, object);
- try {
- final RefUpdate u = repo.updateRef(ref);
- u.setExpectedOldObjectId(ObjectId.zeroId());
- u.setNewObjectId(object.copy());
- u.setRefLogIdent(identifiedUser.get().newRefLogIdent());
- u.setRefLogMessage("created via REST from " + input.revision, false);
- refCreationValidator.validateRefOperation(rsrc.getName(), identifiedUser.get(), u);
- final RefUpdate.Result result = u.update(rw);
- switch (result) {
- case FAST_FORWARD:
- case NEW:
- case NO_CHANGE:
- referenceUpdated.fire(
- name.project(), u, ReceiveCommand.Type.CREATE, identifiedUser.get().state());
- break;
- case LOCK_FAILURE:
- if (repo.getRefDatabase().exactRef(ref) != null) {
- throw new ResourceConflictException("branch \"" + ref + "\" already exists");
+ RefUpdate u = repo.updateRef(ref);
+ u.setExpectedOldObjectId(ObjectId.zeroId());
+ u.setNewObjectId(object.copy());
+ u.setRefLogIdent(identifiedUser.get().newRefLogIdent());
+ u.setRefLogMessage("created via REST from " + input.revision, false);
+ refCreationValidator.validateRefOperation(rsrc.getName(), identifiedUser.get(), u);
+ RefUpdate.Result result = u.update(rw);
+ switch (result) {
+ case FAST_FORWARD:
+ case NEW:
+ case NO_CHANGE:
+ referenceUpdated.fire(
+ name.project(), u, ReceiveCommand.Type.CREATE, identifiedUser.get().state());
+ break;
+ case LOCK_FAILURE:
+ if (repo.getRefDatabase().exactRef(ref) != null) {
+ throw new ResourceConflictException("branch \"" + ref + "\" already exists");
+ }
+ String refPrefix = RefUtil.getRefPrefix(ref);
+ while (!Constants.R_HEADS.equals(refPrefix)) {
+ if (repo.getRefDatabase().exactRef(refPrefix) != null) {
+ throw new ResourceConflictException(
+ "Cannot create branch \""
+ + ref
+ + "\" since it conflicts with branch \""
+ + refPrefix
+ + "\".");
}
- String refPrefix = RefUtil.getRefPrefix(ref);
- while (!Constants.R_HEADS.equals(refPrefix)) {
- if (repo.getRefDatabase().exactRef(refPrefix) != null) {
- throw new ResourceConflictException(
- "Cannot create branch \""
- + ref
- + "\" since it conflicts with branch \""
- + refPrefix
- + "\".");
- }
- refPrefix = RefUtil.getRefPrefix(refPrefix);
- }
- throw new LockFailureException(String.format("Failed to create %s", ref), u);
- case FORCED:
- case IO_FAILURE:
- case NOT_ATTEMPTED:
- case REJECTED:
- case REJECTED_CURRENT_BRANCH:
- case RENAMED:
- case REJECTED_MISSING_OBJECT:
- case REJECTED_OTHER_REASON:
- default:
- throw new IOException(String.format("Failed to create %s: %s", ref, result.name()));
- }
-
- BranchInfo info = new BranchInfo();
- info.ref = ref;
- info.revision = revid.getName();
-
- if (isConfigRef(name.branch())) {
- // Never allow to delete the meta config branch.
- info.canDelete = null;
- } else {
- info.canDelete =
- permissionBackend.currentUser().ref(name).testOrFalse(RefPermission.DELETE)
- && rsrc.getProjectState().statePermitsWrite()
- ? true
- : null;
- }
- return Response.created(info);
- } catch (IOException err) {
- logger.atSevere().withCause(err).log("Cannot create branch \"%s\"", name);
- throw err;
+ refPrefix = RefUtil.getRefPrefix(refPrefix);
+ }
+ throw new LockFailureException(String.format("Failed to create %s", ref), u);
+ case FORCED:
+ case IO_FAILURE:
+ case NOT_ATTEMPTED:
+ case REJECTED:
+ case REJECTED_CURRENT_BRANCH:
+ case RENAMED:
+ case REJECTED_MISSING_OBJECT:
+ case REJECTED_OTHER_REASON:
+ default:
+ throw new IOException(String.format("Failed to create %s: %s", ref, result.name()));
}
+
+ BranchInfo info = new BranchInfo();
+ info.ref = ref;
+ info.revision = revid.getName();
+
+ if (isConfigRef(name.branch())) {
+ // Never allow to delete the meta config branch.
+ info.canDelete = null;
+ } else {
+ info.canDelete =
+ permissionBackend.currentUser().ref(name).testOrFalse(RefPermission.DELETE)
+ && rsrc.getProjectState().statePermitsWrite()
+ ? true
+ : null;
+ }
+ return Response.created(info);
} catch (RefUtil.InvalidRevisionException e) {
throw new BadRequestException("invalid revision \"" + input.revision + "\"", e);
}
diff --git a/java/com/google/gerrit/sshd/commands/UploadArchive.java b/java/com/google/gerrit/sshd/commands/UploadArchive.java
index 8543a1c..67dc5a5 100644
--- a/java/com/google/gerrit/sshd/commands/UploadArchive.java
+++ b/java/com/google/gerrit/sshd/commands/UploadArchive.java
@@ -20,7 +20,7 @@
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.server.change.ArchiveFormat;
+import com.google.gerrit.server.change.ArchiveFormatInternal;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
@@ -174,7 +174,7 @@
// Parse Git arguments
readArguments();
- ArchiveFormat f = allowedFormats.getExtensions().get("." + options.format);
+ ArchiveFormatInternal f = allowedFormats.getExtensions().get("." + options.format);
if (f == null) {
throw new Failure(3, "fatal: upload-archive not permitted for format " + options.format);
}
@@ -222,8 +222,8 @@
}
}
- private Map<String, Object> getFormatOptions(ArchiveFormat f) {
- if (f == ArchiveFormat.ZIP) {
+ private Map<String, Object> getFormatOptions(ArchiveFormatInternal f) {
+ if (f == ArchiveFormatInternal.ZIP) {
int value =
Arrays.asList(
options.level0,
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 9399c3b..fd681d8 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -3210,7 +3210,8 @@
mergeInput.source = "dev";
MergePatchSetInput in = new MergePatchSetInput();
in.merge = mergeInput;
- in.subject = "update change by merge ps2";
+ String subject = "update change by merge ps2";
+ in.subject = subject;
TestWorkInProgressStateChangedListener wipStateChangedListener =
new TestWorkInProgressStateChangedListener();
@@ -3234,6 +3235,36 @@
List<ChangeMessageInfo> messages = gApi.changes().id(changeId).messages();
assertThat(messages).hasSize(2);
assertThat(Iterables.getLast(messages).message).isEqualTo("Uploaded patch set 2.");
+
+ assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.message)
+ .contains(subject);
+ }
+
+ @Test
+ public void createMergePatchSet_SubjectCarriesOverByDefault() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch("dev");
+
+ // create a change for master
+ PushOneCommit.Result result = createChange();
+ String changeId = result.getChangeId();
+ String subject = result.getChange().change().getSubject();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result pushResult =
+ pushFactory.create(user.newIdent(), testRepo).to("refs/heads/dev");
+ pushResult.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = null;
+
+ // Ensure subject carries over
+ gApi.changes().id(changeId).createMergePatchSet(in);
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+ assertThat(changeInfo.subject).isEqualTo(subject);
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java b/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
index 04625c5..e67770c 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
@@ -169,6 +169,21 @@
assertThat(revInfo.commit.message).isEqualTo(input.message + "\n");
}
+ @Test
+ public void cherryPickCommitWithSetTopic() throws Exception {
+ String branch = "foo";
+ RevCommit revCommit = createChange().getCommit();
+ gApi.projects().name(project.get()).branch(branch).create(new BranchInput());
+ CherryPickInput input = new CherryPickInput();
+ input.destination = branch;
+ input.topic = "topic";
+ String changeId =
+ gApi.projects().name(project.get()).commit(revCommit.name()).cherryPick(input).get().id;
+
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+ assertThat(changeInfo.topic).isEqualTo(input.topic);
+ }
+
private IncludedInInfo getIncludedIn(ObjectId id) throws Exception {
return gApi.projects().name(project.get()).commit(id.name()).includedIn();
}
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 2c2abfe..2c6d539 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -403,6 +403,19 @@
}
@Test
+ public void cherryPickWithSetTopic() throws Exception {
+ PushOneCommit.Result r = pushTo("refs/for/master");
+ CherryPickInput in = new CherryPickInput();
+ in.destination = "foo";
+ in.topic = "topic";
+ gApi.projects().name(project.get()).branch(in.destination).create(new BranchInput());
+ ChangeApi orig = gApi.changes().id(project.get() + "~master~" + r.getChangeId());
+
+ ChangeApi cherry = orig.revision(r.getCommit().name()).cherryPick(in);
+ assertThat(cherry.get().topic).isEqualTo("topic");
+ }
+
+ @Test
public void cherryPickWorkInProgressChange() throws Exception {
PushOneCommit.Result r = pushTo("refs/for/master%wip");
CherryPickInput in = new CherryPickInput();
@@ -1079,6 +1092,24 @@
}
@Test
+ public void setReviewedFlagWithMultiplePatchSets() throws Exception {
+ PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+ PushOneCommit.Result r1 = push.to("refs/for/master");
+
+ gApi.changes().id(r1.getChangeId()).current().setReviewed(PushOneCommit.FILE_NAME, true);
+
+ /** Amending the change will result in the file being un-reviewed in the latest patchset */
+ PushOneCommit.Result r2 = amendChange(r1.getChangeId());
+
+ assertThat(gApi.changes().id(r2.getChangeId()).current().reviewed()).isEmpty();
+
+ gApi.changes().id(r2.getChangeId()).current().setReviewed(PushOneCommit.FILE_NAME, true);
+
+ assertThat(Iterables.getOnlyElement(gApi.changes().id(r2.getChangeId()).current().reviewed()))
+ .isEqualTo(PushOneCommit.FILE_NAME);
+ }
+
+ @Test
public void setUnsetReviewedFlagByFileApi() throws Exception {
PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
PushOneCommit.Result r = push.to("refs/for/master");
diff --git a/javatests/com/google/gerrit/acceptance/git/RefOperationValidationIT.java b/javatests/com/google/gerrit/acceptance/git/RefOperationValidationIT.java
index 876e342..d7952e4 100644
--- a/javatests/com/google/gerrit/acceptance/git/RefOperationValidationIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/RefOperationValidationIT.java
@@ -29,6 +29,7 @@
import com.google.gerrit.acceptance.ExtensionRegistry;
import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.BranchInput;
@@ -206,4 +207,22 @@
r4.assertErrorStatus(UPDATE_NONFASTFORWARD.name());
}
}
+
+ @Test
+ @GerritConfig(name = "change.maxFiles", value = "0")
+ public void dontEnforceFileCountForDirectPushes() throws Exception {
+ PushOneCommit push =
+ pushFactory.create(admin.newIdent(), testRepo, "change", "c.txt", "content");
+ PushOneCommit.Result result = push.to("refs/heads/master");
+ result.assertOkStatus();
+ }
+
+ @Test
+ @GerritConfig(name = "change.maxFiles", value = "0")
+ public void enforceFileCountLimitOnPushesForReview() throws Exception {
+ PushOneCommit push =
+ pushFactory.create(admin.newIdent(), testRepo, "change", "c.txt", "content");
+ PushOneCommit.Result result = push.to("refs/for/master");
+ result.assertErrorStatus("Exceeding maximum number of files per change");
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/GetArchiveIT.java b/javatests/com/google/gerrit/acceptance/rest/change/GetArchiveIT.java
new file mode 100644
index 0000000..15e6360
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/change/GetArchiveIT.java
@@ -0,0 +1,154 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.change;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.client.ArchiveFormat;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.git.ObjectIds;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.HashMap;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Test;
+
+public class GetArchiveIT extends AbstractDaemonTest {
+ private static final String DIRECTORY_NAME = "foo";
+ private static final String FILE_NAME = DIRECTORY_NAME + "/bar.txt";
+ private static final String FILE_CONTENT = "some content";
+
+ private String changeId;
+ private RevCommit commit;
+
+ @Before
+ public void setUp() throws Exception {
+ PushOneCommit push =
+ pushFactory.create(admin.newIdent(), testRepo, "My Change", FILE_NAME, FILE_CONTENT);
+ PushOneCommit.Result result = push.to("refs/for/master");
+ result.assertOkStatus();
+
+ changeId = result.getChangeId();
+ commit = result.getCommit();
+ }
+
+ @Test
+ public void formatNotSpecified() throws Exception {
+ BadRequestException ex =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.changes().id(changeId).current().getArchive(null));
+ assertThat(ex).hasMessageThat().isEqualTo("format is not specified");
+ }
+
+ @Test
+ public void unknownFormat() throws Exception {
+ // Test this by a REST call, since the Java API doesn't allow to specify an unknown format.
+ RestResponse res =
+ adminRestSession.get(
+ String.format(
+ "/changes/%s/revisions/current/archive?format=%s", changeId, "unknownFormat"));
+ res.assertBadRequest();
+ assertThat(res.getEntityContent()).isEqualTo("unknown archive format");
+ }
+
+ @Test
+ public void zipFormatIsDisabled() throws Exception {
+ MethodNotAllowedException ex =
+ assertThrows(
+ MethodNotAllowedException.class,
+ () -> gApi.changes().id(changeId).current().getArchive(ArchiveFormat.ZIP));
+ assertThat(ex).hasMessageThat().isEqualTo("zip format is disabled");
+ }
+
+ @Test
+ public void getTarArchive() throws Exception {
+ BinaryResult res = gApi.changes().id(changeId).current().getArchive(ArchiveFormat.TAR);
+ assertThat(res.getAttachmentName())
+ .isEqualTo(commit.abbreviate(ObjectIds.ABBREV_STR_LEN).name() + ".tar");
+ assertThat(res.getContentType()).isEqualTo("application/x-tar");
+ assertThat(res.canGzip()).isFalse();
+
+ byte[] archiveBytes = getBinaryContent(res);
+ try (ByteArrayInputStream in = new ByteArrayInputStream(archiveBytes)) {
+ HashMap<String, String> archiveEntries = getTarContent(in);
+ assertThat(archiveEntries)
+ .containsExactly(DIRECTORY_NAME + "/", null, FILE_NAME, FILE_CONTENT);
+ }
+ }
+
+ @Test
+ public void getTgzArchive() throws Exception {
+ BinaryResult res = gApi.changes().id(changeId).current().getArchive(ArchiveFormat.TGZ);
+ assertThat(res.getAttachmentName())
+ .isEqualTo(commit.abbreviate(ObjectIds.ABBREV_STR_LEN).name() + ".tar.gz");
+ assertThat(res.getContentType()).isEqualTo("application/x-gzip");
+ assertThat(res.canGzip()).isFalse();
+
+ byte[] archiveBytes = getBinaryContent(res);
+ try (ByteArrayInputStream in = new ByteArrayInputStream(archiveBytes);
+ GzipCompressorInputStream gzipIn = new GzipCompressorInputStream(in)) {
+ HashMap<String, String> archiveEntries = getTarContent(gzipIn);
+ assertThat(archiveEntries)
+ .containsExactly(DIRECTORY_NAME + "/", null, FILE_NAME, FILE_CONTENT);
+ }
+ }
+
+ private HashMap<String, String> getTarContent(InputStream in) throws Exception {
+ HashMap<String, String> archiveEntries = new HashMap<>();
+ int bufferSize = 100;
+ try (TarArchiveInputStream tarIn = new TarArchiveInputStream(in)) {
+ TarArchiveEntry entry;
+ while ((entry = tarIn.getNextTarEntry()) != null) {
+ if (entry.isDirectory()) {
+ archiveEntries.put(entry.getName(), null);
+ } else {
+ byte data[] = new byte[bufferSize];
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream();
+ BufferedOutputStream bufferedOut = new BufferedOutputStream(out, bufferSize)) {
+ int count;
+ while ((count = tarIn.read(data, 0, bufferSize)) != -1) {
+ bufferedOut.write(data, 0, count);
+ }
+ bufferedOut.flush();
+ archiveEntries.put(entry.getName(), out.toString());
+ }
+ }
+ }
+ }
+ return archiveEntries;
+ }
+
+ private byte[] getBinaryContent(BinaryResult res) throws Exception {
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ res.writeTo(out);
+ return out.toByteArray();
+ } finally {
+ res.close();
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java b/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
index 85d383e..b01a07b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
@@ -23,7 +23,9 @@
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
@@ -38,8 +40,20 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.events.RefReceivedEvent;
+import com.google.gerrit.server.git.validators.RefOperationValidationListener;
+import com.google.gerrit.server.git.validators.ValidationMessage;
+import com.google.gerrit.server.util.MagicBranch;
+import com.google.gerrit.server.validators.ValidationException;
import com.google.inject.Inject;
+import java.io.IOException;
+import java.util.List;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;
@@ -47,6 +61,7 @@
public class CreateBranchIT extends AbstractDaemonTest {
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private ExtensionRegistry extensionRegistry;
private BranchNameKey testBranch;
@@ -82,7 +97,63 @@
@Test
public void branchAlreadyExists_Conflict() throws Exception {
assertCreateSucceeds(testBranch);
- assertCreateFails(testBranch, ResourceConflictException.class);
+ assertCreateFails(
+ testBranch,
+ ResourceConflictException.class,
+ "branch \"" + testBranch.branch() + "\" already exists");
+ }
+
+ @Test
+ public void createBranch_LockFailure() throws Exception {
+ // check that the branch doesn't exist yet
+ assertThrows(ResourceNotFoundException.class, () -> branch(testBranch).get());
+
+ // Register a validation listener that creates the branch to simulate a concurrent request that
+ // creates the same branch.
+ try (ExtensionRegistry.Registration registration =
+ extensionRegistry
+ .newRegistration()
+ .add(
+ new RefOperationValidationListener() {
+ @Override
+ public List<ValidationMessage> onRefOperation(RefReceivedEvent refEvent)
+ throws ValidationException {
+ try (Repository repo = repoManager.openRepository(project)) {
+ RefUpdate u = repo.updateRef(testBranch.branch());
+ u.setExpectedOldObjectId(ObjectId.zeroId());
+ u.setNewObjectId(repo.exactRef("refs/heads/master").getObjectId());
+ RefUpdate.Result result = u.update();
+ if (result != RefUpdate.Result.NEW) {
+ throw new ValidationException(
+ "Concurrent creation of branch failed: " + result);
+ }
+ return ImmutableList.of();
+ } catch (IOException e) {
+ throw new ValidationException("Concurrent creation of branch failed.", e);
+ }
+ }
+ })) {
+ // Creating the branch is expected to fail, since it is created by the validation listener
+ // right before the ref update to create the new branch is done.
+ assertCreateFails(
+ testBranch,
+ ResourceConflictException.class,
+ "branch \"" + testBranch.branch() + "\" already exists");
+ }
+ }
+
+ @Test
+ public void conflictingBranchAlreadyExists_Conflict() throws Exception {
+ assertCreateSucceeds(testBranch);
+ BranchNameKey testBranch2 = BranchNameKey.create(project, testBranch.branch() + "/foo/bar");
+ assertCreateFails(
+ testBranch2,
+ ResourceConflictException.class,
+ "Cannot create branch \""
+ + testBranch2.branch()
+ + "\" since it conflicts with branch \""
+ + testBranch.branch()
+ + "\"");
}
@Test
@@ -119,6 +190,23 @@
}
@Test
+ public void createMetaConfigBranch() throws Exception {
+ // Since the refs/meta/config branch exists by default, we must delete it before we can test
+ // creating it. Since deleting the refs/meta/config branch is not allowed through the API, we
+ // delete it directly in the remote repository.
+ try (TestRepository<Repository> repo =
+ new TestRepository<>(repoManager.openRepository(project))) {
+ repo.delete(RefNames.REFS_CONFIG);
+ }
+
+ // Create refs/meta/config branch.
+ BranchInfo created =
+ branch(BranchNameKey.create(project, RefNames.REFS_CONFIG)).create(new BranchInput()).get();
+ assertThat(created.ref).isEqualTo(RefNames.REFS_CONFIG);
+ assertThat(created.canDelete).isNull();
+ }
+
+ @Test
public void createUserBranch_Conflict() throws Exception {
projectOperations
.project(allUsers)
@@ -258,6 +346,54 @@
"invalid revision \"invalid\trevision\"");
}
+ @Test
+ public void cannotCreateBranchInMagicBranchNamespace() throws Exception {
+ assertCreateFails(
+ BranchNameKey.create(project, MagicBranch.NEW_CHANGE + "foo"),
+ BadRequestException.class,
+ "not allowed to create branches under \"" + MagicBranch.NEW_CHANGE + "\"");
+ }
+
+ @Test
+ public void cannotCreateBranchWithInvalidName() throws Exception {
+ assertCreateFails(
+ BranchNameKey.create(project, RefNames.REFS_HEADS),
+ BadRequestException.class,
+ "invalid branch name \"" + RefNames.REFS_HEADS + "\"");
+ }
+
+ @Test
+ public void createBranchLeadingSlashesAreRemoved() throws Exception {
+ BranchNameKey expectedNameKey = BranchNameKey.create(project, "test");
+
+ // check that the branch doesn't exist yet
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.projects().name(project.get()).branch(expectedNameKey.branch()).get());
+
+ // create the branch, but include leading slashes in the branch name,
+ // when creating the branch ensure that the branch name in the URL matches the branch name in
+ // the input (if there is a mismatch the creation request is rejected)
+ BranchInput branchInput = new BranchInput();
+ branchInput.ref = "////" + expectedNameKey.shortName();
+ gApi.projects().name(project.get()).branch(branchInput.ref).create(branchInput);
+
+ // verify that the branch was created without the leading slashes in the name
+ assertThat(gApi.projects().name(project.get()).branch(expectedNameKey.branch()).get().ref)
+ .isEqualTo(expectedNameKey.branch());
+ }
+
+ @Test
+ public void branchNameInInputMustMatchBranchNameInUrl() throws Exception {
+ BranchInput branchInput = new BranchInput();
+ branchInput.ref = "foo";
+ BadRequestException ex =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(project.get()).branch("bar").create(branchInput));
+ assertThat(ex).hasMessageThat().isEqualTo("ref must match URL");
+ }
+
private void blockCreateReference() throws Exception {
projectOperations
.project(project)
@@ -302,9 +438,4 @@
assertThat(thrown).hasMessageThat().contains(errMsg);
}
}
-
- private void assertCreateFails(BranchNameKey branch, Class<? extends RestApiException> errType)
- throws Exception {
- assertCreateFails(branch, errType, null);
- }
}
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
index b60cce5..566308d 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -48,6 +48,7 @@
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.notedb.ChangeNoteUtil;
@@ -85,6 +86,7 @@
@Inject private Provider<ChangesCollection> changes;
@Inject private Provider<PostReview> postReview;
@Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private CommentsUtil commentsUtil;
private final Integer[] lines = {0, 1};
@@ -446,6 +448,82 @@
}
@Test
+ public void putDraft_idMismatch() throws Exception {
+ String file = "file";
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ DraftInput comment = newDraft(file, Side.REVISION, 0, "foo");
+ CommentInfo commentInfo = addDraft(changeId, revId, comment);
+ DraftInput draftInput = newDraft(file, Side.REVISION, 0, "bar");
+ draftInput.id = "anything_but_" + commentInfo.id;
+ BadRequestException e =
+ assertThrows(
+ BadRequestException.class,
+ () -> updateDraft(changeId, revId, draftInput, commentInfo.id));
+ assertThat(e).hasMessageThat().contains("id must match URL");
+ }
+
+ @Test
+ public void putDraft_negativeLine() throws Exception {
+ String file = "file";
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ DraftInput comment = newDraft(file, Side.REVISION, -666, "foo");
+ BadRequestException e =
+ assertThrows(BadRequestException.class, () -> addDraft(changeId, revId, comment));
+ assertThat(e).hasMessageThat().contains("line must be >= 0");
+ }
+
+ @Test
+ public void putDraft_invalidRange() throws Exception {
+ String file = "file";
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ DraftInput draftInput = newDraft(file, Side.REVISION, createLineRange(2, 3), "bar");
+ draftInput.line = 666;
+ BadRequestException e =
+ assertThrows(BadRequestException.class, () -> addDraft(changeId, revId, draftInput));
+ assertThat(e)
+ .hasMessageThat()
+ .contains("range endLine must be on the same line as the comment");
+ }
+
+ @Test
+ public void putDraft_updatePath() throws Exception {
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ DraftInput comment = newDraft("file_foo", Side.REVISION, 0, "foo");
+ CommentInfo commentInfo = addDraft(changeId, revId, comment);
+ assertThat(getDraftComments(changeId, revId).keySet()).containsExactly("file_foo");
+ DraftInput draftInput = newDraft("file_bar", Side.REVISION, 0, "bar");
+ updateDraft(changeId, revId, draftInput, commentInfo.id);
+ assertThat(getDraftComments(changeId, revId).keySet()).containsExactly("file_bar");
+ }
+
+ @Test
+ public void putDraft_updateInReplyToAndTag() throws Exception {
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ DraftInput draftInput1 = newDraft(FILE_NAME, Side.REVISION, 0, "foo");
+ CommentInfo commentInfo = addDraft(changeId, revId, draftInput1);
+ DraftInput draftInput2 = newDraft(FILE_NAME, Side.REVISION, 0, "bar");
+ String inReplyTo = "in_reply_to";
+ String tag = "täg";
+ draftInput2.inReplyTo = inReplyTo;
+ draftInput2.tag = tag;
+ updateDraft(changeId, revId, draftInput2, commentInfo.id);
+ com.google.gerrit.entities.Comment comment =
+ Iterables.getOnlyElement(commentsUtil.draftByChange(r.getChange().notes()));
+ assertThat(comment.parentUuid).isEqualTo(inReplyTo);
+ assertThat(comment.tag).isEqualTo(tag);
+ }
+
+ @Test
public void listDrafts() throws Exception {
String file = "file";
PushOneCommit.Result r = createChange();
@@ -677,15 +755,15 @@
addDraft(
r1.getChangeId(),
r1.getCommit().getName(),
- newDraft(FILE_NAME, Side.REVISION, createLineRange(1, 4, 10), "Is it that bad?"));
+ newDraft(FILE_NAME, Side.REVISION, createLineRange(4, 10), "Is it that bad?"));
addDraft(
r1.getChangeId(),
r1.getCommit().getName(),
- newDraft(FILE_NAME, Side.PARENT, createLineRange(1, 0, 7), "what happened to this?"));
+ newDraft(FILE_NAME, Side.PARENT, createLineRange(0, 7), "what happened to this?"));
addDraft(
r2.getChangeId(),
r2.getCommit().getName(),
- newDraft(FILE_NAME, Side.REVISION, createLineRange(1, 4, 15), "better now"));
+ newDraft(FILE_NAME, Side.REVISION, createLineRange(4, 15), "better now"));
addDraft(
r2.getChangeId(),
r2.getCommit().getName(),
@@ -905,7 +983,7 @@
@Test
public void deleteCommentCannotBeAppliedByUser() throws Exception {
PushOneCommit.Result result = createChange();
- CommentInput targetComment = addComment(result.getChangeId(), "My password: abc123");
+ CommentInput targetComment = addComment(result.getChangeId());
Map<String, List<CommentInfo>> commentsMap =
getPublishedComments(result.getChangeId(), result.getCommit().name());
@@ -1083,9 +1161,9 @@
.collect(toList());
}
- private CommentInput addComment(String changeId, String message) throws Exception {
+ private CommentInput addComment(String changeId) throws Exception {
ReviewInput input = new ReviewInput();
- CommentInput comment = newComment(FILE_NAME, Side.REVISION, 0, message, false);
+ CommentInput comment = newComment(FILE_NAME, Side.REVISION, 0, "a message", false);
input.comments = ImmutableMap.of(comment.path, Lists.newArrayList(comment));
gApi.changes().id(changeId).current().review(input);
return comment;
@@ -1240,7 +1318,7 @@
private static CommentInput newCommentOnParent(
String path, int parent, int line, String message) {
CommentInput c = new CommentInput();
- return populate(c, path, Side.PARENT, Integer.valueOf(parent), line, message, false);
+ return populate(c, path, Side.PARENT, parent, line, message, false);
}
private DraftInput newDraft(String path, Side side, int line, String message) {
@@ -1255,7 +1333,7 @@
private DraftInput newDraftOnParent(String path, int parent, int line, String message) {
DraftInput d = new DraftInput();
- return populate(d, path, Side.PARENT, Integer.valueOf(parent), line, message, false);
+ return populate(d, path, Side.PARENT, parent, line, message, false);
}
private static <C extends Comment> C populate(
@@ -1284,11 +1362,11 @@
return populate(c, path, side, parent, line, null, message, unresolved);
}
- private static Comment.Range createLineRange(int line, int startChar, int endChar) {
+ private static Comment.Range createLineRange(int startChar, int endChar) {
Comment.Range range = new Comment.Range();
- range.startLine = line;
+ range.startLine = 1;
range.startCharacter = startChar;
- range.endLine = line;
+ range.endLine = 1;
range.endCharacter = endChar;
return range;
}
diff --git a/javatests/com/google/gerrit/server/change/ArchiveFormatInternalTest.java b/javatests/com/google/gerrit/server/change/ArchiveFormatInternalTest.java
new file mode 100644
index 0000000..003225c
--- /dev/null
+++ b/javatests/com/google/gerrit/server/change/ArchiveFormatInternalTest.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.toList;
+
+import com.google.gerrit.extensions.client.ArchiveFormat;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+
+public class ArchiveFormatInternalTest {
+ @Test
+ public void internalAndExternalArchiveFormatEnumsMatch() throws Exception {
+ assertThat(getEnumNames(ArchiveFormatInternal.class))
+ .containsExactlyElementsIn(getEnumNames(ArchiveFormat.class));
+ }
+
+ private static List<String> getEnumNames(Class<? extends Enum<?>> e) {
+ return Arrays.stream(e.getEnumConstants()).map(Enum::name).collect(toList());
+ }
+}
diff --git a/plugins/download-commands b/plugins/download-commands
index 5c50f9b..3f5a024 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit 5c50f9b17e616fd84d2c561822161fff46bbf902
+Subproject commit 3f5a024fd46f30f4646bfceb285763e44fda15a7
diff --git a/plugins/replication b/plugins/replication
index e3cc87b..864c077 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit e3cc87b88c6a64966a411b8a0a4caad3ebad539b
+Subproject commit 864c077e5e13ebbae5e7d0a3abc95fc8ae3fdc8b
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index c5dd657..de25d79 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -2,7 +2,13 @@
Follow the
[setup instructions for Gerrit backend developers](https://gerrit-review.googlesource.com/Documentation/dev-readme.html)
-where applicable.
+where applicable, the most important command is:
+
+```
+git clone --recurse-submodules https://gerrit.googlesource.com/gerrit
+```
+
+The --recurse-submodules option is needed on git clone to ensure that the core plugins, which are included as git submodules, are also cloned.
## Installing [Bazel](https://bazel.build/)
@@ -46,17 +52,16 @@
or use [nvm - Node Version Manager](https://github.com/nvm-sh/nvm).
-Various steps below require installing additional npm packages. To start developing, it is enough
-to install only top-level packages with the following command:
+### Additional packages
+
+We have several bazel commands to install packages we may need for FE development.
+
+For first time users to get the local server up, `npm start` should be enough and will take care of all of them for you.
```sh
# Install packages from root-level packages.json
bazel fetch @npm//:node_modules
-```
-All other packages are installed by bazel when needed. If you want to install them manually, run the
-following commands:
-```sh
# Install packages from polygerrit-ui/app/packages.json
bazel fetch @ui_npm//:node_modules
@@ -64,7 +69,7 @@
bazel fetch @ui_dev_npm//:node_modules
# Install packages from tools/node_tools/packages.json
-bazel fetch @ui_dev_npm//:node_modules
+bazel fetch @tools_npm//:node_modules
```
More information for installing and using nodejs rules can be found here https://bazelbuild.github.io/rules_nodejs/install.html
@@ -92,6 +97,8 @@
./polygerrit-ui/run-server.sh --plugins=plugins/my_plugin/static/my_plugin.js,plugins/my_plugin/static/my_plugin.html
```
+If any issues occured, please refer to the Troubleshooting section at the bottom or contact the team!
+
## Running locally against production data
### Local website
@@ -201,7 +208,7 @@
* To run the linter on all of your local changes:
```sh
-git diff --name-only master | xargs node_modules/eslint/bin/eslint.js --ext .html,.js
+git diff --name-only HEAD | xargs node_modules/eslint/bin/eslint.js --ext .html,.js
```
We also use the `polylint` tool to lint use of Polymer. To install polylint,
@@ -274,4 +281,22 @@
If you are willing to join the queue and help the community review changes,
you can create an issue through Monorail and request to join the queue!
-We will review your request and start from there.
\ No newline at end of file
+We will review your request and start from there.
+
+## Troubleshotting & Frequently asked questions
+
+1. Local host is blank page and console shows missing files from `polymer-bridges`
+
+Its likely you missed the `polymer-bridges` submodule when you clone the `gerrit` repo.
+
+To fix that, run:
+```
+// fetch the submodule
+git submodule update --init --recursive
+
+// reset the workspace (please save your local changes before running this command)
+npm run clean
+
+// install all dependencies and start the server
+npm start
+```
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
index 850bfb4..e89be0f5 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
@@ -116,6 +116,12 @@
_updateSortedThreads(threads) {
this._sortedThreads =
threads.map(this._getThreadWithSortInfo).sort((c1, c2) => {
+ // threads will be sorted by:
+ // - unresolved first
+ // - with drafts
+ // - file path
+ // - line
+ // - updated time
const c1Date = c1.__date || util.parseDate(c1.updated);
const c2Date = c2.__date || util.parseDate(c2.updated);
const dateCompare = c2Date - c1Date;
@@ -123,11 +129,28 @@
if (!c1.unresolved) { return 1; }
if (!c2.unresolved) { return -1; }
}
+
if (c2.hasDraft || c1.hasDraft) {
if (!c1.hasDraft) { return 1; }
if (!c2.hasDraft) { return -1; }
}
+ // TODO: Update here once we introduce patchset level comments
+ // they may not have or have a special line or path attribute
+
+ if (c1.thread.path !== c2.thread.path) {
+ return c1.thread.path.localeCompare(c2.thread.path);
+ }
+
+ // File level comments (no `line` property)
+ // should always show before any lines
+ if ([c1, c2].some(c => c.thread.line === undefined)) {
+ if (!c1.thread.line) { return -1; }
+ if (!c2.thread.line) { return 1; }
+ } else if (c1.thread.line !== c2.thread.line) {
+ return c1.thread.line - c2.thread.line;
+ }
+
if (dateCompare === 0 && (!c1.id || !c1.id.localeCompare)) {
return 0;
}
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
index cb6ba28..3e87077 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
@@ -114,7 +114,6 @@
},
patch_set: 2,
id: '8caddf38_44770ec1',
- line: 4,
updated: '2018-02-13 22:48:40.000000000',
message: 'Another unresolved comment',
unresolved: true,
@@ -122,7 +121,6 @@
],
patchNum: 2,
path: '/COMMIT_MSG',
- line: 4,
rootId: '8caddf38_44770ec1',
start_datetime: '2018-02-13 22:48:40.000000000',
},
@@ -205,7 +203,7 @@
},
patch_set: 4,
id: 'rc2',
- line: 5,
+ line: 7,
updated: '2019-03-08 18:49:18.000000000',
message: 'test',
unresolved: true,
@@ -228,7 +226,7 @@
],
patchNum: 4,
path: '/COMMIT_MSG',
- line: 5,
+ line: 7,
rootId: 'rc2',
start_datetime: '2019-03-08 18:49:18.000000000',
},
@@ -259,25 +257,26 @@
test('_computeSortedThreads', () => {
assert.equal(element._sortedThreads.length, 7);
- // Draft and unresolved
+ // Draft and unresolved for commit-msg at line 5
assert.equal(element._sortedThreads[0].thread.rootId,
'ecf0b9fa_fe1a5f62');
- // Unresolved robot comment
+ // /COMMIT_MSG
+ // unresolved no draft and file level
assert.equal(element._sortedThreads[1].thread.rootId,
- 'rc2');
- // Unresolved robot comment
- assert.equal(element._sortedThreads[2].thread.rootId,
- 'rc1');
- // unresolved
- assert.equal(element._sortedThreads[3].thread.rootId,
- 'scaddf38_44770ec1');
- // unresolved
- assert.equal(element._sortedThreads[4].thread.rootId,
'8caddf38_44770ec1');
- // resolved and draft
+ // unresolved no draft at line 4
+ assert.equal(element._sortedThreads[2].thread.rootId,
+ 'scaddf38_44770ec1');
+ // unresolved no draft at line 5
+ assert.equal(element._sortedThreads[3].thread.rootId,
+ 'rc1');
+ // Unresolved no draft at line 7
+ assert.equal(element._sortedThreads[4].thread.rootId,
+ 'rc2');
+ // resolved and draft on COMMIT_MSG
assert.equal(element._sortedThreads[5].thread.rootId,
'zcf0b9fa_fe1a5f62');
- // resolved
+ // resolved and on file test.txt
assert.equal(element._sortedThreads[6].thread.rootId,
'09a9fb0a_1484e6cf');
});
@@ -298,19 +297,20 @@
assert.equal(element._sortedThreads.length, 6);
assert.equal(element._sortedThreads[0].thread.rootId,
'ecf0b9fa_fe1a5f62');
- // Unresolved robot comment
+ // /COMMIT_MSG
+ // unresolved no draft and file level
assert.equal(element._sortedThreads[1].thread.rootId,
- 'rc1');
- // unresolved
+ '8caddf38_44770ec1');
+ // unresolved no draft at line 4
assert.equal(element._sortedThreads[2].thread.rootId,
'scaddf38_44770ec1');
- // unresolved
+ // unresolved no draft at line 5
assert.equal(element._sortedThreads[3].thread.rootId,
- '8caddf38_44770ec1');
+ 'rc1');
// resolved and draft
assert.equal(element._sortedThreads[4].thread.rootId,
'zcf0b9fa_fe1a5f62');
- // resolved
+ // resolved and on file test.txt
assert.equal(element._sortedThreads[5].thread.rootId,
'09a9fb0a_1484e6cf');
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index 5bca7be..a3076f0 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -737,14 +737,18 @@
this._prefsChanged(this.prefs);
}
+ _cleanup() {
+ this.cancel();
+ this._blame = null;
+ this._safetyBypass = null;
+ this._showWarning = false;
+ this.clearDiffContent();
+ }
+
/** @param {boolean} newValue */
_loadingChanged(newValue) {
if (newValue) {
- this.cancel();
- this._blame = null;
- this._safetyBypass = null;
- this._showWarning = false;
- this.clearDiffContent();
+ this._cleanup();
}
}
@@ -785,6 +789,7 @@
_diffChanged(newValue) {
if (newValue) {
+ this._cleanup();
this._diffLength = this.getDiffLength(newValue);
this._debounceRenderDiffTable();
}
@@ -806,7 +811,6 @@
}
_renderDiffTable() {
- this._unobserveIncrementalNodes();
if (!this.prefs) {
this.dispatchEvent(
new CustomEvent('render', {bubbles: true, composed: true}));
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index 18b4e09..08576a2 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -952,47 +952,68 @@
});
});
});
+ const setupSampleDiff = function(params) {
+ const {ignore_whitespace, content} = params;
+ element = fixture('basic');
+ element.prefs = {
+ ignore_whitespace: ignore_whitespace || 'IGNORE_ALL',
+ auto_hide_diff_table_header: true,
+ context: 10,
+ cursor_blink_rate: 0,
+ font_size: 12,
+ intraline_difference: true,
+ line_length: 100,
+ line_wrapping: false,
+ show_line_endings: true,
+ show_tabs: true,
+ show_whitespace_errors: true,
+ syntax_highlighting: true,
+ tab_size: 8,
+ theme: 'DEFAULT',
+ };
+ 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: false,
+ };
+ element._renderDiffTable();
+ flushAsynchronousOperations();
+ };
+
+ test('clear diff table content as soon as diff changes', () => {
+ const content = [{
+ a: ['all work and no play make andybons a dull boy'],
+ }, {
+ b: [
+ 'Non eram nescius, Brute, cum, quae summis ingeniis ',
+ ],
+ }];
+ function assertDiffTableWithContent() {
+ assert.isTrue(element.$.diffTable.innerText.includes(content[0].a));
+ }
+ setupSampleDiff({content});
+ assertDiffTableWithContent();
+ const diffCopy = Object.assign({}, element.diff);
+ element.diff = diffCopy;
+ // immediatelly cleaned up
+ assert.equal(element.$.diffTable.innerHTML, '');
+ element._renderDiffTable();
+ flushAsynchronousOperations();
+ // rendered again
+ assertDiffTableWithContent();
+ });
suite('whitespace changes only message', () => {
- const setupDiff = function(ignore_whitespace, diffContent) {
- element = fixture('basic');
- element.prefs = {
- ignore_whitespace,
- auto_hide_diff_table_header: true,
- context: 10,
- cursor_blink_rate: 0,
- font_size: 12,
- intraline_difference: true,
- line_length: 100,
- line_wrapping: false,
- show_line_endings: true,
- show_tabs: true,
- show_whitespace_errors: true,
- syntax_highlighting: true,
- tab_size: 8,
- theme: 'DEFAULT',
- };
-
- 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: diffContent,
- binary: true,
- };
-
- element._renderDiffTable();
- flushAsynchronousOperations();
- };
-
test('show the message if ignore_whitespace is criteria matches', () => {
- setupDiff('IGNORE_ALL', [{skip: 100}]);
+ setupSampleDiff({content: [{skip: 100}]});
assert.isTrue(element.showNoChangeMessage(
/* loading= */ false,
element.prefs,
@@ -1001,7 +1022,7 @@
});
test('do not show the message if still loading', () => {
- setupDiff('IGNORE_ALL', [{skip: 100}]);
+ setupSampleDiff({content: [{skip: 100}]});
assert.isFalse(element.showNoChangeMessage(
/* loading= */ true,
element.prefs,
@@ -1019,7 +1040,7 @@
'exquisitaque doctrina philosophi Graeco sermone tractavissent',
],
}];
- setupDiff('IGNORE_ALL', content);
+ setupSampleDiff({content});
assert.equal(element._diffLength, 3);
assert.isFalse(element.showNoChangeMessage(
/* loading= */ false,
@@ -1038,7 +1059,7 @@
'exquisitaque doctrina philosophi Graeco sermone tractavissent',
],
}];
- setupDiff('IGNORE_NONE', content);
+ setupSampleDiff({ignore_whitespace: 'IGNORE_NONE', content});
assert.isFalse(element.showNoChangeMessage(
/* loading= */ false,
element.prefs,
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js
index 6f1eaa8..252f409 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js
@@ -60,11 +60,12 @@
* All candidates tips to show, will pick randomly.
*/
const RESPECTFUL_REVIEW_TIPS= [
- 'DO: Assume competence.',
- 'DO: Provide rationale or context.',
- 'DO: Consider how comments may be interpreted.',
- 'DON’T: Criticize the person.',
- 'DON’T: Use harsh language.',
+ 'Assume competence.',
+ 'Provide rationale or context.',
+ 'Consider how comments may be interpreted.',
+ 'Avoid harsh language.',
+ 'Make your comments specific and actionable.',
+ 'When disagreeing, explain the advantage of your approach.',
];
/**
@@ -294,8 +295,8 @@
'respectful-tip-dismissed',
{tip: this._respectfulReviewTip}
);
- // add a 3 day delay to the tip cache
- this.$.storage.setRespectfulTipVisibility(/* delayDays= */ 3);
+ // add a 14-day delay to the tip cache
+ this.$.storage.setRespectfulTipVisibility(/* delayDays= */ 14);
}
_onRespectfulReadMoreClick() {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.js b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.js
index 4a0f388..6c9ebee 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.js
@@ -282,7 +282,7 @@
<a tabindex="-1" on-click="_onRespectfulReadMoreClick" href="https://testing.googleblog.com/2019/11/code-health-respectful-reviews-useful.html" target="_blank">
Read more
</a>
- <iron-icon class="close pointer" on-click="_dismissRespectfulTip" icon="gr-icons:close"></iron-icon>
+ <a tabindex="-1" class="close pointer" on-click="_dismissRespectfulTip">Not helpful</a>
</div>
</div>
</template>
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
index 756e6fd..33f19c5 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
@@ -1161,7 +1161,7 @@
});
});
- test('add 3 day delays once dismissed', done => {
+ test('add 14-day delays once dismissed', done => {
// fake stub for storage
const respectfulGetStub = sinon.stub();
const respectfulSetStub = sinon.stub();
@@ -1185,7 +1185,7 @@
MockInteractions.tap(element.shadowRoot
.querySelector('.respectfulReviewTip .close'));
flushAsynchronousOperations();
- assert.isTrue(respectfulSetStub.lastCall.args[0] === 3);
+ assert.isTrue(respectfulSetStub.lastCall.args[0] === 14);
done();
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.html b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.html
index 7a5f4c6..be0f2b2 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.html
@@ -24,20 +24,8 @@
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
<script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
<script src="../../../node_modules/iron-test-helpers/mock-interactions.js" type="module"></script>
-<script type="module" src="./gr-hovercard-account.js"></script>
-
-<script type="module">
- import '../../../test/test-pre-setup.js';
- import '../../../test/common-test-setup.js';
- import './gr-hovercard-account.js';
-
- void (0);
-</script>
-
<test-fixture id="basic">
<template>
<gr-hovercard-account class="hovered"></gr-hovercard-account>
@@ -46,7 +34,6 @@
<script type="module">
- import '../../../test/test-pre-setup.js';
import '../../../test/common-test-setup.js';
import './gr-hovercard-account.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.js b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.js
index a77f5f7..1ba3c23 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.js
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.js
@@ -30,6 +30,12 @@
const DIAGONAL_OVERFLOW = 15;
/**
+ * How long should be wait before showing the hovercard when the user hovers
+ * over the element?
+ */
+const SHOW_DELAY_MS = 500;
+
+/**
* The mixin for gr-hovercard-behavior.
*
* @example
@@ -118,8 +124,8 @@
attached() {
super.attached();
if (!this._target) { this._target = this.target; }
- this.listen(this._target, 'mouseenter', 'show');
- this.listen(this._target, 'focus', 'show');
+ this.listen(this._target, 'mouseenter', 'showDelayed');
+ this.listen(this._target, 'focus', 'showDelayed');
this.listen(this._target, 'mouseleave', 'hide');
this.listen(this._target, 'blur', 'hide');
this.listen(this._target, 'click', 'hide');
@@ -184,6 +190,10 @@
* @param {Event} e DOM Event (e.g. `mouseleave` event)
*/
hide(e) {
+ this._isScheduledToShow = false;
+ if (!this._isShowing) {
+ return;
+ }
const targetRect = this._target.getBoundingClientRect();
const x = e.clientX;
const y = e.clientY;
@@ -195,10 +205,10 @@
return;
}
- // If the hovercard is already hidden or the user is now hovering over the
- // hovercard or the user is returning from the hovercard but now hovering
- // over the target (to stop an annoying flicker effect), just return.
- if (!this._isShowing || e.toElement === this ||
+ // If the user is now hovering over the hovercard or the user is returning
+ // from the hovercard but now hovering over the target (to stop an annoying
+ // flicker effect), just return.
+ if (e.toElement === this ||
(e.fromElement === this && e.toElement === this._target)) {
return;
}
@@ -221,12 +231,31 @@
}
/**
+ * Shows/opens the hovercard with a fixed delay.
+ */
+ showDelayed() {
+ this.showDelayedBy(SHOW_DELAY_MS);
+ }
+
+ /**
+ * Shows/opens the hovercard with the given delay.
+ */
+ showDelayedBy(delayMs) {
+ if (this._isShowing || this._isScheduledToShow) return;
+ this._isScheduledToShow = true;
+ setTimeout(() => {
+ // This happens when the mouse leaves the target before the delay is over.
+ if (!this._isScheduledToShow) return;
+ this._isScheduledToShow = false;
+ this.show();
+ }, delayMs);
+ }
+
+ /**
* Shows/opens the hovercard. This occurs when the user triggers the
* `mousenter` event on the hovercard's `target` element.
- *
- * @param {Event} e DOM Event (e.g., `mouseenter` event)
*/
- show(e) {
+ show() {
if (this._isShowing) {
return;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
index 1ffed0a..99791e7 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
@@ -109,14 +109,31 @@
}, TRANSITION_TIME);
});
- test('card shows on enter and hides on leave', done => {
+ test('showDelayed does not show immediately', done => {
+ element.showDelayedBy(100);
+ setTimeout(() => {
+ assert.isFalse(element._isShowing);
+ done();
+ }, 0);
+ });
+
+ test('showDelayed shows after delay', done => {
+ element.showDelayedBy(1);
+ setTimeout(() => {
+ assert.isTrue(element._isShowing);
+ done();
+ }, 10);
+ });
+
+ test('card is scheduled to show on enter and hides on leave', done => {
const button = dom(document).querySelector('button');
assert.isFalse(element._isShowing);
button.addEventListener('mouseenter', event => {
- assert.isTrue(element._isShowing);
+ assert.isTrue(element._isScheduledToShow);
button.dispatchEvent(new CustomEvent('mouseleave'));
});
button.addEventListener('mouseleave', event => {
+ assert.isFalse(element._isScheduledToShow);
assert.isFalse(element._isShowing);
done();
});
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.html
index df3b9a5..015d71e 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.html
@@ -16,7 +16,6 @@
-->
<link rel="import" href="/bower_components/polymer/polymer.html">
-<script src="../../../test/test-pre-setup.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<dom-module id="mock-diff-response">
<template></template>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.js
index e29d300..e7bc662 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.js
@@ -16,7 +16,6 @@
*/
import '../../../scripts/bundled-polymer.js';
-import '../../../test/test-pre-setup.js';
import '../../../test/common-test-setup.js';
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
index 1597439..8f5c486 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
@@ -26,8 +26,8 @@
const CLEANUP_THROTTLE_INTERVAL = DURATION_DAY;
const CLEANUP_PREFIXES_MAX_AGE_MAP = {
- // respectfultip has a 3 day expiration
- 'respectfultip:': 3 * DURATION_DAY,
+ // respectfultip has a 14-day expiration
+ 'respectfultip:': 14 * DURATION_DAY,
'draft:': DURATION_DAY,
'editablecontent:': DURATION_DAY,
};
diff --git a/polygerrit-ui/app/wct.conf.js b/polygerrit-ui/app/wct.conf.js
index 0e53adb..1a9300e 100644
--- a/polygerrit-ui/app/wct.conf.js
+++ b/polygerrit-ui/app/wct.conf.js
@@ -27,7 +27,7 @@
*/
const headless = 'WCT_HEADLESS_MODE' in process.env ?
- process.env['WCT_HEADLESS_MODE'] !== '0' : false;
+ process.env['WCT_HEADLESS_MODE'] === '1' : false;
const headlessBrowserOptions = {
chrome: ['start-maximized', 'headless', 'disable-gpu', 'no-sandbox'],
diff --git a/tools/node_tools/BUILD b/tools/node_tools/BUILD
index 018674c..4019542 100644
--- a/tools/node_tools/BUILD
+++ b/tools/node_tools/BUILD
@@ -8,8 +8,12 @@
# Usage: rollup_bundle(rollup_bin = "//tools/node_tools:rollup-bin, ...)
nodejs_binary(
name = "rollup-bin",
+ # Define only minimal required dependencies.
+ # Otherwise remote build execution fails with the too many
+ # files error when it builds :release target.
data = [
- "@tools_npm//:node_modules",
+ "@tools_npm//rollup",
+ "@tools_npm//rollup-plugin-terser",
],
# The entry point must be "@tools_npm:node_modules/rollup/dist/bin/rollup",
# But bazel doesn't run it correctly with the following command line: