Blender SVN: New Features #273
@ -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'
|
||||||
|
)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user