##// END OF EJS Templates
httprepo: make __del__ more stable in error situations...
Mads Kiilerich -
r15246:7b15dd91 default
parent child Browse files
Show More
@@ -1,245 +1,247 b''
1 1 # httprepo.py - HTTP repository proxy classes for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from node import nullid
10 10 from i18n import _
11 11 import changegroup, statichttprepo, error, httpconnection, url, util, wireproto
12 12 import os, urllib, urllib2, zlib, httplib
13 13 import errno, socket
14 14
15 15 def zgenerator(f):
16 16 zd = zlib.decompressobj()
17 17 try:
18 18 for chunk in util.filechunkiter(f):
19 19 while chunk:
20 20 yield zd.decompress(chunk, 2**18)
21 21 chunk = zd.unconsumed_tail
22 22 except httplib.HTTPException:
23 23 raise IOError(None, _('connection ended unexpectedly'))
24 24 yield zd.flush()
25 25
26 26 class httprepository(wireproto.wirerepository):
27 27 def __init__(self, ui, path):
28 28 self.path = path
29 29 self.caps = None
30 30 self.handler = None
31 self.urlopener = None
31 32 u = util.url(path)
32 33 if u.query or u.fragment:
33 34 raise util.Abort(_('unsupported URL component: "%s"') %
34 35 (u.query or u.fragment))
35 36
36 37 # urllib cannot handle URLs with embedded user or passwd
37 38 self._url, authinfo = u.authinfo()
38 39
39 40 self.ui = ui
40 41 self.ui.debug('using %s\n' % self._url)
41 42
42 43 self.urlopener = url.opener(ui, authinfo)
43 44
44 45 def __del__(self):
46 if self.urlopener:
45 47 for h in self.urlopener.handlers:
46 48 h.close()
47 49 getattr(h, "close_all", lambda : None)()
48 50
49 51 def url(self):
50 52 return self.path
51 53
52 54 # look up capabilities only when needed
53 55
54 56 def _fetchcaps(self):
55 57 self.caps = set(self._call('capabilities').split())
56 58
57 59 def get_caps(self):
58 60 if self.caps is None:
59 61 try:
60 62 self._fetchcaps()
61 63 except error.RepoError:
62 64 self.caps = set()
63 65 self.ui.debug('capabilities: %s\n' %
64 66 (' '.join(self.caps or ['none'])))
65 67 return self.caps
66 68
67 69 capabilities = property(get_caps)
68 70
69 71 def lock(self):
70 72 raise util.Abort(_('operation not supported over http'))
71 73
72 74 def _callstream(self, cmd, **args):
73 75 if cmd == 'pushkey':
74 76 args['data'] = ''
75 77 data = args.pop('data', None)
76 78 size = 0
77 79 if util.safehasattr(data, 'length'):
78 80 size = data.length
79 81 elif data is not None:
80 82 size = len(data)
81 83 headers = args.pop('headers', {})
82 84
83 85 if size and self.ui.configbool('ui', 'usehttp2', False):
84 86 headers['Expect'] = '100-Continue'
85 87 headers['X-HgHttp2'] = '1'
86 88
87 89 self.ui.debug("sending %s command\n" % cmd)
88 90 q = [('cmd', cmd)]
89 91 headersize = 0
90 92 if len(args) > 0:
91 93 httpheader = self.capable('httpheader')
92 94 if httpheader:
93 95 headersize = int(httpheader.split(',')[0])
94 96 if headersize > 0:
95 97 # The headers can typically carry more data than the URL.
96 98 encargs = urllib.urlencode(sorted(args.items()))
97 99 headerfmt = 'X-HgArg-%s'
98 100 contentlen = headersize - len(headerfmt % '000' + ': \r\n')
99 101 headernum = 0
100 102 for i in xrange(0, len(encargs), contentlen):
101 103 headernum += 1
102 104 header = headerfmt % str(headernum)
103 105 headers[header] = encargs[i:i + contentlen]
104 106 varyheaders = [headerfmt % str(h) for h in range(1, headernum + 1)]
105 107 headers['Vary'] = ','.join(varyheaders)
106 108 else:
107 109 q += sorted(args.items())
108 110 qs = '?%s' % urllib.urlencode(q)
109 111 cu = "%s%s" % (self._url, qs)
110 112 req = urllib2.Request(cu, data, headers)
111 113 if data is not None:
112 114 self.ui.debug("sending %s bytes\n" % size)
113 115 req.add_unredirected_header('Content-Length', '%d' % size)
114 116 try:
115 117 resp = self.urlopener.open(req)
116 118 except urllib2.HTTPError, inst:
117 119 if inst.code == 401:
118 120 raise util.Abort(_('authorization failed'))
119 121 raise
120 122 except httplib.HTTPException, inst:
121 123 self.ui.debug('http error while sending %s command\n' % cmd)
122 124 self.ui.traceback()
123 125 raise IOError(None, inst)
124 126 except IndexError:
125 127 # this only happens with Python 2.3, later versions raise URLError
126 128 raise util.Abort(_('http error, possibly caused by proxy setting'))
127 129 # record the url we got redirected to
128 130 resp_url = resp.geturl()
129 131 if resp_url.endswith(qs):
130 132 resp_url = resp_url[:-len(qs)]
131 133 if self._url.rstrip('/') != resp_url.rstrip('/'):
132 134 if not self.ui.quiet:
133 135 self.ui.warn(_('real URL is %s\n') % resp_url)
134 136 self._url = resp_url
135 137 try:
136 138 proto = resp.getheader('content-type')
137 139 except AttributeError:
138 140 proto = resp.headers.get('content-type', '')
139 141
140 142 safeurl = util.hidepassword(self._url)
141 143 if proto.startswith('application/hg-error'):
142 144 raise error.OutOfBandError(resp.read())
143 145 # accept old "text/plain" and "application/hg-changegroup" for now
144 146 if not (proto.startswith('application/mercurial-') or
145 147 proto.startswith('text/plain') or
146 148 proto.startswith('application/hg-changegroup')):
147 149 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
148 150 raise error.RepoError(
149 151 _("'%s' does not appear to be an hg repository:\n"
150 152 "---%%<--- (%s)\n%s\n---%%<---\n")
151 153 % (safeurl, proto or 'no content-type', resp.read()))
152 154
153 155 if proto.startswith('application/mercurial-'):
154 156 try:
155 157 version = proto.split('-', 1)[1]
156 158 version_info = tuple([int(n) for n in version.split('.')])
157 159 except ValueError:
158 160 raise error.RepoError(_("'%s' sent a broken Content-Type "
159 161 "header (%s)") % (safeurl, proto))
160 162 if version_info > (0, 1):
161 163 raise error.RepoError(_("'%s' uses newer protocol %s") %
162 164 (safeurl, version))
163 165
164 166 return resp
165 167
166 168 def _call(self, cmd, **args):
167 169 fp = self._callstream(cmd, **args)
168 170 try:
169 171 return fp.read()
170 172 finally:
171 173 # if using keepalive, allow connection to be reused
172 174 fp.close()
173 175
174 176 def _callpush(self, cmd, cg, **args):
175 177 # have to stream bundle to a temp file because we do not have
176 178 # http 1.1 chunked transfer.
177 179
178 180 types = self.capable('unbundle')
179 181 try:
180 182 types = types.split(',')
181 183 except AttributeError:
182 184 # servers older than d1b16a746db6 will send 'unbundle' as a
183 185 # boolean capability. They only support headerless/uncompressed
184 186 # bundles.
185 187 types = [""]
186 188 for x in types:
187 189 if x in changegroup.bundletypes:
188 190 type = x
189 191 break
190 192
191 193 tempname = changegroup.writebundle(cg, None, type)
192 194 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
193 195 headers = {'Content-Type': 'application/mercurial-0.1'}
194 196
195 197 try:
196 198 try:
197 199 r = self._call(cmd, data=fp, headers=headers, **args)
198 200 vals = r.split('\n', 1)
199 201 if len(vals) < 2:
200 202 raise error.ResponseError(_("unexpected response:"), r)
201 203 return vals
202 204 except socket.error, err:
203 205 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
204 206 raise util.Abort(_('push failed: %s') % err.args[1])
205 207 raise util.Abort(err.args[1])
206 208 finally:
207 209 fp.close()
208 210 os.unlink(tempname)
209 211
210 212 def _abort(self, exception):
211 213 raise exception
212 214
213 215 def _decompress(self, stream):
214 216 return util.chunkbuffer(zgenerator(stream))
215 217
216 218 class httpsrepository(httprepository):
217 219 def __init__(self, ui, path):
218 220 if not url.has_https:
219 221 raise util.Abort(_('Python support for SSL and HTTPS '
220 222 'is not installed'))
221 223 httprepository.__init__(self, ui, path)
222 224
223 225 def instance(ui, path, create):
224 226 if create:
225 227 raise util.Abort(_('cannot create new http repository'))
226 228 try:
227 229 if path.startswith('https:'):
228 230 inst = httpsrepository(ui, path)
229 231 else:
230 232 inst = httprepository(ui, path)
231 233 try:
232 234 # Try to do useful work when checking compatibility.
233 235 # Usually saves a roundtrip since we want the caps anyway.
234 236 inst._fetchcaps()
235 237 except error.RepoError:
236 238 # No luck, try older compatibility check.
237 239 inst.between([(nullid, nullid)])
238 240 return inst
239 241 except error.RepoError, httpexception:
240 242 try:
241 243 r = statichttprepo.instance(ui, "static-" + path, create)
242 244 ui.note('(falling back to static-http)\n')
243 245 return r
244 246 except error.RepoError:
245 247 raise httpexception # use the original http RepoError instead
@@ -1,203 +1,207 b''
1 1 Test basic functionality of url#rev syntax
2 2
3 3 $ hg init repo
4 4 $ cd repo
5 5 $ echo a > a
6 6 $ hg ci -qAm 'add a'
7 7 $ hg branch foo
8 8 marked working directory as branch foo
9 9 $ echo >> a
10 10 $ hg ci -m 'change a'
11 11 $ cd ..
12 12
13 13 $ hg clone 'repo#foo' clone
14 14 adding changesets
15 15 adding manifests
16 16 adding file changes
17 17 added 2 changesets with 2 changes to 1 files
18 18 updating to branch foo
19 19 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
20 20
21 21 $ hg --cwd clone heads
22 22 changeset: 1:cd2a86ecc814
23 23 branch: foo
24 24 tag: tip
25 25 user: test
26 26 date: Thu Jan 01 00:00:00 1970 +0000
27 27 summary: change a
28 28
29 29 changeset: 0:1f0dee641bb7
30 30 user: test
31 31 date: Thu Jan 01 00:00:00 1970 +0000
32 32 summary: add a
33 33
34 34 $ hg --cwd clone parents
35 35 changeset: 1:cd2a86ecc814
36 36 branch: foo
37 37 tag: tip
38 38 user: test
39 39 date: Thu Jan 01 00:00:00 1970 +0000
40 40 summary: change a
41 41
42 42 $ cat clone/.hg/hgrc
43 43 [paths]
44 44 default = $TESTTMP/repo#foo
45 45
46 46 Changing original repo:
47 47
48 48 $ cd repo
49 49
50 50 $ echo >> a
51 51 $ hg ci -m 'new head of branch foo'
52 52
53 53 $ hg up -qC default
54 54 $ echo bar > bar
55 55 $ hg ci -qAm 'add bar'
56 56
57 57 $ hg log
58 58 changeset: 3:4cd725637392
59 59 tag: tip
60 60 parent: 0:1f0dee641bb7
61 61 user: test
62 62 date: Thu Jan 01 00:00:00 1970 +0000
63 63 summary: add bar
64 64
65 65 changeset: 2:faba9097cad4
66 66 branch: foo
67 67 user: test
68 68 date: Thu Jan 01 00:00:00 1970 +0000
69 69 summary: new head of branch foo
70 70
71 71 changeset: 1:cd2a86ecc814
72 72 branch: foo
73 73 user: test
74 74 date: Thu Jan 01 00:00:00 1970 +0000
75 75 summary: change a
76 76
77 77 changeset: 0:1f0dee641bb7
78 78 user: test
79 79 date: Thu Jan 01 00:00:00 1970 +0000
80 80 summary: add a
81 81
82 82 $ hg -q outgoing '../clone#foo'
83 83 2:faba9097cad4
84 84
85 85 $ hg -q push '../clone#foo'
86 86
87 87 $ hg --cwd ../clone heads
88 88 changeset: 2:faba9097cad4
89 89 branch: foo
90 90 tag: tip
91 91 user: test
92 92 date: Thu Jan 01 00:00:00 1970 +0000
93 93 summary: new head of branch foo
94 94
95 95 changeset: 0:1f0dee641bb7
96 96 user: test
97 97 date: Thu Jan 01 00:00:00 1970 +0000
98 98 summary: add a
99 99
100 100 $ cd ..
101 101
102 102 $ cd clone
103 103 $ hg rollback
104 104 repository tip rolled back to revision 1 (undo push)
105 105
106 106 $ hg -q incoming
107 107 2:faba9097cad4
108 108
109 109 $ hg -q pull
110 110
111 111 $ hg heads
112 112 changeset: 2:faba9097cad4
113 113 branch: foo
114 114 tag: tip
115 115 user: test
116 116 date: Thu Jan 01 00:00:00 1970 +0000
117 117 summary: new head of branch foo
118 118
119 119 changeset: 0:1f0dee641bb7
120 120 user: test
121 121 date: Thu Jan 01 00:00:00 1970 +0000
122 122 summary: add a
123 123
124 124 Pull should not have updated:
125 125
126 126 $ hg parents -q
127 127 1:cd2a86ecc814
128 128
129 129 Going back to the default branch:
130 130
131 131 $ hg up -C 0
132 132 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
133 133
134 134 $ hg parents
135 135 changeset: 0:1f0dee641bb7
136 136 user: test
137 137 date: Thu Jan 01 00:00:00 1970 +0000
138 138 summary: add a
139 139
140 140 No new revs, no update:
141 141
142 142 $ hg pull -qu
143 143
144 144 $ hg parents -q
145 145 0:1f0dee641bb7
146 146
147 147 $ hg rollback
148 148 repository tip rolled back to revision 1 (undo pull)
149 149
150 150 $ hg parents -q
151 151 0:1f0dee641bb7
152 152
153 153 Pull -u takes us back to branch foo:
154 154
155 155 $ hg pull -qu
156 156
157 157 $ hg parents
158 158 changeset: 2:faba9097cad4
159 159 branch: foo
160 160 tag: tip
161 161 user: test
162 162 date: Thu Jan 01 00:00:00 1970 +0000
163 163 summary: new head of branch foo
164 164
165 165 $ hg rollback
166 166 repository tip rolled back to revision 1 (undo pull)
167 167 working directory now based on revision 0
168 168
169 169 $ hg up -C 0
170 170 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
171 171
172 172 $ hg parents -q
173 173 0:1f0dee641bb7
174 174
175 175 $ hg heads -q
176 176 1:cd2a86ecc814
177 177 0:1f0dee641bb7
178 178
179 179 $ hg pull -qur default default
180 180
181 181 $ hg parents
182 182 changeset: 3:4cd725637392
183 183 tag: tip
184 184 parent: 0:1f0dee641bb7
185 185 user: test
186 186 date: Thu Jan 01 00:00:00 1970 +0000
187 187 summary: add bar
188 188
189 189 $ hg heads
190 190 changeset: 3:4cd725637392
191 191 tag: tip
192 192 parent: 0:1f0dee641bb7
193 193 user: test
194 194 date: Thu Jan 01 00:00:00 1970 +0000
195 195 summary: add bar
196 196
197 197 changeset: 2:faba9097cad4
198 198 branch: foo
199 199 user: test
200 200 date: Thu Jan 01 00:00:00 1970 +0000
201 201 summary: new head of branch foo
202 202
203 Test handling of invalid urls
203 204
205 $ hg id http://foo/?bar
206 abort: unsupported URL component: "bar"
207 [255]
General Comments 0
You need to be logged in to leave comments. Login now