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,84 @@
1
+ """Keep the merge tool recipe, literal process arguments included, independent of comparison tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import re
7
+ import sys
8
+ from dataclasses import dataclass
9
+
10
+ from PySide6.QtCore import QProcess
11
+
12
+ from gitpane.config import SHELL_SCRIPT_EXTENSION_SET
13
+ from gitpane.system.linux_distribution import DistributionFamily, DistributionRelease
14
+ from gitpane.tool.kdiff3 import tool_program_usable
15
+ from gitpane.tool.kdiff3_install import with_kdiff3_install_line
16
+
17
+ MERGETOOL_FILENAME_PREFERENCE_KEY = "mergetool_filename"
18
+ MERGETOOL_COMMAND_PREFERENCE_KEY = "mergetool_command"
19
+ MERGETOOL_DEFAULT_COMMAND = '"$BASE" "$LOCAL" "$REMOTE" -o "$MERGED"'
20
+ MERGETOOL_TOKENS = ("$BASE", "$LOCAL", "$REMOTE", "$MERGED")
21
+ MERGETOOL_DEFAULT_BY_KEY = {MERGETOOL_FILENAME_PREFERENCE_KEY: "", MERGETOOL_COMMAND_PREFERENCE_KEY: MERGETOOL_DEFAULT_COMMAND}
22
+ TOKEN_PATTERN = r"\$[A-Za-z_][A-Za-z_0-9]*|\$\{[^}]*\}|\$"
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class MergetoolRecipe:
27
+ """Retain the selected executable and the complete four-input argument template."""
28
+
29
+ program_filename: str
30
+ command: str = MERGETOOL_DEFAULT_COMMAND
31
+
32
+ def validate(self, *, optional: bool = False, platform: str = sys.platform, family: DistributionFamily = DistributionFamily.UNKNOWN,
33
+ release: DistributionRelease = DistributionRelease.OTHER) -> None:
34
+ """Allow an unset Settings choice only when explicitly requested by its caller.
35
+
36
+ The refusal of a missing tool says how to install KDiff3 on ``platform``, and under Linux on the distribution
37
+ ``family`` and ``release``, all received so each system's message can be checked from any of them.
38
+ """
39
+ parse_mergetool_command(self.command, "base", "local", "remote", "merged")
40
+ if not self.program_filename:
41
+ if optional:
42
+ return
43
+ raise ValueError(with_kdiff3_install_line("External merge tool is not configured. Open Settings and save its executable path before resolving conflicts.", platform, family, release))
44
+ if not tool_program_usable(self.program_filename):
45
+ raise ValueError(with_kdiff3_install_line("External merge tool executable is missing or not executable. Choose its full path.", platform, family, release))
46
+ _program_root, program_extension = os.path.splitext(self.program_filename)
47
+ if program_extension.casefold() in SHELL_SCRIPT_EXTENSION_SET:
48
+ raise ValueError("Choose the real external merge executable instead of a shell script launcher.")
49
+
50
+
51
+ def parse_mergetool_command(command: str, base_filename: str | None, local_filename: str | None, remote_filename: str | None, merged_filename: str | None,
52
+ *, allow_missing_base: bool = False) -> list[str]:
53
+ """Build literal arguments, removing a standalone absent base only after explicit agreement."""
54
+ if "\0" in command:
55
+ raise ValueError("External merge arguments cannot contain a null character.")
56
+ # Qt uses three quotes for a literal quote, so backslashes cannot balance a quoted argument.
57
+ if command.replace('"""', '').count('"') % 2:
58
+ raise ValueError("External merge arguments contain an unclosed double quote.")
59
+ arguments = QProcess.splitCommand(command)
60
+ replacement_by_token = dict(zip(MERGETOOL_TOKENS, (base_filename, local_filename, remote_filename, merged_filename), strict=True))
61
+ token_set: set[str] = set()
62
+ for argument in arguments:
63
+ tokens = re.findall(TOKEN_PATTERN, argument)
64
+ for token in tokens:
65
+ if token not in replacement_by_token:
66
+ raise ValueError("External merge arguments contain an unknown parameter. Use $BASE, $LOCAL, $REMOTE and $MERGED only.")
67
+ token_set.add(token)
68
+ if token_set != set(MERGETOOL_TOKENS):
69
+ raise ValueError("External merge arguments must include $BASE, $LOCAL, $REMOTE and $MERGED.")
70
+ for token, filename in replacement_by_token.items():
71
+ if filename and "\0" in filename:
72
+ raise ValueError("External merge filenames cannot contain a null character.")
73
+ if not filename and token != "$BASE":
74
+ raise ValueError(f"The {token} filename is missing. Use manual conflict resolution for an absent side.")
75
+ if not base_filename and not allow_missing_base:
76
+ raise ValueError("The BASE version is missing. A two-input merge requires confirmation.")
77
+ results = []
78
+ for argument in arguments:
79
+ if not base_filename and "$BASE" in argument:
80
+ if argument != "$BASE":
81
+ raise ValueError("Missing BASE can only remove a standalone $BASE argument. This recipe requires manual conflict resolution.")
82
+ continue
83
+ results.append(re.sub(TOKEN_PATTERN, lambda match: replacement_by_token[match.group()], argument))
84
+ return results
@@ -0,0 +1,80 @@
1
+ """Keep the system's plain text editor known, reading it again off the window's thread when asked.
2
+
3
+ The value is the identifier the system names that editor by: a desktop file name on Linux, an application identifier
4
+ on macOS. The files it is read from may sit in a home folder on a network share that stops answering, so the window
5
+ never waits for them: a read runs on a thread of its own, only one at a time, and until its answer arrives the system
6
+ counts as naming no editor. Settings and Show in text editor use the last value received. A read always answers, so the
7
+ next request reads again; an error nobody expects names no editor and is written to the ``--log`` journal, since it
8
+ shows a defect of GitPane rather than something about the system.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import threading
14
+ from typing import TYPE_CHECKING
15
+
16
+ from PySide6.QtCore import QObject, Qt, Signal
17
+
18
+ from gitpane.account.credential_manager import ignore_line
19
+ from gitpane.thread.weak_signal import hand_over_to_the_page, weak_signal_emitter
20
+
21
+ if TYPE_CHECKING:
22
+ from collections.abc import Callable
23
+
24
+ SYSTEM_EDITOR_THREAD_NAME = "gitpane-system-text-editor"
25
+ SYSTEM_EDITOR_READ_FAILURE_LOG_TEMPLATE = "Reading the system's text editor failed unexpectedly: {error_class}: {error}"
26
+
27
+
28
+ def run_system_editor_id_read(read: Callable[[], str], hand_over: Callable[[str], None], log_line: Callable[[str], None] = ignore_line) -> None:
29
+ """Read the system's editor on the worker thread and hand it over to the holder's thread, an empty text when the read failed.
30
+
31
+ A failed read names no editor, so the detection falls back on its rule for no default rather than on a value it could
32
+ not read. The holder always gets an answer, so ``reading`` falls back and the next request reads again. An error
33
+ nobody expects shows a defect of GitPane, not of the system: it is written through ``log_line``.
34
+ """
35
+ system_editor_id = ""
36
+ try:
37
+ system_editor_id = read()
38
+ except (OSError, ValueError, UnicodeError, RuntimeError):
39
+ system_editor_id = ""
40
+ except Exception as error: # noqa: BLE001 -- the worker must always answer the holder; the error is written to the --log journal
41
+ system_editor_id = ""
42
+ log_line(SYSTEM_EDITOR_READ_FAILURE_LOG_TEMPLATE.format(error_class=type(error).__name__, error=error))
43
+ finally:
44
+ hand_over_to_the_page(hand_over, system_editor_id)
45
+
46
+
47
+ class SystemEditorDefault(QObject):
48
+ """The identifier the system names its plain text editor by, as last read; empty until a read answers."""
49
+
50
+ system_editor_id_read = Signal(object)
51
+
52
+ def __init__(self, read_system_editor_id: Callable[[], str], parent: QObject | None = None, *, log_line: Callable[[str], None] = ignore_line) -> None:
53
+ """Keep ``read_system_editor_id``, which reads the session's files and runs only off the window's thread; nothing is read before a request.
54
+
55
+ ``log_line`` writes one line to the ``--log`` journal, from the reading thread, when a read meets an error
56
+ nobody expects; without a journal it writes nothing.
57
+ """
58
+ super().__init__(parent)
59
+ self.read_system_editor_id = read_system_editor_id
60
+ self.log_line = log_line
61
+ self.system_editor_id = ""
62
+ self.reading = False
63
+ self.system_editor_id_read.connect(self.keep_system_editor_id, Qt.ConnectionType.QueuedConnection)
64
+
65
+ def request_read(self) -> None:
66
+ """Start reading the system's editor on a worker thread, unless a read is still running."""
67
+ if self.reading:
68
+ return
69
+ self.reading = True
70
+ # The thread hands its answer to the window thread's relay, never to the holder's own signal: the relay emits it
71
+ # there only while the holder still exists, so a holder deleted during the read, as GitPane quits, is never
72
+ # touched from the reading thread.
73
+ hand_over = weak_signal_emitter(self, lambda holder: holder.system_editor_id_read)
74
+ thread = threading.Thread(target=run_system_editor_id_read, args=(self.read_system_editor_id, hand_over, self.log_line), name=SYSTEM_EDITOR_THREAD_NAME, daemon=True)
75
+ thread.start()
76
+
77
+ def keep_system_editor_id(self, system_editor_id: str) -> None:
78
+ """Keep the identifier a read answered, on the holder's thread, and allow the next read."""
79
+ self.reading = False
80
+ self.system_editor_id = system_editor_id
@@ -0,0 +1,44 @@
1
+ """Choose, for each system, how GitPane reads the text editor the system opens plain text with.
2
+
3
+ Linux reads the desktop's mimeapps.list files, macOS the user's LaunchServices handlers. Windows reads nothing: its
4
+ detection keeps Notepad whatever the system names.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import sys
10
+ from typing import TYPE_CHECKING
11
+
12
+ from gitpane.config import LINUX_PLATFORM, MACOS_PLATFORM, WINDOWS_PLATFORM
13
+ from gitpane.tool.desktop_text_editor import session_desktop_text_editor_id
14
+ from gitpane.tool.editor_recipe import editor_platform
15
+ from gitpane.tool.launch_services_text_editor import session_launch_services_text_editor_id
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Callable
19
+
20
+
21
+ def read_no_system_text_editor() -> str:
22
+ """Read nothing and name no editor, for a system whose detection does not follow a default text editor."""
23
+ return ""
24
+
25
+
26
+ SYSTEM_TEXT_EDITOR_READER_BY_PLATFORM: dict[str, Callable[[], str]] = {
27
+ LINUX_PLATFORM: session_desktop_text_editor_id,
28
+ MACOS_PLATFORM: session_launch_services_text_editor_id,
29
+ WINDOWS_PLATFORM: read_no_system_text_editor,
30
+ }
31
+
32
+
33
+ def system_text_editor_reader(platform: str) -> Callable[[], str]:
34
+ """Answer the reader of the default text editor of ``platform``, in the vocabulary of the editor presets, so any other Unix system reads as Linux."""
35
+ return SYSTEM_TEXT_EDITOR_READER_BY_PLATFORM[editor_platform(platform)]
36
+
37
+
38
+ def session_system_text_editor_id() -> str:
39
+ """Answer the identifier this system names its default text editor by, read from the session's files.
40
+
41
+ It may read a home folder on a network share that stops answering: call it on a thread of its own only, never on the
42
+ window's thread.
43
+ """
44
+ return system_text_editor_reader(sys.platform)()