diff --git a/src/docs/contributor/adding_new_css_and_js.diviner b/src/docs/contributor/adding_new_css_and_js.diviner
index 1f5fd94ac6..00a3808fba 100644
--- a/src/docs/contributor/adding_new_css_and_js.diviner
+++ b/src/docs/contributor/adding_new_css_and_js.diviner
@@ -1,97 +1,96 @@
@title Adding New CSS and JS
@group developer
Explains how to add new CSS and JS files to Phabricator.
= Overview =
Phabricator uses a system called **Celerity** to manage static resources. If you
are a current or former Facebook employee, Celerity is based on the Haste system
used at Facebook and generally behaves similarly.
This document is intended for Phabricator developers and contributors. This
process will not work correctly for third-party code, plugins, or extensions.
= Adding a New File =
To add a new CSS or JS file, create it in an appropriate location in
`webroot/rsrc/css/` or `webroot/rsrc/js/` inside your `phabricator/`
directory.
-Each file must ##@provides## itself as a component, declared in a header
-comment:
+Each file must `@provides` itself as a component, declared in a header comment:
LANG=css
/**
* @provides duck-styles-css
*/
.duck-header {
font-size: 9001px;
}
Note that this comment must be a Javadoc-style comment, not just any comment.
If your component depends on other components (which is common in JS but
rare and inadvisable in CSS), declare then with `@requires`:
LANG=js
/**
* @requires javelin-stratcom
* @provides duck
*/
/**
* Put class documentation here, NOT in the header block.
*/
JX.install('Duck', {
...
});
Then rebuild the Celerity map (see the next section).
= Changing an Existing File =
When you add, move or remove a file, or change the contents of existing JS or
CSS file, you should rebuild the Celerity map:
phabricator/ $ ./bin/celerity map
If you've only changed file content things will generally work even if you
don't, but they might start not working as well in the future if you skip this
step.
The generated file `resources/celerity/map.php` causes merge conflicts
quite often. They can be resolved by running the Celerity mapper. You can
automate this process by running:
phabricator/ $ ./scripts/celerity/install_merge.sh
This will install Git merge driver which will run when a conflict in this file
occurs.
= Including a File =
To include a CSS or JS file in a page, use
@{function:require_celerity_resource}:
require_celerity_resource('duck-style-css');
require_celerity_resource('duck');
If your map is up to date, the resource should now be included correctly when
the page is rendered.
You should place this call as close to the code which actually uses the resource
as possible, i.e. **not** at the top of your Controller. The idea is that you
should @{function:require_celerity_resource} a resource only if you are actually
using it on a specific rendering of the page, not just because some views of the
page might require it.
= Next Steps =
Continue by:
- reading about Javascript-specific guidelines in @{article:Javascript Coding
Standards}; or
- reading about CSS-specific guidelines and features in @{article:CSS Coding
Standards}.
diff --git a/src/docs/contributor/darkconsole.diviner b/src/docs/contributor/darkconsole.diviner
index cf512710c6..cddef89422 100644
--- a/src/docs/contributor/darkconsole.diviner
+++ b/src/docs/contributor/darkconsole.diviner
@@ -1,60 +1,60 @@
@title Using DarkConsole
@group developer
Enabling and using the built-in debugging console.
= Overview =
DarkConsole is a debugging console built into Phabricator which exposes
configuration, performance and error information. It can help you detect,
understand and resolve bugs and performance problems in Phabricator
applications.
DarkConsole was originally implemented as part of the Facebook Lite site; its
name is a bit of play on that (and a reference to the dark color palette its
design uses).
= Warning =
Because DarkConsole exposes some configuration and debugging information, it is
disabled by default (and **you should not enable it in production**). It has
some simple safeguards to prevent leaking credential information, but enabling
it in production may compromise the integrity of an install.
= Enabling DarkConsole =
You enable DarkConsole in your configuration, by setting `darkconsole.enabled`
to `true`, and then turning it on in `Settings` -> `Developer Settings`. Once
DarkConsole is enabled, you can show or hide it by pressing ##`## on your
keyboard.
Since the setting is not available to logged-out users, you can also set
`darkconsole.always-on` if you need to access DarkConsole on logged-out pages.
DarkConsole has a number of tabs, each of which is powered by a "plugin". You
can use them to access different debugging and performance features.
= Plugin: Error Log =
The "Error Log" plugin shows errors that occurred while generating the page,
-similar to the httpd ##error.log##. You can send information to the error log
+similar to the httpd `error.log`. You can send information to the error log
explicitly with the @{function@libphutil:phlog} function.
If errors occurred, a red dot will appear on the plugin tab.
= Plugin: Request =
The "Request" plugin shows information about the HTTP request the server
received, and the server itself.
= Plugin: Services =
The "Services" plugin lists calls a page made to external services, like
MySQL and the command line.
= Plugin: XHProf =
The "XHProf" plugin gives you access to the XHProf profiler. To use it, you need
to install the corresponding PHP plugin -- see instructions in the
@{article:Installation Guide}. Once it is installed, you can use XHProf to
profile the runtime performance of a page.
diff --git a/src/docs/contributor/javascript_coding_standards.diviner b/src/docs/contributor/javascript_coding_standards.diviner
index 65e15e6b55..3b47a566a6 100644
--- a/src/docs/contributor/javascript_coding_standards.diviner
+++ b/src/docs/contributor/javascript_coding_standards.diviner
@@ -1,139 +1,139 @@
@title Javascript Coding Standards
@group standards
This document describes Javascript coding standards for Phabricator and Javelin.
= Overview =
This document outlines technical and style guidelines which are followed in
Phabricator and Javelin. Contributors should also follow these guidelines. Many
of these guidelines are automatically enforced by lint.
These guidelines are essentially identical to the Facebook guidelines, since I
basically copy-pasted them. If you are already familiar with the Facebook
guidelines, you can probably get away with skimming this document.
= Spaces, Linebreaks and Indentation =
- Use two spaces for indentation. Don't use literal tab characters.
- Use Unix linebreaks ("\n"), not MSDOS ("\r\n") or OS9 ("\r").
- - Put a space after control keywords like ##if## and ##for##.
+ - Put a space after control keywords like `if` and `for`.
- Put a space after commas in argument lists.
- - Put space around operators like ##=##, ##<##, etc.
+ - Put space around operators like `=`, `<`, etc.
- Don't put spaces after function names.
- Parentheses should hug their contents.
- Generally, prefer to wrap code at 80 columns.
= Case and Capitalization =
The Javascript language unambiguously dictates casing/naming rules; follow those
rules.
- - Name variables using ##lowercase_with_underscores##.
- - Name classes using ##UpperCamelCase##.
- - Name methods and properties using ##lowerCamelCase##.
- - Name global functions using ##lowerCamelCase##. Avoid defining global
+ - Name variables using `lowercase_with_underscores`.
+ - Name classes using `UpperCamelCase`.
+ - Name methods and properties using `lowerCamelCase`.
+ - Name global functions using `lowerCamelCase`. Avoid defining global
functions.
- - Name constants using ##UPPERCASE##.
- - Write ##true##, ##false##, and ##null## in lowercase.
+ - Name constants using `UPPERCASE`.
+ - Write `true`, `false`, and `null` in lowercase.
- "Internal" methods and properties should be prefixed with an underscore.
For more information about what "internal" means, see
**Leading Underscores**, below.
= Comments =
- - Strongly prefer ##//## comments for making comments inside the bodies of
+ - Strongly prefer `//` comments for making comments inside the bodies of
functions and methods (this lets someone easily comment out a block of code
while debugging later).
= Javascript Language =
- - Use ##[]## and ##{}##, not ##new Array## and ##new Object##.
+ - Use `[]` and `{}`, not `new Array` and `new Object`.
- When creating an object literal, do not quote keys unless required.
= Examples =
**if/else:**
lang=js
if (x > 3) {
// ...
} else if (x === null) {
// ...
} else {
// ...
}
You should always put braces around the body of an if clause, even if it is only
-one line. Note that operators like ##>## and ##===## are also surrounded by
+one line. Note that operators like `>` and `===` are also surrounded by
spaces.
**for (iteration):**
lang=js
for (var ii = 0; ii < 10; ii++) {
// ...
}
Prefer ii, jj, kk, etc., as iterators, since they're easier to pick out
visually and react better to "Find Next..." in editors.
**for (enumeration):**
lang=js
for (var k in obj) {
// ...
}
Make sure you use enumeration only on Objects, not on Arrays. For more details,
see @{article:Javascript Object and Array}.
**switch:**
lang=js
switch (x) {
case 1:
// ...
break;
case 2:
if (flag) {
break;
}
break;
default:
// ...
break;
}
`break` statements should be indented to block level. If you don't push them
-in, you end up with an inconsistent rule for conditional ##break## statements,
-as in the ##2## case.
+in, you end up with an inconsistent rule for conditional `break` statements,
+as in the `2` case.
-If you insist on having a "fall through" case that does not end with ##break##,
+If you insist on having a "fall through" case that does not end with `break`,
make it clear in a comment that you wrote this intentionally. For instance:
lang=js
switch (x) {
case 1:
// ...
// Fall through...
case 2:
//...
break;
}
= Leading Underscores =
By convention, methods names which start with a leading underscore are
considered "internal", which (roughly) means "private". The critical difference
is that this is treated as a signal to Javascript processing scripts that a
symbol is safe to rename since it is not referenced outside the current file.
The upshot here is:
- name internal methods which shouldn't be called outside of a file's scope
with a leading underscore; and
- **never** call an internal method from another file.
If you treat them as though they were "private", you won't run into problems.
diff --git a/src/docs/contributor/n_plus_one.diviner b/src/docs/contributor/n_plus_one.diviner
index 1f3b78424e..6d259671a1 100644
--- a/src/docs/contributor/n_plus_one.diviner
+++ b/src/docs/contributor/n_plus_one.diviner
@@ -1,77 +1,77 @@
@title Performance: N+1 Query Problem
@group developer
How to avoid a common performance pitfall.
= Overview =
The N+1 query problem is a common performance antipattern. It looks like this:
COUNTEREXAMPLE
$cats = load_cats();
foreach ($cats as $cat) {
$cats_hats = load_hats_for_cat($cat);
// ...
}
-Assuming ##load_cats()## has an implementation that boils down to:
+Assuming `load_cats()` has an implementation that boils down to:
SELECT * FROM cat WHERE ...
-..and ##load_hats_for_cat($cat)## has an implementation something like this:
+..and `load_hats_for_cat($cat)` has an implementation something like this:
SELECT * FROM hat WHERE catID = ...
..you will issue "N+1" queries when the code executes, where N is the number of
cats:
SELECT * FROM cat WHERE ...
SELECT * FROM hat WHERE catID = 1
SELECT * FROM hat WHERE catID = 2
SELECT * FROM hat WHERE catID = 3
SELECT * FROM hat WHERE catID = 4
SELECT * FROM hat WHERE catID = 5
...
The problem with this is that each query has quite a bit of overhead. **It is
//much faster// to issue 1 query which returns 100 results than to issue 100
queries which each return 1 result.** This is particularly true if your database
is on a different machine which is, say, 1-2ms away on the network. In this
case, issuing 100 queries serially has a minimum cost of 100-200ms, even if they
can be satisfied instantly by MySQL. This is far higher than the entire
server-side generation cost for most Phabricator pages should be.
= Batching Queries =
Fix the N+1 query problem by batching queries. Load all your data before
iterating through it (this is oversimplified and omits error checking):
$cats = load_cats();
$hats = load_all_hats_for_these_cats($cats);
foreach ($cats as $cat) {
$cats_hats = $hats[$cat->getID()];
}
That is, issue these queries:
SELECT * FROM cat WHERE ...
SELECT * FROM hat WHERE catID IN (1, 2, 3, 4, 5, ...)
In this case, the total number of queries issued is always 2, no matter how many
objects there are. You've removed the "N" part from the page's query plan, and
are no longer paying the overhead of issuing hundreds of extra queries. This
will perform much better (although, as with all performance changes, you should
verify this claim by measuring it).
See also @{method:LiskDAO::loadRelatives} method which provides an abstraction
to prevent this problem.
= Detecting the Problem =
Beyond reasoning about it while figuring out how to load the data you need, the
easiest way to detect this issue is to check the "Services" tab in DarkConsole
(see @{article:Using DarkConsole}), which lists all the service calls made on a
page. If you see a bunch of similar queries, this often indicates an N+1 query
issue (or a similar kind of query batching problem). Restructuring code so you
can run a single query to fetch all the data at once will always improve the
performance of the page.
diff --git a/src/docs/contributor/phabricator_code_layout.diviner b/src/docs/contributor/phabricator_code_layout.diviner
index 0b021d667b..d563d0e2e0 100644
--- a/src/docs/contributor/phabricator_code_layout.diviner
+++ b/src/docs/contributor/phabricator_code_layout.diviner
@@ -1,112 +1,112 @@
@title Phabricator Code Layout
@group developer
Guide to Phabricator code layout, including how URI mapping works through
application class and subdirectory organization best practices.
= URI Mapping =
When a user visits a Phabricator URI, the Phabricator infrastructure parses
that URI with a regular expression to determine what controller class to load.
The Phabricator infrastructure knows where a given controller class lives on
disk from a cache file the Arcanist phutil mapper generates. This mapping
should be updated whenever new classes or files are added:
arc liberate /path/to/phabricator/src
Finally, a given controller class will map to an application which will have
most of its code in standardized subdirectories and classes.
= Best Practice Class and Subdirectory Organization =
-Suppose you were working on the application ##Derp##.
+Suppose you were working on the application `Derp`.
phabricator/src/applications/derp/
-If ##Derp## were as simple as possible, it would have one subdirectory:
+If `Derp` were as simple as possible, it would have one subdirectory:
phabricator/src/applications/derp/controller/
-containing the file ##DerpController.php## with the class
+containing the file `DerpController.php` with the class
- - ##DerpController##: minimally implements a ##processRequest()## method
+ - `DerpController`: minimally implements a `processRequest()` method
which returns some @{class:AphrontResponse} object. The class would probably
extend @{class:PhabricatorController}.
-If ##Derp## were (relatively) complex, one could reasonably expect to see
+If `Derp` were (relatively) complex, one could reasonably expect to see
the following directory layout:
phabricator/src/applications/derp/conduit/
phabricator/src/applications/derp/constants/
phabricator/src/applications/derp/controller/
phabricator/src/applications/derp/editor/
phabricator/src/applications/derp/exception/
phabricator/src/applications/derp/query/
phabricator/src/applications/derp/replyhandler/
phabricator/src/applications/derp/storage/
phabricator/src/applications/derp/view/
(The following two folders are also likely to be included for JavaScript and
CSS respectively. However, static resources are largely outside the scope of
this document. See @{article:Adding New CSS and JS}.)
phabricator/webroot/rsrc/js/application/derp/
phabricator/webroot/rsrc/css/application/derp/
-These directories under ##phabricator/src/applications/derp/## represent
+These directories under `phabricator/src/applications/derp/` represent
the basic set of class types from which most Phabrictor applications are
-assembled. Each would contain a class file. For ##Derp##, these classes could be
+assembled. Each would contain a class file. For `Derp`, these classes could be
something like:
- - **DerpConstants**: constants used in the ##Derp## application.
+ - **DerpConstants**: constants used in the `Derp` application.
- **DerpController**: business logic providing functionality for a given
URI. Typically, controllers load data via Storage or Query classes, then
present the data to the user via one or more View classes.
- **DerpEditor**: business logic for workflows that change one or more
Storage objects. Editor classes are only necessary for particularly
complicated edits and should be used pragmatically versus Storage objects.
- - **DerpException**: exceptions used in the ##Derp## application.
- - **DerpQuery**: query one or more storage objects for pertinent ##Derp##
+ - **DerpException**: exceptions used in the `Derp` application.
+ - **DerpQuery**: query one or more storage objects for pertinent `Derp`
application data. @{class:PhabricatorOffsetPagedQuery} is particularly
handy for pagination and works well with @{class:AphrontPagerView}.
- **DerpReplyHandler**: business logic from any configured email interactions
- users can have with the ##Derp## application.
- - **DerpStorage**: storage objects for the ##Derp## application. Typically
+ users can have with the `Derp` application.
+ - **DerpStorage**: storage objects for the `Derp` application. Typically
there is a base class which extends @{class:PhabricatorLiskDAO} to configure
application-wide storage settings like the application (thus database) name.
Reading more about the @{class:LiskDAO} is highly recommended.
- - **DerpView**: view objects for the ##Derp## application. Typically these
+ - **DerpView**: view objects for the `Derp` application. Typically these
extend @{class:AphrontView}.
- - **DerpConduitAPIMethod**: provides any and all ##Derp## application
+ - **DerpConduitAPIMethod**: provides any and all `Derp` application
functionality that is accessible over Conduit.
-However, it is likely that ##Derp## is even more complex, and rather than
+However, it is likely that `Derp` is even more complex, and rather than
containing one class, each directory has several classes. A typical example
happens around the CRUD of an object:
- **DerpBaseController**: typically extends @{class:PhabricatorController},
- implements ##buildStandardPageResponse## with the ##Derp## application name
- and other ##Derp##-specific meta-data, and contains any controller-specific
- functionality used throughout the ##Derp## application.
- - **DerpDeleteController**: typically extends ##DerpBaseController## and
- presents a confirmation dialogue to the user about deleting a ##Derp##.
- - **DerpEditController**: typically extends ##DerpBaseController## and
- presents a form to create and edit ##Derps##. Most likely uses
- @{class:AphrontFormView} and various ##AphrontFormXControl## classes such as
+ implements `buildStandardPageResponse` with the `Derp` application name
+ and other `Derp`-specific meta-data, and contains any controller-specific
+ functionality used throughout the `Derp` application.
+ - **DerpDeleteController**: typically extends `DerpBaseController` and
+ presents a confirmation dialogue to the user about deleting a `Derp`.
+ - **DerpEditController**: typically extends `DerpBaseController` and
+ presents a form to create and edit `Derps`. Most likely uses
+ @{class:AphrontFormView} and various `AphrontFormXControl` classes such as
@{class:AphrontFormTextControl} to create the form.
- - **DerpListController**: typically extends ##DerpBaseController## and displays
- a set of one or more ##Derps##. Might use @{class:AphrontTableView} to create
- a table of ##Derps##.
- - **DerpViewController**: typically extends ##DerpBaseController## and displays
- a single ##Derp##.
+ - **DerpListController**: typically extends `DerpBaseController` and displays
+ a set of one or more `Derps`. Might use @{class:AphrontTableView} to create
+ a table of `Derps`.
+ - **DerpViewController**: typically extends `DerpBaseController` and displays
+ a single `Derp`.
-Some especially awesome directories might have a ##__tests__## subdirectory
+Some especially awesome directories might have a `__tests__` subdirectory
containing all pertinent unit test code for the class.
= Next Steps =
- Learn about @{article:Adding New CSS and JS}; or
- learn about the @{class:LiskDAO}; or
- learn about @{article:Writing Unit Tests}; or
- learn how to contribute (see @{article:Contributor Introduction}).
diff --git a/src/docs/contributor/php_coding_standards.diviner b/src/docs/contributor/php_coding_standards.diviner
index d79d340326..03f0bc158b 100644
--- a/src/docs/contributor/php_coding_standards.diviner
+++ b/src/docs/contributor/php_coding_standards.diviner
@@ -1,169 +1,169 @@
@title PHP Coding Standards
@group standards
This document describes PHP coding standards for Phabricator and related
projects (like Arcanist and libphutil).
= Overview =
This document outlines technical and style guidelines which are followed in
libphutil. Contributors should also follow these guidelines. Many of these
guidelines are automatically enforced by lint.
These guidelines are essentially identical to the Facebook guidelines, since I
basically copy-pasted them. If you are already familiar with the Facebook
guidelines, you probably don't need to read this super thoroughly.
= Spaces, Linebreaks and Indentation =
- Use two spaces for indentation. Don't use tab literal characters.
- Use Unix linebreaks ("\n"), not MSDOS ("\r\n") or OS9 ("\r").
- - Put a space after control keywords like ##if## and ##for##.
+ - Put a space after control keywords like `if` and `for`.
- Put a space after commas in argument lists.
- - Put a space around operators like ##=##, ##<##, etc.
+ - Put a space around operators like `=`, `<`, etc.
- Don't put spaces after function names.
- Parentheses should hug their contents.
- Generally, prefer to wrap code at 80 columns.
= Case and Capitalization =
- - Name variables and functions using ##lowercase_with_underscores##.
- - Name classes using ##UpperCamelCase##.
- - Name methods and properties using ##lowerCamelCase##.
+ - Name variables and functions using `lowercase_with_underscores`.
+ - Name classes using `UpperCamelCase`.
+ - Name methods and properties using `lowerCamelCase`.
- Use uppercase for common acronyms like ID and HTML.
- - Name constants using ##UPPERCASE##.
- - Write ##true##, ##false## and ##null## in lowercase.
+ - Name constants using `UPPERCASE`.
+ - Write `true`, `false` and `null` in lowercase.
= Comments =
- Do not use "#" (shell-style) comments.
- Prefer "//" comments inside function and method bodies.
= PHP Language Style =
- Use "" tag.
- - Prefer casts like ##(string)## to casting functions like ##strval()##.
- - Prefer type checks like ##$v === null## to type functions like
- ##is_null()##.
+ - Prefer casts like `(string)` to casting functions like `strval()`.
+ - Prefer type checks like `$v === null` to type functions like
+ `is_null()`.
- Avoid all crazy alternate forms of language constructs like "endwhile"
and "<>".
- Always put braces around conditional and loop blocks.
= PHP Language Features =
- Use PHP as a programming language, not a templating language.
- Avoid globals.
- Avoid extract().
- Avoid eval().
- Avoid variable variables.
- Prefer classes over functions.
- Prefer class constants over defines.
- Avoid naked class properties; instead, define accessors.
- Use exceptions for error conditions.
- Use type hints, use `assert_instances_of()` for arrays holding objects.
= Examples =
**if/else:**
if ($some_variable > 3) {
// ...
} else if ($some_variable === null) {
// ...
} else {
// ...
}
You should always put braces around the body of an if clause, even if it is only
one line long. Note spaces around operators and after control statements. Do not
use the "endif" construct, and write "else if" as two words.
**for:**
for ($ii = 0; $ii < 10; $ii++) {
// ...
}
Prefer $ii, $jj, $kk, etc., as iterators, since they're easier to pick out
visually and react better to "Find Next..." in editors.
**foreach:**
foreach ($map as $key => $value) {
// ...
}
**switch:**
switch ($value) {
case 1:
// ...
break;
case 2:
if ($flag) {
// ...
break;
}
break;
default:
// ...
break;
}
`break` statements should be indented to block level.
**array literals:**
$junk = array(
'nuts',
'bolts',
'refuse',
);
Use a trailing comma and put the closing parenthesis on a separate line so that
diffs which add elements to the array affect only one line.
**operators:**
$a + $b; // Put spaces around operators.
$omg.$lol; // Exception: no spaces around string concatenation.
$arr[] = $element; // Couple [] with the array when appending.
$obj = new Thing(); // Always use parens.
**function/method calls:**
// One line
eject($cargo);
// Multiline
AbstractFireFactoryFactoryEngine::promulgateConflagrationInstance(
$fuel,
$ignition_source);
**function/method definitions:**
function example_function($base_value, $additional_value) {
return $base_value + $additional_value;
}
class C {
public static function promulgateConflagrationInstance(
IFuel $fuel,
IgnitionSource $source) {
// ...
}
}
**class:**
class Dog extends Animal {
const CIRCLES_REQUIRED_TO_LIE_DOWN = 3;
private $favoriteFood = 'dirt';
public function getFavoriteFood() {
return $this->favoriteFood;
}
}
diff --git a/src/docs/contributor/unit_tests.diviner b/src/docs/contributor/unit_tests.diviner
index ac0d57e44d..3ac14b3e00 100644
--- a/src/docs/contributor/unit_tests.diviner
+++ b/src/docs/contributor/unit_tests.diviner
@@ -1,86 +1,86 @@
@title Writing Unit Tests
@group developer
Simple guide to libphutil, Arcanist and Phabricator unit tests.
= Overview =
libphutil, Arcanist and Phabricator provide and use a simple unit test
framework. This document is aimed at project contributors and describes how to
use it to add and run tests in these projects or other libphutil libraries.
-In the general case, you can integrate ##arc## with a custom unit test engine
+In the general case, you can integrate `arc` with a custom unit test engine
(like PHPUnit or any other unit testing library) to run tests in other projects.
See @{article:Arcanist User Guide: Customizing Lint, Unit Tests and Workflows}
for information on customizing engines.
= Adding Tests =
To add new tests to a libphutil, Arcanist or Phabricator module:
- - Create a ##__tests__/## directory in the module if it doesn't exist yet.
- - Add classes to the ##__tests__/## directory which extend from
+ - Create a `__tests__/` directory in the module if it doesn't exist yet.
+ - Add classes to the `__tests__/` directory which extend from
@{class:PhabricatorTestCase} (in Phabricator) or
@{class@arcanist:PhutilTestCase} (elsewhere).
- - Run ##arc liberate## on the library root so your classes are loadable.
+ - Run `arc liberate` on the library root so your classes are loadable.
= Running Tests =
Once you've added test classes, you can run them with:
- - ##arc unit path/to/module/##, to explicitly run module tests.
- - ##arc unit##, to run tests for all modules affected by changes in the
+ - `arc unit path/to/module/`, to explicitly run module tests.
+ - `arc unit`, to run tests for all modules affected by changes in the
working copy.
- - ##arc diff## will also run ##arc unit## for you.
+ - `arc diff` will also run `arc unit` for you.
= Example Test Case =
Here's a simple example test:
lang=php
class PhabricatorTrivialTestCase extends PhabricatorTestCase {
private $two;
public function willRunOneTest($test_name) {
// You can execute setup steps which will run before each test in this
// method.
$this->two = 2;
}
public function testAllIsRightWithTheWorld() {
$this->assertEqual(4, $this->two + $this->two, '2 + 2 = 4');
}
}
You can see this class at @{class:PhabricatorTrivialTestCase} and run it with:
phabricator/ $ arc unit src/infrastructure/testing/testcase/
PASS <1ms* testAllIsRightWithTheWorld
For more information on writing tests, see
@{class@arcanist:PhutilTestCase} and @{class:PhabricatorTestCase}.
= Database Isolation =
By default, Phabricator isolates unit tests from the database. It makes a crude
effort to simulate some side effects (principally, ID assignment on insert), but
any queries which read data will fail to select any rows and throw an exception
about isolation. In general, isolation is good, but this can make certain types
of tests difficult to write. When you encounter issues, you can deal with them
in a number of ways. From best to worst:
- Encounter no issues; your tests are fast and isolated.
- Add more simulated side effects if you encounter minor issues and simulation
is reasonable.
- Build a real database simulation layer (fairly complex).
- Disable isolation for a single test by using
- ##LiskDAO::endIsolateAllLiskEffectsToCurrentProcess();## before your test
- and ##LiskDAO::beginIsolateAllLiskEffectsToCurrentProcess();## after your
+ `LiskDAO::endIsolateAllLiskEffectsToCurrentProcess();` before your test
+ and `LiskDAO::beginIsolateAllLiskEffectsToCurrentProcess();` after your
test. This will disable isolation for one test. NOT RECOMMENDED.
- Disable isolation for your entire test case by overriding
- ##getPhabricatorTestCaseConfiguration()## and providing
- ##self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK => false## in the configuration
+ `getPhabricatorTestCaseConfiguration()` and providing
+ `self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK => false` in the configuration
dictionary you return. This will disable isolation entirely. STRONGLY NOT
RECOMMENDED.
diff --git a/src/docs/contributor/using_oauthserver.diviner b/src/docs/contributor/using_oauthserver.diviner
index dc8b377671..ff6f33a0c1 100644
--- a/src/docs/contributor/using_oauthserver.diviner
+++ b/src/docs/contributor/using_oauthserver.diviner
@@ -1,120 +1,120 @@
@title Using the Phabricator OAuth Server
@group developer
How to use the Phabricator OAuth Server.
= Overview =
Phabricator includes an OAuth Server which supports the
`Authorization Code Grant` flow as described in the OAuth 2.0
specification:
http://tools.ietf.org/html/draft-ietf-oauth-v2-23
This functionality can allow clients to integrate with a given
Phabricator instance in a secure way with granular data access.
For example, Phabricator can be used as a central identity store for any
clients that implement OAuth 2.0.
= Vocabulary =
- **Access token** - a token which allows a client to ask for data on behalf
of a resource owner. A given client will only be able to access data included
in the scope(s) the resource owner authorized that client for.
- **Authorization code** - a short-lived code which allows an authenticated
client to ask for an access token on behalf of some resource owner.
- **Client** - this is the application or system asking for data from the
OAuth Server on behalf of the resource owner.
- **Resource owner** - this is the user the client and OAuth Server are
concerned with on a given request.
- **Scope** - this defines a specific piece of granular data a client can
or can not access on behalf of a user. For example, if authorized for the
"whoami" scope on behalf of a given resource owner, the client can get the
results of Conduit.whoami for that resource owner when authenticated with
a valid access token.
= Setup - Creating a Client =
# Visit https://phabricator.example.com/oauthserver/client/create/
# Fill out the form
# Profit
= Obtaining an Authorization Code =
POST or GET https://phabricator.example.com/oauthserver/auth/ with the
following parameters:
- Required - **client_id** - the id of the newly registered client.
- Required - **response_type** - the desired type of authorization code
response. Only code is supported at this time.
- Optional - **redirect_uri** - override the redirect_uri the client
registered. This redirect_uri must have the same fully-qualified domain,
path, port and have at least the same query parameters as the redirect_uri
the client registered, as well as have no fragments.
- Optional - **scope** - specify what scope(s) the client needs access to
in a space-delimited list.
- Optional - **state** - an opaque value the client can send to the server
for programmatic excellence. Some clients use this value to implement XSRF
protection or for debugging purposes.
If done correctly and the resource owner has not yet authorized the client
for the desired scope, then the resource owner will be presented with an
interface to authorize the client for the desired scope. The OAuth Server
will redirect to the pertinent redirect_uri with an authorization code or
an error indicating the resource owner did not authorize the client, depending.
If done correctly and the resource owner has already authorized the client for
the desired scope, then the OAuth Server will redirect to the pertinent
redirect_uri with a valid authorization code.
If there is an error, the OAuth Server will return a descriptive error
message. This error will be presented to the resource owner on the
Phabricator domain if there is reason to believe there is something fishy
with the client. For example, if there is an issue with the redirect_uri.
Otherwise, the OAuth Server will redirect to the pertinent redirect_uri
and include the pertinent error information.
= Obtaining an Access Token =
POST or GET https://phabricator.example.com/oauthserver/token/
with the following parameters:
- Required - **client_id** - the id of the client
- Required - **client_secret** - the secret of the client.
This is used to authenticate the client.
- Required - **code** - the authorization code obtained earlier.
- Required - **grant_type** - the desired type of access grant.
Only token is supported at this time.
- Optional - **redirect_uri** - should be the exact same redirect_uri as
the redirect_uri specified to obtain the authorization code. If no
redirect_uri was specified to obtain the authorization code then this
should not be specified.
If done correctly, the OAuth Server will redirect to the pertinent
redirect_uri with an access token.
If there is an error, the OAuth Server will return a descriptive error
message.
= Using an Access Token =
Simply include a query param with the key of "access_token" and the value
as the earlier obtained access token. For example:
https://phabricator.example.com/api/user.whoami?access_token=ykc7ly7vtibj334oga4fnfbuvnwz4ocp
If the token has expired or is otherwise invalid, the client will receive
an error indicating as such. In these cases, the client should re-initiate
-the entire ##Authorization Code Grant## flow.
+the entire `Authorization Code Grant` flow.
NOTE: See "Scopes" section below for more information on what data is
currently exposed through the OAuth Server.
= Scopes =
There are only two scopes supported at this time.
- **offline_access** - allows an access token to work indefinitely without
expiring.
- **whoami** - allows the client to access the results of Conduit.whoami on
behalf of the resource owner.
diff --git a/src/docs/flavor/javascript_object_array.diviner b/src/docs/flavor/javascript_object_array.diviner
index cf7e3371e6..cdba029e9a 100644
--- a/src/docs/flavor/javascript_object_array.diviner
+++ b/src/docs/flavor/javascript_object_array.diviner
@@ -1,152 +1,152 @@
@title Javascript Object and Array
@group javascript
This document describes the behaviors of Object and Array in Javascript, and
a specific approach to their use which produces basically reasonable language
behavior.
= Primitives =
Javascript has two native datatype primitives, Object and Array. Both are
-classes, so you can use ##new## to instantiate new objects and arrays:
+classes, so you can use `new` to instantiate new objects and arrays:
COUNTEREXAMPLE
var a = new Array(); // Not preferred.
var o = new Object();
However, **you should prefer the shorthand notation** because it's more concise:
lang=js
var a = []; // Preferred.
var o = {};
(A possible exception to this rule is if you want to use the allocation behavior
of the Array constructor, but you almost certainly don't.)
The language relationship between Object and Array is somewhat tricky. Object
and Array are both classes, but "object" is also a primitive type. Object is
//also// the base class of all classes.
lang=js
typeof Object; // "function"
typeof Array; // "function"
typeof {}; // "object"
typeof []; // "object"
var a = [], o = {};
o instanceof Object; // true
o instanceof Array; // false
a instanceof Object; // true
a instanceof Array; // true
= Objects are Maps, Arrays are Lists =
-PHP has a single ##array## datatype which behaves like as both map and a list,
+PHP has a single `array` datatype which behaves like as both map and a list,
and a common mistake is to treat Javascript arrays (or objects) in the same way.
**Don't do this.** It sort of works until it doesn't. Instead, learn how
Javascript's native datatypes work and use them properly.
In Javascript, you should think of Objects as maps ("dictionaries") and Arrays
as lists ("vectors").
You store keys-value pairs in a map, and store ordered values in a list. So,
store key-value pairs in Objects.
var o = { // Good, an object is a map.
name: 'Hubert',
species: 'zebra'
};
console.log(o.name);
...and store ordered values in Arrays.
var a = [1, 2, 3]; // Good, an array is a list.
a.push(4);
Don't store key-value pairs in Arrays and don't expect Objects to be ordered.
COUNTEREXAMPLE
var a = [];
a['name'] = 'Hubert'; // No! Don't do this!
This technically works because Arrays are Objects and you think everything is
fine and dandy, but it won't do what you want and will burn you.
= Iterating over Maps and Lists =
Iterate over a map like this:
lang=js
for (var k in object) {
f(object[k]);
}
NOTE: There's some hasOwnProperty nonsense being omitted here, see below.
Iterate over a list like this:
lang=js
for (var ii = 0; ii < list.length; ii++) {
f(list[ii]);
}
NOTE: There's some sparse array nonsense being omitted here, see below.
-If you try to use ##for (var k in ...)## syntax to iterate over an Array, you'll
+If you try to use `for (var k in ...)` syntax to iterate over an Array, you'll
pick up a whole pile of keys you didn't intend to and it won't work. If you try
-to use ##for (var ii = 0; ...)## syntax to iterate over an Object, it won't work
+to use `for (var ii = 0; ...)` syntax to iterate over an Object, it won't work
at all.
If you consistently treat Arrays as lists and Objects as maps and use the
corresponding iterators, everything will pretty much always work in a reasonable
way.
= hasOwnProperty() =
An issue with this model is that if you write stuff to Object.prototype, it will
-show up every time you use enumeration ##for##:
+show up every time you use enumeration `for`:
COUNTEREXAMPLE
var o = {};
Object.prototype.duck = "quack";
for (var k in o) {
console.log(o[k]); // Logs "quack"
}
There are two ways to avoid this:
- - test that ##k## exists on ##o## by calling ##o.hasOwnProperty(k)## in every
+ - test that `k` exists on `o` by calling `o.hasOwnProperty(k)` in every
single loop everywhere in your program and only use libraries which also do
this and never forget to do it ever; or
- don't write to Object.prototype.
Of these, the first option is terrible garbage. Go with the second option.
= Sparse Arrays =
Another wrench in this mess is that Arrays aren't precisely like lists, because
they do have indexes and may be sparse:
var a = [];
a[2] = 1;
console.log(a); // [undefined, undefined, 1]
The correct way to deal with this is:
for (var ii = 0; ii < list.length; ii++) {
if (list[ii] == undefined) {
continue;
}
f(list[ii]);
}
Avoid sparse arrays if possible.
= Ordered Maps =
If you need an ordered map, you need to have a map for key-value associations
and a list for key order. Don't try to build an ordered map using one Object or
one Array. This generally applies for other complicated datatypes, as well; you
need to build them out of more than one primitive.
diff --git a/src/docs/flavor/javascript_pitfalls.diviner b/src/docs/flavor/javascript_pitfalls.diviner
index beef1b83e6..9276ae4712 100644
--- a/src/docs/flavor/javascript_pitfalls.diviner
+++ b/src/docs/flavor/javascript_pitfalls.diviner
@@ -1,87 +1,87 @@
@title Javascript Pitfalls
@group javascript
This document discusses pitfalls and flaws in the Javascript language, and how
to avoid, work around, or at least understand them.
= Implicit Semicolons =
Javascript tries to insert semicolons if you forgot them. This is a pretty
horrible idea. Notably, it can mask syntax errors by transforming subexpressions
on their own lines into statements with no effect:
lang=js
string = "Here is a fairly long string that does not fit on one "
"line. Note that I forgot the string concatenation operators "
"so this will compile and execute with the wrong behavior. ";
Here's what ECMA262 says about this:
When, as the program is parsed ..., a token ... is encountered that is not
allowed by any production of the grammar, then a semicolon is automatically
inserted before the offending token if one or more of the following conditions
is true: ...
To protect yourself against this "feature", don't use it. Always explicitly
insert semicolons after each statement. You should also prefer to break lines in
places where insertion of a semicolon would not make the unparseable parseable,
usually after operators.
-= ##with## is Bad News =
+= `with` is Bad News =
`with` is a pretty bad feature, for this reason among others:
with (object) {
property = 3; // Might be on object, might be on window: who knows.
}
-Avoid ##with##.
+Avoid `with`.
-= ##arguments## is not an Array =
+= `arguments` is not an Array =
-You can convert ##arguments## to an array using JX.$A() or similar. Note that
-you can pass ##arguments## to Function.prototype.apply() without converting it.
+You can convert `arguments` to an array using JX.$A() or similar. Note that
+you can pass `arguments` to Function.prototype.apply() without converting it.
= Object, Array, and iteration are needlessly hard =
There is essentially only one reasonable, consistent way to use these primitives
but it is not obvious. Navigate these troubled waters with
@{article:Javascript Object and Array}.
= typeof null == "object" =
This statement is true in Javascript:
typeof null == 'object'
This is pretty much a bug in the language that can never be fixed now.
= Number, String, and Boolean objects =
Like Java, Javascript has primitive versions of number, string, and boolean,
and object versions. In Java, there's some argument for this distinction. In
Javascript, it's pretty much completely worthless and the behavior of these
objects is wrong. String and Boolean in particular are essentially unusable:
lang=js
"pancake" == "pancake"; // true
new String("pancake") == new String("pancake"); // false
var b = new Boolean(false);
b; // Shows 'false' in console.
!b; // ALSO shows 'false' in console.
!b == b; // So this is true!
!!b == !b // Negate both sides and it's false! FUCK!
if (b) {
// Better fucking believe this will get executed.
}
There is no advantage to using the object forms (the primitive forms behave like
objects and can have methods and properties, and inherit from Array.prototype,
Number.prototype, etc.) and their logical behavior is at best absurd and at
worst strictly wrong.
-**Never use** ##new Number()##, ##new String()## or ##new Boolean()## unless
+**Never use** `new Number()`, `new String()` or `new Boolean()` unless
your Javascript is God Tier and you are absolutely sure you know what you are
doing.
diff --git a/src/docs/flavor/php_pitfalls.diviner b/src/docs/flavor/php_pitfalls.diviner
index 2b8373f8cc..09e0f108f0 100644
--- a/src/docs/flavor/php_pitfalls.diviner
+++ b/src/docs/flavor/php_pitfalls.diviner
@@ -1,301 +1,301 @@
@title PHP Pitfalls
@group php
This document discusses difficult traps and pitfalls in PHP, and how to avoid,
work around, or at least understand them.
= array_merge() in Incredibly Slow When Merging A List of Arrays =
If you merge a list of arrays like this:
COUNTEREXAMPLE
$result = array();
foreach ($list_of_lists as $one_list) {
$result = array_merge($result, $one_list);
}
...your program now has a huge runtime because it generates a large number of
intermediate arrays and copies every element it has previously seen each time
you iterate.
In a libphutil environment, you can use @{function@libphutil:array_mergev}
instead.
= var_export() Hates Baby Animals =
If you try to var_export() an object that contains recursive references, your
program will terminate. You have no chance to intercept or react to this or
otherwise stop it from happening. Avoid var_export() unless you are certain
you have only simple data. You can use print_r() or var_dump() to display
complex variables safely.
= isset(), empty() and Truthiness =
-A value is "truthy" if it evaluates to true in an ##if## clause:
+A value is "truthy" if it evaluates to true in an `if` clause:
$value = something();
if ($value) {
// Value is truthy.
}
If a value is not truthy, it is "falsey". These values are falsey in PHP:
null // null
0 // integer
0.0 // float
"0" // string
"" // empty string
false // boolean
array() // empty array
Disregarding some bizarre edge cases, all other values are truthy. Note that
because "0" is falsey, this sort of thing (intended to prevent users from making
empty comments) is wrong in PHP:
COUNTEREXAMPLE
if ($comment_text) {
make_comment($comment_text);
}
This is wrong because it prevents users from making the comment "0". //THIS
COMMENT IS TOTALLY AWESOME AND I MAKE IT ALL THE TIME SO YOU HAD BETTER NOT
BREAK IT!!!// A better test is probably strlen().
-In addition to truth tests with ##if##, PHP has two special truthiness operators
+In addition to truth tests with `if`, PHP has two special truthiness operators
which look like functions but aren't: empty() and isset(). These operators help
deal with undeclared variables.
In PHP, there are two major cases where you get undeclared variables -- either
you directly use a variable without declaring it:
COUNTEREXAMPLE
function f() {
if ($not_declared) {
// ...
}
}
...or you index into an array with an index which may not exist:
COUNTEREXAMPLE
function f(array $mystery) {
if ($mystery['stuff']) {
// ...
}
}
When you do either of these, PHP issues a warning. Avoid these warnings by using
empty() and isset() to do tests that are safe to apply to undeclared variables.
empty() evaluates truthiness exactly opposite of if(). isset() returns true for
everything except null. This is the truth table:
VALUE if() empty() isset()
null false true false
0 false true true
0.0 false true true
"0" false true true
"" false true true
false false true true
array() false true true
EVERYTHING ELSE true false true
The value of these operators is that they accept undeclared variables and do not
issue a warning. Specifically, if you try to do this you get a warning:
COUNTEREXAMPLE
if ($not_previously_declared) { // PHP Notice: Undefined variable!
// ...
}
But these are fine:
if (empty($not_previously_declared)) { // No notice, returns true.
// ...
}
if (isset($not_previously_declared)) { // No notice, returns false.
// ...
}
So, isset() really means is_declared_and_is_set_to_something_other_than_null().
empty() really means is_falsey_or_is_not_declared(). Thus:
- If a variable is known to exist, test falsiness with if (!$v), not empty().
In particular, test for empty arrays with if (!$array). There is no reason
to ever use empty() on a declared variable.
- When you use isset() on an array key, like isset($array['key']), it will
evaluate to "false" if the key exists but has the value null! Test for index
existence with array_key_exists().
Put another way, use isset() if you want to type "if ($value !== null)" but are
testing something that may not be declared. Use empty() if you want to type
"if (!$value)" but you are testing something that may not be declared.
= usort(), uksort(), and uasort() are Slow =
This family of functions is often extremely slow for large datasets. You should
avoid them if at all possible. Instead, build an array which contains surrogate
keys that are naturally sortable with a function that uses native comparison
(e.g., sort(), asort(), ksort(), or natcasesort()). Sort this array instead, and
use it to reorder the original array.
In a libphutil environment, you can often do this easily with
@{function@libphutil:isort} or @{function@libphutil:msort}.
= array_intersect() and array_diff() are Also Slow =
These functions are much slower for even moderately large inputs than
array_intersect_key() and array_diff_key(), because they can not make the
-assumption that their inputs are unique scalars as the ##key## varieties can.
-Strongly prefer the ##key## varieties.
+assumption that their inputs are unique scalars as the `key` varieties can.
+Strongly prefer the `key` varieties.
= array_uintersect() and array_udiff() are Definitely Slow Too =
-These functions have the problems of both the ##usort()## family and the
+These functions have the problems of both the `usort()` family and the
`array_diff()` family. Avoid them.
= foreach() Does Not Create Scope =
Variables survive outside of the scope of foreach(). More problematically,
references survive outside of the scope of foreach(). This code mutates
`$array` because the reference leaks from the first loop to the second:
COUNTEREXAMPLE
$array = range(1, 3);
echo implode(',', $array); // Outputs '1,2,3'
foreach ($array as &$value) {}
echo implode(',', $array); // Outputs '1,2,3'
foreach ($array as $value) {}
echo implode(',', $array); // Outputs '1,2,2'
The easiest way to avoid this is to avoid using foreach-by-reference. If you do
use it, unset the reference after the loop:
foreach ($array as &$value) {
// ...
}
unset($value);
= unserialize() is Incredibly Slow on Large Datasets =
The performance of unserialize() is nonlinear in the number of zvals you
unserialize, roughly O(N^2).
zvals approximate time
10000 5ms
100000 85ms
1000000 8,000ms
10000000 72 billion years
= call_user_func() Breaks References =
If you use call_use_func() to invoke a function which takes parameters by
reference, the variables you pass in will have their references broken and will
emerge unmodified. That is, if you have a function that takes references:
function add_one(&$v) {
$v++;
}
...and you call it with call_user_func():
COUNTEREXAMPLE
$x = 41;
call_user_func('add_one', $x);
-...##$x## will not be modified. The solution is to use call_user_func_array()
+...`$x` will not be modified. The solution is to use call_user_func_array()
and wrap the reference in an array:
$x = 41;
call_user_func_array(
'add_one',
array(&$x)); // Note '&$x'!
This will work as expected.
= You Can't Throw From __toString() =
If you throw from __toString(), your program will terminate uselessly and you
won't get the exception.
= An Object Can Have Any Scalar as a Property =
Object properties are not limited to legal variable names:
$property = '!@#$%^&*()';
$obj->$property = 'zebra';
echo $obj->$property; // Outputs 'zebra'.
So, don't make assumptions about property names.
= There is an (object) Cast =
You can cast a dictionary into an object.
$obj = (object)array('flavor' => 'coconut');
echo $obj->flavor; // Outputs 'coconut'.
echo get_class($obj); // Outputs 'stdClass'.
This is occasionally useful, mostly to force an object to become a Javascript
dictionary (vs a list) when passed to json_encode().
= Invoking "new" With an Argument Vector is Really Hard =
-If you have some ##$class_name## and some ##$argv## of constructor
+If you have some `$class_name` and some `$argv` of constructor
arguments and you want to do this:
new $class_name($argv[0], $argv[1], ...);
...you'll probably invent a very interesting, very novel solution that is very
wrong. In a libphutil environment, solve this problem with
@{function@libphutil:newv}. Elsewhere, copy newv()'s implementation.
= Equality is not Transitive =
This isn't terribly surprising since equality isn't transitive in a lot of
languages, but the == operator is not transitive:
$a = ''; $b = 0; $c = '0a';
$a == $b; // true
$b == $c; // true
$c == $a; // false!
When either operand is an integer, the other operand is cast to an integer
before comparison. Avoid this and similar pitfalls by using the === operator,
which is transitive.
= All 676 Letters in the Alphabet =
This doesn't do what you'd expect it to do in C:
for ($c = 'a'; $c <= 'z'; $c++) {
// ...
}
This is because the successor to 'z' is 'aa', which is "less than" 'z'. The
loop will run for ~700 iterations until it reaches 'zz' and terminates. That is,
`$c` will take on these values:
a
b
...
y
z
aa // loop continues because 'aa' <= 'z'
ab
...
mf
mg
...
zw
zx
zy
zz // loop now terminates because 'zz' > 'z'
Instead, use this loop:
foreach (range('a', 'z') as $c) {
// ...
}
diff --git a/src/docs/flavor/recommendations_on_revision_control.diviner b/src/docs/flavor/recommendations_on_revision_control.diviner
index 0cb0eb5e6c..8e30e2f4f1 100644
--- a/src/docs/flavor/recommendations_on_revision_control.diviner
+++ b/src/docs/flavor/recommendations_on_revision_control.diviner
@@ -1,92 +1,92 @@
@title Recommendations on Revision Control
@group review
Project recommendations on how to organize revision control.
This document is purely advisory. Phabricator works with a variety of revision
control strategies, and diverging from the recommendations in this document
will not impact your ability to use it for code review and source management.
This is my (epriestley's) personal take on the issue and not necessarily
representative of the views of the Phabricator team as a whole.
= Overview =
There are a few ways to use SVN, a few ways to use Mercurial, and many many many
ways to use Git. Particularly with Git, every project does things differently,
and all these approaches are valid for small projects. When projects scale,
strategies which enforce **one idea is one commit** are better.
= One Idea is One Commit =
Choose a strategy where **one idea is one commit** in the authoritative
master/remote version of the repository. Specifically, this means that an entire
conceptual changeset ("add a foo widget") is represented in the remote as
exactly one commit (in some form), not a sequence of checkpoint commits.
- - In SVN, this means don't ##commit## until after an idea has been completely
+ - In SVN, this means don't `commit` until after an idea has been completely
written. All reasonable SVN workflows naturally enforce this.
- - In Git, this means squashing checkpoint commits as you go (with ##git commit
- --amend##) or before pushing (with ##git rebase -i## or ##git merge
- --squash##), or having a strict policy where your master/trunk contains only
+ - In Git, this means squashing checkpoint commits as you go (with `git commit
+ --amend`) or before pushing (with `git rebase -i` or `git merge
+ --squash`), or having a strict policy where your master/trunk contains only
merge commits and each is a merge between the old master and a branch which
represents a single idea. Although this preserves the checkpoint commits
along the branches, you can view master alone as a series of single-idea
commits.
- In Mercurial, you can use the "queues" extension before 2.2 or `--amend`
after Mercurial 2.2, or wait to commit until a change is complete (like
SVN), although the latter is not recommended. Without extensions, older
versions of Mercurial do not support liberal mutability doctrines (so you
can't ever combine checkpoint commits) and do not let you build a default
out of only merge commits, so it is not possible to have an authoritative
repository where one commit represents one idea in any real sense.
= Why This Matters =
A strategy where **one idea is one commit** has no real advantage over any other
strategy until your repository hits a velocity where it becomes critical. In
particular:
- Essentially all operations against the master/remote repository are about
ideas, not commits. When one idea is many commits, everything you do is more
complicated because you need to figure out which commits represent an idea
("the foo widget is broken, what do I need to revert?") or what idea is
ultimately represented by a commit ("commit af3291029 makes no sense, what
goal is this change trying to accomplish?").
- Release engineering is greatly simplified. Release engineers can pick or
drop ideas easily when each idea corresponds to one commit. When an idea
is several commits, it becomes easier to accidentally pick or drop half of
an idea and end up in a state which is virtually guaranteed to be wrong.
- Automated testing is greatly simplified. If each idea is one commit, you
can run automated tests against every commit and test failures indicate a
serious problem. If each idea is many commits, most of those commits
represent a known broken state of the codebase (e.g., a checkpoint with a
syntax error which was fixed in the next checkpoint, or with a
half-implemented idea).
- Understanding changes is greatly simplified. You can bisect to a break and
identify the entire idea trivially, without fishing forward and backward in
the log to identify the extents of the idea. And you can be confident in
what you need to revert to remove the entire idea.
- There is no clear value in having checkpoint commits (some of which are
guaranteed to be known broken versions of the repository) persist into the
remote. Consider a theoretical VCS which automatically creates a checkpoint
commit for every keystroke. This VCS would obviously be unusable. But many
checkpoint commits aren't much different, and conceptually represent some
relatively arbitrary point in the sequence of keystrokes that went into
writing a larger idea. Get rid of them or create an abstraction layer (merge
commits) which allows you to ignore them when you are trying to understand
the repository in terms of ideas (which is almost always).
All of these become problems only at scale. Facebook pushes dozens of ideas
every day and thousands on a weekly basis, and could not do this (at least, not
without more people or more errors) without choosing a repository strategy where
**one idea is one commit**.
= Next Steps =
Continue by:
- reading recommendations on structuring branches with
@{article:Recommendations on Branching}; or
- reading recommendations on structuring changes with
@{article:Writing Reviewable Code}.
diff --git a/src/docs/flavor/soon_static_resources.diviner b/src/docs/flavor/soon_static_resources.diviner
index 78820c5081..96f28cfe2f 100644
--- a/src/docs/flavor/soon_static_resources.diviner
+++ b/src/docs/flavor/soon_static_resources.diviner
@@ -1,126 +1,126 @@
@title Things You Should Do Soon: Static Resources
@group sundry
Over time, you'll write more JS and CSS and eventually need to put systems in
place to manage it.
This is part of @{article:Things You Should Do Soon}, which describes
architectural problems in web applications which you should begin to consider
before you encounter them.
= Manage Dependencies Automatically =
The naive way to add static resources to a page is to include them at the top
of the page, before rendering begins, by enumerating filenames. Facebook used to
work like that:
COUNTEREXAMPLE
` and `` references to these resources.
-These references point at ##/res/## URIs, which are handled by
+These references point at `/res/` URIs, which are handled by
@{class:CelerityResourceController}. It responds to these requests and delivers
the relevant resources and packages, managing cache lifetimes and handling any
neessary preprocessing. It uses @{class:CelerityResourceMap} to locate resources
and read packaging rules.
-The dependency and packaging maps are generated by ##bin/celerity map##,
-which updates ##resources/celerity/map.php##.
+The dependency and packaging maps are generated by `bin/celerity map`,
+which updates `resources/celerity/map.php`.
@{class:CelerityStaticResourceResponse} also manages some Javelin information,
and @{function:celerity_generate_unique_node_id} uses this metadata to provide
a better uniqueness guarantee when generating unique node IDs.
diff --git a/src/docs/tech/chatbot.diviner b/src/docs/tech/chatbot.diviner
index e37cedf8cf..9bc1ce4796 100644
--- a/src/docs/tech/chatbot.diviner
+++ b/src/docs/tech/chatbot.diviner
@@ -1,89 +1,89 @@
@title Chat Bot Technical Documentation
@group bot
Configuring and extending the chat bot.
= Overview =
Phabricator includes a simple chat bot daemon, which is primarily intended as
an example of how you can write an external script that interfaces with
Phabricator over Conduit and does some kind of useful work. If you use IRC or
another supported chat protocol, you can also have the bot hang out in your
channel.
NOTE: The chat bot is somewhat experimental and not very mature.
= Configuring the Bot =
The bot reads a JSON configuration file. You can find an example in:
resources/chatbot/example_config.json
These are the configuration values it reads:
- - ##server## String, required, the server to connect to.
- - ##port## Int, optional, the port to connect to (defaults to 6667).
- - ##ssl## Bool, optional, whether to connect via SSL or not (defaults to
+ - `server` String, required, the server to connect to.
+ - `port` Int, optional, the port to connect to (defaults to 6667).
+ - `ssl` Bool, optional, whether to connect via SSL or not (defaults to
false).
- - ##nick## String, nickname to use.
- - ##user## String, optional, username to use (defaults to ##nick##).
- - ##pass## String, optional, password for server.
- - ##nickpass## String, optional, password for NickServ.
- - ##join## Array, list of channels to join.
- - ##handlers## Array, list of handlers to run. These are like plugins for the
+ - `nick` String, nickname to use.
+ - `user` String, optional, username to use (defaults to `nick`).
+ - `pass` String, optional, password for server.
+ - `nickpass` String, optional, password for NickServ.
+ - `join` Array, list of channels to join.
+ - `handlers` Array, list of handlers to run. These are like plugins for the
bot.
- - ##conduit.uri##, ##conduit.user##, ##conduit.cert## Conduit configuration,
+ - `conduit.uri`, `conduit.user`, `conduit.cert` Conduit configuration,
see below.
- - ##notification.channels## Notification configuration, see below.
+ - `notification.channels` Notification configuration, see below.
= Handlers =
You specify a list of "handlers", which are basically plugins or modules for
the bot. These are the default handlers available:
- @{class:PhabricatorBotObjectNameHandler} This handler looks for users
mentioning Phabricator objects like "T123" and "D345" in chat, looks them
up, and says their name with a link to the object. Requires conduit.
- @{class:PhabricatorBotFeedNotificationHandler} This handler posts
notifications about changes to revisions to the channels listed in
- ##notification.channels##.
+ `notification.channels`.
- @{class:PhabricatorBotLogHandler} This handler records chatlogs which can
be browsed in the Phabricator web interface.
- @{class:PhabricatorBotSymbolHandler} This handler posts responses to lookups
for symbols in Diffusion
- @{class:PhabricatorBotMacroHandler} This handler looks for users mentioning
macros, if found will convert image to ASCII and output in chat. Configure
- with ##macro.size## and ##macro.aspect##
+ with `macro.size` and `macro.aspect`
You can also write your own handlers, by extending
@{class:PhabricatorBotHandler}.
= Conduit =
Some handlers (e.g., @{class:PhabricatorBotObjectNameHandler}) need to read data
from Phabricator over Conduit, Phabricator's HTTP API. You can use this method
to allow other scripts or programs to access Phabricator's data from different
servers and in different languages.
To allow the bot to access Conduit, you need to create a user that it can login
with. To do this, login to Phabricator as an administrator and go to
`People -> Create New Account`. Create a new account and flag them as a
"Bot/Script". Then in your configuration file, set these parameters:
- - ##conduit.uri## The URI for your Phabricator install, like
- ##http://phabricator.example.com/##
- - ##conduit.user## The username your bot should login to Phabricator with --
- whatever you selected above, like ##phabot##.
- - ##conduit.cert## The user's certificate, from the "Conduit Certificate" tab
+ - `conduit.uri` The URI for your Phabricator install, like
+ `http://phabricator.example.com/`
+ - `conduit.user` The username your bot should login to Phabricator with --
+ whatever you selected above, like `phabot`.
+ - `conduit.cert` The user's certificate, from the "Conduit Certificate" tab
in the user's administrative view.
Now the bot should be able to connect to Phabricator via Conduit.
= Starting the Bot =
-The bot is a Phabricator daemon, so start it with ##phd##:
+The bot is a Phabricator daemon, so start it with `phd`:
./bin/phd launch phabricatorbot
-If you have issues you can try ##debug## instead of ##launch##, see
+If you have issues you can try `debug` instead of `launch`, see
@{article:Managing Daemons with phd} for more information.
diff --git a/src/docs/tech/conduit.diviner b/src/docs/tech/conduit.diviner
index e2ff5c1cf4..b8b0f9da93 100644
--- a/src/docs/tech/conduit.diviner
+++ b/src/docs/tech/conduit.diviner
@@ -1,57 +1,57 @@
@title Conduit Technical Documentation
@group conduit
Technical overview of the Conduit API.
= Overview =
Conduit is an informal mechanism for transferring ad-hoc JSON blobs around on
the internet.
Theoretically, it provides an API to Phabricator so external scripts (including
scripts written in other languages) can interface with the applications in the
Phabricator suite. It technically does this, sort of, but it is unstable and
incomplete so you should keep your expectations very low if you choose to build
things on top of it.
NOTE: Hopefully, this should improve over time, but making Conduit more robust
isn't currently a major project priority because there isn't much demand for it
outside of internal scripts. If you want to use Conduit to build things on top
of Phabricator, let us know so we can adjust priorities.
Conduit provides an authenticated HTTP API for Phabricator. It is informal and
extremely simple: you post a JSON blob and you get a JSON blob back. You can
access Conduit in PHP with @{class@libphutil:ConduitClient}, or in any language
-by executing ##arc call-conduit method## (see ##arc help call-conduit## for
-more information). You can see and test available methods at ##/conduit/## in
+by executing `arc call-conduit method` (see `arc help call-conduit` for
+more information). You can see and test available methods at `/conduit/` in
the web interface.
Arcanist is implemented using Conduit, and @{class:PhabricatorBot} is
intended as a practical example of how to write a program which interfaces with
Phabricator over Conduit.
= Class Relationships =
-The primary Conduit workflow is exposed at ##/api/##, which routes to
+The primary Conduit workflow is exposed at `/api/`, which routes to
@{class:PhabricatorConduitAPIController}. This controller builds a
@{class:ConduitAPIRequest} representing authentication information and POST
parameters, instantiates an appropriate subclass of @{class:ConduitAPIMethod},
and passes the request to it. Subclasses of @{class:ConduitAPIMethod} implement
the actual methods which Conduit exposes.
Conduit calls which fail throw @{class:ConduitException}, which the controller
handles.
There is a web interface for viewing and testing Conduit called the "Conduit
Console", implemented by @{class:PhabricatorConduitConsoleController} at
`/conduit/`.
A log of connections and calls is stored by
@{class:PhabricatorConduitConnectionLog} and
@{class:PhabricatorConduitMethodCallLog}, and can be accessed on the web via
-@{class:PhabricatorConduitLogController} at ##/conduit/log/##.
+@{class:PhabricatorConduitLogController} at `/conduit/log/`.
Conduit provides a token-based handshake mechanism used by
`arc install-certificate` at `/conduit/token/`, implemented by
@{class:PhabricatorConduitTokenController} which stores generated tokens using
@{class:PhabricatorConduitCertificateToken}.
diff --git a/src/docs/user/configuration/configuration_guide.diviner b/src/docs/user/configuration/configuration_guide.diviner
index 72f438cd3f..b420996848 100644
--- a/src/docs/user/configuration/configuration_guide.diviner
+++ b/src/docs/user/configuration/configuration_guide.diviner
@@ -1,212 +1,212 @@
@title Configuration Guide
@group config
This document contains basic configuration instructions for Phabricator.
= Prerequisites =
This document assumes you've already installed all the components you need.
If you haven't, see @{article:Installation Guide}.
The next steps are:
- Configure your webserver (Apache, nginx, or lighttpd).
- Access Phabricator with your browser.
- Follow the instructions to complete setup.
= Webserver: Configuring Apache =
NOTE: Follow these instructions to use Apache. To use nginx or lighttpd, scroll
down to their sections.
Get Apache running and verify it's serving a test page. Consult the Apache
-documentation for help. Make sure ##mod_php## and ##mod_rewrite## are enabled,
-and ##mod_ssl## if you intend to set up SSL.
+documentation for help. Make sure `mod_php` and `mod_rewrite` are enabled,
+and `mod_ssl` if you intend to set up SSL.
If you haven't already, set up a domain name to point to the host you're
installing on. You can either install Phabricator on a subdomain (like
phabricator.example.com) or an entire domain, but you can not install it in
some subdirectory of an existing website. Navigate to whatever domain you're
going to use and make sure Apache serves you something to verify that DNS
is correctly configured.
NOTE: The domain must contain a dot ('.'), i.e. not be just a bare name like
'http://example/'. Some web browsers will not set cookies otherwise.
Now create a VirtualHost entry for Phabricator. It should look something like
this:
name=httpd.conf
# Change this to the domain which points to your host.
ServerName phabricator.example.com
# Change this to the path where you put 'phabricator' when you checked it
# out from GitHub when following the Installation Guide.
#
# Make sure you include "/webroot" at the end!
DocumentRoot /path/to/phabricator/webroot
RewriteEngine on
RewriteRule ^/rsrc/(.*) - [L,QSA]
RewriteRule ^/favicon.ico - [L,QSA]
RewriteRule ^(.*)$ /index.php?__path__=$1 [B,L,QSA]
If Apache isn't currently configured to serve documents out of the directory
where you put Phabricator, you may also need to add `` section. The
syntax for this section depends on which version of Apache you're running.
(If you don't know, you can usually figure this out by running `httpd -v`.)
For Apache versions older than 2.4, use this:
name="Apache Older Than 2.4"
Order allow,deny
Allow from all
For Apache versions 2.4 and newer, use this:
name="Apache 2.4 and Newer"
Require all granted
After making your edits, restart Apache, then continue to "Setup" below.
= Webserver: Configuring nginx =
NOTE: Follow these instructions to use nginx. To use Apache or lighttpd, scroll
to their sections.
For nginx, use a configuration like this:
name=nginx.conf
server {
server_name phabricator.example.com;
root /path/to/phabricator/webroot;
location / {
index index.php;
rewrite ^/(.*)$ /index.php?__path__=/$1 last;
}
location = /favicon.ico {
try_files $uri =204;
}
location /index.php {
fastcgi_pass localhost:9000;
fastcgi_index index.php;
#required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;
#variables to make the $_SERVER populate in PHP
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
}
}
Restart nginx after making your edits, then continue to "Setup" below.
= Webserver: Configuring lighttpd =
NOTE: Follow these instructions to use lighttpd. To use Apache or niginx, scroll
up to their sections.
For lighttpd, add a section like this to your lighttpd.conf:
$HTTP["host"] =~ "phabricator(\.example\.com)?" {
server.document-root = "/path/to/phabricator/webroot"
url.rewrite-once = (
"^(/rsrc/.*)$" => "$1",
"^(/favicon.ico)$" => "$1",
# This simulates QSA ("query string append") mode in apache
"^(/[^?]*)\?(.*)" => "/index.php?__path__=$1&$2",
"^(/.*)$" => "/index.php?__path__=$1",
)
}
You should also ensure the following modules are listed in your
server.modules list:
mod_fastcgi
mod_rewrite
Finally, you should run the following commands to enable php support:
$ sudo apt-get install php5-cgi # for ubuntu; other distros should be similar
$ sudo lighty-enable-mod fastcgi-php
Restart lighttpd after making your edits, then continue below.
= Setup =
Now, navigate to whichever subdomain you set up. You should see instructions to
continue setup. The rest of this document contains additional instructions for
specific setup steps.
When you resolve any issues and see the welcome screen, enter credentials to
create your initial administrator account. After you log in, you'll want to
configure how other users will be able to log in or register -- until you do,
no one else will be able to sign up or log in. For more information, see
@{article:Configuring Accounts and Registration}.
= Storage: Configuring MySQL =
During setup, you'll need to configure MySQL. To do this, get MySQL running and
verify you can connect to it. Consult the MySQL documentation for help. When
MySQL works, you need to load the Phabricator schemata into it. To do this, run:
phabricator/ $ ./bin/storage upgrade
If your configuration uses an unprivileged user to connect to the database, you
may have to override the default user so the schema changes can be applied with
root or some other admin user:
phabricator/ $ ./bin/storage upgrade --user --password
-You can avoid the prompt the script issues by passing the ##--force## flag (for
+You can avoid the prompt the script issues by passing the `--force` flag (for
example, if you are scripting the upgrade process).
phabricator/ $ ./bin/storage upgrade --force
NOTE: When you update Phabricator, run `storage upgrade` again to apply any
new updates.
= Next Steps =
Continue by:
- setting up your admin account and login/registration with
@{article:Configuring Accounts and Registration}; or
- understanding advanced configuration topics with
@{article:Configuration User Guide: Advanced Configuration}; or
- configuring an alternate file domain with
@{article:Configuring a File Domain}; or
- configuring a preamble script to set up the environment properly behind a
load balancer, or adjust rate limiting with
@{article:Configuring a Preamble Script}; or
- configuring where uploaded files and attachments will be stored with
@{article:Configuring File Storage}; or
- configuring Phabricator so it can send mail with
@{article:Configuring Outbound Email}; or
- configuring inbound mail with @{article:Configuring Inbound Email}; or
- importing repositories with @{article:Diffusion User Guide}; or
- learning about daemons with @{article:Managing Daemons with phd}; or
- learning about notification with
@{article:Notifications User Guide: Setup and Configuration}; or
- configuring backups with
@{article:Configuring Backups and Performing Migrations}; or
- contributing to Phabricator with @{article:Contributor Introduction}.
diff --git a/src/docs/user/configuration/configuring_outbound_email.diviner b/src/docs/user/configuration/configuring_outbound_email.diviner
index 77d09f7cf2..55c51837c7 100644
--- a/src/docs/user/configuration/configuring_outbound_email.diviner
+++ b/src/docs/user/configuration/configuring_outbound_email.diviner
@@ -1,200 +1,200 @@
@title Configuring Outbound Email
@group config
Instructions for configuring Phabricator to send mail.
= Overview =
Phabricator can send outbound email via several different providers, called
"Adapters".
| Send Mail With | Setup | Cost | Inbound | Notes |
|---------|-------|------|---------|-------|
| Mailgun | Easy | Cheap | Yes | Recommended |
| Amazon SES | Easy | Cheap | No | Recommended |
| SendGrid | Medium | Cheap | Yes | Discouraged (See Note) |
| External SMTP | Medium | Varies | No | Gmail, etc. |
| Local SMTP | Hard | Free | No | (Default) sendmail, postfix, etc |
| Custom | Hard | Free | No | Write an adapter for some other service. |
| Drop in a Hole | Easy | Free | No | Drops mail in a deep, dark hole. |
Of these options, sending mail via local SMTP is the default, but usually
requires some configuration to get working. See below for details on how to
select and configure a delivery method.
Overall, Mailgun and SES are much easier to set up, and using one of them is
recommended. In particular, Mailgun will also let you set up inbound email
easily.
If you have some internal mail service you'd like to use you can also
write a custom adapter, but this requires digging into the code.
Phabricator sends mail in the background, so the daemons need to be running for
it to be able to deliver mail. You should receive setup warnings if they are
not. For more information on using daemons, see
@{article:Managing Daemons with phd}.
**Note on SendGrid**: Users have experienced a number of odd issues with
SendGrid, compared to fewer issues with other mailers. We discourage SendGrid
unless you're already using it. If you send to SendGrid via SMTP, you may need
to adjust `phpmailer.smtp-encoding`.
= Basics =
Regardless of how outbound email is delivered, you should configure these keys
in your configuration:
- **metamta.default-address** determines where mail is sent "From" by
default. If your domain is `example.org`, set this to something like
`noreply@example.org`.
- **metamta.domain** should be set to your domain, e.g. `example.org`.
- - **metamta.can-send-as-user** should be left as ##false## in most cases,
+ - **metamta.can-send-as-user** should be left as `false` in most cases,
but see the documentation for details.
= Configuring Mail Adapters =
To choose how mail will be sent, change the `metamta.mail-adapter` key in
your configuration. Possible values are listed in the UI:
- - ##PhabricatorMailImplementationAmazonMailgunAdapter##: use Mailgun, see
+ - `PhabricatorMailImplementationAmazonMailgunAdapter`: use Mailgun, see
"Adapter: Mailgun".
- - ##PhabricatorMailImplementationAmazonSESAdapter##: use Amazon SES, see
+ - `PhabricatorMailImplementationAmazonSESAdapter`: use Amazon SES, see
"Adapter: Amazon SES".
- - ##PhabricatorMailImplementationPHPMailerLiteAdapter##: default, uses
+ - `PhabricatorMailImplementationPHPMailerLiteAdapter`: default, uses
"sendmail", see "Adapter: Sendmail".
- - ##PhabricatorMailImplementationPHPMailerAdapter##: uses SMTP, see
+ - `PhabricatorMailImplementationPHPMailerAdapter`: uses SMTP, see
"Adapter: SMTP".
- - ##PhabricatorMailImplementationSendGridAdapter##: use SendGrid, see
+ - `PhabricatorMailImplementationSendGridAdapter`: use SendGrid, see
"Adapter: SendGrid".
- - ##Some Custom Class You Write##: use a custom adapter you write, see
+ - `Some Custom Class You Write`: use a custom adapter you write, see
"Adapter: Custom".
- - ##PhabricatorMailImplementationTestAdapter##: this will
+ - `PhabricatorMailImplementationTestAdapter`: this will
**completely disable** outbound mail. You can use this if you don't want to
send outbound mail, or want to skip this step for now and configure it
later.
= Adapter: Sendmail =
This is the default, and selected by choosing
`PhabricatorMailImplementationPHPMailerLiteAdapter` as the value for
**metamta.mail-adapter**. This requires a `sendmail` binary to be installed on
the system. Most MTAs (e.g., sendmail, qmail, postfix) should do this, but your
machine may not have one installed by default. For install instructions, consult
the documentation for your favorite MTA.
Since you'll be sending the mail yourself, you are subject to things like SPF
rules, blackholes, and MTA configuration which are beyond the scope of this
document. If you can already send outbound email from the command line or know
how to configure it, this option is straightforward. If you have no idea how to
do any of this, strongly consider using Mailgun or Amazon SES instead.
If you experience issues with mail getting mangled (for example, arriving with
too many or too few newlines) you may try adjusting `phpmailer.smtp-encoding`.
= Adapter: SMTP =
You can use this adapter to send mail via an external SMTP server, like Gmail.
To do this, set these configuration keys:
- **metamta.mail-adapter**: set to
`PhabricatorMailImplementationPHPMailerAdapter`.
- **phpmailer.mailer**: set to `smtp`.
- **phpmailer.smtp-host**: set to hostname of your SMTP server.
- **phpmailer.smtp-port**: set to port of your SMTP server.
- **phpmailer.smtp-user**: set to your username used for authentication.
- **phpmailer.smtp-password**: set to your password used for authentication.
- **phpmailer.smtp-protocol**: set to `tls` or `ssl` if necessary. Use
`ssl` for Gmail.
- **phpmailer.smtp-encoding**: Normally safe to leave as the default, but
adjusting it may help resolve mail mangling issues (for example, mail
arriving with too many or too few newlines).
= Adapter: Mailgun =
Mailgun is an email delivery service. You can learn more at
. Mailgun isn't free, but is very easy to configure
and works well.
To use Mailgun, sign up for an account, then set these configuration keys:
- **metamta.mail-adapter**: set to
`PhabricatorMailImplementationMailgunAdapter`.
- **mailgun.api-key**: set to your Mailgun API key.
- **mailgun.domain**: set to your Mailgun domain.
= Adapter: Amazon SES =
Amazon SES is Amazon's cloud email service. It is not free, but is easier to
configure than sendmail and can simplify outbound email configuration. To use
Amazon SES, you need to sign up for an account with Amazon at
.
To configure Phabricator to use Amazon SES, set these configuration keys:
- **metamta.mail-adapter**: set to
"PhabricatorMailImplementationAmazonSESAdapter".
- **amazon-ses.access-key**: set to your Amazon SES access key.
- **amazon-ses.secret-key**: set to your Amazon SES secret key.
NOTE: Amazon SES **requires you to verify your "From" address**. Configure which
-"From" address to use by setting "##metamta.default-address##" in your config,
+"From" address to use by setting "`metamta.default-address`" in your config,
then follow the Amazon SES verification process to verify it. You won't be able
to send email until you do this!
= Adapter: SendGrid =
SendGrid is an email delivery service like Amazon SES. You can learn more at
. It is easy to configure, but not free.
You can configure SendGrid in two ways: you can send via SMTP or via the REST
-API. To use SMTP, just configure ##sendmail## and leave Phabricator's setup
+API. To use SMTP, just configure `sendmail` and leave Phabricator's setup
with defaults. To use the REST API, follow the instructions in this section.
To configure Phabricator to use SendGrid, set these configuration keys:
- **metamta.mail-adapter**: set to
"PhabricatorMailImplementationSendGridAdapter".
- **sendgrid.api-user**: set to your SendGrid login name.
- **sendgrid.api-key**: set to your SendGrid password.
If you're logged into your SendGrid account, you may be able to find this
information easily by visiting .
= Adapter: Custom =
You can provide a custom adapter by writing a concrete subclass of
@{class:PhabricatorMailImplementationAdapter} and setting it as the
`metamta.mail-adapter`.
TODO: This should be better documented once extending Phabricator is better
documented.
= Adapter: Disable Outbound Mail =
You can use the @{class:PhabricatorMailImplementationTestAdapter} to completely
disable outbound mail, if you don't want to send mail or don't want to configure
it yet. Just set **metamta.mail-adapter** to
`PhabricatorMailImplementationTestAdapter`.
= Testing and Debugging Outbound Email =
You can use the `bin/mail` utility to test, debug, and examine outbound mail. In
particular:
phabricator/ $ ./bin/mail list-outbound # List outbound mail.
phabricator/ $ ./bin/mail show-outbound # Show details about messages.
phabricator/ $ ./bin/mail send-test # Send test messages.
Run `bin/mail help ` for more help on using these commands.
-You can monitor daemons using the Daemon Console (##/daemon/##, or click
+You can monitor daemons using the Daemon Console (`/daemon/`, or click
**Daemon Console** from the homepage).
= Next Steps =
Continue by:
- @{article:Configuring Inbound Email} so users can reply to email they
receive about revisions and tasks to interact with them; or
- learning about daemons with @{article:Managing Daemons with phd}; or
- returning to the @{article:Configuration Guide}.
diff --git a/src/docs/user/configuration/managing_daemons.diviner b/src/docs/user/configuration/managing_daemons.diviner
index 375c1e80d2..36187ca30f 100644
--- a/src/docs/user/configuration/managing_daemons.diviner
+++ b/src/docs/user/configuration/managing_daemons.diviner
@@ -1,140 +1,140 @@
@title Managing Daemons with phd
@group config
-Explains Phabricator daemons and the daemon control program ##phd##.
+Explains Phabricator daemons and the daemon control program `phd`.
= Overview =
Phabricator uses daemons (background processing scripts) to handle a number of
tasks:
- tracking repositories, discovering new commits, and importing and parsing
commits;
- sending email; and
- collecting garbage, like old logs and caches.
Daemons are started and stopped with **phd** (the **Ph**abricator **D**aemon
launcher). Daemons can be monitored via a web console.
You do not need to run daemons for most parts of Phabricator to work, but some
features (principally, repository tracking with Diffusion) require them and
several features will benefit in performance or stability if you configure
daemons.
= phd =
-**phd** is a command-line script (located at ##phabricator/bin/phd##). To get
-a list of commands, run ##phd help##:
+**phd** is a command-line script (located at `phabricator/bin/phd`). To get
+a list of commands, run `phd help`:
phabricator/ $ ./bin/phd help
NAME
phd - phabricator daemon launcher
...
Generally, you will use:
- **phd start** to launch all daemons;
- **phd restart** to restart all daemons;
- **phd status** to get a list of running daemons; and
- **phd stop** to stop all daemons.
If you want finer-grained control, you can use:
- **phd launch** to launch individual daemons; and
- **phd debug** to debug problems with daemons.
NOTE: When you upgrade Phabricator or change configuration, you should restart
the daemons by running `phd restart`.
= Daemon Console =
You can view status and debugging information for daemons in the Daemon Console
-via the web interface. Go to ##/daemon/## in your install or click
+via the web interface. Go to `/daemon/` in your install or click
**Daemon Console** from "More Stuff".
The Daemon Console shows a list of all the daemons that have ever launched, and
allows you to view log information for them. If you have issues with daemons,
you may be able to find error information that will help you resolve the problem
in the console.
NOTE: The easiest way to figure out what's wrong with a daemon is usually to use
**phd debug** to launch it instead of **phd start**. This will run it without
daemonizing it, so you can see output in your console.
= Available Daemons =
You can get a list of launchable daemons with **phd list**:
- **libphutil test daemons** are not generally useful unless you are
developing daemon infrastructure or debugging a daemon problem;
- **PhabricatorTaskmasterDaemon** performs work from a task queue;
- **PhabricatorRepositoryPullLocalDaemon** daemons track repositories, for
more information see @{article:Diffusion User Guide}; and
- **PhabricatorTriggerDaemon** schedules event triggers and cleans up old
logs and caches.
= Debugging and Tuning =
In most cases, **phd start** handles launching all the daemons you need.
However, you may want to use more granular daemon controls to debug daemons,
launch custom daemons, or launch special daemons like the IRC bot.
To debug a daemon, use `phd debug`:
phabricator/bin/ $ ./phd debug
You can pass arguments like this (normal arguments are passed to the daemon
control mechanism, not to the daemon itself):
phabricator/bin/ $ ./phd debug -- --flavor apple
In debug mode, daemons do not daemonize, and they print additional debugging
output to the console. This should make it easier to debug problems. You can
terminate the daemon with `^C`.
To launch a nonstandard daemon, use `phd launch`:
phabricator/bin/ $ ./phd launch
This daemon will daemonize and run normally.
== General Tips ==
- You can set the maximum number of taskmasters that will run at once
by adjusting `phd.taskmasters`. If you have a task backlog, try increasing
it.
- When you `phd launch` or `phd debug` a daemon, you can type any unique
substring of its name, so `phd launch pull` will work correctly.
- `phd stop` and `phd restart` stop **all** of the daemons on the machine, not
just those started with `phd start`. If you're writing a restart script,
have it launch any custom daemons explicitly after `phd restart`.
- You can write your own daemons and manage them with `phd` by extending
@{class:PhabricatorDaemon}. See @{article:libphutil Libraries User Guide}.
- See @{article:Diffusion User Guide} for details about tuning the repository
daemon.
== Multiple Machines ==
If you have multiple machines, you should use `phd launch` to tweak which
daemons launch, and split daemons across machines like this:
- `PhabricatorRepositoryPullLocalDaemon`: Run one copy on any machine.
On each web frontend which is not running a normal copy, run a copy
with the `--no-discovery` flag.
- `PhabricatorTriggerDaemon`: Run one copy on any machine.
- `PhabricatorTaskmasterDaemon`: Run as many copies as you need to keep
tasks from backing up. You can run them all on one machine or split them
across machines.
A gratuitously wasteful install might have a dedicated daemon machine which
runs `phd start` with a large pool of taskmasters set in the config, and then
runs `phd launch PhabricatorRepositoryPullLocalDaemon -- --no-discovery` on each
web server. This is grossly excessive in normal cases.
= Next Steps =
Continue by:
- learning about the repository daemon with @{article:Diffusion User Guide};
or
- writing your own daemons with @{article:libphutil Libraries User Guide}.
diff --git a/src/docs/user/userguide/arcanist.diviner b/src/docs/user/userguide/arcanist.diviner
index 70f4f5d311..734c41f6e6 100644
--- a/src/docs/user/userguide/arcanist.diviner
+++ b/src/docs/user/userguide/arcanist.diviner
@@ -1,183 +1,183 @@
@title Arcanist User Guide
@group userguide
Guide to Arcanist, a command-line interface to Phabricator.
Arcanists provides command-line access to many Phabricator tools (like
Differential, Files, and Paste), integrates with static analysis ("lint") and
unit tests, and manages common workflows like getting changes into Differential
for review.
-A detailed command reference is available by running ##arc help##. This
+A detailed command reference is available by running `arc help`. This
document provides an overview of common workflows and installation.
Arcanist has technical, contributor-focused documentation here:
= Quick Start =
A quick start guide is available at @{article:Arcanist Quick Start}. It provides
a much more compact summary of how to get `arc` set up and running for a new
project. You may want to start there, and return here if you need more
information.
= Overview =
Arcanist is a wrapper script that sits on top of other tools (e.g.,
Differential, linters, unit test frameworks, git, Mercurial, and SVN) and
provides a simple command-line API to manage code review and some related
revision control operations.
For a detailed list of all available commands, run:
$ arc help
For detailed information about a specific command, run:
$ arc help
Arcanist allows you to do things like:
- - get detailed help about available commands with ##arc help##
- - send your code to Differential for review with ##arc diff## (for detailed
+ - get detailed help about available commands with `arc help`
+ - send your code to Differential for review with `arc diff` (for detailed
instructions, see @{article:Arcanist User Guide: arc diff})
- - show pending revision information with ##arc list##
- - find likely reviewers for a change with ##arc cover##
- - apply changes in a revision to the working copy with ##arc patch##
- - download a patch from Differential with ##arc export##
- - update Git commit messages after review with ##arc amend##
- - commit SVN changes with ##arc commit##
- - push Git and Mercurial changes with ##arc land##
- - view enhanced information about Git branches with ##arc branch##
+ - show pending revision information with `arc list`
+ - find likely reviewers for a change with `arc cover`
+ - apply changes in a revision to the working copy with `arc patch`
+ - download a patch from Differential with `arc export`
+ - update Git commit messages after review with `arc amend`
+ - commit SVN changes with `arc commit`
+ - push Git and Mercurial changes with `arc land`
+ - view enhanced information about Git branches with `arc branch`
Once you've configured lint and unit test integration, you can also:
- - check your code for syntax and style errors with ##arc lint##
+ - check your code for syntax and style errors with `arc lint`
(see @{article:Arcanist User Guide: Lint})
- - run unit tests that cover your changes with ##arc unit##
+ - run unit tests that cover your changes with `arc unit`
Arcanist integrates with other tools:
- - upload and download files with ##arc upload## and ##arc download##
- - create and view pastes with ##arc paste##
+ - upload and download files with `arc upload` and `arc download`
+ - create and view pastes with `arc paste`
Arcanist has some advanced features as well, you can:
- - execute Conduit method calls with ##arc call-conduit##
- - create or update libphutil libraries with ##arc liberate##
- - activate tab completion with ##arc shell-complete##
+ - execute Conduit method calls with `arc call-conduit`
+ - create or update libphutil libraries with `arc liberate`
+ - activate tab completion with `arc shell-complete`
- ...or extend Arcanist and add new commands.
Except where otherwise noted, these workflows are generally agnostic to the
underlying version control system and will work properly in git, Mercurial, or
SVN repositories.
= Installing Arcanist =
Arcanist is meant to be installed on your local machine or development server --
whatever machine you're editing code on. It runs on:
- Linux;
- Other operating systems which are pretty similar to Linux, or which
Linux is pretty similar to;
- FreeBSD, a fine operating system held in great esteem by many;
- Mac OS X (see @{article:Arcanist User Guide: Mac OS X}); and
- Windows (see @{article:Arcanist User Guide: Windows}).
Arcanist is written in PHP, so you need to install the PHP CLI first if you
don't already have it. Arcanist should run on PHP 5.2 and newer. If you don't
have PHP installed, you can download it from .
To install Arcanist, pick an install directory and clone the code from GitHub:
some_install_path/ $ git clone https://github.com/phacility/libphutil.git
some_install_path/ $ git clone https://github.com/phacility/arcanist.git
This should leave you with a directory structure like this
some_install_path/ # Wherever you chose to install it.
arcanist/ # Arcanist-specific code and libraries.
libphutil/ # A shared library Arcanist depends upon.
-Now add ##some_install_path/arcanist/bin/## to your PATH environment variable.
+Now add `some_install_path/arcanist/bin/` to your PATH environment variable.
When you type "arc", you should see something like this:
Usage Exception: No command provided. Try 'arc help'.
If you get that far, you've done things correctly. If you get an error or have
trouble getting this far, see these detailed guides:
- On Windows: @{article:Arcanist User Guide: Windows}
- On Mac OS X: @{article:Arcanist User Guide: Mac OS X}
You can later upgrade Arcanist and libphutil to the latest versions with
`arc upgrade`:
$ arc upgrade
== Installing Arcanist for a Team ==
Arcanist changes quickly, so it can be something of a headache to get it
installed and keep people up to date. Here are some approaches you might be
able to use:
- Facebook does most development on development servers, which have a standard
environment and NFS mounts. Arcanist and libphutil themselves live on an
NFS mount, and the default `.bashrc` adds them to the PATH. Updating the
mount source updates everyone's versions, and new employees have a working
`arc` when they first log in.
- Another common approach is to write an install script as an action into
existing build scripts, so users can run `make install-arc` or
`ant install-arc` or similar.
- In general, if this sucks and is causing you pain, let us know (see
@{article:Give Feedback! Get Support!}). We're planning to improve this at
some point, but it's somewhat complicated to get right. While it can take a
little time to set up, we aren't getting feedback that it's a persistent
pain point, so it hasn't been a priority.
== Installing Tab Completion ==
-If you use ##bash##, you can set up tab completion by adding something like this
-to your ##.bashrc##, ##.profile## or similar:
+If you use `bash`, you can set up tab completion by adding something like this
+to your `.bashrc`, `.profile` or similar:
source /path/to/arcanist/resources/shell/bash-completion
== Configuration ==
Some Arcanist commands can be configured. This configuration is read from
several sources:
# A customization can force any setting with
@{method@arcanist:ArcanistWorkingCopyIdentity::setRuntimeConfig}.
# User can specify settings for working copy in `arc/config` file located in
VCS directory (e.g. `.git/arc/config`) in JSON format. This file can also be
modified by running `arc set-config --local`.
# Repository can specify its config in `.arcconfig` in JSON format.
# User can specify the settings in `~/.arcrc` (JSON) through the `config` key.
This file can be modified also by `arc set-config --global`.
# Machine can specify the settings with `/etc/arcconfig` (JSON). On Windows,
the file path is `C:\ProgramData\Phabricator\Arcanist\config`.
The first place where the setting is defined wins.
Existing settings can be printed with `arc get-config`.
== Next Steps ==
Continue by:
- setting up a new project for use with `arc`, with
@{article:Arcanist User Guide: Configuring a New Project}; or
- learning how to use `arc` to send changes for review with
@{article:Arcanist User Guide: arc diff}.
Advanced topics are also available. These are detailed guides to configuring
technical features of `arc` that refine its behavior. You do not need to read
them to get it working.
- @{article:Arcanist User Guide: Commit Ranges}
- @{article:Arcanist User Guide: Lint}
- @{article:Arcanist User Guide: Customizing Existing Linters}
- @{article:Arcanist User Guide: Customizing Lint, Unit Tests and Workflows}
- @{article:Arcanist User Guide: Code Coverage}
- @{article:Arcanist User Guide: Repository Hooks}
diff --git a/src/docs/user/userguide/arcanist_coverage.diviner b/src/docs/user/userguide/arcanist_coverage.diviner
index af7d29d373..a734e5dd80 100644
--- a/src/docs/user/userguide/arcanist_coverage.diviner
+++ b/src/docs/user/userguide/arcanist_coverage.diviner
@@ -1,69 +1,69 @@
@title Arcanist User Guide: Code Coverage
@group userguide
Explains code coverage features in Arcanist and Phabricator.
This is a configuration guide that helps you set up advanced features. If you're
just getting started, you don't need to look at this yet. Instead, start with
the @{article:Arcanist User Guide}.
Before you can configure coverage features, you must set up unit test
integration. For instructions, see @{article:Arcanist User Guide: Configuring
a New Project} and @{article:Arcanist User Guide: Customizing
Lint, Unit Tests and Workflows}.
= Using Coverage Features =
If your project has unit tests with coverage integration (see below for
instructions on setting it up), you can use "arc" to show coverage reports.
For example:
arc unit --detailed-coverage src/some/file.php
Depending on how your test engine is configured, this will run tests relevant
-to ##src/some/file.php## and give you a detailed coverage report.
+to `src/some/file.php` and give you a detailed coverage report.
If the test engine enables coverage by default, it will be uploaded to
Differential and displayed in the right gutter when viewing diffs.
= Enabling Coverage for libphutil, Arcanist and Phabricator =
If you're contributing, libphutil, Arcanist and Phabricator support coverage if
you install Xdebug:
http://xdebug.org/
It should be sufficient to correctly install Xdebug; coverage information will
be automatically enabled.
= Building Coverage Support =
-To add coverage support to a unit test engine, just call ##setCoverage()## when
+To add coverage support to a unit test engine, just call `setCoverage()` when
building @{class@arcanist:ArcanistUnitTestResult} objects. Provide a map of
file names (relative to the working copy root) to coverage report strings.
Coverage report strings look like this:
NNNNNCCCNNNNNNNNCCCCCCNNNUUUNNNNN
Each line in the file is represented by a character. Valid characters are:
- **N** Not executable. This is a comment or whitespace which should be
ignored when computing test coverage.
- **C** Covered. This line has test coverage.
- **U** Uncovered. This line is executable but has no test coverage.
- **X** Unreachable. If your coverage analysis can detect unreachable code,
you can report it here.
This format is intended to be as simple as possible. A valid coverage result
might look like this:
array(
'src/example.php' => 'NNCNNNCNUNNNUNUNUNUNUNC',
'src/other.php' => 'NNUNNNUNCNNNUNUNCNCNCNU',
);
You may also want to filter coverage information to the paths passed to the
unit test engine. See @{class@arcanist:PhutilTestCase} and
@{class@arcanist:PhutilUnitTestEngine} for an example of coverage integration
in PHP using Xdebug.
diff --git a/src/docs/user/userguide/arcanist_extending_lint.diviner b/src/docs/user/userguide/arcanist_extending_lint.diviner
index d89c2c98fe..481ab14a4d 100644
--- a/src/docs/user/userguide/arcanist_extending_lint.diviner
+++ b/src/docs/user/userguide/arcanist_extending_lint.diviner
@@ -1,102 +1,102 @@
@title Arcanist User Guide: Customizing Existing Linters
@group userguide
Explains how to customize existing linters.
This is a configuration guide that helps you set up advanced features. If you're
just getting started, you don't need to look at this yet. Instead, start with
the @{article:Arcanist User Guide}.
This guide explains how to refine lint behavior. To configure lint in the first
place, see @{article:Arcanist User Guide: Configuring a New Project} and
@{article:Arcanist User Guide: Lint}.
= Overview =
Arcanist ships with a number of linters which you may want to reuse in whole
or in part in other projects. This document explains how to customize existing
linters for use in new engines.
First, you should set up an engine by following the instructions in
@{article:Arcanist User Guide: Lint} and possibly
@{article:Arcanist User Guide: Customizing Lint, Unit Tests and Workflows}.
Then, follow this guide to customize linters.
= General Guidelines =
You should customize linters by configuring or composing them, not by extending
them -- their implementations are not necessarily stable. If a linter's
configuration options aren't flexible enough to meet your needs, sending a patch
which improves its configurability is better than one that makes it
nonfinal.
= Changing Rule Severities =
By default, most linters raise lint messages as errors. You may want to reduce
the severity of some messages (e.g., reduce errors to warnings). Do this by
-calling ##setCustomSeverityMap()##:
+calling `setCustomSeverityMap()`:
$linter = new ArcanistTextLinter();
// Change "missing newline at end of file" message from error to warning.
$linter->setCustomSeverityMap(
array(
ArcanistTextLinter::LINT_EOF_NEWLINE
=> ArcanistLintSeverity::SEVERITY_WARNING,
));
See @{class@arcanist:ArcanistLintSeverity} for a list of available severity
constants.
= Disabling Rules =
-To disable rules entirely, set their severities to ##SEVERITY_DISABLED##:
+To disable rules entirely, set their severities to `SEVERITY_DISABLED`:
$linter = new ArcanistTextLinter();
// Disable "Tab Literal" message.
$linter->setCustomSeverityMap(
array(
ArcanistTextLinter::LINT_TAB_LITERAL
=> ArcanistLintSeverity::SEVERITY_DISABLED,
));
= Running Multiple Rulesets =
If you want to run the same linter on different types of files but vary the
configuration based on the file type, just instantiate it twice and configure
each instance appropriately. For instance, this will enforce different column
widths on different languages:
$linters = array();
// Warn on JS/CSS lines longer than 80 columns.
$linters['TextLinter80Col'] = id(new ArcanistTextLinter())
->setPaths(preg_grep('/\.(js|css)$/', $paths));
// Warn on Java lines longer than 120 columns.
$linters['TextLinter120Col'] = id(new ArcanistTextLinter())
->setMaxLineLength(120)
->setPaths(preg_grep('/\.(java)$/', $paths));
// ...
return $linters;
= Customizing Specific Linters =
Some linters are specifically customizable or configurable. Some common
options are documented here, consult class documentation for complete
information.
== ArcanistTextLinter ==
- - Use ##setMaxLineLength()## to change the 80-column warning to something
+ - Use `setMaxLineLength()` to change the 80-column warning to something
else.
== ArcanistXHPASTLinter ==
- - Use ##lint.xhpast.naminghook## in ##.arcconfig## to override naming
+ - Use `lint.xhpast.naminghook` in `.arcconfig` to override naming
convention rules. See @{class@arcanist:ArcanistXHPASTLintNamingHook}
for details.
- - Use ##getXHPASTTreeForPath()## to reuse the AAST in other linters.
+ - Use `getXHPASTTreeForPath()` to reuse the AAST in other linters.
diff --git a/src/docs/user/userguide/arcanist_lint_unit.diviner b/src/docs/user/userguide/arcanist_lint_unit.diviner
index e738c42ffb..e425911fae 100644
--- a/src/docs/user/userguide/arcanist_lint_unit.diviner
+++ b/src/docs/user/userguide/arcanist_lint_unit.diviner
@@ -1,92 +1,92 @@
@title Arcanist User Guide: Customizing Lint, Unit Tests and Workflows
@group userguide
Explains how to build new classes to control how Arcanist behaves.
This is a configuration guide that helps you set up advanced features. If you're
just getting started, you don't need to look at this yet. Instead, start with
the @{article:Arcanist User Guide}.
= Overview =
Arcanist has some basic configuration options available in the `.arcconfig`
file (see @{article:Arcanist User Guide: Configuring a New Project}), but it
can't handle everything. If you want to customize Arcanist at a deeper level,
you need to build new classes. For instance:
- if you want to configure linters, or add new linters, you need to create a
new class which extends @{class@arcanist:ArcanistLintEngine}.
- if you want to integrate with a unit testing framework, you need to create a
new class which extends @{class@arcanist:ArcanistUnitTestEngine}.
- if you you want to change how workflows behave, or add new workflows, you
need to create a new class which extends
@{class@arcanist:ArcanistConfiguration}.
Arcanist works through a sort of dependency-injection approach. For example,
Arcanist does not run lint rules by default, but you can set `lint.engine`
in your `.arcconfig` to the name of a class which extends
@{class@arcanist:ArcanistLintEngine}. When running from inside your project,
Arcanist will load this class and call methods on it in order to run lint. To
make this work, you need to do three things:
- actually write the class;
- - add the library where the class exists to your ##.arcconfig##;
- - add the class name to your ##.arcconfig## as the **lint.engine**,
+ - add the library where the class exists to your `.arcconfig`;
+ - add the class name to your `.arcconfig` as the **lint.engine**,
**unit.engine**, or **arcanist_configuration**.
= Create a libphutil Library =
If you haven't created a library for the class to live in yet, you need to do
that first. Follow the instructions in
@{article:libphutil Libraries User Guide}, then make the library loadable by
-adding it to your ##.arcconfig## like this:
+adding it to your `.arcconfig` like this:
{
// ...
"load" : [
// ...
"/path/to/my/library", // Absolute path
"support/arcanist", // Relative path in this project
// ...
]
// ...
}
You can either specify an absolute path, or a path relative to the project root.
-When you run ##arc list --trace##, you should see a message to the effect that
+When you run `arc list --trace`, you should see a message to the effect that
it has loaded your library.
For debugging or testing, you can also run Arcanist with the
`--load-phutil-library` flag:
arc --load-phutil-library=/path/to/library
You can specify this flag more than once to load several libraries. Note that
if you use this flag, Arcanist will ignore any libraries listed in
`.arcconfig`.
= Use the Class =
-This step is easy: just edit ##.arcconfig## to specify your class name as
+This step is easy: just edit `.arcconfig` to specify your class name as
the appropriate configuration value.
{
// ...
"lint.engine" : "CustomArcanistLintEngine",
// ...
}
Now, when you run Arcanist in your project, it will invoke your class when
appropriate.
-For lint and unit tests, you can also use the ##--engine## flag override the
+For lint and unit tests, you can also use the `--engine` flag override the
default engine:
arc lint --engine MyCustomArcanistLintEngine
This is mostly useful for debugging and testing.
= Next Steps =
- Learn how to reuse existing linters by reading
@{article:Arcanist User Guide: Customizing Existing Linters}.
diff --git a/src/docs/user/userguide/arcanist_new_project.diviner b/src/docs/user/userguide/arcanist_new_project.diviner
index 448d3ea947..cc65344f0f 100644
--- a/src/docs/user/userguide/arcanist_new_project.diviner
+++ b/src/docs/user/userguide/arcanist_new_project.diviner
@@ -1,223 +1,223 @@
@title Arcanist User Guide: Configuring a New Project
@group userguide
Explains how to configure Arcanist projects with `.arcconfig` files.
= Overview =
In most cases, you should be able to use `arc` without specifically configuring
your project for it. If you want to adjust `arc` behaviors, you can create a
`.arcconfig` file in your project to provide project-specific settings.
= .arcconfig Basics =
An `.arcconfig` file is a JSON file which you check into your project's root.
Arcanist uses `.arcconfig` files to customize a number of things about its
behavior. The first thing you're likely to want to configure is the URI
for your Phabricator install. A simple, valid file looks something like this:
name=.arcconfig
{
"phabricator.uri" : "https://phabricator.example.com/"
}
For details on available options, see below.
NOTE: You should commit your `.arcconfig` file! It contains project
configuration, not user configuration.
= Advanced .arcconfig =
Common options are:
- **phabricator.uri**: the URI for the Phabricator install that `arc` should
connect to when run in this project. This option was previously called
`conduit_uri`.
- **repository.callsign**: The callsign of this repository in Diffusion.
Normally, `arc` can detect this automatically, but if it can't figure it out
you can specify it explicitly. Use `arc which` to understand the detection
process.
- **history.immutable**: Configures `arc` to use workflows which never rewrite
history in the working copy. By default, `arc` will perform some rewriting
of unpublished history (amending commit messages, squash merging) on some
workflows in Git. The distinctions are covered in detail below.
Other options include:
- **load**: list of additional Phutil libraries to load at startup.
See below for details about path resolution, or see
@{article:libphutil Libraries User Guide} for a general introduction to
libphutil libraries.
- **https.cabundle**: specifies the path to an alternate certificate bundle
for use when making HTTPS connections.
- **lint.engine**: the name of a subclass of
@{class@arcanist:ArcanistLintEngine}, which should be used to apply lint
rules to this project. See @{article:Arcanist User Guide: Lint}.
- **unit.engine**: the name of a subclass of
@{class@arcanist:ArcanistUnitTestEngine}, which should be used to apply
unit test rules to this project. See
@{article:Arcanist User Guide: Customizing Lint, Unit Tests and Workflows}.
These options are supported, but their use is discouraged:
- **http.basicauth.user**: specify an HTTP basic auth username for use when
connecting to Phabricator.
- **http.basicauth.pass**: specify an HTTP basic auth password for use when
connecting to Phabricator.
- **https.blindly-trust-domains**: a list of domains to trust blindly over
HTTPS, even if their certificates are invalid. This is a brute force
solution to certificate validity problems, and is discouraged. Instead,
use valid certificates.
For a complete list of options, run `arc get-config`. Although all
options can be set in `.arcconfig`, some options (like `editor`) usually do not
make sense to set here because they're likely to vary from user to user.
= History Mutability =
Arcanist workflows run in two broad modes: either history is //mutable// or
//immutable//. Under a //mutable// history, `arc` commands may rewrite the
working copy history; under an //immutable// history, they may not.
You control history mutability by setting `history.immutable` to `true` or
`false` in your configuration. By default, it is `false` in Git (i.e.,
//mutable//) and `true` in Mercurial (i.e., //immutable//). The sections below
explain how these settings affect workflows.
== History Mutability: Git ==
In a workflow with //mutable// history, you rewrite local history. You develop
-in feature branches, but squash or amend before pushing by using ##git commit
---amend##, ##git rebase -i##, or `git merge --squash`. Generally, one idea in
+in feature branches, but squash or amend before pushing by using `git commit
+--amend`, `git rebase -i`, or `git merge --squash`. Generally, one idea in
the remote is represented by one commit.
In a workflow with //immutable// history, you do not rewrite local history. You
develop in feature branches and push them without squashing commits. You do not
-use ##git commit --amend## or ##git rebase -i##. Generally, one idea in the
+use `git commit --amend` or `git rebase -i`. Generally, one idea in the
remote is represented by many commits.
Practically, these are the differences you'll see based on your setting:
- **Mutable**
- `arc diff` will prompt you to amend lint changes into HEAD.
- `arc diff` will amend the commit message in HEAD after creating a
revision.
- `arc land` will default to the `--squash` strategy.
- `arc amend` will amend the commit message in HEAD with information from
the corresponding or specified Differential revision.
- **Immutable**
- `arc diff` will abort if it makes lint changes.
- `arc diff` will not amend the commit message in HEAD after creating a
revision.
- `arc land` will default to the `--merge` strategy.
- `arc amend` will exit with an error message.
== History Mutability: Mercurial ==
Before version 2.2, stock Mercurial has no history mutation commands, so
this setting has no effect. With Mercurial 2.2. or newer, making history
//mutable// means:
- **Mutable** (versions 2.2 and newer)
- `arc diff` will amend the commit message in `.` after creating a
revision.
- `arc amend` will amend the commit message in `.` with information from
the corresponding or specified Differential revision.
- **Immutable** (or versions prior to 2.2)
- `arc diff` will not amend the commit message in `.` after creating a
revision.
- `arc amend` will exit with an error message.
= How Libraries Are Located =
If you specify an external library to load, like 'examplelib', and use a
relative path like this:
{
...
"load": [
"examplelib/src"
],
...
}
...arc looks for it by trying these paths:
- `path/to/root/examplelib/src/` First, arc looks in the project's root
directory (where the `.arcconfig` lives) to see if the library is part of
the project. This makes it easy to just put project-specific code in a
project.
- `path/to/root/../examplelib/src/` Next, arc looks //next to// the project's
root directory to see if the library is in a sibling directory. If you
work with several repositories, this makes it easy to put all the `arc`
code in one repository and just check it out in the same directory as
everything else.
- `php/include/path/examplelib/src` Finally, arc falls back to PHP, which
will look in paths described in the `include_path` php.ini setting. This
allows you to install libraries in some global location if you prefer.
You can alternately supply an absolute path, like `/var/arc/examplelib/src`, but
then everyone will need to install the library at that exact location.
NOTE: Specify the path to the directory which includes
`__phutil_library_init__.php`. For example, if your init file is in
`examplelib/src/__phutil_library_init__.php`, specify `examplelib/src`,
not just `examplelib/`.
The general intent here is:
- Put project-specific code in some directory in the project, like
`support/arc/src/`.
- Put shared code (e.g., which enforces general coding standards or hooks
up to unit tests or whatever) in a separate repository and check it out
next to other repositories.
- Or put everything in some standard location and add it to `include_path`.
= Running Without .arcconfig =
Although you don't need to set up `.arcconfig`, and you can run `arc` command
that require a working copy in any Git, Subversion or Mercurial working copy,
some features won't work unless you set up an `.arcconfig` file.
Without `.arcconfig`:
- You will need to set a default Phabricator URI with
`arc set-config default `, or specify an explicit URI
with `--conduit-uri` each time you run a command.
- You will not be able to run linters through arc unless you pass `--engine`
explicitly.
- You will not be able to customize certain linter parameters even with
`--engine`.
- You will not be able to run unit tests through arc unless you pass
`--engine` explicitly.
- You will not be able to trigger lint and unit integration through
`arc diff`.
- You will not be able to put Git working copies into immutable history mode
(see below).
- You will not be able to specify a repository encoding. UTF-8 will be assumed
if you do not pass `--encoding`.
- You will not be able to add plugins to arc to modify existing workflows or
add new ones.
- You will not be able to load additional libraries unless you specify them
explicitly with `--load-phutil-library`.
- Symbol index integration, which allows users to click function or class
names in Differential and jump to their definitions, will not work.
- `arc patch` will be unable to detect that you are applying changes to the
wrong project.
- In Subversion, `arc` will be unable to determine the canonical root
of a project, and will assume it is the working directory (in Subversion
prior to 1.7) or the root of the checkout (in Subversion after 1.7). This
means the paths of files in diffs won't be anchored to the same place,
and will have different amounts of path context, which may be confusing for
reviewers and will sometimes prevent patches from applying properly if they
are applied against a different directory than they were generated from.
- In Subversion, `arc` will be unable to guess that you intend to update
an existing revision; you must use `--update` explicitly or `--preview`
and attach diffs via the web interface.
= Next Steps =
Continue by:
- returning to @{article:Arcanist User Guide}.
diff --git a/src/docs/user/userguide/differential_test_plans.diviner b/src/docs/user/userguide/differential_test_plans.diviner
index 81992097a2..469fa0e21e 100644
--- a/src/docs/user/userguide/differential_test_plans.diviner
+++ b/src/docs/user/userguide/differential_test_plans.diviner
@@ -1,65 +1,65 @@
@title Differential User Guide: Test Plans
@group userguide
This document describes things you should think about when developing a test
plan.
= Overview =
When you send a revision for review in Differential you must include a test plan
(this can be disabled or made optional in the config). A test plan is a
repeatable list of steps which document what you have done to verify the
behavior of a change. A good test plan convinces a reviewer that you have been
thorough in making sure your change works as intended and has enough detail to
allow someone unfamiliar with your change to verify its behavior.
This document has some common things to think about when developing or reviewing
a test plan. Some of these suggestions might not be applicable to the software
you are writing; they are adapted from Facebook's internal documentation.
= All Changes =
- **Error Handling:** Are errors detected and handled properly? How does your
change deal with error cases? Did you test them and make sure you got the
right error messages and the right behavior? It's important that you test
what happens when things go wrong, not just that the change works if
everything else also works.
- **Service Impact:** How does your change affect services like memcache,
thrift, or databases? Are you adding new cachekeys or queries? Will
this change add a lot of load to services?
- **Performance:** How does your change affect performance? **NOTE**: If
your change is a performance-motivated change, you should include
measurements, profiles or other data in your test plan proving that you have
improved performance.
- **Unit Tests:** Is your change adequately covered by unit tests? Could you
improve test coverage? If you're fixing a bug, did you add a test to prevent
it from happening again? Are the unit tests testing just the code in
question, or would a failure of a database or network service cause your
test to fail?
- **Concurrent Change Robustness:** If you're making a refactoring change, is
it robust against people introducing new calls between the time you started
the change and when you commit it? For example, if you change the parameter
- order of some function from ##f(a, b)## to ##f(b, a)## and a new callsite is
+ order of some function from `f(a, b)` to `f(b, a)` and a new callsite is
introduced in the meantime, could it go unnoticed? How bad would that be?
(Because of this risk, you should almost never make parameter order
changes in weakly typed languages like PHP and Javascript.)
- **Revert Plan:** If your change needs to be reverted and you aren't around,
are any special steps or considerations that the reverter needs to know
about? If there are, make sure they're adequately described in the "Revert
Plan" field so someone without any knowledge of your patch (but with a
general knowledge of the system) can successfully revert your change.
- **Security:** Is your change robust against XSS, CSRF, and injection
attacks? Are you verifying the user has the right capabilities or
permissions? Are you consistently treating user data as untrustworthy? Are
you escaping data properly, and using dangerous functions only
when they are strictly necessary?
- **Architecture:** Is this the right change? Could there be a better way to
solve the problem? Have you talked to (or added as reviewers) domain experts
if you aren't one yourself? What are the limitations of this solution? What
tradeoffs did you make, and why?
= Frontend / User-Facing Changes =
- **Static Resources:** Will your change cause the application to serve more
JS or CSS? Can you use less JS/CSS, or reuse more?
- **Browsers:** Have you tested your change in multiple browsers?
diff --git a/src/docs/user/userguide/diffusion_symbols.diviner b/src/docs/user/userguide/diffusion_symbols.diviner
index 98dd244ec6..ee0d656226 100644
--- a/src/docs/user/userguide/diffusion_symbols.diviner
+++ b/src/docs/user/userguide/diffusion_symbols.diviner
@@ -1,90 +1,90 @@
@title Diffusion User Guide: Symbol Indexes
@group userguide
Guide to configuring and using the symbol index.
= Overview =
Phabricator can maintain a symbol index, which keeps track of where classes
and functions are defined in the codebase. Once you set up indexing, you can
use the index to do things like:
- link symbol uses in Differential code reviews and Diffusion code browsing
to their definitions
- allow you to search for symbols
- let the IRC bot answer questions like "Where is SomeClass?"
NOTE: Symbol indexing is somewhat new, and has broader support for PHP than for
other languages.
= Populating the Index =
To populate the index, you need to write a script which identifies symbols in
your codebase and set up a cronjob which pipes its output to:
./scripts/symbols/import_repository_symbols.php
Phabricator includes a script which can identify symbols in PHP projects:
./scripts/symbols/generate_php_symbols.php
Phabricator also includes a script which can identify symbols in any
programming language that has classes and/or functions, and is supported by
Exuberant Ctags (http://ctags.sourceforge.net):
./scripts/symbols/generate_ctags_symbols.php
If you want to identify symbols from another language, you need to write a
-script which can export them (for example, maybe by parsing a ##ctags## file).
+script which can export them (for example, maybe by parsing a `ctags` file).
The output format of the script should be one symbol per line:
For example:
ExampleClass exampleMethod function php 13 /src/classes/ExampleClass.php
Context is, broadly speaking, the scope or namespace where the symbol is
defined. For object-oriented languages, this is probably a class name. The
symbols with that context are class constants, methods, properties, nested
classes, etc. When printing symbols without a context (those that are defined
-globally, for instance), the #### field should be empty (that is, the
+globally, for instance), the `` field should be empty (that is, the
line should start with a space).
Your script should enumerate all the symbols in your project, and provide paths
from the project root (where ".arcconfig" is) beginning with a "/".
-You can look at ##generate_php_symbols.php## for an example of how you might
+You can look at `generate_php_symbols.php` for an example of how you might
write such a script, and run this command to see its output:
$ cd phabricator/
$ find . -type f -name '*.php' | ./scripts/symbols/generate_php_symbols.php
To actually build the symbol index, pipe this data to the
`import_repository_symbols.php` script, providing the repository callsign:
$ ./scripts/symbols/import_repository_symbols.php rREPO < symbols_data
Then just set up a cronjob to run that however often you like.
You can test that the import worked by querying for symbols using the Conduit
-method ##diffusion.findsymbols##. Some features (like that method, and the
+method `diffusion.findsymbols`. Some features (like that method, and the
IRC bot integration) will start working immediately. Others will require more
configuration.
= Advanced Configuration =
You can configure some more options by going to {nav Diffusion > (Select
repository) > Edit Repository > Edit Symbols}, and filling out these fields:
- **Indexed Languages**: Fill in all the languages you've built indexes for.
You can leave this blank for "All languages".
- **Uses Symbols From**: If this project depends on other repositories, add
the other repositories which symbols should be looked for here. For example,
Phabricator lists "Arcanist" and "libphutil" because it uses classes and
functions from these repositories.
NOTE: Because this feature depends on the syntax highlighter, it will work
better for some languages than others. It currently works fairly well for PHP,
but your mileage may vary for other languages.
diff --git a/src/docs/user/userguide/events.diviner b/src/docs/user/userguide/events.diviner
index 6568509871..888fe2cf06 100644
--- a/src/docs/user/userguide/events.diviner
+++ b/src/docs/user/userguide/events.diviner
@@ -1,330 +1,330 @@
@title Events User Guide: Installing Event Listeners
@group userguide
Using Phabricator event listeners to customize behavior.
= Overview =
Phabricator and Arcanist allow you to install custom runtime event listeners
which can react to certain things happening (like a Maniphest Task being edited
or a user creating a new Differential Revision) and run custom code to perform
logging, synchronize with other systems, or modify workflows.
These listeners are PHP classes which you install beside Phabricator or
Arcanist, and which Phabricator loads at runtime and runs in-process. They
require somewhat more effort upfront than simple configuration switches, but are
the most direct and powerful way to respond to events.
= Installing Event Listeners (Phabricator) =
To install event listeners in Phabricator, follow these steps:
- Write a listener class which extends @{class@libphutil:PhutilEventListener}.
- Add it to a libphutil library, or create a new library (for instructions,
see @{article:libphutil Libraries User Guide}.
- Configure Phabricator to load the library by adding it to `load-libraries`
in the Phabricator config.
- Configure Phabricator to install the event listener by adding the class
name to `events.listeners` in the Phabricator config.
You can verify your listener is registered in the "Events" tab of DarkConsole.
It should appear at the top under "Registered Event Listeners". You can also
see any events the page emitted there. For details on DarkConsole, see
@{article:Using DarkConsole}.
= Installing Event Listeners (Arcanist) =
To install event listeners in Arcanist, follow these steps:
- Write a listener class which extends @{class@libphutil:PhutilEventListener}.
- Add it to a libphutil library, or create a new library (for instructions,
see @{article:libphutil Libraries User Guide}.
- Configure Phabricator to load the library by adding it to `load`
in the Arcanist config (e.g., `.arcconfig`, or user/global config).
- Configure Arcanist to install the event listener by adding the class
name to `events.listeners` in the Arcanist config.
You can verify your listener is registered by running any `arc` command with
`--trace`. You should see output indicating your class was registered as an
event listener.
= Example Listener =
Phabricator includes an example event listener,
@{class:PhabricatorExampleEventListener}, which may be useful as a starting
point in developing your own listeners. This listener listens for a test
event that is emitted by the script `scripts/util/emit_test_event.php`.
If you run this script normally, it should output something like this:
$ ./scripts/util/emit_test_event.php
Emitting event...
Done.
This is because there are no listeners for the event, so nothing reacts to it
when it is emitted. You can add the example listener by either adding it to
your `events.listeners` configuration or with the `--listen` command-line flag:
$ ./scripts/util/emit_test_event.php --listen PhabricatorExampleEventListener
Installing 'PhabricatorExampleEventListener'...
Emitting event...
PhabricatorExampleEventListener got test event at 1341344566
Done.
This time, the listener was installed and had its callback invoked when the
test event was emitted.
= Available Events =
You can find a list of all Phabricator events in @{class:PhabricatorEventType}.
== All Events ==
The special constant `PhutilEventType::TYPE_ALL` will let you listen for all
events. Normally, you want to listen only to specific events, but if you're
writing a generic handler you can listen to all events with this constant
rather than by enumerating each event.
== Arcanist Events ==
Arcanist event constants are listed in @{class@arcanist:ArcanistEventType}.
All Arcanist events have this data available:
- `workflow` The active @{class@arcanist:ArcanistWorkflow}.
== Arcanist: Commit: Will Commit SVN ==
The constant for this event is `ArcanistEventType::TYPE_COMMIT_WILLCOMMITSVN`.
This event is dispatched before an `svn commit` occurs and allows you to
modify the commit message. Data available on this event:
- `message` The text of the message.
== Arcanist: Diff: Will Build Message ==
The constant for this event is `ArcanistEventType::TYPE_DIFF_WILLBUILDMESSAGE`.
This event is dispatched before an editable message is presented to the user,
and allows you to, e.g., fill in default values for fields. Data available
on this event:
- `fields` A map of field values to be compiled into a message.
== Arcanist: Diff: Was Created ==
The constant for this event is `ArcanistEventType::TYPE_DIFF_WASCREATED`.
This event is dispatched after a diff is created. It is currently only useful
for collecting timing information. No data is available on this event.
== Arcanist: Revision: Will Create Revision ==
The constant for this event is
`ArcanistEventType::TYPE_REVISION_WILLCREATEREVISION`.
This event is dispatched before a revision is created. It allows you to modify
fields to, e.g., edit revision titles. Data available on this event:
- `specification` Parameters that will be used to invoke the
`differential.createrevision` Conduit call.
== Controller: Check Request ==
The constant for this event is
`PhabricatorEventType::TYPE_CONTROLLER_CHECKREQUEST`.
This event is dispatched when controller is about to begin execution. It is
meant for checking if the user is allowed to use the application at the moment.
It can check if the user has performed too many operations recently, if his IP
address is allowed or if the servers are overloaded to process the request.
Data available on this event:
- `request` Object of class @{class:AphrontRequest}.
- `controller` Class name of the current controller.
You can delegate the execution to another controller by modifying `controller`.
== Maniphest: Will Edit Task ==
The constant for this event is
`PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK`.
This event is dispatched before a task is edited, and allows you to respond to
or alter the edit. Data available on this event:
- - ##task## The @{class:ManiphestTask} being edited.
- - ##transactions## The list of edits (objects of class
+ - `task` The @{class:ManiphestTask} being edited.
+ - `transactions` The list of edits (objects of class
@{class:ManiphestTransaction}) being applied.
- - ##new## A boolean indicating if this task is being created.
- - ##mail## If this edit originates from email, the
+ - `new` A boolean indicating if this task is being created.
+ - `mail` If this edit originates from email, the
@{class:PhabricatorMetaMTAReceivedMail} object.
This is similar to the next event (did edit task) but occurs before the edit
begins.
== Maniphest: Did Edit Task ==
The constant for this event is
`PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK`.
This event is dispatched after a task is edited, and allows you to react to the
edit. Data available on this event:
- - ##task## The @{class:ManiphestTask} that was edited.
- - ##transactions## The list of edits (objects of class
+ - `task` The @{class:ManiphestTask} that was edited.
+ - `transactions` The list of edits (objects of class
@{class:ManiphestTransaction}) that were applied.
- - ##new## A boolean indicating if this task was newly created.
- - ##mail## If this edit originates from email, the
+ - `new` A boolean indicating if this task was newly created.
+ - `mail` If this edit originates from email, the
@{class:PhabricatorMetaMTAReceivedMail} object.
This is similar to the previous event (will edit task) but occurs after the
edit completes.
== Differential: Will Mark Generated ==
The constant for this event is
`PhabricatorEventType::TYPE_DIFFERENTIAL_WILLMARKGENERATED`.
This event is dispatched before Differential decides if a file is generated (and
doesn't need to be reviewed) or not. Data available on this event:
- - ##corpus## Body of the file.
- - ##is_generated## Boolean indicating if this file should be treated as
+ - `corpus` Body of the file.
+ - `is_generated` Boolean indicating if this file should be treated as
generated.
== Diffusion: Did Discover Commit ==
The constant for this event is
`PhabricatorEventType::TYPE_DIFFUSION_DIDDISCOVERCOMMIT`.
This event is dispatched when the daemons discover a commit for the first time.
This event happens very early in the pipeline, and not all commit information
will be available yet. Data available on this event:
- `commit` The @{class:PhabricatorRepositoryCommit} that was discovered.
- `repository` The @{class:PhabricatorRepository} the commit was discovered
in.
== Diffusion: Lookup User ==
The constant for this event is
`PhabricatorEventType::TYPE_DIFFUSION_LOOKUPUSER`.
This event is dispatched when the daemons are trying to link a commit to a
Phabricator user account. You can listen for it to improve the accuracy of
associating users with their commits.
By default, Phabricator will try to find matches based on usernames, real names,
or email addresses, but this can result in incorrect matches (e.g., if you have
several employees with the same name) or failures to match (e.g., if someone
changed their email address). Listening for this event allows you to intercept
the lookup and supplement the results from another datasource.
Data available on this event:
- `commit` The @{class:PhabricatorRepositoryCommit} that data is being looked
up for.
- `query` The author or committer string being looked up. This will usually
be something like "Abraham Lincoln ", but
comes from the commit metadata so it may not be well-formatted.
- `result` The current result from the lookup (Phabricator's best guess at
the user PHID of the user named in the "query"). To substitute the result
with a different result, replace this with the correct PHID in your event
listener.
Using @{class@libphutil:PhutilEmailAddress} may be helpful in parsing the query.
== Search: Did Update Index ==
The constant for this event is
`PhabricatorEventType::TYPE_SEARCH_DIDUPDATEINDEX`.
This event is dispatched from the Search application's indexing engine, after
it indexes a document. It allows you to publish search-like indexes into other
systems.
Note that this event happens after the update is fully complete: you can not
prevent or modify the update. Further, the event may fire significantly later
in real time than the update, as indexing may occur in the background. You
should use other events if you need guarantees about when the event executes.
Finally, this event may fire more than once for a single update. For example,
if the search indexes are rebuilt, this event will fire on objects which have
not actually changed.
So, good use cases for event listeners are:
- Updating secondary search indexes.
Bad use cases are:
- Editing the object or document.
- Anything with side effects, like sending email.
Data available on this event:
- `phid` The PHID of the updated object.
- `object` The object which was updated (like a @{class:ManiphesTask}).
- `document` The @{class:PhabricatorSearchAbstractDocument} which was indexed.
This contains an abstract representation of the object, and may be useful
in populating secondary indexes because it provides a uniform API.
== Test: Did Run Test ==
The constant for this event is
`PhabricatorEventType::TYPE_TEST_DIDRUNTEST`.
This is a test event for testing event listeners. See above for details.
== UI: Did Render Actions ==
The constant for this event is
`PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS`.
This event is dispatched after a @{class:PhabricatorActionListView} is built by
the UI. It allows you to add new actions that your application may provide, like
"Fax this Object". Data available on this event:
- `object` The object which actions are being rendered for.
- `actions` The current list of available actions.
NOTE: This event is unstable and subject to change.
= Debugging Listeners =
If you're having problems with your listener, try these steps:
- If you're getting an error about Phabricator being unable to find the
listener class, make sure you've added it to a libphutil library and
configured Phabricator to load the library with `load-libraries`.
- Make sure the listener is registered. It should appear in the "Events" tab
of DarkConsole. If it's not there, you may have forgotten to add it to
`events.listeners`.
- Make sure it calls `listen()` on the right events in its `register()`
method. If you don't listen for the events you're interested in, you
won't get a callback.
- Make sure the events you're listening for are actually happening. If they
occur on a normal page they should appear in the "Events" tab of
DarkConsole. If they occur on a POST, you could add a `phlog()`
to the source code near the event and check your error log to make sure the
code ran.
- You can check if your callback is getting invoked by adding `phlog()` with
a message and checking the error log.
- You can try listening to `PhutilEventType::TYPE_ALL` instead of a specific
event type to get all events, to narrow down whether problems are caused
by the types of events you're listening to.
- You can edit the `emit_test_event.php` script to emit other types of
events instead, to test that your listener reacts to them properly. You
might have to use fake data, but this gives you an easy way to test the
at least the basics.
- For scripts, you can run under `--trace` to see which events are emitted
and how many handlers are listening to each event.
= Next Steps =
Continue by:
- taking a look at @{class:PhabricatorExampleEventListener}; or
- building a library with @{article:libphutil Libraries User Guide}.
diff --git a/src/docs/user/userguide/external_editor.diviner b/src/docs/user/userguide/external_editor.diviner
index b818a335d0..1139a28e0e 100644
--- a/src/docs/user/userguide/external_editor.diviner
+++ b/src/docs/user/userguide/external_editor.diviner
@@ -1,51 +1,51 @@
@title User Guide: Configuring an External Editor
@group userguide
Setting up an external editor to integrate with Diffusion and Differential.
= Overview =
You can configure a URI handler to allow you to open files from Differential
and Diffusion in your preferred text editor.
= Configuring Editors =
To configure an external editor, go to {nav Settings > Application Settings >
Display Preferences} and set "Editor Link" to a URI pattern (see below). This
will enable an "Open in Editor" link in Differential, and an "Edit" button in
Diffusion.
In general, you'll set this field to something like:
lang=uri
editor://open/?file=%f
Some editors support opening multiple files at once when filenames are separated
by spaces. If your editor supports this feature, set "Edit Multiple Files" to
"Supported". Otherwise, you can set it to "Not Supported" to disable "Open All"
buttons in the interface.
== Configuring: TextMate on OS X ==
-TextMate installs a ##txmt://## handler by default, so it's easy to configure
+TextMate installs a `txmt://` handler by default, so it's easy to configure
this feature if you use TextMate.
First, create a local directory with symlinks for each repository callsign. For
example, if you're developing Phabricator, it might look like this:
/Users/alincoln/editor_links/ $ ls -l
... ARC -> /Users/alincoln/workspace/arcanist/
... P -> /Users/alincoln/workspace/phabricator/
... PHU -> /Users/alincoln/workspace/libphutil/
Then set your "Editor Link" to:
lang=uri
txmt://open/?url=file:///Users/alincoln/editor_links/%r/%f&line=%l
== Configuring: Other Editors ==
General instructions for configuring some other editors and environments can be
found here:
http://wiki.nette.org/en/howto-editor-link
diff --git a/src/docs/user/userguide/herald.diviner b/src/docs/user/userguide/herald.diviner
index b2e11d65e4..be4992cf4d 100644
--- a/src/docs/user/userguide/herald.diviner
+++ b/src/docs/user/userguide/herald.diviner
@@ -1,98 +1,98 @@
@title Herald User Guide
@group userguide
Use Herald to get notified of changes you care about.
= Overview =
Herald allows you to write processing rules that take effect when objects (such
as Differential revisions and commits) are created or updated. For instance, you
might want to get notified every time someone sends out a revision that affects
some file you're interested in, even if they didn't add you as a reviewer.
Herald is less useful for small organizations (where everyone will generally
know most of what's going on) but the usefulness of the application increases
as an organization scales. Once there is too much activity to keep track of it
all, Herald allows you to filter it down so you're only notified of things you
are interested in.
= Global and Personal Rules =
You can create two kinds of Herald rules, //global// and //personal//:
- **Personal Rules** are rules you own, but they can only affect you. Only
you can edit or delete personal rules, but their actions are limited to
adding you to CC, subscribing you, etc.
- **Global Rules** are rules everyone owns, and they can affect anything.
Anyone can edit or delete a global rule, and they can take any action,
including affecting projects and mailing lists.
The general idea is to prevent individuals from controlling rules that affect
shared resources, so if a rule needs to be updated it's not a big deal if the
person who created it is on vacation.
= Rules, Conditions and Actions =
The best way to think of Herald is as a system similar to the mail rules you can
set up in most email clients, to organize mail based on "To", "Subject", etc.
Herald works very similarly, but operates on Phabricator objects (like revisions
and commits) instead of emails.
Every time an object is created or updated, Herald rules are run on it and
the actions for any matching rules are taken.
To create a new Herald rule, choose which type of event you want to act on
(e.g., changes to Differential Revisions, or Commits), and then set a list of
-conditions. For example, you might add the condition ##Author is alincoln
-(Abraham Lincoln)## to keep track of everything alincoln does. Finally, set
+conditions. For example, you might add the condition `Author is alincoln
+(Abraham Lincoln)` to keep track of everything alincoln does. Finally, set
a list of actions to take when the conditions match, like adding yourself to the
CC list.
Now you'll automatically be added to CC any time alincoln creates a revision,
and can keep an eye on what he's up to.
= Available Actions =
Herald rules can take a number of actions. Note that some actions are only
available from Global rules, and others only from Personal rules. Additionally,
not every action is available for every object type (for instance, you can not
trigger an audit based on a Differential revision).
- **Add CC**: Add a user or mailing list to the CC list for the object. For
personal rules, you can only add yourself.
- **Remove CC**: Remove a user or mailing list from the CC list for the
object. For personal rules, you can only remove yourself.
- **Send an Email to**: Send one email, but don't subscribe to other updates.
For personal rules, you can only email yourself.
- **Trigger an Audit**: For commits, trigger an audit request for a project
or user. For personal rules, you can only trigger an audit request to
yourself.
- **Mark with flag**: Flag the object for later review. This action is only
available on personal rules. If an object already has a flag, this action
will not add another flag.
- **Do Nothing**: Don't do anything. This can be used to disable a rule
temporarily, or to create a rule for an "Another Herald rule" condition.
= Testing Rules =
When you've created a rule, use the "Test Console" to test it out. Enter a
revision or commit and Herald will do a dry run against that object, showing
you which rules //would// match had it actually been updated. Dry runs executed
via the test console don't take any actions.
= Advanced Herald =
A few features in Herald are particularly complicated:
- **matches regexp pair**: for Differential revisions, you can set a condition
like "Any changed file content matches regexp pair...". This allows you to
specify two regexes in JSON format. The first will be used to match the
filename of the changed file; the second will be used to match the content.
For example, if you want to match revisions which add or remove calls to
a "muffinize" function, //but only in JS files//, you can set the value
- to ##["/\\.js$/", "/muffinize/"]## or similar.
+ to `["/\\.js$/", "/muffinize/"]` or similar.
- **Another Herald rule**: you can create Herald rules which depend on other
rules. This can be useful if you need to express a more complicated predicate
than "all" vs "any" allows, or have a common set of conditions which you want
to share between several rules. If a rule is only being used as a group of
conditions, you can set the action to "Do Nothing".
diff --git a/src/docs/user/userguide/libraries.diviner b/src/docs/user/userguide/libraries.diviner
index 656b167d8d..b0614d7a8f 100644
--- a/src/docs/user/userguide/libraries.diviner
+++ b/src/docs/user/userguide/libraries.diviner
@@ -1,155 +1,155 @@
@title libphutil Libraries User Guide
@group userguide
Guide to creating and managing libphutil libraries.
= Overview =
libphutil includes a library system which organizes PHP classes and functions
into modules. Some extensions and customizations of Arcanist and Phabricator
require you to make code available to Phabricator by providing it in a libphutil
library.
For example, if you want to store files in some kind of custom storage engine,
you need to write a class which can interact with that engine and then tell
Phabricator to load it.
In general, you perform these one-time setup steps:
- Create a new directory.
- - Use ##arc liberate## to initialize and name the library.
+ - Use `arc liberate` to initialize and name the library.
- Add a dependency on Phabricator if necessary.
- - Add the library to your Phabricator config or ##.arcconfig## so it will be
+ - Add the library to your Phabricator config or `.arcconfig` so it will be
loaded at runtime.
Then, to add new code, you do this:
- Write or update classes.
- - Update the library metadata by running ##arc liberate## again.
+ - Update the library metadata by running `arc liberate` again.
= Creating a New Library =
To **create a new libphutil library**:
$ mkdir libcustom/
$ cd libcustom/
libcustom/ $ arc liberate src/
Now you'll get a prompt like this:
lang=txt
No library currently exists at that path...
The directory '/some/path/libcustom/src' does not exist.
Do you want to create it? [y/N] y
Creating new libphutil library in '/some/path/libcustom/src'.
Choose a name for the new library.
What do you want to name this library?
Choose a library name (in this case, "libcustom" would be appropriate) and it
you should get some details about the library initialization:
lang=txt
Writing '__phutil_library_init__.php' to
'/some/path/libcustom/src/__phutil_library_init__.php'...
Using library root at 'src'...
Mapping library...
Verifying library...
Finalizing library map...
OKAY Library updated.
This will write three files:
- - ##src/.phutil_module_cache## This is a cache which makes "arc liberate"
+ - `src/.phutil_module_cache` This is a cache which makes "arc liberate"
faster when you run it to update the library. You can safely remove it at
any time. If you check your library into version control, you can add this
file to ignore rules (like .gitignore).
- - ##src/__phutil_library_init__.php## This records the name of the library and
+ - `src/__phutil_library_init__.php` This records the name of the library and
tells libphutil that a library exists here.
- - ##src/__phutil_library_map__.php## This is a map of all the symbols
+ - `src/__phutil_library_map__.php` This is a map of all the symbols
(functions and classes) in the library, which allows them to be autoloaded
at runtime and dependencies to be statically managed by "arc liberate".
= Linking with Phabricator =
If you aren't using this library with Phabricator (e.g., you are only using it
with Arcanist or are building something else on libphutil) you can skip this
step.
But, if you intend to use this library with Phabricator, you need to define its
-dependency on Phabricator by creating a ##.arcconfig## file which points at
+dependency on Phabricator by creating a `.arcconfig` file which points at
Phabricator. For example, you might write this file to
`libcustom/.arcconfig`:
{
"load": [
"phabricator/src/"
]
}
-For details on creating a ##.arcconfig##, see
+For details on creating a `.arcconfig`, see
@{article:Arcanist User Guide: Configuring a New Project}. In general, this
-tells ##arc liberate## that it should look for symbols in Phabricator when
+tells `arc liberate` that it should look for symbols in Phabricator when
performing static analysis.
NOTE: If Phabricator isn't located next to your custom library, specify a
-path which actually points to the ##phabricator/## directory.
+path which actually points to the `phabricator/` directory.
-You do not need to declare dependencies on ##arcanist## or ##libphutil##,
-since ##arc liberate## automatically loads them.
+You do not need to declare dependencies on `arcanist` or `libphutil`,
+since `arc liberate` automatically loads them.
Finally, edit your Phabricator config to tell it to load your library at
-runtime, by adding it to ##load-libraries##:
+runtime, by adding it to `load-libraries`:
...
'load-libraries' => array(
'libcustom' => 'libcustom/src/',
),
...
Now, Phabricator will be able to load classes from your custom library.
= Writing Classes =
To actually write classes, create a new module and put code in it:
libcustom/ $ mkdir src/example/
libcustom/ $ nano src/example/ExampleClass.php # Edit some code.
-Now, run ##arc liberate## to regenerate the static resource map:
+Now, run `arc liberate` to regenerate the static resource map:
libcustom/ $ arc liberate src/
This will automatically regenerate the static map of the library.
= What You Can Extend And Invoke =
libphutil, Arcanist and Phabricator are strict about extensibility of classes
-and visibility of methods and properties. Most classes are marked ##final##, and
+and visibility of methods and properties. Most classes are marked `final`, and
methods have the minimum required visibility (protected or private). The goal of
this strictness is to make it clear what you can safely extend, access, and
invoke, so your code will keep working as the upstream changes.
When developing libraries to work with libphutil, Arcanist and Phabricator, you
should respect method and property visibility and extend only classes marked
`@stable`. They are rendered with a large callout in the documentation (for
example: @{class@libphutil:AbstractDirectedGraph}). These classes are external
interfaces intended for extension.
-If you want to extend a class but it is not marked ##@stable##, here are some
+If you want to extend a class but it is not marked `@stable`, here are some
approaches you can take:
- Good: If possible, use composition rather than extension to build your
feature.
- Good: Check the documentation for a better way to accomplish what you're
trying to do.
- Good: Let us know what your use case is so we can make the class tree more
flexible or configurable, or point you at the right way to do whatever
you're trying to do, or explain why we don't let you do it.
- Discouraged: Send us a patch removing "final" (or turning "protected" or
"private" into "public"). We generally will not accept these patches, unless
there's a good reason that the current behavior is wrong.
- Discouraged: Create an ad-hoc local fork and remove "final" in your copy of
the code. This will make it more difficult for you to upgrade in the future.
- Discouraged: Use Reflection to violate visibility keywords.
diff --git a/src/docs/user/userguide/mail_rules.diviner b/src/docs/user/userguide/mail_rules.diviner
index 64d0723fdb..3640f5e5a5 100644
--- a/src/docs/user/userguide/mail_rules.diviner
+++ b/src/docs/user/userguide/mail_rules.diviner
@@ -1,81 +1,81 @@
@title User Guide: Managing Phabricator Email
@group userguide
How to effectively manage Phabricator email notifications.
= Overview =
Phabricator uses email as a major notification channel, but the amount of email
it sends can seem overwhelming if you're working on an active team. This
document discusses some strategies for managing email.
By far the best approach to managing mail is to **write mail rules** to
categorize mail. Essentially all modern mail clients allow you to quickly
write sophisticated rules to route, categorize, or delete email.
= Reducing Email =
You can reduce the amount of email you receive by turning off some types of
email in {nav Settings > Email Preferences}. For example, you can turn off email
produced by your own actions (like when you comment on a revision), and some
types of less-important notifications about events.
= Mail Rules =
The best approach to managing mail is to write mail rules. Simply writing rules
to move mail from Differential, Maniphest and Herald to separate folders will
vastly simplify mail management.
Phabricator also sets a large number of headers (see below) which can allow you
to write more sophisticated mail rules.
= Mail Headers =
Phabricator sends a variety of mail headers that can be useful in crafting rules
to route and manage mail.
-Headers in plural contain lists. A list containing two items, ##1## and
+Headers in plural contain lists. A list containing two items, `1` and
`15` will generally be formatted like this:
X-Header: <1>, <15>
The intent is to allow you to write a rule which matches against "<1>". If you
just match against "1", you'll incorrectly match "15", but matching "<1>" will
correctly match only "<1>".
Some other headers use a single value but can be presented multiple times.
It is to support e-mail clients which are not able to create rules using regular
expressions or wildcards (namely Outlook).
The headers Phabricator adds to mail are:
- - ##X-Phabricator-Sent-This-Message##: this is attached to all mail
+ - `X-Phabricator-Sent-This-Message`: this is attached to all mail
Phabricator sends. You can use it to differentiate between email from
Phabricator and replies/forwards of Phabricator mail from human beings.
- - ##X-Phabricator-To##: this is attached to all mail Phabricator sends.
+ - `X-Phabricator-To`: this is attached to all mail Phabricator sends.
It shows the PHIDs of the original "To" line, before any mutation
by the mailer configuration.
- - ##X-Phabricator-Cc##: this is attached to all mail Phabricator sends.
+ - `X-Phabricator-Cc`: this is attached to all mail Phabricator sends.
It shows the PHIDs of the original "Cc" line, before any mutation by the
mailer configuration.
- - ##X-Differential-Author##: this is attached to Differential mail and shows
+ - `X-Differential-Author`: this is attached to Differential mail and shows
the revision's author. You can use it to filter mail about your revisions
(or other users' revisions).
- - ##X-Differential-Reviewer##: this is attached to Differential mail and
+ - `X-Differential-Reviewer`: this is attached to Differential mail and
shows the reviewers. You can use it to filter mail about revisions you
are reviewing, versus revisions you are explicitly CC'd on or CC'd as
a result of Herald rules.
- - ##X-Differential-Reviewers##: list version of the previous.
- - ##X-Differential-CC##: this is attached to Differential mail and shows
+ - `X-Differential-Reviewers`: list version of the previous.
+ - `X-Differential-CC`: this is attached to Differential mail and shows
the CCs on the revision.
- - ##X-Differential-CCs##: list version of the previous.
- - ##X-Differential-Explicit-CC##: this is attached to Differential mail and
+ - `X-Differential-CCs`: list version of the previous.
+ - `X-Differential-Explicit-CC`: this is attached to Differential mail and
shows the explicit CCs on the revision (those that were added by humans,
not by Herald).
- - ##X-Differential-Explicit-CCs##: list version of the previous.
- - ##X-Phabricator-Mail-Tags##: this is attached to some mail and has
+ - `X-Differential-Explicit-CCs`: list version of the previous.
+ - `X-Phabricator-Mail-Tags`: this is attached to some mail and has
a list of descriptors about the mail. (This is fairly new and subject
to some change.)
- - ##X-Herald-Rules##: this is attached to some mail and shows Herald rule
+ - `X-Herald-Rules`: this is attached to some mail and shows Herald rule
IDs which have triggered for the object. You can use this to sort or
categorize mail that has triggered specific rules.
diff --git a/src/docs/user/userguide/utf8.diviner b/src/docs/user/userguide/utf8.diviner
index 79e1bc5a44..a604599e9f 100644
--- a/src/docs/user/userguide/utf8.diviner
+++ b/src/docs/user/userguide/utf8.diviner
@@ -1,80 +1,80 @@
@title User Guide: UTF-8 and Character Encoding
@group userguide
How Phabricator handles character encodings.
= Overview =
Phabricator stores all internal text data as UTF-8, processes all text data
as UTF-8, outputs in UTF-8, and expects all inputs to be UTF-8. Principally,
this means that you should write your source code in UTF-8. In most cases this
does not require you to change anything, because ASCII text is a subset of
UTF-8.
If you have a repository with source files that do not have UTF-8, you have two
options:
- Convert all files in the repository to ASCII or UTF-8 (see "Detecting and
Repairing Files" below). This is recommended, especially if the encoding
problems are accidental.
- Configure Phabricator to convert files into UTF-8 from whatever encoding
your repository is in when it needs to (see "Support for Alternate
Encodings" below). This is not completely supported, and repositories with
files that have multiple encodings are not supported.
= Detecting and Repairing Files =
It is recommended that you write source files only in ASCII text, but
Phabricator fully supports UTF-8 source files.
If you have a project which isn't valid UTF-8 because a few files have random
binary nonsense in them, there is a script in libphutil which can help you
identify and fix them:
project/ $ libphutil/scripts/utils/utf8.php
Generally, run this script on all source files with "-t" to find files with bad
byte ranges, and then run it without "-t" on each file to identify where there
are problems. For example:
project/ $ find . -type f -name '*.c' -print0 | xargs -0 -n256 ./utf8 -t
./hello_world.c
If this script exits without output, you're in good shape and all the files that
were identified are valid UTF-8. If it found some problems, you need to repair
them. You can identify the specific problems by omitting the "-t" flag:
project/ $ ./utf8.php hello_world.c
FAIL hello_world.c
3 main()
4 {
5 printf ("Hello World<0xE9><0xD6>!\n");
6 }
7
This shows the offending bytes on line 5 (in the actual console display, they'll
be highlighted). Often a codebase will mostly be valid UTF-8 but have a few
scattered files that have other things in them, like curly quotes which someone
copy-pasted from Word into a comment. In these cases, you can just manually
identify and fix the problems pretty easily.
If you have a prohibitively large number of UTF-8 issues in your source code,
Phabricator doesn't include any default tools to help you process them in a
-systematic way. You could hack up ##utf8.php## as a starting point, or use other
+systematic way. You could hack up `utf8.php` as a starting point, or use other
tools to batch-process your source files.
= Support for Alternate Encodings =
Phabricator has some support for encodings other than UTF-8.
NOTE: Alternate encodings are not completely supported, and a few features will
not work correctly. Codebases with files that have multiple different encodings
(for example, some files in ISO-8859-1 and some files in Shift-JIS) are not
supported at all.
To use an alternate encoding, edit the repository in Diffusion and specify the
encoding to use.
Optionally, you can use the `--encoding` flag when running `arc`, or set
`encoding` in your `.arcconfig`.
diff --git a/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner b/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner
index 726762ecb4..0ff27d912a 100644
--- a/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner
+++ b/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner
@@ -1,181 +1,181 @@
@title Concepts: Behaviors
@group concepts
Javelin behaviors help you glue pieces of code together.
= Overview =
Javelin behaviors provide a place for you to put glue code. For instance, when
a page loads, you often need to instantiate objects, or set up event listeners,
or alert the user that they've won a hog, or create a dependency between two
objects, or modify the DOM, etc.
Sometimes there's enough code involved here or a particular setup step happens
often enough that it makes sense to write a class, but sometimes it's just a
few lines of one-off glue. Behaviors give you a structured place to put this
glue so that it's consistently organized and can benefit from Javelin
infrastructure.
= Behavior Basics =
Behaviors are defined with @{function:JX.behavior}:
lang=js
JX.behavior('win-a-hog', function(config, statics) {
alert("YOU WON A HOG NAMED " + config.hogName + "!");
});
They are called with @{function:JX.initBehaviors}:
lang=js
JX.initBehaviors({
"win-a-hog" : [{hogName : "Ethel"}]
});
Normally, you don't construct the @{function:JX.initBehaviors} call yourself,
but instead use a server-side library which manages behavior initialization for
you. For example, using the PHP library:
lang=php
$config = array('hogName' => 'Ethel');
JavelinHelper::initBehaviors('win-a-hog', $config);
Regardless, this will alert the user that they've won a hog (named Ethel, which
is a good name for a hog) when they load the page.
The callback you pass to @{function:JX.behavior} should have this signature:
lang=js
function(config, statics) {
// ...
}
The function will be invoked once for each configuration dictionary passed to
@{function:JX.initBehaviors}, and the dictionary will be passed as the
-##config## parameter. For example, to alert the user that they've won two hogs:
+`config` parameter. For example, to alert the user that they've won two hogs:
lang=js
JX.initBehaviors({
"win-a-hog" : [{hogName : "Ethel"}, {hogName: "Beatrice"}]
});
-This will invoke the function twice, once for each ##config## dictionary.
+This will invoke the function twice, once for each `config` dictionary.
Usually, you invoke a behavior multiple times if you have several similar
controls on a page, like multiple @{class:JX.Tokenizer}s.
-An initially empty object will be passed in the ##statics## parameter, but
+An initially empty object will be passed in the `statics` parameter, but
changes to this object will persist across invocations of the behavior. For
example:
lang=js
JX.initBehaviors('win-a-hog', function(config, statics) {
statics.hogsWon = (statics.hogsWon || 0) + 1;
if (statics.hogsWon == 1) {
alert("YOU WON A HOG! YOUR HOG IS NAMED " + config.hogName + "!");
} else {
alert("YOU WON ANOTHER HOG!!! THIS ONE IS NAMED " + config.hogName + "!");
}
}
One way to think about behaviors are that they take the anonymous function
passed to @{function:JX.behavior} and put it in a private Javelin namespace,
which you access with @{function:JX.initBehavior}.
Another way to think about them is that you are defining methods which represent
the entirety of the API exposed by the document. The recommended approach to
glue code is that the server interact with Javascript on the client //only// by
invoking behaviors, so the set of available behaviors represent the complete set
of legal interactions available to the server.
= History and Rationale =
This section explains why behaviors exist and how they came about. You can
understand and use them without knowing any of this, but it may be useful or
interesting.
In early 2007, Facebook often solved the "glue code" problem through the use
of global functions and DOM Level 0 event handlers, by manually building HTML
tags in PHP:
lang=php
echo ''.
'Click here to win!'.
'';
(This example produces a link which the user can click to be alerted they have
won a hog, which is slightly different from the automatic alert in the other
examples in this document. Some subtle distinctions are ignored or glossed
over here because they are not important to understanding behaviors.)
This has a wide array of technical and architectural problems:
- Correctly escaping parameters is cumbersome and difficult.
- It resists static analysis, and is difficult to even grep for. You can't
easily package, minify, or determine dependencies for the piece of JS in
the result string.
- DOM Level 0 events have a host of issues in a complex application
environment.
- The JS global namespace becomes polluted with application glue functions.
- The server and client are tightly and relatively arbitrarily coupled, since
many of these handlers called multiple functions or had logic in the
strings. There is no structure to the coupling, so many callers relied on
the full power of arbitrary JS execution.
- It's utterly hideous.
In 2007/2008, we introduced @{function@libphutil:jsprintf} and a function called
onloadRegister() to solve some of the obvious problems:
lang=php
onloadRegister('win_a_hog(%s);', $hog_name);
This registers the snippet for invocation after DOMContentReady fires. This API
makes escaping manageable, and was combined with recommendations to structure
code like this in order to address some of the other problems:
lang=php
$id = uniq_id();
echo 'Click here to win!';
onloadRegister('new WinAHogController(%s, %s);', $id, $hog_name);
By 2010 (particularly with the introduction of XHP) the API had become more
sophisticated, but this is basically how most of Facebook's glue code still
works as of mid-2011. If you view the source of any page, you'll see a bunch
-of ##onloadRegister()## calls in the markup which are generated like this.
+of `onloadRegister()` calls in the markup which are generated like this.
This mitigates many of the problems but is still fairly awkward. Escaping is
easier, but still possible to get wrong. Stuff is a bit easier to grep for, but
not much. You can't get very far with static analysis unless you get very
complex. Coupling between the languages has been reduced but not eliminated. And
now you have a bunch of classes which only really have glue code in them.
Javelin behaviors provide a more structured solution to some of these problems:
- All your Javascript code is in Javascript files, not embedded in strings in
in some host language on the server side.
- You can use static analysis and minification tools normally.
- Provided you use a reasonable server-side library, you can't get escaping
wrong.
- Coupling is reduced because server only passes data to the client, never
code.
- The server declares client dependencies explicitly, not implicitly inside
a string literal. Behaviors are also relatively easy to grep for.
- Behaviors exist in a private, structured namespace instead of the global
namespace.
- Separation between the document's layout and behavior is a consequence of
the structure of behaviors.
- The entire interface the server may invoke against can be readily inferred.
Note that Javelin does provide @{function:JX.onload}, which behaves like
-##onloadRegister()##. However, its use is discouraged.
+`onloadRegister()`. However, its use is discouraged.
The two major downsides to the behavior design appear to be:
- They have a higher setup cost than the ad-hoc methods, but Javelin
philosophically places a very low value on this.
- Because there's a further setup cost to migrate an existing behavior into a
class, behaviors sometimes grow little by little until they are too big,
have more than just glue code, and should have been refactored into a
real class some time ago. This is a pretty high-level drawback and is
manageable through awareness of the risk and code review.
diff --git a/webroot/rsrc/externals/javelin/docs/concepts/event_delegation.diviner b/webroot/rsrc/externals/javelin/docs/concepts/event_delegation.diviner
index 3d62afca24..ca2a177a35 100644
--- a/webroot/rsrc/externals/javelin/docs/concepts/event_delegation.diviner
+++ b/webroot/rsrc/externals/javelin/docs/concepts/event_delegation.diviner
@@ -1,191 +1,191 @@
@title Concepts: Event Delegation
@group concepts
Explains Javelin event delegation with @{class:JX.Stratcom}.
= Overview =
Javelin provides event delegation as a core feature of the library, orchestrated
with @{class:JX.Stratcom}. Event delegation means that the library listens to
every event in the document and then delegates them to handlers you install,
as opposed to you installing handlers on specific nodes for specific events you
are interested in.
Event delegation can greatly simplify event handling for many types of user
interactions, and can also be used to do more traditional event listening for
specific events on specific nodes. The goal is to provide a strictly more
powerful event model, which gives you a very general delegation toolkit for
interactions where delegation makes sense but refines into a very specific
toolkit when you need less generality.
Beyond DOM events, Stratcom provides a general event delegation framework which
Javelin classes integrate with.
= Event Delegation Basics =
Javelin routes events based on the **event type** and **sigil set**.
The **event type** is a string with a general description of the event, and
includes the DOM event types like 'click' and 'keydown'. It may also be a
class-specific event (JX.Duck might emit 'quack').
The **sigil set** is a list of sigils (see @{article:Concepts: Sigils and
Metadata}) for the event. If the event is a DOM event, Javelin builds the
sigil set by walking up the DOM tree from the event target and collecting all
the sigils on nodes (it also collects some other data into the sigil set,
see "Magic Sigils" below). If the event is a class event, Javelin walks up
the class hierarchy collecting class names. If the event is a raw event invoked
with @{method:JX.Stratcom.invoke}, the caller must provide the sigil set.
When you install an event handler, you specify the event type and the (possibly
empty) sigil set you want to listen for.
When an event is invoked, Javelin finds all the listeners for that event type
and compares their sigil sets with the event's sigil set. If all of the sigils
in a callback's sigil set appear in the event's sigil set, the callback is
invoked. Generally, this mechanism allows you to ignore events you are not
interested in.
= Listening to General DOM Events =
You can listen to general DOM events with @{method:JX.Stratcom.listen}. This
method allows you to select which types of events you want to receive, and
which elements must be involved in the event:
lang=js
JX.Stratcom.listen(
'click', // Node
null, // Sigil set
function(e) { // Callback
// ...
});
This listens to all clicks on all elements in the document. More likely, you
want to listen only to some clicks. You accomplish this by annotating your
document with Javelin sigils (see @{article:Concepts: Sigils and Metadata})
and specifying a set of sigils which must be present between the target node
and the document root. For instance, your document might look like this:
lang=html
Important!Some Other Link
If you install a handler like the one above, you'll get callbacks for every
click, no matter which link it is or even if it's on the document itself. If
you just want clicks on the "Important!" link, you can install a more specific
handler:
lang=js
JX.Stratcom.listen(
'click',
'important', // Listen only to this sigil set
function(e) {
// ...
});
Now you will receive a callback only when the event target or some ancestor of
it has the "important" sigil, i.e., only when the user clicks on the
"Important!" link. You can also specify multiple sigils; ancestors must have
all of the sigils for you to get a callback:
lang=js
JX.Stratcom.listen(
'click',
['menu', 'item'], // Listen only for clicks on menu items.
function(e) {
// ...
});
= Listening to Specific DOM Events =
You can listen to specific DOM events with @{method:JX.DOM.listen}. This method
works like @{method:JX.Stratcom.listen} but takes a DOM node as the first
parameter and listens only for events within that node:
lang=js
JX.DOM.listen(
node, // Node
'click', // Event type(s)
null, // Sigil set
function(e) { // Callback
// ...
});
-This is similar to setting ##node.onclick## or ##node.addEventListener##, but
+This is similar to setting `node.onclick` or `node.addEventListener`, but
uses the Javelin delegation core. You can also provide a sigil set, which works
just like @{method:JX.Stratcom.listen} general events. This is useful if your
-node is a container, like a ####, with a lot of stuff in it.
+node is a container, like a ``, with a lot of stuff in it.
= DOM Event Flow =
Events dispatched within the DOM propagate using a bubbling method, as detailed
by http://www.w3.org/TR/DOM-Level-3-Events/#event-flow
Listeners assigned using @{method:JX.Stratcom.listen} or @{method:JX.DOM.listen}
are called in order of the deepest node in the DOM of the nodes which match the
sigil set listened to.
In this example the second listener, subscribed to 'inner', will be called
before the listener subscribed to 'outer'
lang=html
Click Me
= Listening to Class Events =
Beyond DOM events, you can also listen to class events. Every class installed
-by Javelin has static and instance methods called ##listen## (see
+by Javelin has static and instance methods called `listen` (see
@{method:JX.Base.listen}). The static method allows you to listen for all events
emitted by every instance of a class and its descendants:
lang=js
JX.Animal.listen(
'meow',
function(e) {
// Listen for ANY 'meow' from any JX.Animal instance or instance which
// extends JX.Animal.
});
The instance method allows you to listen for all events emitted by that
specific instance:
lang=js
var cat = new JX.Cat();
cat.listen(
'meow',
function(e) {
// Listen for 'meow' from only this cat.
});
= Magic Sigils =
Javelin implements general delegation by building and comparing sigil sets. Some
of these sigils are not DOM sigils, but derived from other things:
- - ##id:*## ID sigils are generated when an examined node has an "id" property.
- - ##obj:*## Object sigils are generated when an event affects a class
+ - `id:*` ID sigils are generated when an examined node has an "id" property.
+ - `obj:*` Object sigils are generated when an event affects a class
instance.
- - ##class:*## Class sigils are generated while walking an affected instance's
+ - `class:*` Class sigils are generated while walking an affected instance's
class chain.
- - ##tag:*## Tag sigils are generated by examining the tag names of DOM nodes.
+ - `tag:*` Tag sigils are generated by examining the tag names of DOM nodes.
-For instance, you can listen to all clicks on #### tags in a document like
+For instance, you can listen to all clicks on `` tags in a document like
this:
lang=js
JX.Stratcom.listen(
'click',
'tag:a',
function(e) {
// ...
});
diff --git a/webroot/rsrc/externals/javelin/docs/concepts/sigils_metadata.diviner b/webroot/rsrc/externals/javelin/docs/concepts/sigils_metadata.diviner
index 38192aedc2..7123cd3332 100644
--- a/webroot/rsrc/externals/javelin/docs/concepts/sigils_metadata.diviner
+++ b/webroot/rsrc/externals/javelin/docs/concepts/sigils_metadata.diviner
@@ -1,129 +1,129 @@
@title Concepts: Sigils and Metadata
@group concepts
Explains Javelin's sigils and metadata.
= Overview =
Javelin introduces two major concepts, "sigils" and "metadata", which are core
parts of the library but don't generally exist in other Javascript libraries.
Both sigils and metadata are extra information you add to the DOM which Javelin
can access. This document explains what they are, why they exist, and how you
use them.
= Sigils =
Sigils are names attached to nodes in the DOM. They behave almost exactly like
CSS class names: sigils are strings, each node may have zero or more sigils, and
sigils are not unique within a document. Sigils convey semantic information
about the structure of the document.
It is reasonable to think of sigils as being CSS class names in a different,
semantic namespace.
-If you're emitting raw tags, you specify sigils by adding a ##data-sigil##
+If you're emitting raw tags, you specify sigils by adding a `data-sigil`
attribute to a node:
lang=html
...
...
However, this should be considered an implementation detail and you should not
rely on it excessively. In Javelin, use @{method:JX.Stratcom.hasSigil} to test
if a node has a given sigil, and @{method:JX.Stratcom.addSigil} to add a sigil
to a node.
Javelin uses sigils instead of CSS classes to rigidly enforce the difference
between semantic information and style information in the document. While CSS
classes can theoretically handle both, the conflation between semantic and style
information in a realistic engineering environment caused a number of problems
at Facebook, including a few silly, preventable, and unfortunately severe bugs.
Javelin separates this information into different namespaces, so developers and
designers can be confident that changing CSS classes and presentation of a
document will never change its semantic meaning. This is also why Javelin does
not have a method to test whether a node has a CSS class, and does not have CSS
selectors. Unless you cheat, Javelin makes it very difficult to use CSS class
names semantically.
This is an unusual decision for a library, and quite possibly the wrong tradeoff
in many environments. But this was a continual source of problems at Facebook's
scale and in its culture, such that it seemed to justify the measures Javelin
takes to prevent accidents with style information having inadvertent or
unrealized semantic value.
= Metadata =
Metadata is arbitrary pieces of data attached to nodes in the DOM. Metadata can
be (and generally is) specified on the server, when the document is constructed.
The value of metadata is that it allows handlers which use event delegation to
distinguish between events which occur on similar nodes. For instance, if you
have newsfeed with several "Like" links in it, your document might look like
this:
lang=html
You can install a listener using Javelin event delegation (see @{article:
Concepts: Event Delegation} for more information) like this:
lang=js
JX.Stratcom.listen(
'click',
['newsfeed', 'story', 'like'],
function(e) {
// ...
});
This calls the function you provide when the user clicks on a "like" link, but
you need to be able to distinguish between the different links so you can know
which story the user is trying to like. Javelin allows you to do this by
attaching **metadata** to each node. Metadata is attached to a node by adding a
-##data-meta## attribute which has an index into data later provided to
+`data-meta` attribute which has an index into data later provided to
@{method:JX.Stratcom.mergeData}:
lang=html
...
This data can now be accessed with @{method:JX.Stratcom.getData}, or with
@{method:JX.Event.getNodeData} in an event handler:
lang=js
JX.Stratcom.listen(
'click',
['newsfeed', 'story', 'like'],
function(e) {
var id = e.getNodeData('story').storyID;
// ...
});
You can also add data to a node programmatically in Javascript with
@{method:JX.Stratcom.addData}.
diff --git a/webroot/rsrc/externals/javelin/docs/facebook.diviner b/webroot/rsrc/externals/javelin/docs/facebook.diviner
index 5126ad2c35..628ec5cfdb 100644
--- a/webroot/rsrc/externals/javelin/docs/facebook.diviner
+++ b/webroot/rsrc/externals/javelin/docs/facebook.diviner
@@ -1,82 +1,82 @@
@title Javelin at Facebook
@group facebook
Information specific to Javelin at Facebook.
= Building Support Scripts =
Javelin now ships with the source to build several libfbjs-based binaries, which
serve to completely sever its dependencies on trunk:
- - ##javelinsymbols##: used for lint
- - ##jsast##: used for documentation generation
- - ##jsxmin##: used to crush packages
+ - `javelinsymbols`: used for lint
+ - `jsast`: used for documentation generation
+ - `jsxmin`: used to crush packages
To build these, first build libfbjs:
javelin/ $ cd externals/libfbjs
javelin/externals/libfbjs/ $ CXX=/usr/bin/g++ make
Note that **you must specify CXX explicitly because the default CXX is broken**.
Now you should be able to build the individual binaries:
javelin/ $ cd support/javelinsymbols
javelin/support/javelinsymbols $ CXX=/usr/bin/g++ make
javelin/ $ cd support/jsast
javelin/support/jsast $ CXX=/usr/bin/g++ make
javelin/ $ cd support/jsxmin
javelin/support/jsxmin $ CXX=/usr/bin/g++ make
= Synchronizing Javelin =
To synchronize Javelin **from** Facebook trunk, run the synchronize script:
javelin/ $ ./scripts/sync-from-facebook.php ~/www
-...where ##~/www## is the root you want to pull Javelin files from. The script
-will copy files out of ##html/js/javelin## and build packages, and leave the
+...where `~/www` is the root you want to pull Javelin files from. The script
+will copy files out of `html/js/javelin` and build packages, and leave the
results in your working copy. From there you can review changes and commit, and
then push, diff, or send a pull request.
To synchronize Javelin **to** Facebook trunk, run the, uh, reverse-synchronize
script:
javelin/ $ ./scripts/sync-to-facebook.php ~/www
-...where ##~/www## is the root you want to push Javelin files to. The script
-will copy files out of the working copy into your ##www## and leave you with a
-dirty ##www##. From there you can review changes.
+...where `~/www` is the root you want to push Javelin files to. The script
+will copy files out of the working copy into your `www` and leave you with a
+dirty `www`. From there you can review changes.
-Once Facebook moves to pure git for ##www## we can probably just submodule
+Once Facebook moves to pure git for `www` we can probably just submodule
Javelin into it and get rid of all this nonsense, but the mixed SVN/git
environment makes that difficult until then.
= Building Documentation =
-Check out ##diviner## and ##libphutil## from Facebook github, and put them in a
-directory with ##javelin##:
+Check out `diviner` and `libphutil` from Facebook github, and put them in a
+directory with `javelin`:
somewhere/ $ ls
diviner/
javelin/
libphutil/
somewhere/ $
-Now run ##diviner## on ##javelin##:
+Now run `diviner` on `javelin`:
somewhere/ $ cd javelin
somewhere/javelin/ $ ../diviner/bin/diviner .
[DivinerArticleEngine] Generating documentation for 48 files...
[JavelinDivinerEngine] Generating documentation for 74 files...
somewhere/javelin/ $
-Documentation is now available in ##javelin/docs/##.
+Documentation is now available in `javelin/docs/`.
= Editing javelinjs.com =
-The source for javelinjs.com lives in ##javelin/support/webroot/##. The site
+The source for javelinjs.com lives in `javelin/support/webroot/`. The site
itself is served off the phabricator.com host. You need access to that host to
push it.