GitPython 3.1.61__py3-none-any.whl → 3.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
git/__init__.py CHANGED
@@ -86,7 +86,7 @@ __all__ = [
86
86
  "to_hex_sha",
87
87
  ]
88
88
 
89
- __version__ = '3.1.61'
89
+ __version__ = '3.2.0'
90
90
 
91
91
  from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Tuple, Union
92
92
 
@@ -295,6 +295,6 @@ def refresh(path: Optional[PathLike] = None) -> None:
295
295
  try:
296
296
  refresh()
297
297
  except Exception as _exc:
298
- raise ImportError("Failed to initialize: {0}".format(_exc)) from _exc
298
+ raise ImportError(f"Failed to initialize: {_exc}") from _exc
299
299
 
300
300
  # } END initialize git executable path
git/cmd.py CHANGED
@@ -100,7 +100,7 @@ _logger = logging.getLogger(__name__)
100
100
 
101
101
 
102
102
  def handle_process_output(
103
- process: "Git.AutoInterrupt" | Popen,
103
+ process: Union["Git.AutoInterrupt", Popen],
104
104
  stdout_handler: Union[
105
105
  None,
106
106
  Callable[[AnyStr], None],
@@ -395,9 +395,7 @@ class _AutoInterrupt:
395
395
  :raise git.exc.GitCommandError:
396
396
  If the return status is not 0.
397
397
  """
398
- if stderr is None:
399
- stderr_b = b""
400
- stderr_b = force_bytes(data=stderr, encoding="utf-8")
398
+ stderr_b = force_bytes(data=stderr, encoding="utf-8") or b""
401
399
  status: Union[int, None]
402
400
  if self.proc is not None:
403
401
  status = self.proc.wait()
@@ -647,7 +645,9 @@ class Git(metaclass=_GitMeta):
647
645
  "_version_info_token",
648
646
  )
649
647
 
650
- re_unsafe_protocol = re.compile(r"(.+)::.+")
648
+ # Match Git's leading transport selector, including an empty helper name.
649
+ # Git also selects the command-executing ext helper for an ext:// URL.
650
+ re_unsafe_protocol = re.compile(r"([A-Za-z0-9][A-Za-z0-9+.-]*|)::|ext://")
651
651
 
652
652
  unsafe_git_ls_remote_options = [
653
653
  # This option allows arbitrary command execution in git-ls-remote.
@@ -947,7 +947,8 @@ class Git(metaclass=_GitMeta):
947
947
 
948
948
  Apart from the usual protocols (http, git, ssh), Git allows "remote helpers"
949
949
  that have the form ``<transport>::<address>``. One of these helpers (``ext::``)
950
- can be used to invoke any arbitrary command.
950
+ can be used to invoke any arbitrary command. Git also selects that helper
951
+ for ``ext://`` URLs and interprets the URL as a command path.
951
952
 
952
953
  See:
953
954
 
@@ -956,11 +957,19 @@ class Git(metaclass=_GitMeta):
956
957
  """
957
958
  match = cls.re_unsafe_protocol.match(url)
958
959
  if match:
959
- protocol = match.group(1)
960
+ protocol = match.group(0)
960
961
  raise UnsafeProtocolError(
961
- f"The `{protocol}::` protocol looks suspicious, use `allow_unsafe_protocols=True` to allow it."
962
+ f"The `{protocol}` protocol looks suspicious, use `allow_unsafe_protocols=True` to allow it."
962
963
  )
963
964
 
965
+ def _check_unsafe_protocols_in_args(self, args: Sequence[Any], kwargs: Mapping[str, Any]) -> None:
966
+ """Check positional operands and standalone values in rendered command options.
967
+
968
+ A short flag's split value can become the repository operand before ``--``.
969
+ """
970
+ for arg in self._unpack_args(args) + self.transform_kwargs(**kwargs):
971
+ self.check_unsafe_protocols(arg)
972
+
964
973
  @classmethod
965
974
  def _canonicalize_option_name(cls, option: str) -> str:
966
975
  """Return the option name used for unsafe-option checks.
@@ -1131,16 +1140,23 @@ class Git(metaclass=_GitMeta):
1131
1140
  self,
1132
1141
  *args: Any,
1133
1142
  allow_unsafe_options: bool = False,
1143
+ allow_unsafe_protocols: bool = False,
1134
1144
  **kwargs: Any,
1135
1145
  ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], "Git.AutoInterrupt"]:
1136
1146
  """List references in a remote repository.
1137
1147
 
1138
1148
  :param allow_unsafe_options:
1139
1149
  Allow unsafe options, like ``--upload-pack`` or ``--exec``.
1150
+
1151
+ :param allow_unsafe_protocols:
1152
+ Allow unsafe protocols to be used, like ``ext``. Positional arguments
1153
+ and split short-option values are checked.
1140
1154
  """
1141
1155
  if not allow_unsafe_options:
1142
1156
  candidate_options = self._option_candidates(args, kwargs)
1143
1157
  Git.check_unsafe_options(options=candidate_options, unsafe_options=self.unsafe_git_ls_remote_options)
1158
+ if not allow_unsafe_protocols:
1159
+ self._check_unsafe_protocols_in_args(args, kwargs)
1144
1160
  return self._call_process("ls_remote", *args, **kwargs)
1145
1161
 
1146
1162
  @property
@@ -1180,52 +1196,112 @@ class Git(metaclass=_GitMeta):
1180
1196
  def execute(
1181
1197
  self,
1182
1198
  command: Union[str, Sequence[Any]],
1199
+ istream: Union[None, int, BinaryIO] = None,
1183
1200
  *,
1184
1201
  as_process: Literal[True],
1202
+ **subprocess_kwargs: Any,
1185
1203
  ) -> "AutoInterrupt": ...
1186
1204
 
1187
1205
  @overload
1188
1206
  def execute(
1189
1207
  self,
1190
1208
  command: Union[str, Sequence[Any]],
1209
+ istream: Union[None, int, BinaryIO] = None,
1191
1210
  *,
1192
1211
  as_process: Literal[False] = False,
1193
- stdout_as_string: Literal[True],
1194
- ) -> Union[str, Tuple[int, str, str]]: ...
1212
+ with_extended_output: Literal[False] = False,
1213
+ stdout_as_string: Literal[True] = True,
1214
+ with_stdout: Literal[True] = True,
1215
+ **subprocess_kwargs: Any,
1216
+ ) -> str: ...
1195
1217
 
1196
1218
  @overload
1197
1219
  def execute(
1198
1220
  self,
1199
1221
  command: Union[str, Sequence[Any]],
1222
+ istream: Union[None, int, BinaryIO] = None,
1200
1223
  *,
1201
1224
  as_process: Literal[False] = False,
1202
- stdout_as_string: Literal[False] = False,
1203
- ) -> Union[bytes, Tuple[int, bytes, str]]: ...
1225
+ with_extended_output: Literal[False] = False,
1226
+ stdout_as_string: Literal[False],
1227
+ universal_newlines: Literal[False] = False,
1228
+ with_stdout: Literal[True] = True,
1229
+ **subprocess_kwargs: Any,
1230
+ ) -> bytes: ...
1204
1231
 
1205
1232
  @overload
1206
1233
  def execute(
1207
1234
  self,
1208
1235
  command: Union[str, Sequence[Any]],
1236
+ istream: Union[None, int, BinaryIO] = None,
1209
1237
  *,
1210
- with_extended_output: Literal[False],
1211
- as_process: Literal[False],
1212
- stdout_as_string: Literal[True],
1213
- ) -> str: ...
1238
+ as_process: Literal[False] = False,
1239
+ with_extended_output: Literal[True],
1240
+ stdout_as_string: Literal[True] = True,
1241
+ with_stdout: Literal[True] = True,
1242
+ **subprocess_kwargs: Any,
1243
+ ) -> Tuple[int, str, str]: ...
1214
1244
 
1215
1245
  @overload
1216
1246
  def execute(
1217
1247
  self,
1218
1248
  command: Union[str, Sequence[Any]],
1249
+ istream: Union[None, int, BinaryIO] = None,
1219
1250
  *,
1220
- with_extended_output: Literal[False],
1221
- as_process: Literal[False],
1251
+ as_process: Literal[False] = False,
1252
+ with_extended_output: Literal[True],
1222
1253
  stdout_as_string: Literal[False],
1223
- ) -> bytes: ...
1254
+ universal_newlines: Literal[False] = False,
1255
+ with_stdout: Literal[True] = True,
1256
+ **subprocess_kwargs: Any,
1257
+ ) -> Tuple[int, bytes, str]: ...
1224
1258
 
1259
+ @overload
1225
1260
  def execute(
1226
1261
  self,
1227
1262
  command: Union[str, Sequence[Any]],
1228
- istream: Union[None, BinaryIO] = None,
1263
+ istream: Union[None, int, BinaryIO] = None,
1264
+ *,
1265
+ as_process: Literal[False] = False,
1266
+ with_extended_output: Literal[True],
1267
+ **subprocess_kwargs: Any,
1268
+ ) -> Tuple[int, Union[str, bytes, None], str]: ...
1269
+
1270
+ @overload
1271
+ def execute(
1272
+ self,
1273
+ command: Union[str, Sequence[Any]],
1274
+ istream: Union[None, int, BinaryIO] = None,
1275
+ *,
1276
+ as_process: Literal[False] = False,
1277
+ with_extended_output: Literal[False] = False,
1278
+ **subprocess_kwargs: Any,
1279
+ ) -> Union[str, bytes, None]: ...
1280
+
1281
+ @overload
1282
+ def execute(
1283
+ self,
1284
+ command: Union[str, Sequence[Any]],
1285
+ istream: Union[None, int, BinaryIO] = None,
1286
+ with_extended_output: bool = False,
1287
+ with_exceptions: bool = True,
1288
+ as_process: bool = False,
1289
+ output_stream: Union[None, BinaryIO] = None,
1290
+ stdout_as_string: bool = True,
1291
+ kill_after_timeout: Union[None, float] = None,
1292
+ with_stdout: bool = True,
1293
+ universal_newlines: bool = False,
1294
+ shell: Union[None, bool] = None,
1295
+ env: Union[None, Mapping[str, str]] = None,
1296
+ max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
1297
+ strip_newline_in_stdout: bool = True,
1298
+ **subprocess_kwargs: Any,
1299
+ ) -> Union[None, str, bytes, Tuple[int, Union[str, bytes, None], str], AutoInterrupt]: ...
1300
+
1301
+ def execute(
1302
+ self,
1303
+ command: Union[str, Sequence[Any]],
1304
+ istream: Union[None, int, BinaryIO] = None,
1229
1305
  with_extended_output: bool = False,
1230
1306
  with_exceptions: bool = True,
1231
1307
  as_process: bool = False,
@@ -1239,7 +1315,7 @@ class Git(metaclass=_GitMeta):
1239
1315
  max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
1240
1316
  strip_newline_in_stdout: bool = True,
1241
1317
  **subprocess_kwargs: Any,
1242
- ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
1318
+ ) -> Union[None, str, bytes, Tuple[int, Union[str, bytes, None], str], AutoInterrupt]:
1243
1319
  R"""Handle executing the command, and consume and return the returned
1244
1320
  information (stdout).
1245
1321
 
@@ -1303,9 +1379,9 @@ class Git(metaclass=_GitMeta):
1303
1379
  carefully considered, due to the following limitations:
1304
1380
 
1305
1381
  1. This feature is not supported at all on Windows.
1306
- 2. Effectiveness may vary by operating system. ``ps --ppid`` is used to
1307
- enumerate child processes, which is available on most GNU/Linux systems
1308
- but not most others.
1382
+ 2. Enumerating child processes requires ``pgrep -P``, or a ``ps`` command
1383
+ supporting the POSIX ``-A`` and ``-o`` options if ``pgrep`` is not
1384
+ installed. Effectiveness may vary on systems without these commands.
1309
1385
  3. Deeper descendants do not receive signals, though they may sometimes
1310
1386
  terminate as a consequence of their parent processes being killed.
1311
1387
  4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side
@@ -1465,14 +1541,24 @@ class Git(metaclass=_GitMeta):
1465
1541
 
1466
1542
  This callback implementation would be ineffective and unsafe on Windows.
1467
1543
  """
1468
- p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
1469
1544
  child_pids = []
1470
- if p.stdout is not None:
1471
- for line in p.stdout:
1472
- if len(line.split()) > 0:
1473
- local_pid = (line.split())[0]
1474
- if local_pid.isdigit():
1475
- child_pids.append(int(local_pid))
1545
+ try:
1546
+ p = Popen(["pgrep", "-P", str(pid)], stdout=PIPE)
1547
+ except FileNotFoundError:
1548
+ # POSIX ps does not support selecting by parent PID.
1549
+ with Popen(["ps", "-A", "-o", "pid=", "-o", "ppid="], stdout=PIPE) as p:
1550
+ if p.stdout is not None:
1551
+ for line in p.stdout:
1552
+ fields = line.split()
1553
+ if len(fields) == 2 and all(field.isdigit() for field in fields):
1554
+ if int(fields[1]) == pid:
1555
+ child_pids.append(int(fields[0]))
1556
+ else:
1557
+ with p:
1558
+ if p.stdout is not None:
1559
+ for line in p.stdout:
1560
+ if line.strip().isdigit():
1561
+ child_pids.append(int(line))
1476
1562
  try:
1477
1563
  os.kill(pid, signal.SIGKILL)
1478
1564
  for child_pid in child_pids:
@@ -1493,7 +1579,7 @@ class Git(metaclass=_GitMeta):
1493
1579
  err = f'Timeout: the command "{" ".join(redacted_command)}" did not complete in {timeout:g} secs.'
1494
1580
  return err if universal_newlines else err.encode(defenc)
1495
1581
 
1496
- def communicate() -> Tuple[AnyStr, AnyStr]:
1582
+ def communicate() -> Tuple[Union[str, bytes, None], Union[str, bytes, None]]:
1497
1583
  assert watchdog is not None
1498
1584
  assert kill_check is not None
1499
1585
  watchdog.start()
@@ -1513,8 +1599,8 @@ class Git(metaclass=_GitMeta):
1513
1599
 
1514
1600
  # Wait for the process to return.
1515
1601
  status = 0
1516
- stdout_value: Union[str, bytes] = b""
1517
- stderr_value: Union[str, bytes] = b""
1602
+ stdout_value: Union[str, bytes, None] = b""
1603
+ stderr_value: Union[str, bytes, None] = b""
1518
1604
  newline = "\n" if universal_newlines else b"\n"
1519
1605
  try:
1520
1606
  if output_stream is None:
@@ -1556,7 +1642,7 @@ class Git(metaclass=_GitMeta):
1556
1642
  if self.GIT_PYTHON_TRACE == "full":
1557
1643
  cmdstr = " ".join(redacted_command)
1558
1644
 
1559
- def as_text(stdout_value: Union[bytes, str]) -> str:
1645
+ def as_text(stdout_value: Union[bytes, str, None]) -> str:
1560
1646
  return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
1561
1647
 
1562
1648
  # END as_text
@@ -1581,6 +1667,8 @@ class Git(metaclass=_GitMeta):
1581
1667
  if isinstance(stdout_value, bytes) and stdout_as_string: # Could also be output_stream.
1582
1668
  stdout_value = safe_decode(stdout_value)
1583
1669
 
1670
+ # stderr is always captured through PIPE.
1671
+ assert stderr_value is not None
1584
1672
  # Allow access to the command's status code.
1585
1673
  if with_extended_output:
1586
1674
  return (status, stdout_value, safe_decode(stderr_value))
@@ -1725,6 +1813,12 @@ class Git(metaclass=_GitMeta):
1725
1813
  This allows your commands to call git more conveniently, as ``None`` is
1726
1814
  realized as non-existent.
1727
1815
 
1816
+ Positional arguments may intentionally contain command options. Higher-level
1817
+ APIs must separate their operands with ``--`` where the Git command supports
1818
+ it, or reject option-shaped operands where Git reparses them internally (for
1819
+ example, ``pull`` and ``remote update``). Shell quoting cannot prevent Git
1820
+ from interpreting a leading-dash argument as an option.
1821
+
1728
1822
  :param kwargs:
1729
1823
  Contains key-values for the following:
1730
1824
 
@@ -1819,7 +1913,7 @@ class Git(metaclass=_GitMeta):
1819
1913
  raise ValueError("Failed to parse header: %r" % header_line)
1820
1914
  return (tokens[0], tokens[1], int(tokens[2]))
1821
1915
 
1822
- def _prepare_ref(self, ref: AnyStr) -> bytes:
1916
+ def _prepare_ref(self, ref: object) -> bytes:
1823
1917
  # Required for command to separate refs on stdin, as bytes.
1824
1918
  if isinstance(ref, bytes):
1825
1919
  # Assume 40 bytes hexsha - bin-to-ascii for some reason returns bytes, not text.
@@ -1846,7 +1940,7 @@ class Git(metaclass=_GitMeta):
1846
1940
  cmd = cast("Git.AutoInterrupt", cmd)
1847
1941
  return cmd
1848
1942
 
1849
- def __get_object_header(self, cmd: "Git.AutoInterrupt", ref: AnyStr) -> Tuple[str, str, int]:
1943
+ def __get_object_header(self, cmd: "Git.AutoInterrupt", ref: Union[str, bytes]) -> Tuple[str, str, int]:
1850
1944
  if cmd.stdin and cmd.stdout:
1851
1945
  cmd.stdin.write(self._prepare_ref(ref))
1852
1946
  cmd.stdin.flush()
@@ -1854,7 +1948,7 @@ class Git(metaclass=_GitMeta):
1854
1948
  else:
1855
1949
  raise ValueError("cmd stdin was empty")
1856
1950
 
1857
- def get_object_header(self, ref: str) -> Tuple[str, str, int]:
1951
+ def get_object_header(self, ref: Union[str, bytes]) -> Tuple[str, str, int]:
1858
1952
  """Use this method to quickly examine the type and size of the object behind the
1859
1953
  given ref.
1860
1954
 
@@ -1868,7 +1962,7 @@ class Git(metaclass=_GitMeta):
1868
1962
  cmd = self._get_persistent_cmd("cat_file_header", "cat_file", batch_check=True)
1869
1963
  return self.__get_object_header(cmd, ref)
1870
1964
 
1871
- def get_object_data(self, ref: str) -> Tuple[str, str, int, bytes]:
1965
+ def get_object_data(self, ref: Union[str, bytes]) -> Tuple[str, str, int, bytes]:
1872
1966
  """Similar to :meth:`get_object_header`, but returns object data as well.
1873
1967
 
1874
1968
  :return:
@@ -1882,7 +1976,7 @@ class Git(metaclass=_GitMeta):
1882
1976
  del stream
1883
1977
  return (hexsha, typename, size, data)
1884
1978
 
1885
- def stream_object_data(self, ref: str) -> Tuple[str, str, int, "Git.CatFileContentStream"]:
1979
+ def stream_object_data(self, ref: Union[str, bytes]) -> Tuple[str, str, int, "Git.CatFileContentStream"]:
1886
1980
  """Similar to :meth:`get_object_data`, but returns the data as a stream.
1887
1981
 
1888
1982
  :return: