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)