mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 08:42:41 +01:00
Render pretty graphical traces for commit branches, etc
Summary: I AM A WIZARD Test Plan: BEHOLD Reviewers: btrahan Reviewed By: btrahan CC: aran, epriestley Maniphest Tasks: T961 Differential Revision: https://secure.phabricator.com/D2007
This commit is contained in:
parent
bee69f9ce2
commit
c2f50e258a
11 changed files with 456 additions and 37 deletions
|
@ -135,7 +135,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'aphront-table-view-css' =>
|
||||
array(
|
||||
'uri' => '/res/3ff30c4f/rsrc/css/aphront/table-view.css',
|
||||
'uri' => '/res/cbc7ab3a/rsrc/css/aphront/table-view.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -642,6 +642,18 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'disk' => '/rsrc/js/application/differential/behavior-show-more.js',
|
||||
),
|
||||
'javelin-behavior-diffusion-commit-graph' =>
|
||||
array(
|
||||
'uri' => '/res/cfe336e8/rsrc/js/application/diffusion/behavior-commit-graph.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
1 => 'javelin-dom',
|
||||
2 => 'javelin-stratcom',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/diffusion/behavior-commit-graph.js',
|
||||
),
|
||||
'javelin-behavior-diffusion-jump-to' =>
|
||||
array(
|
||||
'uri' => '/res/7c42e1ba/rsrc/js/application/diffusion/behavior-jump-to.js',
|
||||
|
@ -1692,7 +1704,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'phabricator-remarkup-css' =>
|
||||
array(
|
||||
'uri' => '/res/11f89984/rsrc/css/core/remarkup.css',
|
||||
'uri' => '/res/ff60c68a/rsrc/css/core/remarkup.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1968,7 +1980,7 @@ celerity_register_resource_map(array(
|
|||
), array(
|
||||
'packages' =>
|
||||
array(
|
||||
'66f447f1' =>
|
||||
'c02b2ef0' =>
|
||||
array(
|
||||
'name' => 'core.pkg.css',
|
||||
'symbols' =>
|
||||
|
@ -1993,7 +2005,7 @@ celerity_register_resource_map(array(
|
|||
17 => 'aphront-pager-view-css',
|
||||
18 => 'phabricator-transaction-view-css',
|
||||
),
|
||||
'uri' => '/res/pkg/66f447f1/core.pkg.css',
|
||||
'uri' => '/res/pkg/c02b2ef0/core.pkg.css',
|
||||
'type' => 'css',
|
||||
),
|
||||
'21d01ed8' =>
|
||||
|
@ -2140,17 +2152,17 @@ celerity_register_resource_map(array(
|
|||
'reverse' =>
|
||||
array(
|
||||
'aphront-attached-file-view-css' => '31583232',
|
||||
'aphront-crumbs-view-css' => '66f447f1',
|
||||
'aphront-dialog-view-css' => '66f447f1',
|
||||
'aphront-form-view-css' => '66f447f1',
|
||||
'aphront-crumbs-view-css' => 'c02b2ef0',
|
||||
'aphront-dialog-view-css' => 'c02b2ef0',
|
||||
'aphront-form-view-css' => 'c02b2ef0',
|
||||
'aphront-headsup-action-list-view-css' => '551249fc',
|
||||
'aphront-list-filter-view-css' => '66f447f1',
|
||||
'aphront-pager-view-css' => '66f447f1',
|
||||
'aphront-panel-view-css' => '66f447f1',
|
||||
'aphront-side-nav-view-css' => '66f447f1',
|
||||
'aphront-table-view-css' => '66f447f1',
|
||||
'aphront-tokenizer-control-css' => '66f447f1',
|
||||
'aphront-typeahead-control-css' => '66f447f1',
|
||||
'aphront-list-filter-view-css' => 'c02b2ef0',
|
||||
'aphront-pager-view-css' => 'c02b2ef0',
|
||||
'aphront-panel-view-css' => 'c02b2ef0',
|
||||
'aphront-side-nav-view-css' => 'c02b2ef0',
|
||||
'aphront-table-view-css' => 'c02b2ef0',
|
||||
'aphront-tokenizer-control-css' => 'c02b2ef0',
|
||||
'aphront-typeahead-control-css' => 'c02b2ef0',
|
||||
'differential-changeset-view-css' => '551249fc',
|
||||
'differential-core-view-css' => '551249fc',
|
||||
'differential-inline-comment-editor' => '9b256876',
|
||||
|
@ -2208,23 +2220,23 @@ celerity_register_resource_map(array(
|
|||
'maniphest-task-detail-css' => '31583232',
|
||||
'maniphest-task-summary-css' => '31583232',
|
||||
'maniphest-transaction-detail-css' => '31583232',
|
||||
'phabricator-app-buttons-css' => '66f447f1',
|
||||
'phabricator-app-buttons-css' => 'c02b2ef0',
|
||||
'phabricator-content-source-view-css' => '551249fc',
|
||||
'phabricator-core-buttons-css' => '66f447f1',
|
||||
'phabricator-core-css' => '66f447f1',
|
||||
'phabricator-directory-css' => '66f447f1',
|
||||
'phabricator-core-buttons-css' => 'c02b2ef0',
|
||||
'phabricator-core-css' => 'c02b2ef0',
|
||||
'phabricator-directory-css' => 'c02b2ef0',
|
||||
'phabricator-drag-and-drop-file-upload' => '9b256876',
|
||||
'phabricator-dropdown-menu' => '21d01ed8',
|
||||
'phabricator-jump-nav' => '66f447f1',
|
||||
'phabricator-jump-nav' => 'c02b2ef0',
|
||||
'phabricator-keyboard-shortcut' => '21d01ed8',
|
||||
'phabricator-keyboard-shortcut-manager' => '21d01ed8',
|
||||
'phabricator-menu-item' => '21d01ed8',
|
||||
'phabricator-object-selector-css' => '551249fc',
|
||||
'phabricator-paste-file-upload' => '21d01ed8',
|
||||
'phabricator-remarkup-css' => '66f447f1',
|
||||
'phabricator-remarkup-css' => 'c02b2ef0',
|
||||
'phabricator-shaped-request' => '9b256876',
|
||||
'phabricator-standard-page-view' => '66f447f1',
|
||||
'phabricator-transaction-view-css' => '66f447f1',
|
||||
'syntax-highlighting-css' => '66f447f1',
|
||||
'phabricator-standard-page-view' => 'c02b2ef0',
|
||||
'phabricator-transaction-view-css' => 'c02b2ef0',
|
||||
'syntax-highlighting-css' => 'c02b2ef0',
|
||||
),
|
||||
));
|
||||
|
|
|
@ -35,6 +35,11 @@ final class DiffusionHistoryController extends DiffusionController {
|
|||
$history_query->needChildChanges(true);
|
||||
}
|
||||
|
||||
$show_graph = !strlen($drequest->getPath());
|
||||
if ($show_graph) {
|
||||
$history_query->needParents(true);
|
||||
}
|
||||
|
||||
$history = $history_query->loadHistory();
|
||||
|
||||
$pager = new AphrontPagerView();
|
||||
|
@ -81,6 +86,11 @@ final class DiffusionHistoryController extends DiffusionController {
|
|||
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
|
||||
$history_table->setHandles($handles);
|
||||
|
||||
if ($show_graph) {
|
||||
$history_table->setParents($history_query->getParents());
|
||||
$history_table->setIsHead($offset == 0);
|
||||
}
|
||||
|
||||
$history_panel = new AphrontPanelView();
|
||||
$history_panel->setHeader('History');
|
||||
$history_panel->addButton($button);
|
||||
|
|
|
@ -31,6 +31,7 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
$history_query = DiffusionHistoryQuery::newFromDiffusionRequest(
|
||||
$drequest);
|
||||
$history_query->setLimit(15);
|
||||
$history_query->needParents(true);
|
||||
$history = $history_query->loadHistory();
|
||||
|
||||
$browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest);
|
||||
|
@ -63,6 +64,8 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
$history_table->setDiffusionRequest($drequest);
|
||||
$history_table->setHandles($handles);
|
||||
$history_table->setHistory($history);
|
||||
$history_table->setParents($history_query->getParents());
|
||||
$history_table->setIsHead(true);
|
||||
|
||||
$callsign = $drequest->getRepository()->getCallsign();
|
||||
$all = phutil_render_tag(
|
||||
|
|
|
@ -23,6 +23,9 @@ abstract class DiffusionHistoryQuery extends DiffusionQuery {
|
|||
|
||||
protected $needDirectChanges;
|
||||
protected $needChildChanges;
|
||||
protected $needParents;
|
||||
|
||||
protected $parents = array();
|
||||
|
||||
final public static function newFromDiffusionRequest(
|
||||
DiffusionRequest $request) {
|
||||
|
@ -40,6 +43,18 @@ abstract class DiffusionHistoryQuery extends DiffusionQuery {
|
|||
return $this;
|
||||
}
|
||||
|
||||
final public function needParents($parents) {
|
||||
$this->needParents = $parents;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getParents() {
|
||||
if (!$this->needParents) {
|
||||
throw new Exception('Specify needParents() before calling getParents()!');
|
||||
}
|
||||
return $this->parents;
|
||||
}
|
||||
|
||||
final public function loadHistory() {
|
||||
return $this->executeQuery();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
* 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.
|
||||
|
@ -29,18 +29,28 @@ final class DiffusionGitHistoryQuery extends DiffusionHistoryQuery {
|
|||
'log '.
|
||||
'--skip=%d '.
|
||||
'-n %d '.
|
||||
'--abbrev=40 '.
|
||||
'--pretty=format:%%H '.
|
||||
'%s -- %s',
|
||||
'--pretty=format:%s '.
|
||||
'%s -- %C',
|
||||
$this->getOffset(),
|
||||
$this->getLimit(),
|
||||
'%H:%P',
|
||||
$commit_hash,
|
||||
$path);
|
||||
// Git omits merge commits if the path is provided, even if it is empty.
|
||||
(strlen($path) ? csprintf('%s', $path) : ''));
|
||||
|
||||
$hashes = explode("\n", $stdout);
|
||||
$hashes = array_filter($hashes);
|
||||
$hash_list = array();
|
||||
$parent_map = array();
|
||||
|
||||
return $this->loadHistoryForCommitIdentifiers($hashes);
|
||||
$lines = explode("\n", trim($stdout));
|
||||
foreach ($lines as $line) {
|
||||
list($hash, $parents) = explode(":", $line);
|
||||
$hash_list[] = $hash;
|
||||
$parent_map[$hash] = preg_split('/\s+/', $parents);
|
||||
}
|
||||
|
||||
$this->parents = $parent_map;
|
||||
|
||||
return $this->loadHistoryForCommitIdentifiers($hash_list);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,5 +8,7 @@
|
|||
|
||||
phutil_require_module('phabricator', 'applications/diffusion/query/history/base');
|
||||
|
||||
phutil_require_module('phutil', 'xsprintf/csprintf');
|
||||
|
||||
|
||||
phutil_require_source('DiffusionGitHistoryQuery.php');
|
||||
|
|
|
@ -33,18 +33,56 @@ final class DiffusionMercurialHistoryQuery extends DiffusionHistoryQuery {
|
|||
$default_path = '';
|
||||
|
||||
list($stdout) = $repository->execxLocalCommand(
|
||||
'log --template %s --limit %d --branch %s --rev %s:0 -- %s',
|
||||
'{node}\\n',
|
||||
'log --debug --template %s --limit %d --branch %s --rev %s:0 -- %s',
|
||||
'{node};{parents}\\n',
|
||||
($this->getOffset() + $this->getLimit()), // No '--skip' in Mercurial.
|
||||
$drequest->getBranch(),
|
||||
$commit_hash,
|
||||
nonempty(ltrim($path, '/'), $default_path));
|
||||
|
||||
$hashes = explode("\n", $stdout);
|
||||
$hashes = array_filter($hashes);
|
||||
$hashes = array_slice($hashes, $this->getOffset());
|
||||
$lines = explode("\n", trim($stdout));
|
||||
$lines = array_slice($lines, $this->getOffset());
|
||||
|
||||
return $this->loadHistoryForCommitIdentifiers($hashes);
|
||||
$hash_list = array();
|
||||
$parent_map = array();
|
||||
|
||||
$last = null;
|
||||
foreach (array_reverse($lines) as $line) {
|
||||
list($hash, $parents) = explode(';', $line);
|
||||
$parents = trim($parents);
|
||||
if (!$parents) {
|
||||
if ($last === null) {
|
||||
$parent_map[$hash] = array('...');
|
||||
} else {
|
||||
$parent_map[$hash] = array($last);
|
||||
}
|
||||
} else {
|
||||
$parents = preg_split('/\s+/', $parents);
|
||||
foreach ($parents as $parent) {
|
||||
list($plocal, $phash) = explode(':', $parent);
|
||||
if (!preg_match('/^0+$/', $phash)) {
|
||||
$parent_map[$hash][] = $phash;
|
||||
}
|
||||
}
|
||||
// This may happen for the zeroth commit in repository, both hashes
|
||||
// are "000000000...".
|
||||
if (empty($parent_map[$hash])) {
|
||||
$parent_map[$hash] = array('...');
|
||||
}
|
||||
}
|
||||
|
||||
// The rendering code expects the first commit to be "mainline", like
|
||||
// Git. Flip the order so it does the right thing.
|
||||
$parent_map[$hash] = array_reverse($parent_map[$hash]);
|
||||
|
||||
$hash_list[] = $hash;
|
||||
$last = $hash;
|
||||
}
|
||||
|
||||
$hash_list = array_reverse($hash_list);
|
||||
$this->parents = $parent_map;
|
||||
|
||||
return $this->loadHistoryForCommitIdentifiers($hash_list);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ final class DiffusionHistoryTableView extends DiffusionView {
|
|||
|
||||
private $history;
|
||||
private $handles = array();
|
||||
private $isHead;
|
||||
private $parents;
|
||||
|
||||
public function setHistory(array $history) {
|
||||
$this->history = $history;
|
||||
|
@ -44,12 +46,28 @@ final class DiffusionHistoryTableView extends DiffusionView {
|
|||
return array_keys($phids);
|
||||
}
|
||||
|
||||
public function setParents(array $parents) {
|
||||
$this->parents = $parents;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setIsHead($is_head) {
|
||||
$this->isHead = $is_head;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
|
||||
$handles = $this->handles;
|
||||
|
||||
$graph = null;
|
||||
if ($this->parents) {
|
||||
$graph = $this->renderGraph();
|
||||
}
|
||||
|
||||
$rows = array();
|
||||
$ii = 0;
|
||||
foreach ($this->history as $history) {
|
||||
$epoch = $history->getEpoch();
|
||||
|
||||
|
@ -79,6 +97,7 @@ final class DiffusionHistoryTableView extends DiffusionView {
|
|||
array(
|
||||
'commit' => $history->getCommitIdentifier(),
|
||||
)),
|
||||
$graph ? $graph[$ii++] : null,
|
||||
self::linkCommit(
|
||||
$drequest->getRepository(),
|
||||
$history->getCommitIdentifier()),
|
||||
|
@ -100,6 +119,7 @@ final class DiffusionHistoryTableView extends DiffusionView {
|
|||
$view->setHeaders(
|
||||
array(
|
||||
'Browse',
|
||||
'',
|
||||
'Commit',
|
||||
'Change',
|
||||
'Date',
|
||||
|
@ -110,6 +130,7 @@ final class DiffusionHistoryTableView extends DiffusionView {
|
|||
$view->setColumnClasses(
|
||||
array(
|
||||
'',
|
||||
'threads',
|
||||
'n',
|
||||
'',
|
||||
'',
|
||||
|
@ -117,7 +138,165 @@ final class DiffusionHistoryTableView extends DiffusionView {
|
|||
'',
|
||||
'wide',
|
||||
));
|
||||
$view->setColumnVisibility(
|
||||
array(
|
||||
true,
|
||||
$graph ? true : false,
|
||||
));
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a merge/branch graph from the parent revision data. We're basically
|
||||
* building up a bunch of strings like this:
|
||||
*
|
||||
* ^
|
||||
* |^
|
||||
* o|
|
||||
* |o
|
||||
* o
|
||||
*
|
||||
* ...which form an ASCII representation of the graph we eventaully want to
|
||||
* draw.
|
||||
*
|
||||
* NOTE: The actual implementation is black magic.
|
||||
*/
|
||||
private function renderGraph() {
|
||||
|
||||
// This keeps our accumulated information about each line of the
|
||||
// merge/branch graph.
|
||||
$graph = array();
|
||||
|
||||
// This holds the next commit we're looking for in each column of the
|
||||
// graph.
|
||||
$threads = array();
|
||||
|
||||
// This is the largest number of columns any row has, i.e. the width of
|
||||
// the graph.
|
||||
$count = 0;
|
||||
|
||||
foreach ($this->history as $key => $history) {
|
||||
$joins = array();
|
||||
$splits = array();
|
||||
|
||||
$parent_list = $this->parents[$history->getCommitIdentifier()];
|
||||
|
||||
// Look for some thread which has this commit as the next commit. If
|
||||
// we find one, this commit goes on that thread. Otherwise, this commit
|
||||
// goes on a new thread.
|
||||
|
||||
$line = '';
|
||||
$found = false;
|
||||
$pos = count($threads);
|
||||
for ($n = 0; $n < $count; $n++) {
|
||||
if (empty($threads[$n])) {
|
||||
$line .= ' ';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($threads[$n] == $history->getCommitIdentifier()) {
|
||||
if ($found) {
|
||||
$line .= ' ';
|
||||
$joins[] = $n;
|
||||
unset($threads[$n]);
|
||||
} else {
|
||||
$line .= 'o';
|
||||
$found = true;
|
||||
$pos = $n;
|
||||
}
|
||||
} else {
|
||||
|
||||
// We render a "|" for any threads which have a commit that we haven't
|
||||
// seen yet, this is later drawn as a vertical line.
|
||||
$line .= '|';
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find the thread this commit goes on, start a new thread.
|
||||
// We use "o" to mark the commit for the rendering engine, or "^" to
|
||||
// indicate that there's nothing after it so the line from the commit
|
||||
// upward should not be drawn.
|
||||
|
||||
if (!$found) {
|
||||
if ($this->isHead) {
|
||||
$line .= '^';
|
||||
} else {
|
||||
$line .= 'o';
|
||||
foreach ($graph as $k => $meta) {
|
||||
// Go back across all the lines we've already drawn and add a
|
||||
// "|" to the end, since this is connected to some future commit
|
||||
// we don't know about.
|
||||
for ($jj = strlen($meta['line']); $jj <= $count; $jj++) {
|
||||
$graph[$k]['line'] .= '|';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the next commit on this thread to the commit's first parent.
|
||||
// This might have the effect of making a new thread.
|
||||
$threads[$pos] = head($parent_list);
|
||||
|
||||
// If we made a new thread, increase the thread count.
|
||||
$count = max($pos + 1, $count);
|
||||
|
||||
// Now, deal with splits (merges). I picked this terms opposite to the
|
||||
// underlying repository term to confuse you.
|
||||
foreach (array_slice($parent_list, 1) as $parent) {
|
||||
$found = false;
|
||||
|
||||
// Try to find the other parent(s) in our existing threads. If we find
|
||||
// them, split to that thread.
|
||||
|
||||
foreach ($threads as $n => $thread_commit) {
|
||||
if ($thread_commit == $parent) {
|
||||
$found = true;
|
||||
$splits[] = $n;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find the parent, we don't know about it yet. Find the
|
||||
// first free thread and add it as the "next" commit in that thread.
|
||||
// This might create a new thread.
|
||||
|
||||
if (!$found) {
|
||||
for ($n = 0; $n < $count; $n++) {
|
||||
if (empty($threads[$n])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$threads[$n] = $parent;
|
||||
$splits[] = $n;
|
||||
$count = max($n + 1, $count);
|
||||
}
|
||||
}
|
||||
|
||||
$graph[] = array(
|
||||
'line' => $line,
|
||||
'split' => $splits,
|
||||
'join' => $joins,
|
||||
);
|
||||
}
|
||||
|
||||
// Render into tags for the behavior.
|
||||
|
||||
foreach ($graph as $k => $meta) {
|
||||
$graph[$k] = javelin_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'sigil' => 'commit-graph',
|
||||
'meta' => $meta,
|
||||
),
|
||||
'');
|
||||
}
|
||||
|
||||
Javelin::initBehavior(
|
||||
'diffusion-commit-graph',
|
||||
array(
|
||||
'count' => $count,
|
||||
));
|
||||
|
||||
return $graph;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,9 +7,12 @@
|
|||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/diffusion/view/base');
|
||||
phutil_require_module('phabricator', 'infrastructure/javelin/api');
|
||||
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
|
||||
phutil_require_module('phabricator', 'view/control/table');
|
||||
|
||||
phutil_require_module('phutil', 'markup');
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('DiffusionHistoryTableView.php');
|
||||
|
|
|
@ -127,6 +127,16 @@ span.single-display-line-content {
|
|||
max-height: 64px;
|
||||
}
|
||||
|
||||
.aphront-table-view td.threads {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.aphront-table-view td.threads canvas {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aphront-table-view th.aphront-table-view-sortable {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
137
webroot/rsrc/js/application/diffusion/behavior-commit-graph.js
Normal file
137
webroot/rsrc/js/application/diffusion/behavior-commit-graph.js
Normal file
|
@ -0,0 +1,137 @@
|
|||
/**
|
||||
* @provides javelin-behavior-diffusion-commit-graph
|
||||
* @requires javelin-behavior
|
||||
* javelin-dom
|
||||
* javelin-stratcom
|
||||
*/
|
||||
|
||||
JX.behavior('diffusion-commit-graph', function(config) {
|
||||
|
||||
var nodes = JX.DOM.scry(document.body, 'div', 'commit-graph');
|
||||
var cxt;
|
||||
|
||||
// Pick the color for column 'c'.
|
||||
function color(c) {
|
||||
var colors = [
|
||||
'#cc0000',
|
||||
'#cc0099',
|
||||
'#6600cc',
|
||||
'#0033cc',
|
||||
'#00cccc',
|
||||
'#00cc33',
|
||||
'#66cc00',
|
||||
'#cc9900',
|
||||
];
|
||||
return colors[c % colors.length];
|
||||
}
|
||||
|
||||
// Stroke a line (for lines between commits).
|
||||
function lstroke(c) {
|
||||
cxt.lineWidth = 3;
|
||||
cxt.strokeStyle = '#ffffff';
|
||||
cxt.stroke();
|
||||
cxt.lineWidth = 1;
|
||||
cxt.strokeStyle = color(c);
|
||||
cxt.stroke();
|
||||
}
|
||||
|
||||
// Stroke with fill (for commit circles).
|
||||
function fstroke(c) {
|
||||
cxt.fillStyle = color(c);
|
||||
cxt.strokeStyle = '#ffffff';
|
||||
cxt.fill();
|
||||
cxt.stroke();
|
||||
}
|
||||
|
||||
|
||||
for (var ii = 0; ii < nodes.length; ii++) {
|
||||
var data = JX.Stratcom.getData(nodes[ii]);
|
||||
|
||||
var cell = 12; // Width of each thread.
|
||||
function xpos(col) {
|
||||
return (col * cell) + (cell / 2);
|
||||
}
|
||||
|
||||
var h = 24;
|
||||
var w = cell * config.count;
|
||||
|
||||
var canvas = JX.$N('canvas', {width: w, height: h});
|
||||
cxt = canvas.getContext('2d');
|
||||
|
||||
cxt.lineWidth = 3;
|
||||
// This gives us sharper lines, since lines drawn on an integer (like 5)
|
||||
// are drawn from 4.5 to 5.5.
|
||||
cxt.translate(0.5, 0.5);
|
||||
|
||||
cxt.strokeStyle = '#ffffff';
|
||||
cxt.fillStyle = '#ffffff';
|
||||
|
||||
// First, figure out which column this commit appears in. It is marked by
|
||||
// "o" (if it has a commit after it) or "^" (if no other commit has it as
|
||||
// a parent). We use this to figure out where to draw the join/split lines.
|
||||
|
||||
var origin = null;
|
||||
for (var jj = 0; jj < data.line.length; jj++) {
|
||||
var c = data.line.charAt(jj);
|
||||
switch (c) {
|
||||
case 'o':
|
||||
case '^':
|
||||
origin = xpos(jj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw all the join lines. These start at some column at the top of the
|
||||
// canvas and join the commit's column. They indicate branching.
|
||||
|
||||
for (var jj = 0; jj < data.join.length; jj++) {
|
||||
var join = data.join[jj];
|
||||
var x = xpos(join);
|
||||
cxt.beginPath();
|
||||
cxt.moveTo(x, 0);
|
||||
cxt.bezierCurveTo(x, h/4, origin, h/4, origin, h/2);
|
||||
lstroke(join);
|
||||
}
|
||||
|
||||
// Draw all the split lines. These start at the commit and end at some
|
||||
// column on the bottom of the canvas. They indicate merging.
|
||||
|
||||
for (var jj = 0; jj < data.split.length; jj++) {
|
||||
var split = data.split[jj];
|
||||
var x = xpos(split);
|
||||
cxt.beginPath();
|
||||
cxt.moveTo(origin, h/2);
|
||||
cxt.bezierCurveTo(origin, 3*h/4, x, 3*h/4, x, h);
|
||||
lstroke(split);
|
||||
}
|
||||
|
||||
// Draw the vertical lines (a branch with no activity at this commit) and
|
||||
// the commit circles.
|
||||
|
||||
for (var jj = 0; jj < data.line.length; jj++) {
|
||||
var c = data.line.charAt(jj);
|
||||
switch (c) {
|
||||
case 'o':
|
||||
case '^':
|
||||
origin = xpos(jj);
|
||||
case '|':
|
||||
cxt.beginPath();
|
||||
cxt.moveTo(xpos(jj), (c == '^' ? h/2 : 0));
|
||||
cxt.lineTo(xpos(jj), h);
|
||||
lstroke(jj);
|
||||
|
||||
if (c == 'o' || c == '^') {
|
||||
cxt.beginPath();
|
||||
cxt.arc(xpos(jj), h/2, 3, 0, 2 * Math.PI, true);
|
||||
fstroke(jj);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
JX.DOM.setContent(nodes[ii], canvas);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
Loading…
Reference in a new issue