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