contrib / remote-helpers / git-remote-hgon commit remote-hg: ensure shared repo is initialized (52f0856)
   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, discovery, util
  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 want the equivalent of hg's clone/pull--insecure option:
  33# git config --global remote-hg.insecure true
  34#
  35# If you want to switch to hg-git compatibility mode:
  36# git config --global remote-hg.hg-git-compat true
  37#
  38# git:
  39# Sensible defaults for git.
  40# hg bookmarks are exported as git branches, hg branches are prefixed
  41# with 'branches/', HEAD is a special case.
  42#
  43# hg:
  44# Emulate hg-git.
  45# Only hg bookmarks are exported as git branches.
  46# Commits are modified to preserve hg information and allow bidirectionality.
  47#
  48
  49NAME_RE = re.compile('^([^<>]+)')
  50AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
  51EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
  52AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
  53RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
  54
  55VERSION = 2
  56
  57def die(msg, *args):
  58    sys.stderr.write('ERROR: %s\n' % (msg % args))
  59    sys.exit(1)
  60
  61def warn(msg, *args):
  62    sys.stderr.write('WARNING: %s\n' % (msg % args))
  63
  64def gitmode(flags):
  65    return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
  66
  67def gittz(tz):
  68    return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
  69
  70def hgmode(mode):
  71    m = { '100755': 'x', '120000': 'l' }
  72    return m.get(mode, '')
  73
  74def hghex(n):
  75    return node.hex(n)
  76
  77def hgbin(n):
  78    return node.bin(n)
  79
  80def hgref(ref):
  81    return ref.replace('___', ' ')
  82
  83def gitref(ref):
  84    return ref.replace(' ', '___')
  85
  86def check_version(*check):
  87    if not hg_version:
  88        return True
  89    return hg_version >= check
  90
  91def get_config(config):
  92    cmd = ['git', 'config', '--get', config]
  93    process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
  94    output, _ = process.communicate()
  95    return output
  96
  97def get_config_bool(config, default=False):
  98    value = get_config(config).rstrip('\n')
  99    if value == "true":
 100        return True
 101    elif value == "false":
 102        return False
 103    else:
 104        return default
 105
 106class Marks:
 107
 108    def __init__(self, path, repo):
 109        self.path = path
 110        self.repo = repo
 111        self.clear()
 112        self.load()
 113
 114        if self.version < VERSION:
 115            if self.version == 1:
 116                self.upgrade_one()
 117
 118            # upgraded?
 119            if self.version < VERSION:
 120                self.clear()
 121                self.version = VERSION
 122
 123    def clear(self):
 124        self.tips = {}
 125        self.marks = {}
 126        self.rev_marks = {}
 127        self.last_mark = 0
 128        self.version = 0
 129
 130    def load(self):
 131        if not os.path.exists(self.path):
 132            return
 133
 134        tmp = json.load(open(self.path))
 135
 136        self.tips = tmp['tips']
 137        self.marks = tmp['marks']
 138        self.last_mark = tmp['last-mark']
 139        self.version = tmp.get('version', 1)
 140
 141        for rev, mark in self.marks.iteritems():
 142            self.rev_marks[mark] = rev
 143
 144    def upgrade_one(self):
 145        def get_id(rev):
 146            return hghex(self.repo.changelog.node(int(rev)))
 147        self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems())
 148        self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems())
 149        self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems())
 150        self.version = 2
 151
 152    def dict(self):
 153        return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version }
 154
 155    def store(self):
 156        json.dump(self.dict(), open(self.path, 'w'))
 157
 158    def __str__(self):
 159        return str(self.dict())
 160
 161    def from_rev(self, rev):
 162        return self.marks[rev]
 163
 164    def to_rev(self, mark):
 165        return str(self.rev_marks[mark])
 166
 167    def next_mark(self):
 168        self.last_mark += 1
 169        return self.last_mark
 170
 171    def get_mark(self, rev):
 172        self.last_mark += 1
 173        self.marks[rev] = self.last_mark
 174        return self.last_mark
 175
 176    def new_mark(self, rev, mark):
 177        self.marks[rev] = mark
 178        self.rev_marks[mark] = rev
 179        self.last_mark = mark
 180
 181    def is_marked(self, rev):
 182        return rev in self.marks
 183
 184    def get_tip(self, branch):
 185        return str(self.tips[branch])
 186
 187    def set_tip(self, branch, tip):
 188        self.tips[branch] = tip
 189
 190class Parser:
 191
 192    def __init__(self, repo):
 193        self.repo = repo
 194        self.line = self.get_line()
 195
 196    def get_line(self):
 197        return sys.stdin.readline().strip()
 198
 199    def __getitem__(self, i):
 200        return self.line.split()[i]
 201
 202    def check(self, word):
 203        return self.line.startswith(word)
 204
 205    def each_block(self, separator):
 206        while self.line != separator:
 207            yield self.line
 208            self.line = self.get_line()
 209
 210    def __iter__(self):
 211        return self.each_block('')
 212
 213    def next(self):
 214        self.line = self.get_line()
 215        if self.line == 'done':
 216            self.line = None
 217
 218    def get_mark(self):
 219        i = self.line.index(':') + 1
 220        return int(self.line[i:])
 221
 222    def get_data(self):
 223        if not self.check('data'):
 224            return None
 225        i = self.line.index(' ') + 1
 226        size = int(self.line[i:])
 227        return sys.stdin.read(size)
 228
 229    def get_author(self):
 230        global bad_mail
 231
 232        ex = None
 233        m = RAW_AUTHOR_RE.match(self.line)
 234        if not m:
 235            return None
 236        _, name, email, date, tz = m.groups()
 237        if name and 'ext:' in name:
 238            m = re.match('^(.+?) ext:\((.+)\)$', name)
 239            if m:
 240                name = m.group(1)
 241                ex = urllib.unquote(m.group(2))
 242
 243        if email != bad_mail:
 244            if name:
 245                user = '%s <%s>' % (name, email)
 246            else:
 247                user = '<%s>' % (email)
 248        else:
 249            user = name
 250
 251        if ex:
 252            user += ex
 253
 254        tz = int(tz)
 255        tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
 256        return (user, int(date), -tz)
 257
 258def fix_file_path(path):
 259    if not os.path.isabs(path):
 260        return path
 261    return os.path.relpath(path, '/')
 262
 263def export_files(files):
 264    global marks, filenodes
 265
 266    final = []
 267    for f in files:
 268        fid = node.hex(f.filenode())
 269
 270        if fid in filenodes:
 271            mark = filenodes[fid]
 272        else:
 273            mark = marks.next_mark()
 274            filenodes[fid] = mark
 275            d = f.data()
 276
 277            print "blob"
 278            print "mark :%u" % mark
 279            print "data %d" % len(d)
 280            print d
 281
 282        path = fix_file_path(f.path())
 283        final.append((gitmode(f.flags()), mark, path))
 284
 285    return final
 286
 287def get_filechanges(repo, ctx, parent):
 288    modified = set()
 289    added = set()
 290    removed = set()
 291
 292    # load earliest manifest first for caching reasons
 293    prev = parent.manifest().copy()
 294    cur = ctx.manifest()
 295
 296    for fn in cur:
 297        if fn in prev:
 298            if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
 299                modified.add(fn)
 300            del prev[fn]
 301        else:
 302            added.add(fn)
 303    removed |= set(prev.keys())
 304
 305    return added | modified, removed
 306
 307def fixup_user_git(user):
 308    name = mail = None
 309    user = user.replace('"', '')
 310    m = AUTHOR_RE.match(user)
 311    if m:
 312        name = m.group(1)
 313        mail = m.group(2).strip()
 314    else:
 315        m = EMAIL_RE.match(user)
 316        if m:
 317            name = m.group(1)
 318            mail = m.group(2)
 319        else:
 320            m = NAME_RE.match(user)
 321            if m:
 322                name = m.group(1).strip()
 323    return (name, mail)
 324
 325def fixup_user_hg(user):
 326    def sanitize(name):
 327        # stole this from hg-git
 328        return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
 329
 330    m = AUTHOR_HG_RE.match(user)
 331    if m:
 332        name = sanitize(m.group(1))
 333        mail = sanitize(m.group(2))
 334        ex = m.group(3)
 335        if ex:
 336            name += ' ext:(' + urllib.quote(ex) + ')'
 337    else:
 338        name = sanitize(user)
 339        if '@' in user:
 340            mail = name
 341        else:
 342            mail = None
 343
 344    return (name, mail)
 345
 346def fixup_user(user):
 347    global mode, bad_mail
 348
 349    if mode == 'git':
 350        name, mail = fixup_user_git(user)
 351    else:
 352        name, mail = fixup_user_hg(user)
 353
 354    if not name:
 355        name = bad_name
 356    if not mail:
 357        mail = bad_mail
 358
 359    return '%s <%s>' % (name, mail)
 360
 361def updatebookmarks(repo, peer):
 362    remotemarks = peer.listkeys('bookmarks')
 363    localmarks = repo._bookmarks
 364
 365    if not remotemarks:
 366        return
 367
 368    for k, v in remotemarks.iteritems():
 369        localmarks[k] = hgbin(v)
 370
 371    if hasattr(localmarks, 'write'):
 372        localmarks.write()
 373    else:
 374        bookmarks.write(repo)
 375
 376def get_repo(url, alias):
 377    global dirname, peer
 378
 379    myui = ui.ui()
 380    myui.setconfig('ui', 'interactive', 'off')
 381    myui.fout = sys.stderr
 382
 383    if get_config_bool('remote-hg.insecure'):
 384        myui.setconfig('web', 'cacerts', '')
 385
 386    extensions.loadall(myui)
 387
 388    if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'):
 389        repo = hg.repository(myui, url)
 390        if not os.path.exists(dirname):
 391            os.makedirs(dirname)
 392    else:
 393        shared_path = os.path.join(gitdir, 'hg')
 394
 395        # setup shared repo (if not there)
 396        try:
 397            hg.peer(myui, {}, shared_path, create=True)
 398        except error.RepoError:
 399            pass
 400
 401        if not os.path.exists(dirname):
 402            os.makedirs(dirname)
 403
 404        local_path = os.path.join(dirname, 'clone')
 405        if not os.path.exists(local_path):
 406            hg.share(myui, shared_path, local_path, update=False)
 407
 408        repo = hg.repository(myui, local_path)
 409        try:
 410            peer = hg.peer(myui, {}, url)
 411        except:
 412            die('Repository error')
 413        repo.pull(peer, heads=None, force=True)
 414
 415        updatebookmarks(repo, peer)
 416
 417    return repo
 418
 419def rev_to_mark(rev):
 420    global marks
 421    return marks.from_rev(rev.hex())
 422
 423def mark_to_rev(mark):
 424    global marks
 425    return marks.to_rev(mark)
 426
 427def export_ref(repo, name, kind, head):
 428    global prefix, marks, mode
 429
 430    ename = '%s/%s' % (kind, name)
 431    try:
 432        tip = marks.get_tip(ename)
 433        tip = repo[tip].rev()
 434    except:
 435        tip = 0
 436
 437    revs = xrange(tip, head.rev() + 1)
 438    total = len(revs)
 439
 440    for rev in revs:
 441
 442        c = repo[rev]
 443        node = c.node()
 444
 445        if marks.is_marked(c.hex()):
 446            continue
 447
 448        (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
 449        rev_branch = extra['branch']
 450
 451        author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
 452        if 'committer' in extra:
 453            user, time, tz = extra['committer'].rsplit(' ', 2)
 454            committer = "%s %s %s" % (user, time, gittz(int(tz)))
 455        else:
 456            committer = author
 457
 458        parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
 459
 460        if len(parents) == 0:
 461            modified = c.manifest().keys()
 462            removed = []
 463        else:
 464            modified, removed = get_filechanges(repo, c, parents[0])
 465
 466        desc += '\n'
 467
 468        if mode == 'hg':
 469            extra_msg = ''
 470
 471            if rev_branch != 'default':
 472                extra_msg += 'branch : %s\n' % rev_branch
 473
 474            renames = []
 475            for f in c.files():
 476                if f not in c.manifest():
 477                    continue
 478                rename = c.filectx(f).renamed()
 479                if rename:
 480                    renames.append((rename[0], f))
 481
 482            for e in renames:
 483                extra_msg += "rename : %s => %s\n" % e
 484
 485            for key, value in extra.iteritems():
 486                if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
 487                    continue
 488                else:
 489                    extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
 490
 491            if extra_msg:
 492                desc += '\n--HG--\n' + extra_msg
 493
 494        if len(parents) == 0 and rev:
 495            print 'reset %s/%s' % (prefix, ename)
 496
 497        modified_final = export_files(c.filectx(f) for f in modified)
 498
 499        print "commit %s/%s" % (prefix, ename)
 500        print "mark :%d" % (marks.get_mark(c.hex()))
 501        print "author %s" % (author)
 502        print "committer %s" % (committer)
 503        print "data %d" % (len(desc))
 504        print desc
 505
 506        if len(parents) > 0:
 507            print "from :%s" % (rev_to_mark(parents[0]))
 508            if len(parents) > 1:
 509                print "merge :%s" % (rev_to_mark(parents[1]))
 510
 511        for f in removed:
 512            print "D %s" % (fix_file_path(f))
 513        for f in modified_final:
 514            print "M %s :%u %s" % f
 515        print
 516
 517        progress = (rev - tip)
 518        if (progress % 100 == 0):
 519            print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
 520
 521    # make sure the ref is updated
 522    print "reset %s/%s" % (prefix, ename)
 523    print "from :%u" % rev_to_mark(head)
 524    print
 525
 526    marks.set_tip(ename, head.hex())
 527
 528def export_tag(repo, tag):
 529    export_ref(repo, tag, 'tags', repo[hgref(tag)])
 530
 531def export_bookmark(repo, bmark):
 532    head = bmarks[hgref(bmark)]
 533    export_ref(repo, bmark, 'bookmarks', head)
 534
 535def export_branch(repo, branch):
 536    tip = get_branch_tip(repo, branch)
 537    head = repo[tip]
 538    export_ref(repo, branch, 'branches', head)
 539
 540def export_head(repo):
 541    global g_head
 542    export_ref(repo, g_head[0], 'bookmarks', g_head[1])
 543
 544def do_capabilities(parser):
 545    global prefix, dirname
 546
 547    print "import"
 548    print "export"
 549    print "refspec refs/heads/branches/*:%s/branches/*" % prefix
 550    print "refspec refs/heads/*:%s/bookmarks/*" % prefix
 551    print "refspec refs/tags/*:%s/tags/*" % prefix
 552
 553    path = os.path.join(dirname, 'marks-git')
 554
 555    if os.path.exists(path):
 556        print "*import-marks %s" % path
 557    print "*export-marks %s" % path
 558    print "option"
 559
 560    print
 561
 562def branch_tip(branch):
 563    return branches[branch][-1]
 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(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 branches:
 583        # empty repo
 584        return
 585
 586    node = repo[branch_tip('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    orig = peer if peer else repo
 604
 605    for branch, heads in orig.branchmap().iteritems():
 606        # only open heads
 607        heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
 608        if heads:
 609            branches[branch] = heads
 610
 611    list_head(repo, cur)
 612
 613    if track_branches:
 614        for branch in branches:
 615            print "? refs/heads/branches/%s" % gitref(branch)
 616
 617    for bmark in bmarks:
 618        print "? refs/heads/%s" % gitref(bmark)
 619
 620    for tag, node in repo.tagslist():
 621        if tag == 'tip':
 622            continue
 623        print "? refs/tags/%s" % gitref(tag)
 624
 625    print
 626
 627def do_import(parser):
 628    repo = parser.repo
 629
 630    path = os.path.join(dirname, 'marks-git')
 631
 632    print "feature done"
 633    if os.path.exists(path):
 634        print "feature import-marks=%s" % path
 635    print "feature export-marks=%s" % path
 636    print "feature force"
 637    sys.stdout.flush()
 638
 639    tmp = encoding.encoding
 640    encoding.encoding = 'utf-8'
 641
 642    # lets get all the import lines
 643    while parser.check('import'):
 644        ref = parser[1]
 645
 646        if (ref == 'HEAD'):
 647            export_head(repo)
 648        elif ref.startswith('refs/heads/branches/'):
 649            branch = ref[len('refs/heads/branches/'):]
 650            export_branch(repo, branch)
 651        elif ref.startswith('refs/heads/'):
 652            bmark = ref[len('refs/heads/'):]
 653            export_bookmark(repo, bmark)
 654        elif ref.startswith('refs/tags/'):
 655            tag = ref[len('refs/tags/'):]
 656            export_tag(repo, tag)
 657
 658        parser.next()
 659
 660    encoding.encoding = tmp
 661
 662    print 'done'
 663
 664def parse_blob(parser):
 665    global blob_marks
 666
 667    parser.next()
 668    mark = parser.get_mark()
 669    parser.next()
 670    data = parser.get_data()
 671    blob_marks[mark] = data
 672    parser.next()
 673
 674def get_merge_files(repo, p1, p2, files):
 675    for e in repo[p1].files():
 676        if e not in files:
 677            if e not in repo[p1].manifest():
 678                continue
 679            f = { 'ctx' : repo[p1][e] }
 680            files[e] = f
 681
 682def parse_commit(parser):
 683    global marks, blob_marks, parsed_refs
 684    global mode
 685
 686    from_mark = merge_mark = None
 687
 688    ref = parser[1]
 689    parser.next()
 690
 691    commit_mark = parser.get_mark()
 692    parser.next()
 693    author = parser.get_author()
 694    parser.next()
 695    committer = parser.get_author()
 696    parser.next()
 697    data = parser.get_data()
 698    parser.next()
 699    if parser.check('from'):
 700        from_mark = parser.get_mark()
 701        parser.next()
 702    if parser.check('merge'):
 703        merge_mark = parser.get_mark()
 704        parser.next()
 705        if parser.check('merge'):
 706            die('octopus merges are not supported yet')
 707
 708    # fast-export adds an extra newline
 709    if data[-1] == '\n':
 710        data = data[:-1]
 711
 712    files = {}
 713
 714    for line in parser:
 715        if parser.check('M'):
 716            t, m, mark_ref, path = line.split(' ', 3)
 717            mark = int(mark_ref[1:])
 718            f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
 719        elif parser.check('D'):
 720            t, path = line.split(' ', 1)
 721            f = { 'deleted' : True }
 722        else:
 723            die('Unknown file command: %s' % line)
 724        files[path] = f
 725
 726    # only export the commits if we are on an internal proxy repo
 727    if dry_run and not peer:
 728        parsed_refs[ref] = None
 729        return
 730
 731    def getfilectx(repo, memctx, f):
 732        of = files[f]
 733        if 'deleted' in of:
 734            raise IOError
 735        if 'ctx' in of:
 736            return of['ctx']
 737        is_exec = of['mode'] == 'x'
 738        is_link = of['mode'] == 'l'
 739        rename = of.get('rename', None)
 740        return context.memfilectx(f, of['data'],
 741                is_link, is_exec, rename)
 742
 743    repo = parser.repo
 744
 745    user, date, tz = author
 746    extra = {}
 747
 748    if committer != author:
 749        extra['committer'] = "%s %u %u" % committer
 750
 751    if from_mark:
 752        p1 = mark_to_rev(from_mark)
 753    else:
 754        p1 = '0' * 40
 755
 756    if merge_mark:
 757        p2 = mark_to_rev(merge_mark)
 758    else:
 759        p2 = '0' * 40
 760
 761    #
 762    # If files changed from any of the parents, hg wants to know, but in git if
 763    # nothing changed from the first parent, nothing changed.
 764    #
 765    if merge_mark:
 766        get_merge_files(repo, p1, p2, files)
 767
 768    # Check if the ref is supposed to be a named branch
 769    if ref.startswith('refs/heads/branches/'):
 770        branch = ref[len('refs/heads/branches/'):]
 771        extra['branch'] = hgref(branch)
 772
 773    if mode == 'hg':
 774        i = data.find('\n--HG--\n')
 775        if i >= 0:
 776            tmp = data[i + len('\n--HG--\n'):].strip()
 777            for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
 778                if k == 'rename':
 779                    old, new = v.split(' => ', 1)
 780                    files[new]['rename'] = old
 781                elif k == 'branch':
 782                    extra[k] = v
 783                elif k == 'extra':
 784                    ek, ev = v.split(' : ', 1)
 785                    extra[ek] = urllib.unquote(ev)
 786            data = data[:i]
 787
 788    ctx = context.memctx(repo, (p1, p2), data,
 789            files.keys(), getfilectx,
 790            user, (date, tz), extra)
 791
 792    tmp = encoding.encoding
 793    encoding.encoding = 'utf-8'
 794
 795    node = hghex(repo.commitctx(ctx))
 796
 797    encoding.encoding = tmp
 798
 799    parsed_refs[ref] = node
 800    marks.new_mark(node, commit_mark)
 801
 802def parse_reset(parser):
 803    global parsed_refs
 804
 805    ref = parser[1]
 806    parser.next()
 807    # ugh
 808    if parser.check('commit'):
 809        parse_commit(parser)
 810        return
 811    if not parser.check('from'):
 812        return
 813    from_mark = parser.get_mark()
 814    parser.next()
 815
 816    try:
 817        rev = mark_to_rev(from_mark)
 818    except KeyError:
 819        rev = None
 820    parsed_refs[ref] = rev
 821
 822def parse_tag(parser):
 823    name = parser[1]
 824    parser.next()
 825    from_mark = parser.get_mark()
 826    parser.next()
 827    tagger = parser.get_author()
 828    parser.next()
 829    data = parser.get_data()
 830    parser.next()
 831
 832    parsed_tags[name] = (tagger, data)
 833
 834def write_tag(repo, tag, node, msg, author):
 835    branch = repo[node].branch()
 836    tip = branch_tip(branch)
 837    tip = repo[tip]
 838
 839    def getfilectx(repo, memctx, f):
 840        try:
 841            fctx = tip.filectx(f)
 842            data = fctx.data()
 843        except error.ManifestLookupError:
 844            data = ""
 845        content = data + "%s %s\n" % (node, tag)
 846        return context.memfilectx(f, content, False, False, None)
 847
 848    p1 = tip.hex()
 849    p2 = '0' * 40
 850    if author:
 851        user, date, tz = author
 852        date_tz = (date, tz)
 853    else:
 854        cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
 855        process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
 856        output, _ = process.communicate()
 857        m = re.match('^.* <.*>', output)
 858        if m:
 859            user = m.group(0)
 860        else:
 861            user = repo.ui.username()
 862        date_tz = None
 863
 864    ctx = context.memctx(repo, (p1, p2), msg,
 865            ['.hgtags'], getfilectx,
 866            user, date_tz, {'branch' : branch})
 867
 868    tmp = encoding.encoding
 869    encoding.encoding = 'utf-8'
 870
 871    tagnode = repo.commitctx(ctx)
 872
 873    encoding.encoding = tmp
 874
 875    return (tagnode, branch)
 876
 877def checkheads_bmark(repo, ref, ctx):
 878    bmark = ref[len('refs/heads/'):]
 879    if not bmark in bmarks:
 880        # new bmark
 881        return True
 882
 883    ctx_old = bmarks[bmark]
 884    ctx_new = ctx
 885    if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
 886        if force_push:
 887            print "ok %s forced update" % ref
 888        else:
 889            print "error %s non-fast forward" % ref
 890            return False
 891
 892    return True
 893
 894def checkheads(repo, remote, p_revs):
 895
 896    remotemap = remote.branchmap()
 897    if not remotemap:
 898        # empty repo
 899        return True
 900
 901    new = {}
 902    ret = True
 903
 904    for node, ref in p_revs.iteritems():
 905        ctx = repo[node]
 906        branch = ctx.branch()
 907        if not branch in remotemap:
 908            # new branch
 909            continue
 910        if not ref.startswith('refs/heads/branches'):
 911            if ref.startswith('refs/heads/'):
 912                if not checkheads_bmark(repo, ref, ctx):
 913                    ret = False
 914
 915            # only check branches
 916            continue
 917        new.setdefault(branch, []).append(ctx.rev())
 918
 919    for branch, heads in new.iteritems():
 920        old = [repo.changelog.rev(x) for x in remotemap[branch]]
 921        for rev in heads:
 922            if check_version(2, 3):
 923                ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
 924            else:
 925                ancestors = repo.changelog.ancestors(rev)
 926            found = False
 927
 928            for x in old:
 929                if x in ancestors:
 930                    found = True
 931                    break
 932
 933            if found:
 934                continue
 935
 936            node = repo.changelog.node(rev)
 937            ref = p_revs[node]
 938            if force_push:
 939                print "ok %s forced update" % ref
 940            else:
 941                print "error %s non-fast forward" % ref
 942                ret = False
 943
 944    return ret
 945
 946def push_unsafe(repo, remote, parsed_refs, p_revs):
 947
 948    force = force_push
 949
 950    fci = discovery.findcommonincoming
 951    commoninc = fci(repo, remote, force=force)
 952    common, _, remoteheads = commoninc
 953
 954    if not checkheads(repo, remote, p_revs):
 955        return None
 956
 957    cg = repo.getbundle('push', heads=list(p_revs), common=common)
 958
 959    unbundle = remote.capable('unbundle')
 960    if unbundle:
 961        if force:
 962            remoteheads = ['force']
 963        return remote.unbundle(cg, remoteheads, 'push')
 964    else:
 965        return remote.addchangegroup(cg, 'push', repo.url())
 966
 967def push(repo, remote, parsed_refs, p_revs):
 968    if hasattr(remote, 'canpush') and not remote.canpush():
 969        print "error cannot push"
 970
 971    if not p_revs:
 972        # nothing to push
 973        return
 974
 975    lock = None
 976    unbundle = remote.capable('unbundle')
 977    if not unbundle:
 978        lock = remote.lock()
 979    try:
 980        ret = push_unsafe(repo, remote, parsed_refs, p_revs)
 981    finally:
 982        if lock is not None:
 983            lock.release()
 984
 985    return ret
 986
 987def check_tip(ref, kind, name, heads):
 988    try:
 989        ename = '%s/%s' % (kind, name)
 990        tip = marks.get_tip(ename)
 991    except KeyError:
 992        return True
 993    else:
 994        return tip in heads
 995
 996def do_export(parser):
 997    global parsed_refs, bmarks, peer
 998
 999    p_bmarks = []
1000    p_revs = {}
1001
1002    parser.next()
1003
1004    for line in parser.each_block('done'):
1005        if parser.check('blob'):
1006            parse_blob(parser)
1007        elif parser.check('commit'):
1008            parse_commit(parser)
1009        elif parser.check('reset'):
1010            parse_reset(parser)
1011        elif parser.check('tag'):
1012            parse_tag(parser)
1013        elif parser.check('feature'):
1014            pass
1015        else:
1016            die('unhandled export command: %s' % line)
1017
1018    need_fetch = False
1019
1020    for ref, node in parsed_refs.iteritems():
1021        bnode = hgbin(node) if node else None
1022        if ref.startswith('refs/heads/branches'):
1023            branch = ref[len('refs/heads/branches/'):]
1024            if branch in branches and bnode in branches[branch]:
1025                # up to date
1026                continue
1027
1028            if peer:
1029                remotemap = peer.branchmap()
1030                if remotemap and branch in remotemap:
1031                    heads = [hghex(e) for e in remotemap[branch]]
1032                    if not check_tip(ref, 'branches', branch, heads):
1033                        print "error %s fetch first" % ref
1034                        need_fetch = True
1035                        continue
1036
1037            p_revs[bnode] = ref
1038            print "ok %s" % ref
1039        elif ref.startswith('refs/heads/'):
1040            bmark = ref[len('refs/heads/'):]
1041            new = node
1042            old = bmarks[bmark].hex() if bmark in bmarks else ''
1043
1044            if old == new:
1045                continue
1046
1047            print "ok %s" % ref
1048            if bmark != fake_bmark and \
1049                    not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1050                p_bmarks.append((ref, bmark, old, new))
1051
1052            if peer:
1053                remote_old = peer.listkeys('bookmarks').get(bmark)
1054                if remote_old:
1055                    if not check_tip(ref, 'bookmarks', bmark, remote_old):
1056                        print "error %s fetch first" % ref
1057                        need_fetch = True
1058                        continue
1059
1060            p_revs[bnode] = ref
1061        elif ref.startswith('refs/tags/'):
1062            if dry_run:
1063                print "ok %s" % ref
1064                continue
1065            tag = ref[len('refs/tags/'):]
1066            tag = hgref(tag)
1067            author, msg = parsed_tags.get(tag, (None, None))
1068            if mode == 'git':
1069                if not msg:
1070                    msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
1071                tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1072                p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1073            else:
1074                fp = parser.repo.opener('localtags', 'a')
1075                fp.write('%s %s\n' % (node, tag))
1076                fp.close()
1077            p_revs[bnode] = ref
1078            print "ok %s" % ref
1079        else:
1080            # transport-helper/fast-export bugs
1081            continue
1082
1083    if need_fetch:
1084        print
1085        return
1086
1087    if dry_run:
1088        if peer and not force_push:
1089            checkheads(parser.repo, peer, p_revs)
1090        print
1091        return
1092
1093    if peer:
1094        if not push(parser.repo, peer, parsed_refs, p_revs):
1095            # do not update bookmarks
1096            print
1097            return
1098
1099        # update remote bookmarks
1100        remote_bmarks = peer.listkeys('bookmarks')
1101        for ref, bmark, old, new in p_bmarks:
1102            if force_push:
1103                old = remote_bmarks.get(bmark, '')
1104            if not peer.pushkey('bookmarks', bmark, old, new):
1105                print "error %s" % ref
1106    else:
1107        # update local bookmarks
1108        for ref, bmark, old, new in p_bmarks:
1109            if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1110                print "error %s" % ref
1111
1112    print
1113
1114def do_option(parser):
1115    global dry_run, force_push
1116    _, key, value = parser.line.split(' ')
1117    if key == 'dry-run':
1118        dry_run = (value == 'true')
1119        print 'ok'
1120    elif key == 'force':
1121        force_push = (value == 'true')
1122        print 'ok'
1123    else:
1124        print 'unsupported'
1125
1126def fix_path(alias, repo, orig_url):
1127    url = urlparse.urlparse(orig_url, 'file')
1128    if url.scheme != 'file' or os.path.isabs(url.path):
1129        return
1130    abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1131    cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1132    subprocess.call(cmd)
1133
1134def main(args):
1135    global prefix, gitdir, dirname, branches, bmarks
1136    global marks, blob_marks, parsed_refs
1137    global peer, mode, bad_mail, bad_name
1138    global track_branches, force_push, is_tmp
1139    global parsed_tags
1140    global filenodes
1141    global fake_bmark, hg_version
1142    global dry_run
1143
1144    alias = args[1]
1145    url = args[2]
1146    peer = None
1147
1148    hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1149    track_branches = get_config_bool('remote-hg.track-branches', True)
1150    force_push = False
1151
1152    if hg_git_compat:
1153        mode = 'hg'
1154        bad_mail = 'none@none'
1155        bad_name = ''
1156    else:
1157        mode = 'git'
1158        bad_mail = 'unknown'
1159        bad_name = 'Unknown'
1160
1161    if alias[4:] == url:
1162        is_tmp = True
1163        alias = hashlib.sha1(alias).hexdigest()
1164    else:
1165        is_tmp = False
1166
1167    gitdir = os.environ['GIT_DIR']
1168    dirname = os.path.join(gitdir, 'hg', alias)
1169    branches = {}
1170    bmarks = {}
1171    blob_marks = {}
1172    parsed_refs = {}
1173    marks = None
1174    parsed_tags = {}
1175    filenodes = {}
1176    fake_bmark = None
1177    try:
1178        hg_version = tuple(int(e) for e in util.version().split('.'))
1179    except:
1180        hg_version = None
1181    dry_run = False
1182
1183    repo = get_repo(url, alias)
1184    prefix = 'refs/hg/%s' % alias
1185
1186    if not is_tmp:
1187        fix_path(alias, peer or repo, url)
1188
1189    marks_path = os.path.join(dirname, 'marks-hg')
1190    marks = Marks(marks_path, repo)
1191
1192    if sys.platform == 'win32':
1193        import msvcrt
1194        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1195
1196    parser = Parser(repo)
1197    for line in parser:
1198        if parser.check('capabilities'):
1199            do_capabilities(parser)
1200        elif parser.check('list'):
1201            do_list(parser)
1202        elif parser.check('import'):
1203            do_import(parser)
1204        elif parser.check('export'):
1205            do_export(parser)
1206        elif parser.check('option'):
1207            do_option(parser)
1208        else:
1209            die('unhandled command: %s' % line)
1210        sys.stdout.flush()
1211
1212def bye():
1213    if not marks:
1214        return
1215    if not is_tmp:
1216        marks.store()
1217    else:
1218        shutil.rmtree(dirname)
1219
1220atexit.register(bye)
1221sys.exit(main(sys.argv))