##// END OF EJS Templates
py3: handle keyword arguments in hgext/shelve.py...
Pulkit Goyal -
r35006:aad6b9fd default
parent child Browse files
Show More
@@ -1,1054 +1,1058
1 1 # shelve.py - save/restore working directory state
2 2 #
3 3 # Copyright 2013 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """save and restore changes to the working directory
9 9
10 10 The "hg shelve" command saves changes made to the working directory
11 11 and reverts those changes, resetting the working directory to a clean
12 12 state.
13 13
14 14 Later on, the "hg unshelve" command restores the changes saved by "hg
15 15 shelve". Changes can be restored even after updating to a different
16 16 parent, in which case Mercurial's merge machinery will resolve any
17 17 conflicts if necessary.
18 18
19 19 You can have more than one shelved change outstanding at a time; each
20 20 shelved change has a distinct name. For details, see the help for "hg
21 21 shelve".
22 22 """
23 23 from __future__ import absolute_import
24 24
25 25 import collections
26 26 import errno
27 27 import itertools
28 28
29 29 from mercurial.i18n import _
30 30 from mercurial import (
31 31 bookmarks,
32 32 bundle2,
33 33 bundlerepo,
34 34 changegroup,
35 35 cmdutil,
36 36 discovery,
37 37 error,
38 38 exchange,
39 39 hg,
40 40 lock as lockmod,
41 41 mdiff,
42 42 merge,
43 43 node as nodemod,
44 44 patch,
45 45 phases,
46 pycompat,
46 47 registrar,
47 48 repair,
48 49 scmutil,
49 50 templatefilters,
50 51 util,
51 52 vfs as vfsmod,
52 53 )
53 54
54 55 from . import (
55 56 rebase,
56 57 )
57 58
58 59 cmdtable = {}
59 60 command = registrar.command(cmdtable)
60 61 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
61 62 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
62 63 # be specifying the version(s) of Mercurial they are tested with, or
63 64 # leave the attribute unspecified.
64 65 testedwith = 'ships-with-hg-core'
65 66
66 67 configtable = {}
67 68 configitem = registrar.configitem(configtable)
68 69
69 70 configitem('shelve', 'maxbackups',
70 71 default=10,
71 72 )
72 73
73 74 backupdir = 'shelve-backup'
74 75 shelvedir = 'shelved'
75 76 shelvefileextensions = ['hg', 'patch', 'oshelve']
76 77 # universal extension is present in all types of shelves
77 78 patchextension = 'patch'
78 79
79 80 # we never need the user, so we use a
80 81 # generic user for all shelve operations
81 82 shelveuser = 'shelve@localhost'
82 83
83 84 class shelvedfile(object):
84 85 """Helper for the file storing a single shelve
85 86
86 87 Handles common functions on shelve files (.hg/.patch) using
87 88 the vfs layer"""
88 89 def __init__(self, repo, name, filetype=None):
89 90 self.repo = repo
90 91 self.name = name
91 92 self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir))
92 93 self.backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
93 94 self.ui = self.repo.ui
94 95 if filetype:
95 96 self.fname = name + '.' + filetype
96 97 else:
97 98 self.fname = name
98 99
99 100 def exists(self):
100 101 return self.vfs.exists(self.fname)
101 102
102 103 def filename(self):
103 104 return self.vfs.join(self.fname)
104 105
105 106 def backupfilename(self):
106 107 def gennames(base):
107 108 yield base
108 109 base, ext = base.rsplit('.', 1)
109 110 for i in itertools.count(1):
110 111 yield '%s-%d.%s' % (base, i, ext)
111 112
112 113 name = self.backupvfs.join(self.fname)
113 114 for n in gennames(name):
114 115 if not self.backupvfs.exists(n):
115 116 return n
116 117
117 118 def movetobackup(self):
118 119 if not self.backupvfs.isdir():
119 120 self.backupvfs.makedir()
120 121 util.rename(self.filename(), self.backupfilename())
121 122
122 123 def stat(self):
123 124 return self.vfs.stat(self.fname)
124 125
125 126 def opener(self, mode='rb'):
126 127 try:
127 128 return self.vfs(self.fname, mode)
128 129 except IOError as err:
129 130 if err.errno != errno.ENOENT:
130 131 raise
131 132 raise error.Abort(_("shelved change '%s' not found") % self.name)
132 133
133 134 def applybundle(self):
134 135 fp = self.opener()
135 136 try:
136 137 gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs)
137 138 bundle2.applybundle(self.repo, gen, self.repo.currenttransaction(),
138 139 source='unshelve',
139 140 url='bundle:' + self.vfs.join(self.fname),
140 141 targetphase=phases.secret)
141 142 finally:
142 143 fp.close()
143 144
144 145 def bundlerepo(self):
145 146 return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root,
146 147 self.vfs.join(self.fname))
147 148 def writebundle(self, bases, node):
148 149 cgversion = changegroup.safeversion(self.repo)
149 150 if cgversion == '01':
150 151 btype = 'HG10BZ'
151 152 compression = None
152 153 else:
153 154 btype = 'HG20'
154 155 compression = 'BZ'
155 156
156 157 outgoing = discovery.outgoing(self.repo, missingroots=bases,
157 158 missingheads=[node])
158 159 cg = changegroup.makechangegroup(self.repo, outgoing, cgversion,
159 160 'shelve')
160 161
161 162 bundle2.writebundle(self.ui, cg, self.fname, btype, self.vfs,
162 163 compression=compression)
163 164
164 165 def writeobsshelveinfo(self, info):
165 166 scmutil.simplekeyvaluefile(self.vfs, self.fname).write(info)
166 167
167 168 def readobsshelveinfo(self):
168 169 return scmutil.simplekeyvaluefile(self.vfs, self.fname).read()
169 170
170 171 class shelvedstate(object):
171 172 """Handle persistence during unshelving operations.
172 173
173 174 Handles saving and restoring a shelved state. Ensures that different
174 175 versions of a shelved state are possible and handles them appropriately.
175 176 """
176 177 _version = 2
177 178 _filename = 'shelvedstate'
178 179 _keep = 'keep'
179 180 _nokeep = 'nokeep'
180 181 # colon is essential to differentiate from a real bookmark name
181 182 _noactivebook = ':no-active-bookmark'
182 183
183 184 @classmethod
184 185 def _verifyandtransform(cls, d):
185 186 """Some basic shelvestate syntactic verification and transformation"""
186 187 try:
187 188 d['originalwctx'] = nodemod.bin(d['originalwctx'])
188 189 d['pendingctx'] = nodemod.bin(d['pendingctx'])
189 190 d['parents'] = [nodemod.bin(h)
190 191 for h in d['parents'].split(' ')]
191 192 d['nodestoremove'] = [nodemod.bin(h)
192 193 for h in d['nodestoremove'].split(' ')]
193 194 except (ValueError, TypeError, KeyError) as err:
194 195 raise error.CorruptedState(str(err))
195 196
196 197 @classmethod
197 198 def _getversion(cls, repo):
198 199 """Read version information from shelvestate file"""
199 200 fp = repo.vfs(cls._filename)
200 201 try:
201 202 version = int(fp.readline().strip())
202 203 except ValueError as err:
203 204 raise error.CorruptedState(str(err))
204 205 finally:
205 206 fp.close()
206 207 return version
207 208
208 209 @classmethod
209 210 def _readold(cls, repo):
210 211 """Read the old position-based version of a shelvestate file"""
211 212 # Order is important, because old shelvestate file uses it
212 213 # to detemine values of fields (i.g. name is on the second line,
213 214 # originalwctx is on the third and so forth). Please do not change.
214 215 keys = ['version', 'name', 'originalwctx', 'pendingctx', 'parents',
215 216 'nodestoremove', 'branchtorestore', 'keep', 'activebook']
216 217 # this is executed only seldomly, so it is not a big deal
217 218 # that we open this file twice
218 219 fp = repo.vfs(cls._filename)
219 220 d = {}
220 221 try:
221 222 for key in keys:
222 223 d[key] = fp.readline().strip()
223 224 finally:
224 225 fp.close()
225 226 return d
226 227
227 228 @classmethod
228 229 def load(cls, repo):
229 230 version = cls._getversion(repo)
230 231 if version < cls._version:
231 232 d = cls._readold(repo)
232 233 elif version == cls._version:
233 234 d = scmutil.simplekeyvaluefile(repo.vfs, cls._filename)\
234 235 .read(firstlinenonkeyval=True)
235 236 else:
236 237 raise error.Abort(_('this version of shelve is incompatible '
237 238 'with the version used in this repo'))
238 239
239 240 cls._verifyandtransform(d)
240 241 try:
241 242 obj = cls()
242 243 obj.name = d['name']
243 244 obj.wctx = repo[d['originalwctx']]
244 245 obj.pendingctx = repo[d['pendingctx']]
245 246 obj.parents = d['parents']
246 247 obj.nodestoremove = d['nodestoremove']
247 248 obj.branchtorestore = d.get('branchtorestore', '')
248 249 obj.keep = d.get('keep') == cls._keep
249 250 obj.activebookmark = ''
250 251 if d.get('activebook', '') != cls._noactivebook:
251 252 obj.activebookmark = d.get('activebook', '')
252 253 except (error.RepoLookupError, KeyError) as err:
253 254 raise error.CorruptedState(str(err))
254 255
255 256 return obj
256 257
257 258 @classmethod
258 259 def save(cls, repo, name, originalwctx, pendingctx, nodestoremove,
259 260 branchtorestore, keep=False, activebook=''):
260 261 info = {
261 262 "name": name,
262 263 "originalwctx": nodemod.hex(originalwctx.node()),
263 264 "pendingctx": nodemod.hex(pendingctx.node()),
264 265 "parents": ' '.join([nodemod.hex(p)
265 266 for p in repo.dirstate.parents()]),
266 267 "nodestoremove": ' '.join([nodemod.hex(n)
267 268 for n in nodestoremove]),
268 269 "branchtorestore": branchtorestore,
269 270 "keep": cls._keep if keep else cls._nokeep,
270 271 "activebook": activebook or cls._noactivebook
271 272 }
272 273 scmutil.simplekeyvaluefile(repo.vfs, cls._filename)\
273 274 .write(info, firstline=str(cls._version))
274 275
275 276 @classmethod
276 277 def clear(cls, repo):
277 278 repo.vfs.unlinkpath(cls._filename, ignoremissing=True)
278 279
279 280 def cleanupoldbackups(repo):
280 281 vfs = vfsmod.vfs(repo.vfs.join(backupdir))
281 282 maxbackups = repo.ui.configint('shelve', 'maxbackups')
282 283 hgfiles = [f for f in vfs.listdir()
283 284 if f.endswith('.' + patchextension)]
284 285 hgfiles = sorted([(vfs.stat(f).st_mtime, f) for f in hgfiles])
285 286 if 0 < maxbackups and maxbackups < len(hgfiles):
286 287 bordermtime = hgfiles[-maxbackups][0]
287 288 else:
288 289 bordermtime = None
289 290 for mtime, f in hgfiles[:len(hgfiles) - maxbackups]:
290 291 if mtime == bordermtime:
291 292 # keep it, because timestamp can't decide exact order of backups
292 293 continue
293 294 base = f[:-(1 + len(patchextension))]
294 295 for ext in shelvefileextensions:
295 296 vfs.tryunlink(base + '.' + ext)
296 297
297 298 def _backupactivebookmark(repo):
298 299 activebookmark = repo._activebookmark
299 300 if activebookmark:
300 301 bookmarks.deactivate(repo)
301 302 return activebookmark
302 303
303 304 def _restoreactivebookmark(repo, mark):
304 305 if mark:
305 306 bookmarks.activate(repo, mark)
306 307
307 308 def _aborttransaction(repo):
308 309 '''Abort current transaction for shelve/unshelve, but keep dirstate
309 310 '''
310 311 tr = repo.currenttransaction()
311 312 backupname = 'dirstate.shelve'
312 313 repo.dirstate.savebackup(tr, backupname)
313 314 tr.abort()
314 315 repo.dirstate.restorebackup(None, backupname)
315 316
316 317 def createcmd(ui, repo, pats, opts):
317 318 """subcommand that creates a new shelve"""
318 319 with repo.wlock():
319 320 cmdutil.checkunfinished(repo)
320 321 return _docreatecmd(ui, repo, pats, opts)
321 322
322 323 def getshelvename(repo, parent, opts):
323 324 """Decide on the name this shelve is going to have"""
324 325 def gennames():
325 326 yield label
326 327 for i in itertools.count(1):
327 328 yield '%s-%02d' % (label, i)
328 329 name = opts.get('name')
329 330 label = repo._activebookmark or parent.branch() or 'default'
330 331 # slashes aren't allowed in filenames, therefore we rename it
331 332 label = label.replace('/', '_')
332 333 label = label.replace('\\', '_')
333 334 # filenames must not start with '.' as it should not be hidden
334 335 if label.startswith('.'):
335 336 label = label.replace('.', '_', 1)
336 337
337 338 if name:
338 339 if shelvedfile(repo, name, patchextension).exists():
339 340 e = _("a shelved change named '%s' already exists") % name
340 341 raise error.Abort(e)
341 342
342 343 # ensure we are not creating a subdirectory or a hidden file
343 344 if '/' in name or '\\' in name:
344 345 raise error.Abort(_('shelved change names can not contain slashes'))
345 346 if name.startswith('.'):
346 347 raise error.Abort(_("shelved change names can not start with '.'"))
347 348
348 349 else:
349 350 for n in gennames():
350 351 if not shelvedfile(repo, n, patchextension).exists():
351 352 name = n
352 353 break
353 354
354 355 return name
355 356
356 357 def mutableancestors(ctx):
357 358 """return all mutable ancestors for ctx (included)
358 359
359 360 Much faster than the revset ancestors(ctx) & draft()"""
360 361 seen = {nodemod.nullrev}
361 362 visit = collections.deque()
362 363 visit.append(ctx)
363 364 while visit:
364 365 ctx = visit.popleft()
365 366 yield ctx.node()
366 367 for parent in ctx.parents():
367 368 rev = parent.rev()
368 369 if rev not in seen:
369 370 seen.add(rev)
370 371 if parent.mutable():
371 372 visit.append(parent)
372 373
373 374 def getcommitfunc(extra, interactive, editor=False):
374 375 def commitfunc(ui, repo, message, match, opts):
375 376 hasmq = util.safehasattr(repo, 'mq')
376 377 if hasmq:
377 378 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
378 379 overrides = {('phases', 'new-commit'): phases.secret}
379 380 try:
380 381 editor_ = False
381 382 if editor:
382 383 editor_ = cmdutil.getcommiteditor(editform='shelve.shelve',
383 **opts)
384 **pycompat.strkwargs(opts))
384 385 with repo.ui.configoverride(overrides):
385 386 return repo.commit(message, shelveuser, opts.get('date'),
386 387 match, editor=editor_, extra=extra)
387 388 finally:
388 389 if hasmq:
389 390 repo.mq.checkapplied = saved
390 391
391 392 def interactivecommitfunc(ui, repo, *pats, **opts):
393 opts = pycompat.byteskwargs(opts)
392 394 match = scmutil.match(repo['.'], pats, {})
393 395 message = opts['message']
394 396 return commitfunc(ui, repo, message, match, opts)
395 397
396 398 return interactivecommitfunc if interactive else commitfunc
397 399
398 400 def _nothingtoshelvemessaging(ui, repo, pats, opts):
399 401 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
400 402 if stat.deleted:
401 403 ui.status(_("nothing changed (%d missing files, see "
402 404 "'hg status')\n") % len(stat.deleted))
403 405 else:
404 406 ui.status(_("nothing changed\n"))
405 407
406 408 def _shelvecreatedcommit(repo, node, name):
407 409 bases = list(mutableancestors(repo[node]))
408 410 shelvedfile(repo, name, 'hg').writebundle(bases, node)
409 411 cmdutil.export(repo, [node],
410 412 fp=shelvedfile(repo, name, patchextension).opener('wb'),
411 413 opts=mdiff.diffopts(git=True))
412 414
413 415 def _includeunknownfiles(repo, pats, opts, extra):
414 416 s = repo.status(match=scmutil.match(repo[None], pats, opts),
415 417 unknown=True)
416 418 if s.unknown:
417 419 extra['shelve_unknown'] = '\0'.join(s.unknown)
418 420 repo[None].add(s.unknown)
419 421
420 422 def _finishshelve(repo):
421 423 _aborttransaction(repo)
422 424
423 425 def _docreatecmd(ui, repo, pats, opts):
424 426 wctx = repo[None]
425 427 parents = wctx.parents()
426 428 if len(parents) > 1:
427 429 raise error.Abort(_('cannot shelve while merging'))
428 430 parent = parents[0]
429 431 origbranch = wctx.branch()
430 432
431 433 if parent.node() != nodemod.nullid:
432 434 desc = "changes to: %s" % parent.description().split('\n', 1)[0]
433 435 else:
434 436 desc = '(changes in empty repository)'
435 437
436 438 if not opts.get('message'):
437 439 opts['message'] = desc
438 440
439 441 lock = tr = activebookmark = None
440 442 try:
441 443 lock = repo.lock()
442 444
443 445 # use an uncommitted transaction to generate the bundle to avoid
444 446 # pull races. ensure we don't print the abort message to stderr.
445 447 tr = repo.transaction('commit', report=lambda x: None)
446 448
447 449 interactive = opts.get('interactive', False)
448 450 includeunknown = (opts.get('unknown', False) and
449 451 not opts.get('addremove', False))
450 452
451 453 name = getshelvename(repo, parent, opts)
452 454 activebookmark = _backupactivebookmark(repo)
453 455 extra = {}
454 456 if includeunknown:
455 457 _includeunknownfiles(repo, pats, opts, extra)
456 458
457 459 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
458 460 # In non-bare shelve we don't store newly created branch
459 461 # at bundled commit
460 462 repo.dirstate.setbranch(repo['.'].branch())
461 463
462 464 commitfunc = getcommitfunc(extra, interactive, editor=True)
463 465 if not interactive:
464 466 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
465 467 else:
466 468 node = cmdutil.dorecord(ui, repo, commitfunc, None,
467 469 False, cmdutil.recordfilter, *pats,
468 **opts)
470 **pycompat.strkwargs(opts))
469 471 if not node:
470 472 _nothingtoshelvemessaging(ui, repo, pats, opts)
471 473 return 1
472 474
473 475 _shelvecreatedcommit(repo, node, name)
474 476
475 477 if ui.formatted():
476 478 desc = util.ellipsis(desc, ui.termwidth())
477 479 ui.status(_('shelved as %s\n') % name)
478 480 hg.update(repo, parent.node())
479 481 if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
480 482 repo.dirstate.setbranch(origbranch)
481 483
482 484 _finishshelve(repo)
483 485 finally:
484 486 _restoreactivebookmark(repo, activebookmark)
485 487 lockmod.release(tr, lock)
486 488
487 489 def _isbareshelve(pats, opts):
488 490 return (not pats
489 491 and not opts.get('interactive', False)
490 492 and not opts.get('include', False)
491 493 and not opts.get('exclude', False))
492 494
493 495 def _iswctxonnewbranch(repo):
494 496 return repo[None].branch() != repo['.'].branch()
495 497
496 498 def cleanupcmd(ui, repo):
497 499 """subcommand that deletes all shelves"""
498 500
499 501 with repo.wlock():
500 502 for (name, _type) in repo.vfs.readdir(shelvedir):
501 503 suffix = name.rsplit('.', 1)[-1]
502 504 if suffix in shelvefileextensions:
503 505 shelvedfile(repo, name).movetobackup()
504 506 cleanupoldbackups(repo)
505 507
506 508 def deletecmd(ui, repo, pats):
507 509 """subcommand that deletes a specific shelve"""
508 510 if not pats:
509 511 raise error.Abort(_('no shelved changes specified!'))
510 512 with repo.wlock():
511 513 try:
512 514 for name in pats:
513 515 for suffix in shelvefileextensions:
514 516 shfile = shelvedfile(repo, name, suffix)
515 517 # patch file is necessary, as it should
516 518 # be present for any kind of shelve,
517 519 # but the .hg file is optional as in future we
518 520 # will add obsolete shelve with does not create a
519 521 # bundle
520 522 if shfile.exists() or suffix == patchextension:
521 523 shfile.movetobackup()
522 524 cleanupoldbackups(repo)
523 525 except OSError as err:
524 526 if err.errno != errno.ENOENT:
525 527 raise
526 528 raise error.Abort(_("shelved change '%s' not found") % name)
527 529
528 530 def listshelves(repo):
529 531 """return all shelves in repo as list of (time, filename)"""
530 532 try:
531 533 names = repo.vfs.readdir(shelvedir)
532 534 except OSError as err:
533 535 if err.errno != errno.ENOENT:
534 536 raise
535 537 return []
536 538 info = []
537 539 for (name, _type) in names:
538 540 pfx, sfx = name.rsplit('.', 1)
539 541 if not pfx or sfx != patchextension:
540 542 continue
541 543 st = shelvedfile(repo, name).stat()
542 544 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
543 545 return sorted(info, reverse=True)
544 546
545 547 def listcmd(ui, repo, pats, opts):
546 548 """subcommand that displays the list of shelves"""
547 549 pats = set(pats)
548 550 width = 80
549 551 if not ui.plain():
550 552 width = ui.termwidth()
551 553 namelabel = 'shelve.newest'
552 554 ui.pager('shelve')
553 555 for mtime, name in listshelves(repo):
554 556 sname = util.split(name)[1]
555 557 if pats and sname not in pats:
556 558 continue
557 559 ui.write(sname, label=namelabel)
558 560 namelabel = 'shelve.name'
559 561 if ui.quiet:
560 562 ui.write('\n')
561 563 continue
562 564 ui.write(' ' * (16 - len(sname)))
563 565 used = 16
564 566 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
565 567 ui.write(age, label='shelve.age')
566 568 ui.write(' ' * (12 - len(age)))
567 569 used += 12
568 570 with open(name + '.' + patchextension, 'rb') as fp:
569 571 while True:
570 572 line = fp.readline()
571 573 if not line:
572 574 break
573 575 if not line.startswith('#'):
574 576 desc = line.rstrip()
575 577 if ui.formatted():
576 578 desc = util.ellipsis(desc, width - used)
577 579 ui.write(desc)
578 580 break
579 581 ui.write('\n')
580 582 if not (opts['patch'] or opts['stat']):
581 583 continue
582 584 difflines = fp.readlines()
583 585 if opts['patch']:
584 586 for chunk, label in patch.difflabel(iter, difflines):
585 587 ui.write(chunk, label=label)
586 588 if opts['stat']:
587 589 for chunk, label in patch.diffstatui(difflines, width=width):
588 590 ui.write(chunk, label=label)
589 591
590 592 def patchcmds(ui, repo, pats, opts, subcommand):
591 593 """subcommand that displays shelves"""
592 594 if len(pats) == 0:
593 595 raise error.Abort(_("--%s expects at least one shelf") % subcommand)
594 596
595 597 for shelfname in pats:
596 598 if not shelvedfile(repo, shelfname, patchextension).exists():
597 599 raise error.Abort(_("cannot find shelf %s") % shelfname)
598 600
599 601 listcmd(ui, repo, pats, opts)
600 602
601 603 def checkparents(repo, state):
602 604 """check parent while resuming an unshelve"""
603 605 if state.parents != repo.dirstate.parents():
604 606 raise error.Abort(_('working directory parents do not match unshelve '
605 607 'state'))
606 608
607 609 def pathtofiles(repo, files):
608 610 cwd = repo.getcwd()
609 611 return [repo.pathto(f, cwd) for f in files]
610 612
611 613 def unshelveabort(ui, repo, state, opts):
612 614 """subcommand that abort an in-progress unshelve"""
613 615 with repo.lock():
614 616 try:
615 617 checkparents(repo, state)
616 618
617 619 repo.vfs.rename('unshelverebasestate', 'rebasestate')
618 620 try:
619 621 rebase.rebase(ui, repo, **{
620 622 'abort' : True
621 623 })
622 624 except Exception:
623 625 repo.vfs.rename('rebasestate', 'unshelverebasestate')
624 626 raise
625 627
626 628 mergefiles(ui, repo, state.wctx, state.pendingctx)
627 629 repair.strip(ui, repo, state.nodestoremove, backup=False,
628 630 topic='shelve')
629 631 finally:
630 632 shelvedstate.clear(repo)
631 633 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
632 634
633 635 def mergefiles(ui, repo, wctx, shelvectx):
634 636 """updates to wctx and merges the changes from shelvectx into the
635 637 dirstate."""
636 638 with ui.configoverride({('ui', 'quiet'): True}):
637 639 hg.update(repo, wctx.node())
638 640 files = []
639 641 files.extend(shelvectx.files())
640 642 files.extend(shelvectx.parents()[0].files())
641 643
642 644 # revert will overwrite unknown files, so move them out of the way
643 645 for file in repo.status(unknown=True).unknown:
644 646 if file in files:
645 647 util.rename(file, scmutil.origpath(ui, repo, file))
646 648 ui.pushbuffer(True)
647 649 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
648 650 *pathtofiles(repo, files),
649 651 **{'no_backup': True})
650 652 ui.popbuffer()
651 653
652 654 def restorebranch(ui, repo, branchtorestore):
653 655 if branchtorestore and branchtorestore != repo.dirstate.branch():
654 656 repo.dirstate.setbranch(branchtorestore)
655 657 ui.status(_('marked working directory as branch %s\n')
656 658 % branchtorestore)
657 659
658 660 def unshelvecleanup(ui, repo, name, opts):
659 661 """remove related files after an unshelve"""
660 662 if not opts.get('keep'):
661 663 for filetype in shelvefileextensions:
662 664 shfile = shelvedfile(repo, name, filetype)
663 665 if shfile.exists():
664 666 shfile.movetobackup()
665 667 cleanupoldbackups(repo)
666 668
667 669 def unshelvecontinue(ui, repo, state, opts):
668 670 """subcommand to continue an in-progress unshelve"""
669 671 # We're finishing off a merge. First parent is our original
670 672 # parent, second is the temporary "fake" commit we're unshelving.
671 673 with repo.lock():
672 674 checkparents(repo, state)
673 675 ms = merge.mergestate.read(repo)
674 676 if list(ms.unresolved()):
675 677 raise error.Abort(
676 678 _("unresolved conflicts, can't continue"),
677 679 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
678 680
679 681 repo.vfs.rename('unshelverebasestate', 'rebasestate')
680 682 try:
681 683 rebase.rebase(ui, repo, **{
682 684 'continue' : True
683 685 })
684 686 except Exception:
685 687 repo.vfs.rename('rebasestate', 'unshelverebasestate')
686 688 raise
687 689
688 690 shelvectx = repo['tip']
689 691 if state.pendingctx not in shelvectx.parents():
690 692 # rebase was a no-op, so it produced no child commit
691 693 shelvectx = state.pendingctx
692 694 else:
693 695 # only strip the shelvectx if the rebase produced it
694 696 state.nodestoremove.append(shelvectx.node())
695 697
696 698 mergefiles(ui, repo, state.wctx, shelvectx)
697 699 restorebranch(ui, repo, state.branchtorestore)
698 700
699 701 repair.strip(ui, repo, state.nodestoremove, backup=False,
700 702 topic='shelve')
701 703 _restoreactivebookmark(repo, state.activebookmark)
702 704 shelvedstate.clear(repo)
703 705 unshelvecleanup(ui, repo, state.name, opts)
704 706 ui.status(_("unshelve of '%s' complete\n") % state.name)
705 707
706 708 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
707 709 """Temporarily commit working copy changes before moving unshelve commit"""
708 710 # Store pending changes in a commit and remember added in case a shelve
709 711 # contains unknown files that are part of the pending change
710 712 s = repo.status()
711 713 addedbefore = frozenset(s.added)
712 714 if not (s.modified or s.added or s.removed):
713 715 return tmpwctx, addedbefore
714 716 ui.status(_("temporarily committing pending changes "
715 717 "(restore with 'hg unshelve --abort')\n"))
716 718 commitfunc = getcommitfunc(extra=None, interactive=False,
717 719 editor=False)
718 720 tempopts = {}
719 721 tempopts['message'] = "pending changes temporary commit"
720 722 tempopts['date'] = opts.get('date')
721 723 with ui.configoverride({('ui', 'quiet'): True}):
722 724 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
723 725 tmpwctx = repo[node]
724 726 return tmpwctx, addedbefore
725 727
726 728 def _unshelverestorecommit(ui, repo, basename):
727 729 """Recreate commit in the repository during the unshelve"""
728 730 with ui.configoverride({('ui', 'quiet'): True}):
729 731 shelvedfile(repo, basename, 'hg').applybundle()
730 732 shelvectx = repo['tip']
731 733 return repo, shelvectx
732 734
733 735 def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
734 736 tmpwctx, shelvectx, branchtorestore,
735 737 activebookmark):
736 738 """Rebase restored commit from its original location to a destination"""
737 739 # If the shelve is not immediately on top of the commit
738 740 # we'll be merging with, rebase it to be on top.
739 741 if tmpwctx.node() == shelvectx.parents()[0].node():
740 742 return shelvectx
741 743
742 744 ui.status(_('rebasing shelved changes\n'))
743 745 try:
744 746 rebase.rebase(ui, repo, **{
745 747 'rev': [shelvectx.rev()],
746 748 'dest': str(tmpwctx.rev()),
747 749 'keep': True,
748 750 'tool': opts.get('tool', ''),
749 751 })
750 752 except error.InterventionRequired:
751 753 tr.close()
752 754
753 755 nodestoremove = [repo.changelog.node(rev)
754 756 for rev in xrange(oldtiprev, len(repo))]
755 757 shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove,
756 758 branchtorestore, opts.get('keep'), activebookmark)
757 759
758 760 repo.vfs.rename('rebasestate', 'unshelverebasestate')
759 761 raise error.InterventionRequired(
760 762 _("unresolved conflicts (see 'hg resolve', then "
761 763 "'hg unshelve --continue')"))
762 764
763 765 # refresh ctx after rebase completes
764 766 shelvectx = repo['tip']
765 767
766 768 if tmpwctx not in shelvectx.parents():
767 769 # rebase was a no-op, so it produced no child commit
768 770 shelvectx = tmpwctx
769 771 return shelvectx
770 772
771 773 def _forgetunknownfiles(repo, shelvectx, addedbefore):
772 774 # Forget any files that were unknown before the shelve, unknown before
773 775 # unshelve started, but are now added.
774 776 shelveunknown = shelvectx.extra().get('shelve_unknown')
775 777 if not shelveunknown:
776 778 return
777 779 shelveunknown = frozenset(shelveunknown.split('\0'))
778 780 addedafter = frozenset(repo.status().added)
779 781 toforget = (addedafter & shelveunknown) - addedbefore
780 782 repo[None].forget(toforget)
781 783
782 784 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
783 785 _restoreactivebookmark(repo, activebookmark)
784 786 # The transaction aborting will strip all the commits for us,
785 787 # but it doesn't update the inmemory structures, so addchangegroup
786 788 # hooks still fire and try to operate on the missing commits.
787 789 # Clean up manually to prevent this.
788 790 repo.unfiltered().changelog.strip(oldtiprev, tr)
789 791 _aborttransaction(repo)
790 792
791 793 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
792 794 """Check potential problems which may result from working
793 795 copy having untracked changes."""
794 796 wcdeleted = set(repo.status().deleted)
795 797 shelvetouched = set(shelvectx.files())
796 798 intersection = wcdeleted.intersection(shelvetouched)
797 799 if intersection:
798 800 m = _("shelved change touches missing files")
799 801 hint = _("run hg status to see which files are missing")
800 802 raise error.Abort(m, hint=hint)
801 803
802 804 @command('unshelve',
803 805 [('a', 'abort', None,
804 806 _('abort an incomplete unshelve operation')),
805 807 ('c', 'continue', None,
806 808 _('continue an incomplete unshelve operation')),
807 809 ('k', 'keep', None,
808 810 _('keep shelve after unshelving')),
809 811 ('n', 'name', '',
810 812 _('restore shelved change with given name'), _('NAME')),
811 813 ('t', 'tool', '', _('specify merge tool')),
812 814 ('', 'date', '',
813 815 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
814 816 _('hg unshelve [[-n] SHELVED]'))
815 817 def unshelve(ui, repo, *shelved, **opts):
816 818 """restore a shelved change to the working directory
817 819
818 820 This command accepts an optional name of a shelved change to
819 821 restore. If none is given, the most recent shelved change is used.
820 822
821 823 If a shelved change is applied successfully, the bundle that
822 824 contains the shelved changes is moved to a backup location
823 825 (.hg/shelve-backup).
824 826
825 827 Since you can restore a shelved change on top of an arbitrary
826 828 commit, it is possible that unshelving will result in a conflict
827 829 between your changes and the commits you are unshelving onto. If
828 830 this occurs, you must resolve the conflict, then use
829 831 ``--continue`` to complete the unshelve operation. (The bundle
830 832 will not be moved until you successfully complete the unshelve.)
831 833
832 834 (Alternatively, you can use ``--abort`` to abandon an unshelve
833 835 that causes a conflict. This reverts the unshelved changes, and
834 836 leaves the bundle in place.)
835 837
836 838 If bare shelved change(when no files are specified, without interactive,
837 839 include and exclude option) was done on newly created branch it would
838 840 restore branch information to the working directory.
839 841
840 842 After a successful unshelve, the shelved changes are stored in a
841 843 backup directory. Only the N most recent backups are kept. N
842 844 defaults to 10 but can be overridden using the ``shelve.maxbackups``
843 845 configuration option.
844 846
845 847 .. container:: verbose
846 848
847 849 Timestamp in seconds is used to decide order of backups. More
848 850 than ``maxbackups`` backups are kept, if same timestamp
849 851 prevents from deciding exact order of them, for safety.
850 852 """
851 853 with repo.wlock():
852 854 return _dounshelve(ui, repo, *shelved, **opts)
853 855
854 856 def _dounshelve(ui, repo, *shelved, **opts):
857 opts = pycompat.byteskwargs(opts)
855 858 abortf = opts.get('abort')
856 859 continuef = opts.get('continue')
857 860 if not abortf and not continuef:
858 861 cmdutil.checkunfinished(repo)
859 862 shelved = list(shelved)
860 863 if opts.get("name"):
861 864 shelved.append(opts["name"])
862 865
863 866 if abortf or continuef:
864 867 if abortf and continuef:
865 868 raise error.Abort(_('cannot use both abort and continue'))
866 869 if shelved:
867 870 raise error.Abort(_('cannot combine abort/continue with '
868 871 'naming a shelved change'))
869 872 if abortf and opts.get('tool', False):
870 873 ui.warn(_('tool option will be ignored\n'))
871 874
872 875 try:
873 876 state = shelvedstate.load(repo)
874 877 if opts.get('keep') is None:
875 878 opts['keep'] = state.keep
876 879 except IOError as err:
877 880 if err.errno != errno.ENOENT:
878 881 raise
879 882 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
880 883 except error.CorruptedState as err:
881 884 ui.debug(str(err) + '\n')
882 885 if continuef:
883 886 msg = _('corrupted shelved state file')
884 887 hint = _('please run hg unshelve --abort to abort unshelve '
885 888 'operation')
886 889 raise error.Abort(msg, hint=hint)
887 890 elif abortf:
888 891 msg = _('could not read shelved state file, your working copy '
889 892 'may be in an unexpected state\nplease update to some '
890 893 'commit\n')
891 894 ui.warn(msg)
892 895 shelvedstate.clear(repo)
893 896 return
894 897
895 898 if abortf:
896 899 return unshelveabort(ui, repo, state, opts)
897 900 elif continuef:
898 901 return unshelvecontinue(ui, repo, state, opts)
899 902 elif len(shelved) > 1:
900 903 raise error.Abort(_('can only unshelve one change at a time'))
901 904 elif not shelved:
902 905 shelved = listshelves(repo)
903 906 if not shelved:
904 907 raise error.Abort(_('no shelved changes to apply!'))
905 908 basename = util.split(shelved[0][1])[1]
906 909 ui.status(_("unshelving change '%s'\n") % basename)
907 910 else:
908 911 basename = shelved[0]
909 912
910 913 if not shelvedfile(repo, basename, patchextension).exists():
911 914 raise error.Abort(_("shelved change '%s' not found") % basename)
912 915
913 916 lock = tr = None
914 917 try:
915 918 lock = repo.lock()
916 919 tr = repo.transaction('unshelve', report=lambda x: None)
917 920 oldtiprev = len(repo)
918 921
919 922 pctx = repo['.']
920 923 tmpwctx = pctx
921 924 # The goal is to have a commit structure like so:
922 925 # ...-> pctx -> tmpwctx -> shelvectx
923 926 # where tmpwctx is an optional commit with the user's pending changes
924 927 # and shelvectx is the unshelved changes. Then we merge it all down
925 928 # to the original pctx.
926 929
927 930 activebookmark = _backupactivebookmark(repo)
928 931 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
929 932 with ui.configoverride(overrides, 'unshelve'):
930 933 tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
931 934 tmpwctx)
932 935 repo, shelvectx = _unshelverestorecommit(ui, repo, basename)
933 936 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
934 937 branchtorestore = ''
935 938 if shelvectx.branch() != shelvectx.p1().branch():
936 939 branchtorestore = shelvectx.branch()
937 940
938 941 shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
939 942 basename, pctx, tmpwctx,
940 943 shelvectx, branchtorestore,
941 944 activebookmark)
942 945 mergefiles(ui, repo, pctx, shelvectx)
943 946 restorebranch(ui, repo, branchtorestore)
944 947 _forgetunknownfiles(repo, shelvectx, addedbefore)
945 948
946 949 shelvedstate.clear(repo)
947 950 _finishunshelve(repo, oldtiprev, tr, activebookmark)
948 951 unshelvecleanup(ui, repo, basename, opts)
949 952 finally:
950 953 if tr:
951 954 tr.release()
952 955 lockmod.release(lock)
953 956
954 957 @command('shelve',
955 958 [('A', 'addremove', None,
956 959 _('mark new/missing files as added/removed before shelving')),
957 960 ('u', 'unknown', None,
958 961 _('store unknown files in the shelve')),
959 962 ('', 'cleanup', None,
960 963 _('delete all shelved changes')),
961 964 ('', 'date', '',
962 965 _('shelve with the specified commit date'), _('DATE')),
963 966 ('d', 'delete', None,
964 967 _('delete the named shelved change(s)')),
965 968 ('e', 'edit', False,
966 969 _('invoke editor on commit messages')),
967 970 ('l', 'list', None,
968 971 _('list current shelves')),
969 972 ('m', 'message', '',
970 973 _('use text as shelve message'), _('TEXT')),
971 974 ('n', 'name', '',
972 975 _('use the given name for the shelved commit'), _('NAME')),
973 976 ('p', 'patch', None,
974 977 _('show patch')),
975 978 ('i', 'interactive', None,
976 979 _('interactive mode, only works while creating a shelve')),
977 980 ('', 'stat', None,
978 981 _('output diffstat-style summary of changes'))] + cmdutil.walkopts,
979 982 _('hg shelve [OPTION]... [FILE]...'))
980 983 def shelvecmd(ui, repo, *pats, **opts):
981 984 '''save and set aside changes from the working directory
982 985
983 986 Shelving takes files that "hg status" reports as not clean, saves
984 987 the modifications to a bundle (a shelved change), and reverts the
985 988 files so that their state in the working directory becomes clean.
986 989
987 990 To restore these changes to the working directory, using "hg
988 991 unshelve"; this will work even if you switch to a different
989 992 commit.
990 993
991 994 When no files are specified, "hg shelve" saves all not-clean
992 995 files. If specific files or directories are named, only changes to
993 996 those files are shelved.
994 997
995 998 In bare shelve (when no files are specified, without interactive,
996 999 include and exclude option), shelving remembers information if the
997 1000 working directory was on newly created branch, in other words working
998 1001 directory was on different branch than its first parent. In this
999 1002 situation unshelving restores branch information to the working directory.
1000 1003
1001 1004 Each shelved change has a name that makes it easier to find later.
1002 1005 The name of a shelved change defaults to being based on the active
1003 1006 bookmark, or if there is no active bookmark, the current named
1004 1007 branch. To specify a different name, use ``--name``.
1005 1008
1006 1009 To see a list of existing shelved changes, use the ``--list``
1007 1010 option. For each shelved change, this will print its name, age,
1008 1011 and description; use ``--patch`` or ``--stat`` for more details.
1009 1012
1010 1013 To delete specific shelved changes, use ``--delete``. To delete
1011 1014 all shelved changes, use ``--cleanup``.
1012 1015 '''
1016 opts = pycompat.byteskwargs(opts)
1013 1017 allowables = [
1014 1018 ('addremove', {'create'}), # 'create' is pseudo action
1015 1019 ('unknown', {'create'}),
1016 1020 ('cleanup', {'cleanup'}),
1017 1021 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
1018 1022 ('delete', {'delete'}),
1019 1023 ('edit', {'create'}),
1020 1024 ('list', {'list'}),
1021 1025 ('message', {'create'}),
1022 1026 ('name', {'create'}),
1023 1027 ('patch', {'patch', 'list'}),
1024 1028 ('stat', {'stat', 'list'}),
1025 1029 ]
1026 1030 def checkopt(opt):
1027 1031 if opts.get(opt):
1028 1032 for i, allowable in allowables:
1029 1033 if opts[i] and opt not in allowable:
1030 1034 raise error.Abort(_("options '--%s' and '--%s' may not be "
1031 1035 "used together") % (opt, i))
1032 1036 return True
1033 1037 if checkopt('cleanup'):
1034 1038 if pats:
1035 1039 raise error.Abort(_("cannot specify names when using '--cleanup'"))
1036 1040 return cleanupcmd(ui, repo)
1037 1041 elif checkopt('delete'):
1038 1042 return deletecmd(ui, repo, pats)
1039 1043 elif checkopt('list'):
1040 1044 return listcmd(ui, repo, pats, opts)
1041 1045 elif checkopt('patch'):
1042 1046 return patchcmds(ui, repo, pats, opts, subcommand='patch')
1043 1047 elif checkopt('stat'):
1044 1048 return patchcmds(ui, repo, pats, opts, subcommand='stat')
1045 1049 else:
1046 1050 return createcmd(ui, repo, pats, opts)
1047 1051
1048 1052 def extsetup(ui):
1049 1053 cmdutil.unfinishedstates.append(
1050 1054 [shelvedstate._filename, False, False,
1051 1055 _('unshelve already in progress'),
1052 1056 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
1053 1057 cmdutil.afterresolvedstates.append(
1054 1058 [shelvedstate._filename, _('hg unshelve --continue')])
General Comments 0
You need to be logged in to leave comments. Login now