# HG changeset patch # User Kostia Balytskyi # Date 1494712349 25200 # Node ID fe3105e6e051553a2495db0a5def2c9686c0c914 # Parent 16d424b971255768b0c16f2f5c5508309eb1da42 shelve: make shelvestate use simplekeyvaluefile Currently shelvestate uses line ordering to differentiate fields. This makes it hard for extensions to wrap shelve, since if two alternative versions of code add a new line, correct merging is going to be problematic. simplekeyvaluefile was introduced fot this purpose specifically. After this patch: - shelve will always write a simplekeyvaluefile - unshelve will check the first line of the file for a version, and if the version is 1, will read it in a position-based way, if the version is 2, will read it in a key-value way As discussed with Yuya previously, this will be able to handle old-style shelvedstate files, but old Mercurial versions will fail on the attempt to read shelvedstate file of version 2 with a self-explanatory message: 'abort: this version of shelve is incompatible with the version used in this repo' diff -r 16d424b97125 -r fe3105e6e051 hgext/shelve.py --- a/hgext/shelve.py Sun May 14 14:15:07 2017 -0700 +++ b/hgext/shelve.py Sat May 13 14:52:29 2017 -0700 @@ -167,7 +167,7 @@ Handles saving and restoring a shelved state. Ensures that different versions of a shelved state are possible and handles them appropriately. """ - _version = 1 + _version = 2 _filename = 'shelvedstate' _keep = 'keep' _nokeep = 'nokeep' @@ -175,26 +175,9 @@ _noactivebook = ':no-active-bookmark' @classmethod - def load(cls, repo): - # Order is important, because old shelvestate file uses it - # to detemine values of fields (i.g. version is on the first line, - # name is on the second and so forth). Please do not change. - keys = ['version', 'name', 'originalwctx', 'pendingctx', 'parents', - 'nodestoremove', 'branchtorestore', 'keep', 'activebook'] - d = {} - fp = repo.vfs(cls._filename) + def _verifyandtransform(cls, d): + """Some basic shelvestate syntactic verification and transformation""" try: - for key in keys: - d[key] = fp.readline().strip() - finally: - fp.close() - - # some basic syntactic verification and transformation - try: - d['version'] = int(d['version']) - if d['version'] != cls._version: - raise error.Abort(_('this version of shelve is incompatible ' - 'with the version used in this repo')) d['originalwctx'] = nodemod.bin(d['originalwctx']) d['pendingctx'] = nodemod.bin(d['pendingctx']) d['parents'] = [nodemod.bin(h) @@ -204,6 +187,50 @@ except (ValueError, TypeError, KeyError) as err: raise error.CorruptedState(str(err)) + @classmethod + def _getversion(cls, repo): + """Read version information from shelvestate file""" + fp = repo.vfs(cls._filename) + try: + version = int(fp.readline().strip()) + except ValueError as err: + raise error.CorruptedState(str(err)) + finally: + fp.close() + return version + + @classmethod + def _readold(cls, repo): + """Read the old position-based version of a shelvestate file""" + # Order is important, because old shelvestate file uses it + # to detemine values of fields (i.g. name is on the second line, + # originalwctx is on the third and so forth). Please do not change. + keys = ['version', 'name', 'originalwctx', 'pendingctx', 'parents', + 'nodestoremove', 'branchtorestore', 'keep', 'activebook'] + # this is executed only seldomly, so it is not a big deal + # that we open this file twice + fp = repo.vfs(cls._filename) + d = {} + try: + for key in keys: + d[key] = fp.readline().strip() + finally: + fp.close() + return d + + @classmethod + def load(cls, repo): + version = cls._getversion(repo) + if version < cls._version: + d = cls._readold(repo) + elif version == cls._version: + d = scmutil.simplekeyvaluefile(repo.vfs, cls._filename)\ + .read(firstlinenonkeyval=True) + else: + raise error.Abort(_('this version of shelve is incompatible ' + 'with the version used in this repo')) + + cls._verifyandtransform(d) try: obj = cls() obj.name = d['name'] @@ -224,19 +251,20 @@ @classmethod def save(cls, repo, name, originalwctx, pendingctx, nodestoremove, branchtorestore, keep=False, activebook=''): - fp = repo.vfs(cls._filename, 'wb') - fp.write('%i\n' % cls._version) - fp.write('%s\n' % name) - fp.write('%s\n' % nodemod.hex(originalwctx.node())) - fp.write('%s\n' % nodemod.hex(pendingctx.node())) - fp.write('%s\n' % - ' '.join([nodemod.hex(p) for p in repo.dirstate.parents()])) - fp.write('%s\n' % - ' '.join([nodemod.hex(n) for n in nodestoremove])) - fp.write('%s\n' % branchtorestore) - fp.write('%s\n' % (cls._keep if keep else cls._nokeep)) - fp.write('%s\n' % (activebook or cls._noactivebook)) - fp.close() + info = { + "name": name, + "originalwctx": nodemod.hex(originalwctx.node()), + "pendingctx": nodemod.hex(pendingctx.node()), + "parents": ' '.join([nodemod.hex(p) + for p in repo.dirstate.parents()]), + "nodestoremove": ' '.join([nodemod.hex(n) + for n in nodestoremove]), + "branchtorestore": branchtorestore, + "keep": cls._keep if keep else cls._nokeep, + "activebook": activebook or cls._noactivebook + } + scmutil.simplekeyvaluefile(repo.vfs, cls._filename)\ + .write(info, firstline=str(cls._version)) @classmethod def clear(cls, repo): diff -r 16d424b97125 -r fe3105e6e051 tests/test-shelve.t --- a/tests/test-shelve.t Sun May 14 14:15:07 2017 -0700 +++ b/tests/test-shelve.t Sat May 13 14:52:29 2017 -0700 @@ -1591,9 +1591,8 @@ Removing restore branch information from shelvedstate file(making it looks like in previous versions) and running unshelve --continue - $ head -n 6 < .hg/shelvedstate > .hg/shelvedstate_oldformat - $ rm .hg/shelvedstate - $ mv .hg/shelvedstate_oldformat .hg/shelvedstate + $ cp .hg/shelvedstate .hg/shelvedstate_old + $ cat .hg/shelvedstate_old | grep -v 'branchtorestore' > .hg/shelvedstate $ echo "aaabbbccc" > a $ rm a.orig @@ -1737,3 +1736,48 @@ [255] $ hg st ! a + $ cd .. + +New versions of Mercurial know how to read onld shelvedstate files + $ hg init oldshelvedstate + $ cd oldshelvedstate + $ echo root > root && hg ci -Am root + adding root + $ echo 1 > a + $ hg add a + $ hg shelve --name ashelve + shelved as ashelve + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo 2 > a + $ hg ci -Am a + adding a + $ hg unshelve + unshelving change 'ashelve' + rebasing shelved changes + rebasing 2:003d2d94241c "changes to: root" (tip) + merging a + warning: conflicts while merging a! (edit, then use 'hg resolve --mark') + unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') + [1] +putting v1 shelvedstate file in place of a created v2 + $ cat << EOF > .hg/shelvedstate + > 1 + > ashelve + > 8b058dae057a5a78f393f4535d9e363dd5efac9d + > 8b058dae057a5a78f393f4535d9e363dd5efac9d + > 8b058dae057a5a78f393f4535d9e363dd5efac9d 003d2d94241cc7aff0c3a148e966d6a4a377f3a7 + > 003d2d94241cc7aff0c3a148e966d6a4a377f3a7 + > + > nokeep + > :no-active-bookmark + > EOF + $ echo 1 > a + $ hg resolve --mark a + (no more unresolved files) + continue: hg unshelve --continue +mercurial does not crash + $ hg unshelve --continue + rebasing 2:003d2d94241c "changes to: root" (tip) + unshelve of 'ashelve' complete + $ cd .. +