diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -816,6 +816,7 @@
     'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php',
     'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php',
     'DiffusionInlineCommentPreviewController' => 'applications/diffusion/controller/DiffusionInlineCommentPreviewController.php',
+    'DiffusionInternalAncestorsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php',
     'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php',
     'DiffusionLastModifiedController' => 'applications/diffusion/controller/DiffusionLastModifiedController.php',
     'DiffusionLastModifiedQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php',
@@ -6149,6 +6150,7 @@
     'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
     'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController',
     'DiffusionInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController',
+    'DiffusionInternalAncestorsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionLastModifiedController' => 'DiffusionController',
     'DiffusionLastModifiedQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
diff --git a/src/applications/audit/query/PhabricatorCommitSearchEngine.php b/src/applications/audit/query/PhabricatorCommitSearchEngine.php
--- a/src/applications/audit/query/PhabricatorCommitSearchEngine.php
+++ b/src/applications/audit/query/PhabricatorCommitSearchEngine.php
@@ -53,6 +53,10 @@
       $query->withUnreachable($map['unreachable']);
     }
 
+    if ($map['ancestorsOf']) {
+      $query->withAncestorsOf($map['ancestorsOf']);
+    }
+
     return $query;
   }
 
@@ -103,6 +107,13 @@
           pht(
             'Find or exclude unreachable commits which are not ancestors of '.
             'any branch, tag, or ref.')),
+      id(new PhabricatorSearchStringListField())
+        ->setLabel(pht('Ancestors Of'))
+        ->setKey('ancestorsOf')
+        ->setDescription(
+          pht(
+            'Find commits which are ancestors of a particular ref, '.
+            'like "master".')),
     );
   }
 
diff --git a/src/applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php
new file mode 100644
--- /dev/null
+++ b/src/applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php
@@ -0,0 +1,51 @@
+<?php
+
+final class DiffusionInternalAncestorsConduitAPIMethod
+  extends DiffusionQueryConduitAPIMethod {
+
+  public function isInternalAPI() {
+    return true;
+  }
+
+  public function getAPIMethodName() {
+    return 'diffusion.internal.ancestors';
+  }
+
+  public function getMethodDescription() {
+    return pht('Internal method for filtering ref ancestors.');
+  }
+
+  protected function defineReturnType() {
+    return 'list<string>';
+  }
+
+  protected function defineCustomParamTypes() {
+    return array(
+      'ref' => 'required string',
+      'commits' => 'required list<string>',
+    );
+  }
+
+  protected function getResult(ConduitAPIRequest $request) {
+    $drequest = $this->getDiffusionRequest();
+    $repository = $drequest->getRepository();
+
+    $commits = $request->getValue('commits');
+    $ref = $request->getValue('ref');
+
+    $graph = new PhabricatorGitGraphStream($repository, $ref);
+
+    $keep = array();
+    foreach ($commits as $identifier) {
+      try {
+        $graph->getCommitDate($identifier);
+        $keep[] = $identifier;
+      } catch (Exception $ex) {
+        // Not an ancestor.
+      }
+    }
+
+    return $keep;
+  }
+
+}
diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php
--- a/src/applications/diffusion/query/DiffusionCommitQuery.php
+++ b/src/applications/diffusion/query/DiffusionCommitQuery.php
@@ -22,10 +22,14 @@
   private $epochMin;
   private $epochMax;
   private $importing;
+  private $ancestorsOf;
 
   private $needCommitData;
   private $needDrafts;
 
+  private $mustFilterRefs = false;
+  private $refRepository;
+
   public function withIDs(array $ids) {
     $this->ids = $ids;
     return $this;
@@ -92,7 +96,7 @@
   }
 
   public function withRepositoryIDs(array $repository_ids) {
-    $this->repositoryIDs = $repository_ids;
+    $this->repositoryIDs = array_unique($repository_ids);
     return $this;
   }
 
@@ -152,6 +156,11 @@
     return $this;
   }
 
