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 | ||
