GitPython 3.1.56__py3-none-any.whl → 3.1.58__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 +44 -14
- git/config.py +12 -4
- git/diff.py +4 -1
- git/index/base.py +76 -16
- git/index/fun.py +100 -2
- git/objects/commit.py +5 -2
- git/objects/fun.py +2 -2
- git/objects/submodule/base.py +32 -18
- 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 +13 -0
- git/repo/base.py +43 -9
- git/util.py +83 -7
- {gitpython-3.1.56.dist-info → gitpython-3.1.58.dist-info}/METADATA +5 -6
- {gitpython-3.1.56.dist-info → gitpython-3.1.58.dist-info}/RECORD +22 -22
- {gitpython-3.1.56.dist-info → gitpython-3.1.58.dist-info}/WHEEL +0 -0
- {gitpython-3.1.56.dist-info → gitpython-3.1.58.dist-info}/licenses/AUTHORS +0 -0
- {gitpython-3.1.56.dist-info → gitpython-3.1.58.dist-info}/licenses/LICENSE +0 -0
- {gitpython-3.1.56.dist-info → gitpython-3.1.58.dist-info}/top_level.txt +0 -0
git/__init__.py
CHANGED
git/cmd.py
CHANGED
|
@@ -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
|
|
@@ -1435,6 +1449,10 @@ class Git(metaclass=_GitMeta):
|
|
|
1435
1449
|
if as_process:
|
|
1436
1450
|
return self.AutoInterrupt(proc, command)
|
|
1437
1451
|
|
|
1452
|
+
watchdog: Optional[threading.Timer] = None
|
|
1453
|
+
kill_check: Optional[threading.Event] = None
|
|
1454
|
+
timeout_error: Optional[Callable[[], Union[str, bytes]]] = None
|
|
1455
|
+
|
|
1438
1456
|
if sys.platform != "win32" and kill_after_timeout is not None:
|
|
1439
1457
|
# Help mypy figure out this is not None even when used inside communicate().
|
|
1440
1458
|
timeout = kill_after_timeout
|
|
@@ -1460,6 +1478,7 @@ class Git(metaclass=_GitMeta):
|
|
|
1460
1478
|
except OSError:
|
|
1461
1479
|
pass
|
|
1462
1480
|
# Tell the main routine that the process was killed.
|
|
1481
|
+
assert kill_check is not None
|
|
1463
1482
|
kill_check.set()
|
|
1464
1483
|
except OSError:
|
|
1465
1484
|
# It is possible that the process gets completed in the duration
|
|
@@ -1467,23 +1486,25 @@ class Git(metaclass=_GitMeta):
|
|
|
1467
1486
|
pass
|
|
1468
1487
|
return
|
|
1469
1488
|
|
|
1489
|
+
def make_timeout_error() -> Union[str, bytes]:
|
|
1490
|
+
err = f'Timeout: the command "{" ".join(redacted_command)}" did not complete in {timeout:g} secs.'
|
|
1491
|
+
return err if universal_newlines else err.encode(defenc)
|
|
1492
|
+
|
|
1470
1493
|
def communicate() -> Tuple[AnyStr, AnyStr]:
|
|
1494
|
+
assert watchdog is not None
|
|
1495
|
+
assert kill_check is not None
|
|
1471
1496
|
watchdog.start()
|
|
1472
1497
|
out, err = proc.communicate()
|
|
1473
1498
|
watchdog.cancel()
|
|
1474
1499
|
if kill_check.is_set():
|
|
1475
|
-
err =
|
|
1476
|
-
" ".join(redacted_command),
|
|
1477
|
-
timeout,
|
|
1478
|
-
)
|
|
1479
|
-
if not universal_newlines:
|
|
1480
|
-
err = err.encode(defenc)
|
|
1500
|
+
err = make_timeout_error()
|
|
1481
1501
|
return out, err
|
|
1482
1502
|
|
|
1483
1503
|
# END helpers
|
|
1484
1504
|
|
|
1485
1505
|
kill_check = threading.Event()
|
|
1486
1506
|
watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
|
|
1507
|
+
timeout_error = make_timeout_error
|
|
1487
1508
|
else:
|
|
1488
1509
|
communicate = proc.communicate
|
|
1489
1510
|
|
|
@@ -1504,15 +1525,24 @@ class Git(metaclass=_GitMeta):
|
|
|
1504
1525
|
status = proc.returncode
|
|
1505
1526
|
else:
|
|
1506
1527
|
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
|
|
1507
|
-
if
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1528
|
+
if watchdog is not None:
|
|
1529
|
+
watchdog.start()
|
|
1530
|
+
try:
|
|
1531
|
+
if proc.stdout is not None:
|
|
1532
|
+
stream_copy(proc.stdout, output_stream, max_chunk_size)
|
|
1533
|
+
stdout_value = proc.stdout.read()
|
|
1534
|
+
if proc.stderr is not None:
|
|
1535
|
+
stderr_value = proc.stderr.read()
|
|
1536
|
+
status = proc.wait()
|
|
1537
|
+
finally:
|
|
1538
|
+
if watchdog is not None:
|
|
1539
|
+
watchdog.cancel()
|
|
1512
1540
|
# Strip trailing "\n".
|
|
1513
1541
|
if stderr_value is not None and stderr_value.endswith(newline): # type: ignore[arg-type]
|
|
1514
1542
|
stderr_value = stderr_value[:-1]
|
|
1515
|
-
|
|
1543
|
+
if kill_check is not None and kill_check.is_set():
|
|
1544
|
+
assert timeout_error is not None
|
|
1545
|
+
stderr_value = timeout_error()
|
|
1516
1546
|
# END stdout handling
|
|
1517
1547
|
finally:
|
|
1518
1548
|
if proc.stdout is not None:
|
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:
|
|
@@ -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
|
git/diff.py
CHANGED
|
@@ -284,7 +284,10 @@ class Diffable:
|
|
|
284
284
|
# END paths handling
|
|
285
285
|
|
|
286
286
|
kwargs["as_process"] = True
|
|
287
|
-
|
|
287
|
+
args = self._process_diff_args(args)
|
|
288
|
+
if create_patch:
|
|
289
|
+
self.repo.git(c="diff.mnemonicPrefix=false")
|
|
290
|
+
proc = diff_cmd(*args, **kwargs)
|
|
288
291
|
|
|
289
292
|
diff_method = Diff._index_from_patch_format if create_patch else Diff._index_from_raw_format
|
|
290
293
|
index = diff_method(self.repo, proc)
|
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,
|
|
@@ -130,6 +133,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
130
133
|
index directly before operating on it using the git command.
|
|
131
134
|
"""
|
|
132
135
|
|
|
136
|
+
unsafe_git_checkout_index_options = ["--prefix"]
|
|
137
|
+
unsafe_git_read_tree_options = ["--index-output"]
|
|
138
|
+
|
|
133
139
|
__slots__ = ("repo", "version", "entries", "_extension_data", "_file_path")
|
|
134
140
|
|
|
135
141
|
_VERSION = 2
|
|
@@ -254,7 +260,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
254
260
|
|
|
255
261
|
@post_clear_cache
|
|
256
262
|
@default_index
|
|
257
|
-
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":
|
|
258
269
|
"""Merge the given `rhs` treeish into the current index, possibly taking
|
|
259
270
|
a common base treeish into account.
|
|
260
271
|
|
|
@@ -268,6 +279,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
268
279
|
Optional treeish reference pointing to the common base of `rhs` and this
|
|
269
280
|
index which equals lhs.
|
|
270
281
|
|
|
282
|
+
:param allow_unsafe_options:
|
|
283
|
+
Allow options that may write to arbitrary paths.
|
|
284
|
+
|
|
271
285
|
:return:
|
|
272
286
|
self (containing the merge and possibly unmerged entries in case of
|
|
273
287
|
conflicts)
|
|
@@ -278,6 +292,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
278
292
|
yourself, you have to commit the changed index (or make a valid tree from
|
|
279
293
|
it) and retry with a three-way :meth:`index.from_tree <from_tree>` call.
|
|
280
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
|
+
|
|
281
301
|
# -i : ignore working tree status
|
|
282
302
|
# --aggressive : handle more merge cases
|
|
283
303
|
# -m : do an actual merge
|
|
@@ -322,7 +342,13 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
322
342
|
return inst
|
|
323
343
|
|
|
324
344
|
@classmethod
|
|
325
|
-
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":
|
|
326
352
|
R"""Merge the given treeish revisions into a new index which is returned.
|
|
327
353
|
The original index will remain unaltered.
|
|
328
354
|
|
|
@@ -346,6 +372,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
346
372
|
:param kwargs:
|
|
347
373
|
Additional arguments passed to :manpage:`git-read-tree(1)`.
|
|
348
374
|
|
|
375
|
+
:param allow_unsafe_options:
|
|
376
|
+
Allow options that may write to arbitrary paths.
|
|
377
|
+
|
|
349
378
|
:return:
|
|
350
379
|
New :class:`IndexFile` instance. It will point to a temporary index location
|
|
351
380
|
which does not exist anymore. If you intend to write such a merged Index,
|
|
@@ -363,6 +392,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
363
392
|
if len(treeish) == 0 or len(treeish) > 3:
|
|
364
393
|
raise ValueError("Please specify between 1 and 3 treeish, got %i" % len(treeish))
|
|
365
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
|
+
|
|
366
401
|
arg_list: List[Union[Treeish, str]] = []
|
|
367
402
|
# Ignore that the working tree and index possibly are out of date.
|
|
368
403
|
if len(treeish) > 1:
|
|
@@ -653,16 +688,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
653
688
|
|
|
654
689
|
:raise ValueError:
|
|
655
690
|
"""
|
|
656
|
-
if not osp.isabs(path):
|
|
657
|
-
return path
|
|
658
691
|
if self.repo.bare:
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
result += os.sep
|
|
665
|
-
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)
|
|
666
697
|
|
|
667
698
|
def _preprocess_add_items(
|
|
668
699
|
self, items: Union[PathLike, Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]]
|
|
@@ -729,14 +760,17 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
729
760
|
) -> List[BaseIndexEntry]:
|
|
730
761
|
entries_added: List[BaseIndexEntry] = []
|
|
731
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)
|
|
732
767
|
for path in paths:
|
|
733
768
|
if osp.isabs(path):
|
|
734
769
|
abspath = path
|
|
735
|
-
gitrelative_path = path[len(
|
|
770
|
+
gitrelative_path = path[len(working_tree_dir) + 1 :]
|
|
736
771
|
else:
|
|
737
772
|
gitrelative_path = path
|
|
738
|
-
|
|
739
|
-
abspath = osp.join(self.repo.working_tree_dir, gitrelative_path)
|
|
773
|
+
abspath = osp.join(working_tree_dir, gitrelative_path)
|
|
740
774
|
# END obtain relative and absolute paths
|
|
741
775
|
|
|
742
776
|
blob = Blob(
|
|
@@ -990,6 +1024,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
990
1024
|
self,
|
|
991
1025
|
items: Union[PathLike, Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]],
|
|
992
1026
|
working_tree: bool = False,
|
|
1027
|
+
allow_unsafe_options: bool = False,
|
|
993
1028
|
**kwargs: Any,
|
|
994
1029
|
) -> List[str]:
|
|
995
1030
|
R"""Remove the given items from the index and optionally from the working tree
|
|
@@ -1020,6 +1055,10 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1020
1055
|
physically removing the respective file. This may fail if there are
|
|
1021
1056
|
uncommitted changes in it.
|
|
1022
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
|
+
|
|
1023
1062
|
:param kwargs:
|
|
1024
1063
|
Additional keyword arguments to be passed to :manpage:`git-rm(1)`, such as
|
|
1025
1064
|
``r`` to allow recursive removal.
|
|
@@ -1031,6 +1070,11 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1031
1070
|
This is interesting to know in case you have provided a directory or globs.
|
|
1032
1071
|
Paths are relative to the repository.
|
|
1033
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
|
+
)
|
|
1034
1078
|
args = []
|
|
1035
1079
|
if not working_tree:
|
|
1036
1080
|
args.append("--cached")
|
|
@@ -1212,6 +1256,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1212
1256
|
paths: Union[None, Iterable[PathLike]] = None,
|
|
1213
1257
|
force: bool = False,
|
|
1214
1258
|
fprogress: Callable = lambda *args: None,
|
|
1259
|
+
allow_unsafe_options: bool = False,
|
|
1215
1260
|
**kwargs: Any,
|
|
1216
1261
|
) -> Union[None, Iterator[PathLike], Sequence[PathLike]]:
|
|
1217
1262
|
"""Check out the given paths or all files from the version known to the index
|
|
@@ -1238,6 +1283,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1238
1283
|
no explicit paths are given. Otherwise progress information will be send
|
|
1239
1284
|
prior and after a file has been checked out.
|
|
1240
1285
|
|
|
1286
|
+
:param allow_unsafe_options:
|
|
1287
|
+
Allow unsafe options, such as ``--prefix``.
|
|
1288
|
+
|
|
1241
1289
|
:param kwargs:
|
|
1242
1290
|
Additional arguments to be passed to :manpage:`git-checkout-index(1)`.
|
|
1243
1291
|
|
|
@@ -1261,6 +1309,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1261
1309
|
i.e. if you want :manpage:`git-checkout(1)`-like behaviour, use
|
|
1262
1310
|
``head.checkout`` instead of ``index.checkout``.
|
|
1263
1311
|
"""
|
|
1312
|
+
if not allow_unsafe_options:
|
|
1313
|
+
Git.check_unsafe_options(
|
|
1314
|
+
options=Git._option_candidates([], kwargs),
|
|
1315
|
+
unsafe_options=self.unsafe_git_checkout_index_options,
|
|
1316
|
+
)
|
|
1317
|
+
|
|
1264
1318
|
args = ["--index"]
|
|
1265
1319
|
if force:
|
|
1266
1320
|
args.append("--force")
|
|
@@ -1402,6 +1456,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1402
1456
|
working_tree: bool = False,
|
|
1403
1457
|
paths: Union[None, Iterable[PathLike]] = None,
|
|
1404
1458
|
head: bool = False,
|
|
1459
|
+
allow_unsafe_options: bool = False,
|
|
1405
1460
|
**kwargs: Any,
|
|
1406
1461
|
) -> "IndexFile":
|
|
1407
1462
|
"""Reset the index to reflect the tree at the given commit. This will not adjust
|
|
@@ -1433,6 +1488,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1433
1488
|
The paths need to exist at the commit, otherwise an exception will be
|
|
1434
1489
|
raised.
|
|
1435
1490
|
|
|
1491
|
+
:param allow_unsafe_options:
|
|
1492
|
+
Allow options that may write to arbitrary paths.
|
|
1493
|
+
|
|
1436
1494
|
:param kwargs:
|
|
1437
1495
|
Additional keyword arguments passed to :manpage:`git-reset(1)`.
|
|
1438
1496
|
|
|
@@ -1449,15 +1507,15 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1449
1507
|
"""
|
|
1450
1508
|
# What we actually want to do is to merge the tree into our existing index,
|
|
1451
1509
|
# which is what git-read-tree does.
|
|
1452
|
-
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)
|
|
1453
1511
|
if not paths:
|
|
1454
1512
|
self.entries = new_inst.entries
|
|
1455
1513
|
else:
|
|
1456
1514
|
nie = new_inst.entries
|
|
1457
1515
|
for path in paths:
|
|
1458
1516
|
path = self._to_relative_path(path)
|
|
1517
|
+
key = entry_key(path, 0)
|
|
1459
1518
|
try:
|
|
1460
|
-
key = entry_key(path, 0)
|
|
1461
1519
|
self.entries[key] = nie[key]
|
|
1462
1520
|
except KeyError:
|
|
1463
1521
|
# If key is not in theirs, it mustn't be in ours.
|
|
@@ -1547,6 +1605,8 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1547
1605
|
args.extend(paths)
|
|
1548
1606
|
|
|
1549
1607
|
kwargs["as_process"] = True
|
|
1608
|
+
if create_patch:
|
|
1609
|
+
self.repo.git(c="diff.mnemonicPrefix=false")
|
|
1550
1610
|
proc = self.repo.git.diff(*args, **kwargs)
|
|
1551
1611
|
|
|
1552
1612
|
diff_method = (
|
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/fun.py
CHANGED
|
@@ -115,11 +115,11 @@ def tree_entries_from_data(data: bytes) -> List[EntryTup]:
|
|
|
115
115
|
# Default encoding for strings in git is UTF-8.
|
|
116
116
|
# Only use the respective unicode object if the byte stream was encoded.
|
|
117
117
|
name_bytes = data[ns:i]
|
|
118
|
-
name = safe_decode(name_bytes)
|
|
118
|
+
name = safe_decode(bytes(name_bytes))
|
|
119
119
|
|
|
120
120
|
# Byte is NULL, get next 20.
|
|
121
121
|
i += 1
|
|
122
|
-
sha = data[i : i + 20]
|
|
122
|
+
sha = bytes(data[i : i + 20])
|
|
123
123
|
i = i + 20
|
|
124
124
|
out.append((sha, mode, name))
|
|
125
125
|
# END for each byte in data stream
|
git/objects/submodule/base.py
CHANGED
|
@@ -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)
|
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
|
@@ -16,6 +16,7 @@ from .reference import Reference
|
|
|
16
16
|
|
|
17
17
|
from typing import Any, TYPE_CHECKING, Type, Union
|
|
18
18
|
|
|
19
|
+
from git.cmd import Git
|
|
19
20
|
from git.types import AnyGitObject, PathLike
|
|
20
21
|
|
|
21
22
|
if TYPE_CHECKING:
|
|
@@ -42,6 +43,8 @@ class TagReference(Reference):
|
|
|
42
43
|
|
|
43
44
|
__slots__ = ()
|
|
44
45
|
|
|
46
|
+
unsafe_git_tag_options = ["--file", "-F"]
|
|
47
|
+
|
|
45
48
|
_common_default = "tags"
|
|
46
49
|
_common_path_default = Reference._common_path_default + "/" + _common_default
|
|
47
50
|
|
|
@@ -92,6 +95,7 @@ class TagReference(Reference):
|
|
|
92
95
|
reference: Union[str, "SymbolicReference"] = "HEAD",
|
|
93
96
|
logmsg: Union[str, None] = None,
|
|
94
97
|
force: bool = False,
|
|
98
|
+
allow_unsafe_options: bool = False,
|
|
95
99
|
**kwargs: Any,
|
|
96
100
|
) -> "TagReference":
|
|
97
101
|
"""Create a new tag reference.
|
|
@@ -121,12 +125,21 @@ class TagReference(Reference):
|
|
|
121
125
|
:param force:
|
|
122
126
|
If ``True``, force creation of a tag even though that tag already exists.
|
|
123
127
|
|
|
128
|
+
:param allow_unsafe_options:
|
|
129
|
+
Allow unsafe options, such as ``--file``.
|
|
130
|
+
|
|
124
131
|
:param kwargs:
|
|
125
132
|
Additional keyword arguments to be passed to :manpage:`git-tag(1)`.
|
|
126
133
|
|
|
127
134
|
:return:
|
|
128
135
|
A new :class:`TagReference`.
|
|
129
136
|
"""
|
|
137
|
+
if not allow_unsafe_options:
|
|
138
|
+
Git.check_unsafe_options(
|
|
139
|
+
options=Git._option_candidates([], kwargs),
|
|
140
|
+
unsafe_options=cls.unsafe_git_tag_options,
|
|
141
|
+
)
|
|
142
|
+
|
|
130
143
|
if "ref" in kwargs and kwargs["ref"]:
|
|
131
144
|
reference = kwargs["ref"]
|
|
132
145
|
|
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,8 +159,10 @@ class Repo:
|
|
|
151
159
|
"-c",
|
|
152
160
|
# Can install hooks that execute during clone:
|
|
153
161
|
"--template",
|
|
162
|
+
# Fetches from an additional caller-controlled URI:
|
|
163
|
+
"--bundle-uri",
|
|
154
164
|
]
|
|
155
|
-
"""Options to :manpage:`git-clone(1)` that
|
|
165
|
+
"""Options to :manpage:`git-clone(1)` that permit unsafe command execution or I/O.
|
|
156
166
|
|
|
157
167
|
The ``--upload-pack``/``-u`` option allows users to execute arbitrary commands
|
|
158
168
|
directly:
|
|
@@ -164,6 +174,11 @@ class Repo:
|
|
|
164
174
|
|
|
165
175
|
The ``--template`` option can install hooks that execute during clone:
|
|
166
176
|
https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---templatetemplate-directory
|
|
177
|
+
|
|
178
|
+
The ``--bundle-uri`` option fetches from an additional URI before fetching from the
|
|
179
|
+
clone URL. An untrusted value can therefore make Git access local files or
|
|
180
|
+
unintended network resources:
|
|
181
|
+
https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---bundle-uriuri
|
|
167
182
|
"""
|
|
168
183
|
|
|
169
184
|
unsafe_git_archive_options = [
|
|
@@ -172,6 +187,10 @@ class Repo:
|
|
|
172
187
|
# Writes output to a caller-controlled filesystem path.
|
|
173
188
|
"--output",
|
|
174
189
|
"-o",
|
|
190
|
+
# Reads from a caller-controlled filesystem path:
|
|
191
|
+
"--add-file",
|
|
192
|
+
# Injects a caller-controlled path and contents:
|
|
193
|
+
"--add-virtual-file",
|
|
175
194
|
]
|
|
176
195
|
|
|
177
196
|
unsafe_git_revision_options = [
|
|
@@ -922,13 +941,17 @@ class Repo:
|
|
|
922
941
|
return False
|
|
923
942
|
|
|
924
943
|
def _get_daemon_export(self) -> bool:
|
|
925
|
-
|
|
926
|
-
|
|
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)
|
|
927
948
|
return osp.exists(filename)
|
|
928
949
|
|
|
929
950
|
def _set_daemon_export(self, value: object) -> None:
|
|
930
|
-
|
|
931
|
-
|
|
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)
|
|
932
955
|
fileexists = osp.exists(filename)
|
|
933
956
|
if value and not fileexists:
|
|
934
957
|
touch(filename)
|
|
@@ -950,8 +973,7 @@ class Repo:
|
|
|
950
973
|
:return:
|
|
951
974
|
List of strings being pathnames of alternates
|
|
952
975
|
"""
|
|
953
|
-
|
|
954
|
-
alternates_path = osp.join(self.git_dir, "objects", "info", "alternates")
|
|
976
|
+
alternates_path = osp.join(self.common_dir, "objects", "info", "alternates")
|
|
955
977
|
|
|
956
978
|
if osp.exists(alternates_path):
|
|
957
979
|
with open(alternates_path, "rb") as f:
|
|
@@ -1269,6 +1291,7 @@ class Repo:
|
|
|
1269
1291
|
|
|
1270
1292
|
keepends = True
|
|
1271
1293
|
for line_bytes in data.splitlines(keepends):
|
|
1294
|
+
line_str = ""
|
|
1272
1295
|
try:
|
|
1273
1296
|
line_str = line_bytes.rstrip().decode(defenc)
|
|
1274
1297
|
except UnicodeDecodeError:
|
|
@@ -1383,6 +1406,7 @@ class Repo:
|
|
|
1383
1406
|
mkdir: bool = True,
|
|
1384
1407
|
odbt: Type[GitCmdObjectDB] = GitCmdObjectDB,
|
|
1385
1408
|
expand_vars: bool = True,
|
|
1409
|
+
allow_unsafe_options: bool = False,
|
|
1386
1410
|
**kwargs: Any,
|
|
1387
1411
|
) -> "Repo":
|
|
1388
1412
|
"""Initialize a git repository at the given path if specified.
|
|
@@ -1407,6 +1431,10 @@ class Repo:
|
|
|
1407
1431
|
information disclosure, allowing attackers to access the contents of
|
|
1408
1432
|
environment variables.
|
|
1409
1433
|
|
|
1434
|
+
:param allow_unsafe_options:
|
|
1435
|
+
Allow unsafe options to be used, such as ``--template`` and
|
|
1436
|
+
``--separate-git-dir``.
|
|
1437
|
+
|
|
1410
1438
|
:param kwargs:
|
|
1411
1439
|
Keyword arguments serving as additional options to the
|
|
1412
1440
|
:manpage:`git-init(1)` command.
|
|
@@ -1414,6 +1442,11 @@ class Repo:
|
|
|
1414
1442
|
:return:
|
|
1415
1443
|
:class:`Repo` (the newly created repo)
|
|
1416
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
|
+
)
|
|
1417
1450
|
if path:
|
|
1418
1451
|
path = expand_path(path, expand_vars)
|
|
1419
1452
|
if mkdir and path and not osp.exists(path):
|
|
@@ -1727,8 +1760,9 @@ class Repo:
|
|
|
1727
1760
|
|
|
1728
1761
|
``None`` if we are not currently rebasing.
|
|
1729
1762
|
"""
|
|
1730
|
-
if self.git_dir:
|
|
1731
|
-
|
|
1763
|
+
if not self.git_dir:
|
|
1764
|
+
return None
|
|
1765
|
+
rebase_head_file = osp.join(self.git_dir, "REBASE_HEAD")
|
|
1732
1766
|
if not osp.isfile(rebase_head_file):
|
|
1733
1767
|
return None
|
|
1734
1768
|
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
|
|
|
@@ -540,8 +602,7 @@ def expand_path(p: Union[None, PathLike], expand_vars: bool = True) -> Optional[
|
|
|
540
602
|
|
|
541
603
|
|
|
542
604
|
def remove_password_if_present(cmdline: Sequence[str]) -> List[str]:
|
|
543
|
-
"""
|
|
544
|
-
username and/or password, replace them by stars (in-place).
|
|
605
|
+
"""Redact credentials in URLs and HTTP Authorization extra headers in a command line.
|
|
545
606
|
|
|
546
607
|
If nothing is found, this just returns the command line as-is.
|
|
547
608
|
|
|
@@ -551,6 +612,16 @@ def remove_password_if_present(cmdline: Sequence[str]) -> List[str]:
|
|
|
551
612
|
new_cmdline = []
|
|
552
613
|
for index, to_parse in enumerate(cmdline):
|
|
553
614
|
new_cmdline.append(to_parse)
|
|
615
|
+
config_key, separator, header = to_parse.partition("=")
|
|
616
|
+
header_name, colon, _ = header.partition(":")
|
|
617
|
+
if (
|
|
618
|
+
separator
|
|
619
|
+
and colon
|
|
620
|
+
and config_key.lower().endswith(".extraheader")
|
|
621
|
+
and header_name.strip().lower() == "authorization"
|
|
622
|
+
):
|
|
623
|
+
new_cmdline[index] = "%s%s%s%s *****" % (config_key, separator, header_name, colon)
|
|
624
|
+
continue
|
|
554
625
|
try:
|
|
555
626
|
url = urlsplit(to_parse)
|
|
556
627
|
# Remove password from the URL if present.
|
|
@@ -611,13 +682,17 @@ class RemoteProgress:
|
|
|
611
682
|
self.error_lines: List[str] = []
|
|
612
683
|
self.other_lines: List[str] = []
|
|
613
684
|
|
|
614
|
-
def _parse_progress_line(self, line: AnyStr) ->
|
|
685
|
+
def _parse_progress_line(self, line: AnyStr) -> Optional[object]:
|
|
615
686
|
"""Parse progress information from the given line as retrieved by
|
|
616
687
|
:manpage:`git-push(1)` or :manpage:`git-fetch(1)`.
|
|
617
688
|
|
|
618
689
|
- Lines that do not contain progress info are stored in :attr:`other_lines`.
|
|
619
690
|
- Lines that seem to contain an error (i.e. start with ``error:`` or ``fatal:``)
|
|
620
691
|
are stored in :attr:`error_lines`.
|
|
692
|
+
|
|
693
|
+
The base implementation returns ``None``. Subclasses may return another
|
|
694
|
+
value for compatibility with existing overrides, but callers should treat
|
|
695
|
+
the return value as unspecified.
|
|
621
696
|
"""
|
|
622
697
|
# handle
|
|
623
698
|
# Counting objects: 4, done.
|
|
@@ -632,7 +707,7 @@ class RemoteProgress:
|
|
|
632
707
|
|
|
633
708
|
if self._cur_line.startswith(("error:", "fatal:")):
|
|
634
709
|
self.error_lines.append(self._cur_line)
|
|
635
|
-
return
|
|
710
|
+
return None
|
|
636
711
|
|
|
637
712
|
cur_count, max_count = None, None
|
|
638
713
|
match = self.re_op_relative.match(line_str)
|
|
@@ -642,7 +717,7 @@ class RemoteProgress:
|
|
|
642
717
|
if not match:
|
|
643
718
|
self.line_dropped(line_str)
|
|
644
719
|
self.other_lines.append(line_str)
|
|
645
|
-
return
|
|
720
|
+
return None
|
|
646
721
|
# END could not get match
|
|
647
722
|
|
|
648
723
|
op_code = 0
|
|
@@ -673,7 +748,7 @@ class RemoteProgress:
|
|
|
673
748
|
self.line_dropped(line_str)
|
|
674
749
|
# Note: Don't add this line to the other lines, as we have to silently
|
|
675
750
|
# drop it.
|
|
676
|
-
return
|
|
751
|
+
return None
|
|
677
752
|
# END handle op code
|
|
678
753
|
|
|
679
754
|
# Figure out stage.
|
|
@@ -699,6 +774,7 @@ class RemoteProgress:
|
|
|
699
774
|
max_count and float(max_count),
|
|
700
775
|
message,
|
|
701
776
|
)
|
|
777
|
+
return None
|
|
702
778
|
|
|
703
779
|
def new_message_handler(self) -> Callable[[str], None]:
|
|
704
780
|
"""
|
|
@@ -708,7 +784,7 @@ class RemoteProgress:
|
|
|
708
784
|
"""
|
|
709
785
|
|
|
710
786
|
def handler(line: AnyStr) -> None:
|
|
711
|
-
|
|
787
|
+
self._parse_progress_line(line.rstrip())
|
|
712
788
|
|
|
713
789
|
# END handler
|
|
714
790
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GitPython
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.58
|
|
4
4
|
Summary: GitPython is a Python library used to interact with Git repositories
|
|
5
5
|
Home-page: https://github.com/gitpython-developers/GitPython
|
|
6
6
|
Author: Sebastian Thiel, Michael Trier
|
|
@@ -33,6 +33,7 @@ Requires-Dist: gitdb<5,>=4.0.1
|
|
|
33
33
|
Requires-Dist: typing-extensions>=3.10.0.2; python_version < "3.10"
|
|
34
34
|
Provides-Extra: test
|
|
35
35
|
Requires-Dist: coverage[toml]; extra == "test"
|
|
36
|
+
Requires-Dist: basedpyright==1.39.9; (python_version >= "3.9" and sys_platform != "cygwin") and extra == "test"
|
|
36
37
|
Requires-Dist: ddt!=1.4.3,>=1.1.1; extra == "test"
|
|
37
38
|
Requires-Dist: mock; python_version < "3.8" and extra == "test"
|
|
38
39
|
Requires-Dist: mypy==1.18.2; python_version >= "3.9" and extra == "test"
|
|
@@ -163,15 +164,13 @@ In the less common case that you do not want to install test dependencies, `pip
|
|
|
163
164
|
|
|
164
165
|
#### With editable *dependencies* (not preferred, and rarely needed)
|
|
165
166
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
If you want to do that *and* you want the versions in GitPython's git submodules to be used, then pass `-e git/ext/gitdb` and/or `-e git/ext/gitdb/gitdb/ext/smmap` to `pip install`. This can be done in any order, and in separate `pip install` commands or the same one, so long as `-e` appears before *each* path. For example, you can install GitPython, gitdb, and smmap editably in the currently active virtual environment this way:
|
|
167
|
+
GitPython, [gitdb](gitdb), and [smmap](smmap) live in this repository as independently packaged projects. To work on all three at once, install each one editably in the same virtual environment:
|
|
169
168
|
|
|
170
169
|
```sh
|
|
171
|
-
pip install -e
|
|
170
|
+
pip install -e smmap -e gitdb -e ".[test]"
|
|
172
171
|
```
|
|
173
172
|
|
|
174
|
-
|
|
173
|
+
You can use `pip list` to check which packages are installed editably and which are installed normally.
|
|
175
174
|
|
|
176
175
|
To reiterate, this approach should only rarely be used. For most development it is preferable to allow the gitdb and smmap dependencices to be retrieved automatically from PyPI in their latest stable packaged versions.
|
|
177
176
|
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
git/__init__.py,sha256=
|
|
2
|
-
git/cmd.py,sha256=
|
|
1
|
+
git/__init__.py,sha256=LzalIqZf4OxqrTAKsm4RVdP-VtO02P4bfFImzWRshdM,8899
|
|
2
|
+
git/cmd.py,sha256=leCmxJSwz5Y_ddQmgIN1Cm8fEPxpPTzE0C5wIVkcqVE,77023
|
|
3
3
|
git/compat.py,sha256=y1E6y6O2q5r8clSlr8ZNmuIWG9nmHuehQEsVsmBffs8,4526
|
|
4
|
-
git/config.py,sha256=
|
|
4
|
+
git/config.py,sha256=ZYg-s7KEcHXJbOYL839HOUqPhHsCbGoj6e2khvYiW8U,38988
|
|
5
5
|
git/db.py,sha256=vIW9uWSbqu99zbuU2ZDmOhVOv1UPTmxrnqiCtRHCfjE,2368
|
|
6
|
-
git/diff.py,sha256=
|
|
6
|
+
git/diff.py,sha256=k7iYB8gBzqrGDNn_XfZPXpS6LFxq02-JnyLo4bdwvVA,28271
|
|
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=jFITcQoUhQacJhDiurNaG2izyseH9vaNciRoVy4acQE,64707
|
|
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=
|
|
21
|
-
git/objects/fun.py,sha256=
|
|
20
|
+
git/objects/commit.py,sha256=YbF2cjl6aziTq2HRgZkxr5TgfU8keIVEEkoBB0DPLKc,33291
|
|
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=jf8PYfhgVthsle_7qICs_YSqWyU3jhPGShNxuJtMTqs,66547
|
|
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=27fZPVVkAPvaRoUt6_IX-tXKLleCYwoKA0GAk453w9E,5418
|
|
36
36
|
git/repo/__init__.py,sha256=CILSVH36fX_WxVFSjD9o1WF5LgsNedPiJvSngKZqfVU,210
|
|
37
|
-
git/repo/base.py,sha256=
|
|
37
|
+
git/repo/base.py,sha256=kEnMAyQA0tyPd_W8BT2Sj5ssjvMtcbrhETMTLgmBe5o,65436
|
|
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.58.dist-info/licenses/AUTHORS,sha256=Iucx_CH3w7_4084yjM6C5BvKfLA8cVfYHWW2pVmpxSk,2410
|
|
40
|
+
gitpython-3.1.58.dist-info/licenses/LICENSE,sha256=hvyUwyGpr7wRUUcTURuv3tIl8lEA3MD3NQ6CvCMbi-s,1503
|
|
41
|
+
gitpython-3.1.58.dist-info/METADATA,sha256=lEyUxCGa5z5oNHvXTNT-6ooCbqoXjaL4DrYdNPAnO9s,13558
|
|
42
|
+
gitpython-3.1.58.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
43
|
+
gitpython-3.1.58.dist-info/top_level.txt,sha256=0hzDuIp8obv624V3GmbqsagBWkk8ohtGU-Bc1PmTT0o,4
|
|
44
|
+
gitpython-3.1.58.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|