protected function getCommitHashes(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { list($stdout) = $repository->execxLocalCommand('log -n 1 --format=%s %s --', '%T', $commit->getCommitIdentifier()); $commit_hash = $commit->getCommitIdentifier(); $tree_hash = trim($stdout); return array(array(ArcanistDifferentialRevisionHash::HASH_GIT_COMMIT, $commit_hash), array(ArcanistDifferentialRevisionHash::HASH_GIT_TREE, $tree_hash)); }
private function getBlockers(PhabricatorRepositoryCommit $commit, HarbormasterBuildPlan $plan, HarbormasterBuild $source) { $call = new ConduitCall('diffusion.commitparentsquery', array('commit' => $commit->getCommitIdentifier(), 'repository' => $commit->getRepository()->getPHID())); $call->setUser(PhabricatorUser::getOmnipotentUser()); $parents = $call->execute(); $parents = id(new DiffusionCommitQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withRepository($commit->getRepository())->withIdentifiers($parents)->execute(); $blockers = array(); $build_objects = array(); foreach ($parents as $parent) { if (!$parent->isImported()) { $blockers[] = pht('Commit %s', $parent->getCommitIdentifier()); } else { $build_objects[] = $parent->getPHID(); } } if ($build_objects) { $buildables = id(new HarbormasterBuildableQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withBuildablePHIDs($build_objects)->withManualBuildables(false)->execute(); $buildable_phids = mpull($buildables, 'getPHID'); if ($buildable_phids) { $builds = id(new HarbormasterBuildQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withBuildablePHIDs($buildable_phids)->withBuildPlanPHIDs(array($plan->getPHID()))->execute(); foreach ($builds as $build) { if (!$build->isComplete()) { $blockers[] = pht('Build %d', $build->getID()); } } } } return $blockers; }
public static function renderLastModifiedColumns(PhabricatorRepository $repository, array $handles, PhabricatorRepositoryCommit $commit = null, PhabricatorRepositoryCommitData $data = null) { if ($commit) { $epoch = $commit->getEpoch(); $modified = DiffusionView::linkCommit($repository, $commit->getCommitIdentifier()); $date = date('M j, Y', $epoch); $time = date('g:i A', $epoch); } else { $modified = ''; $date = ''; $time = ''; } if ($data) { $author_phid = $data->getCommitDetail('authorPHID'); if ($author_phid && isset($handles[$author_phid])) { $author = $handles[$author_phid]->renderLink(); } else { $author = phutil_escape_html($data->getAuthorName()); } $details = AphrontTableView::renderSingleDisplayLine(phutil_escape_html($data->getSummary())); } else { $author = ''; $details = ''; } return array('commit' => $modified, 'date' => $date, 'time' => $time, 'author' => $author, 'details' => $details); }
public static function indexCommit(PhabricatorRepositoryCommit $commit) { $commit_data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $commit->getID()); $date_created = $commit->getEpoch(); $commit_message = $commit_data->getCommitMessage(); $author_phid = $commit_data->getCommitDetail('authorPHID'); $repository = id(new PhabricatorRepository())->loadOneWhere('id = %d', $commit->getRepositoryID()); if (!$repository) { return; } $title = 'r' . $repository->getCallsign() . $commit->getCommitIdentifier() . " " . $commit_data->getSummary(); $doc = new PhabricatorSearchAbstractDocument(); $doc->setPHID($commit->getPHID()); $doc->setDocumentType(PhabricatorPHIDConstants::PHID_TYPE_CMIT); $doc->setDocumentCreated($date_created); $doc->setDocumentModified($date_created); $doc->setDocumentTitle($title); $doc->addField(PhabricatorSearchField::FIELD_BODY, $commit_message); if ($author_phid) { $doc->addRelationship(PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, $author_phid, PhabricatorPHIDConstants::PHID_TYPE_USER, $date_created); } $doc->addRelationship(PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY, $repository->getPHID(), PhabricatorPHIDConstants::PHID_TYPE_REPO, $date_created); $comments = id(new PhabricatorAuditComment())->loadAllWhere('targetPHID = %s', $commit->getPHID()); foreach ($comments as $comment) { if (strlen($comment->getContent())) { $doc->addField(PhabricatorSearchField::FIELD_COMMENT, $comment->getContent()); } } self::reindexAbstractDocument($doc); }
public function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { $ref = id(new DiffusionLowLevelCommitQuery())->setRepository($repository)->withIdentifier($commit->getCommitIdentifier())->execute(); $this->updateCommitData($ref); if ($this->shouldQueueFollowupTasks()) { $this->queueTask('PhabricatorRepositorySvnCommitChangeParserWorker', array('commitID' => $commit->getID())); } }
public function didParseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data) { $user = id(new PhabricatorUser())->loadOneWhere('phid = %s', $data->getCommitDetail('authorPHID')); if (!$user) { return; } $prefixes = array('resolves' => ManiphestTaskStatus::STATUS_CLOSED_RESOLVED, 'fixes' => ManiphestTaskStatus::STATUS_CLOSED_RESOLVED, 'wontfix' => ManiphestTaskStatus::STATUS_CLOSED_WONTFIX, 'wontfixes' => ManiphestTaskStatus::STATUS_CLOSED_WONTFIX, 'spite' => ManiphestTaskStatus::STATUS_CLOSED_SPITE, 'spites' => ManiphestTaskStatus::STATUS_CLOSED_SPITE, 'invalidate' => ManiphestTaskStatus::STATUS_CLOSED_INVALID, 'invaldiates' => ManiphestTaskStatus::STATUS_CLOSED_INVALID, 'close' => ManiphestTaskStatus::STATUS_CLOSED_RESOLVED, 'closes' => ManiphestTaskStatus::STATUS_CLOSED_RESOLVED, 'ref' => null, 'refs' => null, 'references' => null, 'cf.' => null); $suffixes = array('as resolved' => ManiphestTaskStatus::STATUS_CLOSED_RESOLVED, 'as fixed' => ManiphestTaskStatus::STATUS_CLOSED_RESOLVED, 'as wontfix' => ManiphestTaskStatus::STATUS_CLOSED_WONTFIX, 'as spite' => ManiphestTaskStatus::STATUS_CLOSED_SPITE, 'out of spite' => ManiphestTaskStatus::STATUS_CLOSED_SPITE, 'as invalid' => ManiphestTaskStatus::STATUS_CLOSED_INVALID, '' => null); $prefix_regex = array(); foreach ($prefixes as $prefix => $resolution) { $prefix_regex[] = preg_quote($prefix, '/'); } $prefix_regex = implode('|', $prefix_regex); $suffix_regex = array(); foreach ($suffixes as $suffix => $resolution) { $suffix_regex[] = preg_quote($suffix, '/'); } $suffix_regex = implode('|', $suffix_regex); $matches = null; $ok = preg_match_all("/({$prefix_regex})\\s+T(\\d+)\\s*({$suffix_regex})/i", $this->renderValueForCommitMessage($is_edit = false), $matches, PREG_SET_ORDER); if (!$ok) { return; } foreach ($matches as $set) { $prefix = strtolower($set[1]); $task_id = (int) $set[2]; $suffix = strtolower($set[3]); $status = idx($suffixes, $suffix); if (!$status) { $status = idx($prefixes, $prefix); } $tasks = id(new ManiphestTaskQuery())->withTaskIDs(array($task_id))->execute(); $task = idx($tasks, $task_id); if (!$task) { // Task doesn't exist, or the user can't see it. continue; } id(new PhabricatorEdgeEditor())->setUser($user)->addEdge($task->getPHID(), PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT, $commit->getPHID())->save(); if (!$status) { // Text like "Ref T123", don't change the task status. continue; } if ($task->getStatus() != ManiphestTaskStatus::STATUS_OPEN) { // Task is already closed. continue; } $commit_name = $repository->formatCommitName($commit->getCommitIdentifier()); $call = new ConduitCall('maniphest.update', array('id' => $task->getID(), 'status' => $status, 'comments' => "Closed by commit {$commit_name}.")); $call->setUser($user); $call->execute(); } }
private function renderColumns(DiffusionRequest $drequest, array $handles, PhabricatorRepositoryCommit $commit = null, $lint = null) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $viewer = $this->getRequest()->getUser(); if ($commit) { $epoch = $commit->getEpoch(); $modified = DiffusionView::linkCommit($drequest->getRepository(), $commit->getCommitIdentifier()); $date = phabricator_date($epoch, $viewer); $time = phabricator_time($epoch, $viewer); } else { $modified = ''; $date = ''; $time = ''; } $data = $commit->getCommitData(); if ($data) { $author_phid = $data->getCommitDetail('authorPHID'); if ($author_phid && isset($handles[$author_phid])) { $author = $handles[$author_phid]->renderLink(); } else { $author = DiffusionView::renderName($data->getAuthorName()); } $committer = $data->getCommitDetail('committer'); if ($committer) { $committer_phid = $data->getCommitDetail('committerPHID'); if ($committer_phid && isset($handles[$committer_phid])) { $committer = $handles[$committer_phid]->renderLink(); } else { $committer = DiffusionView::renderName($committer); } if ($author != $committer) { $author = hsprintf('%s/%s', $author, $committer); } } $details = AphrontTableView::renderSingleDisplayLine($data->getSummary()); } else { $author = ''; $details = ''; } $return = array('commit' => $modified, 'date' => $date, 'time' => $time, 'author' => $author, 'details' => $details); if ($lint !== null) { $return['lint'] = phutil_tag('a', array('href' => $drequest->generateURI(array('action' => 'lint', 'lint' => null))), number_format($lint)); } // The client treats these results as markup, so make sure they have been // escaped correctly. foreach ($return as $key => $value) { $return[$key] = hsprintf('%s', $value); } return $return; }
public function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { $uri = $repository->getDetail('remote-uri'); $log = $this->getSVNLogXMLObject($uri, $commit->getCommitIdentifier(), $verbose = false); $entry = $log->logentry[0]; $author = (string) $entry->author; $message = (string) $entry->msg; $this->updateCommitData($author, $message); if ($this->shouldQueueFollowupTasks()) { $task = new PhabricatorWorkerTask(); $task->setTaskClass('PhabricatorRepositorySvnCommitChangeParserWorker'); $task->setData(array('commitID' => $commit->getID())); $task->save(); } }
public static function loadAffectedPaths(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit, PhabricatorUser $user) { $drequest = DiffusionRequest::newFromDictionary(array('user' => $user, 'repository' => $repository, 'commit' => $commit->getCommitIdentifier())); $path_query = DiffusionPathChangeQuery::newFromDiffusionRequest($drequest); $paths = $path_query->loadChanges(); $result = array(); foreach ($paths as $path) { $basic_path = '/' . $path->getPath(); if ($path->getFileType() == DifferentialChangeType::FILE_DIRECTORY) { $basic_path = rtrim($basic_path, '/') . '/'; } $result[] = $basic_path; } return $result; }
protected function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { $identifier = $commit->getCommitIdentifier(); $callsign = $repository->getCallsign(); $full_name = 'r' . $callsign . $identifier; $this->log("Parsing %s...\n", $full_name); if ($this->isBadCommit($full_name)) { $this->log('This commit is marked bad!'); return; } $results = $this->parseCommitChanges($repository, $commit); if ($results) { $this->writeCommitChanges($repository, $commit, $results); } $this->finishParse(); }
public function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { $local_path = $repository->getDetail('local-path'); // NOTE: %B was introduced somewhat recently in git's history, so pull // commit message information with %s and %b instead. list($info) = execx('(cd %s && git log -n 1 --pretty=format:%%an%%x00%%s%%n%%n%%b %s)', $local_path, $commit->getCommitIdentifier()); list($author, $message) = explode("", $info); // Make sure these are valid UTF-8. $author = phutil_utf8ize($author); $message = phutil_utf8ize($message); $message = trim($message); $this->updateCommitData($author, $message); if ($this->shouldQueueFollowupTasks()) { $task = new PhabricatorWorkerTask(); $task->setTaskClass('PhabricatorRepositoryGitCommitChangeParserWorker'); $task->setData(array('commitID' => $commit->getID())); $task->save(); } }
private function loadRawPatchText(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { $drequest = DiffusionRequest::newFromDictionary(array('user' => PhabricatorUser::getOmnipotentUser(), 'repository' => $repository, 'commit' => $commit->getCommitIdentifier())); $raw_query = DiffusionRawDiffQuery::newFromDiffusionRequest($drequest); $raw_query->setLinesOfContext(3); $time_key = 'metamta.diffusion.time-limit'; $byte_key = 'metamta.diffusion.byte-limit'; $time_limit = PhabricatorEnv::getEnvConfig($time_key); $byte_limit = PhabricatorEnv::getEnvConfig($byte_key); if ($time_limit) { $raw_query->setTimeout($time_limit); } $raw_diff = $raw_query->loadRawDiff(); $size = strlen($raw_diff); if ($byte_limit && $size > $byte_limit) { $pretty_size = phutil_format_bytes($size); $pretty_limit = phutil_format_bytes($byte_limit); throw new Exception(pht('Patch size of %s exceeds configured byte size limit (%s) of %s.', $pretty_size, $byte_key, $pretty_limit)); } return $raw_diff; }
public static function getMailThreading(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { return array('diffusion-audit-' . $commit->getPHID(), 'Commit r' . $repository->getCallsign() . $commit->getCommitIdentifier()); }
private function writeBrowse(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit, array $effects, array $path_map) { $conn_w = $repository->establishConnection('w'); $sql = array(); foreach ($effects as $effect) { $type = $effect['changeType']; if (!$effect['rawDirect']) { if ($type == DifferentialChangeType::TYPE_COPY_AWAY) { // Don't write COPY_AWAY to the filesystem table if it isn't a direct // event. continue; } if ($type == DifferentialChangeType::TYPE_CHILD) { // Don't write CHILD to the filesystem table. Although doing these // writes has the nice property of letting you see when a directory's // contents were last changed, it explodes the table tremendously // and makes Diffusion far slower. continue; } } if ($effect['rawPath'] == '/') { // Don't write any events on '/' to the filesystem table; in // particular, it doesn't have a meaningful parentID. continue; } $existed = !DifferentialChangeType::isDeleteChangeType($type); $sql[] = qsprintf($conn_w, '(%d, %d, %d, %d, %d, %d)', $repository->getID(), $path_map[$this->getParentPath($effect['rawPath'])], $commit->getCommitIdentifier(), $path_map[$effect['rawPath']], $existed ? 1 : 0, $effect['fileType']); } queryfx($conn_w, 'DELETE FROM %T WHERE repositoryID = %d AND svnCommit = %d', PhabricatorRepository::TABLE_FILESYSTEM, $repository->getID(), $commit->getCommitIdentifier()); foreach (array_chunk($sql, 512) as $sql_chunk) { queryfx($conn_w, 'INSERT INTO %T (repositoryID, parentID, svnCommit, pathID, existed, fileType) VALUES %Q', PhabricatorRepository::TABLE_FILESYSTEM, implode(', ', $sql_chunk)); } }
protected function loadCommitHint(PhabricatorRepositoryCommit $commit) { $viewer = PhabricatorUser::getOmnipotentUser(); $repository = $commit->getRepository(); return id(new DiffusionCommitHintQuery())->setViewer($viewer)->withRepositoryPHIDs(array($repository->getPHID()))->withOldCommitIdentifiers(array($commit->getCommitIdentifier()))->executeOne(); }
private static function buildDiffusionRequest(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { return DiffusionRequest::newFromAphrontRequestDictionary(array('callsign' => $repository->getCallsign(), 'commit' => $commit->getCommitIdentifier())); }
public function isDiffChangedBeforeCommit(PhabricatorRepositoryCommit $commit, DifferentialDiff $old, DifferentialDiff $new) { $viewer = $this->getViewer(); $repository = $commit->getRepository(); $identifier = $commit->getCommitIdentifier(); $vs_changesets = array(); foreach ($old->getChangesets() as $changeset) { $path = $changeset->getAbsoluteRepositoryPath($repository, $old); $path = ltrim($path, '/'); $vs_changesets[$path] = $changeset; } $changesets = array(); foreach ($new->getChangesets() as $changeset) { $path = $changeset->getAbsoluteRepositoryPath($repository, $new); $path = ltrim($path, '/'); $changesets[$path] = $changeset; } if (array_fill_keys(array_keys($changesets), true) != array_fill_keys(array_keys($vs_changesets), true)) { return true; } $file_phids = array(); foreach ($vs_changesets as $changeset) { $metadata = $changeset->getMetadata(); $file_phid = idx($metadata, 'new:binary-phid'); if ($file_phid) { $file_phids[$file_phid] = $file_phid; } } $files = array(); if ($file_phids) { $files = id(new PhabricatorFileQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withPHIDs($file_phids)->execute(); $files = mpull($files, null, 'getPHID'); } foreach ($changesets as $path => $changeset) { $vs_changeset = $vs_changesets[$path]; $file_phid = idx($vs_changeset->getMetadata(), 'new:binary-phid'); if ($file_phid) { if (!isset($files[$file_phid])) { return true; } $drequest = DiffusionRequest::newFromDictionary(array('user' => $viewer, 'repository' => $repository)); $response = DiffusionQuery::callConduitWithDiffusionRequest($viewer, $drequest, 'diffusion.filecontentquery', array('commit' => $identifier, 'path' => $path)); $new_file_phid = $response['filePHID']; if (!$new_file_phid) { return true; } $new_file = id(new PhabricatorFileQuery())->setViewer($viewer)->withPHIDs(array($new_file_phid))->executeOne(); if (!$new_file) { return true; } if ($files[$file_phid]->loadFileData() != $new_file->loadFileData()) { return true; } } else { $context = implode("\n", $changeset->makeChangesWithContext()); $vs_context = implode("\n", $vs_changeset->makeChangesWithContext()); // We couldn't just compare $context and $vs_context because following // diffs will be considered different: // // -(empty line) // -echo 'test'; // (empty line) // // (empty line) // -echo "test"; // -(empty line) $hunk = id(new DifferentialModernHunk())->setChanges($context); $vs_hunk = id(new DifferentialModernHunk())->setChanges($vs_context); if ($hunk->makeOldFile() != $vs_hunk->makeOldFile() || $hunk->makeNewFile() != $vs_hunk->makeNewFile()) { return true; } } } return false; }
private function closeTasks(PhabricatorUser $actor, $acting_as, PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit, $message) { $maniphest = 'PhabricatorManiphestApplication'; if (!PhabricatorApplication::isClassInstalled($maniphest)) { return; } $prefixes = ManiphestTaskStatus::getStatusPrefixMap(); $suffixes = ManiphestTaskStatus::getStatusSuffixMap(); $matches = id(new ManiphestCustomFieldStatusParser())->parseCorpus($message); $task_statuses = array(); foreach ($matches as $match) { $prefix = phutil_utf8_strtolower($match['prefix']); $suffix = phutil_utf8_strtolower($match['suffix']); $status = idx($suffixes, $suffix); if (!$status) { $status = idx($prefixes, $prefix); } foreach ($match['monograms'] as $task_monogram) { $task_id = (int) trim($task_monogram, 'tT'); $task_statuses[$task_id] = $status; } } if (!$task_statuses) { return; } $tasks = id(new ManiphestTaskQuery())->setViewer($actor)->withIDs(array_keys($task_statuses))->execute(); foreach ($tasks as $task_id => $task) { $xactions = array(); $edge_type = ManiphestTaskHasCommitEdgeType::EDGECONST; $xactions[] = id(new ManiphestTransaction())->setTransactionType(PhabricatorTransactions::TYPE_EDGE)->setMetadataValue('edge:type', $edge_type)->setNewValue(array('+' => array($commit->getPHID() => $commit->getPHID()))); $status = $task_statuses[$task_id]; if ($status) { if ($task->getStatus() != $status) { $xactions[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_STATUS)->setNewValue($status); $commit_name = $repository->formatCommitName($commit->getCommitIdentifier()); $status_message = pht('Closed by commit %s.', $commit_name); $xactions[] = id(new ManiphestTransaction())->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)->attachComment(id(new ManiphestTransactionComment())->setContent($status_message)); } } $content_source = PhabricatorContentSource::newForSource(PhabricatorContentSource::SOURCE_DAEMON, array()); $editor = id(new ManiphestTransactionEditor())->setActor($actor)->setActingAsPHID($acting_as)->setContinueOnNoEffect(true)->setContinueOnMissingFields(true)->setContentSource($content_source); $editor->applyTransactions($task, $xactions); } }
private function buildPropertyListView(PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data, array $audit_requests) { $viewer = $this->getViewer(); $commit_phid = $commit->getPHID(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $view = id(new PHUIPropertyListView())->setUser($this->getRequest()->getUser()); $edge_query = id(new PhabricatorEdgeQuery())->withSourcePHIDs(array($commit_phid))->withEdgeTypes(array(DiffusionCommitHasTaskEdgeType::EDGECONST, DiffusionCommitHasRevisionEdgeType::EDGECONST, DiffusionCommitRevertsCommitEdgeType::EDGECONST, DiffusionCommitRevertedByCommitEdgeType::EDGECONST)); $edges = $edge_query->execute(); $task_phids = array_keys($edges[$commit_phid][DiffusionCommitHasTaskEdgeType::EDGECONST]); $revision_phid = key($edges[$commit_phid][DiffusionCommitHasRevisionEdgeType::EDGECONST]); $reverts_phids = array_keys($edges[$commit_phid][DiffusionCommitRevertsCommitEdgeType::EDGECONST]); $reverted_by_phids = array_keys($edges[$commit_phid][DiffusionCommitRevertedByCommitEdgeType::EDGECONST]); $phids = $edge_query->getDestinationPHIDs(array($commit_phid)); if ($data->getCommitDetail('authorPHID')) { $phids[] = $data->getCommitDetail('authorPHID'); } if ($data->getCommitDetail('reviewerPHID')) { $phids[] = $data->getCommitDetail('reviewerPHID'); } if ($data->getCommitDetail('committerPHID')) { $phids[] = $data->getCommitDetail('committerPHID'); } // NOTE: We should never normally have more than a single push log, but // it can occur naturally if a commit is pushed, then the branch it was // on is deleted, then the commit is pushed again (or through other similar // chains of events). This should be rare, but does not indicate a bug // or data issue. // NOTE: We never query push logs in SVN because the commiter is always // the pusher and the commit time is always the push time; the push log // is redundant and we save a query by skipping it. $push_logs = array(); if ($repository->isHosted() && !$repository->isSVN()) { $push_logs = id(new PhabricatorRepositoryPushLogQuery())->setViewer($viewer)->withRepositoryPHIDs(array($repository->getPHID()))->withNewRefs(array($commit->getCommitIdentifier()))->withRefTypes(array(PhabricatorRepositoryPushLog::REFTYPE_COMMIT))->execute(); foreach ($push_logs as $log) { $phids[] = $log->getPusherPHID(); } } $handles = array(); if ($phids) { $handles = $this->loadViewerHandles($phids); } $props = array(); if ($audit_requests) { $user_requests = array(); $other_requests = array(); foreach ($audit_requests as $audit_request) { if (!$audit_request->isInteresting()) { continue; } if ($audit_request->isUser()) { $user_requests[] = $audit_request; } else { $other_requests[] = $audit_request; } } if ($user_requests) { $view->addProperty(pht('Auditors'), $this->renderAuditStatusView($user_requests)); } if ($other_requests) { $view->addProperty(pht('Group Auditors'), $this->renderAuditStatusView($other_requests)); } } $author_phid = $data->getCommitDetail('authorPHID'); $author_name = $data->getAuthorName(); $author_epoch = $data->getCommitDetail('authorEpoch'); $committed_info = id(new PHUIStatusItemView())->setNote(phabricator_datetime($commit->getEpoch(), $viewer)); $committer_phid = $data->getCommitDetail('committerPHID'); $committer_name = $data->getCommitDetail('committer'); if ($committer_phid) { $committed_info->setTarget($handles[$committer_phid]->renderLink()); } else { if (strlen($committer_name)) { $committed_info->setTarget($committer_name); } else { if ($author_phid) { $committed_info->setTarget($handles[$author_phid]->renderLink()); } else { if (strlen($author_name)) { $committed_info->setTarget($author_name); } } } } $committed_list = new PHUIStatusListView(); $committed_list->addItem($committed_info); $view->addProperty(pht('Committed'), $committed_list); if ($push_logs) { $pushed_list = new PHUIStatusListView(); foreach ($push_logs as $push_log) { $pushed_item = id(new PHUIStatusItemView())->setTarget($handles[$push_log->getPusherPHID()]->renderLink())->setNote(phabricator_datetime($push_log->getEpoch(), $viewer)); $pushed_list->addItem($pushed_item); } $view->addProperty(pht('Pushed'), $pushed_list); } $reviewer_phid = $data->getCommitDetail('reviewerPHID'); if ($reviewer_phid) { $view->addProperty(pht('Reviewer'), $handles[$reviewer_phid]->renderLink()); } if ($revision_phid) { $view->addProperty(pht('Differential Revision'), $handles[$revision_phid]->renderLink()); } $parents = $this->getCommitParents(); if ($parents) { $view->addProperty(pht('Parents'), $viewer->renderHandleList(mpull($parents, 'getPHID'))); } if ($this->getCommitExists()) { $view->addProperty(pht('Branches'), phutil_tag('span', array('id' => 'commit-branches'), pht('Unknown'))); $view->addProperty(pht('Tags'), phutil_tag('span', array('id' => 'commit-tags'), pht('Unknown'))); $identifier = $commit->getCommitIdentifier(); $root = $repository->getPathURI("commit/{$identifier}"); Javelin::initBehavior('diffusion-commit-branches', array($root . '/branches/' => 'commit-branches', $root . '/tags/' => 'commit-tags')); } $refs = $this->getCommitRefs(); if ($refs) { $ref_links = array(); foreach ($refs as $ref_data) { $ref_links[] = phutil_tag('a', array('href' => $ref_data['href']), $ref_data['ref']); } $view->addProperty(pht('References'), phutil_implode_html(', ', $ref_links)); } if ($reverts_phids) { $view->addProperty(pht('Reverts'), $viewer->renderHandleList($reverts_phids)); } if ($reverted_by_phids) { $view->addProperty(pht('Reverted By'), $viewer->renderHandleList($reverted_by_phids)); } if ($task_phids) { $task_list = array(); foreach ($task_phids as $phid) { $task_list[] = $handles[$phid]->renderLink(); } $task_list = phutil_implode_html(phutil_tag('br'), $task_list); $view->addProperty(pht('Tasks'), $task_list); } return $view; }
private function attachPatch(PhabricatorMetaMTAMail $template, PhabricatorRepositoryCommit $commit) { if (!$this->getRawPatch()) { return; } $attach_key = 'metamta.diffusion.attach-patches'; $attach_patches = PhabricatorEnv::getEnvConfig($attach_key); if (!$attach_patches) { return; } $repository = $commit->getRepository(); $encoding = $repository->getDetail('encoding', 'UTF-8'); $raw_patch = $this->getRawPatch(); $commit_name = $repository->formatCommitName($commit->getCommitIdentifier()); $template->addAttachment(new PhabricatorMetaMTAAttachment($raw_patch, $commit_name . '.patch', 'text/x-patch; charset=' . $encoding)); }
protected function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { $full_name = 'r' . $repository->getCallsign() . $commit->getCommitIdentifier(); echo "Parsing {$full_name}...\n"; if ($this->isBadCommit($full_name)) { echo "This commit is marked bad!\n"; return; } list($stdout) = $repository->execxLocalCommand('status -C --change %s', $commit->getCommitIdentifier()); $status = ArcanistMercurialParser::parseMercurialStatusDetails($stdout); $common_attributes = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'commitSequence' => $commit->getEpoch()); $changes = array(); // Like Git, Mercurial doesn't track directories directly. We need to infer // directory creation and removal by observing file creation and removal // and testing if the directories in question are previously empty (thus, // created) or subsequently empty (thus, removed). $maybe_new_directories = array(); $maybe_del_directories = array(); $all_directories = array(); // Parse the basic information from "hg status", which shows files that // were directly affected by the change. foreach ($status as $path => $path_info) { $path = '/' . $path; $flags = $path_info['flags']; $change_target = $path_info['from'] ? '/' . $path_info['from'] : null; $changes[$path] = array('path' => $path, 'isDirect' => true, 'targetPath' => $change_target, 'targetCommitID' => $change_target ? $commit->getID() : null, 'changeType' => null, 'fileType' => null, 'flags' => $flags) + $common_attributes; if ($flags & ArcanistRepositoryAPI::FLAG_ADDED) { $maybe_new_directories[] = dirname($path); } else { if ($flags & ArcanistRepositoryAPI::FLAG_DELETED) { $maybe_del_directories[] = dirname($path); } } $all_directories[] = dirname($path); } // Add change information for each source path which doesn't appear in the // status. These files were copied, but were not modified. We also know they // must exist. foreach ($changes as $path => $change) { $from = $change['targetPath']; if ($from && empty($changes[$from])) { $changes[$from] = array('path' => $from, 'isDirect' => false, 'targetPath' => null, 'targetCommitID' => null, 'changeType' => DifferentialChangeType::TYPE_COPY_AWAY, 'fileType' => null, 'flags' => 0) + $common_attributes; } } $away = array(); foreach ($changes as $path => $change) { $target_path = $change['targetPath']; if ($target_path) { $away[$target_path][] = $path; } } // Now that we have all the direct changes, figure out change types. foreach ($changes as $path => $change) { $flags = $change['flags']; $from = $change['targetPath']; if ($from) { $target = $changes[$from]; } else { $target = null; } if ($flags & ArcanistRepositoryAPI::FLAG_ADDED) { if ($target) { if ($target['flags'] & ArcanistRepositoryAPI::FLAG_DELETED) { $change_type = DifferentialChangeType::TYPE_MOVE_HERE; } else { $change_type = DifferentialChangeType::TYPE_COPY_HERE; } } else { $change_type = DifferentialChangeType::TYPE_ADD; } } else { if ($flags & ArcanistRepositoryAPI::FLAG_DELETED) { if (isset($away[$path])) { if (count($away[$path]) > 1) { $change_type = DifferentialChangeType::TYPE_MULTICOPY; } else { $change_type = DifferentialChangeType::TYPE_MOVE_AWAY; } } else { $change_type = DifferentialChangeType::TYPE_DELETE; } } else { if (isset($away[$path])) { $change_type = DifferentialChangeType::TYPE_COPY_AWAY; } else { $change_type = DifferentialChangeType::TYPE_CHANGE; } } } $changes[$path]['changeType'] = $change_type; } // Go through all the affected directories and identify any which were // actually added or deleted. $dir_status = array(); foreach ($maybe_del_directories as $dir) { $exists = false; foreach (DiffusionPathIDQuery::expandPathToRoot($dir) as $path) { if (isset($dir_status[$path])) { break; } // If we know some child exists, we know this path exists. If we don't // know that a child exists, test if this directory still exists. if (!$exists) { $exists = $this->mercurialPathExists($repository, $path, $commit->getCommitIdentifier()); } if ($exists) { $dir_status[$path] = DifferentialChangeType::TYPE_CHILD; } else { $dir_status[$path] = DifferentialChangeType::TYPE_DELETE; } } } list($stdout) = $repository->execxLocalCommand('parents --rev %s --style default', $commit->getCommitIdentifier()); $parents = ArcanistMercurialParser::parseMercurialLog($stdout); $parent = reset($parents); if ($parent) { // TODO: We should expand this to a full 40-character hash using "hg id". $parent = $parent['rev']; } foreach ($maybe_new_directories as $dir) { $exists = false; foreach (DiffusionPathIDQuery::expandPathToRoot($dir) as $path) { if (isset($dir_status[$path])) { break; } if (!$exists) { if ($parent) { $exists = $this->mercurialPathExists($repository, $path, $parent); } else { $exists = false; } } if ($exists) { $dir_status[$path] = DifferentialChangeType::TYPE_CHILD; } else { $dir_status[$path] = DifferentialChangeType::TYPE_ADD; } } } foreach ($all_directories as $dir) { foreach (DiffusionPathIDQuery::expandPathToRoot($dir) as $path) { if (isset($dir_status[$path])) { break; } $dir_status[$path] = DifferentialChangeType::TYPE_CHILD; } } // Merge all the directory statuses into the path statuses. foreach ($dir_status as $path => $status) { if (isset($changes[$path])) { // TODO: The UI probably doesn't handle any of these cases with // terrible elegance, but they are exceedingly rare. $existing_type = $changes[$path]['changeType']; if ($existing_type == DifferentialChangeType::TYPE_DELETE) { // This change removes a file, replaces it with a directory, and then // adds children of that directory. Mark it as a "change" instead, // and make the type a directory. $changes[$path]['fileType'] = DifferentialChangeType::FILE_DIRECTORY; $changes[$path]['changeType'] = DifferentialChangeType::TYPE_CHANGE; } else { if ($existing_type == DifferentialChangeType::TYPE_MOVE_AWAY || $existing_type == DifferentialChangeType::TYPE_MULTICOPY) { // This change moves or copies a file, replaces it with a directory, // and then adds children to that directory. Mark it as "copy away" // instead of whatever it was, and make the type a directory. $changes[$path]['fileType'] = DifferentialChangeType::FILE_DIRECTORY; $changes[$path]['changeType'] = DifferentialChangeType::TYPE_COPY_AWAY; } else { if ($existing_type == DifferentialChangeType::TYPE_ADD) { // This change removes a diretory and replaces it with a file. Mark // it as "change" instead of "add". $changes[$path]['changeType'] = DifferentialChangeType::TYPE_CHANGE; } } } continue; } $changes[$path] = array('path' => $path, 'isDirect' => $status == DifferentialChangeType::TYPE_CHILD ? false : true, 'fileType' => DifferentialChangeType::FILE_DIRECTORY, 'changeType' => $status, 'targetPath' => null, 'targetCommitID' => null) + $common_attributes; } // TODO: use "hg diff --git" to figure out which files are symlinks. foreach ($changes as $path => $change) { if (empty($change['fileType'])) { $changes[$path]['fileType'] = DifferentialChangeType::FILE_NORMAL; } } $all_paths = array(); foreach ($changes as $path => $change) { $all_paths[$path] = true; if ($change['targetPath']) { $all_paths[$change['targetPath']] = true; } } $path_map = $this->lookupOrCreatePaths(array_keys($all_paths)); foreach ($changes as $key => $change) { $changes[$key]['pathID'] = $path_map[$change['path']]; if ($change['targetPath']) { $changes[$key]['targetPathID'] = $path_map[$change['targetPath']]; } else { $changes[$key]['targetPathID'] = null; } } $conn_w = $repository->establishConnection('w'); $changes_sql = array(); foreach ($changes as $change) { $values = array((int) $change['repositoryID'], (int) $change['pathID'], (int) $change['commitID'], $change['targetPathID'] ? (int) $change['targetPathID'] : 'null', $change['targetCommitID'] ? (int) $change['targetCommitID'] : 'null', (int) $change['changeType'], (int) $change['fileType'], (int) $change['isDirect'], (int) $change['commitSequence']); $changes_sql[] = '(' . implode(', ', $values) . ')'; } queryfx($conn_w, 'DELETE FROM %T WHERE commitID = %d', PhabricatorRepository::TABLE_PATHCHANGE, $commit->getID()); foreach (array_chunk($changes_sql, 256) as $sql_chunk) { queryfx($conn_w, 'INSERT INTO %T (repositoryID, pathID, commitID, targetPathID, targetCommitID, changeType, fileType, isDirect, commitSequence) VALUES %Q', PhabricatorRepository::TABLE_PATHCHANGE, implode(', ', $sql_chunk)); } $this->finishParse(); }
private function renderHeadsupActionList(PhabricatorRepositoryCommit $commit, PhabricatorRepository $repository) { $request = $this->getRequest(); $user = $request->getUser(); $actions = array(); // TODO -- integrate permissions into whether or not this action is shown $uri = '/diffusion/' . $repository->getCallSign() . '/commit/' . $commit->getCommitIdentifier() . '/edit/'; $action = new AphrontHeadsupActionView(); $action->setClass('action-edit'); $action->setURI($uri); $action->setName('Edit Commit'); $action->setWorkflow(false); $actions[] = $action; require_celerity_resource('phabricator-flag-css'); $flag = PhabricatorFlagQuery::loadUserFlag($user, $commit->getPHID()); if ($flag) { $class = PhabricatorFlagColor::getCSSClass($flag->getColor()); $color = PhabricatorFlagColor::getColorName($flag->getColor()); $action = new AphrontHeadsupActionView(); $action->setClass('flag-clear ' . $class); $action->setURI('/flag/delete/' . $flag->getID() . '/'); $action->setName('Remove ' . $color . ' Flag'); $action->setWorkflow(true); $actions[] = $action; } else { $action = new AphrontHeadsupActionView(); $action->setClass('phabricator-flag-ghost'); $action->setURI('/flag/edit/' . $commit->getPHID() . '/'); $action->setName('Flag Commit'); $action->setWorkflow(true); $actions[] = $action; } require_celerity_resource('phabricator-object-selector-css'); require_celerity_resource('javelin-behavior-phabricator-object-selector'); $action = new AphrontHeadsupActionView(); $action->setName('Edit Maniphest Tasks'); $action->setURI('/search/attach/' . $commit->getPHID() . '/TASK/edge/'); $action->setWorkflow(true); $action->setClass('attach-maniphest'); $actions[] = $action; if ($user->getIsAdmin()) { $action = new AphrontHeadsupActionView(); $action->setName('MetaMTA Transcripts'); $action->setURI('/mail/?phid=' . $commit->getPHID()); $action->setClass('transcripts-metamta'); $actions[] = $action; } $action = new AphrontHeadsupActionView(); $action->setName('Herald Transcripts'); $action->setURI('/herald/transcript/?phid=' . $commit->getPHID()); $action->setClass('transcripts-herald'); $actions[] = $action; $action = new AphrontHeadsupActionView(); $action->setName('Download Raw Diff'); $action->setURI($request->getRequestURI()->alter('diff', true)); $action->setClass('action-download'); $actions[] = $action; $action_list = new AphrontHeadsupActionListView(); $action_list->setActions($actions); return $action_list; }
private function getCommitProperties(PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data, array $parents) { assert_instances_of($parents, 'PhabricatorRepositoryCommit'); $user = $this->getRequest()->getUser(); $task_phids = PhabricatorEdgeQuery::loadDestinationPHIDs($commit->getPHID(), PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK); $phids = $task_phids; if ($data->getCommitDetail('authorPHID')) { $phids[] = $data->getCommitDetail('authorPHID'); } if ($data->getCommitDetail('reviewerPHID')) { $phids[] = $data->getCommitDetail('reviewerPHID'); } if ($data->getCommitDetail('committerPHID')) { $phids[] = $data->getCommitDetail('committerPHID'); } if ($data->getCommitDetail('differential.revisionPHID')) { $phids[] = $data->getCommitDetail('differential.revisionPHID'); } if ($parents) { foreach ($parents as $parent) { $phids[] = $parent->getPHID(); } } $handles = array(); if ($phids) { $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); } $props = array(); if ($commit->getAuditStatus()) { $status = PhabricatorAuditCommitStatusConstants::getStatusName($commit->getAuditStatus()); $props['Status'] = phutil_render_tag('strong', array(), phutil_escape_html($status)); } $props['Committed'] = phabricator_datetime($commit->getEpoch(), $user); $author_phid = $data->getCommitDetail('authorPHID'); if ($data->getCommitDetail('authorPHID')) { $props['Author'] = $handles[$author_phid]->renderLink(); } else { $props['Author'] = phutil_escape_html($data->getAuthorName()); } $reviewer_phid = $data->getCommitDetail('reviewerPHID'); $reviewer_name = $data->getCommitDetail('reviewerName'); if ($reviewer_phid) { $props['Reviewer'] = $handles[$reviewer_phid]->renderLink(); } else { if ($reviewer_name) { $props['Reviewer'] = phutil_escape_html($reviewer_name); } } $committer = $data->getCommitDetail('committer'); if ($committer) { $committer_phid = $data->getCommitDetail('committerPHID'); if ($data->getCommitDetail('committerPHID')) { $props['Committer'] = $handles[$committer_phid]->renderLink(); } else { $props['Committer'] = phutil_escape_html($committer); } } $revision_phid = $data->getCommitDetail('differential.revisionPHID'); if ($revision_phid) { $props['Differential Revision'] = $handles[$revision_phid]->renderLink(); } if ($parents) { $parent_links = array(); foreach ($parents as $parent) { $parent_links[] = $handles[$parent->getPHID()]->renderLink(); } $props['Parents'] = implode(' · ', $parent_links); } $request = $this->getDiffusionRequest(); $props['Branches'] = '<span id="commit-branches">Unknown</span>'; $props['Tags'] = '<span id="commit-tags">Unknown</span>'; $callsign = $request->getRepository()->getCallsign(); $root = '/diffusion/' . $callsign . '/commit/' . $commit->getCommitIdentifier(); Javelin::initBehavior('diffusion-commit-branches', array($root . '/branches/' => 'commit-branches', $root . '/tags/' => 'commit-tags')); $refs = $this->buildRefs($request); if ($refs) { $props['References'] = $refs; } if ($task_phids) { $task_list = array(); foreach ($task_phids as $phid) { $task_list[] = $handles[$phid]->renderLink(); } $task_list = implode('<br />', $task_list); $props['Tasks'] = $task_list; } return $props; }
private function isCommitOnBranch(PhabricatorRepository $repo, PhabricatorRepositoryCommit $commit, ReleephBranch $releeph_branch) { switch ($repo->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: list($output) = $repo->execxLocalCommand('branch --all --no-color --contains %s', $commit->getCommitIdentifier()); $remote_prefix = 'remotes/origin/'; $branches = array(); foreach (array_filter(explode("\n", $output)) as $line) { $tokens = explode(' ', $line); $ref = last($tokens); if (strncmp($ref, $remote_prefix, strlen($remote_prefix)) === 0) { $branch = substr($ref, strlen($remote_prefix)); $branches[$branch] = $branch; } } return idx($branches, $releeph_branch->getName()); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(DiffusionRequest::newFromDictionary(array('user' => $this->getUser(), 'repository' => $repo, 'commit' => $commit->getCommitIdentifier()))); $path_changes = $change_query->loadChanges(); $commit_paths = mpull($path_changes, 'getPath'); $branch_path = $releeph_branch->getName(); $in_branch = array(); $ex_branch = array(); foreach ($commit_paths as $path) { if (strncmp($path, $branch_path, strlen($branch_path)) === 0) { $in_branch[] = $path; } else { $ex_branch[] = $path; } } if ($in_branch && $ex_branch) { $error = pht('CONFUSION: commit %s in %s contains %d path change(s) that were ' . 'part of a Releeph branch, but also has %d path change(s) not ' . 'part of a Releeph branch!', $commit->getCommitIdentifier(), $repo->getCallsign(), count($in_branch), count($ex_branch)); phlog($error); } return !empty($in_branch); break; } }
protected function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { $full_name = 'r' . $repository->getCallsign() . $commit->getCommitIdentifier(); echo "Parsing {$full_name}...\n"; if ($this->isBadCommit($full_name)) { echo "This commit is marked bad!\n"; return; } // Check if the commit has parents. We're testing to see whether it is the // first commit in history (in which case we must use "git log") or some // other commit (in which case we can use "git diff"). We'd rather use // "git diff" because it has the right behavior for merge commits, but // it requires the commit to have a parent that we can diff against. The // first commit doesn't, so "commit^" is not a valid ref. list($parents) = $repository->execxLocalCommand('log -n1 --format=%s %s', '%P', $commit->getCommitIdentifier()); $use_log = !strlen(trim($parents)); if ($use_log) { // This is the first commit so we need to use "log". We know it's not a // merge commit because it couldn't be merging anything, so this is safe. // NOTE: "--pretty=format: " is to disable diff output, we only want the // part we get from "--raw". list($raw) = $repository->execxLocalCommand('log -n1 -M -C -B --find-copies-harder --raw -t ' . '--pretty=format: --abbrev=40 %s', $commit->getCommitIdentifier()); } else { // Otherwise, we can use "diff", which will give us output for merges. // We diff against the first parent, as this is generally the expectation // and results in sensible behavior. list($raw) = $repository->execxLocalCommand('diff -n1 -M -C -B --find-copies-harder --raw -t ' . '--abbrev=40 %s^1 %s', $commit->getCommitIdentifier(), $commit->getCommitIdentifier()); } $changes = array(); $move_away = array(); $copy_away = array(); $lines = explode("\n", $raw); foreach ($lines as $line) { if (!strlen(trim($line))) { continue; } list($old_mode, $new_mode, $old_hash, $new_hash, $more_stuff) = preg_split('/ +/', $line, 5); // We may only have two pieces here. list($action, $src_path, $dst_path) = array_merge(explode("\t", $more_stuff), array(null)); // Normalize the paths for consistency with the SVN workflow. $src_path = '/' . $src_path; if ($dst_path) { $dst_path = '/' . $dst_path; } $old_mode = intval($old_mode, 8); $new_mode = intval($new_mode, 8); switch ($new_mode & 0160000) { case 0160000: $file_type = DifferentialChangeType::FILE_SUBMODULE; break; case 0120000: $file_type = DifferentialChangeType::FILE_SYMLINK; break; case 040000: $file_type = DifferentialChangeType::FILE_DIRECTORY; break; default: $file_type = DifferentialChangeType::FILE_NORMAL; break; } // TODO: We can detect binary changes as git does, through a combination // of running 'git check-attr' for stuff like 'binary', 'merge' or 'diff', // and by falling back to inspecting the first 8,000 characters of the // buffer for null bytes (this is seriously git's algorithm, see // buffer_is_binary() in xdiff-interface.c). $change_type = null; $change_path = $src_path; $change_target = null; $is_direct = true; switch ($action[0]) { case 'A': $change_type = DifferentialChangeType::TYPE_ADD; break; case 'D': $change_type = DifferentialChangeType::TYPE_DELETE; break; case 'C': $change_type = DifferentialChangeType::TYPE_COPY_HERE; $change_path = $dst_path; $change_target = $src_path; $copy_away[$change_target][] = $change_path; break; case 'R': $change_type = DifferentialChangeType::TYPE_MOVE_HERE; $change_path = $dst_path; $change_target = $src_path; $move_away[$change_target][] = $change_path; break; case 'T': // Type of the file changed, fall through and treat it as a // modification. Not 100% sure this is the right thing to do but it // seems reasonable. // Type of the file changed, fall through and treat it as a // modification. Not 100% sure this is the right thing to do but it // seems reasonable. case 'M': if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { $change_type = DifferentialChangeType::TYPE_CHILD; $is_direct = false; } else { $change_type = DifferentialChangeType::TYPE_CHANGE; } break; // NOTE: "U" (unmerged) and "X" (unknown) statuses are also possible // in theory but shouldn't appear here. // NOTE: "U" (unmerged) and "X" (unknown) statuses are also possible // in theory but shouldn't appear here. default: throw new Exception("Failed to parse line '{$line}'."); } $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => $change_type, 'fileType' => $file_type, 'isDirect' => $is_direct, 'commitSequence' => $commit->getEpoch(), 'targetPath' => $change_target, 'targetCommitID' => $change_target ? $commit->getID() : null); } // Add a change to '/' since git doesn't mention it. $changes['/'] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => '/', 'changeType' => DifferentialChangeType::TYPE_CHILD, 'fileType' => DifferentialChangeType::FILE_DIRECTORY, 'isDirect' => false, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null); foreach ($copy_away as $change_path => $destinations) { if (isset($move_away[$change_path])) { $change_type = DifferentialChangeType::TYPE_MULTICOPY; $is_direct = true; unset($move_away[$change_path]); } else { $change_type = DifferentialChangeType::TYPE_COPY_AWAY; $is_direct = false; } $reference = $changes[reset($destinations)]; $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => $change_type, 'fileType' => $reference['fileType'], 'isDirect' => $is_direct, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null); } foreach ($move_away as $change_path => $destinations) { $reference = $changes[reset($destinations)]; $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => DifferentialChangeType::TYPE_MOVE_AWAY, 'fileType' => $reference['fileType'], 'isDirect' => true, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null); } $paths = array(); foreach ($changes as $change) { $paths[$change['path']] = true; if ($change['targetPath']) { $paths[$change['targetPath']] = true; } } $path_map = $this->lookupOrCreatePaths(array_keys($paths)); foreach ($changes as $key => $change) { $changes[$key]['pathID'] = $path_map[$change['path']]; if ($change['targetPath']) { $changes[$key]['targetPathID'] = $path_map[$change['targetPath']]; } else { $changes[$key]['targetPathID'] = null; } } $conn_w = $repository->establishConnection('w'); $changes_sql = array(); foreach ($changes as $change) { $values = array((int) $change['repositoryID'], (int) $change['pathID'], (int) $change['commitID'], $change['targetPathID'] ? (int) $change['targetPathID'] : 'null', $change['targetCommitID'] ? (int) $change['targetCommitID'] : 'null', (int) $change['changeType'], (int) $change['fileType'], (int) $change['isDirect'], (int) $change['commitSequence']); $changes_sql[] = '(' . implode(', ', $values) . ')'; } queryfx($conn_w, 'DELETE FROM %T WHERE commitID = %d', PhabricatorRepository::TABLE_PATHCHANGE, $commit->getID()); foreach (array_chunk($changes_sql, 256) as $sql_chunk) { queryfx($conn_w, 'INSERT INTO %T (repositoryID, pathID, commitID, targetPathID, targetCommitID, changeType, fileType, isDirect, commitSequence) VALUES %Q', PhabricatorRepository::TABLE_PATHCHANGE, implode(', ', $sql_chunk)); } $this->finishParse(); }
private function loadCommitProperties(PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data, array $parents, array $audit_requests) { assert_instances_of($parents, 'PhabricatorRepositoryCommit'); $viewer = $this->getRequest()->getUser(); $commit_phid = $commit->getPHID(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $edge_query = id(new PhabricatorEdgeQuery())->withSourcePHIDs(array($commit_phid))->withEdgeTypes(array(DiffusionCommitHasTaskEdgeType::EDGECONST, DiffusionCommitHasRevisionEdgeType::EDGECONST, DiffusionCommitRevertsCommitEdgeType::EDGECONST, DiffusionCommitRevertedByCommitEdgeType::EDGECONST)); $edges = $edge_query->execute(); $task_phids = array_keys($edges[$commit_phid][DiffusionCommitHasTaskEdgeType::EDGECONST]); $revision_phid = key($edges[$commit_phid][DiffusionCommitHasRevisionEdgeType::EDGECONST]); $reverts_phids = array_keys($edges[$commit_phid][DiffusionCommitRevertsCommitEdgeType::EDGECONST]); $reverted_by_phids = array_keys($edges[$commit_phid][DiffusionCommitRevertedByCommitEdgeType::EDGECONST]); $phids = $edge_query->getDestinationPHIDs(array($commit_phid)); if ($data->getCommitDetail('authorPHID')) { $phids[] = $data->getCommitDetail('authorPHID'); } if ($data->getCommitDetail('reviewerPHID')) { $phids[] = $data->getCommitDetail('reviewerPHID'); } if ($data->getCommitDetail('committerPHID')) { $phids[] = $data->getCommitDetail('committerPHID'); } if ($parents) { foreach ($parents as $parent) { $phids[] = $parent->getPHID(); } } // NOTE: We should never normally have more than a single push log, but // it can occur naturally if a commit is pushed, then the branch it was // on is deleted, then the commit is pushed again (or through other similar // chains of events). This should be rare, but does not indicate a bug // or data issue. // NOTE: We never query push logs in SVN because the commiter is always // the pusher and the commit time is always the push time; the push log // is redundant and we save a query by skipping it. $push_logs = array(); if ($repository->isHosted() && !$repository->isSVN()) { $push_logs = id(new PhabricatorRepositoryPushLogQuery())->setViewer($viewer)->withRepositoryPHIDs(array($repository->getPHID()))->withNewRefs(array($commit->getCommitIdentifier()))->withRefTypes(array(PhabricatorRepositoryPushLog::REFTYPE_COMMIT))->execute(); foreach ($push_logs as $log) { $phids[] = $log->getPusherPHID(); } } $handles = array(); if ($phids) { $handles = $this->loadViewerHandles($phids); } $props = array(); if ($commit->getAuditStatus()) { $status = PhabricatorAuditCommitStatusConstants::getStatusName($commit->getAuditStatus()); $tag = id(new PHUITagView())->setType(PHUITagView::TYPE_STATE)->setName($status); switch ($commit->getAuditStatus()) { case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT: $tag->setBackgroundColor(PHUITagView::COLOR_ORANGE); break; case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED: $tag->setBackgroundColor(PHUITagView::COLOR_RED); break; case PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED: $tag->setBackgroundColor(PHUITagView::COLOR_BLUE); break; case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED: $tag->setBackgroundColor(PHUITagView::COLOR_GREEN); break; } $props['Status'] = $tag; } if ($audit_requests) { $user_requests = array(); $other_requests = array(); foreach ($audit_requests as $audit_request) { if ($audit_request->isUser()) { $user_requests[] = $audit_request; } else { $other_requests[] = $audit_request; } } if ($user_requests) { $props['Auditors'] = $this->renderAuditStatusView($user_requests); } if ($other_requests) { $props['Project/Package Auditors'] = $this->renderAuditStatusView($other_requests); } } $author_phid = $data->getCommitDetail('authorPHID'); $author_name = $data->getAuthorName(); if (!$repository->isSVN()) { $authored_info = id(new PHUIStatusItemView()); // TODO: In Git, a distinct authorship date is available. When present, // we should show it here. if ($author_phid) { $authored_info->setTarget($handles[$author_phid]->renderLink()); } else { if (strlen($author_name)) { $authored_info->setTarget($author_name); } } $props['Authored'] = id(new PHUIStatusListView())->addItem($authored_info); } $committed_info = id(new PHUIStatusItemView())->setNote(phabricator_datetime($commit->getEpoch(), $viewer)); $committer_phid = $data->getCommitDetail('committerPHID'); $committer_name = $data->getCommitDetail('committer'); if ($committer_phid) { $committed_info->setTarget($handles[$committer_phid]->renderLink()); } else { if (strlen($committer_name)) { $committed_info->setTarget($committer_name); } else { if ($author_phid) { $committed_info->setTarget($handles[$author_phid]->renderLink()); } else { if (strlen($author_name)) { $committed_info->setTarget($author_name); } } } } $props['Committed'] = id(new PHUIStatusListView())->addItem($committed_info); if ($push_logs) { $pushed_list = new PHUIStatusListView(); foreach ($push_logs as $push_log) { $pushed_item = id(new PHUIStatusItemView())->setTarget($handles[$push_log->getPusherPHID()]->renderLink())->setNote(phabricator_datetime($push_log->getEpoch(), $viewer)); $pushed_list->addItem($pushed_item); } $props['Pushed'] = $pushed_list; } $reviewer_phid = $data->getCommitDetail('reviewerPHID'); if ($reviewer_phid) { $props['Reviewer'] = $handles[$reviewer_phid]->renderLink(); } if ($revision_phid) { $props['Differential Revision'] = $handles[$revision_phid]->renderLink(); } if ($parents) { $parent_links = array(); foreach ($parents as $parent) { $parent_links[] = $handles[$parent->getPHID()]->renderLink(); } $props['Parents'] = phutil_implode_html(" ยท ", $parent_links); } $props['Branches'] = phutil_tag('span', array('id' => 'commit-branches'), pht('Unknown')); $props['Tags'] = phutil_tag('span', array('id' => 'commit-tags'), pht('Unknown')); $callsign = $repository->getCallsign(); $root = '/diffusion/' . $callsign . '/commit/' . $commit->getCommitIdentifier(); Javelin::initBehavior('diffusion-commit-branches', array($root . '/branches/' => 'commit-branches', $root . '/tags/' => 'commit-tags')); $refs = $this->buildRefs($drequest); if ($refs) { $props['References'] = $refs; } if ($reverts_phids) { $props[pht('Reverts')] = $viewer->renderHandleList($reverts_phids); } if ($reverted_by_phids) { $props[pht('Reverted By')] = $viewer->renderHandleList($reverted_by_phids); } if ($task_phids) { $task_list = array(); foreach ($task_phids as $phid) { $task_list[] = $handles[$phid]->renderLink(); } $task_list = phutil_implode_html(phutil_tag('br'), $task_list); $props['Tasks'] = $task_list; } return $props; }
protected function getCommitHashes(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { $commit_hash = $commit->getCommitIdentifier(); return array(array(DifferentialRevisionHash::HASH_MERCURIAL_COMMIT, $commit_hash)); }
protected function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { $full_name = 'r' . $repository->getCallsign() . $commit->getCommitIdentifier(); echo "Parsing {$full_name}...\n"; if ($this->isBadCommit($full_name)) { echo "This commit is marked bad!\n"; return; } $local_path = $repository->getDetail('local-path'); list($raw) = execx('(cd %s && git log -n1 -M -C -B --find-copies-harder --raw -t ' . '--abbrev=40 --pretty=format: %s)', $local_path, $commit->getCommitIdentifier()); $changes = array(); $move_away = array(); $copy_away = array(); $lines = explode("\n", $raw); foreach ($lines as $line) { if (!strlen(trim($line))) { continue; } list($old_mode, $new_mode, $old_hash, $new_hash, $more_stuff) = preg_split('/ +/', $line); // We may only have two pieces here. list($action, $src_path, $dst_path) = array_merge(explode("\t", $more_stuff), array(null)); // Normalize the paths for consistency with the SVN workflow. $src_path = '/' . $src_path; if ($dst_path) { $dst_path = '/' . $dst_path; } $old_mode = intval($old_mode, 8); $new_mode = intval($new_mode, 8); $file_type = DifferentialChangeType::FILE_NORMAL; if ($new_mode & 040000) { $file_type = DifferentialChangeType::FILE_DIRECTORY; } else { if ($new_mode & 0120000) { $file_type = DifferentialChangeType::FILE_SYMLINK; } } // TODO: We can detect binary changes as git does, through a combination // of running 'git check-attr' for stuff like 'binary', 'merge' or 'diff', // and by falling back to inspecting the first 8,000 characters of the // buffer for null bytes (this is seriously git's algorithm, see // buffer_is_binary() in xdiff-interface.c). $change_type = null; $change_path = $src_path; $change_target = null; $is_direct = true; switch ($action[0]) { case 'A': $change_type = DifferentialChangeType::TYPE_ADD; break; case 'D': $change_type = DifferentialChangeType::TYPE_DELETE; break; case 'C': $change_type = DifferentialChangeType::TYPE_COPY_HERE; $change_path = $dst_path; $change_target = $src_path; $copy_away[$change_target][] = $change_path; break; case 'R': $change_type = DifferentialChangeType::TYPE_MOVE_HERE; $change_path = $dst_path; $change_target = $src_path; $move_away[$change_target][] = $change_path; break; case 'T': // Type of the file changed, fall through and treat it as a // modification. Not 100% sure this is the right thing to do but it // seems reasonable. // Type of the file changed, fall through and treat it as a // modification. Not 100% sure this is the right thing to do but it // seems reasonable. case 'M': if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { $change_type = DifferentialChangeType::TYPE_CHILD; $is_direct = false; } else { $change_type = DifferentialChangeType::TYPE_CHANGE; } break; // NOTE: "U" (unmerged) and "X" (unknown) statuses are also possible // in theory but shouldn't appear here. // NOTE: "U" (unmerged) and "X" (unknown) statuses are also possible // in theory but shouldn't appear here. default: throw new Exception("Failed to parse line '{$line}'."); } $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => $change_type, 'fileType' => $file_type, 'isDirect' => $is_direct, 'commitSequence' => $commit->getEpoch(), 'targetPath' => $change_target, 'targetCommitID' => $change_target ? $commit->getID() : null); } // Add a change to '/' since git doesn't mention it. $changes['/'] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => '/', 'changeType' => DifferentialChangeType::TYPE_CHILD, 'fileType' => DifferentialChangeType::FILE_DIRECTORY, 'isDirect' => false, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null); foreach ($copy_away as $change_path => $destinations) { if (isset($move_away[$change_path])) { $change_type = DifferentialChangeType::TYPE_MULTICOPY; $is_direct = true; unset($move_away[$change_path]); } else { $change_type = DifferentialChangeType::TYPE_COPY_AWAY; $is_direct = false; } $reference = $changes[reset($destinations)]; $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => $change_type, 'fileType' => $reference['fileType'], 'isDirect' => $is_direct, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null); } foreach ($move_away as $change_path => $destinations) { $reference = $changes[reset($destinations)]; $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => DifferentialChangeType::TYPE_MOVE_AWAY, 'fileType' => $reference['fileType'], 'isDirect' => true, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null); } $paths = array(); foreach ($changes as $change) { $paths[$change['path']] = true; if ($change['targetPath']) { $paths[$change['targetPath']] = true; } } $path_map = $this->lookupOrCreatePaths(array_keys($paths)); foreach ($changes as $key => $change) { $changes[$key]['pathID'] = $path_map[$change['path']]; if ($change['targetPath']) { $changes[$key]['targetPathID'] = $path_map[$change['targetPath']]; } else { $changes[$key]['targetPathID'] = null; } } $conn_w = $repository->establishConnection('w'); $changes_sql = array(); foreach ($changes as $change) { $values = array((int) $change['repositoryID'], (int) $change['pathID'], (int) $change['commitID'], $change['targetPathID'] ? (int) $change['targetPathID'] : 'null', $change['targetCommitID'] ? (int) $change['targetCommitID'] : 'null', (int) $change['changeType'], (int) $change['fileType'], (int) $change['isDirect'], (int) $change['commitSequence']); $changes_sql[] = '(' . implode(', ', $values) . ')'; } queryfx($conn_w, 'DELETE FROM %T WHERE commitID = %d', PhabricatorRepository::TABLE_PATHCHANGE, $commit->getID()); foreach (array_chunk($changes_sql, 256) as $sql_chunk) { queryfx($conn_w, 'INSERT INTO %T (repositoryID, pathID, commitID, targetPathID, targetCommitID, changeType, fileType, isDirect, commitSequence) VALUES %Q', PhabricatorRepository::TABLE_PATHCHANGE, implode(', ', $sql_chunk)); } $this->finishParse(); }
protected function parseCommitChanges(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { $viewer = PhabricatorUser::getOmnipotentUser(); $raw = DiffusionQuery::callConduitWithDiffusionRequest($viewer, DiffusionRequest::newFromDictionary(array('repository' => $repository, 'user' => $viewer)), 'diffusion.internal.gitrawdiffquery', array('commit' => $commit->getCommitIdentifier())); $changes = array(); $move_away = array(); $copy_away = array(); $lines = explode("\n", $raw); foreach ($lines as $line) { if (!strlen(trim($line))) { continue; } list($old_mode, $new_mode, $old_hash, $new_hash, $more_stuff) = preg_split('/ +/', $line, 5); // We may only have two pieces here. list($action, $src_path, $dst_path) = array_merge(explode("\t", $more_stuff), array(null)); // Normalize the paths for consistency with the SVN workflow. $src_path = '/' . $src_path; if ($dst_path) { $dst_path = '/' . $dst_path; } $old_mode = intval($old_mode, 8); $new_mode = intval($new_mode, 8); switch ($new_mode & 0160000) { case 0160000: $file_type = DifferentialChangeType::FILE_SUBMODULE; break; case 0120000: $file_type = DifferentialChangeType::FILE_SYMLINK; break; case 040000: $file_type = DifferentialChangeType::FILE_DIRECTORY; break; default: $file_type = DifferentialChangeType::FILE_NORMAL; break; } // TODO: We can detect binary changes as git does, through a combination // of running 'git check-attr' for stuff like 'binary', 'merge' or 'diff', // and by falling back to inspecting the first 8,000 characters of the // buffer for null bytes (this is seriously git's algorithm, see // buffer_is_binary() in xdiff-interface.c). $change_type = null; $change_path = $src_path; $change_target = null; $is_direct = true; switch ($action[0]) { case 'A': $change_type = DifferentialChangeType::TYPE_ADD; break; case 'D': $change_type = DifferentialChangeType::TYPE_DELETE; break; case 'C': $change_type = DifferentialChangeType::TYPE_COPY_HERE; $change_path = $dst_path; $change_target = $src_path; $copy_away[$change_target][] = $change_path; break; case 'R': $change_type = DifferentialChangeType::TYPE_MOVE_HERE; $change_path = $dst_path; $change_target = $src_path; $move_away[$change_target][] = $change_path; break; case 'T': // Type of the file changed, fall through and treat it as a // modification. Not 100% sure this is the right thing to do but it // seems reasonable. // Type of the file changed, fall through and treat it as a // modification. Not 100% sure this is the right thing to do but it // seems reasonable. case 'M': if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { $change_type = DifferentialChangeType::TYPE_CHILD; $is_direct = false; } else { $change_type = DifferentialChangeType::TYPE_CHANGE; } break; // NOTE: "U" (unmerged) and "X" (unknown) statuses are also possible // in theory but shouldn't appear here. // NOTE: "U" (unmerged) and "X" (unknown) statuses are also possible // in theory but shouldn't appear here. default: throw new Exception(pht("Failed to parse line '%s'.", $line)); } $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => $change_type, 'fileType' => $file_type, 'isDirect' => $is_direct, 'commitSequence' => $commit->getEpoch(), 'targetPath' => $change_target, 'targetCommitID' => $change_target ? $commit->getID() : null); } // Add a change to '/' since git doesn't mention it. $changes['/'] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => '/', 'changeType' => DifferentialChangeType::TYPE_CHILD, 'fileType' => DifferentialChangeType::FILE_DIRECTORY, 'isDirect' => false, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null); foreach ($copy_away as $change_path => $destinations) { if (isset($move_away[$change_path])) { $change_type = DifferentialChangeType::TYPE_MULTICOPY; $is_direct = true; unset($move_away[$change_path]); } else { $change_type = DifferentialChangeType::TYPE_COPY_AWAY; // This change is direct if we picked up a modification above (i.e., // the original copy source was also edited). Otherwise the original // wasn't touched, so leave it as an indirect change. $is_direct = isset($changes[$change_path]); } $reference = $changes[reset($destinations)]; $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => $change_type, 'fileType' => $reference['fileType'], 'isDirect' => $is_direct, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null); } foreach ($move_away as $change_path => $destinations) { $reference = $changes[reset($destinations)]; $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => DifferentialChangeType::TYPE_MOVE_AWAY, 'fileType' => $reference['fileType'], 'isDirect' => true, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null); } $paths = array(); foreach ($changes as $change) { $paths[$change['path']] = true; if ($change['targetPath']) { $paths[$change['targetPath']] = true; } } $path_map = $this->lookupOrCreatePaths(array_keys($paths)); foreach ($changes as $key => $change) { $changes[$key]['pathID'] = $path_map[$change['path']]; if ($change['targetPath']) { $changes[$key]['targetPathID'] = $path_map[$change['targetPath']]; } else { $changes[$key]['targetPathID'] = null; } } $results = array(); foreach ($changes as $change) { $result = id(new PhabricatorRepositoryParsedChange())->setPathID($change['pathID'])->setTargetPathID($change['targetPathID'])->setTargetCommitID($change['targetCommitID'])->setChangeType($change['changeType'])->setFileType($change['fileType'])->setIsDirect($change['isDirect'])->setCommitSequence($change['commitSequence']); $results[] = $result; } return $results; }