Blender SVN: New Features #273

Open
Demeter Dzadik wants to merge 13 commits from New-SVN-features into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
5 changed files with 124 additions and 48 deletions
Showing only changes of commit 504edc7895 - Show all commits

View File

@ -545,13 +545,12 @@ class SVN_OT_cleanup(SVN_Operator, Operator):
class SVN_OT_cancel_running_operation(Operator): class SVN_OT_cancel_running_operation(Operator):
bl_idname = "svn.cancel" bl_idname = "svn.cancel"
bl_label = "SVN Cancel Operation" bl_label = "SVN Cancel Operation"
bl_description = "Cancel the running operation" bl_description = "Cancel ongoing commit/update operation"
bl_options = {'INTERNAL'} bl_options = {'INTERNAL'}
def execute(self, context): def execute(self, context):
Processes.kill('Commit') Processes.kill('Commit')
Processes.kill('Update') Processes.kill('Update')
Processes.kill('List')
return {'FINISHED'} return {'FINISHED'}
registry = [ registry = [

View File

@ -168,16 +168,21 @@ class SVN_file(PropertyGroup):
### File size - Update a string representation one time when file_size_KiB is set. ### ### File size - Update a string representation one time when file_size_KiB is set. ###
### We want to avoid re-calculating that string on every re-draw for optimization. ### ### We want to avoid re-calculating that string on every re-draw for optimization. ###
def get_file_size(self): def get_file_size_ui_str(self):
num = self.file_size_KiB num = self.file_size_KiB
for unit in ("KiB", "MiB", "GiB", "TiB", "PiB", "EiB"): for unit in ("KiB", "MiB", "GiB", "TiB", "PiB", "EiB"):
if num < 1024: if num < 1024:
return f"{num:3.1f} {unit}" num_digits = len(str(num).split(".")[0])
file_size = f"{num:0.{max(0, 3-num_digits)}f} {unit}"
if "." not in file_size:
file_size = file_size.replace(" ", " ")
return file_size
num /= 1024.0 num /= 1024.0
return f"{num:.1f} YiB"
return "Really Big"
def update_file_size(self, _context): def update_file_size(self, _context):
self.file_size = self.get_file_size() self.file_size = self.get_file_size_ui_str()
file_size_KiB: FloatProperty(description="One KibiByte (KiB) is 1024 bytes", update=update_file_size) file_size_KiB: FloatProperty(description="One KibiByte (KiB) is 1024 bytes", update=update_file_size)
file_size: StringProperty(description="File size for displaying in the UI") file_size: StringProperty(description="File size for displaying in the UI")
@ -531,6 +536,30 @@ class SVN_repository(PropertyGroup):
for log_entry in self.log] for log_entry in self.log]
) )
def get_root_subdirs(self):
for f in self.external_files:
if not self.get_parent_file(f):
yield f
def find_dir_children(self, dir: SVN_file):
for f in self.external_files:
if self.get_parent_file(f) == dir:
yield f
def calculate_folder_sizes(self):
for root_subdir in self.get_root_subdirs():
self.calculate_folder_size_recursive(root_subdir)
def calculate_folder_size_recursive(self, dir: SVN_file):
size = 0
for child in self.find_dir_children(dir):
if child.is_dir:
self.calculate_folder_size_recursive(child)
size += child.file_size_KiB
dir.file_size_KiB = size
return size
prev_external_files_active_index: IntProperty( prev_external_files_active_index: IntProperty(
name="Previous Active Index", name="Previous Active Index",
description="Internal value to avoid triggering the update callback unnecessarily", description="Internal value to avoid triggering the update callback unnecessarily",
@ -645,6 +674,11 @@ class SVN_repository(PropertyGroup):
update=refresh_ui_lists update=refresh_ui_lists
) )
show_file_size: BoolProperty(
name="Show Size On Disk",
description="Show size of each file and aggregate size of folders",
default=False
)
show_file_paths: BoolProperty( show_file_paths: BoolProperty(
name="Show File Paths", name="Show File Paths",
description="Show file paths relative to the SVN root, instead of just the file name" description="Show file paths relative to the SVN root, instead of just the file name"
@ -659,8 +693,8 @@ class SVN_repository(PropertyGroup):
name="File Display Mode", name="File Display Mode",
description="Whether the full file tree sould be drawn instead of just modified files as a flat list", description="Whether the full file tree sould be drawn instead of just modified files as a flat list",
items=[ items=[
('FLAT', "Modifications", "Display only modified files as a flat list", 'PRESET', 0), ('FLAT', "Changes", "Display only modified files as a flat list", 'PRESET', 0),
('TREE', "Tree View", "Display the full tree of the entire repository", 'OUTLINER', 1), ('TREE', "File Tree", "Display the full tree of the entire repository", 'OUTLINER', 1),
], ],
update=update_tree_display update=update_tree_display
) )

View File

