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