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,477 @@
|
|
|
1
|
+
"""Read a window's repository again when an operation, a timer or the user asks for it."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass, field, replace
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from PySide6.QtCore import QObject, Signal
|
|
11
|
+
|
|
12
|
+
from gitpane.config import SharedText
|
|
13
|
+
from gitpane.history.file_history import WindowCapability
|
|
14
|
+
from gitpane.history.repository_value import RequestIdentity
|
|
15
|
+
from gitpane.operation.diagnostic import Diagnostic
|
|
16
|
+
from gitpane.reference.reference_edit import final_reference_snapshot
|
|
17
|
+
from gitpane.system.path_identity import canonical_path
|
|
18
|
+
from gitpane.worktree.worktree_snapshot import SubmoduleState, WorktreeKey
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from gitpane.controller.operation_policy import OperationPolicy
|
|
22
|
+
from gitpane.controller.reference_read_controller import ReferenceReadController
|
|
23
|
+
from gitpane.controller.session_registry import RepositorySession, SessionRegistry
|
|
24
|
+
from gitpane.controller.staging_session_registry import StagingSessionRegistry
|
|
25
|
+
from gitpane.history.repository import GitRepository
|
|
26
|
+
from gitpane.operation.repository_operation import OperationIntent, OperationOutcome
|
|
27
|
+
from gitpane.reference.reference_snapshot import ReferenceSnapshot
|
|
28
|
+
from gitpane.sequencer.rebase_state import RebaseSnapshot
|
|
29
|
+
from gitpane.sequencer.replay_state import ReplaySnapshot
|
|
30
|
+
from gitpane.submodule.submodule_discovery import SubmoduleReferenceState, SubmoduleService, SubmoduleSnapshot
|
|
31
|
+
from gitpane.worktree.staging import StagingSession
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class ReplayRefreshPublication:
|
|
36
|
+
"""The state a Cherry-pick or Revert operation published when it ended, offered to the refresh that shows its result.
|
|
37
|
+
|
|
38
|
+
It is display data for that one refresh of that one worktree and Git executable, never a proof for a write, and it
|
|
39
|
+
is used only while it is still the service's latest state, with no read started since.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
state: ReplaySnapshot
|
|
43
|
+
git_filename: str
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class RebaseRefreshPublication:
|
|
48
|
+
"""The state a rebase command published when it ended, offered to the refresh that shows its result.
|
|
49
|
+
|
|
50
|
+
It is display data for that one refresh of that one worktree and Git executable, never a proof for a write. It is
|
|
51
|
+
shown only while it is still the service's latest state with no read started since, and only once the native
|
|
52
|
+
rebase files are found exactly as the command's own capture proved them.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
state: RebaseSnapshot
|
|
56
|
+
git_filename: str
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# Compared by identity: two entries describing the same worktree are still two different reads.
|
|
60
|
+
@dataclass(eq=False)
|
|
61
|
+
class FullRefreshRead:
|
|
62
|
+
"""The working tree read and the pause state observation that the windows of one worktree share for one refresh.
|
|
63
|
+
|
|
64
|
+
One F5, or one operation result that refreshes several windows of the same worktree, reads the working tree once
|
|
65
|
+
and looks at the rebase and replay state once; every window it registered receives both. It describes reads in
|
|
66
|
+
progress for display, never a proof for a write, and it is never reused by a later refresh the user asks for:
|
|
67
|
+
that one starts a new entry and reads again.
|
|
68
|
+
|
|
69
|
+
``staging_generation`` is the read number of the preparation session's read serving this entry. A publication of
|
|
70
|
+
that read, or of an older one, needs no automatic pause observation of its own, because the entry observes itself;
|
|
71
|
+
any later read is new information and is observed as usual.
|
|
72
|
+
|
|
73
|
+
``replay_publication`` is the state a Cherry-pick or Revert of this worktree published when it ended: the entry's
|
|
74
|
+
pause observation shows it instead of reading the replay state again, while it is still the latest one.
|
|
75
|
+
``rebase_publication`` does the same for the state a rebase command published.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
key: WorktreeKey
|
|
79
|
+
git_filename: str
|
|
80
|
+
explicit: bool = False
|
|
81
|
+
session_generation_by_id: dict[str, int] = field(default_factory=dict)
|
|
82
|
+
worktree_read_requested: bool = False
|
|
83
|
+
staging_generation: int = 0
|
|
84
|
+
pause_observed: bool = False
|
|
85
|
+
read_published: bool = False
|
|
86
|
+
replay_publication: ReplayRefreshPublication | None = None
|
|
87
|
+
rebase_publication: RebaseRefreshPublication | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class RefreshScope(Enum):
|
|
91
|
+
"""How much of a repository window one refresh reads again.
|
|
92
|
+
|
|
93
|
+
A write that only changes references (a tag, a branch left unchecked out, a deletion, a remote rename) leaves the
|
|
94
|
+
working tree, the rebase state and the submodules as they were, so its windows read references and graph only.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
FULL = "full"
|
|
98
|
+
REFERENCES = "references"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True)
|
|
102
|
+
class RefreshRequest:
|
|
103
|
+
"""One refresh a window was asked for, kept as is while an earlier refresh of the same window still runs.
|
|
104
|
+
|
|
105
|
+
``reference_snapshot`` is the final reference capture of the operation that asked for the refresh, already checked
|
|
106
|
+
to come from this window's own worktree; it is display data, checked again against the reference files before it
|
|
107
|
+
is shown, and never authorizes a write. ``target_generation`` is the window generation the request was made
|
|
108
|
+
against: once another generation has started, the capture is dropped and the references are read again.
|
|
109
|
+
``full_refresh_read`` is the shared working tree read the caller registered this window in, for the windows one
|
|
110
|
+
operation result refreshes together; a request without it, such as F5, starts a read of its own.
|
|
111
|
+
``outside_already_read`` tells that the working tree and the pause state were read just before this request, which
|
|
112
|
+
follows what they showed: a moved HEAD then asks for nothing more than references and graph.
|
|
113
|
+
``selection_oid`` is a verified replacement for the previously selected commit after a history rewrite; it is
|
|
114
|
+
applied when this refresh actually starts, including when the request waited behind another read.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
scope: RefreshScope
|
|
118
|
+
reference_snapshot: ReferenceSnapshot | None = None
|
|
119
|
+
source_session_id: str | None = None
|
|
120
|
+
source_generation: int | None = None
|
|
121
|
+
target_generation: int = 0
|
|
122
|
+
recovery: bool = False
|
|
123
|
+
full_refresh_read: FullRefreshRead | None = field(default=None, compare=False)
|
|
124
|
+
outside_already_read: bool = False
|
|
125
|
+
selection_oid: str | None = None
|
|
126
|
+
|
|
127
|
+
def merged_with(self, newer: RefreshRequest) -> RefreshRequest:
|
|
128
|
+
"""Combine this waiting request with a newer one: a complete or recovery request wins, the newer capture replaces the older.
|
|
129
|
+
|
|
130
|
+
A newer request without a capture, such as F5, therefore drops the capture an operation handed over earlier.
|
|
131
|
+
The working tree is left unread only when neither request needs it read.
|
|
132
|
+
"""
|
|
133
|
+
scope = RefreshScope.FULL if RefreshScope.FULL in (self.scope, newer.scope) else RefreshScope.REFERENCES
|
|
134
|
+
return replace(newer, scope=scope, recovery=self.recovery or newer.recovery, target_generation=self.target_generation,
|
|
135
|
+
outside_already_read=self.outside_already_read and newer.outside_already_read, selection_oid=newer.selection_oid or self.selection_oid)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class RefreshController(QObject):
|
|
139
|
+
"""The refreshes of every repository window: one running and one waiting per window, the working tree read and the pause observation the windows of one worktree share, the submodule family each window shows."""
|
|
140
|
+
|
|
141
|
+
# Asks for a look at the rebase and replay pause state of a window a refresh read: the session id, then the
|
|
142
|
+
# ReplayRefreshPublication and the RebaseRefreshPublication an ending operation left to show instead of a read, or None.
|
|
143
|
+
pause_observation_requested = Signal(str, object, object)
|
|
144
|
+
refresh_started = Signal(str)
|
|
145
|
+
|
|
146
|
+
def __init__(self, *, session_registry_controller: SessionRegistry, operation_policy_controller: OperationPolicy, staging_session_registry_controller: StagingSessionRegistry, reference_read_controller: ReferenceReadController,
|
|
147
|
+
repository: GitRepository, submodule: SubmoduleService, parent: QObject | None = None) -> None:
|
|
148
|
+
"""Receive the services and the lower controllers a refresh reads through; the wiring builds it once the submodule service exists, and the operations, the timers and the windows ask it for a refresh."""
|
|
149
|
+
super().__init__(parent)
|
|
150
|
+
self.session_registry_controller = session_registry_controller
|
|
151
|
+
self.operation_policy_controller = operation_policy_controller
|
|
152
|
+
self.staging_session_registry_controller = staging_session_registry_controller
|
|
153
|
+
self.repository = repository
|
|
154
|
+
self.reference_read_controller = reference_read_controller
|
|
155
|
+
self.submodule = submodule
|
|
156
|
+
# The latest complete refresh of each worktree, which the windows refreshed together share.
|
|
157
|
+
self.full_refresh_read_by_key: dict[WorktreeKey, FullRefreshRead] = {}
|
|
158
|
+
self.checkout_refresh_session_set: set[str] = set()
|
|
159
|
+
self.submodule_snapshot_by_session: dict[str, SubmoduleSnapshot] = {}
|
|
160
|
+
self.submodule_read_generation_by_session: dict[str, int] = {}
|
|
161
|
+
self.recovery_request_set: set[RequestIdentity] = set()
|
|
162
|
+
|
|
163
|
+
def begin_full_refresh_read(self, key: WorktreeKey, *, explicit: bool = False, published_staging: StagingSession | None = None) -> FullRefreshRead:
|
|
164
|
+
"""Start the entry the windows of one worktree share for one refresh, replacing any older entry of that worktree.
|
|
165
|
+
|
|
166
|
+
``published_staging`` is a preparation session that has just shown the complete read an operation made after
|
|
167
|
+
writing: that read serves the entry, and the session itself reads again when it cannot, so the entry asks for
|
|
168
|
+
no working tree read of its own.
|
|
169
|
+
"""
|
|
170
|
+
shared = FullRefreshRead(key, self.repository.git_filename, explicit)
|
|
171
|
+
if published_staging is not None:
|
|
172
|
+
shared.worktree_read_requested = True
|
|
173
|
+
shared.staging_generation = published_staging.generation
|
|
174
|
+
self.full_refresh_read_by_key[key] = shared
|
|
175
|
+
return shared
|
|
176
|
+
|
|
177
|
+
def full_refresh_read_for(self, session: RepositorySession, shared_by_key: dict[WorktreeKey, FullRefreshRead], *, explicit: bool = False) -> FullRefreshRead:
|
|
178
|
+
"""Answer the entry one operation result shares between the windows of this session's worktree, starting it on first use."""
|
|
179
|
+
key = WorktreeKey.from_context(session.repository_context)
|
|
180
|
+
shared = shared_by_key.get(key)
|
|
181
|
+
if shared is None:
|
|
182
|
+
shared = self.begin_full_refresh_read(key, explicit=explicit)
|
|
183
|
+
shared_by_key[key] = shared
|
|
184
|
+
return shared
|
|
185
|
+
|
|
186
|
+
def _join_full_refresh_read(self, session: RepositorySession, offered: FullRefreshRead | None) -> FullRefreshRead:
|
|
187
|
+
"""Register a complete refresh in the shared read it was offered, or in a new one, and make sure that read is asked for.
|
|
188
|
+
|
|
189
|
+
An offered entry is joined only while it is still the latest one of this worktree and was started with the same
|
|
190
|
+
Git; otherwise the refresh reads for itself.
|
|
191
|
+
"""
|
|
192
|
+
key = WorktreeKey.from_context(session.repository_context)
|
|
193
|
+
shared = offered
|
|
194
|
+
if shared is None or self.full_refresh_read_by_key.get(key) is not shared or shared.key != key or shared.git_filename != self.repository.git_filename:
|
|
195
|
+
shared = self.begin_full_refresh_read(key)
|
|
196
|
+
if not shared.worktree_read_requested:
|
|
197
|
+
shared.worktree_read_requested = True
|
|
198
|
+
staging = self.staging_session_registry_controller.staging_session(session.repository_context)
|
|
199
|
+
# Whether it starts now or waits for a read already running, the read asked here gets the next number. It is
|
|
200
|
+
# recorded first because an explicit read already publishes the stale state while it is being asked for.
|
|
201
|
+
shared.staging_generation = staging.generation + 1
|
|
202
|
+
staging.refresh(explicit=shared.explicit)
|
|
203
|
+
return shared
|
|
204
|
+
|
|
205
|
+
def _observe_full_refresh_pause(self, shared: FullRefreshRead) -> None:
|
|
206
|
+
"""Look at the rebase and replay state once for every window registered in a shared refresh.
|
|
207
|
+
|
|
208
|
+
The observation is asked through the first window still open at the generation it registered with; it publishes
|
|
209
|
+
to every current window of that worktree. A replay state the ending operation published is shown instead of
|
|
210
|
+
read, once, and only while it is still current.
|
|
211
|
+
"""
|
|
212
|
+
if shared.pause_observed:
|
|
213
|
+
return
|
|
214
|
+
for session_id, generation in tuple(shared.session_generation_by_id.items()):
|
|
215
|
+
session = self.session_registry_controller.session_by_id.get(session_id)
|
|
216
|
+
if session is None or session.is_closed or session.generation != generation or not self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE):
|
|
217
|
+
continue
|
|
218
|
+
shared.pause_observed = True
|
|
219
|
+
same_git = shared.git_filename == self.repository.git_filename
|
|
220
|
+
publication = shared.replay_publication if same_git else None
|
|
221
|
+
rebase_publication = shared.rebase_publication if same_git else None
|
|
222
|
+
shared.replay_publication = None
|
|
223
|
+
shared.rebase_publication = None
|
|
224
|
+
self.pause_observation_requested.emit(session_id, publication, rebase_publication)
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
def observe_full_refresh_pauses(self, shared_by_key: dict[WorktreeKey, FullRefreshRead]) -> set[WorktreeKey]:
|
|
228
|
+
"""Observe the pause state once per worktree an operation result refreshed; answer the worktrees whose windows were refreshed."""
|
|
229
|
+
refreshed_key_set: set[WorktreeKey] = set()
|
|
230
|
+
for key, shared in shared_by_key.items():
|
|
231
|
+
if not shared.session_generation_by_id:
|
|
232
|
+
continue
|
|
233
|
+
refreshed_key_set.add(key)
|
|
234
|
+
self._observe_full_refresh_pause(shared)
|
|
235
|
+
return refreshed_key_set
|
|
236
|
+
|
|
237
|
+
def full_refresh_covers(self, key: WorktreeKey, staging: StagingSession) -> bool:
|
|
238
|
+
"""Tell whether the working tree state just published belongs to a shared refresh that observes the pause state itself.
|
|
239
|
+
|
|
240
|
+
The publication of the shared read itself is the last one covered.
|
|
241
|
+
"""
|
|
242
|
+
shared = self.full_refresh_read_by_key.get(key)
|
|
243
|
+
if shared is None or not shared.worktree_read_requested or shared.read_published or staging.generation > shared.staging_generation:
|
|
244
|
+
return False
|
|
245
|
+
if staging.generation == shared.staging_generation and not staging.refresh_active:
|
|
246
|
+
shared.read_published = True
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
def full_refresh_reads(self, key: WorktreeKey, staging: StagingSession) -> bool:
|
|
250
|
+
"""Tell, without marking anything, whether the working tree state just published was read for a shared refresh of this worktree."""
|
|
251
|
+
shared = self.full_refresh_read_by_key.get(key)
|
|
252
|
+
return shared is not None and shared.worktree_read_requested and staging.generation <= shared.staging_generation
|
|
253
|
+
|
|
254
|
+
def leave_full_refresh_read(self, session_id: str) -> None:
|
|
255
|
+
"""Forget a window's place in the shared refresh of its worktree once its own refresh has ended or it closed.
|
|
256
|
+
|
|
257
|
+
Once no window waits any more and the shared working tree read is over, the entry is dropped and covers no later
|
|
258
|
+
publication.
|
|
259
|
+
"""
|
|
260
|
+
for key, shared in tuple(self.full_refresh_read_by_key.items()):
|
|
261
|
+
if shared.session_generation_by_id.pop(session_id, None) is None or shared.session_generation_by_id:
|
|
262
|
+
continue
|
|
263
|
+
staging = self.staging_session_registry_controller.staging_by_key.get(key)
|
|
264
|
+
if staging is None or (staging.generation >= shared.staging_generation and not staging.refresh_active):
|
|
265
|
+
del self.full_refresh_read_by_key[key]
|
|
266
|
+
|
|
267
|
+
def read_submodules(self, session_id: str) -> None:
|
|
268
|
+
"""Refresh the declared family only for its still-current management view."""
|
|
269
|
+
if not self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE):
|
|
270
|
+
return
|
|
271
|
+
session = self.session_registry_controller.session_by_id[session_id]
|
|
272
|
+
if session.repository_context.is_bare:
|
|
273
|
+
return
|
|
274
|
+
identity = self.session_registry_controller.identity_by_session[session_id]
|
|
275
|
+
generation = self.submodule_read_generation_by_session.get(session_id, 0) + 1
|
|
276
|
+
self.submodule_read_generation_by_session[session_id] = generation
|
|
277
|
+
previous = self.submodule_snapshot_by_session.get(session_id)
|
|
278
|
+
|
|
279
|
+
def receive(snapshot: SubmoduleSnapshot | None, error: Exception | None) -> None:
|
|
280
|
+
"""Drop old family reads instead of painting them over a new context."""
|
|
281
|
+
if (self.session_registry_controller.identity_by_session.get(session_id) != identity or session_id not in self.session_registry_controller.window_by_session
|
|
282
|
+
or self.submodule_read_generation_by_session.get(session_id) != generation):
|
|
283
|
+
return
|
|
284
|
+
if error is not None:
|
|
285
|
+
cause = Diagnostic("Read submodules", session.repository_context.repository_dir, "", [], str(error)).prepare().cause
|
|
286
|
+
self.session_registry_controller.window_by_session[session_id].statusBar().showMessage(cause)
|
|
287
|
+
self.session_registry_controller.window_by_session[session_id].show_parent_reference(None, "Unavailable", cause)
|
|
288
|
+
if previous is not None:
|
|
289
|
+
unknown_entries = tuple(replace(entry, submodule=SubmoduleState()) for entry in previous.entries)
|
|
290
|
+
unknown = replace(previous, entries=unknown_entries, cause=cause)
|
|
291
|
+
self.submodule_snapshot_by_session[session_id] = unknown
|
|
292
|
+
self.session_registry_controller.window_by_session[session_id].show_submodules(unknown)
|
|
293
|
+
return
|
|
294
|
+
self.publish_submodule_family(session_id, snapshot, identity, generation)
|
|
295
|
+
|
|
296
|
+
self.submodule.read(session.repository_context, self.repository.git_filename, receive, previous.root_dir if previous else None)
|
|
297
|
+
|
|
298
|
+
def publish_submodule_family(self, session_id: str, snapshot: SubmoduleSnapshot, identity: RequestIdentity, generation: int) -> None:
|
|
299
|
+
"""Show one valid family in its still-current window, then read the committed parent reference of its repository.
|
|
300
|
+
|
|
301
|
+
Both a family read and a walk into another repository end here, so the window, its identity and the family read
|
|
302
|
+
generation are checked the same way before anything is shown, and again when the parent reference arrives.
|
|
303
|
+
"""
|
|
304
|
+
if (self.session_registry_controller.identity_by_session.get(session_id) != identity or session_id not in self.session_registry_controller.window_by_session
|
|
305
|
+
or self.submodule_read_generation_by_session.get(session_id) != generation):
|
|
306
|
+
return
|
|
307
|
+
session = self.session_registry_controller.session_by_id[session_id]
|
|
308
|
+
self.submodule_snapshot_by_session[session_id] = snapshot
|
|
309
|
+
self.session_registry_controller.window_by_session[session_id].show_submodules(snapshot)
|
|
310
|
+
entry = next((entry for entry in snapshot.entries if canonical_path(entry.repository_dir) == canonical_path(session.repository_context.repository_dir)), None)
|
|
311
|
+
if entry is None or entry.parent_dir is None:
|
|
312
|
+
self.session_registry_controller.window_by_session[session_id].show_parent_reference(None)
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
def reference_read(reference: SubmoduleReferenceState | None, error: Exception | None) -> None:
|
|
316
|
+
"""Publish a committed gitlink only while its child view still owns this read."""
|
|
317
|
+
if (self.session_registry_controller.identity_by_session.get(session_id) != identity or session_id not in self.session_registry_controller.window_by_session
|
|
318
|
+
or self.submodule_read_generation_by_session.get(session_id) != generation):
|
|
319
|
+
return
|
|
320
|
+
window = self.session_registry_controller.window_by_session[session_id]
|
|
321
|
+
previous_oid = window.history_model.committed_parent_oid
|
|
322
|
+
if error is not None or reference is None:
|
|
323
|
+
cause = Diagnostic("Read committed parent reference", entry.parent_dir, "", [], str(error or "No parent reference result")).prepare().cause
|
|
324
|
+
window.show_parent_reference(None, "Unavailable", cause)
|
|
325
|
+
return
|
|
326
|
+
cause = Diagnostic("Read committed parent reference", entry.parent_dir, "", [], reference.cause).prepare().cause if reference.cause else ""
|
|
327
|
+
window.show_parent_reference(reference.committed_oid, reference.committed_availability, cause)
|
|
328
|
+
oid = window.history_model.committed_parent_oid
|
|
329
|
+
if oid is not None and oid != previous_oid and (session.snapshot is None or oid not in session.snapshot.ordered_oids):
|
|
330
|
+
self.refresh(session_id)
|
|
331
|
+
|
|
332
|
+
self.submodule.read_reference_state(session.repository_context, self.repository.git_filename, os.fsencode(entry.path), reference_read, parent_dir=entry.parent_dir)
|
|
333
|
+
|
|
334
|
+
def refresh(self, session_id: str, recovery: bool = False, *, scope: RefreshScope = RefreshScope.FULL,
|
|
335
|
+
reference_snapshot: ReferenceSnapshot | None = None, source_session_id: str | None = None,
|
|
336
|
+
full_refresh_read: FullRefreshRead | None = None, outside_already_read: bool = False, selection_oid: str | None = None) -> None:
|
|
337
|
+
"""Keep one active refresh and at most one waiting request while retaining the old view.
|
|
338
|
+
|
|
339
|
+
``scope`` REFERENCES reads references and graph only; ``reference_snapshot`` is the final capture of the
|
|
340
|
+
operation of ``source_session_id``, which the caller already checked belongs to this window's worktree.
|
|
341
|
+
``full_refresh_read`` registers the window in the working tree read an operation result shares between the
|
|
342
|
+
windows it refreshes; the caller then observes the pause state once for all of them. ``outside_already_read``
|
|
343
|
+
keeps a REFERENCES refresh from reading the working tree and the pause state again when HEAD moved, because the
|
|
344
|
+
caller has just read them.
|
|
345
|
+
"""
|
|
346
|
+
if not self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE) or session_id not in self.session_registry_controller.window_by_session:
|
|
347
|
+
return
|
|
348
|
+
session = self.session_registry_controller.session_by_id[session_id]
|
|
349
|
+
request = RefreshRequest(scope, reference_snapshot, source_session_id if reference_snapshot is not None else None,
|
|
350
|
+
reference_snapshot.generation if reference_snapshot is not None else None, session.generation, recovery,
|
|
351
|
+
full_refresh_read, outside_already_read, selection_oid)
|
|
352
|
+
self.submit_refresh_request(session_id, request)
|
|
353
|
+
|
|
354
|
+
def submit_refresh_request(self, session_id: str, request: RefreshRequest) -> None:
|
|
355
|
+
"""Start one refresh request now, or merge it into the request waiting behind the running refresh."""
|
|
356
|
+
if not self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE):
|
|
357
|
+
return
|
|
358
|
+
window = self.session_registry_controller.window_by_session.get(session_id)
|
|
359
|
+
if window is None:
|
|
360
|
+
return
|
|
361
|
+
session = self.session_registry_controller.session_by_id[session_id]
|
|
362
|
+
if session.refresh_active:
|
|
363
|
+
session.refresh_queued = request if session.refresh_queued is None else session.refresh_queued.merged_with(request)
|
|
364
|
+
return
|
|
365
|
+
observation = self._usable_reference_observation(session, request)
|
|
366
|
+
previous_displayed = self.reference_read_controller.reference_snapshot_by_session.get(session_id)
|
|
367
|
+
shared = None
|
|
368
|
+
if request.scope is RefreshScope.FULL and not session.repository_context.is_bare:
|
|
369
|
+
shared = self._join_full_refresh_read(session, request.full_refresh_read)
|
|
370
|
+
session.view_anchor = window.capture_anchor()
|
|
371
|
+
if request.selection_oid is not None:
|
|
372
|
+
session.selected_oid = request.selection_oid
|
|
373
|
+
session.navigation_oid = None
|
|
374
|
+
session.navigation_commit = None
|
|
375
|
+
session.selection_generation += 1
|
|
376
|
+
session.view_anchor = replace(session.view_anchor, selected_oid=request.selection_oid)
|
|
377
|
+
session.refresh_active = True
|
|
378
|
+
session.search(window.filter_edit.text())
|
|
379
|
+
session.snapshot = None
|
|
380
|
+
window.search_timer.stop()
|
|
381
|
+
window.search_pending = False
|
|
382
|
+
window.commit_list.setEnabled(True)
|
|
383
|
+
window.history_state_label.hide()
|
|
384
|
+
previous = self.session_registry_controller.identity_by_session[session_id]
|
|
385
|
+
identity = RequestIdentity(session_id, previous.generation + 1)
|
|
386
|
+
self.session_registry_controller.identity_by_session[session_id] = identity
|
|
387
|
+
session.generation = identity.generation
|
|
388
|
+
session.invalidate_file_selection()
|
|
389
|
+
self.session_registry_controller.generation_by_session[session_id] = identity.generation
|
|
390
|
+
self.refresh_started.emit(session_id)
|
|
391
|
+
if shared is not None:
|
|
392
|
+
shared.session_generation_by_id[session_id] = identity.generation
|
|
393
|
+
if shared is not request.full_refresh_read:
|
|
394
|
+
# Nobody registered this window in a shared read: this refresh observes the pause state itself.
|
|
395
|
+
self._observe_full_refresh_pause(shared)
|
|
396
|
+
elif request.scope is RefreshScope.FULL:
|
|
397
|
+
self.pause_observation_requested.emit(session_id, None, None)
|
|
398
|
+
window.diff_panel.clear_selection("Loading diff…")
|
|
399
|
+
window.file_tree_panel.clear_selection("Loading tree…")
|
|
400
|
+
if request.recovery:
|
|
401
|
+
self.recovery_request_set.add(identity)
|
|
402
|
+
window.statusBar().showMessage(SharedText.HISTORY_REFRESH.value)
|
|
403
|
+
# The capture handed over by the operation is shown under the generation this refresh owns; its files are still
|
|
404
|
+
# checked unchanged before it is.
|
|
405
|
+
shown_again = replace(observation, generation=identity.generation) if observation is not None else None
|
|
406
|
+
before_history = None
|
|
407
|
+
if request.scope is RefreshScope.REFERENCES and not request.outside_already_read:
|
|
408
|
+
def before_history(snapshot: ReferenceSnapshot | None, error: Exception | None) -> None:
|
|
409
|
+
"""Read everything else too when HEAD moved, or when the references could not be read at all."""
|
|
410
|
+
if error is None and snapshot is not None and previous_displayed is not None and snapshot.head_oid == previous_displayed.head_oid:
|
|
411
|
+
return
|
|
412
|
+
self._read_outside_references(session_id)
|
|
413
|
+
self.reference_read_controller.read_history_with_references(session_id, identity, window.history_model.committed_parent_oid, previous=shown_again, before_history=before_history)
|
|
414
|
+
if request.scope is RefreshScope.FULL:
|
|
415
|
+
self.read_submodules(session_id)
|
|
416
|
+
|
|
417
|
+
def _usable_reference_observation(self, session: RepositorySession, request: RefreshRequest) -> ReferenceSnapshot | None:
|
|
418
|
+
"""Answer the handed-over capture only while it still describes this window's repository, Git and generation.
|
|
419
|
+
|
|
420
|
+
Another generation of the window since the request, a closed source window, another Git or another worktree
|
|
421
|
+
drops it, and the refresh reads the references again.
|
|
422
|
+
"""
|
|
423
|
+
observation = request.reference_snapshot
|
|
424
|
+
if observation is None or session.generation != request.target_generation:
|
|
425
|
+
return None
|
|
426
|
+
if request.source_session_id is not None:
|
|
427
|
+
source = self.session_registry_controller.session_by_id.get(request.source_session_id)
|
|
428
|
+
if source is None or source.is_closed:
|
|
429
|
+
return None
|
|
430
|
+
if observation.context != session.repository_context or observation.git_filename != self.repository.git_filename:
|
|
431
|
+
return None
|
|
432
|
+
return observation
|
|
433
|
+
|
|
434
|
+
def _read_outside_references(self, session_id: str) -> None:
|
|
435
|
+
"""Read the working tree, the rebase state and the submodules of a window whose reference-only refresh found HEAD moved."""
|
|
436
|
+
session = self.session_registry_controller.session_by_id.get(session_id)
|
|
437
|
+
if session is None or session.is_closed or not self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE):
|
|
438
|
+
return
|
|
439
|
+
if not session.repository_context.is_bare:
|
|
440
|
+
self.staging_session_registry_controller.staging_session(session.repository_context).refresh()
|
|
441
|
+
self.pause_observation_requested.emit(session_id, None, None)
|
|
442
|
+
self.read_submodules(session_id)
|
|
443
|
+
|
|
444
|
+
def refresh_after_reference_operation(self, operation: OperationIntent, outcome: OperationOutcome, scope: RefreshScope,
|
|
445
|
+
*, replacement_by_oid: dict[str, str | None] | None = None) -> None:
|
|
446
|
+
"""Refresh every window of the operation's repository, handing its final capture to the windows of its own worktree.
|
|
447
|
+
|
|
448
|
+
The origin is checked once, before the first refresh starts a new generation: the source window must still be
|
|
449
|
+
at the generation the operation was captured in, with the same Git and repository. A linked worktree has its
|
|
450
|
+
own HEAD, so its windows always read their references themselves.
|
|
451
|
+
"""
|
|
452
|
+
observation = final_reference_snapshot(outcome)
|
|
453
|
+
source = self.session_registry_controller.session_by_id.get(operation.session_id) if operation.session_id is not None else None
|
|
454
|
+
if (observation is None or source is None or source.is_closed or source.generation != operation.generation
|
|
455
|
+
or source.repository_context != operation.context or observation.context != operation.context
|
|
456
|
+
or observation.git_filename != operation.git_filename or self.repository.git_filename != operation.git_filename):
|
|
457
|
+
observation = None
|
|
458
|
+
observation_by_session: dict[str, ReferenceSnapshot | None] = {}
|
|
459
|
+
for session_id, session in tuple(self.session_registry_controller.session_by_id.items()):
|
|
460
|
+
if not session.is_closed and canonical_path(session.repository_context.common_git_dir) == operation.common_git_dir:
|
|
461
|
+
shared = observation is not None and session.repository_context == observation.context
|
|
462
|
+
observation_by_session[session_id] = observation if shared else None
|
|
463
|
+
shared_by_key: dict[WorktreeKey, FullRefreshRead] = {}
|
|
464
|
+
for session_id, shared_observation in observation_by_session.items():
|
|
465
|
+
self.checkout_refresh_session_set.add(session_id)
|
|
466
|
+
session = self.session_registry_controller.session_by_id[session_id]
|
|
467
|
+
full_refresh_read = self.full_refresh_read_for(session, shared_by_key) if scope is RefreshScope.FULL else None
|
|
468
|
+
selection_oid = None
|
|
469
|
+
if replacement_by_oid is not None and session.selected_oid in replacement_by_oid:
|
|
470
|
+
selection_oid = replacement_by_oid[session.selected_oid]
|
|
471
|
+
if selection_oid is None:
|
|
472
|
+
snapshot = self.reference_read_controller.reference_snapshot_by_session.get(session_id)
|
|
473
|
+
if snapshot is not None:
|
|
474
|
+
selection_oid = replacement_by_oid.get(snapshot.head_oid, snapshot.head_oid)
|
|
475
|
+
self.refresh(session_id, scope=scope, reference_snapshot=shared_observation, source_session_id=operation.session_id,
|
|
476
|
+
full_refresh_read=full_refresh_read, selection_oid=selection_oid)
|
|
477
|
+
self.observe_full_refresh_pauses(shared_by_key)
|