comparison hgext/convert/__init__.py @ 5016:4ebc8693ce72

convert: add filename filtering and renaming support
author Bryan O'Sullivan <bos@serpentine.com>
date Thu, 26 Jul 2007 13:34:36 -0700
parents 914054ca532e
children c7623d2b2a66
comparison
equal deleted inserted replaced
5015:cb100605a516 5016:4ebc8693ce72
9 from cvs import convert_cvs 9 from cvs import convert_cvs
10 from git import convert_git 10 from git import convert_git
11 from hg import mercurial_source, mercurial_sink 11 from hg import mercurial_source, mercurial_sink
12 from subversion import convert_svn 12 from subversion import convert_svn
13 13
14 import os, shutil 14 import os, shlex, shutil
15 from mercurial import hg, ui, util, commands 15 from mercurial import hg, ui, util, commands
16 from mercurial.i18n import _
16 17
17 commands.norepo += " convert" 18 commands.norepo += " convert"
18 19
19 converters = [convert_cvs, convert_git, convert_svn, mercurial_source, 20 converters = [convert_cvs, convert_git, convert_svn, mercurial_source,
20 mercurial_sink] 21 mercurial_sink]
40 except NoRepo: 41 except NoRepo:
41 pass 42 pass
42 raise util.Abort('%s: unknown repository type' % path) 43 raise util.Abort('%s: unknown repository type' % path)
43 44
44 class convert(object): 45 class convert(object):
45 def __init__(self, ui, source, dest, revmapfile, opts): 46 def __init__(self, ui, source, dest, revmapfile, filemapper, opts):
46 47
47 self.source = source 48 self.source = source
48 self.dest = dest 49 self.dest = dest
49 self.ui = ui 50 self.ui = ui
50 self.opts = opts 51 self.opts = opts
51 self.commitcache = {} 52 self.commitcache = {}
52 self.revmapfile = revmapfile 53 self.revmapfile = revmapfile
53 self.revmapfilefd = None 54 self.revmapfilefd = None
54 self.authors = {} 55 self.authors = {}
55 self.authorfile = None 56 self.authorfile = None
57 self.mapfile = filemapper
56 58
57 self.map = {} 59 self.map = {}
58 try: 60 try:
59 origrevmapfile = open(self.revmapfile, 'r') 61 origrevmapfile = open(self.revmapfile, 'r')
60 for l in origrevmapfile: 62 for l in origrevmapfile:
189 'Ignoring bad line in author file map %s: %s\n' 191 'Ignoring bad line in author file map %s: %s\n'
190 % (authorfile, line)) 192 % (authorfile, line))
191 afile.close() 193 afile.close()
192 194
193 def copy(self, rev): 195 def copy(self, rev):
194 c = self.commitcache[rev] 196 commit = self.commitcache[rev]
195 files = self.source.getchanges(rev)
196
197 do_copies = hasattr(self.dest, 'copyfile') 197 do_copies = hasattr(self.dest, 'copyfile')
198 198 filenames = []
199 for f, v in files: 199
200 for f, v in self.source.getchanges(rev):
201 newf = self.mapfile(f)
202 if not newf:
203 continue
204 filenames.append(newf)
200 try: 205 try:
201 data = self.source.getfile(f, v) 206 data = self.source.getfile(f, v)
202 except IOError, inst: 207 except IOError, inst:
203 self.dest.delfile(f) 208 self.dest.delfile(newf)
204 else: 209 else:
205 e = self.source.getmode(f, v) 210 e = self.source.getmode(f, v)
206 self.dest.putfile(f, e, data) 211 self.dest.putfile(newf, e, data)
207 if do_copies: 212 if do_copies:
208 if f in c.copies: 213 if f in commit.copies:
209 # Merely marks that a copy happened. 214 copyf = self.mapfile(commit.copies[f])
210 self.dest.copyfile(c.copies[f], f) 215 if copyf:
211 216 # Merely marks that a copy happened.
212 217 self.dest.copyfile(copyf, newf)
213 r = [self.map[v] for v in c.parents] 218
214 f = [f for f, v in files] 219 parents = [self.map[r] for r in commit.parents]
215 newnode = self.dest.putcommit(f, r, c) 220 newnode = self.dest.putcommit(filenames, parents, commit)
216 self.mapentry(rev, newnode) 221 self.mapentry(rev, newnode)
217 222
218 def convert(self): 223 def convert(self):
219 try: 224 try:
220 self.dest.before() 225 self.dest.before()
259 264
260 def cleanup(self): 265 def cleanup(self):
261 self.dest.after() 266 self.dest.after()
262 if self.revmapfilefd: 267 if self.revmapfilefd:
263 self.revmapfilefd.close() 268 self.revmapfilefd.close()
269
270 def rpairs(name):
271 e = len(name)
272 while e != -1:
273 yield name[:e], name[e+1:]
274 e = name.rfind('/', 0, e)
275
276 class filemapper(object):
277 '''Map and filter filenames when importing.
278 A name can be mapped to itself, a new name, or None (omit from new
279 repository).'''
280
281 def __init__(self, ui, path=None):
282 self.ui = ui
283 self.include = {}
284 self.exclude = {}
285 self.rename = {}
286 if path:
287 if self.parse(path):
288 raise util.Abort(_('errors in filemap'))
289
290 def parse(self, path):
291 errs = 0
292 def check(name, mapping, listname):
293 if name in mapping:
294 self.ui.warn(_('%s:%d: %r already in %s list\n') %
295 (lex.infile, lex.lineno, name, listname))
296 return 1
297 return 0
298 lex = shlex.shlex(open(path), path, True)
299 lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?'
300 cmd = lex.get_token()
301 while cmd:
302 if cmd == 'include':
303 name = lex.get_token()
304 errs += check(name, self.exclude, 'exclude')
305 self.include[name] = name
306 elif cmd == 'exclude':
307 name = lex.get_token()
308 errs += check(name, self.include, 'include')
309 errs += check(name, self.rename, 'rename')
310 self.exclude[name] = name
311 elif cmd == 'rename':
312 src = lex.get_token()
313 dest = lex.get_token()
314 errs += check(src, self.exclude, 'exclude')
315 self.rename[src] = dest
316 elif cmd == 'source':
317 errs += self.parse(lex.get_token())
318 else:
319 self.ui.warn(_('%s:%d: unknown directive %r\n') %
320 (lex.infile, lex.lineno, cmd))
321 errs += 1
322 cmd = lex.get_token()
323 return errs
324
325 def lookup(self, name, mapping):
326 for pre, suf in rpairs(name):
327 try:
328 return mapping[pre], pre, suf
329 except KeyError, err:
330 pass
331 return '', name, ''
332
333 def __call__(self, name):
334 if self.include:
335 inc = self.lookup(name, self.include)[0]
336 else:
337 inc = name
338 if self.exclude:
339 exc = self.lookup(name, self.exclude)[0]
340 else:
341 exc = ''
342 if not inc or exc:
343 return None
344 newpre, pre, suf = self.lookup(name, self.rename)
345 if newpre:
346 if suf:
347 return newpre + '/' + suf
348 return newpre
349 return name
264 350
265 def _convert(ui, src, dest=None, revmapfile=None, **opts): 351 def _convert(ui, src, dest=None, revmapfile=None, **opts):
266 """Convert a foreign SCM repository to a Mercurial one. 352 """Convert a foreign SCM repository to a Mercurial one.
267 353
268 Accepted source formats: 354 Accepted source formats:
341 try: 427 try:
342 revmapfile = destc.revmapfile() 428 revmapfile = destc.revmapfile()
343 except: 429 except:
344 revmapfile = os.path.join(destc, "map") 430 revmapfile = os.path.join(destc, "map")
345 431
346 c = convert(ui, srcc, destc, revmapfile, opts) 432
433 c = convert(ui, srcc, destc, revmapfile, filemapper(ui, opts['filemap']),
434 opts)
347 c.convert() 435 c.convert()
348 436
349 cmdtable = { 437 cmdtable = {
350 "convert": 438 "convert":
351 (_convert, 439 (_convert,
352 [('A', 'authors', '', 'username mapping filename'), 440 [('A', 'authors', '', 'username mapping filename'),
441 ('', 'filemap', '', 'remap file names using contents of file'),
353 ('r', 'rev', '', 'import up to target revision REV'), 442 ('r', 'rev', '', 'import up to target revision REV'),
354 ('', 'datesort', None, 'try to sort changesets by date')], 443 ('', 'datesort', None, 'try to sort changesets by date')],
355 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'), 444 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
356 } 445 }