comparison mercurial/shelve.py @ 42616:5162753c4c14

unshelve: add interactive mode Until now, there is no way to `unshelve` selected changes only from the stored shelve as given in issue6162. This patch makes `unshelve` perform with certain changes only by adding an interactive mode. Differential Revision: https://phab.mercurial-scm.org/D6596
author Navaneeth Suresh <navaneeths1998@gmail.com>
date Tue, 02 Jul 2019 18:02:12 +0530
parents 117437f3f541
children c9114885c14b
comparison
equal deleted inserted replaced
42615:56132ebd14c6 42616:5162753c4c14
692 for filetype in shelvefileextensions: 692 for filetype in shelvefileextensions:
693 shfile = shelvedfile(repo, name, filetype) 693 shfile = shelvedfile(repo, name, filetype)
694 if shfile.exists(): 694 if shfile.exists():
695 shfile.movetobackup() 695 shfile.movetobackup()
696 cleanupoldbackups(repo) 696 cleanupoldbackups(repo)
697 def unshelvecontinue(ui, repo, state, opts): 697 def unshelvecontinue(ui, repo, state, opts, basename=None):
698 """subcommand to continue an in-progress unshelve""" 698 """subcommand to continue an in-progress unshelve"""
699 # We're finishing off a merge. First parent is our original 699 # We're finishing off a merge. First parent is our original
700 # parent, second is the temporary "fake" commit we're unshelving. 700 # parent, second is the temporary "fake" commit we're unshelving.
701 interactive = opts.get('interactive')
701 with repo.lock(): 702 with repo.lock():
702 checkparents(repo, state) 703 checkparents(repo, state)
703 ms = merge.mergestate.read(repo) 704 ms = merge.mergestate.read(repo)
704 if list(ms.unresolved()): 705 if list(ms.unresolved()):
705 raise error.Abort( 706 raise error.Abort(
718 targetphase = phases.secret 719 targetphase = phases.secret
719 overrides = {('phases', 'new-commit'): targetphase} 720 overrides = {('phases', 'new-commit'): targetphase}
720 with repo.ui.configoverride(overrides, 'unshelve'): 721 with repo.ui.configoverride(overrides, 'unshelve'):
721 with repo.dirstate.parentchange(): 722 with repo.dirstate.parentchange():
722 repo.setparents(state.parents[0], nodemod.nullid) 723 repo.setparents(state.parents[0], nodemod.nullid)
723 newnode = repo.commit(text=shelvectx.description(), 724 if not interactive:
724 extra=shelvectx.extra(), 725 ispartialunshelve = False
725 user=shelvectx.user(), 726 newnode = repo.commit(text=shelvectx.description(),
726 date=shelvectx.date()) 727 extra=shelvectx.extra(),
728 user=shelvectx.user(),
729 date=shelvectx.date())
730 else:
731 newnode, ispartialunshelve = _dounshelveinteractive(ui,
732 repo, shelvectx, basename, opts)
727 733
728 if newnode is None: 734 if newnode is None:
729 # If it ended up being a no-op commit, then the normal 735 # If it ended up being a no-op commit, then the normal
730 # merge state clean-up path doesn't happen, so do it 736 # merge state clean-up path doesn't happen, so do it
731 # here. Fix issue5494 737 # here. Fix issue5494
741 747
742 hg.updaterepo(repo, pendingctx.node(), overwrite=False) 748 hg.updaterepo(repo, pendingctx.node(), overwrite=False)
743 mergefiles(ui, repo, state.wctx, shelvectx) 749 mergefiles(ui, repo, state.wctx, shelvectx)
744 restorebranch(ui, repo, state.branchtorestore) 750 restorebranch(ui, repo, state.branchtorestore)
745 751
746 if not phases.supportinternal(repo): 752 if not ispartialunshelve:
747 repair.strip(ui, repo, state.nodestoremove, backup=False, 753 if not phases.supportinternal(repo):
748 topic='shelve') 754 repair.strip(ui, repo, state.nodestoremove, backup=False,
755 topic='shelve')
756 shelvedstate.clear(repo)
757 unshelvecleanup(ui, repo, state.name, opts)
749 _restoreactivebookmark(repo, state.activebookmark) 758 _restoreactivebookmark(repo, state.activebookmark)
750 shelvedstate.clear(repo)
751 unshelvecleanup(ui, repo, state.name, opts)
752 ui.status(_("unshelve of '%s' complete\n") % state.name) 759 ui.status(_("unshelve of '%s' complete\n") % state.name)
753 760
754 def hgcontinueunshelve(ui, repo): 761 def hgcontinueunshelve(ui, repo):
755 """logic to resume unshelve using 'hg continue'""" 762 """logic to resume unshelve using 'hg continue'"""
756 with repo.wlock(): 763 with repo.wlock():
795 else: 802 else:
796 shelvectx = repo[node] 803 shelvectx = repo[node]
797 804
798 return repo, shelvectx 805 return repo, shelvectx
799 806
807 def _dounshelveinteractive(ui, repo, shelvectx, basename, opts):
808 """The user might want to unshelve certain changes only from the stored
809 shelve. So, we would create two commits. One with requested changes to
810 unshelve at that time and the latter is shelved for future.
811 """
812 opts['message'] = shelvectx.description()
813 opts['interactive-unshelve'] = True
814 pats = []
815 commitfunc = getcommitfunc(shelvectx.extra(), interactive=True,
816 editor=True)
817 newnode = cmdutil.dorecord(ui, repo, commitfunc, None, False,
818 cmdutil.recordfilter, *pats,
819 **pycompat.strkwargs(opts))
820 snode = repo.commit(text=shelvectx.description(),
821 extra=shelvectx.extra(),
822 user=shelvectx.user(),
823 date=shelvectx.date())
824 m = scmutil.matchfiles(repo, repo[snode].files())
825 if snode:
826 _shelvecreatedcommit(repo, snode, basename, m)
827
828 return newnode, bool(snode)
829
800 def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx, 830 def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
801 tmpwctx, shelvectx, branchtorestore, 831 tmpwctx, shelvectx, branchtorestore,
802 activebookmark): 832 activebookmark):
803 """Rebase restored commit from its original location to a destination""" 833 """Rebase restored commit from its original location to a destination"""
804 # If the shelve is not immediately on top of the commit 834 # If the shelve is not immediately on top of the commit
805 # we'll be merging with, rebase it to be on top. 835 # we'll be merging with, rebase it to be on top.
806 if tmpwctx.node() == shelvectx.p1().node(): 836 interactive = opts.get('interactive')
807 return shelvectx 837 if tmpwctx.node() == shelvectx.p1().node() and not interactive:
838 # We won't skip on interactive mode because, the user might want to
839 # unshelve certain changes only.
840 return shelvectx, False
808 841
809 overrides = { 842 overrides = {
810 ('ui', 'forcemerge'): opts.get('tool', ''), 843 ('ui', 'forcemerge'): opts.get('tool', ''),
811 ('phases', 'new-commit'): phases.secret, 844 ('phases', 'new-commit'): phases.secret,
812 } 845 }
826 _("unresolved conflicts (see 'hg resolve', then " 859 _("unresolved conflicts (see 'hg resolve', then "
827 "'hg unshelve --continue')")) 860 "'hg unshelve --continue')"))
828 861
829 with repo.dirstate.parentchange(): 862 with repo.dirstate.parentchange():
830 repo.setparents(tmpwctx.node(), nodemod.nullid) 863 repo.setparents(tmpwctx.node(), nodemod.nullid)
831 newnode = repo.commit(text=shelvectx.description(), 864 if not interactive:
832 extra=shelvectx.extra(), 865 ispartialunshelve = False
833 user=shelvectx.user(), 866 newnode = repo.commit(text=shelvectx.description(),
834 date=shelvectx.date()) 867 extra=shelvectx.extra(),
868 user=shelvectx.user(),
869 date=shelvectx.date())
870 else:
871 newnode, ispartialunshelve = _dounshelveinteractive(ui, repo,
872 shelvectx, basename, opts)
835 873
836 if newnode is None: 874 if newnode is None:
837 # If it ended up being a no-op commit, then the normal 875 # If it ended up being a no-op commit, then the normal
838 # merge state clean-up path doesn't happen, so do it 876 # merge state clean-up path doesn't happen, so do it
839 # here. Fix issue5494 877 # here. Fix issue5494
844 ui.status(msg) 882 ui.status(msg)
845 else: 883 else:
846 shelvectx = repo[newnode] 884 shelvectx = repo[newnode]
847 hg.updaterepo(repo, tmpwctx.node(), False) 885 hg.updaterepo(repo, tmpwctx.node(), False)
848 886
849 return shelvectx 887 return shelvectx, ispartialunshelve
850 888
851 def _forgetunknownfiles(repo, shelvectx, addedbefore): 889 def _forgetunknownfiles(repo, shelvectx, addedbefore):
852 # Forget any files that were unknown before the shelve, unknown before 890 # Forget any files that were unknown before the shelve, unknown before
853 # unshelve started, but are now added. 891 # unshelve started, but are now added.
854 shelveunknown = shelvectx.extra().get('shelve_unknown') 892 shelveunknown = shelvectx.extra().get('shelve_unknown')
881 919
882 def dounshelve(ui, repo, *shelved, **opts): 920 def dounshelve(ui, repo, *shelved, **opts):
883 opts = pycompat.byteskwargs(opts) 921 opts = pycompat.byteskwargs(opts)
884 abortf = opts.get('abort') 922 abortf = opts.get('abort')
885 continuef = opts.get('continue') 923 continuef = opts.get('continue')
924 interactive = opts.get('interactive')
886 if not abortf and not continuef: 925 if not abortf and not continuef:
887 cmdutil.checkunfinished(repo) 926 cmdutil.checkunfinished(repo)
888 shelved = list(shelved) 927 shelved = list(shelved)
889 if opts.get("name"): 928 if opts.get("name"):
890 shelved.append(opts["name"]) 929 shelved.append(opts["name"])
891 930
892 if abortf or continuef: 931 if abortf or continuef and not interactive:
893 if abortf and continuef: 932 if abortf and continuef:
894 raise error.Abort(_('cannot use both abort and continue')) 933 raise error.Abort(_('cannot use both abort and continue'))
895 if shelved: 934 if shelved:
896 raise error.Abort(_('cannot combine abort/continue with ' 935 raise error.Abort(_('cannot combine abort/continue with '
897 'naming a shelved change')) 936 'naming a shelved change'))
909 shelved = listshelves(repo) 948 shelved = listshelves(repo)
910 if not shelved: 949 if not shelved:
911 raise error.Abort(_('no shelved changes to apply!')) 950 raise error.Abort(_('no shelved changes to apply!'))
912 basename = util.split(shelved[0][1])[1] 951 basename = util.split(shelved[0][1])[1]
913 ui.status(_("unshelving change '%s'\n") % basename) 952 ui.status(_("unshelving change '%s'\n") % basename)
914 else: 953 elif shelved:
915 basename = shelved[0] 954 basename = shelved[0]
955 if continuef and interactive:
956 state = _loadshelvedstate(ui, repo, opts)
957 return unshelvecontinue(ui, repo, state, opts, basename)
916 958
917 if not shelvedfile(repo, basename, patchextension).exists(): 959 if not shelvedfile(repo, basename, patchextension).exists():
918 raise error.Abort(_("shelved change '%s' not found") % basename) 960 raise error.Abort(_("shelved change '%s' not found") % basename)
919 961
920 repo = repo.unfiltered() 962 repo = repo.unfiltered()
939 _checkunshelveuntrackedproblems(ui, repo, shelvectx) 981 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
940 branchtorestore = '' 982 branchtorestore = ''
941 if shelvectx.branch() != shelvectx.p1().branch(): 983 if shelvectx.branch() != shelvectx.p1().branch():
942 branchtorestore = shelvectx.branch() 984 branchtorestore = shelvectx.branch()
943 985
944 shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, 986 shelvectx, ispartialunshelve = _rebaserestoredcommit(ui, repo, opts,
945 basename, pctx, tmpwctx, 987 tr, oldtiprev, basename, pctx, tmpwctx, shelvectx,
946 shelvectx, branchtorestore, 988 branchtorestore, activebookmark)
947 activebookmark)
948 overrides = {('ui', 'forcemerge'): opts.get('tool', '')} 989 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
949 with ui.configoverride(overrides, 'unshelve'): 990 with ui.configoverride(overrides, 'unshelve'):
950 mergefiles(ui, repo, pctx, shelvectx) 991 mergefiles(ui, repo, pctx, shelvectx)
951 restorebranch(ui, repo, branchtorestore) 992 restorebranch(ui, repo, branchtorestore)
952 _forgetunknownfiles(repo, shelvectx, addedbefore) 993 if not ispartialunshelve:
953 994 _forgetunknownfiles(repo, shelvectx, addedbefore)
954 shelvedstate.clear(repo) 995
955 _finishunshelve(repo, oldtiprev, tr, activebookmark) 996 shelvedstate.clear(repo)
956 unshelvecleanup(ui, repo, basename, opts) 997 _finishunshelve(repo, oldtiprev, tr, activebookmark)
998 unshelvecleanup(ui, repo, basename, opts)
957 finally: 999 finally:
958 if tr: 1000 if tr:
959 tr.release() 1001 tr.release()
960 lockmod.release(lock) 1002 lockmod.release(lock)