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