mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-09 22:31:03 +01:00
(stable) Promote 2018 Week 49
This commit is contained in:
commit
237a2a1909
25 changed files with 464 additions and 211 deletions
|
@ -2976,6 +2976,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEditEngineStaticCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineStaticCommentAction.php',
|
||||
'PhabricatorEditEngineSubtype' => 'applications/transactions/editengine/PhabricatorEditEngineSubtype.php',
|
||||
'PhabricatorEditEngineSubtypeInterface' => 'applications/transactions/editengine/PhabricatorEditEngineSubtypeInterface.php',
|
||||
'PhabricatorEditEngineSubtypeMap' => 'applications/transactions/editengine/PhabricatorEditEngineSubtypeMap.php',
|
||||
'PhabricatorEditEngineSubtypeTestCase' => 'applications/transactions/editengine/__tests__/PhabricatorEditEngineSubtypeTestCase.php',
|
||||
'PhabricatorEditEngineTokenizerCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineTokenizerCommentAction.php',
|
||||
'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php',
|
||||
|
@ -3760,6 +3761,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPeopleAnyOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleAnyOwnerDatasource.php',
|
||||
'PhabricatorPeopleApplication' => 'applications/people/application/PhabricatorPeopleApplication.php',
|
||||
'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php',
|
||||
'PhabricatorPeopleAvailabilitySearchEngineAttachment' => 'applications/people/engineextension/PhabricatorPeopleAvailabilitySearchEngineAttachment.php',
|
||||
'PhabricatorPeopleBadgesProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleBadgesProfileMenuItem.php',
|
||||
'PhabricatorPeopleCommitsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleCommitsProfileMenuItem.php',
|
||||
'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php',
|
||||
|
@ -8729,6 +8731,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEditEngineSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||
'PhabricatorEditEngineStaticCommentAction' => 'PhabricatorEditEngineCommentAction',
|
||||
'PhabricatorEditEngineSubtype' => 'Phobject',
|
||||
'PhabricatorEditEngineSubtypeMap' => 'Phobject',
|
||||
'PhabricatorEditEngineSubtypeTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorEditEngineTokenizerCommentAction' => 'PhabricatorEditEngineCommentAction',
|
||||
'PhabricatorEditField' => 'Phobject',
|
||||
|
@ -9638,6 +9641,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPeopleAnyOwnerDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'PhabricatorPeopleApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleAvailabilitySearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
|
||||
'PhabricatorPeopleBadgesProfileMenuItem' => 'PhabricatorProfileMenuItem',
|
||||
'PhabricatorPeopleCommitsProfileMenuItem' => 'PhabricatorProfileMenuItem',
|
||||
'PhabricatorPeopleController' => 'PhabricatorController',
|
||||
|
|
|
@ -59,10 +59,6 @@ final class PhabricatorAuditEditor
|
|||
|
||||
$this->oldAuditStatus = $object->getAuditStatus();
|
||||
|
||||
$object->loadAndAttachAuditAuthority(
|
||||
$this->getActor(),
|
||||
$this->getActingAsPHID());
|
||||
|
||||
return parent::expandTransactions($object, $xactions);
|
||||
}
|
||||
|
||||
|
|
|
@ -1367,9 +1367,9 @@ final class DifferentialTransactionEditor
|
|||
foreach (array_chunk($sql, 256) as $chunk) {
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'INSERT INTO %T (repositoryID, pathID, epoch, revisionID) VALUES %Q',
|
||||
'INSERT INTO %T (repositoryID, pathID, epoch, revisionID) VALUES %LQ',
|
||||
$table->getTableName(),
|
||||
implode(', ', $chunk));
|
||||
$chunk);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1444,9 +1444,9 @@ final class DifferentialTransactionEditor
|
|||
if ($sql) {
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'INSERT INTO %T (revisionID, type, hash) VALUES %Q',
|
||||
'INSERT INTO %T (revisionID, type, hash) VALUES %LQ',
|
||||
ArcanistDifferentialRevisionHash::TABLE_NAME,
|
||||
implode(', ', $sql));
|
||||
$sql);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ final class DiffusionCommitController extends DiffusionController {
|
|||
->withIdentifiers(array($commit_identifier))
|
||||
->needCommitData(true)
|
||||
->needAuditRequests(true)
|
||||
->needAuditAuthority(array($viewer))
|
||||
->setLimit(100)
|
||||
->needIdentities(true)
|
||||
->execute();
|
||||
|
@ -111,7 +112,6 @@ final class DiffusionCommitController extends DiffusionController {
|
|||
}
|
||||
|
||||
$audit_requests = $commit->getAudits();
|
||||
$commit->loadAndAttachAuditAuthority($viewer);
|
||||
|
||||
$commit_data = $commit->getCommitData();
|
||||
$is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
|
||||
|
|
|
@ -43,9 +43,12 @@ final class DiffusionCommitEditEngine
|
|||
}
|
||||
|
||||
protected function newObjectQuery() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
return id(new DiffusionCommitQuery())
|
||||
->needCommitData(true)
|
||||
->needAuditRequests(true);
|
||||
->needAuditRequests(true)
|
||||
->needAuditAuthority(array($viewer));
|
||||
}
|
||||
|
||||
protected function getEditorURI() {
|
||||
|
|
|
@ -17,6 +17,7 @@ final class DiffusionCommitQuery
|
|||
private $unreachable;
|
||||
|
||||
private $needAuditRequests;
|
||||
private $needAuditAuthority;
|
||||
private $auditIDs;
|
||||
private $auditorPHIDs;
|
||||
private $epochMin;
|
||||
|
@ -121,6 +122,12 @@ final class DiffusionCommitQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function needAuditAuthority(array $users) {
|
||||
assert_instances_of($users, 'PhabricatorUser');
|
||||
$this->needAuditAuthority = $users;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withAuditIDs(array $ids) {
|
||||
$this->auditIDs = $ids;
|
||||
return $this;
|
||||
|
@ -231,14 +238,27 @@ final class DiffusionCommitQuery
|
|||
}
|
||||
|
||||
if (count($subqueries) > 1) {
|
||||
foreach ($subqueries as $key => $subquery) {
|
||||
$subqueries[$key] = '('.$subquery.')';
|
||||
$unions = null;
|
||||
foreach ($subqueries as $subquery) {
|
||||
if (!$unions) {
|
||||
$unions = qsprintf(
|
||||
$conn,
|
||||
'(%Q)',
|
||||
$subquery);
|
||||
continue;
|
||||
}
|
||||
|
||||
$unions = qsprintf(
|
||||
$conn,
|
||||
'%Q UNION DISTINCT (%Q)',
|
||||
$unions,
|
||||
$subquery);
|
||||
}
|
||||
|
||||
$query = qsprintf(
|
||||
$conn,
|
||||
'%Q %Q %Q',
|
||||
implode(' UNION DISTINCT ', $subqueries),
|
||||
$unions,
|
||||
$this->buildOrderClause($conn, true),
|
||||
$this->buildLimitClause($conn));
|
||||
} else {
|
||||
|
@ -423,6 +443,72 @@ final class DiffusionCommitQuery
|
|||
$commits);
|
||||
}
|
||||
|
||||
if ($this->needAuditAuthority) {
|
||||
$authority_users = $this->needAuditAuthority;
|
||||
|
||||
// NOTE: This isn't very efficient since we're running two queries per
|
||||
// user, but there's currently no way to figure out authority for
|
||||
// multiple users in one query. Today, we only ever request authority for
|
||||
// a single user and single commit, so this has no practical impact.
|
||||
|
||||
// NOTE: We're querying with the viewership of query viewer, not the
|
||||
// actual users. If the viewer can't see a project or package, they
|
||||
// won't be able to see who has authority on it. This is safer than
|
||||
// showing them true authority, and should never matter today, but it
|
||||
// also doesn't seem like a significant disclosure and might be
|
||||
// reasonable to adjust later if it causes something weird or confusing
|
||||
// to happen.
|
||||
|
||||
$authority_map = array();
|
||||
foreach ($authority_users as $authority_user) {
|
||||
$authority_phid = $authority_user->getPHID();
|
||||
if (!$authority_phid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result_phids = array();
|
||||
|
||||
// Users have authority over themselves.
|
||||
$result_phids[] = $authority_phid;
|
||||
|
||||
// Users have authority over packages they own.
|
||||
$owned_packages = id(new PhabricatorOwnersPackageQuery())
|
||||
->setViewer($viewer)
|
||||
->withAuthorityPHIDs(array($authority_phid))
|
||||
->execute();
|
||||
foreach ($owned_packages as $package) {
|
||||
$result_phids[] = $package->getPHID();
|
||||
}
|
||||
|
||||
// Users have authority over projects they're members of.
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withMemberPHIDs(array($authority_phid))
|
||||
->execute();
|
||||
foreach ($projects as $project) {
|
||||
$result_phids[] = $project->getPHID();
|
||||
}
|
||||
|
||||
$result_phids = array_fuse($result_phids);
|
||||
|
||||
foreach ($commits as $commit) {
|
||||
$attach_phids = $result_phids;
|
||||
|
||||
// NOTE: When modifying your own commits, you act only on behalf of
|
||||
// yourself, not your packages or projects. The idea here is that you
|
||||
// can't accept your own commits. In the future, this might change or
|
||||
// depend on configuration.
|
||||
$author_phid = $commit->getAuthorPHID();
|
||||
if ($author_phid == $authority_phid) {
|
||||
$attach_phids = array($author_phid);
|
||||
$attach_phids = array_fuse($attach_phids);
|
||||
}
|
||||
|
||||
$commit->attachAuditAuthority($authority_user, $attach_phids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $commits;
|
||||
}
|
||||
|
||||
|
|
|
@ -649,7 +649,7 @@ final class ManiphestTransactionEditor
|
|||
$old_value = $object->getOwnerPHID();
|
||||
$new_value = $xaction->getNewValue();
|
||||
if ($old_value === $new_value) {
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
// When a task is reassigned, move the old owner to the subscriber
|
||||
|
|
|
@ -47,7 +47,7 @@ final class ManiphestTaskSearchEngine
|
|||
// Hide the "Subtypes" constraint from the web UI if the install only
|
||||
// defines one task subtype, since it isn't of any use in this case.
|
||||
$subtype_map = id(new ManiphestTask())->newEditEngineSubtypeMap();
|
||||
$hide_subtypes = (count($subtype_map) == 1);
|
||||
$hide_subtypes = ($subtype_map->getCount() == 1);
|
||||
|
||||
return array(
|
||||
id(new PhabricatorOwnersSearchField())
|
||||
|
|
|
@ -573,7 +573,7 @@ final class ManiphestTask extends ManiphestDAO
|
|||
public function newSubtypeObject() {
|
||||
$subtype_key = $this->getEditEngineSubtype();
|
||||
$subtype_map = $this->newEditEngineSubtypeMap();
|
||||
return idx($subtype_map, $subtype_key);
|
||||
return $subtype_map->getSubtype($subtype_key);
|
||||
}
|
||||
|
||||
/* -( PhabricatorFulltextInterface )--------------------------------------- */
|
||||
|
|
|
@ -214,11 +214,12 @@ final class ManiphestTransaction
|
|||
public function renderSubtypeName($value) {
|
||||
$object = $this->getObject();
|
||||
$map = $object->newEditEngineSubtypeMap();
|
||||
if (!isset($map[$value])) {
|
||||
|
||||
if (!$map->isValidSubtype($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $map[$value]->getName();
|
||||
return $map->getSubtype($value)->getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ final class ManiphestTaskSubtypeDatasource
|
|||
$results = array();
|
||||
|
||||
$subtype_map = id(new ManiphestTask())->newEditEngineSubtypeMap();
|
||||
foreach ($subtype_map as $key => $subtype) {
|
||||
foreach ($subtype_map->getSubtypes() as $key => $subtype) {
|
||||
|
||||
$result = id(new PhabricatorTypeaheadResult())
|
||||
->setIcon($subtype->getIcon())
|
||||
|
|
|
@ -56,9 +56,6 @@ final class ManiphestTaskListView extends ManiphestView {
|
|||
Javelin::initBehavior('maniphest-list-editor');
|
||||
}
|
||||
|
||||
$subtype_map = id(new ManiphestTask())
|
||||
->newEditEngineSubtypeMap();
|
||||
|
||||
foreach ($this->tasks as $task) {
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setUser($this->getUser())
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorPeopleAvailabilitySearchEngineAttachment
|
||||
extends PhabricatorSearchEngineAttachment {
|
||||
|
||||
public function getAttachmentName() {
|
||||
return pht('User Availability');
|
||||
}
|
||||
|
||||
public function getAttachmentDescription() {
|
||||
return pht('Get availability information for users.');
|
||||
}
|
||||
|
||||
public function willLoadAttachmentData($query, $spec) {
|
||||
$query->needAvailability(true);
|
||||
}
|
||||
|
||||
public function getAttachmentForObject($object, $data, $spec) {
|
||||
|
||||
$until = $object->getAwayUntil();
|
||||
if ($until) {
|
||||
$until = (int)$until;
|
||||
} else {
|
||||
$until = null;
|
||||
}
|
||||
|
||||
$value = $object->getDisplayAvailability();
|
||||
if ($value === null) {
|
||||
$value = PhabricatorCalendarEventInvitee::AVAILABILITY_AVAILABLE;
|
||||
}
|
||||
|
||||
$name = PhabricatorCalendarEventInvitee::getAvailabilityName($value);
|
||||
$color = PhabricatorCalendarEventInvitee::getAvailabilityColor($value);
|
||||
|
||||
$event_phid = $object->getAvailabilityEventPHID();
|
||||
|
||||
return array(
|
||||
'value' => $value,
|
||||
'until' => $until,
|
||||
'name' => $name,
|
||||
'color' => $color,
|
||||
'eventPHID' => $event_phid,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1466,7 +1466,10 @@ final class PhabricatorUser
|
|||
}
|
||||
|
||||
public function getConduitSearchAttachments() {
|
||||
return array();
|
||||
return array(
|
||||
id(new PhabricatorPeopleAvailabilitySearchEngineAttachment())
|
||||
->setAttachmentKey('availability'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -15,10 +15,11 @@ final class PhabricatorRepositoryManagementThawWorkflow
|
|||
array(
|
||||
array(
|
||||
'name' => 'demote',
|
||||
'param' => 'device',
|
||||
'param' => 'device/service',
|
||||
'help' => pht(
|
||||
'Demote a device, discarding local changes. Clears stuck '.
|
||||
'write locks and recovers from lost leaders.'),
|
||||
'Demote a device (or all devices in a service) discarding '.
|
||||
'local changes. Clears stuck write locks and recovers from '.
|
||||
'lost leaders.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'promote',
|
||||
|
@ -32,6 +33,12 @@ final class PhabricatorRepositoryManagementThawWorkflow
|
|||
'name' => 'force',
|
||||
'help' => pht('Run operations without asking for confirmation.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'all-repositories',
|
||||
'help' => pht(
|
||||
'Apply the promotion or demotion to all repositories hosted '.
|
||||
'on the device.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'repositories',
|
||||
'wildcard' => true,
|
||||
|
@ -42,12 +49,6 @@ final class PhabricatorRepositoryManagementThawWorkflow
|
|||
public function execute(PhutilArgumentParser $args) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$repositories = $this->loadRepositories($args, 'repositories');
|
||||
if (!$repositories) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('Specify one or more repositories to thaw.'));
|
||||
}
|
||||
|
||||
$promote = $args->getArg('promote');
|
||||
$demote = $args->getArg('demote');
|
||||
|
||||
|
@ -61,17 +62,109 @@ final class PhabricatorRepositoryManagementThawWorkflow
|
|||
pht('Specify either --promote or --demote, but not both.'));
|
||||
}
|
||||
|
||||
$device_name = nonempty($promote, $demote);
|
||||
$target_name = nonempty($promote, $demote);
|
||||
|
||||
$device = id(new AlmanacDeviceQuery())
|
||||
$devices = id(new AlmanacDeviceQuery())
|
||||
->setViewer($viewer)
|
||||
->withNames(array($device_name))
|
||||
->executeOne();
|
||||
if (!$device) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('No device "%s" exists.', $device_name));
|
||||
->withNames(array($target_name))
|
||||
->execute();
|
||||
if (!$devices) {
|
||||
$service = id(new AlmanacServiceQuery())
|
||||
->setViewer($viewer)
|
||||
->withNames(array($target_name))
|
||||
->executeOne();
|
||||
|
||||
if (!$service) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('No device or service named "%s" exists.', $target_name));
|
||||
}
|
||||
|
||||
if ($promote) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'You can not "--promote" an entire service ("%s"). Only a single '.
|
||||
'device may be promoted.',
|
||||
$target_name));
|
||||
}
|
||||
|
||||
$bindings = id(new AlmanacBindingQuery())
|
||||
->setViewer($viewer)
|
||||
->withServicePHIDs(array($service->getPHID()))
|
||||
->execute();
|
||||
if (!$bindings) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Service "%s" is not bound to any devices.',
|
||||
$target_name));
|
||||
}
|
||||
|
||||
$interfaces = id(new AlmanacInterfaceQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(mpull($bindings, 'getInterfacePHID'))
|
||||
->execute();
|
||||
|
||||
$device_phids = mpull($interfaces, 'getDevicePHID');
|
||||
|
||||
$devices = id(new AlmanacDeviceQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($device_phids)
|
||||
->execute();
|
||||
}
|
||||
|
||||
$repository_names = $args->getArg('repositories');
|
||||
$all_repositories = $args->getArg('all-repositories');
|
||||
if ($repository_names && $all_repositories) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Specify a list of repositories or "--all-repositories", '.
|
||||
'but not both.'));
|
||||
} else if (!$repository_names && !$all_repositories) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Select repositories to affect by providing a list of repositories '.
|
||||
'or using the "--all-repositories" flag.'));
|
||||
}
|
||||
|
||||
if ($repository_names) {
|
||||
$repositories = $this->loadRepositories($args, 'repositories');
|
||||
if (!$repositories) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('Specify one or more repositories to thaw.'));
|
||||
}
|
||||
} else {
|
||||
$repositories = array();
|
||||
|
||||
$services = id(new AlmanacServiceQuery())
|
||||
->setViewer($viewer)
|
||||
->withDevicePHIDs(mpull($devices, 'getPHID'))
|
||||
->execute();
|
||||
if ($services) {
|
||||
$repositories = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer($viewer)
|
||||
->withAlmanacServicePHIDs(mpull($services, 'getPHID'))
|
||||
->execute();
|
||||
}
|
||||
|
||||
if (!$repositories) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('There are no repositories on the selected device or service.'));
|
||||
}
|
||||
}
|
||||
|
||||
$display_list = new PhutilConsoleList();
|
||||
foreach ($repositories as $repository) {
|
||||
$display_list->addItem(
|
||||
pht(
|
||||
'%s %s',
|
||||
$repository->getMonogram(),
|
||||
$repository->getName()));
|
||||
}
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n\n%B\n",
|
||||
pht('These repositories will be thawed:'),
|
||||
$display_list->drawConsoleString());
|
||||
|
||||
if ($promote) {
|
||||
$risk_message = pht(
|
||||
'Promoting a device can cause the loss of any repository data which '.
|
||||
|
@ -96,126 +189,128 @@ final class PhabricatorRepositoryManagementThawWorkflow
|
|||
pht('User aborted the workflow.'));
|
||||
}
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
$repository_phid = $repository->getPHID();
|
||||
foreach ($devices as $device) {
|
||||
foreach ($repositories as $repository) {
|
||||
$repository_phid = $repository->getPHID();
|
||||
|
||||
$write_lock = PhabricatorRepositoryWorkingCopyVersion::getWriteLock(
|
||||
$repository_phid);
|
||||
$write_lock = PhabricatorRepositoryWorkingCopyVersion::getWriteLock(
|
||||
$repository_phid);
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Waiting to acquire write lock for "%s"...',
|
||||
$repository->getDisplayName()));
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Waiting to acquire write lock for "%s"...',
|
||||
$repository->getDisplayName()));
|
||||
|
||||
$write_lock->lock(phutil_units('5 minutes in seconds'));
|
||||
try {
|
||||
$write_lock->lock(phutil_units('5 minutes in seconds'));
|
||||
try {
|
||||
|
||||
$service = $repository->loadAlmanacService();
|
||||
if (!$service) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Repository "%s" is not a cluster repository: it is not '.
|
||||
'bound to an Almanac service.',
|
||||
$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()])) {
|
||||
$service = $repository->loadAlmanacService();
|
||||
if (!$service) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Repository "%s" has no active binding to device "%s". Only '.
|
||||
'actively bound devices can be promoted.',
|
||||
$repository->getDisplayName(),
|
||||
$device->getName()));
|
||||
'Repository "%s" is not a cluster repository: it is not '.
|
||||
'bound to an Almanac service.',
|
||||
$repository->getDisplayName()));
|
||||
}
|
||||
|
||||
$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;
|
||||
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()));
|
||||
}
|
||||
$inactive[$device_phid] = $version;
|
||||
}
|
||||
|
||||
if ($inactive) {
|
||||
$handles = $viewer->loadHandles(array_keys($inactive));
|
||||
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
|
||||
$repository->getPHID());
|
||||
$versions = mpull($versions, null, 'getDevicePHID');
|
||||
|
||||
$handle_list = iterator_to_array($handles);
|
||||
$handle_list = mpull($handle_list, 'getName');
|
||||
$handle_list = implode(', ', $handle_list);
|
||||
// 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;
|
||||
}
|
||||
|
||||
throw new PhutilArgumentUsageException(
|
||||
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);
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
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.',
|
||||
'Promoted "%s" to become a leader for "%s".',
|
||||
$device->getName(),
|
||||
$repository->getDisplayName(),
|
||||
$handle_list));
|
||||
$repository->getDisplayName()));
|
||||
}
|
||||
|
||||
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
|
||||
$repository->getPHID(),
|
||||
$device->getPHID(),
|
||||
0);
|
||||
if ($demote) {
|
||||
PhabricatorRepositoryWorkingCopyVersion::demoteDevice(
|
||||
$repository->getPHID(),
|
||||
$device->getPHID());
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Promoted "%s" to become a leader for "%s".',
|
||||
$device->getName(),
|
||||
$repository->getDisplayName()));
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Demoted "%s" from leadership of repository "%s".',
|
||||
$device->getName(),
|
||||
$repository->getDisplayName()));
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$write_lock->unlock();
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
if ($demote) {
|
||||
PhabricatorRepositoryWorkingCopyVersion::demoteDevice(
|
||||
$repository->getPHID(),
|
||||
$device->getPHID());
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Demoted "%s" from leadership of repository "%s".',
|
||||
$device->getName(),
|
||||
$repository->getDisplayName()));
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$write_lock->unlock();
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$write_lock->unlock();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -44,6 +44,15 @@ final class PhabricatorRepositorySearchEngine
|
|||
->setKey('uris')
|
||||
->setDescription(
|
||||
pht('Search for repositories by clone/checkout URI.')),
|
||||
id(new PhabricatorPHIDsSearchField())
|
||||
->setLabel(pht('Services'))
|
||||
->setKey('almanacServicePHIDs')
|
||||
->setAliases(
|
||||
array(
|
||||
'almanacServicePHID',
|
||||
'almanacService',
|
||||
'almanacServices',
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -80,6 +89,10 @@ final class PhabricatorRepositorySearchEngine
|
|||
$query->withURIs($map['uris']);
|
||||
}
|
||||
|
||||
if ($map['almanacServicePHIDs']) {
|
||||
$query->withAlmanacServicePHIDs($map['almanacServicePHIDs']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
|
|
@ -2773,6 +2773,13 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
->setDescription(
|
||||
pht(
|
||||
'True if the repository is importing initial commits.')),
|
||||
id(new PhabricatorConduitSearchFieldSpecification())
|
||||
->setKey('almanacServicePHID')
|
||||
->setType('phid?')
|
||||
->setDescription(
|
||||
pht(
|
||||
'The Almanac Service that hosts this repository, if the '.
|
||||
'repository is clustered.')),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2784,6 +2791,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
'shortName' => $this->getRepositorySlug(),
|
||||
'status' => $this->getStatus(),
|
||||
'isImporting' => (bool)$this->isImporting(),
|
||||
'almanacServicePHID' => $this->getAlmanacServicePHID(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -210,84 +210,32 @@ final class PhabricatorRepositoryCommit
|
|||
return $this->assertAttached($this->committerIdentity);
|
||||
}
|
||||
|
||||
public function loadAndAttachAuditAuthority(
|
||||
PhabricatorUser $viewer,
|
||||
$actor_phid = null) {
|
||||
public function attachAuditAuthority(
|
||||
PhabricatorUser $user,
|
||||
array $authority) {
|
||||
|
||||
if ($actor_phid === null) {
|
||||
$actor_phid = $viewer->getPHID();
|
||||
$user_phid = $user->getPHID();
|
||||
if (!$user->getPHID()) {
|
||||
throw new Exception(
|
||||
pht('You can not attach audit authority for a user with no PHID.'));
|
||||
}
|
||||
|
||||
// TODO: This method is a little weird and sketchy, but worlds better than
|
||||
// what came before it. Eventually, this should probably live in a Query
|
||||
// class.
|
||||
|
||||
// Figure out which requests the actor has authority over: these are user
|
||||
// requests where they are the auditor, and packages and projects they are
|
||||
// a member of.
|
||||
|
||||
if (!$actor_phid) {
|
||||
$attach_key = $viewer->getCacheFragment();
|
||||
$phids = array();
|
||||
} else {
|
||||
$attach_key = $actor_phid;
|
||||
// At least currently, when modifying your own commits, you act only on
|
||||
// behalf of yourself, not your packages/projects -- the idea being that
|
||||
// you can't accept your own commits. This may change or depend on
|
||||
// config.
|
||||
$actor_is_author = ($actor_phid == $this->getAuthorPHID());
|
||||
if ($actor_is_author) {
|
||||
$phids = array($actor_phid);
|
||||
} else {
|
||||
$phids = array();
|
||||
$phids[$actor_phid] = true;
|
||||
|
||||
$owned_packages = id(new PhabricatorOwnersPackageQuery())
|
||||
->setViewer($viewer)
|
||||
->withAuthorityPHIDs(array($actor_phid))
|
||||
->execute();
|
||||
foreach ($owned_packages as $package) {
|
||||
$phids[$package->getPHID()] = true;
|
||||
}
|
||||
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withMemberPHIDs(array($actor_phid))
|
||||
->execute();
|
||||
foreach ($projects as $project) {
|
||||
$phids[$project->getPHID()] = true;
|
||||
}
|
||||
|
||||
$phids = array_keys($phids);
|
||||
}
|
||||
}
|
||||
|
||||
$this->auditAuthorityPHIDs[$attach_key] = array_fuse($phids);
|
||||
$this->auditAuthorityPHIDs[$user_phid] = $authority;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasAuditAuthority(
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorRepositoryAuditRequest $audit,
|
||||
$actor_phid = null) {
|
||||
PhabricatorUser $user,
|
||||
PhabricatorRepositoryAuditRequest $audit) {
|
||||
|
||||
if ($actor_phid === null) {
|
||||
$actor_phid = $viewer->getPHID();
|
||||
}
|
||||
|
||||
if (!$actor_phid) {
|
||||
$attach_key = $viewer->getCacheFragment();
|
||||
} else {
|
||||
$attach_key = $actor_phid;
|
||||
}
|
||||
|
||||
$map = $this->assertAttachedKey($this->auditAuthorityPHIDs, $attach_key);
|
||||
|
||||
if (!$actor_phid) {
|
||||
$user_phid = $user->getPHID();
|
||||
if (!$user_phid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$map = $this->assertAttachedKey($this->auditAuthorityPHIDs, $user_phid);
|
||||
|
||||
return isset($map[$audit->getAuditorPHID()]);
|
||||
}
|
||||
|
||||
|
|
|
@ -61,8 +61,7 @@ Choose the object **subtype** that this form should create and edit.
|
|||
EOTEXT
|
||||
);
|
||||
|
||||
$map = $engine->newSubtypeMap();
|
||||
$map = mpull($map, 'getName');
|
||||
$map = $engine->newSubtypeMap()->getDisplayMap();
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
|
|
|
@ -182,7 +182,7 @@ final class PhabricatorEditEngineSubtype
|
|||
$map[$key] = $subtype;
|
||||
}
|
||||
|
||||
return $map;
|
||||
return new PhabricatorEditEngineSubtypeMap($map);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
|
||||
final class PhabricatorEditEngineSubtypeMap
|
||||
extends Phobject {
|
||||
|
||||
private $subtypes;
|
||||
|
||||
public function __construct(array $subtypes) {
|
||||
assert_instances_of($subtypes, 'PhabricatorEditEngineSubtype');
|
||||
|
||||
$this->subtypes = $subtypes;
|
||||
}
|
||||
|
||||
public function getDisplayMap() {
|
||||
return mpull($this->subtypes, 'getName');
|
||||
}
|
||||
|
||||
public function getCount() {
|
||||
return count($this->subtypes);
|
||||
}
|
||||
|
||||
public function isValidSubtype($subtype_key) {
|
||||
return isset($this->subtypes[$subtype_key]);
|
||||
}
|
||||
|
||||
public function getSubtypes() {
|
||||
return $this->subtypes;
|
||||
}
|
||||
|
||||
public function getSubtype($subtype_key) {
|
||||
if (!$this->isValidSubtype($subtype_key)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Subtype key "%s" does not identify a valid subtype.',
|
||||
$subtype_key));
|
||||
}
|
||||
|
||||
return $this->subtypes[$subtype_key];
|
||||
}
|
||||
|
||||
}
|
|
@ -2541,7 +2541,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!isset($map[$new])) {
|
||||
if (!$map->isValidSubtype($new)) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$transaction_type,
|
||||
pht('Invalid'),
|
||||
|
|
|
@ -64,7 +64,7 @@ final class PhabricatorEditEngineConfigurationEditor
|
|||
foreach ($xactions as $xaction) {
|
||||
$new = $xaction->getNewValue();
|
||||
|
||||
if (isset($map[$new])) {
|
||||
if ($map->isValidSubtype($new)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ final class PhabricatorSubtypeEditEngineExtension
|
|||
$subtype_type = PhabricatorTransactions::TYPE_SUBTYPE;
|
||||
|
||||
$map = $object->newEditEngineSubtypeMap();
|
||||
$options = mpull($map, 'getName');
|
||||
$options = $map->getDisplayMap();
|
||||
|
||||
$subtype_field = id(new PhabricatorSelectEditField())
|
||||
->setKey(self::EDITKEY)
|
||||
|
@ -45,7 +45,7 @@ final class PhabricatorSubtypeEditEngineExtension
|
|||
|
||||
// If subtypes are configured, enable changing them from the bulk editor
|
||||
// and comment action stack.
|
||||
if (count($map) > 1) {
|
||||
if ($map->getCount() > 1) {
|
||||
$subtype_field
|
||||
->setBulkEditLabel(pht('Change subtype to'))
|
||||
->setCommentActionLabel(pht('Change Subtype'))
|
||||
|
|
|
@ -433,6 +433,18 @@ 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.
|
||||
|
||||
If you have lost an entire cluster and replaced it with new devices that you
|
||||
have restored from backups, you can aggressively wipe all memory of the old
|
||||
devices by using `--demote <service>` and `--all-repositories`. **This is
|
||||
dangerous and discards all unreplicated data in any repository on any device.**
|
||||
|
||||
```
|
||||
phabricator/ $ ./bin/repository thaw --demote repo.corp.net --all-repositories
|
||||
```
|
||||
|
||||
After you do this, continue below to promote a leader and restore the cluster
|
||||
to service.
|
||||
|
||||
|
||||
Ambiguous Leaders
|
||||
=================
|
||||
|
|
Loading…
Reference in a new issue