Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 39550
1
###
2
Git "editor" -- basically an application that let's you interact with git.
3
4
###
5
6
{React, ReactDOM, rclass, rtypes, Redux, Actions, Store} = require('./smc-react')
7
{Button, Form, FormControl, FormGroup, Panel, Row, Col, ControlLabel, Tabs, Tab, DropdownButton, MenuItem, Modal} = require('react-bootstrap')
8
{Icon, Octicon, Space, Tip} = require('./r_misc')
9
{webapp_client} = require('./webapp_client')
10
misc = require('smc-util/misc')
11
{defaults, required} = misc
12
13
TABS = [
14
{"name": "Configuration", "icon": "settings", "description": "Configure global git settings as well as repo settings", "init_actions": ['get_git_user_name', 'get_git_user_email', 'update_github_login']},
15
{"name": "Commit", "icon": "git-commit", "description": "Commit files", "init_actions": ['get_changed_tracked_files', 'get_changed_untracked_files', 'update_diff']},
16
{"name": "Log", "icon": "history", "description": "Log of commits", "init_actions": ['update_log']},
17
{"name": "Issues", "icon": "issue-opened", "description": "Github issues for upstream", "init_actions": ['get_github_issues']},
18
]
19
20
TABS_BY_NAME = {}
21
for tab in TABS
22
TABS_BY_NAME[tab["name"].toLowerCase()] = tab
23
24
redux_name = (project_id, path) ->
25
return "editor-#{project_id}-#{path}"
26
27
class GitActions extends Actions
28
29
init: (@project_id, @filename) =>
30
@path = misc.path_split(@filename).head
31
@setState(git_repo_root : @path)
32
@setState(data_file : misc.path_split(@filename).tail)
33
@set_tab('configuration')
34
35
exec: (opts) =>
36
opts = defaults opts,
37
cmd : required
38
args : []
39
cb : required
40
webapp_client.exec
41
project_id : @project_id
42
command : opts.cmd
43
args : opts.args
44
path : @path
45
err_on_exit : true
46
cb : (err, output) =>
47
if err
48
console.warn("git editor ERROR exec'ing #{opts.cmd} #{opts.args.join(' ')}")
49
console.warn(JSON.stringify(output))
50
opts.cb(err, output)
51
opts.cb(err, output)
52
53
get_github_issues: =>
54
url = 'https://api.github.com/repos/sagemathinc/smc/issues'
55
callback = (response) => @setState(github_issues: response)
56
$.get url, callback
57
58
get_current_github_issue: =>
59
store = @redux.getStore(@name)
60
if store.get('current_branch') and store.get('remotes')
61
if store.get('current_branch').startsWith('upstream_issue_')
62
issue_number = store.get('current_branch').slice(15)
63
upstream_url = store.get('remotes').get('upstream')
64
regex = /r?\/([\w]+)\/([\w]+)\.git?/g
65
match = regex.exec(upstream_url)
66
username = match[1]
67
repo = match[2]
68
url = 'https://api.github.com/repos/'+username+'/'+repo+'/issues/'+issue_number
69
callback = (response) => @setState(current_github_issue: response)
70
$.get url, callback
71
72
save_github_login: =>
73
store = @redux.getStore(@name)
74
@exec
75
cmd : "smc-git"
76
args : ['set_github_login', store.get('data_file'), store.get('github_username'), store.get('github_access_token')]
77
cb : (err, output) =>
78
''
79
80
make_upstream_pr_for_current_branch: =>
81
store = @redux.getStore(@name)
82
@exec
83
cmd : "smc-git"
84
args : ['make_upstream_pr_for_current_branch', store.get('data_file')]
85
cb : (err, output) =>
86
''
87
88
update_github_login: =>
89
store = @redux.getStore(@name)
90
@exec
91
cmd : "smc-git"
92
args : ['get_data_file_contents', store.get('data_file')]
93
cb : (err, output) =>
94
data = JSON.parse(output.stdout)
95
@setState(github_username : data['github_username'])
96
@setState(github_access_token : data['github_access_token'])
97
98
set_git_user_name: =>
99
store = @redux.getStore(@name)
100
@exec
101
cmd : "git"
102
args : ['config', '--global', 'user.name', store.get('git_user_name')]
103
cb : (err, output) =>
104
@setState(git_user_name : store.get('git_user_name'))
105
106
get_git_user_name: =>
107
store = @redux.getStore(@name)
108
@exec
109
cmd : "git"
110
args : ['config', '--global', 'user.name']
111
cb : (err, output) =>
112
if err
113
if @redux.getStore('account').get_fullname() != ''
114
name = @redux.getStore('account').get_fullname()
115
else
116
name = 'Unknown name'
117
@setState(git_user_name : name)
118
@set_git_user_name()
119
else
120
@setState(git_user_name : output.stdout)
121
122
set_git_user_email: =>
123
store = @redux.getStore(@name)
124
@exec
125
cmd : "git"
126
args : ['config', '--global', 'user.email', store.get('git_user_email')]
127
cb : (err, output) =>
128
@setState(git_user_email : store.get('git_user_email'))
129
130
get_git_user_email: =>
131
store = @redux.getStore(@name)
132
@exec
133
cmd : "git"
134
args : ['config', '--global', 'user.email']
135
cb : (err, output) =>
136
if err
137
if @redux.getStore('account').get_email_address() != ''
138
email = @redux.getStore('account').get_fullname()
139
else
140
email = 'unknown@unknown.com'
141
@setState(git_user_email : email)
142
@set_git_user_email()
143
else
144
@setState(git_user_email : output.stdout)
145
146
simple_smc_git: (f_name) =>
147
store = @redux.getStore(@name)
148
@exec
149
cmd : "smc-git"
150
args : [f_name]
151
cb : (err, output) =>
152
''
153
154
set_remotes: =>
155
store = @redux.getStore(@name)
156
@exec
157
cmd : "smc-git"
158
args : ['remotes']
159
cb : (err, output) =>
160
@setState(remotes : JSON.parse(output.stdout))
161
162
get_current_branch: =>
163
store = @redux.getStore(@name)
164
@exec
165
cmd : "smc-git"
166
args : ['current_branch']
167
cb : (err, output) =>
168
@setState(current_branch : output.stdout)
169
t = @
170
run = (t) ->
171
t.get_current_github_issue()
172
setTimeout(run(t), 5000)
173
174
get_branches: =>
175
store = @redux.getStore(@name)
176
@exec
177
cmd : "smc-git"
178
args : ['branches']
179
cb : (err, output) =>
180
@setState(branches : JSON.parse(output.stdout))
181
182
create_branch_and_reset_to_upstream_master_with_name: (new_branch_name) =>
183
store = @redux.getStore(@name)
184
@exec
185
cmd : "smc-git"
186
args : ['create_branch_and_reset_to_upstream_master', new_branch_name]
187
cb : (err, output) =>
188
@setState(new_branch_name : '')
189
190
create_branch_and_reset_to_upstream_master: =>
191
store = @redux.getStore(@name)
192
@exec
193
cmd : "smc-git"
194
args : ['create_branch_and_reset_to_upstream_master', store.get('new_branch_name')]
195
cb : (err, output) =>
196
@setState(new_branch_name : '')
197
198
checkout_branch: (branch) =>
199
store = @redux.getStore(@name)
200
@exec
201
cmd : "git"
202
args : ['checkout', '--force', branch]
203
cb : (err, output) =>
204
''
205
206
get_changed_tracked_files: =>
207
store = @redux.getStore(@name)
208
@exec
209
cmd : "smc-git"
210
args : ['changed_tracked_files']
211
cb : (err, output) =>
212
@setState(git_changed_tracked_files : JSON.parse(output.stdout))
213
214
get_changed_untracked_files: =>
215
store = @redux.getStore(@name)
216
@exec
217
cmd : "smc-git"
218
args : ['changed_untracked_files']
219
cb : (err, output) =>
220
@setState(git_changed_untracked_files : JSON.parse(output.stdout))
221
222
223
224
git_add_selected: =>
225
store = @redux.getStore(@name)
226
@exec
227
cmd : "git"
228
args : ['add'].concat store.get('checked_files').get('untracked')
229
cb : (err, output) =>
230
''
231
232
git_add_all: =>
233
store = @redux.getStore(@name)
234
@exec
235
cmd : "git"
236
args : ['add', '.']
237
cb : (err, output) =>
238
''
239
240
run_git_commit: =>
241
store = @redux.getStore(@name)
242
commit_message = store.get('commit_message')
243
if store.get('checked_files')
244
checked_tracked_files = JSON.parse(JSON.stringify(store.get('checked_files').get('tracked')))
245
else
246
checked_tracked_files = []
247
@setState(git_commit_return : 'commiting...')
248
@exec
249
cmd : "git"
250
args : ['commit', '-m', commit_message].concat checked_tracked_files
251
cb : (err, output) =>
252
if err
253
@setState(git_commit_return : JSON.stringify(err) + ' ' + JSON.stringify(output))
254
else
255
@setState(git_commit_return : output.stdout)
256
@setState(commit_message : '')
257
258
259
260
update_diff: =>
261
store = @redux.getStore(@name)
262
if store
263
args = if store.get('file_to_diff') then ['diff', store.get('file_to_diff')] else ['diff']
264
else
265
args = ['diff']
266
@exec
267
cmd : "git"
268
args : args
269
cb : (err, output) =>
270
if err
271
@setState(git_diff : '')
272
else
273
@setState(git_diff : output.stdout)
274
275
update_log: () =>
276
@exec
277
cmd : "git"
278
args : ['log', '-20']
279
cb : (err, output) =>
280
if err
281
@setState(git_log : '')
282
else
283
@setState(git_log : output.stdout)
284
285
run_for_tab: =>
286
store = @redux.getStore(@name)
287
if store
288
tab = store.get('tab')
289
general_actions_to_run = ['set_remotes', 'get_current_branch', 'get_branches']
290
actions_to_run = general_actions_to_run.concat TABS_BY_NAME[tab]["init_actions"]
291
for action in actions_to_run
292
@[action]()
293
294
set_tab: (tab) =>
295
@setState(tab:tab)
296
t = @
297
run = (t) ->
298
t.run_for_tab()
299
setTimeout(run(t), 1000)
300
301
add_or_removed_checked_files: (name, listing_type) =>
302
store = @redux.getStore(@name)
303
if not store.get('checked_files')
304
@setState(checked_files: {"tracked": [], "untracked": []})
305
# I was unable to modify the object as is Only worked once I did -> JSON -> object
306
if store.get('checked_files').get(listing_type).indexOf(name) > -1
307
checked_files = JSON.parse(JSON.stringify(store.get('checked_files')))
308
checked_files[listing_type] = checked_files[listing_type].filter (word) -> word isnt name
309
@setState(checked_files: checked_files)
310
else
311
checked_files = JSON.parse(JSON.stringify(store.get('checked_files')))
312
checked_files[listing_type].push(name)
313
@setState(checked_files: checked_files)
314
315
FileCheckbox = rclass
316
displayName : 'ProjectGit-FileCheckbox'
317
318
propTypes :
319
name : rtypes.string
320
checked : rtypes.bool
321
actions : rtypes.object.isRequired
322
current_path : rtypes.string
323
style : rtypes.object
324
listing_type : rtypes.string
325
326
handle_click: (e) ->
327
@props.actions.add_or_removed_checked_files(@props.name, @props.listing_type)
328
329
render: ->
330
<span onClick={@handle_click} style={@props.style}>
331
<Icon name={if @props.checked then 'check-square-o' else 'square-o'} fixedWidth style={fontSize:'14pt'}/>
332
</span>
333
334
FileRow = rclass
335
displayName : 'ProjectGit-FileRow'
336
337
propTypes :
338
name : rtypes.string.isRequired
339
current_path : rtypes.string
340
actions : rtypes.object.isRequired
341
listing_type : rtypes.string
342
key : rtypes.string
343
344
render: ->
345
<Row key={@props.key} onClick={@handle_click} className={'noselect small'}>
346
<FileCheckbox
347
name = {@props.name}
348
checked = {@props.checked}
349
current_path = {@props.current_path}
350
actions = {@props.actions}
351
style = {verticalAlign:'sub'}
352
listing_type = {@props.listing_type} />
353
{@props.name}
354
</Row>
355
356
FileListing = rclass
357
displayName : 'ProjectGit-FileListing'
358
359
propTypes :
360
listing : rtypes.array.isRequired
361
listing_type : rtypes.string
362
checked_files : rtypes.object
363
current_path : rtypes.string
364
actions : rtypes.object.isRequired
365
366
getDefaultProps: ->
367
file_search : ''
368
369
render_row: (name, key) ->
370
checked = true
371
372
return <FileRow
373
name = {name}
374
key = {key}
375
listing_type = {@props.listing_type}
376
checked = {if @props.checked_files then @props.checked_files[@props.listing_type].indexOf(name) > -1 else false}
377
current_path = {@props.current_path}
378
actions = {@props.actions} />
379
380
render_rows: ->
381
(@render_row(name, idx) for name, idx in @props.listing)
382
383
render: ->
384
<Col sm=12>
385
{@render_rows()}
386
</Col>
387
388
389
390
391
exports.init_redux = init_redux = (redux, project_id, filename) ->
392
name = redux_name(project_id, filename)
393
if redux.getActions(name)?
394
return # already initialized
395
actions = redux.createActions(name, GitActions)
396
actions.init(project_id, filename)
397
redux.createStore(name)
398
399
Git = (name) -> rclass
400
reduxProps:
401
"#{name}" :
402
tab : rtypes.string
403
git_repo_root : rtypes.string
404
data_file : rtypes.string
405
git_user_name : rtypes.string
406
git_user_email : rtypes.string
407
git_user_name_return : rtypes.string
408
git_user_email_return : rtypes.string
409
git_commit_return : rtypes.string
410
commit_message : rtypes.string
411
git_status : rtypes.string
412
git_diff : rtypes.string
413
git_log : rtypes.string
414
current_branch : rtypes.string
415
branches : rtypes.array
416
git_changed_tracked_files : rtypes.array
417
git_changed_untracked_files : rtypes.array
418
checked_files : rtypes.object
419
file_to_diff : rtypes.string
420
show_create_branch_modal : rtypes.bool
421
new_branch_name : rtypes.string
422
interval : rtypes.func
423
github_issues : rtypes.array
424
current_github_issue : rtypes.object
425
remotes : rtypes.object
426
github_username : rtypes.string
427
github_access_token : rtypes.string
428
429
propTypes :
430
actions : rtypes.object
431
432
433
render_user_name_input: ->
434
<FormGroup>
435
<FormControl
436
ref = 'git_user_name'
437
type = 'text'
438
placeholder = {@props.git_user_name ? ''}
439
onChange = {=>@props.actions.setState(git_user_name:ReactDOM.findDOMNode(@refs.git_user_name).value)}
440
onKeyDown = {@handle_user_name_keypress}
441
/>
442
</FormGroup>
443
444
render_user_name_panel: ->
445
head =
446
<span>
447
git config --global user.name "{@render_user_name_input()}"
448
<Button
449
onClick = {=>@props.actions.set_git_user_name()} >
450
Run
451
</Button>
452
453
</span>
454
<Panel header={head}>
455
{<pre>{@props.git_user_name_return}</pre> if @props.git_user_name_return}
456
</Panel>
457
458
handle_user_name_keypress: (e) ->
459
if e.keyCode == 13 and @props.git_name_email != ''
460
@props.actions.set_git_user_name()
461
462
handle_user_email_keypress: (e) ->
463
if e.keyCode == 13 and @props.git_user_email != ''
464
@props.actions.set_git_user_email()
465
466
handle_commit_message_keypress: (e) ->
467
if e.keyCode == 13 and @props.commit_message != ''
468
@props.actions.run_git_commit()
469
470
render_user_email_input: ->
471
<FormGroup>
472
<FormControl
473
ref = 'git_user_email'
474
type = 'text'
475
placeholder = {@props.git_user_email ? ''}
476
onChange = {=>@props.actions.setState(git_user_email:ReactDOM.findDOMNode(@refs.git_user_email).value)}
477
onKeyDown = {@handle_user_email_keypress}
478
/>
479
</FormGroup>
480
481
render_user_email_panel: ->
482
head =
483
<span>
484
git config --global user.email "{@render_user_email_input()}"
485
<Button
486
onClick = {=>@props.actions.set_git_user_email()} >
487
Run
488
</Button>
489
490
</span>
491
<Panel header={head}>
492
{<pre>{@props.git_user_email_return}</pre> if @props.git_user_email_return}
493
</Panel>
494
495
496
render_commit_panel: ->
497
window.refs = @refs # SMELL: is this needed?
498
head =
499
<div>
500
<span>
501
Select the changed tracked files to commit
502
</span>
503
<div>
504
{<FileListing
505
listing = {@props.git_changed_tracked_files}
506
listing_type = 'tracked'
507
checked_files = {@props.checked_files}
508
actions = {@props.actions} /> if @props.git_changed_tracked_files}
509
</div>
510
<FormGroup>
511
Write your commit message
512
<FormControl
513
ref = 'commit_message'
514
type = 'text'
515
value = {@props.commit_message ? ''}
516
placeholder = {@props.commit_message ? 'Commit message'}
517
onChange = {=>@props.actions.setState(commit_message:ReactDOM.findDOMNode(@refs.commit_message).value)}
518
onKeyDown = {@handle_commit_message_keypress}
519
/>
520
</FormGroup>
521
<Button
522
onClick = {=>@props.actions.run_git_commit()} >
523
Commit the selected changed tracked files
524
</Button>
525
</div>
526
<Panel header={head}>
527
{<pre>{@props.git_commit_return}</pre> if @props.git_commit_return}
528
</Panel>
529
530
render_log_panel: ->
531
head =
532
<span>
533
git log
534
535
</span>
536
<Panel header={head}>
537
{<pre>{@props.git_log}</pre> if @props.git_log}
538
</Panel>
539
540
render_changed_untracked_files: ->
541
head =
542
<span>
543
Changed untracked files not covered by .gitignore
544
<Button onClick={=>@props.actions.git_add_selected()}>
545
Add selected
546
</Button>
547
</span>
548
<Panel header={head}>
549
{<FileListing
550
listing = {@props.git_changed_untracked_files}
551
listing_type = 'untracked'
552
checked_files = {@props.checked_files}
553
actions = {@props.actions} /> if @props.git_changed_untracked_files}
554
</Panel>
555
556
render_diff_files: ->
557
if @props.git_changed_tracked_files
558
for file, idx in @props.git_changed_tracked_files
559
<MenuItem key={idx} eventKey="{file}" onSelect={(e)=>@props.actions.setState(file_to_diff:e.target.text);@props.actions.update_diff()}>{file}</MenuItem>
560
561
render_diff: ->
562
head =
563
<span>
564
git diff
565
<Space/> <Space/>
566
<DropdownButton title='file' id='files_to_diff'>
567
{@render_diff_files()}
568
</DropdownButton>
569
<Space/> <Space/>
570
</span>
571
<Panel header={head}>
572
{<pre>{@props.git_diff}</pre> if @props.git_diff}
573
</Panel>
574
575
handle_github_login_keypress: (e) ->
576
if e.keyCode == 13
577
@props.actions.save_github_login()
578
579
render_github_login_panel: ->
580
head =
581
<span>
582
Github login credentials
583
</span>
584
<Panel header={head}>
585
<div>
586
<Row>
587
<Col sm={2}>
588
Username
589
</Col>
590
<Col sm={10}>
591
<FormGroup>
592
<FormControl
593
ref = 'github_username'
594
type = 'text'
595
value = {@props.github_username ? ''}
596
onChange = {=>@props.actions.setState(github_username:ReactDOM.findDOMNode(@refs.github_username).value)}
597
onKeyDown = {@handle_github_login_keypress}
598
/>
599
</FormGroup>
600
</Col>
601
</Row>
602
<Row>
603
<Col sm={2}>
604
Personal access token
605
</Col>
606
<Col sm={10}>
607
<FormGroup>
608
<FormControl
609
ref = 'github_access_token'
610
type = 'password'
611
value = {@props.github_access_token ? ''}
612
onChange = {=>@props.actions.setState(github_access_token:ReactDOM.findDOMNode(@refs.github_access_token).value)}
613
onKeyDown = {@handle_github_login_keypress}
614
/>
615
</FormGroup>
616
</Col>
617
</Row>
618
<Row>
619
<Col sm={2}>
620
</Col>
621
<Col sm={10}>
622
<Button onClick={=>@props.actions.save_github_login()}>
623
Save
624
</Button>
625
</Col>
626
</Row>
627
</div>
628
</Panel>
629
630
render_configuration: ->
631
<div>
632
<Row>
633
<Col sm=6>
634
{@render_user_name_panel()}
635
</Col>
636
<Col sm=6>
637
{@render_user_email_panel()}
638
</Col>
639
</Row>
640
<Row>
641
<Col sm=6>
642
{@render_github_login_panel()}
643
</Col>
644
</Row>
645
</div>
646
647
render_commit: ->
648
<div>
649
<Row>
650
<Col sm=6>
651
{@render_commit_panel()}
652
{@render_changed_untracked_files()}
653
</Col>
654
<Col sm=6>
655
{@render_diff()}
656
</Col>
657
</Row>
658
</div>
659
660
render_log: ->
661
<div>
662
<Row>
663
<Col sm=12>
664
{@render_log_panel()}
665
</Col>
666
</Row>
667
</div>
668
669
pass_issue: (number) ->
670
@props.actions.create_branch_and_reset_to_upstream_master_with_name('upstream_issue_'+number)
671
672
list_issues: ->
673
if @props.github_issues
674
for issue, idx in @props.github_issues
675
t = @
676
do (issue, t) ->
677
<Row key={idx}>
678
<Col sm=8>
679
{ issue.title }
680
</Col>
681
<Col>
682
<Button onClick={=>t.pass_issue(issue.number)}>
683
Create branch for this ticket: upstream_issue_{ issue.number }
684
</Button>
685
</Col>
686
</Row>
687
688
render_issues: ->
689
<div>
690
<Row>
691
<Col sm=12>
692
{@list_issues()}
693
</Col>
694
</Row>
695
</div>
696
697
698
render_tab_header: (name, icon, description)->
699
<Tip delayShow=1300
700
title={name} tip={description}>
701
<span>
702
<Octicon name={icon}/> {name }
703
</span>
704
</Tip>
705
706
render_tab: (idx, name, icon, description) ->
707
<Tab key={idx}
708
eventKey={name.toLowerCase()}
709
title={@render_tab_header(name, icon, description)}>
710
<div style={marginTop:'8px'}></div>
711
{@['render_'+name.toLowerCase()]()}
712
</Tab>
713
714
render_tabs: ->
715
for tab, idx in TABS
716
@render_tab(idx, tab.name, tab.icon, tab.description)
717
718
render_branches: ->
719
if @props.branches
720
for branch, idx in @props.branches
721
<MenuItem key={idx} eventKey="{branch}" onSelect={(e)=>@props.actions.checkout_branch(e.target.text);}>{branch}</MenuItem>
722
723
handle_keypress: (e, input_name, action) ->
724
if e.keyCode == 13 and @props[input_name] != ''
725
@props.actions[action]()
726
727
componentDidMount: ->
728
@props.actions.set_tab('configuration')
729
@props.interval = setInterval =>
730
@props.actions.run_for_tab()
731
, 30000
732
733
componentWillUnmount: ->
734
clearInterval(@props.interval)
735
736
render_current_issue: ->
737
if @props.current_github_issue
738
head =
739
<span className="small">
740
<strong>Working on issue #{@props.current_github_issue.number}:</strong> {@props.current_github_issue.title}
741
</span>
742
<Panel className="small" header={head}>
743
<p>{@props.current_github_issue.body}</p>
744
<a target="_blank" href={@props.current_github_issue.html_url}>Open on Github</a>
745
</Panel>
746
747
render: ->
748
<div>
749
<div>
750
<h2 style={display:'inline'}>Git Repository at {@props.git_repo_root}</h2>
751
<b><br/>(WARNING: The git editor is highly experimental and not finished!)</b>
752
<Space/> <Space/>
753
<DropdownButton title={'Switch branch from '[email protected]_branch} id='switch_branches'>
754
<MenuItem eventKey="{file}" onSelect={(e)=>@props.actions.setState(show_create_branch_modal:true)}>Create a branch and reset to upstream master</MenuItem>
755
{@render_branches()}
756
</DropdownButton>
757
<Space/> <Space/>
758
<Button onClick={=>@props.actions.simple_smc_git('push_to_origin_same_branch')}>
759
Push to origin {@props.current_branch}
760
</Button>
761
<Space/> <Space/>
762
<Button onClick={=>@props.actions.simple_smc_git('pull_upstream_master')}>
763
Pull upstream master
764
</Button>
765
<Space/> <Space/>
766
<Button onClick={=>@props.actions.make_upstream_pr_for_current_branch()}>
767
Send pull request
768
</Button>
769
<div className="custom-modal">
770
<Modal show={@props.show_create_branch_modal} onHide={=>@props.actions.setState(show_create_branch_modal:false)}>
771
<Modal.Header>
772
<Modal.Title>Create a banch</Modal.Title>
773
</Modal.Header>
774
<Modal.Body>
775
<FormGroup>
776
<FormControl
777
ref = 'new_branch_name'
778
type = 'text'
779
value = {@props.new_branch_name ? ''}
780
placeholder = {'Branch name'}
781
onChange = {=>@props.actions.setState(new_branch_name:ReactDOM.findDOMNode(@refs.new_branch_name).value)}
782
onKeyDown = {=>@handle_keypress('new_branch_name', 'create_branch_and_reset_to_upstream_master')}
783
/>
784
</FormGroup>
785
</Modal.Body>
786
<Modal.Footer>
787
<Button onClick={=>@props.actions.setState(show_create_branch_modal:false)}>Close</Button>
788
<Button bsStyle="primary" onClick={=>@props.actions.create_branch_and_reset_to_upstream_master();@props.actions.setState(show_create_branch_modal:false)}>Create the branch</Button>
789
</Modal.Footer>
790
</Modal>
791
</div>
792
</div>
793
<div>
794
{@render_current_issue()}
795
</div>
796
<Tabs animation={false} activeKey={@props.tab} onSelect={(key)=>@props.actions.set_tab(key)} id="git-tabs">
797
{@render_tabs()}
798
</Tabs>
799
800
</div>
801
802
render = (redux, project_id, path) ->
803
name = redux_name(project_id, path)
804
actions = redux.getActions(name)
805
Git_connected = Git(name)
806
<Git_connected actions={actions} />
807
808
exports.free = (project_id, path, dom_node, redux) ->
809
ReactDOM.unmountComponentAtNode(dom_node)
810
811
exports.render = (project_id, path, dom_node, redux) ->
812
init_redux(redux, project_id, path)
813
ReactDOM.render(render(redux, project_id, path), dom_node)
814
815
exports.hide = (project_id, path, dom_node, redux) ->
816
ReactDOM.unmountComponentAtNode(dom_node)
817
818
exports.show = (project_id, path, dom_node, redux) ->
819
ReactDOM.render(render(redux, project_id, path), dom_node)
820