diff --git a/src/parser/__tests__/ArcanistBundleTestCase.php b/src/parser/__tests__/ArcanistBundleTestCase.php
--- a/src/parser/__tests__/ArcanistBundleTestCase.php
+++ b/src/parser/__tests__/ArcanistBundleTestCase.php
@@ -84,7 +84,9 @@
         $configuration_manager);
 
       $repository_api->setBaseCommitArgumentRules('arc:this');
-      $diff = $repository_api->getFullGitDiff();
+      $diff = $repository_api->getFullGitDiff(
+        $repository_api->getBaseCommit(),
+        $repository_api->getHeadCommit());
 
       $parser = new ArcanistDiffParser();
       $parser->setRepositoryAPI($repository_api);
diff --git a/src/repository/api/ArcanistGitAPI.php b/src/repository/api/ArcanistGitAPI.php
--- a/src/repository/api/ArcanistGitAPI.php
+++ b/src/repository/api/ArcanistGitAPI.php
@@ -16,6 +16,9 @@
    */
   const GIT_MAGIC_ROOT_COMMIT = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
 
+  private $symbolicHeadCommit = 'HEAD';
+  private $resolvedHeadCommit;
+
   public static function newHookAPI($root) {
     return new ArcanistGitAPI($root);
   }
@@ -75,6 +78,21 @@
     return !$this->repositoryHasNoCommits;
   }
 
+  /**
+   * Tests if a child commit is descendant of a parent commit.
+   * If child and parent are the same, it returns false.
+   * @param child commit SHA.
+   * @param parent commit SHA.
+   * @return bool
+   */
+  private function isDescendant($child, $parent) {
+    list($common_ancestor) =
+      $this->execxLocal('merge-base %s %s', $child, $parent);
+    $common_ancestor = trim($common_ancestor);
+
+    return $common_ancestor == $parent && $common_ancestor != $child;
+  }
+
   public function getLocalCommitInformation() {
     if ($this->repositoryHasNoCommits) {
       // Zero commits.
@@ -106,7 +124,16 @@
       // this as being the commits X and Y. If we log "B..Y", we only show
       // Y. With "Y --not B", we show X and Y.
 
-      $against = csprintf('%s --not %s', 'HEAD', $this->getBaseCommit());
+      $base_commit = $this->getBaseCommit();
+      $head_commit = $this->getHeadCommit();
+      if ($this->isDescendant($head_commit, $base_commit) === false) {
+        throw new ArcanistUsageException(
+          "base commit ${base_commit} is not a child of head commit ".
+          "${head_commit}");
+      }
+
+      $against = csprintf('%s --not %s',
+        $this->getHeadCommit(), $this->getBaseCommit());
     }
 
     // NOTE: Windows escaping of "%" symbols apparently is inherently broken;
@@ -161,8 +188,9 @@
       }
 
       list($err, $merge_base) = $this->execManualLocal(
-        'merge-base %s HEAD',
-        $symbolic_commit);
+        'merge-base %s %s',
+        $symbolic_commit,
+        $this->getHeadCommit());
       if ($err) {
         throw new ArcanistUsageException(
           "Unable to find any git commit named '{$symbolic_commit}' in ".
@@ -170,8 +198,8 @@
       }
 
       $this->setBaseCommitExplanation(
-        "it is the merge-base of '{$symbolic_commit}' and HEAD, as you ".
-        "explicitly specified.");
+        "it is the merge-base of '{$symbolic_commit}' and ".
+        "{$this->symbolicHeadCommit}, as you explicitly specified.");
       return trim($merge_base);
     }
 
@@ -301,6 +329,40 @@
     return trim($merge_base);
   }
 
