##// END OF EJS Templates
httprepo: use caps instead of between for compat check...
Peter Arrenbrecht -
r13603:395a84f7 default
parent child Browse files
Show More
@@ -1,202 +1,211 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, url, util, wireproto
12 12 import os, urllib, urllib2, urlparse, 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 31 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
32 32 if query or frag:
33 33 raise util.Abort(_('unsupported URL component: "%s"') %
34 34 (query or frag))
35 35
36 36 # urllib cannot handle URLs with embedded user or passwd
37 37 self._url, authinfo = url.getauthinfo(path)
38 38
39 39 self.ui = ui
40 40 self.ui.debug('using %s\n' % self._url)
41 41
42 42 self.urlopener = url.opener(ui, authinfo)
43 43
44 44 def __del__(self):
45 45 for h in self.urlopener.handlers:
46 46 h.close()
47 47 if hasattr(h, "close_all"):
48 48 h.close_all()
49 49
50 50 def url(self):
51 51 return self.path
52 52
53 53 # look up capabilities only when needed
54 54
55 def _fetchcaps(self):
56 self.caps = set(self._call('capabilities').split())
57
55 58 def get_caps(self):
56 59 if self.caps is None:
57 60 try:
58 self.caps = set(self._call('capabilities').split())
61 self._fetchcaps()
59 62 except error.RepoError:
60 63 self.caps = set()
61 64 self.ui.debug('capabilities: %s\n' %
62 65 (' '.join(self.caps or ['none'])))
63 66 return self.caps
64 67
65 68 capabilities = property(get_caps)
66 69
67 70 def lock(self):
68 71 raise util.Abort(_('operation not supported over http'))
69 72
70 73 def _callstream(self, cmd, **args):
71 74 if cmd == 'pushkey':
72 75 args['data'] = ''
73 76 data = args.pop('data', None)
74 77 headers = args.pop('headers', {})
75 78 self.ui.debug("sending %s command\n" % cmd)
76 79 q = [('cmd', cmd)] + sorted(args.items())
77 80 qs = '?%s' % urllib.urlencode(q)
78 81 cu = "%s%s" % (self._url, qs)
79 82 req = urllib2.Request(cu, data, headers)
80 83 if data is not None:
81 84 # len(data) is broken if data doesn't fit into Py_ssize_t
82 85 # add the header ourself to avoid OverflowError
83 86 size = data.__len__()
84 87 self.ui.debug("sending %s bytes\n" % size)
85 88 req.add_unredirected_header('Content-Length', '%d' % size)
86 89 try:
87 90 resp = self.urlopener.open(req)
88 91 except urllib2.HTTPError, inst:
89 92 if inst.code == 401:
90 93 raise util.Abort(_('authorization failed'))
91 94 raise
92 95 except httplib.HTTPException, inst:
93 96 self.ui.debug('http error while sending %s command\n' % cmd)
94 97 self.ui.traceback()
95 98 raise IOError(None, inst)
96 99 except IndexError:
97 100 # this only happens with Python 2.3, later versions raise URLError
98 101 raise util.Abort(_('http error, possibly caused by proxy setting'))
99 102 # record the url we got redirected to
100 103 resp_url = resp.geturl()
101 104 if resp_url.endswith(qs):
102 105 resp_url = resp_url[:-len(qs)]
103 106 if self._url.rstrip('/') != resp_url.rstrip('/'):
104 107 self.ui.status(_('real URL is %s\n') % resp_url)
105 108 self._url = resp_url
106 109 try:
107 110 proto = resp.getheader('content-type')
108 111 except AttributeError:
109 112 proto = resp.headers['content-type']
110 113
111 114 safeurl = url.hidepassword(self._url)
112 115 # accept old "text/plain" and "application/hg-changegroup" for now
113 116 if not (proto.startswith('application/mercurial-') or
114 117 proto.startswith('text/plain') or
115 118 proto.startswith('application/hg-changegroup')):
116 119 self.ui.debug("requested URL: '%s'\n" % url.hidepassword(cu))
117 120 raise error.RepoError(
118 121 _("'%s' does not appear to be an hg repository:\n"
119 122 "---%%<--- (%s)\n%s\n---%%<---\n")
120 123 % (safeurl, proto, resp.read()))
121 124
122 125 if proto.startswith('application/mercurial-'):
123 126 try:
124 127 version = proto.split('-', 1)[1]
125 128 version_info = tuple([int(n) for n in version.split('.')])
126 129 except ValueError:
127 130 raise error.RepoError(_("'%s' sent a broken Content-Type "
128 131 "header (%s)") % (safeurl, proto))
129 132 if version_info > (0, 1):
130 133 raise error.RepoError(_("'%s' uses newer protocol %s") %
131 134 (safeurl, version))
132 135
133 136 return resp
134 137
135 138 def _call(self, cmd, **args):
136 139 fp = self._callstream(cmd, **args)
137 140 try:
138 141 return fp.read()
139 142 finally:
140 143 # if using keepalive, allow connection to be reused
141 144 fp.close()
142 145
143 146 def _callpush(self, cmd, cg, **args):
144 147 # have to stream bundle to a temp file because we do not have
145 148 # http 1.1 chunked transfer.
146 149
147 150 type = ""
148 151 types = self.capable('unbundle')
149 152 # servers older than d1b16a746db6 will send 'unbundle' as a
150 153 # boolean capability
151 154 try:
152 155 types = types.split(',')
153 156 except AttributeError:
154 157 types = [""]
155 158 if types:
156 159 for x in types:
157 160 if x in changegroup.bundletypes:
158 161 type = x
159 162 break
160 163
161 164 tempname = changegroup.writebundle(cg, None, type)
162 165 fp = url.httpsendfile(self.ui, tempname, "rb")
163 166 headers = {'Content-Type': 'application/mercurial-0.1'}
164 167
165 168 try:
166 169 try:
167 170 r = self._call(cmd, data=fp, headers=headers, **args)
168 171 return r.split('\n', 1)
169 172 except socket.error, err:
170 173 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
171 174 raise util.Abort(_('push failed: %s') % err.args[1])
172 175 raise util.Abort(err.args[1])
173 176 finally:
174 177 fp.close()
175 178 os.unlink(tempname)
176 179
177 180 def _abort(self, exception):
178 181 raise exception
179 182
180 183 def _decompress(self, stream):
181 184 return util.chunkbuffer(zgenerator(stream))
182 185
183 186 class httpsrepository(httprepository):
184 187 def __init__(self, ui, path):
185 188 if not url.has_https:
186 189 raise util.Abort(_('Python support for SSL and HTTPS '
187 190 'is not installed'))
188 191 httprepository.__init__(self, ui, path)
189 192
190 193 def instance(ui, path, create):
191 194 if create:
192 195 raise util.Abort(_('cannot create new http repository'))
193 196 try:
194 197 if path.startswith('https:'):
195 198 inst = httpsrepository(ui, path)
196 199 else:
197 200 inst = httprepository(ui, path)
201 try:
202 # Try to do useful work when checking compatibility.
203 # Usually saves a roundtrip since we want the caps anyway.
204 inst._fetchcaps()
205 except error.RepoError:
206 # No luck, try older compatibility check.
198 207 inst.between([(nullid, nullid)])
199 208 return inst
200 209 except error.RepoError:
201 210 ui.note('(falling back to static-http)\n')
202 211 return statichttprepo.instance(ui, "static-" + path, create)
@@ -1,124 +1,119 b''
1 1
2 2 $ hg init a
3 3 $ cd a
4 4 $ echo a > a
5 5 $ hg ci -Ama -d '1123456789 0'
6 6 adding a
7 7 $ hg --config server.uncompressed=True serve -p $HGPORT -d --pid-file=hg.pid
8 8 $ cat hg.pid >> $DAEMON_PIDS
9 9 $ cd ..
10 10 $ ("$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log 2>&1 </dev/null &
11 11 $ echo $! > proxy.pid)
12 12 $ cat proxy.pid >> $DAEMON_PIDS
13 13 $ sleep 2
14 14
15 15 url for proxy, stream
16 16
17 17 $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone --uncompressed http://localhost:$HGPORT/ b
18 18 streaming all changes
19 19 3 files to transfer, 303 bytes of data
20 20 transferred * bytes in * seconds (*B/sec) (glob)
21 21 updating to branch default
22 22 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 23 $ cd b
24 24 $ hg verify
25 25 checking changesets
26 26 checking manifests
27 27 crosschecking files in changesets and manifests
28 28 checking files
29 29 1 files, 1 changesets, 1 total revisions
30 30 $ cd ..
31 31
32 32 url for proxy, pull
33 33
34 34 $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone http://localhost:$HGPORT/ b-pull
35 35 requesting all changes
36 36 adding changesets
37 37 adding manifests
38 38 adding file changes
39 39 added 1 changesets with 1 changes to 1 files
40 40 updating to branch default
41 41 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 42 $ cd b-pull
43 43 $ hg verify
44 44 checking changesets
45 45 checking manifests
46 46 crosschecking files in changesets and manifests
47 47 checking files
48 48 1 files, 1 changesets, 1 total revisions
49 49 $ cd ..
50 50
51 51 host:port for proxy
52 52
53 53 $ http_proxy=localhost:$HGPORT1 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ c
54 54 requesting all changes
55 55 adding changesets
56 56 adding manifests
57 57 adding file changes
58 58 added 1 changesets with 1 changes to 1 files
59 59 updating to branch default
60 60 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
61 61
62 62 proxy url with user name and password
63 63
64 64 $ http_proxy=http://user:passwd@localhost:$HGPORT1 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ d
65 65 requesting all changes
66 66 adding changesets
67 67 adding manifests
68 68 adding file changes
69 69 added 1 changesets with 1 changes to 1 files
70 70 updating to branch default
71 71 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
72 72
73 73 url with user name and password
74 74
75 75 $ http_proxy=http://user:passwd@localhost:$HGPORT1 hg clone --config http_proxy.always=True http://user:passwd@localhost:$HGPORT/ e
76 76 requesting all changes
77 77 adding changesets
78 78 adding manifests
79 79 adding file changes
80 80 added 1 changesets with 1 changes to 1 files
81 81 updating to branch default
82 82 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
83 83
84 84 bad host:port for proxy
85 85
86 86 $ http_proxy=localhost:$HGPORT2 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ f
87 87 abort: error: Connection refused
88 88 [255]
89 89
90 90 do not use the proxy if it is in the no list
91 91
92 92 $ http_proxy=localhost:$HGPORT1 hg clone --config http_proxy.no=localhost http://localhost:$HGPORT/ g
93 93 requesting all changes
94 94 adding changesets
95 95 adding manifests
96 96 adding file changes
97 97 added 1 changesets with 1 changes to 1 files
98 98 updating to branch default
99 99 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 100 $ cat proxy.log
101 * - - [*] "GET http://localhost:$HGPORT/?cmd=between&pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
102 101 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
103 102 * - - [*] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - (glob)
104 * - - [*] "GET http://localhost:$HGPORT/?cmd=between&pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
103 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
105 104 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
106 105 * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
106 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
107 107 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
108 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
109 * - - [*] "GET http://localhost:$HGPORT/?cmd=between&pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
110 108 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
111 109 * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
110 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
112 111 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
113 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
114 * - - [*] "GET http://localhost:$HGPORT/?cmd=between&pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
115 112 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
116 113 * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
114 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
117 115 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
118 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
119 * - - [*] "GET http://localhost:$HGPORT/?cmd=between&pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
120 116 * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
121 117 * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
122 * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
123 118 * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
124 119
@@ -1,45 +1,45 b''
1 1
2 2 $ cat <<EOF >> $HGRCPATH
3 3 > [extensions]
4 4 > schemes=
5 5 >
6 6 > [schemes]
7 7 > l = http://localhost:$HGPORT/
8 8 > parts = http://{1}:$HGPORT/
9 9 > z = file:\$PWD/
10 10 > EOF
11 11 $ hg init test
12 12 $ cd test
13 13 $ echo a > a
14 14 $ hg ci -Am initial
15 15 adding a
16 16 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
17 17 $ cat hg.pid >> $DAEMON_PIDS
18 18 $ hg incoming l://
19 19 comparing with l://
20 20 searching for changes
21 21 no changes found
22 22 [1]
23 23
24 24 check that {1} syntax works
25 25
26 26 $ hg incoming --debug parts://localhost
27 27 using http://localhost:$HGPORT/
28 sending between command
28 sending capabilities command
29 29 comparing with parts://localhost
30 30 sending heads command
31 31 searching for changes
32 32 no changes found
33 33 [1]
34 34
35 35 check that paths are expanded
36 36
37 37 $ PWD=`pwd` hg incoming z://
38 38 comparing with z://
39 39 searching for changes
40 40 no changes found
41 41 [1]
42 42
43 43 errors
44 44
45 45 $ cat errors.log
General Comments 0
You need to be logged in to leave comments. Login now