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