Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/git/cmd.py: 57%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

719 statements  

1# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors 

2# 

3# This module is part of GitPython and is released under the 

4# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ 

5 

6from __future__ import annotations 

7 

8__all__ = ["GitMeta", "Git"] 

9 

10import contextlib 

11import io 

12import itertools 

13import logging 

14import os 

15import re 

16import signal 

17import subprocess 

18import sys 

19import threading 

20import time 

21import warnings 

22from subprocess import DEVNULL, PIPE, Popen 

23from textwrap import dedent 

24 

25# typing --------------------------------------------------------------------------- 

26from typing import ( 

27 IO, 

28 TYPE_CHECKING, 

29 Any, 

30 AnyStr, 

31 BinaryIO, 

32 Callable, 

33 Dict, 

34 Iterator, 

35 List, 

36 Mapping, 

37 Optional, 

38 Sequence, 

39 TextIO, 

40 Tuple, 

41 Union, 

42 cast, 

43 overload, 

44) 

45 

46from git.compat import defenc, force_bytes, safe_decode 

47from git.exc import ( 

48 CommandError, 

49 GitCommandError, 

50 GitCommandNotFound, 

51 UnsafeOptionError, 

52 UnsafeProtocolError, 

53) 

54from git.util import ( 

55 cygpath, 

56 expand_path, 

57 is_cygwin_git, 

58 patch_env, 

59 remove_password_if_present, 

60 stream_copy, 

61) 

62 

63if sys.version_info >= (3, 10): 

64 from typing import TypeAlias 

65else: 

66 from typing_extensions import TypeAlias 

67 

68from git.types import TBD, Literal, PathLike 

69 

70if TYPE_CHECKING: 

71 from git.diff import DiffIndex 

72 from git.repo.base import Repo 

73 

74# --------------------------------------------------------------------------------- 

75 

76execute_kwargs = { 

77 "istream", 

78 "with_extended_output", 

79 "with_exceptions", 

80 "as_process", 

81 "output_stream", 

82 "stdout_as_string", 

83 "kill_after_timeout", 

84 "with_stdout", 

85 "universal_newlines", 

86 "shell", 

87 "env", 

88 "max_chunk_size", 

89 "strip_newline_in_stdout", 

90} 

91 

92_logger = logging.getLogger(__name__) 

93 

94 

95# ============================================================================== 

96## @name Utilities 

97# ------------------------------------------------------------------------------ 

98# Documentation 

99## @{ 

100 

101 

102def _kill_process(pid: int) -> bool: 

103 """Kill a POSIX process and its descendants, returning whether it was killed.""" 

104 # Collect descendants before signalling, while their parent PIDs still identify them. 

105 pids = [pid] 

106 for parent_pid in pids: 

107 try: 

108 try: 

109 p = Popen(["pgrep", "-P", str(parent_pid)], stdout=PIPE) 

110 except FileNotFoundError: 

111 # POSIX ps does not support selecting by parent PID. 

112 ps_args = ["ps", "-ef"] if sys.platform == "cygwin" else ["ps", "-A", "-o", "pid=", "-o", "ppid="] 

113 with Popen(ps_args, stdout=PIPE) as p: 

114 if p.stdout is not None: 

115 for line in p.stdout: 

116 fields = line.split() 

117 if sys.platform == "cygwin": 

118 fields = fields[1:3] # ps -ef starts with UID, PID, PPID. 

119 if len(fields) == 2 and all(field.isdigit() for field in fields): 

120 if int(fields[1]) == parent_pid: 

121 pids.append(int(fields[0])) 

122 else: 

123 with p: 

124 if p.stdout is not None: 

125 for line in p.stdout: 

126 if line.strip().isdigit(): 

127 pids.append(int(line)) 

128 except OSError as ex: 

129 _logger.info("Unable to enumerate child processes: %r", ex) 

130 killed = False 

131 if sys.platform != "win32": 

132 for process_pid in pids: 

133 try: 

134 os.kill(process_pid, signal.SIGKILL) 

135 killed = killed or process_pid == pid 

136 except OSError: 

137 pass 

138 return killed 

139 

140 

141def handle_process_output( 

142 process: Union["Git.AutoInterrupt", Popen], 

143 stdout_handler: Union[ 

144 None, 

145 Callable[[AnyStr], None], 

146 Callable[[List[AnyStr]], None], 

147 Callable[[bytes, "Repo", "DiffIndex"], None], 

148 ], 

149 stderr_handler: Union[None, Callable[[AnyStr], None], Callable[[List[AnyStr]], None]], 

150 finalizer: Union[None, Callable[[Union[Popen, "Git.AutoInterrupt"]], None]] = None, 

151 decode_streams: bool = True, 

152 kill_after_timeout: Union[None, float] = None, 

153) -> None: 

154 R"""Register for notifications to learn that process output is ready to read, and 

155 dispatch lines to the respective line handlers. 

156 

157 This function returns once the finalizer returns. 

158 

159 :param process: 

160 :class:`subprocess.Popen` instance. 

161 

162 :param stdout_handler: 

163 f(stdout_line_string), or ``None``. 

164 

165 :param stderr_handler: 

166 f(stderr_line_string), or ``None``. 

167 

168 :param finalizer: 

169 f(proc) - wait for proc to finish. 

170 

171 :param decode_streams: 

172 Assume stdout/stderr streams are binary and decode them before pushing their 

173 contents to handlers. 

174 

175 This defaults to ``True``. Set it to ``False`` if: 

176 

177 - ``universal_newlines == True``, as then streams are in text mode, or 

178 - decoding must happen later, such as for :class:`~git.diff.Diff`\s. 

179 

180 :param kill_after_timeout: 

181 :class:`float` or ``None``, Default = ``None`` 

182 

183 To specify a timeout in seconds for the git command, after which the process 

184 should be killed. 

185 """ 

186 

187 # Use 2 "pump" threads and wait for both to finish. 

188 def pump_stream( 

189 cmdline: List[str], 

190 name: str, 

191 stream: Union[BinaryIO, TextIO], 

192 is_decode: bool, 

193 handler: Union[None, Callable[[Union[bytes, str]], None]], 

194 ) -> None: 

195 try: 

196 for line in stream: 

197 if handler: 

198 if is_decode: 

199 assert isinstance(line, bytes) 

200 line_str = line.decode(defenc) 

201 handler(line_str) 

202 else: 

203 handler(line) 

204 

205 except Exception as ex: 

206 _logger.error(f"Pumping {name!r} of cmd({remove_password_if_present(cmdline)}) failed due to: {ex!r}") 

207 if not (isinstance(ex, ValueError) and stream.closed): 

208 # Only reraise if the error was not due to the stream closing. 

209 raise CommandError([f"<{name}-pump>"] + remove_password_if_present(cmdline), ex) from ex 

210 finally: 

211 stream.close() 

212 

213 if hasattr(process, "proc"): 

214 process = cast("Git.AutoInterrupt", process) 

215 cmdline: str | Tuple[str, ...] | List[str] = getattr(process.proc, "args", "") 

216 p_stdout = process.proc.stdout if process.proc else None 

217 p_stderr = process.proc.stderr if process.proc else None 

218 else: 

219 process = cast(Popen, process) # type: ignore[redundant-cast] 

220 cmdline = getattr(process, "args", "") 

221 p_stdout = process.stdout 

222 p_stderr = process.stderr 

223 

224 if not isinstance(cmdline, (tuple, list)): 

225 cmdline = cmdline.split() 

226 

227 pumps: List[Tuple[str, IO, Callable[..., None] | None]] = [] 

228 if p_stdout: 

229 pumps.append(("stdout", p_stdout, stdout_handler)) 

230 if p_stderr: 

231 pumps.append(("stderr", p_stderr, stderr_handler)) 

232 

233 threads: List[threading.Thread] = [] 

234 

235 for name, stream, handler in pumps: 

236 t = threading.Thread(target=pump_stream, args=(cmdline, name, stream, decode_streams, handler)) 

237 t.daemon = True 

238 t.start() 

239 threads.append(t) 

240 

241 # Wait for output handlers to finish before finalizing. 

242 # If the child needs stdin, the caller must arrange to feed/close it 

243 # before this call or concurrently; this function only drains output. 

244 deadline = None if kill_after_timeout is None else time.monotonic() + kill_after_timeout 

245 for t in threads: 

246 t.join(timeout=None if deadline is None else max(0, deadline - time.monotonic())) 

247 if t.is_alive(): 

248 if isinstance(process, Git.AutoInterrupt): 

249 if sys.platform != "win32" and process.proc is not None and process.proc.poll() is None: 

250 _kill_process(process.proc.pid) 

251 process._terminate() 

252 else: # Don't want to deal with the other case. 

253 raise RuntimeError( 

254 "Thread join() timed out in cmd.handle_process_output()." 

255 f" kill_after_timeout={kill_after_timeout} seconds" 

256 ) 

257 process._timeout_error = ( 

258 f"error: process killed because it timed out. kill_after_timeout={kill_after_timeout} seconds" 

259 ) 

260 break 

261 

262 if finalizer: 

263 finalizer(process) 

264 

265 

266safer_popen: Callable[..., Popen] 

267 

268if sys.platform == "win32": 

269 

270 def _safer_popen_windows( 

271 command: Union[str, Sequence[Any]], 

272 *, 

273 shell: bool = False, 

274 env: Optional[Mapping[str, str]] = None, 

275 **kwargs: Any, 

276 ) -> Popen: 

277 """Call :class:`subprocess.Popen` on Windows but don't include a CWD in the 

278 search. 

279 

280 This avoids an untrusted search path condition where a file like ``git.exe`` in 

281 a malicious repository would be run when GitPython operates on the repository. 

282 The process using GitPython may have an untrusted repository's working tree as 

283 its current working directory. Some operations may temporarily change to that 

284 directory before running a subprocess. In addition, while by default GitPython 

285 does not run external commands with a shell, it can be made to do so, in which 

286 case the CWD of the subprocess, which GitPython usually sets to a repository 

287 working tree, can itself be searched automatically by the shell. This wrapper 

288 covers all those cases. 

289 

290 :note: 

291 This currently works by setting the 

292 :envvar:`NoDefaultCurrentDirectoryInExePath` environment variable during 

293 subprocess creation. It also takes care of passing Windows-specific process 

294 creation flags, but that is unrelated to path search. 

295 

296 :note: 

297 The current implementation contains a race condition on :attr:`os.environ`. 

298 GitPython isn't thread-safe, but a program using it on one thread should 

299 ideally be able to mutate :attr:`os.environ` on another, without 

300 unpredictable results. See comments in: 

301 https://github.com/gitpython-developers/GitPython/pull/1650 

302 """ 

