mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 14:52:41 +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
|
<?php
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2011 Facebook, Inc.
|
* Copyright 2012 Facebook, Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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);
|
exit($err);
|
||||||
|
|
||||||
case 'repository-launch-readonly':
|
case 'repository-launch-readonly':
|
||||||
$need_launch = phd_load_tracked_repositories_of_type('git');
|
$need_launch = phd_load_tracked_repositories();
|
||||||
if (!$need_launch) {
|
if (!$need_launch) {
|
||||||
echo "There are no repositories with tracking enabled.\n";
|
echo "There are no repositories with tracking enabled.\n";
|
||||||
} else {
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
will_launch($control);
|
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(
|
$control->launchDaemon(
|
||||||
'PhabricatorRepositoryGitFetchDaemon',
|
'PhabricatorRepositoryPullLocalDaemon',
|
||||||
array(
|
array());
|
||||||
$phid,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'repository-launch-master':
|
case 'repository-launch-master':
|
||||||
$need_launch = phd_load_tracked_repositories();
|
$need_launch = phd_load_tracked_repositories();
|
||||||
if (!$need_launch) {
|
if (!$need_launch) {
|
||||||
echo "There are no repositories with tracking enabled.\n";
|
echo "There are no repositories with tracking enabled.\n";
|
||||||
|
exit(1);
|
||||||
} else {
|
} else {
|
||||||
will_launch($control);
|
will_launch($control);
|
||||||
|
|
||||||
|
$control->launchDaemon(
|
||||||
|
'PhabricatorRepositoryPullLocalDaemon',
|
||||||
|
array());
|
||||||
|
|
||||||
foreach ($need_launch as $repository) {
|
foreach ($need_launch as $repository) {
|
||||||
$name = $repository->getName();
|
$name = $repository->getName();
|
||||||
$callsign = $repository->getCallsign();
|
$callsign = $repository->getCallsign();
|
||||||
|
@ -86,12 +81,6 @@ switch (isset($argv[1]) ? $argv[1] : 'help') {
|
||||||
|
|
||||||
switch ($repository->getVersionControlSystem()) {
|
switch ($repository->getVersionControlSystem()) {
|
||||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
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";
|
echo "Launching discovery daemon on the {$desc} repository...\n";
|
||||||
$control->launchDaemon(
|
$control->launchDaemon(
|
||||||
'PhabricatorRepositoryGitCommitDiscoveryDaemon',
|
'PhabricatorRepositoryGitCommitDiscoveryDaemon',
|
||||||
|
@ -108,12 +97,6 @@ switch (isset($argv[1]) ? $argv[1] : 'help') {
|
||||||
));
|
));
|
||||||
break;
|
break;
|
||||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
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";
|
echo "Launching discovery daemon on the {$desc} repository...\n";
|
||||||
$control->launchDaemon(
|
$control->launchDaemon(
|
||||||
'PhabricatorRepositoryMercurialCommitDiscoveryDaemon',
|
'PhabricatorRepositoryMercurialCommitDiscoveryDaemon',
|
||||||
|
@ -224,18 +207,6 @@ switch (isset($argv[1]) ? $argv[1] : 'help') {
|
||||||
exit($err);
|
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() {
|
function phd_load_tracked_repositories() {
|
||||||
phutil_require_module(
|
phutil_require_module(
|
||||||
'phabricator',
|
'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',
|
'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/git',
|
||||||
'PhabricatorRepositoryGitCommitDiscoveryDaemonTestCase' => 'applications/repository/daemon/commitdiscovery/git/__tests__',
|
'PhabricatorRepositoryGitCommitDiscoveryDaemonTestCase' => 'applications/repository/daemon/commitdiscovery/git/__tests__',
|
||||||
'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/git',
|
'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/git',
|
||||||
'PhabricatorRepositoryGitFetchDaemon' => 'applications/repository/daemon/gitfetch',
|
|
||||||
'PhabricatorRepositoryListController' => 'applications/repository/controller/list',
|
'PhabricatorRepositoryListController' => 'applications/repository/controller/list',
|
||||||
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/mercurial',
|
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/mercurial',
|
||||||
'PhabricatorRepositoryMercurialCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/mercurial',
|
'PhabricatorRepositoryMercurialCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/mercurial',
|
||||||
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/mercurial',
|
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/mercurial',
|
||||||
'PhabricatorRepositoryMercurialPullDaemon' => 'applications/repository/daemon/mercurialpull',
|
|
||||||
'PhabricatorRepositoryPullLocalDaemon' => 'applications/repository/daemon/pulllocal',
|
'PhabricatorRepositoryPullLocalDaemon' => 'applications/repository/daemon/pulllocal',
|
||||||
'PhabricatorRepositoryShortcut' => 'applications/repository/storage/shortcut',
|
'PhabricatorRepositoryShortcut' => 'applications/repository/storage/shortcut',
|
||||||
'PhabricatorRepositorySvnCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/svn',
|
'PhabricatorRepositorySvnCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/svn',
|
||||||
|
@ -1752,13 +1750,11 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon',
|
'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon',
|
||||||
'PhabricatorRepositoryGitCommitDiscoveryDaemonTestCase' => 'PhabricatorTestCase',
|
'PhabricatorRepositoryGitCommitDiscoveryDaemonTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
|
'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
|
||||||
'PhabricatorRepositoryGitFetchDaemon' => 'PhabricatorRepositoryPullLocalDaemon',
|
|
||||||
'PhabricatorRepositoryListController' => 'PhabricatorRepositoryController',
|
'PhabricatorRepositoryListController' => 'PhabricatorRepositoryController',
|
||||||
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
|
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
|
||||||
'PhabricatorRepositoryMercurialCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon',
|
'PhabricatorRepositoryMercurialCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon',
|
||||||
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
|
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
|
||||||
'PhabricatorRepositoryMercurialPullDaemon' => 'PhabricatorRepositoryPullLocalDaemon',
|
'PhabricatorRepositoryPullLocalDaemon' => 'PhabricatorDaemon',
|
||||||
'PhabricatorRepositoryPullLocalDaemon' => 'PhabricatorRepositoryDaemon',
|
|
||||||
'PhabricatorRepositoryShortcut' => 'PhabricatorRepositoryDAO',
|
'PhabricatorRepositoryShortcut' => 'PhabricatorRepositoryDAO',
|
||||||
'PhabricatorRepositorySvnCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
|
'PhabricatorRepositorySvnCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
|
||||||
'PhabricatorRepositorySvnCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon',
|
'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.
|
* 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() {
|
/* -( Pulling Repositories )----------------------------------------------- */
|
||||||
$repository = $this->loadRepository();
|
|
||||||
$expected_type = $this->getSupportedRepositoryType();
|
|
||||||
|
|
||||||
$repo_type = $repository->getVersionControlSystem();
|
|
||||||
if ($repo_type != $expected_type) {
|
/**
|
||||||
$repo_type_name = PhabricatorRepositoryType::getNameForRepositoryType(
|
* @task pull
|
||||||
$repo_type);
|
*/
|
||||||
$expected_type_name = PhabricatorRepositoryType::getNameForRepositoryType(
|
public function run() {
|
||||||
$expected_type);
|
|
||||||
$repo_name = $repository->getName().' ('.$repository->getCallsign().')';
|
// Each repository has an individual pull frequency; after we pull it,
|
||||||
throw new Exception(
|
// wait that long to pull it again. When we start up, try to pull everything
|
||||||
"This daemon pulls '{$expected_type_name}' repositories, but the ".
|
// serially.
|
||||||
"repository '{$repo_name}' is a '{$repo_type_name}' repository.");
|
$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();
|
$tracked = $repository->isTracked();
|
||||||
if (!$tracked) {
|
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');
|
$local_path = $repository->getDetail('local-path');
|
||||||
|
|
||||||
if (!$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)) {
|
if (!Filesystem::pathExists($local_path)) {
|
||||||
printf("Creating new directory %s for repo %s\n",
|
$dirname = dirname($local_path);
|
||||||
$local_path, $repository->getName());
|
if (!Filesystem::pathExists($dirname)) {
|
||||||
execx('mkdir -p %s', dirname($local_path));
|
echo "Creating new directory '{$dirname}' ".
|
||||||
$this->executeCreate($repository, $local_path);
|
"for repository '{$callsign}'.\n";
|
||||||
} else {
|
Filesystem::createDirectory($dirname, 0755, $recursive = true);
|
||||||
$this->executeUpdate($repository, $local_path);
|
}
|
||||||
|
|
||||||
|
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/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', 'filesystem');
|
||||||
phutil_require_module('phutil', 'future/exec');
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
||||||
phutil_require_source('PhabricatorRepositoryPullLocalDaemon.php');
|
phutil_require_source('PhabricatorRepositoryPullLocalDaemon.php');
|
||||||
|
|
|
@ -382,4 +382,20 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO {
|
||||||
return 'r'.$this->getCallsign().$short_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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue