[PATCH] convert: extend Perforce converter to support (unofficial) p4 URI scheme

Eugene Baranov eug.baranov at gmail.com
Thu Jul 16 17:34:45 UTC 2015


# HG changeset patch
# User Eugene Baranov <eug.baranov at gmail.com>
# Date 1437065858 -3600
#      Thu Jul 16 17:57:38 2015 +0100
# Node ID bcb96f98d9cfade5098a9a12b49714d218441f83
# Parent  7f1fc20a2a2c5003e8f973248b8049c6f6c9c859
convert: extend Perforce converter to support (unofficial) p4 URI scheme

Before this patch converting from Perforce repository would require you to set
up quite a few environment variables before 'p4' tool would work correctly as
well as explicitly passing source type of 'p4' to 'hg convert'. Which is not
a big deal but can get messy especially if you need to deal with different
Perforce servers.

Introducing support for p4 URI scheme allows to simplify:

    export P4PORT=p4.local:1234
    export P4USER=admin
    export P4PASSWD=xyz
    hg convert -s p4 //Depot/Test/... Test-hg

into:

    hg convert p4://admin:xyz at p4.local:1234/Depot/Test

diff -r 7f1fc20a2a2c -r bcb96f98d9cf hgext/convert/p4.py
--- a/hgext/convert/p4.py	Tue Jul 14 14:40:56 2015 +0100
+++ b/hgext/convert/p4.py	Thu Jul 16 17:57:38 2015 +0100
@@ -37,12 +37,97 @@
         filename = filename.replace(k, v)
     return filename
 
+class p4connection:
+    def __init__(self, path):
+        '''
+        >>> tests = ['p4://admin:xyz at test.local:4321/Depot/Test',
+        ...          'p4://user:password@/Development/Branch/',
+        ...          'p4://power at abc.com/Something/Somewhere/...',
+        ...          'p4://127.0.0.1/local',
+        ...          'p4:///Repo',
+        ...          '//Depot/Main',
+        ...          'Client']
+        >>> for t in tests:
+        ...     p4 = p4connection(t)
+        ...     p4.username, p4.password, p4.port, p4.path
+        ('admin', 'xyz', 'test.local:4321', '//Depot/Test/...')
+        ('user', 'password', '', '//Development/Branch/...')
+        ('power', None, 'abc.com', '//Something/Somewhere/...')
+        (None, None, '127.0.0.1', '//local/...')
+        (None, None, '', '//Repo/...')
+        (None, None, None, '//Depot/Main')
+        (None, None, None, 'Client')
+        '''
+        self.username = None
+        self.password = None
+        self.port = None
+        # Additional behaviour to recognize Perforce URL in a format:
+        # p4://[username[:password]@]/Depot/Name[/[...]]
+        # (it will make sure there is a trailing '/...') 
+        scheme = 'p4://'
+        if path.startswith(scheme):
+            path = path[len(scheme):]
+            netloc = None
+            if '/' in path:
+                netloc, path = path.split('/', 1)
+            else:
+                netloc, path = path, ''
+            if '@' in netloc:
+                userinfo, self.port = netloc.split('@', 1)
+                if ':' in userinfo:
+                    self.username, self.password = userinfo.split(':', 1)
+                else:
+                    self.username = userinfo
+            else:
+                self.port = netloc
+
+            # Make sure path starts with '//'
+            while (not path.startswith('//')):
+                path = '/' + path
+            # and ends with '/...'
+            if path.endswith('/'):
+                path += '...'
+            elif not path.endswith('/...'):
+                path += '/...'
+        self.path = path
+
+    def _getcommand(self, p4cmd):
+        '''
+        >>> tests = ['p4:///Depot/Main',
+        ...          'p4:///Repo',
+        ...          '//Depot/Main',
+        ...          'Client']
+        >>> for t in tests:
+        ...     p4 = p4connection(t)
+        ...     p4._getcommand('test')
+        'p4 -G test'
+        'p4 -G test'
+        'p4 -G test'
+        'p4 -G test'
+        '''
+        p4opts = ''
+        opts = [('-p', self.port),
+                ('-u', self.username),
+                ('-P', self.password)]
+        for k, v in opts:
+            if v:
+                p4opts += k + ' ' + util.shellquote(v) + ' '
+        cmd = 'p4 -G ' + p4opts + p4cmd
+        return cmd
+
+    def runcommand(self, cmd):
+        cmd = self._getcommand(cmd)
+        stdout = util.popen(cmd, mode='rb')
+        return stdout
+
 class p4_source(converter_source):
     def __init__(self, ui, path, revs=None):
         super(p4_source, self).__init__(ui, path, revs=revs)
 
-        if "/" in path and not path.startswith('//'):
+        if ("/" in path and not path.startswith('//')
+            and not path.startswith('p4://')):
             raise NoRepo(_('%s does not look like a P4 repository') % path)
+        self.p4 = p4connection(path)
 
         checktool('p4', abort=False)
 
@@ -68,12 +153,12 @@
         if revs and len(revs) > 1:
             raise util.Abort(_("p4 source does not support specifying "
                                "multiple revisions"))
-        self._parse(ui, path)
+        self._parse(ui, self.p4.path)
 
     def _parse_view(self, path):
         "Read changes affecting the path"
-        cmd = 'p4 -G changes -s submitted %s' % util.shellquote(path)
-        stdout = util.popen(cmd, mode='rb')
+        cmd = 'changes -s submitted %s' % util.shellquote(path)
+        stdout = self.p4.runcommand(cmd)
         for d in loaditer(stdout):
             c = d.get("change", None)
             if c:
@@ -91,8 +176,8 @@
             else:
                 views = {"//": ""}
         else:
-            cmd = 'p4 -G client -o %s' % util.shellquote(path)
-            clientspec = marshal.load(util.popen(cmd, mode='rb'))
+            cmd = 'client -o %s' % util.shellquote(path)
+            clientspec = marshal.load(self.p4.runcommand(cmd))
 
             views = {}
             for client in clientspec:
@@ -124,8 +209,7 @@
         ui.status(_('collecting p4 changelists\n'))
         lastid = None
         for change in self.p4changes:
-            cmd = "p4 -G describe -s %s" % change
-            stdout = util.popen(cmd, mode='rb')
+            stdout = self.p4.runcommand('describe -s %s' % change)
             d = marshal.load(stdout)
             desc = self.recode(d.get("desc", ""))
             shortdesc = desc.split("\n", 1)[0]
@@ -166,9 +250,8 @@
             for filename in copiedfiles:
                 oldname = self.depotname[filename]
 
-                flcmd = 'p4 -G filelog %s' \
-                      % util.shellquote(oldname)
-                flstdout = util.popen(flcmd, mode='rb')
+                flcmd = 'filelog %s' % util.shellquote(oldname)
+                flstdout = self.p4.runcommand(flcmd)
 
                 copiedfilename = None
                 for d in loaditer(flstdout):
@@ -208,12 +291,12 @@
         return self.heads
 
     def getfile(self, name, rev):
-        cmd = 'p4 -G print %s' \
+        cmd = 'print %s' \
             % util.shellquote("%s#%s" % (self.depotname[name], rev))
 
         lasterror = None
         while True:
-            stdout = util.popen(cmd, mode='rb')
+            stdout = self.p4.runcommand(cmd)
 
             mode = None
             contents = ""


More information about the Mercurial-devel mailing list