1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-29 10:12:41 +01:00

Rough cut of repository tracking

Summary: Basic scaffolding for repository tracking, plus daemon infrastructure
(Timelines, Cursors) and some fixes (memory usage, mysql_connect() junk).

Test Plan: parsed Javelin git commit history via daemon

Reviewers:

CC:
This commit is contained in:
epriestley 2011-03-06 22:29:22 -08:00
parent 9f1b50ad2c
commit 57495c4287
38 changed files with 1176 additions and 39 deletions

View file

@ -0,0 +1,28 @@
create table phabricator_repository.repository_commit (
id int unsigned not null auto_increment primary key,
repositoryPHID varchar(64) binary not null,
phid varchar(64) binary not null,
commitIdentifier varchar(40) binary not null,
epoch int unsigned not null,
unique key (phid),
unique key (repositoryPHID, commitIdentifier)
);
create database phabricator_timeline;
create table phabricator_timeline.timeline_event (
id int unsigned not null auto_increment primary key,
type char(4) binary not null,
key (type, id)
);
create table phabricator_timeline.timeline_eventdata (
id int unsigned not null auto_increment primary key,
eventID int unsigned not null,
eventData longblob not null,
unique key (eventID)
);
create table phabricator_timeline.timeline_cursor (
name varchar(255) not null primary key,
position int unsigned not null
);

View file

