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.
Files changed (353) hide show
  1. gitpane/__init__.py +0 -0
  2. gitpane/account/__init__.py +0 -0
  3. gitpane/account/account_choice.py +297 -0
  4. gitpane/account/address_owner.py +67 -0
  5. gitpane/account/authentication.py +1811 -0
  6. gitpane/account/authentication_worker.py +836 -0
  7. gitpane/account/credential_manager.py +480 -0
  8. gitpane/account/credential_manager_install.py +171 -0
  9. gitpane/account/keyring_reader.py +135 -0
  10. gitpane/account/keyring_state.py +158 -0
  11. gitpane/account/ssh.py +312 -0
  12. gitpane/account/ssh_askpass.py +216 -0
  13. gitpane/account/ssh_prompt.py +222 -0
  14. gitpane/application.py +684 -0
  15. gitpane/commit/__init__.py +0 -0
  16. gitpane/commit/commit_metadata.py +88 -0
  17. gitpane/commit/commit_push.py +276 -0
  18. gitpane/commit/commit_write.py +830 -0
  19. gitpane/config.py +1066 -0
  20. gitpane/controller/__init__.py +0 -0
  21. gitpane/controller/account_controller.py +677 -0
  22. gitpane/controller/clone_controller.py +776 -0
  23. gitpane/controller/close_readiness.py +212 -0
  24. gitpane/controller/commit_timing_controller.py +566 -0
  25. gitpane/controller/conflict_controller.py +885 -0
  26. gitpane/controller/console_registry.py +38 -0
  27. gitpane/controller/emoji_window_controller.py +95 -0
  28. gitpane/controller/file_action_controller.py +479 -0
  29. gitpane/controller/history_read_controller.py +671 -0
  30. gitpane/controller/history_rewrite_controller.py +739 -0
  31. gitpane/controller/navigation_controller.py +658 -0
  32. gitpane/controller/opening_controller.py +375 -0
  33. gitpane/controller/operation_policy.py +76 -0
  34. gitpane/controller/rebase_controller.py +797 -0
  35. gitpane/controller/rebase_editor_controller.py +309 -0
  36. gitpane/controller/reference_form_controller.py +1677 -0
  37. gitpane/controller/reference_read_controller.py +242 -0
  38. gitpane/controller/refresh_controller.py +477 -0
  39. gitpane/controller/replay_controller.py +805 -0
  40. gitpane/controller/session_registry.py +337 -0
  41. gitpane/controller/setting_controller.py +592 -0
  42. gitpane/controller/shutdown_controller.py +345 -0
  43. gitpane/controller/staging_controller.py +199 -0
  44. gitpane/controller/staging_session_registry.py +47 -0
  45. gitpane/controller/stash_controller.py +367 -0
  46. gitpane/controller/transport_controller.py +813 -0
  47. gitpane/controller/welcome_controller.py +102 -0
  48. gitpane/controller/window_activation.py +116 -0
  49. gitpane/controller/worktree_state_controller.py +169 -0
  50. gitpane/emoji/__init__.py +0 -0
  51. gitpane/emoji/emoji_catalogue.py +156 -0
  52. gitpane/git_config/__init__.py +0 -0
  53. gitpane/git_config/git_setting.py +441 -0
  54. gitpane/history/__init__.py +0 -0
  55. gitpane/history/avatar.py +241 -0
  56. gitpane/history/file_history.py +359 -0
  57. gitpane/history/graph.py +145 -0
  58. gitpane/history/history_filter.py +31 -0
  59. gitpane/history/history_parse.py +605 -0
  60. gitpane/history/repository.py +1600 -0
  61. gitpane/history/repository_value.py +341 -0
  62. gitpane/history/revision.py +180 -0
  63. gitpane/history/revision_file.py +182 -0
  64. gitpane/history_rewrite/__init__.py +0 -0
  65. gitpane/history_rewrite/graph.py +210 -0
  66. gitpane/history_rewrite/guard.py +288 -0
  67. gitpane/history_rewrite/index.py +125 -0
  68. gitpane/history_rewrite/prepare.py +378 -0
  69. gitpane/history_rewrite/protocol.py +320 -0
  70. gitpane/history_rewrite/publish.py +444 -0
  71. gitpane/history_rewrite/recovery.py +481 -0
  72. gitpane/history_rewrite/value.py +229 -0
  73. gitpane/operation/__init__.py +0 -0
  74. gitpane/operation/console_line.py +313 -0
  75. gitpane/operation/console_tone.py +76 -0
  76. gitpane/operation/diagnostic.py +954 -0
  77. gitpane/operation/launch_signature.py +254 -0
  78. gitpane/operation/operation_status.py +87 -0
  79. gitpane/operation/operation_trace.py +766 -0
  80. gitpane/operation/repository_operation.py +781 -0
  81. gitpane/preference/__init__.py +0 -0
  82. gitpane/preference/preference_location.py +40 -0
  83. gitpane/preference/preference_store.py +590 -0
  84. gitpane/process/__init__.py +0 -0
  85. gitpane/process/exact_arguments.py +77 -0
  86. gitpane/process/git_output_text.py +20 -0
  87. gitpane/process/process_group.py +851 -0
  88. gitpane/process/process_log.py +114 -0
  89. gitpane/process/process_runner.py +1033 -0
  90. gitpane/process/released_child_watch.py +138 -0
  91. gitpane/recent/__init__.py +0 -0
  92. gitpane/recent/mount_table.py +422 -0
  93. gitpane/recent/recent_availability.py +414 -0
  94. gitpane/recent/recent_name.py +11 -0
  95. gitpane/recent/recent_search.py +114 -0
  96. gitpane/reference/__init__.py +0 -0
  97. gitpane/reference/reference_edit.py +2030 -0
  98. gitpane/reference/reference_snapshot.py +196 -0
  99. gitpane/reference/stash.py +730 -0
  100. gitpane/reference/stash_list.py +164 -0
  101. gitpane/sequencer/__init__.py +0 -0
  102. gitpane/sequencer/commit_message_rule.py +154 -0
  103. gitpane/sequencer/conflict.py +506 -0
  104. gitpane/sequencer/merge_recovery.py +163 -0
  105. gitpane/sequencer/merge_state.py +166 -0
  106. gitpane/sequencer/mergetool.py +856 -0
  107. gitpane/sequencer/rebase.py +750 -0
  108. gitpane/sequencer/rebase_edit_file.py +224 -0
  109. gitpane/sequencer/rebase_state.py +523 -0
  110. gitpane/sequencer/replay.py +1435 -0
  111. gitpane/sequencer/replay_legacy.py +253 -0
  112. gitpane/sequencer/replay_state.py +747 -0
  113. gitpane/submodule/__init__.py +0 -0
  114. gitpane/submodule/checkout_refusal.py +102 -0
  115. gitpane/submodule/checkout_refusal_notice.py +111 -0
  116. gitpane/submodule/cloned_without_checkout.py +65 -0
  117. gitpane/submodule/submodule_discovery.py +1180 -0
  118. gitpane/submodule/submodule_loss.py +218 -0
  119. gitpane/system/__init__.py +0 -0
  120. gitpane/system/appearance_portal.py +125 -0
  121. gitpane/system/application_identity.py +48 -0
  122. gitpane/system/command_line.py +36 -0
  123. gitpane/system/desktop_icon_fact.py +128 -0
  124. gitpane/system/desktop_icon_support.py +270 -0
  125. gitpane/system/desktop_shortcut.py +536 -0
  126. gitpane/system/icns_image.py +35 -0
  127. gitpane/system/installed_launcher.py +136 -0
  128. gitpane/system/linux_distribution.py +151 -0
  129. gitpane/system/network_folder.py +46 -0
  130. gitpane/system/path_identity.py +95 -0
  131. gitpane/system/private_tree_removal.py +47 -0
  132. gitpane/system/process_program.py +63 -0
  133. gitpane/system/qt_folder.py +38 -0
  134. gitpane/system/ready_channel_watch.py +67 -0
  135. gitpane/system/startup_activation.py +65 -0
  136. gitpane/system/terminal_detach.py +384 -0
  137. gitpane/system/xdg_session.py +85 -0
  138. gitpane/text/__init__.py +0 -0
  139. gitpane/text/text_query.py +97 -0
  140. gitpane/text/typed_path.py +75 -0
  141. gitpane/text/typed_text.py +17 -0
  142. gitpane/thread/__init__.py +0 -0
  143. gitpane/thread/weak_signal.py +93 -0
  144. gitpane/tool/__init__.py +0 -0
  145. gitpane/tool/app_bundle.py +164 -0
  146. gitpane/tool/desktop_text_editor.py +156 -0
  147. gitpane/tool/difftool.py +429 -0
  148. gitpane/tool/editor.py +445 -0
  149. gitpane/tool/editor_bridge.py +193 -0
  150. gitpane/tool/editor_recipe.py +481 -0
  151. gitpane/tool/file_reveal.py +94 -0
  152. gitpane/tool/git_executable.py +264 -0
  153. gitpane/tool/git_version.py +62 -0
  154. gitpane/tool/kdiff3.py +41 -0
  155. gitpane/tool/kdiff3_install.py +162 -0
  156. gitpane/tool/launch_services_text_editor.py +104 -0
  157. gitpane/tool/mergetool_recipe.py +84 -0
  158. gitpane/tool/system_editor_default.py +80 -0
  159. gitpane/tool/system_text_editor.py +44 -0
  160. gitpane/tool/system_tool.py +816 -0
  161. gitpane/tool/terminal_recipe.py +390 -0
  162. gitpane/transport/__init__.py +0 -0
  163. gitpane/transport/branch_exchange.py +1690 -0
  164. gitpane/transport/clone.py +1273 -0
  165. gitpane/transport/credential_read_retry.py +35 -0
  166. gitpane/transport/git_refusal.py +207 -0
  167. gitpane/transport/push_tracking.py +121 -0
  168. gitpane/transport/refusal_notice.py +225 -0
  169. gitpane/transport/remote.py +747 -0
  170. gitpane/transport/remote_config.py +544 -0
  171. gitpane/ui/__init__.py +0 -0
  172. gitpane/ui/console/__init__.py +0 -0
  173. gitpane/ui/console/error_dialog.py +332 -0
  174. gitpane/ui/console/operation_dialog.py +408 -0
  175. gitpane/ui/console/operation_output.py +91 -0
  176. gitpane/ui/console/operation_output_view.py +373 -0
  177. gitpane/ui/emoji/__init__.py +0 -0
  178. gitpane/ui/emoji/emoji_font.py +257 -0
  179. gitpane/ui/emoji/emoji_grid.py +125 -0
  180. gitpane/ui/emoji/emoji_window.py +154 -0
  181. gitpane/ui/emoji/file_action_icon.py +131 -0
  182. gitpane/ui/emoji/opentype_family_name.py +182 -0
  183. gitpane/ui/history_rewrite/__init__.py +0 -0
  184. gitpane/ui/history_rewrite/details.py +78 -0
  185. gitpane/ui/history_rewrite/preview.py +565 -0
  186. gitpane/ui/history_rewrite/preview_model.py +291 -0
  187. gitpane/ui/history_rewrite/question.py +87 -0
  188. gitpane/ui/history_rewrite/recovery_dialog.py +262 -0
  189. gitpane/ui/look/__init__.py +0 -0
  190. gitpane/ui/look/action_icon.py +504 -0
  191. gitpane/ui/look/application_icon.py +78 -0
  192. gitpane/ui/look/application_style.py +317 -0
  193. gitpane/ui/look/avatar_label.py +53 -0
  194. gitpane/ui/look/color_contrast.py +43 -0
  195. gitpane/ui/look/desktop_appearance_follower.py +118 -0
  196. gitpane/ui/look/fixed_font.py +21 -0
  197. gitpane/ui/look/gtk_settings.py +116 -0
  198. gitpane/ui/look/icon_density.py +16 -0
  199. gitpane/ui/look/reference_font.py +19 -0
  200. gitpane/ui/look/theme_color.py +63 -0
  201. gitpane/ui/look/theme_look.py +95 -0
  202. gitpane/ui/look/theme_palette.py +173 -0
  203. gitpane/ui/look/warning_icon.py +86 -0
  204. gitpane/ui/look/windows_control_look.py +250 -0
  205. gitpane/ui/reader/__init__.py +0 -0
  206. gitpane/ui/reader/commit_message.py +129 -0
  207. gitpane/ui/reader/commit_panel.py +378 -0
  208. gitpane/ui/reader/diff_panel.py +626 -0
  209. gitpane/ui/reader/file_action.py +138 -0
  210. gitpane/ui/reader/file_tree_panel.py +235 -0
  211. gitpane/ui/reader/reader_toolbar.py +56 -0
  212. gitpane/ui/reader/revision_tree.py +80 -0
  213. gitpane/ui/reader/search_result.py +165 -0
  214. gitpane/ui/reader/source_text.py +134 -0
  215. gitpane/ui/reader/source_view.py +151 -0
  216. gitpane/ui/reader/syntax_span.py +67 -0
  217. gitpane/ui/reference/__init__.py +0 -0
  218. gitpane/ui/reference/branch_selector.py +106 -0
  219. gitpane/ui/reference/checkout_dialog.py +419 -0
  220. gitpane/ui/reference/checkout_revision_dialog.py +182 -0
  221. gitpane/ui/reference/commit_summary.py +339 -0
  222. gitpane/ui/reference/create_branch_dialog.py +346 -0
  223. gitpane/ui/reference/create_tag_dialog.py +283 -0
  224. gitpane/ui/reference/delete_local_branch_dialog.py +323 -0
  225. gitpane/ui/reference/delete_tag_dialog.py +394 -0
  226. gitpane/ui/reference/form_opening.py +53 -0
  227. gitpane/ui/reference/reference_dialog.py +417 -0
  228. gitpane/ui/reference/reference_feedback.py +62 -0
  229. gitpane/ui/reference/reference_form.py +139 -0
  230. gitpane/ui/reference/rename_branch_dialog.py +179 -0
  231. gitpane/ui/reference/reset_branch_dialog.py +430 -0
  232. gitpane/ui/reference/reset_dialog.py +263 -0
  233. gitpane/ui/reference/revision_dialog.py +121 -0
  234. gitpane/ui/reference/revision_picker.py +227 -0
  235. gitpane/ui/reference/select_branches_dialog.py +199 -0
  236. gitpane/ui/reference/stash_dialog.py +171 -0
  237. gitpane/ui/repository/__init__.py +0 -0
  238. gitpane/ui/repository/graph_delegate.py +298 -0
  239. gitpane/ui/repository/history_avatar.py +130 -0
  240. gitpane/ui/repository/history_model.py +179 -0
  241. gitpane/ui/repository/repository_list_popup.py +319 -0
  242. gitpane/ui/repository/repository_window.py +2580 -0
  243. gitpane/ui/resource/ArrowDown.png +0 -0
  244. gitpane/ui/resource/ArrowUp.png +0 -0
  245. gitpane/ui/resource/BranchCheckout.png +0 -0
  246. gitpane/ui/resource/BranchCreate.png +0 -0
  247. gitpane/ui/resource/BranchDelete.png +0 -0
  248. gitpane/ui/resource/CopyToClipboard.png +0 -0
  249. gitpane/ui/resource/GitPane-16.png +0 -0
  250. gitpane/ui/resource/GitPane-24.png +0 -0
  251. gitpane/ui/resource/GitPane-256.png +0 -0
  252. gitpane/ui/resource/GitPane-32.png +0 -0
  253. gitpane/ui/resource/GitPane-48.png +0 -0
  254. gitpane/ui/resource/GitPane-64.png +0 -0
  255. gitpane/ui/resource/GitPane.ico +0 -0
  256. gitpane/ui/resource/NotoColorEmoji-OFL.txt +92 -0
  257. gitpane/ui/resource/NotoColorEmoji_WindowsCompatible.ttf +0 -0
  258. gitpane/ui/resource/Renamed.png +0 -0
  259. gitpane/ui/resource/ResetAnotherBranchToHere.png +0 -0
  260. gitpane/ui/resource/ResetCurrentBranchToHere.png +0 -0
  261. gitpane/ui/resource/Stash.png +0 -0
  262. gitpane/ui/resource/Tag.png +0 -0
  263. gitpane/ui/resource/TagCreate.png +0 -0
  264. gitpane/ui/resource/TagDelete.png +0 -0
  265. gitpane/ui/resource/Unmerged.png +0 -0
  266. gitpane/ui/resource/checkout.png +0 -0
  267. gitpane/ui/sequencer/__init__.py +0 -0
  268. gitpane/ui/sequencer/conflict_dialog.py +532 -0
  269. gitpane/ui/sequencer/merge_recovery_dialog.py +67 -0
  270. gitpane/ui/sequencer/rebase_dialog.py +461 -0
  271. gitpane/ui/sequencer/rebase_editor_window.py +220 -0
  272. gitpane/ui/sequencer/replay_conflict_prompt.py +105 -0
  273. gitpane/ui/sequencer/replay_dialog.py +501 -0
  274. gitpane/ui/setting/__init__.py +0 -0
  275. gitpane/ui/setting/account_page.py +724 -0
  276. gitpane/ui/setting/add_account_dialog.py +1005 -0
  277. gitpane/ui/setting/credential_manager_dialog.py +151 -0
  278. gitpane/ui/setting/git_setting_widget.py +388 -0
  279. gitpane/ui/setting/setting_block.py +19 -0
  280. gitpane/ui/setting/setting_dialog.py +750 -0
  281. gitpane/ui/setting/ssh_widget.py +232 -0
  282. gitpane/ui/setting/utility_page.py +240 -0
  283. gitpane/ui/staging/__init__.py +0 -0
  284. gitpane/ui/staging/staging_action.py +356 -0
  285. gitpane/ui/staging/staging_window.py +745 -0
  286. gitpane/ui/transport/__init__.py +0 -0
  287. gitpane/ui/transport/clone_dialog.py +879 -0
  288. gitpane/ui/transport/push_dialog.py +580 -0
  289. gitpane/ui/transport/push_rejection_prompt.py +100 -0
  290. gitpane/ui/transport/push_tracking_question.py +88 -0
  291. gitpane/ui/transport/remote_dialog.py +943 -0
  292. gitpane/ui/welcome/__init__.py +0 -0
  293. gitpane/ui/welcome/invalid_repository_prompt.py +114 -0
  294. gitpane/ui/welcome/welcome_window.py +1137 -0
  295. gitpane/ui/widget/__init__.py +0 -0
  296. gitpane/ui/widget/busy_indicator.py +106 -0
  297. gitpane/ui/widget/commit_date_text.py +55 -0
  298. gitpane/ui/widget/content_growth.py +262 -0
  299. gitpane/ui/widget/content_measure.py +99 -0
  300. gitpane/ui/widget/dialog_layout.py +668 -0
  301. gitpane/ui/widget/drop_down_wheel_filter.py +32 -0
  302. gitpane/ui/widget/flow_layout.py +110 -0
  303. gitpane/ui/widget/long_text.py +542 -0
  304. gitpane/ui/widget/mnemonic.py +13 -0
  305. gitpane/ui/widget/numbered_text_edit.py +127 -0
  306. gitpane/ui/widget/plain_label.py +15 -0
  307. gitpane/ui/widget/plain_tooltip.py +19 -0
  308. gitpane/ui/widget/refresh_advice.py +25 -0
  309. gitpane/ui/widget/result_label.py +227 -0
  310. gitpane/ui/widget/result_status_bar.py +53 -0
  311. gitpane/ui/widget/scrolled_text.py +69 -0
  312. gitpane/ui/widget/shortcut_text.py +31 -0
  313. gitpane/ui/widget/standard_icon_label.py +55 -0
  314. gitpane/ui/widget/system_shortcut.py +75 -0
  315. gitpane/ui/widget/unavailable_reason.py +29 -0
  316. gitpane/ui/widget/wheel_safe_drop_down.py +24 -0
  317. gitpane/ui/window_control/__init__.py +0 -0
  318. gitpane/ui/window_control/button_focus.py +42 -0
  319. gitpane/ui/window_control/compositor_answer.py +152 -0
  320. gitpane/ui/window_control/compositor_growth_cap.py +73 -0
  321. gitpane/ui/window_control/desktop_activation.py +203 -0
  322. gitpane/ui/window_control/dialog_modality.py +122 -0
  323. gitpane/ui/window_control/item_activation.py +42 -0
  324. gitpane/ui/window_control/modal_focus_relay.py +124 -0
  325. gitpane/ui/window_control/wayland_activation.py +695 -0
  326. gitpane/ui/window_control/window_placement.py +305 -0
  327. gitpane/vendor/DISTLIB-LICENSE.txt +284 -0
  328. gitpane/vendor/w32.exe +0 -0
  329. gitpane/vendor/w64-arm.exe +0 -0
  330. gitpane/vendor/w64.exe +0 -0
  331. gitpane/worktree/__init__.py +0 -0
  332. gitpane/worktree/command_sequence.py +110 -0
  333. gitpane/worktree/file_action_target.py +61 -0
  334. gitpane/worktree/file_menu_action.py +52 -0
  335. gitpane/worktree/historical_restore.py +361 -0
  336. gitpane/worktree/ignore_rule.py +266 -0
  337. gitpane/worktree/mutation.py +1539 -0
  338. gitpane/worktree/partial_patch.py +435 -0
  339. gitpane/worktree/staged_reset.py +447 -0
  340. gitpane/worktree/staging.py +1418 -0
  341. gitpane/worktree/trash.py +302 -0
  342. gitpane/worktree/worktree_file.py +406 -0
  343. gitpane/worktree/worktree_service.py +584 -0
  344. gitpane/worktree/worktree_snapshot.py +630 -0
  345. gitpane-1.0.0.dist-info/METADATA +141 -0
  346. gitpane-1.0.0.dist-info/RECORD +353 -0
  347. gitpane-1.0.0.dist-info/WHEEL +5 -0
  348. gitpane-1.0.0.dist-info/entry_points.txt +2 -0
  349. gitpane-1.0.0.dist-info/licenses/LICENSE +21 -0
  350. gitpane-1.0.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +109 -0
  351. gitpane-1.0.0.dist-info/licenses/src/gitpane/ui/resource/NotoColorEmoji-OFL.txt +92 -0
  352. gitpane-1.0.0.dist-info/licenses/src/gitpane/vendor/DISTLIB-LICENSE.txt +284 -0
  353. 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)