[PATCH] convert: Perforce source for conversion to Mercurial

Matt Mackall mpm at selenic.com
Thu Feb 19 20:25:05 CST 2009


On Thu, 2009-02-19 at 22:28 +0000, Frank Kingswood wrote:
> # HG changeset patch
> # User Frank Kingswood <frank at kingswood-consulting.co.uk>
> # Date 1235082528 0
> # Node ID d602f5013d174fb5c1c82d0e1711ea2b0e9e7e20
> # Parent  cf427b04d5c0fde0f2b745586c1693f831543bce
> convert: Perforce source for conversion to Mercurial
> Take two - minor coding style issues fixed.

Patrick, because this is a wholy new feature with no possible regression
impact on any other code, this is fine for 1.2 so long as it meets with
your approval.

It's really short. Any chance we could get a test case as well?

> diff -r cf427b04d5c0 -r d602f5013d17 hgext/convert/__init__.py
> --- a/hgext/convert/__init__.py	Wed Feb 18 13:19:30 2009 +0100
> +++ b/hgext/convert/__init__.py	Thu Feb 19 22:28:48 2009 +0000
> @@ -25,6 +25,7 @@
>      - Monotone [mtn]
>      - GNU Arch [gnuarch]
>      - Bazaar [bzr]
> +    - Perforce [p4]
>  
>      Accepted destination formats [identifiers]:
>      - Mercurial [hg]
> @@ -168,6 +169,21 @@
>      --config convert.svn.startrev=0           (svn revision number)
>          specify start Subversion revision.
>  
> +    Perforce Source
> +    ---------------
> +
> +    The Perforce (P4) importer does a straight import, ignoring labels,
> +    branches and integrations. It will set a tag on the final changeset
> +    referencing the Perforce changelist number.
> +
> +    Source history can be retrieved starting at a specific revision,
> +    instead of being integrally converted. Only single branch
> +    conversions are supported.
> +
> +    --config convert.p4.startrev=0           (perforce changelist number)
> +        specify start Perforce revision.
> +
> +
>      Mercurial Destination
>      ---------------------
>  
> diff -r cf427b04d5c0 -r d602f5013d17 hgext/convert/convcmd.py
> --- a/hgext/convert/convcmd.py	Wed Feb 18 13:19:30 2009 +0100
> +++ b/hgext/convert/convcmd.py	Thu Feb 19 22:28:48 2009 +0000
> @@ -14,6 +14,7 @@
>  from monotone import monotone_source
>  from gnuarch import gnuarch_source
>  from bzr import bzr_source
> +from p4 import p4_source
>  import filemap
>  
>  import os, shutil
> @@ -37,6 +38,7 @@
>      ('mtn', monotone_source),
>      ('gnuarch', gnuarch_source),
>      ('bzr', bzr_source),
> +    ('p4', p4_source),
>      ]
>  
>  sink_converters = [
> diff -r cf427b04d5c0 -r d602f5013d17 hgext/convert/p4.py
> --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
> +++ b/hgext/convert/p4.py	Thu Feb 19 22:28:48 2009 +0000
> @@ -0,0 +1,178 @@
> +#
> +# Mercurial import from Perforce.
> +#
> +# Copyright 2009, Frank Kingswood <frank at kingswood-consulting.co.uk>
> +#
> +# This software may be used and distributed according to the terms
> +# of the GNU General Public License, incorporated herein by reference.
> +#
> +
> +from mercurial import util
> +from mercurial.i18n import _
> +
> +from common import commit, converter_source, checktool
> +import marshal
> +
> +def loaditer(f):
> +    try:
> +        d = marshal.load(f)
> +        while d:
> +            yield d
> +            d = marshal.load(f)
> +    except EOFError, e:
> +        pass
> +
> +class p4_source(converter_source):
> +    def __init__(self, ui, path, rev=None):
> +        super(p4_source, self).__init__(ui, path, rev=rev)
> +
> +        checktool('p4')
> +
> +        self.p4changes = {}
> +        self.heads = {}
> +        self.changeset = {}
> +        self.files = {}
> +        self.tags = {}
> +        self.lastbranch = {}
> +        self.parent = {}
> +        self.encoding = "latin_1"
> +        self.depotname={}           # mapping from local name to depot name
> +
> +        self._parse(ui, path)
> +
> +    def _parse_view(self, path):
> +        "Read changes affecting the path"
> +        cmd = "p4 -G changes -s submitted %s" % path
> +        stdout = util.popen(cmd)
> +        for f in loaditer(stdout):
> +            c = f.get("change", None)
> +            if c:
> +                self.p4changes[c] = True
> +
> +    def _parse(self, ui, path):
> +        "Prepare list of P4 filenames and revisions for import"
> +        ui.status(_('reading p4 views\n'))
> +
> +        # read client spec or view
> +        if "/" in path:
> +            self._parse_view(path)
> +            if path.startswith("//") and path.endswith("/..."):
> +                views = {path[:-3]:""}
> +            else:
> +                views = {"//":""}
> +        else:
> +            cmd = "p4 -G client -o %s" % path
> +            clientspec = marshal.load(util.popen(cmd))
> +            
> +            views = {}
> +            for x in clientspec:
> +                if x.startswith("View"):
> +                    sview,cview = clientspec[x].split()
> +                    self._parse_view(sview)
> +                    if sview.endswith("...") and cview.endswith("..."):
> +                        sview = sview[:-3]
> +                        cview = cview[:-3]
> +                    cview = cview[2:]
> +                    cview = cview[cview.find("/") + 1:]
> +                    views[sview] = cview
> +
> +        # list of changes that affect our source files
> +        self.p4changes = [x for x in self.p4changes]
> +        self.p4changes.sort(key = int)
> +
> +        # prepare a list to check depot filenames to make local view
> +        vieworder = [x for x in views]
> +        vieworder.sort(key = lambda x:-len(x))
> +
> +        # handle revision limiting
> +        startrev = self.ui.config('convert', 'p4.startrev', default=0)
> +        self.p4changes = [x for x in self.p4changes 
> +                          if ((not startrev or int(x)>=int(startrev)) and 
> +                              (not self.rev or int(x)<=int(self.rev)))]
> +
> +        # now read the full changelists to get the list of file revisions
> +        ui.status(_('collect p4 changelists\n'))
> +        lastid = None
> +        for id in self.p4changes:
> +            cmd = "p4 -G describe %s" % id
> +            stdout = util.popen(cmd)
> +            f = marshal.load(stdout)
> +            del stdout
> +
> +            shortdesc = desc = self.recode(f["desc"])
> +            i = shortdesc.find("\n")
> +            if i != -1:
> +                shortdesc = shortdesc[:i]
> +            t = '%s %s' % (f["change"], repr(shortdesc)[1:-1])
> +            ui.status(util.ellipsis(t, 80) + '\n')
> +
> +            if lastid:
> +                parents = [lastid]
> +            else:
> +                parents = []
> +            
> +            date = (int(f["time"]), 0)     # timezone not set
> +            c = commit(author=self.recode(f["user"]), date=util.datestr(date),
> +                        parents=parents, desc=desc, branch='')
> +
> +            files = []
> +            i = 0
> +            while "depotFile%d" % i in f and "rev%d" % i:
> +                oldname = f["depotFile%d" % i]
> +                filename = None
> +                for v in vieworder:
> +                    if oldname.startswith(v):
> +                        filename = views[v] + oldname[len(v):]
> +                        break
> +                if filename:
> +                    files.append((filename, f["rev%d" % i]))
> +                    self.depotname[filename] = oldname
> +                i += 1
> +            self.changeset[id] = c
> +            self.files[id] = files
> +            lastid = id
> +        
> +        if lastid:
> +            self.heads = [lastid]
> +            self.tags["P4 %s" % lastid] = lastid
> +
> +    def getheads(self):
> +        return self.heads
> +
> +    def getfile(self, file, rev):
> +        cmd = "p4 -G print %s#%s" % (self.depotname[file], rev)
> +        stdout = util.popen(cmd)
> +
> +        mode = None
> +        data = ""
> +
> +        for f in loaditer(stdout):
> +            if f["code"] == "stat":
> +                if "+x" in f["type"]:
> +                    mode = "x"
> +                else:
> +                    mode = ""
> +            elif f["code"] == "text":
> +                data += f["data"]
> +
> +        if mode is None:
> +            raise IOError()
> +
> +        self.modecache[(file, rev)] = mode
> +        return data
> +
> +    def getmode(self, file, rev):
> +        return self.modecache[(file, rev)]
> +
> +    def getchanges(self, rev):
> +        self.modecache = {}
> +        return self.files[rev], {}
> +
> +    def getcommit(self, rev):
> +        return self.changeset[rev]
> +
> +    def gettags(self):
> +        return self.tags
> +
> +    def getchangedfiles(self, rev, i):
> +        return util.sort([x[0] for x in self.files[rev]])
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel
-- 
http://selenic.com : development and support for Mercurial and Linux




More information about the Mercurial-devel mailing list