2e4f7ca425fb8f684ff3ac57ab05f34ec6d2c365
   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(node):
  78    return hg.node.hex(node)
  79
  80def hgbin(node):
  81    return hg.node.bin(node)
  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 get_repo(url, alias):
 360    global dirname, peer
 361
 362    myui = ui.ui()
 363    myui.setconfig('ui', 'interactive', 'off')
 364    myui.fout = sys.stderr
 365
 366    if get_config_bool('remote-hg.insecure'):
 367        myui.setconfig('web', 'cacerts', '')
 368
 369    extensions.loadall(myui)
 370
 371    if hg.islocal(url):
 372        repo = hg.repository(myui, url)
 373        if not os.path.exists(dirname):
 374            os.makedirs(dirname)
 375    else:
 376        shared_path = os.path.join(gitdir, 'hg')
 377        if not os.path.exists(shared_path):
 378            try:
 379                hg.clone(myui, {}, url, shared_path, update=False, pull=True)
 380            except:
 381                die('Repository error')
 382
 383        if not os.path.exists(dirname):
 384            os.makedirs(dirname)
 385
 386        local_path = os.path.join(dirname, 'clone')
 387        if not os.path.exists(local_path):
 388            hg.share(myui, shared_path, local_path, update=False)
 389
 390        repo = hg.repository(myui, local_path)
 391        try:
 392            peer = hg.peer(myui, {}, url)
 393        except:
 394            die('Repository error')
 395        repo.pull(peer, heads=None, force=True)
 396
 397    return repo
 398
 399def rev_to_mark(rev):
 400    global marks
 401    return marks.from_rev(rev.hex())
 402
 403def mark_to_rev(mark):
 404    global marks
 405    return marks.to_rev(mark)
 406
 407def export_ref(repo, name, kind, head):
 408    global prefix, marks, mode
 409
 410    ename = '%s/%s' % (kind, name)
 411    tip = marks.get_tip(ename)
 412    if tip and tip in repo:
 413        tip = repo[tip].rev()
 414    else:
 415        tip = 0
 416
 417    revs = xrange(tip, head.rev() + 1)
 418    count = 0
 419
 420    for rev in revs:
 421
 422        c = repo[rev]
 423        node = c.node()
 424
 425        if marks.is_marked(c.hex()):
 426            count += 1
 427            continue
 428
 429        (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
 430        rev_branch = extra['branch']
 431
 432        author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
 433        if 'committer' in extra:
 434            user, time, tz = extra['committer'].rsplit(' ', 2)
 435            committer = "%s %s %s" % (user, time, gittz(int(tz)))
 436        else:
 437            committer = author
 438
 439        parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
 440
 441        if len(parents) == 0:
 442            modified = c.manifest().keys()
 443            removed = []
 444        else:
 445            modified, removed = get_filechanges(repo, c, parents[0])
 446
 447        desc += '\n'
 448
 449        if mode == 'hg':
 450            extra_msg = ''
 451
 452            if rev_branch != 'default':
 453                extra_msg += 'branch : %s\n' % rev_branch
 454
 455            renames = []
 456            for f in c.files():
 457                if f not in c.manifest():
 458                    continue
 459                rename = c.filectx(f).renamed()
 460                if rename:
 461                    renames.append((rename[0], f))
 462
 463            for e in renames:
 464                extra_msg += "rename : %s => %s\n" % e
 465
 466            for key, value in extra.iteritems():
 467                if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
 468                    continue
 469                else:
 470                    extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
 471
 472            if extra_msg:
 473                desc += '\n--HG--\n' + extra_msg
 474
 475        if len(parents) == 0 and rev:
 476            print 'reset %s/%s' % (prefix, ename)
 477
 478        modified_final = export_files(c.filectx(f) for f in modified)
 479
 480        print "commit %s/%s" % (prefix, ename)
 481        print "mark :%d" % (marks.get_mark(c.hex()))
 482        print "author %s" % (author)
 483        print "committer %s" % (committer)
 484        print "data %d" % (len(desc))
 485        print desc
 486
 487        if len(parents) > 0:
 488            print "from :%s" % (rev_to_mark(parents[0]))
 489            if len(parents) > 1:
 490                print "merge :%s" % (rev_to_mark(parents[1]))
 491
 492        for f in modified_final:
 493            print "M %s :%u %s" % f
 494        for f in removed:
 495            print "D %s" % (fix_file_path(f))
 496        print
 497
 498        count += 1
 499        if (count % 100 == 0):
 500            print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
 501
 502    # make sure the ref is updated
 503    print "reset %s/%s" % (prefix, ename)
 504    print "from :%u" % rev_to_mark(head)
 505    print
 506
 507    marks.set_tip(ename, head.hex())
 508
 509def export_tag(repo, tag):
 510    export_ref(repo, tag, 'tags', repo[hgref(tag)])
 511
 512def export_bookmark(repo, bmark):
 513    head = bmarks[hgref(bmark)]
 514    export_ref(repo, bmark, 'bookmarks', head)
 515
 516def export_branch(repo, branch):
 517    tip = get_branch_tip(repo, branch)
 518    head = repo[tip]
 519    export_ref(repo, branch, 'branches', head)
 520
 521def export_head(repo):
 522    global g_head
 523    export_ref(repo, g_head[0], 'bookmarks', g_head[1])
 524
 525def do_capabilities(parser):
 526    global prefix, dirname
 527
 528    print "import"
 529    print "export"
 530    print "refspec refs/heads/branches/*:%s/branches/*" % prefix
 531    print "refspec refs/heads/*:%s/bookmarks/*" % prefix
 532    print "refspec refs/tags/*:%s/tags/*" % prefix
 533
 534    path = os.path.join(dirname, 'marks-git')
 535
 536    if os.path.exists(path):
 537        print "*import-marks %s" % path
 538    print "*export-marks %s" % path
 539
 540    print
 541
 542def branch_tip(repo, branch):
 543    # older versions of mercurial don't have this
 544    if hasattr(repo, 'branchtip'):
 545        return repo.branchtip(branch)
 546    else:
 547        return repo.branchtags()[branch]
 548
 549def get_branch_tip(repo, branch):
 550    global branches
 551
 552    heads = branches.get(hgref(branch), None)
 553    if not heads:
 554        return None
 555
 556    # verify there's only one head
 557    if (len(heads) > 1):
 558        warn("Branch '%s' has more than one head, consider merging" % branch)
 559        return branch_tip(repo, hgref(branch))
 560
 561    return heads[0]
 562
 563def list_head(repo, cur):
 564    global g_head, bmarks
 565
 566    head = bookmarks.readcurrent(repo)
 567    if head:
 568        node = repo[head]
 569    else:
 570        # fake bookmark from current branch
 571        head = cur
 572        node = repo['.']
 573        if not node:
 574            node = repo['tip']
 575        if not node:
 576            return
 577        if head == 'default':
 578            head = 'master'
 579        bmarks[head] = node
 580
 581    head = gitref(head)
 582    print "@refs/heads/%s HEAD" % head
 583    g_head = (head, node)
 584
 585def do_list(parser):
 586    global branches, bmarks, track_branches
 587
 588    repo = parser.repo
 589    for bmark, node in bookmarks.listbookmarks(repo).iteritems():
 590        bmarks[bmark] = repo[node]
 591
 592    cur = repo.dirstate.branch()
 593
 594    list_head(repo, cur)
 595
 596    if track_branches:
 597        for branch in repo.branchmap():
 598            heads = repo.branchheads(branch)
 599            if len(heads):
 600                branches[branch] = heads
 601
 602        for branch in branches:
 603            print "? refs/heads/branches/%s" % gitref(branch)
 604
 605    for bmark in bmarks:
 606        print "? refs/heads/%s" % gitref(bmark)
 607
 608    for tag, node in repo.tagslist():
 609        if tag == 'tip':
 610            continue
 611        print "? refs/tags/%s" % gitref(tag)
 612
 613    print
 614
 615def do_import(parser):
 616    repo = parser.repo
 617
 618    path = os.path.join(dirname, 'marks-git')
 619
 620    print "feature done"
 621    if os.path.exists(path):
 622        print "feature import-marks=%s" % path
 623    print "feature export-marks=%s" % path
 624    sys.stdout.flush()
 625
 626    tmp = encoding.encoding
 627    encoding.encoding = 'utf-8'
 628
 629    # lets get all the import lines
 630    while parser.check('import'):
 631        ref = parser[1]
 632
 633        if (ref == 'HEAD'):
 634            export_head(repo)
 635        elif ref.startswith('refs/heads/branches/'):
 636            branch = ref[len('refs/heads/branches/'):]
 637            export_branch(repo, branch)
 638        elif ref.startswith('refs/heads/'):
 639            bmark = ref[len('refs/heads/'):]
 640            export_bookmark(repo, bmark)
 641        elif ref.startswith('refs/tags/'):
 642            tag = ref[len('refs/tags/'):]
 643            export_tag(repo, tag)
 644
 645        parser.next()
 646
 647    encoding.encoding = tmp
 648
 649    print 'done'
 650
 651def parse_blob(parser):
 652    global blob_marks
 653
 654    parser.next()
 655    mark = parser.get_mark()
 656    parser.next()
 657    data = parser.get_data()
 658    blob_marks[mark] = data
 659    parser.next()
 660
 661def get_merge_files(repo, p1, p2, files):
 662    for e in repo[p1].files():
 663        if e not in files:
 664            if e not in repo[p1].manifest():
 665                continue
 666            f = { 'ctx' : repo[p1][e] }
 667            files[e] = f
 668
 669def parse_commit(parser):
 670    global marks, blob_marks, parsed_refs
 671    global mode
 672
 673    from_mark = merge_mark = None
 674
 675    ref = parser[1]
 676    parser.next()
 677
 678    commit_mark = parser.get_mark()
 679    parser.next()
 680    author = parser.get_author()
 681    parser.next()
 682    committer = parser.get_author()
 683    parser.next()
 684    data = parser.get_data()
 685    parser.next()
 686    if parser.check('from'):
 687        from_mark = parser.get_mark()
 688        parser.next()
 689    if parser.check('merge'):
 690        merge_mark = parser.get_mark()
 691        parser.next()
 692        if parser.check('merge'):
 693            die('octopus merges are not supported yet')
 694
 695    # fast-export adds an extra newline
 696    if data[-1] == '\n':
 697        data = data[:-1]
 698
 699    files = {}
 700
 701    for line in parser:
 702        if parser.check('M'):
 703            t, m, mark_ref, path = line.split(' ', 3)
 704            mark = int(mark_ref[1:])
 705            f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
 706        elif parser.check('D'):
 707            t, path = line.split(' ', 1)
 708            f = { 'deleted' : True }
 709        else:
 710            die('Unknown file command: %s' % line)
 711        files[path] = f
 712
 713    def getfilectx(repo, memctx, f):
 714        of = files[f]
 715        if 'deleted' in of:
 716            raise IOError
 717        if 'ctx' in of:
 718            return of['ctx']
 719        is_exec = of['mode'] == 'x'
 720        is_link = of['mode'] == 'l'
 721        rename = of.get('rename', None)
 722        return context.memfilectx(f, of['data'],
 723                is_link, is_exec, rename)
 724
 725    repo = parser.repo
 726
 727    user, date, tz = author
 728    extra = {}
 729
 730    if committer != author:
 731        extra['committer'] = "%s %u %u" % committer
 732
 733    if from_mark:
 734        p1 = mark_to_rev(from_mark)
 735    else:
 736        p1 = '\0' * 20
 737
 738    if merge_mark:
 739        p2 = mark_to_rev(merge_mark)
 740    else:
 741        p2 = '\0' * 20
 742
 743    #
 744    # If files changed from any of the parents, hg wants to know, but in git if
 745    # nothing changed from the first parent, nothing changed.
 746    #
 747    if merge_mark:
 748        get_merge_files(repo, p1, p2, files)
 749
 750    # Check if the ref is supposed to be a named branch
 751    if ref.startswith('refs/heads/branches/'):
 752        branch = ref[len('refs/heads/branches/'):]
 753        extra['branch'] = hgref(branch)
 754
 755    if mode == 'hg':
 756        i = data.find('\n--HG--\n')
 757        if i >= 0:
 758            tmp = data[i + len('\n--HG--\n'):].strip()
 759            for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
 760                if k == 'rename':
 761                    old, new = v.split(' => ', 1)
 762                    files[new]['rename'] = old
 763                elif k == 'branch':
 764                    extra[k] = v
 765                elif k == 'extra':
 766                    ek, ev = v.split(' : ', 1)
 767                    extra[ek] = urllib.unquote(ev)
 768            data = data[:i]
 769
 770    ctx = context.memctx(repo, (p1, p2), data,
 771            files.keys(), getfilectx,
 772            user, (date, tz), extra)
 773
 774    tmp = encoding.encoding
 775    encoding.encoding = 'utf-8'
 776
 777    node = hghex(repo.commitctx(ctx))
 778
 779    encoding.encoding = tmp
 780
 781    parsed_refs[ref] = node
 782    marks.new_mark(node, commit_mark)
 783
 784def parse_reset(parser):
 785    global parsed_refs
 786
 787    ref = parser[1]
 788    parser.next()
 789    # ugh
 790    if parser.check('commit'):
 791        parse_commit(parser)
 792        return
 793    if not parser.check('from'):
 794        return
 795    from_mark = parser.get_mark()
 796    parser.next()
 797
 798    rev = mark_to_rev(from_mark)
 799    parsed_refs[ref] = rev
 800
 801def parse_tag(parser):
 802    name = parser[1]
 803    parser.next()
 804    from_mark = parser.get_mark()
 805    parser.next()
 806    tagger = parser.get_author()
 807    parser.next()
 808    data = parser.get_data()
 809    parser.next()
 810
 811    parsed_tags[name] = (tagger, data)
 812
 813def write_tag(repo, tag, node, msg, author):
 814    branch = repo[node].branch()
 815    tip = branch_tip(repo, branch)
 816    tip = repo[tip]
 817
 818    def getfilectx(repo, memctx, f):
 819        try:
 820            fctx = tip.filectx(f)
 821            data = fctx.data()
 822        except error.ManifestLookupError:
 823            data = ""
 824        content = data + "%s %s\n" % (node, tag)
 825        return context.memfilectx(f, content, False, False, None)
 826
 827    p1 = tip.hex()
 828    p2 = '\0' * 20
 829    if not author:
 830        author = (None, 0, 0)
 831    user, date, tz = author
 832
 833    ctx = context.memctx(repo, (p1, p2), msg,
 834            ['.hgtags'], getfilectx,
 835            user, (date, tz), {'branch' : branch})
 836
 837    tmp = encoding.encoding
 838    encoding.encoding = 'utf-8'
 839
 840    tagnode = repo.commitctx(ctx)
 841
 842    encoding.encoding = tmp
 843
 844    return tagnode
 845
 846def do_export(parser):
 847    global parsed_refs, bmarks, peer
 848
 849    p_bmarks = []
 850
 851    parser.next()
 852
 853    for line in parser.each_block('done'):
 854        if parser.check('blob'):
 855            parse_blob(parser)
 856        elif parser.check('commit'):
 857            parse_commit(parser)
 858        elif parser.check('reset'):
 859            parse_reset(parser)
 860        elif parser.check('tag'):
 861            parse_tag(parser)
 862        elif parser.check('feature'):
 863            pass
 864        else:
 865            die('unhandled export command: %s' % line)
 866
 867    for ref, node in parsed_refs.iteritems():
 868        bnode = hgbin(node)
 869        if ref.startswith('refs/heads/branches'):
 870            branch = ref[len('refs/heads/branches/'):]
 871            if branch in branches and bnode in branches[branch]:
 872                # up to date
 873                continue
 874            print "ok %s" % ref
 875        elif ref.startswith('refs/heads/'):
 876            bmark = ref[len('refs/heads/'):]
 877            p_bmarks.append((bmark, node))
 878            continue
 879        elif ref.startswith('refs/tags/'):
 880            tag = ref[len('refs/tags/'):]
 881            tag = hgref(tag)
 882            author, msg = parsed_tags.get(tag, (None, None))
 883            if mode == 'git':
 884                if not msg:
 885                    msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
 886                write_tag(parser.repo, tag, node, msg, author)
 887            else:
 888                fp = parser.repo.opener('localtags', 'a')
 889                fp.write('%s %s\n' % (node, tag))
 890                fp.close()
 891            print "ok %s" % ref
 892        else:
 893            # transport-helper/fast-export bugs
 894            continue
 895
 896    if peer:
 897        parser.repo.push(peer, force=force_push, newbranch=True)
 898        remote_bmarks = peer.listkeys('bookmarks')
 899
 900    # handle bookmarks
 901    for bmark, node in p_bmarks:
 902        ref = 'refs/heads/' + bmark
 903        new = node
 904
 905        if bmark in bmarks:
 906            old = bmarks[bmark].hex()
 907        else:
 908            old = ''
 909
 910        if old == new:
 911            continue
 912
 913        if bmark == 'master' and 'master' not in parser.repo._bookmarks:
 914            # fake bookmark
 915            print "ok %s" % ref
 916            continue
 917        elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
 918            # updated locally
 919            pass
 920        else:
 921            print "error %s" % ref
 922            continue
 923
 924        if peer:
 925            old = remote_bmarks.get(bmark, '')
 926            if not peer.pushkey('bookmarks', bmark, old, new):
 927                print "error %s" % ref
 928                continue
 929
 930        print "ok %s" % ref
 931
 932    print
 933
 934def fix_path(alias, repo, orig_url):
 935    url = urlparse.urlparse(orig_url, 'file')
 936    if url.scheme != 'file' or os.path.isabs(url.path):
 937        return
 938    abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
 939    cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
 940    subprocess.call(cmd)
 941
 942def main(args):
 943    global prefix, gitdir, dirname, branches, bmarks
 944    global marks, blob_marks, parsed_refs
 945    global peer, mode, bad_mail, bad_name
 946    global track_branches, force_push, is_tmp
 947    global parsed_tags
 948    global filenodes
 949
 950    alias = args[1]
 951    url = args[2]
 952    peer = None
 953
 954    hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
 955    track_branches = get_config_bool('remote-hg.track-branches', True)
 956    force_push = get_config_bool('remote-hg.force-push')
 957
 958    if hg_git_compat:
 959        mode = 'hg'
 960        bad_mail = 'none@none'
 961        bad_name = ''
 962    else:
 963        mode = 'git'
 964        bad_mail = 'unknown'
 965        bad_name = 'Unknown'
 966
 967    if alias[4:] == url:
 968        is_tmp = True
 969        alias = hashlib.sha1(alias).hexdigest()
 970    else:
 971        is_tmp = False
 972
 973    gitdir = os.environ['GIT_DIR']
 974    dirname = os.path.join(gitdir, 'hg', alias)
 975    branches = {}
 976    bmarks = {}
 977    blob_marks = {}
 978    parsed_refs = {}
 979    marks = None
 980    parsed_tags = {}
 981    filenodes = {}
 982
 983    repo = get_repo(url, alias)
 984    prefix = 'refs/hg/%s' % alias
 985
 986    if not is_tmp:
 987        fix_path(alias, peer or repo, url)
 988
 989    marks_path = os.path.join(dirname, 'marks-hg')
 990    marks = Marks(marks_path, repo)
 991
 992    if sys.platform == 'win32':
 993        import msvcrt
 994        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
 995
 996    parser = Parser(repo)
 997    for line in parser:
 998        if parser.check('capabilities'):
 999            do_capabilities(parser)
1000        elif parser.check('list'):
1001            do_list(parser)
1002        elif parser.check('import'):
1003            do_import(parser)
1004        elif parser.check('export'):
1005            do_export(parser)
1006        else:
1007            die('unhandled command: %s' % line)
1008        sys.stdout.flush()
1009
1010def bye():
1011    if not marks:
1012        return
1013    if not is_tmp:
1014        marks.store()
1015    else:
1016        shutil.rmtree(dirname)
1017
1018atexit.register(bye)
1019sys.exit(main(sys.argv))