Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

306 linhas
8.8 KiB

  1. # imports - standard imports
  2. import os
  3. import pathlib
  4. import re
  5. import sys
  6. import subprocess
  7. from typing import List, Optional
  8. from functools import lru_cache
  9. # imports - module imports
  10. from bench.exceptions import (
  11. InvalidRemoteException,
  12. InvalidBranchException,
  13. CommandFailedError,
  14. VersionNotFound,
  15. )
  16. from bench.app import get_repo_dir
  17. def is_version_upgrade(app="xhiveframework", bench_path=".", branch=None):
  18. upstream_version = get_upstream_version(app=app, branch=branch, bench_path=bench_path)
  19. if not upstream_version:
  20. raise InvalidBranchException(
  21. f"Specified branch of app {app} is not in upstream remote"
  22. )
  23. local_version = get_major_version(get_current_version(app, bench_path=bench_path))
  24. upstream_version = get_major_version(upstream_version)
  25. if upstream_version > local_version:
  26. return (True, local_version, upstream_version)
  27. return (False, local_version, upstream_version)
  28. def switch_branch(branch, apps=None, bench_path=".", upgrade=False, check_upgrade=True):
  29. import git
  30. from bench.bench import Bench
  31. from bench.utils import log, exec_cmd
  32. from bench.utils.bench import (
  33. build_assets,
  34. patch_sites,
  35. post_upgrade,
  36. )
  37. from bench.utils.system import backup_all_sites
  38. apps_dir = os.path.join(bench_path, "apps")
  39. version_upgrade = (False,)
  40. switched_apps = []
  41. if not apps:
  42. apps = [
  43. name for name in os.listdir(apps_dir) if os.path.isdir(os.path.join(apps_dir, name))
  44. ]
  45. for app in apps:
  46. app_dir = os.path.join(apps_dir, app)
  47. if not os.path.exists(app_dir):
  48. log(f"{app} does not exist!", level=2)
  49. continue
  50. repo = git.Repo(app_dir)
  51. unshallow_flag = os.path.exists(os.path.join(app_dir, ".git", "shallow"))
  52. log(f"Fetching upstream {'unshallow ' if unshallow_flag else ''}for {app}")
  53. exec_cmd("git remote set-branches upstream '*'", cwd=app_dir)
  54. exec_cmd(
  55. f"git fetch --all{' --unshallow' if unshallow_flag else ''} --quiet", cwd=app_dir
  56. )
  57. if check_upgrade:
  58. version_upgrade = is_version_upgrade(app=app, bench_path=bench_path, branch=branch)
  59. if version_upgrade[0] and not upgrade:
  60. log(
  61. f"Switching to {branch} will cause upgrade from"
  62. f" {version_upgrade[1]} to {version_upgrade[2]}. Pass --upgrade to"
  63. " confirm",
  64. level=2,
  65. )
  66. sys.exit(1)
  67. print("Switching for " + app)
  68. exec_cmd(f"git checkout -f {branch}", cwd=app_dir)
  69. if str(repo.active_branch) == branch:
  70. switched_apps.append(app)
  71. else:
  72. log(f"Switching branches failed for: {app}", level=2)
  73. if switched_apps:
  74. log(f"Successfully switched branches for: {', '.join(switched_apps)}", level=1)
  75. print(
  76. "Please run `bench update --patch` to be safe from any differences in"
  77. " database schema"
  78. )
  79. if version_upgrade[0] and upgrade:
  80. Bench(bench_path).setup.requirements()
  81. backup_all_sites()
  82. patch_sites()
  83. build_assets()
  84. post_upgrade(version_upgrade[1], version_upgrade[2])
  85. def switch_to_branch(branch=None, apps=None, bench_path=".", upgrade=False):
  86. switch_branch(branch, apps=apps, bench_path=bench_path, upgrade=upgrade)
  87. def switch_to_develop(apps=None, bench_path=".", upgrade=True):
  88. switch_branch("develop", apps=apps, bench_path=bench_path, upgrade=upgrade)
  89. def get_version_from_string(contents, field="__version__"):
  90. match = re.search(
  91. r"^(\s*%s\s*=\s*['\\\"])(.+?)(['\"])" % field, contents, flags=(re.S | re.M)
  92. )
  93. if not match:
  94. raise VersionNotFound(f"{contents} is not a valid version")
  95. return match.group(2)
  96. def get_major_version(version):
  97. import semantic_version
  98. return semantic_version.Version(version).major
  99. def get_develop_version(app, bench_path="."):
  100. repo_dir = get_repo_dir(app, bench_path=bench_path)
  101. with open(os.path.join(repo_dir, os.path.basename(repo_dir), "hooks.py")) as f:
  102. return get_version_from_string(f.read(), field="develop_version")
  103. def get_upstream_version(app, branch=None, bench_path="."):
  104. repo_dir = get_repo_dir(app, bench_path=bench_path)
  105. if not branch:
  106. branch = get_current_branch(app, bench_path=bench_path)
  107. try:
  108. subprocess.call(
  109. f"git fetch --depth=1 --no-tags upstream {branch}", shell=True, cwd=repo_dir
  110. )
  111. except CommandFailedError:
  112. raise InvalidRemoteException(f"Failed to fetch from remote named upstream for {app}")
  113. try:
  114. contents = subprocess.check_output(
  115. f"git show upstream/{branch}:{app}/__init__.py",
  116. shell=True,
  117. cwd=repo_dir,
  118. stderr=subprocess.STDOUT,
  119. )
  120. contents = contents.decode("utf-8")
  121. except subprocess.CalledProcessError as e:
  122. if b"Invalid object" in e.output:
  123. return None
  124. else:
  125. raise
  126. return get_version_from_string(contents)
  127. def get_current_xhiveframework_version(bench_path="."):
  128. try:
  129. return get_major_version(get_current_version("xhiveframework", bench_path=bench_path))
  130. except OSError:
  131. return 0
  132. def get_current_branch(app, bench_path="."):
  133. from bench.utils import get_cmd_output
  134. repo_dir = get_repo_dir(app, bench_path=bench_path)
  135. return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir)
  136. @lru_cache(maxsize=5)
  137. def get_required_deps(org, name, branch, deps="hooks.py"):
  138. import requests
  139. import base64
  140. git_api_url = f"https://api.github.com/repos/{org}/{name}/contents/{name}/{deps}"
  141. params = {"ref": branch or "develop"}
  142. res = requests.get(url=git_api_url, params=params).json()
  143. if "message" in res:
  144. git_url = (
  145. f"https://raw.githubusercontent.com/{org}/{name}/{params['ref']}/{name}/{deps}"
  146. )
  147. return requests.get(git_url).text
  148. return base64.decodebytes(res["content"].encode()).decode()
  149. def required_apps_from_hooks(required_deps: str, local: bool = False) -> List:
  150. import ast
  151. required_apps_re = re.compile(r"required_apps\s+=\s+(.*)")
  152. if local:
  153. required_deps = pathlib.Path(required_deps).read_text()
  154. _req_apps_tag = required_apps_re.search(required_deps)
  155. req_apps_tag = _req_apps_tag[1]
  156. return ast.literal_eval(req_apps_tag)
  157. def get_remote(app, bench_path="."):
  158. repo_dir = get_repo_dir(app, bench_path=bench_path)
  159. contents = subprocess.check_output(
  160. ["git", "remote", "-v"], cwd=repo_dir, stderr=subprocess.STDOUT
  161. )
  162. contents = contents.decode("utf-8")
  163. if re.findall(r"upstream[\s]+", contents):
  164. return "upstream"
  165. elif not contents:
  166. # if contents is an empty string => remote doesn't exist
  167. return False
  168. else:
  169. # get the first remote
  170. return contents.splitlines()[0].split()[0]
  171. def get_app_name(bench_path: str, folder_name: str) -> str:
  172. """Retrieves `name` attribute of app - equivalent to distribution name
  173. of python package. Fetches from pyproject.toml, setup.cfg or setup.py
  174. whichever defines it in that order.
  175. """
  176. app_name = None
  177. apps_path = os.path.join(os.path.abspath(bench_path), "apps")
  178. config_py_path = os.path.join(apps_path, folder_name, "setup.cfg")
  179. setup_py_path = os.path.join(apps_path, folder_name, "setup.py")
  180. pyproject_path = os.path.join(apps_path, folder_name, "pyproject.toml")
  181. pyproject = get_pyproject(pyproject_path)
  182. if pyproject:
  183. app_name = pyproject.get("project", {}).get("name")
  184. if not app_name and os.path.exists(config_py_path):
  185. from setuptools.config import read_configuration
  186. config = read_configuration(config_py_path)
  187. app_name = config.get("metadata", {}).get("name")
  188. if not app_name:
  189. # retrieve app name from setup.py as fallback
  190. with open(setup_py_path, "rb") as f:
  191. app_name = re.search(r'name\s*=\s*[\'"](.*)[\'"]', f.read().decode("utf-8"))[1]
  192. if app_name and folder_name != app_name:
  193. os.rename(os.path.join(apps_path, folder_name), os.path.join(apps_path, app_name))
  194. return app_name
  195. return folder_name
  196. def get_pyproject(pyproject_path: str) -> Optional[dict]:
  197. if not os.path.exists(pyproject_path):
  198. return None
  199. try:
  200. from tomli import load
  201. except ImportError:
  202. from tomllib import load
  203. with open(pyproject_path, "rb") as f:
  204. return load(f)
  205. def check_existing_dir(bench_path, repo_name):
  206. cloned_path = os.path.join(bench_path, "apps", repo_name)
  207. dir_already_exists = os.path.isdir(cloned_path)
  208. return dir_already_exists, cloned_path
  209. def get_current_version(app, bench_path="."):
  210. current_version = None
  211. repo_dir = get_repo_dir(app, bench_path=bench_path)
  212. pyproject_path = os.path.join(repo_dir, "pyproject.toml")
  213. config_path = os.path.join(repo_dir, "setup.cfg")
  214. init_path = os.path.join(repo_dir, os.path.basename(repo_dir), "__init__.py")
  215. setup_path = os.path.join(repo_dir, "setup.py")
  216. try:
  217. pyproject = get_pyproject(pyproject_path)
  218. if pyproject:
  219. current_version = pyproject.get("project", {}).get("version")
  220. if not current_version and os.path.exists(config_path):
  221. from setuptools.config import read_configuration
  222. config = read_configuration(config_path)
  223. current_version = config.get("metadata", {}).get("version")
  224. if not current_version:
  225. with open(init_path) as f:
  226. current_version = get_version_from_string(f.read())
  227. except (AttributeError, VersionNotFound):
  228. # backward compatibility
  229. with open(setup_path) as f:
  230. current_version = get_version_from_string(f.read(), field="version")
  231. return current_version