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