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