├── 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 |

Start Test

21 | 45 |

Back to Home

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 | ![By Git School](http://i.imgur.com/EiuyjJQ.png?1) 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 | ![rebase](images/viz-rebase.gif) 14 | 15 | ## Cherry-pick 16 | ![cherry-pick](images/cherry-pick.gif) 17 | 18 | ## Push/pull 19 | ![push-pull](images/remote.gif) 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 | 37 | 38 | 40 | 41 | 42 | 44 | 45 | 46 | 48 | 49 | 50 | 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 | ![explain_git_with_d3](https://cloud.githubusercontent.com/assets/7910250/17195018/537c8e8c-5410-11e6-94fd-041a0c865344.png) 20 | 21 | Repeat with `feature2` branch, so that you get this: 22 | ![explain_git_with_d3](https://cloud.githubusercontent.com/assets/7910250/17195052/8fcc8b80-5410-11e6-922d-d61173a1cd57.png) 23 | 24 | Push to share changes on GitHub! 25 | ``` 26 | git push 27 | ``` 28 | ![explain_git_with_d3](https://cloud.githubusercontent.com/assets/7910250/17195090/bd7b0778-5410-11e6-9ca6-3f2034887765.png) 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 | ![explain_git_with_d3](https://cloud.githubusercontent.com/assets/7910250/17196198/a5e734ea-5417-11e6-838c-2a030ad0ef70.png) 33 | 34 | When we merge feature into master, Git creates a merge commit with two parents (the branch heads). 35 | ![explain_git_with_d3](https://cloud.githubusercontent.com/assets/7910250/17196233/f5da5db0-5417-11e6-9d25-34a52603e25f.png) 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 | ![explain_git_with_d3](https://cloud.githubusercontent.com/assets/7910250/17195190/47c33676-5411-11e6-93cb-50f904a14f55.png) 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 | ![explain_git_with_d3](https://cloud.githubusercontent.com/assets/7910250/17195338/efa3b078-5411-11e6-81dc-6324701433a4.png) 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 | ![explain_git_with_d3](https://cloud.githubusercontent.com/assets/7910250/17195379/3adb2c42-5412-11e6-85c5-fd5987eafec1.png) 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 | ![explain_git_with_d3](https://cloud.githubusercontent.com/assets/7910250/17195634/d7c6cec0-5413-11e6-9c92-720811a3e036.png) 62 | 63 | What if I asked the same question but with this graph: 64 | ![explain_git_with_d3](https://cloud.githubusercontent.com/assets/7910250/17195686/34375f1c-5414-11e6-8d12-7e17ccadb376.png) 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 | --------------------------------------------------------------------------------