added a primitive revision graph in a code folder

This commit is contained in:
hyung-hwan 2016-12-14 17:07:22 +00:00
parent 3fab2b34ea
commit dd619e2e19
10 changed files with 345 additions and 7 deletions

View File

@ -168,4 +168,33 @@ class Graph extends Controller
$cloc = $cloc = $this->subversion->clocRevByFile ($projectid, $path, $rev); $cloc = $cloc = $this->subversion->clocRevByFile ($projectid, $path, $rev);
print codepot_json_encode ($cloc); print codepot_json_encode ($cloc);
} }
function enjson_revision_graph ($projectid = '', $path = '', $rev = SVN_REVISION_HEAD)
{
$this->load->model ('ProjectModel', 'projects');
$login = $this->login->getUser ();
if (CODEPOT_SIGNIN_COMPULSORY && $login['id'] == '')
{
header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
return;
}
$project = $this->projects->get ($projectid);
if ($project === FALSE || ($project->public !== 'Y' && $login['id'] == ''))
{
header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
return;
}
$this->load->model ('SubversionModel', 'subversion');
$path = $this->converter->HexToAscii ($path);
if ($path == '.') $path = ''; /* treat a period specially */
$path = $this->_normalize_path ($path);
$rg = $this->subversion->revisionGraph ($projectid, $path, $rev);
print codepot_json_encode ($rg);
}
} }

View File

@ -2,6 +2,7 @@
$lang['CODE_COMMIT'] = 'Commit'; $lang['CODE_COMMIT'] = 'Commit';
$lang['CODE_PROPERTIES'] = 'Properties'; $lang['CODE_PROPERTIES'] = 'Properties';
$lang['CODE_PROPERTY'] = 'Property'; $lang['CODE_PROPERTY'] = 'Property';
$lang['CODE_REVISION_GRAPH'] = 'Revision graph';
$lang['CODE_MSG_COMMITTED_BY'] = 'Committed by %s'; $lang['CODE_MSG_COMMITTED_BY'] = 'Committed by %s';
$lang['CODE_MSG_COMMITTED_BY_ON'] = 'Committed by %s on %s'; $lang['CODE_MSG_COMMITTED_BY_ON'] = 'Committed by %s on %s';

View File

@ -2,6 +2,7 @@
$lang['CODE_COMMIT'] = '커밋'; $lang['CODE_COMMIT'] = '커밋';
$lang['CODE_PROPERTIES'] = '속성'; $lang['CODE_PROPERTIES'] = '속성';
$lang['CODE_PROPERTY'] = '속성'; $lang['CODE_PROPERTY'] = '속성';
$lang['CODE_REVISION_GRAPH'] = '리비전 그래프';
$lang['CODE_MSG_COMMITTED_BY'] = '커밋터 %s'; $lang['CODE_MSG_COMMITTED_BY'] = '커밋터 %s';
$lang['CODE_MSG_COMMITTED_BY_ON'] = '커밋터 %s 시간 %s'; $lang['CODE_MSG_COMMITTED_BY_ON'] = '커밋터 %s 시간 %s';

View File

