# HG changeset patch # User Kostia Balytskyi # Date 1494517773 25200 # Node ID 218ca8526ec00a05548a3da6194dfe0c8aa2e0e0 # Parent ed2c44741190dd8518cf92a844c9b6a0aa100bfc 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. diff -r ed2c44741190 -r 218ca8526ec0 mercurial/scmutil.py --- 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) diff -r ed2c44741190 -r 218ca8526ec0 tests/test-simplekeyvaluefile.py --- 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__)