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