contrib / remote-helpers / git-remote-hgon commit remote-hg: always normalize paths (867bf7b)
   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        print "? refs/heads/%s" % gitref(bmark)
 648
 649    for tag, node in repo.tagslist():
 650        if tag == 'tip':
 651            continue
 652        print "? refs/tags/%s" % gitref(tag)
 653
 654    print
 655
 656def do_import(parser):
 657    repo = parser.repo
 658
 659    path = os.path.join(dirname, 'marks-git')
 660
 661    print "feature done"
 662    if os.path.exists(path):
 663        print "feature import-marks=%s" % path
 664    print "feature export-marks=%s" % path
 665    print "feature force"
 666    sys.stdout.flush()
 667
 668    tmp = encoding.encoding
 669    encoding.encoding = 'utf-8'
 670
 671    # lets get all the import lines
 672    while parser.check('import'):
 673        ref = parser[1]
 674
 675        if (ref == 'HEAD'):
 676            export_head(repo)
 677        elif ref.startswith('refs/heads/branches/'):
 678            branch = ref[len('refs/heads/branches/'):]
 679            export_branch(repo, branch)
 680        elif ref.startswith('refs/heads/'):
 681            bmark = ref[len('refs/heads/'):]
 682            export_bookmark(repo, bmark)
 683        elif ref.startswith('refs/tags/'):
 684            tag = ref[len('refs/tags/'):]
 685            export_tag(repo, tag)
 686
 687        parser.next()
 688
 689    encoding.encoding = tmp
 690
 691    print 'done'
 692
 693def parse_blob(parser):
 694    parser.next()
 695    mark = parser.get_mark()
 696    parser.next()
 697    data = parser.get_data()
 698    blob_marks[mark] = data
 699    parser.next()
 700
 701def get_merge_files(repo, p1, p2, files):
 702    for e in repo[p1].files():
 703        if e not in files:
 704            if e not in repo[p1].manifest():
 705                continue
 706            f = { 'ctx' : repo[p1][e] }
 707            files[e] = f
 708
 709def c_style_unescape(string):
 710    if string[0] == string[-1] == '"':
 711        return string.decode('string-escape')[1:-1]
 712    return string
 713
 714def parse_commit(parser):
 715    from_mark = merge_mark = None
 716
 717    ref = parser[1]
 718    parser.next()
 719
 720    commit_mark = parser.get_mark()
 721    parser.next()
 722    author = parser.get_author()
 723    parser.next()
 724    committer = parser.get_author()
 725    parser.next()
 726    data = parser.get_data()
 727    parser.next()
 728    if parser.check('from'):
 729        from_mark = parser.get_mark()
 730        parser.next()
 731    if parser.check('merge'):
 732        merge_mark = parser.get_mark()
 733        parser.next()
 734        if parser.check('merge'):
 735            die('octopus merges are not supported yet')
 736
 737    # fast-export adds an extra newline
 738    if data[-1] == '\n':
 739        data = data[:-1]
 740
 741    files = {}
 742
 743    for line in parser:
 744        if parser.check('M'):
 745            t, m, mark_ref, path = line.split(' ', 3)
 746            mark = int(mark_ref[1:])
 747            f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
 748        elif parser.check('D'):
 749            t, path = line.split(' ', 1)
 750            f = { 'deleted' : True }
 751        else:
 752            die('Unknown file command: %s' % line)
 753        path = c_style_unescape(path)
 754        files[path] = f
 755
 756    # only export the commits if we are on an internal proxy repo
 757    if dry_run and not peer:
 758        parsed_refs[ref] = None
 759        return
 760
 761    def getfilectx(repo, memctx, f):
 762        of = files[f]
 763        if 'deleted' in of:
 764            raise IOError
 765        if 'ctx' in of:
 766            return of['ctx']
 767        is_exec = of['mode'] == 'x'
 768        is_link = of['mode'] == 'l'
 769        rename = of.get('rename', None)
 770        return context.memfilectx(f, of['data'],
 771                is_link, is_exec, rename)
 772
 773    repo = parser.repo
 774
 775    user, date, tz = author
 776    extra = {}
 777
 778    if committer != author:
 779        extra['committer'] = "%s %u %u" % committer
 780
 781    if from_mark:
 782        p1 = mark_to_rev(from_mark)
 783    else:
 784        p1 = '0' * 40
 785
 786    if merge_mark:
 787        p2 = mark_to_rev(merge_mark)
 788    else:
 789        p2 = '0' * 40
 790
 791    #
 792    # If files changed from any of the parents, hg wants to know, but in git if
 793    # nothing changed from the first parent, nothing changed.
 794    #
 795    if merge_mark:
 796        get_merge_files(repo, p1, p2, files)
 797
 798    # Check if the ref is supposed to be a named branch
 799    if ref.startswith('refs/heads/branches/'):
 800        branch = ref[len('refs/heads/branches/'):]
 801        extra['branch'] = hgref(branch)
 802
 803    if mode == 'hg':
 804        i = data.find('\n--HG--\n')
 805        if i >= 0:
 806            tmp = data[i + len('\n--HG--\n'):].strip()
 807            for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
 808                if k == 'rename':
 809                    old, new = v.split(' => ', 1)
 810                    files[new]['rename'] = old
 811                elif k == 'branch':
 812                    extra[k] = v
 813                elif k == 'extra':
 814                    ek, ev = v.split(' : ', 1)
 815                    extra[ek] = urllib.unquote(ev)
 816            data = data[:i]
 817
 818    ctx = context.memctx(repo, (p1, p2), data,
 819            files.keys(), getfilectx,
 820            user, (date, tz), extra)
 821
 822    tmp = encoding.encoding
 823    encoding.encoding = 'utf-8'
 824
 825    node = hghex(repo.commitctx(ctx))
 826
 827    encoding.encoding = tmp
 828
 829    parsed_refs[ref] = node
 830    marks.new_mark(node, commit_mark)
 831
 832def parse_reset(parser):
 833    ref = parser[1]
 834    parser.next()
 835    # ugh
 836    if parser.check('commit'):
 837        parse_commit(parser)
 838        return
 839    if not parser.check('from'):
 840        return
 841    from_mark = parser.get_mark()
 842    parser.next()
 843
 844    try:
 845        rev = mark_to_rev(from_mark)
 846    except KeyError:
 847        rev = None
 848    parsed_refs[ref] = rev
 849
 850def parse_tag(parser):
 851    name = parser[1]
 852    parser.next()
 853    from_mark = parser.get_mark()
 854    parser.next()
 855    tagger = parser.get_author()
 856    parser.next()
 857    data = parser.get_data()
 858    parser.next()
 859
 860    parsed_tags[name] = (tagger, data)
 861
 862def write_tag(repo, tag, node, msg, author):
 863    branch = repo[node].branch()
 864    tip = branch_tip(branch)
 865    tip = repo[tip]
 866
 867    def getfilectx(repo, memctx, f):
 868        try:
 869            fctx = tip.filectx(f)
 870            data = fctx.data()
 871        except error.ManifestLookupError:
 872            data = ""
 873        content = data + "%s %s\n" % (node, tag)
 874        return context.memfilectx(f, content, False, False, None)
 875
 876    p1 = tip.hex()
 877    p2 = '0' * 40
 878    if author:
 879        user, date, tz = author
 880        date_tz = (date, tz)
 881    else:
 882        cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
 883        process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
 884        output, _ = process.communicate()
 885        m = re.match('^.* <.*>', output)
 886        if m:
 887            user = m.group(0)
 888        else:
 889            user = repo.ui.username()
 890        date_tz = None
 891
 892    ctx = context.memctx(repo, (p1, p2), msg,
 893            ['.hgtags'], getfilectx,
 894            user, date_tz, {'branch' : branch})
 895
 896    tmp = encoding.encoding
 897    encoding.encoding = 'utf-8'
 898
 899    tagnode = repo.commitctx(ctx)
 900
 901    encoding.encoding = tmp
 902
 903    return (tagnode, branch)
 904
 905def checkheads_bmark(repo, ref, ctx):
 906    bmark = ref[len('refs/heads/'):]
 907    if not bmark in bmarks:
 908        # new bmark
 909        return True
 910
 911    ctx_old = bmarks[bmark]
 912    ctx_new = ctx
 913    if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
 914        if force_push:
 915            print "ok %s forced update" % ref
 916        else:
 917            print "error %s non-fast forward" % ref
 918            return False
 919
 920    return True
 921
 922def checkheads(repo, remote, p_revs):
 923
 924    remotemap = remote.branchmap()
 925    if not remotemap:
 926        # empty repo
 927        return True
 928
 929    new = {}
 930    ret = True
 931
 932    for node, ref in p_revs.iteritems():
 933        ctx = repo[node]
 934        branch = ctx.branch()
 935        if not branch in remotemap:
 936            # new branch
 937            continue
 938        if not ref.startswith('refs/heads/branches'):
 939            if ref.startswith('refs/heads/'):
 940                if not checkheads_bmark(repo, ref, ctx):
 941                    ret = False
 942
 943            # only check branches
 944            continue
 945        new.setdefault(branch, []).append(ctx.rev())
 946
 947    for branch, heads in new.iteritems():
 948        old = [repo.changelog.rev(x) for x in remotemap[branch]]
 949        for rev in heads:
 950            if check_version(2, 3):
 951                ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
 952            else:
 953                ancestors = repo.changelog.ancestors(rev)
 954            found = False
 955
 956            for x in old:
 957                if x in ancestors:
 958                    found = True
 959                    break
 960
 961            if found:
 962                continue
 963
 964            node = repo.changelog.node(rev)
 965            ref = p_revs[node]
 966            if force_push:
 967                print "ok %s forced update" % ref
 968            else:
 969                print "error %s non-fast forward" % ref
 970                ret = False
 971
 972    return ret
 973
 974def push_unsafe(repo, remote, parsed_refs, p_revs):
 975
 976    force = force_push
 977
 978    fci = discovery.findcommonincoming
 979    commoninc = fci(repo, remote, force=force)
 980    common, _, remoteheads = commoninc
 981
 982    if not checkheads(repo, remote, p_revs):
 983        return None
 984
 985    cg = repo.getbundle('push', heads=list(p_revs), common=common)
 986
 987    unbundle = remote.capable('unbundle')
 988    if unbundle:
 989        if force:
 990            remoteheads = ['force']
 991        return remote.unbundle(cg, remoteheads, 'push')
 992    else:
 993        return remote.addchangegroup(cg, 'push', repo.url())
 994
 995def push(repo, remote, parsed_refs, p_revs):
 996    if hasattr(remote, 'canpush') and not remote.canpush():
 997        print "error cannot push"
 998
 999    if not p_revs:
