Changeset View
Changeset View
Standalone View
Standalone View
arcanist/workflow/ArcanistScriptedDiffWorkflow.php
- This file was added.
<?php | |||||
/** | |||||
* Check scripted diff.validity | |||||
*/ | |||||
final class ArcanistScriptedDiffWorkflow extends ArcanistWorkflow { | |||||
const SCRIPTED_DIFF_START = '-BEGIN VERIFY SCRIPT-'; | |||||
const SCRIPTED_DIFF_END = '-END VERIFY SCRIPT-'; | |||||
const RESULT_OKAY = 0; | |||||
const RESULT_WARNINGS = 1; | |||||
const RESULT_ERRORS = 2; | |||||
const RESULT_SKIP = 3; | |||||
private function getScriptFromCommitMessage($message) { | |||||
$pattern = | |||||
'/'.self::SCRIPTED_DIFF_START.'(.+)'.self::SCRIPTED_DIFF_END.'/s'; | |||||
if (preg_match($pattern, $message, $matches)) { | |||||
return ltrim($matches[1]); | |||||
} | |||||
return null; | |||||
} | |||||
public function getWorkflowName() { | |||||
return 'scripted-diff'; | |||||
} | |||||
public function getCommandSynopses() { | |||||
return phutil_console_format(<<<EOTEXT | |||||
**scripted-diff** | |||||
EOTEXT | |||||
); | |||||
} | |||||
public function getCommandHelp() { | |||||
return phutil_console_format(<<<EOTEXT | |||||
Supports: git | |||||
Check a scripted-diff commit matches the working directory content | |||||
EOTEXT | |||||
); | |||||
} | |||||
public function requiresWorkingCopy() { | |||||
return true; | |||||
} | |||||
public function requiresRepositoryAPI() { | |||||
return true; | |||||
} | |||||
private function render_to_console($result, $failureReason = '') { | |||||
$out = null; | |||||
switch($result) { | |||||
case self::RESULT_OKAY: | |||||
$out = phutil_console_format( | |||||
"**<bg:green> %s </bg>** %s\n", | |||||
pht('OKAY'), | |||||
pht('Scripted-diff check is successful')); | |||||
break; | |||||
case self::RESULT_ERRORS: | |||||
$out = phutil_console_format( | |||||
"**<bg:red> %s </bg>** %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->render_to_console(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->render_to_console(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->render_to_console(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->render_to_console(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->render_to_console($result); | |||||
} | |||||
return $result; | |||||
} | |||||
} |