303 # CREATE_NEW_PROCESS_GROUP is needed for some ways of killing it afterwards. 

304 # https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal 

305 # https://docs.python.org/3/library/subprocess.html#subprocess.CREATE_NEW_PROCESS_GROUP 

306 creationflags = subprocess.CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP 

307 

308 # When using a shell, the shell is the direct subprocess, so the variable must 

309 # be set in its environment, to affect its search behavior. 

310 if shell: 

311 # The original may be immutable, or the caller may reuse it. Mutate a copy. 

312 env = {} if env is None else dict(env) 

313 env["NoDefaultCurrentDirectoryInExePath"] = "1" # The "1" can be any value. 

314 

315 # When not using a shell, the current process does the search in a 

316 # CreateProcessW API call, so the variable must be set in our environment. With 

317 # a shell, that's unnecessary if https://github.com/python/cpython/issues/101283 

318 # is patched. In Python versions where it is unpatched, in the rare case the 

319 # ComSpec environment variable is unset, the search for the shell itself is 

320 # unsafe. Setting NoDefaultCurrentDirectoryInExePath in all cases, as done here, 

321 # is simpler and protects against that. (As above, the "1" can be any value.) 

322 with patch_env("NoDefaultCurrentDirectoryInExePath", "1"): 

323 return Popen( 

324 command, 

325 shell=shell, 

326 env=env, 

327 creationflags=creationflags, 

328 **kwargs, 

329 ) 

330 

331 safer_popen = _safer_popen_windows 

332else: 

333 safer_popen = Popen 

334 

335 

336def dashify(string: str) -> str: 

337 return string.replace("_", "-") 

338 

339 

340def slots_to_dict(self: "Git", exclude: Sequence[str] = ()) -> Dict[str, Any]: 

341 return {s: getattr(self, s) for s in self.__slots__ if s not in exclude} 

342 

343 

344def dict_to_slots_and__excluded_are_none(self: object, d: Mapping[str, Any], excluded: Sequence[str] = ()) -> None: 

345 for k, v in d.items(): 

346 setattr(self, k, v) 

347 for k in excluded: 

348 setattr(self, k, None) 

349 

350 

351## -- End Utilities -- @} 

352 

353 

354class _AutoInterrupt: 

355 """Process wrapper that terminates the wrapped process on finalization. 

356 

357 This kills/interrupts the stored process instance once this instance goes out of 

358 scope. It is used to prevent processes piling up in case iterators stop reading. 

359 

360 All attributes are wired through to the contained process object. 

361 

362 The wait method is overridden to perform automatic status code checking and possibly 

363 raise. 

364 """ 

365 

366 __slots__ = ("proc", "args", "status", "_timeout_error") 

367 

368 # If this is non-zero it will override any status code during _terminate, used 

369 # to prevent race conditions in testing. 

370 _status_code_if_terminate: int = 0 

371 

372 def __init__(self, proc: Union[None, subprocess.Popen], args: Any) -> None: 

373 self.proc = proc 

374 self.args = args 

375 self.status: Union[int, None] = None 

376 self._timeout_error: Optional[str] = None 

377 

378 def _terminate(self) -> None: 

379 """Terminate the underlying process.""" 

380 if self.proc is None: 

381 return 

382 

383 proc = self.proc 

384 self.proc = None 

385 try: 

386 if proc.stdin: 

387 # A timed-out process may already have exited before input is flushed. 

388 with contextlib.suppress(BrokenPipeError): 

389 proc.stdin.close() 

390 # Did the process finish already so we have a return code? 

391 try: 

392 if proc.poll() is not None: 

393 self.status = self._status_code_if_terminate or proc.poll() 

394 return 

395 except OSError as ex: 

396 _logger.info("Ignored error after process had died: %r", ex) 

397 

398 # It can be that nothing really exists anymore... 

399 if getattr(os, "kill", None) is None: 

400 return 

401 

402 # Try to kill it. 

403 try: 

404 proc.terminate() 

405 except (OSError, AttributeError) as ex: 

406 # On interpreter shutdown (notably on Windows), parts of the stdlib used by 

407 # subprocess can already be torn down (e.g. `subprocess._winapi` becomes None), 

408 # which can cause AttributeError during terminate(). In that case, we prefer 

409 # to silently ignore to avoid noisy "Exception ignored in: __del__" messages. 

410 _logger.info("Ignored error while terminating process: %r", ex) 

411 return 

412 # END exception handling 

413 finally: 

414 if proc.stdout: 

415 proc.stdout.close() 

416 if proc.stderr: 

417 proc.stderr.close() 

418 

419 try: 

420 status = proc.wait() 

421 self.status = self._status_code_if_terminate or status 

422 except (OSError, AttributeError) as ex: 

423 _logger.info("Ignored error while waiting for terminated process: %r", ex) 

424 

425 def __del__(self) -> None: 

426 self._terminate() 

427 

428 def __getattr__(self, attr: str) -> Any: 

429 return getattr(self.proc, attr) 

430 

431 # TODO: Bad choice to mimic `proc.wait()` but with different args. 

432 def wait(self, stderr: Union[None, str, bytes] = b"") -> int: 

433 """Wait for the process and return its status code. 

434 

435 :param stderr: 

436 Previously read value of stderr, in case stderr is already closed. 

437 

438 :warn: 

439 May deadlock if output or error pipes are used and not handled separately. 

440 

441 :raise git.exc.GitCommandError: 

442 If the return status is not 0 or output handling timed out. 

443 """ 

444 status: Union[int, None] 

445 if self.proc is not None: 

446 status = self.proc.wait() 

447 p_stderr = self.proc.stderr 

448 else: # Assume the underlying proc was killed earlier or never existed. 

449 status = self.status 

450 p_stderr = None 

451 

452 stderr_b = force_bytes(data=stderr, encoding="utf-8") or b"" 

453 if self._timeout_error is not None: 

454 stderr_b += (b"\n" if stderr_b else b"") + self._timeout_error.encode("utf-8") 

455 status = status or 1 

456 

457 def read_all_from_possibly_closed_stream(stream: Union[IO[bytes], None]) -> bytes: 

458 if stream: 

459 try: 

460 return stderr_b + force_bytes(stream.read()) 

461 except (OSError, ValueError): 

462 return stderr_b or b"" 

463 else: 

464 return stderr_b or b"" 

465 

466 # END status handling 

467 

468 if status != 0: 

469 errstr = read_all_from_possibly_closed_stream(p_stderr) 

470 _logger.debug("AutoInterrupt wait stderr: %r" % (errstr,)) 

471 raise GitCommandError(remove_password_if_present(self.args), status, errstr) 

472 return status 

473 

474 

475_AutoInterrupt.__name__ = "AutoInterrupt" 

476_AutoInterrupt.__qualname__ = "Git.AutoInterrupt" 

477 

478 

479class _CatFileContentStream: 

480 """Object representing a sized read-only stream returning the contents of 

481 an object. 

482 

483 This behaves like a stream, but counts the data read and simulates an empty stream 

484 once our sized content region is empty. 

485 

486 If not all data are read to the end of the object's lifetime, we read the rest to 

487 ensure the underlying stream continues to work. 

488 """ 

489 

490 __slots__ = ("_stream", "_nbr", "_size") 

491 

492 def __init__(self, size: int, stream: IO[bytes]) -> None: 

493 self._stream = stream 

494 self._size = size 

495 self._nbr = 0 # Number of bytes read. 

496 

497 # Special case: If the object is empty, has null bytes, get the final 

498 # newline right away. 

499 if size == 0: 

500 stream.read(1) 

501 # END handle empty streams 

502 

503 def read(self, size: int = -1) -> bytes: 

504 bytes_left = self._size - self._nbr 

505 if bytes_left == 0: 

506 return b"" 

507 if size > -1: 

508 # Ensure we don't try to read past our limit. 

509 size = min(bytes_left, size) 

510 else: 

511 # They try to read all, make sure it's not more than what remains. 

512 size = bytes_left 

513 # END check early depletion 

514 data = self._stream.read(size) 

515 self._nbr += len(data) 

516 

517 # Check for depletion, read our final byte to make the stream usable by 

518 # others. 

519 if self._size - self._nbr == 0: 

520 self._stream.read(1) # final newline 

521 # END finish reading 

522 return data 

523 

524 def readline(self, size: int = -1) -> bytes: 

525 if self._nbr == self._size: 

526 return b"" 

527 

528 # Clamp size to lowest allowed value. 

529 bytes_left = self._size - self._nbr 

530 if size > -1: 

531 size = min(bytes_left, size) 

532 else: 

533 size = bytes_left 

534 # END handle size 

535 

536 data = self._stream.readline(size) 

537 self._nbr += len(data) 

538 

539 # Handle final byte. 

540 if self._size - self._nbr == 0: 

541 self._stream.read(1) 

542 # END finish reading 

543 

544 return data 

545 

546 def readlines(self, size: int = -1) -> List[bytes]: 

547 if self._nbr == self._size: 

548 return [] 

549 

550 # Leave all additional logic to our readline method, we just check the size. 

551 out = [] 

552 nbr = 0 

553 while True: 

554 line = self.readline() 

555 if not line: 

556 break 

557 out.append(line) 

558 if size > -1: 

559 nbr += len(line) 

560 if nbr > size: 

561 break 

562 # END handle size constraint 

563 # END readline loop 

564 return out 

565 

566 # skipcq: PYL-E0301 

567 def __iter__(self) -> "Git.CatFileContentStream": 

568 return self 

569 

570 def __next__(self) -> bytes: 

571 line = self.readline() 

572 if not line: 

573 raise StopIteration 

574 

575 return line 

576 

577 next = __next__ 

578 

579 def __del__(self) -> None: 

580 bytes_left = self._size - self._nbr 

581 if bytes_left: 

582 # Read and discard - seeking is impossible within a stream. 

