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