##// END OF EJS Templates
with: use context manager in unshelveabort
Bryan O'Sullivan -
r27841:83995fdd default
parent child Browse files
Show More
@@ -1,836 +1,833
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 with repo.wlock():
228 228 cmdutil.checkunfinished(repo)
229 229 return _docreatecmd(ui, repo, pats, opts)
230 230
231 231 def _docreatecmd(ui, repo, pats, opts):
232 232 def mutableancestors(ctx):
233 233 """return all mutable ancestors for ctx (included)
234 234
235 235 Much faster than the revset ancestors(ctx) & draft()"""
236 236 seen = set([nullrev])
237 237 visit = collections.deque()
238 238 visit.append(ctx)
239 239 while visit:
240 240 ctx = visit.popleft()
241 241 yield ctx.node()
242 242 for parent in ctx.parents():
243 243 rev = parent.rev()
244 244 if rev not in seen:
245 245 seen.add(rev)
246 246 if parent.mutable():
247 247 visit.append(parent)
248 248
249 249 wctx = repo[None]
250 250 parents = wctx.parents()
251 251 if len(parents) > 1:
252 252 raise error.Abort(_('cannot shelve while merging'))
253 253 parent = parents[0]
254 254
255 255 # we never need the user, so we use a generic user for all shelve operations
256 256 user = 'shelve@localhost'
257 257 label = repo._activebookmark or parent.branch() or 'default'
258 258
259 259 # slashes aren't allowed in filenames, therefore we rename it
260 260 label = label.replace('/', '_')
261 261
262 262 def gennames():
263 263 yield label
264 264 for i in xrange(1, 100):
265 265 yield '%s-%02d' % (label, i)
266 266
267 267 def commitfunc(ui, repo, message, match, opts):
268 268 hasmq = util.safehasattr(repo, 'mq')
269 269 if hasmq:
270 270 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
271 271 backup = repo.ui.backupconfig('phases', 'new-commit')
272 272 try:
273 273 repo.ui. setconfig('phases', 'new-commit', phases.secret)
274 274 editor = cmdutil.getcommiteditor(editform='shelve.shelve', **opts)
275 275 return repo.commit(message, user, opts.get('date'), match,
276 276 editor=editor)
277 277 finally:
278 278 repo.ui.restoreconfig(backup)
279 279 if hasmq:
280 280 repo.mq.checkapplied = saved
281 281
282 282 if parent.node() != nullid:
283 283 desc = "changes to: %s" % parent.description().split('\n', 1)[0]
284 284 else:
285 285 desc = '(changes in empty repository)'
286 286
287 287 if not opts['message']:
288 288 opts['message'] = desc
289 289
290 290 name = opts['name']
291 291
292 292 lock = tr = None
293 293 try:
294 294 lock = repo.lock()
295 295
296 296 # use an uncommitted transaction to generate the bundle to avoid
297 297 # pull races. ensure we don't print the abort message to stderr.
298 298 tr = repo.transaction('commit', report=lambda x: None)
299 299
300 300 if name:
301 301 if shelvedfile(repo, name, 'hg').exists():
302 302 raise error.Abort(_("a shelved change named '%s' already exists"
303 303 ) % name)
304 304 else:
305 305 for n in gennames():
306 306 if not shelvedfile(repo, n, 'hg').exists():
307 307 name = n
308 308 break
309 309 else:
310 310 raise error.Abort(_("too many shelved changes named '%s'") %
311 311 label)
312 312
313 313 # ensure we are not creating a subdirectory or a hidden file
314 314 if '/' in name or '\\' in name:
315 315 raise error.Abort(_('shelved change names may not contain slashes'))
316 316 if name.startswith('.'):
317 317 raise error.Abort(_("shelved change names may not start with '.'"))
318 318 interactive = opts.get('interactive', False)
319 319
320 320 def interactivecommitfunc(ui, repo, *pats, **opts):
321 321 match = scmutil.match(repo['.'], pats, {})
322 322 message = opts['message']
323 323 return commitfunc(ui, repo, message, match, opts)
324 324 if not interactive:
325 325 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
326 326 else:
327 327 node = cmdutil.dorecord(ui, repo, interactivecommitfunc, None,
328 328 False, cmdutil.recordfilter, *pats, **opts)
329 329 if not node:
330 330 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
331 331 if stat.deleted:
332 332 ui.status(_("nothing changed (%d missing files, see "
333 333 "'hg status')\n") % len(stat.deleted))
334 334 else:
335 335 ui.status(_("nothing changed\n"))
336 336 return 1
337 337
338 338 bases = list(mutableancestors(repo[node]))
339 339 shelvedfile(repo, name, 'hg').writebundle(bases, node)
340 340 cmdutil.export(repo, [node],
341 341 fp=shelvedfile(repo, name, 'patch').opener('wb'),
342 342 opts=mdiff.diffopts(git=True))
343 343
344 344
345 345 if ui.formatted():
346 346 desc = util.ellipsis(desc, ui.termwidth())
347 347 ui.status(_('shelved as %s\n') % name)
348 348 hg.update(repo, parent.node())
349 349
350 350 _aborttransaction(repo)
351 351 finally:
352 352 lockmod.release(tr, lock)
353 353
354 354 def cleanupcmd(ui, repo):
355 355 """subcommand that deletes all shelves"""
356 356
357 357 with repo.wlock():
358 358 for (name, _type) in repo.vfs.readdir('shelved'):
359 359 suffix = name.rsplit('.', 1)[-1]
360 360 if suffix in ('hg', 'patch'):
361 361 shelvedfile(repo, name).movetobackup()
362 362 cleanupoldbackups(repo)
363 363
364 364 def deletecmd(ui, repo, pats):
365 365 """subcommand that deletes a specific shelve"""
366 366 if not pats:
367 367 raise error.Abort(_('no shelved changes specified!'))
368 368 with repo.wlock():
369 369 try:
370 370 for name in pats:
371 371 for suffix in 'hg patch'.split():
372 372 shelvedfile(repo, name, suffix).movetobackup()
373 373 cleanupoldbackups(repo)
374 374 except OSError as err:
375 375 if err.errno != errno.ENOENT:
376 376 raise
377 377 raise error.Abort(_("shelved change '%s' not found") % name)
378 378
379 379 def listshelves(repo):
380 380 """return all shelves in repo as list of (time, filename)"""
381 381 try:
382 382 names = repo.vfs.readdir('shelved')
383 383 except OSError as err:
384 384 if err.errno != errno.ENOENT:
385 385 raise
386 386 return []
387 387 info = []
388 388 for (name, _type) in names:
389 389 pfx, sfx = name.rsplit('.', 1)
390 390 if not pfx or sfx != 'patch':
391 391 continue
392 392 st = shelvedfile(repo, name).stat()
393 393 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
394 394 return sorted(info, reverse=True)
395 395
396 396 def listcmd(ui, repo, pats, opts):
397 397 """subcommand that displays the list of shelves"""
398 398 pats = set(pats)
399 399 width = 80
400 400 if not ui.plain():
401 401 width = ui.termwidth()
402 402 namelabel = 'shelve.newest'
403 403 for mtime, name in listshelves(repo):
404 404 sname = util.split(name)[1]
405 405 if pats and sname not in pats:
406 406 continue
407 407 ui.write(sname, label=namelabel)
408 408 namelabel = 'shelve.name'
409 409 if ui.quiet:
410 410 ui.write('\n')
411 411 continue
412 412 ui.write(' ' * (16 - len(sname)))
413 413 used = 16
414 414 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
415 415 ui.write(age, label='shelve.age')
416 416 ui.write(' ' * (12 - len(age)))
417 417 used += 12
418 418 with open(name + '.patch', 'rb') as fp:
419 419 while True:
420 420 line = fp.readline()
421 421 if not line:
422 422 break
423 423 if not line.startswith('#'):
424 424 desc = line.rstrip()
425 425 if ui.formatted():
426 426 desc = util.ellipsis(desc, width - used)
427 427 ui.write(desc)
428 428 break
429 429 ui.write('\n')
430 430 if not (opts['patch'] or opts['stat']):
431 431 continue
432 432 difflines = fp.readlines()
433 433 if opts['patch']:
434 434 for chunk, label in patch.difflabel(iter, difflines):
435 435 ui.write(chunk, label=label)
436 436 if opts['stat']:
437 437 for chunk, label in patch.diffstatui(difflines, width=width,
438 438 git=True):
439 439 ui.write(chunk, label=label)
440 440
441 441 def singlepatchcmds(ui, repo, pats, opts, subcommand):
442 442 """subcommand that displays a single shelf"""
443 443 if len(pats) != 1:
444 444 raise error.Abort(_("--%s expects a single shelf") % subcommand)
445 445 shelfname = pats[0]
446 446
447 447 if not shelvedfile(repo, shelfname, 'patch').exists():
448 448 raise error.Abort(_("cannot find shelf %s") % shelfname)
449 449
450 450 listcmd(ui, repo, pats, opts)
451 451
452 452 def checkparents(repo, state):
453 453 """check parent while resuming an unshelve"""
454 454 if state.parents != repo.dirstate.parents():
455 455 raise error.Abort(_('working directory parents do not match unshelve '
456 456 'state'))
457 457
458 458 def pathtofiles(repo, files):
459 459 cwd = repo.getcwd()
460 460 return [repo.pathto(f, cwd) for f in files]
461 461
462 462 def unshelveabort(ui, repo, state, opts):
463 463 """subcommand that abort an in-progress unshelve"""
464 lock = None
465 try:
466 checkparents(repo, state)
467
468 util.rename(repo.join('unshelverebasestate'),
469 repo.join('rebasestate'))
464 with repo.lock():
470 465 try:
471 rebase.rebase(ui, repo, **{
472 'abort' : True
473 })
474 except Exception:
475 util.rename(repo.join('rebasestate'),
476 repo.join('unshelverebasestate'))
477 raise
466 checkparents(repo, state)
478 467
479 lock = repo.lock()
480
481 mergefiles(ui, repo, state.wctx, state.pendingctx)
468 util.rename(repo.join('unshelverebasestate'),
469 repo.join('rebasestate'))
470 try:
471 rebase.rebase(ui, repo, **{
472 'abort' : True
473 })
474 except Exception:
475 util.rename(repo.join('rebasestate'),
476 repo.join('unshelverebasestate'))
477 raise
482 478
483 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
484 finally:
485 shelvedstate.clear(repo)
486 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
487 lockmod.release(lock)
479 mergefiles(ui, repo, state.wctx, state.pendingctx)
480 repair.strip(ui, repo, state.stripnodes, backup=False,
481 topic='shelve')
482 finally:
483 shelvedstate.clear(repo)
484 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
488 485
489 486 def mergefiles(ui, repo, wctx, shelvectx):
490 487 """updates to wctx and merges the changes from shelvectx into the
491 488 dirstate."""
492 489 oldquiet = ui.quiet
493 490 try:
494 491 ui.quiet = True
495 492 hg.update(repo, wctx.node())
496 493 files = []
497 494 files.extend(shelvectx.files())
498 495 files.extend(shelvectx.parents()[0].files())
499 496
500 497 # revert will overwrite unknown files, so move them out of the way
501 498 for file in repo.status(unknown=True).unknown:
502 499 if file in files:
503 500 util.rename(file, scmutil.origpath(ui, repo, file))
504 501 ui.pushbuffer(True)
505 502 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
506 503 *pathtofiles(repo, files),
507 504 **{'no_backup': True})
508 505 ui.popbuffer()
509 506 finally:
510 507 ui.quiet = oldquiet
511 508
512 509 def unshelvecleanup(ui, repo, name, opts):
513 510 """remove related files after an unshelve"""
514 511 if not opts['keep']:
515 512 for filetype in 'hg patch'.split():
516 513 shelvedfile(repo, name, filetype).movetobackup()
517 514 cleanupoldbackups(repo)
518 515
519 516 def unshelvecontinue(ui, repo, state, opts):
520 517 """subcommand to continue an in-progress unshelve"""
521 518 # We're finishing off a merge. First parent is our original
522 519 # parent, second is the temporary "fake" commit we're unshelving.
523 520 with repo.lock():
524 521 checkparents(repo, state)
525 522 ms = merge.mergestate.read(repo)
526 523 if [f for f in ms if ms[f] == 'u']:
527 524 raise error.Abort(
528 525 _("unresolved conflicts, can't continue"),
529 526 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
530 527
531 528 util.rename(repo.join('unshelverebasestate'),
532 529 repo.join('rebasestate'))
533 530 try:
534 531 rebase.rebase(ui, repo, **{
535 532 'continue' : True
536 533 })
537 534 except Exception:
538 535 util.rename(repo.join('rebasestate'),
539 536 repo.join('unshelverebasestate'))
540 537 raise
541 538
542 539 shelvectx = repo['tip']
543 540 if not shelvectx in state.pendingctx.children():
544 541 # rebase was a no-op, so it produced no child commit
545 542 shelvectx = state.pendingctx
546 543 else:
547 544 # only strip the shelvectx if the rebase produced it
548 545 state.stripnodes.append(shelvectx.node())
549 546
550 547 mergefiles(ui, repo, state.wctx, shelvectx)
551 548
552 549 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
553 550 shelvedstate.clear(repo)
554 551 unshelvecleanup(ui, repo, state.name, opts)
555 552 ui.status(_("unshelve of '%s' complete\n") % state.name)
556 553
557 554 @command('unshelve',
558 555 [('a', 'abort', None,
559 556 _('abort an incomplete unshelve operation')),
560 557 ('c', 'continue', None,
561 558 _('continue an incomplete unshelve operation')),
562 559 ('k', 'keep', None,
563 560 _('keep shelve after unshelving')),
564 561 ('t', 'tool', '', _('specify merge tool')),
565 562 ('', 'date', '',
566 563 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
567 564 _('hg unshelve [SHELVED]'))
568 565 def unshelve(ui, repo, *shelved, **opts):
569 566 """restore a shelved change to the working directory
570 567
571 568 This command accepts an optional name of a shelved change to
572 569 restore. If none is given, the most recent shelved change is used.
573 570
574 571 If a shelved change is applied successfully, the bundle that
575 572 contains the shelved changes is moved to a backup location
576 573 (.hg/shelve-backup).
577 574
578 575 Since you can restore a shelved change on top of an arbitrary
579 576 commit, it is possible that unshelving will result in a conflict
580 577 between your changes and the commits you are unshelving onto. If
581 578 this occurs, you must resolve the conflict, then use
582 579 ``--continue`` to complete the unshelve operation. (The bundle
583 580 will not be moved until you successfully complete the unshelve.)
584 581
585 582 (Alternatively, you can use ``--abort`` to abandon an unshelve
586 583 that causes a conflict. This reverts the unshelved changes, and
587 584 leaves the bundle in place.)
588 585
589 586 After a successful unshelve, the shelved changes are stored in a
590 587 backup directory. Only the N most recent backups are kept. N
591 588 defaults to 10 but can be overridden using the ``shelve.maxbackups``
592 589 configuration option.
593 590
594 591 .. container:: verbose
595 592
596 593 Timestamp in seconds is used to decide order of backups. More
597 594 than ``maxbackups`` backups are kept, if same timestamp
598 595 prevents from deciding exact order of them, for safety.
599 596 """
600 597 with repo.wlock():
601 598 return _dounshelve(ui, repo, *shelved, **opts)
602 599
603 600 def _dounshelve(ui, repo, *shelved, **opts):
604 601 abortf = opts['abort']
605 602 continuef = opts['continue']
606 603 if not abortf and not continuef:
607 604 cmdutil.checkunfinished(repo)
608 605
609 606 if abortf or continuef:
610 607 if abortf and continuef:
611 608 raise error.Abort(_('cannot use both abort and continue'))
612 609 if shelved:
613 610 raise error.Abort(_('cannot combine abort/continue with '
614 611 'naming a shelved change'))
615 612 if abortf and opts.get('tool', False):
616 613 ui.warn(_('tool option will be ignored\n'))
617 614
618 615 try:
619 616 state = shelvedstate.load(repo)
620 617 except IOError as err:
621 618 if err.errno != errno.ENOENT:
622 619 raise
623 620 raise error.Abort(_('no unshelve operation underway'))
624 621
625 622 if abortf:
626 623 return unshelveabort(ui, repo, state, opts)
627 624 elif continuef:
628 625 return unshelvecontinue(ui, repo, state, opts)
629 626 elif len(shelved) > 1:
630 627 raise error.Abort(_('can only unshelve one change at a time'))
631 628 elif not shelved:
632 629 shelved = listshelves(repo)
633 630 if not shelved:
634 631 raise error.Abort(_('no shelved changes to apply!'))
635 632 basename = util.split(shelved[0][1])[1]
636 633 ui.status(_("unshelving change '%s'\n") % basename)
637 634 else:
638 635 basename = shelved[0]
639 636
640 637 if not shelvedfile(repo, basename, 'patch').exists():
641 638 raise error.Abort(_("shelved change '%s' not found") % basename)
642 639
643 640 oldquiet = ui.quiet
644 641 lock = tr = None
645 642 forcemerge = ui.backupconfig('ui', 'forcemerge')
646 643 try:
647 644 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
648 645 lock = repo.lock()
649 646
650 647 tr = repo.transaction('unshelve', report=lambda x: None)
651 648 oldtiprev = len(repo)
652 649
653 650 pctx = repo['.']
654 651 tmpwctx = pctx
655 652 # The goal is to have a commit structure like so:
656 653 # ...-> pctx -> tmpwctx -> shelvectx
657 654 # where tmpwctx is an optional commit with the user's pending changes
658 655 # and shelvectx is the unshelved changes. Then we merge it all down
659 656 # to the original pctx.
660 657
661 658 # Store pending changes in a commit
662 659 s = repo.status()
663 660 if s.modified or s.added or s.removed or s.deleted:
664 661 ui.status(_("temporarily committing pending changes "
665 662 "(restore with 'hg unshelve --abort')\n"))
666 663 def commitfunc(ui, repo, message, match, opts):
667 664 hasmq = util.safehasattr(repo, 'mq')
668 665 if hasmq:
669 666 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
670 667
671 668 backup = repo.ui.backupconfig('phases', 'new-commit')
672 669 try:
673 670 repo.ui.setconfig('phases', 'new-commit', phases.secret)
674 671 return repo.commit(message, 'shelve@localhost',
675 672 opts.get('date'), match)
676 673 finally:
677 674 repo.ui.restoreconfig(backup)
678 675 if hasmq:
679 676 repo.mq.checkapplied = saved
680 677
681 678 tempopts = {}
682 679 tempopts['message'] = "pending changes temporary commit"
683 680 tempopts['date'] = opts.get('date')
684 681 ui.quiet = True
685 682 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
686 683 tmpwctx = repo[node]
687 684
688 685 ui.quiet = True
689 686 shelvedfile(repo, basename, 'hg').applybundle()
690 687
691 688 ui.quiet = oldquiet
692 689
693 690 shelvectx = repo['tip']
694 691
695 692 # If the shelve is not immediately on top of the commit
696 693 # we'll be merging with, rebase it to be on top.
697 694 if tmpwctx.node() != shelvectx.parents()[0].node():
698 695 ui.status(_('rebasing shelved changes\n'))
699 696 try:
700 697 rebase.rebase(ui, repo, **{
701 698 'rev' : [shelvectx.rev()],
702 699 'dest' : str(tmpwctx.rev()),
703 700 'keep' : True,
704 701 'tool' : opts.get('tool', ''),
705 702 })
706 703 except error.InterventionRequired:
707 704 tr.close()
708 705
709 706 stripnodes = [repo.changelog.node(rev)
710 707 for rev in xrange(oldtiprev, len(repo))]
711 708 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes)
712 709
713 710 util.rename(repo.join('rebasestate'),
714 711 repo.join('unshelverebasestate'))
715 712 raise error.InterventionRequired(
716 713 _("unresolved conflicts (see 'hg resolve', then "
717 714 "'hg unshelve --continue')"))
718 715
719 716 # refresh ctx after rebase completes
720 717 shelvectx = repo['tip']
721 718
722 719 if not shelvectx in tmpwctx.children():
723 720 # rebase was a no-op, so it produced no child commit
724 721 shelvectx = tmpwctx
725 722
726 723 mergefiles(ui, repo, pctx, shelvectx)
727 724 shelvedstate.clear(repo)
728 725
729 726 # The transaction aborting will strip all the commits for us,
730 727 # but it doesn't update the inmemory structures, so addchangegroup
731 728 # hooks still fire and try to operate on the missing commits.
732 729 # Clean up manually to prevent this.
733 730 repo.unfiltered().changelog.strip(oldtiprev, tr)
734 731
735 732 unshelvecleanup(ui, repo, basename, opts)
736 733
737 734 _aborttransaction(repo)
738 735 finally:
739 736 ui.quiet = oldquiet
740 737 if tr:
741 738 tr.release()
742 739 lockmod.release(lock)
743 740 ui.restoreconfig(forcemerge)
744 741
745 742 @command('shelve',
746 743 [('A', 'addremove', None,
747 744 _('mark new/missing files as added/removed before shelving')),
748 745 ('', 'cleanup', None,
749 746 _('delete all shelved changes')),
750 747 ('', 'date', '',
751 748 _('shelve with the specified commit date'), _('DATE')),
752 749 ('d', 'delete', None,
753 750 _('delete the named shelved change(s)')),
754 751 ('e', 'edit', False,
755 752 _('invoke editor on commit messages')),
756 753 ('l', 'list', None,
757 754 _('list current shelves')),
758 755 ('m', 'message', '',
759 756 _('use text as shelve message'), _('TEXT')),
760 757 ('n', 'name', '',
761 758 _('use the given name for the shelved commit'), _('NAME')),
762 759 ('p', 'patch', None,
763 760 _('show patch')),
764 761 ('i', 'interactive', None,
765 762 _('interactive mode, only works while creating a shelve')),
766 763 ('', 'stat', None,
767 764 _('output diffstat-style summary of changes'))] + commands.walkopts,
768 765 _('hg shelve [OPTION]... [FILE]...'))
769 766 def shelvecmd(ui, repo, *pats, **opts):
770 767 '''save and set aside changes from the working directory
771 768
772 769 Shelving takes files that "hg status" reports as not clean, saves
773 770 the modifications to a bundle (a shelved change), and reverts the
774 771 files so that their state in the working directory becomes clean.
775 772
776 773 To restore these changes to the working directory, using "hg
777 774 unshelve"; this will work even if you switch to a different
778 775 commit.
779 776
780 777 When no files are specified, "hg shelve" saves all not-clean
781 778 files. If specific files or directories are named, only changes to
782 779 those files are shelved.
783 780
784 781 Each shelved change has a name that makes it easier to find later.
785 782 The name of a shelved change defaults to being based on the active
786 783 bookmark, or if there is no active bookmark, the current named
787 784 branch. To specify a different name, use ``--name``.
788 785
789 786 To see a list of existing shelved changes, use the ``--list``
790 787 option. For each shelved change, this will print its name, age,
791 788 and description; use ``--patch`` or ``--stat`` for more details.
792 789
793 790 To delete specific shelved changes, use ``--delete``. To delete
794 791 all shelved changes, use ``--cleanup``.
795 792 '''
796 793 allowables = [
797 794 ('addremove', set(['create'])), # 'create' is pseudo action
798 795 ('cleanup', set(['cleanup'])),
799 796 # ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
800 797 ('delete', set(['delete'])),
801 798 ('edit', set(['create'])),
802 799 ('list', set(['list'])),
803 800 ('message', set(['create'])),
804 801 ('name', set(['create'])),
805 802 ('patch', set(['patch', 'list'])),
806 803 ('stat', set(['stat', 'list'])),
807 804 ]
808 805 def checkopt(opt):
809 806 if opts[opt]:
810 807 for i, allowable in allowables:
811 808 if opts[i] and opt not in allowable:
812 809 raise error.Abort(_("options '--%s' and '--%s' may not be "
813 810 "used together") % (opt, i))
814 811 return True
815 812 if checkopt('cleanup'):
816 813 if pats:
817 814 raise error.Abort(_("cannot specify names when using '--cleanup'"))
818 815 return cleanupcmd(ui, repo)
819 816 elif checkopt('delete'):
820 817 return deletecmd(ui, repo, pats)
821 818 elif checkopt('list'):
822 819 return listcmd(ui, repo, pats, opts)
823 820 elif checkopt('patch'):
824 821 return singlepatchcmds(ui, repo, pats, opts, subcommand='patch')
825 822 elif checkopt('stat'):
826 823 return singlepatchcmds(ui, repo, pats, opts, subcommand='stat')
827 824 else:
828 825 return createcmd(ui, repo, pats, opts)
829 826
830 827 def extsetup(ui):
831 828 cmdutil.unfinishedstates.append(
832 829 [shelvedstate._filename, False, False,
833 830 _('unshelve already in progress'),
834 831 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
835 832 cmdutil.afterresolvedstates.append(
836 833 [shelvedstate._filename, _('hg unshelve --continue')])
General Comments 0
You need to be logged in to leave comments. Login now