93 |
94 |
94 if version != cls._version: |
95 if version != cls._version: |
95 raise util.Abort(_('this version of shelve is incompatible ' |
96 raise util.Abort(_('this version of shelve is incompatible ' |
96 'with the version used in this repo')) |
97 'with the version used in this repo')) |
97 name = fp.readline().strip() |
98 name = fp.readline().strip() |
|
99 wctx = fp.readline().strip() |
|
100 pendingctx = fp.readline().strip() |
98 parents = [bin(h) for h in fp.readline().split()] |
101 parents = [bin(h) for h in fp.readline().split()] |
99 stripnodes = [bin(h) for h in fp.readline().split()] |
102 stripnodes = [bin(h) for h in fp.readline().split()] |
|
103 unknownfiles = fp.readline()[:-1].split('\0') |
100 finally: |
104 finally: |
101 fp.close() |
105 fp.close() |
102 |
106 |
103 obj = cls() |
107 obj = cls() |
104 obj.name = name |
108 obj.name = name |
|
109 obj.wctx = repo[bin(wctx)] |
|
110 obj.pendingctx = repo[bin(pendingctx)] |
105 obj.parents = parents |
111 obj.parents = parents |
106 obj.stripnodes = stripnodes |
112 obj.stripnodes = stripnodes |
|
113 obj.unknownfiles = unknownfiles |
107 |
114 |
108 return obj |
115 return obj |
109 |
116 |
110 @classmethod |
117 @classmethod |
111 def save(cls, repo, name, stripnodes): |
118 def save(cls, repo, name, originalwctx, pendingctx, stripnodes, |
|
119 unknownfiles): |
112 fp = repo.opener(cls._filename, 'wb') |
120 fp = repo.opener(cls._filename, 'wb') |
113 fp.write('%i\n' % cls._version) |
121 fp.write('%i\n' % cls._version) |
114 fp.write('%s\n' % name) |
122 fp.write('%s\n' % name) |
|
123 fp.write('%s\n' % hex(originalwctx.node())) |
|
124 fp.write('%s\n' % hex(pendingctx.node())) |
115 fp.write('%s\n' % ' '.join([hex(p) for p in repo.dirstate.parents()])) |
125 fp.write('%s\n' % ' '.join([hex(p) for p in repo.dirstate.parents()])) |
116 fp.write('%s\n' % ' '.join([hex(n) for n in stripnodes])) |
126 fp.write('%s\n' % ' '.join([hex(n) for n in stripnodes])) |
|
127 fp.write('%s\n' % '\0'.join(unknownfiles)) |
117 fp.close() |
128 fp.close() |
118 |
129 |
119 @classmethod |
130 @classmethod |
120 def clear(cls, repo): |
131 def clear(cls, repo): |
121 util.unlinkpath(repo.join(cls._filename), ignoremissing=True) |
132 util.unlinkpath(repo.join(cls._filename), ignoremissing=True) |
366 """subcommand that abort an in-progress unshelve""" |
377 """subcommand that abort an in-progress unshelve""" |
367 wlock = repo.wlock() |
378 wlock = repo.wlock() |
368 lock = None |
379 lock = None |
369 try: |
380 try: |
370 checkparents(repo, state) |
381 checkparents(repo, state) |
|
382 |
|
383 util.rename(repo.join('unshelverebasestate'), |
|
384 repo.join('rebasestate')) |
|
385 try: |
|
386 rebase.rebase(ui, repo, **{ |
|
387 'abort' : True |
|
388 }) |
|
389 except Exception: |
|
390 util.rename(repo.join('rebasestate'), |
|
391 repo.join('unshelverebasestate')) |
|
392 raise |
|
393 |
371 lock = repo.lock() |
394 lock = repo.lock() |
372 merge.mergestate(repo).reset() |
395 |
373 if opts['keep']: |
396 mergefiles(ui, repo, state.wctx, state.pendingctx, state.unknownfiles) |
374 repo.setparents(repo.dirstate.parents()[0]) |
397 |
375 else: |
|
376 revertfiles = readshelvedfiles(repo, state.name) |
|
377 wctx = repo.parents()[0] |
|
378 cmdutil.revert(ui, repo, wctx, [wctx.node(), nullid], |
|
379 *pathtofiles(repo, revertfiles), |
|
380 **{'no_backup': True}) |
|
381 # fix up the weird dirstate states the merge left behind |
|
382 mf = wctx.manifest() |
|
383 dirstate = repo.dirstate |
|
384 for f in revertfiles: |
|
385 if f in mf: |
|
386 dirstate.normallookup(f) |
|
387 else: |
|
388 dirstate.drop(f) |
|
389 dirstate._pl = (wctx.node(), nullid) |
|
390 dirstate._dirty = True |
|
391 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve') |
398 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve') |
392 shelvedstate.clear(repo) |
399 shelvedstate.clear(repo) |
393 ui.warn(_("unshelve of '%s' aborted\n") % state.name) |
400 ui.warn(_("unshelve of '%s' aborted\n") % state.name) |
394 finally: |
401 finally: |
395 lockmod.release(lock, wlock) |
402 lockmod.release(lock, wlock) |
396 |
403 |
|
404 def mergefiles(ui, repo, wctx, shelvectx, unknownfiles): |
|
405 """updates to wctx and merges the changes from shelvectx into the |
|
406 dirstate. drops any files in unknownfiles from the dirstate.""" |
|
407 oldquiet = ui.quiet |
|
408 try: |
|
409 ui.quiet = True |
|
410 hg.update(repo, wctx.node()) |
|
411 files = [] |
|
412 files.extend(shelvectx.files()) |
|
413 files.extend(shelvectx.parents()[0].files()) |
|
414 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(), |
|
415 *pathtofiles(repo, files), |
|
416 **{'no_backup': True}) |
|
417 finally: |
|
418 ui.quiet = oldquiet |
|
419 |
|
420 # Send untracked files back to being untracked |
|
421 dirstate = repo.dirstate |
|
422 for f in unknownfiles: |
|
423 dirstate.drop(f) |
|
424 |
397 def unshelvecleanup(ui, repo, name, opts): |
425 def unshelvecleanup(ui, repo, name, opts): |
398 """remove related files after an unshelve""" |
426 """remove related files after an unshelve""" |
399 if not opts['keep']: |
427 if not opts['keep']: |
400 for filetype in 'hg files patch'.split(): |
428 for filetype in 'hg files patch'.split(): |
401 shelvedfile(repo, name, filetype).unlink() |
429 shelvedfile(repo, name, filetype).unlink() |
402 |
|
403 def finishmerge(ui, repo, ms, stripnodes, name, opts): |
|
404 # Reset the working dir so it's no longer in a merge state. |
|
405 dirstate = repo.dirstate |
|
406 dirstate.setparents(dirstate._pl[0]) |
|
407 shelvedstate.clear(repo) |
|
408 |
430 |
409 def unshelvecontinue(ui, repo, state, opts): |
431 def unshelvecontinue(ui, repo, state, opts): |
410 """subcommand to continue an in-progress unshelve""" |
432 """subcommand to continue an in-progress unshelve""" |
411 # We're finishing off a merge. First parent is our original |
433 # We're finishing off a merge. First parent is our original |
412 # parent, second is the temporary "fake" commit we're unshelving. |
434 # parent, second is the temporary "fake" commit we're unshelving. |
417 ms = merge.mergestate(repo) |
439 ms = merge.mergestate(repo) |
418 if [f for f in ms if ms[f] == 'u']: |
440 if [f for f in ms if ms[f] == 'u']: |
419 raise util.Abort( |
441 raise util.Abort( |
420 _("unresolved conflicts, can't continue"), |
442 _("unresolved conflicts, can't continue"), |
421 hint=_("see 'hg resolve', then 'hg unshelve --continue'")) |
443 hint=_("see 'hg resolve', then 'hg unshelve --continue'")) |
422 finishmerge(ui, repo, ms, state.stripnodes, state.name, opts) |
444 |
423 lock = repo.lock() |
445 lock = repo.lock() |
|
446 |
|
447 util.rename(repo.join('unshelverebasestate'), |
|
448 repo.join('rebasestate')) |
|
449 try: |
|
450 rebase.rebase(ui, repo, **{ |
|
451 'continue' : True |
|
452 }) |
|
453 except Exception: |
|
454 util.rename(repo.join('rebasestate'), |
|
455 repo.join('unshelverebasestate')) |
|
456 raise |
|
457 |
|
458 shelvectx = repo['tip'] |
|
459 if not shelvectx in state.pendingctx.children(): |
|
460 # rebase was a no-op, so it produced no child commit |
|
461 shelvectx = state.pendingctx |
|
462 |
|
463 mergefiles(ui, repo, state.wctx, shelvectx, state.unknownfiles) |
|
464 |
|
465 state.stripnodes.append(shelvectx.node()) |
424 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve') |
466 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve') |
|
467 shelvedstate.clear(repo) |
425 unshelvecleanup(ui, repo, state.name, opts) |
468 unshelvecleanup(ui, repo, state.name, opts) |
426 ui.status(_("unshelve of '%s' complete\n") % state.name) |
469 ui.status(_("unshelve of '%s' complete\n") % state.name) |
427 finally: |
470 finally: |
428 lockmod.release(lock, wlock) |
471 lockmod.release(lock, wlock) |
429 |
472 |
489 else: |
532 else: |
490 basename = shelved[0] |
533 basename = shelved[0] |
491 |
534 |
492 shelvedfiles = readshelvedfiles(repo, basename) |
535 shelvedfiles = readshelvedfiles(repo, basename) |
493 |
536 |
494 m, a, r, d = repo.status()[:4] |
|
495 unsafe = set(m + a + r + d).intersection(shelvedfiles) |
|
496 if unsafe: |
|
497 ui.warn(_('the following shelved files have been modified:\n')) |
|
498 for f in sorted(unsafe): |
|
499 ui.warn(' %s\n' % f) |
|
500 ui.warn(_('you must commit, revert, or shelve your changes before you ' |
|
501 'can proceed\n')) |
|
502 raise util.Abort(_('cannot unshelve due to local changes\n')) |
|
503 |
|
504 wlock = lock = tr = None |
537 wlock = lock = tr = None |
505 try: |
538 try: |
506 lock = repo.lock() |
539 lock = repo.lock() |
|
540 wlock = repo.wlock() |
507 |
541 |
508 tr = repo.transaction('unshelve', report=lambda x: None) |
542 tr = repo.transaction('unshelve', report=lambda x: None) |
509 oldtiprev = len(repo) |
543 oldtiprev = len(repo) |
|
544 |
|
545 wctx = repo['.'] |
|
546 tmpwctx = wctx |
|
547 # The goal is to have a commit structure like so: |
|
548 # ...-> wctx -> tmpwctx -> shelvectx |
|
549 # where tmpwctx is an optional commit with the user's pending changes |
|
550 # and shelvectx is the unshelved changes. Then we merge it all down |
|
551 # to the original wctx. |
|
552 |
|
553 # Store pending changes in a commit |
|
554 m, a, r, d, u = repo.status(unknown=True)[:5] |
|
555 if m or a or r or d or u: |
|
556 def commitfunc(ui, repo, message, match, opts): |
|
557 hasmq = util.safehasattr(repo, 'mq') |
|
558 if hasmq: |
|
559 saved, repo.mq.checkapplied = repo.mq.checkapplied, False |
|
560 |
|
561 try: |
|
562 return repo.commit(message, 'shelve@localhost', |
|
563 opts.get('date'), match) |
|
564 finally: |
|
565 if hasmq: |
|
566 repo.mq.checkapplied = saved |
|
567 |
|
568 tempopts = {} |
|
569 tempopts['message'] = "pending changes temporary commit" |
|
570 tempopts['addremove'] = True |
|
571 oldquiet = ui.quiet |
|
572 try: |
|
573 ui.quiet = True |
|
574 node = cmdutil.commit(ui, repo, commitfunc, None, tempopts) |
|
575 finally: |
|
576 ui.quiet = oldquiet |
|
577 tmpwctx = repo[node] |
|
578 |
510 try: |
579 try: |
511 fp = shelvedfile(repo, basename, 'hg').opener() |
580 fp = shelvedfile(repo, basename, 'hg').opener() |
512 gen = changegroup.readbundle(fp, fp.name) |
581 gen = changegroup.readbundle(fp, fp.name) |
513 repo.addchangegroup(gen, 'unshelve', 'bundle:' + fp.name) |
582 repo.addchangegroup(gen, 'unshelve', 'bundle:' + fp.name) |
514 nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)] |
583 nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)] |
515 phases.retractboundary(repo, phases.secret, nodes) |
584 phases.retractboundary(repo, phases.secret, nodes) |
516 tr.close() |
|
517 finally: |
585 finally: |
518 fp.close() |
586 fp.close() |
519 |
587 |
520 tip = repo['tip'] |
588 shelvectx = repo['tip'] |
521 wctx = repo['.'] |
589 |
522 ancestor = tip.ancestor(wctx) |
590 # If the shelve is not immediately on top of the commit |
523 |
591 # we'll be merging with, rebase it to be on top. |
524 wlock = repo.wlock() |
592 if tmpwctx.node() != shelvectx.parents()[0].node(): |
525 |
593 try: |
526 if ancestor.node() != wctx.node(): |
594 rebase.rebase(ui, repo, **{ |
527 conflicts = hg.merge(repo, tip.node(), force=True, remind=False) |
595 'rev' : [shelvectx.rev()], |
528 ms = merge.mergestate(repo) |
596 'dest' : str(tmpwctx.rev()), |
529 stripnodes = [repo.changelog.node(rev) |
597 'keep' : True, |
530 for rev in xrange(oldtiprev, len(repo))] |
598 }) |
531 if conflicts: |
599 except error.InterventionRequired: |
532 shelvedstate.save(repo, basename, stripnodes) |
600 tr.close() |
533 # Fix up the dirstate entries of files from the second |
601 |
534 # parent as if we were not merging, except for those |
602 stripnodes = [repo.changelog.node(rev) |
535 # with unresolved conflicts. |
603 for rev in xrange(oldtiprev, len(repo))] |
536 parents = repo.parents() |
604 shelvedstate.save(repo, basename, wctx, tmpwctx, stripnodes, u) |
537 revertfiles = set(parents[1].files()).difference(ms) |
605 |
538 cmdutil.revert(ui, repo, parents[1], |
606 util.rename(repo.join('rebasestate'), |
539 (parents[0].node(), nullid), |
607 repo.join('unshelverebasestate')) |
540 *pathtofiles(repo, revertfiles), |
|
541 **{'no_backup': True}) |
|
542 raise error.InterventionRequired( |
608 raise error.InterventionRequired( |
543 _("unresolved conflicts (see 'hg resolve', then " |
609 _("unresolved conflicts (see 'hg resolve', then " |
544 "'hg unshelve --continue')")) |
610 "'hg unshelve --continue')")) |
545 finishmerge(ui, repo, ms, stripnodes, basename, opts) |
611 |
546 else: |
612 # refresh ctx after rebase completes |
547 parent = tip.parents()[0] |
613 shelvectx = repo['tip'] |
548 hg.update(repo, parent.node()) |
614 |
549 cmdutil.revert(ui, repo, tip, repo.dirstate.parents(), |
615 if not shelvectx in tmpwctx.children(): |
550 *pathtofiles(repo, tip.files()), |
616 # rebase was a no-op, so it produced no child commit |
551 **{'no_backup': True}) |
617 shelvectx = tmpwctx |
552 |
618 |
553 prevquiet = ui.quiet |
619 mergefiles(ui, repo, wctx, shelvectx, u) |
554 ui.quiet = True |
620 shelvedstate.clear(repo) |
555 try: |
621 |
556 repo.rollback(force=True) |
622 # The transaction aborting will strip all the commits for us, |
557 finally: |
623 # but it doesn't update the inmemory structures, so addchangegroup |
558 ui.quiet = prevquiet |
624 # hooks still fire and try to operate on the missing commits. |
|
625 # Clean up manually to prevent this. |
|
626 repo.changelog.strip(oldtiprev, tr) |
559 |
627 |
560 unshelvecleanup(ui, repo, basename, opts) |
628 unshelvecleanup(ui, repo, basename, opts) |
561 finally: |
629 finally: |
562 if tr: |
630 if tr: |
563 tr.release() |
631 tr.release() |