public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $e_name = true;
     $e_callsign = true;
     $repository = new PhabricatorRepository();
     $type_map = PhabricatorRepositoryType::getAllRepositoryTypes();
     $errors = array();
     if ($request->isFormPost()) {
         $repository->setName($request->getStr('name'));
         $repository->setCallsign($request->getStr('callsign'));
         $repository->setVersionControlSystem($request->getStr('type'));
         if (!strlen($repository->getName())) {
             $e_name = 'Required';
             $errors[] = 'Repository name is required.';
         } else {
             $e_name = null;
         }
         if (!strlen($repository->getCallsign())) {
             $e_callsign = 'Required';
             $errors[] = 'Callsign is required.';
         } else {
             if (!preg_match('/^[A-Z]+$/', $repository->getCallsign())) {
                 $e_callsign = 'Invalid';
                 $errors[] = 'Callsign must be ALL UPPERCASE LETTERS.';
             } else {
                 $e_callsign = null;
             }
         }
         if (empty($type_map[$repository->getVersionControlSystem()])) {
             $errors[] = 'Invalid version control system.';
         }
         if (!$errors) {
             try {
                 $repository->save();
                 return id(new AphrontRedirectResponse())->setURI('/repository/edit/' . $repository->getID() . '/');
             } catch (AphrontQueryDuplicateKeyException $ex) {
                 $e_callsign = 'Duplicate';
                 $errors[] = 'Callsign must be unique. Another repository already ' . 'uses that callsign.';
             }
         }
     }
     $error_view = null;
     if ($errors) {
         $error_view = new AphrontErrorView();
         $error_view->setErrors($errors);
         $error_view->setTitle('Form Errors');
     }
     $form = new AphrontFormView();
     $form->setUser($user)->setAction('/repository/create/')->appendChild(id(new AphrontFormTextControl())->setLabel('Name')->setName('name')->setValue($repository->getName())->setError($e_name)->setCaption('Human-readable repository name.'))->appendChild('<p class="aphront-form-instructions">Select a "Callsign" &mdash; a ' . 'short, uppercase string to identify revisions in this repository. If ' . 'you choose "EX", revisions in this repository will be identified ' . 'with the prefix "rEX".</p>')->appendChild(id(new AphrontFormTextControl())->setLabel('Callsign')->setName('callsign')->setValue($repository->getCallsign())->setError($e_callsign)->setCaption('Short, UPPERCASE identifier. Once set, it can not be changed.'))->appendChild(id(new AphrontFormSelectControl())->setLabel('Type')->setName('type')->setOptions($type_map)->setValue($repository->getVersionControlSystem()))->appendChild(id(new AphrontFormSubmitControl())->setValue('Create Repository')->addCancelButton('/repository/'));
     $panel = new AphrontPanelView();
     $panel->setHeader('Create Repository');
     $panel->appendChild($form);
     $panel->setWidth(AphrontPanelView::WIDTH_FORM);
     return $this->buildStandardPageResponse(array($error_view, $panel), array('title' => 'Create Repository'));
 }
Пример #2
0
 public static final function nameCommit(PhabricatorRepository $repository, $commit)
 {
     switch ($repository->getVersionControlSystem()) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
             $commit_name = substr($commit, 0, 12);
             break;
         default:
             $commit_name = $commit;
             break;
     }
     $callsign = $repository->getCallsign();
     return "r{$callsign}{$commit_name}";
 }
 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();
 }
 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;
     }
     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 getHookContextIdentifier(PhabricatorRepository $repository)
 {
     $identifier = $repository->getCallsign();
     $instance = PhabricatorEnv::getEnvConfig('cluster.instance');
     if (strlen($instance)) {
         $identifier = "{$identifier}:{$instance}";
     }
     return $identifier;
 }
Пример #7
0
 public function loadEditorLink($path, $line, PhabricatorRepository $repository = null)
 {
     $editor = $this->loadPreferences()->getPreference(PhabricatorUserPreferences::PREFERENCE_EDITOR);
     if (is_array($path)) {
         $multiedit = $this->loadPreferences()->getPreference(PhabricatorUserPreferences::PREFERENCE_MULTIEDIT);
         switch ($multiedit) {
             case '':
                 $path = implode(' ', $path);
                 break;
             case 'disable':
                 return null;
         }
     }
     if (!strlen($editor)) {
         return null;
     }
     if ($repository) {
         $callsign = $repository->getCallsign();
     } else {
         $callsign = null;
     }
     $uri = strtr($editor, array('%%' => '%', '%f' => phutil_escape_uri($path), '%l' => phutil_escape_uri($line), '%r' => phutil_escape_uri($callsign)));
     // The resulting URI must have an allowed protocol. Otherwise, we'll return
     // a link to an error page explaining the misconfiguration.
     $ok = PhabricatorHelpEditorProtocolController::hasAllowedProtocol($uri);
     if (!$ok) {
         return '/help/editorprotocol/';
     }
     return (string) $uri;
 }
