GitPython 3.1.47__py3-none-any.whl → 3.1.49__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.47'
89
+ __version__ = '3.1.49'
90
90
 
91
91
  from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Tuple, Union
92
92
 
git/config.py CHANGED
@@ -882,6 +882,24 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
882
882
  return str(value)
883
883
  return force_text(value)
884
884
 
885
+ def _value_to_string_safe(self, value: Union[str, bytes, int, float, bool]) -> str:
886
+ value_str = self._value_to_string(value)
887
+ if re.search(r"[\r\n\x00]", value_str):
888
+ raise ValueError("Git config values must not contain CR, LF, or NUL")
889
+ return value_str
890
+
891
+ @needs_values
892
+ @set_dirty_and_flush_changes
893
+ def set(
894
+ self,
895
+ section: str,
896
+ option: str,
897
+ value: Union[str, bytes, int, float, bool, None] = None,
898
+ ) -> None:
899
+ if value is not None:
900
+ value = self._value_to_string_safe(value)
901
+ return super().set(section, option, value)
902
+
885
903
  @needs_values
886
904
  @set_dirty_and_flush_changes
887
905
  def set_value(self, section: str, option: str, value: Union[str, bytes, int, float, bool]) -> "GitConfigParser":
@@ -902,9 +920,10 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
902
920
  :return:
903
921
  This instance
904
922
  """
923
+ value_str = self._value_to_string_safe(value)
905
924
  if not self.has_section(section):
906
925
  self.add_section(section)
907
- self.set(section, option, self._value_to_string(value))
926
+ super().set(section, option, value_str)
908
927
  return self
909
928
 
910
929
  @needs_values
@@ -929,9 +948,10 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
929
948
  :return:
930
949
  This instance
931
950
  """
951
+ value_str = self._value_to_string_safe(value)
932
952
  if not self.has_section(section):
933
953
  self.add_section(section)
934
- self._sections[section].add(option, self._value_to_string(value))
954
+ self._sections[section].add(option, value_str)
935
955
  return self
936
956
 
937
957
  def rename_section(self, section: str, new_name: str) -> "GitConfigParser":
git/refs/log.py CHANGED
@@ -4,7 +4,6 @@
4
4
  __all__ = ["RefLog", "RefLogEntry"]
5
5
 
6
6
  from mmap import mmap
7
- import os.path as osp
8
7
  import re
9
8
  import time as _time
10
9
 
@@ -212,8 +211,11 @@ class RefLog(List[RefLogEntry], Serializable):
212
211
 
213
212
  :param ref:
214
213
  :class:`~git.refs.symbolic.SymbolicReference` instance
214
+
215
+ :raise ValueError:
216
+ If `ref.path` is invalid or escapes the repository's reflog directory.
215
217
  """
216
- return osp.join(ref.repo.git_dir, "logs", to_native_path(ref.path))
218
+ return to_native_path(ref._get_validated_reflog_path(ref.repo, ref.path))
217
219
 
218
220
  @classmethod
219
221
  def iter_entries(cls, stream: Union[str, "BytesIO", mmap]) -> Iterator[RefLogEntry]:
git/refs/remote.py CHANGED
@@ -58,17 +58,20 @@ class RemoteReference(Head):
58
58
  `kwargs` are given for comparability with the base class method as we
59
59
  should not narrow the signature.
60
60
  """
61
+ for ref in refs:
62
+ cls._check_ref_name_valid(ref.path)
63
+
61
64
  repo.git.branch("-d", "-r", *refs)
62
65
  # The official deletion method will ignore remote symbolic refs - these are
63
66
  # generally ignored in the refs/ folder. We don't though and delete remainders
64
67
  # manually.
65
68
  for ref in refs:
66
69
  try:
67
- os.remove(os.path.join(repo.common_dir, ref.path))
70
+ os.remove(cls._get_validated_path(repo.common_dir, ref.path))
68
71
  except OSError:
69
72
  pass
70
73
  try:
71
- os.remove(os.path.join(repo.git_dir, ref.path))
74
+ os.remove(cls._get_validated_path(repo.git_dir, ref.path))
72
75
  except OSError:
73
76
  pass
74
77
  # END for each ref
git/refs/symbolic.py CHANGED
@@ -110,6 +110,32 @@ class SymbolicReference:
110
110
  def abspath(self) -> PathLike:
111
111
  return join_path_native(_git_dir(self.repo, self.path), self.path)
112
112
 
