remote: display the git commands for Fetch, Push and Pull
[git-cola.git] / cola / widgets / remote.py
blob334c460d25d4f5f5594a0a77d8e1c064c3b5b155
1 """Widgets for Fetch, Push, and Pull"""
2 import fnmatch
4 from qtpy import QtGui
5 from qtpy import QtWidgets
6 from qtpy.QtCore import Qt
8 from ..i18n import N_
9 from ..interaction import Interaction
10 from ..models import main
11 from ..qtutils import connect_button
12 from ..qtutils import get
13 from .. import core
14 from .. import git
15 from .. import gitcmds
16 from .. import icons
17 from .. import qtutils
18 from .. import utils
19 from . import defs
20 from . import log
21 from . import standard
24 FETCH = 'FETCH'
25 PUSH = 'PUSH'
26 PULL = 'PULL'
29 def fetch(context):
30 """Fetch from remote repositories"""
31 return run(context, Fetch)
34 def push(context):
35 """Push to remote repositories"""
36 return run(context, Push)
39 def pull(context):
40 """Pull from remote repositories"""
41 return run(context, Pull)
44 def run(context, RemoteDialog):
45 """Launches fetch/push/pull dialogs."""
46 # Copy global stuff over to speedup startup
47 parent = qtutils.active_window()
48 view = RemoteDialog(context, parent=parent)
49 view.show()
50 return view
53 def combine(result, prev):
54 """Combine multiple (status, out, err) tuples into a combined tuple
56 The current state is passed in via `prev`.
57 The status code is a max() over all the subprocess status codes.
58 Individual (out, err) strings are sequentially concatenated together.
60 """
61 if isinstance(prev, (tuple, list)):
62 if len(prev) != 3:
63 raise AssertionError('combine() with length %d' % len(prev))
64 combined = (
65 max(prev[0], result[0]),
66 combine(prev[1], result[1]),
67 combine(prev[2], result[2]),
69 elif prev and result:
70 combined = prev + '\n\n' + result
71 elif prev:
72 combined = prev
73 else:
74 combined = result
76 return combined
79 def uncheck(value, *checkboxes):
80 """Uncheck the specified checkboxes if value is True"""
81 if value:
82 for checkbox in checkboxes:
83 checkbox.setChecked(False)
86 def strip_remotes(remote_branches):
87 """Strip the <remote>/ prefixes from branches
89 e.g. "origin/main" becomes "main".
91 """
92 branches = [utils.strip_one(branch) for branch in remote_branches]
93 return [branch for branch in branches if branch != 'HEAD']
96 def get_default_remote(context):
97 """Get the name of the default remote to use for pushing.
99 This will be the remote the branch is set to track, if it is set. If it
100 is not, remote.pushDefault will be used (or origin if not set)
103 upstream_remote = gitcmds.upstream_remote(context)
104 return upstream_remote or context.cfg.get('remote.pushDefault', default='origin')
107 class ActionTask(qtutils.Task):
108 """Run actions asynchronously"""
110 def __init__(self, model_action, remote, kwargs):
111 qtutils.Task.__init__(self)
112 self.model_action = model_action
113 self.remote = remote
114 self.kwargs = kwargs
116 def task(self):
117 """Runs the model action and captures the result"""
118 return self.model_action(self.remote, **self.kwargs)
121 class RemoteActionDialog(standard.Dialog):
122 """Interface for performing remote operations"""
124 def __init__(self, context, action, title, parent=None, icon=None):
125 """Customize the dialog based on the remote action"""
126 standard.Dialog.__init__(self, parent=parent)
127 self.setWindowTitle(title)
128 if parent is not None:
129 self.setWindowModality(Qt.WindowModal)
131 self.context = context
132 self.model = model = context.model
133 self.action = action
134 self.filtered_remote_branches = []
135 self.selected_remotes = []
137 self.runtask = qtutils.RunTask(parent=self)
138 self.local_label = QtWidgets.QLabel()
139 self.local_label.setText(N_('Local Branch'))
141 self.local_branch = QtWidgets.QLineEdit()
142 qtutils.add_completer(self.local_branch, model.local_branches)
143 self.local_branch.textChanged.connect(lambda x: self.update_command_display())
145 self.local_branches = QtWidgets.QListWidget()
146 self.local_branches.addItems(model.local_branches)
148 self.remote_label = QtWidgets.QLabel()
149 self.remote_label.setText(N_('Remote'))
151 self.remote_name = QtWidgets.QLineEdit()
152 qtutils.add_completer(self.remote_name, model.remotes)
154 self.remote_name.editingFinished.connect(self.remote_name_edited)
155 self.remote_name.textEdited.connect(lambda x: self.remote_name_edited())
157 self.remotes = QtWidgets.QListWidget()
158 if action == PUSH:
159 mode = QtWidgets.QAbstractItemView.ExtendedSelection
160 self.remotes.setSelectionMode(mode)
161 self.remotes.addItems(model.remotes)
163 self.remote_branch_label = QtWidgets.QLabel()
164 self.remote_branch_label.setText(N_('Remote Branch'))
166 self.remote_branch = QtWidgets.QLineEdit()
167 remote_branches = strip_remotes(model.remote_branches)
168 qtutils.add_completer(self.remote_branch, remote_branches)
170 self.remote_branches = QtWidgets.QListWidget()
171 self.remote_branches.addItems(model.remote_branches)
173 text = N_('Prompt on creation')
174 tooltip = N_('Prompt when pushing creates new remote branches')
175 self.prompt_checkbox = qtutils.checkbox(
176 checked=True, text=text, tooltip=tooltip
179 text = N_('Show remote messages')
180 tooltip = N_('Display remote messages in a separate dialog')
181 self.remote_messages_checkbox = qtutils.checkbox(
182 checked=False, text=text, tooltip=tooltip
185 text = N_('Fast-forward only')
186 tooltip = N_(
187 'Refuse to merge unless the current HEAD is already up-'
188 'to-date or the merge can be resolved as a fast-forward'
190 self.ff_only_checkbox = qtutils.checkbox(
191 checked=True, text=text, tooltip=tooltip
193 self.ff_only_checkbox.toggled.connect(self.update_command_display)
195 text = N_('No fast-forward')
196 tooltip = N_(
197 'Create a merge commit even when the merge resolves as a fast-forward'
199 self.no_ff_checkbox = qtutils.checkbox(
200 checked=False, text=text, tooltip=tooltip
202 self.no_ff_checkbox.toggled.connect(self.update_command_display)
203 text = N_('Force')
204 tooltip = N_(
205 'Allow non-fast-forward updates. Using "force" can '
206 'cause the remote repository to lose commits; '
207 'use it with care'
209 self.force_checkbox = qtutils.checkbox(
210 checked=False, text=text, tooltip=tooltip
212 self.force_checkbox.toggled.connect(self.update_command_display)
214 self.tags_checkbox = qtutils.checkbox(text=N_('Include tags '))
215 self.tags_checkbox.toggled.connect(self.update_command_display)
217 tooltip = N_(
218 'Remove remote-tracking branches that no longer exist on the remote'
220 self.prune_checkbox = qtutils.checkbox(text=N_('Prune '), tooltip=tooltip)
221 self.prune_checkbox.toggled.connect(self.update_command_display)
223 tooltip = N_('Rebase the current branch instead of merging')
224 self.rebase_checkbox = qtutils.checkbox(text=N_('Rebase'), tooltip=tooltip)
225 self.rebase_checkbox.toggled.connect(self.update_command_display)
227 text = N_('Set upstream')
228 tooltip = N_('Configure the remote branch as the the new upstream')
229 self.upstream_checkbox = qtutils.checkbox(text=text, tooltip=tooltip)
230 self.upstream_checkbox.toggled.connect(self.update_command_display)
232 text = N_('Close on completion')
233 tooltip = N_('Close dialog when completed')
234 self.close_on_completion_checkbox = qtutils.checkbox(
235 checked=True, text=text, tooltip=tooltip
238 self.action_button = qtutils.ok_button(title, icon=icon)
239 self.close_button = qtutils.close_button()
240 self.buttons_group = utils.Group(self.close_button, self.action_button)
241 self.inputs_group = utils.Group(
242 self.close_on_completion_checkbox,
243 self.force_checkbox,
244 self.ff_only_checkbox,
245 self.local_branch,
246 self.local_branches,
247 self.tags_checkbox,
248 self.prune_checkbox,
249 self.rebase_checkbox,
250 self.remote_name,
251 self.remotes,
252 self.remote_branch,
253 self.remote_branches,
254 self.upstream_checkbox,
255 self.prompt_checkbox,
256 self.remote_messages_checkbox,
258 self.progress = standard.progress_bar(
259 self,
260 disable=(self.buttons_group, self.inputs_group),
263 self.command_display = log.LogWidget(self.context, display_usage=False)
265 self.local_branch_layout = qtutils.hbox(
266 defs.small_margin, defs.spacing, self.local_label, self.local_branch
269 self.remote_layout = qtutils.hbox(
270 defs.small_margin, defs.spacing, self.remote_label, self.remote_name
273 self.remote_branch_layout = qtutils.hbox(
274 defs.small_margin,
275 defs.spacing,
276 self.remote_branch_label,
277 self.remote_branch,
280 self.options_layout = qtutils.hbox(
281 defs.no_margin,
282 defs.button_spacing,
283 self.force_checkbox,
284 self.ff_only_checkbox,
285 self.no_ff_checkbox,
286 self.tags_checkbox,
287 self.prune_checkbox,
288 self.rebase_checkbox,
289 self.upstream_checkbox,
290 self.prompt_checkbox,
291 self.close_on_completion_checkbox,
292 self.remote_messages_checkbox,
293 qtutils.STRETCH,
294 self.progress,
295 self.close_button,
296 self.action_button,
299 self.remote_input_layout = qtutils.vbox(
300 defs.no_margin, defs.spacing, self.remote_layout, self.remotes
303 self.local_branch_input_layout = qtutils.vbox(
304 defs.no_margin, defs.spacing, self.local_branch_layout, self.local_branches
307 self.remote_branch_input_layout = qtutils.vbox(
308 defs.no_margin,
309 defs.spacing,
310 self.remote_branch_layout,
311 self.remote_branches,
314 if action == PUSH:
315 widgets = (
316 self.remote_input_layout,
317 self.local_branch_input_layout,
318 self.remote_branch_input_layout,
320 else: # fetch and pull
321 widgets = (
322 self.remote_input_layout,
323 self.remote_branch_input_layout,
324 self.local_branch_input_layout,
326 self.top_layout = qtutils.hbox(defs.no_margin, defs.spacing, *widgets)
328 self.main_layout = qtutils.vbox(
329 defs.margin,
330 defs.spacing,
331 self.top_layout,
332 self.command_display,
333 self.options_layout,
335 self.main_layout.setStretchFactor(self.top_layout, 2)
336 self.setLayout(self.main_layout)
338 default_remote = get_default_remote(context)
340 remotes = model.remotes
341 if default_remote in remotes:
342 idx = remotes.index(default_remote)
343 if self.select_remote(idx):
344 self.set_remote_name(default_remote)
345 else:
346 if self.select_first_remote():
347 self.set_remote_name(remotes[0])
349 # Trim the remote list to just the default remote
350 self.update_remotes(update_command_display=False)
352 # Setup signals and slots
353 self.remotes.itemSelectionChanged.connect(self.update_remotes)
355 local = self.local_branches
356 local.itemSelectionChanged.connect(self.update_local_branches)
358 remote = self.remote_branches
359 remote.itemSelectionChanged.connect(self.update_remote_branches)
361 self.no_ff_checkbox.toggled.connect(
362 lambda x: uncheck(x, self.ff_only_checkbox, self.rebase_checkbox)
365 self.ff_only_checkbox.toggled.connect(
366 lambda x: uncheck(x, self.no_ff_checkbox, self.rebase_checkbox)
369 self.rebase_checkbox.toggled.connect(
370 lambda x: uncheck(x, self.no_ff_checkbox, self.ff_only_checkbox)
373 connect_button(self.action_button, self.action_callback)
374 connect_button(self.close_button, self.close)
376 qtutils.add_action(
377 self, N_('Close'), self.close, QtGui.QKeySequence.Close, 'Esc'
379 if action != FETCH:
380 self.prune_checkbox.hide()
382 if action != PUSH:
383 # Push-only options
384 self.upstream_checkbox.hide()
385 self.prompt_checkbox.hide()
387 if action == PULL:
388 # Fetch and Push-only options
389 self.force_checkbox.hide()
390 self.tags_checkbox.hide()
391 self.local_label.hide()
392 self.local_branch.hide()
393 self.local_branches.hide()
394 else:
395 # Pull-only options
396 self.rebase_checkbox.hide()
397 self.no_ff_checkbox.hide()
398 self.ff_only_checkbox.hide()
400 self.init_size(parent=parent)
401 self.set_field_defaults()
403 def set_rebase(self, value):
404 """Check the rebase checkbox"""
405 self.rebase_checkbox.setChecked(value)
407 def set_field_defaults(self):
408 """Set sensible initial defaults"""
409 # Default to "git fetch origin main"
410 action = self.action
411 if action == FETCH:
412 self.set_local_branch('')
413 self.set_remote_branch('')
414 if action == PULL: # Nothing to do when fetching.
415 pass
416 # Select the current branch by default for push
417 if action == PUSH:
418 branch = self.model.currentbranch
419 try:
420 idx = self.model.local_branches.index(branch)
421 except ValueError:
422 return
423 self.select_local_branch(idx)
424 self.set_remote_branch('')
426 self.update_command_display()
428 def update_command_display(self):
429 """Display the git commands that will be run"""
430 commands = ['']
431 for remote in self.selected_remotes:
432 cmd = ['git', self.action.lower()]
433 _, kwargs = self.common_args()
434 args, kwargs = main.remote_args(
435 self.context,
436 remote,
437 push=self.action == PUSH,
438 pull=self.action == PULL,
439 **kwargs
441 cmd.extend(git.transform_kwargs(**kwargs))
442 cmd.extend(args)
443 commands.append(core.list2cmdline(cmd))
444 self.command_display.set_output('\n'.join(commands))
446 def set_remote_name(self, remote_name):
447 """Set the remote name"""
448 self.remote_name.setText(remote_name)
450 def set_local_branch(self, branch):
451 """Set the local branch name"""
452 self.local_branch.setText(branch)
453 if branch:
454 self.local_branch.selectAll()
456 def set_remote_branch(self, branch):
457 """Set the remote branch name"""
458 self.remote_branch.setText(branch)
459 if branch:
460 self.remote_branch.selectAll()
462 def set_remote_branches(self, branches):
463 """Set the list of remote branches"""
464 self.remote_branches.clear()
465 self.remote_branches.addItems(branches)
466 self.filtered_remote_branches = branches
467 qtutils.add_completer(self.remote_branch, strip_remotes(branches))
469 def select_first_remote(self):
470 """Select the first remote in the list view"""
471 return self.select_remote(0)
473 def select_remote(self, idx):
474 """Select a remote by index"""
475 item = self.remotes.item(idx)
476 if item:
477 item.setSelected(True)
478 self.remotes.setCurrentItem(item)
479 self.set_remote_name(item.text())
480 result = True
481 else:
482 result = False
483 return result
485 def select_remote_by_name(self, remote):
486 """Select a remote by name"""
487 remotes = self.model.remotes
488 if remote in remotes:
489 idx = remotes.index(remote)
490 result = self.select_remote(idx)
491 else:
492 result = False
493 return result
495 def set_selected_remotes(self, remotes):
496 """Set the list of selected remotes
498 Return True if all remotes were found and selected.
501 # Invalid remote names are ignored.
502 # This handles a remote going away between sessions.
503 # The selection is unchanged when none of the specified remotes exist.
504 found = False
505 for remote in remotes:
506 if remote in self.model.remotes:
507 found = True
508 break
509 if found:
510 # Only clear the selection if the specified remotes exist
511 self.remotes.clearSelection()
512 found = all(self.select_remote_by_name(x) for x in remotes)
513 return found
515 def select_local_branch(self, idx):
516 """Selects a local branch by index in the list view"""
517 item = self.local_branches.item(idx)
518 if item:
519 item.setSelected(True)
520 self.local_branches.setCurrentItem(item)
521 self.set_local_branch(item.text())
522 result = True
523 else:
524 result = False
525 return result
527 def select_remote_branch(self, idx):
528 """Selects a remote branch by index in the list view"""
529 item = self.remote_branches.item(idx)
530 if item:
531 item.setSelected(True)
532 self.remote_branches.setCurrentItem(item)
533 remote_branch = item.text()
534 branch = remote_branch.split('/', 1)[-1]
535 self.set_remote_branch(branch)
536 result = True
537 else:
538 result = False
539 return result
541 def display_remotes(self, widget):
542 """Display the available remotes in a listwidget"""
543 displayed = []
544 for remote_name in self.model.remotes:
545 url = self.model.remote_url(remote_name, self.action)
546 display = '{}\t({})'.format(remote_name, N_('URL: %s') % url)
547 displayed.append(display)
548 qtutils.set_items(widget, displayed)
550 def update_remotes(self, update_command_display=True):
551 """Update the remote name when a remote from the list is selected"""
552 widget = self.remotes
553 remotes = self.model.remotes
554 selection = qtutils.selected_item(widget, remotes)
555 if not selection:
556 self.selected_remotes = []
557 return
558 self.set_remote_name(selection)
559 self.selected_remotes = qtutils.selected_items(self.remotes, self.model.remotes)
560 self.set_remote_to(selection, self.selected_remotes)
561 if update_command_display:
562 self.update_command_display()
564 def set_remote_to(self, _remote, selected_remotes):
565 context = self.context
566 all_branches = gitcmds.branch_list(context, remote=True)
567 branches = []
568 patterns = []
569 remote = ''
570 for remote_name in selected_remotes:
571 remote = remote or remote_name # Use the first remote when prepopulating.
572 patterns.append(remote_name + '/*')
574 for branch in all_branches:
575 for pat in patterns:
576 if fnmatch.fnmatch(branch, pat):
577 branches.append(branch)
578 break
579 if branches:
580 self.set_remote_branches(branches)
581 else:
582 self.set_remote_branches(all_branches)
584 if self.action == FETCH:
585 self.set_remote_branch('')
586 elif self.action == PUSH:
587 self.set_remote_branch('')
588 elif self.action == PULL:
589 branch = ''
590 current_branch = self.context.model.currentbranch
591 remote_branch = '%s/%s' % (remote, current_branch)
592 if branches and remote_branch in branches:
593 branch = current_branch
594 try:
595 idx = self.filtered_remote_branches.index(remote_branch)
596 except ValueError:
597 pass
598 self.select_remote_branch(idx)
599 return
600 self.set_remote_branch(branch)
602 def remote_name_edited(self):
603 """Update the current remote when the remote name is typed manually"""
604 remote = self.remote_name.text()
605 self.update_selected_remotes(remote)
606 self.set_remote_to(remote, self.selected_remotes)
607 self.update_command_display()
609 def update_local_branches(self):
610 """Update the local/remote branch names when a branch is selected"""
611 branches = self.model.local_branches
612 widget = self.local_branches
613 selection = qtutils.selected_item(widget, branches)
614 if not selection:
615 return
616 self.set_local_branch(selection)
617 if self.action == FETCH:
618 self.set_remote_branch(selection)
619 self.update_command_display()
621 def update_remote_branches(self):
622 """Update the remote branch name when a branch is selected"""
623 widget = self.remote_branches
624 branches = self.filtered_remote_branches
625 selection = qtutils.selected_item(widget, branches)
626 if not selection:
627 return
628 branch = utils.strip_one(selection)
629 if branch == 'HEAD':
630 return
631 self.set_remote_branch(branch)
632 self.update_command_display()
634 def common_args(self):
635 """Returns git arguments common to fetch/push/pull"""
636 remote_name = self.remote_name.text()
637 local_branch = self.local_branch.text()
638 remote_branch = self.remote_branch.text()
640 ff_only = get(self.ff_only_checkbox)
641 force = get(self.force_checkbox)
642 no_ff = get(self.no_ff_checkbox)
643 rebase = get(self.rebase_checkbox)
644 set_upstream = get(self.upstream_checkbox)
645 tags = get(self.tags_checkbox)
646 prune = get(self.prune_checkbox)
648 return (
649 remote_name,
651 'ff_only': ff_only,
652 'force': force,
653 'local_branch': local_branch,
654 'no_ff': no_ff,
655 'rebase': rebase,
656 'remote_branch': remote_branch,
657 'set_upstream': set_upstream,
658 'tags': tags,
659 'prune': prune,
663 # Actions
665 def push_to_all(self, _remote, *args, **kwargs):
666 """Push to all selected remotes"""
667 selected_remotes = self.selected_remotes
668 all_results = None
669 for remote in selected_remotes:
670 result = self.model.push(remote, *args, **kwargs)
671 all_results = combine(result, all_results)
672 return all_results
674 def action_callback(self):
675 """Perform the actual fetch/push/pull operation"""
676 action = self.action
677 remote_messages = get(self.remote_messages_checkbox)
678 if action == FETCH:
679 model_action = self.model.fetch
680 elif action == PUSH:
681 model_action = self.push_to_all
682 else: # if action == PULL:
683 model_action = self.model.pull
685 remote_name = self.remote_name.text()
686 if not remote_name:
687 errmsg = N_('No repository selected.')
688 Interaction.log(errmsg)
689 return
690 remote, kwargs = self.common_args()
691 self.update_selected_remotes(remote)
693 # Check if we're about to create a new branch and warn.
694 remote_branch = self.remote_branch.text()
695 local_branch = self.local_branch.text()
697 if action == PUSH and not remote_branch:
698 branch = local_branch
699 candidate = f'{remote}/{branch}'
700 prompt = get(self.prompt_checkbox)
702 if prompt and candidate not in self.model.remote_branches:
703 title = N_('Push')
704 args = {
705 'branch': branch,
706 'remote': remote,
708 msg = (
710 'Branch "%(branch)s" does not exist in "%(remote)s".\n'
711 'A new remote branch will be published.'
713 % args
715 info_txt = N_('Create a new remote branch?')
716 ok_text = N_('Create Remote Branch')
717 if not Interaction.confirm(
718 title, msg, info_txt, ok_text, icon=icons.cola()
720 return
722 if get(self.force_checkbox):
723 if action == FETCH:
724 title = N_('Force Fetch?')
725 msg = N_('Non-fast-forward fetch overwrites local history!')
726 info_txt = N_('Force fetching from %s?') % remote
727 ok_text = N_('Force Fetch')
728 elif action == PUSH:
729 title = N_('Force Push?')
730 msg = N_(
731 'Non-fast-forward push overwrites published '
732 'history!\n(Did you pull first?)'
734 info_txt = N_('Force push to %s?') % remote
735 ok_text = N_('Force Push')
736 else: # pull: shouldn't happen since the controls are hidden
737 return
738 if not Interaction.confirm(
739 title, msg, info_txt, ok_text, default=False, icon=icons.discard()
741 return
743 self.progress.setMaximumHeight(
744 self.action_button.height() - defs.small_margin * 2
747 # Use a thread to update in the background
748 task = ActionTask(model_action, remote, kwargs)
749 if remote_messages:
750 result = log.show_remote_messages(self, self.context)
751 else:
752 result = None
753 self.runtask.start(
754 task,
755 progress=self.progress,
756 finish=self.action_completed,
757 result=result,
760 def update_selected_remotes(self, remote):
761 """Update the selected remotes when an ad-hoc remote is typed in"""
762 self.selected_remotes = qtutils.selected_items(
763 self.remotes, self.model.remotes
765 if remote not in self.selected_remotes:
766 self.selected_remotes = [remote]
768 def action_completed(self, task):
769 """Grab the results of the action and finish up"""
770 if not task.result or not isinstance(task.result, (list, tuple)):
771 return
773 status, out, err = task.result
774 command = 'git %s' % self.action.lower()
775 message = Interaction.format_command_status(command, status)
776 details = Interaction.format_out_err(out, err)
778 log_message = message
779 if details:
780 log_message += '\n\n' + details
781 Interaction.log(log_message)
783 if status == 0:
784 close_on_completion = get(self.close_on_completion_checkbox)
785 if close_on_completion:
786 self.accept()
787 return
789 if self.action == PUSH:
790 message += '\n\n'
791 message += N_('Have you rebased/pulled lately?')
793 Interaction.critical(self.windowTitle(), message=message, details=details)
795 def export_state(self):
796 """Export persistent settings"""
797 state = standard.Dialog.export_state(self)
798 state['close_on_completion'] = get(self.close_on_completion_checkbox)
799 state['remote_messages'] = get(self.remote_messages_checkbox)
800 return state
802 def apply_state(self, state):
803 """Apply persistent settings"""
804 result = standard.Dialog.apply_state(self, state)
805 # Restore the "close on completion" checkbox
806 close_on_completion = bool(state.get('close_on_completion', True))
807 self.close_on_completion_checkbox.setChecked(close_on_completion)
808 # Restore the "show remote messages" checkbox
809 remote_messages = bool(state.get('remote_messages', False))
810 self.remote_messages_checkbox.setChecked(remote_messages)
811 return result
814 # Use distinct classes so that each saves its own set of preferences
815 class Fetch(RemoteActionDialog):
816 """Fetch from remote repositories"""
818 def __init__(self, context, parent=None):
819 super().__init__(context, FETCH, N_('Fetch'), parent=parent, icon=icons.repo())
821 def export_state(self):
822 """Export persistent settings"""
823 state = RemoteActionDialog.export_state(self)
824 state['tags'] = get(self.tags_checkbox)
825 state['prune'] = get(self.prune_checkbox)
826 return state
828 def apply_state(self, state):
829 """Apply persistent settings"""
830 result = RemoteActionDialog.apply_state(self, state)
831 tags = bool(state.get('tags', False))
832 self.tags_checkbox.setChecked(tags)
833 prune = bool(state.get('prune', False))
834 self.prune_checkbox.setChecked(prune)
835 return result
838 class Push(RemoteActionDialog):
839 """Push to remote repositories"""
841 def __init__(self, context, parent=None):
842 super().__init__(context, PUSH, N_('Push'), parent=parent, icon=icons.push())
844 def export_state(self):
845 """Export persistent settings"""
846 state = RemoteActionDialog.export_state(self)
847 state['prompt'] = get(self.prompt_checkbox)
848 state['tags'] = get(self.tags_checkbox)
849 return state
851 def apply_state(self, state):
852 """Apply persistent settings"""
853 result = RemoteActionDialog.apply_state(self, state)
854 # Restore the "prompt on creation" checkbox
855 prompt = bool(state.get('prompt', True))
856 self.prompt_checkbox.setChecked(prompt)
857 # Restore the "tags" checkbox
858 tags = bool(state.get('tags', False))
859 self.tags_checkbox.setChecked(tags)
860 return result
863 class Pull(RemoteActionDialog):
864 """Pull from remote repositories"""
866 def __init__(self, context, parent=None):
867 super().__init__(context, PULL, N_('Pull'), parent=parent, icon=icons.pull())
869 def apply_state(self, state):
870 """Apply persistent settings"""
871 result = RemoteActionDialog.apply_state(self, state)
872 # Rebase has the highest priority
873 rebase = bool(state.get('rebase', False))
874 self.rebase_checkbox.setChecked(rebase)
876 ff_only = not rebase and bool(state.get('ff_only', False))
877 no_ff = not rebase and not ff_only and bool(state.get('no_ff', False))
878 self.no_ff_checkbox.setChecked(no_ff)
879 # Allow users coming from older versions that have rebase=False to
880 # pickup the new ff_only=True default by only setting ff_only False
881 # when it either exists in the config or when rebase=True.
882 if 'ff_only' in state or rebase:
883 self.ff_only_checkbox.setChecked(ff_only)
884 return result
886 def export_state(self):
887 """Export persistent settings"""
888 state = RemoteActionDialog.export_state(self)
889 state['ff_only'] = get(self.ff_only_checkbox)
890 state['no_ff'] = get(self.no_ff_checkbox)
891 state['rebase'] = get(self.rebase_checkbox)
892 return state