From 268fc20bb12bc9cbb041edaebd941a4b984afce9 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:04:21 +0200 Subject: [PATCH 01/38] =?UTF-8?q?=E2=9A=A1=20git=20descriptors:=20less=20c?= =?UTF-8?q?lone=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 169 ++++++------------ .../descriptor/io_descriptor/git_branch.py | 15 -- .../tank/descriptor/io_descriptor/git_tag.py | 38 ++-- tests/descriptor_tests/test_descriptors.py | 4 +- 4 files changed, 66 insertions(+), 160 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index 2ea2014347..7570d4f19a 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -8,9 +8,7 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. import os -import uuid -import shutil -import tempfile +import shlex import subprocess from .downloadable import IODescriptorDownloadable @@ -87,6 +85,48 @@ def __init__(self, descriptor_dict, sg_connection, bundle_type): if self._path.endswith("/") or self._path.endswith("\\"): self._path = self._path[:-1] + def is_git_available(self): + log.debug("Checking that git exists and can be executed...") + try: + output = _check_output(["git", "--version"]) + except SubprocessCalledProcessError: + log.exception("Unexpected error:") + raise TankGitError( + "Cannot execute the 'git' command. Please make sure that git is " + "installed on your system and that the git executable has been added to the PATH." + ) + else: + log.debug("Git installed: %s" % output) + return True + + def _execute_git_commands(self, commands): + # first probe to check that git exists in our PATH + self.is_git_available() + + str_cmd = " ".join(commands) + + log.debug("Executing command '%s' using subprocess module." % str_cmd) + + # It's important to pass GIT_TERMINAL_PROMPT=0 or the git subprocess will + # just hang waiting for credentials to be entered on the missing terminal. + # I would have expected Windows to give an error about stdin being close and + # aborting the git command but at least on Windows 10 that is not the case. + environ = os.environ.copy() + if _can_hide_terminal(): + environ["GIT_TERMINAL_PROMPT"] = "0" + + try: + output = _check_output(commands, env=environ) + except SubprocessCalledProcessError as e: + raise TankGitError( + "Error executing git operation '%s': %s (Return code %s)" + % (str_cmd, e.output, e.returncode) + ) + else: + output = output.strip().strip("'") + log.debug("Execution successful. stderr/stdout: '%s'" % output) + return output + @LogManager.log_timing def _clone_then_execute_git_commands( self, target_path, commands, depth=None, ref=None, is_latest_commit=None @@ -127,124 +167,16 @@ def _clone_then_execute_git_commands( filesystem.ensure_folder_exists(parent_folder) - # first probe to check that git exists in our PATH - log.debug("Checking that git exists and can be executed...") - try: - output = _check_output(["git", "--version"]) - except: - log.exception("Unexpected error:") - raise TankGitError( - "Cannot execute the 'git' command. Please make sure that git is " - "installed on your system and that the git executable has been added to the PATH." - ) - - log.debug("Git installed: %s" % output) - # Make sure all git commands are correct according to the descriptor type - cmd = self._validate_git_commands( + cmd = self._get_git_clone_commands( target_path, depth=depth, ref=ref, is_latest_commit=is_latest_commit ) + self._execute_git_commands(cmd) - run_with_os_system = True - - # We used to call only os.system here. On macOS and Linux this behaved correctly, - # i.e. if stdin was open you would be prompted on the terminal and if not then an - # error would be raised. This is the best we could do. - # - # However, for Windows, os.system actually pops a terminal, which is annoying, especially - # if you don't require to authenticate. To avoid this popup, we will first launch - # git through the subprocess module and instruct it to not show a terminal for the - # subprocess. - # - # If that fails, then we'll assume that it failed because credentials were required. - # Unfortunately, we can't tell why it failed. - # - # Note: We only try this workflow if we can actually hide the terminal on Windows. - # If we can't there's no point doing all of this and we should just use - # os.system. - if is_windows() and _can_hide_terminal(): - log.debug("Executing command '%s' using subprocess module." % cmd) - try: - # It's important to pass GIT_TERMINAL_PROMPT=0 or the git subprocess will - # just hang waiting for credentials to be entered on the missing terminal. - # I would have expected Windows to give an error about stdin being close and - # aborting the git command but at least on Windows 10 that is not the case. - environ = {} - environ.update(os.environ) - environ["GIT_TERMINAL_PROMPT"] = "0" - _check_output(cmd, env=environ) - - # If that works, we're done and we don't need to use os.system. - run_with_os_system = False - status = 0 - except SubprocessCalledProcessError: - log.debug("Subprocess call failed.") - - if run_with_os_system: - # Make sure path and repo path are quoted. - log.debug("Executing command '%s' using os.system" % cmd) - log.debug( - "Note: in a terminal environment, this may prompt for authentication" - ) - status = os.system(cmd) - - log.debug("Command returned exit code %s" % status) - if status != 0: - raise TankGitError( - "Error executing git operation. The git command '%s' " - "returned error code %s." % (cmd, status) - ) - log.debug("Git clone into '%s' successful." % target_path) - - # clone worked ok! Now execute git commands on this repo - - output = None - - for command in commands: - # we use git -C to specify the working directory where to execute the command - # this option was added in as part of git 1.9 - # and solves an issue with UNC paths on windows. - full_command = 'git -C "%s" %s' % (target_path, command) - log.debug("Executing '%s'" % full_command) - - try: - output = _check_output(full_command, shell=True) + full_commands = ["git", "-C", shlex.quote(target_path)] + full_commands.extend(commands) - # note: it seems on windows, the result is sometimes wrapped in single quotes. - output = output.strip().strip("'") - - except SubprocessCalledProcessError as e: - raise TankGitError( - f"Error executing GIT operation '{full_command}': {e.output}" - f" (Return code {e.returncode}). " - " Supported GIT version: 1.9+." - ) - log.debug("Execution successful. stderr/stdout: '%s'" % output) - - # return the last returned stdout/stderr - return output - - def _tmp_clone_then_execute_git_commands(self, commands, depth=None, ref=None): - """ - Clone into a temp location and executes the given - list of git commands. - - For more details, see :meth:`_clone_then_execute_git_commands`. - - :param commands: list git commands to execute, e.g. ['checkout x'] - :returns: stdout and stderr of the last command executed as a string - """ - clone_tmp = os.path.join( - tempfile.gettempdir(), "sgtk_clone_%s" % uuid.uuid4().hex - ) - filesystem.ensure_folder_exists(clone_tmp) - try: - return self._clone_then_execute_git_commands( - clone_tmp, commands, depth, ref - ) - finally: - log.debug("Cleaning up temp location '%s'" % clone_tmp) - shutil.rmtree(clone_tmp, ignore_errors=True) + return self._execute_git_commands(full_commands) def get_system_name(self): """ @@ -268,8 +200,7 @@ def has_remote_access(self): can_connect = True try: log.debug("%r: Probing if a connection to git can be established..." % self) - # clone repo into temp folder - self._tmp_clone_then_execute_git_commands([], depth=1) + self._execute_git_commands(["git", "ls-remote", shlex.quote(self._path)]) log.debug("...connection established") except Exception as e: log.debug("...could not establish connection: %s" % e) @@ -303,7 +234,7 @@ def _copy(self, target_path, skip_list=None): skip_list=skip_list or [], ) - def _validate_git_commands( + def _get_git_clone_commands( self, target_path, depth=None, ref=None, is_latest_commit=None ): """ diff --git a/python/tank/descriptor/io_descriptor/git_branch.py b/python/tank/descriptor/io_descriptor/git_branch.py index 745060213a..a35cbc6430 100644 --- a/python/tank/descriptor/io_descriptor/git_branch.py +++ b/python/tank/descriptor/io_descriptor/git_branch.py @@ -209,21 +209,6 @@ def get_latest_version(self, constraint_pattern=None): "Latest version will be used." % self ) - try: - # clone the repo, get the latest commit hash - # for the given branch - commands = [ - 'checkout -q "%s"' % self._branch, - "log -n 1 \"%s\" --pretty=format:'%%H'" % self._branch, - ] - git_hash = self._tmp_clone_then_execute_git_commands(commands) - - except Exception as e: - raise TankDescriptorError( - "Could not get latest commit for %s, " - "branch %s: %s" % (self._path, self._branch, e) - ) - # make a new descriptor new_loc_dict = copy.deepcopy(self._descriptor_dict) new_loc_dict["version"] = sgutils.ensure_str(git_hash) diff --git a/python/tank/descriptor/io_descriptor/git_tag.py b/python/tank/descriptor/io_descriptor/git_tag.py index 77dad56c8b..2bb07a224f 100644 --- a/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tank/descriptor/io_descriptor/git_tag.py @@ -211,29 +211,15 @@ def _get_latest_by_pattern(self, pattern): return latest_tag def _fetch_tags(self): - try: - # clone the repo, list all tags - # for the repository, across all branches - commands = ["ls-remote -q --tags %s" % self._path] - tags = self._tmp_clone_then_execute_git_commands(commands, depth=1).split( - "\n" - ) - regex = re.compile(".*refs/tags/([^^]*)$") - git_tags = [] - for tag in tags: - m = regex.match(sgutils.ensure_str(tag)) - if m: - git_tags.append(m.group(1)) + output = self._execute_git_commands(["git", "ls-remote", "--tags", self._path]) - except Exception as e: - raise TankDescriptorError( - "Could not get list of tags for %s: %s" % (self._path, e) - ) + regex = re.compile(".*refs/tags/([^^/]+)$") - if len(git_tags) == 0: - raise TankDescriptorError( - "Git repository %s doesn't have any tags!" % self._path - ) + git_tags = [] + for line in output.splitlines(): + m = regex.match(six.ensure_str(line)) + if m: + git_tags.append(m.group(1)) return git_tags @@ -243,13 +229,17 @@ def _get_latest_version(self): :returns: IODescriptorGitTag object """ tags = self._fetch_tags() - latest_tag = self._find_latest_tag_by_pattern(tags, pattern=None) - if latest_tag is None: + if not tags: raise TankDescriptorError( "Git repository %s doesn't have any tags!" % self._path ) - return latest_tag + tupled_tags = [] + for t in tags: + items = t.lstrip("v").split(".") + tupled_tags.append(tuple(int(item) if item.isdigit() else item for item in items)) + + return sorted(tupled_tags)[-1] def get_latest_cached_version(self, constraint_pattern=None): """ diff --git a/tests/descriptor_tests/test_descriptors.py b/tests/descriptor_tests/test_descriptors.py index 1538d9a1c6..b6ea4f8e7c 100644 --- a/tests/descriptor_tests/test_descriptors.py +++ b/tests/descriptor_tests/test_descriptors.py @@ -687,14 +687,14 @@ def test_git_branch_descriptor_commands(self): } ) self.assertEqual( - desc._io_descriptor._validate_git_commands( + desc._io_descriptor._get_git_clone_commands( target_path, depth=1, ref="master" ), 'git clone --no-hardlinks -q "%s" %s "%s" ' % (self.git_repo_uri, "-b master", target_path,), ) self.assertEqual( - desc._io_descriptor._validate_git_commands(target_path, ref="master"), + desc._io_descriptor._get_git_clone_commands(target_path, ref="master"), 'git clone --no-hardlinks -q "%s" %s "%s" ' % (self.git_repo_uri, "-b master", target_path,), ) From 04d181bc5361716deac0241a0ab769039ddc9650 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:05:38 +0200 Subject: [PATCH 02/38] =?UTF-8?q?=F0=9F=A9=B9=20git=20descriptors:=20=20mo?= =?UTF-8?q?re=20accurate=20exceptions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 5 +++-- python/tank/descriptor/io_descriptor/git_branch.py | 6 ++++-- python/tank/descriptor/io_descriptor/git_tag.py | 6 ++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index 7570d4f19a..2335320fac 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -32,7 +32,8 @@ def _can_hide_terminal(): subprocess.STARTF_USESHOWWINDOW subprocess.SW_HIDE return True - except Exception: + except AttributeError as e: + log.debug("Terminal cant be hidden: %s" % e) return False @@ -202,7 +203,7 @@ def has_remote_access(self): log.debug("%r: Probing if a connection to git can be established..." % self) self._execute_git_commands(["git", "ls-remote", shlex.quote(self._path)]) log.debug("...connection established") - except Exception as e: + except (OSError, SubprocessCalledProcessError) as e: log.debug("...could not establish connection: %s" % e) can_connect = False return can_connect diff --git a/python/tank/descriptor/io_descriptor/git_branch.py b/python/tank/descriptor/io_descriptor/git_branch.py index a35cbc6430..149b30fd06 100644 --- a/python/tank/descriptor/io_descriptor/git_branch.py +++ b/python/tank/descriptor/io_descriptor/git_branch.py @@ -10,7 +10,7 @@ import os import copy -from .git import IODescriptorGit, TankGitError, _check_output +from .git import IODescriptorGit, TankGitError from ..errors import TankDescriptorError from ... import LogManager @@ -19,6 +19,8 @@ except ImportError: from tank_vendor import six as sgutils +from ...util.process import SubprocessCalledProcessError + log = LogManager.get_logger(__name__) @@ -171,7 +173,7 @@ def _download_local(self, destination_path): ref=self._branch, is_latest_commit=is_latest_commit, ) - except Exception as e: + except (TankGitError, OSError, SubprocessCalledProcessError) as e: raise TankDescriptorError( "Could not download %s, branch %s, " "commit %s: %s" % (self._path, self._branch, self._version, e) diff --git a/python/tank/descriptor/io_descriptor/git_tag.py b/python/tank/descriptor/io_descriptor/git_tag.py index 2bb07a224f..008f3e5596 100644 --- a/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tank/descriptor/io_descriptor/git_tag.py @@ -11,7 +11,7 @@ import copy import re -from .git import IODescriptorGit +from .git import IODescriptorGit, TankGitError from ..errors import TankDescriptorError from ... import LogManager @@ -20,6 +20,8 @@ except ImportError: from tank_vendor import six as sgutils +from ...util.process import SubprocessCalledProcessError + log = LogManager.get_logger(__name__) @@ -148,7 +150,7 @@ def _download_local(self, destination_path): self._clone_then_execute_git_commands( destination_path, [], depth=1, ref=self._version ) - except Exception as e: + except (TankGitError, OSError, SubprocessCalledProcessError) as e: raise TankDescriptorError( "Could not download %s, " "tag %s: %s" % (self._path, self._version, e) ) From 25b36299724f1fe62c369b9a6a984f002299999d Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:06:42 +0200 Subject: [PATCH 03/38] =?UTF-8?q?=F0=9F=A9=B9=20git=20descriptors:=20=5Fge?= =?UTF-8?q?t=5Flatest=5Fby=5Fpattern=20already=20includes=20get=5Flatest?= =?UTF-8?q?=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git_tag.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git_tag.py b/python/tank/descriptor/io_descriptor/git_tag.py index 008f3e5596..6c0fa1a1e7 100644 --- a/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tank/descriptor/io_descriptor/git_tag.py @@ -176,10 +176,7 @@ def get_latest_version(self, constraint_pattern=None): :returns: IODescriptorGitTag object """ - if constraint_pattern: - tag_name = self._get_latest_by_pattern(constraint_pattern) - else: - tag_name = self._get_latest_version() + tag_name = self._get_latest_by_pattern(constraint_pattern) new_loc_dict = copy.deepcopy(self._descriptor_dict) new_loc_dict["version"] = sgutils.ensure_str(tag_name) From 4ca95d43939066948440d9b781eb304c3a7d31dd Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:09:27 +0200 Subject: [PATCH 04/38] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20git=20descriptors:?= =?UTF-8?q?=20refactor=20get=20short/long=20commit=20hash=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../descriptor/io_descriptor/git_branch.py | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git_branch.py b/python/tank/descriptor/io_descriptor/git_branch.py index 149b30fd06..251186976d 100644 --- a/python/tank/descriptor/io_descriptor/git_branch.py +++ b/python/tank/descriptor/io_descriptor/git_branch.py @@ -115,25 +115,31 @@ def get_version(self): """ return self._version - def _is_latest_commit(self, version, branch): + def get_latest_commit(self): + output = self._execute_git_commands(["git", "ls-remote", self._path, self._branch]) + latest_commit = output.split("\t")[0] + + if not latest_commit: + raise TankDescriptorError( + "Could not get latest commit for %s, branch %s" + % (self._path, self._branch) + ) + + return sgutils.ensure_str(latest_commit) + + def get_latest_short_commit(self): + return self.get_latest_commit()[:7] + + def _is_latest_commit(self): """ Check if the git_branch descriptor is pointing to the latest commit version. """ # first probe to check that git exists in our PATH log.debug("Checking if the version is pointing to the latest commit...") - try: - output = _check_output(["git", "ls-remote", self._path, branch]) - except: - log.exception("Unexpected error:") - raise TankGitError( - "Cannot execute the 'git' command. Please make sure that git is " - "installed on your system and that the git executable has been added to the PATH." - ) - latest_commit = output.split("\t") - short_latest_commit = latest_commit[0][:7] + short_latest_commit = self.get_latest_short_commit() - if short_latest_commit != version[:7]: + if short_latest_commit != self._version[:7]: return False log.debug( "This version is pointing to the latest commit %s, lets enable shallow clones" @@ -159,7 +165,7 @@ def _download_local(self, destination_path): the git branch descriptor is to be downloaded to. """ depth = None - is_latest_commit = self._is_latest_commit(self._version, self._branch) + is_latest_commit = self._is_latest_commit() if is_latest_commit: depth = 1 try: @@ -213,7 +219,7 @@ def get_latest_version(self, constraint_pattern=None): # make a new descriptor new_loc_dict = copy.deepcopy(self._descriptor_dict) - new_loc_dict["version"] = sgutils.ensure_str(git_hash) + new_loc_dict["version"] = self.get_latest_commit() desc = IODescriptorGitBranch( new_loc_dict, self._sg_connection, self._bundle_type ) From 839a26c389a932cc17717286e328f3cd527d8add Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:19:36 +0200 Subject: [PATCH 05/38] =?UTF-8?q?=F0=9F=A9=B9=20git=20descriptors:=20more?= =?UTF-8?q?=20reliable=20check=20availability=20with=20where/which=20comma?= =?UTF-8?q?nd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index 2335320fac..a3a3bbb9ef 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -88,10 +88,15 @@ def __init__(self, descriptor_dict, sg_connection, bundle_type): def is_git_available(self): log.debug("Checking that git exists and can be executed...") + + if IS_WINDOWS: + cmd = "where" + else: + cmd = "which" + try: - output = _check_output(["git", "--version"]) + output = _check_output([cmd, "git"]) except SubprocessCalledProcessError: - log.exception("Unexpected error:") raise TankGitError( "Cannot execute the 'git' command. Please make sure that git is " "installed on your system and that the git executable has been added to the PATH." From 4016b522cd9a70f278c4de59537846ce3d39d50c Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:19:58 +0200 Subject: [PATCH 06/38] =?UTF-8?q?=F0=9F=90=9B=20git=20descriptors:=20comma?= =?UTF-8?q?nds=20might=20be=20a=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index a3a3bbb9ef..8759f14423 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -109,7 +109,10 @@ def _execute_git_commands(self, commands): # first probe to check that git exists in our PATH self.is_git_available() - str_cmd = " ".join(commands) + if not isinstance(commands, str): + str_cmd = " ".join(commands) + else: + str_cmd = commands log.debug("Executing command '%s' using subprocess module." % str_cmd) From 3631eb702e5bf79a40f43497fdab4b1e5e80454a Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:20:24 +0200 Subject: [PATCH 07/38] =?UTF-8?q?=F0=9F=A9=B9=20git=20descriptors:=20unnee?= =?UTF-8?q?ded=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index 8759f14423..24c369f079 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -121,8 +121,7 @@ def _execute_git_commands(self, commands): # I would have expected Windows to give an error about stdin being close and # aborting the git command but at least on Windows 10 that is not the case. environ = os.environ.copy() - if _can_hide_terminal(): - environ["GIT_TERMINAL_PROMPT"] = "0" + environ["GIT_TERMINAL_PROMPT"] = "0" try: output = _check_output(commands, env=environ) From 88bab54b6c17760ff599558203d685adc363c193 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:21:03 +0200 Subject: [PATCH 08/38] =?UTF-8?q?=F0=9F=A9=B9=20git=20descriptors:=20repla?= =?UTF-8?q?ce=20tk.filesystem=20methods=20with=20builtins=20os/shutil=20me?= =?UTF-8?q?thods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index 24c369f079..df8b7d113f 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -9,6 +9,7 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import os import shlex +import shutil import subprocess from .downloadable import IODescriptorDownloadable @@ -16,7 +17,6 @@ from ...util.process import subprocess_check_output, SubprocessCalledProcessError from ..errors import TankError -from ...util import filesystem from ...util import is_windows log = LogManager.get_logger(__name__) @@ -172,8 +172,7 @@ def _clone_then_execute_git_commands( """ # ensure *parent* folder exists parent_folder = os.path.dirname(target_path) - - filesystem.ensure_folder_exists(parent_folder) + os.makedirs(parent_folder, exist_ok=True) # Make sure all git commands are correct according to the descriptor type cmd = self._get_git_clone_commands( @@ -232,15 +231,8 @@ def _copy(self, target_path, skip_list=None): # make sure item exists locally self.ensure_local() # copy descriptor into target. - # the skip list contains .git folders by default, so pass in [] - # to turn that restriction off. In the case of the git descriptor, - # we want to transfer this folder as well. - filesystem.copy_folder( - self.get_path(), - target_path, - # Make we do not pass none or we will be getting the default skip list. - skip_list=skip_list or [], - ) + shutil.copytree(self.get_path(), target_path) + def _get_git_clone_commands( self, target_path, depth=None, ref=None, is_latest_commit=None From 0cf0e9bc221c4d7ad244b08659cf87364c9dafa5 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:21:34 +0200 Subject: [PATCH 09/38] =?UTF-8?q?=F0=9F=90=9B=20git=20descriptors:=20missi?= =?UTF-8?q?ng=20constant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index df8b7d113f..f4b0c5d116 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -8,7 +8,7 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. import os -import shlex +import platform import shutil import subprocess @@ -20,6 +20,7 @@ from ...util import is_windows log = LogManager.get_logger(__name__) +IS_WINDOWS = platform.system() == "Windows" def _can_hide_terminal(): From e8da551c31f98a75ed19dbaa232de76d5182d6bd Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:22:11 +0200 Subject: [PATCH 10/38] =?UTF-8?q?=F0=9F=A9=B9=20git=20descriptors:=20clone?= =?UTF-8?q?=5Fthen=5Fexecute=20might=20have=20a=20blank=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index f4b0c5d116..d0f1616494 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -181,10 +181,11 @@ def _clone_then_execute_git_commands( ) self._execute_git_commands(cmd) - full_commands = ["git", "-C", shlex.quote(target_path)] - full_commands.extend(commands) + if commands: + full_commands = ["git", "-C", os.path.normpath(target_path)] + full_commands.extend(commands) - return self._execute_git_commands(full_commands) + return self._execute_git_commands(full_commands) def get_system_name(self): """ From 8018fd37c0c45bd268fefa4a6c3ca36f1e89cfd0 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:22:44 +0200 Subject: [PATCH 11/38] =?UTF-8?q?=F0=9F=A9=B9=20git=20descriptors:=20remot?= =?UTF-8?q?e=20access=20checks=20only=20heads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index d0f1616494..ed0855b7bc 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -209,7 +209,7 @@ def has_remote_access(self): can_connect = True try: log.debug("%r: Probing if a connection to git can be established..." % self) - self._execute_git_commands(["git", "ls-remote", shlex.quote(self._path)]) + self._execute_git_commands(["git", "ls-remote", "--heads", self._path]) log.debug("...connection established") except (OSError, SubprocessCalledProcessError) as e: log.debug("...could not establish connection: %s" % e) From ac2a70091c92595c01f2509902bc0f046e8244ef Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:23:17 +0200 Subject: [PATCH 12/38] =?UTF-8?q?=F0=9F=A9=B9=20git=20descriptors:=20less?= =?UTF-8?q?=20complex=20git=20cmd=20maker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 26 +++++++-------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index ed0855b7bc..368534bb1d 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -256,23 +256,13 @@ def _get_git_clone_commands( # complications in cleanup scenarios and with file copying. We want # each repo that we clone to be completely independent on a filesystem level. log.debug("Git Cloning %r into %s" % (self, target_path)) - depth = "--depth %s" % depth if depth else "" - ref = "-b %s" % ref if ref else "" - cmd = 'git clone --no-hardlinks -q "%s" %s "%s" %s' % ( - self._path, - ref, - target_path, - depth, - ) - if self._descriptor_dict.get("type") == "git_branch": - if not is_latest_commit: - if "--depth" in cmd: - depth = "" - cmd = 'git clone --no-hardlinks -q "%s" %s "%s" %s' % ( - self._path, - ref, - target_path, - depth, - ) + + if self._descriptor_dict.get("type") == "git_branch" and not is_latest_commit: + depth = "" + else: + depth = f"--depth {depth}" if depth else "" + + ref = f"-b {ref}" if ref else "" + cmd = f'git clone --no-hardlinks -q "{self._path}" {ref} "{target_path}" {depth}' return cmd From efd03c1c1983e1d5f1710acc561bdcc4134c810f Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:24:54 +0200 Subject: [PATCH 13/38] =?UTF-8?q?=E2=9A=A1=20git=20descriptors:=20add=20ca?= =?UTF-8?q?ching?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 28 ++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index 368534bb1d..4803ea5fb2 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -12,6 +12,8 @@ import shutil import subprocess +from time import time + from .downloadable import IODescriptorDownloadable from ... import LogManager from ...util.process import subprocess_check_output, SubprocessCalledProcessError @@ -59,7 +61,31 @@ class TankGitError(TankError): pass -class IODescriptorGit(IODescriptorDownloadable): +class _IODescriptorGitCache(type): + """Use as metaclass. Caches object instances for 2min.""" + _instances = {} + + def __call__(cls, descriptor_dict, sg_connection, bundle_type): + now = int(time() / 100) + floored_time = now - now % 2 # Cache is valid for 2min + + if descriptor_dict["type"] == "git_branch": # cant fetch last commit here, too soon + version = descriptor_dict.get("version") or descriptor_dict["branch"] + else: + version = descriptor_dict['version'] + + id_ = f"{descriptor_dict['type']}-{descriptor_dict['path']}-{version}" + + cached_time, self = cls._instances.get(id_, (-1, None)) + if cached_time < floored_time: + log.debug(f"{cached_time}, {floored_time}, {id_}") + self = super().__call__(descriptor_dict, sg_connection, bundle_type) + cls._instances[id_] = (floored_time, self) + + return self + + +class IODescriptorGit(IODescriptorDownloadable, metaclass=_IODescriptorGitCache): """ Base class for git descriptors. From b0c450949430b5df952f5e99e2499931182a7ee9 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:28:51 +0200 Subject: [PATCH 14/38] =?UTF-8?q?=F0=9F=A9=B9=20git=20tag=20descriptor:=20?= =?UTF-8?q?move=20regex=20outside?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git_tag.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git_tag.py b/python/tank/descriptor/io_descriptor/git_tag.py index 6c0fa1a1e7..583465eef6 100644 --- a/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tank/descriptor/io_descriptor/git_tag.py @@ -24,6 +24,8 @@ log = LogManager.get_logger(__name__) +TAG_REGEX = re.compile(".*refs/tags/([^^/]+)$") + class IODescriptorGitTag(IODescriptorGit): """ @@ -212,8 +214,6 @@ def _get_latest_by_pattern(self, pattern): def _fetch_tags(self): output = self._execute_git_commands(["git", "ls-remote", "--tags", self._path]) - regex = re.compile(".*refs/tags/([^^/]+)$") - git_tags = [] for line in output.splitlines(): m = regex.match(six.ensure_str(line)) From 7fc47facb39174ad7d3824a109ef118fb894b3b9 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:33:28 +0200 Subject: [PATCH 15/38] =?UTF-8?q?=F0=9F=90=9B=20git=20tag=20descriptor:=20?= =?UTF-8?q?resolve=20tag=20version=20at=20class=20init?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tank/descriptor/io_descriptor/git_tag.py | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git_tag.py b/python/tank/descriptor/io_descriptor/git_tag.py index 583465eef6..c0dda46940 100644 --- a/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tank/descriptor/io_descriptor/git_tag.py @@ -66,10 +66,22 @@ def __init__(self, descriptor_dict, sg_connection, bundle_type): # path is handled by base class - all git descriptors # have a path to a repo - self._version = descriptor_dict.get("version") self._sg_connection = sg_connection self._bundle_type = bundle_type + raw_version = descriptor_dict.get("version") + raw_version_is_latest = raw_version == "latest" + + if "x" in raw_version or raw_version_is_latest: + self._tags = self._fetch_tags() + if raw_version_is_latest: + self._version = self._get_latest_by_pattern(None) + else: + self._version = self._get_latest_by_pattern(raw_version) + log.info(f"{self.get_system_name()}-{raw_version} resolved as {self._version}") + else: + self._version = raw_version + def __str__(self): """ Human readable representation @@ -126,9 +138,7 @@ def _get_cache_paths(self): return paths def get_version(self): - """ - Returns the version number string for this item, .e.g 'v1.2.3' - """ + """Returns the tag name.""" return self._version def _download_local(self, destination_path): @@ -200,41 +210,39 @@ def _get_latest_by_pattern(self, pattern): - v1.2.3.x (will always return a forked version, eg. v1.2.3.2) :returns: IODescriptorGitTag object """ - git_tags = self._fetch_tags() - latest_tag = self._find_latest_tag_by_pattern(git_tags, pattern) - if latest_tag is None: - raise TankDescriptorError( - "'%s' does not have a version matching the pattern '%s'. " - "Available versions are: %s" - % (self.get_system_name(), pattern, ", ".join(git_tags)) - ) + if not pattern: + latest_tag = self._get_latest_tag() + else: + latest_tag = self._find_latest_tag_by_pattern(self._tags, pattern) + if latest_tag is None: + raise TankDescriptorError( + "'%s' does not have a version matching the pattern '%s'. " + "Available versions are: %s" + % (self.get_system_name(), pattern, ", ".join(self._tags)) + ) return latest_tag def _fetch_tags(self): - output = self._execute_git_commands(["git", "ls-remote", "--tags", self._path]) + output = self._execute_git_commands(["git", "ls-remote", "-q", "--tags", self._path]) git_tags = [] for line in output.splitlines(): - m = regex.match(six.ensure_str(line)) + m = TAG_REGEX.match(sgutils.ensure_str(line)) if m: git_tags.append(m.group(1)) return git_tags - def _get_latest_version(self): - """ - Returns a descriptor object that represents the latest version. - :returns: IODescriptorGitTag object - """ - tags = self._fetch_tags() - if not tags: + def _get_latest_tag(self): + """Get latest tag name. Compare them as version numbers.""" + if not self._tags: raise TankDescriptorError( "Git repository %s doesn't have any tags!" % self._path ) tupled_tags = [] - for t in tags: + for t in self._tags: items = t.lstrip("v").split(".") tupled_tags.append(tuple(int(item) if item.isdigit() else item for item in items)) From 016507455b33f8115f1282b200de855f71322c73 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:34:27 +0200 Subject: [PATCH 16/38] =?UTF-8?q?=F0=9F=A9=B9=20git=20tag=20descriptor:=20?= =?UTF-8?q?force=20download=20destination?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tank/descriptor/io_descriptor/git_tag.py | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git_tag.py b/python/tank/descriptor/io_descriptor/git_tag.py index c0dda46940..4f40320ba2 100644 --- a/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tank/descriptor/io_descriptor/git_tag.py @@ -141,26 +141,17 @@ def get_version(self): """Returns the tag name.""" return self._version - def _download_local(self, destination_path): + def download_local(self): """ - Retrieves this version to local repo. - Will exit early if app already exists local. - - This will connect to remote git repositories. - Depending on how git is configured, https repositories - requiring credentials may result in a shell opening up - requesting username and password. - - The git repo will be cloned into the local cache and - will then be adjusted to point at the relevant tag. - - :param destination_path: The destination path on disk to which - the git tag descriptor is to be downloaded to. + Downloads the data represented by the descriptor into the primary bundle + cache path. """ + log.info(f"Downloading {self.get_system_name()}:{self._version}") + try: # clone the repo, checkout the given tag self._clone_then_execute_git_commands( - destination_path, [], depth=1, ref=self._version + self._get_primary_cache_path(), [], depth=1, ref=self._version ) except (TankGitError, OSError, SubprocessCalledProcessError) as e: raise TankDescriptorError( From 8b39f1a216a60378afa08ae8f172ff9a41a1224a Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:38:26 +0200 Subject: [PATCH 17/38] =?UTF-8?q?=E2=9C=A8=20git=20branch=20descriptor:=20?= =?UTF-8?q?version=20is=20optionnal,=20fetch=20latest=20commit=20if=20not?= =?UTF-8?q?=20set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git_branch.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git_branch.py b/python/tank/descriptor/io_descriptor/git_branch.py index 251186976d..b1b62663cf 100644 --- a/python/tank/descriptor/io_descriptor/git_branch.py +++ b/python/tank/descriptor/io_descriptor/git_branch.py @@ -66,7 +66,7 @@ def __init__(self, descriptor_dict, sg_connection, bundle_type): """ # make sure all required fields are there self._validate_descriptor( - descriptor_dict, required=["type", "path", "version", "branch"], optional=[] + descriptor_dict, required=["type", "path", "branch"], optional=["version"] ) # call base class @@ -78,8 +78,8 @@ def __init__(self, descriptor_dict, sg_connection, bundle_type): # have a path to a repo self._sg_connection = sg_connection self._bundle_type = bundle_type - self._version = descriptor_dict.get("version") self._branch = sgutils.ensure_str(descriptor_dict.get("branch")) + self._version = descriptor_dict.get("version") or self.get_latest_commit() def __str__(self): """ @@ -116,6 +116,7 @@ def get_version(self): return self._version def get_latest_commit(self): + """Fetch the latest commit on a specific branch""" output = self._execute_git_commands(["git", "ls-remote", self._path, self._branch]) latest_commit = output.split("\t")[0] @@ -139,7 +140,7 @@ def _is_latest_commit(self): log.debug("Checking if the version is pointing to the latest commit...") short_latest_commit = self.get_latest_short_commit() - if short_latest_commit != self._version[:7]: + if short_latest_commit != self.get_short_version(): return False log.debug( "This version is pointing to the latest commit %s, lets enable shallow clones" From d8d05c2e36bfd55564f6c8c4a657efea93438e8e Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:39:27 +0200 Subject: [PATCH 18/38] =?UTF-8?q?=F0=9F=A9=B9=20git=20branch=20descriptor:?= =?UTF-8?q?=20add=20get=5Fshort=5Fversion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tank/descriptor/io_descriptor/git_branch.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git_branch.py b/python/tank/descriptor/io_descriptor/git_branch.py index b1b62663cf..36e5c96e3c 100644 --- a/python/tank/descriptor/io_descriptor/git_branch.py +++ b/python/tank/descriptor/io_descriptor/git_branch.py @@ -96,25 +96,20 @@ def _get_bundle_cache_path(self, bundle_cache_root): :param bundle_cache_root: Bundle cache root path :return: Path to bundle cache location """ - # If the descriptor is an integer change the version to a string type - if isinstance(self._version, int): - self._version = str(self._version) - # to be MAXPATH-friendly, we only use the first seven chars - short_hash = self._version[:7] - # git@github.com:manneohrstrom/tk-hiero-publish.git -> tk-hiero-publish.git # /full/path/to/local/repo.git -> repo.git name = os.path.basename(self._path) - return os.path.join(bundle_cache_root, "gitbranch", name, short_hash) + return os.path.join(bundle_cache_root, "gitbranch", name, self.get_short_version()) def get_version(self): - """ - Returns the version number string for this item, .e.g 'v1.2.3' - or the branch name 'master' - """ + """Returns the full commit sha.""" return self._version + def get_short_version(self): + """Returns the short commit sha.""" + return self._version[:7] + def get_latest_commit(self): """Fetch the latest commit on a specific branch""" output = self._execute_git_commands(["git", "ls-remote", self._path, self._branch]) From e33cb1fdd0075d013fc6df959dea618bb4841d08 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:40:05 +0200 Subject: [PATCH 19/38] =?UTF-8?q?=F0=9F=A9=B9=20git=20branch=20descriptor:?= =?UTF-8?q?=20force=20download=20destination?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../descriptor/io_descriptor/git_branch.py | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git_branch.py b/python/tank/descriptor/io_descriptor/git_branch.py index 36e5c96e3c..bc667a3914 100644 --- a/python/tank/descriptor/io_descriptor/git_branch.py +++ b/python/tank/descriptor/io_descriptor/git_branch.py @@ -144,22 +144,13 @@ def _is_latest_commit(self): return True - def _download_local(self, destination_path): + def download_local(self): """ - Retrieves this version to local repo. - Will exit early if app already exists local. - - This will connect to remote git repositories. - Depending on how git is configured, https repositories - requiring credentials may result in a shell opening up - requesting username and password. - - The git repo will be cloned into the local cache and - will then be adjusted to point at the relevant commit. - - :param destination_path: The destination path on disk to which - the git branch descriptor is to be downloaded to. + Downloads the data represented by the descriptor into the primary bundle + cache path. """ + log.info(f"Downloading {self.get_system_name()}:{self._version}") + depth = None is_latest_commit = self._is_latest_commit() if is_latest_commit: @@ -167,9 +158,9 @@ def _download_local(self, destination_path): try: # clone the repo, switch to the given branch # then reset to the given commit - commands = ['checkout -q "%s"' % self._version] + commands = [f'checkout', '-q', self._version] self._clone_then_execute_git_commands( - destination_path, + self._get_primary_cache_path(), commands, depth=depth, ref=self._branch, From 1174f3367176de9a7418c315b20ad6d2ff78ef9b Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Wed, 23 Aug 2023 23:50:28 +0200 Subject: [PATCH 20/38] =?UTF-8?q?=E2=AC=87=EF=B8=8F=20py2.7=20does=20not?= =?UTF-8?q?=20support=20fstrings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 10 +++++----- python/tank/descriptor/io_descriptor/git_branch.py | 2 +- python/tank/descriptor/io_descriptor/git_tag.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index 4803ea5fb2..10c0e21d6e 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -74,11 +74,11 @@ def __call__(cls, descriptor_dict, sg_connection, bundle_type): else: version = descriptor_dict['version'] - id_ = f"{descriptor_dict['type']}-{descriptor_dict['path']}-{version}" + id_ = "{}-{}-{}".format(descriptor_dict['type'], descriptor_dict['path'], version) cached_time, self = cls._instances.get(id_, (-1, None)) if cached_time < floored_time: - log.debug(f"{cached_time}, {floored_time}, {id_}") + log.debug("{} {} cache expired: cachedTime:{}".format(self, id_, cached_time)) self = super().__call__(descriptor_dict, sg_connection, bundle_type) cls._instances[id_] = (floored_time, self) @@ -286,9 +286,9 @@ def _get_git_clone_commands( if self._descriptor_dict.get("type") == "git_branch" and not is_latest_commit: depth = "" else: - depth = f"--depth {depth}" if depth else "" + depth = "--depth {}".format(depth) if depth else "" - ref = f"-b {ref}" if ref else "" - cmd = f'git clone --no-hardlinks -q "{self._path}" {ref} "{target_path}" {depth}' + ref = "-b {}".format(ref) if ref else "" + cmd = 'git clone --no-hardlinks -q "{}" {} "{}" {}'.format(self._path, ref, target_path, depth) return cmd diff --git a/python/tank/descriptor/io_descriptor/git_branch.py b/python/tank/descriptor/io_descriptor/git_branch.py index bc667a3914..2f17c48d47 100644 --- a/python/tank/descriptor/io_descriptor/git_branch.py +++ b/python/tank/descriptor/io_descriptor/git_branch.py @@ -149,7 +149,7 @@ def download_local(self): Downloads the data represented by the descriptor into the primary bundle cache path. """ - log.info(f"Downloading {self.get_system_name()}:{self._version}") + log.info("Downloading {}:{}".format(self.get_system_name(), self._version)) depth = None is_latest_commit = self._is_latest_commit() diff --git a/python/tank/descriptor/io_descriptor/git_tag.py b/python/tank/descriptor/io_descriptor/git_tag.py index 4f40320ba2..d99b97a6bb 100644 --- a/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tank/descriptor/io_descriptor/git_tag.py @@ -78,7 +78,7 @@ def __init__(self, descriptor_dict, sg_connection, bundle_type): self._version = self._get_latest_by_pattern(None) else: self._version = self._get_latest_by_pattern(raw_version) - log.info(f"{self.get_system_name()}-{raw_version} resolved as {self._version}") + log.info("{}-{} resolved as {}".format(self.get_system_name(), raw_version, self._version)) else: self._version = raw_version @@ -146,7 +146,7 @@ def download_local(self): Downloads the data represented by the descriptor into the primary bundle cache path. """ - log.info(f"Downloading {self.get_system_name()}:{self._version}") + log.info("Downloading {}:{}".format(self.get_system_name(), self._version)) try: # clone the repo, checkout the given tag From f6afbc83778ff51b6f2ee355df83792dd7c4ca0d Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Thu, 24 Aug 2023 00:15:50 +0200 Subject: [PATCH 21/38] =?UTF-8?q?=F0=9F=8E=A8=20black?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 20 +++++++++++++------ .../descriptor/io_descriptor/git_branch.py | 10 +++++++--- .../tank/descriptor/io_descriptor/git_tag.py | 14 ++++++++++--- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index 10c0e21d6e..1dc128aea8 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -63,22 +63,29 @@ class TankGitError(TankError): class _IODescriptorGitCache(type): """Use as metaclass. Caches object instances for 2min.""" + _instances = {} def __call__(cls, descriptor_dict, sg_connection, bundle_type): now = int(time() / 100) floored_time = now - now % 2 # Cache is valid for 2min - if descriptor_dict["type"] == "git_branch": # cant fetch last commit here, too soon + if ( + descriptor_dict["type"] == "git_branch" + ): # cant fetch last commit here, too soon version = descriptor_dict.get("version") or descriptor_dict["branch"] else: - version = descriptor_dict['version'] + version = descriptor_dict["version"] - id_ = "{}-{}-{}".format(descriptor_dict['type'], descriptor_dict['path'], version) + id_ = "{}-{}-{}".format( + descriptor_dict["type"], descriptor_dict["path"], version + ) cached_time, self = cls._instances.get(id_, (-1, None)) if cached_time < floored_time: - log.debug("{} {} cache expired: cachedTime:{}".format(self, id_, cached_time)) + log.debug( + "{} {} cache expired: cachedTime:{}".format(self, id_, cached_time) + ) self = super().__call__(descriptor_dict, sg_connection, bundle_type) cls._instances[id_] = (floored_time, self) @@ -261,7 +268,6 @@ def _copy(self, target_path, skip_list=None): # copy descriptor into target. shutil.copytree(self.get_path(), target_path) - def _get_git_clone_commands( self, target_path, depth=None, ref=None, is_latest_commit=None ): @@ -289,6 +295,8 @@ def _get_git_clone_commands( depth = "--depth {}".format(depth) if depth else "" ref = "-b {}".format(ref) if ref else "" - cmd = 'git clone --no-hardlinks -q "{}" {} "{}" {}'.format(self._path, ref, target_path, depth) + cmd = 'git clone --no-hardlinks -q "{}" {} "{}" {}'.format( + self._path, ref, target_path, depth + ) return cmd diff --git a/python/tank/descriptor/io_descriptor/git_branch.py b/python/tank/descriptor/io_descriptor/git_branch.py index 2f17c48d47..a82e668aa0 100644 --- a/python/tank/descriptor/io_descriptor/git_branch.py +++ b/python/tank/descriptor/io_descriptor/git_branch.py @@ -100,7 +100,9 @@ def _get_bundle_cache_path(self, bundle_cache_root): # /full/path/to/local/repo.git -> repo.git name = os.path.basename(self._path) - return os.path.join(bundle_cache_root, "gitbranch", name, self.get_short_version()) + return os.path.join( + bundle_cache_root, "gitbranch", name, self.get_short_version() + ) def get_version(self): """Returns the full commit sha.""" @@ -112,7 +114,9 @@ def get_short_version(self): def get_latest_commit(self): """Fetch the latest commit on a specific branch""" - output = self._execute_git_commands(["git", "ls-remote", self._path, self._branch]) + output = self._execute_git_commands( + ["git", "ls-remote", self._path, self._branch] + ) latest_commit = output.split("\t")[0] if not latest_commit: @@ -158,7 +162,7 @@ def download_local(self): try: # clone the repo, switch to the given branch # then reset to the given commit - commands = [f'checkout', '-q', self._version] + commands = [f"checkout", "-q", self._version] self._clone_then_execute_git_commands( self._get_primary_cache_path(), commands, diff --git a/python/tank/descriptor/io_descriptor/git_tag.py b/python/tank/descriptor/io_descriptor/git_tag.py index d99b97a6bb..c58a78bee8 100644 --- a/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tank/descriptor/io_descriptor/git_tag.py @@ -78,7 +78,11 @@ def __init__(self, descriptor_dict, sg_connection, bundle_type): self._version = self._get_latest_by_pattern(None) else: self._version = self._get_latest_by_pattern(raw_version) - log.info("{}-{} resolved as {}".format(self.get_system_name(), raw_version, self._version)) + log.info( + "{}-{} resolved as {}".format( + self.get_system_name(), raw_version, self._version + ) + ) else: self._version = raw_version @@ -215,7 +219,9 @@ def _get_latest_by_pattern(self, pattern): return latest_tag def _fetch_tags(self): - output = self._execute_git_commands(["git", "ls-remote", "-q", "--tags", self._path]) + output = self._execute_git_commands( + ["git", "ls-remote", "-q", "--tags", self._path] + ) git_tags = [] for line in output.splitlines(): @@ -235,7 +241,9 @@ def _get_latest_tag(self): tupled_tags = [] for t in self._tags: items = t.lstrip("v").split(".") - tupled_tags.append(tuple(int(item) if item.isdigit() else item for item in items)) + tupled_tags.append( + tuple(int(item) if item.isdigit() else item for item in items) + ) return sorted(tupled_tags)[-1] From 92b79a924a5dd252ffd66197d5fb1fd995c63ef6 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Thu, 24 Aug 2023 01:21:45 +0200 Subject: [PATCH 22/38] =?UTF-8?q?=F0=9F=90=9B=20git=20descriptor:=20missin?= =?UTF-8?q?g=20copy=20skip=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index 1dc128aea8..f90c73169a 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -266,7 +266,10 @@ def _copy(self, target_path, skip_list=None): # make sure item exists locally self.ensure_local() # copy descriptor into target. - shutil.copytree(self.get_path(), target_path) + shutil.copytree(self.get_path(), + target_path, + ignore=shutil.ignore_patterns(*(skip_list or [])), + dirs_exist_ok=True) def _get_git_clone_commands( self, target_path, depth=None, ref=None, is_latest_commit=None From 0c111619c0740c70c292b0548dafac2fdc245892 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Thu, 24 Aug 2023 01:22:14 +0200 Subject: [PATCH 23/38] =?UTF-8?q?=F0=9F=90=9B=20git=20tag=20descriptor:=20?= =?UTF-8?q?tags=20are=20fetched=20on=20first=20property=20call?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git_tag.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/python/tank/descriptor/io_descriptor/git_tag.py b/python/tank/descriptor/io_descriptor/git_tag.py index c58a78bee8..3f39d9a355 100644 --- a/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tank/descriptor/io_descriptor/git_tag.py @@ -73,7 +73,6 @@ def __init__(self, descriptor_dict, sg_connection, bundle_type): raw_version_is_latest = raw_version == "latest" if "x" in raw_version or raw_version_is_latest: - self._tags = self._fetch_tags() if raw_version_is_latest: self._version = self._get_latest_by_pattern(None) else: @@ -93,6 +92,16 @@ def __str__(self): # git@github.com:manneohrstrom/tk-hiero-publish.git, tag v1.2.3 return "%s, Tag %s" % (self._path, self._version) + @property + def _tags(self): + """Fetch tags if necessary.""" + try: + return self.__tags + except AttributeError: + log.info("Fetch tags for {}".format(self.get_system_name())) + self.__tags = self._fetch_tags() + return self.__tags + def _get_bundle_cache_path(self, bundle_cache_root): """ Given a cache root, compute a cache path suitable From 61faba6d07561159ef9f53ce11fc145e8e8da30b Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Thu, 24 Aug 2023 01:23:02 +0200 Subject: [PATCH 24/38] =?UTF-8?q?=F0=9F=90=9B=20git=20tag=20descriptor:=20?= =?UTF-8?q?fix=20wrong=20tag=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git_tag.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git_tag.py b/python/tank/descriptor/io_descriptor/git_tag.py index 3f39d9a355..ef77e4161e 100644 --- a/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tank/descriptor/io_descriptor/git_tag.py @@ -249,12 +249,12 @@ def _get_latest_tag(self): tupled_tags = [] for t in self._tags: - items = t.lstrip("v").split(".") + items = t.split(".") tupled_tags.append( tuple(int(item) if item.isdigit() else item for item in items) ) - return sorted(tupled_tags)[-1] + return ".".join(map(str, sorted(tupled_tags)[-1])) def get_latest_cached_version(self, constraint_pattern=None): """ From 4087ba434d73fafd829491713effb6e48c71bf40 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Thu, 24 Aug 2023 01:25:29 +0200 Subject: [PATCH 25/38] =?UTF-8?q?=F0=9F=8E=A8=20black?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index f90c73169a..2f0966d7c2 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -266,10 +266,12 @@ def _copy(self, target_path, skip_list=None): # make sure item exists locally self.ensure_local() # copy descriptor into target. - shutil.copytree(self.get_path(), - target_path, - ignore=shutil.ignore_patterns(*(skip_list or [])), - dirs_exist_ok=True) + shutil.copytree( + self.get_path(), + target_path, + ignore=shutil.ignore_patterns(*(skip_list or [])), + dirs_exist_ok=True, + ) def _get_git_clone_commands( self, target_path, depth=None, ref=None, is_latest_commit=None From 3a2fa22d54fa965638fa06157cc7556dc76cb58a Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Thu, 24 Aug 2023 02:33:33 +0200 Subject: [PATCH 26/38] =?UTF-8?q?=F0=9F=90=9B=20git=20descriptors:=20fix?= =?UTF-8?q?=20local=20repo=20path=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 24 ++++++++- .../descriptor/io_descriptor/git_branch.py | 53 +++++++++++-------- .../tank/descriptor/io_descriptor/git_tag.py | 31 +++++++---- 3 files changed, 76 insertions(+), 32 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index 2f0966d7c2..f15d7393dd 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -18,7 +18,7 @@ from ... import LogManager from ...util.process import subprocess_check_output, SubprocessCalledProcessError -from ..errors import TankError +from ..errors import TankError, TankDescriptorError from ...util import is_windows log = LogManager.get_logger(__name__) @@ -273,6 +273,28 @@ def _copy(self, target_path, skip_list=None): dirs_exist_ok=True, ) + @property + def _normalized_path(self): + if os.path.isdir(self._path): + return os.path.dirname(self._path) if self._path.endswith(".git") else self._path + else: + return self._path + + def _path_is_local(self): + """ + Check if path value is an existing folder, and if contain a .git folder. + """ + if os.path.isdir(self._path): + output = self._execute_git_commands(["git", "-C", os.path.normpath(self._normalized_path), "status", "--short"]) + if output.startswith("fatal: not a git repository"): + raise TankDescriptorError( + "Folder is not a git repository: {}".format(self._path) + ) + else: + return True + + return False + def _get_git_clone_commands( self, target_path, depth=None, ref=None, is_latest_commit=None ): diff --git a/python/tank/descriptor/io_descriptor/git_branch.py b/python/tank/descriptor/io_descriptor/git_branch.py index a82e668aa0..254b05db6e 100644 --- a/python/tank/descriptor/io_descriptor/git_branch.py +++ b/python/tank/descriptor/io_descriptor/git_branch.py @@ -9,6 +9,7 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import os import copy +import shutil from .git import IODescriptorGit, TankGitError from ..errors import TankDescriptorError @@ -153,28 +154,38 @@ def download_local(self): Downloads the data represented by the descriptor into the primary bundle cache path. """ - log.info("Downloading {}:{}".format(self.get_system_name(), self._version)) - - depth = None - is_latest_commit = self._is_latest_commit() - if is_latest_commit: - depth = 1 - try: - # clone the repo, switch to the given branch - # then reset to the given commit - commands = [f"checkout", "-q", self._version] - self._clone_then_execute_git_commands( - self._get_primary_cache_path(), - commands, - depth=depth, - ref=self._branch, - is_latest_commit=is_latest_commit, - ) - except (TankGitError, OSError, SubprocessCalledProcessError) as e: - raise TankDescriptorError( - "Could not download %s, branch %s, " - "commit %s: %s" % (self._path, self._branch, self._version, e) + target_path = self._get_primary_cache_path() + + if self._path_is_local() and not self.exists_local(): + log.info("Copying {}:{}".format(self.get_system_name(), self._version)) + shutil.copytree( + self._normalized_path, + target_path, + dirs_exist_ok=True, ) + else: + log.info("Downloading {}:{}".format(self.get_system_name(), self._version)) + + depth = None + is_latest_commit = self._is_latest_commit() + if is_latest_commit: + depth = 1 + try: + # clone the repo, switch to the given branch + # then reset to the given commit + commands = [f"checkout", "-q", self._version] + self._clone_then_execute_git_commands( + target_path, + commands, + depth=depth, + ref=self._branch, + is_latest_commit=is_latest_commit, + ) + except (TankGitError, OSError, SubprocessCalledProcessError) as e: + raise TankDescriptorError( + "Could not download %s, branch %s, " + "commit %s: %s" % (self._path, self._branch, self._version, e) + ) def get_latest_version(self, constraint_pattern=None): """ diff --git a/python/tank/descriptor/io_descriptor/git_tag.py b/python/tank/descriptor/io_descriptor/git_tag.py index ef77e4161e..0e13f569c8 100644 --- a/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tank/descriptor/io_descriptor/git_tag.py @@ -10,6 +10,7 @@ import os import copy import re +import shutil from .git import IODescriptorGit, TankGitError from ..errors import TankDescriptorError @@ -159,17 +160,27 @@ def download_local(self): Downloads the data represented by the descriptor into the primary bundle cache path. """ - log.info("Downloading {}:{}".format(self.get_system_name(), self._version)) - - try: - # clone the repo, checkout the given tag - self._clone_then_execute_git_commands( - self._get_primary_cache_path(), [], depth=1, ref=self._version - ) - except (TankGitError, OSError, SubprocessCalledProcessError) as e: - raise TankDescriptorError( - "Could not download %s, " "tag %s: %s" % (self._path, self._version, e) + target_path = self._get_primary_cache_path() + + if self._path_is_local() and not self.exists_local(): + log.info("Copying {}:{}".format(self.get_system_name(), self._version)) + shutil.copytree( + self._normalized_path, + target_path, + dirs_exist_ok=True, ) + else: + log.info("Downloading {}:{}".format(self.get_system_name(), self._version)) + try: + # clone the repo, checkout the given tag + self._clone_then_execute_git_commands( + target_path, [], depth=1, ref=self._version + ) + except (TankGitError, OSError, SubprocessCalledProcessError) as e: + raise TankDescriptorError( + "Could not download %s, " + "tag %s: %s" % (self._path, self._version, e) + ) def get_latest_version(self, constraint_pattern=None): """ From 0a9e6e59b794bfeba300e23e9ebbc72ecacf311b Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Sat, 26 Aug 2023 22:54:24 +0200 Subject: [PATCH 27/38] =?UTF-8?q?=F0=9F=90=9B=20update=20version=20tags=20?= =?UTF-8?q?used=20by=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/descriptor_tests/test_git.py | 20 +++++++++---------- tests/descriptor_tests/test_io_descriptors.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/descriptor_tests/test_git.py b/tests/descriptor_tests/test_git.py index 914f311f2c..4e4e1ff0bf 100644 --- a/tests/descriptor_tests/test_git.py +++ b/tests/descriptor_tests/test_git.py @@ -62,12 +62,12 @@ def test_latest(self): } desc = self._create_desc(location_dict, True) - self.assertEqual(desc.version, "30c293f29a50b1e58d2580522656695825523dba") + self.assertEqual(desc.version, "ac71eac21baa8b5d26b44d3deb554110ae879d61") location_dict = {"type": "git", "path": self.git_repo_uri} desc = self._create_desc(location_dict, True) - self.assertEqual(desc.version, "v0.16.1") + self.assertEqual(desc.version, "v0.18.2") @skip_if_git_missing def test_tag(self): @@ -94,20 +94,20 @@ def test_tag(self): latest_desc = desc.find_latest_version() - self.assertEqual(latest_desc.version, "v0.16.1") + self.assertEqual(latest_desc.version, "v0.18.2") self.assertEqual(latest_desc.get_path(), None) latest_desc.ensure_local() - self.assertEqual(latest_desc.find_latest_cached_version().version, "v0.16.1") + self.assertEqual(latest_desc.find_latest_cached_version().version, "v0.18.2") self.assertEqual( - latest_desc.find_latest_cached_version("v0.16.x").version, "v0.16.1" + latest_desc.find_latest_cached_version("v0.18.x").version, "v0.18.2" ) self.assertEqual( latest_desc.get_path(), - os.path.join(self.bundle_cache, "git", "tk-config-default.git", "v0.16.1"), + os.path.join(self.bundle_cache, "git", "tk-config-default.git", "v0.18.2"), ) latest_desc = desc.find_latest_version("v0.15.x") @@ -176,7 +176,7 @@ def test_branch(self): latest_desc = desc.find_latest_version() self.assertEqual( - latest_desc.version, "30c293f29a50b1e58d2580522656695825523dba" + latest_desc.version, "ac71eac21baa8b5d26b44d3deb554110ae879d61" ) self.assertEqual(latest_desc.get_path(), None) @@ -185,7 +185,7 @@ def test_branch(self): self.assertEqual( latest_desc.get_path(), os.path.join( - self.bundle_cache, "gitbranch", "tk-config-default.git", "30c293f" + self.bundle_cache, "gitbranch", "tk-config-default.git", "ac71eac" ), ) @@ -212,7 +212,7 @@ def test_branch(self): latest_desc = desc.find_latest_version() self.assertEqual( - latest_desc.version, "7fa75a749c1dfdbd9ad93ee3497c7eaa8e1a488d" + latest_desc.version, "b2960d7e8cce43dda63369a6805881443b3daf17" ) self.assertEqual(latest_desc.get_path(), None) @@ -221,7 +221,7 @@ def test_branch(self): self.assertEqual( latest_desc.get_path(), os.path.join( - self.bundle_cache, "gitbranch", "tk-config-default.git", "7fa75a7" + self.bundle_cache, "gitbranch", "tk-config-default.git", "b2960d7" ), ) diff --git a/tests/descriptor_tests/test_io_descriptors.py b/tests/descriptor_tests/test_io_descriptors.py index cc635ad3de..6a6a150a3e 100644 --- a/tests/descriptor_tests/test_io_descriptors.py +++ b/tests/descriptor_tests/test_io_descriptors.py @@ -227,7 +227,7 @@ def test_git_branch_descriptor_input(self): sg = self.mockgun location = { "type": "git_branch", - "version": 6547378, + "version": "6547378", "branch": "master", "path": self.git_repo_uri, } From 6283a15f900887c57e922df5f3c2a887d025e7a8 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Sat, 26 Aug 2023 23:47:16 +0200 Subject: [PATCH 28/38] =?UTF-8?q?=F0=9F=8F=81=20fix=20tests:=20cant=20move?= =?UTF-8?q?=20readonly=20file=20on=20windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/descriptor_tests/test_downloadables.py | 31 ++++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/tests/descriptor_tests/test_downloadables.py b/tests/descriptor_tests/test_downloadables.py index 1a9b029893..1862058ffa 100644 --- a/tests/descriptor_tests/test_downloadables.py +++ b/tests/descriptor_tests/test_downloadables.py @@ -46,6 +46,25 @@ def _raise_exception(placeholder_a="default_a", placeholder_b="default_b"): raise OSError("An unknown OSError occurred") +def onerror(func, path, exc_info): + """ + Error handler for ``shutil.rmtree``. + + If the error is due to an access error (read only file) + it attempts to add write permission and then retries. + + If the error is for another reason it re-raises the error. + + Usage : ``shutil.rmtree(path, onerror=onerror)`` + """ + import stat + if not os.access(path, os.W_OK): + os.chmod(path, stat.S_IWUSR) + func(path) + else: + raise + + class TestDownloadableIODescriptors(ShotgunTestBase): """ Tests the ability of the descriptor to download to a path on disk. @@ -624,7 +643,9 @@ def test_descriptor_rename_error_fallbacks(self, *_): "e1c03fa", ) if os.path.exists(git_location): - shutil.move(git_location, "%s.bak.%s" % (git_location, uuid.uuid4().hex)) + # cant use `shutil.move` on readonly files on windows + shutil.copytree(git_location, "%s.bak.%s" % (git_location, uuid.uuid4().hex)) + shutil.rmtree(git_location, onerror=onerror) # make sure nothing exists self.assertFalse(os.path.exists(git_location)) @@ -676,7 +697,9 @@ def our_move_mock(src, dst): "e1c03fa", ) if os.path.exists(git_location): - shutil.move(git_location, "%s.bak.%s" % (git_location, uuid.uuid4().hex)) + # cant use `shutil.move` on readonly files on windows + shutil.copytree(git_location, "%s.bak.%s" % (git_location, uuid.uuid4().hex)) + shutil.rmtree(git_location, onerror=onerror) # make sure nothing exists self.assertFalse(os.path.exists(git_location)) @@ -712,7 +735,9 @@ def test_partial_download_handling(self): "e1c03fa", ) if os.path.exists(git_location): - shutil.move(git_location, "%s.bak.%s" % (git_location, uuid.uuid4().hex)) + # cant use `shutil.move` on readonly files on windows + shutil.copytree(git_location, "%s.bak.%s" % (git_location, uuid.uuid4().hex)) + shutil.rmtree(git_location, onerror=onerror) # make sure nothing exists self.assertFalse(os.path.exists(git_location)) From d697a7e2fd8cbefadeea2e7b764aa1027ee31dca Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Sat, 26 Aug 2023 23:49:57 +0200 Subject: [PATCH 29/38] =?UTF-8?q?=20=F0=9F=90=9B=20git=20descriptors:=20fi?= =?UTF-8?q?x=20local=20repo=20path=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 50 +++++++++++++------ .../descriptor/io_descriptor/git_branch.py | 11 ++++ .../tank/descriptor/io_descriptor/git_tag.py | 8 +++ 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index f15d7393dd..344a6156b1 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -114,11 +114,15 @@ def __init__(self, descriptor_dict, sg_connection, bundle_type): descriptor_dict, sg_connection, bundle_type ) - self._path = descriptor_dict.get("path") - # strip trailing slashes - this is so that when we build - # the name later (using os.basename) we construct it correctly. - if self._path.endswith("/") or self._path.endswith("\\"): - self._path = self._path[:-1] + _path = self._normalize_path(descriptor_dict.get("path")) + + if self._path_is_local(_path): + self._path = self._execute_git_commands(["git", "-C", _path, "remote", "get-url", "origin"]) + log.debug("Get remote url from local repo: {} -> {}".format(_path, self._path)) + filtered_local_data = {k: v for k, v in self._fetch_local_data(_path).items() if k not in descriptor_dict} + descriptor_dict.update(filtered_local_data) + else: + self._path = _path def is_git_available(self): log.debug("Checking that git exists and can be executed...") @@ -273,24 +277,37 @@ def _copy(self, target_path, skip_list=None): dirs_exist_ok=True, ) - @property - def _normalized_path(self): - if os.path.isdir(self._path): - return os.path.dirname(self._path) if self._path.endswith(".git") else self._path + def _normalize_path(self, path): + if path.endswith("/") or path.endswith("\\"): + new_path = path[:-1] else: - return self._path + new_path = path + + if os.path.isdir(new_path): + if not new_path.endswith(".git"): + new_path = os.path.join(new_path, ".git") - def _path_is_local(self): + return new_path + + def _path_is_local(self, path): """ Check if path value is an existing folder, and if contain a .git folder. """ - if os.path.isdir(self._path): - output = self._execute_git_commands(["git", "-C", os.path.normpath(self._normalized_path), "status", "--short"]) + if os.path.isdir(path): + output = self._execute_git_commands( + [ + "git", + "-C", + os.path.normpath(path), + "rev-parse", + "--git-dir", + ] + ) if output.startswith("fatal: not a git repository"): raise TankDescriptorError( - "Folder is not a git repository: {}".format(self._path) + "Folder is not a git repository: {}".format(path) ) - else: + elif output == ".": return True return False @@ -327,3 +344,6 @@ def _get_git_clone_commands( ) return cmd + + def _fetch_local_data(self, path): + return {} \ No newline at end of file diff --git a/python/tank/descriptor/io_descriptor/git_branch.py b/python/tank/descriptor/io_descriptor/git_branch.py index 254b05db6e..0c0a0b9dec 100644 --- a/python/tank/descriptor/io_descriptor/git_branch.py +++ b/python/tank/descriptor/io_descriptor/git_branch.py @@ -250,3 +250,14 @@ def get_latest_cached_version(self, constraint_pattern=None): else: # no cached version exists return None + + def _fetch_local_data(self, path): + version = self._execute_git_commands( + [ "git", "-C", os.path.normpath(path), "rev-parse", "HEAD"]) + + branch = self._execute_git_commands( + [ "git", "-C", os.path.normpath(path), "branch", "--show-current"]) + + local_data = {"version": version, "branch": branch} + log.debug("Get local repo data: {}".format(local_data)) + return local_data \ No newline at end of file diff --git a/python/tank/descriptor/io_descriptor/git_tag.py b/python/tank/descriptor/io_descriptor/git_tag.py index 0e13f569c8..055b5a62dd 100644 --- a/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tank/descriptor/io_descriptor/git_tag.py @@ -303,3 +303,11 @@ def get_latest_cached_version(self, constraint_pattern=None): log.debug("Latest cached version resolved to %r" % desc) return desc + + def _fetch_local_data(self, path): + version = self._execute_git_commands( + ["git", "-C", os.path.normpath(path), "describe", "--tags", "--abbrev=0"]) + + local_data = {"version": version} + log.debug("Get local repo data: {}".format(local_data)) + return local_data \ No newline at end of file From 38c2e846af40d27ecdf3599d47ac93daa01add94 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Sat, 26 Aug 2023 23:50:55 +0200 Subject: [PATCH 30/38] =?UTF-8?q?=E2=8F=AA=20rollback=20on=20using=20files?= =?UTF-8?q?ystem.copy=5Ffolder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index 344a6156b1..921cbe7f10 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -9,7 +9,6 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import os import platform -import shutil import subprocess from time import time @@ -19,6 +18,7 @@ from ...util.process import subprocess_check_output, SubprocessCalledProcessError from ..errors import TankError, TankDescriptorError +from ...util import filesystem from ...util import is_windows log = LogManager.get_logger(__name__) @@ -270,12 +270,9 @@ def _copy(self, target_path, skip_list=None): # make sure item exists locally self.ensure_local() # copy descriptor into target. - shutil.copytree( - self.get_path(), - target_path, - ignore=shutil.ignore_patterns(*(skip_list or [])), - dirs_exist_ok=True, - ) + filesystem.copy_folder(self.get_path(), + target_path, + skip_list=skip_list or []) def _normalize_path(self, path): if path.endswith("/") or path.endswith("\\"): From d81a3b77cc5ed34af636f0275dc385441413deb0 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Sat, 26 Aug 2023 23:51:59 +0200 Subject: [PATCH 31/38] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20git=20descriptors?= =?UTF-8?q?:=20overload=20private=20=5Fdownload=5Flocal=20instead=20of=20p?= =?UTF-8?q?ublic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../descriptor/io_descriptor/git_branch.py | 53 ++++++++----------- .../tank/descriptor/io_descriptor/git_tag.py | 33 ++++-------- 2 files changed, 32 insertions(+), 54 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git_branch.py b/python/tank/descriptor/io_descriptor/git_branch.py index 0c0a0b9dec..25694b132e 100644 --- a/python/tank/descriptor/io_descriptor/git_branch.py +++ b/python/tank/descriptor/io_descriptor/git_branch.py @@ -9,7 +9,6 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import os import copy -import shutil from .git import IODescriptorGit, TankGitError from ..errors import TankDescriptorError @@ -149,43 +148,33 @@ def _is_latest_commit(self): return True - def download_local(self): + def _download_local(self, target_path): """ Downloads the data represented by the descriptor into the primary bundle cache path. """ - target_path = self._get_primary_cache_path() - - if self._path_is_local() and not self.exists_local(): - log.info("Copying {}:{}".format(self.get_system_name(), self._version)) - shutil.copytree( - self._normalized_path, + log.info("Downloading {}:{}".format(self.get_system_name(), self._version)) + + depth = None + is_latest_commit = self._is_latest_commit() + if is_latest_commit: + depth = 1 + try: + # clone the repo, switch to the given branch + # then reset to the given commit + commands = [f"checkout", "-q", self._version] + self._clone_then_execute_git_commands( target_path, - dirs_exist_ok=True, + commands, + depth=depth, + ref=self._branch, + is_latest_commit=is_latest_commit, + ) + except (TankGitError, OSError, SubprocessCalledProcessError) as e: + raise TankDescriptorError( + "Could not download %s, branch %s, " + "commit %s: %s" % (self._path, self._branch, self._version, e) ) - else: - log.info("Downloading {}:{}".format(self.get_system_name(), self._version)) - - depth = None - is_latest_commit = self._is_latest_commit() - if is_latest_commit: - depth = 1 - try: - # clone the repo, switch to the given branch - # then reset to the given commit - commands = [f"checkout", "-q", self._version] - self._clone_then_execute_git_commands( - target_path, - commands, - depth=depth, - ref=self._branch, - is_latest_commit=is_latest_commit, - ) - except (TankGitError, OSError, SubprocessCalledProcessError) as e: - raise TankDescriptorError( - "Could not download %s, branch %s, " - "commit %s: %s" % (self._path, self._branch, self._version, e) - ) def get_latest_version(self, constraint_pattern=None): """ diff --git a/python/tank/descriptor/io_descriptor/git_tag.py b/python/tank/descriptor/io_descriptor/git_tag.py index 055b5a62dd..6436fcfe47 100644 --- a/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tank/descriptor/io_descriptor/git_tag.py @@ -10,7 +10,6 @@ import os import copy import re -import shutil from .git import IODescriptorGit, TankGitError from ..errors import TankDescriptorError @@ -155,32 +154,22 @@ def get_version(self): """Returns the tag name.""" return self._version - def download_local(self): + def _download_local(self, target_path): """ Downloads the data represented by the descriptor into the primary bundle cache path. """ - target_path = self._get_primary_cache_path() - - if self._path_is_local() and not self.exists_local(): - log.info("Copying {}:{}".format(self.get_system_name(), self._version)) - shutil.copytree( - self._normalized_path, - target_path, - dirs_exist_ok=True, + log.info("Downloading {}:{}".format(self.get_system_name(), self._version)) + try: + # clone the repo, checkout the given tag + self._clone_then_execute_git_commands( + target_path, [], depth=1, ref=self._version + ) + except (TankGitError, OSError, SubprocessCalledProcessError) as e: + raise TankDescriptorError( + "Could not download %s, " + "tag %s: %s" % (self._path, self._version, e) ) - else: - log.info("Downloading {}:{}".format(self.get_system_name(), self._version)) - try: - # clone the repo, checkout the given tag - self._clone_then_execute_git_commands( - target_path, [], depth=1, ref=self._version - ) - except (TankGitError, OSError, SubprocessCalledProcessError) as e: - raise TankDescriptorError( - "Could not download %s, " - "tag %s: %s" % (self._path, self._version, e) - ) def get_latest_version(self, constraint_pattern=None): """ From 17139121698a31521f2779e8a813e42272d8a0f5 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Sat, 26 Aug 2023 23:53:28 +0200 Subject: [PATCH 32/38] =?UTF-8?q?=F0=9F=8E=A8=20black?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 22 ++++++++++++------- .../descriptor/io_descriptor/git_branch.py | 8 ++++--- .../tank/descriptor/io_descriptor/git_tag.py | 8 +++---- tests/descriptor_tests/test_downloadables.py | 1 + 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index 921cbe7f10..1408614a00 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -117,9 +117,17 @@ def __init__(self, descriptor_dict, sg_connection, bundle_type): _path = self._normalize_path(descriptor_dict.get("path")) if self._path_is_local(_path): - self._path = self._execute_git_commands(["git", "-C", _path, "remote", "get-url", "origin"]) - log.debug("Get remote url from local repo: {} -> {}".format(_path, self._path)) - filtered_local_data = {k: v for k, v in self._fetch_local_data(_path).items() if k not in descriptor_dict} + self._path = self._execute_git_commands( + ["git", "-C", _path, "remote", "get-url", "origin"] + ) + log.debug( + "Get remote url from local repo: {} -> {}".format(_path, self._path) + ) + filtered_local_data = { + k: v + for k, v in self._fetch_local_data(_path).items() + if k not in descriptor_dict + } descriptor_dict.update(filtered_local_data) else: self._path = _path @@ -270,12 +278,10 @@ def _copy(self, target_path, skip_list=None): # make sure item exists locally self.ensure_local() # copy descriptor into target. - filesystem.copy_folder(self.get_path(), - target_path, - skip_list=skip_list or []) + filesystem.copy_folder(self.get_path(), target_path, skip_list=skip_list or []) def _normalize_path(self, path): - if path.endswith("/") or path.endswith("\\"): + if path.endswith("/") or path.endswith("\\"): new_path = path[:-1] else: new_path = path @@ -343,4 +349,4 @@ def _get_git_clone_commands( return cmd def _fetch_local_data(self, path): - return {} \ No newline at end of file + return {} diff --git a/python/tank/descriptor/io_descriptor/git_branch.py b/python/tank/descriptor/io_descriptor/git_branch.py index 25694b132e..2c60c7ea89 100644 --- a/python/tank/descriptor/io_descriptor/git_branch.py +++ b/python/tank/descriptor/io_descriptor/git_branch.py @@ -242,11 +242,13 @@ def get_latest_cached_version(self, constraint_pattern=None): def _fetch_local_data(self, path): version = self._execute_git_commands( - [ "git", "-C", os.path.normpath(path), "rev-parse", "HEAD"]) + ["git", "-C", os.path.normpath(path), "rev-parse", "HEAD"] + ) branch = self._execute_git_commands( - [ "git", "-C", os.path.normpath(path), "branch", "--show-current"]) + ["git", "-C", os.path.normpath(path), "branch", "--show-current"] + ) local_data = {"version": version, "branch": branch} log.debug("Get local repo data: {}".format(local_data)) - return local_data \ No newline at end of file + return local_data diff --git a/python/tank/descriptor/io_descriptor/git_tag.py b/python/tank/descriptor/io_descriptor/git_tag.py index 6436fcfe47..b4e583d95d 100644 --- a/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tank/descriptor/io_descriptor/git_tag.py @@ -167,8 +167,7 @@ def _download_local(self, target_path): ) except (TankGitError, OSError, SubprocessCalledProcessError) as e: raise TankDescriptorError( - "Could not download %s, " - "tag %s: %s" % (self._path, self._version, e) + "Could not download %s, " "tag %s: %s" % (self._path, self._version, e) ) def get_latest_version(self, constraint_pattern=None): @@ -295,8 +294,9 @@ def get_latest_cached_version(self, constraint_pattern=None): def _fetch_local_data(self, path): version = self._execute_git_commands( - ["git", "-C", os.path.normpath(path), "describe", "--tags", "--abbrev=0"]) + ["git", "-C", os.path.normpath(path), "describe", "--tags", "--abbrev=0"] + ) local_data = {"version": version} log.debug("Get local repo data: {}".format(local_data)) - return local_data \ No newline at end of file + return local_data diff --git a/tests/descriptor_tests/test_downloadables.py b/tests/descriptor_tests/test_downloadables.py index 1862058ffa..4b92311c12 100644 --- a/tests/descriptor_tests/test_downloadables.py +++ b/tests/descriptor_tests/test_downloadables.py @@ -58,6 +58,7 @@ def onerror(func, path, exc_info): Usage : ``shutil.rmtree(path, onerror=onerror)`` """ import stat + if not os.access(path, os.W_OK): os.chmod(path, stat.S_IWUSR) func(path) From b3aa8a46f56337e32c6f2770b8f55b86eac4abce Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Sun, 27 Aug 2023 00:29:07 +0200 Subject: [PATCH 33/38] =?UTF-8?q?=20=F0=9F=90=9B=20git=20descriptors:=20ge?= =?UTF-8?q?t=20clone=20commands=20as=20list=20instead=20of=20str?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 28 ++++++++++----------- tests/descriptor_tests/test_descriptors.py | 6 ++--- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index 1408614a00..af051d6e1f 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -155,12 +155,7 @@ def _execute_git_commands(self, commands): # first probe to check that git exists in our PATH self.is_git_available() - if not isinstance(commands, str): - str_cmd = " ".join(commands) - else: - str_cmd = commands - - log.debug("Executing command '%s' using subprocess module." % str_cmd) + log.debug("Executing command '%s' using subprocess module." % commands) # It's important to pass GIT_TERMINAL_PROMPT=0 or the git subprocess will # just hang waiting for credentials to be entered on the missing terminal. @@ -174,7 +169,7 @@ def _execute_git_commands(self, commands): except SubprocessCalledProcessError as e: raise TankGitError( "Error executing git operation '%s': %s (Return code %s)" - % (str_cmd, e.output, e.returncode) + % (commands, e.output, e.returncode) ) else: output = output.strip().strip("'") @@ -337,14 +332,19 @@ def _get_git_clone_commands( log.debug("Git Cloning %r into %s" % (self, target_path)) if self._descriptor_dict.get("type") == "git_branch" and not is_latest_commit: - depth = "" - else: - depth = "--depth {}".format(depth) if depth else "" + depth = None - ref = "-b {}".format(ref) if ref else "" - cmd = 'git clone --no-hardlinks -q "{}" {} "{}" {}'.format( - self._path, ref, target_path, depth - ) + cmd = ['git', 'clone', '--no-hardlinks', '-q'] + + if ref: + cmd.extend(["-b", str(ref)]) + + + if depth: + cmd.extend(["--depth", str(depth)]) + + cmd.append(self._path) + cmd.append(os.path.normpath(target_path)) return cmd diff --git a/tests/descriptor_tests/test_descriptors.py b/tests/descriptor_tests/test_descriptors.py index b6ea4f8e7c..e67712700f 100644 --- a/tests/descriptor_tests/test_descriptors.py +++ b/tests/descriptor_tests/test_descriptors.py @@ -690,13 +690,11 @@ def test_git_branch_descriptor_commands(self): desc._io_descriptor._get_git_clone_commands( target_path, depth=1, ref="master" ), - 'git clone --no-hardlinks -q "%s" %s "%s" ' - % (self.git_repo_uri, "-b master", target_path,), + ['git', 'clone', '--no-hardlinks', '-q', '-b', 'master', self.git_repo_uri, target_path] ) self.assertEqual( desc._io_descriptor._get_git_clone_commands(target_path, ref="master"), - 'git clone --no-hardlinks -q "%s" %s "%s" ' - % (self.git_repo_uri, "-b master", target_path,), + ['git', 'clone', '--no-hardlinks', '-q', '-b', 'master', self.git_repo_uri, target_path] ) From 0bf6d78f5d5e7b5df55631dc372472e56238e89c Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Sun, 27 Aug 2023 00:30:35 +0200 Subject: [PATCH 34/38] =?UTF-8?q?=F0=9F=8E=A8=20black?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 3 +-- tests/descriptor_tests/test_descriptors.py | 22 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index af051d6e1f..c6f69b1dd0 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -334,12 +334,11 @@ def _get_git_clone_commands( if self._descriptor_dict.get("type") == "git_branch" and not is_latest_commit: depth = None - cmd = ['git', 'clone', '--no-hardlinks', '-q'] + cmd = ["git", "clone", "--no-hardlinks", "-q"] if ref: cmd.extend(["-b", str(ref)]) - if depth: cmd.extend(["--depth", str(depth)]) diff --git a/tests/descriptor_tests/test_descriptors.py b/tests/descriptor_tests/test_descriptors.py index e67712700f..f84fa1cee2 100644 --- a/tests/descriptor_tests/test_descriptors.py +++ b/tests/descriptor_tests/test_descriptors.py @@ -690,11 +690,29 @@ def test_git_branch_descriptor_commands(self): desc._io_descriptor._get_git_clone_commands( target_path, depth=1, ref="master" ), - ['git', 'clone', '--no-hardlinks', '-q', '-b', 'master', self.git_repo_uri, target_path] + [ + "git", + "clone", + "--no-hardlinks", + "-q", + "-b", + "master", + self.git_repo_uri, + target_path, + ], ) self.assertEqual( desc._io_descriptor._get_git_clone_commands(target_path, ref="master"), - ['git', 'clone', '--no-hardlinks', '-q', '-b', 'master', self.git_repo_uri, target_path] + [ + "git", + "clone", + "--no-hardlinks", + "-q", + "-b", + "master", + self.git_repo_uri, + target_path, + ], ) From 7a2336f9ac7ea3979d9b5b1014054a5d0a2d14ee Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Sun, 27 Aug 2023 00:45:29 +0200 Subject: [PATCH 35/38] =?UTF-8?q?=F0=9F=90=9B=20filesystem.copy=5Ffolder?= =?UTF-8?q?=20may=20fail=20if=20destination=20already=20exists=20and=20is?= =?UTF-8?q?=20readonly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/util/filesystem.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/tank/util/filesystem.py b/python/tank/util/filesystem.py index c4d4320736..275b47c10f 100644 --- a/python/tank/util/filesystem.py +++ b/python/tank/util/filesystem.py @@ -270,7 +270,12 @@ def copy_folder(src, dst, folder_permissions=0o775, skip_list=None): if os.path.isdir(srcname): files.extend(copy_folder(srcname, dstname, folder_permissions)) else: + if os.path.exists(dstname) and not os.access(dstname, os.W_OK): + # if destination already exists but is readonly, change it to writable + os.chmod(dstname, stat.S_IWUSR) + shutil.copy(srcname, dstname) + files.append(srcname) # if the file extension is sh, set executable permissions if ( From f4262a1a0f1d2574a73f319234992cef7f5ff296 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Sun, 27 Aug 2023 00:59:19 +0200 Subject: [PATCH 36/38] =?UTF-8?q?=F0=9F=90=9B=20fix=20latest=20tag=20in=20?= =?UTF-8?q?tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/descriptor_tests/test_git.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/descriptor_tests/test_git.py b/tests/descriptor_tests/test_git.py index 4e4e1ff0bf..51f96c0adc 100644 --- a/tests/descriptor_tests/test_git.py +++ b/tests/descriptor_tests/test_git.py @@ -62,7 +62,7 @@ def test_latest(self): } desc = self._create_desc(location_dict, True) - self.assertEqual(desc.version, "ac71eac21baa8b5d26b44d3deb554110ae879d61") + self.assertEqual(desc.version, "e1c03faed94346ad77fad2e700edcff1e449f6f4") location_dict = {"type": "git", "path": self.git_repo_uri} @@ -176,7 +176,7 @@ def test_branch(self): latest_desc = desc.find_latest_version() self.assertEqual( - latest_desc.version, "ac71eac21baa8b5d26b44d3deb554110ae879d61" + latest_desc.version, "e1c03faed94346ad77fad2e700edcff1e449f6f4" ) self.assertEqual(latest_desc.get_path(), None) @@ -185,7 +185,7 @@ def test_branch(self): self.assertEqual( latest_desc.get_path(), os.path.join( - self.bundle_cache, "gitbranch", "tk-config-default.git", "ac71eac" + self.bundle_cache, "gitbranch", "tk-config-default.git", "e1c03fa" ), ) From 1cf4e9c8dbf68d24d658f40b774ae67015c4acd5 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Sun, 27 Aug 2023 16:23:39 +0200 Subject: [PATCH 37/38] =?UTF-8?q?=F0=9F=90=9B=20git=20descriptor:=20cache?= =?UTF-8?q?=20id=20for=20branch=20was=20too=20loose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 6 ++---- tests/descriptor_tests/test_git.py | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index c6f69b1dd0..d279860dd8 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -73,13 +73,11 @@ def __call__(cls, descriptor_dict, sg_connection, bundle_type): if ( descriptor_dict["type"] == "git_branch" ): # cant fetch last commit here, too soon - version = descriptor_dict.get("version") or descriptor_dict["branch"] + version = "-".join(filter(None, [descriptor_dict.get("version"), descriptor_dict["branch"]])) else: version = descriptor_dict["version"] - id_ = "{}-{}-{}".format( - descriptor_dict["type"], descriptor_dict["path"], version - ) + id_ = "-".join([descriptor_dict["type"], descriptor_dict["path"], version]) cached_time, self = cls._instances.get(id_, (-1, None)) if cached_time < floored_time: diff --git a/tests/descriptor_tests/test_git.py b/tests/descriptor_tests/test_git.py index 51f96c0adc..38c812847a 100644 --- a/tests/descriptor_tests/test_git.py +++ b/tests/descriptor_tests/test_git.py @@ -62,7 +62,7 @@ def test_latest(self): } desc = self._create_desc(location_dict, True) - self.assertEqual(desc.version, "e1c03faed94346ad77fad2e700edcff1e449f6f4") + self.assertEqual(desc.version, "dac945d50d2bd0a828181dc3e1d31cfea2c64065") location_dict = {"type": "git", "path": self.git_repo_uri} @@ -176,7 +176,7 @@ def test_branch(self): latest_desc = desc.find_latest_version() self.assertEqual( - latest_desc.version, "e1c03faed94346ad77fad2e700edcff1e449f6f4" + latest_desc.version, "dac945d50d2bd0a828181dc3e1d31cfea2c64065" ) self.assertEqual(latest_desc.get_path(), None) @@ -185,7 +185,7 @@ def test_branch(self): self.assertEqual( latest_desc.get_path(), os.path.join( - self.bundle_cache, "gitbranch", "tk-config-default.git", "e1c03fa" + self.bundle_cache, "gitbranch", "tk-config-default.git", "dac945d" ), ) From 8ddde7e67a6436f027bda5734bb8b5997842d5d1 Mon Sep 17 00:00:00 2001 From: Mathieu <923463-mathbou@users.noreply.gitlab.com> Date: Sun, 27 Aug 2023 16:25:00 +0200 Subject: [PATCH 38/38] =?UTF-8?q?=20=F0=9F=8E=A8=20black?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/tank/descriptor/io_descriptor/git.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/tank/descriptor/io_descriptor/git.py b/python/tank/descriptor/io_descriptor/git.py index d279860dd8..23ba4dee15 100644 --- a/python/tank/descriptor/io_descriptor/git.py +++ b/python/tank/descriptor/io_descriptor/git.py @@ -73,7 +73,11 @@ def __call__(cls, descriptor_dict, sg_connection, bundle_type): if ( descriptor_dict["type"] == "git_branch" ): # cant fetch last commit here, too soon - version = "-".join(filter(None, [descriptor_dict.get("version"), descriptor_dict["branch"]])) + version = "-".join( + filter( + None, [descriptor_dict.get("version"), descriptor_dict["branch"]] + ) + ) else: version = descriptor_dict["version"]