113
+ @staticmethod
114
+ def _get_validated_path(base: PathLike, path: PathLike) -> str:
115
+ path = os.fspath(path)
116
+ base_path = os.path.realpath(os.fspath(base))
117
+ abs_path = os.path.realpath(os.path.join(base_path, path))
118
+ try:
119
+ common_path = os.path.commonpath([base_path, abs_path])
120
+ except ValueError as e:
121
+ raise ValueError("Reference path %r escapes the repository" % path) from e
122
+ if os.path.normcase(common_path) != os.path.normcase(base_path):
123
+ raise ValueError("Reference path %r escapes the repository" % path)
124
+ return abs_path
125
+
126
+ @classmethod
127
+ def _get_validated_ref_path(cls, repo: "Repo", path: PathLike) -> str:
128
+ """Return the absolute filesystem path for a ref after validating it."""
129
+ cls._check_ref_name_valid(path)
130
+ ref_path = os.fspath(path)
131
+ return cls._get_validated_path(_git_dir(repo, ref_path), ref_path)
132
+
133
+ @classmethod
134
+ def _get_validated_reflog_path(cls, repo: "Repo", path: PathLike) -> str:
135
+ """Return the absolute filesystem path for a reflog after validating it."""
136
+ cls._check_ref_name_valid(path)
137
+ return cls._get_validated_path(os.path.join(repo.git_dir, "logs"), path)
138
+
113
139
  @classmethod
114
140
  def _get_packed_refs_path(cls, repo: "Repo") -> str:
115
141
  return os.path.join(repo.common_dir, "packed-refs")
@@ -485,7 +511,7 @@ class SymbolicReference:
485
511
  # END handle non-existing
486
512
  # END retrieve old hexsha
487
513
 
488
- fpath = self.abspath
514
+ fpath = self._get_validated_ref_path(self.repo, self.path)
489
515
  assure_directory_exists(fpath, is_file=True)
490
516
 
491
517
  lfd = LockedFD(fpath)
@@ -632,7 +658,7 @@ class SymbolicReference:
632
658
  Alternatively the symbolic reference to be deleted.
633
659
  """
634
660
  full_ref_path = cls.to_full_path(path)
635
- abs_path = os.path.join(repo.common_dir, full_ref_path)
661
+ abs_path = cls._get_validated_ref_path(repo, full_ref_path)
636
662
  if os.path.exists(abs_path):
637
663
  os.remove(abs_path)
638
664
  else:
@@ -695,9 +721,8 @@ class SymbolicReference:
695
721
  symbolic reference. Otherwise it will be resolved to the corresponding object
696
722
  and a detached symbolic reference will be created instead.
697
723
  """
698
- git_dir = _git_dir(repo, path)
699
724
  full_ref_path = cls.to_full_path(path)
700
- abs_ref_path = os.path.join(git_dir, full_ref_path)
725
+ abs_ref_path = cls._get_validated_ref_path(repo, full_ref_path)
701
726
 
702
727
  # Figure out target data.
703
728
  target = reference
@@ -789,8 +814,8 @@ class SymbolicReference:
789
814
  if self.path == new_path:
790
815
  return self
791
816
 
792
- new_abs_path = os.path.join(_git_dir(self.repo, new_path), new_path)
793
- cur_abs_path = os.path.join(_git_dir(self.repo, self.path), self.path)
817
+ new_abs_path = self._get_validated_ref_path(self.repo, new_path)
818
+ cur_abs_path = self._get_validated_ref_path(self.repo, self.path)
794
819
  if os.path.isfile(new_abs_path):
795
820
  if not force:
796
821
  # If they point to the same file, it's not an error.
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
git/util.py CHANGED
@@ -289,7 +289,7 @@ def join_path(a: PathLike, *p: PathLike) -> PathLike:
289
289
 
290
290
  if sys.platform == "win32":
291
291
 
292
- def to_native_path_windows(path: PathLike) -> PathLike:
292
+ def to_native_path_windows(path: PathLike) -> str:
293
293
  path = os.fspath(path)
294
294
  return path.replace("/", "\\")
295
295
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GitPython
3
- Version: 3.1.47
3
+ Version: 3.1.49
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,14 +1,14 @@
1
- git/__init__.py,sha256=49UjU1OnoLWwxOzoGLfyGbV0zNwKjN47YU5i4u3ku48,8899
1
+ git/__init__.py,sha256=RAj8jt-mAbm_gxzftocy0RK5-zDBelW3ZND77e85dHk,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=FPGd0Y3jTFoPzE1GgbJ9L_yBxA0OADZr44-xSfHA874,36570
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
8
8
  git/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  git/remote.py,sha256=pYn9dAlz-QwvNMWXD1M57pMPQitthOM86qTRK_cpTqU,46786
10
10
  git/types.py,sha256=d3oDduUGjfoqffcUL_GOD-3qroF8m3r9ai6Xi9_FraM,10279
