##// END OF EJS Templates
largefiles: don't mute and obfuscate http errors when putlfile fails...
Mads Kiilerich -
r26825:78539633 stable
parent child Browse files
Show More
@@ -1,176 +1,175
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 8 import re
9 9
10 10 from mercurial import error, httppeer, util, wireproto
11 11 from mercurial.i18n import _
12 12
13 13 import lfutil
14 14
15 15 LARGEFILES_REQUIRED_MSG = ('\nThis repository uses the largefiles extension.'
16 16 '\n\nPlease enable it in your Mercurial config '
17 17 'file.\n')
18 18
19 19 # these will all be replaced by largefiles.uisetup
20 20 capabilitiesorig = None
21 21 ssholdcallstream = None
22 22 httpoldcallstream = None
23 23
24 24 def putlfile(repo, proto, sha):
25 25 '''Put a largefile into a repository's local store and into the
26 26 user cache.'''
27 27 proto.redirect()
28 28
29 29 path = lfutil.storepath(repo, sha)
30 30 util.makedirs(os.path.dirname(path))
31 31 tmpfp = util.atomictempfile(path, createmode=repo.store.createmode)
32 32
33 33 try:
34 34 proto.getfile(tmpfp)
35 35 tmpfp._fp.seek(0)
36 36 if sha != lfutil.hexsha1(tmpfp._fp):
37 37 raise IOError(0, _('largefile contents do not match hash'))
38 38 tmpfp.close()
39 39 lfutil.linktousercache(repo, sha)
40 40 except IOError as e:
41 41 repo.ui.warn(_('largefiles: failed to put %s into store: %s\n') %
42 42 (sha, e.strerror))
43 43 return wireproto.pushres(1)
44 44 finally:
45 45 tmpfp.discard()
46 46
47 47 return wireproto.pushres(0)
48 48
49 49 def getlfile(repo, proto, sha):
50 50 '''Retrieve a largefile from the repository-local cache or system
51 51 cache.'''
52 52 filename = lfutil.findfile(repo, sha)
53 53 if not filename:
54 54 raise error.Abort(_('requested largefile %s not present in cache')
55 55 % sha)
56 56 f = open(filename, 'rb')
57 57 length = os.fstat(f.fileno())[6]
58 58
59 59 # Since we can't set an HTTP content-length header here, and
60 60 # Mercurial core provides no way to give the length of a streamres
61 61 # (and reading the entire file into RAM would be ill-advised), we
62 62 # just send the length on the first line of the response, like the
63 63 # ssh proto does for string responses.
64 64 def generator():
65 65 yield '%d\n' % length
66 66 for chunk in util.filechunkiter(f):
67 67 yield chunk
68 68 return wireproto.streamres(generator())
69 69
70 70 def statlfile(repo, proto, sha):
71 71 '''Return '2\n' if the largefile is missing, '0\n' if it seems to be in
72 72 good condition.
73 73
74 74 The value 1 is reserved for mismatched checksum, but that is too expensive
75 75 to be verified on every stat and must be caught be running 'hg verify'
76 76 server side.'''
77 77 filename = lfutil.findfile(repo, sha)
78 78 if not filename:
79 79 return '2\n'
80 80 return '0\n'
81 81
82 82 def wirereposetup(ui, repo):
83 83 class lfileswirerepository(repo.__class__):
84 84 def putlfile(self, sha, fd):
85 85 # unfortunately, httprepository._callpush tries to convert its
86 86 # input file-like into a bundle before sending it, so we can't use
87 87 # it ...
88 88 if issubclass(self.__class__, httppeer.httppeer):
89 res = None
90 try:
91 89 res = self._call('putlfile', data=fd, sha=sha,
92 90 headers={'content-type':'application/mercurial-0.1'})
91 try:
93 92 d, output = res.split('\n', 1)
94 93 for l in output.splitlines(True):
95 94 self.ui.warn(_('remote: '), l) # assume l ends with \n
96 95 return int(d)
97 except (ValueError, urllib2.HTTPError):
96 except ValueError:
98 97 self.ui.warn(_('unexpected putlfile response: %r\n') % res)
99 98 return 1
100 99 # ... but we can't use sshrepository._call because the data=
101 100 # argument won't get sent, and _callpush does exactly what we want
102 101 # in this case: send the data straight through
103 102 else:
104 103 try:
105 104 ret, output = self._callpush("putlfile", fd, sha=sha)
106 105 if ret == "":
107 106 raise error.ResponseError(_('putlfile failed:'),
108 107 output)
109 108 return int(ret)
110 109 except IOError:
111 110 return 1
112 111 except ValueError:
113 112 raise error.ResponseError(
114 113 _('putlfile failed (unexpected response):'), ret)
115 114
116 115 def getlfile(self, sha):
117 116 """returns an iterable with the chunks of the file with sha sha"""
118 117 stream = self._callstream("getlfile", sha=sha)
119 118 length = stream.readline()
120 119 try:
121 120 length = int(length)
122 121 except ValueError:
123 122 self._abort(error.ResponseError(_("unexpected response:"),
124 123 length))
125 124
126 125 # SSH streams will block if reading more than length
127 126 for chunk in util.filechunkiter(stream, 128 * 1024, length):
128 127 yield chunk
129 128 # HTTP streams must hit the end to process the last empty
130 129 # chunk of Chunked-Encoding so the connection can be reused.
131 130 if issubclass(self.__class__, httppeer.httppeer):
132 131 chunk = stream.read(1)
133 132 if chunk:
134 133 self._abort(error.ResponseError(_("unexpected response:"),
135 134 chunk))
136 135
137 136 @wireproto.batchable
138 137 def statlfile(self, sha):
139 138 f = wireproto.future()
140 139 result = {'sha': sha}
141 140 yield result, f
142 141 try:
143 142 yield int(f.value)
144 143 except (ValueError, urllib2.HTTPError):
145 144 # If the server returns anything but an integer followed by a
146 145 # newline, newline, it's not speaking our language; if we get
147 146 # an HTTP error, we can't be sure the largefile is present;
148 147 # either way, consider it missing.
149 148 yield 2
150 149
151 150 repo.__class__ = lfileswirerepository
152 151
153 152 # advertise the largefiles=serve capability
154 153 def capabilities(repo, proto):
155 154 return capabilitiesorig(repo, proto) + ' largefiles=serve'
156 155
157 156 def heads(repo, proto):
158 157 if lfutil.islfilesrepo(repo):
159 158 return wireproto.ooberror(LARGEFILES_REQUIRED_MSG)
160 159 return wireproto.heads(repo, proto)
161 160
162 161 def sshrepocallstream(self, cmd, **args):
163 162 if cmd == 'heads' and self.capable('largefiles'):
164 163 cmd = 'lheads'
165 164 if cmd == 'batch' and self.capable('largefiles'):
166 165 args['cmds'] = args['cmds'].replace('heads ', 'lheads ')
167 166 return ssholdcallstream(self, cmd, **args)
168 167
169 168 headsre = re.compile(r'(^|;)heads\b')
170 169
171 170 def httprepocallstream(self, cmd, **args):
172 171 if cmd == 'heads' and self.capable('largefiles'):
173 172 cmd = 'lheads'
174 173 if cmd == 'batch' and self.capable('largefiles'):
175 174 args['cmds'] = headsre.sub('lheads', args['cmds'])
176 175 return httpoldcallstream(self, cmd, **args)
@@ -1,233 +1,231
1 1 Create user cache directory
2 2
3 3 $ USERCACHE=`pwd`/cache; export USERCACHE
4 4 $ cat <<EOF >> ${HGRCPATH}
5 5 > [extensions]
6 6 > hgext.largefiles=
7 7 > [largefiles]
8 8 > usercache=${USERCACHE}
9 9 > EOF
10 10 $ mkdir -p ${USERCACHE}
11 11
12 12 Create source repo, and commit adding largefile.
13 13
14 14 $ hg init src
15 15 $ cd src
16 16 $ echo large > large
17 17 $ hg add --large large
18 18 $ hg commit -m 'add largefile'
19 19 $ hg rm large
20 20 $ hg commit -m 'branchhead without largefile'
21 21 $ hg up -qr 0
22 22 $ cd ..
23 23
24 24 Discard all cached largefiles in USERCACHE
25 25
26 26 $ rm -rf ${USERCACHE}
27 27
28 28 Create mirror repo, and pull from source without largefile:
29 29 "pull" is used instead of "clone" for suppression of (1) updating to
30 30 tip (= caching largefile from source repo), and (2) recording source
31 31 repo as "default" path in .hg/hgrc.
32 32
33 33 $ hg init mirror
34 34 $ cd mirror
35 35 $ hg pull ../src
36 36 pulling from ../src
37 37 requesting all changes
38 38 adding changesets
39 39 adding manifests
40 40 adding file changes
41 41 added 2 changesets with 1 changes to 1 files
42 42 (run 'hg update' to get a working copy)
43 43
44 44 Update working directory to "tip", which requires largefile("large"),
45 45 but there is no cache file for it. So, hg must treat it as
46 46 "missing"(!) file.
47 47
48 48 $ hg update -r0
49 49 getting changed largefiles
50 50 large: largefile 7f7097b041ccf68cc5561e9600da4655d21c6d18 not available from file:/*/$TESTTMP/mirror (glob)
51 51 0 largefiles updated, 0 removed
52 52 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
53 53 $ hg status
54 54 ! large
55 55
56 56 Update working directory to null: this cleanup .hg/largefiles/dirstate
57 57
58 58 $ hg update null
59 59 getting changed largefiles
60 60 0 largefiles updated, 0 removed
61 61 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
62 62
63 63 Update working directory to tip, again.
64 64
65 65 $ hg update -r0
66 66 getting changed largefiles
67 67 large: largefile 7f7097b041ccf68cc5561e9600da4655d21c6d18 not available from file:/*/$TESTTMP/mirror (glob)
68 68 0 largefiles updated, 0 removed
69 69 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
70 70 $ hg status
71 71 ! large
72 72 $ cd ..
73 73
74 74 Verify that largefiles from pulled branchheads are fetched, also to an empty repo
75 75
76 76 $ hg init mirror2
77 77 $ hg -R mirror2 pull src -r0
78 78 pulling from src
79 79 adding changesets
80 80 adding manifests
81 81 adding file changes
82 82 added 1 changesets with 1 changes to 1 files
83 83 (run 'hg update' to get a working copy)
84 84
85 85 #if unix-permissions
86 86
87 87 Portable way to print file permissions:
88 88
89 89 $ cat > ls-l.py <<EOF
90 90 > #!/usr/bin/env python
91 91 > import sys, os
92 92 > path = sys.argv[1]
93 93 > print '%03o' % (os.lstat(path).st_mode & 0777)
94 94 > EOF
95 95 $ chmod +x ls-l.py
96 96
97 97 Test that files in .hg/largefiles inherit mode from .hg/store, not
98 98 from file in working copy:
99 99
100 100 $ cd src
101 101 $ chmod 750 .hg/store
102 102 $ chmod 660 large
103 103 $ echo change >> large
104 104 $ hg commit -m change
105 105 created new head
106 106 $ ../ls-l.py .hg/largefiles/e151b474069de4ca6898f67ce2f2a7263adf8fea
107 107 640
108 108
109 109 Test permission of with files in .hg/largefiles created by update:
110 110
111 111 $ cd ../mirror
112 112 $ rm -r "$USERCACHE" .hg/largefiles # avoid links
113 113 $ chmod 750 .hg/store
114 114 $ hg pull ../src --update -q
115 115 $ ../ls-l.py .hg/largefiles/e151b474069de4ca6898f67ce2f2a7263adf8fea
116 116 640
117 117
118 118 Test permission of files created by push:
119 119
120 120 $ hg serve -R ../src -d -p $HGPORT --pid-file hg.pid \
121 121 > --config "web.allow_push=*" --config web.push_ssl=no
122 122 $ cat hg.pid >> $DAEMON_PIDS
123 123
124 124 $ echo change >> large
125 125 $ hg commit -m change
126 126
127 127 $ rm -r "$USERCACHE"
128 128
129 129 $ hg push -q http://localhost:$HGPORT/
130 130
131 131 $ ../ls-l.py ../src/.hg/largefiles/b734e14a0971e370408ab9bce8d56d8485e368a9
132 132 640
133 133
134 134 $ cd ..
135 135
136 136 #endif
137 137
138 138 Test issue 4053 (remove --after on a deleted, uncommitted file shouldn't say
139 139 it is missing, but a remove on a nonexistent unknown file still should. Same
140 140 for a forget.)
141 141
142 142 $ cd src
143 143 $ touch x
144 144 $ hg add x
145 145 $ mv x y
146 146 $ hg remove -A x y ENOENT
147 147 ENOENT: * (glob)
148 148 not removing y: file is untracked
149 149 [1]
150 150 $ hg add y
151 151 $ mv y z
152 152 $ hg forget y z ENOENT
153 153 ENOENT: * (glob)
154 154 not removing z: file is already untracked
155 155 [1]
156 156
157 157 Largefiles are accessible from the share's store
158 158 $ cd ..
159 159 $ hg share -q src share_dst --config extensions.share=
160 160 $ hg -R share_dst update -r0
161 161 getting changed largefiles
162 162 1 largefiles updated, 0 removed
163 163 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
164 164
165 165 $ echo modified > share_dst/large
166 166 $ hg -R share_dst ci -m modified
167 167 created new head
168 168
169 169 Only dirstate is in the local store for the share, and the largefile is in the
170 170 share source's local store. Avoid the extra largefiles added in the unix
171 171 conditional above.
172 172 $ hash=`hg -R share_dst cat share_dst/.hglf/large`
173 173 $ echo $hash
174 174 e2fb5f2139d086ded2cb600d5a91a196e76bf020
175 175
176 176 $ find share_dst/.hg/largefiles/* | sort
177 177 share_dst/.hg/largefiles/dirstate
178 178
179 179 $ find src/.hg/largefiles/* | egrep "(dirstate|$hash)" | sort
180 180 src/.hg/largefiles/dirstate
181 181 src/.hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020
182 182
183 183 Inject corruption into the largefiles store and see how update handles that:
184 184
185 185 $ cd src
186 186 $ hg up -qC
187 187 $ cat large
188 188 modified
189 189 $ rm large
190 190 $ cat .hglf/large
191 191 e2fb5f2139d086ded2cb600d5a91a196e76bf020
192 192 $ mv .hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020 ..
193 193 $ echo corruption > .hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020
194 194 $ hg up -C
195 195 getting changed largefiles
196 196 large: data corruption in $TESTTMP/src/.hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020 with hash 6a7bb2556144babe3899b25e5428123735bb1e27
197 197 0 largefiles updated, 0 removed
198 198 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
199 199 $ hg st
200 200 ! large
201 201 ? z
202 202 $ rm .hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020
203 203
204 204 #if serve
205 205
206 206 Test coverage of error handling from putlfile:
207 207
208 208 $ mkdir $TESTTMP/mirrorcache
209 209 $ hg serve -R ../mirror -d -p $HGPORT1 --pid-file hg.pid --config largefiles.usercache=$TESTTMP/mirrorcache
210 210 $ cat hg.pid >> $DAEMON_PIDS
211 211
212 (the following push fails but doesn't show why)
213 212 $ hg push http://localhost:$HGPORT1 -f --config files.usercache=nocache
214 213 pushing to http://localhost:$HGPORT1/
215 214 searching for changes
216 unexpected putlfile response: None
217 abort: remotestore: could not put $TESTTMP/src/.hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020 to remote store http://localhost:$HGPORT1/
215 abort: remotestore: could not open file $TESTTMP/src/.hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020: HTTP Error 403: ssl required
218 216 [255]
219 217
220 218 $ rm .hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020
221 219
222 220 Test coverage of 'missing from store':
223 221
224 222 $ hg serve -R ../mirror -d -p $HGPORT2 --pid-file hg.pid --config largefiles.usercache=$TESTTMP/mirrorcache --config "web.allow_push=*" --config web.push_ssl=no
225 223 $ cat hg.pid >> $DAEMON_PIDS
226 224
227 225 $ hg push http://localhost:$HGPORT2 -f --config largefiles.usercache=nocache
228 226 pushing to http://localhost:$HGPORT2/
229 227 searching for changes
230 228 abort: largefile e2fb5f2139d086ded2cb600d5a91a196e76bf020 missing from store (needs to be uploaded)
231 229 [255]
232 230
233 231 #endif
General Comments 0
You need to be logged in to leave comments. Login now