583 # This includes any terminating newline. 

584 self._stream.read(bytes_left + 1) 

585 # END handle incomplete read 

586 

587 

588_CatFileContentStream.__name__ = "CatFileContentStream" 

589_CatFileContentStream.__qualname__ = "Git.CatFileContentStream" 

590 

591 

592_USE_SHELL_DEFAULT_MESSAGE = ( 

593 "Git.USE_SHELL is deprecated, because only its default value of False is safe. " 

594 "It will be removed in a future release." 

595) 

596 

597_USE_SHELL_DANGER_MESSAGE = ( 

598 "Setting Git.USE_SHELL to True is unsafe and insecure, as the effect of special " 

599 "shell syntax cannot usually be accounted for. This can result in a command " 

600 "injection vulnerability and arbitrary code execution. Git.USE_SHELL is deprecated " 

601 "and will be removed in a future release." 

602) 

603 

604 

605def _warn_use_shell(*, extra_danger: bool) -> None: 

606 warnings.warn( 

607 _USE_SHELL_DANGER_MESSAGE if extra_danger else _USE_SHELL_DEFAULT_MESSAGE, 

608 DeprecationWarning, 

609 stacklevel=3, 

610 ) 

611 

612 

613class _GitMeta(type): 

614 """Metaclass for :class:`Git`. 

615 

616 This helps issue :class:`DeprecationWarning` if :attr:`Git.USE_SHELL` is used. 

617 """ 

618 

619 def __getattribute(cls, name: str) -> Any: 

620 if name == "USE_SHELL": 

621 _warn_use_shell(extra_danger=False) 

622 return super().__getattribute__(name) 

623 

624 def __setattr(cls, name: str, value: Any) -> Any: 

625 if name == "USE_SHELL": 

626 _warn_use_shell(extra_danger=value) 

627 super().__setattr__(name, value) 

628 

629 if not TYPE_CHECKING: 

630 # To preserve static checking for undefined/misspelled attributes while letting 

631 # the methods' bodies be type-checked, these are defined as non-special methods, 

632 # then bound to special names out of view of static type checkers. (The original 

633 # names invoke name mangling (leading "__") to avoid confusion in other scopes.) 

634 __getattribute__ = __getattribute 

635 __setattr__ = __setattr 

636 

637 

638GitMeta = _GitMeta 

639"""Alias of :class:`Git`'s metaclass, whether it is :class:`type` or a custom metaclass. 

640 

641Whether the :class:`Git` class has the default :class:`type` as its metaclass or uses a 

642custom metaclass is not documented and may change at any time. This statically checkable 

643metaclass alias is equivalent at runtime to ``type(Git)``. This should almost never be 

644used. Code that benefits from it is likely to be remain brittle even if it is used. 

645 

646In view of the :class:`Git` class's intended use and :class:`Git` objects' dynamic 

647callable attributes representing git subcommands, it rarely makes sense to inherit from 

648:class:`Git` at all. Using :class:`Git` in multiple inheritance can be especially tricky 

649to do correctly. Attempting uses of :class:`Git` where its metaclass is relevant, such 

650as when a sibling class has an unrelated metaclass and a shared lower bound metaclass 

651might have to be introduced to solve a metaclass conflict, is not recommended. 

652 

653:note: 

654 The correct static type of the :class:`Git` class itself, and any subclasses, is 

655 ``Type[Git]``. (This can be written as ``type[Git]`` in Python 3.9 later.) 

656 

657 :class:`GitMeta` should never be used in any annotation where ``Type[Git]`` is 

658 intended or otherwise possible to use. This alias is truly only for very rare and 

659 inherently precarious situations where it is necessary to deal with the metaclass 

660 explicitly. 

661""" 

662 

663 

664class Git(metaclass=_GitMeta): 

665 """The Git class manages communication with the Git binary. 

666 

667 It provides a convenient interface to calling the Git binary, such as in:: 

668 

669 g = Git( git_dir ) 

670 g.init() # calls 'git init' program 

671 rval = g.ls_files() # calls 'git ls-files' program 

672 

673 Debugging: 

674 

675 * Set the :envvar:`GIT_PYTHON_TRACE` environment variable to print each invocation 

676 of the command to stdout. 

677 * Set its value to ``full`` to see details about the returned values. 

678 """ 

679 

680 __slots__ = ( 

681 "_working_dir", 

682 "cat_file_all", 

683 "cat_file_header", 

684 "_version_info", 

685 "_version_info_token", 

686 "_git_options", 

687 "_persistent_git_options", 

688 "_environment", 

689 ) 

690 

691 _excluded_ = ( 

692 "cat_file_all", 

693 "cat_file_header", 

694 "_version_info", 

695 "_version_info_token", 

696 ) 

697 

698 # Match Git's leading transport selector, including an empty helper name. 

699 # Git also selects the command-executing ext helper for an ext:// URL. 

700 re_unsafe_protocol = re.compile(r"([A-Za-z0-9][A-Za-z0-9+.-]*|)::|ext://") 

701 

702 unsafe_git_ls_remote_options = [ 

703 # This option allows arbitrary command execution in git-ls-remote. 

704 "--upload-pack", 

705 "--exec", 

706 ] 

707 

708 unsafe_git_pathspec_from_file_options = [ 

709 # Reads pathspecs from a caller-controlled file. Some commands include an 

710 # unmatched pathspec in their error output, which can disclose the file. 

711 "--pathspec-from-file", 

712 ] 

713 

714 def __getstate__(self) -> Dict[str, Any]: 

715 return slots_to_dict(self, exclude=self._excluded_) 

716 

717 def __setstate__(self, d: Dict[str, Any]) -> None: 

718 dict_to_slots_and__excluded_are_none(self, d, excluded=self._excluded_) 

719 

720 # CONFIGURATION 

721 

722 git_exec_name = "git" 

723 """Default git command that should work on Linux, Windows, and other systems.""" 

724 

725 GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False) 

726 """Enables debugging of GitPython's git commands.""" 

727 

728 USE_SHELL: bool = False 

729 """Deprecated. If set to ``True``, a shell will be used when executing git commands. 

730 

731 Code that uses ``USE_SHELL = True`` or that passes ``shell=True`` to any GitPython 

732 functions should be updated to use the default value of ``False`` instead. ``True`` 

733 is unsafe unless the effect of syntax treated specially by the shell is fully 

734 considered and accounted for, which is not possible under most circumstances. As 

735 detailed below, it is also no longer needed, even where it had been in the past. 

736 

737 It is in many if not most cases a command injection vulnerability for an application 

738 to set :attr:`USE_SHELL` to ``True``. Any attacker who can cause a specially crafted 

739 fragment of text to make its way into any part of any argument to any git command 

740 (including paths, branch names, etc.) can cause the shell to read and write 

741 arbitrary files and execute arbitrary commands. Innocent input may also accidentally 

742 contain special shell syntax, leading to inadvertent malfunctions. 

743 

744 In addition, how a value of ``True`` interacts with some aspects of GitPython's 

745 operation is not precisely specified and may change without warning, even before 

746 GitPython 4.0.0 when :attr:`USE_SHELL` may be removed. This includes: 

747 

748 * Whether or how GitPython automatically customizes the shell environment. 

749 

750 * Whether, outside of Windows (where :class:`subprocess.Popen` supports lists of 

751 separate arguments even when ``shell=True``), this can be used with any GitPython 

752 functionality other than direct calls to the :meth:`execute` method. 

753 

754 * Whether any GitPython feature that runs git commands ever attempts to partially 

755 sanitize data a shell may treat specially. Currently this is not done. 

756 

757 Prior to GitPython 2.0.8, this had a narrow purpose in suppressing console windows 

758 in graphical Windows applications. In 2.0.8 and higher, it provides no benefit, as 

759 GitPython solves that problem more robustly and safely by using the 

760 ``CREATE_NO_WINDOW`` process creation flag on Windows. 

761 

762 Because Windows path search differs subtly based on whether a shell is used, in rare 

763 cases changing this from ``True`` to ``False`` may keep an unusual git "executable", 

764 such as a batch file, from being found. To fix this, set the command name or full 

765 path in the :envvar:`GIT_PYTHON_GIT_EXECUTABLE` environment variable or pass the 

766 full path to :func:`git.refresh` (or invoke the script using a ``.exe`` shim). 

767 

768 Further reading: 

769 

770 * :meth:`Git.execute` (on the ``shell`` parameter). 

771 * https://github.com/gitpython-developers/GitPython/commit/0d9390866f9ce42870d3116094cd49e0019a970a 

772 * https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags 

773 * https://github.com/python/cpython/issues/91558#issuecomment-1100942950 

774 * https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw 

775 """ 

776 

777 _git_exec_env_var = "GIT_PYTHON_GIT_EXECUTABLE" 

778 _refresh_env_var = "GIT_PYTHON_REFRESH" 

779 

780 GIT_PYTHON_GIT_EXECUTABLE = None 

781 """Provide the full path to the git executable. Otherwise it assumes git is in the 

782 executable search path. 

783 

784 :note: 

785 The git executable is actually found during the refresh step in the top level 

786 ``__init__``. It can also be changed by explicitly calling :func:`git.refresh`. 

787 """ 

788 

789 _refresh_token = object() # Since None would match an initial _version_info_token. 

790 

791 @classmethod 

792 def refresh(cls, path: Union[None, PathLike] = None) -> bool: 

