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,394 @@
|
|
|
1
|
+
"""Reproduce the Git Extensions "Delete tag" window: a tag list, Delete, an optional deletion on one remote."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from PySide6.QtCore import QMargins, QRect, QSignalBlocker, QSize, Qt, Signal
|
|
9
|
+
from PySide6.QtWidgets import QCheckBox, QComboBox, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
|
|
10
|
+
|
|
11
|
+
from gitpane.config import DELETE_BUTTON_TEXT, SharedText
|
|
12
|
+
from gitpane.history.repository_value import ReferenceKind
|
|
13
|
+
from gitpane.operation.operation_status import OperationStatus
|
|
14
|
+
from gitpane.reference.reference_edit import DEFAULT_PUSH_REMOTE_NAME, DELETE_LOCAL_TAG_OPERATION, DELETE_REMOTE_TAG_OPERATION, ReferenceDeletionIntent
|
|
15
|
+
from gitpane.reference.reference_snapshot import short_reference_name
|
|
16
|
+
from gitpane.text.typed_text import typed_value
|
|
17
|
+
from gitpane.ui.look.theme_look import follow_theme
|
|
18
|
+
from gitpane.ui.look.windows_control_look import apply_windows_button_look, apply_windows_combo_look
|
|
19
|
+
from gitpane.ui.reference.reference_feedback import ReferenceSummaryLabel
|
|
20
|
+
from gitpane.ui.reference.reference_form import ReferenceFormDialog
|
|
21
|
+
from gitpane.ui.widget.content_growth import ContentGrowthMixin, OpeningFit
|
|
22
|
+
from gitpane.ui.widget.content_measure import LayoutContentMeasure
|
|
23
|
+
from gitpane.ui.widget.dialog_layout import fit_caption_column, fit_capture_control, install_plain_form_palette
|
|
24
|
+
from gitpane.ui.widget.scrolled_text import ScrolledText
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from gitpane.operation.repository_operation import OperationOutcome
|
|
28
|
+
from gitpane.reference.reference_snapshot import ReferenceSnapshot
|
|
29
|
+
from gitpane.transport.remote_config import RemoteSnapshot
|
|
30
|
+
|
|
31
|
+
DELETE_TAG_TITLE = "Delete tag"
|
|
32
|
+
SELECT_TAG_CAPTION = "Select tag"
|
|
33
|
+
LOCAL_DELETION_TEXT = "This will delete the selected tag from the (local) repository."
|
|
34
|
+
REMOTE_DELETION_CAPTION = "Delete tag also from the following remote(s):"
|
|
35
|
+
REMOTE_LIST_NAME = "Remote"
|
|
36
|
+
RETRY_BUTTON_TEXT = "Retry failed step"
|
|
37
|
+
NO_TAG_CAUSE = "No tags are available"
|
|
38
|
+
LOCAL_DESTINATION = "Local"
|
|
39
|
+
# Refusals of a remote deletion, written before anything is deleted, locally or on a server.
|
|
40
|
+
SEVERAL_PUSH_ADDRESSES_CAUSE = "Remote '{0}' has several push addresses. GitPane deletes a tag on one server only; the tag was not deleted."
|
|
41
|
+
NO_PUSH_ADDRESS_CAUSE = "Remote '{0}' has no push address; the tag was not deleted."
|
|
42
|
+
UNUSABLE_REMOTE_CAUSE = "Remote '{0}' is not configured unambiguously; the tag was not deleted."
|
|
43
|
+
NO_REMOTE_CAUSE = "Select the remote to delete the tag from"
|
|
44
|
+
REMOTE_READ_FAILED_CAUSE = "The remote configuration could not be read: {0}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DeleteTagDialogText(Enum):
|
|
48
|
+
"""Texts only the Delete tag window shows, one member per text so a misspelt name is an error."""
|
|
49
|
+
|
|
50
|
+
DELETED = "deleted"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Wording of the step statuses that have one in the result summary. Any other status is written as its own word, the
|
|
54
|
+
# way Git and the transport name it: that choice is explicit, and a test lists the statuses it covers.
|
|
55
|
+
STEP_SUMMARY_BY_STATUS: dict[OperationStatus, DeleteTagDialogText | SharedText] = {
|
|
56
|
+
OperationStatus.SUCCESS: DeleteTagDialogText.DELETED,
|
|
57
|
+
OperationStatus.UNKNOWN: SharedText.OUTCOME_UNKNOWN,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def step_summary_word(status: OperationStatus) -> str:
|
|
62
|
+
"""Give the word of a step status in the result summary: its wording in the table, or else the status's own word."""
|
|
63
|
+
text = STEP_SUMMARY_BY_STATUS.get(status)
|
|
64
|
+
return status.value if text is None else text.value
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
RETRYABLE_OUTCOME_STATUS_SET = frozenset((OperationStatus.FAILED, OperationStatus.REFUSED, OperationStatus.PARTIAL))
|
|
68
|
+
RETRYABLE_STEP_STATUS_SET = frozenset((OperationStatus.FAILED, OperationStatus.REFUSED, OperationStatus.NOT_STARTED))
|
|
69
|
+
# Client area of the Git Extensions capture, 454 x 171 inside the window borders and title bar.
|
|
70
|
+
DELETE_TAG_CAPTURE_CLIENT_SIZE = QSize(454, 171)
|
|
71
|
+
# Height of the capture's bottom row, a Help link and its explanation; GitPane has no help, so the window loses that row.
|
|
72
|
+
HELP_ROW_HEIGHT = 25
|
|
73
|
+
DELETE_TAG_CLIENT_SIZE = QSize(DELETE_TAG_CAPTURE_CLIENT_SIZE.width(), DELETE_TAG_CAPTURE_CLIENT_SIZE.height() - HELP_ROW_HEIGHT)
|
|
74
|
+
# Measured on the capture in client pixels at 100 %, visible borders included: "Select tag" ink from x 10, y 18; tag list
|
|
75
|
+
# (126, 12, 237, 23); Delete button (370, 11, 73, 23), right of the list rather than in a button band; local text ink
|
|
76
|
+
# from x 10, y 55; check box square (10, 83, 13, 13) with its text from x 29; remote list (10, 106, 270, 23).
|
|
77
|
+
DELETE_TAG_CAPTION_LEFT = 10
|
|
78
|
+
TAG_COMBO_RECT = QRect(126, 12, 237, 23)
|
|
79
|
+
DELETE_BUTTON_RECT = QRect(370, 11, 73, 23)
|
|
80
|
+
REMOTE_COMBO_RECT = QRect(10, 106, 270, 23)
|
|
81
|
+
# Width of the caption, from its left edge to the tag list; a wider caption text widens it.
|
|
82
|
+
SELECT_TAG_CAPTION_WIDTH = TAG_COMBO_RECT.left() - DELETE_TAG_CAPTION_LEFT
|
|
83
|
+
# The window's own margins: the captions, the Delete button's top and right edges and the remote list's bottom set them.
|
|
84
|
+
DELETE_TAG_MARGINS = QMargins(DELETE_TAG_CAPTION_LEFT, DELETE_BUTTON_RECT.top(), DELETE_TAG_CLIENT_SIZE.width() - DELETE_BUTTON_RECT.right() - 1,
|
|
85
|
+
DELETE_TAG_CLIENT_SIZE.height() - REMOTE_COMBO_RECT.bottom() - 1)
|
|
86
|
+
# The tag list sits 1 px lower than the Delete button in the capture.
|
|
87
|
+
TAG_COMBO_DROP = TAG_COMBO_RECT.top() - DELETE_BUTTON_RECT.top()
|
|
88
|
+
TAG_COMBO_TO_BUTTON_SPACE = DELETE_BUTTON_RECT.left() - TAG_COMBO_RECT.right() - 1
|
|
89
|
+
# Vertical spaces between the rows, fitted so that Qt's text, square and list land where the capture has them in the
|
|
90
|
+
# Fusion style: the local text's ink on y 55, the check box square on y 83 and the remote list on y 106.
|
|
91
|
+
FIRST_ROW_TO_LOCAL_TEXT_SPACE = 16
|
|
92
|
+
LOCAL_TEXT_TO_CHECK_BOX_SPACE = 13
|
|
93
|
+
CHECK_BOX_TO_REMOTE_COMBO_SPACE = 5
|
|
94
|
+
# Space between the remote list and the result summary shown after a failed step.
|
|
95
|
+
REMOTE_COMBO_TO_SUMMARY_SPACE = 8
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class DeleteTagDialog(ContentGrowthMixin, ReferenceFormDialog):
|
|
99
|
+
"""Delete one tag locally, and optionally on one remote, like the Git Extensions Delete tag window.
|
|
100
|
+
|
|
101
|
+
The window only freezes a request. The reference form controller reads the remote configuration the first time the check box is
|
|
102
|
+
checked, turns the chosen remote into its single push address, runs Git and answers with ``show_outcome`` or
|
|
103
|
+
``show_error``. The reference form controller, which runs the deletion, reads these attributes: ``intent`` (the tag deletion), ``remote_request``
|
|
104
|
+
(remote configuration, remote name, tag, push address and push target, or None for a local deletion),
|
|
105
|
+
``remote_snapshot`` (the remote configuration read for this window), ``outcome`` (the last result),
|
|
106
|
+
``retry_operation_id`` and ``retry_remote_success`` (set while retrying), ``remote_branch`` (always False: this
|
|
107
|
+
window deletes a tag) and ``local_intents`` (always empty: a tag has no local tracking branch to delete with it).
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
delete_requested = Signal(object)
|
|
111
|
+
remote_requested = Signal()
|
|
112
|
+
retry_requested = Signal()
|
|
113
|
+
|
|
114
|
+
def __init__(self, session_id: str, parent: QWidget | None = None) -> None:
|
|
115
|
+
"""Build the rows of the capture without its help row, with the remote list disabled until the check box is checked."""
|
|
116
|
+
super().__init__(session_id, parent)
|
|
117
|
+
self.intent: ReferenceDeletionIntent | None = None
|
|
118
|
+
self.setWindowTitle(DELETE_TAG_TITLE)
|
|
119
|
+
self.full_ref = ""
|
|
120
|
+
self.remote_snapshot: RemoteSnapshot | None = None
|
|
121
|
+
self.remote_request: tuple | None = None
|
|
122
|
+
# Set once the remote configuration was asked for, so unchecking and checking again does not read it twice.
|
|
123
|
+
self.remote_read_requested = False
|
|
124
|
+
self.outcome: OperationOutcome | None = None
|
|
125
|
+
self.retry_operation_id = ""
|
|
126
|
+
self.retry_remote_success = False
|
|
127
|
+
self.remote_branch = False
|
|
128
|
+
self.local_intents: tuple[ReferenceDeletionIntent, ...] = ()
|
|
129
|
+
install_plain_form_palette(self)
|
|
130
|
+
layout = QVBoxLayout(self)
|
|
131
|
+
layout.setContentsMargins(DELETE_TAG_MARGINS)
|
|
132
|
+
layout.setSpacing(0)
|
|
133
|
+
self.caption_label = QLabel(SELECT_TAG_CAPTION)
|
|
134
|
+
self.tag_combo = QComboBox()
|
|
135
|
+
# The capture shows the tag name selected in a list one can type into; typed names are matched against the list.
|
|
136
|
+
self.tag_combo.setEditable(True)
|
|
137
|
+
self.tag_combo.setInsertPolicy(QComboBox.InsertPolicy.NoInsert)
|
|
138
|
+
self.tag_combo.setAccessibleName(SELECT_TAG_CAPTION)
|
|
139
|
+
self.caption_label.setBuddy(self.tag_combo)
|
|
140
|
+
tag_column = QVBoxLayout()
|
|
141
|
+
tag_column.setContentsMargins(0, TAG_COMBO_DROP, 0, 0)
|
|
142
|
+
tag_column.setSpacing(0)
|
|
143
|
+
tag_column.addWidget(self.tag_combo)
|
|
144
|
+
self.submit_button.setText(DELETE_BUTTON_TEXT)
|
|
145
|
+
self.submit_button.setEnabled(False)
|
|
146
|
+
first_row = QHBoxLayout()
|
|
147
|
+
first_row.setContentsMargins(0, 0, 0, 0)
|
|
148
|
+
first_row.setSpacing(0)
|
|
149
|
+
first_row.addWidget(self.caption_label, 0, Qt.AlignmentFlag.AlignVCenter)
|
|
150
|
+
first_row.addLayout(tag_column, 1)
|
|
151
|
+
first_row.addSpacing(TAG_COMBO_TO_BUTTON_SPACE)
|
|
152
|
+
first_row.addWidget(self.submit_button, 0, Qt.AlignmentFlag.AlignTop)
|
|
153
|
+
layout.addLayout(first_row)
|
|
154
|
+
layout.addSpacing(FIRST_ROW_TO_LOCAL_TEXT_SPACE)
|
|
155
|
+
self.local_label = QLabel(LOCAL_DELETION_TEXT)
|
|
156
|
+
layout.addWidget(self.local_label)
|
|
157
|
+
layout.addSpacing(LOCAL_TEXT_TO_CHECK_BOX_SPACE)
|
|
158
|
+
self.remote_checkbox = QCheckBox(REMOTE_DELETION_CAPTION)
|
|
159
|
+
layout.addWidget(self.remote_checkbox)
|
|
160
|
+
layout.addSpacing(CHECK_BOX_TO_REMOTE_COMBO_SPACE)
|
|
161
|
+
self.remote_combo = QComboBox()
|
|
162
|
+
self.remote_combo.setAccessibleName(REMOTE_LIST_NAME)
|
|
163
|
+
self.remote_combo.setFixedWidth(REMOTE_COMBO_RECT.width())
|
|
164
|
+
self.remote_combo.setEnabled(False)
|
|
165
|
+
layout.addWidget(self.remote_combo, 0, Qt.AlignmentFlag.AlignLeft)
|
|
166
|
+
# Shown only after a result that did not delete everything: each destination's result, then Retry failed step.
|
|
167
|
+
self.summary_space = QWidget()
|
|
168
|
+
self.summary_space.setFixedHeight(REMOTE_COMBO_TO_SUMMARY_SPACE)
|
|
169
|
+
self.summary_space.hide()
|
|
170
|
+
layout.addWidget(self.summary_space)
|
|
171
|
+
self.status_label = ReferenceSummaryLabel()
|
|
172
|
+
# A server's refusal can be longer than the screen: the summary scrolls past the window's growth cap, so Retry
|
|
173
|
+
# failed step under it stays inside the window.
|
|
174
|
+
self.status_area = ScrolledText(self.status_label)
|
|
175
|
+
self.status_area.hide()
|
|
176
|
+
layout.addWidget(self.status_area)
|
|
177
|
+
self.retry_button = QPushButton(RETRY_BUTTON_TEXT)
|
|
178
|
+
self.retry_button.setAutoDefault(False)
|
|
179
|
+
self.retry_button.hide()
|
|
180
|
+
layout.addWidget(self.retry_button, 0, Qt.AlignmentFlag.AlignLeft)
|
|
181
|
+
layout.addStretch(1)
|
|
182
|
+
self.apply_theme_look()
|
|
183
|
+
follow_theme(self, self.apply_theme_look)
|
|
184
|
+
# Enter runs Delete from anywhere in the form. The role is set only once the button belongs to the dialog, so Qt
|
|
185
|
+
# records it as the dialog's main default. There is no Cancel button: Escape and the title bar close the window.
|
|
186
|
+
self.submit_button.setDefault(True)
|
|
187
|
+
self.submit_button.clicked.connect(self._submit)
|
|
188
|
+
self.retry_button.clicked.connect(self.retry_requested.emit)
|
|
189
|
+
self.tag_combo.currentIndexChanged.connect(self._update_intent)
|
|
190
|
+
self.tag_combo.editTextChanged.connect(self._update_intent)
|
|
191
|
+
self.remote_checkbox.toggled.connect(self._remote_toggled)
|
|
192
|
+
self.remote_combo.currentIndexChanged.connect(self._update_intent)
|
|
193
|
+
self.resize(DELETE_TAG_CLIENT_SIZE)
|
|
194
|
+
self.refresh_metrics()
|
|
195
|
+
self.install_content_growth(LayoutContentMeasure(self), OpeningFit.GROW_ONLY, self.height())
|
|
196
|
+
self.tag_combo.setFocus()
|
|
197
|
+
|
|
198
|
+
def apply_theme_look(self) -> None:
|
|
199
|
+
"""Lay the light or dark look of the tag list, Delete, the remote list and Retry failed step, decided on the application palette.
|
|
200
|
+
|
|
201
|
+
Called by the constructor and again at each switch. The remote list and Retry are built with the form, hidden or
|
|
202
|
+
disabled until needed, so they always carry the look of the current appearance. Git Extensions draws the remote
|
|
203
|
+
list like a field one can type into (a light grey border while disabled); GitPane keeps it a plain choice among
|
|
204
|
+
the configured remotes, so no typed name can reach the deletion. Only style sheets and palettes change: the
|
|
205
|
+
tag, the remote choice and any result shown stay as they are.
|
|
206
|
+
"""
|
|
207
|
+
apply_windows_combo_look(self.tag_combo, editable=True)
|
|
208
|
+
apply_windows_button_look(self.submit_button)
|
|
209
|
+
apply_windows_combo_look(self.remote_combo, editable=True)
|
|
210
|
+
apply_windows_button_look(self.retry_button)
|
|
211
|
+
|
|
212
|
+
def fit_capture_controls(self) -> None:
|
|
213
|
+
"""Give the caption and the Delete button the capture's sizes, or more when their text needs it; a wider caption moves the list right."""
|
|
214
|
+
fit_caption_column((self.caption_label,), SELECT_TAG_CAPTION_WIDTH)
|
|
215
|
+
fit_capture_control(self.submit_button, DELETE_BUTTON_RECT.size())
|
|
216
|
+
|
|
217
|
+
def populate(self, snapshot: ReferenceSnapshot, full_ref: str) -> None:
|
|
218
|
+
"""Offer the tags and remotes of the references the window was opened on, the clicked tag selected.
|
|
219
|
+
|
|
220
|
+
No Git process is needed: the remote names come from the same capture. A capture arriving after the click or
|
|
221
|
+
after a result changes nothing, so it never replaces a deletion already asked or done.
|
|
222
|
+
"""
|
|
223
|
+
if self.closed or self.busy or self.completed or self.outcome is not None:
|
|
224
|
+
return
|
|
225
|
+
self.snapshot = snapshot
|
|
226
|
+
self.full_ref = full_ref
|
|
227
|
+
with QSignalBlocker(self.tag_combo), QSignalBlocker(self.remote_combo):
|
|
228
|
+
self.tag_combo.clear()
|
|
229
|
+
for reference in sorted(snapshot.references, key=lambda reference: reference.full_name):
|
|
230
|
+
if reference.kind == ReferenceKind.TAGS and reference.target_oid and not reference.symbolic_target:
|
|
231
|
+
self.tag_combo.addItem(short_reference_name(reference.full_name), reference.full_name)
|
|
232
|
+
self.tag_combo.setCurrentIndex(self.tag_combo.findData(full_ref))
|
|
233
|
+
self.remote_combo.clear()
|
|
234
|
+
for remote_name in sorted(snapshot.remote_names):
|
|
235
|
+
self.remote_combo.addItem(remote_name, remote_name)
|
|
236
|
+
default_index = self.remote_combo.findData(DEFAULT_PUSH_REMOTE_NAME)
|
|
237
|
+
first_index = 0
|
|
238
|
+
self.remote_combo.setCurrentIndex(default_index if default_index >= 0 else first_index if self.remote_combo.count() else -1)
|
|
239
|
+
self.tag_combo.lineEdit().selectAll()
|
|
240
|
+
self.tag_combo.setFocus()
|
|
241
|
+
self._update_intent()
|
|
242
|
+
if not self.tag_combo.count():
|
|
243
|
+
self.submit_button.setToolTip(NO_TAG_CAUSE)
|
|
244
|
+
|
|
245
|
+
def selected_ref(self) -> str:
|
|
246
|
+
"""Return the full name of the tag written in the list, or an empty text when it names no offered tag.
|
|
247
|
+
|
|
248
|
+
The typed name is taken without the blanks around it; the list keeps what was typed.
|
|
249
|
+
"""
|
|
250
|
+
index = self.tag_combo.findText(typed_value(self.tag_combo.currentText()), Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive)
|
|
251
|
+
if index < 0:
|
|
252
|
+
return ""
|
|
253
|
+
return self.tag_combo.itemData(index) or ""
|
|
254
|
+
|
|
255
|
+
def _update_intent(self) -> None:
|
|
256
|
+
"""Freeze the selected tag into a deletion request, and offer Delete once any remote choice can be checked."""
|
|
257
|
+
if self.busy or self.closed or self.completed:
|
|
258
|
+
return
|
|
259
|
+
self.intent = None
|
|
260
|
+
self.remote_request = None
|
|
261
|
+
self.full_ref = self.selected_ref()
|
|
262
|
+
if self.snapshot is not None and self.full_ref:
|
|
263
|
+
try:
|
|
264
|
+
self.intent = ReferenceDeletionIntent.capture(self.snapshot, self.full_ref, session_id=self.session_id)
|
|
265
|
+
except ValueError:
|
|
266
|
+
self.intent = None
|
|
267
|
+
remote_ready = not self.remote_checkbox.isChecked() or self.remote_snapshot is not None
|
|
268
|
+
self.submit_button.setToolTip("")
|
|
269
|
+
self.submit_button.setEnabled(self.intent is not None and remote_ready)
|
|
270
|
+
|
|
271
|
+
def _remote_toggled(self, checked: bool) -> None:
|
|
272
|
+
"""Offer the remote list only while the check box is checked, and ask for the remote configuration the first time."""
|
|
273
|
+
self.remote_combo.setEnabled(checked and not self.busy and not self.completed)
|
|
274
|
+
if checked and not self.remote_read_requested:
|
|
275
|
+
self.remote_read_requested = True
|
|
276
|
+
self.remote_requested.emit()
|
|
277
|
+
self._update_intent()
|
|
278
|
+
|
|
279
|
+
def populate_remote(self, remote_snapshot: RemoteSnapshot) -> None:
|
|
280
|
+
"""Keep the remote configuration read for this window, the one a remote deletion is checked against."""
|
|
281
|
+
if self.closed or self.busy or self.completed or self.outcome is not None:
|
|
282
|
+
return
|
|
283
|
+
self.remote_snapshot = remote_snapshot
|
|
284
|
+
self._update_intent()
|
|
285
|
+
|
|
286
|
+
def show_remote_error(self, cause: str) -> None:
|
|
287
|
+
"""Explain why the remote configuration could not be read and uncheck the remote option, so checking it reads again."""
|
|
288
|
+
if self.closed or self.busy or self.completed:
|
|
289
|
+
return
|
|
290
|
+
self.remote_read_requested = False
|
|
291
|
+
with QSignalBlocker(self.remote_checkbox):
|
|
292
|
+
self.remote_checkbox.setChecked(False)
|
|
293
|
+
self.remote_combo.setEnabled(False)
|
|
294
|
+
self._update_intent()
|
|
295
|
+
self.show_error(REMOTE_READ_FAILED_CAUSE.format(cause))
|
|
296
|
+
|
|
297
|
+
def _submit(self) -> None:
|
|
298
|
+
"""Freeze the form and hand its request to the reference form controller once."""
|
|
299
|
+
if self.closed or self.busy or self.completed or self.intent is None or not self.submit_button.isEnabled():
|
|
300
|
+
return
|
|
301
|
+
self.busy = True
|
|
302
|
+
self._set_controls_enabled(False)
|
|
303
|
+
self.delete_requested.emit(self.intent)
|
|
304
|
+
|
|
305
|
+
def show_error(self, cause: str) -> None:
|
|
306
|
+
"""Refuse or explain a failure: in a message box before any result, under the results after one.
|
|
307
|
+
|
|
308
|
+
Before any result nothing was deleted, so the form is given back for another choice. After a result, a retry
|
|
309
|
+
that could not be prepared keeps the results already obtained and adds its cause below them.
|
|
310
|
+
"""
|
|
311
|
+
self.busy = False
|
|
312
|
+
if self.outcome is None:
|
|
313
|
+
if not self.completed:
|
|
314
|
+
self._set_controls_enabled(True)
|
|
315
|
+
super().show_error(cause)
|
|
316
|
+
return
|
|
317
|
+
self._show_summary(self.outcome, [(cause, True)])
|
|
318
|
+
|
|
319
|
+
def show_outcome(self, outcome: OperationOutcome) -> None:
|
|
320
|
+
"""Close once every requested destination was deleted; otherwise keep the form with each destination's result.
|
|
321
|
+
|
|
322
|
+
After a failed step the results and, when the failed step can be tried again, Retry failed step appear under the
|
|
323
|
+
remote list. The window follows them both ways by the common growth rule: it grows to show them, and gives back
|
|
324
|
+
the height of the lines that leave when a shorter text replaces them, never below its opening height.
|
|
325
|
+
"""
|
|
326
|
+
self.busy = False
|
|
327
|
+
self.outcome = outcome
|
|
328
|
+
if outcome.status == OperationStatus.SUCCESS and self._requested_operation_set() <= self._successful_operation_set(outcome):
|
|
329
|
+
self.accept()
|
|
330
|
+
return
|
|
331
|
+
self.completed = True
|
|
332
|
+
self._set_controls_enabled(False)
|
|
333
|
+
self._show_summary(outcome, [])
|
|
334
|
+
|
|
335
|
+
def _requested_operation_set(self) -> set[str]:
|
|
336
|
+
"""Name the deletion steps this request asked for: always the local one, the remote one when a remote was chosen."""
|
|
337
|
+
operation_set = {DELETE_LOCAL_TAG_OPERATION}
|
|
338
|
+
if self.remote_request is not None:
|
|
339
|
+
operation_set.add(DELETE_REMOTE_TAG_OPERATION)
|
|
340
|
+
return operation_set
|
|
341
|
+
|
|
342
|
+
def _successful_operation_set(self, outcome: OperationOutcome) -> set[str]:
|
|
343
|
+
"""Name the deletion steps the result reports as done."""
|
|
344
|
+
operation_set: set[str] = set()
|
|
345
|
+
for step in outcome.steps:
|
|
346
|
+
if step.status == OperationStatus.SUCCESS:
|
|
347
|
+
operation_set.add(step.operation)
|
|
348
|
+
return operation_set
|
|
349
|
+
|
|
350
|
+
def _remote_name(self) -> str:
|
|
351
|
+
"""Name the chosen remote as the summary writes it, or a generic word for a local deletion."""
|
|
352
|
+
if self.remote_request is None:
|
|
353
|
+
return REMOTE_LIST_NAME
|
|
354
|
+
_remote_snapshot, remote_name, _destination, _endpoint, _push_target = self.remote_request
|
|
355
|
+
return remote_name
|
|
356
|
+
|
|
357
|
+
def _show_summary(self, outcome: OperationOutcome, extra_lines: list[tuple[str, bool]]) -> None:
|
|
358
|
+
"""Write one line per destination, its diagnostic and any extra cause, and offer Retry only for a failed step that can be retried."""
|
|
359
|
+
remote_name = self._remote_name()
|
|
360
|
+
destination_by_operation = {DELETE_LOCAL_TAG_OPERATION: LOCAL_DESTINATION, DELETE_REMOTE_TAG_OPERATION: remote_name}
|
|
361
|
+
summary_lines: list[tuple[str, bool]] = []
|
|
362
|
+
observed_operation_set: set[str] = set()
|
|
363
|
+
diagnostic_detail_set: set[str] = set()
|
|
364
|
+
for step in outcome.steps:
|
|
365
|
+
observed_operation_set.add(step.operation)
|
|
366
|
+
destination = destination_by_operation.get(step.operation, step.operation)
|
|
367
|
+
failed = step.status != OperationStatus.SUCCESS
|
|
368
|
+
summary_lines.append((destination + ": " + step_summary_word(step.status) + " — " + step.effect, failed))
|
|
369
|
+
if step.diagnostic is not None:
|
|
370
|
+
summary_lines.append((destination + ": " + step.diagnostic.detail, failed))
|
|
371
|
+
diagnostic_detail_set.add(step.diagnostic.detail)
|
|
372
|
+
if outcome.diagnostic is not None and outcome.diagnostic.detail not in diagnostic_detail_set:
|
|
373
|
+
summary_lines.append(("Operation: " + outcome.diagnostic.detail, outcome.status != OperationStatus.SUCCESS))
|
|
374
|
+
for operation in (DELETE_LOCAL_TAG_OPERATION, DELETE_REMOTE_TAG_OPERATION):
|
|
375
|
+
if operation in self._requested_operation_set() and operation not in observed_operation_set:
|
|
376
|
+
summary_lines.append((destination_by_operation[operation] + ": Awaiting result", False))
|
|
377
|
+
summary_lines.extend(extra_lines)
|
|
378
|
+
self.status_label.show_summary(summary_lines)
|
|
379
|
+
# A remote step whose outcome is unknown may have deleted the tag on the server: it is never retried blindly.
|
|
380
|
+
remote_unknown = any(step.operation == DELETE_REMOTE_TAG_OPERATION and step.status == OperationStatus.UNKNOWN for step in outcome.steps)
|
|
381
|
+
retryable = (outcome.status in RETRYABLE_OUTCOME_STATUS_SET and not remote_unknown
|
|
382
|
+
and any(step.status in RETRYABLE_STEP_STATUS_SET for step in outcome.steps))
|
|
383
|
+
self.summary_space.show()
|
|
384
|
+
self.status_area.show()
|
|
385
|
+
self.retry_button.setVisible(retryable)
|
|
386
|
+
self.retry_button.setEnabled(retryable)
|
|
387
|
+
|
|
388
|
+
def _set_controls_enabled(self, enabled: bool) -> None:
|
|
389
|
+
"""Enable or disable the choices of the form together; the remote list only ever while the check box is checked."""
|
|
390
|
+
self.tag_combo.setEnabled(enabled)
|
|
391
|
+
self.remote_checkbox.setEnabled(enabled)
|
|
392
|
+
self.remote_combo.setEnabled(enabled and self.remote_checkbox.isChecked())
|
|
393
|
+
if not enabled:
|
|
394
|
+
self.submit_button.setEnabled(False)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""End the opening of a reference form one of three ways: shown once its check accepts it, refused with its warning alone, or closed unseen.
|
|
2
|
+
|
|
3
|
+
A reference form opens on the references its repository window last read, and GitPane checks them again before the
|
|
4
|
+
form can do anything. When that check refuses (the branches changed since, the read failed, the window was refreshed
|
|
5
|
+
meanwhile), a form shown at once would only sit greyed behind its warning, and the user would have to close both. The
|
|
6
|
+
form therefore stays hidden until its check answers: accepted, it is shown filled; refused, it is closed unseen and the
|
|
7
|
+
refusal opens alone, under the form's title, over the window the form belongs to. A form whose check ends otherwise
|
|
8
|
+
(its filling failed, it stopped being current) is closed unseen without a warning. A form closed unseen is deleted,
|
|
9
|
+
since nobody can reopen it. A refusal that comes later, once the form is in use, stays in the form.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
from PySide6.QtCore import Qt
|
|
17
|
+
from PySide6.QtWidgets import QDialog
|
|
18
|
+
|
|
19
|
+
from gitpane.ui.reference.reference_form import reference_warning_box
|
|
20
|
+
from gitpane.ui.window_control.dialog_modality import open_window_modal
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from PySide6.QtWidgets import QWidget
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def reveal_form(dialog: QDialog) -> None:
|
|
27
|
+
"""Show a form its opening check accepted, blocking its repository window like any reference form; a form already shown is left as it is."""
|
|
28
|
+
if dialog.isVisible():
|
|
29
|
+
return
|
|
30
|
+
open_window_modal(dialog)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def close_unseen_form(dialog: QDialog) -> None:
|
|
34
|
+
"""Close a form that was never shown, so its registry and its late answers let it go and the next click opens a new one, then delete it.
|
|
35
|
+
|
|
36
|
+
The form is deleted at the next turn of the event loop rather than left hidden in its window until that window
|
|
37
|
+
closes: nobody can show it again, and each new click makes a new form.
|
|
38
|
+
"""
|
|
39
|
+
dialog.done(QDialog.DialogCode.Rejected)
|
|
40
|
+
dialog.deleteLater()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def refuse_form_opening(dialog: QDialog, cause: str) -> None:
|
|
44
|
+
"""Close a form its opening check refused without ever showing it, and show the refusal alone over its repository window.
|
|
45
|
+
|
|
46
|
+
The warning keeps the form's title, so the user knows which action was refused, and the text the form would have
|
|
47
|
+
shown, with anything sensitive removed. Closing the warning leaves nothing else open.
|
|
48
|
+
"""
|
|
49
|
+
window: QWidget | None = dialog.parentWidget()
|
|
50
|
+
box = reference_warning_box(dialog.windowTitle(), cause, window)
|
|
51
|
+
box.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
|
|
52
|
+
close_unseen_form(dialog)
|
|
53
|
+
open_window_modal(box)
|