puppetfactory 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +13 -0
  3. data/README.md +0 -0
  4. data/bin/pfsh +31 -0
  5. data/bin/puppetfactory +153 -0
  6. data/lib/puppetfactory.rb +300 -0
  7. data/lib/puppetfactory/cli.rb +114 -0
  8. data/lib/puppetfactory/dashboard/rake_tasks.rb +69 -0
  9. data/lib/puppetfactory/dashboard/serverspec_helper.rb +84 -0
  10. data/lib/puppetfactory/dashboard/spec_helper.rb +26 -0
  11. data/lib/puppetfactory/helpers.rb +37 -0
  12. data/lib/puppetfactory/monkeypatches.rb +30 -0
  13. data/lib/puppetfactory/plugins.rb +11 -0
  14. data/lib/puppetfactory/plugins/certificates.rb +28 -0
  15. data/lib/puppetfactory/plugins/classification.rb +75 -0
  16. data/lib/puppetfactory/plugins/code_manager.rb +156 -0
  17. data/lib/puppetfactory/plugins/console_user.rb +62 -0
  18. data/lib/puppetfactory/plugins/dashboard.rb +128 -0
  19. data/lib/puppetfactory/plugins/docker.rb +193 -0
  20. data/lib/puppetfactory/plugins/example.rb +88 -0
  21. data/lib/puppetfactory/plugins/github.rb +102 -0
  22. data/lib/puppetfactory/plugins/gitlab.rb +62 -0
  23. data/lib/puppetfactory/plugins/hooks.rb +46 -0
  24. data/lib/puppetfactory/plugins/login_shell.rb +10 -0
  25. data/lib/puppetfactory/plugins/logs.rb +34 -0
  26. data/lib/puppetfactory/plugins/r10k.rb +112 -0
  27. data/lib/puppetfactory/plugins/shell_user.rb +69 -0
  28. data/lib/puppetfactory/plugins/user_environment.rb +77 -0
  29. data/public/dashboard.js +100 -0
  30. data/public/font-awesome/css/font-awesome.css +2199 -0
  31. data/public/font-awesome/css/font-awesome.min.css +4 -0
  32. data/public/font-awesome/fonts/FontAwesome.otf +0 -0
  33. data/public/font-awesome/fonts/fontawesome-webfont.eot +0 -0
  34. data/public/font-awesome/fonts/fontawesome-webfont.svg +685 -0
  35. data/public/font-awesome/fonts/fontawesome-webfont.ttf +0 -0
  36. data/public/font-awesome/fonts/fontawesome-webfont.woff +0 -0
  37. data/public/font-awesome/fonts/fontawesome-webfont.woff2 +0 -0
  38. data/public/gitviz/LICENSE.md +20 -0
  39. data/public/gitviz/README.md +13 -0
  40. data/public/gitviz/css/explaingit.css +227 -0
  41. data/public/gitviz/css/vendor/1140.css +130 -0
  42. data/public/gitviz/images/forkme_right_red_aa0000.png +0 -0
  43. data/public/gitviz/images/grippy-close.png +0 -0
  44. data/public/gitviz/images/grippy.png +0 -0
  45. data/public/gitviz/images/prompt.gif +0 -0
  46. data/public/gitviz/index.html +734 -0
  47. data/public/gitviz/js/controlbox.js +459 -0
  48. data/public/gitviz/js/explaingit.js +74 -0
  49. data/public/gitviz/js/historyview.js +979 -0
  50. data/public/gitviz/js/main.js +56 -0
  51. data/public/gitviz/js/vendor/d3.min.js +4 -0
  52. data/public/gitviz/js/vendor/jquery-latest.min.js +6 -0
  53. data/public/gitviz/js/vendor/normalize.css +396 -0
  54. data/public/gitviz/js/vendor/require.min.js +35 -0
  55. data/public/gitviz/memtest.html +44 -0
  56. data/public/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  57. data/public/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  58. data/public/images/ui-bg_flat_10_000000_40x100.png +0 -0
  59. data/public/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  60. data/public/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  61. data/public/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  62. data/public/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  63. data/public/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  64. data/public/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  65. data/public/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  66. data/public/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  67. data/public/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  68. data/public/images/ui-icons_222222_256x240.png +0 -0
  69. data/public/images/ui-icons_228ef1_256x240.png +0 -0
  70. data/public/images/ui-icons_454545_256x240.png +0 -0
  71. data/public/images/ui-icons_ef8c08_256x240.png +0 -0
  72. data/public/images/ui-icons_ffd27a_256x240.png +0 -0
  73. data/public/images/ui-icons_ffffff_256x240.png +0 -0
  74. data/public/jquery-1.11.1.min.js +4 -0
  75. data/public/jquery-ui.css +464 -0
  76. data/public/jquery-ui.min.css +7 -0
  77. data/public/jquery-ui.min.js +13 -0
  78. data/public/jquery-ui.structure.min.css +5 -0
  79. data/public/jquery-ui.theme.min.css +5 -0
  80. data/public/jquery.activity-indicator-1.0.0.min.js +10 -0
  81. data/public/jquery.js +9789 -0
  82. data/public/loginscripts.js +18 -0
  83. data/public/scripts.js +36 -0
  84. data/public/style.css +193 -0
  85. data/public/usermanagement.js +133 -0
  86. data/templates/init_scripts.erb +10 -0
  87. data/templates/puppet.conf.erb +10 -0
  88. data/templates/site.pp.erb +50 -0
  89. data/views/dashboard.erb +62 -0
  90. data/views/home.erb +43 -0
  91. data/views/index.erb +29 -0
  92. data/views/logs.erb +26 -0
  93. data/views/shell.erb +35 -0
  94. data/views/users.erb +69 -0
  95. metadata +256 -0
