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 CHANGED
@@ -86,7 +86,7 @@ __all__ = [
86
86
  "to_hex_sha",
87
87
  ]
88
88
 
89
- __version__ = '3.1.48'
89
+ __version__ = '3.1.50'
90
90
 
91
91
  from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Tuple, Union
92
92
 
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
- self.set(section, option, self._value_to_string(value))
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, self._value_to_string(value))
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, TagObject
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
- # Colon search mode
259
- raise NotImplementedError("commit by message search (regex)")
260
- # END handle search
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
- while start < lr:
269
- if rev[start] not in "^~:@":
270
- start += 1
271
- continue
272
- # END handle start
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 obj is None:
277
- # token is a rev name.
278
- if start == 0:
279
- ref = repo.head.ref
280
- else:
281
- if token == "@":
282
- ref = cast("Reference", name_to_object(repo, rev[:start], return_ref=True))
283
- else:
284
- obj = name_to_object(repo, rev[:start])
285
- # END handle token
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
- start += 1
680
+ if token == ":":
681
+ return _tree_lookup(obj, rev[start + 1 :])
682
+ # END handle path
294
683
 
295
- # Try to parse {type}.
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
- start = end + 1 # Skip brace.
359
- parsed_to = start
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
- if token != ":":
366
- found_digit = False
367
- while start < lr:
368
- if rev[start] in digits:
369
- num = num * 10 + int(rev[start])
370
- start += 1
371
- found_digit = True
372
- else:
373
- break
374
- # END handle number
375
- # END number parse loop
376
-
377
- # No explicit number given, 1 is the default. It could be 0 though.
378
- if not found_digit:
379
- num = 1
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
- # Must be n'th parent.
395
- if num:
718
+ if num == 0:
719
+ pass
720
+ else:
396
721
  obj = obj.parents[num - 1]
397
- elif token == ":":
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GitPython
3
- Version: 3.1.48
3
+ Version: 3.1.50
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
@@ -1,7 +1,7 @@
1
- git/__init__.py,sha256=n7ZUyB_I0GQubucxnwYUAvFnLL3hiIF-G36z4clqUL4,8899
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=vH4cb4n4HR_691Tex-ac3z-nLJpQqWxUsff1vqWZ-D8,35867
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=XI6FNYbd2HwrayazVPprFEHTMS6CHkzRZPjkYhw_elM,60220
38
- git/repo/fun.py,sha256=sjzJH8jy18nacsJjKKIqRBKadhhhxiyjmZKWXDErNnw,13787
39
- gitpython-3.1.48.dist-info/licenses/AUTHORS,sha256=uyjC8YZ5vcAlx2nSJ44J8gE7wjA8nc69_zWYo_HKtVc,2360
40
- gitpython-3.1.48.dist-info/licenses/LICENSE,sha256=hvyUwyGpr7wRUUcTURuv3tIl8lEA3MD3NQ6CvCMbi-s,1503
41
- gitpython-3.1.48.dist-info/METADATA,sha256=qvywsw3FCGzL89bcxLt68LzVTKZRbiWdgdqbIpsPttM,14187
42
- gitpython-3.1.48.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
43
- gitpython-3.1.48.dist-info/top_level.txt,sha256=0hzDuIp8obv624V3GmbqsagBWkk8ohtGU-Bc1PmTT0o,4
44
- gitpython-3.1.48.dist-info/RECORD,,
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,,