793 """Update information about the git executable :class:`Git` objects will use. 

794 

795 Called by the :func:`git.refresh` function in the top level ``__init__``. 

796 

797 :param path: 

798 Optional path to the git executable. If not absolute, it is resolved 

799 immediately, relative to the current directory. (See note below.) 

800 

801 :note: 

802 The top-level :func:`git.refresh` should be preferred because it calls this 

803 method and may also update other state accordingly. 

804 

805 :note: 

806 There are three different ways to specify the command that refreshing causes 

807 to be used for git: 

808 

809 1. Pass no `path` argument and do not set the 

810 :envvar:`GIT_PYTHON_GIT_EXECUTABLE` environment variable. The command 

811 name ``git`` is used. It is looked up in a path search by the system, in 

812 each command run (roughly similar to how git is found when running 

813 ``git`` commands manually). This is usually the desired behavior. 

814 

815 2. Pass no `path` argument but set the :envvar:`GIT_PYTHON_GIT_EXECUTABLE` 

816 environment variable. The command given as the value of that variable is 

817 used. This may be a simple command or an arbitrary path. It is looked up 

818 in each command run. Setting :envvar:`GIT_PYTHON_GIT_EXECUTABLE` to 

819 ``git`` has the same effect as not setting it. 

820 

821 3. Pass a `path` argument. This path, if not absolute, is immediately 

822 resolved, relative to the current directory. This resolution occurs at 

823 the time of the refresh. When git commands are run, they are run using 

824 that previously resolved path. If a `path` argument is passed, the 

825 :envvar:`GIT_PYTHON_GIT_EXECUTABLE` environment variable is not 

826 consulted. 

827 

828 :note: 

829 Refreshing always sets the :attr:`Git.GIT_PYTHON_GIT_EXECUTABLE` class 

830 attribute, which can be read on the :class:`Git` class or any of its 

831 instances to check what command is used to run git. This attribute should 

832 not be confused with the related :envvar:`GIT_PYTHON_GIT_EXECUTABLE` 

833 environment variable. The class attribute is set no matter how refreshing is 

834 performed. 

835 """ 

836 # Discern which path to refresh with. 

837 if path is not None: 

838 new_git = os.path.expanduser(path) 

839 new_git = os.path.abspath(new_git) 

840 else: 

841 new_git = os.environ.get(cls._git_exec_env_var, cls.git_exec_name) 

842 

843 # Keep track of the old and new git executable path. 

844 old_git = cls.GIT_PYTHON_GIT_EXECUTABLE 

845 old_refresh_token = cls._refresh_token 

846 cls.GIT_PYTHON_GIT_EXECUTABLE = new_git 

847 cls._refresh_token = object() 

848 

849 # Test if the new git executable path is valid. A GitCommandNotFound error is 

850 # raised by us. A PermissionError is raised if the git executable cannot be 

851 # executed for whatever reason. 

852 has_git = False 

853 try: 

854 cls().version() 

855 has_git = True 

856 except (GitCommandNotFound, PermissionError): 

857 pass 

858 

859 # Warn or raise exception if test failed. 

860 if not has_git: 

861 err = ( 

862 dedent( 

863 """\ 

864 Bad git executable. 

865 The git executable must be specified in one of the following ways: 

866 - be included in your $PATH 

867 - be set via $%s 

868 - explicitly set via git.refresh(<full-path-to-git-executable>) 

869 """ 

870 ) 

871 % cls._git_exec_env_var 

872 ) 

873 

874 # Revert to whatever the old_git was. 

875 cls.GIT_PYTHON_GIT_EXECUTABLE = old_git 

876 cls._refresh_token = old_refresh_token 

877 

878 if old_git is None: 

879 # On the first refresh (when GIT_PYTHON_GIT_EXECUTABLE is None) we only 

880 # are quiet, warn, or error depending on the GIT_PYTHON_REFRESH value. 

881 

882 # Determine what the user wants to happen during the initial refresh. We 

883 # expect GIT_PYTHON_REFRESH to either be unset or be one of the 

884 # following values: 

885 # 

886 # 0|q|quiet|s|silence|silent|n|none 

887 # 1|w|warn|warning|l|log 

888 # 2|r|raise|e|error|exception 

889 

890 mode = os.environ.get(cls._refresh_env_var, "raise").lower() 

891 

892 quiet = ["quiet", "q", "silence", "s", "silent", "none", "n", "0"] 

893 warn = ["warn", "w", "warning", "log", "l", "1"] 

894 error = ["error", "e", "exception", "raise", "r", "2"] 

895 

896 if mode in quiet: 

897 pass 

898 elif mode in warn or mode in error: 

899 err = dedent( 

900 """\ 

901 %s 

902 All git commands will error until this is rectified. 

903 

904 This initial message can be silenced or aggravated in the future by setting the 

905 $%s environment variable. Use one of the following values: 

906 - %s: for no message or exception 

907 - %s: for a warning message (logging level CRITICAL, displayed by default) 

908 - %s: for a raised exception 

909 

910 Example: 

911 export %s=%s 

912 """ 

913 ) % ( 

914 err, 

915 cls._refresh_env_var, 

916 "|".join(quiet), 

917 "|".join(warn), 

918 "|".join(error), 

919 cls._refresh_env_var, 

920 quiet[0], 

921 ) 

922 

923 if mode in warn: 

924 _logger.critical(err) 

925 else: 

926 raise ImportError(err) 

927 else: 

928 err = dedent( 

929 """\ 

930 %s environment variable has been set but it has been set with an invalid value. 

931 

932 Use only the following values: 

933 - %s: for no message or exception 

934 - %s: for a warning message (logging level CRITICAL, displayed by default) 

935 - %s: for a raised exception 

936 """ 

937 ) % ( 

938 cls._refresh_env_var, 

939 "|".join(quiet), 

940 "|".join(warn), 

941 "|".join(error), 

942 ) 

943 raise ImportError(err) 

944 

945 # We get here if this was the initial refresh and the refresh mode was 

946 # not error. Go ahead and set the GIT_PYTHON_GIT_EXECUTABLE such that we 

947 # discern the difference between the first refresh at import time 

948 # and subsequent calls to git.refresh or this refresh method. 

949 cls.GIT_PYTHON_GIT_EXECUTABLE = cls.git_exec_name 

950 else: 

951 # After the first refresh (when GIT_PYTHON_GIT_EXECUTABLE is no longer 

952 # None) we raise an exception. 

953 raise GitCommandNotFound(new_git, err) 

954 

955 return has_git 

956 

957 @classmethod 

958 def is_cygwin(cls) -> bool: 

959 return is_cygwin_git(cls.GIT_PYTHON_GIT_EXECUTABLE) 

960 

961 @overload 

962 @classmethod 

963 def polish_url(cls, url: str, is_cygwin: Literal[False] = ..., expand_vars: bool = ...) -> str: ... 

964 

965 @overload 

966 @classmethod 

967 def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None, expand_vars: bool = True) -> str: ... 

968 

969 @classmethod 

970 def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None, expand_vars: bool = True) -> PathLike: 

971 """Remove any backslashes from URLs to be written in config files. 

972 

973 Windows might create config files containing paths with backslashes, but git 

974 stops liking them as it will escape the backslashes. Hence we undo the escaping 

975 just to be sure. 

976 

977 :param expand_vars: 

978 Expand environment variables and an initial ``~``. Disable this for values 

979 obtained from an untrusted source, such as remote URLs. 

980 """ 

981 if is_cygwin is None: 

982 is_cygwin = cls.is_cygwin() 

983 

984 if is_cygwin: 

985 url = cygpath(url, expand_vars=expand_vars) 

986 else: 

987 if expand_vars: 

988 url = os.path.expandvars(url) 

989 if url.startswith("~"): 

990 url = os.path.expanduser(url) 

991 url = url.replace("\\\\", "\\").replace("\\", "/") 

992 return url 

993 

994 @classmethod 

995 def check_unsafe_protocols(cls, url: str) -> None: 

996 """Check for unsafe protocols. 

997 

998 Apart from the usual protocols (http, git, ssh), Git allows "remote helpers" 

999 that have the form ``<transport>::<address>``. One of these helpers (``ext::``) 

1000 can be used to invoke any arbitrary command. Git also selects that helper 

1001 for ``ext://`` URLs and interprets the URL as a command path. 

1002 

1003 See: 

1004 

1005 - https://git-scm.com/docs/gitremote-helpers 

1006 - https://git-scm.com/docs/git-remote-ext 

1007 """ 

1008 match = cls.re_unsafe_protocol.match(url) 

1009 if match: 

1010 protocol = match.group(0) 

1011 raise UnsafeProtocolError( 

1012 f"The `{protocol}` protocol looks suspicious, use `allow_unsafe_protocols=True` to allow it." 

1013 ) 

1014 

1015 def _check_unsafe_protocols_in_args(self, args: Sequence[Any], kwargs: Mapping[str, Any]) -> None: 

1016 """Check positional operands and standalone values in rendered command options. 

1017 

1018 A short flag's split value can become the repository operand before ``--``. 

1019 """ 

1020 for arg in self._unpack_args(args) + self.transform_kwargs(**kwargs): 

1021 self.check_unsafe_protocols(arg) 

1022 

1023 @classmethod 

1024 def _canonicalize_option_name(cls, option: str) -> str: 

1025 """Return the option name used for unsafe-option checks. 

1026 

1027 Examples: 

1028 ``"--upload-pack=/tmp/helper"`` -> ``"upload-pack"`` 

1029 ``"upload_pack"`` -> ``"upload-pack"`` 

1030 ``"--config core.filemode=false"`` -> ``"config"`` 

1031 """ 

1032 option_name = option.lstrip("-").split("=", 1)[0] 

1033 option_tokens = option_name.split(None, 1) 

1034 if not option_tokens: 

1035 return "" 

1036 return dashify(option_tokens[0]) 

1037 

1038 @classmethod 

1039 def check_unsafe_options( 

1040 cls, options: List[str], unsafe_options: List[str], clusterable_short_options: str = "46flnqsv" 

1041 ) -> None: 

1042 """Raise :class:`~git.exc.UnsafeOptionError` for blocked option spellings. 

1043 

1044 In addition to exact matches, this rejects abbreviated long options accepted 

1045 by Git (for example, ``--upl`` for ``--upload-pack``) and unsafe short options 

1046 whose values are joined to the same token, including after clusterable flags 

1047 (for example, ``-uVALUE`` and ``-fuVALUE``). 

1048 

1049 A list containing only bare names is treated as normalized keyword arguments, 

1050 so multi-character names such as ``upload_p`` are checked as long-option 

1051 abbreviations. If any item starts with ``-``, the list is treated as tokenized 

1052 command-line input: bare items can be option values and are not checked as 

1053 abbreviations. Thus ``["--origin", "upload"]`` is allowed. Single-dash options 

1054 use short-option parsing rather than broad prefix matching, preserving safe 

1055 attached values such as ``-oupstream`` and ``-bcurrent``. 

1056 

1057 Some options passed to ``git <command>`` can execute arbitrary commands and 

1058 are therefore blocked by default unless the caller explicitly allows them. 

1059 """ 

