@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Assign RepositoryIdentity objects to commits

Summary: Depends on D19429. Depends on D19423. Ref T12164. This creates new columns `authorIdentityPHID` and `committerIdentityPHID` on commit objects and starts populating them. Also adds the ability to explicitly set an Identity's assignee to "unassigned()" to null out an incorrect auto-assign. Adds more search functionality to identities. Also creates a daemon task for handling users adding new email address and attempts to associate unclaimed identities.

Test Plan: Imported some repos, watched new columns get populated. Added a new email address for a previous commit, saw daemon job run and assign the identity to the new user. Searched for identities in various and sundry ways.

Reviewers: epriestley

Reviewed By: epriestley

Subscribers: Korvin, PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T12164

Differential Revision: https://secure.phabricator.com/D19443

+297 -9
+3
resources/sql/autopatches/20180509.repo_identity.commits.sql
··· 1 + ALTER TABLE {$NAMESPACE}_repository.repository_commit 2 + ADD COLUMN authorIdentityPHID VARBINARY(64) DEFAULT NULL, 3 + ADD COLUMN committerIdentityPHID VARBINARY(64) DEFAULT NULL;
+10
src/__phutil_library_map__.php
··· 815 815 'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php', 816 816 'DiffusionHistoryView' => 'applications/diffusion/view/DiffusionHistoryView.php', 817 817 'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php', 818 + 'DiffusionIdentityAssigneeDatasource' => 'applications/diffusion/typeahead/DiffusionIdentityAssigneeDatasource.php', 819 + 'DiffusionIdentityAssigneeEditField' => 'applications/diffusion/editfield/DiffusionIdentityAssigneeEditField.php', 820 + 'DiffusionIdentityAssigneeSearchField' => 'applications/diffusion/searchfield/DiffusionIdentityAssigneeSearchField.php', 818 821 'DiffusionIdentityEditController' => 'applications/diffusion/controller/DiffusionIdentityEditController.php', 819 822 'DiffusionIdentityListController' => 'applications/diffusion/controller/DiffusionIdentityListController.php', 823 + 'DiffusionIdentityUnassignedDatasource' => 'applications/diffusion/typeahead/DiffusionIdentityUnassignedDatasource.php', 820 824 'DiffusionIdentityViewController' => 'applications/diffusion/controller/DiffusionIdentityViewController.php', 821 825 'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php', 822 826 'DiffusionInlineCommentPreviewController' => 'applications/diffusion/controller/DiffusionInlineCommentPreviewController.php', ··· 4092 4096 'PhabricatorRepositoryGraphStream' => 'applications/repository/daemon/PhabricatorRepositoryGraphStream.php', 4093 4097 'PhabricatorRepositoryIdentity' => 'applications/repository/storage/PhabricatorRepositoryIdentity.php', 4094 4098 'PhabricatorRepositoryIdentityAssignTransaction' => 'applications/repository/xaction/PhabricatorRepositoryIdentityAssignTransaction.php', 4099 + 'PhabricatorRepositoryIdentityChangeWorker' => 'applications/repository/worker/PhabricatorRepositoryIdentityChangeWorker.php', 4095 4100 'PhabricatorRepositoryIdentityEditEngine' => 'applications/repository/engine/PhabricatorRepositoryIdentityEditEngine.php', 4096 4101 'PhabricatorRepositoryIdentityFerretEngine' => 'applications/repository/search/PhabricatorRepositoryIdentityFerretEngine.php', 4097 4102 'PhabricatorRepositoryIdentityPHIDType' => 'applications/repository/phid/PhabricatorRepositoryIdentityPHIDType.php', ··· 6166 6171 'DiffusionHistoryTableView' => 'DiffusionHistoryView', 6167 6172 'DiffusionHistoryView' => 'DiffusionView', 6168 6173 'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 6174 + 'DiffusionIdentityAssigneeDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 6175 + 'DiffusionIdentityAssigneeEditField' => 'PhabricatorTokenizerEditField', 6176 + 'DiffusionIdentityAssigneeSearchField' => 'PhabricatorSearchTokenizerField', 6169 6177 'DiffusionIdentityEditController' => 'DiffusionController', 6170 6178 'DiffusionIdentityListController' => 'DiffusionController', 6179 + 'DiffusionIdentityUnassignedDatasource' => 'PhabricatorTypeaheadDatasource', 6171 6180 'DiffusionIdentityViewController' => 'DiffusionController', 6172 6181 'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController', 6173 6182 'DiffusionInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController', ··· 10000 10009 'PhabricatorApplicationTransactionInterface', 10001 10010 ), 10002 10011 'PhabricatorRepositoryIdentityAssignTransaction' => 'PhabricatorRepositoryIdentityTransactionType', 10012 + 'PhabricatorRepositoryIdentityChangeWorker' => 'PhabricatorWorker', 10003 10013 'PhabricatorRepositoryIdentityEditEngine' => 'PhabricatorEditEngine', 10004 10014 'PhabricatorRepositoryIdentityFerretEngine' => 'PhabricatorFerretEngine', 10005 10015 'PhabricatorRepositoryIdentityPHIDType' => 'PhabricatorPHIDType',
+15 -3
src/applications/diffusion/controller/DiffusionIdentityViewController.php
··· 104 104 } 105 105 $properties->addProperty( 106 106 pht('Effective User'), 107 - $viewer->renderHandle($effective_phid)); 107 + $this->buildPropertyValue($effective_phid)); 108 108 $properties->addProperty( 109 109 pht('Automatically Detected User'), 110 - $viewer->renderHandle($automatic_phid)); 110 + $this->buildPropertyValue($automatic_phid)); 111 111 $properties->addProperty( 112 112 pht('Manually Set User'), 113 - $viewer->renderHandle($manual_phid)); 113 + $this->buildPropertyValue($manual_phid)); 114 114 115 115 $header = id(new PHUIHeaderView()) 116 116 ->setHeader(array(pht('Identity Assignments'), $tag)); ··· 119 119 ->setHeader($header) 120 120 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 121 121 ->addPropertyList($properties); 122 + } 123 + 124 + private function buildPropertyValue($value) { 125 + $viewer = $this->getViewer(); 126 + 127 + if ($value == DiffusionIdentityUnassignedDatasource::FUNCTION_TOKEN) { 128 + return phutil_tag('em', array(), pht('Explicitly Unassigned')); 129 + } else if (!$value) { 130 + return null; 131 + } else { 132 + return $viewer->renderHandle($value); 133 + } 122 134 } 123 135 }
+22
src/applications/diffusion/editfield/DiffusionIdentityAssigneeEditField.php
··· 1 + <?php 2 + 3 + final class DiffusionIdentityAssigneeEditField 4 + extends PhabricatorTokenizerEditField { 5 + 6 + protected function newDatasource() { 7 + return new DiffusionIdentityAssigneeDatasource(); 8 + } 9 + 10 + protected function newHTTPParameterType() { 11 + return new AphrontUserListHTTPParameterType(); 12 + } 13 + 14 + protected function newConduitParameterType() { 15 + if ($this->getIsSingleValue()) { 16 + return new ConduitUserParameterType(); 17 + } else { 18 + return new ConduitUserListParameterType(); 19 + } 20 + } 21 + 22 + }
+16
src/applications/diffusion/query/DiffusionRepositoryIdentitySearchEngine.php
··· 17 17 18 18 protected function buildCustomSearchFields() { 19 19 return array( 20 + id(new DiffusionIdentityAssigneeSearchField()) 21 + ->setLabel(pht('Assigned To')) 22 + ->setKey('assignee') 23 + ->setDescription(pht('Search for identities by assignee.')), 24 + id(new PhabricatorSearchTextField()) 25 + ->setLabel(pht('Identity Contains')) 26 + ->setKey('match') 27 + ->setDescription(pht('Search for identities by substring.')), 20 28 id(new PhabricatorSearchThreeStateField()) 21 29 ->setLabel(pht('Is Assigned')) 22 30 ->setKey('hasEffectivePHID') ··· 32 40 33 41 if ($map['hasEffectivePHID'] !== null) { 34 42 $query->withHasEffectivePHID($map['hasEffectivePHID']); 43 + } 44 + 45 + if ($map['match'] !== null) { 46 + $query->withIdentityNameLike($map['match']); 47 + } 48 + 49 + if ($map['assignee']) { 50 + $query->withAssigneePHIDs($map['assignee']); 35 51 } 36 52 37 53 return $query;
+22
src/applications/diffusion/searchfield/DiffusionIdentityAssigneeSearchField.php
··· 1 + <?php 2 + 3 + final class DiffusionIdentityAssigneeSearchField 4 + extends PhabricatorSearchTokenizerField { 5 + 6 + protected function getDefaultValue() { 7 + return array(); 8 + } 9 + 10 + protected function getValueFromRequest(AphrontRequest $request, $key) { 11 + return $this->getUsersFromRequest($request, $key); 12 + } 13 + 14 + protected function newDatasource() { 15 + return new DiffusionIdentityAssigneeDatasource(); 16 + } 17 + 18 + protected function newConduitParameterType() { 19 + return new ConduitUserListParameterType(); 20 + } 21 + 22 + }
+21
src/applications/diffusion/typeahead/DiffusionIdentityAssigneeDatasource.php
··· 1 + <?php 2 + 3 + final class DiffusionIdentityAssigneeDatasource 4 + extends PhabricatorTypeaheadCompositeDatasource { 5 + 6 + public function getBrowseTitle() { 7 + return pht('Browse Assignee'); 8 + } 9 + 10 + public function getPlaceholderText() { 11 + return pht('Type a username or function...'); 12 + } 13 + 14 + public function getComponentDatasources() { 15 + return array( 16 + new PhabricatorPeopleDatasource(), 17 + new DiffusionIdentityUnassignedDatasource(), 18 + ); 19 + } 20 + 21 + }
+77
src/applications/diffusion/typeahead/DiffusionIdentityUnassignedDatasource.php
··· 1 + <?php 2 + 3 + final class DiffusionIdentityUnassignedDatasource 4 + extends PhabricatorTypeaheadDatasource { 5 + 6 + const FUNCTION_TOKEN = 'unassigned()'; 7 + 8 + public function getBrowseTitle() { 9 + return pht('Browse Explicitly Unassigned'); 10 + } 11 + 12 + public function getPlaceholderText() { 13 + return pht('Type "unassigned"...'); 14 + } 15 + 16 + public function getDatasourceApplicationClass() { 17 + return 'PhabricatorDiffusionApplication'; 18 + } 19 + 20 + public function getDatasourceFunctions() { 21 + return array( 22 + 'unassigned' => array( 23 + 'name' => pht('Explicitly Unassigned'), 24 + 'summary' => pht('Find results which are not assigned.'), 25 + 'description' => pht( 26 + "This function includes results which have been explicitly ". 27 + "unassigned. Use a query like this to find explicitly ". 28 + "unassigned results:\n\n%s\n\n". 29 + "If you combine this function with other functions, the query will ". 30 + "return results which match the other selectors //or// have no ". 31 + "assignee. For example, this query will find results which are ". 32 + "assigned to `alincoln`, and will also find results which have been ". 33 + "unassigned:\n\n%s", 34 + '> unassigned()', 35 + '> alincoln, unassigned()'), 36 + ), 37 + ); 38 + } 39 + 40 + public function loadResults() { 41 + $results = array( 42 + $this->buildUnassignedResult(), 43 + ); 44 + return $this->filterResultsAgainstTokens($results); 45 + } 46 + 47 + protected function evaluateFunction($function, array $argv_list) { 48 + $results = array(); 49 + 50 + foreach ($argv_list as $argv) { 51 + $results[] = self::FUNCTION_TOKEN; 52 + } 53 + 54 + return $results; 55 + } 56 + 57 + public function renderFunctionTokens($function, array $argv_list) { 58 + $results = array(); 59 + foreach ($argv_list as $argv) { 60 + $results[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult( 61 + $this->buildUnassignedResult()); 62 + } 63 + return $results; 64 + } 65 + 66 + private function buildUnassignedResult() { 67 + $name = pht('Unassigned'); 68 + return $this->newFunctionResult() 69 + ->setName($name.' unassigned') 70 + ->setDisplayName($name) 71 + ->setIcon('fa-ban') 72 + ->setPHID('unassigned()') 73 + ->setUnique(true) 74 + ->addAttribute(pht('Select results with no owner.')); 75 + } 76 + 77 + }
+6
src/applications/people/editor/PhabricatorUserEditor.php
··· 420 420 $user->endWriteLocking(); 421 421 $user->saveTransaction(); 422 422 423 + // Try and match this new address against unclaimed `RepositoryIdentity`s 424 + PhabricatorWorker::scheduleTask( 425 + 'PhabricatorRepositoryIdentityChangeWorker', 426 + array('userPHID' => $user->getPHID()), 427 + array('objectPHID' => $user->getPHID())); 428 + 423 429 return $this; 424 430 } 425 431
+1 -1
src/applications/repository/engine/PhabricatorRepositoryIdentityEditEngine.php
··· 75 75 76 76 protected function buildCustomEditFields($object) { 77 77 return array( 78 - id(new PhabricatorUsersEditField()) 78 + id(new DiffusionIdentityAssigneeEditField()) 79 79 ->setKey('manuallySetUserPHID') 80 80 ->setLabel(pht('Assigned To')) 81 81 ->setDescription(pht('Override this identity\'s assignment.'))
+40 -1
src/applications/repository/query/PhabricatorRepositoryIdentityQuery.php
··· 6 6 private $ids; 7 7 private $phids; 8 8 private $identityNames; 9 + private $emailAddress; 10 + private $assigneePHIDs; 11 + private $identityNameLike; 9 12 private $hasEffectivePHID; 10 13 11 14 public function withIDs(array $ids) { ··· 20 23 21 24 public function withIdentityNames(array $names) { 22 25 $this->identityNames = $names; 26 + return $this; 27 + } 28 + 29 + public function withIdentityNameLike($name_like) { 30 + $this->identityNameLike = $name_like; 31 + return $this; 32 + } 33 + 34 + public function withEmailAddress($address) { 35 + $this->emailAddress = $address; 36 + return $this; 37 + } 38 + 39 + public function withAssigneePHIDs(array $assignees) { 40 + $this->assigneePHIDs = $assignees; 23 41 return $this; 24 42 } 25 43 ··· 57 75 $this->phids); 58 76 } 59 77 60 - if ($this->hasEffectivePHID !== null) { 78 + if ($this->assigneePHIDs !== null) { 79 + $where[] = qsprintf( 80 + $conn, 81 + 'repository_identity.currentEffectiveUserPHID IN (%Ls)', 82 + $this->assigneePHIDs); 83 + } 61 84 85 + if ($this->hasEffectivePHID !== null) { 62 86 if ($this->hasEffectivePHID) { 63 87 $where[] = qsprintf( 64 88 $conn, ··· 80 104 $conn, 81 105 'repository_identity.identityNameHash IN (%Ls)', 82 106 $name_hashes); 107 + } 108 + 109 + if ($this->emailAddress !== null) { 110 + $identity_style = "<{$this->emailAddress}>"; 111 + $where[] = qsprintf( 112 + $conn, 113 + 'repository_identity.identityNameRaw LIKE %<', 114 + $identity_style); 115 + } 116 + 117 + if ($this->identityNameLike != null) { 118 + $where[] = qsprintf( 119 + $conn, 120 + 'repository_identity.identityNameRaw LIKE %~', 121 + $this->identityNameLike); 83 122 } 84 123 85 124 return $where;
+4
src/applications/repository/storage/PhabricatorRepositoryCommit.php
··· 21 21 22 22 protected $repositoryID; 23 23 protected $phid; 24 + protected $authorIdentityPHID; 25 + protected $committerIdentityPHID; 24 26 protected $commitIdentifier; 25 27 protected $epoch; 26 28 protected $mailKey; ··· 113 115 'commitIdentifier' => 'text40', 114 116 'mailKey' => 'bytes20', 115 117 'authorPHID' => 'phid?', 118 + 'authorIdentityPHID' => 'phid?', 119 + 'committerIdentityPHID' => 'phid?', 116 120 'auditStatus' => 'uint32', 117 121 'summary' => 'text255', 118 122 'importStatus' => 'uint32',
+1
src/applications/repository/storage/PhabricatorRepositoryIdentity.php
··· 80 80 public function getCapabilities() { 81 81 return array( 82 82 PhabricatorPolicyCapability::CAN_VIEW, 83 + PhabricatorPolicyCapability::CAN_EDIT, 83 84 ); 84 85 } 85 86
+34
src/applications/repository/worker/PhabricatorRepositoryIdentityChangeWorker.php
··· 1 + <?php 2 + 3 + final class PhabricatorRepositoryIdentityChangeWorker 4 + extends PhabricatorWorker { 5 + 6 + protected function doWork() { 7 + $viewer = PhabricatorUser::getOmnipotentUser(); 8 + 9 + $task_data = $this->getTaskData(); 10 + $user_phid = idx($task_data, 'userPHID'); 11 + 12 + $user = id(new PhabricatorPeopleQuery()) 13 + ->withPHIDs(array($user_phid)) 14 + ->setViewer($viewer) 15 + ->executeOne(); 16 + 17 + $emails = id(new PhabricatorUserEmail())->loadAllWhere( 18 + 'userPHID = %s ORDER BY address', 19 + $user->getPHID()); 20 + 21 + foreach ($emails as $email) { 22 + $identities = id(new PhabricatorRepositoryIdentityQuery()) 23 + ->setViewer($viewer) 24 + ->withEmailAddress($email->getAddress()) 25 + ->execute(); 26 + 27 + foreach ($identities as $identity) { 28 + $identity->setAutomaticGuessedUserPHID($user->getPHID()) 29 + ->save(); 30 + } 31 + } 32 + } 33 + 34 + }
+7
src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php
··· 110 110 $data->setCommitDetail('authorEmail', $ref->getAuthorEmail()); 111 111 112 112 $data->setCommitDetail( 113 + 'authorIdentityPHID', $author_identity->getPHID()); 114 + $data->setCommitDetail( 113 115 'authorPHID', 114 116 $this->resolveUserPHID($commit, $author)); 115 117 ··· 124 126 $data->setCommitDetail( 125 127 'committerPHID', 126 128 $this->resolveUserPHID($commit, $committer)); 129 + $data->setCommitDetail( 130 + 'committerIdentityPHID', $committer_identity->getPHID()); 127 131 } 128 132 129 133 $repository = $this->repository; ··· 160 164 if ($author_phid != $commit->getAuthorPHID()) { 161 165 $commit->setAuthorPHID($author_phid); 162 166 } 167 + 168 + $commit->setAuthorIdentityPHID($author_identity->getPHID()); 169 + $commit->setCommitterIdentityPHID($committer_identity->getPHID()); 163 170 164 171 $commit->setSummary($data->getSummary()); 165 172 $commit->save();
+18 -4
src/applications/repository/xaction/PhabricatorRepositoryIdentityAssignTransaction.php
··· 21 21 return pht( 22 22 '%s assigned this identity to %s.', 23 23 $this->renderAuthor(), 24 - $this->renderHandle($new)); 24 + $this->renderIdentityHandle($new)); 25 25 } else if (!$new) { 26 26 return pht( 27 27 '%s removed %s as the assignee of this identity.', 28 28 $this->renderAuthor(), 29 - $this->renderHandle($old)); 29 + $this->renderIdentityHandle($old)); 30 30 } else { 31 31 return pht( 32 32 '%s changed the assigned user for this identity from %s to %s.', 33 33 $this->renderAuthor(), 34 - $this->renderHandle($old), 35 - $this->renderHandle($new)); 34 + $this->renderIdentityHandle($old), 35 + $this->renderIdentityHandle($new)); 36 + } 37 + } 38 + 39 + private function renderIdentityHandle($handle) { 40 + $unassigned_token = DiffusionIdentityUnassignedDatasource::FUNCTION_TOKEN; 41 + if ($handle === $unassigned_token) { 42 + return phutil_tag('em', array(), pht('Explicitly Unassigned')); 43 + } else { 44 + return $this->renderHandle($handle); 36 45 } 37 46 } 38 47 39 48 public function validateTransactions($object, array $xactions) { 40 49 $errors = array(); 50 + $unassigned_token = DiffusionIdentityUnassignedDatasource::FUNCTION_TOKEN; 41 51 42 52 foreach ($xactions as $xaction) { 43 53 $old = $xaction->getOldValue(); ··· 47 57 } 48 58 49 59 if ($new === $old) { 60 + continue; 61 + } 62 + 63 + if ($new === $unassigned_token) { 50 64 continue; 51 65 } 52 66