-
Notifications
You must be signed in to change notification settings - Fork 1.2k
refactor: Extract git-clone functionality out of InitTemplates class #2821
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
13a4250
[Refactor] extract git-clone functionality out of InitTemplates class…
elbayaaa a510965
apply review comments
elbayaaa 3fb3a3e
typo
elbayaaa 2a35819
apply review comments
elbayaaa 0477000
Merge branch 'develop' into develop
elbayaaa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| """ Manage Git repo """ | ||
|
|
||
| import logging | ||
| import os | ||
| import platform | ||
| import shutil | ||
| import subprocess | ||
| from pathlib import Path | ||
| from typing import Optional | ||
|
|
||
| from samcli.lib.utils import osutils | ||
| from samcli.lib.utils.osutils import rmtree_callback | ||
|
|
||
| LOG = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class CloneRepoException(Exception): | ||
| """ | ||
| Exception class when clone repo fails. | ||
| """ | ||
|
|
||
|
|
||
| class CloneRepoUnstableStateException(CloneRepoException): | ||
| """ | ||
| Exception class when clone repo enters an unstable state. | ||
| """ | ||
elbayaaa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| class GitRepo: | ||
| """ | ||
| Class for managing a Git repo, currently it has a clone functionality only | ||
|
|
||
| Attributes | ||
| ---------- | ||
| url: str | ||
| The URL of this Git repository, example "https://github.com/aws/aws-sam-cli" | ||
| local_path: Path | ||
| The path of the last local clone of this Git repository. Can be used in conjunction with clone_attempted | ||
| to avoid unnecessary multiple cloning of the repository. | ||
| clone_attempted: bool | ||
| whether an attempt to clone this Git repository took place or not. Can be used in conjunction with local_path | ||
| to avoid unnecessary multiple cloning of the repository | ||
|
|
||
| Methods | ||
| ------- | ||
| clone(self, clone_dir: Path, clone_name, replace_existing=False) -> Path: | ||
| creates a local clone of this Git repository. (more details in the method documentation). | ||
| """ | ||
|
|
||
| def __init__(self, url: str) -> None: | ||
| self.url: str = url | ||
| self.local_path: Optional[Path] = None | ||
| self.clone_attempted: bool = False | ||
|
|
||
| @staticmethod | ||
| def _ensure_clone_directory_exists(clone_dir: Path) -> None: | ||
| try: | ||
| clone_dir.mkdir(mode=0o700, parents=True, exist_ok=True) | ||
| except OSError as ex: | ||
| LOG.warning("WARN: Unable to create clone directory.", exc_info=ex) | ||
| raise | ||
elbayaaa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @staticmethod | ||
| def _git_executable() -> str: | ||
| if platform.system().lower() == "windows": | ||
| executables = ["git", "git.cmd", "git.exe", "git.bat"] | ||
| else: | ||
| executables = ["git"] | ||
|
|
||
| for executable in executables: | ||
| try: | ||
| subprocess.Popen([executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||
| # No exception. Let's pick this | ||
| return executable | ||
| except OSError as ex: | ||
| LOG.debug("Unable to find executable %s", executable, exc_info=ex) | ||
|
|
||
| raise OSError("Cannot find git, was looking at executables: {}".format(executables)) | ||
elbayaaa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| def clone(self, clone_dir: Path, clone_name: str, replace_existing: bool = False) -> Path: | ||
| """ | ||
| creates a local clone of this Git repository. | ||
| This method is different from the standard Git clone in the following: | ||
| 1. It accepts the path to clone into as a clone_dir (the parent directory to clone in) and a clone_name (The | ||
| name of the local folder) instead of accepting the full path (the join of both) in one parameter | ||
| 2. It removes the "*.git" files/directories so the clone is not a GitRepo any more | ||
| 3. It has the option to replace the local folder(destination) if already exists | ||
|
|
||
| Parameters | ||
| ---------- | ||
| clone_dir: Path | ||
| The directory to create the local clone inside | ||
| clone_name: str | ||
| The dirname of the local clone | ||
| replace_existing: bool | ||
| Whether to replace the current local clone directory if already exists or not | ||
|
|
||
| Returns | ||
| ------- | ||
| The path of the created local clone | ||
elbayaaa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Raises | ||
| ------ | ||
| OSError: | ||
| when file management errors like unable to mkdir, copytree, rmtree ...etc | ||
elbayaaa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| CloneRepoException: | ||
| General errors like for example; if an error occurred while running `git clone` | ||
| or if the local_clone already exists and replace_existing is not set | ||
| CloneRepoUnstableStateException: | ||
| when reaching unstable state, for example with replace_existing flag set, unstable state can happen | ||
| if removed the current local clone but failed to copy the new one from the temp location to the destination | ||
| """ | ||
|
|
||
| GitRepo._ensure_clone_directory_exists(clone_dir=clone_dir) | ||
| # clone to temp then move to the destination(repo_local_path) | ||
| with osutils.mkdir_temp(ignore_errors=True) as tempdir: | ||
| try: | ||
| temp_path = os.path.normpath(os.path.join(tempdir, clone_name)) | ||
| git_executable: str = GitRepo._git_executable() | ||
| LOG.info("\nCloning from %s", self.url) | ||
| subprocess.check_output( | ||
| [git_executable, "clone", self.url, clone_name], | ||
| cwd=tempdir, | ||
| stderr=subprocess.STDOUT, | ||
| ) | ||
| self.local_path = self._persist_local_repo(temp_path, clone_dir, clone_name, replace_existing) | ||
| return self.local_path | ||
| except OSError as ex: | ||
| LOG.warning("WARN: Could not clone repo %s", self.url, exc_info=ex) | ||
| raise | ||
| except subprocess.CalledProcessError as clone_error: | ||
| output = clone_error.output.decode("utf-8") | ||
| if "not found" in output.lower(): | ||
| LOG.warning("WARN: Could not clone repo %s", self.url, exc_info=clone_error) | ||
| raise CloneRepoException from clone_error | ||
| finally: | ||
| self.clone_attempted = True | ||
|
|
||
| @staticmethod | ||
mgrandis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| def _persist_local_repo(temp_path: str, dest_dir: Path, dest_name: str, replace_existing: bool) -> Path: | ||
| dest_path = os.path.normpath(dest_dir.joinpath(dest_name)) | ||
| try: | ||
| if Path(dest_path).exists(): | ||
elbayaaa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if not replace_existing: | ||
| raise CloneRepoException(f"Can not clone to {dest_path}, directory already exist") | ||
| LOG.debug("Removing old repo at %s", dest_path) | ||
| shutil.rmtree(dest_path, onerror=rmtree_callback) | ||
|
|
||
| LOG.debug("Copying from %s to %s", temp_path, dest_path) | ||
| # Todo consider not removing the .git files/directories | ||
| shutil.copytree(temp_path, dest_path, ignore=shutil.ignore_patterns("*.git")) | ||
| return Path(dest_path) | ||
| except (OSError, shutil.Error) as ex: | ||
| # UNSTABLE STATE | ||
| # it's difficult to see how this scenario could happen except weird permissions, user will need to debug | ||
| raise CloneRepoUnstableStateException( | ||
| "Unstable state when updating repo. " | ||
| f"Check that you have permissions to create/delete files in {dest_dir} directory " | ||
| "or file an issue at https://github.com/aws/aws-sam-cli/issues" | ||
| ) from ex | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.