@ -686,8 +686,7 @@ class SubversionModel extends Model
} }
else if ($info[0]['kind'] == SVN_NODE_DIR) else if ($info[0]['kind'] == SVN_NODE_DIR)
{ {
$fileinfo['fullpath'] = substr ( $fileinfo['fullpath'] = substr ($info[0]['url'], strlen($info[0]['repos']));
$info[0]['url'], strlen($info[0]['repos']));
$fileinfo['name'] = $info[0]['path']; $fileinfo['name'] = $info[0]['path'];
$fileinfo['type'] = 'dir'; $fileinfo['type'] = 'dir';
$fileinfo['size'] = 0; $fileinfo['size'] = 0;
@ -1544,7 +1543,6 @@ class SubversionModel extends Model
function clocRevByFile ($projectid, $path, $rev) function clocRevByFile ($projectid, $path, $rev)
{ {
// this function composes the data as CodeFlower requires // this function composes the data as CodeFlower requires
$stack = array(); $stack = array();
$cloc = new stdClass(); $cloc = new stdClass();
@ -1638,6 +1636,166 @@ class SubversionModel extends Model
return $cloc; return $cloc;
} }
function _add_rg_node (&$nodeids, &$nodes, $name)
{
if (array_key_exists($name, $nodeids)) return $nodeids[$name];
$nid = count($nodeids);
array_push ($nodes, array ('id' => $nid, 'label' => $name));
$nodeids[$name] = $nid;
return $nid;
}
function _add_rg_edge (&$edges, $from, $to, $label)
{
array_push ($edges, array ('from' => $from, 'to' => $to, 'label' => $label));
}
function revisionGraph ($projectid, $path, $rev)
{
// this function is almost blind translation of svn-graph.pl
/* we should get the history from the entire project */
$orgurl = 'file://'.$this->_canonical_path(CODEPOT_SVNREPO_DIR."/{$projectid}");
$startpath = $path;
$interesting = array ("{$startpath}:1" => 1);
$tracking = array ($startpath => 1);
$codeline_changes_forward = array ();
$codeline_changes_back = array ();
$copysource = array ();
$copydest = array ();
$nodeids = array ();
$nodes = array ();
$edges = array ();
$log = @svn_log ($orgurl, 1, $rev, 0, SVN_DISCOVER_CHANGED_PATHS);
if ($log === FALSE || count($log) <= 0) return FALSE;
foreach ($log as $l)
{
$deleted = array();
$currev = $l['rev'];
foreach ($l['paths'] as $p)
{
$curpath = $p['path'];
if ($p['action'] == 'D' && array_key_exists($curpath, $tracking))
{
/* when an item is moved, D and A are listed in order.
* [20] => Array
(
[rev] => 21
[author] => khinsanwai
[msg] => Mov tags/1.0.0 to tags/ATI_POC/1.0.0
[date] => 2013-09-18T06:39:41.553616Z
[paths] => Array
(
[0] => Array
(
[action] => D
[path] => /tags/1.0.0
)
[1] => Array
(
[action] => A
[path] => /tags/ATI_POC/1.0.0
[copyfrom] => /tags/1.0.0
[rev] => 20
)
)
)
*/
//print ("{$curpath}:{$tracking[$curpath]} [label=\"{$curpath}:{$tracking[$curpath]}\\nDelete in {$currev}\", color=red];\n");
$id1 = $this->_add_rg_node ($nodeids, $nodes, "{$curpath}:{$tracking[$curpath]}");
$id2 = $this->_add_rg_node ($nodeids, $nodes, "<<deleted>>");
$this->_add_rg_edge ($edges, $id1, $id2, "deleted");
// i can't simply remove the item from the tracking list.
//unset ($tracking[$curpath]);
//$deleted["{$curpath}:{$tracking[$curpath]}"] = $id1;
continue;
}
if (array_key_exists('copyfrom', $p))
{
$copyfrom_path = $p['copyfrom'];
if (array_key_exists($copyfrom_path, $tracking))
{
$copyfrom_rev = $tracking[$copyfrom_path];
if (array_key_exists ("{$copyfrom_path}:{$copyfrom_rev}", $interesting))
{
$interesting["{$curpath}:{$currev}"] = 1;
$tracking[$curpath] = $currev;
//print ("{$copyfrom_path}:{$copyfrom_rev} -> {$curpath}:{$currev} [label=\"copy at {$currev}\", color=green];\n");
$id1 = $this->_add_rg_node ($nodeids, $nodes, "{$copyfrom_path}:{$copyfrom_rev}");
$id2 = $this->_add_rg_node ($nodeids, $nodes, "{$curpath}:{$currev}");
$this->_add_rg_edge ($edges, $id1, $id2, "copied");
$copysource["{$copyfrom_path}:{$copyfrom_rev}"] = 1;
$copydest["{$curpath}:{$currev}"] = 1;
}
}
}
do
{
if (array_key_exists($curpath, $tracking) && $tracking[$curpath] != $currev)
{
$codeline_changes_forward["{$curpath}:{$tracking[$curpath]}"] = "{$curpath}:{$currev}";
$codeline_changes_back["{$curpath}:{$currev}"] = "{$curpath}:{$tracking[$curpath]}";
$interesting["{$curpath}:{$currev}"] = 1;
$tracking[$curpath] = $currev;
}
if ($curpath == '/') break;
$curpath = dirname ($curpath);
}
while (1);
}
/*foreach ($deleted as $d => $v)
{
$id2 = $this->_add_rg_node ($nodeids, $nodes, "<<deleted>>");
$this->_add_rg_edge ($edges, $id1, $id2, "deleted");
}*/
}
foreach ($codeline_changes_forward as $k => $v)
{
if (array_key_exists ($k, $codeline_changes_back) && !array_key_exists($k, $copysource)) continue;
if (!array_key_exists ($k, $codeline_changes_back) || array_key_exists($k, $copysource))
{
if (array_key_exists($k, $codeline_changes_forward))
{
$nextchange = $codeline_changes_forward[$k];
$changecount = 1;
while (1)
{
if (array_key_exists($nextchange, $copysource) || !array_key_exists($nextchange, $codeline_changes_forward))
{
//print "{$k} -> {$nextchange} [label={$changecount} change(s)]\n";
$id1 = $this->_add_rg_node ($nodeids, $nodes, $k);
$id2 = $this->_add_rg_node ($nodeids, $nodes, $nextchange);
$this->_add_rg_edge ($edges, $id1, $id2, "{$changecount} change(s)");
break;
}
$changecount++;
if (!array_key_exists($nextchange, $codeline_changes_forward)) break;
$nextchange = $codeline_changes_forward[$nextchange];
}
}
}
}
return array ('nodes' => $nodes, 'edges' => $edges);
}
function zipSubdir ($projectid, $path, $rev, $topdir) function zipSubdir ($projectid, $path, $rev, $topdir)
{ {
// codepot_zip_dir() uses ZipArchive. Check if the class // codepot_zip_dir() uses ZipArchive. Check if the class

View File

@ -37,6 +37,9 @@
<script type="text/javascript" src="<?php print base_url_make('/js/d3.min.js')?>"></script> <script type="text/javascript" src="<?php print base_url_make('/js/d3.min.js')?>"></script>
<script type="text/javascript" src="<?php print base_url_make('/js/CodeFlower.js')?>"></script> <script type="text/javascript" src="<?php print base_url_make('/js/CodeFlower.js')?>"></script>
<script type="text/javascript" src="<?php print base_url_make('/js/vis.min.js')?>"></script>
<link type="text/css" rel="stylesheet" href="<?php print base_url_make('/css/vis.min.css')?>" />
<?php <?php
$file_count = count($file['content']); $file_count = count($file['content']);
@ -198,6 +201,67 @@ function show_loc_by_file_graph (response)
$("#code_folder_loc_by_file_spin" ).removeClass ("fa-cog fa-spin"); $("#code_folder_loc_by_file_spin" ).removeClass ("fa-cog fa-spin");
} }
function show_revision_graph (response)
{
var data = $.parseJSON(response);
if (data == null)
{
show_alert ('Invalid data received', "<?php print $this->lang->line('Error')?>");
}
else if (data.nodes.length <= 0)
{
show_alert ('No data to show', "<?php print $this->lang->line('Info')?>");
}
else
{
var options = {
autoResize: true,
height: '500px',
width: '100%',
layout: {
hierarchical: {
enabled: true,
levelSeparation: 150,
nodeSpacing: 200,
treeSpacing: 400,
direction: 'LR', //'LR' 'UD', 'DU', 'RL'
sortMethod: 'directed' // 'hubsize'
}
},
edges: {
smooth: {
type: 'cubicBezier',
forceDirection: 'horizontal', // 'vertical',
roundness: 0.4
}
},
physics: true
};
var i, j;
j = data.nodes.length;
for (i = 0; i < j; i++)
{
data.nodes[i].shape = 'box';
}
j = data.edges.length;
for (i = 0; i < j; i++)
{
data.edges[i].length = 60;
data.edges[i].width = 1;
data.edges[i].arrows = 'to';
data.edges[i].font = { color: 'red' };
}
var network = new vis.Network(document.getElementById('code_folder_result_revision_graph'), data, options);
}
$("#code_folder_revision_graph_button").button("enable");
$("#code_folder_revision_graph_spin" ).removeClass ("fa-cog fa-spin");
}
function render_readme() function render_readme()
{ {
<?php <?php
@ -662,7 +726,12 @@ $(function () {
"<?php print site_url(); ?>", "<?php print site_url(); ?>",
"/graph/enjson_loc_by_lang/<?php print $project->id; ?>/<?php print $hex_headpath;?><?php print $revreq?>"), "/graph/enjson_loc_by_lang/<?php print $project->id; ?>/<?php print $hex_headpath;?><?php print $revreq?>"),
context: document.body, context: document.body,
success: show_loc_by_lang_graph success: show_loc_by_lang_graph,
error: function (xhr, ajaxOptions, thrownError) {
show_alert (xhr.status + ' ' + thrownError, "<?php print $this->lang->line('Error')?>");
$("#code_folder_loc_by_lang_button").button("enable");
$("#code_folder_loc_by_lang_spin" ).removeClass ("fa-cog fa-spin");
}
}); });
return false; return false;
@ -676,7 +745,31 @@ $(function () {
"<?php print site_url(); ?>", "<?php print site_url(); ?>",
"/graph/enjson_loc_by_file/<?php print $project->id; ?>/<?php print $hex_headpath;?><?php print $revreq?>"), "/graph/enjson_loc_by_file/<?php print $project->id; ?>/<?php print $hex_headpath;?><?php print $revreq?>"),
context: document.body, context: document.body,
success: show_loc_by_file_graph success: show_loc_by_file_graph,
error: function (xhr, ajaxOptions, thrownError) {
show_alert (xhr.status + ' ' + thrownError, "<?php print $this->lang->line('Error')?>");
$("#code_folder_loc_by_file_button").button("enable");
$("#code_folder_loc_by_file_spin" ).removeClass ("fa-cog fa-spin");
}
});
return false;
});
$("#code_folder_revision_graph_button").button().click (function () {
$("#code_folder_revision_graph_button").button("disable");
$("#code_folder_revision_graph_spin").addClass ("fa-cog fa-spin");
var ajax_req = $.ajax ({
url: codepot_merge_path (
"<?php print site_url(); ?>",
"/graph/enjson_revision_graph/<?php print $project->id; ?>/<?php print $hex_headpath;?><?php print $revreq?>"),
context: document.body,
success: show_revision_graph,
error: function (xhr, ajaxOptions, thrownError) {
show_alert (xhr.status + ' ' + thrownError, "<?php print $this->lang->line('Error')?>");
$("#code_folder_revision_graph_button").button("enable");
$("#code_folder_revision_graph_spin" ).removeClass ("fa-cog fa-spin");
}
}); });
return false; return false;
@ -875,6 +968,11 @@ $this->load->view (
print $this->lang->line('File'); print $this->lang->line('File');
print '</a>'; print '</a>';
print '<a id="code_folder_revision_graph_button" href="#">';
print '<i id="code_folder_revision_graph_spin" class="fa"></i>';
print $this->lang->line('CODE_REVISION_GRAPH');
print '</a>';
if ($show_search) if ($show_search)
{ {
print '<a id="code_folder_search_button" href="#">'; print '<a id="code_folder_search_button" href="#">';
@ -921,6 +1019,7 @@ $this->load->view (
<div id="code_folder_graph" class="graph"> <div id="code_folder_graph" class="graph">
<div id="code_folder_result_loc_by_lang_graph"></div> <div id="code_folder_result_loc_by_lang_graph"></div>
<div id="code_folder_result_loc_by_file_graph"></div> <div id="code_folder_result_loc_by_file_graph"></div>
<div id="code_folder_result_revision_graph"></div>
</div> </div>
<div id="code_folder_search"> <div id="code_folder_search">

View File

@ -351,9 +351,10 @@ foreach ($urls as $url)
</div> </div>
<?php if (isset($login['id']) && $login['id'] != ''): ?> <?php if (isset($login['id']) && $login['id'] != ''): ?>
<!--
<div id='project_home_new_form'> <div id='project_home_new_form'>
<div style='line-height: 2em;'> <div style='line-height: 2em;'>
<?php <?php/*
print form_dropdown ( print form_dropdown (
'project_home_new_type', 'project_home_new_type',
$project_type_array, $project_type_array,
@ -369,7 +370,7 @@ foreach ($urls as $url)
$tmpmemb, $tmpmemb,
set_value('project_home_new_owner', (in_array($login['id'], $project->members)? $login['id']: '')), set_value('project_home_new_owner', (in_array($login['id'], $project->members)? $login['id']: '')),
'id="project_home_new_owner"' 'id="project_home_new_owner"'
); );*/
?> ?>
<input type='text' id='project_home_new_summary' name='project_home_new_summary' size='50' placeholder='<?php print $this->lang->line('Summary'); ?>'/> <input type='text' id='project_home_new_summary' name='project_home_new_summary' size='50' placeholder='<?php print $this->lang->line('Summary'); ?>'/>
@ -394,6 +395,7 @@ foreach ($urls as $url)
</div> </div>
</div> </div>
</div> </div>
-->
<?php endif; ?> <?php endif; ?>
<div id='project_home_alert'></div> <div id='project_home_alert'></div>

View File

@ -16,6 +16,7 @@ www_DATA = \
project.css \ project.css \
site.css \ site.css \
user.css \ user.css \
vis.min.css \
wiki.css wiki.css
EXTRA_DIST = $(www_DATA) EXTRA_DIST = $(www_DATA)

1
codepot/src/css/vis.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -31,6 +31,7 @@ www_DATA = \
CodeFlower.js \ CodeFlower.js \
pdf.min.js \ pdf.min.js \
pdf.worker.min.js \ pdf.worker.min.js \
vis.min.js \
webodf.js webodf.js
EXTRA_DIST = $(www_DATA) EXTRA_DIST = $(www_DATA)

45
codepot/src/js/vis.min.js vendored Normal file

File diff suppressed because one or more lines are too long