##// END OF EJS Templates
Some small cleanups for convert extension:...
Thomas Arendsen Hein -
r4532:c3a78a49 default
parent child Browse files
Show More
@@ -1,743 +1,749
1 1 # convert.py Foreign SCM converter
2 2 #
3 3 # Copyright 2005, 2006 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 import sys, os, zlib, sha, time, re, locale, socket
9 from mercurial import hg, ui, util, commands, repo
9 from mercurial import hg, ui, util, commands
10 10
11 11 commands.norepo += " convert"
12 12
13 13 class NoRepo(Exception): pass
14 14
15 15 class commit(object):
16 16 def __init__(self, **parts):
17 17 for x in "author date desc parents".split():
18 18 if not x in parts:
19 raise util.Abort("commit missing field %s\n" % x)
19 raise util.Abort("commit missing field %s" % x)
20 20 self.__dict__.update(parts)
21 21
22 22 def recode(s):
23 23 try:
24 24 return s.decode("utf-8").encode("utf-8")
25 25 except:
26 26 try:
27 27 return s.decode("latin-1").encode("utf-8")
28 28 except:
29 29 return s.decode("utf-8", "replace").encode("utf-8")
30 30
31 31 class converter_source(object):
32 32 """Conversion source interface"""
33 33
34 34 def __init__(self, ui, path):
35 35 """Initialize conversion source (or raise NoRepo("message")
36 36 exception if path is not a valid repository)"""
37 37 raise NotImplementedError()
38 38
39 39 def getheads(self):
40 40 """Return a list of this repository's heads"""
41 41 raise NotImplementedError()
42 42
43 43 def getfile(self, name, rev):
44 44 """Return file contents as a string"""
45 45 raise NotImplementedError()
46 46
47 47 def getmode(self, name, rev):
48 48 """Return file mode, eg. '', 'x', or 'l'"""
49 49 raise NotImplementedError()
50 50
51 51 def getchanges(self, version):
52 52 """Return sorted list of (filename, id) tuples for all files changed in rev.
53 53
54 54 id just tells us which revision to return in getfile(), e.g. in
55 55 git it's an object hash."""
56 56 raise NotImplementedError()
57 57
58 58 def getcommit(self, version):
59 59 """Return the commit object for version"""
60 60 raise NotImplementedError()
61 61
62 62 def gettags(self):
63 63 """Return the tags as a dictionary of name: revision"""
64 64 raise NotImplementedError()
65 65
66 66 class converter_sink(object):
67 67 """Conversion sink (target) interface"""
68 68
69 69 def __init__(self, ui, path):
70 70 """Initialize conversion sink (or raise NoRepo("message")
71 71 exception if path is not a valid repository)"""
72 72 raise NotImplementedError()
73 73
74 74 def getheads(self):
75 75 """Return a list of this repository's heads"""
76 76 raise NotImplementedError()
77 77
78 78 def mapfile(self):
79 79 """Path to a file that will contain lines
80 80 source_rev_id sink_rev_id
81 81 mapping equivalent revision identifiers for each system."""
82 82 raise NotImplementedError()
83 83
84 84 def putfile(self, f, e, data):
85 85 """Put file for next putcommit().
86 86 f: path to file
87 87 e: '', 'x', or 'l' (regular file, executable, or symlink)
88 88 data: file contents"""
89 89 raise NotImplementedError()
90 90
91 91 def delfile(self, f):
92 92 """Delete file for next putcommit().
93 93 f: path to file"""
94 94 raise NotImplementedError()
95 95
96 96 def putcommit(self, files, parents, commit):
97 97 """Create a revision with all changed files listed in 'files'
98 98 and having listed parents. 'commit' is a commit object containing
99 99 at a minimum the author, date, and message for this changeset.
100 100 Called after putfile() and delfile() calls. Note that the sink
101 101 repository is not told to update itself to a particular revision
102 102 (or even what that revision would be) before it receives the
103 103 file data."""
104 104 raise NotImplementedError()
105 105
106 106 def puttags(self, tags):
107 107 """Put tags into sink.
108 108 tags: {tagname: sink_rev_id, ...}"""
109 109 raise NotImplementedError()
110 110
111 111
112 112 # CVS conversion code inspired by hg-cvs-import and git-cvsimport
113 113 class convert_cvs(converter_source):
114 114 def __init__(self, ui, path):
115 115 self.path = path
116 116 self.ui = ui
117 117 cvs = os.path.join(path, "CVS")
118 118 if not os.path.exists(cvs):
119 119 raise NoRepo("couldn't open CVS repo %s" % path)
120 120
121 121 self.changeset = {}
122 122 self.files = {}
123 123 self.tags = {}
124 124 self.lastbranch = {}
125 125 self.parent = {}
126 126 self.socket = None
127 127 self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
128 128 self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
129 129 self.encoding = locale.getpreferredencoding()
130 130 self._parse()
131 131 self._connect()
132 132
133 133 def _parse(self):
134 134 if self.changeset:
135 135 return
136 136
137 137 d = os.getcwd()
138 138 try:
139 139 os.chdir(self.path)
140 140 id = None
141 141 state = 0
142 142 for l in os.popen("cvsps -A -u --cvs-direct -q"):
143 143 if state == 0: # header
144 144 if l.startswith("PatchSet"):
145 145 id = l[9:-2]
146 146 elif l.startswith("Date"):
147 147 date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
148 148 date = util.datestr(date)
149 149 elif l.startswith("Branch"):
150 150 branch = l[8:-1]
151 151 self.parent[id] = self.lastbranch.get(branch,'bad')
152 152 self.lastbranch[branch] = id
153 153 elif l.startswith("Ancestor branch"):
154 154 ancestor = l[17:-1]
155 155 self.parent[id] = self.lastbranch[ancestor]
156 156 elif l.startswith("Author"):
157 157 author = self.recode(l[8:-1])
158 158 elif l.startswith("Tag: "):
159 159 t = l[5:-1].rstrip()
160 160 if t != "(none)":
161 161 self.tags[t] = id
162 162 elif l.startswith("Log:"):
163 163 state = 1
164 164 log = ""
165 165 elif state == 1: # log
166 166 if l == "Members: \n":
167 167 files = {}
168 168 log = self.recode(log[:-1])
169 169 if log.isspace():
170 170 log = "*** empty log message ***\n"
171 171 state = 2
172 172 else:
173 173 log += l
174 174 elif state == 2:
175 175 if l == "\n": #
176 176 state = 0
177 177 p = [self.parent[id]]
178 178 if id == "1":
179 179 p = []
180 180 if branch == "HEAD":
181 181 branch = ""
182 182 c = commit(author=author, date=date, parents=p,
183 183 desc=log, branch=branch)
184 184 self.changeset[id] = c
185 185 self.files[id] = files
186 186 else:
187 187 colon = l.rfind(':')
188 188 file = l[1:colon]
189 189 rev = l[colon+1:-2]
190 190 rev = rev.split("->")[1]
191 191 files[file] = rev
192 192
193 193 self.heads = self.lastbranch.values()
194 194 finally:
195 195 os.chdir(d)
196 196
197 197 def _connect(self):
198 198 root = self.cvsroot
199 199 conntype = None
200 200 user, host = None, None
201 201 cmd = ['cvs', 'server']
202 202
203 203 self.ui.status("connecting to %s\n" % root)
204 204
205 205 if root.startswith(":pserver:"):
206 206 root = root[9:]
207 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)', root)
207 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
208 root)
208 209 if m:
209 210 conntype = "pserver"
210 211 user, passw, serv, port, root = m.groups()
211 212 if not user:
212 213 user = "anonymous"
213 214 rr = ":pserver:" + user + "@" + serv + ":" + root
214 215 if port:
215 216 rr2, port = "-", int(port)
216 217 else:
217 218 rr2, port = rr, 2401
218 219 rr += str(port)
219 220
220 221 if not passw:
221 222 passw = "A"
222 223 pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
223 224 for l in pf:
224 225 # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
225 226 m = re.match(r'(/\d+\s+/)?(.*)', l)
226 227 l = m.group(2)
227 228 w, p = l.split(' ', 1)
228 229 if w in [rr, rr2]:
229 230 passw = p
230 231 break
231 232 pf.close()
232 233
233 234 sck = socket.socket()
234 235 sck.connect((serv, port))
235 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw, "END AUTH REQUEST", ""]))
236 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
237 "END AUTH REQUEST", ""]))
236 238 if sck.recv(128) != "I LOVE YOU\n":
237 239 raise NoRepo("CVS pserver authentication failed")
238 240
239 241 self.writep = self.readp = sck.makefile('r+')
240 242
241 243 if not conntype and root.startswith(":local:"):
242 244 conntype = "local"
243 245 root = root[7:]
244 246
245 247 if not conntype:
246 248 # :ext:user@host/home/user/path/to/cvsroot
247 249 if root.startswith(":ext:"):
248 250 root = root[5:]
249 251 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
250 252 if not m:
251 253 conntype = "local"
252 254 else:
253 255 conntype = "rsh"
254 256 user, host, root = m.group(1), m.group(2), m.group(3)
255 257
256 258 if conntype != "pserver":
257 259 if conntype == "rsh":
258 260 rsh = os.environ.get("CVS_RSH" or "rsh")
259 261 if user:
260 262 cmd = [rsh, '-l', user, host] + cmd
261 263 else:
262 264 cmd = [rsh, host] + cmd
263 265
264 266 self.writep, self.readp = os.popen2(cmd)
265 267
266 268 self.realroot = root
267 269
268 270 self.writep.write("Root %s\n" % root)
269 271 self.writep.write("Valid-responses ok error Valid-requests Mode"
270 272 " M Mbinary E Checked-in Created Updated"
271 273 " Merged Removed\n")
272 274 self.writep.write("valid-requests\n")
273 275 self.writep.flush()
274 276 r = self.readp.readline()
275 277 if not r.startswith("Valid-requests"):
276 raise util.Abort("server sucks\n")
278 raise util.Abort("server sucks")
277 279 if "UseUnchanged" in r:
278 280 self.writep.write("UseUnchanged\n")
279 281 self.writep.flush()
280 282 r = self.readp.readline()
281 283
282 284 def getheads(self):
283 285 return self.heads
284 286
285 287 def _getfile(self, name, rev):
286 288 if rev.endswith("(DEAD)"):
287 289 raise IOError
288 290
289 291 args = ("-N -P -kk -r %s --" % rev).split()
290 292 args.append(os.path.join(self.cvsrepo, name))
291 293 for x in args:
292 294 self.writep.write("Argument %s\n" % x)
293 295 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
294 296 self.writep.flush()
295 297
296 298 data = ""
297 299 while 1:
298 300 line = self.readp.readline()
299 301 if line.startswith("Created ") or line.startswith("Updated "):
300 302 self.readp.readline() # path
301 303 self.readp.readline() # entries
302 304 mode = self.readp.readline()[:-1]
303 305 count = int(self.readp.readline()[:-1])
304 306 data = self.readp.read(count)
305 307 elif line.startswith(" "):
306 308 data += line[1:]
307 309 elif line.startswith("M "):
308 310 pass
309 311 elif line.startswith("Mbinary "):
310 312 count = int(self.readp.readline()[:-1])
311 313 data = self.readp.read(count)
312 314 else:
313 315 if line == "ok\n":
314 316 return (data, "x" in mode and "x" or "")
315 317 elif line.startswith("E "):
316 318 self.ui.warn("cvs server: %s\n" % line[2:])
317 319 elif line.startswith("Remove"):
318 320 l = self.readp.readline()
319 321 l = self.readp.readline()
320 322 if l != "ok\n":
321 raise util.Abort("unknown CVS response: %s\n" % l)
323 raise util.Abort("unknown CVS response: %s" % l)
322 324 else:
323 raise util.Abort("unknown CVS response: %s\n" % line)
325 raise util.Abort("unknown CVS response: %s" % line)
324 326
325 327 def getfile(self, file, rev):
326 328 data, mode = self._getfile(file, rev)
327 329 self.modecache[(file, rev)] = mode
328 330 return data
329 331
330 332 def getmode(self, file, rev):
331 333 return self.modecache[(file, rev)]
332 334
333 335 def getchanges(self, rev):
334 336 self.modecache = {}
335 337 files = self.files[rev]
336 338 cl = files.items()
337 339 cl.sort()
338 340 return cl
339 341
340 342 def recode(self, text):
341 343 return text.decode(self.encoding, "replace").encode("utf-8")
342 344
343 345 def getcommit(self, rev):
344 346 return self.changeset[rev]
345 347
346 348 def gettags(self):
347 349 return self.tags
348 350
349 351 class convert_git(converter_source):
350 352 def __init__(self, ui, path):
351 353 if os.path.isdir(path + "/.git"):
352 354 path += "/.git"
353 355 self.path = path
354 356 self.ui = ui
355 357 if not os.path.exists(path + "/objects"):
356 358 raise NoRepo("couldn't open GIT repo %s" % path)
357 359
358 360 def getheads(self):
359 361 fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path)
360 362 return [fh.read()[:-1]]
361 363
362 364 def catfile(self, rev, type):
363 365 if rev == "0" * 40: raise IOError()
364 fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null" % (self.path, type, rev))
366 fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null"
367 % (self.path, type, rev))
365 368 return fh.read()
366 369
367 370 def getfile(self, name, rev):
368 371 return self.catfile(rev, "blob")
369 372
370 373 def getmode(self, name, rev):
371 374 return self.modecache[(name, rev)]
372 375
373 376 def getchanges(self, version):
374 377 self.modecache = {}
375 fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s" % (self.path, version))
378 fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s"
379 % (self.path, version))
376 380 changes = []
377 381 for l in fh:
378 382 if "\t" not in l: continue
379 383 m, f = l[:-1].split("\t")
380 384 m = m.split()
381 385 h = m[3]
382 386 p = (m[1] == "100755")
383 387 s = (m[1] == "120000")
384 388 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
385 389 changes.append((f, h))
386 390 return changes
387 391
388 392 def getcommit(self, version):
389 393 c = self.catfile(version, "commit") # read the commit hash
390 394 end = c.find("\n\n")
391 395 message = c[end+2:]
392 396 message = recode(message)
393 397 l = c[:end].splitlines()
394 398 manifest = l[0].split()[1]
395 399 parents = []
396 400 for e in l[1:]:
397 401 n,v = e.split(" ", 1)
398 402 if n == "author":
399 403 p = v.split()
400 404 tm, tz = p[-2:]
401 405 author = " ".join(p[:-2])
402 406 if author[0] == "<": author = author[1:-1]
403 407 author = recode(author)
404 408 if n == "committer":
405 409 p = v.split()
406 410 tm, tz = p[-2:]
407 411 committer = " ".join(p[:-2])
408 412 if committer[0] == "<": committer = committer[1:-1]
409 413 committer = recode(committer)
410 414 message += "\ncommitter: %s\n" % committer
411 415 if n == "parent": parents.append(v)
412 416
413 417 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
414 418 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
415 419 date = tm + " " + str(tz)
416 420
417 421 c = commit(parents=parents, date=date, author=author, desc=message)
418 422 return c
419 423
420 424 def gettags(self):
421 425 tags = {}
422 426 fh = os.popen('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
423 427 prefix = 'refs/tags/'
424 428 for line in fh:
425 429 line = line.strip()
426 430 if not line.endswith("^{}"):
427 431 continue
428 432 node, tag = line.split(None, 1)
429 433 if not tag.startswith(prefix):
430 434 continue
431 435 tag = tag[len(prefix):-3]
432 436 tags[tag] = node
433 437
434 438 return tags
435 439
436 440 class convert_mercurial(converter_sink):
437 441 def __init__(self, ui, path):
438 442 self.path = path
439 443 self.ui = ui
440 444 try:
441 445 self.repo = hg.repository(self.ui, path)
442 446 except:
443 447 raise NoRepo("could open hg repo %s" % path)
444 448
445 449 def mapfile(self):
446 450 return os.path.join(self.path, ".hg", "shamap")
447 451
448 452 def getheads(self):
449 453 h = self.repo.changelog.heads()
450 454 return [ hg.hex(x) for x in h ]
451 455
452 456 def putfile(self, f, e, data):
453 457 self.repo.wwrite(f, data, e)
454 458 if self.repo.dirstate.state(f) == '?':
455 459 self.repo.dirstate.update([f], "a")
456 460
457 461 def delfile(self, f):
458 462 try:
459 463 os.unlink(self.repo.wjoin(f))
460 464 #self.repo.remove([f])
461 465 except:
462 466 pass
463 467
464 468 def putcommit(self, files, parents, commit):
465 469 seen = {}
466 470 pl = []
467 471 for p in parents:
468 472 if p not in seen:
469 473 pl.append(p)
470 474 seen[p] = 1
471 475 parents = pl
472 476
473 477 if len(parents) < 2: parents.append("0" * 40)
474 478 if len(parents) < 2: parents.append("0" * 40)
475 479 p2 = parents.pop(0)
476 480
477 481 text = commit.desc
478 482 extra = {}
479 483 try:
480 484 extra["branch"] = commit.branch
481 485 except AttributeError:
482 486 pass
483 487
484 488 while parents:
485 489 p1 = p2
486 490 p2 = parents.pop(0)
487 491 a = self.repo.rawcommit(files, text, commit.author, commit.date,
488 492 hg.bin(p1), hg.bin(p2), extra=extra)
489 493 text = "(octopus merge fixup)\n"
490 494 p2 = hg.hex(self.repo.changelog.tip())
491 495
492 496 return p2
493 497
494 498 def puttags(self, tags):
495 499 try:
496 500 old = self.repo.wfile(".hgtags").read()
497 501 oldlines = old.splitlines(1)
498 502 oldlines.sort()
499 503 except:
500 504 oldlines = []
501 505
502 506 k = tags.keys()
503 507 k.sort()
504 508 newlines = []
505 509 for tag in k:
506 510 newlines.append("%s %s\n" % (tags[tag], tag))
507 511
508 512 newlines.sort()
509 513
510 514 if newlines != oldlines:
511 515 self.ui.status("updating tags\n")
512 516 f = self.repo.wfile(".hgtags", "w")
513 517 f.write("".join(newlines))
514 518 f.close()
515 519 if not oldlines: self.repo.add([".hgtags"])
516 520 date = "%s 0" % int(time.mktime(time.gmtime()))
517 521 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
518 522 date, self.repo.changelog.tip(), hg.nullid)
519 523 return hg.hex(self.repo.changelog.tip())
520 524
521 525 converters = [convert_cvs, convert_git, convert_mercurial]
522 526
523 527 def converter(ui, path):
524 528 if not os.path.isdir(path):
525 raise util.Abort("%s: not a directory\n" % path)
529 raise util.Abort("%s: not a directory" % path)
526 530 for c in converters:
527 531 try:
528 532 return c(ui, path)
529 533 except NoRepo:
530 534 pass
531 raise util.Abort("%s: unknown repository type\n" % path)
535 raise util.Abort("%s: unknown repository type" % path)
532 536
533 537 class convert(object):
534 538 def __init__(self, ui, source, dest, mapfile, opts):
535 539
536 540 self.source = source
537 541 self.dest = dest
538 542 self.ui = ui
539 543 self.mapfile = mapfile
540 544 self.opts = opts
541 545 self.commitcache = {}
542 546
543 547 self.map = {}
544 548 try:
545 549 for l in file(self.mapfile):
546 550 sv, dv = l[:-1].split()
547 551 self.map[sv] = dv
548 552 except IOError:
549 553 pass
550 554
551 555 def walktree(self, heads):
552 556 visit = heads
553 557 known = {}
554 558 parents = {}
555 559 while visit:
556 560 n = visit.pop(0)
557 561 if n in known or n in self.map: continue
558 562 known[n] = 1
559 563 self.commitcache[n] = self.source.getcommit(n)
560 564 cp = self.commitcache[n].parents
561 565 for p in cp:
562 566 parents.setdefault(n, []).append(p)
563 567 visit.append(p)
564 568
565 569 return parents
566 570
567 571 def toposort(self, parents):
568 572 visit = parents.keys()
569 573 seen = {}
570 574 children = {}
571 575
572 576 while visit:
573 577 n = visit.pop(0)
574 578 if n in seen: continue
575 579 seen[n] = 1
576 580 pc = 0
577 581 if n in parents:
578 582 for p in parents[n]:
579 583 if p not in self.map: pc += 1
580 584 visit.append(p)
581 585 children.setdefault(p, []).append(n)
582 586 if not pc: root = n
583 587
584 588 s = []
585 589 removed = {}
586 590 visit = children.keys()
587 591 while visit:
588 592 n = visit.pop(0)
589 593 if n in removed: continue
590 594 dep = 0
591 595 if n in parents:
592 596 for p in parents[n]:
593 597 if p in self.map: continue
594 598 if p not in removed:
595 599 # we're still dependent
596 600 visit.append(n)
597 601 dep = 1
598 602 break
599 603
600 604 if not dep:
601 605 # all n's parents are in the list
602 606 removed[n] = 1
603 607 if n not in self.map:
604 608 s.append(n)
605 609 if n in children:
606 610 for c in children[n]:
607 611 visit.insert(0, c)
608 612
609 613 if self.opts.get('datesort'):
610 614 depth = {}
611 615 for n in s:
612 616 depth[n] = 0
613 pl = [p for p in self.commitcache[n].parents if p not in self.map]
617 pl = [p for p in self.commitcache[n].parents
618 if p not in self.map]
614 619 if pl:
615 620 depth[n] = max([depth[p] for p in pl]) + 1
616 621
617 622 s = [(depth[n], self.commitcache[n].date, n) for n in s]
618 623 s.sort()
619 624 s = [e[2] for e in s]
620 625
621 626 return s
622 627
623 628 def copy(self, rev):
624 629 c = self.commitcache[rev]
625 630 files = self.source.getchanges(rev)
626 631
627 632 for f,v in files:
628 633 try:
629 634 data = self.source.getfile(f, v)
630 635 except IOError, inst:
631 636 self.dest.delfile(f)
632 637 else:
633 638 e = self.source.getmode(f, v)
634 639 self.dest.putfile(f, e, data)
635 640
636 641 r = [self.map[v] for v in c.parents]
637 642 f = [f for f,v in files]
638 643 self.map[rev] = self.dest.putcommit(f, r, c)
639 644 file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))
640 645
641 646 def convert(self):
642 647 self.ui.status("scanning source...\n")
643 648 heads = self.source.getheads()
644 649 parents = self.walktree(heads)
645 650 self.ui.status("sorting...\n")
646 651 t = self.toposort(parents)
647 652 num = len(t)
648 653 c = None
649 654
650 655 self.ui.status("converting...\n")
651 656 for c in t:
652 657 num -= 1
653 658 desc = self.commitcache[c].desc
654 659 if "\n" in desc:
655 660 desc = desc.splitlines()[0]
656 661 self.ui.status("%d %s\n" % (num, desc))
657 662 self.copy(c)
658 663
659 664 tags = self.source.gettags()
660 665 ctags = {}
661 666 for k in tags:
662 667 v = tags[k]
663 668 if v in self.map:
664 669 ctags[k] = self.map[v]
665 670
666 671 if c and ctags:
667 672 nrev = self.dest.puttags(ctags)
668 673 # write another hash correspondence to override the previous
669 674 # one so we don't end up with extra tag heads
670 675 if nrev:
671 676 file(self.mapfile, "a").write("%s %s\n" % (c, nrev))
672 677
673 678 def _convert(ui, src, dest=None, mapfile=None, **opts):
674 679 '''Convert a foreign SCM repository to a Mercurial one.
675 680
676 681 Accepted source formats:
677 682 - GIT
678 683 - CVS
679 684
680 685 Accepted destination formats:
681 686 - Mercurial
682 687
683 688 If destination isn't given, a new Mercurial repo named <src>-hg will
684 689 be created. If <mapfile> isn't given, it will be put in a default
685 690 location (<dest>/.hg/shamap by default)
686 691
687 692 The <mapfile> is a simple text file that maps each source commit ID to
688 693 the destination ID for that revision, like so:
689 694
690 695 <source ID> <destination ID>
691 696
692 697 If the file doesn't exist, it's automatically created. It's updated
693 698 on each commit copied, so convert-repo can be interrupted and can
694 699 be run repeatedly to copy new commits.
695 700 '''
696 701
697 702 srcc = converter(ui, src)
698 703 if not hasattr(srcc, "getcommit"):
699 raise util.Abort("%s: can't read from this repo type\n" % src)
704 raise util.Abort("%s: can't read from this repo type" % src)
700 705
701 706 if not dest:
702 707 dest = src + "-hg"
703 708 ui.status("assuming destination %s\n" % dest)
704 709
705 710 # Try to be smart and initalize things when required
706 711 if os.path.isdir(dest):
707 712 if len(os.listdir(dest)) > 0:
708 713 try:
709 714 hg.repository(ui, dest)
710 715 ui.status("destination %s is a Mercurial repository\n" % dest)
711 except repo.RepoError:
716 except hg.RepoError:
712 717 raise util.Abort(
713 """destination directory %s is not empty.
714 Please specify an empty directory to be initialized or an already initialized
715 mercurial repository
716 """ % dest)
718 "destination directory %s is not empty.\n"
719 "Please specify an empty directory to be initialized\n"
720 "or an already initialized mercurial repository"
721 % dest)
717 722 else:
718 723 ui.status("initializing destination %s repository\n" % dest)
719 724 hg.repository(ui, dest, create=True)
720 725 elif os.path.exists(dest):
721 raise util.Abort("destination %s exists and is not a directory\n" % dest)
726 raise util.Abort("destination %s exists and is not a directory" % dest)
722 727 else:
723 728 ui.status("initializing destination %s repository\n" % dest)
724 729 hg.repository(ui, dest, create=True)
725 730
726 731 destc = converter(ui, dest)
727 732 if not hasattr(destc, "putcommit"):
728 raise util.Abort("%s: can't write to this repo type\n" % src)
733 raise util.Abort("%s: can't write to this repo type" % src)
729 734
730 735 if not mapfile:
731 736 try:
732 737 mapfile = destc.mapfile()
733 738 except:
734 739 mapfile = os.path.join(destc, "map")
735 740
736 741 c = convert(ui, srcc, destc, mapfile, opts)
737 742 c.convert()
738 743
739 744 cmdtable = {
740 "convert": (_convert,
745 "convert":
746 (_convert,
741 747 [('', 'datesort', None, 'try to sort changesets by date')],
742 'hg convert [OPTIONS] <src> [dst [map]]'),
748 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
743 749 }
General Comments 0
You need to be logged in to leave comments. Login now