Merge "Fix automatic H2 cache invalidation on serialVersionUID change"
diff --git a/Documentation/cmd-index-project.txt b/Documentation/cmd-index-changes-in-project.txt
similarity index 60%
rename from Documentation/cmd-index-project.txt
rename to Documentation/cmd-index-changes-in-project.txt
index 2196a26..ec1626f 100644
--- a/Documentation/cmd-index-project.txt
+++ b/Documentation/cmd-index-changes-in-project.txt
@@ -1,12 +1,12 @@
-= gerrit index project
+= gerrit index changes in project
== NAME
-gerrit index project - Index all the changes in one or more projects.
+gerrit index changes in project - Index all the changes in one or more projects.
== SYNOPSIS
[verse]
--
-_ssh_ -p <port> <host> _gerrit index project_ <PROJECT> [<PROJECT> ...]
+_ssh_ -p <port> <host> _gerrit index changes-in-project_ <PROJECT> [<PROJECT> ...]
--
== DESCRIPTION
@@ -26,7 +26,7 @@
Index all changes in projects MyProject and NiceProject.
----
- $ ssh -p 29418 user@review.example.com gerrit index project MyProject NiceProject
+ $ ssh -p 29418 user@review.example.com gerrit index changes-in-project MyProject NiceProject
----
GERRIT
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index f535281..2362401 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -136,7 +136,7 @@
link:cmd-index-changes.html[gerrit index changes]::
Index one or more changes.
-link:cmd-index-project.html[gerrit index project]::
+link:cmd-index-changes-in-project.html[gerrit index changes-in-project]::
Index all the changes in one or more projects.
link:cmd-logging-ls-level.html[gerrit logging ls-level]::
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 4c1766f..71441d83 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2779,16 +2779,16 @@
it to 0 disables the dedicated thread pool and indexing will be done in the same
thread as the operation.
+
-If not set or set to a negative value, defaults to 1 plus half of the number of
-logical CPUs as returned by the JVM.
+If not set or set to a zero, defaults to the number of logical CPUs as returned
+by the JVM. If set to a negative value, defaults to a direct executor.
[[index.batchThreads]]index.batchThreads::
+
Number of threads to use for indexing in background operations, such as
online schema upgrades.
+
-If not set or set to a negative value, defaults to the number of logical
-CPUs as returned by the JVM.
+If not set or set to a zero, defaults to the number of logical CPUs as returned
+by the JVM. If set to a negative value, defaults to a direct executor.
[[index.onlineUpgrade]]index.onlineUpgrade::
+
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index bc5a3c6..54f8022 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1354,6 +1354,31 @@
[[index]]
+=== Index project
+
+Adds or updates the current project (and children, if specified) in the secondary index.
+The indexing task is executed asynchronously in background, so this command
+returns immediately.
+
+As an input, a link:#index-project-input[IndexProjectInput] entity can be provided.
+
+.Request
+----
+ POST /projects/MyProject/index HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "index_children": "true"
+ }
+----
+
+.Response
+----
+ HTTP/1.1 202 Accepted
+ Content-Disposition: attachment
+----
+
+[[index.changes]]
=== Index all changes in a project
Adds or updates all the changes belonging to a project in the secondary index.
@@ -1362,7 +1387,7 @@
.Request
----
- POST /projects/MyProject/index HTTP/1.0
+ POST /projects/MyProject/index.changes HTTP/1.0
----
.Response
@@ -3128,6 +3153,17 @@
omitted.
|============================
+[[index-project-input]]
+=== IndexProjectInput
+The `IndexProjectInput` contains parameters for indexing a project.
+
+[options="header",cols="1,^2,4"]
+|================================
+|Field Name ||Description
+|`index_children` ||
+If children should be indexed recursively.
+|================================
+
[[inherited-boolean-info]]
=== InheritedBooleanInfo
A boolean value that can also be inherited.
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
index e518d26..1b946cd 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
@@ -169,7 +169,7 @@
@Override
public void setValue(AccessSection value) {
- Collections.sort(value.getPermissions());
+ sortPermissions(value);
this.value = value;
this.readOnly = !editing || !(projectAccess.isOwnerOf(value) || projectAccess.canUpload());
@@ -204,6 +204,12 @@
}
}
+ private void sortPermissions(AccessSection accessSection) {
+ List<Permission> permissionList = new ArrayList<>(accessSection.getPermissions());
+ Collections.sort(permissionList);
+ accessSection.setPermissions(permissionList);
+ }
+
void setEditing(boolean editing) {
this.editing = editing;
}
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 6828393..abb28ce 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -397,6 +397,8 @@
baseConfig.setString("sshd", null, "listenAddress", "off");
}
+ baseConfig.setInt("index", null, "batchThreads", -1);
+
baseConfig.setInt("receive", null, "changeUpdateThreads", 4);
Module module = createModule();
if (classDesc.equals(methodDesc) && !classDesc.sandboxed() && !methodDesc.sandboxed()) {
@@ -1075,7 +1077,7 @@
ProjectConfig config = ProjectConfig.read(md);
AccessSection s = config.getAccessSection(ref, true);
Permission p = s.getPermission(permission, true);
- p.getRules().clear();
+ p.clearRules();
config.commit(md);
projectCache.evict(config.getProject());
}
diff --git a/java/com/google/gerrit/common/data/AccessSection.java b/java/com/google/gerrit/common/data/AccessSection.java
index 82dc620..f658066 100644
--- a/java/com/google/gerrit/common/data/AccessSection.java
+++ b/java/com/google/gerrit/common/data/AccessSection.java
@@ -34,11 +34,12 @@
super(refPattern);
}
+ // TODO(ekempin): Make this method return an ImmutableList once the GWT UI is gone.
public List<Permission> getPermissions() {
if (permissions == null) {
- permissions = new ArrayList<>();
+ return new ArrayList<>();
}
- return permissions;
+ return new ArrayList<>(permissions);
}
public void setPermissions(List<Permission> list) {
@@ -49,7 +50,7 @@
}
}
- permissions = list;
+ permissions = new ArrayList<>(list);
}
@Nullable
@@ -59,13 +60,19 @@
@Nullable
public Permission getPermission(String name, boolean create) {
- for (Permission p : getPermissions()) {
- if (p.getName().equalsIgnoreCase(name)) {
- return p;
+ if (permissions != null) {
+ for (Permission p : permissions) {
+ if (p.getName().equalsIgnoreCase(name)) {
+ return p;
+ }
}
}
if (create) {
+ if (permissions == null) {
+ permissions = new ArrayList<>();
+ }
+
Permission p = new Permission(name);
permissions.add(p);
return p;
@@ -75,7 +82,10 @@
}
public void addPermission(Permission permission) {
- List<Permission> permissions = getPermissions();
+ if (permissions == null) {
+ permissions = new ArrayList<>();
+ }
+
for (Permission p : permissions) {
if (p.getName().equalsIgnoreCase(permission.getName())) {
throw new IllegalArgumentException();
@@ -133,4 +143,15 @@
return new HashSet<>(getPermissions())
.equals(new HashSet<>(((AccessSection) obj).getPermissions()));
}
+
+ @Override
+ public int hashCode() {
+ int hashCode = super.hashCode();
+ if (permissions != null) {
+ for (Permission permission : permissions) {
+ hashCode += permission.hashCode();
+ }
+ }
+ return hashCode;
+ }
}
diff --git a/java/com/google/gerrit/common/data/Permission.java b/java/com/google/gerrit/common/data/Permission.java
index dff30d7..af23e39 100644
--- a/java/com/google/gerrit/common/data/Permission.java
+++ b/java/com/google/gerrit/common/data/Permission.java
@@ -155,13 +155,16 @@
exclusiveGroup = newExclusiveGroup;
}
+ // TODO(ekempin): Make this method return an ImmutableList once the GWT UI is gone.
public List<PermissionRule> getRules() {
- initRules();
- return rules;
+ if (rules == null) {
+ return new ArrayList<>();
+ }
+ return new ArrayList<>(rules);
}
public void setRules(List<PermissionRule> list) {
- rules = list;
+ rules = new ArrayList<>(list);
}
public void add(PermissionRule rule) {
@@ -181,6 +184,12 @@
}
}
+ public void clearRules() {
+ if (rules != null) {
+ rules.clear();
+ }
+ }
+
public PermissionRule getRule(GroupReference group) {
return getRule(group, false);
}
diff --git a/java/com/google/gerrit/extensions/api/projects/IndexProjectInput.java b/java/com/google/gerrit/extensions/api/projects/IndexProjectInput.java
new file mode 100644
index 0000000..a41f227
--- /dev/null
+++ b/java/com/google/gerrit/extensions/api/projects/IndexProjectInput.java
@@ -0,0 +1,19 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.projects;
+
+public class IndexProjectInput {
+ public Boolean indexChildren;
+}
diff --git a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index c9f47c2..63d40f0 100644
--- a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -191,6 +191,13 @@
void parent(String parent) throws RestApiException;
/**
+ * Reindex the project and children in case {@code indexChildren} is specified.
+ *
+ * @param indexChildren decides if children should be indexed recursively
+ */
+ void index(boolean indexChildren) throws RestApiException;
+
+ /**
* A default implementation which allows source compatibility when adding new methods to the
* interface.
*/
@@ -344,5 +351,10 @@
public void parent(String parent) throws RestApiException {
throw new NotImplementedException();
}
+
+ @Override
+ public void index(boolean indexChildren) throws RestApiException {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/java/com/google/gerrit/httpd/raw/ResourceServlet.java b/java/com/google/gerrit/httpd/raw/ResourceServlet.java
index 64b5bbb..3244232 100644
--- a/java/com/google/gerrit/httpd/raw/ResourceServlet.java
+++ b/java/com/google/gerrit/httpd/raw/ResourceServlet.java
@@ -34,6 +34,7 @@
import com.google.common.hash.Hashing;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.httpd.HtmlDomUtil;
+import com.google.gerrit.server.UsedAt;
import com.google.gerrit.util.http.CacheHeaders;
import com.google.gwtjsonrpc.server.RPCServletUtils;
import java.io.IOException;
@@ -324,6 +325,7 @@
}
}
+ @UsedAt(UsedAt.Project.GOOGLE)
public static class Weigher implements com.google.common.cache.Weigher<Path, Resource> {
@Override
public int weigh(Path p, Resource r) {
diff --git a/java/com/google/gerrit/server/UsedAt.java b/java/com/google/gerrit/server/UsedAt.java
new file mode 100644
index 0000000..b564157
--- /dev/null
+++ b/java/com/google/gerrit/server/UsedAt.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.common.annotations.GwtCompatible;
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * A marker for a method that is public solely because it is called from inside a project or an
+ * organisation using Gerrit.
+ */
+@BindingAnnotation
+@Target({METHOD, TYPE})
+@Retention(RUNTIME)
+@GwtCompatible
+public @interface UsedAt {
+ /** Enumeration of projects that call a method that would otherwise be private. */
+ enum Project {
+ GOOGLE,
+ PLUGIN_DELETE_PROJECT,
+ PLUGIN_SERVICEUSER,
+ PLUGINS_ALL, // Use this project if a method/type is generally made available to all plugins.
+ }
+
+ /** Reference to the project that uses the method annotated with this annotation. */
+ Project value();
+}
diff --git a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index fb42ed0..358a3a8 100644
--- a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -57,7 +57,6 @@
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.ChangeMessageResource;
import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.change.PureRevert;
import com.google.gerrit.server.change.WorkInProgressOp;
import com.google.gerrit.server.restapi.change.Abandon;
import com.google.gerrit.server.restapi.change.ChangeIncludedIn;
@@ -70,6 +69,7 @@
import com.google.gerrit.server.restapi.change.GetAssignee;
import com.google.gerrit.server.restapi.change.GetHashtags;
import com.google.gerrit.server.restapi.change.GetPastAssignees;
+import com.google.gerrit.server.restapi.change.GetPureRevert;
import com.google.gerrit.server.restapi.change.GetTopic;
import com.google.gerrit.server.restapi.change.Ignore;
import com.google.gerrit.server.restapi.change.Index;
@@ -153,7 +153,7 @@
private final SetWorkInProgress setWip;
private final SetReadyForReview setReady;
private final PutMessage putMessage;
- private final PureRevert pureRevert;
+ private final Provider<GetPureRevert> getPureRevertProvider;
private final StarredChangesUtil stars;
@Inject
@@ -200,7 +200,7 @@
SetWorkInProgress setWip,
SetReadyForReview setReady,
PutMessage putMessage,
- PureRevert pureRevert,
+ Provider<GetPureRevert> getPureRevertProvider,
StarredChangesUtil stars,
@Assisted ChangeResource change) {
this.changeApi = changeApi;
@@ -245,7 +245,7 @@
this.setWip = setWip;
this.setReady = setReady;
this.putMessage = putMessage;
- this.pureRevert = pureRevert;
+ this.getPureRevertProvider = getPureRevertProvider;
this.stars = stars;
this.change = change;
}
@@ -714,7 +714,9 @@
@Override
public PureRevertInfo pureRevert(@Nullable String claimedOriginal) throws RestApiException {
try {
- return pureRevert.get(change.getNotes(), claimedOriginal);
+ GetPureRevert getPureRevert = getPureRevertProvider.get();
+ getPureRevert.setClaimedOriginal(claimedOriginal);
+ return getPureRevert.apply(change);
} catch (Exception e) {
throw asRestApiException("Cannot compute pure revert", e);
}
diff --git a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index 1065bff..a49f8c4 100644
--- a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -34,6 +34,7 @@
import com.google.gerrit.extensions.api.projects.DeleteTagsInput;
import com.google.gerrit.extensions.api.projects.DescriptionInput;
import com.google.gerrit.extensions.api.projects.HeadInput;
+import com.google.gerrit.extensions.api.projects.IndexProjectInput;
import com.google.gerrit.extensions.api.projects.ParentInput;
import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.extensions.api.projects.ProjectInput;
@@ -64,6 +65,7 @@
import com.google.gerrit.server.restapi.project.GetDescription;
import com.google.gerrit.server.restapi.project.GetHead;
import com.google.gerrit.server.restapi.project.GetParent;
+import com.google.gerrit.server.restapi.project.Index;
import com.google.gerrit.server.restapi.project.ListBranches;
import com.google.gerrit.server.restapi.project.ListChildProjects;
import com.google.gerrit.server.restapi.project.ListDashboards;
@@ -118,6 +120,7 @@
private final SetHead setHead;
private final GetParent getParent;
private final SetParent setParent;
+ private final Index index;
@AssistedInject
ProjectApiImpl(
@@ -150,6 +153,7 @@
SetHead setHead,
GetParent getParent,
SetParent setParent,
+ Index index,
@Assisted ProjectResource project) {
this(
permissionBackend,
@@ -182,6 +186,7 @@
setHead,
getParent,
setParent,
+ index,
null);
}
@@ -216,6 +221,7 @@
SetHead setHead,
GetParent getParent,
SetParent setParent,
+ Index index,
@Assisted String name) {
this(
permissionBackend,
@@ -248,6 +254,7 @@
setHead,
getParent,
setParent,
+ index,
name);
}
@@ -282,6 +289,7 @@
SetHead setHead,
GetParent getParent,
SetParent setParent,
+ Index index,
String name) {
this.permissionBackend = permissionBackend;
this.createProject = createProject;
@@ -314,6 +322,7 @@
this.getParent = getParent;
this.setParent = setParent;
this.name = name;
+ this.index = index;
}
@Override
@@ -595,6 +604,17 @@
}
}
+ @Override
+ public void index(boolean indexChildren) throws RestApiException {
+ try {
+ IndexProjectInput input = new IndexProjectInput();
+ input.indexChildren = indexChildren;
+ index.apply(checkExists(), input);
+ } catch (Exception e) {
+ throw asRestApiException("Cannot index project", e);
+ }
+ }
+
private ProjectResource checkExists() throws ResourceNotFoundException {
if (project == null) {
throw new ResourceNotFoundException(name);
diff --git a/java/com/google/gerrit/server/change/PureRevert.java b/java/com/google/gerrit/server/change/PureRevert.java
index 2c450a7..ddc9661 100644
--- a/java/com/google/gerrit/server/change/PureRevert.java
+++ b/java/com/google/gerrit/server/change/PureRevert.java
@@ -28,6 +28,7 @@
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
@@ -42,6 +43,7 @@
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
+@Singleton
public class PureRevert {
private final MergeUtil.Factory mergeUtilFactory;
private final GitRepositoryManager repoManager;
diff --git a/java/com/google/gerrit/server/index/IndexModule.java b/java/com/google/gerrit/server/index/IndexModule.java
index d957558..3c9538c 100644
--- a/java/com/google/gerrit/server/index/IndexModule.java
+++ b/java/com/google/gerrit/server/index/IndexModule.java
@@ -104,7 +104,7 @@
public IndexModule(
ListeningExecutorService interactiveExecutor, ListeningExecutorService batchExecutor) {
- this.threads = -1;
+ this.threads = 0;
this.interactiveExecutor = interactiveExecutor;
this.batchExecutor = batchExecutor;
this.closeExecutorsOnShutdown = false;
@@ -211,11 +211,12 @@
return interactiveExecutor;
}
int threads = this.threads;
- if (threads <= 0) {
- threads = config.getInt("index", null, "threads", 0);
- }
- if (threads <= 0) {
- threads = Runtime.getRuntime().availableProcessors() / 2 + 1;
+ if (threads < 0) {
+ return MoreExecutors.newDirectExecutorService();
+ } else if (threads == 0) {
+ threads =
+ config.getInt(
+ "index", null, "threads", Runtime.getRuntime().availableProcessors() / 2 + 1);
}
return MoreExecutors.listeningDecorator(
workQueue.createQueue(threads, "Index-Interactive", true));
@@ -230,7 +231,9 @@
return batchExecutor;
}
int threads = config.getInt("index", null, "batchThreads", 0);
- if (threads <= 0) {
+ if (threads < 0) {
+ return MoreExecutors.newDirectExecutorService();
+ } else if (threads == 0) {
threads = Runtime.getRuntime().availableProcessors();
}
return MoreExecutors.listeningDecorator(workQueue.createQueue(threads, "Index-Batch", true));
diff --git a/java/com/google/gerrit/server/index/change/StalenessChecker.java b/java/com/google/gerrit/server/index/change/StalenessChecker.java
index e567d77..642385c 100644
--- a/java/com/google/gerrit/server/index/change/StalenessChecker.java
+++ b/java/com/google/gerrit/server/index/change/StalenessChecker.java
@@ -34,6 +34,7 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.UsedAt;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.RefState;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -103,6 +104,7 @@
parsePatterns(cd));
}
+ @UsedAt(UsedAt.Project.GOOGLE)
public static boolean isStale(
GitRepositoryManager repoManager,
Change.Id id,
diff --git a/java/com/google/gerrit/server/mail/send/EmailArguments.java b/java/com/google/gerrit/server/mail/send/EmailArguments.java
index 04f4d6c..f49951f 100644
--- a/java/com/google/gerrit/server/mail/send/EmailArguments.java
+++ b/java/com/google/gerrit/server/mail/send/EmailArguments.java
@@ -22,6 +22,7 @@
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.IdentifiedUser.GenericFactory;
+import com.google.gerrit.server.UsedAt;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.config.AllProjectsName;
@@ -49,6 +50,7 @@
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
+@UsedAt(UsedAt.Project.PLUGINS_ALL)
public class EmailArguments {
final GitRepositoryManager server;
final ProjectCache projectCache;
diff --git a/java/com/google/gerrit/server/patch/AutoMerger.java b/java/com/google/gerrit/server/patch/AutoMerger.java
index f1b6639..15fedd7 100644
--- a/java/com/google/gerrit/server/patch/AutoMerger.java
+++ b/java/com/google/gerrit/server/patch/AutoMerger.java
@@ -21,6 +21,7 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.UsedAt;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.InMemoryInserter;
import com.google.inject.Inject;
@@ -54,6 +55,7 @@
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);
}
diff --git a/java/com/google/gerrit/server/project/AccountsSection.java b/java/com/google/gerrit/server/project/AccountsSection.java
index 087a314a..30bd244 100644
--- a/java/com/google/gerrit/server/project/AccountsSection.java
+++ b/java/com/google/gerrit/server/project/AccountsSection.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.project;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.data.PermissionRule;
import java.util.ArrayList;
import java.util.List;
@@ -21,14 +22,14 @@
public class AccountsSection {
protected List<PermissionRule> sameGroupVisibility;
- public List<PermissionRule> getSameGroupVisibility() {
+ public ImmutableList<PermissionRule> getSameGroupVisibility() {
if (sameGroupVisibility == null) {
- sameGroupVisibility = new ArrayList<>();
+ sameGroupVisibility = ImmutableList.of();
}
- return sameGroupVisibility;
+ return ImmutableList.copyOf(sameGroupVisibility);
}
public void setSameGroupVisibility(List<PermissionRule> sameGroupVisibility) {
- this.sameGroupVisibility = sameGroupVisibility;
+ this.sameGroupVisibility = new ArrayList<>(sameGroupVisibility);
}
}
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 1796c40..d42b652 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -737,7 +737,7 @@
}
}
- private List<PermissionRule> loadPermissionRules(
+ private ImmutableList<PermissionRule> loadPermissionRules(
Config rc,
String section,
String subsection,
@@ -746,7 +746,7 @@
boolean useRange) {
Permission perm = new Permission(varName);
loadPermissionRules(rc, section, subsection, varName, groupsByName, perm, useRange);
- return perm.getRules();
+ return ImmutableList.copyOf(perm.getRules());
}
private void loadPermissionRules(
diff --git a/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java b/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
index e42e5d1..4bbd8fc 100644
--- a/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
+++ b/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
@@ -25,6 +25,7 @@
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.UsedAt;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.AccountsUpdate;
@@ -119,6 +120,7 @@
return Strings.isNullOrEmpty(newPassword) ? Response.<String>none() : Response.ok(newPassword);
}
+ @UsedAt(UsedAt.Project.PLUGIN_SERVICEUSER)
public static String generate() {
byte[] rand = new byte[LEN];
rng.nextBytes(rand);
diff --git a/java/com/google/gerrit/server/restapi/change/GetPureRevert.java b/java/com/google/gerrit/server/restapi/change/GetPureRevert.java
index 42675f6..75019af 100644
--- a/java/com/google/gerrit/server/restapi/change/GetPureRevert.java
+++ b/java/com/google/gerrit/server/restapi/change/GetPureRevert.java
@@ -30,13 +30,15 @@
public class GetPureRevert implements RestReadView<ChangeResource> {
private final PureRevert pureRevert;
+ @Nullable private String claimedOriginal;
@Option(
name = "--claimed-original",
aliases = {"-o"},
usage = "SHA1 (40 digit hex) of the original commit")
- @Nullable
- private String claimedOriginal;
+ public void setClaimedOriginal(String claimedOriginal) {
+ this.claimedOriginal = claimedOriginal;
+ }
@Inject
GetPureRevert(PureRevert pureRevert) {
diff --git a/java/com/google/gerrit/server/restapi/change/PutAssignee.java b/java/com/google/gerrit/server/restapi/change/PutAssignee.java
index b6fc010..b7be2a8 100644
--- a/java/com/google/gerrit/server/restapi/change/PutAssignee.java
+++ b/java/com/google/gerrit/server/restapi/change/PutAssignee.java
@@ -89,7 +89,10 @@
throw new UnprocessableEntityException(input.assignee + " is not active");
}
try {
- rsrc.permissions().database(db).user(assignee).check(ChangePermission.READ);
+ rsrc.permissions()
+ .database(db)
+ .absentUser(assignee.getAccountId())
+ .check(ChangePermission.READ);
} catch (AuthException e) {
throw new AuthException("read not permitted for " + input.assignee);
}
diff --git a/java/com/google/gerrit/server/restapi/project/Index.java b/java/com/google/gerrit/server/restapi/project/Index.java
index 24f32f6..5547864 100644
--- a/java/com/google/gerrit/server/restapi/project/Index.java
+++ b/java/com/google/gerrit/server/restapi/project/Index.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 The Android Open Source Project
+// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,57 +16,73 @@
import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
-import com.google.common.io.ByteStreams;
+import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
-import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.api.projects.IndexProjectInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.index.project.ProjectIndexer;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.git.MultiProgressMonitor;
-import com.google.gerrit.server.git.MultiProgressMonitor.Task;
import com.google.gerrit.server.index.IndexExecutor;
-import com.google.gerrit.server.index.change.AllChangesIndexer;
-import com.google.gerrit.server.index.change.ChangeIndexer;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectResource;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import java.io.IOException;
import java.util.concurrent.Future;
-import org.eclipse.jgit.util.io.NullOutputStream;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@RequiresCapability(GlobalCapability.MAINTAIN_SERVER)
@Singleton
-public class Index implements RestModifyView<ProjectResource, ProjectInput> {
+public class Index implements RestModifyView<ProjectResource, IndexProjectInput> {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private final Provider<AllChangesIndexer> allChangesIndexerProvider;
- private final ChangeIndexer indexer;
+ private final ProjectIndexer indexer;
private final ListeningExecutorService executor;
+ private final Provider<ListChildProjects> listChildProjectsProvider;
@Inject
Index(
- Provider<AllChangesIndexer> allChangesIndexerProvider,
- ChangeIndexer indexer,
- @IndexExecutor(BATCH) ListeningExecutorService executor) {
- this.allChangesIndexerProvider = allChangesIndexerProvider;
+ ProjectIndexer indexer,
+ @IndexExecutor(BATCH) ListeningExecutorService executor,
+ Provider<ListChildProjects> listChildProjectsProvider) {
this.indexer = indexer;
this.executor = executor;
+ this.listChildProjectsProvider = listChildProjectsProvider;
}
@Override
- public Response.Accepted apply(ProjectResource resource, ProjectInput input) {
- Project.NameKey project = resource.getNameKey();
- Task mpt =
- new MultiProgressMonitor(ByteStreams.nullOutputStream(), "Reindexing project")
- .beginSubTask("", MultiProgressMonitor.UNKNOWN);
- AllChangesIndexer allChangesIndexer = allChangesIndexerProvider.get();
- allChangesIndexer.setVerboseOut(NullOutputStream.INSTANCE);
- // The REST call is just a trigger for async reindexing, so it is safe to ignore the future's
- // return value.
+ public Response.Accepted apply(ProjectResource rsrc, IndexProjectInput input)
+ throws IOException, AuthException, OrmException, PermissionBackendException,
+ ResourceConflictException {
+ String response = "Project " + rsrc.getName() + " submitted for reindexing";
+
+ reindexAsync(rsrc.getNameKey());
+ if (Boolean.TRUE.equals(input.indexChildren)) {
+ ListChildProjects listChildProjects = listChildProjectsProvider.get();
+ listChildProjects.setRecursive(true);
+ listChildProjects.apply(rsrc).forEach(p -> reindexAsync(new Project.NameKey(p.name)));
+
+ response += " (indexing children recursively)";
+ }
+ return Response.accepted(response);
+ }
+
+ private void reindexAsync(Project.NameKey project) {
@SuppressWarnings("unused")
- Future<Void> ignored =
- executor.submit(allChangesIndexer.reindexProject(indexer, project, mpt, mpt));
- return Response.accepted("Project " + project + " submitted for reindexing");
+ Future<?> possiblyIgnoredError =
+ executor.submit(
+ () -> {
+ try {
+ indexer.index(project);
+ } catch (IOException e) {
+ logger.atWarning().withCause(e).log("reindexing project %s failed", project);
+ }
+ });
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/IndexChanges.java b/java/com/google/gerrit/server/restapi/project/IndexChanges.java
new file mode 100644
index 0000000..b84f86c
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/project/IndexChanges.java
@@ -0,0 +1,72 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.restapi.project;
+
+import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
+
+import com.google.common.io.ByteStreams;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.git.MultiProgressMonitor;
+import com.google.gerrit.server.git.MultiProgressMonitor.Task;
+import com.google.gerrit.server.index.IndexExecutor;
+import com.google.gerrit.server.index.change.AllChangesIndexer;
+import com.google.gerrit.server.index.change.ChangeIndexer;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.util.concurrent.Future;
+import org.eclipse.jgit.util.io.NullOutputStream;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@Singleton
+public class IndexChanges implements RestModifyView<ProjectResource, ProjectInput> {
+
+ private final Provider<AllChangesIndexer> allChangesIndexerProvider;
+ private final ChangeIndexer indexer;
+ private final ListeningExecutorService executor;
+
+ @Inject
+ IndexChanges(
+ Provider<AllChangesIndexer> allChangesIndexerProvider,
+ ChangeIndexer indexer,
+ @IndexExecutor(BATCH) ListeningExecutorService executor) {
+ this.allChangesIndexerProvider = allChangesIndexerProvider;
+ this.indexer = indexer;
+ this.executor = executor;
+ }
+
+ @Override
+ public Response.Accepted apply(ProjectResource resource, ProjectInput input) {
+ Project.NameKey project = resource.getNameKey();
+ Task mpt =
+ new MultiProgressMonitor(ByteStreams.nullOutputStream(), "Reindexing project")
+ .beginSubTask("", MultiProgressMonitor.UNKNOWN);
+ AllChangesIndexer allChangesIndexer = allChangesIndexerProvider.get();
+ allChangesIndexer.setVerboseOut(NullOutputStream.INSTANCE);
+ // The REST call is just a trigger for async reindexing, so it is safe to ignore the future's
+ // return value.
+ @SuppressWarnings("unused")
+ Future<?> possiblyIgnoredError =
+ executor.submit(allChangesIndexer.reindexProject(indexer, project, mpt, mpt));
+ return Response.accepted("Project " + project + " submitted for reindexing");
+ }
+}
diff --git a/java/com/google/gerrit/server/restapi/project/Module.java b/java/com/google/gerrit/server/restapi/project/Module.java
index d2e59cd..f2047e0 100644
--- a/java/com/google/gerrit/server/restapi/project/Module.java
+++ b/java/com/google/gerrit/server/restapi/project/Module.java
@@ -68,6 +68,7 @@
get(PROJECT_KIND, "statistics.git").to(GetStatistics.class);
post(PROJECT_KIND, "gc").to(GarbageCollect.class);
post(PROJECT_KIND, "index").to(Index.class);
+ post(PROJECT_KIND, "index.changes").to(IndexChanges.class);
child(PROJECT_KIND, "branches").to(BranchesCollection.class);
create(BRANCH_KIND).to(CreateBranch.class);
diff --git a/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java b/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
index 422c749..c5c8cf0 100644
--- a/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
+++ b/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
@@ -115,7 +115,7 @@
}
p.add(r);
}
- accessSection.getPermissions().add(p);
+ accessSection.addPermission(p);
}
sections.add(accessSection);
}
diff --git a/java/com/google/gerrit/server/schema/JdbcUtil.java b/java/com/google/gerrit/server/schema/JdbcUtil.java
index 2624923..dddf23a 100644
--- a/java/com/google/gerrit/server/schema/JdbcUtil.java
+++ b/java/com/google/gerrit/server/schema/JdbcUtil.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.schema;
+import com.google.gerrit.server.UsedAt;
+
public class JdbcUtil {
public static String hostname(String hostname) {
@@ -26,6 +28,7 @@
return hostname;
}
+ @UsedAt(UsedAt.Project.PLUGINS_ALL)
public static String port(String port) {
if (port != null && !port.isEmpty()) {
return ":" + port;
diff --git a/java/com/google/gerrit/server/schema/SchemaVersion.java b/java/com/google/gerrit/server/schema/SchemaVersion.java
index 48cc91e..61e9c92 100644
--- a/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -19,6 +19,7 @@
import com.google.common.collect.Lists;
import com.google.gerrit.reviewdb.client.CurrentSchemaVersion;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.UsedAt;
import com.google.gwtorm.jdbc.JdbcExecutor;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
@@ -49,6 +50,7 @@
this.versionNbr = guessVersion(getClass());
}
+ @UsedAt(UsedAt.Project.PLUGIN_DELETE_PROJECT)
public static int guessVersion(Class<?> c) {
String n = c.getName();
n = n.substring(n.lastIndexOf('_') + 1);
diff --git a/java/com/google/gerrit/sshd/commands/IndexProjectCommand.java b/java/com/google/gerrit/sshd/commands/IndexChangesInProjectCommand.java
similarity index 88%
rename from java/com/google/gerrit/sshd/commands/IndexProjectCommand.java
rename to java/com/google/gerrit/sshd/commands/IndexChangesInProjectCommand.java
index 407bbd0..56b00a5 100644
--- a/java/com/google/gerrit/sshd/commands/IndexProjectCommand.java
+++ b/java/com/google/gerrit/sshd/commands/IndexChangesInProjectCommand.java
@@ -19,7 +19,7 @@
import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.restapi.project.Index;
+import com.google.gerrit.server.restapi.project.IndexChanges;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
@@ -28,10 +28,10 @@
import org.kohsuke.args4j.Argument;
@RequiresAnyCapability({MAINTAIN_SERVER})
-@CommandMetaData(name = "project", description = "Index changes of a project")
-final class IndexProjectCommand extends SshCommand {
+@CommandMetaData(name = "changes-in-project", description = "Index changes of a project")
+final class IndexChangesInProjectCommand extends SshCommand {
- @Inject private Index index;
+ @Inject private IndexChanges index;
@Argument(
index = 0,
diff --git a/java/com/google/gerrit/sshd/commands/IndexCommandsModule.java b/java/com/google/gerrit/sshd/commands/IndexCommandsModule.java
index 599c9dc..332ed69 100644
--- a/java/com/google/gerrit/sshd/commands/IndexCommandsModule.java
+++ b/java/com/google/gerrit/sshd/commands/IndexCommandsModule.java
@@ -40,6 +40,6 @@
command(index, IndexStartCommand.class);
}
command(index, IndexChangesCommand.class);
- command(index, IndexProjectCommand.class);
+ command(index, IndexChangesInProjectCommand.class);
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
index b4a05fc..8479dd1 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -15,10 +15,13 @@
package com.google.gerrit.acceptance.api.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
import static java.util.stream.Collectors.toSet;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.AtomicLongMap;
+import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.NoHttpd;
@@ -39,7 +42,9 @@
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.index.IndexExecutor;
import com.google.inject.Inject;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.PushResult;
@@ -52,6 +57,10 @@
public class ProjectIT extends AbstractDaemonTest {
@Inject private DynamicSet<ProjectIndexedListener> projectIndexedListeners;
+ @Inject
+ @IndexExecutor(BATCH)
+ private ListeningExecutorService executor;
+
private ProjectIndexedCounter projectIndexedCounter;
private RegistrationHandle projectIndexedCounterHandle;
@@ -381,6 +390,26 @@
}
}
+ @Test
+ public void reindexProject() throws Exception {
+ createProject("child", project);
+ projectIndexedCounter.clear();
+
+ gApi.projects().name(allProjects.get()).index(false);
+ projectIndexedCounter.assertReindexOf(allProjects.get());
+ }
+
+ @Test
+ public void reindexProjectWithChildren() throws Exception {
+ Project.NameKey middle = createProject("middle", project);
+ Project.NameKey leave = createProject("leave", middle);
+ projectIndexedCounter.clear();
+
+ gApi.projects().name(project.get()).index(true);
+ projectIndexedCounter.assertReindexExactly(
+ ImmutableMap.of(project.get(), 1L, middle.get(), 1L, leave.get(), 1L));
+ }
+
private ConfigInput createTestConfigInput() {
ConfigInput input = new ConfigInput();
input.description = "some description";
@@ -427,5 +456,10 @@
void assertNoReindex() {
assertThat(countsByProject).isEmpty();
}
+
+ void assertReindexExactly(ImmutableMap<String, Long> expected) {
+ assertThat(countsByProject.asMap()).containsExactlyEntriesIn(expected);
+ clear();
+ }
}
}
diff --git a/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java b/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
index b362a36..80430c4 100644
--- a/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
@@ -336,8 +336,7 @@
c ->
cfg.getAccessSection(AccessSection.GLOBAL_CAPABILITIES, true)
.getPermission(c, true)
- .getRules()
- .clear());
+ .clearRules());
}
private PushResult push(String... refSpecs) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/rest/ChangesRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/ChangesRestApiBindingsIT.java
index a7f1329..59c0903 100644
--- a/javatests/com/google/gerrit/acceptance/rest/ChangesRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/ChangesRestApiBindingsIT.java
@@ -248,14 +248,23 @@
ImmutableList.of(RestCall.get("/changes/%s/messages/%s"));
/**
+ * Change edit REST endpoints that create an edit to be tested, each URL contains placeholders for
+ * the change identifier and the change edit identifier.
+ */
+ private static final ImmutableList<RestCall> CHANGE_EDIT_CREATE_ENDPOINTS =
+ ImmutableList.of(
+ // Create change edit by editing an existing file.
+ RestCall.put("/changes/%s/edit/%s"),
+
+ // Create change edit by deleting an existing file.
+ RestCall.delete("/changes/%s/edit/%s"));
+
+ /**
* Change edit REST endpoints to be tested, each URL contains placeholders for the change
* identifier and the change edit identifier.
*/
private static final ImmutableList<RestCall> CHANGE_EDIT_ENDPOINTS =
ImmutableList.of(
- // Create change edit by deleting an existing file.
- RestCall.delete("/changes/%s/edit/%s"),
-
// Calls on existing change edit.
RestCall.get("/changes/%s/edit/%s"),
RestCall.put("/changes/%s/edit/%s"),
@@ -460,10 +469,21 @@
}
@Test
- public void changeEditEndpoints() throws Exception {
+ public void changeEditCreateEndpoints() throws Exception {
String changeId = createChange("Subject", FILENAME, "content").getChangeId();
- // The change edit is created by the first REST call.
+ // Each of the REST calls creates the change edit newly.
+ execute(
+ CHANGE_EDIT_CREATE_ENDPOINTS,
+ () -> adminRestSession.delete("/changes/" + changeId + "/edit"),
+ changeId,
+ FILENAME);
+ }
+
+ @Test
+ public void changeEditEndpoints() throws Exception {
+ String changeId = createChange("Subject", FILENAME, "content").getChangeId();
+ gApi.changes().id(changeId).edit().create();
execute(CHANGE_EDIT_ENDPOINTS, changeId, FILENAME);
}
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/ReflogIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/ReflogIT.java
index 834dbfa..bcae987 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/ReflogIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/ReflogIT.java
@@ -25,16 +25,10 @@
import java.io.File;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.Repository;
-import org.junit.Before;
import org.junit.Test;
@UseLocalDisk
public class ReflogIT extends AbstractDaemonTest {
- @Before
- public void setUp() throws Exception {
- assume().that(notesMigration.disableChangeReviewDb()).isTrue();
- }
-
@Test
public void guessRestApiInReflog() throws Exception {
assume().that(notesMigration.disableChangeReviewDb()).isTrue();
diff --git a/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java b/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
index 208f380..25bb7a6 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
@@ -102,7 +102,7 @@
enableChangeIndexWrites();
changeIndexedCounter.clear();
- String cmd = Joiner.on(" ").join("gerrit", "index", "project", project.get());
+ String cmd = Joiner.on(" ").join("gerrit", "index", "changes-in-project", project.get());
adminSshSession.exec(cmd);
adminSshSession.assertSuccess();
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
index 5694bd0..cc86c0b 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
@@ -93,7 +93,8 @@
}
}),
"index",
- ImmutableList.of("changes", "project"), // "activate" and "start" are not included
+ ImmutableList.of(
+ "changes", "changes-in-project"), // "activate" and "start" are not included
"logging",
ImmutableList.of("ls", "set"),
"plugin",
diff --git a/javatests/com/google/gerrit/common/data/AccessSectionTest.java b/javatests/com/google/gerrit/common/data/AccessSectionTest.java
new file mode 100644
index 0000000..ecdb3c8
--- /dev/null
+++ b/javatests/com/google/gerrit/common/data/AccessSectionTest.java
@@ -0,0 +1,253 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.common.data;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class AccessSectionTest {
+ @Rule public ExpectedException exception = ExpectedException.none();
+
+ private static final String REF_PATTERN = "refs/heads/master";
+
+ private AccessSection accessSection;
+
+ @Before
+ public void setup() {
+ this.accessSection = new AccessSection(REF_PATTERN);
+ }
+
+ @Test
+ public void getName() {
+ assertThat(accessSection.getName()).isEqualTo(REF_PATTERN);
+ }
+
+ @Test
+ public void getEmptyPermissions() {
+ assertThat(accessSection.getPermissions()).isNotNull();
+ assertThat(accessSection.getPermissions()).isEmpty();
+ }
+
+ @Test
+ public void setAndGetPermissions() {
+ Permission abandonPermission = new Permission(Permission.ABANDON);
+ Permission rebasePermission = new Permission(Permission.REBASE);
+ accessSection.setPermissions(ImmutableList.of(abandonPermission, rebasePermission));
+ assertThat(accessSection.getPermissions())
+ .containsExactly(abandonPermission, rebasePermission)
+ .inOrder();
+
+ Permission submitPermission = new Permission(Permission.SUBMIT);
+ accessSection.setPermissions(ImmutableList.of(submitPermission));
+ assertThat(accessSection.getPermissions()).containsExactly(submitPermission);
+ }
+
+ @Test
+ public void cannotSetDuplicatePermissions() {
+ exception.expect(IllegalArgumentException.class);
+ accessSection.setPermissions(
+ ImmutableList.of(new Permission(Permission.ABANDON), new Permission(Permission.ABANDON)));
+ }
+
+ @Test
+ public void cannotSetPermissionsWithConflictingNames() {
+ Permission abandonPermissionLowerCase =
+ new Permission(Permission.ABANDON.toLowerCase(Locale.US));
+ Permission abandonPermissionUpperCase =
+ new Permission(Permission.ABANDON.toUpperCase(Locale.US));
+
+ exception.expect(IllegalArgumentException.class);
+ accessSection.setPermissions(
+ ImmutableList.of(abandonPermissionLowerCase, abandonPermissionUpperCase));
+ }
+
+ @Test
+ public void getNonExistingPermission() {
+ assertThat(accessSection.getPermission("non-existing")).isNull();
+ assertThat(accessSection.getPermission("non-existing", false)).isNull();
+ }
+
+ @Test
+ public void getPermission() {
+ Permission submitPermission = new Permission(Permission.SUBMIT);
+ accessSection.setPermissions(ImmutableList.of(submitPermission));
+ assertThat(accessSection.getPermission(Permission.SUBMIT)).isEqualTo(submitPermission);
+ }
+
+ @Test
+ public void getPermissionWithOtherCase() {
+ Permission submitPermissionLowerCase = new Permission(Permission.SUBMIT.toLowerCase(Locale.US));
+ accessSection.setPermissions(ImmutableList.of(submitPermissionLowerCase));
+ assertThat(accessSection.getPermission(Permission.SUBMIT.toUpperCase(Locale.US)))
+ .isEqualTo(submitPermissionLowerCase);
+ }
+
+ @Test
+ public void createMissingPermissionOnGet() {
+ assertThat(accessSection.getPermission(Permission.SUBMIT)).isNull();
+
+ assertThat(accessSection.getPermission(Permission.SUBMIT, true))
+ .isEqualTo(new Permission(Permission.SUBMIT));
+ }
+
+ @Test
+ public void addPermission() {
+ Permission abandonPermission = new Permission(Permission.ABANDON);
+ Permission rebasePermission = new Permission(Permission.REBASE);
+
+ accessSection.setPermissions(ImmutableList.of(abandonPermission, rebasePermission));
+ assertThat(accessSection.getPermission(Permission.SUBMIT)).isNull();
+
+ Permission submitPermission = new Permission(Permission.SUBMIT);
+ accessSection.addPermission(submitPermission);
+ assertThat(accessSection.getPermission(Permission.SUBMIT)).isEqualTo(submitPermission);
+ assertThat(accessSection.getPermissions())
+ .containsExactly(abandonPermission, rebasePermission, submitPermission)
+ .inOrder();
+ }
+
+ @Test
+ public void cannotAddPermissionByModifyingListThatWasProvidedToAccessSection() {
+ Permission abandonPermission = new Permission(Permission.ABANDON);
+ Permission rebasePermission = new Permission(Permission.REBASE);
+
+ List<Permission> permissions = new ArrayList<>();
+ permissions.add(abandonPermission);
+ permissions.add(rebasePermission);
+ accessSection.setPermissions(permissions);
+ assertThat(accessSection.getPermission(Permission.SUBMIT)).isNull();
+
+ Permission submitPermission = new Permission(Permission.SUBMIT);
+ permissions.add(submitPermission);
+ assertThat(accessSection.getPermission(Permission.SUBMIT)).isNull();
+ }
+
+ @Test
+ public void cannotAddPermissionByModifyingListThatWasRetrievedFromAccessSection() {
+ Permission submitPermission = new Permission(Permission.SUBMIT);
+ accessSection.getPermissions().add(submitPermission);
+ assertThat(accessSection.getPermission(Permission.SUBMIT)).isNull();
+
+ List<Permission> permissions = new ArrayList<>();
+ permissions.add(new Permission(Permission.ABANDON));
+ permissions.add(new Permission(Permission.REBASE));
+ accessSection.setPermissions(permissions);
+ assertThat(accessSection.getPermission(Permission.SUBMIT)).isNull();
+ accessSection.getPermissions().add(submitPermission);
+ assertThat(accessSection.getPermission(Permission.SUBMIT)).isNull();
+ }
+
+ @Test
+ public void removePermission() {
+ Permission abandonPermission = new Permission(Permission.ABANDON);
+ Permission rebasePermission = new Permission(Permission.REBASE);
+ Permission submitPermission = new Permission(Permission.SUBMIT);
+
+ accessSection.setPermissions(
+ ImmutableList.of(abandonPermission, rebasePermission, submitPermission));
+ assertThat(accessSection.getPermission(Permission.SUBMIT)).isNotNull();
+
+ accessSection.remove(submitPermission);
+ assertThat(accessSection.getPermission(Permission.SUBMIT)).isNull();
+ assertThat(accessSection.getPermissions())
+ .containsExactly(abandonPermission, rebasePermission)
+ .inOrder();
+ }
+
+ @Test
+ public void removePermissionByName() {
+ Permission abandonPermission = new Permission(Permission.ABANDON);
+ Permission rebasePermission = new Permission(Permission.REBASE);
+ Permission submitPermission = new Permission(Permission.SUBMIT);
+
+ accessSection.setPermissions(
+ ImmutableList.of(abandonPermission, rebasePermission, submitPermission));
+ assertThat(accessSection.getPermission(Permission.SUBMIT)).isNotNull();
+
+ accessSection.removePermission(Permission.SUBMIT);
+ assertThat(accessSection.getPermission(Permission.SUBMIT)).isNull();
+ assertThat(accessSection.getPermissions())
+ .containsExactly(abandonPermission, rebasePermission)
+ .inOrder();
+ }
+
+ @Test
+ public void removePermissionByNameOtherCase() {
+ Permission abandonPermission = new Permission(Permission.ABANDON);
+ Permission rebasePermission = new Permission(Permission.REBASE);
+
+ String submitLowerCase = Permission.SUBMIT.toLowerCase(Locale.US);
+ String submitUpperCase = Permission.SUBMIT.toUpperCase(Locale.US);
+ Permission submitPermissionLowerCase = new Permission(submitLowerCase);
+
+ accessSection.setPermissions(
+ ImmutableList.of(abandonPermission, rebasePermission, submitPermissionLowerCase));
+ assertThat(accessSection.getPermission(submitLowerCase)).isNotNull();
+ assertThat(accessSection.getPermission(submitUpperCase)).isNotNull();
+
+ accessSection.removePermission(submitUpperCase);
+ assertThat(accessSection.getPermission(submitLowerCase)).isNull();
+ assertThat(accessSection.getPermission(submitUpperCase)).isNull();
+ assertThat(accessSection.getPermissions())
+ .containsExactly(abandonPermission, rebasePermission)
+ .inOrder();
+ }
+
+ @Test
+ public void mergeAccessSections() {
+ Permission abandonPermission = new Permission(Permission.ABANDON);
+ Permission rebasePermission = new Permission(Permission.REBASE);
+ Permission submitPermission = new Permission(Permission.SUBMIT);
+
+ AccessSection accessSection1 = new AccessSection("refs/heads/foo");
+ accessSection1.setPermissions(ImmutableList.of(abandonPermission, rebasePermission));
+
+ AccessSection accessSection2 = new AccessSection("refs/heads/bar");
+ accessSection2.setPermissions(ImmutableList.of(rebasePermission, submitPermission));
+
+ accessSection1.mergeFrom(accessSection2);
+ assertThat(accessSection1.getPermissions())
+ .containsExactly(abandonPermission, rebasePermission, submitPermission)
+ .inOrder();
+ }
+
+ @Test
+ public void testEquals() {
+ Permission abandonPermission = new Permission(Permission.ABANDON);
+ Permission rebasePermission = new Permission(Permission.REBASE);
+
+ accessSection.setPermissions(ImmutableList.of(abandonPermission, rebasePermission));
+
+ AccessSection accessSectionSamePermissionsOtherRef = new AccessSection("refs/heads/other");
+ accessSectionSamePermissionsOtherRef.setPermissions(
+ ImmutableList.of(abandonPermission, rebasePermission));
+ assertThat(accessSection.equals(accessSectionSamePermissionsOtherRef)).isFalse();
+
+ AccessSection accessSectionOther = new AccessSection(REF_PATTERN);
+ accessSectionOther.setPermissions(ImmutableList.of(abandonPermission));
+ assertThat(accessSection.equals(accessSectionOther)).isFalse();
+
+ accessSectionOther.addPermission(rebasePermission);
+ assertThat(accessSection.equals(accessSectionOther)).isTrue();
+ }
+}
diff --git a/javatests/com/google/gerrit/common/data/EncodePathSeparatorTest.java b/javatests/com/google/gerrit/common/data/EncodePathSeparatorTest.java
index 4c4c769..dcd3c05 100644
--- a/javatests/com/google/gerrit/common/data/EncodePathSeparatorTest.java
+++ b/javatests/com/google/gerrit/common/data/EncodePathSeparatorTest.java
@@ -14,20 +14,20 @@
package com.google.gerrit.common.data;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
import org.junit.Test;
public class EncodePathSeparatorTest {
@Test
public void defaultBehaviour() {
- assertEquals("a/b", new GitwebType().replacePathSeparator("a/b"));
+ assertThat(new GitwebType().replacePathSeparator("a/b")).isEqualTo("a/b");
}
@Test
public void exclamationMark() {
GitwebType gitwebType = new GitwebType();
gitwebType.setPathSeparator('!');
- assertEquals("a!b", gitwebType.replacePathSeparator("a/b"));
+ assertThat(gitwebType.replacePathSeparator("a/b")).isEqualTo("a!b");
}
}
diff --git a/javatests/com/google/gerrit/common/data/ParameterizedStringTest.java b/javatests/com/google/gerrit/common/data/ParameterizedStringTest.java
index 0f067c4..b646d2b 100644
--- a/javatests/com/google/gerrit/common/data/ParameterizedStringTest.java
+++ b/javatests/com/google/gerrit/common/data/ParameterizedStringTest.java
@@ -14,9 +14,7 @@
package com.google.gerrit.common.data;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableMap;
import java.util.HashMap;
@@ -26,155 +24,148 @@
public class ParameterizedStringTest {
@Test
public void emptyString() {
- final ParameterizedString p = new ParameterizedString("");
- assertEquals("", p.getPattern());
- assertEquals("", p.getRawPattern());
- assertTrue(p.getParameterNames().isEmpty());
+ ParameterizedString p = new ParameterizedString("");
+ assertThat(p.getPattern()).isEmpty();
+ assertThat(p.getRawPattern()).isEmpty();
+ assertThat(p.getParameterNames()).isEmpty();
- final Map<String, String> a = new HashMap<>();
- assertNotNull(p.bind(a));
- assertEquals(0, p.bind(a).length);
- assertEquals("", p.replace(a));
+ Map<String, String> a = new HashMap<>();
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).isEmpty();
+ assertThat(p.replace(a)).isEmpty();
}
@Test
public void asis1() {
- final ParameterizedString p = ParameterizedString.asis("${bar}c");
- assertEquals("${bar}c", p.getPattern());
- assertEquals("${bar}c", p.getRawPattern());
- assertTrue(p.getParameterNames().isEmpty());
+ ParameterizedString p = ParameterizedString.asis("${bar}c");
+ assertThat(p.getPattern()).isEqualTo("${bar}c");
+ assertThat(p.getRawPattern()).isEqualTo("${bar}c");
+ assertThat(p.getParameterNames()).isEmpty();
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("bar", "frobinator");
- assertNotNull(p.bind(a));
- assertEquals(0, p.bind(a).length);
- assertEquals("${bar}c", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).isEmpty();
+ assertThat(p.replace(a)).isEqualTo("${bar}c");
}
@Test
public void replace1() {
- final ParameterizedString p = new ParameterizedString("${bar}c");
- assertEquals("${bar}c", p.getPattern());
- assertEquals("{0}c", p.getRawPattern());
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("bar"));
+ ParameterizedString p = new ParameterizedString("${bar}c");
+ assertThat(p.getPattern()).isEqualTo("${bar}c");
+ assertThat(p.getRawPattern()).isEqualTo("{0}c");
+ assertThat(p.getParameterNames()).containsExactly("bar");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("bar", "frobinator");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("frobinator", p.bind(a)[0]);
- assertEquals("frobinatorc", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("frobinator");
+ assertThat(p.replace(a)).isEqualTo("frobinatorc");
}
@Test
public void replace2() {
- final ParameterizedString p = new ParameterizedString("a${bar}c");
- assertEquals("a${bar}c", p.getPattern());
- assertEquals("a{0}c", p.getRawPattern());
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("bar"));
+ ParameterizedString p = new ParameterizedString("a${bar}c");
+ assertThat(p.getPattern()).isEqualTo("a${bar}c");
+ assertThat(p.getRawPattern()).isEqualTo("a{0}c");
+ assertThat(p.getParameterNames()).containsExactly("bar");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("bar", "frobinator");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("frobinator", p.bind(a)[0]);
- assertEquals("afrobinatorc", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("frobinator");
+ assertThat(p.replace(a)).isEqualTo("afrobinatorc");
}
@Test
public void replace3() {
- final ParameterizedString p = new ParameterizedString("a${bar}");
- assertEquals("a${bar}", p.getPattern());
- assertEquals("a{0}", p.getRawPattern());
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("bar"));
+ ParameterizedString p = new ParameterizedString("a${bar}");
+ assertThat(p.getPattern()).isEqualTo("a${bar}");
+ assertThat(p.getRawPattern()).isEqualTo("a{0}");
+ assertThat(p.getParameterNames()).containsExactly("bar");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("bar", "frobinator");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("frobinator", p.bind(a)[0]);
- assertEquals("afrobinator", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("frobinator");
+ assertThat(p.replace(a)).isEqualTo("afrobinator");
}
@Test
public void replace4() {
- final ParameterizedString p = new ParameterizedString("a${bar}c");
- assertEquals("a${bar}c", p.getPattern());
- assertEquals("a{0}c", p.getRawPattern());
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("bar"));
+ ParameterizedString p = new ParameterizedString("a${bar}c");
+ assertThat(p.getPattern()).isEqualTo("a${bar}c");
+ assertThat(p.getRawPattern()).isEqualTo("a{0}c");
+ assertThat(p.getParameterNames()).containsExactly("bar");
- final Map<String, String> a = new HashMap<>();
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("", p.bind(a)[0]);
- assertEquals("ac", p.replace(a));
+ Map<String, String> a = new HashMap<>();
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEmpty();
+ assertThat(p.replace(a)).isEqualTo("ac");
}
@Test
public void replaceToLowerCase() {
- final ParameterizedString p = new ParameterizedString("${a.toLowerCase}");
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("a"));
+ ParameterizedString p = new ParameterizedString("${a.toLowerCase}");
+ assertThat(p.getParameterNames()).containsExactly("a");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("a", "foo");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("foo", p.bind(a)[0]);
- assertEquals("foo", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("foo");
+ assertThat(p.replace(a)).isEqualTo("foo");
a.put("a", "FOO");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("foo", p.bind(a)[0]);
- assertEquals("foo", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("foo");
+ assertThat(p.replace(a)).isEqualTo("foo");
}
@Test
public void replaceToUpperCase() {
- final ParameterizedString p = new ParameterizedString("${a.toUpperCase}");
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("a"));
+ ParameterizedString p = new ParameterizedString("${a.toUpperCase}");
+ assertThat(p.getParameterNames()).containsExactly("a");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("a", "foo");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("FOO", p.bind(a)[0]);
- assertEquals("FOO", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("FOO");
+ assertThat(p.replace(a)).isEqualTo("FOO");
a.put("a", "FOO");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("FOO", p.bind(a)[0]);
- assertEquals("FOO", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("FOO");
+ assertThat(p.replace(a)).isEqualTo("FOO");
}
@Test
public void replaceLocalName() {
- final ParameterizedString p = new ParameterizedString("${a.localPart}");
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("a"));
+ ParameterizedString p = new ParameterizedString("${a.localPart}");
+ assertThat(p.getParameterNames()).containsExactly("a");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("a", "foo@example.com");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("foo", p.bind(a)[0]);
- assertEquals("foo", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("foo");
+ assertThat(p.replace(a)).isEqualTo("foo");
a.put("a", "foo");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("foo", p.bind(a)[0]);
- assertEquals("foo", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("foo");
+ assertThat(p.replace(a)).isEqualTo("foo");
}
@Test
@@ -182,226 +173,216 @@
ParameterizedString p =
new ParameterizedString(
"hi, ${userName.toUpperCase},your eamil address is '${email.toLowerCase.localPart}'.right?");
- assertEquals(2, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("userName"));
- assertTrue(p.getParameterNames().contains("email"));
+ assertThat(p.getParameterNames()).containsExactly("userName", "email");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("userName", "firstName lastName");
a.put("email", "FIRSTNAME.LASTNAME@EXAMPLE.COM");
- assertNotNull(p.bind(a));
- assertEquals(2, p.bind(a).length);
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(2);
- assertEquals("FIRSTNAME LASTNAME", p.bind(a)[0]);
- assertEquals("firstname.lastname", p.bind(a)[1]);
- assertEquals(
- "hi, FIRSTNAME LASTNAME,your eamil address is 'firstname.lastname'.right?", p.replace(a));
+ assertThat(p.bind(a)[0]).isEqualTo("FIRSTNAME LASTNAME");
+ assertThat(p.bind(a)[1]).isEqualTo("firstname.lastname");
+ assertThat(p.replace(a))
+ .isEqualTo("hi, FIRSTNAME LASTNAME,your eamil address is 'firstname.lastname'.right?");
}
@Test
public void replaceToUpperCaseToLowerCase() {
- final ParameterizedString p = new ParameterizedString("${a.toUpperCase.toLowerCase}");
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("a"));
+ ParameterizedString p = new ParameterizedString("${a.toUpperCase.toLowerCase}");
+ assertThat(p.getParameterNames()).containsExactly("a");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("a", "FOO@EXAMPLE.COM");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("foo@example.com", p.bind(a)[0]);
- assertEquals("foo@example.com", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("foo@example.com");
+ assertThat(p.replace(a)).isEqualTo("foo@example.com");
a.put("a", "foo@example.com");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("foo@example.com", p.bind(a)[0]);
- assertEquals("foo@example.com", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("foo@example.com");
+ assertThat(p.replace(a)).isEqualTo("foo@example.com");
}
@Test
public void replaceToUpperCaseLocalName() {
- final ParameterizedString p = new ParameterizedString("${a.toUpperCase.localPart}");
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("a"));
+ ParameterizedString p = new ParameterizedString("${a.toUpperCase.localPart}");
+ assertThat(p.getParameterNames()).containsExactly("a");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("a", "foo@example.com");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("FOO", p.bind(a)[0]);
- assertEquals("FOO", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("FOO");
+ assertThat(p.replace(a)).isEqualTo("FOO");
a.put("a", "FOO@EXAMPLE.COM");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("FOO", p.bind(a)[0]);
- assertEquals("FOO", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("FOO");
+ assertThat(p.replace(a)).isEqualTo("FOO");
}
@Test
public void replaceToUpperCaseAnUndefinedMethod() {
- final ParameterizedString p = new ParameterizedString("${a.toUpperCase.anUndefinedMethod}");
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("a"));
+ ParameterizedString p = new ParameterizedString("${a.toUpperCase.anUndefinedMethod}");
+ assertThat(p.getParameterNames()).containsExactly("a");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("a", "foo@example.com");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("FOO@EXAMPLE.COM", p.bind(a)[0]);
- assertEquals("FOO@EXAMPLE.COM", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("FOO@EXAMPLE.COM");
+ assertThat(p.replace(a)).isEqualTo("FOO@EXAMPLE.COM");
a.put("a", "FOO@EXAMPLE.COM");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("FOO@EXAMPLE.COM", p.bind(a)[0]);
- assertEquals("FOO@EXAMPLE.COM", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("FOO@EXAMPLE.COM");
+ assertThat(p.replace(a)).isEqualTo("FOO@EXAMPLE.COM");
}
@Test
public void replaceLocalNameToUpperCase() {
- final ParameterizedString p = new ParameterizedString("${a.localPart.toUpperCase}");
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("a"));
+ ParameterizedString p = new ParameterizedString("${a.localPart.toUpperCase}");
+ assertThat(p.getParameterNames()).containsExactly("a");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("a", "foo@example.com");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("FOO", p.bind(a)[0]);
- assertEquals("FOO", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("FOO");
+ assertThat(p.replace(a)).isEqualTo("FOO");
a.put("a", "FOO@EXAMPLE.COM");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("FOO", p.bind(a)[0]);
- assertEquals("FOO", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("FOO");
+ assertThat(p.replace(a)).isEqualTo("FOO");
}
@Test
public void replaceLocalNameToLowerCase() {
- final ParameterizedString p = new ParameterizedString("${a.localPart.toLowerCase}");
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("a"));
+ ParameterizedString p = new ParameterizedString("${a.localPart.toLowerCase}");
+ assertThat(p.getParameterNames()).containsExactly("a");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("a", "FOO@EXAMPLE.COM");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("foo", p.bind(a)[0]);
- assertEquals("foo", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("foo");
+ assertThat(p.replace(a)).isEqualTo("foo");
a.put("a", "foo@example.com");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("foo", p.bind(a)[0]);
- assertEquals("foo", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("foo");
+ assertThat(p.replace(a)).isEqualTo("foo");
}
@Test
public void replaceLocalNameAnUndefinedMethod() {
- final ParameterizedString p = new ParameterizedString("${a.localPart.anUndefinedMethod}");
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("a"));
+ ParameterizedString p = new ParameterizedString("${a.localPart.anUndefinedMethod}");
+ assertThat(p.getParameterNames()).containsExactly("a");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("a", "FOO@EXAMPLE.COM");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("FOO", p.bind(a)[0]);
- assertEquals("FOO", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("FOO");
+ assertThat(p.replace(a)).isEqualTo("FOO");
a.put("a", "foo@example.com");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("foo", p.bind(a)[0]);
- assertEquals("foo", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("foo");
+ assertThat(p.replace(a)).isEqualTo("foo");
}
@Test
public void replaceToLowerCaseToUpperCase() {
- final ParameterizedString p = new ParameterizedString("${a.toLowerCase.toUpperCase}");
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("a"));
+ ParameterizedString p = new ParameterizedString("${a.toLowerCase.toUpperCase}");
+ assertThat(p.getParameterNames()).hasSize(1);
+ assertThat(p.getParameterNames()).containsExactly("a");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("a", "FOO@EXAMPLE.COM");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("FOO@EXAMPLE.COM", p.bind(a)[0]);
- assertEquals("FOO@EXAMPLE.COM", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("FOO@EXAMPLE.COM");
+ assertThat(p.replace(a)).isEqualTo("FOO@EXAMPLE.COM");
a.put("a", "foo@example.com");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("FOO@EXAMPLE.COM", p.bind(a)[0]);
- assertEquals("FOO@EXAMPLE.COM", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("FOO@EXAMPLE.COM");
+ assertThat(p.replace(a)).isEqualTo("FOO@EXAMPLE.COM");
}
@Test
public void replaceToLowerCaseLocalName() {
- final ParameterizedString p = new ParameterizedString("${a.toLowerCase.localPart}");
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("a"));
+ ParameterizedString p = new ParameterizedString("${a.toLowerCase.localPart}");
+ assertThat(p.getParameterNames()).containsExactly("a");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("a", "FOO@EXAMPLE.COM");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("foo", p.bind(a)[0]);
- assertEquals("foo", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("foo");
+ assertThat(p.replace(a)).isEqualTo("foo");
a.put("a", "foo@example.com");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("foo", p.bind(a)[0]);
- assertEquals("foo", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("foo");
+ assertThat(p.replace(a)).isEqualTo("foo");
}
@Test
public void replaceToLowerCaseAnUndefinedMethod() {
- final ParameterizedString p = new ParameterizedString("${a.toLowerCase.anUndefinedMethod}");
- assertEquals(1, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("a"));
+ ParameterizedString p = new ParameterizedString("${a.toLowerCase.anUndefinedMethod}");
+ assertThat(p.getParameterNames()).containsExactly("a");
- final Map<String, String> a = new HashMap<>();
+ Map<String, String> a = new HashMap<>();
a.put("a", "foo@example.com");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("foo@example.com", p.bind(a)[0]);
- assertEquals("foo@example.com", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("foo@example.com");
+ assertThat(p.replace(a)).isEqualTo("foo@example.com");
a.put("a", "FOO@EXAMPLE.COM");
- assertNotNull(p.bind(a));
- assertEquals(1, p.bind(a).length);
- assertEquals("foo@example.com", p.bind(a)[0]);
- assertEquals("foo@example.com", p.replace(a));
+ assertThat(p.bind(a)).isNotNull();
+ assertThat(p.bind(a)).hasLength(1);
+ assertThat(p.bind(a)[0]).isEqualTo("foo@example.com");
+ assertThat(p.replace(a)).isEqualTo("foo@example.com");
}
@Test
public void replaceSubmitTooltipWithVariables() {
ParameterizedString p = new ParameterizedString("Submit patch set ${patchSet} into ${branch}");
- assertEquals(2, p.getParameterNames().size());
- assertTrue(p.getParameterNames().contains("patchSet"));
+ assertThat(p.getParameterNames()).hasSize(2);
+ assertThat(p.getParameterNames()).containsExactly("patchSet", "branch");
Map<String, String> params =
ImmutableMap.of(
"patchSet", "42",
"branch", "foo");
- assertNotNull(p.bind(params));
- assertEquals(2, p.bind(params).length);
- assertEquals("42", p.bind(params)[0]);
- assertEquals("foo", p.bind(params)[1]);
- assertEquals("Submit patch set 42 into foo", p.replace(params));
+ assertThat(p.bind(params)).isNotNull();
+ assertThat(p.bind(params)).hasLength(2);
+ assertThat(p.bind(params)[0]).isEqualTo("42");
+ assertThat(p.bind(params)[1]).isEqualTo("foo");
+ assertThat(p.replace(params)).isEqualTo("Submit patch set 42 into foo");
}
@Test
@@ -411,7 +392,7 @@
ImmutableMap.of(
"patchSet", "42",
"branch", "foo");
- assertEquals(0, p.bind(params).length);
- assertEquals("Submit patch set 40 into master", p.replace(params));
+ assertThat(p.bind(params)).isEmpty();
+ assertThat(p.replace(params)).isEqualTo("Submit patch set 40 into master");
}
}
diff --git a/javatests/com/google/gerrit/common/data/PermissionTest.java b/javatests/com/google/gerrit/common/data/PermissionTest.java
new file mode 100644
index 0000000..f76323f
--- /dev/null
+++ b/javatests/com/google/gerrit/common/data/PermissionTest.java
@@ -0,0 +1,341 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.common.data;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PermissionTest {
+ private static final String PERMISSION_NAME = "foo";
+
+ private Permission permission;
+
+ @Before
+ public void setup() {
+ this.permission = new Permission(PERMISSION_NAME);
+ }
+
+ @Test
+ public void isPermission() {
+ assertThat(Permission.isPermission(Permission.ABANDON)).isTrue();
+ assertThat(Permission.isPermission("no-permission")).isFalse();
+
+ assertThat(Permission.isPermission(Permission.LABEL + "Code-Review")).isTrue();
+ assertThat(Permission.isPermission(Permission.LABEL_AS + "Code-Review")).isTrue();
+ assertThat(Permission.isPermission("Code-Review")).isFalse();
+ }
+
+ @Test
+ public void hasRange() {
+ assertThat(Permission.hasRange(Permission.ABANDON)).isFalse();
+ assertThat(Permission.hasRange("no-permission")).isFalse();
+
+ assertThat(Permission.hasRange(Permission.LABEL + "Code-Review")).isTrue();
+ assertThat(Permission.hasRange(Permission.LABEL_AS + "Code-Review")).isTrue();
+ assertThat(Permission.hasRange("Code-Review")).isFalse();
+ }
+
+ @Test
+ public void isLabel() {
+ assertThat(Permission.isLabel(Permission.ABANDON)).isFalse();
+ assertThat(Permission.isLabel("no-permission")).isFalse();
+
+ assertThat(Permission.isLabel(Permission.LABEL + "Code-Review")).isTrue();
+ assertThat(Permission.isLabel(Permission.LABEL_AS + "Code-Review")).isFalse();
+ assertThat(Permission.isLabel("Code-Review")).isFalse();
+ }
+
+ @Test
+ public void isLabelAs() {
+ assertThat(Permission.isLabelAs(Permission.ABANDON)).isFalse();
+ assertThat(Permission.isLabelAs("no-permission")).isFalse();
+
+ assertThat(Permission.isLabelAs(Permission.LABEL + "Code-Review")).isFalse();
+ assertThat(Permission.isLabelAs(Permission.LABEL_AS + "Code-Review")).isTrue();
+ assertThat(Permission.isLabelAs("Code-Review")).isFalse();
+ }
+
+ @Test
+ public void forLabel() {
+ assertThat(Permission.forLabel("Code-Review")).isEqualTo(Permission.LABEL + "Code-Review");
+ }
+
+ @Test
+ public void forLabelAs() {
+ assertThat(Permission.forLabelAs("Code-Review")).isEqualTo(Permission.LABEL_AS + "Code-Review");
+ }
+
+ @Test
+ public void extractLabel() {
+ assertThat(Permission.extractLabel(Permission.LABEL + "Code-Review")).isEqualTo("Code-Review");
+ assertThat(Permission.extractLabel(Permission.LABEL_AS + "Code-Review"))
+ .isEqualTo("Code-Review");
+ assertThat(Permission.extractLabel("Code-Review")).isNull();
+ assertThat(Permission.extractLabel(Permission.ABANDON)).isNull();
+ }
+
+ @Test
+ public void canBeOnAllProjects() {
+ assertThat(Permission.canBeOnAllProjects(AccessSection.ALL, Permission.ABANDON)).isTrue();
+ assertThat(Permission.canBeOnAllProjects(AccessSection.ALL, Permission.OWNER)).isFalse();
+ assertThat(Permission.canBeOnAllProjects(AccessSection.ALL, Permission.LABEL + "Code-Review"))
+ .isTrue();
+ assertThat(
+ Permission.canBeOnAllProjects(AccessSection.ALL, Permission.LABEL_AS + "Code-Review"))
+ .isTrue();
+
+ assertThat(Permission.canBeOnAllProjects("refs/heads/*", Permission.ABANDON)).isTrue();
+ assertThat(Permission.canBeOnAllProjects("refs/heads/*", Permission.OWNER)).isTrue();
+ assertThat(Permission.canBeOnAllProjects("refs/heads/*", Permission.LABEL + "Code-Review"))
+ .isTrue();
+ assertThat(Permission.canBeOnAllProjects("refs/heads/*", Permission.LABEL_AS + "Code-Review"))
+ .isTrue();
+ }
+
+ @Test
+ public void getName() {
+ assertThat(permission.getName()).isEqualTo(PERMISSION_NAME);
+ }
+
+ @Test
+ public void getLabel() {
+ assertThat(new Permission(Permission.LABEL + "Code-Review").getLabel())
+ .isEqualTo("Code-Review");
+ assertThat(new Permission(Permission.LABEL_AS + "Code-Review").getLabel())
+ .isEqualTo("Code-Review");
+ assertThat(new Permission("Code-Review").getLabel()).isNull();
+ assertThat(new Permission(Permission.ABANDON).getLabel()).isNull();
+ }
+
+ @Test
+ public void exclusiveGroup() {
+ assertThat(permission.getExclusiveGroup()).isFalse();
+
+ permission.setExclusiveGroup(true);
+ assertThat(permission.getExclusiveGroup()).isTrue();
+
+ permission.setExclusiveGroup(false);
+ assertThat(permission.getExclusiveGroup()).isFalse();
+ }
+
+ @Test
+ public void noExclusiveGroupOnOwnerPermission() {
+ Permission permission = new Permission(Permission.OWNER);
+ assertThat(permission.getExclusiveGroup()).isFalse();
+
+ permission.setExclusiveGroup(true);
+ assertThat(permission.getExclusiveGroup()).isFalse();
+ }
+
+ @Test
+ public void getEmptyRules() {
+ assertThat(permission.getRules()).isNotNull();
+ assertThat(permission.getRules()).isEmpty();
+ }
+
+ @Test
+ public void setAndGetRules() {
+ PermissionRule permissionRule1 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ PermissionRule permissionRule2 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
+ permission.setRules(ImmutableList.of(permissionRule1, permissionRule2));
+ assertThat(permission.getRules()).containsExactly(permissionRule1, permissionRule2).inOrder();
+
+ PermissionRule permissionRule3 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-3"), "group3"));
+ permission.setRules(ImmutableList.of(permissionRule3));
+ assertThat(permission.getRules()).containsExactly(permissionRule3);
+ }
+
+ @Test
+ public void cannotAddPermissionByModifyingListThatWasProvidedToAccessSection() {
+ PermissionRule permissionRule1 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ PermissionRule permissionRule2 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
+ GroupReference groupReference3 = new GroupReference(new AccountGroup.UUID("uuid-3"), "group3");
+
+ List<PermissionRule> rules = new ArrayList<>();
+ rules.add(permissionRule1);
+ rules.add(permissionRule2);
+ permission.setRules(rules);
+ assertThat(permission.getRule(groupReference3)).isNull();
+
+ PermissionRule permissionRule3 = new PermissionRule(groupReference3);
+ rules.add(permissionRule3);
+ assertThat(permission.getRule(groupReference3)).isNull();
+ }
+
+ @Test
+ public void cannotAddPermissionByModifyingListThatWasRetrievedFromAccessSection() {
+ GroupReference groupReference1 = new GroupReference(new AccountGroup.UUID("uuid-1"), "group1");
+ PermissionRule permissionRule1 = new PermissionRule(groupReference1);
+ permission.getRules().add(permissionRule1);
+ assertThat(permission.getRule(groupReference1)).isNull();
+
+ List<PermissionRule> rules = new ArrayList<>();
+ rules.add(new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2")));
+ rules.add(new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-3"), "group3")));
+ permission.setRules(rules);
+ assertThat(permission.getRule(groupReference1)).isNull();
+ permission.getRules().add(permissionRule1);
+ assertThat(permission.getRule(groupReference1)).isNull();
+ }
+
+ @Test
+ public void getNonExistingRule() {
+ GroupReference groupReference = new GroupReference(new AccountGroup.UUID("uuid-1"), "group1");
+ assertThat(permission.getRule(groupReference)).isNull();
+ assertThat(permission.getRule(groupReference, false)).isNull();
+ }
+
+ @Test
+ public void getRule() {
+ GroupReference groupReference = new GroupReference(new AccountGroup.UUID("uuid-1"), "group1");
+ PermissionRule permissionRule = new PermissionRule(groupReference);
+ permission.setRules(ImmutableList.of(permissionRule));
+ assertThat(permission.getRule(groupReference)).isEqualTo(permissionRule);
+ }
+
+ @Test
+ public void createMissingRuleOnGet() {
+ GroupReference groupReference = new GroupReference(new AccountGroup.UUID("uuid-1"), "group1");
+ assertThat(permission.getRule(groupReference)).isNull();
+
+ assertThat(permission.getRule(groupReference, true))
+ .isEqualTo(new PermissionRule(groupReference));
+ }
+
+ @Test
+ public void addRule() {
+ PermissionRule permissionRule1 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ PermissionRule permissionRule2 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
+ permission.setRules(ImmutableList.of(permissionRule1, permissionRule2));
+ GroupReference groupReference3 = new GroupReference(new AccountGroup.UUID("uuid-3"), "group3");
+ assertThat(permission.getRule(groupReference3)).isNull();
+
+ PermissionRule permissionRule3 = new PermissionRule(groupReference3);
+ permission.add(permissionRule3);
+ assertThat(permission.getRule(groupReference3)).isEqualTo(permissionRule3);
+ assertThat(permission.getRules())
+ .containsExactly(permissionRule1, permissionRule2, permissionRule3)
+ .inOrder();
+ }
+
+ @Test
+ public void removeRule() {
+ PermissionRule permissionRule1 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ PermissionRule permissionRule2 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
+ GroupReference groupReference3 = new GroupReference(new AccountGroup.UUID("uuid-3"), "group3");
+ PermissionRule permissionRule3 = new PermissionRule(groupReference3);
+
+ permission.setRules(ImmutableList.of(permissionRule1, permissionRule2, permissionRule3));
+ assertThat(permission.getRule(groupReference3)).isNotNull();
+
+ permission.remove(permissionRule3);
+ assertThat(permission.getRule(groupReference3)).isNull();
+ assertThat(permission.getRules()).containsExactly(permissionRule1, permissionRule2).inOrder();
+ }
+
+ @Test
+ public void removeRuleByGroupReference() {
+ PermissionRule permissionRule1 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ PermissionRule permissionRule2 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
+ GroupReference groupReference3 = new GroupReference(new AccountGroup.UUID("uuid-3"), "group3");
+ PermissionRule permissionRule3 = new PermissionRule(groupReference3);
+
+ permission.setRules(ImmutableList.of(permissionRule1, permissionRule2, permissionRule3));
+ assertThat(permission.getRule(groupReference3)).isNotNull();
+
+ permission.removeRule(groupReference3);
+ assertThat(permission.getRule(groupReference3)).isNull();
+ assertThat(permission.getRules()).containsExactly(permissionRule1, permissionRule2).inOrder();
+ }
+
+ @Test
+ public void clearRules() {
+ PermissionRule permissionRule1 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ PermissionRule permissionRule2 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
+
+ permission.setRules(ImmutableList.of(permissionRule1, permissionRule2));
+ assertThat(permission.getRules()).isNotEmpty();
+
+ permission.clearRules();
+ assertThat(permission.getRules()).isEmpty();
+ }
+
+ @Test
+ public void mergePermissions() {
+ PermissionRule permissionRule1 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ PermissionRule permissionRule2 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
+ PermissionRule permissionRule3 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-3"), "group3"));
+
+ Permission permission1 = new Permission("foo");
+ permission1.setRules(ImmutableList.of(permissionRule1, permissionRule2));
+
+ Permission permission2 = new Permission("bar");
+ permission2.setRules(ImmutableList.of(permissionRule2, permissionRule3));
+
+ permission1.mergeFrom(permission2);
+ assertThat(permission1.getRules())
+ .containsExactly(permissionRule1, permissionRule2, permissionRule3)
+ .inOrder();
+ }
+
+ @Test
+ public void testEquals() {
+ PermissionRule permissionRule1 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ PermissionRule permissionRule2 =
+ new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
+
+ permission.setRules(ImmutableList.of(permissionRule1, permissionRule2));
+
+ Permission permissionSameRulesOtherName = new Permission("bar");
+ permissionSameRulesOtherName.setRules(ImmutableList.of(permissionRule1, permissionRule2));
+ assertThat(permission.equals(permissionSameRulesOtherName)).isFalse();
+
+ Permission permissionSameRulesSameNameOtherExclusiveGroup = new Permission("foo");
+ permissionSameRulesSameNameOtherExclusiveGroup.setRules(
+ ImmutableList.of(permissionRule1, permissionRule2));
+ permissionSameRulesSameNameOtherExclusiveGroup.setExclusiveGroup(true);
+ assertThat(permission.equals(permissionSameRulesSameNameOtherExclusiveGroup)).isFalse();
+
+ Permission permissionOther = new Permission(PERMISSION_NAME);
+ permissionOther.setRules(ImmutableList.of(permissionRule1));
+ assertThat(permission.equals(permissionOther)).isFalse();
+
+ permissionOther.add(permissionRule2);
+ assertThat(permission.equals(permissionOther)).isTrue();
+ }
+}
diff --git a/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior.html b/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior.html
new file mode 100644
index 0000000..68000bc
--- /dev/null
+++ b/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior.html
@@ -0,0 +1,75 @@
+<!--
+@license
+Copyright (C) 2018 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<script>
+(function(window) {
+ 'use strict';
+
+ window.Gerrit = window.Gerrit || {};
+
+ /** @polymerBehavior Gerrit.SafeTypes */
+ Gerrit.SafeTypes = {};
+
+ const SAFE_URL_PATTERN = /^(https?:\/\/|mailto:|\/|#)/i;
+
+ /**
+ * Wraps a string to be used as a URL. An error is thrown if the string cannot
+ * be considered safe.
+ * @constructor
+ * @param {string} url the unwrapped, potentially unsafe URL.
+ */
+ Gerrit.SafeTypes.SafeUrl = function(url) {
+ if (!SAFE_URL_PATTERN.test(url)) {
+ throw new Error(`URL not marked as safe: ${url}`);
+ }
+ this._url = url;
+ };
+
+ /**
+ * Get the string representation of the safe URL.
+ * @returns {string}
+ */
+ Gerrit.SafeTypes.SafeUrl.prototype.asString = function() {
+ return this._url;
+ };
+
+ Gerrit.SafeTypes.safeTypesBridge = function(value, type) {
+ // If the value is being bound to a URL, ensure the value is wrapped in the
+ // SafeUrl type first. If the URL is not safe, allow the SafeUrl constructor
+ // to surface the error.
+ if (type === 'URL') {
+ let safeValue = null;
+ if (value instanceof Gerrit.SafeTypes.SafeUrl) {
+ safeValue = value;
+ } else if (typeof value === 'string') {
+ safeValue = new Gerrit.SafeTypes.SafeUrl(value);
+ }
+ if (safeValue) {
+ return safeValue.asString();
+ }
+ }
+
+ // If the value is being bound to a string or a constant, then the string
+ // can be used as is.
+ if (type === 'STRING' || type === 'CONSTANT') {
+ return value;
+ }
+
+ // Otherwise fail.
+ throw new Error(`Refused to bind value as ${type}: ${value}`);
+ };
+})(window);
+</script>
diff --git a/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html b/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html
new file mode 100644
index 0000000..bc16b39
--- /dev/null
+++ b/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2018 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<title>safe-types-behavior</title>
+
+<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../test/common-test-setup.html"/>
+<link rel="import" href="safe-types-behavior.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <safe-types-element></safe-types-element>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-tooltip-behavior tests', () => {
+ let element;
+ let sandbox;
+
+ suiteSetup(() => {
+ Polymer({
+ is: 'safe-types-element',
+ behaviors: [Gerrit.SafeTypes],
+ });
+ });
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ element = fixture('basic');
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ test('SafeUrl accepts valid urls', () => {
+ function accepts(url) {
+ const safeUrl = new element.SafeUrl(url);
+ assert.isOk(safeUrl);
+ assert.equal(url, safeUrl.asString());
+ }
+ accepts('http://www.google.com/');
+ accepts('https://www.google.com/');
+ accepts('HtTpS://www.google.com/');
+ accepts('//www.google.com/');
+ accepts('/c/1234/file/path.html@45');
+ accepts('#hash-url');
+ accepts('mailto:name@example.com');
+ });
+
+ test('SafeUrl rejects invalid urls', () => {
+ function rejects(url) {
+ assert.throws(() => { new element.SafeUrl(url); });
+ }
+ rejects('javascript://alert("evil");');
+ rejects('ftp:example.com');
+ rejects('data:text/html,scary business');
+ });
+
+ suite('safeTypesBridge', () => {
+ function acceptsString(value, type) {
+ assert.equal(Gerrit.SafeTypes.safeTypesBridge(value, type),
+ value);
+ }
+
+ function rejects(value, type) {
+ assert.throws(() => { Gerrit.SafeTypes.safeTypesBridge(value, type); });
+ }
+
+ test('accepts valid URL strings', () => {
+ acceptsString('/foo/bar', 'URL');
+ acceptsString('#baz', 'URL');
+ });
+
+ test('rejects invalid URL strings', () => {
+ rejects('javascript://void();', 'URL');
+ });
+
+ test('accepts SafeUrl values', () => {
+ const url = '/abc/123';
+ const safeUrl = new element.SafeUrl(url);
+ assert.equal(Gerrit.SafeTypes.safeTypesBridge(safeUrl, 'URL'), url);
+ });
+
+ test('rejects non-string or non-SafeUrl types', () => {
+ rejects(3.1415926, 'URL');
+ });
+
+ test('accepts any binding to STRING or CONSTANT', () => {
+ acceptsString('foo/bar/baz', 'STRING');
+ acceptsString('lorem ipsum dolor', 'CONSTANT');
+ });
+
+ test('rejects all other types', () => {
+ rejects('foo', 'JAVASCRIPT');
+ rejects('foo', 'HTML');
+ rejects('foo', 'RESOURCE_URL');
+ rejects('foo', 'STYLE');
+ });
+ });
+ });
+</script>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
index e601b93..3e886d5 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
@@ -20,7 +20,8 @@
// Latency reporting constants.
const TIMING = {
TYPE: 'timing-report',
- CATEGORY: 'UI Latency',
+ CATEGORY_UI_LATENCY: 'UI Latency',
+ CATEGORY_RPC: 'RPC Timing',
// Reported events - alphabetize below.
APP_STARTED: 'App Started',
PAGE_LOADED: 'Page Loaded',
@@ -170,7 +171,16 @@
report.apply(this, args);
},
- defaultReporter(type, category, eventName, eventValue) {
+ /**
+ * The default reporter reports events immediately.
+ * @param {string} type
+ * @param {string} category
+ * @param {string} eventName
+ * @param {string|number} eventValue
+ * @param {boolean|undefined} opt_noLog If true, the event will not be
+ * logged to the JS console.
+ */
+ defaultReporter(type, category, eventName, eventValue, opt_noLog) {
const detail = {
type,
category,
@@ -178,6 +188,7 @@
value: eventValue,
};
document.dispatchEvent(new CustomEvent(type, {detail}));
+ if (opt_noLog) { return; }
if (type === ERROR.TYPE) {
console.error(eventValue.error || eventName);
} else {
@@ -186,7 +197,17 @@
}
},
- cachingReporter(type, category, eventName, eventValue) {
+ /**
+ * The caching reporter will queue reports until plugins have loaded, and
+ * log events immediately if they're reported after plugins have loaded.
+ * @param {string} type
+ * @param {string} category
+ * @param {string} eventName
+ * @param {string|number} eventValue
+ * @param {boolean|undefined} opt_noLog If true, the event will not be
+ * logged to the JS console.
+ */
+ cachingReporter(type, category, eventName, eventValue, opt_noLog) {
if (type === ERROR.TYPE) {
console.error(eventValue.error || eventName);
}
@@ -196,9 +217,9 @@
this.reporter(...args);
}
}
- this.reporter(type, category, eventName, eventValue);
+ this.reporter(type, category, eventName, eventValue, opt_noLog);
} else {
- pending.push([type, category, eventName, eventValue]);
+ pending.push([type, category, eventName, eventValue, opt_noLog]);
}
},
@@ -208,8 +229,8 @@
appStarted(hidden) {
const startTime =
new Date().getTime() - this.performanceTiming.navigationStart;
- this.reporter(
- TIMING.TYPE, TIMING.CATEGORY, TIMING.APP_STARTED, startTime);
+ this.reporter(TIMING.TYPE, TIMING.CATEGORY_UI_LATENCY,
+ TIMING.APP_STARTED, startTime);
if (hidden) {
this.reporter(PAGE_VISIBILITY.TYPE, PAGE_VISIBILITY.CATEGORY,
PAGE_VISIBILITY.STARTED_HIDDEN);
@@ -226,8 +247,8 @@
} else {
const loadTime = this.performanceTiming.loadEventEnd -
this.performanceTiming.navigationStart;
- this.reporter(
- TIMING.TYPE, TIMING.CATEGORY, TIMING.PAGE_LOADED, loadTime);
+ this.reporter(TIMING.TYPE, TIMING.CATEGORY_UI_LATENCY,
+ TIMING.PAGE_LOADED, loadTime);
}
},
@@ -344,7 +365,8 @@
* @param {number} time The time to report as an integer of milliseconds.
*/
_reportTiming(name, time) {
- this.reporter(TIMING.TYPE, TIMING.CATEGORY, name, Math.round(time));
+ this.reporter(TIMING.TYPE, TIMING.CATEGORY_UI_LATENCY, name,
+ Math.round(time));
},
/**
@@ -395,6 +417,16 @@
return timer.reset();
},
+ /**
+ * Log timing information for an RPC.
+ * @param {string} anonymizedUrl The URL of the RPC with tokens obfuscated.
+ * @param {number} elapsed The time elapsed of the RPC.
+ */
+ reportRpcTiming(anonymizedUrl, elapsed) {
+ this.reporter(TIMING.TYPE, TIMING.CATEGORY_RPC, 'RPC-' + anonymizedUrl,
+ elapsed, true);
+ },
+
reportInteraction(eventName, opt_msg) {
this.reporter(INTERACTION_TYPE, this.category, eventName, opt_msg);
},
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
index e78f49fb..8b85074 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
@@ -264,7 +264,7 @@
sandbox.stub(element, 'now').returns(42);
element.pluginsLoaded();
assert.isTrue(element.defaultReporter.calledWithExactly(
- 'timing-report', 'UI Latency', 'PluginsLoaded', 42
+ 'timing-report', 'UI Latency', 'PluginsLoaded', 42, undefined
));
});
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index efcefe2..e7bd965 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -30,10 +30,12 @@
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/polymer-resin/standalone/polymer-resin.html">
+<link rel="import" href="../behaviors/safe-types-behavior/safe-types-behavior.html">
<script>
security.polymer_resin.install({
allowedIdentifierPrefixes: [''],
reportHandler: security.polymer_resin.CONSOLE_LOGGING_REPORT_HANDLER,
+ safeTypesBridge: Gerrit.SafeTypes.safeTypesBridge,
});
</script>
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index 7acb680..b4aec99 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -99,6 +99,7 @@
'page-error': '_handlePageError',
'title-change': '_handleTitleChange',
'location-change': '_handleLocationChange',
+ 'rpc-log': '_handleRpcLog',
},
observers: [
@@ -332,5 +333,15 @@
console.log(`Please file bugs and feedback at: ${this._feedbackUrl}`);
console.groupEnd();
},
+
+ /**
+ * Intercept RPC log events emitted by REST API interfaces.
+ * Note: the REST API interface cannot use gr-reporting directly because
+ * that would create a cyclic dependency.
+ */
+ _handleRpcLog(e) {
+ this.$.reporting.reportRpcTiming(e.detail.anonymizedUrl,
+ e.detail.elapsed);
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 5d68e01..fa1ad11 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -28,6 +28,15 @@
Defs.patchRange;
/**
+ * @typedef {{
+ * url: string,
+ * fetchOptions: (Object|null|undefined),
+ * anonymizedUrl: (string|undefined),
+ * }}
+ */
+ Defs.FetchRequest;
+
+ /**
* Object to describe a request for passing into _fetchJSON or _fetchRawJSON.
* - url is the URL for the request (excluding get params)
* - errFn is a function to invoke when the request fails.
@@ -40,6 +49,8 @@
* cancelCondition: (function()|null|undefined),
* params: (Object|null|undefined),
* fetchOptions: (Object|null|undefined),
+ * anonymizedUrl: (string|undefined),
+ * reportUrlAsIs: (boolean|undefined),
* }}
*/
Defs.FetchJSONRequest;
@@ -53,6 +64,8 @@
* cancelCondition: (function()|null|undefined),
* params: (Object|null|undefined),
* fetchOptions: (Object|null|undefined),
+ * anonymizedEndpoint: (string|undefined),
+ * reportEndpointAsIs: (boolean|undefined),
* }}
*/
Defs.ChangeFetchRequest;
@@ -78,6 +91,8 @@
* contentType: (string|null|undefined),
* headers: (Object|undefined),
* parseResponse: (boolean|undefined),
+ * anonymizedUrl: (string|undefined),
+ * reportUrlAsIs: (boolean|undefined),
* }}
*/
Defs.SendRequest;
@@ -93,6 +108,8 @@
* contentType: (string|null|undefined),
* headers: (Object|undefined),
* parseResponse: (boolean|undefined),
+ * anonymizedEndpoint: (string|undefined),
+ * reportEndpointAsIs: (boolean|undefined),
* }}
*/
Defs.ChangeSendRequest;
@@ -115,6 +132,9 @@
'Saving draft resulted in HTTP 200 (OK) but expected HTTP 201 (Created)';
const HEADER_REPORTING_BLACKLIST = /^set-cookie$/i;
+ const ANONYMIZED_CHANGE_BASE_URL = '/changes/*~*';
+ const ANONYMIZED_REVISION_BASE_URL = ANONYMIZED_CHANGE_BASE_URL +
+ '/revisions/*';
Polymer({
is: 'gr-rest-api-interface',
@@ -143,6 +163,12 @@
* @event auth-error
*/
+ /**
+ * Fired after an RPC completes.
+ *
+ * @event rpc-log
+ */
+
properties: {
_cache: {
type: Object,
@@ -182,15 +208,14 @@
/**
* Wraps calls to the underlying authenticated fetch function (_auth.fetch)
* with timing and logging.
- * @param {string} url
- * @param {Object=} opt_fetchOptions
+ * @param {Defs.FetchRequest} req
*/
- _fetch(url, opt_fetchOptions) {
+ _fetch(req) {
const start = Date.now();
- const xhr = this._auth.fetch(url, opt_fetchOptions);
+ const xhr = this._auth.fetch(req.url, req.fetchOptions);
// Log the call after it completes.
- xhr.then(res => this._logCall(url, opt_fetchOptions, start, res.status));
+ xhr.then(res => this._logCall(req, start, res.status));
// Return the XHR directly (without the log).
return xhr;
@@ -200,18 +225,27 @@
* Log information about a REST call. Because the elapsed time is determined
* by this method, it should be called immediately after the request
* finishes.
- * @param {string} url
- * @param {Object|undefined} fetchOptions
+ * @param {Defs.FetchRequest} req
* @param {number} startTime the time that the request was started.
* @param {number} status the HTTP status of the response. The status value
* is used here rather than the response object so there is no way this
* method can read the body stream.
*/
- _logCall(url, fetchOptions, startTime, status) {
- const method = (fetchOptions && fetchOptions.method) ?
- fetchOptions.method : 'GET';
- const elapsed = (Date.now() - startTime) + 'ms';
- console.log(['HTTP', status, method, elapsed, url].join(' '));
+ _logCall(req, startTime, status) {
+ const method = (req.fetchOptions && req.fetchOptions.method) ?
+ req.fetchOptions.method : 'GET';
+ const elapsed = (Date.now() - startTime);
+ console.log([
+ 'HTTP',
+ status,
+ method,
+ elapsed + 'ms',
+ req.anonymizedUrl || req.url,
+ ].join(' '));
+ if (req.anonymizedUrl) {
+ this.fire('rpc-log',
+ {status, method, elapsed, anonymizedUrl: req.anonymizedUrl});
+ }
},
/**
@@ -223,7 +257,12 @@
*/
_fetchRawJSON(req) {
const urlWithParams = this._urlWithParams(req.url, req.params);
- return this._fetch(urlWithParams, req.fetchOptions).then(res => {
+ const fetchReq = {
+ url: urlWithParams,
+ fetchOptions: req.fetchOptions,
+ anonymizedUrl: req.reportUrlAsIs ? urlWithParams : req.anonymizedUrl,
+ };
+ return this._fetch(fetchReq).then(res => {
if (req.cancelCondition && req.cancelCondition()) {
res.body.cancel();
return;
@@ -324,10 +363,16 @@
getConfig(noCache) {
if (!noCache) {
- return this._fetchSharedCacheURL({url: '/config/server/info'});
+ return this._fetchSharedCacheURL({
+ url: '/config/server/info',
+ reportUrlAsIs: true,
+ });
}
- return this._fetchJSON({url: '/config/server/info'});
+ return this._fetchJSON({
+ url: '/config/server/info',
+ reportUrlAsIs: true,
+ });
},
getRepo(repo, opt_errFn) {
@@ -336,6 +381,7 @@
return this._fetchSharedCacheURL({
url: '/projects/' + encodeURIComponent(repo),
errFn: opt_errFn,
+ anonymizedUrl: '/projects/*',
});
},
@@ -345,6 +391,7 @@
return this._fetchSharedCacheURL({
url: '/projects/' + encodeURIComponent(repo) + '/config',
errFn: opt_errFn,
+ anonymizedUrl: '/projects/*/config',
});
},
@@ -353,6 +400,7 @@
// supports it.
return this._fetchSharedCacheURL({
url: '/access/?project=' + encodeURIComponent(repo),
+ anonymizedUrl: '/access/?project=*',
});
},
@@ -362,6 +410,7 @@
return this._fetchSharedCacheURL({
url: `/projects/${encodeURIComponent(repo)}/dashboards?inherited`,
errFn: opt_errFn,
+ anonymizedUrl: '/projects/*/dashboards?inherited',
});
},
@@ -374,6 +423,7 @@
url: `/projects/${encodeName}/config`,
body: config,
errFn: opt_errFn,
+ anonymizedUrl: '/projects/*/config',
});
},
@@ -387,6 +437,7 @@
url: `/projects/${encodeName}/gc`,
body: '',
errFn: opt_errFn,
+ anonymizedUrl: '/projects/*/gc',
});
},
@@ -404,6 +455,7 @@
url: `/projects/${encodeName}`,
body: config,
errFn: opt_errFn,
+ anonymizedUrl: '/projects/*',
});
},
@@ -419,6 +471,7 @@
url: `/groups/${encodeName}`,
body: config,
errFn: opt_errFn,
+ anonymizedUrl: '/groups/*',
});
},
@@ -426,6 +479,7 @@
return this._fetchJSON({
url: `/groups/${encodeURIComponent(group)}/detail`,
errFn: opt_errFn,
+ anonymizedUrl: '/groups/*/detail',
});
},
@@ -445,6 +499,7 @@
url: `/projects/${encodeName}/branches/${encodeRef}`,
body: '',
errFn: opt_errFn,
+ anonymizedUrl: '/projects/*/branches/*',
});
},
@@ -464,6 +519,7 @@
url: `/projects/${encodeName}/tags/${encodeRef}`,
body: '',
errFn: opt_errFn,
+ anonymizedUrl: '/projects/*/tags/*',
});
},
@@ -484,6 +540,7 @@
url: `/projects/${encodeName}/branches/${encodeBranch}`,
body: revision,
errFn: opt_errFn,
+ anonymizedUrl: '/projects/*/branches/*',
});
},
@@ -504,6 +561,7 @@
url: `/projects/${encodeName}/tags/${encodeTag}`,
body: revision,
errFn: opt_errFn,
+ anonymizedUrl: '/projects/*/tags/*',
});
},
@@ -513,7 +571,11 @@
*/
getIsGroupOwner(groupName) {
const encodeName = encodeURIComponent(groupName);
- return this._fetchSharedCacheURL({url: `/groups/?owned&q=${encodeName}`})
+ const req = {
+ url: `/groups/?owned&q=${encodeName}`,
+ anonymizedUrl: '/groups/owned&q=*',
+ };
+ return this._fetchSharedCacheURL(req)
.then(configs => configs.hasOwnProperty(groupName));
},
@@ -522,12 +584,15 @@
return this._fetchJSON({
url: `/groups/${encodeName}/members/`,
errFn: opt_errFn,
+ anonymizedUrl: '/groups/*/members',
});
},
getIncludedGroup(groupName) {
- const encodeName = encodeURIComponent(groupName);
- return this._fetchJSON({url: `/groups/${encodeName}/groups/`});
+ return this._fetchJSON({
+ url: `/groups/${encodeURIComponent(groupName)}/groups/`,
+ anonymizedUrl: '/groups/*/groups',
+ });
},
saveGroupName(groupId, name) {
@@ -536,6 +601,7 @@
method: 'PUT',
url: `/groups/${encodeId}/name`,
body: {name},
+ anonymizedUrl: '/groups/*/name',
});
},
@@ -545,6 +611,7 @@
method: 'PUT',
url: `/groups/${encodeId}/owner`,
body: {owner: ownerId},
+ anonymizedUrl: '/groups/*/owner',
});
},
@@ -554,6 +621,7 @@
method: 'PUT',
url: `/groups/${encodeId}/description`,
body: {description},
+ anonymizedUrl: '/groups/*/description',
});
},
@@ -563,6 +631,7 @@
method: 'PUT',
url: `/groups/${encodeId}/options`,
body: options,
+ anonymizedUrl: '/groups/*/options',
});
},
@@ -570,6 +639,7 @@
return this._fetchSharedCacheURL({
url: '/groups/' + group + '/log.audit',
errFn: opt_errFn,
+ anonymizedUrl: '/groups/*/log.audit',
});
},
@@ -580,6 +650,7 @@
method: 'PUT',
url: `/groups/${encodeName}/members/${encodeMember}`,
parseResponse: true,
+ anonymizedUrl: '/groups/*/members/*',
});
},
@@ -590,6 +661,7 @@
method: 'PUT',
url: `/groups/${encodeName}/groups/${encodeIncludedGroup}`,
errFn: opt_errFn,
+ anonymizedUrl: '/groups/*/groups/*',
};
return this._send(req).then(response => {
if (response.ok) {
@@ -604,6 +676,7 @@
return this._send({
method: 'DELETE',
url: `/groups/${encodeName}/members/${encodeMember}`,
+ anonymizedUrl: '/groups/*/members/*',
});
},
@@ -613,11 +686,15 @@
return this._send({
method: 'DELETE',
url: `/groups/${encodeName}/groups/${encodeIncludedGroup}`,
+ anonymizedUrl: '/groups/*/groups/*',
});
},
getVersion() {
- return this._fetchSharedCacheURL({url: '/config/server/version'});
+ return this._fetchSharedCacheURL({
+ url: '/config/server/version',
+ reportUrlAsIs: true,
+ });
},
getDiffPreferences() {
@@ -625,6 +702,7 @@
if (loggedIn) {
return this._fetchSharedCacheURL({
url: '/accounts/self/preferences.diff',
+ reportUrlAsIs: true,
});
}
// These defaults should match the defaults in
@@ -655,6 +733,7 @@
if (loggedIn) {
return this._fetchSharedCacheURL({
url: '/accounts/self/preferences.edit',
+ reportUrlAsIs: true,
});
}
// These defaults should match the defaults in
@@ -696,6 +775,7 @@
url: '/accounts/self/preferences',
body: prefs,
errFn: opt_errFn,
+ reportUrlAsIs: true,
});
},
@@ -711,6 +791,7 @@
url: '/accounts/self/preferences.diff',
body: prefs,
errFn: opt_errFn,
+ reportUrlAsIs: true,
});
},
@@ -726,12 +807,14 @@
url: '/accounts/self/preferences.edit',
body: prefs,
errFn: opt_errFn,
+ reportUrlAsIs: true,
});
},
getAccount() {
return this._fetchSharedCacheURL({
url: '/accounts/self/detail',
+ reportUrlAsIs: true,
errFn: resp => {
if (!resp || resp.status === 403) {
this._cache['/accounts/self/detail'] = null;
@@ -741,7 +824,10 @@
},
getExternalIds() {
- return this._fetchJSON({url: '/accounts/self/external.ids'});
+ return this._fetchJSON({
+ url: '/accounts/self/external.ids',
+ reportUrlAsIs: true,
+ });
},
deleteAccountIdentity(id) {
@@ -750,6 +836,7 @@
url: '/accounts/self/external.ids:delete',
body: id,
parseResponse: true,
+ reportUrlAsIs: true,
});
},
@@ -760,11 +847,15 @@
getAccountDetails(userId) {
return this._fetchJSON({
url: `/accounts/${encodeURIComponent(userId)}/detail`,
+ anonymizedUrl: '/accounts/*/detail',
});
},
getAccountEmails() {
- return this._fetchSharedCacheURL({url: '/accounts/self/emails'});
+ return this._fetchSharedCacheURL({
+ url: '/accounts/self/emails',
+ reportUrlAsIs: true,
+ });
},
/**
@@ -776,6 +867,7 @@
method: 'PUT',
url: '/accounts/self/emails/' + encodeURIComponent(email),
errFn: opt_errFn,
+ anonymizedUrl: '/account/self/emails/*',
});
},
@@ -788,6 +880,7 @@
method: 'DELETE',
url: '/accounts/self/emails/' + encodeURIComponent(email),
errFn: opt_errFn,
+ anonymizedUrl: '/accounts/self/email/*',
});
},
@@ -797,8 +890,13 @@
*/
setPreferredAccountEmail(email, opt_errFn) {
const encodedEmail = encodeURIComponent(email);
- const url = `/accounts/self/emails/${encodedEmail}/preferred`;
- return this._send({method: 'PUT', url, errFn: opt_errFn}).then(() => {
+ const req = {
+ method: 'PUT',
+ url: `/accounts/self/emails/${encodedEmail}/preferred`,
+ errFn: opt_errFn,
+ anonymizedUrl: '/accounts/self/emails/*/preferred',
+ };
+ return this._send(req).then(() => {
// If result of getAccountEmails is in cache, update it in the cache
// so we don't have to invalidate it.
const cachedEmails = this._cache['/accounts/self/emails'];
@@ -840,6 +938,7 @@
body: {name},
errFn: opt_errFn,
parseResponse: true,
+ reportUrlAsIs: true,
};
return this._send(req)
.then(newName => this._updateCachedAccount({name: newName}));
@@ -856,6 +955,7 @@
body: {username},
errFn: opt_errFn,
parseResponse: true,
+ reportUrlAsIs: true,
};
return this._send(req)
.then(newName => this._updateCachedAccount({username: newName}));
@@ -872,6 +972,7 @@
body: {status},
errFn: opt_errFn,
parseResponse: true,
+ reportUrlAsIs: true,
};
return this._send(req)
.then(newStatus => this._updateCachedAccount({status: newStatus}));
@@ -880,15 +981,22 @@
getAccountStatus(userId) {
return this._fetchJSON({
url: `/accounts/${encodeURIComponent(userId)}/status`,
+ anonymizedUrl: '/accounts/*/status',
});
},
getAccountGroups() {
- return this._fetchJSON({url: '/accounts/self/groups'});
+ return this._fetchJSON({
+ url: '/accounts/self/groups',
+ reportUrlAsIs: true,
+ });
},
getAccountAgreements() {
- return this._fetchJSON({url: '/accounts/self/agreements'});
+ return this._fetchJSON({
+ url: '/accounts/self/agreements',
+ reportUrlAsIs: true,
+ });
},
saveAccountAgreement(name) {
@@ -896,6 +1004,7 @@
method: 'PUT',
url: '/accounts/self/agreements',
body: name,
+ reportUrlAsIs: true,
});
},
@@ -911,6 +1020,7 @@
}
return this._fetchSharedCacheURL({
url: '/accounts/self/capabilities' + queryString,
+ anonymizedUrl: '/accounts/self/capabilities?q=*',
});
},
@@ -937,8 +1047,9 @@
return;
}
this._credentialCheck.checking = true;
+ const req = {url: '/accounts/self/detail', reportUrlAsIs: true};
// Skip the REST response cache.
- return this._fetchRawJSON({url: '/accounts/self/detail'}).then(res => {
+ return this._fetchRawJSON(req).then(res => {
if (!res) { return; }
if (res.status === 403) {
this.fire('auth-error');
@@ -958,21 +1069,24 @@
},
getDefaultPreferences() {
- return this._fetchSharedCacheURL({url: '/config/server/preferences'});
+ return this._fetchSharedCacheURL({
+ url: '/config/server/preferences',
+ reportUrlAsIs: true,
+ });
},
getPreferences() {
return this.getLoggedIn().then(loggedIn => {
if (loggedIn) {
- return this._fetchSharedCacheURL({url: '/accounts/self/preferences'})
- .then(res => {
- if (this._isNarrowScreen()) {
- res.default_diff_view = DiffViewMode.UNIFIED;
- } else {
- res.default_diff_view = res.diff_view;
- }
- return Promise.resolve(res);
- });
+ const req = {url: '/accounts/self/preferences', reportUrlAsIs: true};
+ return this._fetchSharedCacheURL(req).then(res => {
+ if (this._isNarrowScreen()) {
+ res.default_diff_view = DiffViewMode.UNIFIED;
+ } else {
+ res.default_diff_view = res.diff_view;
+ }
+ return Promise.resolve(res);
+ });
}
return Promise.resolve({
@@ -988,6 +1102,7 @@
getWatchedProjects() {
return this._fetchSharedCacheURL({
url: '/accounts/self/watched.projects',
+ reportUrlAsIs: true,
});
},
@@ -1002,6 +1117,7 @@
body: projects,
errFn: opt_errFn,
parseResponse: true,
+ reportUrlAsIs: true,
});
},
@@ -1015,6 +1131,7 @@
url: '/accounts/self/watched.projects:delete',
body: projects,
errFn: opt_errFn,
+ reportUrlAsIs: true,
});
},
@@ -1079,7 +1196,12 @@
this._maybeInsertInLookup(change);
}
};
- return this._fetchJSON({url: '/changes/', params}).then(response => {
+ const req = {
+ url: '/changes/',
+ params,
+ reportUrlAsIs: true,
+ };
+ return this._fetchJSON(req).then(response => {
// Response may be an array of changes OR an array of arrays of
// changes.
if (opt_query instanceof Array) {
@@ -1173,6 +1295,7 @@
cancelCondition: opt_cancelCondition,
params: {O: params},
fetchOptions: this._etags.getOptions(urlWithParams),
+ anonymizedUrl: '/changes/*~*/detail?O=' + params,
};
return this._fetchRawJSON(req).then(response => {
if (response && response.status === 304) {
@@ -1213,6 +1336,7 @@
changeNum,
endpoint: '/commit?links',
patchNum,
+ reportEndpointAsIs: true,
});
},
@@ -1233,6 +1357,7 @@
endpoint: '/files',
patchNum: patchRange.patchNum,
params,
+ reportEndpointAsIs: true,
});
},
@@ -1242,10 +1367,16 @@
*/
getChangeEditFiles(changeNum, patchRange) {
let endpoint = '/edit?list';
+ let anonymizedEndpoint = endpoint;
if (patchRange.basePatchNum !== 'PARENT') {
endpoint += '&base=' + encodeURIComponent(patchRange.basePatchNum + '');
+ anonymizedEndpoint += '&base=*';
}
- return this._getChangeURLAndFetch({changeNum, endpoint});
+ return this._getChangeURLAndFetch({
+ changeNum,
+ endpoint,
+ anonymizedEndpoint,
+ });
},
/**
@@ -1259,6 +1390,7 @@
changeNum,
endpoint: `/files?q=${encodeURIComponent(query)}`,
patchNum,
+ anonymizedEndpoint: '/files?q=*',
});
},
@@ -1287,7 +1419,12 @@
},
getChangeRevisionActions(changeNum, patchNum) {
- const req = {changeNum, endpoint: '/actions', patchNum};
+ const req = {
+ changeNum,
+ endpoint: '/actions',
+ patchNum,
+ reportEndpointAsIs: true,
+ };
return this._getChangeURLAndFetch(req).then(revisionActions => {
// The rebase button on change screen is always enabled.
if (revisionActions.rebase) {
@@ -1312,6 +1449,7 @@
endpoint: '/suggest_reviewers',
errFn: opt_errFn,
params,
+ reportEndpointAsIs: true,
});
},
@@ -1319,7 +1457,11 @@
* @param {number|string} changeNum
*/
getChangeIncludedIn(changeNum) {
- return this._getChangeURLAndFetch({changeNum, endpoint: '/in'});
+ return this._getChangeURLAndFetch({
+ changeNum,
+ endpoint: '/in',
+ reportEndpointAsIs: true,
+ });
},
_computeFilter(filter) {
@@ -1345,6 +1487,7 @@
return this._fetchSharedCacheURL({
url: `/groups/?n=${groupsPerPage + 1}&S=${offset}` +
this._computeFilter(filter),
+ anonymizedUrl: '/groups/?*',
});
},
@@ -1362,6 +1505,7 @@
return this._fetchSharedCacheURL({
url: `/projects/?d&n=${reposPerPage + 1}&S=${offset}` +
this._computeFilter(filter),
+ anonymizedUrl: '/projects/?*',
});
},
@@ -1372,6 +1516,7 @@
method: 'PUT',
url: `/projects/${encodeURIComponent(repo)}/HEAD`,
body: {ref},
+ anonymizedUrl: '/projects/*/HEAD',
});
},
@@ -1391,7 +1536,11 @@
const url = `/projects/${repo}/branches?n=${count}&S=${offset}${filter}`;
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
- return this._fetchJSON({url, errFn: opt_errFn});
+ return this._fetchJSON({
+ url,
+ errFn: opt_errFn,
+ anonymizedUrl: '/projects/*/branches?*',
+ });
},
/**
@@ -1411,7 +1560,11 @@
encodedFilter;
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
- return this._fetchJSON({url, errFn: opt_errFn});
+ return this._fetchJSON({
+ url,
+ errFn: opt_errFn,
+ anonymizedUrl: '/projects/*/tags',
+ });
},
/**
@@ -1426,7 +1579,11 @@
const encodedFilter = this._computeFilter(filter);
const n = pluginsPerPage + 1;
const url = `/plugins/?all&n=${n}&S=${offset}${encodedFilter}`;
- return this._fetchJSON({url, errFn: opt_errFn});
+ return this._fetchJSON({
+ url,
+ errFn: opt_errFn,
+ anonymizedUrl: '/plugins/?all',
+ });
},
getRepoAccessRights(repoName, opt_errFn) {
@@ -1435,6 +1592,7 @@
return this._fetchJSON({
url: `/projects/${encodeURIComponent(repoName)}/access`,
errFn: opt_errFn,
+ anonymizedUrl: '/projects/*/access',
});
},
@@ -1445,6 +1603,7 @@
method: 'POST',
url: `/projects/${encodeURIComponent(repoName)}/access`,
body: repoInfo,
+ anonymizedUrl: '/projects/*/access',
});
},
@@ -1454,6 +1613,7 @@
url: `/projects/${encodeURIComponent(projectName)}/access:review`,
body: projectInfo,
parseResponse: true,
+ anonymizedUrl: '/projects/*/access:review',
});
},
@@ -1469,6 +1629,7 @@
url: '/groups/',
errFn: opt_errFn,
params,
+ reportUrlAsIs: true,
});
},
@@ -1488,6 +1649,7 @@
url: '/projects/',
errFn: opt_errFn,
params,
+ reportUrlAsIs: true,
});
},
@@ -1506,6 +1668,7 @@
url: '/accounts/',
errFn: opt_errFn,
params,
+ anonymizedUrl: '/accounts/?n=*',
});
},
@@ -1541,6 +1704,7 @@
changeNum,
endpoint: '/related',
patchNum,
+ reportEndpointAsIs: true,
});
},
@@ -1548,6 +1712,7 @@
return this._getChangeURLAndFetch({
changeNum,
endpoint: '/submitted_together',
+ reportEndpointAsIs: true,
});
},
@@ -1560,7 +1725,11 @@
O: options,
q: 'status:open is:mergeable conflicts:' + changeNum,
};
- return this._fetchJSON({url: '/changes/', params});
+ return this._fetchJSON({
+ url: '/changes/',
+ params,
+ anonymizedUrl: '/changes/conflicts:*',
+ });
},
getChangeCherryPicks(project, changeID, changeNum) {
@@ -1578,7 +1747,11 @@
O: options,
q: query,
};
- return this._fetchJSON({url: '/changes/', params});
+ return this._fetchJSON({
+ url: '/changes/',
+ params,
+ anonymizedUrl: '/changes/change:*',
+ });
},
getChangesWithSameTopic(topic) {
@@ -1592,7 +1765,11 @@
O: options,
q: 'status:open topic:' + topic,
};
- return this._fetchJSON({url: '/changes/', params});
+ return this._fetchJSON({
+ url: '/changes/',
+ params,
+ anonymizedUrl: '/changes/topic:*',
+ });
},
getReviewedFiles(changeNum, patchNum) {
@@ -1600,6 +1777,7 @@
changeNum,
endpoint: '/files?reviewed',
patchNum,
+ reportEndpointAsIs: true,
});
},
@@ -1617,6 +1795,7 @@
patchNum,
endpoint: `/files/${encodeURIComponent(path)}/reviewed`,
errFn: opt_errFn,
+ anonymizedEndpoint: '/files/*/reviewed',
});
},
@@ -1649,6 +1828,7 @@
changeNum,
endpoint: '/edit/',
params,
+ reportEndpointAsIs: true,
});
});
},
@@ -1679,6 +1859,7 @@
base_commit: opt_baseCommit,
},
parseResponse: true,
+ reportUrlAsIs: true,
});
},
@@ -1725,6 +1906,7 @@
endpoint: `/files/${encodeURIComponent(path)}/content`,
errFn: opt_errFn,
headers: {Accept: 'application/json'},
+ anonymizedEndpoint: '/files/*/content',
});
},
@@ -1739,6 +1921,7 @@
method: 'GET',
endpoint: '/edit/' + encodeURIComponent(path),
headers: {Accept: 'application/json'},
+ anonymizedEndpoint: '/edit/*',
});
},
@@ -1747,6 +1930,7 @@
changeNum,
method: 'POST',
endpoint: '/edit:rebase',
+ reportEndpointAsIs: true,
});
},
@@ -1755,6 +1939,7 @@
changeNum,
method: 'DELETE',
endpoint: '/edit',
+ reportEndpointAsIs: true,
});
},
@@ -1764,6 +1949,7 @@
method: 'POST',
endpoint: '/edit',
body: {restore_path},
+ reportEndpointAsIs: true,
});
},
@@ -1773,6 +1959,7 @@
method: 'POST',
endpoint: '/edit',
body: {old_path, new_path},
+ reportEndpointAsIs: true,
});
},
@@ -1781,6 +1968,7 @@
changeNum,
method: 'DELETE',
endpoint: '/edit/' + encodeURIComponent(path),
+ anonymizedEndpoint: '/edit/*',
});
},
@@ -1791,6 +1979,7 @@
endpoint: '/edit/' + encodeURIComponent(path),
body: contents,
contentType: 'text/plain',
+ anonymizedEndpoint: '/edit/*',
});
},
@@ -1801,6 +1990,7 @@
method: 'PUT',
endpoint: '/edit:message',
body: {message},
+ reportEndpointAsIs: true,
});
},
@@ -1809,6 +1999,7 @@
changeNum,
method: 'POST',
endpoint: '/edit:publish',
+ reportEndpointAsIs: true,
});
},
@@ -1818,13 +2009,16 @@
method: 'PUT',
endpoint: '/message',
body: {message},
+ reportEndpointAsIs: true,
});
},
saveChangeStarred(changeNum, starred) {
- const url = '/accounts/self/starred.changes/' + changeNum;
- const method = starred ? 'PUT' : 'DELETE';
- return this._send({method, url});
+ return this._send({
+ method: starred ? 'PUT' : 'DELETE',
+ url: '/accounts/self/starred.changes/' + changeNum,
+ anonymizedUrl: '/accounts/self/starred.changes/*',
+ });
},
/**
@@ -1850,7 +2044,12 @@
}
const url = req.url.startsWith('http') ?
req.url : this.getBaseUrl() + req.url;
- const xhr = this._fetch(url, options).then(response => {
+ const fetchReq = {
+ url,
+ fetchOptions: options,
+ anonymizedUrl: req.reportUrlAsIs ? url : req.anonymizedUrl,
+ };
+ const xhr = this._fetch(fetchReq).then(response => {
if (!response.ok) {
if (req.errFn) {
return req.errFn.call(undefined, response);
@@ -1928,6 +2127,7 @@
errFn: opt_errFn,
cancelCondition: opt_cancelCondition,
params,
+ anonymizedEndpoint: '/files/*/diff',
});
},
@@ -2019,6 +2219,7 @@
changeNum,
endpoint,
patchNum: opt_patchNum,
+ reportEndpointAsIs: true,
});
};
@@ -2109,8 +2310,10 @@
_sendDiffDraftRequest(method, changeNum, patchNum, draft) {
const isCreate = !draft.id && method === 'PUT';
let endpoint = '/drafts';
+ let anonymizedEndpoint = endpoint;
if (draft.id) {
endpoint += '/' + draft.id;
+ anonymizedEndpoint += '/*';
}
let body;
if (method === 'PUT') {
@@ -2121,8 +2324,16 @@
this._pendingRequests[Requests.SEND_DIFF_DRAFT] = [];
}
- const promise = this._getChangeURLAndSend(
- {changeNum, method, patchNum, endpoint, body});
+ const req = {
+ changeNum,
+ method,
+ patchNum,
+ endpoint,
+ body,
+ anonymizedEndpoint,
+ };
+
+ const promise = this._getChangeURLAndSend(req);
this._pendingRequests[Requests.SEND_DIFF_DRAFT].push(promise);
if (isCreate) {
@@ -2136,11 +2347,12 @@
return this._fetchJSON({
url: '/projects/' + encodeURIComponent(project) +
'/commits/' + encodeURIComponent(commit),
+ anonymizedUrl: '/projects/*/comments/*',
});
},
_fetchB64File(url) {
- return this._fetch(this.getBaseUrl() + url)
+ return this._fetch({url: this.getBaseUrl() + url})
.then(response => {
if (!response.ok) { return Promise.reject(response.statusText); }
const type = response.headers.get('X-FYI-Content-Type');
@@ -2241,6 +2453,7 @@
endpoint: '/topic',
body: {topic},
parseResponse: true,
+ reportUrlAsIs: true,
});
},
@@ -2256,6 +2469,7 @@
endpoint: '/hashtags',
body: hashtag,
parseResponse: true,
+ reportUrlAsIs: true,
});
},
@@ -2263,6 +2477,7 @@
return this._send({
method: 'DELETE',
url: '/accounts/self/password.http',
+ reportUrlAsIs: true,
});
},
@@ -2277,11 +2492,15 @@
url: '/accounts/self/password.http',
body: {generate: true},
parseResponse: true,
+ reportUrlAsIs: true,
});
},
getAccountSSHKeys() {
- return this._fetchSharedCacheURL({url: '/accounts/self/sshkeys'});
+ return this._fetchSharedCacheURL({
+ url: '/accounts/self/sshkeys',
+ reportUrlAsIs: true,
+ });
},
addAccountSSHKey(key) {
@@ -2290,6 +2509,7 @@
url: '/accounts/self/sshkeys',
body: key,
contentType: 'plain/text',
+ reportUrlAsIs: true,
};
return this._send(req)
.then(response => {
@@ -2308,15 +2528,24 @@
return this._send({
method: 'DELETE',
url: '/accounts/self/sshkeys/' + id,
+ anonymizedUrl: '/accounts/self/sshkeys/*',
});
},
getAccountGPGKeys() {
- return this._fetchJSON({url: '/accounts/self/gpgkeys'});
+ return this._fetchJSON({
+ url: '/accounts/self/gpgkeys',
+ reportUrlAsIs: true,
+ });
},
addAccountGPGKey(key) {
- const req = {method: 'POST', url: '/accounts/self/gpgkeys', body: key};
+ const req = {
+ method: 'POST',
+ url: '/accounts/self/gpgkeys',
+ body: key,
+ reportUrlAsIs: true,
+ };
return this._send(req)
.then(response => {
if (response.status < 200 && response.status >= 300) {
@@ -2334,6 +2563,7 @@
return this._send({
method: 'DELETE',
url: '/accounts/self/gpgkeys/' + id,
+ anonymizedUrl: '/accounts/self/gpgkeys/*',
});
},
@@ -2342,6 +2572,7 @@
changeNum,
method: 'DELETE',
endpoint: `/reviewers/${account}/votes/${encodeURIComponent(label)}`,
+ anonymizedEndpoint: '/reviewers/*/votes/*',
});
},
@@ -2351,6 +2582,7 @@
method: 'PUT', patchNum,
endpoint: '/description',
body: {description: desc},
+ reportUrlAsIs: true,
});
},
@@ -2359,6 +2591,7 @@
method: 'PUT',
url: '/config/server/email.confirm',
body: {token},
+ reportUrlAsIs: true,
};
return this._send(req).then(response => {
if (response.status === 204) {
@@ -2372,6 +2605,7 @@
return this._fetchJSON({
url: '/config/server/capabilities',
errFn: opt_errFn,
+ reportUrlAsIs: true,
});
},
@@ -2381,6 +2615,7 @@
method: 'PUT',
endpoint: '/assignee',
body: {assignee},
+ reportUrlAsIs: true,
});
},
@@ -2389,6 +2624,7 @@
changeNum,
method: 'DELETE',
endpoint: '/assignee',
+ reportUrlAsIs: true,
});
},
@@ -2408,7 +2644,13 @@
if (opt_message) {
body.message = opt_message;
}
- const req = {changeNum, method: 'POST', endpoint: '/wip', body};
+ const req = {
+ changeNum,
+ method: 'POST',
+ endpoint: '/wip',
+ body,
+ reportUrlAsIs: true,
+ };
return this._getChangeURLAndSend(req).then(response => {
if (response.status === 204) {
return 'Change marked as Work In Progress.';
@@ -2428,6 +2670,7 @@
endpoint: '/ready',
body: opt_body,
errFn: opt_errFn,
+ reportUrlAsIs: true,
});
},
@@ -2444,6 +2687,7 @@
endpoint: `/comments/${commentID}/delete`,
body: {reason},
parseResponse: true,
+ anonymizedEndpoint: '/comments/*/delete',
});
},
@@ -2459,6 +2703,7 @@
return this._fetchJSON({
url: `/changes/?q=change:${changeNum}`,
errFn: opt_errFn,
+ anonymizedUrl: '/changes/?q=change:*',
}).then(res => {
if (!res || !res.length) { return null; }
return res[0];
@@ -2509,6 +2754,11 @@
* @return {!Promise<!Object>}
*/
_getChangeURLAndSend(req) {
+ const anonymizedBaseUrl = req.patchNum ?
+ ANONYMIZED_REVISION_BASE_URL : ANONYMIZED_CHANGE_BASE_URL;
+ const anonymizedEndpoint = req.reportEndpointAsIs ?
+ req.endpoint : req.anonymizedEndpoint;
+
return this._changeBaseURL(req.changeNum, req.patchNum).then(url => {
return this._send({
method: req.method,
@@ -2518,6 +2768,8 @@
contentType: req.contentType,
headers: req.headers,
parseResponse: req.parseResponse,
+ anonymizedUrl: anonymizedEndpoint ?
+ (anonymizedBaseUrl + anonymizedEndpoint) : undefined,
});
});
},
@@ -2528,6 +2780,10 @@
* @return {!Promise<!Object>}
*/
_getChangeURLAndFetch(req) {
+ const anonymizedEndpoint = req.reportEndpointAsIs ?
+ req.endpoint : req.anonymizedEndpoint;
+ const anonymizedBaseUrl = req.patchNum ?
+ ANONYMIZED_REVISION_BASE_URL : ANONYMIZED_CHANGE_BASE_URL;
return this._changeBaseURL(req.changeNum, req.patchNum).then(url => {
return this._fetchJSON({
url: url + req.endpoint,
@@ -2535,6 +2791,8 @@
cancelCondition: req.cancelCondition,
params: req.params,
fetchOptions: req.fetchOptions,
+ anonymizedUrl: anonymizedEndpoint ?
+ (anonymizedBaseUrl + anonymizedEndpoint) : undefined,
});
});
},
@@ -2577,6 +2835,7 @@
endpoint: `/files/${encodedPath}/blame`,
patchNum,
params: opt_base ? {base: 't'} : undefined,
+ anonymizedEndpoint: '/files/*/blame',
});
},
@@ -2621,7 +2880,11 @@
getDashboard(project, dashboard, opt_errFn) {
const url = '/projects/' + encodeURIComponent(project) + '/dashboards/' +
encodeURIComponent(dashboard);
- return this._fetchSharedCacheURL({url, errFn: opt_errFn});
+ return this._fetchSharedCacheURL({
+ url,
+ errFn: opt_errFn,
+ anonymizedUrl: '/projects/*/dashboards/*',
+ });
},
getMergeable(changeNum) {
@@ -2629,6 +2892,7 @@
changeNum,
endpoint: '/revisions/current/mergeable',
parseResponse: true,
+ reportEndpointAsIs: true,
});
},
});
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index 70d1465..193d306 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -711,11 +711,10 @@
sandbox.spy(element, '_send');
element.confirmEmail('foo');
assert.isTrue(element._send.calledOnce);
- assert.deepEqual(element._send.lastCall.args[0], {
- method: 'PUT',
- url: '/config/server/email.confirm',
- body: {token: 'foo'},
- });
+ assert.equal(element._send.lastCall.args[0].method, 'PUT');
+ assert.equal(element._send.lastCall.args[0].url,
+ '/config/server/email.confirm');
+ assert.deepEqual(element._send.lastCall.args[0].body, {token: 'foo'});
});
test('GrReviewerUpdatesParser.parse is used', () => {
@@ -924,11 +923,10 @@
const fetchStub = sandbox.stub(element, '_getChangeURLAndFetch')
.returns(Promise.resolve());
return element.queryChangeFiles('42', 'edit', 'test/path.js').then(() => {
- assert.deepEqual(fetchStub.lastCall.args[0], {
- changeNum: '42',
- endpoint: '/files?q=test%2Fpath.js',
- patchNum: 'edit',
- });
+ assert.equal(fetchStub.lastCall.args[0].changeNum, '42');
+ assert.equal(fetchStub.lastCall.args[0].endpoint,
+ '/files?q=test%2Fpath.js');
+ assert.equal(fetchStub.lastCall.args[0].patchNum, 'edit');
});
});
@@ -1387,12 +1385,25 @@
sandbox.stub(element._auth, 'fetch').returns(Promise.resolve(response));
const startTime = 123;
sandbox.stub(Date, 'now').returns(startTime);
- return element._fetch(url, fetchOptions).then(() => {
+ const req = {url, fetchOptions};
+ return element._fetch(req).then(() => {
assert.isTrue(logStub.calledOnce);
- assert.isTrue(logStub.calledWith(
- url, fetchOptions, startTime, response.status));
+ assert.isTrue(logStub.calledWith(req, startTime, response.status));
assert.isFalse(response.text.called);
});
});
+
+ test('_logCall only reports requests with anonymized URLss', () => {
+ sandbox.stub(Date, 'now').returns(200);
+ const handler = sinon.stub();
+ element.addEventListener('rpc-log', handler);
+
+ element._logCall({url: 'url'}, 100, 200);
+ assert.isFalse(handler.called);
+
+ element._logCall({url: 'url', anonymizedUrl: 'not url'}, 100, 200);
+ flushAsynchronousOperations();
+ assert.isTrue(handler.calledOnce);
+ });
});
</script>
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 6e61c8e..0310e58 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -204,6 +204,7 @@
'gr-patch-set-behavior/gr-patch-set-behavior_test.html',
'gr-path-list-behavior/gr-path-list-behavior_test.html',
'gr-tooltip-behavior/gr-tooltip-behavior_test.html',
+ 'safe-types-behavior/safe-types-behavior_test.html',
];
/* eslint-enable max-len */
for (let file of behaviors) {