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
gitpane/application.py ADDED
@@ -0,0 +1,684 @@
1
+ """Wire the services of GitPane to its controllers, then run its event loop."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import functools
6
+ import os
7
+ import signal
8
+ import sys
9
+ from typing import TYPE_CHECKING
10
+
11
+ from PySide6.QtCore import QObject, QSettings, QStandardPaths, QTimer
12
+ from PySide6.QtGui import QGuiApplication
13
+ from PySide6.QtWidgets import QApplication
14
+
15
+ from gitpane.account.authentication import AuthenticationService
16
+ from gitpane.account.credential_manager import (
17
+ CredentialManagerLocator,
18
+ ignore_line,
19
+ )
20
+ from gitpane.account.credential_manager_install import session_gcm_archive_architecture
21
+ from gitpane.account.keyring_reader import read_session_keyring_state
22
+ from gitpane.account.ssh import SshClientFinder, SshService
23
+ from gitpane.commit.commit_push import CommitPushService
24
+ from gitpane.config import (
25
+ APPLICATION_NAME,
26
+ APPLICATION_VERSION,
27
+ APPLICATION_VERSION_LINE,
28
+ SharedText,
29
+ )
30
+ from gitpane.controller.account_controller import AccountController
31
+ from gitpane.controller.clone_controller import CloneController
32
+ from gitpane.controller.close_readiness import CloseReadiness
33
+ from gitpane.controller.commit_timing_controller import CommitTimingController
34
+ from gitpane.controller.conflict_controller import ConflictController
35
+ from gitpane.controller.console_registry import ConsoleRegistry
36
+ from gitpane.controller.emoji_window_controller import EmojiWindowController
37
+ from gitpane.controller.file_action_controller import (
38
+ STAGING_FOLDER_TOOL,
39
+ STAGING_TEXT_EDITOR_TOOL,
40
+ FileActionController,
41
+ )
42
+ from gitpane.controller.history_read_controller import HistoryReadController
43
+ from gitpane.controller.history_rewrite_controller import HistoryRewriteController
44
+ from gitpane.controller.navigation_controller import (
45
+ NavigationController,
46
+ )
47
+ from gitpane.controller.opening_controller import OpeningController
48
+ from gitpane.controller.operation_policy import OperationPolicy
49
+ from gitpane.controller.rebase_controller import RebaseController
50
+ from gitpane.controller.rebase_editor_controller import RebaseEditorController
51
+ from gitpane.controller.reference_form_controller import (
52
+ ReferenceFormController,
53
+ )
54
+ from gitpane.controller.reference_read_controller import ReferenceReadController
55
+ from gitpane.controller.refresh_controller import RefreshController
56
+ from gitpane.controller.replay_controller import ReplayController
57
+ from gitpane.controller.session_registry import RepositorySession, SessionRegistry
58
+ from gitpane.controller.setting_controller import SettingController
59
+ from gitpane.controller.shutdown_controller import ShutdownController
60
+ from gitpane.controller.staging_controller import StagingController
61
+ from gitpane.controller.staging_session_registry import StagingSessionRegistry
62
+ from gitpane.controller.stash_controller import (
63
+ StashController,
64
+ )
65
+ from gitpane.controller.transport_controller import TransportController
66
+ from gitpane.controller.welcome_controller import WelcomeController
67
+ from gitpane.controller.window_activation import WindowActivationFilter
68
+ from gitpane.controller.worktree_state_controller import WorktreeStateController
69
+ from gitpane.git_config.git_setting import GitSettingService
70
+ from gitpane.history.avatar import AvatarService
71
+ from gitpane.history.file_history import FileHistoryAnchor, WindowCapability
72
+ from gitpane.history.repository import GitRepository
73
+ from gitpane.history.repository_value import (
74
+ RepositoryContext,
75
+ RequestIdentity,
76
+ )
77
+ from gitpane.history_rewrite.recovery import HISTORY_REWRITE_STORAGE_BASENAME
78
+ from gitpane.operation.diagnostic import DiagnosticLog
79
+ from gitpane.operation.repository_operation import (
80
+ RepositoryOperationService,
81
+ )
82
+ from gitpane.preference.preference_location import PREFERENCE_FILE_LOG_TEXT, user_preference_filename
83
+ from gitpane.preference.preference_store import (
84
+ PreferenceStore,
85
+ )
86
+ from gitpane.process.process_log import ProcessLogWriter
87
+ from gitpane.reference.reference_edit import (
88
+ BranchMode,
89
+ ReferenceService,
90
+ )
91
+ from gitpane.reference.stash import StashService
92
+ from gitpane.sequencer.conflict import (
93
+ ConflictService,
94
+ )
95
+ from gitpane.sequencer.mergetool import MergetoolService, TemporaryMergeStore
96
+ from gitpane.sequencer.rebase import RebaseService
97
+ from gitpane.sequencer.replay import ReplayService
98
+ from gitpane.submodule.submodule_discovery import SubmoduleService
99
+ from gitpane.system.application_identity import declare_application_identity
100
+ from gitpane.system.command_line import command_line_parser
101
+ from gitpane.system.desktop_shortcut import create_installed_desktop_shortcut
102
+ from gitpane.system.linux_distribution import session_distribution_family, session_distribution_release
103
+ from gitpane.system.qt_folder import writable_folder
104
+ from gitpane.system.ready_channel_watch import ReadyChannelWatch
105
+ from gitpane.system.terminal_detach import (
106
+ detached_child_command,
107
+ detaches_from_terminal,
108
+ interrupt_stops_gitpane,
109
+ relaunch_detached,
110
+ running_interpreter_options,
111
+ take_ready_channel,
112
+ )
113
+ from gitpane.tool.difftool import DifftoolService
114
+ from gitpane.tool.editor import EditorService
115
+ from gitpane.tool.git_executable import GitExecutableValidator, terminal_git_filename
116
+ from gitpane.tool.system_editor_default import SystemEditorDefault
117
+ from gitpane.tool.system_text_editor import session_system_text_editor_id
118
+ from gitpane.tool.system_tool import SystemToolService
119
+ from gitpane.tool.terminal_recipe import TerminalRegistry, detect_terminal
120
+ from gitpane.transport.branch_exchange import TransportService
121
+ from gitpane.transport.clone import CloneService
122
+ from gitpane.transport.remote import RemoteService
123
+ from gitpane.ui.look.application_icon import install_application_icon
124
+ from gitpane.ui.look.application_style import configure_system_style
125
+ from gitpane.ui.look.desktop_appearance_follower import install_desktop_appearance_follower
126
+ from gitpane.ui.look.theme_palette import install_light_field_palette
127
+ from gitpane.ui.repository.repository_window import RepositoryWindow
128
+ from gitpane.ui.staging.staging_window import StagingWindow
129
+ from gitpane.ui.widget.drop_down_wheel_filter import DropDownWheelFilter
130
+ from gitpane.ui.widget.refresh_advice import refresh_advice_text
131
+ from gitpane.ui.window_control.desktop_activation import desktop_activation
132
+ from gitpane.ui.window_control.modal_focus_relay import ModalFocusRelay, modal_window_activator
133
+ from gitpane.ui.window_control.window_placement import NewWindowPlacement
134
+ from gitpane.worktree.historical_restore import HistoricalRestoreService
135
+ from gitpane.worktree.mutation import MutationService
136
+ from gitpane.worktree.staging import StagingSession
137
+ from gitpane.worktree.worktree_service import WorktreeService
138
+ from gitpane.worktree.worktree_snapshot import WorktreeKey
139
+
140
+ if TYPE_CHECKING:
141
+ from collections.abc import Callable
142
+
143
+ from gitpane.sequencer.merge_recovery import MergeRecoveryStore
144
+
145
+
146
+ class ApplicationWiring(QObject):
147
+ """Build the services of GitPane, then connect them to its windows."""
148
+
149
+ def __init__(self, application: QApplication, preference: PreferenceStore, repository: GitRepository, diagnostic_log: DiagnosticLog | None = None,
150
+ avatar_service: AvatarService | None = None, system_tool: SystemToolService | None = None, terminal_registry: TerminalRegistry | None = None,
151
+ difftool: DifftoolService | None = None, merge_recovery: MergeRecoveryStore | None = None, rebase: RebaseService | None = None,
152
+ leave_event_loop: Callable[[], None] | None = None, system_editor_default: SystemEditorDefault | None = None) -> None:
153
+ """Inject external services and keep window lifetimes explicit.
154
+
155
+ system_editor_default keeps the text editor the system names for plain text; by default it reads the session's
156
+ files of this system, only when asked, so building the wiring reads nothing, and writes an unexpected error of its read to the --log journal.
157
+
158
+ leave_event_loop ends the application once everything has drained. Only the caller that runs the event loop knows
159
+ that a loop runs to be left, so it gives QApplication.exit there; the default, QApplication.quit, does nothing
160
+ while no loop runs, which keeps a wiring driven without one, as in the tests, from stopping later loops.
161
+ """
162
+ super().__init__(application)
163
+ self.session_registry_controller = SessionRegistry()
164
+ self.application = application
165
+ self.leave_event_loop = leave_event_loop if leave_event_loop is not None else application.quit
166
+ self.preference = preference
167
+ self.repository = repository
168
+ # The distribution is read once here and handed to everything that says how to install a tool on it.
169
+ distribution_family = session_distribution_family()
170
+ distribution_release = session_distribution_release()
171
+ self.difftool = difftool if difftool is not None else DifftoolService(repository, parent=self, activation=desktop_activation, distribution_family=distribution_family,
172
+ distribution_release=distribution_release)
173
+ self.worktree_service = WorktreeService(repository.runner, self)
174
+ self.editor = EditorService(self)
175
+ # The operation policy reads this service, so it is built after it: the lambdas read the policy when a write is submitted.
176
+ self.repository_operation = RepositoryOperationService(self.worktree_service, self, allows=lambda intent: self.operation_policy_controller.operation_allowed(intent),
177
+ explains=lambda intent: self.operation_policy_controller.operation_refusal(intent))
178
+ self.rebase = rebase if rebase is not None else RebaseService(self.worktree_service, self, repository_operation=self.repository_operation, editor=self.editor)
179
+ self.operation_policy_controller = OperationPolicy(session_registry_controller=self.session_registry_controller, repository=self.repository, repository_operation=self.repository_operation, rebase=self.rebase)
180
+ self.replay = ReplayService(self.worktree_service, self, repository_operation=self.repository_operation)
181
+ # Connected before the merge tool service is built: it connects itself to this signal while it is built, and Qt
182
+ # calls the slots of a signal in connection order, so these three keep running before its own. The rebase, replay
183
+ # and worktree state controllers are built after that service, so their slots are read by lambdas when the signal comes.
184
+ self.repository_operation.changed.connect(lambda: self.rebase_controller.rebase_actions_changed())
185
+ self.repository_operation.changed.connect(lambda: self.replay_controller.replay_actions_changed())
186
+ self.repository_operation.changed.connect(lambda: self.worktree_state_controller.merge_banner_actions_changed())
187
+ self.reference = ReferenceService(self.worktree_service, self.repository_operation, refresh_advice=refresh_advice_text)
188
+ self.stash = StashService(self.worktree_service, self.repository_operation)
189
+ self.remote = RemoteService(self.worktree_service, self.repository_operation)
190
+ self.mutation = MutationService(repository.runner, self.worktree_service, self, repository_operation=self.repository_operation)
191
+ self.historical_restore = HistoricalRestoreService(self.mutation, self)
192
+ self.git_setting_service = GitSettingService(repository.runner, self.mutation, self)
193
+ # The search of the credential manager writes its --log lines from the background task of each operation.
194
+ log_line = diagnostic_log.write_line if diagnostic_log is not None else ignore_line
195
+ credential_manager = CredentialManagerLocator(sys.platform, os.environ, log_line=log_line, distribution_family=distribution_family,
196
+ architecture=session_gcm_archive_architecture())
197
+ self.authentication = AuthenticationService(preference, self, credential_manager=credential_manager,
198
+ current_git_filename=lambda: terminal_git_filename(self.repository.git_filename))
199
+ self.ssh_client_finder = SshClientFinder(lambda: terminal_git_filename(self.repository.git_filename), sys.platform, os.environ, log_line=log_line)
200
+ self.ssh = SshService(preference, repository.runner, self, repository_operation=self.repository_operation, detect_client=self.ssh_client_finder.find)
201
+ self.transport = TransportService(self.remote, self.authentication, self.ssh, self.repository_operation, repository.runner,
202
+ self, is_current=self.operation_policy_controller.operation_allowed, log=diagnostic_log)
203
+ # Connected before the commit-and-push service is built: it connects itself to this signal while it is built, and
204
+ # Qt calls the slots of a signal in connection order, so these two keep running before its own. The end of a remote
205
+ # deletion comes first, as it did when the end of a transfer called it first; it ignores any other transfer, whose
206
+ # operation is never one a deletion form submitted. The reference form and transport controllers are built after
207
+ # that service, so their slots are read by lambdas when the signal comes.
208
+ self.transport.finished.connect(lambda outcome: self.reference_form_controller.delete_finished(outcome))
209
+ self.transport.finished.connect(lambda outcome: self.transport_controller.transport_finished(outcome))
210
+ self.commit_push = CommitPushService(self.transport, current_operation=self.operation_policy_controller.commit_push_operation, parent=self)
211
+ self.clone = CloneService(repository.runner, self.repository_operation, self.authentication, self.ssh, self, is_current=self.operation_policy_controller.clone_is_current, log=diagnostic_log)
212
+ self.staging_session_registry_controller = StagingSessionRegistry(repository=self.repository, worktree_service=self.worktree_service, rebase=self.rebase, mutation=self.mutation,
213
+ wire_staging_session=self.wire_staging_session, window_factory=StagingWindow, parent=self)
214
+ self.window_activation_controller = WindowActivationFilter(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller,
215
+ staging_session_registry_controller=self.staging_session_registry_controller, application=self.application,
216
+ preference=self.preference, parent=self)
217
+ self.console_registry_controller = ConsoleRegistry()
218
+ self.conflict = ConflictService()
219
+ self.mergetool = MergetoolService(self.conflict, self.mutation, self, temporary_store=TemporaryMergeStore(merge_recovery), activation=desktop_activation,
220
+ distribution_family=distribution_family, distribution_release=distribution_release)
221
+ self.diagnostic_log = diagnostic_log
222
+ self.reference_read_controller = ReferenceReadController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller, repository=self.repository, reference=self.reference,
223
+ diagnostic_log=self.diagnostic_log)
224
+ self.process_log_writer: ProcessLogWriter | None = None
225
+ # The condition is on the open file, not on the option: without a log to write into, no subscriber must be
226
+ # added to the path every single process goes through. The attribute keeps the writer alive with this owner.
227
+ if diagnostic_log is not None and diagnostic_log.stream is not None:
228
+ self.process_log_writer = ProcessLogWriter(diagnostic_log, [repository.runner, self.authentication.runner])
229
+ self.avatar_service = avatar_service if avatar_service is not None else AvatarService(parent=self)
230
+ self.system_tool = system_tool if system_tool is not None else SystemToolService(parent=self, activation=desktop_activation)
231
+ # Git Bash is searched from the Git and the utilities folder GitPane uses, both read at each search.
232
+ self.terminal_registry = terminal_registry if terminal_registry is not None else TerminalRegistry(
233
+ preference, lambda: detect_terminal(git_filename=terminal_git_filename(self.repository.git_filename), git_utility_dir=str(self.preference.value_by_key["git_utility_dir"])),
234
+ parent=self)
235
+ self.validator = GitExecutableValidator(repository.runner)
236
+ self.submodule = SubmoduleService(self.worktree_service, self.repository_operation, self.authentication, self.ssh, diagnostic_log)
237
+ self.refresh_controller = RefreshController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller,
238
+ staging_session_registry_controller=self.staging_session_registry_controller, reference_read_controller=self.reference_read_controller, repository=self.repository, submodule=self.submodule,
239
+ parent=self)
240
+ self.history_rewrite_controller = HistoryRewriteController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller,
241
+ reference_read_controller=self.reference_read_controller, console_registry_controller=self.console_registry_controller,
242
+ refresh_controller=self.refresh_controller, repository=self.repository, worktree_service=self.worktree_service,
243
+ repository_operation=self.repository_operation,
244
+ storage_root_dir=os.path.join(writable_folder(QStandardPaths.StandardLocation.AppLocalDataLocation, sys.platform), HISTORY_REWRITE_STORAGE_BASENAME),
245
+ preference=self.preference, diagnostic_log=self.diagnostic_log, parent=self)
246
+ self.worktree_state_controller = WorktreeStateController(session_registry_controller=self.session_registry_controller, staging_session_registry_controller=self.staging_session_registry_controller,
247
+ reference_read_controller=self.reference_read_controller, refresh_controller=self.refresh_controller, repository_operation=self.repository_operation, conflict=self.conflict,
248
+ mergetool=self.mergetool, parent=self)
249
+ self.history_read_controller = HistoryReadController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller, reference_read_controller=self.reference_read_controller,
250
+ refresh_controller=self.refresh_controller, preference=self.preference, repository=self.repository, clone=self.clone, diagnostic_log=self.diagnostic_log, system_tool=self.system_tool,
251
+ parent=self)
252
+ self.commit_timing_controller = CommitTimingController(session_registry_controller=self.session_registry_controller, staging_session_registry_controller=self.staging_session_registry_controller,
253
+ worktree_state_controller=self.worktree_state_controller, history_read_controller=self.history_read_controller, refresh_controller=self.refresh_controller,
254
+ reference_read_controller=self.reference_read_controller, repository=self.repository, authentication=self.authentication,
255
+ worktree_service=self.worktree_service, repository_operation=self.repository_operation, diagnostic_log=self.diagnostic_log, parent=self)
256
+ self.system_editor_default = system_editor_default if system_editor_default is not None else SystemEditorDefault(session_system_text_editor_id, parent=self, log_line=log_line)
257
+ self.setting_controller = SettingController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller,
258
+ staging_session_registry_controller=self.staging_session_registry_controller, window_activation_controller=self.window_activation_controller, history_read_controller=self.history_read_controller,
259
+ preference=self.preference, repository=self.repository, git_setting_service=self.git_setting_service, ssh=self.ssh, mergetool=self.mergetool, diagnostic_log=self.diagnostic_log,
260
+ terminal_registry=self.terminal_registry, validator=self.validator, system_editor_default=self.system_editor_default,
261
+ distribution_family=distribution_family, distribution_release=distribution_release,
262
+ desktop_shortcut=functools.partial(create_installed_desktop_shortcut, application_module_name=application_module_name()), parent=self)
263
+ self.account_controller = AccountController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller, setting_controller=self.setting_controller, repository=self.repository,
264
+ worktree_service=self.worktree_service, remote=self.remote, authentication=self.authentication, diagnostic_log=self.diagnostic_log,
265
+ read_keyring=read_session_keyring_state)
266
+ self.clone_controller = CloneController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller, account_controller=self.account_controller, application=self.application,
267
+ preference=self.preference, repository=self.repository, authentication=self.authentication, clone=self.clone, diagnostic_log=self.diagnostic_log, parent=self)
268
+ self.opening_controller = OpeningController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller,
269
+ staging_session_registry_controller=self.staging_session_registry_controller, window_activation_controller=self.window_activation_controller,
270
+ reference_read_controller=self.reference_read_controller, refresh_controller=self.refresh_controller, history_read_controller=self.history_read_controller,
271
+ setting_controller=self.setting_controller, clone_controller=self.clone_controller, preference=self.preference, repository=self.repository, authentication=self.authentication, clone=self.clone,
272
+ diagnostic_log=self.diagnostic_log, wire_repository_window=self.wire_repository_window, parent=self)
273
+ self.welcome_controller = WelcomeController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller, window_activation_controller=self.window_activation_controller,
274
+ setting_controller=self.setting_controller, clone_controller=self.clone_controller, opening_controller=self.opening_controller, preference=self.preference, repository=self.repository,
275
+ diagnostic_log=self.diagnostic_log)
276
+ self.emoji_window_controller = EmojiWindowController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller, application=self.application, preference=self.preference,
277
+ diagnostic_log=self.diagnostic_log)
278
+ self.file_action_controller = FileActionController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller,
279
+ staging_session_registry_controller=self.staging_session_registry_controller, refresh_controller=self.refresh_controller, history_read_controller=self.history_read_controller,
280
+ opening_controller=self.opening_controller, preference=self.preference, repository=self.repository, difftool=self.difftool, worktree_service=self.worktree_service,
281
+ historical_restore=self.historical_restore, diagnostic_log=self.diagnostic_log, system_tool=self.system_tool,
282
+ system_editor_default=self.system_editor_default)
283
+ self.staging_controller = StagingController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller,
284
+ staging_session_registry_controller=self.staging_session_registry_controller, window_activation_controller=self.window_activation_controller, refresh_controller=self.refresh_controller,
285
+ worktree_state_controller=self.worktree_state_controller, history_read_controller=self.history_read_controller, opening_controller=self.opening_controller,
286
+ emoji_window_controller=self.emoji_window_controller, file_action_controller=self.file_action_controller, preference=self.preference, repository=self.repository, diagnostic_log=self.diagnostic_log)
287
+ self.transport_controller = TransportController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller,
288
+ staging_session_registry_controller=self.staging_session_registry_controller, console_registry_controller=self.console_registry_controller,
289
+ reference_read_controller=self.reference_read_controller, refresh_controller=self.refresh_controller, account_controller=self.account_controller, clone_controller=self.clone_controller,
290
+ preference=self.preference, repository=self.repository, worktree_service=self.worktree_service, repository_operation=self.repository_operation, remote=self.remote,
291
+ authentication=self.authentication, transport=self.transport, commit_push=self.commit_push, diagnostic_log=self.diagnostic_log, parent=self)
292
+ self.reference_form_controller = ReferenceFormController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller,
293
+ console_registry_controller=self.console_registry_controller, reference_read_controller=self.reference_read_controller, refresh_controller=self.refresh_controller,
294
+ history_read_controller=self.history_read_controller, staging_controller=self.staging_controller, transport_controller=self.transport_controller, preference=self.preference,
295
+ repository=self.repository, worktree_service=self.worktree_service, reference=self.reference, remote=self.remote, transport=self.transport, diagnostic_log=self.diagnostic_log)
296
+ self.replay_controller = ReplayController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller,
297
+ staging_session_registry_controller=self.staging_session_registry_controller, console_registry_controller=self.console_registry_controller, reference_read_controller=self.reference_read_controller,
298
+ refresh_controller=self.refresh_controller, worktree_state_controller=self.worktree_state_controller, history_read_controller=self.history_read_controller,
299
+ reference_form_controller=self.reference_form_controller, repository=self.repository, repository_operation=self.repository_operation, replay=self.replay, mergetool=self.mergetool,
300
+ diagnostic_log=self.diagnostic_log, parent=self)
301
+ self.rebase_editor_controller = RebaseEditorController(editor=self.editor, worktree_service=self.worktree_service, parent=self)
302
+ self.rebase_controller = RebaseController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller,
303
+ staging_session_registry_controller=self.staging_session_registry_controller, window_activation_controller=self.window_activation_controller,
304
+ console_registry_controller=self.console_registry_controller, reference_read_controller=self.reference_read_controller, refresh_controller=self.refresh_controller,
305
+ worktree_state_controller=self.worktree_state_controller, history_read_controller=self.history_read_controller, staging_controller=self.staging_controller,
306
+ replay_controller=self.replay_controller, rebase_editor_controller=self.rebase_editor_controller, preference=self.preference, repository=self.repository,
307
+ worktree_service=self.worktree_service, repository_operation=self.repository_operation, rebase=self.rebase, mergetool=self.mergetool, diagnostic_log=self.diagnostic_log, parent=self)
308
+ self.stash_controller = StashController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller, staging_session_registry_controller=self.staging_session_registry_controller,
309
+ window_activation_controller=self.window_activation_controller, refresh_controller=self.refresh_controller,
310
+ replay_controller=self.replay_controller, preference=self.preference, repository=self.repository, worktree_service=self.worktree_service, stash=self.stash, diagnostic_log=self.diagnostic_log,
311
+ parent=self)
312
+ self.conflict_controller = ConflictController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller,
313
+ staging_session_registry_controller=self.staging_session_registry_controller, window_activation_controller=self.window_activation_controller, refresh_controller=self.refresh_controller,
314
+ worktree_state_controller=self.worktree_state_controller, setting_controller=self.setting_controller,
315
+ reference_form_controller=self.reference_form_controller, replay_controller=self.replay_controller, rebase_controller=self.rebase_controller, stash_controller=self.stash_controller,
316
+ preference=self.preference, repository=self.repository, worktree_service=self.worktree_service, repository_operation=self.repository_operation, rebase=self.rebase, reference=self.reference,
317
+ stash=self.stash, mutation=self.mutation, conflict=self.conflict, mergetool=self.mergetool, diagnostic_log=self.diagnostic_log, parent=self)
318
+ self.close_readiness_controller = CloseReadiness(session_registry_controller=self.session_registry_controller, console_registry_controller=self.console_registry_controller, reference_form_controller=self.reference_form_controller,
319
+ replay_controller=self.replay_controller, rebase_controller=self.rebase_controller, rebase_editor_controller=self.rebase_editor_controller, editor=self.editor,
320
+ repository_operation=self.repository_operation, rebase=self.rebase, replay=self.replay,
321
+ mutation=self.mutation, mergetool=self.mergetool, parent=self)
322
+ self.navigation_controller = NavigationController(session_registry_controller=self.session_registry_controller, operation_policy_controller=self.operation_policy_controller,
323
+ staging_session_registry_controller=self.staging_session_registry_controller, reference_read_controller=self.reference_read_controller, refresh_controller=self.refresh_controller,
324
+ history_read_controller=self.history_read_controller, account_controller=self.account_controller, opening_controller=self.opening_controller, transport_controller=self.transport_controller,
325
+ replay_controller=self.replay_controller, rebase_controller=self.rebase_controller, close_readiness_controller=self.close_readiness_controller, preference=self.preference,
326
+ repository=self.repository, reference=self.reference, authentication=self.authentication, diagnostic_log=self.diagnostic_log, submodule=self.submodule, parent=self)
327
+ self.shutdown_controller = ShutdownController(session_registry_controller=self.session_registry_controller, staging_session_registry_controller=self.staging_session_registry_controller,
328
+ window_activation_controller=self.window_activation_controller, reference_read_controller=self.reference_read_controller, refresh_controller=self.refresh_controller,
329
+ history_read_controller=self.history_read_controller, account_controller=self.account_controller,
330
+ emoji_window_controller=self.emoji_window_controller, transport_controller=self.transport_controller, reference_form_controller=self.reference_form_controller,
331
+ replay_controller=self.replay_controller, rebase_controller=self.rebase_controller, stash_controller=self.stash_controller, conflict_controller=self.conflict_controller,
332
+ close_readiness_controller=self.close_readiness_controller, navigation_controller=self.navigation_controller, application=self.application,
333
+ leave_event_loop=self.leave_event_loop, repository=self.repository,
334
+ difftool=self.difftool, worktree_service=self.worktree_service, editor=self.editor, repository_operation=self.repository_operation, rebase=self.rebase, replay=self.replay, mutation=self.mutation,
335
+ authentication=self.authentication, ssh=self.ssh, transport=self.transport, clone=self.clone, mergetool=self.mergetool, system_tool=self.system_tool, submodule=self.submodule, parent=self)
336
+ # Everything above is built before anything is connected, and the connections below keep the order they had: Qt calls
337
+ # the slots of a signal in connection order, and every object a connection reaches must already exist. The few
338
+ # connections made above stay ahead of a slot that a service adds to the same signal while it is built.
339
+ self.rebase.changed.connect(self.worktree_state_controller.relay_rebase_state)
340
+ self.rebase.changed.connect(self.rebase_controller.follow_rebase_ended_outside)
341
+ self.repository_operation.finished.connect(self.replay_controller.replay_finished)
342
+ self.replay.changed.connect(self.worktree_state_controller.relay_replay_state)
343
+ self.repository_operation.finished.connect(self.stash_controller.stash_finished)
344
+ self.repository_operation.finished.connect(self.stash_controller.stash_pop_finished)
345
+ self.repository_operation.finished.connect(self.stash_controller.stash_drop_finished)
346
+ self.repository_operation.finished.connect(self.reference_form_controller.reset_finished)
347
+ self.repository_operation.finished.connect(self.reference_form_controller.checkout_finished)
348
+ self.repository_operation.finished.connect(self.reference_form_controller.branch_finished)
349
+ self.repository_operation.finished.connect(self.reference_form_controller.delete_finished)
350
+ self.repository_operation.finished.connect(self.reference_form_controller.branch_deletion_finished)
351
+ self.repository_operation.finished.connect(self.reference_form_controller.tag_finished)
352
+ self.repository_operation.finished.connect(self.conflict_controller.merge_abort_finished)
353
+ self.mutation.finished.connect(self.file_action_controller.historical_restore_finished)
354
+ self.repository_operation.finished.connect(self.history_rewrite_controller.operation_finished)
355
+ self.history_rewrite_controller.recovery_requested.connect(self.history_rewrite_controller.present_recovery)
356
+ self.opening_controller.pause_observation_requested.connect(self.history_rewrite_controller.check_pending_recovery)
357
+ self.refresh_controller.pause_observation_requested.connect(lambda session_id, *_: self.history_rewrite_controller.check_pending_recovery(session_id))
358
+ self.refresh_controller.refresh_started.connect(self.history_rewrite_controller.observe_refresh)
359
+ self.worktree_service.idle.connect(self.history_rewrite_controller.readers_changed)
360
+ self.repository.runner.idle.connect(self.history_rewrite_controller.readers_changed)
361
+ self.git_setting_service.changed.connect(self.staging_controller.git_setting_changed)
362
+ self.commit_push.push_started.connect(self.transport_controller.commit_push_started)
363
+ self.commit_push.tracking_decision_requested.connect(self.transport_controller.commit_push_decide_tracking)
364
+ self.commit_push.choose_requested.connect(self.transport_controller.commit_push_choose)
365
+ self.application.aboutToQuit.connect(self.transport.close)
366
+ self.clone.finished.connect(self.clone_controller.clone_finished)
367
+ self.ssh.question_requested.connect(self.account_controller.show_ssh_question)
368
+ self.ssh.question_closed.connect(self.account_controller.close_ssh_question)
369
+ self.application.aboutToQuit.connect(self.ssh.close)
370
+ self.authentication.idle.connect(self.shutdown_controller.maybe_quit)
371
+ self.application.aboutToQuit.connect(self.authentication.close)
372
+ self.mutation.finished.connect(self.staging_controller.log_mutation_result)
373
+ self.mergetool.finished.connect(self.conflict_controller.mergetool_finished)
374
+ self.mergetool.series_changed.connect(self.conflict_controller.mergetool_series_changed)
375
+ self.mergetool.series_finished.connect(self.conflict_controller.mergetool_series_finished)
376
+ self.mergetool.confirmation_requested.connect(self.conflict_controller.confirm_mergetool)
377
+ self.mergetool.confirmation_closed.connect(self.conflict_controller.close_merge_confirmation)
378
+ self.mergetool.idle.connect(self.shutdown_controller.maybe_quit)
379
+ self.application.aboutToQuit.connect(self.mergetool.close)
380
+ self.mutation.finished.connect(self.conflict_controller.conflict_resolution_finished)
381
+ # A drop waiting for the merge tool or another write to release the repository starts as soon as it is free.
382
+ self.repository_operation.changed.connect(self.stash_controller.continue_stash_drops_after_resolution)
383
+ self.difftool.failed.connect(self.file_action_controller.difftool_failed)
384
+ self.difftool.started.connect(self.file_action_controller.difftool_started)
385
+ self.difftool.tool_exited.connect(self.file_action_controller.difftool_closed)
386
+ self.application.aboutToQuit.connect(self.difftool.close)
387
+ self.application.aboutToQuit.connect(self.avatar_service.close)
388
+ self.repository_operation.finished.connect(self.rebase_controller.rebase_finished)
389
+ self.editor.waiting.connect(self.rebase_controller.rebase_editor_waiting)
390
+ # Only a wait reads the native list again, so a reword shows the edited instructions; closing the editor
391
+ # changes the progress text alone, and the command's own result follows.
392
+ self.editor.finished.connect(lambda result: self.rebase_controller.rebase_progress(result.request.operation_id, SharedText.REBASE_APPLYING_COMMITS.value))
393
+ # A request whose bridge was lost ends without an answer, and Git goes on to its own result the same way.
394
+ self.editor.edit_cancelled.connect(lambda request: self.rebase_controller.rebase_progress(request.operation_id, SharedText.REBASE_APPLYING_COMMITS.value))
395
+ self.editor.idle.connect(self.shutdown_controller.maybe_quit)
396
+ self.system_tool.started.connect(self.file_action_controller.tool_started)
397
+ self.system_tool.finished.connect(self.file_action_controller.tool_finished)
398
+ self.system_tool.export_finished.connect(self.file_action_controller.export_finished)
399
+ self.application.aboutToQuit.connect(self.system_tool.close)
400
+ self.validator.validated.connect(self.setting_controller.git_validated)
401
+ self.validator.tools_folder_checked.connect(self.setting_controller.git_tools_folder_checked)
402
+ # Settings applies a change first, then an open accounts page and the clone forms show the accounts it touched, as before.
403
+ self.preference.changed.connect(self.setting_controller.preference_changed)
404
+ self.preference.changed.connect(self.account_controller.preference_changed_for_accounts)
405
+ self.preference.changed.connect(self.clone_controller.preference_changed_for_clone_forms)
406
+ # A read a navigation owns goes to its own slot, connected first; the opening's and the history read's slots that follow ignore it.
407
+ self.repository.opened.connect(self.navigation_controller.navigation_opened)
408
+ self.repository.opened.connect(self.opening_controller.opened)
409
+ self.repository.failed.connect(self.navigation_controller.navigation_read_failed)
410
+ self.repository.failed.connect(self.history_read_controller.failed)
411
+ self.repository.snapshot_read.connect(self.navigation_controller.navigation_snapshot_read)
412
+ self.repository.snapshot_read.connect(self.history_read_controller.snapshot_read)
413
+ self.repository.commit_batch_read.connect(self.navigation_controller.navigation_commit_batch_read)
414
+ self.repository.commit_batch_read.connect(self.history_read_controller.commit_batch_read)
415
+ self.repository.commit_read.connect(self.history_read_controller.commit_read)
416
+ self.repository.matching_read.connect(self.navigation_controller.navigation_matching_read)
417
+ self.repository.matching_read.connect(self.history_read_controller.matching_read)
418
+ self.repository.relation_read.connect(self.history_read_controller.relation_read)
419
+ self.repository.changes_read.connect(self.history_read_controller.changes_read)
420
+ self.repository.diff_read.connect(self.history_read_controller.diff_read)
421
+ self.repository.tree_read.connect(self.history_read_controller.tree_read)
422
+ self.repository.blob_read.connect(self.history_read_controller.blob_read)
423
+ self.repository.file_failed.connect(self.history_read_controller.file_failed)
424
+ self.application.aboutToQuit.connect(self.repository.close)
425
+ self.close_readiness_controller.close_timer.timeout.connect(self.shutdown_controller.resume_pending_close)
426
+ self.repository.runner.idle.connect(self.shutdown_controller.maybe_quit)
427
+ self.repository_operation.idle.connect(self.shutdown_controller.maybe_quit)
428
+ self.system_tool.export_idle.connect(self.shutdown_controller.maybe_quit)
429
+ self.difftool.idle.connect(self.shutdown_controller.maybe_quit)
430
+ self.mutation.finished.connect(self.shutdown_controller.maybe_quit)
431
+ self.worktree_service.idle.connect(self.shutdown_controller.maybe_quit)
432
+ if diagnostic_log is not None:
433
+ self.application.aboutToQuit.connect(diagnostic_log.close)
434
+ # A controller tells a higher one through a signal connected here. The lambda reads the slot when the signal comes,
435
+ # so a slot replaced on its owner after construction is the one called, and passes the options it only takes by name.
436
+ self.refresh_controller.pause_observation_requested.connect(
437
+ lambda session_id, replay_publication, rebase_publication: self.rebase_controller.observe_rebase(session_id, open_paused=True, replay_publication=replay_publication,
438
+ rebase_publication=rebase_publication))
439
+ self.worktree_state_controller.automatic_observation_requested.connect(lambda session_id: self.rebase_controller.observe_rebase(session_id, open_paused=True, automatic=True))
440
+ self.worktree_state_controller.window_conflict_state_shown.connect(lambda session_id, key, conflict_state: self.conflict_controller.show_window_conflict_state(session_id, key, conflict_state))
441
+ # The stash kept for its conflicts is settled before the conflicts a Stash pop left are read again, as before.
442
+ self.worktree_state_controller.worktree_conflict_state_read.connect(lambda key: self.stash_controller.continue_stash_drop_after_resolution(key))
443
+ self.worktree_state_controller.worktree_conflict_state_read.connect(lambda key: self.conflict_controller.read_conflict_retention(key))
444
+ # A published history ends the replay refresh waiting for it before a finished rebase shows its final commit, as before.
445
+ self.history_read_controller.history_published.connect(lambda session_id, generation: self.replay_controller.finish_replay_refresh(session_id, published_generation=generation))
446
+ self.history_read_controller.history_published.connect(lambda session_id, generation: self.history_rewrite_controller.readers_changed())
447
+ self.history_read_controller.history_published.connect(lambda session_id, generation: self.rebase_controller.reveal_rebase_result(session_id))
448
+ self.history_read_controller.history_status_written.connect(lambda session_id, snapshot: self.rebase_controller.show_rebase_end_notice(session_id, snapshot, after_history=True))
449
+ self.history_read_controller.refresh_failed.connect(lambda session_id, diagnostic: self.replay_controller.finish_replay_refresh(session_id, diagnostic))
450
+ self.history_read_controller.refresh_failed.connect(lambda session_id, diagnostic: self.rebase_controller.forget_rebase_end_notice(session_id))
451
+ self.setting_controller.setting_opened.connect(
452
+ lambda dialog, account_page, session_id, context: self.account_controller.bind_setting_account_page(dialog, account_page, session_id, context))
453
+ self.setting_controller.git_missing.connect(lambda: self.welcome_controller.show_missing_git())
454
+ self.setting_controller.waiting_opening_released.connect(lambda waiting: self.opening_controller.resume_waiting_opening(waiting))
455
+ self.clone_controller.open_requested.connect(lambda session_id, repository_dir: self.opening_controller.open(session_id, repository_dir))
456
+ self.clone_controller.open_cancel_requested.connect(lambda session_id: self.opening_controller.cancel_open(session_id))
457
+ self.opening_controller.pause_observation_requested.connect(lambda session_id: self.rebase_controller.observe_rebase(session_id, open_paused=True))
458
+ self.replay_controller.conflict_write_availability_changed.connect(lambda session_id, locked: self.conflict_controller.show_conflict_write_availability(session_id, locked))
459
+ self.rebase_controller.close_wait_requested.connect(lambda session_id: self.close_readiness_controller.show_close_wait(session_id))
460
+ self.rebase_controller.rebase_state_shown.connect(lambda session_id, state: self.conflict_controller.show_rebase_state_in_conflict_dialog(session_id, state))
461
+ self.rebase_controller.conflict_list_requested.connect(lambda session_id: self.conflict_controller.open_conflict_dialog(session_id))
462
+ self.stash_controller.conflict_retention_requested.connect(lambda key, observation: self.conflict_controller.track_conflict_retention(key, observation))
463
+ # The session a published change of repository leaves behind closes at once, its window and the navigation it publishes kept.
464
+ self.navigation_controller.repository_close_requested.connect(lambda owner_id: self.shutdown_controller.close_repository(owner_id, keep_navigation=True))
465
+ # Qt calls the filters of the application from the last installed to the first: the placement filter still sees a
466
+ # window's first show before the activation filter, and the event types of the other filters do not overlap; the
467
+ # modal relay alone follows WindowBlocked and WindowUnblocked.
468
+ application.installEventFilter(self.shutdown_controller)
469
+ self.drop_down_wheel_filter = DropDownWheelFilter(application, self)
470
+ application.installEventFilter(self.window_activation_controller)
471
+ application.applicationStateChanged.connect(self.window_activation_controller.schedule_activation_update)
472
+ self.window_placement = NewWindowPlacement(application, self)
473
+ self.modal_focus_relay: ModalFocusRelay | None = None
474
+ activate_modal_window = modal_window_activator(QGuiApplication.platformName())
475
+ if activate_modal_window is not None:
476
+ self.modal_focus_relay = ModalFocusRelay(application, activate_modal_window, self)
477
+
478
+ def wire_staging_session(self, key: WorktreeKey, staging: StagingSession) -> None:
479
+ """Connect a new preparation session to the controllers that follow what it does.
480
+
481
+ The registry that creates the session sits below these controllers and knows none of them, so its connections are
482
+ made here, together, in the order Qt calls them. The commit timing hears a commit's result after the controllers
483
+ that start its refresh, so the refresh it waits for is already asked.
484
+ """
485
+ staging.changed.connect(lambda: self.worktree_state_controller.show_worktree_state(key))
486
+ staging.changed.connect(lambda: self.history_rewrite_controller.observe_worktree(staging))
487
+ staging.head_moved.connect(lambda: self.worktree_state_controller.follow_moved_head(key))
488
+ staging.pause_observation_requested.connect(lambda: self.worktree_state_controller.observe_worktree_pause(key))
489
+ staging.window_opened.connect(self.staging_controller.staging_window_opened)
490
+ staging.commit_finished.connect(lambda outcome: self.staging_controller.commit_finished(staging, outcome))
491
+ staging.commit_finished.connect(self.commit_push.commit_finished)
492
+ staging.commit_submitted.connect(lambda request: self.commit_timing_controller.commit_clicked(staging, request))
493
+ staging.commit_finished.connect(self.commit_timing_controller.commit_finished)
494
+ staging.commit_push_requested.connect(self.transport_controller.commit_push_requested)
495
+ # The session behind this service has just read its worktree on the same result, so only the others read.
496
+ staging.ignore_service.changed.connect(lambda context: self.staging_controller.ignore_changed(context, refreshed_key=staging.key))
497
+ staging.ignore_service.finished.connect(self.staging_controller.log_mutation_result)
498
+ staging.history_requested.connect(lambda anchor: self.staging_controller.open_staging_history(staging, anchor))
499
+ staging.folder_requested.connect(lambda path: self.system_tool.show_file_location((key.git_dir, STAGING_FOLDER_TOOL), staging.context, path, allow_missing=True))
500
+ staging.text_editor_requested.connect(lambda path: self.file_action_controller.launch_text_editor((key.git_dir, STAGING_TEXT_EDITOR_TOOL), staging.context, path))
501
+ # A brand new session has never been told which window is active, and no activation event is on its way.
502
+ self.window_activation_controller.schedule_activation_update()
503
+
504
+ def wire_repository_window(self, identity: RequestIdentity, session: RepositorySession, window: RepositoryWindow, context: RepositoryContext, file_history_anchor: FileHistoryAnchor | None) -> None:
505
+ """Connect a new repository window and its session to the controllers that answer what the user does in it.
506
+
507
+ The opening that builds the window sits below most of these controllers and knows none of them, so its
508
+ connections are made here, together, in the order Qt calls them, before the window is shown.
509
+ """
510
+ window.file_history_requested.connect(lambda entry: self.file_action_controller.open_file_history(identity.session_id, entry))
511
+ window.remove_history_requested.connect(lambda selection: self.history_rewrite_controller.request(identity.session_id, selection))
512
+ window.remove_history_reason = lambda selection: self.history_rewrite_controller.remove_history_reason(identity.session_id, selection)
513
+ window.file_folder_requested.connect(lambda selection: self.file_action_controller.show_file_folder(identity.session_id, selection))
514
+ window.file_text_editor_requested.connect(lambda selection: self.file_action_controller.show_file_in_text_editor(identity.session_id, selection))
515
+ window.text_editor_reason = lambda selection: self.file_action_controller.text_editor_reason(identity.session_id, selection)
516
+ if file_history_anchor is None:
517
+ self.file_action_controller.read_git_editor_setting(context)
518
+ window.file_save_requested.connect(lambda selection: self.file_action_controller.save_file_as(identity.session_id, selection))
519
+ window.file_restore_requested.connect(lambda selection, target: self.file_action_controller.restore_historical_file(identity.session_id, selection, target))
520
+ window.difftool_requested.connect(lambda: self.file_action_controller.compare_external(identity.session_id))
521
+ window.file_history_resume_requested.connect(lambda: self.history_read_controller.resume_file_history(identity.session_id))
522
+ session.selection_changed.connect(lambda oid: window.diff_panel.clear_selection("Loading diff…"))
523
+ session.selection_changed.connect(lambda oid: window.file_tree_panel.clear_selection("Loading tree…"))
524
+ session.closed.connect(window.file_tree_panel.clear_selection)
525
+ window.file_tree_panel.selection_requested.connect(lambda entry: self.history_read_controller.read_blob(identity.session_id, entry))
526
+ window.file_tree_panel.retry_requested.connect(lambda kind: self.history_read_controller.retry_file_tree(identity.session_id, kind))
527
+ window.detail_tabs.currentChanged.connect(lambda _index: self.history_read_controller.read_visible_tree(identity.session_id))
528
+ window.diff_panel.entry_selected.connect(lambda entry: self.history_read_controller.read_diff(identity.session_id, entry))
529
+ window.diff_panel.retry_requested.connect(lambda: self.history_read_controller.retry_diff(identity.session_id))
530
+ window.diff_panel.full_context_requested.connect(lambda checked: self.history_read_controller.save_reading_preference(identity.session_id, {"diff_show_entire_file": checked}))
531
+ window.reading_preference_requested.connect(lambda values: self.history_read_controller.save_reading_preference(identity.session_id, values))
532
+ window.set_avatar_service(self.avatar_service)
533
+ window.explorer_requested.connect(lambda: self.file_action_controller.open_explorer(identity.session_id))
534
+ if WindowCapability.MANAGE in session.capabilities:
535
+ window.bind_terminal_registry(self.terminal_registry)
536
+ window.terminal_requested.connect(lambda descriptor: self.file_action_controller.launch_terminal(identity.session_id, descriptor))
537
+ window.commit_selected.connect(lambda oid: self.session_registry_controller.session_by_id[identity.session_id].select_commit(oid))
538
+ window.commit_selected.connect(lambda oid: self.history_read_controller.read_selection(identity.session_id, oid))
539
+ window.detail_retry_requested.connect(lambda oid: self.history_read_controller.retry_selection(identity.session_id, oid))
540
+ window.navigation_requested.connect(lambda oid: self.session_registry_controller.session_by_id[identity.session_id].navigate_to_commit(oid))
541
+ window.repository_activated.connect(lambda directory: self.navigation_controller.navigate_repository(identity.session_id, directory))
542
+ window.repository_switch_requested.connect(lambda directory: self.navigation_controller.switch_repository(identity.session_id, directory))
543
+ session.navigation_requested.connect(lambda oid: self.history_read_controller.read_navigation(identity.session_id, oid))
544
+ window.setting_requested.connect(lambda: self.setting_controller.show_setting(identity.session_id))
545
+ window.installEventFilter(self.window_activation_controller)
546
+ window.commit_requested.connect(lambda: self.staging_controller.open_staging(identity.session_id))
547
+ window.checkout_requested.connect(lambda full_ref: self.reference_form_controller.open_checkout(identity.session_id, full_ref))
548
+ window.create_branch_requested.connect(lambda oid, snapshot: self.reference_form_controller.open_branch(identity.session_id, BranchMode.CREATE, oid, expected_snapshot=snapshot))
549
+ window.create_tag_requested.connect(lambda oid, snapshot: self.reference_form_controller.open_tag(identity.session_id, oid, expected_snapshot=snapshot))
550
+ window.remote_requested.connect(lambda: self.transport_controller.open_remote(identity.session_id))
551
+ window.emoji_keyboard_requested.connect(lambda: self.emoji_window_controller.open_emoji_window(identity.session_id))
552
+ window.fetch_requested.connect(lambda: self.transport_controller.fetch(identity.session_id))
553
+ window.push_requested.connect(lambda: self.transport_controller.open_push(identity.session_id))
554
+ window.stash_requested.connect(lambda: self.stash_controller.open_stash(identity.session_id))
555
+ window.resolve_requested.connect(lambda state: self.conflict_controller.open_conflict_dialog(identity.session_id, state))
556
+ # Continue merge works the way Git Extensions does: it opens Prepare changes, where Git's merge message is offered,
557
+ # and the user makes the merge commit with Commit; Git's own merge --continue is never run.
558
+ window.merge_continue_requested.connect(lambda: self.staging_controller.open_staging(identity.session_id))
559
+ window.merge_abort_requested.connect(lambda: self.conflict_controller.abort_merge(identity.session_id))
560
+ window.rebase_requested.connect(lambda: self.rebase_controller.open_rebase_dialog(identity.session_id))
561
+ window.replay_refresh_requested.connect(lambda: self.replay_controller.observe_replay(identity.session_id))
562
+ window.replay_resolve_requested.connect(lambda state: self.replay_controller.resolve_replay(identity.session_id, state))
563
+ window.replay_continue_requested.connect(lambda state: self.replay_controller.continue_replay(identity.session_id, state))
564
+ window.replay_abort_requested.connect(lambda state: self.replay_controller.abort_replay(identity.session_id, state))
565
+ window.replay_requested.connect(lambda oid, kind, snapshot: self.replay_controller.open_replay(identity.session_id, oid, kind, snapshot))
566
+ window.rebase_start_requested.connect(lambda oid, snapshot: self.rebase_controller.start_rebase(identity.session_id, oid, snapshot))
567
+ window.merge_recovery_requested.connect(lambda: self.conflict_controller.open_merge_recovery(identity.session_id))
568
+ window.stash_pop_requested.connect(lambda: self.stash_controller.stash_pop(identity.session_id))
569
+ window.stash_drop_requested.connect(lambda entry: self.stash_controller.drop_stash(identity.session_id, entry))
570
+ window.fetch_selection_requested.connect(lambda choice: self.transport_controller.fetch(identity.session_id, choice))
571
+ window.fetch_menu_requested.connect(lambda: self.transport_controller.read_fetch_choice(identity.session_id))
572
+ window.fetch_default_requested.connect(lambda mode: self.transport_controller.save_fetch_default(identity.session_id, mode))
573
+ window.reset_requested.connect(lambda oid, snapshot: self.reference_form_controller.open_reset(identity.session_id, oid, expected_snapshot=snapshot))
574
+ window.move_branch_requested.connect(lambda oid, snapshot: self.reference_form_controller.open_branch(identity.session_id, BranchMode.MOVE, oid, expected_snapshot=snapshot))
575
+ window.rename_branch_requested.connect(lambda full_ref, oid, snapshot: self.reference_form_controller.open_branch(identity.session_id, BranchMode.RENAME, oid, expected_snapshot=snapshot, full_ref=full_ref))
576
+ window.delete_branch_requested.connect(lambda full_ref, oid, snapshot: self.reference_form_controller.open_delete_branch(identity.session_id, full_ref, oid, expected_snapshot=snapshot))
577
+ window.delete_tag_requested.connect(lambda full_ref, oid, snapshot: self.reference_form_controller.open_delete_tag(identity.session_id, full_ref, oid, expected_snapshot=snapshot))
578
+ window.checkout_commit_requested.connect(lambda oid, snapshot: self.reference_form_controller.open_checkout_revision(identity.session_id, oid, expected_snapshot=snapshot))
579
+ window.checkout_branch_requested.connect(lambda full_ref, oid, snapshot: self.reference_form_controller.open_checkout(identity.session_id, full_ref, target_oid=oid, expected_snapshot=snapshot))
580
+ window.open_requested.connect(lambda: self.welcome_controller.reopen_welcome(identity.session_id))
581
+ window.closed.connect(lambda: self.shutdown_controller.close_repository(identity.session_id))
582
+ window.refresh_requested.connect(lambda: self.refresh_controller.refresh(identity.session_id))
583
+ window.filter_changed.connect(lambda text: self.history_read_controller.change_filter(identity.session_id, text))
584
+ window.search_requested.connect(lambda: self.history_read_controller.start_search(identity.session_id))
585
+
586
+
587
+ def application_module_name() -> str:
588
+ """Answer the name under which the import system loaded this module, which a desktop shortcut passes to ``python -m``.
589
+
590
+ It is read from the module's spec, so it stays right when this module runs itself through -m, where __name__ is
591
+ __main__, and a move of the module cannot leave a stale name in the shortcut. A module loaded without a spec has no
592
+ name to give, and the text is empty.
593
+ """
594
+ if __spec__ is None:
595
+ return ""
596
+ return __spec__.name
597
+
598
+
599
+ def run(argv: list[str], *, preference_filename: str = "") -> int:
600
+ """Parse command-line options and start the installed Qt application.
601
+
602
+ Preferences live in one INI file: ``preference_filename`` when given (test launchers pass a temporary one), otherwise
603
+ the per-user gitpane.ini. The registry is no longer read or written.
604
+
605
+ A GitPane started detached by the gitpane command finds the ready channel in its environment: it is taken before any
606
+ program is started, so none inherits it, and the ready signal is sent once the first window is shown and Git is set
607
+ up, which gives the terminal back. When the command is gone before that (its wait ran out, Ctrl+C, the terminal was
608
+ closed), the standard streams leave its pipes as soon as the event loop runs. This function never detaches by
609
+ itself: only main() decides that.
610
+ """
611
+ ready_channel = take_ready_channel(os.environ)
612
+ option = command_line_parser().parse_args(argv)
613
+ declare_application_identity()
614
+ # The desktop finds a running application by this identifier, which Qt hands to each window when it creates it, so
615
+ # it is set before the application and its first window. Under Wayland it is the application id GNOME reads to name
616
+ # the window and to find the GitPane.desktop entry that carries the icon; under X11 the window class keeps matching
617
+ # the entry's StartupWMClass. Without it, a window started from Python is named after the interpreter.
618
+ QGuiApplication.setDesktopFileName(APPLICATION_NAME)
619
+ application = QApplication([APPLICATION_NAME])
620
+ # Here: the first point where an event loop can watch the channel, so a command gone before the first window is
621
+ # handled as soon as the loop runs, not only once GitPane is ready.
622
+ ready_watch = ReadyChannelWatch(ready_channel, application) if ready_channel is not None else None
623
+ configure_system_style(application)
624
+ # Here: the application just loaded GTK and subscribed Qt's portal listeners, and a dark desktop palette must exist before the Git Extensions colours are decided on it.
625
+ install_desktop_appearance_follower(application)
626
+ install_light_field_palette(application)
627
+ install_application_icon(application)
628
+ application.setOrganizationName(APPLICATION_NAME)
629
+ application.setApplicationName(APPLICATION_NAME)
630
+ application.setApplicationVersion(APPLICATION_VERSION)
631
+ preference_filename = preference_filename or user_preference_filename(sys.platform, os.environ, os.path.expanduser("~"))
632
+ preference = PreferenceStore(QSettings(preference_filename, QSettings.Format.IniFormat))
633
+ git_filename = str(preference.read("git_filename"))
634
+ repository = GitRepository(git_filename)
635
+ diagnostic_log = DiagnosticLog(option.log, writable_folder(QStandardPaths.StandardLocation.AppLocalDataLocation, sys.platform))
636
+ diagnostic_log.write_line(APPLICATION_VERSION_LINE)
637
+ diagnostic_log.write_line(PREFERENCE_FILE_LOG_TEXT.format(filename=preference_filename))
638
+ if preference.warning:
639
+ diagnostic_log.write_line(preference.warning)
640
+ for line in preference.foreign_dir_lines:
641
+ diagnostic_log.write_line(line)
642
+ # Workaround: on macOS, QApplication.quit() asks AppKit to terminate the application. Asked while GitPane answers
643
+ # AppKit's own terminate request (Cmd+Q when nothing runs), AppKit calls the C exit() in the middle of a Python call:
644
+ # exec() never returns and Shiboken's static destructors crash the interpreter. exit() only leaves the event loop,
645
+ # on every system, so exec() returns here and the process ends normally. It is given here because only this function
646
+ # runs the loop: called while no loop runs, exit() would make every later event loop return at once.
647
+ coordinator = ApplicationWiring(application, preference, repository, diagnostic_log, leave_event_loop=application.exit)
648
+ # The startup read blocks nothing: Show in text editor and Settings use the desktop's editor as soon as it arrives.
649
+ coordinator.system_editor_default.request_read()
650
+ coordinator.welcome_controller.show_welcome()
651
+ coordinator.setting_controller.initialize_git()
652
+ if ready_watch is not None:
653
+ # Sent from the event loop, so the first window has had its show handled before the terminal is given back.
654
+ QTimer.singleShot(0, functools.partial(ready_watch.send_ready, diagnostic_log.log_filename or ""))
655
+ application.setQuitOnLastWindowClosed(False)
656
+ application.lastWindowClosed.connect(coordinator.shutdown_controller.request_shutdown)
657
+ try:
658
+ return application.exec()
659
+ finally:
660
+ diagnostic_log.close()
661
+
662
+
663
+ def main(stream_is_terminal: Callable[[int], bool] = os.isatty) -> int:
664
+ """Start GitPane with the command-line arguments of the process, detached from the terminal it was typed in.
665
+
666
+ The installed gitpane command, main.py and python -m gitpane.application call it without argument;
667
+ ``stream_is_terminal`` lets tests decide whether the standard streams are a terminal. The options are read first, so
668
+ --version, --help and an unknown option answer in the terminal. Typed in a terminal on Linux or macOS without
669
+ --attach, GitPane is started again in a session of its own, under the interpreter options of this command, and this
670
+ command returns once its first window is shown; otherwise GitPane runs in this process, and with --attach Ctrl+C
671
+ stops it at once.
672
+ """
673
+ argv = sys.argv[1:]
674
+ option = command_line_parser().parse_args(argv)
675
+ module_name = application_module_name()
676
+ if module_name and detaches_from_terminal(sys.platform, stream_is_terminal, option.attach, os.environ):
677
+ return relaunch_detached(detached_child_command(argv, sys.executable, module_name, running_interpreter_options()), os.environ).exit_code
678
+ if interrupt_stops_gitpane(sys.platform, option.attach):
679
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
680
+ return run(argv)
681
+
682
+
683
+ if __name__ == "__main__":
684
+ raise SystemExit(main())