/**
  * @task internal
  */
 private function buildJoinsClause($conn_r)
 {
     $joins = array();
     if ($this->pathIDs) {
         $path_table = new DifferentialAffectedPath();
         $joins[] = qsprintf($conn_r, 'JOIN %T p ON p.revisionID = r.id', $path_table->getTableName());
     }
     if ($this->commitHashes) {
         $joins[] = qsprintf($conn_r, 'JOIN %T hash_rel ON hash_rel.revisionID = r.id', ArcanistDifferentialRevisionHash::TABLE_NAME);
     }
     if ($this->ccs) {
         $joins[] = qsprintf($conn_r, 'JOIN %T cc_rel ON cc_rel.revisionID = r.id ' . 'AND cc_rel.relation = %s ' . 'AND cc_rel.objectPHID in (%Ls)', DifferentialRevision::RELATIONSHIP_TABLE, DifferentialRevision::RELATION_SUBSCRIBED, $this->ccs);
     }
     if ($this->reviewers) {
         $joins[] = qsprintf($conn_r, 'JOIN %T reviewer_rel ON reviewer_rel.revisionID = r.id ' . 'AND reviewer_rel.relation = %s ' . 'AND reviewer_rel.objectPHID in (%Ls)', DifferentialRevision::RELATIONSHIP_TABLE, DifferentialRevision::RELATION_REVIEWER, $this->reviewers);
     }
     if ($this->subscribers) {
         $joins[] = qsprintf($conn_r, 'JOIN %T sub_rel ON sub_rel.revisionID = r.id ' . 'AND sub_rel.relation IN (%Ls) ' . 'AND sub_rel.objectPHID in (%Ls)', DifferentialRevision::RELATIONSHIP_TABLE, array(DifferentialRevision::RELATION_SUBSCRIBED, DifferentialRevision::RELATION_REVIEWER), $this->subscribers);
     }
     if ($this->responsibles) {
         $joins[] = qsprintf($conn_r, 'LEFT JOIN %T responsibles_rel ON responsibles_rel.revisionID = r.id ' . 'AND responsibles_rel.relation = %s ' . 'AND responsibles_rel.objectPHID in (%Ls)', DifferentialRevision::RELATIONSHIP_TABLE, DifferentialRevision::RELATION_REVIEWER, $this->responsibles);
     }
     if ($this->draftAuthors) {
         $joins[] = qsprintf($conn_r, 'LEFT JOIN %T inline_comment ON inline_comment.revisionID = r.id ' . 'AND inline_comment.commentID IS NULL ' . 'AND inline_comment.authorPHID IN (%Ls)', id(new DifferentialInlineComment())->getTableName(), $this->draftAuthors);
     }
     $joins = implode(' ', $joins);
     return $joins;
 }
 /**
  * @task internal
  */
 private function buildJoinsClause($conn_r)
 {
     $joins = array();
     if ($this->pathIDs) {
         $path_table = new DifferentialAffectedPath();
         $joins[] = qsprintf($conn_r, 'JOIN %T p ON p.revisionID = r.id', $path_table->getTableName());
     }
     if ($this->commitHashes) {
         $joins[] = qsprintf($conn_r, 'JOIN %T hash_rel ON hash_rel.revisionID = r.id', ArcanistDifferentialRevisionHash::TABLE_NAME);
     }
     if ($this->ccs) {
         $joins[] = qsprintf($conn_r, 'JOIN %T e_ccs ON e_ccs.src = r.phid ' . 'AND e_ccs.type = %s ' . 'AND e_ccs.dst in (%Ls)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorEdgeConfig::TYPE_OBJECT_HAS_SUBSCRIBER, $this->ccs);
     }
     if ($this->reviewers) {
         $joins[] = qsprintf($conn_r, 'JOIN %T e_reviewers ON e_reviewers.src = r.phid ' . 'AND e_reviewers.type = %s ' . 'AND e_reviewers.dst in (%Ls)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER, $this->reviewers);
     }
     if ($this->draftAuthors) {
         $differential_draft = new DifferentialDraft();
         $joins[] = qsprintf($conn_r, 'JOIN %T has_draft ON has_draft.objectPHID = r.phid ' . 'AND has_draft.authorPHID IN (%Ls)', $differential_draft->getTableName(), $this->draftAuthors);
     }
     $joins = implode(' ', $joins);
     return $joins;
 }
 /**
  * Update the table which links Differential revisions to paths they affect,
  * so Diffusion can efficiently find pending revisions for a given file.
  */
 private function updateAffectedPathTable(DifferentialRevision $revision, DifferentialDiff $diff)
 {
     $repository = $revision->getRepository();
     if (!$repository) {
         // The repository where the code lives is untracked.
         return;
     }
     $path_prefix = null;
     $local_root = $diff->getSourceControlPath();
     if ($local_root) {
         // We're in a working copy which supports subdirectory checkouts (e.g.,
         // SVN) so we need to figure out what prefix we should add to each path
         // (e.g., trunk/projects/example/) to get the absolute path from the
         // root of the repository. DVCS systems like Git and Mercurial are not
         // affected.
         // Normalize both paths and check if the repository root is a prefix of
         // the local root. If so, throw it away. Note that this correctly handles
         // the case where the remote path is "/".
         $local_root = id(new PhutilURI($local_root))->getPath();
         $local_root = rtrim($local_root, '/');
         $repo_root = id(new PhutilURI($repository->getRemoteURI()))->getPath();
         $repo_root = rtrim($repo_root, '/');
         if (!strncmp($repo_root, $local_root, strlen($repo_root))) {
             $path_prefix = substr($local_root, strlen($repo_root));
         }
     }
     $changesets = $diff->getChangesets();
     $paths = array();
     foreach ($changesets as $changeset) {
         $paths[] = $path_prefix . '/' . $changeset->getFilename();
     }
     // Mark this as also touching all parent paths, so you can see all pending
     // changes to any file within a directory.
     $all_paths = array();
     foreach ($paths as $local) {
         foreach (DiffusionPathIDQuery::expandPathToRoot($local) as $path) {
             $all_paths[$path] = true;
         }
     }
     $all_paths = array_keys($all_paths);
     $path_ids = PhabricatorRepositoryCommitChangeParserWorker::lookupOrCreatePaths($all_paths);
     $table = new DifferentialAffectedPath();
     $conn_w = $table->establishConnection('w');
     $sql = array();
     foreach ($path_ids as $path_id) {
         $sql[] = qsprintf($conn_w, '(%d, %d, %d, %d)', $repository->getID(), $path_id, time(), $revision->getID());
     }
     queryfx($conn_w, 'DELETE FROM %T WHERE revisionID = %d', $table->getTableName(), $revision->getID());
     foreach (array_chunk($sql, 256) as $chunk) {
         queryfx($conn_w, 'INSERT INTO %T (repositoryID, pathID, epoch, revisionID) VALUES %Q', $table->getTableName(), implode(', ', $chunk));
     }
 }
 public function destroyObjectPermanently(PhabricatorDestructionEngine $engine)
 {
     $this->openTransaction();
     $diffs = id(new DifferentialDiffQuery())->setViewer($engine->getViewer())->withRevisionIDs(array($this->getID()))->execute();
     foreach ($diffs as $diff) {
         $engine->destroyObject($diff);
     }
     $conn_w = $this->establishConnection('w');
     queryfx($conn_w, 'DELETE FROM %T WHERE revisionID = %d', self::TABLE_COMMIT, $this->getID());
     // we have to do paths a little differentally as they do not have
     // an id or phid column for delete() to act on
     $dummy_path = new DifferentialAffectedPath();
     queryfx($conn_w, 'DELETE FROM %T WHERE revisionID = %d', $dummy_path->getTableName(), $this->getID());
     $this->delete();
     $this->saveTransaction();
 }
 /**
  * @task internal
  */
 private function buildJoinsClause($conn_r)
 {
     $joins = array();
     if ($this->pathIDs) {
         $path_table = new DifferentialAffectedPath();
         $joins[] = qsprintf($conn_r, 'JOIN %T p ON p.revisionID = r.id', $path_table->getTableName());
     }
     if ($this->commitHashes) {
         $joins[] = qsprintf($conn_r, 'JOIN %T hash_rel ON hash_rel.revisionID = r.id', ArcanistDifferentialRevisionHash::TABLE_NAME);
     }
     if ($this->ccs) {
         $joins[] = qsprintf($conn_r, 'JOIN %T e_ccs ON e_ccs.src = r.phid ' . 'AND e_ccs.type = %s ' . 'AND e_ccs.dst in (%Ls)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorObjectHasSubscriberEdgeType::EDGECONST, $this->ccs);
     }
     if ($this->reviewers) {
         $joins[] = qsprintf($conn_r, 'JOIN %T e_reviewers ON e_reviewers.src = r.phid ' . 'AND e_reviewers.type = %s ' . 'AND e_reviewers.dst in (%Ls)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, DifferentialRevisionHasReviewerEdgeType::EDGECONST, $this->reviewers);
     }
     if ($this->draftAuthors) {
         $differential_draft = new DifferentialDraft();
         $joins[] = qsprintf($conn_r, 'JOIN %T has_draft ON has_draft.objectPHID = r.phid ' . 'AND has_draft.authorPHID IN (%Ls)', $differential_draft->getTableName(), $this->draftAuthors);
     }
     if ($this->commitPHIDs) {
         $joins[] = qsprintf($conn_r, 'JOIN %T commits ON commits.revisionID = r.id', DifferentialRevision::TABLE_COMMIT);
     }
     $joins[] = $this->buildJoinClauseParts($conn_r);
     return $this->formatJoinClause($joins);
 }
 public function delete()
 {
     $this->openTransaction();
     $diffs = $this->loadDiffs();
     foreach ($diffs as $diff) {
         $diff->delete();
     }
     $conn_w = $this->establishConnection('w');
     queryfx($conn_w, 'DELETE FROM %T WHERE revisionID = %d', self::RELATIONSHIP_TABLE, $this->getID());
     queryfx($conn_w, 'DELETE FROM %T WHERE revisionID = %d', self::TABLE_COMMIT, $this->getID());
     $comments = id(new DifferentialComment())->loadAllWhere('revisionID = %d', $this->getID());
     foreach ($comments as $comment) {
         $comment->delete();
     }
     $inlines = id(new DifferentialInlineComment())->loadAllWhere('revisionID = %d', $this->getID());
     foreach ($inlines as $inline) {
         $inline->delete();
     }
     $fields = id(new DifferentialAuxiliaryField())->loadAllWhere('revisionPHID = %s', $this->getPHID());
     foreach ($fields as $field) {
         $field->delete();
     }
     // we have to do paths a little differentally as they do not have
     // an id or phid column for delete() to act on
     $dummy_path = new DifferentialAffectedPath();
     queryfx($conn_w, 'DELETE FROM %T WHERE revisionID = %d', $dummy_path->getTableName(), $this->getID());
     $result = parent::delete();
     $this->saveTransaction();
     return $result;
 }
 /**
  * Update the table which links Differential revisions to paths they affect,
  * so Diffusion can efficiently find pending revisions for a given file.
  */
 private function updateAffectedPathTable(DifferentialRevision $revision, DifferentialDiff $diff, array $changesets)
 {
     assert_instances_of($changesets, 'DifferentialChangeset');
     $project = $diff->loadArcanistProject();
     if (!$project) {
         // Probably an old revision from before projects.
         return;
     }
     $repository = $project->loadRepository();
     if (!$repository) {
         // Probably no project <-> repository link, or the repository where the
         // project lives is untracked.
         return;
     }
     $path_prefix = null;
     $local_root = $diff->getSourceControlPath();
     if ($local_root) {
         // We're in a working copy which supports subdirectory checkouts (e.g.,
         // SVN) so we need to figure out what prefix we should add to each path
         // (e.g., trunk/projects/example/) to get the absolute path from the
         // root of the repository. DVCS systems like Git and Mercurial are not
         // affected.
         // Normalize both paths and check if the repository root is a prefix of
         // the local root. If so, throw it away. Note that this correctly handles
         // the case where the remote path is "/".
         $local_root = id(new PhutilURI($local_root))->getPath();
         $local_root = rtrim($local_root, '/');
         $repo_root = id(new PhutilURI($repository->getRemoteURI()))->getPath();
         $repo_root = rtrim($repo_root, '/');
         if (!strncmp($repo_root, $local_root, strlen($repo_root))) {
             $path_prefix = substr($local_root, strlen($repo_root));
         }
     }
     $paths = array();
     foreach ($changesets as $changeset) {
         $paths[] = $path_prefix . '/' . $changeset->getFilename();
     }
     // Mark this as also touching all parent paths, so you can see all pending
     // changes to any file within a directory.
     $all_paths = array();
     foreach ($paths as $local) {
         foreach (DiffusionPathIDQuery::expandPathToRoot($local) as $path) {
             $all_paths[$path] = true;
         }
     }
     $all_paths = array_keys($all_paths);
     $path_map = id(new DiffusionPathIDQuery($all_paths))->loadPathIDs();
     $table = new DifferentialAffectedPath();
     $conn_w = $table->establishConnection('w');
     $sql = array();
     foreach ($all_paths as $path) {
         $path_id = idx($path_map, $path);
         if (!$path_id) {
             // Don't bother creating these, it probably means we're either adding
             // a file (in which case having this row is irrelevant since Diffusion
             // won't be querying for it) or something is misconfigured (in which
             // case we'd just be writing garbage).
             continue;
         }
         $sql[] = qsprintf($conn_w, '(%d, %d, %d, %d)', $repository->getID(), $path_id, time(), $revision->getID());
     }
     queryfx($conn_w, 'DELETE FROM %T WHERE revisionID = %d', $table->getTableName(), $revision->getID());
     foreach (array_chunk($sql, 256) as $chunk) {
         queryfx($conn_w, 'INSERT INTO %T (repositoryID, pathID, epoch, revisionID) VALUES %Q', $table->getTableName(), implode(', ', $chunk));
     }
 }
 public function destroyObjectPermanently(PhabricatorDestructionEngine $engine)
 {
     $this->openTransaction();
     $diffs = id(new DifferentialDiffQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withRevisionIDs(array($this->getID()))->execute();
     foreach ($diffs as $diff) {
         $engine->destroyObject($diff);
     }
     $conn_w = $this->establishConnection('w');
     queryfx($conn_w, 'DELETE FROM %T WHERE revisionID = %d', self::TABLE_COMMIT, $this->getID());
     try {
         $inlines = id(new DifferentialInlineCommentQuery())->withRevisionIDs(array($this->getID()))->execute();
         foreach ($inlines as $inline) {
             $inline->delete();
         }
     } catch (PhabricatorEmptyQueryException $ex) {
         // TODO: There's still some funky legacy wrapping going on here, and
         // we might catch a raw query exception.
     }
     // we have to do paths a little differentally as they do not have
     // an id or phid column for delete() to act on
     $dummy_path = new DifferentialAffectedPath();
     queryfx($conn_w, 'DELETE FROM %T WHERE revisionID = %d', $dummy_path->getTableName(), $this->getID());
     $this->delete();
     $this->saveTransaction();
 }