1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-18 10:41:08 +01:00

Basic support for Mercurial in Diffusion

Summary: Change import script plus almost all the view stuff. Still some rough
edges but this seems to mostly work. Blame is currently unsupported but I think
everything else works properly.

Test Plan:
Imported the hg repository itself. It doesn't immediately seem completely
broken. Here are some screens:

https://secure.phabricator.com/file/view/PHID-FILE-1438b71cc7c4a2eb4569/
https://secure.phabricator.com/file/view/PHID-FILE-3cec4f72f39e7de2d041/
https://secure.phabricator.com/file/view/PHID-FILE-2ea4883f160e8e5098f9/
https://secure.phabricator.com/file/view/PHID-FILE-35f751a36ebf65399ade/

All the parsers were able to churn through it without errors.

Ran the new "reparse.php" script in various one-commit and repository modes.

Browsed/imported some git repos for good measure.

NOTE: The hg repository is only 15,000 commits and around 1,000 files.
Performance is okay but hg doesn't provide performant, native APIs to get some
data efficiently so we have to do some dumb stuff. If some of these interfaces
are cripplingly slow or whatever, let me know and we can start bundling some
Mercurial extensions with Arcanist.

Reviewers: Makinde, jungejason, nh, tuomaspelkonen, aran

Reviewed By: Makinde

CC: aran, Makinde, epriestley

Differential Revision: 960
This commit is contained in:
epriestley 2011-09-26 11:07:38 -07:00
parent 46373f2be7
commit b1e1b1f9bd
29 changed files with 1007 additions and 70 deletions

View file

