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
... Like
... Like
... 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.