[PATCH 09 of 10 shelve-ext v4] shelve: add obs-based unshelve functionality
Augie Fackler
raf at durin42.com
Tue Mar 21 17:53:56 EDT 2017
On Sat, Mar 11, 2017 at 01:00:28PM -0800, Kostia Balytskyi wrote:
> # HG changeset patch
> # User Kostia Balytskyi <ikostia at fb.com>
> # Date 1489265667 28800
> # Sat Mar 11 12:54:27 2017 -0800
> # Node ID fb764ce7dd0b6b93cdb4449f5bb52164521b0aad
> # Parent e06d7fd580a798de7818adae8b65eb166ea1defd
> shelve: add obs-based unshelve functionality
Can you clarify the relationship of patches 6 and 9 for me? Right now
they both at a headline level do the same thing, which seems unlikely.
>
> Obsolescense-based unshelve works as follows:
> 1. Instead of stripping temporary nodes, markers are created to
> obsolete them.
> 2. Restoring commit is just finding it in an unfiltered repo.
> 3. '--keep' is only passed to rebase on traditional unshelves
> (and thus traditional rebases), becuase we want markers to be
> created fro obsolete-based rebases.
> 4. 'hg unshelve' uses unfiltered repo to perform rebases
> because we want rebase to be able to create markers between original
> and new commits. 'rebaseskipobsolete' is disabled to make rebase not
> skip the commit altogether.
> 5. As per sprint discussions, hiding commits with obsmarkers is
> not ideal. This is okay for now, as long as obsshelve is an
> experimental feature. In future, once a better approach to
> hiding things is implemented, we can change the way obsshelve
> works (and also change its name to not refer to obsolescense).
>
> I have a test for the case when shelve node has been stripped before
> unshelve call, that test is together with ~30 commits I was talking
> about in patch 1.
>
> diff --git a/hgext/shelve.py b/hgext/shelve.py
> --- a/hgext/shelve.py
> +++ b/hgext/shelve.py
> @@ -25,6 +25,7 @@ from __future__ import absolute_import
> import collections
> import errno
> import itertools
> +import time
>
> from mercurial.i18n import _
> from mercurial import (
> @@ -250,8 +251,13 @@ class shelvedstate(object):
>
> def prunenodes(self):
> """Cleanup temporary nodes from the repo"""
> - repair.strip(self.ui, self.repo, self.nodestoprune, backup=False,
> - topic='shelve')
> + if self.obsshelve:
> + unfi = self.repo.unfiltered()
> + relations = [(unfi[n], ()) for n in self.nodestoprune]
> + obsolete.createmarkers(self.repo, relations)
> + else:
> + repair.strip(self.ui, self.repo, self.nodestoprune, backup=False,
> + topic='shelve')
>
> def cleanupoldbackups(repo):
> vfs = vfsmod.vfs(repo.join(backupdir))
> @@ -665,9 +671,14 @@ def unshelvecontinue(ui, repo, state, op
> util.rename(repo.join('unshelverebasestate'),
> repo.join('rebasestate'))
> try:
> - rebase.rebase(ui, repo, **{
> - 'continue' : True
> - })
> + # if shelve is obs-based, we want rebase to be able
> + # to create markers to already-obsoleted commits
> + _repo = repo.unfiltered() if state.obsshelve else repo
> + with ui.configoverride({('experimental', 'rebaseskipobsolete'):
> + 'off'}, 'unshelve'):
> + rebase.rebase(ui, _repo, **{
> + 'continue' : True,
> + })
> except Exception:
> util.rename(repo.join('rebasestate'),
> repo.join('unshelverebasestate'))
> @@ -707,30 +718,58 @@ def _commitworkingcopychanges(ui, repo,
> with ui.configoverride({('ui', 'quiet'): True}):
> node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
> tmpwctx = repo[node]
> + ui.debug("temporary working copy commit: %s:%s\n" %
> + (tmpwctx.rev(), nodemod.short(node)))
> return tmpwctx, addedbefore
>
> -def _unshelverestorecommit(ui, repo, basename):
> +def _unshelverestorecommit(ui, repo, basename, obsshelve):
> """Recreate commit in the repository during the unshelve"""
> with ui.configoverride({('ui', 'quiet'): True}):
> - shelvedfile(repo, basename, 'hg').applybundle()
> - shelvectx = repo['tip']
> + if obsshelve:
> + md = shelvedfile(repo, basename, 'oshelve').readobsshelveinfo()
> + shelvenode = nodemod.bin(md['node'])
> + repo = repo.unfiltered()
> + try:
> + shelvectx = repo[shelvenode]
> + except error.RepoLookupError:
> + m = _("shelved node %s not found in repo")
> + raise error.Abort(m % md['node'])
> + else:
> + shelvedfile(repo, basename, 'hg').applybundle()
> + shelvectx = repo['tip']
> return repo, shelvectx
>
> def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
> - tmpwctx, shelvectx, branchtorestore):
> + tmpwctx, shelvectx, branchtorestore, obsshelve):
> """Rebase restored commit from its original location to a destination"""
> # If the shelve is not immediately on top of the commit
> # we'll be merging with, rebase it to be on top.
> if tmpwctx.node() == shelvectx.parents()[0].node():
> + # shelvectx is immediately on top of the tmpwctx
> return shelvectx
>
> + # we need a new commit extra every time we perform a rebase to ensure
> + # that "nothing to rebase" does not happen with obs-based shelve
> + # "nothing to rebase" means that tip does not point to a "successor"
> + # commit after a rebase and we have no way to learn which commit
> + # should be a "shelvectx". this is a dirty hack until we implement
> + # some way to learn the results of rebase operation, other than
> + # text output and return code
> + def extrafn(ctx, extra):
> + extra['unshelve_time'] = str(time.time())
> +
> ui.status(_('rebasing shelved changes\n'))
> try:
> + # we only want keep to be true if shelve is traditional, since
> + # for obs-based shelve, rebase will also be obs-based and
> + # markers created help us track the relationship between shelvectx
> + # and its new version
> rebase.rebase(ui, repo, **{
> 'rev': [shelvectx.rev()],
> 'dest': str(tmpwctx.rev()),
> - 'keep': True,
> + 'keep': not obsshelve,
> 'tool': opts.get('tool', ''),
> + 'extrafn': extrafn if obsshelve else None
> })
> except error.InterventionRequired:
> tr.close()
> @@ -738,7 +777,7 @@ def _rebaserestoredcommit(ui, repo, opts
> nodestoprune = [repo.changelog.node(rev)
> for rev in xrange(oldtiprev, len(repo))]
> shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoprune,
> - branchtorestore, opts.get('keep'))
> + branchtorestore, opts.get('keep'), obsshelve)
>
> util.rename(repo.join('rebasestate'),
> repo.join('unshelverebasestate'))
> @@ -765,7 +804,10 @@ def _forgetunknownfiles(repo, shelvectx,
> toforget = (addedafter & shelveunknown) - addedbefore
> repo[None].forget(toforget)
>
> -def _finishunshelve(repo, oldtiprev, tr):
> +def _finishunshelve(repo, oldtiprev, tr, obsshelve):
> + if obsshelve:
> + tr.close()
> + return
> # The transaction aborting will strip all the commits for us,
> # but it doesn't update the inmemory structures, so addchangegroup
> # hooks still fire and try to operate on the missing commits.
> @@ -784,6 +826,20 @@ def _checkunshelveuntrackedproblems(ui,
> hint = _("run hg status to see which files are missing")
> raise error.Abort(m, hint=hint)
>
> +def _obsoleteredundantnodes(repo, tr, pctx, shelvectx, tmpwctx):
> + # order is important in the list of [shelvectx, tmpwctx] below
> + # some nodes may already be obsolete
> + unfi = repo.unfiltered()
> + nodestoobsolete = filter(lambda x: x != pctx, [shelvectx, tmpwctx])
> + seen = set()
> + relations = []
> + for nto in nodestoobsolete:
> + if nto in seen:
> + continue
> + seen.add(nto)
> + relations.append((unfi[nto.rev()], ()))
> + obsolete.createmarkers(unfi, relations)
> +
> @command('unshelve',
> [('a', 'abort', None,
> _('abort an incomplete unshelve operation')),
> @@ -896,6 +952,14 @@ def _dounshelve(ui, repo, *shelved, **op
> raise error.Abort(_("shelved change '%s' not found") % basename)
>
> lock = tr = None
> + obsshelve = isobsshelve(repo, ui)
> + obsshelvedfile = shelvedfile(repo, basename, 'oshelve')
> + if obsshelve and not obsshelvedfile.exists():
> + # although we can unshelve a obs-based shelve technically,
> + # this particular shelve was created using a traditional way
> + obsshelve = False
> + ui.note(_("falling back to traditional unshelve since "
> + "shelve was traditional"))
> try:
> lock = repo.lock()
> tr = repo.transaction('unshelve', report=lambda x: None)
> @@ -912,23 +976,28 @@ def _dounshelve(ui, repo, *shelved, **op
> tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
> tmpwctx)
>
> - repo, shelvectx = _unshelverestorecommit(ui, repo, basename)
> + repo, shelvectx = _unshelverestorecommit(ui, repo, basename, obsshelve)
> _checkunshelveuntrackedproblems(ui, repo, shelvectx)
> branchtorestore = ''
> if shelvectx.branch() != shelvectx.p1().branch():
> branchtorestore = shelvectx.branch()
>
> - with ui.configoverride({('ui', 'forcemerge'): opts.get('tool', '')},
> - 'unshelve'):
> + rebaseconfigoverrides = {('ui', 'forcemerge'): opts.get('tool', ''),
> + ('experimental', 'rebaseskipobsolete'): 'off'}
> + with ui.configoverride(rebaseconfigoverrides, 'unshelve'):
> shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
> basename, pctx, tmpwctx,
> - shelvectx, branchtorestore)
> + shelvectx, branchtorestore,
> + obsshelve)
> mergefiles(ui, repo, pctx, shelvectx)
> restorebranch(ui, repo, branchtorestore)
> _forgetunknownfiles(repo, shelvectx, addedbefore)
>
> + if obsshelve:
> + _obsoleteredundantnodes(repo, tr, pctx, shelvectx, tmpwctx)
> +
> shelvedstate.clear(repo)
> - _finishunshelve(repo, oldtiprev, tr)
> + _finishunshelve(repo, oldtiprev, tr, obsshelve)
> unshelvecleanup(ui, repo, basename, opts)
> finally:
> if tr:
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
More information about the Mercurial-devel
mailing list