diff --git a/src/parser/__tests__/ArcanistBaseCommitParserTestCase.php b/src/parser/__tests__/ArcanistBaseCommitParserTestCase.php index 5d6b11d2..609595b6 100644 --- a/src/parser/__tests__/ArcanistBaseCommitParserTestCase.php +++ b/src/parser/__tests__/ArcanistBaseCommitParserTestCase.php @@ -1,162 +1,160 @@ assertCommit( pht('Empty Rules'), null, array( )); $this->assertCommit( 'Literal', 'xyz', array( 'runtime' => 'literal:xyz', )); } public function testResolutionOrder() { // Rules should be resolved in order: args, local, project, global. These // test cases intentionally scramble argument order to test that resolution // order is independent of argument order. $this->assertCommit( pht('Order: Args'), 'y', array( 'local' => 'literal:n', 'project' => 'literal:n', 'runtime' => 'literal:y', 'user' => 'literal:n', )); $this->assertCommit( pht('Order: Local'), 'y', array( 'project' => 'literal:n', 'local' => 'literal:y', 'user' => 'literal:n', )); $this->assertCommit( pht('Order: Project'), 'y', array( 'project' => 'literal:y', 'user' => 'literal:n', )); $this->assertCommit( pht('Order: Global'), 'y', array( 'user' => 'literal:y', )); } public function testLegacyRule() { // 'global' should translate to 'user' $this->assertCommit( pht('"%s" name', 'global'), 'y', array( 'runtime' => 'arc:global, arc:halt', 'local' => 'arc:halt', 'project' => 'arc:halt', 'user' => 'literal:y', )); // 'args' should translate to 'runtime' $this->assertCommit( pht('"%s" name', 'args'), 'y', array( 'runtime' => 'arc:project, literal:y', 'local' => 'arc:halt', 'project' => 'arc:args', 'user' => 'arc:halt', )); } public function testHalt() { // 'arc:halt' should halt all processing. $this->assertCommit( pht('Halt'), null, array( 'runtime' => 'arc:halt', 'local' => 'literal:xyz', )); } public function testYield() { // 'arc:yield' should yield to other rulesets. $this->assertCommit( pht('Yield'), 'xyz', array( 'runtime' => 'arc:yield, literal:abc', 'local' => 'literal:xyz', )); // This one should return to 'runtime' after exhausting 'local'. $this->assertCommit( pht('Yield + Return'), 'abc', array( 'runtime' => 'arc:yield, literal:abc', 'local' => 'arc:skip', )); } public function testJump() { // This should resolve to 'abc' without hitting any of the halts. $this->assertCommit( pht('Jump'), 'abc', array( 'runtime' => 'arc:project, arc:halt', 'local' => 'literal:abc', 'project' => 'arc:user, arc:halt', 'user' => 'arc:local, arc:halt', )); } public function testJumpReturn() { // After jumping to project, we should return to 'runtime'. $this->assertCommit( pht('Jump Return'), 'xyz', array( 'runtime' => 'arc:project, literal:xyz', 'local' => 'arc:halt', 'project' => '', 'user' => 'arc:halt', )); } private function assertCommit($desc, $commit, $rules) { $parser = $this->buildParser(); $result = $parser->resolveBaseCommit($rules); $this->assertEqual($commit, $result, $desc); } private function buildParser() { // TODO: This is a little hacky because we're using the Arcanist repository // itself to execute tests with, but it should be OK until we get proper // isolation for repository-oriented test cases. $root = dirname(phutil_get_library_root('arcanist')); - $working_copy = ArcanistWorkingCopyIdentity::newFromPath($root); - $configuration_manager = new ArcanistConfigurationManager(); - $configuration_manager->setWorkingCopyIdentity($working_copy); - $repo = ArcanistRepositoryAPI::newAPIFromConfigurationManager( - $configuration_manager); + $working_copy = ArcanistWorkingCopy::newFromWorkingDirectory($root); - return new ArcanistBaseCommitParser($repo); + $api = $working_copy->newRepositoryAPI(); + + return new ArcanistBaseCommitParser($api); } } diff --git a/src/workingcopy/ArcanistGitWorkingCopy.php b/src/workingcopy/ArcanistGitWorkingCopy.php index 9cb157e7..820912ad 100644 --- a/src/workingcopy/ArcanistGitWorkingCopy.php +++ b/src/workingcopy/ArcanistGitWorkingCopy.php @@ -1,22 +1,26 @@ getPath('.git'); } protected function newWorkingCopyFromDirectories( $working_directory, $ancestor_directory) { if (!Filesystem::pathExists($ancestor_directory.'/.git')) { return null; } return new self(); } + public function newRepositoryAPI() { + return new ArcanistGitAPI($this->getPath()); + } + } diff --git a/src/workingcopy/ArcanistMercurialWorkingCopy.php b/src/workingcopy/ArcanistMercurialWorkingCopy.php index f062270f..b5df4b62 100644 --- a/src/workingcopy/ArcanistMercurialWorkingCopy.php +++ b/src/workingcopy/ArcanistMercurialWorkingCopy.php @@ -1,22 +1,26 @@ getPath('.hg'); } protected function newWorkingCopyFromDirectories( $working_directory, $ancestor_directory) { if (!Filesystem::pathExists($ancestor_directory.'/.hg')) { return null; } return new self(); } + public function newRepositoryAPI() { + return new ArcanistMercurialAPI($this->getPath()); + } + } diff --git a/src/workingcopy/ArcanistSubversionWorkingCopy.php b/src/workingcopy/ArcanistSubversionWorkingCopy.php index e14e7a60..6c4cefed 100644 --- a/src/workingcopy/ArcanistSubversionWorkingCopy.php +++ b/src/workingcopy/ArcanistSubversionWorkingCopy.php @@ -1,74 +1,78 @@ getWorkingDirectory()); $root = $this->getPath(); foreach ($paths as $path) { if (!Filesystem::isDescendant($path, $root)) { break; } $candidate = $path.'/.arcconfig'; if (Filesystem::pathExists($candidate)) { return $candidate; } } return parent::getProjectConfigurationFilePath(); } public function getMetadataDirectory() { return $this->getPath('.svn'); } protected function newWorkingCopyFromDirectories( $working_directory, $ancestor_directory) { if (!Filesystem::pathExists($ancestor_directory.'/.svn')) { return null; } return new self(); } protected function selectFromNestedWorkingCopies(array $candidates) { // To select the best working copy in Subversion, we first walk up the // tree looking for a working copy with an ".arcconfig" file. If we find // one, this anchors us. foreach (array_reverse($candidates) as $candidate) { $arcconfig = $candidate->getPath('.arcconfig'); if (Filesystem::pathExists($arcconfig)) { return $candidate; } } // If we didn't find one, we select the outermost working copy. This is // because older versions of Subversion (prior to 1.7) put a ".svn" file // in every directory, and all versions of Subversion allow you to check // out any subdirectory of the project as a working copy. // We could possibly refine this by testing if the working copy was made // with a recent version of Subversion and picking the deepest working copy // if it was, similar to Git and Mercurial. return head($candidates); } + public function newRepositoryAPI() { + return new ArcanistSubversionAPI($this->getPath()); + } + } diff --git a/src/workingcopy/ArcanistWorkingCopy.php b/src/workingcopy/ArcanistWorkingCopy.php index c7b9632c..3a6a8ced 100644 --- a/src/workingcopy/ArcanistWorkingCopy.php +++ b/src/workingcopy/ArcanistWorkingCopy.php @@ -1,113 +1,115 @@ setAncestorClass(__CLASS__) ->execute(); $paths = Filesystem::walkToRoot($path); $paths = array_reverse($paths); $candidates = array(); foreach ($paths as $path_key => $ancestor_path) { foreach ($working_types as $working_type) { $working_copy = $working_type->newWorkingCopyFromDirectories( $path, $ancestor_path); if (!$working_copy) { continue; } $working_copy->path = $ancestor_path; $working_copy->workingDirectory = $path; $candidates[] = $working_copy; } } // If we've found multiple candidate working copies, we need to pick one. // We let the innermost working copy pick the best candidate from among // candidates of the same type. The rules for Git and Mercurial differ // slightly from the rules for Subversion. if ($candidates) { $deepest = last($candidates); foreach ($candidates as $key => $candidate) { if (get_class($candidate) != get_class($deepest)) { unset($candidates[$key]); } } $candidates = array_values($candidates); return $deepest->selectFromNestedWorkingCopies($candidates); } return null; } abstract protected function newWorkingCopyFromDirectories( $working_directory, $ancestor_directory); final public function getPath($to_file = null) { return Filesystem::concatenatePaths( array( $this->path, $to_file, )); } final public function getWorkingDirectory() { return $this->workingDirectory; } public function getProjectConfigurationFilePath() { return $this->getPath('.arcconfig'); } public function getLocalConfigurationFilePath() { if ($this->hasMetadataDirectory()) { return $this->getMetadataPath('arc/config'); } return null; } public function getMetadataDirectory() { return null; } final public function hasMetadataDirectory() { return ($this->getMetadataDirectory() !== null); } final public function getMetadataPath($to_file = null) { if (!$this->hasMetadataDirectory()) { throw new Exception( pht( 'This working copy has no metadata directory, so you can not '. 'resolve metadata paths within it.')); } return Filesystem::concatenatePaths( array( $this->getMetadataDirectory(), $to_file, )); } protected function selectFromNestedWorkingCopies(array $candidates) { // Normally, the best working copy in a stack is the deepest working copy. // Subversion uses slightly different rules. return last($candidates); } + abstract public function newRepositoryAPI(); + }