mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-10 08:52:39 +01:00
Merge branch 'master' of github.com:facebook/arcanist into get_engine
This commit is contained in:
commit
e9b7f8e3ca
27 changed files with 1233 additions and 95 deletions
|
@ -122,7 +122,7 @@ foreach (Futures($futures) as $file => $future) {
|
|||
}
|
||||
$requirements->addSourceDependency($name, $value);
|
||||
} else if ($call_name == 'phutil_require_module') {
|
||||
analyze_phutil_require_module($call, $requirements);
|
||||
analyze_phutil_require_module($call, $requirements, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -154,7 +154,7 @@ foreach (Futures($futures) as $file => $future) {
|
|||
|
||||
$call_name = $name->getConcreteString();
|
||||
if ($call_name == 'phutil_require_module') {
|
||||
analyze_phutil_require_module($call, $requirements);
|
||||
analyze_phutil_require_module($call, $requirements, false);
|
||||
} else if ($call_name == 'call_user_func' ||
|
||||
$call_name == 'call_user_func_array') {
|
||||
$params = $call->getChildByIndex(1)->getChildren();
|
||||
|
@ -303,7 +303,7 @@ foreach (Futures($futures) as $file => $future) {
|
|||
$extends = $interface->getChildByIndex(2);
|
||||
foreach ($extends->selectDescendantsOfType('n_CLASS_NAME') as $parent) {
|
||||
$requirements->addInterfaceDependency(
|
||||
$class_name->getConcreteString(),
|
||||
$interface_name->getConcreteString(),
|
||||
$parent,
|
||||
$parent->getConcreteString());
|
||||
}
|
||||
|
@ -327,7 +327,8 @@ echo json_encode($requirements->toDictionary());
|
|||
*/
|
||||
function analyze_phutil_require_module(
|
||||
XHPASTNode $call,
|
||||
PhutilModuleRequirements $requirements) {
|
||||
PhutilModuleRequirements $requirements,
|
||||
$create_dependency) {
|
||||
|
||||
$name = $call->getChildByIndex(0);
|
||||
$params = $call->getChildByIndex(1)->getChildren();
|
||||
|
@ -363,7 +364,9 @@ function analyze_phutil_require_module(
|
|||
return;
|
||||
}
|
||||
|
||||
$requirements->addModuleDependency(
|
||||
$name,
|
||||
$library_value.':'.$module_value);
|
||||
if ($create_dependency) {
|
||||
$requirements->addModuleDependency(
|
||||
$name,
|
||||
$library_value.':'.$module_value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistDifferentialCommitMessage' => 'differential/commitmessage',
|
||||
'ArcanistDifferentialCommitMessageParserException' => 'differential/commitmessage',
|
||||
'ArcanistDifferentialRevisionRef' => 'differential/revision',
|
||||
'ArcanistDownloadWorkflow' => 'workflow/download',
|
||||
'ArcanistExportWorkflow' => 'workflow/export',
|
||||
'ArcanistFilenameLinter' => 'lint/linter/filename',
|
||||
'ArcanistGeneratedLinter' => 'lint/linter/generated',
|
||||
|
@ -54,11 +55,13 @@ phutil_register_library_map(array(
|
|||
'ArcanistLinterTestCase' => 'lint/linter/base/test',
|
||||
'ArcanistListWorkflow' => 'workflow/list',
|
||||
'ArcanistMarkCommittedWorkflow' => 'workflow/mark-committed',
|
||||
'ArcanistMercurialAPI' => 'repository/api/mercurial',
|
||||
'ArcanistNoEffectException' => 'exception/usage/noeffect',
|
||||
'ArcanistNoEngineException' => 'exception/usage/noengine',
|
||||
'ArcanistNoLintLinter' => 'lint/linter/nolint',
|
||||
'ArcanistNoLintTestCaseMisnamed' => 'lint/linter/nolint/__tests__',
|
||||
'ArcanistPEP8Linter' => 'lint/linter/pep8',
|
||||
'ArcanistPasteWorkflow' => 'workflow/paste',
|
||||
'ArcanistPatchWorkflow' => 'workflow/patch',
|
||||
'ArcanistPhutilModuleLinter' => 'lint/linter/phutilmodule',
|
||||
'ArcanistPhutilTestCase' => 'unit/engine/phutil/testcase',
|
||||
|
@ -73,9 +76,11 @@ phutil_register_library_map(array(
|
|||
'ArcanistTextLinterTestCase' => 'lint/linter/text/__tests__',
|
||||
'ArcanistUnitTestResult' => 'unit/result',
|
||||
'ArcanistUnitWorkflow' => 'workflow/unit',
|
||||
'ArcanistUploadWorkflow' => 'workflow/upload',
|
||||
'ArcanistUsageException' => 'exception/usage',
|
||||
'ArcanistUserAbortException' => 'exception/usage/userabort',
|
||||
'ArcanistWorkingCopyIdentity' => 'workingcopyidentity',
|
||||
'ArcanistXHPASTLintNamingHook' => 'lint/linter/xhpast/naminghook',
|
||||
'ArcanistXHPASTLinter' => 'lint/linter/xhpast',
|
||||
'ArcanistXHPASTLinterTestCase' => 'lint/linter/xhpast/__tests__',
|
||||
'BranchInfo' => 'branch',
|
||||
|
@ -100,6 +105,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistCoverWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistDiffParserTestCase' => 'ArcanistPhutilTestCase',
|
||||
'ArcanistDiffWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistDownloadWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistExportWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistFilenameLinter' => 'ArcanistLinter',
|
||||
'ArcanistGeneratedLinter' => 'ArcanistLinter',
|
||||
|
@ -114,11 +120,13 @@ phutil_register_library_map(array(
|
|||
'ArcanistLinterTestCase' => 'ArcanistPhutilTestCase',
|
||||
'ArcanistListWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistMarkCommittedWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI',
|
||||
'ArcanistNoEffectException' => 'ArcanistUsageException',
|
||||
'ArcanistNoEngineException' => 'ArcanistUsageException',
|
||||
'ArcanistNoLintLinter' => 'ArcanistLinter',
|
||||
'ArcanistNoLintTestCaseMisnamed' => 'ArcanistLinterTestCase',
|
||||
'ArcanistPEP8Linter' => 'ArcanistLinter',
|
||||
'ArcanistPasteWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistPatchWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistPhutilModuleLinter' => 'ArcanistLinter',
|
||||
'ArcanistPyFlakesLinter' => 'ArcanistLinter',
|
||||
|
@ -129,6 +137,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistTextLinter' => 'ArcanistLinter',
|
||||
'ArcanistTextLinterTestCase' => 'ArcanistLinterTestCase',
|
||||
'ArcanistUnitWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistUploadWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistUserAbortException' => 'ArcanistUsageException',
|
||||
'ArcanistXHPASTLinter' => 'ArcanistLinter',
|
||||
'ArcanistXHPASTLinterTestCase' => 'ArcanistLinterTestCase',
|
||||
|
|
|
@ -28,7 +28,7 @@ class ArcanistDifferentialRevisionRef {
|
|||
protected $statusName;
|
||||
protected $sourcePath;
|
||||
|
||||
public function newFromDictionary(array $dictionary) {
|
||||
public static function newFromDictionary(array $dictionary) {
|
||||
$ref = new ArcanistDifferentialRevisionRef();
|
||||
$ref->id = $dictionary['id'];
|
||||
$ref->name = $dictionary['name'];
|
||||
|
|
|
@ -65,7 +65,7 @@ abstract class ArcanistLintEngine {
|
|||
protected $charToLine = array();
|
||||
protected $lineToFirstChar = array();
|
||||
private $results = array();
|
||||
private $minimumSeverity = null;
|
||||
private $minimumSeverity = ArcanistLintSeverity::SEVERITY_DISABLED;
|
||||
|
||||
private $changedLines = array();
|
||||
private $commitHookMode = false;
|
||||
|
|
|
@ -155,6 +155,30 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
|
|||
$this->lintPlusOperatorOnStrings($root);
|
||||
$this->lintDuplicateKeysInArray($root);
|
||||
$this->lintReusedIterators($root);
|
||||
$this->lintBraceFormatting($root);
|
||||
}
|
||||
|
||||
private function lintBraceFormatting($root) {
|
||||
|
||||
foreach ($root->selectDescendantsOfType('n_STATEMENT_LIST') as $list) {
|
||||
$tokens = $list->getTokens();
|
||||
if (!$tokens || head($tokens)->getValue() != '{') {
|
||||
continue;
|
||||
}
|
||||
list($before, $after) = $list->getSurroundingNonsemanticTokens();
|
||||
if (count($before) == 1) {
|
||||
$before = reset($before);
|
||||
if ($before->getValue() != ' ') {
|
||||
$this->raiseLintAtToken(
|
||||
$before,
|
||||
self::LINT_FORMATTING_CONVENTIONS,
|
||||
'Put opening braces on the same line as control statements and '.
|
||||
'declarations, with a single space before them.',
|
||||
' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function lintTautologicalExpressions($root) {
|
||||
|
@ -641,27 +665,37 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
|
|||
}
|
||||
|
||||
protected function lintNamingConventions($root) {
|
||||
|
||||
// We're going to build up a list of <type, name, token, error> tuples
|
||||
// and then try to instantiate a hook class which has the opportunity to
|
||||
// override us.
|
||||
$names = array();
|
||||
|
||||
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
|
||||
foreach ($classes as $class) {
|
||||
$name_token = $class->getChildByIndex(1);
|
||||
$name_string = $name_token->getConcreteString();
|
||||
$is_xhp = ($name_string[0] == ':');
|
||||
if ($is_xhp) {
|
||||
if (!$this->isLowerCaseWithXHP($name_string)) {
|
||||
$this->raiseLintAtNode(
|
||||
$name_token,
|
||||
self::LINT_NAMING_CONVENTIONS,
|
||||
'Follow naming conventions: xhp elements should be named using '.
|
||||
'lower case.');
|
||||
}
|
||||
$names[] = array(
|
||||
'xhp-class',
|
||||
$name_string,
|
||||
$name_token,
|
||||
$this->isLowerCaseWithXHP($name_string)
|
||||
? null
|
||||
: 'Follow naming conventions: XHP elements should be named using '.
|
||||
'lower case.',
|
||||
);
|
||||
} else {
|
||||
if (!$this->isUpperCamelCase($name_string)) {
|
||||
$this->raiseLintAtNode(
|
||||
$name_token,
|
||||
self::LINT_NAMING_CONVENTIONS,
|
||||
'Follow naming conventions: classes should be named using '.
|
||||
'UpperCamelCase.');
|
||||
}
|
||||
$names[] = array(
|
||||
'class',
|
||||
$name_string,
|
||||
$name_token,
|
||||
$this->isUpperCamelCase($name_string)
|
||||
? null
|
||||
: 'Follow naming conventions: classes should be named using '.
|
||||
'UpperCamelCase.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -669,13 +703,15 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
|
|||
foreach ($ifaces as $iface) {
|
||||
$name_token = $iface->getChildByIndex(1);
|
||||
$name_string = $name_token->getConcreteString();
|
||||
if (!$this->isUpperCamelCase($name_string)) {
|
||||
$this->raiseLintAtNode(
|
||||
$name_token,
|
||||
self::LINT_NAMING_CONVENTIONS,
|
||||
'Follow naming conventions: interfaces should be named using '.
|
||||
'UpperCamelCase.');
|
||||
}
|
||||
$names[] = array(
|
||||
'interface',
|
||||
$name_string,
|
||||
$name_token,
|
||||
$this->isUpperCamelCase($name_string)
|
||||
? null
|
||||
: 'Follow naming conventions: interfaces should be named using '.
|
||||
'UpperCamelCase.',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
@ -687,13 +723,15 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
|
|||
continue;
|
||||
}
|
||||
$name_string = $name_token->getConcreteString();
|
||||
if (!$this->isLowercaseWithUnderscores($name_string)) {
|
||||
$this->raiseLintAtNode(
|
||||
$name_token,
|
||||
self::LINT_NAMING_CONVENTIONS,
|
||||
'Follow naming conventions: functions should be named using '.
|
||||
'lowercase_with_underscores.');
|
||||
}
|
||||
$names[] = array(
|
||||
'function',
|
||||
$name_string,
|
||||
$name_token,
|
||||
$this->isLowercaseWithUnderscores($name_string)
|
||||
? null
|
||||
: 'Follow naming conventions: functions should be named using '.
|
||||
'lowercase_with_underscores.',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
@ -701,13 +739,15 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
|
|||
foreach ($methods as $method) {
|
||||
$name_token = $method->getChildByIndex(2);
|
||||
$name_string = $name_token->getConcreteString();
|
||||
if (!$this->isLowerCamelCase($name_string)) {
|
||||
$this->raiseLintAtNode(
|
||||
$name_token,
|
||||
self::LINT_NAMING_CONVENTIONS,
|
||||
'Follow naming conventions: methods should be named using '.
|
||||
'lowerCamelCase.');
|
||||
}
|
||||
$names[] = array(
|
||||
'method',
|
||||
$name_string,
|
||||
$name_token,
|
||||
$this->isLowerCamelCase($name_string)
|
||||
? null
|
||||
: 'Follow naming conventions: methods should be named using '.
|
||||
'lowerCamelCase.',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
@ -716,13 +756,15 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
|
|||
foreach ($param_list->getChildren() as $param) {
|
||||
$name_token = $param->getChildByIndex(1);
|
||||
$name_string = $name_token->getConcreteString();
|
||||
if (!$this->isLowercaseWithUnderscores($name_string)) {
|
||||
$this->raiseLintAtNode(
|
||||
$name_token,
|
||||
self::LINT_NAMING_CONVENTIONS,
|
||||
'Follow naming conventions: parameters should be named using '.
|
||||
'lowercase_with_underscores.');
|
||||
}
|
||||
$names[] = array(
|
||||
'parameter',
|
||||
$name_string,
|
||||
$name_token,
|
||||
$this->isLowercaseWithUnderscores($name_string)
|
||||
? null
|
||||
: 'Follow naming conventions: parameters should be named using '.
|
||||
'lowercase_with_underscores.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -733,13 +775,15 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
|
|||
foreach ($constant_list->getChildren() as $constant) {
|
||||
$name_token = $constant->getChildByIndex(0);
|
||||
$name_string = $name_token->getConcreteString();
|
||||
if (!$this->isUppercaseWithUnderscores($name_string)) {
|
||||
$this->raiseLintAtNode(
|
||||
$name_token,
|
||||
self::LINT_NAMING_CONVENTIONS,
|
||||
'Follow naming conventions: class constants should be named using '.
|
||||
'UPPERCASE_WITH_UNDERSCORES.');
|
||||
}
|
||||
$names[] = array(
|
||||
'constant',
|
||||
$name_string,
|
||||
$name_token,
|
||||
$this->isUppercaseWithUnderscores($name_string)
|
||||
? null
|
||||
: 'Follow naming conventions: class constants should be named '.
|
||||
'using UPPERCASE_WITH_UNDERSCORES.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -751,15 +795,45 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
|
|||
}
|
||||
$name_token = $prop->getChildByIndex(0);
|
||||
$name_string = $name_token->getConcreteString();
|
||||
if (!$this->isLowerCamelCase($name_string)) {
|
||||
$this->raiseLintAtNode(
|
||||
$name_token,
|
||||
self::LINT_NAMING_CONVENTIONS,
|
||||
'Follow naming conventions: class properties should be named '.
|
||||
'using lowerCamelCase.');
|
||||
$names[] = array(
|
||||
'member',
|
||||
$name_string,
|
||||
$name_token,
|
||||
$this->isLowerCamelCase($name_string)
|
||||
? null
|
||||
: 'Follow naming conventions: class properties should be named '.
|
||||
'using lowerCamelCase.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$engine = $this->getEngine();
|
||||
$working_copy = $engine->getWorkingCopy();
|
||||
|
||||
if ($working_copy) {
|
||||
// If a naming hook is configured, give it a chance to override the
|
||||
// default results for all the symbol names.
|
||||
$hook_class = $working_copy->getConfig('lint.xhpast.naminghook');
|
||||
if ($hook_class) {
|
||||
$hook_obj = newv($hook_class, array());
|
||||
foreach ($names as $k => $name_attrs) {
|
||||
list($type, $name, $token, $default) = $name_attrs;
|
||||
$result = $hook_obj->lintSymbolName($type, $name, $default);
|
||||
$names[$k][3] = $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Raise anything we're left with.
|
||||
foreach ($names as $k => $name_attrs) {
|
||||
list($type, $name, $token, $result) = $name_attrs;
|
||||
if ($result) {
|
||||
$this->raiseLintAtNode(
|
||||
$token,
|
||||
self::LINT_NAMING_CONVENTIONS,
|
||||
$result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function isUpperCamelCase($str) {
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
function f() {
|
||||
|
||||
}
|
||||
|
||||
function g()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
if (1)
|
||||
{}
|
||||
|
||||
foreach (x() as $y)
|
||||
{}
|
||||
|
||||
while (1)
|
||||
{}
|
||||
|
||||
switch (1)
|
||||
{}
|
||||
|
||||
try
|
||||
{}
|
||||
catch (Exception $x)
|
||||
{}
|
||||
~~~~~~~~~~
|
||||
warning:7:13
|
||||
warning:12:7
|
||||
warning:15:20
|
||||
warning:18:10
|
||||
warning:21:11
|
||||
warning:24:4
|
||||
warning:26:21
|
||||
~~~~~~~~~~
|
||||
<?php
|
||||
|
||||
function f() {
|
||||
|
||||
}
|
||||
|
||||
function g() {
|
||||
|
||||
}
|
||||
|
||||
if (1) {}
|
||||
|
||||
foreach (x() as $y) {}
|
||||
|
||||
while (1) {}
|
||||
|
||||
switch (1) {}
|
||||
|
||||
try {}
|
||||
catch (Exception $x) {}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* You can extend this class and set "lint.xhpast.naminghook" in your
|
||||
* .arcconfig to have an opportunity to override lint results for symbol names.
|
||||
*
|
||||
* @task override Overriding Symbol Name Lint
|
||||
* @group lint
|
||||
*/
|
||||
abstract class ArcanistXHPASTLintNamingHook {
|
||||
|
||||
final public function __construct() {
|
||||
// <empty>
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked for each symbol, which can override the default
|
||||
* determination of name validity or accept it by returning $default. The
|
||||
* symbol types are: xhp-class, class, interface, function, method, parameter,
|
||||
* constant, and member.
|
||||
*
|
||||
* For example, if you want to ban all symbols with "quack" in them and
|
||||
* otherwise accept all the defaults, except allow any naming convention for
|
||||
* methods with "duck" in them, you might implement the method like this:
|
||||
*
|
||||
* if (preg_match('/quack/i', $name)) {
|
||||
* return 'Symbol names containing "quack" are forbidden.';
|
||||
* }
|
||||
* if ($type == 'method' && preg_match('/duck/i', $name)) {
|
||||
* return null; // Always accept.
|
||||
* }
|
||||
* return $default;
|
||||
*
|
||||
* @param string The symbol type.
|
||||
* @param string The symbol name.
|
||||
* @param string|null The default result from the main rule engine.
|
||||
* @return string|null Null to accept the name, or a message to reject it
|
||||
* with. You should return the default value if you don't
|
||||
* want to specifically provide an override.
|
||||
* @task override
|
||||
*/
|
||||
abstract public function lintSymbolName($type, $name, $default);
|
||||
|
||||
}
|
10
src/lint/linter/xhpast/naminghook/__init__.php
Normal file
10
src/lint/linter/xhpast/naminghook/__init__.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
phutil_require_source('ArcanistXHPASTLintNamingHook.php');
|
|
@ -168,7 +168,8 @@ class ArcanistLintMessage {
|
|||
}
|
||||
|
||||
public function isPatchable() {
|
||||
return ($this->getReplacementText() !== null);
|
||||
return ($this->getReplacementText() !== null) &&
|
||||
($this->getReplacementText() !== $this->getOriginalText());
|
||||
}
|
||||
|
||||
public function didApplyPatch() {
|
||||
|
|
|
@ -150,6 +150,17 @@ class ArcanistBundle {
|
|||
$cur_path = '/dev/null';
|
||||
}
|
||||
|
||||
// When the diff is used by `patch`, `patch` ignores what is listed as the
|
||||
// current path and just makes changes to the file at the old path (unless
|
||||
// the current path is '/dev/null'.
|
||||
// If the old path and the current path aren't the same (and neither is
|
||||
// /dev/null), this indicates the file was moved or copied. By listing
|
||||
// both paths as the new file, `patch` will apply the diff to the new
|
||||
// file.
|
||||
if ($cur_path !== '/dev/null' && $old_path !== '/dev/null') {
|
||||
$old_path = $cur_path;
|
||||
}
|
||||
|
||||
$result[] = '--- '.$old_path;
|
||||
$result[] = '+++ '.$cur_path;
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ class ArcanistDiffParser {
|
|||
protected $text;
|
||||
protected $line;
|
||||
protected $isGit;
|
||||
protected $isMercurial;
|
||||
protected $detectBinaryFiles = false;
|
||||
|
||||
protected $changes = array();
|
||||
|
@ -209,6 +210,9 @@ class ArcanistDiffParser {
|
|||
'(?P<binary>Binary) files '.
|
||||
'(?P<old>.+)\s+\d{4}-\d{2}-\d{2} and '.
|
||||
'(?P<new>.+)\s+\d{4}-\d{2}-\d{2} differ.*',
|
||||
|
||||
// This is a normal Mercurial text change, probably from "hg diff".
|
||||
'(?P<type>diff -r) (?P<hgrev>[a-f0-9]+) (?P<cur>.+)',
|
||||
);
|
||||
|
||||
$ok = false;
|
||||
|
@ -274,6 +278,10 @@ class ArcanistDiffParser {
|
|||
$line = $this->nextLine();
|
||||
$this->parseChangeset($change);
|
||||
break;
|
||||
case 'diff -r':
|
||||
$this->setIsMercurial(true);
|
||||
$this->parseIndexHunk($change);
|
||||
break;
|
||||
default:
|
||||
$this->didFailParse("Unknown diff type.");
|
||||
}
|
||||
|
@ -432,8 +440,19 @@ class ArcanistDiffParser {
|
|||
return $this->isGit;
|
||||
}
|
||||
|
||||
public function setIsMercurial($is_mercurial) {
|
||||
$this->isMercurial = $is_mercurial;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsMercurial() {
|
||||
return $this->isMercurial;
|
||||
}
|
||||
|
||||
protected function parseIndexHunk(ArcanistDiffChange $change) {
|
||||
$is_git = $this->getIsGit();
|
||||
$is_mercurial = $this->getIsMercurial();
|
||||
$is_svn = (!$is_git && !$is_mercurial);
|
||||
|
||||
$line = $this->getLine();
|
||||
if ($is_git) {
|
||||
|
@ -532,19 +551,27 @@ class ArcanistDiffParser {
|
|||
}
|
||||
|
||||
$line = $this->getLine();
|
||||
$ok = preg_match('/^=+$/', $line) ||
|
||||
($is_git && preg_match('/^index .*$/', $line));
|
||||
if (!$ok) {
|
||||
if ($is_git) {
|
||||
$this->didFailParse(
|
||||
"Expected 'index af23f...a98bc' header line.");
|
||||
|
||||
if ($is_svn) {
|
||||
$ok = preg_match('/^=+$/', $line);
|
||||
if (!$ok) {
|
||||
$this->didFailParse("Expected '=======================' divider line.");
|
||||
} else {
|
||||
$this->didFailParse(
|
||||
"Expected '==========================' divider line.");
|
||||
// Adding an empty file in SVN can produce an empty line here.
|
||||
$line = $this->nextNonemptyLine();
|
||||
}
|
||||
} else if ($is_git) {
|
||||
$ok = preg_match('/^index .*$/', $line);
|
||||
if (!$ok) {
|
||||
// TODO: "hg diff -g" diffs ("mercurial git-style diffs") do not include
|
||||
// this line, so we can't parse them if we fail on it. Maybe introduce
|
||||
// a flag saying "parse this diff using relaxed git-style diff rules"?
|
||||
|
||||
// $this->didFailParse("Expected 'index af23f...a98bc' header line.");
|
||||
} else {
|
||||
$line = $this->nextLine();
|
||||
}
|
||||
}
|
||||
// Adding an empty file in SVN can produce an empty line here.
|
||||
$line = $this->nextNonemptyLine();
|
||||
|
||||
// If there are files with only whitespace changes and -b or -w are
|
||||
// supplied as command-line flags to `diff', svn and git both produce
|
||||
|
@ -596,14 +623,23 @@ class ArcanistDiffParser {
|
|||
protected function parseHunkTarget() {
|
||||
$line = $this->getLine();
|
||||
$matches = null;
|
||||
|
||||
$remainder = '(?:\s*\(.*\))?';
|
||||
if ($this->getIsMercurial()) {
|
||||
// Something like "Fri Aug 26 01:20:50 2005 -0700", don't bother trying
|
||||
// to parse it.
|
||||
$remainder = '\t.*';
|
||||
}
|
||||
|
||||
$ok = preg_match(
|
||||
'@^[-+]{3} (?:[ab]/)?(?P<path>.*?)(?:\s*\(.*\))?$@',
|
||||
'@^[-+]{3} (?:[ab]/)?(?P<path>.*?)'.$remainder.'$@',
|
||||
$line,
|
||||
$matches);
|
||||
if (!$ok) {
|
||||
$this->didFailParse(
|
||||
"Expected hunk target '+++ path/to/file.ext (revision N)'.");
|
||||
}
|
||||
|
||||
$this->nextLine();
|
||||
return $matches['path'];
|
||||
}
|
||||
|
@ -715,7 +751,7 @@ class ArcanistDiffParser {
|
|||
|
||||
$is_binary = false;
|
||||
if ($this->detectBinaryFiles) {
|
||||
$is_binary = preg_match('/([^\x09\x0A\x20-\x7E]+)/', $corpus);
|
||||
$is_binary = !phutil_is_utf8($corpus);
|
||||
}
|
||||
|
||||
if ($is_binary) {
|
||||
|
|
|
@ -65,6 +65,7 @@ class PhutilModuleRequirements {
|
|||
}
|
||||
|
||||
public function addClassDeclaration(XHPASTNode $where, $name) {
|
||||
$name = self::mungeXHPClassName($name);
|
||||
return $this->addDeclaration('class', $where, $name);
|
||||
}
|
||||
|
||||
|
@ -98,8 +99,10 @@ class PhutilModuleRequirements {
|
|||
}
|
||||
|
||||
public function addClassDependency($child, XHPASTNode $where, $name) {
|
||||
$name = self::mungeXHPClassName($name);
|
||||
if ($child !== null) {
|
||||
if (empty($this->builtins['class'][$name])) {
|
||||
$child = self::mungeXHPClassName($child);
|
||||
$this->chain['class'][$child] = $name;
|
||||
}
|
||||
}
|
||||
|
@ -173,4 +176,13 @@ class PhutilModuleRequirements {
|
|||
'messages' => $this->messages,
|
||||
);
|
||||
}
|
||||
|
||||
private static function mungeXHPClassName($name) {
|
||||
if (strlen($name) && $name[0] == ':') {
|
||||
// XHP's semantic actions munge element names without a preceding colon.
|
||||
$name = substr($name, 1);
|
||||
return 'xhp_'.str_replace(array(':', '-'), array('__', '_'), $name);
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,11 +65,21 @@ abstract class ArcanistRepositoryAPI {
|
|||
"any parent directory. Create an '.arcconfig' file to configure arc.");
|
||||
}
|
||||
|
||||
if (@file_exists($root.'/.svn')) {
|
||||
phutil_require_module('arcanist', 'repository/api/subversion');
|
||||
return new ArcanistSubversionAPI($root);
|
||||
if (Filesystem::pathExists($root.'/.svn')) {
|
||||
return newv('ArcanistSubversionAPI', array($root));
|
||||
}
|
||||
|
||||
if (Filesystem::pathExists($root.'/.hg')) {
|
||||
// TODO: Stabilize and remove.
|
||||
file_put_contents(
|
||||
'php://stderr',
|
||||
phutil_console_format(
|
||||
"**WARNING:** Mercurial support is largely imaginary right now.\n"));
|
||||
|
||||
return newv('ArcanistMercurialAPI', array($root));
|
||||
}
|
||||
|
||||
|
||||
$git_root = self::discoverGitBaseDirectory($root);
|
||||
if ($git_root) {
|
||||
if (!Filesystem::pathsAreEquivalent($root, $git_root)) {
|
||||
|
@ -77,16 +87,16 @@ abstract class ArcanistRepositoryAPI {
|
|||
"'.arcconfig' file is located at '{$root}', but working copy root ".
|
||||
"is '{$git_root}'. Move '.arcconfig' file to the working copy root.");
|
||||
}
|
||||
phutil_require_module('arcanist', 'repository/api/git');
|
||||
return new ArcanistGitAPI($root);
|
||||
|
||||
return newv('ArcanistGitAPI', array($root));
|
||||
}
|
||||
|
||||
throw new ArcanistUsageException(
|
||||
"The current working directory is not part of a working copy for a ".
|
||||
"supported version control system (svn or git).");
|
||||
"supported version control system (svn, git or mercurial).");
|
||||
}
|
||||
|
||||
protected function __construct($path) {
|
||||
public function __construct($path) {
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
|
@ -147,5 +157,6 @@ abstract class ArcanistRepositoryAPI {
|
|||
abstract public function getRawDiffText($path);
|
||||
abstract public function getOriginalFileData($path);
|
||||
abstract public function getCurrentFileData($path);
|
||||
abstract public function getLocalCommitInformation();
|
||||
|
||||
}
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
|
||||
phutil_require_module('arcanist', 'exception/usage');
|
||||
|
||||
phutil_require_module('phutil', 'console');
|
||||
phutil_require_module('phutil', 'filesystem');
|
||||
phutil_require_module('phutil', 'future/exec');
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('ArcanistRepositoryAPI.php');
|
||||
|
|
|
@ -46,6 +46,35 @@ class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getLocalCommitInformation() {
|
||||
list($info) = execx(
|
||||
'(cd %s && git log %s..%s --format=%s --)',
|
||||
$this->getPath(),
|
||||
$this->getRelativeCommit(),
|
||||
'HEAD',
|
||||
'%H%x00%T%x00%P%x00%at%x00%an%x00%s');
|
||||
|
||||
$commits = array();
|
||||
|
||||
$info = trim($info);
|
||||
$info = explode("\n", $info);
|
||||
foreach ($info as $line) {
|
||||
list($commit, $tree, $parents, $time, $author, $title)
|
||||
= explode("\0", $line, 6);
|
||||
|
||||
$commits[] = array(
|
||||
'commit' => $commit,
|
||||
'tree' => $tree,
|
||||
'parents' => array_filter(explode(' ', $parents)),
|
||||
'time' => $time,
|
||||
'author' => $author,
|
||||
'summary' => $title,
|
||||
);
|
||||
}
|
||||
|
||||
return $commits;
|
||||
}
|
||||
|
||||
public function getRelativeCommit() {
|
||||
if ($this->relativeCommit === null) {
|
||||
list($err) = exec_manual(
|
||||
|
@ -97,7 +126,6 @@ class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
|||
}
|
||||
|
||||
public function getRawDiffText($path) {
|
||||
$relative_commit = $this->getRelativeCommit();
|
||||
$options = $this->getDiffFullOptions();
|
||||
list($stdout) = execx(
|
||||
"(cd %s; git diff {$options} %s -- %s)",
|
||||
|
@ -317,7 +345,7 @@ class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
|||
public function getBlame($path) {
|
||||
// TODO: 'git blame' supports --porcelain and we should probably use it.
|
||||
list($stdout) = execx(
|
||||
'(cd %s; git blame -w -C %s -- %s)',
|
||||
'(cd %s; git blame --date=iso -w -C %s -- %s)',
|
||||
$this->getPath(),
|
||||
$this->getRelativeCommit(),
|
||||
$path);
|
||||
|
|
317
src/repository/api/mercurial/ArcanistMercurialAPI.php
Normal file
317
src/repository/api/mercurial/ArcanistMercurialAPI.php
Normal file
|
@ -0,0 +1,317 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interfaces with the Mercurial working copies.
|
||||
*
|
||||
* @group workingcopy
|
||||
*/
|
||||
class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
||||
|
||||
private $status;
|
||||
private $base;
|
||||
private $relativeCommit;
|
||||
|
||||
public function getSourceControlSystemName() {
|
||||
return 'hg';
|
||||
}
|
||||
|
||||
public function getSourceControlBaseRevision() {
|
||||
list($stdout) = execx(
|
||||
'(cd %s && hg id -ir %s)',
|
||||
$this->getPath(),
|
||||
$this->getRelativeCommit());
|
||||
return $stdout;
|
||||
}
|
||||
|
||||
public function getSourceControlPath() {
|
||||
return '/';
|
||||
}
|
||||
|
||||
public function getBranchName() {
|
||||
// TODO: I have nearly no idea how hg local branches work.
|
||||
list($stdout) = execx(
|
||||
'(cd %s && hg branch)',
|
||||
$this->getPath());
|
||||
return $stdout;
|
||||
}
|
||||
|
||||
public function setRelativeCommit($commit) {
|
||||
list($err) = exec_manual(
|
||||
'(cd %s && hg id -ir %s)',
|
||||
$this->getPath(),
|
||||
$commit);
|
||||
|
||||
if ($err) {
|
||||
throw new ArcanistUsageException(
|
||||
"Commit '{$commit}' is not a valid Mercurial commit identifier.");
|
||||
}
|
||||
|
||||
$this->relativeCommit = $commit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRelativeCommit() {
|
||||
if (empty($this->relativeCommit)) {
|
||||
list($stdout) = execx(
|
||||
'(cd %s && hg outgoing --limit 1)',
|
||||
$this->getPath());
|
||||
$logs = $this->parseMercurialLog($stdout);
|
||||
if (!count($logs)) {
|
||||
throw new ArcanistUsageException("You have no outgoing changes!");
|
||||
}
|
||||
$oldest_log = head($logs);
|
||||
|
||||
$this->relativeCommit = $oldest_log['rev'].'~1';
|
||||
}
|
||||
return $this->relativeCommit;
|
||||
}
|
||||
|
||||
public function getLocalCommitInformation() {
|
||||
list($info) = execx(
|
||||
'(cd %s && hg log --rev %s..%s --)',
|
||||
$this->getPath(),
|
||||
$this->getRelativeCommit(),
|
||||
'tip');
|
||||
return $this->parseMercurialLog($info);
|
||||
}
|
||||
|
||||
|
||||
public function getBlame($path) {
|
||||
list($stdout) = execx(
|
||||
'(cd %s && hg blame -u -v -c --rev %s -- %s)',
|
||||
$this->getPath(),
|
||||
$this->getRelativeCommit(),
|
||||
$path);
|
||||
|
||||
$blame = array();
|
||||
foreach (explode("\n", trim($stdout)) as $line) {
|
||||
if (!strlen($line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$matches = null;
|
||||
$ok = preg_match('^/\s*([^:]+?) [a-f0-9]{12}: (.*)$/', $line, $matches);
|
||||
|
||||
if (!$ok) {
|
||||
throw new Exception("Unable to parse Mercurial blame line: {$line}");
|
||||
}
|
||||
|
||||
$revision = $matches[2];
|
||||
$author = trim($matches[1]);
|
||||
$blame[] = array($author, $revision);
|
||||
}
|
||||
|
||||
return $blame;
|
||||
}
|
||||
|
||||
public function getWorkingCopyStatus() {
|
||||
|
||||
// A reviewable revision spans multiple local commits in Mercurial, but
|
||||
// there is no way to get file change status across multiple commits, so
|
||||
// just take the entire diff and parse it to figure out what's changed.
|
||||
|
||||
$diff = $this->getFullMercurialDiff();
|
||||
$parser = new ArcanistDiffParser();
|
||||
$changes = $parser->parseDiff($diff);
|
||||
|
||||
$status_map = array();
|
||||
|
||||
foreach ($changes as $change) {
|
||||
$flags = 0;
|
||||
switch ($change->getType()) {
|
||||
case ArcanistDiffChangeType::TYPE_ADD:
|
||||
case ArcanistDiffChangeType::TYPE_MOVE_HERE:
|
||||
case ArcanistDiffChangeType::TYPE_COPY_HERE:
|
||||
$flags |= self::FLAG_ADDED;
|
||||
break;
|
||||
case ArcanistDiffChangeType::TYPE_CHANGE:
|
||||
case ArcanistDiffChangeType::TYPE_COPY_AWAY: // Check for changes?
|
||||
$flags |= self::FLAG_MODIFIED;
|
||||
break;
|
||||
case ArcanistDiffChangeType::TYPE_DELETE:
|
||||
case ArcanistDiffChangeType::TYPE_MOVE_AWAY:
|
||||
case ArcanistDiffChangeType::TYPE_MULTICOPY:
|
||||
$flags |= self::FLAG_DELETED;
|
||||
break;
|
||||
}
|
||||
$status_map[$change->getCurrentPath()] = $flags;
|
||||
}
|
||||
|
||||
list($stdout) = execx(
|
||||
'(cd %s && hg status)',
|
||||
$this->getPath());
|
||||
|
||||
$working_status = $this->parseMercurialStatus($stdout);
|
||||
foreach ($working_status as $path => $status) {
|
||||
$status |= self::FLAG_UNCOMMITTED;
|
||||
if (!empty($status_map[$path])) {
|
||||
$status_map[$path] |= $status;
|
||||
} else {
|
||||
$status_map[$path] = $status;
|
||||
}
|
||||
}
|
||||
|
||||
return $status_map;
|
||||
}
|
||||
|
||||
private function getDiffOptions() {
|
||||
$options = array(
|
||||
'-g',
|
||||
'-U'.$this->getDiffLinesOfContext(),
|
||||
);
|
||||
return implode(' ', $options);
|
||||
}
|
||||
|
||||
public function getRawDiffText($path) {
|
||||
$options = $this->getDiffOptions();
|
||||
|
||||
list($stdout) = execx(
|
||||
'(cd %s && hg diff %C --rev %s --rev tip -- %s)',
|
||||
$this->getPath(),
|
||||
$options,
|
||||
$this->getRelativeCommit(),
|
||||
$path);
|
||||
|
||||
return $stdout;
|
||||
}
|
||||
|
||||
public function getFullMercurialDiff() {
|
||||
$options = $this->getDiffOptions();
|
||||
|
||||
list($stdout) = execx(
|
||||
'(cd %s && hg diff %C --rev %s --rev tip --)',
|
||||
$this->getPath(),
|
||||
$options,
|
||||
$this->getRelativeCommit());
|
||||
|
||||
return $stdout;
|
||||
}
|
||||
|
||||
public function getOriginalFileData($path) {
|
||||
return $this->getFileDataAtRevision($path, $this->getRelativeCommit());
|
||||
}
|
||||
|
||||
public function getCurrentFileData($path) {
|
||||
return $this->getFileDataAtRevision($path, 'tip');
|
||||
}
|
||||
|
||||
private function getFileDataAtRevision($path, $revision) {
|
||||
list($stdout) = execx(
|
||||
'(cd %s && hg cat --rev %s -- %s)',
|
||||
$this->getPath(),
|
||||
$path);
|
||||
return $stdout;
|
||||
}
|
||||
|
||||
private function parseMercurialStatus($status) {
|
||||
$result = array();
|
||||
|
||||
$status = trim($status);
|
||||
if (!strlen($status)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$lines = explode("\n", $status);
|
||||
foreach ($lines as $line) {
|
||||
$flags = 0;
|
||||
list($code, $path) = explode(' ', $line, 2);
|
||||
switch ($code) {
|
||||
case 'A':
|
||||
$flags |= self::FLAG_ADDED;
|
||||
break;
|
||||
case 'R':
|
||||
$flags |= self::FLAG_REMOVED;
|
||||
break;
|
||||
case 'M':
|
||||
$flags |= self::FLAG_MODIFIED;
|
||||
break;
|
||||
case 'C':
|
||||
// This is "clean" and included only for completeness, these files
|
||||
// have not been changed.
|
||||
break;
|
||||
case '!':
|
||||
$flags |= self::FLAG_MISSING;
|
||||
break;
|
||||
case '?':
|
||||
$flags |= self::FLAG_UNTRACKED;
|
||||
break;
|
||||
case 'I':
|
||||
// This is "ignored" and included only for completeness.
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unknown Mercurial status '{$code}'.");
|
||||
}
|
||||
|
||||
$result[$path] = $flags;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function parseMercurialLog($log) {
|
||||
$result = array();
|
||||
|
||||
$chunks = explode("\n\n", trim($log));
|
||||
foreach ($chunks as $chunk) {
|
||||
$commit = array();
|
||||
$lines = explode("\n", $chunk);
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/^(comparing with|searching for changes)/', $line)) {
|
||||
// These are sent to stdout when you run "hg outgoing" although the
|
||||
// format is otherwise identical to "hg log".
|
||||
continue;
|
||||
}
|
||||
list($name, $value) = explode(':', $line, 2);
|
||||
$value = trim($value);
|
||||
switch ($name) {
|
||||
case 'user':
|
||||
$commit['user'] = $value;
|
||||
break;
|
||||
case 'date':
|
||||
$commit['date'] = strtotime($value);
|
||||
break;
|
||||
case 'summary':
|
||||
$commit['summary'] = $value;
|
||||
break;
|
||||
case 'changeset':
|
||||
list($local, $rev) = explode(':', $value, 2);
|
||||
$commit['local'] = $local;
|
||||
$commit['rev'] = $rev;
|
||||
break;
|
||||
case 'parent':
|
||||
if (empty($commit['parents'])) {
|
||||
$commit['parents'] = array();
|
||||
}
|
||||
list($local, $rev) = explode(':', $value, 2);
|
||||
$commit['parents'][] = array(
|
||||
'local' => $local,
|
||||
'rev' => $rev,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unknown Mercurial log field '{$name}'!");
|
||||
}
|
||||
}
|
||||
$result[] = $commit;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
18
src/repository/api/mercurial/__init__.php
Normal file
18
src/repository/api/mercurial/__init__.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('arcanist', 'exception/usage');
|
||||
phutil_require_module('arcanist', 'parser/diff');
|
||||
phutil_require_module('arcanist', 'parser/diff/changetype');
|
||||
phutil_require_module('arcanist', 'repository/api/base');
|
||||
|
||||
phutil_require_module('phutil', 'future/exec');
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('ArcanistMercurialAPI.php');
|
|
@ -478,4 +478,8 @@ EODIFF;
|
|||
return $info['Repository UUID'];
|
||||
}
|
||||
|
||||
public function getLocalCommitInformation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -222,6 +222,7 @@ class ArcanistBaseWorkflow {
|
|||
} catch (ConduitClientException $ex) {
|
||||
if ($ex->getErrorCode() == 'ERR-NO-CERTIFICATE' ||
|
||||
$ex->getErrorCode() == 'ERR-INVALID-USER') {
|
||||
$conduit_uri = $this->conduitURI;
|
||||
$message =
|
||||
"\n".
|
||||
phutil_console_format(
|
||||
|
@ -597,6 +598,10 @@ class ArcanistBaseWorkflow {
|
|||
echo phutil_console_wrap(
|
||||
"Since you don't have 'svn:ignore' rules for these files, you may ".
|
||||
"have forgotten to 'svn add' them.");
|
||||
} else if ($api instanceof ArcanistMercurialAPI) {
|
||||
echo phutil_console_wrap(
|
||||
"Since you don't have '.hgignore' rules for these files, you ".
|
||||
"may have forgotten to 'hg add' them to your commit.");
|
||||
}
|
||||
|
||||
$prompt = "Do you want to continue without adding these files?";
|
||||
|
@ -919,4 +924,15 @@ class ArcanistBaseWorkflow {
|
|||
return $user_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a message to stderr so that '--json' flags or stdout which is meant
|
||||
* to be piped somewhere aren't disrupted.
|
||||
*
|
||||
* @param string Message to write to stderr.
|
||||
* @return void
|
||||
*/
|
||||
protected function writeStatusMessage($msg) {
|
||||
file_put_contents('php://stderr', $msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -280,8 +280,12 @@ EOTEXT
|
|||
if ($info['uuid']) {
|
||||
$repo_uuid = $info['uuid'];
|
||||
}
|
||||
} else {
|
||||
} else if ($repository_api instanceof ArcanistSubversionAPI) {
|
||||
$repo_uuid = $repository_api->getRepositorySVNUUID();
|
||||
} else if ($repository_api instanceof ArcanistMercurialAPI) {
|
||||
// TODO: Provide this information.
|
||||
} else {
|
||||
throw new Exception("Unsupported repository API!");
|
||||
}
|
||||
|
||||
$working_copy = $this->getWorkingCopy();
|
||||
|
@ -335,6 +339,17 @@ EOTEXT
|
|||
));
|
||||
}
|
||||
|
||||
$local_info = $repository_api->getLocalCommitInformation();
|
||||
if ($local_info) {
|
||||
$conduit->callMethodSynchronous(
|
||||
'differential.setdiffproperty',
|
||||
array(
|
||||
'diff_id' => $diff_info['diffid'],
|
||||
'name' => 'local:commits',
|
||||
'data' => json_encode($local_info),
|
||||
));
|
||||
}
|
||||
|
||||
if ($this->unresolvedTests) {
|
||||
$data = array();
|
||||
foreach ($this->unresolvedTests as $test) {
|
||||
|
@ -527,10 +542,18 @@ EOTEXT
|
|||
}
|
||||
|
||||
protected function shouldOnlyCreateDiff() {
|
||||
|
||||
$repository_api = $this->getRepositoryAPI();
|
||||
if ($repository_api instanceof ArcanistSubversionAPI) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($repository_api instanceof ArcanistMercurialAPI) {
|
||||
// TODO: This is unlikely to be correct since it excludes using local
|
||||
// branching in Mercurial.
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->getArgument('preview') ||
|
||||
$this->getArgument('only');
|
||||
}
|
||||
|
@ -580,11 +603,19 @@ EOTEXT
|
|||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
} else if ($repository_api instanceof ArcanistGitAPI) {
|
||||
$this->parseGitRelativeCommit(
|
||||
$repository_api,
|
||||
$this->getArgument('paths', array()));
|
||||
$paths = $repository_api->getWorkingCopyStatus();
|
||||
} else if ($repository_api instanceof ArcanistMercurialAPI) {
|
||||
// TODO: Unify this and the previous block.
|
||||
|
||||
// TODO: Parse the relative commit.
|
||||
|
||||
$paths = $repository_api->getWorkingCopyStatus();
|
||||
} else {
|
||||
throw new Exception("Unknown VCS!");
|
||||
}
|
||||
|
||||
foreach ($paths as $path => $mask) {
|
||||
|
@ -669,6 +700,9 @@ EOTEXT
|
|||
}
|
||||
$changes = $parser->parseDiff($diff);
|
||||
|
||||
} else if ($repository_api instanceof ArcanistMercurialAPI) {
|
||||
$diff = $repository_api->getFullMercurialDiff();
|
||||
$changes = $parser->parseDiff($diff);
|
||||
} else {
|
||||
throw new Exception("Repository API is not supported.");
|
||||
}
|
||||
|
@ -819,12 +853,6 @@ EOTEXT
|
|||
$mime_type = trim($mime_type);
|
||||
$result['mime'] = $mime_type;
|
||||
|
||||
// TODO: Make this configurable.
|
||||
$bin_limit = 1024 * 1024; // 1 MB limit
|
||||
if (strlen($data) > $bin_limit) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$bytes = strlen($data);
|
||||
echo "Uploading {$desc} '{$name}' ({$mime_type}, {$bytes} bytes)...\n";
|
||||
|
||||
|
|
120
src/workflow/download/ArcanistDownloadWorkflow.php
Normal file
120
src/workflow/download/ArcanistDownloadWorkflow.php
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Download a file from Phabricator.
|
||||
*
|
||||
* @group workflow
|
||||
*/
|
||||
final class ArcanistDownloadWorkflow extends ArcanistBaseWorkflow {
|
||||
|
||||
private $id;
|
||||
private $saveAs;
|
||||
private $show;
|
||||
|
||||
public function getCommandHelp() {
|
||||
return phutil_console_format(<<<EOTEXT
|
||||
**download** __file__ [--as __name__] [--show]
|
||||
Supports: filesystems
|
||||
Download a file to local disk, e.g.:
|
||||
|
||||
$ arc download F33 # Download file 'F33'
|
||||
|
||||
EOTEXT
|
||||
);
|
||||
}
|
||||
|
||||
public function getArguments() {
|
||||
return array(
|
||||
'show' => array(
|
||||
'conflicts' => array(
|
||||
'as' => 'Use --show to direct the file to stdout, or --as to direct '.
|
||||
'it to a named location.',
|
||||
),
|
||||
'help' => 'Write file to stdout instead of to disk.',
|
||||
),
|
||||
'as' => array(
|
||||
'param' => 'name',
|
||||
'help' => 'Save the file with a specific name rather than the default.',
|
||||
),
|
||||
'*' => 'argv',
|
||||
);
|
||||
}
|
||||
|
||||
protected function didParseArguments() {
|
||||
$argv = $this->getArgument('argv');
|
||||
if (!$argv) {
|
||||
throw new ArcanistUsageException("Specify a file to download.");
|
||||
}
|
||||
if (count($argv) > 1) {
|
||||
throw new ArcanistUsageException("Specify exactly one file to download.");
|
||||
}
|
||||
|
||||
$file = reset($argv);
|
||||
if (!preg_match('/^F?\d+/', $file)) {
|
||||
throw new ArcanistUsageException("Specify file by ID, e.g. F123.");
|
||||
}
|
||||
|
||||
$this->id = (int)ltrim($file, 'F');
|
||||
$this->saveAs = $this->getArgument('as');
|
||||
$this->show = $this->getArgument('show');
|
||||
}
|
||||
|
||||
public function requiresAuthentication() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function run() {
|
||||
|
||||
$conduit = $this->getConduit();
|
||||
|
||||
$this->writeStatusMessage("Getting file information...\n");
|
||||
$info = $conduit->callMethodSynchronous(
|
||||
'file.info',
|
||||
array(
|
||||
'id' => $this->id,
|
||||
));
|
||||
|
||||
$bytes = number_format($info['byteSize']);
|
||||
$desc = '('.$bytes.' bytes)';
|
||||
if ($info['name']) {
|
||||
$desc = "'".$info['name']."' ".$desc;
|
||||
}
|
||||
|
||||
$this->writeStatusMessage("Downloading file {$desc}...\n");
|
||||
$data = $conduit->callMethodSynchronous(
|
||||
'file.download',
|
||||
array(
|
||||
'phid' => $info['phid'],
|
||||
));
|
||||
|
||||
$data = base64_decode($data);
|
||||
|
||||
if ($this->show) {
|
||||
echo $data;
|
||||
} else {
|
||||
$path = Filesystem::writeUniqueFile(
|
||||
nonempty($this->saveAs, $info['name'], 'file'),
|
||||
$data);
|
||||
$this->writeStatusMessage("Saved file as '{$path}'.\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
17
src/workflow/download/__init__.php
Normal file
17
src/workflow/download/__init__.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('arcanist', 'exception/usage');
|
||||
phutil_require_module('arcanist', 'workflow/base');
|
||||
|
||||
phutil_require_module('phutil', 'console');
|
||||
phutil_require_module('phutil', 'filesystem');
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('ArcanistDownloadWorkflow.php');
|
|
@ -34,7 +34,7 @@ class ArcanistLiberateWorkflow extends ArcanistBaseWorkflow {
|
|||
**liberate** [__path__]
|
||||
Supports: libphutil
|
||||
Create or update a libphutil library, generating required metadata
|
||||
files like __init__.php.
|
||||
files like \__init__.php.
|
||||
EOTEXT
|
||||
);
|
||||
}
|
||||
|
|
159
src/workflow/paste/ArcanistPasteWorkflow.php
Normal file
159
src/workflow/paste/ArcanistPasteWorkflow.php
Normal file
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Upload a chunk of text to the Paste application, or download one.
|
||||
*
|
||||
* @group workflow
|
||||
*/
|
||||
final class ArcanistPasteWorkflow extends ArcanistBaseWorkflow {
|
||||
|
||||
private $id;
|
||||
private $language;
|
||||
private $title;
|
||||
private $json;
|
||||
|
||||
public function getCommandHelp() {
|
||||
return phutil_console_format(<<<EOTEXT
|
||||
**paste** [--title __title__] [--lang __language__] [--json]
|
||||
**paste** __id__ [--json]
|
||||
Supports: text
|
||||
Share and grab text using the Paste application. To create a paste,
|
||||
use stdin to provide the text:
|
||||
|
||||
$ cat list_of_ducks.txt | arc paste
|
||||
|
||||
To retrieve a paste, specify the paste ID:
|
||||
|
||||
$ arc paste P123
|
||||
|
||||
EOTEXT
|
||||
);
|
||||
}
|
||||
|
||||
public function getArguments() {
|
||||
return array(
|
||||
'title' => array(
|
||||
'param' => 'title',
|
||||
'help' => 'Title for the paste.',
|
||||
),
|
||||
'lang' => array(
|
||||
'param' => 'language',
|
||||
'help' => 'Language for syntax highlighting.',
|
||||
),
|
||||
'json' => array(
|
||||
'help' => 'Output in JSON format.',
|
||||
),
|
||||
'*' => 'argv',
|
||||
);
|
||||
}
|
||||
|
||||
public function requiresAuthentication() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function didParseArguments() {
|
||||
$this->language = $this->getArgument('lang');
|
||||
$this->title = $this->getArgument('title');
|
||||
$this->json = $this->getArgument('json');
|
||||
|
||||
$argv = $this->getArgument('argv');
|
||||
if (count($argv) > 1) {
|
||||
throw new ArcanistUsageException("Specify only one paste to retrieve.");
|
||||
} else if (count($argv) == 1) {
|
||||
$id = $argv[0];
|
||||
if (!preg_match('/^P?\d+/', $id)) {
|
||||
throw new ArcanistUsageException("Specify a paste ID, like P123.");
|
||||
}
|
||||
$this->id = (int)ltrim($id, 'P');
|
||||
|
||||
if ($this->language || $this->title) {
|
||||
throw new ArcanistUsageException(
|
||||
"Use options --lang and --title only when creating pastes.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getTitle() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
private function getLanguage() {
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
private function getJSON() {
|
||||
return $this->json;
|
||||
}
|
||||
|
||||
public function run() {
|
||||
|
||||
if ($this->id) {
|
||||
return $this->getPaste();
|
||||
} else {
|
||||
return $this->createPaste();
|
||||
}
|
||||
}
|
||||
|
||||
private function getPaste() {
|
||||
$conduit = $this->getConduit();
|
||||
|
||||
$info = $conduit->callMethodSynchronous(
|
||||
'paste.info',
|
||||
array(
|
||||
'paste_id' => $this->id,
|
||||
));
|
||||
|
||||
if ($this->getJSON()) {
|
||||
echo json_encode($info)."\n";
|
||||
} else {
|
||||
echo $info['content'];
|
||||
if (!preg_match('/\\n$/', $info['content'])) {
|
||||
// If there's no newline, add one, since it looks stupid otherwise. If
|
||||
// you want byte-for-byte equivalence you can use --json.
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function createPaste() {
|
||||
$conduit = $this->getConduit();
|
||||
|
||||
// Avoid confusion when people type "arc paste" with nothing else.
|
||||
$this->writeStatusMessage("Reading paste from stdin...\n");
|
||||
|
||||
$info = $conduit->callMethodSynchronous(
|
||||
'paste.create',
|
||||
array(
|
||||
'content' => file_get_contents('php://stdin'),
|
||||
'title' => $this->getTitle(),
|
||||
'language' => $this->getLanguage(),
|
||||
));
|
||||
|
||||
if ($this->getArgument('json')) {
|
||||
echo json_encode($info)."\n";
|
||||
} else {
|
||||
echo $info['objectName'].': '.$info['uri']."\n";
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
15
src/workflow/paste/__init__.php
Normal file
15
src/workflow/paste/__init__.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('arcanist', 'exception/usage');
|
||||
phutil_require_module('arcanist', 'workflow/base');
|
||||
|
||||
phutil_require_module('phutil', 'console');
|
||||
|
||||
|
||||
phutil_require_source('ArcanistPasteWorkflow.php');
|
115
src/workflow/upload/ArcanistUploadWorkflow.php
Normal file
115
src/workflow/upload/ArcanistUploadWorkflow.php
Normal file
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Upload a file to Phabricator.
|
||||
*
|
||||
* @group workflow
|
||||
*/
|
||||
final class ArcanistUploadWorkflow extends ArcanistBaseWorkflow {
|
||||
|
||||
private $paths;
|
||||
private $json;
|
||||
|
||||
public function getCommandHelp() {
|
||||
return phutil_console_format(<<<EOTEXT
|
||||
**upload** __file__ [__file__ ...] [--json]
|
||||
Supports: filesystems
|
||||
Upload a file from local disk.
|
||||
|
||||
EOTEXT
|
||||
);
|
||||
}
|
||||
|
||||
public function getArguments() {
|
||||
return array(
|
||||
'json' => array(
|
||||
'help' => 'Output upload information in JSON format.',
|
||||
),
|
||||
'*' => 'paths',
|
||||
);
|
||||
}
|
||||
|
||||
protected function didParseArguments() {
|
||||
if (!$this->getArgument('paths')) {
|
||||
throw new ArcanistUsageException("Specify one or more files to upload.");
|
||||
}
|
||||
|
||||
$this->paths = $this->getArgument('paths');
|
||||
$this->json = $this->getArgument('json');
|
||||
}
|
||||
|
||||
public function requiresAuthentication() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getPaths() {
|
||||
return $this->paths;
|
||||
}
|
||||
|
||||
private function getJSON() {
|
||||
return $this->json;
|
||||
}
|
||||
|
||||
public function run() {
|
||||
|
||||
$conduit = $this->getConduit();
|
||||
|
||||
$results = array();
|
||||
|
||||
foreach ($this->paths as $path) {
|
||||
$name = basename($path);
|
||||
$this->writeStatusMessage("Uploading '{$name}'...\n");
|
||||
try {
|
||||
$data = Filesystem::readFile($path);
|
||||
} catch (FilesystemException $ex) {
|
||||
$this->writeStatusMessage(
|
||||
"Unable to upload file: ".$ex->getMessage()."\n");
|
||||
$results[$path] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
$phid = $conduit->callMethodSynchronous(
|
||||
'file.upload',
|
||||
array(
|
||||
'data_base64' => base64_encode($data),
|
||||
'name' => $name,
|
||||
));
|
||||
$info = $conduit->callMethodSynchronous(
|
||||
'file.info',
|
||||
array(
|
||||
'phid' => $phid,
|
||||
));
|
||||
|
||||
$results[$path] = $info;
|
||||
|
||||
if (!$this->getJSON()) {
|
||||
echo " {$name}: ".$info['uri']."\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->getJSON()) {
|
||||
echo json_encode($results)."\n";
|
||||
} else {
|
||||
$this->writeStatusMessage("Done.\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
16
src/workflow/upload/__init__.php
Normal file
16
src/workflow/upload/__init__.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('arcanist', 'exception/usage');
|
||||
phutil_require_module('arcanist', 'workflow/base');
|
||||
|
||||
phutil_require_module('phutil', 'console');
|
||||
phutil_require_module('phutil', 'filesystem');
|
||||
|
||||
|
||||
phutil_require_source('ArcanistUploadWorkflow.php');
|
Loading…
Reference in a new issue