##// END OF EJS Templates
shelve: add missing space in help text...
Mads Kiilerich -
r30419:819f96b8 stable
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

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