GitPython 3.1.57__py3-none-any.whl → 3.1.59__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- git/__init__.py +1 -1
- git/cmd.py +23 -6
- git/config.py +17 -5
- git/db.py +24 -1
- git/diff.py +4 -3
- git/index/base.py +64 -17
- git/index/fun.py +100 -2
- git/objects/commit.py +5 -2
- git/objects/submodule/base.py +43 -19
- git/objects/submodule/root.py +3 -2
- git/refs/head.py +26 -1
- git/refs/log.py +1 -0
- git/refs/symbolic.py +1 -1
- git/refs/tag.py +6 -4
- git/repo/base.py +53 -12
- git/util.py +62 -0
- {gitpython-3.1.57.dist-info → gitpython-3.1.59.dist-info}/METADATA +1 -1
- {gitpython-3.1.57.dist-info → gitpython-3.1.59.dist-info}/RECORD +22 -22
- {gitpython-3.1.57.dist-info → gitpython-3.1.59.dist-info}/WHEEL +1 -1
- {gitpython-3.1.57.dist-info → gitpython-3.1.59.dist-info}/licenses/AUTHORS +0 -0
- {gitpython-3.1.57.dist-info → gitpython-3.1.59.dist-info}/licenses/LICENSE +0 -0
- {gitpython-3.1.57.dist-info → gitpython-3.1.59.dist-info}/top_level.txt +0 -0
git/__init__.py
CHANGED
git/cmd.py
CHANGED
|
@@ -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)
|
git/config.py
CHANGED
|
@@ -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
|
git/db.py
CHANGED
|
@@ -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:
|
git/diff.py
CHANGED
|
@@ -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]] = []
|
git/index/base.py
CHANGED
|
@@ -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.
|
git/index/fun.py
CHANGED
|
@@ -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),
|
git/objects/commit.py
CHANGED
|
@@ -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:
|
git/objects/submodule/base.py
CHANGED
|
@@ -6,8 +6,10 @@ __all__ = ["Submodule", "UpdateProgress"]
|
|
|
6
6
|
import gc
|
|
7
7
|
from io import BytesIO
|
|
8
8
|
import logging
|
|
9
|
+
import ntpath
|
|
9
10
|
import os
|
|
10
11
|
import os.path as osp
|
|
12
|
+
import shlex
|
|
11
13
|
import stat
|
|
12
14
|
import sys
|
|
13
15
|
import uuid
|
|
@@ -28,6 +30,7 @@ from git.objects.util import TraversableIterableObj
|
|
|
28
30
|
from git.util import (
|
|
29
31
|
IterableList,
|
|
30
32
|
RemoteProgress,
|
|
33
|
+
_to_relative_path,
|
|
31
34
|
join_path_native,
|
|
32
35
|
rmtree,
|
|
33
36
|
to_native_path_linux,
|
|
@@ -268,7 +271,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
268
271
|
raise ValueError("Cannot write blobs of 'historical' submodule configurations")
|
|
269
272
|
# END handle writes of historical submodules
|
|
270
273
|
|
|
271
|
-
return SubmoduleConfigParser(fp_module, read_only=read_only)
|
|
274
|
+
return SubmoduleConfigParser(fp_module, read_only=read_only, merge_includes=False)
|
|
272
275
|
|
|
273
276
|
def _clear_cache(self) -> None:
|
|
274
277
|
"""Clear the possibly changed values."""
|
|
@@ -302,8 +305,20 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
302
305
|
parser.set_submodule(self)
|
|
303
306
|
return SectionConstraint(parser, sm_section(self.name))
|
|
304
307
|
|
|
308
|
+
@classmethod
|
|
309
|
+
def _validated_name(cls, name: str) -> str:
|
|
310
|
+
if (
|
|
311
|
+
not name
|
|
312
|
+
or name.startswith(("/", "\\"))
|
|
313
|
+
or ntpath.splitdrive(name)[0]
|
|
314
|
+
or ".." in name.replace("\\", "/").split("/")
|
|
315
|
+
):
|
|
316
|
+
raise ValueError("Invalid submodule name %r" % name)
|
|
317
|
+
return name
|
|
318
|
+
|
|
305
319
|
@classmethod
|
|
306
320
|
def _module_abspath(cls, parent_repo: "Repo", path: PathLike, name: str) -> PathLike:
|
|
321
|
+
name = cls._validated_name(name)
|
|
307
322
|
if cls._need_gitfile_submodules(parent_repo.git):
|
|
308
323
|
return osp.join(parent_repo.git_dir, "modules", name)
|
|
309
324
|
if parent_repo.working_tree_dir:
|
|
@@ -349,6 +364,15 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
349
364
|
module_abspath = cls._module_abspath(repo, path, name)
|
|
350
365
|
module_checkout_path = module_abspath
|
|
351
366
|
if cls._need_gitfile_submodules(repo.git):
|
|
367
|
+
if not allow_unsafe_options:
|
|
368
|
+
Git.check_unsafe_options(Git._option_candidates([], kwargs), repo.unsafe_git_clone_options)
|
|
369
|
+
multi_options = kwargs.get("multi_options")
|
|
370
|
+
if multi_options:
|
|
371
|
+
Git.check_unsafe_options(
|
|
372
|
+
shlex.split(" ".join(cast("Sequence[str]", multi_options))),
|
|
373
|
+
repo.unsafe_git_clone_options,
|
|
374
|
+
)
|
|
375
|
+
allow_unsafe_options = True
|
|
352
376
|
kwargs["separate_git_dir"] = module_abspath
|
|
353
377
|
module_abspath_dir = osp.dirname(module_abspath)
|
|
354
378
|
if not osp.isdir(module_abspath_dir):
|
|
@@ -379,23 +403,14 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
379
403
|
:raise ValueError:
|
|
380
404
|
If path is not contained in the parent repository's working tree.
|
|
381
405
|
"""
|
|
382
|
-
|
|
406
|
+
if parent_repo.working_tree_dir:
|
|
407
|
+
path = _to_relative_path(parent_repo.working_tree_dir, path)
|
|
408
|
+
else:
|
|
409
|
+
path = to_native_path_linux(path)
|
|
383
410
|
if path.endswith("/"):
|
|
384
411
|
path = path[:-1]
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if osp.isabs(path) and parent_repo.working_tree_dir:
|
|
388
|
-
working_tree_linux = to_native_path_linux(parent_repo.working_tree_dir)
|
|
389
|
-
if not path.startswith(working_tree_linux):
|
|
390
|
-
raise ValueError(
|
|
391
|
-
"Submodule checkout path '%s' needs to be within the parents repository at '%s'"
|
|
392
|
-
% (working_tree_linux, path)
|
|
393
|
-
)
|
|
394
|
-
path = path[len(working_tree_linux.rstrip("/")) + 1 :]
|
|
395
|
-
if not path:
|
|
396
|
-
raise ValueError("Absolute submodule path '%s' didn't yield a valid relative path" % path)
|
|
397
|
-
# END verify converted relative path makes sense
|
|
398
|
-
# END convert to a relative path
|
|
412
|
+
if not path or path == ".":
|
|
413
|
+
raise ValueError("Submodule checkout path must not be the repository root")
|
|
399
414
|
|
|
400
415
|
return path
|
|
401
416
|
|
|
@@ -523,6 +538,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
523
538
|
raise InvalidGitRepositoryError("Cannot add submodules to bare repositories")
|
|
524
539
|
# END handle bare repos
|
|
525
540
|
|
|
541
|
+
cls._validated_name(name)
|
|
526
542
|
path = cls._to_relative_path(repo, path)
|
|
527
543
|
|
|
528
544
|
# Ensure we never put backslashes into the URL, as might happen on Windows.
|
|
@@ -734,9 +750,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
734
750
|
prefix = "DRY-RUN: "
|
|
735
751
|
# END handle prefix
|
|
736
752
|
|
|
737
|
-
|
|
738
|
-
if dry_run:
|
|
739
|
-
mrepo = None
|
|
753
|
+
mrepo = None
|
|
740
754
|
# END init mrepo
|
|
741
755
|
|
|
742
756
|
def fetch_remotes(module_repo: "Repo") -> None:
|
|
@@ -771,6 +785,8 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
771
785
|
# END fetch new data
|
|
772
786
|
|
|
773
787
|
try:
|
|
788
|
+
self._validated_name(self.name)
|
|
789
|
+
|
|
774
790
|
# ENSURE REPO IS PRESENT AND UP-TO-DATE
|
|
775
791
|
#######################################
|
|
776
792
|
try:
|
|
@@ -871,6 +887,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
871
887
|
#############################
|
|
872
888
|
binsha = self.binsha
|
|
873
889
|
hexsha = self.hexsha
|
|
890
|
+
is_detached = False
|
|
874
891
|
if mrepo is not None:
|
|
875
892
|
# mrepo is only set if we are not in dry-run mode or if the module
|
|
876
893
|
# existed.
|
|
@@ -1020,6 +1037,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
1020
1037
|
raise ValueError("You must specify to move at least the module or the configuration of the submodule")
|
|
1021
1038
|
# END handle input
|
|
1022
1039
|
|
|
1040
|
+
self._validated_name(self.name)
|
|
1023
1041
|
module_checkout_path = self._to_relative_path(self.repo, module_path)
|
|
1024
1042
|
|
|
1025
1043
|
# VERIFY DESTINATION
|
|
@@ -1160,6 +1178,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
1160
1178
|
raise ValueError("Need to specify to delete at least the module, or the configuration")
|
|
1161
1179
|
# END handle parameters
|
|
1162
1180
|
|
|
1181
|
+
self._validated_name(self.name)
|
|
1163
1182
|
# Recursively remove children of this submodule.
|
|
1164
1183
|
nc = 0
|
|
1165
1184
|
for csm in self.children():
|
|
@@ -1213,6 +1232,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
1213
1232
|
for remote in mod.remotes:
|
|
1214
1233
|
num_branches_with_new_commits = 0
|
|
1215
1234
|
rrefs = remote.refs
|
|
1235
|
+
rref = None
|
|
1216
1236
|
for rref in rrefs:
|
|
1217
1237
|
num_branches_with_new_commits += len(mod.git.cherry(rref)) != 0
|
|
1218
1238
|
# END for each remote ref
|
|
@@ -1416,6 +1436,9 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
1416
1436
|
if self.name == new_name:
|
|
1417
1437
|
return self
|
|
1418
1438
|
|
|
1439
|
+
self._validated_name(self.name)
|
|
1440
|
+
self._validated_name(new_name)
|
|
1441
|
+
|
|
1419
1442
|
# .git/config
|
|
1420
1443
|
with self.repo.config_writer() as pw:
|
|
1421
1444
|
# As we ourselves didn't write anything about submodules into the parent
|
|
@@ -1463,6 +1486,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
1463
1486
|
If a repository was not available.
|
|
1464
1487
|
This could also mean that it was not yet initialized.
|
|
1465
1488
|
"""
|
|
1489
|
+
self._validated_name(self.name)
|
|
1466
1490
|
module_checkout_abspath = self.abspath
|
|
1467
1491
|
try:
|
|
1468
1492
|
repo = git.Repo(module_checkout_abspath)
|
git/objects/submodule/root.py
CHANGED
|
@@ -7,6 +7,7 @@ import logging
|
|
|
7
7
|
|
|
8
8
|
import git
|
|
9
9
|
from git.exc import InvalidGitRepositoryError
|
|
10
|
+
from git.util import IterableList
|
|
10
11
|
|
|
11
12
|
from .base import Submodule, UpdateProgress
|
|
12
13
|
from .util import find_first_remote_branch
|
|
@@ -19,7 +20,6 @@ from git.types import Commit_ish
|
|
|
19
20
|
|
|
20
21
|
if TYPE_CHECKING:
|
|
21
22
|
from git.repo import Repo
|
|
22
|
-
from git.util import IterableList
|
|
23
23
|
|
|
24
24
|
# ----------------------------------------------------------------------------
|
|
25
25
|
|
|
@@ -162,6 +162,7 @@ class RootModule(Submodule):
|
|
|
162
162
|
prefix = "DRY-RUN: "
|
|
163
163
|
|
|
164
164
|
repo = self.repo
|
|
165
|
+
sms: "IterableList[Submodule]" = IterableList("name")
|
|
165
166
|
|
|
166
167
|
try:
|
|
167
168
|
# SETUP BASE COMMIT
|
|
@@ -182,7 +183,7 @@ class RootModule(Submodule):
|
|
|
182
183
|
# END handle previous commit
|
|
183
184
|
|
|
184
185
|
psms: "IterableList[Submodule]" = self.list_items(repo, parent_commit=previous_commit)
|
|
185
|
-
sms
|
|
186
|
+
sms = self.list_items(repo)
|
|
186
187
|
spsms = set(psms)
|
|
187
188
|
ssms = set(sms)
|
|
188
189
|
|
git/refs/head.py
CHANGED
|
@@ -19,6 +19,7 @@ from .symbolic import SymbolicReference
|
|
|
19
19
|
|
|
20
20
|
from typing import Any, Sequence, TYPE_CHECKING, Union
|
|
21
21
|
|
|
22
|
+
from git.cmd import Git
|
|
22
23
|
from git.types import Commit_ish, PathLike
|
|
23
24
|
|
|
24
25
|
if TYPE_CHECKING:
|
|
@@ -62,6 +63,7 @@ class HEAD(SymbolicReference):
|
|
|
62
63
|
index: bool = True,
|
|
63
64
|
working_tree: bool = False,
|
|
64
65
|
paths: Union[PathLike, Sequence[PathLike], None] = None,
|
|
66
|
+
allow_unsafe_options: bool = False,
|
|
65
67
|
**kwargs: Any,
|
|
66
68
|
) -> "HEAD":
|
|
67
69
|
"""Reset our HEAD to the given commit optionally synchronizing the index and
|
|
@@ -84,12 +86,21 @@ class HEAD(SymbolicReference):
|
|
|
84
86
|
Single path or list of paths relative to the git root directory
|
|
85
87
|
that are to be reset. This allows to partially reset individual files.
|
|
86
88
|
|
|
89
|
+
:param allow_unsafe_options:
|
|
90
|
+
Allow unsafe options such as ``--pathspec-from-file`` to be passed to
|
|
91
|
+
:manpage:`git-reset(1)`.
|
|
92
|
+
|
|
87
93
|
:param kwargs:
|
|
88
94
|
Additional arguments passed to :manpage:`git-reset(1)`.
|
|
89
95
|
|
|
90
96
|
:return:
|
|
91
97
|
self
|
|
92
98
|
"""
|
|
99
|
+
if not allow_unsafe_options:
|
|
100
|
+
Git.check_unsafe_options(
|
|
101
|
+
options=Git._option_candidates([commit], kwargs),
|
|
102
|
+
unsafe_options=Git.unsafe_git_pathspec_from_file_options,
|
|
103
|
+
)
|
|
93
104
|
mode: Union[str, None]
|
|
94
105
|
mode = "--soft"
|
|
95
106
|
if index:
|
|
@@ -234,7 +245,12 @@ class Head(Reference):
|
|
|
234
245
|
self.path = "%s/%s" % (self._common_path_default, new_path)
|
|
235
246
|
return self
|
|
236
247
|
|
|
237
|
-
def checkout(
|
|
248
|
+
def checkout(
|
|
249
|
+
self,
|
|
250
|
+
force: bool = False,
|
|
251
|
+
allow_unsafe_options: bool = False,
|
|
252
|
+
**kwargs: Any,
|
|
253
|
+
) -> Union["HEAD", "Head"]:
|
|
238
254
|
"""Check out this head by setting the HEAD to this reference, by updating the
|
|
239
255
|
index to reflect the tree we point to and by updating the working tree to
|
|
240
256
|
reflect the latest index.
|
|
@@ -246,6 +262,10 @@ class Head(Reference):
|
|
|
246
262
|
If ``False``, :exc:`~git.exc.GitCommandError` will be raised in that
|
|
247
263
|
situation.
|
|
248
264
|
|
|
265
|
+
:param allow_unsafe_options:
|
|
266
|
+
Allow unsafe options such as ``--pathspec-from-file`` to be passed to
|
|
267
|
+
:manpage:`git-checkout(1)`.
|
|
268
|
+
|
|
249
269
|
:param kwargs:
|
|
250
270
|
Additional keyword arguments to be passed to git checkout, e.g.
|
|
251
271
|
``b="new_branch"`` to create a new branch at the given spot.
|
|
@@ -261,6 +281,11 @@ class Head(Reference):
|
|
|
261
281
|
the HEAD detached which is allowed and possible, but remains a special state
|
|
262
282
|
that some tools might not be able to handle.
|
|
263
283
|
"""
|
|
284
|
+
if not allow_unsafe_options:
|
|
285
|
+
Git.check_unsafe_options(
|
|
286
|
+
options=Git._option_candidates([], kwargs),
|
|
287
|
+
unsafe_options=Git.unsafe_git_pathspec_from_file_options,
|
|
288
|
+
)
|
|
264
289
|
kwargs["f"] = force
|
|
265
290
|
if kwargs["f"] is False:
|
|
266
291
|
kwargs.pop("f")
|
git/refs/log.py
CHANGED
git/refs/symbolic.py
CHANGED
|
@@ -119,7 +119,7 @@ class SymbolicReference:
|
|
|
119
119
|
common_path = os.path.commonpath([base_path, abs_path])
|
|
120
120
|
except ValueError as e:
|
|
121
121
|
raise ValueError("Reference path %r escapes the repository" % path) from e
|
|
122
|
-
if
|
|
122
|
+
if common_path != base_path:
|
|
123
123
|
raise ValueError("Reference path %r escapes the repository" % path)
|
|
124
124
|
return abs_path
|
|
125
125
|
|
git/refs/tag.py
CHANGED
|
@@ -134,15 +134,17 @@ class TagReference(Reference):
|
|
|
134
134
|
:return:
|
|
135
135
|
A new :class:`TagReference`.
|
|
136
136
|
"""
|
|
137
|
+
legacy_ref = kwargs.pop("ref", None)
|
|
138
|
+
if legacy_ref:
|
|
139
|
+
reference = legacy_ref
|
|
140
|
+
|
|
137
141
|
if not allow_unsafe_options:
|
|
138
142
|
Git.check_unsafe_options(
|
|
139
|
-
options=Git._option_candidates([], kwargs),
|
|
143
|
+
options=Git._option_candidates([path, reference], kwargs),
|
|
140
144
|
unsafe_options=cls.unsafe_git_tag_options,
|
|
145
|
+
clusterable_short_options="46adefilnqsv",
|
|
141
146
|
)
|
|
142
147
|
|
|
143
|
-
if "ref" in kwargs and kwargs["ref"]:
|
|
144
|
-
reference = kwargs["ref"]
|
|
145
|
-
|
|
146
148
|
if "message" in kwargs and kwargs["message"]:
|
|
147
149
|
kwargs["m"] = kwargs["message"]
|
|
148
150
|
del kwargs["message"]
|
git/repo/base.py
CHANGED
|
@@ -142,6 +142,14 @@ class Repo:
|
|
|
142
142
|
re_author_committer_start = re.compile(r"^(author|committer)")
|
|
143
143
|
re_tab_full_line = re.compile(r"^\t(.*)$")
|
|
144
144
|
|
|
145
|
+
unsafe_git_init_options = [
|
|
146
|
+
# Can install hooks that execute during later Git commands:
|
|
147
|
+
"--template",
|
|
148
|
+
# Redirects the repository metadata to a caller-controlled path:
|
|
149
|
+
"--separate-git-dir",
|
|
150
|
+
]
|
|
151
|
+
"""Options to :manpage:`git-init(1)` that permit unsafe code execution or I/O."""
|
|
152
|
+
|
|
145
153
|
unsafe_git_clone_options = [
|
|
146
154
|
# Executes arbitrary commands:
|
|
147
155
|
"--upload-pack",
|
|
@@ -151,6 +159,8 @@ class Repo:
|
|
|
151
159
|
"-c",
|
|
152
160
|
# Can install hooks that execute during clone:
|
|
153
161
|
"--template",
|
|
162
|
+
# Redirects the repository metadata to a caller-controlled path:
|
|
163
|
+
"--separate-git-dir",
|
|
154
164
|
# Fetches from an additional caller-controlled URI:
|
|
155
165
|
"--bundle-uri",
|
|
156
166
|
]
|
|
@@ -191,6 +201,19 @@ class Repo:
|
|
|
191
201
|
"-o",
|
|
192
202
|
]
|
|
193
203
|
|
|
204
|
+
unsafe_git_blame_options = unsafe_git_revision_options + [
|
|
205
|
+
# These options read from arbitrary files and expose their contents through blame output.
|
|
206
|
+
"--contents",
|
|
207
|
+
"-S",
|
|
208
|
+
"--ignore-revs-file",
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
unsafe_git_diff_options = unsafe_git_revision_options + [
|
|
212
|
+
# Reads caller-controlled order patterns from an arbitrary file.
|
|
213
|
+
"-O",
|
|
214
|
+
"--orderfile",
|
|
215
|
+
]
|
|
216
|
+
|
|
194
217
|
# Invariants
|
|
195
218
|
config_level: ConfigLevels_Tup = ("system", "user", "global", "repository")
|
|
196
219
|
"""Represents the configuration level of a configuration file."""
|
|
@@ -933,13 +956,17 @@ class Repo:
|
|
|
933
956
|
return False
|
|
934
957
|
|
|
935
958
|
def _get_daemon_export(self) -> bool:
|
|
936
|
-
|
|
937
|
-
|
|
959
|
+
git_dir = getattr(self, "git_dir", None)
|
|
960
|
+
if git_dir is None:
|
|
961
|
+
return False
|
|
962
|
+
filename = osp.join(git_dir, self.DAEMON_EXPORT_FILE)
|
|
938
963
|
return osp.exists(filename)
|
|
939
964
|
|
|
940
965
|
def _set_daemon_export(self, value: object) -> None:
|
|
941
|
-
|
|
942
|
-
|
|
966
|
+
git_dir = getattr(self, "git_dir", None)
|
|
967
|
+
if git_dir is None:
|
|
968
|
+
return
|
|
969
|
+
filename = osp.join(git_dir, self.DAEMON_EXPORT_FILE)
|
|
943
970
|
fileexists = osp.exists(filename)
|
|
944
971
|
if value and not fileexists:
|
|
945
972
|
touch(filename)
|
|
@@ -961,8 +988,7 @@ class Repo:
|
|
|
961
988
|
:return:
|
|
962
989
|
List of strings being pathnames of alternates
|
|
963
990
|
"""
|
|
964
|
-
|
|
965
|
-
alternates_path = osp.join(self.git_dir, "objects", "info", "alternates")
|
|
991
|
+
alternates_path = osp.join(self.common_dir, "objects", "info", "alternates")
|
|
966
992
|
|
|
967
993
|
if osp.exists(alternates_path):
|
|
968
994
|
with open(alternates_path, "rb") as f:
|
|
@@ -1138,7 +1164,7 @@ class Repo:
|
|
|
1138
1164
|
:manpage:`git-rev-parse(1)` is a valid option.
|
|
1139
1165
|
|
|
1140
1166
|
:param allow_unsafe_options:
|
|
1141
|
-
Allow unsafe options in revision argument, like ``--output``.
|
|
1167
|
+
Allow unsafe options in revision argument, like ``--output`` or ``--contents``.
|
|
1142
1168
|
|
|
1143
1169
|
:return:
|
|
1144
1170
|
Lazy iterator of :class:`BlameEntry` tuples, where the commit indicates the
|
|
@@ -1150,7 +1176,9 @@ class Repo:
|
|
|
1150
1176
|
"""
|
|
1151
1177
|
if not allow_unsafe_options:
|
|
1152
1178
|
Git.check_unsafe_options(
|
|
1153
|
-
options=Git._option_candidates([rev], kwargs),
|
|
1179
|
+
options=Git._option_candidates([rev], kwargs),
|
|
1180
|
+
unsafe_options=self.unsafe_git_blame_options,
|
|
1181
|
+
clusterable_short_options="46bceflnpqstvw",
|
|
1154
1182
|
)
|
|
1155
1183
|
|
|
1156
1184
|
data: bytes = self.git.blame(rev, "--", file, p=True, incremental=True, stdout_as_string=False, **kwargs)
|
|
@@ -1242,7 +1270,7 @@ class Repo:
|
|
|
1242
1270
|
:manpage:`git-rev-parse(1)` is a valid option.
|
|
1243
1271
|
|
|
1244
1272
|
:param allow_unsafe_options:
|
|
1245
|
-
Allow unsafe options in revision argument, like ``--output``.
|
|
1273
|
+
Allow unsafe options in revision argument, like ``--output`` or ``--contents``.
|
|
1246
1274
|
|
|
1247
1275
|
:return:
|
|
1248
1276
|
list: [git.Commit, list: [<line>]]
|
|
@@ -1258,7 +1286,8 @@ class Repo:
|
|
|
1258
1286
|
if not allow_unsafe_options:
|
|
1259
1287
|
Git.check_unsafe_options(
|
|
1260
1288
|
options=Git._option_candidates([rev, rev_opts_list], kwargs),
|
|
1261
|
-
unsafe_options=self.
|
|
1289
|
+
unsafe_options=self.unsafe_git_blame_options,
|
|
1290
|
+
clusterable_short_options="46bceflnpqstvw",
|
|
1262
1291
|
)
|
|
1263
1292
|
data: bytes = self.git.blame(rev, *rev_opts_list, "--", file, p=True, stdout_as_string=False, **kwargs)
|
|
1264
1293
|
commits: Dict[str, Commit] = {}
|
|
@@ -1280,6 +1309,7 @@ class Repo:
|
|
|
1280
1309
|
|
|
1281
1310
|
keepends = True
|
|
1282
1311
|
for line_bytes in data.splitlines(keepends):
|
|
1312
|
+
line_str = ""
|
|
1283
1313
|
try:
|
|
1284
1314
|
line_str = line_bytes.rstrip().decode(defenc)
|
|
1285
1315
|
except UnicodeDecodeError:
|
|
@@ -1394,6 +1424,7 @@ class Repo:
|
|
|
1394
1424
|
mkdir: bool = True,
|
|
1395
1425
|
odbt: Type[GitCmdObjectDB] = GitCmdObjectDB,
|
|
1396
1426
|
expand_vars: bool = True,
|
|
1427
|
+
allow_unsafe_options: bool = False,
|
|
1397
1428
|
**kwargs: Any,
|
|
1398
1429
|
) -> "Repo":
|
|
1399
1430
|
"""Initialize a git repository at the given path if specified.
|
|
@@ -1418,6 +1449,10 @@ class Repo:
|
|
|
1418
1449
|
information disclosure, allowing attackers to access the contents of
|
|
1419
1450
|
environment variables.
|
|
1420
1451
|
|
|
1452
|
+
:param allow_unsafe_options:
|
|
1453
|
+
Allow unsafe options to be used, such as ``--template`` and
|
|
1454
|
+
``--separate-git-dir``.
|
|
1455
|
+
|
|
1421
1456
|
:param kwargs:
|
|
1422
1457
|
Keyword arguments serving as additional options to the
|
|
1423
1458
|
:manpage:`git-init(1)` command.
|
|
@@ -1425,6 +1460,11 @@ class Repo:
|
|
|
1425
1460
|
:return:
|
|
1426
1461
|
:class:`Repo` (the newly created repo)
|
|
1427
1462
|
"""
|
|
1463
|
+
if not allow_unsafe_options:
|
|
1464
|
+
Git.check_unsafe_options(
|
|
1465
|
+
options=Git._option_candidates([], kwargs),
|
|
1466
|
+
unsafe_options=cls.unsafe_git_init_options,
|
|
1467
|
+
)
|
|
1428
1468
|
if path:
|
|
1429
1469
|
path = expand_path(path, expand_vars)
|
|
1430
1470
|
if mkdir and path and not osp.exists(path):
|
|
@@ -1738,8 +1778,9 @@ class Repo:
|
|
|
1738
1778
|
|
|
1739
1779
|
``None`` if we are not currently rebasing.
|
|
1740
1780
|
"""
|
|
1741
|
-
if self.git_dir:
|
|
1742
|
-
|
|
1781
|
+
if not self.git_dir:
|
|
1782
|
+
return None
|
|
1783
|
+
rebase_head_file = osp.join(self.git_dir, "REBASE_HEAD")
|
|
1743
1784
|
if not osp.isfile(rebase_head_file):
|
|
1744
1785
|
return None
|
|
1745
1786
|
with open(rebase_head_file, "rt") as f:
|
git/util.py
CHANGED
|
@@ -315,6 +315,68 @@ def join_path_native(a: PathLike, *p: PathLike) -> PathLike:
|
|
|
315
315
|
return to_native_path(join_path(a, *p))
|
|
316
316
|
|
|
317
317
|
|
|
318
|
+
def _is_path_rooted(path: PathLike) -> bool:
|
|
319
|
+
r"""Whether ``path`` has a root, including one encoded in a UNC drive.
|
|
320
|
+
|
|
321
|
+
On Windows, ``\directory`` is rooted on the current drive without being
|
|
322
|
+
absolute, while ``C:\directory`` has both a drive and a root. In contrast,
|
|
323
|
+
``directory`` and the drive-relative ``C:directory`` have no root.
|
|
324
|
+
UNC paths are rooted: ``\\server\share`` stores the share in the drive
|
|
325
|
+
returned by :func:`os.path.splitdrive`, while ``\\server\share\directory``
|
|
326
|
+
additionally has a rooted tail.
|
|
327
|
+
On POSIX, which has no drive concept, this simply distinguishes absolute
|
|
328
|
+
paths such as ``/directory`` from relative paths such as ``directory``.
|
|
329
|
+
"""
|
|
330
|
+
drive, tail = osp.splitdrive(os.fspath(path))
|
|
331
|
+
separators = (os.sep,) if os.altsep is None else (os.sep, os.altsep)
|
|
332
|
+
return tail.startswith(separators) or drive.startswith(separators)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _to_relative_path(root: PathLike, path: PathLike) -> str:
|
|
336
|
+
r"""Return a normalized Git-style path confined to ``root``.
|
|
337
|
+
|
|
338
|
+
A Windows path such as ``\directory`` is rooted but not absolute. Resolve it
|
|
339
|
+
against the drive of ``root`` rather than treating it as relative to ``root``.
|
|
340
|
+
Drive-relative paths such as ``C:directory`` are rejected because their meaning
|
|
341
|
+
depends on process-global per-drive state.
|
|
342
|
+
|
|
343
|
+
For example, with ``root`` set to ``C:\repo`` on Windows:
|
|
344
|
+
|
|
345
|
+
* ``directory\file`` -> ``directory/file``
|
|
346
|
+
* ``directory\`` -> ``directory/``
|
|
347
|
+
* ``C:\repo\directory\file`` -> ``directory/file``
|
|
348
|
+
* ``\repo\directory\file`` -> ``directory/file``
|
|
349
|
+
* ``C:directory\file`` -> :exc:`ValueError`
|
|
350
|
+
* ``C:\other\file`` -> :exc:`ValueError`
|
|
351
|
+
|
|
352
|
+
On POSIX, ``/repo/directory/file`` under ``/repo`` similarly becomes
|
|
353
|
+
``directory/file``. A trailing separator is preserved as a Git-style ``/``.
|
|
354
|
+
"""
|
|
355
|
+
path_str = os.fspath(path)
|
|
356
|
+
if not path_str:
|
|
357
|
+
return path_str
|
|
358
|
+
|
|
359
|
+
drive, _tail = osp.splitdrive(path_str)
|
|
360
|
+
rooted = _is_path_rooted(path_str)
|
|
361
|
+
if drive and not rooted:
|
|
362
|
+
raise ValueError("Drive-relative path %r is not supported" % path_str)
|
|
363
|
+
|
|
364
|
+
root_abs = osp.abspath(os.fspath(root))
|
|
365
|
+
path_abs = osp.abspath(osp.join(root_abs, path_str))
|
|
366
|
+
try:
|
|
367
|
+
common_path = osp.commonpath([root_abs, path_abs])
|
|
368
|
+
except ValueError as e:
|
|
369
|
+
raise ValueError("Path %r is not in repository at %r" % (path_str, root_abs)) from e
|
|
370
|
+
if common_path != root_abs:
|
|
371
|
+
raise ValueError("Path %r is not in repository at %r" % (path_str, root_abs))
|
|
372
|
+
|
|
373
|
+
relative_path = to_native_path_linux(osp.relpath(path_abs, root_abs))
|
|
374
|
+
separators = (os.sep,) if os.altsep is None else (os.sep, os.altsep)
|
|
375
|
+
if path_str.endswith(separators) and relative_path != "." and not relative_path.endswith("/"):
|
|
376
|
+
relative_path += "/"
|
|
377
|
+
return relative_path
|
|
378
|
+
|
|
379
|
+
|
|
318
380
|
def assure_directory_exists(path: PathLike, is_file: bool = False) -> bool:
|
|
319
381
|
"""Make sure that the directory pointed to by path exists.
|
|
320
382
|
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
git/__init__.py,sha256=
|
|
2
|
-
git/cmd.py,sha256=
|
|
1
|
+
git/__init__.py,sha256=RQ8_0lssQ6Httk5nXlJwIji5mDSzx_28lTUwZkbXfiA,8899
|
|
2
|
+
git/cmd.py,sha256=y-7TVp8PVWVkXcQrKqrSO2vaD6Bdiue73ZzFlWzLryU,77137
|
|
3
3
|
git/compat.py,sha256=y1E6y6O2q5r8clSlr8ZNmuIWG9nmHuehQEsVsmBffs8,4526
|
|
4
|
-
git/config.py,sha256=
|
|
5
|
-
git/db.py,sha256=
|
|
6
|
-
git/diff.py,sha256=
|
|
4
|
+
git/config.py,sha256=0fc1F5G_S6CksbO2-pTaB5bspNyyPidYhrEhwD9mvCs,39261
|
|
5
|
+
git/db.py,sha256=X19omm9sk3eEy5WAzMoZ2tG9UmdPZFQYf-8yqpvsYF0,3202
|
|
6
|
+
git/diff.py,sha256=QJylerwXENN87gQ30iJ3KbOIoCHFH27QPeKPgUsw5C8,28363
|
|
7
7
|
git/exc.py,sha256=Gc7g1pHpn8OmTse30NHmJVsBJ2CYH8LxaR8y8UA3lIM,7119
|
|
8
8
|
git/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
git/remote.py,sha256=d68K7XmkYXZUlIoLBDRpRom_df6mbkhWMrcPAP5s11c,46900
|
|
10
10
|
git/types.py,sha256=d3oDduUGjfoqffcUL_GOD-3qroF8m3r9ai6Xi9_FraM,10279
|
|
11
|
-
git/util.py,sha256=
|
|
11
|
+
git/util.py,sha256=TJWZ8zeqpsp2NoIr5u_tcUB7yM3OB_RHoYLy4b95TUQ,48135
|
|
12
12
|
git/index/__init__.py,sha256=i-Nqb8Lufp9aFbmxpQBORmmQnjEVVM1Pn58fsQkyGgQ,406
|
|
13
|
-
git/index/base.py,sha256=
|
|
14
|
-
git/index/fun.py,sha256=
|
|
13
|
+
git/index/base.py,sha256=cOeIjjS2LYZwqKdSGvrcgVC37TNZcTNtQzHWbGPyRwk,64775
|
|
14
|
+
git/index/fun.py,sha256=AIW3T6Gr6h5E22z_2wKHevzh5K0HhjjEO9kXAK3nHOM,22419
|
|
15
15
|
git/index/typ.py,sha256=ZjRWp5J79OwFQor1XXN4C6JmHTDclD9H-tKZtAeqyTI,7019
|
|
16
16
|
git/index/util.py,sha256=f0BfE2JPkdypsB2Q_HJ8KU7MC45RRHywL_GWwxa_5hw,3676
|
|
17
17
|
git/objects/__init__.py,sha256=O6ZL_olX7e5-8iIbKviRPkVSJxN37WA-EC0q9d48U5Y,637
|
|
18
18
|
git/objects/base.py,sha256=5-p9uSGWvPxX9hidvkGH9tQOJoH2eaRggUbMr9VJiL0,10285
|
|
19
19
|
git/objects/blob.py,sha256=G9PZMNc5CFRQFNnlquBRAM5-jZg8581lRcxkvARobSk,1231
|
|
20
|
-
git/objects/commit.py,sha256=
|
|
20
|
+
git/objects/commit.py,sha256=YbF2cjl6aziTq2HRgZkxr5TgfU8keIVEEkoBB0DPLKc,33291
|
|
21
21
|
git/objects/fun.py,sha256=bcofjsxrF1NniYWa1r8oAy8uYT_q2rUpLFnYj1aULGU,8949
|
|
22
22
|
git/objects/tag.py,sha256=jAGESnpmTEv-dLakPzheT5ILZFFArcItnXYqfxfDrgc,4441
|
|
23
23
|
git/objects/tree.py,sha256=MmExcS59Iamb2cBqUvoHPdzgixabyvv5voHabossrl8,13898
|
|
24
24
|
git/objects/util.py,sha256=Nlza4zLgdPmr_Yasyvvs6c1rKtW_wMxI6wDmQpQ3ufw,23846
|
|
25
25
|
git/objects/submodule/__init__.py,sha256=6xySp767LVz3UylWgUalntS_nGXRuVzXxDuFAv_Wc2c,303
|
|
26
|
-
git/objects/submodule/base.py,sha256=
|
|
27
|
-
git/objects/submodule/root.py,sha256=
|
|
26
|
+
git/objects/submodule/base.py,sha256=JBoT8FiVCk9ODMMQo2DDR89ThZDmj317Qr2L4JZtB2g,67073
|
|
27
|
+
git/objects/submodule/root.py,sha256=767GIhBwCwn2xvXeMHm2uEO3kZlRyaIaFXnSqcfdZxU,20277
|
|
28
28
|
git/objects/submodule/util.py,sha256=sQqAYaiSJdFkZa9NlAuK_wTsMNiS-kkQnQjvIoJtc_o,3509
|
|
29
29
|
git/refs/__init__.py,sha256=DWlJNnsx-4jM_E-VycbP-FZUdn6iWhjnH_uZ_pZXBro,509
|
|
30
|
-
git/refs/head.py,sha256=
|
|
31
|
-
git/refs/log.py,sha256=
|
|
30
|
+
git/refs/head.py,sha256=Q3VZt5MmEUwltBpBHJmcAZM9gYzCekAKgMpJlPrbttk,11309
|
|
31
|
+
git/refs/log.py,sha256=G2e9GR1owwdmDuKmwGGozt5b1y8qIccUoXiuk2q1E1o,12635
|
|
32
32
|
git/refs/reference.py,sha256=uzrvNTZ5fg7A0Aq4VC3KLSMbksgYUf4KvmHau0tFYIM,5646
|
|
33
33
|
git/refs/remote.py,sha256=WTlkJtpu895jJxbknDNOwL31tivQY_AHQrvroUoFBX4,2902
|
|
34
|
-
git/refs/symbolic.py,sha256=
|
|
35
|
-
git/refs/tag.py,sha256=
|
|
34
|
+
git/refs/symbolic.py,sha256=MPi5UN0ujdE2GMOrzfW2zt-6HlVreuY5W4P7tnBg4V8,36103
|
|
35
|
+
git/refs/tag.py,sha256=Gm1jn9kClc6f6jGQXRrpLyelrM3-KDcqHRRkD0mL4SE,5510
|
|
36
36
|
git/repo/__init__.py,sha256=CILSVH36fX_WxVFSjD9o1WF5LgsNedPiJvSngKZqfVU,210
|
|
37
|
-
git/repo/base.py,sha256=
|
|
37
|
+
git/repo/base.py,sha256=OrH--uFUNqz5XgK-vQou3LxoOdr1wGRtej3kh0zJN8c,66119
|
|
38
38
|
git/repo/fun.py,sha256=PDFNkuTSGGoQc0EEEXjxHw2_KaKYvVjBZ9rrkwH5Q6Y,23462
|
|
39
|
-
gitpython-3.1.
|
|
40
|
-
gitpython-3.1.
|
|
41
|
-
gitpython-3.1.
|
|
42
|
-
gitpython-3.1.
|
|
43
|
-
gitpython-3.1.
|
|
44
|
-
gitpython-3.1.
|
|
39
|
+
gitpython-3.1.59.dist-info/licenses/AUTHORS,sha256=Iucx_CH3w7_4084yjM6C5BvKfLA8cVfYHWW2pVmpxSk,2410
|
|
40
|
+
gitpython-3.1.59.dist-info/licenses/LICENSE,sha256=hvyUwyGpr7wRUUcTURuv3tIl8lEA3MD3NQ6CvCMbi-s,1503
|
|
41
|
+
gitpython-3.1.59.dist-info/METADATA,sha256=x3hW_O_MEvdlUzGbI_Nb4eaXbr00unmxiHwLTIu87ZA,13558
|
|
42
|
+
gitpython-3.1.59.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
43
|
+
gitpython-3.1.59.dist-info/top_level.txt,sha256=0hzDuIp8obv624V3GmbqsagBWkk8ohtGU-Bc1PmTT0o,4
|
|
44
|
+
gitpython-3.1.59.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|