11
- git/util.py,sha256=T1M3Nv-AbKSeZ3if33bpJFX450EeoGK67tr3SrEkjlU,44057
11
+ git/util.py,sha256=cCbGpBpnQq1MtW_REe8ApsAGTzp9JgVCMUN8KFgnx14,44052
12
12
  git/index/__init__.py,sha256=i-Nqb8Lufp9aFbmxpQBORmmQnjEVVM1Pn58fsQkyGgQ,406
13
13
  git/index/base.py,sha256=PdOmwjSjVXdYJF31y1853QdYTV8qUVAgAK0YZw3eyEI,61233
14
14
  git/index/fun.py,sha256=v0WnKQxaAffMZxXXC7pQOiMvRRJfODhUNKDMruxKrN4,17110
@@ -28,17 +28,17 @@ git/objects/submodule/root.py,sha256=5eTtYNHasqdPq6q0oDCPr7IaO6uAHL3b4DxMoiO2LhE
28
28
  git/objects/submodule/util.py,sha256=sQqAYaiSJdFkZa9NlAuK_wTsMNiS-kkQnQjvIoJtc_o,3509
29
29
  git/refs/__init__.py,sha256=DWlJNnsx-4jM_E-VycbP-FZUdn6iWhjnH_uZ_pZXBro,509
30
30
  git/refs/head.py,sha256=59MUMVu9phmE_ImkmNw0sDDSCc75s7TxkQj_cKzRhbo,10397
31
- git/refs/log.py,sha256=ooqlw_mn14FSQGvZXDC0Tt4MQG-4Hob8T92SDH9ZzfY,12517
31
+ git/refs/log.py,sha256=X88JTqjwX1ShvHr-NHNAZj0qLiHbVoF-0v3VymZDwoM,12612
32
32
  git/refs/reference.py,sha256=uzrvNTZ5fg7A0Aq4VC3KLSMbksgYUf4KvmHau0tFYIM,5646
33
- git/refs/remote.py,sha256=WwqV9T7BbYf3F_WZNUQivu9xktIIKGklCjDpwQrhD-A,2806
34
- git/refs/symbolic.py,sha256=seRKwWw7eegQyGoObE-PuuVpSvNyQRUAXqFRq0wI6yA,34892
33
+ git/refs/remote.py,sha256=WTlkJtpu895jJxbknDNOwL31tivQY_AHQrvroUoFBX4,2902
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
37
  git/repo/base.py,sha256=XI6FNYbd2HwrayazVPprFEHTMS6CHkzRZPjkYhw_elM,60220
38
- git/repo/fun.py,sha256=sjzJH8jy18nacsJjKKIqRBKadhhhxiyjmZKWXDErNnw,13787
39
- gitpython-3.1.47.dist-info/licenses/AUTHORS,sha256=uyjC8YZ5vcAlx2nSJ44J8gE7wjA8nc69_zWYo_HKtVc,2360
40
- gitpython-3.1.47.dist-info/licenses/LICENSE,sha256=hvyUwyGpr7wRUUcTURuv3tIl8lEA3MD3NQ6CvCMbi-s,1503
41
- gitpython-3.1.47.dist-info/METADATA,sha256=nopkhZXhd8oDQNXp-Ebzxz86dwqi15j1suk3BH8IoRc,14187
42
- gitpython-3.1.47.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
43
- gitpython-3.1.47.dist-info/top_level.txt,sha256=0hzDuIp8obv624V3GmbqsagBWkk8ohtGU-Bc1PmTT0o,4
44
- gitpython-3.1.47.dist-info/RECORD,,
38
+ git/repo/fun.py,sha256=PDFNkuTSGGoQc0EEEXjxHw2_KaKYvVjBZ9rrkwH5Q6Y,23462
39
+ gitpython-3.1.49.dist-info/licenses/AUTHORS,sha256=uyjC8YZ5vcAlx2nSJ44J8gE7wjA8nc69_zWYo_HKtVc,2360
40
+ gitpython-3.1.49.dist-info/licenses/LICENSE,sha256=hvyUwyGpr7wRUUcTURuv3tIl8lEA3MD3NQ6CvCMbi-s,1503
41
+ gitpython-3.1.49.dist-info/METADATA,sha256=PX5GVf1iClsOdFPHGLrlxU5QPfZ--z5GjS_nNhwsP1Q,14187
42
+ gitpython-3.1.49.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
43
+ gitpython-3.1.49.dist-info/top_level.txt,sha256=0hzDuIp8obv624V3GmbqsagBWkk8ohtGU-Bc1PmTT0o,4
44
+ gitpython-3.1.49.dist-info/RECORD,,