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