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