GitPython 3.1.48__py3-none-any.whl → 3.1.50__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/config.py +37 -2
- git/repo/base.py +22 -0
- git/repo/fun.py +445 -134
- {gitpython-3.1.48.dist-info → gitpython-3.1.50.dist-info}/METADATA +1 -1
- {gitpython-3.1.48.dist-info → gitpython-3.1.50.dist-info}/RECORD +10 -10
- {gitpython-3.1.48.dist-info → gitpython-3.1.50.dist-info}/WHEEL +0 -0
- {gitpython-3.1.48.dist-info → gitpython-3.1.50.dist-info}/licenses/AUTHORS +0 -0
- {gitpython-3.1.48.dist-info → gitpython-3.1.50.dist-info}/licenses/LICENSE +0 -0
- {gitpython-3.1.48.dist-info → gitpython-3.1.50.dist-info}/top_level.txt +0 -0
git/__init__.py
CHANGED
git/config.py
CHANGED
|
@@ -72,6 +72,9 @@ CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbr
|
|
|
72
72
|
See: https://git-scm.com/docs/git-config#_conditional_includes
|
|
73
73
|
"""
|
|
74
74
|
|
|
75
|
+
UNSAFE_CONFIG_CHARS_RE = re.compile(r"[\r\n\x00]")
|
|
76
|
+
"""Characters that cannot be safely written in config names or values."""
|
|
77
|
+
|
|
75
78
|
|
|
76
79
|
class MetaParserBuilder(abc.ABCMeta): # noqa: B024
|
|
77
80
|
"""Utility class wrapping base-class methods into decorators that assure read-only
|
|
@@ -778,6 +781,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
778
781
|
|
|
779
782
|
def add_section(self, section: "cp._SectionName") -> None:
|
|
780
783
|
"""Assures added options will stay in order."""
|
|
784
|
+
self._assure_config_name_safe(section, "section")
|
|
781
785
|
return super().add_section(section)
|
|
782
786
|
|
|
783
787
|
@property
|
|
@@ -882,6 +886,30 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
882
886
|
return str(value)
|
|
883
887
|
return force_text(value)
|
|
884
888
|
|
|
889
|
+
def _value_to_string_safe(self, value: Union[str, bytes, int, float, bool]) -> str:
|
|
890
|
+
value_str = self._value_to_string(value)
|
|
891
|
+
if UNSAFE_CONFIG_CHARS_RE.search(value_str):
|
|
892
|
+
raise ValueError("Git config values must not contain CR, LF, or NUL")
|
|
893
|
+
return value_str
|
|
894
|
+
|
|
895
|
+
def _assure_config_name_safe(self, name: "cp._SectionName", label: str) -> None:
|
|
896
|
+
if isinstance(name, str) and UNSAFE_CONFIG_CHARS_RE.search(name):
|
|
897
|
+
raise ValueError("Git config %s names must not contain CR, LF, or NUL" % label)
|
|
898
|
+
|
|
899
|
+
@needs_values
|
|
900
|
+
@set_dirty_and_flush_changes
|
|
901
|
+
def set(
|
|
902
|
+
self,
|
|
903
|
+
section: str,
|
|
904
|
+
option: str,
|
|
905
|
+
value: Union[str, bytes, int, float, bool, None] = None,
|
|
906
|
+
) -> None:
|
|
907
|
+
self._assure_config_name_safe(section, "section")
|
|
908
|
+
self._assure_config_name_safe(option, "option")
|
|
909
|
+
if value is not None:
|
|
910
|
+
value = self._value_to_string_safe(value)
|
|
911
|
+
return super().set(section, option, value)
|
|
912
|
+
|
|
885
913
|
@needs_values
|
|
886
914
|
@set_dirty_and_flush_changes
|
|
887
915
|
def set_value(self, section: str, option: str, value: Union[str, bytes, int, float, bool]) -> "GitConfigParser":
|
|
@@ -902,9 +930,12 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
902
930
|
:return:
|
|
903
931
|
This instance
|
|
904
932
|
"""
|
|
933
|
+
self._assure_config_name_safe(section, "section")
|
|
934
|
+
self._assure_config_name_safe(option, "option")
|
|
935
|
+
value_str = self._value_to_string_safe(value)
|
|
905
936
|
if not self.has_section(section):
|
|
906
937
|
self.add_section(section)
|
|
907
|
-
|
|
938
|
+
super().set(section, option, value_str)
|
|
908
939
|
return self
|
|
909
940
|
|
|
910
941
|
@needs_values
|
|
@@ -929,9 +960,12 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
929
960
|
:return:
|
|
930
961
|
This instance
|
|
931
962
|
"""
|
|
963
|
+
self._assure_config_name_safe(section, "section")
|
|
964
|
+
self._assure_config_name_safe(option, "option")
|
|
965
|
+
value_str = self._value_to_string_safe(value)
|
|
932
966
|
if not self.has_section(section):
|
|
933
967
|
self.add_section(section)
|
|
934
|
-
self._sections[section].add(option,
|
|
968
|
+
self._sections[section].add(option, value_str)
|
|
935
969
|
return self
|
|
936
970
|
|
|
937
971
|
def rename_section(self, section: str, new_name: str) -> "GitConfigParser":
|
|
@@ -948,6 +982,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
948
982
|
"""
|
|
949
983
|
if not self.has_section(section):
|
|
950
984
|
raise ValueError("Source section '%s' doesn't exist" % section)
|
|
985
|
+
self._assure_config_name_safe(new_name, "section")
|
|
951
986
|
if self.has_section(new_name):
|
|
952
987
|
raise ValueError("Destination section '%s' already exists" % new_name)
|
|
953
988
|
|
git/repo/base.py
CHANGED
|
@@ -242,6 +242,28 @@ class Repo:
|
|
|
242
242
|
# It's important to normalize the paths, as submodules will otherwise
|
|
243
243
|
# initialize their repo instances with paths that depend on path-portions
|
|
244
244
|
# that will not exist after being removed. It's just cleaner.
|
|
245
|
+
if (
|
|
246
|
+
osp.isfile(osp.join(curpath, "gitdir"))
|
|
247
|
+
and osp.isfile(osp.join(curpath, "commondir"))
|
|
248
|
+
and osp.isfile(osp.join(curpath, "HEAD"))
|
|
249
|
+
):
|
|
250
|
+
git_dir = curpath
|
|
251
|
+
|
|
252
|
+
if "GIT_WORK_TREE" in os.environ:
|
|
253
|
+
self._working_tree_dir = os.getenv("GIT_WORK_TREE")
|
|
254
|
+
else:
|
|
255
|
+
# Linked worktree administrative directories store the path to the
|
|
256
|
+
# worktree's .git file in their gitdir file (without "gitdir: " prefix).
|
|
257
|
+
with open(osp.join(git_dir, "gitdir")) as fp:
|
|
258
|
+
worktree_gitfile = fp.read().strip()
|
|
259
|
+
|
|
260
|
+
if not osp.isabs(worktree_gitfile):
|
|
261
|
+
worktree_gitfile = osp.normpath(osp.join(git_dir, worktree_gitfile))
|
|
262
|
+
|
|
263
|
+
self._working_tree_dir = osp.dirname(worktree_gitfile)
|
|
264
|
+
|
|
265
|
+
break
|
|
266
|
+
|
|
245
267
|
if is_git_dir(curpath):
|
|
246
268
|
git_dir = curpath
|
|
247
269
|
# from man git-config : core.worktree
|
git/repo/fun.py
CHANGED
|
@@ -20,6 +20,7 @@ __all__ = [
|
|
|
20
20
|
import os
|
|
21
21
|
import os.path as osp
|
|
22
22
|
from pathlib import Path
|
|
23
|
+
import re
|
|
23
24
|
import stat
|
|
24
25
|
from string import digits
|
|
25
26
|
|
|
@@ -28,19 +29,21 @@ from gitdb.exc import BadName, BadObject
|
|
|
28
29
|
from git.cmd import Git
|
|
29
30
|
from git.exc import WorkTreeRepositoryUnsupported
|
|
30
31
|
from git.objects import Object
|
|
32
|
+
from git.objects.util import parse_date
|
|
31
33
|
from git.refs import SymbolicReference
|
|
32
34
|
from git.util import cygpath, bin_to_hex, hex_to_bin
|
|
33
35
|
|
|
34
36
|
# Typing ----------------------------------------------------------------------
|
|
35
37
|
|
|
36
|
-
from typing import Optional, TYPE_CHECKING, Union, cast, overload
|
|
38
|
+
from typing import Iterator, Optional, TYPE_CHECKING, Tuple, Union, cast, overload
|
|
37
39
|
|
|
38
40
|
from git.types import AnyGitObject, Literal, PathLike
|
|
39
41
|
|
|
40
42
|
if TYPE_CHECKING:
|
|
41
43
|
from git.db import GitCmdObjectDB
|
|
42
|
-
from git.objects import Commit
|
|
44
|
+
from git.objects import Commit
|
|
43
45
|
from git.refs.reference import Reference
|
|
46
|
+
from git.refs.log import RefLog, RefLogEntry
|
|
44
47
|
from git.refs.tag import Tag
|
|
45
48
|
|
|
46
49
|
from .base import Repo
|
|
@@ -139,6 +142,23 @@ def short_to_long(odb: "GitCmdObjectDB", hexsha: str) -> Optional[bytes]:
|
|
|
139
142
|
# END exception handling
|
|
140
143
|
|
|
141
144
|
|
|
145
|
+
def _describe_to_long(repo: "Repo", name: str) -> Optional[bytes]:
|
|
146
|
+
"""Resolve git-describe style names to the abbreviated object they contain."""
|
|
147
|
+
match = re.match(r"^.+-\d+-g([0-9A-Fa-f]{4,40})(?:-dirty)?$", name)
|
|
148
|
+
if match is None:
|
|
149
|
+
match = re.match(r"^.+-g([0-9A-Fa-f]{4,40})(?:-dirty)?$", name)
|
|
150
|
+
if match is None:
|
|
151
|
+
match = re.match(r"^([0-9A-Fa-f]{4,40})-dirty$", name)
|
|
152
|
+
if match is None:
|
|
153
|
+
return None
|
|
154
|
+
# END handle match
|
|
155
|
+
|
|
156
|
+
hexsha = match.group(1)
|
|
157
|
+
if len(hexsha) == 40:
|
|
158
|
+
return hexsha.encode("ascii")
|
|
159
|
+
return short_to_long(repo.odb, hexsha)
|
|
160
|
+
|
|
161
|
+
|
|
142
162
|
@overload
|
|
143
163
|
def name_to_object(repo: "Repo", name: str, return_ref: Literal[False] = ...) -> AnyGitObject: ...
|
|
144
164
|
|
|
@@ -192,6 +212,10 @@ def name_to_object(repo: "Repo", name: str, return_ref: bool = False) -> Union[A
|
|
|
192
212
|
# END for each base
|
|
193
213
|
# END handle hexsha
|
|
194
214
|
|
|
215
|
+
if hexsha is None:
|
|
216
|
+
hexsha = _describe_to_long(repo, name)
|
|
217
|
+
# END handle describe output
|
|
218
|
+
|
|
195
219
|
# Didn't find any ref, this is an error.
|
|
196
220
|
if return_ref:
|
|
197
221
|
raise BadObject("Couldn't find reference named %r" % name)
|
|
@@ -227,6 +251,361 @@ def to_commit(obj: Object) -> "Commit":
|
|
|
227
251
|
return obj
|
|
228
252
|
|
|
229
253
|
|
|
254
|
+
def _object_from_hexsha(repo: "Repo", hexsha: str) -> AnyGitObject:
|
|
255
|
+
return Object.new_from_sha(repo, hex_to_bin(hexsha))
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _current_reflog_ref(repo: "Repo") -> SymbolicReference:
|
|
259
|
+
try:
|
|
260
|
+
return repo.head.ref
|
|
261
|
+
except TypeError:
|
|
262
|
+
return repo.head
|
|
263
|
+
# END handle detached head
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _common_reflog_path(repo: "Repo", ref: SymbolicReference) -> Optional[str]:
|
|
267
|
+
if repo.common_dir == repo.git_dir:
|
|
268
|
+
return None
|
|
269
|
+
# END handle normal repository
|
|
270
|
+
return SymbolicReference._get_validated_path(osp.join(repo.common_dir, "logs"), ref.path)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _ref_log(repo: "Repo", ref: SymbolicReference) -> "RefLog":
|
|
274
|
+
try:
|
|
275
|
+
return ref.log()
|
|
276
|
+
except FileNotFoundError:
|
|
277
|
+
common_path = _common_reflog_path(repo, ref)
|
|
278
|
+
if common_path and osp.isfile(common_path):
|
|
279
|
+
from git.refs.log import RefLog
|
|
280
|
+
|
|
281
|
+
return RefLog.from_file(common_path)
|
|
282
|
+
# END handle linked-worktree branch logs
|
|
283
|
+
try:
|
|
284
|
+
if ref.path == repo.head.ref.path:
|
|
285
|
+
return repo.head.log()
|
|
286
|
+
# END handle linked-worktree current branch logs
|
|
287
|
+
except TypeError:
|
|
288
|
+
pass
|
|
289
|
+
# END handle detached head
|
|
290
|
+
raise
|
|
291
|
+
# END handle missing branch log
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _ref_log_entry(repo: "Repo", ref: SymbolicReference, index: int) -> "RefLogEntry":
|
|
295
|
+
try:
|
|
296
|
+
return ref.log_entry(index)
|
|
297
|
+
except FileNotFoundError:
|
|
298
|
+
common_path = _common_reflog_path(repo, ref)
|
|
299
|
+
if common_path and osp.isfile(common_path):
|
|
300
|
+
from git.refs.log import RefLog
|
|
301
|
+
|
|
302
|
+
return RefLog.entry_at(common_path, index)
|
|
303
|
+
# END handle linked-worktree branch logs
|
|
304
|
+
try:
|
|
305
|
+
if ref.path == repo.head.ref.path:
|
|
306
|
+
return repo.head.log_entry(index)
|
|
307
|
+
# END handle linked-worktree current branch logs
|
|
308
|
+
except TypeError:
|
|
309
|
+
pass
|
|
310
|
+
# END handle detached head
|
|
311
|
+
raise
|
|
312
|
+
# END handle missing branch log
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _find_reflog_entry_by_date(repo: "Repo", ref: SymbolicReference, spec: str) -> str:
|
|
316
|
+
try:
|
|
317
|
+
timestamp, _offset = parse_date(spec)
|
|
318
|
+
except ValueError as e:
|
|
319
|
+
raise NotImplementedError("Support for additional @{...} modes not implemented") from e
|
|
320
|
+
# END handle unsupported dates
|
|
321
|
+
log = _ref_log(repo, ref)
|
|
322
|
+
if not log:
|
|
323
|
+
raise IndexError("Invalid revlog date: %s" % spec)
|
|
324
|
+
# END handle empty log
|
|
325
|
+
|
|
326
|
+
for entry in reversed(log):
|
|
327
|
+
if entry.time[0] <= timestamp:
|
|
328
|
+
return entry.newhexsha
|
|
329
|
+
# END found candidate
|
|
330
|
+
# END for each entry
|
|
331
|
+
return log[0].newhexsha
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _previous_checked_out_branch(repo: "Repo", nth: int) -> AnyGitObject:
|
|
335
|
+
if nth <= 0:
|
|
336
|
+
raise ValueError("Invalid previous checkout selector: -%i" % nth)
|
|
337
|
+
# END handle invalid input
|
|
338
|
+
|
|
339
|
+
seen = 0
|
|
340
|
+
for entry in reversed(_ref_log(repo, repo.head)):
|
|
341
|
+
message = entry.message or ""
|
|
342
|
+
prefix = "checkout: moving from "
|
|
343
|
+
if not message.startswith(prefix):
|
|
344
|
+
continue
|
|
345
|
+
# END skip non-checkouts
|
|
346
|
+
|
|
347
|
+
previous_branch = message[len(prefix) :].split(" to ", 1)[0]
|
|
348
|
+
seen += 1
|
|
349
|
+
if seen == nth:
|
|
350
|
+
return name_to_object(repo, previous_branch)
|
|
351
|
+
# END found selector
|
|
352
|
+
# END for each entry
|
|
353
|
+
raise IndexError("Invalid previous checkout selector: -%i" % nth)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _tracking_branch_object(repo: "Repo", ref: Optional[SymbolicReference]) -> AnyGitObject:
|
|
357
|
+
from git.refs.head import Head
|
|
358
|
+
|
|
359
|
+
if ref is None:
|
|
360
|
+
try:
|
|
361
|
+
head = repo.active_branch
|
|
362
|
+
except TypeError as e:
|
|
363
|
+
raise BadName("@{upstream}") from e
|
|
364
|
+
elif isinstance(ref, Head):
|
|
365
|
+
head = ref
|
|
366
|
+
elif os.fspath(ref.path).startswith("refs/heads/"):
|
|
367
|
+
head = Head(repo, ref.path)
|
|
368
|
+
else:
|
|
369
|
+
raise BadName("%s@{upstream}" % ref.name)
|
|
370
|
+
# END handle head
|
|
371
|
+
|
|
372
|
+
tracking_branch = head.tracking_branch()
|
|
373
|
+
if tracking_branch is None:
|
|
374
|
+
raise BadName("%s@{upstream}" % head.name)
|
|
375
|
+
# END handle missing upstream
|
|
376
|
+
return tracking_branch.commit
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _apply_reflog(repo: "Repo", ref: Optional[SymbolicReference], content: str) -> AnyGitObject:
|
|
380
|
+
if content.startswith("+"):
|
|
381
|
+
content = content[1:]
|
|
382
|
+
# END handle explicit positive sign
|
|
383
|
+
|
|
384
|
+
if content.startswith("-"):
|
|
385
|
+
if ref is not None:
|
|
386
|
+
raise ValueError("Previous checkout selectors do not take an explicit ref")
|
|
387
|
+
if content == "-0":
|
|
388
|
+
raise ValueError("Negative zero is invalid in reflog selector")
|
|
389
|
+
# END handle invalid negative zero
|
|
390
|
+
try:
|
|
391
|
+
return _previous_checked_out_branch(repo, int(content[1:]))
|
|
392
|
+
except ValueError as e:
|
|
393
|
+
raise ValueError("Invalid previous checkout selector: %s" % content) from e
|
|
394
|
+
# END handle previous checkout branch
|
|
395
|
+
|
|
396
|
+
content_lower = content.lower()
|
|
397
|
+
if content_lower in ("u", "upstream", "push"):
|
|
398
|
+
return _tracking_branch_object(repo, ref)
|
|
399
|
+
# END handle sibling branches
|
|
400
|
+
|
|
401
|
+
ref = ref or _current_reflog_ref(repo)
|
|
402
|
+
try:
|
|
403
|
+
entry_no = int(content)
|
|
404
|
+
except ValueError:
|
|
405
|
+
hexsha = _find_reflog_entry_by_date(repo, ref, content)
|
|
406
|
+
else:
|
|
407
|
+
if entry_no >= 100000000:
|
|
408
|
+
hexsha = _find_reflog_entry_by_date(repo, ref, "%s +0000" % entry_no)
|
|
409
|
+
elif entry_no == 0:
|
|
410
|
+
return ref.commit
|
|
411
|
+
else:
|
|
412
|
+
try:
|
|
413
|
+
entry = _ref_log_entry(repo, ref, -(entry_no + 1))
|
|
414
|
+
except IndexError as e:
|
|
415
|
+
raise IndexError("Invalid revlog index: %i" % entry_no) from e
|
|
416
|
+
# END handle index out of bound
|
|
417
|
+
hexsha = entry.newhexsha
|
|
418
|
+
# END handle offset or date-like timestamp
|
|
419
|
+
# END handle content
|
|
420
|
+
return _object_from_hexsha(repo, hexsha)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _find_closing_brace(rev: str, start: int) -> int:
|
|
424
|
+
depth = 1
|
|
425
|
+
escaped = False
|
|
426
|
+
for idx in range(start + 1, len(rev)):
|
|
427
|
+
char = rev[idx]
|
|
428
|
+
if escaped:
|
|
429
|
+
escaped = False
|
|
430
|
+
elif char == "\\":
|
|
431
|
+
escaped = True
|
|
432
|
+
elif char == "{":
|
|
433
|
+
depth += 1
|
|
434
|
+
elif char == "}":
|
|
435
|
+
depth -= 1
|
|
436
|
+
if depth == 0:
|
|
437
|
+
return idx
|
|
438
|
+
# END found end
|
|
439
|
+
# END handle char
|
|
440
|
+
# END for each char
|
|
441
|
+
raise ValueError("Missing closing brace to define type in %s" % rev)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _parse_search(pattern: str) -> Tuple[str, bool]:
|
|
445
|
+
if not pattern:
|
|
446
|
+
raise ValueError("Revision search requires a pattern")
|
|
447
|
+
# END handle empty pattern
|
|
448
|
+
|
|
449
|
+
if pattern.startswith("!-"):
|
|
450
|
+
return pattern[2:], True
|
|
451
|
+
if pattern.startswith("!!"):
|
|
452
|
+
return pattern[1:], False
|
|
453
|
+
if pattern.startswith("!"):
|
|
454
|
+
raise ValueError("Need one character after /!, typically -")
|
|
455
|
+
return pattern, False
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _unescape_braced_regex(pattern: str) -> str:
|
|
459
|
+
out = []
|
|
460
|
+
idx = 0
|
|
461
|
+
while idx < len(pattern):
|
|
462
|
+
char = pattern[idx]
|
|
463
|
+
if char == "\\" and idx + 1 < len(pattern):
|
|
464
|
+
next_char = pattern[idx + 1]
|
|
465
|
+
if next_char in "{}\\":
|
|
466
|
+
out.append(next_char)
|
|
467
|
+
else:
|
|
468
|
+
out.append(char)
|
|
469
|
+
out.append(next_char)
|
|
470
|
+
# END handle escaped char
|
|
471
|
+
idx += 2
|
|
472
|
+
continue
|
|
473
|
+
# END handle backslash
|
|
474
|
+
out.append(char)
|
|
475
|
+
idx += 1
|
|
476
|
+
# END for each char
|
|
477
|
+
return "".join(out)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _find_commit_by_message(
|
|
481
|
+
repo: "Repo", rev: Optional[AnyGitObject], pattern: str, braced: bool = False
|
|
482
|
+
) -> AnyGitObject:
|
|
483
|
+
pattern, negated = _parse_search(_unescape_braced_regex(pattern) if braced else pattern)
|
|
484
|
+
try:
|
|
485
|
+
regex = re.compile(pattern)
|
|
486
|
+
except re.error as e:
|
|
487
|
+
raise ValueError("Invalid commit message regex %r" % pattern) from e
|
|
488
|
+
# END handle invalid regex
|
|
489
|
+
if rev is None:
|
|
490
|
+
commits = _all_ref_commits(repo)
|
|
491
|
+
else:
|
|
492
|
+
commits = _reachable_commits([to_commit(cast(Object, rev))])
|
|
493
|
+
# END handle starting point
|
|
494
|
+
|
|
495
|
+
for commit in commits:
|
|
496
|
+
message = commit.message
|
|
497
|
+
if isinstance(message, bytes):
|
|
498
|
+
message = message.decode(commit.encoding, "replace")
|
|
499
|
+
# END handle bytes message
|
|
500
|
+
matches = regex.search(message or "") is not None
|
|
501
|
+
if matches != negated:
|
|
502
|
+
return commit
|
|
503
|
+
# END found commit
|
|
504
|
+
# END for each commit
|
|
505
|
+
raise BadName("No commit found matching message pattern %r" % pattern)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _all_ref_commits(repo: "Repo") -> Iterator["Commit"]:
|
|
509
|
+
starts = []
|
|
510
|
+
for ref in repo.references:
|
|
511
|
+
try:
|
|
512
|
+
starts.append(to_commit(cast(Object, ref.object)))
|
|
513
|
+
except (BadName, ValueError):
|
|
514
|
+
pass
|
|
515
|
+
# END skip refs that do not point to commits
|
|
516
|
+
# END for each ref
|
|
517
|
+
try:
|
|
518
|
+
starts.append(repo.head.commit)
|
|
519
|
+
except ValueError:
|
|
520
|
+
pass
|
|
521
|
+
# END handle unborn head
|
|
522
|
+
return _reachable_commits(starts)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _reachable_commits(starts: list["Commit"]) -> Iterator["Commit"]:
|
|
526
|
+
seen = set()
|
|
527
|
+
pending = starts[:]
|
|
528
|
+
while pending:
|
|
529
|
+
pending.sort(key=lambda commit: commit.committed_date, reverse=True)
|
|
530
|
+
commit = pending.pop(0)
|
|
531
|
+
if commit.binsha in seen:
|
|
532
|
+
continue
|
|
533
|
+
# END skip seen commit
|
|
534
|
+
seen.add(commit.binsha)
|
|
535
|
+
yield commit
|
|
536
|
+
pending.extend(commit.parents)
|
|
537
|
+
# END while commits remain
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def _index_lookup(repo: "Repo", spec: str) -> AnyGitObject:
|
|
541
|
+
if not spec:
|
|
542
|
+
raise ValueError("':' must be followed by a path")
|
|
543
|
+
# END handle empty lookup
|
|
544
|
+
|
|
545
|
+
stage = 0
|
|
546
|
+
path = spec
|
|
547
|
+
if len(spec) >= 2 and spec[1] == ":" and spec[0] in "0123":
|
|
548
|
+
stage = int(spec[0])
|
|
549
|
+
path = spec[2:]
|
|
550
|
+
# END handle stage
|
|
551
|
+
|
|
552
|
+
try:
|
|
553
|
+
return repo.index.entries[(path, stage)].to_blob(repo)
|
|
554
|
+
except KeyError as e:
|
|
555
|
+
raise BadName("Path %r did not exist in the index at stage %i" % (path, stage)) from e
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def _tree_lookup(obj: AnyGitObject, path: str) -> AnyGitObject:
|
|
559
|
+
if obj.type != "tree":
|
|
560
|
+
obj = to_commit(cast(Object, obj)).tree
|
|
561
|
+
# END get tree
|
|
562
|
+
if not path:
|
|
563
|
+
return obj
|
|
564
|
+
return obj[path]
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def _peel(obj: AnyGitObject, output_type: str, repo: "Repo", rev: str) -> AnyGitObject:
|
|
568
|
+
if output_type.startswith("/"):
|
|
569
|
+
return _find_commit_by_message(repo, obj, output_type[1:], braced=True)
|
|
570
|
+
if output_type == "":
|
|
571
|
+
return deref_tag(obj) if obj.type == "tag" else obj
|
|
572
|
+
if output_type == "object":
|
|
573
|
+
return obj
|
|
574
|
+
if output_type == "commit":
|
|
575
|
+
return to_commit(cast(Object, obj))
|
|
576
|
+
if output_type == "tree":
|
|
577
|
+
return to_commit(cast(Object, obj)).tree if obj.type != "tree" else obj
|
|
578
|
+
if output_type == "blob":
|
|
579
|
+
obj = deref_tag(obj) if obj.type == "tag" else obj
|
|
580
|
+
if obj.type == output_type:
|
|
581
|
+
return obj
|
|
582
|
+
# END handle matching type
|
|
583
|
+
raise ValueError("Could not accommodate requested object type %r, got %s" % (output_type, obj.type))
|
|
584
|
+
if output_type == "tag":
|
|
585
|
+
if obj.type == output_type:
|
|
586
|
+
return obj
|
|
587
|
+
# END handle matching type
|
|
588
|
+
raise ValueError("Could not accommodate requested object type %r, got %s" % (output_type, obj.type))
|
|
589
|
+
# END handle known types
|
|
590
|
+
raise ValueError("Invalid output type: %s ( in %s )" % (output_type, rev))
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def _first_rev_token(rev: str) -> Optional[int]:
|
|
594
|
+
for idx, char in enumerate(rev):
|
|
595
|
+
if char in "^~:":
|
|
596
|
+
return idx
|
|
597
|
+
if char == "@":
|
|
598
|
+
next_char = rev[idx + 1] if idx + 1 < len(rev) else None
|
|
599
|
+
if idx == 0 and next_char in (None, "^", "~", ":", "{"):
|
|
600
|
+
return idx
|
|
601
|
+
if next_char == "{":
|
|
602
|
+
return idx
|
|
603
|
+
# END handle reflog selector
|
|
604
|
+
# END handle at symbol
|
|
605
|
+
# END for each char
|
|
606
|
+
return None
|
|
607
|
+
|
|
608
|
+
|
|
230
609
|
def rev_parse(repo: "Repo", rev: str) -> AnyGitObject:
|
|
231
610
|
"""Parse a revision string. Like :manpage:`git-rev-parse(1)`.
|
|
232
611
|
|
|
@@ -253,137 +632,82 @@ def rev_parse(repo: "Repo", rev: str) -> AnyGitObject:
|
|
|
253
632
|
:raise IndexError:
|
|
254
633
|
If an invalid reflog index is specified.
|
|
255
634
|
"""
|
|
256
|
-
# Are we in colon search mode?
|
|
257
635
|
if rev.startswith(":/"):
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
636
|
+
return _find_commit_by_message(repo, None, rev[2:])
|
|
637
|
+
if rev.startswith(":"):
|
|
638
|
+
return _index_lookup(repo, rev[1:])
|
|
639
|
+
# END handle top-level colon modes
|
|
261
640
|
|
|
262
641
|
obj: Optional[AnyGitObject] = None
|
|
263
642
|
ref = None
|
|
264
|
-
output_type = "commit"
|
|
265
|
-
start = 0
|
|
266
|
-
parsed_to = 0
|
|
267
643
|
lr = len(rev)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
644
|
+
first_token = _first_rev_token(rev)
|
|
645
|
+
if first_token is None:
|
|
646
|
+
return name_to_object(repo, rev)
|
|
647
|
+
# END handle plain name
|
|
648
|
+
|
|
649
|
+
if first_token == 0:
|
|
650
|
+
if rev[0] != "@":
|
|
651
|
+
raise ValueError("Revision specifier must start with an object name: %s" % rev)
|
|
652
|
+
# END handle invalid leading token
|
|
653
|
+
ref = _current_reflog_ref(repo)
|
|
654
|
+
obj = ref.commit
|
|
655
|
+
start = 0 if rev.startswith("@{") else 1
|
|
656
|
+
else:
|
|
657
|
+
if rev[first_token] == "@":
|
|
658
|
+
ref = cast("Reference", name_to_object(repo, rev[:first_token], return_ref=True))
|
|
659
|
+
obj = ref.commit
|
|
660
|
+
else:
|
|
661
|
+
obj = name_to_object(repo, rev[:first_token])
|
|
662
|
+
# END handle anchor
|
|
663
|
+
start = first_token
|
|
664
|
+
# END initialize anchor
|
|
273
665
|
|
|
666
|
+
while start < lr:
|
|
274
667
|
token = rev[start]
|
|
275
668
|
|
|
276
|
-
if
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
# END handle refname
|
|
287
|
-
else:
|
|
288
|
-
if ref is not None:
|
|
289
|
-
obj = ref.commit
|
|
290
|
-
# END handle ref
|
|
291
|
-
# END initialize obj on first token
|
|
669
|
+
if token == "@":
|
|
670
|
+
if start + 1 >= lr or rev[start + 1] != "{":
|
|
671
|
+
raise ValueError("Invalid @ token in revision specifier: %s" % rev)
|
|
672
|
+
# END handle invalid @
|
|
673
|
+
end = _find_closing_brace(rev, start + 1)
|
|
674
|
+
obj = _apply_reflog(repo, ref if first_token != 0 and start == first_token else None, rev[start + 2 : end])
|
|
675
|
+
ref = None
|
|
676
|
+
start = end + 1
|
|
677
|
+
continue
|
|
678
|
+
# END handle reflog
|
|
292
679
|
|
|
293
|
-
|
|
680
|
+
if token == ":":
|
|
681
|
+
return _tree_lookup(obj, rev[start + 1 :])
|
|
682
|
+
# END handle path
|
|
294
683
|
|
|
295
|
-
|
|
296
|
-
if start < lr and rev[start] == "{":
|
|
297
|
-
end = rev.find("}", start)
|
|
298
|
-
if end == -1:
|
|
299
|
-
raise ValueError("Missing closing brace to define type in %s" % rev)
|
|
300
|
-
output_type = rev[start + 1 : end] # Exclude brace.
|
|
301
|
-
|
|
302
|
-
# Handle type.
|
|
303
|
-
if output_type == "commit":
|
|
304
|
-
obj = cast("TagObject", obj)
|
|
305
|
-
if obj and obj.type == "tag":
|
|
306
|
-
obj = deref_tag(obj)
|
|
307
|
-
else:
|
|
308
|
-
# Cannot do anything for non-tags.
|
|
309
|
-
pass
|
|
310
|
-
# END handle tag
|
|
311
|
-
elif output_type == "tree":
|
|
312
|
-
try:
|
|
313
|
-
obj = cast(AnyGitObject, obj)
|
|
314
|
-
obj = to_commit(obj).tree
|
|
315
|
-
except (AttributeError, ValueError):
|
|
316
|
-
pass # Error raised later.
|
|
317
|
-
# END exception handling
|
|
318
|
-
elif output_type in ("", "blob"):
|
|
319
|
-
obj = cast("TagObject", obj)
|
|
320
|
-
if obj and obj.type == "tag":
|
|
321
|
-
obj = deref_tag(obj)
|
|
322
|
-
else:
|
|
323
|
-
# Cannot do anything for non-tags.
|
|
324
|
-
pass
|
|
325
|
-
# END handle tag
|
|
326
|
-
elif token == "@":
|
|
327
|
-
# try single int
|
|
328
|
-
assert ref is not None, "Require Reference to access reflog"
|
|
329
|
-
revlog_index = None
|
|
330
|
-
try:
|
|
331
|
-
# Transform reversed index into the format of our revlog.
|
|
332
|
-
revlog_index = -(int(output_type) + 1)
|
|
333
|
-
except ValueError as e:
|
|
334
|
-
# TODO: Try to parse the other date options, using parse_date maybe.
|
|
335
|
-
raise NotImplementedError("Support for additional @{...} modes not implemented") from e
|
|
336
|
-
# END handle revlog index
|
|
337
|
-
|
|
338
|
-
try:
|
|
339
|
-
entry = ref.log_entry(revlog_index)
|
|
340
|
-
except IndexError as e:
|
|
341
|
-
raise IndexError("Invalid revlog index: %i" % revlog_index) from e
|
|
342
|
-
# END handle index out of bound
|
|
343
|
-
|
|
344
|
-
obj = Object.new_from_sha(repo, hex_to_bin(entry.newhexsha))
|
|
345
|
-
|
|
346
|
-
# Make it pass the following checks.
|
|
347
|
-
output_type = ""
|
|
348
|
-
else:
|
|
349
|
-
raise ValueError("Invalid output type: %s ( in %s )" % (output_type, rev))
|
|
350
|
-
# END handle output type
|
|
351
|
-
|
|
352
|
-
# Empty output types don't require any specific type, its just about
|
|
353
|
-
# dereferencing tags.
|
|
354
|
-
if output_type and obj and obj.type != output_type:
|
|
355
|
-
raise ValueError("Could not accommodate requested object type %r, got %s" % (output_type, obj.type))
|
|
356
|
-
# END verify output type
|
|
684
|
+
start += 1
|
|
357
685
|
|
|
358
|
-
|
|
359
|
-
|
|
686
|
+
if token == "^" and start < lr and rev[start] == "{":
|
|
687
|
+
end = _find_closing_brace(rev, start)
|
|
688
|
+
obj = _peel(obj, rev[start + 1 : end], repo, rev)
|
|
689
|
+
ref = None
|
|
690
|
+
start = end + 1
|
|
360
691
|
continue
|
|
361
692
|
# END parse type
|
|
362
693
|
|
|
363
|
-
# Try to parse a number.
|
|
364
694
|
num = 0
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
# END set default num
|
|
381
|
-
# END number parsing only if non-blob mode
|
|
382
|
-
|
|
383
|
-
parsed_to = start
|
|
384
|
-
# Handle hierarchy walk.
|
|
695
|
+
found_digit = False
|
|
696
|
+
while start < lr:
|
|
697
|
+
if rev[start] in digits:
|
|
698
|
+
num = num * 10 + int(rev[start])
|
|
699
|
+
start += 1
|
|
700
|
+
found_digit = True
|
|
701
|
+
else:
|
|
702
|
+
break
|
|
703
|
+
# END handle number
|
|
704
|
+
# END number parse loop
|
|
705
|
+
|
|
706
|
+
if not found_digit:
|
|
707
|
+
num = 1
|
|
708
|
+
# END set default num
|
|
709
|
+
|
|
385
710
|
try:
|
|
386
|
-
obj = cast(AnyGitObject, obj)
|
|
387
711
|
if token == "~":
|
|
388
712
|
obj = to_commit(obj)
|
|
389
713
|
for _ in range(num):
|
|
@@ -391,15 +715,11 @@ def rev_parse(repo: "Repo", rev: str) -> AnyGitObject:
|
|
|
391
715
|
# END for each history item to walk
|
|
392
716
|
elif token == "^":
|
|
393
717
|
obj = to_commit(obj)
|
|
394
|
-
|
|
395
|
-
|
|
718
|
+
if num == 0:
|
|
719
|
+
pass
|
|
720
|
+
else:
|
|
396
721
|
obj = obj.parents[num - 1]
|
|
397
|
-
|
|
398
|
-
if obj.type != "tree":
|
|
399
|
-
obj = obj.tree
|
|
400
|
-
# END get tree type
|
|
401
|
-
obj = obj[rev[start:]]
|
|
402
|
-
parsed_to = lr
|
|
722
|
+
# END handle parent
|
|
403
723
|
else:
|
|
404
724
|
raise ValueError("Invalid token: %r" % token)
|
|
405
725
|
# END end handle tag
|
|
@@ -410,16 +730,7 @@ def rev_parse(repo: "Repo", rev: str) -> AnyGitObject:
|
|
|
410
730
|
# END exception handling
|
|
411
731
|
# END parse loop
|
|
412
732
|
|
|
413
|
-
# Still no obj? It's probably a simple name.
|
|
414
|
-
if obj is None:
|
|
415
|
-
obj = name_to_object(repo, rev)
|
|
416
|
-
parsed_to = lr
|
|
417
|
-
# END handle simple name
|
|
418
|
-
|
|
419
733
|
if obj is None:
|
|
420
734
|
raise ValueError("Revision specifier could not be parsed: %s" % rev)
|
|
421
735
|
|
|
422
|
-
if parsed_to != lr:
|
|
423
|
-
raise ValueError("Didn't consume complete rev spec %s, consumed part: %s" % (rev, rev[:parsed_to]))
|
|
424
|
-
|
|
425
736
|
return obj
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
git/__init__.py,sha256
|
|
1
|
+
git/__init__.py,sha256=-XcbLWMd0hzWrSVPHoz9wDkN1bWkUznLYN6x5oCYGdQ,8899
|
|
2
2
|
git/cmd.py,sha256=4Ly0NF9kt1JarTD0zpPv7a0Al5I2PaohRMIG0UGKLCw,68559
|
|
3
3
|
git/compat.py,sha256=y1E6y6O2q5r8clSlr8ZNmuIWG9nmHuehQEsVsmBffs8,4526
|
|
4
|
-
git/config.py,sha256=
|
|
4
|
+
git/config.py,sha256=tPAXhoQozL0Mr7x4gtoUlFGUkkT8LtO3Ocqsqz-7ZrU,37412
|
|
5
5
|
git/db.py,sha256=vIW9uWSbqu99zbuU2ZDmOhVOv1UPTmxrnqiCtRHCfjE,2368
|
|
6
6
|
git/diff.py,sha256=xhOeMmFDmXOdpje2vA1mzJpmpBnyX-nrt-mp2RiVR1M,27172
|
|
7
7
|
git/exc.py,sha256=Gc7g1pHpn8OmTse30NHmJVsBJ2CYH8LxaR8y8UA3lIM,7119
|
|
@@ -34,11 +34,11 @@ git/refs/remote.py,sha256=WTlkJtpu895jJxbknDNOwL31tivQY_AHQrvroUoFBX4,2902
|
|
|
34
34
|
git/refs/symbolic.py,sha256=7ScmZHwlqPDKX9bxVC5queSZXUgqEY7_J_Si6gDBSLw,36139
|
|
35
35
|
git/refs/tag.py,sha256=AKrFrMhkNzX8e9x5WmDRtHovbN9zYQghuKoneVW-TJg,5002
|
|
36
36
|
git/repo/__init__.py,sha256=CILSVH36fX_WxVFSjD9o1WF5LgsNedPiJvSngKZqfVU,210
|
|
37
|
-
git/repo/base.py,sha256=
|
|
38
|
-
git/repo/fun.py,sha256=
|
|
39
|
-
gitpython-3.1.
|
|
40
|
-
gitpython-3.1.
|
|
41
|
-
gitpython-3.1.
|
|
42
|
-
gitpython-3.1.
|
|
43
|
-
gitpython-3.1.
|
|
44
|
-
gitpython-3.1.
|
|
37
|
+
git/repo/base.py,sha256=EohoIREtbdpmUqVs7kfUgBIosEzZ29nMgiO9YMuAYDU,61165
|
|
38
|
+
git/repo/fun.py,sha256=PDFNkuTSGGoQc0EEEXjxHw2_KaKYvVjBZ9rrkwH5Q6Y,23462
|
|
39
|
+
gitpython-3.1.50.dist-info/licenses/AUTHORS,sha256=uyjC8YZ5vcAlx2nSJ44J8gE7wjA8nc69_zWYo_HKtVc,2360
|
|
40
|
+
gitpython-3.1.50.dist-info/licenses/LICENSE,sha256=hvyUwyGpr7wRUUcTURuv3tIl8lEA3MD3NQ6CvCMbi-s,1503
|
|
41
|
+
gitpython-3.1.50.dist-info/METADATA,sha256=3LhbhjxZJPBAeX0855vAyV3Eod7P7L0GlQB4qw6mNzY,14187
|
|
42
|
+
gitpython-3.1.50.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
43
|
+
gitpython-3.1.50.dist-info/top_level.txt,sha256=0hzDuIp8obv624V3GmbqsagBWkk8ohtGU-Bc1PmTT0o,4
|
|
44
|
+
gitpython-3.1.50.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|