contrib / remote-helpers / git-remote-hgon commit Merge branch 'fc/contrib-bzr-hg-fixes' (751e2b3)
   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
  26import time as ptime
  27
  28#
  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#
  52
  53NAME_RE = re.compile('^([^<>]+)')
  54AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
  55EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
  56AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
  57RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
  58
  59VERSION = 2
  60
  61def die(msg, *args):
  62    sys.stderr.write('ERROR: %s\n' % (msg % args))
  63    sys.exit(1)
  64
  65def warn(msg, *args):
  66    sys.stderr.write('WARNING: %s\n' % (msg % args))
  67
  68def gitmode(flags):
  69    return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
  70
  71def gittz(tz):
  72    return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
  73
  74def hgmode(mode):
  75    m = { '100755': 'x', '120000': 'l' }
  76    return m.get(mode, '')
  77
  78def hghex(n):
  79    return node.hex(n)
  80
  81def hgbin(n):
  82    return node.bin(n)
  83
  84def hgref(ref):
  85    return ref.replace('___', ' ')
  86
  87def gitref(ref):
  88    return ref.replace(' ', '___')
  89
  90def check_version(*check):
  91    if not hg_version:
  92        return True
  93    return hg_version >= check
  94
  95def get_config(config):
  96    cmd = ['git', 'config', '--get', config]
  97    process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
  98    output, _ = process.communicate()
  99    return output
 100
 101def 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
 109
 110class Marks:
 111
 112    def __init__(self, path, repo):
 113        self.path = path
 114        self.repo = repo
 115        self.clear()
 116        self.load()
 117
 118        if self.version < VERSION:
 119            if self.version == 1:
 120                self.upgrade_one()
 121
 122            # upgraded?
 123            if self.version < VERSION:
 124                self.clear()
 125                self.version = VERSION
 126
 127    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
 135    def load(self):
 136        if not os.path.exists(self.path):
 137            return
 138
 139        tmp = json.load(open(self.path))
 140
 141        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
 147        for rev, mark in self.marks.iteritems():
 148            self.rev_marks[mark] = rev
 149
 150    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
 158    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
 161    def store(self):
 162        json.dump(self.dict(), open(self.path, 'w'))
 163
 164    def __str__(self):
 165        return str(self.dict())
 166
 167    def from_rev(self, rev):
 168        return self.marks[rev]
 169
 170    def to_rev(self, mark):
 171        return str(self.rev_marks[mark])
 172
 173    def next_mark(self):
 174        self.last_mark += 1
 175        return self.last_mark
 176
 177    def get_mark(self, rev):
 178        self.last_mark += 1
 179        self.marks[rev] = self.last_mark
 180        return self.last_mark
 181
 182    def new_mark(self, rev, mark):
 183        self.marks[rev] = mark
 184        self.rev_marks[mark] = rev
 185        self.last_mark = mark
 186
 187    def is_marked(self, rev):
 188        return rev in self.marks
 189
 190    def get_tip(self, branch):
 191        return str(self.tips[branch])
 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        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
 247        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
 255        if ex:
 256            user += ex
 257
 258        tz = int(tz)
 259        tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
 260        return (user, int(date), -tz)
 261
 262def fix_file_path(path):
 263    if not os.path.isabs(path):
 264        return path
 265    return os.path.relpath(path, '/')
 266
 267def export_files(files):
 268    final = []
 269    for f in files:
 270        fid = node.hex(f.filenode())
 271
 272        if fid in filenodes:
 273            mark = filenodes[fid]
 274        else:
 275            mark = marks.next_mark()
 276            filenodes[fid] = mark
 277            d = f.data()
 278
 279            print "blob"
 280            print "mark :%u" % mark
 281            print "data %d" % len(d)
 282            print d
 283
 284        path = fix_file_path(f.path())
 285        final.append((gitmode(f.flags()), mark, path))
 286
 287    return final
 288
 289def get_filechanges(repo, ctx, parent):
 290    modified = set()
 291    added = set()
 292    removed = set()
 293
 294    # load earliest manifest first for caching reasons
 295    prev = parent.manifest().copy()
 296    cur = ctx.manifest()
 297
 298    for fn in cur:
 299        if fn in prev:
 300            if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
 301                modified.add(fn)
 302            del prev[fn]
 303        else:
 304            added.add(fn)
 305    removed |= set(prev.keys())
 306
 307    return added | modified, removed
 308
 309def fixup_user_git(user):
 310    name = mail = None
 311    user = user.replace('"', '')
 312    m = AUTHOR_RE.match(user)
 313    if m:
 314        name = m.group(1)
 315        mail = m.group(2).strip()
 316    else:
 317        m = EMAIL_RE.match(user)
 318        if m:
 319            name = m.group(1)
 320            mail = m.group(2)
 321        else:
 322            m = NAME_RE.match(user)
 323            if m:
 324                name = m.group(1).strip()
 325    return (name, mail)
 326
 327def fixup_user_hg(user):
 328    def sanitize(name):
 329        # stole this from hg-git
 330        return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
 331
 332    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
 346    return (name, mail)
 347
 348def fixup_user(user):
 349    if mode == 'git':
 350        name, mail = fixup_user_git(user)
 351    else:
 352        name, mail = fixup_user_hg(user)
 353
 354    if not name:
 355        name = bad_name
 356    if not mail:
 357        mail = bad_mail
 358
 359    return '%s <%s>' % (name, mail)
 360
 361def updatebookmarks(repo, peer):
 362    remotemarks = peer.listkeys('bookmarks')
 363    localmarks = repo._bookmarks
 364
 365    if not remotemarks:
 366        return
 367
 368    for k, v in remotemarks.iteritems():
 369        localmarks[k] = hgbin(v)
 370
 371    if hasattr(localmarks, 'write'):
 372        localmarks.write()
 373    else:
 374        bookmarks.write(repo)
 375
 376def get_repo(url, alias):
 377    global peer
 378
 379    myui = ui.ui()
 380    myui.setconfig('ui', 'interactive', 'off')
 381    myui.fout = sys.stderr
 382
 383    if get_config_bool('remote-hg.insecure'):
 384        myui.setconfig('web', 'cacerts', '')
 385
 386    extensions.loadall(myui)
 387
 388    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
 395        # 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
 407        # setup shared repo (if not there)
 408        try:
 409            hg.peer(myui, {}, shared_path, create=True)
 410        except error.RepoError:
 411            pass
 412
 413        if not os.path.exists(dirname):
 414            os.makedirs(dirname)
 415
 416        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
 420        repo = hg.repository(myui, local_path)
 421        try:
 422            peer = hg.peer(myui, {}, url)
 423        except:
 424            die('Repository error')
 425        repo.pull(peer, heads=None, force=True)
 426
 427        updatebookmarks(repo, peer)
 428
 429    return repo
 430
 431def rev_to_mark(rev):
 432    return marks.from_rev(rev.hex())
 433
 434def mark_to_rev(mark):
 435    return marks.to_rev(mark)
 436
 437def export_ref(repo, name, kind, head):
 438    ename = '%s/%s' % (kind, name)
 439    try:
 440        tip = marks.get_tip(ename)
 441        tip = repo[tip].rev()
 442    except:
 443        tip = 0
 444
 445    revs = xrange(tip, head.rev() + 1)
 446    total = len(revs)
 447
 448    for rev in revs:
 449
 450        c = repo[rev]
 451        node = c.node()
 452
 453        if marks.is_marked(c.hex()):
 454            continue
 455
 456        (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
 457        rev_branch = extra['branch']
 458
 459        author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
 460        if 'committer' in extra:
 461            user, time, tz = extra['committer'].rsplit(' ', 2)
 462            committer = "%s %s %s" % (user, time, gittz(int(tz)))
 463        else:
 464            committer = author
 465
 466        parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
 467
 468        if len(parents) == 0:
 469            modified = c.manifest().keys()
 470            removed = []
 471        else:
 472            modified, removed = get_filechanges(repo, c, parents[0])
 473
 474        desc += '\n'
 475
 476        if mode == 'hg':
 477            extra_msg = ''
 478
 479            if rev_branch != 'default':
 480                extra_msg += 'branch : %s\n' % rev_branch
 481
 482            renames = []
 483            for f in c.files():
 484                if f not in c.manifest():
 485                    continue
 486                rename = c.filectx(f).renamed()
 487                if rename:
 488                    renames.append((rename[0], f))
 489
 490            for e in renames:
 491                extra_msg += "rename : %s => %s\n" % e
 492
 493            for key, value in extra.iteritems():
 494                if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
 495                    continue
 496                else:
 497                    extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
 498
 499            if extra_msg:
 500                desc += '\n--HG--\n' + extra_msg
 501
 502        if len(parents) == 0 and rev:
 503            print 'reset %s/%s' % (prefix, ename)
 504
 505        modified_final = export_files(c.filectx(f) for f in modified)
 506
 507        print "commit %s/%s" % (prefix, ename)
 508        print "mark :%d" % (marks.get_mark(c.hex()))
 509        print "author %s" % (author)
 510        print "committer %s" % (committer)
 511        print "data %d" % (len(desc))
 512        print desc
 513
 514        if len(parents) > 0:
 515            print "from :%s" % (rev_to_mark(parents[0]))
 516            if len(parents) > 1:
 517                print "merge :%s" % (rev_to_mark(parents[1]))
 518
 519        for f in removed:
 520            print "D %s" % (fix_file_path(f))
 521        for f in modified_final:
 522            print "M %s :%u %s" % f
 523        print
 524
 525        progress = (rev - tip)
 526        if (progress % 100 == 0):
 527            print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
 528
 529    # make sure the ref is updated
 530    print "reset %s/%s" % (prefix, ename)
 531    print "from :%u" % rev_to_mark(head)
 532    print
 533
 534    pending_revs = set(revs) - notes
 535    if pending_revs:
 536        note_mark = marks.next_mark()
 537        ref = "refs/notes/hg"
 538
 539        print "commit %s" % ref
 540        print "mark :%d" % (note_mark)
 541        print "committer remote-hg <> %s" % (ptime.strftime('%s %z'))
 542        desc = "Notes for %s\n" % (name)
 543        print "data %d" % (len(desc))
 544        print desc
 545        if marks.last_note:
 546            print "from :%u" % marks.last_note
 547
 548        for rev in pending_revs:
 549            notes.add(rev)
 550            c = repo[rev]
 551            print "N inline :%u" % rev_to_mark(c)
 552            msg = c.hex()
 553            print "data %d" % (len(msg))
 554            print msg
 555        print
 556
 557        marks.last_note = note_mark
 558
 559    marks.set_tip(ename, head.hex())
 560
 561def export_tag(repo, tag):
 562    export_ref(repo, tag, 'tags', repo[hgref(tag)])
 563
 564def export_bookmark(repo, bmark):
 565    head = bmarks[hgref(bmark)]
 566    export_ref(repo, bmark, 'bookmarks', head)
 567
 568def export_branch(repo, branch):
 569    tip = get_branch_tip(repo, branch)
 570    head = repo[tip]
 571    export_ref(repo, branch, 'branches', head)
 572
 573def export_head(repo):
 574    export_ref(repo, g_head[0], 'bookmarks', g_head[1])
 575
 576def do_capabilities(parser):
 577    print "import"
 578    print "export"
 579    print "refspec refs/heads/branches/*:%s/branches/*" % prefix
 580    print "refspec refs/heads/*:%s/bookmarks/*" % prefix
 581    print "refspec refs/tags/*:%s/tags/*" % prefix
 582
 583    path = os.path.join(dirname, 'marks-git')
 584
 585    if os.path.exists(path):
 586        print "*import-marks %s" % path
 587    print "*export-marks %s" % path
 588    print "option"
 589
 590    print
 591
 592def branch_tip(branch):
 593    return branches[branch][-1]
 594
 595def get_branch_tip(repo, branch):
 596    heads = branches.get(hgref(branch), None)
 597    if not heads:
 598        return None
 599
 600    # verify there's only one head
 601    if (len(heads) > 1):
 602        warn("Branch '%s' has more than one head, consider merging" % branch)
 603        return branch_tip(hgref(branch))
 604
 605    return heads[0]
 606
 607def list_head(repo, cur):
 608    global g_head, fake_bmark
 609
 610    if 'default' not in branches:
 611        # empty repo
 612        return
 613
 614    node = repo[branch_tip('default')]
 615    head = 'master' if not 'master' in bmarks else 'default'
 616    fake_bmark = head
 617    bmarks[head] = node
 618
 619    head = gitref(head)
 620    print "@refs/heads/%s HEAD" % head
 621    g_head = (head, node)
 622
 623def do_list(parser):
 624    repo = parser.repo
 625    for bmark, node in bookmarks.listbookmarks(repo).iteritems():
 626        bmarks[bmark] = repo[node]
 627
 628    cur = repo.dirstate.branch()
 629    orig = peer if peer else repo
 630
 631    for branch, heads in orig.branchmap().iteritems():
 632        # only open heads
 633        heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
 634        if heads:
 635            branches[branch] = heads
 636
 637    list_head(repo, cur)
 638
 639    if track_branches:
 640        for branch in branches:
 641            print "? refs/heads/branches/%s" % gitref(branch)
 642
 643    for bmark in bmarks:
 644        print "? refs/heads/%s" % gitref(bmark)
 645
 646    for tag, node in repo.tagslist():
 647        if tag == 'tip':
 648            continue
 649        print "? refs/tags/%s" % gitref(tag)
 650
 651    print
 652
 653def do_import(parser):
 654    repo = parser.repo
 655
 656    path = os.path.join(dirname, 'marks-git')
 657
 658    print "feature done"
 659    if os.path.exists(path):
 660        print "feature import-marks=%s" % path
 661    print "feature export-marks=%s" % path
 662    print "feature force"
 663    sys.stdout.flush()
 664
 665    tmp = encoding.encoding
 666    encoding.encoding = 'utf-8'
 667
 668    # lets get all the import lines
 669    while parser.check('import'):
 670        ref = parser[1]
 671
 672        if (ref == 'HEAD'):
 673            export_head(repo)
 674        elif ref.startswith('refs/heads/branches/'):
 675            branch = ref[len('refs/heads/branches/'):]
 676            export_branch(repo, branch)
 677        elif ref.startswith('refs/heads/'):
 678            bmark = ref[len('refs/heads/'):]
 679            export_bookmark(repo, bmark)
 680        elif ref.startswith('refs/tags/'):
 681            tag = ref[len('refs/tags/'):]
 682            export_tag(repo, tag)
 683
 684        parser.next()
 685
 686    encoding.encoding = tmp
 687
 688    print 'done'
 689
 690def parse_blob(parser):
 691    parser.next()
 692    mark = parser.get_mark()
 693    parser.next()
 694    data = parser.get_data()
 695    blob_marks[mark] = data
 696    parser.next()
 697
 698def get_merge_files(repo, p1, p2, files):
 699    for e in repo[p1].files():
 700        if e not in files:
 701            if e not in repo[p1].manifest():
 702                continue
 703            f = { 'ctx' : repo[p1][e] }
 704            files[e] = f
 705
 706def parse_commit(parser):
 707    from_mark = merge_mark = None
 708
 709    ref = parser[1]
 710    parser.next()
 711
 712    commit_mark = parser.get_mark()
 713    parser.next()
 714    author = parser.get_author()
 715    parser.next()
 716    committer = parser.get_author()
 717    parser.next()
 718    data = parser.get_data()
 719    parser.next()
 720    if parser.check('from'):
 721        from_mark = parser.get_mark()
 722        parser.next()
 723    if parser.check('merge'):
 724        merge_mark = parser.get_mark()
 725        parser.next()
 726        if parser.check('merge'):
 727            die('octopus merges are not supported yet')
 728
 729    # fast-export adds an extra newline
 730    if data[-1] == '\n':
 731        data = data[:-1]
 732
 733    files = {}
 734
 735    for line in parser:
 736        if parser.check('M'):
 737            t, m, mark_ref, path = line.split(' ', 3)
 738            mark = int(mark_ref[1:])
 739            f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
 740        elif parser.check('D'):
 741            t, path = line.split(' ', 1)
 742            f = { 'deleted' : True }
 743        else:
 744            die('Unknown file command: %s' % line)
 745        files[path] = f
 746
 747    # only export the commits if we are on an internal proxy repo
 748    if dry_run and not peer:
 749        parsed_refs[ref] = None
 750        return
 751
 752    def getfilectx(repo, memctx, f):
 753        of = files[f]
 754        if 'deleted' in of:
 755            raise IOError
 756        if 'ctx' in of:
 757            return of['ctx']
 758        is_exec = of['mode'] == 'x'
 759        is_link = of['mode'] == 'l'
 760        rename = of.get('rename', None)
 761        return context.memfilectx(f, of['data'],
 762                is_link, is_exec, rename)
 763
 764    repo = parser.repo
 765
 766    user, date, tz = author
 767    extra = {}
 768
 769    if committer != author:
 770        extra['committer'] = "%s %u %u" % committer
 771
 772    if from_mark:
 773        p1 = mark_to_rev(from_mark)
 774    else:
 775        p1 = '0' * 40
 776
 777    if merge_mark:
 778        p2 = mark_to_rev(merge_mark)
 779    else:
 780        p2 = '0' * 40
 781
 782    #
 783    # If files changed from any of the parents, hg wants to know, but in git if
 784    # nothing changed from the first parent, nothing changed.
 785    #
 786    if merge_mark:
 787        get_merge_files(repo, p1, p2, files)
 788
 789    # Check if the ref is supposed to be a named branch
 790    if ref.startswith('refs/heads/branches/'):
 791        branch = ref[len('refs/heads/branches/'):]
 792        extra['branch'] = hgref(branch)
 793
 794    if mode == 'hg':
 795        i = data.find('\n--HG--\n')
 796        if i >= 0:
 797            tmp = data[i + len('\n--HG--\n'):].strip()
 798            for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
 799                if k == 'rename':
 800                    old, new = v.split(' => ', 1)
 801                    files[new]['rename'] = old
 802                elif k == 'branch':
 803                    extra[k] = v
 804                elif k == 'extra':
 805                    ek, ev = v.split(' : ', 1)
 806                    extra[ek] = urllib.unquote(ev)
 807            data = data[:i]
 808
 809    ctx = context.memctx(repo, (p1, p2), data,
 810            files.keys(), getfilectx,
 811            user, (date, tz), extra)
 812
 813    tmp = encoding.encoding
 814    encoding.encoding = 'utf-8'
 815
 816    node = hghex(repo.commitctx(ctx))
 817
 818    encoding.encoding = tmp
 819
 820    parsed_refs[ref] = node
 821    marks.new_mark(node, commit_mark)
 822
 823def parse_reset(parser):
 824    ref = parser[1]
 825    parser.next()
 826    # ugh
 827    if parser.check('commit'):
 828        parse_commit(parser)
 829        return
 830    if not parser.check('from'):
 831        return
 832    from_mark = parser.get_mark()
 833    parser.next()
 834
 835    try:
 836        rev = mark_to_rev(from_mark)
 837    except KeyError:
 838        rev = None
 839    parsed_refs[ref] = rev
 840
 841def parse_tag(parser):
 842    name = parser[1]
 843    parser.next()
 844    from_mark = parser.get_mark()
 845    parser.next()
 846    tagger = parser.get_author()
 847    parser.next()
 848    data = parser.get_data()
 849    parser.next()
 850
 851    parsed_tags[name] = (tagger, data)
 852
 853def write_tag(repo, tag, node, msg, author):
 854    branch = repo[node].branch()
 855    tip = branch_tip(branch)
 856    tip = repo[tip]
 857
 858    def getfilectx(repo, memctx, f):
 859        try:
 860            fctx = tip.filectx(f)
 861            data = fctx.data()
 862        except error.ManifestLookupError:
 863            data = ""
 864        content = data + "%s %s\n" % (node, tag)
 865        return context.memfilectx(f, content, False, False, None)
 866
 867    p1 = tip.hex()
 868    p2 = '0' * 40
 869    if author:
 870        user, date, tz = author
 871        date_tz = (date, tz)
 872    else:
 873        cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
 874        process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
 875        output, _ = process.communicate()
 876        m = re.match('^.* <.*>', output)
 877        if m:
 878            user = m.group(0)
 879        else:
 880            user = repo.ui.username()
 881        date_tz = None
 882
 883    ctx = context.memctx(repo, (p1, p2), msg,
 884            ['.hgtags'], getfilectx,
 885            user, date_tz, {'branch' : branch})
 886
 887    tmp = encoding.encoding
 888    encoding.encoding = 'utf-8'
 889
 890    tagnode = repo.commitctx(ctx)
 891
 892    encoding.encoding = tmp
 893
 894    return (tagnode, branch)
 895
 896def checkheads_bmark(repo, ref, ctx):
 897    bmark = ref[len('refs/heads/'):]
 898    if not bmark in bmarks:
 899        # new bmark
 900        return True
 901
 902    ctx_old = bmarks[bmark]
 903    ctx_new = ctx
 904    if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
 905        if force_push:
 906            print "ok %s forced update" % ref
 907        else:
 908            print "error %s non-fast forward" % ref
 909            return False
 910
 911    return True
 912
 913def checkheads(repo, remote, p_revs):
 914
 915    remotemap = remote.branchmap()
 916    if not remotemap:
 917        # empty repo
 918        return True
 919
 920    new = {}
 921    ret = True
 922
 923    for node, ref in p_revs.iteritems():
 924        ctx = repo[node]
 925        branch = ctx.branch()
 926        if not branch in remotemap:
 927            # new branch
 928            continue
 929        if not ref.startswith('refs/heads/branches'):
 930            if ref.startswith('refs/heads/'):
 931                if not checkheads_bmark(repo, ref, ctx):
 932                    ret = False
 933
 934            # only check branches
 935            continue
 936        new.setdefault(branch, []).append(ctx.rev())
 937
 938    for branch, heads in new.iteritems():
 939        old = [repo.changelog.rev(x) for x in remotemap[branch]]
 940        for rev in heads:
 941            if check_version(2, 3):
 942                ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
 943            else:
 944                ancestors = repo.changelog.ancestors(rev)
 945            found = False
 946
 947            for x in old:
 948                if x in ancestors:
 949                    found = True
 950                    break
 951
 952            if found:
 953                continue
 954
 955            node = repo.changelog.node(rev)
 956            ref = p_revs[node]
 957            if force_push:
 958                print "ok %s forced update" % ref
 959            else:
 960                print "error %s non-fast forward" % ref
 961                ret = False
 962
 963    return ret
 964
 965def push_unsafe(repo, remote, parsed_refs, p_revs):
 966
 967    force = force_push
 968
 969    fci = discovery.findcommonincoming
 970    commoninc = fci(repo, remote, force=force)
 971    common, _, remoteheads = commoninc
 972
 973    if not checkheads(repo, remote, p_revs):
 974        return None
 975
 976    cg = repo.getbundle('push', heads=list(p_revs), common=common)
 977
 978    unbundle = remote.capable('unbundle')
 979    if unbundle:
 980        if force:
 981            remoteheads = ['force']
 982        return remote.unbundle(cg, remoteheads, 'push')
 983    else:
 984        return remote.addchangegroup(cg, 'push', repo.url())
 985
 986def push(repo, remote, parsed_refs, p_revs):
 987    if hasattr(remote, 'canpush') and not remote.canpush():
 988        print "error cannot push"
 989
 990    if not p_revs:
 991        # nothing to push
 992        return
 993
 994    lock = None
 995    unbundle = remote.capable('unbundle')
 996    if not unbundle:
 997        lock = remote.lock()
 998    try:
 999        ret = push_unsafe(repo, remote, parsed_refs, p_revs)
1000    finally:
1001        if lock is not None:
1002            lock.release()
1003
1004    return ret
1005
1006def check_tip(ref, kind, name, heads):
1007    try:
1008        ename = '%s/%s' % (kind, name)
1009        tip = marks.get_tip(ename)
1010    except KeyError:
1011        return True
1012    else:
1013        return tip in heads
1014
1015def do_export(parser):
1016    p_bmarks = []
1017    p_revs = {}
1018
1019    parser.next()
1020
1021    for line in parser.each_block('done'):
1022        if parser.check('blob'):
1023            parse_blob(parser)
1024        elif parser.check('commit'):
1025            parse_commit(parser)
1026        elif parser.check('reset'):
1027            parse_reset(parser)
1028        elif parser.check('tag'):
1029            parse_tag(parser)
1030        elif parser.check('feature'):
1031            pass
1032        else:
1033            die('unhandled export command: %s' % line)
1034
1035    need_fetch = False
1036
1037    for ref, node in parsed_refs.iteritems():
1038        bnode = hgbin(node) if node else None
1039        if ref.startswith('refs/heads/branches'):
1040            branch = ref[len('refs/heads/branches/'):]
1041            if branch in branches and bnode in branches[branch]:
1042                # up to date
1043                continue
1044
1045            if peer:
1046                remotemap = peer.branchmap()
1047                if remotemap and branch in remotemap:
1048                    heads = [hghex(e) for e in remotemap[branch]]
1049                    if not check_tip(ref, 'branches', branch, heads):
1050                        print "error %s fetch first" % ref
1051                        need_fetch = True
1052                        continue
1053
1054            p_revs[bnode] = ref
1055            print "ok %s" % ref
1056        elif ref.startswith('refs/heads/'):
1057            bmark = ref[len('refs/heads/'):]
1058            new = node
1059            old = bmarks[bmark].hex() if bmark in bmarks else ''
1060
1061            if old == new:
1062                continue
1063
1064            print "ok %s" % ref
1065            if bmark != fake_bmark and \
1066                    not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1067                p_bmarks.append((ref, bmark, old, new))
1068
1069            if peer:
1070                remote_old = peer.listkeys('bookmarks').get(bmark)
1071                if remote_old:
1072                    if not check_tip(ref, 'bookmarks', bmark, remote_old):
1073                        print "error %s fetch first" % ref
1074                        need_fetch = True
1075                        continue
1076
1077            p_revs[bnode] = ref
1078        elif ref.startswith('refs/tags/'):
1079            if dry_run:
1080                print "ok %s" % ref
1081                continue
1082            tag = ref[len('refs/tags/'):]
1083            tag = hgref(tag)
1084            author, msg = parsed_tags.get(tag, (None, None))
1085            if mode == 'git':
1086                if not msg:
1087                    msg = 'Added tag %s for changeset %s' % (tag, node[:12])
1088                tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1089                p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1090            else:
1091                fp = parser.repo.opener('localtags', 'a')
1092                fp.write('%s %s\n' % (node, tag))
1093                fp.close()
1094            p_revs[bnode] = ref
1095            print "ok %s" % ref
1096        else:
1097            # transport-helper/fast-export bugs
1098            continue
1099
1100    if need_fetch:
1101        print
1102        return
1103
1104    if dry_run:
1105        if peer and not force_push:
1106            checkheads(parser.repo, peer, p_revs)
1107        print
1108        return
1109
1110    if peer:
1111        if not push(parser.repo, peer, parsed_refs, p_revs):
1112            # do not update bookmarks
1113            print
1114            return
1115
1116        # update remote bookmarks
1117        remote_bmarks = peer.listkeys('bookmarks')
1118        for ref, bmark, old, new in p_bmarks:
1119            if force_push:
1120                old = remote_bmarks.get(bmark, '')
1121            if not peer.pushkey('bookmarks', bmark, old, new):
1122                print "error %s" % ref
1123    else:
1124        # update local bookmarks
1125        for ref, bmark, old, new in p_bmarks:
1126            if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1127                print "error %s" % ref
1128
1129    print
1130
1131def do_option(parser):
1132    global dry_run, force_push
1133    _, key, value = parser.line.split(' ')
1134    if key == 'dry-run':
1135        dry_run = (value == 'true')
1136        print 'ok'
1137    elif key == 'force':
1138        force_push = (value == 'true')
1139        print 'ok'
1140    else:
1141        print 'unsupported'
1142
1143def fix_path(alias, repo, orig_url):
1144    url = urlparse.urlparse(orig_url, 'file')
1145    if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)):
1146        return
1147    abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1148    cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1149    subprocess.call(cmd)
1150
1151def main(args):
1152    global prefix, gitdir, dirname, branches, bmarks
1153    global marks, blob_marks, parsed_refs
1154    global peer, mode, bad_mail, bad_name
1155    global track_branches, force_push, is_tmp
1156    global parsed_tags
1157    global filenodes
1158    global fake_bmark, hg_version
1159    global dry_run
1160    global notes, alias
1161
1162    alias = args[1]
1163    url = args[2]
1164    peer = None
1165
1166    hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1167    track_branches = get_config_bool('remote-hg.track-branches', True)
1168    force_push = False
1169
1170    if hg_git_compat:
1171        mode = 'hg'
1172        bad_mail = 'none@none'
1173        bad_name = ''
1174    else:
1175        mode = 'git'
1176        bad_mail = 'unknown'
1177        bad_name = 'Unknown'
1178
1179    if alias[4:] == url:
1180        is_tmp = True
1181        alias = hashlib.sha1(alias).hexdigest()
1182    else:
1183        is_tmp = False
1184
1185    gitdir = os.environ['GIT_DIR']
1186    dirname = os.path.join(gitdir, 'hg', alias)
1187    branches = {}
1188    bmarks = {}
1189    blob_marks = {}
1190    parsed_refs = {}
1191    marks = None
1192    parsed_tags = {}
1193    filenodes = {}
1194    fake_bmark = None
1195    try:
1196        hg_version = tuple(int(e) for e in util.version().split('.'))
1197    except:
1198        hg_version = None
1199    dry_run = False
1200    notes = set()
1201
1202    repo = get_repo(url, alias)
1203    prefix = 'refs/hg/%s' % alias
1204
1205    if not is_tmp:
1206        fix_path(alias, peer or repo, url)
1207
1208    marks_path = os.path.join(dirname, 'marks-hg')
1209    marks = Marks(marks_path, repo)
1210
1211    if sys.platform == 'win32':
1212        import msvcrt
1213        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1214
1215    parser = Parser(repo)
1216    for line in parser:
1217        if parser.check('capabilities'):
1218            do_capabilities(parser)
1219        elif parser.check('list'):
1220            do_list(parser)
1221        elif parser.check('import'):
1222            do_import(parser)
1223        elif parser.check('export'):
1224            do_export(parser)
1225        elif parser.check('option'):
1226            do_option(parser)
1227        else:
1228            die('unhandled command: %s' % line)
1229        sys.stdout.flush()
1230
1231def bye():
1232    if not marks:
1233        return
1234    if not is_tmp:
1235        marks.store()
1236    else:
1237        shutil.rmtree(dirname)
1238
1239atexit.register(bye)
1240sys.exit(main(sys.argv))