1060 # Options can be of the form `foo`, `--foo`, `--foo bar`, or `--foo=bar`. 

1061 # Git accepts any unambiguous prefix of a long option, so an abbreviated 

1062 # spelling such as `--upl` for `--upload-pack` must be rejected too. An 

1063 # option is unsafe if its canonical name is a prefix of any blocked 

1064 # option's canonical name. Only long options and multi-character kwargs 

1065 # can be abbreviations; single-character short options remain exact-match 

1066 # only. 

1067 canonical_unsafe_options = {cls._canonicalize_option_name(option): option for option in unsafe_options} 

1068 unsafe_short_options = { 

1069 canonical: option 

1070 for canonical, option in canonical_unsafe_options.items() 

1071 if option.startswith("-") and not option.startswith("--") and len(canonical) == 1 

1072 } 

1073 # These value-less Git flags can be clustered before another short option 

1074 # (for example, ``-fuVALUE``). Stop at any other character because it may 

1075 # begin an attached value, as ``o`` does in the safe option ``-oupstream``. 

1076 clusterable_short_options_set = frozenset(clusterable_short_options) 

1077 options_are_kwargs = all(not option.startswith("-") for option in options) 

1078 for option in options: 

1079 candidate = cls._canonicalize_option_name(option) 

1080 if not candidate: 

1081 continue 

1082 unsafe_option = canonical_unsafe_options.get(candidate) 

1083 if unsafe_option is not None: 

1084 raise UnsafeOptionError(f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it.") 

1085 option_token = option.split("=", 1)[0].split(None, 1)[0] 

1086 if option_token.startswith("-") and not option_token.startswith("--"): 

1087 for option_char in option_token[1:]: 

1088 unsafe_option = unsafe_short_options.get(option_char) 

1089 if unsafe_option is not None: 

1090 raise UnsafeOptionError( 

1091 f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it." 

1092 ) 

1093 if option_char not in clusterable_short_options_set: 

1094 break 

1095 if not (option.startswith("--") or (options_are_kwargs and len(candidate) > 1)): 

1096 continue 

1097 for canonical, unsafe_option in canonical_unsafe_options.items(): 

1098 if canonical.startswith(candidate): 

1099 raise UnsafeOptionError( 

1100 f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it." 

1101 ) 

1102 

1103 @classmethod 

1104 def _option_candidates(cls, args: Sequence[Any] = (), kwargs: Optional[Mapping[str, Any]] = None) -> List[str]: 

1105 """Collect possible option spellings before command-line transformation.""" 

1106 options = [ 

1107 option for option in cls._unpack_args([arg for arg in args if arg is not None]) if option.startswith("-") 

1108 ] 

1109 if kwargs: 

1110 split_single_char_options = kwargs.get("split_single_char_options", True) 

1111 for key, value in kwargs.items(): 

1112 values = value if isinstance(value, (list, tuple)) else (value,) 

1113 if any(value is True or (value is not False and value is not None) for value in values): 

1114 key = str(key) 

1115 if len(key) != 1: 

1116 options.append(f"--{dashify(key)}") 

1117 elif split_single_char_options: 

1118 options.append(f"-{key}") 

1119 options.extend( 

1120 str(value) 

1121 for value in values 

1122 if value is not True and value not in (False, None) and str(value).startswith("-") 

1123 ) 

1124 else: 

1125 options.extend( 

1126 f"-{key}" if value is True else f"-{key}{value}" 

1127 for value in values 

1128 if value is True or (value is not False and value is not None) 

1129 ) 

1130 return options 

1131 

1132 AutoInterrupt: TypeAlias = _AutoInterrupt 

1133 

1134 CatFileContentStream: TypeAlias = _CatFileContentStream 

1135 

1136 def __init__(self, working_dir: Union[None, PathLike] = None) -> None: 

1137 """Initialize this instance with: 

1138 

1139 :param working_dir: 

1140 Git directory we should work in. If ``None``, we always work in the current 

1141 directory as returned by :func:`os.getcwd`. 

1142 This is meant to be the working tree directory if available, or the 

1143 ``.git`` directory in case of bare repositories. 

1144 """ 

1145 super().__init__() 

1146 self._working_dir = expand_path(working_dir) 

1147 self._git_options: Union[List[str], Tuple[str, ...]] = () 

1148 self._persistent_git_options: List[str] = [] 

1149 

1150 # Extra environment variables to pass to git commands 

1151 self._environment: Dict[str, str] = {} 

1152 

1153 # Cached version slots 

1154 self._version_info: Union[Tuple[int, ...], None] = None 

1155 self._version_info_token: object = None 

1156 

1157 # Cached command slots 

1158 self.cat_file_header: Union[None, TBD] = None 

1159 self.cat_file_all: Union[None, TBD] = None 

1160 

1161 def __getattribute__(self, name: str) -> Any: 

1162 if name == "USE_SHELL": 

1163 _warn_use_shell(extra_danger=False) 

1164 return super().__getattribute__(name) 

1165 

1166 def __getattr__(self, name: str) -> Any: 

1167 """A convenience method as it allows to call the command as if it was an object. 

1168 

1169 :return: 

1170 Callable object that will execute call :meth:`_call_process` with your 

1171 arguments. 

1172 """ 

1173 if name.startswith("_"): 

1174 return super().__getattribute__(name) 

1175 return lambda *args, **kwargs: self._call_process(name, *args, **kwargs) 

1176 

1177 def set_persistent_git_options(self, **kwargs: Any) -> None: 

1178 """Specify command line options to the git executable for subsequent 

1179 subcommand calls. 

1180 

1181 :param kwargs: 

1182 A dict of keyword arguments. 

1183 These arguments are passed as in :meth:`_call_process`, but will be passed 

1184 to the git command rather than the subcommand. 

1185 """ 

1186 

1187 self._persistent_git_options = self.transform_kwargs(split_single_char_options=True, **kwargs) 

1188 

1189 def ls_remote( 

1190 self, 

1191 *args: Any, 

1192 allow_unsafe_options: bool = False, 

1193 allow_unsafe_protocols: bool = False, 

1194 **kwargs: Any, 

1195 ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], "Git.AutoInterrupt"]: 

1196 """List references in a remote repository. 

1197 

1198 :param allow_unsafe_options: 

1199 Allow unsafe options, like ``--upload-pack`` or ``--exec``. 

1200 

1201 :param allow_unsafe_protocols: 

1202 Allow unsafe protocols to be used, like ``ext``. Positional arguments 

1203 and split short-option values are checked. 

1204 """ 

1205 if not allow_unsafe_options: 

1206 candidate_options = self._option_candidates(args, kwargs) 

1207 Git.check_unsafe_options(options=candidate_options, unsafe_options=self.unsafe_git_ls_remote_options) 

1208 if not allow_unsafe_protocols: 

1209 self._check_unsafe_protocols_in_args(args, kwargs) 

1210 return self._call_process("ls_remote", *args, **kwargs) 

1211 

1212 @property 

1213 def working_dir(self) -> Union[None, PathLike]: 

1214 """:return: Git directory we are working on""" 

1215 return self._working_dir 

1216 

1217 @property 

1218 def version_info(self) -> Tuple[int, ...]: 

1219 """ 

1220 :return: Tuple with integers representing the major, minor and additional 

1221 version numbers as parsed from :manpage:`git-version(1)`. Up to four fields 

1222 are used. 

1223 

1224 This value is generated on demand and is cached. 

1225 """ 

1226 # Refreshing is global, but version_info caching is per-instance. 

1227 refresh_token = self._refresh_token # Copy token in case of concurrent refresh. 

1228 

1229 # Use the cached version if obtained after the most recent refresh. 

1230 if self._version_info_token is refresh_token: 

1231 assert self._version_info is not None, "Bug: corrupted token-check state" 

1232 return self._version_info 

1233 

1234 # Run "git version" and parse it. 

1235 process_version = self._call_process("version") 

1236 version_string = process_version.split(" ")[2] 

1237 version_fields = version_string.split(".")[:4] 

1238 leading_numeric_fields = itertools.takewhile(str.isdigit, version_fields) 

1239 self._version_info = tuple(map(int, leading_numeric_fields)) 

1240 

1241 # This value will be considered valid until the next refresh. 

1242 self._version_info_token = refresh_token 

1243 return self._version_info 

1244 

1245 @overload 

1246 def execute( 

1247 self, 

1248 command: Union[str, Sequence[Any]], 

1249 istream: Union[None, int, BinaryIO] = None, 

1250 *, 

1251 as_process: Literal[True], 

1252 **subprocess_kwargs: Any, 

1253 ) -> "AutoInterrupt": ... 

1254 

1255 @overload 

1256 def execute( 

1257 self, 

1258 command: Union[str, Sequence[Any]], 

1259 istream: Union[None, int, BinaryIO] = None, 

1260 *, 

1261 as_process: Literal[False] = False, 

1262 with_extended_output: Literal[False] = False, 

1263 stdout_as_string: Literal[True] = True, 

1264 with_stdout: Literal[True] = True, 

1265 **subprocess_kwargs: Any, 

1266 ) -> str: ... 

1267 

1268 @overload 

1269 def execute( 

1270 self, 

1271 command: Union[str, Sequence[Any]], 

1272 istream: Union[None, int, BinaryIO] = None, 

1273 *, 

1274 as_process: Literal[False] = False, 

1275 with_extended_output: Literal[False] = False, 

1276 stdout_as_string: Literal[False], 

1277 universal_newlines: Literal[False] = False, 

1278 with_stdout: Literal[True] = True, 

1279 **subprocess_kwargs: Any, 

1280 ) -> bytes: ... 

1281 

1282 @overload 

1283 def execute( 

1284 self, 

1285 command: Union[str, Sequence[Any]], 

1286 istream: Union[None, int, BinaryIO] = None, 

1287 *, 

1288 as_process: Literal[False] = False, 

1289 with_extended_output: Literal[True], 

1290 stdout_as_string: Literal[True] = True, 

1291 with_stdout: Literal[True] = True, 

1292 **subprocess_kwargs: Any, 

1293 ) -> Tuple[int, str, str]: ... 

1294 

1295 @overload 

