added a primitive revision graph in a code folder
This commit is contained in:
		| @ -168,4 +168,33 @@ class Graph extends Controller | ||||
| 		$cloc = $cloc = $this->subversion->clocRevByFile ($projectid, $path, $rev); | ||||
| 		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); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| $lang['CODE_COMMIT'] = 'Commit'; | ||||
| $lang['CODE_PROPERTIES'] = 'Properties'; | ||||
| $lang['CODE_PROPERTY'] = 'Property'; | ||||
| $lang['CODE_REVISION_GRAPH'] = 'Revision graph'; | ||||
|  | ||||
| $lang['CODE_MSG_COMMITTED_BY'] = 'Committed by %s'; | ||||
| $lang['CODE_MSG_COMMITTED_BY_ON'] = 'Committed by %s on %s'; | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| $lang['CODE_COMMIT'] = '커밋'; | ||||
| $lang['CODE_PROPERTIES'] = '속성'; | ||||
| $lang['CODE_PROPERTY'] = '속성'; | ||||
| $lang['CODE_REVISION_GRAPH'] = '리비전 그래프'; | ||||
|  | ||||
| $lang['CODE_MSG_COMMITTED_BY'] = '커밋터 %s'; | ||||
| $lang['CODE_MSG_COMMITTED_BY_ON'] = '커밋터 %s 시간 %s'; | ||||
|  | ||||
| @ -686,8 +686,7 @@ class SubversionModel extends Model | ||||
| 		} | ||||
| 		else if ($info[0]['kind'] == SVN_NODE_DIR) | ||||
| 		{ | ||||
| 			$fileinfo['fullpath'] = substr ( | ||||
| 				$info[0]['url'], strlen($info[0]['repos'])); | ||||
| 			$fileinfo['fullpath'] = substr ($info[0]['url'], strlen($info[0]['repos'])); | ||||
| 			$fileinfo['name'] =  $info[0]['path']; | ||||
| 			$fileinfo['type'] = 'dir'; | ||||
| 			$fileinfo['size'] = 0; | ||||
| @ -1544,7 +1543,6 @@ class SubversionModel extends Model | ||||
| 	function clocRevByFile ($projectid, $path, $rev) | ||||
| 	{ | ||||
| 		// this function composes the data as CodeFlower requires | ||||
|  | ||||
| 		$stack = array(); | ||||
| 		$cloc = new stdClass(); | ||||
|  | ||||
| @ -1638,6 +1636,166 @@ class SubversionModel extends Model | ||||
| 		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) | ||||
| 	{ | ||||
| 		// codepot_zip_dir() uses ZipArchive. Check if the class | ||||
|  | ||||
| @ -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/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 | ||||
| 	$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"); | ||||
| } | ||||
|  | ||||
| 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() | ||||
| { | ||||
| 	<?php | ||||
| @ -662,7 +726,12 @@ $(function () { | ||||
| 				"<?php print site_url(); ?>",  | ||||
| 				"/graph/enjson_loc_by_lang/<?php print $project->id; ?>/<?php print $hex_headpath;?><?php print $revreq?>"), | ||||
| 			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; | ||||
| @ -676,7 +745,31 @@ $(function () { | ||||
| 				"<?php print site_url(); ?>",  | ||||
| 				"/graph/enjson_loc_by_file/<?php print $project->id; ?>/<?php print $hex_headpath;?><?php print $revreq?>"), | ||||
| 			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; | ||||
| @ -875,6 +968,11 @@ $this->load->view ( | ||||
| 		print $this->lang->line('File');  | ||||
| 		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) | ||||
| 		{ | ||||
| 			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_result_loc_by_lang_graph"></div> | ||||
| 	<div id="code_folder_result_loc_by_file_graph"></div> | ||||
| 	<div id="code_folder_result_revision_graph"></div> | ||||
| </div> | ||||
|  | ||||
| <div id="code_folder_search"> | ||||
|  | ||||
| @ -351,9 +351,10 @@ foreach ($urls as $url) | ||||
| </div> | ||||
|  | ||||
| <?php if (isset($login['id']) && $login['id'] != ''): ?> | ||||
| <!-- | ||||
| <div id='project_home_new_form'> | ||||
| 	<div style='line-height: 2em;'> | ||||
| 		<?php | ||||
| 		<?php/* | ||||
| 		print form_dropdown ( | ||||
| 			'project_home_new_type',  | ||||
| 			$project_type_array, | ||||
| @ -369,7 +370,7 @@ foreach ($urls as $url) | ||||
| 			$tmpmemb, | ||||
| 			set_value('project_home_new_owner', (in_array($login['id'], $project->members)? $login['id']: '')), | ||||
| 			'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'); ?>'/> | ||||
| @ -394,6 +395,7 @@ foreach ($urls as $url) | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| --> | ||||
| <?php endif; ?> | ||||
|  | ||||
| <div id='project_home_alert'></div> | ||||
|  | ||||
| @ -16,6 +16,7 @@ www_DATA =  \ | ||||
| 	project.css \ | ||||
| 	site.css \ | ||||
| 	user.css \ | ||||
| 	vis.min.css \ | ||||
| 	wiki.css | ||||
|  | ||||
| EXTRA_DIST = $(www_DATA) | ||||
|  | ||||
							
								
								
									
										1
									
								
								codepot/src/css/vis.min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								codepot/src/css/vis.min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -31,6 +31,7 @@ www_DATA = \ | ||||
| 	CodeFlower.js \ | ||||
| 	pdf.min.js \ | ||||
| 	pdf.worker.min.js \ | ||||
| 	vis.min.js \ | ||||
| 	webodf.js | ||||
|  | ||||
| EXTRA_DIST = $(www_DATA) | ||||
|  | ||||
							
								
								
									
										45
									
								
								codepot/src/js/vis.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								codepot/src/js/vis.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user