Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php
index 9fba6e03be..b830486e57 100644
--- a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php
+++ b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php
@@ -1,298 +1,301 @@
<?php
final class PhabricatorConfigSchemaQuery extends Phobject {
private $api;
public function setAPI(PhabricatorStorageManagementAPI $api) {
$this->api = $api;
return $this;
}
protected function getAPI() {
if (!$this->api) {
throw new Exception(pht('Call setAPI() before issuing a query!'));
}
return $this->api;
}
protected function getConn() {
return $this->getAPI()->getConn(null);
}
private function getDatabaseNames() {
$api = $this->getAPI();
$patches = PhabricatorSQLPatchList::buildAllPatches();
return $api->getDatabaseList(
$patches,
$only_living = true);
}
public function loadActualSchema() {
$databases = $this->getDatabaseNames();
$conn = $this->getConn();
$tables = queryfx_all(
$conn,
'SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_COLLATION
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA IN (%Ls)',
$databases);
$database_info = queryfx_all(
$conn,
'SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM INFORMATION_SCHEMA.SCHEMATA
WHERE SCHEMA_NAME IN (%Ls)',
$databases);
$database_info = ipull($database_info, null, 'SCHEMA_NAME');
$sql = array();
foreach ($tables as $table) {
$sql[] = qsprintf(
$conn,
'(TABLE_SCHEMA = %s AND TABLE_NAME = %s)',
$table['TABLE_SCHEMA'],
$table['TABLE_NAME']);
}
if ($sql) {
$column_info = queryfx_all(
$conn,
'SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME,
COLLATION_NAME, COLUMN_TYPE, IS_NULLABLE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE (%Q)',
'('.implode(') OR (', $sql).')');
$column_info = igroup($column_info, 'TABLE_SCHEMA');
} else {
$column_info = array();
}
// NOTE: Tables like KEY_COLUMN_USAGE and TABLE_CONSTRAINTS only contain
// primary, unique, and foreign keys, so we can't use them here. We pull
// indexes later on using SHOW INDEXES.
$server_schema = new PhabricatorConfigServerSchema();
$tables = igroup($tables, 'TABLE_SCHEMA');
foreach ($tables as $database_name => $database_tables) {
$info = $database_info[$database_name];
$database_schema = id(new PhabricatorConfigDatabaseSchema())
->setName($database_name)
->setCharacterSet($info['DEFAULT_CHARACTER_SET_NAME'])
->setCollation($info['DEFAULT_COLLATION_NAME']);
$database_column_info = idx($column_info, $database_name, array());
$database_column_info = igroup($database_column_info, 'TABLE_NAME');
foreach ($database_tables as $table) {
$table_name = $table['TABLE_NAME'];
$table_schema = id(new PhabricatorConfigTableSchema())
->setName($table_name)
->setCollation($table['TABLE_COLLATION']);
$columns = idx($database_column_info, $table_name, array());
foreach ($columns as $column) {
$column_schema = id(new PhabricatorConfigColumnSchema())
->setName($column['COLUMN_NAME'])
->setCharacterSet($column['CHARACTER_SET_NAME'])
->setCollation($column['COLLATION_NAME'])
->setColumnType($column['COLUMN_TYPE'])
->setNullable($column['IS_NULLABLE'] == 'YES');
$table_schema->addColumn($column_schema);
}
$key_parts = queryfx_all(
$conn,
'SHOW INDEXES FROM %T.%T',
$database_name,
$table_name);
$keys = igroup($key_parts, 'Key_name');
foreach ($keys as $key_name => $key_pieces) {
$key_pieces = isort($key_pieces, 'Seq_in_index');
$head = head($key_pieces);
// This handles string indexes which index only a prefix of a field.
$column_names = array();
foreach ($key_pieces as $piece) {
$name = $piece['Column_name'];
if ($piece['Sub_part']) {
$name = $name.'('.$piece['Sub_part'].')';
}
$column_names[] = $name;
}
$key_schema = id(new PhabricatorConfigKeySchema())
->setName($key_name)
->setColumnNames($column_names)
->setUnique(!$head['Non_unique'])
->setIndexType($head['Index_type']);
$table_schema->addKey($key_schema);
}
$database_schema->addTable($table_schema);
}
$server_schema->addDatabase($database_schema);
}
return $server_schema;
}
public function loadExpectedSchema() {
$databases = $this->getDatabaseNames();
$api = $this->getAPI();
if ($api->isCharacterSetAvailable('utf8mb4')) {
// If utf8mb4 is available, we use it with the utf8mb4_unicode_ci
// collation. This is most correct, and will sort properly.
$utf8_charset = 'utf8mb4';
- $utf8_collation = 'utf8mb4_unicode_ci';
+ $utf8_binary_collation = 'utf8mb4_bin';
+ $utf8_sorting_collation = 'utf8mb4_unicode_ci';
} else {
// If utf8mb4 is not available, we use binary. This allows us to store
// 4-byte unicode characters. This has some tradeoffs:
//
// Unicode characters won't sort correctly. There's nothing we can do
// about this while still supporting 4-byte characters.
//
// It's possible that strings will be truncated in the middle of a
// character on insert. We encourage users to set STRICT_ALL_TABLES
// to prevent this.
$utf8_charset = 'binary';
- $utf8_collation = 'binary';
+ $utf8_binary_collation = 'binary';
+ $utf8_sorting_collation = 'binary';
}
$specs = id(new PhutilSymbolLoader())
->setAncestorClass('PhabricatorConfigSchemaSpec')
->loadObjects();
$server_schema = new PhabricatorConfigServerSchema();
foreach ($specs as $spec) {
$spec
- ->setUTF8Collation($utf8_collation)
->setUTF8Charset($utf8_charset)
+ ->setUTF8BinaryCollation($utf8_binary_collation)
+ ->setUTF8SortingCollation($utf8_sorting_collation)
->setServer($server_schema)
->buildSchemata($server_schema);
}
return $server_schema;
}
public function buildComparisonSchema(
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual) {
$comp_server = $actual->newEmptyClone();
$all_databases = $actual->getDatabases() + $expect->getDatabases();
foreach ($all_databases as $database_name => $database_template) {
$actual_database = $actual->getDatabase($database_name);
$expect_database = $expect->getDatabase($database_name);
$issues = $this->compareSchemata($expect_database, $actual_database);
$comp_database = $database_template->newEmptyClone()
->setIssues($issues);
if (!$actual_database) {
$actual_database = $expect_database->newEmptyClone();
}
if (!$expect_database) {
$expect_database = $actual_database->newEmptyClone();
}
$all_tables =
$actual_database->getTables() +
$expect_database->getTables();
foreach ($all_tables as $table_name => $table_template) {
$actual_table = $actual_database->getTable($table_name);
$expect_table = $expect_database->getTable($table_name);
$issues = $this->compareSchemata($expect_table, $actual_table);
$comp_table = $table_template->newEmptyClone()
->setIssues($issues);
if (!$actual_table) {
$actual_table = $expect_table->newEmptyClone();
}
if (!$expect_table) {
$expect_table = $actual_table->newEmptyClone();
}
$all_columns =
$actual_table->getColumns() +
$expect_table->getColumns();
foreach ($all_columns as $column_name => $column_template) {
$actual_column = $actual_table->getColumn($column_name);
$expect_column = $expect_table->getColumn($column_name);
$issues = $this->compareSchemata($expect_column, $actual_column);
$comp_column = $column_template->newEmptyClone()
->setIssues($issues);
$comp_table->addColumn($comp_column);
}
$all_keys =
$actual_table->getKeys() +
$expect_table->getKeys();
foreach ($all_keys as $key_name => $key_template) {
$actual_key = $actual_table->getKey($key_name);
$expect_key = $expect_table->getKey($key_name);
$issues = $this->compareSchemata($expect_key, $actual_key);
$comp_key = $key_template->newEmptyClone()
->setIssues($issues);
$comp_table->addKey($comp_key);
}
$comp_database->addTable($comp_table);
}
$comp_server->addDatabase($comp_database);
}
return $comp_server;
}
private function compareSchemata(
PhabricatorConfigStorageSchema $expect = null,
PhabricatorConfigStorageSchema $actual = null) {
$expect_is_key = ($expect instanceof PhabricatorConfigKeySchema);
$actual_is_key = ($actual instanceof PhabricatorConfigKeySchema);
if ($expect_is_key || $actual_is_key) {
$missing_issue = PhabricatorConfigStorageSchema::ISSUE_MISSINGKEY;
$surplus_issue = PhabricatorConfigStorageSchema::ISSUE_SURPLUSKEY;
} else {
$missing_issue = PhabricatorConfigStorageSchema::ISSUE_MISSING;
$surplus_issue = PhabricatorConfigStorageSchema::ISSUE_SURPLUS;
}
if (!$expect && !$actual) {
throw new Exception(pht('Can not compare two missing schemata!'));
} else if ($expect && !$actual) {
$issues = array($missing_issue);
} else if ($actual && !$expect) {
$issues = array($surplus_issue);
} else {
$issues = $actual->compareTo($expect);
}
return $issues;
}
}
diff --git a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php
index 9915db0f61..cd68a97c4a 100644
--- a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php
+++ b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php
@@ -1,363 +1,398 @@
<?php
abstract class PhabricatorConfigSchemaSpec extends Phobject {
private $server;
private $utf8Charset;
- private $utf8Collation;
+ private $utf8BinaryCollation;
+ private $utf8SortingCollation;
- public function setUTF8Collation($utf8_collation) {
- $this->utf8Collation = $utf8_collation;
+ public function setUTF8SortingCollation($utf8_sorting_collation) {
+ $this->utf8SortingCollation = $utf8_sorting_collation;
return $this;
}
- public function getUTF8Collation() {
- return $this->utf8Collation;
+ public function getUTF8SortingCollation() {
+ return $this->utf8SortingCollation;
+ }
+
+ public function setUTF8BinaryCollation($utf8_binary_collation) {
+ $this->utf8BinaryCollation = $utf8_binary_collation;
+ return $this;
+ }
+
+ public function getUTF8BinaryCollation() {
+ return $this->utf8BinaryCollation;
}
public function setUTF8Charset($utf8_charset) {
$this->utf8Charset = $utf8_charset;
return $this;
}
public function getUTF8Charset() {
return $this->utf8Charset;
}
public function setServer(PhabricatorConfigServerSchema $server) {
$this->server = $server;
return $this;
}
public function getServer() {
return $this->server;
}
abstract public function buildSchemata();
protected function buildLiskSchemata($base) {
$objects = id(new PhutilSymbolLoader())
->setAncestorClass($base)
->loadObjects();
foreach ($objects as $object) {
if ($object->getConfigOption(LiskDAO::CONFIG_NO_TABLE)) {
continue;
}
$this->buildLiskObjectSchema($object);
}
}
protected function buildTransactionSchema(
PhabricatorApplicationTransaction $xaction,
PhabricatorApplicationTransactionComment $comment = null) {
$this->buildLiskObjectSchema($xaction);
if ($comment) {
$this->buildLiskObjectSchema($comment);
}
}
protected function buildCustomFieldSchemata(
PhabricatorLiskDAO $storage,
array $indexes) {
$this->buildLiskObjectSchema($storage);
foreach ($indexes as $index) {
$this->buildLiskObjectSchema($index);
}
}
private function buildLiskObjectSchema(PhabricatorLiskDAO $object) {
$this->buildRawSchema(
$object->getApplicationName(),
$object->getTableName(),
$object->getSchemaColumns(),
$object->getSchemaKeys());
}
protected function buildRawSchema(
$database_name,
$table_name,
array $columns,
array $keys) {
$database = $this->getDatabase($database_name);
$table = $this->newTable($table_name);
foreach ($columns as $name => $type) {
if ($type === null) {
continue;
}
$details = $this->getDetailsForDataType($type);
list($column_type, $charset, $collation, $nullable) = $details;
$column = $this->newColumn($name)
->setDataType($type)
->setColumnType($column_type)
->setCharacterSet($charset)
->setCollation($collation)
->setNullable($nullable);
$table->addColumn($column);
}
foreach ($keys as $key_name => $key_spec) {
if ($key_spec === null) {
// This is a subclass removing a key which Lisk expects.
continue;
}
$key = $this->newKey($key_name)
->setColumnNames(idx($key_spec, 'columns', array()));
$key->setUnique((bool)idx($key_spec, 'unique'));
$key->setIndexType(idx($key_spec, 'type', 'BTREE'));
$table->addKey($key);
}
$database->addTable($table);
}
protected function buildEdgeSchemata(PhabricatorLiskDAO $object) {
$this->buildRawSchema(
$object->getApplicationName(),
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
array(
'src' => 'phid',
'type' => 'uint32',
'dst' => 'phid',
'dateCreated' => 'epoch',
'seq' => 'uint32',
'dataID' => 'id?',
),
array(
'PRIMARY' => array(
'columns' => array('src', 'type', 'dst'),
'unique' => true,
),
'src' => array(
'columns' => array('src', 'type', 'dateCreated', 'seq'),
),
'key_dst' => array(
'columns' => array('dst', 'type', 'src'),
'unique' => true,
),
));
$this->buildRawSchema(
$object->getApplicationName(),
PhabricatorEdgeConfig::TABLE_NAME_EDGEDATA,
array(
'id' => 'id',
'data' => 'text',
),
array(
'PRIMARY' => array(
'columns' => array('id'),
'unique' => true,
),
));
}
public function buildCounterSchema(PhabricatorLiskDAO $object) {
$this->buildRawSchema(
$object->getApplicationName(),
PhabricatorLiskDAO::COUNTER_TABLE_NAME,
array(
'counterName' => 'text32',
'counterValue' => 'id64',
),
array(
'PRIMARY' => array(
'columns' => array('counterName'),
'unique' => true,
),
));
}
protected function getDatabase($name) {
$server = $this->getServer();
$database = $server->getDatabase($this->getNamespacedDatabase($name));
if (!$database) {
$database = $this->newDatabase($name);
$server->addDatabase($database);
}
return $database;
}
protected function newDatabase($name) {
return id(new PhabricatorConfigDatabaseSchema())
->setName($this->getNamespacedDatabase($name))
->setCharacterSet($this->getUTF8Charset())
- ->setCollation($this->getUTF8Collation());
+ ->setCollation($this->getUTF8BinaryCollation());
}
protected function getNamespacedDatabase($name) {
$namespace = PhabricatorLiskDAO::getStorageNamespace();
return $namespace.'_'.$name;
}
protected function newTable($name) {
return id(new PhabricatorConfigTableSchema())
->setName($name)
- ->setCollation($this->getUTF8Collation());
+ ->setCollation($this->getUTF8BinaryCollation());
}
protected function newColumn($name) {
return id(new PhabricatorConfigColumnSchema())
->setName($name);
}
protected function newKey($name) {
return id(new PhabricatorConfigKeySchema())
->setName($name);
}
private function getDetailsForDataType($data_type) {
$column_type = null;
$charset = null;
$collation = null;
// If the type ends with "?", make the column nullable.
$nullable = false;
if (preg_match('/\?$/', $data_type)) {
$nullable = true;
$data_type = substr($data_type, 0, -1);
}
// NOTE: MySQL allows fragments like "VARCHAR(32) CHARACTER SET binary",
// but just interprets that to mean "VARBINARY(32)". The fragment is
// totally disallowed in a MODIFY statement vs a CREATE TABLE statement.
switch ($data_type) {
case 'id':
case 'epoch':
case 'uint32':
$column_type = 'int(10) unsigned';
break;
case 'sint32':
$column_type = 'int(10)';
break;
case 'id64':
case 'uint64':
$column_type = 'bigint(20) unsigned';
break;
case 'sint64':
$column_type = 'bigint(20)';
break;
case 'phid':
case 'policy';
$column_type = 'varbinary(64)';
break;
case 'bytes64':
$column_type = 'binary(64)';
break;
case 'bytes40':
$column_type = 'binary(40)';
break;
case 'bytes32':
$column_type = 'binary(32)';
break;
case 'bytes20':
$column_type = 'binary(20)';
break;
case 'bytes12':
$column_type = 'binary(12)';
break;
case 'bytes4':
$column_type = 'binary(4)';
break;
case 'bytes':
$column_type = 'longblob';
break;
+ case 'sort255':
+ $column_type = 'varchar(255)';
+ $charset = $this->getUTF8Charset();
+ $collation = $this->getUTF8SortingCollation();
+ break;
+ case 'sort128':
+ $column_type = 'varchar(128)';
+ $charset = $this->getUTF8Charset();
+ $collation = $this->getUTF8SortingCollation();
+ break;
+ case 'sort64':
+ $column_type = 'varchar(64)';
+ $charset = $this->getUTF8Charset();
+ $collation = $this->getUTF8SortingCollation();
+ break;
+ case 'sort32':
+ $column_type = 'varchar(32)';
+ $charset = $this->getUTF8Charset();
+ $collation = $this->getUTF8SortingCollation();
+ break;
+ case 'sort':
+ $column_type = 'longtext';
+ $charset = $this->getUTF8Charset();
+ $collation = $this->getUTF8SortingCollation();
+ break;
case 'text255':
$column_type = 'varchar(255)';
$charset = $this->getUTF8Charset();
- $collation = $this->getUTF8Collation();
+ $collation = $this->getUTF8BinaryCollation();
break;
case 'text160':
$column_type = 'varchar(160)';
$charset = $this->getUTF8Charset();
- $collation = $this->getUTF8Collation();
+ $collation = $this->getUTF8BinaryCollation();
break;
case 'text128':
$column_type = 'varchar(128)';
$charset = $this->getUTF8Charset();
- $collation = $this->getUTF8Collation();
+ $collation = $this->getUTF8BinaryCollation();
break;
case 'text80':
$column_type = 'varchar(80)';
$charset = $this->getUTF8Charset();
- $collation = $this->getUTF8Collation();
+ $collation = $this->getUTF8BinaryCollation();
break;
case 'text64':
$column_type = 'varchar(64)';
$charset = $this->getUTF8Charset();
- $collation = $this->getUTF8Collation();
+ $collation = $this->getUTF8BinaryCollation();
break;
case 'text40':
$column_type = 'varchar(40)';
$charset = $this->getUTF8Charset();
- $collation = $this->getUTF8Collation();
+ $collation = $this->getUTF8BinaryCollation();
break;
case 'text32':
$column_type = 'varchar(32)';
$charset = $this->getUTF8Charset();
- $collation = $this->getUTF8Collation();
+ $collation = $this->getUTF8BinaryCollation();
break;
case 'text20':
$column_type = 'varchar(20)';
$charset = $this->getUTF8Charset();
- $collation = $this->getUTF8Collation();
+ $collation = $this->getUTF8BinaryCollation();
break;
case 'text16':
$column_type = 'varchar(16)';
$charset = $this->getUTF8Charset();
- $collation = $this->getUTF8Collation();
+ $collation = $this->getUTF8BinaryCollation();
break;
case 'text12':
$column_type = 'varchar(12)';
$charset = $this->getUTF8Charset();
- $collation = $this->getUTF8Collation();
+ $collation = $this->getUTF8BinaryCollation();
break;
case 'text8':
$column_type = 'varchar(8)';
$charset = $this->getUTF8Charset();
- $collation = $this->getUTF8Collation();
+ $collation = $this->getUTF8BinaryCollation();
break;
case 'text4':
$column_type = 'varchar(4)';
$charset = $this->getUTF8Charset();
- $collation = $this->getUTF8Collation();
+ $collation = $this->getUTF8BinaryCollation();
break;
case 'text':
$column_type = 'longtext';
$charset = $this->getUTF8Charset();
- $collation = $this->getUTF8Collation();
+ $collation = $this->getUTF8BinaryCollation();
break;
case 'bool':
$column_type = 'tinyint(1)';
break;
case 'double':
$column_type = 'double';
break;
case 'date':
$column_type = 'date';
break;
default:
$column_type = pht('<unknown>');
$charset = pht('<unknown>');
$collation = pht('<unknown>');
break;
}
return array($column_type, $charset, $collation, $nullable);
}
}
diff --git a/src/applications/maniphest/storage/ManiphestNameIndex.php b/src/applications/maniphest/storage/ManiphestNameIndex.php
index 7988dc90f9..62e4e8a524 100644
--- a/src/applications/maniphest/storage/ManiphestNameIndex.php
+++ b/src/applications/maniphest/storage/ManiphestNameIndex.php
@@ -1,42 +1,42 @@
<?php
/**
* Denormalizes object names to support queries which need to be ordered or
* grouped by things like projects.
*/
final class ManiphestNameIndex extends ManiphestDAO {
protected $indexedObjectPHID;
protected $indexedObjectName;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
- 'indexedObjectName' => 'text128',
+ 'indexedObjectName' => 'sort128',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => array(
'columns' => array('indexedObjectPHID'),
'unique' => true,
),
'key_name' => array(
'columns' => array('indexedObjectName'),
),
),
) + parent::getConfiguration();
}
public static function updateIndex($phid, $name) {
$table = new ManiphestNameIndex();
$conn_w = $table->establishConnection('w');
queryfx(
$conn_w,
'INSERT INTO %T (indexedObjectPHID, indexedObjectName) VALUES (%s, %s)
ON DUPLICATE KEY UPDATE indexedObjectName = VALUES(indexedObjectName)',
$table->getTableName(),
$phid,
$name);
}
}
diff --git a/src/applications/maniphest/storage/ManiphestTask.php b/src/applications/maniphest/storage/ManiphestTask.php
index d85c2155f5..6661b13f97 100644
--- a/src/applications/maniphest/storage/ManiphestTask.php
+++ b/src/applications/maniphest/storage/ManiphestTask.php
@@ -1,374 +1,377 @@
<?php
final class ManiphestTask extends ManiphestDAO
implements
PhabricatorMarkupInterface,
PhabricatorPolicyInterface,
PhabricatorTokenReceiverInterface,
PhabricatorFlaggableInterface,
PhabricatorMentionableInterface,
PhrequentTrackableInterface,
PhabricatorCustomFieldInterface,
PhabricatorDestructibleInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorProjectInterface {
const MARKUP_FIELD_DESCRIPTION = 'markup:desc';
protected $authorPHID;
protected $ownerPHID;
protected $ccPHIDs = array();
protected $status;
protected $priority;
protected $subpriority = 0;
protected $title = '';
protected $originalTitle = '';
protected $description = '';
protected $originalEmailSource;
protected $mailKey;
protected $viewPolicy = PhabricatorPolicies::POLICY_USER;
protected $editPolicy = PhabricatorPolicies::POLICY_USER;
protected $attached = array();
protected $projectPHIDs = array();
private $subscribersNeedUpdate;
protected $ownerOrdering;
private $groupByProjectPHID = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;
private $edgeProjectPHIDs = self::ATTACHABLE;
public static function initializeNewTask(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorManiphestApplication'))
->executeOne();
$view_policy = $app->getPolicy(ManiphestDefaultViewCapability::CAPABILITY);
$edit_policy = $app->getPolicy(ManiphestDefaultEditCapability::CAPABILITY);
return id(new ManiphestTask())
->setStatus(ManiphestTaskStatus::getDefaultStatus())
->setPriority(ManiphestTaskPriority::getDefaultPriority())
->setAuthorPHID($actor->getPHID())
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy)
->attachProjectPHIDs(array());
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'ccPHIDs' => self::SERIALIZATION_JSON,
'attached' => self::SERIALIZATION_JSON,
'projectPHIDs' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'ownerPHID' => 'phid?',
'status' => 'text12',
'priority' => 'uint32',
- 'title' => 'text',
+ 'title' => 'sort',
'originalTitle' => 'text',
'description' => 'text',
'mailKey' => 'bytes20',
'ownerOrdering' => 'text64?',
'originalEmailSource' => 'text255?',
'subpriority' => 'double',
// T6203/NULLABILITY
// This should not be nullable. It's going away soon anyway.
'ccPHIDs' => 'text?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'priority' => array(
'columns' => array('priority', 'status'),
),
'status' => array(
'columns' => array('status'),
),
'ownerPHID' => array(
'columns' => array('ownerPHID', 'status'),
),
'authorPHID' => array(
'columns' => array('authorPHID', 'status'),
),
'ownerOrdering' => array(
'columns' => array('ownerOrdering'),
),
'priority_2' => array(
'columns' => array('priority', 'subpriority'),
),
'key_dateCreated' => array(
'columns' => array('dateCreated'),
),
'key_dateModified' => array(
'columns' => array('dateModified'),
),
+ 'key_title' => array(
+ 'columns' => array('title(64)'),
+ ),
),
) + parent::getConfiguration();
}
public function loadDependsOnTaskPHIDs() {
return PhabricatorEdgeQuery::loadDestinationPHIDs(
$this->getPHID(),
PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK);
}
public function loadDependedOnByTaskPHIDs() {
return PhabricatorEdgeQuery::loadDestinationPHIDs(
$this->getPHID(),
PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK);
}
public function getAttachedPHIDs($type) {
return array_keys(idx($this->attached, $type, array()));
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(ManiphestTaskPHIDType::TYPECONST);
}
public function getCCPHIDs() {
return array_values(nonempty($this->ccPHIDs, array()));
}
public function getProjectPHIDs() {
return $this->assertAttached($this->edgeProjectPHIDs);
}
public function attachProjectPHIDs(array $phids) {
$this->edgeProjectPHIDs = $phids;
return $this;
}
public function setCCPHIDs(array $phids) {
$this->ccPHIDs = array_values($phids);
$this->subscribersNeedUpdate = true;
return $this;
}
public function setOwnerPHID($phid) {
$this->ownerPHID = nonempty($phid, null);
$this->subscribersNeedUpdate = true;
return $this;
}
public function setTitle($title) {
$this->title = $title;
if (!$this->getID()) {
$this->originalTitle = $title;
}
return $this;
}
public function getMonogram() {
return 'T'.$this->getID();
}
public function attachGroupByProjectPHID($phid) {
$this->groupByProjectPHID = $phid;
return $this;
}
public function getGroupByProjectPHID() {
return $this->assertAttached($this->groupByProjectPHID);
}
public function save() {
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
$result = parent::save();
if ($this->subscribersNeedUpdate) {
// If we've changed the subscriber PHIDs for this task, update the link
// table.
ManiphestTaskSubscriber::updateTaskSubscribers($this);
$this->subscribersNeedUpdate = false;
}
return $result;
}
public function isClosed() {
return ManiphestTaskStatus::isClosedStatus($this->getStatus());
}
public function getPrioritySortVector() {
return array(
$this->getPriority(),
-$this->getSubpriority(),
$this->getID(),
);
}
/* -( Markup Interface )--------------------------------------------------- */
/**
* @task markup
*/
public function getMarkupFieldKey($field) {
$hash = PhabricatorHash::digest($this->getMarkupText($field));
$id = $this->getID();
return "maniphest:T{$id}:{$field}:{$hash}";
}
/**
* @task markup
*/
public function getMarkupText($field) {
return $this->getDescription();
}
/**
* @task markup
*/
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newManiphestMarkupEngine();
}
/**
* @task markup
*/
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
return $output;
}
/**
* @task markup
*/
public function shouldUseMarkupCache($field) {
return (bool)$this->getID();
}
/* -( Policy Interface )--------------------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
// The owner of a task can always view and edit it.
$owner_phid = $this->getOwnerPHID();
if ($owner_phid) {
$user_phid = $user->getPHID();
if ($user_phid == $owner_phid) {
return true;
}
}
return false;
}
public function describeAutomaticCapability($capability) {
return pht('The owner of a task can always view and edit it.');
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {
// Sort of ambiguous who this was intended for; just let them both know.
return array_filter(
array_unique(
array(
$this->getAuthorPHID(),
$this->getOwnerPHID(),
)));
}
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
public function getCustomFieldSpecificationForRole($role) {
return PhabricatorEnv::getEnvConfig('maniphest.fields');
}
public function getCustomFieldBaseClass() {
return 'ManiphestCustomField';
}
public function getCustomFields() {
return $this->assertAttached($this->customFields);
}
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
$this->customFields = $fields;
return $this;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
// TODO: Once this implements PhabricatorTransactionInterface, this
// will be handled automatically and can be removed.
$xactions = id(new ManiphestTransaction())->loadAllWhere(
'objectPHID = %s',
$this->getPHID());
foreach ($xactions as $xaction) {
$engine->destroyObject($xaction);
}
$this->delete();
$this->saveTransaction();
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new ManiphestTransactionEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new ManiphestTransaction();
}
}
diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php
index 8c65ff26a3..ea6acbe8b4 100644
--- a/src/applications/phame/storage/PhamePost.php
+++ b/src/applications/phame/storage/PhamePost.php
@@ -1,264 +1,264 @@
<?php
final class PhamePost extends PhameDAO
implements
PhabricatorPolicyInterface,
PhabricatorMarkupInterface,
PhabricatorTokenReceiverInterface {
const MARKUP_FIELD_BODY = 'markup:body';
const MARKUP_FIELD_SUMMARY = 'markup:summary';
const VISIBILITY_DRAFT = 0;
const VISIBILITY_PUBLISHED = 1;
protected $bloggerPHID;
protected $title;
protected $phameTitle;
protected $body;
protected $visibility;
protected $configData;
protected $datePublished;
protected $blogPHID;
private $blog;
public static function initializePost(
PhabricatorUser $blogger,
PhameBlog $blog) {
$post = id(new PhamePost())
->setBloggerPHID($blogger->getPHID())
->setBlogPHID($blog->getPHID())
->setBlog($blog)
->setDatePublished(0)
->setVisibility(self::VISIBILITY_DRAFT);
return $post;
}
public function setBlog(PhameBlog $blog) {
$this->blog = $blog;
return $this;
}
public function getBlog() {
return $this->blog;
}
public function getViewURI() {
// go for the pretty uri if we can
$domain = ($this->blog ? $this->blog->getDomain() : '');
if ($domain) {
$phame_title = PhabricatorSlug::normalize($this->getPhameTitle());
return 'http://'.$domain.'/post/'.$phame_title;
}
$uri = '/phame/post/view/'.$this->getID().'/';
return PhabricatorEnv::getProductionURI($uri);
}
public function getEditURI() {
return '/phame/post/edit/'.$this->getID().'/';
}
public function isDraft() {
return $this->getVisibility() == self::VISIBILITY_DRAFT;
}
public function getHumanName() {
if ($this->isDraft()) {
$name = 'draft';
} else {
$name = 'post';
}
return $name;
}
public function getCommentsWidget() {
$config_data = $this->getConfigData();
if (empty($config_data)) {
return 'none';
}
return idx($config_data, 'comments_widget', 'none');
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'configData' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'title' => 'text255',
- 'phameTitle' => 'text64',
+ 'phameTitle' => 'sort64',
'visibility' => 'uint32',
// T6203/NULLABILITY
// These seem like they should always be non-null?
'blogPHID' => 'phid?',
'body' => 'text?',
'configData' => 'text?',
// T6203/NULLABILITY
// This one probably should be nullable?
'datePublished' => 'epoch',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'phameTitle' => array(
'columns' => array('bloggerPHID', 'phameTitle'),
'unique' => true,
),
'bloggerPosts' => array(
'columns' => array(
'bloggerPHID',
'visibility',
'datePublished',
'id',
),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPhamePostPHIDType::TYPECONST);
}
public function toDictionary() {
return array(
'id' => $this->getID(),
'phid' => $this->getPHID(),
'blogPHID' => $this->getBlogPHID(),
'bloggerPHID' => $this->getBloggerPHID(),
'viewURI' => $this->getViewURI(),
'title' => $this->getTitle(),
'phameTitle' => $this->getPhameTitle(),
'body' => $this->getBody(),
'summary' => PhabricatorMarkupEngine::summarize($this->getBody()),
'datePublished' => $this->getDatePublished(),
'published' => !$this->isDraft(),
);
}
public static function getVisibilityOptionsForSelect() {
return array(
self::VISIBILITY_DRAFT => 'Draft: visible only to me.',
self::VISIBILITY_PUBLISHED => 'Published: visible to the whole world.',
);
}
public function getCommentsWidgetOptionsForSelect() {
$current = $this->getCommentsWidget();
$options = array();
if ($current == 'facebook' ||
PhabricatorFacebookAuthProvider::getFacebookApplicationID()) {
$options['facebook'] = 'Facebook';
}
if ($current == 'disqus' ||
PhabricatorEnv::getEnvConfig('disqus.shortname')) {
$options['disqus'] = 'Disqus';
}
$options['none'] = 'None';
return $options;
}
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
// Draft posts are visible only to the author. Published posts are visible
// to whoever the blog is visible to.
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
if (!$this->isDraft() && $this->getBlog()) {
return $this->getBlog()->getViewPolicy();
} else {
return PhabricatorPolicies::POLICY_NOONE;
}
break;
case PhabricatorPolicyCapability::CAN_EDIT:
return PhabricatorPolicies::POLICY_NOONE;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
// A blog post's author can always view it, and is the only user allowed
// to edit it.
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
case PhabricatorPolicyCapability::CAN_EDIT:
return ($user->getPHID() == $this->getBloggerPHID());
}
}
public function describeAutomaticCapability($capability) {
return pht(
'The author of a blog post can always view and edit it.');
}
/* -( PhabricatorMarkupInterface Implementation )-------------------------- */
public function getMarkupFieldKey($field) {
$hash = PhabricatorHash::digest($this->getMarkupText($field));
return $this->getPHID().':'.$field.':'.$hash;
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newPhameMarkupEngine();
}
public function getMarkupText($field) {
switch ($field) {
case self::MARKUP_FIELD_BODY:
return $this->getBody();
case self::MARKUP_FIELD_SUMMARY:
return PhabricatorMarkupEngine::summarize($this->getBody());
}
}
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
return $output;
}
public function shouldUseMarkupCache($field) {
return (bool)$this->getPHID();
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {
return array(
$this->getBloggerPHID(),
);
}
}
diff --git a/src/applications/phriction/storage/PhrictionContent.php b/src/applications/phriction/storage/PhrictionContent.php
index 5ae4e225fe..c41ec0f302 100644
--- a/src/applications/phriction/storage/PhrictionContent.php
+++ b/src/applications/phriction/storage/PhrictionContent.php
@@ -1,125 +1,125 @@
<?php
/**
* @task markup Markup Interface
*/
final class PhrictionContent extends PhrictionDAO
implements PhabricatorMarkupInterface {
const MARKUP_FIELD_BODY = 'markup:body';
protected $id;
protected $documentID;
protected $version;
protected $authorPHID;
protected $title;
protected $slug;
protected $content;
protected $description;
protected $changeType;
protected $changeRef;
public function renderContent(PhabricatorUser $viewer) {
return PhabricatorMarkupEngine::renderOneObject(
$this,
self::MARKUP_FIELD_BODY,
$viewer);
}
public function getConfiguration() {
return array(
self::CONFIG_COLUMN_SCHEMA => array(
'version' => 'uint32',
- 'title' => 'text',
+ 'title' => 'sort',
'slug' => 'text128',
'content' => 'text',
'changeType' => 'uint32',
'changeRef' => 'uint32?',
// T6203/NULLABILITY
// This should just be empty if not provided?
'description' => 'text?',
),
self::CONFIG_KEY_SCHEMA => array(
'documentID' => array(
'columns' => array('documentID', 'version'),
'unique' => true,
),
'authorPHID' => array(
'columns' => array('authorPHID'),
),
'slug' => array(
'columns' => array('slug'),
),
),
) + parent::getConfiguration();
}
/* -( Markup Interface )--------------------------------------------------- */
/**
* @task markup
*/
public function getMarkupFieldKey($field) {
if ($this->shouldUseMarkupCache($field)) {
$id = $this->getID();
} else {
$id = PhabricatorHash::digest($this->getMarkupText($field));
}
return "phriction:{$field}:{$id}";
}
/**
* @task markup
*/
public function getMarkupText($field) {
return $this->getContent();
}
/**
* @task markup
*/
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newPhrictionMarkupEngine();
}
/**
* @task markup
*/
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
$toc = PhutilRemarkupHeaderBlockRule::renderTableOfContents(
$engine);
if ($toc) {
$toc = phutil_tag_div('phabricator-remarkup-toc', array(
phutil_tag_div(
'phabricator-remarkup-toc-header',
pht('Table of Contents')),
$toc,
));
}
return phutil_tag_div('phabricator-remarkup', array($toc, $output));
}
/**
* @task markup
*/
public function shouldUseMarkupCache($field) {
return (bool)$this->getID();
}
}
diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php
index 0ce1383d68..9f384ec610 100644
--- a/src/applications/phriction/storage/PhrictionDocument.php
+++ b/src/applications/phriction/storage/PhrictionDocument.php
@@ -1,227 +1,227 @@
<?php
final class PhrictionDocument extends PhrictionDAO
implements
PhabricatorPolicyInterface,
PhabricatorSubscribableInterface,
PhabricatorFlaggableInterface,
PhabricatorTokenReceiverInterface,
PhabricatorDestructibleInterface {
protected $slug;
protected $depth;
protected $contentID;
protected $status;
private $contentObject = self::ATTACHABLE;
private $ancestors = array();
// TODO: This should be `self::ATTACHABLE`, but there are still a lot of call
// sites which load PhrictionDocuments directly.
private $project = null;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
- 'slug' => 'text128',
+ 'slug' => 'sort128',
'depth' => 'uint32',
'contentID' => 'id?',
'status' => 'uint32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'slug' => array(
'columns' => array('slug'),
'unique' => true,
),
'depth' => array(
'columns' => array('depth', 'slug'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhrictionDocumentPHIDType::TYPECONST);
}
public static function getSlugURI($slug, $type = 'document') {
static $types = array(
'document' => '/w/',
'history' => '/phriction/history/',
);
if (empty($types[$type])) {
throw new Exception("Unknown URI type '{$type}'!");
}
$prefix = $types[$type];
if ($slug == '/') {
return $prefix;
} else {
// NOTE: The effect here is to escape non-latin characters, since modern
// browsers deal with escaped UTF8 characters in a reasonable way (showing
// the user a readable URI) but older programs may not.
$slug = phutil_escape_uri($slug);
return $prefix.$slug;
}
}
public function setSlug($slug) {
$this->slug = PhabricatorSlug::normalize($slug);
$this->depth = PhabricatorSlug::getDepth($slug);
return $this;
}
public function attachContent(PhrictionContent $content) {
$this->contentObject = $content;
return $this;
}
public function getContent() {
return $this->assertAttached($this->contentObject);
}
public function getProject() {
return $this->assertAttached($this->project);
}
public function attachProject(PhabricatorProject $project = null) {
$this->project = $project;
return $this;
}
public function hasProject() {
return (bool)$this->getProject();
}
public function getAncestors() {
return $this->ancestors;
}
public function getAncestor($slug) {
return $this->assertAttachedKey($this->ancestors, $slug);
}
public function attachAncestor($slug, $ancestor) {
$this->ancestors[$slug] = $ancestor;
return $this;
}
public static function isProjectSlug($slug) {
$slug = PhabricatorSlug::normalize($slug);
$prefix = 'projects/';
if ($slug == $prefix) {
// The 'projects/' document is not itself a project slug.
return false;
}
return !strncmp($slug, $prefix, strlen($prefix));
}
public static function getProjectSlugIdentifier($slug) {
if (!self::isProjectSlug($slug)) {
throw new Exception("Slug '{$slug}' is not a project slug!");
}
$slug = PhabricatorSlug::normalize($slug);
$parts = explode('/', $slug);
return $parts[1].'/';
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
if ($this->hasProject()) {
return $this->getProject()->getPolicy($capability);
}
return PhabricatorPolicies::POLICY_USER;
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
if ($this->hasProject()) {
return $this->getProject()->hasAutomaticCapability($capability, $user);
}
return false;
}
public function describeAutomaticCapability($capability) {
if ($this->hasProject()) {
return pht(
"This is a project wiki page, and inherits the project's policies.");
}
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return pht(
'To view a wiki document, you must also be able to view all '.
'of its parents.');
}
return null;
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
return false;
}
public function shouldShowSubscribersProperty() {
return true;
}
public function shouldAllowSubscription($phid) {
return true;
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {
return PhabricatorSubscribersQuery::loadSubscribersForPHID($this->phid);
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$contents = id(new PhrictionContent())->loadAllWhere(
'documentID = %d',
$this->getID());
foreach ($contents as $content) {
$content->delete();
}
$this->saveTransaction();
}
}
diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php
index d3538332ae..27db584e24 100644
--- a/src/applications/project/storage/PhabricatorProject.php
+++ b/src/applications/project/storage/PhabricatorProject.php
@@ -1,372 +1,372 @@
<?php
final class PhabricatorProject extends PhabricatorProjectDAO
implements
PhabricatorFlaggableInterface,
PhabricatorPolicyInterface,
PhabricatorSubscribableInterface,
PhabricatorCustomFieldInterface,
PhabricatorDestructibleInterface {
protected $name;
protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
protected $authorPHID;
protected $subprojectPHIDs = array();
protected $phrictionSlug;
protected $profileImagePHID;
protected $icon;
protected $color;
protected $viewPolicy;
protected $editPolicy;
protected $joinPolicy;
protected $isMembershipLocked;
private $memberPHIDs = self::ATTACHABLE;
private $watcherPHIDs = self::ATTACHABLE;
private $sparseWatchers = self::ATTACHABLE;
private $sparseMembers = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;
private $profileImageFile = self::ATTACHABLE;
private $slugs = self::ATTACHABLE;
const DEFAULT_ICON = 'fa-briefcase';
const DEFAULT_COLOR = 'blue';
const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken';
public static function initializeNewProject(PhabricatorUser $actor) {
return id(new PhabricatorProject())
->setName('')
->setAuthorPHID($actor->getPHID())
->setIcon(self::DEFAULT_ICON)
->setColor(self::DEFAULT_COLOR)
->setViewPolicy(PhabricatorPolicies::POLICY_USER)
->setEditPolicy(PhabricatorPolicies::POLICY_USER)
->setJoinPolicy(PhabricatorPolicies::POLICY_USER)
->setIsMembershipLocked(0)
->attachMemberPHIDs(array());
}
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
PhabricatorPolicyCapability::CAN_JOIN,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
case PhabricatorPolicyCapability::CAN_JOIN:
return $this->getJoinPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
if ($this->isUserMember($viewer->getPHID())) {
// Project members can always view a project.
return true;
}
break;
case PhabricatorPolicyCapability::CAN_EDIT:
break;
case PhabricatorPolicyCapability::CAN_JOIN:
$can_edit = PhabricatorPolicyCapability::CAN_EDIT;
if (PhabricatorPolicyFilter::hasCapability($viewer, $this, $can_edit)) {
// Project editors can always join a project.
return true;
}
break;
}
return false;
}
public function describeAutomaticCapability($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return pht('Members of a project can always view it.');
case PhabricatorPolicyCapability::CAN_JOIN:
return pht('Users who can edit a project can always join it.');
}
return null;
}
public function isUserMember($user_phid) {
if ($this->memberPHIDs !== self::ATTACHABLE) {
return in_array($user_phid, $this->memberPHIDs);
}
return $this->assertAttachedKey($this->sparseMembers, $user_phid);
}
public function setIsUserMember($user_phid, $is_member) {
if ($this->sparseMembers === self::ATTACHABLE) {
$this->sparseMembers = array();
}
$this->sparseMembers[$user_phid] = $is_member;
return $this;
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'subprojectPHIDs' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
- 'name' => 'text128',
+ 'name' => 'sort128',
'status' => 'text32',
'phrictionSlug' => 'text128?',
'isMembershipLocked' => 'bool',
'profileImagePHID' => 'phid?',
'icon' => 'text32',
'color' => 'text32',
// T6203/NULLABILITY
// These are definitely wrong and should always exist.
'editPolicy' => 'policy?',
'viewPolicy' => 'policy?',
'joinPolicy' => 'policy?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'key_icon' => array(
'columns' => array('icon'),
),
'key_color' => array(
'columns' => array('color'),
),
'phrictionSlug' => array(
'columns' => array('phrictionSlug'),
'unique' => true,
),
'name' => array(
'columns' => array('name'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorProjectProjectPHIDType::TYPECONST);
}
public function attachMemberPHIDs(array $phids) {
$this->memberPHIDs = $phids;
return $this;
}
public function getMemberPHIDs() {
return $this->assertAttached($this->memberPHIDs);
}
public function setPhrictionSlug($slug) {
// NOTE: We're doing a little magic here and stripping out '/' so that
// project pages always appear at top level under projects/ even if the
// display name is "Hack / Slash" or similar (it will become
// 'hack_slash' instead of 'hack/slash').
$slug = str_replace('/', ' ', $slug);
$slug = PhabricatorSlug::normalize($slug);
$this->phrictionSlug = $slug;
return $this;
}
public function getFullPhrictionSlug() {
$slug = $this->getPhrictionSlug();
return 'projects/'.$slug;
}
// TODO - once we sever project => phriction automagicalness,
// migrate getPhrictionSlug to have no trailing slash and be called
// getPrimarySlug
public function getPrimarySlug() {
$slug = $this->getPhrictionSlug();
return rtrim($slug, '/');
}
public function isArchived() {
return ($this->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED);
}
public function getProfileImageURI() {
return $this->getProfileImageFile()->getBestURI();
}
public function attachProfileImageFile(PhabricatorFile $file) {
$this->profileImageFile = $file;
return $this;
}
public function getProfileImageFile() {
return $this->assertAttached($this->profileImageFile);
}
public function isUserWatcher($user_phid) {
if ($this->watcherPHIDs !== self::ATTACHABLE) {
return in_array($user_phid, $this->watcherPHIDs);
}
return $this->assertAttachedKey($this->sparseWatchers, $user_phid);
}
public function setIsUserWatcher($user_phid, $is_watcher) {
if ($this->sparseWatchers === self::ATTACHABLE) {
$this->sparseWatchers = array();
}
$this->sparseWatchers[$user_phid] = $is_watcher;
return $this;
}
public function attachWatcherPHIDs(array $phids) {
$this->watcherPHIDs = $phids;
return $this;
}
public function getWatcherPHIDs() {
return $this->assertAttached($this->watcherPHIDs);
}
public function attachSlugs(array $slugs) {
$this->slugs = $slugs;
return $this;
}
public function getSlugs() {
return $this->assertAttached($this->slugs);
}
public function getColor() {
if ($this->isArchived()) {
return PHUITagView::COLOR_DISABLED;
}
return $this->color;
}
public function save() {
$this->openTransaction();
$result = parent::save();
$this->updateDatasourceTokens();
$this->saveTransaction();
return $result;
}
public function updateDatasourceTokens() {
$table = self::TABLE_DATASOURCE_TOKEN;
$conn_w = $this->establishConnection('w');
$id = $this->getID();
$slugs = queryfx_all(
$conn_w,
'SELECT * FROM %T WHERE projectPHID = %s',
id(new PhabricatorProjectSlug())->getTableName(),
$this->getPHID());
$all_strings = ipull($slugs, 'slug');
$all_strings[] = $this->getName();
$all_strings = implode(' ', $all_strings);
$tokens = PhabricatorTypeaheadDatasource::tokenizeString($all_strings);
$sql = array();
foreach ($tokens as $token) {
$sql[] = qsprintf($conn_w, '(%d, %s)', $id, $token);
}
$this->openTransaction();
queryfx(
$conn_w,
'DELETE FROM %T WHERE projectID = %d',
$table,
$id);
foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
queryfx(
$conn_w,
'INSERT INTO %T (projectID, token) VALUES %Q',
$table,
$chunk);
}
$this->saveTransaction();
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
return false;
}
public function shouldShowSubscribersProperty() {
return false;
}
public function shouldAllowSubscription($phid) {
return $this->isUserMember($phid) &&
!$this->isUserWatcher($phid);
}
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
public function getCustomFieldSpecificationForRole($role) {
return PhabricatorEnv::getEnvConfig('projects.fields');
}
public function getCustomFieldBaseClass() {
return 'PhabricatorProjectCustomField';
}
public function getCustomFields() {
return $this->assertAttached($this->customFields);
}
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
$this->customFields = $fields;
return $this;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$columns = id(new PhabricatorProjectColumn())
->loadAllWhere('projectPHID = %s', $this->getPHID());
foreach ($columns as $column) {
$engine->destroyObject($column);
}
$slugs = id(new PhabricatorProjectSlug())
->loadAllWhere('projectPHID = %s', $this->getPHID());
foreach ($slugs as $slug) {
$slug->delete();
}
$this->saveTransaction();
}
}
diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
index e1a96149e5..bba53b0d9c 100644
--- a/src/applications/repository/storage/PhabricatorRepository.php
+++ b/src/applications/repository/storage/PhabricatorRepository.php
@@ -1,1591 +1,1591 @@
<?php
/**
* @task uri Repository URI Management
* @task autoclose Autoclose
*/
final class PhabricatorRepository extends PhabricatorRepositoryDAO
implements
PhabricatorPolicyInterface,
PhabricatorFlaggableInterface,
PhabricatorMarkupInterface,
PhabricatorDestructibleInterface,
PhabricatorProjectInterface {
/**
* Shortest hash we'll recognize in raw "a829f32" form.
*/
const MINIMUM_UNQUALIFIED_HASH = 7;
/**
* Shortest hash we'll recognize in qualified "rXab7ef2f8" form.
*/
const MINIMUM_QUALIFIED_HASH = 5;
const TABLE_PATH = 'repository_path';
const TABLE_PATHCHANGE = 'repository_pathchange';
const TABLE_FILESYSTEM = 'repository_filesystem';
const TABLE_SUMMARY = 'repository_summary';
const TABLE_BADCOMMIT = 'repository_badcommit';
const TABLE_LINTMESSAGE = 'repository_lintmessage';
const TABLE_PARENTS = 'repository_parents';
const TABLE_COVERAGE = 'repository_coverage';
const SERVE_OFF = 'off';
const SERVE_READONLY = 'readonly';
const SERVE_READWRITE = 'readwrite';
const BECAUSE_REPOSITORY_IMPORTING = 'auto/importing';
const BECAUSE_AUTOCLOSE_DISABLED = 'auto/disabled';
const BECAUSE_NOT_ON_AUTOCLOSE_BRANCH = 'auto/nobranch';
const BECAUSE_BRANCH_UNTRACKED = 'auto/notrack';
const BECAUSE_BRANCH_NOT_AUTOCLOSE = 'auto/noclose';
protected $name;
protected $callsign;
protected $uuid;
protected $viewPolicy;
protected $editPolicy;
protected $pushPolicy;
protected $versionControlSystem;
protected $details = array();
protected $credentialPHID;
private $commitCount = self::ATTACHABLE;
private $mostRecentCommit = self::ATTACHABLE;
private $projectPHIDs = self::ATTACHABLE;
public static function initializeNewRepository(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorDiffusionApplication'))
->executeOne();
$view_policy = $app->getPolicy(DiffusionDefaultViewCapability::CAPABILITY);
$edit_policy = $app->getPolicy(DiffusionDefaultEditCapability::CAPABILITY);
$push_policy = $app->getPolicy(DiffusionDefaultPushCapability::CAPABILITY);
return id(new PhabricatorRepository())
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy)
->setPushPolicy($push_policy);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'details' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
- 'name' => 'text255',
- 'callsign' => 'text32',
+ 'name' => 'sort255',
+ 'callsign' => 'sort32',
'versionControlSystem' => 'text32',
'uuid' => 'text64?',
'pushPolicy' => 'policy',
'credentialPHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'callsign' => array(
'columns' => array('callsign'),
'unique' => true,
),
'key_name' => array(
'columns' => array('name(128)'),
),
'key_vcs' => array(
'columns' => array('versionControlSystem'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorRepositoryRepositoryPHIDType::TYPECONST);
}
public function toDictionary() {
return array(
'id' => $this->getID(),
'name' => $this->getName(),
'phid' => $this->getPHID(),
'callsign' => $this->getCallsign(),
'monogram' => $this->getMonogram(),
'vcs' => $this->getVersionControlSystem(),
'uri' => PhabricatorEnv::getProductionURI($this->getURI()),
'remoteURI' => (string)$this->getRemoteURI(),
'description' => $this->getDetail('description'),
'isActive' => $this->isTracked(),
'isHosted' => $this->isHosted(),
'isImporting' => $this->isImporting(),
);
}
public function getMonogram() {
return 'r'.$this->getCallsign();
}
public function getDetail($key, $default = null) {
return idx($this->details, $key, $default);
}
public function getHumanReadableDetail($key, $default = null) {
$value = $this->getDetail($key, $default);
switch ($key) {
case 'branch-filter':
case 'close-commits-filter':
$value = array_keys($value);
$value = implode(', ', $value);
break;
}
return $value;
}
public function setDetail($key, $value) {
$this->details[$key] = $value;
return $this;
}
public function attachCommitCount($count) {
$this->commitCount = $count;
return $this;
}
public function getCommitCount() {
return $this->assertAttached($this->commitCount);
}
public function attachMostRecentCommit(
PhabricatorRepositoryCommit $commit = null) {
$this->mostRecentCommit = $commit;
return $this;
}
public function getMostRecentCommit() {
return $this->assertAttached($this->mostRecentCommit);
}
public function getDiffusionBrowseURIForPath(
PhabricatorUser $user,
$path,
$line = null,
$branch = null) {
$drequest = DiffusionRequest::newFromDictionary(
array(
'user' => $user,
'repository' => $this,
'path' => $path,
'branch' => $branch,
));
return $drequest->generateURI(
array(
'action' => 'browse',
'line' => $line,
));
}
public function getLocalPath() {
return $this->getDetail('local-path');
}
public function getSubversionBaseURI($commit = null) {
$subpath = $this->getDetail('svn-subpath');
if (!strlen($subpath)) {
$subpath = null;
}
return $this->getSubversionPathURI($subpath, $commit);
}
public function getSubversionPathURI($path = null, $commit = null) {
$vcs = $this->getVersionControlSystem();
if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) {
throw new Exception('Not a subversion repository!');
}
if ($this->isHosted()) {
$uri = 'file://'.$this->getLocalPath();
} else {
$uri = $this->getDetail('remote-uri');
}
$uri = rtrim($uri, '/');
if (strlen($path)) {
$path = rawurlencode($path);
$path = str_replace('%2F', '/', $path);
$uri = $uri.'/'.ltrim($path, '/');
}
if ($path !== null || $commit !== null) {
$uri .= '@';
}
if ($commit !== null) {
$uri .= $commit;
}
return $uri;
}
public function attachProjectPHIDs(array $project_phids) {
$this->projectPHIDs = $project_phids;
return $this;
}
public function getProjectPHIDs() {
return $this->assertAttached($this->projectPHIDs);
}
/**
* Get the name of the directory this repository should clone or checkout
* into. For example, if the repository name is "Example Repository", a
* reasonable name might be "example-repository". This is used to help users
* get reasonable results when cloning repositories, since they generally do
* not want to clone into directories called "X/" or "Example Repository/".
*
* @return string
*/
public function getCloneName() {
$name = $this->getDetail('clone-name');
// Make some reasonable effort to produce reasonable default directory
// names from repository names.
if (!strlen($name)) {
$name = $this->getName();
$name = phutil_utf8_strtolower($name);
$name = preg_replace('@[/ -:]+@', '-', $name);
$name = trim($name, '-');
if (!strlen($name)) {
$name = $this->getCallsign();
}
}
return $name;
}
/* -( Remote Command Execution )------------------------------------------- */
public function execRemoteCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newRemoteCommandFuture($args)->resolve();
}
public function execxRemoteCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newRemoteCommandFuture($args)->resolvex();
}
public function getRemoteCommandFuture($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newRemoteCommandFuture($args);
}
public function passthruRemoteCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newRemoteCommandPassthru($args)->execute();
}
private function newRemoteCommandFuture(array $argv) {
$argv = $this->formatRemoteCommand($argv);
$future = newv('ExecFuture', $argv);
$future->setEnv($this->getRemoteCommandEnvironment());
return $future;
}
private function newRemoteCommandPassthru(array $argv) {
$argv = $this->formatRemoteCommand($argv);
$passthru = newv('PhutilExecPassthru', $argv);
$passthru->setEnv($this->getRemoteCommandEnvironment());
return $passthru;
}
/* -( Local Command Execution )-------------------------------------------- */
public function execLocalCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newLocalCommandFuture($args)->resolve();
}
public function execxLocalCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newLocalCommandFuture($args)->resolvex();
}
public function getLocalCommandFuture($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newLocalCommandFuture($args);
}
public function passthruLocalCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newLocalCommandPassthru($args)->execute();
}
private function newLocalCommandFuture(array $argv) {
$this->assertLocalExists();
$argv = $this->formatLocalCommand($argv);
$future = newv('ExecFuture', $argv);
$future->setEnv($this->getLocalCommandEnvironment());
if ($this->usesLocalWorkingCopy()) {
$future->setCWD($this->getLocalPath());
}
return $future;
}
private function newLocalCommandPassthru(array $argv) {
$this->assertLocalExists();
$argv = $this->formatLocalCommand($argv);
$future = newv('PhutilExecPassthru', $argv);
$future->setEnv($this->getLocalCommandEnvironment());
if ($this->usesLocalWorkingCopy()) {
$future->setCWD($this->getLocalPath());
}
return $future;
}
/* -( Command Infrastructure )--------------------------------------------- */
private function getSSHWrapper() {
$root = dirname(phutil_get_library_root('phabricator'));
return $root.'/bin/ssh-connect';
}
private function getCommonCommandEnvironment() {
$env = array(
// NOTE: Force the language to "en_US.UTF-8", which overrides locale
// settings. This makes stuff print in English instead of, e.g., French,
// so we can parse the output of some commands, error messages, etc.
'LANG' => 'en_US.UTF-8',
// Propagate PHABRICATOR_ENV explicitly. For discussion, see T4155.
'PHABRICATOR_ENV' => PhabricatorEnv::getSelectedEnvironmentName(),
);
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
// NOTE: See T2965. Some time after Git 1.7.5.4, Git started fataling if
// it can not read $HOME. For many users, $HOME points at /root (this
// seems to be a default result of Apache setup). Instead, explicitly
// point $HOME at a readable, empty directory so that Git looks for the
// config file it's after, fails to locate it, and moves on. This is
// really silly, but seems like the least damaging approach to
// mitigating the issue.
$root = dirname(phutil_get_library_root('phabricator'));
$env['HOME'] = $root.'/support/empty/';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
// NOTE: This overrides certain configuration, extensions, and settings
// which make Mercurial commands do random unusual things.
$env['HGPLAIN'] = 1;
break;
default:
throw new Exception('Unrecognized version control system.');
}
return $env;
}
private function getLocalCommandEnvironment() {
return $this->getCommonCommandEnvironment();
}
private function getRemoteCommandEnvironment() {
$env = $this->getCommonCommandEnvironment();
if ($this->shouldUseSSH()) {
// NOTE: This is read by `bin/ssh-connect`, and tells it which credentials
// to use.
$env['PHABRICATOR_CREDENTIAL'] = $this->getCredentialPHID();
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
// Force SVN to use `bin/ssh-connect`.
$env['SVN_SSH'] = $this->getSSHWrapper();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
// Force Git to use `bin/ssh-connect`.
$env['GIT_SSH'] = $this->getSSHWrapper();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
// We force Mercurial through `bin/ssh-connect` too, but it uses a
// command-line flag instead of an environmental variable.
break;
default:
throw new Exception('Unrecognized version control system.');
}
}
return $env;
}
private function formatRemoteCommand(array $args) {
$pattern = $args[0];
$args = array_slice($args, 1);
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
if ($this->shouldUseHTTP() || $this->shouldUseSVNProtocol()) {
$flags = array();
$flag_args = array();
$flags[] = '--non-interactive';
$flags[] = '--no-auth-cache';
if ($this->shouldUseHTTP()) {
$flags[] = '--trust-server-cert';
}
$credential_phid = $this->getCredentialPHID();
if ($credential_phid) {
$key = PassphrasePasswordKey::loadFromPHID(
$credential_phid,
PhabricatorUser::getOmnipotentUser());
$flags[] = '--username %P';
$flags[] = '--password %P';
$flag_args[] = $key->getUsernameEnvelope();
$flag_args[] = $key->getPasswordEnvelope();
}
$flags = implode(' ', $flags);
$pattern = "svn {$flags} {$pattern}";
$args = array_mergev(array($flag_args, $args));
} else {
$pattern = "svn --non-interactive {$pattern}";
}
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$pattern = "git {$pattern}";
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
if ($this->shouldUseSSH()) {
$pattern = "hg --config ui.ssh=%s {$pattern}";
array_unshift(
$args,
$this->getSSHWrapper());
} else {
$pattern = "hg {$pattern}";
}
break;
default:
throw new Exception('Unrecognized version control system.');
}
array_unshift($args, $pattern);
return $args;
}
private function formatLocalCommand(array $args) {
$pattern = $args[0];
$args = array_slice($args, 1);
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$pattern = "svn --non-interactive {$pattern}";
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$pattern = "git {$pattern}";
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$pattern = "hg {$pattern}";
break;
default:
throw new Exception('Unrecognized version control system.');
}
array_unshift($args, $pattern);
return $args;
}
/**
* Sanitize output of an `hg` command invoked with the `--debug` flag to make
* it usable.
*
* @param string Output from `hg --debug ...`
* @return string Usable output.
*/
public static function filterMercurialDebugOutput($stdout) {
// When hg commands are run with `--debug` and some config file isn't
// trusted, Mercurial prints out a warning to stdout, twice, after Feb 2011.
//
// http://selenic.com/pipermail/mercurial-devel/2011-February/028541.html
$lines = preg_split('/(?<=\n)/', $stdout);
$regex = '/ignoring untrusted configuration option .*\n$/';
foreach ($lines as $key => $line) {
$lines[$key] = preg_replace($regex, '', $line);
}
return implode('', $lines);
}
public function getURI() {
return '/diffusion/'.$this->getCallsign().'/';
}
public function getNormalizedPath() {
$uri = (string)$this->getCloneURIObject();
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$normalized_uri = new PhabricatorRepositoryURINormalizer(
PhabricatorRepositoryURINormalizer::TYPE_GIT,
$uri);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$normalized_uri = new PhabricatorRepositoryURINormalizer(
PhabricatorRepositoryURINormalizer::TYPE_SVN,
$uri);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$normalized_uri = new PhabricatorRepositoryURINormalizer(
PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL,
$uri);
break;
default:
throw new Exception('Unrecognized version control system.');
}
return $normalized_uri->getNormalizedPath();
}
public function isTracked() {
return $this->getDetail('tracking-enabled', false);
}
public function getDefaultBranch() {
$default = $this->getDetail('default-branch');
if (strlen($default)) {
return $default;
}
$default_branches = array(
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => 'master',
PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => 'default',
);
return idx($default_branches, $this->getVersionControlSystem());
}
public function getDefaultArcanistBranch() {
return coalesce($this->getDefaultBranch(), 'svn');
}
private function isBranchInFilter($branch, $filter_key) {
$vcs = $this->getVersionControlSystem();
$is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
$use_filter = ($is_git);
if (!$use_filter) {
// If this VCS doesn't use filters, pass everything through.
return true;
}
$filter = $this->getDetail($filter_key, array());
// If there's no filter set, let everything through.
if (!$filter) {
return true;
}
// If this branch isn't literally named `regexp(...)`, and it's in the
// filter list, let it through.
if (isset($filter[$branch])) {
if (self::extractBranchRegexp($branch) === null) {
return true;
}
}
// If the branch matches a regexp, let it through.
foreach ($filter as $pattern => $ignored) {
$regexp = self::extractBranchRegexp($pattern);
if ($regexp !== null) {
if (preg_match($regexp, $branch)) {
return true;
}
}
}
// Nothing matched, so filter this branch out.
return false;
}
public static function extractBranchRegexp($pattern) {
$matches = null;
if (preg_match('/^regexp\\((.*)\\)\z/', $pattern, $matches)) {
return $matches[1];
}
return null;
}
public function shouldTrackBranch($branch) {
return $this->isBranchInFilter($branch, 'branch-filter');
}
public function formatCommitName($commit_identifier) {
$vcs = $this->getVersionControlSystem();
$type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
$type_hg = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL;
$is_git = ($vcs == $type_git);
$is_hg = ($vcs == $type_hg);
if ($is_git || $is_hg) {
$short_identifier = substr($commit_identifier, 0, 12);
} else {
$short_identifier = $commit_identifier;
}
return 'r'.$this->getCallsign().$short_identifier;
}
public function isImporting() {
return (bool)$this->getDetail('importing', false);
}
/* -( Autoclose )---------------------------------------------------------- */
/**
* Determine if autoclose is active for a branch.
*
* For more details about why, use @{method:shouldSkipAutocloseBranch}.
*
* @param string Branch name to check.
* @return bool True if autoclose is active for the branch.
* @task autoclose
*/
public function shouldAutocloseBranch($branch) {
return ($this->shouldSkipAutocloseBranch($branch) === null);
}
/**
* Determine if autoclose is active for a commit.
*
* For more details about why, use @{method:shouldSkipAutocloseCommit}.
*
* @param PhabricatorRepositoryCommit Commit to check.
* @return bool True if autoclose is active for the commit.
* @task autoclose
*/
public function shouldAutocloseCommit(PhabricatorRepositoryCommit $commit) {
return ($this->shouldSkipAutocloseCommit($commit) === null);
}
/**
* Determine why autoclose should be skipped for a branch.
*
* This method gives a detailed reason why autoclose will be skipped. To
* perform a simple test, use @{method:shouldAutocloseBranch}.
*
* @param string Branch name to check.
* @return const|null Constant identifying reason to skip this branch, or null
* if autoclose is active.
* @task autoclose
*/
public function shouldSkipAutocloseBranch($branch) {
$all_reason = $this->shouldSkipAllAutoclose();
if ($all_reason) {
return $all_reason;
}
if (!$this->shouldTrackBranch($branch)) {
return self::BECAUSE_BRANCH_UNTRACKED;
}
if (!$this->isBranchInFilter($branch, 'close-commits-filter')) {
return self::BECAUSE_BRANCH_NOT_AUTOCLOSE;
}
return null;
}
/**
* Determine why autoclose should be skipped for a commit.
*
* This method gives a detailed reason why autoclose will be skipped. To
* perform a simple test, use @{method:shouldAutocloseCommit}.
*
* @param PhabricatorRepositoryCommit Commit to check.
* @return const|null Constant identifying reason to skip this commit, or null
* if autoclose is active.
* @task autoclose
*/
public function shouldSkipAutocloseCommit(
PhabricatorRepositoryCommit $commit) {
$all_reason = $this->shouldSkipAllAutoclose();
if ($all_reason) {
return $all_reason;
}
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
return null;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
break;
default:
throw new Exception('Unrecognized version control system.');
}
$closeable_flag = PhabricatorRepositoryCommit::IMPORTED_CLOSEABLE;
if (!$commit->isPartiallyImported($closeable_flag)) {
return self::BECAUSE_NOT_ON_AUTOCLOSE_BRANCH;
}
return null;
}
/**
* Determine why all autoclose operations should be skipped for this
* repository.
*
* @return const|null Constant identifying reason to skip all autoclose
* operations, or null if autoclose operations are not blocked at the
* repository level.
* @task autoclose
*/
private function shouldSkipAllAutoclose() {
if ($this->isImporting()) {
return self::BECAUSE_REPOSITORY_IMPORTING;
}
if ($this->getDetail('disable-autoclose', false)) {
return self::BECAUSE_AUTOCLOSE_DISABLED;
}
return null;
}
/* -( Repository URI Management )------------------------------------------ */
/**
* Get the remote URI for this repository.
*
* @return string
* @task uri
*/
public function getRemoteURI() {
return (string)$this->getRemoteURIObject();
}
/**
* Get the remote URI for this repository, including credentials if they're
* used by this repository.
*
* @return PhutilOpaqueEnvelope URI, possibly including credentials.
* @task uri
*/
public function getRemoteURIEnvelope() {
$uri = $this->getRemoteURIObject();
$remote_protocol = $this->getRemoteProtocol();
if ($remote_protocol == 'http' || $remote_protocol == 'https') {
// For SVN, we use `--username` and `--password` flags separately, so
// don't add any credentials here.
if (!$this->isSVN()) {
$credential_phid = $this->getCredentialPHID();
if ($credential_phid) {
$key = PassphrasePasswordKey::loadFromPHID(
$credential_phid,
PhabricatorUser::getOmnipotentUser());
$uri->setUser($key->getUsernameEnvelope()->openEnvelope());
$uri->setPass($key->getPasswordEnvelope()->openEnvelope());
}
}
}
return new PhutilOpaqueEnvelope((string)$uri);
}
/**
* Get the clone (or checkout) URI for this repository, without authentication
* information.
*
* @return string Repository URI.
* @task uri
*/
public function getPublicCloneURI() {
$uri = $this->getCloneURIObject();
// Make sure we don't leak anything if this repo is using HTTP Basic Auth
// with the credentials in the URI or something zany like that.
// If repository is not accessed over SSH we remove both username and
// password.
if (!$this->isHosted()) {
if (!$this->shouldUseSSH()) {
$uri->setUser(null);
// This might be a Git URI or a normal URI. If it's Git, there's no
// password support.
if ($uri instanceof PhutilURI) {
$uri->setPass(null);
}
}
}
return (string)$uri;
}
/**
* Get the protocol for the repository's remote.
*
* @return string Protocol, like "ssh" or "git".
* @task uri
*/
public function getRemoteProtocol() {
$uri = $this->getRemoteURIObject();
if ($uri instanceof PhutilGitURI) {
return 'ssh';
} else {
return $uri->getProtocol();
}
}
/**
* Get a parsed object representation of the repository's remote URI. This
* may be a normal URI (returned as a @{class@libphutil:PhutilURI}) or a git
* URI (returned as a @{class@libphutil:PhutilGitURI}).
*
* @return wild A @{class@libphutil:PhutilURI} or
* @{class@libphutil:PhutilGitURI}.
* @task uri
*/
public function getRemoteURIObject() {
$raw_uri = $this->getDetail('remote-uri');
if (!$raw_uri) {
return new PhutilURI('');
}
if (!strncmp($raw_uri, '/', 1)) {
return new PhutilURI('file://'.$raw_uri);
}
$uri = new PhutilURI($raw_uri);
if ($uri->getProtocol()) {
return $uri;
}
$uri = new PhutilGitURI($raw_uri);
if ($uri->getDomain()) {
return $uri;
}
throw new Exception("Remote URI '{$raw_uri}' could not be parsed!");
}
/**
* Get the "best" clone/checkout URI for this repository, on any protocol.
*/
public function getCloneURIObject() {
if (!$this->isHosted()) {
if ($this->isSVN()) {
// Make sure we pick up the "Import Only" path for Subversion, so
// the user clones the repository starting at the correct path, not
// from the root.
$base_uri = $this->getSubversionBaseURI();
$base_uri = new PhutilURI($base_uri);
$path = $base_uri->getPath();
if (!$path) {
$path = '/';
}
// If the trailing "@" is not required to escape the URI, strip it for
// readability.
if (!preg_match('/@.*@/', $path)) {
$path = rtrim($path, '@');
}
$base_uri->setPath($path);
return $base_uri;
} else {
return $this->getRemoteURIObject();
}
}
// Choose the best URI: pick a read/write URI over a URI which is not
// read/write, and SSH over HTTP.
$serve_ssh = $this->getServeOverSSH();
$serve_http = $this->getServeOverHTTP();
if ($serve_ssh === self::SERVE_READWRITE) {
return $this->getSSHCloneURIObject();
} else if ($serve_http === self::SERVE_READWRITE) {
return $this->getHTTPCloneURIObject();
} else if ($serve_ssh !== self::SERVE_OFF) {
return $this->getSSHCloneURIObject();
} else if ($serve_http !== self::SERVE_OFF) {
return $this->getHTTPCloneURIObject();
} else {
return null;
}
}
/**
* Get the repository's SSH clone/checkout URI, if one exists.
*/
public function getSSHCloneURIObject() {
if (!$this->isHosted()) {
if ($this->shouldUseSSH()) {
return $this->getRemoteURIObject();
} else {
return null;
}
}
$serve_ssh = $this->getServeOverSSH();
if ($serve_ssh === self::SERVE_OFF) {
return null;
}
$uri = new PhutilURI(PhabricatorEnv::getProductionURI($this->getURI()));
if ($this->isSVN()) {
$uri->setProtocol('svn+ssh');
} else {
$uri->setProtocol('ssh');
}
if ($this->isGit()) {
$uri->setPath($uri->getPath().$this->getCloneName().'.git');
} else if ($this->isHg()) {
$uri->setPath($uri->getPath().$this->getCloneName().'/');
}
$ssh_user = PhabricatorEnv::getEnvConfig('diffusion.ssh-user');
if ($ssh_user) {
$uri->setUser($ssh_user);
}
$uri->setPort(PhabricatorEnv::getEnvConfig('diffusion.ssh-port'));
return $uri;
}
/**
* Get the repository's HTTP clone/checkout URI, if one exists.
*/
public function getHTTPCloneURIObject() {
if (!$this->isHosted()) {
if ($this->shouldUseHTTP()) {
return $this->getRemoteURIObject();
} else {
return null;
}
}
$serve_http = $this->getServeOverHTTP();
if ($serve_http === self::SERVE_OFF) {
return null;
}
$uri = PhabricatorEnv::getProductionURI($this->getURI());
$uri = new PhutilURI($uri);
if ($this->isGit()) {
$uri->setPath($uri->getPath().$this->getCloneName().'.git');
} else if ($this->isHg()) {
$uri->setPath($uri->getPath().$this->getCloneName().'/');
}
return $uri;
}
/**
* Determine if we should connect to the remote using SSH flags and
* credentials.
*
* @return bool True to use the SSH protocol.
* @task uri
*/
private function shouldUseSSH() {
if ($this->isHosted()) {
return false;
}
$protocol = $this->getRemoteProtocol();
if ($this->isSSHProtocol($protocol)) {
return true;
}
return false;
}
/**
* Determine if we should connect to the remote using HTTP flags and
* credentials.
*
* @return bool True to use the HTTP protocol.
* @task uri
*/
private function shouldUseHTTP() {
if ($this->isHosted()) {
return false;
}
$protocol = $this->getRemoteProtocol();
return ($protocol == 'http' || $protocol == 'https');
}
/**
* Determine if we should connect to the remote using SVN flags and
* credentials.
*
* @return bool True to use the SVN protocol.
* @task uri
*/
private function shouldUseSVNProtocol() {
if ($this->isHosted()) {
return false;
}
$protocol = $this->getRemoteProtocol();
return ($protocol == 'svn');
}
/**
* Determine if a protocol is SSH or SSH-like.
*
* @param string A protocol string, like "http" or "ssh".
* @return bool True if the protocol is SSH-like.
* @task uri
*/
private function isSSHProtocol($protocol) {
return ($protocol == 'ssh' || $protocol == 'svn+ssh');
}
public function delete() {
$this->openTransaction();
$paths = id(new PhabricatorOwnersPath())
->loadAllWhere('repositoryPHID = %s', $this->getPHID());
foreach ($paths as $path) {
$path->delete();
}
$projects = id(new PhabricatorRepositoryArcanistProject())
->loadAllWhere('repositoryID = %d', $this->getID());
foreach ($projects as $project) {
// note each project deletes its PhabricatorRepositorySymbols
$project->delete();
}
$commits = id(new PhabricatorRepositoryCommit())
->loadAllWhere('repositoryID = %d', $this->getID());
foreach ($commits as $commit) {
// note PhabricatorRepositoryAuditRequests and
// PhabricatorRepositoryCommitData are deleted here too.
$commit->delete();
}
$mirrors = id(new PhabricatorRepositoryMirror())
->loadAllWhere('repositoryPHID = %s', $this->getPHID());
foreach ($mirrors as $mirror) {
$mirror->delete();
}
$ref_cursors = id(new PhabricatorRepositoryRefCursor())
->loadAllWhere('repositoryPHID = %s', $this->getPHID());
foreach ($ref_cursors as $cursor) {
$cursor->delete();
}
$conn_w = $this->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE repositoryID = %d',
self::TABLE_FILESYSTEM,
$this->getID());
queryfx(
$conn_w,
'DELETE FROM %T WHERE repositoryID = %d',
self::TABLE_PATHCHANGE,
$this->getID());
queryfx(
$conn_w,
'DELETE FROM %T WHERE repositoryID = %d',
self::TABLE_SUMMARY,
$this->getID());
$result = parent::delete();
$this->saveTransaction();
return $result;
}
public function isGit() {
$vcs = $this->getVersionControlSystem();
return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
}
public function isSVN() {
$vcs = $this->getVersionControlSystem();
return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_SVN);
}
public function isHg() {
$vcs = $this->getVersionControlSystem();
return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL);
}
public function isHosted() {
return (bool)$this->getDetail('hosting-enabled', false);
}
public function setHosted($enabled) {
return $this->setDetail('hosting-enabled', $enabled);
}
public function getServeOverHTTP() {
if ($this->isSVN()) {
return self::SERVE_OFF;
}
$serve = $this->getDetail('serve-over-http', self::SERVE_OFF);
return $this->normalizeServeConfigSetting($serve);
}
public function setServeOverHTTP($mode) {
return $this->setDetail('serve-over-http', $mode);
}
public function getServeOverSSH() {
$serve = $this->getDetail('serve-over-ssh', self::SERVE_OFF);
return $this->normalizeServeConfigSetting($serve);
}
public function setServeOverSSH($mode) {
return $this->setDetail('serve-over-ssh', $mode);
}
public static function getProtocolAvailabilityName($constant) {
switch ($constant) {
case self::SERVE_OFF:
return pht('Off');
case self::SERVE_READONLY:
return pht('Read Only');
case self::SERVE_READWRITE:
return pht('Read/Write');
default:
return pht('Unknown');
}
}
private function normalizeServeConfigSetting($value) {
switch ($value) {
case self::SERVE_OFF:
case self::SERVE_READONLY:
return $value;
case self::SERVE_READWRITE:
if ($this->isHosted()) {
return self::SERVE_READWRITE;
} else {
return self::SERVE_READONLY;
}
default:
return self::SERVE_OFF;
}
}
/**
* Raise more useful errors when there are basic filesystem problems.
*/
private function assertLocalExists() {
if (!$this->usesLocalWorkingCopy()) {
return;
}
$local = $this->getLocalPath();
Filesystem::assertExists($local);
Filesystem::assertIsDirectory($local);
Filesystem::assertReadable($local);
}
/**
* Determine if the working copy is bare or not. In Git, this corresponds
* to `--bare`. In Mercurial, `--noupdate`.
*/
public function isWorkingCopyBare() {
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
return false;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$local = $this->getLocalPath();
if (Filesystem::pathExists($local.'/.git')) {
return false;
} else {
return true;
}
}
}
public function usesLocalWorkingCopy() {
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
return $this->isHosted();
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
return true;
}
}
public function getHookDirectories() {
$directories = array();
if (!$this->isHosted()) {
return $directories;
}
$root = $this->getLocalPath();
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
if ($this->isWorkingCopyBare()) {
$directories[] = $root.'/hooks/pre-receive-phabricator.d/';
} else {
$directories[] = $root.'/.git/hooks/pre-receive-phabricator.d/';
}
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$directories[] = $root.'/hooks/pre-commit-phabricator.d/';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
// NOTE: We don't support custom Mercurial hooks for now because they're
// messy and we can't easily just drop a `hooks.d/` directory next to
// the hooks.
break;
}
return $directories;
}
public function canDestroyWorkingCopy() {
if ($this->isHosted()) {
// Never destroy hosted working copies.
return false;
}
$default_path = PhabricatorEnv::getEnvConfig(
'repository.default-local-path');
return Filesystem::isDescendant($this->getLocalPath(), $default_path);
}
public function canUsePathTree() {
return !$this->isSVN();
}
public function canMirror() {
if ($this->isGit() || $this->isHg()) {
return true;
}
return false;
}
public function canAllowDangerousChanges() {
if (!$this->isHosted()) {
return false;
}
if ($this->isGit() || $this->isHg()) {
return true;
}
return false;
}
public function shouldAllowDangerousChanges() {
return (bool)$this->getDetail('allow-dangerous-changes');
}
public function writeStatusMessage(
$status_type,
$status_code,
array $parameters = array()) {
$table = new PhabricatorRepositoryStatusMessage();
$conn_w = $table->establishConnection('w');
$table_name = $table->getTableName();
if ($status_code === null) {
queryfx(
$conn_w,
'DELETE FROM %T WHERE repositoryID = %d AND statusType = %s',
$table_name,
$this->getID(),
$status_type);
} else {
queryfx(
$conn_w,
'INSERT INTO %T
(repositoryID, statusType, statusCode, parameters, epoch)
VALUES (%d, %s, %s, %s, %d)
ON DUPLICATE KEY UPDATE
statusCode = VALUES(statusCode),
parameters = VALUES(parameters),
epoch = VALUES(epoch)',
$table_name,
$this->getID(),
$status_type,
$status_code,
json_encode($parameters),
time());
}
return $this;
}
public static function getRemoteURIProtocol($raw_uri) {
$uri = new PhutilURI($raw_uri);
if ($uri->getProtocol()) {
return strtolower($uri->getProtocol());
}
$git_uri = new PhutilGitURI($raw_uri);
if (strlen($git_uri->getDomain()) && strlen($git_uri->getPath())) {
return 'ssh';
}
return null;
}
public static function assertValidRemoteURI($uri) {
if (trim($uri) != $uri) {
throw new Exception(
pht(
'The remote URI has leading or trailing whitespace.'));
}
$protocol = self::getRemoteURIProtocol($uri);
// Catch confusion between Git/SCP-style URIs and normal URIs. See T3619
// for discussion. This is usually a user adding "ssh://" to an implicit
// SSH Git URI.
if ($protocol == 'ssh') {
if (preg_match('(^[^:@]+://[^/:]+:[^\d])', $uri)) {
throw new Exception(
pht(
"The remote URI is not formatted correctly. Remote URIs ".
"with an explicit protocol should be in the form ".
"'proto://domain/path', not 'proto://domain:/path'. ".
"The ':/path' syntax is only valid in SCP-style URIs."));
}
}
switch ($protocol) {
case 'ssh':
case 'http':
case 'https':
case 'git':
case 'svn':
case 'svn+ssh':
break;
default:
// NOTE: We're explicitly rejecting 'file://' because it can be
// used to clone from the working copy of another repository on disk
// that you don't normally have permission to access.
throw new Exception(
pht(
"The URI protocol is unrecognized. It should begin ".
"'ssh://', 'http://', 'https://', 'git://', 'svn://', ".
"'svn+ssh://', or be in the form 'git@domain.com:path'."));
}
return true;
}
/**
* Load the pull frequency for this repository, based on the time since the
* last activity.
*
* We pull rarely used repositories less frequently. This finds the most
* recent commit which is older than the current time (which prevents us from
* spinning on repositories with a silly commit post-dated to some time in
* 2037). We adjust the pull frequency based on when the most recent commit
* occurred.
*
* @param int The minimum update interval to use, in seconds.
* @return int Repository update interval, in seconds.
*/
public function loadUpdateInterval($minimum = 15) {
// If a repository is still importing, always pull it as frequently as
// possible. This prevents us from hanging for a long time at 99.9% when
// importing an inactive repository.
if ($this->isImporting()) {
return $minimum;
}
$window_start = (PhabricatorTime::getNow() + $minimum);
$table = id(new PhabricatorRepositoryCommit());
$last_commit = queryfx_one(
$table->establishConnection('r'),
'SELECT epoch FROM %T
WHERE repositoryID = %d AND epoch <= %d
ORDER BY epoch DESC LIMIT 1',
$table->getTableName(),
$this->getID(),
$window_start);
if ($last_commit) {
$time_since_commit = ($window_start - $last_commit['epoch']);
$last_few_days = phutil_units('3 days in seconds');
if ($time_since_commit <= $last_few_days) {
// For repositories with activity in the recent past, we wait one
// extra second for every 10 minutes since the last commit. This
// shorter backoff is intended to handle weekends and other short
// breaks from development.
$smart_wait = ($time_since_commit / 600);
} else {
// For repositories without recent activity, we wait one extra second
// for every 4 minutes since the last commit. This longer backoff
// handles rarely used repositories, up to the maximum.
$smart_wait = ($time_since_commit / 240);
}
// We'll never wait more than 6 hours to pull a repository.
$longest_wait = phutil_units('6 hours in seconds');
$smart_wait = min($smart_wait, $longest_wait);
$smart_wait = max($minimum, $smart_wait);
} else {
$smart_wait = $minimum;
}
return $smart_wait;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
DiffusionPushCapability::CAPABILITY,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
case DiffusionPushCapability::CAPABILITY:
return $this->getPushPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
/* -( PhabricatorMarkupInterface )----------------------------------------- */
public function getMarkupFieldKey($field) {
$hash = PhabricatorHash::digestForIndex($this->getMarkupText($field));
return "repo:{$hash}";
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newMarkupEngine(array());
}
public function getMarkupText($field) {
return $this->getDetail('description');
}
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
require_celerity_resource('phabricator-remarkup-css');
return phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$output);
}
public function shouldUseMarkupCache($field) {
return true;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$this->saveTransaction();
}
}
diff --git a/src/infrastructure/customfield/storage/PhabricatorCustomFieldStringIndexStorage.php b/src/infrastructure/customfield/storage/PhabricatorCustomFieldStringIndexStorage.php
index 51d5449dc8..8d3d75286e 100644
--- a/src/infrastructure/customfield/storage/PhabricatorCustomFieldStringIndexStorage.php
+++ b/src/infrastructure/customfield/storage/PhabricatorCustomFieldStringIndexStorage.php
@@ -1,36 +1,36 @@
<?php
abstract class PhabricatorCustomFieldStringIndexStorage
extends PhabricatorCustomFieldIndexStorage {
public function getConfiguration() {
return array(
self::CONFIG_COLUMN_SCHEMA => array(
'indexKey' => 'bytes12',
- 'indexValue' => 'text',
+ 'indexValue' => 'sort',
),
self::CONFIG_KEY_SCHEMA => array(
'key_join' => array(
'columns' => array('objectPHID', 'indexKey', 'indexValue(64)'),
),
'key_find' => array(
'columns' => array('indexKey', 'indexValue(64)'),
),
),
) + parent::getConfiguration();
}
public function formatForInsert(AphrontDatabaseConnection $conn) {
return qsprintf(
$conn,
'(%s, %s, %s)',
$this->getObjectPHID(),
$this->getIndexKey(),
$this->getIndexValue());
}
public function getIndexValueType() {
return 'string';
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Aug 14, 8:26 AM (1 d, 22 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
202131
Default Alt Text
(109 KB)

Event Timeline