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