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