+  public function getHeadCommit() {
+    if ($this->resolvedHeadCommit === null) {
+      $this->resolvedHeadCommit =
+        $this->resolveCommit($this->symbolicHeadCommit);
+    }
+
+    return $this->resolvedHeadCommit;
+  }
+
+  final public function setHeadCommit($symbolic_commit) {
+    $this->symbolicHeadCommit = $symbolic_commit;
+    $this->reloadCommitRange();
+    return $this;
+  }
+
+  /**
+   * Translates a symbolic commit (like "HEAD^") to a commit identifier.
+   * @param string_symbol commit.
+   * @return string the commit SHA.
+   */
+  private function resolveCommit($symbolic_commit) {
+    list($err, $commit_hash) = $this->execManualLocal(
+      'rev-parse %s',
+      $symbolic_commit);
+
+    if ($err) {
+      throw new ArcanistUsageException(
+        "Unable to find any git commit named '{$symbolic_commit}' in ".
+        "this repository.");
+    }
+
+    return trim($commit_hash);
+  }
+
   private function getDiffFullOptions($detect_moves_and_renames = true) {
     $options = array(
       self::getDiffBaseOptions(),
@@ -332,11 +394,22 @@
     return implode(' ', $options);
   }
 
-  public function getFullGitDiff() {
+  /**
+   * @param the base revision
+   * @param head revision. If this is null, the generated diff will include the
+   * working copy
+   */
+  public function getFullGitDiff($base, $head=null) {
     $options = $this->getDiffFullOptions();
+
+    $diff_revision = $base;
+    if ($head) {
+      $diff_revision .= '..'.$head;
+    }
+
     list($stdout) = $this->execxLocal(
       "diff {$options} %s --",
-      $this->getBaseCommit());
+      $diff_revision);
     return $stdout;
   }
 
@@ -401,8 +474,9 @@
     } else {
       // 2..N commits.
       list($stdout) = $this->execxLocal(
-        'log --first-parent --format=medium %s..HEAD',
-        $this->getBaseCommit());
+        'log --first-parent --format=medium %s..%s',
+        $this->getBaseCommit(),
+        $this->getHeadCommit());
     }
     return $stdout;
   }
@@ -821,7 +895,7 @@
   }
 
   public function getAllLocalChanges() {
-    $diff = $this->getFullGitDiff();
+    $diff = $this->getFullGitDiff($this->getBaseCommit());
     if (!strlen(trim($diff))) {
       return array();
     }
diff --git a/src/repository/api/ArcanistRepositoryAPI.php b/src/repository/api/ArcanistRepositoryAPI.php
--- a/src/repository/api/ArcanistRepositoryAPI.php
+++ b/src/repository/api/ArcanistRepositoryAPI.php
@@ -575,6 +575,10 @@
     return $this;
   }
 
+  public function setHeadCommit($symbolic_commit) {
+    throw new ArcanistCapabilityNotSupportedException($this);
+  }
+
   final public function getBaseCommit() {
     if (!$this->supportsCommitRanges()) {
       throw new ArcanistCapabilityNotSupportedException($this);
@@ -588,6 +592,10 @@
     return $this->resolvedBaseCommit;
   }
 
+  public function getHeadCommit() {
+    throw new ArcanistCapabilityNotSupportedException($this);
+  }
+
   final public function reloadCommitRange() {
     $this->resolvedBaseCommit = null;
     $this->baseCommitExplanation = null;
diff --git a/src/workflow/ArcanistDiffWorkflow.php b/src/workflow/ArcanistDiffWorkflow.php
--- a/src/workflow/ArcanistDiffWorkflow.php
+++ b/src/workflow/ArcanistDiffWorkflow.php
@@ -395,6 +395,18 @@
         ),
       ),
       '*' => 'paths',
+      'head' => array(
+        'param' => 'commit',
+        'help' => "specify the head commit.\n".
+          "This disables many Arcanist/Phabricator features which depend on ".
+          "having access to the working copy.",
+        'supports' => array('git'),
+        'conflicts' => array(
+          'lintall'   => '--head suppresses lint.',
+          'advice'    => '--head suppresses lint.',
+        ),
+
+      )
     );
 
     if (phutil_is_windows()) {
@@ -433,8 +445,19 @@
         array_unshift($argv, '--ansi');
       }
 
