diff --git a/src/applications/conduit/method/arcanist/ConduitAPI_arcanist_projectinfo_Method.php b/src/applications/conduit/method/arcanist/ConduitAPI_arcanist_projectinfo_Method.php index b5d955671..d4b86e73c 100644 --- a/src/applications/conduit/method/arcanist/ConduitAPI_arcanist_projectinfo_Method.php +++ b/src/applications/conduit/method/arcanist/ConduitAPI_arcanist_projectinfo_Method.php @@ -1,60 +1,63 @@ 'required string', ); } public function defineReturnType() { return 'nonempty dict'; } public function defineErrorTypes() { return array( 'ERR-BAD-ARCANIST-PROJECT' => 'No such project exists.', ); } protected function execute(ConduitAPIRequest $request) { $name = $request->getValue('name'); $project = id(new PhabricatorRepositoryArcanistProject())->loadOneWhere( 'name = %s', $name); if (!$project) { throw new ConduitException('ERR-BAD-ARCANIST-PROJECT'); } $repository = $project->loadRepository(); $repository_phid = null; $tracked = false; $encoding = null; + $dictionary = array(); if ($repository) { $repository_phid = $repository->getPHID(); $tracked = $repository->isTracked(); $encoding = $repository->getDetail('encoding'); + $dictionary = $repository->toDictionary(); } return array( 'name' => $project->getName(), 'phid' => $project->getPHID(), 'repositoryPHID' => $repository_phid, 'tracked' => $tracked, 'encoding' => $encoding, + 'repository' => $dictionary, ); } } diff --git a/src/applications/conduit/method/repository/ConduitAPI_repository_Method.php b/src/applications/conduit/method/repository/ConduitAPI_repository_Method.php index f2e073418..7e220a884 100644 --- a/src/applications/conduit/method/repository/ConduitAPI_repository_Method.php +++ b/src/applications/conduit/method/repository/ConduitAPI_repository_Method.php @@ -1,21 +1,8 @@ $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'), - ); - } - } diff --git a/src/applications/conduit/method/repository/ConduitAPI_repository_query_Method.php b/src/applications/conduit/method/repository/ConduitAPI_repository_query_Method.php index 0a474afd5..39803b3c3 100644 --- a/src/applications/conduit/method/repository/ConduitAPI_repository_query_Method.php +++ b/src/applications/conduit/method/repository/ConduitAPI_repository_query_Method.php @@ -1,45 +1,45 @@ '; } public function defineErrorTypes() { return array( ); } protected function execute(ConduitAPIRequest $request) { $repositories = id(new PhabricatorRepository())->loadAll(); $results = array(); foreach ($repositories as $repository) { - $results[] = $this->buildDictForRepository($repository); + $results[] = $repository->toDictionary(); } return $results; } } diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 0512211b5..7913afd31 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1,592 +1,605 @@ 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($path, $line = null, $branch = null) { $drequest = DiffusionRequest::newFromDictionary( array( 'repository' => $this, 'path' => $path, 'branch' => $branch, )); return $drequest->generateURI( array( 'action' => 'browse', 'line' => $line, )); } public function getLocalPath() { return $this->getDetail('local-path'); } 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); 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 && git {$pattern})", $this->getSSHKeyfile(), ), $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 = "git {$pattern}"; 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); 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 && git {$pattern})"; array_unshift($args, $this->getLocalPath()); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $pattern = "(cd %s && HGPLAIN=1 hg {$pattern})"; array_unshift($args, $this->getLocalPath()); break; default: throw new Exception("Unrecognized version control system."); } array_unshift($args, $pattern); return $args; } 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) { $uri->setUser(null); } else { $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; } }