Merge "ChangeEditModifier: Reject invalid file paths as '400 Bad Request'"
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
index 2b068aa..8f7e360 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -47,6 +47,10 @@
 import java.util.concurrent.TimeUnit;
 import org.eclipse.jgit.lib.Config;
 
+/**
+ * Creates persistent caches depending on gerrit.config parameters. If the cache.directory property
+ * is unset, it will fall back to in-memory caches.
+ */
 @Singleton
 class H2CacheFactory implements PersistentCacheFactory, LifecycleListener {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
diff --git a/java/com/google/gerrit/server/change/FileInfoJson.java b/java/com/google/gerrit/server/change/FileInfoJson.java
index a823975..aca4fb0 100644
--- a/java/com/google/gerrit/server/change/FileInfoJson.java
+++ b/java/com/google/gerrit/server/change/FileInfoJson.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
 import com.google.gerrit.extensions.common.FileInfo;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchListEntry;
@@ -30,6 +31,8 @@
 import com.google.inject.Singleton;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.concurrent.ExecutionException;
+import org.eclipse.jgit.errors.NoMergeBaseException;
 import org.eclipse.jgit.lib.ObjectId;
 
 @Singleton
@@ -42,31 +45,44 @@
   }
 
   public Map<String, FileInfo> toFileInfoMap(Change change, PatchSet patchSet)
