2025-12-01
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
# SPDX-FileCopyrightText: 2023 Blender Studio Tools Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from bpy.props import IntProperty, BoolProperty
|
||||
from bpy.types import UIList, Panel, Operator
|
||||
from ..util import get_addon_prefs
|
||||
|
||||
|
||||
class SVN_UL_log(UIList):
|
||||
show_all_logs: BoolProperty(
|
||||
name='Show All Logs',
|
||||
description='Show the complete SVN Log, instead of only entries that affected the currently selected file',
|
||||
default=False
|
||||
)
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if self.layout_type != 'DEFAULT':
|
||||
raise NotImplemented
|
||||
|
||||
svn = data
|
||||
log_entry = item
|
||||
|
||||
num, auth, date, msg = layout_log_split(layout.row())
|
||||
|
||||
active_file = svn.active_file
|
||||
num.label(text=str(log_entry.revision_number))
|
||||
if item.revision_number == active_file.revision:
|
||||
num.operator('svn.tooltip_log', text="", icon='LAYER_ACTIVE',
|
||||
emboss=False).log_rev = log_entry.revision_number
|
||||
elif log_entry.changes_file(active_file):
|
||||
get_older = num.operator(
|
||||
'svn.download_file_revision', text="", icon='IMPORT', emboss=False)
|
||||
get_older.revision = log_entry.revision_number
|
||||
get_older.file_rel_path = active_file.svn_path
|
||||
auth.label(text=log_entry.revision_author)
|
||||
date.label(text=log_entry.revision_date_simple)
|
||||
|
||||
commit_msg = log_entry.commit_message
|
||||
commit_msg = commit_msg.split(
|
||||
"\n")[0] if "\n" in commit_msg else commit_msg
|
||||
commit_msg = commit_msg[:50] + \
|
||||
"..." if len(commit_msg) > 52 else commit_msg
|
||||
msg.alignment = 'LEFT'
|
||||
msg.operator("svn.display_commit_message", text=commit_msg,
|
||||
emboss=False).log_rev = log_entry.revision_number
|
||||
|
||||
def filter_items(self, context, data, propname):
|
||||
"""Custom filtering functionality:
|
||||
- Always sort by descending revision number
|
||||
- Allow searching for various criteria
|
||||
"""
|
||||
svn = data
|
||||
log_entries = getattr(data, propname)
|
||||
|
||||
# Start off with all entries flagged as visible.
|
||||
flt_flags = [self.bitflag_filter_item] * len(log_entries)
|
||||
# Always sort by descending revision number
|
||||
flt_neworder = sorted(range(len(log_entries)),
|
||||
key=lambda i: log_entries[i].revision_number)
|
||||
flt_neworder.reverse()
|
||||
|
||||
if not self.show_all_logs:
|
||||
flt_flags = [
|
||||
log_entry.affects_active_file * self.bitflag_filter_item
|
||||
for log_entry in log_entries
|
||||
]
|
||||
|
||||
if self.filter_name:
|
||||
# Filtering: Allow comma-separated keywords.
|
||||
# ALL keywords must be found somewhere in the log entry for it to show up.
|
||||
filter_words = [word.strip().lower()
|
||||
for word in self.filter_name.split(",")]
|
||||
for idx, log_entry in enumerate(log_entries):
|
||||
for filter_word in filter_words:
|
||||
if filter_word not in log_entry.text_to_search:
|
||||
flt_flags[idx] = 0
|
||||
break
|
||||
|
||||
return flt_flags, flt_neworder
|
||||
|
||||
def draw_filter(self, context, layout):
|
||||
"""Custom filtering UI.
|
||||
"""
|
||||
main_row = layout.row()
|
||||
main_row.prop(self, 'filter_name', text="")
|
||||
main_row.prop(self, 'show_all_logs', text="",
|
||||
toggle=True, icon='ALIGN_JUSTIFY')
|
||||
|
||||
|
||||
def is_log_useful(context) -> bool:
|
||||
"""Return whether the log has any useful info to display."""
|
||||
|
||||
prefs = get_addon_prefs(context)
|
||||
repo = prefs.active_repo
|
||||
|
||||
if not repo or not repo.authenticated:
|
||||
return False
|
||||
|
||||
if len(repo.log) == 0 or len(repo.external_files) == 0:
|
||||
return False
|
||||
active_file = repo.active_file
|
||||
if active_file.status in ['unversioned', 'added']:
|
||||
return False
|
||||
|
||||
any_visible = any([file.show_in_filelist for file in repo.external_files])
|
||||
if not any_visible:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class VIEW3D_PT_svn_log(Panel):
|
||||
"""Display the revision history of the selected file."""
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = 'SVN'
|
||||
bl_label = 'Revision History'
|
||||
bl_parent_id = "VIEW3D_PT_svn_files"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return is_log_useful(context)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
draw_svn_log(context, layout)
|
||||
|
||||
|
||||
def layout_log_split(layout):
|
||||
main = layout.split(factor=0.4)
|
||||
num_and_auth = main.row()
|
||||
date_and_msg = main.row()
|
||||
|
||||
num_and_auth_split = num_and_auth.split(factor=0.5)
|
||||
num = num_and_auth_split.row()
|
||||
auth = num_and_auth_split.row()
|
||||
|
||||
date_and_msg_split = date_and_msg.split(factor=0.3)
|
||||
date = date_and_msg_split.row()
|
||||
msg = date_and_msg_split.row()
|
||||
|
||||
return num, auth, date, msg
|
||||
|
||||
|
||||
def draw_svn_log(context, layout):
|
||||
num, auth, date, msg = layout_log_split(layout.row())
|
||||
num.label(text="Rev. #")
|
||||
auth.label(text="Author")
|
||||
date.label(text="Date")
|
||||
msg.label(text="Message")
|
||||
|
||||
prefs = get_addon_prefs(context)
|
||||
repo = prefs.active_repo
|
||||
layout.template_list(
|
||||
"SVN_UL_log",
|
||||
"svn_log",
|
||||
repo,
|
||||
"log",
|
||||
repo,
|
||||
"log_active_index",
|
||||
)
|
||||
|
||||
active_log = repo.active_log
|
||||
if not active_log:
|
||||
return
|
||||
layout.label(text="Revision Date: " + active_log.revision_date)
|
||||
|
||||
layout.label(
|
||||
text=f"Files changed in revision `r{active_log.revision_number}`:")
|
||||
|
||||
col = layout.column(align=True)
|
||||
row = col.row()
|
||||
split = row.split(factor=0.80)
|
||||
split.label(text=" Filepath")
|
||||
row = split.row()
|
||||
row.alignment = 'RIGHT'
|
||||
row.label(text="Action")
|
||||
for f in active_log.changed_files:
|
||||
row = col.row()
|
||||
split = row.split(factor=0.90)
|
||||
split.prop(f, 'name', emboss=False, text="", icon=f.file_icon)
|
||||
row = split.row()
|
||||
row.alignment = 'RIGHT'
|
||||
row.operator('svn.explain_status', text="",
|
||||
icon=f.status_icon, emboss=False).status = f.status
|
||||
|
||||
|
||||
def execute_tooltip_log(self, context):
|
||||
"""Set the index on click, to act as if this operator button was
|
||||
click-through in the UIList."""
|
||||
repo = context.scene.svn.get_repo(context)
|
||||
tup = repo.get_log_by_revision(self.log_rev)
|
||||
if tup:
|
||||
repo.log_active_index = tup[0]
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SVN_OT_log_tooltip(Operator):
|
||||
bl_idname = "svn.tooltip_log"
|
||||
bl_label = "" # Don't want the first line of the tooltip on mouse hover.
|
||||
# bl_description = "An operator to be drawn in the log list, that can display a dynamic tooltip"
|
||||
bl_options = {'INTERNAL'}
|
||||
|
||||
log_rev: IntProperty(
|
||||
description="Revision number of the log entry to show in the tooltip"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
return "This is the currently checked out version of the file"
|
||||
|
||||
execute = execute_tooltip_log
|
||||
|
||||
|
||||
class SVN_OT_log_show_commit_msg(Operator):
|
||||
bl_idname = "svn.display_commit_message"
|
||||
bl_label = "" # Don't want the first line of the tooltip on mouse hover.
|
||||
# bl_description = "Show the currently active commit, using a dynamic tooltip"
|
||||
bl_options = {'INTERNAL'}
|
||||
|
||||
log_rev: IntProperty(
|
||||
description="Revision number of the log entry to show in the tooltip"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
log_entry = context.scene.svn.get_repo(context).get_log_by_revision(properties.log_rev)[
|
||||
1]
|
||||
commit_msg = log_entry.commit_message
|
||||
|
||||
# Prettify the tooltips.
|
||||
pretty_msg = ""
|
||||
for line in commit_msg.split("\n"):
|
||||
# Remove leading/trailing whitespace
|
||||
line = line.strip()
|
||||
|
||||
# Add punctuation mark
|
||||
if not (line.endswith(".") or line.endswith("!") or line.endswith("?")):
|
||||
line = line + "."
|
||||
|
||||
# Split long lines into several
|
||||
limit = 300
|
||||
if len(line) > limit:
|
||||
words = line.split(" ")
|
||||
sub_lines = []
|
||||
|
||||
new_line = ""
|
||||
for word in words:
|
||||
if len(new_line) + len(word) < limit:
|
||||
new_line += " "+word
|
||||
else:
|
||||
sub_lines.append(new_line)
|
||||
new_line = word
|
||||
else:
|
||||
sub_lines.append(new_line)
|
||||
line = "\n".join(sub_lines)
|
||||
|
||||
pretty_msg += "\n"+line
|
||||
|
||||
# Remove last period because Blender adds it.
|
||||
if pretty_msg.endswith("."):
|
||||
pretty_msg = pretty_msg[:-1]
|
||||
|
||||
return pretty_msg
|
||||
|
||||
execute = execute_tooltip_log
|
||||
|
||||
|
||||
registry = [
|
||||
VIEW3D_PT_svn_log,
|
||||
SVN_UL_log,
|
||||
SVN_OT_log_tooltip,
|
||||
SVN_OT_log_show_commit_msg
|
||||
]
|
||||
Reference in New Issue
Block a user