p4.py
205 lines
| 6.6 KiB
| text/x-python
|
PythonLexer
|
r7823 | # | ||
# Perforce source for convert extension. | ||||
# | ||||
# Copyright 2009, Frank Kingswood <frank@kingswood-consulting.co.uk> | ||||
# | ||||
|
r8225 | # This software may be used and distributed according to the terms of the | ||
# GNU General Public License version 2, incorporated herein by reference. | ||||
|
r7823 | # | ||
from mercurial import util | ||||
from mercurial.i18n import _ | ||||
|
r7973 | from common import commit, converter_source, checktool, NoRepo | ||
|
r7823 | import marshal | ||
|
r8829 | import re | ||
|
r7823 | |||
def loaditer(f): | ||||
"Yield the dictionary objects generated by p4" | ||||
try: | ||||
while True: | ||||
d = marshal.load(f) | ||||
if not d: | ||||
break | ||||
yield d | ||||
except EOFError: | ||||
pass | ||||
class p4_source(converter_source): | ||||
def __init__(self, ui, path, rev=None): | ||||
super(p4_source, self).__init__(ui, path, rev=rev) | ||||
|
r8829 | if "/" in path and not path.startswith('//'): | ||
|
r7973 | raise NoRepo('%s does not look like a P4 repo' % path) | ||
|
r7905 | checktool('p4', abort=False) | ||
|
r7823 | |||
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.modecache = {} | ||||
|
r8829 | 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]*\$") | ||||
|
r7823 | |||
self._parse(ui, path) | ||||
def _parse_view(self, path): | ||||
"Read changes affecting the path" | ||||
|
r8063 | cmd = 'p4 -G changes -s submitted "%s"' % path | ||
|
r9474 | stdout = util.popen(cmd, mode='rb') | ||
|
r7823 | for d in loaditer(stdout): | ||
c = d.get("change", None) | ||||
if c: | ||||
self.p4changes[c] = True | ||||
def _parse(self, ui, path): | ||||
"Prepare list of P4 filenames and revisions to 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: | ||||
|
r8063 | cmd = 'p4 -G client -o "%s"' % path | ||
|
r9474 | clientspec = marshal.load(util.popen(cmd, mode='rb')) | ||
|
r7869 | |||
|
r7823 | views = {} | ||
for client in clientspec: | ||||
if client.startswith("View"): | ||||
sview, cview = clientspec[client].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 = self.p4changes.keys() | ||||
self.p4changes.sort(key=int) | ||||
# list with depot pathnames, longest first | ||||
vieworder = views.keys() | ||||
|
r9039 | vieworder.sort(key=len, reverse=True) | ||
|
r7823 | |||
# handle revision limiting | ||||
startrev = self.ui.config('convert', 'p4.startrev', default=0) | ||||
|
r7869 | self.p4changes = [x for x in self.p4changes | ||
if ((not startrev or int(x) >= int(startrev)) and | ||||
|
r7823 | (not self.rev or int(x) <= int(self.rev)))] | ||
# now read the full changelists to get the list of file revisions | ||||
ui.status(_('collecting p4 changelists\n')) | ||||
lastid = None | ||||
for change in self.p4changes: | ||||
cmd = "p4 -G describe %s" % change | ||||
|
r9474 | stdout = util.popen(cmd, mode='rb') | ||
|
r7823 | d = marshal.load(stdout) | ||
desc = self.recode(d["desc"]) | ||||
shortdesc = desc.split("\n", 1)[0] | ||||
t = '%s %s' % (d["change"], repr(shortdesc)[1:-1]) | ||||
ui.status(util.ellipsis(t, 80) + '\n') | ||||
if lastid: | ||||
parents = [lastid] | ||||
else: | ||||
parents = [] | ||||
|
r7869 | |||
|
r7823 | date = (int(d["time"]), 0) # timezone not set | ||
c = commit(author=self.recode(d["user"]), date=util.datestr(date), | ||||
parents=parents, desc=desc, branch='', extra={"p4": change}) | ||||
files = [] | ||||
i = 0 | ||||
while ("depotFile%d" % i) in d and ("rev%d" % i) in d: | ||||
oldname = d["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, d["rev%d" % i])) | ||||
self.depotname[filename] = oldname | ||||
i += 1 | ||||
self.changeset[change] = c | ||||
self.files[change] = files | ||||
lastid = change | ||||
|
r7869 | |||
|
r7823 | if lastid: | ||
self.heads = [lastid] | ||||
def getheads(self): | ||||
return self.heads | ||||
def getfile(self, name, rev): | ||||
|
r8063 | cmd = 'p4 -G print "%s#%s"' % (self.depotname[name], rev) | ||
|
r9474 | stdout = util.popen(cmd, mode='rb') | ||
|
r7823 | |||
mode = None | ||||
|
r8829 | contents = "" | ||
keywords = None | ||||
|
r7823 | |||
for d in loaditer(stdout): | ||||
|
r8829 | code = d["code"] | ||
data = d.get("data") | ||||
if code == "error": | ||||
raise IOError(d["generic"], data) | ||||
|
r8843 | |||
|
r8829 | elif code == "stat": | ||
p4type = self.re_type.match(d["type"]) | ||||
if p4type: | ||||
|
r7823 | mode = "" | ||
|
r8829 | 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 | ||||
|
r8843 | |||
|
r8829 | elif code == "text" or code == "binary": | ||
contents += data | ||||
|
r7823 | |||
if mode is None: | ||||
|
r8829 | raise IOError(0, "bad stat") | ||
|
r7823 | |||
self.modecache[(name, rev)] = mode | ||||
|
r8829 | |||
if keywords: | ||||
contents = keywords.sub("$\\1$", contents) | ||||
if mode == "l" and contents.endswith("\n"): | ||||
contents = contents[:-1] | ||||
return contents | ||||
|
r7823 | |||
def getmode(self, name, rev): | ||||
return self.modecache[(name, rev)] | ||||
def getchanges(self, rev): | ||||
return self.files[rev], {} | ||||
def getcommit(self, rev): | ||||
return self.changeset[rev] | ||||
def gettags(self): | ||||
return self.tags | ||||
def getchangedfiles(self, rev, i): | ||||
|
r8209 | return sorted([x[0] for x in self.files[rev]]) | ||