diff --git a/src/internationalization/ArcanistUSEnglishTranslation.php b/src/internationalization/ArcanistUSEnglishTranslation.php
--- a/src/internationalization/ArcanistUSEnglishTranslation.php
+++ b/src/internationalization/ArcanistUSEnglishTranslation.php
@@ -34,7 +34,7 @@
           'these files, or continue. If you continue, these files will be '.
           'marked as binary.',
       ),
-      '%d AFFECTED FILE(S)' => array('AFFECTED FILE', 'AFFECTED FILES'),
+      '%s AFFECTED FILE(S)' => array('AFFECTED FILE', 'AFFECTED FILES'),
       'Do you want to mark these %s file(s) as binary and continue?' => array(
         'Do you want to mark this file as binary and continue?',
         'Do you want to mark these files as binary and continue?',
@@ -57,11 +57,9 @@
 
       '%s line(s)' => array('line', 'lines'),
 
-      '%d test(s)' => array('%d test', '%d tests'),
-
-      '%d assertion(s) passed.' => array(
-        '%d assertion passed.',
-        '%d assertions passed.',
+      '%s assertion(s) passed.' => array(
+        '%s assertion passed.',
+        '%s assertions passed.',
       ),
 
       'Ignore these %s untracked file(s) and continue?' => array(
diff --git a/src/land/ArcanistGitLandEngine.php b/src/land/ArcanistGitLandEngine.php
--- a/src/land/ArcanistGitLandEngine.php
+++ b/src/land/ArcanistGitLandEngine.php
@@ -262,13 +262,26 @@
         $path->removeUpstream($local_branch);
 
         if (!$path->getLength()) {
-          $this->writeInfo(
-            pht('UPDATE'),
-            pht(
-              'Local branch "%s" directly tracks remote, staying on '.
-              'detached HEAD.',
-              $local_branch));
-          return;
+          // The local branch tracked upstream directly; however, it
+          // may not be the only one to do so.  If there's a local
+          // branch of the same name that tracks the remote, try
+          // switching to that.
+          $local_branch = $this->getTargetOnto();
+          list($err) = $api->execManualLocal(
+            'rev-parse --verify %s',
+            $local_branch);
+          if (!$err) {
+            $path = $api->getPathToUpstream($local_branch);
+          }
+          if (!$path->isConnectedToRemote()) {
+            $this->writeInfo(
+              pht('UPDATE'),
+              pht(
+                'Local branch "%s" directly tracks remote, staying on '.
+                'detached HEAD.',
+                $local_branch));
+            return;
+          }
         }
 
         $local_branch = head($path->getLocalBranches());
diff --git a/src/lint/linter/ArcanistLinter.php b/src/lint/linter/ArcanistLinter.php
--- a/src/lint/linter/ArcanistLinter.php
+++ b/src/lint/linter/ArcanistLinter.php
@@ -197,39 +197,6 @@
   }
 
 
-  /**
-   * Obsolete hook which was invoked before a path was linted.
-   *
-   * WARNING: This is an obsolete hook which is not called. If you maintain
-   * a linter which relies on it, update to use @{method:lintPath} instead.
-   *
-   * @task exec
-   */
-  final public function willLintPath($path) {
-    // TODO: Remove this method after some time. In the meantime, the "final"
-    // will fatal subclasses which implement this hook and point at the API
-    // change so maintainers get fewer surprises.
-    throw new PhutilMethodNotImplementedException();
-  }
-
-
-  /**
-   * Obsolete hook which was invoked after linters ran.
-   *
-   * WARNING: This is an obsolete hook which is not called. If you maintain
-   * a linter which relies on it, update to use @{method:didLintPaths} instead.
-   *
-   * @return void
-   * @task exec
-   */
-  final public function didRunLinters() {
-    // TODO: Remove this method after some time. In the meantime, the "final"
-    // will fatal subclasses which implement this hook and point at the API
-    // change so maintainers get fewer surprises.
-    throw new PhutilMethodNotImplementedException();
-  }
-
-
   public function getLinterPriority() {
     return 1.0;
   }
diff --git a/src/unit/engine/phutil/PhutilTestCase.php b/src/unit/engine/phutil/PhutilTestCase.php
--- a/src/unit/engine/phutil/PhutilTestCase.php
+++ b/src/unit/engine/phutil/PhutilTestCase.php
@@ -490,7 +490,10 @@
             call_user_func_array(
               array($this, $name),
               array());
-            $this->passTest(pht('%d assertion(s) passed.', $this->assertions));
+            $this->passTest(
+              pht(
+                '%s assertion(s) passed.',
+                new PhutilNumber($this->assertions)));
           } catch (Exception $ex) {
             $exceptions['Execution'] = $ex;
           }
diff --git a/src/upload/ArcanistFileUploader.php b/src/upload/ArcanistFileUploader.php
--- a/src/upload/ArcanistFileUploader.php
+++ b/src/upload/ArcanistFileUploader.php
@@ -245,14 +245,14 @@
     if ($done) {
       $this->writeStatus(
         pht(
-          'Resuming upload (%d of %d chunks remain).',
-          new PhutilNumber(count($remaining)),
-          new PhutilNumber(count($chunks))));
+          'Resuming upload (%s of %s chunks remain).',
+          phutil_count($remaining),
+          phutil_count($chunks)));
     } else {
       $this->writeStatus(
         pht(
-          'Uploading chunks (%d chunks to upload).',
-          new PhutilNumber(count($remaining))));
+          'Uploading chunks (%s chunks to upload).',
+          phutil_count($remaining)));
     }
 
     $progress = new PhutilConsoleProgressBar();
