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