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