Merge "Cut down to below 73 characters when reverting changes"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 1396cf5..d5abbe3 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -3910,6 +3910,10 @@
The review must be provided in the request body as a
link:#review-input[ReviewInput] entity.
+If the labels are set, the user sending the request will automatically be
+added as a reviewer, otherwise (if they only commented) they are added to
+the CC list.
+
A review cannot be set on a change edit. Trying to post a review for a
change edit fails with `409 Conflict`.
diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeEditApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeEditApi.java
index 25eb7a8..6bd4b73 100644
--- a/java/com/google/gerrit/extensions/api/changes/ChangeEditApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/ChangeEditApi.java
@@ -150,7 +150,22 @@
* @param newContent the desired content of the file
* @throws RestApiException if the content of the file couldn't be modified
*/
- void modifyFile(String filePath, RawInput newContent) throws RestApiException;
+ default void modifyFile(String filePath, RawInput newContent) throws RestApiException {
+ FileContentInput input = new FileContentInput();
+ input.content = newContent;
+ modifyFile(filePath, input);
+ }
+
+ /**
+ * Modify the contents of the specified file of the change edit. If no content is provided, the
+ * content of the file is erased but the file isn't deleted. If the change edit doesn't exist, it
+ * will be created based on the current patch set of the change.
+ *
+ * @param filePath the path of the file which should be modified
+ * @param input the desired content of the file
+ * @throws RestApiException if the content of the file couldn't be modified
+ */
+ void modifyFile(String filePath, FileContentInput input) throws RestApiException;
/**
* Deletes the specified file from the change edit. If the change edit doesn't exist, it will be
@@ -235,7 +250,7 @@
}
@Override
- public void modifyFile(String filePath, RawInput newContent) throws RestApiException {
+ public void modifyFile(String filePath, FileContentInput input) throws RestApiException {
throw new NotImplementedException();
}
diff --git a/java/com/google/gerrit/extensions/api/changes/FileContentInput.java b/java/com/google/gerrit/extensions/api/changes/FileContentInput.java
new file mode 100644
index 0000000..93c253d
--- /dev/null
+++ b/java/com/google/gerrit/extensions/api/changes/FileContentInput.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.changes;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+import com.google.gerrit.extensions.restapi.RawInput;
+
+/** Content to be added to a file (new or existing) via change edit. */
+public class FileContentInput {
+ @DefaultInput public RawInput content;
+}
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index b8ecb23..f390ebc 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -52,6 +52,7 @@
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
+import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
@@ -113,10 +114,12 @@
import com.google.gerrit.server.change.ChangeFinder;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.group.GroupAuditService;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.PerformanceLogContext;
import com.google.gerrit.server.logging.PerformanceLogger;
import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -492,7 +495,7 @@
checkRequiresCapability(viewData);
}
- if (notModified(req, rsrc, viewData.view)) {
+ if (notModified(req, traceContext, viewData, rsrc)) {
logger.atFinest().log("REST call succeeded: %d", SC_NOT_MODIFIED);
res.sendError(SC_NOT_MODIFIED);
return;
@@ -586,7 +589,7 @@
}
status = response.statusCode();
- configureCaching(req, res, rsrc, viewData.view, response.caching());
+ configureCaching(req, res, traceContext, rsrc, viewData, response.caching());
res.setStatus(status);
logger.atFinest().log("REST call succeeded: %d", status);
}
@@ -711,6 +714,46 @@
}
}
+ private String getEtagWithRetry(
+ HttpServletRequest req,
+ TraceContext traceContext,
+ ViewData viewData,
+ ETagView<RestResource> view,
+ RestResource rsrc) {
+ try (TraceTimer ignored =
+ TraceContext.newTimer(
+ "RestApiServlet#getEtagWithRetry:view",
+ Metadata.builder().restViewName(getViewName(viewData)).build())) {
+ return invokeRestEndpointWithRetry(
+ req,
+ traceContext,
+ getViewName(viewData) + "#etag",
+ ActionType.REST_READ_REQUEST,
+ () -> view.getETag(rsrc));
+ } catch (Exception e) {
+ Throwables.throwIfUnchecked(e);
+ throw new IllegalStateException("Failed to get ETag for view", e);
+ }
+ }
+
+ private String getEtagWithRetry(
+ HttpServletRequest req, TraceContext traceContext, RestResource.HasETag rsrc) {
+ try (TraceTimer ignored =
+ TraceContext.newTimer(
+ "RestApiServlet#getEtagWithRetry:resource",
+ Metadata.builder().restViewName(rsrc.getClass().getSimpleName()).build())) {
+ return invokeRestEndpointWithRetry(
+ req,
+ traceContext,
+ rsrc.getClass().getSimpleName() + "#etag",
+ ActionType.REST_READ_REQUEST,
+ () -> rsrc.getETag());
+ } catch (Exception e) {
+ Throwables.throwIfUnchecked(e);
+ throw new IllegalStateException("Failed to get ETag for resource", e);
+ }
+ }
+
private RestResource parseResourceWithRetry(
HttpServletRequest req,
TraceContext traceContext,
@@ -962,24 +1005,27 @@
return defaultMessage;
}
- @SuppressWarnings({"unchecked", "rawtypes"})
- private static boolean notModified(
- HttpServletRequest req, RestResource rsrc, RestView<RestResource> view) {
+ private boolean notModified(
+ HttpServletRequest req, TraceContext traceContext, ViewData viewData, RestResource rsrc) {
if (!isRead(req)) {
return false;
}
+ RestView<RestResource> view = viewData.view;
if (view instanceof ETagView) {
String have = req.getHeader(HttpHeaders.IF_NONE_MATCH);
if (have != null) {
- return have.equals(((ETagView) view).getETag(rsrc));
+ String eTag =
+ getEtagWithRetry(req, traceContext, viewData, (ETagView<RestResource>) view, rsrc);
+ return have.equals(eTag);
}
}
if (rsrc instanceof RestResource.HasETag) {
String have = req.getHeader(HttpHeaders.IF_NONE_MATCH);
if (have != null) {
- return have.equals(((RestResource.HasETag) rsrc).getETag());
+ String eTag = getEtagWithRetry(req, traceContext, (RestResource.HasETag) rsrc);
+ return have.equals(eTag);
}
}
@@ -993,21 +1039,48 @@
return false;
}
- private static <R extends RestResource> void configureCaching(
- HttpServletRequest req, HttpServletResponse res, R rsrc, RestView<R> view, CacheControl c) {
+ private <R extends RestResource> void configureCaching(
+ HttpServletRequest req,
+ HttpServletResponse res,
+ TraceContext traceContext,
+ R rsrc,
+ ViewData viewData,
+ CacheControl cacheControl) {
+ setCacheHeaders(req, res, cacheControl);
if (isRead(req)) {
- switch (c.getType()) {
+ switch (cacheControl.getType()) {
+ case NONE:
+ default:
+ break;
+ case PRIVATE:
+ addResourceStateHeaders(req, res, traceContext, viewData, rsrc);
+ break;
+ case PUBLIC:
+ addResourceStateHeaders(req, res, traceContext, viewData, rsrc);
+ break;
+ }
+ }
+ }
+
+ private static <R extends RestResource> void setCacheHeaders(
+ HttpServletRequest req, HttpServletResponse res, CacheControl cacheControl) {
+ if (isRead(req)) {
+ switch (cacheControl.getType()) {
case NONE:
default:
CacheHeaders.setNotCacheable(res);
break;
case PRIVATE:
- addResourceStateHeaders(res, rsrc, view);
- CacheHeaders.setCacheablePrivate(res, c.getAge(), c.getUnit(), c.isMustRevalidate());
+ CacheHeaders.setCacheablePrivate(
+ res, cacheControl.getAge(), cacheControl.getUnit(), cacheControl.isMustRevalidate());
break;
case PUBLIC:
- addResourceStateHeaders(res, rsrc, view);
- CacheHeaders.setCacheable(req, res, c.getAge(), c.getUnit(), c.isMustRevalidate());
+ CacheHeaders.setCacheable(
+ req,
+ res,
+ cacheControl.getAge(),
+ cacheControl.getUnit(),
+ cacheControl.isMustRevalidate());
break;
}
} else {
@@ -1015,12 +1088,20 @@
}
}
- private static <R extends RestResource> void addResourceStateHeaders(
- HttpServletResponse res, R rsrc, RestView<R> view) {
+ private void addResourceStateHeaders(
+ HttpServletRequest req,
+ HttpServletResponse res,
+ TraceContext traceContext,
+ ViewData viewData,
+ RestResource rsrc) {
+ RestView<RestResource> view = viewData.view;
if (view instanceof ETagView) {
- res.setHeader(HttpHeaders.ETAG, ((ETagView<R>) view).getETag(rsrc));
+ String eTag =
+ getEtagWithRetry(req, traceContext, viewData, (ETagView<RestResource>) view, rsrc);
+ res.setHeader(HttpHeaders.ETAG, eTag);
} else if (rsrc instanceof RestResource.HasETag) {
- res.setHeader(HttpHeaders.ETAG, ((RestResource.HasETag) rsrc).getETag());
+ String eTag = getEtagWithRetry(req, traceContext, (RestResource.HasETag) rsrc);
+ res.setHeader(HttpHeaders.ETAG, eTag);
}
if (rsrc instanceof RestResource.HasLastModified) {
res.setDateHeader(
@@ -1662,43 +1743,47 @@
private long handleException(
TraceContext traceContext, Throwable err, HttpServletRequest req, HttpServletResponse res)
throws IOException {
- logger.atSevere().withCause(err).log("Error in %s %s", req.getMethod(), uriForLogging(req));
- if (!res.isCommitted()) {
- res.reset();
- traceContext.getTraceId().ifPresent(traceId -> res.addHeader(X_GERRIT_TRACE, traceId));
- ImmutableList<String> userMessages =
- globals.exceptionHooks.stream()
- .map(h -> h.getUserMessage(err))
- .filter(Optional::isPresent)
- .map(Optional::get)
- .collect(toImmutableList());
+ if (res.isCommitted()) {
+ logger.atSevere().withCause(err).log(
+ "Error in %s %s, response already committed", req.getMethod(), uriForLogging(req));
+ return 0;
+ }
- Optional<Integer> statusCode =
- globals.exceptionHooks.stream()
- .map(h -> h.getStatusCode(err))
- .filter(Optional::isPresent)
- .map(Optional::get)
- .findFirst();
- if (statusCode.isPresent() && statusCode.get() < 400) {
- StringBuilder msg = new StringBuilder();
- if (userMessages.size() == 1) {
- msg.append(userMessages.get(0));
- } else {
- userMessages.forEach(m -> msg.append("\n* ").append(m));
- }
+ res.reset();
+ traceContext.getTraceId().ifPresent(traceId -> res.addHeader(X_GERRIT_TRACE, traceId));
+ ImmutableList<String> userMessages =
+ globals.exceptionHooks.stream()
+ .map(h -> h.getUserMessage(err))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(toImmutableList());
- res.setStatus(statusCode.get());
- logger.atFinest().withCause(err).log("REST call finished: %d", statusCode.get().intValue());
- return replyText(req, res, true, msg.toString());
- }
-
- StringBuilder msg = new StringBuilder("Internal server error");
- if (!userMessages.isEmpty()) {
+ Optional<Integer> statusCode =
+ globals.exceptionHooks.stream()
+ .map(h -> h.getStatusCode(err))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .findFirst();
+ if (statusCode.isPresent() && statusCode.get() < 400) {
+ StringBuilder msg = new StringBuilder();
+ if (userMessages.size() == 1) {
+ msg.append(userMessages.get(0));
+ } else {
userMessages.forEach(m -> msg.append("\n* ").append(m));
}
- return replyError(req, res, statusCode.orElse(SC_INTERNAL_SERVER_ERROR), msg.toString(), err);
+
+ res.setStatus(statusCode.get());
+ logger.atFinest().withCause(err).log("REST call finished: %d", statusCode.get().intValue());
+ return replyText(req, res, true, msg.toString());
}
- return 0;
+
+ logger.atSevere().withCause(err).log("Error in %s %s", req.getMethod(), uriForLogging(req));
+
+ StringBuilder msg = new StringBuilder("Internal server error");
+ if (!userMessages.isEmpty()) {
+ userMessages.forEach(m -> msg.append("\n* ").append(m));
+ }
+ return replyError(req, res, statusCode.orElse(SC_INTERNAL_SERVER_ERROR), msg.toString(), err);
}
private static String uriForLogging(HttpServletRequest req) {
@@ -1724,13 +1809,13 @@
HttpServletResponse res,
int statusCode,
String msg,
- CacheControl c,
+ CacheControl cacheControl,
@Nullable Throwable err)
throws IOException {
if (err != null) {
RequestUtil.setErrorTraceAttribute(req, err);
}
- configureCaching(req, res, null, null, c);
+ setCacheHeaders(req, res, cacheControl);
checkArgument(statusCode >= 400, "non-error status: %s", statusCode);
res.setStatus(statusCode);
logger.atFinest().withCause(err).log("REST call failed: %d", statusCode);
diff --git a/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java
index 7f0feba..1b0f0c5 100644
--- a/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
import com.google.gerrit.extensions.api.changes.ChangeEditApi;
+import com.google.gerrit.extensions.api.changes.FileContentInput;
import com.google.gerrit.extensions.api.changes.PublishChangeEditInput;
import com.google.gerrit.extensions.client.ChangeEditDetailOption;
import com.google.gerrit.extensions.common.EditInfo;
@@ -24,7 +25,6 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.RawInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -200,9 +200,9 @@
}
@Override
- public void modifyFile(String filePath, RawInput newContent) throws RestApiException {
+ public void modifyFile(String filePath, FileContentInput input) throws RestApiException {
try {
- changeEditsPut.apply(changeResource, filePath, newContent);
+ changeEditsPut.apply(changeResource, filePath, input);
} catch (Exception e) {
throw asRestApiException("Cannot modify file of change edit", e);
}
diff --git a/java/com/google/gerrit/server/patch/AutoMerger.java b/java/com/google/gerrit/server/patch/AutoMerger.java
index a9cc9b5..2e0214c 100644
--- a/java/com/google/gerrit/server/patch/AutoMerger.java
+++ b/java/com/google/gerrit/server/patch/AutoMerger.java
@@ -16,14 +16,17 @@
import static com.google.common.base.Preconditions.checkArgument;
-import com.google.common.flogger.FluentLogger;
+import com.google.common.base.Throwables;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.InMemoryInserter;
import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.update.RetryHelper;
+import com.google.gerrit.server.update.RetryableAction.ActionType;
import com.google.inject.Inject;
import java.io.IOException;
import org.eclipse.jgit.dircache.DirCache;
@@ -42,28 +45,54 @@
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
+/**
+ * Utility class for creating an auto-merge commit of a merge commit.
+ *
+ * <p>An auto-merge commit is the result of merging the 2 parents of a merge commit automatically.
+ * If there are conflicts the auto-merge commit contains Git conflict markers that indicate these
+ * conflicts.
+ *
+ * <p>Creating auto-merge commits for octopus merges (merge commits with more than 2 parents) is not
+ * supported. In this case the auto-merge is created between the first 2 parent commits.
+ *
+ * <p>All created auto-merge commits are stored in the repository of their merge commit as {@code
+ * refs/cache-automerge/} branches. These branches serve:
+ *
+ * <ul>
+ * <li>as a cache so that the each auto-merge gets computed only once
+ * <li>as base for merge commits on which users can comment
+ * </ul>
+ *
+ * <p>The second point means that these commits are referenced from NoteDb. The consequence of this
+ * is that these refs should never be deleted.
+ */
public class AutoMerger {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
@UsedAt(UsedAt.Project.GOOGLE)
public static boolean cacheAutomerge(Config cfg) {
return cfg.getBoolean("change", null, "cacheAutomerge", true);
}
+ private final RetryHelper retryHelper;
private final PersonIdent gerritIdent;
private final boolean save;
@Inject
- AutoMerger(@GerritServerConfig Config cfg, @GerritPersonIdent PersonIdent gerritIdent) {
+ AutoMerger(
+ RetryHelper retryHelper,
+ @GerritServerConfig Config cfg,
+ @GerritPersonIdent PersonIdent gerritIdent) {
+ this.retryHelper = retryHelper;
save = cacheAutomerge(cfg);
this.gerritIdent = gerritIdent;
}
/**
- * Perform an auto-merge of the parents of the given merge commit.
+ * Creates an auto-merge commit of the parents of the given merge commit.
*
- * @return auto-merge commit or {@code null} if an auto-merge commit couldn't be created. Headers
- * of the returned RevCommit are parsed.
+ * <p>In case of an exception the creation of the auto-merge commit is retried a few times. E.g.
+ * this allows the operation to succeed if a Git update fails due to a temporary issue.
+ *
+ * @return auto-merge commit. Headers of the returned RevCommit are parsed.
*/
public RevCommit merge(
Repository repo,
@@ -72,7 +101,34 @@
RevCommit merge,
ThreeWayMergeStrategy mergeStrategy)
throws IOException {
+ try {
+ return retryHelper
+ .action(
+ ActionType.GIT_UPDATE,
+ "createAutoMerge",
+ () -> createAutoMergeCommit(repo, rw, ins, merge, mergeStrategy))
+ .call();
+ } catch (Exception e) {
+ Throwables.throwIfUnchecked(e);
+ Throwables.throwIfInstanceOf(e, IOException.class);
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Creates an auto-merge commit of the parents of the given merge commit.
+ *
+ * @return auto-merge commit. Headers of the returned RevCommit are parsed.
+ */
+ private RevCommit createAutoMergeCommit(
+ Repository repo,
+ RevWalk rw,
+ ObjectInserter ins,
+ RevCommit merge,
+ ThreeWayMergeStrategy mergeStrategy)
+ throws IOException {
checkArgument(rw.getObjectReader().getCreatedFromInserter() == ins);
+
InMemoryInserter tmpIns = null;
if (ins instanceof InMemoryInserter) {
// Caller gave us an in-memory inserter, so ensure anything we write from
@@ -102,17 +158,7 @@
m.setDirCache(dc);
m.setObjectInserter(tmpIns == null ? new NonFlushingWrapper(ins) : tmpIns);
- boolean couldMerge;
- try {
- couldMerge = m.merge(merge.getParents());
- } catch (IOException | RuntimeException e) {
- // It is not safe to continue further down in this method as throwing
- // an exception most likely means that the merge tree was not created
- // and m.getMergeResults() is empty. This would mean that all paths are
- // unmerged and Gerrit UI would show all paths in the patch list.
- logger.atWarning().withCause(e).log("Error attempting automerge %s", refName);
- return null;
- }
+ boolean couldMerge = m.merge(merge.getParents());
ObjectId treeId;
if (couldMerge) {
@@ -173,8 +219,28 @@
RefUpdate ru = repo.updateRef(refName);
ru.setNewObjectId(commitId);
ru.disableRefLog();
- ru.forceUpdate();
- return rw.parseCommit(commitId);
+ switch (ru.forceUpdate()) {
+ case FAST_FORWARD:
+ case FORCED:
+ case NEW:
+ case NO_CHANGE:
+ return rw.parseCommit(commitId);
+ case LOCK_FAILURE:
+ throw new LockFailureException(
+ String.format("Failed to create auto-merge of %s", merge.name()), ru);
+ case IO_FAILURE:
+ case NOT_ATTEMPTED:
+ case REJECTED:
+ case REJECTED_CURRENT_BRANCH:
+ case REJECTED_MISSING_OBJECT:
+ case REJECTED_OTHER_REASON:
+ case RENAMED:
+ default:
+ throw new IOException(
+ String.format(
+ "Failed to create auto-merge of %s: Cannot write %s (%s)",
+ merge.name(), refName, ru.getResult()));
+ }
}
private static class NonFlushingWrapper extends ObjectInserter.Filter {
diff --git a/java/com/google/gerrit/server/permissions/ChangeControl.java b/java/com/google/gerrit/server/permissions/ChangeControl.java
index 07cb50d..253f50c 100644
--- a/java/com/google/gerrit/server/permissions/ChangeControl.java
+++ b/java/com/google/gerrit/server/permissions/ChangeControl.java
@@ -89,7 +89,7 @@
}
/** Can this user see this change? */
- private boolean isVisible(@Nullable ChangeData cd) {
+ private boolean isVisible(ChangeData cd) {
if (getChange().isPrivate() && !isPrivateVisible(cd)) {
return false;
}
@@ -144,7 +144,7 @@
/** Is this user assigned to this change? */
private boolean isAssignee() {
- Account.Id currentAssignee = notes.getChange().getAssignee();
+ Account.Id currentAssignee = getChange().getAssignee();
if (currentAssignee != null && getUser().isIdentifiedUser()) {
Account.Id id = getUser().getAccountId();
return id.equals(currentAssignee);
@@ -153,9 +153,8 @@
}
/** Is this user a reviewer for the change? */
- private boolean isReviewer(@Nullable ChangeData cd) {
+ private boolean isReviewer(ChangeData cd) {
if (getUser().isIdentifiedUser()) {
- cd = cd != null ? cd : changeDataFactory.create(notes);
Collection<Account.Id> results = cd.reviewers().all();
return results.contains(getUser().getAccountId());
}
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index df5d291..5f076b1 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -25,6 +25,7 @@
import com.google.common.base.Enums;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
+import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
@@ -100,6 +101,8 @@
/** Parses a query string meant to be applied to change objects. */
public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuilder> {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
public interface ChangeOperatorFactory extends OperatorFactory<ChangeData, ChangeQueryBuilder> {}
/**
@@ -1441,8 +1444,14 @@
accounts.stream()
.map(id -> ReviewerPredicate.forState(id, state))
.collect(toList()));
+ } else {
+ logger.atFine().log(
+ "Skipping reviewer predicate for %s in default field query"
+ + " because the number of matched accounts (%d) exceeds the limit of %d",
+ who, accounts.size(), MAX_ACCOUNTS_PER_DEFAULT_FIELD);
}
} catch (QueryParseException e) {
+ logger.atFine().log("Parsing %s as account failed: %s", who, e.getMessage());
// Propagate this exception only if we can't use 'who' to query by email
if (reviewerByEmailPredicate == null) {
throw e;
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
index 1808fa6..e220f35 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
@@ -19,6 +19,7 @@
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.api.changes.FileContentInput;
import com.google.gerrit.extensions.common.DiffWebLinkInfo;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.common.Input;
@@ -109,7 +110,7 @@
*/
@Singleton
public static class Create
- implements RestCollectionCreateView<ChangeResource, ChangeEditResource, Put.Input> {
+ implements RestCollectionCreateView<ChangeResource, ChangeEditResource, FileContentInput> {
private final Put putEdit;
@Inject
@@ -118,9 +119,9 @@
}
@Override
- public Response<?> apply(ChangeResource resource, IdString id, Put.Input input)
+ public Response<?> apply(ChangeResource resource, IdString id, FileContentInput input)
throws AuthException, ResourceConflictException, IOException, PermissionBackendException {
- putEdit.apply(resource, id.get(), input.content);
+ putEdit.apply(resource, id.get(), input);
return Response.none();
}
}
@@ -267,11 +268,7 @@
/** Put handler that is activated when PUT request is called on collection element. */
@Singleton
- public static class Put implements RestModifyView<ChangeEditResource, Put.Input> {
- public static class Input {
- @DefaultInput public RawInput content;
- }
-
+ public static class Put implements RestModifyView<ChangeEditResource, FileContentInput> {
private final ChangeEditModifier editModifier;
private final GitRepositoryManager repositoryManager;
@@ -282,17 +279,18 @@
}
@Override
- public Response<?> apply(ChangeEditResource rsrc, Input input)
+ public Response<?> apply(ChangeEditResource rsrc, FileContentInput input)
throws AuthException, ResourceConflictException, IOException, PermissionBackendException {
- return apply(rsrc.getChangeResource(), rsrc.getPath(), input.content);
+ return apply(rsrc.getChangeResource(), rsrc.getPath(), input);
}
- public Response<?> apply(ChangeResource rsrc, String path, RawInput newContent)
+ public Response<?> apply(ChangeResource rsrc, String path, FileContentInput input)
throws ResourceConflictException, AuthException, IOException, PermissionBackendException {
if (Strings.isNullOrEmpty(path) || path.charAt(0) == '/') {
throw new ResourceConflictException("Invalid path: " + path);
}
+ RawInput newContent = input.content;
try (Repository repository = repositoryManager.openRepository(rsrc.getProject())) {
editModifier.modifyFile(repository, rsrc.getNotes(), path, newContent);
} catch (InvalidChangeOperationException e) {
diff --git a/java/com/google/gerrit/server/restapi/change/GetRelated.java b/java/com/google/gerrit/server/restapi/change/GetRelated.java
index a846d50..5f075f6 100644
--- a/java/com/google/gerrit/server/restapi/change/GetRelated.java
+++ b/java/com/google/gerrit/server/restapi/change/GetRelated.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.restapi.change;
+import static com.google.common.base.Preconditions.checkArgument;
import static java.util.stream.Collectors.toSet;
import com.google.common.annotations.VisibleForTesting;
@@ -55,17 +56,20 @@
private final PatchSetUtil psUtil;
private final RelatedChangesSorter sorter;
private final IndexConfig indexConfig;
+ private final ChangeData.Factory changeDataFactory;
@Inject
GetRelated(
Provider<InternalChangeQuery> queryProvider,
PatchSetUtil psUtil,
RelatedChangesSorter sorter,
- IndexConfig indexConfig) {
+ IndexConfig indexConfig,
+ ChangeData.Factory changeDataFactory) {
this.queryProvider = queryProvider;
this.psUtil = psUtil;
this.sorter = sorter;
this.indexConfig = indexConfig;
+ this.changeDataFactory = changeDataFactory;
}
@Override
@@ -98,7 +102,7 @@
boolean isEdit = rsrc.getEdit().isPresent();
PatchSet basePs = isEdit ? rsrc.getEdit().get().getBasePatchSet() : rsrc.getPatchSet();
- reloadChangeIfStale(cds, basePs);
+ cds = reloadChangeIfStale(cds, rsrc.getChange(), basePs);
for (RelatedChangesSorter.PatchSetData d : sorter.sort(cds, basePs)) {
PatchSet ps = d.patchSet();
@@ -127,14 +131,30 @@
return psUtil.byChange(notes).stream().flatMap(ps -> ps.groups().stream()).collect(toSet());
}
- private void reloadChangeIfStale(List<ChangeData> cds, PatchSet wantedPs) {
- for (ChangeData cd : cds) {
- if (cd.getId().equals(wantedPs.id().changeId())) {
- if (cd.patchSet(wantedPs.id()) == null) {
- cd.reloadChange();
- }
- }
+ private List<ChangeData> reloadChangeIfStale(
+ List<ChangeData> changeDatasFromIndex, Change wantedChange, PatchSet wantedPs) {
+ checkArgument(
+ wantedChange.getId().equals(wantedPs.id().changeId()),
+ "change of wantedPs (%s) doesn't match wantedChange (%s)",
+ wantedPs.id().changeId(),
+ wantedChange.getId());
+
+ List<ChangeData> changeDatas = new ArrayList<>(changeDatasFromIndex.size() + 1);
+ changeDatas.addAll(changeDatasFromIndex);
+
+ // Reload the change in case the patch set is absent.
+ changeDatas.stream()
+ .filter(
+ cd -> cd.getId().equals(wantedPs.id().changeId()) && cd.patchSet(wantedPs.id()) == null)
+ .forEach(ChangeData::reloadChange);
+
+ if (changeDatas.stream().noneMatch(cd -> cd.getId().equals(wantedPs.id().changeId()))) {
+ // The change of the wanted patch set is missing in the result from the index.
+ // Load it from NoteDb and add it to the result.
+ changeDatas.add(changeDataFactory.create(wantedChange));
}
+
+ return changeDatas;
}
static RelatedChangeAndCommitInfo newChangeAndCommit(
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index 64c4578..79ae43b 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -16,7 +16,6 @@
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
import static java.util.Comparator.comparing;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toSet;
@@ -459,11 +458,17 @@
try {
ChangeSet indexBackedChangeSet =
mergeSuperSet.setMergeOpRepoManager(orm).completeChangeSet(change, caller);
- checkState(
- indexBackedChangeSet.ids().contains(change.getId()),
- "change %s missing from %s",
- change.getId(),
- indexBackedChangeSet);
+ if (!indexBackedChangeSet.ids().contains(change.getId())) {
+ // indexBackedChangeSet contains only open changes, if the change is missing in this set
+ // it might be that the change was concurrently submitted in the meantime.
+ change = changeDataFactory.create(change).reloadChange();
+ if (!change.isNew()) {
+ throw new ResourceConflictException("change is " + ChangeUtil.status(change));
+ }
+ throw new IllegalStateException(
+ String.format("change %s missing from %s", change.getId(), indexBackedChangeSet));
+ }
+
if (indexBackedChangeSet.furtherHiddenChanges()) {
throw new AuthException(
"A change to be submitted with " + change.getId() + " is not visible");
diff --git a/java/com/google/gerrit/server/submit/MergeSuperSet.java b/java/com/google/gerrit/server/submit/MergeSuperSet.java
index bcebc7f..b36eb7d 100644
--- a/java/com/google/gerrit/server/submit/MergeSuperSet.java
+++ b/java/com/google/gerrit/server/submit/MergeSuperSet.java
@@ -19,16 +19,19 @@
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.plugincontext.PluginContext;
+import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
@@ -53,6 +56,7 @@
* included.
*/
public class MergeSuperSet {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final Provider<InternalChangeQuery> queryProvider;
private final Provider<MergeOpRepoManager> repoManagerProvider;
@@ -60,6 +64,7 @@
private final PermissionBackend permissionBackend;
private final Config cfg;
private final ProjectCache projectCache;
+ private final ChangeNotes.Factory notesFactory;
private MergeOpRepoManager orm;
private boolean closeOrm;
@@ -71,13 +76,15 @@
Provider<MergeOpRepoManager> repoManagerProvider,
DynamicItem<MergeSuperSetComputation> mergeSuperSetComputation,
PermissionBackend permissionBackend,
- ProjectCache projectCache) {
+ ProjectCache projectCache,
+ ChangeNotes.Factory notesFactory) {
this.cfg = cfg;
this.queryProvider = queryProvider;
this.repoManagerProvider = repoManagerProvider;
this.mergeSuperSetComputation = mergeSuperSetComputation;
this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
+ this.notesFactory = notesFactory;
}
public static boolean wholeTopicEnabled(Config config) {
@@ -215,8 +222,23 @@
return false;
}
+ ChangeNotes notes;
try {
- permissionBackend.user(user).change(cd).check(ChangePermission.READ);
+ notes = cd.notes();
+ } catch (NoSuchChangeException e) {
+ // The change was found in the index but is missing in NoteDb.
+ // This can happen in systems with multiple primary nodes when the replication of the index
+ // documents is faster than the replication of the Git data.
+ // Instead of failing, create the change notes from the index data so that the read permission
+ // check can be performed successfully.
+ logger.atWarning().log(
+ "Got change %d of project %s from index, but couldn't find it in NoteDb",
+ cd.getId().get(), cd.project().get());
+ notes = notesFactory.createFromIndexedChange(cd.change());
+ }
+
+ try {
+ permissionBackend.user(user).change(notes).check(ChangePermission.READ);
return true;
} catch (AuthException e) {
return false;
diff --git a/java/com/google/gerrit/server/update/RetryHelper.java b/java/com/google/gerrit/server/update/RetryHelper.java
index bb69453..e13cab1 100644
--- a/java/com/google/gerrit/server/update/RetryHelper.java
+++ b/java/com/google/gerrit/server/update/RetryHelper.java
@@ -481,9 +481,9 @@
if (!traceContext.isTracing()) {
String traceId = "retry-on-failure-" + new RequestId();
traceContext.addTag(RequestId.Type.TRACE_ID, traceId).forceLogging();
- opts.onAutoTrace().ifPresent(c -> c.accept(traceId));
logger.atFine().withCause(t).log(
"AutoRetry: %s failed, retry with tracing enabled", actionName);
+ opts.onAutoTrace().ifPresent(c -> c.accept(traceId));
metrics.autoRetryCount.increment(actionType, actionName, cause);
return true;
}
diff --git a/java/com/google/gerrit/server/update/RetryableAction.java b/java/com/google/gerrit/server/update/RetryableAction.java
index 75ebeb37..167b209 100644
--- a/java/com/google/gerrit/server/update/RetryableAction.java
+++ b/java/com/google/gerrit/server/update/RetryableAction.java
@@ -51,6 +51,7 @@
public enum ActionType {
ACCOUNT_UPDATE,
CHANGE_UPDATE,
+ GIT_UPDATE,
GROUP_UPDATE,
INDEX_QUERY,
PLUGIN_UPDATE,
diff --git a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index cc8d233..bf93d58 100644
--- a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -45,6 +45,7 @@
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
+import com.google.gerrit.extensions.api.changes.FileContentInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.PublishChangeEditInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -64,7 +65,6 @@
import com.google.gerrit.server.project.testing.TestLabels;
import com.google.gerrit.server.restapi.change.ChangeEdits.EditMessage;
import com.google.gerrit.server.restapi.change.ChangeEdits.Post;
-import com.google.gerrit.server.restapi.change.ChangeEdits.Put;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.inject.Inject;
@@ -501,7 +501,7 @@
@Test
public void createAndChangeEditInOneRequestRest() throws Exception {
- Put.Input in = new Put.Input();
+ FileContentInput in = new FileContentInput();
in.content = RawInputUtil.create(CONTENT_NEW);
adminRestSession.putRaw(urlEditFile(changeId, FILE_NAME), in.content).assertNoContent();
ensureSameBytes(getFileContentOfEdit(changeId, FILE_NAME), CONTENT_NEW);
@@ -513,7 +513,7 @@
@Test
public void changeEditRest() throws Exception {
createEmptyEditFor(changeId);
- Put.Input in = new Put.Input();
+ FileContentInput in = new FileContentInput();
in.content = RawInputUtil.create(CONTENT_NEW);
adminRestSession.putRaw(urlEditFile(changeId, FILE_NAME), in.content).assertNoContent();
ensureSameBytes(getFileContentOfEdit(changeId, FILE_NAME), CONTENT_NEW);
@@ -534,7 +534,7 @@
@Test
public void getFileContentRest() throws Exception {
- Put.Input in = new Put.Input();
+ FileContentInput in = new FileContentInput();
in.content = RawInputUtil.create(CONTENT_NEW);
adminRestSession.putRaw(urlEditFile(changeId, FILE_NAME), in.content).assertNoContent();
gApi.changes().id(changeId).edit().modifyFile(FILE_NAME, RawInputUtil.create(CONTENT_NEW2));
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index ed62918..fa84b95 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -1821,7 +1821,8 @@
if (!this._selectedRevision) {
return;
}
- const patchNum = parseInt(patchNumStr, 10);
+ // If patchNumStr is"edit", then patchNum is undefined hence an OR
+ const patchNum = parseInt(patchNumStr, 10) || patchNumStr;
if (patchNum === this._selectedRevision._number) {
return;
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index d9b8464..b80b51e 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -1617,6 +1617,30 @@
});
});
+ test('_selectedRevision is assigned when patchNum is edit', () => {
+ const revision1 = {_number: 1, commit: {parents: []}};
+ const revision2 = {_number: 2, commit: {parents: []}};
+ const revision3 = {_number: 'edit', commit: {parents: []}};
+ sandbox.stub(element.$.restAPI, 'getChangeDetail').returns(
+ Promise.resolve({
+ revisions: {
+ aaa: revision1,
+ bbb: revision2,
+ ccc: revision3,
+ },
+ labels: {},
+ actions: {},
+ current_revision: 'ccc',
+ change_id: 'loremipsumdolorsitamet',
+ }));
+ sandbox.stub(element, '_getEdit').returns(Promise.resolve());
+ sandbox.stub(element, '_getPreferences').returns(Promise.resolve({}));
+ element._patchRange = {patchNum: 'edit'};
+ return element._getChangeDetail().then(() => {
+ assert.strictEqual(element._selectedRevision, revision3);
+ });
+ });
+
test('_sendShowChangeEvent', () => {
element._change = {labels: {}};
element._patchRange = {patchNum: 4};
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index e836ccc..c177283 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -269,16 +269,6 @@
</section>
<section class="actions">
<div class="left">
- <template is="dom-if" if="[[canBeStarted]]">
- <gr-button
- link
- secondary
- disabled="[[_isState(knownLatestState, 'not-latest')]]"
- class="action save"
- has-tooltip
- title="[[_saveTooltip]]"
- on-click="_saveTapHandler">Save</gr-button>
- </template>
<span
id="checkingStatusLabel"
hidden$="[[!_isState(knownLatestState, 'checking')]]">
@@ -297,6 +287,18 @@
id="cancelButton"
class="action cancel"
on-click="_cancelTapHandler">Cancel</gr-button>
+ <template is="dom-if" if="[[canBeStarted]]">
+ <!-- Use 'Send' here as the change may only about reviewers / ccs
+ and when this button is visible, the next button will always
+ be 'Start review' -->
+ <gr-button
+ link
+ disabled="[[_isState(knownLatestState, 'not-latest')]]"
+ class="action save"
+ has-tooltip
+ title="[[_saveTooltip]]"
+ on-click="_saveClickHandler">Send</gr-button>
+ </template>
<gr-button
id="sendButton"
link
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 9551a46..3ce4f1a 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -43,7 +43,7 @@
};
const ButtonTooltips = {
- SAVE: 'Save reply but do not send notification',
+ SAVE: 'Send but do not send notification or change review state',
START_REVIEW: 'Mark as ready for review and send reply',
SEND: 'Send reply',
};
@@ -686,7 +686,7 @@
this._rebuildReviewerArrays(this.change.reviewers, this._owner);
}
- _saveTapHandler(e) {
+ _saveClickHandler(e) {
e.preventDefault();
if (!this.$.ccs.submitEntryText()) {
// Do not proceed with the save if there is an invalid email entry in
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
index 87caf64..fd75c7c 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
@@ -116,13 +116,6 @@
:host([link][primary]) {
--text-color: var(--primary-button-background-color);
}
- :host([secondary]) {
- --background-color: var(--secondary-button-text-color);
- --text-color: var(--secondary-button-background-color);
- }
- :host([link][secondary]) {
- --text-color: var(--secondary-button-text-color);
- }
/* Keep below color definition for primary so that this takes precedence
when disabled. */
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
index 0567777..d0a07b6 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
@@ -110,25 +110,21 @@
<gr-button
id="replyBtn"
link
- secondary
class="action reply"
on-click="_handleCommentReply">Reply</gr-button>
<gr-button
id="quoteBtn"
link
- secondary
class="action quote"
on-click="_handleCommentQuote">Quote</gr-button>
<gr-button
id="ackBtn"
link
- secondary
class="action ack"
on-click="_handleCommentAck">Ack</gr-button>
<gr-button
id="doneBtn"
link
- secondary
class="action done"
on-click="_handleCommentDone">Done</gr-button>
</div>
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
index ea68e5f..a96cf44 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
@@ -119,7 +119,6 @@
--gr-button: {
height: 20px;
padding: 0 var(--spacing-s);
- color: var(--default-button-text-color);
}
}
.editMessage {
@@ -268,7 +267,6 @@
<gr-button
id="deleteBtn"
link
- secondary
class$="action delete [[_computeDeleteButtonClass(_isAdmin, draft)]]"
on-click="_handleCommentDelete">
<iron-icon id="icon" icon="gr-icons:delete"></iron-icon>
@@ -325,22 +323,18 @@
<div class="rightActions">
<gr-button
link
- secondary
class="action cancel hideOnPublished"
on-click="_handleCancel">Cancel</gr-button>
<gr-button
link
- secondary
class="action discard hideOnPublished"
on-click="_handleDiscard">Discard</gr-button>
<gr-button
link
- secondary
class="action edit hideOnPublished"
on-click="_handleEdit">Edit</gr-button>
<gr-button
link
- secondary
disabled$="[[_computeSaveDisabled(_messageText, comment, resolved)]]"
class="action save hideOnPublished"
on-click="_handleSave">Save</gr-button>
@@ -351,7 +345,6 @@
<template is="dom-if" if="[[!_hasHumanReply]]">
<gr-button
link
- secondary
class="action fix"
on-click="_handleFix"
disabled="[[robotButtonDisabled]]">
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
index 25f406e..5d2a1ca 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
@@ -60,6 +60,7 @@
/**
* Because either the source text or the linkification config has changed,
* the content should be re-parsed.
+ *
* @param {string|null|undefined} content The raw, un-linkified source
* string to parse.
* @param {Object|null|undefined} config The server config specifying
@@ -90,6 +91,7 @@
* element should be created and attached to the resulting DOM.
* - To attach an arbitrary fragment: when called with only the `fragment`
* argument, the fragment should be attached to the resulting DOM as is.
+ *
* @param {string|null} text
* @param {string|null} href
* @param {DocumentFragment|undefined} fragment
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
index 61e28cc..1aa707f 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
@@ -19,6 +19,7 @@
/**
* Pattern describing URLs with supported protocols.
+ *
* @type {RegExp}
*/
const URL_PROTOCOL_PATTERN = /^(https?:\/\/|mailto:)/;
@@ -27,6 +28,7 @@
* Construct a parser for linkifying text. Will linkify plain URLs that appear
* in the text as well as custom links if any are specified in the linkConfig
* parameter.
+ *
* @param {Object|null|undefined} linkConfig Comment links as specified by the
* commentlinks field on a project config.
* @param {Function} callback The callback to be fired when an intermediate
@@ -45,6 +47,7 @@
/**
* Emit a callback to create a link element.
+ *
* @param {string} text The text of the link.
* @param {string} href The URL to use as the href of the link.
*/
@@ -56,6 +59,7 @@
/**
* Given the source text and a list of CommentLinkItem objects that were
* generated by the commentlinks config, emit parsing callbacks.
+ *
* @param {string} text The chuml of source text over which the outputArray
* items range.
* @param {!Array<Gerrit.CommentLinkItem>} outputArray The list of items to add
@@ -93,6 +97,7 @@
/**
* Sort the given array of CommentLinkItems such that the positions are in
* reverse order.
+ *
* @param {!Array<Gerrit.CommentLinkItem>} outputArray
*/
GrLinkTextParser.prototype.sortArrayReverse = function(outputArray) {
@@ -108,6 +113,7 @@
* - With the `html` paremeter provided, and the `text` and `href` parameters
* passed as `null`. In this case, the string of HTML will be parsed and the
* first resulting node will be used as the resulting content.
+ *
* @param {string|null} text The text to use if creating a link.
* @param {string|null} href The href to use as the URL if creating a link.
* @param {string|null} html The html to parse and use as the result.
@@ -150,6 +156,7 @@
/**
* Create a CommentLinkItem for a link and append it to the given output
* array.
+ *
* @param {string|null} text The text for the link.
* @param {string|null} href The href to use as the URL of the link.
* @param {number} position The position inside the source text where the link
@@ -172,6 +179,7 @@
/**
* Create a CommentLinkItem specified by an HTMl string and append it to the
* given output array.
+ *
* @param {string|null} html The html to parse and use as the result.
* @param {number} position The position inside the source text where the item
* starts.
@@ -192,6 +200,7 @@
/**
* Does the given range overlap with anything already in the item list.
+ *
* @param {number} position
* @param {number} length
* @param {!Array<Gerrit.CommentLinkItem>} outputArray
@@ -214,6 +223,7 @@
/**
* Parse the given source text and emit callbacks for the items that are
* parsed.
+ *
* @param {string} text
*/
GrLinkTextParser.prototype.parse = function(text) {
@@ -231,6 +241,7 @@
* ba-linkify has found a plain URL and wants it linkified.
* - With only a `text` parameter provided: this represents the non-link
* content that lies between the links the library has found.
+ *
* @param {string} text
* @param {string|null|undefined} href
*/
@@ -257,6 +268,7 @@
/**
* Walk over the given source text to find matches for comemntlink patterns
* and emit parse result callbacks.
+ *
* @param {string} text The raw source text.
* @param {Object|null|undefined} patterns A comment links specification
* object.
diff --git a/polygerrit-ui/app/lint_test.sh b/polygerrit-ui/app/lint_test.sh
index df37e54..4e7d8c9 100755
--- a/polygerrit-ui/app/lint_test.sh
+++ b/polygerrit-ui/app/lint_test.sh
@@ -37,4 +37,4 @@
# eslint installation.
npm link eslint eslint-config-google eslint-plugin-html eslint-plugin-jsdoc
-${eslint_bin} -c ${UI_PATH}/.eslintrc.json --ignore-pattern 'node_modules/' --ignore-pattern 'bower_components/' --ignore-pattern 'gr-linked-text' --ignore-pattern 'scripts/vendor' --ext .html,.js ${UI_PATH}
+${eslint_bin} -c ${UI_PATH}/.eslintrc.json --ignore-pattern 'node_modules/' --ignore-pattern 'bower_components/' --ignore-pattern 'scripts/vendor' --ext .html,.js ${UI_PATH}
diff --git a/polygerrit-ui/app/styles/themes/app-theme.html b/polygerrit-ui/app/styles/themes/app-theme.html
index 87230e6..53827b5 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.html
+++ b/polygerrit-ui/app/styles/themes/app-theme.html
@@ -36,7 +36,6 @@
--primary-button-text-color: white;
/* Used on text color for change list that doesn't need user's attention. */
--reviewed-text-color: black;
- --secondary-button-text-color: #212121;
--tooltip-text-color: white;
--vote-text-color-recommended: #388e3c;
--vote-text-color-disliked: #d32f2f;
@@ -52,7 +51,6 @@
--dialog-background-color: var(--background-color-primary);
--dropdown-background-color: var(--background-color-primary);
--expanded-background-color: var(--background-color-tertiary);
- --secondary-button-background-color: var(--background-color-primary);
--select-background-color: var(--background-color-secondary);
--shell-command-background-color: var(--background-color-secondary);
--shell-command-decoration-background-color: var(--background-color-tertiary);
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.html b/polygerrit-ui/app/styles/themes/dark-theme.html
index 78bc48b..cfc1f62 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.html
+++ b/polygerrit-ui/app/styles/themes/dark-theme.html
@@ -36,7 +36,6 @@
--primary-button-text-color: var(--primary-text-color);
/* Used on text color for change list doesn't need user's attention. */
--reviewed-text-color: #dadce0;
- --secondary-button-text-color: var(--deemphasized-text-color);
--tooltip-text-color: white;
--vote-text-color-recommended: #388e3c;
--vote-text-color-disliked: #d32f2f;
@@ -51,12 +50,11 @@
/* unique background colors */
--assignee-highlight-color: #3a361c;
--comment-background-color: #0b162b;
- --robot-comment-background-color: #e8f0fe;
+ --robot-comment-background-color: rgba(232, 234, 237, 0.08);
--edit-mode-background-color: #5c0a36;
--emphasis-color: #383f4a;
--hover-background-color: rgba(161, 194, 250, 0.2);
--primary-button-background-color: var(--link-color);
- --secondary-button-background-color: var(--primary-text-color);
--selection-background-color: rgba(161, 194, 250, 0.1);
--tooltip-background-color: #111;
--unresolved-comment-background-color: #385a9a;