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