[PATCH] convert: Perforce source for conversion to Mercurial
Frank Kingswood
frank at kingswood-consulting.co.uk
Thu Feb 19 20:47:38 UTC 2009
# HG changeset patch
# User Frank Kingswood <frank at kingswood-consulting.co.uk>
# Date 1235075427 0
# Node ID 1d788d1e989e868e8c2f1de9e98466da149f3079
# Parent cf427b04d5c0fde0f2b745586c1693f831543bce
convert: Perforce source for conversion to Mercurial.
diff -r cf427b04d5c0 -r 1d788d1e989e 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 20:30:27 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 1d788d1e989e 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 20:30:27 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 1d788d1e989e hgext/convert/p4.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/convert/p4.py Thu Feb 19 20:30:27 2009 +0000
@@ -0,0 +1,179 @@
+#
+# 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 = {} # accessed by getheads()
+ 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]])
More information about the Mercurial-devel
mailing list