@ -179,7 +179,7 @@ class BGP_SVN_List(BackgroundProcess):
def acquire_output(self, context, prefs): def acquire_output(self, context, prefs):
self.output = execute_svn_command( self.output = execute_svn_command(
context, context,
["svn", "list", "--recursive", "--xml"], ["svn", "list", "--recursive", "--xml", "-r", "HEAD"],
use_cred=True, use_cred=True,
) )
@ -296,6 +296,8 @@ def update_file_list_svn_status(context, file_statuses: Dict[str, Tuple[str, str
"\nUpdating log...\n", "\nUpdating log...\n",
) )
Processes.start('Log') Processes.start('Log')
if any([not f.is_dir and not f.file_size_KiB for f in repo.external_files]):
Processes.start('List') Processes.start('List')
# Remove file entries who no longer seem to have an SVN status. # Remove file entries who no longer seem to have an SVN status.
@ -372,6 +374,9 @@ def update_file_list_svn_list(context, svn_list_str: str) -> Dict:
if kind == 'file': if kind == 'file':
file_entry.file_size_KiB = float(file_info['size']) / 1024.0 file_entry.file_size_KiB = float(file_info['size']) / 1024.0
# Calculate size of folders.
repo.calculate_folder_sizes()
@bpy.app.handlers.persistent @bpy.app.handlers.persistent
def mark_current_file_as_modified(_dummy1=None, _dummy2=None): def mark_current_file_as_modified(_dummy1=None, _dummy2=None):

View File

@ -5,12 +5,28 @@ import time
import bpy import bpy
from bpy.types import UIList from bpy.types import UIList
from bpy.props import BoolProperty
from .. import constants from .. import constants
from ..util import get_addon_prefs, dots from ..util import get_addon_prefs, dots
from ..threaded.background_process import Processes from ..threaded.background_process import Processes
def file_list_ui_split(layout, context):
repo = context.scene.svn.get_repo(context)
main_split = layout.split(factor=0.9)
left_side = main_split.row()
filepath_ui = left_side.row()
if repo.show_file_size:
filesize_ui = layout.row()
filesize_ui.alignment='RIGHT'
else:
filesize_ui = None
status_ui = layout.row(align=True)
status_ui.alignment='RIGHT'
ops_ui = layout.row(align=True)
return filepath_ui, filesize_ui, status_ui, ops_ui
class SVN_UL_file_list(UIList): class SVN_UL_file_list(UIList):
# Value that indicates that this item has passed the filter process successfully. See rna_ui.c. # Value that indicates that this item has passed the filter process successfully. See rna_ui.c.
@ -26,20 +42,13 @@ class SVN_UL_file_list(UIList):
file_entry = item file_entry = item
prefs = get_addon_prefs(context) prefs = get_addon_prefs(context)
main_row = layout.row() filepath_ui, filesize_ui, status_ui, ops_ui = file_list_ui_split(layout, context)
split = main_row.split(factor=0.6)
filepath_ui = split.row()
split = split.split(factor=0.4)
status_ui = split.row(align=True)
ops_ui = split.row(align=True)
ops_ui.alignment = 'RIGHT'
ops_ui.enabled = file_entry.status_prediction_type == 'NONE' and not prefs.is_busy ops_ui.enabled = file_entry.status_prediction_type == 'NONE' and not prefs.is_busy
if repo.display_mode == 'TREE': if repo.display_mode == 'TREE':
split = filepath_ui.split(factor=0.05 * file_entry.tree_depth + 0.00001) split = filepath_ui.split(factor=0.05 * file_entry.tree_depth + 0.00001)
split.row() split.label()
row = split.row(align=True) row = split.row(align=True)
filepath_ui = row.row(align=True) filepath_ui = row.row(align=True)
if file_entry.has_children: if file_entry.has_children:
@ -53,6 +62,29 @@ class SVN_UL_file_list(UIList):
else: else:
filepath_ui.label(text=file_entry.file_name, icon=file_entry.file_icon) filepath_ui.label(text=file_entry.file_name, icon=file_entry.file_icon)
if repo.show_file_size:
icon_map = {
'KiB' : 'DECORATE',
'MiB' : 'RADIOBUT_ON',
'GiB' : 'SHADING_SOLID',
'TiB' : 'SHADING_SOLID',
'PiB' : 'SHADING_SOLID',
'EiB' : 'SHADING_SOLID',
}
icon = 'BLANK1 '
if file_entry.file_size_KiB == 0:
icon = 'BLANK1'
text=" "
else:
icon = icon_map[file_entry.file_size[-3:]]
text=file_entry.file_size
width = get_sidebar_width(context)
if width < 800:
text = text.replace("iB", "")
filesize_ui.label(text=text)
else:
filesize_ui.label(text=text, icon=icon)
statuses = [file_entry.status] statuses = [file_entry.status]
# SVN operations # SVN operations
ops = [] ops = []
@ -114,10 +146,16 @@ class SVN_UL_file_list(UIList):
op.popup=True op.popup=True
ops.append(op) ops.append(op)
for i in range(max(0, 2-len(ops))):
ops_ui.label(text="", icon='BLANK1')
for op in ops: for op in ops:
if hasattr(op, 'file_rel_path'): if hasattr(op, 'file_rel_path'):
op.file_rel_path = file_entry.svn_path op.file_rel_path = file_entry.svn_path
for i in range(max(0, 2-len(statuses))):
status_ui.label(text="", icon='BLANK1')
# Populate the status icons. # Populate the status icons.
for status in statuses: for status in statuses:
icon = constants.SVN_STATUS_DATA[status][0] icon = constants.SVN_STATUS_DATA[status][0]
@ -167,8 +205,6 @@ class SVN_UL_file_list(UIList):
def draw_process_info(context, layout): def draw_process_info(context, layout):
prefs = get_addon_prefs(context) prefs = get_addon_prefs(context)
process_message = ""
any_error = False
col = layout.column() col = layout.column()
for process in Processes.processes.values(): for process in Processes.processes.values():
if process.name in {'Redraw Viewport', 'Activate File'}: if process.name in {'Redraw Viewport', 'Activate File'}:
@ -180,17 +216,14 @@ def draw_process_info(context, layout):
warning = row.operator( warning = row.operator(
'svn.clear_error', text=f"SVN {process.name}: Error Occurred. Hover to view", icon='ERROR') 'svn.clear_error', text=f"SVN {process.name}: Error Occurred. Hover to view", icon='ERROR')
warning.process_id = process.name warning.process_id = process.name
any_error = True continue
break
if process.is_running: if process.is_running:
message = process.get_ui_message(context) message = process.get_ui_message(context)
if message: if message:
message = message.replace("...", dots()) message = message.replace("...", dots())
process_message = f"SVN: {message}" col.label(text=message)
if not any_error and process_message:
col.label(text=process_message)
if prefs.debug_mode: if prefs.debug_mode:
col.label(text="Processes: " + col.label(text="Processes: " +
", ".join([p.name for p in Processes.running_processes])) ", ".join([p.name for p in Processes.running_processes]))
@ -204,24 +237,24 @@ class SVN_PT_filelist_options(bpy.types.Panel):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
prefs = get_addon_prefs(context)
repo = context.scene.svn.get_repo(context)
layout.label(text="Display Mode:")
layout.prop(repo, 'display_mode', text=" ", expand=True)
layout.use_property_split=True layout.use_property_split=True
layout.use_property_decorate=False layout.use_property_decorate=False
layout.ui_units_x = 17.0
repo = context.scene.svn.get_repo(context)
layout.prop(repo, 'display_mode', text="Display Mode", expand=True)
file_paths = layout.row() file_paths = layout.row()
file_paths.enabled = repo.display_mode == 'FLAT' file_paths.enabled = repo.display_mode == 'FLAT'
file_paths.prop(repo, 'show_file_paths') file_paths.prop(repo, 'show_file_paths')
layout.separator() layout.row().prop(repo, 'show_file_size')
layout.prop(prefs, 'do_auto_updates', icon='TEMP')
def get_sidebar_width(context) -> float:
for region in context.area.regions:
if region.type == 'UI':
return region.width
def draw_file_list(context, layout): def draw_file_list(context, layout):
prefs = get_addon_prefs(context) prefs = get_addon_prefs(context)
@ -235,20 +268,25 @@ def draw_file_list(context, layout):
row.label(text="Repository is not authenticated.", icon='ERROR') row.label(text="Repository is not authenticated.", icon='ERROR')
return return
main_col = layout.column() width = get_sidebar_width(context)
main_row = main_col.row() layout.label(text=str(width))
split = main_row.split(factor=0.6)
filepath_row = split.row()
filepath_row.label(text=" Filepath")
status_row = split.row() top_row = layout.row()
status_row.label(text=" Status") box = top_row.box().row()
filepath_ui, filesize_ui, status_ui, ops_ui = file_list_ui_split(box, context)
filepath_ui.alignment='CENTER'
filepath_ui.label(text="File")
ops_row = main_row.row() if repo.show_file_size:
ops_row.alignment = 'RIGHT' filesize_ui.label(text="Size")
ops_row.label(text="Operations") status_ui.label(text="Status")
row = main_col.row() ops_ui.alignment = 'RIGHT'
ops_ui.label(text="Actions")
top_row.column().prop(prefs, 'do_auto_updates', text="", icon='TEMP')
row = layout.row()
row.template_list( row.template_list(
"SVN_UL_file_list", "SVN_UL_file_list",
"svn_file_list", "svn_file_list",

View File

@ -53,8 +53,8 @@ class VIEW3D_PT_svn_files(Panel):
layout.use_property_split = True layout.use_property_split = True
layout.use_property_decorate = False layout.use_property_decorate = False
draw_process_info(context, layout)
draw_file_list(context, layout) draw_file_list(context, layout)
draw_process_info(context, layout)
registry = [ registry = [