##// END OF EJS Templates
convert: update p4 for Python 3...
Nate Skulic -
r47899:3af29373 stable
parent child Browse files
Show More
@@ -1,405 +1,403 b''
1 # Perforce source for convert extension.
1 # Perforce source for convert extension.
2 #
2 #
3 # Copyright 2009, Frank Kingswood <frank@kingswood-consulting.co.uk>
3 # Copyright 2009, Frank Kingswood <frank@kingswood-consulting.co.uk>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import marshal
9 import marshal
10 import re
10 import re
11
11
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from mercurial import (
13 from mercurial import (
14 error,
14 error,
15 util,
15 util,
16 )
16 )
17 from mercurial.utils import (
17 from mercurial.utils import (
18 dateutil,
18 dateutil,
19 procutil,
19 procutil,
20 stringutil,
20 stringutil,
21 )
21 )
22
22
23 from . import common
23 from . import common
24
24
25
25
26 def loaditer(f):
26 def loaditer(f):
27 """Yield the dictionary objects generated by p4"""
27 """Yield the dictionary objects generated by p4"""
28 try:
28 try:
29 while True:
29 while True:
30 d = marshal.load(f)
30 d = marshal.load(f)
31 if not d:
31 if not d:
32 break
32 break
33 yield d
33 yield d
34 except EOFError:
34 except EOFError:
35 pass
35 pass
36
36
37
37
38 def decodefilename(filename):
38 def decodefilename(filename):
39 """Perforce escapes special characters @, #, *, or %
39 """Perforce escapes special characters @, #, *, or %
40 with %40, %23, %2A, or %25 respectively
40 with %40, %23, %2A, or %25 respectively
41
41
42 >>> decodefilename(b'portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
42 >>> decodefilename(b'portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
43 'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid'
43 'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid'
44 >>> decodefilename(b'//Depot/Directory/%2525/%2523/%23%40.%2A')
44 >>> decodefilename(b'//Depot/Directory/%2525/%2523/%23%40.%2A')
45 '//Depot/Directory/%25/%23/#@.*'
45 '//Depot/Directory/%25/%23/#@.*'
46 """
46 """
47 replacements = [
47 replacements = [
48 (b'%2A', b'*'),
48 (b'%2A', b'*'),
49 (b'%23', b'#'),
49 (b'%23', b'#'),
50 (b'%40', b'@'),
50 (b'%40', b'@'),
51 (b'%25', b'%'),
51 (b'%25', b'%'),
52 ]
52 ]
53 for k, v in replacements:
53 for k, v in replacements:
54 filename = filename.replace(k, v)
54 filename = filename.replace(k, v)
55 return filename
55 return filename
56
56
57
57
58 class p4_source(common.converter_source):
58 class p4_source(common.converter_source):
59 def __init__(self, ui, repotype, path, revs=None):
59 def __init__(self, ui, repotype, path, revs=None):
60 # avoid import cycle
60 # avoid import cycle
61 from . import convcmd
61 from . import convcmd
62
62
63 super(p4_source, self).__init__(ui, repotype, path, revs=revs)
63 super(p4_source, self).__init__(ui, repotype, path, revs=revs)
64
64
65 if b"/" in path and not path.startswith(b'//'):
65 if b"/" in path and not path.startswith(b'//'):
66 raise common.NoRepo(
66 raise common.NoRepo(
67 _(b'%s does not look like a P4 repository') % path
67 _(b'%s does not look like a P4 repository') % path
68 )
68 )
69
69
70 common.checktool(b'p4', abort=False)
70 common.checktool(b'p4', abort=False)
71
71
72 self.revmap = {}
72 self.revmap = {}
73 self.encoding = self.ui.config(
73 self.encoding = self.ui.config(
74 b'convert', b'p4.encoding', convcmd.orig_encoding
74 b'convert', b'p4.encoding', convcmd.orig_encoding
75 )
75 )
76 self.re_type = re.compile(
76 self.re_type = re.compile(
77 br"([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
77 br"([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
78 br"(\+\w+)?$"
78 br"(\+\w+)?$"
79 )
79 )
80 self.re_keywords = re.compile(
80 self.re_keywords = re.compile(
81 br"\$(Id|Header|Date|DateTime|Change|File|Revision|Author)"
81 br"\$(Id|Header|Date|DateTime|Change|File|Revision|Author)"
82 br":[^$\n]*\$"
82 br":[^$\n]*\$"
83 )
83 )
84 self.re_keywords_old = re.compile(br"\$(Id|Header):[^$\n]*\$")
84 self.re_keywords_old = re.compile(br"\$(Id|Header):[^$\n]*\$")
85
85
86 if revs and len(revs) > 1:
86 if revs and len(revs) > 1:
87 raise error.Abort(
87 raise error.Abort(
88 _(
88 _(
89 b"p4 source does not support specifying "
89 b"p4 source does not support specifying "
90 b"multiple revisions"
90 b"multiple revisions"
91 )
91 )
92 )
92 )
93
93
94 def setrevmap(self, revmap):
94 def setrevmap(self, revmap):
95 """Sets the parsed revmap dictionary.
95 """Sets the parsed revmap dictionary.
96
96
97 Revmap stores mappings from a source revision to a target revision.
97 Revmap stores mappings from a source revision to a target revision.
98 It is set in convertcmd.convert and provided by the user as a file
98 It is set in convertcmd.convert and provided by the user as a file
99 on the commandline.
99 on the commandline.
100
100
101 Revisions in the map are considered beeing present in the
101 Revisions in the map are considered beeing present in the
102 repository and ignored during _parse(). This allows for incremental
102 repository and ignored during _parse(). This allows for incremental
103 imports if a revmap is provided.
103 imports if a revmap is provided.
104 """
104 """
105 self.revmap = revmap
105 self.revmap = revmap
106
106
107 def _parse_view(self, path):
107 def _parse_view(self, path):
108 """Read changes affecting the path"""
108 """Read changes affecting the path"""
109 cmd = b'p4 -G changes -s submitted %s' % procutil.shellquote(path)
109 cmd = b'p4 -G changes -s submitted %s' % procutil.shellquote(path)
110 stdout = procutil.popen(cmd, mode=b'rb')
110 stdout = procutil.popen(cmd, mode=b'rb')
111 p4changes = {}
111 p4changes = {}
112 for d in loaditer(stdout):
112 for d in loaditer(stdout):
113 c = d.get(b"change", None)
113 c = d.get(b"change", None)
114 if c:
114 if c:
115 p4changes[c] = True
115 p4changes[c] = True
116 return p4changes
116 return p4changes
117
117
118 def _parse(self, ui, path):
118 def _parse(self, ui, path):
119 """Prepare list of P4 filenames and revisions to import"""
119 """Prepare list of P4 filenames and revisions to import"""
120 p4changes = {}
120 p4changes = {}
121 changeset = {}
121 changeset = {}
122 files_map = {}
122 files_map = {}
123 copies_map = {}
123 copies_map = {}
124 localname = {}
124 localname = {}
125 depotname = {}
125 depotname = {}
126 heads = []
126 heads = []
127
127
128 ui.status(_(b'reading p4 views\n'))
128 ui.status(_(b'reading p4 views\n'))
129
129
130 # read client spec or view
130 # read client spec or view
131 if b"/" in path:
131 if b"/" in path:
132 p4changes.update(self._parse_view(path))
132 p4changes.update(self._parse_view(path))
133 if path.startswith(b"//") and path.endswith(b"/..."):
133 if path.startswith(b"//") and path.endswith(b"/..."):
134 views = {path[:-3]: b""}
134 views = {path[:-3]: b""}
135 else:
135 else:
136 views = {b"//": b""}
136 views = {b"//": b""}
137 else:
137 else:
138 cmd = b'p4 -G client -o %s' % procutil.shellquote(path)
138 cmd = b'p4 -G client -o %s' % procutil.shellquote(path)
139 clientspec = marshal.load(procutil.popen(cmd, mode=b'rb'))
139 clientspec = marshal.load(procutil.popen(cmd, mode=b'rb'))
140
140
141 views = {}
141 views = {}
142 for client in clientspec:
142 for client in clientspec:
143 if client.startswith(b"View"):
143 if client.startswith(b"View"):
144 sview, cview = clientspec[client].split()
144 sview, cview = clientspec[client].split()
145 p4changes.update(self._parse_view(sview))
145 p4changes.update(self._parse_view(sview))
146 if sview.endswith(b"...") and cview.endswith(b"..."):
146 if sview.endswith(b"...") and cview.endswith(b"..."):
147 sview = sview[:-3]
147 sview = sview[:-3]
148 cview = cview[:-3]
148 cview = cview[:-3]
149 cview = cview[2:]
149 cview = cview[2:]
150 cview = cview[cview.find(b"/") + 1 :]
150 cview = cview[cview.find(b"/") + 1 :]
151 views[sview] = cview
151 views[sview] = cview
152
152
153 # list of changes that affect our source files
153 # list of changes that affect our source files
154 p4changes = p4changes.keys()
154 p4changes = sorted(p4changes.keys(), key=int)
155 p4changes.sort(key=int)
156
155
157 # list with depot pathnames, longest first
156 # list with depot pathnames, longest first
158 vieworder = views.keys()
157 vieworder = sorted(views.keys(), key=len, reverse=True)
159 vieworder.sort(key=len, reverse=True)
160
158
161 # handle revision limiting
159 # handle revision limiting
162 startrev = self.ui.config(b'convert', b'p4.startrev')
160 startrev = self.ui.config(b'convert', b'p4.startrev')
163
161
164 # now read the full changelists to get the list of file revisions
162 # now read the full changelists to get the list of file revisions
165 ui.status(_(b'collecting p4 changelists\n'))
163 ui.status(_(b'collecting p4 changelists\n'))
166 lastid = None
164 lastid = None
167 for change in p4changes:
165 for change in p4changes:
168 if startrev and int(change) < int(startrev):
166 if startrev and int(change) < int(startrev):
169 continue
167 continue
170 if self.revs and int(change) > int(self.revs[0]):
168 if self.revs and int(change) > int(self.revs[0]):
171 continue
169 continue
172 if change in self.revmap:
170 if change in self.revmap:
173 # Ignore already present revisions, but set the parent pointer.
171 # Ignore already present revisions, but set the parent pointer.
174 lastid = change
172 lastid = change
175 continue
173 continue
176
174
177 if lastid:
175 if lastid:
178 parents = [lastid]
176 parents = [lastid]
179 else:
177 else:
180 parents = []
178 parents = []
181
179
182 d = self._fetch_revision(change)
180 d = self._fetch_revision(change)
183 c = self._construct_commit(d, parents)
181 c = self._construct_commit(d, parents)
184
182
185 descarr = c.desc.splitlines(True)
183 descarr = c.desc.splitlines(True)
186 if len(descarr) > 0:
184 if len(descarr) > 0:
187 shortdesc = descarr[0].rstrip(b'\r\n')
185 shortdesc = descarr[0].rstrip(b'\r\n')
188 else:
186 else:
189 shortdesc = b'**empty changelist description**'
187 shortdesc = b'**empty changelist description**'
190
188
191 t = b'%s %s' % (c.rev, repr(shortdesc)[1:-1])
189 t = b'%s %s' % (c.rev, shortdesc)
192 ui.status(stringutil.ellipsis(t, 80) + b'\n')
190 ui.status(stringutil.ellipsis(t, 80) + b'\n')
193
191
194 files = []
192 files = []
195 copies = {}
193 copies = {}
196 copiedfiles = []
194 copiedfiles = []
197 i = 0
195 i = 0
198 while (b"depotFile%d" % i) in d and (b"rev%d" % i) in d:
196 while (b"depotFile%d" % i) in d and (b"rev%d" % i) in d:
199 oldname = d[b"depotFile%d" % i]
197 oldname = d[b"depotFile%d" % i]
200 filename = None
198 filename = None
201 for v in vieworder:
199 for v in vieworder:
202 if oldname.lower().startswith(v.lower()):
200 if oldname.lower().startswith(v.lower()):
203 filename = decodefilename(views[v] + oldname[len(v) :])
201 filename = decodefilename(views[v] + oldname[len(v) :])
204 break
202 break
205 if filename:
203 if filename:
206 files.append((filename, d[b"rev%d" % i]))
204 files.append((filename, d[b"rev%d" % i]))
207 depotname[filename] = oldname
205 depotname[filename] = oldname
208 if d.get(b"action%d" % i) == b"move/add":
206 if d.get(b"action%d" % i) == b"move/add":
209 copiedfiles.append(filename)
207 copiedfiles.append(filename)
210 localname[oldname] = filename
208 localname[oldname] = filename
211 i += 1
209 i += 1
212
210
213 # Collect information about copied files
211 # Collect information about copied files
214 for filename in copiedfiles:
212 for filename in copiedfiles:
215 oldname = depotname[filename]
213 oldname = depotname[filename]
216
214
217 flcmd = b'p4 -G filelog %s' % procutil.shellquote(oldname)
215 flcmd = b'p4 -G filelog %s' % procutil.shellquote(oldname)
218 flstdout = procutil.popen(flcmd, mode=b'rb')
216 flstdout = procutil.popen(flcmd, mode=b'rb')
219
217
220 copiedfilename = None
218 copiedfilename = None
221 for d in loaditer(flstdout):
219 for d in loaditer(flstdout):
222 copiedoldname = None
220 copiedoldname = None
223
221
224 i = 0
222 i = 0
225 while (b"change%d" % i) in d:
223 while (b"change%d" % i) in d:
226 if (
224 if (
227 d[b"change%d" % i] == change
225 d[b"change%d" % i] == change
228 and d[b"action%d" % i] == b"move/add"
226 and d[b"action%d" % i] == b"move/add"
229 ):
227 ):
230 j = 0
228 j = 0
231 while (b"file%d,%d" % (i, j)) in d:
229 while (b"file%d,%d" % (i, j)) in d:
232 if d[b"how%d,%d" % (i, j)] == b"moved from":
230 if d[b"how%d,%d" % (i, j)] == b"moved from":
233 copiedoldname = d[b"file%d,%d" % (i, j)]
231 copiedoldname = d[b"file%d,%d" % (i, j)]
234 break
232 break
235 j += 1
233 j += 1
236 i += 1
234 i += 1
237
235
238 if copiedoldname and copiedoldname in localname:
236 if copiedoldname and copiedoldname in localname:
239 copiedfilename = localname[copiedoldname]
237 copiedfilename = localname[copiedoldname]
240 break
238 break
241
239
242 if copiedfilename:
240 if copiedfilename:
243 copies[filename] = copiedfilename
241 copies[filename] = copiedfilename
244 else:
242 else:
245 ui.warn(
243 ui.warn(
246 _(b"cannot find source for copied file: %s@%s\n")
244 _(b"cannot find source for copied file: %s@%s\n")
247 % (filename, change)
245 % (filename, change)
248 )
246 )
249
247
250 changeset[change] = c
248 changeset[change] = c
251 files_map[change] = files
249 files_map[change] = files
252 copies_map[change] = copies
250 copies_map[change] = copies
253 lastid = change
251 lastid = change
254
252
255 if lastid and len(changeset) > 0:
253 if lastid and len(changeset) > 0:
256 heads = [lastid]
254 heads = [lastid]
257
255
258 return {
256 return {
259 b'changeset': changeset,
257 b'changeset': changeset,
260 b'files': files_map,
258 b'files': files_map,
261 b'copies': copies_map,
259 b'copies': copies_map,
262 b'heads': heads,
260 b'heads': heads,
263 b'depotname': depotname,
261 b'depotname': depotname,
264 }
262 }
265
263
266 @util.propertycache
264 @util.propertycache
267 def _parse_once(self):
265 def _parse_once(self):
268 return self._parse(self.ui, self.path)
266 return self._parse(self.ui, self.path)
269
267
270 @util.propertycache
268 @util.propertycache
271 def copies(self):
269 def copies(self):
272 return self._parse_once[b'copies']
270 return self._parse_once[b'copies']
273
271
274 @util.propertycache
272 @util.propertycache
275 def files(self):
273 def files(self):
276 return self._parse_once[b'files']
274 return self._parse_once[b'files']
277
275
278 @util.propertycache
276 @util.propertycache
279 def changeset(self):
277 def changeset(self):
280 return self._parse_once[b'changeset']
278 return self._parse_once[b'changeset']
281
279
282 @util.propertycache
280 @util.propertycache
283 def heads(self):
281 def heads(self):
284 return self._parse_once[b'heads']
282 return self._parse_once[b'heads']
285
283
286 @util.propertycache
284 @util.propertycache
287 def depotname(self):
285 def depotname(self):
288 return self._parse_once[b'depotname']
286 return self._parse_once[b'depotname']
289
287
290 def getheads(self):
288 def getheads(self):
291 return self.heads
289 return self.heads
292
290
293 def getfile(self, name, rev):
291 def getfile(self, name, rev):
294 cmd = b'p4 -G print %s' % procutil.shellquote(
292 cmd = b'p4 -G print %s' % procutil.shellquote(
295 b"%s#%s" % (self.depotname[name], rev)
293 b"%s#%s" % (self.depotname[name], rev)
296 )
294 )
297
295
298 lasterror = None
296 lasterror = None
299 while True:
297 while True:
300 stdout = procutil.popen(cmd, mode=b'rb')
298 stdout = procutil.popen(cmd, mode=b'rb')
301
299
302 mode = None
300 mode = None
303 contents = []
301 contents = []
304 keywords = None
302 keywords = None
305
303
306 for d in loaditer(stdout):
304 for d in loaditer(stdout):
307 code = d[b"code"]
305 code = d[b"code"]
308 data = d.get(b"data")
306 data = d.get(b"data")
309
307
310 if code == b"error":
308 if code == b"error":
311 # if this is the first time error happened
309 # if this is the first time error happened
312 # re-attempt getting the file
310 # re-attempt getting the file
313 if not lasterror:
311 if not lasterror:
314 lasterror = IOError(d[b"generic"], data)
312 lasterror = IOError(d[b"generic"], data)
315 # this will exit inner-most for-loop
313 # this will exit inner-most for-loop
316 break
314 break
317 else:
315 else:
318 raise lasterror
316 raise lasterror
319
317
320 elif code == b"stat":
318 elif code == b"stat":
321 action = d.get(b"action")
319 action = d.get(b"action")
322 if action in [b"purge", b"delete", b"move/delete"]:
320 if action in [b"purge", b"delete", b"move/delete"]:
323 return None, None
321 return None, None
324 p4type = self.re_type.match(d[b"type"])
322 p4type = self.re_type.match(d[b"type"])
325 if p4type:
323 if p4type:
326 mode = b""
324 mode = b""
327 flags = (p4type.group(1) or b"") + (
325 flags = (p4type.group(1) or b"") + (
328 p4type.group(3) or b""
326 p4type.group(3) or b""
329 )
327 )
330 if b"x" in flags:
328 if b"x" in flags:
331 mode = b"x"
329 mode = b"x"
332 if p4type.group(2) == b"symlink":
330 if p4type.group(2) == b"symlink":
333 mode = b"l"
331 mode = b"l"
334 if b"ko" in flags:
332 if b"ko" in flags:
335 keywords = self.re_keywords_old
333 keywords = self.re_keywords_old
336 elif b"k" in flags:
334 elif b"k" in flags:
337 keywords = self.re_keywords
335 keywords = self.re_keywords
338
336
339 elif code == b"text" or code == b"binary":
337 elif code == b"text" or code == b"binary":
340 contents.append(data)
338 contents.append(data)
341
339
342 lasterror = None
340 lasterror = None
343
341
344 if not lasterror:
342 if not lasterror:
345 break
343 break
346
344
347 if mode is None:
345 if mode is None:
348 return None, None
346 return None, None
349
347
350 contents = b''.join(contents)
348 contents = b''.join(contents)
351
349
352 if keywords:
350 if keywords:
353 contents = keywords.sub(b"$\\1$", contents)
351 contents = keywords.sub(b"$\\1$", contents)
354 if mode == b"l" and contents.endswith(b"\n"):
352 if mode == b"l" and contents.endswith(b"\n"):
355 contents = contents[:-1]
353 contents = contents[:-1]
356
354
357 return contents, mode
355 return contents, mode
358
356
359 def getchanges(self, rev, full):
357 def getchanges(self, rev, full):
360 if full:
358 if full:
361 raise error.Abort(_(b"convert from p4 does not support --full"))
359 raise error.Abort(_(b"convert from p4 does not support --full"))
362 return self.files[rev], self.copies[rev], set()
360 return self.files[rev], self.copies[rev], set()
363
361
364 def _construct_commit(self, obj, parents=None):
362 def _construct_commit(self, obj, parents=None):
365 """
363 """
366 Constructs a common.commit object from an unmarshalled
364 Constructs a common.commit object from an unmarshalled
367 `p4 describe` output
365 `p4 describe` output
368 """
366 """
369 desc = self.recode(obj.get(b"desc", b""))
367 desc = self.recode(obj.get(b"desc", b""))
370 date = (int(obj[b"time"]), 0) # timezone not set
368 date = (int(obj[b"time"]), 0) # timezone not set
371 if parents is None:
369 if parents is None:
372 parents = []
370 parents = []
373
371
374 return common.commit(
372 return common.commit(
375 author=self.recode(obj[b"user"]),
373 author=self.recode(obj[b"user"]),
376 date=dateutil.datestr(date, b'%Y-%m-%d %H:%M:%S %1%2'),
374 date=dateutil.datestr(date, b'%Y-%m-%d %H:%M:%S %1%2'),
377 parents=parents,
375 parents=parents,
378 desc=desc,
376 desc=desc,
379 branch=None,
377 branch=None,
380 rev=obj[b'change'],
378 rev=obj[b'change'],
381 extra={b"p4": obj[b'change'], b"convert_revision": obj[b'change']},
379 extra={b"p4": obj[b'change'], b"convert_revision": obj[b'change']},
382 )
380 )
383
381
384 def _fetch_revision(self, rev):
382 def _fetch_revision(self, rev):
385 """Return an output of `p4 describe` including author, commit date as
383 """Return an output of `p4 describe` including author, commit date as
386 a dictionary."""
384 a dictionary."""
387 cmd = b"p4 -G describe -s %s" % rev
385 cmd = b"p4 -G describe -s %s" % rev
388 stdout = procutil.popen(cmd, mode=b'rb')
386 stdout = procutil.popen(cmd, mode=b'rb')
389 return marshal.load(stdout)
387 return marshal.load(stdout)
390
388
391 def getcommit(self, rev):
389 def getcommit(self, rev):
392 if rev in self.changeset:
390 if rev in self.changeset:
393 return self.changeset[rev]
391 return self.changeset[rev]
394 elif rev in self.revmap:
392 elif rev in self.revmap:
395 d = self._fetch_revision(rev)
393 d = self._fetch_revision(rev)
396 return self._construct_commit(d, parents=None)
394 return self._construct_commit(d, parents=None)
397 raise error.Abort(
395 raise error.Abort(
398 _(b"cannot find %s in the revmap or parsed changesets") % rev
396 _(b"cannot find %s in the revmap or parsed changesets") % rev
399 )
397 )
400
398
401 def gettags(self):
399 def gettags(self):
402 return {}
400 return {}
403
401
404 def getchangedfiles(self, rev, i):
402 def getchangedfiles(self, rev, i):
405 return sorted([x[0] for x in self.files[rev]])
403 return sorted([x[0] for x in self.files[rev]])
General Comments 0
You need to be logged in to leave comments. Login now