Patchwork [2,of,4,shelve-ext,v5] scmutil: make simplekeyvaluefile able to have a non-key-value first line

login
register
mail settings
Submitter Kostia Balytskyi
Date May 11, 2017, 9:43 p.m.
Message ID <b79f27451d7af524c07b.1494539005@devvm1416.lla2.facebook.com>
Download mbox | patch
Permalink /patch/20578/
State Accepted
Headers show

Comments

Kostia Balytskyi - May 11, 2017, 9:43 p.m.
# HG changeset patch
# User Kostia Balytskyi <ikostia@fb.com>
# Date 1494517773 25200
#      Thu May 11 08:49:33 2017 -0700
# Node ID b79f27451d7af524c07b89a983cf750bd96292c5
# Parent  ebf9affb2f7013a6a597ddd0765c786ed36f3722
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.
Yuya Nishihara - May 13, 2017, 10:33 a.m.
On Thu, 11 May 2017 14:43:25 -0700, Kostia Balytskyi wrote:
> # HG changeset patch
> # User Kostia Balytskyi <ikostia@fb.com>
> # Date 1494517773 25200
> #      Thu May 11 08:49:33 2017 -0700
> # Node ID b79f27451d7af524c07b89a983cf750bd96292c5
> # Parent  ebf9affb2f7013a6a597ddd0765c786ed36f3722
> scmutil: make simplekeyvaluefile able to have a non-key-value first line

Queued the first two, thanks.

>          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())
> +            update_dict = dict(line[:-1].split('=', 1) for line in lines
> +                                                       if line.strip())

I did s/update_dict/updatedict/ per our coding style.

Patch

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -917,28 +917,57 @@  class simplekeyvaluefile(object):
 
     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())
+            update_dict = dict(line[:-1].split('=', 1) for line in lines
+                                                       if line.strip())
+            if self.firstlinekey in update_dict:
+                e = _("%r can't be used as a key")
+                raise error.CorruptedState(e % self.firstlinekey)
+            d.update(update_dict)
         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 --git a/tests/test-simplekeyvaluefile.py b/tests/test-simplekeyvaluefile.py
--- a/tests/test-simplekeyvaluefile.py
+++ b/tests/test-simplekeyvaluefile.py
@@ -72,5 +72,13 @@  class testsimplekeyvaluefile(unittest.Te
         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__)