@ -171,6 +171,7 @@ phutil_register_library_map(array(
'PhabricatorConduitLogController' => 'applications/conduit/controller/log', 'PhabricatorConduitLogController' => 'applications/conduit/controller/log',
'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/methodcalllog', 'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/methodcalllog',
'PhabricatorController' => 'applications/base/controller/base', 'PhabricatorController' => 'applications/base/controller/base',
'PhabricatorDaemon' => 'infrastructure/daemon/base',
'PhabricatorDirectoryCategory' => 'applications/directory/storage/category', 'PhabricatorDirectoryCategory' => 'applications/directory/storage/category',
'PhabricatorDirectoryCategoryDeleteController' => 'applications/directory/controller/categorydelete', 'PhabricatorDirectoryCategoryDeleteController' => 'applications/directory/controller/categorydelete',
'PhabricatorDirectoryCategoryEditController' => 'applications/directory/controller/categoryedit', 'PhabricatorDirectoryCategoryEditController' => 'applications/directory/controller/categoryedit',
@ -251,13 +252,20 @@ phutil_register_library_map(array(
'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential', 'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential',
'PhabricatorRemarkupRuleManiphest' => 'infrastructure/markup/remarkup/markuprule/maniphest', 'PhabricatorRemarkupRuleManiphest' => 'infrastructure/markup/remarkup/markuprule/maniphest',
'PhabricatorRepository' => 'applications/repository/storage/repository', 'PhabricatorRepository' => 'applications/repository/storage/repository',
'PhabricatorRepositoryCommit' => 'applications/repository/storage/commit',
'PhabricatorRepositoryCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/base',
'PhabricatorRepositoryCommitParserDaemon' => 'applications/repository/daemon/commitparser',
'PhabricatorRepositoryController' => 'applications/repository/controller/base', 'PhabricatorRepositoryController' => 'applications/repository/controller/base',
'PhabricatorRepositoryCreateController' => 'applications/repository/controller/create', 'PhabricatorRepositoryCreateController' => 'applications/repository/controller/create',
'PhabricatorRepositoryDAO' => 'applications/repository/storage/base', 'PhabricatorRepositoryDAO' => 'applications/repository/storage/base',
'PhabricatorRepositoryDaemon' => 'applications/repository/daemon/base',
'PhabricatorRepositoryEditController' => 'applications/repository/controller/edit', 'PhabricatorRepositoryEditController' => 'applications/repository/controller/edit',
'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/git',
'PhabricatorRepositoryGitHubNotification' => 'applications/repository/storage/githubnotification', 'PhabricatorRepositoryGitHubNotification' => 'applications/repository/storage/githubnotification',
'PhabricatorRepositoryGitHubPostReceiveController' => 'applications/repository/controller/github-post-receive', 'PhabricatorRepositoryGitHubPostReceiveController' => 'applications/repository/controller/github-post-receive',
'PhabricatorRepositoryGitPullDaemon' => 'applications/repository/daemon/gitpull',
'PhabricatorRepositoryListController' => 'applications/repository/controller/list', 'PhabricatorRepositoryListController' => 'applications/repository/controller/list',
'PhabricatorRepositoryType' => 'applications/repository/constants/repositorytype',
'PhabricatorSearchAbstractDocument' => 'applications/search/index/abstractdocument', 'PhabricatorSearchAbstractDocument' => 'applications/search/index/abstractdocument',
'PhabricatorSearchBaseController' => 'applications/search/controller/base', 'PhabricatorSearchBaseController' => 'applications/search/controller/base',
'PhabricatorSearchController' => 'applications/search/controller/search', 'PhabricatorSearchController' => 'applications/search/controller/search',
@ -274,6 +282,11 @@ phutil_register_library_map(array(
'PhabricatorSearchQuery' => 'applications/search/storage/query', 'PhabricatorSearchQuery' => 'applications/search/storage/query',
'PhabricatorSearchRelationship' => 'applications/search/constants/relationship', 'PhabricatorSearchRelationship' => 'applications/search/constants/relationship',
'PhabricatorStandardPageView' => 'view/page/standard', 'PhabricatorStandardPageView' => 'view/page/standard',
'PhabricatorTimelineCursor' => 'applications/timeline/storage/cursor',
'PhabricatorTimelineDAO' => 'applications/timeline/storage/base',
'PhabricatorTimelineEvent' => 'applications/timeline/storage/event',
'PhabricatorTimelineEventData' => 'applications/timeline/storage/eventdata',
'PhabricatorTimelineIterator' => 'applications/timeline/cursor/iterator',
'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/common', 'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/common',
'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base', 'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base',
'PhabricatorUser' => 'applications/people/storage/user', 'PhabricatorUser' => 'applications/people/storage/user',
@ -430,6 +443,7 @@ phutil_register_library_map(array(
'PhabricatorConduitLogController' => 'PhabricatorConduitController', 'PhabricatorConduitLogController' => 'PhabricatorConduitController',
'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO',
'PhabricatorController' => 'AphrontController', 'PhabricatorController' => 'AphrontController',
'PhabricatorDaemon' => 'PhutilDaemon',
'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO',
'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController',
@ -500,12 +514,18 @@ phutil_register_library_map(array(
'PhabricatorRemarkupRuleDifferential' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleDifferential' => 'PhutilRemarkupRule',
'PhabricatorRemarkupRuleManiphest' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleManiphest' => 'PhutilRemarkupRule',
'PhabricatorRepository' => 'PhabricatorRepositoryDAO', 'PhabricatorRepository' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryCommit' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryCommitDiscoveryDaemon' => 'PhabricatorRepositoryDaemon',
'PhabricatorRepositoryCommitParserDaemon' => 'PhabricatorRepositoryDaemon',
'PhabricatorRepositoryController' => 'PhabricatorController', 'PhabricatorRepositoryController' => 'PhabricatorController',
'PhabricatorRepositoryCreateController' => 'PhabricatorController', 'PhabricatorRepositoryCreateController' => 'PhabricatorController',
'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO',
'PhabricatorRepositoryDaemon' => 'PhabricatorDaemon',
'PhabricatorRepositoryEditController' => 'PhabricatorController', 'PhabricatorRepositoryEditController' => 'PhabricatorController',
'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon',
'PhabricatorRepositoryGitHubNotification' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryGitHubNotification' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryGitHubPostReceiveController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryGitHubPostReceiveController' => 'PhabricatorRepositoryController',
'PhabricatorRepositoryGitPullDaemon' => 'PhabricatorRepositoryDaemon',
'PhabricatorRepositoryListController' => 'PhabricatorController', 'PhabricatorRepositoryListController' => 'PhabricatorController',
'PhabricatorSearchBaseController' => 'PhabricatorController', 'PhabricatorSearchBaseController' => 'PhabricatorController',
'PhabricatorSearchController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchController' => 'PhabricatorSearchBaseController',
@ -518,6 +538,10 @@ phutil_register_library_map(array(
'PhabricatorSearchMySQLExecutor' => 'PhabricatorSearchExecutor', 'PhabricatorSearchMySQLExecutor' => 'PhabricatorSearchExecutor',
'PhabricatorSearchQuery' => 'PhabricatorSearchDAO', 'PhabricatorSearchQuery' => 'PhabricatorSearchDAO',
'PhabricatorStandardPageView' => 'AphrontPageView', 'PhabricatorStandardPageView' => 'AphrontPageView',
'PhabricatorTimelineCursor' => 'PhabricatorTimelineDAO',
'PhabricatorTimelineDAO' => 'PhabricatorLiskDAO',
'PhabricatorTimelineEvent' => 'PhabricatorTimelineDAO',
'PhabricatorTimelineEventData' => 'PhabricatorTimelineDAO',
'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController',
'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController', 'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController',
'PhabricatorUser' => 'PhabricatorUserDAO', 'PhabricatorUser' => 'PhabricatorUserDAO',

View file

@ -20,28 +20,38 @@ class DarkConsoleErrorLogPluginAPI {
private static $errors = array(); private static $errors = array();
private static $discardMode = false;
public static function enableDiscardMode() {
self::$discardMode = true;
}
public static function getErrors() { public static function getErrors() {
return self::$errors; return self::$errors;
} }
public static function handleError($num, $str, $file, $line, $cxt) { public static function handleError($num, $str, $file, $line, $cxt) {
self::$errors[] = array( if (!self::$discardMode) {
'event' => 'error', self::$errors[] = array(
'num' => $num, 'event' => 'error',
'str' => $str, 'num' => $num,
'file' => $file, 'str' => $str,
'line' => $line, 'file' => $file,
'cxt' => $cxt, 'line' => $line,
'trace' => debug_backtrace(), 'cxt' => $cxt,
); 'trace' => debug_backtrace(),
);
}
error_log("{$file}:{$line} {$str}"); error_log("{$file}:{$line} {$str}");
} }
public static function handleException($ex) { public static function handleException($ex) {
self::$errors[] = array( if (!self::$discardMode) {
'event' => 'exception', self::$errors[] = array(
'exception' => $ex, 'event' => 'exception',
); 'exception' => $ex,
);
}
error_log($ex); error_log($ex);
} }

View file

@ -23,8 +23,16 @@ class DarkConsoleServicesPluginAPI {
private static $events = array(); private static $events = array();
private static $discardMode = false;
public static function enableDiscardMode() {
self::$discardMode = true;
}
public static function addEvent(array $event) { public static function addEvent(array $event) {
self::$events[] = $event; if (!self::$discardMode) {
self::$events[] = $event;
}
} }
public static function getEvents() { public static function getEvents() {

View file

@ -165,7 +165,8 @@ class AphrontDefaultApplicationConfiguration
'/repository/' => array( '/repository/' => array(
'$' => 'PhabricatorRepositoryListController', '$' => 'PhabricatorRepositoryListController',
'create/$' => 'PhabricatorRepositoryCreateController', 'create/$' => 'PhabricatorRepositoryCreateController',
'edit/(?P<id>\d+)/$' => 'PhabricatorRepositoryEditController', 'edit/(?P<id>\d+)/(?:(?P<view>\w+)?/)?$' =>
'PhabricatorRepositoryEditController',
'delete/(?P<id>\d+)/$' => 'PhabricatorRepositoryDeleteController', 'delete/(?P<id>\d+)/$' => 'PhabricatorRepositoryDeleteController',
), ),

View file

@ -26,5 +26,7 @@ final class PhabricatorPHIDConstants {
const PHID_TYPE_PROJ = 'PROJ'; const PHID_TYPE_PROJ = 'PROJ';
const PHID_TYPE_UNKNOWN = '????'; const PHID_TYPE_UNKNOWN = '????';
const PHID_TYPE_MAGIC = '!!!!'; const PHID_TYPE_MAGIC = '!!!!';
const PHID_TYPE_REPO = 'REPO';
const PHID_TYPE_CMIT = 'CMIT';
} }

View file

@ -0,0 +1,24 @@
<?php
/*
* Copyright 2011 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 PhabricatorRepositoryType {
const REPOSITORY_TYPE_GIT = 'git';
const REPOSITORY_TYPE_SVN = 'svn';
}

View file

@ -0,0 +1,10 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_source('PhabricatorRepositoryType.php');

View file

@ -30,8 +30,8 @@ class PhabricatorRepositoryCreateController extends PhabricatorController {
$repository = new PhabricatorRepository(); $repository = new PhabricatorRepository();
$type_map = array( $type_map = array(
'svn' => 'Subversion',
'git' => 'Git', 'git' => 'Git',
'svn' => 'Subversion',
); );
$errors = array(); $errors = array();

View file

@ -19,9 +19,13 @@
class PhabricatorRepositoryEditController extends PhabricatorController { class PhabricatorRepositoryEditController extends PhabricatorController {
private $id; private $id;
private $view;
private $repository;
private $sideNav;
public function willProcessRequest(array $data) { public function willProcessRequest(array $data) {
$this->id = idx($data, 'id'); $this->id = $data['id'];
$this->view = idx($data, 'view');
} }
public function processRequest() { public function processRequest() {
@ -34,6 +38,11 @@ class PhabricatorRepositoryEditController extends PhabricatorController {
return new Aphront404Response(); return new Aphront404Response();
} }
$views = array(
'basic' => 'Basics',
'tracking' => 'Tracking',
);
$vcs = $repository->getVersionControlSystem(); $vcs = $repository->getVersionControlSystem();
if ($vcs == DifferentialRevisionControlSystem::GIT) { if ($vcs == DifferentialRevisionControlSystem::GIT) {
if (!$repository->getDetail('github-token')) { if (!$repository->getDetail('github-token')) {
@ -41,16 +50,60 @@ class PhabricatorRepositoryEditController extends PhabricatorController {
$repository->setDetail('github-token', $token); $repository->setDetail('github-token', $token);
$repository->save(); $repository->save();
} }
$views['github'] = 'Github';
} }
$e_name = true; $this->repository = $repository;
if (!isset($views[$this->view])) {
reset($views);
$this->view = key($views);
}
$nav = new AphrontSideNavView();
foreach ($views as $view => $name) {
$nav->addNavItem(
phutil_render_tag(
'a',
array(
'class' => ($view == $this->view
? 'aphront-side-nav-selected'
: null),
'href' => '/repository/edit/'.$repository->getID().'/'.$view.'/',
),
phutil_escape_html($name)));
}
$this->sideNav = $nav;
switch ($this->view) {
case 'basic':
return $this->processBasicRequest();
case 'tracking':
return $this->processTrackingRequest();
case 'github':
return $this->processGithubRequest();
default:
throw new Exception("Unknown view.");
}
}
protected function processBasicRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$repository = $this->repository;
$repository_id = $repository->getID();
$type_map = array( $type_map = array(
'svn' => 'Subversion', 'svn' => 'Subversion',
'git' => 'Git', 'git' => 'Git',
); );
$errors = array(); $errors = array();
$e_name = true;
if ($request->isFormPost()) { if ($request->isFormPost()) {
$repository->setName($request->getStr('name')); $repository->setName($request->getStr('name'));
@ -64,9 +117,8 @@ class PhabricatorRepositoryEditController extends PhabricatorController {
if (!$errors) { if (!$errors) {
$repository->save(); $repository->save();
return id(new AphrontRedirectResponse()) return id(new AphrontRedirectResponse())
->setURI('/repository/'); ->setURI('/repository/edit/'.$repository_id.'/basic/?saved=true');
} }
} }
$error_view = null; $error_view = null;
@ -74,9 +126,14 @@ class PhabricatorRepositoryEditController extends PhabricatorController {
$error_view = new AphrontErrorView(); $error_view = new AphrontErrorView();
$error_view->setErrors($errors); $error_view->setErrors($errors);
$error_view->setTitle('Form Errors'); $error_view->setTitle('Form Errors');
} else if ($request->getStr('saved')) {
$error_view = new AphrontErrorView();
$error_view->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$error_view->setTitle('Changes Saved');
$error_view->appendChild(
'Repository changes were saved.');
} }
$form = new AphrontFormView(); $form = new AphrontFormView();
$form $form
->setUser($user) ->setUser($user)
@ -97,29 +154,187 @@ class PhabricatorRepositoryEditController extends PhabricatorController {
id(new AphrontFormStaticControl()) id(new AphrontFormStaticControl())
->setLabel('Type') ->setLabel('Type')
->setName('type') ->setName('type')
->setValue($repository->getVersionControlSystem())); ->setValue($repository->getVersionControlSystem()))
->appendChild(
id(new AphrontFormStaticControl())
$form ->setLabel('PHID')
->setName('phid')
->setValue($repository->getPHID()))
->appendChild( ->appendChild(
id(new AphrontFormSubmitControl()) id(new AphrontFormSubmitControl())
->setValue('Save') ->setValue('Save'));
->addCancelButton('/repository/'));
$panel = new AphrontPanelView(); $panel = new AphrontPanelView();
$panel->setHeader('Edit Repository'); $panel->setHeader('Edit Repository');
$panel->appendChild($form); $panel->appendChild($form);
$panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->setWidth(AphrontPanelView::WIDTH_FORM);
$phid = $repository->getID();
$nav = $this->sideNav;
$nav->appendChild($error_view);
$nav->appendChild($panel);
return $this->buildStandardPageResponse(
$nav,
array(
'title' => 'Edit Repository',
));
}
private function processTrackingRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$repository = $this->repository;
$repository_id = $repository->getID();
$errors = array();
$e_uri = null;
$e_path = null;
if ($request->isFormPost()) {
$tracking = ($request->getStr('tracking') == 'enabled' ? true : false);
$repository->setDetail('tracking-enabled', $tracking);
$repository->setDetail('remote-uri', $request->getStr('uri'));
$repository->setDetail('local-path', $request->getStr('path'));
$repository->setDetail(
'pull-frequency',
max(1, $request->getInt('frequency')));
if ($tracking) {
if (!$repository->getDetail('remote-uri')) {
$e_uri = 'Required';
$errors[] = "Repository URI is required.";
}
if (!$repository->getDetail('local-path')) {
$e_path = 'Required';
$errors[] = "Local path is required.";
}
}
if (!$errors) {
$repository->save();
return id(new AphrontRedirectResponse())
->setURI('/repository/edit/'.$repository_id.'/tracking/?saved=true');
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setErrors($errors);
$error_view->setTitle('Form Errors');
} else if ($request->getStr('saved')) {
$error_view = new AphrontErrorView();
$error_view->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$error_view->setTitle('Changes Saved');
$error_view->appendChild(
'Tracking changes were saved. You may need to restart the daemon '.
'before changes will take effect.');
}
$uri_caption = null;
$path_caption = null;
switch ($repository->getVersionControlSystem()) {
case 'git':
$uri_caption =
'The user the tracking daemon runs as must have permission to '.
'<tt>git clone</tt> from this URI.';
$path_caption =
'Directory where the daemon should look to find a copy of the '.
'repository (or create one if it does not yet exist). The daemon '.
'will regularly pull remote changes into this working copy.';
break;
case 'svn':
$uri_caption =
'The user the tracking daemon runs as must have permission to '.
'<tt>svn log</tt> from this URI.';
break;
}
$form = new AphrontFormView();
$form
->setUser($user)
->setAction('/repository/edit/'.$repository->getID().'/tracking/')
->appendChild(
'<p class="aphront-form-instructions">Phabricator can track '.
'repositories, importing commits as they happen and notifying '.
'Differential, Diffusion, Herald, and other services. To enable '.
'tracking for a repository, configure it here and then start (or '.
'restart) the PhabricatorRepositoryTrackingDaemon.</p>')
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Repository')
->setValue($repository->getName()))
->appendChild(
id(new AphrontFormSelectControl())
->setName('tracking')
->setLabel('Tracking')
->setOptions(array(
'disabled' => 'Disabled',
'enabled' => 'Enabled',
))
->setValue(
$repository->getDetail('tracking-enabled')
? 'enabled'
: 'disabled'))
->appendChild(
id(new AphrontFormTextControl())
->setName('uri')
->setLabel('URI')
->setValue($repository->getDetail('remote-uri'))
->setError($e_uri)
->setCaption($uri_caption))
->appendChild(
id(new AphrontFormTextControl())
->setName('path')
->setLabel('Local Path')
->setValue($repository->getDetail('local-path'))
->setError($e_path)
->setCaption($path_caption))
->appendChild(
id(new AphrontFormTextControl())
->setName('frequency')
->setLabel('Pull Frequency')
->setValue($repository->getDetail('pull-frequency', 15))
->setCaption(
'Number of seconds daemon should sleep between requests. Larger '.
'numbers reduce load but also decrease responsiveness.'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Save'));
$panel = new AphrontPanelView();
$panel->setHeader('Repository Tracking');
$panel->appendChild($form);
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$nav = $this->sideNav;
$nav->appendChild($error_view);
$nav->appendChild($panel);
return $this->buildStandardPageResponse(
$nav,
array(
'title' => 'Edit Repository Tracking',
));
}
private function processGithubRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$repository = $this->repository;
$repository_id = $repository->getID();
$token = $repository->getDetail('github-token'); $token = $repository->getDetail('github-token');
$path = '/github-post-receive/'.$phid.'/'.$token.'/'; $path = '/github-post-receive/'.$repository_id.'/'.$token.'/';
$post_uri = PhabricatorEnv::getURI($path); $post_uri = PhabricatorEnv::getURI($path);
$gitform = new AphrontFormView(); $gitform = new AphrontFormView();
$gitform $gitform
->setUser($user) ->setUser($user)
->setAction('/repository/edit/'.$repository->getID().'/')
->appendChild( ->appendChild(
'<p class="aphront-form-instructions">You can configure GitHub to '. '<p class="aphront-form-instructions">You can configure GitHub to '.
'notify Phabricator after changes are pushed. Log into GitHub, go '. 'notify Phabricator after changes are pushed. Log into GitHub, go '.
@ -178,15 +393,13 @@ class PhabricatorRepositoryEditController extends PhabricatorController {
$github->appendChild($gitform); $github->appendChild($gitform);
$github->setWidth(AphrontPanelView::WIDTH_FORM); $github->setWidth(AphrontPanelView::WIDTH_FORM);
$nav = $this->sideNav;
$nav->appendChild($github);
return $this->buildStandardPageResponse( return $this->buildStandardPageResponse(
$nav,
array( array(
$error_view, 'title' => 'Repository Github Integration',
$panel,
$github,
),
array(
'title' => 'Edit Repository',
)); ));
} }
} }

View file

@ -15,10 +15,10 @@ phutil_require_module('phabricator', 'applications/repository/storage/repository
phutil_require_module('phabricator', 'infrastructure/env'); phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'view/control/table'); phutil_require_module('phabricator', 'view/control/table');
phutil_require_module('phabricator', 'view/form/base'); phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/static');
phutil_require_module('phabricator', 'view/form/control/submit'); phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/error'); phutil_require_module('phabricator', 'view/form/error');
phutil_require_module('phabricator', 'view/layout/panel'); phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phabricator', 'view/layout/sidenav');
phutil_require_module('phabricator', 'view/utils'); phutil_require_module('phabricator', 'view/utils');
phutil_require_module('phutil', 'filesystem'); phutil_require_module('phutil', 'filesystem');

View file

@ -0,0 +1,38 @@
<?php
/*
* Copyright 2011 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.
*/
abstract class PhabricatorRepositoryDaemon extends PhabricatorDaemon {
protected function loadRepository() {
$argv = $this->getArgv();
if (count($argv) !== 1) {
throw new Exception("No repository PHID provided!");
}
$repository = id(new PhabricatorRepository())->loadOneWhere(
'phid = %s',
$argv[0]);
if (!$repository) {
throw new Exception("No such repository exists!");
}
return $repository;
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/repository/storage/repository');
phutil_require_module('phabricator', 'infrastructure/daemon/base');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorRepositoryDaemon.php');

View file

@ -0,0 +1,45 @@
<?php
/*
* Copyright 2011 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.
*/
abstract class PhabricatorRepositoryCommitDiscoveryDaemon
extends PhabricatorRepositoryDaemon {
private $repository;
final protected function getRepository() {
return $this->repository;
}
final public function run() {
$this->repository = $this->loadRepository();
$sleep = 15;
while (true) {
$found = $this->discoverCommits();
if ($found) {
$sleep = 15;
} else {
$sleep = min($sleep + 15, 60 * 15);
}
$this->sleep($sleep);
}
}
abstract protected function discoverCommits();
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/repository/daemon/base');
phutil_require_source('PhabricatorRepositoryCommitDiscoveryDaemon.php');

View file

@ -0,0 +1,138 @@
<?php
/*
* Copyright 2011 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.
*/
class PhabricatorRepositoryGitCommitDiscoveryDaemon
extends PhabricatorRepositoryCommitDiscoveryDaemon {
private $lastCommit;
private $commitCache = array();
protected function discoverCommits() {
// NOTE: PhabricatorRepositoryGitPullDaemon does the actual pulls, this
// just parses HEAD.
$repository = $this->getRepository();
// TODO: this should be a constant somewhere
if ($repository->getVersionControlSystem() != 'git') {
throw new Exception("Repository is not a git repository.");
}
$repository_phid = $repository->getPHID();
$repo_base = $repository->getDetail('local-path');
list($commit) = execx(
'(cd %s && git log -n1 --pretty="%%H")',
$repo_base);
$commit = trim($commit);
if ($commit === $this->lastCommit ||
$this->isKnownCommit($commit)) {
return false;
}
$this->lastCommit = $commit;
$this->discoverCommit($commit);
return true;
}
private function discoverCommit($commit) {
$discover = array();
$insert = array();
$repository = $this->getRepository();
$repo_base = $repository->getDetail('local-path');
$discover[] = $commit;
$insert[] = $commit;
while (true) {
$target = array_pop($discover);
list($parents) = execx(
'(cd %s && git log -n1 --pretty="%%P" %s)',
$repo_base,
$target);
$parents = array_filter(explode(' ', trim($parents)));
foreach ($parents as $parent) {
if (!$this->isKnownCommit($parent)) {
echo "{$target} has parent {$parent}\n";
$discover[] = $parent;
$insert[] = $parent;
}
}
if (empty($discover)) {
break;
}
}
while (true) {
$target = array_pop($insert);
list($epoch) = execx(
'(cd %s && git log -n1 --pretty="%%at" %s)',
$repo_base,
$target);
$epoch = trim($epoch);
$commit = new PhabricatorRepositoryCommit();
$commit->setRepositoryPHID($this->getRepository()->getPHID());
$commit->setCommitIdentifier($target);
$commit->setEpoch($epoch);
try {
$commit->save();
$event = new PhabricatorTimelineEvent(
'cmit',
array(
'id' => $commit->getID(),
));
$event->recordEvent();
} catch (AphrontQueryDuplicateKeyException $ex) {
// Ignore. This can happen because we discover the same new commit
// more than once when looking at history, or because of races or
// data inconsistency or cosmic radiation; in any case, we're still
// in a good state if we ignore the failure.
}
if (empty($insert)) {
break;
}
}
}
private function isKnownCommit($target) {
if (isset($this->commitCache[$target])) {
return true;
}
$commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
'repositoryPHID = %s AND commitIdentifier = %s',
$this->getRepository()->getPHID(),
$target);
if (!$commit) {
return false;
}
$this->commitCache[$target] = true;
if (count($this->commitCache) > 16) {
array_shift($this->commitCache);
}
return true;
}
}

View file

@ -0,0 +1,17 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/repository/daemon/commitdiscovery/base');
phutil_require_module('phabricator', 'applications/repository/storage/commit');
phutil_require_module('phabricator', 'applications/timeline/storage/event');
phutil_require_module('phutil', 'future/exec');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorRepositoryGitCommitDiscoveryDaemon.php');

View file

@ -0,0 +1,22 @@
<?php
/*
* Copyright 2011 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.
*/
class PhabricatorRepositoryCommitParserDaemon
extends PhabricatorRepositoryDaemon {
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/repository/daemon/base');
phutil_require_source('PhabricatorRepositoryCommitParserDaemon.php');

View file

@ -0,0 +1,55 @@
<?php
/*
* Copyright 2011 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.
*/
class PhabricatorRepositoryGitPullDaemon
extends PhabricatorRepositoryDaemon {
public function run() {
$repository = $this->loadRepository();
if ($repository->getVersionControlSystem() != 'git') {
throw new Exception("Not a git repository!");
}
$tracked = $repository->getDetail('tracking-enabled');
if (!$tracked) {
throw new Exception("Tracking is not enabled for this repository.");
}
$local_path = $repository->getDetail('local-path');
$remote_uri = $repository->getDetail('remote-uri');
if (!$local_path) {
throw new Exception("No local path is available for this repository.");
}
while (true) {
if (!Filesystem::pathExists($local_path)) {
if (!$remote_uri) {
throw new Exception("No remote URI is available.");
}
execx('mkdir -p %s', dirname($local_path));
execx('git clone %s %s', $remote_uri, rtrim($local_path, '/'));
} else {
execx('(cd %s && git pull)', $local_path);
}
$this->sleep($repository->getDetail('pull-frequency', 15));
}
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/repository/daemon/base');
phutil_require_module('phutil', 'filesystem');
phutil_require_module('phutil', 'future/exec');
phutil_require_source('PhabricatorRepositoryGitPullDaemon.php');

View file

@ -0,0 +1,38 @@
<?php
/*
* Copyright 2011 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.
*/
class PhabricatorRepositoryCommit extends PhabricatorRepositoryDAO {
protected $repositoryPHID;
protected $phid;
protected $commitIdentifier;
protected $epoch;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_CMIT);
}
}

View file

@ -0,0 +1,14 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/phid/constants');
phutil_require_module('phabricator', 'applications/phid/storage/phid');
phutil_require_module('phabricator', 'applications/repository/storage/base');
phutil_require_source('PhabricatorRepositoryCommit.php');

View file

@ -35,7 +35,8 @@ class PhabricatorRepository extends PhabricatorRepositoryDAO {
} }
public function generatePHID() { public function generatePHID() {
return PhabricatorPHID::generateNewPHID('REPO'); return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_REPO);
} }
public function getDetail($key, $default = null) { public function getDetail($key, $default = null) {

View file

@ -6,6 +6,7 @@
phutil_require_module('phabricator', 'applications/phid/constants');
phutil_require_module('phabricator', 'applications/phid/storage/phid'); phutil_require_module('phabricator', 'applications/phid/storage/phid');
phutil_require_module('phabricator', 'applications/repository/storage/base'); phutil_require_module('phabricator', 'applications/repository/storage/base');

View file

@ -0,0 +1,115 @@
<?php
/*
* Copyright 2011 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.
*/
class PhabricatorTimelineIterator implements Iterator {
protected $cursorName;
protected $eventTypes;
protected $cursor;
protected $index = -1;
protected $events = array();
const LOAD_CHUNK_SIZE = 128;
public function __construct($cursor_name, array $event_types) {
$this->cursorName = $cursor_name;
$this->eventTypes = $event_types;
}
protected function loadEvents() {
if (!$this->cursor) {
$this->cursor = id(new PhabricatorTimelineCursor())->loadOneWhere(
'name = %s',
$this->cursorName);
if (!$this->cursor) {
$cursor = new PhabricatorTimelineCursor();
$cursor->setName($this->cursorName);
$cursor->setPosition(0);
$cursor->save();
$this->cursor = $cursor;
}
}
$event = new PhabricatorTimelineEvent();
$event_data = new PhabricatorTimelineEventData();
$raw_data = queryfx_all(
$event->establishConnection('r'),
'SELECT event.*, event_data.eventData eventData
FROM %T event WHERE event.id > %d AND event.type in (%Ls)
LEFT JOIN %T event_data ON event_data.eventID = event.id
ORDER BY event.id ASC LIMIT %d',
$event->getTableName(),
$this->cursor->getPosition(),
$this->eventTypes,
$event_data->getTableName(),
self::LOAD_CHUNK_SIZE);
$events = $event->loadAllFromArray($raw_data);
$events = mpull($events, null, 'getID');
$raw_data = ipull($raw_data, 'eventData', 'id');
foreach ($raw_data as $id => $data) {
if ($data) {
$decoded = json_decode($data, true);
$events[$id]->setData($decoded);
}
}
$this->events = $events;
if ($this->events) {
$this->events = array_values($this->events);
$this->index = 0;
} else {
$this->cursor = null;
}
}
public function current() {
return $this->events[$this->index];
}
public function key() {
return $this->events[$this->index]->getID();
}
public function next() {
if ($this->valid()) {
$this->cursor->setPosition($this->key());
$this->cursor->save();
}
$this->index++;
if (!$this->valid()) {
$this->loadEvents();
}
}
public function valid() {
return isset($this->events[$this->index]);
}
public function rewind() {
if (!$this->valid()) {
$this->loadEvents();
}
}
}

View file

@ -0,0 +1,17 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/timeline/storage/cursor');
phutil_require_module('phabricator', 'applications/timeline/storage/event');
phutil_require_module('phabricator', 'applications/timeline/storage/eventdata');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorTimelineIterator.php');

View file

@ -0,0 +1,25 @@
<?php
/*
* Copyright 2011 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.
*/
class PhabricatorTimelineDAO extends PhabricatorLiskDAO {
public function getApplicationName() {
return 'timeline';
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/base/storage/lisk');
phutil_require_source('PhabricatorTimelineDAO.php');

View file

@ -0,0 +1,31 @@
<?php
/*
* Copyright 2011 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.
*/
class PhabricatorTimelineCursor extends PhabricatorTimelineDAO {
protected $name;
protected $position;
public function getConfiguration() {
return array(
self::CONFIG_IDS => self::IDS_MANUAL,
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/timeline/storage/base');
phutil_require_source('PhabricatorTimelineCursor.php');

View file

@ -0,0 +1,65 @@
<?php
/*
* Copyright 2011 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.
*/
class PhabricatorTimelineEvent extends PhabricatorTimelineDAO {
protected $type;
private $data;
public function __construct($type, $data = null) {
parent::__construct();
if (strlen($type) !== 4) {
throw new Exception("Event types must be exactly 4 characters long.");
}
$this->type = $type;
$this->data = $data;
}
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public function recordEvent() {
if ($this->getID()) {
throw new Exception("Event has already been recorded!");
}
$this->save();
if ($this->data !== null) {
$data = new PhabricatorTimelineEventData();
$data->setEventID($this->getID());
$data->setEventData($this->data);
$data->save();
}
}
public function setData($data) {
$this->data = $data;
return $this;
}
public function getData() {
return $this->data;
}
}

View file

@ -0,0 +1,13 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/timeline/storage/base');
phutil_require_module('phabricator', 'applications/timeline/storage/eventdata');
phutil_require_source('PhabricatorTimelineEvent.php');

View file

@ -0,0 +1,33 @@
<?php
/*
* Copyright 2011 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.
*/
class PhabricatorTimelineEventData extends PhabricatorTimelineDAO {
protected $eventID;
protected $eventData;
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'eventData' => self::SERIALIZATION_JSON,
),
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/timeline/storage/base');
phutil_require_source('PhabricatorTimelineEventData.php');

View file

@ -0,0 +1,33 @@
<?php
/*
* Copyright 2011 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.
*/
abstract class PhabricatorDaemon extends PhutilDaemon {
protected function willRun() {
parent::willRun();
// Both of these store unbounded amounts of log data; make them discard it
// instead so that daemons do not require unbounded amounts of memory.
DarkConsoleServicesPluginAPI::enableDiscardMode();
DarkConsoleErrorLogPluginAPI::enableDiscardMode();
$phabricator = phutil_get_library_root('phabricator');
$root = dirname($phabricator);
require_once $root.'/scripts/__init_env__.php';
}
}

View file

@ -0,0 +1,16 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/console/plugin/errorlog/api');
phutil_require_module('phabricator', 'aphront/console/plugin/services/api');
phutil_require_module('phutil', 'daemon/base');
phutil_require_module('phutil', 'moduleutils');
phutil_require_source('PhabricatorDaemon.php');

View file

@ -119,6 +119,16 @@ class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
$start = microtime(true); $start = microtime(true);
if (!function_exists('mysql_connect')) {
// We have to '@' the actual call since it can spew all sorts of silly
// noise, but it will also silence fatals caused by not having MySQL
// installed, which has bitten me on three separate occasions. Make sure
// such failures are explicit and loud.
throw new Exception(
"About to call mysql_connect(), but the PHP MySQL extension is not ".
"available!");
}
$conn = @mysql_connect( $conn = @mysql_connect(
$host, $host,
$user, $user,