widgets: Use 'Qt' instead of 'QtCore.Qt' consistently
[git-cola.git] / cola / widgets / remote.py
bloba938f1c301d9cfbce5619dbd80e31b8024386f1a
1 import fnmatch
3 from PyQt4 import QtCore
4 from PyQt4 import QtGui
5 from PyQt4.QtCore import Qt
6 from PyQt4.QtCore import SIGNAL
8 import cola
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
17 FETCH = 'Fetch'
18 PUSH = 'Push'
19 PULL = 'Pull'
22 def fetch():
23 return run(Fetch)
26 def push():
27 return run(Push)
30 def pull():
31 return run(Pull)
34 def run(RemoteDialog):
35 """Launches fetch/push/pull dialogs."""
36 # Copy global stuff over to speedup startup
37 model = MainModel()
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)
46 view.show()
47 return view
50 class ActionTask(QtCore.QRunnable):
51 def __init__(self, sender, model_action, remote, kwargs):
52 QtCore.QRunnable.__init__(self)
53 self.sender = sender
54 self.model_action = model_action
55 self.remote = remote
56 self.kwargs = kwargs
58 def run(self):
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
67 """
68 super(RemoteActionDialog, self).__init__(parent=parent)
69 self.model = model
70 self.action = action
71 self.tasks = []
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)
148 if action == PUSH:
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')
166 else:
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()'),
176 self.update_remotes)
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)
189 if action == PULL:
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()
196 else:
197 self.rebase_checkbox.hide()
198 self.remote_name.setFocus()
200 self.resize(666, 420)
202 def set_field_defaults(self):
203 # Default to "git fetch origin master"
204 action = self.action
205 if action == FETCH or action == PULL:
206 self.local_branch.setText('')
207 self.remote_branch.setText('')
208 return
210 # Select the current branch by default for push
211 if action == PUSH:
212 branch = self.model.currentbranch
213 try:
214 idx = self.model.local_branches.index(branch)
215 except ValueError:
216 return
217 if self.select_local_branch(idx):
218 self.set_local_branch(branch)
219 self.set_remote_branch('')
221 def set_remote_name(self, remote_name):
222 self.remote_name.setText(remote_name)
223 if remote_name:
224 self.remote_name.selectAll()
226 def set_local_branch(self, branch):
227 self.local_branch.setText(branch)
228 if branch:
229 self.local_branch.selectAll()
231 def set_remote_branch(self, branch):
232 self.remote_branch.setText(branch)
233 if branch:
234 self.remote_branch.selectAll()
236 def set_remote_branches(self, branches):
237 self.remote_branches.clear()
238 self.remote_branches.addItems(branches)
240 def select_first_remote(self):
241 """Selects the first remote in the list view"""
242 return self.select_remote(0)
244 def select_remote(self, idx):
245 """Selects a remote by index"""
246 item = self.remotes.item(idx)
247 if item:
248 self.remotes.setItemSelected(item, True)
249 self.remotes.setCurrentItem(item)
250 self.set_remote_name(unicode(item.text()))
251 return True
252 else:
253 return False
255 def select_local_branch(self, idx):
256 """Selects a local branch by index in the list view"""
257 item = self.local_branches.item(idx)
258 if not item:
259 return False
260 self.local_branches.setItemSelected(item, True)
261 self.local_branches.setCurrentItem(item)
262 self.local_branch.setText(item.text())
263 return True
265 def display_remotes(self, widget):
266 """Display the available remotes in a listwidget"""
267 displayed = []
268 for remote_name in self.model.remotes:
269 url = self.model.remote_url(remote_name, self.action)
270 display = ('%s\t(%s %s)'
271 % (remote_name, unicode(self.tr('URL:')), url))
272 displayed.append(display)
273 qtutils.set_items(widget,displayed)
275 def update_remotes(self, *rest):
276 """Update the remote name when a remote from the list is selected"""
277 widget = self.remotes
278 remotes = self.model.remotes
279 selection = qtutils.selected_item(widget, remotes)
280 if not selection:
281 return
282 self.set_remote_name(selection)
284 all_branches = gitcmds.branch_list(remote=True)
285 branches = []
286 pat = selection + '/*'
287 for branch in all_branches:
288 if fnmatch.fnmatch(branch, pat):
289 branches.append(branch)
290 if branches:
291 self.set_remote_branches(branches)
292 else:
293 self.set_remote_branches(all_branches)
294 self.set_remote_branch('')
296 def update_local_branches(self,*rest):
297 """Update the local/remote branch names when a branch is selected"""
298 branches = self.model.local_branches
299 widget = self.local_branches
300 selection = qtutils.selected_item(widget, branches)
301 if not selection:
302 return
303 self.set_local_branch(selection)
304 self.set_remote_branch(selection)
306 def update_remote_branches(self,*rest):
307 """Update the remote branch name when a branch is selected"""
308 widget = self.remote_branches
309 branches = self.model.remote_branches
310 selection = qtutils.selected_item(widget,branches)
311 if not selection:
312 return
313 branch = utils.basename(selection)
314 if branch == 'HEAD':
315 return
316 self.set_remote_branch(branch)
318 def common_args(self):
319 """Returns git arguments common to fetch/push/pulll"""
320 remote_name = unicode(self.remote_name.text())
321 local_branch = unicode(self.local_branch.text())
322 remote_branch = unicode(self.remote_branch.text())
324 ffwd_only = self.ffwd_only_checkbox.isChecked()
325 rebase = self.rebase_checkbox.isChecked()
326 tags = self.tags_checkbox.isChecked()
328 return (remote_name,
330 'local_branch': local_branch,
331 'remote_branch': remote_branch,
332 'ffwd': ffwd_only,
333 'rebase': rebase,
334 'tags': tags,
337 #+-------------------------------------------------------------
338 #+ Actions
339 def action_callback(self):
340 action = self.action
341 if action == FETCH:
342 model_action = self.model.fetch
343 elif action == PUSH:
344 model_action = self.model.push
345 else: # if action == PULL:
346 model_action = self.model.pull
348 remote_name = unicode(self.remote_name.text())
349 if not remote_name:
350 errmsg = self.tr('No repository selected.')
351 qtutils.log(1, errmsg)
352 return
353 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,
369 default=False,
370 icon=qtutils.git_icon()):
371 return
373 if not self.ffwd_only_checkbox.isChecked():
374 title = 'Force %s?' % action.title()
375 ok_text = 'Force %s' % action.title()
377 if action == FETCH:
378 msg = 'Non-fast-forward fetch overwrites local history!'
379 info_txt = 'Force fetching from %s?' % remote
380 elif action == PUSH:
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?"
386 return
388 if not qtutils.confirm(title, msg, info_txt, ok_text,
389 default=False,
390 icon=qtutils.discard_icon()):
391 return
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...')
400 self.progress.show()
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)
425 else:
426 title = self.windowTitle()
427 if status == 0:
428 result = 'succeeded'
429 else:
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)
435 self.accept()
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)