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