scmutil: make simplekeyvaluefile able to have a non-key-value first line
To ease migration from files with version numbers in their first lines,
we want simplekeyvaluefile to support a non-key-value first line. In this
way, old versions of Mercurial will read such files, discover a newer version
than the one they know how to handle and fail gracefully, rather than with
exception. Shelve's shelvestate file is an example.
--- a/mercurial/scmutil.py Thu May 11 08:39:44 2017 -0700
+++ b/mercurial/scmutil.py Thu May 11 08:49:33 2017 -0700
@@ -917,28 +917,57 @@
Keys must be alphanumerics and start with a letter, values must not
contain '\n' characters"""
+ firstlinekey = '__firstline'
def __init__(self, vfs, path, keys=None):
self.vfs = vfs
self.path = path
- def read(self):
+ def read(self, firstlinenonkeyval=False):
+ """Read the contents of a simple key-value file
+
+ 'firstlinenonkeyval' indicates whether the first line of file should
+ be treated as a key-value pair or reuturned fully under the
+ __firstline key."""
lines = self.vfs.readlines(self.path)
+ d = {}
+ if firstlinenonkeyval:
+ if not lines:
+ e = _("empty simplekeyvalue file")
+ raise error.CorruptedState(e)
+ # we don't want to include '\n' in the __firstline
+ d[self.firstlinekey] = lines[0][:-1]
+ del lines[0]
+
try:
# the 'if line.strip()' part prevents us from failing on empty
# lines which only contain '\n' therefore are not skipped
# by 'if line'
- d = dict(line[:-1].split('=', 1) for line in lines if line.strip())
+ updatedict = dict(line[:-1].split('=', 1) for line in lines
+ if line.strip())
+ if self.firstlinekey in updatedict:
+ e = _("%r can't be used as a key")
+ raise error.CorruptedState(e % self.firstlinekey)
+ d.update(updatedict)
except ValueError as e:
raise error.CorruptedState(str(e))
return d
- def write(self, data):
+ def write(self, data, firstline=None):
"""Write key=>value mapping to a file
data is a dict. Keys must be alphanumerical and start with a letter.
- Values must not contain newline characters."""
+ Values must not contain newline characters.
+
+ If 'firstline' is not None, it is written to file before
+ everything else, as it is, not in a key=value form"""
lines = []
+ if firstline is not None:
+ lines.append('%s\n' % firstline)
+
for k, v in data.items():
+ if k == self.firstlinekey:
+ e = "key name '%s' is reserved" % self.firstlinekey
+ raise error.ProgrammingError(e)
if not k[0].isalpha():
e = "keys must start with a letter in a key-value file"
raise error.ProgrammingError(e)
--- a/tests/test-simplekeyvaluefile.py Thu May 11 08:39:44 2017 -0700
+++ b/tests/test-simplekeyvaluefile.py Thu May 11 08:49:33 2017 -0700
@@ -72,5 +72,13 @@
self.assertRaises(error.CorruptedState,
scmutil.simplekeyvaluefile(self.vfs, 'badfile').read)
+ def testfirstline(self):
+ dw = {'key1': 'value1'}
+ scmutil.simplekeyvaluefile(self.vfs, 'fl').write(dw, firstline='1.0')
+ self.assertEqual(self.vfs.read('fl'), '1.0\nkey1=value1\n')
+ dr = scmutil.simplekeyvaluefile(self.vfs, 'fl')\
+ .read(firstlinenonkeyval=True)
+ self.assertEqual(dr, {'__firstline': '1.0', 'key1': 'value1'})
+
if __name__ == "__main__":
silenttestrunner.main(__name__)