GitPython 3.1.45__py3-none-any.whl → 3.1.47__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 +39 -19
- git/config.py +22 -10
- git/diff.py +3 -2
- git/index/base.py +8 -6
- git/index/fun.py +12 -6
- git/index/typ.py +16 -3
- git/index/util.py +2 -2
- git/objects/blob.py +2 -1
- git/objects/commit.py +47 -9
- git/objects/submodule/base.py +4 -4
- git/objects/tree.py +4 -2
- git/refs/head.py +1 -5
- git/refs/log.py +1 -1
- git/refs/reference.py +2 -1
- git/refs/symbolic.py +16 -15
- git/refs/tag.py +4 -4
- git/repo/base.py +25 -22
- git/repo/fun.py +1 -1
- git/types.py +2 -2
- git/util.py +16 -16
- {gitpython-3.1.45.dist-info → gitpython-3.1.47.dist-info}/METADATA +11 -11
- gitpython-3.1.47.dist-info/RECORD +44 -0
- {gitpython-3.1.45.dist-info → gitpython-3.1.47.dist-info}/WHEEL +1 -1
- {gitpython-3.1.45.dist-info → gitpython-3.1.47.dist-info}/licenses/AUTHORS +1 -0
- gitpython-3.1.45.dist-info/RECORD +0 -44
- {gitpython-3.1.45.dist-info → gitpython-3.1.47.dist-info}/licenses/LICENSE +0 -0
- {gitpython-3.1.45.dist-info → gitpython-3.1.47.dist-info}/top_level.txt +0 -0
git/__init__.py
CHANGED
git/cmd.py
CHANGED
|
@@ -368,8 +368,12 @@ class _AutoInterrupt:
|
|
|
368
368
|
status = proc.wait() # Ensure the process goes away.
|
|
369
369
|
|
|
370
370
|
self.status = self._status_code_if_terminate or status
|
|
371
|
-
except OSError as ex:
|
|
372
|
-
|
|
371
|
+
except (OSError, AttributeError) as ex:
|
|
372
|
+
# On interpreter shutdown (notably on Windows), parts of the stdlib used by
|
|
373
|
+
# subprocess can already be torn down (e.g. `subprocess._winapi` becomes None),
|
|
374
|
+
# which can cause AttributeError during terminate(). In that case, we prefer
|
|
375
|
+
# to silently ignore to avoid noisy "Exception ignored in: __del__" messages.
|
|
376
|
+
_logger.info("Ignored error while terminating process: %r", ex)
|
|
373
377
|
# END exception handling
|
|
374
378
|
|
|
375
379
|
def __del__(self) -> None:
|
|
@@ -940,6 +944,21 @@ class Git(metaclass=_GitMeta):
|
|
|
940
944
|
f"The `{protocol}::` protocol looks suspicious, use `allow_unsafe_protocols=True` to allow it."
|
|
941
945
|
)
|
|
942
946
|
|
|
947
|
+
@classmethod
|
|
948
|
+
def _canonicalize_option_name(cls, option: str) -> str:
|
|
949
|
+
"""Return the option name used for unsafe-option checks.
|
|
950
|
+
|
|
951
|
+
Examples:
|
|
952
|
+
``"--upload-pack=/tmp/helper"`` -> ``"upload-pack"``
|
|
953
|
+
``"upload_pack"`` -> ``"upload-pack"``
|
|
954
|
+
``"--config core.filemode=false"`` -> ``"config"``
|
|
955
|
+
"""
|
|
956
|
+
option_name = option.lstrip("-").split("=", 1)[0]
|
|
957
|
+
option_tokens = option_name.split(None, 1)
|
|
958
|
+
if not option_tokens:
|
|
959
|
+
return ""
|
|
960
|
+
return dashify(option_tokens[0])
|
|
961
|
+
|
|
943
962
|
@classmethod
|
|
944
963
|
def check_unsafe_options(cls, options: List[str], unsafe_options: List[str]) -> None:
|
|
945
964
|
"""Check for unsafe options.
|
|
@@ -947,15 +966,12 @@ class Git(metaclass=_GitMeta):
|
|
|
947
966
|
Some options that are passed to ``git <command>`` can be used to execute
|
|
948
967
|
arbitrary commands. These are blocked by default.
|
|
949
968
|
"""
|
|
950
|
-
# Options can be of the form `foo`, `--foo bar`, or `--foo=bar
|
|
951
|
-
|
|
952
|
-
bare_unsafe_options = [option.lstrip("-") for option in unsafe_options]
|
|
969
|
+
# Options can be of the form `foo`, `--foo`, `--foo bar`, or `--foo=bar`.
|
|
970
|
+
canonical_unsafe_options = {cls._canonicalize_option_name(option): option for option in unsafe_options}
|
|
953
971
|
for option in options:
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it."
|
|
958
|
-
)
|
|
972
|
+
unsafe_option = canonical_unsafe_options.get(cls._canonicalize_option_name(option))
|
|
973
|
+
if unsafe_option is not None:
|
|
974
|
+
raise UnsafeOptionError(f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it.")
|
|
959
975
|
|
|
960
976
|
AutoInterrupt: TypeAlias = _AutoInterrupt
|
|
961
977
|
|
|
@@ -1360,25 +1376,29 @@ class Git(metaclass=_GitMeta):
|
|
|
1360
1376
|
if output_stream is None:
|
|
1361
1377
|
stdout_value, stderr_value = communicate()
|
|
1362
1378
|
# Strip trailing "\n".
|
|
1363
|
-
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
|
|
1379
|
+
if stdout_value is not None and stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
|
|
1364
1380
|
stdout_value = stdout_value[:-1]
|
|
1365
|
-
if stderr_value.endswith(newline): # type: ignore[arg-type]
|
|
1381
|
+
if stderr_value is not None and stderr_value.endswith(newline): # type: ignore[arg-type]
|
|
1366
1382
|
stderr_value = stderr_value[:-1]
|
|
1367
1383
|
|
|
1368
1384
|
status = proc.returncode
|
|
1369
1385
|
else:
|
|
1370
1386
|
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1387
|
+
if proc.stdout is not None:
|
|
1388
|
+
stream_copy(proc.stdout, output_stream, max_chunk_size)
|
|
1389
|
+
stdout_value = proc.stdout.read()
|
|
1390
|
+
if proc.stderr is not None:
|
|
1391
|
+
stderr_value = proc.stderr.read()
|
|
1374
1392
|
# Strip trailing "\n".
|
|
1375
|
-
if stderr_value.endswith(newline): # type: ignore[arg-type]
|
|
1393
|
+
if stderr_value is not None and stderr_value.endswith(newline): # type: ignore[arg-type]
|
|
1376
1394
|
stderr_value = stderr_value[:-1]
|
|
1377
1395
|
status = proc.wait()
|
|
1378
1396
|
# END stdout handling
|
|
1379
1397
|
finally:
|
|
1380
|
-
proc.stdout
|
|
1381
|
-
|
|
1398
|
+
if proc.stdout is not None:
|
|
1399
|
+
proc.stdout.close()
|
|
1400
|
+
if proc.stderr is not None:
|
|
1401
|
+
proc.stderr.close()
|
|
1382
1402
|
|
|
1383
1403
|
if self.GIT_PYTHON_TRACE == "full":
|
|
1384
1404
|
cmdstr = " ".join(redacted_command)
|
|
@@ -1568,7 +1588,7 @@ class Git(metaclass=_GitMeta):
|
|
|
1568
1588
|
|
|
1569
1589
|
turns into::
|
|
1570
1590
|
|
|
1571
|
-
git rev-list max-count
|
|
1591
|
+
git rev-list --max-count=10 --header=master
|
|
1572
1592
|
|
|
1573
1593
|
:return:
|
|
1574
1594
|
Same as :meth:`execute`. If no args are given, used :meth:`execute`'s
|
git/config.py
CHANGED
|
@@ -66,7 +66,7 @@ _logger = logging.getLogger(__name__)
|
|
|
66
66
|
CONFIG_LEVELS: ConfigLevels_Tup = ("system", "user", "global", "repository")
|
|
67
67
|
"""The configuration level of a configuration file."""
|
|
68
68
|
|
|
69
|
-
CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch):(.+)\"")
|
|
69
|
+
CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch|hasconfig:remote\.\*\.url):(.+)\"")
|
|
70
70
|
"""Section pattern to detect conditional includes.
|
|
71
71
|
|
|
72
72
|
See: https://git-scm.com/docs/git-config#_conditional_includes
|
|
@@ -549,11 +549,21 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
549
549
|
:return:
|
|
550
550
|
The list of paths, where each path is a tuple of (option, value).
|
|
551
551
|
"""
|
|
552
|
+
|
|
553
|
+
def _all_items(section: str) -> List[Tuple[str, str]]:
|
|
554
|
+
"""Return all (key, value) pairs for a section, including duplicate keys."""
|
|
555
|
+
return [
|
|
556
|
+
(key, value)
|
|
557
|
+
for key, values in self._sections[section].items_all()
|
|
558
|
+
if key != "__name__"
|
|
559
|
+
for value in values
|
|
560
|
+
]
|
|
561
|
+
|
|
552
562
|
paths = []
|
|
553
563
|
|
|
554
564
|
for section in self.sections():
|
|
555
565
|
if section == "include":
|
|
556
|
-
paths +=
|
|
566
|
+
paths += _all_items(section)
|
|
557
567
|
|
|
558
568
|
match = CONDITIONAL_INCLUDE_REGEXP.search(section)
|
|
559
569
|
if match is None or self._repo is None:
|
|
@@ -574,12 +584,12 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
574
584
|
if keyword.endswith("/i"):
|
|
575
585
|
value = re.sub(
|
|
576
586
|
r"[a-zA-Z]",
|
|
577
|
-
lambda m: "[{
|
|
587
|
+
lambda m: f"[{m.group().lower()!r}{m.group().upper()!r}]",
|
|
578
588
|
value,
|
|
579
589
|
)
|
|
580
590
|
if self._repo.git_dir:
|
|
581
|
-
if fnmatch.fnmatchcase(
|
|
582
|
-
paths +=
|
|
591
|
+
if fnmatch.fnmatchcase(os.fspath(self._repo.git_dir), value):
|
|
592
|
+
paths += _all_items(section)
|
|
583
593
|
|
|
584
594
|
elif keyword == "onbranch":
|
|
585
595
|
try:
|
|
@@ -589,8 +599,12 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
589
599
|
continue
|
|
590
600
|
|
|
591
601
|
if fnmatch.fnmatchcase(branch_name, value):
|
|
592
|
-
paths +=
|
|
593
|
-
|
|
602
|
+
paths += _all_items(section)
|
|
603
|
+
elif keyword == "hasconfig:remote.*.url":
|
|
604
|
+
for remote in self._repo.remotes:
|
|
605
|
+
if fnmatch.fnmatchcase(remote.url, value):
|
|
606
|
+
paths += _all_items(section)
|
|
607
|
+
break
|
|
594
608
|
return paths
|
|
595
609
|
|
|
596
610
|
def read(self) -> None: # type: ignore[override]
|
|
@@ -629,8 +643,6 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
629
643
|
file_path = cast(IO[bytes], file_path)
|
|
630
644
|
self._read(file_path, file_path.name)
|
|
631
645
|
else:
|
|
632
|
-
# Assume a path if it is not a file-object.
|
|
633
|
-
file_path = cast(PathLike, file_path)
|
|
634
646
|
try:
|
|
635
647
|
with open(file_path, "rb") as fp:
|
|
636
648
|
file_ok = True
|
|
@@ -764,7 +776,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
764
776
|
if self.read_only:
|
|
765
777
|
raise IOError("Cannot execute non-constant method %s.%s" % (self, method_name))
|
|
766
778
|
|
|
767
|
-
def add_section(self, section:
|
|
779
|
+
def add_section(self, section: "cp._SectionName") -> None:
|
|
768
780
|
"""Assures added options will stay in order."""
|
|
769
781
|
return super().add_section(section)
|
|
770
782
|
|
git/diff.py
CHANGED
|
@@ -23,13 +23,14 @@ from typing import (
|
|
|
23
23
|
List,
|
|
24
24
|
Match,
|
|
25
25
|
Optional,
|
|
26
|
+
Sequence,
|
|
26
27
|
Tuple,
|
|
27
28
|
TYPE_CHECKING,
|
|
28
29
|
TypeVar,
|
|
29
30
|
Union,
|
|
30
31
|
cast,
|
|
31
32
|
)
|
|
32
|
-
from git.types import
|
|
33
|
+
from git.types import PathLike, Literal
|
|
33
34
|
|
|
34
35
|
if TYPE_CHECKING:
|
|
35
36
|
from subprocess import Popen
|
|
@@ -289,7 +290,7 @@ class DiffIndex(List[T_Diff]):
|
|
|
289
290
|
The class improves the diff handling convenience.
|
|
290
291
|
"""
|
|
291
292
|
|
|
292
|
-
change_type = ("A", "C", "D", "R", "M", "T")
|
|
293
|
+
change_type: Sequence[Literal["A", "C", "D", "R", "M", "T"]] = ("A", "C", "D", "R", "M", "T") # noqa: F821
|
|
293
294
|
"""Change type invariant identifying possible ways a blob can have changed:
|
|
294
295
|
|
|
295
296
|
* ``A`` = Added
|
git/index/base.py
CHANGED
|
@@ -407,7 +407,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
407
407
|
r = str(self.repo.working_tree_dir)
|
|
408
408
|
rs = r + os.sep
|
|
409
409
|
for path in paths:
|
|
410
|
-
abs_path =
|
|
410
|
+
abs_path = os.fspath(path)
|
|
411
411
|
if not osp.isabs(abs_path):
|
|
412
412
|
abs_path = osp.join(r, path)
|
|
413
413
|
# END make absolute path
|
|
@@ -656,10 +656,10 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
656
656
|
return path
|
|
657
657
|
if self.repo.bare:
|
|
658
658
|
raise InvalidGitRepositoryError("require non-bare repository")
|
|
659
|
-
if not osp.normpath(
|
|
659
|
+
if not osp.normpath(path).startswith(str(self.repo.working_tree_dir)):
|
|
660
660
|
raise ValueError("Absolute path %r is not in git repository at %r" % (path, self.repo.working_tree_dir))
|
|
661
661
|
result = os.path.relpath(path, self.repo.working_tree_dir)
|
|
662
|
-
if
|
|
662
|
+
if os.fspath(path).endswith(os.sep) and not result.endswith(os.sep):
|
|
663
663
|
result += os.sep
|
|
664
664
|
return result
|
|
665
665
|
|
|
@@ -1036,7 +1036,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1036
1036
|
args.append("--")
|
|
1037
1037
|
|
|
1038
1038
|
# Preprocess paths.
|
|
1039
|
-
paths = self._items_to_rela_paths(items)
|
|
1039
|
+
paths = list(map(os.fspath, self._items_to_rela_paths(items))) # type: ignore[arg-type]
|
|
1040
1040
|
removed_paths = self.repo.git.rm(args, paths, **kwargs).splitlines()
|
|
1041
1041
|
|
|
1042
1042
|
# Process output to gain proper paths.
|
|
@@ -1133,6 +1133,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1133
1133
|
author_date: Union[datetime.datetime, str, None] = None,
|
|
1134
1134
|
commit_date: Union[datetime.datetime, str, None] = None,
|
|
1135
1135
|
skip_hooks: bool = False,
|
|
1136
|
+
trailers: Union[None, "Dict[str, str]", "List[Tuple[str, str]]"] = None,
|
|
1136
1137
|
) -> Commit:
|
|
1137
1138
|
"""Commit the current default index file, creating a
|
|
1138
1139
|
:class:`~git.objects.commit.Commit` object.
|
|
@@ -1169,6 +1170,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1169
1170
|
committer=committer,
|
|
1170
1171
|
author_date=author_date,
|
|
1171
1172
|
commit_date=commit_date,
|
|
1173
|
+
trailers=trailers,
|
|
1172
1174
|
)
|
|
1173
1175
|
if not skip_hooks:
|
|
1174
1176
|
run_commit_hook("post-commit", self)
|
|
@@ -1359,11 +1361,11 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
|
|
1359
1361
|
try:
|
|
1360
1362
|
self.entries[(co_path, 0)]
|
|
1361
1363
|
except KeyError:
|
|
1362
|
-
folder =
|
|
1364
|
+
folder = co_path
|
|
1363
1365
|
if not folder.endswith("/"):
|
|
1364
1366
|
folder += "/"
|
|
1365
1367
|
for entry in self.entries.values():
|
|
1366
|
-
if
|
|
1368
|
+
if os.fspath(entry.path).startswith(folder):
|
|
1367
1369
|
p = entry.path
|
|
1368
1370
|
self._write_path_to_stdin(proc, p, p, make_exc, fprogress, read_from_stdout=False)
|
|
1369
1371
|
checked_out_files.append(p)
|
git/index/fun.py
CHANGED
|
@@ -36,7 +36,7 @@ from git.objects.fun import (
|
|
|
36
36
|
)
|
|
37
37
|
from git.util import IndexFileSHA1Writer, finalize_process
|
|
38
38
|
|
|
39
|
-
from .typ import BaseIndexEntry, IndexEntry, CE_NAMEMASK, CE_STAGESHIFT
|
|
39
|
+
from .typ import CE_EXTENDED, BaseIndexEntry, IndexEntry, CE_NAMEMASK, CE_STAGESHIFT
|
|
40
40
|
from .util import pack, unpack
|
|
41
41
|
|
|
42
42
|
# typing -----------------------------------------------------------------------------
|
|
@@ -87,7 +87,7 @@ def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None:
|
|
|
87
87
|
return
|
|
88
88
|
|
|
89
89
|
env = os.environ.copy()
|
|
90
|
-
env["GIT_INDEX_FILE"] = safe_decode(
|
|
90
|
+
env["GIT_INDEX_FILE"] = safe_decode(os.fspath(index.path))
|
|
91
91
|
env["GIT_EDITOR"] = ":"
|
|
92
92
|
cmd = [hp]
|
|
93
93
|
try:
|
|
@@ -158,7 +158,7 @@ def write_cache(
|
|
|
158
158
|
write = stream_sha.write
|
|
159
159
|
|
|
160
160
|
# Header
|
|
161
|
-
version = 2
|
|
161
|
+
version = 3 if any(entry.extended_flags for entry in entries) else 2
|
|
162
162
|
write(b"DIRC")
|
|
163
163
|
write(pack(">LL", version, len(entries)))
|
|
164
164
|
|
|
@@ -172,6 +172,8 @@ def write_cache(
|
|
|
172
172
|
plen = len(path) & CE_NAMEMASK # Path length
|
|
173
173
|
assert plen == len(path), "Path %s too long to fit into index" % entry.path
|
|
174
174
|
flags = plen | (entry.flags & CE_NAMEMASK_INV) # Clear possible previous values.
|
|
175
|
+
if entry.extended_flags:
|
|
176
|
+
flags |= CE_EXTENDED
|
|
175
177
|
write(
|
|
176
178
|
pack(
|
|
177
179
|
">LLLLLL20sH",
|
|
@@ -185,6 +187,8 @@ def write_cache(
|
|
|
185
187
|
flags,
|
|
186
188
|
)
|
|
187
189
|
)
|
|
190
|
+
if entry.extended_flags:
|
|
191
|
+
write(pack(">H", entry.extended_flags))
|
|
188
192
|
write(path)
|
|
189
193
|
real_size = (tell() - beginoffset + 8) & ~7
|
|
190
194
|
write(b"\0" * ((beginoffset + real_size) - tell()))
|
|
@@ -206,8 +210,7 @@ def read_header(stream: IO[bytes]) -> Tuple[int, int]:
|
|
|
206
210
|
unpacked = cast(Tuple[int, int], unpack(">LL", stream.read(4 * 2)))
|
|
207
211
|
version, num_entries = unpacked
|
|
208
212
|
|
|
209
|
-
|
|
210
|
-
assert version in (1, 2), "Unsupported git index version %i, only 1 and 2 are supported" % version
|
|
213
|
+
assert version in (1, 2, 3), "Unsupported git index version %i, only 1, 2, and 3 are supported" % version
|
|
211
214
|
return version, num_entries
|
|
212
215
|
|
|
213
216
|
|
|
@@ -260,12 +263,15 @@ def read_cache(
|
|
|
260
263
|
ctime = unpack(">8s", read(8))[0]
|
|
261
264
|
mtime = unpack(">8s", read(8))[0]
|
|
262
265
|
(dev, ino, mode, uid, gid, size, sha, flags) = unpack(">LLLLLL20sH", read(20 + 4 * 6 + 2))
|
|
266
|
+
extended_flags = 0
|
|
267
|
+
if flags & CE_EXTENDED:
|
|
268
|
+
extended_flags = unpack(">H", read(2))[0]
|
|
263
269
|
path_size = flags & CE_NAMEMASK
|
|
264
270
|
path = read(path_size).decode(defenc)
|
|
265
271
|
|
|
266
272
|
real_size = (tell() - beginoffset + 8) & ~7
|
|
267
273
|
read((beginoffset + real_size) - tell())
|
|
268
|
-
entry = IndexEntry((mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size))
|
|
274
|
+
entry = IndexEntry((mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size, extended_flags))
|
|
269
275
|
# entry_key would be the method to use, but we save the effort.
|
|
270
276
|
entries[(path, entry.stage)] = entry
|
|
271
277
|
count += 1
|
git/index/typ.py
CHANGED
|
@@ -32,6 +32,9 @@ CE_EXTENDED = 0x4000
|
|
|
32
32
|
CE_VALID = 0x8000
|
|
33
33
|
CE_STAGESHIFT = 12
|
|
34
34
|
|
|
35
|
+
CE_EXT_SKIP_WORKTREE = 0x4000
|
|
36
|
+
CE_EXT_INTENT_TO_ADD = 0x2000
|
|
37
|
+
|
|
35
38
|
# } END invariants
|
|
36
39
|
|
|
37
40
|
|
|
@@ -87,6 +90,8 @@ class BaseIndexEntryHelper(NamedTuple):
|
|
|
87
90
|
uid: int = 0
|
|
88
91
|
gid: int = 0
|
|
89
92
|
size: int = 0
|
|
93
|
+
# version 3 extended flags, only when (flags & CE_EXTENDED) is set
|
|
94
|
+
extended_flags: int = 0
|
|
90
95
|
|
|
91
96
|
|
|
92
97
|
class BaseIndexEntry(BaseIndexEntryHelper):
|
|
@@ -102,7 +107,7 @@ class BaseIndexEntry(BaseIndexEntryHelper):
|
|
|
102
107
|
cls,
|
|
103
108
|
inp_tuple: Union[
|
|
104
109
|
Tuple[int, bytes, int, PathLike],
|
|
105
|
-
Tuple[int, bytes, int, PathLike, bytes, bytes, int, int, int, int, int],
|
|
110
|
+
Tuple[int, bytes, int, PathLike, bytes, bytes, int, int, int, int, int, int],
|
|
106
111
|
],
|
|
107
112
|
) -> "BaseIndexEntry":
|
|
108
113
|
"""Override ``__new__`` to allow construction from a tuple for backwards
|
|
@@ -134,6 +139,14 @@ class BaseIndexEntry(BaseIndexEntryHelper):
|
|
|
134
139
|
"""
|
|
135
140
|
return (self.flags & CE_STAGEMASK) >> CE_STAGESHIFT
|
|
136
141
|
|
|
142
|
+
@property
|
|
143
|
+
def skip_worktree(self) -> bool:
|
|
144
|
+
return (self.extended_flags & CE_EXT_SKIP_WORKTREE) > 0
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def intent_to_add(self) -> bool:
|
|
148
|
+
return (self.extended_flags & CE_EXT_INTENT_TO_ADD) > 0
|
|
149
|
+
|
|
137
150
|
@classmethod
|
|
138
151
|
def from_blob(cls, blob: Blob, stage: int = 0) -> "BaseIndexEntry":
|
|
139
152
|
""":return: Fully equipped BaseIndexEntry at the given stage"""
|
|
@@ -179,7 +192,7 @@ class IndexEntry(BaseIndexEntry):
|
|
|
179
192
|
Instance of type :class:`BaseIndexEntry`.
|
|
180
193
|
"""
|
|
181
194
|
time = pack(">LL", 0, 0)
|
|
182
|
-
return IndexEntry((base.mode, base.binsha, base.flags, base.path, time, time, 0, 0, 0, 0, 0))
|
|
195
|
+
return IndexEntry((base.mode, base.binsha, base.flags, base.path, time, time, 0, 0, 0, 0, 0)) # type: ignore[arg-type]
|
|
183
196
|
|
|
184
197
|
@classmethod
|
|
185
198
|
def from_blob(cls, blob: Blob, stage: int = 0) -> "IndexEntry":
|
|
@@ -198,5 +211,5 @@ class IndexEntry(BaseIndexEntry):
|
|
|
198
211
|
0,
|
|
199
212
|
0,
|
|
200
213
|
blob.size,
|
|
201
|
-
)
|
|
214
|
+
) # type: ignore[arg-type]
|
|
202
215
|
)
|
git/index/util.py
CHANGED
|
@@ -15,7 +15,7 @@ from types import TracebackType
|
|
|
15
15
|
|
|
16
16
|
# typing ----------------------------------------------------------------------
|
|
17
17
|
|
|
18
|
-
from typing import Any, Callable, TYPE_CHECKING, Optional, Type
|
|
18
|
+
from typing import Any, Callable, TYPE_CHECKING, Optional, Type, cast
|
|
19
19
|
|
|
20
20
|
from git.types import Literal, PathLike, _T
|
|
21
21
|
|
|
@@ -106,7 +106,7 @@ def git_working_dir(func: Callable[..., _T]) -> Callable[..., _T]:
|
|
|
106
106
|
@wraps(func)
|
|
107
107
|
def set_git_working_dir(self: "IndexFile", *args: Any, **kwargs: Any) -> _T:
|
|
108
108
|
cur_wd = os.getcwd()
|
|
109
|
-
os.chdir(
|
|
109
|
+
os.chdir(cast(PathLike, self.repo.working_tree_dir))
|
|
110
110
|
try:
|
|
111
111
|
return func(self, *args, **kwargs)
|
|
112
112
|
finally:
|
git/objects/blob.py
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
__all__ = ["Blob"]
|
|
7
7
|
|
|
8
8
|
from mimetypes import guess_type
|
|
9
|
+
import os
|
|
9
10
|
import sys
|
|
10
11
|
|
|
11
12
|
if sys.version_info >= (3, 8):
|
|
@@ -44,5 +45,5 @@ class Blob(base.IndexObject):
|
|
|
44
45
|
"""
|
|
45
46
|
guesses = None
|
|
46
47
|
if self.path:
|
|
47
|
-
guesses = guess_type(
|
|
48
|
+
guesses = guess_type(os.fspath(self.path))
|
|
48
49
|
return guesses and guesses[0] or self.DEFAULT_MIME_TYPE
|
git/objects/commit.py
CHANGED
|
@@ -450,14 +450,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
|
|
|
450
450
|
:return:
|
|
451
451
|
List containing key-value tuples of whitespace stripped trailer information.
|
|
452
452
|
"""
|
|
453
|
-
|
|
454
|
-
proc: Git.AutoInterrupt = self.repo.git.execute( # type: ignore[call-overload]
|
|
455
|
-
cmd,
|
|
456
|
-
as_process=True,
|
|
457
|
-
istream=PIPE,
|
|
458
|
-
)
|
|
459
|
-
trailer: str = proc.communicate(str(self.message).encode())[0].decode("utf8")
|
|
460
|
-
trailer = trailer.strip()
|
|
453
|
+
trailer = self._interpret_trailers(self.repo, self.message, ["--parse"], encoding=self.encoding).strip()
|
|
461
454
|
|
|
462
455
|
if not trailer:
|
|
463
456
|
return []
|
|
@@ -469,6 +462,27 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
|
|
|
469
462
|
|
|
470
463
|
return trailer_list
|
|
471
464
|
|
|
465
|
+
@classmethod
|
|
466
|
+
def _interpret_trailers(
|
|
467
|
+
cls,
|
|
468
|
+
repo: "Repo",
|
|
469
|
+
message: Union[str, bytes],
|
|
470
|
+
trailer_args: Sequence[str],
|
|
471
|
+
encoding: str = default_encoding,
|
|
472
|
+
) -> str:
|
|
473
|
+
message_bytes = message if isinstance(message, bytes) else message.encode(encoding, errors="strict")
|
|
474
|
+
cmd = [repo.git.GIT_PYTHON_GIT_EXECUTABLE, "interpret-trailers", *trailer_args]
|
|
475
|
+
proc: Git.AutoInterrupt = repo.git.execute( # type: ignore[call-overload]
|
|
476
|
+
cmd,
|
|
477
|
+
as_process=True,
|
|
478
|
+
istream=PIPE,
|
|
479
|
+
)
|
|
480
|
+
try:
|
|
481
|
+
stdout_bytes, _ = proc.communicate(message_bytes)
|
|
482
|
+
return stdout_bytes.decode(encoding, errors="strict")
|
|
483
|
+
finally:
|
|
484
|
+
finalize_process(proc)
|
|
485
|
+
|
|
472
486
|
@property
|
|
473
487
|
def trailers_dict(self) -> Dict[str, List[str]]:
|
|
474
488
|
"""Get the trailers of the message as a dictionary.
|
|
@@ -570,6 +584,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
|
|
|
570
584
|
committer: Union[None, Actor] = None,
|
|
571
585
|
author_date: Union[None, str, datetime.datetime] = None,
|
|
572
586
|
commit_date: Union[None, str, datetime.datetime] = None,
|
|
587
|
+
trailers: Union[None, Dict[str, str], List[Tuple[str, str]]] = None,
|
|
573
588
|
) -> "Commit":
|
|
574
589
|
"""Commit the given tree, creating a :class:`Commit` object.
|
|
575
590
|
|
|
@@ -609,6 +624,14 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
|
|
|
609
624
|
:param commit_date:
|
|
610
625
|
The timestamp for the committer field.
|
|
611
626
|
|
|
627
|
+
:param trailers:
|
|
628
|
+
Optional trailer key-value pairs to append to the commit message.
|
|
629
|
+
Can be a dictionary mapping trailer keys to values, or a list of
|
|
630
|
+
``(key, value)`` tuples (useful when the same key appears multiple
|
|
631
|
+
times, e.g. multiple ``Signed-off-by`` trailers). Trailers are
|
|
632
|
+
appended using ``git interpret-trailers``.
|
|
633
|
+
See :manpage:`git-interpret-trailers(1)`.
|
|
634
|
+
|
|
612
635
|
:return:
|
|
613
636
|
:class:`Commit` object representing the new commit.
|
|
614
637
|
|
|
@@ -678,6 +701,21 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
|
|
|
678
701
|
tree = repo.tree(tree)
|
|
679
702
|
# END tree conversion
|
|
680
703
|
|
|
704
|
+
# APPLY TRAILERS
|
|
705
|
+
if trailers:
|
|
706
|
+
trailer_args: List[str] = []
|
|
707
|
+
if isinstance(trailers, dict):
|
|
708
|
+
for key, val in trailers.items():
|
|
709
|
+
trailer_args.append("--trailer")
|
|
710
|
+
trailer_args.append(f"{key}: {val}")
|
|
711
|
+
else:
|
|
712
|
+
for key, val in trailers:
|
|
713
|
+
trailer_args.append("--trailer")
|
|
714
|
+
trailer_args.append(f"{key}: {val}")
|
|
715
|
+
|
|
716
|
+
message = cls._interpret_trailers(repo, str(message), trailer_args)
|
|
717
|
+
# END apply trailers
|
|
718
|
+
|
|
681
719
|
# CREATE NEW COMMIT
|
|
682
720
|
new_commit = cls(
|
|
683
721
|
repo,
|
|
@@ -900,7 +938,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
|
|
|
900
938
|
if self.message:
|
|
901
939
|
results = re.findall(
|
|
902
940
|
r"^Co-authored-by: (.*) <(.*?)>$",
|
|
903
|
-
self.message,
|
|
941
|
+
str(self.message),
|
|
904
942
|
re.MULTILINE,
|
|
905
943
|
)
|
|
906
944
|
for author in results:
|
git/objects/submodule/base.py
CHANGED
|
@@ -66,7 +66,7 @@ from git.types import Commit_ish, PathLike, TBD
|
|
|
66
66
|
if TYPE_CHECKING:
|
|
67
67
|
from git.index import IndexFile
|
|
68
68
|
from git.objects.commit import Commit
|
|
69
|
-
from git.refs import Head
|
|
69
|
+
from git.refs import Head, RemoteReference
|
|
70
70
|
from git.repo import Repo
|
|
71
71
|
|
|
72
72
|
# -----------------------------------------------------------------------------
|
|
@@ -352,10 +352,10 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
352
352
|
module_abspath_dir = osp.dirname(module_abspath)
|
|
353
353
|
if not osp.isdir(module_abspath_dir):
|
|
354
354
|
os.makedirs(module_abspath_dir)
|
|
355
|
-
module_checkout_path = osp.join(
|
|
355
|
+
module_checkout_path = osp.join(repo.working_tree_dir, path) # type: ignore[arg-type]
|
|
356
356
|
|
|
357
357
|
if url.startswith("../"):
|
|
358
|
-
remote_name = repo.active_branch.tracking_branch().remote_name
|
|
358
|
+
remote_name = cast("RemoteReference", repo.active_branch.tracking_branch()).remote_name
|
|
359
359
|
repo_remote_url = repo.remote(remote_name).url
|
|
360
360
|
url = os.path.join(repo_remote_url, url)
|
|
361
361
|
|
|
@@ -541,7 +541,7 @@ class Submodule(IndexObject, TraversableIterableObj):
|
|
|
541
541
|
if sm.exists():
|
|
542
542
|
# Reretrieve submodule from tree.
|
|
543
543
|
try:
|
|
544
|
-
sm = repo.head.commit.tree[
|
|
544
|
+
sm = repo.head.commit.tree[os.fspath(path)]
|
|
545
545
|
sm._name = name
|
|
546
546
|
return sm
|
|
547
547
|
except KeyError:
|
git/objects/tree.py
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
__all__ = ["TreeModifier", "Tree"]
|
|
7
7
|
|
|
8
|
+
import os
|
|
8
9
|
import sys
|
|
9
10
|
|
|
10
11
|
import git.diff as git_diff
|
|
@@ -230,7 +231,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
|
|
|
230
231
|
raise TypeError("Unknown mode %o found in tree data for path '%s'" % (mode, path)) from e
|
|
231
232
|
# END for each item
|
|
232
233
|
|
|
233
|
-
def join(self, file:
|
|
234
|
+
def join(self, file: PathLike) -> IndexObjUnion:
|
|
234
235
|
"""Find the named object in this tree's contents.
|
|
235
236
|
|
|
236
237
|
:return:
|
|
@@ -241,6 +242,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
|
|
|
241
242
|
If the given file or tree does not exist in this tree.
|
|
242
243
|
"""
|
|
243
244
|
msg = "Blob or Tree named %r not found"
|
|
245
|
+
file = os.fspath(file)
|
|
244
246
|
if "/" in file:
|
|
245
247
|
tree = self
|
|
246
248
|
item = self
|
|
@@ -269,7 +271,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
|
|
|
269
271
|
raise KeyError(msg % file)
|
|
270
272
|
# END handle long paths
|
|
271
273
|
|
|
272
|
-
def __truediv__(self, file:
|
|
274
|
+
def __truediv__(self, file: PathLike) -> IndexObjUnion:
|
|
273
275
|
"""The ``/`` operator is another syntax for joining.
|
|
274
276
|
|
|
275
277
|
See :meth:`join` for details.
|
git/refs/head.py
CHANGED
|
@@ -22,7 +22,6 @@ from typing import Any, Sequence, TYPE_CHECKING, Union
|
|
|
22
22
|
from git.types import Commit_ish, PathLike
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
|
-
from git.objects import Commit
|
|
26
25
|
from git.refs import RemoteReference
|
|
27
26
|
from git.repo import Repo
|
|
28
27
|
|
|
@@ -44,9 +43,6 @@ class HEAD(SymbolicReference):
|
|
|
44
43
|
|
|
45
44
|
__slots__ = ()
|
|
46
45
|
|
|
47
|
-
# TODO: This can be removed once SymbolicReference.commit has static type hints.
|
|
48
|
-
commit: "Commit"
|
|
49
|
-
|
|
50
46
|
def __init__(self, repo: "Repo", path: PathLike = _HEAD_NAME) -> None:
|
|
51
47
|
if path != self._HEAD_NAME:
|
|
52
48
|
raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path))
|
|
@@ -149,7 +145,7 @@ class Head(Reference):
|
|
|
149
145
|
k_config_remote_ref = "merge" # Branch to merge from remote.
|
|
150
146
|
|
|
151
147
|
@classmethod
|
|
152
|
-
def delete(cls, repo: "Repo", *heads: "Union[Head, str]", force: bool = False, **kwargs: Any) -> None:
|
|
148
|
+
def delete(cls, repo: "Repo", *heads: "Union[Head, str]", force: bool = False, **kwargs: Any) -> None: # type: ignore[override]
|
|
153
149
|
"""Delete the given heads.
|
|
154
150
|
|
|
155
151
|
:param force:
|
git/refs/log.py
CHANGED
|
@@ -145,7 +145,7 @@ class RefLogEntry(Tuple[str, str, Actor, Tuple[int, int], str]):
|
|
|
145
145
|
actor = Actor._from_string(info[82 : email_end + 1])
|
|
146
146
|
time, tz_offset = parse_date(info[email_end + 2 :]) # skipcq: PYL-W0621
|
|
147
147
|
|
|
148
|
-
return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), msg))
|
|
148
|
+
return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), msg)) # type: ignore [arg-type]
|
|
149
149
|
|
|
150
150
|
|
|
151
151
|
class RefLog(List[RefLogEntry], Serializable):
|
git/refs/reference.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
__all__ = ["Reference"]
|
|
5
5
|
|
|
6
|
+
import os
|
|
6
7
|
from git.util import IterableObj, LazyMixin
|
|
7
8
|
|
|
8
9
|
from .symbolic import SymbolicReference, T_References
|
|
@@ -65,7 +66,7 @@ class Reference(SymbolicReference, LazyMixin, IterableObj):
|
|
|
65
66
|
If ``False``, you can provide any path.
|
|
66
67
|
Otherwise the path must start with the default path prefix of this type.
|
|
67
68
|
"""
|
|
68
|
-
if check_path and not
|
|
69
|
+
if check_path and not os.fspath(path).startswith(self._common_path_default + "/"):
|
|
69
70
|
raise ValueError(f"Cannot instantiate {self.__class__.__name__!r} from path {path}")
|
|
70
71
|
self.path: str # SymbolicReference converts to string at the moment.
|
|
71
72
|
super().__init__(repo, path)
|