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