contrib / remote-helpers / git-remote-hgon commit remote-bzr: delay blob fetching until the very end (1816620)
   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, hashlib
  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    revs = xrange(tip, head.rev() + 1)
 377    count = 0
 378
 379    revs = [rev for rev in revs if not marks.is_marked(rev)]
 380
 381    for rev in revs:
 382
 383        c = repo[rev]
 384        (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())
 385        rev_branch = extra['branch']
 386
 387        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
 394        parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
 395
 396        if len(parents) == 0:
 397            modified = c.manifest().keys()
 398            removed = []
 399        else:
 400            modified, removed = get_filechanges(repo, c, parents[0])
 401
 402        desc += '\n'
 403
 404        if mode == 'hg':
 405            extra_msg = ''
 406
 407            if rev_branch != 'default':
 408                extra_msg += 'branch : %s\n' % rev_branch
 409
 410            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
 418            for e in renames:
 419                extra_msg += "rename : %s => %s\n" % e
 420
 421            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
 427            if extra_msg:
 428                desc += '\n--HG--\n' + extra_msg
 429
 430        if len(parents) == 0 and rev:
 431            print 'reset %s/%s' % (prefix, ename)
 432
 433        modified_final = export_files(c.filectx(f) for f in modified)
 434
 435        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
 442        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
 447        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
 453        count += 1
 454        if (count % 100 == 0):
 455            print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
 456            print "#############################################################"
 457
 458    # make sure the ref is updated
 459    print "reset %s/%s" % (prefix, ename)
 460    print "from :%u" % rev_to_mark(rev)
 461    print
 462
 463    marks.set_tip(ename, rev)
 464
 465def export_tag(repo, tag):
 466    export_ref(repo, tag, 'tags', repo[hgref(tag)])
 467
 468def export_bookmark(repo, bmark):
 469    head = bmarks[hgref(bmark)]
 470    export_ref(repo, bmark, 'bookmarks', head)
 471
 472def export_branch(repo, branch):
 473    tip = get_branch_tip(repo, branch)
 474    head = repo[tip]
 475    export_ref(repo, branch, 'branches', head)
 476
 477def export_head(repo):
 478    global g_head
 479    export_ref(repo, g_head[0], 'bookmarks', g_head[1])
 480
 481def do_capabilities(parser):
 482    global prefix, dirname
 483
 484    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
 490    path = os.path.join(dirname, 'marks-git')
 491
 492    if os.path.exists(path):
 493        print "*import-marks %s" % path
 494    print "*export-marks %s" % path
 495
 496    print
 497
 498def 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]
 504
 505def get_branch_tip(repo, branch):
 506    global branches
 507
 508    heads = branches.get(hgref(branch), None)
 509    if not heads:
 510        return None
 511
 512    # 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
 517    return heads[0]
 518
 519def list_head(repo, cur):
 520    global g_head, bmarks
 521
 522    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
 537    head = gitref(head)
 538    print "@refs/heads/%s HEAD" % head
 539    g_head = (head, node)
 540
 541def do_list(parser):
 542    global branches, bmarks, mode, track_branches
 543
 544    repo = parser.repo
 545    for bmark, node in bookmarks.listbookmarks(repo).iteritems():
 546        bmarks[bmark] = repo[node]
 547
 548    cur = repo.dirstate.branch()
 549
 550    list_head(repo, cur)
 551
 552    if track_branches:
 553        for branch in repo.branchmap():
 554            heads = repo.branchheads(branch)
 555            if len(heads):
 556                branches[branch] = heads
 557
 558        for branch in branches:
 559            print "? refs/heads/branches/%s" % gitref(branch)
 560
 561    for bmark in bmarks:
 562        print "? refs/heads/%s" % gitref(bmark)
 563
 564    for tag, node in repo.tagslist():
 565        if tag == 'tip':
 566            continue
 567        print "? refs/tags/%s" % gitref(tag)
 568
 569    print
 570
 571def do_import(parser):
 572    repo = parser.repo
 573
 574    path = os.path.join(dirname, 'marks-git')
 575
 576    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
 582    tmp = encoding.encoding
 583    encoding.encoding = 'utf-8'
 584
 585    # lets get all the import lines
 586    while parser.check('import'):
 587        ref = parser[1]
 588
 589        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
 601        parser.next()
 602
 603    encoding.encoding = tmp
 604
 605    print 'done'
 606
 607def parse_blob(parser):
 608    global blob_marks
 609
 610    parser.next()
 611    mark = parser.get_mark()
 612    parser.next()
 613    data = parser.get_data()
 614    blob_marks[mark] = data
 615    parser.next()
 616
 617def 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
 624
 625def parse_commit(parser):
 626    global marks, blob_marks, parsed_refs
 627    global mode
 628
 629    from_mark = merge_mark = None
 630
 631    ref = parser[1]
 632    parser.next()
 633
 634    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
 651    # fast-export adds an extra newline
 652    if data[-1] == '\n':
 653        data = data[:-1]
 654
 655    files = {}
 656
 657    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
 669    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
 681    repo = parser.repo
 682
 683    user, date, tz = author
 684    extra = {}
 685
 686    if committer != author:
 687        extra['committer'] = "%s %u %u" % committer
 688
 689    if from_mark:
 690        p1 = repo.changelog.node(mark_to_rev(from_mark))
 691    else:
 692        p1 = '\0' * 20
 693
 694    if merge_mark:
 695        p2 = repo.changelog.node(mark_to_rev(merge_mark))
 696    else:
 697        p2 = '\0' * 20
 698
 699    #
 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
 706    # 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
 711    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
 726    ctx = context.memctx(repo, (p1, p2), data,
 727            files.keys(), getfilectx,
 728            user, (date, tz), extra)
 729
 730    tmp = encoding.encoding
 731    encoding.encoding = 'utf-8'
 732
 733    node = repo.commitctx(ctx)
 734
 735    encoding.encoding = tmp
 736
 737    rev = repo[node].rev()
 738
 739    parsed_refs[ref] = node
 740    marks.new_mark(rev, commit_mark)
 741
 742def parse_reset(parser):
 743    global parsed_refs
 744
 745    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
 756    node = parser.repo.changelog.node(mark_to_rev(from_mark))
 757    parsed_refs[ref] = node
 758
 759def 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
 769    parsed_tags[name] = (tagger, data)
 770
 771def write_tag(repo, tag, node, msg, author):
 772    branch = repo[node].branch()
 773    tip = branch_tip(repo, branch)
 774    tip = repo[tip]
 775
 776    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
 785    p1 = tip.hex()
 786    p2 = '\0' * 20
 787    if not author:
 788        author = (None, 0, 0)
 789    user, date, tz = author
 790
 791    ctx = context.memctx(repo, (p1, p2), msg,
 792            ['.hgtags'], getfilectx,
 793            user, (date, tz), {'branch' : branch})
 794
 795    tmp = encoding.encoding
 796    encoding.encoding = 'utf-8'
 797
 798    tagnode = repo.commitctx(ctx)
 799
 800    encoding.encoding = tmp
 801
 802    return tagnode
 803
 804def do_export(parser):
 805    global parsed_refs, bmarks, peer
 806
 807    p_bmarks = []
 808
 809    parser.next()
 810
 811    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
 825    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
 853    if peer:
 854        parser.repo.push(peer, force=force_push)
 855
 856    # handle bookmarks
 857    for bmark, node in p_bmarks:
 858        ref = 'refs/heads/' + bmark
 859        new = hghex(node)
 860
 861        if bmark in bmarks:
 862            old = bmarks[bmark].hex()
 863        else:
 864            old = ''
 865
 866        if old == new:
 867            continue
 868
 869        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
 879        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
 886        print "ok %s" % ref
 887
 888    print
 889
 890def 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)
 897
 898def 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
 906    alias = args[1]
 907    url = args[2]
 908    peer = None
 909
 910    hg_git_compat = False
 911    track_branches = True
 912    force_push = True
 913
 914    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
 925    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
 934    if alias[4:] == url:
 935        is_tmp = True
 936        alias = hashlib.sha1(alias).hexdigest()
 937    else:
 938        is_tmp = False
 939
 940    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
 950    repo = get_repo(url, alias)
 951    prefix = 'refs/hg/%s' % alias
 952
 953    if not is_tmp:
 954        fix_path(alias, peer or repo, url)
 955
 956    if not os.path.exists(dirname):
 957        os.makedirs(dirname)
 958
 959    marks_path = os.path.join(dirname, 'marks-hg')
 960    marks = Marks(marks_path)
 961
 962    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()
 975
 976def bye():
 977    if not marks:
 978        return
 979    if not is_tmp:
 980        marks.store()
 981    else:
 982        shutil.rmtree(dirname)
 983
 984atexit.register(bye)
 985sys.exit(main(sys.argv))