-      if ($this->getRepositoryAPI()->supportsCommitRanges()) {
-        $this->getRepositoryAPI()->getBaseCommit();
+      $repo = $this->getRepositoryAPI();
+      $head_commit = $this->getArgument('head');
+      $range_supported = $repo->supportsCommitRanges();
+      if ($head_commit) {
+        if (!$range_supported) {
+          throw new ArcanistUsageException('Ranged are not supported');
+        }
+
+        $repo->setHeadCommit($head_commit);
+      }
+
+      if ($range_supported) {
+        $repo->getBaseCommit();
       }
 
       $script = phutil_get_library_root('arcanist').'/../scripts/arcanist.php';
@@ -661,7 +684,9 @@
         if ($repository_api instanceof ArcanistSubversionAPI) {
           $repository_api->limitStatusToPaths($this->getArgument('paths'));
         }
-        $this->requireCleanWorkingCopy();
+        if (!$this->getArgument('head')) {
+          $this->requireCleanWorkingCopy();
+        }
       } catch (ArcanistUncommittedChangesException $ex) {
         if ($repository_api instanceof ArcanistMercurialAPI) {
           $use_dirty_changes = false;
@@ -937,7 +962,9 @@
         $repository_api,
         $paths);
     } else if ($repository_api instanceof ArcanistGitAPI) {
-      $diff = $repository_api->getFullGitDiff();
+      $diff = $repository_api->getFullGitDiff(
+        $repository_api->getBaseCommit(),
+        $repository_api->getHeadCommit());
       if (!strlen($diff)) {
         throw new ArcanistUsageException(
           'No changes found. (Did you specify the wrong commit range?)');
@@ -1216,7 +1243,8 @@
   private function runLint() {
     if ($this->getArgument('nolint') ||
         $this->getArgument('only') ||
-        $this->isRawDiffSource()) {
+        $this->isRawDiffSource() ||
+        $this->getArgument('head')) {
       return ArcanistLintWorkflow::RESULT_SKIP;
     }
 
@@ -1297,7 +1325,8 @@
   private function runUnit() {
     if ($this->getArgument('nounit') ||
         $this->getArgument('only') ||
-        $this->isRawDiffSource()) {
+        $this->isRawDiffSource() ||
+        $this->getArgument('head')) {
       return ArcanistUnitWorkflow::RESULT_SKIP;
     }
 
diff --git a/src/workflow/ArcanistExportWorkflow.php b/src/workflow/ArcanistExportWorkflow.php
--- a/src/workflow/ArcanistExportWorkflow.php
+++ b/src/workflow/ArcanistExportWorkflow.php
@@ -178,7 +178,9 @@
 
         if ($repository_api instanceof ArcanistGitAPI) {
           $this->parseBaseCommitArgument($this->getArgument('paths'));
-          $diff = $repository_api->getFullGitDiff();
+          $diff = $repository_api->getFullGitDiff(
+            $repository_api->getBaseCommit(),
+            $repository_api->getHeadCommit());
           $changes = $parser->parseDiff($diff);
           $authors = $this->getConduit()->callMethodSynchronous(
             'user.query',
diff --git a/src/workflow/ArcanistWhichWorkflow.php b/src/workflow/ArcanistWhichWorkflow.php
--- a/src/workflow/ArcanistWhichWorkflow.php
+++ b/src/workflow/ArcanistWhichWorkflow.php
@@ -61,6 +61,10 @@
         ),
         'supports' => array('git', 'hg'),
       ),
+      'head' => array(
+        'param' => 'commit',
+        'help' => 'specify the head commit.'
+      ),
       '*' => 'commit',
     );
   }
@@ -83,7 +87,19 @@
     $repository_api->setBaseCommitArgumentRules(
       $this->getArgument('base', ''));
 
-    if ($repository_api->supportsCommitRanges()) {
+    $supports_ranges = $repository_api->supportsCommitRanges();
+
+    if ($this->getArgument('head')) {
+      if ($supports_ranges === false) {
+        throw new Exception('--head is not supported in this VCS');
+      }
+
+      $head_commit = $this->getArgument('head');
+      $arg .= " --head {$head_commit}";
+      $repository_api->setHeadCommit($head_commit);
+    }
+
+    if ($supports_ranges) {
       $relative = $repository_api->getBaseCommit();
 
       if ($this->getArgument('show-base')) {
@@ -111,7 +127,8 @@
       $relative = substr($relative, 0, 16);
 
       if ($repository_api instanceof ArcanistGitAPI) {
-        $command = "git diff {$relative}..HEAD";
+        $head = $this->getArgument('head', 'HEAD');
+        $command = "git diff {$relative}..{$head}";
       } else if ($repository_api instanceof ArcanistMercurialAPI) {
         $command = "hg diff --rev {$relative}";
       } else {