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