Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php b/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php
index 8bbbedc3c6..4b9d53db85 100644
--- a/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php
+++ b/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php
@@ -1,335 +1,335 @@
<?php
final class PhabricatorJIRAAuthProvider extends PhabricatorOAuth1AuthProvider {
public function getJIRABaseURI() {
return $this->getProviderConfig()->getProperty(self::PROPERTY_JIRA_URI);
}
public function getProviderName() {
return pht('JIRA');
}
public function getDescriptionForCreate() {
return pht('Configure JIRA OAuth. NOTE: Only supports JIRA 6.');
}
public function getConfigurationHelp() {
return $this->getProviderConfigurationHelp();
}
protected function getProviderConfigurationHelp() {
if ($this->isSetup()) {
return pht(
"**Step 1 of 2**: Provide the name and URI for your JIRA install.\n\n".
"In the next step, you will configure JIRA.");
} else {
$login_uri = PhabricatorEnv::getURI($this->getLoginURI());
return pht(
"**Step 2 of 2**: In this step, you will configure JIRA.\n\n".
"**Create a JIRA Application**: Log into JIRA and go to ".
"**Administration**, then **Add-ons**, then **Application Links**. ".
"Click the button labeled **Add Application Link**, and use these ".
"settings to create an application:\n\n".
" - **Server URL**: `%s`\n".
" - Then, click **Next**. On the second page:\n".
" - **Application Name**: `Phabricator`\n".
" - **Application Type**: `Generic Application`\n".
" - Then, click **Create**.\n\n".
"**Configure Your Application**: Find the application you just ".
"created in the table, and click the **Configure** link under ".
"**Actions**. Select **Incoming Authentication** and click the ".
"**OAuth** tab (it may be selected by default). Then, use these ".
"settings:\n\n".
" - **Consumer Key**: Set this to the \"Consumer Key\" value in the ".
"form above.\n".
" - **Consumer Name**: `Phabricator`\n".
" - **Public Key**: Set this to the \"Public Key\" value in the ".
"form above.\n".
" - **Consumer Callback URL**: `%s`\n".
"Click **Save** in JIRA. Authentication should now be configured, ".
"and this provider should work correctly.",
PhabricatorEnv::getProductionURI('/'),
$login_uri);
}
}
protected function newOAuthAdapter() {
$config = $this->getProviderConfig();
return id(new PhutilJIRAAuthAdapter())
->setAdapterDomain($config->getProviderDomain())
->setJIRABaseURI($config->getProperty(self::PROPERTY_JIRA_URI))
->setPrivateKey(
new PhutilOpaqueEnvelope(
$config->getProperty(self::PROPERTY_PRIVATE_KEY)));
}
protected function getLoginIcon() {
return 'Jira';
}
private function isSetup() {
return !$this->getProviderConfig()->getID();
}
const PROPERTY_JIRA_NAME = 'oauth1:jira:name';
const PROPERTY_JIRA_URI = 'oauth1:jira:uri';
const PROPERTY_PUBLIC_KEY = 'oauth1:jira:key:public';
const PROPERTY_PRIVATE_KEY = 'oauth1:jira:key:private';
const PROPERTY_REPORT_LINK = 'oauth1:jira:report:link';
const PROPERTY_REPORT_COMMENT = 'oauth1:jira:report:comment';
public function readFormValuesFromProvider() {
$config = $this->getProviderConfig();
$uri = $config->getProperty(self::PROPERTY_JIRA_URI);
return array(
self::PROPERTY_JIRA_NAME => $this->getProviderDomain(),
self::PROPERTY_JIRA_URI => $uri,
);
}
public function readFormValuesFromRequest(AphrontRequest $request) {
$is_setup = $this->isSetup();
if ($is_setup) {
$name = $request->getStr(self::PROPERTY_JIRA_NAME);
} else {
$name = $this->getProviderDomain();
}
return array(
self::PROPERTY_JIRA_NAME => $name,
self::PROPERTY_JIRA_URI => $request->getStr(self::PROPERTY_JIRA_URI),
self::PROPERTY_REPORT_LINK =>
$request->getInt(self::PROPERTY_REPORT_LINK, 0),
self::PROPERTY_REPORT_COMMENT =>
$request->getInt(self::PROPERTY_REPORT_COMMENT, 0),
);
}
public function processEditForm(
AphrontRequest $request,
array $values) {
$errors = array();
$issues = array();
$is_setup = $this->isSetup();
$key_name = self::PROPERTY_JIRA_NAME;
$key_uri = self::PROPERTY_JIRA_URI;
if (!strlen($values[$key_name])) {
$errors[] = pht('JIRA instance name is required.');
$issues[$key_name] = pht('Required');
} else if (!preg_match('/^[a-z0-9.]+\z/', $values[$key_name])) {
$errors[] = pht(
'JIRA instance name must contain only lowercase letters, digits, and '.
'period.');
$issues[$key_name] = pht('Invalid');
}
if (!strlen($values[$key_uri])) {
$errors[] = pht('JIRA base URI is required.');
$issues[$key_uri] = pht('Required');
} else {
$uri = new PhutilURI($values[$key_uri]);
if (!$uri->getProtocol()) {
$errors[] = pht(
'JIRA base URI should include protocol (like "https://").');
$issues[$key_uri] = pht('Invalid');
}
}
if (!$errors && $is_setup) {
$config = $this->getProviderConfig();
$config->setProviderDomain($values[$key_name]);
$consumer_key = 'phjira.'.Filesystem::readRandomCharacters(16);
list($public, $private) = PhutilJIRAAuthAdapter::newJIRAKeypair();
$config->setProperty(self::PROPERTY_PUBLIC_KEY, $public);
$config->setProperty(self::PROPERTY_PRIVATE_KEY, $private);
$config->setProperty(self::PROPERTY_CONSUMER_KEY, $consumer_key);
}
return array($errors, $issues, $values);
}
public function extendEditForm(
AphrontRequest $request,
AphrontFormView $form,
array $values,
array $issues) {
if (!function_exists('openssl_pkey_new')) {
// TODO: This could be a bit prettier.
throw new Exception(
pht(
"The PHP 'openssl' extension is not installed. You must install ".
"this extension in order to add a JIRA authentication provider, ".
"because JIRA OAuth requests use the RSA-SHA1 signing algorithm. ".
- "Install the 'openssl' extension, restart your webserver, and try ".
+ "Install the 'openssl' extension, restart Phabricator, and try ".
"again."));
}
$form->appendRemarkupInstructions(
pht(
'NOTE: This provider **only supports JIRA 6**. It will not work with '.
'JIRA 5 or earlier.'));
$is_setup = $this->isSetup();
$viewer = $request->getViewer();
$e_required = $request->isFormPost() ? null : true;
$v_name = $values[self::PROPERTY_JIRA_NAME];
if ($is_setup) {
$e_name = idx($issues, self::PROPERTY_JIRA_NAME, $e_required);
} else {
$e_name = null;
}
$v_uri = $values[self::PROPERTY_JIRA_URI];
$e_uri = idx($issues, self::PROPERTY_JIRA_URI, $e_required);
if ($is_setup) {
$form
->appendRemarkupInstructions(
pht(
"**JIRA Instance Name**\n\n".
"Choose a permanent name for this instance of JIRA. Phabricator ".
"uses this name internally to keep track of this instance of ".
"JIRA, in case the URL changes later.\n\n".
"Use lowercase letters, digits, and period. For example, ".
"`jira`, `jira.mycompany` or `jira.engineering` are reasonable ".
"names."))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('JIRA Instance Name'))
->setValue($v_name)
->setName(self::PROPERTY_JIRA_NAME)
->setError($e_name));
} else {
$form
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('JIRA Instance Name'))
->setValue($v_name));
}
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('JIRA Base URI'))
->setValue($v_uri)
->setName(self::PROPERTY_JIRA_URI)
->setCaption(
pht(
'The URI where JIRA is installed. For example: %s',
phutil_tag('tt', array(), 'https://jira.mycompany.com/')))
->setError($e_uri));
if (!$is_setup) {
$config = $this->getProviderConfig();
$ckey = $config->getProperty(self::PROPERTY_CONSUMER_KEY);
$ckey = phutil_tag('tt', array(), $ckey);
$pkey = $config->getProperty(self::PROPERTY_PUBLIC_KEY);
$pkey = phutil_escape_html_newlines($pkey);
$pkey = phutil_tag('tt', array(), $pkey);
$form
->appendRemarkupInstructions(
pht(
'NOTE: **To complete setup**, copy and paste these keys into JIRA '.
'according to the instructions below.'))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Consumer Key'))
->setValue($ckey))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Public Key'))
->setValue($pkey));
$form
->appendRemarkupInstructions(
pht(
'= Integration Options = '."\n".
'Configure how to record Revisions on JIRA tasks.'."\n\n".
'Note you\'ll have to restart the daemons for this to take '.
'effect.'))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
self::PROPERTY_REPORT_LINK,
1,
new PHUIRemarkupView(
$viewer,
pht(
'Create **Issue Link** to the Revision, as an "implemented '.
'in" relationship.')),
$this->shouldCreateJIRALink()))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
self::PROPERTY_REPORT_COMMENT,
1,
new PHUIRemarkupView(
$viewer,
pht(
'**Post a comment** in the JIRA task, similar to the '.
'emails Phabricator sends.')),
$this->shouldCreateJIRAComment()));
}
}
/**
* JIRA uses a setup step to generate public/private keys.
*/
public function hasSetupStep() {
return true;
}
public static function getJIRAProvider() {
$providers = self::getAllEnabledProviders();
foreach ($providers as $provider) {
if ($provider instanceof PhabricatorJIRAAuthProvider) {
return $provider;
}
}
return null;
}
public function newJIRAFuture(
PhabricatorExternalAccount $account,
$path,
$method,
$params = array()) {
$adapter = clone $this->getAdapter();
$adapter->setToken($account->getProperty('oauth1.token'));
$adapter->setTokenSecret($account->getProperty('oauth1.token.secret'));
return $adapter->newJIRAFuture($path, $method, $params);
}
public function shouldCreateJIRALink() {
$config = $this->getProviderConfig();
return $config->getProperty(self::PROPERTY_REPORT_LINK, true);
}
public function shouldCreateJIRAComment() {
$config = $this->getProviderConfig();
return $config->getProperty(self::PROPERTY_REPORT_COMMENT, true);
}
}
diff --git a/src/applications/cache/spec/PhabricatorOpcodeCacheSpec.php b/src/applications/cache/spec/PhabricatorOpcodeCacheSpec.php
index f9739a0328..37c02a437b 100644
--- a/src/applications/cache/spec/PhabricatorOpcodeCacheSpec.php
+++ b/src/applications/cache/spec/PhabricatorOpcodeCacheSpec.php
@@ -1,206 +1,206 @@
<?php
final class PhabricatorOpcodeCacheSpec extends PhabricatorCacheSpec {
public static function getActiveCacheSpec() {
$spec = new PhabricatorOpcodeCacheSpec();
// NOTE: If APCu is installed, it reports that APC is installed.
if (extension_loaded('apc') && !extension_loaded('apcu')) {
$spec->initAPCSpec();
} else if (extension_loaded('Zend OPcache')) {
$spec->initOpcacheSpec();
} else {
$spec->initNoneSpec();
}
return $spec;
}
private function initAPCSpec() {
$this
->setName(pht('APC'))
->setVersion(phpversion('apc'));
if (ini_get('apc.enabled')) {
$this
->setIsEnabled(true)
->setClearCacheCallback('apc_clear_cache');
$mem = apc_sma_info();
$this->setTotalMemory($mem['num_seg'] * $mem['seg_size']);
$info = apc_cache_info();
$this->setUsedMemory($info['mem_size']);
$write_lock = ini_get('apc.write_lock');
$slam_defense = ini_get('apc.slam_defense');
if (!$write_lock || $slam_defense) {
$summary = pht('Adjust APC settings to quiet unnecessary errors.');
$message = pht(
'Some versions of APC may emit unnecessary errors into the '.
'error log under the current APC settings. To resolve this, '.
'enable "%s" and disable "%s" in your PHP configuration.',
'apc.write_lock',
'apc.slam_defense');
$this
->newIssue('extension.apc.write-lock')
->setShortName(pht('Noisy APC'))
->setName(pht('APC Has Noisy Configuration'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('apc.write_lock')
->addPHPConfig('apc.slam_defense');
}
$is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
$is_stat_enabled = ini_get('apc.stat');
if ($is_stat_enabled && !$is_dev) {
$summary = pht(
'"%s" is currently enabled, but should probably be disabled.',
'apc.stat');
$message = pht(
'The "%s" setting is currently enabled in your PHP configuration. '.
'In production mode, "%s" should be disabled. '.
'This will improve performance slightly.',
'apc.stat',
'apc.stat');
$this
->newIssue('extension.apc.stat-enabled')
->setShortName(pht('"%s" Enabled', 'apc.stat'))
->setName(pht('"%s" Enabled in Production', 'apc.stat'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('apc.stat')
->addPhabricatorConfig('phabricator.developer-mode');
} else if (!$is_stat_enabled && $is_dev) {
$summary = pht(
'"%s" is currently disabled, but should probably be enabled.',
'apc.stat');
$message = pht(
'The "%s" setting is currently disabled in your PHP configuration, '.
'but Phabricator is running in development mode. This option should '.
'normally be enabled in development so you do not need to restart '.
- 'your webserver after making changes to the code.',
+ 'anything after making changes to the code.',
'apc.stat');
$this
->newIssue('extension.apc.stat-disabled')
->setShortName(pht('"%s" Disabled', 'apc.stat'))
->setName(pht('"%s" Disabled in Development', 'apc.stat'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('apc.stat')
->addPhabricatorConfig('phabricator.developer-mode');
}
} else {
$this->setIsEnabled(false);
$this->raiseEnableAPCIssue();
}
}
private function initOpcacheSpec() {
$this
->setName(pht('Zend OPcache'))
->setVersion(phpversion('Zend OPcache'));
if (ini_get('opcache.enable')) {
$this
->setIsEnabled(true)
->setClearCacheCallback('opcache_reset');
$status = opcache_get_status();
$memory = $status['memory_usage'];
$mem_used = $memory['used_memory'];
$mem_free = $memory['free_memory'];
$mem_junk = $memory['wasted_memory'];
$this->setUsedMemory($mem_used + $mem_junk);
$this->setTotalMemory($mem_used + $mem_junk + $mem_free);
$this->setEntryCount($status['opcache_statistics']['num_cached_keys']);
$is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
$validate = ini_get('opcache.validate_timestamps');
$freq = ini_get('opcache.revalidate_freq');
if ($is_dev && (!$validate || $freq)) {
$summary = pht(
'OPcache is not configured properly for development.');
$message = pht(
'In development, OPcache should be configured to always reload '.
- 'code so the webserver does not need to be restarted after making '.
- 'changes. To do this, enable "%s" and set "%s" to 0.',
+ 'code so nothing needs to be restarted after making changes. To do '.
+ 'this, enable "%s" and set "%s" to 0.',
'opcache.validate_timestamps',
'opcache.revalidate_freq');
$this
->newIssue('extension.opcache.devmode')
->setShortName(pht('OPcache Config'))
->setName(pht('OPCache Not Configured for Development'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('opcache.validate_timestamps')
->addPHPConfig('opcache.revalidate_freq')
->addPhabricatorConfig('phabricator.developer-mode');
} else if (!$is_dev && $validate) {
$summary = pht('OPcache is not configured ideally for production.');
$message = pht(
'In production, OPcache should be configured to never '.
'revalidate code. This will slightly improve performance. '.
'To do this, disable "%s" in your PHP configuration.',
'opcache.validate_timestamps');
$this
->newIssue('extension.opcache.production')
->setShortName(pht('OPcache Config'))
->setName(pht('OPcache Not Configured for Production'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('opcache.validate_timestamps')
->addPhabricatorConfig('phabricator.developer-mode');
}
} else {
$this->setIsEnabled(false);
$summary = pht('Enabling OPcache will dramatically improve performance.');
$message = pht(
'The PHP "Zend OPcache" extension is installed, but not enabled in '.
'your PHP configuration. Enabling it will dramatically improve '.
'Phabricator performance. Edit the "%s" setting to '.
'enable the extension.',
'opcache.enable');
$this->newIssue('extension.opcache.enable')
->setShortName(pht('OPcache Disabled'))
->setName(pht('Zend OPcache Not Enabled'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('opcache.enable');
}
}
private function initNoneSpec() {
if (version_compare(phpversion(), '5.5', '>=')) {
$message = pht(
'Installing the "Zend OPcache" extension will dramatically improve '.
'performance.');
$this
->newIssue('extension.opcache')
->setShortName(pht('OPcache'))
->setName(pht('Zend OPcache Not Installed'))
->setMessage($message)
->addPHPExtension('Zend OPcache');
} else {
$this->raiseInstallAPCIssue();
}
}
}
diff --git a/src/applications/config/option/PhabricatorUIConfigOptions.php b/src/applications/config/option/PhabricatorUIConfigOptions.php
index d97225979d..84a7b4be6b 100644
--- a/src/applications/config/option/PhabricatorUIConfigOptions.php
+++ b/src/applications/config/option/PhabricatorUIConfigOptions.php
@@ -1,98 +1,98 @@
<?php
final class PhabricatorUIConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('User Interface');
}
public function getDescription() {
return pht('Configure the Phabricator UI, including colors.');
}
public function getFontIcon() {
return 'fa-magnet';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
$manifest = PHUIIconView::getSheetManifest('main-header');
$custom_header_example =
PhabricatorCustomHeaderConfigType::getExampleConfig();
$experimental_link = 'https://secure.phabricator.com/T4214';
$options = array();
foreach (array_keys($manifest) as $sprite_name) {
$key = substr($sprite_name, strlen('main-header-'));
$options[$key] = $key;
}
$example = <<<EOJSON
[
{
"name" : "Copyright 2199 Examplecorp"
},
{
"name" : "Privacy Policy",
"href" : "http://www.example.org/privacy/"
},
{
"name" : "Terms and Conditions",
"href" : "http://www.example.org/terms/"
}
]
EOJSON;
return array(
$this->newOption('ui.header-color', 'enum', 'blindigo')
->setDescription(
pht('Sets the color of the main header.'))
->setEnumOptions($options),
$this->newOption('ui.footer-items', 'list<wild>', array())
->setSummary(
pht(
'Allows you to add footer links on most pages.'))
->setDescription(
pht(
"Allows you to add a footer with links in it to most ".
"pages. You might want to use these links to point at legal ".
"information or an about page.\n\n".
"Specify a list of dictionaries. Each dictionary describes ".
"a footer item. These keys are supported:\n\n".
" - `name` The name of the item.\n".
" - `href` Optionally, the link target of the item. You can ".
" omit this if you just want a piece of text, like a copyright ".
" notice."))
->addExample($example, pht('Basic Example')),
$this->newOption(
'ui.custom-header',
'custom:PhabricatorCustomHeaderConfigType',
null)
->setSummary(
pht('Customize the Phabricator logo.'))
->setDescription(
pht('You can customize the Phabricator logo by specifying the '.
'phid for a viewable image you have uploaded to Phabricator '.
'via the [[ /file/ | Files application]]. This image should '.
'be:'."\n".
' - 192px X 80px; while not enforced, images with these '.
'dimensions will look best across devices.'."\n".
' - have view policy public if [[ '.
'/config/edit/policy.allow-public | `policy.allow-public`]] '.
'is true and otherwise view policy user; mismatches in these '.
'policy settings will result in a broken logo for some users.'.
"\n\n".
- 'You should restart your webserver after updating this value '.
+ 'You should restart Phabricator after updating this value '.
'to see this change take effect.'.
"\n\n".
'As this feature is experimental, please read [[ %s | T4214 ]] '.
'for up to date information.',
$experimental_link))
->addExample($custom_header_example, pht('Valid Config')),
);
}
}
diff --git a/src/applications/config/view/PhabricatorSetupIssueView.php b/src/applications/config/view/PhabricatorSetupIssueView.php
index cb8859bb86..ed82ffa7b7 100644
--- a/src/applications/config/view/PhabricatorSetupIssueView.php
+++ b/src/applications/config/view/PhabricatorSetupIssueView.php
@@ -1,550 +1,563 @@
<?php
final class PhabricatorSetupIssueView extends AphrontView {
private $issue;
public function setIssue(PhabricatorSetupIssue $issue) {
$this->issue = $issue;
return $this;
}
public function getIssue() {
return $this->issue;
}
public function render() {
$issue = $this->getIssue();
$description = array();
$description[] = phutil_tag(
'div',
array(
'class' => 'setup-issue-instructions',
),
phutil_escape_html_newlines($issue->getMessage()));
$configs = $issue->getPHPConfig();
if ($configs) {
$description[] = $this->renderPHPConfig($configs, $issue);
}
$configs = $issue->getMySQLConfig();
if ($configs) {
$description[] = $this->renderMySQLConfig($configs);
}
$configs = $issue->getPhabricatorConfig();
if ($configs) {
$description[] = $this->renderPhabricatorConfig($configs);
}
$related_configs = $issue->getRelatedPhabricatorConfig();
if ($related_configs) {
$description[] = $this->renderPhabricatorConfig($related_configs,
$related = true);
}
$commands = $issue->getCommands();
if ($commands) {
$run_these = pht('Run these %d command(s):', count($commands));
$description[] = phutil_tag(
'div',
array(
'class' => 'setup-issue-config',
),
array(
phutil_tag('p', array(), $run_these),
phutil_tag('pre', array(), phutil_implode_html("\n", $commands)),
));
}
$extensions = $issue->getPHPExtensions();
if ($extensions) {
$install_these = pht(
'Install these %d PHP extension(s):', count($extensions));
$install_info = pht(
'You can usually install a PHP extension using %s or %s. Common '.
'package names are %s or %s. Try commands like these:',
phutil_tag('tt', array(), 'apt-get'),
phutil_tag('tt', array(), 'yum'),
hsprintf('<tt>php-<em>%s</em></tt>', pht('extname')),
hsprintf('<tt>php5-<em>%s</em></tt>', pht('extname')));
// TODO: We should do a better job of detecting how to install extensions
// on the current system.
$install_commands = hsprintf(
"\$ sudo apt-get install php5-<em>extname</em> ".
"# Debian / Ubuntu\n".
"\$ sudo yum install php-<em>extname</em> ".
"# Red Hat / Derivatives");
$fallback_info = pht(
"If those commands don't work, try Google. The process of installing ".
"PHP extensions is not specific to Phabricator, and any instructions ".
"you can find for installing them on your system should work. On Mac ".
"OS X, you might want to try Homebrew.");
$restart_info = pht(
- 'After installing new PHP extensions, <strong>restart your webserver '.
- 'for the changes to take effect</strong>.',
- hsprintf(''));
+ 'After installing new PHP extensions, <strong>restart Phabricator '.
+ 'for the changes to take effect</strong>. For help with restarting '.
+ 'Phabricator, see %s in the documentation.',
+ $this->renderRestartLink());
$description[] = phutil_tag(
'div',
array(
'class' => 'setup-issue-config',
),
array(
phutil_tag('p', array(), $install_these),
phutil_tag('pre', array(), implode("\n", $extensions)),
phutil_tag('p', array(), $install_info),
phutil_tag('pre', array(), $install_commands),
phutil_tag('p', array(), $fallback_info),
phutil_tag('p', array(), $restart_info),
));
}
$related_links = $issue->getLinks();
if ($related_links) {
$description[] = $this->renderRelatedLinks($related_links);
}
$actions = array();
if (!$issue->getIsFatal()) {
if ($issue->getIsIgnored()) {
$actions[] = javelin_tag(
'a',
array(
'href' => '/config/unignore/'.$issue->getIssueKey().'/',
'sigil' => 'workflow',
'class' => 'button grey',
),
pht('Unignore Setup Issue'));
} else {
$actions[] = javelin_tag(
'a',
array(
'href' => '/config/ignore/'.$issue->getIssueKey().'/',
'sigil' => 'workflow',
'class' => 'button grey',
),
pht('Ignore Setup Issue'));
}
$actions[] = javelin_tag(
'a',
array(
'href' => '/config/issue/'.$issue->getIssueKey().'/',
'class' => 'button grey',
'style' => 'float: right',
),
pht('Reload Page'));
}
if ($actions) {
$actions = phutil_tag(
'div',
array(
'class' => 'setup-issue-actions',
),
$actions);
}
if ($issue->getIsIgnored()) {
$status = phutil_tag(
'div',
array(
'class' => 'setup-issue-status',
),
pht(
'This issue is currently ignored, and does not show a global '.
'warning.'));
$next = null;
} else {
$status = null;
$next = phutil_tag(
'div',
array(
'class' => 'setup-issue-next',
),
pht('To continue, resolve this problem and reload the page.'));
}
$name = phutil_tag(
'div',
array(
'class' => 'setup-issue-name',
),
$issue->getName());
$head = phutil_tag(
'div',
array(
'class' => 'setup-issue-head',
),
array($name, $status));
$tail = phutil_tag(
'div',
array(
'class' => 'setup-issue-tail',
),
array($actions));
$issue = phutil_tag(
'div',
array(
'class' => 'setup-issue',
),
array(
$head,
$description,
$tail,
));
$debug_info = phutil_tag(
'div',
array(
'class' => 'setup-issue-debug',
),
pht('Host: %s', php_uname('n')));
return phutil_tag(
'div',
array(
'class' => 'setup-issue-shell',
),
array(
$issue,
$next,
$debug_info,
));
}
private function renderPhabricatorConfig(array $configs, $related = false) {
$issue = $this->getIssue();
$table_info = phutil_tag(
'p',
array(),
pht(
'The current Phabricator configuration has these %d value(s):',
count($configs)));
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
$hidden = array();
foreach ($options as $key => $option) {
if ($option->getHidden()) {
$hidden[$key] = true;
}
}
$table = null;
$dict = array();
foreach ($configs as $key) {
if (isset($hidden[$key])) {
$dict[$key] = null;
} else {
$dict[$key] = PhabricatorEnv::getUnrepairedEnvConfig($key);
}
}
$table = $this->renderValueTable($dict, $hidden);
if ($this->getIssue()->getIsFatal()) {
$update_info = phutil_tag(
'p',
array(),
pht(
'To update these %d value(s), run these command(s) from the command '.
'line:',
count($configs)));
$update = array();
foreach ($configs as $key) {
$update[] = hsprintf(
'<tt>phabricator/ $</tt> ./bin/config set %s <em>value</em>',
$key);
}
$update = phutil_tag('pre', array(), phutil_implode_html("\n", $update));
} else {
$update = array();
foreach ($configs as $config) {
if (idx($options, $config) && $options[$config]->getLocked()) {
continue;
}
$link = phutil_tag(
'a',
array(
'href' => '/config/edit/'.$config.'/?issue='.$issue->getIssueKey(),
),
pht('Edit %s', $config));
$update[] = phutil_tag('li', array(), $link);
}
if ($update) {
$update = phutil_tag('ul', array(), $update);
if (!$related) {
$update_info = phutil_tag(
'p',
array(),
pht('You can update these %d value(s) here:', count($configs)));
} else {
$update_info = phutil_tag(
'p',
array(),
pht('These %d configuration value(s) are related:', count($configs)));
}
} else {
$update = null;
$update_info = null;
}
}
return phutil_tag(
'div',
array(
'class' => 'setup-issue-config',
),
array(
$table_info,
$table,
$update_info,
$update,
));
}
private function renderPHPConfig(array $configs, $issue) {
$table_info = phutil_tag(
'p',
array(),
pht(
'The current PHP configuration has these %d value(s):',
count($configs)));
$dict = array();
foreach ($configs as $key) {
$dict[$key] = $issue->getPHPConfigOriginalValue(
$key,
ini_get($key));
}
$table = $this->renderValueTable($dict);
ob_start();
phpinfo();
$phpinfo = ob_get_clean();
$rex = '@Loaded Configuration File\s*</td><td class="v">(.*?)</td>@i';
$matches = null;
$ini_loc = null;
if (preg_match($rex, $phpinfo, $matches)) {
$ini_loc = trim($matches[1]);
}
$rex = '@Additional \.ini files parsed\s*</td><td class="v">(.*?)</td>@i';
$more_loc = array();
if (preg_match($rex, $phpinfo, $matches)) {
$more_loc = trim($matches[1]);
if ($more_loc == '(none)') {
$more_loc = array();
} else {
$more_loc = preg_split('/\s*,\s*/', $more_loc);
}
}
$info = array();
if (!$ini_loc) {
$info[] = phutil_tag(
'p',
array(),
pht(
'To update these %d value(s), edit your PHP configuration file.',
count($configs)));
} else {
$info[] = phutil_tag(
'p',
array(),
pht(
'To update these %d value(s), edit your PHP configuration file, '.
'located here:',
count($configs)));
$info[] = phutil_tag(
'pre',
array(),
$ini_loc);
}
if ($more_loc) {
$info[] = phutil_tag(
'p',
array(),
pht(
'PHP also loaded these %s configuration file(s):',
phutil_count($more_loc)));
$info[] = phutil_tag(
'pre',
array(),
implode("\n", $more_loc));
}
$info[] = phutil_tag(
'p',
array(),
pht(
'You can find more information about PHP configuration values in the '.
'%s.',
phutil_tag(
'a',
array(
'href' => 'http://php.net/manual/ini.list.php',
'target' => '_blank',
),
pht('PHP Documentation'))));
$info[] = phutil_tag(
'p',
array(),
pht(
- 'After editing the PHP configuration, <strong>restart your '.
- 'webserver for the changes to take effect</strong>.',
- hsprintf('')));
+ 'After editing the PHP configuration, <strong>restart Phabricator for '.
+ 'the changes to take effect</strong>. For help with restarting '.
+ 'Phabricator, see %s in the documentation.',
+ $this->renderRestartLink()));
return phutil_tag(
'div',
array(
'class' => 'setup-issue-config',
),
array(
$table_info,
$table,
$info,
));
}
private function renderMySQLConfig(array $config) {
$values = array();
foreach ($config as $key) {
$value = PhabricatorMySQLSetupCheck::loadRawConfigValue($key);
if ($value === null) {
$value = phutil_tag(
'em',
array(),
pht('(Not Supported)'));
}
$values[$key] = $value;
}
$table = $this->renderValueTable($values);
$doc_href = PhabricatorEnv::getDoclink('User Guide: Amazon RDS');
$doc_link = phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
pht('User Guide: Amazon RDS'));
$info = array();
$info[] = phutil_tag(
'p',
array(),
pht(
'If you are using Amazon RDS, some of the instructions above may '.
'not apply to you. See %s for discussion of Amazon RDS.',
$doc_link));
$table_info = phutil_tag(
'p',
array(),
pht(
'The current MySQL configuration has these %d value(s):',
count($config)));
return phutil_tag(
'div',
array(
'class' => 'setup-issue-config',
),
array(
$table_info,
$table,
$info,
));
}
private function renderValueTable(array $dict, array $hidden = array()) {
$rows = array();
foreach ($dict as $key => $value) {
if (isset($hidden[$key])) {
$value = phutil_tag('em', array(), 'hidden');
} else {
$value = $this->renderValueForDisplay($value);
}
$cols = array(
phutil_tag('th', array(), $key),
phutil_tag('td', array(), $value),
);
$rows[] = phutil_tag('tr', array(), $cols);
}
return phutil_tag('table', array(), $rows);
}
private function renderValueForDisplay($value) {
if ($value === null) {
return phutil_tag('em', array(), 'null');
} else if ($value === false) {
return phutil_tag('em', array(), 'false');
} else if ($value === true) {
return phutil_tag('em', array(), 'true');
} else if ($value === '') {
return phutil_tag('em', array(), 'empty string');
} else if ($value instanceof PhutilSafeHTML) {
return $value;
} else {
return PhabricatorConfigJSON::prettyPrintJSON($value);
}
}
private function renderRelatedLinks(array $links) {
$link_info = phutil_tag(
'p',
array(),
pht(
'%d related link(s):',
count($links)));
$link_list = array();
foreach ($links as $link) {
$link_tag = phutil_tag(
'a',
array(
'target' => '_blank',
'href' => $link['href'],
),
$link['name']);
$link_item = phutil_tag('li', array(), $link_tag);
$link_list[] = $link_item;
}
$link_list = phutil_tag('ul', array(), $link_list);
return phutil_tag(
'div',
array(
'class' => 'setup-issue-config',
),
array(
$link_info,
$link_list,
));
}
+ private function renderRestartLink() {
+ $doc_href = PhabricatorEnv::getDoclink('Restarting Phabricator');
+ return phutil_tag(
+ 'a',
+ array(
+ 'href' => $doc_href,
+ 'target' => '_blank',
+ ),
+ pht('Restarting Phabricator'));
+ }
+
}
diff --git a/src/docs/user/configuration/advanced_configuration.diviner b/src/docs/user/configuration/advanced_configuration.diviner
index 70a78fc902..17212ad4ee 100644
--- a/src/docs/user/configuration/advanced_configuration.diviner
+++ b/src/docs/user/configuration/advanced_configuration.diviner
@@ -1,116 +1,117 @@
@title Configuration User Guide: Advanced Configuration
@group config
Configuring Phabricator for multiple environments.
= Overview =
Phabricator reads configuration from multiple sources. This document explains
the configuration stack and how to set up advanced configuration sources, which
may be useful for deployments with multiple environments (e.g., development and
production).
This is a complicated topic for advanced users. You do not need to understand
this topic to install Phabricator.
= Configuration Sources =
Phabricator supports the following configuration sources, from highest priority
to lowest priority:
- **Database**: Values are stored in the database and edited from the web UI
by administrators. They have the highest priority and override other
settings.
- **Local**: Values are stored in `conf/local/config.json` and edited by
running `bin/config`.
- **Config Files**: Values are stored in a config file in `conf/`. The file
to use is selected by writing to `conf/local/ENVIRONMENT`, or setting the
`PHABRICATOR_ENV` configuration variable. See below for more information.
- **Defaults**: Defaults hard-coded in the Phabricator source, which can not
be edited. They have the lowest priority, and all other settings override
them.
Normally, you install and configure Phabricator by writing enough configuration
into the local config to get access to the database configuration (e.g., the
MySQL username, password, and hostname), then use the web interface to further
configure Phabricator.
= Configuration Files =
Configuration files provide an alternative to database configuration, and may be
appropriate if you want to deploy in multiple environments or create dynamic
configuration. Configuration files are more complicated than database
configuration, which is why they are not used by default.
== Creating a Configuration File ==
To create a configuration file, first choose a name for the config (like
"devserver" or "live"). For the purposes of this section, we'll assume you chose
`exampleconfig`. Replace "exampleconfig" with whatever you actually chose in the
examples below.
First, write an `exampleconfig.conf.php` file here (rename it according to the
name you chose):
phabricator/conf/custom/exampleconfig.conf.php
Its contents should look like this:
<?php
return array(
// Specify whichever keys and values you want to set.
'example.key' => 'examplevalue',
);
For example, to specify MySQL credentials in your config file, you might create
a config like this:
<?php
return array(
'mysql.host' => 'localhost',
'mysql.user' => 'root',
'mysql.pass' => 'hunter2trustno1',
);
== Selecting a Configuration File ==
To select a configuration file, write the name of the file (relative to
`phabricator/conf/`) to `phabricator/conf/local/ENVIRONMENT`. For example, to
select `phabricator/conf/custom/exampleconfig.conf.php`, you would write
"custom/exampleconfig" to `phabrictor/conf/local/ENVIRONMENT`:
phabricator/ $ echo custom/exampleconfig > conf/local/ENVIRONMENT
phabricator/ $ cat conf/local/ENVIRONMENT
custom/exampleconfig
phabricator/ $
You can also set the environmental variable `PHABRICATOR_ENV`. This is more
involved but may be easier in some deployment environments. Note that this needs
to be set in your webserver environment, and also in your shell whenever you
run a script:
```
# Shell
export PHABRICATOR_ENV=custom/exampleconfig
# Apache
SetEnv PHABRICATOR_ENV custom/exampleconfig
# nginx
fastcgi_param PHABRICATOR_ENV "custom/exampleconfig";
# lighttpd
setenv.add-environment = (
"PHABRICATOR_ENV" => "custom/exampleconfig",
)
```
-After creating and selecting a configuration file, restart your webserver. Any
-configuration you set should take effect immediately, and your file should be
-visible in the Config application when examining configuration.
+After creating and selecting a configuration file, restart Phabricator (for
+help, see @{article:Restarting Phabricator}). Any configuration you set should
+take effect immediately, and your file should be visible in the Config
+application when examining configuration.
= Next Steps =
Return to the @{article:Configuration Guide}.
diff --git a/src/docs/user/configuration/custom_fields.diviner b/src/docs/user/configuration/custom_fields.diviner
index 7ecfa98723..9bd1bb0e28 100644
--- a/src/docs/user/configuration/custom_fields.diviner
+++ b/src/docs/user/configuration/custom_fields.diviner
@@ -1,217 +1,219 @@
@title Configuring Custom Fields
@group config
How to add custom fields to applications which support them.
= Overview =
Several Phabricator applications allow the configuration of custom fields. These
fields allow you to add more information to objects, and in some cases reorder
or remove builtin fields.
For example, you could use custom fields to add an "Estimated Hours" field to
tasks, a "Lead" field to projects, or a "T-Shirt Size" field to users.
These applications currently support custom fields:
| Application | Support |
|-------------|---------|
| Differential | Partial Support |
| Diffusion | Limited Support |
| Maniphest | Full Support |
| Owners | Full Support |
| People | Full Support |
| Projects | Full Support |
Custom fields can appear in many interfaces and support search, editing, and
other features.
= Basic Custom Fields =
To get started with custom fields, you can use configuration to select and
reorder fields and to add new simple fields.
If you don't need complicated display controls or sophisticated validation,
these simple fields should cover most use cases. They allow you to attach
things like strings, numbers, and dropdown menus to objects.
The relevant configuration settings are:
| Application | Add Fields | Select Fields |
|-------------|------------|---------------|
| Differential | Planned | `differential.fields` |
| Diffusion | Planned | Planned |
| Maniphest | `maniphest.custom-field-definitions` | `maniphest.fields` |
| Owners | `owners.custom-field-definitions` | `owners.fields` |
| People | `user.custom-field-definitions` | `user.fields` |
| Projects | `projects.custom-field-definitions` | `projects.fields` |
When adding fields, you'll specify a JSON blob like this (for example, as the
value of `maniphest.custom-field-definitions`):
{
"mycompany:estimated-hours": {
"name": "Estimated Hours",
"type": "int",
"caption": "Estimated number of hours this will take.",
"required": true
},
"mycompany:actual-hours": {
"name": "Actual Hours",
"type": "int",
"caption": "Actual number of hours this took."
},
"mycompany:company-jobs": {
"name": "Job Role",
"type": "select",
"options": {
"mycompany:engineer": "Engineer",
"mycompany:nonengineer": "Other"
}
},
"mycompany:favorite-dinosaur": {
"name": "Favorite Dinosaur",
"type": "text"
}
}
The fields will then appear in the other config option for the application
(for example, in `maniphest.fields`) and you can enable, disable, or reorder
them.
For details on how to define a field, see the next section.
= Custom Field Configuration =
When defining custom fields using a configuration option like
`maniphest.custom-field-definitions`, these options are available:
- **name**: Display label for the field on the edit and detail interfaces.
- **description**: Optional text shown when managing the field.
- **type**: Field type. The supported field types are:
- **int**: An integer, rendered as a text field.
- **text**: A string, rendered as a text field.
- **bool**: A boolean value, rendered as a checkbox.
- **select**: Allows the user to select from several options as defined
by **options**, rendered as a dropdown.
- **remarkup**: A text area which allows the user to enter markup.
- **users**: A typeahead which allows multiple users to be input.
- **date**: A date/time picker.
- **header**: Renders a visual divider which you can use to group fields.
- **link**: A text field which allows the user to enter a link.
- **edit**: Show this field on the application's edit interface (this
defaults to `true`).
- **view**: Show this field on the application's view interface (this
defaults to `true`). (Note: Empty fields are not shown.)
- **search**: Show this field on the application's search interface, allowing
users to filter objects by the field value.
- **fulltext**: Index the text in this field as part of the object's global
full-text index. This allows users to find the object by searching for
the field's contents using global search.
- **caption**: A caption to display underneath the field (optional).
- **required**: True if the user should be required to provide a value.
- **options**: If type is set to **select**, provide options for the dropdown
as a dictionary.
- **default**: Default field value.
- **strings**: Allows you to override specific strings based on the field
type. See below.
- **instructions**: Optional block of remarkup text which will appear
above the control when rendered on the edit view.
- **placeholder**: A placeholder text that appears on text boxes. Only
supported in text, int and remarkup fields (optional).
The `strings` value supports different strings per control type. They are:
- **bool**
- **edit.checkbox** Text for the edit interface, no default.
- **view.yes** Text for the view interface, defaults to "Yes".
- **search.default** Text for the search interface, defaults to "(Any)".
- **search.require** Text for the search interface, defaults to "Require".
Some applications have specific options which only work in that application.
In **Maniphest**:
- **copy**: When a user creates a task, the UI gives them an option to
"Create Another Similar Task". Some fields from the original task are copied
into the new task, while others are not; by default, fields are not copied.
If you want this field to be copied, specify `true` for the `copy` property.
Internally, Phabricator implements some additional custom field types and
options. These are not intended for general use and are subject to abrupt
change, but are documented here for completeness:
- **Credentials**: Controls with type `credential` allow selection of a
Passphrase credential which provides `credential.provides`, and creation
of credentials of `credential.type`.
- **Datasource**: Controls with type `datasource` allow selection of tokens
from an arbitrary datasource, controlled with `datasource.class` and
`datasource.parameters`.
= Advanced Custom Fields =
If you want custom fields to have advanced behaviors (sophisticated rendering,
advanced validation, complicated controls, interaction with other systems, etc),
you can write a custom field as an extension and add it to Phabricator.
NOTE: This API is somewhat new and fairly large. You should expect that there
will be occasional changes to the API requiring minor updates in your code.
To do this, extend the appropriate `CustomField` class for the application you
want to add a field to:
| Application | Extend |
|-------------|---------|
| Differential | @{class:DifferentialCustomField} |
| Diffusion | @{class:PhabricatorCommitCustomField} |
| Maniphest | @{class:ManiphestCustomField} |
| Owners | @{class:PhabricatorOwnersCustomField} |
| People | @{class:PhabricatorUserCustomField} |
| Projects | @{class:PhabricatorProjectCustomField} |
The easiest way to get started is to drop your subclass into
-`phabricator/src/extensions/`, which should make it immediately available in the
-UI (if you use APC, you may need to restart your webserver). For example, this
-is a simple template which adds a custom field to Maniphest:
+`phabricator/src/extensions/`. If Phabricator is configured in development
+mode, the class should immediately be available in the UI. If not, you can
+restart Phabricator (for help, see @{article:Restarting Phabricator}).
+
+For example, this is a simple template which adds a custom field to Maniphest:
name=ExampleManiphestCustomField.php
<?php
final class ExampleCustomField extends ManiphestCustomField {
public function getFieldKey() {
return 'example:test';
}
public function shouldAppearInPropertyView() {
return true;
}
public function renderPropertyViewLabel() {
return pht('Example Custom Field');
}
public function renderPropertyViewValue(array $handles) {
return phutil_tag(
'h1',
array(
'style' => 'color: #ff00ff',
),
pht('It worked!'));
}
}
Broadly, you can then add features by overriding more methods and implementing
them. Many of the native fields are implemented on the custom field
architecture, and it may be useful to look at them. For details on available
integrations, see the base class for your application and
@{class:PhabricatorCustomField}.
= Next Steps =
Continue by:
- learning more about extending Phabricator with custom code in
@{article@phabcontrib:Adding New Classes};
- or returning to the @{article: Configuration Guide}.
diff --git a/src/docs/user/field/restarting.diviner b/src/docs/user/field/restarting.diviner
new file mode 100644
index 0000000000..5bdd92da78
--- /dev/null
+++ b/src/docs/user/field/restarting.diviner
@@ -0,0 +1,116 @@
+@title Restarting Phabricator
+@group fieldmanual
+
+Instructions on how to restart HTTP and PHP servers to reload configuration
+changes in Phabricator.
+
+
+Overview
+========
+
+Phabricator's setup and configuration instructions sometimes require you to
+restart your server processes, particularly after making configuration changes.
+This document explains how to restart them properly.
+
+In general, you need to restart both whatever is serving HTTP requests and
+whatever is serving PHP requests. In some cases, these will be the same process
+and handled with one restart command. In other cases, they will be two
+different processes and handled with two different restart commands.
+
+{icon exclamation-circle color=blue} If you have two different processes (for
+example, nginx and PHP-FPM), you need to issue two different restart commands.
+
+It's important to restart both your HTTP server and PHP server because each
+server caches different configuration and settings. Restarting both servers
+after making changes ensures you're running up-to-date configuration.
+
+To restart properly:
+
+ - Identify which HTTP server you are running (for example, Apache or nginx).
+ - Identify which PHP server you are running (for example, mod_php or PHP-FPM).
+ - For each server, follow the instructions below to restart it.
+ - If the instructions tell you to do so, make sure you restart **both**
+ servers!
+
+
+Quick Start
+===========
+
+**Apache**: If you use Apache with `mod_php`, you can just restart Apache. You
+do not need to restart `mod_php` separately. See below for instructions on how
+to do this if you aren't sure. This is a very common configuration.
+
+**nginx**: If you use nginx with PHP-FPM, you need to restart both nginx and
+PHP-FPM. See below for instructions on how to do this if you aren't sure. This
+is also a very common configuration.
+
+It's possible to use Apache or nginx in other configurations, or a different
+webserver. Consult the documentation for your system or webserver if you aren't
+sure how things are set up.
+
+
+Universal Restart
+=================
+
+If you are having trouble properly restarting processes on your server, try
+turning it off and on again. This is effective on every known system and
+under all configurations.
+
+
+HTTP Server: Apache
+===================
+
+If you are using Apache with `mod_php`, you only need to restart Apache.
+
+If you are using Apache in FastCGI mode, you need to restart both Apache and
+the FCGI server (usually PHP-FPM). This is very unusual.
+
+The correct method for restarting Apache depends on what system you are
+running. Consult your system documentation for details. You might use a command
+like one of these on your system, or a different command:
+
+```
+$ sudo apachectl restart
+$ sudo /etc/init.d/httpd restart
+$ sudo service apache2 restart
+```
+
+
+HTTP Server: Nginx
+==================
+
+If you're using Nginx with PHP-FPM, you need to restart both of them. This is
+the most common Nginx configuration.
+
+The correct method for restarting Nginx depends on what system you are running.
+Consult your system documentation for details. You might use a command like
+one of these on your system, or a different command:
+
+```
+$ sudo /etc/init.d/nginx restart
+$ sudo service nginx restart
+```
+
+
+PHP Server: mod_php
+===================
+
+This is a builtin PHP server that runs within Apache. Restarting Apache (see
+above) is sufficient to restart it. There is no separate restart command for
+`mod_php`, so you don't need to do anything else.
+
+
+PHP Server: PHP-FPM
+===================
+
+If you're using FastCGI mode, PHP-FPM is the most common PHP FastCGI server.
+You'll need to restart it if you're running it.
+
+The correct method for restarting PHP-FPM depends on what system you are
+running. Consult your system documentation for details. You might use a command
+like one of these on your system, or a different command:
+
+```
+$ sudo /etc/init.d/php-fpm restart
+$ sudo service php5-fpm reload
+```
diff --git a/src/docs/user/upgrading.diviner b/src/docs/user/upgrading.diviner
index 4291ded636..d9eca240ec 100644
--- a/src/docs/user/upgrading.diviner
+++ b/src/docs/user/upgrading.diviner
@@ -1,133 +1,134 @@
@title Upgrading Phabricator
@group intro
This document contains instructions for keeping Phabricator up to date.
Overview
========
Phabricator is under active development, and new features are released
continuously. Staying up to date will keep your install secure.
We recommend installs upgrade regularly (every 1-2 weeks). Upgrades usually go
smoothly and complete in a few minutes. If you put off upgrades for a long
time, it may take a lot more work to bring things up to date if you want access
to a useful new feature or an important security change.
Staying On Top of Changes
=========================
We release a weekly [[https://secure.phabricator.com/w/changelog | Changelog]],
which describes changes in the previous week. You can look at the changelogs
for an idea of what new features are available, upcoming changes, security
information, and warnings about compatibility issues or migrations.
Stable Branch
=============
You can either run the `master` or `stable` branch of Phabricator. The `stable`
branch is run in the [[ https://phacility.com | Phacility Cluster ]], and lags
about a week behind `master`.
The `stable` branch is a little more stable than `master`, and may be helpful
if you administrate a larger install.
We promote `master` to `stable` about once a week, then publish the changelog
and deploy the cluster. During the week, major bugfixes are cherry-picked to
the `stable` branch. The changelog lists the `stable` hashes for that week,
as well as any fixes which were cherry-picked.
To switch to `stable`, check the branch out in each working copy:
phabricator/ $ git checkout stable
arcanist/ $ git checkout stable
libphutil/ $ git checkout stable
You can now follow the upgrade process normally.
Upgrade Process
===============
-IMPORTANT: You **MUST** restart Apache or PHP-FPM after upgrading.
+IMPORTANT: You **MUST** restart Phabricator after upgrading. For help, see
+@{article:Restarting Phabricator}.
IMPORTANT: You **MUST** upgrade `libphutil`, `arcanist` and `phabricator` at
the same time.
Phabricator runs on many different systems, with many different webservers.
Given this diversity, we don't currently maintain a comprehensive upgrade
script which can work on any system. However, the general steps are the same
on every system:
- Stop the webserver (including `php-fpm`, if you use it).
- Stop the daemons, with `phabricator/bin/phd stop`.
- Run `git pull` in `libphutil/`, `arcanist/` and `phabricator/`.
- Run `phabricator/bin/storage upgrade`.
- Start the daemons, with `phabricator/bin/phd start`.
- Restart the webserver (and `php-fpm`, if you stopped it earlier).
For some more discussion details, see @{article:Configuration Guide}.
This template script roughly outlines the steps required to upgrade Phabricator.
You'll need to adjust paths and commands a bit for your particular system:
```lang=sh
#!/bin/sh
set -e
set -x
# This is an example script for updating Phabricator, similar to the one used to
# update <https://secure.phabricator.com/>. It might not work perfectly on your
# system, but hopefully it should be easy to adapt. This script is not intended
# to work without modifications.
# NOTE: This script assumes you are running it from a directory which contains
# arcanist/, libphutil/, and phabricator/.
ROOT=`pwd` # You can hard-code the path here instead.
### UPDATE WORKING COPIES ######################################################
cd $ROOT/libphutil
git pull
cd $ROOT/arcanist
git pull
cd $ROOT/phabricator
git pull
### CYCLE WEB SERVER AND DAEMONS ###############################################
# Stop daemons.
$ROOT/phabricator/bin/phd stop
# If running the notification server, stop it.
# $ROOT/phabricator/bin/aphlict stop
# Stop the webserver (apache, nginx, lighttpd, etc). This command will differ
# depending on which system and webserver you are running: replace it with an
# appropriate command for your system.
# NOTE: If you're running php-fpm, you should stop it here too.
sudo /etc/init.d/httpd stop
# Upgrade the database schema. You may want to add the "--force" flag to allow
# this script to run noninteractively.
$ROOT/phabricator/bin/storage upgrade
# Restart the webserver. As above, this depends on your system and webserver.
# NOTE: If you're running php-fpm, restart it here too.
sudo /etc/init.d/httpd start
# Restart daemons.
$ROOT/phabricator/bin/phd start
# If running the notification server, start it.
# $ROOT/phabricator/bin/aphlict start
```

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 15, 2:11 AM (14 h, 19 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
337561
Default Alt Text
(60 KB)

Event Timeline