From 105dd1899c358142179e8e0ebc1b76266b9f993b Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 12 May 2013 19:05:52 -0700 Subject: [PATCH] Make repository pulls testable Summary: Ref T2784. This moves us toward being able to test the background and Conduit pipelines for repositories. In particular: - Separate the logic for pulling repositories (`git pull`, `hg pull`) out of `PhabricatorRepositoryPullLocalDaemon` and put it in `PhabricatorRepositoryPullEngine`. This allows repositories to be pulled directly without invoking the daemons. - Add tests for the engine, including a future-looking base test case. - Add basic `PhutilDirectoryFixture`-based repositories. Next steps: # Do the same for repo discovery. # Then we can start writing tests against specific Conduit methods. Test Plan: Ran unit tests. Ran `bin/repository pull` on SVN, Hg and Git repositories. Ran `bin/phd debug pulllocal`. Reviewers: btrahan Reviewed By: btrahan CC: aran, nh Maniphest Tasks: T2784 Differential Revision: https://secure.phabricator.com/D5904 --- src/__phutil_library_map__.php | 5 + .../PhabricatorRepositoryPullLocalDaemon.php | 221 +-------------- .../PhabricatorRepositoryPullEngine.php | 267 ++++++++++++++++++ .../PhabricatorWorkingCopyPullTestCase.php | 32 +++ .../PhabricatorWorkingCopyTestCase.php | 95 +++++++ .../engine/__tests__/data/GT.git.tgz | Bin 0 -> 6830 bytes .../engine/__tests__/data/HT.hg.tgz | Bin 0 -> 1101 bytes .../engine/__tests__/data/ST.svn.tgz | Bin 0 -> 7743 bytes ...icatorRepositoryManagementPullWorkflow.php | 10 +- 9 files changed, 405 insertions(+), 225 deletions(-) create mode 100644 src/applications/repository/engine/PhabricatorRepositoryPullEngine.php create mode 100644 src/applications/repository/engine/__tests__/PhabricatorWorkingCopyPullTestCase.php create mode 100644 src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php create mode 100644 src/applications/repository/engine/__tests__/data/GT.git.tgz create mode 100644 src/applications/repository/engine/__tests__/data/HT.hg.tgz create mode 100644 src/applications/repository/engine/__tests__/data/ST.svn.tgz diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0c6ea3d867..e7f9b09d86 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1300,6 +1300,7 @@ phutil_register_library_map(array( 'PhabricatorRepositoryManagementWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementWorkflow.php', 'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryMercurialCommitChangeParserWorker.php', 'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryMercurialCommitMessageParserWorker.php', + 'PhabricatorRepositoryPullEngine' => 'applications/repository/engine/PhabricatorRepositoryPullEngine.php', 'PhabricatorRepositoryPullLocalDaemon' => 'applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php', 'PhabricatorRepositoryPullLocalDaemonTestCase' => 'applications/repository/daemon/__tests__/PhabricatorRepositoryPullLocalDaemonTestCase.php', 'PhabricatorRepositoryQuery' => 'applications/repository/query/PhabricatorRepositoryQuery.php', @@ -1487,6 +1488,8 @@ phutil_register_library_map(array( 'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/PhabricatorWorkerTaskDetailController.php', 'PhabricatorWorkerTaskUpdateController' => 'applications/daemon/controller/PhabricatorWorkerTaskUpdateController.php', 'PhabricatorWorkerTestCase' => 'infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php', + 'PhabricatorWorkingCopyPullTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyPullTestCase.php', + 'PhabricatorWorkingCopyTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php', 'PhabricatorWorkpanelView' => 'view/layout/PhabricatorWorkpanelView.php', 'PhabricatorXHPASTViewController' => 'applications/phpast/controller/PhabricatorXHPASTViewController.php', 'PhabricatorXHPASTViewDAO' => 'applications/phpast/storage/PhabricatorXHPASTViewDAO.php', @@ -3194,6 +3197,8 @@ phutil_register_library_map(array( 'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController', 'PhabricatorWorkerTaskUpdateController' => 'PhabricatorDaemonController', 'PhabricatorWorkerTestCase' => 'PhabricatorTestCase', + 'PhabricatorWorkingCopyPullTestCase' => 'PhabricatorWorkingCopyTestCase', + 'PhabricatorWorkingCopyTestCase' => 'PhabricatorTestCase', 'PhabricatorWorkpanelView' => 'AphrontView', 'PhabricatorXHPASTViewController' => 'PhabricatorController', 'PhabricatorXHPASTViewDAO' => 'PhabricatorLiskDAO', diff --git a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php index b6e0085d9c..ca8739f700 100644 --- a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php +++ b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php @@ -120,7 +120,9 @@ final class PhabricatorRepositoryPullLocalDaemon $callsign = $repository->getCallsign(); $this->log("Updating repository '{$callsign}'."); - $this->pullRepository($repository); + id(new PhabricatorRepositoryPullEngine()) + ->setRepository($repository) + ->pullRepository(); if (!$no_discovery) { // TODO: It would be nice to discover only if we pulled something, @@ -175,54 +177,6 @@ final class PhabricatorRepositoryPullLocalDaemon } } - - /** - * @task pull - */ - public function pullRepository(PhabricatorRepository $repository) { - $vcs = $repository->getVersionControlSystem(); - - $is_svn = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_SVN); - $is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT); - $is_hg = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL); - - if ($is_svn) { - return; - } - - $callsign = $repository->getCallsign(); - - if (!$is_git && !$is_hg) { - throw new Exception( - "Unknown VCS '{$vcs}' for repository '{$callsign}'!"); - } - - $local_path = $repository->getDetail('local-path'); - if (!$local_path) { - throw new Exception( - "No local path is available for repository '{$callsign}'."); - } - - if (!Filesystem::pathExists($local_path)) { - $dirname = dirname($local_path); - if (!Filesystem::pathExists($dirname)) { - Filesystem::createDirectory($dirname, 0755, $recursive = true); - } - - if ($is_git) { - return $this->executeGitCreate($repository, $local_path); - } else if ($is_hg) { - return $this->executeHgCreate($repository, $local_path); - } - } else { - if ($is_git) { - return $this->executeGitUpdate($repository, $local_path); - } else if ($is_hg) { - return $this->executeHgUpdate($repository, $local_path); - } - } - } - public function discoverRepository(PhabricatorRepository $repository) { $vcs = $repository->getVersionControlSystem(); switch ($vcs) { @@ -468,121 +422,6 @@ final class PhabricatorRepositoryPullLocalDaemon /* -( Git Implementation )------------------------------------------------- */ - private function canWrite($path) { - $default_path = - PhabricatorEnv::getEnvConfig('repository.default-local-path'); - return Filesystem::isDescendant($path, $default_path); - } - - /** - * @task git - */ - private function executeGitCreate( - PhabricatorRepository $repository, - $path) { - - $repository->execxRemoteCommand( - 'clone --origin origin %s %s', - $repository->getRemoteURI(), - rtrim($path, '/')); - } - - - /** - * @task git - */ - private function executeGitUpdate( - PhabricatorRepository $repository, - $path) { - - // Run a bunch of sanity checks to detect people checking out repositories - // inside other repositories, making empty directories, pointing the local - // path at some random file or path, etc. - - list($err, $stdout) = $repository->execLocalCommand( - 'rev-parse --show-toplevel'); - $msg = ''; - - if ($err) { - - // Try to raise a more tailored error message in the more common case - // of the user creating an empty directory. (We could try to remove it, - // but might not be able to, and it's much simpler to raise a good - // message than try to navigate those waters.) - if (is_dir($path)) { - $files = Filesystem::listDirectory($path, $include_hidden = true); - if (!$files) { - $msg = - "Expected to find a git repository at '{$path}', but there ". - "is an empty directory there. Remove the directory: the daemon ". - "will run 'git clone' for you."; - } - } else { - $msg = - "Expected to find a git repository at '{$path}', but there is ". - "a non-repository directory (with other stuff in it) there. Move or ". - "remove this directory (or reconfigure the repository to use a ". - "different directory), and then either clone a repository yourself ". - "or let the daemon do it."; - } - } else { - $repo_path = rtrim($stdout, "\n"); - - if (empty($repo_path)) { - $err = true; - $msg = - "Expected to find a git repository at '{$path}', but ". - "there was no result from `git rev-parse --show-toplevel`. ". - "Something is misconfigured or broken. The git repository ". - "may be inside a '.git/' directory."; - } else if (!Filesystem::pathsAreEquivalent($repo_path, $path)) { - $err = true; - $msg = - "Expected to find repo at '{$path}', but the actual ". - "git repository root for this directory is '{$repo_path}'. ". - "Something is misconfigured. The repository's 'Local Path' should ". - "be set to some place where the daemon can check out a working ". - "copy, and should not be inside another git repository."; - } - } - - if ($err && $this->canWrite($path)) { - phlog("{$path} failed sanity check; recloning. ({$msg})"); - Filesystem::remove($path); - $this->executeGitCreate($repository, $path); - } else if ($err) { - throw new Exception($msg); - } - - $retry = false; - do { - // This is a local command, but needs credentials. - $future = $repository->getRemoteCommandFuture('fetch --all --prune'); - $future->setCWD($path); - list($err, $stdout, $stderr) = $future->resolve(); - - if ($err && !$retry && $this->canWrite($path)) { - $retry = true; - // Fix remote origin url if it doesn't match our configuration - $origin_url = - $repository->execLocalCommand('config --get remote.origin.url'); - $remote_uri = $repository->getDetail('remote-uri'); - if ($origin_url != $remote_uri) { - $repository->execLocalCommand('remote set-url origin %s', - $remote_uri); - } - } else if ($err) { - throw new Exception( - "git fetch failed with error #{$err}:\n". - "stdout:{$stdout}\n\n". - "stderr:{$stderr}\n"); - } else { - $retry = false; - } - } while ($retry); - } - - /** * @task git */ @@ -787,60 +626,6 @@ final class PhabricatorRepositoryPullLocalDaemon /* -( Mercurial Implementation )------------------------------------------- */ - /** - * @task hg - */ - private function executeHgCreate( - PhabricatorRepository $repository, - $path) { - - $repository->execxRemoteCommand( - 'clone %s %s', - $repository->getRemoteURI(), - rtrim($path, '/')); - } - - - /** - * @task hg - */ - private function executeHgUpdate( - PhabricatorRepository $repository, - $path) { - - // This is a local command, but needs credentials. - $future = $repository->getRemoteCommandFuture('pull -u'); - $future->setCWD($path); - - try { - $future->resolvex(); - } catch (CommandException $ex) { - $err = $ex->getError(); - $stdout = $ex->getStdOut(); - - // NOTE: Between versions 2.1 and 2.1.1, Mercurial changed the behavior - // of "hg pull" to return 1 in case of a successful pull with no changes. - // This behavior has been reverted, but users who updated between Feb 1, - // 2012 and Mar 1, 2012 will have the erroring version. Do a dumb test - // against stdout to check for this possibility. - // See: https://github.com/facebook/phabricator/issues/101/ - - // NOTE: Mercurial has translated versions, which translate this error - // string. In a translated version, the string will be something else, - // like "aucun changement trouve". There didn't seem to be an easy way - // to handle this (there are hard ways but this is not a common problem - // and only creates log spam, not application failures). Assume English. - - // TODO: Remove this once we're far enough in the future that deployment - // of 2.1 is exceedingly rare? - if ($err == 1 && preg_match('/no changes found/', $stdout)) { - return; - } else { - throw $ex; - } - } - } - private function executeHgDiscover(PhabricatorRepository $repository) { // NOTE: "--debug" gives us 40-character hashes. list($stdout) = $repository->execxLocalCommand('--debug branches'); diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php new file mode 100644 index 0000000000..d295cc7346 --- /dev/null +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -0,0 +1,267 @@ +repository = $repository; + return $this; + } + + private function getRepository() { + return $this->repository; + } + + public function pullRepository() { + $repository = $this->getRepository(); + + if (!$repository) { + throw new Exception("Call setRepository() before pullRepository()!"); + } + + $is_hg = false; + $is_git = false; + + $vcs = $repository->getVersionControlSystem(); + switch ($vcs) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + // We never pull a local copy of Subversion repositories. + return; + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + $is_git = true; + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: + $is_hg = true; + break; + default: + throw new Exception("Unsupported VCS '{$vcs}'!"); + } + + $callsign = $repository->getCallsign(); + $local_path = $repository->getLocalPath(); + if ($local_path === null) { + throw new Exception( + "No local path is configured for repository '{$callsign}'."); + } + + $dirname = dirname($local_path); + if (!Filesystem::pathExists($dirname)) { + Filesystem::createDirectory($dirname, 0755, $recursive = true); + } + + if (!Filesystem::pathExists($local_path)) { + if ($is_git) { + $this->executeGitCreate(); + } else { + $this->executeMercurialCreate(); + } + } else { + if ($is_git) { + $this->executeGitUpdate(); + } else { + $this->executeMercurialUpdate(); + } + } + + return $this; + } + + +/* -( Pulling Git Working Copies )----------------------------------------- */ + + + /** + * @task git + */ + private function executeGitCreate() { + $repository = $this->getRepository(); + + $repository->execxRemoteCommand( + 'clone --origin origin %s %s', + $repository->getRemoteURI(), + rtrim($repository->getLocalPath(), '/')); + } + + + /** + * @task git + */ + private function executeGitUpdate() { + $repository = $this->getRepository(); + + list($err, $stdout) = $repository->execLocalCommand( + 'rev-parse --show-toplevel'); + + $message = null; + $path = $repository->getLocalPath(); + if ($err) { + // Try to raise a more tailored error message in the more common case + // of the user creating an empty directory. (We could try to remove it, + // but might not be able to, and it's much simpler to raise a good + // message than try to navigate those waters.) + if (is_dir($path)) { + $files = Filesystem::listDirectory($path, $include_hidden = true); + if (!$files) { + $message = + "Expected to find a git repository at '{$path}', but there ". + "is an empty directory there. Remove the directory: the daemon ". + "will run 'git clone' for you."; + } else { + $message = + "Expected to find a git repository at '{$path}', but there is ". + "a non-repository directory (with other stuff in it) there. Move ". + "or remove this directory (or reconfigure the repository to use a ". + "different directory), and then either clone a repository ". + "yourself or let the daemon do it."; + } + } else if (is_file($path)) { + $message = + "Expected to find a git repository at '{$path}', but there is a ". + "file there instead. Remove it and let the daemon clone a ". + "repository for you."; + } else { + $message = + "Expected to find a git repository at '{$path}', but did not."; + } + } else { + $repo_path = rtrim($stdout, "\n"); + + if (empty($repo_path)) { + $err = true; + $msg = + "Expected to find a git repository at '{$path}', but ". + "there was no result from `git rev-parse --show-toplevel`. ". + "Something is misconfigured or broken. The git repository ". + "may be inside a '.git/' directory."; + } else if (!Filesystem::pathsAreEquivalent($repo_path, $path)) { + $err = true; + $msg = + "Expected to find repo at '{$path}', but the actual ". + "git repository root for this directory is '{$repo_path}'. ". + "Something is misconfigured. The repository's 'Local Path' should ". + "be set to some place where the daemon can check out a working ". + "copy, and should not be inside another git repository."; + } + } + + if ($err && $this->canDestroyWorkingCopy($path)) { + phlog("Repository working copy at '{$path}' failed sanity check; ". + "destroying and re-cloning. {$message}"); + Filesystem::remove($path); + $this->executeGitCreate(); + } else if ($err) { + throw new Exception($message); + } + + $retry = false; + do { + // This is a local command, but needs credentials. + $future = $repository->getRemoteCommandFuture('fetch --all --prune'); + $future->setCWD($path); + list($err, $stdout, $stderr) = $future->resolve(); + + if ($err && !$retry && $this->canDestroyWorkingCopy($path)) { + $retry = true; + // Fix remote origin url if it doesn't match our configuration + $origin_url = $repository->execLocalCommand( + 'config --get remote.origin.url'); + $remote_uri = $repository->getDetail('remote-uri'); + if ($origin_url != $remote_uri) { + $repository->execLocalCommand( + 'remote set-url origin %s', + $remote_uri); + } + } else if ($err) { + throw new Exception( + "git fetch failed with error #{$err}:\n". + "stdout:{$stdout}\n\n". + "stderr:{$stderr}\n"); + } else { + $retry = false; + } + } while ($retry); + } + + +/* -( Pulling Mercurial Working Copies )----------------------------------- */ + + + /** + * @task hg + */ + private function executeMercurialCreate() { + $repository = $this->getRepository(); + + $repository->execxRemoteCommand( + 'clone %s %s', + $repository->getRemoteURI(), + rtrim($repository->getLocalPath(), '/')); + } + + + /** + * @task hg + */ + private function executeMercurialUpdate() { + $repository = $this->getRepository(); + $path = $repository->getLocalPath(); + + // This is a local command, but needs credentials. + $future = $repository->getRemoteCommandFuture('pull -u'); + $future->setCWD($path); + + try { + $future->resolvex(); + } catch (CommandException $ex) { + $err = $ex->getError(); + $stdout = $ex->getStdOut(); + + // NOTE: Between versions 2.1 and 2.1.1, Mercurial changed the behavior + // of "hg pull" to return 1 in case of a successful pull with no changes. + // This behavior has been reverted, but users who updated between Feb 1, + // 2012 and Mar 1, 2012 will have the erroring version. Do a dumb test + // against stdout to check for this possibility. + // See: https://github.com/facebook/phabricator/issues/101/ + + // NOTE: Mercurial has translated versions, which translate this error + // string. In a translated version, the string will be something else, + // like "aucun changement trouve". There didn't seem to be an easy way + // to handle this (there are hard ways but this is not a common problem + // and only creates log spam, not application failures). Assume English. + + // TODO: Remove this once we're far enough in the future that deployment + // of 2.1 is exceedingly rare? + if ($err == 1 && preg_match('/no changes found/', $stdout)) { + return; + } else { + throw $ex; + } + } + } + + +/* -( Internals )---------------------------------------------------------- */ + + + private function canDestroyWorkingCopy($path) { + $default_path = PhabricatorEnv::getEnvConfig( + 'repository.default-local-path'); + return Filesystem::isDescendant($path, $default_path); + } + +} diff --git a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyPullTestCase.php b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyPullTestCase.php new file mode 100644 index 0000000000..0ab91d2b48 --- /dev/null +++ b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyPullTestCase.php @@ -0,0 +1,32 @@ +buildPulledRepository('GT'); + + $this->assertEqual( + true, + Filesystem::pathExists($repo->getLocalPath().'/.git')); + } + + public function testHgPullBasic() { + $repo = $this->buildPulledRepository('HT'); + + $this->assertEqual( + true, + Filesystem::pathExists($repo->getLocalPath().'/.hg')); + } + + public function testSVNPullBasic() { + $repo = $this->buildPulledRepository('ST'); + + // We don't pull local clones for SVN, so we don't expect there to be + // a working copy. + $this->assertEqual( + false, + Filesystem::pathExists($repo->getLocalPath())); + } + +} diff --git a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php new file mode 100644 index 0000000000..e8da90dde1 --- /dev/null +++ b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php @@ -0,0 +1,95 @@ + true, + ); + } + + protected function buildBareRepository($callsign) { + if (isset($this->repos[$callsign])) { + return $this->repos[$callsign]; + } + + $data_dir = dirname(__FILE__).'/data/'; + + $types = array( + 'svn' => PhabricatorRepositoryType::REPOSITORY_TYPE_SVN, + 'hg' => PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL, + 'git' => PhabricatorRepositoryType::REPOSITORY_TYPE_GIT, + ); + + $hits = array(); + foreach ($types as $type => $const) { + $path = $data_dir.$callsign.'.'.$type.'.tgz'; + if (Filesystem::pathExists($path)) { + $hits[$const] = $path; + } + } + + if (!$hits) { + throw new Exception( + "No test data for callsign '{$callsign}'. Expected an archive ". + "like '{$callsign}.git.tgz' in '{$data_dir}'."); + } + + if (count($hits) > 1) { + throw new Exception( + "Expected exactly one archive matching callsign '{$callsign}', ". + "found too many: ".implode(', ', $hits)); + } + + $path = head($hits); + $vcs_type = head_key($hits); + + $dir = PhutilDirectoryFixture::newFromArchive($path); + $local = new TempFile('.ignore'); + + $repo = id(new PhabricatorRepository()) + ->setCallsign($callsign) + ->setName(pht('Test Repo "%s"', $callsign)) + ->setVersionControlSystem($vcs_type) + ->setDetail('local-path', dirname($local).'/'.$callsign) + ->setDetail('remote-uri', 'file://'.$dir->getPath().'/'); + + $this->didConstructRepository($repo); + + $repo->save(); + $repo->makeEphemeral(); + + // Keep the disk resources around until we exit. + $this->dirs[] = $dir; + $this->dirs[] = $local; + + $this->repos[$callsign] = $repo; + + return $repo; + } + + protected function didConstructRepository(PhabricatorRepository $repository) { + return; + } + + protected function buildPulledRepository($callsign) { + $repository = $this->buildBareRepository($callsign); + + if (isset($this->pulled[$callsign])) { + return $repository; + } + + id(new PhabricatorRepositoryPullEngine()) + ->setRepository($repository) + ->pullRepository(); + + $this->pulled[$callsign] = true; + + return $repository; + } + +} diff --git a/src/applications/repository/engine/__tests__/data/GT.git.tgz b/src/applications/repository/engine/__tests__/data/GT.git.tgz new file mode 100644 index 0000000000000000000000000000000000000000..b53e9a2d1c2eceeb3e3c3fbdf6c9223debdb02dc GIT binary patch literal 6830 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 literal 0 HcmV?d00001 diff --git a/src/applications/repository/engine/__tests__/data/HT.hg.tgz b/src/applications/repository/engine/__tests__/data/HT.hg.tgz new file mode 100644 index 0000000000000000000000000000000000000000..7b170be199412d9fbe5933809cc004b8284b4eee GIT binary patch literal 1101 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'Pull __repository__, named by callsign or PHID.') ->setArguments( array( - array( - 'name' => 'verbose', - 'help' => 'Show additional debugging information.', - ), array( 'name' => 'repos', 'wildcard' => true, @@ -34,9 +30,9 @@ final class PhabricatorRepositoryManagementPullWorkflow foreach ($repos as $repo) { $console->writeOut("Pulling '%s'...\n", $repo->getCallsign()); - $daemon = new PhabricatorRepositoryPullLocalDaemon(array()); - $daemon->setVerbose($args->getArg('verbose')); - $daemon->pullRepository($repo); + id(new PhabricatorRepositoryPullEngine()) + ->setRepository($repo) + ->pullRepository(); } $console->writeOut("Done.\n");