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