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