1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-04-01 06:58:14 +02:00

(stable) Promote 2019 Week 45

This commit is contained in:
epriestley 2019-11-08 18:08:08 -08:00
commit cf6df55dbe
21 changed files with 334 additions and 76 deletions

View file

@ -10,7 +10,7 @@ return array(
'conpherence.pkg.css' => '3c8a0668',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => '77de226f',
'core.pkg.js' => '6e5c894f',
'core.pkg.js' => '705aec2c',
'differential.pkg.css' => '607c84be',
'differential.pkg.js' => '1b97518d',
'diffusion.pkg.css' => '42c75c37',
@ -448,7 +448,7 @@ return array(
'rsrc/js/application/uiexample/notification-example.js' => '29819b75',
'rsrc/js/core/Busy.js' => '5202e831',
'rsrc/js/core/DragAndDropFileUpload.js' => '4370900d',
'rsrc/js/core/DraggableList.js' => 'c9ad6f70',
'rsrc/js/core/DraggableList.js' => '0169e425',
'rsrc/js/core/Favicon.js' => '7930776a',
'rsrc/js/core/FileUpload.js' => 'ab85e184',
'rsrc/js/core/Hovercard.js' => '074f0783',
@ -777,7 +777,7 @@ return array(
'phabricator-diff-changeset-list' => '0f5c016d',
'phabricator-diff-inline' => 'a4a14a94',
'phabricator-drag-and-drop-file-upload' => '4370900d',
'phabricator-draggable-list' => 'c9ad6f70',
'phabricator-draggable-list' => '0169e425',
'phabricator-fatal-config-template-css' => '20babf50',
'phabricator-favicon' => '7930776a',
'phabricator-feed-css' => 'd8b6e3f8',
@ -920,6 +920,14 @@ return array(
'javelin-uri',
'phabricator-notification',
),
'0169e425' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
'javelin-vector',
'javelin-magical-init',
),
'022516b4' => array(
'javelin-install',
'javelin-util',
@ -2032,14 +2040,6 @@ return array(
'javelin-util',
'phabricator-keyboard-shortcut-manager',
),
'c9ad6f70' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
'javelin-vector',
'javelin-magical-init',
),
'cf32921f' => array(
'javelin-behavior',
'javelin-dom',

View file

@ -4648,6 +4648,7 @@ phutil_register_library_map(array(
'PhabricatorSearchEngineExtensionModule' => 'applications/search/engineextension/PhabricatorSearchEngineExtensionModule.php',
'PhabricatorSearchFerretNgramGarbageCollector' => 'applications/search/garbagecollector/PhabricatorSearchFerretNgramGarbageCollector.php',
'PhabricatorSearchField' => 'applications/search/field/PhabricatorSearchField.php',
'PhabricatorSearchHandleController' => 'applications/search/controller/PhabricatorSearchHandleController.php',
'PhabricatorSearchHost' => 'infrastructure/cluster/search/PhabricatorSearchHost.php',
'PhabricatorSearchHovercardController' => 'applications/search/controller/PhabricatorSearchHovercardController.php',
'PhabricatorSearchIndexVersion' => 'applications/search/storage/PhabricatorSearchIndexVersion.php',
@ -4872,6 +4873,7 @@ phutil_register_library_map(array(
'PhabricatorSystemActionRateLimitException' => 'applications/system/exception/PhabricatorSystemActionRateLimitException.php',
'PhabricatorSystemApplication' => 'applications/system/application/PhabricatorSystemApplication.php',
'PhabricatorSystemDAO' => 'applications/system/storage/PhabricatorSystemDAO.php',
'PhabricatorSystemDebugUIEventListener' => 'applications/system/events/PhabricatorSystemDebugUIEventListener.php',
'PhabricatorSystemDestructionGarbageCollector' => 'applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php',
'PhabricatorSystemDestructionLog' => 'applications/system/storage/PhabricatorSystemDestructionLog.php',
'PhabricatorSystemObjectController' => 'applications/system/controller/PhabricatorSystemObjectController.php',
@ -11270,6 +11272,7 @@ phutil_register_library_map(array(
'PhabricatorSearchEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorSearchFerretNgramGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorSearchField' => 'Phobject',
'PhabricatorSearchHandleController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchHost' => 'Phobject',
'PhabricatorSearchHovercardController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchIndexVersion' => 'PhabricatorSearchDAO',
@ -11511,6 +11514,7 @@ phutil_register_library_map(array(
'PhabricatorSystemActionRateLimitException' => 'Exception',
'PhabricatorSystemApplication' => 'PhabricatorApplication',
'PhabricatorSystemDAO' => 'PhabricatorLiskDAO',
'PhabricatorSystemDebugUIEventListener' => 'PhabricatorEventListener',
'PhabricatorSystemDestructionGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorSystemDestructionLog' => 'PhabricatorSystemDAO',
'PhabricatorSystemObjectController' => 'PhabricatorController',

View file

@ -14,7 +14,9 @@ final class PhutilAsanaAuthAdapter extends PhutilOAuthAuthAdapter {
}
public function getAccountID() {
return $this->getOAuthAccountData('id');
// See T13453. The Asana API has changed to string IDs and now returns a
// "gid" field (previously, it returned an "id" field).
return $this->getOAuthAccountData('gid');
}
public function getAccountEmail() {

View file

@ -329,9 +329,16 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
$actions = array();
if ($panel) {
$panel_actions = $panel->newHeaderEditActions(
$viewer,
$context_phid);
try {
$panel_actions = $panel->newHeaderEditActions(
$viewer,
$context_phid);
} catch (Exception $ex) {
$error_action = id(new PhabricatorActionView())
->setIcon('fa-exclamation-triangle red')
->setName(pht('<Rendering Exception>'));
$panel_actions[] = $error_action;
}
if ($panel_actions) {
foreach ($panel_actions as $panel_action) {

View file

@ -22,6 +22,10 @@ final class DifferentialChangesetSearchEngine
return 'PhabricatorDifferentialApplication';
}
public function canUseInPanelContext() {
return false;
}
public function newQuery() {
$query = id(new DifferentialChangesetQuery());

View file

@ -371,7 +371,7 @@ final class DifferentialChangesetOneUpRenderer
$cell_classes = $block_diff->getNewClasses();
}
} else if ($row_type === 'old') {
if (!$old_ref) {
if (!$old_ref || !$old) {
continue;
}
@ -384,7 +384,7 @@ final class DifferentialChangesetOneUpRenderer
$new_key = null;
} else if ($row_type === 'new') {
if (!$new_ref) {
if (!$new_ref || !$new) {
continue;
}

View file

@ -23,6 +23,7 @@ final class DiffusionRepositoryBranchesManagementPanel
$has_any =
$repository->getDetail('default-branch') ||
$repository->getFetchRules() ||
$repository->getTrackOnlyRules() ||
$repository->getPermanentRefRules();

View file

@ -123,8 +123,11 @@ final class DoorkeeperBridgeAsana extends DoorkeeperBridge {
}
public function fillObjectFromData(DoorkeeperExternalObject $obj, $result) {
$id = $result['id'];
$uri = "https://app.asana.com/0/{$id}/{$id}";
$gid = $result['gid'];
$uri = urisprintf(
'https://app.asana.com/0/%s/%s',
$gid,
$gid);
$obj->setObjectURI($uri);
}

View file

@ -102,7 +102,10 @@ final class PhabricatorAsanaConfigOptions
pht('Workspace Name'));
$out[] = '| ------------ | -------------- |';
foreach ($workspaces as $workspace) {
$out[] = sprintf('| `%s` | `%s` |', $workspace['id'], $workspace['name']);
$out[] = sprintf(
'| `%s` | `%s` |',
$workspace['gid'],
$workspace['name']);
}
$out = implode("\n", $out);

View file

@ -358,7 +358,7 @@ final class DoorkeeperAsanaFeedWorker extends DoorkeeperFeedWorker {
'POST',
$subtask_data + array(
'assignee' => $phid_aid_map[$user_phid],
'completed' => $is_completed,
'completed' => (int)$is_completed,
'parent' => $parent_ref->getObjectID(),
));
@ -393,7 +393,7 @@ final class DoorkeeperAsanaFeedWorker extends DoorkeeperFeedWorker {
'PUT',
$subtask_data + array(
'assignee' => $phid_aid_map[$user_phid],
'completed' => $is_completed,
'completed' => (int)$is_completed,
));
}
@ -484,7 +484,7 @@ final class DoorkeeperAsanaFeedWorker extends DoorkeeperFeedWorker {
return array(
'name' => $title,
'notes' => $notes,
'completed' => $is_completed,
'completed' => (int)$is_completed,
);
}
@ -632,7 +632,7 @@ final class DoorkeeperAsanaFeedWorker extends DoorkeeperFeedWorker {
->setApplicationType(DoorkeeperBridgeAsana::APPTYPE_ASANA)
->setApplicationDomain(DoorkeeperBridgeAsana::APPDOMAIN_ASANA)
->setObjectType($type)
->setObjectID($result['id'])
->setObjectID($result['gid'])
->setIsVisible(true);
$xobj = $ref->newExternalObject();

View file

@ -399,7 +399,7 @@ abstract class HeraldAdapter extends Phobject {
self::CONDITION_IS_NOT_ANY => pht('is not any of'),
self::CONDITION_INCLUDE_ALL => pht('include all of'),
self::CONDITION_INCLUDE_ANY => pht('include any of'),
self::CONDITION_INCLUDE_NONE => pht('do not include'),
self::CONDITION_INCLUDE_NONE => pht('include none of'),
self::CONDITION_IS_ME => pht('is myself'),
self::CONDITION_IS_NOT_ME => pht('is not myself'),
self::CONDITION_REGEXP => pht('matches regexp'),

View file

@ -71,21 +71,30 @@ final class PhabricatorUserUsernameTransaction
}
if (!strlen($new)) {
$errors[] = $this->newRequiredError(
pht('New username is required.'), $xaction);
$errors[] = $this->newInvalidError(
pht('New username is required.'),
$xaction);
} else if (!PhabricatorUser::validateUsername($new)) {
$errors[] = $this->newInvalidError(
PhabricatorUser::describeValidUsername(), $xaction);
PhabricatorUser::describeValidUsername(),
$xaction);
}
$user = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withUsernames(array($new))
->executeOne();
if ($user) {
$errors[] = $this->newInvalidError(
pht('Another user already has that username.'), $xaction);
// See T13446. We may be changing the letter case of a username, which
// is a perfectly fine edit.
$is_self = ($user->getPHID() === $object->getPHID());
if (!$is_self) {
$errors[] = $this->newInvalidError(
pht(
'Another user already has the username "%s".',
$new),
$xaction);
}
}
}

View file

@ -257,16 +257,15 @@ final class PhabricatorRepositoryPullEngine
$path = rtrim($repository->getLocalPath(), '/');
if ($repository->isHosted()) {
$repository->execxRemoteCommand(
'init --bare -- %s',
$path);
} else {
$repository->execxRemoteCommand(
'clone --bare -- %P %s',
$repository->getRemoteURIEnvelope(),
$path);
}
// See T13448. In all cases, we create repositories by using "git init"
// to build a bare, empty working copy. If we try to use "git clone"
// instead, we'll pull in too many refs if "Fetch Refs" is also
// configured. There's no apparent way to make "git clone" behave narrowly
// and no apparent reason to bother.
$repository->execxRemoteCommand(
'init --bare -- %s',
$path);
}
@ -290,29 +289,27 @@ final class PhabricatorRepositoryPullEngine
$files = Filesystem::listDirectory($path, $include_hidden = true);
if (!$files) {
$message = pht(
"Expected to find a git repository at '%s', but there ".
"is an empty directory there. Remove the directory: the daemon ".
"will run '%s' for you.",
$path,
'git clone');
'Expected to find a Git repository at "%s", but there is an '.
'empty directory there. Remove the directory. A daemon will '.
'construct the working copy for you.',
$path);
} else {
$message = pht(
"Expected to find a git repository at '%s', but there is ".
"a non-repository directory (with other stuff in it) there. Move ".
"or remove this directory (or reconfigure the repository to use a ".
"different directory), and then either clone a repository ".
"yourself or let the daemon do it.",
'Expected to find a Git repository at "%s", but there is '.
'a non-repository directory (with other stuff in it) there. '.
'Move or remove this directory. A daemon will construct '.
'the working copy for you.',
$path);
}
} else if (is_file($path)) {
$message = pht(
"Expected to find a git repository at '%s', but there is a ".
"file there instead. Remove it and let the daemon clone a ".
"repository for you.",
'Expected to find a Git repository at "%s", but there is a '.
'file there instead. Move or remove this file. A daemon will '.
'construct the working copy for you.',
$path);
} else {
$message = pht(
"Expected to find a git repository at '%s', but did not.",
'Expected to find a git repository at "%s", but did not.',
$path);
}
} else {
@ -327,11 +324,10 @@ final class PhabricatorRepositoryPullEngine
} else if (!Filesystem::pathsAreEquivalent($repo_path, $path)) {
$err = true;
$message = pht(
"Expected to find repo at '%s', but the actual git repository root ".
"for this directory is '%s'. Something is misconfigured. ".
"The repository's 'Local Path' should be set to some place where ".
"the daemon can check out a working copy, ".
"and should not be inside another git repository.",
'Expected to find a Git repository at "%s", but the actual Git '.
'repository root for this directory is "%s". Something is '.
'misconfigured. This directory should be writable by the daemons '.
'and not inside another Git repository.',
$path,
$repo_path);
}
@ -353,13 +349,56 @@ final class PhabricatorRepositoryPullEngine
// Load the refs we're planning to fetch from the remote repository.
$remote_refs = $this->loadGitRemoteRefs(
$repository,
$repository->getRemoteURIEnvelope());
$repository->getRemoteURIEnvelope(),
$is_local = false);
// Load the refs we're planning to fetch from the local repository, by
// using the local working copy path as the "remote" repository URI.
$local_refs = $this->loadGitRemoteRefs(
$repository,
new PhutilOpaqueEnvelope($path));
new PhutilOpaqueEnvelope($path),
$is_local = true);
// See T13448. The "git fetch --prune ..." flag only prunes local refs
// matching the refspecs we pass it. If "Fetch Refs" is configured, we'll
// pass it a very narrow list of refspecs, and it won't prune older refs
// that aren't currently subject to fetching.
// Since we want to prune everything that isn't (a) on the fetch list and
// (b) in the remote, handle pruning of any surplus leftover refs ourselves
// before we fetch anything.
// (We don't have to do this if "Fetch Refs" isn't set up, since "--prune"
// will work in that case, but it's a little simpler to always go down the
// same code path.)
$surplus_refs = array();
foreach ($local_refs as $local_ref => $local_hash) {
$remote_hash = idx($remote_refs, $local_ref);
if ($remote_hash === null) {
$surplus_refs[] = $local_ref;
}
}
if ($surplus_refs) {
$this->log(
pht(
'Found %s surplus local ref(s) to delete.',
phutil_count($surplus_refs)));
foreach ($surplus_refs as $surplus_ref) {
$this->log(
pht(
'Deleting surplus local ref "%s" ("%s").',
$surplus_ref,
$local_refs[$surplus_ref]));
$repository->execLocalCommand(
'update-ref -d %R --',
$surplus_ref);
unset($local_refs[$surplus_ref]);
}
}
if ($remote_refs === $local_refs) {
$this->log(
@ -378,7 +417,7 @@ final class PhabricatorRepositoryPullEngine
// checked out. See T13280.
$future = $repository->getRemoteCommandFuture(
'fetch --prune --update-head-ok -- %P %Ls',
'fetch --no-tags --update-head-ok -- %P %Ls',
$repository->getRemoteURIEnvelope(),
$fetch_rules);
@ -474,21 +513,32 @@ final class PhabricatorRepositoryPullEngine
private function loadGitRemoteRefs(
PhabricatorRepository $repository,
PhutilOpaqueEnvelope $remote_envelope) {
PhutilOpaqueEnvelope $remote_envelope,
$is_local) {
$ref_rules = $this->getGitRefRules($repository);
// See T13448. When listing local remotes, we want to list everything,
// not just refs we expect to fetch. This allows us to detect that we have
// undesirable refs (which have been deleted in the remote, but are still
// present locally) so we can update our state to reflect the correct
// remote state.
// NOTE: "git ls-remote" does not support "--" until circa January 2016.
// See T12416. None of the flags to "ls-remote" appear dangerous, but
// refuse to list any refs beginning with "-" just in case.
if ($is_local) {
$ref_rules = array();
} else {
$ref_rules = $this->getGitRefRules($repository);
foreach ($ref_rules as $ref_rule) {
if (preg_match('/^-/', $ref_rule)) {
throw new Exception(
pht(
'Refusing to list potentially dangerous ref ("%s") beginning '.
'with "-".',
$ref_rule));
// NOTE: "git ls-remote" does not support "--" until circa January 2016.
// See T12416. None of the flags to "ls-remote" appear dangerous, but
// refuse to list any refs beginning with "-" just in case.
foreach ($ref_rules as $ref_rule) {
if (preg_match('/^-/', $ref_rule)) {
throw new Exception(
pht(
'Refusing to list potentially dangerous ref ("%s") beginning '.
'with "-".',
$ref_rule));
}
}
}

View file

@ -33,6 +33,8 @@ final class PhabricatorSearchApplication extends PhabricatorApplication {
'index/(?P<phid>[^/]+)/' => 'PhabricatorSearchIndexController',
'hovercard/'
=> 'PhabricatorSearchHovercardController',
'handle/(?P<phid>[^/]+)/'
=> 'PhabricatorSearchHandleController',
'edit/' => array(
'key/(?P<queryKey>[^/]+)/' => 'PhabricatorSearchEditController',
'id/(?P<id>[^/]+)/' => 'PhabricatorSearchEditController',

View file

@ -0,0 +1,89 @@
<?php
final class PhabricatorSearchHandleController
extends PhabricatorSearchBaseController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$phid = $request->getURIData('phid');
$handles = $viewer->loadHandles(array($phid));
$handle = $handles[$phid];
$cancel_uri = $handle->getURI();
if (!$cancel_uri) {
$cancel_uri = '/';
}
$rows = array();
$rows[] = array(
pht('PHID'),
$phid,
);
$rows[] = array(
pht('PHID Type'),
phid_get_type($phid),
);
$rows[] = array(
pht('URI'),
$handle->getURI(),
);
$icon = $handle->getIcon();
if ($icon !== null) {
$icon = id(new PHUIIconView())
->setIcon($handle->getIcon());
}
$rows[] = array(
pht('Icon'),
$icon,
);
$rows[] = array(
pht('Object Name'),
$handle->getObjectName(),
);
$rows[] = array(
pht('Name'),
$handle->getName(),
);
$rows[] = array(
pht('Full Name'),
$handle->getFullName(),
);
$rows[] = array(
pht('Tag'),
$handle->renderTag(),
);
$rows[] = array(
pht('Link'),
$handle->renderLink(),
);
$table = id(new AphrontTableView($rows))
->setColumnClasses(
array(
'header',
'wide',
));
return $this->newDialog()
->setTitle(pht('Handle: %s', $phid))
->setWidth(AphrontDialogView::WIDTH_FORM)
->appendChild($table)
->addCancelButton($cancel_uri, pht('Done'));
}
}

View file

@ -14,6 +14,12 @@ final class PhabricatorSystemApplication extends PhabricatorApplication {
return true;
}
public function getEventListeners() {
return array(
new PhabricatorSystemDebugUIEventListener(),
);
}
public function getRoutes() {
return array(
'/status/' => 'PhabricatorStatusController',

View file

@ -0,0 +1,58 @@
<?php
final class PhabricatorSystemDebugUIEventListener
extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
$this->handleActionEvent($event);
break;
}
}
private function handleActionEvent($event) {
$viewer = $event->getUser();
$object = $event->getValue('object');
if (!PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
return;
}
if (!$object || !$object->getPHID()) {
// If we have no object, or the object doesn't have a PHID, we can't
// do anything useful.
return;
}
$phid = $object->getPHID();
$submenu = array();
$submenu[] = id(new PhabricatorActionView())
->setIcon('fa-asterisk')
->setName(pht('View Handle'))
->setHref(urisprintf('/search/handle/%s/', $phid))
->setWorkflow(true);
$submenu[] = id(new PhabricatorActionView())
->setIcon('fa-address-card-o')
->setName(pht('View Hovercard'))
->setHref(urisprintf('/search/hovercard/?phids[]=%s', $phid));
$developer_action = id(new PhabricatorActionView())
->setName(pht('Advanced/Developer...'))
->setIcon('fa-magic')
->setOrder(9001)
->setSubmenu($submenu);
$actions = $event->getValue('actions');
$actions[] = $developer_action;
$event->setValue('actions', $actions);
}
}

View file

@ -1133,6 +1133,8 @@ abstract class PhabricatorApplicationTransaction
} else {
$fragments = array();
foreach ($moves as $move) {
$to_column = $move['columnPHID'];
$board_phid = $move['boardPHID'];
$fragments[] = pht(
'%s (%s)',
$this->renderHandleLink($board_phid),

View file

@ -52,6 +52,14 @@ final class PhabricatorActionListView extends AphrontTagView {
$action->setViewer($viewer);
}
$sort = array();
foreach ($actions as $key => $action) {
$sort[$key] = id(new PhutilSortVector())
->addInt($action->getOrder());
}
$sort = msortv($sort, 'getSelf');
$actions = array_select_keys($actions, array_keys($sort));
require_celerity_resource('phabricator-action-list-view-css');
$items = array();

View file

@ -379,10 +379,11 @@ final class PHUIObjectItemView extends AphrontTagView {
if ($this->objectName) {
$header_name[] = array(
phutil_tag(
javelin_tag(
'span',
array(
'class' => 'phui-oi-objname',
'sigil' => 'ungrabbable',
),
$this->objectName),
' ',

View file

@ -181,6 +181,15 @@ JX.install('DraggableList', {
return;
}
// See T13452. If this is an ungrabble part of the item, don't start a
// drag. We use this to allow users to select text on cards.
var target = e.getTarget();
if (target) {
if (JX.Stratcom.hasSigil(target, 'ungrabbable')) {
return;
}
}
if (JX.Stratcom.pass()) {
// Let other handlers deal with this event before we do.
return;