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,339 @@
|
|
|
1
|
+
"""Present a compact, read-only summary of a captured revision."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from PySide6.QtCore import QEvent, QPoint, QRect, Qt
|
|
9
|
+
from PySide6.QtGui import QColor, QPainter, QPalette
|
|
10
|
+
from PySide6.QtWidgets import QApplication, QGridLayout, QGroupBox, QLabel, QStyle, QStyleOptionGroupBox
|
|
11
|
+
|
|
12
|
+
from gitpane.history.history_parse import COMMIT_TIME_UNAVAILABLE_CAUSE
|
|
13
|
+
from gitpane.history.repository_value import ReferenceKind
|
|
14
|
+
from gitpane.ui.look.theme_color import is_dark_palette, theme_line_color
|
|
15
|
+
from gitpane.ui.look.theme_palette import WINDOWS_CONTROL_LIGHT_COLOR
|
|
16
|
+
from gitpane.ui.widget.commit_date_text import DATE_UNAVAILABLE_TEXT, format_commit_date
|
|
17
|
+
from gitpane.ui.widget.dialog_layout import CAPTION_TO_FIELD_MIN_SPACE
|
|
18
|
+
from gitpane.ui.widget.long_text import ElidedLabel, WrappingLabel
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from collections.abc import Callable
|
|
22
|
+
|
|
23
|
+
from PySide6.QtGui import QPaintEvent
|
|
24
|
+
|
|
25
|
+
from gitpane.history.repository_value import Commit
|
|
26
|
+
from gitpane.reference.reference_snapshot import ReferenceSnapshot
|
|
27
|
+
|
|
28
|
+
REVISION_PREFIX_LENGTH = 7
|
|
29
|
+
# Git Extensions writes eight hexadecimal digits in its commit box.
|
|
30
|
+
GIT_EXTENSIONS_REVISION_PREFIX_LENGTH = 8
|
|
31
|
+
LOADING_COMMIT_TEXT = "Loading commit metadata…"
|
|
32
|
+
NOT_AVAILABLE_TEXT = "n/a"
|
|
33
|
+
# Qt centres a label one pixel lower in a 21 px row than Windows Forms does; a one-pixel bottom margin lifts it.
|
|
34
|
+
GIT_EXTENSIONS_TEXT_LIFT = 1
|
|
35
|
+
# Git Extensions colors, in a light theme: frame line and empty reference band sampled from its Cherry pick commit
|
|
36
|
+
# capture, branch band LightSalmon (also sampled), tag band LightSteelBlue as its source code sets it.
|
|
37
|
+
FRAME_LIGHT_COLOR = "#dcdcdc"
|
|
38
|
+
BRANCH_BAND_LIGHT_COLOR = "#ffa07a"
|
|
39
|
+
TAG_BAND_LIGHT_COLOR = "#b0c4de"
|
|
40
|
+
EMPTY_REFERENCE_BAND_LIGHT_COLOR = WINDOWS_CONTROL_LIGHT_COLOR
|
|
41
|
+
# Dark counterparts of the salmon and steel blue bands: same hues, dark enough for light text (contrast above 7:1).
|
|
42
|
+
BRANCH_BAND_DARK_COLOR = "#6e3524"
|
|
43
|
+
TAG_BAND_DARK_COLOR = "#34465e"
|
|
44
|
+
# Space kept free on each side of the short hash where it interrupts the frame line.
|
|
45
|
+
TITLE_LINE_GAP = 1
|
|
46
|
+
# Git Extensions starts the short hash 9 px after the frame corner, leaving a short piece of line on its left.
|
|
47
|
+
TITLE_INDENT = 9
|
|
48
|
+
# Its digits sit slightly above the line's middle; Qt centres them slightly below, hence this small lift.
|
|
49
|
+
TITLE_TEXT_RAISE = 2
|
|
50
|
+
# Inside the Git Extensions commit box (measured on its capture, from the frame's left, right and bottom edges and from
|
|
51
|
+
# the frame line): subject 18 px under the line and 15 px in, 13 px of space before the metadata rows, rows 21 px high
|
|
52
|
+
# except the last one (31 px), captions 17 px in, values 102 px in, bands ending 13 px before the right edge, 10 px of
|
|
53
|
+
# space under the last row.
|
|
54
|
+
GIT_EXTENSIONS_CONTENT_LEFT = 15
|
|
55
|
+
GIT_EXTENSIONS_CONTENT_TOP_BELOW_LINE = 18
|
|
56
|
+
GIT_EXTENSIONS_CONTENT_RIGHT = 13
|
|
57
|
+
GIT_EXTENSIONS_CONTENT_BOTTOM = 10
|
|
58
|
+
GIT_EXTENSIONS_SUBJECT_TO_METADATA_SPACE = 13
|
|
59
|
+
GIT_EXTENSIONS_METADATA_ROW_HEIGHT = 21
|
|
60
|
+
GIT_EXTENSIONS_LAST_ROW_HEIGHT = 31
|
|
61
|
+
# The Git Extensions v6.0 windows (Create branch, Reset branch) give their last row the height of the others, so their
|
|
62
|
+
# frame is 141 px high instead of the 151 px of Revert and Cherry-pick.
|
|
63
|
+
GIT_EXTENSIONS_COMPACT_LAST_ROW_HEIGHT = GIT_EXTENSIONS_METADATA_ROW_HEIGHT
|
|
64
|
+
GIT_EXTENSIONS_CAPTION_INDENT = 2
|
|
65
|
+
GIT_EXTENSIONS_CAPTION_COLUMN_WIDTH = 87
|
|
66
|
+
# The colored band starts this far left of the value text.
|
|
67
|
+
VALUE_BAND_LEAD = 2
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class GitExtensionsGroupBox(QGroupBox):
|
|
71
|
+
"""Group frame drawn like the Git Extensions ones: a thin grey rectangle whose caption interrupts its top line.
|
|
72
|
+
|
|
73
|
+
The caption starts a few pixels after the frame's corner, leaving a short piece of line on its left. The frame is
|
|
74
|
+
painted here rather than through a style sheet: a style sheet on the frame would also restyle every child and
|
|
75
|
+
change the native metrics the layout was measured with, while painting only replaces the frame drawing.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def frame_color(self) -> QColor:
|
|
79
|
+
"""Use the captured frame grey in a light theme and the theme's line grey in a dark one.
|
|
80
|
+
|
|
81
|
+
The dark grey is worked out from the application palette at paint time, like every other line of the window, so
|
|
82
|
+
the frame shows on the dark body and follows a theme change.
|
|
83
|
+
"""
|
|
84
|
+
if is_dark_palette(self.palette()):
|
|
85
|
+
return theme_line_color(QApplication.palette())
|
|
86
|
+
return QColor(FRAME_LIGHT_COLOR)
|
|
87
|
+
|
|
88
|
+
def title_rectangle(self) -> QRect:
|
|
89
|
+
"""Return where the caption is written: at the style's caption height, moved off the corner."""
|
|
90
|
+
option = QStyleOptionGroupBox()
|
|
91
|
+
self.initStyleOption(option)
|
|
92
|
+
style_rectangle = self.style().subControlRect(QStyle.ComplexControl.CC_GroupBox, option, QStyle.SubControl.SC_GroupBoxLabel, self)
|
|
93
|
+
return QRect(TITLE_INDENT, style_rectangle.top(), self.fontMetrics().horizontalAdvance(self.title()), style_rectangle.height())
|
|
94
|
+
|
|
95
|
+
def frame_rectangle(self) -> QRect:
|
|
96
|
+
"""Return the painted frame: from the line crossing the caption down to the bottom edge."""
|
|
97
|
+
return QRect(QPoint(0, self.title_rectangle().center().y()), QPoint(self.width() - 1, self.height() - 1))
|
|
98
|
+
|
|
99
|
+
def paintEvent(self, event: QPaintEvent) -> None:
|
|
100
|
+
"""Draw the frame, clear its line behind the caption and write the caption across it."""
|
|
101
|
+
title_rect = self.title_rectangle()
|
|
102
|
+
frame = self.frame_rectangle()
|
|
103
|
+
painter = QPainter(self)
|
|
104
|
+
painter.setPen(self.frame_color())
|
|
105
|
+
painter.drawRect(QRect(frame.left(), frame.top(), frame.width() - 1, frame.height() - 1))
|
|
106
|
+
painter.fillRect(title_rect.adjusted(-TITLE_LINE_GAP, 0, TITLE_LINE_GAP, 0), self.palette().color(QPalette.ColorRole.Window))
|
|
107
|
+
painter.setPen(self.palette().color(QPalette.ColorRole.WindowText))
|
|
108
|
+
painter.drawText(title_rect.translated(0, -TITLE_TEXT_RAISE), Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, self.title())
|
|
109
|
+
painter.end()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class CommitSummary(GitExtensionsGroupBox):
|
|
113
|
+
"""Keep a revision's subject and essential metadata aligned in a small form.
|
|
114
|
+
|
|
115
|
+
With ``git_extensions_look`` the frame reproduces the Git Extensions commit box: the short hash is written across
|
|
116
|
+
the frame's top line, and the Branch(es) and Tag(s) values sit on colored bands. Other forms keep the native frame.
|
|
117
|
+
``last_row_height`` sets the height of the Tag(s) row of that look, and ``single_line`` cuts long values with an
|
|
118
|
+
ellipsis instead of wrapping them, so the box never grows the window. ``clock`` gives the current time the relative
|
|
119
|
+
commit date is written against; it is injected so that date can be reproduced, as the window gallery needs, and
|
|
120
|
+
reads the system time when absent.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __init__(self, target_oid: str, *, git_extensions_look: bool = False, last_row_height: int = GIT_EXTENSIONS_LAST_ROW_HEIGHT,
|
|
124
|
+
single_line: bool = False, clock: Callable[[], datetime] | None = None) -> None:
|
|
125
|
+
"""Retain the complete target while using a short hash as the frame caption."""
|
|
126
|
+
super().__init__(target_oid[:REVISION_PREFIX_LENGTH])
|
|
127
|
+
self.target_oid = target_oid
|
|
128
|
+
self.clock = clock or (lambda: datetime.now(UTC))
|
|
129
|
+
self.git_extensions_look = git_extensions_look
|
|
130
|
+
self.last_row_height = last_row_height
|
|
131
|
+
self.single_line = single_line
|
|
132
|
+
self.has_branch = False
|
|
133
|
+
self.has_tag = False
|
|
134
|
+
self.setToolTip(target_oid)
|
|
135
|
+
self.setAccessibleName("Target revision " + target_oid)
|
|
136
|
+
self.grid = QGridLayout(self)
|
|
137
|
+
self.subject_label = self._label(LOADING_COMMIT_TEXT, bold=True)
|
|
138
|
+
self.author_label = self._label(NOT_AVAILABLE_TEXT, bold=True)
|
|
139
|
+
self.date_label = self._label(NOT_AVAILABLE_TEXT)
|
|
140
|
+
self.branch_label = self._label(NOT_AVAILABLE_TEXT)
|
|
141
|
+
self.tag_label = self._label(NOT_AVAILABLE_TEXT)
|
|
142
|
+
subject_row, spacing_row = 0, 1
|
|
143
|
+
# The Git Extensions look keeps an empty row between the subject and the metadata instead of the grid spacing.
|
|
144
|
+
first_metadata_row = spacing_row + 1 if git_extensions_look else spacing_row
|
|
145
|
+
caption_column, value_column, column_count = 0, 1, 2
|
|
146
|
+
self.grid.addWidget(self.subject_label, subject_row, caption_column, 1, column_count)
|
|
147
|
+
self.caption_labels: list[QLabel] = []
|
|
148
|
+
# Git Extensions centres each caption on its value row; the native form keeps it on the value's first line.
|
|
149
|
+
caption_alignment = Qt.AlignmentFlag(0) if git_extensions_look else Qt.AlignmentFlag.AlignTop
|
|
150
|
+
for row, (caption, label) in enumerate((("Author:", self.author_label), ("Commit date:", self.date_label),
|
|
151
|
+
("Branch(es):", self.branch_label), ("Tag(s):", self.tag_label)), start=first_metadata_row):
|
|
152
|
+
caption_label = QLabel(caption)
|
|
153
|
+
self.caption_labels.append(caption_label)
|
|
154
|
+
self.grid.addWidget(caption_label, row, caption_column, alignment=caption_alignment)
|
|
155
|
+
label.setAccessibleName(caption.removesuffix(":"))
|
|
156
|
+
self.grid.addWidget(label, row, value_column)
|
|
157
|
+
self.grid.setColumnStretch(value_column, 1)
|
|
158
|
+
if git_extensions_look:
|
|
159
|
+
self._use_git_extensions_metrics(spacing_row, first_metadata_row)
|
|
160
|
+
|
|
161
|
+
def _use_git_extensions_metrics(self, spacing_row: int, first_metadata_row: int) -> None:
|
|
162
|
+
"""Space the subject, captions and values like the Git Extensions commit box instead of the native form."""
|
|
163
|
+
self.grid.setHorizontalSpacing(0)
|
|
164
|
+
self.grid.setVerticalSpacing(0)
|
|
165
|
+
self.grid.setRowMinimumHeight(spacing_row, GIT_EXTENSIONS_SUBJECT_TO_METADATA_SPACE)
|
|
166
|
+
metadata_row_count = len(self.caption_labels)
|
|
167
|
+
last_metadata_row = first_metadata_row + metadata_row_count - 1
|
|
168
|
+
for row in range(first_metadata_row, last_metadata_row):
|
|
169
|
+
self.grid.setRowMinimumHeight(row, GIT_EXTENSIONS_METADATA_ROW_HEIGHT)
|
|
170
|
+
self.grid.setRowMinimumHeight(last_metadata_row, self.last_row_height)
|
|
171
|
+
caption_column = 0
|
|
172
|
+
self.grid.setColumnMinimumWidth(caption_column, GIT_EXTENSIONS_CAPTION_COLUMN_WIDTH)
|
|
173
|
+
for caption_label in self.caption_labels:
|
|
174
|
+
caption_label.setIndent(GIT_EXTENSIONS_CAPTION_INDENT)
|
|
175
|
+
# The right margin is the space every captured caption keeps before its value: the column is the capture's
|
|
176
|
+
# width, or wider when a larger system text needs it, and then still leaves that space ("Commit date:" at
|
|
177
|
+
# 14 pt under macOS touched its value).
|
|
178
|
+
caption_label.setContentsMargins(0, 0, CAPTION_TO_FIELD_MIN_SPACE, GIT_EXTENSIONS_TEXT_LIFT)
|
|
179
|
+
for value_label in (self.author_label, self.date_label, self.branch_label, self.tag_label):
|
|
180
|
+
value_label.setContentsMargins(0, 0, 0, GIT_EXTENSIONS_TEXT_LIFT)
|
|
181
|
+
self.setTitle(self._short_hash(self.target_oid))
|
|
182
|
+
self._place_git_extensions_content()
|
|
183
|
+
|
|
184
|
+
def _short_hash(self, oid: str) -> str:
|
|
185
|
+
"""Return the frame caption for a commit: eight digits in the Git Extensions look, seven otherwise."""
|
|
186
|
+
return oid[:GIT_EXTENSIONS_REVISION_PREFIX_LENGTH if self.git_extensions_look else REVISION_PREFIX_LENGTH]
|
|
187
|
+
|
|
188
|
+
def _place_git_extensions_content(self) -> None:
|
|
189
|
+
"""Set the grid margins so the content sits at the measured distances from the painted frame.
|
|
190
|
+
|
|
191
|
+
The group box reserves its own margins for the native title and frame, and recomputes them whenever the style
|
|
192
|
+
or font changes; the grid margins are the measured distances minus that reservation.
|
|
193
|
+
"""
|
|
194
|
+
reserved = self.contentsMargins()
|
|
195
|
+
line_top = self.title_rectangle().center().y()
|
|
196
|
+
self.grid.setContentsMargins(max(0, GIT_EXTENSIONS_CONTENT_LEFT - reserved.left()), max(0, line_top + GIT_EXTENSIONS_CONTENT_TOP_BELOW_LINE - reserved.top()),
|
|
197
|
+
max(0, GIT_EXTENSIONS_CONTENT_RIGHT - reserved.right()), max(0, GIT_EXTENSIONS_CONTENT_BOTTOM - reserved.bottom()))
|
|
198
|
+
|
|
199
|
+
def changeEvent(self, event: QEvent) -> None:
|
|
200
|
+
"""Keep the Git Extensions spacing when a new style or font changes the group box's own margins."""
|
|
201
|
+
super().changeEvent(event)
|
|
202
|
+
# The group box sends style and font changes while it is still being built, before the look is known.
|
|
203
|
+
if not getattr(self, "git_extensions_look", False) or not hasattr(self, "caption_labels"):
|
|
204
|
+
return
|
|
205
|
+
if event.type() in (QEvent.Type.StyleChange, QEvent.Type.FontChange):
|
|
206
|
+
self._place_git_extensions_content()
|
|
207
|
+
|
|
208
|
+
def _label(self, text: str, *, bold: bool = False) -> QLabel:
|
|
209
|
+
"""Build a value label: one line cut with an ellipsis, or literal Git text able to wrap without widening the form."""
|
|
210
|
+
if self.single_line:
|
|
211
|
+
label = ElidedLabel(text)
|
|
212
|
+
else:
|
|
213
|
+
label = WrappingLabel(text)
|
|
214
|
+
label.setWordWrap(True)
|
|
215
|
+
font = label.font()
|
|
216
|
+
font.setBold(bold)
|
|
217
|
+
label.setFont(font)
|
|
218
|
+
return label
|
|
219
|
+
|
|
220
|
+
def retarget(self, target_oid: str) -> None:
|
|
221
|
+
"""Show another commit: its short hash in the frame and pending values until its details and references arrive.
|
|
222
|
+
|
|
223
|
+
An answer still expected for the previous commit is ignored afterwards, since it no longer matches the target.
|
|
224
|
+
"""
|
|
225
|
+
if target_oid == self.target_oid:
|
|
226
|
+
return
|
|
227
|
+
self.target_oid = target_oid
|
|
228
|
+
self.setToolTip(target_oid)
|
|
229
|
+
self.setAccessibleName("Target revision " + target_oid)
|
|
230
|
+
self.setTitle(self._short_hash(target_oid))
|
|
231
|
+
self.subject_label.setText(LOADING_COMMIT_TEXT)
|
|
232
|
+
for label in (self.author_label, self.date_label, self.branch_label, self.tag_label):
|
|
233
|
+
label.setText(NOT_AVAILABLE_TEXT)
|
|
234
|
+
self._show_reference_presence(has_branch=False, has_tag=False)
|
|
235
|
+
|
|
236
|
+
def show_commit(self, fields: tuple[str, ...]) -> None:
|
|
237
|
+
"""Show only the subject, author and real commit date of the captured target."""
|
|
238
|
+
oid, author, _author_date, _committer, commit_date, message = fields
|
|
239
|
+
if oid != self.target_oid:
|
|
240
|
+
return
|
|
241
|
+
timestamp = None
|
|
242
|
+
if commit_date:
|
|
243
|
+
try:
|
|
244
|
+
timestamp = int(datetime.fromisoformat(commit_date).timestamp())
|
|
245
|
+
except (ValueError, OverflowError, OSError):
|
|
246
|
+
timestamp = None
|
|
247
|
+
self._show_values(message, author, timestamp, commit_date)
|
|
248
|
+
|
|
249
|
+
def show_loaded_commit(self, commit: Commit) -> None:
|
|
250
|
+
"""Show the subject, author and commit date of a commit the history graph already holds, without Git."""
|
|
251
|
+
if commit.oid != self.target_oid:
|
|
252
|
+
return
|
|
253
|
+
if commit.time_unavailable:
|
|
254
|
+
self._show_values(commit.message, commit.author_name, None, COMMIT_TIME_UNAVAILABLE_CAUSE)
|
|
255
|
+
return
|
|
256
|
+
commit_date = datetime.fromtimestamp(commit.committer_timestamp, UTC).isoformat()
|
|
257
|
+
self._show_values(commit.message, commit.author_name, commit.committer_timestamp, commit_date)
|
|
258
|
+
|
|
259
|
+
def _show_values(self, message: str, author: str, timestamp: int | None, commit_date: str) -> None:
|
|
260
|
+
"""Write the subject line, the author and the commit date the way the history graph writes dates.
|
|
261
|
+
|
|
262
|
+
Wrapping labels also carry the whole message and the exact date as tooltips; one-line labels keep their
|
|
263
|
+
tooltip for the whole text of a value they had to cut.
|
|
264
|
+
"""
|
|
265
|
+
subject, _, _body = message.partition("\n")
|
|
266
|
+
self.subject_label.setText(subject or NOT_AVAILABLE_TEXT)
|
|
267
|
+
self.author_label.setText(author or NOT_AVAILABLE_TEXT)
|
|
268
|
+
if not commit_date:
|
|
269
|
+
self.date_label.setText(NOT_AVAILABLE_TEXT)
|
|
270
|
+
elif timestamp is None:
|
|
271
|
+
self.date_label.setText(DATE_UNAVAILABLE_TEXT)
|
|
272
|
+
else:
|
|
273
|
+
self.date_label.setText(format_commit_date(timestamp, self.clock()))
|
|
274
|
+
if self.single_line:
|
|
275
|
+
return
|
|
276
|
+
self.subject_label.setToolTip(message)
|
|
277
|
+
self.date_label.setToolTip(commit_date)
|
|
278
|
+
|
|
279
|
+
def show_references(self, snapshot: ReferenceSnapshot) -> None:
|
|
280
|
+
"""List real branches and tags at the revision, excluding redundant symbolic aliases."""
|
|
281
|
+
branches: list[str] = []
|
|
282
|
+
tags: list[str] = []
|
|
283
|
+
for reference in sorted(snapshot.references, key=lambda reference: reference.full_name):
|
|
284
|
+
if reference.target_oid != self.target_oid or reference.symbolic_target:
|
|
285
|
+
continue
|
|
286
|
+
name = reference.full_name.removeprefix(reference.kind.prefix)
|
|
287
|
+
if reference.kind == ReferenceKind.TAGS:
|
|
288
|
+
tags.append(name)
|
|
289
|
+
elif reference.kind in (ReferenceKind.HEADS, ReferenceKind.REMOTES):
|
|
290
|
+
branches.append(name)
|
|
291
|
+
self.branch_label.setText(", ".join(branches) or NOT_AVAILABLE_TEXT)
|
|
292
|
+
self.tag_label.setText(", ".join(tags) or NOT_AVAILABLE_TEXT)
|
|
293
|
+
self._show_reference_presence(has_branch=bool(branches), has_tag=bool(tags))
|
|
294
|
+
|
|
295
|
+
def _show_reference_presence(self, *, has_branch: bool, has_tag: bool) -> None:
|
|
296
|
+
"""Remember which bands are filled and, in the Git Extensions look, write present references in bold."""
|
|
297
|
+
self.has_branch = has_branch
|
|
298
|
+
self.has_tag = has_tag
|
|
299
|
+
if not self.git_extensions_look:
|
|
300
|
+
return
|
|
301
|
+
# Git Extensions writes a present branch or tag in bold on its colored band.
|
|
302
|
+
for label, present in ((self.branch_label, self.has_branch), (self.tag_label, self.has_tag)):
|
|
303
|
+
font = label.font()
|
|
304
|
+
font.setBold(present)
|
|
305
|
+
label.setFont(font)
|
|
306
|
+
self.update()
|
|
307
|
+
|
|
308
|
+
def branch_band_color(self, present: bool) -> QColor:
|
|
309
|
+
"""Return the Branch(es) band color: salmon when the commit carries a branch, the control grey otherwise."""
|
|
310
|
+
if not present:
|
|
311
|
+
return self._empty_band_color()
|
|
312
|
+
return QColor(BRANCH_BAND_DARK_COLOR if is_dark_palette(self.palette()) else BRANCH_BAND_LIGHT_COLOR)
|
|
313
|
+
|
|
314
|
+
def tag_band_color(self, present: bool) -> QColor:
|
|
315
|
+
"""Return the Tag(s) band color: steel blue when the commit carries a tag, the control grey otherwise."""
|
|
316
|
+
if not present:
|
|
317
|
+
return self._empty_band_color()
|
|
318
|
+
return QColor(TAG_BAND_DARK_COLOR if is_dark_palette(self.palette()) else TAG_BAND_LIGHT_COLOR)
|
|
319
|
+
|
|
320
|
+
def _empty_band_color(self) -> QColor:
|
|
321
|
+
"""Use the captured control grey in a light theme and the palette's control color in a dark one."""
|
|
322
|
+
if is_dark_palette(self.palette()):
|
|
323
|
+
return self.palette().color(QPalette.ColorRole.Button)
|
|
324
|
+
return QColor(EMPTY_REFERENCE_BAND_LIGHT_COLOR)
|
|
325
|
+
|
|
326
|
+
def paintEvent(self, event: QPaintEvent) -> None:
|
|
327
|
+
"""Draw the Git Extensions frame and reference bands, or the native frame when that look is off."""
|
|
328
|
+
if not self.git_extensions_look:
|
|
329
|
+
QGroupBox.paintEvent(self, event)
|
|
330
|
+
return
|
|
331
|
+
super().paintEvent(event)
|
|
332
|
+
painter = QPainter(self)
|
|
333
|
+
# Each band also covers half of the row spacing above and below its value, so consecutive bands touch like
|
|
334
|
+
# the Git Extensions rows, and it starts a little left of the text so the value does not touch its edge.
|
|
335
|
+
row_spacing = max(0, self.grid.verticalSpacing())
|
|
336
|
+
space_above, space_below = row_spacing // 2, row_spacing - row_spacing // 2
|
|
337
|
+
for label, color in ((self.branch_label, self.branch_band_color(self.has_branch)), (self.tag_label, self.tag_band_color(self.has_tag))):
|
|
338
|
+
value = label.geometry()
|
|
339
|
+
painter.fillRect(QRect(QPoint(value.left() - VALUE_BAND_LEAD, value.top() - space_above), QPoint(value.right(), value.bottom() + space_below)), color)
|