hgext/shelve.py
branchstable
changeset 19961 1d7a36ff2615
parent 19951 d51c4d85ec23
child 19963 6f29cc567845
equal deleted inserted replaced
19960:95304251c376 19961:1d7a36ff2615
    25 from mercurial.node import nullid, bin, hex
    25 from mercurial.node import nullid, bin, hex
    26 from mercurial import changegroup, cmdutil, scmutil, phases
    26 from mercurial import changegroup, cmdutil, scmutil, phases
    27 from mercurial import error, hg, mdiff, merge, patch, repair, util
    27 from mercurial import error, hg, mdiff, merge, patch, repair, util
    28 from mercurial import templatefilters
    28 from mercurial import templatefilters
    29 from mercurial import lock as lockmod
    29 from mercurial import lock as lockmod
       
    30 from hgext import rebase
    30 import errno
    31 import errno
    31 
    32 
    32 cmdtable = {}
    33 cmdtable = {}
    33 command = cmdutil.command(cmdtable)
    34 command = cmdutil.command(cmdtable)
    34 testedwith = 'internal'
    35 testedwith = 'internal'
    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()