Mercurial > public > mercurial-scm > hg
comparison mercurial/shelve.py @ 51812:a1a94d488e14
typing: add type hints to `mercurial.shelve`
Pytype wasn't flagging anything here yet, but PyCharm was really unhappy about
the usage of `state` objects being passed to various methods that accessed attrs
on it, without any obvious attrs on the class because there's no contructor.
Filling that out made PyCharm happy, and a few other things needed to be filled
in to make that easier, so I made a pass over the whole file and filled in the
trivial hints. The other repo, ui, context, matcher, and pats items can be
filled in after the context and match modules are typed.
author | Matt Harbison <matt_harbison@yahoo.com> |
---|---|
date | Tue, 20 Aug 2024 22:34:51 -0400 |
parents | 54b1a3738530 |
children | adbfbbf9963f |
comparison
equal
deleted
inserted
replaced
51811:460e80488cf0 | 51812:a1a94d488e14 |
---|---|
23 | 23 |
24 import collections | 24 import collections |
25 import io | 25 import io |
26 import itertools | 26 import itertools |
27 import stat | 27 import stat |
28 | |
29 from typing import ( | |
30 Any, | |
31 Dict, | |
32 Iterable, | |
33 Iterator, | |
34 List, | |
35 Sequence, | |
36 Tuple, | |
37 ) | |
28 | 38 |
29 from .i18n import _ | 39 from .i18n import _ |
30 from .node import ( | 40 from .node import ( |
31 bin, | 41 bin, |
32 hex, | 42 hex, |
35 from . import ( | 45 from . import ( |
36 bookmarks, | 46 bookmarks, |
37 bundle2, | 47 bundle2, |
38 changegroup, | 48 changegroup, |
39 cmdutil, | 49 cmdutil, |
50 context as contextmod, | |
40 discovery, | 51 discovery, |
41 error, | 52 error, |
42 exchange, | 53 exchange, |
43 hg, | 54 hg, |
44 lock as lockmod, | 55 lock as lockmod, |
67 # generic user for all shelve operations | 78 # generic user for all shelve operations |
68 shelveuser = b'shelve@localhost' | 79 shelveuser = b'shelve@localhost' |
69 | 80 |
70 | 81 |
71 class ShelfDir: | 82 class ShelfDir: |
72 def __init__(self, repo, for_backups=False): | 83 def __init__(self, repo, for_backups: bool = False) -> None: |
73 if for_backups: | 84 if for_backups: |
74 self.vfs = vfsmod.vfs(repo.vfs.join(backupdir)) | 85 self.vfs = vfsmod.vfs(repo.vfs.join(backupdir)) |
75 else: | 86 else: |
76 self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir)) | 87 self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir)) |
77 | 88 |
78 def get(self, name): | 89 def get(self, name: bytes) -> "Shelf": |
79 return Shelf(self.vfs, name) | 90 return Shelf(self.vfs, name) |
80 | 91 |
81 def listshelves(self): | 92 def listshelves(self) -> List[Tuple[float, bytes]]: |
82 """return all shelves in repo as list of (time, name)""" | 93 """return all shelves in repo as list of (time, name)""" |
83 try: | 94 try: |
84 names = self.vfs.listdir() | 95 names = self.vfs.listdir() |
85 except FileNotFoundError: | 96 except FileNotFoundError: |
86 return [] | 97 return [] |
97 mtime = shelf.mtime() | 108 mtime = shelf.mtime() |
98 info.append((mtime, name)) | 109 info.append((mtime, name)) |
99 return sorted(info, reverse=True) | 110 return sorted(info, reverse=True) |
100 | 111 |
101 | 112 |
102 def _use_internal_phase(repo): | 113 def _use_internal_phase(repo) -> bool: |
103 return ( | 114 return ( |
104 phases.supportinternal(repo) | 115 phases.supportinternal(repo) |
105 and repo.ui.config(b'shelve', b'store') == b'internal' | 116 and repo.ui.config(b'shelve', b'store') == b'internal' |
106 ) | 117 ) |
107 | 118 |
108 | 119 |
109 def _target_phase(repo): | 120 def _target_phase(repo) -> int: |
110 return phases.internal if _use_internal_phase(repo) else phases.secret | 121 return phases.internal if _use_internal_phase(repo) else phases.secret |
111 | 122 |
112 | 123 |
113 class Shelf: | 124 class Shelf: |
114 """Represents a shelf, including possibly multiple files storing it. | 125 """Represents a shelf, including possibly multiple files storing it. |
116 Old shelves will have a .patch and a .hg file. Newer shelves will | 127 Old shelves will have a .patch and a .hg file. Newer shelves will |
117 also have a .shelve file. This class abstracts away some of the | 128 also have a .shelve file. This class abstracts away some of the |
118 differences and lets you work with the shelf as a whole. | 129 differences and lets you work with the shelf as a whole. |
119 """ | 130 """ |
120 | 131 |
121 def __init__(self, vfs, name): | 132 def __init__(self, vfs: vfsmod.vfs, name: bytes) -> None: |
122 self.vfs = vfs | 133 self.vfs = vfs |
123 self.name = name | 134 self.name = name |
124 | 135 |
125 def exists(self): | 136 def exists(self) -> bool: |
126 return self._exists(b'.shelve') or self._exists(b'.patch', b'.hg') | 137 return self._exists(b'.shelve') or self._exists(b'.patch', b'.hg') |
127 | 138 |
128 def _exists(self, *exts): | 139 def _exists(self, *exts: bytes) -> bool: |
129 return all(self.vfs.exists(self.name + ext) for ext in exts) | 140 return all(self.vfs.exists(self.name + ext) for ext in exts) |
130 | 141 |
131 def mtime(self): | 142 def mtime(self) -> float: |
132 try: | 143 try: |
133 return self._stat(b'.shelve')[stat.ST_MTIME] | 144 return self._stat(b'.shelve')[stat.ST_MTIME] |
134 except FileNotFoundError: | 145 except FileNotFoundError: |
135 return self._stat(b'.patch')[stat.ST_MTIME] | 146 return self._stat(b'.patch')[stat.ST_MTIME] |
136 | 147 |
137 def _stat(self, ext): | 148 def _stat(self, ext: bytes): |
138 return self.vfs.stat(self.name + ext) | 149 return self.vfs.stat(self.name + ext) |
139 | 150 |
140 def writeinfo(self, info): | 151 def writeinfo(self, info) -> None: |
141 scmutil.simplekeyvaluefile(self.vfs, self.name + b'.shelve').write(info) | 152 scmutil.simplekeyvaluefile(self.vfs, self.name + b'.shelve').write(info) |
142 | 153 |
143 def hasinfo(self): | 154 def hasinfo(self) -> bool: |
144 return self.vfs.exists(self.name + b'.shelve') | 155 return self.vfs.exists(self.name + b'.shelve') |
145 | 156 |
146 def readinfo(self): | 157 def readinfo(self): |
147 return scmutil.simplekeyvaluefile( | 158 return scmutil.simplekeyvaluefile( |
148 self.vfs, self.name + b'.shelve' | 159 self.vfs, self.name + b'.shelve' |
149 ).read() | 160 ).read() |
150 | 161 |
151 def writebundle(self, repo, bases, node): | 162 def writebundle(self, repo, bases, node) -> None: |
152 cgversion = changegroup.safeversion(repo) | 163 cgversion = changegroup.safeversion(repo) |
153 if cgversion == b'01': | 164 if cgversion == b'01': |
154 btype = b'HG10BZ' | 165 btype = b'HG10BZ' |
155 compression = None | 166 compression = None |
156 else: | 167 else: |
172 btype, | 183 btype, |
173 self.vfs, | 184 self.vfs, |
174 compression=compression, | 185 compression=compression, |
175 ) | 186 ) |
176 | 187 |
177 def applybundle(self, repo, tr): | 188 def applybundle(self, repo, tr) -> contextmod.changectx: |
178 filename = self.name + b'.hg' | 189 filename = self.name + b'.hg' |
179 fp = self.vfs(filename) | 190 fp = self.vfs(filename) |
180 try: | 191 try: |
181 targetphase = _target_phase(repo) | 192 targetphase = _target_phase(repo) |
182 gen = exchange.readbundle(repo.ui, fp, filename, self.vfs) | 193 gen = exchange.readbundle(repo.ui, fp, filename, self.vfs) |
195 shelvectx = repo[shelverev] | 206 shelvectx = repo[shelverev] |
196 return shelvectx | 207 return shelvectx |
197 finally: | 208 finally: |
198 fp.close() | 209 fp.close() |
199 | 210 |
200 def open_patch(self, mode=b'rb'): | 211 def open_patch(self, mode: bytes = b'rb'): |
201 return self.vfs(self.name + b'.patch', mode) | 212 return self.vfs(self.name + b'.patch', mode) |
202 | 213 |
203 def patch_from_node(self, repo, node): | 214 def patch_from_node(self, repo, node) -> io.BytesIO: |
204 repo = repo.unfiltered() | 215 repo = repo.unfiltered() |
205 match = _optimized_match(repo, node) | 216 match = _optimized_match(repo, node) |
206 fp = io.BytesIO() | 217 fp = io.BytesIO() |
207 cmdutil.exportfile( | 218 cmdutil.exportfile( |
208 repo, | 219 repo, |
219 # prefer node-based shelf | 230 # prefer node-based shelf |
220 return self.patch_from_node(repo, self.readinfo()[b'node']) | 231 return self.patch_from_node(repo, self.readinfo()[b'node']) |
221 except (FileNotFoundError, error.RepoLookupError): | 232 except (FileNotFoundError, error.RepoLookupError): |
222 return self.open_patch() | 233 return self.open_patch() |
223 | 234 |
224 def _backupfilename(self, backupvfs, filename): | 235 def _backupfilename(self, backupvfs: vfsmod.vfs, filename: bytes) -> bytes: |
225 def gennames(base): | 236 def gennames(base: bytes): |
226 yield base | 237 yield base |
227 base, ext = base.rsplit(b'.', 1) | 238 base, ext = base.rsplit(b'.', 1) |
228 for i in itertools.count(1): | 239 for i in itertools.count(1): |
229 yield b'%s-%d.%s' % (base, i, ext) | 240 yield b'%s-%d.%s' % (base, i, ext) |
230 | 241 |
231 for n in gennames(filename): | 242 for n in gennames(filename): |
232 if not backupvfs.exists(n): | 243 if not backupvfs.exists(n): |
233 return backupvfs.join(n) | 244 return backupvfs.join(n) |
234 | 245 |
235 def movetobackup(self, backupvfs): | 246 # Help pytype- gennames() yields infinitely |
247 raise error.ProgrammingError("unreachable") | |
248 | |
249 def movetobackup(self, backupvfs: vfsmod.vfs) -> None: | |
236 if not backupvfs.isdir(): | 250 if not backupvfs.isdir(): |
237 backupvfs.makedir() | 251 backupvfs.makedir() |
238 for suffix in shelvefileextensions: | 252 for suffix in shelvefileextensions: |
239 filename = self.name + b'.' + suffix | 253 filename = self.name + b'.' + suffix |
240 if self.vfs.exists(filename): | 254 if self.vfs.exists(filename): |
241 util.rename( | 255 util.rename( |
242 self.vfs.join(filename), | 256 self.vfs.join(filename), |
243 self._backupfilename(backupvfs, filename), | 257 self._backupfilename(backupvfs, filename), |
244 ) | 258 ) |
245 | 259 |
246 def delete(self): | 260 def delete(self) -> None: |
247 for ext in shelvefileextensions: | 261 for ext in shelvefileextensions: |
248 self.vfs.tryunlink(self.name + b'.' + ext) | 262 self.vfs.tryunlink(self.name + b'.' + ext) |
249 | 263 |
250 def changed_files(self, ui, repo): | 264 def changed_files(self, ui, repo): |
251 try: | 265 try: |
254 except (FileNotFoundError, error.RepoLookupError): | 268 except (FileNotFoundError, error.RepoLookupError): |
255 filename = self.vfs.join(self.name + b'.patch') | 269 filename = self.vfs.join(self.name + b'.patch') |
256 return patch.changedfiles(ui, repo, filename) | 270 return patch.changedfiles(ui, repo, filename) |
257 | 271 |
258 | 272 |
259 def _optimized_match(repo, node): | 273 def _optimized_match(repo, node: bytes): |
260 """ | 274 """ |
261 Create a matcher so that prefetch doesn't attempt to fetch | 275 Create a matcher so that prefetch doesn't attempt to fetch |
262 the entire repository pointlessly, and as an optimisation | 276 the entire repository pointlessly, and as an optimisation |
263 for movedirstate, if needed. | 277 for movedirstate, if needed. |
264 """ | 278 """ |
270 | 284 |
271 Handles saving and restoring a shelved state. Ensures that different | 285 Handles saving and restoring a shelved state. Ensures that different |
272 versions of a shelved state are possible and handles them appropriately. | 286 versions of a shelved state are possible and handles them appropriately. |
273 """ | 287 """ |
274 | 288 |
289 # Class-wide constants | |
275 _version = 2 | 290 _version = 2 |
276 _filename = b'shelvedstate' | 291 _filename = b'shelvedstate' |
277 _keep = b'keep' | 292 _keep = b'keep' |
278 _nokeep = b'nokeep' | 293 _nokeep = b'nokeep' |
279 # colon is essential to differentiate from a real bookmark name | 294 # colon is essential to differentiate from a real bookmark name |
280 _noactivebook = b':no-active-bookmark' | 295 _noactivebook = b':no-active-bookmark' |
281 _interactive = b'interactive' | 296 _interactive = b'interactive' |
282 | 297 |
298 # Per instance attrs | |
299 name: bytes | |
300 wctx: contextmod.workingctx | |
301 pendingctx: contextmod.changectx | |
302 parents: List[bytes] | |
303 nodestoremove: List[bytes] | |
304 branchtorestore: bytes | |
305 keep: bool | |
306 activebookmark: bytes | |
307 interactive: bool | |
308 | |
283 @classmethod | 309 @classmethod |
284 def _verifyandtransform(cls, d): | 310 def _verifyandtransform(cls, d: Dict[bytes, Any]) -> None: |
285 """Some basic shelvestate syntactic verification and transformation""" | 311 """Some basic shelvestate syntactic verification and transformation""" |
286 try: | 312 try: |
287 d[b'originalwctx'] = bin(d[b'originalwctx']) | 313 d[b'originalwctx'] = bin(d[b'originalwctx']) |
288 d[b'pendingctx'] = bin(d[b'pendingctx']) | 314 d[b'pendingctx'] = bin(d[b'pendingctx']) |
289 d[b'parents'] = [bin(h) for h in d[b'parents'].split(b' ') if h] | 315 d[b'parents'] = [bin(h) for h in d[b'parents'].split(b' ') if h] |
292 ] | 318 ] |
293 except (ValueError, KeyError) as err: | 319 except (ValueError, KeyError) as err: |
294 raise error.CorruptedState(stringutil.forcebytestr(err)) | 320 raise error.CorruptedState(stringutil.forcebytestr(err)) |
295 | 321 |
296 @classmethod | 322 @classmethod |
297 def _getversion(cls, repo): | 323 def _getversion(cls, repo) -> int: |
298 """Read version information from shelvestate file""" | 324 """Read version information from shelvestate file""" |
299 fp = repo.vfs(cls._filename) | 325 fp = repo.vfs(cls._filename) |
300 try: | 326 try: |
301 version = int(fp.readline().strip()) | 327 version = int(fp.readline().strip()) |
302 except ValueError as err: | 328 except ValueError as err: |
304 finally: | 330 finally: |
305 fp.close() | 331 fp.close() |
306 return version | 332 return version |
307 | 333 |
308 @classmethod | 334 @classmethod |
309 def _readold(cls, repo): | 335 def _readold(cls, repo) -> Dict[bytes, Any]: |
310 """Read the old position-based version of a shelvestate file""" | 336 """Read the old position-based version of a shelvestate file""" |
311 # Order is important, because old shelvestate file uses it | 337 # Order is important, because old shelvestate file uses it |
312 # to detemine values of fields (i.g. name is on the second line, | 338 # to detemine values of fields (i.g. name is on the second line, |
313 # originalwctx is on the third and so forth). Please do not change. | 339 # originalwctx is on the third and so forth). Please do not change. |
314 keys = [ | 340 keys = [ |
371 | 397 |
372 @classmethod | 398 @classmethod |
373 def save( | 399 def save( |
374 cls, | 400 cls, |
375 repo, | 401 repo, |
376 name, | 402 name: bytes, |
377 originalwctx, | 403 originalwctx: contextmod.workingctx, |
378 pendingctx, | 404 pendingctx: contextmod.changectx, |
379 nodestoremove, | 405 nodestoremove: List[bytes], |
380 branchtorestore, | 406 branchtorestore: bytes, |
381 keep=False, | 407 keep: bool = False, |
382 activebook=b'', | 408 activebook: bytes = b'', |
383 interactive=False, | 409 interactive: bool = False, |
384 ): | 410 ) -> None: |
385 info = { | 411 info = { |
386 b"name": name, | 412 b"name": name, |
387 b"originalwctx": hex(originalwctx.node()), | 413 b"originalwctx": hex(originalwctx.node()), |
388 b"pendingctx": hex(pendingctx.node()), | 414 b"pendingctx": hex(pendingctx.node()), |
389 b"parents": b' '.join([hex(p) for p in repo.dirstate.parents()]), | 415 b"parents": b' '.join([hex(p) for p in repo.dirstate.parents()]), |
397 scmutil.simplekeyvaluefile(repo.vfs, cls._filename).write( | 423 scmutil.simplekeyvaluefile(repo.vfs, cls._filename).write( |
398 info, firstline=(b"%d" % cls._version) | 424 info, firstline=(b"%d" % cls._version) |
399 ) | 425 ) |
400 | 426 |
401 @classmethod | 427 @classmethod |
402 def clear(cls, repo): | 428 def clear(cls, repo) -> None: |
403 repo.vfs.unlinkpath(cls._filename, ignoremissing=True) | 429 repo.vfs.unlinkpath(cls._filename, ignoremissing=True) |
404 | 430 |
405 | 431 |
406 def cleanupoldbackups(repo): | 432 def cleanupoldbackups(repo) -> None: |
407 maxbackups = repo.ui.configint(b'shelve', b'maxbackups') | 433 maxbackups = repo.ui.configint(b'shelve', b'maxbackups') |
408 backup_dir = ShelfDir(repo, for_backups=True) | 434 backup_dir = ShelfDir(repo, for_backups=True) |
409 hgfiles = backup_dir.listshelves() | 435 hgfiles = backup_dir.listshelves() |
410 if maxbackups > 0 and maxbackups < len(hgfiles): | 436 if maxbackups > 0 and maxbackups < len(hgfiles): |
411 bordermtime = hgfiles[maxbackups - 1][0] | 437 bordermtime = hgfiles[maxbackups - 1][0] |
416 # keep it, because timestamp can't decide exact order of backups | 442 # keep it, because timestamp can't decide exact order of backups |
417 continue | 443 continue |
418 backup_dir.get(name).delete() | 444 backup_dir.get(name).delete() |
419 | 445 |
420 | 446 |
421 def _backupactivebookmark(repo): | 447 def _backupactivebookmark(repo) -> bytes: |
422 activebookmark = repo._activebookmark | 448 activebookmark = repo._activebookmark |
423 if activebookmark: | 449 if activebookmark: |
424 bookmarks.deactivate(repo) | 450 bookmarks.deactivate(repo) |
425 return activebookmark | 451 return activebookmark |
426 | 452 |
427 | 453 |
428 def _restoreactivebookmark(repo, mark): | 454 def _restoreactivebookmark(repo, mark) -> None: |
429 if mark: | 455 if mark: |
430 bookmarks.activate(repo, mark) | 456 bookmarks.activate(repo, mark) |
431 | 457 |
432 | 458 |
433 def _aborttransaction(repo, tr): | 459 def _aborttransaction(repo, tr) -> None: |
434 """Abort current transaction for shelve/unshelve, but keep dirstate""" | 460 """Abort current transaction for shelve/unshelve, but keep dirstate""" |
435 # disable the transaction invalidation of the dirstate, to preserve the | 461 # disable the transaction invalidation of the dirstate, to preserve the |
436 # current change in memory. | 462 # current change in memory. |
437 ds = repo.dirstate | 463 ds = repo.dirstate |
438 # The assert below check that nobody else did such wrapping. | 464 # The assert below check that nobody else did such wrapping. |
454 assert repo.currenttransaction() is None | 480 assert repo.currenttransaction() is None |
455 repo.dirstate.write(None) | 481 repo.dirstate.write(None) |
456 ds.setbranch(current_branch, None) | 482 ds.setbranch(current_branch, None) |
457 | 483 |
458 | 484 |
459 def getshelvename(repo, parent, opts): | 485 def getshelvename(repo, parent, opts) -> bytes: |
460 """Decide on the name this shelve is going to have""" | 486 """Decide on the name this shelve is going to have""" |
461 | 487 |
462 def gennames(): | 488 def gennames(): |
463 yield label | 489 yield label |
464 for i in itertools.count(1): | 490 for i in itertools.count(1): |
494 break | 520 break |
495 | 521 |
496 return name | 522 return name |
497 | 523 |
498 | 524 |
499 def mutableancestors(ctx): | 525 def mutableancestors(ctx) -> Iterator[bytes]: |
500 """return all mutable ancestors for ctx (included) | 526 """return all mutable ancestors for ctx (included) |
501 | 527 |
502 Much faster than the revset ancestors(ctx) & draft()""" | 528 Much faster than the revset ancestors(ctx) & draft()""" |
503 seen = {nullrev} | 529 seen = {nullrev} |
504 visit = collections.deque() | 530 visit = collections.deque() |
512 seen.add(rev) | 538 seen.add(rev) |
513 if parent.mutable(): | 539 if parent.mutable(): |
514 visit.append(parent) | 540 visit.append(parent) |
515 | 541 |
516 | 542 |
517 def getcommitfunc(extra, interactive, editor=False): | 543 def getcommitfunc(extra, interactive: bool, editor: bool = False): |
518 def commitfunc(ui, repo, message, match, opts): | 544 def commitfunc(ui, repo, message, match, opts): |
519 hasmq = hasattr(repo, 'mq') | 545 hasmq = hasattr(repo, 'mq') |
520 if hasmq: | 546 if hasmq: |
521 saved, repo.mq.checkapplied = repo.mq.checkapplied, False | 547 saved, repo.mq.checkapplied = repo.mq.checkapplied, False |
522 | 548 |
548 return commitfunc(ui, repo, message, match, opts) | 574 return commitfunc(ui, repo, message, match, opts) |
549 | 575 |
550 return interactivecommitfunc if interactive else commitfunc | 576 return interactivecommitfunc if interactive else commitfunc |
551 | 577 |
552 | 578 |
553 def _nothingtoshelvemessaging(ui, repo, pats, opts): | 579 def _nothingtoshelvemessaging(ui, repo, pats, opts) -> None: |
554 stat = repo.status(match=scmutil.match(repo[None], pats, opts)) | 580 stat = repo.status(match=scmutil.match(repo[None], pats, opts)) |
555 if stat.deleted: | 581 if stat.deleted: |
556 ui.status( | 582 ui.status( |
557 _(b"nothing changed (%d missing files, see 'hg status')\n") | 583 _(b"nothing changed (%d missing files, see 'hg status')\n") |
558 % len(stat.deleted) | 584 % len(stat.deleted) |
559 ) | 585 ) |
560 else: | 586 else: |
561 ui.status(_(b"nothing changed\n")) | 587 ui.status(_(b"nothing changed\n")) |
562 | 588 |
563 | 589 |
564 def _shelvecreatedcommit(repo, node, name, match): | 590 def _shelvecreatedcommit(repo, node: bytes, name: bytes, match) -> None: |
565 info = {b'node': hex(node)} | 591 info = {b'node': hex(node)} |
566 shelf = ShelfDir(repo).get(name) | 592 shelf = ShelfDir(repo).get(name) |
567 shelf.writeinfo(info) | 593 shelf.writeinfo(info) |
568 bases = list(mutableancestors(repo[node])) | 594 bases = list(mutableancestors(repo[node])) |
569 shelf.writebundle(repo, bases, node) | 595 shelf.writebundle(repo, bases, node) |
571 cmdutil.exportfile( | 597 cmdutil.exportfile( |
572 repo, [node], fp, opts=mdiff.diffopts(git=True), match=match | 598 repo, [node], fp, opts=mdiff.diffopts(git=True), match=match |
573 ) | 599 ) |
574 | 600 |
575 | 601 |
576 def _includeunknownfiles(repo, pats, opts, extra): | 602 def _includeunknownfiles(repo, pats, opts, extra) -> None: |
577 s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True) | 603 s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True) |
578 if s.unknown: | 604 if s.unknown: |
579 extra[b'shelve_unknown'] = b'\0'.join(s.unknown) | 605 extra[b'shelve_unknown'] = b'\0'.join(s.unknown) |
580 repo[None].add(s.unknown) | 606 repo[None].add(s.unknown) |
581 | 607 |
582 | 608 |
583 def _finishshelve(repo, tr): | 609 def _finishshelve(repo, tr) -> None: |
584 if _use_internal_phase(repo): | 610 if _use_internal_phase(repo): |
585 tr.close() | 611 tr.close() |
586 else: | 612 else: |
587 _aborttransaction(repo, tr) | 613 _aborttransaction(repo, tr) |
588 | 614 |
673 finally: | 699 finally: |
674 _restoreactivebookmark(repo, activebookmark) | 700 _restoreactivebookmark(repo, activebookmark) |
675 lockmod.release(tr, lock) | 701 lockmod.release(tr, lock) |
676 | 702 |
677 | 703 |
678 def _isbareshelve(pats, opts): | 704 def _isbareshelve(pats, opts) -> bool: |
679 return ( | 705 return ( |
680 not pats | 706 not pats |
681 and not opts.get(b'interactive', False) | 707 and not opts.get(b'interactive', False) |
682 and not opts.get(b'include', False) | 708 and not opts.get(b'include', False) |
683 and not opts.get(b'exclude', False) | 709 and not opts.get(b'exclude', False) |
684 ) | 710 ) |
685 | 711 |
686 | 712 |
687 def _iswctxonnewbranch(repo): | 713 def _iswctxonnewbranch(repo) -> bool: |
688 return repo[None].branch() != repo[b'.'].branch() | 714 return repo[None].branch() != repo[b'.'].branch() |
689 | 715 |
690 | 716 |
691 def cleanupcmd(ui, repo): | 717 def cleanupcmd(ui, repo) -> None: |
692 """subcommand that deletes all shelves""" | 718 """subcommand that deletes all shelves""" |
693 | 719 |
694 with repo.wlock(): | 720 with repo.wlock(): |
695 shelf_dir = ShelfDir(repo) | 721 shelf_dir = ShelfDir(repo) |
696 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir)) | 722 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir)) |
697 for _mtime, name in shelf_dir.listshelves(): | 723 for _mtime, name in shelf_dir.listshelves(): |
698 shelf_dir.get(name).movetobackup(backupvfs) | 724 shelf_dir.get(name).movetobackup(backupvfs) |
699 cleanupoldbackups(repo) | 725 cleanupoldbackups(repo) |
700 | 726 |
701 | 727 |
702 def deletecmd(ui, repo, pats): | 728 def deletecmd(ui, repo, pats) -> None: |
703 """subcommand that deletes a specific shelve""" | 729 """subcommand that deletes a specific shelve""" |
704 if not pats: | 730 if not pats: |
705 raise error.InputError(_(b'no shelved changes specified!')) | 731 raise error.InputError(_(b'no shelved changes specified!')) |
706 with repo.wlock(): | 732 with repo.wlock(): |
707 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir)) | 733 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir)) |
713 ) | 739 ) |
714 shelf.movetobackup(backupvfs) | 740 shelf.movetobackup(backupvfs) |
715 cleanupoldbackups(repo) | 741 cleanupoldbackups(repo) |
716 | 742 |
717 | 743 |
718 def listcmd(ui, repo, pats, opts): | 744 def listcmd(ui, repo, pats: Iterable[bytes], opts) -> None: |
719 """subcommand that displays the list of shelves""" | 745 """subcommand that displays the list of shelves""" |
720 pats = set(pats) | 746 pats = set(pats) |
721 width = 80 | 747 width = 80 |
722 if not ui.plain(): | 748 if not ui.plain(): |
723 width = ui.termwidth() | 749 width = ui.termwidth() |
760 if opts[b'stat']: | 786 if opts[b'stat']: |
761 for chunk, label in patch.diffstatui(difflines, width=width): | 787 for chunk, label in patch.diffstatui(difflines, width=width): |
762 ui.write(chunk, label=label) | 788 ui.write(chunk, label=label) |
763 | 789 |
764 | 790 |
765 def patchcmds(ui, repo, pats, opts): | 791 def patchcmds(ui, repo, pats: Sequence[bytes], opts) -> None: |
766 """subcommand that displays shelves""" | 792 """subcommand that displays shelves""" |
767 shelf_dir = ShelfDir(repo) | 793 shelf_dir = ShelfDir(repo) |
768 if len(pats) == 0: | 794 if len(pats) == 0: |
769 shelves = shelf_dir.listshelves() | 795 shelves = shelf_dir.listshelves() |
770 if not shelves: | 796 if not shelves: |
777 raise error.Abort(_(b"cannot find shelf %s") % shelfname) | 803 raise error.Abort(_(b"cannot find shelf %s") % shelfname) |
778 | 804 |
779 listcmd(ui, repo, pats, opts) | 805 listcmd(ui, repo, pats, opts) |
780 | 806 |
781 | 807 |
782 def checkparents(repo, state): | 808 def checkparents(repo, state: shelvedstate) -> None: |
783 """check parent while resuming an unshelve""" | 809 """check parent while resuming an unshelve""" |
784 if state.parents != repo.dirstate.parents(): | 810 if state.parents != repo.dirstate.parents(): |
785 raise error.Abort( | 811 raise error.Abort( |
786 _(b'working directory parents do not match unshelve state') | 812 _(b'working directory parents do not match unshelve state') |
787 ) | 813 ) |
788 | 814 |
789 | 815 |
790 def _loadshelvedstate(ui, repo, opts): | 816 def _loadshelvedstate(ui, repo, opts) -> shelvedstate: |
791 try: | 817 try: |
792 state = shelvedstate.load(repo) | 818 state = shelvedstate.load(repo) |
793 if opts.get(b'keep') is None: | 819 if opts.get(b'keep') is None: |
794 opts[b'keep'] = state.keep | 820 opts[b'keep'] = state.keep |
795 return state | 821 return state |
817 raise error.ProgrammingError( | 843 raise error.ProgrammingError( |
818 "a corrupted shelvedstate exists without --abort or --continue" | 844 "a corrupted shelvedstate exists without --abort or --continue" |
819 ) | 845 ) |
820 | 846 |
821 | 847 |
822 def unshelveabort(ui, repo, state): | 848 def unshelveabort(ui, repo, state: shelvedstate) -> None: |
823 """subcommand that abort an in-progress unshelve""" | 849 """subcommand that abort an in-progress unshelve""" |
824 with repo.lock(): | 850 with repo.lock(): |
825 try: | 851 try: |
826 checkparents(repo, state) | 852 checkparents(repo, state) |
827 | 853 |
836 finally: | 862 finally: |
837 shelvedstate.clear(repo) | 863 shelvedstate.clear(repo) |
838 ui.warn(_(b"unshelve of '%s' aborted\n") % state.name) | 864 ui.warn(_(b"unshelve of '%s' aborted\n") % state.name) |
839 | 865 |
840 | 866 |
841 def hgabortunshelve(ui, repo): | 867 def hgabortunshelve(ui, repo) -> None: |
842 """logic to abort unshelve using 'hg abort""" | 868 """logic to abort unshelve using 'hg abort""" |
843 with repo.wlock(): | 869 with repo.wlock(): |
844 state = _loadshelvedstate(ui, repo, {b'abort': True}) | 870 state = _loadshelvedstate(ui, repo, {b'abort': True}) |
845 return unshelveabort(ui, repo, state) | 871 return unshelveabort(ui, repo, state) |
846 | 872 |
847 | 873 |
848 def mergefiles(ui, repo, wctx, shelvectx): | 874 def mergefiles(ui, repo, wctx, shelvectx) -> None: |
849 """updates to wctx and merges the changes from shelvectx into the | 875 """updates to wctx and merges the changes from shelvectx into the |
850 dirstate.""" | 876 dirstate.""" |
851 with ui.configoverride({(b'ui', b'quiet'): True}): | 877 with ui.configoverride({(b'ui', b'quiet'): True}): |
852 hg.update(repo, wctx.node()) | 878 hg.update(repo, wctx.node()) |
853 cmdutil.revert(ui, repo, shelvectx) | 879 cmdutil.revert(ui, repo, shelvectx) |
854 | 880 |
855 | 881 |
856 def restorebranch(ui, repo, branchtorestore): | 882 def restorebranch(ui, repo, branchtorestore: bytes) -> None: |
857 if branchtorestore and branchtorestore != repo.dirstate.branch(): | 883 if branchtorestore and branchtorestore != repo.dirstate.branch(): |
858 repo.dirstate.setbranch(branchtorestore, repo.currenttransaction()) | 884 repo.dirstate.setbranch(branchtorestore, repo.currenttransaction()) |
859 ui.status( | 885 ui.status( |
860 _(b'marked working directory as branch %s\n') % branchtorestore | 886 _(b'marked working directory as branch %s\n') % branchtorestore |
861 ) | 887 ) |
862 | 888 |
863 | 889 |
864 def unshelvecleanup(ui, repo, name, opts): | 890 def unshelvecleanup(ui, repo, name: bytes, opts) -> None: |
865 """remove related files after an unshelve""" | 891 """remove related files after an unshelve""" |
866 if not opts.get(b'keep'): | 892 if not opts.get(b'keep'): |
867 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir)) | 893 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir)) |
868 ShelfDir(repo).get(name).movetobackup(backupvfs) | 894 ShelfDir(repo).get(name).movetobackup(backupvfs) |
869 cleanupoldbackups(repo) | 895 cleanupoldbackups(repo) |
870 | 896 |
871 | 897 |
872 def unshelvecontinue(ui, repo, state, opts): | 898 def unshelvecontinue(ui, repo, state: shelvedstate, opts) -> None: |
873 """subcommand to continue an in-progress unshelve""" | 899 """subcommand to continue an in-progress unshelve""" |
874 # We're finishing off a merge. First parent is our original | 900 # We're finishing off a merge. First parent is our original |
875 # parent, second is the temporary "fake" commit we're unshelving. | 901 # parent, second is the temporary "fake" commit we're unshelving. |
876 interactive = state.interactive | 902 interactive = state.interactive |
877 basename = state.name | 903 basename = state.name |
925 unshelvecleanup(ui, repo, state.name, opts) | 951 unshelvecleanup(ui, repo, state.name, opts) |
926 _restoreactivebookmark(repo, state.activebookmark) | 952 _restoreactivebookmark(repo, state.activebookmark) |
927 ui.status(_(b"unshelve of '%s' complete\n") % state.name) | 953 ui.status(_(b"unshelve of '%s' complete\n") % state.name) |
928 | 954 |
929 | 955 |
930 def hgcontinueunshelve(ui, repo): | 956 def hgcontinueunshelve(ui, repo) -> None: |
931 """logic to resume unshelve using 'hg continue'""" | 957 """logic to resume unshelve using 'hg continue'""" |
932 with repo.wlock(): | 958 with repo.wlock(): |
933 state = _loadshelvedstate(ui, repo, {b'continue': True}) | 959 state = _loadshelvedstate(ui, repo, {b'continue': True}) |
934 return unshelvecontinue(ui, repo, state, {b'keep': state.keep}) | 960 return unshelvecontinue(ui, repo, state, {b'keep': state.keep}) |
935 | 961 |
957 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) | 983 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) |
958 tmpwctx = repo[node] | 984 tmpwctx = repo[node] |
959 return tmpwctx, addedbefore | 985 return tmpwctx, addedbefore |
960 | 986 |
961 | 987 |
962 def _unshelverestorecommit(ui, repo, tr, basename): | 988 def _unshelverestorecommit(ui, repo, tr, basename: bytes): |
963 """Recreate commit in the repository during the unshelve""" | 989 """Recreate commit in the repository during the unshelve""" |
964 repo = repo.unfiltered() | 990 repo = repo.unfiltered() |
965 node = None | 991 node = None |
966 shelf = ShelfDir(repo).get(basename) | 992 shelf = ShelfDir(repo).get(basename) |
967 if shelf.hasinfo(): | 993 if shelf.hasinfo(): |
978 shelvectx = repo[node] | 1004 shelvectx = repo[node] |
979 | 1005 |
980 return repo, shelvectx | 1006 return repo, shelvectx |
981 | 1007 |
982 | 1008 |
983 def _createunshelvectx(ui, repo, shelvectx, basename, interactive, opts): | 1009 def _createunshelvectx( |
1010 ui, repo, shelvectx, basename: bytes, interactive: bool, opts | |
1011 ) -> Tuple[bytes, bool]: | |
984 """Handles the creation of unshelve commit and updates the shelve if it | 1012 """Handles the creation of unshelve commit and updates the shelve if it |
985 was partially unshelved. | 1013 was partially unshelved. |
986 | 1014 |
987 If interactive is: | 1015 If interactive is: |
988 | 1016 |
1040 ui, | 1068 ui, |
1041 repo, | 1069 repo, |
1042 opts, | 1070 opts, |
1043 tr, | 1071 tr, |
1044 oldtiprev, | 1072 oldtiprev, |
1045 basename, | 1073 basename: bytes, |
1046 pctx, | 1074 pctx, |
1047 tmpwctx, | 1075 tmpwctx, |
1048 shelvectx, | 1076 shelvectx, |
1049 branchtorestore, | 1077 branchtorestore, |
1050 activebookmark, | 1078 activebookmark, |
1111 merge.update(tmpwctx) | 1139 merge.update(tmpwctx) |
1112 | 1140 |
1113 return shelvectx, ispartialunshelve | 1141 return shelvectx, ispartialunshelve |
1114 | 1142 |
1115 | 1143 |
1116 def _forgetunknownfiles(repo, shelvectx, addedbefore): | 1144 def _forgetunknownfiles(repo, shelvectx, addedbefore) -> None: |
1117 # Forget any files that were unknown before the shelve, unknown before | 1145 # Forget any files that were unknown before the shelve, unknown before |
1118 # unshelve started, but are now added. | 1146 # unshelve started, but are now added. |
1119 shelveunknown = shelvectx.extra().get(b'shelve_unknown') | 1147 shelveunknown = shelvectx.extra().get(b'shelve_unknown') |
1120 if not shelveunknown: | 1148 if not shelveunknown: |
1121 return | 1149 return |
1123 addedafter = frozenset(repo.status().added) | 1151 addedafter = frozenset(repo.status().added) |
1124 toforget = (addedafter & shelveunknown) - addedbefore | 1152 toforget = (addedafter & shelveunknown) - addedbefore |
1125 repo[None].forget(toforget) | 1153 repo[None].forget(toforget) |
1126 | 1154 |
1127 | 1155 |
1128 def _finishunshelve(repo, oldtiprev, tr, activebookmark): | 1156 def _finishunshelve(repo, oldtiprev, tr, activebookmark) -> None: |
1129 _restoreactivebookmark(repo, activebookmark) | 1157 _restoreactivebookmark(repo, activebookmark) |
1130 # We used to manually strip the commit to update inmemory structure and | 1158 # We used to manually strip the commit to update inmemory structure and |
1131 # prevent some issue around hooks. This no longer seems to be the case, so | 1159 # prevent some issue around hooks. This no longer seems to be the case, so |
1132 # we simply abort the transaction. | 1160 # we simply abort the transaction. |
1133 _aborttransaction(repo, tr) | 1161 _aborttransaction(repo, tr) |
1134 | 1162 |
1135 | 1163 |
1136 def _checkunshelveuntrackedproblems(ui, repo, shelvectx): | 1164 def _checkunshelveuntrackedproblems(ui, repo, shelvectx) -> None: |
1137 """Check potential problems which may result from working | 1165 """Check potential problems which may result from working |
1138 copy having untracked changes.""" | 1166 copy having untracked changes.""" |
1139 wcdeleted = set(repo.status().deleted) | 1167 wcdeleted = set(repo.status().deleted) |
1140 shelvetouched = set(shelvectx.files()) | 1168 shelvetouched = set(shelvectx.files()) |
1141 intersection = wcdeleted.intersection(shelvetouched) | 1169 intersection = wcdeleted.intersection(shelvetouched) |
1143 m = _(b"shelved change touches missing files") | 1171 m = _(b"shelved change touches missing files") |
1144 hint = _(b"run hg status to see which files are missing") | 1172 hint = _(b"run hg status to see which files are missing") |
1145 raise error.Abort(m, hint=hint) | 1173 raise error.Abort(m, hint=hint) |
1146 | 1174 |
1147 | 1175 |
1148 def unshelvecmd(ui, repo, *shelved, **opts): | 1176 def unshelvecmd(ui, repo, *shelved, **opts) -> None: |
1149 opts = pycompat.byteskwargs(opts) | 1177 opts = pycompat.byteskwargs(opts) |
1150 abortf = opts.get(b'abort') | 1178 abortf = opts.get(b'abort') |
1151 continuef = opts.get(b'continue') | 1179 continuef = opts.get(b'continue') |
1152 interactive = opts.get(b'interactive') | 1180 interactive = opts.get(b'interactive') |
1153 if not abortf and not continuef: | 1181 if not abortf and not continuef: |
1180 raise error.InputError( | 1208 raise error.InputError( |
1181 _(b'cannot use both continue and interactive') | 1209 _(b'cannot use both continue and interactive') |
1182 ) | 1210 ) |
1183 elif continuef: | 1211 elif continuef: |
1184 return unshelvecontinue(ui, repo, state, opts) | 1212 return unshelvecontinue(ui, repo, state, opts) |
1213 else: | |
1214 # Unreachable code, but help type checkers not think that | |
1215 # 'basename' may be used before initialization when checking | |
1216 # ShelfDir below. | |
1217 raise error.ProgrammingError("neither abort nor continue specified") | |
1185 elif len(shelved) > 1: | 1218 elif len(shelved) > 1: |
1186 raise error.InputError(_(b'can only unshelve one change at a time')) | 1219 raise error.InputError(_(b'can only unshelve one change at a time')) |
1187 elif not shelved: | 1220 elif not shelved: |
1188 shelved = ShelfDir(repo).listshelves() | 1221 shelved = ShelfDir(repo).listshelves() |
1189 if not shelved: | 1222 if not shelved: |
1197 raise error.InputError(_(b"shelved change '%s' not found") % basename) | 1230 raise error.InputError(_(b"shelved change '%s' not found") % basename) |
1198 | 1231 |
1199 return _dounshelve(ui, repo, basename, opts) | 1232 return _dounshelve(ui, repo, basename, opts) |
1200 | 1233 |
1201 | 1234 |
1202 def _dounshelve(ui, repo, basename, opts): | 1235 def _dounshelve(ui, repo, basename: bytes, opts) -> None: |
1203 repo = repo.unfiltered() | 1236 repo = repo.unfiltered() |
1204 lock = tr = None | 1237 lock = tr = None |
1205 try: | 1238 try: |
1206 lock = repo.lock() | 1239 lock = repo.lock() |
1207 tr = repo.transaction(b'unshelve', report=lambda x: None) | 1240 tr = repo.transaction(b'unshelve', report=lambda x: None) |