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