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