-      throws PatchListNotAvailableException {
+      throws ResourceConflictException, PatchListNotAvailableException {
     return toFileInfoMap(change, patchSet.commitId(), null);
   }
 
   public Map<String, FileInfo> toFileInfoMap(
       Change change, ObjectId objectId, @Nullable PatchSet base)
-      throws PatchListNotAvailableException {
+      throws ResourceConflictException, PatchListNotAvailableException {
     ObjectId a = base != null ? base.commitId() : null;
     return toFileInfoMap(change, PatchListKey.againstCommit(a, objectId, Whitespace.IGNORE_NONE));
   }
 
   public Map<String, FileInfo> toFileInfoMap(Change change, ObjectId objectId, int parent)
-      throws PatchListNotAvailableException {
+      throws ResourceConflictException, PatchListNotAvailableException {
     return toFileInfoMap(
         change, PatchListKey.againstParentNum(parent + 1, objectId, Whitespace.IGNORE_NONE));
   }
 
   private Map<String, FileInfo> toFileInfoMap(Change change, PatchListKey key)
-      throws PatchListNotAvailableException {
+      throws ResourceConflictException, PatchListNotAvailableException {
     return toFileInfoMap(change.getProject(), key);
   }
 
   public Map<String, FileInfo> toFileInfoMap(Project.NameKey project, PatchListKey key)
-      throws PatchListNotAvailableException {
-    PatchList list = patchListCache.get(key, project);
+      throws ResourceConflictException, PatchListNotAvailableException {
+    PatchList list;
+    try {
+      list = patchListCache.get(key, project);
+    } catch (PatchListNotAvailableException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof ExecutionException) {
+        cause = cause.getCause();
+      }
+      if (cause instanceof NoMergeBaseException) {
+        throw new ResourceConflictException(
+            String.format("Cannot create auto merge commit: %s", e.getMessage()), e);
+      }
+      throw e;
+    }
 
     Map<String, FileInfo> files = new TreeMap<>();
     for (PatchListEntry e : list.getPatches()) {
diff --git a/java/com/google/gerrit/server/change/RevisionJson.java b/java/com/google/gerrit/server/change/RevisionJson.java
index fbd14c4..a4994fd 100644
--- a/java/com/google/gerrit/server/change/RevisionJson.java
+++ b/java/com/google/gerrit/server/change/RevisionJson.java
@@ -49,6 +49,7 @@
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.GpgException;
@@ -311,9 +312,13 @@
     }
 
     if (has(ALL_FILES) || (out.isCurrent && has(CURRENT_FILES))) {
-      out.files = fileInfoJson.toFileInfoMap(c, in);
-      out.files.remove(Patch.COMMIT_MSG);
-      out.files.remove(Patch.MERGE_LIST);
+      try {
+        out.files = fileInfoJson.toFileInfoMap(c, in);
+        out.files.remove(Patch.COMMIT_MSG);
+        out.files.remove(Patch.MERGE_LIST);
+      } catch (ResourceConflictException e) {
+        logger.atWarning().withCause(e).log("creating file list failed");
+      }
     }
 
     if (out.isCurrent && has(CURRENT_ACTIONS) && userProvider.get().isIdentifiedUser()) {
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
index aa82247..cbc1b79 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
@@ -185,7 +185,8 @@
 
     @Override
     public Response<EditInfo> apply(ChangeResource rsrc)
-        throws AuthException, IOException, ResourceNotFoundException, PermissionBackendException {
+        throws AuthException, IOException, ResourceNotFoundException, ResourceConflictException,
+            PermissionBackendException {
       Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getNotes(), rsrc.getUser());
       if (!edit.isPresent()) {
         return Response.none();
diff --git a/java/com/google/gerrit/server/restapi/project/FilesInCommitCollection.java b/java/com/google/gerrit/server/restapi/project/FilesInCommitCollection.java
index 0ee8279..0d5ab88 100644
--- a/java/com/google/gerrit/server/restapi/project/FilesInCommitCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/FilesInCommitCollection.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.ChildCollection;
 import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
@@ -86,7 +87,7 @@
 
     @Override
     public Response<Map<String, FileInfo>> apply(CommitResource resource)
-        throws PatchListNotAvailableException {
+        throws ResourceConflictException, PatchListNotAvailableException {
       RevCommit commit = resource.getCommit();
       PatchListKey key;
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
index 359a039..e46f959 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
@@ -192,15 +192,20 @@
       }
     }
 
-    /**
-     * For Polymer 2, use shadowRoot.getSelection instead.
-     */
     _getSelection() {
-      const diffHost = util.querySelector(document.body, 'gr-diff');
-      const selection = diffHost &&
-        diffHost.shadowRoot &&
-        diffHost.shadowRoot.getSelection();
-      return selection ? selection: window.getSelection();
+      const diffHosts = util.querySelectorAll(document.body, 'gr-diff');
+      if (!diffHosts.length) return window.getSelection();
+
+      const curDiffHost = diffHosts.find(diffHost => {
+        if (!diffHost || !diffHost.shadowRoot) return false;
+        const selection = diffHost.shadowRoot.getSelection();
+        // Pick the one with valid selection:
+        // https://developer.mozilla.org/en-US/docs/Web/API/Selection/type
+        return selection && selection.type !== 'None';
+      });
+
+      return curDiffHost ?
+        curDiffHost.shadowRoot.getSelection(): window.getSelection();
     }
 
     /**
diff --git a/polygerrit-ui/app/scripts/util.js b/polygerrit-ui/app/scripts/util.js
index 672c43f..565b9b3 100644
--- a/polygerrit-ui/app/scripts/util.js
+++ b/polygerrit-ui/app/scripts/util.js
@@ -105,7 +105,7 @@
    */
   util.querySelector = (el, selector) => {
     let nodes = [el];
-    let element = null;
+    let result = null;
     while (nodes.length) {
       const node = nodes.pop();
 
@@ -113,19 +113,49 @@
       if (!node || !node.querySelector) continue;
 
       // Try find it with native querySelector directly
-      element = node.querySelector(selector);
+      result = node.querySelector(selector);
 
-      if (element) {
+      if (result) {
         break;
-      } else if (node.shadowRoot) {
-        // If shadowHost detected, add the host and its children
-        nodes = nodes.concat(Array.from(node.children));
-        nodes.push(node.shadowRoot);
-      } else {
-        nodes = nodes.concat(Array.from(node.children));
       }
+
+      // Add all nodes with shadowRoot and loop through
+      const allShadowNodes = [...node.querySelectorAll('*')]
+          .filter(child => !!child.shadowRoot)
+          .map(child => child.shadowRoot);
+      nodes = nodes.concat(allShadowNodes);
     }
-    return element;
+    return result;
+  };
+
+  /**
+   * Query selector all dom elements matching with certain selector.
+   *
+   * This is shadow DOM compatible, but only works when selector is within
+   * one shadow host, won't work if your selector is crossing
+   * multiple shadow hosts.
+   *
+   * Note: this can be very expensive, only use when have to.
+   */
+  util.querySelectorAll = (el, selector) => {
+    let nodes = [el];
+    const results = new Set();
+    while (nodes.length) {
+      const node = nodes.pop();
+
+      if (!node || !node.querySelectorAll) continue;
+
+      // Try find all from regular children
+      [...node.querySelectorAll(selector)]
+          .forEach(el => results.add(el));
+
+      // Add all nodes with shadowRoot and loop through
+      const allShadowNodes = [...node.querySelectorAll('*')]
+          .filter(child => !!child.shadowRoot)
+          .map(child => child.shadowRoot);
+      nodes = nodes.concat(allShadowNodes);
+    }
+    return [...results];
   };
 
   window.util = util;