git_command.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. #
  2. # Copyright (C) 2008 The Android Open Source Project
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import os
  16. import sys
  17. import subprocess
  18. import tempfile
  19. from signal import SIGTERM
  20. from error import GitError
  21. from trace import REPO_TRACE, IsTrace, Trace
  22. GIT = 'git'
  23. MIN_GIT_VERSION = (1, 5, 4)
  24. GIT_DIR = 'GIT_DIR'
  25. LAST_GITDIR = None
  26. LAST_CWD = None
  27. _ssh_proxy_path = None
  28. _ssh_sock_path = None
  29. _ssh_clients = []
  30. def ssh_sock(create=True):
  31. global _ssh_sock_path
  32. if _ssh_sock_path is None:
  33. if not create:
  34. return None
  35. dir = '/tmp'
  36. if not os.path.exists(dir):
  37. dir = tempfile.gettempdir()
  38. _ssh_sock_path = os.path.join(
  39. tempfile.mkdtemp('', 'ssh-', dir),
  40. 'master-%r@%h:%p')
  41. return _ssh_sock_path
  42. def _ssh_proxy():
  43. global _ssh_proxy_path
  44. if _ssh_proxy_path is None:
  45. _ssh_proxy_path = os.path.join(
  46. os.path.dirname(__file__),
  47. 'git_ssh')
  48. return _ssh_proxy_path
  49. def _add_ssh_client(p):
  50. _ssh_clients.append(p)
  51. def _remove_ssh_client(p):
  52. try:
  53. _ssh_clients.remove(p)
  54. except ValueError:
  55. pass
  56. def terminate_ssh_clients():
  57. global _ssh_clients
  58. for p in _ssh_clients:
  59. try:
  60. os.kill(p.pid, SIGTERM)
  61. p.wait()
  62. except OSError:
  63. pass
  64. _ssh_clients = []
  65. _git_version = None
  66. class _GitCall(object):
  67. def version(self):
  68. p = GitCommand(None, ['--version'], capture_stdout=True)
  69. if p.Wait() == 0:
  70. return p.stdout
  71. return None
  72. def version_tuple(self):
  73. global _git_version
  74. if _git_version is None:
  75. ver_str = git.version()
  76. if ver_str.startswith('git version '):
  77. _git_version = tuple(
  78. map(lambda x: int(x),
  79. ver_str[len('git version '):].strip().split('.')[0:3]
  80. ))
  81. else:
  82. print >>sys.stderr, 'fatal: "%s" unsupported' % ver_str
  83. sys.exit(1)
  84. return _git_version
  85. def __getattr__(self, name):
  86. name = name.replace('_','-')
  87. def fun(*cmdv):
  88. command = [name]
  89. command.extend(cmdv)
  90. return GitCommand(None, command).Wait() == 0
  91. return fun
  92. git = _GitCall()
  93. def git_require(min_version, fail=False):
  94. git_version = git.version_tuple()
  95. if min_version <= git_version:
  96. return True
  97. if fail:
  98. need = '.'.join(map(lambda x: str(x), min_version))
  99. print >>sys.stderr, 'fatal: git %s or later required' % need
  100. sys.exit(1)
  101. return False
  102. def _setenv(env, name, value):
  103. env[name] = value.encode()
  104. class GitCommand(object):
  105. def __init__(self,
  106. project,
  107. cmdv,
  108. bare = False,
  109. provide_stdin = False,
  110. capture_stdout = False,
  111. capture_stderr = False,
  112. disable_editor = False,
  113. ssh_proxy = False,
  114. cwd = None,
  115. gitdir = None):
  116. env = os.environ.copy()
  117. for e in [REPO_TRACE,
  118. GIT_DIR,
  119. 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
  120. 'GIT_OBJECT_DIRECTORY',
  121. 'GIT_WORK_TREE',
  122. 'GIT_GRAFT_FILE',
  123. 'GIT_INDEX_FILE']:
  124. if e in env:
  125. del env[e]
  126. if disable_editor:
  127. _setenv(env, 'GIT_EDITOR', ':')
  128. if ssh_proxy:
  129. _setenv(env, 'REPO_SSH_SOCK', ssh_sock())
  130. _setenv(env, 'GIT_SSH', _ssh_proxy())
  131. if project:
  132. if not cwd:
  133. cwd = project.worktree
  134. if not gitdir:
  135. gitdir = project.gitdir
  136. command = [GIT]
  137. if bare:
  138. if gitdir:
  139. _setenv(env, GIT_DIR, gitdir)
  140. cwd = None
  141. command.extend(cmdv)
  142. if provide_stdin:
  143. stdin = subprocess.PIPE
  144. else:
  145. stdin = None
  146. if capture_stdout:
  147. stdout = subprocess.PIPE
  148. else:
  149. stdout = None
  150. if capture_stderr:
  151. stderr = subprocess.PIPE
  152. else:
  153. stderr = None
  154. if IsTrace():
  155. global LAST_CWD
  156. global LAST_GITDIR
  157. dbg = ''
  158. if cwd and LAST_CWD != cwd:
  159. if LAST_GITDIR or LAST_CWD:
  160. dbg += '\n'
  161. dbg += ': cd %s\n' % cwd
  162. LAST_CWD = cwd
  163. if GIT_DIR in env and LAST_GITDIR != env[GIT_DIR]:
  164. if LAST_GITDIR or LAST_CWD:
  165. dbg += '\n'
  166. dbg += ': export GIT_DIR=%s\n' % env[GIT_DIR]
  167. LAST_GITDIR = env[GIT_DIR]
  168. dbg += ': '
  169. dbg += ' '.join(command)
  170. if stdin == subprocess.PIPE:
  171. dbg += ' 0<|'
  172. if stdout == subprocess.PIPE:
  173. dbg += ' 1>|'
  174. if stderr == subprocess.PIPE:
  175. dbg += ' 2>|'
  176. Trace('%s', dbg)
  177. try:
  178. p = subprocess.Popen(command,
  179. cwd = cwd,
  180. env = env,
  181. stdin = stdin,
  182. stdout = stdout,
  183. stderr = stderr)
  184. except Exception, e:
  185. raise GitError('%s: %s' % (command[1], e))
  186. if ssh_proxy:
  187. _add_ssh_client(p)
  188. self.process = p
  189. self.stdin = p.stdin
  190. def Wait(self):
  191. p = self.process
  192. if p.stdin:
  193. p.stdin.close()
  194. self.stdin = None
  195. if p.stdout:
  196. self.stdout = p.stdout.read()
  197. p.stdout.close()
  198. else:
  199. p.stdout = None
  200. if p.stderr:
  201. self.stderr = p.stderr.read()
  202. p.stderr.close()
  203. else:
  204. p.stderr = None
  205. try:
  206. rc = p.wait()
  207. finally:
  208. _remove_ssh_client(p)
  209. return rc