├── LICENSE.md
├── README.md
├── css
└── explaingit.css
├── glossary.md
├── images
├── gitbranch.png
├── gitcheckout.png
├── gitcheckoutb.png
├── gitcommit.png
├── gitreset.png
├── gitrevert.png
└── prompt.gif
├── index.html
├── js
├── controlbox.js
├── explaingit.js
├── historyview.js
├── main.js
└── vendor
│ └── yargs-parser.js
└── memtest.html
/LICENSE.md:
--------------------------------------------------------------------------------
1 | [git 튜토리얼](https://github.com/Violet-Bora-Lee)(이하 튜토리얼)은 MIT License를 준수하는 Wei Wang의 https://github.com/onlywei/explain-git-with-d3를 기반으로 만든 프로젝트입니다.
2 |
3 | 튜토리얼의 라이센스는 [CC-BY-NC](https://creativecommons.org/licenses/by-nc/4.0/legalcode)를 따릅니다.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # git 튜토리얼
2 |
3 | [git 튜토리얼](https://violet-bora-lee.github.io/git-tutorial/)은 git 명령어를 입력 했을 때 어떤 변화가 생기는지 시각적으로 보여주면서 git을 학습할 수 있도록 만든 튜토리얼입니다.
4 |
5 | ## git commit
6 | 
7 |
8 | ## git branch
9 | 
10 |
11 | ## git checkout
12 | 
13 |
14 | ## git checkout -b
15 | 
16 |
17 | ## git reset
18 | 
19 |
20 | ## git revert
21 | 
--------------------------------------------------------------------------------
/css/explaingit.css:
--------------------------------------------------------------------------------
1 | /* styles */
2 |
3 | body, html {
4 | height: 100%;
5 | }
6 |
7 | .intro p, .concept-container p {
8 | padding-top: 10px;
9 | }
10 |
11 | a.openswitch {
12 | display: block;
13 | }
14 |
15 | a.openswitch.selected {
16 | font-weight: bold;
17 | }
18 |
19 | .command-list, .example-list {
20 | margin-top: 10px;
21 | margin-bottom: 10px;
22 | padding: 10px 0;
23 | border-bottom: 2px dashed #888;
24 | border-top: 2px dashed #888;
25 | background-color: #EEE;
26 | }
27 |
28 | .command-list a.openswitch {
29 | font-family: Courier New;
30 | }
31 |
32 | .concept-area {
33 | padding-bottom: 20px;
34 | }
35 |
36 | .concept-container {
37 | display: none;
38 | }
39 |
40 | .playground-container {
41 | margin-top: 20px;
42 | position: relative;
43 | }
44 |
45 | span.cmd {
46 | background-color: #222222;
47 | color: #FFFFFF;
48 | font-family: Courier New;
49 | padding: 0 0.2em;
50 | }
51 |
52 | .svg-container {
53 | margin-left: 250px;
54 | display: block;
55 | overflow: auto;
56 | border: 1px dotted #AAA;
57 | }
58 |
59 | .svg-container.remote-container {
60 | position: absolute;
61 | top: 0px;
62 | right: 0px;
63 | background-color: #EFF1FF;
64 | border-left: 1px dotted #AAA;
65 | border-bottom: 1px dotted #AAA;
66 | }
67 |
68 | #ExplainGitZen-Container {
69 | position: absolute;
70 | top: 0;
71 | bottom: 0;
72 | right: 0;
73 | left: 0;
74 | }
75 |
76 | #ExplainGitZen-Container .svg-container {
77 | display: inline-block;
78 | border: 1px dotted #AAA;
79 | position: absolute;
80 | top: 0;
81 | bottom: 0;
82 | right: 0;
83 | left: 250px;
84 | margin-left: 0;
85 | }
86 |
87 | #ExplainGitZen-Container .svg-container.remote-container {
88 | position: absolute;
89 | top: 0px;
90 | right: 0px;
91 | left: auto;
92 | bottom: auto;
93 | background-color: #EFF1FF;
94 | border-left: 1px dotted #AAA;
95 | border-bottom: 1px dotted #AAA;
96 | }
97 |
98 | #ExplainGitZen-Container .playground-container {
99 | position: absolute;
100 | top: 0;
101 | bottom: 20px;
102 | right: 20px;
103 | left: 20px;
104 | }
105 |
106 | .remote-name-display {
107 | font-weight: bold;
108 | text-align: right;
109 | }
110 |
111 | .control-box {
112 | display: inline-block;
113 | position: absolute;
114 | top: 0px;
115 | bottom: 0;
116 | width: 250px;
117 | vertical-align: bottom;
118 | background-color: #000;
119 | border: 1px dotted #AAA;
120 | }
121 |
122 | .control-box button {
123 | font-family: Courier New;
124 | font-size: 12px;
125 | margin-right: 5px;
126 | margin-bottom: 5px;
127 | }
128 |
129 | .control-box .log {
130 | overflow-y: auto;
131 | position: absolute;
132 | top: 0px;
133 | bottom: 20px;
134 | left: 0;
135 | right: 0;
136 | border-bottom: 1px solid #AAA;
137 | }
138 |
139 | .control-box .log, .control-box input[type="text"] {
140 | font-family: Courier New;
141 | font-size: 14px;
142 | }
143 |
144 | .control-box .log .command-entry {
145 | padding-left: 15px;
146 | color: #FFF;
147 | line-height: 14px;
148 | background: url(../images/prompt.gif) no-repeat left center transparent;
149 | }
150 |
151 | .control-box input[type="text"] {
152 | position: absolute;
153 | bottom: 0;
154 | padding-left: 15px;
155 | color: #FFF;
156 | line-height: 14px;
157 | background: url(../images/prompt.gif) no-repeat left center transparent;
158 | }
159 |
160 | .control-box .log .info, .control-box .log .error {
161 | font-size: 12px;
162 | padding: 5px;
163 | }
164 |
165 | .control-box .log .info {
166 | color: #FFC;
167 | }
168 |
169 | .control-box .log .error {
170 | color: #FCC;
171 | }
172 |
173 | .control-box input[type="text"] {
174 | width: 235px;
175 | border: none;
176 | }
177 |
178 | circle.commit {
179 | fill: #EEEEEE;
180 | stroke: #888888;
181 | stroke-width: 3;
182 | transition-property: stroke, fill;
183 | transition-duration: 500ms;
184 | transition-timing-function: ease-out;
185 | }
186 |
187 | circle.commit.merge-commit {
188 | stroke: #663300;
189 | fill: #FFFFCC;
190 | }
191 |
192 | circle.commit.merge-commit.checked-out {
193 | fill: #FFFFCC;
194 | }
195 |
196 | circle.commit.reverted {
197 | fill: #CCEEFF;
198 | stroke: #0066CC;
199 | }
200 |
201 | circle.commit.reverted.checked-out {
202 | fill: #CCEEFF;
203 | }
204 |
205 | circle.commit.rebased {
206 | stroke: #3300CC;
207 | fill: #CCCCFF;
208 | }
209 |
210 | circle.commit.rebased.checked-out {
211 | fill: #CCCCFF;
212 | }
213 |
214 | circle.commit.cherry-picked {
215 | stroke: #FF0000;
216 | fill: #FFEEEE;
217 | }
218 |
219 | circle.commit.cherry-picked.checked-out {
220 | fill: #FFEEEE;
221 | }
222 |
223 | circle.commit.branchless {
224 | fill: #FEFEFE;
225 | stroke: #DDD;
226 | }
227 |
228 | circle.commit.branchless.checked-out {
229 | fill: #FEFEFE;
230 | }
231 |
232 | circle.commit.checked-out {
233 | fill: #CCFFCC;
234 | stroke: #339900;
235 | }
236 |
237 | circle.commit.logging {
238 | stroke: #0066CC;
239 | fill: #0099EE;
240 | }
241 |
242 | .commit-pointer {
243 | stroke: #666;
244 | stroke-width: 4;
245 | }
246 |
247 | .merge-pointer {
248 | stroke: #663300;
249 | stroke-width: 4;
250 | }
251 |
252 | .commit-pointer.branchless, .merge-pointer.branchless {
253 | stroke: #DDD;
254 | stroke-width: 2;
255 | }
256 |
257 | text.id-label {
258 | text-anchor: middle;
259 | font-family: Courier New;
260 | font-weight: bolder;
261 | fill: #666;
262 | font-size: 10px;
263 | }
264 |
265 | text.message-label {
266 | text-anchor: middle;
267 | font-family: Courier New;
268 | fill: #666;
269 | font-size: 10px;
270 | }
271 |
272 | g.branch-tag > rect {
273 | fill: #FFCC66;
274 | stroke: #CC9900;
275 | stroke-width: 2;
276 | }
277 |
278 | g.branch-tag.git-tag > rect {
279 | fill: #7FC9FF;
280 | stroke: #0026FF;
281 | }
282 |
283 | g.branch-tag.remote-branch > rect {
284 | fill: #CCC;
285 | stroke: #888;
286 | }
287 |
288 | g.branch-tag > text {
289 | text-anchor: middle;
290 | fill: #000;
291 | font-size: 11px;
292 | font-family: Arial;
293 | }
294 |
295 | g.head-tag > rect {
296 | fill: #CCFFCC;
297 | stroke: #339900;
298 | stroke-width: 2;
299 | }
300 |
301 | g.head-tag > text {
302 | text-anchor: middle;
303 | fill: #000;
304 | font-size: 11px;
305 | font-family: Arial;
306 | font-weight: bold;
307 | text-transform: uppercase;
308 | }
309 |
310 | #fork-me svg {
311 | position: absolute;
312 | top: 0;
313 | right: 0;
314 | border: 0;
315 | color: #3f3f3f;
316 | fill: #ea6f5a;
317 | height: 80px;
318 | width: 80px;
319 | }
--------------------------------------------------------------------------------
/glossary.md:
--------------------------------------------------------------------------------
1 | # 용어집
2 |
3 | - repository: 리포지토리
4 | - branch: 브랜치
5 | - commit: 커밋
6 | - local: local
7 | - remote: remote
--------------------------------------------------------------------------------
/images/gitbranch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Violet-Bora-Lee/git-tutorial/438ad3fd7ba297a26b383343461827f64af14449/images/gitbranch.png
--------------------------------------------------------------------------------
/images/gitcheckout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Violet-Bora-Lee/git-tutorial/438ad3fd7ba297a26b383343461827f64af14449/images/gitcheckout.png
--------------------------------------------------------------------------------
/images/gitcheckoutb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Violet-Bora-Lee/git-tutorial/438ad3fd7ba297a26b383343461827f64af14449/images/gitcheckoutb.png
--------------------------------------------------------------------------------
/images/gitcommit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Violet-Bora-Lee/git-tutorial/438ad3fd7ba297a26b383343461827f64af14449/images/gitcommit.png
--------------------------------------------------------------------------------
/images/gitreset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Violet-Bora-Lee/git-tutorial/438ad3fd7ba297a26b383343461827f64af14449/images/gitreset.png
--------------------------------------------------------------------------------
/images/gitrevert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Violet-Bora-Lee/git-tutorial/438ad3fd7ba297a26b383343461827f64af14449/images/gitrevert.png
--------------------------------------------------------------------------------
/images/prompt.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Violet-Bora-Lee/git-tutorial/438ad3fd7ba297a26b383343461827f64af14449/images/prompt.gif
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
18 |
19 |
git 튜토리얼
20 |
21 |
22 | git 튜토리얼은 git 명령어를 시각 자료를 사용해 이해할 수 있도록 만든 웹페이지 기반 튜토리얼입니다.
23 |
24 |
25 | add, stash는 본 튜토리얼에서 다루지 않습니다. 명령어를 직접 입력해 볼 수 있는 샌드박스에서는 버전 관리하고자
26 | 하는 파일이 모두 staging 영역에 올라와 있다고 가정하고 진행됩니다. add나 staging 영역에 관한 개념을 알고 싶다면
27 | 링크 를 참고하시기 바랍니다.
28 |
29 |
30 | 학습할 수 있는 명령어는 다음과 같습니다.
31 |
32 |
33 |
34 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
66 |
67 |
68 |
69 | commit 하고자 하는 파일들이 staging 영역에 올라와 있다고 가정합시다. 왼쪽 하단에 git commit
70 | 을 입력하고 엔터를 누르면 커밋이 완료됩니다. 원하는 만큼 커밋을 자유롭게 만들어보세요.
71 |
72 |
73 |
74 |
75 |
76 | git tag name will create a new tag named "name".
77 | Creating tags just creates a new tag pointing to the currently checked out commit.
78 |
79 |
80 | Tags can be deleted using the command git tag -d name (coming soon).
81 |
82 |
83 | Type git commit and git tag commands
84 | to your hearts desire until you understand this concept.
85 |
86 |
87 |
88 |
89 |
90 | git branch name 을 입력하면 'name'이라는 이름을 가진 브랜치가 만들어집니다.
91 | 브랜치를 만드는 것은 현재 체크아웃된 커밋을 가르키는 이름표를 만드는 것이라고 생각하시면 됩니다.
92 |
93 |
94 | git branch -d name 을 입력하면 name 브랜치를 삭제할 수 있습니다.
95 |
96 |
97 | 브랜치 개념에 대해 잘 알 수 있을 때까지 git commit 과 git branch 를
98 | 자유롭게 입력해보세요.
99 |
100 |
101 |
102 |
103 |
104 | git checkout 은 다양한 상황에서 쓸 수 있는 명령어인데, 주로 브랜치 사이를 왔다 갔다 할 때
105 | 사용합니다. git checkout dev 을 입력하면 master 브랜치에서 dev 브랜치로 이동할 수
106 | 있죠. 직접 명령어를 입력해 dev 브랜치로 이동한 후, commit을 하나 만들어 어떤 일이 일어나는지 살펴봅시다.
107 |
108 |
109 | 브랜치를 체크아웃 할 수도 있지만, 개별 커밋을 체크아웃하는 것도 가능합니다. 실제로 해봅시다.
110 | 새로운 커밋을 몇 개 만든 후 git checkout bb92e0e 을 입력해 ID가 bb92e0e인 커밋을
111 | 체크아웃하고 무슨 일이 일어나는지 살펴봅시다.
112 |
113 |
114 | git commit , git branch , git checkout 을
115 | 여러 번 번갈아 입력하면서 세 명령어가 익숙해질 때까지 자유롭게 실습해보세요.
116 |
117 |
118 |
119 |
120 |
121 | git checkout -b name 은 git branch name 과
122 | git checkout name 을 하나로 합친 명령어입니다.
123 | name 이라는 브랜치가 생성되어이있지 않다는 가정하에,
124 | git checkout -b name 을 입력하면 name 이라는 브랜치가 만들어지면서
125 | 동시에 해당 브랜치를 체크아웃하게 됩니다.
126 |
127 |
128 |
129 |
130 |
131 | git reset 을 사용하면 현재 체크아웃한 브랜치의 원하는 과거 커밋으로 HEAD를 이동시키고,
132 | 그 이후의 커밋들은 모두 버릴 수 있습니다. 원치 않는 커밋을 되돌릴 때 유용한 명령어 입니다.
133 |
134 |
135 | reset 명령어는 보통 --soft , --mixed , --hard
136 | 플래그(flag) 중 하나를 붙여서 사용합니다. soft와 mixed 플래그는 reset으로 되돌린 커밋에 있는 내용을 가지고 무언가를
137 | 다시 하고 싶을 때 주로 사용합니다. 자세한 내용은
138 | 링크
139 | 에서 확인해 보시기 바랍니다. 두 플래그는 시각적으로 표현할 수 없기 때문에 본 튜토리얼에선 hard 플래그만 사용하겠습니다.
140 |
141 |
142 | reset은 HEAD^ 와 함께 사용할 때가 많습니다. HEAD^는 'HEAD 바로 앞의 커밋'을 의미합니다.
143 | HEAD^^ 는 'HEAD 앞의 커밋 두 개'를 의미합니다. ^는 원하는 만큼 붙여서 사용할 수 있습니다.
144 |
145 |
146 | remote 리포지토리에 이미 푸시했거나 머지된 커밋은 절대 git reset 을 사용해서 버리면 안 됩니다.
147 | 이렇게 하면 local 리포지토리와 remote 리포지토리의 싱크가 맞지 않기 때문입니다. reset, push 등에 대해 정확히 알기 전까지
148 | reset을 함부로 사용하지 마세요!
149 |
150 |
151 |
152 |
153 |
154 | 이미 push되어서 팀원간에 공유되고 있는 커밋을 되돌릴 땐 git reset 을 사용할 수 없습니다.
155 | 대신 git revert 를 사용해야 합니다.
156 |
157 |
158 | git revert 를 입력하면 되돌리려고 하는 커밋 안에 있는 작업을 삭제해주는 새로운 커밋이
159 | 만들어집니다.
160 |
161 |
162 |
163 |
164 |
165 | git merge 를 사용하면 두 개의 부모 커밋을 가지는 새로운 커밋이 하나 만들어집니다.
166 | 이 커밋에는 두 브랜치에서 했던 작업의 스냅숏이 포함됩니다.
167 |
168 |
169 | 합치려는 두 개의 브랜치에 분기가 없었다면 'fast-forward' 전략을 사용해 머지합니다.
170 | ff 브랜치를 체크아웃하고 git merge dev 를 입력해 직접 확인해봅시다.
171 |
172 |
173 |
174 |
175 |
176 | git rebase 를 사용하면 현재 체크아웃한 브랜치의 커밋들이 '베이스(기준)' 역할을 하는 브랜치
177 | 끝으로 '이동'합니다. dev 브랜치를 체크아웃한 후, git rebase master 를
178 | 입력해 직접 확인해봅시다.
179 |
180 |
181 | rebase를 사용할 때는 커밋 ID가 변한다는 점에 주의하셔야 합니다.
182 |
183 |
184 | rebase를 하면 기존 커밋과 동일한 내용을 담은 '새로운 커밋'들이 '이동'하고, 기존 커밋들은
185 | 그 자리에 남아있습니다. 따라서 다른 사람에게 공유된 커밋이 들어있는 브랜치를 rebase하면 안됩니다.
186 |
187 |
188 |
189 |
190 |
191 | git fetch will update all of the "remote tracking branches" in your local repository.
192 | Remote tracking branches are tagged in grey.
193 |
194 |
195 |
196 |
197 |
198 | A git pull is a two step process that first does a git fetch ,
199 | and then does a git merge of the remote tracking branch associated with your current branch.
200 | If you have no current branch, the process will stop after fetching.
201 |
202 |
203 | If the argument "--rebase" was given by typing git pull --rebase , the second step of
204 | pull process will be a rebase instead of a merge. This can be set to the default behavior by configuration by typing:
205 | git config branch.BRANCHNAME.rebase true .
206 |
207 |
208 |
209 |
210 |
211 | A git push will find the commits you have on your local branch that the corresponding branch
212 | on the origin server does not have, and send them to the remote repository.
213 |
214 |
215 | By default, all pushes must cause a fast-forward merge on the remote repository. If there is any divergence between
216 | your local branch and the remote branch, your push will be rejected. In this scenario, you need to pull first and then
217 | you will be able to push again.
218 |
219 |
220 |
221 |
222 |
223 | One simple example of the use of git reset is to completely restore your local repository
224 | state to that of the origin.
225 | You can do so by typing git reset origin/master .
226 |
227 |
228 | Note that this won't delete untracked files, you will have to delete those separately with
229 | the command git clean -df .
230 |
231 |
232 |
233 |
234 |
235 | Below is a situation in which you are working in a local branch that is all your own. You want to receive the latest code
236 | from the origin server's master branch. To update your local branch, you can do it without having to switch branches!
237 |
238 |
239 | First do a git fetch , then type git rebase origin/master !
240 |
241 |
242 |
243 |
244 |
245 | git branch -d is used to delete branches.
246 | I have pre-created a bunch of branches for you to delete in the playground below.
247 | Have at it.
248 |
249 |
250 |
251 |
252 |
253 | Do whatever you want in this free playground.
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
280 |
281 |
286 |
287 |
289 |
290 |
291 |
293 |
294 |
295 |
297 |
298 |
299 |
300 |
301 |
553 |
554 |
555 |
--------------------------------------------------------------------------------
/js/controlbox.js:
--------------------------------------------------------------------------------
1 | define(['vendor/yargs-parser', 'd3'],
2 | function(_yargs) {
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 || '아래에 git 명령어를 입력해보세요.';
24 | this._commandHistory = [];
25 | this._currentCommand = -1;
26 | this._tempCommand = '';
27 | this.rebaseConfig = {}; // to configure branches for rebase
28 |
29 | this.undoHistory = {
30 | pointer: 0,
31 | stack: [
32 | { hv: this.historyView.serialize() }
33 | ]
34 | }
35 |
36 | this.historyView.on('lock', this.lock.bind(this))
37 | this.historyView.on('unlock', this.unlock.bind(this))
38 | }
39 |
40 | ControlBox.prototype = {
41 | lock: function () {
42 | this.locked = true
43 | },
44 |
45 | unlock: function () {
46 | this.locked = false
47 | this.createUndoSnapshot(true)
48 | },
49 |
50 | createUndoSnapshot: function (replace) {
51 | var state = this.historyView.serialize()
52 | if (!replace) {
53 | this.undoHistory.pointer++
54 | this.undoHistory.stack.length = this.undoHistory.pointer
55 | this.undoHistory.stack.push({ hv: state })
56 | } else {
57 | this.undoHistory.stack[this.undoHistory.pointer] = { hv: state }
58 | }
59 | },
60 |
61 | render: function(container) {
62 | var cBox = this,
63 | cBoxContainer, log, input;
64 |
65 | cBoxContainer = container.append('div')
66 | .classed('control-box', true);
67 |
68 |
69 | log = cBoxContainer.append('div')
70 | .classed('log', true);
71 |
72 | input = cBoxContainer.append('input')
73 | .attr('type', 'text')
74 | .attr('placeholder', 'git 명령어 입력');
75 |
76 | input.on('keyup', function() {
77 | var e = d3.event;
78 |
79 | switch (e.keyCode) {
80 | case 13:
81 | if (this.value.trim() === '' || cBox.locked) {
82 | return;
83 | }
84 |
85 | cBox._commandHistory.unshift(this.value);
86 | cBox._tempCommand = '';
87 | cBox._currentCommand = -1;
88 | cBox.command(this.value);
89 | this.value = '';
90 | e.stopImmediatePropagation();
91 | break;
92 | case 38:
93 | var previousCommand = cBox._commandHistory[cBox._currentCommand + 1];
94 | if (cBox._currentCommand === -1) {
95 | cBox._tempCommand = this.value;
96 | }
97 |
98 | if (typeof previousCommand === 'string') {
99 | cBox._currentCommand += 1;
100 | this.value = previousCommand;
101 | this.value = this.value; // set cursor to end
102 | }
103 | e.stopImmediatePropagation();
104 | break;
105 | case 40:
106 | var nextCommand = cBox._commandHistory[cBox._currentCommand - 1];
107 | if (typeof nextCommand === 'string') {
108 | cBox._currentCommand -= 1;
109 | this.value = nextCommand;
110 | this.value = this.value; // set cursor to end
111 | } else {
112 | cBox._currentCommand = -1;
113 | this.value = cBox._tempCommand;
114 | this.value = this.value; // set cursor to end
115 | }
116 | e.stopImmediatePropagation();
117 | break;
118 | }
119 | });
120 |
121 | this.container = cBoxContainer;
122 | this.terminalOutput = log;
123 | this.input = input;
124 |
125 | this.info(this.initialMessage);
126 | },
127 |
128 | destroy: function() {
129 | this.terminalOutput.remove();
130 | this.input.remove();
131 | this.container.remove();
132 |
133 | for (var prop in this) {
134 | if (this.hasOwnProperty(prop)) {
135 | this[prop] = null;
136 | }
137 | }
138 | },
139 |
140 | _scrollToBottom: function() {
141 | var log = this.terminalOutput.node();
142 | log.scrollTop = log.scrollHeight;
143 | },
144 |
145 | command: function(entry) {
146 | if (entry.trim() === '') {
147 | return;
148 | }
149 |
150 | if (entry.trim().toLowerCase() === 'undo') {
151 | var lastId = this.undoHistory.pointer - 1
152 | var lastState = this.undoHistory.stack[lastId]
153 | if (lastState) {
154 | this.historyView.deserialize(lastState.hv)
155 | this.undoHistory.pointer = lastId
156 | } else {
157 | this.error("Nothing to undo")
158 | }
159 | this.terminalOutput.append('div')
160 | .classed('command-entry', true)
161 | .html(entry);
162 | return
163 | }
164 |
165 | if (entry.trim().toLowerCase() === 'redo') {
166 | var lastId = this.undoHistory.pointer + 1
167 | var lastState = this.undoHistory.stack[lastId]
168 | if (lastState) {
169 | this.historyView.deserialize(lastState.hv)
170 | this.undoHistory.pointer = lastId
171 | } else {
172 | this.error("Nothing to redo")
173 | }
174 | this.terminalOutput.append('div')
175 | .classed('command-entry', true)
176 | .html(entry);
177 | return
178 | }
179 |
180 | var split = entry.split(' ');
181 |
182 | this.terminalOutput.append('div')
183 | .classed('command-entry', true)
184 | .html(entry);
185 |
186 | this._scrollToBottom();
187 |
188 | if (split[0] !== 'git') {
189 | return this.error();
190 | }
191 |
192 | var method = split[1].replace(/-/g, '_'),
193 | args = split.slice(2),
194 | argsStr = args.join(' ')
195 |
196 | var options = yargs(argsStr)
197 |
198 | try {
199 | if (typeof this[method] === 'function') {
200 | this[method](args, options, argsStr);
201 | this.createUndoSnapshot()
202 | } else {
203 | this.error();
204 | }
205 | } catch (ex) {
206 | console.error(ex.stack)
207 | var msg = (ex && ex.message) ? ex.message : null;
208 | this.error(msg);
209 | }
210 | },
211 |
212 | info: function(msg) {
213 | this.terminalOutput.append('div').classed('info', true).html(msg);
214 | this._scrollToBottom();
215 | },
216 |
217 | error: function(msg) {
218 | msg = msg || '알 수 없는 명령어입니다.';
219 | this.terminalOutput.append('div').classed('error', true).html(msg);
220 | this._scrollToBottom();
221 | },
222 |
223 | transact: function(action, after) {
224 | var oldCommit = this.historyView.getCommit('HEAD')
225 | var oldBranch = this.historyView.currentBranch
226 | var oldRef = oldBranch || oldCommit.id
227 | action.call(this)
228 | var newCommit = this.historyView.getCommit('HEAD')
229 | var newBranch = this.historyView.currentBranch
230 | var newRef = newBranch || newCommit.id
231 | after.call(this, {
232 | commit: oldCommit,
233 | branch: oldBranch,
234 | ref: oldRef
235 | }, {
236 | commit: newCommit,
237 | branch: newBranch,
238 | ref: newRef
239 | })
240 | },
241 |
242 | commit: function(args, opts, cmdStr) {
243 | opts = yargs(cmdStr, {
244 | boolean: ['amend'],
245 | string: ['m']
246 | })
247 | var msg = ""
248 | this.transact(function() {
249 | if (opts.amend) {
250 | this.historyView.amendCommit(opts.m || this.historyView.getCommit('head').message)
251 | } else {
252 | this.historyView.commit(null, opts.m);
253 | }
254 | }, function(before, after) {
255 | var reflogMsg = 'commit: ' + msg
256 | this.historyView.addReflogEntry(
257 | 'HEAD', after.commit.id, reflogMsg
258 | )
259 | if(before.branch) {
260 | this.historyView.addReflogEntry(
261 | before.branch, after.commit.id, reflogMsg
262 | )
263 | }
264 | })
265 | },
266 |
267 | log: function(args) {
268 | if (args.length > 1) {
269 | return this.error("'git log' can take at most one argument in this tool")
270 | }
271 | var logs = this.historyView.getLogEntries(args[0] || 'head').join('
')
272 | this.info(logs)
273 | },
274 |
275 | rev_parse: function(args) {
276 | args.forEach(function(arg) {
277 | this.info(this.historyView.revparse(arg))
278 | }, this)
279 | },
280 |
281 | cherry_pick: function (args, opt, cmdStr) {
282 | opt = yargs(cmdStr, {
283 | number: ['m']
284 | })
285 |
286 | if (!opt._.length) {
287 | this.error('You must specify one or more commits to cherry-pick');
288 | return
289 | }
290 |
291 | if (opt.m !== undefined && isNaN(opt.m)) {
292 | this.error("switch 'm' expects a numerical value");
293 | return
294 | }
295 |
296 | // FIXME: because `cherryPick` is asynchronous,
297 | // it is responsible for its own reflog entries
298 | this.historyView.cherryPick(opt._, opt.m);
299 | },
300 |
301 | branch: function(args, options, cmdStr) {
302 | options = yargs(cmdStr, {
303 | alias: { delete: ['d'], remote: ['r'], all: ['a'] },
304 | boolean: ['a', 'r']
305 | })
306 | var branchName = options._[0]
307 | var startPoint = options._[1] || 'head'
308 |
309 | if (options.delete) {
310 | return this.historyView.deleteBranch(options.delete);
311 | }
312 |
313 | if (options.remote) {
314 | return this.info('This command normally displays all of your remote tracking branches.');
315 | }
316 |
317 | if (options.all) {
318 | return this.info('This command normally displays all of your tracking branches, both remote and local.');
319 | }
320 |
321 | if (options._[2]) {
322 | return this.error('Incorrect usage - supplied too many arguments')
323 | }
324 |
325 | if (!branchName) {
326 | var branches = this.historyView.getBranchList().join('
')
327 | return this.info(branches)
328 | }
329 |
330 | this.transact(function() {
331 | this.historyView.branch(branchName, startPoint)
332 | }, function(before, after) {
333 | var branchCommit = this.historyView.getCommit(branchName)
334 | var reflogMsg = "branch: created from " + before.ref
335 | this.historyView.addReflogEntry(branchName, branchCommit.id, reflogMsg)
336 | })
337 |
338 | },
339 |
340 | checkout: function(args, opts) {
341 | if (opts.b) {
342 | if (opts._[0]) {
343 | this.branch(null, null, opts.b + ' ' + opts._[0])
344 | } else {
345 | this.branch(null, null, opts.b)
346 | }
347 | }
348 |
349 | var name = opts.b || opts._[0]
350 |
351 | this.transact(function() {
352 | this.historyView.checkout(name);
353 | }, function(before, after) {
354 | this.historyView.addReflogEntry(
355 | 'HEAD', after.commit.id,
356 | 'checkout: moving from ' + before.ref +
357 | ' to ' + name
358 | )
359 | })
360 | },
361 |
362 | tag: function(args) {
363 | if (args.length < 1) {
364 | this.info(
365 | 'You need to give a tag name. ' +
366 | 'Normally if you don\'t give a name, ' +
367 | 'this command will list your local tags on the screen.'
368 | );
369 |
370 | return;
371 | }
372 |
373 | while (args.length > 0) {
374 | var arg = args.shift();
375 |
376 | try {
377 | this.historyView.tag(arg);
378 | } catch (err) {
379 | if (err.message.indexOf('already exists') === -1) {
380 | throw new Error(err.message);
381 | }
382 | }
383 | }
384 | },
385 |
386 | doReset: function (name) {
387 | this.transact(function() {
388 | this.historyView.reset(name);
389 | }, function(before, after) {
390 | var reflogMsg = "reset: moving to " + name
391 | this.historyView.addReflogEntry(
392 | 'HEAD', after.commit.id, reflogMsg
393 | )
394 | if (before.branch) {
395 | this.historyView.addReflogEntry(
396 | before.branch, after.commit.id, reflogMsg
397 | )
398 | }
399 | })
400 | },
401 |
402 | reset: function(args) {
403 | while (args.length > 0) {
404 | var arg = args.shift();
405 |
406 | switch (arg) {
407 | case '--soft':
408 | this.info(
409 | '실제 환경에선 --soft 플래그를 붙여서 사용할 수 있는데,' +
410 | '이를 시각화 할 수 있는 방법이 없기 때문에 본 튜토리얼에선 ' +
411 | '--hard 플래그를 붙여서 reset을 실행했다고 가정하고 그 결과를 보여줍니다.'
412 | );
413 | break;
414 | case '--mixed':
415 | this.info(
416 | '실제 환경에선 --soft 플래그를 붙여서 사용할 수 있는데,' +
417 | '이를 시각화 할 수 있는 방법이 없기 때문에 본 튜토리얼에선 ' +
418 | '--hard 플래그를 붙여서 reset을 실행했다고 가정하고 그 결과를 보여줍니다.'
419 | );
420 | break;
421 | case '--hard':
422 | this.doReset(args.join(' '));
423 | args.length = 0;
424 | break;
425 | default:
426 | var remainingArgs = [arg].concat(args);
427 | args.length = 0;
428 | this.info('(--hard를 붙여서 실행했다고 가정)');
429 | this.doReset(remainingArgs.join(' '));
430 | }
431 | }
432 | },
433 |
434 | clean: function(args) {
435 | this.info('Deleting all of your untracked files...');
436 | },
437 |
438 | revert: function(args, opt, cmdStr) {
439 | opt = yargs(cmdStr, {
440 | number: ['m']
441 | })
442 |
443 | if (!opt._.length) {
444 | this.error('되돌릴 커밋을 명시해주세요.');
445 | return
446 | }
447 |
448 | if (opt.m !== undefined && isNaN(opt.m)) {
449 | this.error("switch 'm' expects a numerical value");
450 | return
451 | }
452 |
453 | this.transact(function() {
454 | this.historyView.revert(opt._, opt.m);
455 | }, function(before, after) {
456 | var reflogMsg = 'revert: ' + before.commit.message || before.commit.id
457 | this.historyView.addReflogEntry(
458 | 'HEAD', after.commit.id, reflogMsg
459 | )
460 | if(before.branch) {
461 | this.historyView.addReflogEntry(
462 | before.branch, after.commit.id, reflogMsg
463 | )
464 | }
465 | })
466 | },
467 |
468 | merge: function(args) {
469 | var noFF = false;
470 | var branch = args[0];
471 | var result
472 | if (args.length === 2) {
473 | if (args[0] === '--no-ff') {
474 | noFF = true;
475 | branch = args[1];
476 | } else if (args[1] === '--no-ff') {
477 | noFF = true;
478 | branch = args[0];
479 | } else {
480 | this.info('This demo only supports the --no-ff switch..');
481 | }
482 | }
483 |
484 | this.transact(function() {
485 | result = this.historyView.merge(branch, noFF);
486 |
487 | if (result === 'Fast-Forward') {
488 | this.info('fast-forward로 머지하였습니다.');
489 | }
490 | }, function(before, after) {
491 | var reflogMsg = "merge " + branch + ": "
492 | if (result === 'Fast-Forward') {
493 | reflogMsg += "Fast-forward"
494 | } else {
495 | reflogMsg += "Merge made by the 'recursive' strategy."
496 | }
497 | this.historyView.addReflogEntry(
498 | 'HEAD', after.commit.id, reflogMsg
499 | )
500 | if (before.branch) {
501 | this.historyView.addReflogEntry(
502 | before.branch, after.commit.id, reflogMsg
503 | )
504 | }
505 | })
506 | },
507 |
508 | rebase: function(args) {
509 | var ref = args.shift(),
510 | result = this.historyView.rebase(ref);
511 |
512 | // FIXME: rebase is async, so manages its own
513 | // reflog entries
514 | if (result === 'Fast-Forward') {
515 | this.info('Fast-forwarded to ' + ref + '.');
516 | }
517 | },
518 |
519 | fetch: function() {
520 | if (!this.originView) {
521 | throw new Error('There is no remote server to fetch from.');
522 | }
523 |
524 | var origin = this.originView,
525 | local = this.historyView,
526 | remotePattern = /^origin\/([^\/]+)$/,
527 | rtb, isRTB, fb,
528 | fetchBranches = {},
529 | fetchIds = [], // just to make sure we don't fetch the same commit twice
530 | fetchCommits = [],
531 | fetchCommit,
532 | resultMessage = '';
533 |
534 | // determine which branches to fetch
535 | for (rtb = 0; rtb < local.branches.length; rtb++) {
536 | isRTB = remotePattern.exec(local.branches[rtb]);
537 | if (isRTB) {
538 | fetchBranches[isRTB[1]] = 0;
539 | }
540 | }
541 |
542 | // determine which commits the local repo is missing from the origin
543 | for (fb in fetchBranches) {
544 | if (origin.branches.indexOf(fb) > -1) {
545 | fetchCommit = origin.getCommit(fb);
546 |
547 | var notInLocal = local.getCommit(fetchCommit.id) === null;
548 | while (notInLocal) {
549 | if (fetchIds.indexOf(fetchCommit.id) === -1) {
550 | fetchCommits.unshift(fetchCommit);
551 | fetchIds.unshift(fetchCommit.id);
552 | }
553 | fetchBranches[fb] += 1;
554 | fetchCommit = origin.getCommit(fetchCommit.parent);
555 | notInLocal = local.getCommit(fetchCommit.id) === null;
556 | }
557 | }
558 | }
559 |
560 | // add the fetched commits to the local commit data
561 | for (var fc = 0; fc < fetchCommits.length; fc++) {
562 | fetchCommit = fetchCommits[fc];
563 | local.commitData.push({
564 | id: fetchCommit.id,
565 | parent: fetchCommit.parent,
566 | tags: []
567 | });
568 | }
569 |
570 | // update the remote tracking branch tag locations
571 | for (fb in fetchBranches) {
572 | if (origin.branches.indexOf(fb) > -1) {
573 | var remoteLoc = origin.getCommit(fb).id;
574 | local.moveTag('origin/' + fb, remoteLoc);
575 | }
576 |
577 | resultMessage += 'Fetched ' + fetchBranches[fb] + ' commits on ' + fb + '.';
578 | }
579 |
580 | this.info(resultMessage);
581 |
582 | local.renderCommits();
583 | },
584 |
585 | pull: function(args) {
586 | var control = this,
587 | local = this.historyView,
588 | currentBranch = local.currentBranch,
589 | rtBranch = 'origin/' + currentBranch,
590 | isFastForward = false;
591 |
592 | this.fetch();
593 |
594 | if (!currentBranch) {
595 | throw new Error('You are not currently on a branch.');
596 | }
597 |
598 | if (local.branches.indexOf(rtBranch) === -1) {
599 | throw new Error('Current branch is not set up for pulling.');
600 | }
601 |
602 | this.lock()
603 | setTimeout(function() {
604 | try {
605 | if (args[0] === '--rebase' || control.rebaseConfig[currentBranch] === 'true') {
606 | isFastForward = local.rebase(rtBranch) === 'Fast-Forward';
607 | } else {
608 | isFastForward = local.merge(rtBranch) === 'Fast-Forward';
609 | }
610 | } catch (error) {
611 | control.error(error.message);
612 | } finally {
613 | this.unlock()
614 | }
615 |
616 | if (isFastForward) {
617 | control.info('Fast-forwarded to ' + rtBranch + '.');
618 | }
619 | }, 750);
620 | },
621 |
622 | push: function(args) {
623 | var control = this,
624 | local = this.historyView,
625 | remoteName = args.shift() || 'origin',
626 | remote = this[remoteName + 'View'],
627 | branchArgs = args.pop(),
628 | localRef = local.currentBranch,
629 | remoteRef = local.currentBranch,
630 | localCommit, remoteCommit,
631 | findCommitsToPush,
632 | isCommonCommit,
633 | toPush = [];
634 |
635 | if (remoteName === 'history') {
636 | throw new Error('Sorry, you can\'t have a remote named "history" in this example.');
637 | }
638 |
639 | if (!remote) {
640 | throw new Error('There is no remote server named "' + remoteName + '".');
641 | }
642 |
643 | if (branchArgs) {
644 | branchArgs = /^([^:]*)(:?)(.*)$/.exec(branchArgs);
645 |
646 | branchArgs[1] && (localRef = branchArgs[1]);
647 | branchArgs[2] === ':' && (remoteRef = branchArgs[3]);
648 | }
649 |
650 | if (local.branches.indexOf(localRef) === -1) {
651 | throw new Error('Local ref: ' + localRef + ' does not exist.');
652 | }
653 |
654 | if (!remoteRef) {
655 | throw new Error('No remote branch was specified to push to.');
656 | }
657 |
658 | localCommit = local.getCommit(localRef);
659 | remoteCommit = remote.getCommit(remoteRef);
660 |
661 | findCommitsToPush = function findCommitsToPush(localCommit) {
662 | var commitToPush,
663 | isCommonCommit = remote.getCommit(localCommit.id) !== null;
664 |
665 | while (!isCommonCommit) {
666 | commitToPush = {
667 | id: localCommit.id,
668 | parent: localCommit.parent,
669 | tags: []
670 | };
671 |
672 | if (typeof localCommit.parent2 === 'string') {
673 | commitToPush.parent2 = localCommit.parent2;
674 | findCommitsToPush(local.getCommit(localCommit.parent2));
675 | }
676 |
677 | toPush.unshift(commitToPush);
678 | localCommit = local.getCommit(localCommit.parent);
679 | isCommonCommit = remote.getCommit(localCommit.id) !== null;
680 | }
681 | };
682 |
683 | // push to an existing branch on the remote
684 | if (remoteCommit && remote.branches.indexOf(remoteRef) > -1) {
685 | if (!local.isAncestorOf(remoteCommit.id, localCommit.id)) {
686 | throw new Error('Push rejected. Non fast-forward.');
687 | }
688 |
689 | isCommonCommit = localCommit.id === remoteCommit.id;
690 |
691 | if (isCommonCommit) {
692 | return this.info('Everything up-to-date.');
693 | }
694 |
695 | findCommitsToPush(localCommit);
696 |
697 | remote.commitData = remote.commitData.concat(toPush);
698 | remote.moveTag(remoteRef, toPush[toPush.length - 1].id);
699 | remote.renderCommits();
700 | } else {
701 | this.info('Sorry, creating new remote branches is not supported yet.');
702 | }
703 | },
704 |
705 | config: function(args) {
706 | var path = args.shift().split('.');
707 |
708 | if (path[0] === 'branch') {
709 | if (path[2] === 'rebase') {
710 | this.rebase[path[1]] = args.pop();
711 | }
712 | }
713 | },
714 |
715 | reflog: function (args) {
716 | var reflogExistsFor = function (ref) {
717 | return this.historyView.logs[ref.toLowerCase()]
718 | }.bind(this)
719 |
720 | var ref = ""
721 | var subcommand = "show"
722 | if (args.length === 0) {
723 | ref = "HEAD"
724 | } else if (args.length === 1) {
725 | ref = args[0].trim()
726 | if (ref === "show" || ref === "expire" || ref === "delete" || ref === "exists") {
727 | subcommand = ref
728 | ref = "HEAD"
729 | }
730 | } else if (args.length === 2) {
731 | subcommand = args[0]
732 | ref = args[1]
733 | } else {
734 | this.error("'git reflog' can take at most two arguments in this tool")
735 | return
736 | }
737 |
738 | if (!ref) {
739 | this.error("No ref specified")
740 | return
741 | }
742 |
743 | if (subcommand === "exists") {
744 | if (reflogExistsFor(ref)) {
745 | this.info("Reflog for ref " + ref + " exists")
746 | } else {
747 | this.error("Reflog for ref " + ref + " does not exist")
748 | }
749 | } else if (subcommand === "show") {
750 | var logs = this.historyView.getReflogEntries(ref)
751 | this.info(logs.join("
"))
752 | } else if (subcommand === "expire" || subcommand === "delete") {
753 | this.info("Real git reflog supports the '" + subcommand +
754 | "' subcommand but this tool only supports 'show' and 'exists'")
755 | }
756 | }
757 | };
758 |
759 | return ControlBox;
760 | });
761 |
--------------------------------------------------------------------------------
/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 | historyView = new HistoryView(args);
21 | window.hv = historyView;
22 |
23 | if (args.originData) {
24 | originView = new HistoryView({
25 | name: name + '-Origin',
26 | width: 300,
27 | height: 225,
28 | commitRadius: 15,
29 | remoteName: 'origin',
30 | commitData: args.originData
31 | });
32 |
33 | originView.render(playground);
34 | window.ov = originView;
35 | }
36 |
37 | controlBox = new ControlBox({
38 | historyView: historyView,
39 | originView: originView,
40 | initialMessage: args.initialMessage
41 | });
42 | window.cb = controlBox;
43 |
44 | controlBox.render(playground);
45 | historyView.render(playground);
46 |
47 | openSandBoxes.push({
48 | hv: historyView,
49 | cb: controlBox,
50 | container: container
51 | });
52 | };
53 |
54 | reset = function() {
55 | for (var i = 0; i < openSandBoxes.length; i++) {
56 | var osb = openSandBoxes[i];
57 | osb.hv.destroy();
58 | osb.cb.destroy();
59 | osb.container.style('display', 'none');
60 | }
61 |
62 | openSandBoxes.length = 0;
63 | d3.selectAll('a.openswitch').classed('selected', false);
64 | };
65 |
66 | explainGit = {
67 | HistoryView: HistoryView,
68 | ControlBox: ControlBox,
69 | generateId: HistoryView.generateId,
70 | open: open,
71 | reset: reset
72 | };
73 |
74 | window.explainGit = explainGit;
75 |
76 | return explainGit;
77 | });
78 |
--------------------------------------------------------------------------------
/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;
241 |
242 | for (var i = 0; i < commitData.length; i++) {
243 | commit = commitData[i];
244 | !commit.parent && (commit.parent = 'initial');
245 | !commit.tags && (commit.tags = []);
246 | }
247 |
248 | this.name = config.name || 'UnnamedHistoryView';
249 | this.commitData = commitData;
250 |
251 | this.branches = ['master'];
252 | this.currentBranch = config.currentBranch || 'master';
253 |
254 | this.width = config.width;
255 | this.height = config.height || 400;
256 | this.orginalBaseLine = config.baseLine;
257 | this.baseLine = this.height * (config.baseLine || 0.6);
258 |
259 | this.commitRadius = config.commitRadius || 20;
260 | this.pointerMargin = this.commitRadius * 1.3;
261 |
262 | this.isRemote = typeof config.remoteName === 'string';
263 | this.remoteName = config.remoteName;
264 |
265 | this.logs = {}
266 |
267 | this.initialCommit = {
268 | id: 'initial',
269 | parent: null,
270 | cx: -(this.commitRadius * 2),
271 | cy: this.baseLine
272 | };
273 |
274 | this.locks = 0
275 | this._eventCallbacks = {}
276 | }
277 |
278 | HistoryView.generateId = function() {
279 | return Math.floor((1 + Math.random()) * 0x10000000).toString(16).substring(1);
280 | };
281 |
282 | HistoryView.prototype = {
283 | serialize: function () {
284 | var data = {
285 | commitData: this.commitData,
286 | branches: this.branches,
287 | logs: this.logs,
288 | currentBranch: this.currentBranch,
289 | }
290 |
291 | return JSON.stringify(data)
292 | },
293 |
294 | deserialize: function (data) {
295 | data = JSON.parse(data)
296 | this.commitData = data.commitData
297 | this.branches = data.branches
298 | this.logs = data.logs
299 | this.currentBranch = data.currentBranch
300 | this.renderCommits()
301 | this.renderTags()
302 | },
303 |
304 | emit: function (event) {
305 | var callbacks = this._eventCallbacks[event] || []
306 | callbacks.forEach(function(callback) {
307 | try {
308 | callback(event)
309 | } finally {
310 | // nothing
311 | }
312 | })
313 | },
314 |
315 | on: function (event, callback) {
316 | var callbacks = this._eventCallbacks[event] || []
317 | callbacks.push(callback)
318 | this._eventCallbacks[event] = callbacks
319 |
320 | return function () {
321 | var cbs = this._eventCallbacks[event] || []
322 | var idx = cbs.indexOf(callback)
323 | if (idx > -1) {
324 | cbs.splice(idx, 1)
325 | this._eventCallbacks[event] = cbs
326 | }
327 | }.bind(this)
328 | },
329 |
330 | lock: function () {
331 | this.locks++
332 | if (this.locks === 1) {
333 | this.emit('lock')
334 | }
335 | },
336 |
337 | unlock: function () {
338 | if (this.locks <= 0) {
339 | throw new Error('cannot unlock! not locked')
340 | }
341 |
342 | this.locks--
343 | if (this.locks === 0) {
344 | this.emit('unlock')
345 | }
346 | },
347 |
348 | /**
349 | * @method getCommit
350 | * @param ref {String} the id or a tag name that refers to the commit
351 | * @return {Object} the commit datum object
352 | */
353 | getCommit: function getCommit(ref) {
354 | // Optimization, doesn't seem to break anything
355 | if (!ref) return null;
356 | if (ref.id) return ref
357 |
358 | var commitData = this.commitData,
359 | matchedCommit = null;
360 |
361 | var reflogMatch
362 | if (reflogMatch = ref.match(/^(.*)@\{(\d+)\}(.*)$/)) {
363 | var branchName = reflogMatch[1].toLowerCase()
364 | var count = parseInt(reflogMatch[2], 10)
365 | var rest = reflogMatch[3]
366 |
367 | if (this.logs[branchName] && this.logs[branchName][count]) {
368 | ref = this.logs[branchName][count].destination + rest
369 | }
370 | }
371 |
372 | var parts = /^([^\^\~]+)(.*)$/.exec(ref),
373 | ref = parts[1],
374 | modifier = parts[2];
375 |
376 | if (ref === 'initial') {
377 | return this.initialCommit;
378 | }
379 |
380 | if (ref.toLowerCase() === 'head') {
381 | ref = 'HEAD';
382 | }
383 |
384 | var commitsThatStartWith = commitData
385 | .filter(function(c) {
386 | return c.id.indexOf(ref) === 0
387 | })
388 |
389 | if (commitsThatStartWith.length === 1) {
390 | matchedCommit = commitsThatStartWith[0]
391 | } else if (commitsThatStartWith.length > 1) {
392 | throw new Error("Ref " + ref + " is ambiguous")
393 | }
394 |
395 | for (var i = 0; i < commitData.length; i++) {
396 | var commit = commitData[i];
397 | if (commit === ref) {
398 | matchedCommit = commit;
399 | break;
400 | }
401 |
402 | if (commit.id === ref) {
403 | matchedCommit = commit;
404 | break;
405 | }
406 |
407 | var matchedTag = function() {
408 | for (var j = 0; j < commit.tags.length; j++) {
409 | var tag = commit.tags[j];
410 | if (tag === ref) {
411 | matchedCommit = commit;
412 | return true;
413 | }
414 |
415 | if (tag.indexOf('[') === 0 && tag.indexOf(']') === tag.length - 1) {
416 | tag = tag.substring(1, tag.length - 1);
417 | }
418 | if (tag === ref) {
419 | matchedCommit = commit;
420 | return true;
421 | }
422 | }
423 | }();
424 | if (matchedTag === true) {
425 | break;
426 | }
427 | }
428 |
429 | if (matchedCommit && modifier) {
430 | while (modifier) {
431 | var nextToken = modifier[0]
432 | modifier = modifier.substr(1)
433 | var amountMatch = modifier.match(/^(\d+)(.*)$/),
434 | amount = 1;
435 |
436 | if (amountMatch) {
437 | var amount = ~~amountMatch[1]
438 | }
439 |
440 | if (nextToken === '^') {
441 | if (amount === 0) {
442 | /* do nothing, refers to this commit */
443 | } else if (amount === 1) {
444 | matchedCommit = this.getCommit(matchedCommit.parent)
445 | } else if (amount === 2) {
446 | matchedCommit = this.getCommit(matchedCommit.parent2)
447 | } else {
448 | matchedCommit = null
449 | }
450 | } else if (nextToken === '~') {
451 | for (var i = 0; i < amount; i++) {
452 | if (matchedCommit && matchedCommit.parent) {
453 | matchedCommit = this.getCommit(matchedCommit.parent)
454 | }
455 | }
456 | }
457 | }
458 | }
459 |
460 | return matchedCommit;
461 | },
462 |
463 | revparse: function(refspec) {
464 | var commit
465 | if (commit = this.getCommit(refspec)) {
466 | return commit.id
467 | } else {
468 | throw new Error("Cannot find object from refspec " + refspec)
469 | }
470 | },
471 |
472 | /**
473 | * @method getCircle
474 | * @param ref {String} the id or a tag name that refers to the commit
475 | * @return {d3 Selection} the d3 selected SVG circle
476 | */
477 | getCircle: function(ref) {
478 | var circle = this.svg.select('#' + this.name + '-' + ref),
479 | commit;
480 |
481 | if (circle && !circle.empty()) {
482 | return circle;
483 | }
484 |
485 | commit = this.getCommit(ref);
486 |
487 | if (!commit) {
488 | return null;
489 | }
490 |
491 | return this.svg.select('#' + this.name + '-' + commit.id);
492 | },
493 |
494 | getCircles: function() {
495 | return this.svg.selectAll('circle.commit');
496 | },
497 |
498 | /**
499 | * @method render
500 | * @param container {String} selector for the container to render the SVG into
501 | */
502 | render: function(container) {
503 | var svgContainer, svg;
504 |
505 | svgContainer = container.append('div')
506 | .classed('svg-container', true)
507 | .classed('remote-container', this.isRemote);
508 |
509 | svg = svgContainer.append('svg:svg');
510 |
511 | svg.attr('id', this.name)
512 | .attr('width', this.width)
513 | .attr('height', this.height);
514 |
515 | if (this.isRemote) {
516 | svg.append('svg:text')
517 | .classed('remote-name-display', true)
518 | .text(this.remoteName)
519 | .attr('x', 10)
520 | .attr('y', 25);
521 | } else {
522 | svg.append('svg:text')
523 | .classed('remote-name-display', true)
524 | .text('local 리포지토리')
525 | .attr('x', 10)
526 | .attr('y', 25);
527 |
528 | svg.append('svg:text')
529 | .classed('current-branch-display', true)
530 | .attr('x', 10)
531 | .attr('y', 45);
532 | }
533 |
534 | this.svgContainer = svgContainer;
535 | this.svg = svg;
536 | this.arrowBox = svg.append('svg:g').classed('pointers', true);
537 | this.commitBox = svg.append('svg:g').classed('commits', true);
538 | this.tagBox = svg.append('svg:g').classed('tags', true);
539 |
540 | this.renderCommits();
541 |
542 | this._setCurrentBranch(this.currentBranch);
543 | },
544 |
545 | destroy: function() {
546 | this.svg.remove();
547 | this.svgContainer.remove();
548 | clearInterval(this.refreshSizeTimer);
549 |
550 | for (var prop in this) {
551 | if (this.hasOwnProperty(prop)) {
552 | this[prop] = null;
553 | }
554 | }
555 | },
556 |
557 | _calculatePositionData: function() {
558 | for (var i = 0; i < this.commitData.length; i++) {
559 | var commit = this.commitData[i];
560 | commit.cx = cx(commit, this);
561 | commit.cy = cy(commit, this);
562 | preventOverlap(commit, this);
563 | }
564 | },
565 |
566 | _resizeSvg: function() {
567 | var ele = document.getElementById(this.svg.node().id);
568 | var container = ele.parentNode;
569 | var currentWidth = ele.offsetWidth;
570 | var newWidth;
571 |
572 | if (ele.getBBox().width > container.offsetWidth)
573 | newWidth = Math.round(ele.getBBox().width);
574 | else
575 | newWidth = container.offsetWidth - 5;
576 |
577 | if (currentWidth != newWidth) {
578 | this.svg.attr('width', newWidth);
579 | container.scrollLeft = container.scrollWidth;
580 | }
581 | },
582 |
583 | renderCommits: function() {
584 | if (typeof this.height === 'string' && this.height.indexOf('%') >= 0) {
585 | var perc = this.height.substring(0, this.height.length - 1) / 100.0;
586 | var baseLineCalcHeight = Math.round(this.svg.node().parentNode.offsetHeight * perc) - 65;
587 | var newBaseLine = Math.round(baseLineCalcHeight * (this.originalBaseLine || 0.6));
588 | if (newBaseLine !== this.baseLine) {
589 | this.baseLine = newBaseLine;
590 | this.initialCommit.cy = newBaseLine;
591 | this.svg.attr('height', baseLineCalcHeight);
592 | }
593 | }
594 | this._calculatePositionData();
595 | this._calculatePositionData(); // do this twice to make sure
596 | this._renderCircles();
597 | this._renderPointers();
598 | this._renderMergePointers();
599 | this._renderIdLabels();
600 | this._resizeSvg();
601 | this.currentBranch && this.checkout(this.currentBranch);
602 | },
603 |
604 | _renderCircles: function() {
605 | var view = this,
606 | existingCircles,
607 | newCircles;
608 |
609 | existingCircles = this.commitBox.selectAll('circle.commit')
610 | .data(this.commitData, function(d) {
611 | return d.id;
612 | })
613 | .attr('id', function(d) {
614 | return view.name + '-' + d.id;
615 | })
616 | .classed('reverted', function(d) {
617 | return d.reverted || d.revertSource;
618 | })
619 | .classed('rebased', function(d) {
620 | return d.rebased || d.rebaseSource;
621 | })
622 | .classed('logging', function(d) {
623 | return d.logging;
624 | })
625 | .classed('cherry-picked', function(d) {
626 | return d.cherryPicked || d.cherryPickSource;
627 | });
628 |
629 | existingCircles.transition()
630 | .duration(500)
631 | .call(fixCirclePosition);
632 |
633 | newCircles = existingCircles.enter()
634 | .append('svg:circle')
635 | .attr('id', function(d) {
636 | return view.name + '-' + d.id;
637 | })
638 | .classed('commit', true)
639 | .classed('merge-commit', function(d) {
640 | return typeof d.parent2 === 'string';
641 | })
642 | .classed('rebased', function(d) {
643 | return d.rebased || d.rebaseSource
644 | })
645 | .classed('cherry-picked', function(d) {
646 | return d.cherryPicked || d.cherryPickSource;
647 | })
648 | .call(fixCirclePosition)
649 | .attr('r', 1)
650 | .transition("inflate")
651 | .duration(500)
652 | .attr('r', this.commitRadius)
653 |
654 | existingCircles.exit()
655 | .remove()
656 |
657 | },
658 |
659 | _renderPointers: function() {
660 | var view = this,
661 | existingPointers,
662 | newPointers;
663 |
664 | existingPointers = this.arrowBox.selectAll('line.commit-pointer')
665 | .data(this.commitData, function(d) {
666 | return d.id;
667 | })
668 | .attr('id', function(d) {
669 | return view.name + '-' + d.id + '-to-' + d.parent;
670 | });
671 |
672 | existingPointers.transition()
673 | .duration(500)
674 | .call(fixPointerStartPosition, view)
675 | .call(fixPointerEndPosition, view);
676 |
677 | newPointers = existingPointers.enter()
678 | .append('svg:line')
679 | .attr('id', function(d) {
680 | return view.name + '-' + d.id + '-to-' + d.parent;
681 | })
682 | .classed('commit-pointer', true)
683 | .call(fixPointerStartPosition, view)
684 | .attr('x2', function() {
685 | return d3.select(this).attr('x1');
686 | })
687 | .attr('y2', function() {
688 | return d3.select(this).attr('y1');
689 | })
690 | .attr('marker-end', REG_MARKER_END)
691 | .transition()
692 | .duration(500)
693 | .call(fixPointerEndPosition, view);
694 |
695 | existingPointers.exit()
696 | .remove()
697 | },
698 |
699 | _renderMergePointers: function() {
700 | var view = this,
701 | mergeCommits = [],
702 | existingPointers, newPointers;
703 |
704 | for (var i = 0; i < this.commitData.length; i++) {
705 | var commit = this.commitData[i];
706 | if (typeof commit.parent2 === 'string') {
707 | mergeCommits.push(commit);
708 | }
709 | }
710 |
711 | existingPointers = this.arrowBox.selectAll('polyline.merge-pointer')
712 | .data(mergeCommits, function(d) {
713 | return d.id;
714 | })
715 | .attr('id', function(d) {
716 | return view.name + '-' + d.id + '-to-' + d.parent2;
717 | });
718 |
719 | existingPointers.transition().duration(500)
720 | .attr('points', function(d) {
721 | var p1 = px1(d, view, 'parent2') + ',' + py1(d, view, 'parent2'),
722 | p2 = px2(d, view, 'parent2') + ',' + py2(d, view, 'parent2');
723 |
724 | return [p1, p2].join(' ');
725 | });
726 |
727 | newPointers = existingPointers.enter()
728 | .append('svg:polyline')
729 | .attr('id', function(d) {
730 | return view.name + '-' + d.id + '-to-' + d.parent2;
731 | })
732 | .classed('merge-pointer', true)
733 | .attr('points', function(d) {
734 | var x1 = px1(d, view, 'parent2'),
735 | y1 = py1(d, view, 'parent2'),
736 | p1 = x1 + ',' + y1;
737 |
738 | return [p1, p1].join(' ');
739 | })
740 | .attr('marker-end', MERGE_MARKER_END)
741 | .transition()
742 | .duration(500)
743 | .attr('points', function(d) {
744 | var points = d3.select(this).attr('points').split(' '),
745 | x2 = px2(d, view, 'parent2'),
746 | y2 = py2(d, view, 'parent2');
747 |
748 | points[1] = x2 + ',' + y2;
749 | return points.join(' ');
750 | });
751 |
752 | existingPointers.exit()
753 | .remove()
754 | },
755 |
756 | _renderIdLabels: function() {
757 | this._renderText('id-label', function(d) {
758 | return d.id + '..';
759 | }, 14);
760 | this._renderText('message-label', function(d) {
761 | return d.message;
762 | }, 24);
763 | },
764 |
765 | _renderText: function(className, getText, delta) {
766 | var view = this,
767 | existingTexts,
768 | newtexts;
769 |
770 | existingTexts = this.commitBox.selectAll('text.' + className)
771 | .data(this.commitData, function(d) {
772 | return d.id;
773 | })
774 | .text(getText);
775 |
776 | existingTexts.transition().call(fixIdPosition, view, delta);
777 |
778 | newtexts = existingTexts.enter()
779 | .insert('svg:text', ':first-child')
780 | .classed(className, true)
781 | .text(getText)
782 | .call(fixIdPosition, view, delta);
783 |
784 | existingTexts.exit()
785 | .remove()
786 | },
787 |
788 | _parseTagData: function() {
789 | var tagData = [],
790 | i,
791 | headCommit = null;
792 |
793 | for (i = 0; i < this.commitData.length; i++) {
794 | var c = this.commitData[i];
795 |
796 | for (var t = 0; t < c.tags.length; t++) {
797 | var tagName = c.tags[t];
798 | if (tagName.toUpperCase() === 'HEAD') {
799 | headCommit = c;
800 | } else if (this.branches.indexOf(tagName) === -1) {
801 | this.branches.push(tagName);
802 | }
803 |
804 | tagData.push({
805 | name: tagName,
806 | commit: c.id
807 | });
808 | }
809 | }
810 |
811 | if (!headCommit) {
812 | headCommit = this.getCommit(this.currentBranch);
813 | headCommit.tags.push('HEAD');
814 | tagData.push({
815 | name: 'HEAD',
816 | commit: headCommit.id
817 | });
818 | }
819 |
820 | // find out which commits are not branchless
821 |
822 |
823 | return tagData;
824 | },
825 |
826 | _markBranchlessCommits: function() {
827 | var branch, commit, parent, parent2, c, b;
828 |
829 | // first mark every commit as branchless
830 | for (c = 0; c < this.commitData.length; c++) {
831 | this.commitData[c].branchless = true;
832 | }
833 |
834 | for (b = 0; b < this.branches.length; b++) {
835 | branch = this.branches[b];
836 | if (branch.indexOf('/') === -1) {
837 | commit = this.getCommit(branch);
838 | parent = this.getCommit(commit.parent);
839 | parent2 = this.getCommit(commit.parent2);
840 |
841 | commit.branchless = false;
842 |
843 | while (parent) {
844 | parent.branchless = false;
845 | parent = this.getCommit(parent.parent);
846 | }
847 |
848 | // just in case this is a merge commit
849 | while (parent2) {
850 | parent2.branchless = false;
851 | parent2 = this.getCommit(parent2.parent);
852 | }
853 | }
854 | }
855 |
856 | this.svg.selectAll('circle.commit').call(applyBranchlessClass);
857 | this.svg.selectAll('line.commit-pointer').call(applyBranchlessClass);
858 | this.svg.selectAll('polyline.merge-pointer').call(applyBranchlessClass);
859 | },
860 |
861 | renderTags: function() {
862 | var view = this,
863 | tagData = this._parseTagData(),
864 | existingTags, newTags;
865 |
866 | existingTags = this.tagBox.selectAll('g.branch-tag')
867 | .data(tagData, function(d) {
868 | return d.name;
869 | });
870 |
871 | existingTags.exit().remove();
872 |
873 | existingTags.select('rect')
874 | .transition()
875 | .duration(500)
876 | .attr('y', function(d) {
877 | return tagY(d, view);
878 | })
879 | .attr('x', function(d) {
880 | var commit = view.getCommit(d.commit),
881 | width = Number(d3.select(this).attr('width'));
882 |
883 | return commit.cx - (width / 2);
884 | });
885 |
886 | existingTags.select('text')
887 | .transition()
888 | .duration(500)
889 | .attr('y', function(d) {
890 | return tagY(d, view) + 14;
891 | })
892 | .attr('x', function(d) {
893 | var commit = view.getCommit(d.commit);
894 | return commit.cx;
895 | });
896 |
897 | newTags = existingTags.enter()
898 | .append('g')
899 | .attr('class', function(d) {
900 | var classes = 'branch-tag';
901 | if (d.name.indexOf('[') === 0 && d.name.indexOf(']') === d.name.length - 1) {
902 | classes += ' git-tag';
903 | } else if (d.name.indexOf('/') >= 0) {
904 | classes += ' remote-branch';
905 | } else if (d.name.toUpperCase() === 'HEAD') {
906 | classes += ' head-tag';
907 | }
908 | return classes;
909 | });
910 |
911 | newTags.append('svg:rect')
912 | .attr('width', function(d) {
913 | return (d.name.length * 6) + 10;
914 | })
915 | .attr('height', 20)
916 | .attr('y', function(d) {
917 | return tagY(d, view);
918 | })
919 | .attr('x', function(d) {
920 | var commit = view.getCommit(d.commit),
921 | width = Number(d3.select(this).attr('width'));
922 |
923 | return commit.cx - (width / 2);
924 | });
925 |
926 | newTags.append('svg:text')
927 | .text(function(d) {
928 | if (d.name.indexOf('[') === 0 && d.name.indexOf(']') === d.name.length - 1)
929 | return d.name.substring(1, d.name.length - 1);
930 | return d.name;
931 | })
932 | .attr('y', function(d) {
933 | return tagY(d, view) + 14;
934 | })
935 | .attr('x', function(d) {
936 | var commit = view.getCommit(d.commit);
937 | return commit.cx;
938 | });
939 |
940 | existingTags.exit()
941 | .remove()
942 |
943 | this._markBranchlessCommits();
944 | },
945 |
946 | _setCurrentBranch: function(branch) {
947 | var display = this.svg.select('text.current-branch-display'),
948 | text = '현재 브랜치: ';
949 |
950 | if (branch && branch.indexOf('/') === -1) {
951 | text += branch;
952 | this.currentBranch = branch;
953 | } else {
954 | text += ' DETACHED HEAD';
955 | this.currentBranch = null;
956 | }
957 |
958 | display.text(text);
959 | },
960 |
961 | addReflogEntry: function(ref, destination, reason) {
962 | ref = ref.toLowerCase()
963 | this.logs[ref] = this.logs[ref] || []
964 | this.logs[ref].unshift({
965 | destination: destination,
966 | reason: reason
967 | })
968 | },
969 |
970 | getReflogEntries: function(ref) {
971 | if (!this.logs[ref.toLowerCase()]) {
972 | throw new Error("no reflog for " + ref)
973 | }
974 |
975 | return this.logs[ref.toLowerCase()].map(function(entry, idx) {
976 | return entry.destination + " " + ref + "@{" + idx + "} " + " " + entry.reason
977 | })
978 | },
979 |
980 | moveTag: function(tag, ref) {
981 | var currentLoc = this.getCommit(tag),
982 | newLoc = this.getCommit(ref);
983 |
984 | if (currentLoc) {
985 | currentLoc.tags.splice(currentLoc.tags.indexOf(tag), 1);
986 | }
987 |
988 | newLoc.tags.push(tag);
989 | return this;
990 | },
991 |
992 | amendCommit: function(message) {
993 | this.commit({parent: this.getCommit('head^').id}, message)
994 | },
995 |
996 | commit: function(commit, message) {
997 | commit = commit || {};
998 |
999 | !commit.id && (commit.id = HistoryView.generateId());
1000 | !commit.tags && (commit.tags = []);
1001 |
1002 | commit.message = message
1003 | if (!commit.parent) {
1004 | commit.parent = this.getCommit('HEAD').id;
1005 | }
1006 |
1007 | this.commitData.push(commit);
1008 | if (this.currentBranch) {
1009 | this.moveTag(this.currentBranch, commit.id);
1010 | }
1011 |
1012 | this.renderCommits();
1013 |
1014 | if (this.currentBranch) {
1015 | this.checkout(this.currentBranch);
1016 | } else {
1017 | this.checkout(commit.id)
1018 | }
1019 | return this;
1020 | },
1021 |
1022 | getLogEntries: function(refspec) {
1023 | var ancestors = this.getAncestorSet(refspec)
1024 | delete ancestors.initial
1025 | ancestors[refspec] = -1
1026 | var commitIds = Object.keys(ancestors)
1027 | this.lock()
1028 | this.flashProperty(commitIds, 'logging', function () {
1029 | this.unlock()
1030 | })
1031 | return commitIds.map(function(commitId) {
1032 | return {commit: this.getCommit(commitId), order: ancestors[commitId]}
1033 | }, this).sort(function(a,b) {
1034 | return a.order - b.order
1035 | }).map(function(commitInfo) {
1036 | var commit = commitInfo.commit
1037 | return commit.id + ' ' + (commit.message || "(no message)")
1038 | }, this)
1039 | },
1040 |
1041 | setProperty: function(refs, property) {
1042 | refs.forEach(function(ref) {
1043 | this.getCommit(ref)[property] = true
1044 | }, this)
1045 | },
1046 |
1047 | unsetProperty: function(refs, property) {
1048 | refs.forEach(function(ref) {
1049 | var commit = this.getCommit(ref)
1050 | delete commit[property]
1051 | }, this)
1052 | },
1053 |
1054 | cherryPick: function(refs, mainline) {
1055 | refs.forEach(function(ref) {
1056 | if (!this.getCommit(ref)) {
1057 | throw new Error("fatal: bad revision '" + ref + "'")
1058 | return
1059 | }
1060 | }, this)
1061 |
1062 | if (mainline) {
1063 | if (mainline > 2 || mainline < 1) {
1064 | throw new Error("Commit " + refs[0] + " does not have parent " + mainline)
1065 | return
1066 | }
1067 | var nonMergeRefs = refs.filter(function(ref) {
1068 | var commit = this.getCommit(ref)
1069 | return !commit.parent || !commit.parent2
1070 | }, this)
1071 |
1072 | if (nonMergeRefs.length) {
1073 | throw new Error('mainline specified but ' + nonMergeRefs[0] + ' is not a merge')
1074 | }
1075 | } else {
1076 | var mergeRefs = refs.filter(function(ref) {
1077 | var commit = this.getCommit(ref)
1078 | return commit.parent && commit.parent2
1079 | }, this)
1080 |
1081 | if (mergeRefs.length) {
1082 | throw new Error('cannot cherry-pick merge commit ' + mergeRefs[0] + ' without specifying a mainline with -m')
1083 | }
1084 | }
1085 |
1086 | if (!mainline) {
1087 | refs.forEach(function(ref) {
1088 | var commit = this.getCommit(ref)
1089 | var message = commit.message || ""
1090 | this.lock()
1091 | this.flashProperty([commit.id], 'cherryPicked', function() {
1092 | this.commit({cherryPickSource: [commit.id]}, message)
1093 | var reflogMessage = "cherry-pick: " + message
1094 | this.addReflogEntry(
1095 | 'HEAD', this.getCommit('HEAD').id, reflogMessage
1096 | )
1097 | if (this.currentBranch) {
1098 | this.addReflogEntry(
1099 | this.currentBranch, this.getCommit('HEAD').id, reflogMessage
1100 | )
1101 | }
1102 | this.unlock()
1103 | })
1104 | }, this)
1105 | } else {
1106 | refs.forEach(function(ref) {
1107 | var commit = this.getCommit(ref)
1108 | var message = commit.message || ""
1109 | var cherryPickSource = this.getNonMainlineCommits(commit.id, mainline)
1110 |
1111 | this.lock()
1112 | this.flashProperty(cherryPickSource, 'cherryPicked', function() {
1113 | this.commit({cherryPickSource: cherryPickSource}, message)
1114 | var reflogMessage = "cherry-pick: " + message
1115 | this.addReflogEntry(
1116 | 'HEAD', this.getCommit('HEAD').id, reflogMessage
1117 | )
1118 | if (this.currentBranch) {
1119 | this.addReflogEntry(
1120 | this.currentBranch, this.getCommit('HEAD').id, reflogMessage
1121 | )
1122 | }
1123 | this.unlock()
1124 | })
1125 | }, this)
1126 | }
1127 | },
1128 |
1129 | getNonMainlineCommits: function(ref, mainline) {
1130 | if (mainline === 1) mainline = 2
1131 | else if (mainline === 2) mainline = 1
1132 | else throw new Error("Mainline " + mainline + " isn't supported")
1133 | var ancestor1Set = this.getAncestorSet(ref, 1)
1134 | var ancestor2Set = this.getAncestorSet(ref, 2)
1135 | var uniqueAncestors = getUniqueSetItems(ancestor1Set, ancestor2Set)
1136 | return Object.keys(uniqueAncestors[mainline-1]).concat(ref)
1137 | },
1138 |
1139 | flashProperty: function(refs, property, callback) {
1140 | this.setProperty(refs, property)
1141 | this.renderCommits()
1142 | setTimeout(function() {
1143 | callback && callback.call(this)
1144 | setTimeout(function() {
1145 | this.unsetProperty(refs, property)
1146 | this.renderCommits()
1147 | }.bind(this), 500)
1148 | }.bind(this), 1000)
1149 | },
1150 |
1151 | getParents: function(ref, mainline) {
1152 | var commit,
1153 | parents = []
1154 | if (ref.id) {
1155 | commit = ref
1156 | } else {
1157 | commit = this.getCommit(ref)
1158 | }
1159 | if ((!mainline || mainline === 1) && commit.parent) parents.push(commit.parent)
1160 | if ((!mainline || mainline === 2) && commit.parent2) parents.push(commit.parent2)
1161 | return parents
1162 | },
1163 |
1164 | getAncestorSet: function(ref, mainline) {
1165 | var ancestors = {}
1166 | var i = 1;
1167 | function getAncestor(currentRef, currentMainline) {
1168 | var parents = this.getParents(currentRef, currentMainline)
1169 | parents.forEach(function(parentRef) {
1170 | ancestors[parentRef] = i++
1171 | getAncestor.call(this, parentRef)
1172 | }, this)
1173 | }
1174 | getAncestor.call(this, ref, mainline)
1175 | return ancestors
1176 | },
1177 |
1178 | getBranchList: function() {
1179 | return this.commitData.reduce(function(acc, commit) {
1180 | return acc.concat(commit.tags.filter(function(tag) {
1181 | return !tag.match(/^\[.*\]$/) && tag !== 'HEAD'
1182 | }))
1183 | }, []).map(function(tag) {
1184 | if (this.currentBranch && (tag.toLowerCase() === this.currentBranch.toLowerCase())) {
1185 | return '* ' + tag
1186 | } else {
1187 | return ' ' + tag
1188 | }
1189 | }, this)
1190 | },
1191 |
1192 | branch: function(name, startCommit) {
1193 | if (!name || name.trim() === '') {
1194 | throw new Error('브랜치 이름을 입력하세요.');
1195 | }
1196 |
1197 | if (name === 'HEAD') {
1198 | throw new Error('HEAD는 브랜치 이름으로 사용할 수 없습니다.');
1199 | }
1200 |
1201 | if (this.branches.indexOf(name) > -1) {
1202 | throw new Error('이미 존재하는 브랜치입니다.');
1203 | }
1204 |
1205 | var startPoint = this.getCommit(startCommit || 'head')
1206 | if (!startPoint) {
1207 | throw new Error("fatal: Not a valid object name:'" + startCommit + "'")
1208 | }
1209 | startPoint.tags.push(name);
1210 | this.renderTags();
1211 | return this;
1212 | },
1213 |
1214 | tag: function(name) {
1215 | this.branch('[' + name + ']');
1216 | },
1217 |
1218 | deleteBranch: function(name) {
1219 | var branchIndex,
1220 | commit;
1221 |
1222 | if (!name || name.trim() === '') {
1223 | throw new Error('브랜치 이름을 입력하세요.');
1224 | }
1225 |
1226 | if (name === this.currentBranch) {
1227 | throw new Error('Cannot delete the currently checked-out branch.');
1228 | }
1229 |
1230 | branchIndex = this.branches.indexOf(name);
1231 |
1232 | if (branchIndex === -1) {
1233 | throw new Error('That branch doesn\'t exist.');
1234 | }
1235 |
1236 | this.branches.splice(branchIndex, 1);
1237 | commit = this.getCommit(name);
1238 | delete this.logs[name]
1239 | branchIndex = commit.tags.indexOf(name);
1240 |
1241 | if (branchIndex > -1) {
1242 | commit.tags.splice(branchIndex, 1);
1243 | }
1244 |
1245 | this.renderTags();
1246 | },
1247 |
1248 | checkout: function(ref) {
1249 | var commit = this.getCommit(ref);
1250 |
1251 | if (!commit) {
1252 | throw new Error('Cannot find commit: ' + ref);
1253 | }
1254 |
1255 | var previousHead = this.getCircle('HEAD'),
1256 | newHead = this.getCircle(commit.id);
1257 |
1258 | if (previousHead && !previousHead.empty()) {
1259 | previousHead.classed('checked-out', false);
1260 | }
1261 |
1262 | var isBranch = this.branches.indexOf(ref) !== -1
1263 | this._setCurrentBranch(isBranch ? ref : null);
1264 | this.moveTag('HEAD', commit.id);
1265 | this.renderTags();
1266 |
1267 | newHead.classed('checked-out', true);
1268 |
1269 | return this;
1270 | },
1271 |
1272 | reset: function(ref) {
1273 | var commit = this.getCommit(ref);
1274 |
1275 | if (!commit) {
1276 | throw new Error('Cannot find ref: ' + ref);
1277 | }
1278 |
1279 | if (this.currentBranch) {
1280 | this.moveTag(this.currentBranch, commit.id);
1281 | this.checkout(this.currentBranch);
1282 | } else {
1283 | this.checkout(commit.id);
1284 | }
1285 |
1286 | return this;
1287 | },
1288 |
1289 | revert: function(refs, mainline) {
1290 | refs.forEach(function(ref) {
1291 | if (!this.getCommit(ref)) {
1292 | throw new Error("fatal: bad revision '" + ref + "'")
1293 | return
1294 | }
1295 | }, this)
1296 |
1297 | if (mainline) {
1298 | if (mainline > 2 || mainline < 1) {
1299 | throw new Error("Commit " + refs[0] + " does not have parent " + mainline)
1300 | return
1301 | }
1302 | var nonMergeRefs = refs.filter(function(ref) {
1303 | var commit = this.getCommit(ref)
1304 | return !commit.parent || !commit.parent2
1305 | }, this)
1306 |
1307 | if (nonMergeRefs.length) {
1308 | throw new Error('mainline specified but ' + nonMergeRefs[0] + ' is not a merge')
1309 | }
1310 | } else {
1311 | var mergeRefs = refs.filter(function(ref) {
1312 | var commit = this.getCommit(ref)
1313 | return commit.parent && commit.parent2
1314 | }, this)
1315 |
1316 | if (mergeRefs.length) {
1317 | throw new Error('cannot revert merge commit ' + mergeRefs[0] + ' without specifying a mainline with -m')
1318 | }
1319 | }
1320 |
1321 | if (!mainline) {
1322 | refs.forEach(function(ref) {
1323 | var commit = this.getCommit(ref)
1324 | var message = commit.message || ""
1325 | this.lock()
1326 | this.flashProperty([commit.id], 'reverted', function() {
1327 | this.commit({revertSource: [commit.id]}, "Revert " + commit.id)
1328 | var reflogMessage = "revert: " + message
1329 | this.addReflogEntry(
1330 | 'HEAD', this.getCommit('HEAD').id, reflogMessage
1331 | )
1332 | if (this.currentBranch) {
1333 | this.addReflogEntry(
1334 | this.currentBranch, this.getCommit('HEAD').id, reflogMessage
1335 | )
1336 | }
1337 | this.unlock()
1338 | })
1339 | }, this)
1340 | } else {
1341 | refs.forEach(function(ref) {
1342 | var commit = this.getCommit(ref)
1343 | var message = commit.message || ""
1344 | var revertSource = this.getNonMainlineCommits(commit.id, mainline)
1345 |
1346 | this.lock()
1347 | this.flashProperty(revertSource, 'reverted', function() {
1348 | this.commit({revertSource: revertSource}, "Revert " + commit.id)
1349 | var reflogMessage = "revert: " + message
1350 | this.addReflogEntry(
1351 | 'HEAD', this.getCommit('HEAD').id, reflogMessage
1352 | )
1353 | if (this.currentBranch) {
1354 | this.addReflogEntry(
1355 | this.currentBranch, this.getCommit('HEAD').id, reflogMessage
1356 | )
1357 | }
1358 | this.unlock()
1359 | })
1360 | }, this)
1361 | }
1362 | },
1363 |
1364 | fastForward: function(ref) {
1365 | var targetCommit = this.getCommit(ref);
1366 |
1367 | if (this.currentBranch) {
1368 | this.moveTag(this.currentBranch, targetCommit.id);
1369 | this.checkout(this.currentBranch);
1370 | } else {
1371 | this.checkout(targetCommit.id);
1372 | }
1373 | },
1374 |
1375 | isAncestorOf: function(search, start) {
1376 | var startCommit = this.getCommit(start),
1377 | searchCommit = this.getCommit(search)
1378 |
1379 | if (startCommit === searchCommit) {
1380 | return true
1381 | } else {
1382 | var ancestorOnFirstParent = startCommit.parent && this.isAncestorOf(searchCommit.id, startCommit.parent)
1383 | var ancestorOnSecondParent = startCommit.parent2 && this.isAncestorOf(searchCommit.id, startCommit.parent2)
1384 | return ancestorOnFirstParent || ancestorOnSecondParent
1385 | }
1386 | },
1387 |
1388 | merge: function(ref, noFF) {
1389 | var mergeTarget = this.getCommit(ref),
1390 | currentCommit = this.getCommit('HEAD');
1391 | if (!mergeTarget) {
1392 | throw new Error('Cannot find ref: ' + ref);
1393 | }
1394 |
1395 |
1396 | if (currentCommit.id === mergeTarget.id) {
1397 | throw new Error('Already up-to-date.');
1398 | } else if (currentCommit.parent2 === mergeTarget.id) {
1399 | throw new Error('Already up-to-date.');
1400 | } else if (this.isAncestorOf(mergeTarget, currentCommit)) {
1401 | throw new Error('Already up-to-date.');
1402 | } else if (noFF === true) {
1403 | var branchStartCommit = this.getCommit(mergeTarget.parent);
1404 | while (branchStartCommit.parent !== currentCommit.id) {
1405 | branchStartCommit = this.getCommit(branchStartCommit.parent);
1406 | }
1407 |
1408 | branchStartCommit.isNoFFBranch = true;
1409 |
1410 | this.commit({
1411 | parent2: mergeTarget.id,
1412 | isNoFFCommit: true
1413 | }, 'Merge');
1414 | } else if (this.isAncestorOf(currentCommit.id, mergeTarget.id)) {
1415 | this.fastForward(mergeTarget);
1416 | return 'Fast-Forward';
1417 | } else {
1418 | this.commit({
1419 | parent2: mergeTarget.id
1420 | }, 'Merge');
1421 | }
1422 | },
1423 |
1424 | rebase: function(ref) {
1425 | var targetCommit = this.getCommit(ref)
1426 | if (!targetCommit) {
1427 | throw new Error("Cannot find commit " + ref) // TODO: better message
1428 | }
1429 |
1430 | this.branch('ORIG_HEAD')
1431 | var origHeadCommit = this.getCommit('ORIG_HEAD')
1432 | var origBranch = this.currentBranch
1433 | var origRef = origBranch || origHeadCommit.id
1434 |
1435 | this.checkout(targetCommit.id)
1436 | this.addReflogEntry(
1437 | 'HEAD', targetCommit.id, 'rebase: checkout ' + ref
1438 | )
1439 |
1440 | var ancestorsFromTarget = this.getAncestorSet(ref)
1441 | var ancestorsFromBase = this.getAncestorSet(origHeadCommit.id)
1442 | var uniqueAncestors = getUniqueSetItems(ancestorsFromTarget, ancestorsFromBase)[1]
1443 | var commitsToCopy = Object.keys(uniqueAncestors).concat(origHeadCommit.id)
1444 | .sort(function(key1, key2) {
1445 | console.log(key1, uniqueAncestors[key1], key2, uniqueAncestors[key2])
1446 | return uniqueAncestors[key2] - uniqueAncestors[key1]
1447 | })
1448 |
1449 | this.lock()
1450 | setTimeout(function() {
1451 | this.flashProperty(commitsToCopy, 'rebased', function() {
1452 | commitsToCopy.forEach(function(ref) {
1453 | this.commit({rebased: true, rebaseSource: ref}, this.getCommit(ref).message)
1454 | }, this)
1455 | var newHeadCommit = this.getCommit('HEAD')
1456 | setTimeout(function() {
1457 | this.deleteBranch('ORIG_HEAD')
1458 | if (origBranch) {
1459 | this.moveTag(origBranch, newHeadCommit.id)
1460 | this.reset(origBranch)
1461 | this._setCurrentBranch(origBranch)
1462 | this.addReflogEntry(
1463 | 'HEAD', targetCommit.id, 'rebase finished: returning to resf/heads/' + origBranch
1464 | )
1465 | this.addReflogEntry(
1466 | origBranch, newHeadCommit.id, 'rebase finished: refs/heads/' +
1467 | origBranch + ' onto ' + targetCommit.id
1468 | )
1469 | }
1470 | this.unsetProperty(commitsToCopy, 'rebased')
1471 | this.unlock()
1472 | }.bind(this), 1000)
1473 | })
1474 | }.bind(this), 1000)
1475 | }
1476 | };
1477 |
1478 | return HistoryView;
1479 | });
1480 |
--------------------------------------------------------------------------------
/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': 'https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.min'
49 | },
50 | shim: {
51 | 'd3': {
52 | exports: 'd3'
53 | }
54 | }
55 | });
56 |
--------------------------------------------------------------------------------
/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)});
--------------------------------------------------------------------------------
/memtest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Explain Git with D3
6 |
7 |
8 |
9 |
10 |
11 | Memtest Page
12 | This page exists to help me find any memory leaks that may happen.
13 |
14 | explain git memtest
15 |
16 |
17 | Create and destroy many git history views and control boxes to find memory leaks.
18 |
19 |
20 |
21 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------