GitPython 3.1.48__tar.gz → 3.1.52__tar.gz

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.
Files changed (71) hide show
  1. {gitpython-3.1.48 → gitpython-3.1.52}/AUTHORS +1 -0
  2. gitpython-3.1.52/CONTRIBUTING.md +46 -0
  3. {gitpython-3.1.48 → gitpython-3.1.52/GitPython.egg-info}/PKG-INFO +1 -1
  4. {gitpython-3.1.48/GitPython.egg-info → gitpython-3.1.52}/PKG-INFO +1 -1
  5. gitpython-3.1.52/VERSION +1 -0
  6. {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/changes.rst +45 -0
  7. {gitpython-3.1.48 → gitpython-3.1.52}/git/__init__.py +1 -1
  8. {gitpython-3.1.48 → gitpython-3.1.52}/git/cmd.py +130 -17
  9. {gitpython-3.1.48 → gitpython-3.1.52}/git/config.py +39 -2
  10. {gitpython-3.1.48 → gitpython-3.1.52}/git/diff.py +19 -4
  11. {gitpython-3.1.48 → gitpython-3.1.52}/git/index/base.py +40 -3
  12. {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/commit.py +17 -0
  13. {gitpython-3.1.48 → gitpython-3.1.52}/git/remote.py +19 -7
  14. {gitpython-3.1.48 → gitpython-3.1.52}/git/repo/base.py +101 -11
  15. gitpython-3.1.52/git/repo/fun.py +736 -0
  16. {gitpython-3.1.48 → gitpython-3.1.52}/git/util.py +9 -6
  17. gitpython-3.1.48/CONTRIBUTING.md +0 -15
  18. gitpython-3.1.48/VERSION +0 -1
  19. gitpython-3.1.48/git/repo/fun.py +0 -425
  20. {gitpython-3.1.48 → gitpython-3.1.52}/CHANGES +0 -0
  21. {gitpython-3.1.48 → gitpython-3.1.52}/GitPython.egg-info/SOURCES.txt +0 -0
  22. {gitpython-3.1.48 → gitpython-3.1.52}/GitPython.egg-info/dependency_links.txt +0 -0
  23. {gitpython-3.1.48 → gitpython-3.1.52}/GitPython.egg-info/not-zip-safe +0 -0
  24. {gitpython-3.1.48 → gitpython-3.1.52}/GitPython.egg-info/requires.txt +0 -0
  25. {gitpython-3.1.48 → gitpython-3.1.52}/GitPython.egg-info/top_level.txt +0 -0
  26. {gitpython-3.1.48 → gitpython-3.1.52}/LICENSE +0 -0
  27. {gitpython-3.1.48 → gitpython-3.1.52}/MANIFEST.in +0 -0
  28. {gitpython-3.1.48 → gitpython-3.1.52}/README.md +0 -0
  29. {gitpython-3.1.48 → gitpython-3.1.52}/doc/.gitignore +0 -0
  30. {gitpython-3.1.48 → gitpython-3.1.52}/doc/Makefile +0 -0
  31. {gitpython-3.1.48 → gitpython-3.1.52}/doc/requirements.txt +0 -0
  32. {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/conf.py +0 -0
  33. {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/index.rst +0 -0
  34. {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/intro.rst +0 -0
  35. {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/quickstart.rst +0 -0
  36. {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/reference.rst +0 -0
  37. {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/roadmap.rst +0 -0
  38. {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/tutorial.rst +0 -0
  39. {gitpython-3.1.48 → gitpython-3.1.52}/git/compat.py +0 -0
  40. {gitpython-3.1.48 → gitpython-3.1.52}/git/db.py +0 -0
  41. {gitpython-3.1.48 → gitpython-3.1.52}/git/exc.py +0 -0
  42. {gitpython-3.1.48 → gitpython-3.1.52}/git/index/__init__.py +0 -0
  43. {gitpython-3.1.48 → gitpython-3.1.52}/git/index/fun.py +0 -0
  44. {gitpython-3.1.48 → gitpython-3.1.52}/git/index/typ.py +0 -0
  45. {gitpython-3.1.48 → gitpython-3.1.52}/git/index/util.py +0 -0
  46. {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/__init__.py +0 -0
  47. {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/base.py +0 -0
  48. {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/blob.py +0 -0
  49. {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/fun.py +0 -0
  50. {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/submodule/__init__.py +0 -0
  51. {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/submodule/base.py +0 -0
  52. {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/submodule/root.py +0 -0
  53. {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/submodule/util.py +0 -0
  54. {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/tag.py +0 -0
  55. {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/tree.py +0 -0
  56. {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/util.py +0 -0
  57. {gitpython-3.1.48 → gitpython-3.1.52}/git/py.typed +0 -0
  58. {gitpython-3.1.48 → gitpython-3.1.52}/git/refs/__init__.py +0 -0
  59. {gitpython-3.1.48 → gitpython-3.1.52}/git/refs/head.py +0 -0
  60. {gitpython-3.1.48 → gitpython-3.1.52}/git/refs/log.py +0 -0
  61. {gitpython-3.1.48 → gitpython-3.1.52}/git/refs/reference.py +0 -0
  62. {gitpython-3.1.48 → gitpython-3.1.52}/git/refs/remote.py +0 -0
  63. {gitpython-3.1.48 → gitpython-3.1.52}/git/refs/symbolic.py +0 -0
  64. {gitpython-3.1.48 → gitpython-3.1.52}/git/refs/tag.py +0 -0
  65. {gitpython-3.1.48 → gitpython-3.1.52}/git/repo/__init__.py +0 -0
  66. {gitpython-3.1.48 → gitpython-3.1.52}/git/types.py +0 -0
  67. {gitpython-3.1.48 → gitpython-3.1.52}/pyproject.toml +0 -0
  68. {gitpython-3.1.48 → gitpython-3.1.52}/requirements.txt +0 -0
  69. {gitpython-3.1.48 → gitpython-3.1.52}/setup.cfg +0 -0
  70. {gitpython-3.1.48 → gitpython-3.1.52}/setup.py +0 -0
  71. {gitpython-3.1.48 → gitpython-3.1.52}/test-requirements.txt +0 -0
@@ -57,5 +57,6 @@ Contributors are:
57
57
  -Jonas Scharpf <jonas.scharpf _at_ checkmk.com>
58
58
  -Gordon Marx
59
59
  -Enji Cooper
60
+ -Harshita Yadav <harshitayadav504 _at_ gmail.com>
60
61
 
61
62
  Portions derived from other open source works and are clearly marked.
@@ -0,0 +1,46 @@
1
+ # How to contribute
2
+
3
+ The following is a short step-by-step rundown of what one typically would do to contribute.
4
+
5
+ - [Fork this project](https://github.com/gitpython-developers/GitPython/fork) on GitHub.
6
+ - For setting up the environment to run the self tests, please run `init-tests-after-clone.sh`.
7
+ - Please try to **write a test that fails unless the contribution is present.**
8
+ - Try to avoid massive commits and prefer to take small steps, with one commit for each.
9
+ - Feel free to add yourself to AUTHORS file.
10
+ - Create a pull request.
11
+
12
+ ## Quality expectations
13
+
14
+ Contributions must be made with care and meet the quality bar of the surrounding code.
15
+ That means a change should not leave GitPython worse than it was before: it should be
16
+ readable, maintainable, tested where practical, documented and consistent with the
17
+ existing style and behavior.
18
+
19
+ A contribution that works only narrowly but lowers the quality of the
20
+ codebase may be declined. The maintainers may not always be able to provide
21
+ detailed feedback.
22
+
23
+ ## AI-assisted contributions
24
+
25
+ If AI edits files for you, disclose it in the pull request description and commit
26
+ metadata. Prefer making the agent identity part of the commit, for example by using
27
+ an AI author such as `$agent $version <ai-agent@example.invalid>` or a co-author via
28
+ a `Co-authored-by: <agent-identity>` trailer.
29
+
30
+ Agents operating through a person's GitHub account must identify themselves. For
31
+ example, comments posted by an agent should say so directly with phrases like
32
+ `AI agent on behalf of <person>: ...`.
33
+
34
+ Fully AI-generated comments on pull requests or issues must also be disclosed.
35
+ Undisclosed AI-generated comments may lead to the pull request or issue being closed.
36
+
37
+ AI-assisted proofreading or wording polish does not need disclosure, but it is still
38
+ courteous to mention it when the AI materially influenced the final text.
39
+
40
+ Automated or "full-auto" AI contributions without a human responsible for reviewing
41
+ and standing behind the work may be closed.
42
+
43
+ ## Fuzzing Test Specific Documentation
44
+
45
+ For details related to contributing to the fuzzing test suite and OSS-Fuzz integration, please
46
+ refer to the dedicated [fuzzing README](./fuzzing/README.md).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GitPython
3
- Version: 3.1.48
3
+ Version: 3.1.52
4
4
  Summary: GitPython is a Python library used to interact with Git repositories
5
5
  Home-page: https://github.com/gitpython-developers/GitPython
6
6
  Author: Sebastian Thiel, Michael Trier
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GitPython
3
- Version: 3.1.48
3
+ Version: 3.1.52
4
4
  Summary: GitPython is a Python library used to interact with Git repositories
5
5
  Home-page: https://github.com/gitpython-developers/GitPython
6
6
  Author: Sebastian Thiel, Michael Trier
@@ -0,0 +1 @@
1
+ 3.1.52
@@ -2,6 +2,51 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ 3.1.52
6
+ ======
7
+
8
+ A security fix for
9
+ https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-rwj8-pgh3-r573.
10
+
11
+ See the following for all changes.
12
+ https://github.com/gitpython-developers/GitPython/releases/tag/3.1.52
13
+
14
+ 3.1.51
15
+ ======
16
+
17
+ This is primarily a security release. It prevents additional argument-injection
18
+ paths that could allow execution of arbitrary commands or writing to arbitrary
19
+ files through unsafe Git options (GHSA-956x-8gvw-wg5v), and closes bypasses of
20
+ the existing protections using abbreviated long options or joined short options
21
+ (GHSA-2f96-g7mh-g2hx).
22
+
23
+ The release also improves support for relative worktree paths and diffs against
24
+ the empty tree, preserves stderr from failed diff processes, supports relative
25
+ configuration include paths, and includes assorted documentation, typing, test,
26
+ and CI improvements.
27
+
28
+ See the following for all changes.
29
+ https://github.com/gitpython-developers/GitPython/releases/tag/3.1.51
30
+
31
+ 3.1.50
32
+ ======
33
+
34
+ Save setting of configuration values, this time sections as well, as follow-up to 3.1.49.
35
+
36
+ See the following for all changes.
37
+ https://github.com/gitpython-developers/GitPython/releases/tag/3.1.50
38
+
39
+ 3.1.49
40
+ ======
41
+
42
+ Save setting of configuration values,
43
+ which could be used to inject other more configuration.
44
+
45
+ Also more conforming `rev-parse` implementation.
46
+
47
+ See the following for all changes.
48
+ https://github.com/gitpython-developers/GitPython/releases/tag/3.1.49
49
+
5
50
  3.1.48
6
51
  ======
7
52
 
@@ -86,7 +86,7 @@ __all__ = [
86
86
  "to_hex_sha",
87
87
  ]
88
88
 
89
- __version__ = '3.1.48'
89
+ __version__ = '3.1.52'
90
90
 
91
91
  from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Tuple, Union
92
92
 
@@ -649,6 +649,11 @@ class Git(metaclass=_GitMeta):
649
649
 
650
650
  re_unsafe_protocol = re.compile(r"(.+)::.+")
651
651
 
652
+ unsafe_git_ls_remote_options = [
653
+ # This option allows arbitrary command execution in git-ls-remote.
654
+ "--upload-pack",
655
+ ]
656
+
652
657
  def __getstate__(self) -> Dict[str, Any]:
653
658
  return slots_to_dict(self, exclude=self._excluded_)
654
659
 
@@ -898,29 +903,34 @@ class Git(metaclass=_GitMeta):
898
903
 
899
904
  @overload
900
905
  @classmethod
901
- def polish_url(cls, url: str, is_cygwin: Literal[False] = ...) -> str: ...
906
+ def polish_url(cls, url: str, is_cygwin: Literal[False] = ..., expand_vars: bool = ...) -> str: ...
902
907
 
903
908
  @overload
904
909
  @classmethod
905
- def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> str: ...
910
+ def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None, expand_vars: bool = True) -> str: ...
906
911
 
907
912
  @classmethod
908
- def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> PathLike:
913
+ def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None, expand_vars: bool = True) -> PathLike:
909
914
  """Remove any backslashes from URLs to be written in config files.
910
915
 
911
916
  Windows might create config files containing paths with backslashes, but git
912
917
  stops liking them as it will escape the backslashes. Hence we undo the escaping
913
918
  just to be sure.
919
+
920
+ :param expand_vars:
921
+ Expand environment variables and an initial ``~``. Disable this for values
922
+ obtained from an untrusted source, such as remote URLs.
914
923
  """
915
924
  if is_cygwin is None:
916
925
  is_cygwin = cls.is_cygwin()
917
926
 
918
927
  if is_cygwin:
919
- url = cygpath(url)
928
+ url = cygpath(url, expand_vars=expand_vars)
920
929
  else:
921
- url = os.path.expandvars(url)
922
- if url.startswith("~"):
923
- url = os.path.expanduser(url)
930
+ if expand_vars:
931
+ url = os.path.expandvars(url)
932
+ if url.startswith("~"):
933
+ url = os.path.expanduser(url)
924
934
  url = url.replace("\\\\", "\\").replace("\\", "/")
925
935
  return url
926
936
 
@@ -961,17 +971,80 @@ class Git(metaclass=_GitMeta):
961
971
 
962
972
  @classmethod
963
973
  def check_unsafe_options(cls, options: List[str], unsafe_options: List[str]) -> None:
964
- """Check for unsafe options.
965
-
966
- Some options that are passed to ``git <command>`` can be used to execute
967
- arbitrary commands. These are blocked by default.
974
+ """Raise :class:`~git.exc.UnsafeOptionError` for blocked option spellings.
975
+
976
+ In addition to exact matches, this rejects abbreviated long options accepted
977
+ by Git (for example, ``--upl`` for ``--upload-pack``) and unsafe short options
978
+ whose values are joined to the same token, including after clusterable flags
979
+ (for example, ``-uVALUE`` and ``-fuVALUE``).
980
+
981
+ A list containing only bare names is treated as normalized keyword arguments,
982
+ so multi-character names such as ``upload_p`` are checked as long-option
983
+ abbreviations. If any item starts with ``-``, the list is treated as tokenized
984
+ command-line input: bare items can be option values and are not checked as
985
+ abbreviations. Thus ``["--origin", "upload"]`` is allowed. Single-dash options
986
+ use short-option parsing rather than broad prefix matching, preserving safe
987
+ attached values such as ``-oupstream`` and ``-bcurrent``.
988
+
989
+ Some options passed to ``git <command>`` can execute arbitrary commands and
990
+ are therefore blocked by default unless the caller explicitly allows them.
968
991
  """
969
992
  # Options can be of the form `foo`, `--foo`, `--foo bar`, or `--foo=bar`.
993
+ # Git accepts any unambiguous prefix of a long option, so an abbreviated
994
+ # spelling such as `--upl` for `--upload-pack` must be rejected too. An
995
+ # option is unsafe if its canonical name is a prefix of any blocked
996
+ # option's canonical name. Only long options and multi-character kwargs
997
+ # can be abbreviations; single-character short options remain exact-match
998
+ # only.
970
999
  canonical_unsafe_options = {cls._canonicalize_option_name(option): option for option in unsafe_options}
1000
+ unsafe_short_options = {
1001
+ canonical: option
1002
+ for canonical, option in canonical_unsafe_options.items()
1003
+ if option.startswith("-") and not option.startswith("--") and len(canonical) == 1
1004
+ }
1005
+ # These value-less Git flags can be clustered before another short option
1006
+ # (for example, ``-fuVALUE``). Stop at any other character because it may
1007
+ # begin an attached value, as ``o`` does in the safe option ``-oupstream``.
1008
+ clusterable_short_options = frozenset("46flnqsv")
1009
+ options_are_kwargs = all(not option.startswith("-") for option in options)
971
1010
  for option in options:
972
- unsafe_option = canonical_unsafe_options.get(cls._canonicalize_option_name(option))
1011
+ candidate = cls._canonicalize_option_name(option)
1012
+ if not candidate:
1013
+ continue
1014
+ unsafe_option = canonical_unsafe_options.get(candidate)
973
1015
  if unsafe_option is not None:
974
1016
  raise UnsafeOptionError(f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it.")
1017
+ option_token = option.split("=", 1)[0].split(None, 1)[0]
1018
+ if option_token.startswith("-") and not option_token.startswith("--"):
1019
+ for option_char in option_token[1:]:
1020
+ unsafe_option = unsafe_short_options.get(option_char)
1021
+ if unsafe_option is not None:
1022
+ raise UnsafeOptionError(
1023
+ f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it."
1024
+ )
1025
+ if option_char not in clusterable_short_options:
1026
+ break
1027
+ if not (option.startswith("--") or (options_are_kwargs and len(candidate) > 1)):
1028
+ continue
1029
+ for canonical, unsafe_option in canonical_unsafe_options.items():
1030
+ if canonical.startswith(candidate):
1031
+ raise UnsafeOptionError(
1032
+ f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it."
1033
+ )
1034
+
1035
+ @classmethod
1036
+ def _option_candidates(cls, args: Sequence[Any] = (), kwargs: Optional[Mapping[str, Any]] = None) -> List[str]:
1037
+ """Collect possible option spellings before command-line transformation."""
1038
+ options = [
1039
+ option for option in cls._unpack_args([arg for arg in args if arg is not None]) if option.startswith("-")
1040
+ ]
1041
+ if kwargs:
1042
+ for key, value in kwargs.items():
1043
+ values = value if isinstance(value, (list, tuple)) else (value,)
1044
+ if any(value is True or (value is not False and value is not None) for value in values):
1045
+ key = str(key)
1046
+ options.append(f"-{key}" if len(key) == 1 else f"--{dashify(key)}")
1047
+ return options
975
1048
 
976
1049
  AutoInterrupt: TypeAlias = _AutoInterrupt
977
1050
 
@@ -1030,6 +1103,22 @@ class Git(metaclass=_GitMeta):
1030
1103
 
1031
1104
  self._persistent_git_options = self.transform_kwargs(split_single_char_options=True, **kwargs)
1032
1105
 
1106
+ def ls_remote(
1107
+ self,
1108
+ *args: Any,
1109
+ allow_unsafe_options: bool = False,
1110
+ **kwargs: Any,
1111
+ ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], "Git.AutoInterrupt"]:
1112
+ """List references in a remote repository.
1113
+
1114
+ :param allow_unsafe_options:
1115
+ Allow unsafe options, like ``--upload-pack``.
1116
+ """
1117
+ if not allow_unsafe_options:
1118
+ candidate_options = self._option_candidates(args, kwargs)
1119
+ Git.check_unsafe_options(options=candidate_options, unsafe_options=self.unsafe_git_ls_remote_options)
1120
+ return self._call_process("ls_remote", *args, **kwargs)
1121
+
1033
1122
  @property
1034
1123
  def working_dir(self) -> Union[None, PathLike]:
1035
1124
  """:return: Git directory we are working on"""
@@ -1131,9 +1220,28 @@ class Git(metaclass=_GitMeta):
1131
1220
  information (stdout).
1132
1221
 
1133
1222
  :param command:
1134
- The command argument list to execute.
1135
- It should be a sequence of program arguments, or a string. The
1136
- program to execute is the first item in the args sequence or string.
1223
+ The command to execute. A sequence of program arguments is recommended.
1224
+ A string is also accepted, but its meaning is strongly platform-dependent.
1225
+
1226
+ By default, a shell is not used. On Unix-like systems, a string is the whole
1227
+ program name (so ``"git log -n 1"`` raises :class:`GitCommandNotFound`). On
1228
+ Windows, the program parses the arguments itself, so multi-word strings can
1229
+ work but are not portable.
1230
+
1231
+ Avoid ``shell=True`` (and :attr:`Git.USE_SHELL`): this runs the command in
1232
+ a shell, which is generally unsafe. The shell interprets metacharacters
1233
+ such as ``;``, ``|``, ``&``, ``$(...)``, ``$VAR``, ``%VAR%``, and ``^``
1234
+ (depending on the platform) as syntax. Any untrusted text in the command
1235
+ can then execute arbitrary OS commands. See :attr:`Git.USE_SHELL`.
1236
+
1237
+ Producing a sequence automatically by :func:`shlex.split` and passing it
1238
+ as the command is far safer than ``shell=True``. But :func:`shlex.split`
1239
+ parses POSIX shell syntax on all systems, and the result is still unsafe
1240
+ for anything but *fixed, fully trusted* strings. Do not use it on strings
1241
+ built by interpolating values: whitespace or quoting in an untrusted value
1242
+ can still inject arguments. For input derived in any way from untrusted
1243
+ data, build the argument sequence yourself, while ensuring each argument
1244
+ is fully sanitized.
1137
1245
 
1138
1246
  :param istream:
1139
1247
  Standard input filehandle passed to :class:`subprocess.Popen`.
@@ -1201,6 +1309,11 @@ class Git(metaclass=_GitMeta):
1201
1309
  needed (nor useful) to work around any known operating system specific
1202
1310
  issues.
1203
1311
 
1312
+ On Unix-like systems, when migrating away from passing string commands with
1313
+ ``shell=True``, :func:`shlex.split` may serve as a transitional step in rare
1314
+ cases, with extreme care. (Drop ``shell=True`` and pass the resulting
1315
+ sequence as the command.) See the `command` parameter above on the risks.
1316
+
1204
1317
  :param env:
1205
1318
  A dictionary of environment variables to be passed to
1206
1319
  :class:`subprocess.Popen`.
@@ -1512,7 +1625,7 @@ class Git(metaclass=_GitMeta):
1512
1625
  return args
1513
1626
 
1514
1627
  @classmethod
1515
- def _unpack_args(cls, arg_list: Sequence[str]) -> List[str]:
1628
+ def _unpack_args(cls, arg_list: Sequence[Any]) -> List[str]:
1516
1629
  outlist = []
1517
1630
  if isinstance(arg_list, (list, tuple)):
1518
1631
  for arg in arg_list:
@@ -1588,7 +1701,7 @@ class Git(metaclass=_GitMeta):
1588
1701
 
1589
1702
  turns into::
1590
1703
 
1591
- git rev-list --max-count=10 --header=master
1704
+ git rev-list --max-count=10 --header master
1592
1705
 
1593
1706
  :return:
1594
1707
  Same as :meth:`execute`. If no args are given, used :meth:`execute`'s
@@ -72,6 +72,9 @@ CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbr
72
72
  See: https://git-scm.com/docs/git-config#_conditional_includes
73
73
  """
74
74
 
75
+ UNSAFE_CONFIG_CHARS_RE = re.compile(r"[\r\n\x00]")
76
+ """Characters that cannot be safely written in config names or values."""
77
+
75
78
 
76
79
  class MetaParserBuilder(abc.ABCMeta): # noqa: B024
77
80
  """Utility class wrapping base-class methods into decorators that assure read-only
@@ -631,6 +634,8 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
631
634
  files_to_read = list(self._file_or_files)
632
635
  # END ensure we have a copy of the paths to handle
633
636
 
637
+ files_to_read = [osp.abspath(path) if isinstance(path, (str, os.PathLike)) else path for path in files_to_read]
638
+
634
639
  seen = set(files_to_read)
635
640
  num_read_include_files = 0
636
641
  while files_to_read:
@@ -778,6 +783,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
778
783
 
779
784
  def add_section(self, section: "cp._SectionName") -> None:
780
785
  """Assures added options will stay in order."""
786
+ self._assure_config_name_safe(section, "section")
781
787
  return super().add_section(section)
782
788
 
783
789
  @property
@@ -882,6 +888,30 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
882
888
  return str(value)
883
889
  return force_text(value)
884
890
 
891
+ def _value_to_string_safe(self, value: Union[str, bytes, int, float, bool]) -> str:
892
+ value_str = self._value_to_string(value)
893
+ if UNSAFE_CONFIG_CHARS_RE.search(value_str):
894
+ raise ValueError("Git config values must not contain CR, LF, or NUL")
895
+ return value_str
896
+
897
+ def _assure_config_name_safe(self, name: "cp._SectionName", label: str) -> None:
898
+ if isinstance(name, str) and UNSAFE_CONFIG_CHARS_RE.search(name):
899
+ raise ValueError("Git config %s names must not contain CR, LF, or NUL" % label)
900
+
901
+ @needs_values
902
+ @set_dirty_and_flush_changes
903
+ def set(
904
+ self,
905
+ section: str,
906
+ option: str,
907
+ value: Union[str, bytes, int, float, bool, None] = None,
908
+ ) -> None:
909
+ self._assure_config_name_safe(section, "section")
910
+ self._assure_config_name_safe(option, "option")
911
+ if value is not None:
912
+ value = self._value_to_string_safe(value)
913
+ return super().set(section, option, value)
914
+
885
915
  @needs_values
886
916
  @set_dirty_and_flush_changes
887
917
  def set_value(self, section: str, option: str, value: Union[str, bytes, int, float, bool]) -> "GitConfigParser":
@@ -902,9 +932,12 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
902
932
  :return:
903
933
  This instance
904
934
  """
935
+ self._assure_config_name_safe(section, "section")
936
+ self._assure_config_name_safe(option, "option")
937
+ value_str = self._value_to_string_safe(value)
905
938
  if not self.has_section(section):
906
939
  self.add_section(section)
907
- self.set(section, option, self._value_to_string(value))
940
+ super().set(section, option, value_str)
908
941
  return self
909
942
 
910
943
  @needs_values
@@ -929,9 +962,12 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
929
962
  :return:
930
963
  This instance
931
964
  """
965
+ self._assure_config_name_safe(section, "section")
966
+ self._assure_config_name_safe(option, "option")
967
+ value_str = self._value_to_string_safe(value)
932
968
  if not self.has_section(section):
933
969
  self.add_section(section)
934
- self._sections[section].add(option, self._value_to_string(value))
970
+ self._sections[section].add(option, value_str)
935
971
  return self
936
972
 
937
973
  def rename_section(self, section: str, new_name: str) -> "GitConfigParser":
@@ -948,6 +984,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
948
984
  """
949
985
  if not self.has_section(section):
950
986
  raise ValueError("Source section '%s' doesn't exist" % section)
987
+ self._assure_config_name_safe(new_name, "section")
951
988
  if self.has_section(new_name):
952
989
  raise ValueError("Destination section '%s' already exists" % new_name)
953
990
 
@@ -3,7 +3,7 @@
3
3
  # This module is part of GitPython and is released under the
4
4
  # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
5
5
 
6
- __all__ = ["DiffConstants", "NULL_TREE", "INDEX", "Diffable", "DiffIndex", "Diff"]
6
+ __all__ = ["DiffConstants", "NULL_TREE", "NULL_TREE_SHA", "INDEX", "Diffable", "DiffIndex", "Diff"]
7
7
 
8
8
  import enum
9
9
  import re
@@ -84,6 +84,9 @@ This is an alias of :const:`DiffConstants.NULL_TREE`, which may also be accessed
84
84
  :const:`git.NULL_TREE` and :const:`Diffable.NULL_TREE`.
85
85
  """
86
86
 
87
+ NULL_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
88
+ """SHA of Git's canonical empty tree object."""
89
+
87
90
  INDEX: Literal[DiffConstants.INDEX] = DiffConstants.INDEX
88
91
  """Stand-in indicating you want to diff against the index.
89
92
 
@@ -599,7 +602,14 @@ class Diff:
599
602
 
600
603
  # FIXME: Here SLURPING raw, need to re-phrase header-regexes linewise.
601
604
  text_list: List[bytes] = []
602
- handle_process_output(proc, text_list.append, None, finalize_process, decode_streams=False)
605
+ stderr_list: List[bytes] = []
606
+
607
+ def finalize_process_with_stderr(proc: Union["Popen", "Git.AutoInterrupt"]) -> None:
608
+ finalize_process(proc, stderr=b"".join(stderr_list))
609
+
610
+ handle_process_output(
611
+ proc, text_list.append, stderr_list.append, finalize_process_with_stderr, decode_streams=False
612
+ )
603
613
 
604
614
  # For now, we have to bake the stream.
605
615
  text = b"".join(text_list)
@@ -765,11 +775,16 @@ class Diff:
765
775
  # :100644 100644 687099101... 37c5e30c8... M .gitignore
766
776
 
767
777
  index: "DiffIndex" = DiffIndex()
778
+ stderr_list: List[bytes] = []
779
+
780
+ def finalize_process_with_stderr(proc: Union["Popen", "Git.AutoInterrupt"]) -> None:
781
+ finalize_process(proc, stderr=b"".join(stderr_list))
782
+
768
783
  handle_process_output(
769
784
  proc,
770
785
  lambda byt: cls._handle_diff_line(byt, repo, index),
771
- None,
772
- finalize_process,
786
+ stderr_list.append,
787
+ finalize_process_with_stderr,
773
788
  decode_streams=False,
774
789
  )
775
790
 
@@ -1480,12 +1480,11 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1480
1480
 
1481
1481
  return self
1482
1482
 
1483
- # FIXME: This is documented to accept the same parameters as Diffable.diff, but this
1484
- # does not handle NULL_TREE for `other`. (The suppressed mypy error is about this.)
1485
1483
  def diff(
1486
1484
  self,
1487
- other: Union[ # type: ignore[override]
1485
+ other: Union[
1488
1486
  Literal[git_diff.DiffConstants.INDEX],
1487
+ Literal[git_diff.DiffConstants.NULL_TREE],
1489
1488
  "Tree",
1490
1489
  "Commit",
1491
1490
  str,
@@ -1512,6 +1511,44 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1512
1511
  if other is self.INDEX:
1513
1512
  return git_diff.DiffIndex()
1514
1513
 
1514
+ if other == git_diff.NULL_TREE or other == git_diff.NULL_TREE_SHA:
1515
+ args: List[Union[PathLike, str]] = [
1516
+ "--cached",
1517
+ git_diff.NULL_TREE_SHA,
1518
+ "--abbrev=40",
1519
+ "--full-index",
1520
+ ]
1521
+
1522
+ if not any(x in kwargs for x in ("find_renames", "no_renames", "M")):
1523
+ args.append("-M")
1524
+
1525
+ if create_patch:
1526
+ args.append("-p")
1527
+ args.append("--no-ext-diff")
1528
+ else:
1529
+ args.append("--raw")
1530
+ args.append("-z")
1531
+
1532
+ args.append("--no-color")
1533
+
1534
+ if paths is not None and not isinstance(paths, (tuple, list)):
1535
+ paths = [paths]
1536
+
1537
+ if paths:
1538
+ args.append("--")
1539
+ args.extend(paths)
1540
+
1541
+ kwargs["as_process"] = True
1542
+ proc = self.repo.git.diff(*args, **kwargs)
1543
+
1544
+ diff_method = (
1545
+ git_diff.Diff._index_from_patch_format if create_patch else git_diff.Diff._index_from_raw_format
1546
+ )
1547
+ index = diff_method(self.repo, proc)
1548
+
1549
+ proc.wait()
1550
+ return index
1551
+
1515
1552
  # Index against anything but None is a reverse diff with the respective item.
1516
1553
  # Handle existing -R flags properly.
1517
1554
  # Transform strings to the object so that we can call diff on it.
@@ -86,6 +86,12 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
86
86
  # INVARIANTS
87
87
  default_encoding = "UTF-8"
88
88
 
89
+ # Options to :manpage:`git-rev-list(1)` that can overwrite files.
90
+ unsafe_git_rev_options = [
91
+ "--output",
92
+ "-o",
93
+ ]
94
+
89
95
  type: Literal["commit"] = "commit"
90
96
 
91
97
  __slots__ = (
@@ -302,6 +308,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
302
308
  repo: "Repo",
303
309
  rev: Union[str, "Commit", "SymbolicReference"],
304
310
  paths: Union[PathLike, Sequence[PathLike]] = "",
311
+ allow_unsafe_options: bool = False,
305
312
  **kwargs: Any,
306
313
  ) -> Iterator["Commit"]:
307
314
  R"""Find all commits matching the given criteria.
@@ -330,6 +337,11 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
330
337
  raise ValueError("--pretty cannot be used as parsing expects single sha's only")
331
338
  # END handle pretty
332
339
 
340
+ if not allow_unsafe_options:
341
+ Git.check_unsafe_options(
342
+ options=Git._option_candidates([rev], kwargs), unsafe_options=cls.unsafe_git_rev_options
343
+ )
344
+
333
345
  # Use -- in all cases, to prevent possibility of ambiguous arguments.
334
346
  # See https://github.com/gitpython-developers/GitPython/issues/264.
335
347
 
@@ -374,6 +386,11 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
374
386
  """Create a git stat from changes between this commit and its first parent
375
387
  or from all changes done if this is the very first commit.
376
388
 
389
+ :note:
390
+ If this commit is at the boundary of a shallow clone, this will
391
+ raise :exc:`~git.exc.GitCommandError`, since the parent object
392
+ was never fetched and only exists as a reference on this commit.
393
+
377
394
  :return:
378
395
  :class:`Stats`
379
396
  """