mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-24 21:48:21 +01:00
(stable) Promote 2018 Week 45
This commit is contained in:
commit
de56ef9466
43 changed files with 1050 additions and 235 deletions
|
@ -422,7 +422,6 @@ return array(
|
|||
'rsrc/js/application/repository/repository-crossreference.js' => '9a860428',
|
||||
'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e2e0a072',
|
||||
'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08',
|
||||
'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f',
|
||||
'rsrc/js/application/transactions/behavior-comment-actions.js' => '038bf27f',
|
||||
'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243',
|
||||
'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96',
|
||||
|
@ -674,7 +673,6 @@ return array(
|
|||
'javelin-behavior-select-content' => 'bf5374ef',
|
||||
'javelin-behavior-select-on-click' => '4e3e79a6',
|
||||
'javelin-behavior-setup-check-https' => '491416b3',
|
||||
'javelin-behavior-slowvote-embed' => '887ad43f',
|
||||
'javelin-behavior-stripe-payment-form' => 'a6b98425',
|
||||
'javelin-behavior-test-payment-form' => 'fc91ab6c',
|
||||
'javelin-behavior-time-typeahead' => '522431f7',
|
||||
|
@ -1550,12 +1548,6 @@ return array(
|
|||
'phabricator-keyboard-shortcut',
|
||||
'javelin-stratcom',
|
||||
),
|
||||
'887ad43f' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-request',
|
||||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
),
|
||||
'8935deef' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
|
|
14
resources/sql/autopatches/20181106.repo.01.sync.sql
Normal file
14
resources/sql/autopatches/20181106.repo.01.sync.sql
Normal file
|
@ -0,0 +1,14 @@
|
|||
CREATE TABLE {$NAMESPACE}_repository.repository_syncevent (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
repositoryPHID VARBINARY(64) NOT NULL,
|
||||
epoch INT UNSIGNED NOT NULL,
|
||||
devicePHID VARBINARY(64) NOT NULL,
|
||||
fromDevicePHID VARBINARY(64) NOT NULL,
|
||||
deviceVersion INT UNSIGNED,
|
||||
fromDeviceVersion INT UNSIGNED,
|
||||
resultType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
resultCode INT UNSIGNED NOT NULL,
|
||||
syncWait BIGINT UNSIGNED NOT NULL,
|
||||
properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
2
resources/sql/autopatches/20181106.repo.02.hook.sql
Normal file
2
resources/sql/autopatches/20181106.repo.02.hook.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_repository.repository_pushevent
|
||||
ADD hookWait BIGINT UNSIGNED;
|
|
@ -17,6 +17,8 @@
|
|||
// subclasses of PhabricatorConfigSiteSource to read it and build an instance
|
||||
// environment.
|
||||
|
||||
$hook_start = microtime(true);
|
||||
|
||||
if ($argc > 1) {
|
||||
$context = $argv[1];
|
||||
$context = explode(':', $context, 2);
|
||||
|
@ -35,7 +37,8 @@ if ($argc < 2) {
|
|||
throw new Exception(pht('usage: commit-hook <repository>'));
|
||||
}
|
||||
|
||||
$engine = new DiffusionCommitHookEngine();
|
||||
$engine = id(new DiffusionCommitHookEngine())
|
||||
->setStartTime($hook_start);
|
||||
|
||||
$repository = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
|
@ -204,23 +207,23 @@ try {
|
|||
+---------------------------------------------------------------+
|
||||
| * * * PUSH REJECTED BY EVIL DRAGON BUREAUCRATS * * * |
|
||||
+---------------------------------------------------------------+
|
||||
\
|
||||
\ ^ /^
|
||||
\ / \ // \
|
||||
\ |\___/| / \// .\
|
||||
\ /V V \__ / // | \ \ *----*
|
||||
/ / \/_/ // | \ \ \ |
|
||||
@___@` \/_ // | \ \ \/\ \
|
||||
0/0/| \/_ // | \ \ \ \
|
||||
0/0/0/0/| \/// | \ \ | |
|
||||
0/0/0/0/0/_|_ / ( // | \ _\ | /
|
||||
0/0/0/0/0/0/`/,_ _ _/ ) ; -. | _ _\.-~ / /
|
||||
,-} _ *-.|.-~-. .~ ~
|
||||
\ \__/ `/\ / ~-. _ .-~ /
|
||||
\____(Oo) *. } { /
|
||||
( (--) .----~-.\ \-` .~
|
||||
//__\\\\ \ DENIED! ///.----..< \ _ -~
|
||||
// \\\\ ///-._ _ _ _ _ _ _{^ - - - - ~
|
||||
\
|
||||
\ ^ /^
|
||||
\ / \ // \
|
||||
\ |\___/| / \// .\
|
||||
\ /V V \__ / // | \ \ *----*
|
||||
/ / \/_/ // | \ \ \ |
|
||||
@___@` \/_ // | \ \ \/\ \
|
||||
0/0/| \/_ // | \ \ \ \
|
||||
0/0/0/0/| \/// | \ \ | |
|
||||
0/0/0/0/0/_|_ / ( // | \ _\ | /
|
||||
0/0/0/0/0/0/`/,_ _ _/ ) ; -. | _ _\.-~ / /
|
||||
,-} _ *-.|.-~-. .~ ~
|
||||
* \__/ `/\ / ~-. _ .-~ /
|
||||
\____(Oo) *. } { /
|
||||
( (..) .----~-.\ \-` .~
|
||||
//___\\\\ \ DENIED! ///.----..< \ _ -~
|
||||
// \\\\ ///-._ _ _ _ _ _ _{^ - - - - ~
|
||||
|
||||
EOTXT
|
||||
);
|
||||
|
|
|
@ -307,7 +307,7 @@ try {
|
|||
$ssh_log->setData(
|
||||
array(
|
||||
'c' => $err,
|
||||
'T' => (int)(1000000 * (microtime(true) - $ssh_start_time)),
|
||||
'T' => phutil_microseconds_since($ssh_start_time),
|
||||
));
|
||||
|
||||
exit($err);
|
||||
|
|
|
@ -992,6 +992,9 @@ phutil_register_library_map(array(
|
|||
'DiffusionSymbolController' => 'applications/diffusion/controller/DiffusionSymbolController.php',
|
||||
'DiffusionSymbolDatasource' => 'applications/diffusion/typeahead/DiffusionSymbolDatasource.php',
|
||||
'DiffusionSymbolQuery' => 'applications/diffusion/query/DiffusionSymbolQuery.php',
|
||||
'DiffusionSyncLogListController' => 'applications/diffusion/controller/DiffusionSyncLogListController.php',
|
||||
'DiffusionSyncLogListView' => 'applications/diffusion/view/DiffusionSyncLogListView.php',
|
||||
'DiffusionSyncLogSearchEngine' => 'applications/diffusion/query/DiffusionSyncLogSearchEngine.php',
|
||||
'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php',
|
||||
'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php',
|
||||
'DiffusionTagTableView' => 'applications/diffusion/view/DiffusionTagTableView.php',
|
||||
|
@ -4163,6 +4166,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositorySvnCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositorySvnCommitChangeParserWorker.php',
|
||||
'PhabricatorRepositorySvnCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositorySvnCommitMessageParserWorker.php',
|
||||
'PhabricatorRepositorySymbol' => 'applications/repository/storage/PhabricatorRepositorySymbol.php',
|
||||
'PhabricatorRepositorySyncEvent' => 'applications/repository/storage/PhabricatorRepositorySyncEvent.php',
|
||||
'PhabricatorRepositorySyncEventPHIDType' => 'applications/repository/phid/PhabricatorRepositorySyncEventPHIDType.php',
|
||||
'PhabricatorRepositorySyncEventQuery' => 'applications/repository/query/PhabricatorRepositorySyncEventQuery.php',
|
||||
'PhabricatorRepositoryTestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php',
|
||||
'PhabricatorRepositoryTransaction' => 'applications/repository/storage/PhabricatorRepositoryTransaction.php',
|
||||
'PhabricatorRepositoryTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryTransactionQuery.php',
|
||||
|
@ -6364,6 +6370,9 @@ phutil_register_library_map(array(
|
|||
'DiffusionSymbolController' => 'DiffusionController',
|
||||
'DiffusionSymbolDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'DiffusionSymbolQuery' => 'PhabricatorOffsetPagedQuery',
|
||||
'DiffusionSyncLogListController' => 'DiffusionLogController',
|
||||
'DiffusionSyncLogListView' => 'AphrontView',
|
||||
'DiffusionSyncLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'DiffusionTagListController' => 'DiffusionController',
|
||||
'DiffusionTagListView' => 'DiffusionView',
|
||||
'DiffusionTagTableView' => 'DiffusionView',
|
||||
|
@ -7131,7 +7140,10 @@ phutil_register_library_map(array(
|
|||
'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'LegalpadTransactionView' => 'PhabricatorApplicationTransactionView',
|
||||
'LiskChunkTestCase' => 'PhabricatorTestCase',
|
||||
'LiskDAO' => 'Phobject',
|
||||
'LiskDAO' => array(
|
||||
'Phobject',
|
||||
'AphrontDatabaseTableRefInterface',
|
||||
),
|
||||
'LiskDAOSet' => 'Phobject',
|
||||
'LiskDAOTestCase' => 'PhabricatorTestCase',
|
||||
'LiskEphemeralObjectException' => 'Exception',
|
||||
|
@ -10108,6 +10120,12 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositorySvnCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
|
||||
'PhabricatorRepositorySvnCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
|
||||
'PhabricatorRepositorySymbol' => 'PhabricatorRepositoryDAO',
|
||||
'PhabricatorRepositorySyncEvent' => array(
|
||||
'PhabricatorRepositoryDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
),
|
||||
'PhabricatorRepositorySyncEventPHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorRepositorySyncEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorRepositoryTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorRepositoryTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
|
|
|
@ -63,7 +63,7 @@ final class AlmanacKeys extends Phobject {
|
|||
// protocol does not have a mechanism like a "Host" header.
|
||||
$username = PhabricatorEnv::getEnvConfig('cluster.instance');
|
||||
if (strlen($username)) {
|
||||
return $username;
|
||||
// return $username;
|
||||
}
|
||||
|
||||
$username = PhabricatorEnv::getEnvConfig('diffusion.ssh-user');
|
||||
|
|
|
@ -99,6 +99,82 @@ final class PhabricatorAuthPasswordTestCase extends PhabricatorTestCase {
|
|||
$this->assertTrue($account_engine->isUniquePassword($password2));
|
||||
}
|
||||
|
||||
public function testPasswordBlocklisting() {
|
||||
$user = $this->generateNewTestUser();
|
||||
|
||||
$user
|
||||
->setUsername('iasimov')
|
||||
->setRealName('Isaac Asimov')
|
||||
->save();
|
||||
|
||||
$test_type = PhabricatorAuthPassword::PASSWORD_TYPE_TEST;
|
||||
$content_source = $this->newContentSource();
|
||||
|
||||
$engine = id(new PhabricatorAuthPasswordEngine())
|
||||
->setViewer($user)
|
||||
->setContentSource($content_source)
|
||||
->setPasswordType($test_type)
|
||||
->setObject($user);
|
||||
|
||||
$env = PhabricatorEnv::beginScopedEnv();
|
||||
$env->overrideEnvConfig('account.minimum-password-length', 4);
|
||||
|
||||
$passwords = array(
|
||||
'a23li432m9mdf' => true,
|
||||
|
||||
// Empty.
|
||||
'' => false,
|
||||
|
||||
// Password length tests.
|
||||
'xh3' => false,
|
||||
'xh32' => true,
|
||||
|
||||
// In common password blocklist.
|
||||
'password1' => false,
|
||||
|
||||
// Tests for the account identifier blocklist.
|
||||
'isaac' => false,
|
||||
'iasimov' => false,
|
||||
'iasimov1' => false,
|
||||
'asimov' => false,
|
||||
'iSaAc' => false,
|
||||
'32IASIMOV' => false,
|
||||
'i-am-iasimov-this-is-my-long-strong-password' => false,
|
||||
'iasimo' => false,
|
||||
|
||||
// These are okay: although they're visually similar, they aren't mutual
|
||||
// substrings of any identifier.
|
||||
'iasimo1' => true,
|
||||
'isa1mov' => true,
|
||||
);
|
||||
|
||||
foreach ($passwords as $password => $expect) {
|
||||
$this->assertBlocklistedPassword($engine, $password, $expect);
|
||||
}
|
||||
}
|
||||
|
||||
private function assertBlocklistedPassword(
|
||||
PhabricatorAuthPasswordEngine $engine,
|
||||
$raw_password,
|
||||
$expect_valid) {
|
||||
|
||||
$envelope_1 = new PhutilOpaqueEnvelope($raw_password);
|
||||
$envelope_2 = new PhutilOpaqueEnvelope($raw_password);
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
$engine->checkNewPassword($envelope_1, $envelope_2);
|
||||
} catch (PhabricatorAuthPasswordException $exception) {
|
||||
$caught = $exception;
|
||||
}
|
||||
|
||||
$this->assertEqual(
|
||||
$expect_valid,
|
||||
!($caught instanceof PhabricatorAuthPasswordException),
|
||||
pht('Validity of password "%s".', $raw_password));
|
||||
}
|
||||
|
||||
|
||||
public function testPasswordUpgrade() {
|
||||
$weak_hasher = new PhabricatorIteratedMD5PasswordHasher();
|
||||
|
||||
|
|
|
@ -115,7 +115,9 @@ final class PhabricatorAuthPasswordEngine
|
|||
// revoked passwords or colliding passwords either, so we can skip these
|
||||
// checks.
|
||||
|
||||
if ($this->getObject()->getPHID()) {
|
||||
$object = $this->getObject();
|
||||
|
||||
if ($object->getPHID()) {
|
||||
if ($this->isRevokedPassword($password)) {
|
||||
throw new PhabricatorAuthPasswordException(
|
||||
pht(
|
||||
|
@ -132,6 +134,66 @@ final class PhabricatorAuthPasswordEngine
|
|||
pht('Not Unique'));
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent use of passwords which are similar to any object identifier.
|
||||
// For example, if your username is "alincoln", your password may not be
|
||||
// "alincoln", "lincoln", or "alincoln1".
|
||||
$viewer = $this->getViewer();
|
||||
$blocklist = $object->newPasswordBlocklist($viewer, $this);
|
||||
|
||||
// Smallest number of overlapping characters that we'll consider to be
|
||||
// too similar.
|
||||
$minimum_similarity = 4;
|
||||
|
||||
// Add the domain name to the blocklist.
|
||||
$base_uri = PhabricatorEnv::getAnyBaseURI();
|
||||
$base_uri = new PhutilURI($base_uri);
|
||||
$blocklist[] = $base_uri->getDomain();
|
||||
|
||||
// Generate additional subterms by splitting the raw blocklist on
|
||||
// characters like "@", " " (space), and "." to break up email addresses,
|
||||
// readable names, and domain names into components.
|
||||
$terms_map = array();
|
||||
foreach ($blocklist as $term) {
|
||||
$terms_map[$term] = $term;
|
||||
foreach (preg_split('/[ @.]/', $term) as $subterm) {
|
||||
$terms_map[$subterm] = $term;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip very short terms: it's okay if your password has the substring
|
||||
// "com" in it somewhere even if the install is on "mycompany.com".
|
||||
foreach ($terms_map as $term => $source) {
|
||||
if (strlen($term) < $minimum_similarity) {
|
||||
unset($terms_map[$term]);
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize terms for comparison.
|
||||
$normal_map = array();
|
||||
foreach ($terms_map as $term => $source) {
|
||||
$term = phutil_utf8_strtolower($term);
|
||||
$normal_map[$term] = $source;
|
||||
}
|
||||
|
||||
// Finally, make sure that none of the terms appear in the password,
|
||||
// and that the password does not appear in any of the terms.
|
||||
$normal_password = phutil_utf8_strtolower($raw_password);
|
||||
if (strlen($normal_password) >= $minimum_similarity) {
|
||||
foreach ($normal_map as $term => $source) {
|
||||
if (strpos($term, $normal_password) === false &&
|
||||
strpos($normal_password, $term) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new PhabricatorAuthPasswordException(
|
||||
pht(
|
||||
'The password you entered is very similar to a nonsecret account '.
|
||||
'identifier (like a username or email address). Choose a more '.
|
||||
'distinct password.'),
|
||||
pht('Not Distinct'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function isValidPassword(PhutilOpaqueEnvelope $envelope) {
|
||||
|
|
|
@ -185,7 +185,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
|
|||
|
||||
|
||||
public static function generateNewTOTPKey() {
|
||||
return strtoupper(Filesystem::readRandomCharacters(16));
|
||||
return strtoupper(Filesystem::readRandomCharacters(32));
|
||||
}
|
||||
|
||||
public static function verifyTOTPCode(
|
||||
|
|
|
@ -6,4 +6,22 @@ interface PhabricatorAuthPasswordHashInterface {
|
|||
PhutilOpaqueEnvelope $envelope,
|
||||
PhabricatorAuthPassword $password);
|
||||
|
||||
/**
|
||||
* Return a list of strings which passwords associated with this object may
|
||||
* not be similar to.
|
||||
*
|
||||
* This method allows you to prevent users from selecting their username
|
||||
* as their password or picking other passwords which are trivially similar
|
||||
* to an account or object identifier.
|
||||
*
|
||||
* @param PhabricatorUser The user selecting the password.
|
||||
* @param PhabricatorAuthPasswordEngine The password engine updating a
|
||||
* password.
|
||||
* @return list<string> Blocklist of nonsecret identifiers which the password
|
||||
* should not be similar to.
|
||||
*/
|
||||
public function newPasswordBlocklist(
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorAuthPasswordEngine $engine);
|
||||
|
||||
}
|
||||
|
|
|
@ -109,15 +109,13 @@ final class PhabricatorConduitAPIController
|
|||
$error_info = $ex->getMessage();
|
||||
}
|
||||
|
||||
$time_end = microtime(true);
|
||||
|
||||
$log
|
||||
->setCallerPHID(
|
||||
isset($conduit_user)
|
||||
? $conduit_user->getPHID()
|
||||
: null)
|
||||
->setError((string)$error_code)
|
||||
->setDuration(1000000 * ($time_end - $time_start));
|
||||
->setDuration(phutil_microseconds_since($time_start));
|
||||
|
||||
if (!PhabricatorEnv::isReadOnly()) {
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
|
|
|
@ -73,15 +73,13 @@ final class ConduitSSHWorkflow extends PhabricatorSSHWorkflow {
|
|||
// if the response is large and the receiver is slow to read it.
|
||||
$this->getIOChannel()->flush();
|
||||
|
||||
$time_end = microtime(true);
|
||||
|
||||
$connection_id = idx($metadata, 'connectionID');
|
||||
$log = id(new PhabricatorConduitMethodCallLog())
|
||||
->setCallerPHID($this->getSSHUser()->getPHID())
|
||||
->setConnectionID($connection_id)
|
||||
->setMethod($method)
|
||||
->setError((string)$error_code)
|
||||
->setDuration(1000000 * ($time_end - $time_start))
|
||||
->setDuration(phutil_microseconds_since($time_start))
|
||||
->save();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,6 +118,9 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
|
|||
$this->getQueryRoutePattern() => 'DiffusionPushLogListController',
|
||||
'view/(?P<id>\d+)/' => 'DiffusionPushEventViewController',
|
||||
),
|
||||
'synclog/' => array(
|
||||
$this->getQueryRoutePattern() => 'DiffusionSyncLogListController',
|
||||
),
|
||||
'pulllog/' => array(
|
||||
$this->getQueryRoutePattern() => 'DiffusionPullLogListController',
|
||||
),
|
||||
|
|
|
@ -370,8 +370,17 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
$action_view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('View Push Logs'))
|
||||
->setIcon('fa-list-alt')
|
||||
->setIcon('fa-upload')
|
||||
->setHref($push_uri));
|
||||
|
||||
$pull_uri = $this->getApplicationURI(
|
||||
'synclog/?repositories='.$repository->getPHID());
|
||||
|
||||
$action_view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('View Sync Logs'))
|
||||
->setIcon('fa-exchange')
|
||||
->setHref($pull_uri));
|
||||
}
|
||||
|
||||
$pull_uri = $this->getApplicationURI(
|
||||
|
@ -380,7 +389,7 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
$action_view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('View Pull Logs'))
|
||||
->setIcon('fa-list-alt')
|
||||
->setIcon('fa-download')
|
||||
->setHref($pull_uri));
|
||||
|
||||
return $action_view;
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionSyncLogListController
|
||||
extends DiffusionLogController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
return id(new DiffusionSyncLogSearchEngine())
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
return parent::buildApplicationCrumbs()
|
||||
->addTextCrumb(pht('Sync Logs'), $this->getApplicationURI('synclog/'));
|
||||
}
|
||||
|
||||
}
|
|
@ -31,6 +31,7 @@ final class DiffusionCommitHookEngine extends Phobject {
|
|||
private $mercurialHook;
|
||||
private $mercurialCommits = array();
|
||||
private $gitCommits = array();
|
||||
private $startTime;
|
||||
|
||||
private $heraldViewerProjects;
|
||||
private $rejectCode = PhabricatorRepositoryPushLog::REJECT_BROKEN;
|
||||
|
@ -70,6 +71,15 @@ final class DiffusionCommitHookEngine extends Phobject {
|
|||
return $this->requestIdentifier;
|
||||
}
|
||||
|
||||
public function setStartTime($start_time) {
|
||||
$this->startTime = $start_time;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStartTime() {
|
||||
return $this->startTime;
|
||||
}
|
||||
|
||||
public function setSubversionTransactionInfo($transaction, $repository) {
|
||||
$this->subversionTransaction = $transaction;
|
||||
$this->subversionRepository = $repository;
|
||||
|
@ -1102,11 +1112,14 @@ final class DiffusionCommitHookEngine extends Phobject {
|
|||
private function newPushEvent() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$hook_start = $this->getStartTime();
|
||||
|
||||
$event = PhabricatorRepositoryPushEvent::initializeNewEvent($viewer)
|
||||
->setRepositoryPHID($this->getRepository()->getPHID())
|
||||
->setRemoteAddress($this->getRemoteAddress())
|
||||
->setRemoteProtocol($this->getRemoteProtocol())
|
||||
->setEpoch(PhabricatorTime::getNow());
|
||||
->setEpoch(PhabricatorTime::getNow())
|
||||
->setHookWait(phutil_microseconds_since($hook_start));
|
||||
|
||||
$identifier = $this->getRequestIdentifier();
|
||||
if (strlen($identifier)) {
|
||||
|
|
|
@ -206,7 +206,10 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
|
|||
}
|
||||
}
|
||||
|
||||
$this->synchronizeWorkingCopyFromDevices($fetchable);
|
||||
$this->synchronizeWorkingCopyFromDevices(
|
||||
$fetchable,
|
||||
$this_version,
|
||||
$max_version);
|
||||
} else {
|
||||
$this->synchronizeWorkingCopyFromRemote();
|
||||
}
|
||||
|
@ -407,8 +410,8 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
|
|||
|
||||
$log = $this->logger;
|
||||
if ($log) {
|
||||
$log->writeClusterEngineLogProperty('readWait', $read_wait);
|
||||
$log->writeClusterEngineLogProperty('writeWait', $write_wait);
|
||||
$log->writeClusterEngineLogProperty('readWait', $read_wait);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -653,7 +656,11 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
|
|||
/**
|
||||
* @task internal
|
||||
*/
|
||||
private function synchronizeWorkingCopyFromDevices(array $device_phids) {
|
||||
private function synchronizeWorkingCopyFromDevices(
|
||||
array $device_phids,
|
||||
$local_version,
|
||||
$remote_version) {
|
||||
|
||||
$repository = $this->getRepository();
|
||||
|
||||
$service = $repository->loadAlmanacService();
|
||||
|
@ -694,7 +701,10 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
|
|||
$caught = null;
|
||||
foreach ($fetchable as $binding) {
|
||||
try {
|
||||
$this->synchronizeWorkingCopyFromBinding($binding);
|
||||
$this->synchronizeWorkingCopyFromBinding(
|
||||
$binding,
|
||||
$local_version,
|
||||
$remote_version);
|
||||
$caught = null;
|
||||
break;
|
||||
} catch (Exception $ex) {
|
||||
|
@ -711,14 +721,17 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
|
|||
/**
|
||||
* @task internal
|
||||
*/
|
||||
private function synchronizeWorkingCopyFromBinding($binding) {
|
||||
private function synchronizeWorkingCopyFromBinding(
|
||||
AlmanacBinding $binding,
|
||||
$local_version,
|
||||
$remote_version) {
|
||||
|
||||
$repository = $this->getRepository();
|
||||
$device = AlmanacKeys::getLiveDevice();
|
||||
|
||||
$this->logLine(
|
||||
pht(
|
||||
'Synchronizing this device ("%s") from cluster leader ("%s") before '.
|
||||
'read.',
|
||||
'Synchronizing this device ("%s") from cluster leader ("%s").',
|
||||
$device->getName(),
|
||||
$binding->getDevice()->getName()));
|
||||
|
||||
|
@ -746,17 +759,57 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
|
|||
|
||||
$future->setCWD($local_path);
|
||||
|
||||
$log = PhabricatorRepositorySyncEvent::initializeNewEvent()
|
||||
->setRepositoryPHID($repository->getPHID())
|
||||
->setEpoch(PhabricatorTime::getNow())
|
||||
->setDevicePHID($device->getPHID())
|
||||
->setFromDevicePHID($binding->getDevice()->getPHID())
|
||||
->setDeviceVersion($local_version)
|
||||
->setFromDeviceVersion($remote_version);
|
||||
|
||||
$sync_start = microtime(true);
|
||||
|
||||
try {
|
||||
$future->resolvex();
|
||||
} catch (Exception $ex) {
|
||||
$log->setSyncWait(phutil_microseconds_since($sync_start));
|
||||
|
||||
if ($ex instanceof CommandException) {
|
||||
if ($future->getWasKilledByTimeout()) {
|
||||
$result_type = PhabricatorRepositorySyncEvent::RESULT_TIMEOUT;
|
||||
} else {
|
||||
$result_type = PhabricatorRepositorySyncEvent::RESULT_ERROR;
|
||||
}
|
||||
|
||||
$log
|
||||
->setResultCode($ex->getError())
|
||||
->setResultType($result_type)
|
||||
->setProperty('stdout', $ex->getStdout())
|
||||
->setProperty('stderr', $ex->getStderr());
|
||||
} else {
|
||||
$log
|
||||
->setResultCode(1)
|
||||
->setResultType(PhabricatorRepositorySyncEvent::RESULT_EXCEPTION)
|
||||
->setProperty('message', $ex->getMessage());
|
||||
}
|
||||
|
||||
$log->save();
|
||||
|
||||
$this->logLine(
|
||||
pht(
|
||||
'Synchronization of "%s" from leader "%s" failed: %s',
|
||||
$device->getName(),
|
||||
$binding->getDevice()->getName(),
|
||||
$ex->getMessage()));
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$log
|
||||
->setSyncWait(phutil_microseconds_since($sync_start))
|
||||
->setResultCode(0)
|
||||
->setResultType(PhabricatorRepositorySyncEvent::RESULT_SYNC)
|
||||
->save();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionSyncLogSearchEngine
|
||||
extends PhabricatorApplicationSearchEngine {
|
||||
|
||||
public function getResultTypeDescription() {
|
||||
return pht('Sync Logs');
|
||||
}
|
||||
|
||||
public function getApplicationClassName() {
|
||||
return 'PhabricatorDiffusionApplication';
|
||||
}
|
||||
|
||||
public function newQuery() {
|
||||
return new PhabricatorRepositorySyncEventQuery();
|
||||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
|
||||
if ($map['repositoryPHIDs']) {
|
||||
$query->withRepositoryPHIDs($map['repositoryPHIDs']);
|
||||
}
|
||||
|
||||
if ($map['createdStart'] || $map['createdEnd']) {
|
||||
$query->withEpochBetween(
|
||||
$map['createdStart'],
|
||||
$map['createdEnd']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function buildCustomSearchFields() {
|
||||
return array(
|
||||
id(new PhabricatorSearchDatasourceField())
|
||||
->setDatasource(new DiffusionRepositoryDatasource())
|
||||
->setKey('repositoryPHIDs')
|
||||
->setAliases(array('repository', 'repositories', 'repositoryPHID'))
|
||||
->setLabel(pht('Repositories'))
|
||||
->setDescription(
|
||||
pht('Search for sync logs for specific repositories.')),
|
||||
id(new PhabricatorSearchDateField())
|
||||
->setLabel(pht('Created After'))
|
||||
->setKey('createdStart'),
|
||||
id(new PhabricatorSearchDateField())
|
||||
->setLabel(pht('Created Before'))
|
||||
->setKey('createdEnd'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function newExportFields() {
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$fields = array(
|
||||
id(new PhabricatorPHIDExportField())
|
||||
->setKey('repositoryPHID')
|
||||
->setLabel(pht('Repository PHID')),
|
||||
id(new PhabricatorStringExportField())
|
||||
->setKey('repository')
|
||||
->setLabel(pht('Repository')),
|
||||
id(new PhabricatorPHIDExportField())
|
||||
->setKey('devicePHID')
|
||||
->setLabel(pht('Device PHID')),
|
||||
id(new PhabricatorPHIDExportField())
|
||||
->setKey('fromDevicePHID')
|
||||
->setLabel(pht('From Device PHID')),
|
||||
id(new PhabricatorIntExportField())
|
||||
->setKey('deviceVersion')
|
||||
->setLabel(pht('Device Version')),
|
||||
id(new PhabricatorIntExportField())
|
||||
->setKey('fromDeviceVersion')
|
||||
->setLabel(pht('From Device Version')),
|
||||
id(new PhabricatorStringExportField())
|
||||
->setKey('result')
|
||||
->setLabel(pht('Result')),
|
||||
id(new PhabricatorIntExportField())
|
||||
->setKey('code')
|
||||
->setLabel(pht('Code')),
|
||||
id(new PhabricatorEpochExportField())
|
||||
->setKey('date')
|
||||
->setLabel(pht('Date')),
|
||||
id(new PhabricatorIntExportField())
|
||||
->setKey('syncWait')
|
||||
->setLabel(pht('Sync Wait')),
|
||||
);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
protected function newExportData(array $events) {
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$export = array();
|
||||
foreach ($events as $event) {
|
||||
$repository = $event->getRepository();
|
||||
$repository_phid = $repository->getPHID();
|
||||
$repository_name = $repository->getDisplayName();
|
||||
|
||||
$map = array(
|
||||
'repositoryPHID' => $repository_phid,
|
||||
'repository' => $repository_name,
|
||||
'devicePHID' => $event->getDevicePHID(),
|
||||
'fromDevicePHID' => $event->getFromDevicePHID(),
|
||||
'deviceVersion' => $event->getDeviceVersion(),
|
||||
'fromDeviceVersion' => $event->getFromDeviceVersion(),
|
||||
'result' => $event->getResultType(),
|
||||
'code' => $event->getResultCode(),
|
||||
'date' => $event->getEpoch(),
|
||||
'syncWait' => $event->getSyncWait(),
|
||||
);
|
||||
|
||||
$export[] = $map;
|
||||
}
|
||||
|
||||
return $export;
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
return '/diffusion/synclog/'.$path;
|
||||
}
|
||||
|
||||
protected function getBuiltinQueryNames() {
|
||||
return array(
|
||||
'all' => pht('All Sync Logs'),
|
||||
);
|
||||
}
|
||||
|
||||
public function buildSavedQueryFromBuiltin($query_key) {
|
||||
$query = $this->newSavedQuery();
|
||||
$query->setQueryKey($query_key);
|
||||
|
||||
switch ($query_key) {
|
||||
case 'all':
|
||||
return $query;
|
||||
}
|
||||
|
||||
return parent::buildSavedQueryFromBuiltin($query_key);
|
||||
}
|
||||
|
||||
protected function renderResultList(
|
||||
array $logs,
|
||||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
|
||||
$table = id(new DiffusionSyncLogListView())
|
||||
->setViewer($this->requireViewer())
|
||||
->setLogs($logs);
|
||||
|
||||
return id(new PhabricatorApplicationSearchResultView())
|
||||
->setTable($table);
|
||||
}
|
||||
|
||||
}
|
|
@ -30,7 +30,7 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||
|
||||
if ($this->shouldProxy()) {
|
||||
$command = $this->getProxyCommand(true);
|
||||
$did_synchronize = false;
|
||||
$did_write = false;
|
||||
|
||||
if ($device) {
|
||||
$this->writeClusterEngineLogMessage(
|
||||
|
@ -40,7 +40,7 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||
}
|
||||
} else {
|
||||
$command = csprintf('git-receive-pack %s', $repository->getLocalPath());
|
||||
$did_synchronize = true;
|
||||
$did_write = true;
|
||||
$cluster_engine->synchronizeWorkingCopyBeforeWrite();
|
||||
|
||||
if ($device) {
|
||||
|
@ -60,7 +60,7 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||
|
||||
// We've committed the write (or rejected it), so we can release the lock
|
||||
// without waiting for the client to receive the acknowledgement.
|
||||
if ($did_synchronize) {
|
||||
if ($did_write) {
|
||||
$cluster_engine->synchronizeWorkingCopyAfterWrite();
|
||||
}
|
||||
|
||||
|
@ -69,18 +69,23 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||
}
|
||||
|
||||
if (!$err) {
|
||||
$repository->writeStatusMessage(
|
||||
PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
|
||||
PhabricatorRepositoryStatusMessage::CODE_OKAY);
|
||||
$this->waitForGitClient();
|
||||
|
||||
$host_wait_end = microtime(true);
|
||||
// When a repository is clustered, we reach this cleanup code on both
|
||||
// the proxy and the actual final endpoint node. Don't do more cleanup
|
||||
// or logging than we need to.
|
||||
if ($did_write) {
|
||||
$repository->writeStatusMessage(
|
||||
PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
|
||||
PhabricatorRepositoryStatusMessage::CODE_OKAY);
|
||||
|
||||
$this->updatePushLogWithTimingInformation(
|
||||
$this->getClusterEngineLogProperty('writeWait'),
|
||||
$this->getClusterEngineLogProperty('readWait'),
|
||||
($host_wait_end - $host_wait_start));
|
||||
$host_wait_end = microtime(true);
|
||||
|
||||
$this->updatePushLogWithTimingInformation(
|
||||
$this->getClusterEngineLogProperty('writeWait'),
|
||||
$this->getClusterEngineLogProperty('readWait'),
|
||||
($host_wait_end - $host_wait_start));
|
||||
}
|
||||
}
|
||||
|
||||
return $err;
|
||||
|
|
|
@ -41,9 +41,10 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
$any_host = false;
|
||||
foreach ($logs as $log) {
|
||||
$repository = $log->getRepository();
|
||||
$event = $log->getPushEvent();
|
||||
|
||||
if ($remotes_visible) {
|
||||
$remote_address = $log->getPushEvent()->getRemoteAddress();
|
||||
$remote_address = $event->getRemoteAddress();
|
||||
} else {
|
||||
$remote_address = null;
|
||||
}
|
||||
|
@ -79,10 +80,10 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
phutil_tag('br'),
|
||||
$flag_names);
|
||||
|
||||
$reject_code = $log->getPushEvent()->getRejectCode();
|
||||
$reject_code = $event->getRejectCode();
|
||||
|
||||
if ($reject_code == $reject_herald) {
|
||||
$rule_phid = $log->getPushEvent()->getRejectDetails();
|
||||
$rule_phid = $event->getRejectDetails();
|
||||
$handle = $viewer->renderHandle($rule_phid);
|
||||
$reject_label = pht('Blocked: %s', $handle);
|
||||
} else {
|
||||
|
@ -92,6 +93,11 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
pht('Unknown ("%s")', $reject_code));
|
||||
}
|
||||
|
||||
$host_wait = $this->formatMicroseconds($event->getHostWait());
|
||||
$write_wait = $this->formatMicroseconds($event->getWriteWait());
|
||||
$read_wait = $this->formatMicroseconds($event->getReadWait());
|
||||
$hook_wait = $this->formatMicroseconds($event->getHookWait());
|
||||
|
||||
$rows[] = array(
|
||||
phutil_tag(
|
||||
'a',
|
||||
|
@ -107,7 +113,7 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
$repository->getDisplayName()),
|
||||
$viewer->renderHandle($log->getPusherPHID()),
|
||||
$remote_address,
|
||||
$log->getPushEvent()->getRemoteProtocol(),
|
||||
$event->getRemoteProtocol(),
|
||||
$device,
|
||||
$log->getRefType(),
|
||||
$log->getRefName(),
|
||||
|
@ -121,6 +127,10 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
$flag_names,
|
||||
$reject_label,
|
||||
$viewer->formatShortDateTime($log->getEpoch()),
|
||||
$host_wait,
|
||||
$write_wait,
|
||||
$read_wait,
|
||||
$hook_wait,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -140,6 +150,10 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
pht('Flags'),
|
||||
pht('Result'),
|
||||
pht('Date'),
|
||||
pht('Host Wait'),
|
||||
pht('Write Wait'),
|
||||
pht('Read Wait'),
|
||||
pht('Hook Wait'),
|
||||
))
|
||||
->setColumnClasses(
|
||||
array(
|
||||
|
@ -156,6 +170,10 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
'',
|
||||
'',
|
||||
'right',
|
||||
'n right',
|
||||
'n right',
|
||||
'n right',
|
||||
'n right',
|
||||
))
|
||||
->setColumnVisibility(
|
||||
array(
|
||||
|
@ -170,4 +188,12 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
return $table;
|
||||
}
|
||||
|
||||
private function formatMicroseconds($duration) {
|
||||
if ($duration === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return pht('%sus', new PhutilNumber($duration));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
79
src/applications/diffusion/view/DiffusionSyncLogListView.php
Normal file
79
src/applications/diffusion/view/DiffusionSyncLogListView.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionSyncLogListView extends AphrontView {
|
||||
|
||||
private $logs;
|
||||
|
||||
public function setLogs(array $logs) {
|
||||
assert_instances_of($logs, 'PhabricatorRepositorySyncEvent');
|
||||
$this->logs = $logs;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$events = $this->logs;
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$rows = array();
|
||||
foreach ($events as $event) {
|
||||
$repository = $event->getRepository();
|
||||
$repository_link = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $repository->getURI(),
|
||||
),
|
||||
$repository->getDisplayName());
|
||||
|
||||
$event_id = $event->getID();
|
||||
|
||||
$sync_wait = pht('%sus', new PhutilNumber($event->getSyncWait()));
|
||||
|
||||
$device_link = $viewer->renderHandle($event->getDevicePHID());
|
||||
$from_device_link = $viewer->renderHandle($event->getFromDevicePHID());
|
||||
|
||||
$rows[] = array(
|
||||
$event_id,
|
||||
$repository_link,
|
||||
$device_link,
|
||||
$from_device_link,
|
||||
$event->getDeviceVersion(),
|
||||
$event->getFromDeviceVersion(),
|
||||
$event->getResultType(),
|
||||
$event->getResultCode(),
|
||||
phabricator_datetime($event->getEpoch(), $viewer),
|
||||
$sync_wait,
|
||||
);
|
||||
}
|
||||
|
||||
$table = id(new AphrontTableView($rows))
|
||||
->setHeaders(
|
||||
array(
|
||||
pht('Sync'),
|
||||
pht('Repository'),
|
||||
pht('Device'),
|
||||
pht('From Device'),
|
||||
pht('Version'),
|
||||
pht('From Version'),
|
||||
pht('Result'),
|
||||
pht('Code'),
|
||||
pht('Date'),
|
||||
pht('Sync Wait'),
|
||||
))
|
||||
->setColumnClasses(
|
||||
array(
|
||||
'n',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'n',
|
||||
'n',
|
||||
'wide right',
|
||||
'n',
|
||||
'right',
|
||||
'n right',
|
||||
));
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
}
|
|
@ -30,8 +30,8 @@ final class DrydockSSHCommandInterface extends DrydockCommandInterface {
|
|||
$full_command = call_user_func_array('csprintf', $argv);
|
||||
|
||||
$flags = array();
|
||||
$flags[] = '-o';
|
||||
$flags[] = 'LogLevel=quiet';
|
||||
// $flags[] = '-o';
|
||||
// $flags[] = 'LogLevel=quiet';
|
||||
|
||||
$flags[] = '-o';
|
||||
$flags[] = 'StrictHostKeyChecking=no';
|
||||
|
|
|
@ -174,6 +174,15 @@ final class PhabricatorMemeEngine extends Phobject {
|
|||
private function newAssetData(PhabricatorFile $template) {
|
||||
$template_data = $template->loadFileData();
|
||||
|
||||
// When we aren't adding text, just return the data unmodified. This saves
|
||||
// us from doing expensive stitching when we aren't actually making any
|
||||
// changes to the image.
|
||||
$above_text = $this->getAboveText();
|
||||
$below_text = $this->getBelowText();
|
||||
if (!strlen(trim($above_text)) && !strlen(trim($below_text))) {
|
||||
return $template_data;
|
||||
}
|
||||
|
||||
$result = $this->newImagemagickAsset($template, $template_data);
|
||||
if ($result) {
|
||||
return $result;
|
||||
|
|
|
@ -290,7 +290,7 @@ final class ManiphestTransactionEditor
|
|||
$copy->setOwnerPHID($xaction->getNewValue());
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1665,5 +1665,23 @@ final class PhabricatorUser
|
|||
return new PhutilOpaqueEnvelope($digest);
|
||||
}
|
||||
|
||||
public function newPasswordBlocklist(
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorAuthPasswordEngine $engine) {
|
||||
|
||||
$list = array();
|
||||
$list[] = $this->getUsername();
|
||||
$list[] = $this->getRealName();
|
||||
|
||||
$emails = id(new PhabricatorUserEmail())->loadAllWhere(
|
||||
'userPHID = %s',
|
||||
$this->getPHID());
|
||||
foreach ($emails as $email) {
|
||||
$list[] = $email->getAddress();
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -120,33 +120,71 @@ final class PhabricatorRepositoryManagementThawWorkflow
|
|||
$repository->getDisplayName()));
|
||||
}
|
||||
|
||||
$bindings = $service->getActiveBindings();
|
||||
$bindings = mpull($bindings, null, 'getDevicePHID');
|
||||
if (empty($bindings[$device->getPHID()])) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Repository "%s" has no active binding to device "%s". Only '.
|
||||
'actively bound devices can be promoted or demoted.',
|
||||
$repository->getDisplayName(),
|
||||
$device->getName()));
|
||||
}
|
||||
|
||||
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
|
||||
$repository->getPHID());
|
||||
|
||||
$versions = mpull($versions, null, 'getDevicePHID');
|
||||
$versions = array_select_keys($versions, array_keys($bindings));
|
||||
|
||||
if ($versions && $promote) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Unable to promote "%s" for repository "%s": the leaders for '.
|
||||
'this cluster are not ambiguous.',
|
||||
$device->getName(),
|
||||
$repository->getDisplayName()));
|
||||
}
|
||||
|
||||
if ($promote) {
|
||||
// You can only promote active devices. (You may demote active or
|
||||
// inactive devices.)
|
||||
$bindings = $service->getActiveBindings();
|
||||
$bindings = mpull($bindings, null, 'getDevicePHID');
|
||||
if (empty($bindings[$device->getPHID()])) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Repository "%s" has no active binding to device "%s". Only '.
|
||||
'actively bound devices can be promoted.',
|
||||
$repository->getDisplayName(),
|
||||
$device->getName()));
|
||||
}
|
||||
|
||||
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
|
||||
$repository->getPHID());
|
||||
$versions = mpull($versions, null, 'getDevicePHID');
|
||||
|
||||
// Before we promote, make sure there are no outstanding versions on
|
||||
// devices with inactive bindings. If there are, you need to demote
|
||||
// these first.
|
||||
$inactive = array();
|
||||
foreach ($versions as $device_phid => $version) {
|
||||
if (isset($bindings[$device_phid])) {
|
||||
continue;
|
||||
}
|
||||
$inactive[$device_phid] = $version;
|
||||
}
|
||||
|
||||
if ($inactive) {
|
||||
$handles = $viewer->loadHandles(array_keys($inactive));
|
||||
|
||||
$handle_list = iterator_to_array($handles);
|
||||
$handle_list = mpull($handle_list, 'getName');
|
||||
$handle_list = implode(', ', $handle_list);
|
||||
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Repository "%s" has versions on inactive devices. Demote '.
|
||||
'(or reactivate) these devices before promoting a new '.
|
||||
'leader: %s.',
|
||||
$repository->getDisplayName(),
|
||||
$handle_list));
|
||||
}
|
||||
|
||||
// Now, make sure there are no outstanding versions on devices with
|
||||
// active bindings. These also need to be demoted (or promoting is a
|
||||
// mistake or already happened).
|
||||
$active = array_select_keys($versions, array_keys($bindings));
|
||||
if ($active) {
|
||||
$handles = $viewer->loadHandles(array_keys($active));
|
||||
|
||||
$handle_list = iterator_to_array($handles);
|
||||
$handle_list = mpull($handle_list, 'getName');
|
||||
$handle_list = implode(', ', $handle_list);
|
||||
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Unable to promote "%s" for repository "%s" because this '.
|
||||
'cluster already has one or more unambiguous leaders: %s.',
|
||||
$device->getName(),
|
||||
$repository->getDisplayName(),
|
||||
$handle_list));
|
||||
}
|
||||
|
||||
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
|
||||
$repository->getPHID(),
|
||||
$device->getPHID(),
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorRepositorySyncEventPHIDType extends PhabricatorPHIDType {
|
||||
|
||||
const TYPECONST = 'SYNE';
|
||||
|
||||
public function getTypeName() {
|
||||
return pht('Sync Event');
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new PhabricatorRepositorySyncEvent();
|
||||
}
|
||||
|
||||
public function getPHIDTypeApplicationClass() {
|
||||
return 'PhabricatorDiffusionApplication';
|
||||
}
|
||||
|
||||
protected function buildQueryForObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $phids) {
|
||||
|
||||
return id(new PhabricatorRepositorySyncEventQuery())
|
||||
->withPHIDs($phids);
|
||||
}
|
||||
|
||||
public function loadHandles(
|
||||
PhabricatorHandleQuery $query,
|
||||
array $handles,
|
||||
array $objects) {
|
||||
|
||||
foreach ($handles as $phid => $handle) {
|
||||
$event = $objects[$phid];
|
||||
|
||||
$handle->setName(pht('Sync Event %d', $event->getID()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -162,6 +162,9 @@ final class PhabricatorRepositoryPushLogSearchEngine
|
|||
id(new PhabricatorStringExportField())
|
||||
->setKey('resultDetails')
|
||||
->setLabel(pht('Result Details')),
|
||||
id(new PhabricatorIntExportField())
|
||||
->setKey('hostWait')
|
||||
->setLabel(pht('Host Wait (us)')),
|
||||
id(new PhabricatorIntExportField())
|
||||
->setKey('writeWait')
|
||||
->setLabel(pht('Write Wait (us)')),
|
||||
|
@ -169,8 +172,8 @@ final class PhabricatorRepositoryPushLogSearchEngine
|
|||
->setKey('readWait')
|
||||
->setLabel(pht('Read Wait (us)')),
|
||||
id(new PhabricatorIntExportField())
|
||||
->setKey('hostWait')
|
||||
->setLabel(pht('Host Wait (us)')),
|
||||
->setKey('hookWait')
|
||||
->setLabel(pht('Hook Wait (us)')),
|
||||
);
|
||||
|
||||
if ($viewer->getIsAdmin()) {
|
||||
|
@ -251,9 +254,10 @@ final class PhabricatorRepositoryPushLogSearchEngine
|
|||
'result' => $result,
|
||||
'resultName' => $result_name,
|
||||
'resultDetails' => $event->getRejectDetails(),
|
||||
'hostWait' => $event->getHostWait(),
|
||||
'writeWait' => $event->getWriteWait(),
|
||||
'readWait' => $event->getReadWait(),
|
||||
'hostWait' => $event->getHostWait(),
|
||||
'hookWait' => $event->getHookWait(),
|
||||
);
|
||||
|
||||
if ($viewer->getIsAdmin()) {
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorRepositorySyncEventQuery
|
||||
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $repositoryPHIDs;
|
||||
private $epochMin;
|
||||
private $epochMax;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withRepositoryPHIDs(array $repository_phids) {
|
||||
$this->repositoryPHIDs = $repository_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withEpochBetween($min, $max) {
|
||||
$this->epochMin = $min;
|
||||
$this->epochMax = $max;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new PhabricatorRepositorySyncEvent();
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
protected function willFilterPage(array $events) {
|
||||
$repository_phids = mpull($events, 'getRepositoryPHID');
|
||||
$repository_phids = array_filter($repository_phids);
|
||||
|
||||
if ($repository_phids) {
|
||||
$repositories = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withPHIDs($repository_phids)
|
||||
->execute();
|
||||
$repositories = mpull($repositories, null, 'getPHID');
|
||||
} else {
|
||||
$repositories = array();
|
||||
}
|
||||
|
||||
foreach ($events as $key => $event) {
|
||||
$phid = $event->getRepositoryPHID();
|
||||
|
||||
if (empty($repositories[$phid])) {
|
||||
unset($events[$key]);
|
||||
$this->didRejectResult($event);
|
||||
continue;
|
||||
}
|
||||
|
||||
$event->attachRepository($repositories[$phid]);
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->repositoryPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'repositoryPHID IN (%Ls)',
|
||||
$this->repositoryPHIDs);
|
||||
}
|
||||
|
||||
if ($this->epochMin !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'epoch >= %d',
|
||||
$this->epochMin);
|
||||
}
|
||||
|
||||
if ($this->epochMax !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'epoch <= %d',
|
||||
$this->epochMax);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorDiffusionApplication';
|
||||
}
|
||||
|
||||
}
|
|
@ -19,6 +19,7 @@ final class PhabricatorRepositoryPushEvent
|
|||
protected $writeWait;
|
||||
protected $readWait;
|
||||
protected $hostWait;
|
||||
protected $hookWait;
|
||||
|
||||
private $repository = self::ATTACHABLE;
|
||||
private $logs = self::ATTACHABLE;
|
||||
|
@ -41,6 +42,7 @@ final class PhabricatorRepositoryPushEvent
|
|||
'writeWait' => 'uint64?',
|
||||
'readWait' => 'uint64?',
|
||||
'hostWait' => 'uint64?',
|
||||
'hookWait' => 'uint64?',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_repository' => array(
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorRepositorySyncEvent
|
||||
extends PhabricatorRepositoryDAO
|
||||
implements PhabricatorPolicyInterface {
|
||||
|
||||
protected $repositoryPHID;
|
||||
protected $epoch;
|
||||
protected $devicePHID;
|
||||
protected $fromDevicePHID;
|
||||
protected $deviceVersion;
|
||||
protected $fromDeviceVersion;
|
||||
protected $resultType;
|
||||
protected $resultCode;
|
||||
protected $syncWait;
|
||||
protected $properties = array();
|
||||
|
||||
private $repository = self::ATTACHABLE;
|
||||
|
||||
const RESULT_SYNC = 'sync';
|
||||
const RESULT_ERROR = 'error';
|
||||
const RESULT_TIMEOUT = 'timeout';
|
||||
const RESULT_EXCEPTION = 'exception';
|
||||
|
||||
public static function initializeNewEvent() {
|
||||
return new self();
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_TIMESTAMPS => false,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'properties' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'deviceVersion' => 'uint32?',
|
||||
'fromDeviceVersion' => 'uint32?',
|
||||
'resultType' => 'text32',
|
||||
'resultCode' => 'uint32',
|
||||
'syncWait' => 'uint64',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_repository' => array(
|
||||
'columns' => array('repositoryPHID'),
|
||||
),
|
||||
'key_epoch' => array(
|
||||
'columns' => array('epoch'),
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function getPHIDType() {
|
||||
return PhabricatorRepositorySyncEventPHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public function attachRepository(PhabricatorRepository $repository) {
|
||||
$this->repository = $repository;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRepository() {
|
||||
return $this->assertAttached($this->repository);
|
||||
}
|
||||
|
||||
public function setProperty($key, $value) {
|
||||
$this->properties[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProperty($key, $default = null) {
|
||||
return idx($this->properties, $key, $default);
|
||||
}
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
return $this->getRepository()->getPolicy($capability);
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return $this->getRepository()->hasAutomaticCapability($capability, $viewer);
|
||||
}
|
||||
|
||||
public function describeAutomaticCapability($capability) {
|
||||
return pht(
|
||||
"A repository's sync events are visible to users who can see the ".
|
||||
"repository.");
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,10 @@ final class PhabricatorSlowvoteVoteController
|
|||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
if (!$request->isFormPost()) {
|
||||
return id(new Aphront404Response());
|
||||
}
|
||||
|
||||
$poll = id(new PhabricatorSlowvoteQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
|
@ -16,69 +20,36 @@ final class PhabricatorSlowvoteVoteController
|
|||
if (!$poll) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
if ($poll->getIsClosed()) {
|
||||
return new Aphront400Response();
|
||||
}
|
||||
|
||||
$options = $poll->getOptions();
|
||||
$viewer_choices = $poll->getViewerChoices($viewer);
|
||||
$options = mpull($options, null, 'getID');
|
||||
|
||||
$old_votes = mpull($viewer_choices, null, 'getOptionID');
|
||||
|
||||
if ($request->isAjax()) {
|
||||
$vote = $request->getInt('vote');
|
||||
$votes = array_keys($old_votes);
|
||||
$votes = array_fuse($votes);
|
||||
|
||||
if ($poll->getMethod() == PhabricatorSlowvotePoll::METHOD_PLURALITY) {
|
||||
if (idx($votes, $vote, false)) {
|
||||
$votes = array();
|
||||
} else {
|
||||
$votes = array($vote);
|
||||
}
|
||||
} else {
|
||||
if (idx($votes, $vote, false)) {
|
||||
unset($votes[$vote]);
|
||||
} else {
|
||||
$votes[$vote] = $vote;
|
||||
}
|
||||
}
|
||||
|
||||
$this->updateVotes($viewer, $poll, $old_votes, $votes);
|
||||
|
||||
$updated_choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere(
|
||||
'pollID = %d AND authorPHID = %s',
|
||||
$poll->getID(),
|
||||
$viewer->getPHID());
|
||||
|
||||
$embed = id(new SlowvoteEmbedView())
|
||||
->setPoll($poll)
|
||||
->setOptions($options)
|
||||
->setViewerChoices($updated_choices);
|
||||
|
||||
return id(new AphrontAjaxResponse())
|
||||
->setContent(array(
|
||||
'pollID' => $poll->getID(),
|
||||
'contentHTML' => $embed->render(),
|
||||
));
|
||||
}
|
||||
|
||||
if (!$request->isFormPost()) {
|
||||
return id(new Aphront404Response());
|
||||
}
|
||||
$old_votes = $poll->getViewerChoices($viewer);
|
||||
$old_votes = mpull($old_votes, null, 'getOptionID');
|
||||
|
||||
$votes = $request->getArr('vote');
|
||||
$votes = array_fuse($votes);
|
||||
|
||||
$this->updateVotes($viewer, $poll, $old_votes, $votes);
|
||||
$method = $poll->getMethod();
|
||||
$is_plurality = ($method == PhabricatorSlowvotePoll::METHOD_PLURALITY);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI('/V'.$poll->getID());
|
||||
}
|
||||
if ($is_plurality && count($votes) > 1) {
|
||||
throw new Exception(
|
||||
pht('In this poll, you may only vote for one option.'));
|
||||
}
|
||||
|
||||
private function updateVotes($viewer, $poll, $old_votes, $votes) {
|
||||
if (!empty($votes) && count($votes) > 1 &&
|
||||
$poll->getMethod() == PhabricatorSlowvotePoll::METHOD_PLURALITY) {
|
||||
return id(new Aphront400Response());
|
||||
foreach ($votes as $vote) {
|
||||
if (!isset($options[$vote])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Option ("%s") is not a valid poll option. You may only '.
|
||||
'vote for valid options.',
|
||||
$vote));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($old_votes as $old_vote) {
|
||||
|
@ -98,6 +69,9 @@ final class PhabricatorSlowvoteVoteController
|
|||
->setOptionID($vote)
|
||||
->save();
|
||||
}
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($poll->getURI());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,12 +39,6 @@ final class SlowvoteEmbedView extends AphrontView {
|
|||
}
|
||||
|
||||
require_celerity_resource('phabricator-slowvote-css');
|
||||
require_celerity_resource('javelin-behavior-slowvote-embed');
|
||||
|
||||
$config = array(
|
||||
'pollID' => $poll->getID(),
|
||||
);
|
||||
Javelin::initBehavior('slowvote-embed', $config);
|
||||
|
||||
$user_choices = $poll->getViewerChoices($this->getUser());
|
||||
$user_choices = mpull($user_choices, 'getOptionID', 'getOptionID');
|
||||
|
|
|
@ -414,28 +414,24 @@ present on the leaders but not present on the followers by examining the
|
|||
push logs.
|
||||
|
||||
If you are comfortable discarding these changes, you can instruct Phabricator
|
||||
that it can forget about the leaders in two ways: disable the service bindings
|
||||
to all of the leader devices so they are no longer part of the cluster, or use
|
||||
`bin/repository thaw` to `--demote` the leaders explicitly.
|
||||
that it can forget about the leaders by doing this:
|
||||
|
||||
If you do this, **you will lose data**. Either action will discard any changes
|
||||
on the affected leaders which have not replicated to other devices in the
|
||||
cluster.
|
||||
- Disable the service bindings to all of the leader devices so they are no
|
||||
longer part of the cluster.
|
||||
- Then, use `bin/repository thaw` to `--demote` the leaders explicitly.
|
||||
|
||||
To remove a device from the cluster, disable all of the bindings to it
|
||||
in Almanac, using the web UI.
|
||||
|
||||
{icon exclamation-triangle, color="red"} Any data which is only present on
|
||||
the disabled device will be lost.
|
||||
|
||||
To demote a device without removing it from the cluster, run this command:
|
||||
To demote a device, run this command:
|
||||
|
||||
```
|
||||
phabricator/ $ ./bin/repository thaw rXYZ --demote repo002.corp.net
|
||||
```
|
||||
|
||||
{icon exclamation-triangle, color="red"} Any data which is only present on
|
||||
**this** device will be lost.
|
||||
the demoted device will be lost.
|
||||
|
||||
If you do this, **you will lose unreplicated data**. You will discard any
|
||||
changes on the affected leaders which have not replicated to other devices
|
||||
in the cluster.
|
||||
|
||||
|
||||
Ambiguous Leaders
|
||||
|
|
|
@ -133,7 +133,7 @@ tricky because of shell escaping. The easiest way to do it is to use the
|
|||
Then set the value like this:
|
||||
|
||||
```
|
||||
phabricator/ $ ./bin/config set --stdin < mailers.json
|
||||
phabricator/ $ ./bin/config set --stdin cluster.mailers < mailers.json
|
||||
```
|
||||
|
||||
For alternatives and more information on configuration, see
|
||||
|
|
|
@ -169,8 +169,7 @@ final class PhabricatorWorkerActiveTask extends PhabricatorWorkerTask {
|
|||
|
||||
$t_start = microtime(true);
|
||||
$worker->executeTask();
|
||||
$t_end = microtime(true);
|
||||
$duration = (int)(1000000 * ($t_end - $t_start));
|
||||
$duration = phutil_microseconds_since($t_start);
|
||||
|
||||
$result = $this->archiveTask(
|
||||
PhabricatorWorkerArchiveTask::RESULT_SUCCESS,
|
||||
|
|
|
@ -3,23 +3,23 @@
|
|||
final class QueryFormattingTestCase extends PhabricatorTestCase {
|
||||
|
||||
public function testQueryFormatting() {
|
||||
$conn_r = id(new PhabricatorUser())->establishConnection('r');
|
||||
$conn = id(new PhabricatorUser())->establishConnection('r');
|
||||
|
||||
$this->assertEqual(
|
||||
'NULL',
|
||||
qsprintf($conn_r, '%nd', null));
|
||||
qsprintf($conn, '%nd', null));
|
||||
|
||||
$this->assertEqual(
|
||||
'0',
|
||||
qsprintf($conn_r, '%nd', 0));
|
||||
qsprintf($conn, '%nd', 0));
|
||||
|
||||
$this->assertEqual(
|
||||
'0',
|
||||
qsprintf($conn_r, '%d', 0));
|
||||
qsprintf($conn, '%d', 0));
|
||||
|
||||
$raised = null;
|
||||
try {
|
||||
qsprintf($conn_r, '%d', 'derp');
|
||||
qsprintf($conn, '%d', 'derp');
|
||||
} catch (Exception $ex) {
|
||||
$raised = $ex;
|
||||
}
|
||||
|
@ -29,27 +29,40 @@ final class QueryFormattingTestCase extends PhabricatorTestCase {
|
|||
|
||||
$this->assertEqual(
|
||||
"'<S>'",
|
||||
qsprintf($conn_r, '%s', null));
|
||||
qsprintf($conn, '%s', null));
|
||||
|
||||
$this->assertEqual(
|
||||
'NULL',
|
||||
qsprintf($conn_r, '%ns', null));
|
||||
qsprintf($conn, '%ns', null));
|
||||
|
||||
$this->assertEqual(
|
||||
"'<S>', '<S>'",
|
||||
qsprintf($conn_r, '%Ls', array('x', 'y')));
|
||||
qsprintf($conn, '%Ls', array('x', 'y')));
|
||||
|
||||
$this->assertEqual(
|
||||
"'<B>'",
|
||||
qsprintf($conn_r, '%B', null));
|
||||
qsprintf($conn, '%B', null));
|
||||
|
||||
$this->assertEqual(
|
||||
'NULL',
|
||||
qsprintf($conn_r, '%nB', null));
|
||||
qsprintf($conn, '%nB', null));
|
||||
|
||||
$this->assertEqual(
|
||||
"'<B>', '<B>'",
|
||||
qsprintf($conn_r, '%LB', array('x', 'y')));
|
||||
qsprintf($conn, '%LB', array('x', 'y')));
|
||||
|
||||
$this->assertEqual(
|
||||
'<C>',
|
||||
qsprintf($conn, '%T', 'x'));
|
||||
|
||||
$this->assertEqual(
|
||||
'<C>',
|
||||
qsprintf($conn, '%C', 'y'));
|
||||
|
||||
$this->assertEqual(
|
||||
'<C>.<C>',
|
||||
qsprintf($conn, '%R', new AphrontDatabaseTableRef('x', 'y')));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -162,7 +162,8 @@
|
|||
* @task xaction Managing Transactions
|
||||
* @task isolate Isolation for Unit Testing
|
||||
*/
|
||||
abstract class LiskDAO extends Phobject {
|
||||
abstract class LiskDAO extends Phobject
|
||||
implements AphrontDatabaseTableRefInterface {
|
||||
|
||||
const CONFIG_IDS = 'id-mechanism';
|
||||
const CONFIG_TIMESTAMPS = 'timestamps';
|
||||
|
@ -235,8 +236,11 @@ abstract class LiskDAO extends Phobject {
|
|||
* @return string Connection namespace for cache
|
||||
* @task conn
|
||||
*/
|
||||
abstract protected function getConnectionNamespace();
|
||||
protected function getConnectionNamespace() {
|
||||
return $this->getDatabaseName();
|
||||
}
|
||||
|
||||
abstract protected function getDatabaseName();
|
||||
|
||||
/**
|
||||
* Get an existing, cached connection for this object.
|
||||
|
@ -525,8 +529,8 @@ abstract class LiskDAO extends Phobject {
|
|||
$args = func_get_args();
|
||||
$args = array_slice($args, 1);
|
||||
|
||||
$pattern = 'SELECT * FROM %T WHERE '.$pattern.' %Q';
|
||||
array_unshift($args, $this->getTableName());
|
||||
$pattern = 'SELECT * FROM %R WHERE '.$pattern.' %Q';
|
||||
array_unshift($args, $this);
|
||||
array_push($args, $lock_clause);
|
||||
array_unshift($args, $pattern);
|
||||
|
||||
|
@ -1150,8 +1154,8 @@ abstract class LiskDAO extends Phobject {
|
|||
|
||||
$id = $this->getID();
|
||||
$conn->query(
|
||||
'UPDATE %T SET %Q WHERE %C = '.(is_int($id) ? '%d' : '%s'),
|
||||
$this->getTableName(),
|
||||
'UPDATE %R SET %Q WHERE %C = '.(is_int($id) ? '%d' : '%s'),
|
||||
$this,
|
||||
$map,
|
||||
$this->getIDKeyForUse(),
|
||||
$id);
|
||||
|
@ -1178,8 +1182,8 @@ abstract class LiskDAO extends Phobject {
|
|||
|
||||
$conn = $this->establishConnection('w');
|
||||
$conn->query(
|
||||
'DELETE FROM %T WHERE %C = %d',
|
||||
$this->getTableName(),
|
||||
'DELETE FROM %R WHERE %C = %d',
|
||||
$this,
|
||||
$this->getIDKeyForUse(),
|
||||
$this->getID());
|
||||
|
||||
|
@ -1255,9 +1259,9 @@ abstract class LiskDAO extends Phobject {
|
|||
$data = implode(', ', $data);
|
||||
|
||||
$conn->query(
|
||||
'%Q INTO %T (%LC) VALUES (%Q)',
|
||||
'%Q INTO %R (%LC) VALUES (%Q)',
|
||||
$mode,
|
||||
$this->getTableName(),
|
||||
$this,
|
||||
$columns,
|
||||
$data);
|
||||
|
||||
|
@ -2019,4 +2023,17 @@ abstract class LiskDAO extends Phobject {
|
|||
->getMaximumByteLengthForDataType($data_type);
|
||||
}
|
||||
|
||||
|
||||
/* -( AphrontDatabaseTableRefInterface )----------------------------------- */
|
||||
|
||||
|
||||
public function getAphrontRefDatabaseName() {
|
||||
return $this->getDatabaseName();
|
||||
}
|
||||
|
||||
public function getAphrontRefTableName() {
|
||||
return $this->getTableName();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -187,11 +187,10 @@ abstract class PhabricatorLiskDAO extends LiskDAO {
|
|||
*/
|
||||
abstract public function getApplicationName();
|
||||
|
||||
protected function getConnectionNamespace() {
|
||||
protected function getDatabaseName() {
|
||||
return self::getStorageNamespace().'_'.$this->getApplicationName();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Break a list of escaped SQL statement fragments (e.g., VALUES lists for
|
||||
* INSERT, previously built with @{function:qsprintf}) into chunks which will
|
||||
|
|
|
@ -22,7 +22,7 @@ final class LiskIsolationTestDAO extends LiskDAO {
|
|||
'resource!'));
|
||||
}
|
||||
|
||||
protected function getConnectionNamespace() {
|
||||
protected function getDatabaseName() {
|
||||
return 'test';
|
||||
}
|
||||
|
||||
|
|
|
@ -64,6 +64,8 @@ final class PhabricatorStartup {
|
|||
* @task info
|
||||
*/
|
||||
public static function getMicrosecondsSinceStart() {
|
||||
// This is the same as "phutil_microseconds_since()", but we may not have
|
||||
// loaded libphutil yet.
|
||||
return (int)(1000000 * (microtime(true) - self::getStartTime()));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
/**
|
||||
* @provides javelin-behavior-slowvote-embed
|
||||
* @requires javelin-behavior
|
||||
* javelin-request
|
||||
* javelin-stratcom
|
||||
* javelin-dom
|
||||
*/
|
||||
JX.behavior('slowvote-embed', function() {
|
||||
JX.Stratcom.listen(
|
||||
['click'],
|
||||
'slowvote-option',
|
||||
function(e) {
|
||||
if (!e.isNormalMouseEvent()) {
|
||||
return;
|
||||
}
|
||||
e.kill();
|
||||
|
||||
var pollID = e.getNodeData('slowvote-embed').pollID;
|
||||
var voteURI = '/vote/' + pollID + '/';
|
||||
|
||||
var request = new JX.Request(voteURI, function(r) {
|
||||
var updated_poll = JX.$H(r.contentHTML);
|
||||
var root = JX.$('phabricator-standard-page');
|
||||
|
||||
var polls = JX.DOM.scry(root, 'div', 'slowvote-embed');
|
||||
|
||||
for(var i = 0; i < polls.length; i++) {
|
||||
var data = JX.Stratcom.getData(polls[i]);
|
||||
|
||||
if (data.pollID == pollID) {
|
||||
JX.DOM.replace(polls[i], updated_poll);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
request.addData({vote: e.getNodeData('slowvote-option').optionID});
|
||||
request.send();
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
Add table
Reference in a new issue