diff hgext/convert/p4.py @ 8829:ce4b92f5cea7

convert: Make P4 conversion cope with keywords, binary files and symbolic links. Convert now handles errors from p4 during conversion more gracefully. If keyword expansion is enabled in a P4 file then keywords will be unexpanded in hg. Added testcase for p4 filetypes and keyword (un)expansion. This testcase ignores UTF and Apple files to avoid binary data. Edited by pmezard: fixed collation issue on OSX
author Frank Kingswood <frank@kingswood-consulting.co.uk>
date Thu, 18 Jun 2009 10:39:04 +0100
parents 46293a0c7e9f
children eb7b247a98ea
line wrap: on
line diff
--- a/hgext/convert/p4.py	Thu Jun 18 21:13:56 2009 +0200
+++ b/hgext/convert/p4.py	Thu Jun 18 10:39:04 2009 +0100
@@ -12,6 +12,7 @@
 
 from common import commit, converter_source, checktool, NoRepo
 import marshal
+import re
 
 def loaditer(f):
     "Yield the dictionary objects generated by p4"
@@ -28,7 +29,7 @@
     def __init__(self, ui, path, rev=None):
         super(p4_source, self).__init__(ui, path, rev=rev)
 
-        if not path.startswith('//'):
+        if "/" in path and not path.startswith('//'):
             raise NoRepo('%s does not look like a P4 repo' % path)
 
         checktool('p4', abort=False)
@@ -43,6 +44,9 @@
         self.encoding = "latin_1"
         self.depotname = {}           # mapping from local name to depot name
         self.modecache = {}
+        self.re_type = re.compile("([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)(\+\w+)?$")
+        self.re_keywords = re.compile(r"\$(Id|Header|Date|DateTime|Change|File|Revision|Author):[^$\n]*\$")
+        self.re_keywords_old = re.compile("\$(Id|Header):[^$\n]*\$")
 
         self._parse(ui, path)
 
@@ -146,22 +150,44 @@
         stdout = util.popen(cmd)
 
         mode = None
-        data = ""
+        contents = ""
+        keywords = None
 
         for d in loaditer(stdout):
-            if d["code"] == "stat":
-                if "+x" in d["type"]:
-                    mode = "x"
-                else:
+            code = d["code"]
+            data = d.get("data")
+
+            if code == "error":
+                raise IOError(d["generic"], data)
+            
+            elif code == "stat":
+                p4type = self.re_type.match(d["type"])
+                if p4type:
                     mode = ""
-            elif d["code"] == "text":
-                data += d["data"]
+                    flags = (p4type.group(1) or "") + (p4type.group(3) or "")
+                    if "x" in flags:
+                        mode = "x"
+                    if p4type.group(2) == "symlink":
+                        mode = "l"
+                    if "ko" in flags:
+                        keywords = self.re_keywords_old
+                    elif "k" in flags:
+                        keywords = self.re_keywords
+            
+            elif code == "text" or code == "binary":
+                contents += data
 
         if mode is None:
-            raise IOError()
+            raise IOError(0, "bad stat")
 
         self.modecache[(name, rev)] = mode
-        return data
+
+        if keywords:
+            contents = keywords.sub("$\\1$", contents)
+        if mode == "l" and contents.endswith("\n"):
+            contents = contents[:-1]
+
+        return contents
 
     def getmode(self, name, rev):
         return self.modecache[(name, rev)]