Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ae13c6d

Browse filesBrowse files
committed
Add types to refs/log.py
1 parent 1dd4596 commit ae13c6d
Copy full SHA for ae13c6d

1 file changed

+86-54Lines changed: 86 additions & 54 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎git/refs/log.py‎

Copy file name to clipboardExpand all lines: git/refs/log.py
+86-54Lines changed: 86 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
2+
from mmap import mmap
13
import re
2-
import time
4+
import time as _time
35

46
from git.compat import defenc
57
from git.objects.util import (
@@ -20,20 +22,33 @@
2022
import os.path as osp
2123

2224

25+
# typing ------------------------------------------------------------------
26+
27+
from typing import Iterator, List, Tuple, Union, TYPE_CHECKING
28+
29+
from git.types import PathLike
30+
31+
if TYPE_CHECKING:
32+
from git.refs import SymbolicReference
33+
from io import BytesIO
34+
from git.config import GitConfigParser, SectionConstraint # NOQA
35+
36+
# ------------------------------------------------------------------------------
37+
2338
__all__ = ["RefLog", "RefLogEntry"]
2439

2540

26-
class RefLogEntry(tuple):
41+
class RefLogEntry(Tuple[str, str, Actor, Tuple[int, int], str]):
2742

2843
"""Named tuple allowing easy access to the revlog data fields"""
2944
_re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$')
3045
__slots__ = ()
3146

32-
def __repr__(self):
47+
def __repr__(self) -> str:
3348
"""Representation of ourselves in git reflog format"""
3449
return self.format()
3550

36-
def format(self):
51+
def format(self) -> str:
3752
""":return: a string suitable to be placed in a reflog file"""
3853
act = self.actor
3954
time = self.time
@@ -46,35 +61,36 @@ def format(self):
4661
self.message)
4762

4863
@property
49-
def oldhexsha(self):
64+
def oldhexsha(self) -> str:
5065
"""The hexsha to the commit the ref pointed to before the change"""
5166
return self[0]
5267

5368
@property
54-
def newhexsha(self):
69+
def newhexsha(self) -> str:
5570
"""The hexsha to the commit the ref now points to, after the change"""
5671
return self[1]
5772

5873
@property
59-
def actor(self):
74+
def actor(self) -> Actor:
6075
"""Actor instance, providing access"""
6176
return self[2]
6277

6378
@property
64-
def time(self):
79+
def time(self) -> Tuple[int, int]:
6580
"""time as tuple:
6681
6782
* [0] = int(time)
6883
* [1] = int(timezone_offset) in time.altzone format """
6984
return self[3]
7085

7186
@property
72-
def message(self):
87+
def message(self) -> str:
7388
"""Message describing the operation that acted on the reference"""
7489
return self[4]
7590

7691
@classmethod
77-
def new(cls, oldhexsha, newhexsha, actor, time, tz_offset, message): # skipcq: PYL-W0621
92+
def new(cls, oldhexsha: str, newhexsha: str, actor: Actor, time: int, tz_offset: int, message: str
93+
) -> 'RefLogEntry': # skipcq: PYL-W0621
7894
""":return: New instance of a RefLogEntry"""
7995
if not isinstance(actor, Actor):
8096
raise ValueError("Need actor instance, got %s" % actor)
@@ -111,14 +127,15 @@ def from_line(cls, line: bytes) -> 'RefLogEntry':
111127
# END handle missing end brace
112128

113129
actor = Actor._from_string(info[82:email_end + 1])
114-
time, tz_offset = parse_date(info[email_end + 2:]) # skipcq: PYL-W0621
130+
time, tz_offset = parse_date(
131+
info[email_end + 2:]) # skipcq: PYL-W0621
115132

116133
return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), msg))
117134

118135

119-
class RefLog(list, Serializable):
136+
class RefLog(List[RefLogEntry], Serializable):
120137

121-
"""A reflog contains reflog entries, each of which defines a certain state
138+
"""A reflog contains RefLogEntrys, each of which defines a certain state
122139
of the head in question. Custom query methods allow to retrieve log entries
123140
by date or by other criteria.
124141
@@ -127,11 +144,11 @@ class RefLog(list, Serializable):
127144

