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