Repository navigation
Expand file tree
/
Copy pathlog.py
More file actions
112 lines (97 loc) · 4.18 KB
/
Copy pathlog.py
File metadata and controls
112 lines (97 loc) · 4.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# This module is part of GitPython and is released under the
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
"""Repository reflogs exposed through Git's commit reflog view."""
__all__ = ["RefLog", "RefLogEntry"]
import re
from typing import List, NamedTuple, TYPE_CHECKING, Tuple
from git.exc import GitCommandError
from git.objects.util import parse_actor_and_date, utctz_to_altz
from git.util import Actor
if TYPE_CHECKING:
from git.refs import SymbolicReference
class RefLogEntry(NamedTuple):
"""A commit reflog entry returned by Git.
Git exposes the new object ID, reflog identity, date, and message. It does
not expose raw old object IDs, so these entries do not have ``oldhexsha``.
"""
newhexsha: str
actor: Actor
time: Tuple[int, int]
message: str
class RefLog(List[RefLogEntry]):
"""A snapshot of a reference's commit reflog, oldest entry first.
Git omits entries whose new object is unavailable or is not a commit.
The snapshot is associated with a reference, not an on-disk reflog file;
file serialization and rewriting arbitrary reflog history are unsupported.
"""
def __init__(self, ref: "SymbolicReference") -> None:
ref._get_validated_ref_path(ref.repo, ref.path)
self.ref = ref
try:
ref.repo.git._call_process_safe("reflog", "exists", "--", ref.path)
except GitCommandError as exc:
if exc.status == 1:
super().__init__()
return
raise
output = ref.repo.git._call_process_safe(
"reflog",
"show",
"--format=%H%x00%gn%x00%ge%x00%gD%x00%gs",
"--date=raw",
"-z",
"--no-abbrev",
"--no-decorate",
"--no-notes",
"--no-color",
ref.path,
"--",
)
fields = output.split("\0")
if fields[-1] == "":
fields.pop()
if len(fields) % 5:
raise ValueError("Invalid reflog output from Git")
entries = []
for i in range(0, len(fields), 5):
oid, name, email, selector, message = fields[i : i + 5]
timestamp, offset = selector.rsplit("@{", 1)[1][:-1].split()
entries.append(RefLogEntry(oid, Actor(name, email), (int(timestamp), utctz_to_altz(offset)), message))
super().__init__(reversed(entries))
@classmethod
def append_entry(
cls,
ref: "SymbolicReference",
oldbinsha: bytes,
newbinsha: bytes,
message: str,
) -> RefLogEntry:
"""Append a repository reflog entry without moving the reference.
The committer identity/date follow Git's environment and configuration.
Null object IDs are accepted; non-null IDs must identify existing objects.
"""
ref._get_validated_ref_path(ref.repo, ref.path)
if not isinstance(oldbinsha, bytes) or not isinstance(newbinsha, bytes):
raise ValueError("Object IDs must be binary")
if len(oldbinsha) != ref.repo._oid_size or len(newbinsha) != ref.repo._oid_size:
raise ValueError("Object IDs must match the repository's object format")
if "\0" in message:
raise ValueError("Reflog messages must not contain NUL")
# Pin Git's own identity/date so the return value describes this write,
# even when another process appends immediately afterwards.
identity = ref.repo.git._call_process_safe("var", "GIT_COMMITTER_IDENT")
actor, timestamp, offset = parse_actor_and_date("committer " + identity)
git_date = identity.rsplit("> ", 1)[1]
# Git's reflog messages are single lines with collapsed ASCII whitespace.
message = re.sub(r"[ \t\r\n\v\f]+", " ", message.split("\n", 1)[0]).strip(" ")
ref.repo.git._call_process_safe(
"reflog",
"write",
"--",
ref.path,
oldbinsha.hex(),
newbinsha.hex(),
message,
env={"GIT_COMMITTER_NAME": actor.name, "GIT_COMMITTER_EMAIL": actor.email, "GIT_COMMITTER_DATE": git_date},
)
return RefLogEntry(newbinsha.hex(), actor, (timestamp, offset), message)