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