128145
__slots__ = ('_path', )
129146

130-
def __new__(cls, filepath=None):
147+
def __new__(cls, filepath: Union[PathLike, None] = None) -> 'RefLog':
131148
inst = super(RefLog, cls).__new__(cls)
132149
return inst
133150

134-
def __init__(self, filepath=None):
151+
def __init__(self, filepath: Union[PathLike, None] = None):
135152
"""Initialize this instance with an optional filepath, from which we will
136153
initialize our data. The path is also used to write changes back using
137154
the write() method"""
@@ -142,7 +159,8 @@ def __init__(self, filepath=None):
142159

143160
def _read_from_file(self):
144161
try:
145-
fmap = file_contents_ro_filepath(self._path, stream=True, allow_mmap=True)
162+
fmap = file_contents_ro_filepath(
163+
self._path, stream=True, allow_mmap=True)
146164
except OSError:
147165
# it is possible and allowed that the file doesn't exist !
148166
return
@@ -154,10 +172,10 @@ def _read_from_file(self):
154172
fmap.close()
155173
# END handle closing of handle
156174

157-
#{ Interface
175+
# { Interface
158176

159177
@classmethod
160-
def from_file(cls, filepath):
178+
def from_file(cls, filepath: PathLike) -> 'RefLog':
161179
"""
162180
:return: a new RefLog instance containing all entries from the reflog
163181
at the given filepath
@@ -166,7 +184,7 @@ def from_file(cls, filepath):
166184
return cls(filepath)
167185

168186
@classmethod
169-
def path(cls, ref):
187+
def path(cls, ref: 'SymbolicReference') -> str:
170188
"""
171189
:return: string to absolute path at which the reflog of the given ref
172190
instance would be found. The path is not guaranteed to point to a valid
@@ -175,28 +193,34 @@ def path(cls, ref):
175193
return osp.join(ref.repo.git_dir, "logs", to_native_path(ref.path))
176194

