view mercurial/streamclone.py @ 4865:439e2f2fde42

Fix inconsistency for the stream_out capability in hgweb During some experiments of mine, the uncompressed cloning could not be enabled for hgweb.cgi nor hgwebdir.cgi though the server claimed to be stream_out capable. The only solution was to enable it using the user's .hgrc file. This solution is not acceptable when publishing the repos through an HTTP server because the CGI runs as a www dedicated user whose's home hgrc file may not be accessible to users publishing their repos through their userdir. For such cases we could end up with this typical debug output: hg --debug clone --uncompressed http://server/hg/project destination directory: project sending capabilities command capabilities: lookup changegroupsubset stream=1 unbundle=HG10GZ,HG10BZ,HG10UN sending stream_out command abort: operation forbidden by server The error lies in the fact the hgweb object defines new accessors to the repo configuration that trust things by default (untrusted=True) but the streamclone:stream_out function uses the usual accessors to the repo.ui object, which do not trust by default (untrusted=False) Fix this inconsistency, adding a new parameter to the stream_out function. hgweb then forces a "trust by default" behavior.
author Edouard Gomez <ed.gomez@free.fr>
date Sat, 12 May 2007 00:41:30 +0200
parents 9dc64c8414ca
children 97b734fb9c6f
line wrap: on
line source

# streamclone.py - streaming clone server support for mercurial
#
# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.

from i18n import _
import os, stat, util, lock

# if server supports streaming clone, it advertises "stream"
# capability with value that is version+flags of repo it is serving.
# client only streams if it can read that repo format.

def walkrepo(root):
    '''iterate over metadata files in repository.
    walk in natural (sorted) order.
    yields 2-tuples: name of .d or .i file, size of file.'''

    strip_count = len(root) + len(os.sep)
    def walk(path, recurse):
        ents = os.listdir(path)
        ents.sort()
        for e in ents:
            pe = os.path.join(path, e)
            st = os.lstat(pe)
            if stat.S_ISDIR(st.st_mode):
                if recurse:
                    for x in walk(pe, True):
                        yield x
            else:
                if not stat.S_ISREG(st.st_mode) or len(e) < 2:
                    continue
                sfx = e[-2:]
                if sfx in ('.d', '.i'):
                    yield pe[strip_count:], st.st_size
    # write file data first
    for x in walk(os.path.join(root, 'data'), True):
        yield x
    # write manifest before changelog
    meta = list(walk(root, False))
    meta.sort()
    meta.reverse()
    for x in meta:
        yield x

# stream file format is simple.
#
# server writes out line that says how many files, how many total
# bytes.  separator is ascii space, byte counts are strings.
#
# then for each file:
#
#   server writes out line that says file name, how many bytes in
#   file.  separator is ascii nul, byte count is string.
#
#   server writes out raw file data.

def stream_out(repo, fileobj, untrusted=False):
    '''stream out all metadata files in repository.
    writes to file-like object, must support write() and optional flush().'''

    if not repo.ui.configbool('server', 'uncompressed', untrusted=untrusted):
        fileobj.write('1\n')
        return

    # get consistent snapshot of repo. lock during scan so lock not
    # needed while we stream, and commits can happen.
    try:
        repolock = repo.lock()
    except (lock.LockHeld, lock.LockUnavailable), inst:
        repo.ui.warn('locking the repository failed: %s\n' % (inst,))
        fileobj.write('2\n')
        return

    fileobj.write('0\n')
    repo.ui.debug('scanning\n')
    entries = []
    total_bytes = 0
    for name, size in walkrepo(repo.spath):
        name = repo.decodefn(util.pconvert(name))
        entries.append((name, size))
        total_bytes += size
    repolock.release()

    repo.ui.debug('%d files, %d bytes to transfer\n' %
                  (len(entries), total_bytes))
    fileobj.write('%d %d\n' % (len(entries), total_bytes))
    for name, size in entries:
        repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
        fileobj.write('%s\0%d\n' % (name, size))
        for chunk in util.filechunkiter(repo.sopener(name), limit=size):
            fileobj.write(chunk)
    flush = getattr(fileobj, 'flush', None)
    if flush: flush()