@@ -0,0 +1,74 @@
1
+ define(['historyview', 'controlbox', 'd3'], function (HistoryView, ControlBox, d3) {
2
+ var prefix = 'ExplainGit',
3
+ openSandBoxes = [],
4
+ open,
5
+ reset,
6
+ explainGit;
7
+
8
+ open = function (args) {
9
+ var name = prefix + args.name,
10
+ containerId = name + '-Container',
11
+ container = d3.select('#' + containerId),
12
+ playground = container.select('.playground-container'),
13
+ historyView, originView = null,
14
+ controlBox;
15
+
16
+ container.style('display', 'block');
17
+
18
+ args.name = name;
19
+ args.height = 525;
20
+ historyView = new HistoryView(args);
21
+
22
+ if (args.originData) {
23
+ originView = new HistoryView({
24
+ name: name + '-Origin',
25
+ width: 300,
26
+ height: 225,
27
+ commitRadius: 15,
28
+ remoteName: 'origin',
29
+ commitData: args.originData
30
+ });
31
+
32
+ originView.render(playground);
33
+ }
34
+
35
+ controlBox = new ControlBox({
36
+ historyView: historyView,
37
+ originView: originView,
38
+ initialMessage: args.initialMessage
39
+ });
40
+
41
+ controlBox.render(playground);
42
+ historyView.render(playground);
43
+
44
+ openSandBoxes.push({
45
+ hv: historyView,
46
+ cb: controlBox,
47
+ container: container
48
+ });
49
+ };
50
+
51
+ reset = function () {
52
+ for (var i = 0; i < openSandBoxes.length; i++) {
53
+ var osb = openSandBoxes[i];
54
+ osb.hv.destroy();
55
+ osb.cb.destroy();
56
+ osb.container.style('display', 'none');
57
+ }
58
+
59
+ openSandBoxes.length = 0;
60
+ d3.selectAll('a.openswitch').classed('selected', false);
61
+ };
62
+
63
+ explainGit = {
64
+ HistoryView: HistoryView,
65
+ ControlBox: ControlBox,
66
+ generateId: HistoryView.generateId,
67
+ open: open,
68
+ reset: reset
69
+ };
70
+
71
+ window.explainGit = explainGit;
72
+
73
+ return explainGit;
74
+ });
@@ -0,0 +1,979 @@
1
+ define(['d3'], function () {
2
+ "use strict";
3
+
4
+ var REG_MARKER_END = 'url(#triangle)',
5
+ MERGE_MARKER_END = 'url(#brown-triangle)',
6
+ FADED_MARKER_END = 'url(#faded-triangle)',
7
+
8
+ preventOverlap,
9
+ applyBranchlessClass,
10
+ cx, cy, fixCirclePosition,
11
+ px1, py1, fixPointerStartPosition,
12
+ px2, py2, fixPointerEndPosition,
13
+ fixIdPosition, tagY;
14
+
15
+ preventOverlap = function preventOverlap(commit, view) {
16
+ var commitData = view.commitData,
17
+ baseLine = view.baseLine,
18
+ shift = view.commitRadius * 4.5,
19
+ overlapped = null;
20
+
21
+ for (var i = 0; i < commitData.length; i++) {
22
+ var c = commitData[i];
23
+ if (c.cx === commit.cx && c.cy === commit.cy && c !== commit) {
24
+ overlapped = c;
25
+ break;
26
+ }
27
+ }
28
+
29
+ if (overlapped) {
30
+ var oParent = view.getCommit(overlapped.parent),
31
+ parent = view.getCommit(commit.parent);
32
+
33
+ if (overlapped.cy < baseLine) {
34
+ overlapped = oParent.cy < parent.cy ? overlapped : commit;
35
+ overlapped.cy -= shift;
36
+ } else {
37
+ overlapped = oParent.cy > parent.cy ? overlapped : commit;
38
+ overlapped.cy += shift;
39
+ }
40
+
41
+ preventOverlap(overlapped, view);
42
+ }
43
+ };
44
+
45
+ applyBranchlessClass = function (selection) {
46
+ if (selection.empty()) {
47
+ return;
48
+ }
49
+
50
+ selection.classed('branchless', function (d) {
51
+ return d.branchless;
52
+ });
53
+
54
+ if (selection.classed('commit-pointer')) {
55
+ selection.attr('marker-end', function (d) {
56
+ return d.branchless ? FADED_MARKER_END : REG_MARKER_END;
57
+ });
58
+ } else if (selection.classed('merge-pointer')) {
59
+ selection.attr('marker-end', function (d) {
60
+ return d.branchless ? FADED_MARKER_END : MERGE_MARKER_END;
61
+ });
62
+ }
63
+ };
64
+
65
+ cx = function (commit, view) {
66
+ var parent = view.getCommit(commit.parent),
67
+ parentCX = parent.cx;
68
+
69
+ if (typeof commit.parent2 === 'string') {
70
+ var parent2 = view.getCommit(commit.parent2);
71
+
72
+ parentCX = parent.cx > parent2.cx ? parent.cx : parent2.cx;
73
+ }
74
+
75
+ return parentCX + (view.commitRadius * 4.5);
76
+ };
77
+
78
+ cy = function (commit, view) {
79
+ var parent = view.getCommit(commit.parent),
80
+ parentCY = parent.cy,
81
+ baseLine = view.baseLine,
82
+ shift = view.commitRadius * 4.5,
83
+ branches = [], // count the existing branches
84
+ branchIndex = 0;
85
+
86
+ for (var i = 0; i < view.commitData.length; i++) {
87
+ var d = view.commitData[i];
88
+
89
+ if (d.parent === commit.parent) {
90
+ branches.push(d.id);
91
+ }
92
+ }
93
+
94
+ branchIndex = branches.indexOf(commit.id);
95
+
96
+ if (parentCY === baseLine) {
97
+ var direction = 1;
98
+ for (var bi = 0; bi < branchIndex; bi++) {
99
+ direction *= -1;
100
+ }
101
+
102
+ shift *= Math.ceil(branchIndex / 2);
103
+
104
+ return parentCY + (shift * direction);
105
+ }
106
+
107
+ if (parentCY < baseLine) {
108
+ return parentCY - (shift * branchIndex);
109
+ } else if (parentCY > baseLine) {
110
+ return parentCY + (shift * branchIndex);
111
+ }
112
+ };
113
+
114
+ fixCirclePosition = function (selection) {
115
+ selection
116
+ .attr('cx', function (d) {
117
+ return d.cx;
118
+ })
119
+ .attr('cy', function (d) {
120
+ return d.cy;
121
+ });
122
+ };
123
+
124
+ // calculates the x1 point for commit pointer lines
125
+ px1 = function (commit, view, pp) {
126
+ pp = pp || 'parent';
127
+
128
+ var parent = view.getCommit(commit[pp]),
129
+ startCX = commit.cx,
130
+ diffX = startCX - parent.cx,
131
+ diffY = parent.cy - commit.cy,
132
+ length = Math.sqrt((diffX * diffX) + (diffY * diffY));
133
+
134
+ return startCX - (view.pointerMargin * (diffX / length));
135
+ };
136
+
137
+ // calculates the y1 point for commit pointer lines
138
+ py1 = function (commit, view, pp) {
139
+ pp = pp || 'parent';
140
+
141
+ var parent = view.getCommit(commit[pp]),
142
+ startCY = commit.cy,
143
+ diffX = commit.cx - parent.cx,
144
+ diffY = parent.cy - startCY,
145
+ length = Math.sqrt((diffX * diffX) + (diffY * diffY));
146
+
147
+ return startCY + (view.pointerMargin * (diffY / length));
148
+ };
149
+
150
+ fixPointerStartPosition = function (selection, view) {
151
+ selection.attr('x1', function (d) {
152
+ return px1(d, view);
153
+ }).attr('y1', function (d) {
154
+ return py1(d, view);
155
+ });
156
+ };
157
+
158
+ px2 = function (commit, view, pp) {
159
+ pp = pp || 'parent';
160
+
161
+ var parent = view.getCommit(commit[pp]),
162
+ endCX = parent.cx,
163
+ diffX = commit.cx - endCX,
164
+ diffY = parent.cy - commit.cy,
165
+ length = Math.sqrt((diffX * diffX) + (diffY * diffY));
166
+
167
+ return endCX + (view.pointerMargin * 1.2 * (diffX / length));
168
+ };
169
+
170
+ py2 = function (commit, view, pp) {
171
+ pp = pp || 'parent';
172
+
173
+ var parent = view.getCommit(commit[pp]),
174
+ endCY = parent.cy,
175
+ diffX = commit.cx - parent.cx,
176
+ diffY = endCY - commit.cy,
177
+ length = Math.sqrt((diffX * diffX) + (diffY * diffY));
178
+
179
+ return endCY - (view.pointerMargin * 1.2 * (diffY / length));
180
+ };
181
+
182
+ fixPointerEndPosition = function (selection, view) {
183
+ selection.attr('x2', function (d) {
184
+ return px2(d, view);
185
+ }).attr('y2', function (d) {
186
+ return py2(d, view);
187
+ });
188
+ };
189
+
190
+ fixIdPosition = function (selection, view) {
191
+ selection.attr('x', function (d) {
192
+ return d.cx;
193
+ }).attr('y', function (d) {
194
+ return d.cy + view.commitRadius + 14;
195
+ });
196
+ };
197
+
198
+ tagY = function tagY(t, view) {
199
+ var commit = view.getCommit(t.commit),
200
+ commitCY = commit.cy,
201
+ tags = commit.tags,
202
+ tagIndex = tags.indexOf(t.name);
203
+
204
+ if (tagIndex === -1) {
205
+ tagIndex = tags.length;
206
+ }
207
+
208
+ if (commitCY < (view.baseLine)) {
209
+ return commitCY - 45 - (tagIndex * 25);
210
+ } else {
211
+ return commitCY + 40 + (tagIndex * 25);
212
+ }
213
+ };
214
+
215
+ /**
216
+ * @class HistoryView
217
+ * @constructor
218
+ */
219
+ function HistoryView(config) {
220
+ var commitData = config.commitData || [],
221
+ commit;
222
+
223
+ for (var i = 0; i < commitData.length; i++) {
224
+ commit = commitData[i];
225
+ !commit.parent && (commit.parent = 'initial');
226
+ !commit.tags && (commit.tags = []);
227
+ }
228
+
229
+ this.name = config.name || 'UnnamedHistoryView';
230
+ this.commitData = commitData;
231
+
232
+ this.branches = [];
233
+ this.currentBranch = config.currentBranch || 'master';
234
+
235
+ this.width = config.width || 886;
236
+ this.height = config.height || 400;
237
+ this.baseLine = this.height * (config.baseLine || 0.6);
238
+
239
+ this.commitRadius = config.commitRadius || 20;
240
+ this.pointerMargin = this.commitRadius * 1.3;
241
+
242
+ this.isRemote = typeof config.remoteName === 'string';
243
+ this.remoteName = config.remoteName;
244
+
245
+ this.initialCommit = {
246
+ id: 'initial',
247
+ parent: null,
248
+ cx: -(this.commitRadius * 2),
249
+ cy: this.baseLine
250
+ };
251
+ }
252
+
253
+ HistoryView.generateId = function () {
254
+ return Math.floor((1 + Math.random()) * 0x10000000).toString(16).substring(1);
255
+ };
256
+
257
+ HistoryView.prototype = {
258
+ /**
259
+ * @method getCommit
260
+ * @param ref {String} the id or a tag name that refers to the commit
261
+ * @return {Object} the commit datum object
262
+ */
263
+ getCommit: function getCommit(ref) {
264
+ var commitData = this.commitData,
265
+ headMatcher = /HEAD(\^+)/.exec(ref),
266
+ matchedCommit = null;
267
+
268
+ if (ref === 'initial') {
269
+ return this.initialCommit;
270
+ }
271
+
272
+ if (headMatcher) {
273
+ ref = 'HEAD';
274
+ }
275
+
276
+ for (var i = 0; i < commitData.length; i++) {
277
+ var commit = commitData[i];
278
+ if (commit === ref) {
279
+ matchedCommit = commit;
280
+ break;
281
+ }
282
+
283
+ if (commit.id === ref) {
284
+ matchedCommit = commit;
285
+ break;
286
+ }
287
+
288
+ if (commit.tags.indexOf(ref) >= 0) {
289
+ matchedCommit = commit;
290
+ break;
291
+ }
292
+ }
293
+
294
+ if (headMatcher && matchedCommit) {
295
+ for (var h = 0; h < headMatcher[1].length; h++) {
296
+ matchedCommit = getCommit.call(this, matchedCommit.parent);
297
+ }
298
+ }
299
+
300
+ return matchedCommit;
301
+ },
302
+
303
+ /**
304
+ * @method getCircle
305
+ * @param ref {String} the id or a tag name that refers to the commit
306
+ * @return {d3 Selection} the d3 selected SVG circle
307
+ */
308
+ getCircle: function (ref) {
309
+ var circle = this.svg.select('#' + this.name + '-' + ref),
310
+ commit;
311
+
312
+ if (circle && !circle.empty()) {
313
+ return circle;
314
+ }
315
+
316
+ commit = this.getCommit(ref);
317
+
318
+ if (!commit) {
319
+ return null;
320
+ }
321
+
322
+ return this.svg.select('#' + this.name + '-' + commit.id);
323
+ },
324
+
325
+ getCircles: function () {
326
+ return this.svg.selectAll('circle.commit');
327
+ },
328
+
329
+ /**
330
+ * @method render
331
+ * @param container {String} selector for the container to render the SVG into
332
+ */
333
+ render: function (container) {
334
+ var svgContainer, svg;
335
+
336
+ svgContainer = container.append('div')
337
+ .classed('svg-container', true)
338
+ .classed('remote-container', this.isRemote);
339
+
340
+ svg = svgContainer.append('svg:svg');
341
+
342
+ svg.attr('id', this.name)
343
+ .attr('width', this.width)
344
+ .attr('height', this.height);
345
+
346
+ if (this.isRemote) {
347
+ svg.append('svg:text')
348
+ .classed('remote-name-display', true)
349
+ .text(this.remoteName)
350
+ .attr('x', 10)
351
+ .attr('y', 25);
352
+ } else {
353
+ svg.append('svg:text')
354
+ .classed('remote-name-display', true)
355
+ .text('Local Repository')
356
+ .attr('x', 10)
357
+ .attr('y', 25);
358
+
359
+ svg.append('svg:text')
360
+ .classed('current-branch-display', true)
361
+ .attr('x', 10)
362
+ .attr('y', 45);
363
+ }
364
+
365
+ this.svgContainer = svgContainer;
366
+ this.svg = svg;
367
+ this.arrowBox = svg.append('svg:g').classed('pointers', true);
368
+ this.commitBox = svg.append('svg:g').classed('commits', true);
369
+ this.tagBox = svg.append('svg:g').classed('tags', true);
370
+
371
+ this.renderCommits();
372
+
373
+ this._setCurrentBranch(this.currentBranch);
374
+ },
375
+
376
+ destroy: function () {
377
+ this.svg.remove();
378
+ this.svgContainer.remove();
379
+
380
+ for (var prop in this) {
381
+ if (this.hasOwnProperty(prop)) {
382
+ this[prop] = null;
383
+ }
384
+ }
385
+ },
386
+
387
+ _calculatePositionData: function () {
388
+ for (var i = 0; i < this.commitData.length; i++) {
389
+ var commit = this.commitData[i];
390
+ commit.cx = cx(commit, this);
391
+ commit.cy = cy(commit, this);
392
+ preventOverlap(commit, this);
393
+ }
394
+ },
395
+
396
+ renderCommits: function () {
397
+ this._calculatePositionData();
398
+ this._calculatePositionData(); // do this twice to make sure
399
+ this._renderCircles();
400
+ this._renderPointers();
401
+ this._renderMergePointers();
402
+ this._renderIdLabels();
403
+ this.checkout(this.currentBranch);
404
+ },
405
+
406
+ _renderCircles: function () {
407
+ var view = this,
408
+ existingCircles,
409
+ newCircles;
410
+
411
+ existingCircles = this.commitBox.selectAll('circle.commit')
412
+ .data(this.commitData, function (d) { return d.id; })
413
+ .attr('id', function (d) {
414
+ return view.name + '-' + d.id;
415
+ })
416
+ .classed('reverted', function (d) {
417
+ return d.reverted;
418
+ })
419
+ .classed('rebased', function (d) {
420
+ return d.rebased;
421
+ });
422
+
423
+ existingCircles.transition()
424
+ .duration(500)
425
+ .call(fixCirclePosition);
426
+
427
+ newCircles = existingCircles.enter()
428
+ .append('svg:circle')
429
+ .attr('id', function (d) {
430
+ return view.name + '-' + d.id;
431
+ })
432
+ .classed('commit', true)
433
+ .classed('merge-commit', function (d) {
434
+ return typeof d.parent2 === 'string';
435
+ })
436
+ .call(fixCirclePosition)
437
+ .attr('r', 1)
438
+ .transition()
439
+ .duration(500)
440
+ .attr('r', this.commitRadius);
441
+
442
+ },
443
+
444
+ _renderPointers: function () {
445
+ var view = this,
446
+ existingPointers,
447
+ newPointers;
448
+
449
+ existingPointers = this.arrowBox.selectAll('line.commit-pointer')
450
+ .data(this.commitData, function (d) { return d.id; })
451
+ .attr('id', function (d) {
452
+ return view.name + '-' + d.id + '-to-' + d.parent;
453
+ });
454
+
455
+ existingPointers.transition()
456
+ .duration(500)
457
+ .call(fixPointerStartPosition, view)
458
+ .call(fixPointerEndPosition, view);
459
+
460
+ newPointers = existingPointers.enter()
461
+ .append('svg:line')
462
+ .attr('id', function (d) {
463
+ return view.name + '-' + d.id + '-to-' + d.parent;
464
+ })
465
+ .classed('commit-pointer', true)
466
+ .call(fixPointerStartPosition, view)
467
+ .attr('x2', function () { return d3.select(this).attr('x1'); })
468
+ .attr('y2', function () { return d3.select(this).attr('y1'); })
469
+ .attr('marker-end', REG_MARKER_END)
470
+ .transition()
471
+ .duration(500)
472
+ .call(fixPointerEndPosition, view);
473
+ },
474
+
475
+ _renderMergePointers: function () {
476
+ var view = this,
477
+ mergeCommits = [],
478
+ existingPointers, newPointers;
479
+
480
+ for (var i = 0; i < this.commitData.length; i++) {
481
+ var commit = this.commitData[i];
482
+ if (typeof commit.parent2 === 'string') {
483
+ mergeCommits.push(commit);
484
+ }
485
+ }
486
+
487
+ existingPointers = this.arrowBox.selectAll('polyline.merge-pointer')
488
+ .data(mergeCommits, function (d) { return d.id; })
489
+ .attr('id', function (d) {
490
+ return view.name + '-' + d.id + '-to-' + d.parent2;
491
+ });
492
+
493
+ existingPointers.transition().duration(500)
494
+ .attr('points', function (d) {
495
+ var p1 = px1(d, view, 'parent2') + ',' + py1(d, view, 'parent2'),
496
+ p2 = px2(d, view, 'parent2') + ',' + py2(d, view, 'parent2');
497
+
498
+ return [p1, p2].join(' ');
499
+ });
500
+
501
+ newPointers = existingPointers.enter()
502
+ .append('svg:polyline')
503
+ .attr('id', function (d) {
504
+ return view.name + '-' + d.id + '-to-' + d.parent2;
505
+ })
506
+ .classed('merge-pointer', true)
507
+ .attr('points', function (d) {
508
+ var x1 = px1(d, view, 'parent2'),
509
+ y1 = py1(d, view, 'parent2'),
510
+ p1 = x1 + ',' + y1;
511
+
512
+ return [p1, p1].join(' ');
513
+ })
514
+ .attr('marker-end', MERGE_MARKER_END)
515
+ .transition()
516
+ .duration(500)
517
+ .attr('points', function (d) {
518
+ var points = d3.select(this).attr('points').split(' '),
519
+ x2 = px2(d, view, 'parent2'),
520
+ y2 = py2(d, view, 'parent2');
521
+
522
+ points[1] = x2 + ',' + y2;
523
+ return points.join(' ');
524
+ });
525
+ },
526
+
527
+ _renderIdLabels: function () {
528
+ var view = this,
529
+ existingLabels,
530
+ newLabels;
531
+
532
+ existingLabels = this.commitBox.selectAll('text.id-label')
533
+ .data(this.commitData, function (d) { return d.id; })
534
+ .text(function (d) { return d.id + '..'; });
535
+
536
+ existingLabels.transition().call(fixIdPosition, view);
537
+
538
+ newLabels = existingLabels.enter()
539
+ .insert('svg:text', ':first-child')
540
+ .classed('id-label', true)
541
+ .text(function (d) { return d.id + '..'; })
542
+ .call(fixIdPosition, view);
543
+ },
544
+
545
+ _parseTagData: function () {
546
+ var tagData = [], i,
547
+ headCommit = null;
548
+
549
+ for (i = 0; i < this.commitData.length; i++) {
550
+ var c = this.commitData[i];
551
+
552
+ for (var t = 0; t < c.tags.length; t++) {
553
+ var tagName = c.tags[t];
554
+ if (tagName.toUpperCase() === 'HEAD') {
555
+ headCommit = c;
556
+ } else if (this.branches.indexOf(tagName) === -1) {
557
+ this.branches.push(tagName);
558
+ }
559
+
560
+ tagData.push({name: tagName, commit: c.id});
561
+ }
562
+ }
563
+
564
+ if (!headCommit) {
565
+ headCommit = this.getCommit(this.currentBranch);
566
+ headCommit.tags.push('HEAD');
567
+ tagData.push({name: 'HEAD', commit: headCommit.id});
568
+ }
569
+
570
+ // find out which commits are not branchless
571
+
572
+
573
+ return tagData;
574
+ },
575
+
576
+ _markBranchlessCommits: function () {
577
+ var branch, commit, parent, parent2, c, b;
578
+
579
+ // first mark every commit as branchless
580
+ for (c = 0; c < this.commitData.length; c++) {
581
+ this.commitData[c].branchless = true;
582
+ }
583
+
584
+ for (b = 0; b < this.branches.length; b++) {
585
+ branch = this.branches[b];
586
+ if (branch.indexOf('/') === -1) {
587
+ commit = this.getCommit(branch);
588
+ parent = this.getCommit(commit.parent);
589
+ parent2 = this.getCommit(commit.parent2);
590
+
591
+ commit.branchless = false;
592
+
593
+ while (parent) {
594
+ parent.branchless = false;
595
+ parent = this.getCommit(parent.parent);
596
+ }
597
+
598
+ // just in case this is a merge commit
599
+ while (parent2) {
600
+ parent2.branchless = false;
601
+ parent2 = this.getCommit(parent2.parent);
602
+ }
603
+ }
604
+ }
605
+
606
+ this.svg.selectAll('circle.commit').call(applyBranchlessClass);
607
+ this.svg.selectAll('line.commit-pointer').call(applyBranchlessClass);
608
+ this.svg.selectAll('polyline.merge-pointer').call(applyBranchlessClass);
609
+ },
610
+
611
+ renderTags: function () {
612
+ var view = this,
613
+ tagData = this._parseTagData(),
614
+ existingTags, newTags;
615
+
616
+ existingTags = this.tagBox.selectAll('g.branch-tag')
617
+ .data(tagData, function (d) { return d.name; });
618
+
619
+ existingTags.exit().remove();
620
+
621
+ existingTags.select('rect')
622
+ .transition()
623
+ .duration(500)
624
+ .attr('y', function (d) { return tagY(d, view); })
625
+ .attr('x', function (d) {
626
+ var commit = view.getCommit(d.commit),
627
+ width = Number(d3.select(this).attr('width'));
628
+
629
+ return commit.cx - (width / 2);
630
+ });
631
+
632
+ existingTags.select('text')
633
+ .transition()
634
+ .duration(500)
635
+ .attr('y', function (d) { return tagY(d, view) + 14; })
636
+ .attr('x', function (d) {
637
+ var commit = view.getCommit(d.commit);
638
+ return commit.cx;
639
+ });
640
+
641
+ newTags = existingTags.enter()
642
+ .append('g')
643
+ .attr('class', function (d) {
644
+ var classes = 'branch-tag';
645
+ if (d.name.indexOf('/') >= 0) {
646
+ classes += ' remote-branch';
647
+ } else if (d.name.toUpperCase() === 'HEAD') {
648
+ classes += ' head-tag';
649
+ }
650
+ return classes;
651
+ });
652
+
653
+ newTags.append('svg:rect')
654
+ .attr('width', function (d) {
655
+ return (d.name.length * 6) + 10;
656
+ })
657
+ .attr('height', 20)
658
+ .attr('y', function (d) { return tagY(d, view); })
659
+ .attr('x', function (d) {
660
+ var commit = view.getCommit(d.commit),
661
+ width = Number(d3.select(this).attr('width'));
662
+
663
+ return commit.cx - (width / 2);
664
+ });
665
+
666
+ newTags.append('svg:text')
667
+ .text(function (d) { return d.name; })
668
+ .attr('y', function (d) {
669
+ return tagY(d, view) + 14;
670
+ })
671
+ .attr('x', function (d) {
672
+ var commit = view.getCommit(d.commit);
673
+ return commit.cx;
674
+ });
675
+
676
+ this._markBranchlessCommits();
677
+ },
678
+
679
+ _setCurrentBranch: function (branch) {
680
+ var display = this.svg.select('text.current-branch-display'),
681
+ text = 'Current Branch: ';
682
+
683
+ if (branch && branch.indexOf('/') === -1) {
684
+ text += branch;
685
+ this.currentBranch = branch;
686
+ } else {
687
+ text += ' DETACHED HEAD';
688
+ this.currentBranch = null;
689
+ }
690
+
691
+ display.text(text);
692
+ },
693
+
694
+ moveTag: function (tag, ref) {
695
+ var currentLoc = this.getCommit(tag),
696
+ newLoc = this.getCommit(ref);
697
+
698
+ if (currentLoc) {
699
+ currentLoc.tags.splice(currentLoc.tags.indexOf(tag), 1);
700
+ }
701
+
702
+ newLoc.tags.push(tag);
703
+ return this;
704
+ },
705
+
706
+ /**
707
+ * @method isAncestor
708
+ * @param ref1
709
+ * @param ref2
710
+ * @return {Boolean} whether or not ref1 is an ancestor of ref2
711
+ */
712
+ isAncestor: function isAncestor(ref1, ref2) {
713
+ var currentCommit = this.getCommit(ref1),
714
+ targetTree = this.getCommit(ref2),
715
+ inTree = false,
716
+ additionalTrees = [];
717
+
718
+ if (!currentCommit) {
719
+ return false;
720
+ }
721
+
722
+ while (targetTree) {
723
+ if (targetTree.id === currentCommit.id) {
724
+ inTree = true;
725
+ targetTree = null;
726
+ } else {
727
+ if (targetTree.parent2) {
728
+ additionalTrees.push(targetTree.parent2);
729
+ }
730
+ targetTree = this.getCommit(targetTree.parent);
731
+ }
732
+ }
733
+
734
+ if (inTree) {
735
+ return true;
736
+ }
737
+
738
+ for (var i = 0; i < additionalTrees.length; i++) {
739
+ inTree = isAncestor.call(this, currentCommit, additionalTrees[i]);
740
+ if (inTree) break;
741
+ }
742
+
743
+ return inTree;
744
+ },
745
+
746
+ commit: function (commit) {
747
+ commit = commit || {};
748
+
749
+ !commit.id && (commit.id = HistoryView.generateId());
750
+ !commit.tags && (commit.tags = []);
751
+
752
+ if (!commit.parent) {
753
+ if (!this.currentBranch) {
754
+ throw new Error('Not a good idea to make commits while in a detached HEAD state.');
755
+ }
756
+
757
+ commit.parent = this.getCommit(this.currentBranch).id;
758
+ }
759
+
760
+ this.commitData.push(commit);
761
+ this.moveTag(this.currentBranch, commit.id);
762
+
763
+ this.renderCommits();
764
+
765
+ this.checkout(this.currentBranch);
766
+ return this;
767
+ },
768
+
769
+ branch: function (name) {
770
+ if (!name || name.trim() === '') {
771
+ throw new Error('You need to give a branch name.');
772
+ }
773
+
774
+ if (name === 'HEAD') {
775
+ throw new Error('You cannot name your branch "HEAD".');
776
+ }
777
+
778
+ if (name.indexOf(' ') > -1) {
779
+ throw new Error('Branch names cannot contain spaces.');
780
+ }
781
+
782
+ if (this.branches.indexOf(name) > -1) {
783
+ throw new Error('Branch "' + name + '" already exists.');
784
+ }
785
+
786
+ this.getCommit('HEAD').tags.push(name);
787
+ this.renderTags();
788
+ return this;
789
+ },
790
+
791
+ deleteBranch: function (name) {
792
+ var branchIndex,
793
+ commit;
794
+
795
+ if (!name || name.trim() === '') {
796
+ throw new Error('You need to give a branch name.');
797
+ }
798
+
799
+ if (name === this.currentBranch) {
800
+ throw new Error('Cannot delete the currently checked-out branch.');
801
+ }
802
+
803
+ branchIndex = this.branches.indexOf(name);
804
+
805
+ if (branchIndex === -1) {
806
+ throw new Error('That branch doesn\'t exist.');
807
+ }
808
+
809
+ this.branches.splice(branchIndex, 1);
810
+ commit = this.getCommit(name);
811
+ branchIndex = commit.tags.indexOf(name);
812
+
813
+ if (branchIndex > -1) {
814
+ commit.tags.splice(branchIndex, 1);
815
+ }
816
+
817
+ this.renderTags();
818
+ },
819
+
820
+ checkout: function (ref) {
821
+ var commit = this.getCommit(ref);
822
+
823
+ if (!commit) {
824
+ throw new Error('Cannot find commit: ' + ref);
825
+ }
826
+
827
+ var previousHead = this.getCircle('HEAD'),
828
+ newHead = this.getCircle(commit.id);
829
+
830
+ if (previousHead && !previousHead.empty()) {
831
+ previousHead.classed('checked-out', false);
832
+ }
833
+
834
+ this._setCurrentBranch(ref === commit.id ? null : ref);
835
+ this.moveTag('HEAD', commit.id);
836
+ this.renderTags();
837
+
838
+ newHead.classed('checked-out', true);
839
+
840
+ return this;
841
+ },
842
+
843
+ reset: function (ref) {
844
+ var commit = this.getCommit(ref);
845
+
846
+ if (!commit) {
847
+ throw new Error('Cannot find ref: ' + ref);
848
+ }
849
+
850
+ if (this.currentBranch) {
851
+ this.moveTag(this.currentBranch, commit.id);
852
+ this.checkout(this.currentBranch);
853
+ } else {
854
+ this.checkout(commit.id);
855
+ }
856
+
857
+ return this;
858
+ },
859
+
860
+ revert: function (ref) {
861
+ var commit = this.getCommit(ref);
862
+
863
+ if (!commit) {
864
+ throw new Error('Cannot find ref: ' + ref);
865
+ }
866
+
867
+ if (this.isAncestor(commit, 'HEAD')) {
868
+ commit.reverted = true;
869
+ this.commit({reverts: commit.id});
870
+ } else {
871
+ throw new Error(ref + 'is not an ancestor of HEAD.');
872
+ }
873
+ },
874
+
875
+ fastForward: function (ref) {
876
+ var targetCommit = this.getCommit(ref);
877
+
878
+ if (this.currentBranch) {
879
+ this.moveTag(this.currentBranch, targetCommit.id);
880
+ this.checkout(this.currentBranch);
881
+ } else {
882
+ this.checkout(targetCommit.id);
883
+ }
884
+ },
885
+
886
+ merge: function (ref) {
887
+ var mergeTarget = this.getCommit(ref),
888
+ currentCommit = this.getCommit('HEAD');
889
+
890
+ if (!mergeTarget) {
891
+ throw new Error('Cannot find ref: ' + ref);
892
+ }
893
+
894
+ if (currentCommit.id === mergeTarget.id) {
895
+ throw new Error('Already up-to-date.');
896
+ } else if (currentCommit.parent2 === mergeTarget.id) {
897
+ throw new Error('Already up-to-date.');
898
+ } else if (this.isAncestor(currentCommit, mergeTarget)) {
899
+ this.fastForward(mergeTarget);
900
+ return 'Fast-Forward';
901
+ } else {
902
+ this.commit({parent2: mergeTarget.id});
903
+ }
904
+ },
905
+
906
+ rebase: function (ref) {
907
+ var rebaseTarget = this.getCommit(ref),
908
+ currentCommit = this.getCommit('HEAD'),
909
+ isCommonAncestor,
910
+ rebaseTreeLoc,
911
+ toRebase = [], rebasedCommit,
912
+ remainingHusk;
913
+
914
+ if (!rebaseTarget) {
915
+ throw new Error('Cannot find ref: ' + ref);
916
+ }
917
+
918
+ if (currentCommit.id === rebaseTarget.id) {
919
+ throw new Error('Already up-to-date.');
920
+ } else if (currentCommit.parent2 === rebaseTarget.id) {
921
+ throw new Error('Already up-to-date.');
922
+ }
923
+
924
+ isCommonAncestor = this.isAncestor(currentCommit, rebaseTarget);
925
+
926
+ if (isCommonAncestor) {
927
+ this.fastForward(rebaseTarget);
928
+ return 'Fast-Forward';
929
+ }
930
+
931
+ rebaseTreeLoc = rebaseTarget.id
932
+
933
+ while (!isCommonAncestor) {
934
+ toRebase.unshift(currentCommit);
935
+ currentCommit = this.getCommit(currentCommit.parent);
936
+ isCommonAncestor = this.isAncestor(currentCommit, rebaseTarget);
937
+ }
938
+
939
+ for (var i = 0; i < toRebase.length; i++) {
940
+ rebasedCommit = toRebase[i];
941
+
942
+ remainingHusk = {
943
+ id: rebasedCommit.id,
944
+ parent: rebasedCommit.parent,
945
+ tags: []
946
+ };
947
+
948
+ for (var t = 0; t < rebasedCommit.tags.length; t++) {
949
+ var tagName = rebasedCommit.tags[t];
950
+ if (tagName !== this.currentBranch && tagName !== 'HEAD') {
951
+ remainingHusk.tags.unshift(tagName);
952
+ }
953
+ }
954
+
955
+ this.commitData.push(remainingHusk);
956
+
957
+ rebasedCommit.parent = rebaseTreeLoc;
958
+ rebaseTreeLoc = HistoryView.generateId()
959
+ rebasedCommit.id = rebaseTreeLoc;
960
+ rebasedCommit.tags.length = 0;
961
+ rebasedCommit.rebased = true;
962
+ }
963
+
964
+ if (this.currentBranch) {
965
+ rebasedCommit.tags.push(this.currentBranch);
966
+ }
967
+
968
+ this.renderCommits();
969
+
970
+ if (this.currentBranch) {
971
+ this.checkout(this.currentBranch);
972
+ } else {
973
+ this.checkout(rebasedCommit.id);
974
+ }
975
+ }
976
+ };
977
+
978
+ return HistoryView;
979
+ });