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