GitPython 3.1.57__tar.gz → 3.1.58__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.58/GitPython.egg-info}/PKG-INFO +1 -1
- {gitpython-3.1.57/GitPython.egg-info → gitpython-3.1.58}/PKG-INFO +1 -1
- gitpython-3.1.58/VERSION +1 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/doc/source/changes.rst +20 -2
- {gitpython-3.1.57 → gitpython-3.1.58}/git/__init__.py +1 -1
- {gitpython-3.1.57 → gitpython-3.1.58}/git/cmd.py +16 -2
- {gitpython-3.1.57 → gitpython-3.1.58}/git/config.py +12 -4
- {gitpython-3.1.57 → gitpython-3.1.58}/git/index/base.py +62 -16
- {gitpython-3.1.57 → gitpython-3.1.58}/git/index/fun.py +100 -2
- {gitpython-3.1.57 → gitpython-3.1.58}/git/objects/commit.py +5 -2
- {gitpython-3.1.57 → gitpython-3.1.58}/git/objects/submodule/base.py +32 -18
- {gitpython-3.1.57 → gitpython-3.1.58}/git/objects/submodule/root.py +3 -2
- {gitpython-3.1.57 → gitpython-3.1.58}/git/refs/head.py +26 -1
- {gitpython-3.1.57 → gitpython-3.1.58}/git/refs/log.py +1 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/refs/symbolic.py +1 -1
- {gitpython-3.1.57 → gitpython-3.1.58}/git/repo/base.py +31 -8
- {gitpython-3.1.57 → gitpython-3.1.58}/git/util.py +62 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/pyproject.toml +2 -2
- gitpython-3.1.57/VERSION +0 -1
- {gitpython-3.1.57 → gitpython-3.1.58}/AUTHORS +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/CHANGES +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/CONTRIBUTING.md +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/GitPython.egg-info/SOURCES.txt +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/GitPython.egg-info/dependency_links.txt +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/GitPython.egg-info/not-zip-safe +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/GitPython.egg-info/requires.txt +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/GitPython.egg-info/top_level.txt +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/LICENSE +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/MANIFEST.in +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/README.md +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/doc/.gitignore +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/doc/Makefile +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/doc/requirements.txt +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/doc/source/conf.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/doc/source/index.rst +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/doc/source/intro.rst +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/doc/source/quickstart.rst +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/doc/source/reference.rst +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/doc/source/roadmap.rst +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/doc/source/tutorial.rst +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/compat.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/db.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/diff.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/exc.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/index/__init__.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/index/typ.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/index/util.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/objects/__init__.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/objects/base.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/objects/blob.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/objects/fun.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/objects/submodule/__init__.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/objects/submodule/util.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/objects/tag.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/objects/tree.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/objects/util.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/py.typed +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/refs/__init__.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/refs/reference.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/refs/remote.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/refs/tag.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/remote.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/repo/__init__.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/repo/fun.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/git/types.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/requirements.txt +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/setup.cfg +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/setup.py +0 -0
- {gitpython-3.1.57 → gitpython-3.1.58}/test-requirements.txt +0 -0
gitpython-3.1.58/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.1.58
|
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
3.1.58
|
|
6
|
+
======
|
|
7
|
+
|
|
8
|
+
Security fixes for
|
|
9
|
+
|
|
10
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-hh9p-6wh2-4mfc
|
|
11
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-9rj7-rf2p-w77r
|
|
12
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-4gmw-gg2m-w46p
|
|
13
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-wvpp-8hx9-p66j
|
|
14
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-jm78-9fvv-mhgr
|
|
15
|
+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-hmq2-w58f-27jc
|
|
16
|
+
|
|
17
|
+
If you can, also try and provide feedback on the upcoming v4 branch
|
|
18
|
+
https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome.
|
|
19
|
+
|
|
20
|
+
See the following for all changes.
|
|
21
|
+
https://github.com/gitpython-developers/GitPython/releases/tag/3.1.58
|
|
22
|
+
|
|
5
23
|
3.1.57
|
|
6
24
|
======
|
|
7
25
|
|
|
@@ -14,7 +32,7 @@ If you can, also try and provide feedback on the upcoming v4 branch
|
|
|
14
32
|
https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome.
|
|
15
33
|
|
|
16
34
|
See the following for all changes.
|
|
17
|
-
https://github.com/gitpython-developers/GitPython/releases/tag/3.1.
|
|
35
|
+
https://github.com/gitpython-developers/GitPython/releases/tag/3.1.57
|
|
18
36
|
|
|
19
37
|
3.1.56
|
|
20
38
|
======
|
|
@@ -27,7 +45,7 @@ If you can, also try and provide feedback on the upcoming v4 branch
|
|
|
27
45
|
https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome.
|
|
28
46
|
|
|
29
47
|
See the following for all changes.
|
|
30
|
-
https://github.com/gitpython-developers/GitPython/releases/tag/3.1.
|
|
48
|
+
https://github.com/gitpython-developers/GitPython/releases/tag/3.1.56
|
|
31
49
|
|
|
32
50
|
3.1.55
|
|
33
51
|
======
|
|
@@ -654,6 +654,12 @@ class Git(metaclass=_GitMeta):
|
|
|
654
654
|
"--upload-pack",
|
|
655
655
|
]
|
|
656
656
|
|
|
657
|
+
unsafe_git_pathspec_from_file_options = [
|
|
658
|
+
# Reads pathspecs from a caller-controlled file. Some commands include an
|
|
659
|
+
# unmatched pathspec in their error output, which can disclose the file.
|
|
660
|
+
"--pathspec-from-file",
|
|
661
|
+
]
|
|
662
|
+
|
|
657
663
|
def __getstate__(self) -> Dict[str, Any]:
|
|
658
664
|
return slots_to_dict(self, exclude=self._excluded_)
|
|
659
665
|
|
|
@@ -1044,13 +1050,21 @@ class Git(metaclass=_GitMeta):
|
|
|
1044
1050
|
values = value if isinstance(value, (list, tuple)) else (value,)
|
|
1045
1051
|
if any(value is True or (value is not False and value is not None) for value in values):
|
|
1046
1052
|
key = str(key)
|
|
1047
|
-
|
|
1048
|
-
|
|
1053
|
+
if len(key) != 1:
|
|
1054
|
+
options.append(f"--{dashify(key)}")
|
|
1055
|
+
elif split_single_char_options:
|
|
1056
|
+
options.append(f"-{key}")
|
|
1049
1057
|
options.extend(
|
|
1050
1058
|
str(value)
|
|
1051
1059
|
for value in values
|
|
1052
1060
|
if value is not True and value not in (False, None) and str(value).startswith("-")
|
|
1053
1061
|
)
|
|
1062
|
+
else:
|
|
1063
|
+
options.extend(
|
|
1064
|
+
f"-{key}" if value is True else f"-{key}{value}"
|
|
1065
|
+
for value in values
|
|
1066
|
+
if value is True or (value is not False and value is not None)
|
|
1067
|
+
)
|
|
1054
1068
|
return options
|
|
1055
1069
|
|
|
1056
1070
|
AutoInterrupt: TypeAlias = _AutoInterrupt
|
|
@@ -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:
|
|
@@ -897,6 +903,8 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
897
903
|
def _assure_config_name_safe(self, name: "cp._SectionName", label: str) -> None:
|
|
898
904
|
if isinstance(name, str) and UNSAFE_CONFIG_CHARS_RE.search(name):
|
|
899
905
|
raise ValueError("Git config %s names must not contain CR, LF, or NUL" % label)
|
|
906
|
+
if label == "option" and isinstance(name, str) and not VALID_CONFIG_OPTION_NAME_RE.fullmatch(name):
|
|
907
|
+
raise ValueError("Git config option names may contain only letters, digits, '-', '_', or '.'")
|
|
900
908
|
if label == "section" and isinstance(name, str):
|
|
901
909
|
in_quotes = False
|
|
902
910
|
escaped = False
|
|
@@ -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.
|
|
@@ -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:
|
|
@@ -6,6 +6,7 @@ __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
|
|
11
12
|
import stat
|
|
@@ -28,6 +29,7 @@ from git.objects.util import TraversableIterableObj
|
|
|
28
29
|
from git.util import (
|
|
29
30
|
IterableList,
|
|
30
31
|
RemoteProgress,
|
|
32
|
+
_to_relative_path,
|
|
31
33
|
join_path_native,
|
|
32
34
|
rmtree,
|
|
33
35
|
to_native_path_linux,
|
|
@@ -302,8 +304,20 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
302
304
|
parser.set_submodule(self)
|
|
303
305
|
return SectionConstraint(parser, sm_section(self.name))
|
|
304
306
|
|
|
307
|
+
@classmethod
|
|
308
|
+
def _validated_name(cls, name: str) -> str:
|
|
309
|
+
if (
|
|
310
|
+
not name
|
|
311
|
+
or name.startswith(("/", "\\"))
|
|
312
|
+
or ntpath.splitdrive(name)[0]
|
|
313
|
+
or ".." in name.replace("\\", "/").split("/")
|
|
314
|
+
):
|
|
315
|
+
raise ValueError("Invalid submodule name %r" % name)
|
|
316
|
+
return name
|
|
317
|
+
|
|
305
318
|
@classmethod
|
|
306
319
|
def _module_abspath(cls, parent_repo: "Repo", path: PathLike, name: str) -> PathLike:
|
|
320
|
+
name = cls._validated_name(name)
|
|
307
321
|
if cls._need_gitfile_submodules(parent_repo.git):
|
|
308
322
|
return osp.join(parent_repo.git_dir, "modules", name)
|
|
309
323
|
if parent_repo.working_tree_dir:
|
|
@@ -379,23 +393,14 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
379
393
|
:raise ValueError:
|
|
380
394
|
If path is not contained in the parent repository's working tree.
|
|
381
395
|
"""
|
|
382
|
-
|
|
396
|
+
if parent_repo.working_tree_dir:
|
|
397
|
+
path = _to_relative_path(parent_repo.working_tree_dir, path)
|
|
398
|
+
else:
|
|
399
|
+
path = to_native_path_linux(path)
|
|
383
400
|
if path.endswith("/"):
|
|
384
401
|
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
|
|
402
|
+
if not path or path == ".":
|
|
403
|
+
raise ValueError("Submodule checkout path must not be the repository root")
|
|
399
404
|
|
|
400
405
|
return path
|
|
401
406
|
|
|
@@ -523,6 +528,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
523
528
|
raise InvalidGitRepositoryError("Cannot add submodules to bare repositories")
|
|
524
529
|
# END handle bare repos
|
|
525
530
|
|
|
531
|
+
cls._validated_name(name)
|
|
526
532
|
path = cls._to_relative_path(repo, path)
|
|
527
533
|
|
|
528
534
|
# Ensure we never put backslashes into the URL, as might happen on Windows.
|
|
@@ -734,9 +740,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
734
740
|
prefix = "DRY-RUN: "
|
|
735
741
|
# END handle prefix
|
|
736
742
|
|
|
737
|
-
|
|
738
|
-
if dry_run:
|
|
739
|
-
mrepo = None
|
|
743
|
+
mrepo = None
|
|
740
744
|
# END init mrepo
|
|
741
745
|
|
|
742
746
|
def fetch_remotes(module_repo: "Repo") -> None:
|
|
@@ -771,6 +775,8 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
771
775
|
# END fetch new data
|
|
772
776
|
|
|
773
777
|
try:
|
|
778
|
+
self._validated_name(self.name)
|
|
779
|
+
|
|
774
780
|
# ENSURE REPO IS PRESENT AND UP-TO-DATE
|
|
775
781
|
#######################################
|
|
776
782
|
try:
|
|
@@ -871,6 +877,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
871
877
|
#############################
|
|
872
878
|
binsha = self.binsha
|
|
873
879
|
hexsha = self.hexsha
|
|
880
|
+
is_detached = False
|
|
874
881
|
if mrepo is not None:
|
|
875
882
|
# mrepo is only set if we are not in dry-run mode or if the module
|
|
876
883
|
# existed.
|
|
@@ -1020,6 +1027,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
1020
1027
|
raise ValueError("You must specify to move at least the module or the configuration of the submodule")
|
|
1021
1028
|
# END handle input
|
|
1022
1029
|
|
|
1030
|
+
self._validated_name(self.name)
|
|
1023
1031
|
module_checkout_path = self._to_relative_path(self.repo, module_path)
|
|
1024
1032
|
|
|
1025
1033
|
# VERIFY DESTINATION
|
|
@@ -1160,6 +1168,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
1160
1168
|
raise ValueError("Need to specify to delete at least the module, or the configuration")
|
|
1161
1169
|
# END handle parameters
|
|
1162
1170
|
|
|
1171
|
+
self._validated_name(self.name)
|
|
1163
1172
|
# Recursively remove children of this submodule.
|
|
1164
1173
|
nc = 0
|
|
1165
1174
|
for csm in self.children():
|
|
@@ -1213,6 +1222,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
1213
1222
|
for remote in mod.remotes:
|
|
1214
1223
|
num_branches_with_new_commits = 0
|
|
1215
1224
|
rrefs = remote.refs
|
|
1225
|
+
rref = None
|
|
1216
1226
|
for rref in rrefs:
|
|
1217
1227
|
num_branches_with_new_commits += len(mod.git.cherry(rref)) != 0
|
|
1218
1228
|
# END for each remote ref
|
|
@@ -1416,6 +1426,9 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
1416
1426
|
if self.name == new_name:
|
|
1417
1427
|
return self
|
|
1418
1428
|
|
|
1429
|
+
self._validated_name(self.name)
|
|
1430
|
+
self._validated_name(new_name)
|
|
1431
|
+
|
|
1419
1432
|
# .git/config
|
|
1420
1433
|
with self.repo.config_writer() as pw:
|
|
1421
1434
|
# As we ourselves didn't write anything about submodules into the parent
|
|
@@ -1463,6 +1476,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
1463
1476
|
If a repository was not available.
|
|
1464
1477
|
This could also mean that it was not yet initialized.
|
|
1465
1478
|
"""
|
|
1479
|
+
self._validated_name(self.name)
|
|
1466
1480
|
module_checkout_abspath = self.abspath
|
|
1467
1481
|
try:
|
|
1468
1482
|
repo = git.Repo(module_checkout_abspath)
|
|
@@ -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
|
|
|
@@ -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")
|
|
@@ -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
|
|
|
@@ -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",
|
|
@@ -933,13 +941,17 @@ class Repo:
|
|
|
933
941
|
return False
|
|
934
942
|
|
|
935
943
|
def _get_daemon_export(self) -> bool:
|
|
936
|
-
|
|
937
|
-
|
|
944
|
+
git_dir = getattr(self, "git_dir", None)
|
|
945
|
+
if git_dir is None:
|
|
946
|
+
return False
|
|
947
|
+
filename = osp.join(git_dir, self.DAEMON_EXPORT_FILE)
|
|
938
948
|
return osp.exists(filename)
|
|
939
949
|
|
|
940
950
|
def _set_daemon_export(self, value: object) -> None:
|
|
941
|
-
|
|
942
|
-
|
|
951
|
+
git_dir = getattr(self, "git_dir", None)
|
|
952
|
+
if git_dir is None:
|
|
953
|
+
return
|
|
954
|
+
filename = osp.join(git_dir, self.DAEMON_EXPORT_FILE)
|
|
943
955
|
fileexists = osp.exists(filename)
|
|
944
956
|
if value and not fileexists:
|
|
945
957
|
touch(filename)
|
|
@@ -961,8 +973,7 @@ class Repo:
|
|
|
961
973
|
:return:
|
|
962
974
|
List of strings being pathnames of alternates
|
|
963
975
|
"""
|
|
964
|
-
|
|
965
|
-
alternates_path = osp.join(self.git_dir, "objects", "info", "alternates")
|
|
976
|
+
alternates_path = osp.join(self.common_dir, "objects", "info", "alternates")
|
|
966
977
|
|
|
967
978
|
if osp.exists(alternates_path):
|
|
968
979
|
with open(alternates_path, "rb") as f:
|
|
@@ -1280,6 +1291,7 @@ class Repo:
|
|
|
1280
1291
|
|
|
1281
1292
|
keepends = True
|
|
1282
1293
|
for line_bytes in data.splitlines(keepends):
|
|
1294
|
+
line_str = ""
|
|
1283
1295
|
try:
|
|
1284
1296
|
line_str = line_bytes.rstrip().decode(defenc)
|
|
1285
1297
|
except UnicodeDecodeError:
|
|
@@ -1394,6 +1406,7 @@ class Repo:
|
|
|
1394
1406
|
mkdir: bool = True,
|
|
1395
1407
|
odbt: Type[GitCmdObjectDB] = GitCmdObjectDB,
|
|
1396
1408
|
expand_vars: bool = True,
|
|
1409
|
+
allow_unsafe_options: bool = False,
|
|
1397
1410
|
**kwargs: Any,
|
|
1398
1411
|
) -> "Repo":
|
|
1399
1412
|
"""Initialize a git repository at the given path if specified.
|
|
@@ -1418,6 +1431,10 @@ class Repo:
|
|
|
1418
1431
|
information disclosure, allowing attackers to access the contents of
|
|
1419
1432
|
environment variables.
|
|
1420
1433
|
|
|
1434
|
+
:param allow_unsafe_options:
|
|
1435
|
+
Allow unsafe options to be used, such as ``--template`` and
|
|
1436
|
+
``--separate-git-dir``.
|
|
1437
|
+
|
|
1421
1438
|
:param kwargs:
|
|
1422
1439
|
Keyword arguments serving as additional options to the
|
|
1423
1440
|
:manpage:`git-init(1)` command.
|
|
@@ -1425,6 +1442,11 @@ class Repo:
|
|
|
1425
1442
|
:return:
|
|
1426
1443
|
:class:`Repo` (the newly created repo)
|
|
1427
1444
|
"""
|
|
1445
|
+
if not allow_unsafe_options:
|
|
1446
|
+
Git.check_unsafe_options(
|
|
1447
|
+
options=Git._option_candidates([], kwargs),
|
|
1448
|
+
unsafe_options=cls.unsafe_git_init_options,
|
|
1449
|
+
)
|
|
1428
1450
|
if path:
|
|
1429
1451
|
path = expand_path(path, expand_vars)
|
|
1430
1452
|
if mkdir and path and not osp.exists(path):
|
|
@@ -1738,8 +1760,9 @@ class Repo:
|
|
|
1738
1760
|
|
|
1739
1761
|
``None`` if we are not currently rebasing.
|
|
1740
1762
|
"""
|
|
1741
|
-
if self.git_dir:
|
|
1742
|
-
|
|
1763
|
+
if not self.git_dir:
|
|
1764
|
+
return None
|
|
1765
|
+
rebase_head_file = osp.join(self.git_dir, "REBASE_HEAD")
|
|
1743
1766
|
if not osp.isfile(rebase_head_file):
|
|
1744
1767
|
return None
|
|
1745
1768
|
with open(rebase_head_file, "rt") as f:
|
|
@@ -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
|
|
|
@@ -3,7 +3,7 @@ requires = ["setuptools"]
|
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[tool.pytest.ini_options]
|
|
6
|
-
addopts = "--cov=git --cov-report=term -
|
|
6
|
+
addopts = "--cov=git --cov-report=term -rfEX"
|
|
7
7
|
filterwarnings = "ignore::DeprecationWarning"
|
|
8
8
|
python_files = "test_*.py"
|
|
9
9
|
tmp_path_retention_policy = "failed"
|
|
@@ -13,7 +13,7 @@ testpaths = "test" # Space separated list of paths from root e.g test tests doc
|
|
|
13
13
|
# --cov-report term-missing # to terminal with line numbers
|
|
14
14
|
# --cov-report html:path # html file at path
|
|
15
15
|
# --maxfail # number of errors before giving up
|
|
16
|
-
# -
|
|
16
|
+
# -rfEX # test summary: list failures, errors, and unexpected passes; omit expected failures
|
|
17
17
|
# -ra # test summary: list all non-passing (fail, error, skip, xfail, xpass)
|
|
18
18
|
# --ignore-glob=**/gitdb/* # ignore glob paths
|
|
19
19
|
# filterwarnings ignore::WarningType # ignores those warnings
|
gitpython-3.1.57/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
3.1.57
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|