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,242 @@
1
+ """Read the references a repository window or a reference form shows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import replace
6
+ from typing import TYPE_CHECKING
7
+
8
+ from PySide6.QtCore import QTimer
9
+ from shiboken6 import isValid
10
+
11
+ from gitpane.config import FORM_OPENING_CHECK_TIMEOUT_MS, SharedText
12
+ from gitpane.history.file_history import WindowCapability
13
+ from gitpane.operation.diagnostic import Diagnostic
14
+ from gitpane.reference.stash_list import STASH_LIST_REFTABLE_CAUSE
15
+ from gitpane.ui.reference.form_opening import close_unseen_form, refuse_form_opening
16
+ from gitpane.ui.widget.refresh_advice import refresh_advice_text
17
+
18
+ if TYPE_CHECKING:
19
+ from collections.abc import Callable
20
+
21
+ from PySide6.QtWidgets import QWidget
22
+
23
+ from gitpane.controller.operation_policy import OperationPolicy
24
+ from gitpane.controller.session_registry import SessionRegistry
25
+ from gitpane.history.repository import GitRepository
26
+ from gitpane.history.repository_value import RepositoryContext, RequestIdentity
27
+ from gitpane.operation.diagnostic import DiagnosticLog
28
+ from gitpane.reference.reference_edit import ReferenceService
29
+ from gitpane.reference.reference_snapshot import ReferenceSnapshot
30
+
31
+ # How many times in a row a reference form is opened again because its window was refreshed before its opening check
32
+ # answered: a third refresh in a row means the repository keeps changing, and the form is refused with the advice to
33
+ # refresh instead of opening again for ever.
34
+ FORM_OPENING_RESTART_LIMIT = 2
35
+ # Operation named in the --log file when the stash list of a window could not be read.
36
+ STASH_LIST_DIAGNOSTIC_OPERATION = "Read stash list"
37
+
38
+
39
+ class ReferenceReadController:
40
+ """The reference reads of every window: the branch list and the stash list of a window, the references a form opens on, and the history built from the same capture."""
41
+
42
+ def __init__(self, *, session_registry_controller: SessionRegistry, operation_policy_controller: OperationPolicy, repository: GitRepository, reference: ReferenceService, diagnostic_log: DiagnosticLog | None) -> None:
43
+ """Receive the services and the lower controllers the reads use; the wiring builds it once the --log file is known, and the opening, the refresh, the replay, the stash and the forms read through it."""
44
+ self.session_registry_controller = session_registry_controller
45
+ self.operation_policy_controller = operation_policy_controller
46
+ self.repository = repository
47
+ self.reference = reference
48
+ self.diagnostic_log = diagnostic_log
49
+ self.reference_snapshot_by_session: dict[str, ReferenceSnapshot] = {}
50
+ self.reference_read_generation_by_session: dict[str, int] = {}
51
+ # The stash list cause last written to the --log file for a window: a cause that stays the same over many
52
+ # reference reads is written once, not on every refresh.
53
+ self.logged_stash_cause_by_session: dict[str, str] = {}
54
+ # What waits for the next reference capture of a window: the history that is built from that same capture.
55
+ self.reference_continuation_by_session: dict[str, Callable[[ReferenceSnapshot | None, Exception | None], None]] = {}
56
+ # How many times in a row the reference form being opened again in a window was restarted because its window
57
+ # moved on before its check answered; set only for the time of that new opening.
58
+ self.form_restart_count_by_session: dict[str, int] = {}
59
+
60
+ def read_references(self, session_id: str, *, previous: ReferenceSnapshot | None = None,
61
+ on_complete: Callable[[ReferenceSnapshot | None, Exception | None], None] | None = None) -> None:
62
+ """Refresh branch choices without replacing an already opened checkout intention.
63
+
64
+ A previous capture is shown again only after its reference files are checked unchanged. A continuation waits
65
+ for the capture this read, or a newer read of the same window, publishes; a newer continuation replaces it.
66
+ Every read, a reused one included, makes any older read of the window still in flight unable to publish.
67
+ Every read also reads the stash list again for the left panel, a reused capture included.
68
+ """
69
+ if not self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE):
70
+ return
71
+ session = self.session_registry_controller.session_by_id[session_id]
72
+ window = self.session_registry_controller.window_by_session[session_id]
73
+ generation = session.generation
74
+ read_generation = self.reference_read_generation_by_session.get(session_id, 0) + 1
75
+ self.reference_read_generation_by_session[session_id] = read_generation
76
+ if on_complete is not None:
77
+ self.reference_continuation_by_session[session_id] = on_complete
78
+ window.branch_selector.show_loading()
79
+
80
+ def complete(snapshot: ReferenceSnapshot | None, error: Exception | None) -> None:
81
+ """Discard late reads after closure or a newer history generation."""
82
+ if (session.is_closed or session.generation != generation or not isValid(window)
83
+ or self.reference_read_generation_by_session.get(session_id) != read_generation):
84
+ return
85
+ continuation = self.reference_continuation_by_session.pop(session_id, None)
86
+ if error is None and not self._publish_reference_snapshot(session_id, generation, read_generation, snapshot):
87
+ snapshot = None
88
+ error = ValueError("Git or the repository changed while reading references; refresh again")
89
+ if error is not None:
90
+ window.branch_selector.show_error(str(error))
91
+ window.statusBar().showMessage(str(error))
92
+ if continuation is not None:
93
+ continuation(snapshot, error)
94
+
95
+ if previous is None:
96
+ self.reference.read(session.repository_context, self.repository.git_filename, generation, complete, with_stash_list=True)
97
+ return
98
+ self.reference.read_display(session.repository_context, self.repository.git_filename, generation, complete, previous=previous, with_stash_list=True)
99
+
100
+ def read_form_references(self, session_id: str, dialog: QWidget, context: RepositoryContext, generation: int, complete: Callable, *,
101
+ menu_capture: ReferenceSnapshot | None, reopen: Callable[[ReferenceSnapshot | None], object]) -> None:
102
+ """Read the references a form opens on, showing the window's own capture again while its files are unchanged.
103
+
104
+ The window capture only draws the form: every submission captures its references again and checks them when
105
+ Git starts, so reusing it here never authorizes a write.
106
+
107
+ Any refresh of the window starts a new generation, and a form opened on an older one can no longer write. When
108
+ such a refresh, or a change of Git or of the management right, comes before the answer, the form, not shown
109
+ yet, is closed and opened again through ``reopen`` on the menu's capture moved to the window's new generation,
110
+ so it gets its own new check; ``complete`` is not called. Such refreshes come from GitPane itself (another
111
+ program moved HEAD, an operation finished in another window), never from a hand quick enough to beat the check,
112
+ so the user is shown nothing about them. The new check still compares the references with the menu's capture,
113
+ so a refresh that found them changed refuses the form as usual. Past FORM_OPENING_RESTART_LIMIT restarts in a
114
+ row, the form is refused with the advice to refresh instead of opening again.
115
+
116
+ Every check ends: ``complete`` shows the form or refuses it, a form it leaves hidden (its filling raised, or it
117
+ stopped being current) is closed unseen, and a check with no answer within FORM_OPENING_CHECK_TIMEOUT_MS
118
+ refuses the form, so the next click opens a new one. A second click while the form waits gives back the waiting
119
+ form and starts no other check: the wait lasts one reference read, and a second form or read would only cross
120
+ the first.
121
+ """
122
+ git_filename = self.repository.git_filename
123
+ restart_count = self.form_restart_count_by_session.pop(session_id, 0)
124
+ check_answered = False
125
+
126
+ def answered(snapshot: ReferenceSnapshot | None, error: Exception | None) -> None:
127
+ """Open again a form not shown yet whose window moved on; otherwise hand the answer to the form's own checks."""
128
+ nonlocal check_answered
129
+ check_answered = True
130
+ session = self.session_registry_controller.session_by_id.get(session_id)
131
+ if (isValid(dialog) and not dialog.closed and session is not None and not session.is_closed
132
+ and not self._form_authority_kept(session_id, generation, context, git_filename)):
133
+ restart(session.generation)
134
+ return
135
+ try:
136
+ complete(snapshot, error)
137
+ finally:
138
+ if form_waits():
139
+ close_unseen_form(dialog)
140
+
141
+ def restart(current_generation: int) -> None:
142
+ """Close the outdated form unseen and open it again under the window's current generation, within the restart limit."""
143
+ if restart_count >= FORM_OPENING_RESTART_LIMIT:
144
+ refuse_form_opening(dialog, refresh_advice_text(SharedText.REFERENCE_CHANGED_DURING_READING))
145
+ return
146
+ close_unseen_form(dialog)
147
+ capture = replace(menu_capture, generation=current_generation) if menu_capture is not None else None
148
+ self.form_restart_count_by_session[session_id] = restart_count + 1
149
+ try:
150
+ reopen(capture)
151
+ finally:
152
+ # An opening that stops before its own check (the right to manage lost meanwhile) leaves no count behind.
153
+ self.form_restart_count_by_session.pop(session_id, None)
154
+
155
+ def form_waits() -> bool:
156
+ """Tell whether the form is still alive and open without ever having been shown."""
157
+ return isValid(dialog) and not dialog.closed and not dialog.isVisible()
158
+
159
+ def give_up() -> None:
160
+ """Refuse a form whose check gave no answer within its limit."""
161
+ if not check_answered and form_waits():
162
+ refuse_form_opening(dialog, SharedText.FORM_CHECK_UNANSWERED.value)
163
+
164
+ # The form is the timer's context object: the timer dies with the form, and a form closed meanwhile is left as it is.
165
+ QTimer.singleShot(FORM_OPENING_CHECK_TIMEOUT_MS, dialog, give_up)
166
+ self.reference.read_display(context, git_filename, generation, answered, previous=self.reference_snapshot_by_session.get(session_id))
167
+
168
+ def form_is_current(self, session_id: str, generation: int, dialog: QWidget, dialog_by_session: dict[str, QWidget],
169
+ context: RepositoryContext, git_filename: str) -> bool:
170
+ """Tell whether a result read for one opened form may still reach it.
171
+
172
+ The form must be alive and still be this window's form, and the window must keep the generation, repository,
173
+ Git and management authority the form was opened with.
174
+ """
175
+ session = self.session_registry_controller.session_by_id.get(session_id)
176
+ return (isValid(dialog) and not dialog.closed and dialog_by_session.get(session_id) is dialog and session is not None and not session.is_closed
177
+ and self._form_authority_kept(session_id, generation, context, git_filename))
178
+
179
+ def _form_authority_kept(self, session_id: str, generation: int, context: RepositoryContext, git_filename: str) -> bool:
180
+ """Tell whether an open window still has the generation, repository, Git and management right a form was opened with."""
181
+ session = self.session_registry_controller.session_by_id[session_id]
182
+ return (session.generation == generation and session.repository_context == context and self.repository.git_filename == git_filename
183
+ and self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE))
184
+
185
+ def _publish_reference_snapshot(self, session_id: str, generation: int, read_generation: int, snapshot: ReferenceSnapshot) -> bool:
186
+ """Show one reference capture in its window, only while that window, its authority and this very read are current.
187
+
188
+ The capture must belong to the window's repository, to the Git in use and to the generation the read was made
189
+ for; otherwise nothing is shown and the caller reports the read as failed. The branch list and the Stashes node
190
+ of the left panel are updated together from the same capture.
191
+ """
192
+ session = self.session_registry_controller.session_by_id.get(session_id)
193
+ window = self.session_registry_controller.window_by_session.get(session_id)
194
+ if (session is None or window is None or session.is_closed or not isValid(window) or not self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE)
195
+ or session.generation != generation or self.reference_read_generation_by_session.get(session_id) != read_generation
196
+ or snapshot.context != session.repository_context or snapshot.git_filename != self.repository.git_filename
197
+ or snapshot.generation != generation):
198
+ return False
199
+ self.reference_snapshot_by_session[session_id] = snapshot
200
+ window.branch_selector.populate(snapshot)
201
+ if snapshot.stash_list is not None:
202
+ window.show_stashes(snapshot.stash_list)
203
+ self._log_stash_list_cause(session_id, snapshot)
204
+ return True
205
+
206
+ def _log_stash_list_cause(self, session_id: str, snapshot: ReferenceSnapshot) -> None:
207
+ """Write why a window's stash list could not be read to the --log file, once per new cause.
208
+
209
+ The reftable format is supported, not a failure, so its cause stays on the Stashes node only. Repeating the
210
+ same cause on every refresh would bury the real Git failures the log is read for.
211
+ """
212
+ cause = snapshot.stash_list.cause if snapshot.stash_list is not None else ""
213
+ previous_cause = self.logged_stash_cause_by_session.get(session_id, "")
214
+ self.logged_stash_cause_by_session[session_id] = cause
215
+ if not cause or cause == STASH_LIST_REFTABLE_CAUSE or cause == previous_cause or self.diagnostic_log is None:
216
+ return
217
+ self.diagnostic_log.write(Diagnostic(STASH_LIST_DIAGNOSTIC_OPERATION, snapshot.context.repository_dir, "", [], cause).prepare())
218
+
219
+ def read_history_with_references(self, session_id: str, identity: RequestIdentity, committed_parent_oid: str | None = None, *,
220
+ previous: ReferenceSnapshot | None = None,
221
+ before_history: Callable[[ReferenceSnapshot | None, Exception | None], None] | None = None) -> None:
222
+ """Read the branch list, then build the history of this request from that very capture.
223
+
224
+ A window that cannot manage its repository has no branch list, so its history reads its own references. When
225
+ the reference capture fails, the branch list shows the error and the history still reads its own references,
226
+ so an empty graph is never the result of a failed branch list. ``previous`` is a capture that may be shown
227
+ again after its files are checked unchanged; ``before_history`` decides, from the capture, what else the
228
+ refresh must read before the history starts.
229
+ """
230
+ if not self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE):
231
+ self.repository.read_snapshot(identity, committed_parent_oid)
232
+ return
233
+
234
+ def start_history(snapshot: ReferenceSnapshot | None, error: Exception | None) -> None:
235
+ """Start the history only for the request that asked for it, never for a replaced one."""
236
+ if self.session_registry_controller.identity_by_session.get(session_id) != identity:
237
+ return
238
+ if before_history is not None:
239
+ before_history(snapshot, error)
240
+ self.repository.read_snapshot(identity, committed_parent_oid, reference_snapshot=snapshot if error is None else None)
241
+
242
+ self.read_references(session_id, previous=previous, on_complete=start_history)