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