Merge branch 'stable-3.5' into stable-3.6

* stable-3.5:
  Instantiate JSCH SSH Session Factory explicitily
  Wrap #mobileSearch in a div
  Retry inserting new patch-sets on lock failures
  Update git submodules
  Include tsconfig-plugins-base.json in TypeScript Plugin API
  Tweaks to @gerritcodereview/typescript-api
  Fix publish script for @gerritcodereview/typescript-api
  Disable printing cache stats by default on Init/Reindex
  Remove eTag from RevisionResource, subject to experiment

Release-Notes: skip
Change-Id: Iad12f9fc29c298a81e74c85a194cb69b2dfc96d9
diff --git a/Documentation/pg-plugin-dev.txt b/Documentation/pg-plugin-dev.txt
index b8a30ee..381c3e1 100644
--- a/Documentation/pg-plugin-dev.txt
+++ b/Documentation/pg-plugin-dev.txt
@@ -30,6 +30,33 @@
 });
 ```
 
+== TypeScript API ==
+
+Gerrit provides a TypeScript plugin API.
+
+For a plugin built inline, its `tsconfig.json` can extends Gerrit plugin
+TypeScript configuration:
+
+`tsconfig.json`:
+``` json
+{
+  "extends": "../tsconfig-plugins-base.json"
+}
+```
+
+For standalone plugins (outside of a Gerrit tree), a TypeScript plugin API is
+published:
+link:https://www.npmjs.com/package/@gerritcodereview/typescript-api[@gerritcodereview/typescript-api].
+It provides a TypeScript configuration `tsconfig-plugins-base.json` which can
+be used in your plugin `tsconfig.json`:
+
+``` json
+{
+  "extends": "node_modules/@gerritcodereview/typescript-api/tsconfig-plugins-base.json",
+  // your custom configuration and overrides
+}
+```
+
 [[low-level-api-concepts]]
 == Low-level DOM API concepts
 
diff --git a/Documentation/pgm-init.txt b/Documentation/pgm-init.txt
index f6c3c85..9f592486 100644
--- a/Documentation/pgm-init.txt
+++ b/Documentation/pgm-init.txt
@@ -99,6 +99,9 @@
 	The administrator must manually install the required library in the `lib/`
 	folder.
 
+--show-cache-stats::
+	Show cache statistics at the end of program.
+
 == CONTEXT
 This command can only be run on a server which has direct local access to the
 managed Git repositories.
diff --git a/Documentation/pgm-reindex.txt b/Documentation/pgm-reindex.txt
index 0653d8d..b74829d 100644
--- a/Documentation/pgm-reindex.txt
+++ b/Documentation/pgm-reindex.txt
@@ -36,9 +36,8 @@
 	Reindex only index with given name. This option can be supplied
 	more than once to reindex multiple indices.
 
---disable-cache-stats::
-	Disables printing cache statistics at the end of program to reduce
-	noise. Defaulted when reindex is run from init on a new site.
+--show-cache-stats::
+	Show cache statistics at the end of program.
 
 == CONTEXT
 The secondary index must be enabled. See
diff --git a/java/com/google/gerrit/pgm/Init.java b/java/com/google/gerrit/pgm/Init.java
index 4c7b47b..2a746b8 100644
--- a/java/com/google/gerrit/pgm/Init.java
+++ b/java/com/google/gerrit/pgm/Init.java
@@ -95,6 +95,9 @@
   @Option(name = "--reindex-threads", usage = "Number of threads to use for reindex after init")
   private int reindexThreads = 1;
 
+  @Option(name = "--show-cache-stats", usage = "Show cache statistics at the end")
+  private boolean showCacheStats;
+
   @Inject Browser browser;
 
   private GerritIndexStatus indexStatus;
@@ -167,7 +170,7 @@
           indicesToReindex.add(schemaDef.getName());
         }
       }
-      reindex(indicesToReindex, run.flags.isNew);
+      reindex(indicesToReindex);
     }
     start(run);
   }
@@ -280,7 +283,7 @@
     }
   }
 
-  private void reindex(List<String> indices, boolean isNewSite) throws Exception {
+  private void reindex(List<String> indices) throws Exception {
     if (indices.isEmpty()) {
       return;
     }
@@ -291,8 +294,8 @@
       reindexArgs.add("--index");
       reindexArgs.add(index);
     }
-    if (isNewSite) {
-      reindexArgs.add("--disable-cache-stats");
+    if (showCacheStats) {
+      reindexArgs.add("--show-cache-stats");
     }
 
     getConsoleUI()
diff --git a/java/com/google/gerrit/pgm/Reindex.java b/java/com/google/gerrit/pgm/Reindex.java
index c4e185d..7ee799f 100644
--- a/java/com/google/gerrit/pgm/Reindex.java
+++ b/java/com/google/gerrit/pgm/Reindex.java
@@ -86,12 +86,8 @@
   @Option(name = "--index", usage = "Only reindex specified indices")
   private List<String> indices = new ArrayList<>();
 
-  @Option(
-      name = "--disable-cache-stats",
-      usage =
-          "Disables printing the cache statistics."
-              + "Defaults to true when reindex is run from init on a new site, false otherwise")
-  private boolean disableCacheStats;
+  @Option(name = "--show-cache-stats", usage = "Show cache statistics at the end.")
+  private boolean showCacheStats;
 
   private Injector dbInjector;
   private Injector sysInjector;
@@ -123,7 +119,7 @@
 
     try {
       boolean ok = list ? list() : reindex();
-      if (!disableCacheStats) {
+      if (showCacheStats) {
         printCacheStats();
       }
       return ok ? 0 : 1;
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index f212384..1cebf5f 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -1023,91 +1023,112 @@
             Strings.nullToEmpty(magicBranchCmd.getMessage()));
         return;
       }
+      try {
+        retryHelper
+            .changeUpdate(
+                "insertChangesAndPatchSets",
+                updateFactory -> {
+                  try (BatchUpdate bu =
+                          updateFactory.create(
+                              project.getNameKey(), user.materializedCopy(), TimeUtil.now());
+                      ObjectInserter ins = repo.newObjectInserter();
+                      ObjectReader reader = ins.newReader();
+                      RevWalk rw = new RevWalk(reader)) {
+                    bu.setRepository(repo, rw, ins);
+                    bu.setRefLogMessage("push");
+                    if (magicBranch != null) {
+                      bu.setNotify(magicBranch.getNotifyForNewChange());
+                    }
 
-      try (BatchUpdate bu =
-              batchUpdateFactory.create(
-                  project.getNameKey(), user.materializedCopy(), TimeUtil.now());
-          ObjectInserter ins = repo.newObjectInserter();
-          ObjectReader reader = ins.newReader();
-          RevWalk rw = new RevWalk(reader)) {
-        bu.setRepository(repo, rw, ins);
-        bu.setRefLogMessage("push");
-        if (magicBranch != null) {
-          bu.setNotify(magicBranch.getNotifyForNewChange());
-        }
+                    logger.atFine().log("Adding %d replace requests", newChanges.size());
+                    for (ReplaceRequest replace : replaceByChange.values()) {
+                      replace.addOps(bu, replaceProgress);
+                      if (magicBranch != null) {
+                        bu.setNotifyHandling(
+                            replace.ontoChange, magicBranch.getNotifyHandling(replace.notes));
+                        if (magicBranch.shouldPublishComments()) {
+                          bu.addOp(
+                              replace.notes.getChangeId(),
+                              publishCommentsOp.create(replace.psId, project.getNameKey()));
+                          Optional<ChangeNotes> changeNotes =
+                              getChangeNotes(replace.notes.getChangeId());
+                          if (!changeNotes.isPresent()) {
+                            // If not present, no need to update attention set here since this is a
+                            // new change.
+                            continue;
+                          }
+                          List<HumanComment> drafts =
+                              commentsUtil.draftByChangeAuthor(
+                                  changeNotes.get(), user.getAccountId());
+                          if (drafts.isEmpty()) {
+                            // If no comments, attention set shouldn't update since the user didn't
+                            // reply.
+                            continue;
+                          }
+                          replyAttentionSetUpdates.processAutomaticAttentionSetRulesOnReply(
+                              bu,
+                              changeNotes.get(),
+                              isReadyForReview(changeNotes.get()),
+                              user,
+                              drafts);
+                        }
+                      }
+                    }
 
-        logger.atFine().log("Adding %d replace requests", newChanges.size());
-        for (ReplaceRequest replace : replaceByChange.values()) {
-          replace.addOps(bu, replaceProgress);
-          if (magicBranch != null) {
-            bu.setNotifyHandling(replace.ontoChange, magicBranch.getNotifyHandling(replace.notes));
-            if (magicBranch.shouldPublishComments()) {
-              bu.addOp(
-                  replace.notes.getChangeId(),
-                  publishCommentsOp.create(replace.psId, project.getNameKey()));
-              Optional<ChangeNotes> changeNotes = getChangeNotes(replace.notes.getChangeId());
-              if (!changeNotes.isPresent()) {
-                // If not present, no need to update attention set here since this is a new change.
-                continue;
-              }
-              List<HumanComment> drafts =
-                  commentsUtil.draftByChangeAuthor(changeNotes.get(), user.getAccountId());
-              if (drafts.isEmpty()) {
-                // If no comments, attention set shouldn't update since the user didn't reply.
-                continue;
-              }
-              replyAttentionSetUpdates.processAutomaticAttentionSetRulesOnReply(
-                  bu, changeNotes.get(), isReadyForReview(changeNotes.get()), user, drafts);
-            }
-          }
-        }
+                    logger.atFine().log("Adding %d create requests", newChanges.size());
+                    for (CreateRequest create : newChanges) {
+                      create.addOps(bu);
+                    }
 
-        logger.atFine().log("Adding %d create requests", newChanges.size());
-        for (CreateRequest create : newChanges) {
-          create.addOps(bu);
-        }
+                    logger.atFine().log("Adding %d group update requests", newChanges.size());
+                    updateGroups.forEach(r -> r.addOps(bu));
 
-        logger.atFine().log("Adding %d group update requests", newChanges.size());
-        updateGroups.forEach(r -> r.addOps(bu));
+                    logger.atFine().log("Executing batch");
+                    try {
+                      bu.execute();
+                    } catch (UpdateException e) {
+                      throw asRestApiException(e);
+                    }
 
-        logger.atFine().log("Executing batch");
-        try {
-          bu.execute();
-        } catch (UpdateException e) {
-          throw asRestApiException(e);
-        }
+                    replaceByChange.values().stream()
+                        .forEach(
+                            req ->
+                                result.addChange(
+                                    ReceiveCommitsResult.ChangeStatus.REPLACED, req.ontoChange));
+                    newChanges.stream()
+                        .forEach(
+                            req ->
+                                result.addChange(
+                                    ReceiveCommitsResult.ChangeStatus.CREATED, req.changeId));
 
-        replaceByChange.values().stream()
-            .forEach(
-                req ->
-                    result.addChange(ReceiveCommitsResult.ChangeStatus.REPLACED, req.ontoChange));
-        newChanges.stream()
-            .forEach(
-                req -> result.addChange(ReceiveCommitsResult.ChangeStatus.CREATED, req.changeId));
-
-        if (magicBranchCmd != null) {
-          magicBranchCmd.setResult(OK);
-        }
-        for (ReplaceRequest replace : replaceByChange.values()) {
-          String rejectMessage = replace.getRejectMessage();
-          if (rejectMessage == null) {
-            if (replace.inputCommand.getResult() == NOT_ATTEMPTED) {
-              // Not necessarily the magic branch, so need to set OK on the original value.
-              replace.inputCommand.setResult(OK);
-            }
-          } else {
-            logger.atFine().log("Rejecting due to message from ReplaceOp");
-            reject(replace.inputCommand, rejectMessage);
-          }
-        }
-
+                    if (magicBranchCmd != null) {
+                      magicBranchCmd.setResult(OK);
+                    }
+                    for (ReplaceRequest replace : replaceByChange.values()) {
+                      String rejectMessage = replace.getRejectMessage();
+                      if (rejectMessage == null) {
+                        if (replace.inputCommand.getResult() == NOT_ATTEMPTED) {
+                          // Not necessarily the magic branch, so need to set OK on the original
+                          // value.
+                          replace.inputCommand.setResult(OK);
+                        }
+                      } else {
+                        logger.atFine().log("Rejecting due to message from ReplaceOp");
+                        reject(replace.inputCommand, rejectMessage);
+                      }
+                    }
+                  }
+                  return null;
+                })
+            .defaultTimeoutMultiplier(5)
+            .call();
       } catch (ResourceConflictException e) {
         addError(e.getMessage());
         reject(magicBranchCmd, "conflict");
       } catch (BadRequestException | UnprocessableEntityException | AuthException e) {
         logger.atFine().withCause(e).log("Rejecting due to client error");
         reject(magicBranchCmd, e.getMessage());
-      } catch (RestApiException | IOException e) {
+      } catch (RestApiException | UpdateException e) {
         throw new StorageException("Can't insert change/patch set for " + project.getName(), e);
       }
 
diff --git a/polygerrit-ui/app/api/BUILD_for_publishing_api_only b/polygerrit-ui/app/api/BUILD_for_publishing_api_only
index 67a26cd..c1bb6bd 100644
--- a/polygerrit-ui/app/api/BUILD_for_publishing_api_only
+++ b/polygerrit-ui/app/api/BUILD_for_publishing_api_only
@@ -43,9 +43,13 @@
         ["**/*"],
         exclude = [
             "BUILD",
+            "BUILD_for_publishing_api_only",
             "tsconfig.json",
             "publish.sh",
         ],
     ),
-    deps = [":js_plugin_api_compiled"],
+    deps = [
+        ":js_plugin_api_compiled",
+        "//plugins:tsconfig-plugins-base.json",
+    ],
 )
diff --git a/polygerrit-ui/app/api/publish.sh b/polygerrit-ui/app/api/publish.sh
index 16de4c9..1d6368c 100755
--- a/polygerrit-ui/app/api/publish.sh
+++ b/polygerrit-ui/app/api/publish.sh
@@ -7,19 +7,24 @@
 #
 # Adding the `--upload` argument will also publish the package.
 
+set -e
+
 bazel_bin=$(which bazelisk 2>/dev/null)
 if [[ -z "$bazel_bin" ]]; then
     echo "Warning: bazelisk is not installed; falling back to bazel."
     bazel_bin=bazel
 fi
 api_path=polygerrit-ui/app/api
+plugins_path=plugins
 
 function cleanup() {
   echo "Cleaning up ..."
   rm -f ${api_path}/BUILD
+  rm -f ${api_path}/tsconfig-plugins-base.json
 }
 trap cleanup EXIT
 cp ${api_path}/BUILD_for_publishing_api_only ${api_path}/BUILD
+cp ${plugins_path}/tsconfig-plugins-base.json ${api_path}/tsconfig-plugins-base.json
 
 ${bazel_bin} build //${api_path}:js_plugin_api_npm_package
 
diff --git a/polygerrit-ui/app/api/tsconfig.json b/polygerrit-ui/app/api/tsconfig.json
index 037e4f2..6960192 100644
--- a/polygerrit-ui/app/api/tsconfig.json
+++ b/polygerrit-ui/app/api/tsconfig.json
@@ -1,9 +1,9 @@
 {
   "extends": "../../../plugins/tsconfig-plugins-base.json",
   "compilerOptions": {
-    "rootDir": ".",
+    "rootDir": "."
   },
   "include": [
-    "**/*",
-  ],
+    "**/*"
+  ]
 }