diff options
Diffstat (limited to 'git_command.py')
-rw-r--r-- | git_command.py | 561 |
1 files changed, 296 insertions, 265 deletions
diff --git a/git_command.py b/git_command.py index d4d4bed4..c7245ade 100644 --- a/git_command.py +++ b/git_command.py | |||
@@ -24,7 +24,7 @@ import platform_utils | |||
24 | from repo_trace import REPO_TRACE, IsTrace, Trace | 24 | from repo_trace import REPO_TRACE, IsTrace, Trace |
25 | from wrapper import Wrapper | 25 | from wrapper import Wrapper |
26 | 26 | ||
27 | GIT = 'git' | 27 | GIT = "git" |
28 | # NB: These do not need to be kept in sync with the repo launcher script. | 28 | # NB: These do not need to be kept in sync with the repo launcher script. |
29 | # These may be much newer as it allows the repo launcher to roll between | 29 | # These may be much newer as it allows the repo launcher to roll between |
30 | # different repo releases while source versions might require a newer git. | 30 | # different repo releases while source versions might require a newer git. |
@@ -36,126 +36,138 @@ GIT = 'git' | |||
36 | # git-1.7 is in (EOL) Ubuntu Precise. git-1.9 is in Ubuntu Trusty. | 36 | # git-1.7 is in (EOL) Ubuntu Precise. git-1.9 is in Ubuntu Trusty. |
37 | MIN_GIT_VERSION_SOFT = (1, 9, 1) | 37 | MIN_GIT_VERSION_SOFT = (1, 9, 1) |
38 | MIN_GIT_VERSION_HARD = (1, 7, 2) | 38 | MIN_GIT_VERSION_HARD = (1, 7, 2) |
39 | GIT_DIR = 'GIT_DIR' | 39 | GIT_DIR = "GIT_DIR" |
40 | 40 | ||
41 | LAST_GITDIR = None | 41 | LAST_GITDIR = None |
42 | LAST_CWD = None | 42 | LAST_CWD = None |
43 | 43 | ||
44 | 44 | ||
45 | class _GitCall(object): | 45 | class _GitCall(object): |
46 | @functools.lru_cache(maxsize=None) | 46 | @functools.lru_cache(maxsize=None) |
47 | def version_tuple(self): | 47 | def version_tuple(self): |
48 | ret = Wrapper().ParseGitVersion() | 48 | ret = Wrapper().ParseGitVersion() |
49 | if ret is None: | 49 | if ret is None: |
50 | print('fatal: unable to detect git version', file=sys.stderr) | 50 | print("fatal: unable to detect git version", file=sys.stderr) |
51 | sys.exit(1) | 51 | sys.exit(1) |
52 | return ret | 52 | return ret |
53 | 53 | ||
54 | def __getattr__(self, name): | 54 | def __getattr__(self, name): |
55 | name = name.replace('_', '-') | 55 | name = name.replace("_", "-") |
56 | 56 | ||
57 | def fun(*cmdv): | 57 | def fun(*cmdv): |
58 | command = [name] | 58 | command = [name] |
59 | command.extend(cmdv) | 59 | command.extend(cmdv) |
60 | return GitCommand(None, command).Wait() == 0 | 60 | return GitCommand(None, command).Wait() == 0 |
61 | return fun | 61 | |
62 | return fun | ||
62 | 63 | ||
63 | 64 | ||
64 | git = _GitCall() | 65 | git = _GitCall() |
65 | 66 | ||
66 | 67 | ||
67 | def RepoSourceVersion(): | 68 | def RepoSourceVersion(): |
68 | """Return the version of the repo.git tree.""" | 69 | """Return the version of the repo.git tree.""" |
69 | ver = getattr(RepoSourceVersion, 'version', None) | 70 | ver = getattr(RepoSourceVersion, "version", None) |
70 | 71 | ||
71 | # We avoid GitCommand so we don't run into circular deps -- GitCommand needs | 72 | # We avoid GitCommand so we don't run into circular deps -- GitCommand needs |
72 | # to initialize version info we provide. | 73 | # to initialize version info we provide. |
73 | if ver is None: | 74 | if ver is None: |
74 | env = GitCommand._GetBasicEnv() | 75 | env = GitCommand._GetBasicEnv() |
76 | |||
77 | proj = os.path.dirname(os.path.abspath(__file__)) | ||
78 | env[GIT_DIR] = os.path.join(proj, ".git") | ||
79 | result = subprocess.run( | ||
80 | [GIT, "describe", HEAD], | ||
81 | stdout=subprocess.PIPE, | ||
82 | stderr=subprocess.DEVNULL, | ||
83 | encoding="utf-8", | ||
84 | env=env, | ||
85 | check=False, | ||
86 | ) | ||
87 | if result.returncode == 0: | ||
88 | ver = result.stdout.strip() | ||
89 | if ver.startswith("v"): | ||
90 | ver = ver[1:] | ||
91 | else: | ||
92 | ver = "unknown" | ||
93 | setattr(RepoSourceVersion, "version", ver) | ||
94 | |||
95 | return ver | ||
75 | 96 | ||
76 | proj = os.path.dirname(os.path.abspath(__file__)) | ||
77 | env[GIT_DIR] = os.path.join(proj, '.git') | ||
78 | result = subprocess.run([GIT, 'describe', HEAD], stdout=subprocess.PIPE, | ||
79 | stderr=subprocess.DEVNULL, encoding='utf-8', | ||
80 | env=env, check=False) | ||
81 | if result.returncode == 0: | ||
82 | ver = result.stdout.strip() | ||
83 | if ver.startswith('v'): | ||
84 | ver = ver[1:] | ||
85 | else: | ||
86 | ver = 'unknown' | ||
87 | setattr(RepoSourceVersion, 'version', ver) | ||
88 | 97 | ||
89 | return ver | 98 | class UserAgent(object): |
99 | """Mange User-Agent settings when talking to external services | ||
90 | 100 | ||
101 | We follow the style as documented here: | ||
102 | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent | ||
103 | """ | ||
91 | 104 | ||
92 | class UserAgent(object): | 105 | _os = None |
93 | """Mange User-Agent settings when talking to external services | 106 | _repo_ua = None |
94 | 107 | _git_ua = None | |
95 | We follow the style as documented here: | 108 | |
96 | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent | 109 | @property |
97 | """ | 110 | def os(self): |
98 | 111 | """The operating system name.""" | |
99 | _os = None | 112 | if self._os is None: |
100 | _repo_ua = None | 113 | os_name = sys.platform |
101 | _git_ua = None | 114 | if os_name.lower().startswith("linux"): |
102 | 115 | os_name = "Linux" | |
103 | @property | 116 | elif os_name == "win32": |
104 | def os(self): | 117 | os_name = "Win32" |
105 | """The operating system name.""" | 118 | elif os_name == "cygwin": |
106 | if self._os is None: | 119 | os_name = "Cygwin" |
107 | os_name = sys.platform | 120 | elif os_name == "darwin": |
108 | if os_name.lower().startswith('linux'): | 121 | os_name = "Darwin" |
109 | os_name = 'Linux' | 122 | self._os = os_name |
110 | elif os_name == 'win32': | 123 | |
111 | os_name = 'Win32' | 124 | return self._os |
112 | elif os_name == 'cygwin': | 125 | |
113 | os_name = 'Cygwin' | 126 | @property |
114 | elif os_name == 'darwin': | 127 | def repo(self): |
115 | os_name = 'Darwin' | 128 | """The UA when connecting directly from repo.""" |
116 | self._os = os_name | 129 | if self._repo_ua is None: |
117 | 130 | py_version = sys.version_info | |
118 | return self._os | 131 | self._repo_ua = "git-repo/%s (%s) git/%s Python/%d.%d.%d" % ( |
119 | 132 | RepoSourceVersion(), | |
120 | @property | 133 | self.os, |
121 | def repo(self): | 134 | git.version_tuple().full, |
122 | """The UA when connecting directly from repo.""" | 135 | py_version.major, |
123 | if self._repo_ua is None: | 136 | py_version.minor, |
124 | py_version = sys.version_info | 137 | py_version.micro, |
125 | self._repo_ua = 'git-repo/%s (%s) git/%s Python/%d.%d.%d' % ( | 138 | ) |
126 | RepoSourceVersion(), | 139 | |
127 | self.os, | 140 | return self._repo_ua |
128 | git.version_tuple().full, | 141 | |
129 | py_version.major, py_version.minor, py_version.micro) | 142 | @property |
130 | 143 | def git(self): | |
131 | return self._repo_ua | 144 | """The UA when running git.""" |
132 | 145 | if self._git_ua is None: | |
133 | @property | 146 | self._git_ua = "git/%s (%s) git-repo/%s" % ( |
134 | def git(self): | 147 | git.version_tuple().full, |
135 | """The UA when running git.""" | 148 | self.os, |
136 | if self._git_ua is None: | 149 | RepoSourceVersion(), |
137 | self._git_ua = 'git/%s (%s) git-repo/%s' % ( | 150 | ) |
138 | git.version_tuple().full, | 151 | |
139 | self.os, | 152 | return self._git_ua |
140 | RepoSourceVersion()) | ||
141 | |||
142 | return self._git_ua | ||
143 | 153 | ||
144 | 154 | ||
145 | user_agent = UserAgent() | 155 | user_agent = UserAgent() |
146 | 156 | ||
147 | 157 | ||
148 | def git_require(min_version, fail=False, msg=''): | 158 | def git_require(min_version, fail=False, msg=""): |
149 | git_version = git.version_tuple() | 159 | git_version = git.version_tuple() |
150 | if min_version <= git_version: | 160 | if min_version <= git_version: |
151 | return True | 161 | return True |
152 | if fail: | 162 | if fail: |
153 | need = '.'.join(map(str, min_version)) | 163 | need = ".".join(map(str, min_version)) |
154 | if msg: | 164 | if msg: |
155 | msg = ' for ' + msg | 165 | msg = " for " + msg |
156 | print('fatal: git %s or later required%s' % (need, msg), file=sys.stderr) | 166 | print( |
157 | sys.exit(1) | 167 | "fatal: git %s or later required%s" % (need, msg), file=sys.stderr |
158 | return False | 168 | ) |
169 | sys.exit(1) | ||
170 | return False | ||
159 | 171 | ||
160 | 172 | ||
161 | def _build_env( | 173 | def _build_env( |
@@ -164,175 +176,194 @@ def _build_env( | |||
164 | disable_editor: Optional[bool] = False, | 176 | disable_editor: Optional[bool] = False, |
165 | ssh_proxy: Optional[Any] = None, | 177 | ssh_proxy: Optional[Any] = None, |
166 | gitdir: Optional[str] = None, | 178 | gitdir: Optional[str] = None, |
167 | objdir: Optional[str] = None | 179 | objdir: Optional[str] = None, |
168 | ): | 180 | ): |
169 | """Constucts an env dict for command execution.""" | 181 | """Constucts an env dict for command execution.""" |
170 | |||
171 | assert _kwargs_only == (), '_build_env only accepts keyword arguments.' | ||
172 | |||
173 | env = GitCommand._GetBasicEnv() | ||
174 | |||
175 | if disable_editor: | ||
176 | env['GIT_EDITOR'] = ':' | ||
177 | if ssh_proxy: | ||
178 | env['REPO_SSH_SOCK'] = ssh_proxy.sock() | ||
179 | env['GIT_SSH'] = ssh_proxy.proxy | ||
180 | env['GIT_SSH_VARIANT'] = 'ssh' | ||
181 | if 'http_proxy' in env and 'darwin' == sys.platform: | ||
182 | s = "'http.proxy=%s'" % (env['http_proxy'],) | ||
183 | p = env.get('GIT_CONFIG_PARAMETERS') | ||
184 | if p is not None: | ||
185 | s = p + ' ' + s | ||
186 | env['GIT_CONFIG_PARAMETERS'] = s | ||
187 | if 'GIT_ALLOW_PROTOCOL' not in env: | ||
188 | env['GIT_ALLOW_PROTOCOL'] = ( | ||
189 | 'file:git:http:https:ssh:persistent-http:persistent-https:sso:rpc') | ||
190 | env['GIT_HTTP_USER_AGENT'] = user_agent.git | ||
191 | |||
192 | if objdir: | ||
193 | # Set to the place we want to save the objects. | ||
194 | env['GIT_OBJECT_DIRECTORY'] = objdir | ||
195 | |||
196 | alt_objects = os.path.join(gitdir, 'objects') if gitdir else None | ||
197 | if alt_objects and os.path.realpath(alt_objects) != os.path.realpath(objdir): | ||
198 | # Allow git to search the original place in case of local or unique refs | ||
199 | # that git will attempt to resolve even if we aren't fetching them. | ||
200 | env['GIT_ALTERNATE_OBJECT_DIRECTORIES'] = alt_objects | ||
201 | if bare and gitdir is not None: | ||
202 | env[GIT_DIR] = gitdir | ||
203 | |||
204 | return env | ||
205 | 182 | ||
183 | assert _kwargs_only == (), "_build_env only accepts keyword arguments." | ||
184 | |||
185 | env = GitCommand._GetBasicEnv() | ||
186 | |||
187 | if disable_editor: | ||
188 | env["GIT_EDITOR"] = ":" | ||
189 | if ssh_proxy: | ||
190 | env["REPO_SSH_SOCK"] = ssh_proxy.sock() | ||
191 | env["GIT_SSH"] = ssh_proxy.proxy | ||
192 | env["GIT_SSH_VARIANT"] = "ssh" | ||
193 | if "http_proxy" in env and "darwin" == sys.platform: | ||
194 | s = "'http.proxy=%s'" % (env["http_proxy"],) | ||
195 | p = env.get("GIT_CONFIG_PARAMETERS") | ||
196 | if p is not None: | ||
197 | s = p + " " + s | ||
198 | env["GIT_CONFIG_PARAMETERS"] = s | ||
199 | if "GIT_ALLOW_PROTOCOL" not in env: | ||
200 | env[ | ||
201 | "GIT_ALLOW_PROTOCOL" | ||
202 | ] = "file:git:http:https:ssh:persistent-http:persistent-https:sso:rpc" | ||
203 | env["GIT_HTTP_USER_AGENT"] = user_agent.git | ||
204 | |||
205 | if objdir: | ||
206 | # Set to the place we want to save the objects. | ||
207 | env["GIT_OBJECT_DIRECTORY"] = objdir | ||
208 | |||
209 | alt_objects = os.path.join(gitdir, "objects") if gitdir else None | ||
210 | if alt_objects and os.path.realpath(alt_objects) != os.path.realpath( | ||
211 | objdir | ||
212 | ): | ||
213 | # Allow git to search the original place in case of local or unique | ||
214 | # refs that git will attempt to resolve even if we aren't fetching | ||
215 | # them. | ||
216 | env["GIT_ALTERNATE_OBJECT_DIRECTORIES"] = alt_objects | ||
217 | if bare and gitdir is not None: | ||
218 | env[GIT_DIR] = gitdir | ||
206 | 219 | ||
207 | class GitCommand(object): | ||
208 | """Wrapper around a single git invocation.""" | ||
209 | |||
210 | def __init__(self, | ||
211 | project, | ||
212 | cmdv, | ||
213 | bare=False, | ||
214 | input=None, | ||
215 | capture_stdout=False, | ||
216 | capture_stderr=False, | ||
217 | merge_output=False, | ||
218 | disable_editor=False, | ||
219 | ssh_proxy=None, | ||
220 | cwd=None, | ||
221 | gitdir=None, | ||
222 | objdir=None): | ||
223 | |||
224 | if project: | ||
225 | if not cwd: | ||
226 | cwd = project.worktree | ||
227 | if not gitdir: | ||
228 | gitdir = project.gitdir | ||
229 | |||
230 | # Git on Windows wants its paths only using / for reliability. | ||
231 | if platform_utils.isWindows(): | ||
232 | if objdir: | ||
233 | objdir = objdir.replace('\\', '/') | ||
234 | if gitdir: | ||
235 | gitdir = gitdir.replace('\\', '/') | ||
236 | |||
237 | env = _build_env( | ||
238 | disable_editor=disable_editor, | ||
239 | ssh_proxy=ssh_proxy, | ||
240 | objdir=objdir, | ||
241 | gitdir=gitdir, | ||
242 | bare=bare, | ||
243 | ) | ||
244 | |||
245 | command = [GIT] | ||
246 | if bare: | ||
247 | cwd = None | ||
248 | command.append(cmdv[0]) | ||
249 | # Need to use the --progress flag for fetch/clone so output will be | ||
250 | # displayed as by default git only does progress output if stderr is a TTY. | ||
251 | if sys.stderr.isatty() and cmdv[0] in ('fetch', 'clone'): | ||
252 | if '--progress' not in cmdv and '--quiet' not in cmdv: | ||
253 | command.append('--progress') | ||
254 | command.extend(cmdv[1:]) | ||
255 | |||
256 | stdin = subprocess.PIPE if input else None | ||
257 | stdout = subprocess.PIPE if capture_stdout else None | ||
258 | stderr = (subprocess.STDOUT if merge_output else | ||
259 | (subprocess.PIPE if capture_stderr else None)) | ||
260 | |||
261 | dbg = '' | ||
262 | if IsTrace(): | ||
263 | global LAST_CWD | ||
264 | global LAST_GITDIR | ||
265 | |||
266 | if cwd and LAST_CWD != cwd: | ||
267 | if LAST_GITDIR or LAST_CWD: | ||
268 | dbg += '\n' | ||
269 | dbg += ': cd %s\n' % cwd | ||
270 | LAST_CWD = cwd | ||
271 | |||
272 | if GIT_DIR in env and LAST_GITDIR != env[GIT_DIR]: | ||
273 | if LAST_GITDIR or LAST_CWD: | ||
274 | dbg += '\n' | ||
275 | dbg += ': export GIT_DIR=%s\n' % env[GIT_DIR] | ||
276 | LAST_GITDIR = env[GIT_DIR] | ||
277 | |||
278 | if 'GIT_OBJECT_DIRECTORY' in env: | ||
279 | dbg += ': export GIT_OBJECT_DIRECTORY=%s\n' % env['GIT_OBJECT_DIRECTORY'] | ||
280 | if 'GIT_ALTERNATE_OBJECT_DIRECTORIES' in env: | ||
281 | dbg += ': export GIT_ALTERNATE_OBJECT_DIRECTORIES=%s\n' % ( | ||
282 | env['GIT_ALTERNATE_OBJECT_DIRECTORIES']) | ||
283 | |||
284 | dbg += ': ' | ||
285 | dbg += ' '.join(command) | ||
286 | if stdin == subprocess.PIPE: | ||
287 | dbg += ' 0<|' | ||
288 | if stdout == subprocess.PIPE: | ||
289 | dbg += ' 1>|' | ||
290 | if stderr == subprocess.PIPE: | ||
291 | dbg += ' 2>|' | ||
292 | elif stderr == subprocess.STDOUT: | ||
293 | dbg += ' 2>&1' | ||
294 | |||
295 | with Trace('git command %s %s with debug: %s', LAST_GITDIR, command, dbg): | ||
296 | try: | ||
297 | p = subprocess.Popen(command, | ||
298 | cwd=cwd, | ||
299 | env=env, | ||
300 | encoding='utf-8', | ||
301 | errors='backslashreplace', | ||
302 | stdin=stdin, | ||
303 | stdout=stdout, | ||
304 | stderr=stderr) | ||
305 | except Exception as e: | ||
306 | raise GitError('%s: %s' % (command[1], e)) | ||
307 | |||
308 | if ssh_proxy: | ||
309 | ssh_proxy.add_client(p) | ||
310 | |||
311 | self.process = p | ||
312 | |||
313 | try: | ||
314 | self.stdout, self.stderr = p.communicate(input=input) | ||
315 | finally: | ||
316 | if ssh_proxy: | ||
317 | ssh_proxy.remove_client(p) | ||
318 | self.rc = p.wait() | ||
319 | |||
320 | @staticmethod | ||
321 | def _GetBasicEnv(): | ||
322 | """Return a basic env for running git under. | ||
323 | |||
324 | This is guaranteed to be side-effect free. | ||
325 | """ | ||
326 | env = os.environ.copy() | ||
327 | for key in (REPO_TRACE, | ||
328 | GIT_DIR, | ||
329 | 'GIT_ALTERNATE_OBJECT_DIRECTORIES', | ||
330 | 'GIT_OBJECT_DIRECTORY', | ||
331 | 'GIT_WORK_TREE', | ||
332 | 'GIT_GRAFT_FILE', | ||
333 | 'GIT_INDEX_FILE'): | ||
334 | env.pop(key, None) | ||
335 | return env | 220 | return env |
336 | 221 | ||
337 | def Wait(self): | 222 | |
338 | return self.rc | 223 | class GitCommand(object): |
224 | """Wrapper around a single git invocation.""" | ||
225 | |||
226 | def __init__( | ||
227 | self, | ||
228 | project, | ||
229 | cmdv, | ||
230 | bare=False, | ||
231 | input=None, | ||
232 | capture_stdout=False, | ||
233 | capture_stderr=False, | ||
234 | merge_output=False, | ||
235 | disable_editor=False, | ||
236 | ssh_proxy=None, | ||
237 | cwd=None, | ||
238 | gitdir=None, | ||
239 | objdir=None, | ||
240 | ): | ||
241 | if project: | ||
242 | if not cwd: | ||
243 | cwd = project.worktree | ||
244 | if not gitdir: | ||
245 | gitdir = project.gitdir | ||
246 | |||
247 | # Git on Windows wants its paths only using / for reliability. | ||
248 | if platform_utils.isWindows(): | ||
249 | if objdir: | ||
250 | objdir = objdir.replace("\\", "/") | ||
251 | if gitdir: | ||
252 | gitdir = gitdir.replace("\\", "/") | ||
253 | |||
254 | env = _build_env( | ||
255 | disable_editor=disable_editor, | ||
256 | ssh_proxy=ssh_proxy, | ||
257 | objdir=objdir, | ||
258 | gitdir=gitdir, | ||
259 | bare=bare, | ||
260 | ) | ||
261 | |||
262 | command = [GIT] | ||
263 | if bare: | ||
264 | cwd = None | ||
265 | command.append(cmdv[0]) | ||
266 | # Need to use the --progress flag for fetch/clone so output will be | ||
267 | # displayed as by default git only does progress output if stderr is a | ||
268 | # TTY. | ||
269 | if sys.stderr.isatty() and cmdv[0] in ("fetch", "clone"): | ||
270 | if "--progress" not in cmdv and "--quiet" not in cmdv: | ||
271 | command.append("--progress") | ||
272 | command.extend(cmdv[1:]) | ||
273 | |||
274 | stdin = subprocess.PIPE if input else None | ||
275 | stdout = subprocess.PIPE if capture_stdout else None | ||
276 | stderr = ( | ||
277 | subprocess.STDOUT | ||
278 | if merge_output | ||
279 | else (subprocess.PIPE if capture_stderr else None) | ||
280 | ) | ||
281 | |||
282 | dbg = "" | ||
283 | if IsTrace(): | ||
284 | global LAST_CWD | ||
285 | global LAST_GITDIR | ||
286 | |||
287 | if cwd and LAST_CWD != cwd: | ||
288 | if LAST_GITDIR or LAST_CWD: | ||
289 | dbg += "\n" | ||
290 | dbg += ": cd %s\n" % cwd | ||
291 | LAST_CWD = cwd | ||
292 | |||
293 | if GIT_DIR in env and LAST_GITDIR != env[GIT_DIR]: | ||
294 | if LAST_GITDIR or LAST_CWD: | ||
295 | dbg += "\n" | ||
296 | dbg += ": export GIT_DIR=%s\n" % env[GIT_DIR] | ||
297 | LAST_GITDIR = env[GIT_DIR] | ||
298 | |||
299 | if "GIT_OBJECT_DIRECTORY" in env: | ||
300 | dbg += ( | ||
301 | ": export GIT_OBJECT_DIRECTORY=%s\n" | ||
302 | % env["GIT_OBJECT_DIRECTORY"] | ||
303 | ) | ||
304 | if "GIT_ALTERNATE_OBJECT_DIRECTORIES" in env: | ||
305 | dbg += ": export GIT_ALTERNATE_OBJECT_DIRECTORIES=%s\n" % ( | ||
306 | env["GIT_ALTERNATE_OBJECT_DIRECTORIES"] | ||
307 | ) | ||
308 | |||
309 | dbg += ": " | ||
310 | dbg += " ".join(command) | ||
311 | if stdin == subprocess.PIPE: | ||
312 | dbg += " 0<|" | ||
313 | if stdout == subprocess.PIPE: | ||
314 | dbg += " 1>|" | ||
315 | if stderr == subprocess.PIPE: | ||
316 | dbg += " 2>|" | ||
317 | elif stderr == subprocess.STDOUT: | ||
318 | dbg += " 2>&1" | ||
319 | |||
320 | with Trace( | ||
321 | "git command %s %s with debug: %s", LAST_GITDIR, command, dbg | ||
322 | ): | ||
323 | try: | ||
324 | p = subprocess.Popen( | ||
325 | command, | ||
326 | cwd=cwd, | ||
327 | env=env, | ||
328 | encoding="utf-8", | ||
329 | errors="backslashreplace", | ||
330 | stdin=stdin, | ||
331 | stdout=stdout, | ||
332 | stderr=stderr, | ||
333 | ) | ||
334 | except Exception as e: | ||
335 | raise GitError("%s: %s" % (command[1], e)) | ||
336 | |||
337 | if ssh_proxy: | ||
338 | ssh_proxy.add_client(p) | ||
339 | |||
340 | self.process = p | ||
341 | |||
342 | try: | ||
343 | self.stdout, self.stderr = p.communicate(input=input) | ||
344 | finally: | ||
345 | if ssh_proxy: | ||
346 | ssh_proxy.remove_client(p) | ||
347 | self.rc = p.wait() | ||
348 | |||
349 | @staticmethod | ||
350 | def _GetBasicEnv(): | ||
351 | """Return a basic env for running git under. | ||
352 | |||
353 | This is guaranteed to be side-effect free. | ||
354 | """ | ||
355 | env = os.environ.copy() | ||
356 | for key in ( | ||
357 | REPO_TRACE, | ||
358 | GIT_DIR, | ||
359 | "GIT_ALTERNATE_OBJECT_DIRECTORIES", | ||
360 | "GIT_OBJECT_DIRECTORY", | ||
361 | "GIT_WORK_TREE", | ||
362 | "GIT_GRAFT_FILE", | ||
363 | "GIT_INDEX_FILE", | ||
364 | ): | ||
365 | env.pop(key, None) | ||
366 | return env | ||
367 | |||
368 | def Wait(self): | ||
369 | return self.rc | ||