diff --git a/resources/sql/autopatches/20180509.repo_identity.commits.sql b/resources/sql/autopatches/20180509.repo_identity.commits.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20180509.repo_identity.commits.sql @@ -0,0 +1,3 @@ +ALTER TABLE {$NAMESPACE}_repository.repository_commit + ADD COLUMN authorIdentityPHID VARBINARY(64) DEFAULT NULL, + ADD COLUMN committerIdentityPHID VARBINARY(64) DEFAULT NULL; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -815,8 +815,12 @@ 'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php', 'DiffusionHistoryView' => 'applications/diffusion/view/DiffusionHistoryView.php', 'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php', + 'DiffusionIdentityAssigneeDatasource' => 'applications/diffusion/typeahead/DiffusionIdentityAssigneeDatasource.php', + 'DiffusionIdentityAssigneeEditField' => 'applications/diffusion/editfield/DiffusionIdentityAssigneeEditField.php', + 'DiffusionIdentityAssigneeSearchField' => 'applications/diffusion/searchfield/DiffusionIdentityAssigneeSearchField.php', 'DiffusionIdentityEditController' => 'applications/diffusion/controller/DiffusionIdentityEditController.php', 'DiffusionIdentityListController' => 'applications/diffusion/controller/DiffusionIdentityListController.php', + 'DiffusionIdentityUnassignedDatasource' => 'applications/diffusion/typeahead/DiffusionIdentityUnassignedDatasource.php', 'DiffusionIdentityViewController' => 'applications/diffusion/controller/DiffusionIdentityViewController.php', 'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php', 'DiffusionInlineCommentPreviewController' => 'applications/diffusion/controller/DiffusionInlineCommentPreviewController.php', @@ -4092,6 +4096,7 @@ 'PhabricatorRepositoryGraphStream' => 'applications/repository/daemon/PhabricatorRepositoryGraphStream.php', 'PhabricatorRepositoryIdentity' => 'applications/repository/storage/PhabricatorRepositoryIdentity.php', 'PhabricatorRepositoryIdentityAssignTransaction' => 'applications/repository/xaction/PhabricatorRepositoryIdentityAssignTransaction.php', + 'PhabricatorRepositoryIdentityChangeWorker' => 'applications/repository/worker/PhabricatorRepositoryIdentityChangeWorker.php', 'PhabricatorRepositoryIdentityEditEngine' => 'applications/repository/engine/PhabricatorRepositoryIdentityEditEngine.php', 'PhabricatorRepositoryIdentityFerretEngine' => 'applications/repository/search/PhabricatorRepositoryIdentityFerretEngine.php', 'PhabricatorRepositoryIdentityPHIDType' => 'applications/repository/phid/PhabricatorRepositoryIdentityPHIDType.php', @@ -6166,8 +6171,12 @@ 'DiffusionHistoryTableView' => 'DiffusionHistoryView', 'DiffusionHistoryView' => 'DiffusionView', 'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', + 'DiffusionIdentityAssigneeDatasource' => 'PhabricatorTypeaheadCompositeDatasource', + 'DiffusionIdentityAssigneeEditField' => 'PhabricatorTokenizerEditField', + 'DiffusionIdentityAssigneeSearchField' => 'PhabricatorSearchTokenizerField', 'DiffusionIdentityEditController' => 'DiffusionController', 'DiffusionIdentityListController' => 'DiffusionController', + 'DiffusionIdentityUnassignedDatasource' => 'PhabricatorTypeaheadDatasource', 'DiffusionIdentityViewController' => 'DiffusionController', 'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController', 'DiffusionInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController', @@ -10000,6 +10009,7 @@ 'PhabricatorApplicationTransactionInterface', ), 'PhabricatorRepositoryIdentityAssignTransaction' => 'PhabricatorRepositoryIdentityTransactionType', + 'PhabricatorRepositoryIdentityChangeWorker' => 'PhabricatorWorker', 'PhabricatorRepositoryIdentityEditEngine' => 'PhabricatorEditEngine', 'PhabricatorRepositoryIdentityFerretEngine' => 'PhabricatorFerretEngine', 'PhabricatorRepositoryIdentityPHIDType' => 'PhabricatorPHIDType', diff --git a/src/applications/diffusion/controller/DiffusionIdentityViewController.php b/src/applications/diffusion/controller/DiffusionIdentityViewController.php --- a/src/applications/diffusion/controller/DiffusionIdentityViewController.php +++ b/src/applications/diffusion/controller/DiffusionIdentityViewController.php @@ -104,13 +104,13 @@ } $properties->addProperty( pht('Effective User'), - $viewer->renderHandle($effective_phid)); + $this->buildPropertyValue($effective_phid)); $properties->addProperty( pht('Automatically Detected User'), - $viewer->renderHandle($automatic_phid)); + $this->buildPropertyValue($automatic_phid)); $properties->addProperty( pht('Manually Set User'), - $viewer->renderHandle($manual_phid)); + $this->buildPropertyValue($manual_phid)); $header = id(new PHUIHeaderView()) ->setHeader(array(pht('Identity Assignments'), $tag)); @@ -120,4 +120,16 @@ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); } + + private function buildPropertyValue($value) { + $viewer = $this->getViewer(); + + if ($value == DiffusionIdentityUnassignedDatasource::FUNCTION_TOKEN) { + return phutil_tag('em', array(), pht('Explicitly Unassigned')); + } else if (!$value) { + return null; + } else { + return $viewer->renderHandle($value); + } + } } diff --git a/src/applications/diffusion/editfield/DiffusionIdentityAssigneeEditField.php b/src/applications/diffusion/editfield/DiffusionIdentityAssigneeEditField.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/editfield/DiffusionIdentityAssigneeEditField.php @@ -0,0 +1,22 @@ +getIsSingleValue()) { + return new ConduitUserParameterType(); + } else { + return new ConduitUserListParameterType(); + } + } + +} diff --git a/src/applications/diffusion/query/DiffusionRepositoryIdentitySearchEngine.php b/src/applications/diffusion/query/DiffusionRepositoryIdentitySearchEngine.php --- a/src/applications/diffusion/query/DiffusionRepositoryIdentitySearchEngine.php +++ b/src/applications/diffusion/query/DiffusionRepositoryIdentitySearchEngine.php @@ -17,6 +17,14 @@ protected function buildCustomSearchFields() { return array( + id(new DiffusionIdentityAssigneeSearchField()) + ->setLabel(pht('Assigned To')) + ->setKey('assignee') + ->setDescription(pht('Search for identities by assignee.')), + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Identity Contains')) + ->setKey('match') + ->setDescription(pht('Search for identities by substring.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Is Assigned')) ->setKey('hasEffectivePHID') @@ -34,6 +42,14 @@ $query->withHasEffectivePHID($map['hasEffectivePHID']); } + if ($map['match'] !== null) { + $query->withIdentityNameLike($map['match']); + } + + if ($map['assignee']) { + $query->withAssigneePHIDs($map['assignee']); + } + return $query; } diff --git a/src/applications/diffusion/searchfield/DiffusionIdentityAssigneeSearchField.php b/src/applications/diffusion/searchfield/DiffusionIdentityAssigneeSearchField.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/searchfield/DiffusionIdentityAssigneeSearchField.php @@ -0,0 +1,22 @@ +getUsersFromRequest($request, $key); + } + + protected function newDatasource() { + return new DiffusionIdentityAssigneeDatasource(); + } + + protected function newConduitParameterType() { + return new ConduitUserListParameterType(); + } + +} diff --git a/src/applications/diffusion/typeahead/DiffusionIdentityAssigneeDatasource.php b/src/applications/diffusion/typeahead/DiffusionIdentityAssigneeDatasource.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/typeahead/DiffusionIdentityAssigneeDatasource.php @@ -0,0 +1,21 @@ + array( + 'name' => pht('Explicitly Unassigned'), + 'summary' => pht('Find results which are not assigned.'), + 'description' => pht( + "This function includes results which have been explicitly ". + "unassigned. Use a query like this to find explicitly ". + "unassigned results:\n\n%s\n\n". + "If you combine this function with other functions, the query will ". + "return results which match the other selectors //or// have no ". + "assignee. For example, this query will find results which are ". + "assigned to `alincoln`, and will also find results which have been ". + "unassigned:\n\n%s", + '> unassigned()', + '> alincoln, unassigned()'), + ), + ); + } + + public function loadResults() { + $results = array( + $this->buildUnassignedResult(), + ); + return $this->filterResultsAgainstTokens($results); + } + + protected function evaluateFunction($function, array $argv_list) { + $results = array(); + + foreach ($argv_list as $argv) { + $results[] = self::FUNCTION_TOKEN; + } + + return $results; + } + + public function renderFunctionTokens($function, array $argv_list) { + $results = array(); + foreach ($argv_list as $argv) { + $results[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult( + $this->buildUnassignedResult()); + } + return $results; + } + + private function buildUnassignedResult() { + $name = pht('Unassigned'); + return $this->newFunctionResult() + ->setName($name.' unassigned') + ->setDisplayName($name) + ->setIcon('fa-ban') + ->setPHID('unassigned()') + ->setUnique(true) + ->addAttribute(pht('Select results with no owner.')); + } + +} diff --git a/src/applications/people/editor/PhabricatorUserEditor.php b/src/applications/people/editor/PhabricatorUserEditor.php --- a/src/applications/people/editor/PhabricatorUserEditor.php +++ b/src/applications/people/editor/PhabricatorUserEditor.php @@ -420,6 +420,12 @@ $user->endWriteLocking(); $user->saveTransaction(); + // Try and match this new address against unclaimed `RepositoryIdentity`s + PhabricatorWorker::scheduleTask( + 'PhabricatorRepositoryIdentityChangeWorker', + array('userPHID' => $user->getPHID()), + array('objectPHID' => $user->getPHID())); + return $this; } diff --git a/src/applications/repository/engine/PhabricatorRepositoryIdentityEditEngine.php b/src/applications/repository/engine/PhabricatorRepositoryIdentityEditEngine.php --- a/src/applications/repository/engine/PhabricatorRepositoryIdentityEditEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryIdentityEditEngine.php @@ -75,7 +75,7 @@ protected function buildCustomEditFields($object) { return array( - id(new PhabricatorUsersEditField()) + id(new DiffusionIdentityAssigneeEditField()) ->setKey('manuallySetUserPHID') ->setLabel(pht('Assigned To')) ->setDescription(pht('Override this identity\'s assignment.')) diff --git a/src/applications/repository/query/PhabricatorRepositoryIdentityQuery.php b/src/applications/repository/query/PhabricatorRepositoryIdentityQuery.php --- a/src/applications/repository/query/PhabricatorRepositoryIdentityQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryIdentityQuery.php @@ -6,6 +6,9 @@ private $ids; private $phids; private $identityNames; + private $emailAddress; + private $assigneePHIDs; + private $identityNameLike; private $hasEffectivePHID; public function withIDs(array $ids) { @@ -23,6 +26,21 @@ return $this; } + public function withIdentityNameLike($name_like) { + $this->identityNameLike = $name_like; + return $this; + } + + public function withEmailAddress($address) { + $this->emailAddress = $address; + return $this; + } + + public function withAssigneePHIDs(array $assignees) { + $this->assigneePHIDs = $assignees; + return $this; + } + public function withHasEffectivePHID($has_effective_phid) { $this->hasEffectivePHID = $has_effective_phid; return $this; @@ -57,8 +75,14 @@ $this->phids); } - if ($this->hasEffectivePHID !== null) { + if ($this->assigneePHIDs !== null) { + $where[] = qsprintf( + $conn, + 'repository_identity.currentEffectiveUserPHID IN (%Ls)', + $this->assigneePHIDs); + } + if ($this->hasEffectivePHID !== null) { if ($this->hasEffectivePHID) { $where[] = qsprintf( $conn, @@ -82,6 +106,21 @@ $name_hashes); } + if ($this->emailAddress !== null) { + $identity_style = "<{$this->emailAddress}>"; + $where[] = qsprintf( + $conn, + 'repository_identity.identityNameRaw LIKE %<', + $identity_style); + } + + if ($this->identityNameLike != null) { + $where[] = qsprintf( + $conn, + 'repository_identity.identityNameRaw LIKE %~', + $this->identityNameLike); + } + return $where; } diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -21,6 +21,8 @@ protected $repositoryID; protected $phid; + protected $authorIdentityPHID; + protected $committerIdentityPHID; protected $commitIdentifier; protected $epoch; protected $mailKey; @@ -113,6 +115,8 @@ 'commitIdentifier' => 'text40', 'mailKey' => 'bytes20', 'authorPHID' => 'phid?', + 'authorIdentityPHID' => 'phid?', + 'committerIdentityPHID' => 'phid?', 'auditStatus' => 'uint32', 'summary' => 'text255', 'importStatus' => 'uint32', diff --git a/src/applications/repository/storage/PhabricatorRepositoryIdentity.php b/src/applications/repository/storage/PhabricatorRepositoryIdentity.php --- a/src/applications/repository/storage/PhabricatorRepositoryIdentity.php +++ b/src/applications/repository/storage/PhabricatorRepositoryIdentity.php @@ -80,6 +80,7 @@ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } diff --git a/src/applications/repository/worker/PhabricatorRepositoryIdentityChangeWorker.php b/src/applications/repository/worker/PhabricatorRepositoryIdentityChangeWorker.php new file mode 100644 --- /dev/null +++ b/src/applications/repository/worker/PhabricatorRepositoryIdentityChangeWorker.php @@ -0,0 +1,34 @@ +getTaskData(); + $user_phid = idx($task_data, 'userPHID'); + + $user = id(new PhabricatorPeopleQuery()) + ->withPHIDs(array($user_phid)) + ->setViewer($viewer) + ->executeOne(); + + $emails = id(new PhabricatorUserEmail())->loadAllWhere( + 'userPHID = %s ORDER BY address', + $user->getPHID()); + + foreach ($emails as $email) { + $identities = id(new PhabricatorRepositoryIdentityQuery()) + ->setViewer($viewer) + ->withEmailAddress($email->getAddress()) + ->execute(); + + foreach ($identities as $identity) { + $identity->setAutomaticGuessedUserPHID($user->getPHID()) + ->save(); + } + } + } + +} diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -109,6 +109,8 @@ $data->setCommitDetail('authorName', $ref->getAuthorName()); $data->setCommitDetail('authorEmail', $ref->getAuthorEmail()); + $data->setCommitDetail( + 'authorIdentityPHID', $author_identity->getPHID()); $data->setCommitDetail( 'authorPHID', $this->resolveUserPHID($commit, $author)); @@ -124,6 +126,8 @@ $data->setCommitDetail( 'committerPHID', $this->resolveUserPHID($commit, $committer)); + $data->setCommitDetail( + 'committerIdentityPHID', $committer_identity->getPHID()); } $repository = $this->repository; @@ -161,6 +165,9 @@ $commit->setAuthorPHID($author_phid); } + $commit->setAuthorIdentityPHID($author_identity->getPHID()); + $commit->setCommitterIdentityPHID($committer_identity->getPHID()); + $commit->setSummary($data->getSummary()); $commit->save(); diff --git a/src/applications/repository/xaction/PhabricatorRepositoryIdentityAssignTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryIdentityAssignTransaction.php --- a/src/applications/repository/xaction/PhabricatorRepositoryIdentityAssignTransaction.php +++ b/src/applications/repository/xaction/PhabricatorRepositoryIdentityAssignTransaction.php @@ -21,23 +21,33 @@ return pht( '%s assigned this identity to %s.', $this->renderAuthor(), - $this->renderHandle($new)); + $this->renderIdentityHandle($new)); } else if (!$new) { return pht( '%s removed %s as the assignee of this identity.', $this->renderAuthor(), - $this->renderHandle($old)); + $this->renderIdentityHandle($old)); } else { return pht( '%s changed the assigned user for this identity from %s to %s.', $this->renderAuthor(), - $this->renderHandle($old), - $this->renderHandle($new)); + $this->renderIdentityHandle($old), + $this->renderIdentityHandle($new)); + } + } + + private function renderIdentityHandle($handle) { + $unassigned_token = DiffusionIdentityUnassignedDatasource::FUNCTION_TOKEN; + if ($handle === $unassigned_token) { + return phutil_tag('em', array(), pht('Explicitly Unassigned')); + } else { + return $this->renderHandle($handle); } } public function validateTransactions($object, array $xactions) { $errors = array(); + $unassigned_token = DiffusionIdentityUnassignedDatasource::FUNCTION_TOKEN; foreach ($xactions as $xaction) { $old = $xaction->getOldValue(); @@ -50,6 +60,10 @@ continue; } + if ($new === $unassigned_token) { + continue; + } + $assignee_list = id(new PhabricatorPeopleQuery()) ->setViewer($this->getActor()) ->withPHIDs(array($new))