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