2011-01-12 10:49:48 +01:00
|
|
|
<?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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
class ArcanistSvnHookPreCommitWorkflow extends ArcanistBaseWorkflow {
|
|
|
|
|
|
|
|
public function getCommandHelp() {
|
|
|
|
return phutil_console_format(<<<EOTEXT
|
2011-02-15 23:57:24 +01:00
|
|
|
**svn-hook-pre-commit** __repository__ __transaction__
|
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];
|
|
|
|
}
|
|
|
|
$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-02-15 23:57:24 +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();
|
|
|
|
foreach ($groups as $config => $group) {
|
|
|
|
$message[] = "Files underneath '{$config}':\n\n";
|
|
|
|
$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);
|
|
|
|
|
2011-01-12 10:49:48 +01:00
|
|
|
$data = array();
|
|
|
|
foreach ($paths as $path) {
|
2011-02-15 23:57:24 +01:00
|
|
|
// TODO: This should be done in parallel.
|
2011-01-12 10:49:48 +01:00
|
|
|
list($err, $filedata) = exec_manual(
|
|
|
|
'svnlook cat --transaction %s %s %s',
|
|
|
|
$transaction,
|
|
|
|
$repository,
|
|
|
|
$path);
|
|
|
|
$data[$path] = $err ? null : $filedata;
|
|
|
|
}
|
2011-01-13 00:45:17 +01:00
|
|
|
|
2011-02-15 23:57:24 +01:00
|
|
|
$working_copy = ArcanistWorkingCopyIdentity::newFromRootAndConfigFile(
|
|
|
|
$project_root,
|
|
|
|
$config);
|
|
|
|
|
|
|
|
$lint_engine = $working_copy->getConfig('lint_engine');
|
|
|
|
if (!$lint_engine) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
PhutilSymbolLoader::loadClass($lint_engine);
|
|
|
|
|
|
|
|
$engine = newv($lint_engine, array());
|
|
|
|
$engine->setWorkingCopy($working_copy);
|
|
|
|
$engine->setMinimumSeverity(ArcanistLintSeverity::SEVERITY_ERROR);
|
|
|
|
$engine->setPaths(array_keys($data));
|
|
|
|
$engine->setFileData($data);
|
|
|
|
$engine->setCommitHookMode(true);
|
|
|
|
|
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
|
|
|
|
|
|
|
$renderer = new ArcanistLintRenderer();
|
|
|
|
$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);
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|