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.
- {gitpython-3.1.48 → gitpython-3.1.52}/AUTHORS +1 -0
- gitpython-3.1.52/CONTRIBUTING.md +46 -0
- {gitpython-3.1.48 → gitpython-3.1.52/GitPython.egg-info}/PKG-INFO +1 -1
- {gitpython-3.1.48/GitPython.egg-info → gitpython-3.1.52}/PKG-INFO +1 -1
- gitpython-3.1.52/VERSION +1 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/changes.rst +45 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/__init__.py +1 -1
- {gitpython-3.1.48 → gitpython-3.1.52}/git/cmd.py +130 -17
- {gitpython-3.1.48 → gitpython-3.1.52}/git/config.py +39 -2
- {gitpython-3.1.48 → gitpython-3.1.52}/git/diff.py +19 -4
- {gitpython-3.1.48 → gitpython-3.1.52}/git/index/base.py +40 -3
- {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/commit.py +17 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/remote.py +19 -7
- {gitpython-3.1.48 → gitpython-3.1.52}/git/repo/base.py +101 -11
- gitpython-3.1.52/git/repo/fun.py +736 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/util.py +9 -6
- gitpython-3.1.48/CONTRIBUTING.md +0 -15
- gitpython-3.1.48/VERSION +0 -1
- gitpython-3.1.48/git/repo/fun.py +0 -425
- {gitpython-3.1.48 → gitpython-3.1.52}/CHANGES +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/GitPython.egg-info/SOURCES.txt +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/GitPython.egg-info/dependency_links.txt +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/GitPython.egg-info/not-zip-safe +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/GitPython.egg-info/requires.txt +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/GitPython.egg-info/top_level.txt +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/LICENSE +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/MANIFEST.in +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/README.md +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/doc/.gitignore +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/doc/Makefile +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/doc/requirements.txt +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/conf.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/index.rst +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/intro.rst +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/quickstart.rst +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/reference.rst +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/roadmap.rst +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/doc/source/tutorial.rst +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/compat.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/db.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/exc.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/index/__init__.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/index/fun.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/index/typ.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/index/util.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/__init__.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/base.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/blob.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/fun.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/submodule/__init__.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/submodule/base.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/submodule/root.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/submodule/util.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/tag.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/tree.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/objects/util.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/py.typed +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/refs/__init__.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/refs/head.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/refs/log.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/refs/reference.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/refs/remote.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/refs/symbolic.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/refs/tag.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/repo/__init__.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/git/types.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/pyproject.toml +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/requirements.txt +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/setup.cfg +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/setup.py +0 -0
- {gitpython-3.1.48 → gitpython-3.1.52}/test-requirements.txt +0 -0
|
@@ -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).
|
gitpython-3.1.52/VERSION
ADDED
|
@@ -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
|
|
|
@@ -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
|
-
|
|
922
|
-
|
|
923
|
-
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
|
-
"""
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
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
|
-
|
|
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
|
|
1135
|
-
|
|
1136
|
-
|
|
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[
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
772
|
-
|
|
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[
|
|
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
|
"""
|