1#!/usr/bin/env python
   2#
   3# Copyright (c) 2012 Felipe Contreras
   4#
   5# Inspired by Rocco Rutte's hg-fast-export
   7# 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/".
  14from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions
  16import re
  18import sys
  19import os
  20import json
  21import shutil
  22import subprocess
  23import urllib
  24import atexit
  25import urlparse, hashlib
  26#
  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#
  51NAME_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+)')
  57def die(msg, *args):
  59    sys.stderr.write('ERROR: %s\n' % (msg % args))
  60    sys.exit(1)
  61def warn(msg, *args):
  63    sys.stderr.write('WARNING: %s\n' % (msg % args))
  64def gitmode(flags):
  66    return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
  67def gittz(tz):
  69    return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
  70def hgmode(mode):
  72    m = { '100755': 'x', '120000': 'l' }
  73    return m.get(mode, '')
  74def hghex(node):
  76    return hg.node.hex(node)
  77def hgref(ref):
  79    return ref.replace('___', ' ')
  80def gitref(ref):
  82    return ref.replace(' ', '___')
  83def get_config(config):
  85    cmd = ['git', 'config', '--get', config]
  86    process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
  87    output, _ = process.communicate()
  88    return output
  89def get_config_bool(config, default=False):
  91    value = get_config(config).rstrip('\n')
  92    if value == "true":
  93        return True
  94    elif value == "false":
  95        return False
  96    else:
  97        return default
  98class Marks:
 100    def __init__(self, path):
 102        self.path = path
 103        self.tips = {}
 104        self.marks = {}
 105        self.rev_marks = {}
 106        self.last_mark = 0
 107        self.load()
 109    def load(self):
 111        if not os.path.exists(self.path):
 112            return
 113        tmp = json.load(open(self.path))
 115        self.tips = tmp['tips']
 117        self.marks = tmp['marks']
 118        self.last_mark = tmp['last-mark']
 119        for rev, mark in self.marks.iteritems():
 121            self.rev_marks[mark] = int(rev)
 122    def dict(self):
 124        return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
 125    def store(self):
 127        json.dump(self.dict(), open(self.path, 'w'))
 128    def __str__(self):
 130        return str(self.dict())
 131    def from_rev(self, rev):
 133        return self.marks[str(rev)]
 134    def to_rev(self, mark):
 136        return self.rev_marks[mark]
 137    def next_mark(self):
 139        self.last_mark += 1
 140        return self.last_mark
 141    def get_mark(self, rev):
 143        self.last_mark += 1
 144        self.marks[str(rev)] = self.last_mark
 145        return self.last_mark
 146    def new_mark(self, rev, mark):
 148        self.marks[str(rev)] = mark
 149        self.rev_marks[mark] = rev
 150        self.last_mark = mark
 151    def is_marked(self, rev):
 153        return str(rev) in self.marks
 154    def get_tip(self, branch):
 156        return self.tips.get(branch, 0)
 157    def set_tip(self, branch, tip):
 159        self.tips[branch] = tip
 160class Parser:
 162    def __init__(self, repo):
 164        self.repo = repo
 165        self.line = self.get_line()
 166    def get_line(self):
 168        return sys.stdin.readline().strip()
 169    def __getitem__(self, i):
 171        return self.line.split()[i]
 172    def check(self, word):
 174        return self.line.startswith(word)
 175    def each_block(self, separator):
 177        while self.line != separator:
 178            yield self.line
 179            self.line = self.get_line()
 180    def __iter__(self):
 182        return self.each_block('')
 183    def next(self):
 185        self.line = self.get_line()
 186        if self.line == 'done':
 187            self.line = None
 188    def get_mark(self):
 190        i = self.line.index(':') + 1
 191        return int(self.line[i:])
 192    def get_data(self):
 194        if not self.check('data'):
 195            return None
 196        i = self.line.index(' ') + 1
 197        size = int(self.line[i:])
 198        return sys.stdin.read(size)
 199    def get_author(self):
 201        global bad_mail
 202        ex = None
 204        m = RAW_AUTHOR_RE.match(self.line)
 205        if not m:
 206            return None
 207        _, name, email, date, tz = m.groups()
 208        if name and 'ext:' in name:
 209            m = re.match('^(.+?) ext:\((.+)\)$', name)
 210            if m:
 211                name = m.group(1)
 212                ex = urllib.unquote(m.group(2))
 213        if email != bad_mail:
 215            if name:
 216                user = '%s <%s>' % (name, email)
 217            else:
 218                user = '<%s>' % (email)
 219        else:
 220            user = name
 221        if ex:
 223            user += ex
 224        tz = int(tz)
 226        tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
 227        return (user, int(date), -tz)
 228def fix_file_path(path):
 230    if not os.path.isabs(path):
 231        return path
 232    return os.path.relpath(path, '/')
 233def export_files(files):
 235    global marks, filenodes
 236    final = []
 238    for f in files:
 239        fid = node.hex(f.filenode())
 240        if fid in filenodes:
 242            mark = filenodes[fid]
 243        else:
 244            mark = marks.next_mark()
 245            filenodes[fid] = mark
 246            d = f.data()
 247            print "blob"
 249            print "mark :%u" % mark
 250            print "data %d" % len(d)
 251            print d
 252        path = fix_file_path(f.path())
 254        final.append((gitmode(f.flags()), mark, path))
 255    return final
 257def get_filechanges(repo, ctx, parent):
 259    modified = set()
 260    added = set()
 261    removed = set()
 262    # load earliest manifest first for caching reasons
 264    prev = repo[parent].manifest().copy()
 265    cur = ctx.manifest()
 266    for fn in cur:
 268        if fn in prev:
 269            if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
 270                modified.add(fn)
 271            del prev[fn]
 272        else:
 273            added.add(fn)
 274    removed |= set(prev.keys())
 275    return added | modified, removed
 277def fixup_user_git(user):
 279    name = mail = None
 280    user = user.replace('"', '')
 281    m = AUTHOR_RE.match(user)
 282    if m:
 283        name = m.group(1)
 284        mail = m.group(2).strip()
 285    else:
 286        m = EMAIL_RE.match(user)
 287        if m:
 288            name = m.group(1)
 289            mail = m.group(2)
 290        else:
 291            m = NAME_RE.match(user)
 292            if m:
 293                name = m.group(1).strip()
 294    return (name, mail)
 295def fixup_user_hg(user):
 297    def sanitize(name):
 298        # stole this from hg-git
 299        return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
 300    m = AUTHOR_HG_RE.match(user)
 302    if m:
 303        name = sanitize(m.group(1))
 304        mail = sanitize(m.group(2))
 305        ex = m.group(3)
 306        if ex:
 307            name += ' ext:(' + urllib.quote(ex) + ')'
 308    else:
 309        name = sanitize(user)
 310        if '@' in user:
 311            mail = name
 312        else:
 313            mail = None
 314    return (name, mail)
 316def fixup_user(user):
 318    global mode, bad_mail
 319    if mode == 'git':
 321        name, mail = fixup_user_git(user)
 322    else:
 323        name, mail = fixup_user_hg(user)
 324    if not name:
 326        name = bad_name
 327    if not mail:
 328        mail = bad_mail
 329    return '%s <%s>' % (name, mail)
 331def get_repo(url, alias):
 333    global dirname, peer
 334    myui = ui.ui()
 336    myui.setconfig('ui', 'interactive', 'off')
 337    myui.fout = sys.stderr
 338    if get_config_bool('remote-hg.insecure'):
 340        myui.setconfig('web', 'cacerts', '')
 341    try:
 343        mod = extensions.load(myui, 'hgext.schemes', None)
 344        mod.extsetup(myui)
 345    except ImportError:
 346        pass
 347    if hg.islocal(url):
 349        repo = hg.repository(myui, url)
 350    else:
 351        local_path = os.path.join(dirname, 'clone')
 352        if not os.path.exists(local_path):
 353            try:
 354                peer, dstpeer = hg.clone(myui, {}, url, local_path, update=True, pull=True)
 355            except:
 356                die('Repository error')
 357            repo = dstpeer.local()
 358        else:
 359            repo = hg.repository(myui, local_path)
 360            try:
 361                peer = hg.peer(myui, {}, url)
 362            except:
 363                die('Repository error')
 364            repo.pull(peer, heads=None, force=True)
 365    return repo
 367def rev_to_mark(rev):
 369    global marks
 370    return marks.from_rev(rev)
 371def mark_to_rev(mark):
 373    global marks
 374    return marks.to_rev(mark)
 375def export_ref(repo, name, kind, head):
 377    global prefix, marks, mode
 378    ename = '%s/%s' % (kind, name)
 380    tip = marks.get_tip(ename)
 381    revs = xrange(tip, head.rev() + 1)
 383    count = 0
 384    revs = [rev for rev in revs if not marks.is_marked(rev)]
 386    for rev in revs:
 388        c = repo[rev]
 390        (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())
 391        rev_branch = extra['branch']
 392        author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
 394        if 'committer' in extra:
 395            user, time, tz = extra['committer'].rsplit(' ', 2)
 396            committer = "%s %s %s" % (user, time, gittz(int(tz)))
 397        else:
 398            committer = author
 399        parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
 401        if len(parents) == 0:
 403            modified = c.manifest().keys()
 404            removed = []
 405        else:
 406            modified, removed = get_filechanges(repo, c, parents[0])
 407        desc += '\n'
 409        if mode == 'hg':
 411            extra_msg = ''
 412            if rev_branch != 'default':
 414                extra_msg += 'branch : %s\n' % rev_branch
 415            renames = []
 417            for f in c.files():
 418                if f not in c.manifest():
 419                    continue
 420                rename = c.filectx(f).renamed()
 421                if rename:
 422                    renames.append((rename[0], f))
 423            for e in renames:
 425                extra_msg += "rename : %s => %s\n" % e
 426            for key, value in extra.iteritems():
 428                if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
 429                    continue
 430                else:
 431                    extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
 432            if extra_msg:
 434                desc += '\n--HG--\n' + extra_msg
 435        if len(parents) == 0 and rev:
 437            print 'reset %s/%s' % (prefix, ename)
 438        modified_final = export_files(c.filectx(f) for f in modified)
 440        print "commit %s/%s" % (prefix, ename)
 442        print "mark :%d" % (marks.get_mark(rev))
 443        print "author %s" % (author)
 444        print "committer %s" % (committer)
 445        print "data %d" % (len(desc))
 446        print desc
 447        if len(parents) > 0:
 449            print "from :%s" % (rev_to_mark(parents[0]))
 450            if len(parents) > 1:
 451                print "merge :%s" % (rev_to_mark(parents[1]))
 452        for f in modified_final:
 454            print "M %s :%u %s" % f
 455        for f in removed:
 456            print "D %s" % (fix_file_path(f))
 457        print
 458        count += 1
 460        if (count % 100 == 0):
 461            print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
 462    # make sure the ref is updated
 464    print "reset %s/%s" % (prefix, ename)
 465    print "from :%u" % rev_to_mark(rev)
 466    print
 467    marks.set_tip(ename, rev)
 469def export_tag(repo, tag):
 471    export_ref(repo, tag, 'tags', repo[hgref(tag)])
 472def export_bookmark(repo, bmark):
 474    head = bmarks[hgref(bmark)]
 475    export_ref(repo, bmark, 'bookmarks', head)
 476def export_branch(repo, branch):
 478    tip = get_branch_tip(repo, branch)
 479    head = repo[tip]
 480    export_ref(repo, branch, 'branches', head)
 481def export_head(repo):
 483    global g_head
 484    export_ref(repo, g_head[0], 'bookmarks', g_head[1])
 485def do_capabilities(parser):
 487    global prefix, dirname
 488    print "import"
 490    print "export"
 491    print "refspec refs/heads/branches/*:%s/branches/*" % prefix
 492    print "refspec refs/heads/*:%s/bookmarks/*" % prefix
 493    print "refspec refs/tags/*:%s/tags/*" % prefix
 494    path = os.path.join(dirname, 'marks-git')
 496    if os.path.exists(path):
 498        print "*import-marks %s" % path
 499    print "*export-marks %s" % path
 500    print
 502def branch_tip(repo, branch):
 504    # older versions of mercurial don't have this
 505    if hasattr(repo, 'branchtip'):
 506        return repo.branchtip(branch)
 507    else:
 508        return repo.branchtags()[branch]
 509def get_branch_tip(repo, branch):
 511    global branches
 512    heads = branches.get(hgref(branch), None)
 514    if not heads:
 515        return None
 516    # verify there's only one head
 518    if (len(heads) > 1):
 519        warn("Branch '%s' has more than one head, consider merging" % branch)
 520        return branch_tip(repo, hgref(branch))
 521    return heads[0]
 523def list_head(repo, cur):
 525    global g_head, bmarks
 526    head = bookmarks.readcurrent(repo)
 528    if head:
 529        node = repo[head]
 530    else:
 531        # fake bookmark from current branch
 532        head = cur
 533        node = repo['.']
 534        if not node:
 535            node = repo['tip']
 536        if not node:
 537            return
 538        if head == 'default':
 539            head = 'master'
 540        bmarks[head] = node
 541    head = gitref(head)
 543    print "@refs/heads/%s HEAD" % head
 544    g_head = (head, node)
 545def do_list(parser):
 547    global branches, bmarks, track_branches
 548    repo = parser.repo
 550    for bmark, node in bookmarks.listbookmarks(repo).iteritems():
 551        bmarks[bmark] = repo[node]
 552    cur = repo.dirstate.branch()
 554    list_head(repo, cur)
 556    if track_branches:
 558        for branch in repo.branchmap():
 559            heads = repo.branchheads(branch)
 560            if len(heads):
 561                branches[branch] = heads
 562        for branch in branches:
 564            print "? refs/heads/branches/%s" % gitref(branch)
 565    for bmark in bmarks:
 567        print "? refs/heads/%s" % gitref(bmark)
 568    for tag, node in repo.tagslist():
 570        if tag == 'tip':
 571            continue
 572        print "? refs/tags/%s" % gitref(tag)
 573    print
 575def do_import(parser):
 577    repo = parser.repo
 578    path = os.path.join(dirname, 'marks-git')
 580    print "feature done"
 582    if os.path.exists(path):
 583        print "feature import-marks=%s" % path
 584    print "feature export-marks=%s" % path
 585    sys.stdout.flush()
 586    tmp = encoding.encoding
 588    encoding.encoding = 'utf-8'
 589    # lets get all the import lines
 591    while parser.check('import'):
 592        ref = parser[1]
 593        if (ref == 'HEAD'):
 595            export_head(repo)
 596        elif ref.startswith('refs/heads/branches/'):
 597            branch = ref[len('refs/heads/branches/'):]
 598            export_branch(repo, branch)
 599        elif ref.startswith('refs/heads/'):
 600            bmark = ref[len('refs/heads/'):]
 601            export_bookmark(repo, bmark)
 602        elif ref.startswith('refs/tags/'):
 603            tag = ref[len('refs/tags/'):]
 604            export_tag(repo, tag)
 605        parser.next()
 607    encoding.encoding = tmp
 609    print 'done'
 611def parse_blob(parser):
 613    global blob_marks
 614    parser.next()
 616    mark = parser.get_mark()
 617    parser.next()
 618    data = parser.get_data()
 619    blob_marks[mark] = data
 620    parser.next()
 621def get_merge_files(repo, p1, p2, files):
 623    for e in repo[p1].files():
 624        if e not in files:
 625            if e not in repo[p1].manifest():
 626                continue
 627            f = { 'ctx' : repo[p1][e] }
 628            files[e] = f
 629def parse_commit(parser):
 631    global marks, blob_marks, parsed_refs
 632    global mode
 633    from_mark = merge_mark = None
 635    ref = parser[1]
 637    parser.next()
 638    commit_mark = parser.get_mark()
 640    parser.next()
 641    author = parser.get_author()
 642    parser.next()
 643    committer = parser.get_author()
 644    parser.next()
 645    data = parser.get_data()
 646    parser.next()
 647    if parser.check('from'):
 648        from_mark = parser.get_mark()
 649        parser.next()
 650    if parser.check('merge'):
 651        merge_mark = parser.get_mark()
 652        parser.next()
 653        if parser.check('merge'):
 654            die('octopus merges are not supported yet')
 655    # fast-export adds an extra newline
 657    if data[-1] == '\n':
 658        data = data[:-1]
 659    files = {}
 661    for line in parser:
 663        if parser.check('M'):
 664            t, m, mark_ref, path = line.split(' ', 3)
 665            mark = int(mark_ref[1:])
 666            f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
 667        elif parser.check('D'):
 668            t, path = line.split(' ', 1)
 669            f = { 'deleted' : True }
 670        else:
 671            die('Unknown file command: %s' % line)
 672        files[path] = f
 673    def getfilectx(repo, memctx, f):
 675        of = files[f]
 676        if 'deleted' in of:
 677            raise IOError
 678        if 'ctx' in of:
 679            return of['ctx']
 680        is_exec = of['mode'] == 'x'
 681        is_link = of['mode'] == 'l'
 682        rename = of.get('rename', None)
 683        return context.memfilectx(f, of['data'],
 684                is_link, is_exec, rename)
 685    repo = parser.repo
 687    user, date, tz = author
 689    extra = {}
 690    if committer != author:
 692        extra['committer'] = "%s %u %u" % committer
 693    if from_mark:
 695        p1 = repo.changelog.node(mark_to_rev(from_mark))
 696    else:
 697        p1 = '\0' * 20
 698    if merge_mark:
 700        p2 = repo.changelog.node(mark_to_rev(merge_mark))
 701    else:
 702        p2 = '\0' * 20
 703    #
 705    # If files changed from any of the parents, hg wants to know, but in git if
 706    # nothing changed from the first parent, nothing changed.
 707    #
 708    if merge_mark:
 709        get_merge_files(repo, p1, p2, files)
 710    # Check if the ref is supposed to be a named branch
 712    if ref.startswith('refs/heads/branches/'):
 713        branch = ref[len('refs/heads/branches/'):]
 714        extra['branch'] = hgref(branch)
 715    if mode == 'hg':
 717        i = data.find('\n--HG--\n')
 718        if i >= 0:
 719            tmp = data[i + len('\n--HG--\n'):].strip()
 720            for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
 721                if k == 'rename':
 722                    old, new = v.split(' => ', 1)
 723                    files[new]['rename'] = old
 724                elif k == 'branch':
 725                    extra[k] = v
 726                elif k == 'extra':
 727                    ek, ev = v.split(' : ', 1)
 728                    extra[ek] = urllib.unquote(ev)
 729            data = data[:i]
 730    ctx = context.memctx(repo, (p1, p2), data,
 732            files.keys(), getfilectx,
 733            user, (date, tz), extra)
 734    tmp = encoding.encoding
 736    encoding.encoding = 'utf-8'
 737    node = repo.commitctx(ctx)
 739    encoding.encoding = tmp
 741    rev = repo[node].rev()
 743    parsed_refs[ref] = node
 745    marks.new_mark(rev, commit_mark)
 746def parse_reset(parser):
 748    global parsed_refs
 749    ref = parser[1]
 751    parser.next()
 752    # ugh
 753    if parser.check('commit'):
 754        parse_commit(parser)
 755        return
 756    if not parser.check('from'):
 757        return
 758    from_mark = parser.get_mark()
 759    parser.next()
 760    node = parser.repo.changelog.node(mark_to_rev(from_mark))
 762    parsed_refs[ref] = node
 763def parse_tag(parser):
 765    name = parser[1]
 766    parser.next()
 767    from_mark = parser.get_mark()
 768    parser.next()
 769    tagger = parser.get_author()
 770    parser.next()
 771    data = parser.get_data()
 772    parser.next()
 773    parsed_tags[name] = (tagger, data)
 775def write_tag(repo, tag, node, msg, author):
 777    branch = repo[node].branch()
 778    tip = branch_tip(repo, branch)
 779    tip = repo[tip]
 780    def getfilectx(repo, memctx, f):
 782        try:
 783            fctx = tip.filectx(f)
 784            data = fctx.data()
 785        except error.ManifestLookupError:
 786            data = ""
 787        content = data + "%s %s\n" % (hghex(node), tag)
 788        return context.memfilectx(f, content, False, False, None)
 789    p1 = tip.hex()
 791    p2 = '\0' * 20
 792    if not author:
 793        author = (None, 0, 0)
 794    user, date, tz = author
 795    ctx = context.memctx(repo, (p1, p2), msg,
 797            ['.hgtags'], getfilectx,
 798            user, (date, tz), {'branch' : branch})
 799    tmp = encoding.encoding
 801    encoding.encoding = 'utf-8'
 802    tagnode = repo.commitctx(ctx)
 804    encoding.encoding = tmp
 806    return tagnode
 808def do_export(parser):
 810    global parsed_refs, bmarks, peer
 811    p_bmarks = []
 813    parser.next()
 815    for line in parser.each_block('done'):
 817        if parser.check('blob'):
 818            parse_blob(parser)
 819        elif parser.check('commit'):
 820            parse_commit(parser)
 821        elif parser.check('reset'):
 822            parse_reset(parser)
 823        elif parser.check('tag'):
 824            parse_tag(parser)
 825        elif parser.check('feature'):
 826            pass
 827        else:
 828            die('unhandled export command: %s' % line)
 829    for ref, node in parsed_refs.iteritems():
 831        if ref.startswith('refs/heads/branches'):
 832            branch = ref[len('refs/heads/branches/'):]
 833            if branch in branches and node in branches[branch]:
 834                # up to date
 835                continue
 836            print "ok %s" % ref
 837        elif ref.startswith('refs/heads/'):
 838            bmark = ref[len('refs/heads/'):]
 839            p_bmarks.append((bmark, node))
 840            continue
 841        elif ref.startswith('refs/tags/'):
 842            tag = ref[len('refs/tags/'):]
 843            tag = hgref(tag)
 844            author, msg = parsed_tags.get(tag, (None, None))
 845            if mode == 'git':
 846                if not msg:
 847                    msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6]));
 848                write_tag(parser.repo, tag, node, msg, author)
 849            else:
 850                fp = parser.repo.opener('localtags', 'a')
 851                fp.write('%s %s\n' % (hghex(node), tag))
 852                fp.close()
 853            print "ok %s" % ref
 854        else:
 855            # transport-helper/fast-export bugs
 856            continue
 857    if peer:
 859        parser.repo.push(peer, force=force_push, newbranch=True)
 860    # handle bookmarks
 862    for bmark, node in p_bmarks:
 863        ref = 'refs/heads/' + bmark
 864        new = hghex(node)
 865        if bmark in bmarks:
 867            old = bmarks[bmark].hex()
 868        else:
 869            old = ''
 870        if old == new:
 872            continue
 873        if bmark == 'master' and 'master' not in parser.repo._bookmarks:
 875            # fake bookmark
 876            print "ok %s" % ref
 877            continue
 878        elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
 879            # updated locally
 880            pass
 881        else:
 882            print "error %s" % ref
 883            continue
 884        if peer:
 886            rb = peer.listkeys('bookmarks')
 887            old = rb.get(bmark, '')
 888            if not peer.pushkey('bookmarks', bmark, old, new):
 889                print "error %s" % ref
 890                continue
 891        print "ok %s" % ref
 893    print
 895def fix_path(alias, repo, orig_url):
 897    url = urlparse.urlparse(orig_url, 'file')
 898    if url.scheme != 'file' or os.path.isabs(url.path):
 899        return
 900    abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
 901    cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
 902    subprocess.call(cmd)
 903def main(args):
 905    global prefix, dirname, branches, bmarks
 906    global marks, blob_marks, parsed_refs
 907    global peer, mode, bad_mail, bad_name
 908    global track_branches, force_push, is_tmp
 909    global parsed_tags
 910    global filenodes
 911    alias = args[1]
 913    url = args[2]
 914    peer = None
 915    hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
 917    track_branches = get_config_bool('remote-hg.track-branches', True)
 918    force_push = get_config_bool('remote-hg.force-push')
 919    if hg_git_compat:
 921        mode = 'hg'
 922        bad_mail = 'none@none'
 923        bad_name = ''
 924    else:
 925        mode = 'git'
 926        bad_mail = 'unknown'
 927        bad_name = 'Unknown'
 928    if alias[4:] == url:
 930        is_tmp = True
 931        alias = hashlib.sha1(alias).hexdigest()
 932    else:
 933        is_tmp = False
 934    gitdir = os.environ['GIT_DIR']
 936    dirname = os.path.join(gitdir, 'hg', alias)
 937    branches = {}
 938    bmarks = {}
 939    blob_marks = {}
 940    parsed_refs = {}
 941    marks = None
 942    parsed_tags = {}
 943    filenodes = {}
 944    repo = get_repo(url, alias)
 946    prefix = 'refs/hg/%s' % alias
 947    if not is_tmp:
 949        fix_path(alias, peer or repo, url)
 950    if not os.path.exists(dirname):
 952        os.makedirs(dirname)
 953    marks_path = os.path.join(dirname, 'marks-hg')
 955    marks = Marks(marks_path)
 956    if sys.platform == 'win32':
 958        import msvcrt
 959        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
 960    parser = Parser(repo)
 962    for line in parser:
 963        if parser.check('capabilities'):
 964            do_capabilities(parser)
 965        elif parser.check('list'):
 966            do_list(parser)
 967        elif parser.check('import'):
 968            do_import(parser)
 969        elif parser.check('export'):
 970            do_export(parser)
 971        else:
 972            die('unhandled command: %s' % line)
 973        sys.stdout.flush()
 974def bye():
 976    if not marks:
 977        return
 978    if not is_tmp:
 979        marks.store()
 980    else:
 981        shutil.rmtree(dirname)
 982atexit.register(bye)
 984sys.exit(main(sys.argv))