From 9b9b3de8f4bdcc32e9f23ee113bdca1c74ae6a51 Mon Sep 17 00:00:00 2001 From: "demeterdzadik@gmail.com" Date: Fri, 7 Jul 2023 14:50:25 +0200 Subject: [PATCH 01/12] SVN: Don't auth if repo non-existent --- scripts-blender/addons/blender_svn/repository.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_svn/repository.py b/scripts-blender/addons/blender_svn/repository.py index 92adb26a..1c06ac00 100644 --- a/scripts-blender/addons/blender_svn/repository.py +++ b/scripts-blender/addons/blender_svn/repository.py @@ -231,6 +231,7 @@ class SVN_repository(PropertyGroup): return self + @property def exists(self) -> bool: return Path(self.directory).exists() @@ -248,7 +249,8 @@ class SVN_repository(PropertyGroup): def authenticate(self, context): self.auth_failed = False - Processes.start('Authenticate') + if self.exists and self.is_cred_entered: + Processes.start('Authenticate') username: StringProperty( name="Username", -- 2.30.2 From a8fc415cddeb9c508ace5f4a4b54ee4c0b468ff0 Mon Sep 17 00:00:00 2001 From: "demeterdzadik@gmail.com" Date: Fri, 7 Jul 2023 14:50:58 +0200 Subject: [PATCH 02/12] SVN: Fix process not set is_running=False on error --- .../addons/blender_svn/threaded/background_process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_svn/threaded/background_process.py b/scripts-blender/addons/blender_svn/threaded/background_process.py index d9bf2ebf..ff959133 100644 --- a/scripts-blender/addons/blender_svn/threaded/background_process.py +++ b/scripts-blender/addons/blender_svn/threaded/background_process.py @@ -78,6 +78,7 @@ class BackgroundProcess: def handle_error(self, context, error): self.output = "" self.error = error.stderr.decode() + self.is_running = False def process_output(self, context, prefs): """ @@ -133,7 +134,6 @@ class BackgroundProcess: return self.tick_delay elif self.error: self.debug_print("Shutdown: There was an error.") - self.is_running = False return elif self.output: self.debug_print("Processing output") -- 2.30.2 From 47ec1f60ffffff5244b504226b979cb0f1c08ceb Mon Sep 17 00:00:00 2001 From: "demeterdzadik@gmail.com" Date: Fri, 7 Jul 2023 14:51:28 +0200 Subject: [PATCH 03/12] SVN: Better msgs on why repo isn't authenticating --- scripts-blender/addons/blender_svn/ui/ui_prefs.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts-blender/addons/blender_svn/ui/ui_prefs.py b/scripts-blender/addons/blender_svn/ui/ui_prefs.py index 36d78e27..c873c8aa 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_prefs.py +++ b/scripts-blender/addons/blender_svn/ui/ui_prefs.py @@ -138,11 +138,11 @@ def draw_prefs(self, context) -> None: draw_process_info(context, layout.row()) + if not self.active_repo.exists: + draw_repo_error(layout, "Repository not found on file system.") + return if not self.active_repo.authenticated and not auth_in_progress and not auth_error: - split = layout.split(factor=0.24) - split.row() - col = split.column() - col.label(text="Repository not authenticated. Enter your credentials.") + draw_repo_error(layout, "Repository not authenticated. Enter your credentials.") return if len(self.repositories) > 0 and self.active_repo.authenticated: @@ -155,6 +155,12 @@ def draw_prefs(self, context) -> None: layout.label(text="Log: ") draw_svn_log(context, layout, file_browser=False) +def draw_repo_error(layout, message): + split = layout.split(factor=0.24) + split.row() + col = split.column() + col.alert=True + col.label(text=message, icon='ERROR') registry = [ SVN_UL_repositories, -- 2.30.2 From aea472eee96cfb327f1f5a6cd3b4971648bece13 Mon Sep 17 00:00:00 2001 From: "demeterdzadik@gmail.com" Date: Fri, 7 Jul 2023 16:07:27 +0200 Subject: [PATCH 04/12] SVN: Massive UI drawing performance boost It was taking a crazy amount of time to determine whether the SVN log should even be drawn, and also determining whether each file should be filtered or not. Now instead of doing this at drawing time, we store a flag on each file, and update that whenever the search filter changes, and after status updates and operations. Should be safe...? Maybe? A similar change for SVN log entries might follow, but the slow-down from that seems nowhere near as bad. --- .../blender_svn/operators/simple_commands.py | 9 ++- .../addons/blender_svn/repository.py | 57 +++++++++++++------ .../addons/blender_svn/threaded/svn_status.py | 1 + .../addons/blender_svn/ui/ui_file_list.py | 21 +------ .../addons/blender_svn/ui/ui_log.py | 8 ++- 5 files changed, 56 insertions(+), 40 deletions(-) diff --git a/scripts-blender/addons/blender_svn/operators/simple_commands.py b/scripts-blender/addons/blender_svn/operators/simple_commands.py index fd4a8f47..f37eb20d 100644 --- a/scripts-blender/addons/blender_svn/operators/simple_commands.py +++ b/scripts-blender/addons/blender_svn/operators/simple_commands.py @@ -18,6 +18,11 @@ from ..util import get_addon_prefs, redraw_viewport # TODO: Add an operator to revert all local changes to the working copy. class SVN_Operator: + @staticmethod + def update_file_list(context): + repo = context.scene.svn.get_repo(context) + repo.update_file_filter(context) + def execute_svn_command(self, context, command: List[str], use_cred=False) -> str: # Since a status update might already be being requested when an SVN operator is run, # we want to ignore the first update after any SVN operator. @@ -51,6 +56,7 @@ class SVN_Operator_Single_File(SVN_Operator): # file.status_prediction_type = "SKIP_ONCE" redraw_viewport() + self.update_file_list(context) return ret def _execute(self, context: Context) -> Set[str]: @@ -227,13 +233,14 @@ class SVN_OT_download_repo_revision(SVN_Operator, Operator): # NOTE: This can take a long time, but providing a progress bar is # fundamentally impossible because SVN itself doesn't provide the command # line with any progress info. - # TODO: Doing it in the background may be an option, just a hassle. + # TODO: Should run in the background like regular `svn up`. output = self.execute_svn_command( context, ["svn", "up", f"-r{self.revision}", "--accept", "postpone"], use_cred=True ) self.report({"INFO"}, output.split("\n")[-2]) + self.update_file_list(context) return {"FINISHED"} def set_predicted_file_status(self, repo, file_entry: "SVN_file"): diff --git a/scripts-blender/addons/blender_svn/repository.py b/scripts-blender/addons/blender_svn/repository.py index 1c06ac00..bedd4bfe 100644 --- a/scripts-blender/addons/blender_svn/repository.py +++ b/scripts-blender/addons/blender_svn/repository.py @@ -123,6 +123,16 @@ class SVN_file(PropertyGroup): return 'QUESTION' + @property + def has_default_status(self): + return self.status == 'normal' and self.repos_status == 'none' and self.status_prediction_type == 'NONE' + + show_in_filelist: BoolProperty( + name="Show In File List", + description="Flag indicating whether this file should be drawn in the file list. This flag is updated for every file whenever the file search string is modified. If we did this filtering during drawing time, it is painfully slow", + default=False + ) + class SVN_log(PropertyGroup): """Property Group that can represent an SVN log entry.""" @@ -251,6 +261,8 @@ class SVN_repository(PropertyGroup): self.auth_failed = False if self.exists and self.is_cred_entered: Processes.start('Authenticate') + # Trigger the file list filtering. + self.file_search_filter = self.file_search_filter username: StringProperty( name="Username", @@ -467,18 +479,11 @@ class SVN_repository(PropertyGroup): return self.get_file_by_absolute_path(bpy.data.filepath) ### File List UIList filter properties ### - # These are normally stored on the UIList, but then they cannot be accessed - # from anywhere else, since template_list() does not return the UIList instance. + # Filtering properties are normally stored on the UIList, + # but then they cannot be accessed from anywhere else, + # since template_list() does not return the UIList instance. # We need to be able to access them outside of drawing code, to be able to - # know which entries are visible and ensure that a filtered out entry can never - # be the active one. - - def get_visible_indicies(self, context) -> List[int]: - flt_flags, _flt_neworder = bpy.types.SVN_UL_file_list.cls_filter_items( - context, self, 'external_files') - - visible_indicies = [i for i, flag in enumerate(flt_flags) if flag != 0] - return visible_indicies + # ensure that a filtered out entry can never be the active one. def force_good_active_index(self, context) -> bool: """ @@ -486,14 +491,34 @@ class SVN_repository(PropertyGroup): If the active element is being filtered out, set the active element to something that is visible. """ - visible_indicies = self.get_visible_indicies(context) - if len(visible_indicies) == 0: - self.external_files_active_index = 0 - elif self.external_files_active_index not in visible_indicies: - self.external_files_active_index = visible_indicies[0] + if not self.active_file.show_in_filelist: + for i, file in enumerate(self.external_files): + if file.show_in_filelist: + self.external_files_active_index = i + return def update_file_filter(self, context): """Should run when any of the SVN file list search filters are changed.""" + + UI_LIST = bpy.types.UI_UL_list + if self.file_search_filter: + filter_list = UI_LIST.filter_items_by_name( + self.file_search_filter, + 1, + self.external_files, + "name", + reverse=False + ) + filter_list = [bool(val) for val in filter_list] + self.external_files.foreach_set('show_in_filelist', filter_list) + else: + for file in self.external_files: + if file == self.current_blend_file: + file.show_in_filelist = True + continue + + file.show_in_filelist = not file.has_default_status + self.force_good_active_index(context) file_search_filter: StringProperty( diff --git a/scripts-blender/addons/blender_svn/threaded/svn_status.py b/scripts-blender/addons/blender_svn/threaded/svn_status.py index c3c15d88..c8c84e6c 100644 --- a/scripts-blender/addons/blender_svn/threaded/svn_status.py +++ b/scripts-blender/addons/blender_svn/threaded/svn_status.py @@ -273,6 +273,7 @@ def update_file_list(context, file_statuses: Dict[str, Tuple[str, str, int]]): if file_entry.svn_path not in svn_paths: repo.remove_file_entry(file_entry) + repo.update_file_filter(context) repo.force_good_active_index(context) diff --git a/scripts-blender/addons/blender_svn/ui/ui_file_list.py b/scripts-blender/addons/blender_svn/ui/ui_file_list.py index a368f30e..8584912a 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_file_list.py +++ b/scripts-blender/addons/blender_svn/ui/ui_file_list.py @@ -121,9 +121,9 @@ class SVN_UL_file_list(UIList): properties to the addon preferences) we can find a visible entry from other UI code, allowing us to avoid situations where the active element becomes hidden.""" - flt_flags = [] flt_neworder = [] list_items = getattr(data, propname) + flt_flags = [file.show_in_filelist * cls.UILST_FLT_ITEM for file in list_items] helper_funcs = bpy.types.UI_UL_list @@ -134,25 +134,6 @@ class SVN_UL_file_list(UIList): if not repo: return flt_flags, flt_neworder - def has_default_status(file): - return file.status == 'normal' and file.repos_status == 'none' and file.status_prediction_type == 'NONE' - - if repo.file_search_filter: - flt_flags = helper_funcs.filter_items_by_name(repo.file_search_filter, cls.UILST_FLT_ITEM, list_items, "name", - reverse=False) - else: - # Start with all files visible. - flt_flags = [cls.UILST_FLT_ITEM] * len(list_items) - - for i, item in enumerate(list_items): - if item == repo.current_blend_file: - # ALWAYS display the current .blend file. - continue - - if has_default_status(item): - # Filter out files that have default statuses. - flt_flags[i] = 0 - return flt_flags, flt_neworder def filter_items(self, context, data, propname): diff --git a/scripts-blender/addons/blender_svn/ui/ui_log.py b/scripts-blender/addons/blender_svn/ui/ui_log.py index 7600fbc3..62e5a25e 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_log.py +++ b/scripts-blender/addons/blender_svn/ui/ui_log.py @@ -99,13 +99,15 @@ def is_log_useful(context): repo = context.scene.svn.get_repo(context) if len(repo.log) == 0: return False - any_visible = repo.get_visible_indicies(context) - if not any_visible: - return False active_file = repo.active_file if active_file.status in ['unversioned', 'added']: return False + if repo.file_search_filter: + any_visible = any([file.show_in_filelist for file in repo.external_files]) + if not any_visible: + return False + return True -- 2.30.2 From df1879086949e7bee8fd32a64fdc62d4a672335e Mon Sep 17 00:00:00 2001 From: "demeterdzadik@gmail.com" Date: Fri, 7 Jul 2023 16:33:12 +0200 Subject: [PATCH 05/12] SVN: Optimize log filtering Same method as previous commit: Whenever the active file changes, we set flags on each log file, indicating whether that file is affected. This doesn't optimize text-based search filtering, only filtering by the active file. Either way, both of these types of filtering are already very fast, so the speed-up is not as dramatic as the previous improvement. And for that reason, I probably won't bother also optimizing the text-based filtering. --- .../addons/blender_svn/repository.py | 16 +++++++++ .../addons/blender_svn/ui/ui_log.py | 34 +++++++------------ 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/scripts-blender/addons/blender_svn/repository.py b/scripts-blender/addons/blender_svn/repository.py index bedd4bfe..f97d66f1 100644 --- a/scripts-blender/addons/blender_svn/repository.py +++ b/scripts-blender/addons/blender_svn/repository.py @@ -164,6 +164,11 @@ class SVN_log(PropertyGroup): name="Changed Files", description="List of file entries that were affected by this revision" ) + def changes_file(self, file: SVN_file) -> bool: + for affected_file in self.changed_files: + if affected_file.svn_path == "/"+file.svn_path: + return True + return False matches_filter: BoolProperty( name="Matches Filter", @@ -189,6 +194,11 @@ class SVN_log(PropertyGroup): date = self.revision_date_simple return " ".join([rev, auth, files, msg, date]).lower() + affects_active_file: BoolProperty( + name="Affects Active File", + description="Flag set whenever the active file index updates. Used to accelerate drawing performance by moving filtering logic from the drawing code to update callbacks and flags", + default=False + ) class SVN_repository(PropertyGroup): ### Basic SVN Info. ### @@ -435,6 +445,12 @@ class SVN_repository(PropertyGroup): relative_path=self.active_file.name) Processes.start('Activate File') + # Filter out log entries that did not affect the selected file. + self.log.foreach_set( + 'affects_active_file', + [log_entry.changes_file(self.active_file) for log_entry in self.log] + ) + external_files_active_index: IntProperty( name="File List", description="Files tracked by SVN", diff --git a/scripts-blender/addons/blender_svn/ui/ui_log.py b/scripts-blender/addons/blender_svn/ui/ui_log.py index 62e5a25e..0abd7f9b 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_log.py +++ b/scripts-blender/addons/blender_svn/ui/ui_log.py @@ -60,29 +60,21 @@ class SVN_UL_log(UIList): key=lambda i: log_entries[i].revision_number) flt_neworder.reverse() - is_filebrowser = context.space_data.type == 'FILE_BROWSER' - active_file = svn.get_filebrowser_active_file( - context) if is_filebrowser else svn.active_file - if not self.show_all_logs: - # Filter out log entries that did not affect the selected file. - for idx, log_entry in enumerate(log_entries): - for affected_file in log_entry.changed_files: - if affected_file.svn_path == "/"+active_file.svn_path: - # If the active file is one of the files affected by this log - # entry, break the for loop and skip the else block. - break - else: - flt_flags[idx] = 0 + flt_flags = [ + log_entry.affects_active_file * self.bitflag_filter_item + for log_entry in log_entries + ] - # 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 + 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 -- 2.30.2 From 968031176d1497ca2e7e915340de44d5f4679687 Mon Sep 17 00:00:00 2001 From: "demeterdzadik@gmail.com" Date: Fri, 7 Jul 2023 19:42:16 +0200 Subject: [PATCH 06/12] SVN: Improve UX when moving between repositories Jumping between Pets and Gold with the "Current Blend" active repository mode seems to be working super smoothly now. --- scripts-blender/addons/blender_svn/props.py | 5 ++--- scripts-blender/addons/blender_svn/svn_info.py | 2 +- scripts-blender/addons/blender_svn/threaded/svn_status.py | 4 ++++ scripts-blender/addons/blender_svn/ui/ui_file_list.py | 1 - scripts-blender/addons/blender_svn/ui/ui_prefs.py | 6 ++++-- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/scripts-blender/addons/blender_svn/props.py b/scripts-blender/addons/blender_svn/props.py index f989578c..cf3af646 100644 --- a/scripts-blender/addons/blender_svn/props.py +++ b/scripts-blender/addons/blender_svn/props.py @@ -42,13 +42,12 @@ class SVN_scene_properties(PropertyGroup): return prefs.active_repo def get_scene_repo(self, context) -> Optional['SVN_repository']: - scene_svn = context.scene.svn - if not scene_svn.svn_url or not scene_svn.svn_directory: + if not self.svn_url or not self.svn_directory: return prefs = get_addon_prefs(context) for repo in prefs.repositories: - if (repo.url == scene_svn.svn_url) and (Path(repo.directory) == Path(scene_svn.svn_directory)): + if (repo.url == self.svn_url) and (Path(repo.directory) == Path(self.svn_directory)): return repo diff --git a/scripts-blender/addons/blender_svn/svn_info.py b/scripts-blender/addons/blender_svn/svn_info.py index d3fb1454..37e451e4 100644 --- a/scripts-blender/addons/blender_svn/svn_info.py +++ b/scripts-blender/addons/blender_svn/svn_info.py @@ -12,7 +12,7 @@ def get_svn_info(path: Path or str) -> Tuple[str, str]: except subprocess.CalledProcessError as e: error_msg = e.stderr.decode() if "is not a working copy" in error_msg: - return None, None, None + return None, None elif "E200009" in error_msg: # If we're in a folder that wasn't yet added to the repo, # try again one folder higher. diff --git a/scripts-blender/addons/blender_svn/threaded/svn_status.py b/scripts-blender/addons/blender_svn/threaded/svn_status.py index c8c84e6c..48396cf7 100644 --- a/scripts-blender/addons/blender_svn/threaded/svn_status.py +++ b/scripts-blender/addons/blender_svn/threaded/svn_status.py @@ -89,6 +89,10 @@ def init_svn_of_current_file(_scene=None): repo = scene_svn.get_scene_repo(context) if not repo: repo = prefs.init_repo(context, scene_svn.svn_directory) + + for i, other_repo in enumerate(prefs.repositories): + if other_repo == repo: + prefs.active_repo_idx = i else: repo = prefs.active_repo diff --git a/scripts-blender/addons/blender_svn/ui/ui_file_list.py b/scripts-blender/addons/blender_svn/ui/ui_file_list.py index 8584912a..bea266f7 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_file_list.py +++ b/scripts-blender/addons/blender_svn/ui/ui_file_list.py @@ -28,7 +28,6 @@ class SVN_UL_file_list(UIList): if self.layout_type != 'DEFAULT': raise NotImplemented - repo = data file_entry = item prefs = get_addon_prefs(context) diff --git a/scripts-blender/addons/blender_svn/ui/ui_prefs.py b/scripts-blender/addons/blender_svn/ui/ui_prefs.py index c873c8aa..1f75c12f 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_prefs.py +++ b/scripts-blender/addons/blender_svn/ui/ui_prefs.py @@ -15,6 +15,10 @@ class SVN_UL_repositories(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): repo = item row = layout.row() + + prefs = get_addon_prefs(context) + if prefs.ui_mode == 'CURRENT_BLEND' and repo != context.scene.svn.get_repo(context): + row.enabled = False row.label(text=repo.display_name) if not repo.is_valid: @@ -107,8 +111,6 @@ def draw_prefs(self, context) -> None: repo_col.enabled = not auth_in_progress list_row = repo_col.row() - if self.ui_mode == 'CURRENT_BLEND': - list_row.enabled = False col = list_row.column() col.template_list( "SVN_UL_repositories", -- 2.30.2 From e108b80852739ae22d00548c8eba10e6a4e42891 Mon Sep 17 00:00:00 2001 From: "demeterdzadik@gmail.com" Date: Fri, 7 Jul 2023 21:28:27 +0200 Subject: [PATCH 07/12] SVN: Fix some errors when initializing the addon --- scripts-blender/addons/blender_svn/repository.py | 5 ++++- scripts-blender/addons/blender_svn/svn_info.py | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts-blender/addons/blender_svn/repository.py b/scripts-blender/addons/blender_svn/repository.py index f97d66f1..9f38a1c7 100644 --- a/scripts-blender/addons/blender_svn/repository.py +++ b/scripts-blender/addons/blender_svn/repository.py @@ -224,7 +224,8 @@ class SVN_repository(PropertyGroup): self.name = self.directory root_dir, base_url = get_svn_info(self.directory) - self.initialize(root_dir, base_url) + if root_dir and base_url: + self.initialize(root_dir, base_url) directory: StringProperty( name="Root Directory", @@ -507,6 +508,8 @@ class SVN_repository(PropertyGroup): If the active element is being filtered out, set the active element to something that is visible. """ + if len(self.external_files) == 0: + return if not self.active_file.show_in_filelist: for i, file in enumerate(self.external_files): if file.show_in_filelist: diff --git a/scripts-blender/addons/blender_svn/svn_info.py b/scripts-blender/addons/blender_svn/svn_info.py index 37e451e4..0c0b8532 100644 --- a/scripts-blender/addons/blender_svn/svn_info.py +++ b/scripts-blender/addons/blender_svn/svn_info.py @@ -6,8 +6,12 @@ from .threaded.execute_subprocess import execute_command def get_svn_info(path: Path or str) -> Tuple[str, str]: """Use the `svn info` command to get the root dir, the URL, and the relative URL.""" + path = Path(path) + if not path.exists(): + return "", "" + try: - dirpath_str = str(Path(path).as_posix()) + dirpath_str = str(path.as_posix()) svn_info = execute_command(dirpath_str, ["svn", "info"]) except subprocess.CalledProcessError as e: error_msg = e.stderr.decode() -- 2.30.2 From f3a865c51bd720cbba8579feefd2c7f924d460b6 Mon Sep 17 00:00:00 2001 From: "demeterdzadik@gmail.com" Date: Fri, 7 Jul 2023 21:29:36 +0200 Subject: [PATCH 08/12] SVN: Allow downloading repo version in bg --- .../blender_svn/operators/simple_commands.py | 32 +------------- .../blender_svn/operators/svn_update.py | 43 ++++++++++++++++--- .../addons/blender_svn/threaded/update.py | 10 ++++- .../addons/blender_svn/ui/ui_context_menus.py | 2 +- .../addons/blender_svn/ui/ui_file_list.py | 2 +- 5 files changed, 50 insertions(+), 39 deletions(-) diff --git a/scripts-blender/addons/blender_svn/operators/simple_commands.py b/scripts-blender/addons/blender_svn/operators/simple_commands.py index f37eb20d..0c922308 100644 --- a/scripts-blender/addons/blender_svn/operators/simple_commands.py +++ b/scripts-blender/addons/blender_svn/operators/simple_commands.py @@ -36,11 +36,12 @@ class SVN_Operator_Single_File(SVN_Operator): """Base class for SVN operators operating on a single file.""" file_rel_path: StringProperty() + # Flag to differentiate operators that require that the file exists pre-execute. missing_file_allowed = False def execute(self, context: Context) -> Set[str]: - """Most operators want to make sure that the file exists pre-execute.""" if not self.file_exists(context) and not type(self).missing_file_allowed: + # If the operator requires the file to exist and it doesn't, cancel. self.report({'ERROR'}, f'File is no longer on the file system: "{self.file_rel_path}"') return {'CANCELLED'} @@ -219,34 +220,6 @@ class SVN_OT_download_file_revision(May_Modifiy_Current_Blend, Operator): file_entry.repos_status = 'modified' -class SVN_OT_download_repo_revision(SVN_Operator, Operator): - bl_idname = "svn.download_repo_revision" - bl_label = "Download Repository Revision" - bl_description = "Revert the entire working copy to this revision. Can be used to see what state a project was in at a certain point in time. May take a long time to download all the files" - bl_options = {'INTERNAL'} - - missing_file_allowed = True - - revision: IntProperty() - - def execute(self, context: Context) -> Set[str]: - # NOTE: This can take a long time, but providing a progress bar is - # fundamentally impossible because SVN itself doesn't provide the command - # line with any progress info. - # TODO: Should run in the background like regular `svn up`. - output = self.execute_svn_command( - context, - ["svn", "up", f"-r{self.revision}", "--accept", "postpone"], - use_cred=True - ) - self.report({"INFO"}, output.split("\n")[-2]) - self.update_file_list(context) - return {"FINISHED"} - - def set_predicted_file_status(self, repo, file_entry: "SVN_file"): - file_entry.status = 'normal' - - class SVN_OT_restore_file(May_Modifiy_Current_Blend, Operator): bl_idname = "svn.restore_file" bl_label = "Restore File" @@ -453,7 +426,6 @@ class SVN_OT_cleanup(SVN_Operator, Operator): registry = [ SVN_OT_update_single, SVN_OT_download_file_revision, - SVN_OT_download_repo_revision, SVN_OT_revert_file, SVN_OT_restore_file, SVN_OT_unadd_file, diff --git a/scripts-blender/addons/blender_svn/operators/svn_update.py b/scripts-blender/addons/blender_svn/operators/svn_update.py index dc765a14..61af90c3 100644 --- a/scripts-blender/addons/blender_svn/operators/svn_update.py +++ b/scripts-blender/addons/blender_svn/operators/svn_update.py @@ -5,6 +5,7 @@ from typing import List, Dict, Union, Any, Set, Optional, Tuple import bpy from bpy.types import Operator, Context +from bpy.props import IntProperty from .simple_commands import May_Modifiy_Current_Blend from ..threaded.background_process import Processes @@ -17,6 +18,12 @@ class SVN_OT_update_all(May_Modifiy_Current_Blend, Operator): bl_description = "Download all the latest updates from the remote repository" bl_options = {'INTERNAL'} + revision: IntProperty( + name = "Revision", + description = "Which revision to revert the repository to. 0 means to update to the latest version instead", + default = 0 + ) + @classmethod def poll(cls, context): if get_addon_prefs(context).is_busy: @@ -35,29 +42,53 @@ class SVN_OT_update_all(May_Modifiy_Current_Blend, Operator): def invoke(self, context, event): repo = context.scene.svn.get_repo(context) current_blend = repo.current_blend_file - if current_blend and current_blend.repos_status != 'none': - self.file_rel_path = current_blend.svn_path - return context.window_manager.invoke_props_dialog(self, width=500) + if self.revision == 0: + if current_blend and current_blend.repos_status != 'none': + self.file_rel_path = current_blend.svn_path + return context.window_manager.invoke_props_dialog(self, width=500) + else: + for f in repo.external_files: + if f.status in ['modified', 'added', 'conflicted', 'deleted', 'missing', 'unversioned']: + return context.window_manager.invoke_props_dialog(self, width=500) + return self.execute(context) + def draw(self, context): + if self.revision != 0: + layout = self.layout + col = layout.column() + col.label(text="You have uncommitted local changes.") + col.label(text="These won't be lost, but if you want to revert the state of the entire local repository to a ") + col.label(text="past point in time, you would get a better result if you reverted or committed your changes first.") + col.separator() + col.label(text="Press OK to proceed anyways. Click out of this window to cancel.") + super().draw(context) + def execute(self, context: Context) -> Set[str]: self.set_predicted_file_statuses(context) Processes.stop('Status') if self.reload_file: + command = ["svn", "up", "--accept", "postpone"] + if self.revision > 0: + command.insert(2, f"-r{self.revision}") self.execute_svn_command( context, - ["svn", "up", "--accept", "postpone"], + command, use_cred=True ) bpy.ops.wm.open_mainfile(filepath=bpy.data.filepath, load_ui=False) Processes.start('Log') else: - Processes.start('Update') + Processes.start('Update', revision=self.revision) return {"FINISHED"} def set_predicted_file_statuses(self, context): repo = context.scene.svn.get_repo(context) + if self.revision != 0: + # File status prediction is not supported for reverting the entire + # repository. It would be complicated to implement, and not really useful. + return for f in repo.external_files: status_predict_flag_bkp = f.status_prediction_type f.status_prediction_type = "SVN_UP" @@ -79,5 +110,5 @@ class SVN_OT_update_all(May_Modifiy_Current_Blend, Operator): registry = [ - SVN_OT_update_all + SVN_OT_update_all, ] diff --git a/scripts-blender/addons/blender_svn/threaded/update.py b/scripts-blender/addons/blender_svn/threaded/update.py index af72f91a..467ca4cc 100644 --- a/scripts-blender/addons/blender_svn/threaded/update.py +++ b/scripts-blender/addons/blender_svn/threaded/update.py @@ -14,11 +14,19 @@ class BGP_SVN_Update(BackgroundProcess): repeat_delay = 0 debug = False + def __init__(self, revision=0): + super().__init__() + + self.revision = revision + def acquire_output(self, context, prefs): Processes.kill('Status') + command = ["svn", "up", "--accept", "postpone"] + if self.revision > 0: + command.insert(2, f"-r{self.revision}") self.output = execute_svn_command( context, - ["svn", "up", "--accept", "postpone"], + command, use_cred=True ) diff --git a/scripts-blender/addons/blender_svn/ui/ui_context_menus.py b/scripts-blender/addons/blender_svn/ui/ui_context_menus.py index cdee1748..1a38d6fe 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_context_menus.py +++ b/scripts-blender/addons/blender_svn/ui/ui_context_menus.py @@ -58,7 +58,7 @@ def svn_log_list_context_menu(self: UIList, context: Context) -> None: repo = context.scene.svn.get_repo(context) active_log = repo.active_log_filebrowser if is_filebrowser else repo.active_log - layout.operator("svn.download_repo_revision", + layout.operator("svn.update_all", text=f"Revert Repository To r{active_log.revision_number}").revision = active_log.revision_number layout.separator() diff --git a/scripts-blender/addons/blender_svn/ui/ui_file_list.py b/scripts-blender/addons/blender_svn/ui/ui_file_list.py index bea266f7..29a94b7a 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_file_list.py +++ b/scripts-blender/addons/blender_svn/ui/ui_file_list.py @@ -223,7 +223,7 @@ def draw_repo_file_list(context, layout, repo): col.separator() col.operator("svn.commit", icon='EXPORT', text="") - col.operator("svn.update_all", icon='IMPORT', text="") + col.operator("svn.update_all", icon='IMPORT', text="").revision=0 col.separator() col.operator("svn.cleanup", icon='BRUSH_DATA', text="") -- 2.30.2 From 42c5015f12b3ce40c5eb7d1bdef83c9305b0f438 Mon Sep 17 00:00:00 2001 From: "demeterdzadik@gmail.com" Date: Sat, 8 Jul 2023 00:54:09 +0200 Subject: [PATCH 09/12] SVN: Add svn checkout workflow This has been a long time coming, and with this, the SVN add-on could be considered feature-complete. (Although we still don't have SVN locking, but I would rather implement that in a completely custom and de-coupled way, another day.) --- .../addons/blender_svn/operators/__init__.py | 6 +- .../blender_svn/operators/simple_commands.py | 3 - .../blender_svn/operators/svn_checkout.py | 94 ++++++++++++++++ scripts-blender/addons/blender_svn/prefs.py | 16 ++- scripts-blender/addons/blender_svn/props.py | 2 +- .../addons/blender_svn/repository.py | 27 +++-- .../threaded/background_process.py | 3 + .../threaded/execute_subprocess.py | 2 +- .../addons/blender_svn/threaded/svn_log.py | 2 +- .../addons/blender_svn/threaded/svn_status.py | 4 +- .../addons/blender_svn/ui/ui_log.py | 9 +- .../addons/blender_svn/ui/ui_prefs.py | 103 ++++++++++++++++-- .../addons/blender_svn/ui/ui_sidebar.py | 2 +- 13 files changed, 236 insertions(+), 37 deletions(-) create mode 100644 scripts-blender/addons/blender_svn/operators/svn_checkout.py diff --git a/scripts-blender/addons/blender_svn/operators/__init__.py b/scripts-blender/addons/blender_svn/operators/__init__.py index a96a8436..1e1d3c5a 100644 --- a/scripts-blender/addons/blender_svn/operators/__init__.py +++ b/scripts-blender/addons/blender_svn/operators/__init__.py @@ -2,12 +2,14 @@ from . import ( simple_commands, svn_commit, svn_update, - ui_operators + ui_operators, + svn_checkout ) modules = [ simple_commands, svn_commit, svn_update, - ui_operators + ui_operators, + svn_checkout ] \ No newline at end of file diff --git a/scripts-blender/addons/blender_svn/operators/simple_commands.py b/scripts-blender/addons/blender_svn/operators/simple_commands.py index 0c922308..2f14d3c7 100644 --- a/scripts-blender/addons/blender_svn/operators/simple_commands.py +++ b/scripts-blender/addons/blender_svn/operators/simple_commands.py @@ -15,7 +15,6 @@ from ..threaded.execute_subprocess import execute_svn_command from ..threaded.background_process import Processes from ..util import get_addon_prefs, redraw_viewport -# TODO: Add an operator to revert all local changes to the working copy. class SVN_Operator: @staticmethod @@ -53,8 +52,6 @@ class SVN_Operator_Single_File(SVN_Operator): file = self.get_file(context) if file: Processes.start('Status') - # self.set_predicted_file_status(repo, file) - # file.status_prediction_type = "SKIP_ONCE" redraw_viewport() self.update_file_list(context) diff --git a/scripts-blender/addons/blender_svn/operators/svn_checkout.py b/scripts-blender/addons/blender_svn/operators/svn_checkout.py new file mode 100644 index 00000000..dee6db51 --- /dev/null +++ b/scripts-blender/addons/blender_svn/operators/svn_checkout.py @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# (c) 2022, Blender Foundation - Demeter Dzadik + +from typing import List, Dict, Union, Any, Set, Optional, Tuple + +from bpy.types import Operator +from bpy.props import BoolProperty + +from .simple_commands import SVN_Operator +from ..util import get_addon_prefs +from ..threaded.background_process import Processes + +import subprocess +from pathlib import Path + +class SVN_OT_checkout_initiate(Operator): + bl_idname = "svn.checkout_initiate" + bl_label = "Initiate SVN Checkout" + bl_description = "Checkout a remote SVN repository" + bl_options = {'INTERNAL'} + + create: BoolProperty( + name = "Create Repo Entry", + description = "Whether a new repo entry should be created, or the active one used", + default = True + ) + + def execute(self, context): + prefs = get_addon_prefs(context) + prefs.active_repo_mode = 'SELECTED_REPO' + if self.create: + prefs.repositories.add() + prefs.active_repo_idx = len(prefs.repositories)-1 + + prefs.checkout_mode = True + return {'FINISHED'} + +class SVN_OT_checkout_finalize(Operator, SVN_Operator): + bl_idname = "svn.checkout_finalize" + bl_label = "Finalize SVN Checkout" + bl_description = "Checkout the specified SVN repository to the selected path" + bl_options = {'INTERNAL'} + + def execute(self, context): + prefs = get_addon_prefs(context) + repo = prefs.active_repo + # `svn checkout` is an outlier in every way from other SVN commands: + # - Credentials are provided with an equal sign + # - We need live output in the console, but we don't need to store it. + # - It needs to be able to run even if the current directory isn't a valid repo. + # So, we're not going to use our `execute_subprocess` api here. + self.execute_svn_command( + context, + ['svn', 'cleanup'] + ) + p = subprocess.Popen( + ["svn", "checkout", f"--username={repo.username}", f"--password={repo.password}", repo.url, repo.display_name], + shell = False, + cwd = repo.directory+"/", + stdout = subprocess.PIPE, + start_new_session = True + ) + repo.directory = str((Path(repo.directory) / repo.display_name)) + while True: + line = p.stdout.readline().decode() + print(line.replace("\n", "")) + if not line: + break + prefs = get_addon_prefs(context) + prefs.checkout_mode = False + prefs.save_repo_info_to_file() + Processes.start('Authenticate') + return {'FINISHED'} + + +class SVN_OT_checkout_cancel(Operator): + bl_idname = "svn.checkout_cancel" + bl_label = "Cancel SVN Checkout" + bl_description = "Cancel the checkout UI" + bl_options = {'INTERNAL'} + + def execute(self, context): + prefs = get_addon_prefs(context) + prefs.checkout_mode = False + repo = prefs.active_repo + if not repo.url and not repo.username and not repo.password and not repo.directory: + prefs.repositories.remove(prefs.active_repo_idx) + return {'FINISHED'} + +registry = [ + SVN_OT_checkout_initiate, + SVN_OT_checkout_finalize, + SVN_OT_checkout_cancel +] diff --git a/scripts-blender/addons/blender_svn/prefs.py b/scripts-blender/addons/blender_svn/prefs.py index ab72f3f3..661d5e28 100644 --- a/scripts-blender/addons/blender_svn/prefs.py +++ b/scripts-blender/addons/blender_svn/prefs.py @@ -47,7 +47,7 @@ class SVN_addon_preferences(AddonPreferences): return self.idx_updating = True active_repo = self.active_repo - if self.ui_mode == 'CURRENT_BLEND': + if self.active_repo_mode == 'CURRENT_BLEND': scene_svn = context.scene.svn scene_svn_idx = self.repositories.find(scene_svn.svn_directory) if scene_svn_idx == -1: @@ -62,13 +62,19 @@ class SVN_addon_preferences(AddonPreferences): self.idx_updating = False - def update_ui_mode(self, context): - if self.ui_mode == 'CURRENT_BLEND': + def update_active_repo_mode(self, context): + if self.active_repo_mode == 'CURRENT_BLEND': scene_svn = context.scene.svn scene_svn_idx = self.repositories.find(scene_svn.svn_directory) self.active_repo_idx = scene_svn_idx - ui_mode: EnumProperty( + checkout_mode: BoolProperty( + name="Checkout In Progress", + description="Internal flag to indicate that the user is currently trying to create a new checkout", + default=False + ) + + active_repo_mode: EnumProperty( name = "Choose Repository", description = "Whether the add-on should communicate with the repository of the currently opened .blend file, or the repository selected in the list below", items = [ @@ -76,7 +82,7 @@ class SVN_addon_preferences(AddonPreferences): ('SELECTED_REPO', "Selected Repo", "Communicate with the selected repository") ], default = 'CURRENT_BLEND', - update = update_ui_mode + update = update_active_repo_mode ) active_repo_idx: IntProperty( diff --git a/scripts-blender/addons/blender_svn/props.py b/scripts-blender/addons/blender_svn/props.py index cf3af646..223cfd0c 100644 --- a/scripts-blender/addons/blender_svn/props.py +++ b/scripts-blender/addons/blender_svn/props.py @@ -36,7 +36,7 @@ class SVN_scene_properties(PropertyGroup): """ prefs = get_addon_prefs(context) - if prefs.ui_mode == 'CURRENT_BLEND': + if prefs.active_repo_mode == 'CURRENT_BLEND': return self.get_scene_repo(context) else: return prefs.active_repo diff --git a/scripts-blender/addons/blender_svn/repository.py b/scripts-blender/addons/blender_svn/repository.py index 9f38a1c7..54920f9f 100644 --- a/scripts-blender/addons/blender_svn/repository.py +++ b/scripts-blender/addons/blender_svn/repository.py @@ -234,13 +234,30 @@ class SVN_repository(PropertyGroup): description="Absolute directory path of the SVN repository's root in the file system", update=update_directory ) + @property - def is_valid(self): + def dir_exists(self): dir_path = Path(self.directory) return dir_path.exists() and dir_path.is_dir() - def initialize(self, directory: str, url: str, display_name=""): + @property + def is_valid_svn(self): + dir_path = Path(self.directory) + root_dir, base_url = get_svn_info(self.directory) + return ( + dir_path.exists() and + dir_path.is_dir() and + root_dir and base_url and + root_dir == self.directory and + base_url == self.url + ) + + def initialize(self, directory: str, url: str, display_name="", username="", password=""): self.url = url + if username: + self.username = username + if password: + self.password = password if self.directory != directory: # Don't set this if it's already set, to avoid infinite recursion # via the update callback. @@ -252,10 +269,6 @@ class SVN_repository(PropertyGroup): return self - @property - def exists(self) -> bool: - return Path(self.directory).exists() - ### Credentials. ### def update_cred(self, context): if not (self.username and self.password): @@ -270,7 +283,7 @@ class SVN_repository(PropertyGroup): def authenticate(self, context): self.auth_failed = False - if self.exists and self.is_cred_entered: + if self.is_valid_svn and self.is_cred_entered: Processes.start('Authenticate') # Trigger the file list filtering. self.file_search_filter = self.file_search_filter diff --git a/scripts-blender/addons/blender_svn/threaded/background_process.py b/scripts-blender/addons/blender_svn/threaded/background_process.py index ff959133..7427ed41 100644 --- a/scripts-blender/addons/blender_svn/threaded/background_process.py +++ b/scripts-blender/addons/blender_svn/threaded/background_process.py @@ -67,6 +67,9 @@ class BackgroundProcess: Should save data into self.output and self.error. Reading Blender data from this function is safe, but writing isn't! """ + repo = context.scene.svn.get_repo(context) + if not repo.is_valid_svn: + self.stop() try: self.acquire_output(context, prefs) except subprocess.CalledProcessError as error: diff --git a/scripts-blender/addons/blender_svn/threaded/execute_subprocess.py b/scripts-blender/addons/blender_svn/threaded/execute_subprocess.py index 6bc2c771..59ca0f59 100644 --- a/scripts-blender/addons/blender_svn/threaded/execute_subprocess.py +++ b/scripts-blender/addons/blender_svn/threaded/execute_subprocess.py @@ -34,7 +34,7 @@ def execute_svn_command(context, command: List[str], *, ignore_errors=False, pri command.append("--non-interactive") try: - if repo.is_valid: + if repo.is_valid_svn: return execute_command(repo.directory, command) except subprocess.CalledProcessError as error: if ignore_errors: diff --git a/scripts-blender/addons/blender_svn/threaded/svn_log.py b/scripts-blender/addons/blender_svn/threaded/svn_log.py index 343aa419..28d76b61 100644 --- a/scripts-blender/addons/blender_svn/threaded/svn_log.py +++ b/scripts-blender/addons/blender_svn/threaded/svn_log.py @@ -150,7 +150,7 @@ class BGP_SVN_Log(BackgroundProcess): print_errors=False, use_cred=True ) - self.debug_print("Output: \n" + self.output) + self.debug_print("Output: \n" + str(self.output)) except subprocess.CalledProcessError as error: error_msg = error.stderr.decode() if "No such revision" in error_msg: diff --git a/scripts-blender/addons/blender_svn/threaded/svn_status.py b/scripts-blender/addons/blender_svn/threaded/svn_status.py index 48396cf7..51d5af4d 100644 --- a/scripts-blender/addons/blender_svn/threaded/svn_status.py +++ b/scripts-blender/addons/blender_svn/threaded/svn_status.py @@ -77,7 +77,7 @@ def init_svn_of_current_file(_scene=None): repo.authenticated = False repo.auth_failed = False - if prefs.ui_mode == 'CURRENT_BLEND': + if prefs.active_repo_mode == 'CURRENT_BLEND': if not bpy.data.filepath: scene_svn.svn_url = "" return @@ -172,7 +172,7 @@ class BGP_SVN_Authenticate(BGP_SVN_Status): def acquire_output(self, context, prefs): repo = context.scene.svn.get_repo(context) - if not repo or not repo.is_cred_entered or repo.authenticated: + if not repo or not repo.is_valid_svn or not repo.is_cred_entered or repo.authenticated: return super().acquire_output(context, prefs) diff --git a/scripts-blender/addons/blender_svn/ui/ui_log.py b/scripts-blender/addons/blender_svn/ui/ui_log.py index 0abd7f9b..287b9f63 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_log.py +++ b/scripts-blender/addons/blender_svn/ui/ui_log.py @@ -89,16 +89,15 @@ class SVN_UL_log(UIList): def is_log_useful(context): repo = context.scene.svn.get_repo(context) - if len(repo.log) == 0: + 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 - if repo.file_search_filter: - any_visible = any([file.show_in_filelist for file in repo.external_files]) - if not any_visible: - return False + any_visible = any([file.show_in_filelist for file in repo.external_files]) + if not any_visible: + return False return True diff --git a/scripts-blender/addons/blender_svn/ui/ui_prefs.py b/scripts-blender/addons/blender_svn/ui/ui_prefs.py index 1f75c12f..53959011 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_prefs.py +++ b/scripts-blender/addons/blender_svn/ui/ui_prefs.py @@ -3,13 +3,14 @@ from pathlib import Path -from bpy.types import UIList, Operator +from bpy.types import UIList, Operator, Menu from bpy_extras.io_utils import ImportHelper from ..util import get_addon_prefs from .ui_log import draw_svn_log, is_log_useful from .ui_file_list import draw_repo_file_list, draw_process_info from ..threaded.background_process import Processes +import platform class SVN_UL_repositories(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): @@ -17,11 +18,11 @@ class SVN_UL_repositories(UIList): row = layout.row() prefs = get_addon_prefs(context) - if prefs.ui_mode == 'CURRENT_BLEND' and repo != context.scene.svn.get_repo(context): + if prefs.active_repo_mode == 'CURRENT_BLEND' and repo != context.scene.svn.get_repo(context): row.enabled = False row.label(text=repo.display_name) - if not repo.is_valid: + if not repo.dir_exists: row.alert = True row.prop(repo, 'directory', text="") @@ -80,13 +81,90 @@ class SVN_OT_repo_remove(Operator): prefs.save_repo_info_to_file() return {'FINISHED'} +class SVN_MT_add_repo(Menu): + bl_idname = "SVN_MT_add_repo" + bl_label = "Add Repo" -def draw_prefs(self, context) -> None: + def draw(self, context): + layout = self.layout + layout.operator("svn.repo_add", text="Browse Existing Checkout", icon='FILE_FOLDER') + layout.operator("svn.checkout_initiate", text="Create New Checkout", icon='URL').create=True + + +def draw_prefs(self, context): + if self.checkout_mode: + draw_prefs_checkout(self, context) + else: + draw_prefs_repos(self, context) + + +def draw_prefs_checkout(self, context): + def get_terminal_howto(): + msg_windows = "If you don't, cancel this operation and toggle it using Window->Toggle System Console." + msg_linux = "If you don't, quit Blender and re-launch it from a terminal." + msg_mac = msg_linux + + system = platform.system() + if system == "Windows": + return msg_windows + elif system == "Linux": + return msg_linux + elif system == "Darwin": + return msg_mac + + layout = self.layout + col = layout.column() + col.alert = True + + col.label(text="IMPORTANT! ", icon='ERROR') + col.label(text="Make sure you have Blender's terminal open!") + col.label(text=get_terminal_howto()) + col.separator() + col.label(text="Downloading a repository can take a long time, and the UI will be locked.") + col.label(text="Without a terminal, you won't be able to track the progress of the checkout.") + col.separator() + + col = layout.column() + col.label(text="To interrupt the checkout, you can press Ctrl+C in the terminal.", icon='INFO') + col.label(text="You can resume it by re-running this operation, or with the SVN Update button.", icon='INFO') + col.separator() + + prefs = get_addon_prefs(context) + repo = prefs.repositories[-1] + col.prop(repo, 'directory') + for other_repo in prefs.repositories: + if other_repo == repo: + continue + if other_repo.directory == repo.directory: + row = col.row() + row.alert=True + row.label(text="A repository at this filepath is already specified.", icon='ERROR') + break + + col.prop(repo, 'display_name', text="Folder Name", icon='NEWFOLDER') + col.prop(repo, 'url', icon='URL') + for other_repo in prefs.repositories: + if other_repo == repo: + continue + if other_repo.url == repo.url: + sub = col.column() + sub.alert=True + sub.label(text="A repository with this URL is already specified.") + sub.label(text="If you're sure you want to checkout another copy of the repo, feel free to proceed.") + break + col.prop(repo, 'username', icon='USER') + col.prop(repo, 'password', icon='LOCKED') + + op_row = layout.row() + op_row.operator('svn.checkout_finalize', text="Checkout", icon='CHECKMARK') + op_row.operator('svn.checkout_cancel', text="Cancel", icon="X") + +def draw_prefs_repos(self, context) -> None: layout = self.layout row = layout.row() row.use_property_split = True - row.prop(self, 'ui_mode', expand=True) + row.prop(self, 'active_repo_mode', expand=True) auth_in_progress = False auth_error = False @@ -95,7 +173,7 @@ def draw_prefs(self, context) -> None: auth_in_progress = auth_proc.is_running auth_error = auth_proc.error - if self.ui_mode == 'CURRENT_BLEND' and not context.scene.svn.get_repo(context): + if self.active_repo_mode == 'CURRENT_BLEND' and not context.scene.svn.get_repo(context): split = layout.split(factor=0.4) split.row() split.row().label(text="Current file is not in a repository.") @@ -122,7 +200,7 @@ def draw_prefs(self, context) -> None: ) op_col = list_row.column() - op_col.operator('svn.repo_add', icon='ADD', text="") + op_col.menu('SVN_MT_add_repo', icon='ADD', text="") op_col.operator('svn.repo_remove', icon='REMOVE', text="") if len(self.repositories) == 0: @@ -140,9 +218,15 @@ def draw_prefs(self, context) -> None: draw_process_info(context, layout.row()) - if not self.active_repo.exists: + if not self.active_repo.dir_exists: draw_repo_error(layout, "Repository not found on file system.") return + if not self.active_repo.is_valid_svn: + draw_repo_error(layout, "Directory is not an SVN repository.") + split = layout.split(factor=0.24) + split.row() + split.row().operator("svn.checkout_initiate", text="Create New Checkout", icon='URL').create=False + return if not self.active_repo.authenticated and not auth_in_progress and not auth_error: draw_repo_error(layout, "Repository not authenticated. Enter your credentials.") return @@ -167,5 +251,6 @@ def draw_repo_error(layout, message): registry = [ SVN_UL_repositories, SVN_OT_repo_add, - SVN_OT_repo_remove + SVN_OT_repo_remove, + SVN_MT_add_repo ] \ No newline at end of file diff --git a/scripts-blender/addons/blender_svn/ui/ui_sidebar.py b/scripts-blender/addons/blender_svn/ui/ui_sidebar.py index 6558d679..f936b79b 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_sidebar.py +++ b/scripts-blender/addons/blender_svn/ui/ui_sidebar.py @@ -17,7 +17,7 @@ class VIEW3D_PT_svn_credentials(Panel): @classmethod def poll(cls, context): prefs = get_addon_prefs(context) - if prefs.ui_mode == 'CURRENT_BLEND': + if prefs.active_repo_mode == 'CURRENT_BLEND': repo = context.scene.svn.get_scene_repo(context) else: repo = context.scene.svn.get_repo(context) -- 2.30.2 From 769062777224c8206407a0e336bf90bb9bab1fb2 Mon Sep 17 00:00:00 2001 From: "demeterdzadik@gmail.com" Date: Sat, 8 Jul 2023 01:18:23 +0200 Subject: [PATCH 10/12] SVN: Fix UI error when cancelling a checkout --- scripts-blender/addons/blender_svn/prefs.py | 2 +- scripts-blender/addons/blender_svn/ui/ui_prefs.py | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/scripts-blender/addons/blender_svn/prefs.py b/scripts-blender/addons/blender_svn/prefs.py index 661d5e28..c4e6801d 100644 --- a/scripts-blender/addons/blender_svn/prefs.py +++ b/scripts-blender/addons/blender_svn/prefs.py @@ -97,7 +97,7 @@ class SVN_addon_preferences(AddonPreferences): @property def active_repo(self) -> SVN_repository: - if len(self.repositories) > 0: + if 0 < len(self.repositories) < self.active_repo_idx-1: return self.repositories[self.active_repo_idx] debug_mode: BoolProperty( diff --git a/scripts-blender/addons/blender_svn/ui/ui_prefs.py b/scripts-blender/addons/blender_svn/ui/ui_prefs.py index 53959011..d0ff7a52 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_prefs.py +++ b/scripts-blender/addons/blender_svn/ui/ui_prefs.py @@ -207,14 +207,13 @@ def draw_prefs_repos(self, context) -> None: return if self.active_repo_idx-1 > len(self.repositories): return - active_repo = self.repositories[self.active_repo_idx] - if not active_repo: + if not self.active_repo: return - repo_col.prop(active_repo, 'display_name', icon='FILE_TEXT') - repo_col.prop(active_repo, 'url', icon='URL') - repo_col.prop(active_repo, 'username', icon='USER') - repo_col.prop(active_repo, 'password', icon='LOCKED') + repo_col.prop(self.active_repo, 'display_name', icon='FILE_TEXT') + repo_col.prop(self.active_repo, 'url', icon='URL') + repo_col.prop(self.active_repo, 'username', icon='USER') + repo_col.prop(self.active_repo, 'password', icon='LOCKED') draw_process_info(context, layout.row()) -- 2.30.2 From b3e9c83ebee54202de089a1971650ec36c97e58c Mon Sep 17 00:00:00 2001 From: "demeterdzadik@gmail.com" Date: Sat, 8 Jul 2023 01:22:30 +0200 Subject: [PATCH 11/12] SVN: autopep8 --- .../addons/blender_svn/__init__.py | 29 ++++++----- .../blender_svn/operators/simple_commands.py | 24 +++++---- .../blender_svn/operators/svn_checkout.py | 20 +++++--- .../blender_svn/operators/svn_commit.py | 11 ++-- .../blender_svn/operators/svn_update.py | 19 ++++--- .../blender_svn/operators/ui_operators.py | 3 +- scripts-blender/addons/blender_svn/prefs.py | 37 ++++++++------ scripts-blender/addons/blender_svn/props.py | 14 +++--- .../addons/blender_svn/repository.py | 42 +++++++++------- .../addons/blender_svn/svn_info.py | 1 + .../threaded/background_process.py | 13 +++-- .../addons/blender_svn/threaded/commit.py | 5 +- .../threaded/execute_subprocess.py | 1 + .../threaded/filebrowser_activate_file.py | 1 + .../blender_svn/threaded/redraw_viewport.py | 3 +- .../addons/blender_svn/threaded/svn_log.py | 14 ++++-- .../addons/blender_svn/threaded/svn_status.py | 40 +++++++-------- .../addons/blender_svn/threaded/update.py | 3 +- .../addons/blender_svn/ui/__init__.py | 2 +- .../addons/blender_svn/ui/ui_context_menus.py | 9 ++-- .../addons/blender_svn/ui/ui_file_list.py | 13 +++-- .../addons/blender_svn/ui/ui_filebrowser.py | 1 - .../addons/blender_svn/ui/ui_log.py | 7 +-- .../blender_svn/ui/ui_outdated_warning.py | 2 + .../addons/blender_svn/ui/ui_prefs.py | 50 +++++++++++++------ .../addons/blender_svn/ui/ui_sidebar.py | 1 - scripts-blender/addons/blender_svn/util.py | 3 ++ 27 files changed, 217 insertions(+), 151 deletions(-) diff --git a/scripts-blender/addons/blender_svn/__init__.py b/scripts-blender/addons/blender_svn/__init__.py index ef0b2a8d..8c7051de 100644 --- a/scripts-blender/addons/blender_svn/__init__.py +++ b/scripts-blender/addons/blender_svn/__init__.py @@ -2,6 +2,17 @@ # (c) 2021, Blender Foundation - Paul Golter # (c) 2022, Blender Foundation - Demeter Dzadik +from . import ( + props, + repository, + operators, + threaded, + ui, + prefs, + svn_info, +) +import importlib +from bpy.utils import register_class, unregister_class bl_info = { "name": "Blender SVN", "author": "Demeter Dzadik, Paul Golter", @@ -15,18 +26,6 @@ bl_info = { "category": "Generic", } -from bpy.utils import register_class, unregister_class -import importlib - -from . import ( - props, - repository, - operators, - threaded, - ui, - prefs, - svn_info, -) modules = [ props, @@ -38,6 +37,7 @@ modules = [ svn_info, ] + def register_unregister_modules(modules: list, register: bool): """Recursively register or unregister modules by looking for either un/register() functions or lists named `registry` which should be a list of @@ -54,7 +54,8 @@ def register_unregister_modules(modules: list, register: bool): register_func(c) except Exception as e: un = 'un' if not register else '' - print(f"Warning: SVN failed to {un}register class: {c.__name__}") + print( + f"Warning: SVN failed to {un}register class: {c.__name__}") print(e) if hasattr(m, 'modules'): @@ -65,8 +66,10 @@ def register_unregister_modules(modules: list, register: bool): elif hasattr(m, 'unregister'): m.unregister() + def register(): register_unregister_modules(modules, True) + def unregister(): register_unregister_modules(modules, False) diff --git a/scripts-blender/addons/blender_svn/operators/simple_commands.py b/scripts-blender/addons/blender_svn/operators/simple_commands.py index 2f14d3c7..08952342 100644 --- a/scripts-blender/addons/blender_svn/operators/simple_commands.py +++ b/scripts-blender/addons/blender_svn/operators/simple_commands.py @@ -41,7 +41,8 @@ class SVN_Operator_Single_File(SVN_Operator): def execute(self, context: Context) -> Set[str]: if not self.file_exists(context) and not type(self).missing_file_allowed: # If the operator requires the file to exist and it doesn't, cancel. - self.report({'ERROR'}, f'File is no longer on the file system: "{self.file_rel_path}"') + self.report( + {'ERROR'}, f'File is no longer on the file system: "{self.file_rel_path}"') return {'CANCELLED'} status = Processes.get('Status') @@ -145,7 +146,8 @@ class SVN_OT_update_single(May_Modifiy_Current_Blend, Operator): def _execute(self, context: Context) -> Set[str]: self.will_conflict = False - file_entry = context.scene.svn.get_repo(context).get_file_by_svn_path(self.file_rel_path) + file_entry = context.scene.svn.get_repo( + context).get_file_by_svn_path(self.file_rel_path) if file_entry.status not in ['normal', 'none']: self.will_conflict = True @@ -179,7 +181,8 @@ class SVN_OT_download_file_revision(May_Modifiy_Current_Blend, Operator): revision: IntProperty() def invoke(self, context, event): - file_entry = context.scene.svn.get_repo(context).get_file_by_svn_path(self.file_rel_path) + file_entry = context.scene.svn.get_repo( + context).get_file_by_svn_path(self.file_rel_path) if self.file_is_current_blend(context) and file_entry.status != 'normal': self.report({'ERROR'}, 'You must first revert or commit the changes to this file.') @@ -187,7 +190,8 @@ class SVN_OT_download_file_revision(May_Modifiy_Current_Blend, Operator): return super().invoke(context, event) def _execute(self, context: Context) -> Set[str]: - file_entry = context.scene.svn.get_repo(context).get_file_by_svn_path(self.file_rel_path) + file_entry = context.scene.svn.get_repo( + context).get_file_by_svn_path(self.file_rel_path) if file_entry.status == 'modified': # If file has local modifications, let's avoid a conflict by cancelling # and telling the user to resolve it in advance. @@ -197,7 +201,8 @@ class SVN_OT_download_file_revision(May_Modifiy_Current_Blend, Operator): self.execute_svn_command( context, - ["svn", "up" ,f"-r{self.revision}", f"{self.file_rel_path}", "--accept", "postpone"], + ["svn", "up", f"-r{self.revision}", + f"{self.file_rel_path}", "--accept", "postpone"], use_cred=True ) @@ -227,7 +232,7 @@ class SVN_OT_restore_file(May_Modifiy_Current_Blend, Operator): def _execute(self, context: Context) -> Set[str]: self.execute_svn_command( - context, + context, ["svn", "revert", f"{self.file_rel_path}"] ) @@ -327,7 +332,7 @@ class SVN_OT_remove_file(SVN_Operator_Single_File, Warning_Operator, Operator): def _execute(self, context: Context) -> Set[str]: self.execute_svn_command( - context, + context, ["svn", "remove", f"{self.file_rel_path}"] ) @@ -375,7 +380,8 @@ class SVN_OT_resolve_conflict(May_Modifiy_Current_Blend, Operator): def _execute(self, context: Context) -> Set[str]: self.execute_svn_command( context, - ["svn", "resolve", f"{self.file_rel_path}", "--accept", f"{self.resolve_method}"] + ["svn", "resolve", f"{self.file_rel_path}", + "--accept", f"{self.resolve_method}"] ) return {"FINISHED"} @@ -400,7 +406,7 @@ class SVN_OT_cleanup(SVN_Operator, Operator): def execute(self, context: Context) -> Set[str]: repo = context.scene.svn.get_repo(context) - + repo.external_files.clear() self.execute_svn_command(context, ["svn", "cleanup"]) repo.reload_svn_log(context) diff --git a/scripts-blender/addons/blender_svn/operators/svn_checkout.py b/scripts-blender/addons/blender_svn/operators/svn_checkout.py index dee6db51..03be4919 100644 --- a/scripts-blender/addons/blender_svn/operators/svn_checkout.py +++ b/scripts-blender/addons/blender_svn/operators/svn_checkout.py @@ -13,6 +13,7 @@ from ..threaded.background_process import Processes import subprocess from pathlib import Path + class SVN_OT_checkout_initiate(Operator): bl_idname = "svn.checkout_initiate" bl_label = "Initiate SVN Checkout" @@ -20,9 +21,9 @@ class SVN_OT_checkout_initiate(Operator): bl_options = {'INTERNAL'} create: BoolProperty( - name = "Create Repo Entry", - description = "Whether a new repo entry should be created, or the active one used", - default = True + name="Create Repo Entry", + description="Whether a new repo entry should be created, or the active one used", + default=True ) def execute(self, context): @@ -35,6 +36,7 @@ class SVN_OT_checkout_initiate(Operator): prefs.checkout_mode = True return {'FINISHED'} + class SVN_OT_checkout_finalize(Operator, SVN_Operator): bl_idname = "svn.checkout_finalize" bl_label = "Finalize SVN Checkout" @@ -54,11 +56,12 @@ class SVN_OT_checkout_finalize(Operator, SVN_Operator): ['svn', 'cleanup'] ) p = subprocess.Popen( - ["svn", "checkout", f"--username={repo.username}", f"--password={repo.password}", repo.url, repo.display_name], - shell = False, - cwd = repo.directory+"/", - stdout = subprocess.PIPE, - start_new_session = True + ["svn", "checkout", f"--username={repo.username}", + f"--password={repo.password}", repo.url, repo.display_name], + shell=False, + cwd=repo.directory+"/", + stdout=subprocess.PIPE, + start_new_session=True ) repo.directory = str((Path(repo.directory) / repo.display_name)) while True: @@ -87,6 +90,7 @@ class SVN_OT_checkout_cancel(Operator): prefs.repositories.remove(prefs.active_repo_idx) return {'FINISHED'} + registry = [ SVN_OT_checkout_initiate, SVN_OT_checkout_finalize, diff --git a/scripts-blender/addons/blender_svn/operators/svn_commit.py b/scripts-blender/addons/blender_svn/operators/svn_commit.py index 5c9c896d..90a9a9cd 100644 --- a/scripts-blender/addons/blender_svn/operators/svn_commit.py +++ b/scripts-blender/addons/blender_svn/operators/svn_commit.py @@ -86,7 +86,7 @@ class SVN_OT_commit(SVN_Operator, Popup_Operator, Operator): self.is_file_really_dirty = bpy.data.is_dirty # This flag is needed as a workaround because bpy.data.is_dirty gets set to True - # when we change the operator's checkboxes or + # when we change the operator's checkboxes or self.is_file_dirty_on_invoke = bpy.data.is_dirty for f in repo.external_files: @@ -119,7 +119,8 @@ class SVN_OT_commit(SVN_Operator, Popup_Operator, Operator): icon = 'ERROR' op_row = split.row() op_row.alignment = 'LEFT' - op_row.operator('svn.save_during_commit', icon='FILE_BLEND', text="Save") + op_row.operator('svn.save_during_commit', + icon='FILE_BLEND', text="Save") row.label(text=text, icon=icon) row = layout.row() @@ -159,9 +160,9 @@ class SVN_OT_commit(SVN_Operator, Popup_Operator, Operator): self.set_predicted_file_statuses(files_to_commit) Processes.stop('Status') Processes.start('Commit', - commit_msg=repo.commit_message, - file_list=filepaths - ) + commit_msg=repo.commit_message, + file_list=filepaths + ) report = f"{(len(files_to_commit))} files" if len(files_to_commit) == 1: diff --git a/scripts-blender/addons/blender_svn/operators/svn_update.py b/scripts-blender/addons/blender_svn/operators/svn_update.py index 61af90c3..161ad625 100644 --- a/scripts-blender/addons/blender_svn/operators/svn_update.py +++ b/scripts-blender/addons/blender_svn/operators/svn_update.py @@ -19,9 +19,9 @@ class SVN_OT_update_all(May_Modifiy_Current_Blend, Operator): bl_options = {'INTERNAL'} revision: IntProperty( - name = "Revision", - description = "Which revision to revert the repository to. 0 means to update to the latest version instead", - default = 0 + name="Revision", + description="Which revision to revert the repository to. 0 means to update to the latest version instead", + default=0 ) @classmethod @@ -50,7 +50,7 @@ class SVN_OT_update_all(May_Modifiy_Current_Blend, Operator): for f in repo.external_files: if f.status in ['modified', 'added', 'conflicted', 'deleted', 'missing', 'unversioned']: return context.window_manager.invoke_props_dialog(self, width=500) - + return self.execute(context) def draw(self, context): @@ -58,10 +58,13 @@ class SVN_OT_update_all(May_Modifiy_Current_Blend, Operator): layout = self.layout col = layout.column() col.label(text="You have uncommitted local changes.") - col.label(text="These won't be lost, but if you want to revert the state of the entire local repository to a ") - col.label(text="past point in time, you would get a better result if you reverted or committed your changes first.") + col.label( + text="These won't be lost, but if you want to revert the state of the entire local repository to a ") + col.label( + text="past point in time, you would get a better result if you reverted or committed your changes first.") col.separator() - col.label(text="Press OK to proceed anyways. Click out of this window to cancel.") + col.label( + text="Press OK to proceed anyways. Click out of this window to cancel.") super().draw(context) def execute(self, context: Context) -> Set[str]: @@ -72,7 +75,7 @@ class SVN_OT_update_all(May_Modifiy_Current_Blend, Operator): if self.revision > 0: command.insert(2, f"-r{self.revision}") self.execute_svn_command( - context, + context, command, use_cred=True ) diff --git a/scripts-blender/addons/blender_svn/operators/ui_operators.py b/scripts-blender/addons/blender_svn/operators/ui_operators.py index 607dc20b..7d1bd7cf 100644 --- a/scripts-blender/addons/blender_svn/operators/ui_operators.py +++ b/scripts-blender/addons/blender_svn/operators/ui_operators.py @@ -3,6 +3,7 @@ from bpy.props import BoolProperty, StringProperty from bpy.types import Operator from ..threaded.background_process import Processes + class SVN_OT_custom_tooltip(Operator): """Tooltip""" bl_idname = "svn.custom_tooltip" @@ -70,4 +71,4 @@ class SVN_OT_clear_error(Operator): registry = [ SVN_OT_custom_tooltip, SVN_OT_clear_error -] \ No newline at end of file +] diff --git a/scripts-blender/addons/blender_svn/prefs.py b/scripts-blender/addons/blender_svn/prefs.py index c4e6801d..d331c921 100644 --- a/scripts-blender/addons/blender_svn/prefs.py +++ b/scripts-blender/addons/blender_svn/prefs.py @@ -15,6 +15,7 @@ import json from pathlib import Path from .threaded.background_process import Processes + class SVN_addon_preferences(AddonPreferences): bl_idname = __package__ @@ -56,7 +57,7 @@ class SVN_addon_preferences(AddonPreferences): self.active_repo_idx = scene_svn_idx self.idx_updating = False return - + if not active_repo.authenticated and not active_repo.auth_failed and active_repo.is_cred_entered: active_repo.authenticate(context) @@ -75,14 +76,15 @@ class SVN_addon_preferences(AddonPreferences): ) active_repo_mode: EnumProperty( - name = "Choose Repository", - description = "Whether the add-on should communicate with the repository of the currently opened .blend file, or the repository selected in the list below", - items = [ + name="Choose Repository", + description="Whether the add-on should communicate with the repository of the currently opened .blend file, or the repository selected in the list below", + items=[ ('CURRENT_BLEND', "Current Blend", "Check if the current .blend file is in an SVN repository, and communicate with that if that is the case. The file list will display only the files of the repository of the current .blend file. If the current .blend is not in a repository, do nothing"), - ('SELECTED_REPO', "Selected Repo", "Communicate with the selected repository") + ('SELECTED_REPO', "Selected Repo", + "Communicate with the selected repository") ], - default = 'CURRENT_BLEND', - update = update_active_repo_mode + default='CURRENT_BLEND', + update=update_active_repo_mode ) active_repo_idx: IntProperty( @@ -101,11 +103,11 @@ class SVN_addon_preferences(AddonPreferences): return self.repositories[self.active_repo_idx] debug_mode: BoolProperty( - name = "Debug Mode", - description = "Enable some debug UI", - default = False + name="Debug Mode", + description="Enable some debug UI", + default=False ) - + @property def is_busy(self): return Processes.is_running('Commit', 'Update') @@ -117,21 +119,25 @@ class SVN_addon_preferences(AddonPreferences): ) def save_repo_info_to_file(self): - saved_props = {'url', 'directory', 'name', 'username', 'password', 'display_name'} + saved_props = {'url', 'directory', 'name', + 'username', 'password', 'display_name'} repo_data = {} for repo in self['repositories']: directory = repo.get('directory', '') - repo_data[directory] = {key:value for key, value in repo.to_dict().items() if key in saved_props} + repo_data[directory] = { + key: value for key, value in repo.to_dict().items() if key in saved_props} - filepath = Path(bpy.utils.user_resource('CONFIG')) / Path("blender_svn.txt") + filepath = Path(bpy.utils.user_resource('CONFIG')) / \ + Path("blender_svn.txt") with open(filepath, "w") as f: json.dump(repo_data, f, indent=4) def load_repo_info_from_file(self): self.loading = True try: - filepath = Path(bpy.utils.user_resource('CONFIG')) / Path("blender_svn.txt") + filepath = Path(bpy.utils.user_resource( + 'CONFIG')) / Path("blender_svn.txt") if not filepath.exists(): return @@ -154,6 +160,7 @@ class SVN_addon_preferences(AddonPreferences): draw = ui_prefs.draw_prefs + registry = [ SVN_addon_preferences ] diff --git a/scripts-blender/addons/blender_svn/props.py b/scripts-blender/addons/blender_svn/props.py index 223cfd0c..39a383ca 100644 --- a/scripts-blender/addons/blender_svn/props.py +++ b/scripts-blender/addons/blender_svn/props.py @@ -2,18 +2,16 @@ # (c) 2021, Blender Foundation - Paul Golter # (c) 2022, Blender Foundation - Demeter Dzadik +from .util import get_addon_prefs +from bpy.props import StringProperty, PointerProperty +from bpy.types import PropertyGroup +import bpy +from pathlib import Path +from typing import Optional, Dict, Any, List, Tuple, Set from . import wheels # This will load the dateutil and BAT wheel files. wheels.preload_dependencies() -from typing import Optional, Dict, Any, List, Tuple, Set -from pathlib import Path - -import bpy -from bpy.types import PropertyGroup -from bpy.props import StringProperty, PointerProperty - -from .util import get_addon_prefs class SVN_scene_properties(PropertyGroup): """Subversion properties to match this scene to a repo in the UserPrefs""" diff --git a/scripts-blender/addons/blender_svn/repository.py b/scripts-blender/addons/blender_svn/repository.py index 54920f9f..b920ca41 100644 --- a/scripts-blender/addons/blender_svn/repository.py +++ b/scripts-blender/addons/blender_svn/repository.py @@ -14,6 +14,7 @@ from .svn_info import get_svn_info from .util import get_addon_prefs from . import constants + class SVN_file(PropertyGroup): """Property Group that can represent a version of a File in an SVN repository.""" @@ -52,7 +53,8 @@ class SVN_file(PropertyGroup): items=[ ("NONE", "None", "File status is not predicted, but actual."), ("SVN_UP", "Update", "File status is predicted by `svn up`. Status is protected until process is finished."), - ("SVN_COMMIT", "Commit", "File status is predicted by `svn commit`. Status is protected until process is finished."), + ("SVN_COMMIT", "Commit", + "File status is predicted by `svn commit`. Status is protected until process is finished."), ("SKIP_ONCE", "Skip Once", "File status is predicted by a working-copy svn file operation, like Revert. Next status update should be ignored, and this enum should be set to SKIPPED_ONCE."), ("SKIPPED_ONCE", "Skipped Once", "File status update was skipped. Next status update can be considered accurate, and this flag can be reset to NONE. Until then, operations on this file should remain disabled."), ], @@ -164,6 +166,7 @@ class SVN_log(PropertyGroup): name="Changed Files", description="List of file entries that were affected by this revision" ) + def changes_file(self, file: SVN_file) -> bool: for affected_file in self.changed_files: if affected_file.svn_path == "/"+file.svn_path: @@ -190,7 +193,7 @@ class SVN_log(PropertyGroup): rev = "r"+str(self.revision_number) auth = self.revision_author files = " ".join([f.svn_path for f in self.changed_files]) - msg = self.commit_message + msg = self.commit_message date = self.revision_date_simple return " ".join([rev, auth, files, msg, date]).lower() @@ -200,6 +203,7 @@ class SVN_log(PropertyGroup): default=False ) + class SVN_repository(PropertyGroup): ### Basic SVN Info. ### @property @@ -210,14 +214,14 @@ class SVN_repository(PropertyGroup): get_addon_prefs(context).save_repo_info_to_file() display_name: StringProperty( - name = "Display Name", - description = "Display name of this SVN repository", - update = update_repo_info_file + name="Display Name", + description="Display name of this SVN repository", + update=update_repo_info_file ) url: StringProperty( - name = "URL", - description = "URL of the remote repository", + name="URL", + description="URL of the remote repository", ) def update_directory(self, context): @@ -245,8 +249,8 @@ class SVN_repository(PropertyGroup): dir_path = Path(self.directory) root_dir, base_url = get_svn_info(self.directory) return ( - dir_path.exists() and - dir_path.is_dir() and + dir_path.exists() and + dir_path.is_dir() and root_dir and base_url and root_dir == self.directory and base_url == self.url @@ -385,7 +389,6 @@ class SVN_repository(PropertyGroup): current = file.revision return latest > current - ### SVN File List. ### external_files: CollectionProperty(type=SVN_file) @@ -461,8 +464,9 @@ class SVN_repository(PropertyGroup): # Filter out log entries that did not affect the selected file. self.log.foreach_set( - 'affects_active_file', - [log_entry.changes_file(self.active_file) for log_entry in self.log] + 'affects_active_file', + [log_entry.changes_file(self.active_file) + for log_entry in self.log] ) external_files_active_index: IntProperty( @@ -509,8 +513,8 @@ class SVN_repository(PropertyGroup): return self.get_file_by_absolute_path(bpy.data.filepath) ### File List UIList filter properties ### - # Filtering properties are normally stored on the UIList, - # but then they cannot be accessed from anywhere else, + # Filtering properties are normally stored on the UIList, + # but then they cannot be accessed from anywhere else, # since template_list() does not return the UIList instance. # We need to be able to access them outside of drawing code, to be able to # ensure that a filtered out entry can never be the active one. @@ -531,13 +535,13 @@ class SVN_repository(PropertyGroup): def update_file_filter(self, context): """Should run when any of the SVN file list search filters are changed.""" - + UI_LIST = bpy.types.UI_UL_list if self.file_search_filter: filter_list = UI_LIST.filter_items_by_name( - self.file_search_filter, - 1, - self.external_files, + self.file_search_filter, + 1, + self.external_files, "name", reverse=False ) @@ -564,4 +568,4 @@ registry = [ SVN_file, SVN_log, SVN_repository, -] \ No newline at end of file +] diff --git a/scripts-blender/addons/blender_svn/svn_info.py b/scripts-blender/addons/blender_svn/svn_info.py index 0c0b8532..275658b9 100644 --- a/scripts-blender/addons/blender_svn/svn_info.py +++ b/scripts-blender/addons/blender_svn/svn_info.py @@ -4,6 +4,7 @@ import subprocess from .threaded.execute_subprocess import execute_command + def get_svn_info(path: Path or str) -> Tuple[str, str]: """Use the `svn info` command to get the root dir, the URL, and the relative URL.""" path = Path(path) diff --git a/scripts-blender/addons/blender_svn/threaded/background_process.py b/scripts-blender/addons/blender_svn/threaded/background_process.py index 7427ed41..983a7a06 100644 --- a/scripts-blender/addons/blender_svn/threaded/background_process.py +++ b/scripts-blender/addons/blender_svn/threaded/background_process.py @@ -2,7 +2,8 @@ # (c) 2022, Blender Foundation - Demeter Dzadik import bpy -import threading, subprocess +import threading +import subprocess import random from typing import List @@ -180,8 +181,8 @@ class BackgroundProcess: if not bpy.app.timers.is_registered(self.timer_function): self.debug_print("Register timer") bpy.app.timers.register( - self.timer_function, - first_interval=self.first_interval, + self.timer_function, + first_interval=self.first_interval, persistent=persistent ) @@ -206,6 +207,8 @@ def get_recursive_subclasses(typ) -> List[type]: processes = {} + + class ProcessManager: @property def processes(self): @@ -227,7 +230,6 @@ class ProcessManager: if proc_name in self.processes: return self.processes[proc_name].is_running - def get(self, proc_name: str): return self.processes.get(proc_name) @@ -259,6 +261,7 @@ class ProcessManager: process.stop() del self.processes[proc_name] + # I named this variable with title-case, to indicate that it's a Singleton. # There should only be one. -Processes = ProcessManager() \ No newline at end of file +Processes = ProcessManager() diff --git a/scripts-blender/addons/blender_svn/threaded/commit.py b/scripts-blender/addons/blender_svn/threaded/commit.py index c0644dcf..af7c7eb2 100644 --- a/scripts-blender/addons/blender_svn/threaded/commit.py +++ b/scripts-blender/addons/blender_svn/threaded/commit.py @@ -10,6 +10,7 @@ from .background_process import Processes, BackgroundProcess from .execute_subprocess import execute_svn_command from ..util import get_addon_prefs + class BGP_SVN_Commit(BackgroundProcess): name = "Commit" needs_authentication = True @@ -33,7 +34,8 @@ class BGP_SVN_Commit(BackgroundProcess): Processes.kill('Status') sanitized_commit_msg = self.commit_msg.replace('"', "'") - command = ["svn", "commit", "-m", f"{sanitized_commit_msg}"] + self.file_list + command = ["svn", "commit", "-m", + f"{sanitized_commit_msg}"] + self.file_list self.output = execute_svn_command( context, command, @@ -67,4 +69,3 @@ class BGP_SVN_Commit(BackgroundProcess): def stop(self): super().stop() - \ No newline at end of file diff --git a/scripts-blender/addons/blender_svn/threaded/execute_subprocess.py b/scripts-blender/addons/blender_svn/threaded/execute_subprocess.py index 59ca0f59..7e395c9b 100644 --- a/scripts-blender/addons/blender_svn/threaded/execute_subprocess.py +++ b/scripts-blender/addons/blender_svn/threaded/execute_subprocess.py @@ -4,6 +4,7 @@ import subprocess from typing import List + def get_credential_commands(context) -> List[str]: repo = context.scene.svn.get_repo(context) assert (repo.is_cred_entered), "No username or password entered for this repository. The UI shouldn't have allowed you to get into a state where you can press an SVN operation button without having your credentials entered, so this is a bug!" diff --git a/scripts-blender/addons/blender_svn/threaded/filebrowser_activate_file.py b/scripts-blender/addons/blender_svn/threaded/filebrowser_activate_file.py index f6b9c0bd..5438fbe5 100644 --- a/scripts-blender/addons/blender_svn/threaded/filebrowser_activate_file.py +++ b/scripts-blender/addons/blender_svn/threaded/filebrowser_activate_file.py @@ -1,5 +1,6 @@ from .background_process import BackgroundProcess + class BGP_SVN_Activate_File(BackgroundProcess): """This crazy hacky method of activating the file with some delay is necessary because Blender won't let us select the file immediately when changing the diff --git a/scripts-blender/addons/blender_svn/threaded/redraw_viewport.py b/scripts-blender/addons/blender_svn/threaded/redraw_viewport.py index 5dbedd71..f2911c2c 100644 --- a/scripts-blender/addons/blender_svn/threaded/redraw_viewport.py +++ b/scripts-blender/addons/blender_svn/threaded/redraw_viewport.py @@ -1,6 +1,7 @@ from .background_process import BackgroundProcess, Processes from ..util import redraw_viewport + class BGP_SVN_Redraw_Viewport(BackgroundProcess): name = "Redraw Viewport" repeat_delay = 1 @@ -18,4 +19,4 @@ class BGP_SVN_Redraw_Viewport(BackgroundProcess): def register(): - Processes.start("Redraw Viewport") \ No newline at end of file + Processes.start("Redraw Viewport") diff --git a/scripts-blender/addons/blender_svn/threaded/svn_log.py b/scripts-blender/addons/blender_svn/threaded/svn_log.py index 28d76b61..3563eed4 100644 --- a/scripts-blender/addons/blender_svn/threaded/svn_log.py +++ b/scripts-blender/addons/blender_svn/threaded/svn_log.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-or-later # (c) 2022, Blender Foundation - Demeter Dzadik +from datetime import datetime from pathlib import Path import subprocess @@ -57,7 +58,8 @@ def reload_svn_log(self, context): log_entry.revision_author = r_author log_entry.revision_date = r_date - log_entry.revision_date_simple = svn_date_simple(r_date).split(" ")[0][5:] + log_entry.revision_date_simple = svn_date_simple(r_date).split(" ")[ + 0][5:] # File change set is on line 3 until the commit message begins... file_change_lines = chunk[2:-(r_msg_length+1)] @@ -74,7 +76,8 @@ def reload_svn_log(self, context): log_file_entry = log_entry.changed_files.add() log_file_entry.name = file_path.name log_file_entry.svn_path = str(file_path.as_posix()) - log_file_entry.absolute_path = str(repo.svn_to_absolute_path(file_path).as_posix()) + log_file_entry.absolute_path = str( + repo.svn_to_absolute_path(file_path).as_posix()) log_file_entry.revision = r_number log_file_entry.status = constants.SVN_STATUS_CHAR_TO_NAME[status_char] @@ -146,7 +149,8 @@ class BGP_SVN_Log(BackgroundProcess): try: self.output = execute_svn_command( context, - ["svn", "log", "--verbose", f"-r{latest_log_rev+1}:HEAD", "--limit", "10"], + ["svn", "log", "--verbose", + f"-r{latest_log_rev+1}:HEAD", "--limit", "10"], print_errors=False, use_cred=True ) @@ -170,7 +174,7 @@ class BGP_SVN_Log(BackgroundProcess): rev_no = repo.log[-1].revision_number return f"Updating log. Current: r{rev_no}..." -from datetime import datetime + def svn_date_to_datetime(datetime_str: str) -> datetime: """Convert a string from SVN's datetime format to a datetime object.""" date, time, _timezone, _day, _n_day, _mo, _y = datetime_str.split(" ") @@ -184,4 +188,4 @@ def svn_date_simple(datetime_str: str) -> str: date_str = f"{dt.year}-{month_name}-{dt.day}" time_str = f"{str(dt.hour).zfill(2)}:{str(dt.minute).zfill(2)}" - return date_str + " " + time_str \ No newline at end of file + return date_str + " " + time_str diff --git a/scripts-blender/addons/blender_svn/threaded/svn_status.py b/scripts-blender/addons/blender_svn/threaded/svn_status.py index 51d5af4d..06f85ac8 100644 --- a/scripts-blender/addons/blender_svn/threaded/svn_status.py +++ b/scripts-blender/addons/blender_svn/threaded/svn_status.py @@ -1,25 +1,22 @@ # SPDX-License-Identifier: GPL-2.0-or-later # (c) 2022, Blender Foundation - Demeter Dzadik +from ..svn_info import get_svn_info +from ..util import get_addon_prefs +from .. import constants +from .execute_subprocess import execute_svn_command +from .background_process import BackgroundProcess, Processes +from bpy.types import Operator +from bpy.props import StringProperty +import bpy +import xmltodict +import time +from pathlib import Path +from typing import List, Dict, Union, Any, Set, Optional, Tuple from .. import wheels # This will load the xmltodict wheel file. wheels.preload_dependencies() -from typing import List, Dict, Union, Any, Set, Optional, Tuple -from pathlib import Path -import time -import xmltodict - -import bpy -from bpy.props import StringProperty -from bpy.types import Operator - -from .background_process import BackgroundProcess, Processes -from .execute_subprocess import execute_svn_command -from .. import constants -from ..util import get_addon_prefs -from ..svn_info import get_svn_info - class SVN_OT_explain_status(Operator): bl_idname = "svn.explain_status" @@ -72,7 +69,7 @@ def init_svn_of_current_file(_scene=None): prefs.sync_repo_info_file() for repo in prefs.repositories: - # This would ideally only run when opening Blender for the first + # This would ideally only run when opening Blender for the first # time, but there is no app handler for that, sadly. repo.authenticated = False repo.auth_failed = False @@ -89,7 +86,7 @@ def init_svn_of_current_file(_scene=None): repo = scene_svn.get_scene_repo(context) if not repo: repo = prefs.init_repo(context, scene_svn.svn_directory) - + for i, other_repo in enumerate(prefs.repositories): if other_repo == repo: prefs.active_repo_idx = i @@ -129,6 +126,7 @@ def set_scene_svn_info(context) -> bool: ############## AUTOMATICALLY KEEPING FILE STATUSES UP TO DATE ################## ################################################################################ + class BGP_SVN_Status(BackgroundProcess): name = "Status" needs_authentication = True @@ -142,7 +140,7 @@ class BGP_SVN_Status(BackgroundProcess): def acquire_output(self, context, prefs): self.output = execute_svn_command( - context, + context, ["svn", "status", "--show-updates", "--verbose", "--xml"], use_cred=True ) @@ -228,7 +226,8 @@ def update_file_list(context, file_statuses: Dict[str, Tuple[str, str, int]]): entry_existed = False file_entry = repo.external_files.add() file_entry.svn_path = svn_path_str - file_entry.absolute_path = str(repo.svn_to_absolute_path(svn_path).as_posix()) + file_entry.absolute_path = str( + repo.svn_to_absolute_path(svn_path).as_posix()) file_entry['name'] = svn_path.name if not file_entry.exists: @@ -261,7 +260,8 @@ def update_file_list(context, file_statuses: Dict[str, Tuple[str, str, int]]): # File entry status has changed between local and repo. file_strings = [] for svn_path, repos_status in new_files_on_repo: - status_char = constants.SVN_STATUS_NAME_TO_CHAR.get(repos_status, " ") + status_char = constants.SVN_STATUS_NAME_TO_CHAR.get( + repos_status, " ") file_strings.append(f"{status_char} {svn_path}") print( "SVN: Detected file changes on remote:\n", diff --git a/scripts-blender/addons/blender_svn/threaded/update.py b/scripts-blender/addons/blender_svn/threaded/update.py index 467ca4cc..e7ac825e 100644 --- a/scripts-blender/addons/blender_svn/threaded/update.py +++ b/scripts-blender/addons/blender_svn/threaded/update.py @@ -25,7 +25,7 @@ class BGP_SVN_Update(BackgroundProcess): if self.revision > 0: command.insert(2, f"-r{self.revision}") self.output = execute_svn_command( - context, + context, command, use_cred=True ) @@ -44,7 +44,6 @@ class BGP_SVN_Update(BackgroundProcess): Processes.start('Log') Processes.start('Status') - def get_ui_message(self, context) -> str: """Return a string that should be drawn in the UI for user feedback, depending on the state of the process.""" diff --git a/scripts-blender/addons/blender_svn/ui/__init__.py b/scripts-blender/addons/blender_svn/ui/__init__.py index dfc52cbb..10daf702 100644 --- a/scripts-blender/addons/blender_svn/ui/__init__.py +++ b/scripts-blender/addons/blender_svn/ui/__init__.py @@ -16,4 +16,4 @@ modules = [ ui_prefs, ui_outdated_warning, ui_context_menus -] \ No newline at end of file +] diff --git a/scripts-blender/addons/blender_svn/ui/ui_context_menus.py b/scripts-blender/addons/blender_svn/ui/ui_context_menus.py index 1a38d6fe..8082620b 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_context_menus.py +++ b/scripts-blender/addons/blender_svn/ui/ui_context_menus.py @@ -6,6 +6,7 @@ from bpy.types import Context, UIList, Operator from bpy.props import StringProperty from pathlib import Path + class SVN_OT_open_blend_file(Operator): # This is needed because drawing a button for wm.open_mainfile in the UI # directly simply does not work; Blender just opens a full-screen filebrowser, @@ -18,7 +19,7 @@ class SVN_OT_open_blend_file(Operator): filepath: StringProperty() def execute(self, context): - bpy.ops.wm.open_mainfile(filepath=self.filepath, load_ui = False) + bpy.ops.wm.open_mainfile(filepath=self.filepath, load_ui=False) return {'FINISHED'} @@ -39,7 +40,7 @@ def svn_file_list_context_menu(self: UIList, context: Context) -> None: active_file = context.scene.svn.get_repo(context).active_file if active_file.name.endswith("blend"): layout.operator("svn.open_blend_file", - text=f"Open {active_file.name}").filepath = active_file.absolute_path + text=f"Open {active_file.name}").filepath = active_file.absolute_path else: layout.operator("wm.path_open", text=f"Open {active_file.name}").filepath = str(Path(active_file.absolute_path)) @@ -67,8 +68,10 @@ def register(): bpy.types.UI_MT_list_item_context_menu.append(svn_file_list_context_menu) bpy.types.UI_MT_list_item_context_menu.append(svn_log_list_context_menu) + def unregister(): bpy.types.UI_MT_list_item_context_menu.remove(svn_file_list_context_menu) bpy.types.UI_MT_list_item_context_menu.remove(svn_log_list_context_menu) -registry = [SVN_OT_open_blend_file] \ No newline at end of file + +registry = [SVN_OT_open_blend_file] diff --git a/scripts-blender/addons/blender_svn/ui/ui_file_list.py b/scripts-blender/addons/blender_svn/ui/ui_file_list.py index 29a94b7a..b6c460cb 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_file_list.py +++ b/scripts-blender/addons/blender_svn/ui/ui_file_list.py @@ -122,7 +122,8 @@ class SVN_UL_file_list(UIList): element becomes hidden.""" flt_neworder = [] list_items = getattr(data, propname) - flt_flags = [file.show_in_filelist * cls.UILST_FLT_ITEM for file in list_items] + flt_flags = [file.show_in_filelist * + cls.UILST_FLT_ITEM for file in list_items] helper_funcs = bpy.types.UI_UL_list @@ -147,7 +148,8 @@ class SVN_UL_file_list(UIList): row.prop(self, 'show_file_paths', text="", toggle=True, icon="FILE_FOLDER") - row.prop(context.scene.svn.get_repo(context), 'file_search_filter', text="") + row.prop(context.scene.svn.get_repo(context), + 'file_search_filter', text="") def draw_process_info(context, layout): @@ -167,7 +169,7 @@ def draw_process_info(context, layout): warning.process_id = process.name any_error = True break - + if process.is_running: message = process.get_ui_message(context) if message: @@ -177,7 +179,8 @@ def draw_process_info(context, layout): if not any_error and process_message: col.label(text=process_message) if prefs.debug_mode: - col.label(text="Processes: " + ", ".join([p.name for p in Processes.running_processes])) + col.label(text="Processes: " + + ", ".join([p.name for p in Processes.running_processes])) def draw_repo_file_list(context, layout, repo): @@ -223,7 +226,7 @@ def draw_repo_file_list(context, layout, repo): col.separator() col.operator("svn.commit", icon='EXPORT', text="") - col.operator("svn.update_all", icon='IMPORT', text="").revision=0 + col.operator("svn.update_all", icon='IMPORT', text="").revision = 0 col.separator() col.operator("svn.cleanup", icon='BRUSH_DATA', text="") diff --git a/scripts-blender/addons/blender_svn/ui/ui_filebrowser.py b/scripts-blender/addons/blender_svn/ui/ui_filebrowser.py index 697ee9aa..7b2a5a25 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_filebrowser.py +++ b/scripts-blender/addons/blender_svn/ui/ui_filebrowser.py @@ -67,4 +67,3 @@ registry = [ FILEBROWSER_PT_SVN_files, FILEBROWSER_PT_SVN_log ] - diff --git a/scripts-blender/addons/blender_svn/ui/ui_log.py b/scripts-blender/addons/blender_svn/ui/ui_log.py index 287b9f63..c6d8d64e 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_log.py +++ b/scripts-blender/addons/blender_svn/ui/ui_log.py @@ -62,14 +62,15 @@ class SVN_UL_log(UIList): if not self.show_all_logs: flt_flags = [ - log_entry.affects_active_file * self.bitflag_filter_item + 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(",")] + 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: @@ -269,4 +270,4 @@ registry = [ SVN_UL_log, SVN_OT_log_tooltip, SVN_OT_log_show_commit_msg -] \ No newline at end of file +] diff --git a/scripts-blender/addons/blender_svn/ui/ui_outdated_warning.py b/scripts-blender/addons/blender_svn/ui/ui_outdated_warning.py index 19561537..5b4a6cce 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_outdated_warning.py +++ b/scripts-blender/addons/blender_svn/ui/ui_outdated_warning.py @@ -3,6 +3,7 @@ import bpy + def draw_outdated_file_warning(self, context): repo = context.scene.svn.get_repo(context) if not repo: @@ -31,6 +32,7 @@ def draw_outdated_file_warning(self, context): 'svn.custom_tooltip', text="SVN: This .blend file is outdated.", icon='ERROR') warning.tooltip = "The currently opened .blend file has a newer version available on the remote repository. This means any changes in this file will result in a conflict, and potential loss of data. See the SVN panel for info" + def register(): bpy.types.VIEW3D_HT_header.prepend(draw_outdated_file_warning) diff --git a/scripts-blender/addons/blender_svn/ui/ui_prefs.py b/scripts-blender/addons/blender_svn/ui/ui_prefs.py index d0ff7a52..bee70ed8 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_prefs.py +++ b/scripts-blender/addons/blender_svn/ui/ui_prefs.py @@ -12,6 +12,7 @@ from .ui_file_list import draw_repo_file_list, draw_process_info from ..threaded.background_process import Processes import platform + class SVN_UL_repositories(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): repo = item @@ -26,6 +27,7 @@ class SVN_UL_repositories(UIList): row.alert = True row.prop(repo, 'directory', text="") + class SVN_OT_repo_add(Operator, ImportHelper): """Add a repository to the list""" bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} @@ -45,7 +47,8 @@ class SVN_OT_repo_add(Operator, ImportHelper): try: repo = prefs.init_repo(context, path) except Exception as e: - self.report({'ERROR'}, "Failed to initialize repository. Ensure you have SVN installed, and that the selected directory is the root of a repository.") + self.report( + {'ERROR'}, "Failed to initialize repository. Ensure you have SVN installed, and that the selected directory is the root of a repository.") print(e) return {'CANCELLED'} if not repo: @@ -59,6 +62,7 @@ class SVN_OT_repo_add(Operator, ImportHelper): prefs.save_repo_info_to_file() return {'FINISHED'} + class SVN_OT_repo_remove(Operator): """Remove a repository from the list""" bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} @@ -81,14 +85,17 @@ class SVN_OT_repo_remove(Operator): prefs.save_repo_info_to_file() return {'FINISHED'} + class SVN_MT_add_repo(Menu): bl_idname = "SVN_MT_add_repo" bl_label = "Add Repo" def draw(self, context): layout = self.layout - layout.operator("svn.repo_add", text="Browse Existing Checkout", icon='FILE_FOLDER') - layout.operator("svn.checkout_initiate", text="Create New Checkout", icon='URL').create=True + layout.operator( + "svn.repo_add", text="Browse Existing Checkout", icon='FILE_FOLDER') + layout.operator("svn.checkout_initiate", + text="Create New Checkout", icon='URL').create = True def draw_prefs(self, context): @@ -103,7 +110,7 @@ def draw_prefs_checkout(self, context): msg_windows = "If you don't, cancel this operation and toggle it using Window->Toggle System Console." msg_linux = "If you don't, quit Blender and re-launch it from a terminal." msg_mac = msg_linux - + system = platform.system() if system == "Windows": return msg_windows @@ -120,13 +127,17 @@ def draw_prefs_checkout(self, context): col.label(text="Make sure you have Blender's terminal open!") col.label(text=get_terminal_howto()) col.separator() - col.label(text="Downloading a repository can take a long time, and the UI will be locked.") - col.label(text="Without a terminal, you won't be able to track the progress of the checkout.") + col.label( + text="Downloading a repository can take a long time, and the UI will be locked.") + col.label( + text="Without a terminal, you won't be able to track the progress of the checkout.") col.separator() col = layout.column() - col.label(text="To interrupt the checkout, you can press Ctrl+C in the terminal.", icon='INFO') - col.label(text="You can resume it by re-running this operation, or with the SVN Update button.", icon='INFO') + col.label( + text="To interrupt the checkout, you can press Ctrl+C in the terminal.", icon='INFO') + col.label( + text="You can resume it by re-running this operation, or with the SVN Update button.", icon='INFO') col.separator() prefs = get_addon_prefs(context) @@ -137,8 +148,9 @@ def draw_prefs_checkout(self, context): continue if other_repo.directory == repo.directory: row = col.row() - row.alert=True - row.label(text="A repository at this filepath is already specified.", icon='ERROR') + row.alert = True + row.label( + text="A repository at this filepath is already specified.", icon='ERROR') break col.prop(repo, 'display_name', text="Folder Name", icon='NEWFOLDER') @@ -148,9 +160,10 @@ def draw_prefs_checkout(self, context): continue if other_repo.url == repo.url: sub = col.column() - sub.alert=True + sub.alert = True sub.label(text="A repository with this URL is already specified.") - sub.label(text="If you're sure you want to checkout another copy of the repo, feel free to proceed.") + sub.label( + text="If you're sure you want to checkout another copy of the repo, feel free to proceed.") break col.prop(repo, 'username', icon='USER') col.prop(repo, 'password', icon='LOCKED') @@ -159,6 +172,7 @@ def draw_prefs_checkout(self, context): op_row.operator('svn.checkout_finalize', text="Checkout", icon='CHECKMARK') op_row.operator('svn.checkout_cancel', text="Cancel", icon="X") + def draw_prefs_repos(self, context) -> None: layout = self.layout @@ -224,10 +238,12 @@ def draw_prefs_repos(self, context) -> None: draw_repo_error(layout, "Directory is not an SVN repository.") split = layout.split(factor=0.24) split.row() - split.row().operator("svn.checkout_initiate", text="Create New Checkout", icon='URL').create=False + split.row().operator("svn.checkout_initiate", + text="Create New Checkout", icon='URL').create = False return if not self.active_repo.authenticated and not auth_in_progress and not auth_error: - draw_repo_error(layout, "Repository not authenticated. Enter your credentials.") + draw_repo_error( + layout, "Repository not authenticated. Enter your credentials.") return if len(self.repositories) > 0 and self.active_repo.authenticated: @@ -240,16 +256,18 @@ def draw_prefs_repos(self, context) -> None: layout.label(text="Log: ") draw_svn_log(context, layout, file_browser=False) + def draw_repo_error(layout, message): split = layout.split(factor=0.24) split.row() col = split.column() - col.alert=True + col.alert = True col.label(text=message, icon='ERROR') + registry = [ SVN_UL_repositories, SVN_OT_repo_add, SVN_OT_repo_remove, SVN_MT_add_repo -] \ No newline at end of file +] diff --git a/scripts-blender/addons/blender_svn/ui/ui_sidebar.py b/scripts-blender/addons/blender_svn/ui/ui_sidebar.py index f936b79b..1bc81a38 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_sidebar.py +++ b/scripts-blender/addons/blender_svn/ui/ui_sidebar.py @@ -67,4 +67,3 @@ registry = [ VIEW3D_PT_svn_credentials, VIEW3D_PT_svn_files, ] - diff --git a/scripts-blender/addons/blender_svn/util.py b/scripts-blender/addons/blender_svn/util.py index af2d756a..e04c98b8 100644 --- a/scripts-blender/addons/blender_svn/util.py +++ b/scripts-blender/addons/blender_svn/util.py @@ -8,12 +8,15 @@ import bpy package_name = __package__ + def get_addon_prefs(context): return context.preferences.addons[__package__].preferences + def dots(): return "." * int((time() % 10) + 3) + def redraw_viewport(context=None) -> None: """This causes the sidebar UI to refresh without having to mouse-hover it.""" context = bpy.context -- 2.30.2 From 10f3e1ef0b51d4acd11b1b73fba700ffebe09f45 Mon Sep 17 00:00:00 2001 From: "demeterdzadik@gmail.com" Date: Sat, 8 Jul 2023 01:23:01 +0200 Subject: [PATCH 12/12] SVN: Bump version to 1.0.0 --- scripts-blender/addons/blender_svn/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_svn/__init__.py b/scripts-blender/addons/blender_svn/__init__.py index 8c7051de..614853d2 100644 --- a/scripts-blender/addons/blender_svn/__init__.py +++ b/scripts-blender/addons/blender_svn/__init__.py @@ -18,7 +18,7 @@ bl_info = { "author": "Demeter Dzadik, Paul Golter", "description": "Blender Add-on to interact with Subversion.", "blender": (3, 1, 0), - "version": (0, 2, 1), + "version": (1, 0, 0), "location": "View3D", "warning": "", "doc_url": "", -- 2.30.2