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
  26#
  28# If you are not in hg-git-compat mode and want to disable the tracking of
  29# named branches:
  30# git config --global remote-hg.track-branches false
  31#
  32# If you want the equivalent of hg's clone/pull--insecure option:
  33# git config --global remote-hg.insecure true
  34#
  35# If you want to switch to hg-git compatibility mode:
  36# git config --global remote-hg.hg-git-compat true
  37#
  38# git:
  39# Sensible defaults for git.
  40# hg bookmarks are exported as git branches, hg branches are prefixed
  41# with 'branches/', HEAD is a special case.
  42#
  43# hg:
  44# Emulate hg-git.
  45# Only hg bookmarks are exported as git branches.
  46# Commits are modified to preserve hg information and allow bidirectionality.
  47#
  48NAME_RE = re.compile('^([^<>]+)')
  50AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
  51EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
  52AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
  53RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
  54VERSION = 2
  56def die(msg, *args):
  58    sys.stderr.write('ERROR: %s\n' % (msg % args))
  59    sys.exit(1)
  60def warn(msg, *args):
  62    sys.stderr.write('WARNING: %s\n' % (msg % args))
  63def gitmode(flags):
  65    return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
  66def gittz(tz):
  68    return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
  69def hgmode(mode):
  71    m = { '100755': 'x', '120000': 'l' }
  72    return m.get(mode, '')
  73def hghex(n):
  75    return node.hex(n)
  76def hgbin(n):
  78    return node.bin(n)
  79def hgref(ref):
  81    return ref.replace('___', ' ')
  82def gitref(ref):
  84    return ref.replace(' ', '___')
  85def check_version(*check):
  87    if not hg_version:
  88        return True
  89    return hg_version >= check
  90def get_config(config):
  92    cmd = ['git', 'config', '--get', config]
  93    process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
  94    output, _ = process.communicate()
  95    return output
  96def get_config_bool(config, default=False):
  98    value = get_config(config).rstrip('\n')
  99    if value == "true":
 100        return True
 101    elif value == "false":
 102        return False
 103    else:
 104        return default
 105class Marks:
 107    def __init__(self, path, repo):
 109        self.path = path
 110        self.repo = repo
 111        self.clear()
 112        self.load()
 113        if self.version < VERSION:
 115            if self.version == 1:
 116                self.upgrade_one()
 117            # upgraded?
 119            if self.version < VERSION:
 120                self.clear()
 121                self.version = VERSION
 122    def clear(self):
 124        self.tips = {}
 125        self.marks = {}
 126        self.rev_marks = {}
 127        self.last_mark = 0
 128        self.version = 0
 129    def load(self):
 131        if not os.path.exists(self.path):
 132            return
 133        tmp = json.load(open(self.path))
 135        self.tips = tmp['tips']
 137        self.marks = tmp['marks']
 138        self.last_mark = tmp['last-mark']
 139        self.version = tmp.get('version', 1)
 140        for rev, mark in self.marks.iteritems():
 142            self.rev_marks[mark] = rev
 143    def upgrade_one(self):
 145        def get_id(rev):
 146            return hghex(self.repo.changelog.node(int(rev)))
 147        self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems())
 148        self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems())
 149        self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems())
 150        self.version = 2
 151    def dict(self):
 153        return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version }
 154    def store(self):
 156        json.dump(self.dict(), open(self.path, 'w'))
 157    def __str__(self):
 159        return str(self.dict())
 160    def from_rev(self, rev):
 162        return self.marks[rev]
 163    def to_rev(self, mark):
 165        return str(self.rev_marks[mark])
 166    def next_mark(self):
 168        self.last_mark += 1
 169        return self.last_mark
 170    def get_mark(self, rev):
 172        self.last_mark += 1
 173        self.marks[rev] = self.last_mark
 174        return self.last_mark
 175    def new_mark(self, rev, mark):
 177        self.marks[rev] = mark
 178        self.rev_marks[mark] = rev
 179        self.last_mark = mark
 180    def is_marked(self, rev):
 182        return rev in self.marks
 183    def get_tip(self, branch):
 185        return str(self.tips[branch])
 186    def set_tip(self, branch, tip):
 188        self.tips[branch] = tip
 189class Parser:
 191    def __init__(self, repo):
 193        self.repo = repo
 194        self.line = self.get_line()
 195    def get_line(self):
 197        return sys.stdin.readline().strip()
 198    def __getitem__(self, i):
 200        return self.line.split()[i]
 201    def check(self, word):
 203        return self.line.startswith(word)
 204    def each_block(self, separator):
 206        while self.line != separator:
 207            yield self.line
 208            self.line = self.get_line()
 209    def __iter__(self):
 211        return self.each_block('')
 212    def next(self):
 214        self.line = self.get_line()
 215        if self.line == 'done':
 216            self.line = None
 217    def get_mark(self):
 219        i = self.line.index(':') + 1
 220        return int(self.line[i:])
 221    def get_data(self):
 223        if not self.check('data'):
 224            return None
 225        i = self.line.index(' ') + 1
 226        size = int(self.line[i:])
 227        return sys.stdin.read(size)
 228    def get_author(self):
 230        global bad_mail
 231        ex = None
 233        m = RAW_AUTHOR_RE.match(self.line)
 234        if not m:
 235            return None
 236        _, name, email, date, tz = m.groups()
 237        if name and 'ext:' in name:
 238            m = re.match('^(.+?) ext:\((.+)\)$', name)
 239            if m:
 240                name = m.group(1)
 241                ex = urllib.unquote(m.group(2))
 242        if email != bad_mail:
 244            if name:
 245                user = '%s <%s>' % (name, email)
 246            else:
 247                user = '<%s>' % (email)
 248        else:
 249            user = name
 250        if ex:
 252            user += ex
 253        tz = int(tz)
 255        tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
 256        return (user, int(date), -tz)
 257def fix_file_path(path):
 259    if not os.path.isabs(path):
 260        return path
 261    return os.path.relpath(path, '/')
 262def export_files(files):
 264    global marks, filenodes
 265    final = []
 267    for f in files:
 268        fid = node.hex(f.filenode())
 269        if fid in filenodes:
 271            mark = filenodes[fid]
 272        else:
 273            mark = marks.next_mark()
 274            filenodes[fid] = mark
 275            d = f.data()
 276            print "blob"
 278            print "mark :%u" % mark
 279            print "data %d" % len(d)
 280            print d
 281        path = fix_file_path(f.path())
 283        final.append((gitmode(f.flags()), mark, path))
 284    return final
 286def get_filechanges(repo, ctx, parent):
 288    modified = set()
 289    added = set()
 290    removed = set()
 291    # load earliest manifest first for caching reasons
 293    prev = parent.manifest().copy()
 294    cur = ctx.manifest()
 295    for fn in cur:
 297        if fn in prev:
 298            if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
 299                modified.add(fn)
 300            del prev[fn]
 301        else:
 302            added.add(fn)
 303    removed |= set(prev.keys())
 304    return added | modified, removed
 306def fixup_user_git(user):
 308    name = mail = None
 309    user = user.replace('"', '')
 310    m = AUTHOR_RE.match(user)
 311    if m:
 312        name = m.group(1)
 313        mail = m.group(2).strip()
 314    else:
 315        m = EMAIL_RE.match(user)
 316        if m:
 317            name = m.group(1)
 318            mail = m.group(2)
 319        else:
 320            m = NAME_RE.match(user)
 321            if m:
 322                name = m.group(1).strip()
 323    return (name, mail)
 324def fixup_user_hg(user):
 326    def sanitize(name):
 327        # stole this from hg-git
 328        return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
 329    m = AUTHOR_HG_RE.match(user)
 331    if m:
 332        name = sanitize(m.group(1))
 333        mail = sanitize(m.group(2))
 334        ex = m.group(3)
 335        if ex:
 336            name += ' ext:(' + urllib.quote(ex) + ')'
 337    else:
 338        name = sanitize(user)
 339        if '@' in user:
 340            mail = name
 341        else:
 342            mail = None
 343    return (name, mail)
 345def fixup_user(user):
 347    global mode, bad_mail
 348    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 dirname, 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        if not os.path.exists(shared_path):
 395            try:
 396                hg.clone(myui, {}, url, shared_path, update=False, pull=True)
 397            except:
 398                die('Repository error')
 399        if not os.path.exists(dirname):
 401            os.makedirs(dirname)
 402        local_path = os.path.join(dirname, 'clone')
 404        if not os.path.exists(local_path):
 405            hg.share(myui, shared_path, local_path, update=False)
 406        repo = hg.repository(myui, local_path)
 408        try:
 409            peer = hg.peer(myui, {}, url)
 410        except:
 411            die('Repository error')
 412        repo.pull(peer, heads=None, force=True)
 413        updatebookmarks(repo, peer)
 415    return repo
 417def rev_to_mark(rev):
 419    global marks
 420    return marks.from_rev(rev.hex())
 421def mark_to_rev(mark):
 423    global marks
 424    return marks.to_rev(mark)
 425def export_ref(repo, name, kind, head):
 427    global prefix, marks, mode
 428    ename = '%s/%s' % (kind, name)
 430    try:
 431        tip = marks.get_tip(ename)
 432        tip = repo[tip].rev()
 433    except:
 434        tip = 0
 435    revs = xrange(tip, head.rev() + 1)
 437    total = len(revs)
 438    for rev in revs:
 440        c = repo[rev]
 442        node = c.node()
 443        if marks.is_marked(c.hex()):
 445            continue
 446        (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
 448        rev_branch = extra['branch']
 449        author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
 451        if 'committer' in extra:
 452            user, time, tz = extra['committer'].rsplit(' ', 2)
 453            committer = "%s %s %s" % (user, time, gittz(int(tz)))
 454        else:
 455            committer = author
 456        parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
 458        if len(parents) == 0:
 460            modified = c.manifest().keys()
 461            removed = []
 462        else:
 463            modified, removed = get_filechanges(repo, c, parents[0])
 464        desc += '\n'
 466        if mode == 'hg':
 468            extra_msg = ''
 469            if rev_branch != 'default':
 471                extra_msg += 'branch : %s\n' % rev_branch
 472            renames = []
 474            for f in c.files():
 475                if f not in c.manifest():
 476                    continue
 477                rename = c.filectx(f).renamed()
 478                if rename:
 479                    renames.append((rename[0], f))
 480            for e in renames:
 482                extra_msg += "rename : %s => %s\n" % e
 483            for key, value in extra.iteritems():
 485                if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
 486                    continue
 487                else:
 488                    extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
 489            if extra_msg:
 491                desc += '\n--HG--\n' + extra_msg
 492        if len(parents) == 0 and rev:
 494            print 'reset %s/%s' % (prefix, ename)
 495        modified_final = export_files(c.filectx(f) for f in modified)
 497        print "commit %s/%s" % (prefix, ename)
 499        print "mark :%d" % (marks.get_mark(c.hex()))
 500        print "author %s" % (author)
 501        print "committer %s" % (committer)
 502        print "data %d" % (len(desc))
 503        print desc
 504        if len(parents) > 0:
 506            print "from :%s" % (rev_to_mark(parents[0]))
 507            if len(parents) > 1:
 508                print "merge :%s" % (rev_to_mark(parents[1]))
 509        for f in removed:
 511            print "D %s" % (fix_file_path(f))
 512        for f in modified_final:
 513            print "M %s :%u %s" % f
 514        print
 515        progress = (rev - tip)
 517        if (progress % 100 == 0):
 518            print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
 519    # make sure the ref is updated
 521    print "reset %s/%s" % (prefix, ename)
 522    print "from :%u" % rev_to_mark(head)
 523    print
 524    marks.set_tip(ename, head.hex())
 526def export_tag(repo, tag):
 528    export_ref(repo, tag, 'tags', repo[hgref(tag)])
 529def export_bookmark(repo, bmark):
 531    head = bmarks[hgref(bmark)]
 532    export_ref(repo, bmark, 'bookmarks', head)
 533def export_branch(repo, branch):
 535    tip = get_branch_tip(repo, branch)
 536    head = repo[tip]
 537    export_ref(repo, branch, 'branches', head)
 538def export_head(repo):
 540    global g_head
 541    export_ref(repo, g_head[0], 'bookmarks', g_head[1])
 542def do_capabilities(parser):
 544    global prefix, dirname
 545    print "import"
 547    print "export"
 548    print "refspec refs/heads/branches/*:%s/branches/*" % prefix
 549    print "refspec refs/heads/*:%s/bookmarks/*" % prefix
 550    print "refspec refs/tags/*:%s/tags/*" % prefix
 551    path = os.path.join(dirname, 'marks-git')
 553    if os.path.exists(path):
 555        print "*import-marks %s" % path
 556    print "*export-marks %s" % path
 557    print "option"
 558    print
 560def branch_tip(branch):
 562    return branches[branch][-1]
 563def get_branch_tip(repo, branch):
 565    global branches
 566    heads = branches.get(hgref(branch), None)
 568    if not heads:
 569        return None
 570    # verify there's only one head
 572    if (len(heads) > 1):
 573        warn("Branch '%s' has more than one head, consider merging" % branch)
 574        return branch_tip(hgref(branch))
 575    return heads[0]
 577def list_head(repo, cur):
 579    global g_head, bmarks, fake_bmark
 580    if 'default' not in branches:
 582        # empty repo
 583        return
 584    node = repo[branch_tip('default')]
 586    head = 'master' if not 'master' in bmarks else 'default'
 587    fake_bmark = head
 588    bmarks[head] = node
 589    head = gitref(head)
 591    print "@refs/heads/%s HEAD" % head
 592    g_head = (head, node)
 593def do_list(parser):
 595    global branches, bmarks, track_branches
 596    repo = parser.repo
 598    for bmark, node in bookmarks.listbookmarks(repo).iteritems():
 599        bmarks[bmark] = repo[node]
 600    cur = repo.dirstate.branch()
 602    orig = peer if peer else repo
 603    for branch, heads in orig.branchmap().iteritems():
 605        # only open heads
 606        heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
 607        if heads:
 608            branches[branch] = heads
 609    list_head(repo, cur)
 611    if track_branches:
 613        for branch in branches:
 614            print "? refs/heads/branches/%s" % gitref(branch)
 615    for bmark in bmarks:
 617        print "? refs/heads/%s" % gitref(bmark)
 618    for tag, node in repo.tagslist():
 620        if tag == 'tip':
 621            continue
 622        print "? refs/tags/%s" % gitref(tag)
 623    print
 625def do_import(parser):
 627    repo = parser.repo
 628    path = os.path.join(dirname, 'marks-git')
 630    print "feature done"
 632    if os.path.exists(path):
 633        print "feature import-marks=%s" % path
 634    print "feature export-marks=%s" % path
 635    print "feature force"
 636    sys.stdout.flush()
 637    tmp = encoding.encoding
 639    encoding.encoding = 'utf-8'
 640    # lets get all the import lines
 642    while parser.check('import'):
 643        ref = parser[1]
 644        if (ref == 'HEAD'):
 646            export_head(repo)
 647        elif ref.startswith('refs/heads/branches/'):
 648            branch = ref[len('refs/heads/branches/'):]
 649            export_branch(repo, branch)
 650        elif ref.startswith('refs/heads/'):
 651            bmark = ref[len('refs/heads/'):]
 652            export_bookmark(repo, bmark)
 653        elif ref.startswith('refs/tags/'):
 654            tag = ref[len('refs/tags/'):]
 655            export_tag(repo, tag)
 656        parser.next()
 658    encoding.encoding = tmp
 660    print 'done'
 662def parse_blob(parser):
 664    global blob_marks
 665    parser.next()
 667    mark = parser.get_mark()
 668    parser.next()
 669    data = parser.get_data()
 670    blob_marks[mark] = data
 671    parser.next()
 672def get_merge_files(repo, p1, p2, files):
 674    for e in repo[p1].files():
 675        if e not in files:
 676            if e not in repo[p1].manifest():
 677                continue
 678            f = { 'ctx' : repo[p1][e] }
 679            files[e] = f
 680def parse_commit(parser):
 682    global marks, blob_marks, parsed_refs
 683    global mode
 684    from_mark = merge_mark = None
 686    ref = parser[1]
 688    parser.next()
 689    commit_mark = parser.get_mark()
 691    parser.next()
 692    author = parser.get_author()
 693    parser.next()
 694    committer = parser.get_author()
 695    parser.next()
 696    data = parser.get_data()
 697    parser.next()
 698    if parser.check('from'):
 699        from_mark = parser.get_mark()
 700        parser.next()
 701    if parser.check('merge'):
 702        merge_mark = parser.get_mark()
 703        parser.next()
 704        if parser.check('merge'):
 705            die('octopus merges are not supported yet')
 706    # fast-export adds an extra newline
 708    if data[-1] == '\n':
 709        data = data[:-1]
 710    files = {}
 712    for line in parser:
 714        if parser.check('M'):
 715            t, m, mark_ref, path = line.split(' ', 3)
 716            mark = int(mark_ref[1:])
 717            f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
 718        elif parser.check('D'):
 719            t, path = line.split(' ', 1)
 720            f = { 'deleted' : True }
 721        else:
 722            die('Unknown file command: %s' % line)
 723        files[path] = f
 724    # only export the commits if we are on an internal proxy repo
 726    if dry_run and not peer:
 727        parsed_refs[ref] = None
 728        return
 729    def getfilectx(repo, memctx, f):
 731        of = files[f]
 732        if 'deleted' in of:
 733            raise IOError
 734        if 'ctx' in of:
 735            return of['ctx']
 736        is_exec = of['mode'] == 'x'
 737        is_link = of['mode'] == 'l'
 738        rename = of.get('rename', None)
 739        return context.memfilectx(f, of['data'],
 740                is_link, is_exec, rename)
 741    repo = parser.repo
 743    user, date, tz = author
 745    extra = {}
 746    if committer != author:
 748        extra['committer'] = "%s %u %u" % committer
 749    if from_mark:
 751        p1 = mark_to_rev(from_mark)
 752    else:
 753        p1 = '0' * 40
 754    if merge_mark:
 756        p2 = mark_to_rev(merge_mark)
 757    else:
 758        p2 = '0' * 40
 759    #
 761    # If files changed from any of the parents, hg wants to know, but in git if
 762    # nothing changed from the first parent, nothing changed.
 763    #
 764    if merge_mark:
 765        get_merge_files(repo, p1, p2, files)
 766    # Check if the ref is supposed to be a named branch
 768    if ref.startswith('refs/heads/branches/'):
 769        branch = ref[len('refs/heads/branches/'):]
 770        extra['branch'] = hgref(branch)
 771    if mode == 'hg':
 773        i = data.find('\n--HG--\n')
 774        if i >= 0:
 775            tmp = data[i + len('\n--HG--\n'):].strip()
 776            for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
 777                if k == 'rename':
 778                    old, new = v.split(' => ', 1)
 779                    files[new]['rename'] = old
 780                elif k == 'branch':
 781                    extra[k] = v
 782                elif k == 'extra':
 783                    ek, ev = v.split(' : ', 1)
 784                    extra[ek] = urllib.unquote(ev)
 785            data = data[:i]
 786    ctx = context.memctx(repo, (p1, p2), data,
 788            files.keys(), getfilectx,
 789            user, (date, tz), extra)
 790    tmp = encoding.encoding
 792    encoding.encoding = 'utf-8'
 793    node = hghex(repo.commitctx(ctx))
 795    encoding.encoding = tmp
 797    parsed_refs[ref] = node
 799    marks.new_mark(node, commit_mark)
 800def parse_reset(parser):
 802    global parsed_refs
 803    ref = parser[1]
 805    parser.next()
 806    # ugh
 807    if parser.check('commit'):
 808        parse_commit(parser)
 809        return
 810    if not parser.check('from'):
 811        return
 812    from_mark = parser.get_mark()
 813    parser.next()
 814    try:
 816        rev = mark_to_rev(from_mark)
 817    except KeyError:
 818        rev = None
 819    parsed_refs[ref] = rev
 820def parse_tag(parser):
 822    name = parser[1]
 823    parser.next()
 824    from_mark = parser.get_mark()
 825    parser.next()
 826    tagger = parser.get_author()
 827    parser.next()
 828    data = parser.get_data()
 829    parser.next()
 830    parsed_tags[name] = (tagger, data)
 832def write_tag(repo, tag, node, msg, author):
 834    branch = repo[node].branch()
 835    tip = branch_tip(branch)
 836    tip = repo[tip]
 837    def getfilectx(repo, memctx, f):
 839        try:
 840            fctx = tip.filectx(f)
 841            data = fctx.data()
 842        except error.ManifestLookupError:
 843            data = ""
 844        content = data + "%s %s\n" % (node, tag)
 845        return context.memfilectx(f, content, False, False, None)
 846    p1 = tip.hex()
 848    p2 = '0' * 40
 849    if author:
 850        user, date, tz = author
 851        date_tz = (date, tz)
 852    else:
 853        cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
 854        process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
 855        output, _ = process.communicate()
 856        m = re.match('^.* <.*>', output)
 857        if m:
 858            user = m.group(0)
 859        else:
 860            user = repo.ui.username()
 861        date_tz = None
 862    ctx = context.memctx(repo, (p1, p2), msg,
 864            ['.hgtags'], getfilectx,
 865            user, date_tz, {'branch' : branch})
 866    tmp = encoding.encoding
 868    encoding.encoding = 'utf-8'
 869    tagnode = repo.commitctx(ctx)
 871    encoding.encoding = tmp
 873    return (tagnode, branch)
 875def checkheads_bmark(repo, ref, ctx):
 877    bmark = ref[len('refs/heads/'):]
 878    if not bmark in bmarks:
 879        # new bmark
 880        return True
 881    ctx_old = bmarks[bmark]
 883    ctx_new = ctx
 884    if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
 885        if force_push:
 886            print "ok %s forced update" % ref
 887        else:
 888            print "error %s non-fast forward" % ref
 889            return False
 890    return True
 892def checkheads(repo, remote, p_revs):
 894    remotemap = remote.branchmap()
 896    if not remotemap:
 897        # empty repo
 898        return True
 899    new = {}
 901    ret = True
 902    for node, ref in p_revs.iteritems():
 904        ctx = repo[node]
 905        branch = ctx.branch()
 906        if not branch in remotemap:
 907            # new branch
 908            continue
 909        if not ref.startswith('refs/heads/branches'):
 910            if ref.startswith('refs/heads/'):
 911                if not checkheads_bmark(repo, ref, ctx):
 912                    ret = False
 913            # only check branches
 915            continue
 916        new.setdefault(branch, []).append(ctx.rev())
 917    for branch, heads in new.iteritems():
 919        old = [repo.changelog.rev(x) for x in remotemap[branch]]
 920        for rev in heads:
 921            if check_version(2, 3):
 922                ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
 923            else:
 924                ancestors = repo.changelog.ancestors(rev)
 925            found = False
 926            for x in old:
 928                if x in ancestors:
 929                    found = True
 930                    break
 931            if found:
 933                continue
 934            node = repo.changelog.node(rev)
 936            ref = p_revs[node]
 937            if force_push:
 938                print "ok %s forced update" % ref
 939            else:
 940                print "error %s non-fast forward" % ref
 941                ret = False
 942    return ret
 944def push_unsafe(repo, remote, parsed_refs, p_revs):
 946    force = force_push
 948    fci = discovery.findcommonincoming
 950    commoninc = fci(repo, remote, force=force)
 951    common, _, remoteheads = commoninc
 952    if not checkheads(repo, remote, p_revs):
 954        return None
 955    cg = repo.getbundle('push', heads=list(p_revs), common=common)
 957    unbundle = remote.capable('unbundle')
 959    if unbundle:
 960        if force:
 961            remoteheads = ['force']
 962        return remote.unbundle(cg, remoteheads, 'push')
 963    else:
 964        return remote.addchangegroup(cg, 'push', repo.url())
 965def push(repo, remote, parsed_refs, p_revs):
 967    if hasattr(remote, 'canpush') and not remote.canpush():
 968        print "error cannot push"
 969    if not p_revs:
 971        # nothing to push
 972        return
 973    lock = None
 975    unbundle = remote.capable('unbundle')
 976    if not unbundle:
 977        lock = remote.lock()
 978    try:
 979        ret = push_unsafe(repo, remote, parsed_refs, p_revs)
 980    finally:
 981        if lock is not None:
 982            lock.release()
 983    return ret
 985def check_tip(ref, kind, name, heads):
 987    try:
 988        ename = '%s/%s' % (kind, name)
 989        tip = marks.get_tip(ename)
 990    except KeyError:
 991        return True
 992    else:
 993        return tip in heads
 994def do_export(parser):
 996    global parsed_refs, bmarks, peer
 997    p_bmarks = []
 999    p_revs = {}
1000    parser.next()
1002    for line in parser.each_block('done'):
1004        if parser.check('blob'):
1005            parse_blob(parser)
1006        elif parser.check('commit'):
1007            parse_commit(parser)
1008        elif parser.check('reset'):
1009            parse_reset(parser)
1010        elif parser.check('tag'):
1011            parse_tag(parser)
1012        elif parser.check('feature'):
1013            pass
1014        else:
1015            die('unhandled export command: %s' % line)
1016    need_fetch = False
1018    for ref, node in parsed_refs.iteritems():
1020        bnode = hgbin(node) if node else None
1021        if ref.startswith('refs/heads/branches'):
1022            branch = ref[len('refs/heads/branches/'):]
1023            if branch in branches and bnode in branches[branch]:
1024                # up to date
1025                continue
1026            if peer:
1028                remotemap = peer.branchmap()
1029                if remotemap and branch in remotemap:
1030                    heads = [hghex(e) for e in remotemap[branch]]
1031                    if not check_tip(ref, 'branches', branch, heads):
1032                        print "error %s fetch first" % ref
1033                        need_fetch = True
1034                        continue
1035            p_revs[bnode] = ref
1037            print "ok %s" % ref
1038        elif ref.startswith('refs/heads/'):
1039            bmark = ref[len('refs/heads/'):]
1040            new = node
1041            old = bmarks[bmark].hex() if bmark in bmarks else ''
1042            if old == new:
1044                continue
1045            print "ok %s" % ref
1047            if bmark != fake_bmark and \
1048                    not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1049                p_bmarks.append((ref, bmark, old, new))
1050            if peer:
1052                remote_old = peer.listkeys('bookmarks').get(bmark)
1053                if remote_old:
1054                    if not check_tip(ref, 'bookmarks', bmark, remote_old):
1055                        print "error %s fetch first" % ref
1056                        need_fetch = True
1057                        continue
1058            p_revs[bnode] = ref
1060        elif ref.startswith('refs/tags/'):
1061            if dry_run:
1062                print "ok %s" % ref
1063                continue
1064            tag = ref[len('refs/tags/'):]
1065            tag = hgref(tag)
1066            author, msg = parsed_tags.get(tag, (None, None))
1067            if mode == 'git':
1068                if not msg:
1069                    msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
1070                tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1071                p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1072            else:
1073                fp = parser.repo.opener('localtags', 'a')
1074                fp.write('%s %s\n' % (node, tag))
1075                fp.close()
1076            p_revs[bnode] = ref
1077            print "ok %s" % ref
1078        else:
1079            # transport-helper/fast-export bugs
1080            continue
1081    if need_fetch:
1083        print
1084        return
1085    if dry_run:
1087        if peer and not force_push:
1088            checkheads(parser.repo, peer, p_revs)
1089        print
1090        return
1091    if peer:
1093        if not push(parser.repo, peer, parsed_refs, p_revs):
1094            # do not update bookmarks
1095            print
1096            return
1097        # update remote bookmarks
1099        remote_bmarks = peer.listkeys('bookmarks')
1100        for ref, bmark, old, new in p_bmarks:
1101            if force_push:
1102                old = remote_bmarks.get(bmark, '')
1103            if not peer.pushkey('bookmarks', bmark, old, new):
1104                print "error %s" % ref
1105    else:
1106        # update local bookmarks
1107        for ref, bmark, old, new in p_bmarks:
1108            if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1109                print "error %s" % ref
1110    print
1112def do_option(parser):
1114    global dry_run, force_push
1115    _, key, value = parser.line.split(' ')
1116    if key == 'dry-run':
1117        dry_run = (value == 'true')
1118        print 'ok'
1119    elif key == 'force':
1120        force_push = (value == 'true')
1121        print 'ok'
1122    else:
1123        print 'unsupported'
1124def fix_path(alias, repo, orig_url):
1126    url = urlparse.urlparse(orig_url, 'file')
1127    if url.scheme != 'file' or os.path.isabs(url.path):
1128        return
1129    abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1130    cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1131    subprocess.call(cmd)
1132def main(args):
1134    global prefix, gitdir, dirname, branches, bmarks
1135    global marks, blob_marks, parsed_refs
1136    global peer, mode, bad_mail, bad_name
1137    global track_branches, force_push, is_tmp
1138    global parsed_tags
1139    global filenodes
1140    global fake_bmark, hg_version
1141    global dry_run
1142    alias = args[1]
1144    url = args[2]
1145    peer = None
1146    hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1148    track_branches = get_config_bool('remote-hg.track-branches', True)
1149    force_push = False
1150    if hg_git_compat:
1152        mode = 'hg'
1153        bad_mail = 'none@none'
1154        bad_name = ''
1155    else:
1156        mode = 'git'
1157        bad_mail = 'unknown'
1158        bad_name = 'Unknown'
1159    if alias[4:] == url:
1161        is_tmp = True
1162        alias = hashlib.sha1(alias).hexdigest()
1163    else:
1164        is_tmp = False
1165    gitdir = os.environ['GIT_DIR']
1167    dirname = os.path.join(gitdir, 'hg', alias)
1168    branches = {}
1169    bmarks = {}
1170    blob_marks = {}
1171    parsed_refs = {}
1172    marks = None
1173    parsed_tags = {}
1174    filenodes = {}
1175    fake_bmark = None
1176    try:
1177        hg_version = tuple(int(e) for e in util.version().split('.'))
1178    except:
1179        hg_version = None
1180    dry_run = False
1181    repo = get_repo(url, alias)
1183    prefix = 'refs/hg/%s' % alias
1184    if not is_tmp:
1186        fix_path(alias, peer or repo, url)
1187    marks_path = os.path.join(dirname, 'marks-hg')
1189    marks = Marks(marks_path, repo)
1190    if sys.platform == 'win32':
1192        import msvcrt
1193        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1194    parser = Parser(repo)
1196    for line in parser:
1197        if parser.check('capabilities'):
1198            do_capabilities(parser)
1199        elif parser.check('list'):
1200            do_list(parser)
1201        elif parser.check('import'):
1202            do_import(parser)
1203        elif parser.check('export'):
1204            do_export(parser)
1205        elif parser.check('option'):
1206            do_option(parser)
1207        else:
1208            die('unhandled command: %s' % line)
1209        sys.stdout.flush()
1210def bye():
1212    if not marks:
1213        return
1214    if not is_tmp:
1215        marks.store()
1216    else:
1217        shutil.rmtree(dirname)
1218atexit.register(bye)
1220sys.exit(main(sys.argv))