21f8acdda8df3b89db4f7d01d72e4184a155bf53
   1#!/usr/bin/env python
   2#
   3# Copyright (c) 2012 Felipe Contreras
   4#
   5
   6# Inspired by Rocco Rutte's hg-fast-export
   7
   8# Just copy to your ~/bin, or anywhere in your $PATH.
   9# Then you can clone with:
  10# git clone hg::/path/to/mercurial/repo/
  11#
  12# For remote repositories a local clone is stored in
  13# "$GIT_DIR/hg/origin/clone/.hg/".
  14
  15from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions
  16
  17import re
  18import sys
  19import os
  20import json
  21import shutil
  22import subprocess
  23import urllib
  24import atexit
  25import urlparse, hashlib
  26
  27#
  28# If you are not in hg-git-compat mode and want to disable the tracking of
  29# named branches:
  30# git config --global remote-hg.track-branches false
  31#
  32# If you don't want to force pushes (and thus risk creating new remote heads):
  33# git config --global remote-hg.force-push false
  34#
  35# If you want the equivalent of hg's clone/pull--insecure option:
  36# git config --global remote-hg.insecure true
  37#
  38# If you want to switch to hg-git compatibility mode:
  39# git config --global remote-hg.hg-git-compat true
  40#
  41# git:
  42# Sensible defaults for git.
  43# hg bookmarks are exported as git branches, hg branches are prefixed
  44# with 'branches/', HEAD is a special case.
  45#
  46# hg:
  47# Emulate hg-git.
  48# Only hg bookmarks are exported as git branches.
  49# Commits are modified to preserve hg information and allow bidirectionality.
  50#
  51
  52NAME_RE = re.compile('^([^<>]+)')
  53AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
  54EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
  55AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
  56RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
  57
  58def die(msg, *args):
  59    sys.stderr.write('ERROR: %s\n' % (msg % args))
  60    sys.exit(1)
  61
  62def warn(msg, *args):
  63    sys.stderr.write('WARNING: %s\n' % (msg % args))
  64
  65def gitmode(flags):
  66    return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
  67
  68def gittz(tz):
  69    return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
  70
  71def hgmode(mode):
  72    m = { '100755': 'x', '120000': 'l' }
  73    return m.get(mode, '')
  74
  75def hghex(node):
  76    return hg.node.hex(node)
  77
  78def hgref(ref):
  79    return ref.replace('___', ' ')
  80
  81def gitref(ref):
  82    return ref.replace(' ', '___')
  83
  84def get_config(config):
  85    cmd = ['git', 'config', '--get', config]
  86    process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
  87    output, _ = process.communicate()
  88    return output
  89
  90def get_config_bool(config, default=False):
  91    value = get_config(config).rstrip('\n')
  92    if value == "true":
  93        return True
  94    elif value == "false":
  95        return False
  96    else:
  97        return default
  98
  99class Marks:
 100
 101    def __init__(self, path):
 102        self.path = path
 103        self.tips = {}
 104        self.marks = {}
 105        self.rev_marks = {}
 106        self.last_mark = 0
 107
 108        self.load()
 109
 110    def load(self):
 111        if not os.path.exists(self.path):
 112            return
 113
 114        tmp = json.load(open(self.path))
 115
 116        self.tips = tmp['tips']
 117        self.marks = tmp['marks']
 118        self.last_mark = tmp['last-mark']
 119
 120        for rev, mark in self.marks.iteritems():
 121            self.rev_marks[mark] = int(rev)
 122
 123    def dict(self):
 124        return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
 125
 126    def store(self):
 127        json.dump(self.dict(), open(self.path, 'w'))
 128
 129    def __str__(self):
 130        return str(self.dict())
 131
 132    def from_rev(self, rev):
 133        return self.marks[str(rev)]
 134
 135    def to_rev(self, mark):
 136        return self.rev_marks[mark]
 137
 138    def next_mark(self):
 139        self.last_mark += 1
 140        return self.last_mark
 141
 142    def get_mark(self, rev):
 143        self.last_mark += 1
 144        self.marks[str(rev)] = self.last_mark
 145        return self.last_mark
 146
 147    def new_mark(self, rev, mark):
 148        self.marks[str(rev)] = mark
 149        self.rev_marks[mark] = rev
 150        self.last_mark = mark
 151
 152    def is_marked(self, rev):
 153        return str(rev) in self.marks
 154
 155    def get_tip(self, branch):
 156        return self.tips.get(branch, 0)
 157
 158    def set_tip(self, branch, tip):
 159        self.tips[branch] = tip
 160
 161class Parser:
 162
 163    def __init__(self, repo):
 164        self.repo = repo
 165        self.line = self.get_line()
 166
 167    def get_line(self):
 168        return sys.stdin.readline().strip()
 169
 170    def __getitem__(self, i):
 171        return self.line.split()[i]
 172
 173    def check(self, word):
 174        return self.line.startswith(word)
 175
 176    def each_block(self, separator):
 177        while self.line != separator:
 178            yield self.line
 179            self.line = self.get_line()
 180
 181    def __iter__(self):
 182        return self.each_block('')
 183
 184    def next(self):
 185        self.line = self.get_line()
 186        if self.line == 'done':
 187            self.line = None
 188
 189    def get_mark(self):
 190        i = self.line.index(':') + 1
 191        return int(self.line[i:])
 192
 193    def get_data(self):
 194        if not self.check('data'):
 195            return None
 196        i = self.line.index(' ') + 1
 197        size = int(self.line[i:])
 198        return sys.stdin.read(size)
 199
 200    def get_author(self):
 201        global bad_mail
 202
 203        ex = None
 204        m = RAW_AUTHOR_RE.match(self.line)
 205        if not m:
 206            return None
 207        _, name, email, date, tz = m.groups()
 208        if name and 'ext:' in name:
 209            m = re.match('^(.+?) ext:\((.+)\)$', name)
 210            if m:
 211                name = m.group(1)
 212                ex = urllib.unquote(m.group(2))
 213
 214        if email != bad_mail:
 215            if name:
 216                user = '%s <%s>' % (name, email)
 217            else:
 218                user = '<%s>' % (email)
 219        else:
 220            user = name
 221
 222        if ex:
 223            user += ex
 224
 225        tz = int(tz)
 226        tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
 227        return (user, int(date), -tz)
 228
 229def fix_file_path(path):
 230    if not os.path.isabs(path):
 231        return path
 232    return os.path.relpath(path, '/')
 233
 234def export_files(files):
 235    global marks, filenodes
 236
 237    final = []
 238    for f in files:
 239        fid = node.hex(f.filenode())
 240
 241        if fid in filenodes:
 242            mark = filenodes[fid]
 243        else:
 244            mark = marks.next_mark()
 245            filenodes[fid] = mark
 246            d = f.data()
 247
 248            print "blob"
 249            print "mark :%u" % mark
 250            print "data %d" % len(d)
 251            print d
 252
 253        path = fix_file_path(f.path())
 254        final.append((gitmode(f.flags()), mark, path))
 255
 256    return final
 257
 258def get_filechanges(repo, ctx, parent):
 259    modified = set()
 260    added = set()
 261    removed = set()
 262
 263    # load earliest manifest first for caching reasons
 264    prev = repo[parent].manifest().copy()
 265    cur = ctx.manifest()
 266
 267    for fn in cur:
 268        if fn in prev:
 269            if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
 270                modified.add(fn)
 271            del prev[fn]
 272        else:
 273            added.add(fn)
 274    removed |= set(prev.keys())
 275
 276    return added | modified, removed
 277
 278def fixup_user_git(user):
 279    name = mail = None
 280    user = user.replace('"', '')
 281    m = AUTHOR_RE.match(user)
 282    if m:
 283        name = m.group(1)
 284        mail = m.group(2).strip()
 285    else:
 286        m = EMAIL_RE.match(user)
 287        if m:
 288            name = m.group(1)
 289            mail = m.group(2)
 290        else:
 291            m = NAME_RE.match(user)
 292            if m:
 293                name = m.group(1).strip()
 294    return (name, mail)
 295
 296def fixup_user_hg(user):
 297    def sanitize(name):
 298        # stole this from hg-git
 299        return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
 300
 301    m = AUTHOR_HG_RE.match(user)
 302    if m:
 303        name = sanitize(m.group(1))
 304        mail = sanitize(m.group(2))
 305        ex = m.group(3)
 306        if ex:
 307            name += ' ext:(' + urllib.quote(ex) + ')'
 308    else:
 309        name = sanitize(user)
 310        if '@' in user:
 311            mail = name
 312        else:
 313            mail = None
 314
 315    return (name, mail)
 316
 317def fixup_user(user):
 318    global mode, bad_mail
 319
 320    if mode == 'git':
 321        name, mail = fixup_user_git(user)
 322    else:
 323        name, mail = fixup_user_hg(user)
 324
 325    if not name:
 326        name = bad_name
 327    if not mail:
 328        mail = bad_mail
 329
 330    return '%s <%s>' % (name, mail)
 331
 332def get_repo(url, alias):
 333    global dirname, peer
 334
 335    myui = ui.ui()
 336    myui.setconfig('ui', 'interactive', 'off')
 337    myui.fout = sys.stderr
 338
 339    if get_config_bool('remote-hg.insecure'):
 340        myui.setconfig('web', 'cacerts', '')
 341
 342    extensions.loadall(myui)
 343
 344    if hg.islocal(url):
 345        repo = hg.repository(myui, url)
 346    else:
 347        local_path = os.path.join(dirname, 'clone')
 348        if not os.path.exists(local_path):
 349            try:
 350                peer, dstpeer = hg.clone(myui, {}, url, local_path, update=True, pull=True)
 351            except:
 352                die('Repository error')
 353            repo = dstpeer.local()
 354        else:
 355            repo = hg.repository(myui, local_path)
 356            try:
 357                peer = hg.peer(myui, {}, url)
 358            except:
 359                die('Repository error')
 360            repo.pull(peer, heads=None, force=True)
 361
 362    return repo
 363
 364def rev_to_mark(rev):
 365    global marks
 366    return marks.from_rev(rev)
 367
 368def mark_to_rev(mark):
 369    global marks
 370    return marks.to_rev(mark)
 371
 372def export_ref(repo, name, kind, head):
 373    global prefix, marks, mode
 374
 375    ename = '%s/%s' % (kind, name)
 376    tip = marks.get_tip(ename)
 377
 378    revs = xrange(tip, head.rev() + 1)
 379    count = 0
 380
 381    revs = [rev for rev in revs if not marks.is_marked(rev)]
 382
 383    for rev in revs:
 384
 385        c = repo[rev]
 386        (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())
 387        rev_branch = extra['branch']
 388
 389        author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
 390        if 'committer' in extra:
 391            user, time, tz = extra['committer'].rsplit(' ', 2)
 392            committer = "%s %s %s" % (user, time, gittz(int(tz)))
 393        else:
 394            committer = author
 395
 396        parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
 397
 398        if len(parents) == 0:
 399            modified = c.manifest().keys()
 400            removed = []
 401        else:
 402            modified, removed = get_filechanges(repo, c, parents[0])
 403
 404        desc += '\n'
 405
 406        if mode == 'hg':
 407            extra_msg = ''
 408
 409            if rev_branch != 'default':
 410                extra_msg += 'branch : %s\n' % rev_branch
 411
 412            renames = []
 413            for f in c.files():
 414                if f not in c.manifest():
 415                    continue
 416                rename = c.filectx(f).renamed()
 417                if rename:
 418                    renames.append((rename[0], f))
 419
 420            for e in renames:
 421                extra_msg += "rename : %s => %s\n" % e
 422
 423            for key, value in extra.iteritems():
 424                if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
 425                    continue
 426                else:
 427                    extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
 428
 429            if extra_msg:
 430                desc += '\n--HG--\n' + extra_msg
 431
 432        if len(parents) == 0 and rev:
 433            print 'reset %s/%s' % (prefix, ename)
 434
 435        modified_final = export_files(c.filectx(f) for f in modified)
 436
 437        print "commit %s/%s" % (prefix, ename)
 438        print "mark :%d" % (marks.get_mark(rev))
 439        print "author %s" % (author)
 440        print "committer %s" % (committer)
 441        print "data %d" % (len(desc))
 442        print desc
 443
 444        if len(parents) > 0:
 445            print "from :%s" % (rev_to_mark(parents[0]))
 446            if len(parents) > 1:
 447                print "merge :%s" % (rev_to_mark(parents[1]))
 448
 449        for f in modified_final:
 450            print "M %s :%u %s" % f
 451        for f in removed:
 452            print "D %s" % (fix_file_path(f))
 453        print
 454
 455        count += 1
 456        if (count % 100 == 0):
 457            print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
 458
 459    # make sure the ref is updated
 460    print "reset %s/%s" % (prefix, ename)
 461    print "from :%u" % rev_to_mark(rev)
 462    print
 463
 464    marks.set_tip(ename, rev)
 465
 466def export_tag(repo, tag):
 467    export_ref(repo, tag, 'tags', repo[hgref(tag)])
 468
 469def export_bookmark(repo, bmark):
 470    head = bmarks[hgref(bmark)]
 471    export_ref(repo, bmark, 'bookmarks', head)
 472
 473def export_branch(repo, branch):
 474    tip = get_branch_tip(repo, branch)
 475    head = repo[tip]
 476    export_ref(repo, branch, 'branches', head)
 477
 478def export_head(repo):
 479    global g_head
 480    export_ref(repo, g_head[0], 'bookmarks', g_head[1])
 481
 482def do_capabilities(parser):
 483    global prefix, dirname
 484
 485    print "import"
 486    print "export"
 487    print "refspec refs/heads/branches/*:%s/branches/*" % prefix
 488    print "refspec refs/heads/*:%s/bookmarks/*" % prefix
 489    print "refspec refs/tags/*:%s/tags/*" % prefix
 490
 491    path = os.path.join(dirname, 'marks-git')
 492
 493    if os.path.exists(path):
 494        print "*import-marks %s" % path
 495    print "*export-marks %s" % path
 496
 497    print
 498
 499def branch_tip(repo, branch):
 500    # older versions of mercurial don't have this
 501    if hasattr(repo, 'branchtip'):
 502        return repo.branchtip(branch)
 503    else:
 504        return repo.branchtags()[branch]
 505
 506def get_branch_tip(repo, branch):
 507    global branches
 508
 509    heads = branches.get(hgref(branch), None)
 510    if not heads:
 511        return None
 512
 513    # verify there's only one head
 514    if (len(heads) > 1):
 515        warn("Branch '%s' has more than one head, consider merging" % branch)
 516        return branch_tip(repo, hgref(branch))
 517
 518    return heads[0]
 519
 520def list_head(repo, cur):
 521    global g_head, bmarks
 522
 523    head = bookmarks.readcurrent(repo)
 524    if head:
 525        node = repo[head]
 526    else:
 527        # fake bookmark from current branch
 528        head = cur
 529        node = repo['.']
 530        if not node:
 531            node = repo['tip']
 532        if not node:
 533            return
 534        if head == 'default':
 535            head = 'master'
 536        bmarks[head] = node
 537
 538    head = gitref(head)
 539    print "@refs/heads/%s HEAD" % head
 540    g_head = (head, node)
 541
 542def do_list(parser):
 543    global branches, bmarks, track_branches
 544
 545    repo = parser.repo
 546    for bmark, node in bookmarks.listbookmarks(repo).iteritems():
 547        bmarks[bmark] = repo[node]
 548
 549    cur = repo.dirstate.branch()
 550
 551    list_head(repo, cur)
 552
 553    if track_branches:
 554        for branch in repo.branchmap():
 555            heads = repo.branchheads(branch)
 556            if len(heads):
 557                branches[branch] = heads
 558
 559        for branch in branches:
 560            print "? refs/heads/branches/%s" % gitref(branch)
 561
 562    for bmark in bmarks:
 563        print "? refs/heads/%s" % gitref(bmark)
 564
 565    for tag, node in repo.tagslist():
 566        if tag == 'tip':
 567            continue
 568        print "? refs/tags/%s" % gitref(tag)
 569
 570    print
 571
 572def do_import(parser):
 573    repo = parser.repo
 574
 575    path = os.path.join(dirname, 'marks-git')
 576
 577    print "feature done"
 578    if os.path.exists(path):
 579        print "feature import-marks=%s" % path
 580    print "feature export-marks=%s" % path
 581    sys.stdout.flush()
 582
 583    tmp = encoding.encoding
 584    encoding.encoding = 'utf-8'
 585
 586    # lets get all the import lines
 587    while parser.check('import'):
 588        ref = parser[1]
 589
 590        if (ref == 'HEAD'):
 591            export_head(repo)
 592        elif ref.startswith('refs/heads/branches/'):
 593            branch = ref[len('refs/heads/branches/'):]
 594            export_branch(repo, branch)
 595        elif ref.startswith('refs/heads/'):
 596            bmark = ref[len('refs/heads/'):]
 597            export_bookmark(repo, bmark)
 598        elif ref.startswith('refs/tags/'):
 599            tag = ref[len('refs/tags/'):]
 600            export_tag(repo, tag)
 601
 602        parser.next()
 603
 604    encoding.encoding = tmp
 605
 606    print 'done'
 607
 608def parse_blob(parser):
 609    global blob_marks
 610
 611    parser.next()
 612    mark = parser.get_mark()
 613    parser.next()
 614    data = parser.get_data()
 615    blob_marks[mark] = data
 616    parser.next()
 617
 618def get_merge_files(repo, p1, p2, files):
 619    for e in repo[p1].files():
 620        if e not in files:
 621            if e not in repo[p1].manifest():
 622                continue
 623            f = { 'ctx' : repo[p1][e] }
 624            files[e] = f
 625
 626def parse_commit(parser):
 627    global marks, blob_marks, parsed_refs
 628    global mode
 629
 630    from_mark = merge_mark = None
 631
 632    ref = parser[1]
 633    parser.next()
 634
 635    commit_mark = parser.get_mark()
 636    parser.next()
 637    author = parser.get_author()
 638    parser.next()
 639    committer = parser.get_author()
 640    parser.next()
 641    data = parser.get_data()
 642    parser.next()
 643    if parser.check('from'):
 644        from_mark = parser.get_mark()
 645        parser.next()
 646    if parser.check('merge'):
 647        merge_mark = parser.get_mark()
 648        parser.next()
 649        if parser.check('merge'):
 650            die('octopus merges are not supported yet')
 651
 652    # fast-export adds an extra newline
 653    if data[-1] == '\n':
 654        data = data[:-1]
 655
 656    files = {}
 657
 658    for line in parser:
 659        if parser.check('M'):
 660            t, m, mark_ref, path = line.split(' ', 3)
 661            mark = int(mark_ref[1:])
 662            f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
 663        elif parser.check('D'):
 664            t, path = line.split(' ', 1)
 665            f = { 'deleted' : True }
 666        else:
 667            die('Unknown file command: %s' % line)
 668        files[path] = f
 669
 670    def getfilectx(repo, memctx, f):
 671        of = files[f]
 672        if 'deleted' in of:
 673            raise IOError
 674        if 'ctx' in of:
 675            return of['ctx']
 676        is_exec = of['mode'] == 'x'
 677        is_link = of['mode'] == 'l'
 678        rename = of.get('rename', None)
 679        return context.memfilectx(f, of['data'],
 680                is_link, is_exec, rename)
 681
 682    repo = parser.repo
 683
 684    user, date, tz = author
 685    extra = {}
 686
 687    if committer != author:
 688        extra['committer'] = "%s %u %u" % committer
 689
 690    if from_mark:
 691        p1 = repo.changelog.node(mark_to_rev(from_mark))
 692    else:
 693        p1 = '\0' * 20
 694
 695    if merge_mark:
 696        p2 = repo.changelog.node(mark_to_rev(merge_mark))
 697    else:
 698        p2 = '\0' * 20
 699
 700    #
 701    # If files changed from any of the parents, hg wants to know, but in git if
 702    # nothing changed from the first parent, nothing changed.
 703    #
 704    if merge_mark:
 705        get_merge_files(repo, p1, p2, files)
 706
 707    # Check if the ref is supposed to be a named branch
 708    if ref.startswith('refs/heads/branches/'):
 709        branch = ref[len('refs/heads/branches/'):]
 710        extra['branch'] = hgref(branch)
 711
 712    if mode == 'hg':
 713        i = data.find('\n--HG--\n')
 714        if i >= 0:
 715            tmp = data[i + len('\n--HG--\n'):].strip()
 716            for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
 717                if k == 'rename':
 718                    old, new = v.split(' => ', 1)
 719                    files[new]['rename'] = old
 720                elif k == 'branch':
 721                    extra[k] = v
 722                elif k == 'extra':
 723                    ek, ev = v.split(' : ', 1)
 724                    extra[ek] = urllib.unquote(ev)
 725            data = data[:i]
 726
 727    ctx = context.memctx(repo, (p1, p2), data,
 728            files.keys(), getfilectx,
 729            user, (date, tz), extra)
 730
 731    tmp = encoding.encoding
 732    encoding.encoding = 'utf-8'
 733
 734    node = repo.commitctx(ctx)
 735
 736    encoding.encoding = tmp
 737
 738    rev = repo[node].rev()
 739
 740    parsed_refs[ref] = node
 741    marks.new_mark(rev, commit_mark)
 742
 743def parse_reset(parser):
 744    global parsed_refs
 745
 746    ref = parser[1]
 747    parser.next()
 748    # ugh
 749    if parser.check('commit'):
 750        parse_commit(parser)
 751        return
 752    if not parser.check('from'):
 753        return
 754    from_mark = parser.get_mark()
 755    parser.next()
 756
 757    node = parser.repo.changelog.node(mark_to_rev(from_mark))
 758    parsed_refs[ref] = node
 759
 760def parse_tag(parser):
 761    name = parser[1]
 762    parser.next()
 763    from_mark = parser.get_mark()
 764    parser.next()
 765    tagger = parser.get_author()
 766    parser.next()
 767    data = parser.get_data()
 768    parser.next()
 769
 770    parsed_tags[name] = (tagger, data)
 771
 772def write_tag(repo, tag, node, msg, author):
 773    branch = repo[node].branch()
 774    tip = branch_tip(repo, branch)
 775    tip = repo[tip]
 776
 777    def getfilectx(repo, memctx, f):
 778        try:
 779            fctx = tip.filectx(f)
 780            data = fctx.data()
 781        except error.ManifestLookupError:
 782            data = ""
 783        content = data + "%s %s\n" % (hghex(node), tag)
 784        return context.memfilectx(f, content, False, False, None)
 785
 786    p1 = tip.hex()
 787    p2 = '\0' * 20
 788    if not author:
 789        author = (None, 0, 0)
 790    user, date, tz = author
 791
 792    ctx = context.memctx(repo, (p1, p2), msg,
 793            ['.hgtags'], getfilectx,
 794            user, (date, tz), {'branch' : branch})
 795
 796    tmp = encoding.encoding
 797    encoding.encoding = 'utf-8'
 798
 799    tagnode = repo.commitctx(ctx)
 800
 801    encoding.encoding = tmp
 802
 803    return tagnode
 804
 805def do_export(parser):
 806    global parsed_refs, bmarks, peer
 807
 808    p_bmarks = []
 809
 810    parser.next()
 811
 812    for line in parser.each_block('done'):
 813        if parser.check('blob'):
 814            parse_blob(parser)
 815        elif parser.check('commit'):
 816            parse_commit(parser)
 817        elif parser.check('reset'):
 818            parse_reset(parser)
 819        elif parser.check('tag'):
 820            parse_tag(parser)
 821        elif parser.check('feature'):
 822            pass
 823        else:
 824            die('unhandled export command: %s' % line)
 825
 826    for ref, node in parsed_refs.iteritems():
 827        if ref.startswith('refs/heads/branches'):
 828            branch = ref[len('refs/heads/branches/'):]
 829            if branch in branches and node in branches[branch]:
 830                # up to date
 831                continue
 832            print "ok %s" % ref
 833        elif ref.startswith('refs/heads/'):
 834            bmark = ref[len('refs/heads/'):]
 835            p_bmarks.append((bmark, node))
 836            continue
 837        elif ref.startswith('refs/tags/'):
 838            tag = ref[len('refs/tags/'):]
 839            tag = hgref(tag)
 840            author, msg = parsed_tags.get(tag, (None, None))
 841            if mode == 'git':
 842                if not msg:
 843                    msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6]));
 844                write_tag(parser.repo, tag, node, msg, author)
 845            else:
 846                fp = parser.repo.opener('localtags', 'a')
 847                fp.write('%s %s\n' % (hghex(node), tag))
 848                fp.close()
 849            print "ok %s" % ref
 850        else:
 851            # transport-helper/fast-export bugs
 852            continue
 853
 854    if peer:
 855        parser.repo.push(peer, force=force_push, newbranch=True)
 856        remote_bmarks = peer.listkeys('bookmarks')
 857
 858    # handle bookmarks
 859    for bmark, node in p_bmarks:
 860        ref = 'refs/heads/' + bmark
 861        new = hghex(node)
 862
 863        if bmark in bmarks:
 864            old = bmarks[bmark].hex()
 865        else:
 866            old = ''
 867
 868        if old == new:
 869            continue
 870
 871        if bmark == 'master' and 'master' not in parser.repo._bookmarks:
 872            # fake bookmark
 873            print "ok %s" % ref
 874            continue
 875        elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
 876            # updated locally
 877            pass
 878        else:
 879            print "error %s" % ref
 880            continue
 881
 882        if peer:
 883            old = remote_bmarks.get(bmark, '')
 884            if not peer.pushkey('bookmarks', bmark, old, new):
 885                print "error %s" % ref
 886                continue
 887
 888        print "ok %s" % ref
 889
 890    print
 891
 892def fix_path(alias, repo, orig_url):
 893    url = urlparse.urlparse(orig_url, 'file')
 894    if url.scheme != 'file' or os.path.isabs(url.path):
 895        return
 896    abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
 897    cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
 898    subprocess.call(cmd)
 899
 900def main(args):
 901    global prefix, dirname, branches, bmarks
 902    global marks, blob_marks, parsed_refs
 903    global peer, mode, bad_mail, bad_name
 904    global track_branches, force_push, is_tmp
 905    global parsed_tags
 906    global filenodes
 907
 908    alias = args[1]
 909    url = args[2]
 910    peer = None
 911
 912    hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
 913    track_branches = get_config_bool('remote-hg.track-branches', True)
 914    force_push = get_config_bool('remote-hg.force-push')
 915
 916    if hg_git_compat:
 917        mode = 'hg'
 918        bad_mail = 'none@none'
 919        bad_name = ''
 920    else:
 921        mode = 'git'
 922        bad_mail = 'unknown'
 923        bad_name = 'Unknown'
 924
 925    if alias[4:] == url:
 926        is_tmp = True
 927        alias = hashlib.sha1(alias).hexdigest()
 928    else:
 929        is_tmp = False
 930
 931    gitdir = os.environ['GIT_DIR']
 932    dirname = os.path.join(gitdir, 'hg', alias)
 933    branches = {}
 934    bmarks = {}
 935    blob_marks = {}
 936    parsed_refs = {}
 937    marks = None
 938    parsed_tags = {}
 939    filenodes = {}
 940
 941    repo = get_repo(url, alias)
 942    prefix = 'refs/hg/%s' % alias
 943
 944    if not is_tmp:
 945        fix_path(alias, peer or repo, url)
 946
 947    if not os.path.exists(dirname):
 948        os.makedirs(dirname)
 949
 950    marks_path = os.path.join(dirname, 'marks-hg')
 951    marks = Marks(marks_path)
 952
 953    if sys.platform == 'win32':
 954        import msvcrt
 955        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
 956
 957    parser = Parser(repo)
 958    for line in parser:
 959        if parser.check('capabilities'):
 960            do_capabilities(parser)
 961        elif parser.check('list'):
 962            do_list(parser)
 963        elif parser.check('import'):
 964            do_import(parser)
 965        elif parser.check('export'):
 966            do_export(parser)
 967        else:
 968            die('unhandled command: %s' % line)
 969        sys.stdout.flush()
 970
 971def bye():
 972    if not marks:
 973        return
 974    if not is_tmp:
 975        marks.store()
 976    else:
 977        shutil.rmtree(dirname)
 978
 979atexit.register(bye)
 980sys.exit(main(sys.argv))