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
# (c) 2022, Blender Foundation - Demeter Dzadik
import bpy
from collections import OrderedDict
SVN_STATUS_DATA = OrderedDict(
@ -94,7 +95,6 @@ SVN_STATUS_DATA = OrderedDict(
]
)
# Based on PySVN/svn/constants.py/STATUS_TYPE_LOOKUP.
ENUM_SVN_STATUS = [
(status, status.title(),
@ -119,3 +119,15 @@ SVN_STATUS_CHAR_TO_NAME = {
SVN_STATUS_NAME_TO_CHAR = {value: key for key,
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.background_process import Processes
from ..util import get_addon_prefs, redraw_viewport
from ..constants import DEPTH_ENUM
class SVN_Operator:
@ -456,42 +457,49 @@ class SVN_OT_resolve_conflict(May_Modifiy_Current_Blend, Operator):
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_label = "Set Directory Depth"
bl_description = "Set update depth of this directory"
bl_options = {'INTERNAL'}
depth: 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'
depth: DEPTH_ENUM
popup: BoolProperty(
name="Popup",
description="Whether the operator should use a pop-up prompt",
default=False
)
@classmethod
def description(cls, context, properties):
depth = cls.__annotations__['depth']
desc = depth.keywords['description']
items = depth.keywords['items']
desc = DEPTH_ENUM.keywords['description']
items = DEPTH_ENUM.keywords['items']
for identifier, name, item_desc, icon, num in items:
if identifier == properties.depth:
return desc + ":\n" + item_desc
def execute(self, context):
repo = context.scene.svn.get_repo(context)
active_file = repo.active_file
def invoke(self, context, event):
if self.popup:
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")
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'}
@ -509,8 +517,11 @@ class SVN_OT_cleanup(SVN_Operator, Operator):
def execute(self, context: Context) -> Set[str]:
repo = context.scene.svn.get_repo(context)
prefs = get_addon_prefs(context)
repo.external_files.clear()
# Load folder depth data from file.
prefs.load_repo_info_from_file()
self.execute_svn_command(context, ["svn", "cleanup"])
repo.reload_svn_log(context)

View File

@ -129,11 +129,13 @@ class SVN_addon_preferences(AddonPreferences):
saved_props = {'url', 'directory', 'name',
'username', 'password', 'display_name'}
repo_data = {}
for repo in self['repositories']:
directory = repo.get('directory', '')
for repo_dict, repo in zip(self['repositories'], self.repositories):
directory = repo_dict.get('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')) / \
Path("blender_svn.txt")
@ -157,7 +159,15 @@ class SVN_addon_preferences(AddonPreferences):
repo = self.repositories.add()
repo.directory = directory
for key, value in repo_data.items():
setattr(repo, key, value)
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)
finally:
self.loading = False

View File

@ -48,17 +48,13 @@ class SVN_file(PropertyGroup):
options=set()
)
depth: EnumProperty(
name="Depth",
description="The depth to which this directory should be updated. If this is a file and not a directory, this value isn't used",
items=[
('EMPTY', 'empty', "Updates will not pull in any files or subdirectories not already present"),
('FILES', 'files', "Updates will pull in any files not already present, but not subdirectories"),
('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'
)
depth: constants.DEPTH_ENUM
@property
def depth_icon(self):
for identifier, name, item_desc, icon, num in constants.DEPTH_ENUM.keywords['items']:
if identifier == self.depth:
return icon
status: EnumProperty(
name="Status",
description="SVN Status of the file in the local repository (aka working copy)",
@ -269,6 +265,7 @@ class SVN_repository(PropertyGroup):
url: StringProperty(
name="URL",
description="URL of the remote repository",
update=update_repo_info_file
)
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
# future support for partial check-outs, so that would require storing user-intended
# partially checked out folders separately somewhere.
self.list_command_output = execute_svn_command(
context,
["svn", "list", "--recursive", "--xml"],
use_cred=True,
)
# self.list_command_output = execute_svn_command(
# context,
# ["svn", "list", "--recursive", "--xml"],
# use_cred=True,
# )
def process_output(self, context, prefs):
repo = context.scene.svn.get_repo(context)
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)
self.timestamp_last_update = time.time()
if prefs.do_auto_updates:

View File

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