Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php b/src/applications/differential/view/DifferentialDiffTableOfContentsView.php
index cffb1f34a1..90056969f5 100644
--- a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php
+++ b/src/applications/differential/view/DifferentialDiffTableOfContentsView.php
@@ -1,322 +1,325 @@
<?php
final class DifferentialDiffTableOfContentsView extends AphrontView {
private $changesets = array();
private $visibleChangesets = array();
private $references = array();
private $repository;
private $diff;
private $renderURI = '/differential/changeset/';
private $revisionID;
private $whitespace;
private $unitTestData;
public function setChangesets($changesets) {
$this->changesets = $changesets;
return $this;
}
public function setVisibleChangesets($visible_changesets) {
$this->visibleChangesets = $visible_changesets;
return $this;
}
public function setRenderingReferences(array $references) {
$this->references = $references;
return $this;
}
public function setRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function setDiff(DifferentialDiff $diff) {
$this->diff = $diff;
return $this;
}
public function setUnitTestData($unit_test_data) {
$this->unitTestData = $unit_test_data;
return $this;
}
public function setRevisionID($revision_id) {
$this->revisionID = $revision_id;
return $this;
}
public function setWhitespace($whitespace) {
$this->whitespace = $whitespace;
return $this;
}
public function render() {
$this->requireResource('differential-core-view-css');
$this->requireResource('differential-table-of-contents-css');
$rows = array();
$coverage = array();
if ($this->unitTestData) {
$coverage_by_file = array();
foreach ($this->unitTestData as $result) {
$test_coverage = idx($result, 'coverage');
if (!$test_coverage) {
continue;
}
foreach ($test_coverage as $file => $results) {
$coverage_by_file[$file][] = $results;
}
}
foreach ($coverage_by_file as $file => $coverages) {
$coverage[$file] = ArcanistUnitTestResult::mergeCoverage($coverages);
}
}
$changesets = $this->changesets;
$paths = array();
foreach ($changesets as $id => $changeset) {
$type = $changeset->getChangeType();
$ftype = $changeset->getFileType();
$ref = idx($this->references, $id);
$display_file = $changeset->getDisplayFilename();
$meta = null;
if (DifferentialChangeType::isOldLocationChangeType($type)) {
$away = $changeset->getAwayPaths();
if (count($away) > 1) {
$meta = array();
if ($type == DifferentialChangeType::TYPE_MULTICOPY) {
$meta[] = pht('Deleted after being copied to multiple locations:');
} else {
$meta[] = pht('Copied to multiple locations:');
}
foreach ($away as $path) {
$meta[] = $path;
}
$meta = phutil_implode_html(phutil_tag('br'), $meta);
} else {
if ($type == DifferentialChangeType::TYPE_MOVE_AWAY) {
$display_file = $this->renderRename(
$display_file,
reset($away),
"\xE2\x86\x92");
} else {
$meta = pht('Copied to %s', reset($away));
}
}
} else if ($type == DifferentialChangeType::TYPE_MOVE_HERE) {
$old_file = $changeset->getOldFile();
$display_file = $this->renderRename(
$display_file,
$old_file,
"\xE2\x86\x90");
} else if ($type == DifferentialChangeType::TYPE_COPY_HERE) {
$meta = pht('Copied from %s', $changeset->getOldFile());
}
$link = $this->renderChangesetLink($changeset, $ref, $display_file);
$line_count = $changeset->getAffectedLineCount();
if ($line_count == 0) {
$lines = '';
} else {
$lines = ' '.pht('(%d line(s))', $line_count);
}
$char = DifferentialChangeType::getSummaryCharacterForChangeType($type);
$chartitle = DifferentialChangeType::getFullNameForChangeType($type);
$desc = DifferentialChangeType::getShortNameForFileType($ftype);
if ($desc) {
$desc = '('.$desc.')';
}
$pchar =
($changeset->getOldProperties() === $changeset->getNewProperties())
? ''
- : phutil_tag('span', array('title' => pht('Properties Changed')), 'M');
+ : phutil_tag(
+ 'span',
+ array('title' => pht('Properties Changed')),
+ 'M');
$fname = $changeset->getFilename();
$cov = $this->renderCoverage($coverage, $fname);
if ($cov === null) {
$mcov = $cov = phutil_tag('em', array(), '-');
} else {
$mcov = phutil_tag(
'div',
array(
'id' => 'differential-mcoverage-'.md5($fname),
'class' => 'differential-mcoverage-loading',
),
(isset($this->visibleChangesets[$id]) ?
pht('Loading...') : pht('?')));
}
if ($meta) {
$meta = phutil_tag(
'div',
array(
'class' => 'differential-toc-meta'
),
$meta);
}
if ($this->diff && $this->repository) {
$paths[] =
$changeset->getAbsoluteRepositoryPath($this->repository, $this->diff);
}
$rows[] = array(
$char,
$pchar,
$desc,
array($link, $lines, $meta),
$cov,
$mcov
);
}
$editor_link = null;
if ($paths && $this->user) {
$editor_link = $this->user->loadEditorLink(
$paths,
1, // line number
$this->repository->getCallsign());
if ($editor_link) {
$editor_link =
phutil_tag(
'a',
array(
'href' => $editor_link,
'class' => 'button differential-toc-edit-all',
),
pht('Open All in Editor'));
}
}
$reveal_link = javelin_tag(
'a',
array(
'sigil' => 'differential-reveal-all',
'mustcapture' => true,
'class' => 'button differential-toc-reveal-all',
),
pht('Show All Context'));
$buttons = phutil_tag(
'div',
array(
'class' => 'differential-toc-buttons grouped'
),
array(
$editor_link,
$reveal_link
));
$table = id(new AphrontTableView($rows));
$table->setHeaders(
array(
'',
'',
'',
pht('Path'),
pht('Coverage (All)'),
pht('Coverage (Touched)'),
));
$table->setColumnClasses(
array(
'differential-toc-char center',
'differential-toc-prop center',
'differential-toc-ftype center',
'differential-toc-file wide',
'differential-toc-cov',
'differential-toc-cov',
));
$table->setDeviceVisibility(
array(
true,
true,
true,
true,
false,
false,
));
$anchor = id(new PhabricatorAnchorView())
->setAnchorName('toc')
->setNavigationMarker(true);
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Table of Contents'))
->appendChild($anchor)
->appendChild($table)
->appendChild($buttons);
}
private function renderRename($display_file, $other_file, $arrow) {
$old = explode('/', $display_file);
$new = explode('/', $other_file);
$start = count($old);
foreach ($old as $index => $part) {
if (!isset($new[$index]) || $part != $new[$index]) {
$start = $index;
break;
}
}
$end = count($old);
foreach (array_reverse($old) as $from_end => $part) {
$index = count($new) - $from_end - 1;
if (!isset($new[$index]) || $part != $new[$index]) {
$end = $from_end;
break;
}
}
$rename =
'{'.
implode('/', array_slice($old, $start, count($old) - $end - $start)).
' '.$arrow.' '.
implode('/', array_slice($new, $start, count($new) - $end - $start)).
'}';
array_splice($new, $start, count($new) - $end - $start, $rename);
return implode('/', $new);
}
private function renderCoverage(array $coverage, $file) {
$info = idx($coverage, $file);
if (!$info) {
return null;
}
$not_covered = substr_count($info, 'U');
$covered = substr_count($info, 'C');
if (!$not_covered && !$covered) {
return null;
}
return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered)));
}
private function renderChangesetLink(
DifferentialChangeset $changeset,
$ref,
$display_file) {
return javelin_tag(
'a',
array(
'href' => '#'.$changeset->getAnchorName(),
'sigil' => 'differential-load',
'meta' => array(
'id' => 'diff-'.$changeset->getAnchorName(),
),
),
$display_file);
}
}
diff --git a/src/docs/contributor/database.diviner b/src/docs/contributor/database.diviner
index 99ee5aeb68..48e2c20b79 100644
--- a/src/docs/contributor/database.diviner
+++ b/src/docs/contributor/database.diviner
@@ -1,175 +1,176 @@
@title Database Schema
@group developer
This document describes key components of the database schema and should answer
questions like how to store new types of data.
= Database System =
Phabricator uses MySQL with InnoDB engine. The only exception is the
`search_documentfield` table which uses MyISAM because MySQL doesn't support
fulltext search in InnoDB.
Let us know if you need to use other database system: @{article:Give Feedback!
Get Support!}.
= PHP Drivers =
Phabricator supports [[ http://www.php.net/book.mysql | MySQL ]] and
[[ http://www.php.net/book.mysqli | MySQLi ]] PHP extensions. Most installations
use MySQL but MySQLi should work equally well.
= Databases =
Each Phabricator application has its own database. The names are prefixed by
`phabricator_`. This design has two advantages:
* Each database is easier to comprehend and to maintain.
* We don't do cross-database joins so each database can live on its own machine
which is useful for load-balancing.
= Connections =
Phabricator specifies if it will use any opened connection just for reading or
also for writing. This allows opening write connections to master and read
connections to slave in master/slave replication. It is useful for
load-balancing.
= Tables =
Each table name is prefixed by its application. For example, Differential
revisions are stored in database `phabricator_differential` and table
`differential_revision`. This duplicity allows easy recognition of the table in
DarkConsole (see @{article:Using DarkConsole}) and other places.
The exception is tables which share the same schema over different databases
such as `edge`.
We use lower-case table names with words separated by underscores. The reason is
that MySQL can be configured (with `lower_case_table_names`) to lower-case the
table names anyway.
= Column Names =
Phabricator uses camelCase names for columns. The main advantage is that they
directly map to properties in PHP classes.
Don't use MySQL reserved words (such as `order`) for column names.
= Data Types =
Phabricator uses `int unsigned` columns for storing dates instead of `date` or
`datetime`. We don't need to care about time-zones in both MySQL and PHP because
of it. The other reason is that PHP internally uses numbers for storing dates.
Phabricator uses UTF-8 encoding for storing all text data. We use
`utf8_general_ci` collation for free-text and `utf8_bin` for identifiers.
We don't use the `enum` data type because each change to the list of possible
values requires altering the table (which is slow with big tables). We use
numbers (or short strings in some cases) mapped to PHP constants instead.
= JSON =
Some data don't require structured access - you don't need to filter or order by
them. We store these data as text fields in JSON format. This approach has
several advantages:
* If we decide to add another unstructured field then we don't need to alter the
table (which is slow for big tables in MySQL).
* Table structure is not cluttered by fields which could be unused most of the
time.
An example of such usage can be found in column
`differential_diffproperty.data`.
= Primary Keys =
Most tables have auto-increment column named `id`. However creating such column
is not required for tables which are not usually directly referenced (such as
tables expressing M:N relations). Example of such table is
`differential_relationship`.
= Indexes =
Create all indexes necessary for fast query execution in most cases. Don't
create indexes which are not used. You can analyze queries @{article:Using
DarkConsole}.
Older MySQL versions are not able to use indexes for tuple search:
`(a, b) IN ((%s, %d), (%s, %d))`. Use `AND` and `OR` instead:
`((a = %s AND b = %d) OR (a = %s AND b = %d))`.
= Foreign Keys =
We don't use InnoDB's foreign keys because our application is so great that
no inconsistencies can arise. It will just slow us down.
= PHIDs =
Each globally referencable object in Phabricator has its associated PHID
(Phabricator ID) which serves as a global identifier. We use PHIDs for
referencing data in different databases.
We use both autoincrementing IDs and global PHIDs because each is useful in
different contexts. Autoincrementing IDs are chronologically ordered and allow
us to construct short, human-readable object names (like D2258) and URIs. Global
PHIDs allow us to represent relationships between different types of objects in
a homogeneous way.
For example, the concept of "subscribers" is more powerfully done with PHIDs
because we could theoretically have users, projects, teams, and more all as
"subscribers" of other objects. Using an ID column we would need to add a
"type" column to avoid ID collision; using PHIDs does not require this
additional column.
= Transactions =
Transactional code should be written using transactions. Example of such code is
inserting multiple records where one doesn't make sense without the other or
selecting data later used for update. See chapter in @{class:LiskDAO}.
= Advanced Features =
We don't use MySQL advanced features such as triggers, stored procedures or
events because we like expressing the application logic in PHP more than in SQL.
Some of these features (especially triggers) can also cause big confusion.
Avoiding these advanced features is also good for supporting other database
systems (which we don't support anyway).
= Schema Denormalization =
Phabricator uses schema denormalization for performance reasons sparingly. Try
to avoid it if possible.
= Changing the Schema =
There are three simple steps to update the schema:
# Create a `.sql` file in `resources/sql/patches/`. This file should:
- Contain the appropriate MySQL commands to update the schema.
- Be named as `YYYYMMDD.patchname.ext`. For example, `20130217.example.sql`.
- Use `${NAMESPACE}` rather than `phabricator` for database names.
- Use `COLLATE utf8_bin` for any columns that are to be used as identifiers,
such as PHID columns. Otherwise, use `COLLATE utf8_general_ci`.
- Name all indexes so it is possible to delete them later.
# Edit `src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php` and
- add your patch to @{method@phabricator:PhabricatorBuiltinPatchList::getPatches}.
+ add your patch to
+ @{method@phabricator:PhabricatorBuiltinPatchList::getPatches}.
# Run `bin/storage upgrade`.
It is also possible to create more complex patches in PHP for data migration
(due to schema changes or otherwise.) However, the schema changes themselves
should be done in separate `.sql` files. Order can be guaranteed by editing
`src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php`
appropriately.
See the
[[https://secure.phabricator.com/rPb39175342dc5bee0c2246b05fa277e76a7e96ed3
| commit adding policy storage for Paste ]] for a reasonable example of the code
changes.
= See Also =
* @{class:LiskDAO}
* @{class:PhabricatorPHID}
diff --git a/src/infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php b/src/infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php
index e5f4b872d4..25df41bedd 100644
--- a/src/infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php
+++ b/src/infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php
@@ -1,91 +1,92 @@
<?php
final class PhabricatorBotFlowdockProtocolAdapter
extends PhabricatorBotBaseStreamingProtocolAdapter {
public function getServiceType() {
return 'Flowdock';
}
protected function buildStreamingUrl($channel) {
$organization = $this->getConfig('flowdock.organization');
if (empty($organization)) {
$this->getConfig('organization');
}
if (empty($organization)) {
throw new Exception(
'"flowdock.organization" configuration variable not set');
}
$ssl = $this->getConfig('ssl');
$url = ($ssl) ? 'https://' : 'http://';
- $url .= "{$this->authtoken}@stream.flowdock.com/flows/{$organization}/{$channel}";
+ $url .= "{$this->authtoken}@stream.flowdock.com";
+ $url .= "/flows/{$organization}/{$channel}";
return $url;
}
protected function processMessage($m_obj) {
$command = null;
switch ($m_obj['event']) {
case 'message':
$command = 'MESSAGE';
break;
default:
// For now, ignore anything which we don't otherwise know about.
break;
}
if ($command === null) {
return false;
}
// TODO: These should be usernames, not user IDs.
$sender = id(new PhabricatorBotUser())
->setName($m_obj['user']);
$target = id(new PhabricatorBotChannel())
->setName($m_obj['flow']);
return id(new PhabricatorBotMessage())
->setCommand($command)
->setSender($sender)
->setTarget($target)
->setBody($m_obj['content']);
}
public function writeMessage(PhabricatorBotMessage $message) {
switch ($message->getCommand()) {
case 'MESSAGE':
$this->speak(
$message->getBody(),
$message->getTarget());
break;
}
}
private function speak(
$body,
PhabricatorBotTarget $flow) {
// The $flow->getName() returns the flow's UUID,
// as such, the Flowdock API does not require the organization
// to be specified in the URI
$this->performPost(
'/messages',
array(
'flow' => $flow->getName(),
'event' => 'message',
'content' => $body));
}
public function __destruct() {
if ($this->readHandles) {
foreach ($this->readHandles as $read_handle) {
curl_multi_remove_handle($this->multiHandle, $read_handle);
curl_close($read_handle);
}
}
curl_multi_close($this->multiHandle);
}
}
diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php
index 9bdc0d0253..a5766fde93 100644
--- a/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php
+++ b/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php
@@ -1,194 +1,194 @@
<?php
/**
* Looks for Dxxxx, Txxxx and links to them.
*/
final class PhabricatorBotObjectNameHandler extends PhabricatorBotHandler {
/**
* Map of PHIDs to the last mention of them (as an epoch timestamp); prevents
* us from spamming chat when a single object is discussed.
*/
private $recentlyMentioned = array();
public function receiveMessage(PhabricatorBotMessage $original_message) {
switch ($original_message->getCommand()) {
case 'MESSAGE':
$message = $original_message->getBody();
$matches = null;
$paste_ids = array();
$commit_names = array();
$vote_ids = array();
$file_ids = array();
$object_names = array();
$output = array();
$pattern =
'@'.
'(?<!/)(?:^|\b)'.
'(R2D2)'.
'(?:\b|$)'.
'@';
if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
switch ($match[1]) {
case 'R2D2':
$output[$match[1]] = pht('beep hoop bop');
break;
}
}
}
$pattern =
'@'.
'(?<!/)(?:^|\b)'. // Negative lookbehind prevent matching "/D123".
'([A-Z])(\d+)'.
'(?:\b|$)'.
'@';
if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
switch ($match[1]) {
case 'P':
$paste_ids[] = $match[2];
break;
case 'V':
$vote_ids[] = $match[2];
break;
case 'F':
$file_ids[] = $match[2];
break;
default:
$name = $match[1].$match[2];
switch ($name) {
case 'T1000':
$output[$name] = pht(
'T1000: A mimetic poly-alloy assassin controlled by '.
'Skynet');
break;
default:
$object_names[] = $name;
break;
}
break;
}
}
}
$pattern =
'@'.
'(?<!/)(?:^|\b)'.
'(r[A-Z]+)([0-9a-z]{0,40})'.
'(?:\b|$)'.
'@';
if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
if ($match[2]) {
$commit_names[] = $match[1].$match[2];
} else {
$object_names[] = $match[1];
}
}
}
if ($object_names) {
$objects = $this->getConduit()->callMethodSynchronous(
'phid.lookup',
array(
'names' => $object_names,
));
foreach ($objects as $object) {
$output[$object['phid']] = $object['fullName'].' - '.$object['uri'];
}
}
if ($vote_ids) {
foreach ($vote_ids as $vote_id) {
$vote = $this->getConduit()->callMethodSynchronous(
'slowvote.info',
array(
'poll_id' => $vote_id,
));
$output[$vote['phid']] = 'V'.$vote['id'].': '.$vote['question'].
' Come Vote '.$vote['uri'];
}
}
if ($file_ids) {
foreach ($file_ids as $file_id) {
$file = $this->getConduit()->callMethodSynchronous(
'file.info',
array(
'id' => $file_id,
));
- $output[$file['phid']] = $file['objectName'].': '.$file['uri'].' - '.
- $file['name'];
+ $output[$file['phid']] = $file['objectName'].': '.
+ $file['uri'].' - '.$file['name'];
}
}
if ($paste_ids) {
foreach ($paste_ids as $paste_id) {
$paste = $this->getConduit()->callMethodSynchronous(
'paste.info',
array(
'paste_id' => $paste_id,
));
// Eventually I'd like to show the username of the paster as well,
// however that will need something like a user.username_from_phid
// since we (ideally) want to keep the bot to Conduit calls...and
// not call to Phabricator-specific stuff (like actually loading
// the User object and fetching his/her username.)
$output[$paste['phid']] = 'P'.$paste['id'].': '.$paste['uri'].' - '.
$paste['title'];
if ($paste['language']) {
$output[$paste['phid']] .= ' ('.$paste['language'].')';
}
}
}
if ($commit_names) {
$commits = $this->getConduit()->callMethodSynchronous(
'diffusion.getcommits',
array(
'commits' => $commit_names,
));
foreach ($commits as $commit) {
if (isset($commit['error'])) {
continue;
}
$output[$commit['commitPHID']] = $commit['uri'];
}
}
foreach ($output as $phid => $description) {
// Don't mention the same object more than once every 10 minutes
// in public channels, so we avoid spamming the chat over and over
// again for discsussions of a specific revision, for example.
$target_name = $original_message->getTarget()->getName();
if (empty($this->recentlyMentioned[$target_name])) {
$this->recentlyMentioned[$target_name] = array();
}
$quiet_until = idx(
$this->recentlyMentioned[$target_name],
$phid,
0) + (60 * 10);
if (time() < $quiet_until) {
// Remain quiet on this channel.
continue;
}
$this->recentlyMentioned[$target_name][$phid] = time();
$this->replyTo($original_message, $description);
}
break;
}
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 3, 1:54 PM (4 h, 14 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
165847
Default Alt Text
(25 KB)

Event Timeline