diff --git a/src/workflow/ArcanistCommitWorkflow.php b/src/workflow/ArcanistCommitWorkflow.php
--- a/src/workflow/ArcanistCommitWorkflow.php
+++ b/src/workflow/ArcanistCommitWorkflow.php
@@ -225,10 +225,10 @@
     if ($modified_but_not_included) {
       $prefix = pht(
         '%s locally modified path(s) are not included in this revision:',
-        new PhutilNumber(count($modified_but_not_included)));
+        phutil_count($modified_but_not_included));
       $prompt = pht(
         'These %s path(s) will NOT be committed. Commit this revision anyway?',
-        new PhutilNumber(count($modified_but_not_included)));
+        phutil_count($modified_but_not_included));
       $this->promptFileWarning($prefix, $prompt, $modified_but_not_included);
     }
 
@@ -251,7 +251,7 @@
     if ($do_not_exist) {
       $prefix = pht(
         'Revision includes changes to %s path(s) that do not exist:',
-        new PhutilNumber(count($do_not_exist)));
+        phutil_count($do_not_exist));
       $prompt = pht('Commit this revision anyway?');
       $this->promptFileWarning($prefix, $prompt, $do_not_exist);
     }
diff --git a/src/workflow/ArcanistCoverWorkflow.php b/src/workflow/ArcanistCoverWorkflow.php
--- a/src/workflow/ArcanistCoverWorkflow.php
+++ b/src/workflow/ArcanistCoverWorkflow.php
@@ -120,7 +120,7 @@
         foreach ($files as $file => $info) {
           $line_noun = pht(
             '%s line(s)',
-            new PhutilNumber(count($info['lines'])));
+            phutil_count($info['lines']));
           $lines = $this->readableSequenceFromLineNumbers($info['lines']);
           echo "  {$file}: {$line_noun} {$lines}\n";
         }
diff --git a/src/workflow/ArcanistDiffWorkflow.php b/src/workflow/ArcanistDiffWorkflow.php
--- a/src/workflow/ArcanistDiffWorkflow.php
+++ b/src/workflow/ArcanistDiffWorkflow.php
@@ -962,7 +962,7 @@
         'works best for changes which will receive detailed human review, '.
         'and not as well for large automated changes or bulk checkins. '.
         'See %s for information about reviewing big checkins. Continue anyway?',
