mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 06:42:42 +01:00
Run one daemon to pull all working copies, not one daemon per working copy
Summary: Allow the pull daemon to take a list of repositories. By default, pull all repositories. Make some effort to respect pull frequencies, although we'll necessarily suffer a bit if running with only one process. NOTE: We still launch one discovery daemon per working copy, so this only cuts the daemon count in half. Test Plan: - Ran `phd debug pulllocal`, verified behavior. - Ran `pull.php P MTEST SVNTEST --trace`, verified it pulled the repos and ran the right commands. - Ran `phd repository-launch-master`, verified the right daemons launched, checked daemon console. - Ran `phd repository-launch-readonly`, verified the right daemon launched, checked daemon console. Reviewers: btrahan, csilvers, davidreuss Reviewed By: csilvers CC: aran Differential Revision: https://secure.phabricator.com/D2418
This commit is contained in:
parent
8c6fa3e62d
commit
1c62a35710
10 changed files with 358 additions and 281 deletions
|
@ -2,7 +2,7 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -49,35 +49,30 @@ switch (isset($argv[1]) ? $argv[1] : 'help') {
|
|||
exit($err);
|
||||
|
||||
case 'repository-launch-readonly':
|
||||
$need_launch = phd_load_tracked_repositories_of_type('git');
|
||||
$need_launch = phd_load_tracked_repositories();
|
||||
if (!$need_launch) {
|
||||
echo "There are no repositories with tracking enabled.\n";
|
||||
} else {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
will_launch($control);
|
||||
|
||||
foreach ($need_launch as $repository) {
|
||||
$name = $repository->getName();
|
||||
$callsign = $repository->getCallsign();
|
||||
$desc = "'{$name}' ({$callsign})";
|
||||
$phid = $repository->getPHID();
|
||||
|
||||
echo "Launching 'git fetch' daemon on the {$desc} repository...\n";
|
||||
$control->launchDaemon(
|
||||
'PhabricatorRepositoryGitFetchDaemon',
|
||||
array(
|
||||
$phid,
|
||||
));
|
||||
}
|
||||
}
|
||||
'PhabricatorRepositoryPullLocalDaemon',
|
||||
array());
|
||||
break;
|
||||
|
||||
case 'repository-launch-master':
|
||||
$need_launch = phd_load_tracked_repositories();
|
||||
if (!$need_launch) {
|
||||
echo "There are no repositories with tracking enabled.\n";
|
||||
exit(1);
|
||||
} else {
|
||||
will_launch($control);
|
||||
|
||||
$control->launchDaemon(
|
||||
'PhabricatorRepositoryPullLocalDaemon',
|
||||
array());
|
||||
|
||||
foreach ($need_launch as $repository) {
|
||||
$name = $repository->getName();
|
||||
$callsign = $repository->getCallsign();
|
||||
|
@ -86,12 +81,6 @@ switch (isset($argv[1]) ? $argv[1] : 'help') {
|
|||
|
||||
switch ($repository->getVersionControlSystem()) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
echo "Launching 'git fetch' daemon on the {$desc} repository...\n";
|
||||
$control->launchDaemon(
|
||||
'PhabricatorRepositoryGitFetchDaemon',
|
||||
array(
|
||||
$phid,
|
||||
));
|
||||
echo "Launching discovery daemon on the {$desc} repository...\n";
|
||||
$control->launchDaemon(
|
||||
'PhabricatorRepositoryGitCommitDiscoveryDaemon',
|
||||
|
@ -108,12 +97,6 @@ switch (isset($argv[1]) ? $argv[1] : 'help') {
|
|||
));
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||
echo "Launching 'hg pull' daemon on the {$desc} repository...\n";
|
||||
$control->launchDaemon(
|
||||
'PhabricatorRepositoryMercurialPullDaemon',
|
||||
array(
|
||||
$phid,
|
||||
));
|
||||
echo "Launching discovery daemon on the {$desc} repository...\n";
|
||||
$control->launchDaemon(
|
||||
'PhabricatorRepositoryMercurialCommitDiscoveryDaemon',
|
||||
|
@ -224,18 +207,6 @@ switch (isset($argv[1]) ? $argv[1] : 'help') {
|
|||
exit($err);
|
||||
}
|
||||
|
||||
function phd_load_tracked_repositories_of_type($type) {
|
||||
$repositories = phd_load_tracked_repositories();
|
||||
|
||||
foreach ($repositories as $key => $repository) {
|
||||
if ($repository->getVersionControlSystem() != $type) {
|
||||
unset($repositories[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $repositories;
|
||||
}
|
||||
|
||||
function phd_load_tracked_repositories() {
|
||||
phutil_require_module(
|
||||
'phabricator',
|
||||
|
|
51
scripts/repository/pull.php
Executable file
51
scripts/repository/pull.php
Executable file
|
@ -0,0 +1,51 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
$root = dirname(dirname(dirname(__FILE__)));
|
||||
require_once $root.'/scripts/__init_script__.php';
|
||||
|
||||
$args = new PhutilArgumentParser($argv);
|
||||
$args->setTagline('manually pull working copies');
|
||||
$args->setSynopsis(<<<EOHELP
|
||||
**pull.php** [__options__] __repository-callsign-or-phid ...__
|
||||
Manually pull/fetch working copies for the named repositories.
|
||||
EOHELP
|
||||
);
|
||||
$args->parseStandardArguments();
|
||||
$args->parse(
|
||||
array(
|
||||
array(
|
||||
'name' => 'repositories',
|
||||
'wildcard' => true,
|
||||
),
|
||||
));
|
||||
|
||||
$repo_names = $args->getArg('repositories');
|
||||
if (!$repo_names) {
|
||||
echo "Specify one or more repositories to pull, by callsign or PHID.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$repos = PhabricatorRepository::loadAllByPHIDOrCallsign($repo_names);
|
||||
foreach ($repos as $repo) {
|
||||
$callsign = $repo->getCallsign();
|
||||
echo "Pulling '{$callsign}'...\n";
|
||||
PhabricatorRepositoryPullLocalDaemon::pullRepository($repo);
|
||||
}
|
||||
echo "Done.\n";
|
|
@ -857,12 +857,10 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/git',
|
||||
'PhabricatorRepositoryGitCommitDiscoveryDaemonTestCase' => 'applications/repository/daemon/commitdiscovery/git/__tests__',
|
||||
'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/git',
|
||||
'PhabricatorRepositoryGitFetchDaemon' => 'applications/repository/daemon/gitfetch',
|
||||
'PhabricatorRepositoryListController' => 'applications/repository/controller/list',
|
||||
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/mercurial',
|
||||
'PhabricatorRepositoryMercurialCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/mercurial',
|
||||
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/mercurial',
|
||||
'PhabricatorRepositoryMercurialPullDaemon' => 'applications/repository/daemon/mercurialpull',
|
||||
'PhabricatorRepositoryPullLocalDaemon' => 'applications/repository/daemon/pulllocal',
|
||||
'PhabricatorRepositoryShortcut' => 'applications/repository/storage/shortcut',
|
||||
'PhabricatorRepositorySvnCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/svn',
|
||||
|
@ -1752,13 +1750,11 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon',
|
||||
'PhabricatorRepositoryGitCommitDiscoveryDaemonTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
|
||||
'PhabricatorRepositoryGitFetchDaemon' => 'PhabricatorRepositoryPullLocalDaemon',
|
||||
'PhabricatorRepositoryListController' => 'PhabricatorRepositoryController',
|
||||
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
|
||||
'PhabricatorRepositoryMercurialCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon',
|
||||
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
|
||||
'PhabricatorRepositoryMercurialPullDaemon' => 'PhabricatorRepositoryPullLocalDaemon',
|
||||
'PhabricatorRepositoryPullLocalDaemon' => 'PhabricatorRepositoryDaemon',
|
||||
'PhabricatorRepositoryPullLocalDaemon' => 'PhabricatorDaemon',
|
||||
'PhabricatorRepositoryShortcut' => 'PhabricatorRepositoryDAO',
|
||||
'PhabricatorRepositorySvnCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
|
||||
'PhabricatorRepositorySvnCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon',
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorRepositoryGitFetchDaemon
|
||||
extends PhabricatorRepositoryPullLocalDaemon {
|
||||
|
||||
protected function getSupportedRepositoryType() {
|
||||
return PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
|
||||
}
|
||||
|
||||
protected function executeCreate(
|
||||
PhabricatorRepository $repository,
|
||||
$local_path) {
|
||||
|
||||
$repository->execxRemoteCommand(
|
||||
'clone --origin origin %s %s',
|
||||
$repository->getRemoteURI(),
|
||||
rtrim($local_path, '/'));
|
||||
}
|
||||
|
||||
protected function executeUpdate(
|
||||
PhabricatorRepository $repository,
|
||||
$local_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');
|
||||
|
||||
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($local_path)) {
|
||||
$files = Filesystem::listDirectory($local_path, $include_hidden = true);
|
||||
if (!$files) {
|
||||
throw new Exception(
|
||||
"Expected to find a git repository at '{$local_path}', but there ".
|
||||
"is an empty directory there. Remove the directory: the daemon ".
|
||||
"will run 'git clone' for you.");
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
"Expected to find a git repository at '{$local_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)) {
|
||||
throw new Exception(
|
||||
"Expected to find a git repository at '{$local_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.");
|
||||
}
|
||||
|
||||
if (!Filesystem::pathsAreEquivalent($repo_path, $local_path)) {
|
||||
throw new Exception(
|
||||
"Expected to find repo at '{$local_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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This is a local command, but needs credentials.
|
||||
$future = $repository->getRemoteCommandFuture('fetch --all --prune');
|
||||
$future->setCWD($local_path);
|
||||
$future->resolvex();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/repository/constants/repositorytype');
|
||||
phutil_require_module('phabricator', 'applications/repository/daemon/pulllocal');
|
||||
|
||||
phutil_require_module('phutil', 'filesystem');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorRepositoryGitFetchDaemon.php');
|
|
@ -1,73 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorRepositoryMercurialPullDaemon
|
||||
extends PhabricatorRepositoryPullLocalDaemon {
|
||||
|
||||
protected function getSupportedRepositoryType() {
|
||||
return PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL;
|
||||
}
|
||||
|
||||
protected function executeCreate(
|
||||
PhabricatorRepository $repository,
|
||||
$local_path) {
|
||||
$repository->execxRemoteCommand(
|
||||
'clone %s %s',
|
||||
$repository->getRemoteURI(),
|
||||
rtrim($local_path, '/'));
|
||||
}
|
||||
|
||||
protected function executeUpdate(
|
||||
PhabricatorRepository $repository,
|
||||
$local_path) {
|
||||
|
||||
// This is a local command, but needs credentials.
|
||||
$future = $repository->getRemoteCommandFuture('pull -u');
|
||||
$future->setCWD($local_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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/repository/constants/repositorytype');
|
||||
phutil_require_module('phabricator', 'applications/repository/daemon/pulllocal');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorRepositoryMercurialPullDaemon.php');
|
|
@ -16,54 +16,293 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
abstract class PhabricatorRepositoryPullLocalDaemon
|
||||
extends PhabricatorRepositoryDaemon {
|
||||
/**
|
||||
* Run pull commands on local working copies to keep them up to date. This
|
||||
* daemon handles all repository types.
|
||||
*
|
||||
* By default, the daemon pulls **every** repository. If you want it to be
|
||||
* responsible for only some repositories, you can launch it with a list of
|
||||
* PHIDs or callsigns:
|
||||
*
|
||||
* ./phd launch repositorypulllocal X Q Z
|
||||
*
|
||||
* If you have a very large number of repositories and some aren't being pulled
|
||||
* as frequently as you'd like, you can either change the pull frequency of
|
||||
* the less-important repositories to a larger number (so the daemon will skip
|
||||
* them more often) or launch one daemon for all the less-important repositories
|
||||
* and one for the more important repositories (or one for each more important
|
||||
* repository).
|
||||
*
|
||||
* @task pull Pulling Repositories
|
||||
* @task git Git Implementation
|
||||
* @task hg Mercurial Implementation
|
||||
*/
|
||||
final class PhabricatorRepositoryPullLocalDaemon
|
||||
extends PhabricatorDaemon {
|
||||
|
||||
abstract protected function getSupportedRepositoryType();
|
||||
abstract protected function executeCreate(
|
||||
PhabricatorRepository $repository,
|
||||
$local_path);
|
||||
abstract protected function executeUpdate(
|
||||
PhabricatorRepository $repository,
|
||||
$local_path);
|
||||
|
||||
final public function run() {
|
||||
$repository = $this->loadRepository();
|
||||
$expected_type = $this->getSupportedRepositoryType();
|
||||
/* -( Pulling Repositories )----------------------------------------------- */
|
||||
|
||||
$repo_type = $repository->getVersionControlSystem();
|
||||
if ($repo_type != $expected_type) {
|
||||
$repo_type_name = PhabricatorRepositoryType::getNameForRepositoryType(
|
||||
$repo_type);
|
||||
$expected_type_name = PhabricatorRepositoryType::getNameForRepositoryType(
|
||||
$expected_type);
|
||||
$repo_name = $repository->getName().' ('.$repository->getCallsign().')';
|
||||
throw new Exception(
|
||||
"This daemon pulls '{$expected_type_name}' repositories, but the ".
|
||||
"repository '{$repo_name}' is a '{$repo_type_name}' repository.");
|
||||
|
||||
/**
|
||||
* @task pull
|
||||
*/
|
||||
public function run() {
|
||||
|
||||
// Each repository has an individual pull frequency; after we pull it,
|
||||
// wait that long to pull it again. When we start up, try to pull everything
|
||||
// serially.
|
||||
$retry_after = array();
|
||||
|
||||
while (true) {
|
||||
$repositories = $this->loadRepositories();
|
||||
|
||||
// Shuffle the repositories, then re-key the array since shuffle()
|
||||
// discards keys. This is mostly for startup, we'll use soft priorities
|
||||
// later.
|
||||
shuffle($repositories);
|
||||
$repositories = mpull($repositories, null, 'getID');
|
||||
|
||||
// If any repositories were deleted, remove them from the retry timer map
|
||||
// so we don't end up with a retry timer that never gets updated and
|
||||
// causes us to sleep for the minimum amount of time.
|
||||
$retry_after = array_select_keys(
|
||||
$retry_after,
|
||||
array_keys($repositories));
|
||||
|
||||
// Assign soft priorities to repositories based on how frequently they
|
||||
// should pull again.
|
||||
asort($retry_after);
|
||||
$repositories = array_select_keys(
|
||||
$repositories,
|
||||
array_keys($retry_after)) + $repositories;
|
||||
|
||||
foreach ($repositories as $id => $repository) {
|
||||
$after = idx($retry_after, $id, 0);
|
||||
if ($after >= time()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
self::pullRepository($repository);
|
||||
$sleep_for = $repository->getDetail('pull-frequency', 15);
|
||||
$retry_after[$id] = time() + $sleep_for;
|
||||
} catch (Exception $ex) {
|
||||
$retry_after[$id] = time() + 15;
|
||||
phlog($ex);
|
||||
}
|
||||
}
|
||||
|
||||
$sleep_until = max(min($retry_after), time() + 15);
|
||||
sleep($sleep_until - time());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task pull
|
||||
*/
|
||||
protected function loadRepositories() {
|
||||
$argv = $this->getArgv();
|
||||
if (!count($argv)) {
|
||||
return id(new PhabricatorRepository())->loadAll();
|
||||
} else {
|
||||
return PhabricatorRepository::loadAllByPHIDOrCallsign($argv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task pull
|
||||
*/
|
||||
public static function pullRepository(PhabricatorRepository $repository) {
|
||||
$tracked = $repository->isTracked();
|
||||
if (!$tracked) {
|
||||
throw new Exception("Tracking is not enabled for this repository.");
|
||||
return;
|
||||
}
|
||||
|
||||
$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 this repository.");
|
||||
throw new Exception(
|
||||
"No local path is available for repository '{$callsign}'.");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (!Filesystem::pathExists($local_path)) {
|
||||
printf("Creating new directory %s for repo %s\n",
|
||||
$local_path, $repository->getName());
|
||||
execx('mkdir -p %s', dirname($local_path));
|
||||
$this->executeCreate($repository, $local_path);
|
||||
} else {
|
||||
$this->executeUpdate($repository, $local_path);
|
||||
$dirname = dirname($local_path);
|
||||
if (!Filesystem::pathExists($dirname)) {
|
||||
echo "Creating new directory '{$dirname}' ".
|
||||
"for repository '{$callsign}'.\n";
|
||||
Filesystem::createDirectory($dirname, 0755, $recursive = true);
|
||||
}
|
||||
|
||||
if ($is_git) {
|
||||
self::executeGitCreate($repository, $local_path);
|
||||
} else if ($is_hg) {
|
||||
self::executeHgCreate($repository, $local_path);
|
||||
}
|
||||
} else {
|
||||
if ($is_git) {
|
||||
self::executeGitUpdate($repository, $local_path);
|
||||
} else if ($is_hg) {
|
||||
self::executeHgUpdate($repository, $local_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* -( Git Implementation )------------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* @task git
|
||||
*/
|
||||
private static function executeGitCreate(
|
||||
PhabricatorRepository $repository,
|
||||
$path) {
|
||||
|
||||
$repository->execxRemoteCommand(
|
||||
'clone --origin origin %s %s',
|
||||
$repository->getRemoteURI(),
|
||||
rtrim($path, '/'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task git
|
||||
*/
|
||||
private static 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');
|
||||
|
||||
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) {
|
||||
throw new Exception(
|
||||
"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.");
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
"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)) {
|
||||
throw new Exception(
|
||||
"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.");
|
||||
}
|
||||
|
||||
if (!Filesystem::pathsAreEquivalent($repo_path, $path)) {
|
||||
throw new Exception(
|
||||
"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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This is a local command, but needs credentials.
|
||||
$future = $repository->getRemoteCommandFuture('fetch --all --prune');
|
||||
$future->setCWD($path);
|
||||
$future->resolvex();
|
||||
}
|
||||
|
||||
|
||||
/* -( Mercurial Implementation )------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* @task hg
|
||||
*/
|
||||
private static function executeHgCreate(
|
||||
PhabricatorRepository $repository,
|
||||
$path) {
|
||||
|
||||
$repository->execxRemoteCommand(
|
||||
'clone %s %s',
|
||||
$repository->getRemoteURI(),
|
||||
rtrim($path, '/'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task hg
|
||||
*/
|
||||
private static 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;
|
||||
}
|
||||
$this->sleep($repository->getDetail('pull-frequency', 15));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/repository/constants/repositorytype');
|
||||
phutil_require_module('phabricator', 'applications/repository/daemon/base');
|
||||
phutil_require_module('phabricator', 'applications/repository/storage/repository');
|
||||
phutil_require_module('phabricator', 'infrastructure/daemon/base');
|
||||
|
||||
phutil_require_module('phutil', 'error');
|
||||
phutil_require_module('phutil', 'filesystem');
|
||||
phutil_require_module('phutil', 'future/exec');
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorRepositoryPullLocalDaemon.php');
|
||||
|
|
|
@ -382,4 +382,20 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO {
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue