1
0
Fork 0
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:
epriestley 2018-11-12 11:06:32 -08:00
commit de56ef9466
43 changed files with 1050 additions and 235 deletions

View file

@ -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',

View 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};

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_repository.repository_pushevent
ADD hookWait BIGINT UNSIGNED;

View file

@ -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
);

View file

@ -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);

View file

@ -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',

View file

@ -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');

View file

@ -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();

View file

@ -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) {

View file

@ -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(

View file

@ -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);
}

View file

@ -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();

View file

@ -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();
}
}

View file

@ -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',
),

View file

@ -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;

View file

@ -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/'));
}
}

View file

@ -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)) {

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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));
}
}

View 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;
}
}

View file

@ -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';

View file

@ -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;

View file

@ -290,7 +290,7 @@ final class ManiphestTransactionEditor
$copy->setOwnerPHID($xaction->getNewValue());
break;
default:
continue;
break;
}
}

View file

@ -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;
}
}

View file

@ -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(),

View file

@ -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()));
}
}
}

View file

@ -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()) {

View file

@ -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';
}
}

View file

@ -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(

View file

@ -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.");
}
}

View file

@ -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());
}
}

View file

@ -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');

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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')));
}

View file

@ -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();
}
}

View file

@ -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

View file

@ -22,7 +22,7 @@ final class LiskIsolationTestDAO extends LiskDAO {
'resource!'));
}
protected function getConnectionNamespace() {
protected function getDatabaseName() {
return 'test';
}

View file

@ -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()));
}

View file

@ -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();
});
});