PageRenderTime 46ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/jasy/core/Repository.py

http://github.com/zynga/jasy
Python | 268 lines | 145 code | 90 blank | 33 comment | 42 complexity | 60b3f9290ccc2ae8a6b01c923f902fbc MD5 | raw file
  1. #
  2. # Jasy - Web Tooling Framework
  3. # Copyright 2010-2012 Zynga Inc.
  4. #
  5. import subprocess, os, hashlib, shutil, re, tempfile, sys
  6. from urllib.parse import urlparse
  7. from jasy.core.Logging import *
  8. __all__ = [
  9. "enableRepositoryUpdates", "isRepository", "getRepositoryType", "getRepositoryFolder", "updateRepository",
  10. "getGitBranch"
  11. ]
  12. __nullDevice = open(os.devnull, 'w')
  13. __gitAccountUrl = re.compile("([a-zA-Z0-9-_]+)@([a-zA-Z0-9-_\.]+):([a-zA-Z0-9/_-]+\.git)")
  14. __gitHash = re.compile(r"^[a-f0-9]{40}$")
  15. __versionNumber = re.compile(r"^v?([0-9\.]+)(-?(a|b|rc|alpha|beta)([0-9]+)?)?\+?$")
  16. __branchParser = re.compile("^ref:.*/([a-zA-Z0-9_-]+)$")
  17. __enableUpdates = True
  18. # ======================================================
  19. # PUBLIC API
  20. # ======================================================
  21. def enableRepositoryUpdates(enabled):
  22. global __enableUpdates
  23. __enableUpdates = enabled
  24. def isRepository(url):
  25. # TODO: Support for svn, hg, etc.
  26. return isGitRepositoryUrl(url)
  27. def getRepositoryType(url):
  28. if isGitRepositoryUrl(url):
  29. return "git"
  30. # TODO: Support for svn, hg, etc.
  31. else:
  32. return None
  33. def getRepositoryFolder(url, version=None, kind=None):
  34. if kind == "git" or isGitRepositoryUrl(url):
  35. version = expandGitVersion(version)
  36. folder = url[url.rindex("/")+1:]
  37. if folder.endswith(".git"):
  38. folder = folder[:-4]
  39. identifier = "%s@%s" % (url, version)
  40. version = version[version.rindex("/")+1:]
  41. # TODO: Support for svn, hg, etc.
  42. return "%s-%s-%s" % (folder, version, hashlib.sha1(identifier.encode("utf-8")).hexdigest())
  43. def updateRepository(url, version=None, path=None, update=True):
  44. revision = None
  45. if isGitRepositoryUrl(url):
  46. version = expandGitVersion(version)
  47. revision = updateGitRepository(url, version, path, update)
  48. # TODO: Support for svn, hg, etc.
  49. return revision
  50. # ======================================================
  51. # COMMAND LINE UTILITIES
  52. # ======================================================
  53. def executeCommand(args, msg):
  54. """Executes the given process and outputs message when errors happen."""
  55. debug("Executing command: %s", " ".join(args))
  56. indent()
  57. # Using shell on Windows to resolve binaries like "git"
  58. output = tempfile.TemporaryFile(mode="w+t")
  59. returnValue = subprocess.call(args, stdout=output, stderr=output, shell=sys.platform == "win32")
  60. if returnValue != 0:
  61. raise Exception("Error during executing shell command: %s" % msg)
  62. output.seek(0)
  63. result = output.read().strip("\n\r")
  64. output.close()
  65. for line in result.splitlines():
  66. debug(line)
  67. outdent()
  68. return result
  69. # ======================================================
  70. # GIT SUPPORT
  71. # ======================================================
  72. def updateGitRepository(url, version, path, update=True):
  73. """Clones the given repository URL (optionally with overriding/update features)"""
  74. old = os.getcwd()
  75. if os.path.exists(path) and os.path.exists(os.path.join(path, ".git")):
  76. if not os.path.exists(os.path.join(path, ".git", "HEAD")):
  77. error("Invalid git clone. Cleaning up...")
  78. shutil.rmtree(path)
  79. else:
  80. os.chdir(path)
  81. revision = executeCommand(["git", "rev-parse", "HEAD"], "Could not detect current revision")
  82. if update and (version == "master" or "refs/heads/" in version):
  83. if __enableUpdates:
  84. info("Updating %s", colorize("%s @ " % url, "grey") + colorize(version, "magenta"))
  85. try:
  86. executeCommand(["git", "fetch", "-q", "--depth", "1", "origin", version], "Could not fetch updated revision!")
  87. executeCommand(["git", "reset", "-q", "--hard", "FETCH_HEAD"], "Could not update checkout!")
  88. newRevision = executeCommand(["git", "rev-parse", "HEAD"], "Could not detect current revision")
  89. if revision != newRevision:
  90. indent()
  91. info("Updated from %s to %s", revision[:10], newRevision[:10])
  92. revision = newRevision
  93. outdent()
  94. except Exception:
  95. error("Error during git transaction! Could not update clone.")
  96. error("Please verify that the host is reachable or disable automatic branch updates.")
  97. os.chdir(old)
  98. return
  99. except KeyboardInterrupt:
  100. print()
  101. error("Git transaction was aborted by user!")
  102. os.chdir(old)
  103. return
  104. else:
  105. debug("Updates disabled")
  106. else:
  107. debug("Using existing clone")
  108. os.chdir(old)
  109. return revision
  110. info("Cloning %s", colorize("%s @ " % url, "bold") + colorize(version, "magenta"))
  111. os.makedirs(path)
  112. os.chdir(path)
  113. try:
  114. executeCommand(["git", "init", "."], "Could not initialize GIT repository!")
  115. executeCommand(["git", "remote", "add", "origin", url], "Could not register remote repository!")
  116. executeCommand(["git", "fetch", "-q", "--depth", "1", "origin", version], "Could not fetch revision!")
  117. executeCommand(["git", "reset", "-q", "--hard", "FETCH_HEAD"], "Could not update checkout!")
  118. revision = executeCommand(["git", "rev-parse", "HEAD"], "Could not detect current revision")
  119. except Exception:
  120. error("Error during git transaction! Intitial clone required for continuing!")
  121. error("Please verify that the host is reachable.")
  122. error("Cleaning up...")
  123. os.chdir(old)
  124. shutil.rmtree(path)
  125. return
  126. except KeyboardInterrupt:
  127. print()
  128. error("Git transaction was aborted by user!")
  129. error("Cleaning up...")
  130. os.chdir(old)
  131. shutil.rmtree(path)
  132. return
  133. os.chdir(old)
  134. return revision
  135. def getGitBranch(path=None):
  136. """Returns the name of the git branch"""
  137. if path is None:
  138. path = os.getcwd()
  139. headfile = os.path.join(path, ".git/HEAD")
  140. if not os.path.exists(headfile):
  141. raise Exception("Invalid GIT project path: %s" % path)
  142. match = __branchParser.match(open(headfile).read())
  143. if match is not None:
  144. return match.group(1)
  145. return None
  146. def isGitRepositoryUrl(url):
  147. """Figures out whether the given string is a valid Git repository URL"""
  148. # Detects these urls correctly
  149. # foo => False
  150. # ../bar => False
  151. # https://faz.net?x=1 => False
  152. # git@github.com:zynga/apibrowser.git => True
  153. # https://github.com/zynga/core => True
  154. # https://wpbasti@github.com/zynga/apibrowser.git => True
  155. # git://github.com/zynga/core.git => True
  156. # git://gitorious.org/qt/qtdeclarative.git => True
  157. # https://git.gitorious.org/qt/qtdeclarative.git => True
  158. if not url.endswith(".git"):
  159. return False
  160. parsed = urlparse(url)
  161. if parsed.scheme in ("git", "https"):
  162. return not parsed.params and not parsed.query and not parsed.fragment
  163. elif not parsed.scheme and parsed.path == url and __gitAccountUrl.match(url) != None:
  164. return True
  165. return False
  166. def expandGitVersion(version=None):
  167. if version is None:
  168. version = "master"
  169. if version.startswith("refs/"):
  170. pass
  171. elif re.compile(r"^[a-f0-9]{40}$").match(version):
  172. # See also: http://git.661346.n2.nabble.com/Fetch-by-SHA-missing-td5604552.html
  173. raise Exception("Can't fetch non tags/branches: %s@%s!" % (url, version))
  174. elif __versionNumber.match(version) is not None:
  175. version = "refs/tags/" + version
  176. else:
  177. version = "refs/heads/" + version
  178. return version