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;
 }