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.
6 changed files with 85 additions and 47 deletions
Showing only changes of commit 547d96b058 - Show all commits

View File

@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
# (c) 2022, Blender Foundation - Demeter Dzadik # (c) 2022, Blender Foundation - Demeter Dzadik
import bpy
from collections import OrderedDict from collections import OrderedDict
SVN_STATUS_DATA = OrderedDict( SVN_STATUS_DATA = OrderedDict(
@ -94,7 +95,6 @@ SVN_STATUS_DATA = OrderedDict(
] ]
) )
# Based on PySVN/svn/constants.py/STATUS_TYPE_LOOKUP. # Based on PySVN/svn/constants.py/STATUS_TYPE_LOOKUP.
ENUM_SVN_STATUS = [ ENUM_SVN_STATUS = [
(status, status.title(), (status, status.title(),
@ -119,3 +119,15 @@ SVN_STATUS_CHAR_TO_NAME = {
SVN_STATUS_NAME_TO_CHAR = {value: key for key, SVN_STATUS_NAME_TO_CHAR = {value: key for key,
value in SVN_STATUS_CHAR_TO_NAME.items()} value in SVN_STATUS_CHAR_TO_NAME.items()}
DEPTH_ENUM = bpy.props.EnumProperty(
name="Depth",
description="The depth to which this directory should be updated", # For non-directory entries, this property is not used.
items=[
('INFINITY', 'Recursive (Default)', "Default update behaviour: Updates will recursively pull in any files or subdirectories not already present", 'OUTLINER', 3),
('IMMEDIATES', 'Non-Recursive', "Updates will pull in any files or subdirectories not already present; those subdirectories' will be left empty", 'PRESET', 2),
('FILES', 'Files Only', "Updates will pull in any files not already present, but not subdirectories", 'FILE', 1),
('EMPTY', 'Empty', "Updates will not pull in any files or subdirectories not already present", 'SELECT_SET', 0),
],
default='INFINITY'
)

View File

@ -14,6 +14,7 @@ from send2trash import send2trash
from ..threaded.execute_subprocess import execute_svn_command from ..threaded.execute_subprocess import execute_svn_command
from ..threaded.background_process import Processes from ..threaded.background_process import Processes
from ..util import get_addon_prefs, redraw_viewport from ..util import get_addon_prefs, redraw_viewport
from ..constants import DEPTH_ENUM
class SVN_Operator: class SVN_Operator:
@ -456,42 +457,49 @@ class SVN_OT_resolve_conflict(May_Modifiy_Current_Blend, Operator):
file_entry.status = 'normal' file_entry.status = 'normal'
class SVN_OT_set_directory_depth(Operator): class SVN_OT_set_directory_depth(SVN_Operator_Single_File, Operator):
bl_idname = "svn.set_directory_depth" bl_idname = "svn.set_directory_depth"
bl_label = "Set Directory Depth" bl_label = "Set Directory Depth"
bl_description = "Set update depth of this directory" bl_description = "Set update depth of this directory"
bl_options = {'INTERNAL'} bl_options = {'INTERNAL'}
depth: EnumProperty( depth: DEPTH_ENUM
name="Depth", popup: BoolProperty(
description="The depth to which this directory should be updated", # For non-directory entries, this property is not used. name="Popup",
items=[ description="Whether the operator should use a pop-up prompt",
('INFINITY', 'Recursive (Default)', "Default update behaviour: Updates will recursively pull in any files or subdirectories not already present", 'OUTLINER', 3), default=False
('IMMEDIATES', 'Non-Recursive', "Updates will pull in any files or subdirectories not already present; those subdirectories' will be left empty", 'PRESET', 2),
('FILES', 'Files Only', "Updates will pull in any files not already present, but not subdirectories", 'FILE', 1),
('EMPTY', 'Empty', "Updates will not pull in any files or subdirectories not already present", 'SELECT_SET', 0),
],
default='INFINITY'
) )
@classmethod @classmethod
def description(cls, context, properties): def description(cls, context, properties):
depth = cls.__annotations__['depth'] desc = DEPTH_ENUM.keywords['description']
desc = depth.keywords['description'] items = DEPTH_ENUM.keywords['items']
items = depth.keywords['items']
for identifier, name, item_desc, icon, num in items: for identifier, name, item_desc, icon, num in items:
if identifier == properties.depth: if identifier == properties.depth:
return desc + ":\n" + item_desc return desc + ":\n" + item_desc
def execute(self, context): def invoke(self, context, event):
repo = context.scene.svn.get_repo(context) if self.popup:
active_file = repo.active_file return context.window_manager.invoke_props_dialog(self)
return self.execute(context)
if not active_file.is_dir: def draw(self, context):
layout = self.layout
layout.use_property_split=True
layout.use_property_decorate=False
layout.prop(self, 'depth')
def execute(self, context):
file_entry = self.get_file(context)
if not file_entry.is_dir:
self.report({'ERROR'}, "Active file entry is not a directory") self.report({'ERROR'}, "Active file entry is not a directory")
return {'CANCELLED'} return {'CANCELLED'}
active_file.depth = self.depth file_entry.depth = self.depth
# Depth info needs to be saved to file to make sure it doesn't get lost.
get_addon_prefs(context).save_repo_info_to_file()
return {'FINISHED'} return {'FINISHED'}
@ -509,8 +517,11 @@ class SVN_OT_cleanup(SVN_Operator, Operator):
def execute(self, context: Context) -> Set[str]: def execute(self, context: Context) -> Set[str]:
repo = context.scene.svn.get_repo(context) repo = context.scene.svn.get_repo(context)
prefs = get_addon_prefs(context)
repo.external_files.clear() repo.external_files.clear()
# Load folder depth data from file.
prefs.load_repo_info_from_file()
self.execute_svn_command(context, ["svn", "cleanup"]) self.execute_svn_command(context, ["svn", "cleanup"])
repo.reload_svn_log(context) repo.reload_svn_log(context)

View File

@ -129,11 +129,13 @@ class SVN_addon_preferences(AddonPreferences):
saved_props = {'url', 'directory', 'name', saved_props = {'url', 'directory', 'name',
'username', 'password', 'display_name'} 'username', 'password', 'display_name'}
repo_data = {} repo_data = {}
for repo in self['repositories']: for repo_dict, repo in zip(self['repositories'], self.repositories):
directory = repo.get('directory', '') directory = repo_dict.get('directory', '')
repo_data[directory] = { repo_data[directory] = {
key: value for key, value in repo.to_dict().items() if key in saved_props} key: value for key, value in repo_dict.to_dict().items() if key in saved_props}
repo_data[directory]['custom_directory_depths'] = {dir_entry.svn_path: dir_entry.depth for dir_entry in repo.external_files if dir_entry.is_dir and dir_entry.depth != 'INFINITY'}
filepath = Path(bpy.utils.user_resource('CONFIG')) / \ filepath = Path(bpy.utils.user_resource('CONFIG')) / \
Path("blender_svn.txt") Path("blender_svn.txt")
@ -157,6 +159,14 @@ class SVN_addon_preferences(AddonPreferences):
repo = self.repositories.add() repo = self.repositories.add()
repo.directory = directory repo.directory = directory
for key, value in repo_data.items(): for key, value in repo_data.items():
if key == 'custom_directory_depths':
for dir_path, depth in repo_data['custom_directory_depths'].items():
dir_entry = self.external_files.get(dir_path)
if not dir_entry:
dir_entry = self.external_files.add()
dir_entry.svn_path = dir_path
dir_entry.depth = depth
else:
setattr(repo, key, value) setattr(repo, key, value)
finally: finally:
self.loading = False self.loading = False

View File

@ -48,17 +48,13 @@ class SVN_file(PropertyGroup):
options=set() options=set()
) )
depth: EnumProperty( depth: constants.DEPTH_ENUM
name="Depth", @property
description="The depth to which this directory should be updated. If this is a file and not a directory, this value isn't used", def depth_icon(self):
items=[ for identifier, name, item_desc, icon, num in constants.DEPTH_ENUM.keywords['items']:
('EMPTY', 'empty', "Updates will not pull in any files or subdirectories not already present"), if identifier == self.depth:
('FILES', 'files', "Updates will pull in any files not already present, but not subdirectories"), return icon
('IMMEDIATES', 'immediates', "Updates will pull in any files or subdirectories not already present; those subdirectories' will have depth=empty"),
('INFINITY', 'infinity', "Default update behaviour: Updates will pull in any files or subdirectories not already present; those subdirectories' will have depth-infinity"),
],
default='INFINITY'
)
status: EnumProperty( status: EnumProperty(
name="Status", name="Status",
description="SVN Status of the file in the local repository (aka working copy)", description="SVN Status of the file in the local repository (aka working copy)",
@ -269,6 +265,7 @@ class SVN_repository(PropertyGroup):
url: StringProperty( url: StringProperty(
name="URL", name="URL",
description="URL of the remote repository", description="URL of the remote repository",
update=update_repo_info_file
) )
def update_directory(self, context): def update_directory(self, context):

View File

@ -153,16 +153,16 @@ class BGP_SVN_Status(BackgroundProcess):
# NOTE: This one-by-one updating functionality conflicts with a potential # NOTE: This one-by-one updating functionality conflicts with a potential
# future support for partial check-outs, so that would require storing user-intended # future support for partial check-outs, so that would require storing user-intended
# partially checked out folders separately somewhere. # partially checked out folders separately somewhere.
self.list_command_output = execute_svn_command( # self.list_command_output = execute_svn_command(
context, # context,
["svn", "list", "--recursive", "--xml"], # ["svn", "list", "--recursive", "--xml"],
use_cred=True, # use_cred=True,
) # )
def process_output(self, context, prefs): def process_output(self, context, prefs):
repo = context.scene.svn.get_repo(context) repo = context.scene.svn.get_repo(context)
update_file_list_svn_status(context, svn_status_xml_to_dict(self.output)) update_file_list_svn_status(context, svn_status_xml_to_dict(self.output))
update_file_list_svn_list(context, self.list_command_output) # update_file_list_svn_list(context, self.list_command_output)
repo.refresh_ui_lists(context) repo.refresh_ui_lists(context)
self.timestamp_last_update = time.time() self.timestamp_last_update = time.time()
if prefs.do_auto_updates: if prefs.do_auto_updates:

View File

@ -74,6 +74,7 @@ class SVN_UL_file_list(UIList):
# I think it's better to let the user know in advance. # I think it's better to let the user know in advance.
statuses.append('conflicted') statuses.append('conflicted')
# Updating the file will create an actual conflict. # Updating the file will create an actual conflict.
if not prefs.do_auto_updates:
ops.append(ops_ui.operator( ops.append(ops_ui.operator(
'svn.update_single', text="", icon='IMPORT')) 'svn.update_single', text="", icon='IMPORT'))
@ -88,11 +89,13 @@ class SVN_UL_file_list(UIList):
# From user POV it makes a bit more sense to call a file that doesn't # From user POV it makes a bit more sense to call a file that doesn't
# exist yet "added" instead of "outdated". # exist yet "added" instead of "outdated".
statuses.append('added') statuses.append('added')
if not prefs.do_auto_updates:
ops.append(ops_ui.operator( ops.append(ops_ui.operator(
'svn.update_single', text="", icon='IMPORT')) 'svn.update_single', text="", icon='IMPORT'))
elif file_entry.status == 'normal' and file_entry.repos_status == 'modified': elif file_entry.status == 'normal' and file_entry.repos_status == 'modified':
# From user POV, this file is outdated, not 'normal'. # From user POV, this file is outdated, not 'normal'.
statuses = ['none'] statuses = ['none']
if not prefs.do_auto_updates:
ops.append(ops_ui.operator( ops.append(ops_ui.operator(
'svn.update_single', text="", icon='IMPORT')) 'svn.update_single', text="", icon='IMPORT'))
elif file_entry.status in ['normal', 'external', 'ignored']: elif file_entry.status in ['normal', 'external', 'ignored']:
@ -101,6 +104,11 @@ class SVN_UL_file_list(UIList):
print("Unknown file status: ", file_entry.svn_path, print("Unknown file status: ", file_entry.svn_path,
file_entry.status, file_entry.repos_status) file_entry.status, file_entry.repos_status)
if file_entry.is_dir:
op = ops_ui.operator('svn.set_directory_depth', text="", icon=file_entry.depth_icon)
op.popup=True
ops.append(op)
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