gitpane 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitpane/__init__.py +0 -0
- gitpane/account/__init__.py +0 -0
- gitpane/account/account_choice.py +297 -0
- gitpane/account/address_owner.py +67 -0
- gitpane/account/authentication.py +1811 -0
- gitpane/account/authentication_worker.py +836 -0
- gitpane/account/credential_manager.py +480 -0
- gitpane/account/credential_manager_install.py +171 -0
- gitpane/account/keyring_reader.py +135 -0
- gitpane/account/keyring_state.py +158 -0
- gitpane/account/ssh.py +312 -0
- gitpane/account/ssh_askpass.py +216 -0
- gitpane/account/ssh_prompt.py +222 -0
- gitpane/application.py +684 -0
- gitpane/commit/__init__.py +0 -0
- gitpane/commit/commit_metadata.py +88 -0
- gitpane/commit/commit_push.py +276 -0
- gitpane/commit/commit_write.py +830 -0
- gitpane/config.py +1066 -0
- gitpane/controller/__init__.py +0 -0
- gitpane/controller/account_controller.py +677 -0
- gitpane/controller/clone_controller.py +776 -0
- gitpane/controller/close_readiness.py +212 -0
- gitpane/controller/commit_timing_controller.py +566 -0
- gitpane/controller/conflict_controller.py +885 -0
- gitpane/controller/console_registry.py +38 -0
- gitpane/controller/emoji_window_controller.py +95 -0
- gitpane/controller/file_action_controller.py +479 -0
- gitpane/controller/history_read_controller.py +671 -0
- gitpane/controller/history_rewrite_controller.py +739 -0
- gitpane/controller/navigation_controller.py +658 -0
- gitpane/controller/opening_controller.py +375 -0
- gitpane/controller/operation_policy.py +76 -0
- gitpane/controller/rebase_controller.py +797 -0
- gitpane/controller/rebase_editor_controller.py +309 -0
- gitpane/controller/reference_form_controller.py +1677 -0
- gitpane/controller/reference_read_controller.py +242 -0
- gitpane/controller/refresh_controller.py +477 -0
- gitpane/controller/replay_controller.py +805 -0
- gitpane/controller/session_registry.py +337 -0
- gitpane/controller/setting_controller.py +592 -0
- gitpane/controller/shutdown_controller.py +345 -0
- gitpane/controller/staging_controller.py +199 -0
- gitpane/controller/staging_session_registry.py +47 -0
- gitpane/controller/stash_controller.py +367 -0
- gitpane/controller/transport_controller.py +813 -0
- gitpane/controller/welcome_controller.py +102 -0
- gitpane/controller/window_activation.py +116 -0
- gitpane/controller/worktree_state_controller.py +169 -0
- gitpane/emoji/__init__.py +0 -0
- gitpane/emoji/emoji_catalogue.py +156 -0
- gitpane/git_config/__init__.py +0 -0
- gitpane/git_config/git_setting.py +441 -0
- gitpane/history/__init__.py +0 -0
- gitpane/history/avatar.py +241 -0
- gitpane/history/file_history.py +359 -0
- gitpane/history/graph.py +145 -0
- gitpane/history/history_filter.py +31 -0
- gitpane/history/history_parse.py +605 -0
- gitpane/history/repository.py +1600 -0
- gitpane/history/repository_value.py +341 -0
- gitpane/history/revision.py +180 -0
- gitpane/history/revision_file.py +182 -0
- gitpane/history_rewrite/__init__.py +0 -0
- gitpane/history_rewrite/graph.py +210 -0
- gitpane/history_rewrite/guard.py +288 -0
- gitpane/history_rewrite/index.py +125 -0
- gitpane/history_rewrite/prepare.py +378 -0
- gitpane/history_rewrite/protocol.py +320 -0
- gitpane/history_rewrite/publish.py +444 -0
- gitpane/history_rewrite/recovery.py +481 -0
- gitpane/history_rewrite/value.py +229 -0
- gitpane/operation/__init__.py +0 -0
- gitpane/operation/console_line.py +313 -0
- gitpane/operation/console_tone.py +76 -0
- gitpane/operation/diagnostic.py +954 -0
- gitpane/operation/launch_signature.py +254 -0
- gitpane/operation/operation_status.py +87 -0
- gitpane/operation/operation_trace.py +766 -0
- gitpane/operation/repository_operation.py +781 -0
- gitpane/preference/__init__.py +0 -0
- gitpane/preference/preference_location.py +40 -0
- gitpane/preference/preference_store.py +590 -0
- gitpane/process/__init__.py +0 -0
- gitpane/process/exact_arguments.py +77 -0
- gitpane/process/git_output_text.py +20 -0
- gitpane/process/process_group.py +851 -0
- gitpane/process/process_log.py +114 -0
- gitpane/process/process_runner.py +1033 -0
- gitpane/process/released_child_watch.py +138 -0
- gitpane/recent/__init__.py +0 -0
- gitpane/recent/mount_table.py +422 -0
- gitpane/recent/recent_availability.py +414 -0
- gitpane/recent/recent_name.py +11 -0
- gitpane/recent/recent_search.py +114 -0
- gitpane/reference/__init__.py +0 -0
- gitpane/reference/reference_edit.py +2030 -0
- gitpane/reference/reference_snapshot.py +196 -0
- gitpane/reference/stash.py +730 -0
- gitpane/reference/stash_list.py +164 -0
- gitpane/sequencer/__init__.py +0 -0
- gitpane/sequencer/commit_message_rule.py +154 -0
- gitpane/sequencer/conflict.py +506 -0
- gitpane/sequencer/merge_recovery.py +163 -0
- gitpane/sequencer/merge_state.py +166 -0
- gitpane/sequencer/mergetool.py +856 -0
- gitpane/sequencer/rebase.py +750 -0
- gitpane/sequencer/rebase_edit_file.py +224 -0
- gitpane/sequencer/rebase_state.py +523 -0
- gitpane/sequencer/replay.py +1435 -0
- gitpane/sequencer/replay_legacy.py +253 -0
- gitpane/sequencer/replay_state.py +747 -0
- gitpane/submodule/__init__.py +0 -0
- gitpane/submodule/checkout_refusal.py +102 -0
- gitpane/submodule/checkout_refusal_notice.py +111 -0
- gitpane/submodule/cloned_without_checkout.py +65 -0
- gitpane/submodule/submodule_discovery.py +1180 -0
- gitpane/submodule/submodule_loss.py +218 -0
- gitpane/system/__init__.py +0 -0
- gitpane/system/appearance_portal.py +125 -0
- gitpane/system/application_identity.py +48 -0
- gitpane/system/command_line.py +36 -0
- gitpane/system/desktop_icon_fact.py +128 -0
- gitpane/system/desktop_icon_support.py +270 -0
- gitpane/system/desktop_shortcut.py +536 -0
- gitpane/system/icns_image.py +35 -0
- gitpane/system/installed_launcher.py +136 -0
- gitpane/system/linux_distribution.py +151 -0
- gitpane/system/network_folder.py +46 -0
- gitpane/system/path_identity.py +95 -0
- gitpane/system/private_tree_removal.py +47 -0
- gitpane/system/process_program.py +63 -0
- gitpane/system/qt_folder.py +38 -0
- gitpane/system/ready_channel_watch.py +67 -0
- gitpane/system/startup_activation.py +65 -0
- gitpane/system/terminal_detach.py +384 -0
- gitpane/system/xdg_session.py +85 -0
- gitpane/text/__init__.py +0 -0
- gitpane/text/text_query.py +97 -0
- gitpane/text/typed_path.py +75 -0
- gitpane/text/typed_text.py +17 -0
- gitpane/thread/__init__.py +0 -0
- gitpane/thread/weak_signal.py +93 -0
- gitpane/tool/__init__.py +0 -0
- gitpane/tool/app_bundle.py +164 -0
- gitpane/tool/desktop_text_editor.py +156 -0
- gitpane/tool/difftool.py +429 -0
- gitpane/tool/editor.py +445 -0
- gitpane/tool/editor_bridge.py +193 -0
- gitpane/tool/editor_recipe.py +481 -0
- gitpane/tool/file_reveal.py +94 -0
- gitpane/tool/git_executable.py +264 -0
- gitpane/tool/git_version.py +62 -0
- gitpane/tool/kdiff3.py +41 -0
- gitpane/tool/kdiff3_install.py +162 -0
- gitpane/tool/launch_services_text_editor.py +104 -0
- gitpane/tool/mergetool_recipe.py +84 -0
- gitpane/tool/system_editor_default.py +80 -0
- gitpane/tool/system_text_editor.py +44 -0
- gitpane/tool/system_tool.py +816 -0
- gitpane/tool/terminal_recipe.py +390 -0
- gitpane/transport/__init__.py +0 -0
- gitpane/transport/branch_exchange.py +1690 -0
- gitpane/transport/clone.py +1273 -0
- gitpane/transport/credential_read_retry.py +35 -0
- gitpane/transport/git_refusal.py +207 -0
- gitpane/transport/push_tracking.py +121 -0
- gitpane/transport/refusal_notice.py +225 -0
- gitpane/transport/remote.py +747 -0
- gitpane/transport/remote_config.py +544 -0
- gitpane/ui/__init__.py +0 -0
- gitpane/ui/console/__init__.py +0 -0
- gitpane/ui/console/error_dialog.py +332 -0
- gitpane/ui/console/operation_dialog.py +408 -0
- gitpane/ui/console/operation_output.py +91 -0
- gitpane/ui/console/operation_output_view.py +373 -0
- gitpane/ui/emoji/__init__.py +0 -0
- gitpane/ui/emoji/emoji_font.py +257 -0
- gitpane/ui/emoji/emoji_grid.py +125 -0
- gitpane/ui/emoji/emoji_window.py +154 -0
- gitpane/ui/emoji/file_action_icon.py +131 -0
- gitpane/ui/emoji/opentype_family_name.py +182 -0
- gitpane/ui/history_rewrite/__init__.py +0 -0
- gitpane/ui/history_rewrite/details.py +78 -0
- gitpane/ui/history_rewrite/preview.py +565 -0
- gitpane/ui/history_rewrite/preview_model.py +291 -0
- gitpane/ui/history_rewrite/question.py +87 -0
- gitpane/ui/history_rewrite/recovery_dialog.py +262 -0
- gitpane/ui/look/__init__.py +0 -0
- gitpane/ui/look/action_icon.py +504 -0
- gitpane/ui/look/application_icon.py +78 -0
- gitpane/ui/look/application_style.py +317 -0
- gitpane/ui/look/avatar_label.py +53 -0
- gitpane/ui/look/color_contrast.py +43 -0
- gitpane/ui/look/desktop_appearance_follower.py +118 -0
- gitpane/ui/look/fixed_font.py +21 -0
- gitpane/ui/look/gtk_settings.py +116 -0
- gitpane/ui/look/icon_density.py +16 -0
- gitpane/ui/look/reference_font.py +19 -0
- gitpane/ui/look/theme_color.py +63 -0
- gitpane/ui/look/theme_look.py +95 -0
- gitpane/ui/look/theme_palette.py +173 -0
- gitpane/ui/look/warning_icon.py +86 -0
- gitpane/ui/look/windows_control_look.py +250 -0
- gitpane/ui/reader/__init__.py +0 -0
- gitpane/ui/reader/commit_message.py +129 -0
- gitpane/ui/reader/commit_panel.py +378 -0
- gitpane/ui/reader/diff_panel.py +626 -0
- gitpane/ui/reader/file_action.py +138 -0
- gitpane/ui/reader/file_tree_panel.py +235 -0
- gitpane/ui/reader/reader_toolbar.py +56 -0
- gitpane/ui/reader/revision_tree.py +80 -0
- gitpane/ui/reader/search_result.py +165 -0
- gitpane/ui/reader/source_text.py +134 -0
- gitpane/ui/reader/source_view.py +151 -0
- gitpane/ui/reader/syntax_span.py +67 -0
- gitpane/ui/reference/__init__.py +0 -0
- gitpane/ui/reference/branch_selector.py +106 -0
- gitpane/ui/reference/checkout_dialog.py +419 -0
- gitpane/ui/reference/checkout_revision_dialog.py +182 -0
- gitpane/ui/reference/commit_summary.py +339 -0
- gitpane/ui/reference/create_branch_dialog.py +346 -0
- gitpane/ui/reference/create_tag_dialog.py +283 -0
- gitpane/ui/reference/delete_local_branch_dialog.py +323 -0
- gitpane/ui/reference/delete_tag_dialog.py +394 -0
- gitpane/ui/reference/form_opening.py +53 -0
- gitpane/ui/reference/reference_dialog.py +417 -0
- gitpane/ui/reference/reference_feedback.py +62 -0
- gitpane/ui/reference/reference_form.py +139 -0
- gitpane/ui/reference/rename_branch_dialog.py +179 -0
- gitpane/ui/reference/reset_branch_dialog.py +430 -0
- gitpane/ui/reference/reset_dialog.py +263 -0
- gitpane/ui/reference/revision_dialog.py +121 -0
- gitpane/ui/reference/revision_picker.py +227 -0
- gitpane/ui/reference/select_branches_dialog.py +199 -0
- gitpane/ui/reference/stash_dialog.py +171 -0
- gitpane/ui/repository/__init__.py +0 -0
- gitpane/ui/repository/graph_delegate.py +298 -0
- gitpane/ui/repository/history_avatar.py +130 -0
- gitpane/ui/repository/history_model.py +179 -0
- gitpane/ui/repository/repository_list_popup.py +319 -0
- gitpane/ui/repository/repository_window.py +2580 -0
- gitpane/ui/resource/ArrowDown.png +0 -0
- gitpane/ui/resource/ArrowUp.png +0 -0
- gitpane/ui/resource/BranchCheckout.png +0 -0
- gitpane/ui/resource/BranchCreate.png +0 -0
- gitpane/ui/resource/BranchDelete.png +0 -0
- gitpane/ui/resource/CopyToClipboard.png +0 -0
- gitpane/ui/resource/GitPane-16.png +0 -0
- gitpane/ui/resource/GitPane-24.png +0 -0
- gitpane/ui/resource/GitPane-256.png +0 -0
- gitpane/ui/resource/GitPane-32.png +0 -0
- gitpane/ui/resource/GitPane-48.png +0 -0
- gitpane/ui/resource/GitPane-64.png +0 -0
- gitpane/ui/resource/GitPane.ico +0 -0
- gitpane/ui/resource/NotoColorEmoji-OFL.txt +92 -0
- gitpane/ui/resource/NotoColorEmoji_WindowsCompatible.ttf +0 -0
- gitpane/ui/resource/Renamed.png +0 -0
- gitpane/ui/resource/ResetAnotherBranchToHere.png +0 -0
- gitpane/ui/resource/ResetCurrentBranchToHere.png +0 -0
- gitpane/ui/resource/Stash.png +0 -0
- gitpane/ui/resource/Tag.png +0 -0
- gitpane/ui/resource/TagCreate.png +0 -0
- gitpane/ui/resource/TagDelete.png +0 -0
- gitpane/ui/resource/Unmerged.png +0 -0
- gitpane/ui/resource/checkout.png +0 -0
- gitpane/ui/sequencer/__init__.py +0 -0
- gitpane/ui/sequencer/conflict_dialog.py +532 -0
- gitpane/ui/sequencer/merge_recovery_dialog.py +67 -0
- gitpane/ui/sequencer/rebase_dialog.py +461 -0
- gitpane/ui/sequencer/rebase_editor_window.py +220 -0
- gitpane/ui/sequencer/replay_conflict_prompt.py +105 -0
- gitpane/ui/sequencer/replay_dialog.py +501 -0
- gitpane/ui/setting/__init__.py +0 -0
- gitpane/ui/setting/account_page.py +724 -0
- gitpane/ui/setting/add_account_dialog.py +1005 -0
- gitpane/ui/setting/credential_manager_dialog.py +151 -0
- gitpane/ui/setting/git_setting_widget.py +388 -0
- gitpane/ui/setting/setting_block.py +19 -0
- gitpane/ui/setting/setting_dialog.py +750 -0
- gitpane/ui/setting/ssh_widget.py +232 -0
- gitpane/ui/setting/utility_page.py +240 -0
- gitpane/ui/staging/__init__.py +0 -0
- gitpane/ui/staging/staging_action.py +356 -0
- gitpane/ui/staging/staging_window.py +745 -0
- gitpane/ui/transport/__init__.py +0 -0
- gitpane/ui/transport/clone_dialog.py +879 -0
- gitpane/ui/transport/push_dialog.py +580 -0
- gitpane/ui/transport/push_rejection_prompt.py +100 -0
- gitpane/ui/transport/push_tracking_question.py +88 -0
- gitpane/ui/transport/remote_dialog.py +943 -0
- gitpane/ui/welcome/__init__.py +0 -0
- gitpane/ui/welcome/invalid_repository_prompt.py +114 -0
- gitpane/ui/welcome/welcome_window.py +1137 -0
- gitpane/ui/widget/__init__.py +0 -0
- gitpane/ui/widget/busy_indicator.py +106 -0
- gitpane/ui/widget/commit_date_text.py +55 -0
- gitpane/ui/widget/content_growth.py +262 -0
- gitpane/ui/widget/content_measure.py +99 -0
- gitpane/ui/widget/dialog_layout.py +668 -0
- gitpane/ui/widget/drop_down_wheel_filter.py +32 -0
- gitpane/ui/widget/flow_layout.py +110 -0
- gitpane/ui/widget/long_text.py +542 -0
- gitpane/ui/widget/mnemonic.py +13 -0
- gitpane/ui/widget/numbered_text_edit.py +127 -0
- gitpane/ui/widget/plain_label.py +15 -0
- gitpane/ui/widget/plain_tooltip.py +19 -0
- gitpane/ui/widget/refresh_advice.py +25 -0
- gitpane/ui/widget/result_label.py +227 -0
- gitpane/ui/widget/result_status_bar.py +53 -0
- gitpane/ui/widget/scrolled_text.py +69 -0
- gitpane/ui/widget/shortcut_text.py +31 -0
- gitpane/ui/widget/standard_icon_label.py +55 -0
- gitpane/ui/widget/system_shortcut.py +75 -0
- gitpane/ui/widget/unavailable_reason.py +29 -0
- gitpane/ui/widget/wheel_safe_drop_down.py +24 -0
- gitpane/ui/window_control/__init__.py +0 -0
- gitpane/ui/window_control/button_focus.py +42 -0
- gitpane/ui/window_control/compositor_answer.py +152 -0
- gitpane/ui/window_control/compositor_growth_cap.py +73 -0
- gitpane/ui/window_control/desktop_activation.py +203 -0
- gitpane/ui/window_control/dialog_modality.py +122 -0
- gitpane/ui/window_control/item_activation.py +42 -0
- gitpane/ui/window_control/modal_focus_relay.py +124 -0
- gitpane/ui/window_control/wayland_activation.py +695 -0
- gitpane/ui/window_control/window_placement.py +305 -0
- gitpane/vendor/DISTLIB-LICENSE.txt +284 -0
- gitpane/vendor/w32.exe +0 -0
- gitpane/vendor/w64-arm.exe +0 -0
- gitpane/vendor/w64.exe +0 -0
- gitpane/worktree/__init__.py +0 -0
- gitpane/worktree/command_sequence.py +110 -0
- gitpane/worktree/file_action_target.py +61 -0
- gitpane/worktree/file_menu_action.py +52 -0
- gitpane/worktree/historical_restore.py +361 -0
- gitpane/worktree/ignore_rule.py +266 -0
- gitpane/worktree/mutation.py +1539 -0
- gitpane/worktree/partial_patch.py +435 -0
- gitpane/worktree/staged_reset.py +447 -0
- gitpane/worktree/staging.py +1418 -0
- gitpane/worktree/trash.py +302 -0
- gitpane/worktree/worktree_file.py +406 -0
- gitpane/worktree/worktree_service.py +584 -0
- gitpane/worktree/worktree_snapshot.py +630 -0
- gitpane-1.0.0.dist-info/METADATA +141 -0
- gitpane-1.0.0.dist-info/RECORD +353 -0
- gitpane-1.0.0.dist-info/WHEEL +5 -0
- gitpane-1.0.0.dist-info/entry_points.txt +2 -0
- gitpane-1.0.0.dist-info/licenses/LICENSE +21 -0
- gitpane-1.0.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +109 -0
- gitpane-1.0.0.dist-info/licenses/src/gitpane/ui/resource/NotoColorEmoji-OFL.txt +92 -0
- gitpane-1.0.0.dist-info/licenses/src/gitpane/vendor/DISTLIB-LICENSE.txt +284 -0
- gitpane-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,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)()
|