GitPython 3.1.50__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 (70) hide show
  1. {gitpython-3.1.50 → gitpython-3.1.52}/AUTHORS +1 -0
  2. gitpython-3.1.52/CONTRIBUTING.md +46 -0
  3. {gitpython-3.1.50 → gitpython-3.1.52/GitPython.egg-info}/PKG-INFO +1 -1
  4. {gitpython-3.1.50/GitPython.egg-info → gitpython-3.1.52}/PKG-INFO +1 -1
  5. gitpython-3.1.52/VERSION +1 -0
  6. {gitpython-3.1.50 → gitpython-3.1.52}/doc/source/changes.rst +26 -0
  7. {gitpython-3.1.50 → gitpython-3.1.52}/git/__init__.py +1 -1
  8. {gitpython-3.1.50 → gitpython-3.1.52}/git/cmd.py +130 -17
  9. {gitpython-3.1.50 → gitpython-3.1.52}/git/config.py +2 -0
  10. {gitpython-3.1.50 → gitpython-3.1.52}/git/diff.py +19 -4
  11. {gitpython-3.1.50 → gitpython-3.1.52}/git/index/base.py +40 -3
  12. {gitpython-3.1.50 → gitpython-3.1.52}/git/objects/commit.py +17 -0
  13. {gitpython-3.1.50 → gitpython-3.1.52}/git/remote.py +19 -7
  14. {gitpython-3.1.50 → gitpython-3.1.52}/git/repo/base.py +79 -11
  15. {gitpython-3.1.50 → gitpython-3.1.52}/git/util.py +9 -6
  16. gitpython-3.1.50/CONTRIBUTING.md +0 -15
  17. gitpython-3.1.50/VERSION +0 -1
  18. {gitpython-3.1.50 → gitpython-3.1.52}/CHANGES +0 -0
  19. {gitpython-3.1.50 → gitpython-3.1.52}/GitPython.egg-info/SOURCES.txt +0 -0
  20. {gitpython-3.1.50 → gitpython-3.1.52}/GitPython.egg-info/dependency_links.txt +0 -0
  21. {gitpython-3.1.50 → gitpython-3.1.52}/GitPython.egg-info/not-zip-safe +0 -0
  22. {gitpython-3.1.50 → gitpython-3.1.52}/GitPython.egg-info/requires.txt +0 -0
  23. {gitpython-3.1.50 → gitpython-3.1.52}/GitPython.egg-info/top_level.txt +0 -0
  24. {gitpython-3.1.50 → gitpython-3.1.52}/LICENSE +0 -0
  25. {gitpython-3.1.50 → gitpython-3.1.52}/MANIFEST.in +0 -0
  26. {gitpython-3.1.50 → gitpython-3.1.52}/README.md +0 -0
  27. {gitpython-3.1.50 → gitpython-3.1.52}/doc/.gitignore +0 -0
  28. {gitpython-3.1.50 → gitpython-3.1.52}/doc/Makefile +0 -0
  29. {gitpython-3.1.50 → gitpython-3.1.52}/doc/requirements.txt +0 -0
  30. {gitpython-3.1.50 → gitpython-3.1.52}/doc/source/conf.py +0 -0
  31. {gitpython-3.1.50 → gitpython-3.1.52}/doc/source/index.rst +0 -0
  32. {gitpython-3.1.50 → gitpython-3.1.52}/doc/source/intro.rst +0 -0
  33. {gitpython-3.1.50 → gitpython-3.1.52}/doc/source/quickstart.rst +0 -0
  34. {gitpython-3.1.50 → gitpython-3.1.52}/doc/source/reference.rst +0 -0
  35. {gitpython-3.1.50 → gitpython-3.1.52}/doc/source/roadmap.rst +0 -0
  36. {gitpython-3.1.50 → gitpython-3.1.52}/doc/source/tutorial.rst +0 -0
  37. {gitpython-3.1.50 → gitpython-3.1.52}/git/compat.py +0 -0
  38. {gitpython-3.1.50 → gitpython-3.1.52}/git/db.py +0 -0
  39. {gitpython-3.1.50 → gitpython-3.1.52}/git/exc.py +0 -0
  40. {gitpython-3.1.50 → gitpython-3.1.52}/git/index/__init__.py +0 -0
  41. {gitpython-3.1.50 → gitpython-3.1.52}/git/index/fun.py +0 -0
  42. {gitpython-3.1.50 → gitpython-3.1.52}/git/index/typ.py +0 -0
  43. {gitpython-3.1.50 → gitpython-3.1.52}/git/index/util.py +0 -0
  44. {gitpython-3.1.50 → gitpython-3.1.52}/git/objects/__init__.py +0 -0
  45. {gitpython-3.1.50 → gitpython-3.1.52}/git/objects/base.py +0 -0
  46. {gitpython-3.1.50 → gitpython-3.1.52}/git/objects/blob.py +0 -0
  47. {gitpython-3.1.50 → gitpython-3.1.52}/git/objects/fun.py +0 -0
  48. {gitpython-3.1.50 → gitpython-3.1.52}/git/objects/submodule/__init__.py +0 -0
  49. {gitpython-3.1.50 → gitpython-3.1.52}/git/objects/submodule/base.py +0 -0
  50. {gitpython-3.1.50 → gitpython-3.1.52}/git/objects/submodule/root.py +0 -0
  51. {gitpython-3.1.50 → gitpython-3.1.52}/git/objects/submodule/util.py +0 -0
  52. {gitpython-3.1.50 → gitpython-3.1.52}/git/objects/tag.py +0 -0
  53. {gitpython-3.1.50 → gitpython-3.1.52}/git/objects/tree.py +0 -0
  54. {gitpython-3.1.50 → gitpython-3.1.52}/git/objects/util.py +0 -0
  55. {gitpython-3.1.50 → gitpython-3.1.52}/git/py.typed +0 -0
  56. {gitpython-3.1.50 → gitpython-3.1.52}/git/refs/__init__.py +0 -0
  57. {gitpython-3.1.50 → gitpython-3.1.52}/git/refs/head.py +0 -0
  58. {gitpython-3.1.50 → gitpython-3.1.52}/git/refs/log.py +0 -0
  59. {gitpython-3.1.50 → gitpython-3.1.52}/git/refs/reference.py +0 -0
  60. {gitpython-3.1.50 → gitpython-3.1.52}/git/refs/remote.py +0 -0
  61. {gitpython-3.1.50 → gitpython-3.1.52}/git/refs/symbolic.py +0 -0
  62. {gitpython-3.1.50 → gitpython-3.1.52}/git/refs/tag.py +0 -0
  63. {gitpython-3.1.50 → gitpython-3.1.52}/git/repo/__init__.py +0 -0
  64. {gitpython-3.1.50 → gitpython-3.1.52}/git/repo/fun.py +0 -0
  65. {gitpython-3.1.50 → gitpython-3.1.52}/git/types.py +0 -0
  66. {gitpython-3.1.50 → gitpython-3.1.52}/pyproject.toml +0 -0
  67. {gitpython-3.1.50 → gitpython-3.1.52}/requirements.txt +0 -0
  68. {gitpython-3.1.50 → gitpython-3.1.52}/setup.cfg +0 -0
  69. {gitpython-3.1.50 → gitpython-3.1.52}/setup.py +0 -0
  70. {gitpython-3.1.50 → 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.50
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.50
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,32 @@
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
+
5
31
  3.1.50
6
32
  ======
7
33
 
@@ -86,7 +86,7 @@ __all__ = [
86
86
  "to_hex_sha",
87
87
  ]
88
88
 
89
- __version__ = '3.1.50'
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
@@ -634,6 +634,8 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
634
634
  files_to_read = list(self._file_or_files)
635
635
  # END ensure we have a copy of the paths to handle
636
636
 
637
+ files_to_read = [osp.abspath(path) if isinstance(path, (str, os.PathLike)) else path for path in files_to_read]
638
+
637
639
  seen = set(files_to_read)
638
640
  num_read_include_files = 0
639
641
  while files_to_read:
@@ -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
  """
@@ -517,6 +517,9 @@ class FetchInfo(IterableObj):
517
517
  raise NotImplementedError
518
518
 
519
519
 
520
+ Progress = Union[RemoteProgress, "UpdateProgress", Callable[..., RemoteProgress], None]
521
+
522
+
520
523
  class Remote(LazyMixin, IterableObj):
521
524
  """Provides easy read and write access to a git remote.
522
525
 
@@ -872,7 +875,7 @@ class Remote(LazyMixin, IterableObj):
872
875
  def _get_fetch_info_from_stderr(
873
876
  self,
874
877
  proc: "Git.AutoInterrupt",
875
- progress: Union[Callable[..., Any], RemoteProgress, None],
878
+ progress: Progress,
876
879
  kill_after_timeout: Union[None, float] = None,
877
880
  ) -> IterableList["FetchInfo"]:
878
881
  progress = to_progress_instance(progress)
@@ -1000,7 +1003,7 @@ class Remote(LazyMixin, IterableObj):
1000
1003
  def fetch(
1001
1004
  self,
1002
1005
  refspec: Union[str, List[str], None] = None,
1003
- progress: Union[RemoteProgress, None, "UpdateProgress"] = None,
1006
+ progress: Progress = None,
1004
1007
  verbose: bool = True,
1005
1008
  kill_after_timeout: Union[None, float] = None,
1006
1009
  allow_unsafe_protocols: bool = False,
@@ -1068,7 +1071,10 @@ class Remote(LazyMixin, IterableObj):
1068
1071
  Git.check_unsafe_protocols(ref)
1069
1072
 
1070
1073
  if not allow_unsafe_options:
1071
- Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=self.unsafe_git_fetch_options)
1074
+ Git.check_unsafe_options(
1075
+ options=Git._option_candidates([], kwargs),
1076
+ unsafe_options=self.unsafe_git_fetch_options,
1077
+ )
1072
1078
 
1073
1079
  proc = self.repo.git.fetch(
1074
1080
  "--", self, *args, as_process=True, with_stdout=False, universal_newlines=True, v=verbose, **kwargs
@@ -1081,7 +1087,7 @@ class Remote(LazyMixin, IterableObj):
1081
1087
  def pull(
1082
1088
  self,
1083
1089
  refspec: Union[str, List[str], None] = None,
1084
- progress: Union[RemoteProgress, "UpdateProgress", None] = None,
1090
+ progress: Progress = None,
1085
1091
  kill_after_timeout: Union[None, float] = None,
1086
1092
  allow_unsafe_protocols: bool = False,
1087
1093
  allow_unsafe_options: bool = False,
@@ -1122,7 +1128,10 @@ class Remote(LazyMixin, IterableObj):
1122
1128
  Git.check_unsafe_protocols(ref)
1123
1129
 
1124
1130
  if not allow_unsafe_options:
1125
- Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=self.unsafe_git_pull_options)
1131
+ Git.check_unsafe_options(
1132
+ options=Git._option_candidates([], kwargs),
1133
+ unsafe_options=self.unsafe_git_pull_options,
1134
+ )
1126
1135
 
1127
1136
  proc = self.repo.git.pull(
1128
1137
  "--", self, refspec, with_stdout=False, as_process=True, universal_newlines=True, v=True, **kwargs
@@ -1135,7 +1144,7 @@ class Remote(LazyMixin, IterableObj):
1135
1144
  def push(
1136
1145
  self,
1137
1146
  refspec: Union[str, List[str], None] = None,
1138
- progress: Union[RemoteProgress, "UpdateProgress", Callable[..., RemoteProgress], None] = None,
1147
+ progress: Progress = None,
1139
1148
  kill_after_timeout: Union[None, float] = None,
1140
1149
  allow_unsafe_protocols: bool = False,
1141
1150
  allow_unsafe_options: bool = False,
@@ -1195,7 +1204,10 @@ class Remote(LazyMixin, IterableObj):
1195
1204
  Git.check_unsafe_protocols(ref)
1196
1205
 
1197
1206
  if not allow_unsafe_options:
1198
- Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=self.unsafe_git_push_options)
1207
+ Git.check_unsafe_options(
1208
+ options=Git._option_candidates([], kwargs),
1209
+ unsafe_options=self.unsafe_git_push_options,
1210
+ )
1199
1211
 
1200
1212
  proc = self.repo.git.push(
1201
1213
  "--",
@@ -161,6 +161,20 @@ class Repo:
161
161
  https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---configltkeygtltvaluegt
162
162
  """
163
163
 
164
+ unsafe_git_archive_options = [
165
+ # Allows arbitrary command execution through the remote git-upload-archive command.
166
+ "--exec",
167
+ # Writes output to a caller-controlled filesystem path.
168
+ "--output",
169
+ "-o",
170
+ ]
171
+
172
+ unsafe_git_revision_options = [
173
+ # This option allows output to be written to arbitrary files before revision parsing.
174
+ "--output",
175
+ "-o",
176
+ ]
177
+
164
178
  # Invariants
165
179
  config_level: ConfigLevels_Tup = ("system", "user", "global", "repository")
166
180
  """Represents the configuration level of a configuration file."""
@@ -295,7 +309,8 @@ class Repo:
295
309
  sm_gitpath = find_worktree_git_dir(dotgit)
296
310
 
297
311
  if sm_gitpath is not None:
298
- git_dir = expand_path(sm_gitpath, expand_vars)
312
+ # worktrees can use relative paths as of Git 2.48, so we join to curpath
313
+ git_dir = osp.normpath(osp.join(curpath, sm_gitpath))
299
314
  self._working_tree_dir = curpath
300
315
  break
301
316
 
@@ -774,6 +789,7 @@ class Repo:
774
789
  self,
775
790
  rev: Union[str, Commit, "SymbolicReference", None] = None,
776
791
  paths: Union[PathLike, Sequence[PathLike]] = "",
792
+ allow_unsafe_options: bool = False,
777
793
  **kwargs: Any,
778
794
  ) -> Iterator[Commit]:
779
795
  """An iterator of :class:`~git.objects.commit.Commit` objects representing the
@@ -791,6 +807,9 @@ class Repo:
791
807
  Arguments to be passed to :manpage:`git-rev-list(1)`.
792
808
  Common ones are ``max_count`` and ``skip``.
793
809
 
810
+ :param allow_unsafe_options:
811
+ Allow unsafe options in the revision argument, like ``--output``.
812
+
794
813
  :note:
795
814
  To receive only commits between two named revisions, use the
796
815
  ``"revA...revB"`` revision specifier.
@@ -801,7 +820,18 @@ class Repo:
801
820
  if rev is None:
802
821
  rev = self.head.commit
803
822
 
804
- return Commit.iter_items(self, rev, paths, **kwargs)
823
+ if not allow_unsafe_options:
824
+ Git.check_unsafe_options(
825
+ options=Git._option_candidates([rev], kwargs), unsafe_options=self.unsafe_git_revision_options
826
+ )
827
+
828
+ return Commit.iter_items(
829
+ self,
830
+ rev,
831
+ paths,
832
+ allow_unsafe_options=allow_unsafe_options,
833
+ **kwargs,
834
+ )
805
835
 
806
836
  def merge_base(self, *rev: TBD, **kwargs: Any) -> List[Commit]:
807
837
  R"""Find the closest common ancestor for the given revision
@@ -1078,7 +1108,9 @@ class Repo:
1078
1108
  )
1079
1109
  return active_branch
1080
1110
 
1081
- def blame_incremental(self, rev: str | HEAD | None, file: str, **kwargs: Any) -> Iterator["BlameEntry"]:
1111
+ def blame_incremental(
1112
+ self, rev: str | HEAD | None, file: str, allow_unsafe_options: bool = False, **kwargs: Any
1113
+ ) -> Iterator["BlameEntry"]:
1082
1114
  """Iterator for blame information for the given file at the given revision.
1083
1115
 
1084
1116
  Unlike :meth:`blame`, this does not return the actual file's contents, only a
@@ -1089,6 +1121,9 @@ class Repo:
1089
1121
  uncommitted changes. Otherwise, anything successfully parsed by
1090
1122
  :manpage:`git-rev-parse(1)` is a valid option.
1091
1123
 
1124
+ :param allow_unsafe_options:
1125
+ Allow unsafe options in revision argument, like ``--output``.
1126
+
1092
1127
  :return:
1093
1128
  Lazy iterator of :class:`BlameEntry` tuples, where the commit indicates the
1094
1129
  commit to blame for the line, and range indicates a span of line numbers in
@@ -1097,6 +1132,10 @@ class Repo:
1097
1132
  If you combine all line number ranges outputted by this command, you should get
1098
1133
  a continuous range spanning all line numbers in the file.
1099
1134
  """
1135
+ if not allow_unsafe_options:
1136
+ Git.check_unsafe_options(
1137
+ options=Git._option_candidates([rev], kwargs), unsafe_options=self.unsafe_git_revision_options
1138
+ )
1100
1139
 
1101
1140
  data: bytes = self.git.blame(rev, "--", file, p=True, incremental=True, stdout_as_string=False, **kwargs)
1102
1141
  commits: Dict[bytes, Commit] = {}
@@ -1175,7 +1214,8 @@ class Repo:
1175
1214
  rev: Union[str, HEAD, None],
1176
1215
  file: str,
1177
1216
  incremental: bool = False,
1178
- rev_opts: Optional[List[str]] = None,
1217
+ rev_opts: Optional[Sequence[str]] = None,
1218
+ allow_unsafe_options: bool = False,
1179
1219
  **kwargs: Any,
1180
1220
  ) -> List[List[Commit | List[str | bytes] | None]] | Iterator[BlameEntry] | None:
1181
1221
  """The blame information for the given file at the given revision.
@@ -1185,6 +1225,9 @@ class Repo:
1185
1225
  uncommitted changes. Otherwise, anything successfully parsed by
1186
1226
  :manpage:`git-rev-parse(1)` is a valid option.
1187
1227
 
1228
+ :param allow_unsafe_options:
1229
+ Allow unsafe options in revision argument, like ``--output``.
1230
+
1188
1231
  :return:
1189
1232
  list: [git.Commit, list: [<line>]]
1190
1233
 
@@ -1194,9 +1237,14 @@ class Repo:
1194
1237
  appearance.
1195
1238
  """
1196
1239
  if incremental:
1197
- return self.blame_incremental(rev, file, **kwargs)
1198
- rev_opts = rev_opts or []
1199
- data: bytes = self.git.blame(rev, *rev_opts, "--", file, p=True, stdout_as_string=False, **kwargs)
1240
+ return self.blame_incremental(rev, file, allow_unsafe_options=allow_unsafe_options, **kwargs)
1241
+ rev_opts_list = list(rev_opts or [])
1242
+ if not allow_unsafe_options:
1243
+ Git.check_unsafe_options(
1244
+ options=Git._option_candidates([rev, rev_opts_list], kwargs),
1245
+ unsafe_options=self.unsafe_git_revision_options,
1246
+ )
1247
+ data: bytes = self.git.blame(rev, *rev_opts_list, "--", file, p=True, stdout_as_string=False, **kwargs)
1200
1248
  commits: Dict[str, Commit] = {}
1201
1249
  blames: List[List[Commit | List[str | bytes] | None]] = []
1202
1250
 
@@ -1404,17 +1452,21 @@ class Repo:
1404
1452
  if multi_options:
1405
1453
  multi = shlex.split(" ".join(multi_options))
1406
1454
 
1455
+ clone_url = Git.polish_url(url, expand_vars=False)
1407
1456
  if not allow_unsafe_protocols:
1408
- Git.check_unsafe_protocols(url)
1457
+ Git.check_unsafe_protocols(clone_url)
1409
1458
  if not allow_unsafe_options:
1410
- Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=cls.unsafe_git_clone_options)
1459
+ Git.check_unsafe_options(
1460
+ options=Git._option_candidates([], kwargs),
1461
+ unsafe_options=cls.unsafe_git_clone_options,
1462
+ )
1411
1463
  if not allow_unsafe_options and multi:
1412
1464
  Git.check_unsafe_options(options=multi, unsafe_options=cls.unsafe_git_clone_options)
1413
1465
 
1414
1466
  proc = git.clone(
1415
1467
  multi,
1416
1468
  "--",
1417
- Git.polish_url(url),
1469
+ clone_url,
1418
1470
  clone_path,
1419
1471
  with_extended_output=True,
1420
1472
  as_process=True,
@@ -1454,7 +1506,7 @@ class Repo:
1454
1506
  # escape the backslashes. Hence we undo the escaping just to be sure.
1455
1507
  if repo.remotes:
1456
1508
  with repo.remotes[0].config_writer as writer:
1457
- writer.set_value("url", Git.polish_url(repo.remotes[0].url))
1509
+ writer.set_value("url", Git.polish_url(repo.remotes[0].url, expand_vars=False))
1458
1510
  # END handle remote repo
1459
1511
  return repo
1460
1512
 
@@ -1582,6 +1634,8 @@ class Repo:
1582
1634
  ostream: Union[TextIO, BinaryIO],
1583
1635
  treeish: Optional[str] = None,
1584
1636
  prefix: Optional[str] = None,
1637
+ allow_unsafe_options: bool = False,
1638
+ allow_unsafe_protocols: bool = False,
1585
1639
  **kwargs: Any,
1586
1640
  ) -> Repo:
1587
1641
  """Archive the tree at the given revision.
@@ -1604,6 +1658,12 @@ class Repo:
1604
1658
  repository-relative path to a directory or file to place into the archive,
1605
1659
  or a list or tuple of multiple paths.
1606
1660
 
1661
+ :param allow_unsafe_options:
1662
+ Allow unsafe options, like ``--exec`` or ``--output``.
1663
+
1664
+ :param allow_unsafe_protocols:
1665
+ Allow unsafe protocols to be used in ``remote``, like ``ext``.
1666
+
1607
1667
  :raise git.exc.GitCommandError:
1608
1668
  If something went wrong.
1609
1669
 
@@ -1614,6 +1674,14 @@ class Repo:
1614
1674
  treeish = self.head.commit
1615
1675
  if prefix and "prefix" not in kwargs:
1616
1676
  kwargs["prefix"] = prefix
1677
+ remote = kwargs.get("remote")
1678
+ if not allow_unsafe_protocols and remote is not None:
1679
+ Git.check_unsafe_protocols(str(remote))
1680
+ if not allow_unsafe_options:
1681
+ Git.check_unsafe_options(
1682
+ options=Git._option_candidates([], kwargs),
1683
+ unsafe_options=self.unsafe_git_archive_options,
1684
+ )
1617
1685
  kwargs["output_stream"] = ostream
1618
1686
  path = kwargs.pop("path", [])
1619
1687
  path = cast(Union[PathLike, List[PathLike], Tuple[PathLike, ...]], path)
@@ -382,13 +382,13 @@ def py_where(program: str, path: Optional[PathLike] = None) -> List[str]:
382
382
  return progs
383
383
 
384
384
 
385
- def _cygexpath(drive: Optional[str], path: str) -> str:
385
+ def _cygexpath(drive: Optional[str], path: str, expand_vars: bool = True) -> str:
386
386
  if osp.isabs(path) and not drive:
387
387
  # Invoked from `cygpath()` directly with `D:Apps\123`?
388
388
  # It's an error, leave it alone just slashes)
389
389
  p = path # convert to str if AnyPath given
390
390
  else:
391
- p = path and osp.normpath(osp.expandvars(osp.expanduser(path)))
391
+ p = path and osp.normpath(osp.expandvars(osp.expanduser(path)) if expand_vars else path)
392
392
  if osp.isabs(p):
393
393
  if drive:
394
394
  # Confusing, maybe a remote system should expand vars.
@@ -416,7 +416,7 @@ _cygpath_parsers: Tuple[Tuple[Pattern[str], Callable, bool], ...] = (
416
416
  )
417
417
 
418
418
 
419
- def cygpath(path: str) -> str:
419
+ def cygpath(path: str, expand_vars: bool = True) -> str:
420
420
  """Use :meth:`git.cmd.Git.polish_url` instead, that works on any environment."""
421
421
  path = os.fspath(path) # Ensure is str and not AnyPath.
422
422
  # Fix to use Paths when 3.5 dropped. Or to be just str if only for URLs?
@@ -424,12 +424,15 @@ def cygpath(path: str) -> str:
424
424
  for regex, parser, recurse in _cygpath_parsers:
425
425
  match = regex.match(path)
426
426
  if match:
427
- path = parser(*match.groups())
427
+ if parser is _cygexpath:
428
+ path = parser(*match.groups(), expand_vars=expand_vars)
429
+ else:
430
+ path = parser(*match.groups())
428
431
  if recurse:
429
- path = cygpath(path)
432
+ path = cygpath(path, expand_vars=expand_vars)
430
433
  break
431
434
  else:
432
- path = _cygexpath(None, path)
435
+ path = _cygexpath(None, path, expand_vars=expand_vars)
433
436
 
434
437
  return path
435
438
 
@@ -1,15 +0,0 @@
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
- ## Fuzzing Test Specific Documentation
13
-
14
- For details related to contributing to the fuzzing test suite and OSS-Fuzz integration, please
15
- refer to the dedicated [fuzzing README](./fuzzing/README.md).
gitpython-3.1.50/VERSION DELETED
@@ -1 +0,0 @@
1
- 3.1.50
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes