Improve Diffusion blame views
Summary: - Make some effort to simplify the code. - Make "Skip Past This Commit" work in Git and Mercurial. - Make blame work in Mercurial. - Add tooltip hover state to show more information about commits. Test Plan: Viewed blame views in SVN, Git, Hg. Clicked line numbers, hovered/clicked commits, hovered/clicked "blame past..." Reviewers: btrahan, vrana, jungejason Reviewed By: vrana CC: aran Maniphest Tasks: T378 Differential Revision: https://secure.phabricator.com/D2142
This commit is contained in:
@@ -23,11 +23,17 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
||||
public function processRequest() {
|
||||
|
||||
$request = $this->getRequest();
|
||||
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
|
||||
$before = $request->getStr('before');
|
||||
if ($before) {
|
||||
return $this->buildBeforeResponse($before);
|
||||
}
|
||||
|
||||
$path = $drequest->getPath();
|
||||
$selected = $request->getStr('view');
|
||||
$needs_blame = ($selected == 'blame' || $selected == 'plainblame');
|
||||
|
||||
$file_query = DiffusionFileContentQuery::newFromDiffusionRequest(
|
||||
$this->diffusionRequest);
|
||||
$file_query->setNeedsBlame($needs_blame);
|
||||
@@ -99,8 +105,6 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: blame of blame.
|
||||
switch ($selected) {
|
||||
case 'plain':
|
||||
$style =
|
||||
@@ -166,7 +170,7 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
||||
$corpus = phutil_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'style' => 'padding: 0pt 2em;',
|
||||
'style' => 'padding: 0 2em;',
|
||||
),
|
||||
$corpus_table);
|
||||
|
||||
@@ -245,20 +249,13 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
||||
DiffusionFileContentQuery $file_query,
|
||||
$selected) {
|
||||
|
||||
$last_rev = null;
|
||||
$color = '#eeeeee';
|
||||
$rows = array();
|
||||
$n = 1;
|
||||
$view = $this->getRequest()->getStr('view');
|
||||
|
||||
if ($blame_dict) {
|
||||
$epoch_list = ipull($blame_dict, 'epoch');
|
||||
$epoch_max = max($epoch_list);
|
||||
$epoch_min = min($epoch_list);
|
||||
$epoch_range = $epoch_max - $epoch_min + 1;
|
||||
$epoch_list = ipull($blame_dict, 'epoch');
|
||||
$epoch_min = min($epoch_list);
|
||||
$epoch_max = max($epoch_list);
|
||||
$epoch_range = ($epoch_max - $epoch_min) + 1;
|
||||
}
|
||||
|
||||
$targ = '';
|
||||
$min_line = 0;
|
||||
$line = $drequest->getLine();
|
||||
if (strpos($line, '-') !== false) {
|
||||
@@ -270,109 +267,219 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
||||
$max_line = $line;
|
||||
}
|
||||
|
||||
$display = array();
|
||||
|
||||
$line_number = 1;
|
||||
$last_rev = null;
|
||||
$color = null;
|
||||
foreach ($text_list as $k => $line) {
|
||||
$display_line = array(
|
||||
'color' => null,
|
||||
'epoch' => null,
|
||||
'commit' => null,
|
||||
'author' => null,
|
||||
'target' => null,
|
||||
'highlighted' => null,
|
||||
'line' => $line_number,
|
||||
'data' => $line,
|
||||
);
|
||||
|
||||
if ($needs_blame) {
|
||||
// If the line's rev is same as the line above, show empty content
|
||||
// with same color; otherwise generate blame info. The newer a change
|
||||
// is, the darker the color.
|
||||
$rev = $rev_list[$k];
|
||||
// is, the more saturated the color.
|
||||
|
||||
// TODO: SVN doesn't always give us blame for the last line, if empty?
|
||||
// Bug with our stuff or with SVN?
|
||||
$rev = idx($rev_list, $k, $last_rev);
|
||||
|
||||
if ($last_rev == $rev) {
|
||||
$blame_info =
|
||||
($file_query->getSupportsBlameOnBlame() ?
|
||||
'<th style="background: '.$color.'; width: 2em;"></th>' : '').
|
||||
'<th style="background: '.$color.'; width: 9em;"></th>'.
|
||||
'<th style="background: '.$color.'"></th>';
|
||||
$display_line['color'] = $color;
|
||||
} else {
|
||||
$blame = $blame_dict[$rev];
|
||||
|
||||
$revision_time = null;
|
||||
if ($blame_dict) {
|
||||
$color_number = (int)(0xEE -
|
||||
0xEE * ($blame_dict[$rev]['epoch'] - $epoch_min) / $epoch_range);
|
||||
$color = sprintf('#%02xee%02x', $color_number, $color_number);
|
||||
$revision_time = phabricator_datetime(
|
||||
$blame_dict[$rev]['epoch'],
|
||||
$this->getRequest()->getUser());
|
||||
}
|
||||
$color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range;
|
||||
|
||||
$revision_link = self::renderRevision(
|
||||
$drequest,
|
||||
substr($rev, 0, 7));
|
||||
$color_value = 0xF6 * (1.0 - $color_ratio);
|
||||
$color = sprintf(
|
||||
'#%02x%02x%02x',
|
||||
$color_value,
|
||||
0xF6,
|
||||
$color_value);
|
||||
|
||||
if (!$file_query->getSupportsBlameOnBlame()) {
|
||||
$prev_link = '';
|
||||
} else {
|
||||
$prev_rev = $file_query->getPrevRev($rev);
|
||||
$path = $drequest->getPath();
|
||||
$prev_link = self::renderBrowse(
|
||||
$drequest,
|
||||
$path,
|
||||
"\xC2\xAB",
|
||||
$prev_rev,
|
||||
$n,
|
||||
$selected,
|
||||
'Blame previous revision');
|
||||
$prev_link = phutil_render_tag(
|
||||
'th',
|
||||
array(
|
||||
'class' => 'diffusion-wide-link',
|
||||
'style' => 'background: '.$color.'; width: 2em;',
|
||||
),
|
||||
$prev_link);
|
||||
}
|
||||
$display_line['epoch'] = $blame['epoch'];
|
||||
$display_line['color'] = $color;
|
||||
$display_line['commit'] = $rev;
|
||||
|
||||
if (isset($blame_dict[$rev]['handle'])) {
|
||||
$author_link = $blame_dict[$rev]['handle']->renderLink();
|
||||
} else {
|
||||
$author_link = phutil_escape_html($blame_dict[$rev]['author']);
|
||||
$author_link = phutil_render_tag(
|
||||
'span',
|
||||
array(
|
||||
),
|
||||
phutil_escape_html($blame_dict[$rev]['author']));
|
||||
}
|
||||
$blame_info =
|
||||
$prev_link .
|
||||
'<th style="background: '.$color.'; width: 12em;" title="'.
|
||||
phutil_escape_html($revision_time).'">'.$revision_link.'</th>'.
|
||||
'<th style="background: '.$color.'; width: 12em'.
|
||||
'; font-weight: normal; color: #333;">'.$author_link.'</th>';
|
||||
$display_line['author'] = $author_link;
|
||||
|
||||
$last_rev = $rev;
|
||||
}
|
||||
} else {
|
||||
$blame_info = null;
|
||||
}
|
||||
|
||||
// Highlight the line of interest if needed.
|
||||
if ($min_line > 0 && ($n >= $min_line && $n <= $max_line)) {
|
||||
$tr = '<tr style="background: #ffff00;">';
|
||||
if ($targ == '') {
|
||||
$targ = '<a id="scroll_target"></a>';
|
||||
Javelin::initBehavior('diffusion-jump-to',
|
||||
array('target' => 'scroll_target'));
|
||||
if ($min_line) {
|
||||
if ($line_number == $min_line) {
|
||||
$display_line['target'] = true;
|
||||
}
|
||||
if ($line_number >= $min_line && $line_number <= $max_line) {
|
||||
$display_line['highlighted'] = true;
|
||||
}
|
||||
} else {
|
||||
$tr = '<tr>';
|
||||
$targ = null;
|
||||
}
|
||||
|
||||
$href = $drequest->generateURI(
|
||||
$display[] = $display_line;
|
||||
++$line_number;
|
||||
}
|
||||
|
||||
$commits = id(new PhabricatorAuditCommitQuery())
|
||||
->withIdentifiers(
|
||||
$drequest->getRepository()->getID(),
|
||||
array_filter(ipull($display, 'commit')))
|
||||
->needCommitData(true)
|
||||
->execute();
|
||||
$commits = mpull($commits, null, 'getCommitIdentifier');
|
||||
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$rows = array();
|
||||
foreach ($display as $line) {
|
||||
|
||||
$line_href = $drequest->generateURI(
|
||||
array(
|
||||
'action' => 'browse',
|
||||
'stable' => true,
|
||||
'action' => 'browse',
|
||||
'line' => $line['line'],
|
||||
'stable' => true,
|
||||
));
|
||||
$href = (string)$href;
|
||||
|
||||
$query_params = null;
|
||||
if ($view) {
|
||||
$query_params = '?view='.$view;
|
||||
$line_href->setQueryParams($request->getRequestURI()->getQueryParams());
|
||||
|
||||
$blame = array();
|
||||
if ($line['color']) {
|
||||
$color = $line['color'];
|
||||
|
||||
$before_link = null;
|
||||
$commit_link = null;
|
||||
if (idx($line, 'commit')) {
|
||||
$commit = $line['commit'];
|
||||
|
||||
$summary = 'Unknown';
|
||||
if (idx($commits, $commit)) {
|
||||
$summary = $commits[$commit]->getCommitData()->getSummary();
|
||||
}
|
||||
|
||||
$tooltip = phabricator_date(
|
||||
$line['epoch'],
|
||||
$user)." \xC2\xB7 ".$summary;
|
||||
|
||||
Javelin::initBehavior('phabricator-tooltips', array());
|
||||
require_celerity_resource('aphront-tooltip-css');
|
||||
|
||||
$commit_link = javelin_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $drequest->generateURI(
|
||||
array(
|
||||
'action' => 'commit',
|
||||
'commit' => $line['commit'],
|
||||
)),
|
||||
'sigil' => 'has-tooltip',
|
||||
'meta' => array(
|
||||
'tip' => $tooltip,
|
||||
'align' => 'E',
|
||||
'size' => 600,
|
||||
),
|
||||
),
|
||||
phutil_escape_html(phutil_utf8_shorten($line['commit'], 9, '')));
|
||||
|
||||
$before_link = javelin_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $line_href->alter('before', $commit),
|
||||
'sigil' => 'has-tooltip',
|
||||
'meta' => array(
|
||||
'tip' => 'Skip Past This Commit',
|
||||
'align' => 'E',
|
||||
'size' => 300,
|
||||
),
|
||||
),
|
||||
"\xC2\xAB");
|
||||
}
|
||||
|
||||
$blame[] = phutil_render_tag(
|
||||
'th',
|
||||
array(
|
||||
'class' => 'diffusion-blame-link',
|
||||
'style' => 'background: '.$color,
|
||||
),
|
||||
$before_link);
|
||||
|
||||
$blame[] = phutil_render_tag(
|
||||
'th',
|
||||
array(
|
||||
'class' => 'diffusion-rev-link',
|
||||
'style' => 'background: '.$color,
|
||||
),
|
||||
$commit_link);
|
||||
|
||||
$blame[] = phutil_render_tag(
|
||||
'th',
|
||||
array(
|
||||
'class' => 'diffusion-author-link',
|
||||
'style' => 'background: '.$color,
|
||||
),
|
||||
idx($line, 'author'));
|
||||
}
|
||||
|
||||
$link = phutil_render_tag(
|
||||
$line_link = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $href.'$'.$n.$query_params,
|
||||
'href' => $line_href,
|
||||
),
|
||||
$n);
|
||||
phutil_escape_html($line['line']));
|
||||
|
||||
$rows[] = $tr.$blame_info.
|
||||
'<th class="diffusion-wide-link">'.$link.'</th>'.
|
||||
'<td>'.$targ.$line.'</td></tr>';
|
||||
++$n;
|
||||
$blame[] = phutil_render_tag(
|
||||
'th',
|
||||
array(
|
||||
'class' => 'diffusion-line-link',
|
||||
'style' => isset($color) ? 'background: '.$color : null,
|
||||
),
|
||||
$line_link);
|
||||
|
||||
$blame = implode('', $blame);
|
||||
|
||||
if ($line['target']) {
|
||||
Javelin::initBehavior(
|
||||
'diffusion-jump-to',
|
||||
array(
|
||||
'target' => 'scroll_target',
|
||||
));
|
||||
$anchor_text = '<a id="scroll_target"></a>';
|
||||
} else {
|
||||
$anchor_text = null;
|
||||
}
|
||||
|
||||
$line_text = phutil_render_tag(
|
||||
'td',
|
||||
array(
|
||||
),
|
||||
$anchor_text.$line['data']);
|
||||
|
||||
$rows[] = phutil_render_tag(
|
||||
'tr',
|
||||
array(
|
||||
'style' => ($line['highlighted'] ? 'background: #ffff00;' : null),
|
||||
),
|
||||
$blame.
|
||||
$line_text);
|
||||
}
|
||||
|
||||
return $rows;
|
||||
@@ -493,5 +600,34 @@ final class DiffusionBrowseFileController extends DiffusionController {
|
||||
return $panel;
|
||||
}
|
||||
|
||||
private function buildBeforeResponse($before) {
|
||||
$request = $this->getRequest();
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
|
||||
$before_req = DiffusionRequest::newFromDictionary(
|
||||
array(
|
||||
'repository' => $drequest->getRepository(),
|
||||
'commit' => $before,
|
||||
));
|
||||
|
||||
$query = DiffusionCommitParentsQuery::newFromDiffusionRequest($before_req);
|
||||
$parents = $query->loadParents();
|
||||
$parent = head($parents);
|
||||
|
||||
// NOTE: If they get back to the very first commit, we just keep them there.
|
||||
// We could maybe show a message or something.
|
||||
|
||||
$before_uri = $drequest->generateURI(
|
||||
array(
|
||||
'action' => 'browse',
|
||||
'commit' => $parent ? $parent->getCommitIdentifier() : $before,
|
||||
'line' => $drequest->getLine(),
|
||||
));
|
||||
|
||||
$before_uri->setQueryParams($request->getRequestURI()->getQueryParams());
|
||||
$before_uri = $before_uri->alter('before', null);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($before_uri);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,12 +10,16 @@ phutil_require_module('arcanist', 'difference');
|
||||
|
||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||
phutil_require_module('phabricator', 'aphront/writeguard');
|
||||
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/request/base');
|
||||
phutil_require_module('phabricator', 'applications/files/storage/file');
|
||||
phutil_require_module('phabricator', 'applications/markup/syntax');
|
||||
phutil_require_module('phabricator', 'infrastructure/celerity/api');
|
||||
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/layout/panel');
|
||||
|
||||
Reference in New Issue
Block a user