4 from PyQt4
import QtCore
5 from PyQt4
import QtGui
6 from PyQt4
.QtCore
import Qt
7 from PyQt4
.QtCore
import SIGNAL
10 from cola
import gitcmds
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
.qtutils
import connect_button
16 from cola
.widgets
import defs
17 from cola
.widgets
import standard
18 from cola
.main
.model
import MainModel
37 def run(RemoteDialog
):
38 """Launches fetch/push/pull dialogs."""
39 # Copy global stuff over to speedup startup
41 global_model
= cola
.model()
42 model
.currentbranch
= global_model
.currentbranch
43 model
.local_branches
= global_model
.local_branches
44 model
.remote_branches
= global_model
.remote_branches
45 model
.tags
= global_model
.tags
46 model
.remotes
= global_model
.remotes
47 parent
= qtutils
.active_window()
48 view
= RemoteDialog(model
, parent
)
53 def combine(result
, existing
):
57 if type(existing
) is tuple:
58 if len(existing
) == 3:
59 return (max(existing
[0], result
[0]),
60 combine(existing
[1], result
[1]),
61 combine(existing
[2], result
[2]))
63 raise AssertionError('combine() with length %d' % len(existing
))
65 if existing
and result
:
66 return existing
+ '\n\n' + result
73 class ActionTask(QtCore
.QRunnable
):
75 def __init__(self
, sender
, model_action
, remote
, kwargs
):
76 QtCore
.QRunnable
.__init
__(self
)
78 self
.model_action
= model_action
83 """Runs the model action and captures the result"""
84 status
, out
, err
= self
.model_action(self
.remote
, **self
.kwargs
)
85 self
.sender
.emit(SIGNAL('action_completed'), self
, status
, out
, err
)
88 class ProgressAnimationThread(QtCore
.QThread
):
90 def __init__(self
, txt
, parent
, timeout
=0.25):
91 QtCore
.QThread
.__init
__(self
, parent
)
94 self
.timeout
= timeout
106 self
.idx
= (self
.idx
+ 1) % len(self
.symbols
)
107 return self
.txt
+ self
.symbols
[self
.idx
]
115 self
.emit(SIGNAL('str'), self
.next())
116 time
.sleep(self
.timeout
)
119 class RemoteActionDialog(standard
.Dialog
):
121 def __init__(self
, model
, action
, parent
):
122 """Customizes the dialog based on the remote action
124 standard
.Dialog
.__init
__(self
, parent
=parent
)
128 self
.filtered_remote_branches
= []
129 self
.selected_remotes
= []
131 self
.setAttribute(Qt
.WA_MacMetalStyle
)
132 self
.setWindowModality(Qt
.WindowModal
)
133 self
.setWindowTitle(N_(action
))
135 self
.progress
= QtGui
.QProgressDialog(self
)
136 self
.progress
.setFont(qtutils
.diff_font())
137 self
.progress
.setRange(0, 0)
138 self
.progress
.setCancelButton(None)
139 self
.progress
.setWindowTitle(action
)
140 self
.progress
.setWindowModality(Qt
.WindowModal
)
141 self
.progress
.setLabelText(N_('Updating') + '.. ')
142 self
.progress_thread
= ProgressAnimationThread(N_('Updating'), self
)
144 self
.local_label
= QtGui
.QLabel()
145 self
.local_label
.setText(N_('Local Branch'))
147 self
.local_branch
= QtGui
.QLineEdit()
148 self
.local_branches
= QtGui
.QListWidget()
149 self
.local_branches
.addItems(self
.model
.local_branches
)
151 self
.remote_label
= QtGui
.QLabel()
152 self
.remote_label
.setText(N_('Remote'))
154 self
.remote_name
= QtGui
.QLineEdit()
155 self
.remotes
= QtGui
.QListWidget()
157 self
.remotes
.setSelectionMode(QtGui
.QAbstractItemView
.ExtendedSelection
)
158 self
.remotes
.addItems(self
.model
.remotes
)
160 self
.remote_branch_label
= QtGui
.QLabel()
161 self
.remote_branch_label
.setText(N_('Remote Branch'))
163 self
.remote_branch
= QtGui
.QLineEdit()
164 self
.remote_branches
= QtGui
.QListWidget()
165 self
.remote_branches
.addItems(self
.model
.remote_branches
)
167 self
.ffwd_only_checkbox
= QtGui
.QCheckBox()
168 self
.ffwd_only_checkbox
.setText(N_('Fast Forward Only '))
169 self
.ffwd_only_checkbox
.setChecked(True)
171 self
.tags_checkbox
= QtGui
.QCheckBox()
172 self
.tags_checkbox
.setText(N_('Include tags '))
174 self
.rebase_checkbox
= QtGui
.QCheckBox()
175 self
.rebase_checkbox
.setText(N_('Rebase '))
177 self
.action_button
= QtGui
.QPushButton()
178 self
.action_button
.setText(N_(action
))
179 self
.action_button
.setIcon(qtutils
.ok_icon())
181 self
.close_button
= QtGui
.QPushButton()
182 self
.close_button
.setText(N_('Close'))
183 self
.close_button
.setIcon(qtutils
.close_icon())
185 self
.local_branch_layout
= QtGui
.QHBoxLayout()
186 self
.local_branch_layout
.addWidget(self
.local_label
)
187 self
.local_branch_layout
.addWidget(self
.local_branch
)
189 self
.remote_branch_layout
= QtGui
.QHBoxLayout()
190 self
.remote_branch_layout
.addWidget(self
.remote_label
)
191 self
.remote_branch_layout
.addWidget(self
.remote_name
)
193 self
.remote_branches_layout
= QtGui
.QHBoxLayout()
194 self
.remote_branches_layout
.addWidget(self
.remote_branch_label
)
195 self
.remote_branches_layout
.addWidget(self
.remote_branch
)
197 self
.options_layout
= QtGui
.QHBoxLayout()
198 self
.options_layout
.setSpacing(defs
.button_spacing
)
199 self
.options_layout
.addStretch()
200 self
.options_layout
.addWidget(self
.ffwd_only_checkbox
)
201 self
.options_layout
.addWidget(self
.tags_checkbox
)
202 self
.options_layout
.addWidget(self
.rebase_checkbox
)
203 self
.options_layout
.addWidget(self
.action_button
)
204 self
.options_layout
.addWidget(self
.close_button
)
206 self
.main_layout
= QtGui
.QVBoxLayout()
207 self
.main_layout
.setMargin(defs
.margin
)
208 self
.main_layout
.setSpacing(defs
.spacing
)
209 self
.main_layout
.addLayout(self
.remote_branch_layout
)
210 self
.main_layout
.addWidget(self
.remotes
)
212 self
.main_layout
.addLayout(self
.local_branch_layout
)
213 self
.main_layout
.addWidget(self
.local_branches
)
214 self
.main_layout
.addLayout(self
.remote_branches_layout
)
215 self
.main_layout
.addWidget(self
.remote_branches
)
216 else: # fetch and pull
217 self
.main_layout
.addLayout(self
.remote_branches_layout
)
218 self
.main_layout
.addWidget(self
.remote_branches
)
219 self
.main_layout
.addLayout(self
.local_branch_layout
)
220 self
.main_layout
.addWidget(self
.local_branches
)
221 self
.main_layout
.addLayout(self
.options_layout
)
222 self
.setLayout(self
.main_layout
)
224 remotes
= self
.model
.remotes
225 if 'origin' in remotes
:
226 idx
= remotes
.index('origin')
227 if self
.select_remote(idx
):
228 self
.remote_name
.setText('origin')
230 if self
.select_first_remote():
231 self
.remote_name
.setText(remotes
[0])
233 # Trim the remote list to just the default remote
234 self
.update_remotes()
235 self
.set_field_defaults()
237 # Setup signals and slots
238 self
.connect(self
.remotes
, SIGNAL('itemSelectionChanged()'),
241 self
.connect(self
.local_branches
, SIGNAL('itemSelectionChanged()'),
242 self
.update_local_branches
)
244 self
.connect(self
.remote_branches
, SIGNAL('itemSelectionChanged()'),
245 self
.update_remote_branches
)
247 connect_button(self
.action_button
, self
.action_callback
)
248 connect_button(self
.close_button
, self
.close
)
250 qtutils
.add_action(self
, N_('Close'),
251 self
.close
, QtGui
.QKeySequence
.Close
, 'Esc')
253 self
.connect(self
, SIGNAL('action_completed'), self
.action_completed
)
254 self
.connect(self
.progress_thread
, SIGNAL('str'), self
.update_progress
)
257 self
.tags_checkbox
.hide()
258 self
.ffwd_only_checkbox
.hide()
259 self
.local_label
.hide()
260 self
.local_branch
.hide()
261 self
.local_branches
.hide()
262 self
.remote_branch
.setFocus()
264 self
.rebase_checkbox
.hide()
266 if not qtutils
.apply_state(self
):
267 self
.resize(666, 420)
269 self
.remote_name
.setFocus()
271 def set_rebase(self
, value
):
272 self
.rebase_checkbox
.setChecked(value
)
274 def set_field_defaults(self
):
275 # Default to "git fetch origin master"
277 if action
== FETCH
or action
== PULL
:
278 self
.local_branch
.setText('')
279 self
.remote_branch
.setText('')
282 # Select the current branch by default for push
284 branch
= self
.model
.currentbranch
286 idx
= self
.model
.local_branches
.index(branch
)
289 if self
.select_local_branch(idx
):
290 self
.set_local_branch(branch
)
291 self
.set_remote_branch('')
293 def set_remote_name(self
, remote_name
):
294 self
.remote_name
.setText(remote_name
)
296 self
.remote_name
.selectAll()
298 def set_local_branch(self
, branch
):
299 self
.local_branch
.setText(branch
)
301 self
.local_branch
.selectAll()
303 def set_remote_branch(self
, branch
):
304 self
.remote_branch
.setText(branch
)
306 self
.remote_branch
.selectAll()
308 def set_remote_branches(self
, branches
):
309 self
.remote_branches
.clear()
310 self
.remote_branches
.addItems(branches
)
311 self
.filtered_remote_branches
= branches
313 def select_first_remote(self
):
314 """Selects the first remote in the list view"""
315 return self
.select_remote(0)
317 def select_remote(self
, idx
):
318 """Selects a remote by index"""
319 item
= self
.remotes
.item(idx
)
321 self
.remotes
.setItemSelected(item
, True)
322 self
.remotes
.setCurrentItem(item
)
323 self
.set_remote_name(unicode(item
.text()))
328 def select_local_branch(self
, idx
):
329 """Selects a local branch by index in the list view"""
330 item
= self
.local_branches
.item(idx
)
333 self
.local_branches
.setItemSelected(item
, True)
334 self
.local_branches
.setCurrentItem(item
)
335 self
.local_branch
.setText(item
.text())
338 def display_remotes(self
, widget
):
339 """Display the available remotes in a listwidget"""
341 for remote_name
in self
.model
.remotes
:
342 url
= self
.model
.remote_url(remote_name
, self
.action
)
343 display
= ('%s\t(%s)'
344 % (remote_name
, N_('URL: %s') % url
))
345 displayed
.append(display
)
346 qtutils
.set_items(widget
,displayed
)
348 def update_remotes(self
, *rest
):
349 """Update the remote name when a remote from the list is selected"""
350 widget
= self
.remotes
351 remotes
= self
.model
.remotes
352 selection
= qtutils
.selected_item(widget
, remotes
)
354 self
.selected_remotes
= []
356 self
.set_remote_name(selection
)
357 self
.selected_remotes
= qtutils
.selected_items(self
.remotes
,
360 all_branches
= gitcmds
.branch_list(remote
=True)
363 for remote
in self
.selected_remotes
:
367 for branch
in all_branches
:
369 if fnmatch
.fnmatch(branch
, pat
):
370 branches
.append(branch
)
373 self
.set_remote_branches(branches
)
375 self
.set_remote_branches(all_branches
)
376 self
.set_remote_branch('')
378 def update_local_branches(self
,*rest
):
379 """Update the local/remote branch names when a branch is selected"""
380 branches
= self
.model
.local_branches
381 widget
= self
.local_branches
382 selection
= qtutils
.selected_item(widget
, branches
)
385 self
.set_local_branch(selection
)
386 self
.set_remote_branch(selection
)
388 def update_remote_branches(self
,*rest
):
389 """Update the remote branch name when a branch is selected"""
390 widget
= self
.remote_branches
391 branches
= self
.filtered_remote_branches
392 selection
= qtutils
.selected_item(widget
, branches
)
395 branch
= utils
.strip_one(selection
)
398 self
.set_remote_branch(branch
)
400 def common_args(self
):
401 """Returns git arguments common to fetch/push/pulll"""
402 remote_name
= unicode(self
.remote_name
.text())
403 local_branch
= unicode(self
.local_branch
.text())
404 remote_branch
= unicode(self
.remote_branch
.text())
406 ffwd_only
= self
.ffwd_only_checkbox
.isChecked()
407 rebase
= self
.rebase_checkbox
.isChecked()
408 tags
= self
.tags_checkbox
.isChecked()
412 'local_branch': local_branch
,
413 'remote_branch': remote_branch
,
421 def action_callback(self
):
424 model_action
= self
.model
.fetch
426 model_action
= self
.push_to_all
427 else: # if action == PULL:
428 model_action
= self
.model
.pull
430 remote_name
= unicode(self
.remote_name
.text())
432 errmsg
= N_('No repository selected.')
433 Interaction
.log(errmsg
)
435 remote
, kwargs
= self
.common_args()
436 self
.selected_remotes
= qtutils
.selected_items(self
.remotes
,
439 # Check if we're about to create a new branch and warn.
440 remote_branch
= unicode(self
.remote_branch
.text())
441 local_branch
= unicode(self
.local_branch
.text())
443 if action
== PUSH
and not remote_branch
:
444 branch
= local_branch
445 candidate
= '%s/%s' % (remote
, branch
)
446 if candidate
not in self
.model
.remote_branches
:
448 args
= dict(branch
=branch
, remote
=remote
)
449 msg
= N_('Branch "%(branch)s" does not exist in "%(remote)s".\n'
450 'A new remote branch will be published.') % args
451 info_txt
= N_('Create a new remote branch?')
452 ok_text
= N_('Create Remote Branch')
453 if not qtutils
.confirm(title
, msg
, info_txt
, ok_text
,
455 icon
=qtutils
.git_icon()):
458 if not self
.ffwd_only_checkbox
.isChecked():
460 title
= N_('Force Fetch?')
461 msg
= N_('Non-fast-forward fetch overwrites local history!')
462 info_txt
= N_('Force fetching from %s?') % remote
463 ok_text
= N_('Force Fetch')
465 title
= N_('Force Push?')
466 msg
= N_('Non-fast-forward push overwrites published '
467 'history!\n(Did you pull first?)')
468 info_txt
= N_('Force push to %s?') % remote
469 ok_text
= N_('Force Push')
470 else: # pull: shouldn't happen since the controls are hidden
471 msg
= "You probably don't want to do this.\n\tContinue?"
474 if not qtutils
.confirm(title
, msg
, info_txt
, ok_text
,
476 icon
=qtutils
.discard_icon()):
479 # Disable the GUI by default
480 self
.action_button
.setEnabled(False)
481 self
.close_button
.setEnabled(False)
482 QtGui
.QApplication
.setOverrideCursor(Qt
.WaitCursor
)
484 # Show a nice progress bar
486 self
.progress_thread
.start()
488 # Use a thread to update in the background
489 task
= ActionTask(self
, model_action
, remote
, kwargs
)
490 self
.tasks
.append(task
)
491 QtCore
.QThreadPool
.globalInstance().start(task
)
493 def update_progress(self
, txt
):
494 self
.progress
.setLabelText(txt
)
496 def push_to_all(self
, dummy_remote
, *args
, **kwargs
):
497 selected_remotes
= self
.selected_remotes
499 for remote
in selected_remotes
:
500 result
= self
.model
.push(remote
, *args
, **kwargs
)
501 all_results
= combine(result
, all_results
)
504 def action_completed(self
, task
, status
, out
, err
):
505 # Grab the results of the action and finish up
506 self
.action_button
.setEnabled(True)
507 self
.close_button
.setEnabled(True)
508 QtGui
.QApplication
.restoreOverrideCursor()
510 self
.progress_thread
.stop()
511 self
.progress_thread
.wait()
512 self
.progress
.close()
513 if task
in self
.tasks
:
514 self
.tasks
.remove(task
)
516 already_up_to_date
= N_('Already up-to-date.')
518 if not out
: # git fetch --tags --verbose doesn't print anything...
519 out
= already_up_to_date
521 command
= 'git %s' % self
.action
.lower()
522 message
= (N_('"%(command)s" returned exit status %(status)d') %
523 dict(command
=command
, status
=status
))
525 message
+= '\n\n' + out
527 message
+= '\n\n' + err
529 Interaction
.log(message
)
535 if self
.action
== PUSH
:
537 message
+= N_('Have you rebased/pulled lately?')
539 Interaction
.critical(self
.windowTitle(),
540 message
=message
, details
=output
)
543 # Use distinct classes so that each saves its own set of preferences
544 class Fetch(RemoteActionDialog
):
545 def __init__(self
, model
, parent
):
546 RemoteActionDialog
.__init
__(self
, model
, FETCH
, parent
)
549 class Push(RemoteActionDialog
):
550 def __init__(self
, model
, parent
):
551 RemoteActionDialog
.__init
__(self
, model
, PUSH
, parent
)
554 class Pull(RemoteActionDialog
):
555 def __init__(self
, model
, parent
):
556 RemoteActionDialog
.__init
__(self
, model
, PULL
, parent
)
558 def apply_state(self
, state
):
559 RemoteActionDialog
.apply_state(self
, state
)
561 rebase
= state
['rebase']
565 self
.rebase_checkbox
.setChecked(rebase
)
567 def export_state(self
):
568 state
= RemoteActionDialog
.export_state(self
)
569 state
['rebase'] = self
.rebase_checkbox
.isChecked()
572 def done(self
, exit_code
):
573 qtutils
.save_state(self
)
574 return RemoteActionDialog
.done(self
, exit_code
)