75079762bddc176f4cada167c3f8a4ed105213b7
   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        if not os.path.exists(dirname):
 347            os.makedirs(dirname)
 348    else:
 349        shared_path = os.path.join(gitdir, 'hg')
 350        if not os.path.exists(shared_path):
 351            try:
 352                hg.clone(myui, {}, url, shared_path, update=False, pull=True)
 353            except:
 354                die('Repository error')
 355
 356        if not os.path.exists(dirname):
 357            os.makedirs(dirname)
 358
 359        local_path = os.path.join(dirname, 'clone')
 360        if not os.path.exists(local_path):
 361            hg.share(myui, shared_path, local_path, update=False)
 362
 363        repo = hg.repository(myui, local_path)
 364        try:
 365            peer = hg.peer(myui, {}, url)
 366        except:
 367            die('Repository error')
 368        repo.pull(peer, heads=None, force=True)
 369
 370    return repo
 371
 372def rev_to_mark(rev):
 373    global marks
 374    return marks.from_rev(rev)
 375
 376def mark_to_rev(mark):
 377    global marks
 378    return marks.to_rev(mark)
 379
 380def export_ref(repo, name, kind, head):
 381    global prefix, marks, mode
 382
 383    ename = '%s/%s' % (kind, name)
 384    tip = marks.get_tip(ename)
 385
 386    revs = xrange(tip, head.rev() + 1)
 387    count = 0
 388
 389    revs = [rev for rev in revs if not marks.is_marked(rev)]
 390
 391    for rev in revs:
 392
 393        c = repo[rev]
 394        (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())
 395        rev_branch = extra['branch']
 396
 397        author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
 398        if 'committer' in extra:
 399            user, time, tz = extra['committer'].rsplit(' ', 2)
 400            committer = "%s %s %s" % (user, time, gittz(int(tz)))
 401        else:
 402            committer = author
 403
 404        parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
 405
 406        if len(parents) == 0:
 407            modified = c.manifest().keys()
 408            removed = []
 409        else:
 410            modified, removed = get_filechanges(repo, c, parents[0])
 411
 412        desc += '\n'
 413
 414        if mode == 'hg':
 415            extra_msg = ''
 416
 417            if rev_branch != 'default':
 418                extra_msg += 'branch : %s\n' % rev_branch
 419
 420            renames = []
 421            for f in c.files():
 422                if f not in c.manifest():
 423                    continue
 424                rename = c.filectx(f).renamed()
 425                if rename:
 426                    renames.append((rename[0], f))
 427
 428            for e in renames:
 429                extra_msg += "rename : %s => %s\n" % e
 430
 431            for key, value in extra.iteritems():
 432                if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
 433                    continue
 434                else:
 435                    extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
 436
 437            if extra_msg:
 438                desc += '\n--HG--\n' + extra_msg
 439
 440        if len(parents) == 0 and rev:
 441            print 'reset %s/%s' % (prefix, ename)
 442
 443        modified_final = export_files(c.filectx(f) for f in modified)
 444
 445        print "commit %s/%s" % (prefix, ename)
 446        print "mark :%d" % (marks.get_mark(rev))
 447        print "author %s" % (author)
 448        print "committer %s" % (committer)
 449        print "data %d" % (len(desc))
 450        print desc
 451
 452        if len(parents) > 0:
 453            print "from :%s" % (rev_to_mark(parents[0]))
 454            if len(parents) > 1:
 455                print "merge :%s" % (rev_to_mark(parents[1]))
 456
 457        for f in modified_final:
 458            print "M %s :%u %s" % f
 459        for f in removed:
 460            print "D %s" % (fix_file_path(f))
 461        print
 462
 463        count += 1
 464        if (count % 100 == 0):
 465            print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
 466
 467    # make sure the ref is updated
 468    print "reset %s/%s" % (prefix, ename)
 469    print "from :%u" % rev_to_mark(rev)
 470    print
 471
 472    marks.set_tip(ename, rev)
 473
 474def export_tag(repo, tag):
 475    export_ref(repo, tag, 'tags', repo[hgref(tag)])
 476
 477def export_bookmark(repo, bmark):
 478    head = bmarks[hgref(bmark)]
 479    export_ref(repo, bmark, 'bookmarks', head)
 480
 481def export_branch(repo, branch):
 482    tip = get_branch_tip(repo, branch)
 483    head = repo[tip]
 484    export_ref(repo, branch, 'branches', head)
 485
 486def export_head(repo):
 487    global g_head
 488    export_ref(repo, g_head[0], 'bookmarks', g_head[1])
 489
 490def do_capabilities(parser):
 491    global prefix, dirname
 492
 493    print "import"
 494    print "export"
 495    print "refspec refs/heads/branches/*:%s/branches/*" % prefix
 496    print "refspec refs/heads/*:%s/bookmarks/*" % prefix
 497    print "refspec refs/tags/*:%s/tags/*" % prefix
 498
 499    path = os.path.join(dirname, 'marks-git')
 500
 501    if os.path.exists(path):
 502        print "*import-marks %s" % path
 503    print "*export-marks %s" % path
 504
 505    print
 506
 507def branch_tip(repo, branch):
 508    # older versions of mercurial don't have this
 509    if hasattr(repo, 'branchtip'):
 510        return repo.branchtip(branch)
 511    else:
 512        return repo.branchtags()[branch]
 513
 514def get_branch_tip(repo, branch):
 515    global branches
 516
 517    heads = branches.get(hgref(branch), None)
 518    if not heads:
 519        return None
 520
 521    # verify there's only one head
 522    if (len(heads) > 1):
 523        warn("Branch '%s' has more than one head, consider merging" % branch)
 524        return branch_tip(repo, hgref(branch))
 525
 526    return heads[0]
 527
 528def list_head(repo, cur):
 529    global g_head, bmarks
 530
 531    head = bookmarks.readcurrent(repo)
 532    if head:
 533        node = repo[head]
 534    else:
 535        # fake bookmark from current branch
 536        head = cur
 537        node = repo['.']
 538        if not node:
 539            node = repo['tip']
 540        if not node:
 541            return
 542        if head == 'default':
 543            head = 'master'
 544        bmarks[head] = node
 545
 546    head = gitref(head)
 547    print "@refs/heads/%s HEAD" % head
 548    g_head = (head, node)
 549
 550def do_list(parser):
 551    global branches, bmarks, track_branches
 552
 553    repo = parser.repo
 554    for bmark, node in bookmarks.listbookmarks(repo).iteritems():
 555        bmarks[bmark] = repo[node]
 556
 557    cur = repo.dirstate.branch()
 558
 559    list_head(repo, cur)
 560
 561    if track_branches:
 562        for branch in repo.branchmap():
 563            heads = repo.branchheads(branch)
 564            if len(heads):
 565                branches[branch] = heads
 566
 567        for branch in branches:
 568            print "? refs/heads/branches/%s" % gitref(branch)
 569
 570    for bmark in bmarks:
 571        print "? refs/heads/%s" % gitref(bmark)
 572
 573    for tag, node in repo.tagslist():
 574        if tag == 'tip':
 575            continue
 576        print "? refs/tags/%s" % gitref(tag)
 577
 578    print
 579
 580def do_import(parser):
 581    repo = parser.repo
 582
 583    path = os.path.join(dirname, 'marks-git')
 584
 585    print "feature done"
 586    if os.path.exists(path):
 587        print "feature import-marks=%s" % path
 588    print "feature export-marks=%s" % path
 589    sys.stdout.flush()
 590
 591    tmp = encoding.encoding
 592    encoding.encoding = 'utf-8'
 593
 594    # lets get all the import lines
 595    while parser.check('import'):
 596        ref = parser[1]
 597
 598        if (ref == 'HEAD'):
 599            export_head(repo)
 600        elif ref.startswith('refs/heads/branches/'):
 601            branch = ref[len('refs/heads/branches/'):]
 602            export_branch(repo, branch)
 603        elif ref.startswith('refs/heads/'):
 604            bmark = ref[len('refs/heads/'):]
 605            export_bookmark(repo, bmark)
 606        elif ref.startswith('refs/tags/'):
 607            tag = ref[len('refs/tags/'):]
 608            export_tag(repo, tag)
 609
 610        parser.next()
 611
 612    encoding.encoding = tmp
 613
 614    print 'done'
 615
 616def parse_blob(parser):
 617    global blob_marks
 618
 619    parser.next()
 620    mark = parser.get_mark()
 621    parser.next()
 622    data = parser.get_data()
 623    blob_marks[mark] = data
 624    parser.next()
 625
 626def get_merge_files(repo, p1, p2, files):
 627    for e in repo[p1].files():
 628        if e not in files:
 629            if e not in repo[p1].manifest():
 630                continue
 631            f = { 'ctx' : repo[p1][e] }
 632            files[e] = f
 633
 634def parse_commit(parser):
 635    global marks, blob_marks, parsed_refs
 636    global mode
 637
 638    from_mark = merge_mark = None
 639
 640    ref = parser[1]
 641    parser.next()
 642
 643    commit_mark = parser.get_mark()
 644    parser.next()
 645    author = parser.get_author()
 646    parser.next()
 647    committer = parser.get_author()
 648    parser.next()
 649    data = parser.get_data()
 650    parser.next()
 651    if parser.check('from'):
 652        from_mark = parser.get_mark()
 653        parser.next()
 654    if parser.check('merge'):
 655        merge_mark = parser.get_mark()
 656        parser.next()
 657        if parser.check('merge'):
 658            die('octopus merges are not supported yet')
 659
 660    # fast-export adds an extra newline
 661    if data[-1] == '\n':
 662        data = data[:-1]
 663
 664    files = {}
 665
 666    for line in parser:
 667        if parser.check('M'):
 668            t, m, mark_ref, path = line.split(' ', 3)
 669            mark = int(mark_ref[1:])
 670            f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
 671        elif parser.check('D'):
 672            t, path = line.split(' ', 1)
 673            f = { 'deleted' : True }
 674        else:
 675            die('Unknown file command: %s' % line)
 676        files[path] = f
 677
 678    def getfilectx(repo, memctx, f):
 679        of = files[f]
 680        if 'deleted' in of:
 681            raise IOError
 682        if 'ctx' in of:
 683            return of['ctx']
 684        is_exec = of['mode'] == 'x'
 685        is_link = of['mode'] == 'l'
 686        rename = of.get('rename', None)
 687        return context.memfilectx(f, of['data'],
 688                is_link, is_exec, rename)
 689
 690    repo = parser.repo
 691
 692    user, date, tz = author
 693    extra = {}
 694
 695    if committer != author:
 696        extra['committer'] = "%s %u %u" % committer
 697
 698    if from_mark:
 699        p1 = repo.changelog.node(mark_to_rev(from_mark))
 700    else:
 701        p1 = '\0' * 20
 702
 703    if merge_mark:
 704        p2 = repo.changelog.node(mark_to_rev(merge_mark))
 705    else:
 706        p2 = '\0' * 20
 707
 708    #
 709    # If files changed from any of the parents, hg wants to know, but in git if
 710    # nothing changed from the first parent, nothing changed.
 711    #
 712    if merge_mark:
 713        get_merge_files(repo, p1, p2, files)
 714
 715    # Check if the ref is supposed to be a named branch
 716    if ref.startswith('refs/heads/branches/'):
 717        branch = ref[len('refs/heads/branches/'):]
 718        extra['branch'] = hgref(branch)
 719
 720    if mode == 'hg':
 721        i = data.find('\n--HG--\n')
 722        if i >= 0:
 723            tmp = data[i + len('\n--HG--\n'):].strip()
 724            for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
 725                if k == 'rename':
 726                    old, new = v.split(' => ', 1)
 727                    files[new]['rename'] = old
 728                elif k == 'branch':
 729                    extra[k] = v
 730                elif k == 'extra':
 731                    ek, ev = v.split(' : ', 1)
 732                    extra[ek] = urllib.unquote(ev)
 733            data = data[:i]
 734
 735    ctx = context.memctx(repo, (p1, p2), data,
 736            files.keys(), getfilectx,
 737            user, (date, tz), extra)
 738
 739    tmp = encoding.encoding
 740    encoding.encoding = 'utf-8'
 741
 742    node = repo.commitctx(ctx)
 743
 744    encoding.encoding = tmp
 745
 746    rev = repo[node].rev()
 747
 748    parsed_refs[ref] = node
 749    marks.new_mark(rev, commit_mark)
 750
 751def parse_reset(parser):
 752    global parsed_refs
 753
 754    ref = parser[1]
 755    parser.next()
 756    # ugh
 757    if parser.check('commit'):
 758        parse_commit(parser)
 759        return
 760    if not parser.check('from'):
 761        return
 762    from_mark = parser.get_mark()
 763    parser.next()
 764
 765    node = parser.repo.changelog.node(mark_to_rev(from_mark))
 766    parsed_refs[ref] = node
 767
 768def parse_tag(parser):
 769    name = parser[1]
 770    parser.next()
 771    from_mark = parser.get_mark()
 772    parser.next()
 773    tagger = parser.get_author()
 774    parser.next()
 775    data = parser.get_data()
 776    parser.next()
 777
 778    parsed_tags[name] = (tagger, data)
 779
 780def write_tag(repo, tag, node, msg, author):
 781    branch = repo[node].branch()
 782    tip = branch_tip(repo, branch)
 783    tip = repo[tip]
 784
 785    def getfilectx(repo, memctx, f):
 786        try:
 787            fctx = tip.filectx(f)
 788            data = fctx.data()
 789        except error.ManifestLookupError:
 790            data = ""
 791        content = data + "%s %s\n" % (hghex(node), tag)
 792        return context.memfilectx(f, content, False, False, None)
 793
 794    p1 = tip.hex()
 795    p2 = '\0' * 20
 796    if not author:
 797        author = (None, 0, 0)
 798    user, date, tz = author
 799
 800    ctx = context.memctx(repo, (p1, p2), msg,
 801            ['.hgtags'], getfilectx,
 802            user, (date, tz), {'branch' : branch})
 803
 804    tmp = encoding.encoding
 805    encoding.encoding = 'utf-8'
 806
 807    tagnode = repo.commitctx(ctx)
 808
 809    encoding.encoding = tmp
 810
 811    return tagnode
 812
 813def do_export(parser):
 814    global parsed_refs, bmarks, peer
 815
 816    p_bmarks = []
 817
 818    parser.next()
 819
 820    for line in parser.each_block('done'):
 821        if parser.check('blob'):
 822            parse_blob(parser)
 823        elif parser.check('commit'):
 824            parse_commit(parser)
 825        elif parser.check('reset'):
 826            parse_reset(parser)
 827        elif parser.check('tag'):
 828            parse_tag(parser)
 829        elif parser.check('feature'):
 830            pass
 831        else:
 832            die('unhandled export command: %s' % line)
 833
 834    for ref, node in parsed_refs.iteritems():
 835        if ref.startswith('refs/heads/branches'):
 836            branch = ref[len('refs/heads/branches/'):]
 837            if branch in branches and node in branches[branch]:
 838                # up to date
 839                continue
 840            print "ok %s" % ref
 841        elif ref.startswith('refs/heads/'):
 842            bmark = ref[len('refs/heads/'):]
 843            p_bmarks.append((bmark, node))
 844            continue
 845        elif ref.startswith('refs/tags/'):
 846            tag = ref[len('refs/tags/'):]
 847            tag = hgref(tag)
 848            author, msg = parsed_tags.get(tag, (None, None))
 849            if mode == 'git':
 850                if not msg:
 851                    msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6]));
 852                write_tag(parser.repo, tag, node, msg, author)
 853            else:
 854                fp = parser.repo.opener('localtags', 'a')
 855                fp.write('%s %s\n' % (hghex(node), tag))
 856                fp.close()
 857            print "ok %s" % ref
 858        else:
 859            # transport-helper/fast-export bugs
 860            continue
 861
 862    if peer:
 863        parser.repo.push(peer, force=force_push, newbranch=True)
 864        remote_bmarks = peer.listkeys('bookmarks')
 865
 866    # handle bookmarks
 867    for bmark, node in p_bmarks:
 868        ref = 'refs/heads/' + bmark
 869        new = hghex(node)
 870
 871        if bmark in bmarks:
 872            old = bmarks[bmark].hex()
 873        else:
 874            old = ''
 875
 876        if old == new:
 877            continue
 878
 879        if bmark == 'master' and 'master' not in parser.repo._bookmarks:
 880            # fake bookmark
 881            print "ok %s" % ref
 882            continue
 883        elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
 884            # updated locally
 885            pass
 886        else:
 887            print "error %s" % ref
 888            continue
 889
 890        if peer:
 891            old = remote_bmarks.get(bmark, '')
 892            if not peer.pushkey('bookmarks', bmark, old, new):
 893                print "error %s" % ref
 894                continue
 895
 896        print "ok %s" % ref
 897
 898    print
 899
 900def fix_path(alias, repo, orig_url):
 901    url = urlparse.urlparse(orig_url, 'file')
 902    if url.scheme != 'file' or os.path.isabs(url.path):
 903        return
 904    abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
 905    cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
 906    subprocess.call(cmd)
 907
 908def main(args):
 909    global prefix, gitdir, dirname, branches, bmarks
 910    global marks, blob_marks, parsed_refs
 911    global peer, mode, bad_mail, bad_name
 912    global track_branches, force_push, is_tmp
 913    global parsed_tags
 914    global filenodes
 915
 916    alias = args[1]
 917    url = args[2]
 918    peer = None
 919
 920    hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
 921    track_branches = get_config_bool('remote-hg.track-branches', True)
 922    force_push = get_config_bool('remote-hg.force-push')
 923
 924    if hg_git_compat:
 925        mode = 'hg'
 926        bad_mail = 'none@none'
 927        bad_name = ''
 928    else:
 929        mode = 'git'
 930        bad_mail = 'unknown'
 931        bad_name = 'Unknown'
 932
 933    if alias[4:] == url:
 934        is_tmp = True
 935        alias = hashlib.sha1(alias).hexdigest()
 936    else:
 937        is_tmp = False
 938
 939    gitdir = os.environ['GIT_DIR']
 940    dirname = os.path.join(gitdir, 'hg', alias)
 941    branches = {}
 942    bmarks = {}
 943    blob_marks = {}
 944    parsed_refs = {}
 945    marks = None
 946    parsed_tags = {}
 947    filenodes = {}
 948
 949    repo = get_repo(url, alias)
 950    prefix = 'refs/hg/%s' % alias
 951
 952    if not is_tmp:
 953        fix_path(alias, peer or repo, url)
 954
 955    marks_path = os.path.join(dirname, 'marks-hg')
 956    marks = Marks(marks_path)
 957
 958    if sys.platform == 'win32':
 959        import msvcrt
 960        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
 961
 962    parser = Parser(repo)
 963    for line in parser:
 964        if parser.check('capabilities'):
 965            do_capabilities(parser)
 966        elif parser.check('list'):
 967            do_list(parser)
 968        elif parser.check('import'):
 969            do_import(parser)
 970        elif parser.check('export'):
 971            do_export(parser)
 972        else:
 973            die('unhandled command: %s' % line)
 974        sys.stdout.flush()
 975
 976def bye():
 977    if not marks:
 978        return
 979    if not is_tmp:
 980        marks.store()
 981    else:
 982        shutil.rmtree(dirname)
 983
 984atexit.register(bye)
 985sys.exit(main(sys.argv))