p4.py
296 lines
| 10.2 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. | ||
timeless
|
r28371 | from __future__ import absolute_import | ||
Frank Kingswood
|
r7823 | |||
timeless
|
r28371 | import marshal | ||
import re | ||||
Yuya Nishihara
|
r29205 | from mercurial.i18n import _ | ||
timeless
|
r28371 | from mercurial import ( | ||
error, | ||||
util, | ||||
) | ||||
Frank Kingswood
|
r7823 | |||
timeless
|
r28371 | from . import common | ||
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 | ||||
Eugene Baranov
|
r25788 | def decodefilename(filename): | ||
"""Perforce escapes special characters @, #, *, or % | ||||
with %40, %23, %2A, or %25 respectively | ||||
>>> decodefilename('portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid') | ||||
'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid' | ||||
>>> decodefilename('//Depot/Directory/%2525/%2523/%23%40.%2A') | ||||
'//Depot/Directory/%25/%23/#@.*' | ||||
""" | ||||
replacements = [('%2A', '*'), ('%23', '#'), ('%40', '@'), ('%25', '%')] | ||||
for k, v in replacements: | ||||
filename = filename.replace(k, v) | ||||
return filename | ||||
timeless
|
r28371 | class p4_source(common.converter_source): | ||
Durham Goode
|
r25748 | def __init__(self, ui, path, revs=None): | ||
Eugene Baranov
|
r25884 | # avoid import cycle | ||
timeless
|
r28371 | from . import convcmd | ||
Eugene Baranov
|
r25884 | |||
Durham Goode
|
r25748 | super(p4_source, self).__init__(ui, path, revs=revs) | ||
Frank Kingswood
|
r7823 | |||
Frank Kingswood
|
r8829 | if "/" in path and not path.startswith('//'): | ||
timeless
|
r28371 | raise common.NoRepo(_('%s does not look like a P4 repository') % | ||
path) | ||||
Matt Mackall
|
r7973 | |||
timeless
|
r28371 | common.checktool('p4', abort=False) | ||
Frank Kingswood
|
r7823 | |||
self.p4changes = {} | ||||
self.heads = {} | ||||
self.changeset = {} | ||||
self.files = {} | ||||
Eugene Baranov
|
r25751 | self.copies = {} | ||
Frank Kingswood
|
r7823 | self.tags = {} | ||
self.lastbranch = {} | ||||
self.parent = {} | ||||
Eugene Baranov
|
r25884 | self.encoding = self.ui.config('convert', 'p4.encoding', | ||
default=convcmd.orig_encoding) | ||||
Frank Kingswood
|
r7823 | self.depotname = {} # mapping from local name to depot name | ||
Eugene Baranov
|
r25751 | self.localname = {} # mapping from depot name to local 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 | |||
Durham Goode
|
r25748 | if revs and len(revs) > 1: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_("p4 source does not support specifying " | ||
Durham Goode
|
r25748 | "multiple revisions")) | ||
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 | ||||
Durham Goode
|
r25748 | (not self.revs or int(x) <= int(self.revs[0])))] | ||
Frank Kingswood
|
r7823 | |||
# 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) | ||
Matt Mackall
|
r21758 | desc = self.recode(d.get("desc", "")) | ||
Frank Kingswood
|
r7823 | 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 | ||
timeless
|
r28371 | c = common.commit(author=self.recode(d["user"]), | ||
date=util.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'), | ||||
parents=parents, desc=desc, branch=None, | ||||
extra={"p4": change}) | ||||
Frank Kingswood
|
r7823 | |||
files = [] | ||||
Eugene Baranov
|
r25751 | copies = {} | ||
copiedfiles = [] | ||||
Frank Kingswood
|
r7823 | 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: | ||||
Eugene Baranov
|
r25776 | if oldname.lower().startswith(v.lower()): | ||
Eugene Baranov
|
r25788 | filename = decodefilename(views[v] + oldname[len(v):]) | ||
Frank Kingswood
|
r7823 | break | ||
if filename: | ||||
files.append((filename, d["rev%d" % i])) | ||||
self.depotname[filename] = oldname | ||||
Eugene Baranov
|
r25751 | if (d.get("action%d" % i) == "move/add"): | ||
copiedfiles.append(filename) | ||||
self.localname[oldname] = filename | ||||
Frank Kingswood
|
r7823 | i += 1 | ||
Eugene Baranov
|
r25751 | |||
# Collect information about copied files | ||||
for filename in copiedfiles: | ||||
oldname = self.depotname[filename] | ||||
flcmd = 'p4 -G filelog %s' \ | ||||
% util.shellquote(oldname) | ||||
flstdout = util.popen(flcmd, mode='rb') | ||||
copiedfilename = None | ||||
for d in loaditer(flstdout): | ||||
copiedoldname = None | ||||
i = 0 | ||||
while ("change%d" % i) in d: | ||||
if (d["change%d" % i] == change and | ||||
d["action%d" % i] == "move/add"): | ||||
j = 0 | ||||
while ("file%d,%d" % (i, j)) in d: | ||||
if d["how%d,%d" % (i, j)] == "moved from": | ||||
copiedoldname = d["file%d,%d" % (i, j)] | ||||
break | ||||
j += 1 | ||||
i += 1 | ||||
if copiedoldname and copiedoldname in self.localname: | ||||
copiedfilename = self.localname[copiedoldname] | ||||
break | ||||
if copiedfilename: | ||||
copies[filename] = copiedfilename | ||||
else: | ||||
ui.warn(_("cannot find source for copied file: %s@%s\n") | ||||
% (filename, change)) | ||||
Frank Kingswood
|
r7823 | self.changeset[change] = c | ||
self.files[change] = files | ||||
Eugene Baranov
|
r25751 | self.copies[change] = copies | ||
Frank Kingswood
|
r7823 | 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)) | ||||
Frank Kingswood
|
r7823 | |||
Eugene Baranov
|
r25775 | lasterror = None | ||
while True: | ||||
stdout = util.popen(cmd, mode='rb') | ||||
mode = None | ||||
Eugene Baranov
|
r25882 | contents = [] | ||
Eugene Baranov
|
r25775 | keywords = None | ||
Frank Kingswood
|
r7823 | |||
Eugene Baranov
|
r25775 | for d in loaditer(stdout): | ||
code = d["code"] | ||||
data = d.get("data") | ||||
Frank Kingswood
|
r8829 | |||
Eugene Baranov
|
r25775 | if code == "error": | ||
# if this is the first time error happened | ||||
# re-attempt getting the file | ||||
if not lasterror: | ||||
lasterror = IOError(d["generic"], data) | ||||
# this will exit inner-most for-loop | ||||
break | ||||
else: | ||||
raise lasterror | ||||
Dirkjan Ochtman
|
r8843 | |||
Eugene Baranov
|
r25775 | elif code == "stat": | ||
action = d.get("action") | ||||
if action in ["purge", "delete", "move/delete"]: | ||||
return None, None | ||||
p4type = self.re_type.match(d["type"]) | ||||
if p4type: | ||||
mode = "" | ||||
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 | |||
Eugene Baranov
|
r25775 | elif code == "text" or code == "binary": | ||
Eugene Baranov
|
r25882 | contents.append(data) | ||
Eugene Baranov
|
r25775 | |||
lasterror = None | ||||
if not lasterror: | ||||
break | ||||
Frank Kingswood
|
r7823 | |||
if mode is None: | ||||
Mads Kiilerich
|
r22296 | return None, None | ||
Frank Kingswood
|
r7823 | |||
Eugene Baranov
|
r25882 | contents = ''.join(contents) | ||
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 | |||
Mads Kiilerich
|
r22300 | def getchanges(self, rev, full): | ||
if full: | ||||
timeless@mozdev.org
|
r26779 | raise error.Abort(_("convert from p4 does not support --full")) | ||
Eugene Baranov
|
r25751 | return self.files[rev], self.copies[rev], set() | ||
Frank Kingswood
|
r7823 | |||
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]]) | ||