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