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