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