1 from __future__
import division
, absolute_import
, unicode_literals
5 from PyQt4
import QtGui
6 from PyQt4
.QtCore
import Qt
7 from PyQt4
.QtCore
import SIGNAL
9 from cola
import gitcmds
10 from cola
import icons
11 from cola
import qtutils
12 from cola
import utils
13 from cola
.i18n
import N_
14 from cola
.interaction
import Interaction
15 from cola
.models
import main
16 from cola
.qtutils
import connect_button
17 from cola
.widgets
import defs
18 from cola
.widgets
import standard
19 from cola
.widgets
.standard
import ProgressDialog
39 def run(RemoteDialog
):
40 """Launches fetch/push/pull dialogs."""
41 # Copy global stuff over to speedup startup
42 model
= main
.MainModel()
43 global_model
= main
.model()
44 model
.currentbranch
= global_model
.currentbranch
45 model
.local_branches
= global_model
.local_branches
46 model
.remote_branches
= global_model
.remote_branches
47 model
.tags
= global_model
.tags
48 model
.remotes
= global_model
.remotes
49 parent
= qtutils
.active_window()
50 view
= RemoteDialog(model
, parent
=parent
)
55 def combine(result
, existing
):
59 if type(existing
) is tuple:
60 if len(existing
) == 3:
61 return (max(existing
[0], result
[0]),
62 combine(existing
[1], result
[1]),
63 combine(existing
[2], result
[2]))
65 raise AssertionError('combine() with length %d' % len(existing
))
67 if existing
and result
:
68 return existing
+ '\n\n' + result
75 class ActionTask(qtutils
.Task
):
77 def __init__(self
, parent
, model_action
, remote
, kwargs
):
78 qtutils
.Task
.__init
__(self
, parent
)
79 self
.model_action
= model_action
84 """Runs the model action and captures the result"""
85 return self
.model_action(self
.remote
, **self
.kwargs
)
88 class RemoteActionDialog(standard
.Dialog
):
90 def __init__(self
, model
, action
, title
, parent
=None, icon
=None):
91 """Customizes the dialog based on the remote action
93 standard
.Dialog
.__init
__(self
, parent
=parent
)
96 self
.filtered_remote_branches
= []
97 self
.selected_remotes
= []
99 self
.setAttribute(Qt
.WA_MacMetalStyle
)
100 self
.setWindowTitle(title
)
101 if parent
is not None:
102 self
.setWindowModality(Qt
.WindowModal
)
104 self
.runtask
= qtutils
.RunTask(parent
=self
)
105 self
.progress
= ProgressDialog(title
, N_('Updating'), self
)
107 self
.local_label
= QtGui
.QLabel()
108 self
.local_label
.setText(N_('Local Branch'))
110 self
.local_branch
= QtGui
.QLineEdit()
111 self
.local_branches
= QtGui
.QListWidget()
112 self
.local_branches
.addItems(self
.model
.local_branches
)
114 self
.remote_label
= QtGui
.QLabel()
115 self
.remote_label
.setText(N_('Remote'))
117 self
.remote_name
= QtGui
.QLineEdit()
118 self
.remotes
= QtGui
.QListWidget()
120 self
.remotes
.setSelectionMode(QtGui
.QAbstractItemView
.ExtendedSelection
)
121 self
.remotes
.addItems(self
.model
.remotes
)
123 self
.remote_branch_label
= QtGui
.QLabel()
124 self
.remote_branch_label
.setText(N_('Remote Branch'))
126 self
.remote_branch
= QtGui
.QLineEdit()
127 self
.remote_branches
= QtGui
.QListWidget()
128 self
.remote_branches
.addItems(self
.model
.remote_branches
)
130 text
= N_('Fast Forward Only ')
131 self
.ffwd_only_checkbox
= qtutils
.checkbox(text
=text
, checked
=True)
132 self
.tags_checkbox
= qtutils
.checkbox(text
=N_('Include tags '))
133 self
.rebase_checkbox
= qtutils
.checkbox(text
=N_('Rebase '))
137 self
.action_button
= qtutils
.create_button(text
=title
, icon
=icon
)
138 self
.close_button
= qtutils
.close_button()
140 self
.buttons
= utils
.Group(self
.action_button
, self
.close_button
)
142 self
.local_branch_layout
= qtutils
.hbox(defs
.small_margin
, defs
.spacing
,
146 self
.remote_branch_layout
= qtutils
.hbox(defs
.small_margin
, defs
.spacing
,
150 self
.remote_branches_layout
= qtutils
.hbox(defs
.small_margin
, defs
.spacing
,
151 self
.remote_branch_label
,
154 self
.options_layout
= qtutils
.hbox(defs
.no_margin
, defs
.button_spacing
,
156 self
.ffwd_only_checkbox
,
158 self
.rebase_checkbox
,
163 self
.remote_branch_layout
, self
.remotes
,
164 self
.local_branch_layout
, self
.local_branches
,
165 self
.remote_branches_layout
, self
.remote_branches
,
168 else: # fetch and pull
170 self
.remote_branch_layout
, self
.remotes
,
171 self
.remote_branches_layout
, self
.remote_branches
,
172 self
.local_branch_layout
, self
.local_branches
,
175 self
.main_layout
= qtutils
.vbox(defs
.no_margin
, defs
.spacing
, *widgets
)
176 self
.setLayout(self
.main_layout
)
178 default_remote
= gitcmds
.default_remote() or 'origin'
180 remotes
= self
.model
.remotes
181 if default_remote
in remotes
:
182 idx
= remotes
.index(default_remote
)
183 if self
.select_remote(idx
):
184 self
.remote_name
.setText(default_remote
)
186 if self
.select_first_remote():
187 self
.remote_name
.setText(remotes
[0])
189 # Trim the remote list to just the default remote
190 self
.update_remotes()
191 self
.set_field_defaults()
193 # Setup signals and slots
194 self
.connect(self
.remotes
, SIGNAL('itemSelectionChanged()'),
197 self
.connect(self
.local_branches
, SIGNAL('itemSelectionChanged()'),
198 self
.update_local_branches
)
200 self
.connect(self
.remote_branches
, SIGNAL('itemSelectionChanged()'),
201 self
.update_remote_branches
)
203 connect_button(self
.action_button
, self
.action_callback
)
204 connect_button(self
.close_button
, self
.close
)
206 qtutils
.add_action(self
, N_('Close'), self
.close
,
207 QtGui
.QKeySequence
.Close
, 'Esc')
210 self
.tags_checkbox
.hide()
211 self
.ffwd_only_checkbox
.hide()
212 self
.local_label
.hide()
213 self
.local_branch
.hide()
214 self
.local_branches
.hide()
215 self
.remote_branch
.setFocus()
217 self
.rebase_checkbox
.hide()
219 if not self
.restore_state():
220 self
.resize(666, 420)
222 self
.remote_name
.setFocus()
224 def set_rebase(self
, value
):
225 self
.rebase_checkbox
.setChecked(value
)
227 def set_field_defaults(self
):
228 # Default to "git fetch origin master"
230 if action
== FETCH
or action
== PULL
:
231 self
.local_branch
.setText('')
232 self
.remote_branch
.setText('')
235 # Select the current branch by default for push
237 branch
= self
.model
.currentbranch
239 idx
= self
.model
.local_branches
.index(branch
)
242 if self
.select_local_branch(idx
):
243 self
.set_local_branch(branch
)
244 self
.set_remote_branch('')
246 def set_remote_name(self
, remote_name
):
247 self
.remote_name
.setText(remote_name
)
249 self
.remote_name
.selectAll()
251 def set_local_branch(self
, branch
):
252 self
.local_branch
.setText(branch
)
254 self
.local_branch
.selectAll()
256 def set_remote_branch(self
, branch
):
257 self
.remote_branch
.setText(branch
)
259 self
.remote_branch
.selectAll()
261 def set_remote_branches(self
, branches
):
262 self
.remote_branches
.clear()
263 self
.remote_branches
.addItems(branches
)
264 self
.filtered_remote_branches
= branches
266 def select_first_remote(self
):
267 """Selects the first remote in the list view"""
268 return self
.select_remote(0)
270 def select_remote(self
, idx
):
271 """Selects a remote by index"""
272 item
= self
.remotes
.item(idx
)
274 self
.remotes
.setItemSelected(item
, True)
275 self
.remotes
.setCurrentItem(item
)
276 self
.set_remote_name(item
.text())
281 def select_local_branch(self
, idx
):
282 """Selects a local branch by index in the list view"""
283 item
= self
.local_branches
.item(idx
)
286 self
.local_branches
.setItemSelected(item
, True)
287 self
.local_branches
.setCurrentItem(item
)
288 self
.local_branch
.setText(item
.text())
291 def display_remotes(self
, widget
):
292 """Display the available remotes in a listwidget"""
294 for remote_name
in self
.model
.remotes
:
295 url
= self
.model
.remote_url(remote_name
, self
.action
)
296 display
= ('%s\t(%s)'
297 % (remote_name
, N_('URL: %s') % url
))
298 displayed
.append(display
)
299 qtutils
.set_items(widget
,displayed
)
301 def update_remotes(self
, *rest
):
302 """Update the remote name when a remote from the list is selected"""
303 widget
= self
.remotes
304 remotes
= self
.model
.remotes
305 selection
= qtutils
.selected_item(widget
, remotes
)
307 self
.selected_remotes
= []
309 self
.set_remote_name(selection
)
310 self
.selected_remotes
= qtutils
.selected_items(self
.remotes
,
313 all_branches
= gitcmds
.branch_list(remote
=True)
316 for remote
in self
.selected_remotes
:
320 for branch
in all_branches
:
322 if fnmatch
.fnmatch(branch
, pat
):
323 branches
.append(branch
)
326 self
.set_remote_branches(branches
)
328 self
.set_remote_branches(all_branches
)
329 self
.set_remote_branch('')
331 def update_local_branches(self
,*rest
):
332 """Update the local/remote branch names when a branch is selected"""
333 branches
= self
.model
.local_branches
334 widget
= self
.local_branches
335 selection
= qtutils
.selected_item(widget
, branches
)
338 self
.set_local_branch(selection
)
339 self
.set_remote_branch(selection
)
341 def update_remote_branches(self
,*rest
):
342 """Update the remote branch name when a branch is selected"""
343 widget
= self
.remote_branches
344 branches
= self
.filtered_remote_branches
345 selection
= qtutils
.selected_item(widget
, branches
)
348 branch
= utils
.strip_one(selection
)
351 self
.set_remote_branch(branch
)
353 def common_args(self
):
354 """Returns git arguments common to fetch/push/pulll"""
355 remote_name
= self
.remote_name
.text()
356 local_branch
= self
.local_branch
.text()
357 remote_branch
= self
.remote_branch
.text()
359 ffwd_only
= self
.ffwd_only_checkbox
.isChecked()
360 rebase
= self
.rebase_checkbox
.isChecked()
361 tags
= self
.tags_checkbox
.isChecked()
365 'local_branch': local_branch
,
366 'remote_branch': remote_branch
,
374 def push_to_all(self
, dummy_remote
, *args
, **kwargs
):
375 selected_remotes
= self
.selected_remotes
377 for remote
in selected_remotes
:
378 result
= self
.model
.push(remote
, *args
, **kwargs
)
379 all_results
= combine(result
, all_results
)
382 def action_callback(self
):
385 model_action
= self
.model
.fetch
387 model_action
= self
.push_to_all
388 else: # if action == PULL:
389 model_action
= self
.model
.pull
391 remote_name
= self
.remote_name
.text()
393 errmsg
= N_('No repository selected.')
394 Interaction
.log(errmsg
)
396 remote
, kwargs
= self
.common_args()
397 self
.selected_remotes
= qtutils
.selected_items(self
.remotes
,
400 # Check if we're about to create a new branch and warn.
401 remote_branch
= self
.remote_branch
.text()
402 local_branch
= self
.local_branch
.text()
404 if action
== PUSH
and not remote_branch
:
405 branch
= local_branch
406 candidate
= '%s/%s' % (remote
, branch
)
407 if candidate
not in self
.model
.remote_branches
:
409 args
= dict(branch
=branch
, remote
=remote
)
410 msg
= N_('Branch "%(branch)s" does not exist in "%(remote)s".\n'
411 'A new remote branch will be published.') % args
412 info_txt
= N_('Create a new remote branch?')
413 ok_text
= N_('Create Remote Branch')
414 if not qtutils
.confirm(title
, msg
, info_txt
, ok_text
,
418 if not self
.ffwd_only_checkbox
.isChecked():
420 title
= N_('Force Fetch?')
421 msg
= N_('Non-fast-forward fetch overwrites local history!')
422 info_txt
= N_('Force fetching from %s?') % remote
423 ok_text
= N_('Force Fetch')
425 title
= N_('Force Push?')
426 msg
= N_('Non-fast-forward push overwrites published '
427 'history!\n(Did you pull first?)')
428 info_txt
= N_('Force push to %s?') % remote
429 ok_text
= N_('Force Push')
430 else: # pull: shouldn't happen since the controls are hidden
431 msg
= "You probably don't want to do this.\n\tContinue?"
434 if not qtutils
.confirm(title
, msg
, info_txt
, ok_text
,
435 default
=False, icon
=icons
.discard()):
438 # Disable the GUI by default
439 self
.buttons
.setEnabled(False)
441 # Use a thread to update in the background
442 task
= ActionTask(self
, model_action
, remote
, kwargs
)
443 self
.runtask
.start(task
,
444 progress
=self
.progress
,
445 finish
=self
.action_completed
)
447 def action_completed(self
, task
):
448 # Grab the results of the action and finish up
449 status
, out
, err
= task
.result
450 self
.buttons
.setEnabled(True)
452 already_up_to_date
= N_('Already up-to-date.')
454 if not out
: # git fetch --tags --verbose doesn't print anything...
455 out
= already_up_to_date
457 command
= 'git %s' % self
.action
.lower()
458 message
= (N_('"%(command)s" returned exit status %(status)d') %
459 dict(command
=command
, status
=status
))
464 details
+= '\n\n' + err
466 log_message
= message
468 log_message
+= '\n\n' + details
469 Interaction
.log(log_message
)
475 if self
.action
== PUSH
:
477 message
+= N_('Have you rebased/pulled lately?')
479 Interaction
.critical(self
.windowTitle(),
480 message
=message
, details
=details
)
483 # Use distinct classes so that each saves its own set of preferences
484 class Fetch(RemoteActionDialog
):
486 def __init__(self
, model
, parent
=None):
487 RemoteActionDialog
.__init
__(self
, model
, FETCH
, N_('Fetch'),
488 parent
=parent
, icon
=icons
.repo())
491 class Push(RemoteActionDialog
):
493 def __init__(self
, model
, parent
=None):
494 RemoteActionDialog
.__init
__(self
, model
, PUSH
, N_('Push'),
495 parent
=parent
, icon
=icons
.push())
498 class Pull(RemoteActionDialog
):
500 def __init__(self
, model
, parent
=None):
501 RemoteActionDialog
.__init
__(self
, model
, PULL
, N_('Pull'),
502 parent
=parent
, icon
=icons
.pull())
504 def apply_state(self
, state
):
505 result
= RemoteActionDialog
.apply_state(self
, state
)
507 rebase
= state
['rebase']
511 self
.rebase_checkbox
.setChecked(rebase
)
514 def export_state(self
):
515 state
= RemoteActionDialog
.export_state(self
)
516 state
['rebase'] = self
.rebase_checkbox
.isChecked()
519 def done(self
, exit_code
):
521 return RemoteActionDialog
.done(self
, exit_code
)