##// END OF EJS Templates
convert: improve cycles detection message
Patrick Mezard -
r6131:fddeeb00 default
parent child Browse files
Show More
@@ -1,344 +1,348
1 1 # convcmd - convert extension commands definition
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from common import NoRepo, SKIPREV, converter_source, converter_sink, mapfile
9 9 from cvs import convert_cvs
10 10 from darcs import darcs_source
11 11 from git import convert_git
12 12 from hg import mercurial_source, mercurial_sink
13 13 from subversion import debugsvnlog, svn_source, svn_sink
14 14 from gnuarch import gnuarch_source
15 15 import filemap
16 16
17 17 import os, shutil
18 18 from mercurial import hg, util
19 19 from mercurial.i18n import _
20 20
21 orig_encoding = 'ascii'
22
23 def recode(s):
24 if isinstance(s, unicode):
25 return s.encode(orig_encoding, 'replace')
26 else:
27 return s.decode('utf-8').encode(orig_encoding, 'replace')
28
21 29 source_converters = [
22 30 ('cvs', convert_cvs),
23 31 ('git', convert_git),
24 32 ('svn', svn_source),
25 33 ('hg', mercurial_source),
26 34 ('darcs', darcs_source),
27 35 ('gnuarch', gnuarch_source),
28 36 ]
29 37
30 38 sink_converters = [
31 39 ('hg', mercurial_sink),
32 40 ('svn', svn_sink),
33 41 ]
34 42
35 43 def convertsource(ui, path, type, rev):
36 44 exceptions = []
37 45 for name, source in source_converters:
38 46 try:
39 47 if not type or name == type:
40 48 return source(ui, path, rev)
41 49 except NoRepo, inst:
42 50 exceptions.append(inst)
43 51 if not ui.quiet:
44 52 for inst in exceptions:
45 53 ui.write(_("%s\n") % inst)
46 54 raise util.Abort('%s: unknown repository type' % path)
47 55
48 56 def convertsink(ui, path, type):
49 57 for name, sink in sink_converters:
50 58 try:
51 59 if not type or name == type:
52 60 return sink(ui, path)
53 61 except NoRepo, inst:
54 62 ui.note(_("convert: %s\n") % inst)
55 63 raise util.Abort('%s: unknown repository type' % path)
56 64
57 65 class converter(object):
58 66 def __init__(self, ui, source, dest, revmapfile, opts):
59 67
60 68 self.source = source
61 69 self.dest = dest
62 70 self.ui = ui
63 71 self.opts = opts
64 72 self.commitcache = {}
65 73 self.authors = {}
66 74 self.authorfile = None
67 75
68 76 self.map = mapfile(ui, revmapfile)
69 77
70 78 # Read first the dst author map if any
71 79 authorfile = self.dest.authorfile()
72 80 if authorfile and os.path.exists(authorfile):
73 81 self.readauthormap(authorfile)
74 82 # Extend/Override with new author map if necessary
75 83 if opts.get('authors'):
76 84 self.readauthormap(opts.get('authors'))
77 85 self.authorfile = self.dest.authorfile()
78 86
79 87 self.splicemap = mapfile(ui, ui.config('convert', 'splicemap'))
80 88
81 89 def walktree(self, heads):
82 90 '''Return a mapping that identifies the uncommitted parents of every
83 91 uncommitted changeset.'''
84 92 visit = heads
85 93 known = {}
86 94 parents = {}
87 95 while visit:
88 96 n = visit.pop(0)
89 97 if n in known or n in self.map: continue
90 98 known[n] = 1
91 99 commit = self.cachecommit(n)
92 100 parents[n] = []
93 101 for p in commit.parents:
94 102 parents[n].append(p)
95 103 visit.append(p)
96 104
97 105 return parents
98 106
99 107 def toposort(self, parents):
100 108 '''Return an ordering such that every uncommitted changeset is
101 109 preceeded by all its uncommitted ancestors.'''
102 110 visit = parents.keys()
103 111 seen = {}
104 112 children = {}
105 113 actives = []
106 114
107 115 while visit:
108 116 n = visit.pop(0)
109 117 if n in seen: continue
110 118 seen[n] = 1
111 119 # Ensure that nodes without parents are present in the 'children'
112 120 # mapping.
113 121 children.setdefault(n, [])
114 122 hasparent = False
115 123 for p in parents[n]:
116 124 if not p in self.map:
117 125 visit.append(p)
118 126 hasparent = True
119 127 children.setdefault(p, []).append(n)
120 128 if not hasparent:
121 129 actives.append(n)
122 130
123 131 del seen
124 132 del visit
125 133
126 134 if self.opts.get('datesort'):
127 135 dates = {}
128 136 def getdate(n):
129 137 if n not in dates:
130 138 dates[n] = util.parsedate(self.commitcache[n].date)
131 139 return dates[n]
132 140
133 141 def picknext(nodes):
134 142 return min([(getdate(n), n) for n in nodes])[1]
135 143 else:
136 144 prev = [None]
137 145 def picknext(nodes):
138 146 # Return the first eligible child of the previously converted
139 147 # revision, or any of them.
140 148 next = nodes[0]
141 149 for n in nodes:
142 150 if prev[0] in parents[n]:
143 151 next = n
144 152 break
145 153 prev[0] = next
146 154 return next
147 155
148 156 s = []
149 157 pendings = {}
150 158 while actives:
151 159 n = picknext(actives)
152 160 actives.remove(n)
153 161 s.append(n)
154 162
155 163 # Update dependents list
156 164 for c in children.get(n, []):
157 165 if c not in pendings:
158 166 pendings[c] = [p for p in parents[c] if p not in self.map]
159 pendings[c].remove(n)
167 try:
168 pendings[c].remove(n)
169 except ValueError:
170 raise util.Abort(_('cycle detected between %s and %s')
171 % (recode(c), recode(n)))
160 172 if not pendings[c]:
161 173 # Parents are converted, node is eligible
162 174 actives.insert(0, c)
163 175 pendings[c] = None
164 176
165 177 if len(s) != len(parents):
166 178 raise util.Abort(_("not all revisions were sorted"))
167 179
168 180 return s
169 181
170 182 def writeauthormap(self):
171 183 authorfile = self.authorfile
172 184 if authorfile:
173 185 self.ui.status('Writing author map file %s\n' % authorfile)
174 186 ofile = open(authorfile, 'w+')
175 187 for author in self.authors:
176 188 ofile.write("%s=%s\n" % (author, self.authors[author]))
177 189 ofile.close()
178 190
179 191 def readauthormap(self, authorfile):
180 192 afile = open(authorfile, 'r')
181 193 for line in afile:
182 194 try:
183 195 srcauthor = line.split('=')[0].strip()
184 196 dstauthor = line.split('=')[1].strip()
185 197 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
186 198 self.ui.status(
187 199 'Overriding mapping for author %s, was %s, will be %s\n'
188 200 % (srcauthor, self.authors[srcauthor], dstauthor))
189 201 else:
190 202 self.ui.debug('Mapping author %s to %s\n'
191 203 % (srcauthor, dstauthor))
192 204 self.authors[srcauthor] = dstauthor
193 205 except IndexError:
194 206 self.ui.warn(
195 207 'Ignoring bad line in author file map %s: %s\n'
196 208 % (authorfile, line))
197 209 afile.close()
198 210
199 211 def cachecommit(self, rev):
200 212 commit = self.source.getcommit(rev)
201 213 commit.author = self.authors.get(commit.author, commit.author)
202 214 self.commitcache[rev] = commit
203 215 return commit
204 216
205 217 def copy(self, rev):
206 218 commit = self.commitcache[rev]
207 219 do_copies = hasattr(self.dest, 'copyfile')
208 220 filenames = []
209 221
210 222 changes = self.source.getchanges(rev)
211 223 if isinstance(changes, basestring):
212 224 if changes == SKIPREV:
213 225 dest = SKIPREV
214 226 else:
215 227 dest = self.map[changes]
216 228 self.map[rev] = dest
217 229 return
218 230 files, copies = changes
219 231 pbranches = []
220 232 if commit.parents:
221 233 for prev in commit.parents:
222 234 if prev not in self.commitcache:
223 235 self.cachecommit(prev)
224 236 pbranches.append((self.map[prev],
225 237 self.commitcache[prev].branch))
226 238 self.dest.setbranch(commit.branch, pbranches)
227 239 for f, v in files:
228 240 filenames.append(f)
229 241 try:
230 242 data = self.source.getfile(f, v)
231 243 except IOError, inst:
232 244 self.dest.delfile(f)
233 245 else:
234 246 e = self.source.getmode(f, v)
235 247 self.dest.putfile(f, e, data)
236 248 if do_copies:
237 249 if f in copies:
238 250 copyf = copies[f]
239 251 # Merely marks that a copy happened.
240 252 self.dest.copyfile(copyf, f)
241 253
242 254 try:
243 255 parents = [self.splicemap[rev]]
244 256 self.ui.debug('spliced in %s as parents of %s\n' %
245 257 (parents, rev))
246 258 except KeyError:
247 259 parents = [b[0] for b in pbranches]
248 260 newnode = self.dest.putcommit(filenames, parents, commit)
249 261 self.source.converted(rev, newnode)
250 262 self.map[rev] = newnode
251 263
252 264 def convert(self):
253 265
254 def recode(s):
255 if isinstance(s, unicode):
256 return s.encode(orig_encoding, 'replace')
257 else:
258 return s.decode('utf-8').encode(orig_encoding, 'replace')
259
260 266 try:
261 267 self.source.before()
262 268 self.dest.before()
263 269 self.source.setrevmap(self.map)
264 270 self.ui.status("scanning source...\n")
265 271 heads = self.source.getheads()
266 272 parents = self.walktree(heads)
267 273 self.ui.status("sorting...\n")
268 274 t = self.toposort(parents)
269 275 num = len(t)
270 276 c = None
271 277
272 278 self.ui.status("converting...\n")
273 279 for c in t:
274 280 num -= 1
275 281 desc = self.commitcache[c].desc
276 282 if "\n" in desc:
277 283 desc = desc.splitlines()[0]
278 284 # convert log message to local encoding without using
279 285 # tolocal() because util._encoding conver() use it as
280 286 # 'utf-8'
281 287 self.ui.status("%d %s\n" % (num, recode(desc)))
282 288 self.ui.note(_("source: %s\n" % recode(c)))
283 289 self.copy(c)
284 290
285 291 tags = self.source.gettags()
286 292 ctags = {}
287 293 for k in tags:
288 294 v = tags[k]
289 295 if self.map.get(v, SKIPREV) != SKIPREV:
290 296 ctags[k] = self.map[v]
291 297
292 298 if c and ctags:
293 299 nrev = self.dest.puttags(ctags)
294 300 # write another hash correspondence to override the previous
295 301 # one so we don't end up with extra tag heads
296 302 if nrev:
297 303 self.map[c] = nrev
298 304
299 305 self.writeauthormap()
300 306 finally:
301 307 self.cleanup()
302 308
303 309 def cleanup(self):
304 310 try:
305 311 self.dest.after()
306 312 finally:
307 313 self.source.after()
308 314 self.map.close()
309 315
310 orig_encoding = 'ascii'
311
312 316 def convert(ui, src, dest=None, revmapfile=None, **opts):
313 317 global orig_encoding
314 318 orig_encoding = util._encoding
315 319 util._encoding = 'UTF-8'
316 320
317 321 if not dest:
318 322 dest = hg.defaultdest(src) + "-hg"
319 323 ui.status("assuming destination %s\n" % dest)
320 324
321 325 destc = convertsink(ui, dest, opts.get('dest_type'))
322 326
323 327 try:
324 328 srcc = convertsource(ui, src, opts.get('source_type'),
325 329 opts.get('rev'))
326 330 except Exception:
327 331 for path in destc.created:
328 332 shutil.rmtree(path, True)
329 333 raise
330 334
331 335 fmap = opts.get('filemap')
332 336 if fmap:
333 337 srcc = filemap.filemap_source(ui, srcc, fmap)
334 338 destc.setfilemapmode(True)
335 339
336 340 if not revmapfile:
337 341 try:
338 342 revmapfile = destc.revmapfile()
339 343 except:
340 344 revmapfile = os.path.join(destc, "map")
341 345
342 346 c = converter(ui, srcc, destc, revmapfile, opts)
343 347 c.convert()
344 348
General Comments 0
You need to be logged in to leave comments. Login now