diff --git a/resources/sql/patches/004.daemonrepos.sql b/resources/sql/patches/004.daemonrepos.sql new file mode 100644 index 0000000000..9108c92cb8 --- /dev/null +++ b/resources/sql/patches/004.daemonrepos.sql @@ -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 +); \ No newline at end of file diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 41383b0a97..73762557b8 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,536 +1,560 @@ array( 'Aphront400Response' => 'aphront/response/400', 'Aphront404Response' => 'aphront/response/404', 'AphrontAjaxResponse' => 'aphront/response/ajax', 'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration', 'AphrontController' => 'aphront/controller', 'AphrontDatabaseConnection' => 'storage/connection/base', 'AphrontDefaultApplicationConfiguration' => 'aphront/default/configuration', 'AphrontDefaultApplicationController' => 'aphront/default/controller', 'AphrontDialogResponse' => 'aphront/response/dialog', 'AphrontDialogView' => 'view/dialog', 'AphrontErrorView' => 'view/form/error', 'AphrontException' => 'aphront/exception/base', 'AphrontFileResponse' => 'aphront/response/file', 'AphrontFormCheckboxControl' => 'view/form/control/checkbox', 'AphrontFormControl' => 'view/form/control/base', 'AphrontFormDividerControl' => 'view/form/control/divider', 'AphrontFormFileControl' => 'view/form/control/file', 'AphrontFormMarkupControl' => 'view/form/control/markup', 'AphrontFormPasswordControl' => 'view/form/control/password', 'AphrontFormRecaptchaControl' => 'view/form/control/recaptcha', 'AphrontFormSelectControl' => 'view/form/control/select', 'AphrontFormStaticControl' => 'view/form/control/static', 'AphrontFormSubmitControl' => 'view/form/control/submit', 'AphrontFormTextAreaControl' => 'view/form/control/textarea', 'AphrontFormTextControl' => 'view/form/control/text', 'AphrontFormTokenizerControl' => 'view/form/control/tokenizer', 'AphrontFormView' => 'view/form/base', 'AphrontHeadsupActionListView' => 'view/layout/headsup/actionlist', 'AphrontHeadsupActionView' => 'view/layout/headsup/action', 'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql', 'AphrontNullView' => 'view/null', 'AphrontPageView' => 'view/page/base', 'AphrontPanelView' => 'view/layout/panel', 'AphrontQueryConnectionException' => 'storage/exception/connection', 'AphrontQueryConnectionLostException' => 'storage/exception/connectionlost', 'AphrontQueryCountException' => 'storage/exception/count', 'AphrontQueryDuplicateKeyException' => 'storage/exception/duplicatekey', 'AphrontQueryException' => 'storage/exception/base', 'AphrontQueryObjectMissingException' => 'storage/exception/objectmissing', 'AphrontQueryParameterException' => 'storage/exception/parameter', 'AphrontQueryRecoverableException' => 'storage/exception/recoverable', 'AphrontRedirectException' => 'aphront/exception/redirect', 'AphrontRedirectResponse' => 'aphront/response/redirect', 'AphrontRequest' => 'aphront/request', 'AphrontRequestFailureView' => 'view/page/failure', 'AphrontResponse' => 'aphront/response/base', 'AphrontSideNavView' => 'view/layout/sidenav', 'AphrontTableView' => 'view/control/table', 'AphrontURIMapper' => 'aphront/mapper', 'AphrontView' => 'view/base', 'AphrontWebpageResponse' => 'aphront/response/webpage', 'CelerityAPI' => 'infrastructure/celerity/api', 'CelerityResourceController' => 'infrastructure/celerity/controller', 'CelerityResourceMap' => 'infrastructure/celerity/map', 'CelerityStaticResourceResponse' => 'infrastructure/celerity/response', 'ConduitAPIMethod' => 'applications/conduit/method/base', 'ConduitAPIRequest' => 'applications/conduit/protocol/request', 'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect', 'ConduitAPI_conduit_ping_Method' => 'applications/conduit/method/conduit/ping', 'ConduitAPI_differential_creatediff_Method' => 'applications/conduit/method/differential/creatediff', 'ConduitAPI_differential_createrevision_Method' => 'applications/conduit/method/differential/createrevision', 'ConduitAPI_differential_find_Method' => 'applications/conduit/method/differential/find', 'ConduitAPI_differential_getcommitmessage_Method' => 'applications/conduit/method/differential/getcommitmessage', 'ConduitAPI_differential_getcommitpaths_Method' => 'applications/conduit/method/differential/getcommitpaths', 'ConduitAPI_differential_getdiff_Method' => 'applications/conduit/method/differential/getdiff', 'ConduitAPI_differential_markcommitted_Method' => 'applications/conduit/method/differential/markcommitted', 'ConduitAPI_differential_parsecommitmessage_Method' => 'applications/conduit/method/differential/parsecommitmessage', 'ConduitAPI_differential_setdiffproperty_Method' => 'applications/conduit/method/differential/setdiffproperty', 'ConduitAPI_differential_updaterevision_Method' => 'applications/conduit/method/differential/updaterevision', 'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload', 'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find', 'ConduitException' => 'applications/conduit/protocol/exception', 'DarkConsole' => 'aphront/console/api', 'DarkConsoleConfigPlugin' => 'aphront/console/plugin/config', 'DarkConsoleController' => 'aphront/console/controller', 'DarkConsoleCore' => 'aphront/console/core', 'DarkConsoleErrorLogPlugin' => 'aphront/console/plugin/errorlog', 'DarkConsoleErrorLogPluginAPI' => 'aphront/console/plugin/errorlog/api', 'DarkConsolePlugin' => 'aphront/console/plugin/base', 'DarkConsoleRequestPlugin' => 'aphront/console/plugin/request', 'DarkConsoleServicesPlugin' => 'aphront/console/plugin/services', 'DarkConsoleServicesPluginAPI' => 'aphront/console/plugin/services/api', 'DarkConsoleXHProfPlugin' => 'aphront/console/plugin/xhprof', 'DarkConsoleXHProfPluginAPI' => 'aphront/console/plugin/xhprof/api', 'DifferentialAction' => 'applications/differential/constants/action', 'DifferentialAddCommentView' => 'applications/differential/view/addcomment', 'DifferentialAttachController' => 'applications/differential/controller/attach', 'DifferentialCCWelcomeMail' => 'applications/differential/mail/ccwelcome', 'DifferentialChangeType' => 'applications/differential/constants/changetype', 'DifferentialChangeset' => 'applications/differential/storage/changeset', 'DifferentialChangesetDetailView' => 'applications/differential/view/changesetdetailview', 'DifferentialChangesetListView' => 'applications/differential/view/changesetlistview', 'DifferentialChangesetParser' => 'applications/differential/parser/changeset', 'DifferentialChangesetViewController' => 'applications/differential/controller/changesetview', 'DifferentialComment' => 'applications/differential/storage/comment', 'DifferentialCommentEditor' => 'applications/differential/editor/comment', 'DifferentialCommentMail' => 'applications/differential/mail/comment', 'DifferentialCommentPreviewController' => 'applications/differential/controller/commentpreview', 'DifferentialCommentSaveController' => 'applications/differential/controller/commentsave', 'DifferentialCommitMessage' => 'applications/differential/parser/commitmessage', 'DifferentialCommitMessageData' => 'applications/differential/data/commitmessage', 'DifferentialCommitMessageParserException' => 'applications/differential/parser/commitmessage/exception', 'DifferentialController' => 'applications/differential/controller/base', 'DifferentialDAO' => 'applications/differential/storage/base', 'DifferentialDiff' => 'applications/differential/storage/diff', 'DifferentialDiffContentMail' => 'applications/differential/mail/diffcontent', 'DifferentialDiffCreateController' => 'applications/differential/controller/diffcreate', 'DifferentialDiffProperty' => 'applications/differential/storage/diffproperty', 'DifferentialDiffTableOfContentsView' => 'applications/differential/view/difftableofcontents', 'DifferentialDiffViewController' => 'applications/differential/controller/diffview', 'DifferentialHunk' => 'applications/differential/storage/hunk', 'DifferentialInlineComment' => 'applications/differential/storage/inlinecomment', 'DifferentialInlineCommentEditController' => 'applications/differential/controller/inlinecommentedit', 'DifferentialInlineCommentPreviewController' => 'applications/differential/controller/inlinecommentpreview', 'DifferentialInlineCommentView' => 'applications/differential/view/inlinecomment', 'DifferentialLintStatus' => 'applications/differential/constants/lintstatus', 'DifferentialMail' => 'applications/differential/mail/base', 'DifferentialMarkupEngineFactory' => 'applications/differential/parser/markup', 'DifferentialNewDiffMail' => 'applications/differential/mail/newdiff', 'DifferentialReviewRequestMail' => 'applications/differential/mail/reviewrequest', 'DifferentialRevision' => 'applications/differential/storage/revision', 'DifferentialRevisionCommentListView' => 'applications/differential/view/revisioncommentlist', 'DifferentialRevisionCommentView' => 'applications/differential/view/revisioncomment', 'DifferentialRevisionControlSystem' => 'applications/differential/constants/revisioncontrolsystem', 'DifferentialRevisionDetailView' => 'applications/differential/view/revisiondetail', 'DifferentialRevisionEditController' => 'applications/differential/controller/revisionedit', 'DifferentialRevisionEditor' => 'applications/differential/editor/revision', 'DifferentialRevisionListController' => 'applications/differential/controller/revisionlist', 'DifferentialRevisionListData' => 'applications/differential/data/revisionlist', 'DifferentialRevisionStatus' => 'applications/differential/constants/revisionstatus', 'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/revisionupdatehistory', 'DifferentialRevisionViewController' => 'applications/differential/controller/revisionview', 'DifferentialSubscribeController' => 'applications/differential/controller/subscribe', 'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus', 'Javelin' => 'infrastructure/javelin/api', 'LiskDAO' => 'storage/lisk/dao', 'ManiphestController' => 'applications/maniphest/controller/base', 'ManiphestDAO' => 'applications/maniphest/storage/base', 'ManiphestTask' => 'applications/maniphest/storage/task', 'ManiphestTaskDetailController' => 'applications/maniphest/controller/taskdetail', 'ManiphestTaskEditController' => 'applications/maniphest/controller/taskedit', 'ManiphestTaskListController' => 'applications/maniphest/controller/tasklist', 'ManiphestTaskListView' => 'applications/maniphest/view/tasklist', 'ManiphestTaskPriority' => 'applications/maniphest/constants/priority', 'ManiphestTaskSelectorSearchController' => 'applications/maniphest/controller/taskselectorsearch', 'ManiphestTaskStatus' => 'applications/maniphest/constants/status', 'ManiphestTaskSummaryView' => 'applications/maniphest/view/tasksummary', 'ManiphestTransaction' => 'applications/maniphest/storage/transaction', 'ManiphestTransactionDetailView' => 'applications/maniphest/view/transactiondetail', 'ManiphestTransactionEditor' => 'applications/maniphest/editor/transaction', 'ManiphestTransactionListView' => 'applications/maniphest/view/transactionlist', 'ManiphestTransactionSaveController' => 'applications/maniphest/controller/transactionsave', 'ManiphestTransactionType' => 'applications/maniphest/constants/transactiontype', 'Phabricator404Controller' => 'applications/base/controller/404', 'PhabricatorAuthController' => 'applications/auth/controller/base', 'PhabricatorConduitAPIController' => 'applications/conduit/controller/api', 'PhabricatorConduitConnectionLog' => 'applications/conduit/storage/connectionlog', 'PhabricatorConduitConsoleController' => 'applications/conduit/controller/console', 'PhabricatorConduitController' => 'applications/conduit/controller/base', 'PhabricatorConduitDAO' => 'applications/conduit/storage/base', 'PhabricatorConduitLogController' => 'applications/conduit/controller/log', 'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/methodcalllog', 'PhabricatorController' => 'applications/base/controller/base', + 'PhabricatorDaemon' => 'infrastructure/daemon/base', 'PhabricatorDirectoryCategory' => 'applications/directory/storage/category', 'PhabricatorDirectoryCategoryDeleteController' => 'applications/directory/controller/categorydelete', 'PhabricatorDirectoryCategoryEditController' => 'applications/directory/controller/categoryedit', 'PhabricatorDirectoryCategoryListController' => 'applications/directory/controller/categorylist', 'PhabricatorDirectoryController' => 'applications/directory/controller/base', 'PhabricatorDirectoryDAO' => 'applications/directory/storage/base', 'PhabricatorDirectoryItem' => 'applications/directory/storage/item', 'PhabricatorDirectoryItemDeleteController' => 'applications/directory/controller/itemdelete', 'PhabricatorDirectoryItemEditController' => 'applications/directory/controller/itemedit', 'PhabricatorDirectoryItemListController' => 'applications/directory/controller/itemlist', 'PhabricatorDirectoryMainController' => 'applications/directory/controller/main', 'PhabricatorDraft' => 'applications/draft/storage/draft', 'PhabricatorDraftDAO' => 'applications/draft/storage/base', 'PhabricatorEmailLoginController' => 'applications/auth/controller/email', 'PhabricatorEmailTokenController' => 'applications/auth/controller/emailtoken', 'PhabricatorEnv' => 'infrastructure/env', 'PhabricatorFile' => 'applications/files/storage/file', 'PhabricatorFileController' => 'applications/files/controller/base', 'PhabricatorFileDAO' => 'applications/files/storage/base', 'PhabricatorFileListController' => 'applications/files/controller/list', 'PhabricatorFileStorageBlob' => 'applications/files/storage/storageblob', 'PhabricatorFileURI' => 'applications/files/uri', 'PhabricatorFileUploadController' => 'applications/files/controller/upload', 'PhabricatorFileViewController' => 'applications/files/controller/view', 'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/selector', 'PhabricatorLiskDAO' => 'applications/base/storage/lisk', 'PhabricatorLoginController' => 'applications/auth/controller/login', 'PhabricatorLogoutController' => 'applications/auth/controller/logout', 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/base', 'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/amazonses', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/phpmailerlite', 'PhabricatorMetaMTAController' => 'applications/metamta/controller/base', 'PhabricatorMetaMTADAO' => 'applications/metamta/storage/base', 'PhabricatorMetaMTADaemon' => 'applications/metamta/daemon/mta', 'PhabricatorMetaMTAListController' => 'applications/metamta/controller/list', 'PhabricatorMetaMTAMail' => 'applications/metamta/storage/mail', 'PhabricatorMetaMTAMailingList' => 'applications/metamta/storage/mailinglist', 'PhabricatorMetaMTAMailingListEditController' => 'applications/metamta/controller/mailinglistedit', 'PhabricatorMetaMTAMailingListsController' => 'applications/metamta/controller/mailinglists', 'PhabricatorMetaMTASendController' => 'applications/metamta/controller/send', 'PhabricatorMetaMTAViewController' => 'applications/metamta/controller/view', 'PhabricatorOAuthDefaultRegistrationController' => 'applications/auth/controller/oauthregistration/default', 'PhabricatorOAuthDiagnosticsController' => 'applications/auth/controller/oauthdiagnostics', 'PhabricatorOAuthFailureView' => 'applications/auth/view/oauthfailure', 'PhabricatorOAuthLoginController' => 'applications/auth/controller/oauth', 'PhabricatorOAuthProvider' => 'applications/auth/oauth/provider/base', 'PhabricatorOAuthProviderFacebook' => 'applications/auth/oauth/provider/facebook', 'PhabricatorOAuthProviderGithub' => 'applications/auth/oauth/provider/github', 'PhabricatorOAuthRegistrationController' => 'applications/auth/controller/oauthregistration/base', 'PhabricatorOAuthUnlinkController' => 'applications/auth/controller/unlink', 'PhabricatorObjectHandle' => 'applications/phid/handle', 'PhabricatorObjectHandleData' => 'applications/phid/handle/data', 'PhabricatorObjectSelectorDialog' => 'view/control/objectselector', 'PhabricatorPHID' => 'applications/phid/storage/phid', 'PhabricatorPHIDAllocateController' => 'applications/phid/controller/allocate', 'PhabricatorPHIDConstants' => 'applications/phid/constants', 'PhabricatorPHIDController' => 'applications/phid/controller/base', 'PhabricatorPHIDDAO' => 'applications/phid/storage/base', 'PhabricatorPHIDListController' => 'applications/phid/controller/list', 'PhabricatorPHIDLookupController' => 'applications/phid/controller/lookup', 'PhabricatorPHIDType' => 'applications/phid/storage/type', 'PhabricatorPHIDTypeEditController' => 'applications/phid/controller/typeedit', 'PhabricatorPHIDTypeListController' => 'applications/phid/controller/typelist', 'PhabricatorPeopleController' => 'applications/people/controller/base', 'PhabricatorPeopleEditController' => 'applications/people/controller/edit', 'PhabricatorPeopleListController' => 'applications/people/controller/list', 'PhabricatorPeopleProfileController' => 'applications/people/controller/profile', 'PhabricatorPeopleProfileEditController' => 'applications/people/controller/profileedit', 'PhabricatorProject' => 'applications/project/storage/project', 'PhabricatorProjectAffiliation' => 'applications/project/storage/affiliation', 'PhabricatorProjectAffiliationEditController' => 'applications/project/controller/editaffiliation', 'PhabricatorProjectController' => 'applications/project/controller/base', 'PhabricatorProjectDAO' => 'applications/project/storage/base', 'PhabricatorProjectEditController' => 'applications/project/controller/edit', 'PhabricatorProjectListController' => 'applications/project/controller/list', 'PhabricatorProjectProfile' => 'applications/project/storage/profile', 'PhabricatorProjectProfileController' => 'applications/project/controller/profile', 'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential', 'PhabricatorRemarkupRuleManiphest' => 'infrastructure/markup/remarkup/markuprule/maniphest', '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', 'PhabricatorRepositoryCreateController' => 'applications/repository/controller/create', 'PhabricatorRepositoryDAO' => 'applications/repository/storage/base', + 'PhabricatorRepositoryDaemon' => 'applications/repository/daemon/base', 'PhabricatorRepositoryEditController' => 'applications/repository/controller/edit', + 'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/git', 'PhabricatorRepositoryGitHubNotification' => 'applications/repository/storage/githubnotification', 'PhabricatorRepositoryGitHubPostReceiveController' => 'applications/repository/controller/github-post-receive', + 'PhabricatorRepositoryGitPullDaemon' => 'applications/repository/daemon/gitpull', 'PhabricatorRepositoryListController' => 'applications/repository/controller/list', + 'PhabricatorRepositoryType' => 'applications/repository/constants/repositorytype', 'PhabricatorSearchAbstractDocument' => 'applications/search/index/abstractdocument', 'PhabricatorSearchBaseController' => 'applications/search/controller/base', 'PhabricatorSearchController' => 'applications/search/controller/search', 'PhabricatorSearchDAO' => 'applications/search/storage/base', 'PhabricatorSearchDifferentialIndexer' => 'applications/search/index/indexer/differential', 'PhabricatorSearchDocument' => 'applications/search/storage/document/document', 'PhabricatorSearchDocumentField' => 'applications/search/storage/document/field', 'PhabricatorSearchDocumentIndexer' => 'applications/search/index/indexer/base', 'PhabricatorSearchDocumentRelationship' => 'applications/search/storage/document/relationship', 'PhabricatorSearchExecutor' => 'applications/search/execute/base', 'PhabricatorSearchField' => 'applications/search/constants/field', 'PhabricatorSearchManiphestIndexer' => 'applications/search/index/indexer/maniphest', 'PhabricatorSearchMySQLExecutor' => 'applications/search/execute/mysql', 'PhabricatorSearchQuery' => 'applications/search/storage/query', 'PhabricatorSearchRelationship' => 'applications/search/constants/relationship', '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', 'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base', 'PhabricatorUser' => 'applications/people/storage/user', 'PhabricatorUserDAO' => 'applications/people/storage/base', 'PhabricatorUserOAuthInfo' => 'applications/people/storage/useroauthinfo', 'PhabricatorUserProfile' => 'applications/people/storage/profile', 'PhabricatorUserSettingsController' => 'applications/people/controller/settings', 'PhabricatorXHProfController' => 'applications/xhprof/controller/base', 'PhabricatorXHProfProfileController' => 'applications/xhprof/controller/profile', 'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/symbol', 'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/toplevel', ), 'function' => array( '_qsprintf_check_scalar_type' => 'storage/qsprintf', '_qsprintf_check_type' => 'storage/qsprintf', 'celerity_generate_unique_node_id' => 'infrastructure/celerity/api', 'celerity_register_resource_map' => 'infrastructure/celerity/map', 'javelin_render_tag' => 'infrastructure/javelin/markup', 'phabricator_format_relative_time' => 'view/utils', 'phabricator_format_timestamp' => 'view/utils', 'phabricator_format_units_generic' => 'view/utils', 'phabricator_render_form' => 'infrastructure/javelin/markup', 'qsprintf' => 'storage/qsprintf', 'queryfx' => 'storage/queryfx', 'queryfx_all' => 'storage/queryfx', 'queryfx_one' => 'storage/queryfx', 'require_celerity_resource' => 'infrastructure/celerity/api', 'vqsprintf' => 'storage/qsprintf', 'vqueryfx' => 'storage/queryfx', 'vqueryfx_all' => 'storage/queryfx', 'xsprintf_query' => 'storage/qsprintf', ), 'requires_class' => array( 'Aphront400Response' => 'AphrontResponse', 'Aphront404Response' => 'AphrontResponse', 'AphrontAjaxResponse' => 'AphrontResponse', 'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration', 'AphrontDefaultApplicationController' => 'AphrontController', 'AphrontDialogResponse' => 'AphrontResponse', 'AphrontDialogView' => 'AphrontView', 'AphrontErrorView' => 'AphrontView', 'AphrontFileResponse' => 'AphrontResponse', 'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormControl' => 'AphrontView', 'AphrontFormDividerControl' => 'AphrontFormControl', 'AphrontFormFileControl' => 'AphrontFormControl', 'AphrontFormMarkupControl' => 'AphrontFormControl', 'AphrontFormPasswordControl' => 'AphrontFormControl', 'AphrontFormRecaptchaControl' => 'AphrontFormControl', 'AphrontFormSelectControl' => 'AphrontFormControl', 'AphrontFormStaticControl' => 'AphrontFormControl', 'AphrontFormSubmitControl' => 'AphrontFormControl', 'AphrontFormTextAreaControl' => 'AphrontFormControl', 'AphrontFormTextControl' => 'AphrontFormControl', 'AphrontFormTokenizerControl' => 'AphrontFormControl', 'AphrontFormView' => 'AphrontView', 'AphrontHeadsupActionListView' => 'AphrontView', 'AphrontHeadsupActionView' => 'AphrontView', 'AphrontMySQLDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontNullView' => 'AphrontView', 'AphrontPageView' => 'AphrontView', 'AphrontPanelView' => 'AphrontView', 'AphrontQueryConnectionException' => 'AphrontQueryException', 'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException', 'AphrontQueryCountException' => 'AphrontQueryException', 'AphrontQueryDuplicateKeyException' => 'AphrontQueryException', 'AphrontQueryObjectMissingException' => 'AphrontQueryException', 'AphrontQueryParameterException' => 'AphrontQueryException', 'AphrontQueryRecoverableException' => 'AphrontQueryException', 'AphrontRedirectException' => 'AphrontException', 'AphrontRedirectResponse' => 'AphrontResponse', 'AphrontRequestFailureView' => 'AphrontView', 'AphrontSideNavView' => 'AphrontView', 'AphrontTableView' => 'AphrontView', 'AphrontWebpageResponse' => 'AphrontResponse', 'CelerityResourceController' => 'AphrontController', 'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod', 'ConduitAPI_conduit_ping_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_createrevision_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_find_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getcommitmessage_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getcommitpaths_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getdiff_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_markcommitted_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_parsecommitmessage_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_updaterevision_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod', 'ConduitAPI_user_find_Method' => 'ConduitAPIMethod', 'DarkConsoleConfigPlugin' => 'DarkConsolePlugin', 'DarkConsoleController' => 'PhabricatorController', 'DarkConsoleErrorLogPlugin' => 'DarkConsolePlugin', 'DarkConsoleRequestPlugin' => 'DarkConsolePlugin', 'DarkConsoleServicesPlugin' => 'DarkConsolePlugin', 'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin', 'DifferentialAddCommentView' => 'AphrontView', 'DifferentialAttachController' => 'DifferentialController', 'DifferentialCCWelcomeMail' => 'DifferentialReviewRequestMail', 'DifferentialChangeset' => 'DifferentialDAO', 'DifferentialChangesetDetailView' => 'AphrontView', 'DifferentialChangesetListView' => 'AphrontView', 'DifferentialChangesetViewController' => 'DifferentialController', 'DifferentialComment' => 'DifferentialDAO', 'DifferentialCommentMail' => 'DifferentialMail', 'DifferentialCommentPreviewController' => 'DifferentialController', 'DifferentialCommentSaveController' => 'DifferentialController', 'DifferentialController' => 'PhabricatorController', 'DifferentialDAO' => 'PhabricatorLiskDAO', 'DifferentialDiff' => 'DifferentialDAO', 'DifferentialDiffContentMail' => 'DifferentialMail', 'DifferentialDiffCreateController' => 'DifferentialController', 'DifferentialDiffProperty' => 'DifferentialDAO', 'DifferentialDiffTableOfContentsView' => 'AphrontView', 'DifferentialDiffViewController' => 'DifferentialController', 'DifferentialHunk' => 'DifferentialDAO', 'DifferentialInlineComment' => 'DifferentialDAO', 'DifferentialInlineCommentEditController' => 'DifferentialController', 'DifferentialInlineCommentPreviewController' => 'DifferentialController', 'DifferentialInlineCommentView' => 'AphrontView', 'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail', 'DifferentialReviewRequestMail' => 'DifferentialMail', 'DifferentialRevision' => 'DifferentialDAO', 'DifferentialRevisionCommentListView' => 'AphrontView', 'DifferentialRevisionCommentView' => 'AphrontView', 'DifferentialRevisionDetailView' => 'AphrontView', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionUpdateHistoryView' => 'AphrontView', 'DifferentialRevisionViewController' => 'DifferentialController', 'DifferentialSubscribeController' => 'DifferentialController', 'ManiphestController' => 'PhabricatorController', 'ManiphestDAO' => 'PhabricatorLiskDAO', 'ManiphestTask' => 'ManiphestDAO', 'ManiphestTaskDetailController' => 'ManiphestController', 'ManiphestTaskEditController' => 'ManiphestController', 'ManiphestTaskListController' => 'ManiphestController', 'ManiphestTaskListView' => 'AphrontView', 'ManiphestTaskSelectorSearchController' => 'ManiphestController', 'ManiphestTaskSummaryView' => 'AphrontView', 'ManiphestTransaction' => 'ManiphestDAO', 'ManiphestTransactionDetailView' => 'AphrontView', 'ManiphestTransactionListView' => 'AphrontView', 'ManiphestTransactionSaveController' => 'ManiphestController', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAuthController' => 'PhabricatorController', 'PhabricatorConduitAPIController' => 'PhabricatorConduitController', 'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitConsoleController' => 'PhabricatorConduitController', 'PhabricatorConduitController' => 'PhabricatorController', 'PhabricatorConduitDAO' => 'PhabricatorLiskDAO', 'PhabricatorConduitLogController' => 'PhabricatorConduitController', 'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO', 'PhabricatorController' => 'AphrontController', + 'PhabricatorDaemon' => 'PhutilDaemon', 'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryController' => 'PhabricatorController', 'PhabricatorDirectoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorDirectoryItem' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryItemDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController', 'PhabricatorDraft' => 'PhabricatorDraftDAO', 'PhabricatorDraftDAO' => 'PhabricatorLiskDAO', 'PhabricatorEmailLoginController' => 'PhabricatorAuthController', 'PhabricatorEmailTokenController' => 'PhabricatorAuthController', 'PhabricatorFile' => 'PhabricatorFileDAO', 'PhabricatorFileController' => 'PhabricatorController', 'PhabricatorFileDAO' => 'PhabricatorLiskDAO', 'PhabricatorFileListController' => 'PhabricatorFileController', 'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO', 'PhabricatorFileUploadController' => 'PhabricatorFileController', 'PhabricatorFileViewController' => 'PhabricatorFileController', 'PhabricatorLiskDAO' => 'LiskDAO', 'PhabricatorLoginController' => 'PhabricatorAuthController', 'PhabricatorLogoutController' => 'PhabricatorAuthController', 'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMetaMTAController' => 'PhabricatorController', 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO', 'PhabricatorMetaMTAListController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingList' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingListEditController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMailingListsController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTASendController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController', 'PhabricatorOAuthDefaultRegistrationController' => 'PhabricatorOAuthRegistrationController', 'PhabricatorOAuthDiagnosticsController' => 'PhabricatorAuthController', 'PhabricatorOAuthFailureView' => 'AphrontView', 'PhabricatorOAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorOAuthProviderFacebook' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthProviderGithub' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthRegistrationController' => 'PhabricatorAuthController', 'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController', 'PhabricatorPHID' => 'PhabricatorPHIDDAO', 'PhabricatorPHIDAllocateController' => 'PhabricatorPHIDController', 'PhabricatorPHIDController' => 'PhabricatorController', 'PhabricatorPHIDDAO' => 'PhabricatorLiskDAO', 'PhabricatorPHIDListController' => 'PhabricatorPHIDController', 'PhabricatorPHIDLookupController' => 'PhabricatorPHIDController', 'PhabricatorPHIDType' => 'PhabricatorPHIDDAO', 'PhabricatorPHIDTypeEditController' => 'PhabricatorPHIDController', 'PhabricatorPHIDTypeListController' => 'PhabricatorPHIDController', 'PhabricatorPeopleController' => 'PhabricatorController', 'PhabricatorPeopleEditController' => 'PhabricatorPeopleController', 'PhabricatorPeopleListController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfileEditController' => 'PhabricatorPeopleController', 'PhabricatorProject' => 'PhabricatorProjectDAO', 'PhabricatorProjectAffiliation' => 'PhabricatorProjectDAO', 'PhabricatorProjectAffiliationEditController' => 'PhabricatorProjectController', 'PhabricatorProjectController' => 'PhabricatorController', 'PhabricatorProjectDAO' => 'PhabricatorLiskDAO', 'PhabricatorProjectEditController' => 'PhabricatorProjectController', 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectProfile' => 'PhabricatorProjectDAO', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController', 'PhabricatorRemarkupRuleDifferential' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleManiphest' => 'PhutilRemarkupRule', 'PhabricatorRepository' => 'PhabricatorRepositoryDAO', + 'PhabricatorRepositoryCommit' => 'PhabricatorRepositoryDAO', + 'PhabricatorRepositoryCommitDiscoveryDaemon' => 'PhabricatorRepositoryDaemon', + 'PhabricatorRepositoryCommitParserDaemon' => 'PhabricatorRepositoryDaemon', 'PhabricatorRepositoryController' => 'PhabricatorController', 'PhabricatorRepositoryCreateController' => 'PhabricatorController', 'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO', + 'PhabricatorRepositoryDaemon' => 'PhabricatorDaemon', 'PhabricatorRepositoryEditController' => 'PhabricatorController', + 'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon', 'PhabricatorRepositoryGitHubNotification' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryGitHubPostReceiveController' => 'PhabricatorRepositoryController', + 'PhabricatorRepositoryGitPullDaemon' => 'PhabricatorRepositoryDaemon', 'PhabricatorRepositoryListController' => 'PhabricatorController', 'PhabricatorSearchBaseController' => 'PhabricatorController', 'PhabricatorSearchController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO', 'PhabricatorSearchDifferentialIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO', 'PhabricatorSearchManiphestIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchMySQLExecutor' => 'PhabricatorSearchExecutor', 'PhabricatorSearchQuery' => 'PhabricatorSearchDAO', 'PhabricatorStandardPageView' => 'AphrontPageView', + 'PhabricatorTimelineCursor' => 'PhabricatorTimelineDAO', + 'PhabricatorTimelineDAO' => 'PhabricatorLiskDAO', + 'PhabricatorTimelineEvent' => 'PhabricatorTimelineDAO', + 'PhabricatorTimelineEventData' => 'PhabricatorTimelineDAO', 'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController', 'PhabricatorUser' => 'PhabricatorUserDAO', 'PhabricatorUserDAO' => 'PhabricatorLiskDAO', 'PhabricatorUserOAuthInfo' => 'PhabricatorUserDAO', 'PhabricatorUserProfile' => 'PhabricatorUserDAO', 'PhabricatorUserSettingsController' => 'PhabricatorPeopleController', 'PhabricatorXHProfController' => 'PhabricatorController', 'PhabricatorXHProfProfileController' => 'PhabricatorXHProfController', 'PhabricatorXHProfProfileSymbolView' => 'AphrontView', 'PhabricatorXHProfProfileTopLevelView' => 'AphrontView', ), 'requires_interface' => array( ), )); diff --git a/src/aphront/console/plugin/errorlog/api/DarkConsoleErrorLogPluginAPI.php b/src/aphront/console/plugin/errorlog/api/DarkConsoleErrorLogPluginAPI.php index bee9db704a..a6eea09524 100755 --- a/src/aphront/console/plugin/errorlog/api/DarkConsoleErrorLogPluginAPI.php +++ b/src/aphront/console/plugin/errorlog/api/DarkConsoleErrorLogPluginAPI.php @@ -1,48 +1,58 @@ 'error', - 'num' => $num, - 'str' => $str, - 'file' => $file, - 'line' => $line, - 'cxt' => $cxt, - 'trace' => debug_backtrace(), - ); + if (!self::$discardMode) { + self::$errors[] = array( + 'event' => 'error', + 'num' => $num, + 'str' => $str, + 'file' => $file, + 'line' => $line, + 'cxt' => $cxt, + 'trace' => debug_backtrace(), + ); + } error_log("{$file}:{$line} {$str}"); } public static function handleException($ex) { - self::$errors[] = array( - 'event' => 'exception', - 'exception' => $ex, - ); + if (!self::$discardMode) { + self::$errors[] = array( + 'event' => 'exception', + 'exception' => $ex, + ); + } error_log($ex); } } diff --git a/src/aphront/console/plugin/services/api/DarkConsoleServicesPluginAPI.php b/src/aphront/console/plugin/services/api/DarkConsoleServicesPluginAPI.php index c652075a39..23163081a2 100755 --- a/src/aphront/console/plugin/services/api/DarkConsoleServicesPluginAPI.php +++ b/src/aphront/console/plugin/services/api/DarkConsoleServicesPluginAPI.php @@ -1,35 +1,43 @@ getResourceURIMapRules() + array( '/' => array( '$' => 'PhabricatorDirectoryMainController', ), '/directory/' => array( 'item/$' => 'PhabricatorDirectoryItemListController', 'item/edit/(?:(?P\d+)/)?$' => 'PhabricatorDirectoryItemEditController', 'item/delete/(?P\d+)/' => 'PhabricatorDirectoryItemDeleteController', 'category/$' => 'PhabricatorDirectoryCategoryListController', 'category/edit/(?:(?P\d+)/)?$' => 'PhabricatorDirectoryCategoryEditController', 'category/delete/(?P\d+)/' => 'PhabricatorDirectoryCategoryDeleteController', ), '/file/' => array( '$' => 'PhabricatorFileListController', 'upload/$' => 'PhabricatorFileUploadController', '(?Pinfo)/(?P[^/]+)/' => 'PhabricatorFileViewController', '(?Pview)/(?P[^/]+)/' => 'PhabricatorFileViewController', '(?Pdownload)/(?P[^/]+)/' => 'PhabricatorFileViewController', ), '/phid/' => array( '$' => 'PhabricatorPHIDLookupController', 'list/$' => 'PhabricatorPHIDListController', 'type/$' => 'PhabricatorPHIDTypeListController', 'type/edit/(?:(?P\d+)/)?$' => 'PhabricatorPHIDTypeEditController', 'new/$' => 'PhabricatorPHIDAllocateController', ), '/people/' => array( '$' => 'PhabricatorPeopleListController', 'edit/(?:(?P\w+)/)?$' => 'PhabricatorPeopleEditController', ), '/p/(?P\w+)/$' => 'PhabricatorPeopleProfileController', '/profile/' => array( 'edit/$' => 'PhabricatorPeopleProfileEditController', ), '/conduit/' => array( '$' => 'PhabricatorConduitConsoleController', 'method/(?P[^/]+)$' => 'PhabricatorConduitConsoleController', 'log/$' => 'PhabricatorConduitLogController', ), '/api/(?P[^/]+)$' => 'PhabricatorConduitAPIController', '/D(?P\d+)' => 'DifferentialRevisionViewController', '/differential/' => array( '$' => 'DifferentialRevisionListController', 'filter/(?P\w+)/$' => 'DifferentialRevisionListController', 'diff/' => array( '(?P\d+)/$' => 'DifferentialDiffViewController', 'create/$' => 'DifferentialDiffCreateController', ), 'changeset/$' => 'DifferentialChangesetViewController', 'revision/edit/(?:(?P\d+)/)?$' => 'DifferentialRevisionEditController', 'comment/' => array( 'preview/(?P\d+)/$' => 'DifferentialCommentPreviewController', 'save/$' => 'DifferentialCommentSaveController', 'inline/' => array( 'preview/(?P\d+)/$' => 'DifferentialInlineCommentPreviewController', 'edit/(?P\d+)/$' => 'DifferentialInlineCommentEditController', ), ), 'attach/(?P\d+)/(?P\w+)/$' => 'DifferentialAttachController', 'subscribe/(?Padd|rem)/(?P\d+)/$' => 'DifferentialSubscribeController', ), '/typeahead/' => array( 'common/(?P\w+)/$' => 'PhabricatorTypeaheadCommonDatasourceController', ), '/mail/' => array( '$' => 'PhabricatorMetaMTAListController', 'send/$' => 'PhabricatorMetaMTASendController', 'view/(?P\d+)/$' => 'PhabricatorMetaMTAViewController', 'lists/$' => 'PhabricatorMetaMTAMailingListsController', 'lists/edit/(?:(?P\d+)/)?$' => 'PhabricatorMetaMTAMailingListEditController', ), '/login/' => array( '$' => 'PhabricatorLoginController', 'email/$' => 'PhabricatorEmailLoginController', 'etoken/(?P\w+)/$' => 'PhabricatorEmailTokenController', ), '/logout/$' => 'PhabricatorLogoutController', '/oauth/' => array( '(?Pgithub|facebook)/' => array( 'login/$' => 'PhabricatorOAuthLoginController', 'diagnose/$' => 'PhabricatorOAuthDiagnosticsController', 'unlink/$' => 'PhabricatorOAuthUnlinkController', ), ), '/xhprof/' => array( 'profile/(?P[^/]+)/$' => 'PhabricatorXHProfProfileController', ), '/~/' => 'DarkConsoleController', '/settings/' => array( '(?:page/(?P[^/]+)/)?$' => 'PhabricatorUserSettingsController', ), '/maniphest/' => array( '$' => 'ManiphestTaskListController', 'view/(?P\w+)/$' => 'ManiphestTaskListController', 'task/' => array( 'create/$' => 'ManiphestTaskEditController', 'edit/(?P\d+)/$' => 'ManiphestTaskEditController', ), 'transaction/' => array( 'save/' => 'ManiphestTransactionSaveController', ), 'select/search/$' => 'ManiphestTaskSelectorSearchController', ), '/T(?P\d+)$' => 'ManiphestTaskDetailController', '/github-post-receive/(?P\d+)/(?P[^/]+)/$' => 'PhabricatorRepositoryGitHubPostReceiveController', '/repository/' => array( '$' => 'PhabricatorRepositoryListController', 'create/$' => 'PhabricatorRepositoryCreateController', - 'edit/(?P\d+)/$' => 'PhabricatorRepositoryEditController', + 'edit/(?P\d+)/(?:(?P\w+)?/)?$' => + 'PhabricatorRepositoryEditController', 'delete/(?P\d+)/$' => 'PhabricatorRepositoryDeleteController', ), '/search/' => array( '$' => 'PhabricatorSearchController', '(?P\d+)/$' => 'PhabricatorSearchController', ), '/project/' => array( '$' => 'PhabricatorProjectListController', 'edit/(?:(?P\d+)/)?$' => 'PhabricatorProjectEditController', 'view/(?P\d+)/$' => 'PhabricatorProjectProfileController', 'affiliation/(?P\d+)/$' => 'PhabricatorProjectAffiliationEditController', ), ); } protected function getResourceURIMapRules() { return array( '/res/' => array( '(?Ppkg/)?(?P[a-f0-9]{8})/(?P.+\.(?:css|js))$' => 'CelerityResourceController', ), ); } public function buildRequest() { $request = new AphrontRequest($this->getHost(), $this->getPath()); $request->setRequestData($_GET + $_POST); $request->setApplicationConfiguration($this); return $request; } public function handleException(Exception $ex) { $class = phutil_escape_html(get_class($ex)); $message = phutil_escape_html($ex->getMessage()); $content = '
'. '

Unhandled Exception "'.$class.'": '.$message.'

'. ''.phutil_escape_html((string)$ex).''. '
'; $user = $this->getRequest()->getUser(); if (!$user) { // If we hit an exception very early, we won't have a user. $user = new PhabricatorUser(); } $dialog = new AphrontDialogView(); $dialog ->setTitle('Exception!') ->setClass('aphront-exception-dialog') ->setUser($user) ->appendChild($content) ->addCancelButton('/'); $response = new AphrontDialogResponse(); $response->setDialog($dialog); return $response; } public function willSendResponse(AphrontResponse $response) { $request = $this->getRequest(); if ($response instanceof AphrontDialogResponse) { if (!$request->isAjax()) { $view = new PhabricatorStandardPageView(); $view->setRequest($request); $view->appendChild( '
'. $response->buildResponseString(). '
'); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } else { return id(new AphrontAjaxResponse()) ->setContent(array( 'dialog' => $response->buildResponseString(), )); } } else if ($response instanceof AphrontRedirectResponse) { if ($request->isAjax()) { return id(new AphrontAjaxResponse()) ->setContent( array( 'redirect' => $response->getURI(), )); } } else if ($response instanceof Aphront404Response) { $failure = new AphrontRequestFailureView(); $failure->setHeader('404 Not Found'); $failure->appendChild( '

The page you requested was not found.

'); $view = new PhabricatorStandardPageView(); $view->setTitle('404 Not Found'); $view->setRequest($this->getRequest()); $view->appendChild($failure); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); $response->setHTTPResponseCode(404); return $response; } return $response; } public function build404Controller() { return array(new Phabricator404Controller($this->getRequest()), array()); } } diff --git a/src/applications/phid/constants/PhabricatorPHIDConstants.php b/src/applications/phid/constants/PhabricatorPHIDConstants.php index e8363a9d71..30aa59dc1c 100644 --- a/src/applications/phid/constants/PhabricatorPHIDConstants.php +++ b/src/applications/phid/constants/PhabricatorPHIDConstants.php @@ -1,30 +1,32 @@ getRequest(); $user = $request->getUser(); $e_name = true; $e_callsign = true; $repository = new PhabricatorRepository(); $type_map = array( - 'svn' => 'Subversion', 'git' => 'Git', + 'svn' => 'Subversion', ); $errors = array(); if ($request->isFormPost()) { $repository->setName($request->getStr('name')); $repository->setCallsign($request->getStr('callsign')); $repository->setVersionControlSystem($request->getStr('type')); if (!strlen($repository->getName())) { $e_name = 'Required'; $errors[] = 'Repository name is required.'; } else { $e_name = null; } if (!strlen($repository->getCallsign())) { $e_callsign = 'Required'; $errors[] = 'Callsign is required.'; } else if (!preg_match('/^[A-Z]+$/', $repository->getCallsign())) { $e_callsign = 'Invalid'; $errors[] = 'Callsign must be ALL UPPERCASE LETTERS.'; } else { $e_callsign = null; } if (empty($type_map[$repository->getVersionControlSystem()])) { $errors[] = 'Invalid version control system.'; } if (!$errors) { try { $repository->save(); return id(new AphrontRedirectResponse()) ->setURI('/repository/edit/'.$repository->getID().'/'); } catch (AphrontQueryDuplicateKeyException $ex) { $e_callsign = 'Duplicate'; $errors[] = 'Callsign must be unique. Another repository already '. 'uses that callsign.'; } } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setErrors($errors); $error_view->setTitle('Form Errors'); } $form = new AphrontFormView(); $form ->setUser($user) ->setAction('/repository/create/') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Name') ->setName('name') ->setValue($repository->getName()) ->setError($e_name) ->setCaption('Human-readable repository name.')) ->appendChild( '

Select a "Callsign" — a '. 'short, uppercase string to identify revisions in this repository. If '. 'you choose "EX", revisions in this repository will be identified '. 'with the prefix "rEX".

') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Callsign') ->setName('callsign') ->setValue($repository->getCallsign()) ->setError($e_callsign) ->setCaption( 'Short, UPPERCASE identifier. Once set, it can not be changed.')) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Type') ->setName('type') ->setOptions($type_map) ->setValue($repository->getVersionControlSystem())) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Create Repository') ->addCancelButton('/repository/')); $panel = new AphrontPanelView(); $panel->setHeader('Create Repository'); $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FORM); return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Create Repository', )); } } diff --git a/src/applications/repository/controller/edit/PhabricatorRepositoryEditController.php b/src/applications/repository/controller/edit/PhabricatorRepositoryEditController.php index 7eac802e03..50964bd660 100644 --- a/src/applications/repository/controller/edit/PhabricatorRepositoryEditController.php +++ b/src/applications/repository/controller/edit/PhabricatorRepositoryEditController.php @@ -1,192 +1,405 @@ id = idx($data, 'id'); + $this->id = $data['id']; + $this->view = idx($data, 'view'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $repository = id(new PhabricatorRepository())->load($this->id); if (!$repository) { return new Aphront404Response(); } + $views = array( + 'basic' => 'Basics', + 'tracking' => 'Tracking', + ); + $vcs = $repository->getVersionControlSystem(); if ($vcs == DifferentialRevisionControlSystem::GIT) { if (!$repository->getDetail('github-token')) { $token = substr(base64_encode(Filesystem::readRandomBytes(8)), 0, 8); $repository->setDetail('github-token', $token); $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( 'svn' => 'Subversion', 'git' => 'Git', ); + $errors = array(); + $e_name = true; + if ($request->isFormPost()) { $repository->setName($request->getStr('name')); if (!strlen($repository->getName())) { $e_name = 'Required'; $errors[] = 'Repository name is required.'; } else { $e_name = null; } if (!$errors) { $repository->save(); return id(new AphrontRedirectResponse()) - ->setURI('/repository/'); + ->setURI('/repository/edit/'.$repository_id.'/basic/?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( + 'Repository changes were saved.'); } - $form = new AphrontFormView(); $form ->setUser($user) ->setAction('/repository/edit/'.$repository->getID().'/') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Name') ->setName('name') ->setValue($repository->getName()) ->setError($e_name) ->setCaption('Human-readable repository name.')) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Callsign') ->setName('callsign') ->setValue($repository->getCallsign())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Type') ->setName('type') - ->setValue($repository->getVersionControlSystem())); + ->setValue($repository->getVersionControlSystem())) + ->appendChild( + id(new AphrontFormStaticControl()) + ->setLabel('PHID') + ->setName('phid') + ->setValue($repository->getPHID())) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Save')); + + $panel = new AphrontPanelView(); + $panel->setHeader('Edit Repository'); + $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', + )); + } + + 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 '. + 'git clone 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 '. + 'svn log from this URI.'; + break; + } + + $form = new AphrontFormView(); $form + ->setUser($user) + ->setAction('/repository/edit/'.$repository->getID().'/tracking/') + ->appendChild( + '

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.

') + ->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') - ->addCancelButton('/repository/')); + ->setValue('Save')); $panel = new AphrontPanelView(); - $panel->setHeader('Edit Repository'); + $panel->setHeader('Repository Tracking'); $panel->appendChild($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 Tracking', + )); + } + + + private function processGithubRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + $repository = $this->repository; + $repository_id = $repository->getID(); + $token = $repository->getDetail('github-token'); - $path = '/github-post-receive/'.$phid.'/'.$token.'/'; + $path = '/github-post-receive/'.$repository_id.'/'.$token.'/'; $post_uri = PhabricatorEnv::getURI($path); $gitform = new AphrontFormView(); $gitform ->setUser($user) - ->setAction('/repository/edit/'.$repository->getID().'/') ->appendChild( '

You can configure GitHub to '. 'notify Phabricator after changes are pushed. Log into GitHub, go '. 'to "Admin" → "Service Hooks" → "Post-Receive URLs", and '. 'add this URL to the list. Obviously, this will only work if your '. 'Phabricator installation is accessible from the internet.

') ->appendChild( '

If things are working '. 'properly, push notifications should appear below once you make some '. 'commits.

') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('URL') ->setCaption('Set this as a GitHub "Post-Receive URL".') ->setValue($post_uri)) ->appendChild('

') ->appendChild('

Recent Commit Notifications

'); $notifications = id(new PhabricatorRepositoryGitHubNotification()) ->loadAllWhere( 'repositoryPHID = %s ORDER BY id DESC limit 10', $repository->getPHID()); $rows = array(); foreach ($notifications as $notification) { $rows[] = array( phutil_escape_html($notification->getRemoteAddress()), phabricator_format_timestamp($notification->getDateCreated()), $notification->getPayload() ? phutil_escape_html(substr($notification->getPayload(), 0, 32).'...') : 'Empty', ); } $notification_table = new AphrontTableView($rows); $notification_table->setHeaders( array( 'Remote Address', 'Received', 'Payload', )); $notification_table->setColumnClasses( array( null, null, 'wide', )); $notification_table->setNoDataString( 'Phabricator has not yet received any commit notifications for this '. 'repository from GitHub.'); $gitform->appendChild($notification_table); $github = new AphrontPanelView(); $github->setHeader('GitHub Integration'); $github->appendChild($gitform); $github->setWidth(AphrontPanelView::WIDTH_FORM); + $nav = $this->sideNav; + $nav->appendChild($github); + return $this->buildStandardPageResponse( + $nav, array( - $error_view, - $panel, - $github, - ), - array( - 'title' => 'Edit Repository', + 'title' => 'Repository Github Integration', )); } - } diff --git a/src/applications/repository/controller/edit/__init__.php b/src/applications/repository/controller/edit/__init__.php index 96c6efbc23..32113a75bc 100644 --- a/src/applications/repository/controller/edit/__init__.php +++ b/src/applications/repository/controller/edit/__init__.php @@ -1,29 +1,29 @@ getArgv(); + if (count($argv) !== 1) { + throw new Exception("No repository PHID provided!"); + } - private static $events = array(); + $repository = id(new PhabricatorRepository())->loadOneWhere( + 'phid = %s', + $argv[0]); - public static function addEvent(array $event) { - self::$events[] = $event; - } + if (!$repository) { + throw new Exception("No such repository exists!"); + } - public static function getEvents() { - return self::$events; + return $repository; } } - diff --git a/src/applications/repository/storage/repository/__init__.php b/src/applications/repository/daemon/base/__init__.php similarity index 59% copy from src/applications/repository/storage/repository/__init__.php copy to src/applications/repository/daemon/base/__init__.php index 1fea420fa4..97230a3be2 100644 --- a/src/applications/repository/storage/repository/__init__.php +++ b/src/applications/repository/daemon/base/__init__.php @@ -1,15 +1,15 @@ repository; } - public static function getEvents() { - return self::$events; + 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(); +} diff --git a/src/applications/repository/daemon/commitdiscovery/base/__init__.php b/src/applications/repository/daemon/commitdiscovery/base/__init__.php new file mode 100644 index 0000000000..b48312c05a --- /dev/null +++ b/src/applications/repository/daemon/commitdiscovery/base/__init__.php @@ -0,0 +1,12 @@ +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; + } + +} diff --git a/src/applications/repository/daemon/commitdiscovery/git/__init__.php b/src/applications/repository/daemon/commitdiscovery/git/__init__.php new file mode 100644 index 0000000000..91b37d309a --- /dev/null +++ b/src/applications/repository/daemon/commitdiscovery/git/__init__.php @@ -0,0 +1,17 @@ +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)); + } + } + +} diff --git a/src/applications/repository/daemon/gitpull/__init__.php b/src/applications/repository/daemon/gitpull/__init__.php new file mode 100644 index 0000000000..19e3eb4519 --- /dev/null +++ b/src/applications/repository/daemon/gitpull/__init__.php @@ -0,0 +1,15 @@ + true, - self::CONFIG_SERIALIZATION => array( - 'details' => self::SERIALIZATION_JSON, - ), + self::CONFIG_AUX_PHID => true, + self::CONFIG_TIMESTAMPS => false, ) + parent::getConfiguration(); } public function generatePHID() { - return PhabricatorPHID::generateNewPHID('REPO'); - } - - public function getDetail($key, $default = null) { - return idx($this->details, $key, $default); - } - - public function setDetail($key, $value) { - $this->details[$key] = $value; - return $this; + return PhabricatorPHID::generateNewPHID( + PhabricatorPHIDConstants::PHID_TYPE_CMIT); } } diff --git a/src/applications/repository/storage/repository/__init__.php b/src/applications/repository/storage/commit/__init__.php similarity index 66% copy from src/applications/repository/storage/repository/__init__.php copy to src/applications/repository/storage/commit/__init__.php index 1fea420fa4..9d410d797e 100644 --- a/src/applications/repository/storage/repository/__init__.php +++ b/src/applications/repository/storage/commit/__init__.php @@ -1,15 +1,14 @@ true, self::CONFIG_SERIALIZATION => array( 'details' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function generatePHID() { - return PhabricatorPHID::generateNewPHID('REPO'); + return PhabricatorPHID::generateNewPHID( + PhabricatorPHIDConstants::PHID_TYPE_REPO); } public function getDetail($key, $default = null) { return idx($this->details, $key, $default); } public function setDetail($key, $value) { $this->details[$key] = $value; return $this; } } diff --git a/src/applications/repository/storage/repository/__init__.php b/src/applications/repository/storage/repository/__init__.php index 1fea420fa4..6480f38dac 100644 --- a/src/applications/repository/storage/repository/__init__.php +++ b/src/applications/repository/storage/repository/__init__.php @@ -1,15 +1,16 @@ 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(); + } + } + +} diff --git a/src/applications/timeline/cursor/iterator/__init__.php b/src/applications/timeline/cursor/iterator/__init__.php new file mode 100644 index 0000000000..3b881a2988 --- /dev/null +++ b/src/applications/timeline/cursor/iterator/__init__.php @@ -0,0 +1,17 @@ + self::IDS_MANUAL, + self::CONFIG_TIMESTAMPS => false, + ) + parent::getConfiguration(); } } - diff --git a/src/applications/timeline/storage/cursor/__init__.php b/src/applications/timeline/storage/cursor/__init__.php new file mode 100644 index 0000000000..486c4acd51 --- /dev/null +++ b/src/applications/timeline/storage/cursor/__init__.php @@ -0,0 +1,12 @@ +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; + } + +} diff --git a/src/applications/timeline/storage/event/__init__.php b/src/applications/timeline/storage/event/__init__.php new file mode 100644 index 0000000000..f1769381d5 --- /dev/null +++ b/src/applications/timeline/storage/event/__init__.php @@ -0,0 +1,13 @@ + array( + 'eventData' => self::SERIALIZATION_JSON, + ), + self::CONFIG_TIMESTAMPS => false, + ) + parent::getConfiguration(); } } - diff --git a/src/applications/timeline/storage/eventdata/__init__.php b/src/applications/timeline/storage/eventdata/__init__.php new file mode 100644 index 0000000000..bd5c020d24 --- /dev/null +++ b/src/applications/timeline/storage/eventdata/__init__.php @@ -0,0 +1,12 @@ +configuration = $configuration; } public function escapeString($string) { $this->requireConnection(); return mysql_real_escape_string($string, $this->connection); } public function escapeColumnName($name) { return '`'.str_replace('`', '\\`', $name).'`'; } public function escapeMultilineComment($comment) { // These can either terminate a comment, confuse the hell out of the parser, // make MySQL execute the comment as a query, or, in the case of semicolon, // are quasi-dangerous because the semicolon could turn a broken query into // a working query plus an ignored query. static $map = array( '--' => '(DOUBLEDASH)', '*/' => '(STARSLASH)', '//' => '(SLASHSLASH)', '#' => '(HASH)', '!' => '(BANG)', ';' => '(SEMICOLON)', ); $comment = str_replace( array_keys($map), array_values($map), $comment); // For good measure, kill anything else that isn't a nice printable // character. $comment = preg_replace('/[^\x20-\x7F]+/', ' ', $comment); return '/* '.$comment.' */'; } public function escapeStringForLikeClause($value) { $value = $this->escapeString($value); // Ideally the query shouldn't be modified after safely escaping it, // but we need to escape _ and % within LIKE terms. $value = str_replace( // Even though we've already escaped, we need to replace \ with \\ // because MYSQL unescapes twice inside a LIKE clause. See note // at mysql.com. However, if the \ is being used to escape a single // quote ('), then the \ should not be escaped. Thus, after all \ // are replaced with \\, we need to revert instances of \\' back to // \'. array('\\', '\\\\\'', '_', '%'), array('\\\\', '\\\'', '\_', '\%'), $value); return $value; } private function getConfiguration($key, $default = null) { return idx($this->configuration, $key, $default); } private function closeConnection() { if ($this->connection) { $this->connection = null; $key = $this->getConnectionCacheKey(); unset(self::$connectionCache[$key]); } } private function getConnectionCacheKey() { $user = $this->getConfiguration('user'); $host = $this->getConfiguration('host'); $database = $this->getConfiguration('database'); return "{$user}:{$host}:{$database}"; } private function establishConnection() { $this->closeConnection(); $user = $this->getConfiguration('user'); $host = $this->getConfiguration('host'); $database = $this->getConfiguration('database'); $key = $this->getConnectionCacheKey(); if (isset(self::$connectionCache[$key])) { $this->connection = self::$connectionCache[$key]; return; } $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( $host, $user, $this->getConfiguration('pass'), $new_link = true, $flags = 0); if (!$conn) { $errno = mysql_errno(); $error = mysql_error(); throw new AphrontQueryConnectionException( "Attempt to connect to {$user}@{$host} failed with error #{$errno}: ". "{$error}."); } $ret = @mysql_select_db($database, $conn); if (!$ret) { $this->throwQueryException($conn); } $end = microtime(true); DarkConsoleServicesPluginAPI::addEvent( array( 'event' => DarkConsoleServicesPluginAPI::EVENT_CONNECT, 'host' => $host, 'database' => $database, 'start' => $start, 'end' => $end, )); self::$connectionCache[$key] = $conn; $this->connection = $conn; } public function getInsertID() { return mysql_insert_id($this->requireConnection()); } public function getAffectedRows() { return mysql_affected_rows($this->requireConnection()); } public function getTransactionKey() { return (int)$this->requireConnection(); } private function requireConnection() { if (!$this->connection) { $this->establishConnection(); } return $this->connection; } public function selectAllResults() { $result = array(); $res = $this->lastResult; if ($res == null) { throw new Exception('No query result to fetch from!'); } while (($row = mysql_fetch_assoc($res)) !== false) { $result[] = $row; } return $result; } public function executeRawQuery($raw_query) { $this->lastResult = null; $retries = 3; while ($retries--) { try { $this->requireConnection(); $start = microtime(true); $result = @mysql_query($raw_query, $this->connection); $end = microtime(true); DarkConsoleServicesPluginAPI::addEvent( array( 'event' => DarkConsoleServicesPluginAPI::EVENT_QUERY, 'query' => $raw_query, 'start' => $start, 'end' => $end, )); if ($result) { $this->lastResult = $result; break; } $this->throwQueryException($this->connection); } catch (AphrontQueryConnectionLostException $ex) { if (!$retries) { throw $ex; } if ($this->isInsideTransaction()) { throw $ex; } $this->closeConnection(); } } } private function throwQueryException($connection) { $errno = mysql_errno($connection); $error = mysql_error($connection); switch ($errno) { case 2013: // Connection Dropped case 2006: // Gone Away throw new AphrontQueryConnectionLostException("#{$errno}: {$error}"); case 1213: // Deadlock case 1205: // Lock wait timeout exceeded throw new AphrontQueryRecoverableException("#{$errno}: {$error}"); case 1062: // Duplicate Key // NOTE: In some versions of MySQL we get a key name back here, but // older versions just give us a key index ("key 2") so it's not // portable to parse the key out of the error and attach it to the // exception. throw new AphrontQueryDuplicateKeyException("{$errno}: {$error}"); default: // TODO: 1064 is syntax error, and quite terrible in production. throw new AphrontQueryException("#{$errno}: {$error}"); } } }