... | ... |
@@ -9,5 +9,6 @@ $app->mount('', new GitList\Controller\BlobController()); |
9 | 9 |
$app->mount('', new GitList\Controller\CommitController()); |
10 | 10 |
$app->mount('', new GitList\Controller\TreeController()); |
11 | 11 |
$app->mount('', new GitList\Controller\NetworkController()); |
12 |
+$app->mount('', new GitList\Controller\TreeGraphController()); |
|
12 | 13 |
|
13 | 14 |
return $app; |
14 | 15 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,157 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+namespace GitList\Controller; |
|
4 |
+ |
|
5 |
+use GitList\Git\Repository; |
|
6 |
+use Gitter\Model\Commit\Commit; |
|
7 |
+use Silex\Application; |
|
8 |
+use Silex\ControllerProviderInterface; |
|
9 |
+use Symfony\Component\HttpFoundation\Request; |
|
10 |
+ |
|
11 |
+class TreeGraphController implements ControllerProviderInterface |
|
12 |
+{ |
|
13 |
+ public function connect(Application $app) |
|
14 |
+ { |
|
15 |
+ $route = $app['controllers_factory']; |
|
16 |
+ |
|
17 |
+// $route->get('{repo}/graph/{commitishPath}/{page}.json', |
|
18 |
+// function ($repo, $commitishPath, $page) use ($app) { |
|
19 |
+// /** @var $repository Repository */ |
|
20 |
+// $repository = $app['git']->getRepositoryFromName($app['git.repos'], $repo); |
|
21 |
+// |
|
22 |
+// if ($commitishPath === null) { |
|
23 |
+// $commitishPath = $repository->getHead(); |
|
24 |
+// } |
|
25 |
+// |
|
26 |
+// $pager = $app['util.view']->getPager($page, $repository->getTotalCommits($commitishPath)); |
|
27 |
+// $commits = $repository->getPaginatedCommits($commitishPath, $pager['current']); |
|
28 |
+// |
|
29 |
+// $jsonFormattedCommits = array(); |
|
30 |
+// |
|
31 |
+// foreach ($commits as $commit) { |
|
32 |
+// $detailsUrl = $app['url_generator']->generate( |
|
33 |
+// 'commit', |
|
34 |
+// array( |
|
35 |
+// 'repo' => $repo, |
|
36 |
+// 'commit' => $commit->getHash() |
|
37 |
+// ) |
|
38 |
+// ); |
|
39 |
+// |
|
40 |
+// $jsonFormattedCommits[$commit->getHash()] = array( |
|
41 |
+// 'hash' => $commit->getHash(), |
|
42 |
+// 'parentsHash' => $commit->getParentsHash(), |
|
43 |
+// 'date' => $commit->getDate()->format('U'), |
|
44 |
+// 'message' => htmlentities($commit->getMessage()), |
|
45 |
+// 'details' => $detailsUrl, |
|
46 |
+// 'author' => array( |
|
47 |
+// 'name' => $commit->getAuthor()->getName(), |
|
48 |
+// 'email' => $commit->getAuthor()->getEmail(), |
|
49 |
+// // due to the lack of a inbuilt javascript md5 mechanism, build the full avatar url on the php side |
|
50 |
+// 'image' => 'http://gravatar.com/avatar/' . md5( |
|
51 |
+// strtolower($commit->getAuthor()->getEmail()) |
|
52 |
+// ) . '?s=40' |
|
53 |
+// ) |
|
54 |
+// ); |
|
55 |
+// } |
|
56 |
+// |
|
57 |
+// $nextPageUrl = null; |
|
58 |
+// |
|
59 |
+// if ($pager['last'] !== $pager['current']) { |
|
60 |
+// $nextPageUrl = $app['url_generator']->generate( |
|
61 |
+// 'networkData', |
|
62 |
+// array( |
|
63 |
+// 'repo' => $repo, |
|
64 |
+// 'commitishPath' => $commitishPath, |
|
65 |
+// 'page' => $pager['next'] |
|
66 |
+// ) |
|
67 |
+// ); |
|
68 |
+// } |
|
69 |
+// |
|
70 |
+// // when no commits are given, return an empty response - issue #369 |
|
71 |
+// if( count($commits) === 0 ) { |
|
72 |
+// return $app->json( array( |
|
73 |
+// 'repo' => $repo, |
|
74 |
+// 'commitishPath' => $commitishPath, |
|
75 |
+// 'nextPage' => null, |
|
76 |
+// 'start' => null, |
|
77 |
+// 'commits' => $jsonFormattedCommits |
|
78 |
+// ), 200 |
|
79 |
+// ); |
|
80 |
+// } |
|
81 |
+// |
|
82 |
+// return $app->json( array( |
|
83 |
+// 'repo' => $repo, |
|
84 |
+// 'commitishPath' => $commitishPath, |
|
85 |
+// 'nextPage' => $nextPageUrl, |
|
86 |
+// 'start' => $commits[0]->getHash(), |
|
87 |
+// 'commits' => $jsonFormattedCommits |
|
88 |
+// ), 200 |
|
89 |
+// ); |
|
90 |
+// } |
|
91 |
+// )->assert('repo', $app['util.routing']->getRepositoryRegex()) |
|
92 |
+// ->assert('commitishPath', $app['util.routing']->getCommitishPathRegex()) |
|
93 |
+// ->value('commitishPath', null) |
|
94 |
+// ->convert('commitishPath', 'escaper.argument:escape') |
|
95 |
+// ->assert('page', '\d+') |
|
96 |
+// ->value('page', '0') |
|
97 |
+// ->bind('graphData'); |
|
98 |
+ |
|
99 |
+ $route->get( |
|
100 |
+ '{repo}/treegraph/{commitishPath}', |
|
101 |
+ function ($repo, $commitishPath) use ($app) { |
|
102 |
+ /** @var \GitList\Git\Repository $repository */ |
|
103 |
+ $repository = $app['git']->getRepositoryFromName($app['git.repos'], $repo); |
|
104 |
+ |
|
105 |
+ $command = 'log --graph --date-order --all -C -M -n 100 --date=iso ' . |
|
106 |
+ '--pretty=format:"B[%d] C[%H] D[%ad] A[%an] E[%ae] H[%h] S[%s]"'; |
|
107 |
+ $rawRows = $repository->getClient()->run($repository, $command); |
|
108 |
+ $rawRows = explode("\n", $rawRows); |
|
109 |
+ $graphItems = array(); |
|
110 |
+ |
|
111 |
+ foreach ($rawRows as $row) { |
|
112 |
+ if (preg_match("/^(.+?)(\s(B\[(.*?)\])? C\[(.+?)\] D\[(.+?)\] A\[(.+?)\] E\[(.+?)\] H\[(.+?)\] S\[(.+?)\])?$/", $row, $output)) { |
|
113 |
+ if (!isset($output[4])) { |
|
114 |
+ $graphItems[] = array( |
|
115 |
+ "relation"=>$output[1] |
|
116 |
+ ); |
|
117 |
+ continue; |
|
118 |
+ } |
|
119 |
+ $graphItems[] = array( |
|
120 |
+ "relation"=>$output[1], |
|
121 |
+ "branch"=>$output[4], |
|
122 |
+ "rev"=>$output[5], |
|
123 |
+ "date"=>$output[6], |
|
124 |
+ "author"=>$output[7], |
|
125 |
+ "author_email"=>$output[8], |
|
126 |
+ "short_rev"=>$output[9], |
|
127 |
+ "subject"=>preg_replace('/(^|\s)(#[[:xdigit:]]+)(\s|$)/', '$1<a href="$2">$2</a>$3', $output[10]) |
|
128 |
+ ); |
|
129 |
+ } |
|
130 |
+ } |
|
131 |
+ |
|
132 |
+ if ($commitishPath === null) { |
|
133 |
+ $commitishPath = $repository->getHead(); |
|
134 |
+ } |
|
135 |
+ |
|
136 |
+ list($branch, $file) = $app['util.routing']->parseCommitishPathParam($commitishPath, $repo); |
|
137 |
+ list($branch, $file) = $app['util.repository']->extractRef($repository, $branch, $file); |
|
138 |
+ |
|
139 |
+ return $app['twig']->render( |
|
140 |
+ 'treegraph.twig', |
|
141 |
+ array( |
|
142 |
+ 'repo' => $repo, |
|
143 |
+ 'branch' => $branch, |
|
144 |
+ 'commitishPath' => $commitishPath, |
|
145 |
+ 'graphItems' => $graphItems, |
|
146 |
+ ) |
|
147 |
+ ); |
|
148 |
+ } |
|
149 |
+ )->assert('repo', $app['util.routing']->getRepositoryRegex()) |
|
150 |
+ ->assert('commitishPath', $app['util.routing']->getCommitishPathRegex()) |
|
151 |
+ ->value('commitishPath', null) |
|
152 |
+ ->convert('commitishPath', 'escaper.argument:escape') |
|
153 |
+ ->bind('treegraph'); |
|
154 |
+ |
|
155 |
+ return $route; |
|
156 |
+ } |
|
157 |
+} |
0 | 158 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,14 @@ |
1 |
+body {font:13.34px/1.4 helvetica,arial,freesans,clean,sans-serif;} |
|
2 |
+em {font-style:normal;} |
|
3 |
+ |
|
4 |
+#git-graph-container{ clear: both} |
|
5 |
+#rev-container, #rel-container {float:left;} |
|
6 |
+#git-graph-container li {list-style-type:none;height:28px;line-height:27px;overflow:hidden;} |
|
7 |
+#git-graph-container li .node-relation {font-family:'Bitstream Vera Sans Mono', 'Courier', monospace;} |
|
8 |
+#git-graph-container li .author {color:#666666;} |
|
9 |
+#git-graph-container li .time {color:#999999;font-size:80%} |
|
10 |
+#git-graph-container li a {color:#000000;} |
|
11 |
+#git-graph-container li a em {color:#BB0000;border-bottom:1px dotted #BBBBBB;text-decoration:none;font-style:normal;} |
|
12 |
+ |
|
13 |
+#rev-list {margin:0;padding:0 5px 0 0;} |
|
14 |
+#graph-raw-list {margin:0px;} |
|
0 | 15 |
\ No newline at end of file |
1 | 16 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,17 @@ |
1 |
+$(document).ready(function () { |
|
2 |
+ var graphList = []; |
|
3 |
+ |
|
4 |
+ if (!document.getElementById('graph-canvas')) { |
|
5 |
+ return; |
|
6 |
+ } |
|
7 |
+ |
|
8 |
+ $("#graph-raw-list li span.node-relation").each(function () { |
|
9 |
+ graphList.push($(this).text()); |
|
10 |
+ }) |
|
11 |
+ |
|
12 |
+ gitGraph(document.getElementById('graph-canvas'), graphList); |
|
13 |
+ |
|
14 |
+ if ($("#rev-container")) { |
|
15 |
+ $("#rev-container").css("width", $('#git-graph-container').width() - $('#graph-canvas').width()); |
|
16 |
+ } |
|
17 |
+}) |
0 | 18 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,399 @@ |
1 |
+/* |
|
2 |
+ * Copyright (c) 2011, Terrence Lee <kill889@gmail.com> |
|
3 |
+ * All rights reserved. |
|
4 |
+ * |
|
5 |
+ * Redistribution and use in source and binary forms, with or without |
|
6 |
+ * modification, are permitted provided that the following conditions are met: |
|
7 |
+ * * Redistributions of source code must retain the above copyright |
|
8 |
+ * notice, this list of conditions and the following disclaimer. |
|
9 |
+ * * Redistributions in binary form must reproduce the above copyright |
|
10 |
+ * notice, this list of conditions and the following disclaimer in the |
|
11 |
+ * documentation and/or other materials provided with the distribution. |
|
12 |
+ * * Neither the name of the <organization> nor the |
|
13 |
+ * names of its contributors may be used to endorse or promote products |
|
14 |
+ * derived from this software without specific prior written permission. |
|
15 |
+ * |
|
16 |
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|
17 |
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
19 |
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY |
|
20 |
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
21 |
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
22 |
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
23 |
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
24 |
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
25 |
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
26 |
+ */ |
|
27 |
+ |
|
28 |
+var gitGraph = function (canvas, rawGraphList, config) { |
|
29 |
+ if (!canvas.getContext) { |
|
30 |
+ return; |
|
31 |
+ } |
|
32 |
+ |
|
33 |
+ if (typeof config === "undefined") { |
|
34 |
+ config = { |
|
35 |
+ unitSize: 28, |
|
36 |
+ lineWidth: 3, |
|
37 |
+ nodeRadius: 6 |
|
38 |
+ }; |
|
39 |
+ } |
|
40 |
+ |
|
41 |
+ var flows = []; |
|
42 |
+ var graphList = []; |
|
43 |
+ |
|
44 |
+ var ctx = canvas.getContext("2d"); |
|
45 |
+ |
|
46 |
+ var init = function () { |
|
47 |
+ var maxWidth = 0; |
|
48 |
+ var i; |
|
49 |
+ var l = rawGraphList.length; |
|
50 |
+ var row; |
|
51 |
+ var midStr; |
|
52 |
+ |
|
53 |
+ for (i = 0; i < l; i++) { |
|
54 |
+ midStr = rawGraphList[i].replace(/\s+/g, " ").replace(/^\s+|\s+$/g, ""); |
|
55 |
+ |
|
56 |
+ maxWidth = Math.max(midStr.replace(/(\_|\s)/g, "").length, maxWidth); |
|
57 |
+ |
|
58 |
+ row = midStr.split(""); |
|
59 |
+ |
|
60 |
+ graphList.unshift(row); |
|
61 |
+ } |
|
62 |
+ |
|
63 |
+ canvas.width = maxWidth * config.unitSize; |
|
64 |
+ canvas.height = graphList.length * config.unitSize; |
|
65 |
+ |
|
66 |
+ ctx.lineWidth = config.lineWidth; |
|
67 |
+ ctx.lineJoin = "round"; |
|
68 |
+ ctx.lineCap = "round"; |
|
69 |
+ }; |
|
70 |
+ |
|
71 |
+ var genRandomStr = function () { |
|
72 |
+ var chars = "0123456789ABCDEF"; |
|
73 |
+ var stringLength = 6; |
|
74 |
+ var randomString = '', rnum, i; |
|
75 |
+ for (i = 0; i < stringLength; i++) { |
|
76 |
+ rnum = Math.floor(Math.random() * chars.length); |
|
77 |
+ randomString += chars.substring(rnum, rnum + 1); |
|
78 |
+ } |
|
79 |
+ |
|
80 |
+ return randomString; |
|
81 |
+ }; |
|
82 |
+ |
|
83 |
+ var findFlow = function (id) { |
|
84 |
+ var i = flows.length; |
|
85 |
+ |
|
86 |
+ while (i-- && flows[i].id !== id) {} |
|
87 |
+ |
|
88 |
+ return i; |
|
89 |
+ }; |
|
90 |
+ |
|
91 |
+ var findColomn = function (symbol, row) { |
|
92 |
+ var i = row.length; |
|
93 |
+ |
|
94 |
+ while (i-- && row[i] !== symbol) {} |
|
95 |
+ |
|
96 |
+ return i; |
|
97 |
+ }; |
|
98 |
+ |
|
99 |
+ var findBranchOut = function (row) { |
|
100 |
+ if (!row) { |
|
101 |
+ return -1 |
|
102 |
+ } |
|
103 |
+ |
|
104 |
+ var i = row.length; |
|
105 |
+ |
|
106 |
+ while (i-- && |
|
107 |
+ !(row[i - 1] && row[i] === "/" && row[i - 1] === "|") && |
|
108 |
+ !(row[i - 2] && row[i] === "_" && row[i - 2] === "|")) {} |
|
109 |
+ |
|
110 |
+ return i; |
|
111 |
+ } |
|
112 |
+ |
|
113 |
+ var genNewFlow = function () { |
|
114 |
+ var newId; |
|
115 |
+ |
|
116 |
+ do { |
|
117 |
+ newId = genRandomStr(); |
|
118 |
+ } while (findFlow(newId) !== -1); |
|
119 |
+ |
|
120 |
+ return {id:newId, color:"#" + newId}; |
|
121 |
+ }; |
|
122 |
+ |
|
123 |
+ //draw method |
|
124 |
+ var drawLineRight = function (x, y, color) { |
|
125 |
+ ctx.strokeStyle = color; |
|
126 |
+ ctx.beginPath(); |
|
127 |
+ ctx.moveTo(x, y + config.unitSize / 2); |
|
128 |
+ ctx.lineTo(x + config.unitSize, y + config.unitSize / 2); |
|
129 |
+ ctx.stroke(); |
|
130 |
+ }; |
|
131 |
+ |
|
132 |
+ var drawLineUp = function (x, y, color) { |
|
133 |
+ ctx.strokeStyle = color; |
|
134 |
+ ctx.beginPath(); |
|
135 |
+ ctx.moveTo(x, y + config.unitSize / 2); |
|
136 |
+ ctx.lineTo(x, y - config.unitSize / 2); |
|
137 |
+ ctx.stroke(); |
|
138 |
+ }; |
|
139 |
+ |
|
140 |
+ var drawNode = function (x, y, color) { |
|
141 |
+ ctx.strokeStyle = color; |
|
142 |
+ |
|
143 |
+ drawLineUp(x, y, color); |
|
144 |
+ |
|
145 |
+ ctx.beginPath(); |
|
146 |
+ ctx.arc(x, y, config.nodeRadius, 0, Math.PI * 2, true); |
|
147 |
+ ctx.fill(); |
|
148 |
+ }; |
|
149 |
+ |
|
150 |
+ var drawLineIn = function (x, y, color) { |
|
151 |
+ ctx.strokeStyle = color; |
|
152 |
+ |
|
153 |
+ ctx.beginPath(); |
|
154 |
+ ctx.moveTo(x + config.unitSize, y + config.unitSize / 2); |
|
155 |
+ ctx.lineTo(x, y - config.unitSize / 2); |
|
156 |
+ ctx.stroke(); |
|
157 |
+ }; |
|
158 |
+ |
|
159 |
+ var drawLineOut = function (x, y, color) { |
|
160 |
+ ctx.strokeStyle = color; |
|
161 |
+ ctx.beginPath(); |
|
162 |
+ ctx.moveTo(x, y + config.unitSize / 2); |
|
163 |
+ ctx.lineTo(x + config.unitSize, y - config.unitSize / 2); |
|
164 |
+ ctx.stroke(); |
|
165 |
+ }; |
|
166 |
+ |
|
167 |
+ var draw = function (graphList) { |
|
168 |
+ var colomn, colomnIndex, prevColomn, condenseIndex; |
|
169 |
+ var x, y; |
|
170 |
+ var color; |
|
171 |
+ var nodePos, outPos; |
|
172 |
+ var tempFlow; |
|
173 |
+ var prevRowLength = 0; |
|
174 |
+ var flowSwapPos = -1; |
|
175 |
+ var lastLinePos; |
|
176 |
+ var i, k, l; |
|
177 |
+ var condenseCurrentLength, condensePrevLength = 0, condenseNextLength = 0; |
|
178 |
+ |
|
179 |
+ var inlineIntersect = false; |
|
180 |
+ |
|
181 |
+ //initiate for first row |
|
182 |
+ for (i = 0, l = graphList[0].length; i < l; i++) { |
|
183 |
+ if (graphList[0][i] !== "_" && graphList[0][i] !== " ") { |
|
184 |
+ flows.push(genNewFlow()); |
|
185 |
+ } |
|
186 |
+ } |
|
187 |
+ |
|
188 |
+ y = canvas.height - config.unitSize * 0.5; |
|
189 |
+ |
|
190 |
+ //iterate |
|
191 |
+ for (i = 0, l = graphList.length; i < l; i++) { |
|
192 |
+ x = config.unitSize * 0.5; |
|
193 |
+ |
|
194 |
+ currentRow = graphList[i]; |
|
195 |
+ nextRow = graphList[i + 1]; |
|
196 |
+ prevRow = graphList[i - 1]; |
|
197 |
+ |
|
198 |
+ flowSwapPos = -1; |
|
199 |
+ |
|
200 |
+ condenseCurrentLength = currentRow.filter(function (val) { |
|
201 |
+ return (val !== " " && val !== "_") |
|
202 |
+ }).length; |
|
203 |
+ |
|
204 |
+ if (nextRow) { |
|
205 |
+ condenseNextLength = nextRow.filter(function (val) { |
|
206 |
+ return (val !== " " && val !== "_") |
|
207 |
+ }).length; |
|
208 |
+ } else { |
|
209 |
+ condenseNextLength = 0; |
|
210 |
+ } |
|
211 |
+ |
|
212 |
+ //pre process begin |
|
213 |
+ //use last row for analysing |
|
214 |
+ if (prevRow) { |
|
215 |
+ if (!inlineIntersect) { |
|
216 |
+ //intersect might happen |
|
217 |
+ for (colomnIndex = 0; colomnIndex < prevRowLength; colomnIndex++) { |
|
218 |
+ if (prevRow[colomnIndex + 1] && |
|
219 |
+ (prevRow[colomnIndex] === "/" && prevRow[colomnIndex + 1] === "|") || |
|
220 |
+ ((prevRow[colomnIndex] === "_" && prevRow[colomnIndex + 1] === "|") && |
|
221 |
+ (prevRow[colomnIndex + 2] === "/"))) { |
|
222 |
+ |
|
223 |
+ flowSwapPos = colomnIndex; |
|
224 |
+ |
|
225 |
+ //swap two flow |
|
226 |
+ tempFlow = {id:flows[flowSwapPos].id, color:flows[flowSwapPos].color}; |
|
227 |
+ |
|
228 |
+ flows[flowSwapPos].id = flows[flowSwapPos + 1].id; |
|
229 |
+ flows[flowSwapPos].color = flows[flowSwapPos + 1].color; |
|
230 |
+ |
|
231 |
+ flows[flowSwapPos + 1].id = tempFlow.id; |
|
232 |
+ flows[flowSwapPos + 1].color = tempFlow.color; |
|
233 |
+ } |
|
234 |
+ } |
|
235 |
+ } |
|
236 |
+ |
|
237 |
+ if (condensePrevLength < condenseCurrentLength && |
|
238 |
+ ((nodePos = findColomn("*", currentRow)) !== -1 && |
|
239 |
+ (findColomn("_", currentRow) === -1))) { |
|
240 |
+ |
|
241 |
+ flows.splice(nodePos - 1, 0, genNewFlow()); |
|
242 |
+ } |
|
243 |
+ |
|
244 |
+ if (prevRowLength > currentRow.length && |
|
245 |
+ (nodePos = findColomn("*", prevRow)) !== -1) { |
|
246 |
+ |
|
247 |
+ if (findColomn("_", currentRow) === -1 && |
|
248 |
+ findColomn("/", currentRow) === -1 && |
|
249 |
+ findColomn("\\", currentRow) === -1) { |
|
250 |
+ |
|
251 |
+ flows.splice(nodePos + 1, 1); |
|
252 |
+ } |
|
253 |
+ } |
|
254 |
+ } //done with the previous row |
|
255 |
+ |
|
256 |
+ prevRowLength = currentRow.length; //store for next round |
|
257 |
+ colomnIndex = 0; //reset index |
|
258 |
+ condenseIndex = 0; |
|
259 |
+ condensePrevLength = 0; |
|
260 |
+ while (colomnIndex < currentRow.length) { |
|
261 |
+ colomn = currentRow[colomnIndex]; |
|
262 |
+ |
|
263 |
+ if (colomn !== " " && colomn !== "_") { |
|
264 |
+ ++condensePrevLength; |
|
265 |
+ } |
|
266 |
+ |
|
267 |
+ if (colomn === " " && |
|
268 |
+ currentRow[colomnIndex + 1] && |
|
269 |
+ currentRow[colomnIndex + 1] === "_" && |
|
270 |
+ currentRow[colomnIndex - 1] && |
|
271 |
+ currentRow[colomnIndex - 1] === "|") { |
|
272 |
+ |
|
273 |
+ currentRow.splice(colomnIndex, 1); |
|
274 |
+ |
|
275 |
+ currentRow[colomnIndex] = "/"; |
|
276 |
+ colomn = "/"; |
|
277 |
+ } |
|
278 |
+ |
|
279 |
+ //create new flow only when no intersetc happened |
|
280 |
+ if (flowSwapPos === -1 && |
|
281 |
+ colomn === "/" && |
|
282 |
+ currentRow[colomnIndex - 1] && |
|
283 |
+ currentRow[colomnIndex - 1] === "|") { |
|
284 |
+ |
|
285 |
+ flows.splice(condenseIndex, 0, genNewFlow()); |
|
286 |
+ } |
|
287 |
+ |
|
288 |
+ //change \ and / to | when it's in the last position of the whole row |
|
289 |
+ if (colomn === "/" || colomn === "\\") { |
|
290 |
+ if (!(colomn === "/" && findBranchOut(nextRow) === -1)) { |
|
291 |
+ if ((lastLinePos = Math.max(findColomn("|", currentRow), |
|
292 |
+ findColomn("*", currentRow))) !== -1 && |
|
293 |
+ (lastLinePos < colomnIndex - 1)) { |
|
294 |
+ |
|
295 |
+ while (currentRow[++lastLinePos] === " ") {} |
|
296 |
+ |
|
297 |
+ if (lastLinePos === colomnIndex) { |
|
298 |
+ currentRow[colomnIndex] = "|"; |
|
299 |
+ } |
|
300 |
+ } |
|
301 |
+ } |
|
302 |
+ } |
|
303 |
+ |
|
304 |
+ if (colomn === "*" && |
|
305 |
+ prevRow && |
|
306 |
+ prevRow[condenseIndex + 1] === "\\") { |
|
307 |
+ flows.splice(condenseIndex + 1, 1); |
|
308 |
+ } |
|
309 |
+ |
|
310 |
+ if (colomn !== " ") { |
|
311 |
+ ++condenseIndex; |
|
312 |
+ } |
|
313 |
+ |
|
314 |
+ ++colomnIndex; |
|
315 |
+ } |
|
316 |
+ |
|
317 |
+ condenseCurrentLength = currentRow.filter(function (val) { |
|
318 |
+ return (val !== " " && val !== "_") |
|
319 |
+ }).length; |
|
320 |
+ |
|
321 |
+ //do some clean up |
|
322 |
+ if (flows.length > condenseCurrentLength) { |
|
323 |
+ flows.splice(condenseCurrentLength, flows.length - condenseCurrentLength); |
|
324 |
+ } |
|
325 |
+ |
|
326 |
+ colomnIndex = 0; |
|
327 |
+ |
|
328 |
+ //a little inline analysis and draw process |
|
329 |
+ while (colomnIndex < currentRow.length) { |
|
330 |
+ colomn = currentRow[colomnIndex]; |
|
331 |
+ prevColomn = currentRow[colomnIndex - 1]; |
|
332 |
+ |
|
333 |
+ if (currentRow[colomnIndex] === " ") { |
|
334 |
+ currentRow.splice(colomnIndex, 1); |
|
335 |
+ x += config.unitSize; |
|
336 |
+ |
|
337 |
+ continue; |
|
338 |
+ } |
|
339 |
+ |
|
340 |
+ //inline interset |
|
341 |
+ if ((colomn === "_" || colomn === "/") && |
|
342 |
+ currentRow[colomnIndex - 1] === "|" && |
|
343 |
+ currentRow[colomnIndex - 2] === "_") { |
|
344 |
+ |
|
345 |
+ inlineIntersect = true; |
|
346 |
+ |
|
347 |
+ tempFlow = flows.splice(colomnIndex - 2, 1)[0]; |
|
348 |
+ flows.splice(colomnIndex - 1, 0, tempFlow); |
|
349 |
+ currentRow.splice(colomnIndex - 2, 1); |
|
350 |
+ |
|
351 |
+ colomnIndex = colomnIndex - 1; |
|
352 |
+ } else { |
|
353 |
+ inlineIntersect = false; |
|
354 |
+ } |
|
355 |
+ |
|
356 |
+ color = flows[colomnIndex].color; |
|
357 |
+ |
|
358 |
+ switch (colomn) { |
|
359 |
+ case "_" : |
|
360 |
+ drawLineRight(x, y, color); |
|
361 |
+ |
|
362 |
+ x += config.unitSize; |
|
363 |
+ break; |
|
364 |
+ |
|
365 |
+ case "*" : |
|
366 |
+ drawNode(x, y, color); |
|
367 |
+ break; |
|
368 |
+ |
|
369 |
+ case "|" : |
|
370 |
+ drawLineUp(x, y, color); |
|
371 |
+ break; |
|
372 |
+ |
|
373 |
+ case "/" : |
|
374 |
+ if (prevColomn && |
|
375 |
+ (prevColomn === "/" || |
|
376 |
+ prevColomn === " ")) { |
|
377 |
+ x -= config.unitSize; |
|
378 |
+ } |
|
379 |
+ |
|
380 |
+ drawLineOut(x, y, color); |
|
381 |
+ |
|
382 |
+ x += config.unitSize; |
|
383 |
+ break; |
|
384 |
+ |
|
385 |
+ case "\\" : |
|
386 |
+ drawLineIn(x, y, color); |
|
387 |
+ break; |
|
388 |
+ } |
|
389 |
+ |
|
390 |
+ ++colomnIndex; |
|
391 |
+ } |
|
392 |
+ |
|
393 |
+ y -= config.unitSize; |
|
394 |
+ } |
|
395 |
+ }; |
|
396 |
+ |
|
397 |
+ init(); |
|
398 |
+ draw(graphList); |
|
399 |
+}; |
|
0 | 400 |
\ No newline at end of file |
... | ... |
@@ -5,6 +5,7 @@ |
5 | 5 |
<title>{% block title %}Welcome!{% endblock %}</title> |
6 | 6 |
<link rel="stylesheet" type="text/css" href="{{ app.request.basepath }}/themes/{{ app.theme }}/css/style.css"> |
7 | 7 |
<link rel="stylesheet" type="text/css" href="{{ app.request.basepath }}/themes/{{ app.theme }}/css/fontawesome.css"> |
8 |
+ <link rel="stylesheet" type="text/css" href="{{ app.request.basepath }}/themes/{{ app.theme }}/css/gitgraph.css"> |
|
8 | 9 |
<link rel="shortcut icon" type="image/png" href="{{ app.request.basepath }}/themes/{{ app.theme }}/img/favicon.png" /> |
9 | 10 |
<!--[if lt IE 9]> |
10 | 11 |
<script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/html5.js"></script> |
... | ... |
@@ -13,6 +14,7 @@ |
13 | 14 |
|
14 | 15 |
<body> |
15 | 16 |
{% block body %}{% endblock %} |
17 |
+ {% block javascripts %} |
|
16 | 18 |
<script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/jquery.js"></script> |
17 | 19 |
<script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/raphael.js"></script> |
18 | 20 |
<script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/bootstrap.js"></script> |
... | ... |
@@ -21,5 +23,8 @@ |
21 | 23 |
<script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/table.js"></script> |
22 | 24 |
<script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/main.js"></script> |
23 | 25 |
<script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/networkGraph.js"></script> |
26 |
+ <script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/gitgraph.js"></script> |
|
27 |
+ <script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/draw.js"></script> |
|
28 |
+ {% endblock %} |
|
24 | 29 |
</body> |
25 | 30 |
</html> |
26 | 31 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,48 @@ |
1 |
+{% extends 'layout_page.twig' %} |
|
2 |
+ |
|
3 |
+{% set page = 'treegraph' %} |
|
4 |
+ |
|
5 |
+{% block title %}GitList{% endblock %} |
|
6 |
+ |
|
7 |
+{% block content %} |
|
8 |
+ {% include 'breadcrumb.twig' with {breadcrumbs: [{dir: 'Graph', path:''}]} %} |
|
9 |
+ <div class="network-view"> |
|
10 |
+ <div class="network-header"> |
|
11 |
+ <div class="meta">Graph of {{ repo }} </div> |
|
12 |
+ </div> |
|
13 |
+ |
|
14 |
+ <div id="git-graph-container"> |
|
15 |
+ <div id="rel-container"> |
|
16 |
+ <canvas id="graph-canvas" width="100px"> |
|
17 |
+ <ul id="graph-raw-list"> |
|
18 |
+ {% for item in graphItems %} |
|
19 |
+ <li><span class="node-relation">{{ item.relation }}</span></li> |
|
20 |
+ {% endfor %} |
|
21 |
+ </ul> |
|
22 |
+ </canvas> |
|
23 |
+ </div> |
|
24 |
+ <div style="float:left;" id="rev-container"> |
|
25 |
+ <ul id="rev-list"> |
|
26 |
+ {% for item in graphItems %} |
|
27 |
+ <li> |
|
28 |
+ {% if item.rev is defined %} |
|
29 |
+ <a id="{{ item.short_rev }}" class="btn btn-default btn-sm" href="{{ path('commit', {repo: repo, commit: item.rev}) }}"> {{ item.short_rev }} </a> |
|
30 |
+ <strong> {{ item.branch }} </strong> |
|
31 |
+ <em>{{ item.subject }}</em> by |
|
32 |
+ <span class="author">{{ item.author }} <{{ item.author_email }}></span> |
|
33 |
+ <span class="time">{{ item.date }}</span>; |
|
34 |
+ {% else %} |
|
35 |
+ <span/> |
|
36 |
+ {% endif %} |
|
37 |
+ </li> |
|
38 |
+ {% endfor %} |
|
39 |
+ </ul> |
|
40 |
+ </div> |
|
41 |
+ <div style="clear:both"><!-- --></div> |
|
42 |
+ </div> |
|
43 |
+ </div> |
|
44 |
+ |
|
45 |
+ |
|
46 |
+ |
|
47 |
+ <hr/> |
|
48 |
+{% endblock %} |
0 | 49 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,14 @@ |
1 |
+body {font:13.34px/1.4 helvetica,arial,freesans,clean,sans-serif;} |
|
2 |
+em {font-style:normal;} |
|
3 |
+ |
|
4 |
+#git-graph-container{ clear: both} |
|
5 |
+#rev-container, #rel-container {float:left;} |
|
6 |
+#git-graph-container li {list-style-type:none;height:28px;line-height:27px;overflow:hidden;} |
|
7 |
+#git-graph-container li .node-relation {font-family:'Bitstream Vera Sans Mono', 'Courier', monospace;} |
|
8 |
+#git-graph-container li .author {color:#666666;} |
|
9 |
+#git-graph-container li .time {color:#999999;font-size:80%} |
|
10 |
+#git-graph-container li a {color:#000000;} |
|
11 |
+#git-graph-container li a em {color:#BB0000;border-bottom:1px dotted #BBBBBB;text-decoration:none;font-style:normal;} |
|
12 |
+ |
|
13 |
+#rev-list {margin:0;padding:0 5px 0 0;} |
|
14 |
+#graph-raw-list {margin:0px;} |
|
0 | 15 |
\ No newline at end of file |
1 | 16 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,17 @@ |
1 |
+$(document).ready(function () { |
|
2 |
+ var graphList = []; |
|
3 |
+ |
|
4 |
+ if (!document.getElementById('graph-canvas')) { |
|
5 |
+ return; |
|
6 |
+ } |
|
7 |
+ |
|
8 |
+ $("#graph-raw-list li span.node-relation").each(function () { |
|
9 |
+ graphList.push($(this).text()); |
|
10 |
+ }) |
|
11 |
+ |
|
12 |
+ gitGraph(document.getElementById('graph-canvas'), graphList); |
|
13 |
+ |
|
14 |
+ if ($("#rev-container")) { |
|
15 |
+ $("#rev-container").css("width", $('#git-graph-container').width() - $('#graph-canvas').width()); |
|
16 |
+ } |
|
17 |
+}) |
0 | 18 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,399 @@ |
1 |
+/* |
|
2 |
+ * Copyright (c) 2011, Terrence Lee <kill889@gmail.com> |
|
3 |
+ * All rights reserved. |
|
4 |
+ * |
|
5 |
+ * Redistribution and use in source and binary forms, with or without |
|
6 |
+ * modification, are permitted provided that the following conditions are met: |
|
7 |
+ * * Redistributions of source code must retain the above copyright |
|
8 |
+ * notice, this list of conditions and the following disclaimer. |
|
9 |
+ * * Redistributions in binary form must reproduce the above copyright |
|
10 |
+ * notice, this list of conditions and the following disclaimer in the |
|
11 |
+ * documentation and/or other materials provided with the distribution. |
|
12 |
+ * * Neither the name of the <organization> nor the |
|
13 |
+ * names of its contributors may be used to endorse or promote products |
|
14 |
+ * derived from this software without specific prior written permission. |
|
15 |
+ * |
|
16 |
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|
17 |
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
19 |
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY |
|
20 |
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
21 |
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
22 |
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
23 |
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
24 |
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
25 |
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
26 |
+ */ |
|
27 |
+ |
|
28 |
+var gitGraph = function (canvas, rawGraphList, config) { |
|
29 |
+ if (!canvas.getContext) { |
|
30 |
+ return; |
|
31 |
+ } |
|
32 |
+ |
|
33 |
+ if (typeof config === "undefined") { |
|
34 |
+ config = { |
|
35 |
+ unitSize: 28, |
|
36 |
+ lineWidth: 3, |
|
37 |
+ nodeRadius: 6 |
|
38 |
+ }; |
|
39 |
+ } |
|
40 |
+ |
|
41 |
+ var flows = []; |
|
42 |
+ var graphList = []; |
|
43 |
+ |
|
44 |
+ var ctx = canvas.getContext("2d"); |
|
45 |
+ |
|
46 |
+ var init = function () { |
|
47 |
+ var maxWidth = 0; |
|
48 |
+ var i; |
|
49 |
+ var l = rawGraphList.length; |
|
50 |
+ var row; |
|
51 |
+ var midStr; |
|
52 |
+ |
|
53 |
+ for (i = 0; i < l; i++) { |
|
54 |
+ midStr = rawGraphList[i].replace(/\s+/g, " ").replace(/^\s+|\s+$/g, ""); |
|
55 |
+ |
|
56 |
+ maxWidth = Math.max(midStr.replace(/(\_|\s)/g, "").length, maxWidth); |
|
57 |
+ |
|
58 |
+ row = midStr.split(""); |
|
59 |
+ |
|
60 |
+ graphList.unshift(row); |
|
61 |
+ } |
|
62 |
+ |
|
63 |
+ canvas.width = maxWidth * config.unitSize; |
|
64 |
+ canvas.height = graphList.length * config.unitSize; |
|
65 |
+ |
|
66 |
+ ctx.lineWidth = config.lineWidth; |
|
67 |
+ ctx.lineJoin = "round"; |
|
68 |
+ ctx.lineCap = "round"; |
|
69 |
+ }; |
|
70 |
+ |
|
71 |
+ var genRandomStr = function () { |
|
72 |
+ var chars = "0123456789ABCDEF"; |
|
73 |
+ var stringLength = 6; |
|
74 |
+ var randomString = '', rnum, i; |
|
75 |
+ for (i = 0; i < stringLength; i++) { |
|
76 |
+ rnum = Math.floor(Math.random() * chars.length); |
|
77 |
+ randomString += chars.substring(rnum, rnum + 1); |
|
78 |
+ } |
|
79 |
+ |
|
80 |
+ return randomString; |
|
81 |
+ }; |
|
82 |
+ |
|
83 |
+ var findFlow = function (id) { |
|
84 |
+ var i = flows.length; |
|
85 |
+ |
|
86 |
+ while (i-- && flows[i].id !== id) {} |
|
87 |
+ |
|
88 |
+ return i; |
|
89 |
+ }; |
|
90 |
+ |
|
91 |
+ var findColomn = function (symbol, row) { |
|
92 |
+ var i = row.length; |
|
93 |
+ |
|
94 |
+ while (i-- && row[i] !== symbol) {} |
|
95 |
+ |
|
96 |
+ return i; |
|
97 |
+ }; |
|
98 |
+ |
|
99 |
+ var findBranchOut = function (row) { |
|
100 |
+ if (!row) { |
|
101 |
+ return -1 |
|
102 |
+ } |
|
103 |
+ |
|
104 |
+ var i = row.length; |
|
105 |
+ |
|
106 |
+ while (i-- && |
|
107 |
+ !(row[i - 1] && row[i] === "/" && row[i - 1] === "|") && |
|
108 |
+ !(row[i - 2] && row[i] === "_" && row[i - 2] === "|")) {} |
|
109 |
+ |
|
110 |
+ return i; |
|
111 |
+ } |
|
112 |
+ |
|
113 |
+ var genNewFlow = function () { |
|
114 |
+ var newId; |
|
115 |
+ |
|
116 |
+ do { |
|
117 |
+ newId = genRandomStr(); |
|
118 |
+ } while (findFlow(newId) !== -1); |
|
119 |
+ |
|
120 |
+ return {id:newId, color:"#" + newId}; |
|
121 |
+ }; |
|
122 |
+ |
|
123 |
+ //draw method |
|
124 |
+ var drawLineRight = function (x, y, color) { |
|
125 |
+ ctx.strokeStyle = color; |
|
126 |
+ ctx.beginPath(); |
|
127 |
+ ctx.moveTo(x, y + config.unitSize / 2); |
|
128 |
+ ctx.lineTo(x + config.unitSize, y + config.unitSize / 2); |
|
129 |
+ ctx.stroke(); |
|
130 |
+ }; |
|
131 |
+ |
|
132 |
+ var drawLineUp = function (x, y, color) { |
|
133 |
+ ctx.strokeStyle = color; |
|
134 |
+ ctx.beginPath(); |
|
135 |
+ ctx.moveTo(x, y + config.unitSize / 2); |
|
136 |
+ ctx.lineTo(x, y - config.unitSize / 2); |
|
137 |
+ ctx.stroke(); |
|
138 |
+ }; |
|
139 |
+ |
|
140 |
+ var drawNode = function (x, y, color) { |
|
141 |
+ ctx.strokeStyle = color; |
|
142 |
+ |
|
143 |
+ drawLineUp(x, y, color); |
|
144 |
+ |
|
145 |
+ ctx.beginPath(); |
|
146 |
+ ctx.arc(x, y, config.nodeRadius, 0, Math.PI * 2, true); |
|
147 |
+ ctx.fill(); |
|
148 |
+ }; |
|
149 |
+ |
|
150 |
+ var drawLineIn = function (x, y, color) { |
|
151 |
+ ctx.strokeStyle = color; |
|
152 |
+ |
|
153 |
+ ctx.beginPath(); |
|
154 |
+ ctx.moveTo(x + config.unitSize, y + config.unitSize / 2); |
|
155 |
+ ctx.lineTo(x, y - config.unitSize / 2); |
|
156 |
+ ctx.stroke(); |
|
157 |
+ }; |
|
158 |
+ |
|
159 |
+ var drawLineOut = function (x, y, color) { |
|
160 |
+ ctx.strokeStyle = color; |
|
161 |
+ ctx.beginPath(); |
|
162 |
+ ctx.moveTo(x, y + config.unitSize / 2); |
|
163 |
+ ctx.lineTo(x + config.unitSize, y - config.unitSize / 2); |
|
164 |
+ ctx.stroke(); |
|
165 |
+ }; |
|
166 |
+ |
|
167 |
+ var draw = function (graphList) { |
|
168 |
+ var colomn, colomnIndex, prevColomn, condenseIndex; |
|
169 |
+ var x, y; |
|
170 |
+ var color; |
|
171 |
+ var nodePos, outPos; |
|
172 |
+ var tempFlow; |
|
173 |
+ var prevRowLength = 0; |
|
174 |
+ var flowSwapPos = -1; |
|
175 |
+ var lastLinePos; |
|
176 |
+ var i, k, l; |
|
177 |
+ var condenseCurrentLength, condensePrevLength = 0, condenseNextLength = 0; |
|
178 |
+ |
|
179 |
+ var inlineIntersect = false; |
|
180 |
+ |
|
181 |
+ //initiate for first row |
|
182 |
+ for (i = 0, l = graphList[0].length; i < l; i++) { |
|
183 |
+ if (graphList[0][i] !== "_" && graphList[0][i] !== " ") { |
|
184 |
+ flows.push(genNewFlow()); |
|
185 |
+ } |
|
186 |
+ } |
|
187 |
+ |
|
188 |
+ y = canvas.height - config.unitSize * 0.5; |
|
189 |
+ |
|
190 |
+ //iterate |
|
191 |
+ for (i = 0, l = graphList.length; i < l; i++) { |
|
192 |
+ x = config.unitSize * 0.5; |
|
193 |
+ |
|
194 |
+ currentRow = graphList[i]; |
|
195 |
+ nextRow = graphList[i + 1]; |
|
196 |
+ prevRow = graphList[i - 1]; |
|
197 |
+ |
|
198 |
+ flowSwapPos = -1; |
|
199 |
+ |
|
200 |
+ condenseCurrentLength = currentRow.filter(function (val) { |
|
201 |
+ return (val !== " " && val !== "_") |
|
202 |
+ }).length; |
|
203 |
+ |
|
204 |
+ if (nextRow) { |
|
205 |
+ condenseNextLength = nextRow.filter(function (val) { |
|
206 |
+ return (val !== " " && val !== "_") |
|
207 |
+ }).length; |
|
208 |
+ } else { |
|
209 |
+ condenseNextLength = 0; |
|
210 |
+ } |
|
211 |
+ |
|
212 |
+ //pre process begin |
|
213 |
+ //use last row for analysing |
|
214 |
+ if (prevRow) { |
|
215 |
+ if (!inlineIntersect) { |
|
216 |
+ //intersect might happen |
|
217 |
+ for (colomnIndex = 0; colomnIndex < prevRowLength; colomnIndex++) { |
|
218 |
+ if (prevRow[colomnIndex + 1] && |
|
219 |
+ (prevRow[colomnIndex] === "/" && prevRow[colomnIndex + 1] === "|") || |
|
220 |
+ ((prevRow[colomnIndex] === "_" && prevRow[colomnIndex + 1] === "|") && |
|
221 |
+ (prevRow[colomnIndex + 2] === "/"))) { |
|
222 |
+ |
|
223 |
+ flowSwapPos = colomnIndex; |
|
224 |
+ |
|
225 |
+ //swap two flow |
|
226 |
+ tempFlow = {id:flows[flowSwapPos].id, color:flows[flowSwapPos].color}; |
|
227 |
+ |
|
228 |
+ flows[flowSwapPos].id = flows[flowSwapPos + 1].id; |
|
229 |
+ flows[flowSwapPos].color = flows[flowSwapPos + 1].color; |
|
230 |
+ |
|
231 |
+ flows[flowSwapPos + 1].id = tempFlow.id; |
|
232 |
+ flows[flowSwapPos + 1].color = tempFlow.color; |
|
233 |
+ } |
|
234 |
+ } |
|
235 |
+ } |
|
236 |
+ |
|
237 |
+ if (condensePrevLength < condenseCurrentLength && |
|
238 |
+ ((nodePos = findColomn("*", currentRow)) !== -1 && |
|
239 |
+ (findColomn("_", currentRow) === -1))) { |
|
240 |
+ |
|
241 |
+ flows.splice(nodePos - 1, 0, genNewFlow()); |
|
242 |
+ } |
|
243 |
+ |
|
244 |
+ if (prevRowLength > currentRow.length && |
|
245 |
+ (nodePos = findColomn("*", prevRow)) !== -1) { |
|
246 |
+ |
|
247 |
+ if (findColomn("_", currentRow) === -1 && |
|
248 |
+ findColomn("/", currentRow) === -1 && |
|
249 |
+ findColomn("\\", currentRow) === -1) { |
|
250 |
+ |
|
251 |
+ flows.splice(nodePos + 1, 1); |
|
252 |
+ } |
|
253 |
+ } |
|
254 |
+ } //done with the previous row |
|
255 |
+ |
|
256 |
+ prevRowLength = currentRow.length; //store for next round |
|
257 |
+ colomnIndex = 0; //reset index |
|
258 |
+ condenseIndex = 0; |
|
259 |
+ condensePrevLength = 0; |
|
260 |
+ while (colomnIndex < currentRow.length) { |
|
261 |
+ colomn = currentRow[colomnIndex]; |
|
262 |
+ |
|
263 |
+ if (colomn !== " " && colomn !== "_") { |
|
264 |
+ ++condensePrevLength; |
|
265 |
+ } |
|
266 |
+ |
|
267 |
+ if (colomn === " " && |
|
268 |
+ currentRow[colomnIndex + 1] && |
|
269 |
+ currentRow[colomnIndex + 1] === "_" && |
|
270 |
+ currentRow[colomnIndex - 1] && |
|
271 |
+ currentRow[colomnIndex - 1] === "|") { |
|
272 |
+ |
|
273 |
+ currentRow.splice(colomnIndex, 1); |
|
274 |
+ |
|
275 |
+ currentRow[colomnIndex] = "/"; |
|
276 |
+ colomn = "/"; |
|
277 |
+ } |
|
278 |
+ |
|
279 |
+ //create new flow only when no intersetc happened |
|
280 |
+ if (flowSwapPos === -1 && |
|
281 |
+ colomn === "/" && |
|
282 |
+ currentRow[colomnIndex - 1] && |
|
283 |
+ currentRow[colomnIndex - 1] === "|") { |
|
284 |
+ |
|
285 |
+ flows.splice(condenseIndex, 0, genNewFlow()); |
|
286 |
+ } |
|
287 |
+ |
|
288 |
+ //change \ and / to | when it's in the last position of the whole row |
|
289 |
+ if (colomn === "/" || colomn === "\\") { |
|
290 |
+ if (!(colomn === "/" && findBranchOut(nextRow) === -1)) { |
|
291 |
+ if ((lastLinePos = Math.max(findColomn("|", currentRow), |
|
292 |
+ findColomn("*", currentRow))) !== -1 && |
|
293 |
+ (lastLinePos < colomnIndex - 1)) { |
|
294 |
+ |
|
295 |
+ while (currentRow[++lastLinePos] === " ") {} |
|
296 |
+ |
|
297 |
+ if (lastLinePos === colomnIndex) { |
|
298 |
+ currentRow[colomnIndex] = "|"; |
|
299 |
+ } |
|
300 |
+ } |
|
301 |
+ } |
|
302 |
+ } |
|
303 |
+ |
|
304 |
+ if (colomn === "*" && |
|
305 |
+ prevRow && |
|
306 |
+ prevRow[condenseIndex + 1] === "\\") { |
|
307 |
+ flows.splice(condenseIndex + 1, 1); |
|
308 |
+ } |
|
309 |
+ |
|
310 |
+ if (colomn !== " ") { |
|
311 |
+ ++condenseIndex; |
|
312 |
+ } |
|
313 |
+ |
|
314 |
+ ++colomnIndex; |
|
315 |
+ } |
|
316 |
+ |
|
317 |
+ condenseCurrentLength = currentRow.filter(function (val) { |
|
318 |
+ return (val !== " " && val !== "_") |
|
319 |
+ }).length; |
|
320 |
+ |
|
321 |
+ //do some clean up |
|
322 |
+ if (flows.length > condenseCurrentLength) { |
|
323 |
+ flows.splice(condenseCurrentLength, flows.length - condenseCurrentLength); |
|
324 |
+ } |
|
325 |
+ |
|
326 |
+ colomnIndex = 0; |
|
327 |
+ |
|
328 |
+ //a little inline analysis and draw process |
|
329 |
+ while (colomnIndex < currentRow.length) { |
|
330 |
+ colomn = currentRow[colomnIndex]; |
|
331 |
+ prevColomn = currentRow[colomnIndex - 1]; |
|
332 |
+ |
|
333 |
+ if (currentRow[colomnIndex] === " ") { |
|
334 |
+ currentRow.splice(colomnIndex, 1); |
|
335 |
+ x += config.unitSize; |
|
336 |
+ |
|
337 |
+ continue; |
|
338 |
+ } |
|
339 |
+ |
|
340 |
+ //inline interset |
|
341 |
+ if ((colomn === "_" || colomn === "/") && |
|
342 |
+ currentRow[colomnIndex - 1] === "|" && |
|
343 |
+ currentRow[colomnIndex - 2] === "_") { |
|
344 |
+ |
|
345 |
+ inlineIntersect = true; |
|
346 |
+ |
|
347 |
+ tempFlow = flows.splice(colomnIndex - 2, 1)[0]; |
|
348 |
+ flows.splice(colomnIndex - 1, 0, tempFlow); |
|
349 |
+ currentRow.splice(colomnIndex - 2, 1); |
|
350 |
+ |
|
351 |
+ colomnIndex = colomnIndex - 1; |
|
352 |
+ } else { |
|
353 |
+ inlineIntersect = false; |
|
354 |
+ } |
|
355 |
+ |
|
356 |
+ color = flows[colomnIndex].color; |
|
357 |
+ |
|
358 |
+ switch (colomn) { |
|
359 |
+ case "_" : |
|
360 |
+ drawLineRight(x, y, color); |
|
361 |
+ |
|
362 |
+ x += config.unitSize; |
|
363 |
+ break; |
|
364 |
+ |
|
365 |
+ case "*" : |
|
366 |
+ drawNode(x, y, color); |
|
367 |
+ break; |
|
368 |
+ |
|
369 |
+ case "|" : |
|
370 |
+ drawLineUp(x, y, color); |
|
371 |
+ break; |
|
372 |
+ |
|
373 |
+ case "/" : |
|
374 |
+ if (prevColomn && |
|
375 |
+ (prevColomn === "/" || |
|
376 |
+ prevColomn === " ")) { |
|
377 |
+ x -= config.unitSize; |
|
378 |
+ } |
|
379 |
+ |
|
380 |
+ drawLineOut(x, y, color); |
|
381 |
+ |
|
382 |
+ x += config.unitSize; |
|
383 |
+ break; |
|
384 |
+ |
|
385 |
+ case "\\" : |
|
386 |
+ drawLineIn(x, y, color); |
|
387 |
+ break; |
|
388 |
+ } |
|
389 |
+ |
|
390 |
+ ++colomnIndex; |
|
391 |
+ } |
|
392 |
+ |
|
393 |
+ y -= config.unitSize; |
|
394 |
+ } |
|
395 |
+ }; |
|
396 |
+ |
|
397 |
+ init(); |
|
398 |
+ draw(graphList); |
|
399 |
+}; |
|
0 | 400 |
\ No newline at end of file |
... | ... |
@@ -4,6 +4,7 @@ |
4 | 4 |
<meta charset="UTF-8" /> |
5 | 5 |
<title>{{ app.title }}{% if app.title %} - {% endif %}{% block title %}Welcome!{% endblock %}</title> |
6 | 6 |
<link rel="stylesheet" type="text/css" href="{{ app.request.basepath }}/themes/{{ app.theme }}/css/style.css"> |
7 |
+ <link rel="stylesheet" type="text/css" href="{{ app.request.basepath }}/themes/{{ app.theme }}/css/gitgraph.css"> |
|
7 | 8 |
<link rel="shortcut icon" type="image/png" href="{{ app.request.basepath }}/themes/{{ app.theme }}/img/favicon.png" /> |
8 | 9 |
<!--[if lt IE 9]> |
9 | 10 |
<script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/html5.js"></script> |
... | ... |
@@ -12,6 +13,7 @@ |
12 | 13 |
|
13 | 14 |
<body> |
14 | 15 |
{% block body %}{% endblock %} |
16 |
+ {% block javascripts %} |
|
15 | 17 |
<script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/jquery.js"></script> |
16 | 18 |
<script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/raphael.js"></script> |
17 | 19 |
<script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/bootstrap.js"></script> |
... | ... |
@@ -21,5 +23,8 @@ |
21 | 23 |
<script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/list.min.js"></script> |
22 | 24 |
<script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/main.js"></script> |
23 | 25 |
<script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/networkGraph.js"></script> |
26 |
+ <script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/gitgraph.js"></script> |
|
27 |
+ <script src="{{ app.request.basepath }}/themes/{{ app.theme }}/js/draw.js"></script> |
|
28 |
+ {% endblock %} |
|
24 | 29 |
</body> |
25 | 30 |
</html> |
... | ... |
@@ -3,4 +3,5 @@ |
3 | 3 |
<li{% if page in ['commits', 'searchcommits'] %} class="active"{% endif %}><a href="{{ path('commits', {repo: repo, commitishPath: branch}) }}">Commits</a></li> |
4 | 4 |
<li{% if page == 'stats' %} class="active"{% endif %}><a href="{{ path('stats', {repo: repo, branch: branch}) }}">Stats</a></li> |
5 | 5 |
<li{% if page == 'network' %} class="active"{% endif %}><a href="{{ path('network', {repo: repo, branch: branch}) }}">Network</a></li> |
6 |
+ <li{% if page == 'treegraph' %} class="active"{% endif %}><a href="{{ path('treegraph', {repo: repo, branch: branch}) }}">Graph</a></li> |
|
6 | 7 |
</ul> |
7 | 8 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,48 @@ |
1 |
+{% extends 'layout_page.twig' %} |
|
2 |
+ |
|
3 |
+{% set page = 'treegraph' %} |
|
4 |
+ |
|
5 |
+{% block title %}GitList{% endblock %} |
|
6 |
+ |
|
7 |
+{% block content %} |
|
8 |
+ {% include 'breadcrumb.twig' with {breadcrumbs: [{dir: 'Graph', path:''}]} %} |
|
9 |
+ <div class="network-view"> |
|
10 |
+ <div class="network-header"> |
|
11 |
+ <div class="meta">Graph of {{ repo }} </div> |
|
12 |
+ </div> |
|
13 |
+ |
|
14 |
+ <div id="git-graph-container"> |
|
15 |
+ <div id="rel-container"> |
|
16 |
+ <canvas id="graph-canvas" width="100px"> |
|
17 |
+ <ul id="graph-raw-list"> |
|
18 |
+ {% for item in graphItems %} |
|
19 |
+ <li><span class="node-relation">{{ item.relation }}</span></li> |
|
20 |
+ {% endfor %} |
|
21 |
+ </ul> |
|
22 |
+ </canvas> |
|
23 |
+ </div> |
|
24 |
+ <div style="float:left;" id="rev-container"> |
|
25 |
+ <ul id="rev-list"> |
|
26 |
+ {% for item in graphItems %} |
|
27 |
+ <li> |
|
28 |
+ {% if item.rev is defined %} |
|
29 |
+ <a id="{{ item.short_rev }}" class="btn btn-small" href="{{ path('commit', {repo: repo, commit: item.rev}) }}"> {{ item.short_rev }} </a> |
|
30 |
+ <strong> {{ item.branch }} </strong> |
|
31 |
+ <em>{{ item.subject }}</em> by |
|
32 |
+ <span class="author">{{ item.author }} <{{ item.author_email }}></span> |
|
33 |
+ <span class="time">{{ item.date }}</span>; |
|
34 |
+ {% else %} |
|
35 |
+ <span/> |
|
36 |
+ {% endif %} |
|
37 |
+ </li> |
|
38 |
+ {% endfor %} |
|
39 |
+ </ul> |
|
40 |
+ </div> |
|
41 |
+ <div style="clear:both"><!-- --></div> |
|
42 |
+ </div> |
|
43 |
+ </div> |
|
44 |
+ |
|
45 |
+ |
|
46 |
+ |
|
47 |
+ <hr/> |
|
48 |
+{% endblock %} |