-        new PhutilNumber(count($changes)),
+        phutil_count($changes),
         'https://secure.phabricator.com/book/phabricator/article/'.
           'differential_large_changes/');
 
@@ -1070,18 +1070,18 @@
             'contain invalid byte sequences). You can either stop this '.
             'workflow and fix these files, or continue. If you continue, '.
             'these files will be marked as binary.',
-            new PhutilNumber(count($utf8_problems))),
+            phutil_count($utf8_problems)),
           pht(
             "You can learn more about how Phabricator handles character ".
             "encodings (and how to configure encoding settings and detect and ".
             "correct encoding problems) by reading 'User Guide: UTF-8 and ".
             "Character Encoding' in the Phabricator documentation."),
           pht(
-            '%d AFFECTED FILE(S)',
-            count($utf8_problems)));
+            '%s AFFECTED FILE(S)',
+            phutil_count($utf8_problems)));
       $confirm = pht(
         'Do you want to mark these %s file(s) as binary and continue?',
-        new PhutilNumber(count($utf8_problems)));
+        phutil_count($utf8_problems));
 
       echo phutil_console_format(
         "**%s**\n",
diff --git a/src/workflow/ArcanistUploadWorkflow.php b/src/workflow/ArcanistUploadWorkflow.php
--- a/src/workflow/ArcanistUploadWorkflow.php
+++ b/src/workflow/ArcanistUploadWorkflow.php
@@ -143,14 +143,14 @@
     if ($done) {
       $this->writeStatus(
         pht(
-          'Resuming upload (%d of %d chunks remain).',
-          new PhutilNumber(count($remaining)),
-          new PhutilNumber(count($chunks))));
+          'Resuming upload (%s of %s chunks remain).',
+          phutil_count($remaining),
+          phutil_count($chunks)));
     } else {
       $this->writeStatus(
         pht(
-          'Uploading chunks (%d chunks to upload).',
-          new PhutilNumber(count($remaining))));
+          'Uploading chunks (%s chunks to upload).',
+          phutil_count($remaining)));
     }
 
     $progress = new PhutilConsoleProgressBar();
diff --git a/src/workflow/ArcanistWorkflow.php b/src/workflow/ArcanistWorkflow.php
--- a/src/workflow/ArcanistWorkflow.php
+++ b/src/workflow/ArcanistWorkflow.php
@@ -938,17 +938,17 @@
       if ($api instanceof ArcanistGitAPI) {
         $hint = pht(
           '(To ignore these %s change(s), add them to "%s".)',
-          new PhutilNumber(count($untracked)),
+          phutil_count($untracked),
           '.git/info/exclude');
       } else if ($api instanceof ArcanistSubversionAPI) {
         $hint = pht(
           '(To ignore these %s change(s), add them to "%s".)',
-          new PhutilNumber(count($untracked)),
+          phutil_count($untracked),
           'svn:ignore');
       } else if ($api instanceof ArcanistMercurialAPI) {
         $hint = pht(
           '(To ignore these %s change(s), add them to "%s".)',
-          new PhutilNumber(count($untracked)),
+          phutil_count($untracked),
           '.hgignore');
       }
 
@@ -961,7 +961,7 @@
 
       $prompt = pht(
         'Ignore these %s untracked file(s) and continue?',
-        new PhutilNumber(count($untracked)));
+        phutil_count($untracked));
 
       if (!phutil_console_confirm($prompt)) {
         throw new ArcanistUserAbortException();
@@ -1150,11 +1150,11 @@
     if ($this->getShouldAmend()) {
       $prompt = pht(
         'Do you want to amend these %s change(s) to the current commit?',
-        new PhutilNumber(count($files)));
+        phutil_count($files));
     } else {
       $prompt = pht(
         'Do you want to create a new commit with these %s change(s)?',
-        new PhutilNumber(count($files)));
+        phutil_count($files));
     }
     return $prompt;
   }