diff --git a/resources/sql/autopatches/20160824.repohint.01.hint.sql b/resources/sql/autopatches/20160824.repohint.01.hint.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20160824.repohint.01.hint.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_commithint ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + repositoryPHID VARBINARY(64) NOT NULL, + oldCommitIdentifier VARCHAR(40) NOT NULL COLLATE {$COLLATE_TEXT}, + newCommitIdentifier VARCHAR(40) COLLATE {$COLLATE_TEXT}, + hintType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + UNIQUE KEY `key_old` (repositoryPHID, oldCommitIdentifier) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; 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 @@ -612,6 +612,7 @@ 'DiffusionCommitHash' => 'applications/diffusion/data/DiffusionCommitHash.php', 'DiffusionCommitHeraldField' => 'applications/diffusion/herald/DiffusionCommitHeraldField.php', 'DiffusionCommitHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionCommitHeraldFieldGroup.php', + 'DiffusionCommitHintQuery' => 'applications/diffusion/query/DiffusionCommitHintQuery.php', 'DiffusionCommitHookEngine' => 'applications/diffusion/engine/DiffusionCommitHookEngine.php', 'DiffusionCommitHookRejectException' => 'applications/diffusion/exception/DiffusionCommitHookRejectException.php', 'DiffusionCommitMergeHeraldField' => 'applications/diffusion/herald/DiffusionCommitMergeHeraldField.php', @@ -3384,6 +3385,7 @@ 'PhabricatorRepositoryCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php', 'PhabricatorRepositoryCommitData' => 'applications/repository/storage/PhabricatorRepositoryCommitData.php', 'PhabricatorRepositoryCommitHeraldWorker' => 'applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php', + 'PhabricatorRepositoryCommitHint' => 'applications/repository/storage/PhabricatorRepositoryCommitHint.php', 'PhabricatorRepositoryCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php', 'PhabricatorRepositoryCommitOwnersWorker' => 'applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php', 'PhabricatorRepositoryCommitPHIDType' => 'applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php', @@ -3404,6 +3406,7 @@ 'PhabricatorRepositoryManagementCacheWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php', 'PhabricatorRepositoryManagementClusterizeWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementClusterizeWorkflow.php', 'PhabricatorRepositoryManagementDiscoverWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php', + 'PhabricatorRepositoryManagementHintWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementHintWorkflow.php', 'PhabricatorRepositoryManagementImportingWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php', 'PhabricatorRepositoryManagementListPathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListPathsWorkflow.php', 'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php', @@ -5105,6 +5108,7 @@ 'DiffusionCommitHash' => 'Phobject', 'DiffusionCommitHeraldField' => 'HeraldField', 'DiffusionCommitHeraldFieldGroup' => 'HeraldFieldGroup', + 'DiffusionCommitHintQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DiffusionCommitHookEngine' => 'Phobject', 'DiffusionCommitHookRejectException' => 'Exception', 'DiffusionCommitMergeHeraldField' => 'DiffusionCommitHeraldField', @@ -8350,6 +8354,10 @@ 'PhabricatorRepositoryCommitChangeParserWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitData' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryCommitHeraldWorker' => 'PhabricatorRepositoryCommitParserWorker', + 'PhabricatorRepositoryCommitHint' => array( + 'PhabricatorRepositoryDAO', + 'PhabricatorPolicyInterface', + ), 'PhabricatorRepositoryCommitMessageParserWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitOwnersWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitPHIDType' => 'PhabricatorPHIDType', @@ -8374,6 +8382,7 @@ 'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementClusterizeWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementDiscoverWorkflow' => 'PhabricatorRepositoryManagementWorkflow', + 'PhabricatorRepositoryManagementHintWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementImportingWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementListPathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow', diff --git a/src/applications/diffusion/query/DiffusionCommitHintQuery.php b/src/applications/diffusion/query/DiffusionCommitHintQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/query/DiffusionCommitHintQuery.php @@ -0,0 +1,64 @@ +<?php + +final class DiffusionCommitHintQuery + extends PhabricatorCursorPagedPolicyAwareQuery { + + private $ids; + private $repositoryPHIDs; + private $oldCommitIdentifiers; + + public function withIDs(array $ids) { + $this->ids = $ids; + return $this; + } + + public function withRepositoryPHIDs(array $phids) { + $this->repositoryPHIDs = $phids; + return $this; + } + + public function withOldCommitIdentifiers(array $identifiers) { + $this->oldCommitIdentifiers = $identifiers; + return $this; + } + + public function newResultObject() { + return new PhabricatorRepositoryCommitHint(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->repositoryPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'reositoryPHID IN (%Ls)', + $this->repositoryPHIDs); + } + + if ($this->oldCommitIdentifiers !== null) { + $where[] = qsprintf( + $conn, + 'oldCommitIdentifier IN (%Ls)', + $this->oldCommitIdentifiers); + } + + return $where; + } + + public function getQueryApplicationClass() { + return 'PhabricatorDiffusionApplication'; + } + +} diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementHintWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementHintWorkflow.php new file mode 100644 --- /dev/null +++ b/src/applications/repository/management/PhabricatorRepositoryManagementHintWorkflow.php @@ -0,0 +1,97 @@ +<?php + +final class PhabricatorRepositoryManagementHintWorkflow + extends PhabricatorRepositoryManagementWorkflow { + + protected function didConstruct() { + $this + ->setName('hint') + ->setExamples('**hint** [options] ...') + ->setSynopsis( + pht( + 'Write hints about unusual (rewritten or unreadable) commits.')) + ->setArguments(array()); + } + + public function execute(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + + echo tsprintf( + "%s\n", + pht('Reading list of hints from stdin...')); + + $hints = file_get_contents('php://stdin'); + if ($hints === false) { + throw new PhutilArgumentUsageException(pht('Failed to read stdin.')); + } + + try { + $hints = phutil_json_decode($hints); + } catch (Exception $ex) { + throw new PhutilArgumentUsageException( + pht( + 'Expected a list of hints in JSON format: %s', + $ex->getMessage())); + } + + $repositories = array(); + foreach ($hints as $idx => $hint) { + if (!is_array($hint)) { + throw new PhutilArgumentUsageException( + pht( + 'Each item in the list of hints should be a JSON object, but '. + 'the item at index "%s" is not.', + $idx)); + } + + try { + PhutilTypeSpec::checkMap( + $hint, + array( + 'repository' => 'string|int', + 'old' => 'string', + 'new' => 'optional string|null', + 'hint' => 'string', + )); + } catch (Exception $ex) { + throw new PhutilArgumentUsageException( + pht( + 'Unexpected hint format at index "%s": %s', + $idx, + $ex->getMessage())); + } + + $repository_identifier = $hint['repository']; + $repository = idx($repositories, $repository_identifier); + if (!$repository) { + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->withIdentifiers(array($repository_identifier)) + ->executeOne(); + if (!$repository) { + throw new PhutilArgumentUsageException( + pht( + 'Repository identifier "%s" (in hint at index "%s") does not '. + 'identify a valid repository.', + $repository_identifier, + $idx)); + } + + $repositories[$repository_identifier] = $repository; + } + + PhabricatorRepositoryCommitHint::updateHint( + $repository->getPHID(), + $hint['old'], + idx($hint, 'new'), + $hint['hint']); + + echo tsprintf( + "%s\n", + pht( + 'Updated hint for "%s".', + $hint['old'])); + } + } + +} diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommitHint.php b/src/applications/repository/storage/PhabricatorRepositoryCommitHint.php new file mode 100644 --- /dev/null +++ b/src/applications/repository/storage/PhabricatorRepositoryCommitHint.php @@ -0,0 +1,128 @@ +<?php + +final class PhabricatorRepositoryCommitHint + extends PhabricatorRepositoryDAO + implements PhabricatorPolicyInterface { + + protected $repositoryPHID; + protected $oldCommitIdentifier; + protected $newCommitIdentifier; + protected $hintType; + + const HINT_NONE = 'none'; + const HINT_REWRITTEN = 'rewritten'; + const HINT_UNREADABLE = 'unreadable'; + + protected function getConfiguration() { + return array( + self::CONFIG_TIMESTAMPS => false, + self::CONFIG_COLUMN_SCHEMA => array( + 'oldCommitIdentifier' => 'text40', + 'newCommitIdentifier' => 'text40?', + 'hintType' => 'text32', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_old' => array( + 'columns' => array('repositoryPHID', 'oldCommitIdentifier'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public static function getAllHintTypes() { + return array( + self::HINT_NONE, + self::HINT_REWRITTEN, + self::HINT_UNREADABLE, + ); + } + + public static function updateHint($repository_phid, $old, $new, $type) { + switch ($type) { + case self::HINT_NONE: + break; + case self::HINT_REWRITTEN: + if (!$new) { + throw new Exception( + pht( + 'When hinting a commit ("%s") as rewritten, you must provide '. + 'the commit it was rewritten into.', + $old)); + } + break; + case self::HINT_UNREADABLE: + if ($new) { + throw new Exception( + pht( + 'When hinting a commit ("%s") as unreadable, you must not '. + 'provide a new commit ("%s").', + $old, + $new)); + } + break; + default: + $all_types = self::getAllHintTypes(); + throw new Exception( + pht( + 'Hint type ("%s") for commit ("%s") is not valid. Valid hints '. + 'are: %s.', + $type, + $old, + implode(', ', $all_types))); + } + + $table = new self(); + $table_name = $table->getTableName(); + $conn = $table->establishConnection('w'); + + if ($type == self::HINT_NONE) { + queryfx( + $conn, + 'DELETE FROM %T WHERE repositoryPHID = %s AND oldCommitIdentifier = %s', + $table_name, + $repository_phid, + $old); + } else { + queryfx( + $conn, + 'INSERT INTO %T + (repositoryPHID, oldCommitIdentifier, newCommitIdentifier, hintType) + VALUES (%s, %s, %ns, %s) + ON DUPLICATE KEY UPDATE + newCommitIdentifier = VALUES(newCommitIdentifier), + hintType = VALUES(hintType)', + $table_name, + $repository_phid, + $old, + $new, + $type); + } + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return PhabricatorPolicies::getMostOpenPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + +}