gitpane 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitpane/__init__.py +0 -0
- gitpane/account/__init__.py +0 -0
- gitpane/account/account_choice.py +297 -0
- gitpane/account/address_owner.py +67 -0
- gitpane/account/authentication.py +1811 -0
- gitpane/account/authentication_worker.py +836 -0
- gitpane/account/credential_manager.py +480 -0
- gitpane/account/credential_manager_install.py +171 -0
- gitpane/account/keyring_reader.py +135 -0
- gitpane/account/keyring_state.py +158 -0
- gitpane/account/ssh.py +312 -0
- gitpane/account/ssh_askpass.py +216 -0
- gitpane/account/ssh_prompt.py +222 -0
- gitpane/application.py +684 -0
- gitpane/commit/__init__.py +0 -0
- gitpane/commit/commit_metadata.py +88 -0
- gitpane/commit/commit_push.py +276 -0
- gitpane/commit/commit_write.py +830 -0
- gitpane/config.py +1066 -0
- gitpane/controller/__init__.py +0 -0
- gitpane/controller/account_controller.py +677 -0
- gitpane/controller/clone_controller.py +776 -0
- gitpane/controller/close_readiness.py +212 -0
- gitpane/controller/commit_timing_controller.py +566 -0
- gitpane/controller/conflict_controller.py +885 -0
- gitpane/controller/console_registry.py +38 -0
- gitpane/controller/emoji_window_controller.py +95 -0
- gitpane/controller/file_action_controller.py +479 -0
- gitpane/controller/history_read_controller.py +671 -0
- gitpane/controller/history_rewrite_controller.py +739 -0
- gitpane/controller/navigation_controller.py +658 -0
- gitpane/controller/opening_controller.py +375 -0
- gitpane/controller/operation_policy.py +76 -0
- gitpane/controller/rebase_controller.py +797 -0
- gitpane/controller/rebase_editor_controller.py +309 -0
- gitpane/controller/reference_form_controller.py +1677 -0
- gitpane/controller/reference_read_controller.py +242 -0
- gitpane/controller/refresh_controller.py +477 -0
- gitpane/controller/replay_controller.py +805 -0
- gitpane/controller/session_registry.py +337 -0
- gitpane/controller/setting_controller.py +592 -0
- gitpane/controller/shutdown_controller.py +345 -0
- gitpane/controller/staging_controller.py +199 -0
- gitpane/controller/staging_session_registry.py +47 -0
- gitpane/controller/stash_controller.py +367 -0
- gitpane/controller/transport_controller.py +813 -0
- gitpane/controller/welcome_controller.py +102 -0
- gitpane/controller/window_activation.py +116 -0
- gitpane/controller/worktree_state_controller.py +169 -0
- gitpane/emoji/__init__.py +0 -0
- gitpane/emoji/emoji_catalogue.py +156 -0
- gitpane/git_config/__init__.py +0 -0
- gitpane/git_config/git_setting.py +441 -0
- gitpane/history/__init__.py +0 -0
- gitpane/history/avatar.py +241 -0
- gitpane/history/file_history.py +359 -0
- gitpane/history/graph.py +145 -0
- gitpane/history/history_filter.py +31 -0
- gitpane/history/history_parse.py +605 -0
- gitpane/history/repository.py +1600 -0
- gitpane/history/repository_value.py +341 -0
- gitpane/history/revision.py +180 -0
- gitpane/history/revision_file.py +182 -0
- gitpane/history_rewrite/__init__.py +0 -0
- gitpane/history_rewrite/graph.py +210 -0
- gitpane/history_rewrite/guard.py +288 -0
- gitpane/history_rewrite/index.py +125 -0
- gitpane/history_rewrite/prepare.py +378 -0
- gitpane/history_rewrite/protocol.py +320 -0
- gitpane/history_rewrite/publish.py +444 -0
- gitpane/history_rewrite/recovery.py +481 -0
- gitpane/history_rewrite/value.py +229 -0
- gitpane/operation/__init__.py +0 -0
- gitpane/operation/console_line.py +313 -0
- gitpane/operation/console_tone.py +76 -0
- gitpane/operation/diagnostic.py +954 -0
- gitpane/operation/launch_signature.py +254 -0
- gitpane/operation/operation_status.py +87 -0
- gitpane/operation/operation_trace.py +766 -0
- gitpane/operation/repository_operation.py +781 -0
- gitpane/preference/__init__.py +0 -0
- gitpane/preference/preference_location.py +40 -0
- gitpane/preference/preference_store.py +590 -0
- gitpane/process/__init__.py +0 -0
- gitpane/process/exact_arguments.py +77 -0
- gitpane/process/git_output_text.py +20 -0
- gitpane/process/process_group.py +851 -0
- gitpane/process/process_log.py +114 -0
- gitpane/process/process_runner.py +1033 -0
- gitpane/process/released_child_watch.py +138 -0
- gitpane/recent/__init__.py +0 -0
- gitpane/recent/mount_table.py +422 -0
- gitpane/recent/recent_availability.py +414 -0
- gitpane/recent/recent_name.py +11 -0
- gitpane/recent/recent_search.py +114 -0
- gitpane/reference/__init__.py +0 -0
- gitpane/reference/reference_edit.py +2030 -0
- gitpane/reference/reference_snapshot.py +196 -0
- gitpane/reference/stash.py +730 -0
- gitpane/reference/stash_list.py +164 -0
- gitpane/sequencer/__init__.py +0 -0
- gitpane/sequencer/commit_message_rule.py +154 -0
- gitpane/sequencer/conflict.py +506 -0
- gitpane/sequencer/merge_recovery.py +163 -0
- gitpane/sequencer/merge_state.py +166 -0
- gitpane/sequencer/mergetool.py +856 -0
- gitpane/sequencer/rebase.py +750 -0
- gitpane/sequencer/rebase_edit_file.py +224 -0
- gitpane/sequencer/rebase_state.py +523 -0
- gitpane/sequencer/replay.py +1435 -0
- gitpane/sequencer/replay_legacy.py +253 -0
- gitpane/sequencer/replay_state.py +747 -0
- gitpane/submodule/__init__.py +0 -0
- gitpane/submodule/checkout_refusal.py +102 -0
- gitpane/submodule/checkout_refusal_notice.py +111 -0
- gitpane/submodule/cloned_without_checkout.py +65 -0
- gitpane/submodule/submodule_discovery.py +1180 -0
- gitpane/submodule/submodule_loss.py +218 -0
- gitpane/system/__init__.py +0 -0
- gitpane/system/appearance_portal.py +125 -0
- gitpane/system/application_identity.py +48 -0
- gitpane/system/command_line.py +36 -0
- gitpane/system/desktop_icon_fact.py +128 -0
- gitpane/system/desktop_icon_support.py +270 -0
- gitpane/system/desktop_shortcut.py +536 -0
- gitpane/system/icns_image.py +35 -0
- gitpane/system/installed_launcher.py +136 -0
- gitpane/system/linux_distribution.py +151 -0
- gitpane/system/network_folder.py +46 -0
- gitpane/system/path_identity.py +95 -0
- gitpane/system/private_tree_removal.py +47 -0
- gitpane/system/process_program.py +63 -0
- gitpane/system/qt_folder.py +38 -0
- gitpane/system/ready_channel_watch.py +67 -0
- gitpane/system/startup_activation.py +65 -0
- gitpane/system/terminal_detach.py +384 -0
- gitpane/system/xdg_session.py +85 -0
- gitpane/text/__init__.py +0 -0
- gitpane/text/text_query.py +97 -0
- gitpane/text/typed_path.py +75 -0
- gitpane/text/typed_text.py +17 -0
- gitpane/thread/__init__.py +0 -0
- gitpane/thread/weak_signal.py +93 -0
- gitpane/tool/__init__.py +0 -0
- gitpane/tool/app_bundle.py +164 -0
- gitpane/tool/desktop_text_editor.py +156 -0
- gitpane/tool/difftool.py +429 -0
- gitpane/tool/editor.py +445 -0
- gitpane/tool/editor_bridge.py +193 -0
- gitpane/tool/editor_recipe.py +481 -0
- gitpane/tool/file_reveal.py +94 -0
- gitpane/tool/git_executable.py +264 -0
- gitpane/tool/git_version.py +62 -0
- gitpane/tool/kdiff3.py +41 -0
- gitpane/tool/kdiff3_install.py +162 -0
- gitpane/tool/launch_services_text_editor.py +104 -0
- gitpane/tool/mergetool_recipe.py +84 -0
- gitpane/tool/system_editor_default.py +80 -0
- gitpane/tool/system_text_editor.py +44 -0
- gitpane/tool/system_tool.py +816 -0
- gitpane/tool/terminal_recipe.py +390 -0
- gitpane/transport/__init__.py +0 -0
- gitpane/transport/branch_exchange.py +1690 -0
- gitpane/transport/clone.py +1273 -0
- gitpane/transport/credential_read_retry.py +35 -0
- gitpane/transport/git_refusal.py +207 -0
- gitpane/transport/push_tracking.py +121 -0
- gitpane/transport/refusal_notice.py +225 -0
- gitpane/transport/remote.py +747 -0
- gitpane/transport/remote_config.py +544 -0
- gitpane/ui/__init__.py +0 -0
- gitpane/ui/console/__init__.py +0 -0
- gitpane/ui/console/error_dialog.py +332 -0
- gitpane/ui/console/operation_dialog.py +408 -0
- gitpane/ui/console/operation_output.py +91 -0
- gitpane/ui/console/operation_output_view.py +373 -0
- gitpane/ui/emoji/__init__.py +0 -0
- gitpane/ui/emoji/emoji_font.py +257 -0
- gitpane/ui/emoji/emoji_grid.py +125 -0
- gitpane/ui/emoji/emoji_window.py +154 -0
- gitpane/ui/emoji/file_action_icon.py +131 -0
- gitpane/ui/emoji/opentype_family_name.py +182 -0
- gitpane/ui/history_rewrite/__init__.py +0 -0
- gitpane/ui/history_rewrite/details.py +78 -0
- gitpane/ui/history_rewrite/preview.py +565 -0
- gitpane/ui/history_rewrite/preview_model.py +291 -0
- gitpane/ui/history_rewrite/question.py +87 -0
- gitpane/ui/history_rewrite/recovery_dialog.py +262 -0
- gitpane/ui/look/__init__.py +0 -0
- gitpane/ui/look/action_icon.py +504 -0
- gitpane/ui/look/application_icon.py +78 -0
- gitpane/ui/look/application_style.py +317 -0
- gitpane/ui/look/avatar_label.py +53 -0
- gitpane/ui/look/color_contrast.py +43 -0
- gitpane/ui/look/desktop_appearance_follower.py +118 -0
- gitpane/ui/look/fixed_font.py +21 -0
- gitpane/ui/look/gtk_settings.py +116 -0
- gitpane/ui/look/icon_density.py +16 -0
- gitpane/ui/look/reference_font.py +19 -0
- gitpane/ui/look/theme_color.py +63 -0
- gitpane/ui/look/theme_look.py +95 -0
- gitpane/ui/look/theme_palette.py +173 -0
- gitpane/ui/look/warning_icon.py +86 -0
- gitpane/ui/look/windows_control_look.py +250 -0
- gitpane/ui/reader/__init__.py +0 -0
- gitpane/ui/reader/commit_message.py +129 -0
- gitpane/ui/reader/commit_panel.py +378 -0
- gitpane/ui/reader/diff_panel.py +626 -0
- gitpane/ui/reader/file_action.py +138 -0
- gitpane/ui/reader/file_tree_panel.py +235 -0
- gitpane/ui/reader/reader_toolbar.py +56 -0
- gitpane/ui/reader/revision_tree.py +80 -0
- gitpane/ui/reader/search_result.py +165 -0
- gitpane/ui/reader/source_text.py +134 -0
- gitpane/ui/reader/source_view.py +151 -0
- gitpane/ui/reader/syntax_span.py +67 -0
- gitpane/ui/reference/__init__.py +0 -0
- gitpane/ui/reference/branch_selector.py +106 -0
- gitpane/ui/reference/checkout_dialog.py +419 -0
- gitpane/ui/reference/checkout_revision_dialog.py +182 -0
- gitpane/ui/reference/commit_summary.py +339 -0
- gitpane/ui/reference/create_branch_dialog.py +346 -0
- gitpane/ui/reference/create_tag_dialog.py +283 -0
- gitpane/ui/reference/delete_local_branch_dialog.py +323 -0
- gitpane/ui/reference/delete_tag_dialog.py +394 -0
- gitpane/ui/reference/form_opening.py +53 -0
- gitpane/ui/reference/reference_dialog.py +417 -0
- gitpane/ui/reference/reference_feedback.py +62 -0
- gitpane/ui/reference/reference_form.py +139 -0
- gitpane/ui/reference/rename_branch_dialog.py +179 -0
- gitpane/ui/reference/reset_branch_dialog.py +430 -0
- gitpane/ui/reference/reset_dialog.py +263 -0
- gitpane/ui/reference/revision_dialog.py +121 -0
- gitpane/ui/reference/revision_picker.py +227 -0
- gitpane/ui/reference/select_branches_dialog.py +199 -0
- gitpane/ui/reference/stash_dialog.py +171 -0
- gitpane/ui/repository/__init__.py +0 -0
- gitpane/ui/repository/graph_delegate.py +298 -0
- gitpane/ui/repository/history_avatar.py +130 -0
- gitpane/ui/repository/history_model.py +179 -0
- gitpane/ui/repository/repository_list_popup.py +319 -0
- gitpane/ui/repository/repository_window.py +2580 -0
- gitpane/ui/resource/ArrowDown.png +0 -0
- gitpane/ui/resource/ArrowUp.png +0 -0
- gitpane/ui/resource/BranchCheckout.png +0 -0
- gitpane/ui/resource/BranchCreate.png +0 -0
- gitpane/ui/resource/BranchDelete.png +0 -0
- gitpane/ui/resource/CopyToClipboard.png +0 -0
- gitpane/ui/resource/GitPane-16.png +0 -0
- gitpane/ui/resource/GitPane-24.png +0 -0
- gitpane/ui/resource/GitPane-256.png +0 -0
- gitpane/ui/resource/GitPane-32.png +0 -0
- gitpane/ui/resource/GitPane-48.png +0 -0
- gitpane/ui/resource/GitPane-64.png +0 -0
- gitpane/ui/resource/GitPane.ico +0 -0
- gitpane/ui/resource/NotoColorEmoji-OFL.txt +92 -0
- gitpane/ui/resource/NotoColorEmoji_WindowsCompatible.ttf +0 -0
- gitpane/ui/resource/Renamed.png +0 -0
- gitpane/ui/resource/ResetAnotherBranchToHere.png +0 -0
- gitpane/ui/resource/ResetCurrentBranchToHere.png +0 -0
- gitpane/ui/resource/Stash.png +0 -0
- gitpane/ui/resource/Tag.png +0 -0
- gitpane/ui/resource/TagCreate.png +0 -0
- gitpane/ui/resource/TagDelete.png +0 -0
- gitpane/ui/resource/Unmerged.png +0 -0
- gitpane/ui/resource/checkout.png +0 -0
- gitpane/ui/sequencer/__init__.py +0 -0
- gitpane/ui/sequencer/conflict_dialog.py +532 -0
- gitpane/ui/sequencer/merge_recovery_dialog.py +67 -0
- gitpane/ui/sequencer/rebase_dialog.py +461 -0
- gitpane/ui/sequencer/rebase_editor_window.py +220 -0
- gitpane/ui/sequencer/replay_conflict_prompt.py +105 -0
- gitpane/ui/sequencer/replay_dialog.py +501 -0
- gitpane/ui/setting/__init__.py +0 -0
- gitpane/ui/setting/account_page.py +724 -0
- gitpane/ui/setting/add_account_dialog.py +1005 -0
- gitpane/ui/setting/credential_manager_dialog.py +151 -0
- gitpane/ui/setting/git_setting_widget.py +388 -0
- gitpane/ui/setting/setting_block.py +19 -0
- gitpane/ui/setting/setting_dialog.py +750 -0
- gitpane/ui/setting/ssh_widget.py +232 -0
- gitpane/ui/setting/utility_page.py +240 -0
- gitpane/ui/staging/__init__.py +0 -0
- gitpane/ui/staging/staging_action.py +356 -0
- gitpane/ui/staging/staging_window.py +745 -0
- gitpane/ui/transport/__init__.py +0 -0
- gitpane/ui/transport/clone_dialog.py +879 -0
- gitpane/ui/transport/push_dialog.py +580 -0
- gitpane/ui/transport/push_rejection_prompt.py +100 -0
- gitpane/ui/transport/push_tracking_question.py +88 -0
- gitpane/ui/transport/remote_dialog.py +943 -0
- gitpane/ui/welcome/__init__.py +0 -0
- gitpane/ui/welcome/invalid_repository_prompt.py +114 -0
- gitpane/ui/welcome/welcome_window.py +1137 -0
- gitpane/ui/widget/__init__.py +0 -0
- gitpane/ui/widget/busy_indicator.py +106 -0
- gitpane/ui/widget/commit_date_text.py +55 -0
- gitpane/ui/widget/content_growth.py +262 -0
- gitpane/ui/widget/content_measure.py +99 -0
- gitpane/ui/widget/dialog_layout.py +668 -0
- gitpane/ui/widget/drop_down_wheel_filter.py +32 -0
- gitpane/ui/widget/flow_layout.py +110 -0
- gitpane/ui/widget/long_text.py +542 -0
- gitpane/ui/widget/mnemonic.py +13 -0
- gitpane/ui/widget/numbered_text_edit.py +127 -0
- gitpane/ui/widget/plain_label.py +15 -0
- gitpane/ui/widget/plain_tooltip.py +19 -0
- gitpane/ui/widget/refresh_advice.py +25 -0
- gitpane/ui/widget/result_label.py +227 -0
- gitpane/ui/widget/result_status_bar.py +53 -0
- gitpane/ui/widget/scrolled_text.py +69 -0
- gitpane/ui/widget/shortcut_text.py +31 -0
- gitpane/ui/widget/standard_icon_label.py +55 -0
- gitpane/ui/widget/system_shortcut.py +75 -0
- gitpane/ui/widget/unavailable_reason.py +29 -0
- gitpane/ui/widget/wheel_safe_drop_down.py +24 -0
- gitpane/ui/window_control/__init__.py +0 -0
- gitpane/ui/window_control/button_focus.py +42 -0
- gitpane/ui/window_control/compositor_answer.py +152 -0
- gitpane/ui/window_control/compositor_growth_cap.py +73 -0
- gitpane/ui/window_control/desktop_activation.py +203 -0
- gitpane/ui/window_control/dialog_modality.py +122 -0
- gitpane/ui/window_control/item_activation.py +42 -0
- gitpane/ui/window_control/modal_focus_relay.py +124 -0
- gitpane/ui/window_control/wayland_activation.py +695 -0
- gitpane/ui/window_control/window_placement.py +305 -0
- gitpane/vendor/DISTLIB-LICENSE.txt +284 -0
- gitpane/vendor/w32.exe +0 -0
- gitpane/vendor/w64-arm.exe +0 -0
- gitpane/vendor/w64.exe +0 -0
- gitpane/worktree/__init__.py +0 -0
- gitpane/worktree/command_sequence.py +110 -0
- gitpane/worktree/file_action_target.py +61 -0
- gitpane/worktree/file_menu_action.py +52 -0
- gitpane/worktree/historical_restore.py +361 -0
- gitpane/worktree/ignore_rule.py +266 -0
- gitpane/worktree/mutation.py +1539 -0
- gitpane/worktree/partial_patch.py +435 -0
- gitpane/worktree/staged_reset.py +447 -0
- gitpane/worktree/staging.py +1418 -0
- gitpane/worktree/trash.py +302 -0
- gitpane/worktree/worktree_file.py +406 -0
- gitpane/worktree/worktree_service.py +584 -0
- gitpane/worktree/worktree_snapshot.py +630 -0
- gitpane-1.0.0.dist-info/METADATA +141 -0
- gitpane-1.0.0.dist-info/RECORD +353 -0
- gitpane-1.0.0.dist-info/WHEEL +5 -0
- gitpane-1.0.0.dist-info/entry_points.txt +2 -0
- gitpane-1.0.0.dist-info/licenses/LICENSE +21 -0
- gitpane-1.0.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +109 -0
- gitpane-1.0.0.dist-info/licenses/src/gitpane/ui/resource/NotoColorEmoji-OFL.txt +92 -0
- gitpane-1.0.0.dist-info/licenses/src/gitpane/vendor/DISTLIB-LICENSE.txt +284 -0
- gitpane-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Read the references a repository window or a reference form shows."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import replace
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from PySide6.QtCore import QTimer
|
|
9
|
+
from shiboken6 import isValid
|
|
10
|
+
|
|
11
|
+
from gitpane.config import FORM_OPENING_CHECK_TIMEOUT_MS, SharedText
|
|
12
|
+
from gitpane.history.file_history import WindowCapability
|
|
13
|
+
from gitpane.operation.diagnostic import Diagnostic
|
|
14
|
+
from gitpane.reference.stash_list import STASH_LIST_REFTABLE_CAUSE
|
|
15
|
+
from gitpane.ui.reference.form_opening import close_unseen_form, refuse_form_opening
|
|
16
|
+
from gitpane.ui.widget.refresh_advice import refresh_advice_text
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
|
|
21
|
+
from PySide6.QtWidgets import QWidget
|
|
22
|
+
|
|
23
|
+
from gitpane.controller.operation_policy import OperationPolicy
|
|
24
|
+
from gitpane.controller.session_registry import SessionRegistry
|
|
25
|
+
from gitpane.history.repository import GitRepository
|
|
26
|
+
from gitpane.history.repository_value import RepositoryContext, RequestIdentity
|
|
27
|
+
from gitpane.operation.diagnostic import DiagnosticLog
|
|
28
|
+
from gitpane.reference.reference_edit import ReferenceService
|
|
29
|
+
from gitpane.reference.reference_snapshot import ReferenceSnapshot
|
|
30
|
+
|
|
31
|
+
# How many times in a row a reference form is opened again because its window was refreshed before its opening check
|
|
32
|
+
# answered: a third refresh in a row means the repository keeps changing, and the form is refused with the advice to
|
|
33
|
+
# refresh instead of opening again for ever.
|
|
34
|
+
FORM_OPENING_RESTART_LIMIT = 2
|
|
35
|
+
# Operation named in the --log file when the stash list of a window could not be read.
|
|
36
|
+
STASH_LIST_DIAGNOSTIC_OPERATION = "Read stash list"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ReferenceReadController:
|
|
40
|
+
"""The reference reads of every window: the branch list and the stash list of a window, the references a form opens on, and the history built from the same capture."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, *, session_registry_controller: SessionRegistry, operation_policy_controller: OperationPolicy, repository: GitRepository, reference: ReferenceService, diagnostic_log: DiagnosticLog | None) -> None:
|
|
43
|
+
"""Receive the services and the lower controllers the reads use; the wiring builds it once the --log file is known, and the opening, the refresh, the replay, the stash and the forms read through it."""
|
|
44
|
+
self.session_registry_controller = session_registry_controller
|
|
45
|
+
self.operation_policy_controller = operation_policy_controller
|
|
46
|
+
self.repository = repository
|
|
47
|
+
self.reference = reference
|
|
48
|
+
self.diagnostic_log = diagnostic_log
|
|
49
|
+
self.reference_snapshot_by_session: dict[str, ReferenceSnapshot] = {}
|
|
50
|
+
self.reference_read_generation_by_session: dict[str, int] = {}
|
|
51
|
+
# The stash list cause last written to the --log file for a window: a cause that stays the same over many
|
|
52
|
+
# reference reads is written once, not on every refresh.
|
|
53
|
+
self.logged_stash_cause_by_session: dict[str, str] = {}
|
|
54
|
+
# What waits for the next reference capture of a window: the history that is built from that same capture.
|
|
55
|
+
self.reference_continuation_by_session: dict[str, Callable[[ReferenceSnapshot | None, Exception | None], None]] = {}
|
|
56
|
+
# How many times in a row the reference form being opened again in a window was restarted because its window
|
|
57
|
+
# moved on before its check answered; set only for the time of that new opening.
|
|
58
|
+
self.form_restart_count_by_session: dict[str, int] = {}
|
|
59
|
+
|
|
60
|
+
def read_references(self, session_id: str, *, previous: ReferenceSnapshot | None = None,
|
|
61
|
+
on_complete: Callable[[ReferenceSnapshot | None, Exception | None], None] | None = None) -> None:
|
|
62
|
+
"""Refresh branch choices without replacing an already opened checkout intention.
|
|
63
|
+
|
|
64
|
+
A previous capture is shown again only after its reference files are checked unchanged. A continuation waits
|
|
65
|
+
for the capture this read, or a newer read of the same window, publishes; a newer continuation replaces it.
|
|
66
|
+
Every read, a reused one included, makes any older read of the window still in flight unable to publish.
|
|
67
|
+
Every read also reads the stash list again for the left panel, a reused capture included.
|
|
68
|
+
"""
|
|
69
|
+
if not self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE):
|
|
70
|
+
return
|
|
71
|
+
session = self.session_registry_controller.session_by_id[session_id]
|
|
72
|
+
window = self.session_registry_controller.window_by_session[session_id]
|
|
73
|
+
generation = session.generation
|
|
74
|
+
read_generation = self.reference_read_generation_by_session.get(session_id, 0) + 1
|
|
75
|
+
self.reference_read_generation_by_session[session_id] = read_generation
|
|
76
|
+
if on_complete is not None:
|
|
77
|
+
self.reference_continuation_by_session[session_id] = on_complete
|
|
78
|
+
window.branch_selector.show_loading()
|
|
79
|
+
|
|
80
|
+
def complete(snapshot: ReferenceSnapshot | None, error: Exception | None) -> None:
|
|
81
|
+
"""Discard late reads after closure or a newer history generation."""
|
|
82
|
+
if (session.is_closed or session.generation != generation or not isValid(window)
|
|
83
|
+
or self.reference_read_generation_by_session.get(session_id) != read_generation):
|
|
84
|
+
return
|
|
85
|
+
continuation = self.reference_continuation_by_session.pop(session_id, None)
|
|
86
|
+
if error is None and not self._publish_reference_snapshot(session_id, generation, read_generation, snapshot):
|
|
87
|
+
snapshot = None
|
|
88
|
+
error = ValueError("Git or the repository changed while reading references; refresh again")
|
|
89
|
+
if error is not None:
|
|
90
|
+
window.branch_selector.show_error(str(error))
|
|
91
|
+
window.statusBar().showMessage(str(error))
|
|
92
|
+
if continuation is not None:
|
|
93
|
+
continuation(snapshot, error)
|
|
94
|
+
|
|
95
|
+
if previous is None:
|
|
96
|
+
self.reference.read(session.repository_context, self.repository.git_filename, generation, complete, with_stash_list=True)
|
|
97
|
+
return
|
|
98
|
+
self.reference.read_display(session.repository_context, self.repository.git_filename, generation, complete, previous=previous, with_stash_list=True)
|
|
99
|
+
|
|
100
|
+
def read_form_references(self, session_id: str, dialog: QWidget, context: RepositoryContext, generation: int, complete: Callable, *,
|
|
101
|
+
menu_capture: ReferenceSnapshot | None, reopen: Callable[[ReferenceSnapshot | None], object]) -> None:
|
|
102
|
+
"""Read the references a form opens on, showing the window's own capture again while its files are unchanged.
|
|
103
|
+
|
|
104
|
+
The window capture only draws the form: every submission captures its references again and checks them when
|
|
105
|
+
Git starts, so reusing it here never authorizes a write.
|
|
106
|
+
|
|
107
|
+
Any refresh of the window starts a new generation, and a form opened on an older one can no longer write. When
|
|
108
|
+
such a refresh, or a change of Git or of the management right, comes before the answer, the form, not shown
|
|
109
|
+
yet, is closed and opened again through ``reopen`` on the menu's capture moved to the window's new generation,
|
|
110
|
+
so it gets its own new check; ``complete`` is not called. Such refreshes come from GitPane itself (another
|
|
111
|
+
program moved HEAD, an operation finished in another window), never from a hand quick enough to beat the check,
|
|
112
|
+
so the user is shown nothing about them. The new check still compares the references with the menu's capture,
|
|
113
|
+
so a refresh that found them changed refuses the form as usual. Past FORM_OPENING_RESTART_LIMIT restarts in a
|
|
114
|
+
row, the form is refused with the advice to refresh instead of opening again.
|
|
115
|
+
|
|
116
|
+
Every check ends: ``complete`` shows the form or refuses it, a form it leaves hidden (its filling raised, or it
|
|
117
|
+
stopped being current) is closed unseen, and a check with no answer within FORM_OPENING_CHECK_TIMEOUT_MS
|
|
118
|
+
refuses the form, so the next click opens a new one. A second click while the form waits gives back the waiting
|
|
119
|
+
form and starts no other check: the wait lasts one reference read, and a second form or read would only cross
|
|
120
|
+
the first.
|
|
121
|
+
"""
|
|
122
|
+
git_filename = self.repository.git_filename
|
|
123
|
+
restart_count = self.form_restart_count_by_session.pop(session_id, 0)
|
|
124
|
+
check_answered = False
|
|
125
|
+
|
|
126
|
+
def answered(snapshot: ReferenceSnapshot | None, error: Exception | None) -> None:
|
|
127
|
+
"""Open again a form not shown yet whose window moved on; otherwise hand the answer to the form's own checks."""
|
|
128
|
+
nonlocal check_answered
|
|
129
|
+
check_answered = True
|
|
130
|
+
session = self.session_registry_controller.session_by_id.get(session_id)
|
|
131
|
+
if (isValid(dialog) and not dialog.closed and session is not None and not session.is_closed
|
|
132
|
+
and not self._form_authority_kept(session_id, generation, context, git_filename)):
|
|
133
|
+
restart(session.generation)
|
|
134
|
+
return
|
|
135
|
+
try:
|
|
136
|
+
complete(snapshot, error)
|
|
137
|
+
finally:
|
|
138
|
+
if form_waits():
|
|
139
|
+
close_unseen_form(dialog)
|
|
140
|
+
|
|
141
|
+
def restart(current_generation: int) -> None:
|
|
142
|
+
"""Close the outdated form unseen and open it again under the window's current generation, within the restart limit."""
|
|
143
|
+
if restart_count >= FORM_OPENING_RESTART_LIMIT:
|
|
144
|
+
refuse_form_opening(dialog, refresh_advice_text(SharedText.REFERENCE_CHANGED_DURING_READING))
|
|
145
|
+
return
|
|
146
|
+
close_unseen_form(dialog)
|
|
147
|
+
capture = replace(menu_capture, generation=current_generation) if menu_capture is not None else None
|
|
148
|
+
self.form_restart_count_by_session[session_id] = restart_count + 1
|
|
149
|
+
try:
|
|
150
|
+
reopen(capture)
|
|
151
|
+
finally:
|
|
152
|
+
# An opening that stops before its own check (the right to manage lost meanwhile) leaves no count behind.
|
|
153
|
+
self.form_restart_count_by_session.pop(session_id, None)
|
|
154
|
+
|
|
155
|
+
def form_waits() -> bool:
|
|
156
|
+
"""Tell whether the form is still alive and open without ever having been shown."""
|
|
157
|
+
return isValid(dialog) and not dialog.closed and not dialog.isVisible()
|
|
158
|
+
|
|
159
|
+
def give_up() -> None:
|
|
160
|
+
"""Refuse a form whose check gave no answer within its limit."""
|
|
161
|
+
if not check_answered and form_waits():
|
|
162
|
+
refuse_form_opening(dialog, SharedText.FORM_CHECK_UNANSWERED.value)
|
|
163
|
+
|
|
164
|
+
# The form is the timer's context object: the timer dies with the form, and a form closed meanwhile is left as it is.
|
|
165
|
+
QTimer.singleShot(FORM_OPENING_CHECK_TIMEOUT_MS, dialog, give_up)
|
|
166
|
+
self.reference.read_display(context, git_filename, generation, answered, previous=self.reference_snapshot_by_session.get(session_id))
|
|
167
|
+
|
|
168
|
+
def form_is_current(self, session_id: str, generation: int, dialog: QWidget, dialog_by_session: dict[str, QWidget],
|
|
169
|
+
context: RepositoryContext, git_filename: str) -> bool:
|
|
170
|
+
"""Tell whether a result read for one opened form may still reach it.
|
|
171
|
+
|
|
172
|
+
The form must be alive and still be this window's form, and the window must keep the generation, repository,
|
|
173
|
+
Git and management authority the form was opened with.
|
|
174
|
+
"""
|
|
175
|
+
session = self.session_registry_controller.session_by_id.get(session_id)
|
|
176
|
+
return (isValid(dialog) and not dialog.closed and dialog_by_session.get(session_id) is dialog and session is not None and not session.is_closed
|
|
177
|
+
and self._form_authority_kept(session_id, generation, context, git_filename))
|
|
178
|
+
|
|
179
|
+
def _form_authority_kept(self, session_id: str, generation: int, context: RepositoryContext, git_filename: str) -> bool:
|
|
180
|
+
"""Tell whether an open window still has the generation, repository, Git and management right a form was opened with."""
|
|
181
|
+
session = self.session_registry_controller.session_by_id[session_id]
|
|
182
|
+
return (session.generation == generation and session.repository_context == context and self.repository.git_filename == git_filename
|
|
183
|
+
and self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE))
|
|
184
|
+
|
|
185
|
+
def _publish_reference_snapshot(self, session_id: str, generation: int, read_generation: int, snapshot: ReferenceSnapshot) -> bool:
|
|
186
|
+
"""Show one reference capture in its window, only while that window, its authority and this very read are current.
|
|
187
|
+
|
|
188
|
+
The capture must belong to the window's repository, to the Git in use and to the generation the read was made
|
|
189
|
+
for; otherwise nothing is shown and the caller reports the read as failed. The branch list and the Stashes node
|
|
190
|
+
of the left panel are updated together from the same capture.
|
|
191
|
+
"""
|
|
192
|
+
session = self.session_registry_controller.session_by_id.get(session_id)
|
|
193
|
+
window = self.session_registry_controller.window_by_session.get(session_id)
|
|
194
|
+
if (session is None or window is None or session.is_closed or not isValid(window) or not self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE)
|
|
195
|
+
or session.generation != generation or self.reference_read_generation_by_session.get(session_id) != read_generation
|
|
196
|
+
or snapshot.context != session.repository_context or snapshot.git_filename != self.repository.git_filename
|
|
197
|
+
or snapshot.generation != generation):
|
|
198
|
+
return False
|
|
199
|
+
self.reference_snapshot_by_session[session_id] = snapshot
|
|
200
|
+
window.branch_selector.populate(snapshot)
|
|
201
|
+
if snapshot.stash_list is not None:
|
|
202
|
+
window.show_stashes(snapshot.stash_list)
|
|
203
|
+
self._log_stash_list_cause(session_id, snapshot)
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
def _log_stash_list_cause(self, session_id: str, snapshot: ReferenceSnapshot) -> None:
|
|
207
|
+
"""Write why a window's stash list could not be read to the --log file, once per new cause.
|
|
208
|
+
|
|
209
|
+
The reftable format is supported, not a failure, so its cause stays on the Stashes node only. Repeating the
|
|
210
|
+
same cause on every refresh would bury the real Git failures the log is read for.
|
|
211
|
+
"""
|
|
212
|
+
cause = snapshot.stash_list.cause if snapshot.stash_list is not None else ""
|
|
213
|
+
previous_cause = self.logged_stash_cause_by_session.get(session_id, "")
|
|
214
|
+
self.logged_stash_cause_by_session[session_id] = cause
|
|
215
|
+
if not cause or cause == STASH_LIST_REFTABLE_CAUSE or cause == previous_cause or self.diagnostic_log is None:
|
|
216
|
+
return
|
|
217
|
+
self.diagnostic_log.write(Diagnostic(STASH_LIST_DIAGNOSTIC_OPERATION, snapshot.context.repository_dir, "", [], cause).prepare())
|
|
218
|
+
|
|
219
|
+
def read_history_with_references(self, session_id: str, identity: RequestIdentity, committed_parent_oid: str | None = None, *,
|
|
220
|
+
previous: ReferenceSnapshot | None = None,
|
|
221
|
+
before_history: Callable[[ReferenceSnapshot | None, Exception | None], None] | None = None) -> None:
|
|
222
|
+
"""Read the branch list, then build the history of this request from that very capture.
|
|
223
|
+
|
|
224
|
+
A window that cannot manage its repository has no branch list, so its history reads its own references. When
|
|
225
|
+
the reference capture fails, the branch list shows the error and the history still reads its own references,
|
|
226
|
+
so an empty graph is never the result of a failed branch list. ``previous`` is a capture that may be shown
|
|
227
|
+
again after its files are checked unchanged; ``before_history`` decides, from the capture, what else the
|
|
228
|
+
refresh must read before the history starts.
|
|
229
|
+
"""
|
|
230
|
+
if not self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE):
|
|
231
|
+
self.repository.read_snapshot(identity, committed_parent_oid)
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
def start_history(snapshot: ReferenceSnapshot | None, error: Exception | None) -> None:
|
|
235
|
+
"""Start the history only for the request that asked for it, never for a replaced one."""
|
|
236
|
+
if self.session_registry_controller.identity_by_session.get(session_id) != identity:
|
|
237
|
+
return
|
|
238
|
+
if before_history is not None:
|
|
239
|
+
before_history(snapshot, error)
|
|
240
|
+
self.repository.read_snapshot(identity, committed_parent_oid, reference_snapshot=snapshot if error is None else None)
|
|
241
|
+
|
|
242
|
+
self.read_references(session_id, previous=previous, on_complete=start_history)
|