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