mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 23:02:42 +01:00
Allow "blame previous revision" to track file moves and handle edge cases
Summary: - Track + message through file moves. - Stop + message on file create. - Stop + message on first commit. Test Plan: - Tested blaming through a move, through a create, and through the first commit. - Verified this doesn't break anything in SVN / Mercurial. Reviewers: vrana, btrahan, jungejason Reviewed By: btrahan CC: aran Maniphest Tasks: T1091 Differential Revision: https://secure.phabricator.com/D2295
This commit is contained in:
parent
20a5c9b261
commit
e9bd842227
12 changed files with 339 additions and 14 deletions
|
@ -321,6 +321,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionGitHistoryQuery' => 'applications/diffusion/query/history/git',
|
||||
'DiffusionGitLastModifiedQuery' => 'applications/diffusion/query/lastmodified/git',
|
||||
'DiffusionGitMergedCommitsQuery' => 'applications/diffusion/query/mergedcommits/git',
|
||||
'DiffusionGitRenameHistoryQuery' => 'applications/diffusion/query/renamehistory/git',
|
||||
'DiffusionGitRequest' => 'applications/diffusion/request/git',
|
||||
'DiffusionGitTagListQuery' => 'applications/diffusion/query/taglist/git',
|
||||
'DiffusionHistoryController' => 'applications/diffusion/controller/history',
|
||||
|
@ -339,6 +340,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionMercurialHistoryQuery' => 'applications/diffusion/query/history/mercurial',
|
||||
'DiffusionMercurialLastModifiedQuery' => 'applications/diffusion/query/lastmodified/mercurial',
|
||||
'DiffusionMercurialMergedCommitsQuery' => 'applications/diffusion/query/mergedcommits/mercurial',
|
||||
'DiffusionMercurialRenameHistoryQuery' => 'applications/diffusion/query/renamehistory/mercurial',
|
||||
'DiffusionMercurialRequest' => 'applications/diffusion/request/mercurial',
|
||||
'DiffusionMercurialTagListQuery' => 'applications/diffusion/query/taglist/mercurial',
|
||||
'DiffusionMergedCommitsQuery' => 'applications/diffusion/query/mergedcommits/base',
|
||||
|
@ -350,6 +352,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionPathQueryTestCase' => 'applications/diffusion/query/pathid/base/__tests__',
|
||||
'DiffusionPathValidateController' => 'applications/diffusion/controller/pathvalidate',
|
||||
'DiffusionQuery' => 'applications/diffusion/query/base',
|
||||
'DiffusionRenameHistoryQuery' => 'applications/diffusion/query/renamehistory/base',
|
||||
'DiffusionRepositoryController' => 'applications/diffusion/controller/repository',
|
||||
'DiffusionRepositoryPath' => 'applications/diffusion/data/repositorypath',
|
||||
'DiffusionRepositoryTag' => 'applications/diffusion/tag',
|
||||
|
@ -362,6 +365,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionSvnHistoryQuery' => 'applications/diffusion/query/history/svn',
|
||||
'DiffusionSvnLastModifiedQuery' => 'applications/diffusion/query/lastmodified/svn',
|
||||
'DiffusionSvnMergedCommitsQuery' => 'applications/diffusion/query/mergedcommits/svn',
|
||||
'DiffusionSvnRenameHistoryQuery' => 'applications/diffusion/query/renamehistory/svn',
|
||||
'DiffusionSvnRequest' => 'applications/diffusion/request/svn',
|
||||
'DiffusionSvnTagListQuery' => 'applications/diffusion/query/taglist/svn',
|
||||
'DiffusionSymbolController' => 'applications/diffusion/controller/symbol',
|
||||
|
@ -1282,6 +1286,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionGitHistoryQuery' => 'DiffusionHistoryQuery',
|
||||
'DiffusionGitLastModifiedQuery' => 'DiffusionLastModifiedQuery',
|
||||
'DiffusionGitMergedCommitsQuery' => 'DiffusionMergedCommitsQuery',
|
||||
'DiffusionGitRenameHistoryQuery' => 'DiffusionRenameHistoryQuery',
|
||||
'DiffusionGitRequest' => 'DiffusionRequest',
|
||||
'DiffusionGitTagListQuery' => 'DiffusionTagListQuery',
|
||||
'DiffusionHistoryController' => 'DiffusionController',
|
||||
|
@ -1300,12 +1305,14 @@ phutil_register_library_map(array(
|
|||
'DiffusionMercurialHistoryQuery' => 'DiffusionHistoryQuery',
|
||||
'DiffusionMercurialLastModifiedQuery' => 'DiffusionLastModifiedQuery',
|
||||
'DiffusionMercurialMergedCommitsQuery' => 'DiffusionMergedCommitsQuery',
|
||||
'DiffusionMercurialRenameHistoryQuery' => 'DiffusionRenameHistoryQuery',
|
||||
'DiffusionMercurialRequest' => 'DiffusionRequest',
|
||||
'DiffusionMercurialTagListQuery' => 'DiffusionTagListQuery',
|
||||
'DiffusionMergedCommitsQuery' => 'DiffusionQuery',
|
||||
'DiffusionPathCompleteController' => 'DiffusionController',
|
||||
'DiffusionPathQueryTestCase' => 'PhabricatorTestCase',
|
||||
'DiffusionPathValidateController' => 'DiffusionController',
|
||||
'DiffusionRenameHistoryQuery' => 'DiffusionQuery',
|
||||
'DiffusionRepositoryController' => 'DiffusionController',
|
||||
'DiffusionSvnBrowseQuery' => 'DiffusionBrowseQuery',
|
||||
'DiffusionSvnCommitParentsQuery' => 'DiffusionCommitParentsQuery',
|
||||
|
@ -1315,6 +1322,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionSvnHistoryQuery' => 'DiffusionHistoryQuery',
|
||||
'DiffusionSvnLastModifiedQuery' => 'DiffusionLastModifiedQuery',
|
||||
'DiffusionSvnMergedCommitsQuery' => 'DiffusionMergedCommitsQuery',
|
||||
'DiffusionSvnRenameHistoryQuery' => 'DiffusionRenameHistoryQuery',
|
||||
'DiffusionSvnRequest' => 'DiffusionRequest',
|
||||
'DiffusionSvnTagListQuery' => 'DiffusionTagListQuery',
|
||||
'DiffusionSymbolController' => 'DiffusionController',
|
||||
|
|
|
@ -69,6 +69,39 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
|||
'path' => true,
|
||||
'view' => 'browse',
|
||||
));
|
||||
|
||||
$follow = $request->getStr('follow');
|
||||
if ($follow) {
|
||||
$notice = new AphrontErrorView();
|
||||
$notice->setSeverity(AphrontErrorView::SEVERITY_WARNING);
|
||||
$notice->setTitle('Unable to Continue');
|
||||
switch ($follow) {
|
||||
case 'first':
|
||||
$notice->appendChild(
|
||||
"Unable to continue tracing the history of this file because ".
|
||||
"this commit is the first commit in the repository.");
|
||||
break;
|
||||
case 'created':
|
||||
$notice->appendChild(
|
||||
"Unable to continue tracing the history of this file because ".
|
||||
"this commit created the file.");
|
||||
break;
|
||||
}
|
||||
$content[] = $notice;
|
||||
}
|
||||
|
||||
$renamed = $request->getStr('renamed');
|
||||
if ($renamed) {
|
||||
$notice = new AphrontErrorView();
|
||||
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
|
||||
$notice->setTitle('File Renamed');
|
||||
$notice->appendChild(
|
||||
"File history passes through a rename from '".
|
||||
phutil_escape_html($drequest->getPath())."' to '".
|
||||
phutil_escape_html($renamed)."'.");
|
||||
$content[] = $notice;
|
||||
}
|
||||
|
||||
$content[] = $view_select_panel;
|
||||
$content[] = $corpus;
|
||||
$content[] = $this->buildOpenRevisions();
|
||||
|
@ -612,30 +645,82 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
|||
$request = $this->getRequest();
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
|
||||
$before_req = DiffusionRequest::newFromDictionary(
|
||||
array(
|
||||
'repository' => $drequest->getRepository(),
|
||||
'commit' => $before,
|
||||
));
|
||||
// NOTE: We need to get the grandparent so we can capture filename changes
|
||||
// in the parent.
|
||||
|
||||
$query = DiffusionCommitParentsQuery::newFromDiffusionRequest($before_req);
|
||||
$parents = $query->loadParents();
|
||||
$parent = head($parents);
|
||||
$parent = $this->loadParentRevisionOf($before);
|
||||
$old_filename = null;
|
||||
$was_created = false;
|
||||
if ($parent) {
|
||||
$grandparent = $this->loadParentRevisionOf(
|
||||
$parent->getCommitIdentifier());
|
||||
|
||||
// NOTE: If they get back to the very first commit, we just keep them there.
|
||||
// We could maybe show a message or something.
|
||||
if ($grandparent) {
|
||||
$rename_query = DiffusionRenameHistoryQuery::newFromDiffusionRequest(
|
||||
$drequest);
|
||||
$rename_query->setOldCommit($grandparent->getCommitIdentifier());
|
||||
$old_filename = $rename_query->loadOldFilename();
|
||||
$was_created = $rename_query->getWasCreated();
|
||||
}
|
||||
}
|
||||
|
||||
$follow = null;
|
||||
if ($was_created) {
|
||||
// If the file was created in history, that means older commits won't
|
||||
// have it. Since we know it existed at 'before', it must have been
|
||||
// created then; jump there.
|
||||
$target_commit = $before;
|
||||
$follow = 'created';
|
||||
} else if ($parent) {
|
||||
// If we found a parent, jump to it. This is the normal case.
|
||||
$target_commit = $parent->getCommitIdentifier();
|
||||
} else {
|
||||
// If there's no parent, this was probably created in the initial commit?
|
||||
// And the "was_created" check will fail because we can't identify the
|
||||
// grandparent. Keep the user at 'before'.
|
||||
$target_commit = $before;
|
||||
$follow = 'first';
|
||||
}
|
||||
|
||||
$path = $drequest->getPath();
|
||||
$renamed = null;
|
||||
if ($old_filename !== null &&
|
||||
$old_filename !== $path) {
|
||||
$renamed = $path;
|
||||
$path = $old_filename;
|
||||
}
|
||||
|
||||
$before_uri = $drequest->generateURI(
|
||||
array(
|
||||
'action' => 'browse',
|
||||
'commit' => $parent ? $parent->getCommitIdentifier() : $before,
|
||||
'line' => $drequest->getLine(),
|
||||
'commit' => $target_commit,
|
||||
// If there's a follow error, drop the line so the user sees the
|
||||
// message.
|
||||
'line' => $follow ? null : $drequest->getLine(),
|
||||
'path' => $path,
|
||||
));
|
||||
|
||||
$before_uri->setQueryParams($request->getRequestURI()->getQueryParams());
|
||||
$before_uri = $before_uri->alter('before', null);
|
||||
$before_uri = $before_uri->alter('renamed', $renamed);
|
||||
$before_uri = $before_uri->alter('follow', $follow);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($before_uri);
|
||||
}
|
||||
|
||||
private function loadParentRevisionOf($commit) {
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
|
||||
$before_req = DiffusionRequest::newFromDictionary(
|
||||
array(
|
||||
'repository' => $drequest->getRepository(),
|
||||
'commit' => $commit,
|
||||
));
|
||||
|
||||
$query = DiffusionCommitParentsQuery::newFromDiffusionRequest($before_req);
|
||||
$parents = $query->loadParents();
|
||||
|
||||
return head($parents);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ phutil_require_module('phabricator', 'applications/audit/query/commit');
|
|||
phutil_require_module('phabricator', 'applications/diffusion/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/diffusion/query/filecontent/base');
|
||||
phutil_require_module('phabricator', 'applications/diffusion/query/parents/base');
|
||||
phutil_require_module('phabricator', 'applications/diffusion/query/renamehistory/base');
|
||||
phutil_require_module('phabricator', 'applications/diffusion/request/base');
|
||||
phutil_require_module('phabricator', 'applications/files/storage/file');
|
||||
phutil_require_module('phabricator', 'applications/markup/syntax');
|
||||
|
@ -22,6 +23,7 @@ phutil_require_module('phabricator', 'infrastructure/javelin/api');
|
|||
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
|
||||
phutil_require_module('phabricator', 'infrastructure/util/hash');
|
||||
phutil_require_module('phabricator', 'view/form/control/select');
|
||||
phutil_require_module('phabricator', 'view/form/error');
|
||||
phutil_require_module('phabricator', 'view/layout/panel');
|
||||
phutil_require_module('phabricator', 'view/utils');
|
||||
|
||||
|
|
|
@ -38,10 +38,14 @@ final class DiffusionGitHistoryQuery extends DiffusionHistoryQuery {
|
|||
// Git omits merge commits if the path is provided, even if it is empty.
|
||||
(strlen($path) ? csprintf('%s', $path) : ''));
|
||||
|
||||
$lines = explode("\n", trim($stdout));
|
||||
$lines = array_filter($lines);
|
||||
if (!$lines) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$hash_list = array();
|
||||
$parent_map = array();
|
||||
|
||||
$lines = explode("\n", trim($stdout));
|
||||
foreach ($lines as $line) {
|
||||
list($hash, $parents) = explode(":", $line);
|
||||
$hash_list[] = $hash;
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 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.
|
||||
*/
|
||||
|
||||
abstract class DiffusionRenameHistoryQuery extends DiffusionQuery {
|
||||
|
||||
private $oldCommit;
|
||||
private $wasCreated;
|
||||
|
||||
protected function setWasCreated($was_created) {
|
||||
$this->wasCreated = $was_created;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWasCreated() {
|
||||
return $this->wasCreated;
|
||||
}
|
||||
|
||||
public function setOldCommit($old_commit) {
|
||||
$this->oldCommit = $old_commit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOldCommit() {
|
||||
return $this->oldCommit;
|
||||
}
|
||||
|
||||
final public static function newFromDiffusionRequest(
|
||||
DiffusionRequest $request) {
|
||||
return self::newQueryObject(__CLASS__, $request);
|
||||
}
|
||||
|
||||
final public function loadOldFilename() {
|
||||
return $this->executeQuery();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/diffusion/query/base');
|
||||
|
||||
|
||||
phutil_require_source('DiffusionRenameHistoryQuery.php');
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 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 DiffusionGitRenameHistoryQuery
|
||||
extends DiffusionRenameHistoryQuery {
|
||||
|
||||
protected function executeQuery() {
|
||||
$drequest = $this->getRequest();
|
||||
|
||||
$repository = $drequest->getRepository();
|
||||
list($err, $stdout) = $repository->execLocalCommand(
|
||||
'log --format=%s --follow --find-copies-harder -M -C --summary '.
|
||||
'%s..%s -- %s',
|
||||
'%x20',
|
||||
$this->getOldCommit(),
|
||||
$drequest->getCommit(),
|
||||
$drequest->getPath());
|
||||
|
||||
if ($err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$lines = explode("\n", $stdout);
|
||||
$lines = array_map('trim', $lines);
|
||||
$lines = array_filter($lines);
|
||||
|
||||
$name = null;
|
||||
foreach ($lines as $line) {
|
||||
list($action, $info) = explode(' ', $line, 2);
|
||||
switch ($action) {
|
||||
case 'rename':
|
||||
// rename path/to/file/{old.ext => new.ext} (86%)
|
||||
$matches = null;
|
||||
$ok = preg_match(
|
||||
'/^(.*){(.*) => (.*)} \([0-9%]+\)$/',
|
||||
$info,
|
||||
$matches);
|
||||
if (!$ok) {
|
||||
throw new Exception(
|
||||
"Unparseable git log --summary line: {$line}.");
|
||||
}
|
||||
|
||||
$name = $matches[1].$matches[2];
|
||||
break;
|
||||
case 'create':
|
||||
// create mode 100644 <filename>
|
||||
$this->setWasCreated(true);
|
||||
break;
|
||||
default:
|
||||
// Anything else we care about?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/diffusion/query/renamehistory/base');
|
||||
|
||||
|
||||
phutil_require_source('DiffusionGitRenameHistoryQuery.php');
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 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 DiffusionMercurialRenameHistoryQuery
|
||||
extends DiffusionRenameHistoryQuery {
|
||||
|
||||
protected function executeQuery() {
|
||||
// TODO: Implement.
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/diffusion/query/renamehistory/base');
|
||||
|
||||
|
||||
phutil_require_source('DiffusionMercurialRenameHistoryQuery.php');
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 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 DiffusionSvnRenameHistoryQuery
|
||||
extends DiffusionRenameHistoryQuery {
|
||||
|
||||
protected function executeQuery() {
|
||||
// TODO: Implement.
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/diffusion/query/renamehistory/base');
|
||||
|
||||
|
||||
phutil_require_source('DiffusionSvnRenameHistoryQuery.php');
|
Loading…
Reference in a new issue