Merge "Merge branch 'stable-2.15'"
diff --git a/java/com/google/gerrit/sshd/AbstractGitCommand.java b/java/com/google/gerrit/sshd/AbstractGitCommand.java
index 30b6408..c49ae82 100644
--- a/java/com/google/gerrit/sshd/AbstractGitCommand.java
+++ b/java/com/google/gerrit/sshd/AbstractGitCommand.java
@@ -32,6 +32,8 @@
   @Argument(index = 0, metaVar = "PROJECT.git", required = true, usage = "project name")
   protected ProjectState projectState;
 
+  @Inject private SshScope sshScope;
+
   @Inject private GitRepositoryManager repoManager;
 
   @Inject private SshSession session;
@@ -47,24 +49,30 @@
   @Override
   public void start(Environment env) {
     Context ctx = context.subContext(newSession(), context.getCommandLine());
-    startThreadWithContext(
-        ctx,
-        new ProjectCommandRunnable() {
-          @Override
-          public void executeParseCommand() throws Exception {
-            parseCommandLine();
-          }
+    final Context old = sshScope.set(ctx);
+    try {
+      startThread(
+          new ProjectCommandRunnable() {
+            @Override
+            public void executeParseCommand() throws Exception {
+              parseCommandLine();
+            }
 
-          @Override
-          public void run() throws Exception {
-            AbstractGitCommand.this.service();
-          }
+            @Override
+            public void run() throws Exception {
+              AbstractGitCommand.this.service();
+            }
 
-          @Override
-          public Project.NameKey getProjectName() {
-            return projectState.getNameKey();
-          }
-        });
+            @Override
+            public Project.NameKey getProjectName() {
+              Project project = projectState.getProject();
+              return project.getNameKey();
+            }
+          },
+          AccessPath.GIT);
+    } finally {
+      sshScope.set(old);
+    }
   }
 
   private SshSession newSession() {
@@ -73,7 +81,6 @@
             session,
             session.getRemoteAddress(),
             userFactory.create(session.getRemoteAddress(), user.getAccountId()));
-    n.setAccessPath(AccessPath.GIT);
     return n;
   }
 
diff --git a/java/com/google/gerrit/sshd/BaseCommand.java b/java/com/google/gerrit/sshd/BaseCommand.java
index 3d5a440..2081967 100644
--- a/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/java/com/google/gerrit/sshd/BaseCommand.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.AccessPath;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.DynamicOptions;
 import com.google.gerrit.server.IdentifiedUser;
@@ -50,7 +51,6 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.nio.charset.Charset;
-import java.util.Optional;
 import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.atomic.AtomicReference;
@@ -262,25 +262,27 @@
   }
 
   /**
-   * Spawn a function into its own thread with the provided context.
+   * Spawn a function into its own thread.
    *
    * <p>Typically this should be invoked within {@link Command#start(Environment)}, such as:
    *
    * <pre>
-   * startThreadWithContext(SshScope.Context context, new CommandRunnable() {
+   * startThread(new CommandRunnable() {
    *   public void run() throws Exception {
    *     runImp();
    *   }
-   * });
+   * },
+   * accessPath);
    * </pre>
    *
    * <p>If the function throws an exception, it is translated to a simple message for the client, a
    * non-zero exit code, and the stack trace is logged.
    *
    * @param thunk the runnable to execute on the thread, performing the command's logic.
+   * @param accessPath the path used by the end user for running the SSH command
    */
-  protected void startThreadWithContext(SshScope.Context context, CommandRunnable thunk) {
-    final TaskThunk tt = new TaskThunk(thunk, Optional.ofNullable(context));
+  protected void startThread(final CommandRunnable thunk, AccessPath accessPath) {
+    final TaskThunk tt = new TaskThunk(thunk, accessPath);
 
     if (isAdminHighPriorityCommand()) {
       // Admin commands should not block the main work threads (there
@@ -293,28 +295,6 @@
     }
   }
 
-  /**
-   * Spawn a function into its own thread.
-   *
-   * <p>Typically this should be invoked within {@link Command#start(Environment)}, such as:
-   *
-   * <pre>
-   * startThread(new CommandRunnable() {
-   *   public void run() throws Exception {
-   *     runImp();
-   *   }
-   * });
-   * </pre>
-   *
-   * <p>If the function throws an exception, it is translated to a simple message for the client, a
-   * non-zero exit code, and the stack trace is logged.
-   *
-   * @param thunk the runnable to execute on the thread, performing the command's logic.
-   */
-  protected void startThread(final CommandRunnable thunk) {
-    startThreadWithContext(null, thunk);
-  }
-
   private boolean isAdminHighPriorityCommand() {
     if (getClass().getAnnotation(AdminHighPriorityCommand.class) != null) {
       try {
@@ -439,21 +419,21 @@
 
   private final class TaskThunk implements CancelableRunnable, ProjectRunnable {
     private final CommandRunnable thunk;
-    private final Context taskContext;
     private final String taskName;
+    private final AccessPath accessPath;
 
     private Project.NameKey projectName;
 
-    private TaskThunk(CommandRunnable thunk, Optional<Context> oneOffContext) {
+    private TaskThunk(final CommandRunnable thunk, AccessPath accessPath) {
       this.thunk = thunk;
       this.taskName = getTaskName();
-      this.taskContext = oneOffContext.orElse(context);
+      this.accessPath = accessPath;
     }
 
     @Override
     public void cancel() {
       synchronized (this) {
-        final Context old = sshScope.set(taskContext);
+        final Context old = sshScope.set(context);
         try {
           onExit(STATUS_CANCEL);
         } finally {
@@ -468,7 +448,8 @@
         final Thread thisThread = Thread.currentThread();
         final String thisName = thisThread.getName();
         int rc = 0;
-        final Context old = sshScope.set(taskContext);
+        context.getSession().setAccessPath(accessPath);
+        final Context old = sshScope.set(context);
         try {
           context.started = TimeUtil.nowMs();
           thisThread.setName("SSH " + taskName);
diff --git a/java/com/google/gerrit/sshd/SshCommand.java b/java/com/google/gerrit/sshd/SshCommand.java
index 99c8724..47c6098 100644
--- a/java/com/google/gerrit/sshd/SshCommand.java
+++ b/java/com/google/gerrit/sshd/SshCommand.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.sshd;
 
+import com.google.gerrit.server.AccessPath;
 import com.google.gerrit.server.logging.TraceContext;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -46,7 +47,8 @@
               stderr.flush();
             }
           }
-        });
+        },
+        AccessPath.SSH_COMMAND);
   }
 
   protected abstract void run() throws UnloggedFailure, Failure, Exception;
diff --git a/java/com/google/gerrit/sshd/commands/ScpCommand.java b/java/com/google/gerrit/sshd/commands/ScpCommand.java
index 89a09ef..b3a9a16 100644
--- a/java/com/google/gerrit/sshd/commands/ScpCommand.java
+++ b/java/com/google/gerrit/sshd/commands/ScpCommand.java
@@ -25,6 +25,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.server.AccessPath;
 import com.google.gerrit.server.tools.ToolsCatalog;
 import com.google.gerrit.server.tools.ToolsCatalog.Entry;
 import com.google.gerrit.sshd.BaseCommand;
@@ -82,7 +83,7 @@
 
   @Override
   public void start(Environment env) {
-    startThread(this::runImp);
+    startThread(this::runImp, AccessPath.SSH_COMMAND);
   }
 
   private void runImp() {