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