@ -261,6 +261,13 @@ phutil_register_library_map(array(
'DiffusionHomeController' => 'applications/diffusion/controller/home', 'DiffusionHomeController' => 'applications/diffusion/controller/home',
'DiffusionLastModifiedController' => 'applications/diffusion/controller/lastmodified', 'DiffusionLastModifiedController' => 'applications/diffusion/controller/lastmodified',
'DiffusionLastModifiedQuery' => 'applications/diffusion/query/lastmodified/base', 'DiffusionLastModifiedQuery' => 'applications/diffusion/query/lastmodified/base',
'DiffusionMercurialBranchQuery' => 'applications/diffusion/query/branch/mercurial',
'DiffusionMercurialBrowseQuery' => 'applications/diffusion/query/browse/mercurial',
'DiffusionMercurialDiffQuery' => 'applications/diffusion/query/diff/mercurial',
'DiffusionMercurialFileContentQuery' => 'applications/diffusion/query/filecontent/mercurial',
'DiffusionMercurialHistoryQuery' => 'applications/diffusion/query/history/mercurial',
'DiffusionMercurialLastModifiedQuery' => 'applications/diffusion/query/lastmodified/mercurial',
'DiffusionMercurialRequest' => 'applications/diffusion/request/mercurial',
'DiffusionPathChange' => 'applications/diffusion/data/pathchange', 'DiffusionPathChange' => 'applications/diffusion/data/pathchange',
'DiffusionPathChangeQuery' => 'applications/diffusion/query/pathchange/base', 'DiffusionPathChangeQuery' => 'applications/diffusion/query/pathchange/base',
'DiffusionPathCompleteController' => 'applications/diffusion/controller/pathcomplete', 'DiffusionPathCompleteController' => 'applications/diffusion/controller/pathcomplete',
@ -582,6 +589,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryGitHubNotification' => 'applications/repository/storage/githubnotification', 'PhabricatorRepositoryGitHubNotification' => 'applications/repository/storage/githubnotification',
'PhabricatorRepositoryGitHubPostReceiveController' => 'applications/repository/controller/github-post-receive', 'PhabricatorRepositoryGitHubPostReceiveController' => 'applications/repository/controller/github-post-receive',
'PhabricatorRepositoryListController' => 'applications/repository/controller/list', 'PhabricatorRepositoryListController' => 'applications/repository/controller/list',
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/mercurial',
'PhabricatorRepositoryMercurialCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/mercurial', 'PhabricatorRepositoryMercurialCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/mercurial',
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/mercurial', 'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/mercurial',
'PhabricatorRepositoryMercurialPullDaemon' => 'applications/repository/daemon/mercurialpull', 'PhabricatorRepositoryMercurialPullDaemon' => 'applications/repository/daemon/mercurialpull',
@ -927,6 +935,13 @@ phutil_register_library_map(array(
'DiffusionHistoryTableView' => 'DiffusionView', 'DiffusionHistoryTableView' => 'DiffusionView',
'DiffusionHomeController' => 'DiffusionController', 'DiffusionHomeController' => 'DiffusionController',
'DiffusionLastModifiedController' => 'DiffusionController', 'DiffusionLastModifiedController' => 'DiffusionController',
'DiffusionMercurialBranchQuery' => 'DiffusionBranchQuery',
'DiffusionMercurialBrowseQuery' => 'DiffusionBrowseQuery',
'DiffusionMercurialDiffQuery' => 'DiffusionDiffQuery',
'DiffusionMercurialFileContentQuery' => 'DiffusionFileContentQuery',
'DiffusionMercurialHistoryQuery' => 'DiffusionHistoryQuery',
'DiffusionMercurialLastModifiedQuery' => 'DiffusionLastModifiedQuery',
'DiffusionMercurialRequest' => 'DiffusionRequest',
'DiffusionPathCompleteController' => 'DiffusionController', 'DiffusionPathCompleteController' => 'DiffusionController',
'DiffusionPathQueryTestCase' => 'PhabricatorTestCase', 'DiffusionPathQueryTestCase' => 'PhabricatorTestCase',
'DiffusionPathValidateController' => 'DiffusionController', 'DiffusionPathValidateController' => 'DiffusionController',
@ -1188,10 +1203,8 @@ phutil_register_library_map(array(
'PhabricatorRepositoryGitHubNotification' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryGitHubNotification' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryGitHubPostReceiveController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryGitHubPostReceiveController' => 'PhabricatorRepositoryController',
'PhabricatorRepositoryListController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryListController' => 'PhabricatorRepositoryController',
<<<<<<< HEAD 'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
'PhabricatorRepositoryMercurialCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon', 'PhabricatorRepositoryMercurialCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon',
=======
>>>>>>> Add a Mercurial message parser
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
'PhabricatorRepositoryMercurialPullDaemon' => 'PhabricatorRepositoryPullLocalDaemon', 'PhabricatorRepositoryMercurialPullDaemon' => 'PhabricatorRepositoryPullLocalDaemon',
'PhabricatorRepositoryPullLocalDaemon' => 'PhabricatorRepositoryDaemon', 'PhabricatorRepositoryPullLocalDaemon' => 'PhabricatorRepositoryDaemon',

View file

@ -172,6 +172,7 @@ class DiffusionCommitController extends DiffusionController {
$vcs_supports_directory_changes = true; $vcs_supports_directory_changes = true;
break; break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$vcs_supports_directory_changes = false; $vcs_supports_directory_changes = false;
break; break;
default: default:

View file

@ -33,6 +33,9 @@ abstract class DiffusionBranchQuery {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$class = 'DiffusionGitBranchQuery'; $class = 'DiffusionGitBranchQuery';
break; break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$class = 'DiffusionMercurialBranchQuery';
break;
default: default:
throw new Exception("Unsupported VCS!"); throw new Exception("Unsupported VCS!");
} }

View file

@ -0,0 +1,40 @@
<?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.
*/
final class DiffusionMercurialBranchQuery extends DiffusionBranchQuery {
protected function executeQuery() {
$drequest = $this->getRequest();
$repository = $drequest->getRepository();
list($stdout) = $repository->execxLocalCommand(
'branches');
$branch_info = ArcanistMercurialParser::parseMercurialBranches($stdout);
$branches = array();
foreach ($branch_info as $name => $info) {
$branch = new DiffusionBranchInformation();
$branch->setName($name);
$branch->setHeadCommitIdentifier($info['rev']);
$branches[] = $branch;
}
return $branches;
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('arcanist', 'repository/parser/mercurial');
phutil_require_module('phabricator', 'applications/diffusion/data/branch');
phutil_require_module('phabricator', 'applications/diffusion/query/branch/base');
phutil_require_source('DiffusionMercurialBranchQuery.php');

View file

@ -46,6 +46,9 @@ abstract class DiffusionBrowseQuery {
// TODO: Verify local-path? // TODO: Verify local-path?
$class = 'DiffusionGitBrowseQuery'; $class = 'DiffusionGitBrowseQuery';
break; break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$class = 'DiffusionMercurialBrowseQuery';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$class = 'DiffusionSvnBrowseQuery'; $class = 'DiffusionSvnBrowseQuery';
break; break;

View file

@ -0,0 +1,95 @@
<?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.
*/
final class DiffusionMercurialBrowseQuery extends DiffusionBrowseQuery {
protected function executeQuery() {
$drequest = $this->getRequest();
$repository = $drequest->getRepository();
$path = $drequest->getPath();
$commit = $drequest->getCommit();
// TODO: This is a really really awful mess but Mercurial doesn't offer
// an equivalent of "git ls-files -- directory". If it's any comfort, this
// is what "hgweb" does too, see:
//
// http://selenic.com/repo/hg/file/91dc8878f888/mercurial/hgweb/webcommands.py#l320
//
// derp derp derp derp
//
// Anyway, figure out what's in this path by applying massive amounts
// of brute force.
list($entire_manifest) = $repository->execxLocalCommand(
'manifest --rev %s',
$commit);
$entire_manifest = explode("\n", $entire_manifest);
$results = array();
$match_against = trim($path, '/');
$match_len = strlen($match_against);
// For the root, don't trim. For other paths, trim the "/" after we match.
// We need this because Mercurial's canonical paths have no leading "/",
// but ours do.
$trim_len = $match_len ? $match_len + 1 : 0;
foreach ($entire_manifest as $path) {
if (strncmp($path, $match_against, $match_len)) {
continue;
}
if (!strlen($path)) {
continue;
}
$remainder = substr($path, $trim_len);
if (!strlen($remainder)) {
// There is a file with this exact name in the manifest, so clearly
// it's a file.
$this->reason = self::REASON_IS_FILE;
return array();
}
$parts = explode('/', $remainder);
if (count($parts) == 1) {
$type = DifferentialChangeType::FILE_NORMAL;
} else {
$type = DifferentialChangeType::FILE_DIRECTORY;
}
$results[reset($parts)] = $type;
}
foreach ($results as $key => $type) {
$result = new DiffusionRepositoryPath();
$result->setPath($key);
$result->setFileType($type);
$results[$key] = $result;
}
if (empty($results)) {
// TODO: Detect "deleted" by issuing "hg log"?
$this->reason = self::REASON_IS_NONEXISTENT;
}
return $results;
}
}

View file

@ -0,0 +1,14 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/differential/constants/changetype');
phutil_require_module('phabricator', 'applications/diffusion/data/repositorypath');
phutil_require_module('phabricator', 'applications/diffusion/query/browse/base');
phutil_require_source('DiffusionMercurialBrowseQuery.php');

View file

@ -34,6 +34,9 @@ abstract class DiffusionDiffQuery {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$class = 'DiffusionGitDiffQuery'; $class = 'DiffusionGitDiffQuery';
break; break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$class = 'DiffusionMercurialDiffQuery';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$class = 'DiffusionSvnDiffQuery'; $class = 'DiffusionSvnDiffQuery';
break; break;

View file

@ -0,0 +1,56 @@
<?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.
*/
final class DiffusionMercurialDiffQuery extends DiffusionDiffQuery {
protected function executeQuery() {
$drequest = $this->getRequest();
$repository = $drequest->getRepository();
$effective_commit = $this->getEffectiveCommit();
if (!$effective_commit) {
return null;
}
// TODO: This side effect is kind of skethcy.
$drequest->setCommit($effective_commit);
$path = $drequest->getPath();
list($raw_diff) = $repository->execxLocalCommand(
'diff -U %d --git --change %s -- %s',
65535,
$effective_commit,
$path);
$parser = new ArcanistDiffParser();
$parser->setDetectBinaryFiles(true);
$changes = $parser->parseDiff($raw_diff);
$diff = DifferentialDiff::newFromRawChanges($changes);
$changesets = $diff->getChangesets();
$changeset = reset($changesets);
$this->renderingReference =
$drequest->getBranchURIComponent($drequest->getBranch()).
$drequest->getPath().';'.
$drequest->getCommit();
return $changeset;
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('arcanist', 'parser/diff');
phutil_require_module('phabricator', 'applications/differential/storage/diff');
phutil_require_module('phabricator', 'applications/diffusion/query/diff/base');
phutil_require_source('DiffusionMercurialDiffQuery.php');

View file

@ -35,6 +35,9 @@ abstract class DiffusionFileContentQuery {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$class = 'DiffusionGitFileContentQuery'; $class = 'DiffusionGitFileContentQuery';
break; break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$class = 'DiffusionMercurialFileContentQuery';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$class = 'DiffusionSvnFileContentQuery'; $class = 'DiffusionSvnFileContentQuery';
break; break;

View file

@ -0,0 +1,47 @@
<?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.
*/
final class DiffusionMercurialFileContentQuery
extends DiffusionFileContentQuery {
protected function executeQuery() {
$drequest = $this->getRequest();
$repository = $drequest->getRepository();
$path = $drequest->getPath();
$commit = $drequest->getCommit();
list($corpus) = $repository->execxLocalCommand(
'cat --rev %s -- %s',
$commit,
$path);
$file_content = new DiffusionFileContent();
$file_content->setCorpus($corpus);
return $file_content;
}
protected function tokenizeLine($line) {
// TODO: Support blame.
throw new Exception(
"Diffusion does not currently support blame for Mercurial.");
}
}

View file

@ -0,0 +1,13 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/diffusion/data/filecontent');
phutil_require_module('phabricator', 'applications/diffusion/query/filecontent/base');
phutil_require_source('DiffusionMercurialFileContentQuery.php');

View file

@ -41,6 +41,9 @@ abstract class DiffusionHistoryQuery {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$class = 'DiffusionSvnHistoryQuery'; $class = 'DiffusionSvnHistoryQuery';
break; break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$class = 'DiffusionMercurialHistoryQuery';
break;
default: default:
throw new Exception("Unsupported VCS!"); throw new Exception("Unsupported VCS!");
} }
@ -90,4 +93,76 @@ abstract class DiffusionHistoryQuery {
} }
abstract protected function executeQuery(); abstract protected function executeQuery();
final protected function loadHistoryForCommitIdentifiers(array $identifiers) {
if (!$identifiers) {
return array();
}
$commits = array();
$commit_data = array();
$path_changes = array();
$drequest = $this->getRequest();
$repository = $drequest->getRepository();
$path = $drequest->getPath();
$commits = id(new PhabricatorRepositoryCommit())->loadAllWhere(
'repositoryID = %d AND commitIdentifier IN (%Ls)',
$repository->getID(),
$identifiers);
$commits = mpull($commits, null, 'getCommitIdentifier');
if (!$commits) {
return array();
}
$commit_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere(
'commitID in (%Ld)',
mpull($commits, 'getID'));
$commit_data = mpull($commit_data, null, 'getCommitID');
$conn_r = $repository->establishConnection('r');
$path_normal = DiffusionPathIDQuery::normalizePath($path);
$paths = queryfx_all(
$conn_r,
'SELECT id, path FROM %T WHERE path IN (%Ls)',
PhabricatorRepository::TABLE_PATH,
array($path_normal));
$paths = ipull($paths, 'id', 'path');
$path_id = idx($paths, $path_normal);
$path_changes = queryfx_all(
$conn_r,
'SELECT * FROM %T WHERE commitID IN (%Ld) AND pathID = %d',
PhabricatorRepository::TABLE_PATHCHANGE,
mpull($commits, 'getID'),
$path_id);
$path_changes = ipull($path_changes, null, 'commitID');
$history = array();
foreach ($identifiers as $identifier) {
$item = new DiffusionPathChange();
$item->setCommitIdentifier($identifier);
$commit = idx($commits, $identifier);
if ($commit) {
$item->setCommit($commit);
$data = idx($commit_data, $commit->getID());
if ($data) {
$item->setCommitData($data);
}
$change = idx($path_changes, $commit->getID());
if ($change) {
$item->setChangeType($change['changeType']);
$item->setFileType($change['fileType']);
}
}
$history[] = $item;
}
return $history;
}
} }

View file

@ -6,9 +6,16 @@
phutil_require_module('phabricator', 'applications/diffusion/data/pathchange');
phutil_require_module('phabricator', 'applications/diffusion/query/pathid/base');
phutil_require_module('phabricator', 'applications/repository/constants/repositorytype'); phutil_require_module('phabricator', 'applications/repository/constants/repositorytype');
phutil_require_module('phabricator', 'applications/repository/storage/commit');
phutil_require_module('phabricator', 'applications/repository/storage/commitdata');
phutil_require_module('phabricator', 'applications/repository/storage/repository');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phutil', 'symbols'); phutil_require_module('phutil', 'symbols');
phutil_require_module('phutil', 'utils');
phutil_require_source('DiffusionHistoryQuery.php'); phutil_require_source('DiffusionHistoryQuery.php');

View file

@ -43,67 +43,7 @@ final class DiffusionGitHistoryQuery extends DiffusionHistoryQuery {
$hashes = explode("\n", $stdout); $hashes = explode("\n", $stdout);
$hashes = array_filter($hashes); $hashes = array_filter($hashes);
$commits = array(); return $this->loadHistoryForCommitIdentifiers($hashes);
$commit_data = array();
$path_changes = array();
$conn_r = $repository->establishConnection('r');
if ($hashes) {
$commits = id(new PhabricatorRepositoryCommit())->loadAllWhere(
'repositoryID = %d AND commitIdentifier IN (%Ls)',
$repository->getID(),
$hashes);
$commits = mpull($commits, null, 'getCommitIdentifier');
if ($commits) {
$commit_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere(
'commitID in (%Ld)',
mpull($commits, 'getID'));
$commit_data = mpull($commit_data, null, 'getCommitID');
}
if ($commits) {
$path_normal = '/'.trim($path, '/');
$paths = queryfx_all(
$conn_r,
'SELECT id, path FROM %T WHERE path IN (%Ls)',
PhabricatorRepository::TABLE_PATH,
array($path_normal));
$paths = ipull($paths, 'id', 'path');
$path_id = idx($paths, $path_normal);
$path_changes = queryfx_all(
$conn_r,
'SELECT * FROM %T WHERE commitID IN (%Ld) AND pathID = %d',
PhabricatorRepository::TABLE_PATHCHANGE,
mpull($commits, 'getID'),
$path_id);
$path_changes = ipull($path_changes, null, 'commitID');
}
}
$history = array();
foreach ($hashes as $hash) {
$item = new DiffusionPathChange();
$item->setCommitIdentifier($hash);
$commit = idx($commits, $hash);
if ($commit) {
$item->setCommit($commit);
$data = idx($commit_data, $commit->getID());
if ($data) {
$item->setCommitData($data);
}
$change = idx($path_changes, $commit->getID());
if ($change) {
$item->setChangeType($change['changeType']);
$item->setFileType($change['fileType']);
}
}
$history[] = $item;
}
return $history;
} }
} }

View file

@ -6,15 +6,9 @@
phutil_require_module('phabricator', 'applications/diffusion/data/pathchange');
phutil_require_module('phabricator', 'applications/diffusion/query/history/base'); phutil_require_module('phabricator', 'applications/diffusion/query/history/base');
phutil_require_module('phabricator', 'applications/repository/storage/commit');
phutil_require_module('phabricator', 'applications/repository/storage/commitdata');
phutil_require_module('phabricator', 'applications/repository/storage/repository');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phutil', 'future/exec'); phutil_require_module('phutil', 'future/exec');
phutil_require_module('phutil', 'utils');
phutil_require_source('DiffusionGitHistoryQuery.php'); phutil_require_source('DiffusionGitHistoryQuery.php');

View file

@ -0,0 +1,45 @@
<?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.
*/
final class DiffusionMercurialHistoryQuery extends DiffusionHistoryQuery {
protected function executeQuery() {
$drequest = $this->getRequest();
$repository = $drequest->getRepository();
$path = $drequest->getPath();
$commit_hash = $drequest->getCommit();
$path = DiffusionPathIDQuery::normalizePath($path);
list($stdout) = $repository->execxLocalCommand(
'log --template %s --limit %d --branch %s --rev %s:0 -- %s',
'{node}\\n',
($this->getOffset() + $this->getLimit()), // No '--skip' in Mercurial.
$drequest->getBranch(),
$commit_hash,
nonempty(ltrim($path, '/'), '.'));
$hashes = explode("\n", $stdout);
$hashes = array_filter($hashes);
$hashes = array_slice($hashes, $this->getOffset());
return $this->loadHistoryForCommitIdentifiers($hashes);
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/diffusion/query/history/base');
phutil_require_module('phabricator', 'applications/diffusion/query/pathid/base');
phutil_require_module('phutil', 'utils');
phutil_require_source('DiffusionMercurialHistoryQuery.php');

View file

@ -33,6 +33,9 @@ abstract class DiffusionLastModifiedQuery {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$class = 'DiffusionGitLastModifiedQuery'; $class = 'DiffusionGitLastModifiedQuery';
break; break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$class = 'DiffusionMercurialLastModifiedQuery';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$class = 'DiffusionSvnLastModifiedQuery'; $class = 'DiffusionSvnLastModifiedQuery';
break; break;

View file

@ -0,0 +1,50 @@
<?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.
*/
final class DiffusionMercurialLastModifiedQuery
extends DiffusionLastModifiedQuery {
protected function executeQuery() {
$drequest = $this->getRequest();
$repository = $drequest->getRepository();
$path = $drequest->getPath();
// TODO: Share some of this with History query.
list($hash) = $repository->execxLocalCommand(
'log --template %s --limit 1 --branch %s --rev %s:0 -- %s',
'{node}\\n',
$drequest->getBranch(),
$drequest->getCommit(),
nonempty(ltrim($path, '/'), '.'));
$hash = trim($hash);
$commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
'repositoryID = %d AND commitIdentifier = %s',
$repository->getID(),
$hash);
if ($commit) {
$commit_data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());
}
return array($commit, $commit_data);
}
}

View file

@ -0,0 +1,16 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/diffusion/query/lastmodified/base');
phutil_require_module('phabricator', 'applications/repository/storage/commit');
phutil_require_module('phabricator', 'applications/repository/storage/commitdata');
phutil_require_module('phutil', 'utils');
phutil_require_source('DiffusionMercurialLastModifiedQuery.php');

View file

@ -55,6 +55,9 @@ class DiffusionRequest {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$class = 'DiffusionSvnRequest'; $class = 'DiffusionSvnRequest';
break; break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$class = 'DiffusionMercurialRequest';
break;
default: default:
$class = 'DiffusionRequest'; $class = 'DiffusionRequest';
break; break;

View file

@ -0,0 +1,81 @@
<?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.
*/
// TODO: This has some minor code duplication vs the Git request that could be
// shared.
class DiffusionMercurialRequest extends DiffusionRequest {
protected function initializeFromAphrontRequestDictionary(array $data) {
parent::initializeFromAphrontRequestDictionary($data);
$path = $this->path;
$parts = explode('/', $path);
$branch = array_shift($parts);
if ($branch != ':') {
$this->branch = $this->decodeBranchName($branch);
}
foreach ($parts as $key => $part) {
if ($part == '..') {
unset($parts[$key]);
}
}
$this->path = implode('/', $parts);
}
public function getBranch() {
if ($this->branch) {
return $this->branch;
}
if ($this->repository) {
return $this->repository->getDetail('default-branch', 'default');
}
throw new Exception("Unable to determine branch!");
}
public function getUriPath() {
return '/diffusion/'.$this->getCallsign().'/browse/'.
$this->getBranchURIComponent($this->branch).$this->path;
}
public function getCommit() {
if ($this->commit) {
return $this->commit;
}
return $this->getBranch();
}
public function getStableCommitName() {
return substr($this->stableCommitName, 0, 16);
}
public function getBranchURIComponent($branch) {
return $this->encodeBranchName($branch).'/';
}
private function decodeBranchName($branch) {
return str_replace(':', '/', $branch);
}
private function encodeBranchName($branch) {
return str_replace('/', ':', $branch);
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/diffusion/request/base');
phutil_require_source('DiffusionMercurialRequest.php');

View file

@ -0,0 +1,354 @@
<?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 PhabricatorRepositoryMercurialCommitChangeParserWorker
extends PhabricatorRepositoryCommitChangeParserWorker {
protected function parseCommit(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
$full_name = 'r'.$repository->getCallsign().$commit->getCommitIdentifier();
echo "Parsing {$full_name}...\n";
if ($this->isBadCommit($full_name)) {
echo "This commit is marked bad!\n";
return;
}
list($stdout) = $repository->execxLocalCommand(
'status -C --change %s',
$commit->getCommitIdentifier());
$status = ArcanistMercurialParser::parseMercurialStatusDetails($stdout);
$common_attributes = array(
'repositoryID' => $repository->getID(),
'commitID' => $commit->getID(),
'commitSequence' => $commit->getEpoch(),
);
$changes = array();
// Like Git, Mercurial doesn't track directories directly. We need to infer
// directory creation and removal by observing file creation and removal
// and testing if the directories in question are previously empty (thus,
// created) or subsequently empty (thus, removed).
$maybe_new_directories = array();
$maybe_del_directories = array();
$all_directories = array();
// Parse the basic information from "hg status", which shows files that
// were directly affected by the change.
foreach ($status as $path => $path_info) {
$path = '/'.$path;
$flags = $path_info['flags'];
$change_target = $path_info['from'] ? '/'.$path_info['from'] : null;
$changes[$path] = array(
'path' => $path,
'isDirect' => true,
'targetPath' => $change_target,
'targetCommitID' => $change_target ? $commit->getID() : null,
// We're going to fill these in shortly.
'changeType' => null,
'fileType' => null,
'flags' => $flags,
) + $common_attributes;
if ($flags & ArcanistRepositoryAPI::FLAG_ADDED) {
$maybe_new_directories[] = dirname($path);
} else if ($flags & ArcanistRepositoryAPI::FLAG_DELETED) {
$maybe_del_directories[] = dirname($path);
}
$all_directories[] = dirname($path);
}
// Add change information for each source path which doesn't appear in the
// status. These files were copied, but were not modified. We also know they
// must exist.
foreach ($changes as $path => $change) {
$from = $change['targetPath'];
if ($from && empty($changes[$from])) {
$changes[$from] = array(
'path' => $from,
'isDirect' => false,
'targetPath' => null,
'targetCommitID' => null,
'changeType' => DifferentialChangeType::TYPE_COPY_AWAY,
'fileType' => null,
'flags' => 0,
) + $common_attributes;
}
}
$away = array();
foreach ($changes as $path => $change) {
if ($path['targetPath']) {
$away[$path['targetPath']][] = $path;
}
}
// Now that we have all the direct changes, figure out change types.
foreach ($changes as $path => $change) {
$flags = $change['flags'];
$from = $change['targetPath'];
if ($from) {
$target = $changes[$from];
} else {
$target = null;
}
if ($flags & ArcanistRepositoryAPI::FLAG_ADDED) {
if ($target) {
if ($target['flags'] & ArcanistRepositoryAPI::FLAG_DELETED) {
$change_type = DifferentialChangeType::TYPE_MOVE_HERE;
} else {
$change_type = DifferentialChangeType::TYPE_COPY_HERE;
}
} else {
$change_type = DifferentialChangeType::TYPE_ADD;
}
} else if ($flags & ArcanistRepositoryAPI::FLAG_DELETED) {
if (isset($away[$path])) {
if (count($away[$path]) > 1) {
$change_type = DifferentialChangeType::TYPE_MULTICOPY;
} else {
$change_type = DifferentialChangeType::TYPE_MOVE_AWAY;
}
} else {
$change_type = DifferentialChangeType::TYPE_DELETE;
}
} else {
if (isset($away[$path])) {
$change_type = DifferentialChangeType::TYPE_COPY_AWAY;
} else {
$change_type = DifferentialChangeType::TYPE_CHANGE;
}
}
$changes[$path]['changeType'] = $change_type;
}
// Go through all the affected directories and identify any which were
// actually added or deleted.
$dir_status = array();
foreach ($maybe_del_directories as $dir) {
$exists = false;
foreach (DiffusionPathIDQuery::expandPathToRoot($dir) as $path) {
if (isset($dir_status[$path])) {
break;
}
// If we know some child exists, we know this path exists. If we don't
// know that a child exists, test if this directory still exists.
if (!$exists) {
$exists = $this->mercurialPathExists(
$repository,
$path,
$commit->getCommitIdentifier());
}
if ($exists) {
$dir_status[$path] = DifferentialChangeType::TYPE_CHILD;
} else {
$dir_status[$path] = DifferentialChangeType::TYPE_DELETE;
}
}
}
list($stdout) = $repository->execxLocalCommand(
'parents --rev %s --style default',
$commit->getCommitIdentifier());
$parents = ArcanistMercurialParser::parseMercurialLog($stdout);
$parent = reset($parents);
if ($parent) {
// TODO: We should expand this to a full 40-character hash using "hg id".
$parent = $parent['rev'];
}
foreach ($maybe_new_directories as $dir) {
$exists = false;
foreach (DiffusionPathIDQuery::expandPathToRoot($dir) as $path) {
if (isset($dir_status[$path])) {
break;
}
if (!$exists) {
if ($parent) {
$exists = $this->mercurialPathExists($repository, $path, $parent);
} else {
$exists = false;
}
}
if ($exists) {
$dir_status[$path] = DifferentialChangeType::TYPE_CHILD;
} else {
$dir_status[$path] = DifferentialChangeType::TYPE_ADD;
}
}
}
foreach ($all_directories as $dir) {
foreach (DiffusionPathIDQuery::expandPathToRoot($dir) as $path) {
if (isset($dir_status[$path])) {
break;
}
$dir_status[$path] = DifferentialChangeType::TYPE_CHILD;
}
}
// Merge all the directory statuses into the path statuses.
foreach ($dir_status as $path => $status) {
if (isset($changes[$path])) {
// TODO: The UI probably doesn't handle any of these cases with
// terrible elegance, but they are exceedingly rare.
$existing_type = $changes[$path]['changeType'];
if ($existing_type == DifferentialChangeType::TYPE_DELETE) {
// This change removes a file, replaces it with a directory, and then
// adds children of that directory. Mark it as a "change" instead,
// and make the type a directory.
$changes[$path]['fileType'] = DifferentialChangeType::FILE_DIRECTORY;
$changes[$path]['changeType'] = DifferentialChangeType::TYPE_CHANGE;
} else if ($existing_type == DifferentialChangeType::TYPE_MOVE_AWAY ||
$existing_type == DifferentialChangeType::TYPE_MULTICOPY) {
// This change moves or copies a file, replaces it with a directory,
// and then adds children to that directory. Mark it as "copy away"
// instead of whatever it was, and make the type a directory.
$changes[$path]['fileType'] = DifferentialChangeType::FILE_DIRECTORY;
$changes[$path]['changeType']
= DifferentialChangeType::TYPE_COPY_AWAY;
} else if ($existing_type == DifferentialChangeType::TYPE_ADD) {
// This change removes a diretory and replaces it with a file. Mark
// it as "change" instead of "add".
$changes[$path]['changeType'] = DifferentialChangeType::TYPE_CHANGE;
}
continue;
}
$changes[$path] = array(
'path' => $path,
'isDirect' => ($status == DifferentialChangeType::TYPE_CHILD)
? false
: true,
'fileType' => DifferentialChangeType::FILE_DIRECTORY,
'changeType' => $status,
'targetPath' => null,
'targetCommitID' => null,
) + $common_attributes;
}
// TODO: use "hg diff --git" to figure out which files are symlinks.
foreach ($changes as $path => $change) {
if (empty($change['fileType'])) {
$changes[$path]['fileType'] = DifferentialChangeType::FILE_NORMAL;
}
}
$all_paths = array();
foreach ($changes as $path => $change) {
$all_paths[$path] = true;
if ($change['targetPath']) {
$all_paths[$change['targetPath']] = true;
}
}
$path_map = $this->lookupOrCreatePaths(array_keys($all_paths));
foreach ($changes as $key => $change) {
$changes[$key]['pathID'] = $path_map[$change['path']];
if ($change['targetPath']) {
$changes[$key]['targetPathID'] = $path_map[$change['targetPath']];
} else {
$changes[$key]['targetPathID'] = null;
}
}
$conn_w = $repository->establishConnection('w');
$changes_sql = array();
foreach ($changes as $change) {
$values = array(
(int)$change['repositoryID'],
(int)$change['pathID'],
(int)$change['commitID'],
$change['targetPathID']
? (int)$change['targetPathID']
: 'null',
$change['targetCommitID']
? (int)$change['targetCommitID']
: 'null',
(int)$change['changeType'],
(int)$change['fileType'],
(int)$change['isDirect'],
(int)$change['commitSequence'],
);
$changes_sql[] = '('.implode(', ', $values).')';
}
queryfx(
$conn_w,
'DELETE FROM %T WHERE commitID = %d',
PhabricatorRepository::TABLE_PATHCHANGE,
$commit->getID());
foreach (array_chunk($changes_sql, 256) as $sql_chunk) {
queryfx(
$conn_w,
'INSERT INTO %T
(repositoryID, pathID, commitID, targetPathID, targetCommitID,
changeType, fileType, isDirect, commitSequence)
VALUES %Q',
PhabricatorRepository::TABLE_PATHCHANGE,
implode(', ', $sql_chunk));
}
$this->finishParse();
}
private function mercurialPathExists(
PhabricatorRepository $repository,
$path,
$rev) {
if ($path == '/') {
return true;
}
// NOTE: For directories, this grabs the entire directory contents, but
// we don't have any more surgical approach available to us in Mercurial.
// We can't use "log" because it doesn't have enough information for us
// to figure out when a directory is deleted by a change.
list($err) = $repository->execLocalCommand(
'cat --rev %s -- %s > /dev/null',
$rev,
$path);
if ($err) {
return false;
} else {
return true;
}
}
}

View file

@ -0,0 +1,19 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('arcanist', 'repository/api/base');
phutil_require_module('arcanist', 'repository/parser/mercurial');
phutil_require_module('phabricator', 'applications/differential/constants/changetype');
phutil_require_module('phabricator', 'applications/diffusion/query/pathid/base');
phutil_require_module('phabricator', 'applications/repository/storage/repository');
phutil_require_module('phabricator', 'applications/repository/worker/commitchangeparser/base');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_source('PhabricatorRepositoryMercurialCommitChangeParserWorker.php');

View file

@ -758,6 +758,7 @@ class PhabricatorRepositorySvnCommitChangeParserWorker
return $map; return $map;
} }
// TODO: Replace with DiffusionPathIDQuery::getParentPath().
private function getParentPath($path) { private function getParentPath($path) {
$path = rtrim($path, '/'); $path = rtrim($path, '/');
$path = dirname($path); $path = dirname($path);
@ -767,6 +768,7 @@ class PhabricatorRepositorySvnCommitChangeParserWorker
return $path; return $path;
} }
// TODO: Replace with DiffusionPathIDQuery::expandPathToRoot().
private function expandAllParentPaths($path, $include_self = false) { private function expandAllParentPaths($path, $include_self = false) {
$parents = array(); $parents = array();
if ($include_self) { if ($include_self) {