diff --git a/.arcconfig b/.arcconfig --- a/.arcconfig +++ b/.arcconfig @@ -6,5 +6,6 @@ "git:arc.feature.start.default" : "origin/master", "arc.feature.start.default" : "master", "history.immutable" : false, - "load" : ["arcanist"] + "load" : ["arcanist"], + "arcanist_configuration" : "ArcanistBitcoinABCConfiguration" } diff --git a/arcanist/.phutil_module_cache b/arcanist/.phutil_module_cache --- a/arcanist/.phutil_module_cache +++ b/arcanist/.phutil_module_cache @@ -1 +1 @@ -{"__symbol_cache_version__":11,"d60c8224f471e0ecddc2a6f3c6839cd1":{"have":{"class":{"AutoPEP8FormatLinter":75}},"need":{"function":{"pht":297,"id":1317},"class":{"ArcanistExternalLinter":104,"ArcanistLintMessage":1324,"Filesystem":1168,"ArcanistLinter":1431,"ArcanistLintSeverity":1509}},"xmap":{"AutoPEP8FormatLinter":["ArcanistExternalLinter"]}},"213c3145da34ed6dfc0d70d628a2a086":{"have":{"class":{"CheckDocLinter":106}},"need":{"function":{"pht":323,"id":1868},"class":{"ArcanistExternalLinter":129,"ArcanistLintMessage":1875,"Filesystem":737,"ArcanistLinter":1923,"ArcanistLintSeverity":2009}},"xmap":{"CheckDocLinter":["ArcanistExternalLinter"]}},"7bab1f879b8a86dd9977b8c0d075935f":{"have":{"class":{"ClangFormatLinter":79}},"need":{"function":{"pht":302,"execx":787,"id":1664},"class":{"ArcanistExternalLinter":105,"ArcanistLintMessage":1671,"Filesystem":1515,"ArcanistLinter":1778,"ArcanistLintSeverity":1856}},"xmap":{"ClangFormatLinter":["ArcanistExternalLinter"]}},"0e068a1116ed03e86a2388020a821983":{"have":{"class":{"PythonFormatLinter":75}},"need":{"function":{"pht":305,"id":1614},"class":{"ArcanistExternalLinter":102,"ArcanistLintMessage":1621,"Filesystem":733,"ArcanistLinter":1752,"ArcanistLintSeverity":1835}},"xmap":{"PythonFormatLinter":["ArcanistExternalLinter"]}},"ea2beb1668dfbdd87488f18fbb20178f":{"have":{"class":{"TestsLinter":103}},"need":{"function":{"pht":318,"id":2676},"class":{"ArcanistExternalLinter":123,"ArcanistLintMessage":2683,"Filesystem":791,"ArcanistLinter":2731,"ArcanistLintSeverity":2839}},"xmap":{"TestsLinter":["ArcanistExternalLinter"]}}} \ No newline at end of file +{"__symbol_cache_version__":11,"d60c8224f471e0ecddc2a6f3c6839cd1":{"have":{"class":{"AutoPEP8FormatLinter":75}},"need":{"function":{"pht":297,"id":1317},"class":{"ArcanistExternalLinter":104,"ArcanistLintMessage":1324,"Filesystem":1168,"ArcanistLinter":1431,"ArcanistLintSeverity":1509}},"xmap":{"AutoPEP8FormatLinter":["ArcanistExternalLinter"]}},"213c3145da34ed6dfc0d70d628a2a086":{"have":{"class":{"CheckDocLinter":106}},"need":{"function":{"pht":323,"id":1868},"class":{"ArcanistExternalLinter":129,"ArcanistLintMessage":1875,"Filesystem":737,"ArcanistLinter":1923,"ArcanistLintSeverity":2009}},"xmap":{"CheckDocLinter":["ArcanistExternalLinter"]}},"7bab1f879b8a86dd9977b8c0d075935f":{"have":{"class":{"ClangFormatLinter":79}},"need":{"function":{"pht":302,"execx":787,"id":1664},"class":{"ArcanistExternalLinter":105,"ArcanistLintMessage":1671,"Filesystem":1515,"ArcanistLinter":1778,"ArcanistLintSeverity":1856}},"xmap":{"ClangFormatLinter":["ArcanistExternalLinter"]}},"e3132d407656d565ce80d359f4f0fe5f":{"have":{"class":{"PythonFormatLinter":124}},"need":{"function":{"pht":354,"id":1845},"class":{"ArcanistExternalLinter":151,"ArcanistLintMessage":1852,"Filesystem":777,"ArcanistLinter":1977,"ArcanistLintSeverity":2060}},"xmap":{"PythonFormatLinter":["ArcanistExternalLinter"]}},"ea2beb1668dfbdd87488f18fbb20178f":{"have":{"class":{"TestsLinter":103}},"need":{"function":{"pht":318,"id":2676},"class":{"ArcanistExternalLinter":123,"ArcanistLintMessage":2683,"Filesystem":791,"ArcanistLinter":2731,"ArcanistLintSeverity":2839}},"xmap":{"TestsLinter":["ArcanistExternalLinter"]}},"74a84e54995fbefdfd6b55cb8a57f57f":{"have":{"class":{"ArcanistBitcoinABCConfiguration":13}},"need":{"class":{"ArcanistConfiguration":53},"class\/interface":{"ArcanistWorkflow":122}},"xmap":{"ArcanistBitcoinABCConfiguration":["ArcanistConfiguration"]}},"2677f95ae93348c53cdab5ed15d78095":{"have":{"class":{"LocaleDependenceLinter":155}},"need":{"function":{"pht":571,"id":3291},"class":{"ArcanistExternalLinter":186,"ArcanistLintMessage":3298,"Filesystem":1041,"ArcanistLinter":3348,"ArcanistLintSeverity":3507}},"xmap":{"LocaleDependenceLinter":["ArcanistExternalLinter"]}},"8b6ad3dbb733e91406c473640377beed":{"have":{"class":{"ArcanistScriptedDiffWorkflow":59}},"need":{"function":{"phutil_console_format":714,"pht":1340,"id":2372},"class":{"ArcanistWorkflow":96,"TempFile":2379,"ExecFuture":2845,"PhutilConsole":1708,"Filesystem":2407}},"xmap":{"ArcanistScriptedDiffWorkflow":["ArcanistWorkflow"]}}} \ No newline at end of file diff --git a/arcanist/__phutil_library_map__.php b/arcanist/__phutil_library_map__.php --- a/arcanist/__phutil_library_map__.php +++ b/arcanist/__phutil_library_map__.php @@ -9,6 +9,8 @@ phutil_register_library_map(array( '__library_version__' => 2, 'class' => array( + 'ArcanistBitcoinABCConfiguration' => 'configuration/ArcanistBitcoinABCConfiguration.php', + 'ArcanistScriptedDiffWorkflow' => 'workflow/ArcanistScriptedDiffWorkflow.php', 'AutoPEP8FormatLinter' => 'linter/AutoPEP8Linter.php', 'CheckDocLinter' => 'linter/CheckDocLinter.php', 'ClangFormatLinter' => 'linter/ClangFormatLinter.php', @@ -17,6 +19,8 @@ ), 'function' => array(), 'xmap' => array( + 'ArcanistBitcoinABCConfiguration' => 'ArcanistConfiguration', + 'ArcanistScriptedDiffWorkflow' => 'ArcanistWorkflow', 'AutoPEP8FormatLinter' => 'ArcanistExternalLinter', 'CheckDocLinter' => 'ArcanistExternalLinter', 'ClangFormatLinter' => 'ArcanistExternalLinter', diff --git a/arcanist/configuration/ArcanistBitcoinABCConfiguration.php b/arcanist/configuration/ArcanistBitcoinABCConfiguration.php new file mode 100644 --- /dev/null +++ b/arcanist/configuration/ArcanistBitcoinABCConfiguration.php @@ -0,0 +1,13 @@ +buildChildWorkflow('scripted-diff', array()); + return $scriptedDiff->run(); + } + return 0; + } +} diff --git a/arcanist/workflow/ArcanistScriptedDiffWorkflow.php b/arcanist/workflow/ArcanistScriptedDiffWorkflow.php new file mode 100644 --- /dev/null +++ b/arcanist/workflow/ArcanistScriptedDiffWorkflow.php @@ -0,0 +1,172 @@ + %s ** %s\n", + pht('OKAY'), + pht('Scripted-diff check is successful')); + break; + case self::RESULT_ERRORS: + $out = phutil_console_format( + "** %s ** %s\n", + pht('FAILED'), + pht($failureReason)); + break; + default: + /* If there is nothing to display. */ + break; + } + + if ($out) { + PhutilConsole::getConsole()->writeOut($out); + } + + return $result; + } + + public function run() { + $root = $this->getWorkingCopy()->getProjectRoot(); + $api = $this->getRepositoryAPI(); + + /* Git is the only source control system supported. */ + if ($api->getSourceControlSystemName() !== 'git') { + return self::RESULT_SKIP; + } + + $headCommit = $api->getHeadCommit(); + + $script = $this->getScriptFromCommitMessage( + $api->getCommitMessage($headCommit)); + + /* If this is not a scripted-diff, skip the check. */ + if (!$script) { + return self::RESULT_SKIP; + } + + $currentBranch = $api->getBranchName(); + $baseCommit = $api->getBaseCommit(); + + /* + * Check that the working tree is clean. + * + * The getUncommittedStatus() method returns an array of flagged files for + * any of these file events: modified, added, deleted, untracked, conflict, + * missing, unstaged, uncommited. + * + * If the array is empty the working tree is clean. + * Note: until PHP 5.5, empty() only works if the parameter is a variable. + */ + $uncommited = $api->getUncommittedStatus(); + if (!empty($uncommited)) { + return $this->renderToConsole(self::RESULT_ERRORS, + 'Scripted-diff check cannot run due to uncommited changes. '. + 'Please commit or reset your changes.'); + } + + /* Checkout the base commit. */ + $out = $api->execxLocal('checkout %s', $baseCommit); + + $result = self::RESULT_OKAY; + + /* Apply the script to the base commit from the root directory. */ + $future = new ExecFuture($script); + $future->setCWD($root); + list($ret, , $stderr) = $future->resolve(); + + if ($ret != 0) { + $result = $this->renderToConsole(self::RESULT_ERRORS, + "Scripted-diff: the script returned an error ($ret): $stderr"); + } else { + /* + * If the script ran successfully, compare the ouput working tree to the + * current commit state. + */ + list($retRaw) = $api->execManualLocal( + '--no-pager diff --exit-code %s', $headCommit); + + /* + * Lint the output, then redo the comparison. + * The final result should not depend on whether the code is formatted or + * not. + */ + $lintWorkflow = $this->buildChildWorkflow('lint', + ['--apply-patches', '--output=none']); + if ($lintWorkflow->run() != 0) { + $result = $this->renderToConsole(self::RESULT_ERRORS, + 'Error while linting the base commit. '. + 'Fixes the issue then run `arc scripted-diff` again.'); + } else { + list($retLinted) = $api->execManualLocal( + '--no-pager diff --exit-code %s', $headCommit); + + if ($retRaw != 0 && $retLinted != 0) { + $result = $this->renderToConsole(self::RESULT_ERRORS, + 'Scripted-diff: scripted output does not match the commit '. + 'content.'); + } + } + } + + $api->execxLocal('reset --hard HEAD'); + $api->execxLocal('checkout %s', $currentBranch); + + if ($result == self::RESULT_OKAY) { + return $this->renderToConsole($result); + } + + return $result; + } +}