GitPython 3.1.57__tar.gz → 3.1.59__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.57 → gitpython-3.1.59/GitPython.egg-info}/PKG-INFO +1 -1
- {gitpython-3.1.57/GitPython.egg-info → gitpython-3.1.59}/PKG-INFO +1 -1
- gitpython-3.1.59/VERSION +1 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/doc/source/changes.rst +37 -2
- {gitpython-3.1.57 → gitpython-3.1.59}/git/__init__.py +1 -1
- {gitpython-3.1.57 → gitpython-3.1.59}/git/cmd.py +23 -6
- {gitpython-3.1.57 → gitpython-3.1.59}/git/config.py +17 -5
- {gitpython-3.1.57 → gitpython-3.1.59}/git/db.py +24 -1
- {gitpython-3.1.57 → gitpython-3.1.59}/git/diff.py +4 -3
- {gitpython-3.1.57 → gitpython-3.1.59}/git/index/base.py +64 -17
- {gitpython-3.1.57 → gitpython-3.1.59}/git/index/fun.py +100 -2
- {gitpython-3.1.57 → gitpython-3.1.59}/git/objects/commit.py +5 -2
- {gitpython-3.1.57 → gitpython-3.1.59}/git/objects/submodule/base.py +43 -19
- {gitpython-3.1.57 → gitpython-3.1.59}/git/objects/submodule/root.py +3 -2
- {gitpython-3.1.57 → gitpython-3.1.59}/git/refs/head.py +26 -1
- {gitpython-3.1.57 → gitpython-3.1.59}/git/refs/log.py +1 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/refs/symbolic.py +1 -1
- {gitpython-3.1.57 → gitpython-3.1.59}/git/refs/tag.py +6 -4
- {gitpython-3.1.57 → gitpython-3.1.59}/git/repo/base.py +53 -12
- {gitpython-3.1.57 → gitpython-3.1.59}/git/util.py +62 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/pyproject.toml +2 -2
- gitpython-3.1.57/VERSION +0 -1
- {gitpython-3.1.57 → gitpython-3.1.59}/AUTHORS +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/CHANGES +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/CONTRIBUTING.md +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/GitPython.egg-info/SOURCES.txt +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/GitPython.egg-info/dependency_links.txt +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/GitPython.egg-info/not-zip-safe +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/GitPython.egg-info/requires.txt +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/GitPython.egg-info/top_level.txt +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/LICENSE +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/MANIFEST.in +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/README.md +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/doc/.gitignore +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/doc/Makefile +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/doc/requirements.txt +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/doc/source/conf.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/doc/source/index.rst +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/doc/source/intro.rst +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/doc/source/quickstart.rst +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/doc/source/reference.rst +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/doc/source/roadmap.rst +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/doc/source/tutorial.rst +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/compat.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/exc.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/index/__init__.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/index/typ.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/index/util.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/objects/__init__.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/objects/base.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/objects/blob.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/objects/fun.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/objects/submodule/__init__.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/objects/submodule/util.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/objects/tag.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/objects/tree.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/objects/util.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/py.typed +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/refs/__init__.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/refs/reference.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/refs/remote.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/remote.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/repo/__init__.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/repo/fun.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/git/types.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/requirements.txt +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/setup.cfg +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/setup.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.59}/test-requirements.txt +0 -0
gitpython-3.1.59/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.1.59
|
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
3.1.59
|
|
6
|
+
======
|
|
7
|
+
|
|
8
|
+
Security fixes for
|
|
9
|
+
|
|
10
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-5xxx-qhh7-9287
|
|
11
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-3wxw-xv34-2frg
|
|
12
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-8mcc-hrx5-hvxc
|
|
13
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-284h-m62q-gf8w
|
|
14
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-7833-fr7j-v32q
|
|
15
|
+
|
|
16
|
+
If you can, also try and provide feedback on the upcoming v4 branch
|
|
17
|
+
https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome.
|
|
18
|
+
|
|
19
|
+
See the following for all changes.
|
|
20
|
+
https://github.com/gitpython-developers/GitPython/releases/tag/3.1.59
|
|
21
|
+
|
|
22
|
+
3.1.58
|
|
23
|
+
======
|
|
24
|
+
|
|
25
|
+
Security fixes for
|
|
26
|
+
|
|
27
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-hh9p-6wh2-4mfc
|
|
28
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-9rj7-rf2p-w77r
|
|
29
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-4gmw-gg2m-w46p
|
|
30
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-wvpp-8hx9-p66j
|
|
31
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-jm78-9fvv-mhgr
|
|
32
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-hmq2-w58f-27jc
|
|
33
|
+
|
|
34
|
+
If you can, also try and provide feedback on the upcoming v4 branch
|
|
35
|
+
https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome.
|
|
36
|
+
|
|
37
|
+
See the following for all changes.
|
|
38
|
+
https://github.com/gitpython-developers/GitPython/releases/tag/3.1.58
|
|
39
|
+
|
|
5
40
|
3.1.57
|
|
6
41
|
======
|
|
7
42
|
|
|
@@ -14,7 +49,7 @@ If you can, also try and provide feedback on the upcoming v4 branch
|
|
|
14
49
|
https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome.
|
|
15
50
|
|
|
16
51
|
See the following for all changes.
|
|
17
|
-
https://github.com/gitpython-developers/GitPython/releases/tag/3.1.
|
|
52
|
+
https://github.com/gitpython-developers/GitPython/releases/tag/3.1.57
|
|
18
53
|
|
|
19
54
|
3.1.56
|
|
20
55
|
======
|
|
@@ -27,7 +62,7 @@ If you can, also try and provide feedback on the upcoming v4 branch
|
|
|
27
62
|
https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome.
|
|
28
63
|
|
|
29
64
|
See the following for all changes.
|
|
30
|
-
https://github.com/gitpython-developers/GitPython/releases/tag/3.1.
|
|
65
|
+
https://github.com/gitpython-developers/GitPython/releases/tag/3.1.56
|
|
31
66
|
|
|
32
67
|
3.1.55
|
|
33
68
|
======
|
|
@@ -652,6 +652,13 @@ class Git(metaclass=_GitMeta):
|
|
|
652
652
|
unsafe_git_ls_remote_options = [
|
|
653
653
|
# This option allows arbitrary command execution in git-ls-remote.
|
|
654
654
|
"--upload-pack",
|
|
655
|
+
"--exec",
|
|
656
|
+
]
|
|
657
|
+
|
|
658
|
+
unsafe_git_pathspec_from_file_options = [
|
|
659
|
+
# Reads pathspecs from a caller-controlled file. Some commands include an
|
|
660
|
+
# unmatched pathspec in their error output, which can disclose the file.
|
|
661
|
+
"--pathspec-from-file",
|
|
655
662
|
]
|
|
656
663
|
|
|
657
664
|
def __getstate__(self) -> Dict[str, Any]:
|
|
@@ -970,7 +977,9 @@ class Git(metaclass=_GitMeta):
|
|
|
970
977
|
return dashify(option_tokens[0])
|
|
971
978
|
|
|
972
979
|
@classmethod
|
|
973
|
-
def check_unsafe_options(
|
|
980
|
+
def check_unsafe_options(
|
|
981
|
+
cls, options: List[str], unsafe_options: List[str], clusterable_short_options: str = "46flnqsv"
|
|
982
|
+
) -> None:
|
|
974
983
|
"""Raise :class:`~git.exc.UnsafeOptionError` for blocked option spellings.
|
|
975
984
|
|
|
976
985
|
In addition to exact matches, this rejects abbreviated long options accepted
|
|
@@ -1005,7 +1014,7 @@ class Git(metaclass=_GitMeta):
|
|
|
1005
1014
|
# These value-less Git flags can be clustered before another short option
|
|
1006
1015
|
# (for example, ``-fuVALUE``). Stop at any other character because it may
|
|
1007
1016
|
# begin an attached value, as ``o`` does in the safe option ``-oupstream``.
|
|
1008
|
-
|
|
1017
|
+
clusterable_short_options_set = frozenset(clusterable_short_options)
|
|
1009
1018
|
options_are_kwargs = all(not option.startswith("-") for option in options)
|
|
1010
1019
|
for option in options:
|
|
1011
1020
|
candidate = cls._canonicalize_option_name(option)
|
|
@@ -1022,7 +1031,7 @@ class Git(metaclass=_GitMeta):
|
|
|
1022
1031
|
raise UnsafeOptionError(
|
|
1023
1032
|
f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it."
|
|
1024
1033
|
)
|
|
1025
|
-
if option_char not in
|
|
1034
|
+
if option_char not in clusterable_short_options_set:
|
|
1026
1035
|
break
|
|
1027
1036
|
if not (option.startswith("--") or (options_are_kwargs and len(candidate) > 1)):
|
|
1028
1037
|
continue
|
|
@@ -1044,13 +1053,21 @@ class Git(metaclass=_GitMeta):
|
|
|
1044
1053
|
values = value if isinstance(value, (list, tuple)) else (value,)
|
|
1045
1054
|
if any(value is True or (value is not False and value is not None) for value in values):
|
|
1046
1055
|
key = str(key)
|
|
1047
|
-
|
|
1048
|
-
|
|
1056
|
+
if len(key) != 1:
|
|
1057
|
+
options.append(f"--{dashify(key)}")
|
|
1058
|
+
elif split_single_char_options:
|
|
1059
|
+
options.append(f"-{key}")
|
|
1049
1060
|
options.extend(
|
|
1050
1061
|
str(value)
|
|
1051
1062
|
for value in values
|
|
1052
1063
|
if value is not True and value not in (False, None) and str(value).startswith("-")
|
|
1053
1064
|
)
|
|
1065
|
+
else:
|
|
1066
|
+
options.extend(
|
|
1067
|
+
f"-{key}" if value is True else f"-{key}{value}"
|
|
1068
|
+
for value in values
|
|
1069
|
+
if value is True or (value is not False and value is not None)
|
|
1070
|
+
)
|
|
1054
1071
|
return options
|
|
1055
1072
|
|
|
1056
1073
|
AutoInterrupt: TypeAlias = _AutoInterrupt
|
|
@@ -1119,7 +1136,7 @@ class Git(metaclass=_GitMeta):
|
|
|
1119
1136
|
"""List references in a remote repository.
|
|
1120
1137
|
|
|
1121
1138
|
:param allow_unsafe_options:
|
|
1122
|
-
Allow unsafe options, like ``--upload-pack``.
|
|
1139
|
+
Allow unsafe options, like ``--upload-pack`` or ``--exec``.
|
|
1123
1140
|
"""
|
|
1124
1141
|
if not allow_unsafe_options:
|
|
1125
1142
|
candidate_options = self._option_candidates(args, kwargs)
|
|
@@ -75,6 +75,9 @@ See: https://git-scm.com/docs/git-config#_conditional_includes
|
|
|
75
75
|
UNSAFE_CONFIG_CHARS_RE = re.compile(r"[\r\n\x00]")
|
|
76
76
|
"""Characters that cannot be safely written in config names or values."""
|
|
77
77
|
|
|
78
|
+
VALID_CONFIG_OPTION_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]+$")
|
|
79
|
+
"""Pattern for option names that can be written without changing config syntax."""
|
|
80
|
+
|
|
78
81
|
|
|
79
82
|
class MetaParserBuilder(abc.ABCMeta): # noqa: B024
|
|
80
83
|
"""Utility class wrapping base-class methods into decorators that assure read-only
|
|
@@ -577,8 +580,12 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
577
580
|
|
|
578
581
|
if keyword in ["gitdir", "gitdir/i"]:
|
|
579
582
|
value = osp.expanduser(value)
|
|
583
|
+
git_dir = os.fspath(self._repo.git_dir) if self._repo.git_dir else None
|
|
584
|
+
if sys.platform == "win32":
|
|
585
|
+
git_dir = git_dir.replace("\\", "/") if git_dir else None
|
|
580
586
|
|
|
581
|
-
|
|
587
|
+
drive, _tail = osp.splitdrive(value)
|
|
588
|
+
if not drive and not any(value.startswith(s) for s in ["./", "/"]):
|
|
582
589
|
value = "**/" + value
|
|
583
590
|
if value.endswith("/"):
|
|
584
591
|
value += "**"
|
|
@@ -590,9 +597,8 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
590
597
|
lambda m: f"[{m.group().lower()!r}{m.group().upper()!r}]",
|
|
591
598
|
value,
|
|
592
599
|
)
|
|
593
|
-
if
|
|
594
|
-
|
|
595
|
-
paths += _all_items(section)
|
|
600
|
+
if git_dir and fnmatch.fnmatchcase(git_dir, value):
|
|
601
|
+
paths += _all_items(section)
|
|
596
602
|
|
|
597
603
|
elif keyword == "onbranch":
|
|
598
604
|
try:
|
|
@@ -699,7 +705,11 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
699
705
|
continue
|
|
700
706
|
|
|
701
707
|
for v in values:
|
|
702
|
-
|
|
708
|
+
value = self._value_to_string(v)
|
|
709
|
+
if any(char in value for char in '\n\t\b\\"'):
|
|
710
|
+
value = value.replace("\\", "\\\\").replace('"', '\\"')
|
|
711
|
+
value = '"%s\\\n"' % value.replace("\n", "\\n").replace("\t", "\\t").replace("\b", "\\b")
|
|
712
|
+
fp.write(("\t%s = %s\n" % (key, value)).encode(defenc))
|
|
703
713
|
# END if key is not __name__
|
|
704
714
|
|
|
705
715
|
# END section writing
|
|
@@ -897,6 +907,8 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
897
907
|
def _assure_config_name_safe(self, name: "cp._SectionName", label: str) -> None:
|
|
898
908
|
if isinstance(name, str) and UNSAFE_CONFIG_CHARS_RE.search(name):
|
|
899
909
|
raise ValueError("Git config %s names must not contain CR, LF, or NUL" % label)
|
|
910
|
+
if label == "option" and isinstance(name, str) and not VALID_CONFIG_OPTION_NAME_RE.fullmatch(name):
|
|
911
|
+
raise ValueError("Git config option names may contain only letters, digits, '-', '_', or '.'")
|
|
900
912
|
if label == "section" and isinstance(name, str):
|
|
901
913
|
in_quotes = False
|
|
902
914
|
escaped = False
|
|
@@ -5,10 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
__all__ = ["GitCmdObjectDB", "GitDB"]
|
|
7
7
|
|
|
8
|
-
from
|
|
8
|
+
from subprocess import PIPE
|
|
9
|
+
|
|
10
|
+
from gitdb.base import IStream, OInfo, OStream
|
|
9
11
|
from gitdb.db import GitDB, LooseObjectDB
|
|
10
12
|
from gitdb.exc import BadObject
|
|
13
|
+
from gitdb.fun import stream_copy
|
|
11
14
|
|
|
15
|
+
from git.compat import force_text
|
|
12
16
|
from git.util import bin_to_hex, hex_to_bin
|
|
13
17
|
from git.exc import GitCommandError
|
|
14
18
|
|
|
@@ -46,6 +50,25 @@ class GitCmdObjectDB(LooseObjectDB):
|
|
|
46
50
|
hexsha, typename, size, stream = self._git.stream_object_data(bin_to_hex(binsha))
|
|
47
51
|
return OStream(hex_to_bin(hexsha), typename, size, stream)
|
|
48
52
|
|
|
53
|
+
def store(self, istream: IStream) -> IStream:
|
|
54
|
+
"""Store an object using git itself."""
|
|
55
|
+
if istream.binsha is not None or self.ostream() is not None:
|
|
56
|
+
return super().store(istream)
|
|
57
|
+
|
|
58
|
+
proc = self._git.hash_object(
|
|
59
|
+
"-t", force_text(istream.type), "-w", "--stdin", "--literally", as_process=True, istream=PIPE
|
|
60
|
+
)
|
|
61
|
+
assert proc.stdin is not None
|
|
62
|
+
try:
|
|
63
|
+
stream_copy(istream.read, proc.stdin.write, istream.size, self.stream_chunk_size)
|
|
64
|
+
finally:
|
|
65
|
+
proc.stdin.close()
|
|
66
|
+
assert proc.stdout is not None
|
|
67
|
+
hexsha = proc.stdout.read().strip()
|
|
68
|
+
proc.wait()
|
|
69
|
+
istream.binsha = hex_to_bin(hexsha)
|
|
70
|
+
return istream
|
|
71
|
+
|
|
49
72
|
# { Interface
|
|
50
73
|
|
|
51
74
|
def partial_to_complete_sha_hex(self, partial_hexsha: str) -> bytes:
|
|
@@ -220,8 +220,8 @@ class Diffable:
|
|
|
220
220
|
to be read and diffed.
|
|
221
221
|
|
|
222
222
|
:param allow_unsafe_options:
|
|
223
|
-
If ``True``, allow options such as ``--output`` that can write to
|
|
224
|
-
filesystem paths.
|
|
223
|
+
If ``True``, allow options such as ``--output`` and ``-O`` that can write to
|
|
224
|
+
or read from arbitrary filesystem paths.
|
|
225
225
|
|
|
226
226
|
:param kwargs:
|
|
227
227
|
Additional arguments passed to :manpage:`git-diff(1)`, such as ``R=True`` to
|
|
@@ -238,7 +238,8 @@ class Diffable:
|
|
|
238
238
|
if not allow_unsafe_options:
|
|
239
239
|
Git.check_unsafe_options(
|
|
240
240
|
options=Git._option_candidates([other], kwargs),
|
|
241
|
-
unsafe_options=self.repo.
|
|
241
|
+
unsafe_options=self.repo.unsafe_git_diff_options,
|
|
242
|
+
clusterable_short_options="46abceflmnpqrstuvwzBCDMNRW",
|
|
242
243
|
)
|
|
243
244
|
|
|
244
245
|
args: List[Union[PathLike, Diffable]] = []
|
|
@@ -34,6 +34,8 @@ from git.util import (
|
|
|
34
34
|
LockedFD,
|
|
35
35
|
join_path_native,
|
|
36
36
|
file_contents_ro,
|
|
37
|
+
_is_path_rooted,
|
|
38
|
+
_to_relative_path,
|
|
37
39
|
to_native_path_linux,
|
|
38
40
|
unbare_repo,
|
|
39
41
|
to_bin_sha,
|
|
@@ -58,6 +60,7 @@ from typing import (
|
|
|
58
60
|
Any,
|
|
59
61
|
BinaryIO,
|
|
60
62
|
Callable,
|
|
63
|
+
cast,
|
|
61
64
|
Dict,
|
|
62
65
|
Generator,
|
|
63
66
|
IO,
|
|
@@ -131,6 +134,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
131
134
|
"""
|
|
132
135
|
|
|
133
136
|
unsafe_git_checkout_index_options = ["--prefix"]
|
|
137
|
+
unsafe_git_read_tree_options = ["--index-output"]
|
|
134
138
|
|
|
135
139
|
__slots__ = ("repo", "version", "entries", "_extension_data", "_file_path")
|
|
136
140
|
|
|
@@ -256,7 +260,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
256
260
|
|
|
257
261
|
@post_clear_cache
|
|
258
262
|
@default_index
|
|
259
|
-
def merge_tree(
|
|
263
|
+
def merge_tree(
|
|
264
|
+
self,
|
|
265
|
+
rhs: Treeish,
|
|
266
|
+
base: Union[None, Treeish] = None,
|
|
267
|
+
allow_unsafe_options: bool = False,
|
|
268
|
+
) -> "IndexFile":
|
|
260
269
|
"""Merge the given `rhs` treeish into the current index, possibly taking
|
|
261
270
|
a common base treeish into account.
|
|
262
271
|
|
|
@@ -270,6 +279,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
270
279
|
Optional treeish reference pointing to the common base of `rhs` and this
|
|
271
280
|
index which equals lhs.
|
|
272
281
|
|
|
282
|
+
:param allow_unsafe_options:
|
|
283
|
+
Allow options that may write to arbitrary paths.
|
|
284
|
+
|
|
273
285
|
:return:
|
|
274
286
|
self (containing the merge and possibly unmerged entries in case of
|
|
275
287
|
conflicts)
|
|
@@ -280,6 +292,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
280
292
|
yourself, you have to commit the changed index (or make a valid tree from
|
|
281
293
|
it) and retry with a three-way :meth:`index.from_tree <from_tree>` call.
|
|
282
294
|
"""
|
|
295
|
+
if not allow_unsafe_options:
|
|
296
|
+
Git.check_unsafe_options(
|
|
297
|
+
options=Git._option_candidates([base, rhs]),
|
|
298
|
+
unsafe_options=self.unsafe_git_read_tree_options,
|
|
299
|
+
)
|
|
300
|
+
|
|
283
301
|
# -i : ignore working tree status
|
|
284
302
|
# --aggressive : handle more merge cases
|
|
285
303
|
# -m : do an actual merge
|
|
@@ -324,7 +342,13 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
324
342
|
return inst
|
|
325
343
|
|
|
326
344
|
@classmethod
|
|
327
|
-
def from_tree(
|
|
345
|
+
def from_tree(
|
|
346
|
+
cls,
|
|
347
|
+
repo: "Repo",
|
|
348
|
+
*treeish: Treeish,
|
|
349
|
+
allow_unsafe_options: bool = False,
|
|
350
|
+
**kwargs: Any,
|
|
351
|
+
) -> "IndexFile":
|
|
328
352
|
R"""Merge the given treeish revisions into a new index which is returned.
|
|
329
353
|
The original index will remain unaltered.
|
|
330
354
|
|
|
@@ -348,6 +372,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
348
372
|
:param kwargs:
|
|
349
373
|
Additional arguments passed to :manpage:`git-read-tree(1)`.
|
|
350
374
|
|
|
375
|
+
:param allow_unsafe_options:
|
|
376
|
+
Allow options that may write to arbitrary paths.
|
|
377
|
+
|
|
351
378
|
:return:
|
|
352
379
|
New :class:`IndexFile` instance. It will point to a temporary index location
|
|
353
380
|
which does not exist anymore. If you intend to write such a merged Index,
|
|
@@ -365,6 +392,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
365
392
|
if len(treeish) == 0 or len(treeish) > 3:
|
|
366
393
|
raise ValueError("Please specify between 1 and 3 treeish, got %i" % len(treeish))
|
|
367
394
|
|
|
395
|
+
if not allow_unsafe_options:
|
|
396
|
+
Git.check_unsafe_options(
|
|
397
|
+
options=Git._option_candidates(treeish, kwargs),
|
|
398
|
+
unsafe_options=cls.unsafe_git_read_tree_options,
|
|
399
|
+
)
|
|
400
|
+
|
|
368
401
|
arg_list: List[Union[Treeish, str]] = []
|
|
369
402
|
# Ignore that the working tree and index possibly are out of date.
|
|
370
403
|
if len(treeish) > 1:
|
|
@@ -655,16 +688,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
655
688
|
|
|
656
689
|
:raise ValueError:
|
|
657
690
|
"""
|
|
658
|
-
if not osp.isabs(path):
|
|
659
|
-
return path
|
|
660
691
|
if self.repo.bare:
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
result += os.sep
|
|
667
|
-
return result
|
|
692
|
+
drive, _tail = osp.splitdrive(os.fspath(path))
|
|
693
|
+
if drive or _is_path_rooted(path):
|
|
694
|
+
raise InvalidGitRepositoryError("paths with a drive or root require a non-bare repository")
|
|
695
|
+
return path
|
|
696
|
+
return _to_relative_path(cast(PathLike, self.repo.working_tree_dir), path)
|
|
668
697
|
|
|
669
698
|
def _preprocess_add_items(
|
|
670
699
|
self, items: Union[PathLike, Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]]
|
|
@@ -731,14 +760,17 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
731
760
|
) -> List[BaseIndexEntry]:
|
|
732
761
|
entries_added: List[BaseIndexEntry] = []
|
|
733
762
|
if path_rewriter:
|
|
763
|
+
working_tree_dir = self.repo.working_tree_dir
|
|
764
|
+
if working_tree_dir is None:
|
|
765
|
+
raise InvalidGitRepositoryError("Cannot rewrite paths without a working tree")
|
|
766
|
+
working_tree_dir = str(working_tree_dir)
|
|
734
767
|
for path in paths:
|
|
735
768
|
if osp.isabs(path):
|
|
736
769
|
abspath = path
|
|
737
|
-
gitrelative_path = path[len(
|
|
770
|
+
gitrelative_path = path[len(working_tree_dir) + 1 :]
|
|
738
771
|
else:
|
|
739
772
|
gitrelative_path = path
|
|
740
|
-
|
|
741
|
-
abspath = osp.join(self.repo.working_tree_dir, gitrelative_path)
|
|
773
|
+
abspath = osp.join(working_tree_dir, gitrelative_path)
|
|
742
774
|
# END obtain relative and absolute paths
|
|
743
775
|
|
|
744
776
|
blob = Blob(
|
|
@@ -992,6 +1024,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
992
1024
|
self,
|
|
993
1025
|
items: Union[PathLike, Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]],
|
|
994
1026
|
working_tree: bool = False,
|
|
1027
|
+
allow_unsafe_options: bool = False,
|
|
995
1028
|
**kwargs: Any,
|
|
996
1029
|
) -> List[str]:
|
|
997
1030
|
R"""Remove the given items from the index and optionally from the working tree
|
|
@@ -1022,6 +1055,10 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1022
1055
|
physically removing the respective file. This may fail if there are
|
|
1023
1056
|
uncommitted changes in it.
|
|
1024
1057
|
|
|
1058
|
+
:param allow_unsafe_options:
|
|
1059
|
+
Allow unsafe options such as ``--pathspec-from-file`` to be passed to
|
|
1060
|
+
:manpage:`git-rm(1)`.
|
|
1061
|
+
|
|
1025
1062
|
:param kwargs:
|
|
1026
1063
|
Additional keyword arguments to be passed to :manpage:`git-rm(1)`, such as
|
|
1027
1064
|
``r`` to allow recursive removal.
|
|
@@ -1033,6 +1070,11 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1033
1070
|
This is interesting to know in case you have provided a directory or globs.
|
|
1034
1071
|
Paths are relative to the repository.
|
|
1035
1072
|
"""
|
|
1073
|
+
if not allow_unsafe_options:
|
|
1074
|
+
Git.check_unsafe_options(
|
|
1075
|
+
options=Git._option_candidates([], kwargs),
|
|
1076
|
+
unsafe_options=Git.unsafe_git_pathspec_from_file_options,
|
|
1077
|
+
)
|
|
1036
1078
|
args = []
|
|
1037
1079
|
if not working_tree:
|
|
1038
1080
|
args.append("--cached")
|
|
@@ -1414,6 +1456,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1414
1456
|
working_tree: bool = False,
|
|
1415
1457
|
paths: Union[None, Iterable[PathLike]] = None,
|
|
1416
1458
|
head: bool = False,
|
|
1459
|
+
allow_unsafe_options: bool = False,
|
|
1417
1460
|
**kwargs: Any,
|
|
1418
1461
|
) -> "IndexFile":
|
|
1419
1462
|
"""Reset the index to reflect the tree at the given commit. This will not adjust
|
|
@@ -1445,6 +1488,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1445
1488
|
The paths need to exist at the commit, otherwise an exception will be
|
|
1446
1489
|
raised.
|
|
1447
1490
|
|
|
1491
|
+
:param allow_unsafe_options:
|
|
1492
|
+
Allow options that may write to arbitrary paths.
|
|
1493
|
+
|
|
1448
1494
|
:param kwargs:
|
|
1449
1495
|
Additional keyword arguments passed to :manpage:`git-reset(1)`.
|
|
1450
1496
|
|
|
@@ -1461,15 +1507,15 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1461
1507
|
"""
|
|
1462
1508
|
# What we actually want to do is to merge the tree into our existing index,
|
|
1463
1509
|
# which is what git-read-tree does.
|
|
1464
|
-
new_inst = type(self).from_tree(self.repo, commit)
|
|
1510
|
+
new_inst = type(self).from_tree(self.repo, commit, allow_unsafe_options=allow_unsafe_options)
|
|
1465
1511
|
if not paths:
|
|
1466
1512
|
self.entries = new_inst.entries
|
|
1467
1513
|
else:
|
|
1468
1514
|
nie = new_inst.entries
|
|
1469
1515
|
for path in paths:
|
|
1470
1516
|
path = self._to_relative_path(path)
|
|
1517
|
+
key = entry_key(path, 0)
|
|
1471
1518
|
try:
|
|
1472
|
-
key = entry_key(path, 0)
|
|
1473
1519
|
self.entries[key] = nie[key]
|
|
1474
1520
|
except KeyError:
|
|
1475
1521
|
# If key is not in theirs, it mustn't be in ours.
|
|
@@ -1521,7 +1567,8 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1521
1567
|
if not allow_unsafe_options:
|
|
1522
1568
|
Git.check_unsafe_options(
|
|
1523
1569
|
options=Git._option_candidates([other], kwargs),
|
|
1524
|
-
unsafe_options=self.repo.
|
|
1570
|
+
unsafe_options=self.repo.unsafe_git_diff_options,
|
|
1571
|
+
clusterable_short_options="46abceflmnpqrstuvwzBCDMNRW",
|
|
1525
1572
|
)
|
|
1526
1573
|
|
|
1527
1574
|
# Only run if we are the default repository index.
|
|
@@ -26,7 +26,7 @@ import sys
|
|
|
26
26
|
from gitdb.base import IStream
|
|
27
27
|
from gitdb.typ import str_tree_type
|
|
28
28
|
|
|
29
|
-
from git.cmd import handle_process_output, safer_popen
|
|
29
|
+
from git.cmd import Git, handle_process_output, safer_popen
|
|
30
30
|
from git.compat import defenc, force_bytes, force_text, safe_decode
|
|
31
31
|
from git.exc import HookExecutionError, UnmergedEntriesError
|
|
32
32
|
from git.objects.fun import (
|
|
@@ -79,6 +79,99 @@ def _has_file_extension(path: str) -> str:
|
|
|
79
79
|
return osp.splitext(path)[1]
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def _is_in_windows_system_root(path: str) -> bool:
|
|
83
|
+
"""Return whether ``path`` is inside the Windows installation directory."""
|
|
84
|
+
system_root = os.environ.get("SystemRoot")
|
|
85
|
+
if not system_root:
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
system_root = osp.normcase(osp.realpath(system_root))
|
|
89
|
+
path = osp.normcase(osp.realpath(path))
|
|
90
|
+
try:
|
|
91
|
+
return osp.commonpath((system_root, path)) == system_root
|
|
92
|
+
except ValueError:
|
|
93
|
+
# Paths on different drives have no common path on Windows.
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _which_from_path(command: str) -> Union[str, None]:
|
|
98
|
+
"""Resolve ``command`` from PATH, excluding the Windows installation."""
|
|
99
|
+
for directory in os.get_exec_path():
|
|
100
|
+
# Unlike POSIX, Windows does not define an empty PATH entry as the current
|
|
101
|
+
# directory. Skip it rather than letting abspath() turn it into one.
|
|
102
|
+
if not directory:
|
|
103
|
+
continue
|
|
104
|
+
directory = osp.abspath(directory)
|
|
105
|
+
candidate = osp.join(directory, command)
|
|
106
|
+
# SystemRoot contains the WSL launcher stubs. They are valid executables but
|
|
107
|
+
# not suitable for running a Windows Git hook: the hook path and environment
|
|
108
|
+
# were prepared for Git for Windows, and WSL may have no distribution at all.
|
|
109
|
+
if _is_in_windows_system_root(candidate):
|
|
110
|
+
continue
|
|
111
|
+
if osp.isfile(candidate) and os.access(candidate, os.X_OK):
|
|
112
|
+
return candidate
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
_GIT_FOR_WINDOWS_PREFIXES = ("mingw64", "mingw32", "clangarm64", "clang64", "clang32", "ucrt64")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _git_for_windows_root() -> Union[str, None]:
|
|
120
|
+
"""Infer a standard Git for Windows root from GitPython's selected executable."""
|
|
121
|
+
git_executable = os.fspath(Git.GIT_PYTHON_GIT_EXECUTABLE or Git.git_exec_name)
|
|
122
|
+
if osp.dirname(git_executable):
|
|
123
|
+
# CreateProcess resolves a relative executable path containing a directory
|
|
124
|
+
# from the parent process cwd, even when Popen supplies a different child cwd.
|
|
125
|
+
git_executable = osp.abspath(git_executable)
|
|
126
|
+
else:
|
|
127
|
+
# GitPython deliberately retains a bare executable name so later PATH changes
|
|
128
|
+
# affect Git commands. Resolve it with the same PATH snapshot used for Bash.
|
|
129
|
+
names = (git_executable,) if _has_file_extension(git_executable) else (git_executable, f"{git_executable}.exe")
|
|
130
|
+
for name in names:
|
|
131
|
+
resolved = _which_from_path(name)
|
|
132
|
+
if resolved is not None:
|
|
133
|
+
git_executable = resolved
|
|
134
|
+
break
|
|
135
|
+
else:
|
|
136
|
+
git_executable = ""
|
|
137
|
+
if not git_executable:
|
|
138
|
+
return None
|
|
139
|
+
if osp.basename(git_executable).lower() not in ("git", "git.exe"):
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
executable_dir = osp.dirname(git_executable)
|
|
143
|
+
directory_name = osp.basename(executable_dir).lower()
|
|
144
|
+
if directory_name == "cmd":
|
|
145
|
+
# The normal system-wide PATH entry is <git-root>/cmd.
|
|
146
|
+
return osp.dirname(executable_dir)
|
|
147
|
+
if directory_name == "bin":
|
|
148
|
+
prefix = osp.dirname(executable_dir)
|
|
149
|
+
if osp.basename(prefix).lower() in _GIT_FOR_WINDOWS_PREFIXES:
|
|
150
|
+
# Git Bash commonly exposes <git-root>/<platform>/bin/git.exe.
|
|
151
|
+
return osp.dirname(prefix)
|
|
152
|
+
if osp.basename(prefix).lower() != "usr":
|
|
153
|
+
# An explicitly configured Git may be the root-level bin/git.exe. Do
|
|
154
|
+
# not make the same inference from usr/bin: unlike the recognized
|
|
155
|
+
# platform prefixes, "usr" has no reliably bounded parent layout.
|
|
156
|
+
return prefix
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _git_for_windows_bash() -> Union[str, None]:
|
|
161
|
+
"""Return Bash from the Git for Windows installation selected by GitPython."""
|
|
162
|
+
git_root = _git_for_windows_root()
|
|
163
|
+
if git_root is None:
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
# Match gix-path's precedence: prefer the lightweight bin shim, then the
|
|
167
|
+
# underlying usr/bin executable. Both belong to the same installation as Git.
|
|
168
|
+
for relative_path in ("bin/bash.exe", "usr/bin/bash.exe"):
|
|
169
|
+
candidate = osp.join(git_root, *relative_path.split("/"))
|
|
170
|
+
if osp.isfile(candidate) and os.access(candidate, os.X_OK):
|
|
171
|
+
return candidate
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
|
|
82
175
|
def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None:
|
|
83
176
|
"""Run the commit hook of the given name. Silently ignore hooks that do not exist.
|
|
84
177
|
|
|
@@ -112,7 +205,12 @@ def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None:
|
|
|
112
205
|
# an absolute path in this form, although a relative path is preferable
|
|
113
206
|
# because it also works with the Windows Subsystem for Linux wrapper.
|
|
114
207
|
bash_hp = hp
|
|
115
|
-
|
|
208
|
+
# Prefer Bash associated with GitPython's selected Git installation. If
|
|
209
|
+
# that layout is not recognized, use an explicitly configured non-system
|
|
210
|
+
# PATH entry. Preserve the bare fallback for installations that previously
|
|
211
|
+
# relied on WSL or another CreateProcess-resolved Bash.
|
|
212
|
+
bash_executable = _git_for_windows_bash() or _which_from_path("bash.exe") or "bash.exe"
|
|
213
|
+
cmd = [bash_executable, Path(bash_hp).as_posix()]
|
|
116
214
|
|
|
117
215
|
process = safer_popen(
|
|
118
216
|
cmd + list(args),
|
|
@@ -575,11 +575,14 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
|
|
|
575
575
|
|
|
576
576
|
if hasattr(proc_or_stream, "wait"):
|
|
577
577
|
proc_or_stream = cast(Popen, proc_or_stream)
|
|
578
|
-
|
|
579
|
-
|
|
578
|
+
stream = proc_or_stream.stdout
|
|
579
|
+
if stream is None:
|
|
580
|
+
raise ValueError("Process has no stdout stream")
|
|
580
581
|
elif hasattr(proc_or_stream, "readline"):
|
|
581
582
|
proc_or_stream = cast(IO, proc_or_stream) # type: ignore[redundant-cast]
|
|
582
583
|
stream = proc_or_stream
|
|
584
|
+
else:
|
|
585
|
+
raise TypeError("Expected a process or stream")
|
|
583
586
|
|
|
584
587
|
readline = stream.readline
|
|
585
588
|
while True:
|