p4.py
203 lines
| 6.7 KiB
| text/x-python
|
PythonLexer
Frank Kingswood
|
r7823 | # Perforce source for convert extension. | ||
# | ||||
# Copyright 2009, Frank Kingswood <frank@kingswood-consulting.co.uk> | ||||
# | ||||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Frank Kingswood
|
r7823 | |||
from mercurial import util | ||||
from mercurial.i18n import _ | ||||
Matt Mackall
|
r7973 | from common import commit, converter_source, checktool, NoRepo | ||
Frank Kingswood
|
r7823 | import marshal | ||
Frank Kingswood
|
r8829 | import re | ||
Frank Kingswood
|
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) | ||||
Frank Kingswood
|
r8829 | if "/" in path and not path.startswith('//'): | ||
Martin Geisler
|
r10939 | raise NoRepo(_('%s does not look like a P4 repository') % path) | ||
Matt Mackall
|
r7973 | |||
Mads Kiilerich
|
r7905 | checktool('p4', abort=False) | ||
Frank Kingswood
|
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 | ||||
Matt Mackall
|
r10282 | 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)" | ||||
r":[^$\n]*\$") | ||||
Frank Kingswood
|
r8829 | self.re_keywords_old = re.compile("\$(Id|Header):[^$\n]*\$") | ||
Frank Kingswood
|
r7823 | |||
self._parse(ui, path) | ||||
def _parse_view(self, path): | ||||
"Read changes affecting the path" | ||||
Frank Kingswood
|
r11347 | cmd = 'p4 -G changes -s submitted %s' % util.shellquote(path) | ||
Peter Ingebretson
|
r9474 | stdout = util.popen(cmd, mode='rb') | ||
Frank Kingswood
|
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: | ||||
Frank Kingswood
|
r11347 | cmd = 'p4 -G client -o %s' % util.shellquote(path) | ||
Peter Ingebretson
|
r9474 | clientspec = marshal.load(util.popen(cmd, mode='rb')) | ||
Dirkjan Ochtman
|
r7869 | |||
Frank Kingswood
|
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() | ||||
Martin Geisler
|
r9039 | vieworder.sort(key=len, reverse=True) | ||
Frank Kingswood
|
r7823 | |||
# handle revision limiting | ||||
startrev = self.ui.config('convert', 'p4.startrev', default=0) | ||||
Dirkjan Ochtman
|
r7869 | self.p4changes = [x for x in self.p4changes | ||
if ((not startrev or int(x) >= int(startrev)) and | ||||
Frank Kingswood
|
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: | ||||
Mike Sperber
|
r12891 | cmd = "p4 -G describe -s %s" % change | ||
Peter Ingebretson
|
r9474 | stdout = util.popen(cmd, mode='rb') | ||
Frank Kingswood
|
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 = [] | ||||
Dirkjan Ochtman
|
r7869 | |||
Frank Kingswood
|
r7823 | date = (int(d["time"]), 0) # timezone not set | ||
FUJIWARA Katsunori
|
r16514 | c = commit(author=self.recode(d["user"]), | ||
date=util.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'), | ||||
Matt Mackall
|
r10282 | parents=parents, desc=desc, branch='', | ||
extra={"p4": change}) | ||||
Frank Kingswood
|
r7823 | |||
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 | ||||
Dirkjan Ochtman
|
r7869 | |||
Frank Kingswood
|
r7823 | if lastid: | ||
self.heads = [lastid] | ||||
def getheads(self): | ||||
return self.heads | ||||
def getfile(self, name, rev): | ||||
Martin Geisler
|
r11348 | cmd = 'p4 -G print %s' \ | ||
% util.shellquote("%s#%s" % (self.depotname[name], rev)) | ||||
Peter Ingebretson
|
r9474 | stdout = util.popen(cmd, mode='rb') | ||
Frank Kingswood
|
r7823 | |||
mode = None | ||||
Frank Kingswood
|
r8829 | contents = "" | ||
keywords = None | ||||
Frank Kingswood
|
r7823 | |||
for d in loaditer(stdout): | ||||
Frank Kingswood
|
r8829 | code = d["code"] | ||
data = d.get("data") | ||||
if code == "error": | ||||
raise IOError(d["generic"], data) | ||||
Dirkjan Ochtman
|
r8843 | |||
Frank Kingswood
|
r8829 | elif code == "stat": | ||
p4type = self.re_type.match(d["type"]) | ||||
if p4type: | ||||
Frank Kingswood
|
r7823 | mode = "" | ||
Frank Kingswood
|
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 | ||||
Dirkjan Ochtman
|
r8843 | |||
Matt Mackall
|
r12401 | elif code == "text" or code == "binary": | ||
Frank Kingswood
|
r8829 | contents += data | ||
Frank Kingswood
|
r7823 | |||
if mode is None: | ||||
Frank Kingswood
|
r8829 | raise IOError(0, "bad stat") | ||
Frank Kingswood
|
r7823 | |||
Frank Kingswood
|
r8829 | if keywords: | ||
contents = keywords.sub("$\\1$", contents) | ||||
if mode == "l" and contents.endswith("\n"): | ||||
contents = contents[:-1] | ||||
Patrick Mezard
|
r11134 | return contents, mode | ||
Frank Kingswood
|
r7823 | |||
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): | ||||
Matt Mackall
|
r8209 | return sorted([x[0] for x in self.files[rev]]) | ||