gOdoo-cli 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- godoo_cli/__init__.py +2 -0
- godoo_cli/__main__.py +5 -0
- godoo_cli/cli.py +87 -0
- godoo_cli/cli_common.py +183 -0
- godoo_cli/commands/__init__.py +9 -0
- godoo_cli/commands/backup/__init__.py +1 -0
- godoo_cli/commands/backup/cli.py +17 -0
- godoo_cli/commands/backup/load.py +110 -0
- godoo_cli/commands/backup/pull.py +134 -0
- godoo_cli/commands/backup/util.py +35 -0
- godoo_cli/commands/bootstrap.py +187 -0
- godoo_cli/commands/config.py +43 -0
- godoo_cli/commands/db/__init__.py +1 -0
- godoo_cli/commands/db/cli.py +18 -0
- godoo_cli/commands/db/connection.py +94 -0
- godoo_cli/commands/db/passwords.py +56 -0
- godoo_cli/commands/db/query.py +41 -0
- godoo_cli/commands/launch.py +325 -0
- godoo_cli/commands/rpc/__init__.py +8 -0
- godoo_cli/commands/rpc/cli.py +34 -0
- godoo_cli/commands/rpc/config_parameters.py +48 -0
- godoo_cli/commands/rpc/importer.py +70 -0
- godoo_cli/commands/rpc/modules.py +144 -0
- godoo_cli/commands/rpc/translations.py +129 -0
- godoo_cli/commands/shell.py +45 -0
- godoo_cli/commands/source_get.py +279 -0
- godoo_cli/commands/test.py +126 -0
- godoo_cli/git/__init__.py +4 -0
- godoo_cli/git/git_odoo.py +89 -0
- godoo_cli/git/git_odoo_addons.py +95 -0
- godoo_cli/git/git_repo.py +207 -0
- godoo_cli/git/git_url.py +152 -0
- godoo_cli/git/zip_download.py +49 -0
- godoo_cli/helpers/__init__.py +1 -0
- godoo_cli/helpers/bootstrap.py +61 -0
- godoo_cli/helpers/cli.py +15 -0
- godoo_cli/helpers/odoo_files.py +278 -0
- godoo_cli/helpers/odoo_manifest.py +112 -0
- godoo_cli/helpers/system.py +176 -0
- godoo_cli/version.py +1 -0
- godoo_cli-0.7.0.dist-info/LICENSE +165 -0
- godoo_cli-0.7.0.dist-info/METADATA +197 -0
- godoo_cli-0.7.0.dist-info/RECORD +45 -0
- godoo_cli-0.7.0.dist-info/WHEEL +4 -0
- godoo_cli-0.7.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import concurrent.futures
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
from git import Commit
|
|
7
|
+
|
|
8
|
+
from ..helpers.odoo_manifest import update_yml, yaml_roundtrip_loader
|
|
9
|
+
from .git_repo import git_ensure_repo
|
|
10
|
+
from .git_url import GitUrl
|
|
11
|
+
|
|
12
|
+
LOGGER = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def git_ensure_addon_repos(
|
|
16
|
+
root_folder: Path,
|
|
17
|
+
git_yml_path: Path,
|
|
18
|
+
generate_yml_compare_comments: bool = False,
|
|
19
|
+
download_archive: bool = False,
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Clone repos specified in Yml.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
root_folder : Path
|
|
27
|
+
clone target folder
|
|
28
|
+
git_yml_path : Path
|
|
29
|
+
yml describing what to clone
|
|
30
|
+
generate_yml_compare_comments : bool, optional
|
|
31
|
+
wether to add three dot compare on remote to repo urls
|
|
32
|
+
download_archive : bool, optional
|
|
33
|
+
wether to download as .zip (fast but no history), by default False
|
|
34
|
+
"""
|
|
35
|
+
yaml = yaml_roundtrip_loader()
|
|
36
|
+
git_repos = yaml.load(git_yml_path.resolve())
|
|
37
|
+
if _git_clone_addon_repos(root_folder=root_folder, git_repos=git_repos, download_archive=download_archive):
|
|
38
|
+
update_yml(git_repos, generate_yml_compare_comments)
|
|
39
|
+
LOGGER.info("Updating Git Thirdparty Repo Commit hashes")
|
|
40
|
+
yaml.dump(git_repos, git_yml_path)
|
|
41
|
+
|
|
42
|
+
return git_repos
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _git_clone_addon_repos(
|
|
46
|
+
root_folder: Path,
|
|
47
|
+
git_repos: Dict[str, Dict[str, str]],
|
|
48
|
+
download_archive: bool = False,
|
|
49
|
+
) -> Dict[str, Commit]:
|
|
50
|
+
"""
|
|
51
|
+
Clones Git repos specified in dict into Root folder.
|
|
52
|
+
Ensures repo names are prefixed and uses 8 threads to clone.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
root_folder : Path
|
|
57
|
+
clone target folder
|
|
58
|
+
git_repos : Dict[str, Dict[str, str]]
|
|
59
|
+
dict of {parent_folder_name:{"url":clone_url,"branch":clone_branch,"commit":specific_commit_to_clone}}
|
|
60
|
+
branch and commit are optional
|
|
61
|
+
branch defaults to odoo branch from spec file
|
|
62
|
+
download_archive : bool, optional
|
|
63
|
+
wether to download as .zip (fast but no history), by default False
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
Dict[str:Commit]
|
|
67
|
+
dict of {git_src_url:HeadCommit}
|
|
68
|
+
"""
|
|
69
|
+
default_branch = git_repos["odoo"].get("branch")
|
|
70
|
+
LOGGER.info("Cloning Thirdparty Addons source.")
|
|
71
|
+
with concurrent.futures.ThreadPoolExecutor(8) as executor:
|
|
72
|
+
futures = []
|
|
73
|
+
thirdparty_repos = git_repos.get("thirdparty")
|
|
74
|
+
if not thirdparty_repos:
|
|
75
|
+
LOGGER.info("No Thirdparty Key in manifest. Skipping...")
|
|
76
|
+
return
|
|
77
|
+
for prefix, repos in thirdparty_repos.items():
|
|
78
|
+
for repo in repos:
|
|
79
|
+
repo_url = GitUrl(repo["url"])
|
|
80
|
+
name = f"{prefix}_{repo_url.name}"
|
|
81
|
+
futures.append(
|
|
82
|
+
executor.submit(
|
|
83
|
+
git_ensure_repo,
|
|
84
|
+
target_folder=Path(root_folder / name),
|
|
85
|
+
repo_src=repo_url.url,
|
|
86
|
+
branch=repo.get("branch", default_branch),
|
|
87
|
+
commit=repo.get("commit"),
|
|
88
|
+
zip_mode=download_archive,
|
|
89
|
+
filter="blob:none",
|
|
90
|
+
single_branch=True,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
clone_results = [f.result() for f in futures]
|
|
94
|
+
clone_results = {r[0]: r[1] for r in clone_results if r}
|
|
95
|
+
return clone_results
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
""" Module to provide GIT interaction."""
|
|
2
|
+
import logging
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from git import GitCommandError, InvalidGitRepositoryError, Repo
|
|
7
|
+
|
|
8
|
+
from .git_url import GitUrl
|
|
9
|
+
from .zip_download import git_download_zip
|
|
10
|
+
|
|
11
|
+
LOGGER = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _git_clean_clone(repo_src: str, target_folder: Path, **kwargs):
|
|
15
|
+
"""Clears targetfolder and does a clean clone_from
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
repo_src : str
|
|
20
|
+
Clone url
|
|
21
|
+
target_folder : Path
|
|
22
|
+
Download folder
|
|
23
|
+
branch : str
|
|
24
|
+
branch to clone
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
_type_re
|
|
29
|
+
_description_
|
|
30
|
+
"""
|
|
31
|
+
LOGGER.debug("Cloning Repo: %s, to '%s', Kwargs: '%s'", repo_src, target_folder, kwargs)
|
|
32
|
+
if not isinstance(target_folder, Path):
|
|
33
|
+
target_folder = Path(target_folder)
|
|
34
|
+
if target_folder.exists():
|
|
35
|
+
LOGGER.debug("Clearing Repo folder: %s", target_folder)
|
|
36
|
+
shutil.rmtree(target_folder)
|
|
37
|
+
return Repo.clone_from(repo_src, target_folder, **kwargs)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def git_pull_checkout_reset(
|
|
41
|
+
repo: Repo, branch: str = "master", commit: str = "", pull: str = "", reset_hard: bool = True
|
|
42
|
+
):
|
|
43
|
+
"""
|
|
44
|
+
Ensure git Repo is on specific branch and commit.
|
|
45
|
+
Will unshallow if neccessary
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
repo : Repo
|
|
50
|
+
Git Repo
|
|
51
|
+
branch : str, optional
|
|
52
|
+
branch to checkout, by default "master"
|
|
53
|
+
commit : str, optional
|
|
54
|
+
specific commitSHA to checkout, by default ""
|
|
55
|
+
pull : str, optional
|
|
56
|
+
specific target to pull. Usually used in conjunction with branch.
|
|
57
|
+
reset_hard : bool, optional
|
|
58
|
+
will call reset --hard before checkout to ensure clean git repo, by default True
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
if reset_hard:
|
|
62
|
+
repo.git.reset("--hard", "HEAD")
|
|
63
|
+
|
|
64
|
+
if pull:
|
|
65
|
+
LOGGER.debug("Pulling Repo: %s, %s", repo.working_dir, pull)
|
|
66
|
+
try:
|
|
67
|
+
repo.remotes[0].pull(pull)
|
|
68
|
+
except GitCommandError as e:
|
|
69
|
+
if "fatal: refusing to merge unrelated histories" in e.stderr:
|
|
70
|
+
clone_kwargs = {}
|
|
71
|
+
if branch:
|
|
72
|
+
clone_kwargs["branch"] = branch
|
|
73
|
+
repo = _git_clean_clone(repo.remotes[0].url, repo.working_dir, **clone_kwargs)
|
|
74
|
+
else:
|
|
75
|
+
raise e
|
|
76
|
+
if commit:
|
|
77
|
+
if str(repo.head.commit) != str(commit):
|
|
78
|
+
LOGGER.debug("Checking out %s to Commit: %s", repo.git_dir, commit)
|
|
79
|
+
repo.git.checkout(commit)
|
|
80
|
+
return
|
|
81
|
+
if branch:
|
|
82
|
+
LOGGER.debug("Checking Out repo %s to Branch: %s", repo.git_dir, branch)
|
|
83
|
+
repo.git.checkout(branch)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def git_ensure_ref(
|
|
87
|
+
target_folder: Path,
|
|
88
|
+
repo_src: str,
|
|
89
|
+
branch: str = "master",
|
|
90
|
+
commit: str = "",
|
|
91
|
+
pull: str = "",
|
|
92
|
+
**kwargs,
|
|
93
|
+
):
|
|
94
|
+
"""
|
|
95
|
+
Clone a git Repo and ensure its HEAD is set to Branch and commit.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
target_folder : Path
|
|
101
|
+
"target folder name"
|
|
102
|
+
repo_src : str
|
|
103
|
+
"repo source url"
|
|
104
|
+
commit : str, optional
|
|
105
|
+
commit on which to set the head
|
|
106
|
+
pull : str, optional
|
|
107
|
+
specific target to pull. Usually used in conjunction with branch.
|
|
108
|
+
branch : str, optional
|
|
109
|
+
branch on which to set head, by default 'master'
|
|
110
|
+
**kwargs
|
|
111
|
+
get passed to git clone
|
|
112
|
+
"""
|
|
113
|
+
LOGGER.info("Ensuring Repo '%s' --> '%s'", repo_src, target_folder)
|
|
114
|
+
target_folder.mkdir(exist_ok=True, parents=True)
|
|
115
|
+
try:
|
|
116
|
+
repo = Repo(target_folder)
|
|
117
|
+
current = str(repo.head.commit)
|
|
118
|
+
|
|
119
|
+
if not pull and commit and str(commit) != str(current):
|
|
120
|
+
pull = str(commit)
|
|
121
|
+
|
|
122
|
+
if not pull and branch and not commit:
|
|
123
|
+
remote_name = str(repo.remotes[0].name)
|
|
124
|
+
try:
|
|
125
|
+
remote_commit = repo.git.rev_parse(remote_name + "/" + str(branch))
|
|
126
|
+
except Exception:
|
|
127
|
+
remote_commit = "unknown"
|
|
128
|
+
LOGGER.debug(
|
|
129
|
+
"Repo: %s comparing local head '%s' with remote head '%s'", repo.working_dir, current, remote_commit
|
|
130
|
+
)
|
|
131
|
+
if str(remote_commit) != current:
|
|
132
|
+
pull = str(branch)
|
|
133
|
+
|
|
134
|
+
git_pull_checkout_reset(repo=repo, branch=branch, commit=commit, pull=pull)
|
|
135
|
+
|
|
136
|
+
if current == str(repo.head.commit):
|
|
137
|
+
LOGGER.info("Repo Commit matches. Skipping: '%s' --> '%s'", repo_src, target_folder)
|
|
138
|
+
else:
|
|
139
|
+
LOGGER.info("Pulled Repo from: '%s'. Head is now at: %s", repo_src, repo.head.commit)
|
|
140
|
+
except InvalidGitRepositoryError:
|
|
141
|
+
repo = _git_clean_clone(repo_src, target_folder, branch=branch, **kwargs)
|
|
142
|
+
git_pull_checkout_reset(repo=repo, branch=branch, commit=commit, pull=False)
|
|
143
|
+
|
|
144
|
+
LOGGER.info(
|
|
145
|
+
"Cloned Repo: '%s'. Branch='%s' Head='%s'",
|
|
146
|
+
repo_src,
|
|
147
|
+
repo.active_branch.name if not repo.head.is_detached else "Detached",
|
|
148
|
+
repo.head.commit,
|
|
149
|
+
)
|
|
150
|
+
return repo_src, repo.head.commit
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def git_ensure_repo(
|
|
154
|
+
target_folder: Path,
|
|
155
|
+
repo_src: str,
|
|
156
|
+
branch: str = "master",
|
|
157
|
+
commit: str = "",
|
|
158
|
+
pull: str = "",
|
|
159
|
+
zip_mode: bool = False,
|
|
160
|
+
**kwargs,
|
|
161
|
+
):
|
|
162
|
+
"""Ensures git remote contents are in folder.
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
target_folder : Path
|
|
167
|
+
Folder to clone/download into
|
|
168
|
+
repo_src : str
|
|
169
|
+
Git Remote Url
|
|
170
|
+
branch : str, optional
|
|
171
|
+
target branch, by default "master"
|
|
172
|
+
commit : str, optional
|
|
173
|
+
target ref, by default ""
|
|
174
|
+
pull : str, optional
|
|
175
|
+
ref to pull, by default ""
|
|
176
|
+
zip_mode : bool, optional
|
|
177
|
+
wether to download a zip (fast but no commit history) or clone and fetch (fully working git clone), by default False
|
|
178
|
+
**kwargs
|
|
179
|
+
get passed to git clone
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
if isinstance(target_folder, str):
|
|
183
|
+
target_folder = Path(target_folder)
|
|
184
|
+
|
|
185
|
+
if target_folder.exists() and any(target_folder.iterdir()) and not target_folder.glob(".git"):
|
|
186
|
+
LOGGER.info("Assuming '%s' got pulled in .Zip mode. Skipping Clone and commit check.", target_folder)
|
|
187
|
+
zip_mode = True
|
|
188
|
+
|
|
189
|
+
if zip_mode and GitUrl(repo_src).url_type == "ssh":
|
|
190
|
+
LOGGER.info("Zip downloading currently not supported for SSH type Urls")
|
|
191
|
+
zip_mode = False
|
|
192
|
+
|
|
193
|
+
if zip_mode:
|
|
194
|
+
return git_download_zip(
|
|
195
|
+
target_folder=target_folder,
|
|
196
|
+
repo_url=repo_src,
|
|
197
|
+
branch=branch,
|
|
198
|
+
commit=commit,
|
|
199
|
+
)
|
|
200
|
+
return git_ensure_ref(
|
|
201
|
+
target_folder=target_folder,
|
|
202
|
+
repo_src=repo_src,
|
|
203
|
+
branch=branch,
|
|
204
|
+
commit=commit,
|
|
205
|
+
pull=pull,
|
|
206
|
+
**kwargs,
|
|
207
|
+
)
|
godoo_cli/git/git_url.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GitRemoteType(Enum):
|
|
7
|
+
gitlab = "gitlab"
|
|
8
|
+
github = "github"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GitUrl:
|
|
12
|
+
"""Class to Structurize Git URLs.
|
|
13
|
+
Works with SSH and HTTP(s) URLs
|
|
14
|
+
Can generate Compare Urls
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
url: str
|
|
18
|
+
url_type: Literal["http", "ssh"]
|
|
19
|
+
domain: str
|
|
20
|
+
path: str
|
|
21
|
+
user: str
|
|
22
|
+
port: int
|
|
23
|
+
name: str
|
|
24
|
+
|
|
25
|
+
def __init__(self, url: str) -> None:
|
|
26
|
+
self.url = url
|
|
27
|
+
if "http" in url:
|
|
28
|
+
http_regex = r"(?P<schema>https?):\/\/(?P<domain>[^\/]+)(?P<path>.*)"
|
|
29
|
+
http_match = re.search(http_regex, url)
|
|
30
|
+
self.url_type = http_match.group("schema")
|
|
31
|
+
self.domain = http_match.group("domain")
|
|
32
|
+
self.path = http_match.group("path")
|
|
33
|
+
else:
|
|
34
|
+
ssh_regex = r"(?P<user>\w+)@(?P<domain>[^:]+):(?:(?P<port>\d+)]?:)?(?P<path>.*)"
|
|
35
|
+
ssh_match = re.search(ssh_regex, url)
|
|
36
|
+
self.url_type = "ssh"
|
|
37
|
+
self.domain = ssh_match.group("domain")
|
|
38
|
+
self.path = ssh_match.group("path")
|
|
39
|
+
self.user = ssh_match.group("user")
|
|
40
|
+
self.port = ssh_match.group("port")
|
|
41
|
+
|
|
42
|
+
if self.path.endswith(".git"): # Todo Python 3.9 Use Removesuffix and RemovePrefix
|
|
43
|
+
self.path = self.path[:-4]
|
|
44
|
+
if self.path.endswith("/"):
|
|
45
|
+
self.path = self.path[:-1]
|
|
46
|
+
if self.path.startswith("/"):
|
|
47
|
+
self.path = self.path[1:]
|
|
48
|
+
self.name = self.path.split("/")[-1]
|
|
49
|
+
|
|
50
|
+
def _clean_http_url(self) -> str:
|
|
51
|
+
"""Return HTTPs URL without .git suffix.
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
str
|
|
56
|
+
Https:// url
|
|
57
|
+
"""
|
|
58
|
+
return f"https://{self.domain}/{self.path}"
|
|
59
|
+
|
|
60
|
+
def _git_type(self) -> GitRemoteType:
|
|
61
|
+
"""Get git Remote type.
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
Literal
|
|
66
|
+
"github", "gitlab"
|
|
67
|
+
|
|
68
|
+
Raises
|
|
69
|
+
------
|
|
70
|
+
ValueError
|
|
71
|
+
If Type cannot be determined.
|
|
72
|
+
"""
|
|
73
|
+
if "gitlab" in self.domain:
|
|
74
|
+
return GitRemoteType.gitlab
|
|
75
|
+
elif "github" in self.domain:
|
|
76
|
+
return GitRemoteType.github
|
|
77
|
+
else:
|
|
78
|
+
raise ValueError(f"Cant get Git Service type from {self.domain}")
|
|
79
|
+
|
|
80
|
+
def get_compare_url(self, from_compare: str, to_compare: str) -> str:
|
|
81
|
+
"""Get Compare url between two Refs.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
from_compare : str
|
|
86
|
+
from compare ref
|
|
87
|
+
to_compare : str
|
|
88
|
+
to compare ref
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
str
|
|
93
|
+
Compare Url Like: https://github.com/odoo/odoo/compare/<commitSHA>...<branchName>
|
|
94
|
+
"""
|
|
95
|
+
remote_type = self._git_type()
|
|
96
|
+
if from_compare == to_compare:
|
|
97
|
+
return # Nothing to Compare here
|
|
98
|
+
http_url = self._clean_http_url()
|
|
99
|
+
if remote_type in [GitRemoteType.github, GitRemoteType.gitlab]:
|
|
100
|
+
return f"{http_url}/compare/{from_compare}...{to_compare}"
|
|
101
|
+
|
|
102
|
+
def get_archive_url(self, ref: str) -> str:
|
|
103
|
+
"""Get Download Url for Zip file.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
branch : str, optional
|
|
108
|
+
Repo Branch, by default ""
|
|
109
|
+
commit : str, optional
|
|
110
|
+
Repo download ref, by default ""
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
str
|
|
115
|
+
Url from which to download a zip file
|
|
116
|
+
|
|
117
|
+
Raises
|
|
118
|
+
------
|
|
119
|
+
ValueError
|
|
120
|
+
if ref is not specified
|
|
121
|
+
"""
|
|
122
|
+
if not ref:
|
|
123
|
+
raise ValueError("Missing either download ref (e.g. branch or commit) to generate Archive URL.")
|
|
124
|
+
http_url = self._clean_http_url()
|
|
125
|
+
remote_type = self._git_type()
|
|
126
|
+
if remote_type == GitRemoteType.github:
|
|
127
|
+
return f"{http_url}/archive/{ref }.zip"
|
|
128
|
+
if remote_type == GitRemoteType.gitlab:
|
|
129
|
+
return f"{http_url}/-/archive/{ref}/{self.name}.zip"
|
|
130
|
+
|
|
131
|
+
def get_file_raw_url(self, ref: str, file_path: str) -> str:
|
|
132
|
+
"""Gets the URL Pointing to the Raw file contents on the Remote.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
ref : str
|
|
137
|
+
Branch, Commit, Tag ,...
|
|
138
|
+
file_path : str
|
|
139
|
+
Relative file path in Repository
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
str
|
|
144
|
+
URL Pointing to the Raw file contents on the Remote
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
http_url = self._clean_http_url()
|
|
148
|
+
remote_type = self._git_type()
|
|
149
|
+
if remote_type == GitRemoteType.github:
|
|
150
|
+
return f"{http_url.replace(self.domain,'raw.githubusercontent.com')}/{ref}/{file_path}"
|
|
151
|
+
if remote_type == GitRemoteType.gitlab:
|
|
152
|
+
return f"{http_url}/-/raw/{ref}/{file_path}"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import shutil
|
|
3
|
+
import tempfile
|
|
4
|
+
import zipfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ..helpers.system import download_file
|
|
8
|
+
from .git_url import GitUrl
|
|
9
|
+
|
|
10
|
+
LOGGER = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def git_download_zip(repo_url: str, target_folder: Path, branch: str, commit: str = ""):
|
|
14
|
+
"""Download Repo Zip from Github.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
repo_url : str
|
|
19
|
+
Github Repo Url
|
|
20
|
+
target_folder : Path
|
|
21
|
+
Download Target
|
|
22
|
+
branch : str
|
|
23
|
+
BRanch to download
|
|
24
|
+
commit : str, optional
|
|
25
|
+
Specific Commit to download, by default ""
|
|
26
|
+
|
|
27
|
+
Raises
|
|
28
|
+
------
|
|
29
|
+
FileNotFoundError
|
|
30
|
+
If Download failed
|
|
31
|
+
"""
|
|
32
|
+
git_url = GitUrl(repo_url)
|
|
33
|
+
download_url = git_url.get_archive_url(ref=commit or branch)
|
|
34
|
+
with tempfile.TemporaryDirectory() as tdir:
|
|
35
|
+
zip_path = Path(tdir) / f"{git_url.name}.zip"
|
|
36
|
+
LOGGER.info("Downloading GitRepo Zip: '%s'", download_url)
|
|
37
|
+
LOGGER.debug("Target Path: '%s' ", zip_path)
|
|
38
|
+
download_file(download_url, zip_path)
|
|
39
|
+
if not zip_path.exists():
|
|
40
|
+
raise FileNotFoundError(f"Could not download Repo Zip from: {download_url}")
|
|
41
|
+
LOGGER.info("Extracting GitRepo Zip: %s", git_url.name)
|
|
42
|
+
ex_location = Path(tdir) / "extract"
|
|
43
|
+
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
|
44
|
+
zip_ref.extractall(ex_location)
|
|
45
|
+
for path in ex_location.glob("*"):
|
|
46
|
+
LOGGER.info("Moving %s to %s", path.stem, target_folder)
|
|
47
|
+
shutil.rmtree(target_folder, ignore_errors=True)
|
|
48
|
+
path.rename(target_folder)
|
|
49
|
+
break
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Provides Various Helper Functions for Odoo"""
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
import subprocess
|
|
4
|
+
from ast import literal_eval
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
from .odoo_files import _get_python_requirements_of_modules
|
|
9
|
+
from .system import run_cmd
|
|
10
|
+
|
|
11
|
+
LOGGER = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _install_py_reqs_for_modules(addon_paths: List[Path], module_names: List[str] = None):
|
|
15
|
+
"""Install Python Requirements mentioned in odoo module manifests of given modules
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
addon_paths : List[Path]
|
|
20
|
+
odoo-bin addons path
|
|
21
|
+
module_names : List[str], Optional
|
|
22
|
+
List of modules to filter for, by default "all available modules"
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
CompletedProcess
|
|
27
|
+
"""
|
|
28
|
+
py_reqs = _get_python_requirements_of_modules(addon_paths=addon_paths, filter_module_names=module_names)
|
|
29
|
+
if py_reqs:
|
|
30
|
+
installed_packages = run_cmd(
|
|
31
|
+
"pip list --format json --disable-pip-version-check", check=True, shell=True, stdout=subprocess.PIPE
|
|
32
|
+
).stdout.decode("utf-8")
|
|
33
|
+
installed_packages = [m.get("name") for m in literal_eval(installed_packages)]
|
|
34
|
+
if missing_packages := [p for p in py_reqs if p not in installed_packages]:
|
|
35
|
+
LOGGER.info("Installing Python requirements: %s", missing_packages)
|
|
36
|
+
res = run_cmd(f"pip install {' '.join(missing_packages)} --disable-pip-version-check", shell=True)
|
|
37
|
+
if res.returncode != 0:
|
|
38
|
+
raise FileNotFoundError("Pip installation error at: %s" % missing_packages)
|
|
39
|
+
return res
|
|
40
|
+
LOGGER.debug("No py Requirements to be installed")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _install_py_reqs_by_odoo_cmd(addon_paths: List[Path], odoo_bin_cmd: str):
|
|
44
|
+
"""Install Python reqs for modules mentioned in odoo-bin commandline --init or -i directives.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
addon_paths : List[Path]
|
|
49
|
+
odoo-bin addons-path
|
|
50
|
+
odoo_bin_cmd : str
|
|
51
|
+
odoo-bin commandline
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
CompletedProcess
|
|
56
|
+
"""
|
|
57
|
+
install_modules = []
|
|
58
|
+
for m in re.finditer(r'(--init|-i) "?([^ \n]+)"?', odoo_bin_cmd):
|
|
59
|
+
install_modules += m.group(2).split(",")
|
|
60
|
+
if install_modules:
|
|
61
|
+
return _install_py_reqs_for_modules(addon_paths=addon_paths, module_names=install_modules)
|
godoo_cli/helpers/cli.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
LOGGER = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def check_dangerous_command():
|
|
8
|
+
isdev = str(os.getenv("WORKSPACE_IS_DEV"))
|
|
9
|
+
if not isdev.lower() == "true":
|
|
10
|
+
LOGGER.warning(
|
|
11
|
+
"""This function is dangerous in Production environments.
|
|
12
|
+
Please set 'WORKSPACE_IS_DEV=true' as environment Variable to continue
|
|
13
|
+
"""
|
|
14
|
+
)
|
|
15
|
+
exit(1)
|