contrib / remote-helpers / git-remote-hgon commit Merge branch 'tg/diff-no-index-refactor' (73b0631)
   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(r'([^ \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            mail = m.group(1)
 320        else:
 321            m = NAME_RE.match(user)
 322            if m:
 323                name = m.group(1).strip()
 324    return (name, mail)
 325
 326def fixup_user_hg(user):
 327    def sanitize(name):
 328        # stole this from hg-git
 329        return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
 330
 331    m = AUTHOR_HG_RE.match(user)
 332    if m:
 333        name = sanitize(m.group(1))
 334        mail = sanitize(m.group(2))
 335        ex = m.group(3)
 336        if ex:
 337            name += ' ext:(' + urllib.quote(ex) + ')'
 338    else:
 339        name = sanitize(user)
 340        if '@' in user:
 341            mail = name
 342        else:
 343            mail = None
 344
 345    return (name, mail)
 346
 347def fixup_user(user):
 348    if mode == 'git':
 349        name, mail = fixup_user_git(user)
 350    else:
 351        name, mail = fixup_user_hg(user)
 352
 353    if not name:
 354        name = bad_name
 355    if not mail:
 356        mail = bad_mail
 357
 358    return '%s <%s>' % (name, mail)
 359
 360def updatebookmarks(repo, peer):
 361    remotemarks = peer.listkeys('bookmarks')
 362    localmarks = repo._bookmarks
 363
 364    if not remotemarks:
 365        return
 366
 367    for k, v in remotemarks.iteritems():
 368        localmarks[k] = hgbin(v)
 369
 370    if hasattr(localmarks, 'write'):
 371        localmarks.write()
 372    else:
 373        bookmarks.write(repo)
 374
 375def get_repo(url, alias):
 376    global peer
 377
 378    myui = ui.ui()
 379    myui.setconfig('ui', 'interactive', 'off')
 380    myui.fout = sys.stderr
 381
 382    if get_config_bool('remote-hg.insecure'):
 383        myui.setconfig('web', 'cacerts', '')
 384
 385    extensions.loadall(myui)
 386
 387    if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'):
 388        repo = hg.repository(myui, url)
 389        if not os.path.exists(dirname):
 390            os.makedirs(dirname)
 391    else:
 392        shared_path = os.path.join(gitdir, 'hg')
 393
 394        # check and upgrade old organization
 395        hg_path = os.path.join(shared_path, '.hg')
 396        if os.path.exists(shared_path) and not os.path.exists(hg_path):
 397            repos = os.listdir(shared_path)
 398            for x in repos:
 399                local_hg = os.path.join(shared_path, x, 'clone', '.hg')
 400                if not os.path.exists(local_hg):
 401                    continue
 402                if not os.path.exists(hg_path):
 403                    shutil.move(local_hg, hg_path)
 404                shutil.rmtree(os.path.join(shared_path, x, 'clone'))
 405
 406        # setup shared repo (if not there)
 407        try:
 408            hg.peer(myui, {}, shared_path, create=True)
 409        except error.RepoError:
 410            pass
 411
 412        if not os.path.exists(dirname):
 413            os.makedirs(dirname)
 414
 415        local_path = os.path.join(dirname, 'clone')
 416        if not os.path.exists(local_path):
 417            hg.share(myui, shared_path, local_path, update=False)
 418
 419        repo = hg.repository(myui, local_path)
 420        try:
 421            peer = hg.peer(myui, {}, url)
 422        except:
 423            die('Repository error')
 424        repo.pull(peer, heads=None, force=True)
 425
 426        updatebookmarks(repo, peer)
 427
 428    return repo
 429
 430def rev_to_mark(rev):
 431    return marks.from_rev(rev.hex())
 432
 433def mark_to_rev(mark):
 434    return marks.to_rev(mark)
 435
 436def export_ref(repo, name, kind, head):
 437    ename = '%s/%s' % (kind, name)
 438    try:
 439        tip = marks.get_tip(ename)
 440        tip = repo[tip].rev()
 441    except:
 442        tip = 0
 443
 444    revs = xrange(tip, head.rev() + 1)
 445    total = len(revs)
 446
 447    for rev in revs:
 448
 449        c = repo[rev]
 450        node = c.node()
 451
 452        if marks.is_marked(c.hex()):
 453            continue
 454
 455        (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
 456        rev_branch = extra['branch']
 457
 458        author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
 459        if 'committer' in extra:
 460            user, time, tz = extra['committer'].rsplit(' ', 2)
 461            committer = "%s %s %s" % (user, time, gittz(int(tz)))
 462        else:
 463            committer = author
 464
 465        parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
 466
 467        if len(parents) == 0:
 468            modified = c.manifest().keys()
 469            removed = []
 470        else:
 471            modified, removed = get_filechanges(repo, c, parents[0])
 472
 473        desc += '\n'
 474
 475        if mode == 'hg':
 476            extra_msg = ''
 477
 478            if rev_branch != 'default':
 479                extra_msg += 'branch : %s\n' % rev_branch
 480
 481            renames = []
 482            for f in c.files():
 483                if f not in c.manifest():
 484                    continue
 485                rename = c.filectx(f).renamed()
 486                if rename:
 487                    renames.append((rename[0], f))
 488
 489            for e in renames:
 490                extra_msg += "rename : %s => %s\n" % e
 491
 492            for key, value in extra.iteritems():
 493                if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
 494                    continue
 495                else:
 496                    extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
 497
 498            if extra_msg:
 499                desc += '\n--HG--\n' + extra_msg
 500
 501        if len(parents) == 0 and rev:
 502            print 'reset %s/%s' % (prefix, ename)
 503
 504        modified_final = export_files(c.filectx(f) for f in modified)
 505
 506        print "commit %s/%s" % (prefix, ename)
 507        print "mark :%d" % (marks.get_mark(c.hex()))
 508        print "author %s" % (author)
 509        print "committer %s" % (committer)
 510        print "data %d" % (len(desc))
 511        print desc
 512
 513        if len(parents) > 0:
 514            print "from :%s" % (rev_to_mark(parents[0]))
 515            if len(parents) > 1:
 516                print "merge :%s" % (rev_to_mark(parents[1]))
 517
 518        for f in removed:
 519            print "D %s" % (fix_file_path(f))
 520        for f in modified_final:
 521            print "M %s :%u %s" % f
 522        print
 523
 524        progress = (rev - tip)
 525        if (progress % 100 == 0):
 526            print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
 527
 528    # make sure the ref is updated
 529    print "reset %s/%s" % (prefix, ename)
 530    print "from :%u" % rev_to_mark(head)
 531    print
 532
 533    pending_revs = set(revs) - notes
 534    if pending_revs:
 535        note_mark = marks.next_mark()
 536        ref = "refs/notes/hg"
 537
 538        print "commit %s" % ref
 539        print "mark :%d" % (note_mark)
 540        print "committer remote-hg <> %s" % (ptime.strftime('%s %z'))
 541        desc = "Notes for %s\n" % (name)
 542        print "data %d" % (len(desc))
 543        print desc
 544        if marks.last_note:
 545            print "from :%u" % marks.last_note
 546
 547        for rev in pending_revs:
 548            notes.add(rev)
 549            c = repo[rev]
 550            print "N inline :%u" % rev_to_mark(c)
 551            msg = c.hex()
 552            print "data %d" % (len(msg))
 553            print msg
 554        print
 555
 556        marks.last_note = note_mark
 557
 558    marks.set_tip(ename, head.hex())
 559
 560def export_tag(repo, tag):
 561    export_ref(repo, tag, 'tags', repo[hgref(tag)])
 562
 563def export_bookmark(repo, bmark):
 564    head = bmarks[hgref(bmark)]
 565    export_ref(repo, bmark, 'bookmarks', head)
 566
 567def export_branch(repo, branch):
 568    tip = get_branch_tip(repo, branch)
 569    head = repo[tip]
 570    export_ref(repo, branch, 'branches', head)
 571
 572def export_head(repo):
 573    export_ref(repo, g_head[0], 'bookmarks', g_head[1])
 574
 575def do_capabilities(parser):
 576    print "import"
 577    print "export"
 578    print "refspec refs/heads/branches/*:%s/branches/*" % prefix
 579    print "refspec refs/heads/*:%s/bookmarks/*" % prefix
 580    print "refspec refs/tags/*:%s/tags/*" % prefix
 581
 582    path = os.path.join(dirname, 'marks-git')
 583
 584    if os.path.exists(path):
 585        print "*import-marks %s" % path
 586    print "*export-marks %s" % path
 587    print "option"
 588
 589    print
 590
 591def branch_tip(branch):
 592    return branches[branch][-1]
 593
 594def get_branch_tip(repo, branch):
 595    heads = branches.get(hgref(branch), None)
 596    if not heads:
 597        return None
 598
 599    # verify there's only one head
 600    if (len(heads) > 1):
 601        warn("Branch '%s' has more than one head, consider merging" % branch)
 602        return branch_tip(hgref(branch))
 603
 604    return heads[0]
 605
 606def list_head(repo, cur):
 607    global g_head, fake_bmark
 608
 609    if 'default' not in branches:
 610        # empty repo
 611        return
 612
 613    node = repo[branch_tip('default')]
 614    head = 'master' if not 'master' in bmarks else 'default'
 615    fake_bmark = head
 616    bmarks[head] = node
 617
 618    head = gitref(head)
 619    print "@refs/heads/%s HEAD" % head
 620    g_head = (head, node)
 621
 622def do_list(parser):
 623    repo = parser.repo
 624    for bmark, node in bookmarks.listbookmarks(repo).iteritems():
 625        bmarks[bmark] = repo[node]
 626
 627    cur = repo.dirstate.branch()
 628    orig = peer if peer else repo
 629
 630    for branch, heads in orig.branchmap().iteritems():
 631        # only open heads
 632        heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
 633        if heads:
 634            branches[branch] = heads
 635
 636    list_head(repo, cur)
 637
 638    if track_branches:
 639        for branch in branches:
 640            print "? refs/heads/branches/%s" % gitref(branch)
 641
 642    for bmark in bmarks:
 643        print "? refs/heads/%s" % gitref(bmark)
 644
 645    for tag, node in repo.tagslist():
 646        if tag == 'tip':
 647            continue
 648        print "? refs/tags/%s" % gitref(tag)
 649
 650    print
 651
 652def do_import(parser):
 653    repo = parser.repo
 654
 655    path = os.path.join(dirname, 'marks-git')
 656
 657    print "feature done"
 658    if os.path.exists(path):
 659        print "feature import-marks=%s" % path
 660    print "feature export-marks=%s" % path
 661    print "feature force"
 662    sys.stdout.flush()
 663
 664    tmp = encoding.encoding
 665    encoding.encoding = 'utf-8'
 666
 667    # lets get all the import lines
 668    while parser.check('import'):
 669        ref = parser[1]
 670
 671        if (ref == 'HEAD'):
 672            export_head(repo)
 673        elif ref.startswith('refs/heads/branches/'):
 674            branch = ref[len('refs/heads/branches/'):]
 675            export_branch(repo, branch)
 676        elif ref.startswith('refs/heads/'):
 677            bmark = ref[len('refs/heads/'):]
 678            export_bookmark(repo, bmark)
 679        elif ref.startswith('refs/tags/'):
 680            tag = ref[len('refs/tags/'):]
 681            export_tag(repo, tag)
 682
 683        parser.next()
 684
 685    encoding.encoding = tmp
 686
 687    print 'done'
 688
 689def parse_blob(parser):
 690    parser.next()
 691    mark = parser.get_mark()
 692    parser.next()
 693    data = parser.get_data()
 694    blob_marks[mark] = data
 695    parser.next()
 696
 697def get_merge_files(repo, p1, p2, files):
 698    for e in repo[p1].files():
 699        if e not in files:
 700            if e not in repo[p1].manifest():
 701                continue
 702            f = { 'ctx' : repo[p1][e] }
 703            files[e] = f
 704
 705def c_style_unescape(string):
 706    if string[0] == string[-1] == '"':
 707        return string.decode('string-escape')[1:-1]
 708    return string
 709
 710def parse_commit(parser):
 711    from_mark = merge_mark = None
 712
 713    ref = parser[1]
 714    parser.next()
 715
 716    commit_mark = parser.get_mark()
 717    parser.next()
 718    author = parser.get_author()
 719    parser.next()
 720    committer = parser.get_author()
 721    parser.next()
 722    data = parser.get_data()
 723    parser.next()
 724    if parser.check('from'):
 725        from_mark = parser.get_mark()
 726        parser.next()
 727    if parser.check('merge'):
 728        merge_mark = parser.get_mark()
 729        parser.next()
 730        if parser.check('merge'):
 731            die('octopus merges are not supported yet')
 732
 733    # fast-export adds an extra newline
 734    if data[-1] == '\n':
 735        data = data[:-1]
 736
 737    files = {}
 738
 739    for line in parser:
 740        if parser.check('M'):
 741            t, m, mark_ref, path = line.split(' ', 3)
 742            mark = int(mark_ref[1:])
 743            f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
 744        elif parser.check('D'):
 745            t, path = line.split(' ', 1)
 746            f = { 'deleted' : True }
 747        else:
 748            die('Unknown file command: %s' % line)
 749        path = c_style_unescape(path)
 750        files[path] = f
 751
 752    # only export the commits if we are on an internal proxy repo
 753    if dry_run and not peer:
 754        parsed_refs[ref] = None
 755        return
 756
 757    def getfilectx(repo, memctx, f):
 758        of = files[f]
 759        if 'deleted' in of:
 760            raise IOError
 761        if 'ctx' in of:
 762            return of['ctx']
 763        is_exec = of['mode'] == 'x'
 764        is_link = of['mode'] == 'l'
 765        rename = of.get('rename', None)
 766        return context.memfilectx(f, of['data'],
 767                is_link, is_exec, rename)
 768
 769    repo = parser.repo
 770
 771    user, date, tz = author
 772    extra = {}
 773
 774    if committer != author:
 775        extra['committer'] = "%s %u %u" % committer
 776
 777    if from_mark:
 778        p1 = mark_to_rev(from_mark)
 779    else:
 780        p1 = '0' * 40
 781
 782    if merge_mark:
 783        p2 = mark_to_rev(merge_mark)
 784    else:
 785        p2 = '0' * 40
 786
 787    #
 788    # If files changed from any of the parents, hg wants to know, but in git if
 789    # nothing changed from the first parent, nothing changed.
 790    #
 791    if merge_mark:
 792        get_merge_files(repo, p1, p2, files)
 793
 794    # Check if the ref is supposed to be a named branch
 795    if ref.startswith('refs/heads/branches/'):
 796        branch = ref[len('refs/heads/branches/'):]
 797        extra['branch'] = hgref(branch)
 798
 799    if mode == 'hg':
 800        i = data.find('\n--HG--\n')
 801        if i >= 0:
 802            tmp = data[i + len('\n--HG--\n'):].strip()
 803            for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
 804                if k == 'rename':
 805                    old, new = v.split(' => ', 1)
 806                    files[new]['rename'] = old
 807                elif k == 'branch':
 808                    extra[k] = v
 809                elif k == 'extra':
 810                    ek, ev = v.split(' : ', 1)
 811                    extra[ek] = urllib.unquote(ev)
 812            data = data[:i]
 813
 814    ctx = context.memctx(repo, (p1, p2), data,
 815            files.keys(), getfilectx,
 816            user, (date, tz), extra)
 817
 818    tmp = encoding.encoding
 819    encoding.encoding = 'utf-8'
 820
 821    node = hghex(repo.commitctx(ctx))
 822
 823    encoding.encoding = tmp
 824
 825    parsed_refs[ref] = node
 826    marks.new_mark(node, commit_mark)
 827
 828def parse_reset(parser):
 829    ref = parser[1]
 830    parser.next()
 831    # ugh
 832    if parser.check('commit'):
 833        parse_commit(parser)
 834        return
 835    if not parser.check('from'):
 836        return
 837    from_mark = parser.get_mark()
 838    parser.next()
 839
 840    try:
 841        rev = mark_to_rev(from_mark)
 842    except KeyError:
 843        rev = None
 844    parsed_refs[ref] = rev
 845
 846def parse_tag(parser):
 847    name = parser[1]
 848    parser.next()
 849    from_mark = parser.get_mark()
 850    parser.next()
 851    tagger = parser.get_author()
 852    parser.next()
 853    data = parser.get_data()
 854    parser.next()
 855
 856    parsed_tags[name] = (tagger, data)
 857
 858def write_tag(repo, tag, node, msg, author):
 859    branch = repo[node].branch()
 860    tip = branch_tip(branch)
 861    tip = repo[tip]
 862
 863    def getfilectx(repo, memctx, f):
 864        try:
 865            fctx = tip.filectx(f)
 866            data = fctx.data()
 867        except error.ManifestLookupError:
 868            data = ""
 869        content = data + "%s %s\n" % (node, tag)
 870        return context.memfilectx(f, content, False, False, None)
 871
 872    p1 = tip.hex()
 873    p2 = '0' * 40
 874    if author:
 875        user, date, tz = author
 876        date_tz = (date, tz)
 877    else:
 878        cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
 879        process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
 880        output, _ = process.communicate()
 881        m = re.match('^.* <.*>', output)
 882        if m:
 883            user = m.group(0)
 884        else:
 885            user = repo.ui.username()
 886        date_tz = None
 887
 888    ctx = context.memctx(repo, (p1, p2), msg,
 889            ['.hgtags'], getfilectx,
 890            user, date_tz, {'branch' : branch})
 891
 892    tmp = encoding.encoding
 893    encoding.encoding = 'utf-8'
 894
 895    tagnode = repo.commitctx(ctx)
 896
 897    encoding.encoding = tmp
 898
 899    return (tagnode, branch)
 900
 901def checkheads_bmark(repo, ref, ctx):
 902    bmark = ref[len('refs/heads/'):]
 903    if not bmark in bmarks:
 904        # new bmark
 905        return True
 906
 907    ctx_old = bmarks[bmark]
 908    ctx_new = ctx
 909    if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
 910        if force_push:
 911            print "ok %s forced update" % ref
 912        else:
 913            print "error %s non-fast forward" % ref
 914            return False
 915
 916    return True
 917
 918def checkheads(repo, remote, p_revs):
 919
 920    remotemap = remote.branchmap()
 921    if not remotemap:
 922        # empty repo
 923        return True
 924
 925    new = {}
 926    ret = True
 927
 928    for node, ref in p_revs.iteritems():
 929        ctx = repo[node]
 930        branch = ctx.branch()
 931        if not branch in remotemap:
 932            # new branch
 933            continue
 934        if not ref.startswith('refs/heads/branches'):
 935            if ref.startswith('refs/heads/'):
 936                if not checkheads_bmark(repo, ref, ctx):
 937                    ret = False
 938
 939            # only check branches
 940            continue
 941        new.setdefault(branch, []).append(ctx.rev())
 942
 943    for branch, heads in new.iteritems():
 944        old = [repo.changelog.rev(x) for x in remotemap[branch]]
 945        for rev in heads:
 946            if check_version(2, 3):
 947                ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
 948            else:
 949                ancestors = repo.changelog.ancestors(rev)
 950            found = False
 951
 952            for x in old:
 953                if x in ancestors:
 954                    found = True
 955                    break
 956
 957            if found:
 958                continue
 959
 960            node = repo.changelog.node(rev)
 961            ref = p_revs[node]
 962            if force_push:
 963                print "ok %s forced update" % ref
 964            else:
 965                print "error %s non-fast forward" % ref
 966                ret = False
 967
 968    return ret
 969
 970def push_unsafe(repo, remote, parsed_refs, p_revs):
 971
 972    force = force_push
 973
 974    fci = discovery.findcommonincoming
 975    commoninc = fci(repo, remote, force=force)
 976    common, _, remoteheads = commoninc
 977
 978    if not checkheads(repo, remote, p_revs):
 979        return None
 980
 981    cg = repo.getbundle('push', heads=list(p_revs), common=common)
 982
 983    unbundle = remote.capable('unbundle')
 984    if unbundle:
 985        if force:
 986            remoteheads = ['force']
 987        return remote.unbundle(cg, remoteheads, 'push')
 988    else:
 989        return remote.addchangegroup(cg, 'push', repo.url())
 990
 991def push(repo, remote, parsed_refs, p_revs):
 992    if hasattr(remote, 'canpush') and not remote.canpush():
 993        print "error cannot push"
 994
 995    if not p_revs:
 996        # nothing to push
 997        return
 998
 999    lock = None
1000    unbundle = remote.capable('unbundle')
1001    if not unbundle:
1002        lock = remote.lock()
1003    try:
1004        ret = push_unsafe(repo, remote, parsed_refs, p_revs)
1005    finally:
1006        if lock is not None:
1007            lock.release()
1008
1009    return ret
1010
1011def check_tip(ref, kind, name, heads):
1012    try:
1013        ename = '%s/%s' % (kind, name)
1014        tip = marks.get_tip(ename)
1015    except KeyError:
1016        return True
1017    else:
1018        return tip in heads
1019
1020def do_export(parser):
1021    p_bmarks = []
1022    p_revs = {}
1023
1024    parser.next()
1025
1026    for line in parser.each_block('done'):
1027        if parser.check('blob'):
1028            parse_blob(parser)
1029        elif parser.check('commit'):
1030            parse_commit(parser)
1031        elif parser.check('reset'):
1032            parse_reset(parser)
1033        elif parser.check('tag'):
1034            parse_tag(parser)
1035        elif parser.check('feature'):
1036            pass
1037        else:
1038            die('unhandled export command: %s' % line)
1039
1040    need_fetch = False
1041
1042    for ref, node in parsed_refs.iteritems():
1043        bnode = hgbin(node) if node else None
1044        if ref.startswith('refs/heads/branches'):
1045            branch = ref[len('refs/heads/branches/'):]
1046            if branch in branches and bnode in branches[branch]:
1047                # up to date
1048                continue
1049
1050            if peer:
1051                remotemap = peer.branchmap()
1052                if remotemap and branch in remotemap:
1053                    heads = [hghex(e) for e in remotemap[branch]]
1054                    if not check_tip(ref, 'branches', branch, heads):
1055                        print "error %s fetch first" % ref
1056                        need_fetch = True
1057                        continue
1058
1059            p_revs[bnode] = ref
1060            print "ok %s" % ref
1061        elif ref.startswith('refs/heads/'):
1062            bmark = ref[len('refs/heads/'):]
1063            new = node
1064            old = bmarks[bmark].hex() if bmark in bmarks else ''
1065
1066            if old == new:
1067                continue
1068
1069            print "ok %s" % ref
1070            if bmark != fake_bmark and \
1071                    not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1072                p_bmarks.append((ref, bmark, old, new))
1073
1074            if peer:
1075                remote_old = peer.listkeys('bookmarks').get(bmark)
1076                if remote_old:
1077                    if not check_tip(ref, 'bookmarks', bmark, remote_old):
1078                        print "error %s fetch first" % ref
1079                        need_fetch = True
1080                        continue
1081
1082            p_revs[bnode] = ref
1083        elif ref.startswith('refs/tags/'):
1084            if dry_run:
1085                print "ok %s" % ref
1086                continue
1087            tag = ref[len('refs/tags/'):]
1088            tag = hgref(tag)
1089            author, msg = parsed_tags.get(tag, (None, None))
1090            if mode == 'git':
1091                if not msg:
1092                    msg = 'Added tag %s for changeset %s' % (tag, node[:12])
1093                tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1094                p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1095            else:
1096                fp = parser.repo.opener('localtags', 'a')
1097                fp.write('%s %s\n' % (node, tag))
1098                fp.close()
1099            p_revs[bnode] = ref
1100            print "ok %s" % ref
1101        else:
1102            # transport-helper/fast-export bugs
1103            continue
1104
1105    if need_fetch:
1106        print
1107        return
1108
1109    if dry_run:
1110        if peer and not force_push:
1111            checkheads(parser.repo, peer, p_revs)
1112        print
1113        return
1114
1115    if peer:
1116        if not push(parser.repo, peer, parsed_refs, p_revs):
1117            # do not update bookmarks
1118            print
1119            return
1120
1121        # update remote bookmarks
1122        remote_bmarks = peer.listkeys('bookmarks')
1123        for ref, bmark, old, new in p_bmarks:
1124            if force_push:
1125                old = remote_bmarks.get(bmark, '')
1126            if not peer.pushkey('bookmarks', bmark, old, new):
1127                print "error %s" % ref
1128    else:
1129        # update local bookmarks
1130        for ref, bmark, old, new in p_bmarks:
1131            if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1132                print "error %s" % ref
1133
1134    print
1135
1136def do_option(parser):
1137    global dry_run, force_push
1138    _, key, value = parser.line.split(' ')
1139    if key == 'dry-run':
1140        dry_run = (value == 'true')
1141        print 'ok'
1142    elif key == 'force':
1143        force_push = (value == 'true')
1144        print 'ok'
1145    else:
1146        print 'unsupported'
1147
1148def fix_path(alias, repo, orig_url):
1149    url = urlparse.urlparse(orig_url, 'file')
1150    if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)):
1151        return
1152    abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1153    cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1154    subprocess.call(cmd)
1155
1156def main(args):
1157    global prefix, gitdir, dirname, branches, bmarks
1158    global marks, blob_marks, parsed_refs
1159    global peer, mode, bad_mail, bad_name
1160    global track_branches, force_push, is_tmp
1161    global parsed_tags
1162    global filenodes
1163    global fake_bmark, hg_version
1164    global dry_run
1165    global notes, alias
1166
1167    alias = args[1]
1168    url = args[2]
1169    peer = None
1170
1171    hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1172    track_branches = get_config_bool('remote-hg.track-branches', True)
1173    force_push = False
1174
1175    if hg_git_compat:
1176        mode = 'hg'
1177        bad_mail = 'none@none'
1178        bad_name = ''
1179    else:
1180        mode = 'git'
1181        bad_mail = 'unknown'
1182        bad_name = 'Unknown'
1183
1184    if alias[4:] == url:
1185        is_tmp = True
1186        alias = hashlib.sha1(alias).hexdigest()
1187    else:
1188        is_tmp = False
1189
1190    gitdir = os.environ['GIT_DIR']
1191    dirname = os.path.join(gitdir, 'hg', alias)
1192    branches = {}
1193    bmarks = {}
1194    blob_marks = {}
1195    parsed_refs = {}
1196    marks = None
1197    parsed_tags = {}
1198    filenodes = {}
1199    fake_bmark = None
1200    try:
1201        hg_version = tuple(int(e) for e in util.version().split('.'))
1202    except:
1203        hg_version = None
1204    dry_run = False
1205    notes = set()
1206
1207    repo = get_repo(url, alias)
1208    prefix = 'refs/hg/%s' % alias
1209
1210    if not is_tmp:
1211        fix_path(alias, peer or repo, url)
1212
1213    marks_path = os.path.join(dirname, 'marks-hg')
1214    marks = Marks(marks_path, repo)
1215
1216    if sys.platform == 'win32':
1217        import msvcrt
1218        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1219
1220    parser = Parser(repo)
1221    for line in parser:
1222        if parser.check('capabilities'):
1223            do_capabilities(parser)
1224        elif parser.check('list'):
1225            do_list(parser)
1226        elif parser.check('import'):
1227            do_import(parser)
1228        elif parser.check('export'):
1229            do_export(parser)
1230        elif parser.check('option'):
1231            do_option(parser)
1232        else:
1233            die('unhandled command: %s' % line)
1234        sys.stdout.flush()
1235
1236def bye():
1237    if not marks:
1238        return
1239    if not is_tmp:
1240        marks.store()
1241    else:
1242        shutil.rmtree(dirname)
1243
1244atexit.register(bye)
1245sys.exit(main(sys.argv))