121
|
1 # streamclone.py - streaming clone server support for mercurial
|
|
2 #
|
|
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
|
|
4 #
|
|
5 # This software may be used and distributed according to the terms of the
|
|
6 # GNU General Public License version 2, incorporated herein by reference.
|
|
7
|
|
8 import util, error
|
|
9 from i18n import _
|
|
10
|
|
11 from mercurial import store
|
|
12
|
|
13 class StreamException(Exception):
|
|
14 def __init__(self, code):
|
|
15 Exception.__init__(self)
|
|
16 self.code = code
|
|
17 def __str__(self):
|
|
18 return '%i\n' % self.code
|
|
19
|
|
20 # if server supports streaming clone, it advertises "stream"
|
|
21 # capability with value that is version+flags of repo it is serving.
|
|
22 # client only streams if it can read that repo format.
|
|
23
|
|
24 # stream file format is simple.
|
|
25 #
|
|
26 # server writes out line that says how many files, how many total
|
|
27 # bytes. separator is ascii space, byte counts are strings.
|
|
28 #
|
|
29 # then for each file:
|
|
30 #
|
|
31 # server writes out line that says filename, how many bytes in
|
|
32 # file. separator is ascii nul, byte count is string.
|
|
33 #
|
|
34 # server writes out raw file data.
|
|
35
|
|
36 def stream_out(repo, untrusted=False):
|
|
37 '''stream out all metadata files in repository.
|
|
38 writes to file-like object, must support write() and optional flush().'''
|
|
39
|
|
40 if not repo.ui.configbool('server', 'uncompressed', untrusted=untrusted):
|
|
41 raise StreamException(1)
|
|
42
|
|
43 entries = []
|
|
44 total_bytes = 0
|
|
45 try:
|
|
46 # get consistent snapshot of repo, lock during scan
|
|
47 lock = repo.lock()
|
|
48 try:
|
|
49 repo.ui.debug(_('scanning\n'))
|
|
50 for name, ename, size in repo.store.walk():
|
|
51 # for backwards compat, name was partially encoded
|
|
52 entries.append((store.encodedir(name), size))
|
|
53 total_bytes += size
|
|
54 finally:
|
|
55 lock.release()
|
|
56 except error.LockError:
|
|
57 raise StreamException(2)
|
|
58
|
|
59 yield '0\n'
|
|
60 repo.ui.debug(_('%d files, %d bytes to transfer\n') %
|
|
61 (len(entries), total_bytes))
|
|
62 yield '%d %d\n' % (len(entries), total_bytes)
|
|
63 for name, size in entries:
|
|
64 repo.ui.debug(_('sending %s (%d bytes)\n') % (name, size))
|
|
65 yield '%s\0%d\n' % (name, size)
|
|
66 for chunk in util.filechunkiter(repo.sopener(name), limit=size):
|
|
67 yield chunk
|