Merge "Move 'User Refs' docs to the intro-user doc."
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 14523ec..c648725 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2201,7 +2201,7 @@
thread pool waiting for a worker thread to become available.
0 sets the queue size to the Integer.MAX_VALUE.
+
-By default 50.
+By default 200.
[[httpd.maxWait]]httpd.maxWait::
+
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 94f8ac9..4b57827 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -3326,6 +3326,60 @@
`application/json` the content is returned as JSON string and
`X-FYI-Content-Encoding` is set to `json`.
+[[get-safe-content]]
+=== Download Content
+--
+'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/files/link:#file-id[\{file-id\}]/download'
+--
+
+Downloads the content of a file from a certain revision, in a safe format
+that poses no risk for inadvertent execution of untrusted code.
+
+If the content type is defined as safe, the binary file content is returned
+verbatim. If the content type is not safe, the file is stored inside a ZIP
+file, containing a single entry with a random, unpredictable name having the
+same base and suffix as the true filename. The ZIP file is returned in
+verbatim binary form.
+
+See link:config-gerrit.html#mimetype.name.safe[Gerrit config documentation]
+for information about safe file type configuration.
+
+The HTTP resource Content-Type is dependent on the file type: the
+applicable type for safe files, or "application/zip" for unsafe files.
+
+The `suffix` parameter can be specified to decorate the names of the files.
+The suffix is inserted between the base filename and the random component or
+extension, or appended to the filename if neither such component is present.
+Only the lowercase Roman letters a-z are permitted; other characters are ignored.
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/website%2Freleases%2Flogo.png/safe_content HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment; filename="logo.png"
+ Content-Type: image/png
+
+ `[binary data for logo.png]`
+----
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/gerrit-server%2Fsrc%2Fmain%2Fjava%2Fcom%2Fgoogle%2Fgerrit%2Fserver%2Fproject%2FRefControl.java/safe_content?suffix=new HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: Content-Disposition:attachment; filename="RefControl_new-931cdb73ae9d97eb500a3533455b055d90b99944.java.zip"
+ Content-Type:application/zip
+
+ `[binary ZIP archive containing a single file, "RefControl_new-cb218df1337df48a0e7ab30a49a8067ac7321881.java"]`
+----
+
[[get-diff]]
=== Get Diff
--
diff --git a/Documentation/user-named-destinations.txt b/Documentation/user-named-destinations.txt
new file mode 100644
index 0000000..1b6f143
--- /dev/null
+++ b/Documentation/user-named-destinations.txt
@@ -0,0 +1,32 @@
+= Gerrit Code Review - Named Destinations
+
+[[user-named-destinations]]
+== User Named Destinations
+It is possible to define named destination sets on a user level.
+To do this, define the named destination sets in files named after
+each destination set in the `destinations` directory of the user's
+account ref in the `All-Users` project. The user's account ref is
+based on the user's account id which is an integer. The account
+refs are sharded by the last two digits (`+nn+`) in the refname,
+leading to refs of the format `+refs/users/nn/accountid+`. The
+user's destination files are a 2 column tab delimited file. Each
+row in a destination file represents a single destination in the
+named set. The left column represents the ref of the destination,
+and the right column represents the project of the destination.
+
+Example destination file named `destinations/myreviews`:
+
+----
+# Ref Project
+#
+refs/heads/master gerrit
+refs/heads/stable-2.11 gerrit
+refs/heads/master plugins/cookbook-plugin
+----
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index 1015e0f..80b14eb 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -53,18 +53,6 @@
+
The change has all necessary approvals and may be submitted.
-- [[submitted-merge-pending]]`Submitted, Merge Pending`:
-+
-The change was submitted and was added to the merge queue.
-+
-The change stays in the merge queue if it depends on a change that is
-still in review. In this case it will get automatically merged when all
-dependency changes have been merged.
-+
-This status can also mean that the change depends on an abandoned
-change or on an outdated patch set of another change. In this case you
-may want to rebase the change.
-
- [[merged]]`Merged`:
+
The change was successfully merged into the destination branch.
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index bb60a7e..adf77cf 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -88,6 +88,12 @@
as a legacy numerical 'ID' such as 15183, or a newer style Change-Id
that was scraped out of the commit message.
+[[destination]]
+destination:'NAME'::
++
+Changes which match the current user's destination named 'NAME'.
+(see link:user-named-destinations.html[Named Destinations]).
+
[[owner]]
owner:'USER', o:'USER'::
+
@@ -283,7 +289,7 @@
is:open, is:pending::
+
-True if the change is either open or submitted, merge pending.
+True if the change is open.
is:draft::
+
@@ -306,8 +312,7 @@
[[status]]
status:open, status:pending::
+
-True if the change state is either 'review in progress' or 'submitted,
-merge pending'.
+True if the change state is 'review in progress'.
status:reviewed::
+
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
index d978c70..43ad799 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
@@ -23,6 +23,7 @@
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestScopePropagator;
+import com.google.gerrit.testutil.DisabledReviewDb;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Key;
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
index 69a09f0..52cddf2 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_GPGKEY;
+import com.google.common.base.MoreObjects;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Ordering;
import com.google.gerrit.common.PageLinks;
@@ -35,6 +36,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -78,8 +80,7 @@
while (userIds.hasNext()) {
String userId = userIds.next();
if (isAllowed(userId, allowedUserIds)) {
- @SuppressWarnings("unchecked")
- Iterator<PGPSignature> sigs = key.getSignaturesForID(userId);
+ Iterator<PGPSignature> sigs = getSignaturesForId(key, userId);
while (sigs.hasNext()) {
if (isValidCertification(key, sigs.next(), userId)) {
return;
@@ -96,6 +97,14 @@
}
}
+ @SuppressWarnings("unchecked")
+ private Iterator<PGPSignature> getSignaturesForId(PGPPublicKey key,
+ String userId) {
+ return MoreObjects.firstNonNull(
+ key.getSignaturesForID(userId),
+ Collections.emptyIterator());
+ }
+
private Set<String> getAllowedUserIds() {
IdentifiedUser user = userProvider.get();
Set<String> result = new HashSet<>();
diff --git a/gerrit-gwtui-common/BUCK b/gerrit-gwtui-common/BUCK
index 3977487..2a79db4 100644
--- a/gerrit-gwtui-common/BUCK
+++ b/gerrit-gwtui-common/BUCK
@@ -22,12 +22,6 @@
java_library(
name = 'client-lib',
- exported_deps = [':client-lib2'],
- visibility = ['PUBLIC'],
-)
-
-java_library(
- name = 'client-lib2',
srcs = glob(['src/main/**/*.java']),
resources = glob(['src/main/**/*']),
exported_deps = EXPORTED_DEPS,
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java
index 5db620c..0250939 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java
@@ -201,6 +201,10 @@
this.auth = auth;
}
+ public long getExpiresAt() {
+ return expiresAt;
+ }
+
Account.Id getAccountId() {
return accountId;
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index 5028e4d3..9f94dbf 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -353,7 +353,7 @@
private ThreadPool threadPool(Config cfg) {
int maxThreads = cfg.getInt("httpd", null, "maxthreads", 25);
int minThreads = cfg.getInt("httpd", null, "minthreads", 5);
- int maxQueued = cfg.getInt("httpd", null, "maxqueued", 50);
+ int maxQueued = cfg.getInt("httpd", null, "maxqueued", 200);
int idleTimeout = (int)MILLISECONDS.convert(60, SECONDS);
int maxCapacity = maxQueued == 0
? Integer.MAX_VALUE
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
index 1632c82..1c70468 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -215,12 +215,11 @@
if (secureStoreInitData != null && currentSecureStoreClassName != null
&& !currentSecureStoreClassName.equals(secureStoreInitData.className)) {
- String err =
- String.format(
- "Different secure store was previously configured: %s. "
- + "Use SwitchSecureStore program to switch between implementations.",
- currentSecureStoreClassName);
- die(err, new RuntimeException("secure store mismatch"));
+ String err = String.format(
+ "Different secure store was previously configured: %s. "
+ + "Use SwitchSecureStore program to switch between implementations.",
+ currentSecureStoreClassName);
+ throw die(err);
}
m.add(new InitModule(standalone, initDb));
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/rules/PrologCompiler.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/rules/PrologCompiler.java
index 31c3be1..064cc19 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/rules/PrologCompiler.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/rules/PrologCompiler.java
@@ -262,14 +262,16 @@
}
}
- private List<File> getAllFiles(File dir, String extension) {
+ private List<File> getAllFiles(File dir, String extension)
+ throws IOException {
ArrayList<File> fileList = new ArrayList<>();
getAllFiles(dir, extension, fileList);
return fileList;
}
- private void getAllFiles(File dir, String extension, List<File> fileList) {
- for (File f : dir.listFiles()) {
+ private void getAllFiles(File dir, String extension, List<File> fileList)
+ throws IOException {
+ for (File f : listFiles(dir)) {
if (f.getName().endsWith(extension)) {
fileList.add(f);
}
@@ -279,14 +281,16 @@
}
}
- private List<String> getRelativePaths(File dir, String extension) {
+ private List<String> getRelativePaths(File dir, String extension)
+ throws IOException {
ArrayList<String> pathList = new ArrayList<>();
getRelativePaths(dir, extension, "", pathList);
return pathList;
}
- private void getRelativePaths(File dir, String extension, String path, List<String> pathList) {
- for (File f : dir.listFiles()) {
+ private static void getRelativePaths(File dir, String extension, String path,
+ List<String> pathList) throws IOException {
+ for (File f : listFiles(dir)) {
if (f.getName().endsWith(extension)) {
pathList.add(path + f.getName());
}
@@ -296,8 +300,8 @@
}
}
- private void deleteAllFiles(File dir) {
- for (File f : dir.listFiles()) {
+ private static void deleteAllFiles(File dir) throws IOException {
+ for (File f : listFiles(dir)) {
if (f.isDirectory()) {
deleteAllFiles(f);
} else {
@@ -306,4 +310,12 @@
}
dir.delete();
}
+
+ private static File[] listFiles(File dir) throws IOException {
+ File[] files = dir.listFiles();
+ if (files == null) {
+ throw new IOException("Failed to list directory: " + dir);
+ }
+ return files;
+ }
}
diff --git a/gerrit-plugin-gwtui/BUCK b/gerrit-plugin-gwtui/BUCK
index 132ade5..ec5903a 100644
--- a/gerrit-plugin-gwtui/BUCK
+++ b/gerrit-plugin-gwtui/BUCK
@@ -16,15 +16,9 @@
java_library(
name = 'gwtui-api-lib',
- exported_deps = [':gwtui-api-lib2'],
- visibility = ['PUBLIC'],
-)
-
-java_library(
- name = 'gwtui-api-lib2',
srcs = SRCS,
resources = glob(['src/main/**/*']),
- exported_deps = ['//gerrit-gwtui-common:client-lib2'],
+ exported_deps = ['//gerrit-gwtui-common:client-lib'],
provided_deps = DEPS + ['//lib/gwt:dev'],
visibility = ['PUBLIC'],
)
@@ -62,7 +56,7 @@
'//lib:gwtjsonrpc',
'//lib:gwtorm_client',
'//lib/gwt:dev__jar',
- '//gerrit-gwtui-common:client-lib2',
+ '//gerrit-gwtui-common:client-lib',
'//gerrit-common:client',
'//gerrit-reviewdb:client',
],
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAccountDestinations.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAccountDestinations.java
new file mode 100644
index 0000000..d928bec
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAccountDestinations.java
@@ -0,0 +1,80 @@
+// Copyright (C) 2015 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.account;
+
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.git.DestinationList;
+import com.google.gerrit.server.git.ValidationError;
+import com.google.gerrit.server.git.VersionedMetaData;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.FileMode;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+/** Preferences for user accounts. */
+public class VersionedAccountDestinations extends VersionedMetaData {
+ private static final Logger log = LoggerFactory.getLogger(VersionedAccountDestinations.class);
+
+ public static VersionedAccountDestinations forUser(Account.Id id) {
+ return new VersionedAccountDestinations(RefNames.refsUsers(id));
+ }
+
+ private final String ref;
+ private final DestinationList destinations = new DestinationList();
+
+ private VersionedAccountDestinations(String ref) {
+ this.ref = ref;
+ }
+
+ @Override
+ protected String getRefName() {
+ return ref;
+ }
+
+ public DestinationList getDestinationList() {
+ return destinations;
+ }
+
+ @Override
+ protected void onLoad() throws IOException, ConfigInvalidException {
+ String prefix = DestinationList.DIR_NAME + "/";
+ for (PathInfo p : getPathInfos(true)) {
+ if (p.fileMode == FileMode.REGULAR_FILE) {
+ String path = p.path;
+ if (path.startsWith(prefix)) {
+ String label = path.substring(prefix.length());
+ ValidationError.Sink errors = destinations.createLoggerSink(path, log);
+ destinations.parseLabel(label, readUTF8(path), errors);
+ }
+ }
+ }
+ }
+
+ public ValidationError.Sink createSink(String file) {
+ return ValidationError.createLoggerSink(file, log);
+ }
+
+ @Override
+ protected boolean onSave(CommitBuilder commit) throws IOException,
+ ConfigInvalidException {
+ throw new UnsupportedOperationException("Cannot yet save destinations");
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DownloadContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DownloadContent.java
new file mode 100644
index 0000000..733ff0b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DownloadContent.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.kohsuke.args4j.Option;
+
+import java.io.IOException;
+
+public class DownloadContent implements RestReadView<FileResource> {
+ private final FileContentUtil fileContentUtil;
+
+ @Option(name = "--suffix")
+ private String suffix;
+
+ @Inject
+ DownloadContent(FileContentUtil fileContentUtil) {
+ this.fileContentUtil = fileContentUtil;
+ }
+
+ @Override
+ public BinaryResult apply(FileResource rsrc)
+ throws ResourceNotFoundException, IOException, NoSuchChangeException,
+ OrmException {
+ String path = rsrc.getPatchKey().get();
+ ProjectState projectState =
+ rsrc.getRevision().getControl().getProjectControl().getProjectState();
+ ObjectId revstr = ObjectId.fromString(
+ rsrc.getRevision().getPatchSet().getRevision().get());
+ return fileContentUtil.downloadContent(projectState, revstr, path, suffix);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
index 4336058..3027fd0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
@@ -16,6 +16,12 @@
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Strings;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.PatchScript.FileMode;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -26,8 +32,11 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import eu.medsea.mimeutil.MimeType;
+
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
@@ -35,9 +44,13 @@
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.NB;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.Random;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
@Singleton
public class FileContentUtil {
@@ -45,6 +58,8 @@
private static final String X_GIT_SYMLINK = "x-git/symlink";
private static final String X_GIT_GITLINK = "x-git/gitlink";
private static final int MAX_SIZE = 5 << 20;
+ private static final String ZIP_TYPE = "application/zip";
+ private static final Random rng = new Random();
private final GitRepositoryManager repoManager;
private final FileTypeRegistry registry;
@@ -75,7 +90,7 @@
.base64();
}
- final ObjectLoader obj = repo.open(id, OBJ_BLOB);
+ ObjectLoader obj = repo.open(id, OBJ_BLOB);
byte[] raw;
try {
raw = obj.getCachedBytes(MAX_SIZE);
@@ -110,6 +125,133 @@
return result;
}
+ public BinaryResult downloadContent(ProjectState project, ObjectId revstr,
+ String path, @Nullable String suffix)
+ throws ResourceNotFoundException, IOException {
+ suffix = Strings.emptyToNull(CharMatcher.inRange('a', 'z')
+ .retainFrom(Strings.nullToEmpty(suffix)));
+
+ try (Repository repo = openRepository(project);
+ RevWalk rw = new RevWalk(repo)) {
+ RevCommit commit = rw.parseCommit(revstr);
+ ObjectReader reader = rw.getObjectReader();
+ TreeWalk tw = TreeWalk.forPath(reader, path, commit.getTree());
+ if (tw == null) {
+ throw new ResourceNotFoundException();
+ }
+
+ int mode = tw.getFileMode(0).getObjectType();
+ if (mode != Constants.OBJ_BLOB) {
+ throw new ResourceNotFoundException();
+ }
+
+ ObjectId id = tw.getObjectId(0);
+ ObjectLoader obj = repo.open(id, OBJ_BLOB);
+ byte[] raw;
+ try {
+ raw = obj.getCachedBytes(MAX_SIZE);
+ } catch (LargeObjectException e) {
+ raw = null;
+ }
+
+ MimeType contentType = registry.getMimeType(path, raw);
+ return registry.isSafeInline(contentType)
+ ? wrapBlob(path, obj, raw, contentType, suffix)
+ : zipBlob(path, obj, commit, suffix);
+ }
+ }
+
+ private BinaryResult wrapBlob(String path, final ObjectLoader obj, byte[] raw,
+ MimeType contentType, @Nullable String suffix) {
+ return asBinaryResult(raw, obj)
+ .setContentType(contentType.toString())
+ .setAttachmentName(safeFileName(path, suffix));
+ }
+
+ @SuppressWarnings("resource")
+ private BinaryResult zipBlob(final String path, final ObjectLoader obj,
+ RevCommit commit, final @Nullable String suffix) {
+ final String commitName = commit.getName();
+ final long when = commit.getCommitTime() * 1000L;
+ return new BinaryResult() {
+ @Override
+ public void writeTo(OutputStream os) throws IOException {
+ try (ZipOutputStream zipOut = new ZipOutputStream(os)) {
+ String decoration = randSuffix();
+ if (!Strings.isNullOrEmpty(suffix)) {
+ decoration = suffix + '-' + decoration;
+ }
+ ZipEntry e = new ZipEntry(safeFileName(path, decoration));
+ e.setComment(commitName + ":" + path);
+ e.setSize(obj.getSize());
+ e.setTime(when);
+ zipOut.putNextEntry(e);
+ obj.copyTo(zipOut);
+ zipOut.closeEntry();
+ }
+ }
+ }.setContentType(ZIP_TYPE)
+ .setAttachmentName(safeFileName(path, suffix) + ".zip")
+ .disableGzip();
+ }
+
+ private static String safeFileName(String fileName, @Nullable String suffix) {
+ // Convert a file path (e.g. "src/Init.c") to a safe file name with
+ // no meta-characters that might be unsafe on any given platform.
+ //
+ int slash = fileName.lastIndexOf('/');
+ if (slash >= 0) {
+ fileName = fileName.substring(slash + 1);
+ }
+
+ StringBuilder r = new StringBuilder(fileName.length());
+ for (int i = 0; i < fileName.length(); i++) {
+ final char c = fileName.charAt(i);
+ if (c == '_' || c == '-' || c == '.' || c == '@') {
+ r.append(c);
+ } else if ('0' <= c && c <= '9') {
+ r.append(c);
+ } else if ('A' <= c && c <= 'Z') {
+ r.append(c);
+ } else if ('a' <= c && c <= 'z') {
+ r.append(c);
+ } else if (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
+ r.append('-');
+ } else {
+ r.append('_');
+ }
+ }
+ fileName = r.toString();
+
+ int ext = fileName.lastIndexOf('.');
+ if (suffix == null) {
+ return fileName;
+ } else if (ext <= 0) {
+ return fileName + "_" + suffix;
+ } else {
+ return fileName.substring(0, ext) + "_" + suffix
+ + fileName.substring(ext);
+ }
+ }
+
+ private static String randSuffix() {
+ // Produce a random suffix that is difficult (or nearly impossible)
+ // for an attacker to guess in advance. This reduces the risk that
+ // an attacker could upload a *.class file and have us send a ZIP
+ // that can be invoked through an applet tag in the victim's browser.
+ //
+ Hasher h = Hashing.md5().newHasher();
+ byte[] buf = new byte[8];
+
+ NB.encodeInt64(buf, 0, TimeUtil.nowMs());
+ h.putBytes(buf);
+
+ rng.nextBytes(buf);
+ h.putBytes(buf);
+
+ return h.hash().toString();
+ }
+
public static String resolveContentType(ProjectState project, String path,
FileMode fileMode, String mimeType) {
switch (fileMode) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
index aeed0a6..a502a7d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
@@ -19,13 +19,12 @@
import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.restapi.ETagView;
import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.ChangeSet;
import com.google.gerrit.server.git.MergeSuperSet;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.OrmRuntimeException;
import com.google.inject.Inject;
@@ -69,11 +68,9 @@
rsrc.getChangeResource().prepareETag(h, user);
h.putBoolean(Submit.wholeTopicEnabled(config));
ReviewDb db = dbProvider.get();
- ChangeSet cs = mergeSuperSet.completeChangeSet(db,
- ChangeSet.create(rsrc.getChange()));
- ProjectControl ctl = rsrc.getControl().getProjectControl();
- for (Change c : cs.changes()) {
- new ChangeResource(ctl.controlFor(c)).prepareETag(h, user);
+ ChangeSet cs = mergeSuperSet.completeChangeSet(db, rsrc.getChange());
+ for (ChangeData cd : cs.changes()) {
+ new ChangeResource(cd.changeControl()).prepareETag(h, user);
}
} catch (IOException | OrmException e) {
throw new OrmRuntimeException(e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index ed2b835..99f8ba6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -105,6 +105,7 @@
put(FILE_KIND, "reviewed").to(PutReviewed.class);
delete(FILE_KIND, "reviewed").to(DeleteReviewed.class);
get(FILE_KIND, "content").to(GetContent.class);
+ get(FILE_KIND, "download").to(DownloadContent.class);
get(FILE_KIND, "diff").to(GetDiff.class);
child(CHANGE_KIND, "edit").to(ChangeEdits.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java
index 093506c..29fa0cf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.change;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import com.google.common.base.Splitter;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
@@ -41,6 +43,7 @@
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
+import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
@@ -124,8 +127,8 @@
for (ScoreDoc h : hits) {
Document doc = searcher.doc(h.doc);
- AccountInfo info = new AccountInfo(
- doc.getField(ID).numericValue().intValue());
+ IndexableField idField = checkNotNull(doc.getField(ID));
+ AccountInfo info = new AccountInfo(idField.numericValue().intValue());
info.name = doc.get(NAME);
info.email = doc.get(EMAIL);
info.username = doc.get(USERNAME);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index c6048bb..ec8d70d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -182,11 +182,9 @@
rsrc.getPatchSet().getRevision().get()));
}
- ChangeSet submittedChanges = ChangeSet.create(change);
-
try {
ReviewDb db = dbProvider.get();
- mergeOpProvider.get().merge(db, submittedChanges, caller, true);
+ mergeOpProvider.get().merge(db, change, caller, true);
change = db.changes().get(change.getId());
} catch (NoSuchChangeException e) {
throw new OrmException("Submission failed", e);
@@ -306,8 +304,7 @@
ChangeSet cs;
try {
- cs = mergeSuperSet.completeChangeSet(db,
- ChangeSet.create(cd.change()));
+ cs = mergeSuperSet.completeChangeSet(db, cd.change());
} catch (OrmException | IOException e) {
throw new OrmRuntimeException("Could not determine complete set of " +
"changes to be submitted", e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
index 1f6dfe4..a4f9fef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
@@ -60,8 +60,8 @@
ResourceConflictException, Exception {
try {
ChangeSet cs = mergeSuperSet.completeChangeSet(dbProvider.get(),
- ChangeSet.create(resource.getChange()));
- if (cs.ids().size() > 1) {
+ resource.getChange());
+ if (cs.size() > 1) {
return json.create(EnumSet.of(
ListChangesOption.CURRENT_REVISION,
ListChangesOption.CURRENT_COMMIT))
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index 5aef5bd..38d3188 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -235,6 +235,12 @@
for (PatchSet p :
db.patchSets().byRevision(a.getAncestorRevision())) {
Change c = db.changes().get(p.getId().getParentKey());
+ if (c == null) {
+ log.error("Error while generating the ancestor change for"
+ + " revision " + a.getAncestorRevision() + ": Cannot find"
+ + " Change entry in database for " + p.getId().getParentKey());
+ continue;
+ }
ca.dependsOn.add(newDependsOn(c, p));
}
}
@@ -255,6 +261,12 @@
continue;
}
final Change c = db.changes().get(p.getId().getParentKey());
+ if (c == null) {
+ log.error("Error while generating the list of descendants for"
+ + " revision " + revId.get() + ": Cannot find Change entry in"
+ + " database for " + p.getId().getParentKey());
+ continue;
+ }
ca.neededBy.add(newNeededBy(c, p));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
index 531db79..d4b0c4b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
@@ -14,70 +14,103 @@
package com.google.gerrit.server.git;
-import com.google.auto.value.AutoValue;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.SetMultimap;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.OrmException;
-/** A set of changes grouped together to be submitted atomically.*/
-@AutoValue
-public abstract class ChangeSet {
- public static ChangeSet create(Iterable<Change> changes) {
- ImmutableSet.Builder<Project.NameKey> pb = ImmutableSet.builder();
- ImmutableSet.Builder<Branch.NameKey> bb = ImmutableSet.builder();
- ImmutableSet.Builder<Change.Id> ib = ImmutableSet.builder();
- ImmutableSet.Builder<PatchSet.Id> psb = ImmutableSet.builder();
- ImmutableSetMultimap.Builder<Project.NameKey, Branch.NameKey> pbb =
- ImmutableSetMultimap.builder();
- ImmutableSetMultimap.Builder<Project.NameKey, Change.Id> pcb =
- ImmutableSetMultimap.builder();
- ImmutableSetMultimap.Builder<Branch.NameKey, Change.Id> cbb =
- ImmutableSetMultimap.builder();
- ImmutableSet.Builder<Change> cb = ImmutableSet.builder();
+import java.util.HashSet;
+import java.util.Set;
- for (Change c : changes) {
- Branch.NameKey branch = c.getDest();
- Project.NameKey project = branch.getParentKey();
- pb.add(project);
- bb.add(branch);
- ib.add(c.getId());
- psb.add(c.currentPatchSetId());
- pbb.put(project, branch);
- pcb.put(project, c.getId());
- cbb.put(branch, c.getId());
- cb.add(c);
+/**
+ * A set of changes grouped together to be submitted atomically.
+ * <p>
+ * This class is not thread safe.
+ */
+public class ChangeSet {
+ private final ImmutableCollection<ChangeData> changeData;
+
+ public ChangeSet(Iterable<ChangeData> changes) {
+ Set<Change.Id> ids = new HashSet<>();
+ ImmutableSet.Builder<ChangeData> cdb = ImmutableSet.builder();
+ for (ChangeData cd : changes) {
+ if (ids.add(cd.getId())) {
+ cdb.add(cd);
+ }
}
-
- return new AutoValue_ChangeSet(pb.build(), bb.build(), ib.build(),
- psb.build(), pbb.build(), pcb.build(), cbb.build(), cb.build());
+ changeData = cdb.build();
}
- public static ChangeSet create(Change change) {
- return create(ImmutableList.of(change));
+ public ChangeSet(ChangeData change) {
+ this(ImmutableList.of(change));
}
- public abstract ImmutableSet<Project.NameKey> projects();
- public abstract ImmutableSet<Branch.NameKey> branches();
- public abstract ImmutableSet<Change.Id> ids();
- public abstract ImmutableSet<PatchSet.Id> patchIds();
- public abstract ImmutableSetMultimap<Project.NameKey, Branch.NameKey>
- branchesByProject();
- public abstract ImmutableSetMultimap<Project.NameKey, Change.Id>
- changesByProject();
- public abstract ImmutableSetMultimap<Branch.NameKey, Change.Id>
- changesByBranch();
- public abstract ImmutableSet<Change> changes();
+ public ImmutableSet<Change.Id> ids() {
+ ImmutableSet.Builder<Change.Id> ret = ImmutableSet.builder();
+ for (ChangeData cd : changeData) {
+ ret.add(cd.getId());
+ }
+ return ret.build();
+ }
- @Override
- public int hashCode() {
- return ids().hashCode();
+ public Set<PatchSet.Id> patchIds() throws OrmException {
+ Set<PatchSet.Id> ret = new HashSet<>();
+ for (ChangeData cd : changeData) {
+ ret.add(cd.change().currentPatchSetId());
+ }
+ return ret;
+ }
+
+ public SetMultimap<Project.NameKey, Branch.NameKey> branchesByProject()
+ throws OrmException {
+ SetMultimap<Project.NameKey, Branch.NameKey> ret =
+ HashMultimap.create();
+ for (ChangeData cd : changeData) {
+ ret.put(cd.change().getProject(), cd.change().getDest());
+ }
+ return ret;
+ }
+
+ public Multimap<Project.NameKey, Change.Id> changesByProject()
+ throws OrmException {
+ ListMultimap<Project.NameKey, Change.Id> ret =
+ ArrayListMultimap.create();
+ for (ChangeData cd : changeData) {
+ ret.put(cd.change().getProject(), cd.getId());
+ }
+ return ret;
+ }
+
+ public Multimap<Branch.NameKey, Change.Id> changesByBranch()
+ throws OrmException {
+ ListMultimap<Branch.NameKey, Change.Id> ret =
+ ArrayListMultimap.create();
+ for (ChangeData cd : changeData) {
+ ret.put(cd.change().getDest(), cd.getId());
+ }
+ return ret;
+ }
+
+ public ImmutableCollection<ChangeData> changes() {
+ return changeData;
}
public int size() {
- return ids().size();
+ return changeData.size();
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + ids();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/DestinationList.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/DestinationList.java
new file mode 100644
index 0000000..ca1f705c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/DestinationList.java
@@ -0,0 +1,61 @@
+// Copyright (C) 2015 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.git;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Project;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+public class DestinationList extends TabFile {
+ public static final String DIR_NAME = "destinations";
+ private SetMultimap<String, Branch.NameKey> destinations = HashMultimap.create();
+
+ public Set<Branch.NameKey> getDestinations(String label) {
+ return destinations.get(label);
+ }
+
+ public void parseLabel(String label, String text,
+ ValidationError.Sink errors) throws IOException {
+ destinations.replaceValues(label,
+ toSet(parse(text, DIR_NAME + label, TRIM, null, errors)));
+ }
+
+ public String asText(String label) {
+ Set<Branch.NameKey> dests = destinations.get(label);
+ if (dests == null) {
+ return null;
+ }
+ List<Row> rows = Lists.newArrayListWithCapacity(dests.size());
+ for (Branch.NameKey dest : sort(dests)) {
+ rows.add(new Row(dest.get(), dest.getParentKey().get()));
+ }
+ return asText("Ref", "Project", rows);
+ }
+
+ protected static Set<Branch.NameKey> toSet(List<Row> destRows) {
+ Set<Branch.NameKey> dests = Sets.newHashSetWithExpectedSize(destRows.size());
+ for(Row row : destRows) {
+ dests.add(new Branch.NameKey(new Project.NameKey(row.right), row.left));
+ }
+ return dests;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupList.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupList.java
index d07572b..1477f6a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupList.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupList.java
@@ -36,7 +36,7 @@
public static GroupList parse(String text, ValidationError.Sink errors)
throws IOException {
- List<Row> rows = parse(text, FILE_NAME, errors);
+ List<Row> rows = parse(text, FILE_NAME, TRIM, TRIM, errors);
Map<AccountGroup.UUID, GroupReference> groupsByUUID =
new HashMap<>(rows.size());
for(Row row : rows) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index d945f77..3bfa47e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -25,6 +25,7 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
import com.google.common.collect.Table;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.TimeUtil;
@@ -331,14 +332,14 @@
}
}
- public void merge(ReviewDb db, ChangeSet changes, IdentifiedUser caller,
+ public void merge(ReviewDb db, Change change, IdentifiedUser caller,
boolean checkSubmitRules) throws NoSuchChangeException,
OrmException, ResourceConflictException {
- logPrefix = String.format("[%s]: ", String.valueOf(changes.hashCode()));
+ logPrefix = String.format("[%s]: ", String.valueOf(change.hashCode()));
this.db = db;
- logDebug("Beginning merge of {}", changes);
+ logDebug("Beginning integration of {}", change);
try {
- ChangeSet cs = mergeSuperSet.completeChangeSet(db, changes);
+ ChangeSet cs = mergeSuperSet.completeChangeSet(db, change);
logDebug("Calculated to merge {}", cs);
if (checkSubmitRules) {
logDebug("Checking submit rules and state");
@@ -361,15 +362,17 @@
logDebug("Beginning merge attempt on {}", cs);
Map<Branch.NameKey, ListMultimap<SubmitType, ChangeData>> toSubmit =
new HashMap<>();
+ logDebug("Perform the merges");
try {
- logDebug("Perform the merges");
- for (Project.NameKey project : cs.projects()) {
+ Multimap<Project.NameKey, Branch.NameKey> br = cs.branchesByProject();
+ Multimap<Branch.NameKey, Change.Id> cbb = cs.changesByBranch();
+ for (Project.NameKey project : br.keySet()) {
openRepository(project);
- for (Branch.NameKey branch : cs.branchesByProject().get(project)) {
+ for (Branch.NameKey branch : br.get(project)) {
setDestProject(branch);
List<ChangeData> cds = new ArrayList<>();
- for (Change.Id id : cs.changesByBranch().get(branch)) {
+ for (Change.Id id : cbb.get(branch)) {
cds.add(changeDataFactory.create(db, id));
}
ListMultimap<SubmitType, ChangeData> submitting =
@@ -393,9 +396,9 @@
}
logDebug("Write out the new branch tips");
SubmoduleOp subOp = subOpProvider.get();
- for (Project.NameKey project : cs.projects()) {
+ for (Project.NameKey project : br.keySet()) {
openRepository(project);
- for (Branch.NameKey branch : cs.branchesByProject().get(project)) {
+ for (Branch.NameKey branch : br.get(project)) {
RefUpdate update = updateBranch(branch);
pendingRefUpdates.remove(branch);
@@ -413,7 +416,8 @@
}
closeRepository();
}
- updateSuperProjects(subOp, cs.branches());
+
+ updateSuperProjects(subOp, br.values());
checkState(pendingRefUpdates.isEmpty(), "programmer error: "
+ "pending ref update list not emptied");
} catch (NoSuchProjectException noProject) {
@@ -501,7 +505,7 @@
if (branchUpdate.getOldObjectId() != null) {
branchTip =
(CodeReviewCommit) rw.parseCommit(branchUpdate.getOldObjectId());
- } else if (repo.getFullBranch().equals(destBranch.get())) {
+ } else if (Objects.equals(repo.getFullBranch(), destBranch.get())) {
branchTip = null;
branchUpdate.setExpectedOldObjectId(ObjectId.zeroId());
} else {
@@ -698,8 +702,12 @@
CodeReviewCommit currentTip =
mergeTip != null ? mergeTip.getCurrentTip() : null;
if (Objects.equals(branchTip, currentTip)) {
- logDebug("Branch already at merge tip {}, no update to perform",
- currentTip.name());
+ if (currentTip != null) {
+ logDebug("Branch already at merge tip {}, no update to perform",
+ currentTip.name());
+ } else {
+ logDebug("Both branch and merge tip are nonexistent, no update");
+ }
return null;
} else if (currentTip == null) {
logDebug("No merge tip, no update to perform");
@@ -906,7 +914,7 @@
}
private void updateSuperProjects(SubmoduleOp subOp,
- Set<Branch.NameKey> branches) {
+ Collection<Branch.NameKey> branches) {
logDebug("Updating superprojects");
try {
subOp.updateSuperProjects(db, branches);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java
index 804417f..3add138 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.git;
import com.google.common.base.Strings;
+import com.google.common.collect.Multimap;
import com.google.gerrit.common.data.SubmitTypeRecord;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.reviewdb.client.Branch;
@@ -80,25 +81,27 @@
this.repoManager = repoManager;
}
- public ChangeSet completeChangeSet(ReviewDb db, ChangeSet changes)
+ public ChangeSet completeChangeSet(ReviewDb db, Change change)
throws MissingObjectException, IncorrectObjectTypeException, IOException,
OrmException {
+ ChangeData cd = changeDataFactory.create(db, change.getId());
if (Submit.wholeTopicEnabled(cfg)) {
- return completeChangeSetIncludingTopics(db, changes);
+ return completeChangeSetIncludingTopics(db, new ChangeSet(cd));
} else {
- return completeChangeSetWithoutTopic(db, changes);
+ return completeChangeSetWithoutTopic(db, new ChangeSet(cd));
}
}
private ChangeSet completeChangeSetWithoutTopic(ReviewDb db, ChangeSet changes)
throws MissingObjectException, IncorrectObjectTypeException, IOException,
OrmException {
- List<Change> ret = new ArrayList<>();
+ List<ChangeData> ret = new ArrayList<>();
- for (Project.NameKey project : changes.projects()) {
+ Multimap<Project.NameKey, Change.Id> pc = changes.changesByProject();
+ for (Project.NameKey project : pc.keySet()) {
try (Repository repo = repoManager.openRepository(project);
RevWalk rw = CodeReviewCommit.newRevWalk(repo)) {
- for (Change.Id cId : changes.changesByProject().get(project)) {
+ for (Change.Id cId : pc.get(project)) {
ChangeData cd = changeDataFactory.create(db, cId);
SubmitTypeRecord r = new SubmitRuleEvaluator(cd).getSubmitType();
@@ -106,7 +109,7 @@
logErrorAndThrow("Failed to get submit type for " + cd.getId());
}
if (r.type == SubmitType.CHERRY_PICK) {
- ret.add(cd.change());
+ ret.add(cd);
continue;
}
@@ -137,17 +140,15 @@
// Merged changes are ok to exclude
Iterable<ChangeData> destChanges = queryProvider.get()
.byCommitsOnBranchNotMerged(cd.change().getDest(), hashes);
-
for (ChangeData chd : destChanges) {
- Change chg = chd.change();
- ret.add(chg);
+ ret.add(chd);
}
}
}
}
}
- return ChangeSet.create(ret);
+ return new ChangeSet(ret);
}
private ChangeSet completeChangeSetIncludingTopics(
@@ -157,24 +158,18 @@
boolean done = false;
ChangeSet newCs = completeChangeSetWithoutTopic(db, changes);
while (!done) {
- List<Change> chgs = new ArrayList<>();
+ List<ChangeData> chgs = new ArrayList<>();
done = true;
- for (Change.Id cId : newCs.ids()) {
- // TODO(sbeller): Cache the change data here and in completeChangeSet
- // There is no need to reread it a few times.
- ChangeData cd = changeDataFactory.create(db, cId);
- chgs.add(cd.change());
-
+ for (ChangeData cd : newCs.changes()) {
+ chgs.add(cd);
String topic = cd.change().getTopic();
if (!Strings.isNullOrEmpty(topic) && !topicsTraversed.contains(topic)) {
- for (ChangeData addCd : queryProvider.get().byTopicOpen(topic)) {
- chgs.add(addCd.change());
- }
+ chgs.addAll(queryProvider.get().byTopicOpen(topic));
done = false;
topicsTraversed.add(topic);
}
}
- changes = ChangeSet.create(chgs);
+ changes = new ChangeSet(chgs);
newCs = completeChangeSetWithoutTopic(db, changes);
}
return newCs;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/QueryList.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/QueryList.java
index 0df866d..dffb18a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/QueryList.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/QueryList.java
@@ -28,7 +28,7 @@
public static QueryList parse(String text, ValidationError.Sink errors)
throws IOException {
- return new QueryList(parse(text, FILE_NAME, errors));
+ return new QueryList(parse(text, FILE_NAME, TRIM, TRIM, errors));
}
public String getQuery(String name) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 0ae77b0..9e5353a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -1787,31 +1787,28 @@
throws OrmException, ResourceConflictException {
Submit submit = submitProvider.get();
RevisionResource rsrc = new RevisionResource(changes.parse(changeCtl), ps);
- List<Change> changes = Lists.newArrayList(rsrc.getChange());
try {
- mergeOpProvider.get().merge(db, ChangeSet.create(changes),
+ mergeOpProvider.get().merge(db, rsrc.getChange(),
(IdentifiedUser) changeCtl.getCurrentUser(), false);
} catch (NoSuchChangeException e) {
throw new OrmException(e);
}
addMessage("");
- for (Change c : changes) {
- c = db.changes().get(c.getId());
- switch (c.getStatus()) {
- case MERGED:
- addMessage("Change " + c.getChangeId() + " merged.");
+ Change c = db.changes().get(rsrc.getChange().getId());
+ switch (c.getStatus()) {
+ case MERGED:
+ addMessage("Change " + c.getChangeId() + " merged.");
+ break;
+ case NEW:
+ ChangeMessage msg = submit.getConflictMessage(rsrc);
+ if (msg != null) {
+ addMessage("Change " + c.getChangeId() + ": " + msg.getMessage());
break;
- case NEW:
- ChangeMessage msg = submit.getConflictMessage(rsrc);
- if (msg != null) {
- addMessage("Change " + c.getChangeId() + ": " + msg.getMessage());
- break;
- }
- //$FALL-THROUGH$
- default:
- addMessage("change " + c.getChangeId() + " is "
- + c.getStatus().name().toLowerCase());
- }
+ }
+ //$FALL-THROUGH$
+ default:
+ addMessage("change " + c.getChangeId() + " is "
+ + c.getStatus().name().toLowerCase());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
index 8fe8379..7951fd3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
@@ -121,6 +121,10 @@
RevWalk rw = new RevWalk(repo)) {
ObjectId id = repo.resolve(destBranch.get());
+ if (id == null) {
+ logAndThrowSubmoduleException(
+ "Cannot resolve submodule destination branch " + destBranch);
+ }
RevCommit commit = rw.parseCommit(id);
Set<SubmoduleSubscription> oldSubscriptions =
@@ -180,7 +184,7 @@
}
protected void updateSuperProjects(ReviewDb db,
- Set<Branch.NameKey> updatedBranches) throws SubmoduleException {
+ Collection<Branch.NameKey> updatedBranches) throws SubmoduleException {
try {
// These (repo/branch) will be updated later with all the given
// individual submodule subscriptions
@@ -253,7 +257,7 @@
}
DirCacheEntry dce = dc.getEntry(s.getPath());
- ObjectId oldId = null;
+ ObjectId oldId;
if (dce != null) {
if (!dce.getFileMode().equals(FileMode.GITLINK)) {
log.error("Requested to update gitlink " + s.getPath() + " in "
@@ -283,10 +287,7 @@
try {
rw.markStart(newCommit);
-
- if (oldId != null) {
- rw.markUninteresting(rw.parseCommit(oldId));
- }
+ rw.markUninteresting(rw.parseCommit(oldId));
for (RevCommit c : rw) {
msgbuf.append(c.getFullMessage() + "\n\n");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TabFile.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TabFile.java
index 13d2b1e..87f9a23 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TabFile.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TabFile.java
@@ -27,6 +27,17 @@
import java.util.Map;
public class TabFile {
+ public interface Parser {
+ public String parse(String str);
+ }
+
+ public static Parser TRIM = new Parser() {
+ public String parse(String str) {
+ return str.trim();
+ }
+ };
+
+
protected static class Row {
public String left;
public String right;
@@ -37,9 +48,9 @@
}
}
- protected static List<Row> parse(String text, String filename,
- ValidationError.Sink errors) throws IOException {
- List<Row> rows = new ArrayList<>();
+ protected static List<Row> parse(String text, String filename, Parser left,
+ Parser right, ValidationError.Sink errors) throws IOException {
+ List<Row> rows = new ArrayList<Row>();
BufferedReader br = new BufferedReader(new StringReader(text));
String s;
for (int lineNumber = 1; (s = br.readLine()) != null; lineNumber++) {
@@ -54,8 +65,15 @@
continue;
}
- rows.add(new Row(s.substring(0, tab).trim(),
- s.substring(tab + 1).trim()));
+ Row row = new Row(s.substring(0, tab), s.substring(tab + 1));
+ rows.add(row);
+
+ if (left != null) {
+ row.left = left.parse(row.left);
+ }
+ if (right != null) {
+ row.right = right.parse(row.right);
+ }
}
return rows;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
index 17f51ef..dfde5d5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.git;
import com.google.common.base.MoreObjects;
+import com.google.common.collect.Lists;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
@@ -50,6 +51,7 @@
import java.io.IOException;
import java.io.StringReader;
import java.util.Objects;
+import java.util.List;
/**
* Support for metadata stored within a version controlled branch.
@@ -59,6 +61,23 @@
* later be written back to the repository.
*/
public abstract class VersionedMetaData {
+ /**
+ * Path information that does not hold references to any repository
+ * data structures, allowing the application to retain this object
+ * for long periods of time.
+ */
+ public static class PathInfo {
+ public final FileMode fileMode;
+ public final String path;
+ public final ObjectId objectId;
+
+ protected PathInfo(TreeWalk tw) {
+ fileMode = tw.getFileMode(0);
+ path = tw.getPathString();
+ objectId = tw.getObjectId(0);
+ }
+ }
+
private RevCommit revision;
protected ObjectReader reader;
protected ObjectInserter inserter;
@@ -439,6 +458,17 @@
return null;
}
+ public List<PathInfo> getPathInfos(boolean recursive) throws IOException {
+ TreeWalk tw = new TreeWalk(reader);
+ tw.addTree(revision.getTree());
+ tw.setRecursive(recursive);
+ List<PathInfo> paths = Lists.newArrayList();
+ while (tw.next()) {
+ paths.add(new PathInfo(tw));
+ }
+ return paths;
+ }
+
protected static void set(Config rc, String section, String subsection,
String name, String value) {
if (value != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 88bb942..c6ffc27 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -23,6 +23,7 @@
import com.google.gerrit.common.errors.NotSignedInException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -34,6 +35,7 @@
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.account.VersionedAccountQueries;
+import com.google.gerrit.server.account.VersionedAccountDestinations;
import com.google.gerrit.server.change.ChangeTriplet;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
@@ -98,6 +100,7 @@
public static final String FIELD_CONFLICTS = "conflicts";
public static final String FIELD_DELETED = "deleted";
public static final String FIELD_DELTA = "delta";
+ public static final String FIELD_DESTINATION = "destination";
public static final String FIELD_DRAFTBY = "draftby";
public static final String FIELD_EDITBY = "editby";
public static final String FIELD_FILE = "file";
@@ -818,6 +821,28 @@
return IsReviewedPredicate.create(args.getSchema(), parseAccount(who));
}
+ @Operator
+ public Predicate<ChangeData> destination(String name)
+ throws QueryParseException {
+ AllUsersName allUsers = args.allUsersName.get();
+ try (Repository git = args.repoManager.openRepository(allUsers)) {
+ VersionedAccountDestinations d =
+ VersionedAccountDestinations.forUser(self());
+ d.load(git);
+ Set<Branch.NameKey> destinations =
+ d.getDestinationList().getDestinations(name);
+ if (destinations != null) {
+ return new DestinationPredicate(destinations, name);
+ }
+ } catch (RepositoryNotFoundException e) {
+ throw new QueryParseException("Unknown named destination (no " +
+ allUsers.get() +" repo): " + name, e);
+ } catch (IOException | ConfigInvalidException e) {
+ throw new QueryParseException("Error parsing named destination: " + name, e);
+ }
+ throw new QueryParseException("Unknown named destination: " + name);
+ }
+
@Override
protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
if (query.startsWith("refs/")) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
index 983e8ef..5184c53 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
@@ -28,7 +28,7 @@
static FieldDef<ChangeData, ?> commitField(Schema<ChangeData> schema,
String id) {
if (id.length() == OBJECT_ID_STRING_LENGTH
- && schema.hasField(EXACT_COMMIT)) {
+ && schema != null && schema.hasField(EXACT_COMMIT)) {
return EXACT_COMMIT;
}
return COMMIT;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DestinationPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DestinationPredicate.java
new file mode 100644
index 0000000..25fa09f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DestinationPredicate.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2015 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.query.change;
+
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.server.OrmException;
+
+import java.util.Set;
+
+class DestinationPredicate extends OperatorPredicate<ChangeData> {
+ Set<Branch.NameKey> destinations;
+
+ DestinationPredicate(Set<Branch.NameKey> destinations, String value) {
+ super(ChangeQueryBuilder.FIELD_DESTINATION, value);
+ this.destinations = destinations;
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ Change change = object.change();
+ if (change == null) {
+ return false;
+ }
+ return destinations.contains(change.getDest());
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index 56d26e5..10c3db3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -128,7 +128,7 @@
public Iterable<ChangeData> byCommitsOnBranchNotMerged(Branch.NameKey branch,
List<String> hashes) throws OrmException {
Schema<ChangeData> schema = schema(indexes);
- if (schema.hasField(ChangeField.EXACT_COMMIT)) {
+ if (schema != null && schema.hasField(ChangeField.EXACT_COMMIT)) {
return query(commitsOnBranchNotMerged(branch, commits(schema, hashes)));
} else {
return byCommitsOnBranchNotMerged(
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/DestinationListTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/DestinationListTest.java
new file mode 100644
index 0000000..2304ece
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/DestinationListTest.java
@@ -0,0 +1,164 @@
+// Copyright (C) 2015 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.git;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.replay;
+
+import com.google.common.collect.Sets;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Project;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Set;
+
+public class DestinationListTest extends TestCase {
+ public static final String R_FOO = "refs/heads/foo";
+ public static final String R_BAR = "refs/heads/bar";
+
+ public static final String P_MY = "myproject";
+ public static final String P_SLASH = "my/project/with/slashes";
+ public static final String P_COMPLEX = " a/project/with spaces and \ttabs ";
+
+ public static final String L_FOO = R_FOO + "\t" + P_MY + "\n";
+ public static final String L_BAR = R_BAR + "\t" + P_SLASH + "\n";
+ public static final String L_FOO_PAD_F = " " + R_FOO + "\t" + P_MY + "\n";
+ public static final String L_FOO_PAD_E = R_FOO + " \t" + P_MY + "\n";
+ public static final String L_COMPLEX = R_FOO + "\t" + P_COMPLEX + "\n";
+ public static final String L_BAD = R_FOO + "\n";
+
+ public static final String HEADER = "# Ref\tProject\n";
+ public static final String HEADER_PROPER = "# Ref \tProject\n";
+ public static final String C1 = "# A Simple Comment\n";
+ public static final String C2 = "# Comment with a tab\t and multi # # #\n";
+
+ public static final String F_SIMPLE = L_FOO + L_BAR;
+ public static final String F_PROPER = L_BAR + L_FOO; // alpha order
+ public static final String F_PAD_F = L_FOO_PAD_F + L_BAR;
+ public static final String F_PAD_E = L_FOO_PAD_E + L_BAR;
+
+ public static final String LABEL = "label";
+ public static final String LABEL2 = "another";
+
+ public static final Branch.NameKey B_FOO = dest(P_MY, R_FOO);
+ public static final Branch.NameKey B_BAR = dest(P_SLASH, R_BAR);
+ public static final Branch.NameKey B_COMPLEX = dest(P_COMPLEX, R_FOO);
+
+ public static final Set<Branch.NameKey> D_SIMPLE = Sets.newHashSet();
+ static {
+ D_SIMPLE.clear();
+ D_SIMPLE.add(B_FOO);
+ D_SIMPLE.add(B_BAR);
+ }
+
+ private static Branch.NameKey dest(String project, String ref) {
+ return new Branch.NameKey(new Project.NameKey(project), ref);
+ }
+
+ @Test
+ public void testParseSimple() throws Exception {
+ DestinationList dl = new DestinationList();
+ dl.parseLabel(LABEL, F_SIMPLE, null);
+ Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ assertThat(branches).containsExactlyElementsIn(D_SIMPLE);
+ }
+
+ @Test
+ public void testParseWHeader() throws Exception {
+ DestinationList dl = new DestinationList();
+ dl.parseLabel(LABEL, HEADER + F_SIMPLE, null);
+ Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ assertThat(branches).containsExactlyElementsIn(D_SIMPLE);
+ }
+
+ @Test
+ public void testParseWComments() throws Exception {
+ DestinationList dl = new DestinationList();
+ dl.parseLabel(LABEL, C1 + F_SIMPLE + C2, null);
+ Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ assertThat(branches).containsExactlyElementsIn(D_SIMPLE);
+ }
+
+ @Test
+ public void testParseFooComment() throws Exception {
+ DestinationList dl = new DestinationList();
+ dl.parseLabel(LABEL, "#" + L_FOO + L_BAR, null);
+ Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ assertThat(branches).doesNotContain(B_FOO);
+ assertThat(branches).contains(B_BAR);
+ }
+
+ @Test
+ public void testParsePaddedFronts() throws Exception {
+ DestinationList dl = new DestinationList();
+ dl.parseLabel(LABEL, F_PAD_F, null);
+ Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ assertThat(branches).containsExactlyElementsIn(D_SIMPLE);
+ }
+
+ @Test
+ public void testParsePaddedEnds() throws Exception {
+ DestinationList dl = new DestinationList();
+ dl.parseLabel(LABEL, F_PAD_E, null);
+ Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ assertThat(branches).containsExactlyElementsIn(D_SIMPLE);
+ }
+
+ @Test
+ public void testParseComplex() throws Exception {
+ DestinationList dl = new DestinationList();
+ dl.parseLabel(LABEL, L_COMPLEX, null);
+ Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ assertThat(branches).contains(B_COMPLEX);
+ }
+
+ @Test(expected = IOException.class)
+ public void testParseBad() throws IOException {
+ ValidationError.Sink sink = createNiceMock(ValidationError.Sink.class);
+ replay(sink);
+ new DestinationList().parseLabel(LABEL, L_BAD, sink);
+ }
+
+ @Test
+ public void testParse2Labels() throws Exception {
+ DestinationList dl = new DestinationList();
+ dl.parseLabel(LABEL, F_SIMPLE, null);
+ Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ assertThat(branches).containsExactlyElementsIn(D_SIMPLE);
+
+ dl.parseLabel(LABEL2, L_COMPLEX, null);
+ branches = dl.getDestinations(LABEL);
+ assertThat(branches).containsExactlyElementsIn(D_SIMPLE);
+ branches = dl.getDestinations(LABEL2);
+ assertThat(branches).contains(B_COMPLEX);
+ }
+
+ @Test
+ public void testAsText() throws Exception {
+ String text = HEADER_PROPER + "#\n" + F_PROPER;
+ DestinationList dl = new DestinationList();
+ dl.parseLabel(LABEL, F_SIMPLE, null);
+ String asText = dl.asText(LABEL);
+ assertThat(text).isEqualTo(asText);
+
+ dl.parseLabel(LABEL2, asText, null);
+ assertThat(text).isEqualTo(dl.asText(LABEL2));
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 1372479..27c3443 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -62,6 +62,7 @@
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.testutil.ConfigSuite;
+import com.google.gerrit.testutil.DisabledReviewDb;
import com.google.gerrit.testutil.InMemoryDatabase;
import com.google.gerrit.testutil.InMemoryRepositoryManager;
import com.google.gerrit.testutil.InMemoryRepositoryManager.Repo;
@@ -123,6 +124,8 @@
@Inject protected InternalChangeQuery internalChangeQuery;
@Inject protected NotesMigration notesMigration;
@Inject protected ProjectControl.GenericFactory projectControlFactory;
+ @Inject protected ChangeQueryBuilder queryBuilder;
+ @Inject protected QueryProcessor queryProcessor;
@Inject protected SchemaCreator schemaCreator;
@Inject protected ThreadLocalRequestContext requestContext;
@@ -1195,6 +1198,41 @@
}
}
+ @Test
+ public void prepopulatedFields() throws Exception {
+ assume().that(notesMigration.enabled()).isFalse();
+ TestRepository<Repo> repo = createProject("repo");
+ Change change = newChange(repo, null, null, null, null).insert();
+
+ db = new DisabledReviewDb();
+ requestContext.setContext(newRequestContext(userId));
+ // Use QueryProcessor directly instead of API so we get ChangeDatas back.
+ List<ChangeData> cds = queryProcessor
+ .queryChanges(queryBuilder.parse(change.getId().toString()))
+ .changes();
+ assertThat(cds).hasSize(1);
+
+ ChangeData cd = cds.get(0);
+ cd.change();
+ cd.patchSets();
+ cd.currentApprovals();
+ cd.changedLines();
+ cd.reviewedBy();
+
+ // TODO(dborowitz): Swap out GitRepositoryManager somehow? Will probably be
+ // necessary for notedb anyway.
+ cd.isMergeable();
+
+ // Don't use ExpectedException since that wouldn't distinguish between
+ // failures here and on the previous calls.
+ try {
+ cd.messages();
+ } catch (AssertionError e) {
+ assertThat(e.getMessage()).isEqualTo(DisabledReviewDb.MESSAGE);
+ }
+ }
+
+
protected ChangeInserter newChange(
TestRepository<Repo> repo,
@Nullable RevCommit commit, @Nullable String key, @Nullable Integer owner,
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV14Test.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV14Test.java
index 79e326f..9bdd795 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV14Test.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV14Test.java
@@ -71,6 +71,13 @@
// Ignore.
}
+ @Override
+ @Ignore
+ @Test
+ public void prepopulatedFields() throws Exception {
+ // Ignore.
+ }
+
@Test
public void isReviewed() throws Exception {
clockStepMs = MILLISECONDS.convert(2, MINUTES);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/DisabledReviewDb.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/DisabledReviewDb.java
similarity index 96%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/DisabledReviewDb.java
rename to gerrit-server/src/test/java/com/google/gerrit/testutil/DisabledReviewDb.java
index 44d3d7f..d5444e3 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/DisabledReviewDb.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/DisabledReviewDb.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.acceptance;
+package com.google.gerrit.testutil;
import com.google.gerrit.reviewdb.server.AccountAccess;
import com.google.gerrit.reviewdb.server.AccountDiffPreferenceAccess;
@@ -41,8 +41,8 @@
import com.google.gwtorm.server.StatementExecutor;
/** ReviewDb that is disabled for testing. */
-class DisabledReviewDb implements ReviewDb {
- private static final String MESSAGE = "ReviewDb is disabled for this test";
+public class DisabledReviewDb implements ReviewDb {
+ public static final String MESSAGE = "ReviewDb is disabled for this test";
@Override
public void close() {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/UploadArchive.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/UploadArchive.java
index 379ee4b..c3dae3a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/UploadArchive.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/UploadArchive.java
@@ -162,7 +162,7 @@
// Find out the object to get from the specified reference and paths
ObjectId treeId = repo.resolve(options.treeIsh);
- if (treeId.equals(ObjectId.zeroId())) {
+ if (treeId == null) {
throw new Failure(4, "fatal: reference not found");
}