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