3 from PyQt4
.QtCore
import Qt
4 from PyQt4
import QtCore
5 from PyQt4
import QtGui
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
.views
import standard
14 from cola
.widgets
import defs
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 standard
.Dialog
.__init
__(self
, parent
=parent
)
73 self
.setWindowTitle(self
.tr(action
))
74 self
.setWindowModality(QtCore
.Qt
.WindowModal
)
76 self
.progress
= QtGui
.QProgressDialog(self
)
77 self
.progress
.setRange(0, 0)
78 self
.progress
.setCancelButton(None)
79 self
.progress
.setWindowTitle(self
.tr(action
))
80 self
.progress
.setWindowModality(Qt
.WindowModal
)
82 self
.local_label
= QtGui
.QLabel()
83 self
.local_label
.setText(self
.tr('Local Branches'))
85 self
.local_branch
= QtGui
.QLineEdit()
86 self
.local_branches
= QtGui
.QListWidget()
87 self
.local_branches
.addItems(self
.model
.local_branches
)
89 self
.remote_label
= QtGui
.QLabel()
90 self
.remote_label
.setText(self
.tr('Remote'))
92 self
.remote_name
= QtGui
.QLineEdit()
93 self
.remotes
= QtGui
.QListWidget()
94 self
.remotes
.addItems(self
.model
.remotes
)
96 self
.remote_label
= QtGui
.QLabel()
97 self
.remote_label
.setText(self
.tr('Remote Branch'))
99 self
.remote_branch
= QtGui
.QLineEdit()
100 self
.remote_branches
= QtGui
.QListWidget()
101 self
.remote_branches
.addItems(self
.model
.remote_branches
)
103 self
.ffwd_only_checkbox
= QtGui
.QCheckBox()
104 self
.ffwd_only_checkbox
.setText(self
.tr('Fast Forward Only '))
105 self
.ffwd_only_checkbox
.setChecked(True)
107 self
.tags_checkbox
= QtGui
.QCheckBox()
108 self
.tags_checkbox
.setText(self
.tr('Include tags '))
110 self
.rebase_checkbox
= QtGui
.QCheckBox()
111 self
.rebase_checkbox
.setText(self
.tr('Rebase '))
113 self
.action_button
= QtGui
.QPushButton()
114 self
.action_button
.setText(self
.tr(action
))
115 self
.action_button
.setIcon(qtutils
.ok_icon())
117 self
.close_button
= QtGui
.QPushButton()
118 self
.close_button
.setText(self
.tr('Close'))
119 self
.close_button
.setIcon(qtutils
.close_icon())
121 self
.local_branch_layout
= QtGui
.QHBoxLayout()
122 self
.local_branch_layout
.addWidget(self
.local_label
)
123 self
.local_branch_layout
.addWidget(self
.local_branch
)
125 self
.remote_branch_layout
= QtGui
.QHBoxLayout()
126 self
.remote_branch_layout
.addWidget(self
.remote_label
)
127 self
.remote_branch_layout
.addWidget(self
.remote_name
)
129 self
.remote_branches_layout
= QtGui
.QHBoxLayout()
130 self
.remote_branches_layout
.addWidget(self
.remote_label
)
131 self
.remote_branches_layout
.addWidget(self
.remote_branch
)
133 self
.options_layout
= QtGui
.QHBoxLayout()
134 self
.options_layout
.setSpacing(defs
.button_spacing
)
135 self
.options_layout
.addStretch()
136 self
.options_layout
.addWidget(self
.ffwd_only_checkbox
)
137 self
.options_layout
.addWidget(self
.tags_checkbox
)
138 self
.options_layout
.addWidget(self
.rebase_checkbox
)
139 self
.options_layout
.addWidget(self
.action_button
)
140 self
.options_layout
.addWidget(self
.close_button
)
142 self
.main_layout
= QtGui
.QVBoxLayout()
143 self
.main_layout
.setMargin(defs
.margin
)
144 self
.main_layout
.setSpacing(defs
.spacing
)
145 self
.main_layout
.addLayout(self
.remote_branch_layout
)
146 self
.main_layout
.addWidget(self
.remotes
)
148 self
.main_layout
.addLayout(self
.local_branch_layout
)
149 self
.main_layout
.addWidget(self
.local_branches
)
150 self
.main_layout
.addLayout(self
.remote_branches_layout
)
151 self
.main_layout
.addWidget(self
.remote_branches
)
152 else: # fetch and pull
153 self
.main_layout
.addLayout(self
.remote_branches_layout
)
154 self
.main_layout
.addWidget(self
.remote_branches
)
155 self
.main_layout
.addLayout(self
.local_branch_layout
)
156 self
.main_layout
.addWidget(self
.local_branches
)
157 self
.main_layout
.addLayout(self
.options_layout
)
158 self
.setLayout(self
.main_layout
)
160 remotes
= self
.model
.remotes
161 if 'origin' in remotes
:
162 idx
= remotes
.index('origin')
163 if self
.select_remote(idx
):
164 self
.remote_name
.setText('origin')
166 if self
.select_first_remote():
167 self
.remote_name
.setText(remotes
[0])
169 # Trim the remote list to just the default remote
170 self
.update_remotes()
171 self
.set_field_defaults()
173 # Setup signals and slots
174 self
.connect(self
.remotes
, SIGNAL('itemSelectionChanged()'),
177 self
.connect(self
.local_branches
, SIGNAL('itemSelectionChanged()'),
178 self
.update_local_branches
)
180 self
.connect(self
.remote_branches
, SIGNAL('itemSelectionChanged()'),
181 self
.update_remote_branches
)
183 connect_button(self
.action_button
, self
.action_callback
)
184 connect_button(self
.close_button
, self
.reject
)
186 self
.connect(self
, SIGNAL('action_completed'), self
.action_completed
)
189 self
.tags_checkbox
.hide()
190 self
.ffwd_only_checkbox
.hide()
191 self
.local_label
.hide()
192 self
.local_branch
.hide()
193 self
.local_branches
.hide()
194 self
.remote_branch
.setFocus()
196 self
.rebase_checkbox
.hide()
197 self
.remote_name
.setFocus()
199 self
.resize(666, 420)
201 def set_field_defaults(self
):
202 # Default to "git fetch origin master"
204 if action
== FETCH
or action
== PULL
:
205 self
.local_branch
.setText('')
206 self
.remote_branch
.setText('')
209 # Select the current branch by default for push
211 branch
= self
.model
.currentbranch
213 idx
= self
.model
.local_branches
.index(branch
)
216 if self
.select_local_branch(idx
):
217 self
.set_local_branch(branch
)
218 self
.set_remote_branch('')
220 def set_remote_name(self
, remote_name
):
221 self
.remote_name
.setText(remote_name
)
223 self
.remote_name
.selectAll()
225 def set_local_branch(self
, branch
):
226 self
.local_branch
.setText(branch
)
228 self
.local_branch
.selectAll()
230 def set_remote_branch(self
, branch
):
231 self
.remote_branch
.setText(branch
)
233 self
.remote_branch
.selectAll()
235 def set_remote_branches(self
, branches
):
236 self
.remote_branches
.clear()
237 self
.remote_branches
.addItems(branches
)
239 def select_first_remote(self
):
240 """Selects the first remote in the list view"""
241 return self
.select_remote(0)
243 def select_remote(self
, idx
):
244 """Selects a remote by index"""
245 item
= self
.remotes
.item(idx
)
247 self
.remotes
.setItemSelected(item
, True)
248 self
.remotes
.setCurrentItem(item
)
249 self
.set_remote_name(unicode(item
.text()))
254 def select_local_branch(self
, idx
):
255 """Selects a local branch by index in the list view"""
256 item
= self
.local_branches
.item(idx
)
259 self
.local_branches
.setItemSelected(item
, True)
260 self
.local_branches
.setCurrentItem(item
)
261 self
.local_branch
.setText(item
.text())
264 def display_remotes(self
, widget
):
265 """Display the available remotes in a listwidget"""
267 for remote_name
in self
.model
.remotes
:
268 url
= self
.model
.remote_url(remote_name
, self
.action
)
269 display
= ('%s\t(%s %s)'
270 % (remote_name
, unicode(self
.tr('URL:')), url
))
271 displayed
.append(display
)
272 qtutils
.set_items(widget
,displayed
)
274 def update_remotes(self
, *rest
):
275 """Update the remote name when a remote from the list is selected"""
276 widget
= self
.remotes
277 remotes
= self
.model
.remotes
278 selection
= qtutils
.selected_item(widget
, remotes
)
281 self
.set_remote_name(selection
)
283 all_branches
= gitcmds
.branch_list(remote
=True)
285 pat
= selection
+ '/*'
286 for branch
in all_branches
:
287 if fnmatch
.fnmatch(branch
, pat
):
288 branches
.append(branch
)
290 self
.set_remote_branches(branches
)
292 self
.set_remote_branches(all_branches
)
293 self
.set_remote_branch('')
295 def update_local_branches(self
,*rest
):
296 """Update the local/remote branch names when a branch is selected"""
297 branches
= self
.model
.local_branches
298 widget
= self
.local_branches
299 selection
= qtutils
.selected_item(widget
, branches
)
302 self
.set_local_branch(selection
)
303 self
.set_remote_branch(selection
)
305 def update_remote_branches(self
,*rest
):
306 """Update the remote branch name when a branch is selected"""
307 widget
= self
.remote_branches
308 branches
= self
.model
.remote_branches
309 selection
= qtutils
.selected_item(widget
,branches
)
312 branch
= utils
.basename(selection
)
315 self
.set_remote_branch(branch
)
317 def common_args(self
):
318 """Returns git arguments common to fetch/push/pulll"""
319 remote_name
= unicode(self
.remote_name
.text())
320 local_branch
= unicode(self
.local_branch
.text())
321 remote_branch
= unicode(self
.remote_branch
.text())
323 ffwd_only
= self
.ffwd_only_checkbox
.isChecked()
324 rebase
= self
.rebase_checkbox
.isChecked()
325 tags
= self
.tags_checkbox
.isChecked()
329 'local_branch': local_branch
,
330 'remote_branch': remote_branch
,
336 #+-------------------------------------------------------------
338 def action_callback(self
):
341 model_action
= self
.model
.fetch
343 model_action
= self
.model
.push
344 else: # if action == PULL:
345 model_action
= self
.model
.pull
347 remote_name
= unicode(self
.remote_name
.text())
349 errmsg
= self
.tr('No repository selected.')
350 qtutils
.log(1, errmsg
)
352 remote
, kwargs
= self
.common_args()
355 # Check if we're about to create a new branch and warn.
356 remote_branch
= unicode(self
.remote_branch
.text())
357 local_branch
= unicode(self
.local_branch
.text())
359 if action
== PUSH
and not remote_branch
:
360 branch
= local_branch
361 candidate
= '%s/%s' % (remote
, branch
)
362 if candidate
not in self
.model
.remote_branches
:
363 title
= self
.tr(PUSH
)
364 msg
= 'Branch "%s" does not exist in %s.' % (branch
, remote
)
365 msg
+= '\nA new remote branch will be published.'
366 info_txt
= 'Create a new remote branch?'
367 ok_text
= 'Create Remote Branch'
368 if not qtutils
.confirm(title
, msg
, info_txt
, ok_text
,
370 icon
=qtutils
.git_icon()):
373 if not self
.ffwd_only_checkbox
.isChecked():
374 title
= 'Force %s?' % action
.title()
375 ok_text
= 'Force %s' % action
.title()
378 msg
= 'Non-fast-forward fetch overwrites local history!'
379 info_txt
= 'Force fetching from %s?' % remote
381 msg
= ('Non-fast-forward push overwrites published '
382 'history!\n(Did you pull first?)')
383 info_txt
= 'Force push to %s?' % remote
384 else: # pull: shouldn't happen since the controls are hidden
385 msg
= "You probably don't want to do this.\n\tContinue?"
388 if not qtutils
.confirm(title
, msg
, info_txt
, ok_text
,
390 icon
=qtutils
.discard_icon()):
393 # Disable the GUI by default
394 self
.setEnabled(False)
395 self
.progress
.setEnabled(True)
396 QtGui
.QApplication
.setOverrideCursor(Qt
.WaitCursor
)
398 # Show a nice progress bar
399 self
.progress
.setLabelText('Updating...')
402 # Use a thread to update in the background
403 task
= ActionTask(self
, model_action
, remote
, kwargs
)
404 self
.tasks
.append(task
)
405 QtCore
.QThreadPool
.globalInstance().start(task
)
407 def action_completed(self
, task
, status
, output
):
408 # Grab the results of the action and finish up
409 if task
in self
.tasks
:
410 self
.tasks
.remove(task
)
412 if not output
: # git fetch --tags --verbose doesn't print anything...
413 output
= self
.tr('Already up-to-date.')
414 # Force the status to 1 so that we always display the log
415 qtutils
.log(1, output
)
417 self
.progress
.close()
418 QtGui
.QApplication
.restoreOverrideCursor()
420 if status
!= 0 and self
.action
== PUSH
:
421 remote_name
= unicode(self
.remote_name
.text())
422 message
= 'Error pushing to "%s".\n\nPull first?' % remote_name
423 qtutils
.critical('Push Error',
424 message
=message
, details
=output
)
426 title
= self
.windowTitle()
430 result
= 'returned exit status %d' % status
432 message
= '"git %s" %s' % (self
.action
.lower(), result
)
433 qtutils
.information(title
,
434 message
=message
, details
=output
)
438 # Use distinct classes so that each saves its own set of preferences
439 class Fetch(RemoteActionDialog
):
440 def __init__(self
, model
, parent
):
441 super(Fetch
, self
).__init
__(model
, FETCH
, parent
)
444 class Push(RemoteActionDialog
):
445 def __init__(self
, model
, parent
):
446 super(Push
, self
).__init
__(model
, PUSH
, parent
)
449 class Pull(RemoteActionDialog
):
450 def __init__(self
, model
, parent
):
451 super(Pull
, self
).__init
__(model
, PULL
, parent
)