Пример #8
0
 /**
  * Internal. Use @{method:newFromDictionary}, not this method.
  *
  * @param   PhabricatorRepository   Repository object.
  * @return  DiffusionRequest        New request object.
  * @task new
  */
 private static final function newFromRepository(PhabricatorRepository $repository)
 {
     $map = array(PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => 'DiffusionGitRequest', PhabricatorRepositoryType::REPOSITORY_TYPE_SVN => 'DiffusionSvnRequest', PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => 'DiffusionMercurialRequest');
     $class = idx($map, $repository->getVersionControlSystem());
     if (!$class) {
         throw new Exception("Unknown version control system!");
     }
     $object = new $class();
     $object->repository = $repository;
     $object->callsign = $repository->getCallsign();
     return $object;
 }
 /**
  * @task git
  */
 private function executeGitDiscover(PhabricatorRepository $repository)
 {
     list($remotes) = $repository->execxLocalCommand('remote show -n origin');
     $matches = null;
     if (!preg_match('/^\\s*Fetch URL:\\s*(.*?)\\s*$/m', $remotes, $matches)) {
         throw new Exception("Expected 'Fetch URL' in 'git remote show -n origin'.");
     }
     self::executeGitVerifySameOrigin($matches[1], $repository->getRemoteURI(), $repository->getLocalPath());
     list($stdout) = $repository->execxLocalCommand('branch -r --verbose --no-abbrev');
     $branches = DiffusionGitBranchQuery::parseGitRemoteBranchOutput($stdout, $only_this_remote = DiffusionBranchInformation::DEFAULT_GIT_REMOTE);
     $callsign = $repository->getCallsign();
     $tracked_something = false;
     $this->log("Discovering commits in repository '{$callsign}'...");
     foreach ($branches as $name => $commit) {
         $this->log("Examining branch '{$name}', at {$commit}.");
         if (!$repository->shouldTrackBranch($name)) {
             $this->log("Skipping, branch is untracked.");
             continue;
         }
         $tracked_something = true;
         if ($this->isKnownCommit($repository, $commit)) {
             $this->log("Skipping, HEAD is known.");
             continue;
         }
         $this->log("Looking for new commits.");
         $this->executeGitDiscoverCommit($repository, $commit, $name, false);
     }
     if (!$tracked_something) {
         $repo_name = $repository->getName();
         $repo_callsign = $repository->getCallsign();
         throw new Exception("Repository r{$repo_callsign} '{$repo_name}' has no tracked branches! " . "Verify that your branch filtering settings are correct.");
     }
     $this->log("Discovering commits on autoclose branches...");
     foreach ($branches as $name => $commit) {
         $this->log("Examining branch '{$name}', at {$commit}'.");
         if (!$repository->shouldTrackBranch($name)) {
             $this->log("Skipping, branch is untracked.");
             continue;
         }
         if (!$repository->shouldAutocloseBranch($name)) {
             $this->log("Skipping, branch is not autoclose.");
             continue;
         }
         if ($this->isKnownCommitOnAnyAutocloseBranch($repository, $commit)) {
             $this->log("Skipping, commit is known on an autoclose branch.");
             continue;
         }
         $this->log("Looking for new autoclose commits.");
         $this->executeGitDiscoverCommit($repository, $commit, $name, true);
     }
 }
 private function verifySubversionRoot(PhabricatorRepository $repository)
 {
     list($xml) = $repository->execxRemoteCommand('info --xml %s', $repository->getSubversionPathURI());
     $xml = phutil_utf8ize($xml);
     $xml = new SimpleXMLElement($xml);
     $remote_root = (string) $xml->entry[0]->repository[0]->root[0];
     $expect_root = $repository->getSubversionPathURI();
     $normal_type_svn = PhabricatorRepositoryURINormalizer::TYPE_SVN;
     $remote_normal = id(new PhabricatorRepositoryURINormalizer($normal_type_svn, $remote_root))->getNormalizedPath();
     $expect_normal = id(new PhabricatorRepositoryURINormalizer($normal_type_svn, $expect_root))->getNormalizedPath();
     if ($remote_normal != $expect_normal) {
         throw new Exception(pht('Repository "%s" does not have a correctly configured remote URI. ' . 'The remote URI for a Subversion repository MUST point at the ' . 'repository root. The root for this repository is "%s", but the ' . 'configured URI is "%s". To resolve this error, set the remote URI ' . 'to point at the repository root. If you want to import only part ' . 'of a Subversion repository, use the "Import Only" option.', $repository->getCallsign(), $remote_root, $expect_root));
     }
 }
 private function buildActionList(PhabricatorRepository $repository)
 {
     $viewer = $this->getRequest()->getUser();
     $view_uri = $this->getApplicationURI($repository->getCallsign() . '/');
     $edit_uri = $this->getApplicationURI($repository->getCallsign() . '/edit/');
     $view = id(new PhabricatorActionListView())->setUser($viewer)->setObject($repository)->setObjectURI($view_uri);
     $can_edit = PhabricatorPolicyFilter::hasCapability($viewer, $repository, PhabricatorPolicyCapability::CAN_EDIT);
     $view->addAction(id(new PhabricatorActionView())->setName(pht('Edit Repository'))->setIcon('fa-pencil')->setHref($edit_uri)->setWorkflow(!$can_edit)->setDisabled(!$can_edit));
     if ($repository->isHosted()) {
         $callsign = $repository->getCallsign();
         $push_uri = $this->getApplicationURI('pushlog/?repositories=r' . $callsign);
         $view->addAction(id(new PhabricatorActionView())->setName(pht('View Push Logs'))->setIcon('fa-list-alt')->setHref($push_uri));
     }
     return $view;
 }
 private function renderHeadsupActionList(PhabricatorRepositoryCommit $commit, PhabricatorRepository $repository)
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $actions = id(new PhabricatorActionListView())->setUser($user)->setObject($commit)->setObjectURI($request->getRequestURI());
     $can_edit = PhabricatorPolicyFilter::hasCapability($user, $commit, PhabricatorPolicyCapability::CAN_EDIT);
     $uri = '/diffusion/' . $repository->getCallsign() . '/commit/' . $commit->getCommitIdentifier() . '/edit/';
     $action = id(new PhabricatorActionView())->setName(pht('Edit Commit'))->setHref($uri)->setIcon('fa-pencil')->setDisabled(!$can_edit)->setWorkflow(!$can_edit);
     $actions->addAction($action);
     require_celerity_resource('phabricator-object-selector-css');
     require_celerity_resource('javelin-behavior-phabricator-object-selector');
     $maniphest = 'PhabricatorManiphestApplication';
     if (PhabricatorApplication::isClassInstalled($maniphest)) {
         $action = id(new PhabricatorActionView())->setName(pht('Edit Maniphest Tasks'))->setIcon('fa-anchor')->setHref('/search/attach/' . $commit->getPHID() . '/TASK/edge/')->setWorkflow(true)->setDisabled(!$can_edit);
         $actions->addAction($action);
     }
     $action = id(new PhabricatorActionView())->setName(pht('Download Raw Diff'))->setHref($request->getRequestURI()->alter('diff', true))->setIcon('fa-download');
     $actions->addAction($action);
     return $actions;
 }
 private static function buildDiffusionRequest(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit)
 {
     return DiffusionRequest::newFromAphrontRequestDictionary(array('callsign' => $repository->getCallsign(), 'commit' => $commit->getCommitIdentifier()));
 }
 protected function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit)
 {
     // PREAMBLE: This class is absurdly complicated because it is very difficult
     // to get the information we need out of SVN. The actual data we need is:
     //
     //  1. Recursively, what were the affected paths?
     //  2. For each affected path, is it a file or a directory?
     //  3. How was each path affected (e.g. add, delete, move, copy)?
     //
     // We spend nearly all of our effort figuring out (1) and (2) because
     // "svn log" is not recursive and does not give us file/directory
     // information (that is, it will report a directory move as a single move,
     // even if many thousands of paths are affected).
     //
     // Instead, we have to "svn ls -R" the location of each path in its previous
     // life to figure out whether it is a file or a directory and exactly which
     // recursive paths were affected if it was moved or copied. This is very
     // complicated and has many special cases.
     $uri = $repository->getDetail('remote-uri');
     $svn_commit = $commit->getCommitIdentifier();
     $callsign = $repository->getCallsign();
     $full_name = 'r' . $callsign . $svn_commit;
     echo "Parsing {$full_name}...\n";
     if ($this->isBadCommit($full_name)) {
         echo "This commit is marked bad!\n";
         return;
     }
     // Pull the top-level path changes out of "svn log". This is pretty
     // straightforward; just parse the XML log.
     $log = $this->getSVNLogXMLObject($uri, $svn_commit, $verbose = true);
     $entry = $log->logentry[0];
     if (!$entry->paths) {
         // TODO: Explicitly mark this commit as broken elsewhere? This isn't
         // supposed to happen but we have some cases like rE27 and rG935 in the
         // Facebook repositories where things got all clowned up.
         return;
     }
     $raw_paths = array();
     foreach ($entry->paths->path as $path) {
         $name = trim((string) $path);
         $raw_paths[$name] = array('rawPath' => $name, 'rawTargetPath' => (string) $path['copyfrom-path'], 'rawChangeType' => (string) $path['action'], 'rawTargetCommit' => (string) $path['copyfrom-rev']);
     }
     $copied_or_moved_map = array();
     $deleted_paths = array();
     $add_paths = array();
     foreach ($raw_paths as $path => $raw_info) {
         if ($raw_info['rawTargetPath']) {
             $copied_or_moved_map[$raw_info['rawTargetPath']][] = $raw_info;
         }
         switch ($raw_info['rawChangeType']) {
             case 'D':
                 $deleted_paths[$path] = $raw_info;
                 break;
             case 'A':
                 $add_paths[$path] = $raw_info;
                 break;
         }
     }
     // If a path was deleted, we need to look in the repository history to
     // figure out where the former valid location for it is so we can figure out
     // if it was a directory or not, among other things.
     $lookup_here = array();
     foreach ($raw_paths as $path => $raw_info) {
         if ($raw_info['rawChangeType'] != 'D') {
             continue;
         }
         // If a change copies a directory and then deletes something from it,
         // we need to look at the old location for information about the path, not
         // the new location. This workflow is pretty ridiculous -- so much so that
         // Trac gets it wrong. See Facebook rO6 for an example, if you happen to
         // work at Facebook.
         $parents = $this->expandAllParentPaths($path, $include_self = true);
         foreach ($parents as $parent) {
             if (isset($add_paths[$parent])) {
                 $relative_path = substr($path, strlen($parent));
                 $lookup_here[$path] = array('rawPath' => $add_paths[$parent]['rawTargetPath'] . $relative_path, 'rawCommit' => $add_paths[$parent]['rawTargetCommit']);
                 continue 2;
             }
         }
         // Otherwise we can just look at the previous revision.
         $lookup_here[$path] = array('rawPath' => $path, 'rawCommit' => $svn_commit - 1);
     }
     $lookup = array();
     foreach ($raw_paths as $path => $raw_info) {
         if ($raw_info['rawChangeType'] == 'D') {
             $lookup[$path] = $lookup_here[$path];
         } else {
             // For everything that wasn't deleted, we can just look it up directly.
             $lookup[$path] = array('rawPath' => $path, 'rawCommit' => $svn_commit);
         }
     }
     $path_file_types = $this->lookupPathFileTypes($repository, $lookup);
     $effects = array();
     $resolved_types = array();
     $supplemental = array();
     foreach ($raw_paths as $path => $raw_info) {
         if (isset($resolved_types[$path])) {
             $type = $resolved_types[$path];
         } else {
             switch ($raw_info['rawChangeType']) {
                 case 'D':
                     if (isset($copied_or_moved_map[$path])) {
                         if (count($copied_or_moved_map[$path]) > 1) {
                             $type = DifferentialChangeType::TYPE_MULTICOPY;
                         } else {
                             $type = DifferentialChangeType::TYPE_MOVE_AWAY;
                         }
                     } else {
                         $type = DifferentialChangeType::TYPE_DELETE;
                         $file_type = $path_file_types[$path];
                         if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
                             // Bad. Child paths aren't enumerated in "svn log" so we need
                             // to go fishing.
                             $list = $this->lookupRecursiveFileList($repository, $lookup[$path]);
                             foreach ($list as $deleted_path => $path_file_type) {
                                 $deleted_path = rtrim($path . '/' . $deleted_path, '/');
                                 if (!empty($raw_paths[$deleted_path])) {
                                     // We somehow learned about this deletion explicitly?
                                     // TODO: Unclear how this is possible.
                                     continue;
                                 }
                                 $effects[$deleted_path] = array('rawPath' => $deleted_path, 'rawTargetPath' => null, 'rawTargetCommit' => null, 'rawDirect' => true, 'changeType' => $type, 'fileType' => $path_file_type);
                             }
                         }
                     }
                     break;
                 case 'A':
                     $copy_from = $raw_info['rawTargetPath'];
                     $copy_rev = $raw_info['rawTargetCommit'];
                     if (!strlen($copy_from)) {
                         $type = DifferentialChangeType::TYPE_ADD;
                     } else {
                         if (isset($deleted_paths[$copy_from])) {
                             $type = DifferentialChangeType::TYPE_MOVE_HERE;
                             $other_type = DifferentialChangeType::TYPE_MOVE_AWAY;
                         } else {
                             $type = DifferentialChangeType::TYPE_COPY_HERE;
                             $other_type = DifferentialChangeType::TYPE_COPY_AWAY;
                         }
                         $source_file_type = $this->lookupPathFileType($repository, $copy_from, array('rawPath' => $copy_from, 'rawCommit' => $copy_rev));
                         if ($source_file_type == DifferentialChangeType::FILE_DELETED) {
                             throw new Exception("Something is wrong; source of a copy must exist.");
                         }
                         if ($source_file_type != DifferentialChangeType::FILE_DIRECTORY) {
                             if (isset($raw_paths[$copy_from])) {
                                 break;
                             }
                             $effects[$copy_from] = array('rawPath' => $copy_from, 'rawTargetPath' => null, 'rawTargetCommit' => null, 'rawDirect' => false, 'changeType' => $other_type, 'fileType' => $source_file_type);
                         } else {
                             // ULTRADISASTER. We've added a directory which was copied
                             // or moved from somewhere else. This is the most complex and
                             // ridiculous case.
                             $list = $this->lookupRecursiveFileList($repository, array('rawPath' => $copy_from, 'rawCommit' => $copy_rev));
                             foreach ($list as $from_path => $from_file_type) {
                                 $full_from = rtrim($copy_from . '/' . $from_path, '/');
                                 $full_to = rtrim($path . '/' . $from_path, '/');
                                 if (empty($raw_paths[$full_to])) {
                                     $effects[$full_to] = array('rawPath' => $full_to, 'rawTargetPath' => $full_from, 'rawTargetCommit' => $copy_rev, 'rawDirect' => false, 'changeType' => $type, 'fileType' => $from_file_type);
                                 } else {
                                     // This means we picked the file up explicitly elsewhere.
                                     // If the file as modified, SVN will drop the copy
                                     // information. We need to restore it.
                                     $supplemental[$full_to]['rawTargetPath'] = $full_from;
                                     $supplemental[$full_to]['rawTargetCommit'] = $copy_rev;
                                     if ($raw_paths[$full_to]['rawChangeType'] == 'M') {
                                         $resolved_types[$full_to] = $type;
                                     }
                                 }
                                 if (empty($raw_paths[$full_from])) {
                                     if ($other_type == DifferentialChangeType::TYPE_COPY_AWAY) {
                                         $effects[$full_from] = array('rawPath' => $full_from, 'rawTargetPath' => null, 'rawTargetCommit' => null, 'rawDirect' => false, 'changeType' => $other_type, 'fileType' => $from_file_type);
                                     }
                                 }
                             }
                         }
                     }
                     break;
                     // This is "replaced", caused by "svn rm"-ing a file, putting another
                     // in its place, and then "svn add"-ing it. We do not distinguish
                     // between this and "M".
                 // This is "replaced", caused by "svn rm"-ing a file, putting another
                 // in its place, and then "svn add"-ing it. We do not distinguish
                 // between this and "M".
                 case 'R':
                 case 'M':
                     if (isset($copied_or_moved_map[$path])) {
                         $type = DifferentialChangeType::TYPE_COPY_AWAY;
                     } else {
                         $type = DifferentialChangeType::TYPE_CHANGE;
                     }
                     break;
             }
         }
         $resolved_types[$path] = $type;
     }
     foreach ($raw_paths as $path => $raw_info) {
         $raw_paths[$path]['changeType'] = $resolved_types[$path];
         if (isset($supplemental[$path])) {
             foreach ($supplemental[$path] as $key => $value) {
                 $raw_paths[$path][$key] = $value;
             }
         }
     }
     foreach ($raw_paths as $path => $raw_info) {
         $effects[$path] = array('rawPath' => $path, 'rawTargetPath' => $raw_info['rawTargetPath'], 'rawTargetCommit' => $raw_info['rawTargetCommit'], 'rawDirect' => true, 'changeType' => $raw_info['changeType'], 'fileType' => $path_file_types[$path]);
     }
     $parents = array();
     foreach ($effects as $path => $effect) {
         foreach ($this->expandAllParentPaths($path) as $parent_path) {
             $parents[$parent_path] = true;
         }
     }
     $parents = array_keys($parents);
     foreach ($parents as $parent) {
         if (isset($effects[$parent])) {
             continue;
         }
         $effects[$parent] = array('rawPath' => $parent, 'rawTargetPath' => null, 'rawTargetCommit' => null, 'rawDirect' => false, 'changeType' => DifferentialChangeType::TYPE_CHILD, 'fileType' => DifferentialChangeType::FILE_DIRECTORY);
     }
     $lookup_paths = array();
     foreach ($effects as $effect) {
         $lookup_paths[$effect['rawPath']] = true;
         if ($effect['rawTargetPath']) {
             $lookup_paths[$effect['rawTargetPath']] = true;
         }
     }
     $lookup_paths = array_keys($lookup_paths);
     $lookup_commits = array();
     foreach ($effects as $effect) {
         if ($effect['rawTargetCommit']) {
             $lookup_commits[$effect['rawTargetCommit']] = true;
         }
     }
     $lookup_commits = array_keys($lookup_commits);
     $path_map = $this->lookupOrCreatePaths($lookup_paths);
     $commit_map = $this->lookupSvnCommits($repository, $lookup_commits);
     $this->writeChanges($repository, $commit, $effects, $path_map, $commit_map);
     $this->writeBrowse($repository, $commit, $effects, $path_map);
     $this->finishParse();
 }
 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();
 }
 private function expectChanges(PhabricatorRepository $repository, array $commits, array $expect)
 {
     foreach ($commits as $commit) {
         $commit_identifier = $commit->getCommitIdentifier();
         $expect_changes = idx($expect, $commit_identifier);
         if ($expect_changes === null) {
             $this->assertEqual($commit_identifier, null, pht('No test entry for commit "%s" in repository "%s"!', $commit_identifier, $repository->getCallsign()));
         }
         $changes = $this->parseCommit($repository, $commit);
         $path_map = id(new DiffusionPathQuery())->withPathIDs(mpull($changes, 'getPathID'))->execute();
         $path_map = ipull($path_map, 'path');
         $target_commits = array_filter(mpull($changes, 'getTargetCommitID'));
         if ($target_commits) {
             $commits = id(new DiffusionCommitQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withIDs($target_commits)->execute();
             $target_commits = mpull($commits, 'getCommitIdentifier', 'getID');
         }
         $dicts = array();
         foreach ($changes as $key => $change) {
             $target_path = idx($path_map, $change->getTargetPathID());
             $target_commit = idx($target_commits, $change->getTargetCommitID());
             $dicts[$key] = array($path_map[(int) $change->getPathID()], $target_path, $target_commit ? (string) $target_commit : null, (int) $change->getChangeType(), (int) $change->getFileType(), (int) $change->getIsDirect(), (int) $change->getCommitSequence());
         }
         $dicts = ipull($dicts, null, 0);
         $expect_changes = ipull($expect_changes, null, 0);
         ksort($dicts);
         ksort($expect_changes);
         $this->assertEqual($expect_changes, $dicts, pht('Commit %s', $commit_identifier));
     }
 }
 protected function buildDictForRepository(PhabricatorRepository $repository)
 {
     return array('name' => $repository->getName(), 'phid' => $repository->getPHID(), 'callsign' => $repository->getCallsign(), 'vcs' => $repository->getVersionControlSystem(), 'uri' => PhabricatorEnv::getProductionURI($repository->getURI()), 'remoteURI' => (string) $repository->getPublicRemoteURI(), 'tracking' => $repository->getDetail('tracking-enabled'), 'description' => $repository->getDetail('description'));
 }
Пример #18
0
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $shortcuts = id(new PhabricatorRepositoryShortcut())->loadAll();
     if ($shortcuts) {
         $shortcuts = msort($shortcuts, 'getSequence');
         $rows = array();
         foreach ($shortcuts as $shortcut) {
             $rows[] = array(phutil_render_tag('a', array('href' => $shortcut->getHref()), phutil_escape_html($shortcut->getName())), phutil_escape_html($shortcut->getDescription()));
         }
         $shortcut_table = new AphrontTableView($rows);
         $shortcut_table->setHeaders(array('Link', ''));
         $shortcut_table->setColumnClasses(array('pri', 'wide'));
         $shortcut_panel = new AphrontPanelView();
         $shortcut_panel->setHeader('Shortcuts');
         $shortcut_panel->appendChild($shortcut_table);
     } else {
         $shortcut_panel = null;
     }
     $repository = new PhabricatorRepository();
     $repositories = $repository->loadAll();
     foreach ($repositories as $key => $repo) {
         if (!$repo->isTracked()) {
             unset($repositories[$key]);
         }
     }
     $repository_ids = mpull($repositories, 'getID');
     $summaries = array();
     $commits = array();
     if ($repository_ids) {
         $summaries = queryfx_all($repository->establishConnection('r'), 'SELECT * FROM %T WHERE repositoryID IN (%Ld)', PhabricatorRepository::TABLE_SUMMARY, $repository_ids);
         $summaries = ipull($summaries, null, 'repositoryID');
         $commit_ids = array_filter(ipull($summaries, 'lastCommitID'));
         if ($commit_ids) {
             $commit = new PhabricatorRepositoryCommit();
             $commits = $commit->loadAllWhere('id IN (%Ld)', $commit_ids);
             $commits = mpull($commits, null, 'getRepositoryID');
         }
     }
     $rows = array();
     foreach ($repositories as $repository) {
         $id = $repository->getID();
         $commit = idx($commits, $id);
         $size = idx(idx($summaries, $id, array()), 'size', 0);
         $date = '-';
         $time = '-';
         if ($commit) {
             $date = phabricator_date($commit->getEpoch(), $user);
             $time = phabricator_time($commit->getEpoch(), $user);
         }
         $rows[] = array(phutil_render_tag('a', array('href' => '/diffusion/' . $repository->getCallsign() . '/'), phutil_escape_html($repository->getName())), phutil_escape_html($repository->getDetail('description')), PhabricatorRepositoryType::getNameForRepositoryType($repository->getVersionControlSystem()), $size ? number_format($size) : '-', $commit ? DiffusionView::linkCommit($repository, $commit->getCommitIdentifier()) : '-', $date, $time);
     }
     $repository_tool_uri = PhabricatorEnv::getProductionURI('/repository/');
     $repository_tool = phutil_render_tag('a', array('href' => $repository_tool_uri), 'repository tool');
     $no_repositories_txt = 'This instance of Phabricator does not have any ' . 'configured repositories. ';
     if ($user->getIsAdmin()) {
         $no_repositories_txt .= 'To setup one or more repositories, visit the ' . $repository_tool . '.';
     } else {
         $no_repositories_txt .= 'Ask an administrator to setup one or more ' . 'repositories via the ' . $repository_tool . '.';
     }
     $table = new AphrontTableView($rows);
     $table->setNoDataString($no_repositories_txt);
     $table->setHeaders(array('Repository', 'Description', 'VCS', 'Commits', 'Last', 'Date', 'Time'));
     $table->setColumnClasses(array('pri', 'wide', '', 'n', 'n', '', 'right'));
     $panel = new AphrontPanelView();
     $panel->setHeader('Browse Repositories');
     $panel->appendChild($table);
     $crumbs = $this->buildCrumbs();
     return $this->buildStandardPageResponse(array($crumbs, $shortcut_panel, $panel), array('title' => 'Diffusion'));
 }
 /**
  * @task pull
  */
 private function buildUpdateFuture(PhabricatorRepository $repository, $no_discovery)
 {
     $bin = dirname(phutil_get_library_root('phabricator')) . '/bin/repository';
     $flags = array();
     if ($no_discovery) {
         $flags[] = '--no-discovery';
     }
     $callsign = $repository->getCallsign();
     $future = new ExecFuture('%s update %Ls -- %s', $bin, $flags, $callsign);
     // Sometimes, the underlying VCS commands will hang indefinitely. We've
     // observed this occasionally with GitHub, and other users have observed
     // it with other VCS servers.
     // To limit the damage this can cause, kill the update out after a
     // reasonable amount of time, under the assumption that it has hung.
     // Since it's hard to know what a "reasonable" amount of time is given that
     // users may be downloading a repository full of pirated movies over a
     // potato, these limits are fairly generous. Repositories exceeding these
     // limits can be manually pulled with `bin/repository update X`, which can
     // just run for as long as it wants.
     if ($repository->isImporting()) {
         $timeout = phutil_units('4 hours in seconds');
     } else {
         $timeout = phutil_units('15 minutes in seconds');
     }
     $future->setTimeout($timeout);
     return $future;
 }
 private function buildPropertiesTable(PhabricatorRepository $repository)
 {
     $properties = array();
     $properties['Name'] = $repository->getName();
     $properties['Callsign'] = $repository->getCallsign();
     $properties['Description'] = $repository->getDetail('description');
     switch ($repository->getVersionControlSystem()) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
             $properties['Clone URI'] = $repository->getPublicRemoteURI();
             break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
             $properties['Repository Root'] = $repository->getPublicRemoteURI();
             break;
     }
     $rows = array();
     foreach ($properties as $key => $value) {
         $rows[] = array(phutil_escape_html($key), phutil_escape_html($value));
     }
     $table = new AphrontTableView($rows);
     $table->setColumnClasses(array('header', 'wide'));
     $panel = new AphrontPanelView();
     $panel->setHeader('Repository Properties');
     $panel->appendChild($table);
     return $panel;
 }
 private function buildBasicProperties(PhabricatorRepository $repository, PhabricatorActionListView $actions)
 {
     $viewer = $this->getRequest()->getUser();
     $view = id(new PHUIPropertyListView())->setUser($viewer)->setActionList($actions);
     $type = PhabricatorRepositoryType::getNameForRepositoryType($repository->getVersionControlSystem());
     $view->addProperty(pht('Type'), $type);
     $view->addProperty(pht('Callsign'), $repository->getCallsign());
     $clone_name = $repository->getDetail('clone-name');
     if ($repository->isHosted()) {
         $view->addProperty(pht('Clone/Checkout As'), $clone_name ? $clone_name . '/' : phutil_tag('em', array(), $repository->getCloneName() . '/'));
     }
     $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs($repository->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
     if ($project_phids) {
         $this->loadHandles($project_phids);
         $project_text = $this->renderHandlesForPHIDs($project_phids);
     } else {
         $project_text = phutil_tag('em', array(), pht('None'));
     }
     $view->addProperty(pht('Projects'), $project_text);
     $view->addProperty(pht('Status'), $this->buildRepositoryStatus($repository));
     $view->addProperty(pht('Update Frequency'), $this->buildRepositoryUpdateInterval($repository));
     $description = $repository->getDetail('description');
     $view->addSectionHeader(pht('Description'));
     if (!strlen($description)) {
         $description = phutil_tag('em', array(), pht('No description provided.'));
     } else {
         $description = PhabricatorMarkupEngine::renderOneObject($repository, 'description', $viewer);
     }
     $view->addTextContent($description);
     return $view;
 }
 public static function getMailThreading(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit)
 {
     return array('diffusion-audit-' . $commit->getPHID(), 'Commit r' . $repository->getCallsign() . $commit->getCommitIdentifier());
 }
 protected function getRepositoryControllerURI(PhabricatorRepository $repository, $path)
 {
     return $this->getApplicationURI($repository->getCallsign() . '/' . $path);
 }
 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();
 }
