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