177195
@classmethod
178-
def iter_entries(cls, stream):
196+
def iter_entries(cls, stream: Union[str, 'BytesIO', mmap]) -> Iterator[RefLogEntry]:
179197
"""
180198
:return: Iterator yielding RefLogEntry instances, one for each line read
181199
sfrom the given stream.
182200
:param stream: file-like object containing the revlog in its native format
183-
or basestring instance pointing to a file to read"""
201+
or string instance pointing to a file to read"""
184202
new_entry = RefLogEntry.from_line
185203
if isinstance(stream, str):
186-
stream = file_contents_ro_filepath(stream)
204+
# default args return mmap on py>3
205+
_stream = file_contents_ro_filepath(stream)
206+
assert isinstance(_stream, mmap)
207+
else:
208+
_stream = stream
187209
# END handle stream type
188210
while True:
189-
line = stream.readline()
211+
line = _stream.readline()
190212
if not line:
191213
return
192214
yield new_entry(line.strip())
193215
# END endless loop
194-
stream.close()
195216

196217
@classmethod
197-
def entry_at(cls, filepath, index):
198-
""":return: RefLogEntry at the given index
218+
def entry_at(cls, filepath: PathLike, index: int) -> 'RefLogEntry':
219+
"""
220+
:return: RefLogEntry at the given index
221+
199222
:param filepath: full path to the index file from which to read the entry
223+
200224
:param index: python list compatible index, i.e. it may be negative to
201225
specify an entry counted from the end of the list
202226
@@ -210,21 +234,19 @@ def entry_at(cls, filepath, index):
210234
if index < 0:
211235
return RefLogEntry.from_line(fp.readlines()[index].strip())
212236
# read until index is reached
237+
213238
for i in range(index + 1):
214239
line = fp.readline()
215240
if not line:
216-
break
241+
raise IndexError(
242+
f"Index file ended at line {i+1}, before given index was reached")
217243
# END abort on eof
218244
# END handle runup
219245

220-
if i != index or not line: # skipcq:PYL-W0631
221-
raise IndexError
222-
# END handle exception
223-
224246
return RefLogEntry.from_line(line.strip())
225247
# END handle index
226248

227-
def to_file(self, filepath):
249+
def to_file(self, filepath: PathLike) -> None:
228250
"""Write the contents of the reflog instance to a file at the given filepath.
229251
:param filepath: path to file, parent directories are assumed to exist"""
230252
lfd = LockedFD(filepath)
@@ -241,65 +263,75 @@ def to_file(self, filepath):
241263
# END handle change
242264

243265
@classmethod
244-
def append_entry(cls, config_reader, filepath, oldbinsha, newbinsha, message):
266+
def append_entry(cls, config_reader: Union[Actor, 'GitConfigParser', 'SectionConstraint', None],
267+
filepath: PathLike, oldbinsha: bytes, newbinsha: bytes, message: str,
268+
write: bool = True) -> 'RefLogEntry':
245269
"""Append a new log entry to the revlog at filepath.
246270
247271
:param config_reader: configuration reader of the repository - used to obtain
248-
user information. May also be an Actor instance identifying the committer directly.
249-
May also be None
272+
user information. May also be an Actor instance identifying the committer directly or None.
250273
:param filepath: full path to the log file
251274
:param oldbinsha: binary sha of the previous commit
252275
:param newbinsha: binary sha of the current commit
253276
:param message: message describing the change to the reference
254277
:param write: If True, the changes will be written right away. Otherwise
255278
the change will not be written
279+
256280
:return: RefLogEntry objects which was appended to the log
281+
257282
:note: As we are append-only, concurrent access is not a problem as we
258283
do not interfere with readers."""
284+
259285
if len(oldbinsha) != 20 or len(newbinsha) != 20:
260286
raise ValueError("Shas need to be given in binary format")
261287
# END handle sha type
262288
assure_directory_exists(filepath, is_file=True)
263289
first_line = message.split('\n')[0]
264-
committer = isinstance(config_reader, Actor) and config_reader or Actor.committer(config_reader)
290+
if isinstance(config_reader, Actor):
291+
committer = config_reader # mypy thinks this is Actor | Gitconfigparser, but why?
292+
else:
293+
committer = Actor.committer(config_reader)
265294
entry = RefLogEntry((
266295
bin_to_hex(oldbinsha).decode('ascii'),
267296
bin_to_hex(newbinsha).decode('ascii'),
268-
committer, (int(time.time()), time.altzone), first_line
297+
committer, (int(_time.time()), _time.altzone), first_line
269298
))
270299

271-
lf = LockFile(filepath)
272-
lf._obtain_lock_or_raise()
273-
fd = open(filepath, 'ab')
274-
try:
275-
fd.write(entry.format().encode(defenc))
276-
finally:
277-
fd.close()
278-
lf._release_lock()
279-
# END handle write operation
280-
300+
if write:
301+
lf = LockFile(filepath)
302+
lf._obtain_lock_or_raise()
303+
fd = open(filepath, 'ab')
304+
try:
305+
fd.write(entry.format().encode(defenc))
306+
finally:
307+
fd.close()
308+
lf._release_lock()
309+
# END handle write operation
281310
return entry
282311

283-
def write(self):
312+
def write(self) -> 'RefLog':
284313
"""Write this instance's data to the file we are originating from
285314
:return: self"""
286315
if self._path is None:
287-
raise ValueError("Instance was not initialized with a path, use to_file(...) instead")
316+
raise ValueError(
317+
"Instance was not initialized with a path, use to_file(...) instead")
288318
# END assert path
289319
self.to_file(self._path)
290320
return self
291321

292-
#} END interface
322+
# } END interface
293323

294-
#{ Serializable Interface
295-
def _serialize(self, stream):
324+
# { Serializable Interface
325+
def _serialize(self, stream: 'BytesIO') -> 'RefLog':
296326
write = stream.write
297327

298328
# write all entries
299329
for e in self:
300330
write(e.format().encode(defenc))
301331
# END for each entry
332+
return self
302333

303-
def _deserialize(self, stream):
334+
def _deserialize(self, stream: 'BytesIO') -> 'RefLog':
304335
self.extend(self.iter_entries(stream))
305-
#} END serializable interface
336+
# } END serializable interface
337+
return self

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.