diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 089ecb6415..9b9bf41c2c 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -1,286 +1,292 @@ <?php final class DiffusionRepositoryController extends DiffusionController { public function processRequest() { $drequest = $this->diffusionRequest; $content = array(); $crumbs = $this->buildCrumbs(); $content[] = $crumbs; $content[] = $this->buildPropertiesTable($drequest->getRepository()); $history_results = $this->callConduitWithDiffusionRequest( 'diffusion.historyquery', array( 'commit' => $drequest->getCommit(), 'path' => $drequest->getPath(), 'offset' => 0, 'limit' => 15)); $history = DiffusionPathChange::newFromConduit( $history_results['pathChanges']); $browse_results = DiffusionBrowseResultSet::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.browsequery', array( 'path' => $drequest->getPath(), 'commit' => $drequest->getCommit(), ))); $browse_paths = $browse_results->getPaths(); $phids = array(); foreach ($history as $item) { $data = $item->getCommitData(); if ($data) { if ($data->getCommitDetail('authorPHID')) { $phids[$data->getCommitDetail('authorPHID')] = true; } if ($data->getCommitDetail('committerPHID')) { $phids[$data->getCommitDetail('committerPHID')] = true; } } } foreach ($browse_paths as $item) { $data = $item->getLastCommitData(); if ($data) { if ($data->getCommitDetail('authorPHID')) { $phids[$data->getCommitDetail('authorPHID')] = true; } if ($data->getCommitDetail('committerPHID')) { $phids[$data->getCommitDetail('committerPHID')] = true; } } } $phids = array_keys($phids); $handles = $this->loadViewerHandles($phids); $readme = $this->callConduitWithDiffusionRequest( 'diffusion.readmequery', array( 'paths' => $browse_results->getPathDicts() )); $history_table = new DiffusionHistoryTableView(); $history_table->setUser($this->getRequest()->getUser()); $history_table->setDiffusionRequest($drequest); $history_table->setHandles($handles); $history_table->setHistory($history); $history_table->loadRevisions(); $history_table->setParents($history_results['parents']); $history_table->setIsHead(true); $callsign = $drequest->getRepository()->getCallsign(); $all = phutil_tag( 'a', array( 'href' => $drequest->generateURI( array( 'action' => 'history', )), ), pht('View Full Commit History')); $panel = new AphrontPanelView(); $panel->setHeader(pht("Recent Commits · %s", $all)); $panel->appendChild($history_table); $panel->setNoBackground(); $content[] = $panel; $browse_table = new DiffusionBrowseTableView(); $browse_table->setDiffusionRequest($drequest); $browse_table->setHandles($handles); $browse_table->setPaths($browse_paths); $browse_table->setUser($this->getRequest()->getUser()); $browse_panel = new AphrontPanelView(); $browse_panel->setHeader(phutil_tag( 'a', array('href' => $drequest->generateURI(array('action' => 'browse'))), pht('Browse Repository'))); $browse_panel->appendChild($browse_table); $browse_panel->setNoBackground(); $content[] = $browse_panel; $content[] = $this->buildTagListTable($drequest); $content[] = $this->buildBranchListTable($drequest); if ($readme) { $box = new PHUIBoxView(); $box->setShadow(true); $box->appendChild($readme); $box->addPadding(PHUI::PADDING_LARGE); $panel = new AphrontPanelView(); $panel->setHeader(pht('README')); $panel->setNoBackground(); $panel->appendChild($box); $content[] = $panel; } return $this->buildApplicationPage( $content, array( 'title' => $drequest->getRepository()->getName(), 'dust' => true, 'device' => true, )); } private function buildPropertiesTable(PhabricatorRepository $repository) { + $user = $this->getRequest()->getUser(); $header = id(new PhabricatorHeaderView()) ->setHeader($repository->getName()); - $view = new PhabricatorPropertyListView(); + $view = id(new PhabricatorPropertyListView()) + ->setUser($user); $view->addProperty(pht('Callsign'), $repository->getCallsign()); switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $view->addProperty( pht('Clone URI'), $repository->getPublicRemoteURI()); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $view->addProperty( pht('Repository Root'), $repository->getPublicRemoteURI()); break; } $description = $repository->getDetail('description'); if (strlen($description)) { + $description = PhabricatorMarkupEngine::renderOneObject( + $repository, + 'description', + $user); $view->addTextContent($description); } return array($header, $view); } private function buildBranchListTable(DiffusionRequest $drequest) { if ($drequest->getBranch() !== null) { $limit = 15; $branches = DiffusionBranchInformation::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.branchquery', array( 'limit' => $limit ))); if (!$branches) { return null; } $more_branches = (count($branches) > $limit); $branches = array_slice($branches, 0, $limit); $commits = id(new PhabricatorAuditCommitQuery()) ->withIdentifiers( $drequest->getRepository()->getID(), mpull($branches, 'getHeadCommitIdentifier')) ->needCommitData(true) ->execute(); $table = new DiffusionBranchTableView(); $table->setDiffusionRequest($drequest); $table->setBranches($branches); $table->setCommits($commits); $table->setUser($this->getRequest()->getUser()); $panel = new AphrontPanelView(); $panel->setHeader(pht('Branches')); $panel->setNoBackground(); if ($more_branches) { $panel->setCaption(pht('Showing %d branches.', $limit)); } $panel->addButton( phutil_tag( 'a', array( 'href' => $drequest->generateURI( array( 'action' => 'branches', )), 'class' => 'grey button', ), pht("Show All Branches \xC2\xBB"))); $panel->appendChild($table); return $panel; } return null; } private function buildTagListTable(DiffusionRequest $drequest) { $tag_limit = 15; $tags = array(); try { $tags = DiffusionRepositoryTag::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.tagsquery', array('limit' => $tag_limit + 1))); } catch (ConduitException $e) { if ($e->getMessage() != 'ERR-UNSUPPORTED-VCS') { throw $e; } } if (!$tags) { return null; } $more_tags = (count($tags) > $tag_limit); $tags = array_slice($tags, 0, $tag_limit); $commits = id(new PhabricatorAuditCommitQuery()) ->withIdentifiers( $drequest->getRepository()->getID(), mpull($tags, 'getCommitIdentifier')) ->needCommitData(true) ->execute(); $view = new DiffusionTagListView(); $view->setDiffusionRequest($drequest); $view->setTags($tags); $view->setUser($this->getRequest()->getUser()); $view->setCommits($commits); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); $panel = new AphrontPanelView(); $panel->setHeader(pht('Tags')); if ($more_tags) { $panel->setCaption(pht('Showing the %d most recent tags.', $tag_limit)); } $panel->addButton( phutil_tag( 'a', array( 'href' => $drequest->generateURI( array( 'action' => 'tags', )), 'class' => 'grey button', ), pht("Show All Tags \xC2\xBB"))); $panel->appendChild($view); return $panel; } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php index 9ff999911e..c5ac2e8449 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php @@ -1,133 +1,133 @@ <?php final class DiffusionRepositoryEditBasicController extends DiffusionController { public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $drequest = $this->diffusionRequest; $repository = $drequest->getRepository(); $repository = id(new PhabricatorRepositoryQuery()) ->setViewer($user) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->withIDs(array($repository->getID())) ->executeOne(); if (!$repository) { return new Aphront404Response(); } $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); $v_name = $repository->getName(); $v_desc = $repository->getDetail('description'); $e_name = true; $errors = array(); if ($request->isFormPost()) { $v_name = $request->getStr('name'); $v_desc = $request->getStr('description'); if (!strlen($v_name)) { $e_name = pht('Required'); $errors[] = pht('Repository name is required.'); } else { $e_name = null; } if (!$errors) { $xactions = array(); $template = id(new PhabricatorRepositoryTransaction()); $type_name = PhabricatorRepositoryTransaction::TYPE_NAME; $type_desc = PhabricatorRepositoryTransaction::TYPE_DESCRIPTION; $xactions[] = id(clone $template) ->setTransactionType($type_name) ->setNewValue($v_name); $xactions[] = id(clone $template) ->setTransactionType($type_desc) ->setNewValue($v_desc); id(new PhabricatorRepositoryEditor()) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) ->setActor($user) ->applyTransactions($repository, $xactions); return id(new AphrontRedirectResponse())->setURI($edit_uri); } } $content = array(); $crumbs = $this->buildCrumbs(); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('Edit Basics'))); $content[] = $crumbs; $title = pht('Edit %s', $repository->getName()); if ($errors) { $content[] = id(new AphrontErrorView()) ->setTitle(pht('Form Errors')) ->setErrors($errors); } $form = id(new AphrontFormView()) ->setUser($user) ->setFlexible(true) ->appendChild( id(new AphrontFormTextControl()) ->setName('name') ->setLabel(pht('Name')) ->setValue($v_name) ->setError($e_name)) ->appendChild( - id(new AphrontFormTextAreaControl()) + id(new PhabricatorRemarkupControl()) ->setName('description') ->setLabel(pht('Description')) ->setValue($v_desc)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save')) ->addCancelButton($edit_uri)) ->appendChild(id(new PHUIFormDividerControl())) ->appendRemarkupInstructions($this->getReadmeInstructions()); $content[] = $form; return $this->buildApplicationPage( $content, array( 'title' => $title, 'dust' => true, 'device' => true, )); } private function getReadmeInstructions() { return pht(<<<EOTEXT You can also create a `README` file at the repository root (or in any subdirectory) to provide information about the repository. These formats are supported: | File Name | Rendered As... | |-----------------|----------------| | `README` | Plain Text | | `README.txt` | Plain Text | | `README.remarkup` | Remarkup | | `README.md` | Remarkup | | `README.rainbow` | \xC2\xA1Fiesta! | EOTEXT ); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php index 81d3344030..eead89d4d2 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php @@ -1,112 +1,119 @@ <?php final class DiffusionRepositoryEditController extends DiffusionController { public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $drequest = $this->diffusionRequest; $repository = $drequest->getRepository(); $content = array(); $crumbs = $this->buildCrumbs(); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('Edit'))); $content[] = $crumbs; $title = pht('Edit %s', $repository->getName()); $content[] = id(new PhabricatorHeaderView()) ->setHeader($title); $content[] = $this->buildBasicActions($repository); $content[] = $this->buildBasicProperties($repository); $content[] = id(new PhabricatorHeaderView()) ->setHeader(pht('Edit History')); $xactions = id(new PhabricatorRepositoryTransactionQuery()) ->setViewer($user) ->withObjectPHIDs(array($repository->getPHID())) ->execute(); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($user); foreach ($xactions as $xaction) { if ($xaction->getComment()) { $engine->addObject( $xaction->getComment(), PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); } } $engine->process(); $xaction_view = id(new PhabricatorApplicationTransactionView()) ->setUser($user) ->setTransactions($xactions) ->setMarkupEngine($engine); $content[] = $xaction_view; return $this->buildApplicationPage( $content, array( 'title' => $title, 'device' => true, 'dust' => true, )); } private function buildBasicActions(PhabricatorRepository $repository) { $user = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setUser($user); $can_edit = PhabricatorPolicyFilter::hasCapability( $user, $repository, PhabricatorPolicyCapability::CAN_EDIT); $edit = id(new PhabricatorActionView()) ->setIcon('edit') ->setName(pht('Edit Basic Information')) ->setHref($this->getRepositoryControllerURI($repository, 'edit/basic/')) ->setDisabled(!$can_edit); $view->addAction($edit); return $view; } private function buildBasicProperties(PhabricatorRepository $repository) { + $user = $this->getRequest()->getUser(); + $view = id(new PhabricatorPropertyListView()) - ->setUser($this->getRequest()->getUser()) + ->setUser($user) ->setObject($repository); $view->addProperty(pht('Name'), $repository->getName()); $view->addProperty(pht('ID'), $repository->getID()); $view->addProperty(pht('PHID'), $repository->getPHID()); $type = PhabricatorRepositoryType::getNameForRepositoryType( $repository->getVersionControlSystem()); $view->addProperty(pht('Type'), $type); $view->addProperty(pht('Callsign'), $repository->getCallsign()); $description = $repository->getDetail('description'); + $view->addSectionHeader(pht('Description')); if (!strlen($description)) { - $description = phutil_tag('em', array(), pht('None')); + $description = phutil_tag('em', array(), pht('No description provided.')); + } else { + $description = PhabricatorMarkupEngine::renderOneObject( + $repository, + 'description', + $user); } - $view->addProperty(pht('Description'), $description); - + $view->addTextContent($description); return $view; } } diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 51195b9518..eeb6f18a8e 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1,745 +1,781 @@ <?php /** * @task uri Repository URI Management */ final class PhabricatorRepository extends PhabricatorRepositoryDAO - implements PhabricatorPolicyInterface { + implements + PhabricatorPolicyInterface, + PhabricatorMarkupInterface { /** * Shortest hash we'll recognize in raw "a829f32" form. */ const MINIMUM_UNQUALIFIED_HASH = 7; /** * Shortest hash we'll recognize in qualified "rXab7ef2f8" form. */ const MINIMUM_QUALIFIED_HASH = 5; const TABLE_PATH = 'repository_path'; const TABLE_PATHCHANGE = 'repository_pathchange'; const TABLE_FILESYSTEM = 'repository_filesystem'; const TABLE_SUMMARY = 'repository_summary'; const TABLE_BADCOMMIT = 'repository_badcommit'; const TABLE_LINTMESSAGE = 'repository_lintmessage'; protected $phid; protected $name; protected $callsign; protected $uuid; protected $versionControlSystem; protected $details = array(); private $sshKeyfile; public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'details' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPHIDConstants::PHID_TYPE_REPO); } public function toDictionary() { return array( 'name' => $this->getName(), 'phid' => $this->getPHID(), 'callsign' => $this->getCallsign(), 'vcs' => $this->getVersionControlSystem(), 'uri' => PhabricatorEnv::getProductionURI($this->getURI()), 'remoteURI' => (string)$this->getPublicRemoteURI(), 'tracking' => $this->getDetail('tracking-enabled'), 'description' => $this->getDetail('description'), ); } public function getDetail($key, $default = null) { return idx($this->details, $key, $default); } public function setDetail($key, $value) { $this->details[$key] = $value; return $this; } public function getDiffusionBrowseURIForPath( PhabricatorUser $user, $path, $line = null, $branch = null) { $drequest = DiffusionRequest::newFromDictionary( array( 'user' => $user, 'repository' => $this, 'path' => $path, 'branch' => $branch, )); return $drequest->generateURI( array( 'action' => 'browse', 'line' => $line, )); } public function getLocalPath() { return $this->getDetail('local-path'); } public function getSubversionBaseURI() { $vcs = $this->getVersionControlSystem(); if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) { throw new Exception("Not a subversion repository!"); } $uri = $this->getDetail('remote-uri'); $subpath = $this->getDetail('svn-subpath'); return $uri.$subpath; } public function execRemoteCommand($pattern /* , $arg, ... */) { $args = func_get_args(); $args = $this->formatRemoteCommand($args); return call_user_func_array('exec_manual', $args); } public function execxRemoteCommand($pattern /* , $arg, ... */) { $args = func_get_args(); $args = $this->formatRemoteCommand($args); return call_user_func_array('execx', $args); } public function getRemoteCommandFuture($pattern /* , $arg, ... */) { $args = func_get_args(); $args = $this->formatRemoteCommand($args); return newv('ExecFuture', $args); } public function passthruRemoteCommand($pattern /* , $arg, ... */) { $args = func_get_args(); $args = $this->formatRemoteCommand($args); return call_user_func_array('phutil_passthru', $args); } public function execLocalCommand($pattern /* , $arg, ... */) { $args = func_get_args(); $args = $this->formatLocalCommand($args); return call_user_func_array('exec_manual', $args); } public function execxLocalCommand($pattern /* , $arg, ... */) { $args = func_get_args(); $args = $this->formatLocalCommand($args); return call_user_func_array('execx', $args); } public function getLocalCommandFuture($pattern /* , $arg, ... */) { $args = func_get_args(); $args = $this->formatLocalCommand($args); return newv('ExecFuture', $args); } public function passthruLocalCommand($pattern /* , $arg, ... */) { $args = func_get_args(); $args = $this->formatLocalCommand($args); return call_user_func_array('phutil_passthru', $args); } private function formatRemoteCommand(array $args) { $pattern = $args[0]; $args = array_slice($args, 1); $empty = $this->getEmptyReadableDirectoryPath(); if ($this->shouldUseSSH()) { switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $pattern = "SVN_SSH=%s svn --non-interactive {$pattern}"; array_unshift( $args, csprintf( 'ssh -l %s -i %s', $this->getSSHLogin(), $this->getSSHKeyfile())); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $command = call_user_func_array( 'csprintf', array_merge( array( "(ssh-add %s && HOME=%s git {$pattern})", $this->getSSHKeyfile(), $empty, ), $args)); $pattern = "ssh-agent sh -c %s"; $args = array($command); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $pattern = "hg --config ui.ssh=%s {$pattern}"; array_unshift( $args, csprintf( 'ssh -l %s -i %s', $this->getSSHLogin(), $this->getSSHKeyfile())); break; default: throw new Exception("Unrecognized version control system."); } } else if ($this->shouldUseHTTP()) { switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $pattern = "svn ". "--non-interactive ". "--no-auth-cache ". "--trust-server-cert ". "--username %s ". "--password %s ". $pattern; array_unshift( $args, $this->getDetail('http-login'), $this->getDetail('http-pass')); break; default: throw new Exception( "No support for HTTP Basic Auth in this version control system."); } } else if ($this->shouldUseSVNProtocol()) { switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $pattern = "svn ". "--non-interactive ". "--no-auth-cache ". "--username %s ". "--password %s ". $pattern; array_unshift( $args, $this->getDetail('http-login'), $this->getDetail('http-pass')); break; default: throw new Exception( "SVN protocol is SVN only."); } } else { switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $pattern = "svn --non-interactive {$pattern}"; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $pattern = "HOME=%s git {$pattern}"; array_unshift($args, $empty); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $pattern = "hg {$pattern}"; break; default: throw new Exception("Unrecognized version control system."); } } array_unshift($args, $pattern); return $args; } private function formatLocalCommand(array $args) { $pattern = $args[0]; $args = array_slice($args, 1); $empty = $this->getEmptyReadableDirectoryPath(); switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $pattern = "(cd %s && svn --non-interactive {$pattern})"; array_unshift($args, $this->getLocalPath()); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $pattern = "(cd %s && HOME=%s git {$pattern})"; array_unshift($args, $this->getLocalPath(), $empty); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $hgplain = (phutil_is_windows() ? "set HGPLAIN=1 &&" : "HGPLAIN=1"); $pattern = "(cd %s && {$hgplain} hg {$pattern})"; array_unshift($args, $this->getLocalPath()); break; default: throw new Exception("Unrecognized version control system."); } array_unshift($args, $pattern); return $args; } private function getEmptyReadableDirectoryPath() { // See T2965. Some time after Git 1.7.5.4, Git started fataling if it can // not read $HOME. For many users, $HOME points at /root (this seems to be // a default result of Apache setup). Instead, explicitly point $HOME at a // readable, empty directory so that Git looks for the config file it's // after, fails to locate it, and moves on. This is really silly, but seems // like the least damaging approach to mitigating the issue. $root = dirname(phutil_get_library_root('phabricator')); return $root.'/support/empty/'; } private function getSSHLogin() { return $this->getDetail('ssh-login'); } private function getSSHKeyfile() { if ($this->sshKeyfile === null) { $key = $this->getDetail('ssh-key'); $keyfile = $this->getDetail('ssh-keyfile'); if ($keyfile) { // Make sure we can read the file, that it exists, etc. Filesystem::readFile($keyfile); $this->sshKeyfile = $keyfile; } else if ($key) { $keyfile = new TempFile('phabricator-repository-ssh-key'); chmod($keyfile, 0600); Filesystem::writeFile($keyfile, $key); $this->sshKeyfile = $keyfile; } else { $this->sshKeyfile = ''; } } return (string)$this->sshKeyfile; } public function getURI() { return '/diffusion/'.$this->getCallsign().'/'; } public function isTracked() { return $this->getDetail('tracking-enabled', false); } public function getDefaultBranch() { $default = $this->getDetail('default-branch'); if (strlen($default)) { return $default; } $default_branches = array( PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => 'master', PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => 'default', ); return idx($default_branches, $this->getVersionControlSystem()); } public function getDefaultArcanistBranch() { return coalesce($this->getDefaultBranch(), 'svn'); } private function isBranchInFilter($branch, $filter_key) { $vcs = $this->getVersionControlSystem(); $is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT); $use_filter = ($is_git); if ($use_filter) { $filter = $this->getDetail($filter_key, array()); if ($filter && empty($filter[$branch])) { return false; } } // By default, all branches pass. return true; } public function shouldTrackBranch($branch) { return $this->isBranchInFilter($branch, 'branch-filter'); } public function shouldAutocloseBranch($branch) { if ($this->getDetail('disable-autoclose', false)) { return false; } return $this->isBranchInFilter($branch, 'close-commits-filter'); } public function shouldAutocloseCommit( PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data) { if ($this->getDetail('disable-autoclose', false)) { return false; } switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: return true; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: return true; default: throw new Exception("Unrecognized version control system."); } $branches = $data->getCommitDetail('seenOnBranches', array()); foreach ($branches as $branch) { if ($this->shouldAutocloseBranch($branch)) { return true; } } return false; } public function formatCommitName($commit_identifier) { $vcs = $this->getVersionControlSystem(); $type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; $type_hg = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL; $is_git = ($vcs == $type_git); $is_hg = ($vcs == $type_hg); if ($is_git || $is_hg) { $short_identifier = substr($commit_identifier, 0, 12); } else { $short_identifier = $commit_identifier; } return 'r'.$this->getCallsign().$short_identifier; } public static function loadAllByPHIDOrCallsign(array $names) { $repositories = array(); foreach ($names as $name) { $repo = id(new PhabricatorRepository())->loadOneWhere( 'phid = %s OR callsign = %s', $name, $name); if (!$repo) { throw new Exception( "No repository with PHID or callsign '{$name}' exists!"); } $repositories[$repo->getID()] = $repo; } return $repositories; } /* -( Repository URI Management )------------------------------------------ */ /** * Get the remote URI for this repository. * * @return string * @task uri */ public function getRemoteURI() { return (string)$this->getRemoteURIObject(); } /** * Get the remote URI for this repository, without authentication information. * * @return string Repository URI. * @task uri */ public function getPublicRemoteURI() { $uri = $this->getRemoteURIObject(); // Make sure we don't leak anything if this repo is using HTTP Basic Auth // with the credentials in the URI or something zany like that. if ($uri instanceof PhutilGitURI) { if (!$this->getDetail('show-user', false)) { $uri->setUser(null); } } else { if (!$this->getDetail('show-user', false)) { $uri->setUser(null); } $uri->setPass(null); } return (string)$uri; } /** * Get the protocol for the repository's remote. * * @return string Protocol, like "ssh" or "git". * @task uri */ public function getRemoteProtocol() { $uri = $this->getRemoteURIObject(); if ($uri instanceof PhutilGitURI) { return 'ssh'; } else { return $uri->getProtocol(); } } /** * Get a parsed object representation of the repository's remote URI. This * may be a normal URI (returned as a @{class@libphutil:PhutilURI}) or a git * URI (returned as a @{class@libphutil:PhutilGitURI}). * * @return wild A @{class@libphutil:PhutilURI} or * @{class@libphutil:PhutilGitURI}. * @task uri */ private function getRemoteURIObject() { $raw_uri = $this->getDetail('remote-uri'); if (!$raw_uri) { return new PhutilURI(''); } if (!strncmp($raw_uri, '/', 1)) { return new PhutilURI('file://'.$raw_uri); } $uri = new PhutilURI($raw_uri); if ($uri->getProtocol()) { if ($this->isSSHProtocol($uri->getProtocol())) { if ($this->getSSHLogin()) { $uri->setUser($this->getSSHLogin()); } } return $uri; } $uri = new PhutilGitURI($raw_uri); if ($uri->getDomain()) { if ($this->getSSHLogin()) { $uri->setUser($this->getSSHLogin()); } return $uri; } throw new Exception("Remote URI '{$raw_uri}' could not be parsed!"); } /** * Determine if we should connect to the remote using SSH flags and * credentials. * * @return bool True to use the SSH protocol. * @task uri */ private function shouldUseSSH() { $protocol = $this->getRemoteProtocol(); if ($this->isSSHProtocol($protocol)) { return (bool)$this->getSSHKeyfile(); } else { return false; } } /** * Determine if we should connect to the remote using HTTP flags and * credentials. * * @return bool True to use the HTTP protocol. * @task uri */ private function shouldUseHTTP() { $protocol = $this->getRemoteProtocol(); if ($protocol == 'http' || $protocol == 'https') { return (bool)$this->getDetail('http-login'); } else { return false; } } /** * Determine if we should connect to the remote using SVN flags and * credentials. * * @return bool True to use the SVN protocol. * @task uri */ private function shouldUseSVNProtocol() { $protocol = $this->getRemoteProtocol(); if ($protocol == 'svn') { return (bool)$this->getDetail('http-login'); } else { return false; } } /** * Determine if a protocol is SSH or SSH-like. * * @param string A protocol string, like "http" or "ssh". * @return bool True if the protocol is SSH-like. * @task uri */ private function isSSHProtocol($protocol) { return ($protocol == 'ssh' || $protocol == 'svn+ssh'); } public function delete() { $this->openTransaction(); $paths = id(new PhabricatorOwnersPath()) ->loadAllWhere('repositoryPHID = %s', $this->getPHID()); foreach ($paths as $path) { $path->delete(); } $projects = id(new PhabricatorRepositoryArcanistProject()) ->loadAllWhere('repositoryID = %d', $this->getID()); foreach ($projects as $project) { // note each project deletes its PhabricatorRepositorySymbols $project->delete(); } $commits = id(new PhabricatorRepositoryCommit()) ->loadAllWhere('repositoryID = %d', $this->getID()); foreach ($commits as $commit) { // note PhabricatorRepositoryAuditRequests and // PhabricatorRepositoryCommitData are deleted here too. $commit->delete(); } $conn_w = $this->establishConnection('w'); queryfx( $conn_w, 'DELETE FROM %T WHERE repositoryID = %d', self::TABLE_FILESYSTEM, $this->getID()); queryfx( $conn_w, 'DELETE FROM %T WHERE repositoryID = %d', self::TABLE_PATHCHANGE, $this->getID()); queryfx( $conn_w, 'DELETE FROM %T WHERE repositoryID = %d', self::TABLE_SUMMARY, $this->getID()); $result = parent::delete(); $this->saveTransaction(); return $result; } public function isGit() { $vcs = $this->getVersionControlSystem(); return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT); } public function isSVN() { $vcs = $this->getVersionControlSystem(); return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_SVN); } public function isHg() { $vcs = $this->getVersionControlSystem(); return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL); } /** * Link external bug tracking system if defined. * * @param string Plain text. * @param string Commit identifier. * @return string Remarkup */ public function linkBugtraq($message, $revision = null) { $bugtraq_url = PhabricatorEnv::getEnvConfig('bugtraq.url'); list($bugtraq_re, $id_re) = PhabricatorEnv::getEnvConfig('bugtraq.logregex') + array('', ''); switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: // TODO: Get bugtraq:logregex and bugtraq:url from SVN properties. break; } if (!$bugtraq_url || $bugtraq_re == '') { return $message; } $matches = null; $flags = PREG_SET_ORDER | PREG_OFFSET_CAPTURE; preg_match_all('('.$bugtraq_re.')', $message, $matches, $flags); foreach ($matches as $match) { list($all, $all_offset) = array_shift($match); if ($id_re != '') { // Match substrings with bug IDs preg_match_all('('.$id_re.')', $all, $match, PREG_OFFSET_CAPTURE); list(, $match) = $match; } else { $all_offset = 0; } foreach ($match as $val) { list($s, $offset) = $val; $message = substr_replace( $message, '[[ '.str_replace('%BUGID%', $s, $bugtraq_url).' | '.$s.' ]]', $offset + $all_offset, strlen($s)); } } return $message; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return PhabricatorPolicies::POLICY_USER; case PhabricatorPolicyCapability::CAN_EDIT: return PhabricatorPolicies::POLICY_ADMIN; } } public function hasAutomaticCapability($capability, PhabricatorUser $user) { return false; } + +/* -( PhabricatorMarkupInterface )----------------------------------------- */ + + + public function getMarkupFieldKey($field) { + $hash = PhabricatorHash::digestForIndex($this->getMarkupText($field)); + return "repo:{$hash}"; + } + + public function newMarkupEngine($field) { + return PhabricatorMarkupEngine::newMarkupEngine(array()); + } + + public function getMarkupText($field) { + return $this->getDetail('description'); + } + + public function didMarkupText( + $field, + $output, + PhutilMarkupEngine $engine) { + require_celerity_resource('phabricator-remarkup-css'); + return phutil_tag( + 'div', + array( + 'class' => 'phabricator-remarkup', + ), + $output); + } + + public function shouldUseMarkupCache($field) { + return true; + } + }