1296 def execute( 

1297 self, 

1298 command: Union[str, Sequence[Any]], 

1299 istream: Union[None, int, BinaryIO] = None, 

1300 *, 

1301 as_process: Literal[False] = False, 

1302 with_extended_output: Literal[True], 

1303 stdout_as_string: Literal[False], 

1304 universal_newlines: Literal[False] = False, 

1305 with_stdout: Literal[True] = True, 

1306 **subprocess_kwargs: Any, 

1307 ) -> Tuple[int, bytes, str]: ... 

1308 

1309 @overload 

1310 def execute( 

1311 self, 

1312 command: Union[str, Sequence[Any]], 

1313 istream: Union[None, int, BinaryIO] = None, 

1314 *, 

1315 as_process: Literal[False] = False, 

1316 with_extended_output: Literal[True], 

1317 **subprocess_kwargs: Any, 

1318 ) -> Tuple[int, Union[str, bytes, None], str]: ... 

1319 

1320 @overload 

1321 def execute( 

1322 self, 

1323 command: Union[str, Sequence[Any]], 

1324 istream: Union[None, int, BinaryIO] = None, 

1325 *, 

1326 as_process: Literal[False] = False, 

1327 with_extended_output: Literal[False] = False, 

1328 **subprocess_kwargs: Any, 

1329 ) -> Union[str, bytes, None]: ... 

1330 

1331 @overload 

1332 def execute( 

1333 self, 

1334 command: Union[str, Sequence[Any]], 

1335 istream: Union[None, int, BinaryIO] = None, 

1336 with_extended_output: bool = False, 

1337 with_exceptions: bool = True, 

1338 as_process: bool = False, 

1339 output_stream: Union[None, BinaryIO] = None, 

1340 stdout_as_string: bool = True, 

1341 kill_after_timeout: Union[None, float] = None, 

1342 with_stdout: bool = True, 

1343 universal_newlines: bool = False, 

1344 shell: Union[None, bool] = None, 

1345 env: Union[None, Mapping[str, str]] = None, 

1346 max_chunk_size: int = io.DEFAULT_BUFFER_SIZE, 

1347 strip_newline_in_stdout: bool = True, 

1348 **subprocess_kwargs: Any, 

1349 ) -> Union[None, str, bytes, Tuple[int, Union[str, bytes, None], str], AutoInterrupt]: ... 

1350 

1351 def execute( 

1352 self, 

1353 command: Union[str, Sequence[Any]], 

1354 istream: Union[None, int, BinaryIO] = None, 

1355 with_extended_output: bool = False, 

1356 with_exceptions: bool = True, 

1357 as_process: bool = False, 

1358 output_stream: Union[None, BinaryIO] = None, 

1359 stdout_as_string: bool = True, 

1360 kill_after_timeout: Union[None, float] = None, 

1361 with_stdout: bool = True, 

1362 universal_newlines: bool = False, 

1363 shell: Union[None, bool] = None, 

1364 env: Union[None, Mapping[str, str]] = None, 

1365 max_chunk_size: int = io.DEFAULT_BUFFER_SIZE, 

1366 strip_newline_in_stdout: bool = True, 

1367 **subprocess_kwargs: Any, 

1368 ) -> Union[None, str, bytes, Tuple[int, Union[str, bytes, None], str], AutoInterrupt]: 

1369 R"""Handle executing the command, and consume and return the returned 

1370 information (stdout). 

1371 

1372 :param command: 

1373 The command to execute. A sequence of program arguments is recommended. 

1374 A string is also accepted, but its meaning is strongly platform-dependent. 

1375 

1376 By default, a shell is not used. On Unix-like systems, a string is the whole 

1377 program name (so ``"git log -n 1"`` raises :class:`GitCommandNotFound`). On 

1378 Windows, the program parses the arguments itself, so multi-word strings can 

1379 work but are not portable. 

1380 

1381 Avoid ``shell=True`` (and :attr:`Git.USE_SHELL`): this runs the command in 

1382 a shell, which is generally unsafe. The shell interprets metacharacters 

1383 such as ``;``, ``|``, ``&``, ``$(...)``, ``$VAR``, ``%VAR%``, and ``^`` 

1384 (depending on the platform) as syntax. Any untrusted text in the command 

1385 can then execute arbitrary OS commands. See :attr:`Git.USE_SHELL`. 

1386 

1387 Producing a sequence automatically by :func:`shlex.split` and passing it 

1388 as the command is far safer than ``shell=True``. But :func:`shlex.split` 

1389 parses POSIX shell syntax on all systems, and the result is still unsafe 

1390 for anything but *fixed, fully trusted* strings. Do not use it on strings 

1391 built by interpolating values: whitespace or quoting in an untrusted value 

1392 can still inject arguments. For input derived in any way from untrusted 

1393 data, build the argument sequence yourself, while ensuring each argument 

1394 is fully sanitized. 

1395 

1396 :param istream: 

1397 Standard input filehandle passed to :class:`subprocess.Popen`. 

1398 

1399 :param with_extended_output: 

1400 Whether to return a (status, stdout, stderr) tuple. 

1401 

1402 :param with_exceptions: 

1403 Whether to raise an exception when git returns a non-zero status. 

1404 

1405 :param as_process: 

1406 Whether to return the created process instance directly from which 

1407 streams can be read on demand. This will render `with_extended_output` 

1408 and `with_exceptions` ineffective - the caller will have to deal with 

1409 the details. It is important to note that the process will be placed 

1410 into an :class:`AutoInterrupt` wrapper that will interrupt the process 

1411 once it goes out of scope. If you use the command in iterators, you 

1412 should pass the whole process instance instead of a single stream. 

1413 

1414 :param output_stream: 

1415 If set to a file-like object, data produced by the git command will be 

1416 copied to the given stream instead of being returned as a string. 

1417 This feature only has any effect if `as_process` is ``False``. 

1418 

1419 :param stdout_as_string: 

1420 If ``False``, the command's standard output will be bytes. Otherwise, it 

1421 will be decoded into a string using the default encoding (usually UTF-8). 

1422 The latter can fail, if the output contains binary data. 

1423 

1424 :param kill_after_timeout: 

1425 Specifies a timeout in seconds for the git command, after which the process 

1426 should be killed. This will have no effect if `as_process` is set to 

1427 ``True``. It is set to ``None`` by default and will let the process run 

1428 until the timeout is explicitly specified. Uses of this feature should be 

1429 carefully considered, due to the following limitations: 

1430 

1431 1. This feature is not supported at all on Windows. 

1432 2. Enumerating child processes requires ``pgrep -P``, or a ``ps`` command 

1433 supporting the POSIX ``-A`` and ``-o`` options if ``pgrep`` is not 

1434 installed (``ps -ef`` on Cygwin). Effectiveness may vary on systems 

1435 without these commands. 

1436 3. Descendants are enumerated before signalling. Processes that detach 

1437 or spawn after enumeration may not receive signals. 

1438 4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side 

1439 effects on a repository. For example, stale locks in case of 

1440 :manpage:`git-gc(1)` could render the repository incapable of accepting 

1441 changes until the lock is manually removed. 

1442 

1443 :param with_stdout: 

1444 If ``True``, default ``True``, we open stdout on the created process. 

1445 

1446 :param universal_newlines: 

1447 If ``True``, pipes will be opened as text, and lines are split at all known 

1448 line endings. 

1449 

1450 :param shell: 

1451 Whether to invoke commands through a shell 

1452 (see :class:`Popen(..., shell=True) <subprocess.Popen>`). 

1453 If this is not ``None``, it overrides :attr:`USE_SHELL`. 

1454 

1455 Passing ``shell=True`` to this or any other GitPython function should be 

1456 avoided, as it is unsafe under most circumstances. This is because it is 

1457 typically not feasible to fully consider and account for the effect of shell 

1458 expansions, especially when passing ``shell=True`` to other methods that 

1459 forward it to :meth:`Git.execute`. Passing ``shell=True`` is also no longer 

1460 needed (nor useful) to work around any known operating system specific 

1461 issues. 

1462 

1463 On Unix-like systems, when migrating away from passing string commands with 

1464 ``shell=True``, :func:`shlex.split` may serve as a transitional step in rare 

1465 cases, with extreme care. (Drop ``shell=True`` and pass the resulting 

1466 sequence as the command.) See the `command` parameter above on the risks. 

1467 

1468 :param env: 

1469 A dictionary of environment variables to be passed to 

1470 :class:`subprocess.Popen`. 

1471 

1472 :param max_chunk_size: 

1473 Maximum number of bytes in one chunk of data passed to the `output_stream` 

1474 in one invocation of its ``write()`` method. If the given number is not 

1475 positive then the default value is used. 

1476 

1477 :param strip_newline_in_stdout: 

1478 Whether to strip the trailing ``\n`` of the command stdout. 

1479 

1480 :param subprocess_kwargs: 

1481 Keyword arguments to be passed to :class:`subprocess.Popen`. Please note 

1482 that some of the valid kwargs are already set by this method; the ones you 

1483 specify may not be the same ones. 

1484 

1485 :return: 

1486 * str(output), if `extended_output` is ``False`` (Default) 

1487 * tuple(int(status), str(stdout), str(stderr)), 

1488 if `extended_output` is ``True`` 

1489 

1490 If `output_stream` is ``True``, the stdout value will be your output stream: 

1491 

1492 * output_stream, if `extended_output` is ``False`` 

1493 * tuple(int(status), output_stream, str(stderr)), 

1494 if `extended_output` is ``True`` 

1495 

1496 Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent 

1497 output regardless of system language. 

1498 

1499 :raise git.exc.GitCommandError: 

1500 

1501 :note: 

1502 If you add additional keyword arguments to the signature of this method, you 

1503 must update the ``execute_kwargs`` variable housed in this module. 

1504 """ 

1505 # Remove password for the command if present. 

1506 redacted_command = remove_password_if_present(command) 

1507 if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process): 

1508 _logger.info(" ".join(redacted_command)) 

1509 

1510 # Allow the user to have the command executed in their working dir. 

1511 try: 

1512 cwd = self._working_dir or os.getcwd() # type: Optional[PathLike] 

1513 if not os.access(str(cwd), os.X_OK): 

1514 cwd = None 

1515 except FileNotFoundError: 

1516 cwd = None 

1517 

1518 # Start the process. 

1519 inline_env = env 

1520 env = os.environ.copy() 

