##// END OF EJS Templates
largefiles: make the protocol hack for replacing heads with lheads more precise...
Mads Kiilerich -
r19917:cff331cb default
parent child Browse files
Show More
@@ -1,174 +1,177 b''
1 1 # Copyright 2011 Fog Creek Software
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 import os
7 7 import urllib2
8 import re
8 9
9 10 from mercurial import error, httppeer, util, wireproto
10 11 from mercurial.wireproto import batchable, future
11 12 from mercurial.i18n import _
12 13
13 14 import lfutil
14 15
15 16 LARGEFILES_REQUIRED_MSG = ('\nThis repository uses the largefiles extension.'
16 17 '\n\nPlease enable it in your Mercurial config '
17 18 'file.\n')
18 19
19 20 # these will all be replaced by largefiles.uisetup
20 21 capabilitiesorig = None
21 22 ssholdcallstream = None
22 23 httpoldcallstream = None
23 24
24 25 def putlfile(repo, proto, sha):
25 26 '''Put a largefile into a repository's local store and into the
26 27 user cache.'''
27 28 proto.redirect()
28 29
29 30 path = lfutil.storepath(repo, sha)
30 31 util.makedirs(os.path.dirname(path))
31 32 tmpfp = util.atomictempfile(path, createmode=repo.store.createmode)
32 33
33 34 try:
34 35 try:
35 36 proto.getfile(tmpfp)
36 37 tmpfp._fp.seek(0)
37 38 if sha != lfutil.hexsha1(tmpfp._fp):
38 39 raise IOError(0, _('largefile contents do not match hash'))
39 40 tmpfp.close()
40 41 lfutil.linktousercache(repo, sha)
41 42 except IOError, e:
42 43 repo.ui.warn(_('largefiles: failed to put %s into store: %s') %
43 44 (sha, e.strerror))
44 45 return wireproto.pushres(1)
45 46 finally:
46 47 tmpfp.discard()
47 48
48 49 return wireproto.pushres(0)
49 50
50 51 def getlfile(repo, proto, sha):
51 52 '''Retrieve a largefile from the repository-local cache or system
52 53 cache.'''
53 54 filename = lfutil.findfile(repo, sha)
54 55 if not filename:
55 56 raise util.Abort(_('requested largefile %s not present in cache') % sha)
56 57 f = open(filename, 'rb')
57 58 length = os.fstat(f.fileno())[6]
58 59
59 60 # Since we can't set an HTTP content-length header here, and
60 61 # Mercurial core provides no way to give the length of a streamres
61 62 # (and reading the entire file into RAM would be ill-advised), we
62 63 # just send the length on the first line of the response, like the
63 64 # ssh proto does for string responses.
64 65 def generator():
65 66 yield '%d\n' % length
66 67 for chunk in util.filechunkiter(f):
67 68 yield chunk
68 69 return wireproto.streamres(generator())
69 70
70 71 def statlfile(repo, proto, sha):
71 72 '''Return '2\n' if the largefile is missing, '0\n' if it seems to be in
72 73 good condition.
73 74
74 75 The value 1 is reserved for mismatched checksum, but that is too expensive
75 76 to be verified on every stat and must be caught be running 'hg verify'
76 77 server side.'''
77 78 filename = lfutil.findfile(repo, sha)
78 79 if not filename:
79 80 return '2\n'
80 81 return '0\n'
81 82
82 83 def wirereposetup(ui, repo):
83 84 class lfileswirerepository(repo.__class__):
84 85 def putlfile(self, sha, fd):
85 86 # unfortunately, httprepository._callpush tries to convert its
86 87 # input file-like into a bundle before sending it, so we can't use
87 88 # it ...
88 89 if issubclass(self.__class__, httppeer.httppeer):
89 90 res = None
90 91 try:
91 92 res = self._call('putlfile', data=fd, sha=sha,
92 93 headers={'content-type':'application/mercurial-0.1'})
93 94 d, output = res.split('\n', 1)
94 95 for l in output.splitlines(True):
95 96 self.ui.warn(_('remote: '), l, '\n')
96 97 return int(d)
97 98 except (ValueError, urllib2.HTTPError):
98 99 self.ui.warn(_('unexpected putlfile response: %s') % res)
99 100 return 1
100 101 # ... but we can't use sshrepository._call because the data=
101 102 # argument won't get sent, and _callpush does exactly what we want
102 103 # in this case: send the data straight through
103 104 else:
104 105 try:
105 106 ret, output = self._callpush("putlfile", fd, sha=sha)
106 107 if ret == "":
107 108 raise error.ResponseError(_('putlfile failed:'),
108 109 output)
109 110 return int(ret)
110 111 except IOError:
111 112 return 1
112 113 except ValueError:
113 114 raise error.ResponseError(
114 115 _('putlfile failed (unexpected response):'), ret)
115 116
116 117 def getlfile(self, sha):
117 118 """returns an iterable with the chunks of the file with sha sha"""
118 119 stream = self._callstream("getlfile", sha=sha)
119 120 length = stream.readline()
120 121 try:
121 122 length = int(length)
122 123 except ValueError:
123 124 self._abort(error.ResponseError(_("unexpected response:"),
124 125 length))
125 126
126 127 # SSH streams will block if reading more than length
127 128 for chunk in util.filechunkiter(stream, 128 * 1024, length):
128 129 yield chunk
129 130 # HTTP streams must hit the end to process the last empty
130 131 # chunk of Chunked-Encoding so the connection can be reused.
131 132 if issubclass(self.__class__, httppeer.httppeer):
132 133 chunk = stream.read(1)
133 134 if chunk:
134 135 self._abort(error.ResponseError(_("unexpected response:"),
135 136 chunk))
136 137
137 138 @batchable
138 139 def statlfile(self, sha):
139 140 f = future()
140 141 result = {'sha': sha}
141 142 yield result, f
142 143 try:
143 144 yield int(f.value)
144 145 except (ValueError, urllib2.HTTPError):
145 146 # If the server returns anything but an integer followed by a
146 147 # newline, newline, it's not speaking our language; if we get
147 148 # an HTTP error, we can't be sure the largefile is present;
148 149 # either way, consider it missing.
149 150 yield 2
150 151
151 152 repo.__class__ = lfileswirerepository
152 153
153 154 # advertise the largefiles=serve capability
154 155 def capabilities(repo, proto):
155 156 return capabilitiesorig(repo, proto) + ' largefiles=serve'
156 157
157 158 def heads(repo, proto):
158 159 if lfutil.islfilesrepo(repo):
159 160 return wireproto.ooberror(LARGEFILES_REQUIRED_MSG)
160 161 return wireproto.heads(repo, proto)
161 162
162 163 def sshrepocallstream(self, cmd, **args):
163 164 if cmd == 'heads' and self.capable('largefiles'):
164 165 cmd = 'lheads'
165 166 if cmd == 'batch' and self.capable('largefiles'):
166 167 args['cmds'] = args['cmds'].replace('heads ', 'lheads ')
167 168 return ssholdcallstream(self, cmd, **args)
168 169
170 headsre = re.compile(r'(^|;)heads\b')
171
169 172 def httprepocallstream(self, cmd, **args):
170 173 if cmd == 'heads' and self.capable('largefiles'):
171 174 cmd = 'lheads'
172 175 if cmd == 'batch' and self.capable('largefiles'):
173 args['cmds'] = args['cmds'].replace('heads ', 'lheads ')
176 args['cmds'] = headsre.sub('lheads', args['cmds'])
174 177 return httpoldcallstream(self, cmd, **args)
General Comments 0
You need to be logged in to leave comments. Login now