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