Merge "Sidebar UX improvements"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 48b572a..91a1117 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1450,6 +1450,16 @@
+
By default 100,000.
+[[change.maxFiles]]change.maxFileSizeDownload::
++
+The link:rest-api-changes.html#get-content[GetContent] and
+link:rest-api-changes.html#get-safe-content[DownloadContent] REST APIs will
+refuse to load files larger than this limit (in bytes). 0 or negative values
+mean "unlimited".
+
++
+By default 0 (unlimited).
+
[[change.maxPatchSets]]change.maxPatchSets::
+
Maximum number of patch sets allowed per change. If this is insufficient,
diff --git a/java/com/google/gerrit/server/change/FileContentUtil.java b/java/com/google/gerrit/server/change/FileContentUtil.java
index c54b902..4c0eb69 100644
--- a/java/com/google/gerrit/server/change/FileContentUtil.java
+++ b/java/com/google/gerrit/server/change/FileContentUtil.java
@@ -25,6 +25,7 @@
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.mime.FileTypeRegistry;
import com.google.gerrit.server.project.ProjectState;
@@ -39,6 +40,7 @@
import java.util.zip.ZipOutputStream;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
@@ -60,11 +62,15 @@
private final GitRepositoryManager repoManager;
private final FileTypeRegistry registry;
+ private final long maxFileSizeBytes;
@Inject
- FileContentUtil(GitRepositoryManager repoManager, FileTypeRegistry ftr) {
+ FileContentUtil(
+ GitRepositoryManager repoManager, FileTypeRegistry ftr, @GerritServerConfig Config config) {
this.repoManager = repoManager;
this.registry = ftr;
+ long maxFileSizeDownload = config.getLong("change", null, "maxFileSizeDownload", 0);
+ this.maxFileSizeBytes = maxFileSizeDownload > 0 ? maxFileSizeDownload : Long.MAX_VALUE;
}
/**
@@ -117,6 +123,7 @@
}
ObjectLoader obj = repo.open(id, OBJ_BLOB);
+ checkMaxFileSizeBytes(obj);
byte[] raw;
try {
raw = obj.getCachedBytes(MAX_SIZE);
@@ -154,7 +161,7 @@
public BinaryResult downloadContent(
ProjectState project, ObjectId revstr, String path, @Nullable Integer parent)
- throws ResourceNotFoundException, IOException {
+ throws ResourceNotFoundException, IOException, BadRequestException {
try (Repository repo = openRepository(project);
RevWalk rw = new RevWalk(repo)) {
String suffix = "new";
@@ -179,6 +186,8 @@
ObjectId id = tw.getObjectId(0);
ObjectLoader obj = repo.open(id, OBJ_BLOB);
+ checkMaxFileSizeBytes(obj);
+
byte[] raw;
try {
raw = obj.getCachedBytes(MAX_SIZE);
@@ -194,6 +203,16 @@
}
}
+ private void checkMaxFileSizeBytes(ObjectLoader obj) throws BadRequestException {
+ if (obj.getSize() > this.maxFileSizeBytes) {
+ throw new BadRequestException(
+ String.format(
+ "File too big. File size: %d bytes. Configured 'maxFileSizeDownload' limit: %d"
+ + " bytes.",
+ obj.getSize(), this.maxFileSizeBytes));
+ }
+ }
+
private BinaryResult wrapBlob(
String path,
final ObjectLoader obj,
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 7086447..7a50bdd 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -224,6 +224,7 @@
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.server.validators.AccountActivationValidationListener;
+import com.google.gerrit.server.validators.CustomKeyedValueValidationListener;
import com.google.gerrit.server.validators.GroupCreationValidationListener;
import com.google.gerrit.server.validators.HashtagValidationListener;
import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
@@ -420,6 +421,7 @@
DynamicSet.setOf(binder(), HashtagValidationListener.class);
DynamicSet.setOf(binder(), OutgoingEmailValidationListener.class);
DynamicSet.setOf(binder(), AccountActivationValidationListener.class);
+ DynamicSet.setOf(binder(), CustomKeyedValueValidationListener.class);
DynamicItem.itemOf(binder(), AvatarProvider.class);
DynamicSet.setOf(binder(), LifecycleListener.class);
DynamicSet.setOf(binder(), TopMenu.class);
diff --git a/java/com/google/gerrit/server/restapi/change/DownloadContent.java b/java/com/google/gerrit/server/restapi/change/DownloadContent.java
index 74c0acc..ae801b4 100644
--- a/java/com/google/gerrit/server/restapi/change/DownloadContent.java
+++ b/java/com/google/gerrit/server/restapi/change/DownloadContent.java
@@ -16,6 +16,7 @@
import static com.google.gerrit.server.project.ProjectCache.illegalState;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
@@ -44,7 +45,7 @@
@Override
public Response<BinaryResult> apply(FileResource rsrc)
- throws ResourceNotFoundException, IOException, NoSuchChangeException {
+ throws BadRequestException, ResourceNotFoundException, IOException, NoSuchChangeException {
String path = rsrc.getPatchKey().fileName();
RevisionResource rev = rsrc.getRevision();
return Response.ok(
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index cc7712b..ef7a14c 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -1718,6 +1718,34 @@
}
@Test
+ @GerritConfig(name = "change.maxFileSizeDownload", value = "10")
+ public void content_maxFileSizeDownload() throws Exception {
+ Map<String, String> files =
+ ImmutableMap.of("dir/file1.txt", " 9 bytes ", "dir/file2.txt", "11 bytes xx");
+ PushOneCommit.Result result =
+ pushFactory.create(admin.newIdent(), testRepo, SUBJECT, files).to("refs/for/master");
+ result.assertOkStatus();
+
+ // 9 bytes should be fine, because the limit is 10 bytes.
+ assertContent(result, "dir/file1.txt", " 9 bytes ");
+
+ // 11 bytes should throw, because the limit is 10 bytes.
+ BadRequestException exception =
+ assertThrows(
+ BadRequestException.class,
+ () ->
+ gApi.changes()
+ .id(result.getChangeId())
+ .revision(result.getCommit().name())
+ .file("dir/file2.txt")
+ .content());
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo(
+ "File too big. File size: 11 bytes. Configured 'maxFileSizeDownload' limit: 10 bytes.");
+ }
+
+ @Test
public void patchsetLevelContentDoesNotExist() throws Exception {
PushOneCommit.Result change = createChange();
assertThrows(
diff --git a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
index b119104..4402249 100644
--- a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
+++ b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
@@ -23,7 +23,6 @@
import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Project;
@@ -69,13 +68,19 @@
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
-import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
+/**
+ * Tests queries against the project index.
+ *
+ * <p>Note, returned projects are sorted by name. Projects that start with a capital letter are
+ * returned first. {@code InMemoryModule} sets that name for the All-Users project to {@code
+ * Test-Projects}, this is why it's returned after {@code Alll-Users}.
+ */
@Ignore
public abstract class AbstractQueryProjectsTest extends GerritServerTests {
@Rule public final GerritTestName testName = new GerritTestName();
@@ -112,6 +117,8 @@
protected Injector injector;
protected AccountInfo currentUserInfo;
protected CurrentUser user;
+ protected ProjectInfo allProjectsInfo;
+ protected ProjectInfo allUsersInfo;
protected abstract Injector createInjector();
@@ -141,6 +148,13 @@
user = userFactory.create(userId);
requestContext.setContext(newRequestContext(userId));
currentUserInfo = gApi.accounts().id(userId.get()).get();
+
+ // All-Projects and All-Users are not indexed, index them now.
+ gApi.projects().name(allProjects.get()).index(/* indexChildren= */ false);
+ gApi.projects().name(allUsers.get()).index(/* indexChildren= */ false);
+
+ allProjectsInfo = gApi.projects().name(allProjects.get()).get();
+ allUsersInfo = gApi.projects().name(allUsers.get()).get();
}
protected void initAfterLifecycleStart() throws Exception {}
@@ -170,6 +184,8 @@
ProjectInfo project = createProject(name("project"));
assertQuery("name:" + project.name, project);
+ assertQuery("name:" + allProjects.get(), allProjectsInfo);
+ assertQuery("name:" + allUsers.get(), allUsersInfo);
// only exact match
ProjectInfo projectWithHyphen = createProject(name("project-with-hyphen"));
@@ -188,12 +204,15 @@
@Test
public void byParentOfAllProjects() throws Exception {
- Set<String> excludedProjects = ImmutableSet.of(allProjects.get(), allUsers.get());
- ProjectInfo[] projects =
- gApi.projects().list().get().stream()
- .filter(p -> !excludedProjects.contains(p.name))
- .toArray(s -> new ProjectInfo[s]);
- assertQuery("parent:" + allProjects.get(), projects);
+ ProjectInfo parent1 = createProject(name("parent1"));
+ createProject(name("child"), parent1.name);
+
+ ProjectInfo parent2 = createProject(name("parent2"));
+ createProject(name("child2"), parent2.name);
+
+ // TODO: All-Users should be returned as well, since it's a direct child project under
+ // All-Projects
+ assertQuery("parent:" + allProjects.get(), parent1, parent2);
}
@Test
@@ -231,7 +250,7 @@
ProjectInfo project1 = createProjectWithState(name("project1"), ProjectState.ACTIVE);
ProjectInfo project2 = createProjectWithState(name("project2"), ProjectState.READ_ONLY);
- assertQuery("state:active", project1);
+ assertQuery("state:active", allUsersInfo, allProjectsInfo, project1);
assertQuery("state:read-only", project2);
}