| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,7 @@ | |||
| 1 | + | ||
| 2 | + from mmap import mmap | ||
| 1 | 3 | import re | |
| 2 | - import time | ||
| 4 | + import time as _time | ||
| 3 | 5 | ||
| 4 | 6 | from git.compat import defenc | |
| 5 | 7 | from git.objects.util import ( | |
@@ -20,20 +22,33 @@ | |||
| 20 | 22 | import os.path as osp | |
| 21 | 23 | ||
| 22 | 24 | ||
| 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 | + | ||
| 23 | 38 | __all__ = ["RefLog", "RefLogEntry"] | |
| 24 | 39 | ||
| 25 | 40 | ||
| 26 | - class RefLogEntry(tuple): | ||
| 41 | + class RefLogEntry(Tuple[str, str, Actor, Tuple[int, int], str]): | ||
| 27 | 42 | ||
| 28 | 43 | """Named tuple allowing easy access to the revlog data fields""" | |
| 29 | 44 | _re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$') | |
| 30 | 45 | __slots__ = () | |
| 31 | 46 | ||
| 32 | - def __repr__(self): | ||
| 47 | + def __repr__(self) -> str: | ||
| 33 | 48 | """Representation of ourselves in git reflog format""" | |
| 34 | 49 | return self.format() | |
| 35 | 50 | ||
| 36 | - def format(self): | ||
| 51 | + def format(self) -> str: | ||
| 37 | 52 | """:return: a string suitable to be placed in a reflog file""" | |
| 38 | 53 | act = self.actor | |
| 39 | 54 | time = self.time | |
@@ -46,35 +61,36 @@ def format(self): | |||
| 46 | 61 | self.message) | |
| 47 | 62 | ||
| 48 | 63 | @property | |
| 49 | - def oldhexsha(self): | ||
| 64 | + def oldhexsha(self) -> str: | ||
| 50 | 65 | """The hexsha to the commit the ref pointed to before the change""" | |
| 51 | 66 | return self[0] | |
| 52 | 67 | ||
| 53 | 68 | @property | |
| 54 | - def newhexsha(self): | ||
| 69 | + def newhexsha(self) -> str: | ||
| 55 | 70 | """The hexsha to the commit the ref now points to, after the change""" | |
| 56 | 71 | return self[1] | |
| 57 | 72 | ||
| 58 | 73 | @property | |
| 59 | - def actor(self): | ||
| 74 | + def actor(self) -> Actor: | ||
| 60 | 75 | """Actor instance, providing access""" | |
| 61 | 76 | return self[2] | |
| 62 | 77 | ||
| 63 | 78 | @property | |
| 64 | - def time(self): | ||
| 79 | + def time(self) -> Tuple[int, int]: | ||
| 65 | 80 | """time as tuple: | |
| 66 | 81 | ||
| 67 | 82 | * [0] = int(time) | |
| 68 | 83 | * [1] = int(timezone_offset) in time.altzone format """ | |
| 69 | 84 | return self[3] | |
| 70 | 85 | ||
| 71 | 86 | @property | |
| 72 | - def message(self): | ||
| 87 | + def message(self) -> str: | ||
| 73 | 88 | """Message describing the operation that acted on the reference""" | |
| 74 | 89 | return self[4] | |
| 75 | 90 | ||
| 76 | 91 | @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 | ||
| 78 | 94 | """:return: New instance of a RefLogEntry""" | |
| 79 | 95 | if not isinstance(actor, Actor): | |
| 80 | 96 | raise ValueError("Need actor instance, got %s" % actor) | |
@@ -111,14 +127,15 @@ def from_line(cls, line: bytes) -> 'RefLogEntry': | |||
| 111 | 127 | # END handle missing end brace | |
| 112 | 128 | ||
| 113 | 129 | 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 | ||
| 115 | 132 | ||
| 116 | 133 | return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), msg)) | |
| 117 | 134 | ||
| 118 | 135 | ||
| 119 | - class RefLog(list, Serializable): | ||
| 136 | + class RefLog(List[RefLogEntry], Serializable): | ||
| 120 | 137 | ||
| 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 | ||
| 122 | 139 | of the head in question. Custom query methods allow to retrieve log entries | |
| 123 | 140 | by date or by other criteria. | |
| 124 | 141 | ||
@@ -127,11 +144,11 @@ class RefLog(list, Serializable): | |||
| 127 | 144 | ||
| 128 | 145 | __slots__ = ('_path', ) | |
| 129 | 146 | ||
| 130 | - def __new__(cls, filepath=None): | ||
| 147 | + def __new__(cls, filepath: Union[PathLike, None] = None) -> 'RefLog': | ||
| 131 | 148 | inst = super(RefLog, cls).__new__(cls) | |
| 132 | 149 | return inst | |
| 133 | 150 | ||
| 134 | - def __init__(self, filepath=None): | ||
| 151 | + def __init__(self, filepath: Union[PathLike, None] = None): | ||
| 135 | 152 | """Initialize this instance with an optional filepath, from which we will | |
| 136 | 153 | initialize our data. The path is also used to write changes back using | |
| 137 | 154 | the write() method""" | |
@@ -142,7 +159,8 @@ def __init__(self, filepath=None): | |||
| 142 | 159 | ||
| 143 | 160 | def _read_from_file(self): | |
| 144 | 161 | 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) | ||
| 146 | 164 | except OSError: | |
| 147 | 165 | # it is possible and allowed that the file doesn't exist ! | |
| 148 | 166 | return | |
@@ -154,10 +172,10 @@ def _read_from_file(self): | |||
| 154 | 172 | fmap.close() | |
| 155 | 173 | # END handle closing of handle | |
| 156 | 174 | ||
| 157 | - #{ Interface | ||
| 175 | + # { Interface | ||
| 158 | 176 | ||
| 159 | 177 | @classmethod | |
| 160 | - def from_file(cls, filepath): | ||
| 178 | + def from_file(cls, filepath: PathLike) -> 'RefLog': | ||
| 161 | 179 | """ | |
| 162 | 180 | :return: a new RefLog instance containing all entries from the reflog | |
| 163 | 181 | at the given filepath | |
@@ -166,7 +184,7 @@ def from_file(cls, filepath): | |||
| 166 | 184 | return cls(filepath) | |
| 167 | 185 | ||
| 168 | 186 | @classmethod | |
| 169 | - def path(cls, ref): | ||
| 187 | + def path(cls, ref: 'SymbolicReference') -> str: | ||
| 170 | 188 | """ | |
| 171 | 189 | :return: string to absolute path at which the reflog of the given ref | |
| 172 | 190 | instance would be found. The path is not guaranteed to point to a valid | |
@@ -175,28 +193,34 @@ def path(cls, ref): | |||
| 175 | 193 | return osp.join(ref.repo.git_dir, "logs", to_native_path(ref.path)) | |
| 176 | 194 | ||
| 177 | 195 | @classmethod | |
| 178 | - def iter_entries(cls, stream): | ||
| 196 | + def iter_entries(cls, stream: Union[str, 'BytesIO', mmap]) -> Iterator[RefLogEntry]: | ||
| 179 | 197 | """ | |
| 180 | 198 | :return: Iterator yielding RefLogEntry instances, one for each line read | |
| 181 | 199 | sfrom the given stream. | |
| 182 | 200 | :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""" | ||
| 184 | 202 | new_entry = RefLogEntry.from_line | |
| 185 | 203 | 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 | ||
| 187 | 209 | # END handle stream type | |
| 188 | 210 | while True: | |
| 189 | - line = stream.readline() | ||
| 211 | + line = _stream.readline() | ||
| 190 | 212 | if not line: | |
| 191 | 213 | return | |
| 192 | 214 | yield new_entry(line.strip()) | |
| 193 | 215 | # END endless loop | |
| 194 | - stream.close() | ||
| 195 | 216 | ||
| 196 | 217 | @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 | + | ||
| 199 | 222 | :param filepath: full path to the index file from which to read the entry | |
| 223 | + | ||
| 200 | 224 | :param index: python list compatible index, i.e. it may be negative to | |
| 201 | 225 | specify an entry counted from the end of the list | |
| 202 | 226 | ||
@@ -210,21 +234,19 @@ def entry_at(cls, filepath, index): | |||
| 210 | 234 | if index < 0: | |
| 211 | 235 | return RefLogEntry.from_line(fp.readlines()[index].strip()) | |
| 212 | 236 | # read until index is reached | |
| 237 | + | ||
| 213 | 238 | for i in range(index + 1): | |
| 214 | 239 | line = fp.readline() | |
| 215 | 240 | if not line: | |
| 216 | - break | ||
| 241 | + raise IndexError( | ||
| 242 | + f"Index file ended at line {i+1}, before given index was reached") | ||
| 217 | 243 | # END abort on eof | |
| 218 | 244 | # END handle runup | |
| 219 | 245 | ||
| 220 | - if i != index or not line: # skipcq:PYL-W0631 | ||
| 221 | - raise IndexError | ||
| 222 | - # END handle exception | ||
| 223 | - | ||
| 224 | 246 | return RefLogEntry.from_line(line.strip()) | |
| 225 | 247 | # END handle index | |
| 226 | 248 | ||
| 227 | - def to_file(self, filepath): | ||
| 249 | + def to_file(self, filepath: PathLike) -> None: | ||
| 228 | 250 | """Write the contents of the reflog instance to a file at the given filepath. | |
| 229 | 251 | :param filepath: path to file, parent directories are assumed to exist""" | |
| 230 | 252 | lfd = LockedFD(filepath) | |
@@ -241,65 +263,75 @@ def to_file(self, filepath): | |||
| 241 | 263 | # END handle change | |
| 242 | 264 | ||
| 243 | 265 | @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': | ||
| 245 | 269 | """Append a new log entry to the revlog at filepath. | |
| 246 | 270 | ||
| 247 | 271 | :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. | ||
| 250 | 273 | :param filepath: full path to the log file | |
| 251 | 274 | :param oldbinsha: binary sha of the previous commit | |
| 252 | 275 | :param newbinsha: binary sha of the current commit | |
| 253 | 276 | :param message: message describing the change to the reference | |
| 254 | 277 | :param write: If True, the changes will be written right away. Otherwise | |
| 255 | 278 | the change will not be written | |
| 279 | + | ||
| 256 | 280 | :return: RefLogEntry objects which was appended to the log | |
| 281 | + | ||
| 257 | 282 | :note: As we are append-only, concurrent access is not a problem as we | |
| 258 | 283 | do not interfere with readers.""" | |
| 284 | + | ||
| 259 | 285 | if len(oldbinsha) != 20 or len(newbinsha) != 20: | |
| 260 | 286 | raise ValueError("Shas need to be given in binary format") | |
| 261 | 287 | # END handle sha type | |
| 262 | 288 | assure_directory_exists(filepath, is_file=True) | |
| 263 | 289 | 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) | ||
| 265 | 294 | entry = RefLogEntry(( | |
| 266 | 295 | bin_to_hex(oldbinsha).decode('ascii'), | |
| 267 | 296 | bin_to_hex(newbinsha).decode('ascii'), | |
| 268 | - committer, (int(time.time()), time.altzone), first_line | ||
| 297 | + committer, (int(_time.time()), _time.altzone), first_line | ||
| 269 | 298 | )) | |
| 270 | 299 | ||
| 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 | ||
| 281 | 310 | return entry | |
| 282 | 311 | ||
| 283 | - def write(self): | ||
| 312 | + def write(self) -> 'RefLog': | ||
| 284 | 313 | """Write this instance's data to the file we are originating from | |
| 285 | 314 | :return: self""" | |
| 286 | 315 | 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") | ||
| 288 | 318 | # END assert path | |
| 289 | 319 | self.to_file(self._path) | |
| 290 | 320 | return self | |
| 291 | 321 | ||
| 292 | - #} END interface | ||
| 322 | + # } END interface | ||
| 293 | 323 | ||
| 294 | - #{ Serializable Interface | ||
| 295 | - def _serialize(self, stream): | ||
| 324 | + # { Serializable Interface | ||
| 325 | + def _serialize(self, stream: 'BytesIO') -> 'RefLog': | ||
| 296 | 326 | write = stream.write | |
| 297 | 327 | ||
| 298 | 328 | # write all entries | |
| 299 | 329 | for e in self: | |
| 300 | 330 | write(e.format().encode(defenc)) | |
| 301 | 331 | # END for each entry | |
| 332 | + return self | ||
| 302 | 333 | ||
| 303 | - def _deserialize(self, stream): | ||
| 334 | + def _deserialize(self, stream: 'BytesIO') -> 'RefLog': | ||
| 304 | 335 | self.extend(self.iter_entries(stream)) | |
| 305 | - #} END serializable interface | ||
| 336 | + # } END serializable interface | ||
| 337 | + return self | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments