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,323 @@
|
|
|
1
|
+
"""Reproduce the Git Extensions "Delete branch" window: a list of local branches to type or pick, then one Delete button."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from PySide6.QtCore import QMargins, QSize, Qt, Signal
|
|
9
|
+
from PySide6.QtWidgets import QComboBox, QHBoxLayout, QLabel, QMessageBox, QPushButton, QVBoxLayout, QWidget
|
|
10
|
+
from shiboken6 import isValid
|
|
11
|
+
|
|
12
|
+
from gitpane.config import DELETE_BRANCH_TITLE, DELETE_BUTTON_TEXT, ICON_SIDE, LOCAL_PREFIX
|
|
13
|
+
from gitpane.operation.operation_status import OperationStatus
|
|
14
|
+
from gitpane.reference.reference_snapshot import short_reference_name
|
|
15
|
+
from gitpane.text.typed_text import typed_value
|
|
16
|
+
from gitpane.ui.look.action_icon import action_icon
|
|
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_form import ReferenceFormDialog
|
|
20
|
+
from gitpane.ui.reference.select_branches_dialog import SELECT_BRANCHES_CAPTION, SelectBranchesDialog
|
|
21
|
+
from gitpane.ui.widget.dialog_layout import BandButtonSizing, fit_caption_column, fit_capture_control, install_banded_layout
|
|
22
|
+
from gitpane.ui.window_control.dialog_modality import open_window_modal, prepare_message_box
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from collections.abc import Callable
|
|
26
|
+
|
|
27
|
+
from gitpane.operation.repository_operation import OperationOutcome
|
|
28
|
+
from gitpane.reference.reference_edit import BranchDeletionIntent
|
|
29
|
+
from gitpane.reference.reference_snapshot import ReferenceSnapshot
|
|
30
|
+
|
|
31
|
+
MULTIPLE_BRANCHES_NAME = "Select multiple branches"
|
|
32
|
+
MULTIPLE_BRANCHES_HINT = "Type several branch names separated by spaces"
|
|
33
|
+
UNMERGED_QUESTION_HEADING = "The following branches are not fully merged:"
|
|
34
|
+
UNMERGED_QUESTION_ENDING = "Delete them anyway?"
|
|
35
|
+
NO_BRANCH_CAUSE = "Select at least one branch to delete"
|
|
36
|
+
CURRENT_BRANCH_CAUSE = "is the current branch"
|
|
37
|
+
OCCUPIED_BRANCH_CAUSE = "is checked out in a worktree or held by a rebase or bisect"
|
|
38
|
+
UNKNOWN_BRANCH_CAUSE = "is not a local branch"
|
|
39
|
+
# Typed branch names are separated by ASCII whitespace only: Git refuses the ASCII space and every ASCII control
|
|
40
|
+
# character in a branch name, but accepts other Unicode spaces such as the non-breaking space, which therefore
|
|
41
|
+
# belong to the name and never separate two of them.
|
|
42
|
+
BRANCH_NAME_SEPARATOR_PATTERN = re.compile(r"\s+", re.ASCII)
|
|
43
|
+
# Client area of the Git Extensions capture (412 x 91 inside the window borders and title bar).
|
|
44
|
+
DELETE_BRANCH_CLIENT_SIZE = QSize(412, 91)
|
|
45
|
+
# Measured on the capture in client pixels at 100 %: white body y 0-49, band line y 50, band y 51-90; "Select branches"
|
|
46
|
+
# ink from x 15; branch list (104, 12, 272, 23); multiple selection button (381, 11, 21, 23); Delete button
|
|
47
|
+
# (325, 59, 73, 23) in the band. The body starts 1 px left of the caption ink and at the multiple button's top.
|
|
48
|
+
DELETE_BRANCH_BODY_MARGINS = QMargins(14, 11, 10, 0)
|
|
49
|
+
# Caption width from its left edge to the branch list (104 - 14); a wider caption text widens it.
|
|
50
|
+
DELETE_BRANCH_CAPTION_WIDTH = 90
|
|
51
|
+
# Qt's first anti-aliased ink pixel sits on the text origin, one pixel left of the capture's ink.
|
|
52
|
+
DELETE_BRANCH_CAPTION_INDENT = 1
|
|
53
|
+
BRANCH_LIST_TO_BUTTON_SPACE = 5
|
|
54
|
+
MULTIPLE_BRANCHES_BUTTON_SIZE = QSize(21, 23)
|
|
55
|
+
DELETE_BUTTON_SIZE = QSize(73, 23)
|
|
56
|
+
# The list sits 1 px lower than the multiple selection button in the capture.
|
|
57
|
+
BRANCH_LIST_DROP = 1
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class DeleteLocalBranchDialog(ReferenceFormDialog):
|
|
61
|
+
"""Delete one or several local branches, like the Git Extensions Delete branch window.
|
|
62
|
+
|
|
63
|
+
The list takes branch names separated by spaces, as the Git Extensions list does; Git never allows an ASCII space
|
|
64
|
+
or control character in a branch name, and only those separate names, so the text reads back without ambiguity
|
|
65
|
+
even for a name holding another Unicode space. The window only checks the typed names against the
|
|
66
|
+
branches the reference form controller offered and hands their full names over; that controller decides which ones are merged,
|
|
67
|
+
asks about the others and runs Git.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
delete_requested = Signal(object)
|
|
71
|
+
|
|
72
|
+
def __init__(self, session_id: str, parent: QWidget | None = None) -> None:
|
|
73
|
+
"""Build the caption, the editable branch list, the multiple selection button and the button band of the capture."""
|
|
74
|
+
super().__init__(session_id, parent)
|
|
75
|
+
self.setWindowTitle(DELETE_BRANCH_TITLE)
|
|
76
|
+
# The request the reference form controller froze once every branch was classified and every question answered.
|
|
77
|
+
self.intent: BranchDeletionIntent | None = None
|
|
78
|
+
# The full names of the local branches this window may delete, as the reference form controller computed them at opening.
|
|
79
|
+
self.candidate_refs: tuple[str, ...] = ()
|
|
80
|
+
# The Select multiple branches window while it is open.
|
|
81
|
+
self.select_dialog: SelectBranchesDialog | None = None
|
|
82
|
+
self.unmerged_question: QMessageBox | None = None
|
|
83
|
+
# Raised for each request the reference form controller has to ask Git about, so a late answer for an older one is dropped.
|
|
84
|
+
self.classification_generation = 0
|
|
85
|
+
layout = QVBoxLayout()
|
|
86
|
+
layout.setSpacing(0)
|
|
87
|
+
row = QHBoxLayout()
|
|
88
|
+
row.setContentsMargins(0, 0, 0, 0)
|
|
89
|
+
row.setSpacing(0)
|
|
90
|
+
self.caption_label = QLabel(SELECT_BRANCHES_CAPTION)
|
|
91
|
+
self.caption_label.setContentsMargins(DELETE_BRANCH_CAPTION_INDENT, 0, 0, 0)
|
|
92
|
+
self.branch_combo = QComboBox()
|
|
93
|
+
self.branch_combo.setEditable(True)
|
|
94
|
+
# Typed names stay in the text; the list itself only ever holds the offered branches.
|
|
95
|
+
self.branch_combo.setInsertPolicy(QComboBox.InsertPolicy.NoInsert)
|
|
96
|
+
self.branch_combo.setAccessibleName(SELECT_BRANCHES_CAPTION)
|
|
97
|
+
self.caption_label.setBuddy(self.branch_combo)
|
|
98
|
+
branch_column = QVBoxLayout()
|
|
99
|
+
branch_column.setContentsMargins(0, BRANCH_LIST_DROP, 0, 0)
|
|
100
|
+
branch_column.setSpacing(0)
|
|
101
|
+
branch_column.addWidget(self.branch_combo)
|
|
102
|
+
self.multiple_button = QPushButton()
|
|
103
|
+
self.multiple_button.setIcon(action_icon("choose_revision", self.devicePixelRatioF()))
|
|
104
|
+
self.multiple_button.setIconSize(QSize(ICON_SIDE, ICON_SIDE))
|
|
105
|
+
self.multiple_button.setFixedSize(MULTIPLE_BRANCHES_BUTTON_SIZE)
|
|
106
|
+
self.multiple_button.setAccessibleName(MULTIPLE_BRANCHES_NAME)
|
|
107
|
+
self.multiple_button.setToolTip(MULTIPLE_BRANCHES_HINT)
|
|
108
|
+
# Only the Delete button may be the default one; see the Enter handling of the reference forms.
|
|
109
|
+
self.multiple_button.setAutoDefault(False)
|
|
110
|
+
# Offered once the window knows its branches.
|
|
111
|
+
self.multiple_button.setEnabled(False)
|
|
112
|
+
row.addWidget(self.caption_label, 0, Qt.AlignmentFlag.AlignVCenter)
|
|
113
|
+
row.addLayout(branch_column, 1)
|
|
114
|
+
row.addSpacing(BRANCH_LIST_TO_BUTTON_SPACE)
|
|
115
|
+
row.addWidget(self.multiple_button, 0, Qt.AlignmentFlag.AlignTop)
|
|
116
|
+
layout.addLayout(row)
|
|
117
|
+
layout.addStretch(1)
|
|
118
|
+
self.submit_button.setText(DELETE_BUTTON_TEXT)
|
|
119
|
+
self.submit_button.setEnabled(False)
|
|
120
|
+
# Git Extensions shows this form as a white body over a grey band holding its single button; there is no
|
|
121
|
+
# Cancel button, Escape and the title bar close it.
|
|
122
|
+
self.apply_theme_look()
|
|
123
|
+
self.button_band = install_banded_layout(self, layout, [self.submit_button], body_margins=DELETE_BRANCH_BODY_MARGINS, button_sizing=BandButtonSizing.WINDOW)
|
|
124
|
+
follow_theme(self, self.apply_theme_look)
|
|
125
|
+
# Enter runs Delete from anywhere in the form. The role is set only now, once the button belongs to the dialog:
|
|
126
|
+
# Qt then records it as the dialog's main default and gives it back whenever the focus moves.
|
|
127
|
+
self.submit_button.setDefault(True)
|
|
128
|
+
self.submit_button.clicked.connect(self._submit)
|
|
129
|
+
self.multiple_button.clicked.connect(self._open_select_dialog)
|
|
130
|
+
self.branch_combo.editTextChanged.connect(self._text_changed)
|
|
131
|
+
self.resize(DELETE_BRANCH_CLIENT_SIZE)
|
|
132
|
+
# Git Extensions gives this window the same minimum and maximum height: it only grows in width.
|
|
133
|
+
self.single_row_height = DELETE_BRANCH_CLIENT_SIZE.height()
|
|
134
|
+
self.refresh_metrics()
|
|
135
|
+
self.branch_combo.setFocus()
|
|
136
|
+
|
|
137
|
+
def apply_theme_look(self) -> None:
|
|
138
|
+
"""Lay the light or dark look of the branch list and the multiple selection button, decided on the application palette.
|
|
139
|
+
|
|
140
|
+
Called by the constructor and again at each switch; the body and the band follow the switch on their own. Only
|
|
141
|
+
style sheets and palettes change: the branches typed or picked stay as they are.
|
|
142
|
+
"""
|
|
143
|
+
apply_windows_combo_look(self.branch_combo, editable=True)
|
|
144
|
+
apply_windows_button_look(self.multiple_button)
|
|
145
|
+
|
|
146
|
+
def fit_capture_controls(self) -> None:
|
|
147
|
+
"""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."""
|
|
148
|
+
fit_caption_column((self.caption_label,), DELETE_BRANCH_CAPTION_WIDTH)
|
|
149
|
+
fit_capture_control(self.submit_button, DELETE_BUTTON_SIZE)
|
|
150
|
+
|
|
151
|
+
def populate(self, snapshot: ReferenceSnapshot, candidate_refs: tuple[str, ...], initial_ref: str) -> None:
|
|
152
|
+
"""Offer the deletable branches of the references the window was opened on, starting with the clicked branch.
|
|
153
|
+
|
|
154
|
+
The clicked branch is written in the list even when it cannot be deleted, so pressing Delete explains why.
|
|
155
|
+
"""
|
|
156
|
+
if self.closed or self.busy or self.completed:
|
|
157
|
+
return
|
|
158
|
+
self.snapshot = snapshot
|
|
159
|
+
self.candidate_refs = candidate_refs
|
|
160
|
+
self.branch_combo.blockSignals(True)
|
|
161
|
+
self.branch_combo.clear()
|
|
162
|
+
for full_ref in candidate_refs:
|
|
163
|
+
self.branch_combo.addItem(short_reference_name(full_ref), full_ref)
|
|
164
|
+
initial_index = self.branch_combo.findData(initial_ref)
|
|
165
|
+
self.branch_combo.setCurrentIndex(initial_index)
|
|
166
|
+
if initial_index < 0:
|
|
167
|
+
self.branch_combo.setEditText(short_reference_name(initial_ref))
|
|
168
|
+
self.branch_combo.blockSignals(False)
|
|
169
|
+
self.branch_combo.lineEdit().selectAll()
|
|
170
|
+
self.branch_combo.setFocus()
|
|
171
|
+
self.multiple_button.setEnabled(True)
|
|
172
|
+
self._text_changed()
|
|
173
|
+
|
|
174
|
+
def selected_refs(self) -> tuple[str, ...]:
|
|
175
|
+
"""Read the typed names as full local branch names, in the typed order and each only once."""
|
|
176
|
+
full_refs: list[str] = []
|
|
177
|
+
for name in BRANCH_NAME_SEPARATOR_PATTERN.split(self.branch_combo.currentText()):
|
|
178
|
+
if not name:
|
|
179
|
+
continue
|
|
180
|
+
full_ref = LOCAL_PREFIX + name
|
|
181
|
+
if full_ref not in full_refs:
|
|
182
|
+
full_refs.append(full_ref)
|
|
183
|
+
return tuple(full_refs)
|
|
184
|
+
|
|
185
|
+
def known_selected_refs(self) -> tuple[str, ...]:
|
|
186
|
+
"""Read the typed names that are offered branches, silently leaving the others aside, to pre-check them in the list."""
|
|
187
|
+
full_refs: list[str] = []
|
|
188
|
+
for full_ref in self.selected_refs():
|
|
189
|
+
if full_ref in self.candidate_refs:
|
|
190
|
+
full_refs.append(full_ref)
|
|
191
|
+
return tuple(full_refs)
|
|
192
|
+
|
|
193
|
+
def _open_select_dialog(self) -> None:
|
|
194
|
+
"""Open Select multiple branches on the offered branches, the typed ones checked, without starting Git."""
|
|
195
|
+
if self.closed or self.busy or self.completed or self.snapshot is None:
|
|
196
|
+
return
|
|
197
|
+
if self.select_dialog is not None and isValid(self.select_dialog) and self.select_dialog.isVisible():
|
|
198
|
+
self.select_dialog.raise_()
|
|
199
|
+
return
|
|
200
|
+
dialog = SelectBranchesDialog(self.candidate_refs, set(self.known_selected_refs()), self)
|
|
201
|
+
self.select_dialog = dialog
|
|
202
|
+
dialog.selection_applied.connect(self._apply_selection)
|
|
203
|
+
dialog.finished.connect(dialog.deleteLater)
|
|
204
|
+
open_window_modal(dialog)
|
|
205
|
+
|
|
206
|
+
def _apply_selection(self, full_refs: tuple[str, ...]) -> None:
|
|
207
|
+
"""Write the branches checked in Select multiple branches as the list text, their names separated by spaces.
|
|
208
|
+
|
|
209
|
+
Git Extensions replaces the text with the checked branches however its window was closed, so an empty
|
|
210
|
+
selection empties the text.
|
|
211
|
+
"""
|
|
212
|
+
self.select_dialog = None
|
|
213
|
+
if self.closed or self.busy or self.completed:
|
|
214
|
+
return
|
|
215
|
+
names: list[str] = []
|
|
216
|
+
for full_ref in full_refs:
|
|
217
|
+
names.append(short_reference_name(full_ref))
|
|
218
|
+
self.branch_combo.setEditText(" ".join(names))
|
|
219
|
+
self.branch_combo.setFocus()
|
|
220
|
+
|
|
221
|
+
def _refusal_cause(self, full_refs: tuple[str, ...]) -> str:
|
|
222
|
+
"""Explain which typed names cannot be deleted and why, or answer an empty text when all of them can."""
|
|
223
|
+
if not full_refs:
|
|
224
|
+
return NO_BRANCH_CAUSE
|
|
225
|
+
causes: list[str] = []
|
|
226
|
+
for full_ref in full_refs:
|
|
227
|
+
if full_ref in self.candidate_refs:
|
|
228
|
+
continue
|
|
229
|
+
name = "'" + short_reference_name(full_ref) + "' "
|
|
230
|
+
if self.snapshot is not None and full_ref == self.snapshot.head_ref:
|
|
231
|
+
causes.append(name + CURRENT_BRANCH_CAUSE)
|
|
232
|
+
elif self.snapshot is not None and full_ref in self.snapshot.object_oid_by_name:
|
|
233
|
+
causes.append(name + OCCUPIED_BRANCH_CAUSE)
|
|
234
|
+
else:
|
|
235
|
+
causes.append(name + UNKNOWN_BRANCH_CAUSE)
|
|
236
|
+
if not causes:
|
|
237
|
+
return ""
|
|
238
|
+
return "Cannot delete: " + "; ".join(causes) + "."
|
|
239
|
+
|
|
240
|
+
def _text_changed(self) -> None:
|
|
241
|
+
"""Offer Delete again once the typed names change, dropping the explanation of an earlier refusal."""
|
|
242
|
+
self.submit_button.setToolTip("")
|
|
243
|
+
self.submit_button.setEnabled(self.snapshot is not None and bool(typed_value(self.branch_combo.currentText())) and not self.busy
|
|
244
|
+
and not self.completed and not self.closed)
|
|
245
|
+
|
|
246
|
+
def _submit(self) -> None:
|
|
247
|
+
"""Hand the typed branches to the reference form controller once, or explain before any process why some cannot be deleted."""
|
|
248
|
+
if self.closed or self.busy or self.completed or self.snapshot is None or not self.submit_button.isEnabled():
|
|
249
|
+
return
|
|
250
|
+
full_refs = self.selected_refs()
|
|
251
|
+
cause = self._refusal_cause(full_refs)
|
|
252
|
+
if cause:
|
|
253
|
+
self.show_error(cause)
|
|
254
|
+
return
|
|
255
|
+
self.busy = True
|
|
256
|
+
self._set_controls_enabled(False)
|
|
257
|
+
self.delete_requested.emit(full_refs)
|
|
258
|
+
|
|
259
|
+
def show_unmerged_question(self, unmerged_refs: tuple[str, ...], answer: Callable[[bool], None]) -> None:
|
|
260
|
+
"""Ask once whether the listed branches may lose their commits, without blocking the application.
|
|
261
|
+
|
|
262
|
+
No is the default and the answer of Escape. An answer given after the window closed is dropped.
|
|
263
|
+
"""
|
|
264
|
+
if self.closed:
|
|
265
|
+
return
|
|
266
|
+
lines = [UNMERGED_QUESTION_HEADING]
|
|
267
|
+
for full_ref in unmerged_refs:
|
|
268
|
+
lines.append(short_reference_name(full_ref))
|
|
269
|
+
lines.append(UNMERGED_QUESTION_ENDING)
|
|
270
|
+
question = QMessageBox(QMessageBox.Icon.Warning, DELETE_BRANCH_TITLE, "\n".join(lines),
|
|
271
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, self)
|
|
272
|
+
prepare_message_box(question, DELETE_BRANCH_TITLE)
|
|
273
|
+
question.setTextFormat(Qt.TextFormat.PlainText)
|
|
274
|
+
no_button = question.button(QMessageBox.StandardButton.No)
|
|
275
|
+
yes_button = question.button(QMessageBox.StandardButton.Yes)
|
|
276
|
+
question.setDefaultButton(no_button)
|
|
277
|
+
question.setEscapeButton(no_button)
|
|
278
|
+
self.unmerged_question = question
|
|
279
|
+
|
|
280
|
+
def finished() -> None:
|
|
281
|
+
"""Pass the answer on once, and only while the window that asked is still open."""
|
|
282
|
+
self.unmerged_question = None
|
|
283
|
+
accepted = question.clickedButton() is yes_button
|
|
284
|
+
question.deleteLater()
|
|
285
|
+
if self.closed:
|
|
286
|
+
return
|
|
287
|
+
answer(accepted)
|
|
288
|
+
|
|
289
|
+
question.finished.connect(finished)
|
|
290
|
+
open_window_modal(question)
|
|
291
|
+
|
|
292
|
+
def release(self) -> None:
|
|
293
|
+
"""Give the form back to the user after a request that wrote nothing, such as a No to the question."""
|
|
294
|
+
if self.closed:
|
|
295
|
+
return
|
|
296
|
+
self.busy = False
|
|
297
|
+
self.intent = None
|
|
298
|
+
self._set_controls_enabled(True)
|
|
299
|
+
self._text_changed()
|
|
300
|
+
|
|
301
|
+
def show_outcome(self, outcome: OperationOutcome) -> None:
|
|
302
|
+
"""Close once every branch was deleted; otherwise keep the form, frozen, while the console explains each branch."""
|
|
303
|
+
self.busy = False
|
|
304
|
+
if outcome.status == OperationStatus.SUCCESS:
|
|
305
|
+
self.accept()
|
|
306
|
+
return
|
|
307
|
+
self.completed = True
|
|
308
|
+
self._set_controls_enabled(False)
|
|
309
|
+
|
|
310
|
+
def _set_controls_enabled(self, enabled: bool) -> None:
|
|
311
|
+
"""Enable or disable the branch list, the multiple selection button and the Delete button together."""
|
|
312
|
+
self.branch_combo.setEnabled(enabled)
|
|
313
|
+
self.multiple_button.setEnabled(enabled)
|
|
314
|
+
self.submit_button.setEnabled(enabled)
|
|
315
|
+
|
|
316
|
+
def _closed(self) -> None:
|
|
317
|
+
"""Close a pending question or branch selection with the window, so its late answer can never start a deletion."""
|
|
318
|
+
super()._closed()
|
|
319
|
+
self.classification_generation += 1
|
|
320
|
+
if self.unmerged_question is not None and isValid(self.unmerged_question):
|
|
321
|
+
self.unmerged_question.reject()
|
|
322
|
+
if self.select_dialog is not None and isValid(self.select_dialog):
|
|
323
|
+
self.select_dialog.reject()
|