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