+  public function withAncestorsOf(array $refs) {
+    $this->ancestorsOf = $refs;
+    return $this;
+  }
+
   public function getIdentifierMap() {
     if ($this->identifierMap === null) {
       throw new Exception(
@@ -307,6 +316,54 @@
   protected function didFilterPage(array $commits) {
     $viewer = $this->getViewer();
 
+    if ($this->mustFilterRefs) {
+      // If this flag is set, the query has an "Ancestors Of" constraint and
+      // at least one of the constraining refs had too many ancestors for us
+      // to apply the constraint with a big "commitIdentifier IN (%Ls)" clause.
+      // We're going to filter each page and hope we get a full result set
+      // before the query overheats.
+
+      $ancestor_list = mpull($commits, 'getCommitIdentifier');
+      $ancestor_list = array_values($ancestor_list);
+
+      foreach ($this->ancestorsOf as $ref) {
+        try {
+          $ancestor_list = DiffusionQuery::callConduitWithDiffusionRequest(
+            $viewer,
+            DiffusionRequest::newFromDictionary(
+              array(
+                'repository' => $this->refRepository,
+                'user' => $viewer,
+              )),
+            'diffusion.internal.ancestors',
+            array(
+              'ref' => $ref,
+              'commits' => $ancestor_list,
+            ));
+        } catch (ConduitClientException $ex) {
+          throw new PhabricatorSearchConstraintException(
+            $ex->getMessage());
+        }
+
+        if (!$ancestor_list) {
+          break;
+        }
+      }
+
+      $ancestor_list = array_fuse($ancestor_list);
+      foreach ($commits as $key => $commit) {
+        $identifier = $commit->getCommitIdentifier();
+        if (!isset($ancestor_list[$identifier])) {
+          $this->didRejectResult($commit);
+          unset($commits[$key]);
+        }
+      }
+
+      if (!$commits) {
+        return $commits;
+      }
+    }
+
     if ($this->needCommitData) {
       $data = id(new PhabricatorRepositoryCommitData())->loadAllWhere(
         'commitID in (%Ld)',
@@ -364,6 +421,95 @@
       $this->withRepositoryIDs($repository_ids);
     }
 
+    if ($this->ancestorsOf !== null) {
+      if (count($this->repositoryIDs) !== 1) {
+        throw new PhabricatorSearchConstraintException(
+          pht(
+            'To search for commits which are ancestors of particular refs, '.
+            'you must constrain the search to exactly one repository.'));
+      }
+
+      $repository_id = head($this->repositoryIDs);
+      $history_limit = $this->getRawResultLimit() * 32;
+      $viewer = $this->getViewer();
+
+      $repository = id(new PhabricatorRepositoryQuery())
+        ->setViewer($viewer)
+        ->withIDs(array($repository_id))
+        ->executeOne();
+
+      if (!$repository) {
+        throw new PhabricatorEmptyQueryException();
+      }
+
+      if ($repository->isSVN()) {
+        throw new PhabricatorSearchConstraintException(
+          pht(
+            'Subversion does not support searching for ancestors of '.
+            'a particular ref. This operation is not meaningful in '.
+            'Subversion.'));
+      }
+
+      if ($repository->isHg()) {
+        throw new PhabricatorSearchConstraintException(
+          pht(
+            'Mercurial does not currently support searching for ancestors of '.
+            'a particular ref.'));
+      }
+
+      $can_constrain = true;
+      $history_identifiers = array();
+      foreach ($this->ancestorsOf as $key => $ref) {
+        try {
+          $raw_history = DiffusionQuery::callConduitWithDiffusionRequest(
+            $viewer,
+            DiffusionRequest::newFromDictionary(
+              array(
+                'repository' => $repository,
+                'user' => $viewer,
+              )),
+            'diffusion.historyquery',
+            array(
+              'commit' => $ref,
+              'limit' => $history_limit,
+            ));
+        } catch (ConduitClientException $ex) {
+          throw new PhabricatorSearchConstraintException(
+            $ex->getMessage());
+        }
+
+        $ref_identifiers = array();
+        foreach ($raw_history['pathChanges'] as $change) {
+          $ref_identifiers[] = $change['commitIdentifier'];
+        }
+
+        // If this ref had fewer total commits than the limit, we're safe to
+        // apply the constraint as a large `IN (...)` query for a list of
+        // commit identifiers. This is efficient.
+        if ($history_limit) {
+          if (count($ref_identifiers) >= $history_limit) {
+            $can_constrain = false;
+            break;
+          }
+        }
+
+        $history_identifiers += array_fuse($ref_identifiers);
+      }
+
+      // If all refs had a small number of ancestors, we can just put the
+      // constraint into the query here and we're done. Otherwise, we need
+      // to filter each page after it comes out of the MySQL layer.
+      if ($can_constrain) {
+        $where[] = qsprintf(
+          $conn,
+          'commit.commitIdentifier IN (%Ls)',
+          $history_identifiers);
+      } else {
+        $this->mustFilterRefs = true;
+        $this->refRepository = $repository;
+      }
+    }
+
     if ($this->ids !== null) {
       $where[] = qsprintf(
         $conn,