1521 # Attempt to force all output to plain ASCII English, which is what some parsing 

1522 # code may expect. 

1523 # According to https://askubuntu.com/a/311796, we are setting LANGUAGE as well 

1524 # just to be sure. 

1525 env["LANGUAGE"] = "C" 

1526 env["LC_ALL"] = "C" 

1527 env.update(self._environment) 

1528 if inline_env is not None: 

1529 env.update(inline_env) 

1530 

1531 if sys.platform == "win32": 

1532 if kill_after_timeout is not None: 

1533 raise GitCommandError( 

1534 redacted_command, 

1535 '"kill_after_timeout" feature is not supported on Windows.', 

1536 ) 

1537 cmd_not_found_exception = OSError 

1538 else: 

1539 cmd_not_found_exception = FileNotFoundError 

1540 # END handle 

1541 

1542 stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb") 

1543 if shell is None: 

1544 # Get the value of USE_SHELL with no deprecation warning. Do this without 

1545 # warnings.catch_warnings, to avoid a race condition with application code 

1546 # configuring warnings. The value could be looked up in type(self).__dict__ 

1547 # or Git.__dict__, but those can break under some circumstances. This works 

1548 # the same as self.USE_SHELL in more situations; see Git.__getattribute__. 

1549 shell = super().__getattribute__("USE_SHELL") 

1550 _logger.debug( 

1551 "Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)", 

1552 redacted_command, 

1553 cwd, 

1554 "<valid stream>" if istream else "None", 

1555 shell, 

1556 universal_newlines, 

1557 ) 

1558 try: 

1559 proc = safer_popen( 

1560 command, 

1561 env=env, 

1562 cwd=cwd, 

1563 bufsize=-1, 

1564 stdin=(istream or DEVNULL), 

1565 stderr=PIPE, 

1566 stdout=stdout_sink, 

1567 shell=shell, 

1568 universal_newlines=universal_newlines, 

1569 encoding=defenc if universal_newlines else None, 

1570 **subprocess_kwargs, 

1571 ) 

1572 except cmd_not_found_exception as err: 

1573 raise GitCommandNotFound(redacted_command, err) from err 

1574 else: 

1575 # Replace with a typeguard for Popen[bytes]? 

1576 proc.stdout = cast(BinaryIO, proc.stdout) 

1577 proc.stderr = cast(BinaryIO, proc.stderr) 

1578 

1579 if as_process: 

1580 return self.AutoInterrupt(proc, command) 

1581 

1582 watchdog: Optional[threading.Timer] = None 

1583 kill_check: Optional[threading.Event] = None 

1584 timeout_error: Optional[Callable[[], Union[str, bytes]]] = None 

1585 

1586 if sys.platform != "win32" and kill_after_timeout is not None: 

1587 # Help mypy figure out this is not None even when used inside communicate(). 

1588 timeout = kill_after_timeout 

1589 

1590 def kill_process(pid: int) -> None: 

1591 if _kill_process(pid): 

1592 assert kill_check is not None 

1593 kill_check.set() 

1594 

1595 def make_timeout_error() -> Union[str, bytes]: 

1596 err = f'Timeout: the command "{" ".join(redacted_command)}" did not complete in {timeout:g} secs.' 

1597 return err if universal_newlines else err.encode(defenc) 

1598 

1599 def communicate() -> Tuple[Union[str, bytes, None], Union[str, bytes, None]]: 

1600 assert watchdog is not None 

1601 assert kill_check is not None 

1602 watchdog.start() 

1603 out, err = proc.communicate() 

1604 watchdog.cancel() 

1605 if kill_check.is_set(): 

1606 err = make_timeout_error() 

1607 return out, err 

1608 

1609 # END helpers 

1610 

1611 kill_check = threading.Event() 

1612 watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,)) 

1613 timeout_error = make_timeout_error 

1614 else: 

1615 communicate = proc.communicate 

1616 

1617 # Wait for the process to return. 

1618 status = 0 

1619 stdout_value: Union[str, bytes, None] = b"" 

1620 stderr_value: Union[str, bytes, None] = b"" 

1621 newline = "\n" if universal_newlines else b"\n" 

1622 try: 

1623 if output_stream is None: 

1624 stdout_value, stderr_value = communicate() 

1625 # Strip trailing "\n". 

1626 if stdout_value is not None and stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type] 

1627 stdout_value = stdout_value[:-1] 

1628 if stderr_value is not None and stderr_value.endswith(newline): # type: ignore[arg-type] 

1629 stderr_value = stderr_value[:-1] 

1630 

1631 status = proc.returncode 

1632 else: 

1633 max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE 

1634 if watchdog is not None: 

1635 watchdog.start() 

1636 try: 

1637 if proc.stdout is not None: 

1638 stream_copy(proc.stdout, output_stream, max_chunk_size) 

1639 stdout_value = proc.stdout.read() 

1640 if proc.stderr is not None: 

1641 stderr_value = proc.stderr.read() 

1642 status = proc.wait() 

1643 finally: 

1644 if watchdog is not None: 

1645 watchdog.cancel() 

1646 # Strip trailing "\n". 

1647 if stderr_value is not None and stderr_value.endswith(newline): # type: ignore[arg-type] 

1648 stderr_value = stderr_value[:-1] 

1649 if kill_check is not None and kill_check.is_set(): 

1650 assert timeout_error is not None 

1651 stderr_value = timeout_error() 

1652 # END stdout handling 

1653 finally: 

1654 if proc.stdout is not None: 

1655 proc.stdout.close() 

1656 if proc.stderr is not None: 

1657 proc.stderr.close() 

1658 

1659 if self.GIT_PYTHON_TRACE == "full": 

1660 cmdstr = " ".join(redacted_command) 

1661 

1662 def as_text(stdout_value: Union[bytes, str, None]) -> str: 

1663 return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>" 

1664 

1665 # END as_text 

1666 

1667 if stderr_value: 

1668 _logger.info( 

1669 "%s -> %d; stdout: '%s'; stderr: '%s'", 

1670 cmdstr, 

1671 status, 

1672 as_text(stdout_value), 

1673 safe_decode(stderr_value), 

1674 ) 

1675 elif stdout_value: 

1676 _logger.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value)) 

1677 else: 

1678 _logger.info("%s -> %d", cmdstr, status) 

1679 # END handle debug printing 

1680 

1681 if with_exceptions and status != 0: 

1682 raise GitCommandError(redacted_command, status, stderr_value, stdout_value) 

1683 

1684 if isinstance(stdout_value, bytes) and stdout_as_string: # Could also be output_stream. 

1685 stdout_value = safe_decode(stdout_value) 

1686 

1687 # stderr is always captured through PIPE. 

1688 assert stderr_value is not None 

1689 # Allow access to the command's status code. 

1690 if with_extended_output: 

1691 return (status, stdout_value, safe_decode(stderr_value)) 

1692 else: 

1693 return stdout_value 

1694 

1695 def environment(self) -> Dict[str, str]: 

1696 return self._environment 

1697 

1698 def update_environment(self, **kwargs: Any) -> Dict[str, Union[str, None]]: 

1699 """Set environment variables for future git invocations. Return all changed 

1700 values in a format that can be passed back into this function to revert the 

1701 changes. 

1702 

1703 Examples:: 

1704 

1705 old_env = self.update_environment(PWD='/tmp') 

1706 self.update_environment(**old_env) 

1707 

1708 :param kwargs: 

1709 Environment variables to use for git processes. 

1710 

1711 :return: 

1712 Dict that maps environment variables to their old values 

1713 """ 

1714 old_env = {} 

1715 for key, value in kwargs.items(): 

1716 # Set value if it is None. 

1717 if value is not None: 

1718 old_env[key] = self._environment.get(key) 

1719 self._environment[key] = value 

1720 # Remove key from environment if its value is None. 

1721 elif key in self._environment: 

1722 old_env[key] = self._environment[key] 

1723 del self._environment[key] 

1724 return old_env 

1725 

1726 @contextlib.contextmanager 

1727 def custom_environment(self, **kwargs: Any) -> Iterator[None]: 

1728 """A context manager around the above :meth:`update_environment` method to 

1729 restore the environment back to its previous state after operation. 

1730 

1731 Examples:: 

1732 

1733 with self.custom_environment(GIT_SSH='/bin/ssh_wrapper'): 

1734 repo.remotes.origin.fetch() 

1735 

1736 :param kwargs: 

1737 See :meth:`update_environment`. 

1738 """ 

1739 old_env = self.update_environment(**kwargs) 

1740 try: 

1741 yield 

1742 finally: 

1743 self.update_environment(**old_env) 

1744 

1745 def transform_kwarg(self, name: str, value: Any, split_single_char_options: bool) -> List[str]: 

1746 if len(name) == 1: 

1747 if value is True: 

1748 return ["-%s" % name] 

1749 elif value not in (False, None): 

1750 if split_single_char_options: 

1751 return ["-%s" % name, "%s" % value] 

1752 else: 

1753 return ["-%s%s" % (name, value)] 

1754 else: 

1755 if value is True: 

1756 return ["--%s" % dashify(name)] 

1757 elif value is not False and value is not None: 

1758 return ["--%s=%s" % (dashify(name), value)] 

1759 return [] 

1760 

1761 def transform_kwargs(self, split_single_char_options: bool = True, **kwargs: Any) -> List[str]: 

1762 """Transform Python-style kwargs into git command line options.""" 

1763 args = [] 

1764 for k, v in kwargs.items(): 

1765 if isinstance(v, (list, tuple)): 

1766 for value in v: 

1767 args += self.transform_kwarg(k, value, split_single_char_options) 

1768 else: 

1769 args += self.transform_kwarg(k, v, split_single_char_options) 

1770 return args 

1771 

1772 @classmethod 

1773 def _unpack_args(cls, arg_list: Sequence[Any]) -> List[str]: 

1774 outlist = [] 

1775 if isinstance(arg_list, (list, tuple)): 

1776 for arg in arg_list: 

1777 outlist.extend(cls._unpack_args(arg)) 

1778 else: 

1779 outlist.append(str(arg_list)) 

1780 

1781 return outlist 

1782 

1783 def __call__(self, **kwargs: Any) -> "Git": 

