diff --git a/src/applications/repository/daemon/commitdiscovery/git/__tests__/PhabricatorRepositoryGitCommitDiscoveryDaemonTestCase.php b/src/applications/repository/daemon/commitdiscovery/git/__tests__/PhabricatorRepositoryGitCommitDiscoveryDaemonTestCase.php index 8ba7af1d1b..1bef77e5c1 100644 --- a/src/applications/repository/daemon/commitdiscovery/git/__tests__/PhabricatorRepositoryGitCommitDiscoveryDaemonTestCase.php +++ b/src/applications/repository/daemon/commitdiscovery/git/__tests__/PhabricatorRepositoryGitCommitDiscoveryDaemonTestCase.php @@ -1,101 +1,113 @@ assertEqual( $expect, !$ex, "Verification that '{$remote}' and '{$config}' are the same origin ". "had a different outcome than expected: {$message}"); } } } diff --git a/src/applications/repository/storage/repository/PhabricatorRepository.php b/src/applications/repository/storage/repository/PhabricatorRepository.php index adf2fb1ade..85017c7cf8 100644 --- a/src/applications/repository/storage/repository/PhabricatorRepository.php +++ b/src/applications/repository/storage/repository/PhabricatorRepository.php @@ -1,345 +1,353 @@ true, self::CONFIG_SERIALIZATION => array( 'details' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPHIDConstants::PHID_TYPE_REPO); } 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) { switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $branch = '/'.$this->getDetail('default-branch'); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $branch = null; break; default: throw new Exception("Unknown VCS."); } return '/diffusion/'.$this->getCallsign().'/browse'.$branch.$path; } public static function newPhutilURIFromGitURI($raw_uri) { - // If there's no protocol (git implicit SSH) reformat the URI to be a - // normal URI. These git URIs look like "user@domain.com:path" instead of - // "ssh://user@domain/path". - $uri = new PhutilURI($raw_uri); if (!$uri->getProtocol()) { - list($domain, $path) = explode(':', $raw_uri, 2); - $uri = new PhutilURI('ssh://'.$domain.'/'.$path); + if (strpos($raw_uri, '/') === 0) { + // If the URI starts with a '/', it's an implicit file:// URI on the + // local disk. + $uri = new PhutilURI('file://'.$raw_uri); + } else if (strpos($raw_uri, ':') !== false) { + // If there's no protocol (git implicit SSH) but the URI has a colon, + // it's a git implicit SSH URI. Reformat the URI to be a normal URI. + // These git URIs look like "user@domain.com:path" instead of + // "ssh://user@domain/path". + list($domain, $path) = explode(':', $raw_uri, 2); + $uri = new PhutilURI('ssh://'.$domain.'/'.$path); + } else { + throw new Exception("The Git URI '{$raw_uri}' could not be parsed."); + } } return $uri; } public function getRemoteURI() { $raw_uri = $this->getDetail('remote-uri'); $vcs = $this->getVersionControlSystem(); $is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT); if ($is_git) { $uri = self::newPhutilURIFromGitURI($raw_uri); } else { $uri = new PhutilURI($raw_uri); } if ($this->isSSHProtocol($uri->getProtocol())) { if ($this->getSSHLogin()) { $uri->setUser($this->getSSHLogin()); } } return (string)$uri; } 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 { 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 && 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 shouldUseSSH() { $uri = new PhutilURI($this->getRemoteURI()); $protocol = $uri->getProtocol(); if ($this->isSSHProtocol($protocol)) { return (bool)$this->getSSHKeyfile(); } else { return false; } } public function shouldUseHTTP() { $uri = new PhutilURI($this->getRemoteURI()); $protocol = $uri->getProtocol(); if ($this->isHTTPProtocol($protocol)) { return (bool)$this->getDetail('http-login'); } else { return false; } } private function isSSHProtocol($protocol) { return ($protocol == 'ssh' || $protocol == 'svn+ssh'); } private function isHTTPProtocol($protocol) { return ($protocol == 'http' || $protocol == 'https'); } public function isTracked() { return $this->getDetail('tracking-enabled', false); } public function shouldTrackBranch($branch) { $vcs = $this->getVersionControlSystem(); $is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT); $use_filter = ($is_git); if ($use_filter) { $filter = $this->getDetail('branch-filter', array()); if ($filter && !isset($filter[$branch])) { return false; } } // By default, track all branches. return true; } } diff --git a/src/applications/repository/storage/repository/__tests__/PhabricatorRepositoryTestCase.php b/src/applications/repository/storage/repository/__tests__/PhabricatorRepositoryTestCase.php index 7af6246422..eb5d178d26 100644 --- a/src/applications/repository/storage/repository/__tests__/PhabricatorRepositoryTestCase.php +++ b/src/applications/repository/storage/repository/__tests__/PhabricatorRepositoryTestCase.php @@ -1,65 +1,85 @@ 'ssh://user@domain.com/path.git', 'user@domain.com:path.git' => 'ssh://user@domain.com/path.git', + '/path/to/local/repo.git' => 'file:///path/to/local/repo.git', ); foreach ($map as $raw => $expect) { $uri = PhabricatorRepository::newPhutilURIFromGitURI($raw); $this->assertEqual( $expect, (string)$uri, "Normalized Git URI '{$raw}'"); } } + public function testParseBadGitURI() { + $junk = array( + 'herp derp moon balloon', + ); + + foreach ($junk as $garbage) { + $ex = null; + try { + $uri = PhabricatorRepository::newPhutilURIFromGitURI($garbage); + } catch (Exception $caught) { + $ex = $caught; + } + $this->assertEqual( + true, + (bool)$ex, + 'Expect exception when parsing garbage.'); + } + } + public function testBranchFilter() { $git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; $repo = new PhabricatorRepository(); $repo->setVersionControlSystem($git); $this->assertEqual( true, $repo->shouldTrackBranch('imaginary'), 'Track all branches by default.'); $repo->setDetail( 'branch-filter', array( 'master' => true, )); $this->assertEqual( true, $repo->shouldTrackBranch('master'), 'Track listed branches.'); $this->assertEqual( false, $repo->shouldTrackBranch('imaginary'), 'Do not track unlisted branches.'); } }