Пример #25
0
 public function loadEditorLink($path, $line, PhabricatorRepository $repository = null)
 {
     $editor = $this->getUserSetting(PhabricatorEditorSetting::SETTINGKEY);
     if (is_array($path)) {
         $multi_key = PhabricatorEditorMultipleSetting::SETTINGKEY;
         $multiedit = $this->getUserSetting($multi_key);
         switch ($multiedit) {
             case PhabricatorEditorMultipleSetting::VALUE_SPACES:
                 $path = implode(' ', $path);
                 break;
             case PhabricatorEditorMultipleSetting::VALUE_SINGLE:
             default:
                 return null;
         }
     }
     if (!strlen($editor)) {
         return null;
     }
     if ($repository) {
         $callsign = $repository->getCallsign();
     } else {
         $callsign = null;
     }
     $uri = strtr($editor, array('%%' => '%', '%f' => phutil_escape_uri($path), '%l' => phutil_escape_uri($line), '%r' => phutil_escape_uri($callsign)));
     // The resulting URI must have an allowed protocol. Otherwise, we'll return
     // a link to an error page explaining the misconfiguration.
     $ok = PhabricatorHelpEditorProtocolController::hasAllowedProtocol($uri);
     if (!$ok) {
         return '/help/editorprotocol/';
     }
     return (string) $uri;
 }