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('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\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            name = m.group(1)
 320            mail = m.group(2)
 321        else:
 322            m = NAME_RE.match(user)
 323            if m:
 324                name = m.group(1).strip()
 325    return (name, mail)
 326def fixup_user_hg(user):
 328    def sanitize(name):
 329        # stole this from hg-git
 330        return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
 331    m = AUTHOR_HG_RE.match(user)
 333    if m:
 334        name = sanitize(m.group(1))
 335        mail = sanitize(m.group(2))
 336        ex = m.group(3)
 337        if ex:
 338            name += ' ext:(' + urllib.quote(ex) + ')'
 339    else:
 340        name = sanitize(user)
 341        if '@' in user:
 342            mail = name
 343        else:
 344            mail = None
 345    return (name, mail)
 347def fixup_user(user):
 349    if mode == 'git':
 350        name, mail = fixup_user_git(user)
 351    else:
 352        name, mail = fixup_user_hg(user)
 353    if not name:
 355        name = bad_name
 356    if not mail:
 357        mail = bad_mail
 358    return '%s <%s>' % (name, mail)
 360def updatebookmarks(repo, peer):
 362    remotemarks = peer.listkeys('bookmarks')
 363    localmarks = repo._bookmarks
 364    if not remotemarks:
 366        return
 367    for k, v in remotemarks.iteritems():
 369        localmarks[k] = hgbin(v)
 370    if hasattr(localmarks, 'write'):
 372        localmarks.write()
 373    else:
 374        bookmarks.write(repo)
 375def get_repo(url, alias):
 377    global peer
 378    myui = ui.ui()
 380    myui.setconfig('ui', 'interactive', 'off')
 381    myui.fout = sys.stderr
 382    if get_config_bool('remote-hg.insecure'):
 384        myui.setconfig('web', 'cacerts', '')
 385    extensions.loadall(myui)
 387    if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'):
 389        repo = hg.repository(myui, url)
 390        if not os.path.exists(dirname):
 391            os.makedirs(dirname)
 392    else:
 393        shared_path = os.path.join(gitdir, 'hg')
 394        # check and upgrade old organization
 396        hg_path = os.path.join(shared_path, '.hg')
 397        if os.path.exists(shared_path) and not os.path.exists(hg_path):
 398            repos = os.listdir(shared_path)
 399            for x in repos:
 400                local_hg = os.path.join(shared_path, x, 'clone', '.hg')
 401                if not os.path.exists(local_hg):
 402                    continue
 403                if not os.path.exists(hg_path):
 404                    shutil.move(local_hg, hg_path)
 405                shutil.rmtree(os.path.join(shared_path, x, 'clone'))
 406        # setup shared repo (if not there)
 408        try:
 409            hg.peer(myui, {}, shared_path, create=True)
 410        except error.RepoError:
 411            pass
 412        if not os.path.exists(dirname):
 414            os.makedirs(dirname)
 415        local_path = os.path.join(dirname, 'clone')
 417        if not os.path.exists(local_path):
 418            hg.share(myui, shared_path, local_path, update=False)
 419        repo = hg.repository(myui, local_path)
 421        try:
 422            peer = hg.peer(myui, {}, url)
 423        except:
 424            die('Repository error')
 425        repo.pull(peer, heads=None, force=True)
 426        updatebookmarks(repo, peer)
 428    return repo
 430def rev_to_mark(rev):
 432    return marks.from_rev(rev.hex())
 433def mark_to_rev(mark):
 435    return marks.to_rev(mark)
 436def export_ref(repo, name, kind, head):
 438    ename = '%s/%s' % (kind, name)
 439    try:
 440        tip = marks.get_tip(ename)
 441        tip = repo[tip].rev()
 442    except:
 443        tip = 0
 444    revs = xrange(tip, head.rev() + 1)
 446    total = len(revs)
 447    for rev in revs:
 449        c = repo[rev]
 451        node = c.node()
 452        if marks.is_marked(c.hex()):
 454            continue
 455        (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
 457        rev_branch = extra['branch']
 458        author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
 460        if 'committer' in extra:
 461            user, time, tz = extra['committer'].rsplit(' ', 2)
 462            committer = "%s %s %s" % (user, time, gittz(int(tz)))
 463        else:
 464            committer = author
 465        parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
 467        if len(parents) == 0:
 469            modified = c.manifest().keys()
 470            removed = []
 471        else:
 472            modified, removed = get_filechanges(repo, c, parents[0])
 473        desc += '\n'
 475        if mode == 'hg':
 477            extra_msg = ''
 478            if rev_branch != 'default':
 480                extra_msg += 'branch : %s\n' % rev_branch
 481            renames = []
 483            for f in c.files():
 484                if f not in c.manifest():
 485                    continue
 486                rename = c.filectx(f).renamed()
 487                if rename:
 488                    renames.append((rename[0], f))
 489            for e in renames:
 491                extra_msg += "rename : %s => %s\n" % e
 492            for key, value in extra.iteritems():
 494                if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
 495                    continue
 496                else:
 497                    extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
 498            if extra_msg:
 500                desc += '\n--HG--\n' + extra_msg
 501        if len(parents) == 0 and rev:
 503            print 'reset %s/%s' % (prefix, ename)
 504        modified_final = export_files(c.filectx(f) for f in modified)
 506        print "commit %s/%s" % (prefix, ename)
 508        print "mark :%d" % (marks.get_mark(c.hex()))
 509        print "author %s" % (author)
 510        print "committer %s" % (committer)
 511        print "data %d" % (len(desc))
 512        print desc
 513        if len(parents) > 0:
 515            print "from :%s" % (rev_to_mark(parents[0]))
 516            if len(parents) > 1:
 517                print "merge :%s" % (rev_to_mark(parents[1]))
 518        for f in removed:
 520            print "D %s" % (fix_file_path(f))
 521        for f in modified_final:
 522            print "M %s :%u %s" % f
 523        print
 524        progress = (rev - tip)
 526        if (progress % 100 == 0):
 527            print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
 528    # make sure the ref is updated
 530    print "reset %s/%s" % (prefix, ename)
 531    print "from :%u" % rev_to_mark(head)
 532    print
 533    pending_revs = set(revs) - notes
 535    if pending_revs:
 536        note_mark = marks.next_mark()
 537        ref = "refs/notes/hg"
 538        print "commit %s" % ref
 540        print "mark :%d" % (note_mark)
 541        print "committer remote-hg <> %s" % (ptime.strftime('%s %z'))
 542        desc = "Notes for %s\n" % (name)
 543        print "data %d" % (len(desc))
 544        print desc
 545        if marks.last_note:
 546            print "from :%u" % marks.last_note
 547        for rev in pending_revs:
 549            notes.add(rev)
 550            c = repo[rev]
 551            print "N inline :%u" % rev_to_mark(c)
 552            msg = c.hex()
 553            print "data %d" % (len(msg))
 554            print msg
 555        print
 556        marks.last_note = note_mark
 558    marks.set_tip(ename, head.hex())
 560def export_tag(repo, tag):
 562    export_ref(repo, tag, 'tags', repo[hgref(tag)])
 563def export_bookmark(repo, bmark):
 565    head = bmarks[hgref(bmark)]
 566    export_ref(repo, bmark, 'bookmarks', head)
 567def export_branch(repo, branch):
 569    tip = get_branch_tip(repo, branch)
 570    head = repo[tip]
 571    export_ref(repo, branch, 'branches', head)
 572def export_head(repo):
 574    export_ref(repo, g_head[0], 'bookmarks', g_head[1])
 575def do_capabilities(parser):
 577    print "import"
 578    print "export"
 579    print "refspec refs/heads/branches/*:%s/branches/*" % prefix
 580    print "refspec refs/heads/*:%s/bookmarks/*" % prefix
 581    print "refspec refs/tags/*:%s/tags/*" % prefix
 582    path = os.path.join(dirname, 'marks-git')
 584    if os.path.exists(path):
 586        print "*import-marks %s" % path
 587    print "*export-marks %s" % path
 588    print "option"
 589    print
 591def branch_tip(branch):
 593    return branches[branch][-1]
 594def get_branch_tip(repo, branch):
 596    heads = branches.get(hgref(branch), None)
 597    if not heads:
 598        return None
 599    # verify there's only one head
 601    if (len(heads) > 1):
 602        warn("Branch '%s' has more than one head, consider merging" % branch)
 603        return branch_tip(hgref(branch))
 604    return heads[0]
 606def list_head(repo, cur):
 608    global g_head, fake_bmark
 609    if 'default' not in branches:
 611        # empty repo
 612        return
 613    node = repo[branch_tip('default')]
 615    head = 'master' if not 'master' in bmarks else 'default'
 616    fake_bmark = head
 617    bmarks[head] = node
 618    head = gitref(head)
 620    print "@refs/heads/%s HEAD" % head
 621    g_head = (head, node)
 622def do_list(parser):
 624    repo = parser.repo
 625    for bmark, node in bookmarks.listbookmarks(repo).iteritems():
 626        bmarks[bmark] = repo[node]
 627    cur = repo.dirstate.branch()
 629    orig = peer if peer else repo
 630    for branch, heads in orig.branchmap().iteritems():
 632        # only open heads
 633        heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
 634        if heads:
 635            branches[branch] = heads
 636    list_head(repo, cur)
 638    if track_branches:
 640        for branch in branches:
 641            print "? refs/heads/branches/%s" % gitref(branch)
 642    for bmark in bmarks:
 644        print "? refs/heads/%s" % gitref(bmark)
 645    for tag, node in repo.tagslist():
 647        if tag == 'tip':
 648            continue
 649        print "? refs/tags/%s" % gitref(tag)
 650    print
 652def do_import(parser):
 654    repo = parser.repo
 655    path = os.path.join(dirname, 'marks-git')
 657    print "feature done"
 659    if os.path.exists(path):
 660        print "feature import-marks=%s" % path
 661    print "feature export-marks=%s" % path
 662    print "feature force"
 663    sys.stdout.flush()
 664    tmp = encoding.encoding
 666    encoding.encoding = 'utf-8'
 667    # lets get all the import lines
 669    while parser.check('import'):
 670        ref = parser[1]
 671        if (ref == 'HEAD'):
 673            export_head(repo)
 674        elif ref.startswith('refs/heads/branches/'):
 675            branch = ref[len('refs/heads/branches/'):]
 676            export_branch(repo, branch)
 677        elif ref.startswith('refs/heads/'):
 678            bmark = ref[len('refs/heads/'):]
 679            export_bookmark(repo, bmark)
 680        elif ref.startswith('refs/tags/'):
 681            tag = ref[len('refs/tags/'):]
 682            export_tag(repo, tag)
 683        parser.next()
 685    encoding.encoding = tmp
 687    print 'done'
 689def parse_blob(parser):
 691    parser.next()
 692    mark = parser.get_mark()
 693    parser.next()
 694    data = parser.get_data()
 695    blob_marks[mark] = data
 696    parser.next()
 697def get_merge_files(repo, p1, p2, files):
 699    for e in repo[p1].files():
 700        if e not in files:
 701            if e not in repo[p1].manifest():
 702                continue
 703            f = { 'ctx' : repo[p1][e] }
 704            files[e] = f
 705def c_style_unescape(string):
 707    if string[0] == string[-1] == '"':
 708        return string.decode('string-escape')[1:-1]
 709    return string
 710def parse_commit(parser):
 712    from_mark = merge_mark = None
 713    ref = parser[1]
 715    parser.next()
 716    commit_mark = parser.get_mark()
 718    parser.next()
 719    author = parser.get_author()
 720    parser.next()
 721    committer = parser.get_author()
 722    parser.next()
 723    data = parser.get_data()
 724    parser.next()
 725    if parser.check('from'):
 726        from_mark = parser.get_mark()
 727        parser.next()
 728    if parser.check('merge'):
 729        merge_mark = parser.get_mark()
 730        parser.next()
 731        if parser.check('merge'):
 732            die('octopus merges are not supported yet')
 733    # fast-export adds an extra newline
 735    if data[-1] == '\n':
 736        data = data[:-1]
 737    files = {}
 739    for line in parser:
 741        if parser.check('M'):
 742            t, m, mark_ref, path = line.split(' ', 3)
 743            mark = int(mark_ref[1:])
 744            f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
 745        elif parser.check('D'):
 746            t, path = line.split(' ', 1)
 747            f = { 'deleted' : True }
 748        else:
 749            die('Unknown file command: %s' % line)
 750        path = c_style_unescape(path).decode('utf-8')
 751        files[path] = f
 752    # only export the commits if we are on an internal proxy repo
 754    if dry_run and not peer:
 755        parsed_refs[ref] = None
 756        return
 757    def getfilectx(repo, memctx, f):
 759        of = files[f]
 760        if 'deleted' in of:
 761            raise IOError
 762        if 'ctx' in of:
 763            return of['ctx']
 764        is_exec = of['mode'] == 'x'
 765        is_link = of['mode'] == 'l'
 766        rename = of.get('rename', None)
 767        return context.memfilectx(f, of['data'],
 768                is_link, is_exec, rename)
 769    repo = parser.repo
 771    user, date, tz = author
 773    extra = {}
 774    if committer != author:
 776        extra['committer'] = "%s %u %u" % committer
 777    if from_mark:
 779        p1 = mark_to_rev(from_mark)
 780    else:
 781        p1 = '0' * 40
 782    if merge_mark:
 784        p2 = mark_to_rev(merge_mark)
 785    else:
 786        p2 = '0' * 40
 787    #
 789    # If files changed from any of the parents, hg wants to know, but in git if
 790    # nothing changed from the first parent, nothing changed.
 791    #
 792    if merge_mark:
 793        get_merge_files(repo, p1, p2, files)
 794    # Check if the ref is supposed to be a named branch
 796    if ref.startswith('refs/heads/branches/'):
 797        branch = ref[len('refs/heads/branches/'):]
 798        extra['branch'] = hgref(branch)
 799    if mode == 'hg':
 801        i = data.find('\n--HG--\n')
 802        if i >= 0:
 803            tmp = data[i + len('\n--HG--\n'):].strip()
 804            for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
 805                if k == 'rename':
 806                    old, new = v.split(' => ', 1)
 807                    files[new]['rename'] = old
 808                elif k == 'branch':
 809                    extra[k] = v
 810                elif k == 'extra':
 811                    ek, ev = v.split(' : ', 1)
 812                    extra[ek] = urllib.unquote(ev)
 813            data = data[:i]
 814    ctx = context.memctx(repo, (p1, p2), data,
 816            files.keys(), getfilectx,
 817            user, (date, tz), extra)
 818    tmp = encoding.encoding
 820    encoding.encoding = 'utf-8'
 821    node = hghex(repo.commitctx(ctx))
 823    encoding.encoding = tmp
 825    parsed_refs[ref] = node
 827    marks.new_mark(node, commit_mark)
 828def parse_reset(parser):
 830    ref = parser[1]
 831    parser.next()
 832    # ugh
 833    if parser.check('commit'):
 834        parse_commit(parser)
 835        return
 836    if not parser.check('from'):
 837        return
 838    from_mark = parser.get_mark()
 839    parser.next()
 840    try:
 842        rev = mark_to_rev(from_mark)
 843    except KeyError:
 844        rev = None
 845    parsed_refs[ref] = rev
 846def parse_tag(parser):
 848    name = parser[1]
 849    parser.next()
 850    from_mark = parser.get_mark()
 851    parser.next()
 852    tagger = parser.get_author()
 853    parser.next()
 854    data = parser.get_data()
 855    parser.next()
 856    parsed_tags[name] = (tagger, data)
 858def write_tag(repo, tag, node, msg, author):
 860    branch = repo[node].branch()
 861    tip = branch_tip(branch)
 862    tip = repo[tip]
 863    def getfilectx(repo, memctx, f):
 865        try:
 866            fctx = tip.filectx(f)
 867            data = fctx.data()
 868        except error.ManifestLookupError:
 869            data = ""
 870        content = data + "%s %s\n" % (node, tag)
 871        return context.memfilectx(f, content, False, False, None)
 872    p1 = tip.hex()
 874    p2 = '0' * 40
 875    if author:
 876        user, date, tz = author
 877        date_tz = (date, tz)
 878    else:
 879        cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
 880        process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
 881        output, _ = process.communicate()
 882        m = re.match('^.* <.*>', output)
 883        if m:
 884            user = m.group(0)
 885        else:
 886            user = repo.ui.username()
 887        date_tz = None
 888    ctx = context.memctx(repo, (p1, p2), msg,
 890            ['.hgtags'], getfilectx,
 891            user, date_tz, {'branch' : branch})
 892    tmp = encoding.encoding
 894    encoding.encoding = 'utf-8'
 895    tagnode = repo.commitctx(ctx)
 897    encoding.encoding = tmp
 899    return (tagnode, branch)
 901def checkheads_bmark(repo, ref, ctx):
 903    bmark = ref[len('refs/heads/'):]
 904    if not bmark in bmarks:
 905        # new bmark
 906        return True
 907    ctx_old = bmarks[bmark]
 909    ctx_new = ctx
 910    if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
 911        if force_push:
 912            print "ok %s forced update" % ref
 913        else:
 914            print "error %s non-fast forward" % ref
 915            return False
 916    return True
 918def checkheads(repo, remote, p_revs):
 920    remotemap = remote.branchmap()
 922    if not remotemap:
 923        # empty repo
 924        return True
 925    new = {}
 927    ret = True
 928    for node, ref in p_revs.iteritems():
 930        ctx = repo[node]
 931        branch = ctx.branch()
 932        if not branch in remotemap:
 933            # new branch
 934            continue
 935        if not ref.startswith('refs/heads/branches'):
 936            if ref.startswith('refs/heads/'):
 937                if not checkheads_bmark(repo, ref, ctx):
 938                    ret = False
 939            # only check branches
 941            continue
 942        new.setdefault(branch, []).append(ctx.rev())
 943    for branch, heads in new.iteritems():
 945        old = [repo.changelog.rev(x) for x in remotemap[branch]]
 946        for rev in heads:
 947            if check_version(2, 3):
 948                ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
 949            else:
 950                ancestors = repo.changelog.ancestors(rev)
 951            found = False
 952            for x in old:
 954                if x in ancestors:
 955                    found = True
 956                    break
 957            if found:
 959                continue
 960            node = repo.changelog.node(rev)
 962            ref = p_revs[node]
 963            if force_push:
 964                print "ok %s forced update" % ref
 965            else:
 966                print "error %s non-fast forward" % ref
 967                ret = False
 968    return ret
 970def push_unsafe(repo, remote, parsed_refs, p_revs):
 972    force = force_push
 974    fci = discovery.findcommonincoming
 976    commoninc = fci(repo, remote, force=force)
 977    common, _, remoteheads = commoninc
 978    if not checkheads(repo, remote, p_revs):
 980        return None
 981    cg = repo.getbundle('push', heads=list(p_revs), common=common)
 983    unbundle = remote.capable('unbundle')
 985    if unbundle:
 986        if force:
 987            remoteheads = ['force']
 988        return remote.unbundle(cg, remoteheads, 'push')
 989    else:
 990        return remote.addchangegroup(cg, 'push', repo.url())
 991def push(repo, remote, parsed_refs, p_revs):
 993    if hasattr(remote, 'canpush') and not remote.canpush():
 994        print "error cannot push"
 995    if not p_revs:
 997        # nothing to push
 998        return
 999    lock = None
1001    unbundle = remote.capable('unbundle')
1002    if not unbundle:
1003        lock = remote.lock()
1004    try:
1005        ret = push_unsafe(repo, remote, parsed_refs, p_revs)
1006    finally:
1007        if lock is not None:
1008            lock.release()
1009    return ret
1011def check_tip(ref, kind, name, heads):
1013    try:
1014        ename = '%s/%s' % (kind, name)
1015        tip = marks.get_tip(ename)
1016    except KeyError:
1017        return True
1018    else:
1019        return tip in heads
1020def do_export(parser):
1022    p_bmarks = []
1023    p_revs = {}
1024    parser.next()
1026    for line in parser.each_block('done'):
1028        if parser.check('blob'):
1029            parse_blob(parser)
1030        elif parser.check('commit'):
1031            parse_commit(parser)
1032        elif parser.check('reset'):
1033            parse_reset(parser)
1034        elif parser.check('tag'):
1035            parse_tag(parser)
1036        elif parser.check('feature'):
1037            pass
1038        else:
1039            die('unhandled export command: %s' % line)
1040    need_fetch = False
1042    for ref, node in parsed_refs.iteritems():
1044        bnode = hgbin(node) if node else None
1045        if ref.startswith('refs/heads/branches'):
1046            branch = ref[len('refs/heads/branches/'):]
1047            if branch in branches and bnode in branches[branch]:
1048                # up to date
1049                continue
1050            if peer:
1052                remotemap = peer.branchmap()
1053                if remotemap and branch in remotemap:
1054                    heads = [hghex(e) for e in remotemap[branch]]
1055                    if not check_tip(ref, 'branches', branch, heads):
1056                        print "error %s fetch first" % ref
1057                        need_fetch = True
1058                        continue
1059            p_revs[bnode] = ref
1061            print "ok %s" % ref
1062        elif ref.startswith('refs/heads/'):
1063            bmark = ref[len('refs/heads/'):]
1064            new = node
1065            old = bmarks[bmark].hex() if bmark in bmarks else ''
1066            if old == new:
1068                continue
1069            print "ok %s" % ref
1071            if bmark != fake_bmark and \
1072                    not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1073                p_bmarks.append((ref, bmark, old, new))
1074            if peer:
1076                remote_old = peer.listkeys('bookmarks').get(bmark)
1077                if remote_old:
1078                    if not check_tip(ref, 'bookmarks', bmark, remote_old):
1079                        print "error %s fetch first" % ref
1080                        need_fetch = True
1081                        continue
1082            p_revs[bnode] = ref
1084        elif ref.startswith('refs/tags/'):
1085            if dry_run:
1086                print "ok %s" % ref
1087                continue
1088            tag = ref[len('refs/tags/'):]
1089            tag = hgref(tag)
1090            author, msg = parsed_tags.get(tag, (None, None))
1091            if mode == 'git':
1092                if not msg:
1093                    msg = 'Added tag %s for changeset %s' % (tag, node[:12])
1094                tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1095                p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1096            else:
1097                fp = parser.repo.opener('localtags', 'a')
1098                fp.write('%s %s\n' % (node, tag))
1099                fp.close()
1100            p_revs[bnode] = ref
1101            print "ok %s" % ref
1102        else:
1103            # transport-helper/fast-export bugs
1104            continue
1105    if need_fetch:
1107        print
1108        return
1109    if dry_run:
1111        if peer and not force_push:
1112            checkheads(parser.repo, peer, p_revs)
1113        print
1114        return
1115    if peer:
1117        if not push(parser.repo, peer, parsed_refs, p_revs):
1118            # do not update bookmarks
1119            print
1120            return
1121        # update remote bookmarks
1123        remote_bmarks = peer.listkeys('bookmarks')
1124        for ref, bmark, old, new in p_bmarks:
1125            if force_push:
1126                old = remote_bmarks.get(bmark, '')
1127            if not peer.pushkey('bookmarks', bmark, old, new):
1128                print "error %s" % ref
1129    else:
1130        # update local bookmarks
1131        for ref, bmark, old, new in p_bmarks:
1132            if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1133                print "error %s" % ref
1134    print
1136def do_option(parser):
1138    global dry_run, force_push
1139    _, key, value = parser.line.split(' ')
1140    if key == 'dry-run':
1141        dry_run = (value == 'true')
1142        print 'ok'
1143    elif key == 'force':
1144        force_push = (value == 'true')
1145        print 'ok'
1146    else:
1147        print 'unsupported'
1148def fix_path(alias, repo, orig_url):
1150    url = urlparse.urlparse(orig_url, 'file')
1151    if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)):
1152        return
1153    abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1154    cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1155    subprocess.call(cmd)
1156def main(args):
1158    global prefix, gitdir, dirname, branches, bmarks
1159    global marks, blob_marks, parsed_refs
1160    global peer, mode, bad_mail, bad_name
1161    global track_branches, force_push, is_tmp
1162    global parsed_tags
1163    global filenodes
1164    global fake_bmark, hg_version
1165    global dry_run
1166    global notes, alias
1167    alias = args[1]
1169    url = args[2]
1170    peer = None
1171    hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1173    track_branches = get_config_bool('remote-hg.track-branches', True)
1174    force_push = False
1175    if hg_git_compat:
1177        mode = 'hg'
1178        bad_mail = 'none@none'
1179        bad_name = ''
1180    else:
1181        mode = 'git'
1182        bad_mail = 'unknown'
1183        bad_name = 'Unknown'
1184    if alias[4:] == url:
1186        is_tmp = True
1187        alias = hashlib.sha1(alias).hexdigest()
1188    else:
1189        is_tmp = False
1190    gitdir = os.environ['GIT_DIR']
1192    dirname = os.path.join(gitdir, 'hg', alias)
1193    branches = {}
1194    bmarks = {}
1195    blob_marks = {}
1196    parsed_refs = {}
1197    marks = None
1198    parsed_tags = {}
1199    filenodes = {}
1200    fake_bmark = None
1201    try:
1202        hg_version = tuple(int(e) for e in util.version().split('.'))
1203    except:
1204        hg_version = None
1205    dry_run = False
1206    notes = set()
1207    repo = get_repo(url, alias)
1209    prefix = 'refs/hg/%s' % alias
1210    if not is_tmp:
1212        fix_path(alias, peer or repo, url)
1213    marks_path = os.path.join(dirname, 'marks-hg')
1215    marks = Marks(marks_path, repo)
1216    if sys.platform == 'win32':
1218        import msvcrt
1219        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1220    parser = Parser(repo)
1222    for line in parser:
1223        if parser.check('capabilities'):
1224            do_capabilities(parser)
1225        elif parser.check('list'):
1226            do_list(parser)
1227        elif parser.check('import'):
1228            do_import(parser)
1229        elif parser.check('export'):
1230            do_export(parser)
1231        elif parser.check('option'):
1232            do_option(parser)
1233        else:
1234            die('unhandled command: %s' % line)
1235        sys.stdout.flush()
1236def bye():
1238    if not marks:
1239        return
1240    if not is_tmp:
1241        marks.store()
1242    else:
1243        shutil.rmtree(dirname)
1244atexit.register(bye)
1246sys.exit(main(sys.argv))