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