1784 """Specify command line options to the git executable for a subcommand call. 

1785 

1786 :param kwargs: 

1787 A dict of keyword arguments. 

1788 These arguments are passed as in :meth:`_call_process`, but will be passed 

1789 to the git command rather than the subcommand. 

1790 

1791 Examples:: 

1792 

1793 git(work_tree='/tmp').difftool() 

1794 """ 

1795 self._git_options = self.transform_kwargs(split_single_char_options=True, **kwargs) 

1796 return self 

1797 

1798 @overload 

1799 def _call_process( 

1800 self, method: str, *args: None, **kwargs: None 

1801 ) -> str: ... # If no args were given, execute the call with all defaults. 

1802 

1803 @overload 

1804 def _call_process( 

1805 self, 

1806 method: str, 

1807 istream: int, 

1808 as_process: Literal[True], 

1809 *args: Any, 

1810 **kwargs: Any, 

1811 ) -> "Git.AutoInterrupt": ... 

1812 

1813 @overload 

1814 def _call_process( 

1815 self, method: str, *args: Any, **kwargs: Any 

1816 ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], "Git.AutoInterrupt"]: ... 

1817 

1818 def _call_process( 

1819 self, method: str, *args: Any, **kwargs: Any 

1820 ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], "Git.AutoInterrupt"]: 

1821 """Run the given git command with the specified arguments and return the result 

1822 as a string. 

1823 

1824 :param method: 

1825 The command. Contained ``_`` characters will be converted to hyphens, such 

1826 as in ``ls_files`` to call ``ls-files``. 

1827 

1828 :param args: 

1829 The list of arguments. If ``None`` is included, it will be pruned. 

1830 This allows your commands to call git more conveniently, as ``None`` is 

1831 realized as non-existent. 

1832 

1833 Positional arguments may intentionally contain command options. Higher-level 

1834 APIs must separate their operands with ``--`` where the Git command supports 

1835 it, or reject option-shaped operands where Git reparses them internally (for 

1836 example, ``pull`` and ``remote update``). Shell quoting cannot prevent Git 

1837 from interpreting a leading-dash argument as an option. 

1838 

1839 :param kwargs: 

1840 Contains key-values for the following: 

1841 

1842 - The :meth:`execute()` kwds, as listed in ``execute_kwargs``. 

1843 - "Command options" to be converted by :meth:`transform_kwargs`. 

1844 - The ``insert_kwargs_after`` key which its value must match one of 

1845 ``*args``. 

1846 

1847 It also contains any command options, to be appended after the matched arg. 

1848 

1849 Examples:: 

1850 

1851 git.rev_list('master', max_count=10, header=True) 

1852 

1853 turns into:: 

1854 

1855 git rev-list --max-count=10 --header master 

1856 

1857 :return: 

1858 Same as :meth:`execute`. If no args are given, used :meth:`execute`'s 

1859 default (especially ``as_process = False``, ``stdout_as_string = True``) and 

1860 return :class:`str`. 

1861 """ 

1862 # Handle optional arguments prior to calling transform_kwargs. 

1863 # Otherwise these'll end up in args, which is bad. 

1864 exec_kwargs = {k: v for k, v in kwargs.items() if k in execute_kwargs} 

1865 opts_kwargs = {k: v for k, v in kwargs.items() if k not in execute_kwargs} 

1866 

1867 insert_after_this_arg = opts_kwargs.pop("insert_kwargs_after", None) 

1868 

1869 # Prepare the argument list. 

1870 

1871 opt_args = self.transform_kwargs(**opts_kwargs) 

1872 ext_args = self._unpack_args([a for a in args if a is not None]) 

1873 

1874 if insert_after_this_arg is None: 

1875 args_list = opt_args + ext_args 

1876 else: 

1877 try: 

1878 index = ext_args.index(insert_after_this_arg) 

1879 except ValueError as err: 

1880 raise ValueError( 

1881 "Couldn't find argument '%s' in args %s to insert cmd options after" 

1882 % (insert_after_this_arg, str(ext_args)) 

1883 ) from err 

1884 # END handle error 

1885 args_list = ext_args[: index + 1] + opt_args + ext_args[index + 1 :] 

1886 # END handle opts_kwargs 

1887 

1888 call = [self.GIT_PYTHON_GIT_EXECUTABLE] 

1889 

1890 # Add persistent git options. 

1891 call.extend(self._persistent_git_options) 

1892 

1893 # Add the git options, then reset to empty to avoid side effects. 

1894 call.extend(self._git_options) 

1895 self._git_options = () 

1896 

1897 call.append(dashify(method)) 

1898 call.extend(args_list) 

1899 

1900 return self.execute(call, **exec_kwargs) 

1901 

1902 def _parse_object_header(self, header_line: str) -> Tuple[str, str, int]: 

1903 """ 

1904 :param header_line: 

1905 A line of the form:: 

1906 

1907 <hex_sha> type_string size_as_int 

1908 

1909 :return: 

1910 (hex_sha, type_string, size_as_int) 

1911 

1912 :raise ValueError: 

1913 If the header contains indication for an error due to incorrect input sha. 

1914 """ 

1915 tokens = header_line.split() 

1916 if len(tokens) != 3: 

1917 if not tokens: 

1918 err_msg = ( 

1919 f"SHA is empty, possible dubious ownership in the repository " 

1920 f"""at {self._working_dir}.\n If this is unintended run:\n\n """ 

1921 f""" "git config --global --add safe.directory {self._working_dir}" """ 

1922 ) 

1923 raise ValueError(err_msg) 

1924 else: 

1925 raise ValueError("SHA %s could not be resolved, git returned: %r" % (tokens[0], header_line.strip())) 

1926 # END handle actual return value 

1927 # END error handling 

1928 

1929 if len(tokens[0]) != 40: 

1930 raise ValueError("Failed to parse header: %r" % header_line) 

1931 return (tokens[0], tokens[1], int(tokens[2])) 

1932 

1933 def _prepare_ref(self, ref: object) -> bytes: 

1934 # Required for command to separate refs on stdin, as bytes. 

1935 if isinstance(ref, bytes): 

1936 # Assume 40 bytes hexsha - bin-to-ascii for some reason returns bytes, not text. 

1937 refstr: str = ref.decode("ascii") 

1938 elif not isinstance(ref, str): 

1939 refstr = str(ref) # Could be ref-object. 

1940 else: 

1941 refstr = ref 

1942 

1943 # A line feed terminates a request, so one object name must be one line. An 

1944 # embedded one would queue a second request on the persistent command while 

1945 # only one response line is read back, leaving every later call one response 

1946 # behind, answered with the header of an object it did not ask for. 

1947 if refstr.endswith("\n"): 

1948 refstr = refstr[:-1] 

1949 if "\n" in refstr: 

1950 raise ValueError("Object name %r contains a line feed" % refstr) 

1951 return (refstr + "\n").encode(defenc) 

1952 

1953 def _get_persistent_cmd(self, attr_name: str, cmd_name: str, *args: Any, **kwargs: Any) -> "Git.AutoInterrupt": 

1954 cur_val = getattr(self, attr_name) 

1955 if cur_val is not None: 

1956 return cur_val 

1957 

1958 options = {"istream": PIPE, "as_process": True} 

1959 options.update(kwargs) 

1960 

1961 cmd = self._call_process(cmd_name, *args, **options) 

1962 setattr(self, attr_name, cmd) 

1963 cmd = cast("Git.AutoInterrupt", cmd) 

1964 return cmd 

1965 

1966 def __get_object_header(self, cmd: "Git.AutoInterrupt", ref: Union[str, bytes]) -> Tuple[str, str, int]: 

1967 if cmd.stdin and cmd.stdout: 

1968 cmd.stdin.write(self._prepare_ref(ref)) 

1969 cmd.stdin.flush() 

1970 return self._parse_object_header(cmd.stdout.readline()) 

1971 else: 

1972 raise ValueError("cmd stdin was empty") 

1973 

1974 def get_object_header(self, ref: Union[str, bytes]) -> Tuple[str, str, int]: 

1975 """Use this method to quickly examine the type and size of the object behind the 

1976 given ref. 

1977 

1978 :note: 

1979 The method will only suffer from the costs of command invocation once and 

1980 reuses the command in subsequent calls. 

1981 

1982 :return: 

1983 (hexsha, type_string, size_as_int) 

1984 """ 

1985 cmd = self._get_persistent_cmd("cat_file_header", "cat_file", batch_check=True) 

1986 return self.__get_object_header(cmd, ref) 

1987 

1988 def get_object_data(self, ref: Union[str, bytes]) -> Tuple[str, str, int, bytes]: 

1989 """Similar to :meth:`get_object_header`, but returns object data as well. 

1990 

1991 :return: 

1992 (hexsha, type_string, size_as_int, data_string) 

1993 

1994 :note: 

1995 Not threadsafe. 

1996 """ 

1997 hexsha, typename, size, stream = self.stream_object_data(ref) 

1998 data = stream.read(size) 

1999 del stream 

2000 return (hexsha, typename, size, data) 

2001 

2002 def stream_object_data(self, ref: Union[str, bytes]) -> Tuple[str, str, int, "Git.CatFileContentStream"]: 

2003 """Similar to :meth:`get_object_data`, but returns the data as a stream. 

2004 

2005 :return: 

2006 (hexsha, type_string, size_as_int, stream) 

2007 

2008 :note: 

2009 This method is not threadsafe. You need one independent :class:`Git` 

2010 instance per thread to be safe! 

2011 """ 

2012 cmd = self._get_persistent_cmd("cat_file_all", "cat_file", batch=True) 

2013 hexsha, typename, size = self.__get_object_header(cmd, ref) 

2014 cmd_stdout = cmd.stdout if cmd.stdout is not None else io.BytesIO() 

2015 return (hexsha, typename, size, self.CatFileContentStream(size, cmd_stdout)) 

2016 

2017 def clear_cache(self) -> "Git": 

2018 """Clear all kinds of internal caches to release resources. 

2019 

2020 Currently persistent commands will be interrupted. 

2021 

2022 :return: 

2023 self 

2024 """ 

2025 for cmd in (self.cat_file_all, self.cat_file_header): 

2026 if cmd: 

2027 cmd.__del__() 

2028 

2029 self.cat_file_all = None 

2030 self.cat_file_header = None 

2031 return self