1000        # nothing to push
1001        return
1002
1003    lock = None
1004    unbundle = remote.capable('unbundle')
1005    if not unbundle:
1006        lock = remote.lock()
1007    try:
1008        ret = push_unsafe(repo, remote, parsed_refs, p_revs)
1009    finally:
1010        if lock is not None:
1011            lock.release()
1012
1013    return ret
1014
1015def check_tip(ref, kind, name, heads):
1016    try:
1017        ename = '%s/%s' % (kind, name)
1018        tip = marks.get_tip(ename)
1019    except KeyError:
1020        return True
1021    else:
1022        return tip in heads
1023
1024def do_export(parser):
1025    p_bmarks = []
1026    p_revs = {}
1027
1028    parser.next()
1029
1030    for line in parser.each_block('done'):
1031        if parser.check('blob'):
1032            parse_blob(parser)
1033        elif parser.check('commit'):
1034            parse_commit(parser)
1035        elif parser.check('reset'):
1036            parse_reset(parser)
1037        elif parser.check('tag'):
1038            parse_tag(parser)
1039        elif parser.check('feature'):
1040            pass
1041        else:
1042            die('unhandled export command: %s' % line)
1043
1044    need_fetch = False
1045
1046    for ref, node in parsed_refs.iteritems():
1047        bnode = hgbin(node) if node else None
1048        if ref.startswith('refs/heads/branches'):
1049            branch = ref[len('refs/heads/branches/'):]
1050            if branch in branches and bnode in branches[branch]:
1051                # up to date
1052                continue
1053
1054            if peer:
1055                remotemap = peer.branchmap()
1056                if remotemap and branch in remotemap:
1057                    heads = [hghex(e) for e in remotemap[branch]]
1058                    if not check_tip(ref, 'branches', branch, heads):
1059                        print "error %s fetch first" % ref
1060                        need_fetch = True
1061                        continue
1062
1063            p_revs[bnode] = ref
1064            print "ok %s" % ref
1065        elif ref.startswith('refs/heads/'):
1066            bmark = ref[len('refs/heads/'):]
1067            new = node
1068            old = bmarks[bmark].hex() if bmark in bmarks else ''
1069
1070            if old == new:
1071                continue
1072
1073            print "ok %s" % ref
1074            if bmark != fake_bmark and \
1075                    not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1076                p_bmarks.append((ref, bmark, old, new))
1077
1078            if peer:
1079                remote_old = peer.listkeys('bookmarks').get(bmark)
1080                if remote_old:
1081                    if not check_tip(ref, 'bookmarks', bmark, remote_old):
1082                        print "error %s fetch first" % ref
1083                        need_fetch = True
1084                        continue
1085
1086            p_revs[bnode] = ref
1087        elif ref.startswith('refs/tags/'):
1088            if dry_run:
1089                print "ok %s" % ref
1090                continue
1091            tag = ref[len('refs/tags/'):]
1092            tag = hgref(tag)
1093            author, msg = parsed_tags.get(tag, (None, None))
1094            if mode == 'git':
1095                if not msg:
1096                    msg = 'Added tag %s for changeset %s' % (tag, node[:12])
1097                tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1098                p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1099            else:
1100                fp = parser.repo.opener('localtags', 'a')
1101                fp.write('%s %s\n' % (node, tag))
1102                fp.close()
1103            p_revs[bnode] = ref
1104            print "ok %s" % ref
1105        else:
1106            # transport-helper/fast-export bugs
1107            continue
1108
1109    if need_fetch:
1110        print
1111        return
1112
1113    if dry_run:
1114        if peer and not force_push:
1115            checkheads(parser.repo, peer, p_revs)
1116        print
1117        return
1118
1119    if peer:
1120        if not push(parser.repo, peer, parsed_refs, p_revs):
1121            # do not update bookmarks
1122            print
1123            return
1124
1125        # update remote bookmarks
1126        remote_bmarks = peer.listkeys('bookmarks')
1127        for ref, bmark, old, new in p_bmarks:
1128            if force_push:
1129                old = remote_bmarks.get(bmark, '')
1130            if not peer.pushkey('bookmarks', bmark, old, new):
1131                print "error %s" % ref
1132    else:
1133        # update local bookmarks
1134        for ref, bmark, old, new in p_bmarks:
1135            if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1136                print "error %s" % ref
1137
1138    print
1139
1140def do_option(parser):
1141    global dry_run, force_push
1142    _, key, value = parser.line.split(' ')
1143    if key == 'dry-run':
1144        dry_run = (value == 'true')
1145        print 'ok'
1146    elif key == 'force':
1147        force_push = (value == 'true')
1148        print 'ok'
1149    else:
1150        print 'unsupported'
1151
1152def fix_path(alias, repo, orig_url):
1153    url = urlparse.urlparse(orig_url, 'file')
1154    if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)):
1155        return
1156    abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1157    cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1158    subprocess.call(cmd)
1159
1160def main(args):
1161    global prefix, gitdir, dirname, branches, bmarks
1162    global marks, blob_marks, parsed_refs
1163    global peer, mode, bad_mail, bad_name
1164    global track_branches, force_push, is_tmp
1165    global parsed_tags
1166    global filenodes
1167    global fake_bmark, hg_version
1168    global dry_run
1169    global notes, alias
1170
1171    marks = None
1172    is_tmp = False
1173    gitdir = os.environ.get('GIT_DIR', None)
1174
1175    if len(args) < 3:
1176        die('Not enough arguments.')
1177
1178    if not gitdir:
1179        die('GIT_DIR not set')
1180
1181    alias = args[1]
1182    url = args[2]
1183    peer = None
1184
1185    hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1186    track_branches = get_config_bool('remote-hg.track-branches', True)
1187    force_push = False
1188
1189    if hg_git_compat:
1190        mode = 'hg'
1191        bad_mail = 'none@none'
1192        bad_name = ''
1193    else:
1194        mode = 'git'
1195        bad_mail = 'unknown'
1196        bad_name = 'Unknown'
1197
1198    if alias[4:] == url:
1199        is_tmp = True
1200        alias = hashlib.sha1(alias).hexdigest()
1201
1202    dirname = os.path.join(gitdir, 'hg', alias)
1203    branches = {}
1204    bmarks = {}
1205    blob_marks = {}
1206    parsed_refs = {}
1207    parsed_tags = {}
1208    filenodes = {}
1209    fake_bmark = None
1210    try:
1211        hg_version = tuple(int(e) for e in util.version().split('.'))
1212    except:
1213        hg_version = None
1214    dry_run = False
1215    notes = set()
1216
1217    repo = get_repo(url, alias)
1218    prefix = 'refs/hg/%s' % alias
1219
1220    if not is_tmp:
1221        fix_path(alias, peer or repo, url)
1222
1223    marks_path = os.path.join(dirname, 'marks-hg')
1224    marks = Marks(marks_path, repo)
1225
1226    if sys.platform == 'win32':
1227        import msvcrt
1228        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1229
1230    parser = Parser(repo)
1231    for line in parser:
1232        if parser.check('capabilities'):
1233            do_capabilities(parser)
1234        elif parser.check('list'):
1235            do_list(parser)
1236        elif parser.check('import'):
1237            do_import(parser)
1238        elif parser.check('export'):
1239            do_export(parser)
1240        elif parser.check('option'):
1241            do_option(parser)
1242        else:
1243            die('unhandled command: %s' % line)
1244        sys.stdout.flush()
1245
1246def bye():
1247    if not marks:
1248        return
1249    if not is_tmp:
1250        marks.store()
1251    else:
1252        shutil.rmtree(dirname)
1253
1254atexit.register(bye)
1255sys.exit(main(sys.argv))