3 from PyQt4
import QtCore
4 from PyQt4
import QtGui
5 from PyQt4
.QtCore
import Qt
6 from PyQt4
.QtCore
import SIGNAL
9 from cola
import gitcmds
10 from cola
import qtutils
11 from cola
import utils
12 from cola
.qtutils
import connect_button
13 from cola
.widgets
import defs
14 from cola
.widgets
import standard
15 from cola
.main
.model
import MainModel
34 def run(RemoteDialog
):
35 """Launches fetch/push/pull dialogs."""
36 # Copy global stuff over to speedup startup
38 global_model
= cola
.model()
39 model
.currentbranch
= global_model
.currentbranch
40 model
.local_branches
= global_model
.local_branches
41 model
.remote_branches
= global_model
.remote_branches
42 model
.tags
= global_model
.tags
43 model
.remotes
= global_model
.remotes
44 parent
= qtutils
.active_window()
45 view
= RemoteDialog(model
, parent
)
50 class ActionTask(QtCore
.QRunnable
):
51 def __init__(self
, sender
, model_action
, remote
, kwargs
):
52 QtCore
.QRunnable
.__init
__(self
)
54 self
.model_action
= model_action
59 """Runs the model action and captures the result"""
60 status
, output
= self
.model_action(self
.remote
, **self
.kwargs
)
61 self
.sender
.emit(SIGNAL('action_completed'), self
, status
, output
)
64 class RemoteActionDialog(standard
.Dialog
):
65 def __init__(self
, model
, action
, parent
):
66 """Customizes the dialog based on the remote action
68 super(RemoteActionDialog
, self
).__init
__(parent
=parent
)
73 self
.setAttribute(Qt
.WA_MacMetalStyle
)
74 self
.setWindowModality(Qt
.WindowModal
)
75 self
.setWindowTitle(self
.tr(action
))
77 self
.progress
= QtGui
.QProgressDialog(self
)
78 self
.progress
.setRange(0, 0)
79 self
.progress
.setCancelButton(None)
80 self
.progress
.setWindowTitle(self
.tr(action
))
81 self
.progress
.setWindowModality(Qt
.WindowModal
)
83 self
.local_label
= QtGui
.QLabel()
84 self
.local_label
.setText(self
.tr('Local Branch'))
86 self
.local_branch
= QtGui
.QLineEdit()
87 self
.local_branches
= QtGui
.QListWidget()
88 self
.local_branches
.addItems(self
.model
.local_branches
)
90 self
.remote_label
= QtGui
.QLabel()
91 self
.remote_label
.setText(self
.tr('Remote'))
93 self
.remote_name
= QtGui
.QLineEdit()
94 self
.remotes
= QtGui
.QListWidget()
95 self
.remotes
.addItems(self
.model
.remotes
)
97 self
.remote_branch_label
= QtGui
.QLabel()
98 self
.remote_branch_label
.setText(self
.tr('Remote Branch'))
100 self
.remote_branch
= QtGui
.QLineEdit()
101 self
.remote_branches
= QtGui
.QListWidget()
102 self
.remote_branches
.addItems(self
.model
.remote_branches
)
104 self
.ffwd_only_checkbox
= QtGui
.QCheckBox()
105 self
.ffwd_only_checkbox
.setText(self
.tr('Fast Forward Only '))
106 self
.ffwd_only_checkbox
.setChecked(True)
108 self
.tags_checkbox
= QtGui
.QCheckBox()
109 self
.tags_checkbox
.setText(self
.tr('Include tags '))
111 self
.rebase_checkbox
= QtGui
.QCheckBox()
112 self
.rebase_checkbox
.setText(self
.tr('Rebase '))
114 self
.action_button
= QtGui
.QPushButton()
115 self
.action_button
.setText(self
.tr(action
))
116 self
.action_button
.setIcon(qtutils
.ok_icon())
118 self
.close_button
= QtGui
.QPushButton()
119 self
.close_button
.setText(self
.tr('Close'))
120 self
.close_button
.setIcon(qtutils
.close_icon())
122 self
.local_branch_layout
= QtGui
.QHBoxLayout()
123 self
.local_branch_layout
.addWidget(self
.local_label
)
124 self
.local_branch_layout
.addWidget(self
.local_branch
)
126 self
.remote_branch_layout
= QtGui
.QHBoxLayout()
127 self
.remote_branch_layout
.addWidget(self
.remote_label
)
128 self
.remote_branch_layout
.addWidget(self
.remote_name
)
130 self
.remote_branches_layout
= QtGui
.QHBoxLayout()
131 self
.remote_branches_layout
.addWidget(self
.remote_branch_label
)
132 self
.remote_branches_layout
.addWidget(self
.remote_branch
)
134 self
.options_layout
= QtGui
.QHBoxLayout()
135 self
.options_layout
.setSpacing(defs
.button_spacing
)
136 self
.options_layout
.addStretch()
137 self
.options_layout
.addWidget(self
.ffwd_only_checkbox
)
138 self
.options_layout
.addWidget(self
.tags_checkbox
)
139 self
.options_layout
.addWidget(self
.rebase_checkbox
)
140 self
.options_layout
.addWidget(self
.action_button
)
141 self
.options_layout
.addWidget(self
.close_button
)
143 self
.main_layout
= QtGui
.QVBoxLayout()
144 self
.main_layout
.setMargin(defs
.margin
)
145 self
.main_layout
.setSpacing(defs
.spacing
)
146 self
.main_layout
.addLayout(self
.remote_branch_layout
)
147 self
.main_layout
.addWidget(self
.remotes
)
149 self
.main_layout
.addLayout(self
.local_branch_layout
)
150 self
.main_layout
.addWidget(self
.local_branches
)
151 self
.main_layout
.addLayout(self
.remote_branches_layout
)
152 self
.main_layout
.addWidget(self
.remote_branches
)
153 else: # fetch and pull
154 self
.main_layout
.addLayout(self
.remote_branches_layout
)
155 self
.main_layout
.addWidget(self
.remote_branches
)
156 self
.main_layout
.addLayout(self
.local_branch_layout
)
157 self
.main_layout
.addWidget(self
.local_branches
)
158 self
.main_layout
.addLayout(self
.options_layout
)
159 self
.setLayout(self
.main_layout
)
161 remotes
= self
.model
.remotes
162 if 'origin' in remotes
:
163 idx
= remotes
.index('origin')
164 if self
.select_remote(idx
):
165 self
.remote_name
.setText('origin')
167 if self
.select_first_remote():
168 self
.remote_name
.setText(remotes
[0])
170 # Trim the remote list to just the default remote
171 self
.update_remotes()
172 self
.set_field_defaults()
174 # Setup signals and slots
175 self
.connect(self
.remotes
, SIGNAL('itemSelectionChanged()'),
178 self
.connect(self
.local_branches
, SIGNAL('itemSelectionChanged()'),
179 self
.update_local_branches
)
181 self
.connect(self
.remote_branches
, SIGNAL('itemSelectionChanged()'),
182 self
.update_remote_branches
)
184 connect_button(self
.action_button
, self
.action_callback
)
185 connect_button(self
.close_button
, self
.reject
)
187 self
.connect(self
, SIGNAL('action_completed'), self
.action_completed
)
190 self
.tags_checkbox
.hide()
191 self
.ffwd_only_checkbox
.hide()
192 self
.local_label
.hide()
193 self
.local_branch
.hide()
194 self
.local_branches
.hide()
195 self
.remote_branch
.setFocus()
197 self
.rebase_checkbox
.hide()
199 if not qtutils
.apply_state(self
):
200 self
.resize(666, 420)
202 self
.remote_name
.setFocus()
205 def set_field_defaults(self
):
206 # Default to "git fetch origin master"
208 if action
== FETCH
or action
== PULL
:
209 self
.local_branch
.setText('')
210 self
.remote_branch
.setText('')
213 # Select the current branch by default for push
215 branch
= self
.model
.currentbranch
217 idx
= self
.model
.local_branches
.index(branch
)
220 if self
.select_local_branch(idx
):
221 self
.set_local_branch(branch
)
222 self
.set_remote_branch('')
224 def set_remote_name(self
, remote_name
):
225 self
.remote_name
.setText(remote_name
)
227 self
.remote_name
.selectAll()
229 def set_local_branch(self
, branch
):
230 self
.local_branch
.setText(branch
)
232 self
.local_branch
.selectAll()
234 def set_remote_branch(self
, branch
):
235 self
.remote_branch
.setText(branch
)
237 self
.remote_branch
.selectAll()
239 def set_remote_branches(self
, branches
):
240 self
.remote_branches
.clear()
241 self
.remote_branches
.addItems(branches
)
243 def select_first_remote(self
):
244 """Selects the first remote in the list view"""
245 return self
.select_remote(0)
247 def select_remote(self
, idx
):
248 """Selects a remote by index"""
249 item
= self
.remotes
.item(idx
)
251 self
.remotes
.setItemSelected(item
, True)
252 self
.remotes
.setCurrentItem(item
)
253 self
.set_remote_name(unicode(item
.text()))
258 def select_local_branch(self
, idx
):
259 """Selects a local branch by index in the list view"""
260 item
= self
.local_branches
.item(idx
)
263 self
.local_branches
.setItemSelected(item
, True)
264 self
.local_branches
.setCurrentItem(item
)
265 self
.local_branch
.setText(item
.text())
268 def display_remotes(self
, widget
):
269 """Display the available remotes in a listwidget"""
271 for remote_name
in self
.model
.remotes
:
272 url
= self
.model
.remote_url(remote_name
, self
.action
)
273 display
= ('%s\t(%s %s)'
274 % (remote_name
, unicode(self
.tr('URL:')), url
))
275 displayed
.append(display
)
276 qtutils
.set_items(widget
,displayed
)
278 def update_remotes(self
, *rest
):
279 """Update the remote name when a remote from the list is selected"""
280 widget
= self
.remotes
281 remotes
= self
.model
.remotes
282 selection
= qtutils
.selected_item(widget
, remotes
)
285 self
.set_remote_name(selection
)
287 all_branches
= gitcmds
.branch_list(remote
=True)
289 pat
= selection
+ '/*'
290 for branch
in all_branches
:
291 if fnmatch
.fnmatch(branch
, pat
):
292 branches
.append(branch
)
294 self
.set_remote_branches(branches
)
296 self
.set_remote_branches(all_branches
)
297 self
.set_remote_branch('')
299 def update_local_branches(self
,*rest
):
300 """Update the local/remote branch names when a branch is selected"""
301 branches
= self
.model
.local_branches
302 widget
= self
.local_branches
303 selection
= qtutils
.selected_item(widget
, branches
)
306 self
.set_local_branch(selection
)
307 self
.set_remote_branch(selection
)
309 def update_remote_branches(self
,*rest
):
310 """Update the remote branch name when a branch is selected"""
311 widget
= self
.remote_branches
312 branches
= self
.model
.remote_branches
313 selection
= qtutils
.selected_item(widget
,branches
)
316 branch
= utils
.basename(selection
)
319 self
.set_remote_branch(branch
)
321 def common_args(self
):
322 """Returns git arguments common to fetch/push/pulll"""
323 remote_name
= unicode(self
.remote_name
.text())
324 local_branch
= unicode(self
.local_branch
.text())
325 remote_branch
= unicode(self
.remote_branch
.text())
327 ffwd_only
= self
.ffwd_only_checkbox
.isChecked()
328 rebase
= self
.rebase_checkbox
.isChecked()
329 tags
= self
.tags_checkbox
.isChecked()
333 'local_branch': local_branch
,
334 'remote_branch': remote_branch
,
340 #+-------------------------------------------------------------
342 def action_callback(self
):
345 model_action
= self
.model
.fetch
347 model_action
= self
.model
.push
348 else: # if action == PULL:
349 model_action
= self
.model
.pull
351 remote_name
= unicode(self
.remote_name
.text())
353 errmsg
= self
.tr('No repository selected.')
354 qtutils
.log(1, errmsg
)
356 remote
, kwargs
= self
.common_args()
358 # Check if we're about to create a new branch and warn.
359 remote_branch
= unicode(self
.remote_branch
.text())
360 local_branch
= unicode(self
.local_branch
.text())
362 if action
== PUSH
and not remote_branch
:
363 branch
= local_branch
364 candidate
= '%s/%s' % (remote
, branch
)
365 if candidate
not in self
.model
.remote_branches
:
366 title
= self
.tr(PUSH
)
367 msg
= 'Branch "%s" does not exist in %s.' % (branch
, remote
)
368 msg
+= '\nA new remote branch will be published.'
369 info_txt
= 'Create a new remote branch?'
370 ok_text
= 'Create Remote Branch'
371 if not qtutils
.confirm(title
, msg
, info_txt
, ok_text
,
373 icon
=qtutils
.git_icon()):
376 if not self
.ffwd_only_checkbox
.isChecked():
377 title
= 'Force %s?' % action
.title()
378 ok_text
= 'Force %s' % action
.title()
381 msg
= 'Non-fast-forward fetch overwrites local history!'
382 info_txt
= 'Force fetching from %s?' % remote
384 msg
= ('Non-fast-forward push overwrites published '
385 'history!\n(Did you pull first?)')
386 info_txt
= 'Force push to %s?' % remote
387 else: # pull: shouldn't happen since the controls are hidden
388 msg
= "You probably don't want to do this.\n\tContinue?"
391 if not qtutils
.confirm(title
, msg
, info_txt
, ok_text
,
393 icon
=qtutils
.discard_icon()):
396 # Disable the GUI by default
397 self
.setEnabled(False)
398 self
.progress
.setEnabled(True)
399 QtGui
.QApplication
.setOverrideCursor(Qt
.WaitCursor
)
401 # Show a nice progress bar
402 self
.progress
.setLabelText('Updating...')
405 # Use a thread to update in the background
406 task
= ActionTask(self
, model_action
, remote
, kwargs
)
407 self
.tasks
.append(task
)
408 QtCore
.QThreadPool
.globalInstance().start(task
)
410 def action_completed(self
, task
, status
, output
):
411 # Grab the results of the action and finish up
412 if task
in self
.tasks
:
413 self
.tasks
.remove(task
)
415 if not output
: # git fetch --tags --verbose doesn't print anything...
416 output
= self
.tr('Already up-to-date.')
417 # Force the status to 1 so that we always display the log
418 qtutils
.log(1, output
)
420 self
.progress
.close()
421 QtGui
.QApplication
.restoreOverrideCursor()
423 if status
!= 0 and self
.action
== PUSH
:
424 remote_name
= unicode(self
.remote_name
.text())
425 message
= 'Error pushing to "%s".\n\nPull first?' % remote_name
426 qtutils
.critical('Push Error',
427 message
=message
, details
=output
)
429 title
= self
.windowTitle()
433 result
= 'returned exit status %d' % status
435 message
= '"git %s" %s' % (self
.action
.lower(), result
)
436 qtutils
.information(title
,
437 message
=message
, details
=output
)
441 # Use distinct classes so that each saves its own set of preferences
442 class Fetch(RemoteActionDialog
):
443 def __init__(self
, model
, parent
):
444 super(Fetch
, self
).__init
__(model
, FETCH
, parent
)
447 class Push(RemoteActionDialog
):
448 def __init__(self
, model
, parent
):
449 super(Push
, self
).__init
__(model
, PUSH
, parent
)
452 class Pull(RemoteActionDialog
):
453 def __init__(self
, model
, parent
):
454 super(Pull
, self
).__init
__(model
, PULL
, parent
)
456 def apply_state(self
, state
):
457 super(Pull
, self
).apply_state(state
)
459 rebase
= state
['rebase']
463 self
.rebase_checkbox
.setChecked(rebase
)
465 def export_state(self
):
466 state
= super(Pull
, self
).export_state()
467 state
['rebase'] = self
.rebase_checkbox
.isChecked()
470 def done(self
, exit_code
):
471 qtutils
.save_state(self
)
472 return super(Pull
, self
).done(exit_code
)