1 """Widgets for Fetch, Push, and Pull"""
7 from qtpy
import QtWidgets
8 from qtpy
.QtCore
import Qt
11 from ..interaction
import Interaction
12 from ..models
import main
13 from ..models
.main
import FETCH
, FETCH_HEAD
, PULL
, PUSH
14 from ..qtutils
import connect_button
15 from ..qtutils
import get
18 from .. import gitcmds
20 from .. import qtutils
24 from . import standard
28 """Fetch from remote repositories"""
29 return run(context
, Fetch
)
33 """Push to remote repositories"""
34 return run(context
, Push
)
38 """Pull from remote repositories"""
39 return run(context
, Pull
)
42 def run(context
, RemoteDialog
):
43 """Launches fetch/push/pull dialogs."""
44 # Copy global stuff over to speedup startup
45 parent
= qtutils
.active_window()
46 view
= RemoteDialog(context
, parent
=parent
)
51 def combine(result
, prev
):
52 """Combine multiple (status, out, err) tuples into a combined tuple
54 The current state is passed in via `prev`.
55 The status code is a max() over all the subprocess status codes.
56 Individual (out, err) strings are sequentially concatenated together.
59 if isinstance(prev
, (tuple, list)):
61 raise AssertionError('combine() with length %d' % len(prev
))
63 max(prev
[0], result
[0]),
64 combine(prev
[1], result
[1]),
65 combine(prev
[2], result
[2]),
68 combined
= prev
+ '\n\n' + result
77 def uncheck(value
, *checkboxes
):
78 """Uncheck the specified checkboxes if value is True"""
80 for checkbox
in checkboxes
:
81 checkbox
.setChecked(False)
84 def strip_remotes(remote_branches
):
85 """Strip the <remote>/ prefixes from branches
87 e.g. "origin/main" becomes "main".
90 branches
= [utils
.strip_one(branch
) for branch
in remote_branches
]
91 return [branch
for branch
in branches
if branch
!= 'HEAD']
94 def get_default_remote(context
):
95 """Get the name of the default remote to use for pushing.
97 This will be the remote the branch is set to track, if it is set. If it
98 is not, remote.pushDefault will be used (or origin if not set)
101 upstream_remote
= gitcmds
.upstream_remote(context
)
102 return upstream_remote
or context
.cfg
.get('remote.pushDefault', default
='origin')
105 class ActionTask(qtutils
.Task
):
106 """Run actions asynchronously"""
108 def __init__(self
, model_action
, remote
, kwargs
):
109 qtutils
.Task
.__init
__(self
)
110 self
.model_action
= model_action
115 """Runs the model action and captures the result"""
116 return self
.model_action(self
.remote
, **self
.kwargs
)
119 class RemoteActionDialog(standard
.Dialog
):
120 """Interface for performing remote operations"""
122 def __init__(self
, context
, action
, title
, parent
=None, icon
=None):
123 """Customize the dialog based on the remote action"""
124 standard
.Dialog
.__init
__(self
, parent
=parent
)
125 self
.setWindowTitle(title
)
126 if parent
is not None:
127 self
.setWindowModality(Qt
.WindowModal
)
129 self
.context
= context
130 self
.model
= model
= context
.model
132 self
.filtered_remote_branches
= []
133 self
.selected_remotes
= []
134 self
.selected_remotes_by_worktree
= {}
135 self
.last_updated
= 0.0
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 self
.local_branch
.textChanged
.connect(lambda x
: self
.update_command_display())
143 local_branches
= self
.get_local_branches()
144 qtutils
.add_completer(self
.local_branch
, local_branches
)
146 self
.local_branches
= QtWidgets
.QListWidget()
147 self
.local_branches
.addItems(local_branches
)
149 self
.remote_label
= QtWidgets
.QLabel()
150 self
.remote_label
.setText(N_('Remote'))
152 self
.remote_name
= QtWidgets
.QLineEdit()
153 qtutils
.add_completer(self
.remote_name
, model
.remotes
)
155 self
.remote_name
.editingFinished
.connect(self
.remote_name_edited
)
156 self
.remote_name
.textEdited
.connect(lambda _
: self
.remote_name_edited())
158 self
.remotes
= QtWidgets
.QListWidget()
160 mode
= QtWidgets
.QAbstractItemView
.ExtendedSelection
161 self
.remotes
.setSelectionMode(mode
)
162 self
.remotes
.addItems(model
.remotes
)
164 self
.remote_branch_label
= QtWidgets
.QLabel()
165 self
.remote_branch_label
.setText(N_('Remote Branch'))
167 self
.remote_branch
= QtWidgets
.QLineEdit()
168 self
.remote_branch
.textChanged
.connect(lambda _
: self
.update_command_display())
169 remote_branches
= strip_remotes(model
.remote_branches
)
170 qtutils
.add_completer(self
.remote_branch
, remote_branches
)
172 self
.remote_branches
= QtWidgets
.QListWidget()
173 self
.remote_branches
.addItems(model
.remote_branches
)
175 text
= N_('Prompt on creation')
176 tooltip
= N_('Prompt when pushing creates new remote branches')
177 self
.prompt_checkbox
= qtutils
.checkbox(
178 checked
=True, text
=text
, tooltip
=tooltip
181 text
= N_('Show remote messages')
182 tooltip
= N_('Display remote messages in a separate dialog')
183 self
.remote_messages_checkbox
= qtutils
.checkbox(
184 checked
=False, text
=text
, tooltip
=tooltip
187 text
= N_('Fast-forward only')
189 'Refuse to merge unless the current HEAD is already up-'
190 'to-date or the merge can be resolved as a fast-forward'
192 self
.ff_only_checkbox
= qtutils
.checkbox(
193 checked
=True, text
=text
, tooltip
=tooltip
195 self
.ff_only_checkbox
.toggled
.connect(self
.update_command_display
)
197 text
= N_('No fast-forward')
199 'Create a merge commit even when the merge resolves as a fast-forward'
201 self
.no_ff_checkbox
= qtutils
.checkbox(
202 checked
=False, text
=text
, tooltip
=tooltip
204 self
.no_ff_checkbox
.toggled
.connect(self
.update_command_display
)
207 'Allow non-fast-forward updates. Using "force" can '
208 'cause the remote repository to lose commits; '
211 self
.force_checkbox
= qtutils
.checkbox(
212 checked
=False, text
=text
, tooltip
=tooltip
214 self
.force_checkbox
.toggled
.connect(self
.update_command_display
)
216 self
.tags_checkbox
= qtutils
.checkbox(text
=N_('Include tags '))
217 self
.tags_checkbox
.toggled
.connect(self
.update_command_display
)
220 'Remove remote-tracking branches that no longer exist on the remote'
222 self
.prune_checkbox
= qtutils
.checkbox(text
=N_('Prune '), tooltip
=tooltip
)
223 self
.prune_checkbox
.toggled
.connect(self
.update_command_display
)
225 tooltip
= N_('Rebase the current branch instead of merging')
226 self
.rebase_checkbox
= qtutils
.checkbox(text
=N_('Rebase'), tooltip
=tooltip
)
227 self
.rebase_checkbox
.toggled
.connect(self
.update_command_display
)
229 text
= N_('Set upstream')
230 tooltip
= N_('Configure the remote branch as the the new upstream')
231 self
.upstream_checkbox
= qtutils
.checkbox(text
=text
, tooltip
=tooltip
)
232 self
.upstream_checkbox
.toggled
.connect(self
.update_command_display
)
234 text
= N_('Close on completion')
235 tooltip
= N_('Close dialog when completed')
236 self
.close_on_completion_checkbox
= qtutils
.checkbox(
237 checked
=True, text
=text
, tooltip
=tooltip
240 self
.action_button
= qtutils
.ok_button(title
, icon
=icon
)
241 self
.close_button
= qtutils
.close_button()
242 self
.buttons_group
= utils
.Group(self
.close_button
, self
.action_button
)
243 self
.inputs_group
= utils
.Group(
244 self
.close_on_completion_checkbox
,
246 self
.ff_only_checkbox
,
251 self
.rebase_checkbox
,
255 self
.remote_branches
,
256 self
.upstream_checkbox
,
257 self
.prompt_checkbox
,
258 self
.remote_messages_checkbox
,
260 self
.progress
= standard
.progress_bar(
262 disable
=(self
.buttons_group
, self
.inputs_group
),
265 self
.command_display
= log
.LogWidget(self
.context
, display_usage
=False)
267 self
.local_branch_layout
= qtutils
.hbox(
268 defs
.small_margin
, defs
.spacing
, self
.local_label
, self
.local_branch
271 self
.remote_layout
= qtutils
.hbox(
272 defs
.small_margin
, defs
.spacing
, self
.remote_label
, self
.remote_name
275 self
.remote_branch_layout
= qtutils
.hbox(
278 self
.remote_branch_label
,
282 self
.options_layout
= qtutils
.hbox(
286 self
.ff_only_checkbox
,
290 self
.rebase_checkbox
,
291 self
.upstream_checkbox
,
292 self
.prompt_checkbox
,
293 self
.close_on_completion_checkbox
,
294 self
.remote_messages_checkbox
,
301 self
.remote_input_layout
= qtutils
.vbox(
302 defs
.no_margin
, defs
.spacing
, self
.remote_layout
, self
.remotes
305 self
.local_branch_input_layout
= qtutils
.vbox(
306 defs
.no_margin
, defs
.spacing
, self
.local_branch_layout
, self
.local_branches
309 self
.remote_branch_input_layout
= qtutils
.vbox(
312 self
.remote_branch_layout
,
313 self
.remote_branches
,
318 self
.remote_input_layout
,
319 self
.local_branch_input_layout
,
320 self
.remote_branch_input_layout
,
322 else: # fetch and pull
324 self
.remote_input_layout
,
325 self
.remote_branch_input_layout
,
326 self
.local_branch_input_layout
,
328 self
.top_layout
= qtutils
.hbox(defs
.no_margin
, defs
.spacing
, *widgets
)
330 self
.main_layout
= qtutils
.vbox(
334 self
.command_display
,
337 self
.main_layout
.setStretchFactor(self
.top_layout
, 2)
338 self
.setLayout(self
.main_layout
)
340 default_remote
= get_default_remote(context
)
342 remotes
= model
.remotes
343 if default_remote
in remotes
:
344 idx
= remotes
.index(default_remote
)
345 if self
.select_remote(idx
):
346 self
.set_remote_name(default_remote
)
348 if self
.select_first_remote():
349 self
.set_remote_name(remotes
[0])
351 # Trim the remote list to just the default remote
352 self
.update_remotes(update_command_display
=False)
354 # Setup signals and slots
355 self
.remotes
.itemSelectionChanged
.connect(self
.update_remotes
)
357 local
= self
.local_branches
358 local
.itemSelectionChanged
.connect(self
.update_local_branches
)
360 remote
= self
.remote_branches
361 remote
.itemSelectionChanged
.connect(self
.update_remote_branches
)
363 self
.no_ff_checkbox
.toggled
.connect(
364 lambda x
: uncheck(x
, self
.ff_only_checkbox
, self
.rebase_checkbox
)
367 self
.ff_only_checkbox
.toggled
.connect(
368 lambda x
: uncheck(x
, self
.no_ff_checkbox
, self
.rebase_checkbox
)
371 self
.rebase_checkbox
.toggled
.connect(
372 lambda x
: uncheck(x
, self
.no_ff_checkbox
, self
.ff_only_checkbox
)
375 connect_button(self
.action_button
, self
.action_callback
)
376 connect_button(self
.close_button
, self
.close
)
379 self
, N_('Close'), self
.close
, QtGui
.QKeySequence
.Close
, 'Esc'
382 self
.prune_checkbox
.hide()
386 self
.upstream_checkbox
.hide()
387 self
.prompt_checkbox
.hide()
390 # Fetch and Push-only options
391 self
.force_checkbox
.hide()
392 self
.tags_checkbox
.hide()
393 self
.local_label
.hide()
394 self
.local_branch
.hide()
395 self
.local_branches
.hide()
398 self
.rebase_checkbox
.hide()
399 self
.no_ff_checkbox
.hide()
400 self
.ff_only_checkbox
.hide()
402 self
.init_size(parent
=parent
)
403 self
.set_field_defaults()
405 def set_rebase(self
, value
):
406 """Check the rebase checkbox"""
407 self
.rebase_checkbox
.setChecked(value
)
409 def set_field_defaults(self
):
410 """Set sensible initial defaults"""
411 # Default to "git fetch origin main"
414 self
.set_local_branch('')
415 self
.set_remote_branch('')
416 if action
== PULL
: # Nothing to do when fetching.
418 # Select the current branch by default for push
420 branch
= self
.model
.currentbranch
422 idx
= self
.model
.local_branches
.index(branch
)
425 self
.select_local_branch(idx
)
426 self
.set_remote_branch(branch
)
428 self
.update_command_display()
430 def update_command_display(self
):
431 """Display the git commands that will be run"""
433 for remote
in self
.selected_remotes
:
434 cmd
= ['git', self
.action
]
435 _
, kwargs
= self
.common_args()
436 args
, kwargs
= main
.remote_args(self
.context
, remote
, self
.action
, **kwargs
)
437 cmd
.extend(git
.transform_kwargs(**kwargs
))
439 commands
.append(core
.list2cmdline(cmd
))
440 self
.command_display
.set_output('\n'.join(commands
))
442 def set_remote_name(self
, remote_name
):
443 """Set the remote name"""
444 self
.remote_name
.setText(remote_name
)
446 def set_local_branch(self
, branch
):
447 """Set the local branch name"""
448 self
.local_branch
.setText(branch
)
450 self
.local_branch
.selectAll()
452 def set_remote_branch(self
, branch
):
453 """Set the remote branch name"""
454 self
.remote_branch
.setText(branch
)
456 self
.remote_branch
.selectAll()
458 def set_remote_branches(self
, branches
):
459 """Set the list of remote branches"""
460 self
.remote_branches
.clear()
461 self
.remote_branches
.addItems(branches
)
462 self
.filtered_remote_branches
= branches
463 qtutils
.add_completer(self
.remote_branch
, strip_remotes(branches
))
465 def select_first_remote(self
):
466 """Select the first remote in the list view"""
467 return self
.select_remote(0)
469 def select_remote(self
, idx
, make_current
=True):
470 """Select a remote by index"""
471 item
= self
.remotes
.item(idx
)
473 item
.setSelected(True)
475 self
.remotes
.setCurrentItem(item
)
476 self
.set_remote_name(item
.text())
482 def select_remote_by_name(self
, remote
, make_current
=True):
483 """Select a remote by name"""
484 remotes
= self
.model
.remotes
485 if remote
in remotes
:
486 idx
= remotes
.index(remote
)
487 result
= self
.select_remote(idx
, make_current
=make_current
)
492 def set_selected_remotes(self
, remotes
):
493 """Set the list of selected remotes
495 Return True if all remotes were found and selected.
498 # Invalid remote names are ignored.
499 # This handles a remote going away between sessions.
500 # The selection is unchanged when none of the specified remotes exist.
502 for remote
in remotes
:
503 if remote
in self
.model
.remotes
:
507 # Only clear the selection if the specified remotes exist
508 self
.remotes
.clearSelection()
509 found
= all(self
.select_remote_by_name(x
) for x
in remotes
)
512 def select_local_branch(self
, idx
):
513 """Selects a local branch by index in the list view"""
514 item
= self
.local_branches
.item(idx
)
516 item
.setSelected(True)
517 self
.local_branches
.setCurrentItem(item
)
518 self
.set_local_branch(item
.text())
524 def select_remote_branch(self
, idx
):
525 """Selects a remote branch by index in the list view"""
526 item
= self
.remote_branches
.item(idx
)
528 item
.setSelected(True)
529 self
.remote_branches
.setCurrentItem(item
)
530 remote_branch
= item
.text()
531 branch
= remote_branch
.split('/', 1)[-1]
532 self
.set_remote_branch(branch
)
538 def display_remotes(self
, widget
):
539 """Display the available remotes in a listwidget"""
541 for remote_name
in self
.model
.remotes
:
542 url
= self
.model
.remote_url(remote_name
, self
.action
)
543 display
= '{}\t({})'.format(remote_name
, N_('URL: %s') % url
)
544 displayed
.append(display
)
545 qtutils
.set_items(widget
, displayed
)
547 def update_remotes(self
, update_command_display
=True):
548 """Update the remote name when a remote from the list is selected"""
549 widget
= self
.remotes
550 remotes
= self
.model
.remotes
551 selection
= qtutils
.selected_item(widget
, remotes
)
553 self
.selected_remotes
= []
555 self
.set_remote_name(selection
)
556 self
.selected_remotes
= qtutils
.selected_items(self
.remotes
, self
.model
.remotes
)
557 self
.set_remote_to(selection
, self
.selected_remotes
)
558 worktree
= self
.context
.git
.worktree()
559 self
.selected_remotes_by_worktree
[worktree
] = self
.selected_remotes
560 if update_command_display
:
561 self
.update_command_display()
563 def set_remote_to(self
, _remote
, selected_remotes
):
564 context
= self
.context
565 all_branches
= gitcmds
.branch_list(context
, remote
=True)
569 for remote_name
in selected_remotes
:
570 remote
= remote
or remote_name
# Use the first remote when prepopulating.
571 patterns
.append(remote_name
+ '/*')
573 for branch
in all_branches
:
575 if fnmatch
.fnmatch(branch
, pat
):
576 branches
.append(branch
)
579 self
.set_remote_branches(branches
)
581 self
.set_remote_branches(all_branches
)
583 if self
.action
== FETCH
:
584 self
.set_remote_branch('')
585 elif self
.action
in (PUSH
, PULL
):
587 current_branch
= self
.context
.model
.currentbranch
588 remote_branch
= f
'{remote}/{current_branch}'
589 if branches
and remote_branch
in branches
:
590 branch
= current_branch
592 idx
= self
.filtered_remote_branches
.index(remote_branch
)
595 self
.select_remote_branch(idx
)
597 self
.set_remote_branch(branch
)
599 def remote_name_edited(self
):
600 """Update the current remote when the remote name is typed manually"""
601 remote
= self
.remote_name
.text()
602 self
.update_selected_remotes(remote
)
603 self
.set_remote_to(remote
, self
.selected_remotes
)
604 self
.update_command_display()
606 def get_local_branches(self
):
607 """Calculate the list of local branches"""
608 if self
.action
== FETCH
:
609 branches
= self
.model
.local_branches
+ [FETCH_HEAD
]
611 branches
= self
.model
.local_branches
614 def update_local_branches(self
):
615 """Update the local/remote branch names when a branch is selected"""
616 branches
= self
.get_local_branches()
617 widget
= self
.local_branches
618 selection
= qtutils
.selected_item(widget
, branches
)
621 self
.set_local_branch(selection
)
622 if self
.action
== FETCH
and selection
!= FETCH_HEAD
:
623 self
.set_remote_branch(selection
)
624 self
.update_command_display()
626 def update_remote_branches(self
):
627 """Update the remote branch name when a branch is selected"""
628 widget
= self
.remote_branches
629 branches
= self
.filtered_remote_branches
630 selection
= qtutils
.selected_item(widget
, branches
)
633 branch
= utils
.strip_one(selection
)
636 self
.set_remote_branch(branch
)
637 self
.update_command_display()
639 def common_args(self
):
640 """Returns git arguments common to fetch/push/pull"""
641 remote_name
= self
.remote_name
.text()
642 local_branch
= self
.local_branch
.text()
643 remote_branch
= self
.remote_branch
.text()
645 ff_only
= get(self
.ff_only_checkbox
)
646 force
= get(self
.force_checkbox
)
647 no_ff
= get(self
.no_ff_checkbox
)
648 rebase
= get(self
.rebase_checkbox
)
649 set_upstream
= get(self
.upstream_checkbox
)
650 tags
= get(self
.tags_checkbox
)
651 prune
= get(self
.prune_checkbox
)
658 'local_branch': local_branch
,
661 'remote_branch': remote_branch
,
662 'set_upstream': set_upstream
,
670 def push_to_all(self
, _remote
, *args
, **kwargs
):
671 """Push to all selected remotes"""
672 selected_remotes
= self
.selected_remotes
674 for remote
in selected_remotes
:
675 result
= self
.model
.push(remote
, *args
, **kwargs
)
676 all_results
= combine(result
, all_results
)
679 def action_callback(self
):
680 """Perform the actual fetch/push/pull operation"""
682 remote_messages
= get(self
.remote_messages_checkbox
)
684 model_action
= self
.model
.fetch
686 model_action
= self
.push_to_all
687 else: # if action == PULL:
688 model_action
= self
.model
.pull
690 remote_name
= self
.remote_name
.text()
692 errmsg
= N_('No repository selected.')
693 Interaction
.log(errmsg
)
695 remote
, kwargs
= self
.common_args()
696 self
.update_selected_remotes(remote
)
698 # Check if we're about to create a new branch and warn.
699 remote_branch
= self
.remote_branch
.text()
700 local_branch
= self
.local_branch
.text()
702 if action
== PUSH
and not remote_branch
:
703 branch
= local_branch
704 candidate
= f
'{remote}/{branch}'
705 prompt
= get(self
.prompt_checkbox
)
707 if prompt
and candidate
not in self
.model
.remote_branches
:
715 'Branch "%(branch)s" does not exist in "%(remote)s".\n'
716 'A new remote branch will be published.'
720 info_txt
= N_('Create a new remote branch?')
721 ok_text
= N_('Create Remote Branch')
722 if not Interaction
.confirm(
723 title
, msg
, info_txt
, ok_text
, icon
=icons
.cola()
727 if get(self
.force_checkbox
):
729 title
= N_('Force Fetch?')
730 msg
= N_('Non-fast-forward fetch overwrites local history!')
731 info_txt
= N_('Force fetching from %s?') % remote
732 ok_text
= N_('Force Fetch')
734 title
= N_('Force Push?')
736 'Non-fast-forward push overwrites published '
737 'history!\n(Did you pull first?)'
739 info_txt
= N_('Force push to %s?') % remote
740 ok_text
= N_('Force Push')
741 else: # pull: shouldn't happen since the controls are hidden
743 if not Interaction
.confirm(
744 title
, msg
, info_txt
, ok_text
, default
=False, icon
=icons
.discard()
748 self
.progress
.setMaximumHeight(
749 self
.action_button
.height() - defs
.small_margin
* 2
752 # Use a thread to update in the background
753 task
= ActionTask(model_action
, remote
, kwargs
)
755 result
= log
.show_remote_messages(self
, self
.context
)
760 progress
=self
.progress
,
761 finish
=self
.action_completed
,
765 def update_selected_remotes(self
, remote
):
766 """Update the selected remotes when an ad-hoc remote is typed in"""
767 self
.selected_remotes
= qtutils
.selected_items(self
.remotes
, self
.model
.remotes
)
768 if remote
not in self
.selected_remotes
:
769 self
.selected_remotes
= [remote
]
770 worktree
= self
.context
.git
.worktree()
771 self
.selected_remotes_by_worktree
[worktree
] = self
.selected_remotes
773 def action_completed(self
, task
):
774 """Grab the results of the action and finish up"""
775 if not task
.result
or not isinstance(task
.result
, (list, tuple)):
778 status
, out
, err
= task
.result
779 command
= 'git %s' % self
.action
780 message
= Interaction
.format_command_status(command
, status
)
781 details
= Interaction
.format_out_err(out
, err
)
783 log_message
= message
785 log_message
+= '\n\n' + details
786 Interaction
.log(log_message
)
789 close_on_completion
= get(self
.close_on_completion_checkbox
)
790 if close_on_completion
:
794 if self
.action
== PUSH
:
796 message
+= N_('Have you rebased/pulled lately?')
798 Interaction
.critical(self
.windowTitle(), message
=message
, details
=details
)
800 def export_state(self
):
801 """Export persistent settings"""
802 state
= standard
.Dialog
.export_state(self
)
803 state
['close_on_completion'] = get(self
.close_on_completion_checkbox
)
804 state
['remote_messages'] = get(self
.remote_messages_checkbox
)
805 state
['selected_remotes'] = self
.selected_remotes_by_worktree
806 state
['last_updated'] = self
.last_updated
809 def apply_state(self
, state
):
810 """Apply persistent settings"""
811 result
= standard
.Dialog
.apply_state(self
, state
)
812 # Restore the "close on completion" checkbox
813 close_on_completion
= bool(state
.get('close_on_completion', True))
814 self
.close_on_completion_checkbox
.setChecked(close_on_completion
)
815 # Restore the "show remote messages" checkbox
816 remote_messages
= bool(state
.get('remote_messages', False))
817 self
.remote_messages_checkbox
.setChecked(remote_messages
)
818 # Restore the selected remotes.
819 self
.selected_remotes_by_worktree
= state
.get('selected_remotes', {})
820 self
.last_updated
= state
.get('last_updated', 0.0)
821 current_time
= time
.time()
822 one_month
= 60.0 * 60.0 * 24.0 * 31.0 # one month is ~31 days.
823 if (current_time
- self
.last_updated
) > one_month
:
824 self
._prune
_selected
_remotes
()
825 self
.last_updated
= current_time
826 # Selected remotes are stored per-worktree.
827 worktree
= self
.context
.git
.worktree()
828 selected_remotes
= self
.selected_remotes_by_worktree
.get(worktree
, [])
830 # Restore the stored selection. We stash away the current selection so that
831 # we can restore it in case we are unable to apply the stored selection.
832 current_selection
= self
.remotes
.selectedItems()
833 self
.remotes
.clearSelection()
835 for idx
, remote
in enumerate(selected_remotes
):
836 make_current
= idx
== 0 or not selected
837 if self
.select_remote_by_name(remote
, make_current
=make_current
):
839 # Restore the original selection if nothing was selected.
841 for item
in current_selection
:
842 item
.setSelected(True)
845 def _prune_selected_remotes(self
):
846 """Prune stale worktrees from the persistent selected_remotes_by_worktree"""
847 worktrees
= list(self
.selected_remotes_by_worktree
.keys())
848 for worktree
in worktrees
:
849 if not os
.path
.exists(worktree
):
850 self
.selected_remotes_by_worktree
.pop(worktree
, None)
853 # Use distinct classes so that each saves its own set of preferences
854 class Fetch(RemoteActionDialog
):
855 """Fetch from remote repositories"""
857 def __init__(self
, context
, parent
=None):
858 super().__init
__(context
, FETCH
, N_('Fetch'), parent
=parent
, icon
=icons
.repo())
860 def export_state(self
):
861 """Export persistent settings"""
862 state
= RemoteActionDialog
.export_state(self
)
863 state
['tags'] = get(self
.tags_checkbox
)
864 state
['prune'] = get(self
.prune_checkbox
)
867 def apply_state(self
, state
):
868 """Apply persistent settings"""
869 result
= RemoteActionDialog
.apply_state(self
, state
)
870 tags
= bool(state
.get('tags', False))
871 self
.tags_checkbox
.setChecked(tags
)
872 prune
= bool(state
.get('prune', False))
873 self
.prune_checkbox
.setChecked(prune
)
877 class Push(RemoteActionDialog
):
878 """Push to remote repositories"""
880 def __init__(self
, context
, parent
=None):
881 super().__init
__(context
, PUSH
, N_('Push'), parent
=parent
, icon
=icons
.push())
883 def export_state(self
):
884 """Export persistent settings"""
885 state
= RemoteActionDialog
.export_state(self
)
886 state
['prompt'] = get(self
.prompt_checkbox
)
887 state
['tags'] = get(self
.tags_checkbox
)
890 def apply_state(self
, state
):
891 """Apply persistent settings"""
892 result
= RemoteActionDialog
.apply_state(self
, state
)
893 # Restore the "prompt on creation" checkbox
894 prompt
= bool(state
.get('prompt', True))
895 self
.prompt_checkbox
.setChecked(prompt
)
896 # Restore the "tags" checkbox
897 tags
= bool(state
.get('tags', False))
898 self
.tags_checkbox
.setChecked(tags
)
902 class Pull(RemoteActionDialog
):
903 """Pull from remote repositories"""
905 def __init__(self
, context
, parent
=None):
906 super().__init
__(context
, PULL
, N_('Pull'), parent
=parent
, icon
=icons
.pull())
908 def apply_state(self
, state
):
909 """Apply persistent settings"""
910 result
= RemoteActionDialog
.apply_state(self
, state
)
911 # Rebase has the highest priority
912 rebase
= bool(state
.get('rebase', False))
913 self
.rebase_checkbox
.setChecked(rebase
)
915 ff_only
= not rebase
and bool(state
.get('ff_only', False))
916 no_ff
= not rebase
and not ff_only
and bool(state
.get('no_ff', False))
917 self
.no_ff_checkbox
.setChecked(no_ff
)
918 # Allow users coming from older versions that have rebase=False to
919 # pickup the new ff_only=True default by only setting ff_only False
920 # when it either exists in the config or when rebase=True.
921 if 'ff_only' in state
or rebase
:
922 self
.ff_only_checkbox
.setChecked(ff_only
)
925 def export_state(self
):
926 """Export persistent settings"""
927 state
= RemoteActionDialog
.export_state(self
)
928 state
['ff_only'] = get(self
.ff_only_checkbox
)
929 state
['no_ff'] = get(self
.no_ff_checkbox
)
930 state
['rebase'] = get(self
.rebase_checkbox
)