##// END OF EJS Templates
shelve: use a context manager for file I/O in listcmd
Bryan O'Sullivan -
r27775:3ea35a07 default
parent child Browse files
Show More
@@ -1,856 +1,853 b''
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
24 24 import collections
25 25 import itertools
26 26 from mercurial.i18n import _
27 27 from mercurial.node import nullid, nullrev, bin, hex
28 28 from mercurial import changegroup, cmdutil, scmutil, phases, commands
29 29 from mercurial import error, hg, mdiff, merge, patch, repair, util
30 30 from mercurial import templatefilters, exchange, bundlerepo, bundle2
31 31 from mercurial import lock as lockmod
32 32 from hgext import rebase
33 33 import errno
34 34
35 35 cmdtable = {}
36 36 command = cmdutil.command(cmdtable)
37 37 # Note for extension authors: ONLY specify testedwith = 'internal' for
38 38 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
39 39 # be specifying the version(s) of Mercurial they are tested with, or
40 40 # leave the attribute unspecified.
41 41 testedwith = 'internal'
42 42
43 43 backupdir = 'shelve-backup'
44 44
45 45 class shelvedfile(object):
46 46 """Helper for the file storing a single shelve
47 47
48 48 Handles common functions on shelve files (.hg/.patch) using
49 49 the vfs layer"""
50 50 def __init__(self, repo, name, filetype=None):
51 51 self.repo = repo
52 52 self.name = name
53 53 self.vfs = scmutil.vfs(repo.join('shelved'))
54 54 self.backupvfs = scmutil.vfs(repo.join(backupdir))
55 55 self.ui = self.repo.ui
56 56 if filetype:
57 57 self.fname = name + '.' + filetype
58 58 else:
59 59 self.fname = name
60 60
61 61 def exists(self):
62 62 return self.vfs.exists(self.fname)
63 63
64 64 def filename(self):
65 65 return self.vfs.join(self.fname)
66 66
67 67 def backupfilename(self):
68 68 def gennames(base):
69 69 yield base
70 70 base, ext = base.rsplit('.', 1)
71 71 for i in itertools.count(1):
72 72 yield '%s-%d.%s' % (base, i, ext)
73 73
74 74 name = self.backupvfs.join(self.fname)
75 75 for n in gennames(name):
76 76 if not self.backupvfs.exists(n):
77 77 return n
78 78
79 79 def movetobackup(self):
80 80 if not self.backupvfs.isdir():
81 81 self.backupvfs.makedir()
82 82 util.rename(self.filename(), self.backupfilename())
83 83
84 84 def stat(self):
85 85 return self.vfs.stat(self.fname)
86 86
87 87 def opener(self, mode='rb'):
88 88 try:
89 89 return self.vfs(self.fname, mode)
90 90 except IOError as err:
91 91 if err.errno != errno.ENOENT:
92 92 raise
93 93 raise error.Abort(_("shelved change '%s' not found") % self.name)
94 94
95 95 def applybundle(self):
96 96 fp = self.opener()
97 97 try:
98 98 gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs)
99 99 if not isinstance(gen, bundle2.unbundle20):
100 100 gen.apply(self.repo, 'unshelve',
101 101 'bundle:' + self.vfs.join(self.fname),
102 102 targetphase=phases.secret)
103 103 if isinstance(gen, bundle2.unbundle20):
104 104 bundle2.applybundle(self.repo, gen,
105 105 self.repo.currenttransaction(),
106 106 source='unshelve',
107 107 url='bundle:' + self.vfs.join(self.fname))
108 108 finally:
109 109 fp.close()
110 110
111 111 def bundlerepo(self):
112 112 return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root,
113 113 self.vfs.join(self.fname))
114 114 def writebundle(self, bases, node):
115 115 btype = 'HG10BZ'
116 116 cgversion = '01'
117 117 compression = None
118 118 if 'generaldelta' in self.repo.requirements:
119 119 btype = 'HG20'
120 120 cgversion = '02'
121 121 compression = 'BZ'
122 122
123 123 cg = changegroup.changegroupsubset(self.repo, bases, [node], 'shelve',
124 124 version=cgversion)
125 125 changegroup.writebundle(self.ui, cg, self.fname, btype, self.vfs,
126 126 compression=compression)
127 127
128 128 class shelvedstate(object):
129 129 """Handle persistence during unshelving operations.
130 130
131 131 Handles saving and restoring a shelved state. Ensures that different
132 132 versions of a shelved state are possible and handles them appropriately.
133 133 """
134 134 _version = 1
135 135 _filename = 'shelvedstate'
136 136
137 137 @classmethod
138 138 def load(cls, repo):
139 139 fp = repo.vfs(cls._filename)
140 140 try:
141 141 version = int(fp.readline().strip())
142 142
143 143 if version != cls._version:
144 144 raise error.Abort(_('this version of shelve is incompatible '
145 145 'with the version used in this repo'))
146 146 name = fp.readline().strip()
147 147 wctx = fp.readline().strip()
148 148 pendingctx = fp.readline().strip()
149 149 parents = [bin(h) for h in fp.readline().split()]
150 150 stripnodes = [bin(h) for h in fp.readline().split()]
151 151 finally:
152 152 fp.close()
153 153
154 154 obj = cls()
155 155 obj.name = name
156 156 obj.wctx = repo[bin(wctx)]
157 157 obj.pendingctx = repo[bin(pendingctx)]
158 158 obj.parents = parents
159 159 obj.stripnodes = stripnodes
160 160
161 161 return obj
162 162
163 163 @classmethod
164 164 def save(cls, repo, name, originalwctx, pendingctx, stripnodes):
165 165 fp = repo.vfs(cls._filename, 'wb')
166 166 fp.write('%i\n' % cls._version)
167 167 fp.write('%s\n' % name)
168 168 fp.write('%s\n' % hex(originalwctx.node()))
169 169 fp.write('%s\n' % hex(pendingctx.node()))
170 170 fp.write('%s\n' % ' '.join([hex(p) for p in repo.dirstate.parents()]))
171 171 fp.write('%s\n' % ' '.join([hex(n) for n in stripnodes]))
172 172 fp.close()
173 173
174 174 @classmethod
175 175 def clear(cls, repo):
176 176 util.unlinkpath(repo.join(cls._filename), ignoremissing=True)
177 177
178 178 def cleanupoldbackups(repo):
179 179 vfs = scmutil.vfs(repo.join(backupdir))
180 180 maxbackups = repo.ui.configint('shelve', 'maxbackups', 10)
181 181 hgfiles = [f for f in vfs.listdir() if f.endswith('.hg')]
182 182 hgfiles = sorted([(vfs.stat(f).st_mtime, f) for f in hgfiles])
183 183 if 0 < maxbackups and maxbackups < len(hgfiles):
184 184 bordermtime = hgfiles[-maxbackups][0]
185 185 else:
186 186 bordermtime = None
187 187 for mtime, f in hgfiles[:len(hgfiles) - maxbackups]:
188 188 if mtime == bordermtime:
189 189 # keep it, because timestamp can't decide exact order of backups
190 190 continue
191 191 base = f[:-3]
192 192 for ext in 'hg patch'.split():
193 193 try:
194 194 vfs.unlink(base + '.' + ext)
195 195 except OSError as err:
196 196 if err.errno != errno.ENOENT:
197 197 raise
198 198
199 199 def _aborttransaction(repo):
200 200 '''Abort current transaction for shelve/unshelve, but keep dirstate
201 201 '''
202 202 backupname = 'dirstate.shelve'
203 203 dirstatebackup = None
204 204 try:
205 205 # create backup of (un)shelved dirstate, because aborting transaction
206 206 # should restore dirstate to one at the beginning of the
207 207 # transaction, which doesn't include the result of (un)shelving
208 208 fp = repo.vfs.open(backupname, "w")
209 209 dirstatebackup = backupname
210 210 # clearing _dirty/_dirtypl of dirstate by _writedirstate below
211 211 # is unintentional. but it doesn't cause problem in this case,
212 212 # because no code path refers them until transaction is aborted.
213 213 repo.dirstate._writedirstate(fp) # write in-memory changes forcibly
214 214
215 215 tr = repo.currenttransaction()
216 216 tr.abort()
217 217
218 218 # restore to backuped dirstate
219 219 repo.vfs.rename(dirstatebackup, 'dirstate')
220 220 dirstatebackup = None
221 221 finally:
222 222 if dirstatebackup:
223 223 repo.vfs.unlink(dirstatebackup)
224 224
225 225 def createcmd(ui, repo, pats, opts):
226 226 """subcommand that creates a new shelve"""
227 227 wlock = repo.wlock()
228 228 try:
229 229 cmdutil.checkunfinished(repo)
230 230 return _docreatecmd(ui, repo, pats, opts)
231 231 finally:
232 232 lockmod.release(wlock)
233 233
234 234 def _docreatecmd(ui, repo, pats, opts):
235 235 def mutableancestors(ctx):
236 236 """return all mutable ancestors for ctx (included)
237 237
238 238 Much faster than the revset ancestors(ctx) & draft()"""
239 239 seen = set([nullrev])
240 240 visit = collections.deque()
241 241 visit.append(ctx)
242 242 while visit:
243 243 ctx = visit.popleft()
244 244 yield ctx.node()
245 245 for parent in ctx.parents():
246 246 rev = parent.rev()
247 247 if rev not in seen:
248 248 seen.add(rev)
249 249 if parent.mutable():
250 250 visit.append(parent)
251 251
252 252 wctx = repo[None]
253 253 parents = wctx.parents()
254 254 if len(parents) > 1:
255 255 raise error.Abort(_('cannot shelve while merging'))
256 256 parent = parents[0]
257 257
258 258 # we never need the user, so we use a generic user for all shelve operations
259 259 user = 'shelve@localhost'
260 260 label = repo._activebookmark or parent.branch() or 'default'
261 261
262 262 # slashes aren't allowed in filenames, therefore we rename it
263 263 label = label.replace('/', '_')
264 264
265 265 def gennames():
266 266 yield label
267 267 for i in xrange(1, 100):
268 268 yield '%s-%02d' % (label, i)
269 269
270 270 def commitfunc(ui, repo, message, match, opts):
271 271 hasmq = util.safehasattr(repo, 'mq')
272 272 if hasmq:
273 273 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
274 274 backup = repo.ui.backupconfig('phases', 'new-commit')
275 275 try:
276 276 repo.ui. setconfig('phases', 'new-commit', phases.secret)
277 277 editor = cmdutil.getcommiteditor(editform='shelve.shelve', **opts)
278 278 return repo.commit(message, user, opts.get('date'), match,
279 279 editor=editor)
280 280 finally:
281 281 repo.ui.restoreconfig(backup)
282 282 if hasmq:
283 283 repo.mq.checkapplied = saved
284 284
285 285 if parent.node() != nullid:
286 286 desc = "changes to: %s" % parent.description().split('\n', 1)[0]
287 287 else:
288 288 desc = '(changes in empty repository)'
289 289
290 290 if not opts['message']:
291 291 opts['message'] = desc
292 292
293 293 name = opts['name']
294 294
295 295 lock = tr = None
296 296 try:
297 297 lock = repo.lock()
298 298
299 299 # use an uncommitted transaction to generate the bundle to avoid
300 300 # pull races. ensure we don't print the abort message to stderr.
301 301 tr = repo.transaction('commit', report=lambda x: None)
302 302
303 303 if name:
304 304 if shelvedfile(repo, name, 'hg').exists():
305 305 raise error.Abort(_("a shelved change named '%s' already exists"
306 306 ) % name)
307 307 else:
308 308 for n in gennames():
309 309 if not shelvedfile(repo, n, 'hg').exists():
310 310 name = n
311 311 break
312 312 else:
313 313 raise error.Abort(_("too many shelved changes named '%s'") %
314 314 label)
315 315
316 316 # ensure we are not creating a subdirectory or a hidden file
317 317 if '/' in name or '\\' in name:
318 318 raise error.Abort(_('shelved change names may not contain slashes'))
319 319 if name.startswith('.'):
320 320 raise error.Abort(_("shelved change names may not start with '.'"))
321 321 interactive = opts.get('interactive', False)
322 322
323 323 def interactivecommitfunc(ui, repo, *pats, **opts):
324 324 match = scmutil.match(repo['.'], pats, {})
325 325 message = opts['message']
326 326 return commitfunc(ui, repo, message, match, opts)
327 327 if not interactive:
328 328 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
329 329 else:
330 330 node = cmdutil.dorecord(ui, repo, interactivecommitfunc, None,
331 331 False, cmdutil.recordfilter, *pats, **opts)
332 332 if not node:
333 333 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
334 334 if stat.deleted:
335 335 ui.status(_("nothing changed (%d missing files, see "
336 336 "'hg status')\n") % len(stat.deleted))
337 337 else:
338 338 ui.status(_("nothing changed\n"))
339 339 return 1
340 340
341 341 bases = list(mutableancestors(repo[node]))
342 342 shelvedfile(repo, name, 'hg').writebundle(bases, node)
343 343 cmdutil.export(repo, [node],
344 344 fp=shelvedfile(repo, name, 'patch').opener('wb'),
345 345 opts=mdiff.diffopts(git=True))
346 346
347 347
348 348 if ui.formatted():
349 349 desc = util.ellipsis(desc, ui.termwidth())
350 350 ui.status(_('shelved as %s\n') % name)
351 351 hg.update(repo, parent.node())
352 352
353 353 _aborttransaction(repo)
354 354 finally:
355 355 lockmod.release(tr, lock)
356 356
357 357 def cleanupcmd(ui, repo):
358 358 """subcommand that deletes all shelves"""
359 359
360 360 wlock = None
361 361 try:
362 362 wlock = repo.wlock()
363 363 for (name, _type) in repo.vfs.readdir('shelved'):
364 364 suffix = name.rsplit('.', 1)[-1]
365 365 if suffix in ('hg', 'patch'):
366 366 shelvedfile(repo, name).movetobackup()
367 367 cleanupoldbackups(repo)
368 368 finally:
369 369 lockmod.release(wlock)
370 370
371 371 def deletecmd(ui, repo, pats):
372 372 """subcommand that deletes a specific shelve"""
373 373 if not pats:
374 374 raise error.Abort(_('no shelved changes specified!'))
375 375 wlock = repo.wlock()
376 376 try:
377 377 for name in pats:
378 378 for suffix in 'hg patch'.split():
379 379 shelvedfile(repo, name, suffix).movetobackup()
380 380 cleanupoldbackups(repo)
381 381 except OSError as err:
382 382 if err.errno != errno.ENOENT:
383 383 raise
384 384 raise error.Abort(_("shelved change '%s' not found") % name)
385 385 finally:
386 386 lockmod.release(wlock)
387 387
388 388 def listshelves(repo):
389 389 """return all shelves in repo as list of (time, filename)"""
390 390 try:
391 391 names = repo.vfs.readdir('shelved')
392 392 except OSError as err:
393 393 if err.errno != errno.ENOENT:
394 394 raise
395 395 return []
396 396 info = []
397 397 for (name, _type) in names:
398 398 pfx, sfx = name.rsplit('.', 1)
399 399 if not pfx or sfx != 'patch':
400 400 continue
401 401 st = shelvedfile(repo, name).stat()
402 402 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
403 403 return sorted(info, reverse=True)
404 404
405 405 def listcmd(ui, repo, pats, opts):
406 406 """subcommand that displays the list of shelves"""
407 407 pats = set(pats)
408 408 width = 80
409 409 if not ui.plain():
410 410 width = ui.termwidth()
411 411 namelabel = 'shelve.newest'
412 412 for mtime, name in listshelves(repo):
413 413 sname = util.split(name)[1]
414 414 if pats and sname not in pats:
415 415 continue
416 416 ui.write(sname, label=namelabel)
417 417 namelabel = 'shelve.name'
418 418 if ui.quiet:
419 419 ui.write('\n')
420 420 continue
421 421 ui.write(' ' * (16 - len(sname)))
422 422 used = 16
423 423 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
424 424 ui.write(age, label='shelve.age')
425 425 ui.write(' ' * (12 - len(age)))
426 426 used += 12
427 fp = open(name + '.patch', 'rb')
428 try:
427 with open(name + '.patch', 'rb') as fp:
429 428 while True:
430 429 line = fp.readline()
431 430 if not line:
432 431 break
433 432 if not line.startswith('#'):
434 433 desc = line.rstrip()
435 434 if ui.formatted():
436 435 desc = util.ellipsis(desc, width - used)
437 436 ui.write(desc)
438 437 break
439 438 ui.write('\n')
440 439 if not (opts['patch'] or opts['stat']):
441 440 continue
442 441 difflines = fp.readlines()
443 442 if opts['patch']:
444 443 for chunk, label in patch.difflabel(iter, difflines):
445 444 ui.write(chunk, label=label)
446 445 if opts['stat']:
447 446 for chunk, label in patch.diffstatui(difflines, width=width,
448 447 git=True):
449 448 ui.write(chunk, label=label)
450 finally:
451 fp.close()
452 449
453 450 def singlepatchcmds(ui, repo, pats, opts, subcommand):
454 451 """subcommand that displays a single shelf"""
455 452 if len(pats) != 1:
456 453 raise error.Abort(_("--%s expects a single shelf") % subcommand)
457 454 shelfname = pats[0]
458 455
459 456 if not shelvedfile(repo, shelfname, 'patch').exists():
460 457 raise error.Abort(_("cannot find shelf %s") % shelfname)
461 458
462 459 listcmd(ui, repo, pats, opts)
463 460
464 461 def checkparents(repo, state):
465 462 """check parent while resuming an unshelve"""
466 463 if state.parents != repo.dirstate.parents():
467 464 raise error.Abort(_('working directory parents do not match unshelve '
468 465 'state'))
469 466
470 467 def pathtofiles(repo, files):
471 468 cwd = repo.getcwd()
472 469 return [repo.pathto(f, cwd) for f in files]
473 470
474 471 def unshelveabort(ui, repo, state, opts):
475 472 """subcommand that abort an in-progress unshelve"""
476 473 lock = None
477 474 try:
478 475 checkparents(repo, state)
479 476
480 477 util.rename(repo.join('unshelverebasestate'),
481 478 repo.join('rebasestate'))
482 479 try:
483 480 rebase.rebase(ui, repo, **{
484 481 'abort' : True
485 482 })
486 483 except Exception:
487 484 util.rename(repo.join('rebasestate'),
488 485 repo.join('unshelverebasestate'))
489 486 raise
490 487
491 488 lock = repo.lock()
492 489
493 490 mergefiles(ui, repo, state.wctx, state.pendingctx)
494 491
495 492 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
496 493 finally:
497 494 shelvedstate.clear(repo)
498 495 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
499 496 lockmod.release(lock)
500 497
501 498 def mergefiles(ui, repo, wctx, shelvectx):
502 499 """updates to wctx and merges the changes from shelvectx into the
503 500 dirstate."""
504 501 oldquiet = ui.quiet
505 502 try:
506 503 ui.quiet = True
507 504 hg.update(repo, wctx.node())
508 505 files = []
509 506 files.extend(shelvectx.files())
510 507 files.extend(shelvectx.parents()[0].files())
511 508
512 509 # revert will overwrite unknown files, so move them out of the way
513 510 for file in repo.status(unknown=True).unknown:
514 511 if file in files:
515 512 util.rename(file, scmutil.origpath(ui, repo, file))
516 513 ui.pushbuffer(True)
517 514 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
518 515 *pathtofiles(repo, files),
519 516 **{'no_backup': True})
520 517 ui.popbuffer()
521 518 finally:
522 519 ui.quiet = oldquiet
523 520
524 521 def unshelvecleanup(ui, repo, name, opts):
525 522 """remove related files after an unshelve"""
526 523 if not opts['keep']:
527 524 for filetype in 'hg patch'.split():
528 525 shelvedfile(repo, name, filetype).movetobackup()
529 526 cleanupoldbackups(repo)
530 527
531 528 def unshelvecontinue(ui, repo, state, opts):
532 529 """subcommand to continue an in-progress unshelve"""
533 530 # We're finishing off a merge. First parent is our original
534 531 # parent, second is the temporary "fake" commit we're unshelving.
535 532 lock = None
536 533 try:
537 534 checkparents(repo, state)
538 535 ms = merge.mergestate.read(repo)
539 536 if [f for f in ms if ms[f] == 'u']:
540 537 raise error.Abort(
541 538 _("unresolved conflicts, can't continue"),
542 539 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
543 540
544 541 lock = repo.lock()
545 542
546 543 util.rename(repo.join('unshelverebasestate'),
547 544 repo.join('rebasestate'))
548 545 try:
549 546 rebase.rebase(ui, repo, **{
550 547 'continue' : True
551 548 })
552 549 except Exception:
553 550 util.rename(repo.join('rebasestate'),
554 551 repo.join('unshelverebasestate'))
555 552 raise
556 553
557 554 shelvectx = repo['tip']
558 555 if not shelvectx in state.pendingctx.children():
559 556 # rebase was a no-op, so it produced no child commit
560 557 shelvectx = state.pendingctx
561 558 else:
562 559 # only strip the shelvectx if the rebase produced it
563 560 state.stripnodes.append(shelvectx.node())
564 561
565 562 mergefiles(ui, repo, state.wctx, shelvectx)
566 563
567 564 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
568 565 shelvedstate.clear(repo)
569 566 unshelvecleanup(ui, repo, state.name, opts)
570 567 ui.status(_("unshelve of '%s' complete\n") % state.name)
571 568 finally:
572 569 lockmod.release(lock)
573 570
574 571 @command('unshelve',
575 572 [('a', 'abort', None,
576 573 _('abort an incomplete unshelve operation')),
577 574 ('c', 'continue', None,
578 575 _('continue an incomplete unshelve operation')),
579 576 ('k', 'keep', None,
580 577 _('keep shelve after unshelving')),
581 578 ('t', 'tool', '', _('specify merge tool')),
582 579 ('', 'date', '',
583 580 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
584 581 _('hg unshelve [SHELVED]'))
585 582 def unshelve(ui, repo, *shelved, **opts):
586 583 """restore a shelved change to the working directory
587 584
588 585 This command accepts an optional name of a shelved change to
589 586 restore. If none is given, the most recent shelved change is used.
590 587
591 588 If a shelved change is applied successfully, the bundle that
592 589 contains the shelved changes is moved to a backup location
593 590 (.hg/shelve-backup).
594 591
595 592 Since you can restore a shelved change on top of an arbitrary
596 593 commit, it is possible that unshelving will result in a conflict
597 594 between your changes and the commits you are unshelving onto. If
598 595 this occurs, you must resolve the conflict, then use
599 596 ``--continue`` to complete the unshelve operation. (The bundle
600 597 will not be moved until you successfully complete the unshelve.)
601 598
602 599 (Alternatively, you can use ``--abort`` to abandon an unshelve
603 600 that causes a conflict. This reverts the unshelved changes, and
604 601 leaves the bundle in place.)
605 602
606 603 After a successful unshelve, the shelved changes are stored in a
607 604 backup directory. Only the N most recent backups are kept. N
608 605 defaults to 10 but can be overridden using the ``shelve.maxbackups``
609 606 configuration option.
610 607
611 608 .. container:: verbose
612 609
613 610 Timestamp in seconds is used to decide order of backups. More
614 611 than ``maxbackups`` backups are kept, if same timestamp
615 612 prevents from deciding exact order of them, for safety.
616 613 """
617 614 wlock = repo.wlock()
618 615 try:
619 616 return _dounshelve(ui, repo, *shelved, **opts)
620 617 finally:
621 618 lockmod.release(wlock)
622 619
623 620 def _dounshelve(ui, repo, *shelved, **opts):
624 621 abortf = opts['abort']
625 622 continuef = opts['continue']
626 623 if not abortf and not continuef:
627 624 cmdutil.checkunfinished(repo)
628 625
629 626 if abortf or continuef:
630 627 if abortf and continuef:
631 628 raise error.Abort(_('cannot use both abort and continue'))
632 629 if shelved:
633 630 raise error.Abort(_('cannot combine abort/continue with '
634 631 'naming a shelved change'))
635 632 if abortf and opts.get('tool', False):
636 633 ui.warn(_('tool option will be ignored\n'))
637 634
638 635 try:
639 636 state = shelvedstate.load(repo)
640 637 except IOError as err:
641 638 if err.errno != errno.ENOENT:
642 639 raise
643 640 raise error.Abort(_('no unshelve operation underway'))
644 641
645 642 if abortf:
646 643 return unshelveabort(ui, repo, state, opts)
647 644 elif continuef:
648 645 return unshelvecontinue(ui, repo, state, opts)
649 646 elif len(shelved) > 1:
650 647 raise error.Abort(_('can only unshelve one change at a time'))
651 648 elif not shelved:
652 649 shelved = listshelves(repo)
653 650 if not shelved:
654 651 raise error.Abort(_('no shelved changes to apply!'))
655 652 basename = util.split(shelved[0][1])[1]
656 653 ui.status(_("unshelving change '%s'\n") % basename)
657 654 else:
658 655 basename = shelved[0]
659 656
660 657 if not shelvedfile(repo, basename, 'patch').exists():
661 658 raise error.Abort(_("shelved change '%s' not found") % basename)
662 659
663 660 oldquiet = ui.quiet
664 661 lock = tr = None
665 662 forcemerge = ui.backupconfig('ui', 'forcemerge')
666 663 try:
667 664 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
668 665 lock = repo.lock()
669 666
670 667 tr = repo.transaction('unshelve', report=lambda x: None)
671 668 oldtiprev = len(repo)
672 669
673 670 pctx = repo['.']
674 671 tmpwctx = pctx
675 672 # The goal is to have a commit structure like so:
676 673 # ...-> pctx -> tmpwctx -> shelvectx
677 674 # where tmpwctx is an optional commit with the user's pending changes
678 675 # and shelvectx is the unshelved changes. Then we merge it all down
679 676 # to the original pctx.
680 677
681 678 # Store pending changes in a commit
682 679 s = repo.status()
683 680 if s.modified or s.added or s.removed or s.deleted:
684 681 ui.status(_("temporarily committing pending changes "
685 682 "(restore with 'hg unshelve --abort')\n"))
686 683 def commitfunc(ui, repo, message, match, opts):
687 684 hasmq = util.safehasattr(repo, 'mq')
688 685 if hasmq:
689 686 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
690 687
691 688 backup = repo.ui.backupconfig('phases', 'new-commit')
692 689 try:
693 690 repo.ui.setconfig('phases', 'new-commit', phases.secret)
694 691 return repo.commit(message, 'shelve@localhost',
695 692 opts.get('date'), match)
696 693 finally:
697 694 repo.ui.restoreconfig(backup)
698 695 if hasmq:
699 696 repo.mq.checkapplied = saved
700 697
701 698 tempopts = {}
702 699 tempopts['message'] = "pending changes temporary commit"
703 700 tempopts['date'] = opts.get('date')
704 701 ui.quiet = True
705 702 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
706 703 tmpwctx = repo[node]
707 704
708 705 ui.quiet = True
709 706 shelvedfile(repo, basename, 'hg').applybundle()
710 707
711 708 ui.quiet = oldquiet
712 709
713 710 shelvectx = repo['tip']
714 711
715 712 # If the shelve is not immediately on top of the commit
716 713 # we'll be merging with, rebase it to be on top.
717 714 if tmpwctx.node() != shelvectx.parents()[0].node():
718 715 ui.status(_('rebasing shelved changes\n'))
719 716 try:
720 717 rebase.rebase(ui, repo, **{
721 718 'rev' : [shelvectx.rev()],
722 719 'dest' : str(tmpwctx.rev()),
723 720 'keep' : True,
724 721 'tool' : opts.get('tool', ''),
725 722 })
726 723 except error.InterventionRequired:
727 724 tr.close()
728 725
729 726 stripnodes = [repo.changelog.node(rev)
730 727 for rev in xrange(oldtiprev, len(repo))]
731 728 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes)
732 729
733 730 util.rename(repo.join('rebasestate'),
734 731 repo.join('unshelverebasestate'))
735 732 raise error.InterventionRequired(
736 733 _("unresolved conflicts (see 'hg resolve', then "
737 734 "'hg unshelve --continue')"))
738 735
739 736 # refresh ctx after rebase completes
740 737 shelvectx = repo['tip']
741 738
742 739 if not shelvectx in tmpwctx.children():
743 740 # rebase was a no-op, so it produced no child commit
744 741 shelvectx = tmpwctx
745 742
746 743 mergefiles(ui, repo, pctx, shelvectx)
747 744 shelvedstate.clear(repo)
748 745
749 746 # The transaction aborting will strip all the commits for us,
750 747 # but it doesn't update the inmemory structures, so addchangegroup
751 748 # hooks still fire and try to operate on the missing commits.
752 749 # Clean up manually to prevent this.
753 750 repo.unfiltered().changelog.strip(oldtiprev, tr)
754 751
755 752 unshelvecleanup(ui, repo, basename, opts)
756 753
757 754 _aborttransaction(repo)
758 755 finally:
759 756 ui.quiet = oldquiet
760 757 if tr:
761 758 tr.release()
762 759 lockmod.release(lock)
763 760 ui.restoreconfig(forcemerge)
764 761
765 762 @command('shelve',
766 763 [('A', 'addremove', None,
767 764 _('mark new/missing files as added/removed before shelving')),
768 765 ('', 'cleanup', None,
769 766 _('delete all shelved changes')),
770 767 ('', 'date', '',
771 768 _('shelve with the specified commit date'), _('DATE')),
772 769 ('d', 'delete', None,
773 770 _('delete the named shelved change(s)')),
774 771 ('e', 'edit', False,
775 772 _('invoke editor on commit messages')),
776 773 ('l', 'list', None,
777 774 _('list current shelves')),
778 775 ('m', 'message', '',
779 776 _('use text as shelve message'), _('TEXT')),
780 777 ('n', 'name', '',
781 778 _('use the given name for the shelved commit'), _('NAME')),
782 779 ('p', 'patch', None,
783 780 _('show patch')),
784 781 ('i', 'interactive', None,
785 782 _('interactive mode, only works while creating a shelve')),
786 783 ('', 'stat', None,
787 784 _('output diffstat-style summary of changes'))] + commands.walkopts,
788 785 _('hg shelve [OPTION]... [FILE]...'))
789 786 def shelvecmd(ui, repo, *pats, **opts):
790 787 '''save and set aside changes from the working directory
791 788
792 789 Shelving takes files that "hg status" reports as not clean, saves
793 790 the modifications to a bundle (a shelved change), and reverts the
794 791 files so that their state in the working directory becomes clean.
795 792
796 793 To restore these changes to the working directory, using "hg
797 794 unshelve"; this will work even if you switch to a different
798 795 commit.
799 796
800 797 When no files are specified, "hg shelve" saves all not-clean
801 798 files. If specific files or directories are named, only changes to
802 799 those files are shelved.
803 800
804 801 Each shelved change has a name that makes it easier to find later.
805 802 The name of a shelved change defaults to being based on the active
806 803 bookmark, or if there is no active bookmark, the current named
807 804 branch. To specify a different name, use ``--name``.
808 805
809 806 To see a list of existing shelved changes, use the ``--list``
810 807 option. For each shelved change, this will print its name, age,
811 808 and description; use ``--patch`` or ``--stat`` for more details.
812 809
813 810 To delete specific shelved changes, use ``--delete``. To delete
814 811 all shelved changes, use ``--cleanup``.
815 812 '''
816 813 allowables = [
817 814 ('addremove', set(['create'])), # 'create' is pseudo action
818 815 ('cleanup', set(['cleanup'])),
819 816 # ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
820 817 ('delete', set(['delete'])),
821 818 ('edit', set(['create'])),
822 819 ('list', set(['list'])),
823 820 ('message', set(['create'])),
824 821 ('name', set(['create'])),
825 822 ('patch', set(['patch', 'list'])),
826 823 ('stat', set(['stat', 'list'])),
827 824 ]
828 825 def checkopt(opt):
829 826 if opts[opt]:
830 827 for i, allowable in allowables:
831 828 if opts[i] and opt not in allowable:
832 829 raise error.Abort(_("options '--%s' and '--%s' may not be "
833 830 "used together") % (opt, i))
834 831 return True
835 832 if checkopt('cleanup'):
836 833 if pats:
837 834 raise error.Abort(_("cannot specify names when using '--cleanup'"))
838 835 return cleanupcmd(ui, repo)
839 836 elif checkopt('delete'):
840 837 return deletecmd(ui, repo, pats)
841 838 elif checkopt('list'):
842 839 return listcmd(ui, repo, pats, opts)
843 840 elif checkopt('patch'):
844 841 return singlepatchcmds(ui, repo, pats, opts, subcommand='patch')
845 842 elif checkopt('stat'):
846 843 return singlepatchcmds(ui, repo, pats, opts, subcommand='stat')
847 844 else:
848 845 return createcmd(ui, repo, pats, opts)
849 846
850 847 def extsetup(ui):
851 848 cmdutil.unfinishedstates.append(
852 849 [shelvedstate._filename, False, False,
853 850 _('unshelve already in progress'),
854 851 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
855 852 cmdutil.afterresolvedstates.append(
856 853 [shelvedstate._filename, _('hg unshelve --continue')])
General Comments 0
You need to be logged in to leave comments. Login now