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