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