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