├── images
├── prompt.gif
├── remote.gif
├── cherry-pick.gif
└── viz-rebase.gif
├── css
├── images
│ ├── ui-icons_444444_256x240.png
│ ├── ui-icons_555555_256x240.png
│ ├── ui-icons_777620_256x240.png
│ ├── ui-icons_777777_256x240.png
│ ├── ui-icons_cc0000_256x240.png
│ └── ui-icons_ffffff_256x240.png
├── 1140.css
├── explaingit.css
├── normalize.css
└── jquery-ui.min.css
├── LICENSE.md
├── js
├── main.js
├── explaingit.js
├── demos.js
├── vendor
│ ├── require.min.js
│ └── yargs-parser.js
├── controlbox.js
└── historyview.js
├── memtest.html
├── README.md
├── index.html
└── examples
└── merging.md
/images/prompt.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-school/visualizing-git/HEAD/images/prompt.gif
--------------------------------------------------------------------------------
/images/remote.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-school/visualizing-git/HEAD/images/remote.gif
--------------------------------------------------------------------------------
/images/cherry-pick.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-school/visualizing-git/HEAD/images/cherry-pick.gif
--------------------------------------------------------------------------------
/images/viz-rebase.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-school/visualizing-git/HEAD/images/viz-rebase.gif
--------------------------------------------------------------------------------
/css/images/ui-icons_444444_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-school/visualizing-git/HEAD/css/images/ui-icons_444444_256x240.png
--------------------------------------------------------------------------------
/css/images/ui-icons_555555_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-school/visualizing-git/HEAD/css/images/ui-icons_555555_256x240.png
--------------------------------------------------------------------------------
/css/images/ui-icons_777620_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-school/visualizing-git/HEAD/css/images/ui-icons_777620_256x240.png
--------------------------------------------------------------------------------
/css/images/ui-icons_777777_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-school/visualizing-git/HEAD/css/images/ui-icons_777777_256x240.png
--------------------------------------------------------------------------------
/css/images/ui-icons_cc0000_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-school/visualizing-git/HEAD/css/images/ui-icons_cc0000_256x240.png
--------------------------------------------------------------------------------
/css/images/ui-icons_ffffff_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-school/visualizing-git/HEAD/css/images/ui-icons_ffffff_256x240.png
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Wei Wang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/js/main.js:
--------------------------------------------------------------------------------
1 | if (!String.prototype.trim) {
2 | String.prototype.trim = function() {
3 | return this.replace(/^\s+|\s+$/g, '');
4 | };
5 | }
6 |
7 | if (!Array.isArray) {
8 | Array.isArray = function(vArg) {
9 | return Object.prototype.toString.call(vArg) === "[object Array]";
10 | };
11 | }
12 |
13 | if (!Array.prototype.indexOf) {
14 | Array.prototype.indexOf = function(searchElement /*, fromIndex */ ) {
15 | "use strict";
16 | if (this == null) {
17 | throw new TypeError();
18 | }
19 | var t = Object(this);
20 | var len = t.length >>> 0;
21 | if (len === 0) {
22 | return -1;
23 | }
24 | var n = 0;
25 | if (arguments.length > 1) {
26 | n = Number(arguments[1]);
27 | if (n != n) { // shortcut for verifying if it's NaN
28 | n = 0;
29 | } else if (n != 0 && n != Infinity && n != -Infinity) {
30 | n = (n > 0 || -1) * Math.floor(Math.abs(n));
31 | }
32 | }
33 | if (n >= len) {
34 | return -1;
35 | }
36 | var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
37 | for (; k < len; k++) {
38 | if (k in t && t[k] === searchElement) {
39 | return k;
40 | }
41 | }
42 | return -1;
43 | }
44 | }
45 |
46 | require.config({
47 | paths: {
48 | 'd3': 'vendor/d3'
49 | },
50 | shim: {
51 | 'd3': {
52 | exports: 'd3'
53 | }
54 | }
55 | });
56 |
--------------------------------------------------------------------------------
/memtest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Explain Git with D3
6 |
7 |
8 |
9 |
10 |
11 | Memtest Page
12 | This page exists to help me find any memory leaks that may happen.
13 |
14 | explain git memtest
15 |
16 |
17 | Create and destroy many git history views and control boxes to find memory leaks.
18 |
19 |
20 |
21 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Visualize Git
2 | =============
3 |
4 | Git is an amazingly powerful tool — and it can be amazingly confusing. Demystify Git commands with visualizations powered by D3. Give it a try at [http://git-school.github.io/visualizing-git/](http://git-school.github.io/visualizing-git/)!
5 |
6 | 
7 |
8 | [Visualize Git](http://git-school.github.io/visualizing-git/) illustrates what's going on underneath the hood when you use common Git operations. You'll see what exactly is happening to your commit graph. We aim to support all the most basic git operations, including interacting with remotes.
9 |
10 | Here are some examples of the fun things you can do with it:
11 |
12 | ## Rebase
13 | 
14 |
15 | ## Cherry-pick
16 | 
17 |
18 | ## Push/pull
19 | 
20 |
21 | ## Supported operations
22 |
23 | Type `help` in the command box to see a list of supported operations
24 |
25 | `pres()` = Turn on presenter mode
26 | `undo` = Undo the last git command
27 | `redo` = Redo the last undone git command
28 | `mode` = Change mode (`local` or `remote`)
29 | `clear` = Clear the history pane and reset the visualization
30 |
31 | Available Git Commands:
32 | ```
33 | git branch
34 | git checkout
35 | git cherry_pick
36 | git commit
37 | git fetch
38 | git log
39 | git merge
40 | git pull
41 | git push
42 | git rebase
43 | git reflog
44 | git reset
45 | git rev_parse
46 | git revert
47 | git tag
48 | ```
49 |
50 |
51 | We hope you find this tool useful! Issues and pull requests are welcome! Enjoy! :sparkles:
52 |
53 | Based on the awesome work done by [@onlywei](https://github.com/onlywei/explain-git-with-d3) :bow:
54 |
--------------------------------------------------------------------------------
/js/explaingit.js:
--------------------------------------------------------------------------------
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 args = Object.create(_args),
10 | name = prefix + args.name,
11 | containerId = name + '-Container',
12 | container = d3.select('#' + containerId),
13 | playground = container.select('.playground-container'),
14 | historyView, originView = null,
15 | controlBox;
16 |
17 | container.style('display', 'block');
18 |
19 | args.name = name;
20 | args.savedState = args.hvSavedState
21 | historyView = new HistoryView(args);
22 | window.hv = historyView;
23 |
24 | if (args.originData) {
25 | originView = new HistoryView({
26 | name: name + '-Origin',
27 | width: 300,
28 | height: 400,
29 | commitRadius: args.commitRadius,
30 | remoteName: 'origin',
31 | commitData: args.originData,
32 | savedState: args.ovSavedState
33 | });
34 |
35 | originView.render(playground);
36 | window.ov = originView;
37 | }
38 |
39 | controlBox = new ControlBox({
40 | historyView: historyView,
41 | originView: originView,
42 | initialMessage: args.initialMessage,
43 | undoHistory: args.undoHistory
44 | });
45 | window.cb = controlBox;
46 |
47 | controlBox.render(playground);
48 | historyView.render(playground);
49 |
50 | openSandBoxes.push({
51 | hv: historyView,
52 | cb: controlBox,
53 | container: container
54 | });
55 | };
56 |
57 | reset = function() {
58 | for (var i = 0; i < openSandBoxes.length; i++) {
59 | var osb = openSandBoxes[i];
60 | osb.hv.destroy();
61 | osb.cb.destroy();
62 | osb.container.style('display', 'none');
63 | }
64 |
65 | openSandBoxes.length = 0;
66 | d3.selectAll('a.openswitch').classed('selected', false);
67 | };
68 |
69 | explainGit = {
70 | HistoryView: HistoryView,
71 | ControlBox: ControlBox,
72 | generateId: HistoryView.generateId,
73 | open: open,
74 | reset: reset
75 | };
76 |
77 | window.explainGit = explainGit;
78 |
79 | return explainGit;
80 | });
81 |
--------------------------------------------------------------------------------
/css/1140.css:
--------------------------------------------------------------------------------
1 | /* CSS Resets */
2 |
3 | html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,address,cite,code,del,dfn,em,img,ins,q,small,strong,sub,sup,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{border:0;margin:0;padding:0}article,aside,figure,figure img,figcaption,hgroup,footer,header,nav,section,video,object{display:block}a img{border:0}figure{position:relative}figure img{width:100%}
4 |
5 |
6 | /* ==================================================================================================================== */
7 | /* ! The 1140px Grid V2 by Andy Taylor \ http://cssgrid.net \ http://www.twitter.com/andytlr \ http://www.andytlr.com */
8 | /* ==================================================================================================================== */
9 |
10 | .container {
11 | padding-left: 20px;
12 | padding-right: 20px;
13 | }
14 |
15 | .row {
16 | width: 100%;
17 | max-width: 1140px;
18 | min-width: 755px;
19 | margin: 0 auto;
20 | overflow: hidden;
21 | }
22 |
23 | .onecol, .twocol, .threecol, .fourcol, .fivecol, .sixcol, .sevencol, .eightcol, .ninecol, .tencol, .elevencol {
24 | margin-right: 3.8%;
25 | float: left;
26 | min-height: 1px;
27 | }
28 |
29 | .row .onecol {
30 | width: 4.85%;
31 | }
32 |
33 | .row .twocol {
34 | width: 13.45%;
35 | }
36 |
37 | .row .threecol {
38 | width: 22.05%;
39 | }
40 |
41 | .row .fourcol {
42 | width: 30.75%;
43 | }
44 |
45 | .row .fivecol {
46 | width: 39.45%;
47 | }
48 |
49 | .row .sixcol {
50 | width: 48%;
51 | }
52 |
53 | .row .sevencol {
54 | width: 56.75%;
55 | }
56 |
57 | .row .eightcol {
58 | width: 65.4%;
59 | }
60 |
61 | .row .ninecol {
62 | width: 74.05%;
63 | }
64 |
65 | .row .tencol {
66 | width: 82.7%;
67 | }
68 |
69 | .row .elevencol {
70 | width: 91.35%;
71 | }
72 |
73 | .row .twelvecol {
74 | width: 100%;
75 | float: left;
76 | }
77 |
78 | .last {
79 | margin-right: 0px;
80 | }
81 |
82 | img, object, embed {
83 | max-width: 100%;
84 | }
85 |
86 | img {
87 | height: auto;
88 | }
89 |
90 |
91 | /* Smaller screens */
92 |
93 | @media only screen and (max-width: 1023px) {
94 |
95 | body {
96 | font-size: 0.8em;
97 | line-height: 1.5em;
98 | }
99 |
100 | }
101 |
102 |
103 | /* Mobile */
104 |
105 | @media handheld, only screen and (max-width: 767px) {
106 |
107 | body {
108 | font-size: 16px;
109 | -webkit-text-size-adjust: none;
110 | }
111 |
112 | .row, body, .container {
113 | width: 100%;
114 | min-width: 0;
115 | margin-left: 0px;
116 | margin-right: 0px;
117 | padding-left: 0px;
118 | padding-right: 0px;
119 | }
120 |
121 | .row .onecol, .row .twocol, .row .threecol, .row .fourcol, .row .fivecol, .row .sixcol, .row .sevencol, .row .eightcol, .row .ninecol, .row .tencol, .row .elevencol, .row .twelvecol {
122 | width: auto;
123 | float: none;
124 | margin-left: 0px;
125 | margin-right: 0px;
126 | padding-left: 20px;
127 | padding-right: 20px;
128 | }
129 |
130 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
15 | Visualizing Git
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
31 |
32 |
51 |
52 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/examples/merging.md:
--------------------------------------------------------------------------------
1 | ### Make local changes with using branches
2 |
3 | Scenario dropdown - choose `Free Explore with Remote`
4 |
5 | Make two commits on a branch called `feature1`
6 | ```
7 | git checkout -b feature1
8 | git commit
9 | git commit
10 | ```
11 |
12 | Merge `feature1` into `master`
13 | ```
14 | git checkout master
15 | git merge feature1 --no-ff
16 | // --no-ff means "no fast-forward"
17 | // see below on discussion
18 | ```
19 | 
20 |
21 | Repeat with `feature2` branch, so that you get this:
22 | 
23 |
24 | Push to share changes on GitHub!
25 | ```
26 | git push
27 | ```
28 | 
29 |
30 | #### What is `--no-ff` all about?
31 | Typically when we think about a merge, we are combining two branches that both have changes introduced to them:
32 | 
33 |
34 | When we merge feature into master, Git creates a merge commit with two parents (the branch heads).
35 | 
36 |
37 | But let's say we have the two branches in the image below and we want to merge `feature1` into `master`.
38 |
39 | 
40 |
41 | Note that the `master` branch is pointing to a commit that is already in the history of the `feature1` branch. This means that all of the commits on the `master` branch are *already* on the `feature1` branch.
42 |
43 | In this case, when you tell Git to merge, it will do a *fast-forward merge* by default, meaning it will simply move (or *fast-forward*) the `master` and `HEAD` refs so that they point to the commit that `feature1` points to.
44 |
45 | 
46 |
47 | This can be useful because it minimizes branching in our commit history. Remember that `git pull` consists of a `git fetch` then a `git merge`. What if every time we pulled we got a new merge commit instead of doing a *fast-forward* merge. Then we would get meaningless and unnecessary merge commits that would clutter up our history, and `git log` would show a mess of branches.
48 |
49 | Sometimes it's useful to show a branch as being a separate section of our history.
50 | For example, say we've done a bunch of work on a feature branch and want to make it clear which commits were on it after merging into master.
51 |
52 | In this case we want to force Git to create a new merge commit and NOT do a *fast-forward merge* by default by saying `git merge feature1 --no-ff`.
53 |
54 | 
55 |
56 | ##### Why would we want to use --no-ff?
57 | This is useful for creating a story with our commit history, so it's clear which commits have work for a given feature.
58 |
59 | If I ask you which commits correspond to `feature1` and which correspond to `feature2` in this graph, could you tell me with certainty?
60 |
61 | 
62 |
63 | What if I asked the same question but with this graph:
64 | 
65 |
66 | With the merge commits it becomes clear which commits correspond to a given feature.
67 |
68 | #### Your push will be rejected if it's not a fast-forward
69 | Have you seen this in your console before?
70 | ```
71 | To https://github.com/USERNAME/REPOSITORY.git
72 | ! [rejected] master -> master (non-fast-forward)
73 | error: failed to push some refs to 'https://github.com/USERNAME/REPOSITORY.git'
74 | To prevent you from losing history, non-fast-forward updates were rejected
75 | Merge the remote changes (e.g. 'git pull') before pushing again. See the
76 | 'Note about fast-forwards' section of 'git push --help' for details.
77 | ```
78 |
79 | This happens when your remote branch has changes on it that you don't have locally. When you push you're saying to git "make the remote branch look exactly like what I have here". If Git detects that you'll lose information on your remote by doing this, it rejects the push and advises you to first pull to get all the commits locally, and then try pushing again. After doing this, your push will result in a fast-forwarding of the branch ref on your remote.
80 |
--------------------------------------------------------------------------------
/css/explaingit.css:
--------------------------------------------------------------------------------
1 | /* styles */
2 |
3 | #last-command {
4 | position: absolute;
5 | bottom: 10px;
6 | right: 10px;
7 | font-size: 70px;
8 | opacity: 0.8;
9 | font-family: Helvetica, Arial, sans-serif;
10 | }
11 |
12 | body, html {
13 | height: 100%;
14 | }
15 |
16 | .intro p, .concept-container p {
17 | padding-top: 10px;
18 | }
19 |
20 | a.openswitch {
21 | display: block;
22 | }
23 |
24 | a.openswitch.selected {
25 | font-weight: bold;
26 | }
27 |
28 | .command-list, .example-list {
29 | margin-top: 10px;
30 | margin-bottom: 10px;
31 | padding: 10px 0;
32 | border-bottom: 2px dashed #888;
33 | border-top: 2px dashed #888;
34 | background-color: #EEE;
35 | }
36 |
37 | .command-list a.openswitch {
38 | font-family: Courier New;
39 | }
40 |
41 | .concept-area {
42 | padding-bottom: 20px;
43 | }
44 |
45 | .concept-container {
46 | display: none;
47 | }
48 |
49 | .playground-container {
50 | margin-top: 20px;
51 | position: relative;
52 | }
53 |
54 | span.cmd {
55 | background-color: #222222;
56 | color: #FFFFFF;
57 | font-family: Courier New;
58 | padding: 0 0.2em;
59 | }
60 |
61 | .svg-container {
62 | margin-left: 250px;
63 | display: block;
64 | overflow: auto;
65 | border: 1px dotted #AAA;
66 | }
67 |
68 | .svg-container.remote-container {
69 | position: absolute;
70 | top: 0px;
71 | right: 0px;
72 | background-color: #EFF1FF;
73 | border-left: 1px dotted #AAA;
74 | border-bottom: 1px dotted #AAA;
75 | z-index: 100
76 | }
77 |
78 | #ExplainGitZen-Container {
79 | position: absolute;
80 | top: 0;
81 | bottom: 0;
82 | right: 0;
83 | left: 0;
84 | }
85 |
86 | #ExplainGitZen-Container .svg-container {
87 | display: inline-block;
88 | border: 1px dotted #AAA;
89 | position: absolute;
90 | top: 0;
91 | bottom: 0;
92 | right: 0;
93 | left: 250px;
94 | margin-left: 0;
95 | }
96 |
97 | #ExplainGitZen-Container .svg-container.remote-container {
98 | position: absolute;
99 | top: 0px;
100 | right: 0px;
101 | left: auto;
102 | bottom: auto;
103 | background-color: #EFF1FF;
104 | border-left: 1px dotted #AAA;
105 | border-bottom: 1px dotted #AAA;
106 | min-height: 400px;
107 | }
108 |
109 | #ExplainGitZen-Container .playground-container {
110 | position: absolute;
111 | top: 0;
112 | bottom: 20px;
113 | right: 20px;
114 | left: 20px;
115 | }
116 |
117 | .remote-name-display {
118 | font-weight: bold;
119 | text-align: right;
120 | }
121 |
122 | .control-box {
123 | display: inline-block;
124 | position: absolute;
125 | top: 0px;
126 | bottom: 0;
127 | width: 250px;
128 | vertical-align: bottom;
129 | border: 1px dotted #AAA;
130 | }
131 |
132 | .control-box select {
133 | position: absolute;
134 | left: 3px;
135 | top: 3px;
136 | }
137 |
138 | .control-box button {
139 | font-family: Courier New;
140 | font-size: 12px;
141 | margin-right: 5px;
142 | margin-bottom: 5px;
143 | }
144 |
145 | .control-box .log {
146 | overflow-y: auto;
147 | position: absolute;
148 | background: #000;
149 | top: 30px;
150 | bottom: 20px;
151 | left: 0;
152 | right: 0;
153 | border-bottom: 1px solid #AAA;
154 | }
155 |
156 | .control-box .log .reflog-entry, .control-box .log .log-entry {
157 | display: inline-block;
158 | padding-left: 15px;
159 | text-indent: -15px;
160 | }
161 |
162 | .control-box .log, .control-box input[type="text"], .control-box .input {
163 | font-family: Courier New;
164 | font-size: 14px;
165 | }
166 |
167 | .control-box .log .command-entry {
168 | padding-left: 15px;
169 | color: #FFF;
170 | line-height: 14px;
171 | background: url(../images/prompt.gif) no-repeat left top black;
172 | }
173 |
174 | .control-box input[type="text"] {
175 | position: absolute;
176 | bottom: 0;
177 | padding-left: 15px;
178 | color: #FFF;
179 | line-height: 14px;
180 | background: url(../images/prompt.gif) no-repeat left center black;
181 | }
182 |
183 | .control-box .log .info, .control-box .log .error {
184 | font-size: 12px;
185 | padding: 5px;
186 | }
187 |
188 | .control-box .log .info {
189 | color: #FFC;
190 | }
191 |
192 | .control-box .log .error {
193 | color: #FCC;
194 | }
195 |
196 | .control-box input[type="text"] {
197 | width: 235px;
198 | border: none;
199 | }
200 |
201 | circle.commit {
202 | fill: #CCCCCC;
203 | stroke: #888888;
204 | stroke-width: 3;
205 | transition-property: stroke, fill;
206 | transition-duration: 500ms;
207 | transition-timing-function: ease-out;
208 | }
209 |
210 | circle.commit.reverted {
211 | fill: #CCEEFF;
212 | stroke: #0066CC;
213 | }
214 |
215 | circle.commit.reverted.checked-out {
216 | fill: #CCEEFF;
217 | }
218 |
219 | circle.commit.rebased {
220 | stroke: #560066;
221 | fill: #b907a4;
222 | }
223 |
224 | circle.commit.rebased.checked-out {
225 | fill: #CCCCFF;
226 | }
227 |
228 | circle.commit.cherry-picked {
229 | stroke: #660000;
230 | fill: #ff5151;
231 | }
232 |
233 | circle.commit.cherry-picked.checked-out {
234 | fill: #ff5151;
235 | }
236 |
237 | circle.commit.branchless {
238 | fill: #FEFEFE;
239 | stroke: #888888;
240 | stroke-dasharray: 5, 5;
241 | }
242 |
243 | circle.commit.branchless.checked-out {
244 | fill: #FEFEFE;
245 | }
246 |
247 | circle.commit.checked-out {
248 | fill: #8ce08c;
249 | stroke: #339900;
250 | }
251 |
252 | circle.commit.logging {
253 | stroke: #0066CC;
254 | fill: #0099EE;
255 | }
256 |
257 | .commit-pointer {
258 | stroke: rgba(100, 100, 100, 0.5);
259 | stroke-width: 4;
260 | }
261 |
262 | .merge-pointer {
263 | stroke: rgba(100, 100, 100, 0.5);
264 | stroke-width: 4;
265 | stroke-dasharray: 15, 4;
266 | }
267 |
268 | .commit-pointer.branchless, .merge-pointer.branchless {
269 | stroke: #DDD;
270 | stroke-width: 2;
271 | }
272 |
273 | text.id-label {
274 | text-anchor: middle;
275 | font-family: Courier New;
276 | font-weight: bolder;
277 | fill: #666;
278 | font-size: 10px;
279 | }
280 |
281 | text.message-label {
282 | text-anchor: middle;
283 | font-family: Courier New;
284 | fill: #666;
285 | font-size: 10px;
286 | }
287 |
288 | g.branch-tag > rect {
289 | fill: #FFCC66;
290 | stroke: #CC9900;
291 | stroke-width: 2;
292 | }
293 |
294 | g.branch-tag.git-tag > rect {
295 | fill: #7FC9FF;
296 | stroke: #0026FF;
297 | }
298 |
299 | g.branch-tag.remote-branch > rect {
300 | fill: #CCC;
301 | stroke: #888;
302 | }
303 |
304 | g.branch-tag > text {
305 | text-anchor: middle;
306 | fill: #000;
307 | font-size: 15px;
308 | font-family: Arial;
309 | }
310 |
311 | g.head-tag > rect {
312 | fill: #CCFFCC;
313 | stroke: #339900;
314 | stroke-width: 2;
315 | }
316 |
317 | g.head-tag > text {
318 | text-anchor: middle;
319 | fill: #000;
320 | font-size: 15px;
321 | font-family: Arial;
322 | font-weight: bold;
323 | text-transform: uppercase;
324 | }
325 |
--------------------------------------------------------------------------------
/js/demos.js:
--------------------------------------------------------------------------------
1 | define([], function () {
2 |
3 | var free = {
4 | title: 'Free Explore',
5 | key: 'free',
6 | message: 'Have fun!',
7 | commitData: [
8 | {id: 'e137e9b', tags: ['master'], message: 'first commit'},
9 | ]
10 | }
11 |
12 | var freeWithRemote = {
13 | title: 'Free Explore with Remote',
14 | key: 'free-remote',
15 | message: 'Have fun!',
16 | commitData: [
17 | {id: 'e137e9b', tags: ['master', 'origin/master'], message: 'first commit'},
18 | ],
19 | originData: [
20 | {id: 'e137e9b', tags: ['master'], message: 'first commit'}
21 | ]
22 | }
23 |
24 | var upstreamChanges = {
25 | title: 'Upstream Changes',
26 | key: 'upstream-changes',
27 | message: 'Someone else has been working here!',
28 | currentBranch: "feature",
29 | commitData: [
30 | {
31 | "id": "e137e9b",
32 | "tags": [],
33 | "message": "first commit",
34 | "parent": "initial",
35 | },
36 | {
37 | "id": "84c98fe",
38 | "parent": "e137e9b",
39 | "tags": [ "master", "origin/master" ],
40 | },
41 | {
42 | "id": "1c016b6",
43 | "parent": "e137e9b",
44 | "tags": [ "feature", "origin/feature", "HEAD" ],
45 | }
46 | ],
47 | originData: [
48 | {
49 | "id": "e137e9b",
50 | "tags": [],
51 | "message": "first commit",
52 | "parent": "initial",
53 | },
54 | {
55 | "id": "84c98fe",
56 | "parent": "e137e9b",
57 | "tags": [ "master", "HEAD" ],
58 | },
59 | {
60 | "id": "1c016b6",
61 | "parent": "e137e9b",
62 | "tags": [],
63 | },
64 | {
65 | "id": "fd0af32",
66 | "tags": [ "feature" ],
67 | "parent": "1c016b6",
68 | }
69 | ]
70 | }
71 |
72 | var rewrittenHistory = {
73 | title: 'Rewritten Remote History',
74 | key: 'rewritten-history',
75 | message: 'Someone force-pushed and re-wrote history on the remote!',
76 | currentBranch: "feature",
77 | commitData: [
78 | {
79 | "id": "e137e9b",
80 | "tags": [],
81 | "message": "first commit",
82 | "parent": "initial",
83 | "cx": 50,
84 | "cy": 330,
85 | "branchless": false
86 | },
87 | {
88 | "id": "84c98fe",
89 | "parent": "e137e9b",
90 | "tags": [
91 | "master",
92 | "origin/master"
93 | ],
94 | "cx": 140,
95 | "cy": 330,
96 | "branchless": false
97 | },
98 | {
99 | "id": "1c016b6",
100 | "parent": "e137e9b",
101 | "tags": [],
102 | "cx": 140,
103 | "cy": 240,
104 | "branchless": false
105 | },
106 | {
107 | "id": "fd0af32",
108 | "parent": "1c016b6",
109 | "tags": [],
110 | "cx": 230,
111 | "cy": 240,
112 | "branchless": false
113 | },
114 | {
115 | "id": "5041e4c",
116 | "tags": [
117 | "feature",
118 | "origin/feature",
119 | "HEAD"
120 | ],
121 | "parent": "fd0af32",
122 | "cx": 320,
123 | "cy": 240,
124 | "branchless": false
125 | }
126 | ],
127 | originData: [
128 | {
129 | "id": "e137e9b",
130 | "tags": [],
131 | "message": "first commit",
132 | "parent": "initial",
133 | "cx": 50,
134 | "cy": 360,
135 | "branchless": false
136 | },
137 | {
138 | "id": "84c98fe",
139 | "parent": "e137e9b",
140 | "tags": [
141 | "master"
142 | ],
143 | "cx": 140,
144 | "cy": 360,
145 | "branchless": false
146 | },
147 | {
148 | "id": "1c016b6",
149 | "parent": "e137e9b",
150 | "tags": [],
151 | "cx": 140,
152 | "cy": 270,
153 | "branchless": false
154 | },
155 | {
156 | "id": "fd0af32",
157 | "tags": [
158 | "feature",
159 | "HEAD"
160 | ],
161 | "parent": "1c016b6",
162 | "cx": 230,
163 | "cy": 270,
164 | "branchless": false
165 | },
166 | {
167 | "id": "5041e4c",
168 | "tags": [],
169 | "parent": "fd0af32",
170 | "cx": 320,
171 | "cy": 270,
172 | "branchless": true
173 | }
174 | ]
175 |
176 | }
177 |
178 | var revert = {
179 | title: 'Revert',
180 | key: 'revert',
181 | message: 'Oops, let\'s revert some commits',
182 | commitData: [
183 | {
184 | "id": "e137e9b",
185 | "tags": [],
186 | "message": "first commit",
187 | "parent": "initial",
188 | "cx": 50,
189 | "cy": 330,
190 | "branchless": false
191 | },
192 | {
193 | "id": "dd70cfe",
194 | "tags": [],
195 | "parent": "e137e9b",
196 | "cx": 140,
197 | "cy": 330,
198 | "branchless": false
199 | },
200 | {
201 | "id": "2545b6f",
202 | "tags": [],
203 | "parent": "dd70cfe",
204 | "cx": 230,
205 | "cy": 330,
206 | "branchless": false
207 | },
208 | {
209 | "id": "3d6ef16",
210 | "tags": [],
211 | "parent": "dd70cfe",
212 | "cx": 230,
213 | "cy": 240,
214 | "branchless": false
215 | },
216 | {
217 | "id": "077415f",
218 | "tags": [
219 | "feature"
220 | ],
221 | "parent": "3d6ef16",
222 | "cx": 320,
223 | "cy": 240,
224 | "branchless": false
225 | },
226 | {
227 | "parent2": "077415f",
228 | "id": "8686fb6",
229 | "tags": [
230 | "master",
231 | "HEAD"
232 | ],
233 | "message": "Merge",
234 | "parent": "2545b6f",
235 | "cx": 410,
236 | "cy": 330,
237 | "branchless": false
238 | }
239 | ]
240 | }
241 |
242 | var cherryPick = {
243 | title: 'Cherry Pick',
244 | key: 'cherry-pick',
245 | message: 'Let\'s pick some commits',
246 | commitData: [
247 | {
248 | "id": "e137e9b",
249 | "tags": [],
250 | "message": "first commit",
251 | "parent": "initial",
252 | "cx": 50,
253 | "cy": 318,
254 | "branchless": false
255 | },
256 | {
257 | "id": "790dd94",
258 | "tags": [],
259 | "parent": "e137e9b",
260 | "cx": 140,
261 | "cy": 318,
262 | "branchless": false
263 | },
264 | {
265 | "id": "96e9ce7",
266 | "tags": [
267 | "[bugfix1]"
268 | ],
269 | "parent": "790dd94",
270 | "cx": 230,
271 | "cy": 318,
272 | "branchless": false
273 | },
274 | {
275 | "id": "44db644",
276 | "tags": [],
277 | "parent": "96e9ce7",
278 | "cx": 320,
279 | "cy": 318,
280 | "branchless": false
281 | },
282 | {
283 | "id": "06127d7",
284 | "tags": [],
285 | "parent": "44db644",
286 | "cx": 410,
287 | "cy": 318,
288 | "branchless": false
289 | },
290 | {
291 | "id": "60c6c2c",
292 | "tags": [],
293 | "parent": "790dd94",
294 | "cx": 230,
295 | "cy": 228,
296 | "branchless": false
297 | },
298 | {
299 | "id": "8f7c801",
300 | "tags": [
301 | "release",
302 | "HEAD"
303 | ],
304 | "parent": "60c6c2c",
305 | "cx": 320,
306 | "cy": 228,
307 | "branchless": false
308 | },
309 | {
310 | "id": "78ecb32",
311 | "tags": [],
312 | "parent": "44db644",
313 | "cx": 410,
314 | "cy": 228,
315 | "branchless": false
316 | },
317 | {
318 | "id": "12e9bbb",
319 | "tags": [
320 | "bugfix2"
321 | ],
322 | "parent": "78ecb32",
323 | "cx": 500,
324 | "cy": 228,
325 | "branchless": false
326 | },
327 | {
328 | "id": "e8ce346",
329 | "tags": [],
330 | "parent": "06127d7",
331 | "cx": 500,
332 | "cy": 318,
333 | "branchless": false
334 | },
335 | {
336 | "parent2": "12e9bbb",
337 | "id": "5749661",
338 | "tags": [
339 | "master"
340 | ],
341 | "message": "Merge",
342 | "parent": "e8ce346",
343 | "cx": 590,
344 | "cy": 318,
345 | "branchless": false
346 | }
347 | ]
348 | }
349 |
350 | var rebase = {
351 | title: 'Rebasing',
352 | key: 'rebase',
353 | message: 'Try rebasing the `feature` branch',
354 | commitData: [
355 | {id: 'e137e9b', tags: ['master'], message: 'first commit'}
356 | ]
357 | }
358 |
359 | return [
360 | free, freeWithRemote, upstreamChanges, rewrittenHistory, revert, cherryPick
361 | ]
362 | })
363 |
--------------------------------------------------------------------------------
/css/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v2.1.0 | MIT License | git.io/normalize */
2 |
3 | /* ==========================================================================
4 | HTML5 display definitions
5 | ========================================================================== */
6 |
7 | /**
8 | * Correct `block` display not defined in IE 8/9.
9 | */
10 |
11 | article,
12 | aside,
13 | details,
14 | figcaption,
15 | figure,
16 | footer,
17 | header,
18 | hgroup,
19 | main,
20 | nav,
21 | section,
22 | summary {
23 | display: block;
24 | }
25 |
26 | /**
27 | * Correct `inline-block` display not defined in IE 8/9.
28 | */
29 |
30 | audio,
31 | canvas,
32 | video {
33 | display: inline-block;
34 | }
35 |
36 | /**
37 | * Prevent modern browsers from displaying `audio` without controls.
38 | * Remove excess height in iOS 5 devices.
39 | */
40 |
41 | audio:not([controls]) {
42 | display: none;
43 | height: 0;
44 | }
45 |
46 | /**
47 | * Address styling not present in IE 8/9.
48 | */
49 |
50 | [hidden] {
51 | display: none;
52 | }
53 |
54 | /* ==========================================================================
55 | Base
56 | ========================================================================== */
57 |
58 | /**
59 | * 1. Set default font family to sans-serif.
60 | * 2. Prevent iOS text size adjust after orientation change, without disabling
61 | * user zoom.
62 | */
63 |
64 | html {
65 | font-family: sans-serif; /* 1 */
66 | -webkit-text-size-adjust: 100%; /* 2 */
67 | -ms-text-size-adjust: 100%; /* 2 */
68 | }
69 |
70 | /**
71 | * Remove default margin.
72 | */
73 |
74 | body {
75 | margin: 0;
76 | }
77 |
78 | /* ==========================================================================
79 | Links
80 | ========================================================================== */
81 |
82 | /**
83 | * Address `outline` inconsistency between Chrome and other browsers.
84 | */
85 |
86 | a:focus {
87 | outline: thin dotted;
88 | }
89 |
90 | /**
91 | * Improve readability when focused and also mouse hovered in all browsers.
92 | */
93 |
94 | a:active,
95 | a:hover {
96 | outline: 0;
97 | }
98 |
99 | /* ==========================================================================
100 | Typography
101 | ========================================================================== */
102 |
103 | /**
104 | * Address variable `h1` font-size and margin within `section` and `article`
105 | * contexts in Firefox 4+, Safari 5, and Chrome.
106 | */
107 |
108 | h1 {
109 | font-size: 2em;
110 | margin: 0.67em 0;
111 | }
112 |
113 | /**
114 | * Address styling not present in IE 8/9, Safari 5, and Chrome.
115 | */
116 |
117 | abbr[title] {
118 | border-bottom: 1px dotted;
119 | }
120 |
121 | /**
122 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
123 | */
124 |
125 | b,
126 | strong {
127 | font-weight: bold;
128 | }
129 |
130 | /**
131 | * Address styling not present in Safari 5 and Chrome.
132 | */
133 |
134 | dfn {
135 | font-style: italic;
136 | }
137 |
138 | /**
139 | * Address differences between Firefox and other browsers.
140 | */
141 |
142 | hr {
143 | -moz-box-sizing: content-box;
144 | box-sizing: content-box;
145 | height: 0;
146 | }
147 |
148 | /**
149 | * Address styling not present in IE 8/9.
150 | */
151 |
152 | mark {
153 | background: #ff0;
154 | color: #000;
155 | }
156 |
157 | /**
158 | * Correct font family set oddly in Safari 5 and Chrome.
159 | */
160 |
161 | code,
162 | kbd,
163 | pre,
164 | samp {
165 | font-family: monospace, serif;
166 | font-size: 1em;
167 | }
168 |
169 | /**
170 | * Improve readability of pre-formatted text in all browsers.
171 | */
172 |
173 | pre {
174 | white-space: pre-wrap;
175 | }
176 |
177 | /**
178 | * Set consistent quote types.
179 | */
180 |
181 | q {
182 | quotes: "\201C" "\201D" "\2018" "\2019";
183 | }
184 |
185 | /**
186 | * Address inconsistent and variable font size in all browsers.
187 | */
188 |
189 | small {
190 | font-size: 80%;
191 | }
192 |
193 | /**
194 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
195 | */
196 |
197 | sub,
198 | sup {
199 | font-size: 75%;
200 | line-height: 0;
201 | position: relative;
202 | vertical-align: baseline;
203 | }
204 |
205 | sup {
206 | top: -0.5em;
207 | }
208 |
209 | sub {
210 | bottom: -0.25em;
211 | }
212 |
213 | /* ==========================================================================
214 | Embedded content
215 | ========================================================================== */
216 |
217 | /**
218 | * Remove border when inside `a` element in IE 8/9.
219 | */
220 |
221 | img {
222 | border: 0;
223 | }
224 |
225 | /**
226 | * Correct overflow displayed oddly in IE 9.
227 | */
228 |
229 | svg:not(:root) {
230 | overflow: hidden;
231 | }
232 |
233 | /* ==========================================================================
234 | Figures
235 | ========================================================================== */
236 |
237 | /**
238 | * Address margin not present in IE 8/9 and Safari 5.
239 | */
240 |
241 | figure {
242 | margin: 0;
243 | }
244 |
245 | /* ==========================================================================
246 | Forms
247 | ========================================================================== */
248 |
249 | /**
250 | * Define consistent border, margin, and padding.
251 | */
252 |
253 | fieldset {
254 | border: 1px solid #c0c0c0;
255 | margin: 0 2px;
256 | padding: 0.35em 0.625em 0.75em;
257 | }
258 |
259 | /**
260 | * 1. Correct `color` not being inherited in IE 8/9.
261 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
262 | */
263 |
264 | legend {
265 | border: 0; /* 1 */
266 | padding: 0; /* 2 */
267 | }
268 |
269 | /**
270 | * 1. Correct font family not being inherited in all browsers.
271 | * 2. Correct font size not being inherited in all browsers.
272 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
273 | */
274 |
275 | button,
276 | input,
277 | select,
278 | textarea {
279 | font-family: inherit; /* 1 */
280 | font-size: 100%; /* 2 */
281 | margin: 0; /* 3 */
282 | }
283 |
284 | /**
285 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
286 | * the UA stylesheet.
287 | */
288 |
289 | button,
290 | input {
291 | line-height: normal;
292 | }
293 |
294 | /**
295 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
296 | * All other form control elements do not inherit `text-transform` values.
297 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
298 | * Correct `select` style inheritance in Firefox 4+ and Opera.
299 | */
300 |
301 | button,
302 | select {
303 | text-transform: none;
304 | }
305 |
306 | /**
307 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
308 | * and `video` controls.
309 | * 2. Correct inability to style clickable `input` types in iOS.
310 | * 3. Improve usability and consistency of cursor style between image-type
311 | * `input` and others.
312 | */
313 |
314 | button,
315 | html input[type="button"], /* 1 */
316 | input[type="reset"],
317 | input[type="submit"] {
318 | -webkit-appearance: button; /* 2 */
319 | cursor: pointer; /* 3 */
320 | }
321 |
322 | /**
323 | * Re-set default cursor for disabled elements.
324 | */
325 |
326 | button[disabled],
327 | html input[disabled] {
328 | cursor: default;
329 | }
330 |
331 | /**
332 | * 1. Address box sizing set to `content-box` in IE 8/9.
333 | * 2. Remove excess padding in IE 8/9.
334 | */
335 |
336 | input[type="checkbox"],
337 | input[type="radio"] {
338 | box-sizing: border-box; /* 1 */
339 | padding: 0; /* 2 */
340 | }
341 |
342 | /**
343 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
344 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
345 | * (include `-moz` to future-proof).
346 | */
347 |
348 | input[type="search"] {
349 | -webkit-appearance: textfield; /* 1 */
350 | -moz-box-sizing: content-box;
351 | -webkit-box-sizing: content-box; /* 2 */
352 | box-sizing: content-box;
353 | }
354 |
355 | /**
356 | * Remove inner padding and search cancel button in Safari 5 and Chrome
357 | * on OS X.
358 | */
359 |
360 | input[type="search"]::-webkit-search-cancel-button,
361 | input[type="search"]::-webkit-search-decoration {
362 | -webkit-appearance: none;
363 | }
364 |
365 | /**
366 | * Remove inner padding and border in Firefox 4+.
367 | */
368 |
369 | button::-moz-focus-inner,
370 | input::-moz-focus-inner {
371 | border: 0;
372 | padding: 0;
373 | }
374 |
375 | /**
376 | * 1. Remove default vertical scrollbar in IE 8/9.
377 | * 2. Improve readability and alignment in all browsers.
378 | */
379 |
380 | textarea {
381 | overflow: auto; /* 1 */
382 | vertical-align: top; /* 2 */
383 | }
384 |
385 | /* ==========================================================================
386 | Tables
387 | ========================================================================== */
388 |
389 | /**
390 | * Remove most spacing between table cells.
391 | */
392 |
393 | table {
394 | border-collapse: collapse;
395 | border-spacing: 0;
396 | }
397 |
--------------------------------------------------------------------------------
/js/vendor/require.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | RequireJS 2.1.4 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
3 | Available via the MIT or new BSD license.
4 | see: http://github.com/jrburke/requirejs for details
5 | */
6 | var requirejs,require,define;
7 | (function(Y){function I(b){return"[object Function]"===L.call(b)}function J(b){return"[object Array]"===L.call(b)}function x(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(I(n)){if(this.events.error)try{e=k.execCb(c,n,b,e)}catch(d){a=d}else e=k.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=[this.map.id],a.requireType="define",A(this.error=a)}else e=n;this.exports=e;if(this.map.isDefine&&
19 | !this.ignore&&(p[c]=e,l.onResourceLoad))l.onResourceLoad(k,this.map,this.depMaps);delete j[c];this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=h(a.prefix);this.depMaps.push(d);s(d,"defined",t(this,function(e){var n,d;d=this.map.name;var v=this.map.parentMap?this.map.parentMap.name:null,g=k.makeRequire(a.parentMap,{enableBuildCallback:!0});
20 | if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,v,!0)})||""),e=h(a.prefix+"!"+d,this.map.parentMap),s(e,"defined",t(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=i(j,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",t(this,function(a){this.emit("error",a)}));d.enable()}}else n=t(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=t(this,function(a){this.inited=!0;this.error=a;a.requireModules=
21 | [b];E(j,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&delete j[a.map.id]});A(a)}),n.fromText=t(this,function(e,c){var d=a.name,u=h(d),v=O;c&&(e=c);v&&(O=!1);q(u);r(m.config,b)&&(m.config[d]=m.config[b]);try{l.exec(e)}catch(j){return A(F("fromtexteval","fromText eval for "+b+" failed: "+j,j,[b]))}v&&(O=!0);this.depMaps.push(u);k.completeLoad(d);g([d],n)}),e.load(a.name,g,n,m)}));k.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){this.enabling=this.enabled=!0;x(this.depMaps,t(this,function(a,
22 | b){var c,e;if("string"===typeof a){a=h(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=i(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",t(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&s(a,"error",this.errback)}c=a.id;e=j[c];!r(N,c)&&(e&&!e.enabled)&&k.enable(a,this)}));E(this.pluginMaps,t(this,function(a){var b=i(j,a.id);b&&!b.enabled&&k.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=
23 | this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){x(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};k={config:m,contextName:b,registry:j,defined:p,urlFetched:S,defQueue:G,Module:W,makeModuleMap:h,nextTick:l.nextTick,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=m.pkgs,c=m.shim,e={paths:!0,config:!0,map:!0};E(a,function(a,b){e[b]?"map"===b?Q(m[b],a,!0,!0):Q(m[b],a,!0):m[b]=a});a.shim&&(E(a.shim,function(a,
24 | b){J(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=k.makeShimExports(a);c[b]=a}),m.shim=c);a.packages&&(x(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ga,"").replace(aa,"")}}),m.pkgs=b);E(j,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=h(b))});if(a.deps||a.callback)k.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Y,arguments));
25 | return b||a.exports&&Z(a.exports)}},makeRequire:function(a,d){function g(e,c,u){var i,m;d.enableBuildCallback&&(c&&I(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(I(c))return A(F("requireargs","Invalid require call"),u);if(a&&r(N,e))return N[e](j[a.id]);if(l.get)return l.get(k,e,a);i=h(e,a,!1,!0);i=i.id;return!r(p,i)?A(F("notloaded",'Module name "'+i+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):p[i]}K();k.nextTick(function(){K();m=q(h(null,a));m.skipMap=d.skipMap;
26 | m.init(e,c,u,{enabled:!0});C()});return g}d=d||{};Q(g,{isBrowser:z,toUrl:function(b){var d,f=b.lastIndexOf("."),h=b.split("/")[0];if(-1!==f&&(!("."===h||".."===h)||1g.attachEvent.toString().indexOf("[native code"))&&
33 | !V?(O=!0,g.attachEvent("onreadystatechange",b.onScriptLoad)):(g.addEventListener("load",b.onScriptLoad,!1),g.addEventListener("error",b.onScriptError,!1)),g.src=d,K=g,D?B.insertBefore(g,D):B.appendChild(g),K=null,g;$&&(importScripts(d),b.completeLoad(c))};z&&M(document.getElementsByTagName("script"),function(b){B||(B=b.parentNode);if(s=b.getAttribute("data-main"))return q.baseUrl||(H=s.split("/"),ba=H.pop(),ca=H.length?H.join("/")+"/":"./",q.baseUrl=ca,s=ba),s=s.replace(aa,""),q.deps=q.deps?q.deps.concat(s):
34 | [s],!0});define=function(b,c,d){var i,g;"string"!==typeof b&&(d=c,c=b,b=null);J(c)||(d=c,c=[]);!c.length&&I(d)&&d.length&&(d.toString().replace(ia,"").replace(ja,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c));if(O){if(!(i=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),i=P;i&&(b||(b=i.getAttribute("data-requiremodule")),g=C[i.getAttribute("data-requirecontext")])}(g?
35 | g.defQueue:R).push([b,c,d])};define.amd={jQuery:!0};l.exec=function(b){return eval(b)};l(q)}})(this);
36 |
--------------------------------------------------------------------------------
/css/jquery-ui.min.css:
--------------------------------------------------------------------------------
1 | /*! jQuery UI - v1.11.4 - 2016-06-06
2 | * http://jqueryui.com
3 | * Includes: core.css, draggable.css, resizable.css, theme.css
4 | * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Arial%2CHelvetica%2Csans-serif&fsDefault=1em&fwDefault=normal&cornerRadius=3px&bgColorHeader=e9e9e9&bgTextureHeader=flat&borderColorHeader=dddddd&fcHeader=333333&iconColorHeader=444444&bgColorContent=ffffff&bgTextureContent=flat&borderColorContent=dddddd&fcContent=333333&iconColorContent=444444&bgColorDefault=f6f6f6&bgTextureDefault=flat&borderColorDefault=c5c5c5&fcDefault=454545&iconColorDefault=777777&bgColorHover=ededed&bgTextureHover=flat&borderColorHover=cccccc&fcHover=2b2b2b&iconColorHover=555555&bgColorActive=007fff&bgTextureActive=flat&borderColorActive=003eff&fcActive=ffffff&iconColorActive=ffffff&bgColorHighlight=fffa90&bgTextureHighlight=flat&borderColorHighlight=dad55e&fcHighlight=777620&iconColorHighlight=777620&bgColorError=fddfdf&bgTextureError=flat&borderColorError=f1a899&fcError=5f3f3f&iconColorError=cc0000&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=666666&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=5px&offsetTopShadow=0px&offsetLeftShadow=0px&cornerRadiusShadow=8px
5 | * Copyright jQuery Foundation and other contributors; Licensed MIT */
6 |
7 | .ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#2b2b2b;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#5f3f3f}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_777777_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_555555_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_777620_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cc0000_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:0 0 0 0;padding:5px;background:#666;opacity:.3;filter:Alpha(Opacity=30);border-radius:8px}
--------------------------------------------------------------------------------
/js/vendor/yargs-parser.js:
--------------------------------------------------------------------------------
1 | !function(n){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=n();else if("function"==typeof define&&define.amd)define([],n);else{var t;"undefined"!=typeof window?t=window:"undefined"!=typeof global?t=global:"undefined"!=typeof self&&(t=self),t.yargsParser=n()}}(function(){return function n(t,e,r){function o(c,u){if(!e[c]){if(!t[c]){var a="function"==typeof require&&require;if(!u&&a)return a(c,!0);if(i)return i(c,!0);var s=new Error("Cannot find module '"+c+"'");throw s.code="MODULE_NOT_FOUND",s}var f=e[c]={exports:{}};t[c][0].call(f.exports,function(n){var e=t[c][1][n];return o(e?e:n)},f,f.exports,n,t,e,r)}return e[c].exports}for(var i="function"==typeof require&&require,c=0;c=0;r--){var o=n[r];"."===o?n.splice(r,1):".."===o?(n.splice(r,1),e++):e&&(n.splice(r,1),e--)}if(t)for(;e--;e)n.unshift("..");return n}function r(n,t){if(n.filter)return n.filter(t);for(var e=[],r=0;r=-1&&!o;i--){var c=i>=0?arguments[i]:n.cwd();if("string"!=typeof c)throw new TypeError("Arguments to path.resolve must be strings");c&&(e=c+"/"+e,o="/"===c.charAt(0))}return e=t(r(e.split("/"),function(n){return!!n}),!o).join("/"),(o?"/":"")+e||"."},e.normalize=function(n){var o=e.isAbsolute(n),i="/"===c(n,-1);return n=t(r(n.split("/"),function(n){return!!n}),!o).join("/"),n||o||(n="."),n&&i&&(n+="/"),(o?"/":"")+n},e.isAbsolute=function(n){return"/"===n.charAt(0)},e.join=function(){var n=Array.prototype.slice.call(arguments,0);return e.normalize(r(n,function(n,t){if("string"!=typeof n)throw new TypeError("Arguments to path.join must be strings");return n}).join("/"))},e.relative=function(n,t){function r(n){for(var t=0;t=0&&""===n[e];e--);return t>e?[]:n.slice(t,e-t+1)}n=e.resolve(n).substr(1),t=e.resolve(t).substr(1);for(var o=r(n.split("/")),i=r(t.split("/")),c=Math.min(o.length,i.length),u=c,a=0;c>a;a++)if(o[a]!==i[a]){u=a;break}for(var s=[],a=u;at&&(t=n.length+t),n.substr(t,e)}}).call(this,n("_process"))},{_process:3}],3:[function(n,t,e){function r(){}var o=t.exports={};o.nextTick=function(){var n="undefined"!=typeof window&&window.setImmediate,t="undefined"!=typeof window&&window.MutationObserver,e="undefined"!=typeof window&&window.postMessage&&window.addEventListener;if(n)return function(n){return window.setImmediate(n)};var r=[];if(t){var o=document.createElement("div"),i=new MutationObserver(function(){var n=r.slice();r.length=0,n.forEach(function(n){n()})});return i.observe(o,{attributes:!0}),function(n){r.length||o.setAttribute("yes","no"),r.push(n)}}return e?(window.addEventListener("message",function(n){var t=n.source;if((t===window||null===t)&&"process-tick"===n.data&&(n.stopPropagation(),r.length>0)){var e=r.shift();e()}},!0),function(n){r.push(n),window.postMessage("process-tick","*")}):function(n){setTimeout(n,0)}}(),o.title="browser",o.browser=!0,o.env={},o.argv=[],o.on=r,o.addListener=r,o.once=r,o.off=r,o.removeListener=r,o.removeAllListeners=r,o.emit=r,o.binding=function(n){throw new Error("process.binding is not supported")},o.cwd=function(){return"/"},o.chdir=function(n){throw new Error("process.chdir is not supported")}},{}],4:[function(n,t,e){t.exports=function(n){return n&&"object"==typeof n&&"function"==typeof n.copy&&"function"==typeof n.fill&&"function"==typeof n.readUInt8}},{}],5:[function(n,t,e){(function(t,r){function o(n,t){var r={seen:[],stylize:c};return arguments.length>=3&&(r.depth=arguments[2]),arguments.length>=4&&(r.colors=arguments[3]),y(t)?r.showHidden=t:t&&e._extend(r,t),j(r.showHidden)&&(r.showHidden=!1),j(r.depth)&&(r.depth=2),j(r.colors)&&(r.colors=!1),j(r.customInspect)&&(r.customInspect=!0),r.colors&&(r.stylize=i),a(r,n,r.depth)}function i(n,t){var e=o.styles[t];return e?"["+o.colors[e][0]+"m"+n+"["+o.colors[e][1]+"m":n}function c(n,t){return n}function u(n){var t={};return n.forEach(function(n,e){t[n]=!0}),t}function a(n,t,r){if(n.customInspect&&t&&z(t.inspect)&&t.inspect!==e.inspect&&(!t.constructor||t.constructor.prototype!==t)){var o=t.inspect(r,n);return m(o)||(o=a(n,o,r)),o}var i=s(n,t);if(i)return i;var c=Object.keys(t),y=u(c);if(n.showHidden&&(c=Object.getOwnPropertyNames(t)),A(t)&&(c.indexOf("message")>=0||c.indexOf("description")>=0))return f(t);if(0===c.length){if(z(t)){var d=t.name?": "+t.name:"";return n.stylize("[Function"+d+"]","special")}if(E(t))return n.stylize(RegExp.prototype.toString.call(t),"regexp");if(x(t))return n.stylize(Date.prototype.toString.call(t),"date");if(A(t))return f(t)}var v="",b=!1,w=["{","}"];if(h(t)&&(b=!0,w=["[","]"]),z(t)){var j=t.name?": "+t.name:"";v=" [Function"+j+"]"}if(E(t)&&(v=" "+RegExp.prototype.toString.call(t)),x(t)&&(v=" "+Date.prototype.toUTCString.call(t)),A(t)&&(v=" "+f(t)),0===c.length&&(!b||0==t.length))return w[0]+v+w[1];if(0>r)return E(t)?n.stylize(RegExp.prototype.toString.call(t),"regexp"):n.stylize("[Object]","special");n.seen.push(t);var O;return O=b?l(n,t,r,y,c):c.map(function(e){return p(n,t,r,y,e,b)}),n.seen.pop(),g(O,v,w)}function s(n,t){if(j(t))return n.stylize("undefined","undefined");if(m(t)){var e="'"+JSON.stringify(t).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return n.stylize(e,"string")}return b(t)?n.stylize(""+t,"number"):y(t)?n.stylize(""+t,"boolean"):d(t)?n.stylize("null","null"):void 0}function f(n){return"["+Error.prototype.toString.call(n)+"]"}function l(n,t,e,r,o){for(var i=[],c=0,u=t.length;u>c;++c)$(t,String(c))?i.push(p(n,t,e,r,String(c),!0)):i.push("");return o.forEach(function(o){o.match(/^\d+$/)||i.push(p(n,t,e,r,o,!0))}),i}function p(n,t,e,r,o,i){var c,u,s;if(s=Object.getOwnPropertyDescriptor(t,o)||{value:t[o]},s.get?u=s.set?n.stylize("[Getter/Setter]","special"):n.stylize("[Getter]","special"):s.set&&(u=n.stylize("[Setter]","special")),$(r,o)||(c="["+o+"]"),u||(n.seen.indexOf(s.value)<0?(u=d(e)?a(n,s.value,null):a(n,s.value,e-1),u.indexOf("\n")>-1&&(u=i?u.split("\n").map(function(n){return" "+n}).join("\n").substr(2):"\n"+u.split("\n").map(function(n){return" "+n}).join("\n"))):u=n.stylize("[Circular]","special")),j(c)){if(i&&o.match(/^\d+$/))return u;c=JSON.stringify(""+o),c.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(c=c.substr(1,c.length-2),c=n.stylize(c,"name")):(c=c.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),c=n.stylize(c,"string"))}return c+": "+u}function g(n,t,e){var r=0,o=n.reduce(function(n,t){return r++,t.indexOf("\n")>=0&&r++,n+t.replace(/\u001b\[\d\d?m/g,"").length+1},0);return o>60?e[0]+(""===t?"":t+"\n ")+" "+n.join(",\n ")+" "+e[1]:e[0]+t+" "+n.join(", ")+" "+e[1]}function h(n){return Array.isArray(n)}function y(n){return"boolean"==typeof n}function d(n){return null===n}function v(n){return null==n}function b(n){return"number"==typeof n}function m(n){return"string"==typeof n}function w(n){return"symbol"==typeof n}function j(n){return void 0===n}function E(n){return O(n)&&"[object RegExp]"===_(n)}function O(n){return"object"==typeof n&&null!==n}function x(n){return O(n)&&"[object Date]"===_(n)}function A(n){return O(n)&&("[object Error]"===_(n)||n instanceof Error)}function z(n){return"function"==typeof n}function S(n){return null===n||"boolean"==typeof n||"number"==typeof n||"string"==typeof n||"symbol"==typeof n||"undefined"==typeof n}function _(n){return Object.prototype.toString.call(n)}function k(n){return 10>n?"0"+n.toString(10):n.toString(10)}function N(){var n=new Date,t=[k(n.getHours()),k(n.getMinutes()),k(n.getSeconds())].join(":");return[n.getDate(),F[n.getMonth()],t].join(" ")}function $(n,t){return Object.prototype.hasOwnProperty.call(n,t)}var D=/%[sdj%]/g;e.format=function(n){if(!m(n)){for(var t=[],e=0;e=i)return n;switch(n){case"%s":return String(r[e++]);case"%d":return Number(r[e++]);case"%j":try{return JSON.stringify(r[e++])}catch(t){return"[Circular]"}default:return n}}),u=r[e];i>e;u=r[++e])c+=d(u)||!O(u)?" "+u:" "+o(u);return c},e.deprecate=function(n,o){function i(){if(!c){if(t.throwDeprecation)throw new Error(o);t.traceDeprecation?console.trace(o):console.error(o),c=!0}return n.apply(this,arguments)}if(j(r.process))return function(){return e.deprecate(n,o).apply(this,arguments)};if(t.noDeprecation===!0)return n;var c=!1;return i};var C,B={};e.debuglog=function(n){if(j(C)&&(C=t.env.NODE_DEBUG||""),n=n.toUpperCase(),!B[n])if(new RegExp("\\b"+n+"\\b","i").test(C)){var r=t.pid;B[n]=function(){var t=e.format.apply(e,arguments);console.error("%s %d: %s",n,r,t)}}else B[n]=function(){};return B[n]},e.inspect=o,o.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},o.styles={special:"cyan",number:"yellow","boolean":"yellow",undefined:"grey","null":"bold",string:"green",date:"magenta",regexp:"red"},e.isArray=h,e.isBoolean=y,e.isNull=d,e.isNullOrUndefined=v,e.isNumber=b,e.isString=m,e.isSymbol=w,e.isUndefined=j,e.isRegExp=E,e.isObject=O,e.isDate=x,e.isError=A,e.isFunction=z,e.isPrimitive=S,e.isBuffer=n("./support/isBuffer");var F=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];e.log=function(){console.log("%s - %s",N(),e.format.apply(e,arguments))},e.inherits=n("inherits"),e._extend=function(n,t){if(!t||!O(t))return n;for(var e=Object.keys(t),r=e.length;r--;)n[e[r]]=t[e[r]];return n}}).call(this,n("_process"),"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./support/isBuffer":4,_process:3,inherits:1}],6:[function(n,t,e){(function(e){function r(t,r){function c(n,t,e){var r=E(t,L.nargs);e.length-(n+1)o;o++)g(t,e[o]);return n+r}function p(n,t,e){for(var r=n+1,o=n+1;o1&&N["dot-notation"]&&(L.aliases[o[0]]||[]).forEach(function(n){n=n.split(".");var t=[].concat(o);t.shift(),n=n.concat(t),w(M,n,r)}),E(n,L.normalize)&&!E(n,L.arrays)){var c=[n].concat(L.aliases[n]||[]);c.forEach(function(n){M.__defineSetter__(n,function(n){t=s.normalize(n)}),M.__defineGetter__(n,function(){return"string"==typeof t?s.normalize(t):t})})}}function h(t){var r={};b(r,L.aliases,$),Object.keys(L.configs).forEach(function(o){var i=t[o]||r[o];if(i)try{var c=null,u=s.resolve(e.cwd(),i);if("function"==typeof L.configs[o]){try{c=L.configs[o](u)}catch(a){c=a}if(c instanceof Error)return void(I=c)}else c=n(u);y(c)}catch(f){t[o]&&(I=Error(F("Invalid JSON config file: %s",i)))}})}function y(n,t){Object.keys(n).forEach(function(e){var r=n[e],o=t?t+"."+e:e;"[object Object]"===Object.prototype.toString.call(r)?y(r,o):m(M,o.split("."))&&!L.defaulted[o]||g(o,r)})}function d(){"undefined"!=typeof D&&D.forEach(function(n){y(n)})}function v(n,t){if("undefined"!=typeof C){var r="string"==typeof C?C:"";Object.keys(e.env).forEach(function(o){if(""===r||0===o.lastIndexOf(r,0)){var i=o.split("__").map(function(n,t){return 0===t&&(n=n.substring(r.length)),a(n)});!(t&&L.configs[i.join(".")]||!t)||m(n,i)&&!L.defaulted[i.join(".")]||g(i.join("."),e.env[o])}})}}function b(n,t,e){Object.keys(e).forEach(function(r){m(n,r.split("."))||(w(n,r.split("."),e[r]),(t[r]||[]).forEach(function(t){m(n,t.split("."))||w(n,t.split("."),e[r])}))})}function m(n,t){var e=n;N["dot-notation"]||(t=[t.join(".")]),t.slice(0,-1).forEach(function(n){e=e[n]||{}});var r=t[t.length-1];return"object"!=typeof e?!1:r in e}function w(n,t,e){var r=n;N["dot-notation"]||(t=[t.join(".")]),t.slice(0,-1).forEach(function(n){void 0===r[n]&&(r[n]={}),r=r[n]});var o=t[t.length-1];e===i?r[o]=i(r[o]):void 0===r[o]&&E(o,L.arrays)?r[o]=Array.isArray(e)?e:[e]:void 0===r[o]||E(o,L.bools)?r[o]=e:Array.isArray(r[o])?r[o].push(e):r[o]=[r[o],e]}function j(){Array.prototype.slice.call(arguments).forEach(function(n){Object.keys(n||{}).forEach(function(n){L.aliases[n]||(L.aliases[n]=[].concat(k[n]||[]),L.aliases[n].concat(n).forEach(function(t){if(/-/.test(t)&&N["camel-case-expansion"]){var e=a(t);L.aliases[n].push(e),B[e]=!0}}),L.aliases[n].forEach(function(t){L.aliases[t]=[n].concat(L.aliases[n].filter(function(n){return t!==n}))}))})})}function E(n,t){var e=!1,r=[].concat(L.aliases[n]||[],n);return r.forEach(function(n){t[n]&&(e=t[n])}),e}function O(n){[].concat(L.aliases[n]||[],n).forEach(function(n){L.defaulted[n]=!0})}function x(n){[].concat(L.aliases[n]||[],n).forEach(function(n){delete L.defaulted[n]})}function A(n){var t={"boolean":!0,string:"",number:void 0,array:[]};return t[n]}function z(n,t){var e="boolean";return t.strings&&t.strings[n]?e="string":t.numbers&&t.numbers[n]?e="number":t.arrays&&t.arrays[n]&&(e="array"),e}function S(n){return N["parse-numbers"]?"number"==typeof n?!0:/^0x[0-9a-f]+$/i.test(n)?!0:/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(n):!1}function _(n){return void 0===n}r||(r={}),t=f(t);var k=o(r.alias||{}),N=u({},{"short-option-groups":!0,"camel-case-expansion":!0,"dot-notation":!0,"parse-numbers":!0,"boolean-negation":!0},r.configuration),$=r["default"]||{},D=r.configObjects||[],C=r.envPrefix,B={},F=r.__||function(n){return l.format.apply(l,Array.prototype.slice.call(arguments))},I=null,L={aliases:{},arrays:{},bools:{},strings:{},numbers:{},counts:{},normalize:{},configs:{},defaulted:{},nargs:{}};[].concat(r.array).filter(Boolean).forEach(function(n){L.arrays[n]=!0}),[].concat(r["boolean"]).filter(Boolean).forEach(function(n){L.bools[n]=!0}),[].concat(r.string).filter(Boolean).forEach(function(n){L.strings[n]=!0}),[].concat(r.number).filter(Boolean).forEach(function(n){L.numbers[n]=!0}),[].concat(r.count).filter(Boolean).forEach(function(n){L.counts[n]=!0}),[].concat(r.normalize).filter(Boolean).forEach(function(n){L.normalize[n]=!0}),Object.keys(r.narg||{}).forEach(function(n){L.nargs[n]=r.narg[n]}),Array.isArray(r.config)||"string"==typeof r.config?[].concat(r.config).filter(Boolean).forEach(function(n){L.configs[n]=!0}):Object.keys(r.config||{}).forEach(function(n){L.configs[n]=r.config[n]}),j(r.key,k,r["default"],L.arrays),Object.keys($).forEach(function(n){(L.aliases[n]||[]).forEach(function(t){$[t]=$[n]})});var M={_:[]};Object.keys(L.bools).forEach(function(n){g(n,n in $?$[n]:!1),O(n)});var U=[];-1!==t.indexOf("--")&&(U=t.slice(t.indexOf("--")+1),t=t.slice(0,t.indexOf("--")));for(var P=0;PP+1?(t.splice(P+1,0,R[2]),P=p(P,R[1],t)):g(R[1],R[2]);else if(Z.match(/^--no-.+/)&&N["boolean-negation"])J=Z.match(/^--no-(.+)/)[1],g(J,!1);else if(Z.match(/^--.+/)||!N["short-option-groups"]&&Z.match(/^-.+/))J=Z.match(/^--?(.+)/)[1],E(J,L.nargs)?P=c(P,J,t):E(J,L.arrays)&&t.length>P+1?P=p(P,J,t):(H=t[P+1],void 0===H||H.match(/^-/)||E(J,L.bools)||E(J,L.counts)?/^(true|false)$/.test(H)?(g(J,H),P++):g(J,A(z(J,L))):(g(J,H),P++));else if(Z.match(/^-.\..+=/))R=Z.match(/^-([^=]+)=([\s\S]*)$/),g(R[1],R[2]);else if(Z.match(/^-.\..+/))H=t[P+1],J=Z.match(/^-(.\..+)/)[1],void 0===H||H.match(/^-/)||E(J,L.bools)||E(J,L.counts)?g(J,A(z(J,L))):(g(J,H),P++);else if(Z.match(/^-[^-]+/)){T=Z.slice(1,-1).split(""),G=!1;for(var W=0;WP+1?(t.splice(P+1,0,q),P=p(P,J,t)):g(J,q),G=!0;break}if("-"!==H){if(/[A-Za-z]/.test(T[W])&&/-?\d+(\.\d*)?(e-?\d+)?$/.test(H)){g(T[W],H),G=!0;break}if(T[W+1]&&T[W+1].match(/\W/)){g(T[W],H),G=!0;break}g(T[W],A(z(T[W],L)))}else g(T[W],H)}J=Z.slice(-1)[0],G||"-"===J||(E(J,L.nargs)?P=c(P,J,t):E(J,L.arrays)&&t.length>P+1?P=p(P,J,t):(H=t[P+1],void 0===H||/^(-|--)[^-]/.test(H)||E(J,L.bools)||E(J,L.counts)?/^(true|false)$/.test(H)?(g(J,H),P++):g(J,A(z(J,L))):(g(J,H),P++)))}else M._.push(L.strings._||!S(Z)?Z:Number(Z))}return v(M,!0),h(M),d(),v(M,!1),b(M,L.aliases,$),Object.keys(L.counts).forEach(function(n){g(n,$[n])}),U.forEach(function(n){M._.push(n)}),{argv:M,error:I,aliases:L.aliases,newAliases:B,configuration:N}}function o(n){var t=[],e=!0,r={};for(Object.keys(n).forEach(function(e){t.push([].concat(n[e],e))});e;){e=!1;for(var o=0;o-1&&n%1==0&&t>n}function o(n,t,e){var r=n[t];E.call(n,t)&&f(r,e)&&(void 0!==e||t in n)||(n[t]=e)}function i(n){return function(t){return null==t?void 0:t[n]}}function c(n,t,e,r){e||(e={});for(var i=-1,c=t.length;++i1?e[o-1]:void 0,c=o>2?e[2]:void 0;for(i="function"==typeof i?(o--,i):void 0,c&&a(e[0],e[1],c)&&(i=3>o?void 0:i,o=1),t=Object(t);++r-1&&n%1==0&&v>=n}function h(n){var t=typeof n;return!!n&&("object"==t||"function"==t)}var y=n("lodash.keys"),d=n("lodash.rest"),v=9007199254740991,b="[object Function]",m="[object GeneratorFunction]",w=/^(?:0|[1-9]\d*)$/,j=Object.prototype,E=j.hasOwnProperty,O=j.toString,x=j.propertyIsEnumerable,A=!x.call({valueOf:1},"valueOf"),z=i("length"),S=u(function(n,t){if(A||s(t)||l(t))return void c(t,y(t),n);for(var e in t)E.call(t,e)&&o(n,e,t[e])});t.exports=S},{"lodash.keys":10,"lodash.rest":11}],10:[function(n,t,e){function r(n,t){for(var e=-1,r=Array(n);++e-1&&n%1==0&&t>n}function i(n,t){return S.call(n,t)||"object"==typeof n&&t in n&&null===a(n)}function c(n){return $(Object(n))}function u(n){return function(t){return null==t?void 0:t[n]}}function a(n){return N(Object(n))}function s(n){var t=n?n.length:void 0;return y(t)&&(C(n)||b(n)||l(n))?r(t,String):null}function f(n){var t=n&&n.constructor,e="function"==typeof t&&t.prototype||z;return n===e}function l(n){return g(n)&&S.call(n,"callee")&&(!k.call(n,"callee")||_.call(n)==j)}function p(n){return null!=n&&y(D(n))&&!h(n)}function g(n){return v(n)&&p(n)}function h(n){var t=d(n)?_.call(n):"";return t==E||t==O}function y(n){return"number"==typeof n&&n>-1&&n%1==0&&w>=n}function d(n){var t=typeof n;return!!n&&("object"==t||"function"==t)}function v(n){return!!n&&"object"==typeof n}function b(n){return"string"==typeof n||!C(n)&&v(n)&&_.call(n)==x}function m(n){var t=f(n);if(!t&&!p(n))return c(n);var e=s(n),r=!!e,u=e||[],a=u.length;for(var l in n)!i(n,l)||r&&("length"==l||o(l,a))||t&&"constructor"==l||u.push(l);return u}var w=9007199254740991,j="[object Arguments]",E="[object Function]",O="[object GeneratorFunction]",x="[object String]",A=/^(?:0|[1-9]\d*)$/,z=Object.prototype,S=z.hasOwnProperty,_=z.toString,k=z.propertyIsEnumerable,N=Object.getPrototypeOf,$=Object.keys,D=u("length"),C=Array.isArray;t.exports=m},{}],11:[function(n,t,e){function r(n,t,e){var r=e.length;switch(r){case 0:return n.call(t);case 1:return n.call(t,e[0]);case 2:return n.call(t,e[0],e[1]);case 3:return n.call(t,e[0],e[1],e[2])}return n.apply(t,e)}function o(n,t){if("function"!=typeof n)throw new TypeError(l);return t=A(void 0===t?n.length-1:s(t),0),function(){for(var e=arguments,o=-1,i=A(e.length-t,0),c=Array(i);++on?-1:1;return t*g}var e=n%1;return n===n?e?n-e:n:0}function f(n){if("number"==typeof n)return n;if(a(n))return h;if(c(n)){var t=i(n.valueOf)?n.valueOf():n;n=c(t)?t+"":t}if("string"!=typeof n)return 0===n?n:+n;n=n.replace(b,"");var e=w.test(n);return e||j.test(n)?E(n.slice(2),e?2:8):m.test(n)?h:+n}var l="Expected a function",p=1/0,g=1.7976931348623157e308,h=NaN,y="[object Function]",d="[object GeneratorFunction]",v="[object Symbol]",b=/^\s+|\s+$/g,m=/^[-+]0x[0-9a-f]+$/i,w=/^0b[01]+$/i,j=/^0o[0-7]+$/i,E=parseInt,O=Object.prototype,x=O.toString,A=Math.max;t.exports=o},{}]},{},[6])(6)});
--------------------------------------------------------------------------------
/js/controlbox.js:
--------------------------------------------------------------------------------
1 | define(['vendor/yargs-parser', 'd3', 'demos'],
2 | function(_yargs, d3, demos) {
3 | "use strict";
4 |
5 | function yargs(str, opts) {
6 | var result = _yargs(str, opts)
7 |
8 | // make every value in result._ a string
9 | result._ = result._.map(function(val) {
10 | return "" + val
11 | })
12 |
13 | return result
14 | }
15 |
16 | /**
17 | * @class ControlBox
18 | * @constructor
19 | */
20 | function ControlBox(config) {
21 | this.historyView = config.historyView;
22 | this.originView = config.originView;
23 | this.initialMessage = config.initialMessage || 'Enter git commands below.';
24 | this._commandHistory = [];
25 | this._currentCommand = -1;
26 | this._tempCommand = '';
27 | this.rebaseConfig = {}; // to configure branches for rebase
28 |
29 | this.undoHistory = config.undoHistory || {
30 | pointer: 0,
31 | stack: [
32 | {
33 | hv: this.historyView.serialize(),
34 | ov: this.originView && this.originView.serialize()
35 | }
36 | ]
37 | }
38 |
39 | this.mode = 'local'
40 |
41 | this.historyView.on('lock', this.lock.bind(this))
42 | this.historyView.on('unlock', this.unlock.bind(this))
43 | }
44 |
45 | ControlBox.prototype = {
46 | lock: function () {
47 | this.locked = true
48 | },
49 |
50 | unlock: function () {
51 | this.locked = false
52 | this.createUndoSnapshot(true)
53 | },
54 |
55 | createUndoSnapshot: function (replace) {
56 | var state = {
57 | hv: this.historyView.serialize(),
58 | ov: (this.originView && this.originView.serialize()) || 'null'
59 | }
60 | if (!replace) {
61 | this.undoHistory.pointer++
62 | this.undoHistory.stack.length = this.undoHistory.pointer
63 | this.undoHistory.stack.push(state)
64 | } else {
65 | this.undoHistory.stack[this.undoHistory.pointer] = state
66 | }
67 |
68 | this.persist()
69 | },
70 |
71 | persist: function () {
72 | if (window.localStorage) {
73 | window.localStorage.setItem('git-viz-snapshot', JSON.stringify(this.undoHistory))
74 | }
75 | },
76 |
77 | getRepoView: function () {
78 | if (this.mode === 'local') {
79 | return this.historyView
80 | } else if (this.mode === 'origin') {
81 | return this.originView
82 | } else {
83 | throw new Error('invalid mode: ' + this.mode)
84 | }
85 | },
86 |
87 | changeMode: function (mode) {
88 | console.log(mode)
89 | if (mode === 'local' && this.historyView) {
90 | this.mode = 'local'
91 | } else if (mode === 'remote' && this.originView) {
92 | this.mode = 'origin'
93 | } else {
94 | throw new Error('invalid mode: ' + mode)
95 | }
96 | },
97 |
98 | render: function(container) {
99 | var cBox = this,
100 | cBoxContainer, log, input, selector;
101 |
102 | cBoxContainer = container.append('div')
103 | .classed('control-box', true);
104 |
105 | selector = cBoxContainer.append('select')
106 | .classed('scenario-chooser', true)
107 |
108 | demos.forEach(function (demo) {
109 | var opt = selector.append('option')
110 | .text(demo.title)
111 | .attr('value', demo.key)
112 | if (window.location.hash === ('#' + demo.key)) {
113 | opt.attr('selected', 'selected')
114 | }
115 | })
116 |
117 | selector.on('change', function () {
118 | if (!confirm('This will erase your current progress. Continue?')) {
119 | d3.event.preventDefault()
120 | d3.event.stopPropagation()
121 | selector.node().value = window.location.hash.replace(/^#/, '') || demos[0].key
122 | return false
123 | }
124 | var currentDemo = window.location.hash
125 | var sel = selector.node()
126 | var newDemo = sel.options[sel.selectedIndex].value
127 | if (('#' + newDemo) !== currentDemo) {
128 | window.location.hash = newDemo
129 | }
130 | })
131 |
132 | log = cBoxContainer.append('div')
133 | .classed('log', true);
134 |
135 | input = cBoxContainer.append('input')
136 | .attr('type', 'text')
137 | .classed('input', true)
138 | .attr('placeholder', 'enter git command');
139 |
140 | log.on('click', function () {
141 | if (d3.event.target === log.node()) {
142 | input.node().focus()
143 | }
144 | })
145 |
146 | setTimeout(function() {
147 | input.node().focus()
148 | })
149 |
150 | input.on('keyup', function() {
151 | var e = d3.event;
152 |
153 | switch (e.keyCode) {
154 | case 13:
155 | if (this.value.trim() === '' || cBox.locked) {
156 | return;
157 | }
158 |
159 | cBox._commandHistory.unshift(this.value);
160 | cBox._tempCommand = '';
161 | cBox._currentCommand = -1;
162 | cBox.command(this.value);
163 | this.value = '';
164 | e.stopImmediatePropagation();
165 | break;
166 | case 38:
167 | var previousCommand = cBox._commandHistory[cBox._currentCommand + 1];
168 | if (cBox._currentCommand === -1) {
169 | cBox._tempCommand = this.value;
170 | }
171 |
172 | if (typeof previousCommand === 'string') {
173 | cBox._currentCommand += 1;
174 | this.value = previousCommand;
175 | this.value = this.value; // set cursor to end
176 | }
177 | e.stopImmediatePropagation();
178 | break;
179 | case 40:
180 | var nextCommand = cBox._commandHistory[cBox._currentCommand - 1];
181 | if (typeof nextCommand === 'string') {
182 | cBox._currentCommand -= 1;
183 | this.value = nextCommand;
184 | this.value = this.value; // set cursor to end
185 | } else {
186 | cBox._currentCommand = -1;
187 | this.value = cBox._tempCommand;
188 | this.value = this.value; // set cursor to end
189 | }
190 | e.stopImmediatePropagation();
191 | break;
192 | default:
193 | document.getElementById('last-command').textContent = document.querySelectorAll(".control-box .input")[0].value
194 | }
195 |
196 | });
197 |
198 | this.container = cBoxContainer;
199 | this.terminalOutput = log;
200 | this.input = input;
201 |
202 | this.info(this.initialMessage);
203 | },
204 |
205 | destroy: function() {
206 | this.terminalOutput.remove();
207 | this.input.remove();
208 | this.container.remove();
209 |
210 | for (var prop in this) {
211 | if (this.hasOwnProperty(prop)) {
212 | this[prop] = null;
213 | }
214 | }
215 | },
216 |
217 | _scrollToBottom: function() {
218 | var log = this.terminalOutput.node();
219 | log.scrollTop = log.scrollHeight;
220 | },
221 |
222 | command: function(entry) {
223 | entry = entry.trim()
224 | if (entry === '') {
225 | return;
226 | }
227 |
228 | document.getElementById('last-command').textContent = entry
229 |
230 | if (entry.trim() === 'help' || entry.trim() === 'help()') {
231 | this.info('pres() = Turn on presenter mode')
232 | this.info('undo = Undo the last git command')
233 | this.info('redo = Redo the last undone git command')
234 | this.info('mode = Change mode (`local` or `remote`)')
235 | this.info('clear = Clear the history pane and reset the visualization')
236 | this.info()
237 | this.info('Available Git Commands:')
238 | this.info('`git branch`')
239 | this.info('`git checkout`')
240 | this.info('`git cherry_pick`')
241 | this.info('`git commit`')
242 | this.info('`git fetch`')
243 | this.info('`git log`')
244 | this.info('`git merge`')
245 | this.info('`git pull`')
246 | this.info('`git push`')
247 | this.info('`git rebase`')
248 | this.info('`git reflog`')
249 | this.info('`git reset`')
250 | this.info('`git rev_parse`')
251 | this.info('`git revert`')
252 | this.info('`git tag`')
253 | return
254 | }
255 |
256 | if (entry === 'pres()') {
257 | window.pres()
258 | return
259 | }
260 |
261 | if (entry.toLowerCase().indexOf('mode ') === 0) {
262 | var mode = entry.split(' ').pop()
263 | this.changeMode(mode)
264 | return
265 | }
266 |
267 | if (entry.toLowerCase() === 'undo') {
268 | var lastId = this.undoHistory.pointer - 1
269 | var lastState = this.undoHistory.stack[lastId]
270 | if (lastState) {
271 | this.historyView.deserialize(lastState.hv)
272 | this.originView && this.originView.deserialize(lastState.ov)
273 | this.undoHistory.pointer = lastId
274 | } else {
275 | this.error("Nothing to undo")
276 | }
277 | this.persist()
278 | this.terminalOutput.append('div')
279 | .classed('command-entry', true)
280 | .html(entry);
281 | this._scrollToBottom();
282 | return
283 | }
284 |
285 | if (entry.toLowerCase() === 'redo') {
286 | var lastId = this.undoHistory.pointer + 1
287 | var lastState = this.undoHistory.stack[lastId]
288 | if (lastState) {
289 | this.historyView.deserialize(lastState.hv)
290 | this.originView && this.originView.deserialize(lastState.ov)
291 | this.undoHistory.pointer = lastId
292 | } else {
293 | this.error("Nothing to redo")
294 | }
295 | this.persist()
296 | this.terminalOutput.append('div')
297 | .classed('command-entry', true)
298 | .html(entry);
299 | this._scrollToBottom();
300 | return
301 | }
302 |
303 | if (entry.toLowerCase() === 'clear') {
304 | window.resetVis()
305 | return
306 | }
307 |
308 | var split = entry.split(' ');
309 |
310 | this.terminalOutput.append('div')
311 | .classed('command-entry', true)
312 | .html(entry);
313 |
314 | this._scrollToBottom();
315 |
316 | if (split[0] !== 'git') {
317 | return this.error();
318 | }
319 |
320 | var method = split[1].replace(/-/g, '_'),
321 | args = split.slice(2),
322 | argsStr = args.join(' ')
323 |
324 | var options = yargs(argsStr)
325 |
326 | try {
327 | if (typeof this[method] === 'function') {
328 | this[method](args, options, argsStr);
329 | this.createUndoSnapshot()
330 | } else {
331 | this.error();
332 | }
333 | } catch (ex) {
334 | console.error(ex.stack)
335 | var msg = (ex && ex.message) ? ex.message : null;
336 | this.error(msg);
337 | }
338 | },
339 |
340 | info: function(msg) {
341 | this.terminalOutput.append('div').classed('info', true).html(msg);
342 | this._scrollToBottom();
343 | },
344 |
345 | error: function(msg) {
346 | msg = msg || 'I don\'t understand that.';
347 | this.terminalOutput.append('div').classed('error', true).html(msg);
348 | this._scrollToBottom();
349 | },
350 |
351 | transact: function(action, after) {
352 | var oldCommit = this.getRepoView().getCommit('HEAD')
353 | var oldBranch = this.getRepoView().currentBranch
354 | var oldRef = oldBranch || oldCommit.id
355 | action.call(this)
356 | var newCommit = this.getRepoView().getCommit('HEAD')
357 | var newBranch = this.getRepoView().currentBranch
358 | var newRef = newBranch || newCommit.id
359 | after.call(this, {
360 | commit: oldCommit,
361 | branch: oldBranch,
362 | ref: oldRef
363 | }, {
364 | commit: newCommit,
365 | branch: newBranch,
366 | ref: newRef
367 | })
368 | },
369 |
370 | commit: function(args, opts, cmdStr) {
371 | opts = yargs(cmdStr, {
372 | boolean: ['amend'],
373 | string: ['m']
374 | })
375 | var msg = ""
376 | this.transact(function() {
377 | if (opts.amend) {
378 | this.getRepoView().amendCommit(opts.m || this.getRepoView().getCommit('head').message)
379 | } else {
380 | this.getRepoView().commit(null, opts.m);
381 | }
382 | }, function(before, after) {
383 | var reflogMsg = 'commit: ' + msg
384 | this.getRepoView().addReflogEntry(
385 | 'HEAD', after.commit.id, reflogMsg
386 | )
387 | if(before.branch) {
388 | this.getRepoView().addReflogEntry(
389 | before.branch, after.commit.id, reflogMsg
390 | )
391 | }
392 | })
393 | },
394 |
395 | log: function(args) {
396 | if (args.length > 1) {
397 | return this.error("'git log' can take at most one argument in this tool")
398 | }
399 | var logs = this.getRepoView().getLogEntries(args[0] || 'head')
400 | .map(function(l) {
401 | return "> " + l + ""
402 | }).join('')
403 | this.info(logs)
404 | },
405 |
406 | rev_parse: function(args) {
407 | args.forEach(function(arg) {
408 | this.info(this.getRepoView().revparse(arg))
409 | }, this)
410 | },
411 |
412 | cherry_pick: function (args, opt, cmdStr) {
413 | opt = yargs(cmdStr, {
414 | number: ['m']
415 | })
416 |
417 | if (!opt._.length) {
418 | this.error('You must specify one or more commits to cherry-pick');
419 | return
420 | }
421 |
422 | if (opt.m !== undefined && isNaN(opt.m)) {
423 | this.error("switch 'm' expects a numerical value");
424 | return
425 | }
426 |
427 | // FIXME: because `cherryPick` is asynchronous,
428 | // it is responsible for its own reflog entries
429 | this.getRepoView().cherryPick(opt._, opt.m);
430 | },
431 |
432 | branch: function(args, options, cmdStr) {
433 | options = yargs(cmdStr, {
434 | alias: { delete: ['d'], remote: ['r'], all: ['a'] },
435 | boolean: ['a', 'r']
436 | })
437 | var branchName = options._[0]
438 | var startPoint = options._[1] || 'head'
439 |
440 | if (options.delete) {
441 | return this.getRepoView().deleteBranch(options.delete);
442 | }
443 |
444 | if (options._[2]) {
445 | return this.error('Incorrect usage - supplied too many arguments')
446 | }
447 |
448 | if (!branchName) {
449 | var branches
450 | if (options.remote) {
451 | branches = this.getRepoView().getBranchList().filter(function (b) {
452 | return b.indexOf(' origin/') === 0
453 | }).join('
')
454 | } else if (options.all) {
455 | branches = this.getRepoView().getBranchList().join('
')
456 | } else {
457 | branches = this.getRepoView().getBranchList().filter(function(b) {
458 | return b.indexOf(' origin/') !== 0
459 | }).join('
')
460 | }
461 | return this.info(branches)
462 | }
463 |
464 | this.transact(function() {
465 | this.getRepoView().branch(branchName, startPoint)
466 | }, function(before, after) {
467 | var branchCommit = this.getRepoView().getCommit(branchName)
468 | var reflogMsg = "branch: created from " + before.ref
469 | this.getRepoView().addReflogEntry(branchName, branchCommit.id, reflogMsg)
470 | })
471 |
472 | },
473 |
474 | checkout: function(args, opts) {
475 | if (opts.b) {
476 | if (opts._[0]) {
477 | this.branch(null, null, opts.b + ' ' + opts._[0])
478 | } else {
479 | this.branch(null, null, opts.b)
480 | }
481 | }
482 |
483 | var name = opts.b || opts._[0]
484 |
485 | this.transact(function() {
486 | this.getRepoView().checkout(name);
487 | }, function(before, after) {
488 | this.getRepoView().addReflogEntry(
489 | 'HEAD', after.commit.id,
490 | 'checkout: moving from ' + before.ref +
491 | ' to ' + name
492 | )
493 | })
494 | },
495 |
496 | tag: function(args) {
497 | if (args.length < 1) {
498 | this.info(
499 | 'You need to give a tag name. ' +
500 | 'Normally if you don\'t give a name, ' +
501 | 'this command will list your local tags on the screen.'
502 | );
503 |
504 | return;
505 | }
506 |
507 | while (args.length > 0) {
508 | var arg = args.shift();
509 |
510 | try {
511 | this.getRepoView().tag(arg);
512 | } catch (err) {
513 | if (err.message.indexOf('already exists') === -1) {
514 | throw new Error(err.message);
515 | }
516 | }
517 | }
518 | },
519 |
520 | doReset: function (name) {
521 | this.transact(function() {
522 | this.getRepoView().reset(name);
523 | }, function(before, after) {
524 | var reflogMsg = "reset: moving to " + name
525 | this.getRepoView().addReflogEntry(
526 | 'HEAD', after.commit.id, reflogMsg
527 | )
528 | if (before.branch) {
529 | this.getRepoView().addReflogEntry(
530 | before.branch, after.commit.id, reflogMsg
531 | )
532 | }
533 | })
534 | },
535 |
536 | reset: function(args) {
537 | while (args.length > 0) {
538 | var arg = args.shift();
539 |
540 | switch (arg) {
541 | case '--soft':
542 | this.info(
543 | 'The "--soft" flag works in real git, but ' +
544 | 'I am unable to show you how it works in this demo. ' +
545 | 'So I am just going to show you what "--hard" looks like instead.'
546 | );
547 | break;
548 | case '--mixed':
549 | this.info(
550 | 'The "--mixed" flag works in real git, but ' +
551 | 'I am unable to show you how it works in this demo. ' +
552 | 'So I am just going to show you what "--hard" looks like instead.'
553 | );
554 | break;
555 | case '--hard':
556 | this.doReset(args.join(' '));
557 | args.length = 0;
558 | break;
559 | default:
560 | var remainingArgs = [arg].concat(args);
561 | args.length = 0;
562 | this.info('Assuming "--hard".');
563 | this.doReset(remainingArgs.join(' '));
564 | }
565 | }
566 | },
567 |
568 | clean: function(args) {
569 | this.info('Deleting all of your untracked files...');
570 | },
571 |
572 | revert: function(args, opt, cmdStr) {
573 | opt = yargs(cmdStr, {
574 | number: ['m']
575 | })
576 |
577 | if (!opt._.length) {
578 | this.error('You must specify a commit to revert');
579 | return
580 | }
581 |
582 | if (opt.m !== undefined && isNaN(opt.m)) {
583 | this.error("switch 'm' expects a numerical value");
584 | return
585 | }
586 |
587 | this.transact(function() {
588 | this.getRepoView().revert(opt._, opt.m);
589 | }, function(before, after) {
590 | var reflogMsg = 'revert: ' + before.commit.message || before.commit.id
591 | this.getRepoView().addReflogEntry(
592 | 'HEAD', after.commit.id, reflogMsg
593 | )
594 | if(before.branch) {
595 | this.getRepoView().addReflogEntry(
596 | before.branch, after.commit.id, reflogMsg
597 | )
598 | }
599 | })
600 | },
601 |
602 | merge: function(args) {
603 | var noFF = false;
604 | var branch = args[0];
605 | var result
606 | if (args.length === 2) {
607 | if (args[0] === '--no-ff') {
608 | noFF = true;
609 | branch = args[1];
610 | } else if (args[1] === '--no-ff') {
611 | noFF = true;
612 | branch = args[0];
613 | } else {
614 | this.info('This demo only supports the --no-ff switch..');
615 | }
616 | }
617 |
618 | this.transact(function() {
619 | result = this.getRepoView().merge(branch, noFF);
620 |
621 | if (result === 'Fast-Forward') {
622 | this.info('You have performed a fast-forward merge.');
623 | }
624 | }, function(before, after) {
625 | var reflogMsg = "merge " + branch + ": "
626 | if (result === 'Fast-Forward') {
627 | reflogMsg += "Fast-forward"
628 | } else {
629 | reflogMsg += "Merge made by the 'recursive' strategy."
630 | }
631 | this.getRepoView().addReflogEntry(
632 | 'HEAD', after.commit.id, reflogMsg
633 | )
634 | if (before.branch) {
635 | this.getRepoView().addReflogEntry(
636 | before.branch, after.commit.id, reflogMsg
637 | )
638 | }
639 | })
640 | },
641 |
642 | rebase: function(args) {
643 | var ref = args.shift(),
644 | result = this.getRepoView().rebase(ref);
645 |
646 | // FIXME: rebase is async, so manages its own
647 | // reflog entries
648 | if (result === 'Fast-Forward') {
649 | this.info('Fast-forwarded to ' + ref + '.');
650 | }
651 | },
652 |
653 | fetch: function() {
654 | if (this.mode !== 'local') {
655 | throw new Error('can only fetch from local')
656 | }
657 | if (!this.originView) {
658 | throw new Error('There is no remote server to fetch from.');
659 | }
660 |
661 | var origin = this.originView,
662 | local = this.historyView,
663 | remotePattern = /^origin\/([^\/]+)$/,
664 | rtb, isRTB, fb,
665 | fetchBranches = {},
666 | fetchIds = [], // just to make sure we don't fetch the same commit twice
667 | fetchCommits = [],
668 | fetchCommit,
669 | resultMessage = '';
670 |
671 | // determine which branches to fetch
672 | for (rtb = 0; rtb < local.branches.length; rtb++) {
673 | isRTB = remotePattern.exec(local.branches[rtb]);
674 | if (isRTB) {
675 | fetchBranches[isRTB[1]] = 0;
676 | }
677 | }
678 |
679 | // determine which commits the local repo is missing from the origin
680 | function checkCommit (commit, branch) {
681 | var notInLocal = local.getCommit(commit.id) === null
682 | if (notInLocal && commit.id) {
683 | if (fetchIds.indexOf(commit.id) === -1) {
684 | fetchCommits.unshift(commit)
685 | fetchIds.unshift(commit.id)
686 | }
687 | fetchBranches[branch] += 1
688 | commit.parent && checkCommit(origin.getCommit(commit.parent), branch)
689 | commit.parent2 && checkCommit(origin.getCommit(commit.parent2), branch)
690 | }
691 | }
692 |
693 | for (fb in fetchBranches) {
694 | if (origin.branches.indexOf(fb) > -1) {
695 | checkCommit(origin.getCommit(fb), fb)
696 | }
697 | }
698 |
699 | // add the fetched commits to the local commit data
700 | for (var fc = 0; fc < fetchCommits.length; fc++) {
701 | fetchCommit = fetchCommits[fc];
702 | local.commitData.push({
703 | id: fetchCommit.id,
704 | parent: fetchCommit.parent,
705 | parent2: fetchCommit.parent2,
706 | tags: []
707 | });
708 | }
709 |
710 | // update the remote tracking branch tag locations
711 | for (fb in fetchBranches) {
712 | if (origin.branches.indexOf(fb) > -1) {
713 | var remoteLoc = origin.getCommit(fb).id;
714 | local.moveTag('origin/' + fb, remoteLoc);
715 | }
716 |
717 | resultMessage += 'Fetched ' + fetchBranches[fb] + ' commits on ' + fb + '.';
718 | }
719 |
720 | this.info(resultMessage);
721 |
722 | local.renderCommits();
723 | },
724 |
725 | pull: function(args) {
726 | if (this.mode !== 'local') {
727 | throw new Error('can only pull from local')
728 | }
729 | var control = this,
730 | local = this.historyView,
731 | currentBranch = local.currentBranch,
732 | rtBranch = 'origin/' + currentBranch,
733 | isFastForward = false;
734 |
735 | this.fetch();
736 |
737 | if (!currentBranch) {
738 | throw new Error('You are not currently on a branch.');
739 | }
740 |
741 | if (local.branches.indexOf(rtBranch) === -1) {
742 | throw new Error('Current branch is not set up for pulling.');
743 | }
744 |
745 | this.lock()
746 | setTimeout(function() {
747 | try {
748 | if (args[0] === '--rebase' || control.rebaseConfig[currentBranch] === 'true') {
749 | isFastForward = local.rebase(rtBranch) === 'Fast-Forward';
750 | } else {
751 | isFastForward = local.merge(rtBranch) === 'Fast-Forward';
752 | }
753 | } catch (error) {
754 | control.error(error.message);
755 | } finally {
756 | this.unlock()
757 | }
758 |
759 | if (isFastForward) {
760 | control.info('Fast-forwarded to ' + rtBranch + '.');
761 | }
762 | }.bind(this), 750);
763 | },
764 |
765 | push: function(args, opts, cmdStr) {
766 | var opt = yargs(cmdStr, {
767 | alias: { force: ['f'], upstream: ['u'] },
768 | boolean: ['f', 'u']
769 | })
770 |
771 | if (this.mode !== 'local') {
772 | throw new Error('can only push from local')
773 | }
774 | var control = this,
775 | local = this.historyView,
776 | remoteName = opt._[0] || 'origin',
777 | remote = this[remoteName + 'View'],
778 | branchArgs = opt._[1],
779 | localRef = local.currentBranch,
780 | remoteRef = local.currentBranch,
781 | localCommit, remoteCommit,
782 | findCommitsToPush,
783 | isCommonCommit,
784 | idsToPush = [],
785 | toPush = [];
786 |
787 | if (remoteName === 'history') {
788 | throw new Error('Sorry, you can\'t have a remote named "history" in this example.');
789 | }
790 |
791 | if (!remote) {
792 | throw new Error('There is no remote server named "' + remoteName + '".');
793 | }
794 |
795 | if (remote.branches.indexOf(remoteRef) === -1) {
796 | remote.branch(remoteRef, 'e137e9b')
797 | }
798 |
799 | if (branchArgs) {
800 | branchArgs = /^([^:]*)(:?)(.*)$/.exec(branchArgs);
801 |
802 | branchArgs[1] && (localRef = branchArgs[1]);
803 | branchArgs[2] === ':' && (remoteRef = branchArgs[3]);
804 | }
805 |
806 | if (local.branches.indexOf(localRef) === -1) {
807 | throw new Error('Local ref: ' + localRef + ' does not exist.');
808 | }
809 |
810 | if (!remoteRef) {
811 | throw new Error('No remote branch was specified to push to.');
812 | }
813 |
814 | localCommit = local.getCommit(localRef);
815 | remoteCommit = remote.getCommit(remoteRef);
816 |
817 | findCommitsToPush = function findCommitsToPush(localCommit) {
818 | var alreadyPushed = remote.getCommit(localCommit.id) !== null
819 | if (!alreadyPushed && idsToPush.indexOf(localCommit.id) === -1) {
820 | idsToPush.push(localCommit.id)
821 |
822 | toPush.push(Object.assign({}, localCommit, {tags: []}))
823 |
824 | localCommit.parent && findCommitsToPush(local.getCommit(localCommit.parent))
825 | localCommit.parent2 && findCommitsToPush(local.getCommit(localCommit.parent2))
826 | }
827 | }
828 |
829 | // push to an existing branch on the remote
830 | if (remoteCommit && remote.branches.indexOf(remoteRef) > -1) {
831 | if (!local.isAncestorOf(remoteCommit.id, localCommit.id) && !opt.f) {
832 | throw new Error('Push rejected. Non fast-forward. Try pulling first');
833 | }
834 |
835 | isCommonCommit = localCommit.id === remoteCommit.id;
836 |
837 | if (isCommonCommit) {
838 | return this.info('Everything up-to-date.');
839 | }
840 |
841 | if (!opt.f) {
842 | findCommitsToPush(localCommit);
843 | remote.commitData = remote.commitData.concat(toPush);
844 | } else {
845 | var localData = JSON.parse(JSON.stringify(local.commitData))
846 | localData.forEach(function(commit) {
847 | var originTagIndex = commit.tags.indexOf('origin/' + localRef)
848 | if (originTagIndex > -1) {
849 | commit.tags.splice(originTagIndex, 1)
850 | }
851 | })
852 | remote.commitData = localData
853 | this.info('forced update')
854 | }
855 |
856 | remote.moveTag(remoteRef, localCommit.id);
857 | local.moveTag('origin/' + localRef, localRef)
858 | remote.renderCommits();
859 | local.renderTags()
860 | }
861 | },
862 |
863 | config: function(args) {
864 | var path = args.shift().split('.');
865 |
866 | if (path[0] === 'branch') {
867 | if (path[2] === 'rebase') {
868 | this.rebase[path[1]] = args.pop();
869 | }
870 | }
871 | },
872 |
873 | reflog: function (args) {
874 | var reflogExistsFor = function (ref) {
875 | return this.getRepoView().logs[ref.toLowerCase()]
876 | }.bind(this)
877 |
878 | var ref = ""
879 | var subcommand = "show"
880 | if (args.length === 0) {
881 | ref = "HEAD"
882 | } else if (args.length === 1) {
883 | ref = args[0].trim()
884 | if (ref === "show" || ref === "expire" || ref === "delete" || ref === "exists") {
885 | subcommand = ref
886 | ref = "HEAD"
887 | }
888 | } else if (args.length === 2) {
889 | subcommand = args[0]
890 | ref = args[1]
891 | } else {
892 | this.error("'git reflog' can take at most two arguments in this tool")
893 | return
894 | }
895 |
896 | if (!ref) {
897 | this.error("No ref specified")
898 | return
899 | }
900 |
901 | if (subcommand === "exists") {
902 | if (reflogExistsFor(ref)) {
903 | this.info("Reflog for ref " + ref + " exists")
904 | } else {
905 | this.error("Reflog for ref " + ref + " does not exist")
906 | }
907 | } else if (subcommand === "show") {
908 | var logs = this.getRepoView().getReflogEntries(ref)
909 | this.info(
910 | logs.map(function(l) {
911 | return "> " + l + ""
912 | }).join('')
913 | )
914 | } else if (subcommand === "expire" || subcommand === "delete") {
915 | this.info("Real git reflog supports the '" + subcommand +
916 | "' subcommand but this tool only supports 'show' and 'exists'")
917 | }
918 | }
919 | };
920 |
921 | return ControlBox;
922 | });
923 |
--------------------------------------------------------------------------------
/js/historyview.js:
--------------------------------------------------------------------------------
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, getUniqueSetItems;
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 || cy(parent, view),
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 (commit.isNoFFBranch === true) {
97 | branchIndex++;
98 | }
99 | if (commit.isNoFFCommit === true) {
100 | branchIndex--;
101 | }
102 |
103 | if (parentCY === baseLine) {
104 | var direction = 1;
105 | for (var bi = 0; bi < branchIndex; bi++) {
106 | direction *= -1;
107 | }
108 |
109 | shift *= Math.ceil(branchIndex / 2);
110 |
111 | return parentCY + (shift * direction);
112 | }
113 |
114 | if (parentCY < baseLine) {
115 | return parentCY - (shift * branchIndex);
116 | } else if (parentCY > baseLine) {
117 | return parentCY + (shift * branchIndex);
118 | }
119 | };
120 |
121 | fixCirclePosition = function(selection) {
122 | selection
123 | .attr('cx', function(d) {
124 | return d.cx;
125 | })
126 | .attr('cy', function(d) {
127 | return d.cy;
128 | });
129 | };
130 |
131 | // calculates the x1 point for commit pointer lines
132 | px1 = function(commit, view, pp) {
133 | pp = pp || 'parent';
134 |
135 | var parent = view.getCommit(commit[pp]),
136 | startCX = commit.cx,
137 | diffX = startCX - parent.cx,
138 | diffY = parent.cy - commit.cy,
139 | length = Math.sqrt((diffX * diffX) + (diffY * diffY));
140 |
141 | return startCX - (view.pointerMargin * (diffX / length));
142 | };
143 |
144 | // calculates the y1 point for commit pointer lines
145 | py1 = function(commit, view, pp) {
146 | pp = pp || 'parent';
147 |
148 | var parent = view.getCommit(commit[pp]),
149 | startCY = commit.cy,
150 | diffX = commit.cx - parent.cx,
151 | diffY = parent.cy - startCY,
152 | length = Math.sqrt((diffX * diffX) + (diffY * diffY));
153 |
154 | return startCY + (view.pointerMargin * (diffY / length));
155 | };
156 |
157 | fixPointerStartPosition = function(selection, view) {
158 | selection.attr('x1', function(d) {
159 | return px1(d, view);
160 | }).attr('y1', function(d) {
161 | return py1(d, view);
162 | });
163 | };
164 |
165 | px2 = function(commit, view, pp) {
166 | pp = pp || 'parent';
167 |
168 | var parent = view.getCommit(commit[pp]),
169 | endCX = parent.cx,
170 | diffX = commit.cx - endCX,
171 | diffY = parent.cy - commit.cy,
172 | length = Math.sqrt((diffX * diffX) + (diffY * diffY));
173 |
174 | return endCX + (view.pointerMargin * 1.2 * (diffX / length));
175 | };
176 |
177 | py2 = function(commit, view, pp) {
178 | pp = pp || 'parent';
179 |
180 | var parent = view.getCommit(commit[pp]),
181 | endCY = parent.cy,
182 | diffX = commit.cx - parent.cx,
183 | diffY = endCY - commit.cy,
184 | length = Math.sqrt((diffX * diffX) + (diffY * diffY));
185 |
186 | return endCY - (view.pointerMargin * 1.2 * (diffY / length));
187 | };
188 |
189 | fixPointerEndPosition = function(selection, view) {
190 | selection.attr('x2', function(d) {
191 | return px2(d, view);
192 | }).attr('y2', function(d) {
193 | return py2(d, view);
194 | });
195 | };
196 |
197 | fixIdPosition = function(selection, view, delta) {
198 | selection.attr('x', function(d) {
199 | return d.cx;
200 | }).attr('y', function(d) {
201 | return d.cy + view.commitRadius + delta;
202 | });
203 | };
204 |
205 | tagY = function tagY(t, view) {
206 | var commit = view.getCommit(t.commit),
207 | commitCY = commit.cy,
208 | tags = commit.tags,
209 | tagIndex = tags.indexOf(t.name);
210 |
211 | if (tagIndex === -1) {
212 | tagIndex = tags.length;
213 | }
214 |
215 | if (commitCY < (view.baseLine)) {
216 | return commitCY - 45 - (tagIndex * 25);
217 | } else {
218 | return commitCY + 50 + (tagIndex * 25);
219 | }
220 | };
221 |
222 | getUniqueSetItems = function(set1, set2) {
223 | var uniqueSet1 = JSON.parse(JSON.stringify(set1))
224 | var uniqueSet2 = JSON.parse(JSON.stringify(set2))
225 | for (var id in set1) {
226 | delete uniqueSet2[id]
227 | }
228 | for (var id in set2) {
229 | delete uniqueSet1[id]
230 | }
231 | return [uniqueSet1, uniqueSet2]
232 | };
233 |
234 | /**
235 | * @class HistoryView
236 | * @constructor
237 | */
238 | function HistoryView(config) {
239 | var commitData = config.commitData || [],
240 | commit, branch;
241 |
242 | this.branches = [];
243 | for (var i = 0; i < commitData.length; i++) {
244 | commit = commitData[i];
245 | !commit.parent && (commit.parent = 'initial');
246 | !commit.tags && (commit.tags = []);
247 | for (var j = 0; j < commit.tags.length; j++) {
248 | branch = commit.tags[j]
249 | if (branch.indexOf('[') !== 0 && this.branches.indexOf(branch) === -1) {
250 | this.branches.push(branch)
251 | }
252 | }
253 | }
254 |
255 | this.name = config.name || 'UnnamedHistoryView';
256 | this.commitData = commitData;
257 |
258 | this.currentBranch = config.currentBranch || 'master';
259 |
260 | this.width = config.width;
261 | this.height = config.height || 400;
262 | this.orginalBaseLine = config.baseLine;
263 | this.baseLine = this.height * (config.baseLine || 0.9);
264 |
265 | this.commitRadius = config.commitRadius || 20;
266 | this.pointerMargin = this.commitRadius * 1.3;
267 |
268 | this.isRemote = typeof config.remoteName === 'string';
269 | this.remoteName = config.remoteName;
270 |
271 | this.logs = {}
272 |
273 | this.initialCommit = {
274 | id: 'initial',
275 | parent: null,
276 | cx: -(this.commitRadius * 2),
277 | cy: this.baseLine
278 | };
279 |
280 | this.locks = 0
281 | this._eventCallbacks = {}
282 |
283 | if (config.savedState) {
284 | setTimeout(function() {
285 | this.deserialize(config.savedState)
286 | }.bind(this))
287 | }
288 | }
289 |
290 | HistoryView.generateId = function() {
291 | return Math.floor((1 + Math.random()) * 0x10000000).toString(16).substring(1);
292 | };
293 |
294 | HistoryView.prototype = {
295 | serialize: function () {
296 | var data = {
297 | commitData: this.commitData,
298 | branches: this.branches,
299 | logs: this.logs,
300 | currentBranch: this.currentBranch,
301 | }
302 |
303 | return JSON.stringify(data)
304 | },
305 |
306 | deserialize: function (data) {
307 | data = JSON.parse(data)
308 | if (data) {
309 | this.commitData = data.commitData
310 | this.branches = data.branches
311 | this.logs = data.logs
312 | this._setCurrentBranch(data.currentBranch || null)
313 | this.renderCommits()
314 | this.renderTags()
315 | }
316 | },
317 |
318 | emit: function (event) {
319 | var callbacks = this._eventCallbacks[event] || []
320 | callbacks.forEach(function(callback) {
321 | try {
322 | callback(event)
323 | } finally {
324 | // nothing
325 | }
326 | })
327 | },
328 |
329 | on: function (event, callback) {
330 | var callbacks = this._eventCallbacks[event] || []
331 | callbacks.push(callback)
332 | this._eventCallbacks[event] = callbacks
333 |
334 | return function () {
335 | var cbs = this._eventCallbacks[event] || []
336 | var idx = cbs.indexOf(callback)
337 | if (idx > -1) {
338 | cbs.splice(idx, 1)
339 | this._eventCallbacks[event] = cbs
340 | }
341 | }.bind(this)
342 | },
343 |
344 | lock: function () {
345 | this.locks++
346 | if (this.locks === 1) {
347 | this.emit('lock')
348 | }
349 | },
350 |
351 | unlock: function () {
352 | if (this.locks <= 0) {
353 | throw new Error('cannot unlock! not locked')
354 | }
355 |
356 | this.locks--
357 | if (this.locks === 0) {
358 | this.emit('unlock')
359 | }
360 | },
361 |
362 | /**
363 | * @method getCommit
364 | * @param ref {String} the id or a tag name that refers to the commit
365 | * @return {Object} the commit datum object
366 | */
367 | getCommit: function getCommit(ref) {
368 | // Optimization, doesn't seem to break anything
369 | if (!ref) return null;
370 | if (ref.id) return ref
371 |
372 | var commitData = this.commitData,
373 | matchedCommit = null;
374 |
375 | var reflogMatch
376 | if (reflogMatch = ref.match(/^(.*)@\{(\d+)\}(.*)$/)) {
377 | var branchName = reflogMatch[1].toLowerCase()
378 | var count = parseInt(reflogMatch[2], 10)
379 | var rest = reflogMatch[3]
380 |
381 | if (this.logs[branchName] && this.logs[branchName][count]) {
382 | ref = this.logs[branchName][count].destination + rest
383 | }
384 | }
385 |
386 | var parts = /^([^\^\~]+)(.*)$/.exec(ref),
387 | ref = parts[1],
388 | modifier = parts[2];
389 |
390 | if (ref === 'initial') {
391 | return this.initialCommit;
392 | }
393 |
394 | if (ref.toLowerCase() === 'head') {
395 | ref = 'HEAD';
396 | }
397 |
398 | var commitsThatStartWith = commitData
399 | .filter(function(c) {
400 | return c.id.indexOf(ref) === 0
401 | })
402 |
403 | if (commitsThatStartWith.length === 1) {
404 | matchedCommit = commitsThatStartWith[0]
405 | } else if (commitsThatStartWith.length > 1) {
406 | throw new Error("Ref " + ref + " is ambiguous")
407 | }
408 |
409 | for (var i = 0; i < commitData.length; i++) {
410 | var commit = commitData[i];
411 | if (commit === ref) {
412 | matchedCommit = commit;
413 | break;
414 | }
415 |
416 | if (commit.id === ref) {
417 | matchedCommit = commit;
418 | break;
419 | }
420 |
421 | var matchedTag = function() {
422 | for (var j = 0; j < commit.tags.length; j++) {
423 | var tag = commit.tags[j];
424 | if (tag === ref) {
425 | matchedCommit = commit;
426 | return true;
427 | }
428 |
429 | if (tag.indexOf('[') === 0 && tag.indexOf(']') === tag.length - 1) {
430 | tag = tag.substring(1, tag.length - 1);
431 | }
432 | if (tag === ref) {
433 | matchedCommit = commit;
434 | return true;
435 | }
436 | }
437 | }();
438 | if (matchedTag === true) {
439 | break;
440 | }
441 | }
442 |
443 | if (matchedCommit && modifier) {
444 | while (modifier) {
445 | var nextToken = modifier[0]
446 | modifier = modifier.substr(1)
447 | var amountMatch = modifier.match(/^(\d+)(.*)$/),
448 | amount = 1;
449 |
450 | if (amountMatch) {
451 | var amount = ~~amountMatch[1]
452 | }
453 |
454 | if (nextToken === '^') {
455 | if (amount === 0) {
456 | /* do nothing, refers to this commit */
457 | } else if (amount === 1) {
458 | matchedCommit = this.getCommit(matchedCommit.parent)
459 | } else if (amount === 2) {
460 | matchedCommit = this.getCommit(matchedCommit.parent2)
461 | } else {
462 | matchedCommit = null
463 | }
464 | } else if (nextToken === '~') {
465 | for (var i = 0; i < amount; i++) {
466 | if (matchedCommit && matchedCommit.parent) {
467 | matchedCommit = this.getCommit(matchedCommit.parent)
468 | }
469 | }
470 | }
471 | }
472 | }
473 |
474 | return matchedCommit;
475 | },
476 |
477 | revparse: function(refspec) {
478 | var commit
479 | if (commit = this.getCommit(refspec)) {
480 | return commit.id
481 | } else {
482 | throw new Error("Cannot find object from refspec " + refspec)
483 | }
484 | },
485 |
486 | /**
487 | * @method getCircle
488 | * @param ref {String} the id or a tag name that refers to the commit
489 | * @return {d3 Selection} the d3 selected SVG circle
490 | */
491 | getCircle: function(ref) {
492 | var circle = this.svg.select('#' + this.name + '-' + ref),
493 | commit;
494 |
495 | if (circle && !circle.empty()) {
496 | return circle;
497 | }
498 |
499 | commit = this.getCommit(ref);
500 |
501 | if (!commit) {
502 | return null;
503 | }
504 |
505 | return this.svg.select('#' + this.name + '-' + commit.id);
506 | },
507 |
508 | getCircles: function() {
509 | return this.svg.selectAll('circle.commit');
510 | },
511 |
512 | /**
513 | * @method render
514 | * @param container {String} selector for the container to render the SVG into
515 | */
516 | render: function(container) {
517 | var svgContainer, svg;
518 |
519 | svgContainer = container.append('div')
520 | .classed('svg-container', true)
521 | .classed('remote-container', this.isRemote);
522 |
523 | if (this.isRemote) {
524 | $(svgContainer).draggable();
525 | }
526 |
527 | svg = svgContainer.append('svg:svg');
528 |
529 | svg.attr('id', this.name)
530 | .attr('width', this.width)
531 | .attr('height', this.isRemote ? this.height + 150 : this.height);
532 |
533 | if (this.isRemote) {
534 | svg.append('svg:text')
535 | .classed('remote-name-display', true)
536 | .text(this.remoteName)
537 | .attr('x', 10)
538 | .attr('y', 25);
539 | } else {
540 | svg.append('svg:text')
541 | .classed('remote-name-display', true)
542 | .text('Local Repository')
543 | .attr('x', 10)
544 | .attr('y', 25);
545 |
546 | svg.append('svg:text')
547 | .classed('current-branch-display', true)
548 | .attr('x', 10)
549 | .attr('y', 45);
550 | }
551 |
552 | this.svgContainer = svgContainer;
553 | this.svg = svg;
554 | this.arrowBox = svg.append('svg:g').classed('pointers', true);
555 | this.commitBox = svg.append('svg:g').classed('commits', true);
556 | this.tagBox = svg.append('svg:g').classed('tags', true);
557 |
558 | this.renderCommits();
559 |
560 | this._setCurrentBranch(this.currentBranch);
561 | },
562 |
563 | destroy: function() {
564 | this.svg.remove();
565 | this.svgContainer.remove();
566 | clearInterval(this.refreshSizeTimer);
567 |
568 | for (var prop in this) {
569 | if (this.hasOwnProperty(prop)) {
570 | this[prop] = null;
571 | }
572 | }
573 | },
574 |
575 | _calculatePositionData: function() {
576 | for (var i = 0; i < this.commitData.length; i++) {
577 | var commit = this.commitData[i];
578 | commit.cx = cx(commit, this);
579 | commit.cy = cy(commit, this);
580 | preventOverlap(commit, this);
581 | }
582 | },
583 |
584 | _resizeSvg: function() {
585 | var ele = document.getElementById(this.svg.node().id);
586 | var container = ele.parentNode;
587 | var currentWidth = ele.offsetWidth;
588 | var newWidth;
589 |
590 | if (ele.getBBox().width > container.offsetWidth)
591 | newWidth = Math.round(ele.getBBox().width);
592 | else
593 | newWidth = container.offsetWidth - 5;
594 |
595 | if (currentWidth != newWidth) {
596 | this.svg.attr('width', newWidth);
597 | container.scrollLeft = container.scrollWidth;
598 | }
599 | },
600 |
601 | renderCommits: function() {
602 | if (typeof this.height === 'string' && this.height.indexOf('%') >= 0) {
603 | var perc = this.height.substring(0, this.height.length - 1) / 100.0;
604 | var baseLineCalcHeight = Math.round(this.svg.node().parentNode.offsetHeight * perc) - 65;
605 | var newBaseLine = Math.round(baseLineCalcHeight * (this.originalBaseLine || 0.6));
606 | if (newBaseLine !== this.baseLine) {
607 | this.baseLine = newBaseLine;
608 | this.initialCommit.cy = newBaseLine;
609 | this.svg.attr('height', baseLineCalcHeight);
610 | }
611 | }
612 | this._calculatePositionData();
613 | this._calculatePositionData(); // do this twice to make sure
614 | this._renderCircles();
615 | this._renderPointers();
616 | this._renderMergePointers();
617 | this._renderIdLabels();
618 | this._resizeSvg();
619 | this.currentBranch && this.checkout(this.currentBranch);
620 | },
621 |
622 | _renderCircles: function() {
623 | var view = this,
624 | existingCircles,
625 | newCircles;
626 |
627 | existingCircles = this.commitBox.selectAll('circle.commit')
628 | .data(this.commitData, function(d) {
629 | return d.id;
630 | })
631 | .attr('id', function(d) {
632 | return view.name + '-' + d.id;
633 | })
634 | .classed('reverted', function(d) {
635 | return d.reverted || d.revertSource;
636 | })
637 | .classed('rebased', function(d) {
638 | return d.rebased || d.rebaseSource;
639 | })
640 | .classed('logging', function(d) {
641 | return d.logging;
642 | })
643 | .classed('cherry-picked', function(d) {
644 | return d.cherryPicked || d.cherryPickSource;
645 | })
646 | .classed('checked-out', function(d) {
647 | return d.tags.indexOf('HEAD') > -1
648 | });
649 |
650 | existingCircles.transition()
651 | .duration(500)
652 | .call(fixCirclePosition);
653 |
654 | newCircles = existingCircles.enter()
655 | .append('svg:circle')
656 | .attr('id', function(d) {
657 | return view.name + '-' + d.id;
658 | })
659 | .classed('commit', true)
660 | .classed('merge-commit', function(d) {
661 | return typeof d.parent2 === 'string';
662 | })
663 | .classed('rebased', function(d) {
664 | return d.rebased || d.rebaseSource
665 | })
666 | .classed('cherry-picked', function(d) {
667 | return d.cherryPicked || d.cherryPickSource;
668 | })
669 | .call(fixCirclePosition)
670 | .attr('r', 1)
671 | .transition("inflate")
672 | .duration(500)
673 | .attr('r', this.commitRadius)
674 |
675 | existingCircles.exit()
676 | .remove()
677 |
678 | },
679 |
680 | _renderPointers: function() {
681 | var view = this,
682 | existingPointers,
683 | newPointers;
684 |
685 | existingPointers = this.arrowBox.selectAll('line.commit-pointer')
686 | .data(this.commitData, function(d) {
687 | return d.id;
688 | })
689 | .attr('id', function(d) {
690 | return view.name + '-' + d.id + '-to-' + d.parent;
691 | });
692 |
693 | existingPointers.transition()
694 | .duration(500)
695 | .call(fixPointerStartPosition, view)
696 | .call(fixPointerEndPosition, view);
697 |
698 | newPointers = existingPointers.enter()
699 | .append('svg:line')
700 | .attr('id', function(d) {
701 | return view.name + '-' + d.id + '-to-' + d.parent;
702 | })
703 | .classed('commit-pointer', true)
704 | .call(fixPointerStartPosition, view)
705 | .attr('x2', function() {
706 | return d3.select(this).attr('x1');
707 | })
708 | .attr('y2', function() {
709 | return d3.select(this).attr('y1');
710 | })
711 | .attr('marker-end', REG_MARKER_END)
712 | .transition()
713 | .duration(500)
714 | .call(fixPointerEndPosition, view);
715 |
716 | existingPointers.exit()
717 | .remove()
718 | },
719 |
720 | _renderMergePointers: function() {
721 | var view = this,
722 | mergeCommits = [],
723 | existingPointers, newPointers;
724 |
725 | for (var i = 0; i < this.commitData.length; i++) {
726 | var commit = this.commitData[i];
727 | if (typeof commit.parent2 === 'string') {
728 | mergeCommits.push(commit);
729 | }
730 | }
731 |
732 | existingPointers = this.arrowBox.selectAll('polyline.merge-pointer')
733 | .data(mergeCommits, function(d) {
734 | return d.id;
735 | })
736 | .attr('id', function(d) {
737 | return view.name + '-' + d.id + '-to-' + d.parent2;
738 | });
739 |
740 | existingPointers.transition().duration(500)
741 | .attr('points', function(d) {
742 | var p1 = px1(d, view, 'parent2') + ',' + py1(d, view, 'parent2'),
743 | p2 = px2(d, view, 'parent2') + ',' + py2(d, view, 'parent2');
744 |
745 | return [p1, p2].join(' ');
746 | });
747 |
748 | newPointers = existingPointers.enter()
749 | .append('svg:polyline')
750 | .attr('id', function(d) {
751 | return view.name + '-' + d.id + '-to-' + d.parent2;
752 | })
753 | .classed('merge-pointer', true)
754 | .attr('points', function(d) {
755 | var x1 = px1(d, view, 'parent2'),
756 | y1 = py1(d, view, 'parent2'),
757 | p1 = x1 + ',' + y1;
758 |
759 | return [p1, p1].join(' ');
760 | })
761 | .attr('marker-end', MERGE_MARKER_END)
762 | .transition()
763 | .duration(500)
764 | .attr('points', function(d) {
765 | var points = d3.select(this).attr('points').split(' '),
766 | x2 = px2(d, view, 'parent2'),
767 | y2 = py2(d, view, 'parent2');
768 |
769 | points[1] = x2 + ',' + y2;
770 | return points.join(' ');
771 | });
772 |
773 | existingPointers.exit()
774 | .remove()
775 | },
776 |
777 | _renderIdLabels: function() {
778 | this._renderText('id-label', function(d) {
779 | return d.id + '..';
780 | }, 14);
781 | this._renderText('message-label', function(d) {
782 | return d.message;
783 | }, 24);
784 | },
785 |
786 | _renderText: function(className, getText, delta) {
787 | var view = this,
788 | existingTexts,
789 | newtexts;
790 |
791 | existingTexts = this.commitBox.selectAll('text.' + className)
792 | .data(this.commitData, function(d) {
793 | return d.id;
794 | })
795 | .text(getText);
796 |
797 | existingTexts.transition().call(fixIdPosition, view, delta);
798 |
799 | newtexts = existingTexts.enter()
800 | .insert('svg:text', ':first-child')
801 | .classed(className, true)
802 | .text(getText)
803 | .call(fixIdPosition, view, delta);
804 |
805 | existingTexts.exit()
806 | .remove()
807 | },
808 |
809 | _parseTagData: function() {
810 | var tagData = [],
811 | i,
812 | headCommit = null;
813 |
814 | for (i = 0; i < this.commitData.length; i++) {
815 | var c = this.commitData[i];
816 |
817 | for (var t = 0; t < c.tags.length; t++) {
818 | var tagName = c.tags[t];
819 | if (tagName.toUpperCase() === 'HEAD') {
820 | headCommit = c;
821 | } else if (this.branches.indexOf(tagName) === -1) {
822 | this.branches.push(tagName);
823 | }
824 |
825 | tagData.push({
826 | name: tagName,
827 | commit: c.id
828 | });
829 | }
830 | }
831 |
832 | if (!headCommit) {
833 | headCommit = this.getCommit(this.currentBranch);
834 | headCommit.tags.push('HEAD');
835 | tagData.push({
836 | name: 'HEAD',
837 | commit: headCommit.id
838 | });
839 | }
840 |
841 | // find out which commits are not branchless
842 |
843 |
844 | return tagData;
845 | },
846 |
847 | _walkCommit: function (commit) {
848 | commit.branchless = false
849 | commit.parent && this._walkCommit(this.getCommit(commit.parent))
850 | commit.parent2 && this._walkCommit(this.getCommit(commit.parent2))
851 | },
852 |
853 | _markBranchlessCommits: function() {
854 | var branch, commit, parent, parent2, c, b;
855 |
856 | // first mark every commit as branchless
857 | for (c = 0; c < this.commitData.length; c++) {
858 | this.commitData[c].branchless = true;
859 | }
860 |
861 | for (b = 0; b < this.branches.length; b++) {
862 | branch = this.branches[b];
863 | if (branch.indexOf('/') === -1) {
864 | commit = this.getCommit(branch);
865 | parent = this.getCommit(commit.parent);
866 | parent2 = this.getCommit(commit.parent2);
867 |
868 | this._walkCommit(commit)
869 | }
870 | }
871 |
872 | this.svg.selectAll('circle.commit').call(applyBranchlessClass);
873 | this.svg.selectAll('line.commit-pointer').call(applyBranchlessClass);
874 | this.svg.selectAll('polyline.merge-pointer').call(applyBranchlessClass);
875 | },
876 |
877 | renderTags: function() {
878 | var view = this,
879 | tagData = this._parseTagData(),
880 | existingTags, newTags;
881 |
882 | existingTags = this.tagBox.selectAll('g.branch-tag')
883 | .data(tagData, function(d) {
884 | return d.name;
885 | });
886 |
887 | existingTags.exit().remove();
888 |
889 | existingTags.select('rect')
890 | .transition()
891 | .duration(500)
892 | .attr('y', function(d) {
893 | return tagY(d, view);
894 | })
895 | .attr('x', function(d) {
896 | var commit = view.getCommit(d.commit),
897 | width = Number(d3.select(this).attr('width'));
898 |
899 | return commit.cx - (width / 2);
900 | });
901 |
902 | existingTags.select('text')
903 | .transition()
904 | .duration(500)
905 | .attr('y', function(d) {
906 | return tagY(d, view) + 14;
907 | })
908 | .attr('x', function(d) {
909 | var commit = view.getCommit(d.commit);
910 | return commit.cx;
911 | });
912 |
913 | newTags = existingTags.enter()
914 | .append('g')
915 | .attr('class', function(d) {
916 | var classes = 'branch-tag';
917 | if (d.name.indexOf('[') === 0 && d.name.indexOf(']') === d.name.length - 1) {
918 | classes += ' git-tag';
919 | } else if (d.name.indexOf('/') >= 0) {
920 | classes += ' remote-branch';
921 | } else if (d.name.toUpperCase() === 'HEAD') {
922 | classes += ' head-tag';
923 | }
924 | return classes;
925 | });
926 |
927 | newTags.append('svg:rect')
928 | .attr('width', function(d) {
929 | return (d.name.length * 8) + 20;
930 | })
931 | .attr('height', 20)
932 | .attr('y', function(d) {
933 | return tagY(d, view);
934 | })
935 | .attr('x', function(d) {
936 | var commit = view.getCommit(d.commit),
937 | width = Number(d3.select(this).attr('width'));
938 |
939 | return commit.cx - (width / 2);
940 | });
941 |
942 | newTags.append('svg:text')
943 | .text(function(d) {
944 | if (d.name.indexOf('[') === 0 && d.name.indexOf(']') === d.name.length - 1)
945 | return d.name.substring(1, d.name.length - 1);
946 | return d.name;
947 | })
948 | .attr('y', function(d) {
949 | return tagY(d, view) + 14;
950 | })
951 | .attr('x', function(d) {
952 | var commit = view.getCommit(d.commit);
953 | return commit.cx;
954 | });
955 |
956 | existingTags.exit()
957 | .remove()
958 |
959 | this._markBranchlessCommits();
960 | },
961 |
962 | _setCurrentBranch: function(branch) {
963 | var display = this.svg.select('text.current-branch-display'),
964 | text = 'HEAD: ';
965 |
966 | if (branch && branch.indexOf('/') === -1) {
967 | text += branch;
968 | this.currentBranch = branch;
969 | } else {
970 | text += ' (detached head)';
971 | this.currentBranch = null;
972 | }
973 |
974 | display.text(text);
975 | },
976 |
977 | addReflogEntry: function(ref, destination, reason) {
978 | ref = ref.toLowerCase()
979 | this.logs[ref] = this.logs[ref] || []
980 | this.logs[ref].unshift({
981 | destination: destination,
982 | reason: reason
983 | })
984 | },
985 |
986 | getReflogEntries: function(ref) {
987 | if (!this.logs[ref.toLowerCase()]) {
988 | throw new Error("no reflog for " + ref)
989 | }
990 |
991 | return this.logs[ref.toLowerCase()].map(function(entry, idx) {
992 | return entry.destination + " " + ref + "@{" + idx + "} " + " " + entry.reason
993 | })
994 | },
995 |
996 | moveTag: function(tag, ref) {
997 | var currentLoc = this.getCommit(tag),
998 | newLoc = this.getCommit(ref);
999 |
1000 | if (currentLoc) {
1001 | currentLoc.tags.splice(currentLoc.tags.indexOf(tag), 1);
1002 | }
1003 |
1004 | newLoc.tags.push(tag);
1005 | return this;
1006 | },
1007 |
1008 | amendCommit: function(message) {
1009 | this.commit({parent: this.getCommit('head^').id}, message)
1010 | },
1011 |
1012 | commit: function(commit, message) {
1013 | commit = commit || {};
1014 |
1015 | !commit.id && (commit.id = HistoryView.generateId());
1016 | !commit.tags && (commit.tags = []);
1017 |
1018 | commit.message = message
1019 | if (!commit.parent) {
1020 | commit.parent = this.getCommit('HEAD').id;
1021 | }
1022 |
1023 | this.commitData.push(commit);
1024 | if (this.currentBranch) {
1025 | this.moveTag(this.currentBranch, commit.id);
1026 | }
1027 |
1028 | this.renderCommits();
1029 |
1030 | if (this.currentBranch) {
1031 | this.checkout(this.currentBranch);
1032 | } else {
1033 | this.checkout(commit.id)
1034 | }
1035 | return this;
1036 | },
1037 |
1038 | getLogEntries: function(refspec) {
1039 | var ancestors = this.getAncestorSet(refspec)
1040 | delete ancestors.initial
1041 | ancestors[refspec] = -1
1042 | var commitIds = Object.keys(ancestors)
1043 | this.lock()
1044 | this.flashProperty(commitIds, 'logging', null, this.unlock)
1045 | return commitIds.map(function(commitId) {
1046 | return {commit: this.getCommit(commitId), order: ancestors[commitId]}
1047 | }, this).sort(function(a,b) {
1048 | return a.order - b.order
1049 | }).map(function(commitInfo) {
1050 | var commit = commitInfo.commit
1051 | return commit.id + ' ' + (commit.message || "(no message)")
1052 | }, this)
1053 | },
1054 |
1055 | setProperty: function(refs, property) {
1056 | refs.forEach(function(ref) {
1057 | this.getCommit(ref)[property] = true
1058 | }, this)
1059 | },
1060 |
1061 | unsetProperty: function(refs, property) {
1062 | refs.forEach(function(ref) {
1063 | var commit = this.getCommit(ref)
1064 | delete commit[property]
1065 | }, this)
1066 | },
1067 |
1068 | cherryPick: function(refs, mainline) {
1069 | refs.forEach(function(ref) {
1070 | if (!this.getCommit(ref)) {
1071 | throw new Error("fatal: bad revision '" + ref + "'")
1072 | return
1073 | }
1074 | }, this)
1075 |
1076 | if (mainline) {
1077 | if (mainline > 2 || mainline < 1) {
1078 | throw new Error("Commit " + refs[0] + " does not have parent " + mainline)
1079 | return
1080 | }
1081 | var nonMergeRefs = refs.filter(function(ref) {
1082 | var commit = this.getCommit(ref)
1083 | return !commit.parent || !commit.parent2
1084 | }, this)
1085 |
1086 | if (nonMergeRefs.length) {
1087 | throw new Error('mainline specified but ' + nonMergeRefs[0] + ' is not a merge')
1088 | }
1089 | } else {
1090 | var mergeRefs = refs.filter(function(ref) {
1091 | var commit = this.getCommit(ref)
1092 | return commit.parent && commit.parent2
1093 | }, this)
1094 |
1095 | if (mergeRefs.length) {
1096 | throw new Error('cannot cherry-pick merge commit ' + mergeRefs[0] + ' without specifying a mainline with -m')
1097 | }
1098 | }
1099 |
1100 | if (!mainline) {
1101 | refs.forEach(function(ref) {
1102 | var commit = this.getCommit(ref)
1103 | var message = commit.message || ""
1104 | this.lock()
1105 | this.flashProperty([commit.id], 'cherryPicked', function() {
1106 | this.commit({cherryPickSource: [commit.id]}, message)
1107 | var reflogMessage = "cherry-pick: " + message
1108 | this.addReflogEntry(
1109 | 'HEAD', this.getCommit('HEAD').id, reflogMessage
1110 | )
1111 | if (this.currentBranch) {
1112 | this.addReflogEntry(
1113 | this.currentBranch, this.getCommit('HEAD').id, reflogMessage
1114 | )
1115 | }
1116 | }, this.unlock)
1117 | }, this)
1118 | } else {
1119 | refs.forEach(function(ref) {
1120 | var commit = this.getCommit(ref)
1121 | var message = commit.message || ""
1122 | var cherryPickSource = this.getNonMainlineCommits(commit.id, mainline)
1123 |
1124 | this.lock()
1125 | this.flashProperty(cherryPickSource, 'cherryPicked', function() {
1126 | this.commit({cherryPickSource: cherryPickSource}, message)
1127 | var reflogMessage = "cherry-pick: " + message
1128 | this.addReflogEntry(
1129 | 'HEAD', this.getCommit('HEAD').id, reflogMessage
1130 | )
1131 | if (this.currentBranch) {
1132 | this.addReflogEntry(
1133 | this.currentBranch, this.getCommit('HEAD').id, reflogMessage
1134 | )
1135 | }
1136 | }, this.unlock)
1137 | }, this)
1138 | }
1139 | },
1140 |
1141 | getNonMainlineCommits: function(ref, mainline) {
1142 | if (mainline === 1) mainline = 2
1143 | else if (mainline === 2) mainline = 1
1144 | else throw new Error("Mainline " + mainline + " isn't supported")
1145 | var ancestor1Set = this.getAncestorSet(ref, 1)
1146 | var ancestor2Set = this.getAncestorSet(ref, 2)
1147 | var uniqueAncestors = getUniqueSetItems(ancestor1Set, ancestor2Set)
1148 | return Object.keys(uniqueAncestors[mainline-1]).concat(ref)
1149 | },
1150 |
1151 | flashProperty: function(refs, property, callback, callback2) {
1152 | this.setProperty(refs, property)
1153 | this.renderCommits()
1154 | setTimeout(function() {
1155 | callback && callback.call(this)
1156 | setTimeout(function() {
1157 | this.unsetProperty(refs, property)
1158 | this.renderCommits()
1159 | callback2 && callback2.call(this)
1160 | }.bind(this), 500)
1161 | }.bind(this), 1000)
1162 | },
1163 |
1164 | getParents: function(ref, mainline) {
1165 | var commit,
1166 | parents = []
1167 | if (ref.id) {
1168 | commit = ref
1169 | } else {
1170 | commit = this.getCommit(ref)
1171 | }
1172 | if ((!mainline || mainline === 1) && commit.parent) parents.push(commit.parent)
1173 | if ((!mainline || mainline === 2) && commit.parent2) parents.push(commit.parent2)
1174 | return parents
1175 | },
1176 |
1177 | getAncestorSet: function(ref, mainline) {
1178 | var ancestors = {}
1179 | var i = 1;
1180 | function getAncestor(currentRef, currentMainline) {
1181 | var parents = this.getParents(currentRef, currentMainline)
1182 | parents.forEach(function(parentRef) {
1183 | ancestors[parentRef] = i++
1184 | getAncestor.call(this, parentRef)
1185 | }, this)
1186 | }
1187 | getAncestor.call(this, ref, mainline)
1188 | return ancestors
1189 | },
1190 |
1191 | getBranchList: function() {
1192 | return this.commitData.reduce(function(acc, commit) {
1193 | return acc.concat(commit.tags.filter(function(tag) {
1194 | return !tag.match(/^\[.*\]$/) && tag !== 'HEAD'
1195 | }))
1196 | }, []).map(function(tag) {
1197 | if (this.currentBranch && (tag.toLowerCase() === this.currentBranch.toLowerCase())) {
1198 | return '* ' + tag
1199 | } else {
1200 | return ' ' + tag
1201 | }
1202 | }, this)
1203 | },
1204 |
1205 | branch: function(name, startCommit) {
1206 | if (!name || name.trim() === '') {
1207 | throw new Error('You need to give a branch name.');
1208 | }
1209 |
1210 | if (name === 'HEAD') {
1211 | throw new Error('You cannot name your branch "HEAD".');
1212 | }
1213 |
1214 | if (this.branches.indexOf(name) > -1) {
1215 | throw new Error('Branch "' + name + '" already exists.');
1216 | }
1217 |
1218 | var startPoint = this.getCommit(startCommit || 'head')
1219 | if (!startPoint) {
1220 | throw new Error("fatal: Not a valid object name:'" + startCommit + "'")
1221 | }
1222 | startPoint.tags.push(name);
1223 | this.renderTags();
1224 | return this;
1225 | },
1226 |
1227 | tag: function(name) {
1228 | this.branch('[' + name + ']');
1229 | },
1230 |
1231 | deleteBranch: function(name) {
1232 | var branchIndex,
1233 | commit;
1234 |
1235 | if (!name || name.trim() === '') {
1236 | throw new Error('You need to give a branch name.');
1237 | }
1238 |
1239 | if (name === this.currentBranch) {
1240 | throw new Error('Cannot delete the currently checked-out branch.');
1241 | }
1242 |
1243 | branchIndex = this.branches.indexOf(name);
1244 |
1245 | if (branchIndex === -1) {
1246 | throw new Error('That branch doesn\'t exist.');
1247 | }
1248 |
1249 | this.branches.splice(branchIndex, 1);
1250 | commit = this.getCommit(name);
1251 | delete this.logs[name]
1252 | branchIndex = commit.tags.indexOf(name);
1253 |
1254 | if (branchIndex > -1) {
1255 | commit.tags.splice(branchIndex, 1);
1256 | }
1257 |
1258 | this.renderTags();
1259 | },
1260 |
1261 | checkout: function(ref) {
1262 | var commit = this.getCommit(ref);
1263 |
1264 | if (!commit) {
1265 | throw new Error('Cannot find commit: ' + ref);
1266 | }
1267 |
1268 | var previousHead = this.getCircle('HEAD'),
1269 | newHead = this.getCircle(commit.id);
1270 |
1271 | if (previousHead && !previousHead.empty()) {
1272 | previousHead.classed('checked-out', false);
1273 | }
1274 |
1275 | var isBranch = this.branches.indexOf(ref) !== -1
1276 | this._setCurrentBranch(isBranch ? ref : null);
1277 | this.moveTag('HEAD', commit.id);
1278 | this.renderTags();
1279 |
1280 | newHead.classed('checked-out', true);
1281 |
1282 | return this;
1283 | },
1284 |
1285 | reset: function(ref) {
1286 | var commit = this.getCommit(ref);
1287 |
1288 | if (!commit) {
1289 | throw new Error('Cannot find ref: ' + ref);
1290 | }
1291 |
1292 | if (this.currentBranch) {
1293 | this.moveTag(this.currentBranch, commit.id);
1294 | this.checkout(this.currentBranch);
1295 | } else {
1296 | this.checkout(commit.id);
1297 | }
1298 |
1299 | return this;
1300 | },
1301 |
1302 | revert: function(refs, mainline) {
1303 | refs.forEach(function(ref) {
1304 | if (!this.getCommit(ref)) {
1305 | throw new Error("fatal: bad revision '" + ref + "'")
1306 | return
1307 | }
1308 | }, this)
1309 |
1310 | if (mainline) {
1311 | if (mainline > 2 || mainline < 1) {
1312 | throw new Error("Commit " + refs[0] + " does not have parent " + mainline)
1313 | return
1314 | }
1315 | var nonMergeRefs = refs.filter(function(ref) {
1316 | var commit = this.getCommit(ref)
1317 | return !commit.parent || !commit.parent2
1318 | }, this)
1319 |
1320 | if (nonMergeRefs.length) {
1321 | throw new Error('mainline specified but ' + nonMergeRefs[0] + ' is not a merge')
1322 | }
1323 | } else {
1324 | var mergeRefs = refs.filter(function(ref) {
1325 | var commit = this.getCommit(ref)
1326 | return commit.parent && commit.parent2
1327 | }, this)
1328 |
1329 | if (mergeRefs.length) {
1330 | throw new Error('cannot revert merge commit ' + mergeRefs[0] + ' without specifying a mainline with -m')
1331 | }
1332 | }
1333 |
1334 | if (!mainline) {
1335 | refs.forEach(function(ref) {
1336 | var commit = this.getCommit(ref)
1337 | var message = commit.message || ""
1338 | this.lock()
1339 | this.flashProperty([commit.id], 'reverted', function() {
1340 | this.commit({revertSource: [commit.id]}, "Revert " + commit.id)
1341 | var reflogMessage = "revert: " + message
1342 | this.addReflogEntry(
1343 | 'HEAD', this.getCommit('HEAD').id, reflogMessage
1344 | )
1345 | if (this.currentBranch) {
1346 | this.addReflogEntry(
1347 | this.currentBranch, this.getCommit('HEAD').id, reflogMessage
1348 | )
1349 | }
1350 | }, this.unlock)
1351 | }, this)
1352 | } else {
1353 | refs.forEach(function(ref) {
1354 | var commit = this.getCommit(ref)
1355 | var message = commit.message || ""
1356 | var revertSource = this.getNonMainlineCommits(commit.id, mainline)
1357 |
1358 | this.lock()
1359 | this.flashProperty(revertSource, 'reverted', function() {
1360 | this.commit({revertSource: revertSource}, "Revert " + commit.id)
1361 | var reflogMessage = "revert: " + message
1362 | this.addReflogEntry(
1363 | 'HEAD', this.getCommit('HEAD').id, reflogMessage
1364 | )
1365 | if (this.currentBranch) {
1366 | this.addReflogEntry(
1367 | this.currentBranch, this.getCommit('HEAD').id, reflogMessage
1368 | )
1369 | }
1370 | }, this.unlock)
1371 | }, this)
1372 | }
1373 | },
1374 |
1375 | fastForward: function(ref) {
1376 | var targetCommit = this.getCommit(ref);
1377 |
1378 | if (this.currentBranch) {
1379 | this.moveTag(this.currentBranch, targetCommit.id);
1380 | this.checkout(this.currentBranch);
1381 | } else {
1382 | this.checkout(targetCommit.id);
1383 | }
1384 | },
1385 |
1386 | isAncestorOf: function(search, start) {
1387 | var startCommit = this.getCommit(start),
1388 | searchCommit = this.getCommit(search)
1389 |
1390 | if (!searchCommit) {
1391 | return false
1392 | }
1393 |
1394 | if (startCommit === searchCommit) {
1395 | return true
1396 | } else {
1397 | var ancestorOnFirstParent = startCommit.parent && this.isAncestorOf(searchCommit.id, startCommit.parent)
1398 | var ancestorOnSecondParent = startCommit.parent2 && this.isAncestorOf(searchCommit.id, startCommit.parent2)
1399 | return ancestorOnFirstParent || ancestorOnSecondParent
1400 | }
1401 | },
1402 |
1403 | merge: function(ref, noFF) {
1404 | var mergeTarget = this.getCommit(ref),
1405 | currentCommit = this.getCommit('HEAD');
1406 | if (!mergeTarget) {
1407 | throw new Error('Cannot find ref: ' + ref);
1408 | }
1409 |
1410 |
1411 | if (currentCommit.id === mergeTarget.id) {
1412 | throw new Error('Already up-to-date.');
1413 | } else if (currentCommit.parent2 === mergeTarget.id) {
1414 | throw new Error('Already up-to-date.');
1415 | } else if (this.isAncestorOf(mergeTarget, currentCommit)) {
1416 | throw new Error('Already up-to-date.');
1417 | } else if (noFF === true) {
1418 | var branchStartCommit = this.getCommit(mergeTarget.parent);
1419 | while (branchStartCommit.parent !== currentCommit.id) {
1420 | branchStartCommit = this.getCommit(branchStartCommit.parent);
1421 | }
1422 |
1423 | branchStartCommit.isNoFFBranch = true;
1424 |
1425 | this.commit({
1426 | parent2: mergeTarget.id,
1427 | isNoFFCommit: true
1428 | }, 'Merge');
1429 | } else if (this.isAncestorOf(currentCommit.id, mergeTarget.id)) {
1430 | this.fastForward(mergeTarget);
1431 | return 'Fast-Forward';
1432 | } else {
1433 | this.commit({
1434 | parent2: mergeTarget.id
1435 | }, 'Merge');
1436 | }
1437 | },
1438 |
1439 | rebase: function(ref) {
1440 | var targetCommit = this.getCommit(ref)
1441 | if (!targetCommit) {
1442 | throw new Error("Cannot find commit " + ref) // TODO: better message
1443 | }
1444 |
1445 | this.branch('ORIG_HEAD')
1446 | var origHeadCommit = this.getCommit('ORIG_HEAD')
1447 | var origBranch = this.currentBranch
1448 | var origRef = origBranch || origHeadCommit.id
1449 |
1450 | this.checkout(targetCommit.id)
1451 | this.addReflogEntry(
1452 | 'HEAD', targetCommit.id, 'rebase: checkout ' + ref
1453 | )
1454 |
1455 | var ancestorsFromTarget = this.getAncestorSet(ref)
1456 | var ancestorsFromBase = this.getAncestorSet(origHeadCommit.id)
1457 | var uniqueAncestors = getUniqueSetItems(ancestorsFromTarget, ancestorsFromBase)[1]
1458 | var commitsToCopy = Object.keys(uniqueAncestors).concat(origHeadCommit.id)
1459 | .sort(function(key1, key2) {
1460 | return uniqueAncestors[key2] - uniqueAncestors[key1]
1461 | })
1462 |
1463 | this.lock()
1464 | setTimeout(function() {
1465 | this.flashProperty(commitsToCopy, 'rebased', function() {
1466 | commitsToCopy.forEach(function(ref) {
1467 | var oldCommit = this.getCommit(ref)
1468 | this.commit({rebased: true, rebaseSource: ref}, oldCommit.message)
1469 | this.addReflogEntry(
1470 | 'HEAD', this.getCommit('HEAD').id, 'rebase: ' + (oldCommit.message || oldCommit.id)
1471 | )
1472 | }, this)
1473 | var newHeadCommit = this.getCommit('HEAD')
1474 | this.lock()
1475 | setTimeout(function() {
1476 | this.deleteBranch('ORIG_HEAD')
1477 | if (origBranch) {
1478 | this.moveTag(origBranch, newHeadCommit.id)
1479 | this.reset(origBranch)
1480 | this._setCurrentBranch(origBranch)
1481 | this.addReflogEntry(
1482 | 'HEAD', this.getCommit('HEAD').id, 'rebase finished: returning to refs/heads/' + origBranch
1483 | )
1484 | this.addReflogEntry(
1485 | origBranch, newHeadCommit.id, 'rebase finished: refs/heads/' +
1486 | origBranch + ' onto ' + targetCommit.id
1487 | )
1488 | }
1489 | this.unsetProperty(commitsToCopy, 'rebased')
1490 | this.unlock()
1491 | }.bind(this), 1000)
1492 | }, this.unlock)
1493 | }.bind(this), 1000)
1494 | }
1495 | };
1496 |
1497 | return HistoryView;
1498 | });
1499 |
--------------------------------------------------------------------------------