2011-01-12 10:49:48 +01:00
|
|
|
<?php
|
|
|
|
|
2011-02-19 20:36:08 +01:00
|
|
|
/**
|
|
|
|
* Installable as an SVN "pre-commit" hook.
|
|
|
|
*
|
|
|
|
* @group workflow
|
|
|
|
*/
|
2012-01-31 21:07:05 +01:00
|
|
|
final class ArcanistSvnHookPreCommitWorkflow extends ArcanistBaseWorkflow {
|
2011-01-12 10:49:48 +01:00
|
|
|
|
Make Arcanist workflow names explicit
Summary:
Currently, adding a new workflow requires you to override ArcanistConfiguration, which is messy. Instead, just load everything that extends ArcanistBaseWorkflow.
Remove all the rules tying workflow names to class names through arcane incantations.
This has a very small performance cost in that we need to load every Workflow class every time now, but we don't hit __init__ and such anymore and it was pretty negligible on my machine (98ms vs 104ms or something).
Test Plan: Ran "arc help", "arc which", "arc diff", etc.
Reviewers: edward, vrana, btrahan
Reviewed By: edward
CC: aran, zeeg
Differential Revision: https://secure.phabricator.com/D3691
2012-10-17 17:35:03 +02:00
|
|
|
public function getWorkflowName() {
|
|
|
|
return 'svn-hook-pre-commit';
|
|
|
|
}
|
|
|
|
|
2012-03-05 19:02:37 +01:00
|
|
|
public function getCommandSynopses() {
|
2011-01-12 10:49:48 +01:00
|
|
|
return phutil_console_format(<<<EOTEXT
|
2011-02-15 23:57:24 +01:00
|
|
|
**svn-hook-pre-commit** __repository__ __transaction__
|
2012-03-05 19:02:37 +01:00
|
|
|
EOTEXT
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getCommandHelp() {
|
|
|
|
return phutil_console_format(<<<EOTEXT
|
2011-01-12 10:49:48 +01:00
|
|
|
Supports: svn
|
2011-02-19 07:17:41 +01:00
|
|
|
You can install this as an SVN pre-commit hook. For more information,
|
|
|
|
see the article "Installing Arcanist SVN Hooks" in the Arcanist
|
|
|
|
documentation.
|
2011-01-12 10:49:48 +01:00
|
|
|
EOTEXT
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getArguments() {
|
|
|
|
return array(
|
|
|
|
'*' => 'svnargs',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2011-01-15 05:00:11 +01:00
|
|
|
public function shouldShellComplete() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-01-12 10:49:48 +01:00
|
|
|
public function run() {
|
|
|
|
|
|
|
|
$svnargs = $this->getArgument('svnargs');
|
|
|
|
$repository = $svnargs[0];
|
|
|
|
$transaction = $svnargs[1];
|
|
|
|
|
|
|
|
list($commit_message) = execx(
|
|
|
|
'svnlook log --transaction %s %s',
|
|
|
|
$transaction,
|
|
|
|
$repository);
|
|
|
|
|
2011-02-15 23:57:24 +01:00
|
|
|
if (strpos($commit_message, '@bypass-lint') !== false) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-01-12 10:49:48 +01:00
|
|
|
// TODO: Do stuff with commit message.
|
2011-01-13 00:45:17 +01:00
|
|
|
|
2011-01-12 10:49:48 +01:00
|
|
|
list($changed) = execx(
|
|
|
|
'svnlook changed --transaction %s %s',
|
|
|
|
$transaction,
|
|
|
|
$repository);
|
2011-01-13 00:45:17 +01:00
|
|
|
|
2011-01-12 10:49:48 +01:00
|
|
|
$paths = array();
|
|
|
|
$changed = explode("\n", trim($changed));
|
|
|
|
foreach ($changed as $line) {
|
|
|
|
$matches = null;
|
|
|
|
preg_match('/^..\s*(.*)$/', $line, $matches);
|
|
|
|
$paths[$matches[1]] = strlen($matches[1]);
|
|
|
|
}
|
2011-01-13 00:45:17 +01:00
|
|
|
|
2011-01-12 10:49:48 +01:00
|
|
|
$resolved = array();
|
|
|
|
$failed = array();
|
|
|
|
$missing = array();
|
|
|
|
$found = array();
|
|
|
|
asort($paths);
|
2011-01-13 00:45:17 +01:00
|
|
|
|
2011-01-12 10:49:48 +01:00
|
|
|
foreach ($paths as $path => $length) {
|
|
|
|
foreach ($resolved as $rpath => $root) {
|
|
|
|
if (!strncmp($path, $rpath, strlen($rpath))) {
|
|
|
|
$resolved[$path] = $root;
|
|
|
|
continue 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$config = $path;
|
2011-01-13 00:45:17 +01:00
|
|
|
|
2011-01-12 10:49:48 +01:00
|
|
|
if (basename($config) == '.arcconfig') {
|
|
|
|
$resolved[$config] = $config;
|
|
|
|
continue;
|
|
|
|
}
|
2011-01-13 00:45:17 +01:00
|
|
|
|
2011-01-12 10:49:48 +01:00
|
|
|
$config = rtrim($config, '/');
|
|
|
|
$last_config = $config;
|
|
|
|
do {
|
|
|
|
if (!empty($missing[$config])) {
|
|
|
|
break;
|
|
|
|
} else if (!empty($found[$config])) {
|
|
|
|
$resolved[$path] = $found[$config];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
list($err) = exec_manual(
|
|
|
|
'svnlook cat --transaction %s %s %s',
|
|
|
|
$transaction,
|
|
|
|
$repository,
|
|
|
|
$config ? $config.'/.arcconfig' : '.arcconfig');
|
|
|
|
if ($err) {
|
|
|
|
$missing[$path] = true;
|
|
|
|
} else {
|
|
|
|
$resolved[$path] = $config ? $config.'/.arcconfig' : '.arcconfig';
|
|
|
|
$found[$config] = $resolved[$path];
|
2011-03-05 09:42:46 +01:00
|
|
|
break;
|
2011-01-12 10:49:48 +01:00
|
|
|
}
|
|
|
|
$config = dirname($config);
|
|
|
|
if ($config == '.') {
|
|
|
|
$config = '';
|
|
|
|
}
|
|
|
|
if ($config == $last_config) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
$last_config = $config;
|
|
|
|
} while (true);
|
2011-01-13 00:45:17 +01:00
|
|
|
|
2011-01-12 10:49:48 +01:00
|
|
|
if (empty($resolved[$path])) {
|
|
|
|
$failed[] = $path;
|
|
|
|
}
|
|
|
|
}
|
2011-01-13 00:45:17 +01:00
|
|
|
|
2011-01-12 10:49:48 +01:00
|
|
|
if ($failed && $resolved) {
|
|
|
|
$failed_paths = ' '.implode("\n ", $failed);
|
|
|
|
$resolved_paths = ' '.implode("\n ", array_keys($resolved));
|
|
|
|
throw new ArcanistUsageException(
|
|
|
|
"This commit includes a mixture of files in Arcanist projects and ".
|
|
|
|
"outside of Arcanist projects. A commit which affects an Arcanist ".
|
|
|
|
"project must affect only that project.\n\n".
|
|
|
|
"Files in projects:\n\n".
|
|
|
|
$resolved_paths."\n\n".
|
|
|
|
"Files not in projects:\n\n".
|
|
|
|
$failed_paths);
|
|
|
|
}
|
2011-01-13 00:45:17 +01:00
|
|
|
|
2011-01-12 10:49:48 +01:00
|
|
|
if (!$resolved) {
|
|
|
|
// None of the affected paths are beneath a .arcconfig file.
|
2011-02-15 23:57:24 +01:00
|
|
|
return 0;
|
2011-01-12 10:49:48 +01:00
|
|
|
}
|
2011-01-13 00:45:17 +01:00
|
|
|
|
2011-01-12 10:49:48 +01:00
|
|
|
$groups = array();
|
|
|
|
foreach ($resolved as $path => $project) {
|
|
|
|
$groups[$project][] = $path;
|
|
|
|
}
|
|
|
|
if (count($groups) > 1) {
|
|
|
|
$message = array();
|
Add a lint check for clobbering locals with iterators
Summary:
See D2049, D2050. Identify reuses of locals as iterator variables. Before raising an error, we require:
- Variable is declared before the loop.
- Variable is used after the loop, ignoring uses as an iterator variable.
I think this identifies all problems with a very low false positive rate (the false positives are suspicious/unconventional code, but not necessarily errors).
Also fix an issue identified by the linter.
Test Plan:
- Verified this identified the bugs in D2049 and D2050.
- Ran linter against libphutil/, arcanist/ and phabricator/ (see D2051, this, and next diff).
- Ran unit tests.
Reviewers: vrana, btrahan
Reviewed By: vrana
CC: aran, epriestley, jungejason
Differential Revision: https://secure.phabricator.com/D2052
2012-03-29 22:21:18 +02:00
|
|
|
foreach ($groups as $project => $group) {
|
|
|
|
$message[] = "Files underneath '{$project}':\n\n";
|
2011-01-12 10:49:48 +01:00
|
|
|
$message[] = " ".implode("\n ", $group)."\n\n";
|
|
|
|
}
|
|
|
|
$message = implode('', $message);
|
|
|
|
throw new ArcanistUsageException(
|
|
|
|
"This commit includes a mixture of files from different Arcanist ".
|
|
|
|
"projects. A commit which affects an Arcanist project must affect ".
|
|
|
|
"only that project.\n\n".
|
|
|
|
$message);
|
|
|
|
}
|
2011-01-13 00:45:17 +01:00
|
|
|
|
2011-02-15 23:57:24 +01:00
|
|
|
$config_file = key($groups);
|
|
|
|
$project_root = dirname($config_file);
|
2011-01-12 10:49:48 +01:00
|
|
|
$paths = reset($groups);
|
2011-01-13 00:45:17 +01:00
|
|
|
|
2011-02-15 23:57:24 +01:00
|
|
|
list($config) = execx(
|
|
|
|
'svnlook cat --transaction %s %s %s',
|
|
|
|
$transaction,
|
|
|
|
$repository,
|
|
|
|
$config_file);
|
|
|
|
|
|
|
|
$working_copy = ArcanistWorkingCopyIdentity::newFromRootAndConfigFile(
|
|
|
|
$project_root,
|
2011-02-25 01:34:27 +01:00
|
|
|
$config,
|
|
|
|
$config_file." (svnlook: {$transaction} {$repository})");
|
2011-02-15 23:57:24 +01:00
|
|
|
|
[arc svn-hook-pre-commit] Access working copy
Summary:
Creates a new hook API that can be used to interface with
SVN/Git/Mercurial in the context of a commit hook. Currently only adds a
function to read the modified file data in a Subversion commit hook.
An object of this API is created in the SvnHookPreCommitWorkflow and
passed on the Lint Engine which then uses it to access current file
data, of the way the APIs seem to be structured); linters use the
getData function which is essentially a wrapper around the engine's
call, with another layer of caching.
Task ID: #770556
Blame Rev:
Test Plan:
- Create a local svn repository and add a minimal hook to run the local
version of arc to test commits
(http://phabricator.com/docs/arcanist/article/Installing_Arcanist_SVN_Hooks.html)
- Create a temporary repository that can trigger any of the linters
available, and test against a temporary linter by committing against
the test repository: the linter should be able to access all required
files by using loadData/getData in the LintEngine and Linter.
Revert Plan:
Tags: lint, svn-hook-pre-commit
Reviewers: jungejason, asukhachev, epriestley, aran
Reviewed By: epriestley
CC: aran, jungejason, epriestley, kunalb, asukhachev
Differential Revision: https://secure.phabricator.com/D1256
2011-12-21 05:26:05 +01:00
|
|
|
$repository_api = new ArcanistSubversionHookAPI(
|
|
|
|
$project_root,
|
|
|
|
$transaction,
|
|
|
|
$repository);
|
|
|
|
|
2012-11-21 00:37:31 +01:00
|
|
|
$lint_engine = $working_copy->getConfig('lint.engine');
|
2011-02-15 23:57:24 +01:00
|
|
|
if (!$lint_engine) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
$engine = newv($lint_engine, array());
|
|
|
|
$engine->setWorkingCopy($working_copy);
|
|
|
|
$engine->setMinimumSeverity(ArcanistLintSeverity::SEVERITY_ERROR);
|
[arc svn-hook-pre-commit] Access working copy
Summary:
Creates a new hook API that can be used to interface with
SVN/Git/Mercurial in the context of a commit hook. Currently only adds a
function to read the modified file data in a Subversion commit hook.
An object of this API is created in the SvnHookPreCommitWorkflow and
passed on the Lint Engine which then uses it to access current file
data, of the way the APIs seem to be structured); linters use the
getData function which is essentially a wrapper around the engine's
call, with another layer of caching.
Task ID: #770556
Blame Rev:
Test Plan:
- Create a local svn repository and add a minimal hook to run the local
version of arc to test commits
(http://phabricator.com/docs/arcanist/article/Installing_Arcanist_SVN_Hooks.html)
- Create a temporary repository that can trigger any of the linters
available, and test against a temporary linter by committing against
the test repository: the linter should be able to access all required
files by using loadData/getData in the LintEngine and Linter.
Revert Plan:
Tags: lint, svn-hook-pre-commit
Reviewers: jungejason, asukhachev, epriestley, aran
Reviewed By: epriestley
CC: aran, jungejason, epriestley, kunalb, asukhachev
Differential Revision: https://secure.phabricator.com/D1256
2011-12-21 05:26:05 +01:00
|
|
|
$engine->setPaths($paths);
|
2011-02-15 23:57:24 +01:00
|
|
|
$engine->setCommitHookMode(true);
|
[arc svn-hook-pre-commit] Access working copy
Summary:
Creates a new hook API that can be used to interface with
SVN/Git/Mercurial in the context of a commit hook. Currently only adds a
function to read the modified file data in a Subversion commit hook.
An object of this API is created in the SvnHookPreCommitWorkflow and
passed on the Lint Engine which then uses it to access current file
data, of the way the APIs seem to be structured); linters use the
getData function which is essentially a wrapper around the engine's
call, with another layer of caching.
Task ID: #770556
Blame Rev:
Test Plan:
- Create a local svn repository and add a minimal hook to run the local
version of arc to test commits
(http://phabricator.com/docs/arcanist/article/Installing_Arcanist_SVN_Hooks.html)
- Create a temporary repository that can trigger any of the linters
available, and test against a temporary linter by committing against
the test repository: the linter should be able to access all required
files by using loadData/getData in the LintEngine and Linter.
Revert Plan:
Tags: lint, svn-hook-pre-commit
Reviewers: jungejason, asukhachev, epriestley, aran
Reviewed By: epriestley
CC: aran, jungejason, epriestley, kunalb, asukhachev
Differential Revision: https://secure.phabricator.com/D1256
2011-12-21 05:26:05 +01:00
|
|
|
$engine->setHookAPI($repository_api);
|
2011-02-15 23:57:24 +01:00
|
|
|
|
2011-02-17 21:45:15 +01:00
|
|
|
try {
|
|
|
|
$results = $engine->run();
|
|
|
|
} catch (ArcanistNoEffectException $no_effect) {
|
|
|
|
// Nothing to do, bail out.
|
|
|
|
return 0;
|
|
|
|
}
|
2011-02-15 23:57:24 +01:00
|
|
|
|
|
|
|
$failures = array();
|
|
|
|
foreach ($results as $result) {
|
|
|
|
if (!$result->getMessages()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$failures[] = $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($failures) {
|
|
|
|
$at = "@";
|
|
|
|
$msg = phutil_console_format(
|
|
|
|
"\n**LINT ERRORS**\n\n".
|
|
|
|
"This changeset has lint errors. You must fix all lint errors before ".
|
|
|
|
"you can commit.\n\n".
|
|
|
|
"You can add '{$at}bypass-lint' to your commit message to disable ".
|
|
|
|
"lint checks for this commit, or '{$at}nolint' to the file with ".
|
|
|
|
"errors to disable lint for that file.\n\n");
|
|
|
|
echo phutil_console_wrap($msg);
|
2012-06-02 02:32:35 +02:00
|
|
|
|
|
|
|
$renderer = new ArcanistLintConsoleRenderer();
|
2011-02-15 23:57:24 +01:00
|
|
|
foreach ($failures as $result) {
|
|
|
|
echo $renderer->renderLintResult($result);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
2011-01-12 10:49:48 +01:00
|
|
|
|
2011-02-17 08:14:55 +01:00
|
|
|
return 0;
|
2011-01-12 10:49:48 +01:00
|
|
|
}
|
|
|
|
}
|