PageRenderTime 79ms CodeModel.GetById 50ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/wpt/harness/wptrunner/update/tree.py

https://gitlab.com/0072016/0072016-SDK-js-sdk-framework-
Python | 385 lines | 357 code | 19 blank | 9 comment | 7 complexity | 4149987be05c06c666f023754ba35c3b MD5 | raw file
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. import os
  5. import re
  6. import subprocess
  7. from .. import vcs
  8. from ..vcs import bind_to_repo, git, hg
  9. def get_unique_name(existing, initial):
  10. """Get a name either equal to initial or of the form initial_N, for some
  11. integer N, that is not in the set existing.
  12. :param existing: Set of names that must not be chosen.
  13. :param initial: Name, or name prefix, to use"""
  14. if initial not in existing:
  15. return initial
  16. for i in xrange(len(existing) + 1):
  17. test = "%s_%s" % (initial, i + 1)
  18. if test not in existing:
  19. return test
  20. assert False
  21. class NoVCSTree(object):
  22. name = "non-vcs"
  23. def __init__(self, root=None):
  24. if root is None:
  25. root = os.path.abspath(os.curdir)
  26. self.root = root
  27. @classmethod
  28. def is_type(cls, path=None):
  29. return True
  30. @property
  31. def is_clean(self):
  32. return True
  33. def add_new(self, prefix=None):
  34. pass
  35. def create_patch(self, patch_name, message):
  36. pass
  37. def update_patch(self, include=None):
  38. pass
  39. def commit_patch(self):
  40. pass
  41. class HgTree(object):
  42. name = "mercurial"
  43. def __init__(self, root=None):
  44. if root is None:
  45. root = hg("root").strip()
  46. self.root = root
  47. self.hg = vcs.bind_to_repo(hg, self.root)
  48. def __getstate__(self):
  49. rv = self.__dict__.copy()
  50. del rv['hg']
  51. return rv
  52. def __setstate__(self, dict):
  53. self.__dict__.update(dict)
  54. self.hg = vcs.bind_to_repo(vcs.hg, self.root)
  55. @classmethod
  56. def is_type(cls, path=None):
  57. kwargs = {"log_error": False}
  58. if path is not None:
  59. kwargs["repo"] = path
  60. try:
  61. hg("root", **kwargs)
  62. except:
  63. return False
  64. return True
  65. @property
  66. def is_clean(self):
  67. return self.hg("status").strip() == ""
  68. def add_new(self, prefix=None):
  69. if prefix is not None:
  70. args = ("-I", prefix)
  71. else:
  72. args = ()
  73. self.hg("add", *args)
  74. def create_patch(self, patch_name, message):
  75. try:
  76. self.hg("qinit", log_error=False)
  77. except subprocess.CalledProcessError:
  78. pass
  79. patch_names = [item.strip() for item in self.hg("qseries").split("\n") if item.strip()]
  80. suffix = 0
  81. test_name = patch_name
  82. while test_name in patch_names:
  83. suffix += 1
  84. test_name = "%s-%i" % (patch_name, suffix)
  85. self.hg("qnew", test_name, "-X", self.root, "-m", message)
  86. def update_patch(self, include=None):
  87. if include is not None:
  88. args = []
  89. for item in include:
  90. args.extend(["-I", item])
  91. else:
  92. args = ()
  93. self.hg("qrefresh", *args)
  94. return True
  95. def commit_patch(self):
  96. self.hg("qfinish")
  97. def contains_commit(self, commit):
  98. try:
  99. self.hg("identify", "-r", commit.sha1)
  100. return True
  101. except subprocess.CalledProcessError:
  102. return False
  103. class GitTree(object):
  104. name = "git"
  105. def __init__(self, root=None):
  106. if root is None:
  107. root = git("rev-parse", "--show-toplevel").strip()
  108. self.root = root
  109. self.git = vcs.bind_to_repo(git, self.root)
  110. self.message = None
  111. self.commit_cls = Commit
  112. def __getstate__(self):
  113. rv = self.__dict__.copy()
  114. del rv['git']
  115. return rv
  116. def __setstate__(self, dict):
  117. self.__dict__.update(dict)
  118. self.git = vcs.bind_to_repo(vcs.git, self.root)
  119. @classmethod
  120. def is_type(cls, path=None):
  121. kwargs = {"log_error": False}
  122. if path is not None:
  123. kwargs["repo"] = path
  124. try:
  125. git("rev-parse", "--show-toplevel", **kwargs)
  126. except:
  127. return False
  128. return True
  129. @property
  130. def rev(self):
  131. """Current HEAD revision"""
  132. if vcs.is_git_root(self.root):
  133. return self.git("rev-parse", "HEAD").strip()
  134. else:
  135. return None
  136. @property
  137. def is_clean(self):
  138. return self.git("status").strip() == ""
  139. def add_new(self, prefix=None):
  140. """Add files to the staging area.
  141. :param prefix: None to include all files or a path prefix to
  142. add all files under that path.
  143. """
  144. if prefix is None:
  145. args = ("-a",)
  146. else:
  147. args = ("--no-ignore-removal", prefix)
  148. self.git("add", *args)
  149. def list_refs(self, ref_filter=None):
  150. """Get a list of sha1, name tuples for references in a repository.
  151. :param ref_filter: Pattern that reference name must match (from the end,
  152. matching whole /-delimited segments only
  153. """
  154. args = []
  155. if ref_filter is not None:
  156. args.append(ref_filter)
  157. data = self.git("show-ref", *args)
  158. rv = []
  159. for line in data.split("\n"):
  160. if not line.strip():
  161. continue
  162. sha1, ref = line.split()
  163. rv.append((sha1, ref))
  164. return rv
  165. def list_remote(self, remote, ref_filter=None):
  166. """Return a list of (sha1, name) tupes for references in a remote.
  167. :param remote: URL of the remote to list.
  168. :param ref_filter: Pattern that the reference name must match.
  169. """
  170. args = []
  171. if ref_filter is not None:
  172. args.append(ref_filter)
  173. data = self.git("ls-remote", remote, *args)
  174. rv = []
  175. for line in data.split("\n"):
  176. if not line.strip():
  177. continue
  178. sha1, ref = line.split()
  179. rv.append((sha1, ref))
  180. return rv
  181. def get_remote_sha1(self, remote, branch):
  182. """Return the SHA1 of a particular branch in a remote.
  183. :param remote: the remote URL
  184. :param branch: the branch name"""
  185. for sha1, ref in self.list_remote(remote, branch):
  186. if ref == "refs/heads/%s" % branch:
  187. return self.commit_cls(self, sha1)
  188. assert False
  189. def create_patch(self, patch_name, message):
  190. # In git a patch is actually a commit
  191. self.message = message
  192. def update_patch(self, include=None):
  193. """Commit the staged changes, or changes to listed files.
  194. :param include: Either None, to commit staged changes, or a list
  195. of filenames (which must already be in the repo)
  196. to commit
  197. """
  198. if include is not None:
  199. args = tuple(include)
  200. else:
  201. args = ()
  202. if self.git("status", "-uno", "-z", *args).strip():
  203. self.git("add", *args)
  204. return True
  205. return False
  206. def commit_patch(self):
  207. assert self.message is not None
  208. if self.git("diff", "--name-only", "--staged", "-z").strip():
  209. self.git("commit", "-m", self.message)
  210. return True
  211. return False
  212. def init(self):
  213. self.git("init")
  214. assert vcs.is_git_root(self.root)
  215. def checkout(self, rev, branch=None, force=False):
  216. """Checkout a particular revision, optionally into a named branch.
  217. :param rev: Revision identifier (e.g. SHA1) to checkout
  218. :param branch: Branch name to use
  219. :param force: Force-checkout
  220. """
  221. args = []
  222. if branch:
  223. branches = [ref[len("refs/heads/"):] for sha1, ref in self.list_refs()
  224. if ref.startswith("refs/heads/")]
  225. branch = get_unique_name(branches, branch)
  226. args += ["-b", branch]
  227. if force:
  228. args.append("-f")
  229. args.append(rev)
  230. self.git("checkout", *args)
  231. def update(self, remote, remote_branch, local_branch):
  232. """Fetch from the remote and checkout into a local branch.
  233. :param remote: URL to the remote repository
  234. :param remote_branch: Branch on the remote repository to check out
  235. :param local_branch: Local branch name to check out into
  236. """
  237. if not vcs.is_git_root(self.root):
  238. self.init()
  239. self.git("clean", "-xdf")
  240. self.git("fetch", remote, "%s:%s" % (remote_branch, local_branch))
  241. self.checkout(local_branch)
  242. self.git("submodule", "update", "--init", "--recursive")
  243. def clean(self):
  244. self.git("checkout", self.rev)
  245. self.git("branch", "-D", self.local_branch)
  246. def paths(self):
  247. """List paths in the tree"""
  248. repo_paths = [self.root] + [os.path.join(self.root, path)
  249. for path in self.submodules()]
  250. rv = []
  251. for repo_path in repo_paths:
  252. paths = vcs.git("ls-tree", "-r", "--name-only", "HEAD", repo=repo_path).split("\n")
  253. rel_path = os.path.relpath(repo_path, self.root)
  254. rv.extend(os.path.join(rel_path, item.strip()) for item in paths if item.strip())
  255. return rv
  256. def submodules(self):
  257. """List submodule directories"""
  258. output = self.git("submodule", "status", "--recursive")
  259. rv = []
  260. for line in output.split("\n"):
  261. line = line.strip()
  262. if not line:
  263. continue
  264. parts = line.split(" ")
  265. rv.append(parts[1])
  266. return rv
  267. def contains_commit(self, commit):
  268. try:
  269. self.git("rev-parse", "--verify", commit.sha1)
  270. return True
  271. except subprocess.CalledProcessError:
  272. return False
  273. class CommitMessage(object):
  274. def __init__(self, text):
  275. self.text = text
  276. self._parse_message()
  277. def __str__(self):
  278. return self.text
  279. def _parse_message(self):
  280. lines = self.text.splitlines()
  281. self.full_summary = lines[0]
  282. self.body = "\n".join(lines[1:])
  283. class Commit(object):
  284. msg_cls = CommitMessage
  285. _sha1_re = re.compile("^[0-9a-f]{40}$")
  286. def __init__(self, tree, sha1):
  287. """Object representing a commit in a specific GitTree.
  288. :param tree: GitTree to which this commit belongs.
  289. :param sha1: Full sha1 string for the commit
  290. """
  291. assert self._sha1_re.match(sha1)
  292. self.tree = tree
  293. self.git = tree.git
  294. self.sha1 = sha1
  295. self.author, self.email, self.message = self._get_meta()
  296. def __getstate__(self):
  297. rv = self.__dict__.copy()
  298. del rv['git']
  299. return rv
  300. def __setstate__(self, dict):
  301. self.__dict__.update(dict)
  302. self.git = self.tree.git
  303. def _get_meta(self):
  304. author, email, message = self.git("show", "-s", "--format=format:%an\n%ae\n%B", self.sha1).split("\n", 2)
  305. return author, email, self.msg_cls(message)