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