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